diff --git a/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc index 979a2d62513f..9773925138af 100644 --- a/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc +++ b/Documentation/ABI/testing/sysfs-driver-intel-m10-bmc @@ -13,3 +13,24 @@ Contact: Xu Yilun Description: Read only. Returns the firmware version of Intel MAX10 BMC chip. Format: "0x%x". + +What: /sys/bus/spi/devices/.../mac_address +Date: January 2021 +KernelVersion: 5.12 +Contact: Russ Weight +Description: Read only. Returns the first MAC address in a block + of sequential MAC addresses assigned to the board + that is managed by the Intel MAX10 BMC. It is stored in + FLASH storage and is mirrored in the MAX10 BMC register + space. + Format: "%02x:%02x:%02x:%02x:%02x:%02x". + +What: /sys/bus/spi/devices/.../mac_count +Date: January 2021 +KernelVersion: 5.12 +Contact: Russ Weight +Description: Read only. Returns the number of sequential MAC + addresses assigned to the board managed by the Intel + MAX10 BMC. This value is stored in FLASH and is mirrored + in the MAX10 BMC register space. + Format: "%u". diff --git a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop index fd2ac02bc5bd..4989ab266682 100644 --- a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop +++ b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop @@ -1,11 +1,11 @@ -What: /sys/devices/platform/ideapad/camera_power +What: /sys/bus/platform/devices/VPC2004:*/camera_power Date: Dec 2010 KernelVersion: 2.6.37 Contact: "Ike Panhc " Description: Control the power of camera module. 1 means on, 0 means off. -What: /sys/devices/platform/ideapad/fan_mode +What: /sys/bus/platform/devices/VPC2004:*/fan_mode Date: June 2012 KernelVersion: 3.6 Contact: "Maxim Mikityanskiy " @@ -18,7 +18,7 @@ Description: * 2 -> Dust Cleaning * 4 -> Efficient Thermal Dissipation Mode -What: /sys/devices/platform/ideapad/touchpad +What: /sys/bus/platform/devices/VPC2004:*/touchpad Date: May 2017 KernelVersion: 4.13 Contact: "Ritesh Raj Sarraf " @@ -27,7 +27,16 @@ Description: * 1 -> Switched On * 0 -> Switched Off -What: /sys/bus/pci/devices///VPC2004:00/fn_lock +What: /sys/bus/platform/devices/VPC2004:*/conservation_mode +Date: Aug 2017 +KernelVersion: 4.14 +Contact: platform-driver-x86@vger.kernel.org +Description: + Controls whether the conservation mode is enabled or not. + This feature limits the maximum battery charge percentage to + around 50-60% in order to prolong the lifetime of the battery. + +What: /sys/bus/platform/devices/VPC2004:*/fn_lock Date: May 2018 KernelVersion: 4.18 Contact: "Oleg Keri " @@ -41,3 +50,12 @@ Description: # echo "0" > \ /sys/bus/pci/devices/0000:00:1f.0/PNP0C09:00/VPC2004:00/fn_lock + +What: /sys/bus/platform/devices/VPC2004:*/usb_charging +Date: Feb 2021 +KernelVersion: 5.12 +Contact: platform-driver-x86@vger.kernel.org +Description: + Controls whether the "always on USB charging" feature is + enabled or not. This feature enables charging USB devices + even if the computer is not turned on. diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst index 5fe1ade88c17..91fd6846ce17 100644 --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst @@ -51,6 +51,7 @@ detailed description): - UWB enable and disable - LCD Shadow (PrivacyGuard) enable and disable - Lap mode sensor + - Setting keyboard language A compatibility table by model and feature is maintained on the web site, http://ibm-acpi.sf.net/. I appreciate any success or failure @@ -1466,6 +1467,30 @@ Sysfs notes rfkill controller switch "tpacpi_uwb_sw": refer to Documentation/driver-api/rfkill.rst for details. + +Setting keyboard language +------------------------- + +sysfs: keyboard_lang + +This feature is used to set keyboard language to ECFW using ASL interface. +Fewer thinkpads models like T580 , T590 , T15 Gen 1 etc.. has "=", "(', +")" numeric keys, which are not displaying correctly, when keyboard language +is other than "english". This is because the default keyboard language in ECFW +is set as "english". Hence using this sysfs, user can set the correct keyboard +language to ECFW and then these key's will work correctly. + +Example of command to set keyboard language is mentioned below:: + + echo jp > /sys/devices/platform/thinkpad_acpi/keyboard_lang + +Text corresponding to keyboard layout to be set in sysfs are: be(Belgian), +cz(Czech), da(Danish), de(German), en(English), es(Spain), et(Estonian), +fr(French), fr-ch(French(Switzerland)), hu(Hungarian), it(Italy), jp (Japan), +nl(Dutch), nn(Norway), pl(Polish), pt(portugese), sl(Slovenian), sv(Sweden), +tr(Turkey) + + Adaptive keyboard ----------------- diff --git a/Documentation/block/inline-encryption.rst b/Documentation/block/inline-encryption.rst index e75151e467d3..7f9b40d6b416 100644 --- a/Documentation/block/inline-encryption.rst +++ b/Documentation/block/inline-encryption.rst @@ -182,8 +182,9 @@ API presented to device drivers A :c:type:``struct blk_keyslot_manager`` should be set up by device drivers in the ``request_queue`` of the device. The device driver needs to call -``blk_ksm_init`` on the ``blk_keyslot_manager``, which specifying the number of -keyslots supported by the hardware. +``blk_ksm_init`` (or its resource-managed variant ``devm_blk_ksm_init``) on the +``blk_keyslot_manager``, while specifying the number of keyslots supported by +the hardware. The device driver also needs to tell the KSM how to actually manipulate the IE hardware in the device to do things like programming the crypto key into @@ -202,10 +203,9 @@ needs each and every of its keyslots to be reprogrammed with the key it "should have" at the point in time when the function is called. This is useful e.g. if a device loses all its keys on runtime power down/up. -``blk_ksm_destroy`` should be called to free up all resources used by a keyslot -manager upon ``blk_ksm_init``, once the ``blk_keyslot_manager`` is no longer -needed. - +If the driver used ``blk_ksm_init`` instead of ``devm_blk_ksm_init``, then +``blk_ksm_destroy`` should be called to free up all resources used by a +``blk_keyslot_manager`` once it is no longer needed. Layered Devices =============== diff --git a/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml b/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml new file mode 100644 index 000000000000..64ffff460026 --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/auxdisplay/holtek,ht16k33.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Holtek HT16K33 RAM mapping 16*8 LED controller with keyscan + +maintainers: + - Robin van der Gracht + +allOf: + - $ref: "/schemas/input/matrix-keymap.yaml#" + +properties: + compatible: + const: holtek,ht16k33 + + reg: + maxItems: 1 + + refresh-rate-hz: + maxItems: 1 + description: Display update interval in Hertz + + interrupts: + maxItems: 1 + + debounce-delay-ms: + maxItems: 1 + description: Debouncing interval time in milliseconds + + linux,keymap: true + + linux,no-autorepeat: + description: Disable keyrepeat + + default-brightness-level: + minimum: 1 + maximum: 16 + default: 16 + description: Initial brightness level + +required: + - compatible + - reg + - refresh-rate-hz + +additionalProperties: false + +examples: + - | + #include + #include + i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + ht16k33: ht16k33@70 { + compatible = "holtek,ht16k33"; + reg = <0x70>; + refresh-rate-hz = <20>; + interrupt-parent = <&gpio4>; + interrupts = <5 (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)>; + debounce-delay-ms = <50>; + linux,keymap = , + , + , + , + , + , + , + , + , + ; + }; + }; diff --git a/Documentation/devicetree/bindings/display/ht16k33.txt b/Documentation/devicetree/bindings/display/ht16k33.txt deleted file mode 100644 index d5a8b070b467..000000000000 --- a/Documentation/devicetree/bindings/display/ht16k33.txt +++ /dev/null @@ -1,40 +0,0 @@ -Holtek ht16k33 RAM mapping 16*8 LED controller driver with keyscan -------------------------------------------------------------------------------- - -Required properties: -- compatible: "holtek,ht16k33" -- reg: I2C slave address of the chip. -- interrupts: Interrupt specification for the key pressed interrupt. -- refresh-rate-hz: Display update interval in HZ. -- debounce-delay-ms: Debouncing interval time in milliseconds. -- linux,keymap: The keymap for keys as described in the binding - document (devicetree/bindings/input/matrix-keymap.txt). - -Optional properties: -- linux,no-autorepeat: Disable keyrepeat. -- default-brightness-level: Initial brightness level [0-15] (default: 15). - -Example: - -&i2c1 { - ht16k33: ht16k33@70 { - compatible = "holtek,ht16k33"; - reg = <0x70>; - refresh-rate-hz = <20>; - debounce-delay-ms = <50>; - interrupt-parent = <&gpio4>; - interrupts = <5 (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)>; - linux,keymap = < - MATRIX_KEY(2, 0, KEY_F6) - MATRIX_KEY(3, 0, KEY_F8) - MATRIX_KEY(4, 0, KEY_F10) - MATRIX_KEY(5, 0, KEY_F4) - MATRIX_KEY(6, 0, KEY_F2) - MATRIX_KEY(2, 1, KEY_F5) - MATRIX_KEY(3, 1, KEY_F7) - MATRIX_KEY(4, 1, KEY_F9) - MATRIX_KEY(5, 1, KEY_F3) - MATRIX_KEY(6, 1, KEY_F1) - >; - }; -}; diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index d5117c638b75..021d8ae42da3 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -95,9 +95,6 @@ properties: pattern: spd$ # These are special cases that don't conform to the above pattern. # Each requires a standard at24 model as fallback. - - items: - - const: rohm,br24t01 - - const: atmel,24c01 - items: - const: nxp,se97b - const: atmel,24c02 @@ -113,6 +110,12 @@ properties: - items: - const: renesas,r1ex24128 - const: atmel,24c128 + - items: + - const: rohm,br24g01 + - const: atmel,24c01 + - items: + - const: rohm,br24t01 + - const: atmel,24c01 label: description: Descriptive name of the EEPROM. diff --git a/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml b/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml new file mode 100644 index 000000000000..3bc8e73dfbf0 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/ti,tps23861.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI TPS23861 PoE PSE + +maintainers: + - Robert Marko + +description: | + The TPS23861 is a IEEE 802.3at Quad Port Power-over-Ethernet PSE Controller. + + Datasheets: + https://www.ti.com/lit/gpn/tps23861 + + +properties: + compatible: + enum: + - ti,tps23861 + + reg: + maxItems: 1 + + shunt-resistor-micro-ohms: + description: The value of curent sense resistor in microohms. + default: 255000 + minimum: 250000 + maximum: 255000 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + tps23861@30 { + compatible = "ti,tps23861"; + reg = <0x30>; + shunt-resistor-micro-ohms = <255000>; + }; + }; diff --git a/Documentation/devicetree/bindings/i2c/i2c-sirf.txt b/Documentation/devicetree/bindings/i2c/i2c-sirf.txt deleted file mode 100644 index 2701eefb00f7..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-sirf.txt +++ /dev/null @@ -1,19 +0,0 @@ -I2C for SiRFprimaII platforms - -Required properties : -- compatible : Must be "sirf,prima2-i2c" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: interrupt number to the cpu. - -Optional properties: -- clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz. - The absence of the property indicates the default frequency 100 kHz. - -Examples : - -i2c0: i2c@b00e0000 { - compatible = "sirf,prima2-i2c"; - reg = <0xb00e0000 0x10000>; - interrupts = <24>; -}; diff --git a/Documentation/devicetree/bindings/i2c/i2c-stu300.txt b/Documentation/devicetree/bindings/i2c/i2c-stu300.txt deleted file mode 100644 index bd81a482634f..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-stu300.txt +++ /dev/null @@ -1,15 +0,0 @@ -ST Microelectronics DDC I2C - -Required properties : -- compatible : Must be "st,ddci2c" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: interrupt number to the cpu. -- #address-cells = <1>; -- #size-cells = <0>; - -Optional properties: -- Child nodes conforming to i2c bus binding - -Examples : - diff --git a/Documentation/devicetree/bindings/i2c/i2c-zx2967.txt b/Documentation/devicetree/bindings/i2c/i2c-zx2967.txt deleted file mode 100644 index cb806d1ae4c9..000000000000 --- a/Documentation/devicetree/bindings/i2c/i2c-zx2967.txt +++ /dev/null @@ -1,22 +0,0 @@ -ZTE zx2967 I2C controller - -Required properties: - - compatible: must be "zte,zx296718-i2c" - - reg: physical address and length of the device registers - - interrupts: a single interrupt specifier - - clocks: clock for the device - - #address-cells: should be <1> - - #size-cells: should be <0> - - clock-frequency: the desired I2C bus clock frequency. - -Examples: - - i2c@112000 { - compatible = "zte,zx296718-i2c"; - reg = <0x00112000 0x1000>; - interrupts = ; - clocks = <&osc24m>; - #address-cells = <1> - #size-cells = <0>; - clock-frequency = <1600000>; - }; diff --git a/Documentation/devicetree/bindings/i2c/marvell,mv64xxx-i2c.yaml b/Documentation/devicetree/bindings/i2c/marvell,mv64xxx-i2c.yaml index 5b5ae402f97a..eb72dd571def 100644 --- a/Documentation/devicetree/bindings/i2c/marvell,mv64xxx-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/marvell,mv64xxx-i2c.yaml @@ -18,21 +18,14 @@ properties: - const: allwinner,sun4i-a10-i2c - const: allwinner,sun6i-a31-i2c - items: - - const: allwinner,sun8i-a23-i2c + - enum: + - allwinner,sun8i-a23-i2c + - allwinner,sun8i-a83t-i2c + - allwinner,sun50i-a64-i2c + - allwinner,sun50i-a100-i2c + - allwinner,sun50i-h6-i2c + - allwinner,sun50i-h616-i2c - const: allwinner,sun6i-a31-i2c - - items: - - const: allwinner,sun8i-a83t-i2c - - const: allwinner,sun6i-a31-i2c - - items: - - const: allwinner,sun50i-a64-i2c - - const: allwinner,sun6i-a31-i2c - - items: - - const: allwinner,sun50i-a100-i2c - - const: allwinner,sun6i-a31-i2c - - items: - - const: allwinner,sun50i-h6-i2c - - const: allwinner,sun6i-a31-i2c - - const: marvell,mv64xxx-i2c - const: marvell,mv78230-i2c - const: marvell,mv78230-a0-i2c diff --git a/Documentation/devicetree/bindings/i2c/renesas,i2c.txt b/Documentation/devicetree/bindings/i2c/renesas,i2c.txt index 96d869ac3839..5762d2d1ab9c 100644 --- a/Documentation/devicetree/bindings/i2c/renesas,i2c.txt +++ b/Documentation/devicetree/bindings/i2c/renesas,i2c.txt @@ -26,6 +26,7 @@ Required properties: "renesas,i2c-r8a77980" if the device is a part of a R8A77980 SoC. "renesas,i2c-r8a77990" if the device is a part of a R8A77990 SoC. "renesas,i2c-r8a77995" if the device is a part of a R8A77995 SoC. + "renesas,i2c-r8a779a0" if the device is a part of a R8A779A0 SoC. "renesas,rcar-gen1-i2c" for a generic R-Car Gen1 compatible device. "renesas,rcar-gen2-i2c" for a generic R-Car Gen2 or RZ/G1 compatible device. diff --git a/Documentation/devicetree/bindings/mfd/bd9571mwv.txt b/Documentation/devicetree/bindings/mfd/bd9571mwv.txt index 8c4678650d1a..1d6413e96c37 100644 --- a/Documentation/devicetree/bindings/mfd/bd9571mwv.txt +++ b/Documentation/devicetree/bindings/mfd/bd9571mwv.txt @@ -1,7 +1,7 @@ -* ROHM BD9571MWV Power Management Integrated Circuit (PMIC) bindings +* ROHM BD9571MWV/BD9574MWF Power Management Integrated Circuit (PMIC) bindings Required properties: - - compatible : Should be "rohm,bd9571mwv". + - compatible : Should be "rohm,bd9571mwv" or "rohm,bd9574mwf". - reg : I2C slave address. - interrupts : The interrupt line the device is connected to. - interrupt-controller : Marks the device node as an interrupt controller. diff --git a/Documentation/devicetree/bindings/mfd/ene-kb930.yaml b/Documentation/devicetree/bindings/mfd/ene-kb930.yaml new file mode 100644 index 000000000000..06ed9ec8f4bb --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/ene-kb930.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/ene-kb930.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ENE KB930 Embedded Controller bindings + +description: | + This binding describes the ENE KB930 Embedded Controller attached to an + I2C bus. + +maintainers: + - Dmitry Osipenko + +properties: + compatible: + items: + - enum: + - acer,a500-iconia-ec # Acer A500 Iconia tablet device + - const: ene,kb930 + reg: + maxItems: 1 + + monitored-battery: true + power-supplies: true + system-power-controller: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + battery: battery-cell { + compatible = "simple-battery"; + charge-full-design-microamp-hours = <3260000>; + energy-full-design-microwatt-hours = <24000000>; + operating-range-celsius = <0 40>; + }; + + mains: ac-adapter { + compatible = "gpio-charger"; + charger-type = "mains"; + gpios = <&gpio 125 0>; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + embedded-controller@58 { + compatible = "acer,a500-iconia-ec", "ene,kb930"; + reg = <0x58>; + + system-power-controller; + + monitored-battery = <&battery>; + power-supplies = <&mains>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml b/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml index d08e8fe76446..5a1e8d21f7a0 100644 --- a/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml @@ -83,8 +83,9 @@ properties: 2 - scaled voltage based on an optional resistor divider and optional offset 3 - pre-scaled 16-bit voltage value + 4 - fan tach input to report RPM's $ref: /schemas/types.yaml#/definitions/uint32 - enum: [0, 1, 2, 3] + enum: [0, 1, 2, 3, 4] gw,voltage-divider-ohms: description: Values of resistors for divider on raw ADC input diff --git a/Documentation/devicetree/bindings/mfd/iqs62x.yaml b/Documentation/devicetree/bindings/mfd/iqs62x.yaml index 541b06d80e73..044cd7542c2b 100644 --- a/Documentation/devicetree/bindings/mfd/iqs62x.yaml +++ b/Documentation/devicetree/bindings/mfd/iqs62x.yaml @@ -93,7 +93,7 @@ examples: pwmleds { compatible = "pwm-leds"; - panel { + led-1 { pwms = <&iqs620a_pwm 0 1000000>; max-brightness = <255>; }; diff --git a/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml b/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml index e82c9a07b6fb..e75b3a8ba816 100644 --- a/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml +++ b/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml @@ -26,6 +26,8 @@ properties: - const: allwinner,sun9i-a80-mmc - const: allwinner,sun50i-a64-emmc - const: allwinner,sun50i-a64-mmc + - const: allwinner,sun50i-a100-emmc + - const: allwinner,sun50i-a100-mmc - items: - const: allwinner,sun8i-a83t-mmc - const: allwinner,sun7i-a20-mmc @@ -47,6 +49,12 @@ properties: - items: - const: allwinner,sun50i-h6-mmc - const: allwinner,sun50i-a64-mmc + - items: + - const: allwinner,sun50i-h616-emmc + - const: allwinner,sun50i-a100-emmc + - items: + - const: allwinner,sun50i-h616-mmc + - const: allwinner,sun50i-a100-mmc reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/mmc/arm,pl18x.yaml b/Documentation/devicetree/bindings/mmc/arm,pl18x.yaml new file mode 100644 index 000000000000..47595cb483be --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/arm,pl18x.yaml @@ -0,0 +1,223 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/arm,pl18x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ARM PrimeCell MultiMedia Card Interface (MMCI) PL180 and PL181 + +maintainers: + - Linus Walleij + - Ulf Hansson + +description: + The ARM PrimeCells MMCI PL180 and PL181 provides an interface for + reading and writing to MultiMedia and SD cards alike. Over the years + vendors have use the VHDL code from ARM to create derivative MMC/SD/SDIO + host controllers with very similar characteristics. + +allOf: + - $ref: /schemas/arm/primecell.yaml# + - $ref: mmc-controller.yaml# + +# We need a select here so we don't match all nodes with 'arm,primecell' +select: + properties: + compatible: + contains: + enum: + - arm,pl180 + - arm,pl181 + - arm,pl18x + required: + - compatible + +properties: + compatible: + oneOf: + - description: The first version of the block, simply called + PL180 and found in the ARM Integrator IM/PD1 logic module. + items: + - const: arm,pl180 + - const: arm,primecell + - description: The improved version of the block, found in the + ARM Versatile and later reference designs. Further revisions + exist but get detected at runtime by reading some magic numbers + in the PrimeCell ID registers. + items: + - const: arm,pl181 + - const: arm,primecell + - description: Wildcard entry that will let the operating system + inspect the PrimeCell ID registers to determine which hardware + variant of PL180 or PL181 this is. + items: + - const: arm,pl18x + - const: arm,primecell + + clocks: + description: One or two clocks, the "apb_pclk" and the "MCLK" + which is the core block clock. The names are not compulsory. + minItems: 1 + maxItems: 2 + + power-domains: true + + resets: + maxItems: 1 + + reg: + description: the MMIO memory window must be exactly 4KB (0x1000) and the + layout should provide the PrimeCell ID registers so that the device can + be discovered. On ST Micro variants, a second register window may be + defined if a delay block is present and used for tuning. + + interrupts: + description: The first interrupt is the command interrupt and corresponds + to the event at the end of a command. The second interrupt is the + PIO (polled I/O) interrupt and occurs when the FIFO needs to be + emptied as part of a bulk read from the card. Some variants have these + two interrupts wired into the same line (logic OR) and in that case + only one interrupt may be provided. + minItems: 1 + maxItems: 2 + + st,sig-dir-dat0: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, bus signal direction pins used for + DAT[0]. + + st,sig-dir-dat2: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, bus signal direction pins used for + DAT[2]. + + st,sig-dir-dat31: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, bus signal direction pins used for + DAT[3] and DAT[1]. + + st,sig-dir-dat74: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, bus signal direction pins used for + DAT[7] and DAT[4]. + + st,sig-dir-cmd: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, CMD signal direction used for + pin CMD. + + st,sig-pin-fbclk: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, feedback clock FBCLK signal pin + in use. + + st,sig-dir: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, signal direction polarity used for + pins CMD, DAT[0], DAT[1], DAT[2] and DAT[3]. + + st,neg-edge: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, data and command phase relation, + generated on the sd clock falling edge. + + st,use-ckin: + $ref: /schemas/types.yaml#/definitions/flag + description: ST Micro-specific property, use CKIN pin from an external + driver to sample the receive data (for example with a voltage switch + transceiver). + + st,cmd-gpios: + maxItems: 1 + description: + The GPIO matching the CMD pin. + + st,ck-gpios: + maxItems: 1 + description: + The GPIO matching the CK pin. + + st,ckin-gpios: + maxItems: 1 + description: + The GPIO matching the CKIN pin. + +dependencies: + st,cmd-gpios: [ "st,use-ckin" ] + st,ck-gpios: [ "st,use-ckin" ] + st,ckin-gpios: [ "st,use-ckin" ] + +unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + +examples: + - | + #include + #include + + mmc@5000 { + compatible = "arm,pl180", "arm,primecell"; + reg = <0x5000 0x1000>; + interrupts-extended = <&vic 22 &sic 1>; + clocks = <&xtal24mhz>, <&pclk>; + clock-names = "mclk", "apb_pclk"; + }; + + mmc@80126000 { + compatible = "arm,pl18x", "arm,primecell"; + reg = <0x80126000 0x1000>; + interrupts = <0 60 IRQ_TYPE_LEVEL_HIGH>; + dmas = <&dma 29 0 0x2>, <&dma 29 0 0x0>; + dma-names = "rx", "tx"; + clocks = <&prcc_kclk 1 5>, <&prcc_pclk 1 5>; + clock-names = "sdi", "apb_pclk"; + max-frequency = <100000000>; + bus-width = <4>; + cap-sd-highspeed; + cap-mmc-highspeed; + cd-gpios = <&gpio2 31 0x4>; + st,sig-dir-dat0; + st,sig-dir-dat2; + st,sig-dir-cmd; + st,sig-pin-fbclk; + vmmc-supply = <&ab8500_ldo_aux3_reg>; + vqmmc-supply = <&vmmci>; + }; + + mmc@101f6000 { + compatible = "arm,pl18x", "arm,primecell"; + reg = <0x101f6000 0x1000>; + clocks = <&sdiclk>, <&pclksdi>; + clock-names = "mclk", "apb_pclk"; + interrupt-parent = <&vica>; + interrupts = <22>; + max-frequency = <400000>; + bus-width = <4>; + cap-mmc-highspeed; + cap-sd-highspeed; + full-pwr-cycle; + st,sig-dir-dat0; + st,sig-dir-dat2; + st,sig-dir-dat31; + st,sig-dir-cmd; + st,sig-pin-fbclk; + vmmc-supply = <&vmmc_regulator>; + }; + + mmc@52007000 { + compatible = "arm,pl18x", "arm,primecell"; + arm,primecell-periphid = <0x10153180>; + reg = <0x52007000 0x1000>; + interrupts = <49>; + interrupt-names = "cmd_irq"; + clocks = <&rcc 0>; + clock-names = "apb_pclk"; + resets = <&rcc 1>; + cap-sd-highspeed; + cap-mmc-highspeed; + max-frequency = <120000000>; + }; diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt index ed1456f5c94d..c51a62d751dc 100644 --- a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt @@ -12,6 +12,7 @@ Required Properties: - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SoC. Must provide a second register area and marvell,pad-type. - "marvell,armada-ap806-sdhci": For controllers on Armada AP806. + - "marvell,armada-ap807-sdhci": For controllers on Armada AP807. - "marvell,armada-cp110-sdhci": For controllers on Armada CP110. - clocks: diff --git a/Documentation/devicetree/bindings/mmc/mmci.txt b/Documentation/devicetree/bindings/mmc/mmci.txt deleted file mode 100644 index 4ec921e4bf34..000000000000 --- a/Documentation/devicetree/bindings/mmc/mmci.txt +++ /dev/null @@ -1,74 +0,0 @@ -* ARM PrimeCell MultiMedia Card Interface (MMCI) PL180/1 - -The ARM PrimeCell MMCI PL180 and PL181 provides an interface for -reading and writing to MultiMedia and SD cards alike. - -This file documents differences between the core properties described -by mmc.txt and the properties used by the mmci driver. Using "st" as -the prefix for a property, indicates support by the ST Micro variant. - -Required properties: -- compatible : contains "arm,pl18x", "arm,primecell". -- vmmc-supply : phandle to the regulator device tree node, mentioned - as the VCC/VDD supply in the eMMC/SD specs. - -Optional properties: -- arm,primecell-periphid : contains the PrimeCell Peripheral ID, it overrides - the ID provided by the HW -- resets : phandle to internal reset line. - Should be defined for sdmmc variant. -- vqmmc-supply : phandle to the regulator device tree node, mentioned - as the VCCQ/VDD_IO supply in the eMMC/SD specs. -specific for ux500 variant: -- st,sig-dir-dat0 : bus signal direction pin used for DAT[0]. -- st,sig-dir-dat2 : bus signal direction pin used for DAT[2]. -- st,sig-dir-dat31 : bus signal direction pin used for DAT[3] and DAT[1]. -- st,sig-dir-dat74 : bus signal direction pin used for DAT[4] to DAT[7]. -- st,sig-dir-cmd : cmd signal direction pin used for CMD. -- st,sig-pin-fbclk : feedback clock signal pin used. - -specific for sdmmc variant: -- reg : a second base register may be defined if a delay - block is present and used for tuning. -- st,sig-dir : signal direction polarity used for cmd, dat0 dat123. -- st,neg-edge : data & command phase relation, generated on - sd clock falling edge. -- st,use-ckin : use ckin pin from an external driver to sample - the receive data (example: with voltage - switch transceiver). - -Deprecated properties: -- mmc-cap-mmc-highspeed : indicates whether MMC is high speed capable. -- mmc-cap-sd-highspeed : indicates whether SD is high speed capable. - -Example: - -sdi0_per1@80126000 { - compatible = "arm,pl18x", "arm,primecell"; - reg = <0x80126000 0x1000>; - interrupts = <0 60 IRQ_TYPE_LEVEL_HIGH>; - - dmas = <&dma 29 0 0x2>, /* Logical - DevToMem */ - <&dma 29 0 0x0>; /* Logical - MemToDev */ - dma-names = "rx", "tx"; - - clocks = <&prcc_kclk 1 5>, <&prcc_pclk 1 5>; - clock-names = "sdi", "apb_pclk"; - - max-frequency = <100000000>; - bus-width = <4>; - cap-sd-highspeed; - cap-mmc-highspeed; - cd-gpios = <&gpio2 31 0x4>; // 95 - st,sig-dir-dat0; - st,sig-dir-dat2; - st,sig-dir-cmd; - st,sig-pin-fbclk; - - vmmc-supply = <&ab8500_ldo_aux3_reg>; - vqmmc-supply = <&vmmci>; - - pinctrl-names = "default", "sleep"; - pinctrl-0 = <&sdi0_default_mode>; - pinctrl-1 = <&sdi0_sleep_mode>; -}; diff --git a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml index 6bbf29b5c239..5908a717d2e8 100644 --- a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml +++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml @@ -59,6 +59,7 @@ properties: - renesas,sdhi-r8a77980 # R-Car V3H - renesas,sdhi-r8a77990 # R-Car E3 - renesas,sdhi-r8a77995 # R-Car D3 + - renesas,sdhi-r8a779a0 # R-Car V3U - const: renesas,rcar-gen3-sdhi # R-Car Gen3 or RZ/G2 reg: diff --git a/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml b/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml index 1ae945434c53..3a79e39253d2 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml +++ b/Documentation/devicetree/bindings/mmc/sdhci-am654.yaml @@ -15,12 +15,19 @@ allOf: properties: compatible: - enum: - - ti,am654-sdhci-5.1 - - ti,j721e-sdhci-8bit - - ti,j721e-sdhci-4bit - - ti,j7200-sdhci-8bit - - ti,j721e-sdhci-4bit + oneOf: + - const: ti,am654-sdhci-5.1 + - const: ti,j721e-sdhci-8bit + - const: ti,j721e-sdhci-4bit + - const: ti,j721e-sdhci-4bit + - const: ti,am64-sdhci-8bit + - const: ti,am64-sdhci-4bit + - items: + - const: ti,j7200-sdhci-8bit + - const: ti,j721e-sdhci-8bit + - items: + - const: ti,j7200-sdhci-4bit + - const: ti,j721e-sdhci-4bit reg: maxItems: 2 diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 3b602fd6180b..4c7fa6a4ed15 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -17,10 +17,11 @@ Required properties: "qcom,msm8916-sdhci", "qcom,sdhci-msm-v4" "qcom,msm8992-sdhci", "qcom,sdhci-msm-v4" "qcom,msm8996-sdhci", "qcom,sdhci-msm-v4" - "qcom,sm8250-sdhci", "qcom,sdhci-msm-v5" - "qcom,sdm845-sdhci", "qcom,sdhci-msm-v5" "qcom,qcs404-sdhci", "qcom,sdhci-msm-v5" "qcom,sc7180-sdhci", "qcom,sdhci-msm-v5"; + "qcom,sdm845-sdhci", "qcom,sdhci-msm-v5" + "qcom,sdx55-sdhci", "qcom,sdhci-msm-v5"; + "qcom,sm8250-sdhci", "qcom,sdhci-msm-v5" NOTE that some old device tree files may be floating around that only have the string "qcom,sdhci-msm-v4" without the SoC compatible string but doing that should be considered a deprecated practice. @@ -30,10 +31,12 @@ Required properties: - SD Core register map (required for controllers earlier than msm-v5) - CQE register map (Optional, CQE support is present on SDHC instance meant for eMMC and version v4.2 and above) + - Inline Crypto Engine register map (optional) - reg-names: When CQE register map is supplied, below reg-names are required - "hc" for Host controller register map - "core" for SD core register map - "cqhci" for CQE register map + - "ice" for Inline Crypto Engine register map (optional) - interrupts: Should contain an interrupt-specifiers for the interrupts: - Host controller interrupt (required) - pinctrl-names: Should contain only one value - "default". @@ -46,6 +49,7 @@ Required properties: "xo" - TCXO clock (optional) "cal" - reference clock for RCLK delay calibration (optional) "sleep" - sleep clock for RCLK delay calibration (optional) + "ice" - clock for Inline Crypto Engine (optional) - qcom,ddr-config: Certain chipsets and platforms require particular settings for the DDR_CONFIG register. Use this field to specify the register diff --git a/Documentation/devicetree/bindings/mmc/sdhci-sirf.txt b/Documentation/devicetree/bindings/mmc/sdhci-sirf.txt deleted file mode 100644 index dd6ed464bcb8..000000000000 --- a/Documentation/devicetree/bindings/mmc/sdhci-sirf.txt +++ /dev/null @@ -1,18 +0,0 @@ -* SiRFprimII/marco/atlas6 SDHCI Controller - -This file documents differences between the core properties in mmc.txt -and the properties used by the sdhci-sirf driver. - -Required properties: -- compatible: sirf,prima2-sdhc - -Optional properties: -- cd-gpios: card detect gpio, with zero flags. - -Example: - - sd0: sdhci@56000000 { - compatible = "sirf,prima2-sdhc"; - reg = <0xcd000000 0x100000>; - cd-gpios = <&gpio 6 0>; - }; diff --git a/Documentation/devicetree/bindings/mmc/zx-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/zx-dw-mshc.txt deleted file mode 100644 index 0f59bd5361f5..000000000000 --- a/Documentation/devicetree/bindings/mmc/zx-dw-mshc.txt +++ /dev/null @@ -1,31 +0,0 @@ -* ZTE specific extensions to the Synopsys Designware Mobile Storage - Host Controller - -The Synopsys designware mobile storage host controller is used to interface -a SoC with storage medium such as eMMC or SD/MMC cards. This file documents -differences between the core Synopsys dw mshc controller properties described -by synopsys-dw-mshc.txt and the properties used by the ZTE specific -extensions to the Synopsys Designware Mobile Storage Host Controller. - -Required Properties: - -* compatible: should be - - "zte,zx296718-dw-mshc": for ZX SoCs - -Example: - - mmc1: mmc@1110000 { - compatible = "zte,zx296718-dw-mshc"; - reg = <0x01110000 0x1000>; - interrupts = ; - fifo-depth = <32>; - data-addr = <0x200>; - fifo-watermark-aligned; - bus-width = <4>; - clock-frequency = <50000000>; - clocks = <&topcrm SD0_AHB>, <&topcrm SD0_WCLK>; - clock-names = "biu", "ciu"; - max-frequency = <50000000>; - cap-sdio-irq; - cap-sd-highspeed; - }; diff --git a/Documentation/devicetree/bindings/regulator/max8997-regulator.txt b/Documentation/devicetree/bindings/regulator/max8997-regulator.txt index 6fe825b8ac1b..b53c5e2b335f 100644 --- a/Documentation/devicetree/bindings/regulator/max8997-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/max8997-regulator.txt @@ -35,6 +35,7 @@ Optional properties: - interrupts: Interrupt specifiers for two interrupt sources. - First interrupt specifier is for 'irq1' interrupt. - Second interrupt specifier is for 'alert' interrupt. +- charger-supply: regulator node for charging current. - max8997,pmic-buck1-uses-gpio-dvs: 'buck1' can be controlled by gpio dvs. - max8997,pmic-buck2-uses-gpio-dvs: 'buck2' can be controlled by gpio dvs. - max8997,pmic-buck5-uses-gpio-dvs: 'buck5' can be controlled by gpio dvs. diff --git a/Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt b/Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt index d86584ed4d93..451cc4e86b01 100644 --- a/Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt @@ -4,7 +4,8 @@ Required properties: - compatible: "microchip,mcp16502" - reg: I2C slave address - lpm-gpios: GPIO for LPM pin. Note that this GPIO *must* remain high during - suspend-to-ram, keeping the PMIC into HIBERNATE mode. + suspend-to-ram, keeping the PMIC into HIBERNATE mode; this + property is optional; - regulators: A node that houses a sub-node for each regulator within the device. Each sub-node is identified using the node's name. The content of each sub-node is defined by the diff --git a/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml b/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml new file mode 100644 index 000000000000..61dd5af80db6 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/mt6315-regulator.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/mt6315-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek MT6315 Regulator + +maintainers: + - Hsin-Hsiung Wang + +description: | + The MT6315 is a power management IC (PMIC) configurable with SPMI. + that contains 4 BUCKs output which can combine with each other + by different efuse settings. + +properties: + compatible: + const: mediatek,mt6315-regulator + + reg: + maxItems: 1 + + regulators: + type: object + description: List of regulators and its properties + + patternProperties: + "^vbuck[1-4]$": + type: object + $ref: "regulator.yaml#" + + properties: + regulator-name: + pattern: "^vbuck[1-4]$" + + additionalProperties: false + +required: + - compatible + - reg + - regulators + +additionalProperties: false + +examples: + - | + pmic@6 { + compatible = "mediatek,mt6315-regulator"; + reg = <0x6 0>; + + regulators { + vbuck1 { + regulator-compatible = "vbuck1"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1193750>; + regulator-enable-ramp-delay = <256>; + regulator-allowed-modes = <0 1 2 4>; + }; + + vbuck3 { + regulator-compatible = "vbuck3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1193750>; + regulator-enable-ramp-delay = <256>; + regulator-allowed-modes = <0 1 2 4>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml b/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml index c2b0a8b6da1e..f70f2e758a00 100644 --- a/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/nxp,pca9450-regulator.yaml @@ -87,6 +87,11 @@ properties: additionalProperties: false + sd-vsel-gpios: + description: GPIO that is used to switch LDO5 between being configured by + LDO5CTRL_L or LDO5CTRL_H register. Use this if the SD_VSEL signal is + connected to a host GPIO. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/regulator/nxp,pf8x00-regulator.yaml b/Documentation/devicetree/bindings/regulator/nxp,pf8x00-regulator.yaml index 956156fe52a3..8761437ed8ad 100644 --- a/Documentation/devicetree/bindings/regulator/nxp,pf8x00-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/nxp,pf8x00-regulator.yaml @@ -62,8 +62,11 @@ properties: $ref: "/schemas/types.yaml#/definitions/uint32" minimum: 2100 maximum: 4500 + deprecated: true description: BUCK regulators current limit in mA. + This property is deprecated, please use + "regulator-max-microamp" instead. Listed current limits in mA are, 2100 (default) @@ -73,21 +76,11 @@ properties: nxp,phase-shift: $ref: "/schemas/types.yaml#/definitions/uint32" - minimum: 45 - maximum: 0 + default: 0 + enum: [ 0, 45, 90, 135, 180, 225, 270, 315 ] description: BUCK regulators phase shift control in degrees. - Listed phase shift control values in degrees are, - 45 - 90 - 135 - 180 - 225 - 270 - 315 - 0 (default) - unevaluatedProperties: false "^vsnvs$": diff --git a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.txt b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.txt index 7d462b899473..ce1e04354006 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.txt @@ -50,6 +50,8 @@ First Level Nodes - PMIC "qcom,pm8350-rpmh-regulators" "qcom,pm8350c-rpmh-regulators" "qcom,pm8998-rpmh-regulators" + "qcom,pmc8180-rpmh-regulators" + "qcom,pmc8180c-rpmh-regulators" "qcom,pmi8998-rpmh-regulators" "qcom,pm6150-rpmh-regulators" "qcom,pm6150l-rpmh-regulators" diff --git a/Documentation/devicetree/bindings/regulator/qcom-labibb-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom-labibb-regulator.yaml index 53853ec20fe2..cf784bd1f5e5 100644 --- a/Documentation/devicetree/bindings/regulator/qcom-labibb-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom-labibb-regulator.yaml @@ -22,11 +22,17 @@ properties: type: object properties: + qcom,soft-start-us: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Regulator soft start time in microseconds. + enum: [200, 400, 600, 800] + default: 200 interrupts: - maxItems: 1 + minItems: 1 + maxItems: 2 description: - Short-circuit interrupt for lab. + Short-circuit and over-current interrupts for lab. required: - interrupts @@ -35,11 +41,17 @@ properties: type: object properties: + qcom,discharge-resistor-kohms: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Discharge resistor value in KiloOhms. + enum: [300, 64, 32, 16] + default: 300 interrupts: - maxItems: 1 + minItems: 1 + maxItems: 2 description: - Short-circuit interrupt for lab. + Short-circuit and over-current interrupts for ibb. required: - interrupts @@ -57,13 +69,15 @@ examples: compatible = "qcom,pmi8998-lab-ibb"; lab { - interrupts = <0x3 0x0 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "sc-err"; + interrupts = <0x3 0xde 0x1 IRQ_TYPE_EDGE_RISING>, + <0x3 0xde 0x0 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "sc-err", "ocp"; }; ibb { - interrupts = <0x3 0x2 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "sc-err"; + interrupts = <0x3 0xdc 0x2 IRQ_TYPE_EDGE_RISING>, + <0x3 0xdc 0x0 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "sc-err", "ocp"; }; }; diff --git a/Documentation/devicetree/bindings/regulator/richtek,rt4831-regulator.yaml b/Documentation/devicetree/bindings/regulator/richtek,rt4831-regulator.yaml new file mode 100644 index 000000000000..d9c23333e157 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/richtek,rt4831-regulator.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/richtek,rt4831-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Richtek RT4831 Display Bias Voltage Regulator + +maintainers: + - ChiYuan Huang + +description: | + RT4831 is a multifunctional device that can provide power to the LCD display + and LCD backlight. + + For Display Bias Voltage DSVP and DSVN, the output range is about 4V to 6.5V. + It is sufficient to meet the current LCD power requirement. + + DSVLCM is a boost regulator in IC internal as DSVP and DSVN input power. + Its voltage should be configured above 0.15V to 0.2V gap larger than the + voltage needed for DSVP and DSVN. Too much voltage gap could improve the + voltage drop from the heavy loading scenario. But it also make the power + efficiency worse. It's a trade-off. + + Datasheet is available at + https://www.richtek.com/assets/product_file/RT4831A/DS4831A-05.pdf + +patternProperties: + "^DSV(LCM|P|N)$": + type: object + $ref: regulator.yaml# + description: + Properties for single Display Bias Voltage regulator. + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml index 7866a655d81c..908248260afa 100644 --- a/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml +++ b/Documentation/devicetree/bindings/spi/allwinner,sun6i-a31-spi.yaml @@ -25,6 +25,7 @@ properties: - enum: - allwinner,sun8i-r40-spi - allwinner,sun50i-h6-spi + - allwinner,sun50i-h616-spi - const: allwinner,sun8i-h3-spi reg: diff --git a/Documentation/devicetree/bindings/mtd/cadence-quadspi.txt b/Documentation/devicetree/bindings/spi/cadence-quadspi.txt similarity index 97% rename from Documentation/devicetree/bindings/mtd/cadence-quadspi.txt rename to Documentation/devicetree/bindings/spi/cadence-quadspi.txt index 945be7d5b236..8ace832a2d80 100644 --- a/Documentation/devicetree/bindings/mtd/cadence-quadspi.txt +++ b/Documentation/devicetree/bindings/spi/cadence-quadspi.txt @@ -5,6 +5,7 @@ Required properties: Generic default - "cdns,qspi-nor". For TI 66AK2G SoC - "ti,k2g-qspi", "cdns,qspi-nor". For TI AM654 SoC - "ti,am654-ospi", "cdns,qspi-nor". + For Intel LGM SoC - "intel,lgm-qspi", "cdns,qspi-nor". - reg : Contains two entries, each of which is a tuple consisting of a physical address and length. The first entry is the address and length of the controller register set. The second entry is the diff --git a/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml b/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml new file mode 100644 index 000000000000..35a8045b2c70 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/nvidia,tegra210-quad.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/nvidia,tegra210-quad.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra Quad SPI Controller + +maintainers: + - Thierry Reding + - Jonathan Hunter + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + enum: + - nvidia,tegra210-qspi + - nvidia,tegra186-qspi + - nvidia,tegra194-qspi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clock-names: + items: + - const: qspi + - const: qspi_out + + clocks: + maxItems: 2 + + resets: + maxItems: 1 + + dmas: + maxItems: 2 + + dma-names: + items: + - const: rx + - const: tx + +patternProperties: + "@[0-9a-f]+": + type: object + + properties: + spi-rx-bus-width: + enum: [1, 2, 4] + + spi-tx-bus-width: + enum: [1, 2, 4] + + nvidia,tx-clk-tap-delay: + description: + Delays the clock going out to device with this tap value. + Tap value varies based on platform design trace lengths from Tegra + QSPI to corresponding slave device. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 31 + + nvidia,rx-clk-tap-delay: + description: + Delays the clock coming in from the device with this tap value. + Tap value varies based on platform design trace lengths from Tegra + QSPI to corresponding slave device. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 255 + + required: + - reg + +required: + - compatible + - reg + - interrupts + - clock-names + - clocks + - resets + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + spi@70410000 { + compatible = "nvidia,tegra210-qspi"; + reg = <0x70410000 0x1000>; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&tegra_car TEGRA210_CLK_QSPI>, + <&tegra_car TEGRA210_CLK_QSPI_PM>; + clock-names = "qspi", "qspi_out"; + resets = <&tegra_car 211>; + dmas = <&apbdma 5>, <&apbdma 5>; + dma-names = "rx", "tx"; + + flash@0 { + compatible = "spi-nor"; + reg = <0>; + spi-max-frequency = <104000000>; + spi-tx-bus-width = <2>; + spi-rx-bus-width = <2>; + nvidia,tx-clk-tap-delay = <0>; + nvidia,rx-clk-tap-delay = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/spi/realtek,rtl-spi.yaml b/Documentation/devicetree/bindings/spi/realtek,rtl-spi.yaml new file mode 100644 index 000000000000..30a62a211984 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/realtek,rtl-spi.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/realtek,rtl-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek RTL838x/RTL839x SPI controller + +maintainers: + - Bert Vermeulen + - Birger Koblitz + +allOf: + - $ref: "spi-controller.yaml#" + +properties: + compatible: + oneOf: + - const: realtek,rtl8380-spi + - const: realtek,rtl8382-spi + - const: realtek,rtl8391-spi + - const: realtek,rtl8392-spi + - const: realtek,rtl8393-spi + + reg: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + spi: spi@1200 { + compatible = "realtek,rtl8382-spi"; + reg = <0x1200 0x100>; + #address-cells = <1>; + #size-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml index 44c7ddb4b109..b104899205f6 100644 --- a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml @@ -47,6 +47,7 @@ properties: - renesas,msiof-r8a77980 # R-Car V3H - renesas,msiof-r8a77990 # R-Car E3 - renesas,msiof-r8a77995 # R-Car D3 + - renesas,msiof-r8a779a0 # R-Car V3U - const: renesas,rcar-gen3-msiof # generic R-Car Gen3 and RZ/G2 # compatible device - items: diff --git a/Documentation/devicetree/bindings/spi/spi-controller.yaml b/Documentation/devicetree/bindings/spi/spi-controller.yaml index 5f505810104d..06786f1b43d2 100644 --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml @@ -152,8 +152,9 @@ patternProperties: spi-rx-bus-width: description: Bus width to the SPI bus used for read transfers. + If 0 is provided, then no RX will be possible on this device. $ref: /schemas/types.yaml#/definitions/uint32 - enum: [1, 2, 4, 8] + enum: [0, 1, 2, 4, 8] default: 1 spi-rx-delay-us: @@ -163,8 +164,9 @@ patternProperties: spi-tx-bus-width: description: Bus width to the SPI bus used for write transfers. + If 0 is provided, then no TX will be possible on this device. $ref: /schemas/types.yaml#/definitions/uint32 - enum: [1, 2, 4, 8] + enum: [0, 1, 2, 4, 8] default: 1 spi-tx-delay-us: diff --git a/Documentation/devicetree/bindings/spi/spi-sirf.txt b/Documentation/devicetree/bindings/spi/spi-sirf.txt deleted file mode 100644 index ddd78ff68fae..000000000000 --- a/Documentation/devicetree/bindings/spi/spi-sirf.txt +++ /dev/null @@ -1,42 +0,0 @@ -* CSR SiRFprimaII Serial Peripheral Interface - -Required properties: -- compatible : Should be "sirf,prima2-spi", "sirf,prima2-usp" - or "sirf,atlas7-usp" -- reg : Offset and length of the register set for the device -- interrupts : Should contain SPI interrupt -- resets: phandle to the reset controller asserting this device in - reset - See ../reset/reset.txt for details. -- dmas : Must contain an entry for each entry in clock-names. - See ../dma/dma.txt for details. -- dma-names : Must include the following entries: - - rx - - tx -- clocks : Must contain an entry for each entry in clock-names. - See ../clocks/clock-bindings.txt for details. - -- #address-cells: Number of cells required to define a chip select - address on the SPI bus. Should be set to 1. -- #size-cells: Should be zero. - -Optional properties: -- spi-max-frequency: Specifies maximum SPI clock frequency, - Units - Hz. Definition as per - Documentation/devicetree/bindings/spi/spi-bus.txt -- cs-gpios: should specify GPIOs used for chipselects. - -Example: - -spi0: spi@b00d0000 { - compatible = "sirf,prima2-spi"; - reg = <0xb00d0000 0x10000>; - interrupts = <15>; - dmas = <&dmac1 9>, - <&dmac1 4>; - dma-names = "rx", "tx"; - #address-cells = <1>; - #size-cells = <0>; - clocks = <&clks 19>; - resets = <&rstc 26>; -}; diff --git a/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml b/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml new file mode 100644 index 000000000000..7cd364430573 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml @@ -0,0 +1,153 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/qcom-spmi-adc-tm5.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm's SPMI PMIC ADC Thermal Monitoring +maintainers: + - Dmitry Baryshkov + +properties: + compatible: + const: qcom,spmi-adc-tm5 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + description: + Number of cells required to uniquely identify the thermal sensors. Since + we have multiple sensors this is set to 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + qcom,avg-samples: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of samples to be used for measurement. + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + default: 1 + + qcom,decimation: + $ref: /schemas/types.yaml#/definitions/uint32 + description: This parameter is used to decrease ADC sampling rate. + Quicker measurements can be made by reducing decimation ratio. + enum: + - 250 + - 420 + - 840 + default: 840 + +patternProperties: + "^([-a-z0-9]*)@[0-7]$": + type: object + description: + Represent one thermal sensor. + + properties: + reg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Specify the sensor channel. There are 8 channels in PMIC5's ADC TM + minimum: 0 + maximum: 7 + + io-channels: + description: + From common IIO binding. Used to pipe PMIC ADC channel to thermal monitor + + qcom,ratiometric: + $ref: /schemas/types.yaml#/definitions/flag + description: + Channel calibration type. + If this property is specified VADC will use the VDD reference + (1.875V) and GND for channel calibration. If property is not found, + channel will be calibrated with 0V and 1.25V reference channels, + also known as absolute calibration. + + qcom,hw-settle-time-us: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Time between AMUX getting configured and the ADC starting conversion. + enum: [15, 100, 200, 300, 400, 500, 600, 700, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000] + + qcom,pre-scaling: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Used for scaling the channel input signal before the + signal is fed to VADC. The configuration for this node is to know the + pre-determined ratio and use it for post scaling. It is a pair of + integers, denoting the numerator and denominator of the fraction by + which input signal is multiplied. For example, <1 3> indicates the + signal is scaled down to 1/3 of its value before ADC measurement. If + property is not found default value depending on chip will be used. + items: + - const: 1 + - enum: [ 1, 3, 4, 6, 20, 8, 10 ] + + required: + - reg + - io-channels + + additionalProperties: + false + +required: + - compatible + - reg + - interrupts + - "#address-cells" + - "#size-cells" + - "#thermal-sensor-cells" + +additionalProperties: false + +examples: + - | + #include + #include + spmi_bus { + #address-cells = <1>; + #size-cells = <0>; + pm8150b_adc: adc@3100 { + reg = <0x3100>; + compatible = "qcom,spmi-adc5"; + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + + /* Other propreties are omitted */ + conn-therm@4f { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + }; + }; + + pm8150b_adc_tm: adc-tm@3500 { + compatible = "qcom,spmi-adc-tm5"; + reg = <0x3500>; + interrupts = <0x2 0x35 0x0 IRQ_TYPE_EDGE_RISING>; + #thermal-sensor-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + conn-therm@0 { + reg = <0>; + io-channels = <&pm8150b_adc ADC5_AMUX_THM3_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/thermal/tango-thermal.txt b/Documentation/devicetree/bindings/thermal/tango-thermal.txt deleted file mode 100644 index 2c918d742867..000000000000 --- a/Documentation/devicetree/bindings/thermal/tango-thermal.txt +++ /dev/null @@ -1,17 +0,0 @@ -* Tango Thermal - -The SMP8758 SoC includes 3 instances of this temperature sensor -(in the CPU, video decoder, and PCIe controller). - -Required properties: -- #thermal-sensor-cells: Should be 0 (see Documentation/devicetree/bindings/thermal/thermal-sensor.yaml) -- compatible: "sigma,smp8758-thermal" -- reg: Address range of the thermal registers - -Example: - - cpu_temp: thermal@920100 { - #thermal-sensor-cells = <0>; - compatible = "sigma,smp8758-thermal"; - reg = <0x920100 12>; - }; diff --git a/Documentation/devicetree/bindings/thermal/zx2967-thermal.txt b/Documentation/devicetree/bindings/thermal/zx2967-thermal.txt deleted file mode 100644 index 3dc1c6bf0478..000000000000 --- a/Documentation/devicetree/bindings/thermal/zx2967-thermal.txt +++ /dev/null @@ -1,116 +0,0 @@ -* ZTE zx2967 family Thermal - -Required Properties: -- compatible: should be one of the following. - * zte,zx296718-thermal -- reg: physical base address of the controller and length of memory mapped - region. -- clocks : Pairs of phandle and specifier referencing the controller's clocks. -- clock-names: "topcrm" for the topcrm clock. - "apb" for the apb clock. -- #thermal-sensor-cells: must be 0. - -Please note: slope coefficient defined in thermal-zones section need to be -multiplied by 1000. - -Example for tempsensor: - - tempsensor: tempsensor@148a000 { - compatible = "zte,zx296718-thermal"; - reg = <0x0148a000 0x20>; - clocks = <&topcrm TEMPSENSOR_GATE>, <&audiocrm AUDIO_TS_PCLK>; - clock-names = "topcrm", "apb"; - #thermal-sensor-cells = <0>; - }; - -Example for cooling device: - - cooling_dev: cooling_dev { - cluster0_cooling_dev: cluster0-cooling-dev { - #cooling-cells = <2>; - cpumask = <0xf>; - capacitance = <1500>; - }; - - cluster1_cooling_dev: cluster1-cooling-dev { - #cooling-cells = <2>; - cpumask = <0x30>; - capacitance = <2000>; - }; - }; - -Example for thermal zones: - - thermal-zones { - zx296718_thermal: zx296718_thermal { - polling-delay-passive = <500>; - polling-delay = <1000>; - sustainable-power = <6500>; - - thermal-sensors = <&tempsensor 0>; - /* - * slope need to be multiplied by 1000. - */ - coefficients = <1951 (-922)>; - - trips { - trip0: switch_on_temperature { - temperature = <90000>; - hysteresis = <2000>; - type = "passive"; - }; - - trip1: desired_temperature { - temperature = <100000>; - hysteresis = <2000>; - type = "passive"; - }; - - crit: critical_temperature { - temperature = <110000>; - hysteresis = <2000>; - type = "critical"; - }; - }; - - cooling-maps { - map0 { - trip = <&trip0>; - cooling-device = <&gpu 2 5>; - }; - - map1 { - trip = <&trip0>; - cooling-device = <&cluster0_cooling_dev 1 2>; - }; - - map2 { - trip = <&trip1>; - cooling-device = <&cluster0_cooling_dev 1 2>; - }; - - map3 { - trip = <&crit>; - cooling-device = <&cluster0_cooling_dev 1 2>; - }; - - map4 { - trip = <&trip0>; - cooling-device = <&cluster1_cooling_dev 1 2>; - contribution = <9000>; - }; - - map5 { - trip = <&trip1>; - cooling-device = <&cluster1_cooling_dev 1 2>; - contribution = <4096>; - }; - - map6 { - trip = <&crit>; - cooling-device = <&cluster1_cooling_dev 1 2>; - contribution = <4096>; - }; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/watchdog/allwinner,sun4i-a10-wdt.yaml b/Documentation/devicetree/bindings/watchdog/allwinner,sun4i-a10-wdt.yaml index 5ac607de8be4..9aa3c313c49f 100644 --- a/Documentation/devicetree/bindings/watchdog/allwinner,sun4i-a10-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/allwinner,sun4i-a10-wdt.yaml @@ -19,13 +19,11 @@ properties: - const: allwinner,sun4i-a10-wdt - const: allwinner,sun6i-a31-wdt - items: - - const: allwinner,sun50i-a64-wdt - - const: allwinner,sun6i-a31-wdt - - items: - - const: allwinner,sun50i-a100-wdt - - const: allwinner,sun6i-a31-wdt - - items: - - const: allwinner,sun50i-h6-wdt + - enum: + - allwinner,sun50i-a64-wdt + - allwinner,sun50i-a100-wdt + - allwinner,sun50i-h6-wdt + - allwinner,sun50i-h616-wdt - const: allwinner,sun6i-a31-wdt - items: - const: allwinner,suniv-f1c100s-wdt diff --git a/Documentation/devicetree/bindings/watchdog/intel,keembay-wdt.yaml b/Documentation/devicetree/bindings/watchdog/intel,keembay-wdt.yaml new file mode 100644 index 000000000000..1437ff8a122f --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/intel,keembay-wdt.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/watchdog/intel,keembay-wdt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Intel Keem Bay SoC non-secure Watchdog Timer + +maintainers: + - Wan Ahmad Zainie + +properties: + compatible: + enum: + - intel,keembay-wdt + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + items: + - description: interrupt specifier for threshold interrupt line + - description: interrupt specifier for timeout interrupt line + + interrupt-names: + items: + - const: threshold + - const: timeout + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + +additionalProperties: false + +examples: + - | + #include + #include + #define KEEM_BAY_A53_TIM + + watchdog: watchdog@2033009c { + compatible = "intel,keembay-wdt"; + reg = <0x2033009c 0x10>; + interrupts = , + ; + interrupt-names = "threshold", "timeout"; + clocks = <&scmi_clk KEEM_BAY_A53_TIM>; + }; + +... diff --git a/Documentation/devicetree/bindings/watchdog/mtk-wdt.txt b/Documentation/devicetree/bindings/watchdog/mtk-wdt.txt index 4dd36bd3f1ad..e36ba60de829 100644 --- a/Documentation/devicetree/bindings/watchdog/mtk-wdt.txt +++ b/Documentation/devicetree/bindings/watchdog/mtk-wdt.txt @@ -4,14 +4,15 @@ Required properties: - compatible should contain: "mediatek,mt2701-wdt", "mediatek,mt6589-wdt": for MT2701 - "mediatek,mt2712-wdt", "mediatek,mt6589-wdt": for MT2712 + "mediatek,mt2712-wdt": for MT2712 "mediatek,mt6589-wdt": for MT6589 "mediatek,mt6797-wdt", "mediatek,mt6589-wdt": for MT6797 "mediatek,mt7622-wdt", "mediatek,mt6589-wdt": for MT7622 "mediatek,mt7623-wdt", "mediatek,mt6589-wdt": for MT7623 "mediatek,mt7629-wdt", "mediatek,mt6589-wdt": for MT7629 - "mediatek,mt8183-wdt", "mediatek,mt6589-wdt": for MT8183 + "mediatek,mt8183-wdt": for MT8183 "mediatek,mt8516-wdt", "mediatek,mt6589-wdt": for MT8516 + "mediatek,mt8192-wdt": for MT8192 - reg : Specifies base physical address and size of the registers. diff --git a/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml b/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml index 6933005b52bd..ab66d3f0c476 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml @@ -50,6 +50,7 @@ properties: - renesas,r8a77980-wdt # R-Car V3H - renesas,r8a77990-wdt # R-Car E3 - renesas,r8a77995-wdt # R-Car D3 + - renesas,r8a779a0-wdt # R-Car V3U - const: renesas,rcar-gen3-wdt # R-Car Gen3 and RZ/G2 reg: diff --git a/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt b/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt deleted file mode 100644 index 5b7ec2c707d8..000000000000 --- a/Documentation/devicetree/bindings/watchdog/sigma,smp8642-wdt.txt +++ /dev/null @@ -1,18 +0,0 @@ -Sigma Designs SMP86xx/SMP87xx watchdog - -Required properties: -- compatible: Should be "sigma,smp8642-wdt" -- reg: Specifies the physical address region -- clocks: Should be a phandle to the clock - -Optional properties: -- timeout-sec: watchdog timeout in seconds - -Example: - -watchdog@1fd00 { - compatible = "sigma,smp8642-wdt"; - reg = <0x1fd00 8>; - clocks = <&xtal_in_clk>; - timeout-sec = <30>; -}; diff --git a/Documentation/devicetree/bindings/watchdog/sirfsoc_wdt.txt b/Documentation/devicetree/bindings/watchdog/sirfsoc_wdt.txt deleted file mode 100644 index 0dce5e3100b4..000000000000 --- a/Documentation/devicetree/bindings/watchdog/sirfsoc_wdt.txt +++ /dev/null @@ -1,18 +0,0 @@ -SiRFSoC Timer and Watchdog Timer(WDT) Controller - -Required properties: -- compatible: "sirf,prima2-tick" -- reg: Address range of tick timer/WDT register set -- interrupts: interrupt number to the cpu - -Optional properties: -- timeout-sec : Contains the watchdog timeout in seconds - -Example: - -timer@b0020000 { - compatible = "sirf,prima2-tick"; - reg = <0xb0020000 0x1000>; - interrupts = <0>; - timeout-sec = <30>; -}; diff --git a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml index f7ee9229c29f..b58596b1831d 100644 --- a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml @@ -18,10 +18,16 @@ properties: - const: snps,dw-wdt - items: - enum: + - rockchip,px30-wdt - rockchip,rk3066-wdt - rockchip,rk3188-wdt + - rockchip,rk3228-wdt - rockchip,rk3288-wdt + - rockchip,rk3308-wdt + - rockchip,rk3328-wdt - rockchip,rk3368-wdt + - rockchip,rk3399-wdt + - rockchip,rv1108-wdt - const: snps,dw-wdt reg: diff --git a/Documentation/devicetree/bindings/watchdog/stericsson-coh901327.txt b/Documentation/devicetree/bindings/watchdog/stericsson-coh901327.txt deleted file mode 100644 index 8ffb88e39e76..000000000000 --- a/Documentation/devicetree/bindings/watchdog/stericsson-coh901327.txt +++ /dev/null @@ -1,19 +0,0 @@ -ST-Ericsson COH 901 327 Watchdog timer - -Required properties: -- compatible: must be "stericsson,coh901327". -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: the interrupt used for the watchdog timeout warning. - -Optional properties: -- timeout-sec: contains the watchdog timeout in seconds. - -Example: - -watchdog: watchdog@c0012000 { - compatible = "stericsson,coh901327"; - reg = <0xc0012000 0x1000>; - interrupts = <3>; - timeout-sec = <60>; -}; diff --git a/Documentation/devicetree/bindings/watchdog/zte,zx2967-wdt.txt b/Documentation/devicetree/bindings/watchdog/zte,zx2967-wdt.txt deleted file mode 100644 index 06ce67766756..000000000000 --- a/Documentation/devicetree/bindings/watchdog/zte,zx2967-wdt.txt +++ /dev/null @@ -1,32 +0,0 @@ -ZTE zx2967 Watchdog timer - -Required properties: - -- compatible : should be one of the following. - * zte,zx296718-wdt -- reg : Specifies base physical address and size of the registers. -- clocks : Pairs of phandle and specifier referencing the controller's clocks. -- resets : Reference to the reset controller controlling the watchdog - controller. - -Optional properties: - -- timeout-sec : Contains the watchdog timeout in seconds. -- zte,wdt-reset-sysctrl : Directs how to reset system by the watchdog. - if we don't want to restart system when watchdog been triggered, - it's not required, vice versa. - It should include following fields. - * phandle of aon-sysctrl. - * offset of register that be written, should be 0xb0. - * configure value that be written to aon-sysctrl. - * bit mask, corresponding bits will be affected. - -Example: - -wdt: watchdog@1465000 { - compatible = "zte,zx296718-wdt"; - reg = <0x1465000 0x1000>; - clocks = <&topcrm WDT_WCLK>; - resets = <&toprst 35>; - zte,wdt-reset-sysctrl = <&aon_sysctrl 0xb0 1 0x115>; -}; diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index 2456d0a97ed8..9d9af54d68c5 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -99,6 +99,7 @@ available subsections can be seen below. rfkill serial/index sm501 + surface_aggregator/index switchtec sync_file vfio-mediated-device diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst new file mode 100644 index 000000000000..8e0b000d0e64 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client-api.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +=============================== +Client Driver API Documentation +=============================== + +.. contents:: + :depth: 2 + + +Serial Hub Communication +======================== + +.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c + :export: + + +Controller and Core Interface +============================= + +.. kernel-doc:: include/linux/surface_aggregator/controller.h + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.c + :export: + +.. kernel-doc:: drivers/platform/surface/aggregator/core.c + :export: + + +Client Bus and Client Device API +================================ + +.. kernel-doc:: include/linux/surface_aggregator/device.h + +.. kernel-doc:: drivers/platform/surface/aggregator/bus.c + :export: diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst new file mode 100644 index 000000000000..26d13085a117 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -0,0 +1,393 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` +.. |ssam_device| replace:: :c:type:`struct ssam_device ` +.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` +.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` +.. |ssam_client_link| replace:: :c:func:`ssam_client_link` +.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller` +.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get` +.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put` +.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc` +.. |ssam_device_add| replace:: :c:func:`ssam_device_add` +.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove` +.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register` +.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister` +.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver` +.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` +.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` +.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` +.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` + + +====================== +Writing Client Drivers +====================== + +For the API documentation, refer to: + +.. toctree:: + :maxdepth: 2 + + client-api + + +Overview +======== + +Client drivers can be set up in two main ways, depending on how the +corresponding device is made available to the system. We specifically +differentiate between devices that are presented to the system via one of +the conventional ways, e.g. as platform devices via ACPI, and devices that +are non-discoverable and instead need to be explicitly provided by some +other mechanism, as discussed further below. + + +Non-SSAM Client Drivers +======================= + +All communication with the SAM EC is handled via the |ssam_controller| +representing that EC to the kernel. Drivers targeting a non-SSAM device (and +thus not being a |ssam_device_driver|) need to explicitly establish a +connection/relation to that controller. This can be done via the +|ssam_client_bind| function. Said function returns a reference to the SSAM +controller, but, more importantly, also establishes a device link between +client device and controller (this can also be done separate via +|ssam_client_link|). It is important to do this, as it, first, guarantees +that the returned controller is valid for use in the client driver for as +long as this driver is bound to its device, i.e. that the driver gets +unbound before the controller ever becomes invalid, and, second, as it +ensures correct suspend/resume ordering. This setup should be done in the +driver's probe function, and may be used to defer probing in case the SSAM +subsystem is not ready yet, for example: + +.. code-block:: c + + static int client_driver_probe(struct platform_device *pdev) + { + struct ssam_controller *ctrl; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + // ... + + return 0; + } + +The controller may be separately obtained via |ssam_get_controller| and its +lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|. +Note that none of these functions, however, guarantee that the controller +will not be shut down or suspended. These functions essentially only operate +on the reference, i.e. only guarantee a bare minimum of accessibility +without any guarantees at all on practical operability. + + +Adding SSAM Devices +=================== + +If a device does not already exist/is not already provided via conventional +means, it should be provided as |ssam_device| via the SSAM client device +hub. New devices can be added to this hub by entering their UID into the +corresponding registry. SSAM devices can also be manually allocated via +|ssam_device_alloc|, subsequently to which they have to be added via +|ssam_device_add| and eventually removed via |ssam_device_remove|. By +default, the parent of the device is set to the controller device provided +for allocation, however this may be changed before the device is added. Note +that, when changing the parent device, care must be taken to ensure that the +controller lifetime and suspend/resume ordering guarantees, in the default +setup provided through the parent-child relation, are preserved. If +necessary, by use of |ssam_client_link| as is done for non-SSAM client +drivers and described in more detail above. + +A client device must always be removed by the party which added the +respective device before the controller shuts down. Such removal can be +guaranteed by linking the driver providing the SSAM device to the controller +via |ssam_client_link|, causing it to unbind before the controller driver +unbinds. Client devices registered with the controller as parent are +automatically removed when the controller shuts down, but this should not be +relied upon, especially as this does not extend to client devices with a +different parent. + + +SSAM Client Drivers +=================== + +SSAM client device drivers are, in essence, no different than other device +driver types. They are represented via |ssam_device_driver| and bind to a +|ssam_device| via its UID (:c:type:`struct ssam_device.uid `) +member and the match table +(:c:type:`struct ssam_device_driver.match_table `), +which should be set when declaring the driver struct instance. Refer to the +|SSAM_DEVICE| macro documentation for more details on how to define members +of the driver's match table. + +The UID for SSAM client devices consists of a ``domain``, a ``category``, +a ``target``, an ``instance``, and a ``function``. The ``domain`` is used +differentiate between physical SAM devices +(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can +be accessed via the Surface Serial Hub, and virtual ones +(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device +hubs, that have no real representation on the SAM EC and are solely used on +the kernel/driver-side. For physical devices, ``category`` represents the +target category, ``target`` the target ID, and ``instance`` the instance ID +used to access the physical SAM device. In addition, ``function`` references +a specific device functionality, but has no meaning to the SAM EC. The +(default) name of a client device is generated based on its UID. + +A driver instance can be registered via |ssam_device_driver_register| and +unregistered via |ssam_device_driver_unregister|. For convenience, the +|module_ssam_device_driver| macro may be used to define module init- and +exit-functions registering the driver. + +The controller associated with a SSAM client device can be found in its +:c:type:`struct ssam_device.ctrl ` member. This reference is +guaranteed to be valid for at least as long as the client driver is bound, +but should also be valid for as long as the client device exists. Note, +however, that access outside of the bound client driver must ensure that the +controller device is not suspended while making any requests or +(un-)registering event notifiers (and thus should generally be avoided). This +is guaranteed when the controller is accessed from inside the bound client +driver. + + +Making Synchronous Requests +=========================== + +Synchronous requests are (currently) the main form of host-initiated +communication with the EC. There are a couple of ways to define and execute +such requests, however, most of them boil down to something similar as shown +in the example below. This example defines a write-read request, meaning +that the caller provides an argument to the SAM EC and receives a response. +The caller needs to know the (maximum) length of the response payload and +provide a buffer for it. + +Care must be taken to ensure that any command payload data passed to the SAM +EC is provided in little-endian format and, similarly, any response payload +data received from it is converted from little-endian to host endianness. + +.. code-block:: c + + int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret) + { + struct ssam_request rqst; + struct ssam_response resp; + int status; + + /* Convert request argument to little-endian. */ + __le32 arg_le = cpu_to_le32(arg); + __le32 ret_le = cpu_to_le32(0); + + /* + * Initialize request specification. Replace this with your values. + * The rqst.payload field may be NULL if rqst.length is zero, + * indicating that the request does not have any argument. + * + * Note: The request parameters used here are not valid, i.e. + * they do not correspond to an actual SAM/EC request. + */ + rqst.target_category = SSAM_SSH_TC_SAM; + rqst.target_id = 0x01; + rqst.command_id = 0x02; + rqst.instance_id = 0x03; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(arg_le); + rqst.payload = (u8 *)&arg_le; + + /* Initialize request response. */ + resp.capacity = sizeof(ret_le); + resp.length = 0; + resp.pointer = (u8 *)&ret_le; + + /* + * Perform actual request. The response pointer may be null in case + * the request does not have any response. This must be consistent + * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification + * above. + */ + status = ssam_request_sync(ctrl, &rqst, &resp); + + /* + * Alternatively use + * + * ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le)); + * + * to perform the request, allocating the message buffer directly + * on the stack as opposed to allocation via kzalloc(). + */ + + /* + * Convert request response back to native format. Note that in the + * error case, this value is not touched by the SSAM core, i.e. + * 'ret_le' will be zero as specified in its initialization. + */ + *ret = le32_to_cpu(ret_le); + + return status; + } + +Note that |ssam_request_sync| in its essence is a wrapper over lower-level +request primitives, which may also be used to perform requests. Refer to its +implementation and documentation for more details. + +An arguably more user-friendly way of defining such functions is by using +one of the generator macros, for example via: + +.. code-block:: c + + SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { + .target_category = SSAM_SSH_TC_TMP, + .target_id = 0x01, + .command_id = 0x03, + .instance_id = 0x00, + }); + +This example defines a function + +.. code-block:: c + + int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); + +executing the specified request, with the controller passed in when calling +said function. In this example, the argument is provided via the ``arg`` +pointer. Note that the generated function allocates the message buffer on +the stack. Thus, if the argument provided via the request is large, these +kinds of macros should be avoided. Also note that, in contrast to the +previous non-macro example, this function does not do any endianness +conversion, which has to be handled by the caller. Apart from those +differences the function generated by the macro is similar to the one +provided in the non-macro example above. + +The full list of such function-generating macros is + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and + without argument. +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no + argument. +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but + with argument. + +Refer to their respective documentation for more details. For each one of +these macros, a special variant is provided, which targets request types +applicable to multiple instances of the same device type: + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W` + +The difference of those macros to the previously mentioned versions is, that +the device target and instance IDs are not fixed for the generated function, +but instead have to be provided by the caller of said function. + +Additionally, variants for direct use with client devices, i.e. +|ssam_device|, are also provided. These can, for example, be used as +follows: + +.. code-block:: c + + SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x01, + }); + +This invocation of the macro defines a function + +.. code-block:: c + + int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); + +executing the specified request, using the device IDs and controller given +in the client device. The full list of such macros for client devices is: + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W` + + +Handling Events +=============== + +To receive events from the SAM EC, an event notifier must be registered for +the desired event via |ssam_notifier_register|. The notifier must be +unregistered via |ssam_notifier_unregister| once it is not required any +more. + +Event notifiers are registered by providing (at minimum) a callback to call +in case an event has been received, the registry specifying how the event +should be enabled, an event ID specifying for which target category and, +optionally and depending on the registry used, for which instance ID events +should be enabled, and finally, flags describing how the EC will send these +events. If the specific registry does not enable events by instance ID, the +instance ID must be set to zero. Additionally, a priority for the respective +notifier may be specified, which determines its order in relation to any +other notifier registered for the same target category. + +By default, event notifiers will receive all events for the specific target +category, regardless of the instance ID specified when registering the +notifier. The core may be instructed to only call a notifier if the target +ID or instance ID (or both) of the event match the ones implied by the +notifier IDs (in case of target ID, the target ID of the registry), by +providing an event mask (see |ssam_event_mask|). + +In general, the target ID of the registry is also the target ID of the +enabled event (with the notable exception being keyboard input events on the +Surface Laptop 1 and 2, which are enabled via a registry with target ID 1, +but provide events with target ID 2). + +A full example for registering an event notifier and handling received +events is provided below: + +.. code-block:: c + + u32 notifier_callback(struct ssam_event_notifier *nf, + const struct ssam_event *event) + { + int status = ... + + /* Handle the event here ... */ + + /* Convert return value and indicate that we handled the event. */ + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; + } + + int setup_notifier(struct ssam_device *sdev, + struct ssam_event_notifier *nf) + { + /* Set priority wrt. other handlers of same target category. */ + nf->base.priority = 1; + + /* Set event/notifier callback. */ + nf->base.fn = notifier_callback; + + /* Specify event registry, i.e. how events get enabled/disabled. */ + nf->event.reg = SSAM_EVENT_REGISTRY_KIP; + + /* Specify which event to enable/disable */ + nf->event.id.target_category = sdev->uid.category; + nf->event.id.instance = sdev->uid.instance; + + /* + * Specify for which events the notifier callback gets executed. + * This essentially tells the core if it can skip notifiers that + * don't have target or instance IDs matching those of the event. + */ + nf->event.mask = SSAM_EVENT_MASK_STRICT; + + /* Specify event flags. */ + nf->event.flags = SSAM_EVENT_SEQUENCED; + + return ssam_notifier_register(sdev->ctrl, nf); + } + +Multiple event notifiers can be registered for the same event. The event +handler core takes care of enabling and disabling events when notifiers are +registered and unregistered, by keeping track of how many notifiers for a +specific event (combination of registry, event target category, and event +instance ID) are currently registered. This means that a specific event will +be enabled when the first notifier for it is being registered and disabled +when the last notifier for it is being unregistered. Note that the event +flags are therefore only used on the first registered notifier, however, one +should take care that notifiers for a specific event are always registered +with the same flag and it is considered a bug to do otherwise. diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst new file mode 100644 index 000000000000..248c1372d879 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` +.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` +.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` + +============================== +User-Space EC Interface (cdev) +============================== + +The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM +controller to allow for a (more or less) direct connection from user-space to +the SAM EC. It is intended to be used for development and debugging, and +therefore should not be used or relied upon in any other way. Note that this +module is not loaded automatically, but instead must be loaded manually. + +The provided interface is accessible through the ``/dev/surface/aggregator`` +device-file. All functionality of this interface is provided via IOCTLs. +These IOCTLs and their respective input/output parameter structs are defined in +``include/uapi/linux/surface_aggregator/cdev.h``. + +A small python library and scripts for accessing this interface can be found +at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. + + +Controller IOCTLs +================= + +The following IOCTLs are provided: + +.. flat-table:: Controller IOCTLs + :widths: 1 1 1 1 4 + :header-rows: 1 + + * - Type + - Number + - Direction + - Name + - Description + + * - ``0xA5`` + - ``1`` + - ``WR`` + - ``REQUEST`` + - Perform synchronous SAM request. + + +``REQUEST`` +----------- + +Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. + +Executes a synchronous SAM request. The request specification is passed in +as argument of type |ssam_cdev_request|, which is then written to/modified +by the IOCTL to return status and result of the request. + +Request payload data must be allocated separately and is passed in via the +``payload.data`` and ``payload.length`` members. If a response is required, +the response buffer must be allocated by the caller and passed in via the +``response.data`` member. The ``response.length`` member must be set to the +capacity of this buffer, or if no response is required, zero. Upon +completion of the request, the call will write the response to the response +buffer (if its capacity allows it) and overwrite the length field with the +actual size of the response, in bytes. + +Additionally, if the request has a response, this must be indicated via the +request flags, as is done with in-kernel requests. Request flags can be set +via the ``flags`` member and the values correspond to the values found in +|ssam_cdev_request_flags|. + +Finally, the status of the request itself is returned in the ``status`` +member (a negative errno value indicating failure). Note that failure +indication of the IOCTL is separated from failure indication of the request: +The IOCTL returns a negative status code if anything failed during setup of +the request (``-EFAULT``) or if the provided argument or any of its fields +are invalid (``-EINVAL``). In this case, the status value of the request +argument may be set, providing more detail on what went wrong (e.g. +``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL +will return with a zero status code in case the request has been set up, +submitted, and completed (i.e. handed back to user-space) successfully from +inside the IOCTL, but the request ``status`` member may still be negative in +case the actual execution of the request failed after it has been submitted. + +A full definition of the argument struct is provided below: + +.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst new file mode 100644 index 000000000000..3ccabce23271 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +=========================== +Client Driver Documentation +=========================== + +This is the documentation for client drivers themselves. Refer to +:doc:`../client` for documentation on how to write client drivers. + +.. toctree:: + :maxdepth: 1 + + cdev + san + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst new file mode 100644 index 000000000000..38c2580e7758 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/san.rst @@ -0,0 +1,44 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |san_client_link| replace:: :c:func:`san_client_link` +.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` +.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` + +=================== +Surface ACPI Notify +=================== + +The Surface ACPI Notify (SAN) device provides the bridge between ACPI and +SAM controller. Specifically, ACPI code can execute requests and handle +battery and thermal events via this interface. In addition to this, events +relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from +ACPI code (note: the Surface Book 3 uses a different method for this). The +only currently known event sent via this interface is a dGPU power-on +notification. While this driver handles the former part internally, it only +relays the dGPU events to any other driver interested via its public API and +does not handle them. + +The public interface of this driver is split into two parts: Client +registration and notifier-block registration. + +A client to the SAN interface can be linked as consumer to the SAN device +via |san_client_link|. This can be used to ensure that the a client +receiving dGPU events does not miss any events due to the SAN interface not +being set up as this forces the client driver to unbind once the SAN driver +is unbound. + +Notifier-blocks can be registered by any device for as long as the module is +loaded, regardless of being linked as client or not. Registration is done +with |san_dgpu_notifier_register|. If the notifier is not needed any more, it +should be unregistered via |san_dgpu_notifier_unregister|. + +Consult the API documentation below for more details. + + +API Documentation +================= + +.. kernel-doc:: include/linux/surface_acpi_notify.h + +.. kernel-doc:: drivers/platform/surface/surface_acpi_notify.c + :export: diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst new file mode 100644 index 000000000000..6f3e1094904d --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +======================================= +Surface System Aggregator Module (SSAM) +======================================= + +.. toctree:: + :maxdepth: 2 + + overview + client + clients/index + ssh + internal + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst new file mode 100644 index 000000000000..639a67b5a392 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal-api.rst @@ -0,0 +1,67 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +========================== +Internal API Documentation +========================== + +.. contents:: + :depth: 2 + + +Packet Transport Layer +====================== + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.c + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_msgb.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c + :internal: + + +Request Transport Layer +======================= + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.c + :internal: + + +Controller +========== + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.c + :internal: + + +Client Device Bus +================= + +.. kernel-doc:: drivers/platform/surface/aggregator/bus.c + :internal: + + +Core +==== + +.. kernel-doc:: drivers/platform/surface/aggregator/core.c + :internal: + + +Trace Helpers +============= + +.. kernel-doc:: drivers/platform/surface/aggregator/trace.h diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst new file mode 100644 index 000000000000..72704734982a --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal.rst @@ -0,0 +1,577 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl ` +.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit` +.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel` +.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown` +.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf` +.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl ` +.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit` +.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel` +.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown` +.. |ssh_packet| replace:: :c:type:`struct ssh_packet ` +.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get` +.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put` +.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops ` +.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority ` +.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags ` +.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY` +.. |ssh_frame| replace:: :c:type:`struct ssh_frame ` +.. |ssh_command| replace:: :c:type:`struct ssh_command ` +.. |ssh_request| replace:: :c:type:`struct ssh_request ` +.. |ssh_request_get| replace:: :c:func:`ssh_request_get` +.. |ssh_request_put| replace:: :c:func:`ssh_request_put` +.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops ` +.. |ssh_request_init| replace:: :c:func:`ssh_request_init` +.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags ` +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` +.. |ssam_device| replace:: :c:type:`struct ssam_device ` +.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` +.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` +.. |ssam_client_link| replace:: :c:func:`ssam_client_link` +.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync ` +.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry ` +.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id ` +.. |ssam_nf| replace:: :c:type:`struct ssam_nf ` +.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc` +.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec` +.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` +.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt ` +.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue ` +.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit` + +===================== +Core Driver Internals +===================== + +Architectural overview of the Surface System Aggregator Module (SSAM) core +and Surface Serial Hub (SSH) driver. For the API documentation, refer to: + +.. toctree:: + :maxdepth: 2 + + internal-api + + +Overview +======== + +The SSAM core implementation is structured in layers, somewhat following the +SSH protocol structure: + +Lower-level packet transport is implemented in the *packet transport layer +(PTL)*, directly building on top of the serial device (serdev) +infrastructure of the kernel. As the name indicates, this layer deals with +the packet transport logic and handles things like packet validation, packet +acknowledgment (ACKing), packet (retransmission) timeouts, and relaying +packet payloads to higher-level layers. + +Above this sits the *request transport layer (RTL)*. This layer is centered +around command-type packet payloads, i.e. requests (sent from host to EC), +responses of the EC to those requests, and events (sent from EC to host). +It, specifically, distinguishes events from request responses, matches +responses to their corresponding requests, and implements request timeouts. + +The *controller* layer is building on top of this and essentially decides +how request responses and, especially, events are dealt with. It provides an +event notifier system, handles event activation/deactivation, provides a +workqueue for event and asynchronous request completion, and also manages +the message counters required for building command messages (``SEQ``, +``RQID``). This layer basically provides a fundamental interface to the SAM +EC for use in other kernel drivers. + +While the controller layer already provides an interface for other kernel +drivers, the client *bus* extends this interface to provide support for +native SSAM devices, i.e. devices that are not defined in ACPI and not +implemented as platform devices, via |ssam_device| and |ssam_device_driver| +simplify management of client devices and client drivers. + +Refer to :doc:`client` for documentation regarding the client device/driver +API and interface options for other kernel drivers. It is recommended to +familiarize oneself with that chapter and the :doc:`ssh` before continuing +with the architectural overview below. + + +Packet Transport Layer +====================== + +The packet transport layer is represented via |ssh_ptl| and is structured +around the following key concepts: + +Packets +------- + +Packets are the fundamental transmission unit of the SSH protocol. They are +managed by the packet transport layer, which is essentially the lowest layer +of the driver and is built upon by other components of the SSAM core. +Packets to be transmitted by the SSAM core are represented via |ssh_packet| +(in contrast, packets received by the core do not have any specific +structure and are managed entirely via the raw |ssh_frame|). + +This structure contains the required fields to manage the packet inside the +transport layer, as well as a reference to the buffer containing the data to +be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it +contains an internal reference count, which is used for managing its +lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this +counter reaches zero, the ``release()`` callback provided to the packet via +its |ssh_packet_ops| reference is executed, which may then deallocate the +packet or its enclosing structure (e.g. |ssh_request|). + +In addition to the ``release`` callback, the |ssh_packet_ops| reference also +provides a ``complete()`` callback, which is run once the packet has been +completed and provides the status of this completion, i.e. zero on success +or a negative errno value in case of an error. Once the packet has been +submitted to the packet transport layer, the ``complete()`` callback is +always guaranteed to be executed before the ``release()`` callback, i.e. the +packet will always be completed, either successfully, with an error, or due +to cancellation, before it will be released. + +The state of a packet is managed via its ``state`` flags +(|ssh_packet_flags|), which also contains the packet type. In particular, +the following bits are noteworthy: + +* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either + through error or success, is imminent. It indicates that no further + references of the packet should be taken and any existing references + should be dropped as soon as possible. The process setting this bit is + responsible for removing any references to this packet from the packet + queue and pending set. + +* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the + ``complete()`` callback and is used to ensure that this callback only runs + once. + +* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on + the packet queue and cleared when it is dequeued. + +* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to + the pending set and cleared when it is removed from it. + +Packet Queue +------------ + +The packet queue is the first of the two fundamental collections in the +packet transport layer. It is a priority queue, with priority of the +respective packets based on the packet type (major) and number of tries +(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value. + +All packets to be transmitted by the transport layer must be submitted to +this queue via |ssh_ptl_submit|. Note that this includes control packets +sent by the transport layer itself. Internally, data packets can be +re-submitted to this queue due to timeouts or NAK packets sent by the EC. + +Pending Set +----------- + +The pending set is the second of the two fundamental collections in the +packet transport layer. It stores references to packets that have already +been transmitted, but wait for acknowledgment (e.g. the corresponding ACK +packet) by the EC. + +Note that a packet may both be pending and queued if it has been +re-submitted due to a packet acknowledgment timeout or NAK. On such a +re-submission, packets are not removed from the pending set. + +Transmitter Thread +------------------ + +The transmitter thread is responsible for most of the actual work regarding +packet transmission. In each iteration, it (waits for and) checks if the +next packet on the queue (if any) can be transmitted and, if so, removes it +from the queue and increments its counter for the number of transmission +attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by +the EC, the packet is added to the pending set. Next, the packet's data is +submitted to the serdev subsystem. In case of an error or timeout during +this submission, the packet is completed by the transmitter thread with the +status value of the callback set accordingly. In case the packet is +unsequenced, i.e. does not require an ACK by the EC, the packet is completed +with success on the transmitter thread. + +Transmission of sequenced packets is limited by the number of concurrently +pending packets, i.e. a limit on how many packets may be waiting for an ACK +from the EC in parallel. This limit is currently set to one (see :doc:`ssh` +for the reasoning behind this). Control packets (i.e. ACK and NAK) can +always be transmitted. + +Receiver Thread +--------------- + +Any data received from the EC is put into a FIFO buffer for further +processing. This processing happens on the receiver thread. The receiver +thread parses and validates the received message into its |ssh_frame| and +corresponding payload. It prepares and submits the necessary ACK (and on +validation error or invalid data NAK) packets for the received messages. + +This thread also handles further processing, such as matching ACK messages +to the corresponding pending packet (via sequence ID) and completing it, as +well as initiating re-submission of all currently pending packets on +receival of a NAK message (re-submission in case of a NAK is similar to +re-submission due to timeout, see below for more details on that). Note that +the successful completion of a sequenced packet will always run on the +receiver thread (whereas any failure-indicating completion will run on the +process where the failure occurred). + +Any payload data is forwarded via a callback to the next upper layer, i.e. +the request transport layer. + +Timeout Reaper +-------------- + +The packet acknowledgment timeout is a per-packet timeout for sequenced +packets, started when the respective packet begins (re-)transmission (i.e. +this timeout is armed once per transmission attempt on the transmitter +thread). It is used to trigger re-submission or, when the number of tries +has been exceeded, cancellation of the packet in question. + +This timeout is handled via a dedicated reaper task, which is essentially a +work item (re-)scheduled to run when the next packet is set to time out. The +work item then checks the set of pending packets for any packets that have +exceeded the timeout and, if there are any remaining packets, re-schedules +itself to the next appropriate point in time. + +If a timeout has been detected by the reaper, the packet will either be +re-submitted if it still has some remaining tries left, or completed with +``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and +triggered by receival of a NAK, means that the packet is added to the queue +with a now incremented number of tries, yielding a higher priority. The +timeout for the packet will be disabled until the next transmission attempt +and the packet remains on the pending set. + +Note that due to transmission and packet acknowledgment timeouts, the packet +transport layer is always guaranteed to make progress, if only through +timing out packets, and will never fully block. + +Concurrency and Locking +----------------------- + +There are two main locks in the packet transport layer: One guarding access +to the packet queue and one guarding access to the pending set. These +collections may only be accessed and modified under the respective lock. If +access to both collections is needed, the pending lock must be acquired +before the queue lock to avoid deadlocks. + +In addition to guarding the collections, after initial packet submission +certain packet fields may only be accessed under one of the locks. +Specifically, the packet priority must only be accessed while holding the +queue lock and the packet timestamp must only be accessed while holding the +pending lock. + +Other parts of the packet transport layer are guarded independently. State +flags are managed by atomic bit operations and, if necessary, memory +barriers. Modifications to the timeout reaper work item and expiration date +are guarded by their own lock. + +The reference of the packet to the packet transport layer (``ptl``) is +somewhat special. It is either set when the upper layer request is submitted +or, if there is none, when the packet is first submitted. After it is set, +it will not change its value. Functions that may run concurrently with +submission, i.e. cancellation, can not rely on the ``ptl`` reference to be +set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas +setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry. + +Some packet fields may be read outside of the respective locks guarding +them, specifically priority and state for tracing. In those cases, proper +access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such +read-only access is only allowed when stale values are not critical. + +With respect to the interface for higher layers, packet submission +(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival +(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be +executed concurrently with respect to each other. Note that packet +submission may not run concurrently with itself for the same packet. +Equally, shutdown and data receival may also not run concurrently with +themselves (but may run concurrently with each other). + + +Request Transport Layer +======================= + +The request transport layer is represented via |ssh_rtl| and builds on top +of the packet transport layer. It deals with requests, i.e. SSH packets sent +by the host containing a |ssh_command| as frame payload. This layer +separates responses to requests from events, which are also sent by the EC +via a |ssh_command| payload. While responses are handled in this layer, +events are relayed to the next upper layer, i.e. the controller layer, via +the corresponding callback. The request transport layer is structured around +the following key concepts: + +Request +------- + +Requests are packets with a command-type payload, sent from host to EC to +query data from or trigger an action on it (or both simultaneously). They +are represented by |ssh_request|, wrapping the underlying |ssh_packet| +storing its message data (i.e. SSH frame with command payload). Note that +all top-level representations, e.g. |ssam_request_sync| are built upon this +struct. + +As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the +reference counter inside the packet struct (which can be accessed via +|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the +``release()`` callback of the |ssh_request_ops| reference of the request is +called. + +Requests can have an optional response that is equally sent via a SSH +message with command-type payload (from EC to host). The party constructing +the request must know if a response is expected and mark this in the request +flags provided to |ssh_request_init|, so that the request transport layer +can wait for this response. + +Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback +provided via its request ops reference and is guaranteed to be completed +before it is released once it has been submitted to the request transport +layer via |ssh_rtl_submit|. For a request without a response, successful +completion will occur once the underlying packet has been successfully +transmitted by the packet transport layer (i.e. from within the packet +completion callback). For a request with response, successful completion +will occur once the response has been received and matched to the request +via its request ID (which happens on the packet layer's data-received +callback running on the receiver thread). If the request is completed with +an error, the status value will be set to the corresponding (negative) errno +value. + +The state of a request is again managed via its ``state`` flags +(|ssh_request_flags|), which also encode the request type. In particular, +the following bits are noteworthy: + +* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either + through error or success, is imminent. It indicates that no further + references of the request should be taken and any existing references + should be dropped as soon as possible. The process setting this bit is + responsible for removing any references to this request from the request + queue and pending set. + +* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the + ``complete()`` callback and is used to ensure that this callback only runs + once. + +* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on + the request queue and cleared when it is dequeued. + +* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to + the pending set and cleared when it is removed from it. + +Request Queue +------------- + +The request queue is the first of the two fundamental collections in the +request transport layer. In contrast to the packet queue of the packet +transport layer, it is not a priority queue and the simple first come first +serve principle applies. + +All requests to be transmitted by the request transport layer must be +submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may +not be re-submitted, and will not be re-submitted automatically on timeout. +Instead, the request is completed with a timeout error. If desired, the +caller can create and submit a new request for another try, but it must not +submit the same request again. + +Pending Set +----------- + +The pending set is the second of the two fundamental collections in the +request transport layer. This collection stores references to all pending +requests, i.e. requests awaiting a response from the EC (similar to what the +pending set of the packet transport layer does for packets). + +Transmitter Task +---------------- + +The transmitter task is scheduled when a new request is available for +transmission. It checks if the next request on the request queue can be +transmitted and, if so, submits its underlying packet to the packet +transport layer. This check ensures that only a limited number of +requests can be pending, i.e. waiting for a response, at the same time. If +the request requires a response, the request is added to the pending set +before its packet is submitted. + +Packet Completion Callback +-------------------------- + +The packet completion callback is executed once the underlying packet of a +request has been completed. In case of an error completion, the +corresponding request is completed with the error value provided in this +callback. + +On successful packet completion, further processing depends on the request. +If the request expects a response, it is marked as transmitted and the +request timeout is started. If the request does not expect a response, it is +completed with success. + +Data-Received Callback +---------------------- + +The data received callback notifies the request transport layer of data +being received by the underlying packet transport layer via a data-type +frame. In general, this is expected to be a command-type payload. + +If the request ID of the command is one of the request IDs reserved for +events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the +event callback registered in the request transport layer. If the request ID +indicates a response to a request, the respective request is looked up in +the pending set and, if found and marked as transmitted, completed with +success. + +Timeout Reaper +-------------- + +The request-response-timeout is a per-request timeout for requests expecting +a response. It is used to ensure that a request does not wait indefinitely +on a response from the EC and is started after the underlying packet has +been successfully completed. + +This timeout is, similar to the packet acknowledgment timeout on the packet +transport layer, handled via a dedicated reaper task. This task is +essentially a work-item (re-)scheduled to run when the next request is set +to time out. The work item then scans the set of pending requests for any +requests that have timed out and completes them with ``-ETIMEDOUT`` as +status. Requests will not be re-submitted automatically. Instead, the issuer +of the request must construct and submit a new request, if so desired. + +Note that this timeout, in combination with packet transmission and +acknowledgment timeouts, guarantees that the request layer will always make +progress, even if only through timing out packets, and never fully block. + +Concurrency and Locking +----------------------- + +Similar to the packet transport layer, there are two main locks in the +request transport layer: One guarding access to the request queue and one +guarding access to the pending set. These collections may only be accessed +and modified under the respective lock. + +Other parts of the request transport layer are guarded independently. State +flags are (again) managed by atomic bit operations and, if necessary, memory +barriers. Modifications to the timeout reaper work item and expiration date +are guarded by their own lock. + +Some request fields may be read outside of the respective locks guarding +them, specifically the state for tracing. In those cases, proper access is +ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only +access is only allowed when stale values are not critical. + +With respect to the interface for higher layers, request submission +(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer +shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with +respect to each other. Note that request submission may not run concurrently +with itself for the same request (and also may only be called once per +request). Equally, shutdown may also not run concurrently with itself. + + +Controller Layer +================ + +The controller layer extends on the request transport layer to provide an +easy-to-use interface for client drivers. It is represented by +|ssam_controller| and the SSH driver. While the lower level transport layers +take care of transmitting and handling packets and requests, the controller +layer takes on more of a management role. Specifically, it handles device +initialization, power management, and event handling, including event +delivery and registration via the (event) completion system (|ssam_cplt|). + +Event Registration +------------------ + +In general, an event (or rather a class of events) has to be explicitly +requested by the host before the EC will send it (HID input events seem to +be the exception). This is done via an event-enable request (similarly, +events should be disabled via an event-disable request once no longer +desired). + +The specific request used to enable (or disable) an event is given via an +event registry, i.e. the governing authority of this event (so to speak), +represented by |ssam_event_registry|. As parameters to this request, the +target category and, depending on the event registry, instance ID of the +event to be enabled must be provided. This (optional) instance ID must be +zero if the registry does not use it. Together, target category and instance +ID form the event ID, represented by |ssam_event_id|. In short, both, event +registry and event ID, are required to uniquely identify a respective class +of events. + +Note that a further *request ID* parameter must be provided for the +enable-event request. This parameter does not influence the class of events +being enabled, but instead is set as the request ID (RQID) on each event of +this class sent by the EC. It is used to identify events (as a limited +number of request IDs is reserved for use in events only, specifically one +to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific +class. Currently, the controller always sets this parameter to the target +category specified in |ssam_event_id|. + +As multiple client drivers may rely on the same (or overlapping) classes of +events and enable/disable calls are strictly binary (i.e. on/off), the +controller has to manage access to these events. It does so via reference +counting, storing the counter inside an RB-tree based mapping with event +registry and ID as key (there is no known list of valid event registry and +event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and +|ssam_nf_refcount_dec| for details. + +This management is done together with notifier registration (described in +the next section) via the top-level |ssam_notifier_register| and +|ssam_notifier_unregister| functions. + +Event Delivery +-------------- + +To receive events, a client driver has to register an event notifier via +|ssam_notifier_register|. This increments the reference counter for that +specific class of events (as detailed in the previous section), enables the +class on the EC (if it has not been enabled already), and installs the +provided notifier callback. + +Notifier callbacks are stored in lists, with one (RCU) list per target +category (provided via the event ID; NB: there is a fixed known number of +target categories). There is no known association from the combination of +event registry and event ID to the command data (target ID, target category, +command ID, and instance ID) that can be provided by an event class, apart +from target category and instance ID given via the event ID. + +Note that due to the way notifiers are (or rather have to be) stored, client +drivers may receive events that they have not requested and need to account +for them. Specifically, they will, by default, receive all events from the +same target category. To simplify dealing with this, filtering of events by +target ID (provided via the event registry) and instance ID (provided via +the event ID) can be requested when registering a notifier. This filtering +is applied when iterating over the notifiers at the time they are executed. + +All notifier callbacks are executed on a dedicated workqueue, the so-called +completion workqueue. After an event has been received via the callback +installed in the request layer (running on the receiver thread of the packet +transport layer), it will be put on its respective event queue +(|ssam_event_queue|). From this event queue the completion work item of that +queue (running on the completion workqueue) will pick up the event and +execute the notifier callback. This is done to avoid blocking on the +receiver thread. + +There is one event queue per combination of target ID and target category. +This is done to ensure that notifier callbacks are executed in sequence for +events of the same target ID and target category. Callbacks can be executed +in parallel for events with a different combination of target ID and target +category. + +Concurrency and Locking +----------------------- + +Most of the concurrency related safety guarantees of the controller are +provided by the lower-level request transport layer. In addition to this, +event (un-)registration is guarded by its own lock. + +Access to the controller state is guarded by the state lock. This lock is a +read/write semaphore. The reader part can be used to ensure that the state +does not change while functions depending on the state to stay the same +(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|, +|ssam_request_sync_submit|, and derivatives) are executed and this guarantee +is not already provided otherwise (e.g. through |ssam_client_bind| or +|ssam_client_link|). The writer part guards any transitions that will change +the state, i.e. initialization, destruction, suspension, and resumption. + +The controller state may be accessed (read-only) outside the state lock for +smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|). +Note that such checks are not supposed to (and will not) protect against all +invalid usages, but rather aim to help catch them. In those cases, proper +variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. + +Assuming any preconditions on the state not changing have been satisfied, +all non-initialization and non-shutdown functions may run concurrently with +each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|, +|ssam_request_sync_submit|, as well as all functions building on top of those. diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst new file mode 100644 index 000000000000..1e9d57e50063 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/overview.rst @@ -0,0 +1,77 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +======== +Overview +======== + +The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*) +embedded controller (EC) on Microsoft Surface devices. It has been originally +introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but +its responsibilities and feature-set have since been expanded significantly +with the following generations. + + +Features and Integration +======================== + +Not much is currently known about SAM on 4th generation devices (Surface Pro +4, Surface Book 1), due to the use of a different communication interface +between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface +Book 2, Surface Laptop 1) and later generation devices, SAM is responsible +for providing battery information (both current status and static values, +such as maximum capacity etc.), as well as an assortment of temperature +sensors (e.g. skin temperature) and cooling/performance-mode setting to the +host. On the Surface Book 2, specifically, it additionally provides an +interface for properly handling clipboard detachment (i.e. separating the +display part from the keyboard part of the device), on the Surface Laptop 1 +and 2 it is required for keyboard HID input. This HID subsystem has been +restructured for 7th generation devices and on those, specifically Surface +Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e. +keyboard and touchpad). + +While features have not changed much on a coarse level since the 5th +generation, internal interfaces have undergone some rather large changes. On +5th and 6th generation devices, both battery and temperature information is +exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or +SAN), translating ACPI generic serial bus write-/read-accesses to SAM +requests. On 7th generation devices, this additional layer is gone and these +devices require a driver hooking directly into the SAM interface. Equally, +on newer generations, less devices are declared in ACPI, making them a bit +harder to discover and requiring us to hard-code a sort of device registry. +Due to this, a SSAM bus and subsystem with client devices +(:c:type:`struct ssam_device `) has been implemented. + + +Communication +============= + +The type of communication interface between host and EC depends on the +generation of the Surface device. On 4th generation devices, host and EC +communicate via HID, specifically using a HID-over-I2C device, whereas on +5th and later generations, communication takes place via a USART serial +device. In accordance to the drivers found on other operating systems, we +refer to the serial device and its driver as Surface Serial Hub (SSH). When +needed, we differentiate between both types of SAM by referring to them as +SAM-over-SSH and SAM-over-HID. + +Currently, this subsystem only supports SAM-over-SSH. The SSH communication +interface is described in more detail below. The HID interface has not been +reverse engineered yet and it is, at the moment, unclear how many (and +which) concepts of the SSH interface detailed below can be transferred to +it. + +Surface Serial Hub +------------------ + +As already elaborated above, the Surface Serial Hub (SSH) is the +communication interface for SAM on 5th- and all later-generation Surface +devices. On the highest level, communication can be separated into two main +types: Requests, messages sent from host to EC that may trigger a direct +response from the EC (explicitly associated with the request), and events +(sometimes also referred to as notifications), sent from EC to host without +being a direct response to a previous request. We may also refer to requests +without response as commands. In general, events need to be enabled via one +of multiple dedicated requests before they are sent by the EC. + +See :doc:`ssh` for a more technical protocol documentation and +:doc:`internal` for an overview of the internal driver architecture. diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst new file mode 100644 index 000000000000..bf007d6c9873 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/ssh.rst @@ -0,0 +1,344 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` +.. |TYPE| replace:: ``TYPE`` +.. |LEN| replace:: ``LEN`` +.. |SEQ| replace:: ``SEQ`` +.. |SYN| replace:: ``SYN`` +.. |NAK| replace:: ``NAK`` +.. |ACK| replace:: ``ACK`` +.. |DATA| replace:: ``DATA`` +.. |DATA_SEQ| replace:: ``DATA_SEQ`` +.. |DATA_NSQ| replace:: ``DATA_NSQ`` +.. |TC| replace:: ``TC`` +.. |TID| replace:: ``TID`` +.. |IID| replace:: ``IID`` +.. |RQID| replace:: ``RQID`` +.. |CID| replace:: ``CID`` + +=========================== +Surface Serial Hub Protocol +=========================== + +The Surface Serial Hub (SSH) is the central communication interface for the +embedded Surface Aggregator Module controller (SAM or EC), found on newer +Surface generations. We will refer to this protocol and interface as +SAM-over-SSH, as opposed to SAM-over-HID for the older generations. + +On Surface devices with SAM-over-SSH, SAM is connected to the host via UART +and defined in ACPI as device with ID ``MSHW0084``. On these devices, +significant functionality is provided via SAM, including access to battery +and power information and events, thermal read-outs and events, and many +more. For Surface Laptops, keyboard input is handled via HID directed +through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes +touchpad input. + +Note that the standard disclaimer for this subsystem also applies to this +document: All of this has been reverse-engineered and may thus be erroneous +and/or incomplete. + +All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``. +All multi-byte values are little-endian, there is no implicit padding between +values. + + +SSH Packet Protocol: Definitions +================================ + +The fundamental communication unit of the SSH protocol is a frame +(:c:type:`struct ssh_frame `). A frame consists of the following +fields, packed together and in order: + +.. flat-table:: SSH Frame + :widths: 1 1 4 + :header-rows: 1 + + * - Field + - Type + - Description + + * - |TYPE| + - |u8| + - Type identifier of the frame. + + * - |LEN| + - |u16| + - Length of the payload associated with the frame. + + * - |SEQ| + - |u8| + - Sequence ID (see explanation below). + +Each frame structure is followed by a CRC over this structure. The CRC over +the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly +after the frame structure and before the payload. The payload is followed by +its own CRC (over all payload bytes). If the payload is not present (i.e. +the frame has ``LEN=0``), the CRC of the payload is still present and will +evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it +equals the number of bytes inbetween the CRC of the frame and the CRC of the +payload. + +Additionally, the following fixed two-byte sequences are used: + +.. flat-table:: SSH Byte Sequences + :widths: 1 1 4 + :header-rows: 1 + + * - Name + - Value + - Description + + * - |SYN| + - ``[0xAA, 0x55]`` + - Synchronization bytes. + +A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and +CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes, +followed finally, regardless if the payload is present, the payload CRC. The +messages corresponding to an exchange are, in part, identified by having the +same sequence ID (|SEQ|), stored inside the frame (more on this in the next +section). The sequence ID is a wrapping counter. + +A frame can have the following types +(:c:type:`enum ssh_frame_type `): + +.. flat-table:: SSH Frame Types + :widths: 1 1 4 + :header-rows: 1 + + * - Name + - Value + - Short Description + + * - |NAK| + - ``0x04`` + - Sent on error in previously received message. + + * - |ACK| + - ``0x40`` + - Sent to acknowledge receival of |DATA| frame. + + * - |DATA_SEQ| + - ``0x80`` + - Sent to transfer data. Sequenced. + + * - |DATA_NSQ| + - ``0x00`` + - Same as |DATA_SEQ|, but does not need to be ACKed. + +Both |NAK|- and |ACK|-type frames are used to control flow of messages and +thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the +other hand must carry a payload. The flow sequence and interaction of +different frame types will be described in more depth in the next section. + + +SSH Packet Protocol: Flow Sequence +================================== + +Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or +|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In +case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a +|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of +the frame by responding with a message containing an |ACK|-type frame with +the same sequence ID of the |DATA| frame. In other words, the sequence ID of +the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an +error, e.g. an invalid CRC, the receiving party responds with a message +containing an |NAK|-type frame. As the sequence ID of the previous data +frame, for which an error is indicated via the |NAK| frame, cannot be relied +upon, the sequence ID of the |NAK| frame should not be used and is set to +zero. After receival of an |NAK| frame, the sending party should re-send all +outstanding (non-ACKed) messages. + +Sequence IDs are not synchronized between the two parties, meaning that they +are managed independently for each party. Identifying the messages +corresponding to a single exchange thus relies on the sequence ID as well as +the type of the message, and the context. Specifically, the sequence ID is +used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not +``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. + +An example exchange might look like this: + +:: + + tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- + rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- + +where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)`` +indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame, +``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the +previous payload. In case of an error, the exchange would look like this: + +:: + + tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- + rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- + +upon which the sender should re-send the message. ``FRAME(N)`` indicates an +|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed +to zero. For |DATA_NSQ|-type frames, both exchanges are the same: + +:: + + tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ---------------------- + rx: ------------------------------------------------------------------- + +Here, an error can be detected, but not corrected or indicated to the +sending party. These exchanges are symmetric, i.e. switching ``rx`` and +``tx`` results again in a valid exchange. Currently, no longer exchanges are +known. + + +Commands: Requests, Responses, and Events +========================================= + +Commands are sent as payload inside a data frame. Currently, this is the +only known payload type of |DATA| frames, with a payload-type value of +``0x80`` (:c:type:`SSH_PLD_TYPE_CMD `). + +The command-type payload (:c:type:`struct ssh_command `) +consists of an eight-byte command structure, followed by optional and +variable length command data. The length of this optional data is derived +from the frame payload length given in the corresponding frame, i.e. it is +``frame.len - sizeof(struct ssh_command)``. The command struct contains the +following fields, packed together and in order: + +.. flat-table:: SSH Command + :widths: 1 1 4 + :header-rows: 1 + + * - Field + - Type + - Description + + * - |TYPE| + - |u8| + - Type of the payload. For commands always ``0x80``. + + * - |TC| + - |u8| + - Target category. + + * - |TID| (out) + - |u8| + - Target ID for outgoing (host to EC) commands. + + * - |TID| (in) + - |u8| + - Target ID for incoming (EC to host) commands. + + * - |IID| + - |u8| + - Instance ID. + + * - |RQID| + - |u16| + - Request ID. + + * - |CID| + - |u8| + - Command ID. + +The command struct and data, in general, does not contain any failure +detection mechanism (e.g. CRCs), this is solely done on the frame level. + +Command-type payloads are used by the host to send commands and requests to +the EC as well as by the EC to send responses and events back to the host. +We differentiate between requests (sent by the host), responses (sent by the +EC in response to a request), and events (sent by the EC without a preceding +request). + +Commands and events are uniquely identified by their target category +(``TC``) and command ID (``CID``). The target category specifies a general +category for the command (e.g. system in general, vs. battery and AC, vs. +temperature, and so on), while the command ID specifies the command inside +that category. Only the combination of |TC| + |CID| is unique. Additionally, +commands have an instance ID (``IID``), which is used to differentiate +between different sub-devices. For example ``TC=3`` ``CID=1`` is a +request to get the temperature on a thermal sensor, where |IID| specifies +the respective sensor. If the instance ID is not used, it should be set to +zero. If instance IDs are used, they, in general, start with a value of one, +whereas zero may be used for instance independent queries, if applicable. A +response to a request should have the same target category, command ID, and +instance ID as the corresponding request. + +Responses are matched to their corresponding request via the request ID +(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence +ID on the frames. Note that the sequence ID of the frames for a +request-response pair does not match. Only the request ID has to match. +Frame-protocol wise these are two separate exchanges, and may even be +separated, e.g. by an event being sent after the request but before the +response. Not all commands produce a response, and this is not detectable by +|TC| + |CID|. It is the responsibility of the issuing party to wait for a +response (or signal this to the communication framework, as is done in +SAN/ACPI via the ``SNC`` flag). + +Events are identified by unique and reserved request IDs. These IDs should +not be used by the host when sending a new request. They are used on the +host to, first, detect events and, second, match them with a registered +event handler. Request IDs for events are chosen by the host and directed to +the EC when setting up and enabling an event source (via the +enable-event-source request). The EC then uses the specified request ID for +events sent from the respective source. Note that an event should still be +identified by its target category, command ID, and, if applicable, instance +ID, as a single event source can send multiple different event types. In +general, however, a single target category should map to a single reserved +event request ID. + +Furthermore, requests, responses, and events have an associated target ID +(``TID``). This target ID is split into output (host to EC) and input (EC to +host) fields, with the respecting other field (e.g. output field on incoming +messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and +secondary (``0x02``). In general, the response to a request should have the +same ``TID`` value, however, the field (output vs. input) should be used in +accordance to the direction in which the response is sent (i.e. on the input +field, as responses are generally sent from the EC to the host). + +Note that, even though requests and events should be uniquely identifiable +by target category and command ID alone, the EC may require specific +target ID and instance ID values to accept a command. A command that is +accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` +and vice versa. + + +Limitations and Observations +============================ + +The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel, +with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for +events). In practice, however, this is more limited. From our testing +(although via a python and thus a user-space program), it seems that the EC +can handle up to four requests (mostly) reliably in parallel at a certain +time. With five or more requests in parallel, consistent discarding of +commands (ACKed frame but no command response) has been observed. For five +simultaneous commands, this reproducibly resulted in one command being +dropped and four commands being handled. + +However, it has also been noted that, even with three requests in parallel, +occasional frame drops happen. Apart from this, with a limit of three +pending requests, no dropped commands (i.e. command being dropped but frame +carrying command being ACKed) have been observed. In any case, frames (and +possibly also commands) should be re-sent by the host if a certain timeout +is exceeded. This is done by the EC for frames with a timeout of one second, +up to two re-tries (i.e. three transmissions in total). The limit of +re-tries also applies to received NAKs, and, in a worst case scenario, can +lead to entire messages being dropped. + +While this also seems to work fine for pending data frames as long as no +transmission failures occur, implementation and handling of these seems to +depend on the assumption that there is only one non-acknowledged data frame. +In particular, the detection of repeated frames relies on the last sequence +number. This means that, if a frame that has been successfully received by +the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC +will only detect this if it has the sequence ID of the last frame received +by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1`` +followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0`` +frame as such, and thus execute the command in this frame each time it has +been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and +then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of +the first one and ignore it, thus executing the contained command only once. + +In conclusion, this suggests a limit of at most one pending un-ACKed frame +(per party, effectively leading to synchronous communication regarding +frames) and at most three pending commands. The limit to synchronous frame +transfers seems to be consistent with behavior observed on Windows. diff --git a/Documentation/driver-api/thermal/sysfs-api.rst b/Documentation/driver-api/thermal/sysfs-api.rst index e7520cb439ac..a4969c474cc3 100644 --- a/Documentation/driver-api/thermal/sysfs-api.rst +++ b/Documentation/driver-api/thermal/sysfs-api.rst @@ -520,19 +520,6 @@ available_policies RW, Optional -passive - Attribute is only present for zones in which the passive cooling - policy is not supported by native thermal driver. Default is zero - and can be set to a temperature (in millidegrees) to enable a - passive trip point for the zone. Activation is done by polling with - an interval of 1 second. - - Unit: millidegrees Celsius - - Valid values: 0 (disabled) or greater than 1000 - - RW, Optional - emul_temp Interface to set the emulated temperature method in thermal zone (sensor). After setting this temperature, the thermal zone may pass diff --git a/Documentation/hwmon/ab8500.rst b/Documentation/hwmon/ab8500.rst deleted file mode 100644 index 33f93a9cec04..000000000000 --- a/Documentation/hwmon/ab8500.rst +++ /dev/null @@ -1,26 +0,0 @@ -Kernel driver ab8500 -==================== - -Supported chips: - - * ST-Ericsson AB8500 - - Prefix: 'ab8500' - - Addresses scanned: - - - Datasheet: http://www.stericsson.com/developers/documentation.jsp - -Authors: - - Martin Persson - - Hongbo Zhang - -Description ------------ - -See also Documentation/hwmon/abx500.rst. This is the ST-Ericsson AB8500 specific -driver. - -Currently only the AB8500 internal sensor and one external sensor for battery -temperature are monitored. Other GPADC channels can also be monitored if needed -in future. diff --git a/Documentation/hwmon/abx500.rst b/Documentation/hwmon/abx500.rst deleted file mode 100644 index 3d88b2ce0f00..000000000000 --- a/Documentation/hwmon/abx500.rst +++ /dev/null @@ -1,32 +0,0 @@ -Kernel driver abx500 -==================== - -Supported chips: - - * ST-Ericsson ABx500 series - - Prefix: 'abx500' - - Addresses scanned: - - - Datasheet: http://www.stericsson.com/developers/documentation.jsp - -Authors: - Martin Persson - Hongbo Zhang - -Description ------------ - -Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically, -this is kernel hwmon driver for ABx500. - -There are some GPADCs inside ABx500 which are designed for connecting to -thermal sensors, and there is also a thermal sensor inside ABx500 too, which -raises interrupt when critical temperature reached. - -This abx500 is a common layer which can monitor all of the sensors, every -specific abx500 chip has its special configurations in its own file, e.g. some -sensors can be configured invisible if they are not available on that chip, and -the corresponding gpadc_addr should be set to 0, thus this sensor won't be -polled. diff --git a/Documentation/hwmon/aht10.rst b/Documentation/hwmon/aht10.rst new file mode 100644 index 000000000000..482262ca117c --- /dev/null +++ b/Documentation/hwmon/aht10.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver aht10 +===================== + +Supported chips: + + * Aosong AHT10 + + Prefix: 'aht10' + + Addresses scanned: None + + Datasheet: + + Chinese: http://www.aosong.com/userfiles/files/media/AHT10%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C%20A3%2020201210.pdf + English: https://server4.eca.ir/eshop/AHT10/Aosong_AHT10_en_draft_0c.pdf + +Author: Johannes Cornelis Draaijer + + +Description +----------- + +The AHT10 is a Temperature and Humidity sensor + +The address of this i2c device may only be 0x38 + +Usage Notes +----------- + +This driver does not probe for AHT10 devices, as there is no reliable +way to determine if an i2c chip is or isn't an AHT10. The device has +to be instantiated explicitly with the address 0x38. See +Documentation/i2c/instantiating-devices.rst for details. + +Sysfs entries +------------- + +=============== ============================================ +temp1_input Measured temperature in millidegrees Celcius +humidity1_input Measured humidity in %H +update_interval The minimum interval for polling the sensor, + in milliseconds. Writable. Must be at + least 2000. +=============== ============================================ diff --git a/Documentation/hwmon/ina2xx.rst b/Documentation/hwmon/ina2xx.rst index f78a5cd44c4c..27d2e39bc8ac 100644 --- a/Documentation/hwmon/ina2xx.rst +++ b/Documentation/hwmon/ina2xx.rst @@ -74,7 +74,7 @@ bus supply voltage. The shunt value in micro-ohms can be set via platform data or device tree at compile-time or via the shunt_resistor attribute in sysfs at run-time. Please -refer to the Documentation/devicetree/bindings/hwmon/ina2xx.txt for bindings +refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings if the device tree is used. Additionally ina226 supports update_interval attribute as described in diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index fcb870ce6286..8d5a2df1ecb6 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -18,10 +18,8 @@ Hardware Monitoring Kernel Drivers .. toctree:: :maxdepth: 1 - ab8500 abituguru abituguru3 - abx500 acpi_power_meter ad7314 adc128d818 @@ -39,6 +37,7 @@ Hardware Monitoring Kernel Drivers adt7462 adt7470 adt7475 + aht10 amc6821 amd_energy asb100 @@ -178,6 +177,7 @@ Hardware Monitoring Kernel Drivers tmp401 tmp421 tmp513 + tps23861 tps40422 tps53679 twl4030-madc-hwmon diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst index 346e74674c51..92c0a7d7808c 100644 --- a/Documentation/hwmon/max16601.rst +++ b/Documentation/hwmon/max16601.rst @@ -5,6 +5,14 @@ Kernel driver max16601 Supported chips: + * Maxim MAX16508 + + Prefix: 'max16508' + + Addresses scanned: - + + Datasheet: Not published + * Maxim MAX16601 Prefix: 'max16601' @@ -19,8 +27,8 @@ Author: Guenter Roeck Description ----------- -This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator -Chipset. +This driver supports the MAX16508 VR13 Dual-Output Voltage Regulator +as well as the MAX16601 VR13.HC Dual-Output Voltage Regulator chipsets. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. @@ -45,115 +53,76 @@ Sysfs entries The following attributes are supported. -======================= ======================================================= -in1_label "vin1" -in1_input VCORE input voltage. -in1_alarm Input voltage alarm. +=============================== =============================================== +in1_label "vin1" +in1_input VCORE input voltage. +in1_alarm Input voltage alarm. -in2_label "vout1" -in2_input VCORE output voltage. -in2_alarm Output voltage alarm. +in2_label "vout1" +in2_input VCORE output voltage. +in2_alarm Output voltage alarm. -curr1_label "iin1" -curr1_input VCORE input current, derived from duty cycle and output - current. -curr1_max Maximum input current. -curr1_max_alarm Current high alarm. +curr1_label "iin1" +curr1_input VCORE input current, derived from duty cycle + and output current. +curr1_max Maximum input current. +curr1_max_alarm Current high alarm. -curr2_label "iin1.0" -curr2_input VCORE phase 0 input current. +curr[P+2]_label "iin1.P" +curr[P+2]_input VCORE phase P input current. -curr3_label "iin1.1" -curr3_input VCORE phase 1 input current. +curr[N+2]_label "iin2" +curr[N+2]_input VCORE input current, derived from sensor + element. + 'N' is the number of enabled/populated phases. -curr4_label "iin1.2" -curr4_input VCORE phase 2 input current. +curr[N+3]_label "iin3" +curr[N+3]_input VSA input current. -curr5_label "iin1.3" -curr5_input VCORE phase 3 input current. +curr[N+4]_label "iout1" +curr[N+4]_input VCORE output current. +curr[N+4]_crit Critical output current. +curr[N+4]_crit_alarm Output current critical alarm. +curr[N+4]_max Maximum output current. +curr[N+4]_max_alarm Output current high alarm. -curr6_label "iin1.4" -curr6_input VCORE phase 4 input current. +curr[N+P+5]_label "iout1.P" +curr[N+P+5]_input VCORE phase P output current. -curr7_label "iin1.5" -curr7_input VCORE phase 5 input current. +curr[2*N+5]_label "iout3" +curr[2*N+5]_input VSA output current. +curr[2*N+5]_highest Historical maximum VSA output current. +curr[2*N+5]_reset_history Write any value to reset curr21_highest. +curr[2*N+5]_crit Critical output current. +curr[2*N+5]_crit_alarm Output current critical alarm. +curr[2*N+5]_max Maximum output current. +curr[2*N+5]_max_alarm Output current high alarm. -curr8_label "iin1.6" -curr8_input VCORE phase 6 input current. +power1_label "pin1" +power1_input Input power, derived from duty cycle and output + current. +power1_alarm Input power alarm. -curr9_label "iin1.7" -curr9_input VCORE phase 7 input current. +power2_label "pin2" +power2_input Input power, derived from input current sensor. -curr10_label "iin2" -curr10_input VCORE input current, derived from sensor element. +power3_label "pout" +power3_input Output power. -curr11_label "iin3" -curr11_input VSA input current. +temp1_input VCORE temperature. +temp1_crit Critical high temperature. +temp1_crit_alarm Chip temperature critical high alarm. +temp1_max Maximum temperature. +temp1_max_alarm Chip temperature high alarm. -curr12_label "iout1" -curr12_input VCORE output current. -curr12_crit Critical output current. -curr12_crit_alarm Output current critical alarm. -curr12_max Maximum output current. -curr12_max_alarm Output current high alarm. +temp2_input TSENSE_0 temperature +temp3_input TSENSE_1 temperature +temp4_input TSENSE_2 temperature +temp5_input TSENSE_3 temperature -curr13_label "iout1.0" -curr13_input VCORE phase 0 output current. - -curr14_label "iout1.1" -curr14_input VCORE phase 1 output current. - -curr15_label "iout1.2" -curr15_input VCORE phase 2 output current. - -curr16_label "iout1.3" -curr16_input VCORE phase 3 output current. - -curr17_label "iout1.4" -curr17_input VCORE phase 4 output current. - -curr18_label "iout1.5" -curr18_input VCORE phase 5 output current. - -curr19_label "iout1.6" -curr19_input VCORE phase 6 output current. - -curr20_label "iout1.7" -curr20_input VCORE phase 7 output current. - -curr21_label "iout3" -curr21_input VSA output current. -curr21_highest Historical maximum VSA output current. -curr21_reset_history Write any value to reset curr21_highest. -curr21_crit Critical output current. -curr21_crit_alarm Output current critical alarm. -curr21_max Maximum output current. -curr21_max_alarm Output current high alarm. - -power1_label "pin1" -power1_input Input power, derived from duty cycle and output current. -power1_alarm Input power alarm. - -power2_label "pin2" -power2_input Input power, derived from input current sensor. - -power3_label "pout" -power3_input Output power. - -temp1_input VCORE temperature. -temp1_crit Critical high temperature. -temp1_crit_alarm Chip temperature critical high alarm. -temp1_max Maximum temperature. -temp1_max_alarm Chip temperature high alarm. - -temp2_input TSENSE_0 temperature -temp3_input TSENSE_1 temperature -temp4_input TSENSE_2 temperature -temp5_input TSENSE_3 temperature - -temp6_input VSA temperature. -temp6_crit Critical high temperature. -temp6_crit_alarm Chip temperature critical high alarm. -temp6_max Maximum temperature. -temp6_max_alarm Chip temperature high alarm. -======================= ======================================================= +temp6_input VSA temperature. +temp6_crit Critical high temperature. +temp6_crit_alarm Chip temperature critical high alarm. +temp6_max Maximum temperature. +temp6_max_alarm Chip temperature high alarm. +=============================== =============================================== diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst index 8646ad519fcd..2e1408d174bd 100644 --- a/Documentation/hwmon/nct6683.rst +++ b/Documentation/hwmon/nct6683.rst @@ -61,5 +61,6 @@ Board Firmware version Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13 Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13 Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13 +ASRock X570 NCT6683D EC firmware version 1.0 build 06/28/19 MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20 =============== =============================================== diff --git a/Documentation/hwmon/tps23861.rst b/Documentation/hwmon/tps23861.rst new file mode 100644 index 000000000000..46d121ff3f31 --- /dev/null +++ b/Documentation/hwmon/tps23861.rst @@ -0,0 +1,41 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver tps23861 +====================== + +Supported chips: + * Texas Instruments TPS23861 + + Prefix: 'tps23861' + + Datasheet: https://www.ti.com/lit/gpn/tps23861 + +Author: Robert Marko + +Description +----------- + +This driver supports hardware monitoring for Texas Instruments TPS23861 PoE PSE. + +TPS23861 is a quad port IEEE802.3at PSE controller with optional I2C control +and monitoring capabilities. + +TPS23861 offers three modes of operation: Auto, Semi-Auto and Manual. + +This driver only supports the Auto mode of operation providing monitoring +as well as enabling/disabling the four ports. + +Sysfs entries +------------- + +======================= ===================================================================== +in[0-3]_input Voltage on ports [1-4] +in[0-3]_label "Port[1-4]" +in4_input IC input voltage +in4_label "Input" +temp1_input IC die temperature +temp1_label "Die" +curr[1-4]_input Current on ports [1-4] +in[1-4]_label "Port[1-4]" +in[0-3]_enable Enable/disable ports [1-4] +======================= ===================================================================== diff --git a/Documentation/i2c/slave-testunit-backend.rst b/Documentation/i2c/slave-testunit-backend.rst index 2c38e64f0bac..ecfc2abec32d 100644 --- a/Documentation/i2c/slave-testunit-backend.rst +++ b/Documentation/i2c/slave-testunit-backend.rst @@ -22,8 +22,9 @@ Instantiating the device is regular. Example for bus 0, address 0x30: After that, you will have a write-only device listening. Reads will just return an 8-bit version number of the testunit. When writing, the device consists of 4 -8-bit registers and all must be written to start a testcase, i.e. you must -always write 4 bytes to the device. The registers are: +8-bit registers and, except for some "partial" commands, all registers must be +written to start a testcase, i.e. you usually write 4 bytes to the device. The +registers are: 0x00 CMD - which test to trigger 0x01 DATAL - configuration byte 1 for the test @@ -67,3 +68,21 @@ status word is currently ignored in the Linux Kernel. Example to send a notification after 10ms: # i2cset -y 0 0x30 0x02 0x42 0x64 0x01 i + +0x03 SMBUS_BLOCK_PROC_CALL (partial command) + DATAL - must be '1', i.e. one further byte will be written + DATAH - number of bytes to be sent back + DELAY - not applicable, partial command! + +This test will respond to a block process call as defined by the SMBus +specification. The one data byte written specifies how many bytes will be sent +back in the following read transfer. Note that in this read transfer, the +testunit will prefix the length of the bytes to follow. So, if your host bus +driver emulates SMBus calls like the majority does, it needs to support the +I2C_M_RECV_LEN flag of an i2c_msg. This is a good testcase for it. The returned +data consists of the length first, and then of an array of bytes from length-1 +to 0. Here is an example which emulates i2c_smbus_block_process_call() using +i2ctransfer (you need i2c-tools v4.2 or later): + +# i2ctransfer -y 0 w3@0x30 0x03 0x01 0x10 r? +0x10 0x0f 0x0e 0x0d 0x0c 0x0b 0x0a 0x09 0x08 0x07 0x06 0x05 0x04 0x03 0x02 0x01 0x00 diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index a4c75a28c839..b5231d7f9200 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -324,6 +324,8 @@ Code Seq# Include File Comments 0xA3 90-9F linux/dtlk.h 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem 0xA4 00-1F uapi/asm/sgx.h +0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator + 0xAA 00-3F linux/uapi/linux/userfaultfd.h 0xAB 00-1F linux/nbd.h 0xAC 00-1F linux/raw.h diff --git a/MAINTAINERS b/MAINTAINERS index 8982a9ebbaa6..061f64b93a4c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2775,6 +2775,15 @@ F: Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic. F: drivers/irqchip/irq-aspeed-scu-ic.c F: include/dt-bindings/interrupt-controller/aspeed-scu-ic.h +ASPEED SD/MMC DRIVER +M: Andrew Jeffery +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +L: openbmc@lists.ozlabs.org (moderated for non-subscribers) +L: linux-mmc@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml +F: drivers/mmc/host/sdhci-of-aspeed* + ASPEED VIDEO ENGINE DRIVER M: Eddie James L: linux-media@vger.kernel.org @@ -4946,17 +4955,17 @@ M: Matthew Garrett M: Pali Rohár L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-laptop.c +F: drivers/platform/x86/dell/dell-laptop.c DELL LAPTOP FREEFALL DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-smo8800.c +F: drivers/platform/x86/dell/dell-smo8800.c DELL LAPTOP RBTN DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-rbtn.* +F: drivers/platform/x86/dell/dell-rbtn.* DELL LAPTOP SMM DRIVER M: Pali Rohár @@ -4968,26 +4977,26 @@ DELL REMOTE BIOS UPDATE DRIVER M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell_rbu.c +F: drivers/platform/x86/dell/dell_rbu.c DELL SMBIOS DRIVER M: Pali Rohár M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios.* +F: drivers/platform/x86/dell/dell-smbios.* DELL SMBIOS SMM DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-smm.c +F: drivers/platform/x86/dell/dell-smbios-smm.c DELL SMBIOS WMI DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-wmi.c +F: drivers/platform/x86/dell/dell-smbios-wmi.c F: tools/wmi/dell-smbios-example.c DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas) @@ -4995,12 +5004,12 @@ M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/driver-api/dcdbas.rst -F: drivers/platform/x86/dcdbas.* +F: drivers/platform/x86/dell/dcdbas.* DELL WMI DESCRIPTOR DRIVER M: Mario Limonciello S: Maintained -F: drivers/platform/x86/dell-wmi-descriptor.c +F: drivers/platform/x86/dell/dell-wmi-descriptor.c DELL WMI SYSMAN DRIVER M: Divya Bharathi @@ -5009,13 +5018,13 @@ M: Prasanth Ksr L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-class-firmware-attributes -F: drivers/platform/x86/dell-wmi-sysman/ +F: drivers/platform/x86/dell/dell-wmi-sysman/ DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-wmi.c +F: drivers/platform/x86/dell/dell-wmi.c DELTA ST MEDIA DRIVER M: Hugues Fruchet @@ -8132,7 +8141,7 @@ F: net/hsr/ HT16K33 LED CONTROLLER DRIVER M: Robin van der Gracht S: Maintained -F: Documentation/devicetree/bindings/display/ht16k33.txt +F: Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml F: drivers/auxdisplay/ht16k33.c HTCPEN TOUCHSCREEN DRIVER @@ -8915,7 +8924,6 @@ L: linux-gpio@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-ich.c -F: drivers/gpio/gpio-intel-mid.c F: drivers/gpio/gpio-merrifield.c F: drivers/gpio/gpio-ml-ioh.c F: drivers/gpio/gpio-pch.c @@ -9087,7 +9095,6 @@ M: Andy Shevchenko S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-*cove.c -F: drivers/gpio/gpio-msic.c INTEL PMIC MULTIFUNCTION DEVICE DRIVERS M: Andy Shevchenko @@ -11688,9 +11695,9 @@ F: drivers/video/fbdev/atmel_lcdfb.c F: include/video/atmel_lcdc.h MICROCHIP MCP16502 PMIC DRIVER -M: Andrei Stefanescu +M: Claudiu Beznea L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) -S: Maintained +S: Supported F: Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt F: drivers/regulator/mcp16502.c @@ -11805,12 +11812,31 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/surface/ +MICROSOFT SURFACE HOT-PLUG DRIVER +M: Maximilian Luz +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/surface/surface_hotplug.c + MICROSOFT SURFACE PRO 3 BUTTON DRIVER M: Chen Yu L: platform-driver-x86@vger.kernel.org S: Supported F: drivers/platform/surface/surfacepro3_button.c +MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM +M: Maximilian Luz +S: Maintained +W: https://github.com/linux-surface/surface-aggregator-module +C: irc://chat.freenode.net/##linux-surface +F: Documentation/driver-api/surface_aggregator/ +F: drivers/platform/surface/aggregator/ +F: drivers/platform/surface/surface_acpi_notify.c +F: drivers/platform/surface/surface_aggregator_cdev.c +F: include/linux/surface_acpi_notify.h +F: include/linux/surface_aggregator/ +F: include/uapi/linux/surface_aggregator/ + MICROTEK X6 SCANNER M: Oliver Neukum S: Maintained @@ -17524,6 +17550,14 @@ M: Laxman Dewangan S: Supported F: drivers/spi/spi-tegra* +TEGRA QUAD SPI DRIVER +M: Thierry Reding +M: Jonathan Hunter +M: Sowjanya Komatineni +L: linux-tegra@vger.kernel.org +S: Maintained +F: drivers/spi/spi-tegra210-quad.c + TEGRA VIDEO DRIVER M: Thierry Reding M: Jonathan Hunter @@ -17616,6 +17650,15 @@ F: include/dt-bindings/soc/ti,sci_pm_domain.h F: include/linux/soc/ti/ti_sci_inta_msi.h F: include/linux/soc/ti/ti_sci_protocol.h +TEXAS INSTRUMENTS TPS23861 PoE PSE DRIVER +M: Robert Marko +M: Luka Perkov +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml +F: Documentation/hwmon/tps23861.rst +F: drivers/hwmon/tps23861.c + THANKO'S RAREMONO AM/FM/SW RADIO RECEIVER USB DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org @@ -17669,7 +17712,7 @@ F: drivers/thermal/gov_power_allocator.c F: include/trace/events/thermal_power_allocator.h THINKPAD ACPI EXTRAS DRIVER -M: Henrique de Moraes Holschuh +M: Henrique de Moraes Holschuh L: ibm-acpi-devel@lists.sourceforge.net L: platform-driver-x86@vger.kernel.org S: Maintained diff --git a/arch/arm/mach-pxa/z2.c b/arch/arm/mach-pxa/z2.c index 21fd76bb09cd..89eb5243c85f 100644 --- a/arch/arm/mach-pxa/z2.c +++ b/arch/arm/mach-pxa/z2.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -578,8 +577,13 @@ static struct pxa2xx_spi_chip lms283_chip_info = { .gpio_cs = GPIO88_ZIPITZ2_LCD_CS, }; -static const struct lms283gf05_pdata lms283_pdata = { - .reset_gpio = GPIO19_ZIPITZ2_LCD_RESET, +static struct gpiod_lookup_table lms283_gpio_table = { + .dev_id = "spi2.0", /* SPI bus 2 chip select 0 */ + .table = { + GPIO_LOOKUP("gpio-pxa", GPIO19_ZIPITZ2_LCD_RESET, + "reset", GPIO_ACTIVE_LOW), + { }, + }, }; static struct spi_board_info spi_board_info[] __initdata = { @@ -595,7 +599,6 @@ static struct spi_board_info spi_board_info[] __initdata = { { .modalias = "lms283gf05", .controller_data = &lms283_chip_info, - .platform_data = &lms283_pdata, .max_speed_hz = 400000, .bus_num = 2, .chip_select = 0, @@ -615,6 +618,7 @@ static void __init z2_spi_init(void) { pxa2xx_set_spi_info(1, &pxa_ssp1_master_info); pxa2xx_set_spi_info(2, &pxa_ssp2_master_info); + gpiod_add_lookup_table(&lms283_gpio_table); spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); } #else diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts index 26a4a5a6871e..2f0528d01299 100644 --- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts +++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts @@ -232,7 +232,7 @@ &adsp { &apps_rsc { pm8009-rpmh-regulators { - compatible = "qcom,pm8009-rpmh-regulators"; + compatible = "qcom,pm8009-1-rpmh-regulators"; qcom,pmic-id = "f"; vdd-s1-supply = <&vph_pwr>; @@ -241,6 +241,13 @@ pm8009-rpmh-regulators { vdd-l5-l6-supply = <&vreg_bob>; vdd-l7-supply = <&vreg_s4a_1p8>; + vreg_s2f_0p95: smps2 { + regulator-name = "vreg_s2f_0p95"; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <952000>; + regulator-initial-mode = ; + }; + vreg_l1f_1p1: ldo1 { regulator-name = "vreg_l1f_1p1"; regulator-min-microvolt = <1104000>; diff --git a/arch/x86/platform/intel-mid/device_libs/Makefile b/arch/x86/platform/intel-mid/device_libs/Makefile index 480fed21cc7d..918edac9ab9a 100644 --- a/arch/x86/platform/intel-mid/device_libs/Makefile +++ b/arch/x86/platform/intel-mid/device_libs/Makefile @@ -30,4 +30,3 @@ obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o obj-$(subst m,y,$(CONFIG_INTEL_MID_POWER_BUTTON)) += platform_mrfld_power_btn.o obj-$(subst m,y,$(CONFIG_RTC_DRV_CMOS)) += platform_mrfld_rtc.o -obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_mrfld_wdt.o diff --git a/block/keyslot-manager.c b/block/keyslot-manager.c index 2b18b17a15fb..7ba541f73ce8 100644 --- a/block/keyslot-manager.c +++ b/block/keyslot-manager.c @@ -29,6 +29,7 @@ #define pr_fmt(fmt) "blk-crypto: " fmt #include +#include #include #include #include @@ -132,6 +133,34 @@ int blk_ksm_init(struct blk_keyslot_manager *ksm, unsigned int num_slots) } EXPORT_SYMBOL_GPL(blk_ksm_init); +static void blk_ksm_destroy_callback(void *ksm) +{ + blk_ksm_destroy(ksm); +} + +/** + * devm_blk_ksm_init() - Resource-managed blk_ksm_init() + * @dev: The device which owns the blk_keyslot_manager. + * @ksm: The blk_keyslot_manager to initialize. + * @num_slots: The number of key slots to manage. + * + * Like blk_ksm_init(), but causes blk_ksm_destroy() to be called automatically + * on driver detach. + * + * Return: 0 on success, or else a negative error code. + */ +int devm_blk_ksm_init(struct device *dev, struct blk_keyslot_manager *ksm, + unsigned int num_slots) +{ + int err = blk_ksm_init(ksm, num_slots); + + if (err) + return err; + + return devm_add_action_or_reset(dev, blk_ksm_destroy_callback, ksm); +} +EXPORT_SYMBOL_GPL(devm_blk_ksm_init); + static inline struct hlist_head * blk_ksm_hash_bucket_for_key(struct blk_keyslot_manager *ksm, const struct blk_crypto_key *key) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 4f906380b031..95105db642b9 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -670,27 +670,24 @@ static int thermal_get_trend(struct thermal_zone_device *thermal, return 0; } - -static int thermal_notify(struct thermal_zone_device *thermal, int trip, - enum thermal_trip_type trip_type) +static void acpi_thermal_zone_device_hot(struct thermal_zone_device *thermal) { - u8 type = 0; struct acpi_thermal *tz = thermal->devdata; - if (trip_type == THERMAL_TRIP_CRITICAL) - type = ACPI_THERMAL_NOTIFY_CRITICAL; - else if (trip_type == THERMAL_TRIP_HOT) - type = ACPI_THERMAL_NOTIFY_HOT; - else - return 0; + acpi_bus_generate_netlink_event(tz->device->pnp.device_class, + dev_name(&tz->device->dev), + ACPI_THERMAL_NOTIFY_HOT, 1); +} + +static void acpi_thermal_zone_device_critical(struct thermal_zone_device *thermal) +{ + struct acpi_thermal *tz = thermal->devdata; acpi_bus_generate_netlink_event(tz->device->pnp.device_class, - dev_name(&tz->device->dev), type, 1); + dev_name(&tz->device->dev), + ACPI_THERMAL_NOTIFY_CRITICAL, 1); - if (trip_type == THERMAL_TRIP_CRITICAL && nocrt) - return 1; - - return 0; + thermal_zone_device_critical(thermal); } static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, @@ -760,25 +757,6 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, } } - for (i = 0; i < tz->devices.count; i++) { - handle = tz->devices.handles[i]; - status = acpi_bus_get_device(handle, &dev); - if (ACPI_SUCCESS(status) && (dev == device)) { - if (bind) - result = thermal_zone_bind_cooling_device - (thermal, THERMAL_TRIPS_NONE, - cdev, THERMAL_NO_LIMIT, - THERMAL_NO_LIMIT, - THERMAL_WEIGHT_DEFAULT); - else - result = thermal_zone_unbind_cooling_device - (thermal, THERMAL_TRIPS_NONE, - cdev); - if (result) - goto failed; - } - } - failed: return result; } @@ -805,7 +783,8 @@ static struct thermal_zone_device_ops acpi_thermal_zone_ops = { .get_trip_temp = thermal_get_trip_temp, .get_crit_temp = thermal_get_crit_temp, .get_trend = thermal_get_trend, - .notify = thermal_notify, + .hot = acpi_thermal_zone_device_hot, + .critical = acpi_thermal_zone_device_critical, }; static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz) diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index a2b59b84bb88..1509cb74705a 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -507,6 +507,3 @@ config PANEL depends on PARPORT select AUXDISPLAY select PARPORT_PANEL - -config CHARLCD - tristate "Character LCD core support" if COMPILE_TEST diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index d951d54b26f5..1e69cc6d21a0 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -117,8 +117,7 @@ static void ht16k33_fb_queue(struct ht16k33_priv *priv) { struct ht16k33_fbdev *fbdev = &priv->fbdev; - schedule_delayed_work(&fbdev->work, - msecs_to_jiffies(HZ / fbdev->refresh_rate)); + schedule_delayed_work(&fbdev->work, HZ / fbdev->refresh_rate); } /* @@ -402,11 +401,6 @@ static int ht16k33_probe(struct i2c_client *client, return -EIO; } - if (client->irq <= 0) { - dev_err(&client->dev, "No IRQ specified\n"); - return -EINVAL; - } - priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -459,9 +453,12 @@ static int ht16k33_probe(struct i2c_client *client, if (err) goto err_fbdev_info; - err = ht16k33_keypad_probe(client, &priv->keypad); - if (err) - goto err_fbdev_unregister; + /* Keypad */ + if (client->irq > 0) { + err = ht16k33_keypad_probe(client, &priv->keypad); + if (err) + goto err_fbdev_unregister; + } /* Backlight */ memset(&bl_props, 0, sizeof(struct backlight_properties)); diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 7f4b3b62492c..f2469d3435ca 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -68,7 +68,7 @@ static int regcache_hw_init(struct regmap *map) map->cache_bypass = cache_bypass; if (ret == 0) { map->reg_defaults_raw = tmp_buf; - map->cache_free = 1; + map->cache_free = true; } else { kfree(tmp_buf); } diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index 6f431b73e617..31fc5fa817d8 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -965,8 +965,11 @@ EXPORT_SYMBOL(qcom_scm_ice_available); * qcom_scm_ice_invalidate_key() - Invalidate an inline encryption key * @index: the keyslot to invalidate * - * The UFSHCI standard defines a standard way to do this, but it doesn't work on - * these SoCs; only this SCM call does. + * The UFSHCI and eMMC standards define a standard way to do this, but it + * doesn't work on these SoCs; only this SCM call does. + * + * It is assumed that the SoC has only one ICE instance being used, as this SCM + * call doesn't specify which ICE instance the keyslot belongs to. * * Return: 0 on success; -errno on failure. */ @@ -995,10 +998,13 @@ EXPORT_SYMBOL(qcom_scm_ice_invalidate_key); * units, e.g. 1 = 512 bytes, 8 = 4096 bytes, etc. * * Program a key into a keyslot of Qualcomm ICE (Inline Crypto Engine), where it - * can then be used to encrypt/decrypt UFS I/O requests inline. + * can then be used to encrypt/decrypt UFS or eMMC I/O requests inline. * - * The UFSHCI standard defines a standard way to do this, but it doesn't work on - * these SoCs; only this SCM call does. + * The UFSHCI and eMMC standards define a standard way to do this, but it + * doesn't work on these SoCs; only this SCM call does. + * + * It is assumed that the SoC has only one ICE instance being used, as this SCM + * call doesn't specify which ICE instance the keyslot belongs to. * * Return: 0 on success; -errno on failure. */ diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index fa225175e68d..f62f0b6b0bc0 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1253,13 +1253,6 @@ config GPIO_MAX77650 GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. These chips have a single pin that can be configured as GPIO. -config GPIO_MSIC - bool "Intel MSIC mixed signal gpio support" - depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC - help - Enable support for GPIO on intel MSIC controllers found in - intel MID devices - config GPIO_PALMAS bool "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS @@ -1455,13 +1448,6 @@ config GPIO_BT8XX If unsure, say N. -config GPIO_INTEL_MID - bool "Intel MID GPIO support" - depends on X86_INTEL_MID - select GPIOLIB_IRQCHIP - help - Say Y here to support Intel MID GPIO. - config GPIO_MERRIFIELD tristate "Intel Merrifield GPIO support" depends on X86_INTEL_MID diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 35e3b6026665..a2106d667fb3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -67,7 +67,6 @@ obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o obj-$(CONFIG_GPIO_ICH) += gpio-ich.o -obj-$(CONFIG_GPIO_INTEL_MID) += gpio-intel-mid.o obj-$(CONFIG_GPIO_IOP) += gpio-iop.o obj-$(CONFIG_GPIO_IT87) += gpio-it87.o obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index 0229fa79499e..b8b1473a5b1e 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -101,7 +101,7 @@ for a few GPIOs. Those should stay where they are. At the same time it makes sense to get rid of code duplication in existing or new coming drivers. For example, gpio-ml-ioh should be incorporated into -gpio-pch. In similar way gpio-intel-mid into gpio-pxa. +gpio-pch. Generic MMIO GPIO diff --git a/drivers/gpio/gpio-bd9571mwv.c b/drivers/gpio/gpio-bd9571mwv.c index c0abc9c6851b..df6102b57734 100644 --- a/drivers/gpio/gpio-bd9571mwv.c +++ b/drivers/gpio/gpio-bd9571mwv.c @@ -1,31 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * ROHM BD9571MWV-M GPIO driver + * ROHM BD9571MWV-M and BD9574MWF-M GPIO driver * * Copyright (C) 2017 Marek Vasut * - * 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 "as is" WITHOUT ANY WARRANTY of any - * kind, whether expressed or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License version 2 for more details. - * * Based on the TPS65086 driver * * NOTE: Interrupts are not supported yet. */ #include +#include #include #include #include struct bd9571mwv_gpio { + struct regmap *regmap; struct gpio_chip chip; - struct bd9571mwv *bd; }; static int bd9571mwv_gpio_get_direction(struct gpio_chip *chip, @@ -34,7 +27,7 @@ static int bd9571mwv_gpio_get_direction(struct gpio_chip *chip, struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip); int ret, val; - ret = regmap_read(gpio->bd->regmap, BD9571MWV_GPIO_DIR, &val); + ret = regmap_read(gpio->regmap, BD9571MWV_GPIO_DIR, &val); if (ret < 0) return ret; if (val & BIT(offset)) @@ -48,8 +41,7 @@ static int bd9571mwv_gpio_direction_input(struct gpio_chip *chip, { struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip); - regmap_update_bits(gpio->bd->regmap, BD9571MWV_GPIO_DIR, - BIT(offset), 0); + regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_DIR, BIT(offset), 0); return 0; } @@ -60,9 +52,9 @@ static int bd9571mwv_gpio_direction_output(struct gpio_chip *chip, struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip); /* Set the initial value */ - regmap_update_bits(gpio->bd->regmap, BD9571MWV_GPIO_OUT, + regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_OUT, BIT(offset), value ? BIT(offset) : 0); - regmap_update_bits(gpio->bd->regmap, BD9571MWV_GPIO_DIR, + regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_DIR, BIT(offset), BIT(offset)); return 0; @@ -73,7 +65,7 @@ static int bd9571mwv_gpio_get(struct gpio_chip *chip, unsigned int offset) struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip); int ret, val; - ret = regmap_read(gpio->bd->regmap, BD9571MWV_GPIO_IN, &val); + ret = regmap_read(gpio->regmap, BD9571MWV_GPIO_IN, &val); if (ret < 0) return ret; @@ -85,7 +77,7 @@ static void bd9571mwv_gpio_set(struct gpio_chip *chip, unsigned int offset, { struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip); - regmap_update_bits(gpio->bd->regmap, BD9571MWV_GPIO_OUT, + regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_OUT, BIT(offset), value ? BIT(offset) : 0); } @@ -113,9 +105,9 @@ static int bd9571mwv_gpio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gpio); - gpio->bd = dev_get_drvdata(pdev->dev.parent); + gpio->regmap = dev_get_regmap(pdev->dev.parent, NULL); gpio->chip = template_chip; - gpio->chip.parent = gpio->bd->dev; + gpio->chip.parent = pdev->dev.parent; ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); if (ret < 0) { @@ -127,7 +119,8 @@ static int bd9571mwv_gpio_probe(struct platform_device *pdev) } static const struct platform_device_id bd9571mwv_gpio_id_table[] = { - { "bd9571mwv-gpio", }, + { "bd9571mwv-gpio", ROHM_CHIP_TYPE_BD9571 }, + { "bd9574mwf-gpio", ROHM_CHIP_TYPE_BD9574 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, bd9571mwv_gpio_id_table); diff --git a/drivers/gpio/gpio-intel-mid.c b/drivers/gpio/gpio-intel-mid.c deleted file mode 100644 index 86a10c808ef6..000000000000 --- a/drivers/gpio/gpio-intel-mid.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID GPIO driver - * - * Copyright (c) 2008-2014,2016 Intel Corporation. - */ - -/* Supports: - * Moorestown platform Langwell chip. - * Medfield platform Penwell chip. - * Clovertrail platform Cloverview chip. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INTEL_MID_IRQ_TYPE_EDGE (1 << 0) -#define INTEL_MID_IRQ_TYPE_LEVEL (1 << 1) - -/* - * Langwell chip has 64 pins and thus there are 2 32bit registers to control - * each feature, while Penwell chip has 96 pins for each block, and need 3 32bit - * registers to control them, so we only define the order here instead of a - * structure, to get a bit offset for a pin (use GPDR as an example): - * - * nreg = ngpio / 32; - * reg = offset / 32; - * bit = offset % 32; - * reg_addr = reg_base + GPDR * nreg * 4 + reg * 4; - * - * so the bit of reg_addr is to control pin offset's GPDR feature -*/ - -enum GPIO_REG { - GPLR = 0, /* pin level read-only */ - GPDR, /* pin direction */ - GPSR, /* pin set */ - GPCR, /* pin clear */ - GRER, /* rising edge detect */ - GFER, /* falling edge detect */ - GEDR, /* edge detect result */ - GAFR, /* alt function */ -}; - -/* intel_mid gpio driver data */ -struct intel_mid_gpio_ddata { - u16 ngpio; /* number of gpio pins */ - u32 chip_irq_type; /* chip interrupt type */ -}; - -struct intel_mid_gpio { - struct gpio_chip chip; - void __iomem *reg_base; - spinlock_t lock; - struct pci_dev *pdev; -}; - -static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 32; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static void __iomem *gpio_reg_2bit(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 16; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static int intel_gpio_request(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gafr = gpio_reg_2bit(chip, offset, GAFR); - u32 value = readl(gafr); - int shift = (offset % 16) << 1, af = (value >> shift) & 3; - - if (af) { - value &= ~(3 << shift); - writel(value, gafr); - } - return 0; -} - -static int intel_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gplr = gpio_reg(chip, offset, GPLR); - - return !!(readl(gplr) & BIT(offset % 32)); -} - -static void intel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - void __iomem *gpsr, *gpcr; - - if (value) { - gpsr = gpio_reg(chip, offset, GPSR); - writel(BIT(offset % 32), gpsr); - } else { - gpcr = gpio_reg(chip, offset, GPCR); - writel(BIT(offset % 32), gpcr); - } -} - -static int intel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - u32 value; - unsigned long flags; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value &= ~BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - unsigned long flags; - - intel_gpio_set(chip, offset, value); - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value |= BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_mid_irq_type(struct irq_data *d, unsigned type) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - u32 gpio = irqd_to_hwirq(d); - unsigned long flags; - u32 value; - void __iomem *grer = gpio_reg(&priv->chip, gpio, GRER); - void __iomem *gfer = gpio_reg(&priv->chip, gpio, GFER); - - if (gpio >= priv->chip.ngpio) - return -EINVAL; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - if (type & IRQ_TYPE_EDGE_RISING) - value = readl(grer) | BIT(gpio % 32); - else - value = readl(grer) & (~BIT(gpio % 32)); - writel(value, grer); - - if (type & IRQ_TYPE_EDGE_FALLING) - value = readl(gfer) | BIT(gpio % 32); - else - value = readl(gfer) & (~BIT(gpio % 32)); - writel(value, gfer); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static void intel_mid_irq_unmask(struct irq_data *d) -{ -} - -static void intel_mid_irq_mask(struct irq_data *d) -{ -} - -static struct irq_chip intel_mid_irqchip = { - .name = "INTEL_MID-GPIO", - .irq_mask = intel_mid_irq_mask, - .irq_unmask = intel_mid_irq_unmask, - .irq_set_type = intel_mid_irq_type, -}; - -static const struct intel_mid_gpio_ddata gpio_lincroft = { - .ngpio = 64, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE | INTEL_MID_IRQ_TYPE_LEVEL, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct pci_device_id intel_gpio_ids[] = { - { - /* Lincroft */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080f), - .driver_data = (kernel_ulong_t)&gpio_lincroft, - }, - { - /* Penwell AON */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081f), - .driver_data = (kernel_ulong_t)&gpio_penwell_aon, - }, - { - /* Penwell Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081a), - .driver_data = (kernel_ulong_t)&gpio_penwell_core, - }, - { - /* Cloverview Aon */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08eb), - .driver_data = (kernel_ulong_t)&gpio_cloverview_aon, - }, - { - /* Cloverview Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08f7), - .driver_data = (kernel_ulong_t)&gpio_cloverview_core, - }, - { } -}; - -static void intel_mid_irq_handler(struct irq_desc *desc) -{ - struct gpio_chip *gc = irq_desc_get_handler_data(desc); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - struct irq_data *data = irq_desc_get_irq_data(desc); - struct irq_chip *chip = irq_data_get_irq_chip(data); - u32 base, gpio, mask; - unsigned long pending; - void __iomem *gedr; - - /* check GPIO controller to check which pin triggered the interrupt */ - for (base = 0; base < priv->chip.ngpio; base += 32) { - gedr = gpio_reg(&priv->chip, base, GEDR); - while ((pending = readl(gedr))) { - gpio = __ffs(pending); - mask = BIT(gpio); - /* Clear before handling so we can't lose an edge */ - writel(mask, gedr); - generic_handle_irq(irq_find_mapping(gc->irq.domain, - base + gpio)); - } - } - - chip->irq_eoi(data); -} - -static int intel_mid_irq_init_hw(struct gpio_chip *chip) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *reg; - unsigned base; - - for (base = 0; base < priv->chip.ngpio; base += 32) { - /* Clear the rising-edge detect register */ - reg = gpio_reg(&priv->chip, base, GRER); - writel(0, reg); - /* Clear the falling-edge detect register */ - reg = gpio_reg(&priv->chip, base, GFER); - writel(0, reg); - /* Clear the edge detect status register */ - reg = gpio_reg(&priv->chip, base, GEDR); - writel(~0, reg); - } - - return 0; -} - -static int __maybe_unused intel_gpio_runtime_idle(struct device *dev) -{ - int err = pm_schedule_suspend(dev, 500); - return err ?: -EBUSY; -} - -static const struct dev_pm_ops intel_gpio_pm_ops = { - SET_RUNTIME_PM_OPS(NULL, NULL, intel_gpio_runtime_idle) -}; - -static int intel_gpio_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - void __iomem *base; - struct intel_mid_gpio *priv; - u32 gpio_base; - u32 irq_base; - int retval; - struct gpio_irq_chip *girq; - struct intel_mid_gpio_ddata *ddata = - (struct intel_mid_gpio_ddata *)id->driver_data; - - retval = pcim_enable_device(pdev); - if (retval) - return retval; - - retval = pcim_iomap_regions(pdev, 1 << 0 | 1 << 1, pci_name(pdev)); - if (retval) { - dev_err(&pdev->dev, "I/O memory mapping error\n"); - return retval; - } - - base = pcim_iomap_table(pdev)[1]; - - irq_base = readl(base); - gpio_base = readl(sizeof(u32) + base); - - /* release the IO mapping, since we already get the info from bar1 */ - pcim_iounmap_regions(pdev, 1 << 1); - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->reg_base = pcim_iomap_table(pdev)[0]; - priv->chip.label = dev_name(&pdev->dev); - priv->chip.parent = &pdev->dev; - priv->chip.request = intel_gpio_request; - priv->chip.direction_input = intel_gpio_direction_input; - priv->chip.direction_output = intel_gpio_direction_output; - priv->chip.get = intel_gpio_get; - priv->chip.set = intel_gpio_set; - priv->chip.base = gpio_base; - priv->chip.ngpio = ddata->ngpio; - priv->chip.can_sleep = false; - priv->pdev = pdev; - - spin_lock_init(&priv->lock); - - girq = &priv->chip.irq; - girq->chip = &intel_mid_irqchip; - girq->init_hw = intel_mid_irq_init_hw; - girq->parent_handler = intel_mid_irq_handler; - girq->num_parents = 1; - girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents, - sizeof(*girq->parents), - GFP_KERNEL); - if (!girq->parents) - return -ENOMEM; - girq->parents[0] = pdev->irq; - girq->first = irq_base; - girq->default_type = IRQ_TYPE_NONE; - girq->handler = handle_simple_irq; - - pci_set_drvdata(pdev, priv); - - retval = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); - if (retval) { - dev_err(&pdev->dev, "gpiochip_add error %d\n", retval); - return retval; - } - - pm_runtime_put_noidle(&pdev->dev); - pm_runtime_allow(&pdev->dev); - - return 0; -} - -static struct pci_driver intel_gpio_driver = { - .name = "intel_mid_gpio", - .id_table = intel_gpio_ids, - .probe = intel_gpio_probe, - .driver = { - .pm = &intel_gpio_pm_ops, - }, -}; - -builtin_pci_driver(intel_gpio_driver); diff --git a/drivers/gpio/gpio-msic.c b/drivers/gpio/gpio-msic.c deleted file mode 100644 index 7e3c96e4ab2c..000000000000 --- a/drivers/gpio/gpio-msic.c +++ /dev/null @@ -1,314 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Medfield MSIC GPIO driver> - * Copyright (c) 2011, Intel Corporation. - * - * Author: Mathias Nyman - * Based on intel_pmic_gpio.c - */ - -#include -#include -#include -#include -#include -#include -#include - -/* the offset for the mapping of global gpio pin to irq */ -#define MSIC_GPIO_IRQ_OFFSET 0x100 - -#define MSIC_GPIO_DIR_IN 0 -#define MSIC_GPIO_DIR_OUT BIT(5) -#define MSIC_GPIO_TRIG_FALL BIT(1) -#define MSIC_GPIO_TRIG_RISE BIT(2) - -/* masks for msic gpio output GPIOxxxxCTLO registers */ -#define MSIC_GPIO_DIR_MASK BIT(5) -#define MSIC_GPIO_DRV_MASK BIT(4) -#define MSIC_GPIO_REN_MASK BIT(3) -#define MSIC_GPIO_RVAL_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DOUT_MASK BIT(0) - -/* masks for msic gpio input GPIOxxxxCTLI registers */ -#define MSIC_GPIO_GLBYP_MASK BIT(5) -#define MSIC_GPIO_DBNC_MASK (BIT(4) | BIT(3)) -#define MSIC_GPIO_INTCNT_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DIN_MASK BIT(0) - -#define MSIC_NUM_GPIO 24 - -struct msic_gpio { - struct platform_device *pdev; - struct mutex buslock; - struct gpio_chip chip; - int irq; - unsigned irq_base; - unsigned long trig_change_mask; - unsigned trig_type; -}; - -/* - * MSIC has 24 gpios, 16 low voltage (1.2-1.8v) and 8 high voltage (3v). - * Both the high and low voltage gpios are divided in two banks. - * GPIOs are numbered with GPIO0LV0 as gpio_base in the following order: - * GPIO0LV0..GPIO0LV7: low voltage, bank 0, gpio_base - * GPIO1LV0..GPIO1LV7: low voltage, bank 1, gpio_base + 8 - * GPIO0HV0..GPIO0HV3: high voltage, bank 0, gpio_base + 16 - * GPIO1HV0..GPIO1HV3: high voltage, bank 1, gpio_base + 20 - */ - -static int msic_gpio_to_ireg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLI - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLI - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLI - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLI - offset + 20; -} - -static int msic_gpio_to_oreg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLO - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLO - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLO - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLO - offset + 20; -} - -static int msic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, MSIC_GPIO_DIR_IN, MSIC_GPIO_DIR_MASK); -} - -static int msic_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - int reg; - unsigned mask; - - value = (!!value) | MSIC_GPIO_DIR_OUT; - mask = MSIC_GPIO_DIR_MASK | MSIC_GPIO_DOUT_MASK; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, value, mask); -} - -static int msic_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - u8 r; - int ret; - int reg; - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - return reg; - - ret = intel_msic_reg_read(reg, &r); - if (ret < 0) - return ret; - - return !!(r & MSIC_GPIO_DIN_MASK); -} - -static void msic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return; - - intel_msic_reg_update(reg, !!value , MSIC_GPIO_DOUT_MASK); -} - -/* - * This is called from genirq with mg->buslock locked and - * irq_desc->lock held. We can not access the scu bus here, so we - * store the change and update in the bus_sync_unlock() function below - */ -static int msic_irq_type(struct irq_data *data, unsigned type) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - u32 gpio = data->irq - mg->irq_base; - - if (gpio >= mg->chip.ngpio) - return -EINVAL; - - /* mark for which gpio the trigger changed, protected by buslock */ - mg->trig_change_mask |= (1 << gpio); - mg->trig_type = type; - - return 0; -} - -static int msic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) -{ - struct msic_gpio *mg = gpiochip_get_data(chip); - return mg->irq_base + offset; -} - -static void msic_bus_lock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - mutex_lock(&mg->buslock); -} - -static void msic_bus_sync_unlock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - int offset; - int reg; - u8 trig = 0; - - /* We can only get one change at a time as the buslock covers the - entire transaction. The irq_desc->lock is dropped before we are - called but that is fine */ - if (mg->trig_change_mask) { - offset = __ffs(mg->trig_change_mask); - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - goto out; - - if (mg->trig_type & IRQ_TYPE_EDGE_RISING) - trig |= MSIC_GPIO_TRIG_RISE; - if (mg->trig_type & IRQ_TYPE_EDGE_FALLING) - trig |= MSIC_GPIO_TRIG_FALL; - - intel_msic_reg_update(reg, trig, MSIC_GPIO_INTCNT_MASK); - mg->trig_change_mask = 0; - } -out: - mutex_unlock(&mg->buslock); -} - -/* Firmware does all the masking and unmasking for us, no masking here. */ -static void msic_irq_unmask(struct irq_data *data) { } - -static void msic_irq_mask(struct irq_data *data) { } - -static struct irq_chip msic_irqchip = { - .name = "MSIC-GPIO", - .irq_mask = msic_irq_mask, - .irq_unmask = msic_irq_unmask, - .irq_set_type = msic_irq_type, - .irq_bus_lock = msic_bus_lock, - .irq_bus_sync_unlock = msic_bus_sync_unlock, -}; - -static void msic_gpio_irq_handler(struct irq_desc *desc) -{ - struct irq_data *data = irq_desc_get_irq_data(desc); - struct msic_gpio *mg = irq_data_get_irq_handler_data(data); - struct irq_chip *chip = irq_data_get_irq_chip(data); - struct intel_msic *msic = pdev_to_intel_msic(mg->pdev); - unsigned long pending; - int i; - int bitnr; - u8 pin; - - for (i = 0; i < (mg->chip.ngpio / BITS_PER_BYTE); i++) { - intel_msic_irq_read(msic, INTEL_MSIC_GPIO0LVIRQ + i, &pin); - pending = pin; - - for_each_set_bit(bitnr, &pending, BITS_PER_BYTE) - generic_handle_irq(mg->irq_base + i * BITS_PER_BYTE + bitnr); - } - chip->irq_eoi(data); -} - -static int platform_msic_gpio_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct intel_msic_gpio_pdata *pdata = dev_get_platdata(dev); - struct msic_gpio *mg; - int irq = platform_get_irq(pdev, 0); - int retval; - int i; - - if (irq < 0) { - dev_err(dev, "no IRQ line: %d\n", irq); - return irq; - } - - if (!pdata || !pdata->gpio_base) { - dev_err(dev, "incorrect or missing platform data\n"); - return -EINVAL; - } - - mg = kzalloc(sizeof(*mg), GFP_KERNEL); - if (!mg) - return -ENOMEM; - - dev_set_drvdata(dev, mg); - - mg->pdev = pdev; - mg->irq = irq; - mg->irq_base = pdata->gpio_base + MSIC_GPIO_IRQ_OFFSET; - mg->chip.label = "msic_gpio"; - mg->chip.direction_input = msic_gpio_direction_input; - mg->chip.direction_output = msic_gpio_direction_output; - mg->chip.get = msic_gpio_get; - mg->chip.set = msic_gpio_set; - mg->chip.to_irq = msic_gpio_to_irq; - mg->chip.base = pdata->gpio_base; - mg->chip.ngpio = MSIC_NUM_GPIO; - mg->chip.can_sleep = true; - mg->chip.parent = dev; - - mutex_init(&mg->buslock); - - retval = gpiochip_add_data(&mg->chip, mg); - if (retval) { - dev_err(dev, "Adding MSIC gpio chip failed\n"); - goto err; - } - - for (i = 0; i < mg->chip.ngpio; i++) { - irq_set_chip_data(i + mg->irq_base, mg); - irq_set_chip_and_handler(i + mg->irq_base, - &msic_irqchip, - handle_simple_irq); - } - irq_set_chained_handler_and_data(mg->irq, msic_gpio_irq_handler, mg); - - return 0; -err: - kfree(mg); - return retval; -} - -static struct platform_driver platform_msic_gpio_driver = { - .driver = { - .name = "msic_gpio", - }, - .probe = platform_msic_gpio_probe, -}; - -static int __init platform_msic_gpio_init(void) -{ - return platform_driver_register(&platform_msic_gpio_driver); -} -subsys_initcall(platform_msic_gpio_init); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 97eec8d8dbdc..844198cb4e31 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -3469,6 +3469,10 @@ EXPORT_SYMBOL_GPL(gpiod_add_lookup_table); */ void gpiod_remove_lookup_table(struct gpiod_lookup_table *table) { + /* Nothing to remove */ + if (!table) + return; + mutex_lock(&gpio_lookup_lock); list_del(&table->list); diff --git a/drivers/gpu/drm/gma500/oaktrail_device.c b/drivers/gpu/drm/gma500/oaktrail_device.c index 08cd5f73c868..aff0534831ef 100644 --- a/drivers/gpu/drm/gma500/oaktrail_device.c +++ b/drivers/gpu/drm/gma500/oaktrail_device.c @@ -10,9 +10,6 @@ #include #include -#include -#include - #include #include "intel_bios.h" diff --git a/drivers/gpu/drm/gma500/psb_drv.h b/drivers/gpu/drm/gma500/psb_drv.h index 020a71b91577..694495070c65 100644 --- a/drivers/gpu/drm/gma500/psb_drv.h +++ b/drivers/gpu/drm/gma500/psb_drv.h @@ -386,6 +386,8 @@ struct psb_ops; #define PSB_NUM_PIPE 3 +struct intel_scu_ipc_dev; + struct drm_psb_private { struct drm_device *dev; struct pci_dev *aux_pdev; /* Currently only used by mrst */ @@ -525,6 +527,7 @@ struct drm_psb_private { * Used for modifying backlight from * xrandr -- consider removing and using HAL instead */ + struct intel_scu_ipc_dev *scu; struct backlight_device *backlight_device; struct drm_property *backlight_property; bool backlight_enabled; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1ecf697d8d99..54f04e61fb83 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -38,19 +38,6 @@ config HWMON_DEBUG_CHIP comment "Native drivers" -config SENSORS_AB8500 - tristate "AB8500 thermal monitoring" - depends on AB8500_GPADC && AB8500_BM && (IIO = y) - default n - help - If you say yes here you get support for the thermal sensor part - of the AB8500 chip. The driver includes thermal management for - AB8500 die and two GPADC channels. The GPADC channel are preferably - used to access sensors outside the AB8500 chip. - - This driver can also be built as a module. If so, the module - will be called abx500-temp. - config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI @@ -257,6 +244,16 @@ config SENSORS_ADT7475 This driver can also be built as a module. If so, the module will be called adt7475. +config SENSORS_AHT10 + tristate "Aosong AHT10" + depends on I2C + help + If you say yes here, you get support for the Aosong AHT10 + temperature and humidity sensors + + This driver can also be built as a module. If so, the module + will be called aht10. + config SENSORS_AS370 tristate "Synaptics AS370 SoC hardware monitoring driver" help @@ -1136,6 +1133,17 @@ config SENSORS_TC654 This driver can also be built as a module. If so, the module will be called tc654. +config SENSORS_TPS23861 + tristate "Texas Instruments TPS23861 PoE PSE" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments + TPS23861 802.3at PoE PSE chips. + + This driver can also be built as a module. If so, the module + will be called tps23861. + config SENSORS_MENF21BMC_HWMON tristate "MEN 14F021P00 BMC Hardware Monitoring" depends on MFD_MENF21BMC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 09a86c5e1d29..fe38e8a5c979 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -21,7 +21,6 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o -obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7314) += ad7314.o @@ -45,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o +obj-$(CONFIG_SENSORS_AHT10) += aht10.o obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o @@ -144,6 +144,7 @@ obj-$(CONFIG_SENSORS_MAX31790) += max31790.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_TC654) += tc654.o +obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) += mr75203.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c deleted file mode 100644 index 53f3379d799d..000000000000 --- a/drivers/hwmon/ab8500.c +++ /dev/null @@ -1,224 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson 2010 - 2013 - * Author: Martin Persson - * Hongbo Zhang - * - * When the AB8500 thermal warning temperature is reached (threshold cannot - * be changed by SW), an interrupt is set, and if no further action is taken - * within a certain time frame, kernel_power_off will be called. - * - * When AB8500 thermal shutdown temperature is reached a hardware shutdown of - * the AB8500 will occur. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "abx500.h" - -#define DEFAULT_POWER_OFF_DELAY (HZ * 10) -#define THERMAL_VCC 1800 -#define PULL_UP_RESISTOR 47000 - -#define AB8500_SENSOR_AUX1 0 -#define AB8500_SENSOR_AUX2 1 -#define AB8500_SENSOR_BTEMP_BALL 2 -#define AB8500_SENSOR_BAT_CTRL 3 -#define NUM_MONITORED_SENSORS 4 - -struct ab8500_gpadc_cfg { - const struct abx500_res_to_temp *temp_tbl; - int tbl_sz; - int vcc; - int r_up; -}; - -struct ab8500_temp { - struct iio_channel *aux1; - struct iio_channel *aux2; - struct ab8500_btemp *btemp; - struct delayed_work power_off_work; - struct ab8500_gpadc_cfg cfg; - struct abx500_temp *abx500_data; -}; - -/* - * The hardware connection is like this: - * VCC----[ R_up ]-----[ NTC ]----GND - * where R_up is pull-up resistance, and GPADC measures voltage on NTC. - * and res_to_temp table is strictly sorted by falling resistance values. - */ -static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg, - int v_ntc, int *temp) -{ - int r_ntc, i = 0, tbl_sz = cfg->tbl_sz; - const struct abx500_res_to_temp *tbl = cfg->temp_tbl; - - if (cfg->vcc < 0 || v_ntc >= cfg->vcc) - return -EINVAL; - - r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc); - if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist) - return -EINVAL; - - while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) && - i < tbl_sz - 2) - i++; - - /* return milli-Celsius */ - *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 * - (r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); - - return 0; -} - -static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) -{ - int voltage, ret; - struct ab8500_temp *ab8500_data = data->plat_data; - - if (sensor == AB8500_SENSOR_BTEMP_BALL) { - *temp = ab8500_btemp_get_temp(ab8500_data->btemp); - } else if (sensor == AB8500_SENSOR_BAT_CTRL) { - *temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp); - } else if (sensor == AB8500_SENSOR_AUX1) { - ret = iio_read_channel_processed(ab8500_data->aux1, &voltage); - if (ret < 0) - return ret; - ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); - if (ret < 0) - return ret; - } else if (sensor == AB8500_SENSOR_AUX2) { - ret = iio_read_channel_processed(ab8500_data->aux2, &voltage); - if (ret < 0) - return ret; - ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); - if (ret < 0) - return ret; - } - - return 0; -} - -static void ab8500_thermal_power_off(struct work_struct *work) -{ - struct ab8500_temp *ab8500_data = container_of(work, - struct ab8500_temp, power_off_work.work); - struct abx500_temp *abx500_data = ab8500_data->abx500_data; - - dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); - - kernel_power_off(); -} - -static ssize_t ab8500_show_name(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - return sprintf(buf, "ab8500\n"); -} - -static ssize_t ab8500_show_label(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - char *label; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int index = attr->index; - - switch (index) { - case 1: - label = "ext_adc1"; - break; - case 2: - label = "ext_adc2"; - break; - case 3: - label = "bat_temp"; - break; - case 4: - label = "bat_ctrl"; - break; - default: - return -EINVAL; - } - - return sprintf(buf, "%s\n", label); -} - -static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) -{ - struct ab8500_temp *ab8500_data = data->plat_data; - - dev_warn(&data->pdev->dev, "Power off in %d s\n", - DEFAULT_POWER_OFF_DELAY / HZ); - - schedule_delayed_work(&ab8500_data->power_off_work, - DEFAULT_POWER_OFF_DELAY); - return 0; -} - -int abx500_hwmon_init(struct abx500_temp *data) -{ - struct ab8500_temp *ab8500_data; - - ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data), - GFP_KERNEL); - if (!ab8500_data) - return -ENOMEM; - - ab8500_data->btemp = ab8500_btemp_get(); - if (IS_ERR(ab8500_data->btemp)) - return PTR_ERR(ab8500_data->btemp); - - INIT_DELAYED_WORK(&ab8500_data->power_off_work, - ab8500_thermal_power_off); - - ab8500_data->cfg.vcc = THERMAL_VCC; - ab8500_data->cfg.r_up = PULL_UP_RESISTOR; - ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor; - ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size; - - data->plat_data = ab8500_data; - ab8500_data->aux1 = devm_iio_channel_get(&data->pdev->dev, "aux1"); - if (IS_ERR(ab8500_data->aux1)) { - if (PTR_ERR(ab8500_data->aux1) == -ENODEV) - return -EPROBE_DEFER; - dev_err(&data->pdev->dev, "failed to get AUX1 ADC channel\n"); - return PTR_ERR(ab8500_data->aux1); - } - ab8500_data->aux2 = devm_iio_channel_get(&data->pdev->dev, "aux2"); - if (IS_ERR(ab8500_data->aux2)) { - if (PTR_ERR(ab8500_data->aux2) == -ENODEV) - return -EPROBE_DEFER; - dev_err(&data->pdev->dev, "failed to get AUX2 ADC channel\n"); - return PTR_ERR(ab8500_data->aux2); - } - - data->gpadc_addr[0] = AB8500_SENSOR_AUX1; - data->gpadc_addr[1] = AB8500_SENSOR_AUX2; - data->gpadc_addr[2] = AB8500_SENSOR_BTEMP_BALL; - data->gpadc_addr[3] = AB8500_SENSOR_BAT_CTRL; - data->monitored_sensors = NUM_MONITORED_SENSORS; - - data->ops.read_sensor = ab8500_read_sensor; - data->ops.irq_handler = ab8500_temp_irq_handler; - data->ops.show_name = ab8500_show_name; - data->ops.show_label = ab8500_show_label; - data->ops.is_visible = NULL; - - return 0; -} -EXPORT_SYMBOL(abx500_hwmon_init); - -MODULE_AUTHOR("Hongbo Zhang "); -MODULE_DESCRIPTION("AB8500 temperature driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c deleted file mode 100644 index 4b9648819836..000000000000 --- a/drivers/hwmon/abx500.c +++ /dev/null @@ -1,487 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson 2010 - 2013 - * Author: Martin Persson - * Hongbo Zhang - * - * ABX500 does not provide auto ADC, so to monitor the required temperatures, - * a periodic work is used. It is more important to not wake up the CPU than - * to perform this job, hence the use of a deferred delay. - * - * A deferred delay for thermal monitor is considered safe because: - * If the chip gets too hot during a sleep state it's most likely due to - * external factors, such as the surrounding temperature. I.e. no SW decisions - * will make any difference. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "abx500.h" - -#define DEFAULT_MONITOR_DELAY HZ -#define DEFAULT_MAX_TEMP 130 - -static inline void schedule_monitor(struct abx500_temp *data) -{ - data->work_active = true; - schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY); -} - -static void threshold_updated(struct abx500_temp *data) -{ - int i; - for (i = 0; i < data->monitored_sensors; i++) - if (data->max[i] != 0 || data->min[i] != 0) { - schedule_monitor(data); - return; - } - - dev_dbg(&data->pdev->dev, "No active thresholds.\n"); - cancel_delayed_work_sync(&data->work); - data->work_active = false; -} - -static void gpadc_monitor(struct work_struct *work) -{ - int temp, i, ret; - char alarm_node[30]; - bool updated_min_alarm, updated_max_alarm; - struct abx500_temp *data; - - data = container_of(work, struct abx500_temp, work.work); - mutex_lock(&data->lock); - - for (i = 0; i < data->monitored_sensors; i++) { - /* Thresholds are considered inactive if set to 0 */ - if (data->max[i] == 0 && data->min[i] == 0) - continue; - - if (data->max[i] < data->min[i]) - continue; - - ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp); - if (ret < 0) { - dev_err(&data->pdev->dev, "GPADC read failed\n"); - continue; - } - - updated_min_alarm = false; - updated_max_alarm = false; - - if (data->min[i] != 0) { - if (temp < data->min[i]) { - if (data->min_alarm[i] == false) { - data->min_alarm[i] = true; - updated_min_alarm = true; - } - } else { - if (data->min_alarm[i] == true) { - data->min_alarm[i] = false; - updated_min_alarm = true; - } - } - } - if (data->max[i] != 0) { - if (temp > data->max[i]) { - if (data->max_alarm[i] == false) { - data->max_alarm[i] = true; - updated_max_alarm = true; - } - } else if (temp < data->max[i] - data->max_hyst[i]) { - if (data->max_alarm[i] == true) { - data->max_alarm[i] = false; - updated_max_alarm = true; - } - } - } - - if (updated_min_alarm) { - ret = sprintf(alarm_node, "temp%d_min_alarm", i + 1); - sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); - } - if (updated_max_alarm) { - ret = sprintf(alarm_node, "temp%d_max_alarm", i + 1); - sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); - } - } - - schedule_monitor(data); - mutex_unlock(&data->lock); -} - -/* HWMON sysfs interfaces */ -static ssize_t name_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - /* Show chip name */ - return data->ops.show_name(dev, devattr, buf); -} - -static ssize_t label_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - /* Show each sensor label */ - return data->ops.show_label(dev, devattr, buf); -} - -static ssize_t input_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - int ret, temp; - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - u8 gpadc_addr = data->gpadc_addr[attr->index]; - - ret = data->ops.read_sensor(data, gpadc_addr, &temp); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", temp); -} - -/* Set functions (RW nodes) */ -static ssize_t min_store(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = kstrtol(buf, 10, &val); - if (res < 0) - return res; - - val = clamp_val(val, 0, DEFAULT_MAX_TEMP); - - mutex_lock(&data->lock); - data->min[attr->index] = val; - threshold_updated(data); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t max_store(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = kstrtol(buf, 10, &val); - if (res < 0) - return res; - - val = clamp_val(val, 0, DEFAULT_MAX_TEMP); - - mutex_lock(&data->lock); - data->max[attr->index] = val; - threshold_updated(data); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t max_hyst_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = kstrtoul(buf, 10, &val); - if (res < 0) - return res; - - val = clamp_val(val, 0, DEFAULT_MAX_TEMP); - - mutex_lock(&data->lock); - data->max_hyst[attr->index] = val; - threshold_updated(data); - mutex_unlock(&data->lock); - - return count; -} - -/* Show functions (RO nodes) */ -static ssize_t min_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - - return sprintf(buf, "%lu\n", data->min[attr->index]); -} - -static ssize_t max_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - - return sprintf(buf, "%lu\n", data->max[attr->index]); -} - -static ssize_t max_hyst_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - - return sprintf(buf, "%lu\n", data->max_hyst[attr->index]); -} - -static ssize_t min_alarm_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - - return sprintf(buf, "%d\n", data->min_alarm[attr->index]); -} - -static ssize_t max_alarm_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct abx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - - return sprintf(buf, "%d\n", data->max_alarm[attr->index]); -} - -static umode_t abx500_attrs_visible(struct kobject *kobj, - struct attribute *attr, int n) -{ - struct device *dev = kobj_to_dev(kobj); - struct abx500_temp *data = dev_get_drvdata(dev); - - if (data->ops.is_visible) - return data->ops.is_visible(attr, n); - - return attr->mode; -} - -/* Chip name, required by hwmon */ -static SENSOR_DEVICE_ATTR_RO(name, name, 0); - -/* GPADC - SENSOR1 */ -static SENSOR_DEVICE_ATTR_RO(temp1_label, label, 0); -static SENSOR_DEVICE_ATTR_RO(temp1_input, input, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_min, min, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_max, max, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, max_hyst, 0); -static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, min_alarm, 0); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, max_alarm, 0); - -/* GPADC - SENSOR2 */ -static SENSOR_DEVICE_ATTR_RO(temp2_label, label, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_input, input, 1); -static SENSOR_DEVICE_ATTR_RW(temp2_min, min, 1); -static SENSOR_DEVICE_ATTR_RW(temp2_max, max, 1); -static SENSOR_DEVICE_ATTR_RW(temp2_max_hyst, max_hyst, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, min_alarm, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, max_alarm, 1); - -/* GPADC - SENSOR3 */ -static SENSOR_DEVICE_ATTR_RO(temp3_label, label, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_input, input, 2); -static SENSOR_DEVICE_ATTR_RW(temp3_min, min, 2); -static SENSOR_DEVICE_ATTR_RW(temp3_max, max, 2); -static SENSOR_DEVICE_ATTR_RW(temp3_max_hyst, max_hyst, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_min_alarm, min_alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, max_alarm, 2); - -/* GPADC - SENSOR4 */ -static SENSOR_DEVICE_ATTR_RO(temp4_label, label, 3); -static SENSOR_DEVICE_ATTR_RO(temp4_input, input, 3); -static SENSOR_DEVICE_ATTR_RW(temp4_min, min, 3); -static SENSOR_DEVICE_ATTR_RW(temp4_max, max, 3); -static SENSOR_DEVICE_ATTR_RW(temp4_max_hyst, max_hyst, 3); -static SENSOR_DEVICE_ATTR_RO(temp4_min_alarm, min_alarm, 3); -static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, max_alarm, 3); - -static struct attribute *abx500_temp_attributes[] = { - &sensor_dev_attr_name.dev_attr.attr, - - &sensor_dev_attr_temp1_label.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - - &sensor_dev_attr_temp2_label.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - - &sensor_dev_attr_temp3_label.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - - &sensor_dev_attr_temp4_label.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp4_min.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - NULL -}; - -static const struct attribute_group abx500_temp_group = { - .attrs = abx500_temp_attributes, - .is_visible = abx500_attrs_visible, -}; - -static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) -{ - struct platform_device *pdev = irq_data; - struct abx500_temp *data = platform_get_drvdata(pdev); - - data->ops.irq_handler(irq, data); - return IRQ_HANDLED; -} - -static int setup_irqs(struct platform_device *pdev) -{ - int ret; - int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); - - if (irq < 0) { - dev_err(&pdev->dev, "Get irq by name failed\n"); - return irq; - } - - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - abx500_temp_irq_handler, 0, "abx500-temp", pdev); - if (ret < 0) - dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); - - return ret; -} - -static int abx500_temp_probe(struct platform_device *pdev) -{ - struct abx500_temp *data; - int err; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->pdev = pdev; - mutex_init(&data->lock); - - /* Chip specific initialization */ - err = abx500_hwmon_init(data); - if (err < 0 || !data->ops.read_sensor || !data->ops.show_name || - !data->ops.show_label) - return err; - - INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor); - - platform_set_drvdata(pdev, data); - - err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); - if (err < 0) { - dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); - return err; - } - - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Class registration failed (%d)\n", err); - goto exit_sysfs_group; - } - - if (data->ops.irq_handler) { - err = setup_irqs(pdev); - if (err < 0) - goto exit_hwmon_reg; - } - return 0; - -exit_hwmon_reg: - hwmon_device_unregister(data->hwmon_dev); -exit_sysfs_group: - sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); - return err; -} - -static int abx500_temp_remove(struct platform_device *pdev) -{ - struct abx500_temp *data = platform_get_drvdata(pdev); - - cancel_delayed_work_sync(&data->work); - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); - - return 0; -} - -static int abx500_temp_suspend(struct platform_device *pdev, - pm_message_t state) -{ - struct abx500_temp *data = platform_get_drvdata(pdev); - - if (data->work_active) - cancel_delayed_work_sync(&data->work); - - return 0; -} - -static int abx500_temp_resume(struct platform_device *pdev) -{ - struct abx500_temp *data = platform_get_drvdata(pdev); - - if (data->work_active) - schedule_monitor(data); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id abx500_temp_match[] = { - { .compatible = "stericsson,abx500-temp" }, - {}, -}; -MODULE_DEVICE_TABLE(of, abx500_temp_match); -#endif - -static struct platform_driver abx500_temp_driver = { - .driver = { - .name = "abx500-temp", - .of_match_table = of_match_ptr(abx500_temp_match), - }, - .suspend = abx500_temp_suspend, - .resume = abx500_temp_resume, - .probe = abx500_temp_probe, - .remove = abx500_temp_remove, -}; - -module_platform_driver(abx500_temp_driver); - -MODULE_AUTHOR("Martin Persson "); -MODULE_DESCRIPTION("ABX500 temperature driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h deleted file mode 100644 index 4517594260f2..000000000000 --- a/drivers/hwmon/abx500.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson 2010 - 2013 - * Author: Martin Persson - * Hongbo Zhang - */ - -#ifndef _ABX500_H -#define _ABX500_H - -#define NUM_SENSORS 5 - -struct abx500_temp; - -/* - * struct abx500_temp_ops - abx500 chip specific ops - * @read_sensor: reads gpadc output - * @irq_handler: irq handler - * @show_name: hwmon device name - * @show_label: hwmon attribute label - * @is_visible: is attribute visible - */ -struct abx500_temp_ops { - int (*read_sensor)(struct abx500_temp *, u8, int *); - int (*irq_handler)(int, struct abx500_temp *); - ssize_t (*show_name)(struct device *, - struct device_attribute *, char *); - ssize_t (*show_label) (struct device *, - struct device_attribute *, char *); - int (*is_visible)(struct attribute *, int); -}; - -/* - * struct abx500_temp - representation of temp mon device - * @pdev: platform device - * @hwmon_dev: hwmon device - * @ops: abx500 chip specific ops - * @gpadc_addr: gpadc channel address - * @min: sensor temperature min value - * @max: sensor temperature max value - * @max_hyst: sensor temperature hysteresis value for max limit - * @min_alarm: sensor temperature min alarm - * @max_alarm: sensor temperature max alarm - * @work: delayed work scheduled to monitor temperature periodically - * @work_active: True if work is active - * @lock: mutex - * @monitored_sensors: number of monitored sensors - * @plat_data: private usage, usually points to platform specific data - */ -struct abx500_temp { - struct platform_device *pdev; - struct device *hwmon_dev; - struct abx500_temp_ops ops; - u8 gpadc_addr[NUM_SENSORS]; - unsigned long min[NUM_SENSORS]; - unsigned long max[NUM_SENSORS]; - unsigned long max_hyst[NUM_SENSORS]; - bool min_alarm[NUM_SENSORS]; - bool max_alarm[NUM_SENSORS]; - struct delayed_work work; - bool work_active; - struct mutex lock; - int monitored_sensors; - void *plat_data; -}; - -int abx500_hwmon_init(struct abx500_temp *data); - -#endif /* _ABX500_H */ diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c new file mode 100644 index 000000000000..2d9770cb4401 --- /dev/null +++ b/drivers/hwmon/aht10.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * aht10.c - Linux hwmon driver for AHT10 Temperature and Humidity sensor + * Copyright (C) 2020 Johannes Cornelis Draaijer + */ + +#include +#include +#include +#include +#include + +#define AHT10_MEAS_SIZE 6 + +/* + * Poll intervals (in milliseconds) + */ +#define AHT10_DEFAULT_MIN_POLL_INTERVAL 2000 +#define AHT10_MIN_POLL_INTERVAL 2000 + +/* + * I2C command delays (in microseconds) + */ +#define AHT10_MEAS_DELAY 80000 +#define AHT10_CMD_DELAY 350000 +#define AHT10_DELAY_EXTRA 100000 + +/* + * Command bytes + */ +#define AHT10_CMD_INIT 0b11100001 +#define AHT10_CMD_MEAS 0b10101100 +#define AHT10_CMD_RST 0b10111010 + +/* + * Flags in the answer byte/command + */ +#define AHT10_CAL_ENABLED BIT(3) +#define AHT10_BUSY BIT(7) +#define AHT10_MODE_NOR (BIT(5) | BIT(6)) +#define AHT10_MODE_CYC BIT(5) +#define AHT10_MODE_CMD BIT(6) + +#define AHT10_MAX_POLL_INTERVAL_LEN 30 + +/** + * struct aht10_data - All the data required to operate an AHT10 chip + * @client: the i2c client associated with the AHT10 + * @lock: a mutex that is used to prevent parallel access to the + * i2c client + * @min_poll_interval: the minimum poll interval + * While the poll rate limit is not 100% necessary, + * the datasheet recommends that a measurement + * is not performed too often to prevent + * the chip from warming up due to the heat it generates. + * If it's unwanted, it can be ignored setting it to + * it to 0. Default value is 2000 ms + * @previous_poll_time: the previous time that the AHT10 + * was polled + * @temperature: the latest temperature value received from + * the AHT10 + * @humidity: the latest humidity value received from the + * AHT10 + */ + +struct aht10_data { + struct i2c_client *client; + /* + * Prevent simultaneous access to the i2c + * client and previous_poll_time + */ + struct mutex lock; + ktime_t min_poll_interval; + ktime_t previous_poll_time; + int temperature; + int humidity; +}; + +/** + * aht10_init() - Initialize an AHT10 chip + * @client: the i2c client associated with the AHT10 + * @data: the data associated with this AHT10 chip + * Return: 0 if succesfull, 1 if not + */ +static int aht10_init(struct aht10_data *data) +{ + const u8 cmd_init[] = {AHT10_CMD_INIT, AHT10_CAL_ENABLED | AHT10_MODE_CYC, + 0x00}; + int res; + u8 status; + struct i2c_client *client = data->client; + + res = i2c_master_send(client, cmd_init, 3); + if (res < 0) + return res; + + usleep_range(AHT10_CMD_DELAY, AHT10_CMD_DELAY + + AHT10_DELAY_EXTRA); + + res = i2c_master_recv(client, &status, 1); + if (res != 1) + return -ENODATA; + + if (status & AHT10_BUSY) + return -EBUSY; + + return 0; +} + +/** + * aht10_polltime_expired() - check if the minimum poll interval has + * expired + * @data: the data containing the time to compare + * Return: 1 if the minimum poll interval has expired, 0 if not + */ +static int aht10_polltime_expired(struct aht10_data *data) +{ + ktime_t current_time = ktime_get_boottime(); + ktime_t difference = ktime_sub(current_time, data->previous_poll_time); + + return ktime_after(difference, data->min_poll_interval); +} + +/** + * aht10_read_values() - read and parse the raw data from the AHT10 + * @aht10_data: the struct aht10_data to use for the lock + * Return: 0 if succesfull, 1 if not + */ +static int aht10_read_values(struct aht10_data *data) +{ + const u8 cmd_meas[] = {AHT10_CMD_MEAS, 0x33, 0x00}; + u32 temp, hum; + int res; + u8 raw_data[AHT10_MEAS_SIZE]; + struct i2c_client *client = data->client; + + mutex_lock(&data->lock); + if (aht10_polltime_expired(data)) { + res = i2c_master_send(client, cmd_meas, sizeof(cmd_meas)); + if (res < 0) { + mutex_unlock(&data->lock); + return res; + } + + usleep_range(AHT10_MEAS_DELAY, + AHT10_MEAS_DELAY + AHT10_DELAY_EXTRA); + + res = i2c_master_recv(client, raw_data, AHT10_MEAS_SIZE); + if (res != AHT10_MEAS_SIZE) { + mutex_unlock(&data->lock); + if (res >= 0) + return -ENODATA; + else + return res; + } + + hum = ((u32)raw_data[1] << 12u) | + ((u32)raw_data[2] << 4u) | + ((raw_data[3] & 0xF0u) >> 4u); + + temp = ((u32)(raw_data[3] & 0x0Fu) << 16u) | + ((u32)raw_data[4] << 8u) | + raw_data[5]; + + temp = ((temp * 625) >> 15u) * 10; + hum = ((hum * 625) >> 16u) * 10; + + data->temperature = (int)temp - 50000; + data->humidity = hum; + data->previous_poll_time = ktime_get_boottime(); + } + mutex_unlock(&data->lock); + return 0; +} + +/** + * aht10_interval_write() - store the given minimum poll interval. + * Return: 0 on success, -EINVAL if a value lower than the + * AHT10_MIN_POLL_INTERVAL is given + */ +static ssize_t aht10_interval_write(struct aht10_data *data, + long val) +{ + data->min_poll_interval = ms_to_ktime(clamp_val(val, 2000, LONG_MAX)); + return 0; +} + +/** + * aht10_interval_read() - read the minimum poll interval + * in milliseconds + */ +static ssize_t aht10_interval_read(struct aht10_data *data, + long *val) +{ + *val = ktime_to_ms(data->min_poll_interval); + return 0; +} + +/** + * aht10_temperature1_read() - read the temperature in millidegrees + */ +static int aht10_temperature1_read(struct aht10_data *data, long *val) +{ + int res; + + res = aht10_read_values(data); + if (res < 0) + return res; + + *val = data->temperature; + return 0; +} + +/** + * aht10_humidity1_read() - read the relative humidity in millipercent + */ +static int aht10_humidity1_read(struct aht10_data *data, long *val) +{ + int res; + + res = aht10_read_values(data); + if (res < 0) + return res; + + *val = data->humidity; + return 0; +} + +static umode_t aht10_hwmon_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + case hwmon_humidity: + return 0444; + case hwmon_chip: + return 0644; + default: + return 0; + } +} + +static int aht10_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct aht10_data *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return aht10_temperature1_read(data, val); + case hwmon_humidity: + return aht10_humidity1_read(data, val); + case hwmon_chip: + return aht10_interval_read(data, val); + default: + return -EOPNOTSUPP; + } +} + +static int aht10_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct aht10_data *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_chip: + return aht10_interval_write(data, val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info *aht10_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT), + NULL, +}; + +static const struct hwmon_ops aht10_hwmon_ops = { + .is_visible = aht10_hwmon_visible, + .read = aht10_hwmon_read, + .write = aht10_hwmon_write, +}; + +static const struct hwmon_chip_info aht10_chip_info = { + .ops = &aht10_hwmon_ops, + .info = aht10_info, +}; + +static int aht10_probe(struct i2c_client *client, + const struct i2c_device_id *aht10_id) +{ + struct device *device = &client->dev; + struct device *hwmon_dev; + struct aht10_data *data; + int res; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENOENT; + + data = devm_kzalloc(device, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->min_poll_interval = ms_to_ktime(AHT10_DEFAULT_MIN_POLL_INTERVAL); + data->client = client; + + mutex_init(&data->lock); + + res = aht10_init(data); + if (res < 0) + return res; + + res = aht10_read_values(data); + if (res < 0) + return res; + + hwmon_dev = devm_hwmon_device_register_with_info(device, + client->name, + data, + &aht10_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id aht10_id[] = { + { "aht10", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, aht10_id); + +static struct i2c_driver aht10_driver = { + .driver = { + .name = "aht10", + }, + .probe = aht10_probe, + .id_table = aht10_id, +}; + +module_i2c_driver(aht10_driver); + +MODULE_AUTHOR("Johannes Cornelis Draaijer "); +MODULE_DESCRIPTION("AHT10 Temperature and Humidity sensor driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/amd_energy.c b/drivers/hwmon/amd_energy.c index 822c2e74b98d..a86cc8d6d93d 100644 --- a/drivers/hwmon/amd_energy.c +++ b/drivers/hwmon/amd_energy.c @@ -333,6 +333,7 @@ static struct platform_device *amd_energy_platdev; static const struct x86_cpu_id cpu_ids[] __initconst = { X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x30, NULL), {} }; MODULE_DEVICE_TABLE(x86cpu, cpu_ids); diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 89207af81c48..28b137eedf2e 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -565,7 +565,7 @@ static int applesmc_init_index(struct applesmc_registers *s) static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; - bool left_light_sensor = 0, right_light_sensor = 0; + bool left_light_sensor = false, right_light_sensor = false; unsigned int count; u8 tmp[1]; int ret; diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index 3d8239fd66ed..3cb88d6fbec0 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -620,7 +620,7 @@ static ssize_t rpm_show(struct device *dev, struct device_attribute *attr, static umode_t pwm_is_visible(struct kobject *kobj, struct attribute *a, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); if (!priv->pwm_present[index]) @@ -631,7 +631,7 @@ static umode_t pwm_is_visible(struct kobject *kobj, static umode_t fan_dev_is_visible(struct kobject *kobj, struct attribute *a, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); if (!priv->fan_tach_present[index]) diff --git a/drivers/hwmon/da9052-hwmon.c b/drivers/hwmon/da9052-hwmon.c index 4af2fc309c28..ed6c5df94fdf 100644 --- a/drivers/hwmon/da9052-hwmon.c +++ b/drivers/hwmon/da9052-hwmon.c @@ -299,7 +299,7 @@ static ssize_t label_show(struct device *dev, static umode_t da9052_channel_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct da9052_hwmon *hwmon = dev_get_drvdata(dev); struct device_attribute *dattr = container_of(attr, struct device_attribute, attr); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index ec448f5f2dc3..73b9db9e3aab 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1159,6 +1159,13 @@ static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), }, }, + { + .ident = "Dell XPS 15 L502X", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"), + }, + }, { } }; diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 3ea4021f267c..befe989ca7b9 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -299,7 +299,7 @@ static DEVICE_ATTR(fan1_target, 0644, fan1_input_show, set_rpm); static umode_t gpio_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct gpio_fan_data *data = dev_get_drvdata(dev); if (index == 0 && !data->alarm_gpio) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3bc2551577a3..5ff3669c2b60 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -448,7 +448,8 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->is_zen = true; switch (boot_cpu_data.x86_model) { - case 0x0 ... 0x1: /* Zen3 */ + case 0x0 ... 0x1: /* Zen3 SP3/TR */ + case 0x21: /* Zen3 Ryzen Desktop */ k10temp_get_ccd_support(pdev, data, 8); break; } diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index cc7f2980fe83..f8d4534ce172 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -321,7 +321,7 @@ static SENSOR_DEVICE_ATTR_RO(gpio2_alarm, alarm, MAX6650_ALRM_GPIO2); static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct max6650_data *data = dev_get_drvdata(dev); struct device_attribute *devattr; diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 7f7e30f0de7b..a23047a3bfe2 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -169,6 +169,7 @@ superio_exit(int ioreg) #define NCT6683_CUSTOMER_ID_INTEL 0x805 #define NCT6683_CUSTOMER_ID_MITAC 0xa0e #define NCT6683_CUSTOMER_ID_MSI 0x201 +#define NCT6683_CUSTOMER_ID_ASROCK 0xe2c #define NCT6683_REG_BUILD_YEAR 0x604 #define NCT6683_REG_BUILD_MONTH 0x605 @@ -1225,6 +1226,8 @@ static int nct6683_probe(struct platform_device *pdev) break; case NCT6683_CUSTOMER_ID_MSI: break; + case NCT6683_CUSTOMER_ID_ASROCK: + break; default: if (!force) return -ENODEV; diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 94f4b8b4a2ba..6a9ba23cd302 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1700,8 +1700,8 @@ static int __init pc87360_device_add(unsigned short address) continue; res[res_count].start = extra_isa[i]; res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1; - res[res_count].name = "pc87360", - res[res_count].flags = IORESOURCE_IO, + res[res_count].name = "pc87360"; + res[res_count].flags = IORESOURCE_IO; err = acpi_check_resource_conflict(&res[res_count]); if (err) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 03606d4298a4..32d2fc850621 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -158,10 +158,10 @@ config SENSORS_MAX16064 be called max16064. config SENSORS_MAX16601 - tristate "Maxim MAX16601" + tristate "Maxim MAX16508, MAX16601" help If you say yes here you get hardware monitoring support for Maxim - MAX16601. + MAX16508 and MAX16601. This driver can also be built as a module. If so, the module will be called max16601. diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index d6bbbb223871..ffde5aaa5036 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -472,7 +472,7 @@ static struct pmbus_driver_info ibm_cffps_info[] = { }; static struct pmbus_platform_data ibm_cffps_pdata = { - .flags = PMBUS_SKIP_STATUS_CHECK, + .flags = PMBUS_SKIP_STATUS_CHECK | PMBUS_NO_CAPABILITY, }; static int ibm_cffps_probe(struct i2c_client *client) diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index c75a6bf39641..e9a66fd9e144 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -371,21 +371,18 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, case PMBUS_VIN_OV_WARN_LIMIT: word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); ret = pmbus_write_word_data(client, 0, reg, word); - pmbus_clear_cache(client); break; case PMBUS_IIN_OC_WARN_LIMIT: word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); ret = pmbus_write_word_data(client, 0, LM25066_MFR_IIN_OC_WARN_LIMIT, word); - pmbus_clear_cache(client); break; case PMBUS_PIN_OP_WARN_LIMIT: word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); ret = pmbus_write_word_data(client, 0, LM25066_MFR_PIN_OP_WARN_LIMIT, word); - pmbus_clear_cache(client); break; case PMBUS_VIRT_VMON_UV_WARN_LIMIT: /* Adjust from VIN coefficients (for LM25056) */ @@ -393,7 +390,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); ret = pmbus_write_word_data(client, 0, LM25056_VAUX_UV_WARN_LIMIT, word); - pmbus_clear_cache(client); break; case PMBUS_VIRT_VMON_OV_WARN_LIMIT: /* Adjust from VIN coefficients (for LM25056) */ @@ -401,7 +397,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); ret = pmbus_write_word_data(client, 0, LM25056_VAUX_OV_WARN_LIMIT, word); - pmbus_clear_cache(client); break; case PMBUS_VIRT_RESET_PIN_HISTORY: ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index a960b86e72d2..0d1204c2dd54 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Hardware monitoring driver for Maxim MAX16601 + * Hardware monitoring driver for Maxim MAX16508 and MAX16601. * * Implementation notes: * - * Ths chip supports two rails, VCORE and VSA. Telemetry information for the - * two rails is reported in two subsequent I2C addresses. The driver + * This chip series supports two rails, VCORE and VSA. Telemetry information + * for the two rails is reported in two subsequent I2C addresses. The driver * instantiates a dummy I2C client at the second I2C address to report * information for the VSA rail in a single instance of the driver. * Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2. @@ -31,6 +31,9 @@ #include "pmbus.h" +enum chips { max16508, max16601 }; + +#define REG_DEFAULT_NUM_POP 0xc4 #define REG_SETPT_DVID 0xd1 #define DAC_10MV_MODE BIT(4) #define REG_IOUT_AVG_PK 0xee @@ -40,7 +43,10 @@ #define CORE_RAIL_INDICATOR BIT(7) #define REG_PHASE_REPORTING 0xf4 +#define MAX16601_NUM_PHASES 8 + struct max16601_data { + enum chips id; struct pmbus_driver_info info; struct i2c_client *vsa; int iout_avg_pkg; @@ -185,6 +191,7 @@ static int max16601_write_word(struct i2c_client *client, int page, int reg, static int max16601_identify(struct i2c_client *client, struct pmbus_driver_info *info) { + struct max16601_data *data = to_max16601_data(info); int reg; reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID); @@ -195,6 +202,21 @@ static int max16601_identify(struct i2c_client *client, else info->vrm_version[0] = vr12; + if (data->id != max16601) + return 0; + + reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP); + if (reg < 0) + return reg; + + /* + * If REG_DEFAULT_NUM_POP returns 0, we don't know how many phases + * are populated. Stick with the default in that case. + */ + reg &= 0x0f; + if (reg && reg <= MAX16601_NUM_PHASES) + info->phases[0] = reg; + return 0; } @@ -216,7 +238,7 @@ static struct pmbus_driver_info max16601_info = { .func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL, - .phases[0] = 8, + .phases[0] = MAX16601_NUM_PHASES, .pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, .pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, .pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, @@ -239,28 +261,61 @@ static void max16601_remove(void *_data) i2c_unregister_device(data->vsa); } -static int max16601_probe(struct i2c_client *client) +static const struct i2c_device_id max16601_id[] = { + {"max16508", max16508}, + {"max16601", max16601}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max16601_id); + +static int max16601_get_id(struct i2c_client *client) { struct device *dev = &client->dev; u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; - struct max16601_data *data; + enum chips id; int ret; + ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0 || ret < 11) + return -ENODEV; + + /* + * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" + * or "MAX16500y.xx". + */ + if (!strncmp(buf, "MAX16500", 8)) { + id = max16508; + } else if (!strncmp(buf, "MAX16601", 8)) { + id = max16601; + } else { + buf[ret] = '\0'; + dev_err(dev, "Unsupported chip '%s'\n", buf); + return -ENODEV; + } + return id; +} + +static int max16601_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct i2c_device_id *id; + struct max16601_data *data; + int ret, chip_id; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) return -ENODEV; - ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); - if (ret < 0) - return -ENODEV; + chip_id = max16601_get_id(client); + if (chip_id < 0) + return chip_id; - /* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */ - if (ret < 11 || strncmp(buf, "MAX16601", 8)) { - buf[ret] = '\0'; - dev_err(dev, "Unsupported chip '%s'\n", buf); - return -ENODEV; - } + id = i2c_match_id(max16601_id, client); + if (chip_id != id->driver_data) + dev_warn(&client->dev, + "Device mismatch: Configured %s (%d), detected %d\n", + id->name, (int) id->driver_data, chip_id); ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID); if (ret < 0) @@ -275,6 +330,7 @@ static int max16601_probe(struct i2c_client *client) if (!data) return -ENOMEM; + data->id = chip_id; data->iout_avg_pkg = 0xfc00; data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1); if (IS_ERR(data->vsa)) { @@ -290,13 +346,6 @@ static int max16601_probe(struct i2c_client *client) return pmbus_do_probe(client, &data->info); } -static const struct i2c_device_id max16601_id[] = { - {"max16601", 0}, - {} -}; - -MODULE_DEVICE_TABLE(i2c, max16601_id); - static struct i2c_driver max16601_driver = { .driver = { .name = "max16601", diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index e5a9f4019cd5..17489abc49d5 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -17,6 +17,7 @@ enum max31785_regs { #define MAX31785 0x3030 #define MAX31785A 0x3040 +#define MAX31785B 0x3061 #define MFR_FAN_CONFIG_DUAL_TACH BIT(12) @@ -329,7 +330,7 @@ static int max31785_probe(struct i2c_client *client) struct device *dev = &client->dev; struct pmbus_driver_info *info; bool dual_tach = false; - s64 ret; + int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -350,12 +351,14 @@ static int max31785_probe(struct i2c_client *client) if (ret < 0) return ret; - if (ret == MAX31785A) { + if (ret == MAX31785A || ret == MAX31785B) { dual_tach = true; } else if (ret == MAX31785) { - if (!strcmp("max31785a", client->name)) - dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n"); + if (!strcmp("max31785a", client->name) || + !strcmp("max31785b", client->name)) + dev_warn(dev, "Expected max31785a/b, found max31785: cannot provide secondary tachometer readings\n"); } else { + dev_err(dev, "Unrecognized MAX31785 revision: %x\n", ret); return -ENODEV; } @@ -371,6 +374,7 @@ static int max31785_probe(struct i2c_client *client) static const struct i2c_device_id max31785_id[] = { { "max31785", 0 }, { "max31785a", 0 }, + { "max31785b", 0 }, { }, }; @@ -379,6 +383,7 @@ MODULE_DEVICE_TABLE(i2c, max31785_id); static const struct of_device_id max31785_of_match[] = { { .compatible = "maxim,max31785" }, { .compatible = "maxim,max31785a" }, + { .compatible = "maxim,max31785b" }, { }, }; diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 192442b3b7a2..aadea85fe630 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -974,7 +974,7 @@ static ssize_t pmbus_set_sensor(struct device *dev, if (ret < 0) rv = ret; else - sensor->data = regval; + sensor->data = -ENODATA; mutex_unlock(&data->update_lock); return rv; } @@ -1262,7 +1262,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, * which global bit is set) for this page is accessible. */ if (!ret && attr->gbit && - (!upper || (upper && data->has_status_word)) && + (!upper || data->has_status_word) && pmbus_check_status_register(client, page)) { ret = pmbus_add_boolean(data, name, "alarm", index, NULL, NULL, @@ -2204,9 +2204,11 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, } /* Enable PEC if the controller supports it */ - ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); - if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) - client->flags |= I2C_CLIENT_PEC; + if (!(data->flags & PMBUS_NO_CAPABILITY)) { + ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) + client->flags |= I2C_CLIENT_PEC; + } /* * Check if the chip is write protected. If it is, we can not clear diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 111a91dc6b79..17518b4cab1b 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -21,15 +21,21 @@ #define MAX_PWM 255 -struct pwm_fan_ctx { - struct mutex lock; - struct pwm_device *pwm; - struct regulator *reg_en; - +struct pwm_fan_tach { int irq; atomic_t pulses; unsigned int rpm; u8 pulses_per_revolution; +}; + +struct pwm_fan_ctx { + struct mutex lock; + struct pwm_device *pwm; + struct pwm_state pwm_state; + struct regulator *reg_en; + + int tach_count; + struct pwm_fan_tach *tachs; ktime_t sample_start; struct timer_list rpm_timer; @@ -40,6 +46,7 @@ struct pwm_fan_ctx { struct thermal_cooling_device *cdev; struct hwmon_chip_info info; + struct hwmon_channel_info fan_channel; }; static const u32 pwm_fan_channel_config_pwm[] = { @@ -52,22 +59,12 @@ static const struct hwmon_channel_info pwm_fan_channel_pwm = { .config = pwm_fan_channel_config_pwm, }; -static const u32 pwm_fan_channel_config_fan[] = { - HWMON_F_INPUT, - 0 -}; - -static const struct hwmon_channel_info pwm_fan_channel_fan = { - .type = hwmon_fan, - .config = pwm_fan_channel_config_fan, -}; - /* This handler assumes self resetting edge triggered interrupt. */ static irqreturn_t pulse_handler(int irq, void *dev_id) { - struct pwm_fan_ctx *ctx = dev_id; + struct pwm_fan_tach *tach = dev_id; - atomic_inc(&ctx->pulses); + atomic_inc(&tach->pulses); return IRQ_HANDLED; } @@ -76,13 +73,18 @@ static void sample_timer(struct timer_list *t) { struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer); unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start); - int pulses; + int i; if (delta) { - pulses = atomic_read(&ctx->pulses); - atomic_sub(pulses, &ctx->pulses); - ctx->rpm = (unsigned int)(pulses * 1000 * 60) / - (ctx->pulses_per_revolution * delta); + for (i = 0; i < ctx->tach_count; i++) { + struct pwm_fan_tach *tach = &ctx->tachs[i]; + int pulses; + + pulses = atomic_read(&tach->pulses); + atomic_sub(pulses, &tach->pulses); + tach->rpm = (unsigned int)(pulses * 1000 * 60) / + (tach->pulses_per_revolution * delta); + } ctx->sample_start = ktime_get(); } @@ -94,18 +96,17 @@ static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm) { unsigned long period; int ret = 0; - struct pwm_state state = { }; + struct pwm_state *state = &ctx->pwm_state; mutex_lock(&ctx->lock); if (ctx->pwm_value == pwm) goto exit_set_pwm_err; - pwm_init_state(ctx->pwm, &state); - period = ctx->pwm->args.period; - state.duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM); - state.enabled = pwm ? true : false; + period = state->period; + state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM); + state->enabled = pwm ? true : false; - ret = pwm_apply_state(ctx->pwm, &state); + ret = pwm_apply_state(ctx->pwm, state); if (!ret) ctx->pwm_value = pwm; exit_set_pwm_err: @@ -152,7 +153,7 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_fan: - *val = ctx->rpm; + *val = ctx->tachs[channel].rpm; return 0; default: @@ -287,7 +288,9 @@ static void pwm_fan_regulator_disable(void *data) static void pwm_fan_pwm_disable(void *__ctx) { struct pwm_fan_ctx *ctx = __ctx; - pwm_disable(ctx->pwm); + + ctx->pwm_state.enabled = false; + pwm_apply_state(ctx->pwm, &ctx->pwm_state); del_timer_sync(&ctx->rpm_timer); } @@ -298,9 +301,10 @@ static int pwm_fan_probe(struct platform_device *pdev) struct pwm_fan_ctx *ctx; struct device *hwmon; int ret; - struct pwm_state state = { }; - int tach_count; const struct hwmon_channel_info **channels; + u32 *fan_channel_config; + int channel_count = 1; /* We always have a PWM channel. */ + int i; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -334,22 +338,20 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx->pwm_value = MAX_PWM; - pwm_init_state(ctx->pwm, &state); + pwm_init_state(ctx->pwm, &ctx->pwm_state); + /* * __set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned * long. Check this here to prevent the fan running at a too low * frequency. */ - if (state.period > ULONG_MAX / MAX_PWM + 1) { + if (ctx->pwm_state.period > ULONG_MAX / MAX_PWM + 1) { dev_err(dev, "Configured period too big\n"); return -EINVAL; } /* Set duty cycle to maximum allowed and enable PWM output */ - state.duty_cycle = ctx->pwm->args.period - 1; - state.enabled = true; - - ret = pwm_apply_state(ctx->pwm, &state); + ret = __set_pwm(ctx, MAX_PWM); if (ret) { dev_err(dev, "Failed to configure PWM: %d\n", ret); return ret; @@ -359,27 +361,46 @@ static int pwm_fan_probe(struct platform_device *pdev) if (ret) return ret; - tach_count = platform_irq_count(pdev); - if (tach_count < 0) - return dev_err_probe(dev, tach_count, + ctx->tach_count = platform_irq_count(pdev); + if (ctx->tach_count < 0) + return dev_err_probe(dev, ctx->tach_count, "Could not get number of fan tachometer inputs\n"); + dev_dbg(dev, "%d fan tachometer inputs\n", ctx->tach_count); - channels = devm_kcalloc(dev, tach_count + 2, + if (ctx->tach_count) { + channel_count++; /* We also have a FAN channel. */ + + ctx->tachs = devm_kcalloc(dev, ctx->tach_count, + sizeof(struct pwm_fan_tach), + GFP_KERNEL); + if (!ctx->tachs) + return -ENOMEM; + + ctx->fan_channel.type = hwmon_fan; + fan_channel_config = devm_kcalloc(dev, ctx->tach_count + 1, + sizeof(u32), GFP_KERNEL); + if (!fan_channel_config) + return -ENOMEM; + ctx->fan_channel.config = fan_channel_config; + } + + channels = devm_kcalloc(dev, channel_count + 1, sizeof(struct hwmon_channel_info *), GFP_KERNEL); if (!channels) return -ENOMEM; channels[0] = &pwm_fan_channel_pwm; - if (tach_count > 0) { + for (i = 0; i < ctx->tach_count; i++) { + struct pwm_fan_tach *tach = &ctx->tachs[i]; u32 ppr = 2; - ctx->irq = platform_get_irq(pdev, 0); - if (ctx->irq == -EPROBE_DEFER) - return ctx->irq; - if (ctx->irq > 0) { - ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0, - pdev->name, ctx); + tach->irq = platform_get_irq(pdev, i); + if (tach->irq == -EPROBE_DEFER) + return tach->irq; + if (tach->irq > 0) { + ret = devm_request_irq(dev, tach->irq, pulse_handler, 0, + pdev->name, tach); if (ret) { dev_err(dev, "Failed to request interrupt: %d\n", @@ -388,22 +409,27 @@ static int pwm_fan_probe(struct platform_device *pdev) } } - of_property_read_u32(dev->of_node, - "pulses-per-revolution", - &ppr); - ctx->pulses_per_revolution = ppr; - if (!ctx->pulses_per_revolution) { + of_property_read_u32_index(dev->of_node, + "pulses-per-revolution", + i, + &ppr); + tach->pulses_per_revolution = ppr; + if (!tach->pulses_per_revolution) { dev_err(dev, "pulses-per-revolution can't be zero.\n"); return -EINVAL; } - dev_dbg(dev, "tach: irq=%d, pulses_per_revolution=%d\n", - ctx->irq, ctx->pulses_per_revolution); + fan_channel_config[i] = HWMON_F_INPUT; + dev_dbg(dev, "tach%d: irq=%d, pulses_per_revolution=%d\n", + i, tach->irq, tach->pulses_per_revolution); + } + + if (ctx->tach_count > 0) { ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); - channels[1] = &pwm_fan_channel_fan; + channels[1] = &ctx->fan_channel; } ctx->info.ops = &pwm_fan_hwmon_ops; @@ -432,7 +458,6 @@ static int pwm_fan_probe(struct platform_device *pdev) return ret; } ctx->cdev = cdev; - thermal_cdev_update(cdev); } return 0; @@ -441,17 +466,17 @@ static int pwm_fan_probe(struct platform_device *pdev) static int pwm_fan_disable(struct device *dev) { struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - struct pwm_args args; int ret; - pwm_get_args(ctx->pwm, &args); - if (ctx->pwm_value) { - ret = pwm_config(ctx->pwm, 0, args.period); + /* keep ctx->pwm_state unmodified for pwm_fan_resume() */ + struct pwm_state state = ctx->pwm_state; + + state.duty_cycle = 0; + state.enabled = false; + ret = pwm_apply_state(ctx->pwm, &state); if (ret < 0) return ret; - - pwm_disable(ctx->pwm); } if (ctx->reg_en) { @@ -479,8 +504,6 @@ static int pwm_fan_suspend(struct device *dev) static int pwm_fan_resume(struct device *dev) { struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - struct pwm_args pargs; - unsigned long duty; int ret; if (ctx->reg_en) { @@ -494,12 +517,7 @@ static int pwm_fan_resume(struct device *dev) if (ctx->pwm_value == 0) return 0; - pwm_get_args(ctx->pwm, &pargs); - duty = DIV_ROUND_UP_ULL(ctx->pwm_value * (pargs.period - 1), MAX_PWM); - ret = pwm_config(ctx->pwm, duty, pargs.period); - if (ret) - return ret; - return pwm_enable(ctx->pwm); + return pwm_apply_state(ctx->pwm, &ctx->pwm_state); } #endif diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c index b637836b58a1..37531b5c8254 100644 --- a/drivers/hwmon/smsc47m1.c +++ b/drivers/hwmon/smsc47m1.c @@ -682,7 +682,7 @@ static int __init smsc47m1_handle_resources(unsigned short address, /* Request the resources */ if (!devm_request_region(dev, start, len, DRVNAME)) { dev_err(dev, - "Region 0x%hx-0x%hx already in use!\n", + "Region 0x%x-0x%x already in use!\n", start, start + len); return -EBUSY; } diff --git a/drivers/hwmon/tps23861.c b/drivers/hwmon/tps23861.c new file mode 100644 index 000000000000..c2484f15298b --- /dev/null +++ b/drivers/hwmon/tps23861.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Sartura Ltd. + * + * Driver for the TI TPS23861 PoE PSE. + * + * Author: Robert Marko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEMPERATURE 0x2c +#define INPUT_VOLTAGE_LSB 0x2e +#define INPUT_VOLTAGE_MSB 0x2f +#define PORT_1_CURRENT_LSB 0x30 +#define PORT_1_CURRENT_MSB 0x31 +#define PORT_1_VOLTAGE_LSB 0x32 +#define PORT_1_VOLTAGE_MSB 0x33 +#define PORT_2_CURRENT_LSB 0x34 +#define PORT_2_CURRENT_MSB 0x35 +#define PORT_2_VOLTAGE_LSB 0x36 +#define PORT_2_VOLTAGE_MSB 0x37 +#define PORT_3_CURRENT_LSB 0x38 +#define PORT_3_CURRENT_MSB 0x39 +#define PORT_3_VOLTAGE_LSB 0x3a +#define PORT_3_VOLTAGE_MSB 0x3b +#define PORT_4_CURRENT_LSB 0x3c +#define PORT_4_CURRENT_MSB 0x3d +#define PORT_4_VOLTAGE_LSB 0x3e +#define PORT_4_VOLTAGE_MSB 0x3f +#define PORT_N_CURRENT_LSB_OFFSET 0x04 +#define PORT_N_VOLTAGE_LSB_OFFSET 0x04 +#define VOLTAGE_CURRENT_MASK GENMASK(13, 0) +#define PORT_1_RESISTANCE_LSB 0x60 +#define PORT_1_RESISTANCE_MSB 0x61 +#define PORT_2_RESISTANCE_LSB 0x62 +#define PORT_2_RESISTANCE_MSB 0x63 +#define PORT_3_RESISTANCE_LSB 0x64 +#define PORT_3_RESISTANCE_MSB 0x65 +#define PORT_4_RESISTANCE_LSB 0x66 +#define PORT_4_RESISTANCE_MSB 0x67 +#define PORT_N_RESISTANCE_LSB_OFFSET 0x02 +#define PORT_RESISTANCE_MASK GENMASK(13, 0) +#define PORT_RESISTANCE_RSN_MASK GENMASK(15, 14) +#define PORT_RESISTANCE_RSN_OTHER 0 +#define PORT_RESISTANCE_RSN_LOW 1 +#define PORT_RESISTANCE_RSN_OPEN 2 +#define PORT_RESISTANCE_RSN_SHORT 3 +#define PORT_1_STATUS 0x0c +#define PORT_2_STATUS 0x0d +#define PORT_3_STATUS 0x0e +#define PORT_4_STATUS 0x0f +#define PORT_STATUS_CLASS_MASK GENMASK(7, 4) +#define PORT_STATUS_DETECT_MASK GENMASK(3, 0) +#define PORT_CLASS_UNKNOWN 0 +#define PORT_CLASS_1 1 +#define PORT_CLASS_2 2 +#define PORT_CLASS_3 3 +#define PORT_CLASS_4 4 +#define PORT_CLASS_RESERVED 5 +#define PORT_CLASS_0 6 +#define PORT_CLASS_OVERCURRENT 7 +#define PORT_CLASS_MISMATCH 8 +#define PORT_DETECT_UNKNOWN 0 +#define PORT_DETECT_SHORT 1 +#define PORT_DETECT_RESERVED 2 +#define PORT_DETECT_RESISTANCE_LOW 3 +#define PORT_DETECT_RESISTANCE_OK 4 +#define PORT_DETECT_RESISTANCE_HIGH 5 +#define PORT_DETECT_OPEN_CIRCUIT 6 +#define PORT_DETECT_RESERVED_2 7 +#define PORT_DETECT_MOSFET_FAULT 8 +#define PORT_DETECT_LEGACY 9 +/* Measurment beyond clamp voltage */ +#define PORT_DETECT_CAPACITANCE_INVALID_BEYOND 10 +/* Insufficient voltage delta */ +#define PORT_DETECT_CAPACITANCE_INVALID_DELTA 11 +#define PORT_DETECT_CAPACITANCE_OUT_OF_RANGE 12 +#define POE_PLUS 0x40 +#define OPERATING_MODE 0x12 +#define OPERATING_MODE_OFF 0 +#define OPERATING_MODE_MANUAL 1 +#define OPERATING_MODE_SEMI 2 +#define OPERATING_MODE_AUTO 3 +#define OPERATING_MODE_PORT_1_MASK GENMASK(1, 0) +#define OPERATING_MODE_PORT_2_MASK GENMASK(3, 2) +#define OPERATING_MODE_PORT_3_MASK GENMASK(5, 4) +#define OPERATING_MODE_PORT_4_MASK GENMASK(7, 6) + +#define DETECT_CLASS_RESTART 0x18 +#define POWER_ENABLE 0x19 +#define TPS23861_NUM_PORTS 4 + +#define TEMPERATURE_LSB 652 /* 0.652 degrees Celsius */ +#define VOLTAGE_LSB 3662 /* 3.662 mV */ +#define SHUNT_RESISTOR_DEFAULT 255000 /* 255 mOhm */ +#define CURRENT_LSB_255 62260 /* 62.260 uA */ +#define CURRENT_LSB_250 61039 /* 61.039 uA */ +#define RESISTANCE_LSB 110966 /* 11.0966 Ohm*/ +#define RESISTANCE_LSB_LOW 157216 /* 15.7216 Ohm*/ + +struct tps23861_data { + struct regmap *regmap; + u32 shunt_resistor; + struct i2c_client *client; + struct dentry *debugfs_dir; +}; + +static struct regmap_config tps23861_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int tps23861_read_temp(struct tps23861_data *data, long *val) +{ + unsigned int regval; + int err; + + err = regmap_read(data->regmap, TEMPERATURE, ®val); + if (err < 0) + return err; + + *val = (regval * TEMPERATURE_LSB) - 20000; + + return 0; +} + +static int tps23861_read_voltage(struct tps23861_data *data, int channel, + long *val) +{ + unsigned int regval; + int err; + + if (channel < TPS23861_NUM_PORTS) { + err = regmap_bulk_read(data->regmap, + PORT_1_VOLTAGE_LSB + channel * PORT_N_VOLTAGE_LSB_OFFSET, + ®val, 2); + } else { + err = regmap_bulk_read(data->regmap, + INPUT_VOLTAGE_LSB, + ®val, 2); + } + if (err < 0) + return err; + + *val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * VOLTAGE_LSB) / 1000; + + return 0; +} + +static int tps23861_read_current(struct tps23861_data *data, int channel, + long *val) +{ + unsigned int current_lsb; + unsigned int regval; + int err; + + if (data->shunt_resistor == SHUNT_RESISTOR_DEFAULT) + current_lsb = CURRENT_LSB_255; + else + current_lsb = CURRENT_LSB_250; + + err = regmap_bulk_read(data->regmap, + PORT_1_CURRENT_LSB + channel * PORT_N_CURRENT_LSB_OFFSET, + ®val, 2); + if (err < 0) + return err; + + *val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * current_lsb) / 1000000; + + return 0; +} + +static int tps23861_port_disable(struct tps23861_data *data, int channel) +{ + unsigned int regval = 0; + int err; + + regval |= BIT(channel + 4); + err = regmap_write(data->regmap, POWER_ENABLE, regval); + + return err; +} + +static int tps23861_port_enable(struct tps23861_data *data, int channel) +{ + unsigned int regval = 0; + int err; + + regval |= BIT(channel); + regval |= BIT(channel + 4); + err = regmap_write(data->regmap, DETECT_CLASS_RESTART, regval); + + return err; +} + +static umode_t tps23861_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + default: + return 0; + } + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + case hwmon_in_enable: + return 0200; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_label: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static int tps23861_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct tps23861_data *data = dev_get_drvdata(dev); + int err; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_enable: + if (val == 0) + err = tps23861_port_disable(data, channel); + else if (val == 1) + err = tps23861_port_enable(data, channel); + else + err = -EINVAL; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return err; +} + +static int tps23861_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct tps23861_data *data = dev_get_drvdata(dev); + int err; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + err = tps23861_read_temp(data, val); + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + err = tps23861_read_voltage(data, channel, val); + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + err = tps23861_read_current(data, channel, val); + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return err; +} + +static const char * const tps23861_port_label[] = { + "Port1", + "Port2", + "Port3", + "Port4", + "Input", +}; + +static int tps23861_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_in: + case hwmon_curr: + *str = tps23861_port_label[channel]; + break; + case hwmon_temp: + *str = "Die"; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_channel_info *tps23861_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), + NULL +}; + +static const struct hwmon_ops tps23861_hwmon_ops = { + .is_visible = tps23861_is_visible, + .write = tps23861_write, + .read = tps23861_read, + .read_string = tps23861_read_string, +}; + +static const struct hwmon_chip_info tps23861_chip_info = { + .ops = &tps23861_hwmon_ops, + .info = tps23861_info, +}; + +static char *tps23861_port_operating_mode(struct tps23861_data *data, int port) +{ + unsigned int regval; + int mode; + + regmap_read(data->regmap, OPERATING_MODE, ®val); + + switch (port) { + case 1: + mode = FIELD_GET(OPERATING_MODE_PORT_1_MASK, regval); + break; + case 2: + mode = FIELD_GET(OPERATING_MODE_PORT_2_MASK, regval); + break; + case 3: + mode = FIELD_GET(OPERATING_MODE_PORT_3_MASK, regval); + break; + case 4: + mode = FIELD_GET(OPERATING_MODE_PORT_4_MASK, regval); + break; + default: + mode = -EINVAL; + } + + switch (mode) { + case OPERATING_MODE_OFF: + return "Off"; + case OPERATING_MODE_MANUAL: + return "Manual"; + case OPERATING_MODE_SEMI: + return "Semi-Auto"; + case OPERATING_MODE_AUTO: + return "Auto"; + default: + return "Invalid"; + } +} + +static char *tps23861_port_detect_status(struct tps23861_data *data, int port) +{ + unsigned int regval; + + regmap_read(data->regmap, + PORT_1_STATUS + (port - 1), + ®val); + + switch (FIELD_GET(PORT_STATUS_DETECT_MASK, regval)) { + case PORT_DETECT_UNKNOWN: + return "Unknown device"; + case PORT_DETECT_SHORT: + return "Short circuit"; + case PORT_DETECT_RESISTANCE_LOW: + return "Too low resistance"; + case PORT_DETECT_RESISTANCE_OK: + return "Valid resistance"; + case PORT_DETECT_RESISTANCE_HIGH: + return "Too high resistance"; + case PORT_DETECT_OPEN_CIRCUIT: + return "Open circuit"; + case PORT_DETECT_MOSFET_FAULT: + return "MOSFET fault"; + case PORT_DETECT_LEGACY: + return "Legacy device"; + case PORT_DETECT_CAPACITANCE_INVALID_BEYOND: + return "Invalid capacitance, beyond clamp voltage"; + case PORT_DETECT_CAPACITANCE_INVALID_DELTA: + return "Invalid capacitance, insufficient voltage delta"; + case PORT_DETECT_CAPACITANCE_OUT_OF_RANGE: + return "Valid capacitance, outside of legacy range"; + case PORT_DETECT_RESERVED: + case PORT_DETECT_RESERVED_2: + default: + return "Invalid"; + } +} + +static char *tps23861_port_class_status(struct tps23861_data *data, int port) +{ + unsigned int regval; + + regmap_read(data->regmap, + PORT_1_STATUS + (port - 1), + ®val); + + switch (FIELD_GET(PORT_STATUS_CLASS_MASK, regval)) { + case PORT_CLASS_UNKNOWN: + return "Unknown"; + case PORT_CLASS_RESERVED: + case PORT_CLASS_0: + return "0"; + case PORT_CLASS_1: + return "1"; + case PORT_CLASS_2: + return "2"; + case PORT_CLASS_3: + return "3"; + case PORT_CLASS_4: + return "4"; + case PORT_CLASS_OVERCURRENT: + return "Overcurrent"; + case PORT_CLASS_MISMATCH: + return "Mismatch"; + default: + return "Invalid"; + } +} + +static char *tps23861_port_poe_plus_status(struct tps23861_data *data, int port) +{ + unsigned int regval; + + regmap_read(data->regmap, POE_PLUS, ®val); + + if (BIT(port + 3) & regval) + return "Yes"; + else + return "No"; +} + +static int tps23861_port_resistance(struct tps23861_data *data, int port) +{ + u16 regval; + + regmap_bulk_read(data->regmap, + PORT_1_RESISTANCE_LSB + PORT_N_RESISTANCE_LSB_OFFSET * (port - 1), + ®val, + 2); + + switch (FIELD_GET(PORT_RESISTANCE_RSN_MASK, regval)) { + case PORT_RESISTANCE_RSN_OTHER: + return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB) / 10000; + case PORT_RESISTANCE_RSN_LOW: + return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB_LOW) / 10000; + case PORT_RESISTANCE_RSN_SHORT: + case PORT_RESISTANCE_RSN_OPEN: + default: + return 0; + } +} + +static int tps23861_port_status_show(struct seq_file *s, void *data) +{ + struct tps23861_data *priv = s->private; + int i; + + for (i = 1; i < TPS23861_NUM_PORTS + 1; i++) { + seq_printf(s, "Port: \t\t%d\n", i); + seq_printf(s, "Operating mode: %s\n", tps23861_port_operating_mode(priv, i)); + seq_printf(s, "Detected: \t%s\n", tps23861_port_detect_status(priv, i)); + seq_printf(s, "Class: \t\t%s\n", tps23861_port_class_status(priv, i)); + seq_printf(s, "PoE Plus: \t%s\n", tps23861_port_poe_plus_status(priv, i)); + seq_printf(s, "Resistance: \t%d\n", tps23861_port_resistance(priv, i)); + seq_putc(s, '\n'); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(tps23861_port_status); + +static void tps23861_init_debugfs(struct tps23861_data *data) +{ + data->debugfs_dir = debugfs_create_dir(data->client->name, NULL); + + debugfs_create_file("port_status", + 0400, + data->debugfs_dir, + data, + &tps23861_port_status_fops); +} + +static int tps23861_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tps23861_data *data; + struct device *hwmon_dev; + u32 shunt_resistor; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + i2c_set_clientdata(client, data); + + data->regmap = devm_regmap_init_i2c(client, &tps23861_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + if (!of_property_read_u32(dev->of_node, "shunt-resistor-micro-ohms", &shunt_resistor)) + data->shunt_resistor = shunt_resistor; + else + data->shunt_resistor = SHUNT_RESISTOR_DEFAULT; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &tps23861_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + tps23861_init_debugfs(data); + + return 0; +} + +static int tps23861_remove(struct i2c_client *client) +{ + struct tps23861_data *data = i2c_get_clientdata(client); + + debugfs_remove_recursive(data->debugfs_dir); + + return 0; +} + +static const struct of_device_id __maybe_unused tps23861_of_match[] = { + { .compatible = "ti,tps23861", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tps23861_of_match); + +static struct i2c_driver tps23861_driver = { + .probe_new = tps23861_probe, + .remove = tps23861_remove, + .driver = { + .name = "tps23861", + .of_match_table = of_match_ptr(tps23861_of_match), + }, +}; +module_i2c_driver(tps23861_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Marko "); +MODULE_DESCRIPTION("TI TPS23861 PoE PSE"); diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 3964ceab2817..8618aaf32350 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1110,7 +1110,7 @@ clear_caseopen(struct device *dev, struct w83627ehf_data *data, int channel, static umode_t w83627ehf_attrs_visible(struct kobject *kobj, struct attribute *a, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct w83627ehf_data *data = dev_get_drvdata(dev); struct device_attribute *devattr; struct sensor_device_attribute *sda; diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c index 913db013fe90..fc90293afcbf 100644 --- a/drivers/i2c/algos/i2c-algo-bit.c +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -622,9 +622,7 @@ static int bit_xfer_atomic(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], static u32 bit_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL_ALL | I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING; } diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8c55faaea0e7..05ebf7546e3f 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -586,13 +586,6 @@ config I2C_DIGICOLOR This driver can also be built as a module. If so, the module will be called i2c-digicolor. -config I2C_EFM32 - tristate "EFM32 I2C controller" - depends on ARCH_EFM32 || COMPILE_TEST - help - This driver supports the i2c block found in Energy Micro's EFM32 - SoCs. - config I2C_EG20T tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C" depends on PCI && (X86_32 || MIPS || COMPILE_TEST) @@ -1000,16 +993,6 @@ config I2C_SIMTEC This driver can also be built as a module. If so, the module will be called i2c-simtec. -config I2C_SIRF - tristate "CSR SiRFprimaII I2C interface" - depends on ARCH_SIRF || COMPILE_TEST - help - If you say yes to this option, support will be included for the - CSR SiRFprimaII I2C interface. - - This driver can also be built as a module. If so, the module - will be called i2c-sirf. - config I2C_SPRD tristate "Spreadtrum I2C interface" depends on I2C=y && (ARCH_SPRD || COMPILE_TEST) @@ -1050,19 +1033,6 @@ config I2C_STM32F7 This driver can also be built as module. If so, the module will be called i2c-stm32f7. -config I2C_STU300 - tristate "ST Microelectronics DDC I2C interface" - depends on MACH_U300 || COMPILE_TEST - default y if MACH_U300 - help - If you say yes to this option, support will be included for the - I2C interface from ST Microelectronics simply called "DDC I2C" - supporting both I2C and DDC, used in e.g. the U300 series - mobile platforms. - - This driver can also be built as a module. If so, the module - will be called i2c-stu300. - config I2C_SUN6I_P2WI tristate "Allwinner sun6i internal P2WI controller" depends on RESET_CONTROLLER @@ -1401,15 +1371,6 @@ config I2C_OPAL This driver can also be built as a module. If so, the module will be called as i2c-opal. -config I2C_ZX2967 - tristate "ZTE ZX2967 I2C support" - depends on ARCH_ZX - default y - help - Selecting this option will add ZX2967 I2C driver. - This driver can also be built as a module. If so, the module will be - called i2c-zx2967. - config I2C_FSI tristate "FSI I2C driver" depends on FSI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 683c49faca05..615f35e3e31f 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -58,7 +58,6 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-bayt obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o i2c-designware-pci-y := i2c-designware-pcidrv.o obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.o -obj-$(CONFIG_I2C_EFM32) += i2c-efm32.o obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o @@ -99,13 +98,11 @@ obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o -obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o obj-$(CONFIG_I2C_SPRD) += i2c-sprd.o obj-$(CONFIG_I2C_ST) += i2c-st.o obj-$(CONFIG_I2C_STM32F4) += i2c-stm32f4.o i2c-stm32f7-drv-objs := i2c-stm32f7.o i2c-stm32.o obj-$(CONFIG_I2C_STM32F7) += i2c-stm32f7-drv.o -obj-$(CONFIG_I2C_STU300) += i2c-stu300.o obj-$(CONFIG_I2C_SUN6I_P2WI) += i2c-sun6i-p2wi.o obj-$(CONFIG_I2C_SYNQUACER) += i2c-synquacer.o obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o @@ -122,7 +119,6 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o obj-$(CONFIG_I2C_XLR) += i2c-xlr.o obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o -obj-$(CONFIG_I2C_ZX2967) += i2c-zx2967.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/i2c-amd-mp2-pci.c b/drivers/i2c/busses/i2c-amd-mp2-pci.c index cd3fd5ee5f65..ce130a821ea5 100644 --- a/drivers/i2c/busses/i2c-amd-mp2-pci.c +++ b/drivers/i2c/busses/i2c-amd-mp2-pci.c @@ -30,7 +30,7 @@ static void amd_mp2_c2p_mutex_unlock(struct amd_i2c_common *i2c_common) struct amd_mp2_dev *privdata = i2c_common->mp2_dev; if (unlikely(privdata->c2p_lock_busid != i2c_common->bus_id)) { - dev_warn(ndev_dev(privdata), + pci_warn(privdata->pci_dev, "bus %d attempting to unlock C2P locked by bus %d\n", i2c_common->bus_id, privdata->c2p_lock_busid); return; @@ -59,8 +59,7 @@ int amd_mp2_bus_enable_set(struct amd_i2c_common *i2c_common, bool enable) struct amd_mp2_dev *privdata = i2c_common->mp2_dev; union i2c_cmd_base i2c_cmd_base; - dev_dbg(ndev_dev(privdata), "%s id: %d\n", __func__, - i2c_common->bus_id); + pci_dbg(privdata->pci_dev, "id: %d\n", i2c_common->bus_id); i2c_cmd_base.ul = 0; i2c_cmd_base.s.i2c_cmd = enable ? i2c_enable : i2c_disable; @@ -111,20 +110,19 @@ EXPORT_SYMBOL_GPL(amd_mp2_rw); static void amd_mp2_pci_check_rw_event(struct amd_i2c_common *i2c_common) { struct amd_mp2_dev *privdata = i2c_common->mp2_dev; + struct pci_dev *pdev = privdata->pci_dev; int len = i2c_common->eventval.r.length; u32 slave_addr = i2c_common->eventval.r.slave_addr; bool err = false; if (unlikely(len != i2c_common->msg->len)) { - dev_err(ndev_dev(privdata), - "length %d in event doesn't match buffer length %d!\n", + pci_err(pdev, "length %d in event doesn't match buffer length %d!\n", len, i2c_common->msg->len); err = true; } if (unlikely(slave_addr != i2c_common->msg->addr)) { - dev_err(ndev_dev(privdata), - "unexpected slave address %x (expected: %x)!\n", + pci_err(pdev, "unexpected slave address %x (expected: %x)!\n", slave_addr, i2c_common->msg->addr); err = true; } @@ -136,13 +134,14 @@ static void amd_mp2_pci_check_rw_event(struct amd_i2c_common *i2c_common) static void __amd_mp2_process_event(struct amd_i2c_common *i2c_common) { struct amd_mp2_dev *privdata = i2c_common->mp2_dev; + struct pci_dev *pdev = privdata->pci_dev; enum status_type sts = i2c_common->eventval.r.status; enum response_type res = i2c_common->eventval.r.response; int len = i2c_common->eventval.r.length; if (res != command_success) { if (res != command_failed) - dev_err(ndev_dev(privdata), "invalid response to i2c command!\n"); + pci_err(pdev, "invalid response to i2c command!\n"); return; } @@ -155,32 +154,26 @@ static void __amd_mp2_process_event(struct amd_i2c_common *i2c_common) privdata->mmio + AMD_C2P_MSG2, len); } else if (sts != i2c_readfail_event) { - dev_err(ndev_dev(privdata), - "invalid i2c status after read (%d)!\n", sts); + pci_err(pdev, "invalid i2c status after read (%d)!\n", sts); } break; case i2c_write: if (sts == i2c_writecomplete_event) amd_mp2_pci_check_rw_event(i2c_common); else if (sts != i2c_writefail_event) - dev_err(ndev_dev(privdata), - "invalid i2c status after write (%d)!\n", sts); + pci_err(pdev, "invalid i2c status after write (%d)!\n", sts); break; case i2c_enable: if (sts == i2c_busenable_complete) i2c_common->cmd_success = true; else if (sts != i2c_busenable_failed) - dev_err(ndev_dev(privdata), - "invalid i2c status after bus enable (%d)!\n", - sts); + pci_err(pdev, "invalid i2c status after bus enable (%d)!\n", sts); break; case i2c_disable: if (sts == i2c_busdisable_complete) i2c_common->cmd_success = true; else if (sts != i2c_busdisable_failed) - dev_err(ndev_dev(privdata), - "invalid i2c status after bus disable (%d)!\n", - sts); + pci_err(pdev, "invalid i2c status after bus disable (%d)!\n", sts); break; default: break; @@ -190,10 +183,10 @@ static void __amd_mp2_process_event(struct amd_i2c_common *i2c_common) void amd_mp2_process_event(struct amd_i2c_common *i2c_common) { struct amd_mp2_dev *privdata = i2c_common->mp2_dev; + struct pci_dev *pdev = privdata->pci_dev; if (unlikely(i2c_common->reqcmd == i2c_none)) { - dev_warn(ndev_dev(privdata), - "received msg but no cmd was sent (bus = %d)!\n", + pci_warn(pdev, "received msg but no cmd was sent (bus = %d)!\n", i2c_common->bus_id); return; } @@ -208,6 +201,7 @@ EXPORT_SYMBOL_GPL(amd_mp2_process_event); static irqreturn_t amd_mp2_irq_isr(int irq, void *dev) { struct amd_mp2_dev *privdata = dev; + struct pci_dev *pdev = privdata->pci_dev; struct amd_i2c_common *i2c_common; u32 val; unsigned int bus_id; @@ -236,8 +230,7 @@ static irqreturn_t amd_mp2_irq_isr(int irq, void *dev) val = readl(privdata->mmio + AMD_P2C_MSG_INTEN); if (val != 0) { writel(0, privdata->mmio + AMD_P2C_MSG_INTEN); - dev_warn(ndev_dev(privdata), - "received irq without message\n"); + pci_warn(pdev, "received irq without message\n"); ret = IRQ_HANDLED; } } @@ -255,13 +248,13 @@ EXPORT_SYMBOL_GPL(amd_mp2_rw_timeout); int amd_mp2_register_cb(struct amd_i2c_common *i2c_common) { struct amd_mp2_dev *privdata = i2c_common->mp2_dev; + struct pci_dev *pdev = privdata->pci_dev; if (i2c_common->bus_id > 1) return -EINVAL; if (privdata->busses[i2c_common->bus_id]) { - dev_err(ndev_dev(privdata), - "Bus %d already taken!\n", i2c_common->bus_id); + pci_err(pdev, "Bus %d already taken!\n", i2c_common->bus_id); return -EINVAL; } @@ -301,13 +294,13 @@ static int amd_mp2_pci_init(struct amd_mp2_dev *privdata, rc = pcim_enable_device(pci_dev); if (rc) { - dev_err(ndev_dev(privdata), "Failed to enable MP2 PCI device\n"); + pci_err(pci_dev, "Failed to enable MP2 PCI device\n"); goto err_pci_enable; } rc = pcim_iomap_regions(pci_dev, 1 << 2, pci_name(pci_dev)); if (rc) { - dev_err(ndev_dev(privdata), "I/O memory remapping failed\n"); + pci_err(pci_dev, "I/O memory remapping failed\n"); goto err_pci_enable; } privdata->mmio = pcim_iomap_table(pci_dev)[2]; @@ -327,7 +320,7 @@ static int amd_mp2_pci_init(struct amd_mp2_dev *privdata, rc = devm_request_irq(&pci_dev->dev, pci_dev->irq, amd_mp2_irq_isr, IRQF_SHARED, dev_name(&pci_dev->dev), privdata); if (rc) - dev_err(&pci_dev->dev, "Failure requesting irq %i: %d\n", + pci_err(pci_dev, "Failure requesting irq %i: %d\n", pci_dev->irq, rc); return rc; @@ -363,7 +356,7 @@ static int amd_mp2_pci_probe(struct pci_dev *pci_dev, privdata->probed = true; - dev_info(&pci_dev->dev, "MP2 device registered.\n"); + pci_info(pci_dev, "MP2 device registered.\n"); return 0; } @@ -397,8 +390,7 @@ static int amd_mp2_pci_suspend(struct device *dev) ret = pci_save_state(pci_dev); if (ret) { - dev_err(ndev_dev(privdata), - "pci_save_state failed = %d\n", ret); + pci_err(pci_dev, "pci_save_state failed = %d\n", ret); return ret; } @@ -417,8 +409,7 @@ static int amd_mp2_pci_resume(struct device *dev) pci_restore_state(pci_dev); ret = pci_enable_device(pci_dev); if (ret < 0) { - dev_err(ndev_dev(privdata), - "pci_enable_device failed = %d\n", ret); + pci_err(pci_dev, "pci_enable_device failed = %d\n", ret); return ret; } diff --git a/drivers/i2c/busses/i2c-amd-mp2-plat.c b/drivers/i2c/busses/i2c-amd-mp2-plat.c index 506433bc0ff2..de058671f9b8 100644 --- a/drivers/i2c/busses/i2c-amd-mp2-plat.c +++ b/drivers/i2c/busses/i2c-amd-mp2-plat.c @@ -88,8 +88,7 @@ static void i2c_amd_cmd_completion(struct amd_i2c_common *i2c_common) union i2c_event *event = &i2c_common->eventval; if (event->r.status == i2c_readcomplete_event) - dev_dbg(&i2c_dev->pdev->dev, "%s readdata:%*ph\n", - __func__, event->r.length, + dev_dbg(&i2c_dev->pdev->dev, "readdata:%*ph\n", event->r.length, i2c_common->msg->buf); complete(&i2c_dev->cmd_complete); diff --git a/drivers/i2c/busses/i2c-amd-mp2.h b/drivers/i2c/busses/i2c-amd-mp2.h index 058362edebaa..ddecd0c88656 100644 --- a/drivers/i2c/busses/i2c-amd-mp2.h +++ b/drivers/i2c/busses/i2c-amd-mp2.h @@ -185,12 +185,6 @@ struct amd_mp2_dev { unsigned int probed; }; -#define ndev_pdev(ndev) ((ndev)->pci_dev) -#define ndev_name(ndev) pci_name(ndev_pdev(ndev)) -#define ndev_dev(ndev) (&ndev_pdev(ndev)->dev) -#define work_amd_i2c_common(__work) \ - container_of(__work, struct amd_i2c_common, work.work) - /* PCIe communication driver */ int amd_mp2_rw(struct amd_i2c_common *i2c_common, enum i2c_cmd reqcmd); diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c index d8295b1c379d..cceaf69279a9 100644 --- a/drivers/i2c/busses/i2c-bcm-iproc.c +++ b/drivers/i2c/busses/i2c-bcm-iproc.c @@ -93,6 +93,7 @@ #define S_CMD_STATUS_MASK 0x07 #define S_CMD_STATUS_SUCCESS 0x0 #define S_CMD_STATUS_TIMEOUT 0x5 +#define S_CMD_STATUS_MASTER_ABORT 0x7 #define IE_OFFSET 0x38 #define IE_M_RX_FIFO_FULL_SHIFT 31 @@ -159,6 +160,11 @@ #define IE_S_ALL_INTERRUPT_SHIFT 21 #define IE_S_ALL_INTERRUPT_MASK 0x3f +/* + * It takes ~18us to reading 10bytes of data, hence to keep tasklet + * running for less time, max slave read per tasklet is set to 10 bytes. + */ +#define MAX_SLAVE_RX_PER_INT 10 enum i2c_slave_read_status { I2C_SLAVE_RX_FIFO_EMPTY = 0, @@ -205,8 +211,18 @@ struct bcm_iproc_i2c_dev { /* bytes that have been read */ unsigned int rx_bytes; unsigned int thld_bytes; + + bool slave_rx_only; + bool rx_start_rcvd; + bool slave_read_complete; + u32 tx_underrun; + u32 slave_int_mask; + struct tasklet_struct slave_rx_tasklet; }; +/* tasklet to process slave rx data */ +static void slave_rx_tasklet_fn(unsigned long); + /* * Can be expanded in the future if more interrupt status bits are utilized */ @@ -215,7 +231,8 @@ struct bcm_iproc_i2c_dev { #define ISR_MASK_SLAVE (BIT(IS_S_START_BUSY_SHIFT)\ | BIT(IS_S_RX_EVENT_SHIFT) | BIT(IS_S_RD_EVENT_SHIFT)\ - | BIT(IS_S_TX_UNDERRUN_SHIFT)) + | BIT(IS_S_TX_UNDERRUN_SHIFT) | BIT(IS_S_RX_FIFO_FULL_SHIFT)\ + | BIT(IS_S_RX_THLD_SHIFT)) static int bcm_iproc_i2c_reg_slave(struct i2c_client *slave); static int bcm_iproc_i2c_unreg_slave(struct i2c_client *slave); @@ -259,6 +276,7 @@ static void bcm_iproc_i2c_slave_init( { u32 val; + iproc_i2c->tx_underrun = 0; if (need_reset) { /* put controller in reset */ val = iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET); @@ -295,8 +313,13 @@ static void bcm_iproc_i2c_slave_init( /* Enable interrupt register to indicate a valid byte in receive fifo */ val = BIT(IE_S_RX_EVENT_SHIFT); + /* Enable interrupt register to indicate Slave Rx FIFO Full */ + val |= BIT(IE_S_RX_FIFO_FULL_SHIFT); + /* Enable interrupt register to indicate a Master read transaction */ + val |= BIT(IE_S_RD_EVENT_SHIFT); /* Enable interrupt register for the Slave BUSY command */ val |= BIT(IE_S_START_BUSY_SHIFT); + iproc_i2c->slave_int_mask = val; iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val); } @@ -311,9 +334,10 @@ static void bcm_iproc_i2c_check_slave_status( return; val = (val >> S_CMD_STATUS_SHIFT) & S_CMD_STATUS_MASK; - if (val == S_CMD_STATUS_TIMEOUT) { - dev_err(iproc_i2c->device, "slave random stretch time timeout\n"); - + if (val == S_CMD_STATUS_TIMEOUT || val == S_CMD_STATUS_MASTER_ABORT) { + dev_err(iproc_i2c->device, (val == S_CMD_STATUS_TIMEOUT) ? + "slave random stretch time timeout\n" : + "Master aborted read transaction\n"); /* re-initialize i2c for recovery */ bcm_iproc_i2c_enable_disable(iproc_i2c, false); bcm_iproc_i2c_slave_init(iproc_i2c, true); @@ -321,76 +345,187 @@ static void bcm_iproc_i2c_check_slave_status( } } +static void bcm_iproc_i2c_slave_read(struct bcm_iproc_i2c_dev *iproc_i2c) +{ + u8 rx_data, rx_status; + u32 rx_bytes = 0; + u32 val; + + while (rx_bytes < MAX_SLAVE_RX_PER_INT) { + val = iproc_i2c_rd_reg(iproc_i2c, S_RX_OFFSET); + rx_status = (val >> S_RX_STATUS_SHIFT) & S_RX_STATUS_MASK; + rx_data = ((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK); + + if (rx_status == I2C_SLAVE_RX_START) { + /* Start of SMBUS Master write */ + i2c_slave_event(iproc_i2c->slave, + I2C_SLAVE_WRITE_REQUESTED, &rx_data); + iproc_i2c->rx_start_rcvd = true; + iproc_i2c->slave_read_complete = false; + } else if (rx_status == I2C_SLAVE_RX_DATA && + iproc_i2c->rx_start_rcvd) { + /* Middle of SMBUS Master write */ + i2c_slave_event(iproc_i2c->slave, + I2C_SLAVE_WRITE_RECEIVED, &rx_data); + } else if (rx_status == I2C_SLAVE_RX_END && + iproc_i2c->rx_start_rcvd) { + /* End of SMBUS Master write */ + if (iproc_i2c->slave_rx_only) + i2c_slave_event(iproc_i2c->slave, + I2C_SLAVE_WRITE_RECEIVED, + &rx_data); + + i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP, + &rx_data); + } else if (rx_status == I2C_SLAVE_RX_FIFO_EMPTY) { + iproc_i2c->rx_start_rcvd = false; + iproc_i2c->slave_read_complete = true; + break; + } + + rx_bytes++; + } +} + +static void slave_rx_tasklet_fn(unsigned long data) +{ + struct bcm_iproc_i2c_dev *iproc_i2c = (struct bcm_iproc_i2c_dev *)data; + u32 int_clr; + + bcm_iproc_i2c_slave_read(iproc_i2c); + + /* clear pending IS_S_RX_EVENT_SHIFT interrupt */ + int_clr = BIT(IS_S_RX_EVENT_SHIFT); + + if (!iproc_i2c->slave_rx_only && iproc_i2c->slave_read_complete) { + /* + * In case of single byte master-read request, + * IS_S_TX_UNDERRUN_SHIFT event is generated before + * IS_S_START_BUSY_SHIFT event. Hence start slave data send + * from first IS_S_TX_UNDERRUN_SHIFT event. + * + * This means don't send any data from slave when + * IS_S_RD_EVENT_SHIFT event is generated else it will increment + * eeprom or other backend slave driver read pointer twice. + */ + iproc_i2c->tx_underrun = 0; + iproc_i2c->slave_int_mask |= BIT(IE_S_TX_UNDERRUN_SHIFT); + + /* clear IS_S_RD_EVENT_SHIFT interrupt */ + int_clr |= BIT(IS_S_RD_EVENT_SHIFT); + } + + /* clear slave interrupt */ + iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, int_clr); + /* enable slave interrupts */ + iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, iproc_i2c->slave_int_mask); +} + static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c, u32 status) { u32 val; - u8 value, rx_status; + u8 value; - /* Slave RX byte receive */ - if (status & BIT(IS_S_RX_EVENT_SHIFT)) { - val = iproc_i2c_rd_reg(iproc_i2c, S_RX_OFFSET); - rx_status = (val >> S_RX_STATUS_SHIFT) & S_RX_STATUS_MASK; - if (rx_status == I2C_SLAVE_RX_START) { - /* Start of SMBUS for Master write */ - i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_WRITE_REQUESTED, &value); + /* + * Slave events in case of master-write, master-write-read and, + * master-read + * + * Master-write : only IS_S_RX_EVENT_SHIFT event + * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT + * events + * Master-read : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT + * events or only IS_S_RD_EVENT_SHIFT + * + * iproc has a slave rx fifo size of 64 bytes. Rx fifo full interrupt + * (IS_S_RX_FIFO_FULL_SHIFT) will be generated when RX fifo becomes + * full. This can happen if Master issues write requests of more than + * 64 bytes. + */ + if (status & BIT(IS_S_RX_EVENT_SHIFT) || + status & BIT(IS_S_RD_EVENT_SHIFT) || + status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) { + /* disable slave interrupts */ + val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET); + val &= ~iproc_i2c->slave_int_mask; + iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val); - val = iproc_i2c_rd_reg(iproc_i2c, S_RX_OFFSET); - value = (u8)((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK); - i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_WRITE_RECEIVED, &value); - } else if (status & BIT(IS_S_RD_EVENT_SHIFT)) { + if (status & BIT(IS_S_RD_EVENT_SHIFT)) + /* Master-write-read request */ + iproc_i2c->slave_rx_only = false; + else + /* Master-write request only */ + iproc_i2c->slave_rx_only = true; + + /* schedule tasklet to read data later */ + tasklet_schedule(&iproc_i2c->slave_rx_tasklet); + + /* + * clear only IS_S_RX_EVENT_SHIFT and + * IS_S_RX_FIFO_FULL_SHIFT interrupt. + */ + val = BIT(IS_S_RX_EVENT_SHIFT); + if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) + val |= BIT(IS_S_RX_FIFO_FULL_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, val); + } + + if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) { + iproc_i2c->tx_underrun++; + if (iproc_i2c->tx_underrun == 1) /* Start of SMBUS for Master Read */ i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_READ_REQUESTED, &value); - iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value); - - val = BIT(S_CMD_START_BUSY_SHIFT); - iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val); - - /* - * Enable interrupt for TX FIFO becomes empty and - * less than PKT_LENGTH bytes were output on the SMBUS - */ - val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET); - val |= BIT(IE_S_TX_UNDERRUN_SHIFT); - iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val); - } else { - /* Master write other than start */ - value = (u8)((val >> S_RX_DATA_SHIFT) & S_RX_DATA_MASK); + I2C_SLAVE_READ_REQUESTED, + &value); + else + /* Master read other than start */ i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_WRITE_RECEIVED, &value); - if (rx_status == I2C_SLAVE_RX_END) - i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_STOP, &value); - } - } else if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) { - /* Master read other than start */ - i2c_slave_event(iproc_i2c->slave, - I2C_SLAVE_READ_PROCESSED, &value); + I2C_SLAVE_READ_PROCESSED, + &value); iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, value); + /* start transfer */ val = BIT(S_CMD_START_BUSY_SHIFT); iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val); + + /* clear interrupt */ + iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, + BIT(IS_S_TX_UNDERRUN_SHIFT)); } - /* Stop */ + /* Stop received from master in case of master read transaction */ if (status & BIT(IS_S_START_BUSY_SHIFT)) { - i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP, &value); /* - * Enable interrupt for TX FIFO becomes empty and + * Disable interrupt for TX FIFO becomes empty and * less than PKT_LENGTH bytes were output on the SMBUS */ - val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET); - val &= ~BIT(IE_S_TX_UNDERRUN_SHIFT); - iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val); + iproc_i2c->slave_int_mask &= ~BIT(IE_S_TX_UNDERRUN_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, + iproc_i2c->slave_int_mask); + + /* End of SMBUS for Master Read */ + val = BIT(S_TX_WR_STATUS_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, S_TX_OFFSET, val); + + val = BIT(S_CMD_START_BUSY_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, S_CMD_OFFSET, val); + + /* flush TX FIFOs */ + val = iproc_i2c_rd_reg(iproc_i2c, S_FIFO_CTRL_OFFSET); + val |= (BIT(S_FIFO_TX_FLUSH_SHIFT)); + iproc_i2c_wr_reg(iproc_i2c, S_FIFO_CTRL_OFFSET, val); + + i2c_slave_event(iproc_i2c->slave, I2C_SLAVE_STOP, &value); + + /* clear interrupt */ + iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, + BIT(IS_S_START_BUSY_SHIFT)); } - /* clear interrupt status */ - iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, status); + /* check slave transmit status only if slave is transmitting */ + if (!iproc_i2c->slave_rx_only) + bcm_iproc_i2c_check_slave_status(iproc_i2c); - bcm_iproc_i2c_check_slave_status(iproc_i2c); return true; } @@ -505,12 +640,17 @@ static void bcm_iproc_i2c_process_m_event(struct bcm_iproc_i2c_dev *iproc_i2c, static irqreturn_t bcm_iproc_i2c_isr(int irq, void *data) { struct bcm_iproc_i2c_dev *iproc_i2c = data; - u32 status = iproc_i2c_rd_reg(iproc_i2c, IS_OFFSET); + u32 slave_status; + u32 status; bool ret; - u32 sl_status = status & ISR_MASK_SLAVE; - if (sl_status) { - ret = bcm_iproc_i2c_slave_isr(iproc_i2c, sl_status); + status = iproc_i2c_rd_reg(iproc_i2c, IS_OFFSET); + /* process only slave interrupt which are enabled */ + slave_status = status & iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET) & + ISR_MASK_SLAVE; + + if (slave_status) { + ret = bcm_iproc_i2c_slave_isr(iproc_i2c, slave_status); if (ret) return IRQ_HANDLED; else @@ -1066,6 +1206,10 @@ static int bcm_iproc_i2c_reg_slave(struct i2c_client *slave) return -EAFNOSUPPORT; iproc_i2c->slave = slave; + + tasklet_init(&iproc_i2c->slave_rx_tasklet, slave_rx_tasklet_fn, + (unsigned long)iproc_i2c); + bcm_iproc_i2c_slave_init(iproc_i2c, false); return 0; } @@ -1086,6 +1230,8 @@ static int bcm_iproc_i2c_unreg_slave(struct i2c_client *slave) IE_S_ALL_INTERRUPT_SHIFT); iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, tmp); + tasklet_kill(&iproc_i2c->slave_rx_tasklet); + /* Erase the slave address programmed */ tmp = iproc_i2c_rd_reg(iproc_i2c, S_CFG_SMBUS_ADDR_OFFSET); tmp &= ~BIT(S_CFG_EN_NIC_SMB_ADDR3_SHIFT); diff --git a/drivers/i2c/busses/i2c-digicolor.c b/drivers/i2c/busses/i2c-digicolor.c index f67639dc74b7..60c838c7c454 100644 --- a/drivers/i2c/busses/i2c-digicolor.c +++ b/drivers/i2c/busses/i2c-digicolor.c @@ -160,12 +160,11 @@ static irqreturn_t dc_i2c_irq(int irq, void *dev_id) { struct dc_i2c *i2c = dev_id; int cmd_status = dc_i2c_cmd_status(i2c); - unsigned long flags; u8 addr_cmd; writeb_relaxed(1, i2c->regs + II_INTFLAG_CLEAR); - spin_lock_irqsave(&i2c->lock, flags); + spin_lock(&i2c->lock); if (cmd_status == II_CMD_STATUS_ACK_BAD || cmd_status == II_CMD_STATUS_ABORT) { @@ -207,7 +206,7 @@ static irqreturn_t dc_i2c_irq(int irq, void *dev_id) } out: - spin_unlock_irqrestore(&i2c->lock, flags); + spin_unlock(&i2c->lock); return IRQ_HANDLED; } diff --git a/drivers/i2c/busses/i2c-efm32.c b/drivers/i2c/busses/i2c-efm32.c deleted file mode 100644 index f6e13ceeb2b3..000000000000 --- a/drivers/i2c/busses/i2c-efm32.c +++ /dev/null @@ -1,469 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2014 Uwe Kleine-Koenig for Pengutronix - */ -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "efm32-i2c" - -#define MASK_VAL(mask, val) ((val << __ffs(mask)) & mask) - -#define REG_CTRL 0x00 -#define REG_CTRL_EN 0x00001 -#define REG_CTRL_SLAVE 0x00002 -#define REG_CTRL_AUTOACK 0x00004 -#define REG_CTRL_AUTOSE 0x00008 -#define REG_CTRL_AUTOSN 0x00010 -#define REG_CTRL_ARBDIS 0x00020 -#define REG_CTRL_GCAMEN 0x00040 -#define REG_CTRL_CLHR__MASK 0x00300 -#define REG_CTRL_BITO__MASK 0x03000 -#define REG_CTRL_BITO_OFF 0x00000 -#define REG_CTRL_BITO_40PCC 0x01000 -#define REG_CTRL_BITO_80PCC 0x02000 -#define REG_CTRL_BITO_160PCC 0x03000 -#define REG_CTRL_GIBITO 0x08000 -#define REG_CTRL_CLTO__MASK 0x70000 -#define REG_CTRL_CLTO_OFF 0x00000 - -#define REG_CMD 0x04 -#define REG_CMD_START 0x00001 -#define REG_CMD_STOP 0x00002 -#define REG_CMD_ACK 0x00004 -#define REG_CMD_NACK 0x00008 -#define REG_CMD_CONT 0x00010 -#define REG_CMD_ABORT 0x00020 -#define REG_CMD_CLEARTX 0x00040 -#define REG_CMD_CLEARPC 0x00080 - -#define REG_STATE 0x08 -#define REG_STATE_BUSY 0x00001 -#define REG_STATE_MASTER 0x00002 -#define REG_STATE_TRANSMITTER 0x00004 -#define REG_STATE_NACKED 0x00008 -#define REG_STATE_BUSHOLD 0x00010 -#define REG_STATE_STATE__MASK 0x000e0 -#define REG_STATE_STATE_IDLE 0x00000 -#define REG_STATE_STATE_WAIT 0x00020 -#define REG_STATE_STATE_START 0x00040 -#define REG_STATE_STATE_ADDR 0x00060 -#define REG_STATE_STATE_ADDRACK 0x00080 -#define REG_STATE_STATE_DATA 0x000a0 -#define REG_STATE_STATE_DATAACK 0x000c0 - -#define REG_STATUS 0x0c -#define REG_STATUS_PSTART 0x00001 -#define REG_STATUS_PSTOP 0x00002 -#define REG_STATUS_PACK 0x00004 -#define REG_STATUS_PNACK 0x00008 -#define REG_STATUS_PCONT 0x00010 -#define REG_STATUS_PABORT 0x00020 -#define REG_STATUS_TXC 0x00040 -#define REG_STATUS_TXBL 0x00080 -#define REG_STATUS_RXDATAV 0x00100 - -#define REG_CLKDIV 0x10 -#define REG_CLKDIV_DIV__MASK 0x001ff -#define REG_CLKDIV_DIV(div) MASK_VAL(REG_CLKDIV_DIV__MASK, (div)) - -#define REG_SADDR 0x14 -#define REG_SADDRMASK 0x18 -#define REG_RXDATA 0x1c -#define REG_RXDATAP 0x20 -#define REG_TXDATA 0x24 -#define REG_IF 0x28 -#define REG_IF_START 0x00001 -#define REG_IF_RSTART 0x00002 -#define REG_IF_ADDR 0x00004 -#define REG_IF_TXC 0x00008 -#define REG_IF_TXBL 0x00010 -#define REG_IF_RXDATAV 0x00020 -#define REG_IF_ACK 0x00040 -#define REG_IF_NACK 0x00080 -#define REG_IF_MSTOP 0x00100 -#define REG_IF_ARBLOST 0x00200 -#define REG_IF_BUSERR 0x00400 -#define REG_IF_BUSHOLD 0x00800 -#define REG_IF_TXOF 0x01000 -#define REG_IF_RXUF 0x02000 -#define REG_IF_BITO 0x04000 -#define REG_IF_CLTO 0x08000 -#define REG_IF_SSTOP 0x10000 - -#define REG_IFS 0x2c -#define REG_IFC 0x30 -#define REG_IFC__MASK 0x1ffcf - -#define REG_IEN 0x34 - -#define REG_ROUTE 0x38 -#define REG_ROUTE_SDAPEN 0x00001 -#define REG_ROUTE_SCLPEN 0x00002 -#define REG_ROUTE_LOCATION__MASK 0x00700 -#define REG_ROUTE_LOCATION(n) MASK_VAL(REG_ROUTE_LOCATION__MASK, (n)) - -struct efm32_i2c_ddata { - struct i2c_adapter adapter; - - struct clk *clk; - void __iomem *base; - unsigned int irq; - u8 location; - unsigned long frequency; - - /* transfer data */ - struct completion done; - struct i2c_msg *msgs; - size_t num_msgs; - size_t current_word, current_msg; - int retval; -}; - -static u32 efm32_i2c_read32(struct efm32_i2c_ddata *ddata, unsigned offset) -{ - return readl(ddata->base + offset); -} - -static void efm32_i2c_write32(struct efm32_i2c_ddata *ddata, - unsigned offset, u32 value) -{ - writel(value, ddata->base + offset); -} - -static void efm32_i2c_send_next_msg(struct efm32_i2c_ddata *ddata) -{ - struct i2c_msg *cur_msg = &ddata->msgs[ddata->current_msg]; - - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_START); - efm32_i2c_write32(ddata, REG_TXDATA, i2c_8bit_addr_from_msg(cur_msg)); -} - -static void efm32_i2c_send_next_byte(struct efm32_i2c_ddata *ddata) -{ - struct i2c_msg *cur_msg = &ddata->msgs[ddata->current_msg]; - - if (ddata->current_word >= cur_msg->len) { - /* cur_msg completely transferred */ - ddata->current_word = 0; - ddata->current_msg += 1; - - if (ddata->current_msg >= ddata->num_msgs) { - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_STOP); - complete(&ddata->done); - } else { - efm32_i2c_send_next_msg(ddata); - } - } else { - efm32_i2c_write32(ddata, REG_TXDATA, - cur_msg->buf[ddata->current_word++]); - } -} - -static void efm32_i2c_recv_next_byte(struct efm32_i2c_ddata *ddata) -{ - struct i2c_msg *cur_msg = &ddata->msgs[ddata->current_msg]; - - cur_msg->buf[ddata->current_word] = efm32_i2c_read32(ddata, REG_RXDATA); - ddata->current_word += 1; - if (ddata->current_word >= cur_msg->len) { - /* cur_msg completely transferred */ - ddata->current_word = 0; - ddata->current_msg += 1; - - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_NACK); - - if (ddata->current_msg >= ddata->num_msgs) { - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_STOP); - complete(&ddata->done); - } else { - efm32_i2c_send_next_msg(ddata); - } - } else { - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_ACK); - } -} - -static irqreturn_t efm32_i2c_irq(int irq, void *dev_id) -{ - struct efm32_i2c_ddata *ddata = dev_id; - struct i2c_msg *cur_msg = &ddata->msgs[ddata->current_msg]; - u32 irqflag = efm32_i2c_read32(ddata, REG_IF); - u32 state = efm32_i2c_read32(ddata, REG_STATE); - - efm32_i2c_write32(ddata, REG_IFC, irqflag & REG_IFC__MASK); - - switch (state & REG_STATE_STATE__MASK) { - case REG_STATE_STATE_IDLE: - /* arbitration lost? */ - ddata->retval = -EAGAIN; - complete(&ddata->done); - break; - case REG_STATE_STATE_WAIT: - /* - * huh, this shouldn't happen. - * Reset hardware state and get out - */ - ddata->retval = -EIO; - efm32_i2c_write32(ddata, REG_CMD, - REG_CMD_STOP | REG_CMD_ABORT | - REG_CMD_CLEARTX | REG_CMD_CLEARPC); - complete(&ddata->done); - break; - case REG_STATE_STATE_START: - /* "caller" is expected to send an address */ - break; - case REG_STATE_STATE_ADDR: - /* wait for Ack or NAck of slave */ - break; - case REG_STATE_STATE_ADDRACK: - if (state & REG_STATE_NACKED) { - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_STOP); - ddata->retval = -ENXIO; - complete(&ddata->done); - } else if (cur_msg->flags & I2C_M_RD) { - /* wait for slave to send first data byte */ - } else { - efm32_i2c_send_next_byte(ddata); - } - break; - case REG_STATE_STATE_DATA: - if (cur_msg->flags & I2C_M_RD) { - efm32_i2c_recv_next_byte(ddata); - } else { - /* wait for Ack or Nack of slave */ - } - break; - case REG_STATE_STATE_DATAACK: - if (state & REG_STATE_NACKED) { - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_STOP); - complete(&ddata->done); - } else { - efm32_i2c_send_next_byte(ddata); - } - } - - return IRQ_HANDLED; -} - -static int efm32_i2c_master_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, int num) -{ - struct efm32_i2c_ddata *ddata = i2c_get_adapdata(adap); - int ret; - - if (ddata->msgs) - return -EBUSY; - - ddata->msgs = msgs; - ddata->num_msgs = num; - ddata->current_word = 0; - ddata->current_msg = 0; - ddata->retval = -EIO; - - reinit_completion(&ddata->done); - - dev_dbg(&ddata->adapter.dev, "state: %08x, status: %08x\n", - efm32_i2c_read32(ddata, REG_STATE), - efm32_i2c_read32(ddata, REG_STATUS)); - - efm32_i2c_send_next_msg(ddata); - - wait_for_completion(&ddata->done); - - if (ddata->current_msg >= ddata->num_msgs) - ret = ddata->num_msgs; - else - ret = ddata->retval; - - return ret; -} - -static u32 efm32_i2c_functionality(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static const struct i2c_algorithm efm32_i2c_algo = { - .master_xfer = efm32_i2c_master_xfer, - .functionality = efm32_i2c_functionality, -}; - -static u32 efm32_i2c_get_configured_location(struct efm32_i2c_ddata *ddata) -{ - u32 reg = efm32_i2c_read32(ddata, REG_ROUTE); - - return (reg & REG_ROUTE_LOCATION__MASK) >> - __ffs(REG_ROUTE_LOCATION__MASK); -} - -static int efm32_i2c_probe(struct platform_device *pdev) -{ - struct efm32_i2c_ddata *ddata; - struct resource *res; - unsigned long rate; - struct device_node *np = pdev->dev.of_node; - u32 location, frequency; - int ret; - u32 clkdiv; - - ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); - if (!ddata) - return -ENOMEM; - platform_set_drvdata(pdev, ddata); - - init_completion(&ddata->done); - strlcpy(ddata->adapter.name, pdev->name, sizeof(ddata->adapter.name)); - ddata->adapter.owner = THIS_MODULE; - ddata->adapter.algo = &efm32_i2c_algo; - ddata->adapter.dev.parent = &pdev->dev; - ddata->adapter.dev.of_node = pdev->dev.of_node; - i2c_set_adapdata(&ddata->adapter, ddata); - - ddata->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(ddata->clk)) { - ret = PTR_ERR(ddata->clk); - dev_err(&pdev->dev, "failed to get clock: %d\n", ret); - return ret; - } - - ddata->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); - if (IS_ERR(ddata->base)) - return PTR_ERR(ddata->base); - - if (resource_size(res) < 0x42) { - dev_err(&pdev->dev, "memory resource too small\n"); - return -EINVAL; - } - - ret = platform_get_irq(pdev, 0); - if (ret <= 0) { - if (!ret) - ret = -EINVAL; - return ret; - } - - ddata->irq = ret; - - ret = clk_prepare_enable(ddata->clk); - if (ret < 0) { - dev_err(&pdev->dev, "failed to enable clock (%d)\n", ret); - return ret; - } - - - ret = of_property_read_u32(np, "energymicro,location", &location); - - if (ret) - /* fall back to wrongly namespaced property */ - ret = of_property_read_u32(np, "efm32,location", &location); - - if (!ret) { - dev_dbg(&pdev->dev, "using location %u\n", location); - } else { - /* default to location configured in hardware */ - location = efm32_i2c_get_configured_location(ddata); - - dev_info(&pdev->dev, "fall back to location %u\n", location); - } - - ddata->location = location; - - ret = of_property_read_u32(np, "clock-frequency", &frequency); - if (!ret) { - dev_dbg(&pdev->dev, "using frequency %u\n", frequency); - } else { - frequency = I2C_MAX_STANDARD_MODE_FREQ; - dev_info(&pdev->dev, "defaulting to 100 kHz\n"); - } - ddata->frequency = frequency; - - rate = clk_get_rate(ddata->clk); - if (!rate) { - dev_err(&pdev->dev, "there is no input clock available\n"); - ret = -EINVAL; - goto err_disable_clk; - } - clkdiv = DIV_ROUND_UP(rate, 8 * ddata->frequency) - 1; - if (clkdiv >= 0x200) { - dev_err(&pdev->dev, - "input clock too fast (%lu) to divide down to bus freq (%lu)", - rate, ddata->frequency); - ret = -EINVAL; - goto err_disable_clk; - } - - dev_dbg(&pdev->dev, "input clock = %lu, bus freq = %lu, clkdiv = %lu\n", - rate, ddata->frequency, (unsigned long)clkdiv); - efm32_i2c_write32(ddata, REG_CLKDIV, REG_CLKDIV_DIV(clkdiv)); - - efm32_i2c_write32(ddata, REG_ROUTE, REG_ROUTE_SDAPEN | - REG_ROUTE_SCLPEN | - REG_ROUTE_LOCATION(ddata->location)); - - efm32_i2c_write32(ddata, REG_CTRL, REG_CTRL_EN | - REG_CTRL_BITO_160PCC | 0 * REG_CTRL_GIBITO); - - efm32_i2c_write32(ddata, REG_IFC, REG_IFC__MASK); - efm32_i2c_write32(ddata, REG_IEN, REG_IF_TXC | REG_IF_ACK | REG_IF_NACK - | REG_IF_ARBLOST | REG_IF_BUSERR | REG_IF_RXDATAV); - - /* to make bus idle */ - efm32_i2c_write32(ddata, REG_CMD, REG_CMD_ABORT); - - ret = request_irq(ddata->irq, efm32_i2c_irq, 0, DRIVER_NAME, ddata); - if (ret < 0) { - dev_err(&pdev->dev, "failed to request irq (%d)\n", ret); - goto err_disable_clk; - } - - ret = i2c_add_adapter(&ddata->adapter); - if (ret) { - free_irq(ddata->irq, ddata); - -err_disable_clk: - clk_disable_unprepare(ddata->clk); - } - return ret; -} - -static int efm32_i2c_remove(struct platform_device *pdev) -{ - struct efm32_i2c_ddata *ddata = platform_get_drvdata(pdev); - - i2c_del_adapter(&ddata->adapter); - free_irq(ddata->irq, ddata); - clk_disable_unprepare(ddata->clk); - - return 0; -} - -static const struct of_device_id efm32_i2c_dt_ids[] = { - { - .compatible = "energymicro,efm32-i2c", - }, { - /* sentinel */ - } -}; -MODULE_DEVICE_TABLE(of, efm32_i2c_dt_ids); - -static struct platform_driver efm32_i2c_driver = { - .probe = efm32_i2c_probe, - .remove = efm32_i2c_remove, - - .driver = { - .name = DRIVER_NAME, - .of_match_table = efm32_i2c_dt_ids, - }, -}; -module_platform_driver(efm32_i2c_driver); - -MODULE_AUTHOR("Uwe Kleine-Koenig "); -MODULE_DESCRIPTION("EFM32 i2c driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/i2c/busses/i2c-elektor.c b/drivers/i2c/busses/i2c-elektor.c index b72a3c3ef2ab..b0f50dce9d0f 100644 --- a/drivers/i2c/busses/i2c-elektor.c +++ b/drivers/i2c/busses/i2c-elektor.c @@ -49,7 +49,7 @@ static int mmapped; static wait_queue_head_t pcf_wait; static int pcf_pending; -static spinlock_t lock; +static DEFINE_SPINLOCK(lock); static struct i2c_adapter pcf_isa_ops; @@ -132,7 +132,6 @@ static irqreturn_t pcf_isa_handler(int this_irq, void *dev_id) { static int pcf_isa_init(void) { - spin_lock_init(&lock); if (!mmapped) { if (!request_region(base, 2, pcf_isa_ops.name)) { printk(KERN_ERR "%s: requested I/O region (%#x:2) is " diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index a4a6825c8758..7a048abbf92b 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -520,5 +520,5 @@ module_exit(i2c_gpio_exit); MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); MODULE_DESCRIPTION("Platform-independent bitbanging I2C driver"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:i2c-gpio"); diff --git a/drivers/i2c/busses/i2c-hix5hd2.c b/drivers/i2c/busses/i2c-hix5hd2.c index ab15b1ec2ab3..c45f226c2b85 100644 --- a/drivers/i2c/busses/i2c-hix5hd2.c +++ b/drivers/i2c/busses/i2c-hix5hd2.c @@ -413,10 +413,8 @@ static int hix5hd2_i2c_probe(struct platform_device *pdev) return PTR_ERR(priv->regs); irq = platform_get_irq(pdev, 0); - if (irq <= 0) { - dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n"); + if (irq <= 0) return irq; - } priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index 877fe3733a42..4acee6f9e5a3 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -72,6 +72,7 @@ * Jasper Lake (SOC) 0x4da3 32 hard yes yes yes * Comet Lake-V (PCH) 0xa3a3 32 hard yes yes yes * Alder Lake-S (PCH) 0x7aa3 32 hard yes yes yes + * Alder Lake-P (PCH) 0x51a3 32 hard yes yes yes * * Features supported by this driver: * Software PEC no @@ -228,6 +229,7 @@ #define PCI_DEVICE_ID_INTEL_TIGERLAKE_H_SMBUS 0x43a3 #define PCI_DEVICE_ID_INTEL_ELKHART_LAKE_SMBUS 0x4b23 #define PCI_DEVICE_ID_INTEL_JASPER_LAKE_SMBUS 0x4da3 +#define PCI_DEVICE_ID_INTEL_ALDER_LAKE_P_SMBUS 0x51a3 #define PCI_DEVICE_ID_INTEL_BROXTON_SMBUS 0x5ad4 #define PCI_DEVICE_ID_INTEL_ALDER_LAKE_S_SMBUS 0x7aa3 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_SMBUS 0x8c22 @@ -1084,6 +1086,7 @@ static const struct pci_device_id i801_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_TIGERLAKE_H_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_JASPER_LAKE_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ALDER_LAKE_S_SMBUS) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ALDER_LAKE_P_SMBUS) }, { 0, } }; @@ -1433,7 +1436,7 @@ static int i801_add_mux(struct i801_priv *priv) const struct i801_mux_config *mux_config; struct i2c_mux_gpio_platform_data gpio_data; struct gpiod_lookup_table *lookup; - int err, i; + int i; if (!priv->mux_drvdata) return 0; @@ -1473,22 +1476,17 @@ static int i801_add_mux(struct i801_priv *priv) PLATFORM_DEVID_NONE, &gpio_data, sizeof(struct i2c_mux_gpio_platform_data)); if (IS_ERR(priv->mux_pdev)) { - err = PTR_ERR(priv->mux_pdev); gpiod_remove_lookup_table(lookup); - priv->mux_pdev = NULL; dev_err(dev, "Failed to register i2c-mux-gpio device\n"); - return err; } - return 0; + return PTR_ERR_OR_ZERO(priv->mux_pdev); } static void i801_del_mux(struct i801_priv *priv) { - if (priv->mux_pdev) - platform_device_unregister(priv->mux_pdev); - if (priv->lookup) - gpiod_remove_lookup_table(priv->lookup); + platform_device_unregister(priv->mux_pdev); + gpiod_remove_lookup_table(priv->lookup); } static unsigned int i801_get_adapter_class(struct i801_priv *priv) @@ -1772,6 +1770,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id) case PCI_DEVICE_ID_INTEL_JASPER_LAKE_SMBUS: case PCI_DEVICE_ID_INTEL_EBG_SMBUS: case PCI_DEVICE_ID_INTEL_ALDER_LAKE_S_SMBUS: + case PCI_DEVICE_ID_INTEL_ALDER_LAKE_P_SMBUS: priv->features |= FEATURE_BLOCK_PROC; priv->features |= FEATURE_I2C_BLOCK_READ; priv->features |= FEATURE_IRQ; diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index a8e8af57e33f..b80fdc1f0092 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -209,6 +209,7 @@ struct imx_i2c_struct { struct imx_i2c_dma *dma; struct i2c_client *slave; + enum i2c_slave_event last_slave_event; }; static const struct imx_i2c_hwdata imx1_i2c_hwdata = { @@ -550,7 +551,7 @@ static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx, i2c_imx->cur_clk = i2c_clk_rate; - div = (i2c_clk_rate + i2c_imx->bitrate - 1) / i2c_imx->bitrate; + div = DIV_ROUND_UP(i2c_clk_rate, i2c_imx->bitrate); if (div < i2c_clk_div[0].div) i = 0; else if (div > i2c_clk_div[i2c_imx->hwdata->ndivs - 1].div) @@ -568,8 +569,8 @@ static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx, * This delay is used in I2C bus disable function * to fix chip hardware bug. */ - i2c_imx->disable_delay = (500000U * i2c_clk_div[i].div - + (i2c_clk_rate / 2) - 1) / (i2c_clk_rate / 2); + i2c_imx->disable_delay = DIV_ROUND_UP(500000U * i2c_clk_div[i].div, + i2c_clk_rate / 2); #ifdef CONFIG_I2C_DEBUG_BUS dev_dbg(&i2c_imx->adapter.dev, "I2C_CLK=%d, REQ DIV=%d\n", @@ -675,6 +676,36 @@ static void i2c_imx_enable_bus_idle(struct imx_i2c_struct *i2c_imx) } } +static void i2c_imx_slave_event(struct imx_i2c_struct *i2c_imx, + enum i2c_slave_event event, u8 *val) +{ + i2c_slave_event(i2c_imx->slave, event, val); + i2c_imx->last_slave_event = event; +} + +static void i2c_imx_slave_finish_op(struct imx_i2c_struct *i2c_imx) +{ + u8 val; + + while (i2c_imx->last_slave_event != I2C_SLAVE_STOP) { + switch (i2c_imx->last_slave_event) { + case I2C_SLAVE_READ_REQUESTED: + i2c_imx_slave_event(i2c_imx, I2C_SLAVE_READ_PROCESSED, + &val); + break; + + case I2C_SLAVE_WRITE_REQUESTED: + case I2C_SLAVE_READ_PROCESSED: + case I2C_SLAVE_WRITE_RECEIVED: + i2c_imx_slave_event(i2c_imx, I2C_SLAVE_STOP, &val); + break; + + case I2C_SLAVE_STOP: + break; + } + } +} + static irqreturn_t i2c_imx_slave_isr(struct imx_i2c_struct *i2c_imx, unsigned int status, unsigned int ctl) { @@ -687,9 +718,11 @@ static irqreturn_t i2c_imx_slave_isr(struct imx_i2c_struct *i2c_imx, } if (status & I2SR_IAAS) { /* Addressed as a slave */ + i2c_imx_slave_finish_op(i2c_imx); if (status & I2SR_SRW) { /* Master wants to read from us*/ dev_dbg(&i2c_imx->adapter.dev, "read requested"); - i2c_slave_event(i2c_imx->slave, I2C_SLAVE_READ_REQUESTED, &value); + i2c_imx_slave_event(i2c_imx, + I2C_SLAVE_READ_REQUESTED, &value); /* Slave transmit */ ctl |= I2CR_MTX; @@ -699,7 +732,8 @@ static irqreturn_t i2c_imx_slave_isr(struct imx_i2c_struct *i2c_imx, imx_i2c_write_reg(value, i2c_imx, IMX_I2C_I2DR); } else { /* Master wants to write to us */ dev_dbg(&i2c_imx->adapter.dev, "write requested"); - i2c_slave_event(i2c_imx->slave, I2C_SLAVE_WRITE_REQUESTED, &value); + i2c_imx_slave_event(i2c_imx, + I2C_SLAVE_WRITE_REQUESTED, &value); /* Slave receive */ ctl &= ~I2CR_MTX; @@ -710,17 +744,20 @@ static irqreturn_t i2c_imx_slave_isr(struct imx_i2c_struct *i2c_imx, } else if (!(ctl & I2CR_MTX)) { /* Receive mode */ if (status & I2SR_IBB) { /* No STOP signal detected */ value = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); - i2c_slave_event(i2c_imx->slave, I2C_SLAVE_WRITE_RECEIVED, &value); + i2c_imx_slave_event(i2c_imx, + I2C_SLAVE_WRITE_RECEIVED, &value); } else { /* STOP signal is detected */ dev_dbg(&i2c_imx->adapter.dev, "STOP signal detected"); - i2c_slave_event(i2c_imx->slave, I2C_SLAVE_STOP, &value); + i2c_imx_slave_event(i2c_imx, + I2C_SLAVE_STOP, &value); } } else if (!(status & I2SR_RXAK)) { /* Transmit mode received ACK */ ctl |= I2CR_MTX; imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); - i2c_slave_event(i2c_imx->slave, I2C_SLAVE_READ_PROCESSED, &value); + i2c_imx_slave_event(i2c_imx, + I2C_SLAVE_READ_PROCESSED, &value); imx_i2c_write_reg(value, i2c_imx, IMX_I2C_I2DR); } else { /* Transmit mode received NAK */ @@ -761,6 +798,7 @@ static int i2c_imx_reg_slave(struct i2c_client *client) return -EBUSY; i2c_imx->slave = client; + i2c_imx->last_slave_event = I2C_SLAVE_STOP; /* Resume */ ret = pm_runtime_get_sync(i2c_imx->adapter.dev.parent); @@ -813,10 +851,17 @@ static irqreturn_t i2c_imx_isr(int irq, void *dev_id) status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); + if (status & I2SR_IIF) { i2c_imx_clear_irq(i2c_imx, I2SR_IIF); - if (i2c_imx->slave && !(ctl & I2CR_MSTA)) - return i2c_imx_slave_isr(i2c_imx, status, ctl); + if (i2c_imx->slave) { + if (!(ctl & I2CR_MSTA)) { + return i2c_imx_slave_isr(i2c_imx, status, ctl); + } else if (i2c_imx->last_slave_event != + I2C_SLAVE_STOP) { + i2c_imx_slave_finish_op(i2c_imx); + } + } return i2c_imx_master_isr(i2c_imx, status); } diff --git a/drivers/i2c/busses/i2c-jz4780.c b/drivers/i2c/busses/i2c-jz4780.c index cb4a25ebb890..8509c5f11356 100644 --- a/drivers/i2c/busses/i2c-jz4780.c +++ b/drivers/i2c/busses/i2c-jz4780.c @@ -437,9 +437,8 @@ static irqreturn_t jz4780_i2c_irq(int irqno, void *dev_id) unsigned short intst; unsigned short intmsk; struct jz4780_i2c *i2c = dev_id; - unsigned long flags; - spin_lock_irqsave(&i2c->lock, flags); + spin_lock(&i2c->lock); intmsk = jz4780_i2c_readw(i2c, JZ4780_I2C_INTM); intst = jz4780_i2c_readw(i2c, JZ4780_I2C_INTST); @@ -551,7 +550,7 @@ static irqreturn_t jz4780_i2c_irq(int irqno, void *dev_id) } done: - spin_unlock_irqrestore(&i2c->lock, flags); + spin_unlock(&i2c->lock); return IRQ_HANDLED; } diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c index 71d7bae2cbca..4e0b7c2882ce 100644 --- a/drivers/i2c/busses/i2c-mlxcpld.c +++ b/drivers/i2c/busses/i2c-mlxcpld.c @@ -1,34 +1,8 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* - * Copyright (c) 2016 Mellanox Technologies. All rights reserved. - * Copyright (c) 2016 Michael Shych + * Mellanox i2c driver * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * Alternatively, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2 as published by the Free - * Software Foundation. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (C) 2016-2020 Mellanox Technologies */ #include @@ -37,7 +11,9 @@ #include #include #include +#include #include +#include /* General defines */ #define MLXPLAT_CPLD_LPC_I2C_BASE_ADDR 0x2000 @@ -51,7 +27,7 @@ #define MLXCPLD_I2C_MAX_ADDR_LEN 4 #define MLXCPLD_I2C_RETR_NUM 2 #define MLXCPLD_I2C_XFER_TO 500000 /* usec */ -#define MLXCPLD_I2C_POLL_TIME 2000 /* usec */ +#define MLXCPLD_I2C_POLL_TIME 400 /* usec */ /* LPC I2C registers */ #define MLXCPLD_LPCI2C_CPBLTY_REG 0x0 @@ -72,6 +48,16 @@ #define MLXCPLD_LPCI2C_ACK_IND 1 #define MLXCPLD_LPCI2C_NACK_IND 2 +#define MLXCPLD_I2C_FREQ_1000KHZ_SET 0x04 +#define MLXCPLD_I2C_FREQ_400KHZ_SET 0x0f +#define MLXCPLD_I2C_FREQ_100KHZ_SET 0x42 + +enum mlxcpld_i2c_frequency { + MLXCPLD_I2C_FREQ_1000KHZ = 1, + MLXCPLD_I2C_FREQ_400KHZ = 2, + MLXCPLD_I2C_FREQ_100KHZ = 3, +}; + struct mlxcpld_i2c_curr_xfer { u8 cmd; u8 addr_width; @@ -489,8 +475,45 @@ static struct i2c_adapter mlxcpld_i2c_adapter = { .nr = MLXCPLD_I2C_BUS_NUM, }; +static int +mlxcpld_i2c_set_frequency(struct mlxcpld_i2c_priv *priv, + struct mlxreg_core_hotplug_platform_data *pdata) +{ + struct mlxreg_core_item *item = pdata->items; + struct mlxreg_core_data *data; + u32 regval; + u8 freq; + int err; + + if (!item) + return 0; + + /* Read frequency setting. */ + data = item->data; + err = regmap_read(pdata->regmap, data->reg, ®val); + if (err) + return err; + + /* Set frequency only if it is not 100KHz, which is default. */ + switch ((data->reg & data->mask) >> data->bit) { + case MLXCPLD_I2C_FREQ_1000KHZ: + freq = MLXCPLD_I2C_FREQ_1000KHZ_SET; + break; + case MLXCPLD_I2C_FREQ_400KHZ: + freq = MLXCPLD_I2C_FREQ_400KHZ_SET; + break; + default: + return 0; + } + + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_HALF_CYC_REG, &freq, 1); + + return 0; +} + static int mlxcpld_i2c_probe(struct platform_device *pdev) { + struct mlxreg_core_hotplug_platform_data *pdata; struct mlxcpld_i2c_priv *priv; int err; u8 val; @@ -505,6 +528,14 @@ static int mlxcpld_i2c_probe(struct platform_device *pdev) priv->dev = &pdev->dev; priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR; + /* Set I2C bus frequency if platform data provides this info. */ + pdata = dev_get_platdata(&pdev->dev); + if (pdata) { + err = mlxcpld_i2c_set_frequency(priv, pdata); + if (err) + goto mlxcpld_i2_probe_failed; + } + /* Register with i2c layer */ mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO); /* Read capability register */ @@ -523,8 +554,12 @@ static int mlxcpld_i2c_probe(struct platform_device *pdev) err = i2c_add_numbered_adapter(&priv->adap); if (err) - mutex_destroy(&priv->lock); + goto mlxcpld_i2_probe_failed; + return 0; + +mlxcpld_i2_probe_failed: + mutex_destroy(&priv->lock); return err; } diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c index 5cfe70aedced..c590d36b5fd1 100644 --- a/drivers/i2c/busses/i2c-mv64xxx.c +++ b/drivers/i2c/busses/i2c-mv64xxx.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -717,6 +718,10 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap); int rc, ret = num; + rc = pm_runtime_resume_and_get(&adap->dev); + if (rc) + return rc; + BUG_ON(drv_data->msgs != NULL); drv_data->msgs = msgs; drv_data->num_msgs = num; @@ -732,6 +737,9 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) drv_data->num_msgs = 0; drv_data->msgs = NULL; + pm_runtime_mark_last_busy(&adap->dev); + pm_runtime_put_autosuspend(&adap->dev); + return ret; } @@ -805,7 +813,7 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, * need to know tclk in order to calculate bus clock * factors. */ - if (IS_ERR(drv_data->clk)) { + if (!drv_data->clk) { rc = -ENODEV; goto out; } @@ -828,7 +836,6 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, rc = PTR_ERR(drv_data->rstc); goto out; } - reset_control_deassert(drv_data->rstc); /* Its not yet defined how timeouts will be specified in device tree. * So hard code the value to 1 second. @@ -893,6 +900,32 @@ static int mv64xxx_i2c_init_recovery_info(struct mv64xxx_i2c_data *drv_data, return 0; } +static int +mv64xxx_i2c_runtime_suspend(struct device *dev) +{ + struct mv64xxx_i2c_data *drv_data = dev_get_drvdata(dev); + + reset_control_assert(drv_data->rstc); + clk_disable_unprepare(drv_data->reg_clk); + clk_disable_unprepare(drv_data->clk); + + return 0; +} + +static int +mv64xxx_i2c_runtime_resume(struct device *dev) +{ + struct mv64xxx_i2c_data *drv_data = dev_get_drvdata(dev); + + clk_prepare_enable(drv_data->clk); + clk_prepare_enable(drv_data->reg_clk); + reset_control_reset(drv_data->rstc); + + mv64xxx_i2c_hw_init(drv_data); + + return 0; +} + static int mv64xxx_i2c_probe(struct platform_device *pd) { @@ -920,18 +953,22 @@ mv64xxx_i2c_probe(struct platform_device *pd) /* Not all platforms have clocks */ drv_data->clk = devm_clk_get(&pd->dev, NULL); - if (PTR_ERR(drv_data->clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - if (!IS_ERR(drv_data->clk)) - clk_prepare_enable(drv_data->clk); + if (IS_ERR(drv_data->clk)) { + if (PTR_ERR(drv_data->clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + drv_data->clk = NULL; + } drv_data->reg_clk = devm_clk_get(&pd->dev, "reg"); - if (PTR_ERR(drv_data->reg_clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - if (!IS_ERR(drv_data->reg_clk)) - clk_prepare_enable(drv_data->reg_clk); + if (IS_ERR(drv_data->reg_clk)) { + if (PTR_ERR(drv_data->reg_clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + drv_data->reg_clk = NULL; + } drv_data->irq = platform_get_irq(pd, 0); + if (drv_data->irq < 0) + return drv_data->irq; if (pdata) { drv_data->freq_m = pdata->freq_m; @@ -942,16 +979,12 @@ mv64xxx_i2c_probe(struct platform_device *pd) } else if (pd->dev.of_node) { rc = mv64xxx_of_config(drv_data, &pd->dev); if (rc) - goto exit_clk; - } - if (drv_data->irq < 0) { - rc = drv_data->irq; - goto exit_reset; + return rc; } rc = mv64xxx_i2c_init_recovery_info(drv_data, &pd->dev); if (rc == -EPROBE_DEFER) - goto exit_reset; + return rc; drv_data->adapter.dev.parent = &pd->dev; drv_data->adapter.algo = &mv64xxx_i2c_algo; @@ -962,7 +995,14 @@ mv64xxx_i2c_probe(struct platform_device *pd) platform_set_drvdata(pd, drv_data); i2c_set_adapdata(&drv_data->adapter, drv_data); - mv64xxx_i2c_hw_init(drv_data); + pm_runtime_set_autosuspend_delay(&pd->dev, MSEC_PER_SEC); + pm_runtime_use_autosuspend(&pd->dev); + pm_runtime_enable(&pd->dev); + if (!pm_runtime_enabled(&pd->dev)) { + rc = mv64xxx_i2c_runtime_resume(&pd->dev); + if (rc) + goto exit_disable_pm; + } rc = request_irq(drv_data->irq, mv64xxx_i2c_intr, 0, MV64XXX_I2C_CTLR_NAME, drv_data); @@ -970,7 +1010,7 @@ mv64xxx_i2c_probe(struct platform_device *pd) dev_err(&drv_data->adapter.dev, "mv64xxx: Can't register intr handler irq%d: %d\n", drv_data->irq, rc); - goto exit_reset; + goto exit_disable_pm; } else if ((rc = i2c_add_numbered_adapter(&drv_data->adapter)) != 0) { dev_err(&drv_data->adapter.dev, "mv64xxx: Can't add i2c adapter, rc: %d\n", -rc); @@ -981,54 +1021,50 @@ mv64xxx_i2c_probe(struct platform_device *pd) exit_free_irq: free_irq(drv_data->irq, drv_data); -exit_reset: - reset_control_assert(drv_data->rstc); -exit_clk: - clk_disable_unprepare(drv_data->reg_clk); - clk_disable_unprepare(drv_data->clk); +exit_disable_pm: + pm_runtime_disable(&pd->dev); + if (!pm_runtime_status_suspended(&pd->dev)) + mv64xxx_i2c_runtime_suspend(&pd->dev); return rc; } static int -mv64xxx_i2c_remove(struct platform_device *dev) +mv64xxx_i2c_remove(struct platform_device *pd) { - struct mv64xxx_i2c_data *drv_data = platform_get_drvdata(dev); + struct mv64xxx_i2c_data *drv_data = platform_get_drvdata(pd); i2c_del_adapter(&drv_data->adapter); free_irq(drv_data->irq, drv_data); - reset_control_assert(drv_data->rstc); - clk_disable_unprepare(drv_data->reg_clk); - clk_disable_unprepare(drv_data->clk); + pm_runtime_disable(&pd->dev); + if (!pm_runtime_status_suspended(&pd->dev)) + mv64xxx_i2c_runtime_suspend(&pd->dev); return 0; } -#ifdef CONFIG_PM -static int mv64xxx_i2c_resume(struct device *dev) +static void +mv64xxx_i2c_shutdown(struct platform_device *pd) { - struct mv64xxx_i2c_data *drv_data = dev_get_drvdata(dev); - - mv64xxx_i2c_hw_init(drv_data); - - return 0; + pm_runtime_disable(&pd->dev); + if (!pm_runtime_status_suspended(&pd->dev)) + mv64xxx_i2c_runtime_suspend(&pd->dev); } -static const struct dev_pm_ops mv64xxx_i2c_pm = { - .resume = mv64xxx_i2c_resume, +static const struct dev_pm_ops mv64xxx_i2c_pm_ops = { + SET_RUNTIME_PM_OPS(mv64xxx_i2c_runtime_suspend, + mv64xxx_i2c_runtime_resume, NULL) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; -#define mv64xxx_i2c_pm_ops (&mv64xxx_i2c_pm) -#else -#define mv64xxx_i2c_pm_ops NULL -#endif - static struct platform_driver mv64xxx_i2c_driver = { .probe = mv64xxx_i2c_probe, .remove = mv64xxx_i2c_remove, + .shutdown = mv64xxx_i2c_shutdown, .driver = { .name = MV64XXX_I2C_CTLR_NAME, - .pm = mv64xxx_i2c_pm_ops, + .pm = &mv64xxx_i2c_pm_ops, .of_match_table = mv64xxx_i2c_of_match_table, }, }; diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 046d241183c5..c3f584795911 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -86,6 +86,9 @@ struct geni_i2c_dev { u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; int suspended; + void *dma_buf; + size_t xfer_len; + dma_addr_t dma_addr; }; struct geni_i2c_err_log { @@ -348,14 +351,65 @@ static void geni_i2c_tx_fsm_rst(struct geni_i2c_dev *gi2c) dev_err(gi2c->se.dev, "Timeout resetting TX_FSM\n"); } +static void geni_i2c_rx_msg_cleanup(struct geni_i2c_dev *gi2c, + struct i2c_msg *cur) +{ + gi2c->cur_rd = 0; + if (gi2c->dma_buf) { + if (gi2c->err) + geni_i2c_rx_fsm_rst(gi2c); + geni_se_rx_dma_unprep(&gi2c->se, gi2c->dma_addr, gi2c->xfer_len); + i2c_put_dma_safe_msg_buf(gi2c->dma_buf, cur, !gi2c->err); + } +} + +static void geni_i2c_tx_msg_cleanup(struct geni_i2c_dev *gi2c, + struct i2c_msg *cur) +{ + gi2c->cur_wr = 0; + if (gi2c->dma_buf) { + if (gi2c->err) + geni_i2c_tx_fsm_rst(gi2c); + geni_se_tx_dma_unprep(&gi2c->se, gi2c->dma_addr, gi2c->xfer_len); + i2c_put_dma_safe_msg_buf(gi2c->dma_buf, cur, !gi2c->err); + } +} + +static void geni_i2c_stop_xfer(struct geni_i2c_dev *gi2c) +{ + int ret; + u32 geni_status; + struct i2c_msg *cur; + + /* Resume device, as runtime suspend can happen anytime during transfer */ + ret = pm_runtime_get_sync(gi2c->se.dev); + if (ret < 0) { + dev_err(gi2c->se.dev, "Failed to resume device: %d\n", ret); + return; + } + + geni_status = readl_relaxed(gi2c->se.base + SE_GENI_STATUS); + if (geni_status & M_GENI_CMD_ACTIVE) { + cur = gi2c->cur; + geni_i2c_abort_xfer(gi2c); + if (cur->flags & I2C_M_RD) + geni_i2c_rx_msg_cleanup(gi2c, cur); + else + geni_i2c_tx_msg_cleanup(gi2c, cur); + } + + pm_runtime_put_sync_suspend(gi2c->se.dev); +} + static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, u32 m_param) { - dma_addr_t rx_dma; + dma_addr_t rx_dma = 0; unsigned long time_left; void *dma_buf; struct geni_se *se = &gi2c->se; size_t len = msg->len; + struct i2c_msg *cur; dma_buf = i2c_get_dma_safe_msg_buf(msg, 32); if (dma_buf) @@ -370,19 +424,18 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, geni_se_select_mode(se, GENI_SE_FIFO); i2c_put_dma_safe_msg_buf(dma_buf, msg, false); dma_buf = NULL; + } else { + gi2c->xfer_len = len; + gi2c->dma_addr = rx_dma; + gi2c->dma_buf = dma_buf; } + cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); if (!time_left) geni_i2c_abort_xfer(gi2c); - gi2c->cur_rd = 0; - if (dma_buf) { - if (gi2c->err) - geni_i2c_rx_fsm_rst(gi2c); - geni_se_rx_dma_unprep(se, rx_dma, len); - i2c_put_dma_safe_msg_buf(dma_buf, msg, !gi2c->err); - } + geni_i2c_rx_msg_cleanup(gi2c, cur); return gi2c->err; } @@ -390,11 +443,12 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, u32 m_param) { - dma_addr_t tx_dma; + dma_addr_t tx_dma = 0; unsigned long time_left; void *dma_buf; struct geni_se *se = &gi2c->se; size_t len = msg->len; + struct i2c_msg *cur; dma_buf = i2c_get_dma_safe_msg_buf(msg, 32); if (dma_buf) @@ -409,22 +463,21 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, geni_se_select_mode(se, GENI_SE_FIFO); i2c_put_dma_safe_msg_buf(dma_buf, msg, false); dma_buf = NULL; + } else { + gi2c->xfer_len = len; + gi2c->dma_addr = tx_dma; + gi2c->dma_buf = dma_buf; } if (!dma_buf) /* Get FIFO IRQ */ writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG); + cur = gi2c->cur; time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT); if (!time_left) geni_i2c_abort_xfer(gi2c); - gi2c->cur_wr = 0; - if (dma_buf) { - if (gi2c->err) - geni_i2c_tx_fsm_rst(gi2c); - geni_se_tx_dma_unprep(se, tx_dma, len); - i2c_put_dma_safe_msg_buf(dma_buf, msg, !gi2c->err); - } + geni_i2c_tx_msg_cleanup(gi2c, cur); return gi2c->err; } @@ -623,6 +676,13 @@ static int geni_i2c_remove(struct platform_device *pdev) return 0; } +static void geni_i2c_shutdown(struct platform_device *pdev) +{ + struct geni_i2c_dev *gi2c = platform_get_drvdata(pdev); + + geni_i2c_stop_xfer(gi2c); +} + static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev) { int ret; @@ -687,6 +747,7 @@ MODULE_DEVICE_TABLE(of, geni_i2c_dt_match); static struct platform_driver geni_i2c_driver = { .probe = geni_i2c_probe, .remove = geni_i2c_remove, + .shutdown = geni_i2c_shutdown, .driver = { .name = "geni_i2c", .pm = &geni_i2c_pm_ops, diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c index 5a47915869ae..61dc20fd1191 100644 --- a/drivers/i2c/busses/i2c-qup.c +++ b/drivers/i2c/busses/i2c-qup.c @@ -1603,7 +1603,7 @@ static int qup_i2c_xfer_v2(struct i2c_adapter *adap, static u32 qup_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL_ALL & ~I2C_FUNC_SMBUS_QUICK); } static const struct i2c_algorithm qup_i2c_algo = { diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c index 217def2d7cb4..12f6d452c0f7 100644 --- a/drivers/i2c/busses/i2c-rcar.c +++ b/drivers/i2c/busses/i2c-rcar.c @@ -91,7 +91,6 @@ #define RCAR_BUS_PHASE_START (MDBS | MIE | ESG) #define RCAR_BUS_PHASE_DATA (MDBS | MIE) -#define RCAR_BUS_MASK_DATA (~(ESG | FSB) & 0xFF) #define RCAR_BUS_PHASE_STOP (MDBS | MIE | FSB) #define RCAR_IRQ_SEND (MNR | MAL | MST | MAT | MDE) @@ -120,6 +119,7 @@ enum rcar_i2c_type { }; struct rcar_i2c_priv { + u32 flags; void __iomem *io; struct i2c_adapter adap; struct i2c_msg *msg; @@ -130,7 +130,6 @@ struct rcar_i2c_priv { int pos; u32 icccr; - u32 flags; u8 recovery_icmcr; /* protected by adapter lock */ enum rcar_i2c_type devtype; struct i2c_client *slave; @@ -621,27 +620,16 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv) /* * This driver has a lock-free design because there are IP cores (at least * R-Car Gen2) which have an inherent race condition in their hardware design. - * There, we need to clear RCAR_BUS_MASK_DATA bits as soon as possible after + * There, we need to switch to RCAR_BUS_PHASE_DATA as soon as possible after * the interrupt was generated, otherwise an unwanted repeated message gets * generated. It turned out that taking a spinlock at the beginning of the ISR * was already causing repeated messages. Thus, this driver was converted to * the now lockless behaviour. Please keep this in mind when hacking the driver. + * R-Car Gen3 seems to have this fixed but earlier versions than R-Car Gen2 are + * likely affected. Therefore, we have different interrupt handler entries. */ -static irqreturn_t rcar_i2c_irq(int irq, void *ptr) +static irqreturn_t rcar_i2c_irq(int irq, struct rcar_i2c_priv *priv, u32 msr) { - struct rcar_i2c_priv *priv = ptr; - u32 msr, val; - - /* Clear START or STOP immediately, except for REPSTART after read */ - if (likely(!(priv->flags & ID_P_REP_AFTER_RD))) { - val = rcar_i2c_read(priv, ICMCR); - rcar_i2c_write(priv, ICMCR, val & RCAR_BUS_MASK_DATA); - } - - msr = rcar_i2c_read(priv, ICMSR); - - /* Only handle interrupts that are currently enabled */ - msr &= rcar_i2c_read(priv, ICMIER); if (!msr) { if (rcar_i2c_slave_irq(priv)) return IRQ_HANDLED; @@ -685,6 +673,41 @@ static irqreturn_t rcar_i2c_irq(int irq, void *ptr) return IRQ_HANDLED; } +static irqreturn_t rcar_i2c_gen2_irq(int irq, void *ptr) +{ + struct rcar_i2c_priv *priv = ptr; + u32 msr; + + /* Clear START or STOP immediately, except for REPSTART after read */ + if (likely(!(priv->flags & ID_P_REP_AFTER_RD))) + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); + + /* Only handle interrupts that are currently enabled */ + msr = rcar_i2c_read(priv, ICMSR); + msr &= rcar_i2c_read(priv, ICMIER); + + return rcar_i2c_irq(irq, priv, msr); +} + +static irqreturn_t rcar_i2c_gen3_irq(int irq, void *ptr) +{ + struct rcar_i2c_priv *priv = ptr; + u32 msr; + + /* Only handle interrupts that are currently enabled */ + msr = rcar_i2c_read(priv, ICMSR); + msr &= rcar_i2c_read(priv, ICMIER); + + /* + * Clear START or STOP immediately, except for REPSTART after read or + * if a spurious interrupt was detected. + */ + if (likely(!(priv->flags & ID_P_REP_AFTER_RD) && msr)) + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); + + return rcar_i2c_irq(irq, priv, msr); +} + static struct dma_chan *rcar_i2c_request_dma_chan(struct device *dev, enum dma_transfer_direction dir, dma_addr_t port_addr) @@ -931,6 +954,8 @@ static int rcar_i2c_probe(struct platform_device *pdev) struct rcar_i2c_priv *priv; struct i2c_adapter *adap; struct device *dev = &pdev->dev; + unsigned long irqflags = 0; + irqreturn_t (*irqhandler)(int irq, void *ptr) = rcar_i2c_gen3_irq; int ret; /* Otherwise logic will break because some bytes must always use PIO */ @@ -979,6 +1004,11 @@ static int rcar_i2c_probe(struct platform_device *pdev) rcar_i2c_write(priv, ICSAR, 0); /* Gen2: must be 0 if not using slave */ + if (priv->devtype < I2C_RCAR_GEN3) { + irqflags |= IRQF_NO_THREAD; + irqhandler = rcar_i2c_gen2_irq; + } + if (priv->devtype == I2C_RCAR_GEN3) { priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); if (!IS_ERR(priv->rstc)) { @@ -998,7 +1028,7 @@ static int rcar_i2c_probe(struct platform_device *pdev) priv->flags |= ID_P_HOST_NOTIFY; priv->irq = platform_get_irq(pdev, 0); - ret = devm_request_irq(dev, priv->irq, rcar_i2c_irq, 0, dev_name(dev), priv); + ret = devm_request_irq(dev, priv->irq, irqhandler, irqflags, dev_name(dev), priv); if (ret < 0) { dev_err(dev, "cannot get irq %d\n", priv->irq); goto out_pm_disable; diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index 3eafe0eb3e4c..62a903fbe912 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -781,7 +781,7 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, /* declare our i2c functionality */ static u32 s3c24xx_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL_ALL | I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING; } diff --git a/drivers/i2c/busses/i2c-sirf.c b/drivers/i2c/busses/i2c-sirf.c deleted file mode 100644 index 30db8fafe078..000000000000 --- a/drivers/i2c/busses/i2c-sirf.c +++ /dev/null @@ -1,475 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * I2C bus driver for CSR SiRFprimaII - * - * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SIRFSOC_I2C_CLK_CTRL 0x00 -#define SIRFSOC_I2C_STATUS 0x0C -#define SIRFSOC_I2C_CTRL 0x10 -#define SIRFSOC_I2C_IO_CTRL 0x14 -#define SIRFSOC_I2C_SDA_DELAY 0x18 -#define SIRFSOC_I2C_CMD_START 0x1C -#define SIRFSOC_I2C_CMD_BUF 0x30 -#define SIRFSOC_I2C_DATA_BUF 0x80 - -#define SIRFSOC_I2C_CMD_BUF_MAX 16 -#define SIRFSOC_I2C_DATA_BUF_MAX 16 - -#define SIRFSOC_I2C_CMD(x) (SIRFSOC_I2C_CMD_BUF + (x)*0x04) -#define SIRFSOC_I2C_DATA_MASK(x) (0xFF<<(((x)&3)*8)) -#define SIRFSOC_I2C_DATA_SHIFT(x) (((x)&3)*8) - -#define SIRFSOC_I2C_DIV_MASK (0xFFFF) - -/* I2C status flags */ -#define SIRFSOC_I2C_STAT_BUSY BIT(0) -#define SIRFSOC_I2C_STAT_TIP BIT(1) -#define SIRFSOC_I2C_STAT_NACK BIT(2) -#define SIRFSOC_I2C_STAT_TR_INT BIT(4) -#define SIRFSOC_I2C_STAT_STOP BIT(6) -#define SIRFSOC_I2C_STAT_CMD_DONE BIT(8) -#define SIRFSOC_I2C_STAT_ERR BIT(9) -#define SIRFSOC_I2C_CMD_INDEX (0x1F<<16) - -/* I2C control flags */ -#define SIRFSOC_I2C_RESET BIT(0) -#define SIRFSOC_I2C_CORE_EN BIT(1) -#define SIRFSOC_I2C_MASTER_MODE BIT(2) -#define SIRFSOC_I2C_CMD_DONE_EN BIT(11) -#define SIRFSOC_I2C_ERR_INT_EN BIT(12) - -#define SIRFSOC_I2C_SDA_DELAY_MASK (0xFF) -#define SIRFSOC_I2C_SCLF_FILTER (3<<8) - -#define SIRFSOC_I2C_START_CMD BIT(0) - -#define SIRFSOC_I2C_CMD_RP(x) ((x)&0x7) -#define SIRFSOC_I2C_NACK BIT(3) -#define SIRFSOC_I2C_WRITE BIT(4) -#define SIRFSOC_I2C_READ BIT(5) -#define SIRFSOC_I2C_STOP BIT(6) -#define SIRFSOC_I2C_START BIT(7) - -#define SIRFSOC_I2C_ERR_NOACK 1 -#define SIRFSOC_I2C_ERR_TIMEOUT 2 - -struct sirfsoc_i2c { - void __iomem *base; - struct clk *clk; - u32 cmd_ptr; /* Current position in CMD buffer */ - u8 *buf; /* Buffer passed by user */ - u32 msg_len; /* Message length */ - u32 finished_len; /* number of bytes read/written */ - u32 read_cmd_len; /* number of read cmd sent */ - int msg_read; /* 1 indicates a read message */ - int err_status; /* 1 indicates an error on bus */ - - u32 sda_delay; /* For suspend/resume */ - u32 clk_div; - int last; /* Last message in transfer, STOP cmd can be sent */ - - struct completion done; /* indicates completion of message transfer */ - struct i2c_adapter adapter; -}; - -static void i2c_sirfsoc_read_data(struct sirfsoc_i2c *siic) -{ - u32 data = 0; - int i; - - for (i = 0; i < siic->read_cmd_len; i++) { - if (!(i & 0x3)) - data = readl(siic->base + SIRFSOC_I2C_DATA_BUF + i); - siic->buf[siic->finished_len++] = - (u8)((data & SIRFSOC_I2C_DATA_MASK(i)) >> - SIRFSOC_I2C_DATA_SHIFT(i)); - } -} - -static void i2c_sirfsoc_queue_cmd(struct sirfsoc_i2c *siic) -{ - u32 regval; - int i = 0; - - if (siic->msg_read) { - while (((siic->finished_len + i) < siic->msg_len) - && (siic->cmd_ptr < SIRFSOC_I2C_CMD_BUF_MAX)) { - regval = SIRFSOC_I2C_READ | SIRFSOC_I2C_CMD_RP(0); - if (((siic->finished_len + i) == - (siic->msg_len - 1)) && siic->last) - regval |= SIRFSOC_I2C_STOP | SIRFSOC_I2C_NACK; - writel(regval, - siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); - i++; - } - - siic->read_cmd_len = i; - } else { - while ((siic->cmd_ptr < SIRFSOC_I2C_CMD_BUF_MAX - 1) - && (siic->finished_len < siic->msg_len)) { - regval = SIRFSOC_I2C_WRITE | SIRFSOC_I2C_CMD_RP(0); - if ((siic->finished_len == (siic->msg_len - 1)) - && siic->last) - regval |= SIRFSOC_I2C_STOP; - writel(regval, - siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); - writel(siic->buf[siic->finished_len++], - siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); - } - } - siic->cmd_ptr = 0; - - /* Trigger the transfer */ - writel(SIRFSOC_I2C_START_CMD, siic->base + SIRFSOC_I2C_CMD_START); -} - -static irqreturn_t i2c_sirfsoc_irq(int irq, void *dev_id) -{ - struct sirfsoc_i2c *siic = (struct sirfsoc_i2c *)dev_id; - u32 i2c_stat = readl(siic->base + SIRFSOC_I2C_STATUS); - - if (i2c_stat & SIRFSOC_I2C_STAT_ERR) { - /* Error conditions */ - siic->err_status = SIRFSOC_I2C_ERR_NOACK; - writel(SIRFSOC_I2C_STAT_ERR, siic->base + SIRFSOC_I2C_STATUS); - - if (i2c_stat & SIRFSOC_I2C_STAT_NACK) - dev_dbg(&siic->adapter.dev, "ACK not received\n"); - else - dev_err(&siic->adapter.dev, "I2C error\n"); - - /* - * Due to hardware ANOMALY, we need to reset I2C earlier after - * we get NOACK while accessing non-existing clients, otherwise - * we will get errors even we access existing clients later - */ - writel(readl(siic->base + SIRFSOC_I2C_CTRL) | SIRFSOC_I2C_RESET, - siic->base + SIRFSOC_I2C_CTRL); - while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) - cpu_relax(); - - complete(&siic->done); - } else if (i2c_stat & SIRFSOC_I2C_STAT_CMD_DONE) { - /* CMD buffer execution complete */ - if (siic->msg_read) - i2c_sirfsoc_read_data(siic); - if (siic->finished_len == siic->msg_len) - complete(&siic->done); - else /* Fill a new CMD buffer for left data */ - i2c_sirfsoc_queue_cmd(siic); - - writel(SIRFSOC_I2C_STAT_CMD_DONE, siic->base + SIRFSOC_I2C_STATUS); - } - - return IRQ_HANDLED; -} - -static void i2c_sirfsoc_set_address(struct sirfsoc_i2c *siic, - struct i2c_msg *msg) -{ - unsigned char addr; - u32 regval = SIRFSOC_I2C_START | SIRFSOC_I2C_CMD_RP(0) | SIRFSOC_I2C_WRITE; - - /* no data and last message -> add STOP */ - if (siic->last && (msg->len == 0)) - regval |= SIRFSOC_I2C_STOP; - - writel(regval, siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); - - addr = i2c_8bit_addr_from_msg(msg); - - /* Reverse direction bit */ - if (msg->flags & I2C_M_REV_DIR_ADDR) - addr ^= 1; - - writel(addr, siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); -} - -static int i2c_sirfsoc_xfer_msg(struct sirfsoc_i2c *siic, struct i2c_msg *msg) -{ - u32 regval = readl(siic->base + SIRFSOC_I2C_CTRL); - /* timeout waiting for the xfer to finish or fail */ - int timeout = msecs_to_jiffies((msg->len + 1) * 50); - - i2c_sirfsoc_set_address(siic, msg); - - writel(regval | SIRFSOC_I2C_CMD_DONE_EN | SIRFSOC_I2C_ERR_INT_EN, - siic->base + SIRFSOC_I2C_CTRL); - i2c_sirfsoc_queue_cmd(siic); - - if (wait_for_completion_timeout(&siic->done, timeout) == 0) { - siic->err_status = SIRFSOC_I2C_ERR_TIMEOUT; - dev_err(&siic->adapter.dev, "Transfer timeout\n"); - } - - writel(regval & ~(SIRFSOC_I2C_CMD_DONE_EN | SIRFSOC_I2C_ERR_INT_EN), - siic->base + SIRFSOC_I2C_CTRL); - writel(0, siic->base + SIRFSOC_I2C_CMD_START); - - /* i2c control doesn't response, reset it */ - if (siic->err_status == SIRFSOC_I2C_ERR_TIMEOUT) { - writel(readl(siic->base + SIRFSOC_I2C_CTRL) | SIRFSOC_I2C_RESET, - siic->base + SIRFSOC_I2C_CTRL); - while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) - cpu_relax(); - } - return siic->err_status ? -EAGAIN : 0; -} - -static u32 i2c_sirfsoc_func(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static int i2c_sirfsoc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, - int num) -{ - struct sirfsoc_i2c *siic = adap->algo_data; - int i, ret; - - clk_enable(siic->clk); - - for (i = 0; i < num; i++) { - siic->buf = msgs[i].buf; - siic->msg_len = msgs[i].len; - siic->msg_read = !!(msgs[i].flags & I2C_M_RD); - siic->err_status = 0; - siic->cmd_ptr = 0; - siic->finished_len = 0; - siic->last = (i == (num - 1)); - - ret = i2c_sirfsoc_xfer_msg(siic, &msgs[i]); - if (ret) { - clk_disable(siic->clk); - return ret; - } - } - - clk_disable(siic->clk); - return num; -} - -/* I2C algorithms associated with this master controller driver */ -static const struct i2c_algorithm i2c_sirfsoc_algo = { - .master_xfer = i2c_sirfsoc_xfer, - .functionality = i2c_sirfsoc_func, -}; - -static int i2c_sirfsoc_probe(struct platform_device *pdev) -{ - struct sirfsoc_i2c *siic; - struct i2c_adapter *adap; - struct clk *clk; - int bitrate; - int ctrl_speed; - int irq; - - int err; - u32 regval; - - clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(clk)) { - err = PTR_ERR(clk); - dev_err(&pdev->dev, "Clock get failed\n"); - goto err_get_clk; - } - - err = clk_prepare(clk); - if (err) { - dev_err(&pdev->dev, "Clock prepare failed\n"); - goto err_clk_prep; - } - - err = clk_enable(clk); - if (err) { - dev_err(&pdev->dev, "Clock enable failed\n"); - goto err_clk_en; - } - - ctrl_speed = clk_get_rate(clk); - - siic = devm_kzalloc(&pdev->dev, sizeof(*siic), GFP_KERNEL); - if (!siic) { - err = -ENOMEM; - goto out; - } - adap = &siic->adapter; - adap->class = I2C_CLASS_DEPRECATED; - - siic->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(siic->base)) { - err = PTR_ERR(siic->base); - goto out; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = irq; - goto out; - } - err = devm_request_irq(&pdev->dev, irq, i2c_sirfsoc_irq, 0, - dev_name(&pdev->dev), siic); - if (err) - goto out; - - adap->algo = &i2c_sirfsoc_algo; - adap->algo_data = siic; - adap->retries = 3; - - adap->dev.of_node = pdev->dev.of_node; - adap->dev.parent = &pdev->dev; - adap->nr = pdev->id; - - strlcpy(adap->name, "sirfsoc-i2c", sizeof(adap->name)); - - platform_set_drvdata(pdev, adap); - init_completion(&siic->done); - - /* Controller initialisation */ - - writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); - while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) - cpu_relax(); - writel(SIRFSOC_I2C_CORE_EN | SIRFSOC_I2C_MASTER_MODE, - siic->base + SIRFSOC_I2C_CTRL); - - siic->clk = clk; - - err = of_property_read_u32(pdev->dev.of_node, - "clock-frequency", &bitrate); - if (err < 0) - bitrate = I2C_MAX_STANDARD_MODE_FREQ; - - /* - * Due to some hardware design issues, we need to tune the formula. - * Since i2c is open drain interface that allows the slave to - * stall the transaction by holding the SCL line at '0', the RTL - * implementation is waiting for SCL feedback from the pin after - * setting it to High-Z ('1'). This wait adds to the high-time - * interval counter few cycles of the input synchronization - * (depending on the SCL_FILTER_REG field), and also the time it - * takes for the board pull-up resistor to rise the SCL line. - * For slow SCL settings these additions are negligible, - * but they start to affect the speed when clock is set to faster - * frequencies. - * Through the actual tests, use the different user_div value(which - * in the divider formula 'Fio / (Fi2c * user_div)') to adapt - * the different ranges of i2c bus clock frequency, to make the SCL - * more accurate. - */ - if (bitrate <= 30000) - regval = ctrl_speed / (bitrate * 5); - else if (bitrate > 30000 && bitrate <= 280000) - regval = (2 * ctrl_speed) / (bitrate * 11); - else - regval = ctrl_speed / (bitrate * 6); - - writel(regval, siic->base + SIRFSOC_I2C_CLK_CTRL); - if (regval > 0xFF) - writel(0xFF, siic->base + SIRFSOC_I2C_SDA_DELAY); - else - writel(regval, siic->base + SIRFSOC_I2C_SDA_DELAY); - - err = i2c_add_numbered_adapter(adap); - if (err < 0) - goto out; - - clk_disable(clk); - - dev_info(&pdev->dev, " I2C adapter ready to operate\n"); - - return 0; - -out: - clk_disable(clk); -err_clk_en: - clk_unprepare(clk); -err_clk_prep: - clk_put(clk); -err_get_clk: - return err; -} - -static int i2c_sirfsoc_remove(struct platform_device *pdev) -{ - struct i2c_adapter *adapter = platform_get_drvdata(pdev); - struct sirfsoc_i2c *siic = adapter->algo_data; - - writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); - i2c_del_adapter(adapter); - clk_unprepare(siic->clk); - clk_put(siic->clk); - return 0; -} - -#ifdef CONFIG_PM -static int i2c_sirfsoc_suspend(struct device *dev) -{ - struct i2c_adapter *adapter = dev_get_drvdata(dev); - struct sirfsoc_i2c *siic = adapter->algo_data; - - clk_enable(siic->clk); - siic->sda_delay = readl(siic->base + SIRFSOC_I2C_SDA_DELAY); - siic->clk_div = readl(siic->base + SIRFSOC_I2C_CLK_CTRL); - clk_disable(siic->clk); - return 0; -} - -static int i2c_sirfsoc_resume(struct device *dev) -{ - struct i2c_adapter *adapter = dev_get_drvdata(dev); - struct sirfsoc_i2c *siic = adapter->algo_data; - - clk_enable(siic->clk); - writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); - while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) - cpu_relax(); - writel(SIRFSOC_I2C_CORE_EN | SIRFSOC_I2C_MASTER_MODE, - siic->base + SIRFSOC_I2C_CTRL); - writel(siic->clk_div, siic->base + SIRFSOC_I2C_CLK_CTRL); - writel(siic->sda_delay, siic->base + SIRFSOC_I2C_SDA_DELAY); - clk_disable(siic->clk); - return 0; -} - -static const struct dev_pm_ops i2c_sirfsoc_pm_ops = { - .suspend = i2c_sirfsoc_suspend, - .resume = i2c_sirfsoc_resume, -}; -#endif - -static const struct of_device_id sirfsoc_i2c_of_match[] = { - { .compatible = "sirf,prima2-i2c", }, - {}, -}; -MODULE_DEVICE_TABLE(of, sirfsoc_i2c_of_match); - -static struct platform_driver i2c_sirfsoc_driver = { - .driver = { - .name = "sirfsoc_i2c", -#ifdef CONFIG_PM - .pm = &i2c_sirfsoc_pm_ops, -#endif - .of_match_table = sirfsoc_i2c_of_match, - }, - .probe = i2c_sirfsoc_probe, - .remove = i2c_sirfsoc_remove, -}; -module_platform_driver(i2c_sirfsoc_driver); - -MODULE_DESCRIPTION("SiRF SoC I2C master controller driver"); -MODULE_AUTHOR("Zhiwu Song "); -MODULE_AUTHOR("Xiangzhen Ye "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-stm32f7.c b/drivers/i2c/busses/i2c-stm32f7.c index 473fbe144b7e..c62c815b88eb 100644 --- a/drivers/i2c/busses/i2c-stm32f7.c +++ b/drivers/i2c/busses/i2c-stm32f7.c @@ -2035,12 +2035,8 @@ static int stm32f7_i2c_probe(struct platform_device *pdev) } irq_error = platform_get_irq(pdev, 1); - if (irq_error <= 0) { - if (irq_error != -EPROBE_DEFER) - dev_err(&pdev->dev, "Failed to get IRQ error: %d\n", - irq_error); + if (irq_error <= 0) return irq_error ? : -ENOENT; - } i2c_dev->wakeup_src = of_property_read_bool(pdev->dev.of_node, "wakeup-source"); diff --git a/drivers/i2c/busses/i2c-stu300.c b/drivers/i2c/busses/i2c-stu300.c deleted file mode 100644 index 64d739baf480..000000000000 --- a/drivers/i2c/busses/i2c-stu300.c +++ /dev/null @@ -1,1008 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2007-2012 ST-Ericsson AB - * ST DDC I2C master mode driver, used in e.g. U300 series platforms. - * Author: Linus Walleij - * Author: Jonas Aaberg - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* the name of this kernel module */ -#define NAME "stu300" - -/* CR (Control Register) 8bit (R/W) */ -#define I2C_CR (0x00000000) -#define I2C_CR_RESET_VALUE (0x00) -#define I2C_CR_RESET_UMASK (0x00) -#define I2C_CR_DDC1_ENABLE (0x80) -#define I2C_CR_TRANS_ENABLE (0x40) -#define I2C_CR_PERIPHERAL_ENABLE (0x20) -#define I2C_CR_DDC2B_ENABLE (0x10) -#define I2C_CR_START_ENABLE (0x08) -#define I2C_CR_ACK_ENABLE (0x04) -#define I2C_CR_STOP_ENABLE (0x02) -#define I2C_CR_INTERRUPT_ENABLE (0x01) -/* SR1 (Status Register 1) 8bit (R/-) */ -#define I2C_SR1 (0x00000004) -#define I2C_SR1_RESET_VALUE (0x00) -#define I2C_SR1_RESET_UMASK (0x00) -#define I2C_SR1_EVF_IND (0x80) -#define I2C_SR1_ADD10_IND (0x40) -#define I2C_SR1_TRA_IND (0x20) -#define I2C_SR1_BUSY_IND (0x10) -#define I2C_SR1_BTF_IND (0x08) -#define I2C_SR1_ADSL_IND (0x04) -#define I2C_SR1_MSL_IND (0x02) -#define I2C_SR1_SB_IND (0x01) -/* SR2 (Status Register 2) 8bit (R/-) */ -#define I2C_SR2 (0x00000008) -#define I2C_SR2_RESET_VALUE (0x00) -#define I2C_SR2_RESET_UMASK (0x40) -#define I2C_SR2_MASK (0xBF) -#define I2C_SR2_SCLFAL_IND (0x80) -#define I2C_SR2_ENDAD_IND (0x20) -#define I2C_SR2_AF_IND (0x10) -#define I2C_SR2_STOPF_IND (0x08) -#define I2C_SR2_ARLO_IND (0x04) -#define I2C_SR2_BERR_IND (0x02) -#define I2C_SR2_DDC2BF_IND (0x01) -/* CCR (Clock Control Register) 8bit (R/W) */ -#define I2C_CCR (0x0000000C) -#define I2C_CCR_RESET_VALUE (0x00) -#define I2C_CCR_RESET_UMASK (0x00) -#define I2C_CCR_MASK (0xFF) -#define I2C_CCR_FMSM (0x80) -#define I2C_CCR_CC_MASK (0x7F) -/* OAR1 (Own Address Register 1) 8bit (R/W) */ -#define I2C_OAR1 (0x00000010) -#define I2C_OAR1_RESET_VALUE (0x00) -#define I2C_OAR1_RESET_UMASK (0x00) -#define I2C_OAR1_ADD_MASK (0xFF) -/* OAR2 (Own Address Register 2) 8bit (R/W) */ -#define I2C_OAR2 (0x00000014) -#define I2C_OAR2_RESET_VALUE (0x40) -#define I2C_OAR2_RESET_UMASK (0x19) -#define I2C_OAR2_MASK (0xE6) -#define I2C_OAR2_FR_25_10MHZ (0x00) -#define I2C_OAR2_FR_10_1667MHZ (0x20) -#define I2C_OAR2_FR_1667_2667MHZ (0x40) -#define I2C_OAR2_FR_2667_40MHZ (0x60) -#define I2C_OAR2_FR_40_5333MHZ (0x80) -#define I2C_OAR2_FR_5333_66MHZ (0xA0) -#define I2C_OAR2_FR_66_80MHZ (0xC0) -#define I2C_OAR2_FR_80_100MHZ (0xE0) -#define I2C_OAR2_FR_MASK (0xE0) -#define I2C_OAR2_ADD_MASK (0x06) -/* DR (Data Register) 8bit (R/W) */ -#define I2C_DR (0x00000018) -#define I2C_DR_RESET_VALUE (0x00) -#define I2C_DR_RESET_UMASK (0xFF) -#define I2C_DR_D_MASK (0xFF) -/* ECCR (Extended Clock Control Register) 8bit (R/W) */ -#define I2C_ECCR (0x0000001C) -#define I2C_ECCR_RESET_VALUE (0x00) -#define I2C_ECCR_RESET_UMASK (0xE0) -#define I2C_ECCR_MASK (0x1F) -#define I2C_ECCR_CC_MASK (0x1F) - -/* - * These events are more or less responses to commands - * sent into the hardware, presumably reflecting the state - * of an internal state machine. - */ -enum stu300_event { - STU300_EVENT_NONE = 0, - STU300_EVENT_1, - STU300_EVENT_2, - STU300_EVENT_3, - STU300_EVENT_4, - STU300_EVENT_5, - STU300_EVENT_6, - STU300_EVENT_7, - STU300_EVENT_8, - STU300_EVENT_9 -}; - -enum stu300_error { - STU300_ERROR_NONE = 0, - STU300_ERROR_ACKNOWLEDGE_FAILURE, - STU300_ERROR_BUS_ERROR, - STU300_ERROR_ARBITRATION_LOST, - STU300_ERROR_UNKNOWN -}; - -/* timeout waiting for the controller to respond */ -#define STU300_TIMEOUT (msecs_to_jiffies(1000)) - -/* - * The number of address send athemps tried before giving up. - * If the first one fails it seems like 5 to 8 attempts are required. - */ -#define NUM_ADDR_RESEND_ATTEMPTS 12 - -/* I2C clock speed, in Hz 0-400kHz*/ -static unsigned int scl_frequency = I2C_MAX_STANDARD_MODE_FREQ; -module_param(scl_frequency, uint, 0644); - -/** - * struct stu300_dev - the stu300 driver state holder - * @pdev: parent platform device - * @adapter: corresponding I2C adapter - * @clk: hardware block clock - * @irq: assigned interrupt line - * @cmd_issue_lock: this locks the following cmd_ variables - * @cmd_complete: acknowledge completion for an I2C command - * @cmd_event: expected event coming in as a response to a command - * @cmd_err: error code as response to a command - * @speed: current bus speed in Hz - * @msg_index: index of current message - * @msg_len: length of current message - */ - -struct stu300_dev { - struct platform_device *pdev; - struct i2c_adapter adapter; - void __iomem *virtbase; - struct clk *clk; - int irq; - spinlock_t cmd_issue_lock; - struct completion cmd_complete; - enum stu300_event cmd_event; - enum stu300_error cmd_err; - unsigned int speed; - int msg_index; - int msg_len; -}; - -/* Local forward function declarations */ -static int stu300_init_hw(struct stu300_dev *dev); - -/* - * The block needs writes in both MSW and LSW in order - * for all data lines to reach their destination. - */ -static inline void stu300_wr8(u32 value, void __iomem *address) -{ - writel((value << 16) | value, address); -} - -/* - * This merely masks off the duplicates which appear - * in bytes 1-3. You _MUST_ use 32-bit bus access on this - * device, else it will not work. - */ -static inline u32 stu300_r8(void __iomem *address) -{ - return readl(address) & 0x000000FFU; -} - -static void stu300_irq_enable(struct stu300_dev *dev) -{ - u32 val; - val = stu300_r8(dev->virtbase + I2C_CR); - val |= I2C_CR_INTERRUPT_ENABLE; - /* Twice paranoia (possible HW glitch) */ - stu300_wr8(val, dev->virtbase + I2C_CR); - stu300_wr8(val, dev->virtbase + I2C_CR); -} - -static void stu300_irq_disable(struct stu300_dev *dev) -{ - u32 val; - val = stu300_r8(dev->virtbase + I2C_CR); - val &= ~I2C_CR_INTERRUPT_ENABLE; - /* Twice paranoia (possible HW glitch) */ - stu300_wr8(val, dev->virtbase + I2C_CR); - stu300_wr8(val, dev->virtbase + I2C_CR); -} - - -/* - * Tells whether a certain event or events occurred in - * response to a command. The events represent states in - * the internal state machine of the hardware. The events - * are not very well described in the hardware - * documentation and can only be treated as abstract state - * machine states. - * - * @ret 0 = event has not occurred or unknown error, any - * other value means the correct event occurred or an error. - */ - -static int stu300_event_occurred(struct stu300_dev *dev, - enum stu300_event mr_event) { - u32 status1; - u32 status2; - - /* What event happened? */ - status1 = stu300_r8(dev->virtbase + I2C_SR1); - - if (!(status1 & I2C_SR1_EVF_IND)) - /* No event at all */ - return 0; - - status2 = stu300_r8(dev->virtbase + I2C_SR2); - - /* Block any multiple interrupts */ - stu300_irq_disable(dev); - - /* Check for errors first */ - if (status2 & I2C_SR2_AF_IND) { - dev->cmd_err = STU300_ERROR_ACKNOWLEDGE_FAILURE; - return 1; - } else if (status2 & I2C_SR2_BERR_IND) { - dev->cmd_err = STU300_ERROR_BUS_ERROR; - return 1; - } else if (status2 & I2C_SR2_ARLO_IND) { - dev->cmd_err = STU300_ERROR_ARBITRATION_LOST; - return 1; - } - - switch (mr_event) { - case STU300_EVENT_1: - if (status1 & I2C_SR1_ADSL_IND) - return 1; - break; - case STU300_EVENT_2: - case STU300_EVENT_3: - case STU300_EVENT_7: - case STU300_EVENT_8: - if (status1 & I2C_SR1_BTF_IND) { - return 1; - } - break; - case STU300_EVENT_4: - if (status2 & I2C_SR2_STOPF_IND) - return 1; - break; - case STU300_EVENT_5: - if (status1 & I2C_SR1_SB_IND) - /* Clear start bit */ - return 1; - break; - case STU300_EVENT_6: - if (status2 & I2C_SR2_ENDAD_IND) { - /* First check for any errors */ - return 1; - } - break; - case STU300_EVENT_9: - if (status1 & I2C_SR1_ADD10_IND) - return 1; - break; - default: - break; - } - /* If we get here, we're on thin ice. - * Here we are in a status where we have - * gotten a response that does not match - * what we requested. - */ - dev->cmd_err = STU300_ERROR_UNKNOWN; - dev_err(&dev->pdev->dev, - "Unhandled interrupt! %d sr1: 0x%x sr2: 0x%x\n", - mr_event, status1, status2); - return 0; -} - -static irqreturn_t stu300_irh(int irq, void *data) -{ - struct stu300_dev *dev = data; - int res; - - /* Just make sure that the block is clocked */ - clk_enable(dev->clk); - - /* See if this was what we were waiting for */ - spin_lock(&dev->cmd_issue_lock); - - res = stu300_event_occurred(dev, dev->cmd_event); - if (res || dev->cmd_err != STU300_ERROR_NONE) - complete(&dev->cmd_complete); - - spin_unlock(&dev->cmd_issue_lock); - - clk_disable(dev->clk); - - return IRQ_HANDLED; -} - -/* - * Sends a command and then waits for the bits masked by *flagmask* - * to go high or low by IRQ awaiting. - */ -static int stu300_start_and_await_event(struct stu300_dev *dev, - u8 cr_value, - enum stu300_event mr_event) -{ - int ret; - - /* Lock command issue, fill in an event we wait for */ - spin_lock_irq(&dev->cmd_issue_lock); - init_completion(&dev->cmd_complete); - dev->cmd_err = STU300_ERROR_NONE; - dev->cmd_event = mr_event; - spin_unlock_irq(&dev->cmd_issue_lock); - - /* Turn on interrupt, send command and wait. */ - cr_value |= I2C_CR_INTERRUPT_ENABLE; - stu300_wr8(cr_value, dev->virtbase + I2C_CR); - ret = wait_for_completion_interruptible_timeout(&dev->cmd_complete, - STU300_TIMEOUT); - if (ret < 0) { - dev_err(&dev->pdev->dev, - "wait_for_completion_interruptible_timeout() " - "returned %d waiting for event %04x\n", ret, mr_event); - return ret; - } - - if (ret == 0) { - dev_err(&dev->pdev->dev, "controller timed out " - "waiting for event %d, reinit hardware\n", mr_event); - (void) stu300_init_hw(dev); - return -ETIMEDOUT; - } - - if (dev->cmd_err != STU300_ERROR_NONE) { - dev_err(&dev->pdev->dev, "controller (start) " - "error %d waiting for event %d, reinit hardware\n", - dev->cmd_err, mr_event); - (void) stu300_init_hw(dev); - return -EIO; - } - - return 0; -} - -/* - * This waits for a flag to be set, if it is not set on entry, an interrupt is - * configured to wait for the flag using a completion. - */ -static int stu300_await_event(struct stu300_dev *dev, - enum stu300_event mr_event) -{ - int ret; - - /* Is it already here? */ - spin_lock_irq(&dev->cmd_issue_lock); - dev->cmd_err = STU300_ERROR_NONE; - dev->cmd_event = mr_event; - - init_completion(&dev->cmd_complete); - - /* Turn on the I2C interrupt for current operation */ - stu300_irq_enable(dev); - - /* Unlock the command block and wait for the event to occur */ - spin_unlock_irq(&dev->cmd_issue_lock); - - ret = wait_for_completion_interruptible_timeout(&dev->cmd_complete, - STU300_TIMEOUT); - if (ret < 0) { - dev_err(&dev->pdev->dev, - "wait_for_completion_interruptible_timeout()" - "returned %d waiting for event %04x\n", ret, mr_event); - return ret; - } - - if (ret == 0) { - if (mr_event != STU300_EVENT_6) { - dev_err(&dev->pdev->dev, "controller " - "timed out waiting for event %d, reinit " - "hardware\n", mr_event); - (void) stu300_init_hw(dev); - } - return -ETIMEDOUT; - } - - if (dev->cmd_err != STU300_ERROR_NONE) { - if (mr_event != STU300_EVENT_6) { - dev_err(&dev->pdev->dev, "controller " - "error (await_event) %d waiting for event %d, " - "reinit hardware\n", dev->cmd_err, mr_event); - (void) stu300_init_hw(dev); - } - return -EIO; - } - - return 0; -} - -/* - * Waits for the busy bit to go low by repeated polling. - */ -#define BUSY_RELEASE_ATTEMPTS 10 -static int stu300_wait_while_busy(struct stu300_dev *dev) -{ - unsigned long timeout; - int i; - - for (i = 0; i < BUSY_RELEASE_ATTEMPTS; i++) { - timeout = jiffies + STU300_TIMEOUT; - - while (!time_after(jiffies, timeout)) { - /* Is not busy? */ - if ((stu300_r8(dev->virtbase + I2C_SR1) & - I2C_SR1_BUSY_IND) == 0) - return 0; - msleep(1); - } - - dev_err(&dev->pdev->dev, "transaction timed out " - "waiting for device to be free (not busy). " - "Attempt: %d\n", i+1); - - dev_err(&dev->pdev->dev, "base address = " - "0x%p, reinit hardware\n", dev->virtbase); - - (void) stu300_init_hw(dev); - } - - dev_err(&dev->pdev->dev, "giving up after %d attempts " - "to reset the bus.\n", BUSY_RELEASE_ATTEMPTS); - - return -ETIMEDOUT; -} - -struct stu300_clkset { - unsigned long rate; - u32 setting; -}; - -static const struct stu300_clkset stu300_clktable[] = { - { 0, 0xFFU }, - { 2500000, I2C_OAR2_FR_25_10MHZ }, - { 10000000, I2C_OAR2_FR_10_1667MHZ }, - { 16670000, I2C_OAR2_FR_1667_2667MHZ }, - { 26670000, I2C_OAR2_FR_2667_40MHZ }, - { 40000000, I2C_OAR2_FR_40_5333MHZ }, - { 53330000, I2C_OAR2_FR_5333_66MHZ }, - { 66000000, I2C_OAR2_FR_66_80MHZ }, - { 80000000, I2C_OAR2_FR_80_100MHZ }, - { 100000000, 0xFFU }, -}; - - -static int stu300_set_clk(struct stu300_dev *dev, unsigned long clkrate) -{ - - u32 val; - int i = 0; - - /* Locate the appropriate clock setting */ - while (i < ARRAY_SIZE(stu300_clktable) - 1 && - stu300_clktable[i].rate < clkrate) - i++; - - if (stu300_clktable[i].setting == 0xFFU) { - dev_err(&dev->pdev->dev, "too %s clock rate requested " - "(%lu Hz).\n", i ? "high" : "low", clkrate); - return -EINVAL; - } - - stu300_wr8(stu300_clktable[i].setting, - dev->virtbase + I2C_OAR2); - - dev_dbg(&dev->pdev->dev, "Clock rate %lu Hz, I2C bus speed %d Hz " - "virtbase %p\n", clkrate, dev->speed, dev->virtbase); - - if (dev->speed > I2C_MAX_STANDARD_MODE_FREQ) - /* Fast Mode I2C */ - val = ((clkrate/dev->speed) - 9)/3 + 1; - else - /* Standard Mode I2C */ - val = ((clkrate/dev->speed) - 7)/2 + 1; - - /* According to spec the divider must be > 2 */ - if (val < 0x002) { - dev_err(&dev->pdev->dev, "too low clock rate (%lu Hz).\n", - clkrate); - return -EINVAL; - } - - /* We have 12 bits clock divider only! */ - if (val & 0xFFFFF000U) { - dev_err(&dev->pdev->dev, "too high clock rate (%lu Hz).\n", - clkrate); - return -EINVAL; - } - - if (dev->speed > I2C_MAX_STANDARD_MODE_FREQ) { - /* CC6..CC0 */ - stu300_wr8((val & I2C_CCR_CC_MASK) | I2C_CCR_FMSM, - dev->virtbase + I2C_CCR); - dev_dbg(&dev->pdev->dev, "set clock divider to 0x%08x, " - "Fast Mode I2C\n", val); - } else { - /* CC6..CC0 */ - stu300_wr8((val & I2C_CCR_CC_MASK), - dev->virtbase + I2C_CCR); - dev_dbg(&dev->pdev->dev, "set clock divider to " - "0x%08x, Standard Mode I2C\n", val); - } - - /* CC11..CC7 */ - stu300_wr8(((val >> 7) & 0x1F), - dev->virtbase + I2C_ECCR); - - return 0; -} - - -static int stu300_init_hw(struct stu300_dev *dev) -{ - u32 dummy; - unsigned long clkrate; - int ret; - - /* Disable controller */ - stu300_wr8(0x00, dev->virtbase + I2C_CR); - /* - * Set own address to some default value (0x00). - * We do not support slave mode anyway. - */ - stu300_wr8(0x00, dev->virtbase + I2C_OAR1); - /* - * The I2C controller only operates properly in 26 MHz but we - * program this driver as if we didn't know. This will also set the two - * high bits of the own address to zero as well. - * There is no known hardware issue with running in 13 MHz - * However, speeds over 200 kHz are not used. - */ - clkrate = clk_get_rate(dev->clk); - ret = stu300_set_clk(dev, clkrate); - - if (ret) - return ret; - /* - * Enable block, do it TWICE (hardware glitch) - * Setting bit 7 can enable DDC mode. (Not used currently.) - */ - stu300_wr8(I2C_CR_PERIPHERAL_ENABLE, - dev->virtbase + I2C_CR); - stu300_wr8(I2C_CR_PERIPHERAL_ENABLE, - dev->virtbase + I2C_CR); - /* Make a dummy read of the status register SR1 & SR2 */ - dummy = stu300_r8(dev->virtbase + I2C_SR2); - dummy = stu300_r8(dev->virtbase + I2C_SR1); - - return 0; -} - - - -/* Send slave address. */ -static int stu300_send_address(struct stu300_dev *dev, - struct i2c_msg *msg, int resend) -{ - u32 val; - int ret; - - if (msg->flags & I2C_M_TEN) { - /* This is probably how 10 bit addresses look */ - val = (0xf0 | (((u32) msg->addr & 0x300) >> 7)) & - I2C_DR_D_MASK; - if (msg->flags & I2C_M_RD) - /* This is the direction bit */ - val |= 0x01; - } else { - val = i2c_8bit_addr_from_msg(msg); - } - - if (resend) { - if (msg->flags & I2C_M_RD) - dev_dbg(&dev->pdev->dev, "read resend\n"); - else - dev_dbg(&dev->pdev->dev, "write resend\n"); - } - - stu300_wr8(val, dev->virtbase + I2C_DR); - - /* For 10bit addressing, await 10bit request (EVENT 9) */ - if (msg->flags & I2C_M_TEN) { - ret = stu300_await_event(dev, STU300_EVENT_9); - /* - * The slave device wants a 10bit address, send the rest - * of the bits (the LSBits) - */ - val = msg->addr & I2C_DR_D_MASK; - /* This clears "event 9" */ - stu300_wr8(val, dev->virtbase + I2C_DR); - if (ret != 0) - return ret; - } - /* FIXME: Why no else here? two events for 10bit? - * Await event 6 (normal) or event 9 (10bit) - */ - - if (resend) - dev_dbg(&dev->pdev->dev, "await event 6\n"); - ret = stu300_await_event(dev, STU300_EVENT_6); - - /* - * Clear any pending EVENT 6 no matter what happened during - * await_event. - */ - val = stu300_r8(dev->virtbase + I2C_CR); - val |= I2C_CR_PERIPHERAL_ENABLE; - stu300_wr8(val, dev->virtbase + I2C_CR); - - return ret; -} - -static int stu300_xfer_msg(struct i2c_adapter *adap, - struct i2c_msg *msg, int stop) -{ - u32 cr; - u32 val; - u32 i; - int ret; - int attempts = 0; - struct stu300_dev *dev = i2c_get_adapdata(adap); - - clk_enable(dev->clk); - - /* Remove this if (0) to trace each and every message. */ - if (0) { - dev_dbg(&dev->pdev->dev, "I2C message to: 0x%04x, len: %d, " - "flags: 0x%04x, stop: %d\n", - msg->addr, msg->len, msg->flags, stop); - } - - /* - * For some reason, sending the address sometimes fails when running - * on the 13 MHz clock. No interrupt arrives. This is a work around, - * which tries to restart and send the address up to 10 times before - * really giving up. Usually 5 to 8 attempts are enough. - */ - do { - if (attempts) - dev_dbg(&dev->pdev->dev, "wait while busy\n"); - /* Check that the bus is free, or wait until some timeout */ - ret = stu300_wait_while_busy(dev); - if (ret != 0) - goto exit_disable; - - if (attempts) - dev_dbg(&dev->pdev->dev, "re-int hw\n"); - /* - * According to ST, there is no problem if the clock is - * changed between 13 and 26 MHz during a transfer. - */ - ret = stu300_init_hw(dev); - if (ret) - goto exit_disable; - - /* Send a start condition */ - cr = I2C_CR_PERIPHERAL_ENABLE; - /* Setting the START bit puts the block in master mode */ - if (!(msg->flags & I2C_M_NOSTART)) - cr |= I2C_CR_START_ENABLE; - if ((msg->flags & I2C_M_RD) && (msg->len > 1)) - /* On read more than 1 byte, we need ack. */ - cr |= I2C_CR_ACK_ENABLE; - /* Check that it gets through */ - if (!(msg->flags & I2C_M_NOSTART)) { - if (attempts) - dev_dbg(&dev->pdev->dev, "send start event\n"); - ret = stu300_start_and_await_event(dev, cr, - STU300_EVENT_5); - } - - if (attempts) - dev_dbg(&dev->pdev->dev, "send address\n"); - - if (ret == 0) - /* Send address */ - ret = stu300_send_address(dev, msg, attempts != 0); - - if (ret != 0) { - attempts++; - dev_dbg(&dev->pdev->dev, "failed sending address, " - "retrying. Attempt: %d msg_index: %d/%d\n", - attempts, dev->msg_index, dev->msg_len); - } - - } while (ret != 0 && attempts < NUM_ADDR_RESEND_ATTEMPTS); - - if (attempts < NUM_ADDR_RESEND_ATTEMPTS && attempts > 0) { - dev_dbg(&dev->pdev->dev, "managed to get address " - "through after %d attempts\n", attempts); - } else if (attempts == NUM_ADDR_RESEND_ATTEMPTS) { - dev_dbg(&dev->pdev->dev, "I give up, tried %d times " - "to resend address.\n", - NUM_ADDR_RESEND_ATTEMPTS); - goto exit_disable; - } - - - if (msg->flags & I2C_M_RD) { - /* READ: we read the actual bytes one at a time */ - for (i = 0; i < msg->len; i++) { - if (i == msg->len-1) { - /* - * Disable ACK and set STOP condition before - * reading last byte - */ - val = I2C_CR_PERIPHERAL_ENABLE; - - if (stop) - val |= I2C_CR_STOP_ENABLE; - - stu300_wr8(val, - dev->virtbase + I2C_CR); - } - /* Wait for this byte... */ - ret = stu300_await_event(dev, STU300_EVENT_7); - if (ret != 0) - goto exit_disable; - /* This clears event 7 */ - msg->buf[i] = (u8) stu300_r8(dev->virtbase + I2C_DR); - } - } else { - /* WRITE: we send the actual bytes one at a time */ - for (i = 0; i < msg->len; i++) { - /* Write the byte */ - stu300_wr8(msg->buf[i], - dev->virtbase + I2C_DR); - /* Check status */ - ret = stu300_await_event(dev, STU300_EVENT_8); - /* Next write to DR will clear event 8 */ - if (ret != 0) { - dev_err(&dev->pdev->dev, "error awaiting " - "event 8 (%d)\n", ret); - goto exit_disable; - } - } - /* Check NAK */ - if (!(msg->flags & I2C_M_IGNORE_NAK)) { - if (stu300_r8(dev->virtbase + I2C_SR2) & - I2C_SR2_AF_IND) { - dev_err(&dev->pdev->dev, "I2C payload " - "send returned NAK!\n"); - ret = -EIO; - goto exit_disable; - } - } - if (stop) { - /* Send stop condition */ - val = I2C_CR_PERIPHERAL_ENABLE; - val |= I2C_CR_STOP_ENABLE; - stu300_wr8(val, dev->virtbase + I2C_CR); - } - } - - /* Check that the bus is free, or wait until some timeout occurs */ - ret = stu300_wait_while_busy(dev); - if (ret != 0) { - dev_err(&dev->pdev->dev, "timeout waiting for transfer " - "to commence.\n"); - goto exit_disable; - } - - /* Dummy read status registers */ - val = stu300_r8(dev->virtbase + I2C_SR2); - val = stu300_r8(dev->virtbase + I2C_SR1); - ret = 0; - - exit_disable: - /* Disable controller */ - stu300_wr8(0x00, dev->virtbase + I2C_CR); - clk_disable(dev->clk); - return ret; -} - -static int stu300_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, - int num) -{ - int ret = -1; - int i; - - struct stu300_dev *dev = i2c_get_adapdata(adap); - dev->msg_len = num; - - for (i = 0; i < num; i++) { - /* - * Another driver appears to send stop for each message, - * here we only do that for the last message. Possibly some - * peripherals require this behaviour, then their drivers - * have to send single messages in order to get "stop" for - * each message. - */ - dev->msg_index = i; - - ret = stu300_xfer_msg(adap, &msgs[i], (i == (num - 1))); - - if (ret != 0) { - num = ret; - break; - } - } - - return num; -} - -static int stu300_xfer_todo(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) -{ - /* TODO: implement polling for this case if need be. */ - WARN(1, "%s: atomic transfers not implemented\n", dev_name(&adap->dev)); - return -EOPNOTSUPP; -} - -static u32 stu300_func(struct i2c_adapter *adap) -{ - /* This is the simplest thing you can think of... */ - return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR; -} - -static const struct i2c_algorithm stu300_algo = { - .master_xfer = stu300_xfer, - .master_xfer_atomic = stu300_xfer_todo, - .functionality = stu300_func, -}; - -static const struct i2c_adapter_quirks stu300_quirks = { - .flags = I2C_AQ_NO_ZERO_LEN, -}; - -static int stu300_probe(struct platform_device *pdev) -{ - struct stu300_dev *dev; - struct i2c_adapter *adap; - int bus_nr; - int ret = 0; - - dev = devm_kzalloc(&pdev->dev, sizeof(struct stu300_dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - bus_nr = pdev->id; - dev->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(dev->clk)) { - dev_err(&pdev->dev, "could not retrieve i2c bus clock\n"); - return PTR_ERR(dev->clk); - } - - dev->pdev = pdev; - dev->virtbase = devm_platform_ioremap_resource(pdev, 0); - dev_dbg(&pdev->dev, "initialize bus device I2C%d on virtual " - "base %p\n", bus_nr, dev->virtbase); - if (IS_ERR(dev->virtbase)) - return PTR_ERR(dev->virtbase); - - dev->irq = platform_get_irq(pdev, 0); - ret = devm_request_irq(&pdev->dev, dev->irq, stu300_irh, 0, NAME, dev); - if (ret < 0) - return ret; - - dev->speed = scl_frequency; - - clk_prepare_enable(dev->clk); - ret = stu300_init_hw(dev); - clk_disable(dev->clk); - if (ret != 0) { - dev_err(&dev->pdev->dev, "error initializing hardware.\n"); - return -EIO; - } - - /* IRQ event handling initialization */ - spin_lock_init(&dev->cmd_issue_lock); - dev->cmd_event = STU300_EVENT_NONE; - dev->cmd_err = STU300_ERROR_NONE; - - adap = &dev->adapter; - adap->owner = THIS_MODULE; - /* DDC class but actually often used for more generic I2C */ - adap->class = I2C_CLASS_DEPRECATED; - strlcpy(adap->name, "ST Microelectronics DDC I2C adapter", - sizeof(adap->name)); - adap->nr = bus_nr; - adap->algo = &stu300_algo; - adap->dev.parent = &pdev->dev; - adap->dev.of_node = pdev->dev.of_node; - adap->quirks = &stu300_quirks; - - i2c_set_adapdata(adap, dev); - - /* i2c device drivers may be active on return from add_adapter() */ - ret = i2c_add_numbered_adapter(adap); - if (ret) - return ret; - - platform_set_drvdata(pdev, dev); - dev_info(&pdev->dev, "ST DDC I2C @ %p, irq %d\n", - dev->virtbase, dev->irq); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int stu300_suspend(struct device *device) -{ - struct stu300_dev *dev = dev_get_drvdata(device); - - /* Turn off everything */ - stu300_wr8(0x00, dev->virtbase + I2C_CR); - return 0; -} - -static int stu300_resume(struct device *device) -{ - int ret = 0; - struct stu300_dev *dev = dev_get_drvdata(device); - - clk_enable(dev->clk); - ret = stu300_init_hw(dev); - clk_disable(dev->clk); - - if (ret != 0) - dev_err(device, "error re-initializing hardware.\n"); - return ret; -} - -static SIMPLE_DEV_PM_OPS(stu300_pm, stu300_suspend, stu300_resume); -#define STU300_I2C_PM (&stu300_pm) -#else -#define STU300_I2C_PM NULL -#endif - -static int stu300_remove(struct platform_device *pdev) -{ - struct stu300_dev *dev = platform_get_drvdata(pdev); - - i2c_del_adapter(&dev->adapter); - /* Turn off everything */ - stu300_wr8(0x00, dev->virtbase + I2C_CR); - return 0; -} - -static const struct of_device_id stu300_dt_match[] = { - { .compatible = "st,ddci2c" }, - {}, -}; -MODULE_DEVICE_TABLE(of, stu300_dt_match); - -static struct platform_driver stu300_i2c_driver = { - .driver = { - .name = NAME, - .pm = STU300_I2C_PM, - .of_match_table = stu300_dt_match, - }, - .probe = stu300_probe, - .remove = stu300_remove, - -}; - -static int __init stu300_init(void) -{ - return platform_driver_register(&stu300_i2c_driver); -} - -static void __exit stu300_exit(void) -{ - platform_driver_unregister(&stu300_i2c_driver); -} - -/* - * The systems using this bus often have very basic devices such - * as regulators on the I2C bus, so this needs to be loaded early. - * Therefore it is registered in the subsys_initcall(). - */ -subsys_initcall(stu300_init); -module_exit(stu300_exit); - -MODULE_AUTHOR("Linus Walleij "); -MODULE_DESCRIPTION("ST Micro DDC I2C adapter (" NAME ")"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" NAME); diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 8b113ae32dc7..c883044715f3 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -550,7 +550,7 @@ static int tegra_i2c_poll_register(struct tegra_i2c_dev *i2c_dev, void __iomem *addr = i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg); u32 val; - if (!i2c_dev->atomic_mode && !in_irq()) + if (!i2c_dev->atomic_mode) return readl_relaxed_poll_timeout(addr, val, !(val & mask), delay_us, timeout_us); @@ -1739,9 +1739,10 @@ static int tegra_i2c_probe(struct platform_device *pdev) /* interrupt will be enabled during of transfer time */ irq_set_status_flags(i2c_dev->irq, IRQ_NOAUTOEN); - err = devm_request_irq(i2c_dev->dev, i2c_dev->irq, tegra_i2c_isr, - IRQF_NO_SUSPEND, dev_name(i2c_dev->dev), - i2c_dev); + err = devm_request_threaded_irq(i2c_dev->dev, i2c_dev->irq, + NULL, tegra_i2c_isr, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + dev_name(i2c_dev->dev), i2c_dev); if (err) return err; diff --git a/drivers/i2c/busses/i2c-zx2967.c b/drivers/i2c/busses/i2c-zx2967.c deleted file mode 100644 index 8db9519695a6..000000000000 --- a/drivers/i2c/busses/i2c-zx2967.c +++ /dev/null @@ -1,602 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2017 Sanechips Technology Co., Ltd. - * Copyright 2017 Linaro Ltd. - * - * Author: Baoyou Xie - */ - -#include -#include -#include -#include -#include -#include - -#define REG_CMD 0x04 -#define REG_DEVADDR_H 0x0C -#define REG_DEVADDR_L 0x10 -#define REG_CLK_DIV_FS 0x14 -#define REG_CLK_DIV_HS 0x18 -#define REG_WRCONF 0x1C -#define REG_RDCONF 0x20 -#define REG_DATA 0x24 -#define REG_STAT 0x28 - -#define I2C_STOP 0 -#define I2C_MASTER BIT(0) -#define I2C_ADDR_MODE_TEN BIT(1) -#define I2C_IRQ_MSK_ENABLE BIT(3) -#define I2C_RW_READ BIT(4) -#define I2C_CMB_RW_EN BIT(5) -#define I2C_START BIT(6) - -#define I2C_ADDR_LOW_MASK GENMASK(6, 0) -#define I2C_ADDR_LOW_SHIFT 0 -#define I2C_ADDR_HI_MASK GENMASK(2, 0) -#define I2C_ADDR_HI_SHIFT 7 - -#define I2C_WFIFO_RESET BIT(7) -#define I2C_RFIFO_RESET BIT(7) - -#define I2C_IRQ_ACK_CLEAR BIT(7) -#define I2C_INT_MASK GENMASK(6, 0) - -#define I2C_TRANS_DONE BIT(0) -#define I2C_SR_EDEVICE BIT(1) -#define I2C_SR_EDATA BIT(2) - -#define I2C_FIFO_MAX 16 - -#define I2C_TIMEOUT msecs_to_jiffies(1000) - -#define DEV(i2c) ((i2c)->adap.dev.parent) - -struct zx2967_i2c { - struct i2c_adapter adap; - struct clk *clk; - struct completion complete; - u32 clk_freq; - void __iomem *reg_base; - size_t residue; - int irq; - int msg_rd; - u8 *cur_trans; - u8 access_cnt; - int error; -}; - -static void zx2967_i2c_writel(struct zx2967_i2c *i2c, - u32 val, unsigned long reg) -{ - writel_relaxed(val, i2c->reg_base + reg); -} - -static u32 zx2967_i2c_readl(struct zx2967_i2c *i2c, unsigned long reg) -{ - return readl_relaxed(i2c->reg_base + reg); -} - -static void zx2967_i2c_writesb(struct zx2967_i2c *i2c, - void *data, unsigned long reg, int len) -{ - writesb(i2c->reg_base + reg, data, len); -} - -static void zx2967_i2c_readsb(struct zx2967_i2c *i2c, - void *data, unsigned long reg, int len) -{ - readsb(i2c->reg_base + reg, data, len); -} - -static void zx2967_i2c_start_ctrl(struct zx2967_i2c *i2c) -{ - u32 status; - u32 ctl; - - status = zx2967_i2c_readl(i2c, REG_STAT); - status |= I2C_IRQ_ACK_CLEAR; - zx2967_i2c_writel(i2c, status, REG_STAT); - - ctl = zx2967_i2c_readl(i2c, REG_CMD); - if (i2c->msg_rd) - ctl |= I2C_RW_READ; - else - ctl &= ~I2C_RW_READ; - ctl &= ~I2C_CMB_RW_EN; - ctl |= I2C_START; - zx2967_i2c_writel(i2c, ctl, REG_CMD); -} - -static void zx2967_i2c_flush_fifos(struct zx2967_i2c *i2c) -{ - u32 offset; - u32 val; - - if (i2c->msg_rd) { - offset = REG_RDCONF; - val = I2C_RFIFO_RESET; - } else { - offset = REG_WRCONF; - val = I2C_WFIFO_RESET; - } - - val |= zx2967_i2c_readl(i2c, offset); - zx2967_i2c_writel(i2c, val, offset); -} - -static int zx2967_i2c_empty_rx_fifo(struct zx2967_i2c *i2c, u32 size) -{ - u8 val[I2C_FIFO_MAX] = {0}; - int i; - - if (size > I2C_FIFO_MAX) { - dev_err(DEV(i2c), "fifo size %d over the max value %d\n", - size, I2C_FIFO_MAX); - return -EINVAL; - } - - zx2967_i2c_readsb(i2c, val, REG_DATA, size); - for (i = 0; i < size; i++) { - *i2c->cur_trans++ = val[i]; - i2c->residue--; - } - - barrier(); - - return 0; -} - -static int zx2967_i2c_fill_tx_fifo(struct zx2967_i2c *i2c) -{ - size_t residue = i2c->residue; - u8 *buf = i2c->cur_trans; - - if (residue == 0) { - dev_err(DEV(i2c), "residue is %d\n", (int)residue); - return -EINVAL; - } - - if (residue <= I2C_FIFO_MAX) { - zx2967_i2c_writesb(i2c, buf, REG_DATA, residue); - - /* Again update before writing to FIFO to make sure isr sees. */ - i2c->residue = 0; - i2c->cur_trans = NULL; - } else { - zx2967_i2c_writesb(i2c, buf, REG_DATA, I2C_FIFO_MAX); - i2c->residue -= I2C_FIFO_MAX; - i2c->cur_trans += I2C_FIFO_MAX; - } - - barrier(); - - return 0; -} - -static int zx2967_i2c_reset_hardware(struct zx2967_i2c *i2c) -{ - u32 val; - u32 clk_div; - - val = I2C_MASTER | I2C_IRQ_MSK_ENABLE; - zx2967_i2c_writel(i2c, val, REG_CMD); - - clk_div = clk_get_rate(i2c->clk) / i2c->clk_freq - 1; - zx2967_i2c_writel(i2c, clk_div, REG_CLK_DIV_FS); - zx2967_i2c_writel(i2c, clk_div, REG_CLK_DIV_HS); - - zx2967_i2c_writel(i2c, I2C_FIFO_MAX - 1, REG_WRCONF); - zx2967_i2c_writel(i2c, I2C_FIFO_MAX - 1, REG_RDCONF); - zx2967_i2c_writel(i2c, 1, REG_RDCONF); - - zx2967_i2c_flush_fifos(i2c); - - return 0; -} - -static void zx2967_i2c_isr_clr(struct zx2967_i2c *i2c) -{ - u32 status; - - status = zx2967_i2c_readl(i2c, REG_STAT); - status |= I2C_IRQ_ACK_CLEAR; - zx2967_i2c_writel(i2c, status, REG_STAT); -} - -static irqreturn_t zx2967_i2c_isr(int irq, void *dev_id) -{ - u32 status; - struct zx2967_i2c *i2c = (struct zx2967_i2c *)dev_id; - - status = zx2967_i2c_readl(i2c, REG_STAT) & I2C_INT_MASK; - zx2967_i2c_isr_clr(i2c); - - if (status & I2C_SR_EDEVICE) - i2c->error = -ENXIO; - else if (status & I2C_SR_EDATA) - i2c->error = -EIO; - else if (status & I2C_TRANS_DONE) - i2c->error = 0; - else - goto done; - - complete(&i2c->complete); -done: - return IRQ_HANDLED; -} - -static void zx2967_set_addr(struct zx2967_i2c *i2c, u16 addr) -{ - u16 val; - - val = (addr >> I2C_ADDR_LOW_SHIFT) & I2C_ADDR_LOW_MASK; - zx2967_i2c_writel(i2c, val, REG_DEVADDR_L); - - val = (addr >> I2C_ADDR_HI_SHIFT) & I2C_ADDR_HI_MASK; - zx2967_i2c_writel(i2c, val, REG_DEVADDR_H); - if (val) - val = zx2967_i2c_readl(i2c, REG_CMD) | I2C_ADDR_MODE_TEN; - else - val = zx2967_i2c_readl(i2c, REG_CMD) & ~I2C_ADDR_MODE_TEN; - zx2967_i2c_writel(i2c, val, REG_CMD); -} - -static int zx2967_i2c_xfer_bytes(struct zx2967_i2c *i2c, u32 bytes) -{ - unsigned long time_left; - int rd = i2c->msg_rd; - int ret; - - reinit_completion(&i2c->complete); - - if (rd) { - zx2967_i2c_writel(i2c, bytes - 1, REG_RDCONF); - } else { - ret = zx2967_i2c_fill_tx_fifo(i2c); - if (ret) - return ret; - } - - zx2967_i2c_start_ctrl(i2c); - - time_left = wait_for_completion_timeout(&i2c->complete, - I2C_TIMEOUT); - if (time_left == 0) - return -ETIMEDOUT; - - if (i2c->error) - return i2c->error; - - return rd ? zx2967_i2c_empty_rx_fifo(i2c, bytes) : 0; -} - -static int zx2967_i2c_xfer_msg(struct zx2967_i2c *i2c, - struct i2c_msg *msg) -{ - int ret; - int i; - - zx2967_i2c_flush_fifos(i2c); - - i2c->cur_trans = msg->buf; - i2c->residue = msg->len; - i2c->access_cnt = msg->len / I2C_FIFO_MAX; - i2c->msg_rd = msg->flags & I2C_M_RD; - - for (i = 0; i < i2c->access_cnt; i++) { - ret = zx2967_i2c_xfer_bytes(i2c, I2C_FIFO_MAX); - if (ret) - return ret; - } - - if (i2c->residue > 0) { - ret = zx2967_i2c_xfer_bytes(i2c, i2c->residue); - if (ret) - return ret; - } - - i2c->residue = 0; - i2c->access_cnt = 0; - - return 0; -} - -static int zx2967_i2c_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, int num) -{ - struct zx2967_i2c *i2c = i2c_get_adapdata(adap); - int ret; - int i; - - zx2967_set_addr(i2c, msgs->addr); - - for (i = 0; i < num; i++) { - ret = zx2967_i2c_xfer_msg(i2c, &msgs[i]); - if (ret) - return ret; - } - - return num; -} - -static void -zx2967_smbus_xfer_prepare(struct zx2967_i2c *i2c, u16 addr, - char read_write, u8 command, int size, - union i2c_smbus_data *data) -{ - u32 val; - - val = zx2967_i2c_readl(i2c, REG_RDCONF); - val |= I2C_RFIFO_RESET; - zx2967_i2c_writel(i2c, val, REG_RDCONF); - zx2967_set_addr(i2c, addr); - val = zx2967_i2c_readl(i2c, REG_CMD); - val &= ~I2C_RW_READ; - zx2967_i2c_writel(i2c, val, REG_CMD); - - switch (size) { - case I2C_SMBUS_BYTE: - zx2967_i2c_writel(i2c, command, REG_DATA); - break; - case I2C_SMBUS_BYTE_DATA: - zx2967_i2c_writel(i2c, command, REG_DATA); - if (read_write == I2C_SMBUS_WRITE) - zx2967_i2c_writel(i2c, data->byte, REG_DATA); - break; - case I2C_SMBUS_WORD_DATA: - zx2967_i2c_writel(i2c, command, REG_DATA); - if (read_write == I2C_SMBUS_WRITE) { - zx2967_i2c_writel(i2c, (data->word >> 8), REG_DATA); - zx2967_i2c_writel(i2c, (data->word & 0xff), - REG_DATA); - } - break; - } -} - -static int zx2967_smbus_xfer_read(struct zx2967_i2c *i2c, int size, - union i2c_smbus_data *data) -{ - unsigned long time_left; - u8 buf[2]; - u32 val; - - reinit_completion(&i2c->complete); - - val = zx2967_i2c_readl(i2c, REG_CMD); - val |= I2C_CMB_RW_EN; - zx2967_i2c_writel(i2c, val, REG_CMD); - - val = zx2967_i2c_readl(i2c, REG_CMD); - val |= I2C_START; - zx2967_i2c_writel(i2c, val, REG_CMD); - - time_left = wait_for_completion_timeout(&i2c->complete, - I2C_TIMEOUT); - if (time_left == 0) - return -ETIMEDOUT; - - if (i2c->error) - return i2c->error; - - switch (size) { - case I2C_SMBUS_BYTE: - case I2C_SMBUS_BYTE_DATA: - val = zx2967_i2c_readl(i2c, REG_DATA); - data->byte = val; - break; - case I2C_SMBUS_WORD_DATA: - case I2C_SMBUS_PROC_CALL: - buf[0] = zx2967_i2c_readl(i2c, REG_DATA); - buf[1] = zx2967_i2c_readl(i2c, REG_DATA); - data->word = (buf[0] << 8) | buf[1]; - break; - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static int zx2967_smbus_xfer_write(struct zx2967_i2c *i2c) -{ - unsigned long time_left; - u32 val; - - reinit_completion(&i2c->complete); - val = zx2967_i2c_readl(i2c, REG_CMD); - val |= I2C_START; - zx2967_i2c_writel(i2c, val, REG_CMD); - - time_left = wait_for_completion_timeout(&i2c->complete, - I2C_TIMEOUT); - if (time_left == 0) - return -ETIMEDOUT; - - if (i2c->error) - return i2c->error; - - return 0; -} - -static int zx2967_smbus_xfer(struct i2c_adapter *adap, u16 addr, - unsigned short flags, char read_write, - u8 command, int size, union i2c_smbus_data *data) -{ - struct zx2967_i2c *i2c = i2c_get_adapdata(adap); - - if (size == I2C_SMBUS_QUICK) - read_write = I2C_SMBUS_WRITE; - - switch (size) { - case I2C_SMBUS_QUICK: - case I2C_SMBUS_BYTE: - case I2C_SMBUS_BYTE_DATA: - case I2C_SMBUS_WORD_DATA: - zx2967_smbus_xfer_prepare(i2c, addr, read_write, - command, size, data); - break; - default: - return -EOPNOTSUPP; - } - - if (read_write == I2C_SMBUS_READ) - return zx2967_smbus_xfer_read(i2c, size, data); - - return zx2967_smbus_xfer_write(i2c); -} - -static u32 zx2967_i2c_func(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | - I2C_FUNC_SMBUS_QUICK | - I2C_FUNC_SMBUS_BYTE | - I2C_FUNC_SMBUS_BYTE_DATA | - I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA | - I2C_FUNC_SMBUS_PROC_CALL | - I2C_FUNC_SMBUS_I2C_BLOCK; -} - -static int __maybe_unused zx2967_i2c_suspend(struct device *dev) -{ - struct zx2967_i2c *i2c = dev_get_drvdata(dev); - - i2c_mark_adapter_suspended(&i2c->adap); - clk_disable_unprepare(i2c->clk); - - return 0; -} - -static int __maybe_unused zx2967_i2c_resume(struct device *dev) -{ - struct zx2967_i2c *i2c = dev_get_drvdata(dev); - - clk_prepare_enable(i2c->clk); - i2c_mark_adapter_resumed(&i2c->adap); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(zx2967_i2c_dev_pm_ops, - zx2967_i2c_suspend, zx2967_i2c_resume); - -static const struct i2c_algorithm zx2967_i2c_algo = { - .master_xfer = zx2967_i2c_xfer, - .smbus_xfer = zx2967_smbus_xfer, - .functionality = zx2967_i2c_func, -}; - -static const struct i2c_adapter_quirks zx2967_i2c_quirks = { - .flags = I2C_AQ_NO_ZERO_LEN, -}; - -static const struct of_device_id zx2967_i2c_of_match[] = { - { .compatible = "zte,zx296718-i2c", }, - { }, -}; -MODULE_DEVICE_TABLE(of, zx2967_i2c_of_match); - -static int zx2967_i2c_probe(struct platform_device *pdev) -{ - struct zx2967_i2c *i2c; - void __iomem *reg_base; - struct clk *clk; - int ret; - - i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); - if (!i2c) - return -ENOMEM; - - reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(reg_base)) - return PTR_ERR(reg_base); - - clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(clk)) { - dev_err(&pdev->dev, "missing controller clock"); - return PTR_ERR(clk); - } - - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(&pdev->dev, "failed to enable i2c_clk\n"); - return ret; - } - - ret = device_property_read_u32(&pdev->dev, "clock-frequency", - &i2c->clk_freq); - if (ret) { - dev_err(&pdev->dev, "missing clock-frequency"); - return ret; - } - - ret = platform_get_irq(pdev, 0); - if (ret < 0) - return ret; - - i2c->irq = ret; - i2c->reg_base = reg_base; - i2c->clk = clk; - - init_completion(&i2c->complete); - platform_set_drvdata(pdev, i2c); - - ret = zx2967_i2c_reset_hardware(i2c); - if (ret) { - dev_err(&pdev->dev, "failed to initialize i2c controller\n"); - goto err_clk_unprepare; - } - - ret = devm_request_irq(&pdev->dev, i2c->irq, - zx2967_i2c_isr, 0, dev_name(&pdev->dev), i2c); - if (ret) { - dev_err(&pdev->dev, "failed to request irq %i\n", i2c->irq); - goto err_clk_unprepare; - } - - i2c_set_adapdata(&i2c->adap, i2c); - strlcpy(i2c->adap.name, "zx2967 i2c adapter", - sizeof(i2c->adap.name)); - i2c->adap.algo = &zx2967_i2c_algo; - i2c->adap.quirks = &zx2967_i2c_quirks; - i2c->adap.nr = pdev->id; - i2c->adap.dev.parent = &pdev->dev; - i2c->adap.dev.of_node = pdev->dev.of_node; - - ret = i2c_add_numbered_adapter(&i2c->adap); - if (ret) - goto err_clk_unprepare; - - return 0; - -err_clk_unprepare: - clk_disable_unprepare(i2c->clk); - return ret; -} - -static int zx2967_i2c_remove(struct platform_device *pdev) -{ - struct zx2967_i2c *i2c = platform_get_drvdata(pdev); - - i2c_del_adapter(&i2c->adap); - clk_disable_unprepare(i2c->clk); - - return 0; -} - -static struct platform_driver zx2967_i2c_driver = { - .probe = zx2967_i2c_probe, - .remove = zx2967_i2c_remove, - .driver = { - .name = "zx2967_i2c", - .of_match_table = zx2967_i2c_of_match, - .pm = &zx2967_i2c_dev_pm_ops, - }, -}; -module_platform_driver(zx2967_i2c_driver); - -MODULE_AUTHOR("Baoyou Xie "); -MODULE_DESCRIPTION("ZTE ZX2967 I2C Bus Controller driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 37c510d9347a..8ceaa88dd78f 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -225,12 +225,8 @@ static void i2c_acpi_register_device(struct i2c_adapter *adapter, adev->power.flags.ignore_parent = true; acpi_device_set_enumerated(adev); - if (IS_ERR(i2c_new_client_device(adapter, info))) { + if (IS_ERR(i2c_new_client_device(adapter, info))) adev->power.flags.ignore_parent = false; - dev_err(&adapter->dev, - "failed to add I2C device %s from ACPI\n", - dev_name(&adev->dev)); - } } static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level, diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c index f5c9787992e9..d2d32c0fd8c3 100644 --- a/drivers/i2c/i2c-core-smbus.c +++ b/drivers/i2c/i2c-core-smbus.c @@ -323,8 +323,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, */ unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; - int num = read_write == I2C_SMBUS_READ ? 2 : 1; - int i; + int nmsgs = read_write == I2C_SMBUS_READ ? 2 : 1; u8 partial_pec = 0; int status; struct i2c_msg msg[2] = { @@ -340,6 +339,8 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, .buf = msgbuf1, }, }; + bool wants_pec = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK + && size != I2C_SMBUS_I2C_BLOCK_DATA); msgbuf0[0] = command; switch (size) { @@ -348,13 +349,13 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, /* Special case: The read/write field is used as data */ msg[0].flags = flags | (read_write == I2C_SMBUS_READ ? I2C_M_RD : 0); - num = 1; + nmsgs = 1; break; case I2C_SMBUS_BYTE: if (read_write == I2C_SMBUS_READ) { /* Special case: only a read! */ msg[0].flags = I2C_M_RD | flags; - num = 1; + nmsgs = 1; } break; case I2C_SMBUS_BYTE_DATA: @@ -375,7 +376,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, } break; case I2C_SMBUS_PROC_CALL: - num = 2; /* Special case */ + nmsgs = 2; /* Special case */ read_write = I2C_SMBUS_READ; msg[0].len = 3; msg[1].len = 2; @@ -398,12 +399,11 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, } i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i < msg[0].len; i++) - msg[0].buf[i] = data->block[i - 1]; + memcpy(msg[0].buf + 1, data->block, msg[0].len - 1); } break; case I2C_SMBUS_BLOCK_PROC_CALL: - num = 2; /* Another special case */ + nmsgs = 2; /* Another special case */ read_write = I2C_SMBUS_READ; if (data->block[0] > I2C_SMBUS_BLOCK_MAX) { dev_err(&adapter->dev, @@ -414,8 +414,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, msg[0].len = data->block[0] + 2; i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i < msg[0].len; i++) - msg[0].buf[i] = data->block[i - 1]; + memcpy(msg[0].buf + 1, data->block, msg[0].len - 1); msg[1].flags |= I2C_M_RECV_LEN; msg[1].len = 1; /* block length will be added by @@ -437,8 +436,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, msg[0].len = data->block[0] + 1; i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i <= data->block[0]; i++) - msg[0].buf[i] = data->block[i]; + memcpy(msg[0].buf + 1, data->block + 1, data->block[0]); } break; default: @@ -446,33 +444,31 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, return -EOPNOTSUPP; } - i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK - && size != I2C_SMBUS_I2C_BLOCK_DATA); - if (i) { + if (wants_pec) { /* Compute PEC if first message is a write */ if (!(msg[0].flags & I2C_M_RD)) { - if (num == 1) /* Write only */ + if (nmsgs == 1) /* Write only */ i2c_smbus_add_pec(&msg[0]); else /* Write followed by read */ partial_pec = i2c_smbus_msg_pec(0, &msg[0]); } /* Ask for PEC if last message is a read */ - if (msg[num-1].flags & I2C_M_RD) - msg[num-1].len++; + if (msg[nmsgs - 1].flags & I2C_M_RD) + msg[nmsgs - 1].len++; } - status = __i2c_transfer(adapter, msg, num); + status = __i2c_transfer(adapter, msg, nmsgs); if (status < 0) goto cleanup; - if (status != num) { + if (status != nmsgs) { status = -EIO; goto cleanup; } status = 0; /* Check PEC if last message is a read */ - if (i && (msg[num-1].flags & I2C_M_RD)) { - status = i2c_smbus_check_pec(partial_pec, &msg[num-1]); + if (wants_pec && (msg[nmsgs - 1].flags & I2C_M_RD)) { + status = i2c_smbus_check_pec(partial_pec, &msg[nmsgs - 1]); if (status < 0) goto cleanup; } @@ -490,8 +486,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, data->word = msgbuf1[0] | (msgbuf1[1] << 8); break; case I2C_SMBUS_I2C_BLOCK_DATA: - for (i = 0; i < data->block[0]; i++) - data->block[i + 1] = msg[1].buf[i]; + memcpy(data->block + 1, msg[1].buf, data->block[0]); break; case I2C_SMBUS_BLOCK_DATA: case I2C_SMBUS_BLOCK_PROC_CALL: @@ -502,8 +497,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, status = -EPROTO; goto cleanup; } - for (i = 0; i < msg[1].buf[0] + 1; i++) - data->block[i] = msg[1].buf[i]; + memcpy(data->block, msg[1].buf, msg[1].buf[0] + 1); break; } diff --git a/drivers/i2c/i2c-slave-testunit.c b/drivers/i2c/i2c-slave-testunit.c index c288102de324..56dae08dfd48 100644 --- a/drivers/i2c/i2c-slave-testunit.c +++ b/drivers/i2c/i2c-slave-testunit.c @@ -19,6 +19,7 @@ enum testunit_cmds { TU_CMD_READ_BYTES = 1, /* save 0 for ABORT, RESET or similar */ TU_CMD_HOST_NOTIFY, + TU_CMD_SMBUS_BLOCK_PROC_CALL, TU_NUM_CMDS }; @@ -88,6 +89,8 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client, enum i2c_slave_event event, u8 *val) { struct testunit_data *tu = i2c_get_clientdata(client); + bool is_proc_call = tu->reg_idx == 3 && tu->regs[TU_REG_DATAL] == 1 && + tu->regs[TU_REG_CMD] == TU_CMD_SMBUS_BLOCK_PROC_CALL; int ret = 0; switch (event) { @@ -118,12 +121,17 @@ static int i2c_slave_testunit_slave_cb(struct i2c_client *client, fallthrough; case I2C_SLAVE_WRITE_REQUESTED: + memset(tu->regs, 0, TU_NUM_REGS); tu->reg_idx = 0; break; - case I2C_SLAVE_READ_REQUESTED: case I2C_SLAVE_READ_PROCESSED: - *val = TU_CUR_VERSION; + if (is_proc_call && tu->regs[TU_REG_DATAH]) + tu->regs[TU_REG_DATAH]--; + fallthrough; + + case I2C_SLAVE_READ_REQUESTED: + *val = is_proc_call ? tu->regs[TU_REG_DATAH] : TU_CUR_VERSION; break; } diff --git a/drivers/i2c/i2c-stub.c b/drivers/i2c/i2c-stub.c index 537a598e22db..d642cad219d9 100644 --- a/drivers/i2c/i2c-stub.c +++ b/drivers/i2c/i2c-stub.c @@ -7,7 +7,6 @@ */ -#define DEBUG 1 #define pr_fmt(fmt) "i2c-stub: " fmt #include diff --git a/drivers/i2c/muxes/i2c-mux-gpio.c b/drivers/i2c/muxes/i2c-mux-gpio.c index 4effe563e9e8..bac415a52b78 100644 --- a/drivers/i2c/muxes/i2c-mux-gpio.c +++ b/drivers/i2c/muxes/i2c-mux-gpio.c @@ -49,60 +49,112 @@ static int i2c_mux_gpio_deselect(struct i2c_mux_core *muxc, u32 chan) return 0; } -#ifdef CONFIG_OF -static int i2c_mux_gpio_probe_dt(struct gpiomux *mux, - struct platform_device *pdev) +#ifdef CONFIG_ACPI + +static int i2c_mux_gpio_get_acpi_adr(struct device *dev, + struct fwnode_handle *fwdev, + unsigned int *adr) + { - struct device_node *np = pdev->dev.of_node; - struct device_node *adapter_np, *child; - struct i2c_adapter *adapter; - unsigned *values; - int i = 0; + unsigned long long adr64; + acpi_status status; - if (!np) - return -ENODEV; + status = acpi_evaluate_integer(ACPI_HANDLE_FWNODE(fwdev), + METHOD_NAME__ADR, + NULL, &adr64); - adapter_np = of_parse_phandle(np, "i2c-parent", 0); - if (!adapter_np) { - dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); - return -ENODEV; + if (!ACPI_SUCCESS(status)) { + dev_err(dev, "Cannot get address\n"); + return -EINVAL; } - adapter = of_find_i2c_adapter_by_node(adapter_np); - of_node_put(adapter_np); + + *adr = adr64; + if (*adr != adr64) { + dev_err(dev, "Address out of range\n"); + return -ERANGE; + } + + return 0; +} + +#else + +static int i2c_mux_gpio_get_acpi_adr(struct device *dev, + struct fwnode_handle *fwdev, + unsigned int *adr) +{ + return -EINVAL; +} + +#endif + +static int i2c_mux_gpio_probe_fw(struct gpiomux *mux, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *adapter_np; + struct i2c_adapter *adapter = NULL; + struct fwnode_handle *child; + unsigned *values; + int rc, i = 0; + + if (is_of_node(dev->fwnode)) { + if (!np) + return -ENODEV; + + adapter_np = of_parse_phandle(np, "i2c-parent", 0); + if (!adapter_np) { + dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); + return -ENODEV; + } + adapter = of_find_i2c_adapter_by_node(adapter_np); + of_node_put(adapter_np); + + } else if (is_acpi_node(dev->fwnode)) { + /* + * In ACPI land the mux should be a direct child of the i2c + * bus it muxes. + */ + acpi_handle dev_handle = ACPI_HANDLE(dev->parent); + + adapter = i2c_acpi_find_adapter_by_handle(dev_handle); + } + if (!adapter) return -EPROBE_DEFER; mux->data.parent = i2c_adapter_id(adapter); put_device(&adapter->dev); - mux->data.n_values = of_get_child_count(np); - - values = devm_kcalloc(&pdev->dev, + mux->data.n_values = device_get_child_node_count(dev); + values = devm_kcalloc(dev, mux->data.n_values, sizeof(*mux->data.values), GFP_KERNEL); if (!values) { - dev_err(&pdev->dev, "Cannot allocate values array"); + dev_err(dev, "Cannot allocate values array"); return -ENOMEM; } - for_each_child_of_node(np, child) { - of_property_read_u32(child, "reg", values + i); + device_for_each_child_node(dev, child) { + if (is_of_node(child)) { + fwnode_property_read_u32(child, "reg", values + i); + + } else if (is_acpi_node(child)) { + rc = i2c_mux_gpio_get_acpi_adr(dev, child, values + i); + if (rc) + return rc; + } + i++; } mux->data.values = values; - if (of_property_read_u32(np, "idle-state", &mux->data.idle)) + if (fwnode_property_read_u32(dev->fwnode, "idle-state", &mux->data.idle)) mux->data.idle = I2C_MUX_GPIO_NO_IDLE; return 0; } -#else -static int i2c_mux_gpio_probe_dt(struct gpiomux *mux, - struct platform_device *pdev) -{ - return 0; -} -#endif static int i2c_mux_gpio_probe(struct platform_device *pdev) { @@ -118,7 +170,7 @@ static int i2c_mux_gpio_probe(struct platform_device *pdev) return -ENOMEM; if (!dev_get_platdata(&pdev->dev)) { - ret = i2c_mux_gpio_probe_dt(mux, pdev); + ret = i2c_mux_gpio_probe_fw(mux, pdev); if (ret < 0) return ret; } else { diff --git a/drivers/i2c/muxes/i2c-mux-mlxcpld.c b/drivers/i2c/muxes/i2c-mux-mlxcpld.c index 5ed55ca4fe93..1a879f6a31ef 100644 --- a/drivers/i2c/muxes/i2c-mux-mlxcpld.c +++ b/drivers/i2c/muxes/i2c-mux-mlxcpld.c @@ -1,35 +1,8 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* - * drivers/i2c/muxes/i2c-mux-mlxcpld.c - * Copyright (c) 2016 Mellanox Technologies. All rights reserved. - * Copyright (c) 2016 Michael Shych + * Mellanox i2c mux driver * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * Alternatively, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2 as published by the Free - * Software Foundation. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * Copyright (C) 2016-2020 Mellanox Technologies */ #include @@ -38,19 +11,19 @@ #include #include #include -#include +#include #include #include -#define CPLD_MUX_MAX_NCHANS 8 - /* mlxcpld_mux - mux control structure: - * @last_chan - last register value + * @last_val - last selected register value or -1 if mux deselected * @client - I2C device client + * @pdata: platform data */ struct mlxcpld_mux { - u8 last_chan; + int last_val; struct i2c_client *client; + struct mlxcpld_mux_plat_data pdata; }; /* MUX logic description. @@ -81,37 +54,50 @@ struct mlxcpld_mux { * */ -static const struct i2c_device_id mlxcpld_mux_id[] = { - { "mlxcpld_mux_module", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); - /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() * for this as they will try to lock adapter a second time. */ static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, - struct i2c_client *client, u8 val) + struct mlxcpld_mux *mux, u32 val) { - struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); - union i2c_smbus_data data = { .byte = val }; + struct i2c_client *client = mux->client; + union i2c_smbus_data data; + struct i2c_msg msg; + u8 buf[3]; - return __i2c_smbus_xfer(adap, client->addr, client->flags, - I2C_SMBUS_WRITE, pdata->sel_reg_addr, - I2C_SMBUS_BYTE_DATA, &data); + switch (mux->pdata.reg_size) { + case 1: + data.byte = val; + return __i2c_smbus_xfer(adap, client->addr, client->flags, + I2C_SMBUS_WRITE, mux->pdata.sel_reg_addr, + I2C_SMBUS_BYTE_DATA, &data); + case 2: + buf[0] = mux->pdata.sel_reg_addr >> 8; + buf[1] = mux->pdata.sel_reg_addr; + buf[2] = val; + msg.addr = client->addr; + msg.buf = buf; + msg.len = mux->pdata.reg_size + 1; + msg.flags = 0; + return __i2c_transfer(adap, &msg, 1); + default: + return -EINVAL; + } } static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) { - struct mlxcpld_mux *data = i2c_mux_priv(muxc); - struct i2c_client *client = data->client; - u8 regval = chan + 1; + struct mlxcpld_mux *mux = i2c_mux_priv(muxc); + u32 regval = chan; int err = 0; + if (mux->pdata.reg_size == 1) + regval += 1; + /* Only select the channel if its different from the last channel */ - if (data->last_chan != regval) { - err = mlxcpld_mux_reg_write(muxc->parent, client, regval); - data->last_chan = err < 0 ? 0 : regval; + if (mux->last_val != regval) { + err = mlxcpld_mux_reg_write(muxc->parent, mux, regval); + mux->last_val = err < 0 ? -1 : regval; } return err; @@ -119,56 +105,64 @@ static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) { - struct mlxcpld_mux *data = i2c_mux_priv(muxc); - struct i2c_client *client = data->client; + struct mlxcpld_mux *mux = i2c_mux_priv(muxc); /* Deselect active channel */ - data->last_chan = 0; + mux->last_val = -1; - return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); + return mlxcpld_mux_reg_write(muxc->parent, mux, 0); } /* Probe/reomove functions */ -static int mlxcpld_mux_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int mlxcpld_mux_probe(struct platform_device *pdev) { - struct i2c_adapter *adap = client->adapter; - struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); + struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&pdev->dev); + struct i2c_client *client = to_i2c_client(pdev->dev.parent); struct i2c_mux_core *muxc; - int num, force; struct mlxcpld_mux *data; - int err; + int num, err; + u32 func; if (!pdata) return -EINVAL; - if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + switch (pdata->reg_size) { + case 1: + func = I2C_FUNC_SMBUS_WRITE_BYTE_DATA; + break; + case 2: + func = I2C_FUNC_I2C; + break; + default: + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, func)) return -ENODEV; - muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, + muxc = i2c_mux_alloc(client->adapter, &pdev->dev, pdata->num_adaps, sizeof(*data), 0, mlxcpld_mux_select_chan, mlxcpld_mux_deselect); if (!muxc) return -ENOMEM; + platform_set_drvdata(pdev, muxc); data = i2c_mux_priv(muxc); - i2c_set_clientdata(client, muxc); data->client = client; - data->last_chan = 0; /* force the first selection */ + memcpy(&data->pdata, pdata, sizeof(*pdata)); + data->last_val = -1; /* force the first selection */ /* Create an adapter for each channel. */ - for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { - if (num >= pdata->num_adaps) - /* discard unconfigured channels */ - break; - - force = pdata->adap_ids[num]; - - err = i2c_mux_add_adapter(muxc, force, num, 0); + for (num = 0; num < pdata->num_adaps; num++) { + err = i2c_mux_add_adapter(muxc, 0, pdata->chan_ids[num], 0); if (err) goto virt_reg_failed; } + /* Notify caller when all channels' adapters are created. */ + if (pdata->completion_notify) + pdata->completion_notify(pdata->handle, muxc->parent, muxc->adapter); + return 0; virt_reg_failed: @@ -176,24 +170,23 @@ static int mlxcpld_mux_probe(struct i2c_client *client, return err; } -static int mlxcpld_mux_remove(struct i2c_client *client) +static int mlxcpld_mux_remove(struct platform_device *pdev) { - struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); i2c_mux_del_adapters(muxc); return 0; } -static struct i2c_driver mlxcpld_mux_driver = { - .driver = { - .name = "mlxcpld-mux", +static struct platform_driver mlxcpld_mux_driver = { + .driver = { + .name = "i2c-mux-mlxcpld", }, - .probe = mlxcpld_mux_probe, - .remove = mlxcpld_mux_remove, - .id_table = mlxcpld_mux_id, + .probe = mlxcpld_mux_probe, + .remove = mlxcpld_mux_remove, }; -module_i2c_driver(mlxcpld_mux_driver); +module_platform_driver(mlxcpld_mux_driver); MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c index 8682cf1e213f..14723896aab2 100644 --- a/drivers/iio/adc/qcom-vadc-common.c +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -368,6 +368,28 @@ static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, return 0; } +static s32 qcom_vadc_map_temp_voltage(const struct vadc_map_pt *pts, + u32 tablesize, int input) +{ + u32 i = 0; + + /* + * Table must be sorted, find the interval of 'y' which contains value + * 'input' and map it to proper 'x' value + */ + while (i < tablesize && pts[i].y < input) + i++; + + if (i == 0) + return pts[0].x; + if (i == tablesize) + return pts[tablesize - 1].x; + + /* interpolate linearly */ + return fixp_linear_interpolate(pts[i - 1].y, pts[i - 1].x, + pts[i].y, pts[i].x, input); +} + static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph, u16 adc_code, bool absolute, @@ -463,6 +485,21 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph, return 0; } +/* convert voltage to ADC code, using 1.875V reference */ +static u16 qcom_vadc_scale_voltage_code(s32 voltage, + const struct vadc_prescale_ratio *prescale, + const u32 full_scale_code_volt, + unsigned int factor) +{ + s64 volt = voltage; + s64 adc_vdd_ref_mv = 1875; /* reference voltage */ + + volt *= prescale->num * factor * full_scale_code_volt; + volt = div64_s64(volt, (s64)prescale->den * adc_vdd_ref_mv * 1000); + + return volt; +} + static int qcom_vadc_scale_code_voltage_factor(u16 adc_code, const struct vadc_prescale_ratio *prescale, const struct adc5_data *data, @@ -627,6 +664,19 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, } EXPORT_SYMBOL(qcom_vadc_scale); +u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio, + u32 full_scale_code_volt, int temp) +{ + const struct vadc_prescale_ratio *prescale = &adc5_prescale_ratios[prescale_ratio]; + s32 voltage; + + voltage = qcom_vadc_map_temp_voltage(adcmap_100k_104ef_104fb_1875_vref, + ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref), + temp); + return qcom_vadc_scale_voltage_code(voltage, prescale, full_scale_code_volt, 1000); +} +EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale); + int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype, unsigned int prescale_ratio, const struct adc5_data *data, diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index bdfce7b15621..40c723e9a852 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2085,6 +2085,17 @@ config MFD_KHADAS_MCU additional drivers must be enabled in order to use the functionality of the device. +config MFD_ACER_A500_EC + tristate "Support for Acer Iconia Tab A500 Embedded Controller" + depends on I2C + depends on (ARCH_TEGRA_2x_SOC && OF) || COMPILE_TEST + select MFD_CORE + select REGMAP + help + Support for Embedded Controller found on Acer Iconia Tab A500. + The controller itself is ENE KB930, it is running firmware + customized for the specific needs of the Acer A500 hardware. + menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100 @@ -2129,7 +2140,7 @@ config RAVE_SP_CORE device found on several devices in RAVE line of hardware. config SGI_MFD_IOC3 - tristate "SGI IOC3 core driver" + bool "SGI IOC3 core driver" depends on PCI && MIPS && 64BIT select MFD_CORE help diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 14fdb188af02..025543418835 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -264,6 +264,7 @@ obj-$(CONFIG_MFD_ROHM_BD71828) += rohm-bd71828.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o obj-$(CONFIG_MFD_STMFX) += stmfx.o obj-$(CONFIG_MFD_KHADAS_MCU) += khadas-mcu.o +obj-$(CONFIG_MFD_ACER_A500_EC) += acer-ec-a500.o obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index a3bac9da8cbb..a9037911162b 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -610,52 +609,52 @@ int ab8500_suspend(struct ab8500 *ab8500) } static const struct mfd_cell ab8500_bm_devs[] = { - OF_MFD_CELL("ab8500-charger", NULL, &ab8500_bm_data, + MFD_CELL_OF("ab8500-charger", NULL, &ab8500_bm_data, sizeof(ab8500_bm_data), 0, "stericsson,ab8500-charger"), - OF_MFD_CELL("ab8500-btemp", NULL, &ab8500_bm_data, + MFD_CELL_OF("ab8500-btemp", NULL, &ab8500_bm_data, sizeof(ab8500_bm_data), 0, "stericsson,ab8500-btemp"), - OF_MFD_CELL("ab8500-fg", NULL, &ab8500_bm_data, + MFD_CELL_OF("ab8500-fg", NULL, &ab8500_bm_data, sizeof(ab8500_bm_data), 0, "stericsson,ab8500-fg"), - OF_MFD_CELL("ab8500-chargalg", NULL, &ab8500_bm_data, + MFD_CELL_OF("ab8500-chargalg", NULL, &ab8500_bm_data, sizeof(ab8500_bm_data), 0, "stericsson,ab8500-chargalg"), }; static const struct mfd_cell ab8500_devs[] = { #ifdef CONFIG_DEBUG_FS - OF_MFD_CELL("ab8500-debug", + MFD_CELL_OF("ab8500-debug", NULL, NULL, 0, 0, "stericsson,ab8500-debug"), #endif - OF_MFD_CELL("ab8500-sysctrl", + MFD_CELL_OF("ab8500-sysctrl", NULL, NULL, 0, 0, "stericsson,ab8500-sysctrl"), - OF_MFD_CELL("ab8500-ext-regulator", + MFD_CELL_OF("ab8500-ext-regulator", NULL, NULL, 0, 0, "stericsson,ab8500-ext-regulator"), - OF_MFD_CELL("ab8500-regulator", + MFD_CELL_OF("ab8500-regulator", NULL, NULL, 0, 0, "stericsson,ab8500-regulator"), - OF_MFD_CELL("ab8500-clk", + MFD_CELL_OF("ab8500-clk", NULL, NULL, 0, 0, "stericsson,ab8500-clk"), - OF_MFD_CELL("ab8500-gpadc", + MFD_CELL_OF("ab8500-gpadc", NULL, NULL, 0, 0, "stericsson,ab8500-gpadc"), - OF_MFD_CELL("ab8500-rtc", + MFD_CELL_OF("ab8500-rtc", NULL, NULL, 0, 0, "stericsson,ab8500-rtc"), - OF_MFD_CELL("ab8500-acc-det", + MFD_CELL_OF("ab8500-acc-det", NULL, NULL, 0, 0, "stericsson,ab8500-acc-det"), - OF_MFD_CELL("ab8500-poweron-key", + MFD_CELL_OF("ab8500-poweron-key", NULL, NULL, 0, 0, "stericsson,ab8500-poweron-key"), - OF_MFD_CELL("ab8500-pwm", + MFD_CELL_OF("ab8500-pwm", NULL, NULL, 0, 1, "stericsson,ab8500-pwm"), - OF_MFD_CELL("ab8500-pwm", + MFD_CELL_OF("ab8500-pwm", NULL, NULL, 0, 2, "stericsson,ab8500-pwm"), - OF_MFD_CELL("ab8500-pwm", + MFD_CELL_OF("ab8500-pwm", NULL, NULL, 0, 3, "stericsson,ab8500-pwm"), - OF_MFD_CELL("ab8500-denc", + MFD_CELL_OF("ab8500-denc", NULL, NULL, 0, 0, "stericsson,ab8500-denc"), - OF_MFD_CELL("pinctrl-ab8500", + MFD_CELL_OF("pinctrl-ab8500", NULL, NULL, 0, 0, "stericsson,ab8500-gpio"), - OF_MFD_CELL("abx500-temp", + MFD_CELL_OF("abx500-temp", NULL, NULL, 0, 0, "stericsson,abx500-temp"), - OF_MFD_CELL("ab8500-usb", + MFD_CELL_OF("ab8500-usb", NULL, NULL, 0, 0, "stericsson,ab8500-usb"), - OF_MFD_CELL("ab8500-codec", + MFD_CELL_OF("ab8500-codec", NULL, NULL, 0, 0, "stericsson,ab8500-codec"), }; diff --git a/drivers/mfd/acer-ec-a500.c b/drivers/mfd/acer-ec-a500.c new file mode 100644 index 000000000000..80c2fdd14fc4 --- /dev/null +++ b/drivers/mfd/acer-ec-a500.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Acer Iconia Tab A500 Embedded Controller Driver + * + * Copyright 2020 GRATE-driver project + */ + +#include +#include +#include +#include +#include +#include +#include + +#define A500_EC_I2C_ERR_TIMEOUT 500 +#define A500_EC_POWER_CMD_TIMEOUT 1000 + +/* + * Controller's firmware expects specific command opcodes to be used for the + * corresponding registers. Unsupported commands are skipped by the firmware. + */ +#define CMD_SHUTDOWN 0x0 +#define CMD_WARM_REBOOT 0x0 +#define CMD_COLD_REBOOT 0x1 + +enum { + REG_CURRENT_NOW = 0x03, + REG_SHUTDOWN = 0x52, + REG_WARM_REBOOT = 0x54, + REG_COLD_REBOOT = 0x55, +}; + +static struct i2c_client *a500_ec_client_pm_off; + +static int a500_ec_read(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_sizel) +{ + struct i2c_client *client = context; + unsigned int reg, retries = 5; + u16 *ret_val = val_buf; + s32 ret = 0; + + reg = *(u8 *)reg_buf; + + while (retries-- > 0) { + ret = i2c_smbus_read_word_data(client, reg); + if (ret >= 0) + break; + + msleep(A500_EC_I2C_ERR_TIMEOUT); + } + + if (ret < 0) { + dev_err(&client->dev, "read 0x%x failed: %d\n", reg, ret); + return ret; + } + + *ret_val = ret; + + if (reg == REG_CURRENT_NOW) + fsleep(10000); + + return 0; +} + +static int a500_ec_write(void *context, const void *data, size_t count) +{ + struct i2c_client *client = context; + unsigned int reg, val, retries = 5; + s32 ret = 0; + + reg = *(u8 *)(data + 0); + val = *(u16 *)(data + 1); + + while (retries-- > 0) { + ret = i2c_smbus_write_word_data(client, reg, val); + if (ret >= 0) + break; + + msleep(A500_EC_I2C_ERR_TIMEOUT); + } + + if (ret < 0) { + dev_err(&client->dev, "write 0x%x failed: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static const struct regmap_config a500_ec_regmap_config = { + .name = "KB930", + .reg_bits = 8, + .val_bits = 16, + .max_register = 0xff, +}; + +static const struct regmap_bus a500_ec_regmap_bus = { + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, + .write = a500_ec_write, + .read = a500_ec_read, + .max_raw_read = 2, +}; + +static void a500_ec_poweroff(void) +{ + i2c_smbus_write_word_data(a500_ec_client_pm_off, + REG_SHUTDOWN, CMD_SHUTDOWN); + + mdelay(A500_EC_POWER_CMD_TIMEOUT); +} + +static int a500_ec_restart_notify(struct notifier_block *this, + unsigned long reboot_mode, void *data) +{ + if (reboot_mode == REBOOT_WARM) + i2c_smbus_write_word_data(a500_ec_client_pm_off, + REG_WARM_REBOOT, CMD_WARM_REBOOT); + else + i2c_smbus_write_word_data(a500_ec_client_pm_off, + REG_COLD_REBOOT, CMD_COLD_REBOOT); + + mdelay(A500_EC_POWER_CMD_TIMEOUT); + + return NOTIFY_DONE; +} + +static struct notifier_block a500_ec_restart_handler = { + .notifier_call = a500_ec_restart_notify, + .priority = 200, +}; + +static const struct mfd_cell a500_ec_cells[] = { + { .name = "acer-a500-iconia-battery", }, + { .name = "acer-a500-iconia-leds", }, +}; + +static int a500_ec_probe(struct i2c_client *client) +{ + struct regmap *regmap; + int err; + + regmap = devm_regmap_init(&client->dev, &a500_ec_regmap_bus, + client, &a500_ec_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + err = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + a500_ec_cells, ARRAY_SIZE(a500_ec_cells), + NULL, 0, NULL); + if (err) { + dev_err(&client->dev, "failed to add sub-devices: %d\n", err); + return err; + } + + if (of_device_is_system_power_controller(client->dev.of_node)) { + a500_ec_client_pm_off = client; + + err = register_restart_handler(&a500_ec_restart_handler); + if (err) + return err; + + if (!pm_power_off) + pm_power_off = a500_ec_poweroff; + } + + return 0; +} + +static int a500_ec_remove(struct i2c_client *client) +{ + if (of_device_is_system_power_controller(client->dev.of_node)) { + if (pm_power_off == a500_ec_poweroff) + pm_power_off = NULL; + + unregister_restart_handler(&a500_ec_restart_handler); + } + + return 0; +} + +static const struct of_device_id a500_ec_match[] = { + { .compatible = "acer,a500-iconia-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, a500_ec_match); + +static struct i2c_driver a500_ec_driver = { + .driver = { + .name = "acer-a500-embedded-controller", + .of_match_table = a500_ec_match, + }, + .probe_new = a500_ec_probe, + .remove = a500_ec_remove, +}; +module_i2c_driver(a500_ec_driver); + +MODULE_DESCRIPTION("Acer Iconia Tab A500 Embedded Controller driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/altera-sysmgr.c b/drivers/mfd/altera-sysmgr.c index 193a96c8b1ea..20cb294c7512 100644 --- a/drivers/mfd/altera-sysmgr.c +++ b/drivers/mfd/altera-sysmgr.c @@ -145,7 +145,8 @@ static int sysmgr_probe(struct platform_device *pdev) sysmgr_config.reg_write = s10_protected_reg_write; /* Need physical address for SMCC call */ - regmap = devm_regmap_init(dev, NULL, (void *)res->start, + regmap = devm_regmap_init(dev, NULL, + (void *)(uintptr_t)res->start, &sysmgr_config); } else { base = devm_ioremap(dev, res->start, resource_size(res)); diff --git a/drivers/mfd/bd9571mwv.c b/drivers/mfd/bd9571mwv.c index fab3cdc27ed6..e15b1acfb063 100644 --- a/drivers/mfd/bd9571mwv.c +++ b/drivers/mfd/bd9571mwv.c @@ -1,16 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * ROHM BD9571MWV-M MFD driver + * ROHM BD9571MWV-M and BD9574MVF-M core driver * * Copyright (C) 2017 Marek Vasut - * - * 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 "as is" WITHOUT ANY WARRANTY of any - * kind, whether expressed or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License version 2 for more details. + * Copyright (C) 2020 Renesas Electronics Corporation * * Based on the TPS65086 driver */ @@ -18,6 +11,7 @@ #include #include #include +#include #include #include @@ -110,13 +104,78 @@ static struct regmap_irq_chip bd9571mwv_irq_chip = { .num_irqs = ARRAY_SIZE(bd9571mwv_irqs), }; -static int bd9571mwv_identify(struct bd9571mwv *bd) +static const struct mfd_cell bd9574mwf_cells[] = { + { .name = "bd9574mwf-regulator", }, + { .name = "bd9574mwf-gpio", }, +}; + +static const struct regmap_range bd9574mwf_readable_yes_ranges[] = { + regmap_reg_range(BD9571MWV_VENDOR_CODE, BD9571MWV_PRODUCT_REVISION), + regmap_reg_range(BD9571MWV_BKUP_MODE_CNT, BD9571MWV_BKUP_MODE_CNT), + regmap_reg_range(BD9571MWV_DVFS_VINIT, BD9571MWV_DVFS_SETVMAX), + regmap_reg_range(BD9571MWV_DVFS_SETVID, BD9571MWV_DVFS_MONIVDAC), + regmap_reg_range(BD9571MWV_GPIO_IN, BD9571MWV_GPIO_IN), + regmap_reg_range(BD9571MWV_GPIO_INT, BD9571MWV_GPIO_INTMASK), + regmap_reg_range(BD9571MWV_INT_INTREQ, BD9571MWV_INT_INTMASK), +}; + +static const struct regmap_access_table bd9574mwf_readable_table = { + .yes_ranges = bd9574mwf_readable_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(bd9574mwf_readable_yes_ranges), +}; + +static const struct regmap_range bd9574mwf_writable_yes_ranges[] = { + regmap_reg_range(BD9571MWV_BKUP_MODE_CNT, BD9571MWV_BKUP_MODE_CNT), + regmap_reg_range(BD9571MWV_DVFS_SETVID, BD9571MWV_DVFS_SETVID), + regmap_reg_range(BD9571MWV_GPIO_DIR, BD9571MWV_GPIO_OUT), + regmap_reg_range(BD9571MWV_GPIO_INT_SET, BD9571MWV_GPIO_INTMASK), + regmap_reg_range(BD9571MWV_INT_INTREQ, BD9571MWV_INT_INTMASK), +}; + +static const struct regmap_access_table bd9574mwf_writable_table = { + .yes_ranges = bd9574mwf_writable_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(bd9574mwf_writable_yes_ranges), +}; + +static const struct regmap_range bd9574mwf_volatile_yes_ranges[] = { + regmap_reg_range(BD9571MWV_DVFS_MONIVDAC, BD9571MWV_DVFS_MONIVDAC), + regmap_reg_range(BD9571MWV_GPIO_IN, BD9571MWV_GPIO_IN), + regmap_reg_range(BD9571MWV_GPIO_INT, BD9571MWV_GPIO_INT), + regmap_reg_range(BD9571MWV_INT_INTREQ, BD9571MWV_INT_INTREQ), +}; + +static const struct regmap_access_table bd9574mwf_volatile_table = { + .yes_ranges = bd9574mwf_volatile_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(bd9574mwf_volatile_yes_ranges), +}; + +static const struct regmap_config bd9574mwf_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .rd_table = &bd9574mwf_readable_table, + .wr_table = &bd9574mwf_writable_table, + .volatile_table = &bd9574mwf_volatile_table, + .max_register = 0xff, +}; + +static struct regmap_irq_chip bd9574mwf_irq_chip = { + .name = "bd9574mwf", + .status_base = BD9571MWV_INT_INTREQ, + .mask_base = BD9571MWV_INT_INTMASK, + .ack_base = BD9571MWV_INT_INTREQ, + .init_ack_masked = true, + .num_regs = 1, + .irqs = bd9571mwv_irqs, + .num_irqs = ARRAY_SIZE(bd9571mwv_irqs), +}; + +static int bd957x_identify(struct device *dev, struct regmap *regmap) { - struct device *dev = bd->dev; unsigned int value; int ret; - ret = regmap_read(bd->regmap, BD9571MWV_VENDOR_CODE, &value); + ret = regmap_read(regmap, BD9571MWV_VENDOR_CODE, &value); if (ret) { dev_err(dev, "Failed to read vendor code register (ret=%i)\n", ret); @@ -129,84 +188,82 @@ static int bd9571mwv_identify(struct bd9571mwv *bd) return -EINVAL; } - ret = regmap_read(bd->regmap, BD9571MWV_PRODUCT_CODE, &value); + ret = regmap_read(regmap, BD9571MWV_PRODUCT_CODE, &value); if (ret) { dev_err(dev, "Failed to read product code register (ret=%i)\n", ret); return ret; } - - if (value != BD9571MWV_PRODUCT_CODE_VAL) { - dev_err(dev, "Invalid product code ID %02x (expected %02x)\n", - value, BD9571MWV_PRODUCT_CODE_VAL); - return -EINVAL; - } - - ret = regmap_read(bd->regmap, BD9571MWV_PRODUCT_REVISION, &value); + ret = regmap_read(regmap, BD9571MWV_PRODUCT_REVISION, &value); if (ret) { dev_err(dev, "Failed to read revision register (ret=%i)\n", ret); return ret; } - dev_info(dev, "Device: BD9571MWV rev. %d\n", value & 0xff); - return 0; } static int bd9571mwv_probe(struct i2c_client *client, - const struct i2c_device_id *ids) + const struct i2c_device_id *ids) { - struct bd9571mwv *bd; - int ret; + const struct regmap_config *regmap_config; + const struct regmap_irq_chip *irq_chip; + const struct mfd_cell *cells; + struct device *dev = &client->dev; + struct regmap *regmap; + struct regmap_irq_chip_data *irq_data; + int ret, num_cells, irq = client->irq; - bd = devm_kzalloc(&client->dev, sizeof(*bd), GFP_KERNEL); - if (!bd) - return -ENOMEM; - - i2c_set_clientdata(client, bd); - bd->dev = &client->dev; - bd->irq = client->irq; - - bd->regmap = devm_regmap_init_i2c(client, &bd9571mwv_regmap_config); - if (IS_ERR(bd->regmap)) { - dev_err(bd->dev, "Failed to initialize register map\n"); - return PTR_ERR(bd->regmap); + /* Read the PMIC product code */ + ret = i2c_smbus_read_byte_data(client, BD9571MWV_PRODUCT_CODE); + if (ret < 0) { + dev_err(dev, "Failed to read product code\n"); + return ret; } - ret = bd9571mwv_identify(bd); + switch (ret) { + case BD9571MWV_PRODUCT_CODE_BD9571MWV: + regmap_config = &bd9571mwv_regmap_config; + irq_chip = &bd9571mwv_irq_chip; + cells = bd9571mwv_cells; + num_cells = ARRAY_SIZE(bd9571mwv_cells); + break; + case BD9571MWV_PRODUCT_CODE_BD9574MWF: + regmap_config = &bd9574mwf_regmap_config; + irq_chip = &bd9574mwf_irq_chip; + cells = bd9574mwf_cells; + num_cells = ARRAY_SIZE(bd9574mwf_cells); + break; + default: + dev_err(dev, "Unsupported device 0x%x\n", ret); + return -ENODEV; + } + + regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to initialize register map\n"); + return PTR_ERR(regmap); + } + + ret = bd957x_identify(dev, regmap); if (ret) return ret; - ret = regmap_add_irq_chip(bd->regmap, bd->irq, IRQF_ONESHOT, 0, - &bd9571mwv_irq_chip, &bd->irq_data); + ret = devm_regmap_add_irq_chip(dev, regmap, irq, IRQF_ONESHOT, 0, + irq_chip, &irq_data); if (ret) { - dev_err(bd->dev, "Failed to register IRQ chip\n"); + dev_err(dev, "Failed to register IRQ chip\n"); return ret; } - ret = mfd_add_devices(bd->dev, PLATFORM_DEVID_AUTO, bd9571mwv_cells, - ARRAY_SIZE(bd9571mwv_cells), NULL, 0, - regmap_irq_get_domain(bd->irq_data)); - if (ret) { - regmap_del_irq_chip(bd->irq, bd->irq_data); - return ret; - } - - return 0; -} - -static int bd9571mwv_remove(struct i2c_client *client) -{ - struct bd9571mwv *bd = i2c_get_clientdata(client); - - regmap_del_irq_chip(bd->irq, bd->irq_data); - - return 0; + return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cells, num_cells, + NULL, 0, regmap_irq_get_domain(irq_data)); } static const struct of_device_id bd9571mwv_of_match_table[] = { { .compatible = "rohm,bd9571mwv", }, + { .compatible = "rohm,bd9574mwf", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, bd9571mwv_of_match_table); @@ -223,7 +280,6 @@ static struct i2c_driver bd9571mwv_driver = { .of_match_table = bd9571mwv_of_match_table, }, .probe = bd9571mwv_probe, - .remove = bd9571mwv_remove, .id_table = bd9571mwv_id_table, }; module_i2c_driver(bd9571mwv_driver); diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index a5983d515db0..167faac9b75b 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -2954,12 +2954,12 @@ static const struct mfd_cell common_prcmu_devs[] = { }; static const struct mfd_cell db8500_prcmu_devs[] = { - OF_MFD_CELL("db8500-prcmu-regulators", NULL, + MFD_CELL_OF("db8500-prcmu-regulators", NULL, &db8500_regulators, sizeof(db8500_regulators), 0, "stericsson,db8500-prcmu-regulator"), - OF_MFD_CELL("cpuidle-dbx500", + MFD_CELL_OF("cpuidle-dbx500", NULL, NULL, 0, 0, "stericsson,cpuidle-dbx500"), - OF_MFD_CELL("db8500-thermal", + MFD_CELL_OF("db8500-thermal", NULL, NULL, 0, 0, "stericsson,db8500-thermal"), }; diff --git a/drivers/mfd/gateworks-gsc.c b/drivers/mfd/gateworks-gsc.c index 576da62fbb0c..d87876747b91 100644 --- a/drivers/mfd/gateworks-gsc.c +++ b/drivers/mfd/gateworks-gsc.c @@ -234,7 +234,7 @@ static int gsc_probe(struct i2c_client *client) ret = devm_regmap_add_irq_chip(dev, gsc->regmap, client->irq, IRQF_ONESHOT | IRQF_SHARED | - IRQF_TRIGGER_FALLING, 0, + IRQF_TRIGGER_LOW, 0, &gsc_irq_chip, &irq_data); if (ret) return ret; diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c index 2d7c588ef1ed..1522c8afc540 100644 --- a/drivers/mfd/intel-lpss-pci.c +++ b/drivers/mfd/intel-lpss-pci.c @@ -277,6 +277,19 @@ static const struct pci_device_id intel_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x4dea), (kernel_ulong_t)&bxt_i2c_info }, { PCI_VDEVICE(INTEL, 0x4deb), (kernel_ulong_t)&bxt_i2c_info }, { PCI_VDEVICE(INTEL, 0x4dfb), (kernel_ulong_t)&spt_info }, + /* ADL-P */ + { PCI_VDEVICE(INTEL, 0x51a8), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x51a9), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x51aa), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x51ab), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x51c5), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51c6), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51c7), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x51e8), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51e9), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51ea), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51eb), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x51fb), (kernel_ulong_t)&bxt_info }, /* APL */ { PCI_VDEVICE(INTEL, 0x5aac), (kernel_ulong_t)&apl_i2c_info }, { PCI_VDEVICE(INTEL, 0x5aae), (kernel_ulong_t)&apl_i2c_info }, @@ -293,6 +306,21 @@ static const struct pci_device_id intel_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x5ac4), (kernel_ulong_t)&bxt_info }, { PCI_VDEVICE(INTEL, 0x5ac6), (kernel_ulong_t)&bxt_info }, { PCI_VDEVICE(INTEL, 0x5aee), (kernel_ulong_t)&bxt_uart_info }, + /* ADL-S */ + { PCI_VDEVICE(INTEL, 0x7aa8), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x7aa9), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x7aaa), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x7aab), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x7acc), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7acd), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7ace), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7acf), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7adc), (kernel_ulong_t)&bxt_uart_info }, + { PCI_VDEVICE(INTEL, 0x7af9), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x7afb), (kernel_ulong_t)&bxt_info }, + { PCI_VDEVICE(INTEL, 0x7afc), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7afd), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x7afe), (kernel_ulong_t)&bxt_uart_info }, /* LKF */ { PCI_VDEVICE(INTEL, 0x98a8), (kernel_ulong_t)&bxt_uart_info }, { PCI_VDEVICE(INTEL, 0x98a9), (kernel_ulong_t)&bxt_uart_info }, diff --git a/drivers/mfd/intel-m10-bmc.c b/drivers/mfd/intel-m10-bmc.c index b84579b7b4f0..06c977519479 100644 --- a/drivers/mfd/intel-m10-bmc.c +++ b/drivers/mfd/intel-m10-bmc.c @@ -60,9 +60,52 @@ static ssize_t bmcfw_version_show(struct device *dev, } static DEVICE_ATTR_RO(bmcfw_version); +static ssize_t mac_address_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_m10bmc *max10 = dev_get_drvdata(dev); + unsigned int macaddr_low, macaddr_high; + int ret; + + ret = m10bmc_sys_read(max10, M10BMC_MAC_LOW, &macaddr_low); + if (ret) + return ret; + + ret = m10bmc_sys_read(max10, M10BMC_MAC_HIGH, &macaddr_high); + if (ret) + return ret; + + return sysfs_emit(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + (u8)FIELD_GET(M10BMC_MAC_BYTE1, macaddr_low), + (u8)FIELD_GET(M10BMC_MAC_BYTE2, macaddr_low), + (u8)FIELD_GET(M10BMC_MAC_BYTE3, macaddr_low), + (u8)FIELD_GET(M10BMC_MAC_BYTE4, macaddr_low), + (u8)FIELD_GET(M10BMC_MAC_BYTE5, macaddr_high), + (u8)FIELD_GET(M10BMC_MAC_BYTE6, macaddr_high)); +} +static DEVICE_ATTR_RO(mac_address); + +static ssize_t mac_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_m10bmc *max10 = dev_get_drvdata(dev); + unsigned int macaddr_high; + int ret; + + ret = m10bmc_sys_read(max10, M10BMC_MAC_HIGH, &macaddr_high); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", + (u8)FIELD_GET(M10BMC_MAC_COUNT, macaddr_high)); +} +static DEVICE_ATTR_RO(mac_count); + static struct attribute *m10bmc_attrs[] = { &dev_attr_bmc_version.attr, &dev_attr_bmcfw_version.attr, + &dev_attr_mac_address.attr, + &dev_attr_mac_count.attr, NULL, }; ATTRIBUTE_GROUPS(m10bmc); diff --git a/drivers/mfd/iqs62x.c b/drivers/mfd/iqs62x.c index 761b4ef3a381..d1fc38a78acb 100644 --- a/drivers/mfd/iqs62x.c +++ b/drivers/mfd/iqs62x.c @@ -36,7 +36,6 @@ #define IQS62X_PROD_NUM 0x00 #define IQS62X_SYS_FLAGS 0x10 -#define IQS62X_SYS_FLAGS_IN_ATI BIT(2) #define IQS620_HALL_FLAGS 0x16 #define IQS621_HALL_FLAGS 0x19 @@ -57,10 +56,10 @@ #define IQS620_TEMP_CAL_OFFS 0xC4 #define IQS62X_SYS_SETTINGS 0xD0 -#define IQS62X_SYS_SETTINGS_SOFT_RESET BIT(7) #define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6) #define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5) #define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4) +#define IQS62X_SYS_SETTINGS_COMM_ATI BIT(3) #define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1) #define IQS62X_PWR_SETTINGS 0xD2 @@ -82,9 +81,8 @@ #define IQS62X_FW_REC_TYPE_MASK 3 #define IQS62X_FW_REC_TYPE_DATA 4 -#define IQS62X_ATI_POLL_SLEEP_US 10000 -#define IQS62X_ATI_POLL_TIMEOUT_US 500000 -#define IQS62X_ATI_STABLE_DELAY_MS 150 +#define IQS62X_ATI_STARTUP_MS 350 +#define IQS62X_FILT_SETTLE_MS 250 struct iqs62x_fw_rec { u8 type; @@ -112,9 +110,16 @@ static int iqs62x_dev_init(struct iqs62x_core *iqs62x) struct iqs62x_fw_blk *fw_blk; unsigned int val; int ret; - u8 clk_div = 1; list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) { + /* + * In case ATI is in progress, wait for it to complete before + * lowering the core clock frequency. + */ + if (fw_blk->addr == IQS62X_SYS_SETTINGS && + *fw_blk->data & IQS62X_SYS_SETTINGS_CLK_DIV) + msleep(IQS62X_ATI_STARTUP_MS); + if (fw_blk->mask) ret = regmap_update_bits(iqs62x->regmap, fw_blk->addr, fw_blk->mask, *fw_blk->data); @@ -135,7 +140,6 @@ static int iqs62x_dev_init(struct iqs62x_core *iqs62x) if (val & IQS620_PROX_SETTINGS_4_SAR_EN) iqs62x->ui_sel = IQS62X_UI_SAR1; - fallthrough; case IQS621_PROD_NUM: @@ -183,28 +187,32 @@ static int iqs62x_dev_init(struct iqs62x_core *iqs62x) return ret; } - ret = regmap_read(iqs62x->regmap, IQS62X_SYS_SETTINGS, &val); + /* + * Place the device in streaming mode at first so as not to miss the + * limited number of interrupts that would otherwise occur after ATI + * completes. The device is subsequently placed in event mode by the + * interrupt handler. + * + * In the meantime, mask interrupts during ATI to prevent the device + * from soliciting I2C traffic until the noise-sensitive ATI process + * is complete. + */ + ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS, + IQS62X_SYS_SETTINGS_ACK_RESET | + IQS62X_SYS_SETTINGS_EVENT_MODE | + IQS62X_SYS_SETTINGS_COMM_ATI | + IQS62X_SYS_SETTINGS_REDO_ATI, + IQS62X_SYS_SETTINGS_ACK_RESET | + IQS62X_SYS_SETTINGS_REDO_ATI); if (ret) return ret; - if (val & IQS62X_SYS_SETTINGS_CLK_DIV) - clk_div = iqs62x->dev_desc->clk_div; - - ret = regmap_write(iqs62x->regmap, IQS62X_SYS_SETTINGS, val | - IQS62X_SYS_SETTINGS_ACK_RESET | - IQS62X_SYS_SETTINGS_EVENT_MODE | - IQS62X_SYS_SETTINGS_REDO_ATI); - if (ret) - return ret; - - ret = regmap_read_poll_timeout(iqs62x->regmap, IQS62X_SYS_FLAGS, val, - !(val & IQS62X_SYS_FLAGS_IN_ATI), - IQS62X_ATI_POLL_SLEEP_US, - IQS62X_ATI_POLL_TIMEOUT_US * clk_div); - if (ret) - return ret; - - msleep(IQS62X_ATI_STABLE_DELAY_MS * clk_div); + /* + * The following delay gives the device time to deassert its RDY output + * in case a communication window was open while the REDO_ATI field was + * written. This prevents an interrupt from being serviced prematurely. + */ + usleep_range(5000, 5100); return 0; } @@ -435,6 +443,11 @@ const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = { .mask = BIT(7), .val = BIT(7), }, + [IQS62X_EVENT_SYS_ATI] = { + .reg = IQS62X_EVENT_SYS, + .mask = BIT(2), + .val = BIT(2), + }, }; EXPORT_SYMBOL_GPL(iqs62x_events); @@ -469,7 +482,6 @@ static irqreturn_t iqs62x_irq(int irq, void *context) switch (event_reg) { case IQS62X_EVENT_UI_LO: event_data.ui_data = get_unaligned_le16(&event_map[i]); - fallthrough; case IQS62X_EVENT_UI_HI: @@ -490,7 +502,6 @@ static irqreturn_t iqs62x_irq(int irq, void *context) case IQS62X_EVENT_HYST: event_map[i] <<= iqs62x->dev_desc->hyst_shift; - fallthrough; case IQS62X_EVENT_WHEEL: @@ -525,19 +536,46 @@ static irqreturn_t iqs62x_irq(int irq, void *context) "Failed to re-initialize device: %d\n", ret); return IRQ_NONE; } + + iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_RESET); + reinit_completion(&iqs62x->ati_done); + } else if (event_flags & BIT(IQS62X_EVENT_SYS_ATI)) { + iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_ATI); + reinit_completion(&iqs62x->ati_done); + } else if (!completion_done(&iqs62x->ati_done)) { + ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS, + IQS62X_SYS_SETTINGS_EVENT_MODE, 0xFF); + if (ret) { + dev_err(&client->dev, + "Failed to enable event mode: %d\n", ret); + return IRQ_NONE; + } + + msleep(IQS62X_FILT_SETTLE_MS); + complete_all(&iqs62x->ati_done); } - ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags, - &event_data); - if (ret & NOTIFY_STOP_MASK) - return IRQ_NONE; + /* + * Reset and ATI events are not broadcast to the sub-device drivers + * until ATI has completed. Any other events that may have occurred + * during ATI are ignored. + */ + if (completion_done(&iqs62x->ati_done)) { + event_flags |= iqs62x->event_cache; + ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags, + &event_data); + if (ret & NOTIFY_STOP_MASK) + return IRQ_NONE; + + iqs62x->event_cache = 0; + } /* * Once the communication window is closed, a small delay is added to * ensure the device's RDY output has been deasserted by the time the * interrupt handler returns. */ - usleep_range(50, 100); + usleep_range(150, 200); return IRQ_HANDLED; } @@ -571,6 +609,12 @@ static void iqs62x_firmware_load(const struct firmware *fw, void *context) goto err_out; } + if (!wait_for_completion_timeout(&iqs62x->ati_done, + msecs_to_jiffies(2000))) { + dev_err(&client->dev, "Failed to complete ATI\n"); + goto err_out; + } + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, iqs62x->dev_desc->sub_devs, iqs62x->dev_desc->num_sub_devs, @@ -752,22 +796,17 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs620at", .sub_devs = iqs620at_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs620at_sub_devs), - .prod_num = IQS620_PROD_NUM, .sw_num = 0x08, .cal_regs = iqs620at_cal_regs, .num_cal_regs = ARRAY_SIZE(iqs620at_cal_regs), - .prox_mask = BIT(0), .sar_mask = BIT(1) | BIT(7), .hall_mask = BIT(2), .hyst_mask = BIT(3), .temp_mask = BIT(4), - .prox_settings = IQS620_PROX_SETTINGS_4, .hall_flags = IQS620_HALL_FLAGS, - - .clk_div = 4, .fw_name = "iqs620a.bin", .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], }, @@ -775,20 +814,15 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs620a", .sub_devs = iqs620a_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs620a_sub_devs), - .prod_num = IQS620_PROD_NUM, .sw_num = 0x08, - .prox_mask = BIT(0), .sar_mask = BIT(1) | BIT(7), .hall_mask = BIT(2), .hyst_mask = BIT(3), .temp_mask = BIT(4), - .prox_settings = IQS620_PROX_SETTINGS_4, .hall_flags = IQS620_HALL_FLAGS, - - .clk_div = 4, .fw_name = "iqs620a.bin", .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], }, @@ -796,23 +830,18 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs621", .sub_devs = iqs621_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs621_sub_devs), - .prod_num = IQS621_PROD_NUM, .sw_num = 0x09, .cal_regs = iqs621_cal_regs, .num_cal_regs = ARRAY_SIZE(iqs621_cal_regs), - .prox_mask = BIT(0), .hall_mask = BIT(1), .als_mask = BIT(2), .hyst_mask = BIT(3), .temp_mask = BIT(4), - .als_flags = IQS621_ALS_FLAGS, .hall_flags = IQS621_HALL_FLAGS, .hyst_shift = 5, - - .clk_div = 2, .fw_name = "iqs621.bin", .event_regs = &iqs621_event_regs[IQS62X_UI_PROX], }, @@ -820,21 +849,16 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs622", .sub_devs = iqs622_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs622_sub_devs), - .prod_num = IQS622_PROD_NUM, .sw_num = 0x06, - .prox_mask = BIT(0), .sar_mask = BIT(1), .hall_mask = BIT(2), .als_mask = BIT(3), .ir_mask = BIT(4), - .prox_settings = IQS622_PROX_SETTINGS_4, .als_flags = IQS622_ALS_FLAGS, .hall_flags = IQS622_HALL_FLAGS, - - .clk_div = 2, .fw_name = "iqs622.bin", .event_regs = &iqs622_event_regs[IQS62X_UI_PROX], }, @@ -842,14 +866,10 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs624", .sub_devs = iqs624_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs624_sub_devs), - .prod_num = IQS624_PROD_NUM, .sw_num = 0x0B, - .interval = IQS624_INTERVAL_NUM, .interval_div = 3, - - .clk_div = 2, .fw_name = "iqs624.bin", .event_regs = &iqs624_event_regs[IQS62X_UI_PROX], }, @@ -857,20 +877,16 @@ static const struct iqs62x_dev_desc iqs62x_devs[] = { .dev_name = "iqs625", .sub_devs = iqs625_sub_devs, .num_sub_devs = ARRAY_SIZE(iqs625_sub_devs), - .prod_num = IQS625_PROD_NUM, .sw_num = 0x0B, - .interval = IQS625_INTERVAL_NUM, .interval_div = 10, - - .clk_div = 2, .fw_name = "iqs625.bin", .event_regs = &iqs625_event_regs[IQS62X_UI_PROX], }, }; -static const struct regmap_config iqs62x_map_config = { +static const struct regmap_config iqs62x_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = IQS62X_MAX_REG, @@ -894,9 +910,11 @@ static int iqs62x_probe(struct i2c_client *client) BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh); INIT_LIST_HEAD(&iqs62x->fw_blk_head); + + init_completion(&iqs62x->ati_done); init_completion(&iqs62x->fw_done); - iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_map_config); + iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_regmap_config); if (IS_ERR(iqs62x->regmap)) { ret = PTR_ERR(iqs62x->regmap); dev_err(&client->dev, "Failed to initialize register map: %d\n", diff --git a/drivers/mfd/max8997.c b/drivers/mfd/max8997.c index 68d8f2b95287..55d3a6f97783 100644 --- a/drivers/mfd/max8997.c +++ b/drivers/mfd/max8997.c @@ -29,9 +29,9 @@ static const struct mfd_cell max8997_devs[] = { { .name = "max8997-pmic", }, { .name = "max8997-rtc", }, - { .name = "max8997-battery", }, + { .name = "max8997-battery", .of_compatible = "maxim,max8997-battery", }, { .name = "max8997-haptic", }, - { .name = "max8997-muic", }, + { .name = "max8997-muic", .of_compatible = "maxim,max8997-muic", }, { .name = "max8997-led", .id = 1 }, { .name = "max8997-led", .id = 2 }, }; diff --git a/drivers/mfd/mcp-sa11x0.c b/drivers/mfd/mcp-sa11x0.c index 98fa0af0e56e..4629dff187cd 100644 --- a/drivers/mfd/mcp-sa11x0.c +++ b/drivers/mfd/mcp-sa11x0.c @@ -214,8 +214,7 @@ static int mcp_sa11x0_probe(struct platform_device *dev) * rate. This is the period for 3 64-bit frames. Always * round this time up. */ - mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) / - mcp->sclk_rate; + mcp->rw_timeout = DIV_ROUND_UP(64 * 3 * 1000000, mcp->sclk_rate); ret = mcp_host_add(mcp, data->codec_pdata); if (ret == 0) diff --git a/drivers/mfd/mt6360-core.c b/drivers/mfd/mt6360-core.c index 4661c1b29a72..480722acf706 100644 --- a/drivers/mfd/mt6360-core.c +++ b/drivers/mfd/mt6360-core.c @@ -292,17 +292,17 @@ static const struct resource mt6360_ldo_resources[] = { }; static const struct mfd_cell mt6360_devs[] = { - OF_MFD_CELL("mt6360_adc", mt6360_adc_resources, + MFD_CELL_OF("mt6360_adc", mt6360_adc_resources, NULL, 0, 0, "mediatek,mt6360_adc"), - OF_MFD_CELL("mt6360_chg", mt6360_chg_resources, + MFD_CELL_OF("mt6360_chg", mt6360_chg_resources, NULL, 0, 0, "mediatek,mt6360_chg"), - OF_MFD_CELL("mt6360_led", mt6360_led_resources, + MFD_CELL_OF("mt6360_led", mt6360_led_resources, NULL, 0, 0, "mediatek,mt6360_led"), - OF_MFD_CELL("mt6360_pmic", mt6360_pmic_resources, + MFD_CELL_OF("mt6360_pmic", mt6360_pmic_resources, NULL, 0, 0, "mediatek,mt6360_pmic"), - OF_MFD_CELL("mt6360_ldo", mt6360_ldo_resources, + MFD_CELL_OF("mt6360_ldo", mt6360_ldo_resources, NULL, 0, 0, "mediatek,mt6360_ldo"), - OF_MFD_CELL("mt6360_tcpc", NULL, + MFD_CELL_OF("mt6360_tcpc", NULL, NULL, 0, 0, "mediatek,mt6360_tcpc"), }; diff --git a/drivers/mfd/wm831x-auxadc.c b/drivers/mfd/wm831x-auxadc.c index 8a7cc0f86958..65b98f3fbd92 100644 --- a/drivers/mfd/wm831x-auxadc.c +++ b/drivers/mfd/wm831x-auxadc.c @@ -93,11 +93,10 @@ static int wm831x_auxadc_read_irq(struct wm831x *wm831x, wait_for_completion_timeout(&req->done, msecs_to_jiffies(500)); mutex_lock(&wm831x->auxadc_lock); - - list_del(&req->list); ret = req->val; out: + list_del(&req->list); mutex_unlock(&wm831x->auxadc_lock); kfree(req); diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index c12fe13e4b14..ae8b69aee619 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -81,3 +81,11 @@ config MMC_TEST This driver is only of interest to those developing or testing a host driver. Most people should say N here. +config MMC_CRYPTO + bool "MMC Crypto Engine Support" + depends on BLK_INLINE_ENCRYPTION + help + Enable Crypto Engine Support in MMC. + Enabling this makes it possible for the kernel to use the crypto + capabilities of the MMC device (if present) to perform crypto + operations on data being transferred to/from the device. diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 95ffe008ebdf..6a907736cd7a 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_MMC_BLOCK) += mmc_block.o mmc_block-objs := block.o queue.o obj-$(CONFIG_MMC_TEST) += mmc_test.o obj-$(CONFIG_SDIO_UART) += sdio_uart.o +mmc_core-$(CONFIG_MMC_CRYPTO) += crypto.o diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 42e27a298218..b877f62df366 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -51,6 +51,7 @@ #include "block.h" #include "core.h" #include "card.h" +#include "crypto.h" #include "host.h" #include "bus.h" #include "mmc_ops.h" @@ -1247,6 +1248,8 @@ static void mmc_blk_data_prep(struct mmc_queue *mq, struct mmc_queue_req *mqrq, memset(brq, 0, sizeof(struct mmc_blk_request)); + mmc_crypto_prepare_req(mqrq); + brq->mrq.data = &brq->data; brq->mrq.tag = req->tag; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 19f1ee57fb34..1136b859ddd8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -37,6 +37,7 @@ #include "core.h" #include "card.h" +#include "crypto.h" #include "bus.h" #include "host.h" #include "sdio_bus.h" @@ -547,10 +548,10 @@ int mmc_cqe_recovery(struct mmc_host *host) host->cqe_ops->cqe_recovery_start(host); memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = MMC_STOP_TRANSMISSION, - cmd.flags = MMC_RSP_R1B | MMC_CMD_AC, + cmd.opcode = MMC_STOP_TRANSMISSION; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; cmd.flags &= ~MMC_RSP_CRC; /* Ignore CRC */ - cmd.busy_timeout = MMC_CQE_RECOVERY_TIMEOUT, + cmd.busy_timeout = MMC_CQE_RECOVERY_TIMEOUT; mmc_wait_for_cmd(host, &cmd, 0); memset(&cmd, 0, sizeof(cmd)); @@ -558,7 +559,7 @@ int mmc_cqe_recovery(struct mmc_host *host) cmd.arg = 1; /* Discard entire queue */ cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; cmd.flags &= ~MMC_RSP_CRC; /* Ignore CRC */ - cmd.busy_timeout = MMC_CQE_RECOVERY_TIMEOUT, + cmd.busy_timeout = MMC_CQE_RECOVERY_TIMEOUT; err = mmc_wait_for_cmd(host, &cmd, 0); host->cqe_ops->cqe_recovery_finish(host); @@ -992,6 +993,8 @@ void mmc_set_initial_state(struct mmc_host *host) host->ops->hs400_enhanced_strobe(host, &host->ios); mmc_set_ios(host); + + mmc_crypto_set_initial_state(host); } /** diff --git a/drivers/mmc/core/crypto.c b/drivers/mmc/core/crypto.c new file mode 100644 index 000000000000..419a368f8402 --- /dev/null +++ b/drivers/mmc/core/crypto.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC crypto engine (inline encryption) support + * + * Copyright 2020 Google LLC + */ + +#include +#include + +#include "core.h" +#include "crypto.h" +#include "queue.h" + +void mmc_crypto_set_initial_state(struct mmc_host *host) +{ + /* Reset might clear all keys, so reprogram all the keys. */ + if (host->caps2 & MMC_CAP2_CRYPTO) + blk_ksm_reprogram_all_keys(&host->ksm); +} + +void mmc_crypto_setup_queue(struct request_queue *q, struct mmc_host *host) +{ + if (host->caps2 & MMC_CAP2_CRYPTO) + blk_ksm_register(&host->ksm, q); +} +EXPORT_SYMBOL_GPL(mmc_crypto_setup_queue); + +void mmc_crypto_prepare_req(struct mmc_queue_req *mqrq) +{ + struct request *req = mmc_queue_req_to_req(mqrq); + struct mmc_request *mrq = &mqrq->brq.mrq; + + if (!req->crypt_keyslot) + return; + + mrq->crypto_enabled = true; + mrq->crypto_key_slot = blk_ksm_get_slot_idx(req->crypt_keyslot); + + /* + * For now we assume that all MMC drivers set max_dun_bytes_supported=4, + * which is the limit for CQHCI crypto. So all DUNs should be 32-bit. + */ + WARN_ON_ONCE(req->crypt_ctx->bc_dun[0] > U32_MAX); + + mrq->data_unit_num = req->crypt_ctx->bc_dun[0]; +} +EXPORT_SYMBOL_GPL(mmc_crypto_prepare_req); diff --git a/drivers/mmc/core/crypto.h b/drivers/mmc/core/crypto.h new file mode 100644 index 000000000000..fbe9a520bf90 --- /dev/null +++ b/drivers/mmc/core/crypto.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * MMC crypto engine (inline encryption) support + * + * Copyright 2020 Google LLC + */ + +#ifndef _MMC_CORE_CRYPTO_H +#define _MMC_CORE_CRYPTO_H + +struct mmc_host; +struct mmc_queue_req; +struct request_queue; + +#ifdef CONFIG_MMC_CRYPTO + +void mmc_crypto_set_initial_state(struct mmc_host *host); + +void mmc_crypto_setup_queue(struct request_queue *q, struct mmc_host *host); + +void mmc_crypto_prepare_req(struct mmc_queue_req *mqrq); + +#else /* CONFIG_MMC_CRYPTO */ + +static inline void mmc_crypto_set_initial_state(struct mmc_host *host) +{ +} + +static inline void mmc_crypto_setup_queue(struct request_queue *q, + struct mmc_host *host) +{ +} + +static inline void mmc_crypto_prepare_req(struct mmc_queue_req *mqrq) +{ +} + +#endif /* !CONFIG_MMC_CRYPTO */ + +#endif /* _MMC_CORE_CRYPTO_H */ diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 3e11217857bc..a774f675a76c 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -25,6 +25,7 @@ #include #include "core.h" +#include "crypto.h" #include "host.h" #include "slot-gpio.h" #include "pwrseq.h" @@ -163,6 +164,50 @@ static void mmc_retune_timer(struct timer_list *t) mmc_retune_needed(host); } +static void mmc_of_parse_timing_phase(struct device *dev, const char *prop, + struct mmc_clk_phase *phase) +{ + int degrees[2] = {0}; + int rc; + + rc = device_property_read_u32_array(dev, prop, degrees, 2); + phase->valid = !rc; + if (phase->valid) { + phase->in_deg = degrees[0]; + phase->out_deg = degrees[1]; + } +} + +void +mmc_of_parse_clk_phase(struct mmc_host *host, struct mmc_clk_phase_map *map) +{ + struct device *dev = host->parent; + + mmc_of_parse_timing_phase(dev, "clk-phase-legacy", + &map->phase[MMC_TIMING_LEGACY]); + mmc_of_parse_timing_phase(dev, "clk-phase-mmc-hs", + &map->phase[MMC_TIMING_MMC_HS]); + mmc_of_parse_timing_phase(dev, "clk-phase-sd-hs", + &map->phase[MMC_TIMING_SD_HS]); + mmc_of_parse_timing_phase(dev, "clk-phase-uhs-sdr12", + &map->phase[MMC_TIMING_UHS_SDR12]); + mmc_of_parse_timing_phase(dev, "clk-phase-uhs-sdr25", + &map->phase[MMC_TIMING_UHS_SDR25]); + mmc_of_parse_timing_phase(dev, "clk-phase-uhs-sdr50", + &map->phase[MMC_TIMING_UHS_SDR50]); + mmc_of_parse_timing_phase(dev, "clk-phase-uhs-sdr104", + &map->phase[MMC_TIMING_UHS_SDR104]); + mmc_of_parse_timing_phase(dev, "clk-phase-uhs-ddr50", + &map->phase[MMC_TIMING_UHS_DDR50]); + mmc_of_parse_timing_phase(dev, "clk-phase-mmc-ddr52", + &map->phase[MMC_TIMING_MMC_DDR52]); + mmc_of_parse_timing_phase(dev, "clk-phase-mmc-hs200", + &map->phase[MMC_TIMING_MMC_HS200]); + mmc_of_parse_timing_phase(dev, "clk-phase-mmc-hs400", + &map->phase[MMC_TIMING_MMC_HS400]); +} +EXPORT_SYMBOL(mmc_of_parse_clk_phase); + /** * mmc_of_parse() - parse host's device-tree node * @host: host whose node should be parsed. diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index ff3063ce2acd..0d80b72ddde8 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1697,7 +1697,6 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, goto free_card; if (err) { - err = 0; /* * Just disable enhanced area off & sz * will try to enable ERASE_GROUP_DEF @@ -1802,7 +1801,6 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, pr_warn("%s: Enabling HPI failed\n", mmc_hostname(card->host)); card->ext_csd.hpi_en = 0; - err = 0; } else { card->ext_csd.hpi_en = 1; } @@ -1831,7 +1829,6 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, pr_warn("%s: Cache is supported, but failed to turn on (%d)\n", mmc_hostname(card->host), err); card->ext_csd.cache_ctrl = 0; - err = 0; } else { card->ext_csd.cache_ctrl = 1; } @@ -1851,7 +1848,6 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, mmc_hostname(card->host)); card->ext_csd.cmdq_support = false; card->ext_csd.cmdq_depth = 0; - err = 0; } } /* @@ -1899,7 +1895,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, static int mmc_can_sleep(struct mmc_card *card) { - return (card && card->ext_csd.rev >= 3); + return card->ext_csd.rev >= 3; } static int mmc_sleep(struct mmc_host *host) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index baa6314f69b4..265d95ec82ce 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -296,7 +296,7 @@ mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, return 0; } -static int mmc_spi_send_csd(struct mmc_card *card, u32 *csd) +static int mmc_spi_send_csd(struct mmc_host *host, u32 *csd) { int ret, i; __be32 *csd_tmp; @@ -305,7 +305,7 @@ static int mmc_spi_send_csd(struct mmc_card *card, u32 *csd) if (!csd_tmp) return -ENOMEM; - ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd_tmp, 16); + ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CSD, csd_tmp, 16); if (ret) goto err; @@ -320,7 +320,7 @@ static int mmc_spi_send_csd(struct mmc_card *card, u32 *csd) int mmc_send_csd(struct mmc_card *card, u32 *csd) { if (mmc_host_is_spi(card->host)) - return mmc_spi_send_csd(card, csd); + return mmc_spi_send_csd(card->host, csd); return mmc_send_cxd_native(card->host, card->rca << 16, csd, MMC_SEND_CSD); diff --git a/drivers/mmc/core/mmc_test.c b/drivers/mmc/core/mmc_test.c index 152e7525ed33..63524551a13a 100644 --- a/drivers/mmc/core/mmc_test.c +++ b/drivers/mmc/core/mmc_test.c @@ -624,7 +624,7 @@ static unsigned int mmc_test_capacity(struct mmc_card *card) * Fill the first couple of sectors of the card with known data * so that bad reads/writes can be detected */ -static int __mmc_test_prepare(struct mmc_test_card *test, int write) +static int __mmc_test_prepare(struct mmc_test_card *test, int write, int val) { int ret, i; @@ -633,7 +633,7 @@ static int __mmc_test_prepare(struct mmc_test_card *test, int write) return ret; if (write) - memset(test->buffer, 0xDF, 512); + memset(test->buffer, val, 512); else { for (i = 0; i < 512; i++) test->buffer[i] = i; @@ -650,31 +650,17 @@ static int __mmc_test_prepare(struct mmc_test_card *test, int write) static int mmc_test_prepare_write(struct mmc_test_card *test) { - return __mmc_test_prepare(test, 1); + return __mmc_test_prepare(test, 1, 0xDF); } static int mmc_test_prepare_read(struct mmc_test_card *test) { - return __mmc_test_prepare(test, 0); + return __mmc_test_prepare(test, 0, 0); } static int mmc_test_cleanup(struct mmc_test_card *test) { - int ret, i; - - ret = mmc_test_set_blksize(test, 512); - if (ret) - return ret; - - memset(test->buffer, 0, 512); - - for (i = 0; i < BUFFER_SIZE / 512; i++) { - ret = mmc_test_buffer_transfer(test, test->buffer, i, 512, 1); - if (ret) - return ret; - } - - return 0; + return __mmc_test_prepare(test, 1, 0); } /*******************************************************************/ @@ -2124,7 +2110,7 @@ static int mmc_test_rw_multiple(struct mmc_test_card *test, if (mmc_can_erase(test->card) && tdata->prepare & MMC_TEST_PREP_ERASE) { ret = mmc_erase(test->card, dev_addr, - size / 512, MMC_SECURE_ERASE_ARG); + size / 512, test->card->erase_arg); if (ret) ret = mmc_erase(test->card, dev_addr, size / 512, MMC_ERASE_ARG); @@ -3267,17 +3253,12 @@ static void mmc_test_remove(struct mmc_card *card) mmc_test_free_dbgfs_file(card); } -static void mmc_test_shutdown(struct mmc_card *card) -{ -} - static struct mmc_driver mmc_driver = { .drv = { .name = "mmc_test", }, .probe = mmc_test_probe, .remove = mmc_test_remove, - .shutdown = mmc_test_shutdown, }; static int __init mmc_test_init(void) diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index 002426e3cf76..27d2b8ed9484 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -19,6 +18,7 @@ #include "block.h" #include "core.h" #include "card.h" +#include "crypto.h" #include "host.h" #define MMC_DMA_MAP_MERGE_SEGMENTS 512 @@ -33,8 +33,6 @@ void mmc_cqe_check_busy(struct mmc_queue *mq) { if ((mq->cqe_busy & MMC_CQE_DCMD_BUSY) && !mmc_cqe_dcmd_busy(mq)) mq->cqe_busy &= ~MMC_CQE_DCMD_BUSY; - - mq->cqe_busy &= ~MMC_CQE_QUEUE_FULL; } static inline bool mmc_cqe_can_dcmd(struct mmc_host *host) @@ -407,6 +405,8 @@ static void mmc_setup_queue(struct mmc_queue *mq, struct mmc_card *card) mutex_init(&mq->complete_lock); init_waitqueue_head(&mq->wait); + + mmc_crypto_setup_queue(mq->queue, host); } static inline bool mmc_merge_capable(struct mmc_host *host) diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h index fd11491ced9f..57c59b6cb1b9 100644 --- a/drivers/mmc/core/queue.h +++ b/drivers/mmc/core/queue.h @@ -81,7 +81,6 @@ struct mmc_queue { int in_flight[MMC_ISSUE_MAX]; unsigned int cqe_busy; #define MMC_CQE_DCMD_BUSY BIT(0) -#define MMC_CQE_QUEUE_FULL BIT(1) bool busy; bool use_cqe; bool recovery_needed; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 6f054c449d46..6fa51a6ed058 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -860,7 +860,7 @@ int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) return err; } -int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card) +int mmc_sd_get_csd(struct mmc_card *card) { int err; @@ -1046,7 +1046,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, } if (!oldcard) { - err = mmc_sd_get_csd(host, card); + err = mmc_sd_get_csd(card); if (err) goto free_card; diff --git a/drivers/mmc/core/sd.h b/drivers/mmc/core/sd.h index 497c026a5c5a..1af5a038bae9 100644 --- a/drivers/mmc/core/sd.h +++ b/drivers/mmc/core/sd.h @@ -10,7 +10,7 @@ struct mmc_host; struct mmc_card; int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr); -int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card); +int mmc_sd_get_csd(struct mmc_card *card); void mmc_decode_cid(struct mmc_card *card); int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, bool reinit); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 694a212cbe25..0fda7784cab2 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -751,7 +751,7 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, * Read CSD, before selecting the card */ if (!oldcard && card->type == MMC_TYPE_SD_COMBO) { - err = mmc_sd_get_csd(host, card); + err = mmc_sd_get_csd(card); if (err) goto remove; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 596f32637315..b236dfe2e879 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -168,6 +168,20 @@ config MMC_SDHCI_OF_ASPEED If unsure, say N. +config MMC_SDHCI_OF_ASPEED_TEST + bool "Tests for the ASPEED SDHCI driver" + depends on MMC_SDHCI_OF_ASPEED && KUNIT=y + help + Enable KUnit tests for the ASPEED SDHCI driver. Select this + option only if you will boot the kernel for the purpose of running + unit tests (e.g. under UML or qemu). + + The KUnit tests generally exercise parts of the driver that do not + directly touch the hardware, for example, the phase correction + calculations. + + If unsure, say N. + config MMC_SDHCI_OF_AT91 tristate "SDHCI OF support for the Atmel SDMMC controller" depends on MMC_SDHCI_PLTFM @@ -312,18 +326,6 @@ config MMC_SDHCI_S3C If unsure, say N. -config MMC_SDHCI_SIRF - tristate "SDHCI support on CSR SiRFprimaII and SiRFmarco SoCs" - depends on ARCH_SIRF || COMPILE_TEST - depends on MMC_SDHCI_PLTFM - select MMC_SDHCI_IO_ACCESSORS - help - This selects the SDHCI support for SiRF System-on-Chip devices. - - If you have a controller with this interface, say Y or M here. - - If unsure, say N. - config MMC_SDHCI_PXAV3 tristate "Marvell MMP2 SD Host Controller support (PXAV3)" depends on CLKDEV_LOOKUP @@ -544,6 +546,7 @@ config MMC_SDHCI_MSM depends on MMC_SDHCI_PLTFM select MMC_SDHCI_IO_ACCESSORS select MMC_CQHCI + select QCOM_SCM if MMC_CRYPTO && ARCH_QCOM help This selects the Secure Digital Host Controller Interface (SDHCI) support present in Qualcomm SOCs. The controller supports @@ -608,13 +611,6 @@ config MMC_DAVINCI If you have an DAVINCI board with a Multimedia Card slot, say Y or M here. If unsure, say N. -config MMC_GOLDFISH - tristate "goldfish qemu Multimedia Card Interface support" - depends on GOLDFISH || COMPILE_TEST - help - This selects the Goldfish Multimedia card Interface emulation - found on the Goldfish Android virtual device emulation. - config MMC_SPI tristate "MMC/SD/SDIO over SPI" depends on SPI_MASTER @@ -868,15 +864,6 @@ config MMC_DW_ROCKCHIP Synopsys DesignWare Memory Card Interface driver. Select this option for platforms based on RK3066, RK3188 and RK3288 SoC's. -config MMC_DW_ZX - tristate "ZTE specific extensions for Synopsys DW Memory Card Interface" - depends on MMC_DW && ARCH_ZX - select MMC_DW_PLTFM - help - This selects support for ZTE SoC specific extensions to the - Synopsys DesignWare Memory Card Interface driver. Select this option - for platforms based on ZX296718 SoC's. - config MMC_SH_MMCIF tristate "SuperH Internal MMCIF support" depends on SUPERH || ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 451c25fc2c69..6df5c4774260 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_MMC_SDHCI_ACPI) += sdhci-acpi.o obj-$(CONFIG_MMC_SDHCI_PXAV3) += sdhci-pxav3.o obj-$(CONFIG_MMC_SDHCI_PXAV2) += sdhci-pxav2.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o -obj-$(CONFIG_MMC_SDHCI_SIRF) += sdhci-sirf.o obj-$(CONFIG_MMC_SDHCI_F_SDH30) += sdhci_f_sdh30.o obj-$(CONFIG_MMC_SDHCI_MILBEAUT) += sdhci-milbeaut.o obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o @@ -34,7 +33,6 @@ obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_MVSDIO) += mvsdio.o obj-$(CONFIG_MMC_DAVINCI) += davinci_mmc.o -obj-$(CONFIG_MMC_GOLDFISH) += android-goldfish.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_MMC_SPI) += of_mmc_spi.o @@ -61,7 +59,6 @@ obj-$(CONFIG_MMC_DW_HI3798CV200) += dw_mmc-hi3798cv200.o obj-$(CONFIG_MMC_DW_K3) += dw_mmc-k3.o obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o -obj-$(CONFIG_MMC_DW_ZX) += dw_mmc-zx.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_VUB300) += vub300.o @@ -104,6 +101,8 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o obj-$(CONFIG_MMC_CQHCI) += cqhci.o +cqhci-y += cqhci-core.o +cqhci-$(CONFIG_MMC_CRYPTO) += cqhci-crypto.o obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o ifeq ($(CONFIG_CB710_DEBUG),y) diff --git a/drivers/mmc/host/android-goldfish.c b/drivers/mmc/host/android-goldfish.c deleted file mode 100644 index e878fdf8f20a..000000000000 --- a/drivers/mmc/host/android-goldfish.c +++ /dev/null @@ -1,545 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright 2007, Google Inc. - * Copyright 2012, Intel Inc. - * - * based on omap.c driver, which was - * Copyright (C) 2004 Nokia Corporation - * Written by Tuukka Tikkanen and Juha Yrjölä - * Misc hacks here and there by Tony Lindgren - * Other hacks (DMA, SD, etc) by David Brownell - */ - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#define DRIVER_NAME "goldfish_mmc" - -#define BUFFER_SIZE 16384 - -#define GOLDFISH_MMC_READ(host, addr) (readl(host->reg_base + addr)) -#define GOLDFISH_MMC_WRITE(host, addr, x) (writel(x, host->reg_base + addr)) - -enum { - /* status register */ - MMC_INT_STATUS = 0x00, - /* set this to enable IRQ */ - MMC_INT_ENABLE = 0x04, - /* set this to specify buffer address */ - MMC_SET_BUFFER = 0x08, - - /* MMC command number */ - MMC_CMD = 0x0C, - - /* MMC argument */ - MMC_ARG = 0x10, - - /* MMC response (or R2 bits 0 - 31) */ - MMC_RESP_0 = 0x14, - - /* MMC R2 response bits 32 - 63 */ - MMC_RESP_1 = 0x18, - - /* MMC R2 response bits 64 - 95 */ - MMC_RESP_2 = 0x1C, - - /* MMC R2 response bits 96 - 127 */ - MMC_RESP_3 = 0x20, - - MMC_BLOCK_LENGTH = 0x24, - MMC_BLOCK_COUNT = 0x28, - - /* MMC state flags */ - MMC_STATE = 0x2C, - - /* MMC_INT_STATUS bits */ - - MMC_STAT_END_OF_CMD = 1U << 0, - MMC_STAT_END_OF_DATA = 1U << 1, - MMC_STAT_STATE_CHANGE = 1U << 2, - MMC_STAT_CMD_TIMEOUT = 1U << 3, - - /* MMC_STATE bits */ - MMC_STATE_INSERTED = 1U << 0, - MMC_STATE_READ_ONLY = 1U << 1, -}; - -/* - * Command types - */ -#define OMAP_MMC_CMDTYPE_BC 0 -#define OMAP_MMC_CMDTYPE_BCR 1 -#define OMAP_MMC_CMDTYPE_AC 2 -#define OMAP_MMC_CMDTYPE_ADTC 3 - - -struct goldfish_mmc_host { - struct mmc_request *mrq; - struct mmc_command *cmd; - struct mmc_data *data; - struct device *dev; - unsigned char id; /* 16xx chips have 2 MMC blocks */ - void *virt_base; - unsigned int phys_base; - int irq; - unsigned char bus_mode; - unsigned char hw_bus_mode; - - unsigned int sg_len; - unsigned dma_done:1; - unsigned dma_in_use:1; - - void __iomem *reg_base; -}; - -static inline int -goldfish_mmc_cover_is_open(struct goldfish_mmc_host *host) -{ - return 0; -} - -static ssize_t -goldfish_mmc_show_cover_switch(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct goldfish_mmc_host *host = dev_get_drvdata(dev); - - return sprintf(buf, "%s\n", goldfish_mmc_cover_is_open(host) ? "open" : - "closed"); -} - -static DEVICE_ATTR(cover_switch, S_IRUGO, goldfish_mmc_show_cover_switch, NULL); - -static void -goldfish_mmc_start_command(struct goldfish_mmc_host *host, struct mmc_command *cmd) -{ - u32 cmdreg; - u32 resptype; - u32 cmdtype; - - host->cmd = cmd; - - resptype = 0; - cmdtype = 0; - - /* Our hardware needs to know exact type */ - switch (mmc_resp_type(cmd)) { - case MMC_RSP_NONE: - break; - case MMC_RSP_R1: - case MMC_RSP_R1B: - /* resp 1, 1b, 6, 7 */ - resptype = 1; - break; - case MMC_RSP_R2: - resptype = 2; - break; - case MMC_RSP_R3: - resptype = 3; - break; - default: - dev_err(mmc_dev(mmc_from_priv(host)), - "Invalid response type: %04x\n", mmc_resp_type(cmd)); - break; - } - - if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) - cmdtype = OMAP_MMC_CMDTYPE_ADTC; - else if (mmc_cmd_type(cmd) == MMC_CMD_BC) - cmdtype = OMAP_MMC_CMDTYPE_BC; - else if (mmc_cmd_type(cmd) == MMC_CMD_BCR) - cmdtype = OMAP_MMC_CMDTYPE_BCR; - else - cmdtype = OMAP_MMC_CMDTYPE_AC; - - cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12); - - if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) - cmdreg |= 1 << 6; - - if (cmd->flags & MMC_RSP_BUSY) - cmdreg |= 1 << 11; - - if (host->data && !(host->data->flags & MMC_DATA_WRITE)) - cmdreg |= 1 << 15; - - GOLDFISH_MMC_WRITE(host, MMC_ARG, cmd->arg); - GOLDFISH_MMC_WRITE(host, MMC_CMD, cmdreg); -} - -static void goldfish_mmc_xfer_done(struct goldfish_mmc_host *host, - struct mmc_data *data) -{ - if (host->dma_in_use) { - enum dma_data_direction dma_data_dir; - - dma_data_dir = mmc_get_dma_dir(data); - - if (dma_data_dir == DMA_FROM_DEVICE) { - /* - * We don't really have DMA, so we need - * to copy from our platform driver buffer - */ - sg_copy_from_buffer(data->sg, 1, host->virt_base, - data->sg->length); - } - host->data->bytes_xfered += data->sg->length; - dma_unmap_sg(mmc_dev(mmc_from_priv(host)), data->sg, - host->sg_len, dma_data_dir); - } - - host->data = NULL; - host->sg_len = 0; - - /* - * NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing - * dozens of requests until the card finishes writing data. - * It'd be cheaper to just wait till an EOFB interrupt arrives... - */ - - if (!data->stop) { - host->mrq = NULL; - mmc_request_done(mmc_from_priv(host), data->mrq); - return; - } - - goldfish_mmc_start_command(host, data->stop); -} - -static void goldfish_mmc_end_of_data(struct goldfish_mmc_host *host, - struct mmc_data *data) -{ - if (!host->dma_in_use) { - goldfish_mmc_xfer_done(host, data); - return; - } - if (host->dma_done) - goldfish_mmc_xfer_done(host, data); -} - -static void goldfish_mmc_cmd_done(struct goldfish_mmc_host *host, - struct mmc_command *cmd) -{ - host->cmd = NULL; - if (cmd->flags & MMC_RSP_PRESENT) { - if (cmd->flags & MMC_RSP_136) { - /* response type 2 */ - cmd->resp[3] = - GOLDFISH_MMC_READ(host, MMC_RESP_0); - cmd->resp[2] = - GOLDFISH_MMC_READ(host, MMC_RESP_1); - cmd->resp[1] = - GOLDFISH_MMC_READ(host, MMC_RESP_2); - cmd->resp[0] = - GOLDFISH_MMC_READ(host, MMC_RESP_3); - } else { - /* response types 1, 1b, 3, 4, 5, 6 */ - cmd->resp[0] = - GOLDFISH_MMC_READ(host, MMC_RESP_0); - } - } - - if (host->data == NULL || cmd->error) { - host->mrq = NULL; - mmc_request_done(mmc_from_priv(host), cmd->mrq); - } -} - -static irqreturn_t goldfish_mmc_irq(int irq, void *dev_id) -{ - struct goldfish_mmc_host *host = (struct goldfish_mmc_host *)dev_id; - u16 status; - int end_command = 0; - int end_transfer = 0; - int state_changed = 0; - int cmd_timeout = 0; - - while ((status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS)) != 0) { - GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status); - - if (status & MMC_STAT_END_OF_CMD) - end_command = 1; - - if (status & MMC_STAT_END_OF_DATA) - end_transfer = 1; - - if (status & MMC_STAT_STATE_CHANGE) - state_changed = 1; - - if (status & MMC_STAT_CMD_TIMEOUT) { - end_command = 0; - cmd_timeout = 1; - } - } - - if (cmd_timeout) { - struct mmc_request *mrq = host->mrq; - mrq->cmd->error = -ETIMEDOUT; - host->mrq = NULL; - mmc_request_done(mmc_from_priv(host), mrq); - } - - if (end_command) - goldfish_mmc_cmd_done(host, host->cmd); - - if (end_transfer) { - host->dma_done = 1; - goldfish_mmc_end_of_data(host, host->data); - } else if (host->data != NULL) { - /* - * WORKAROUND -- after porting this driver from 2.6 to 3.4, - * during device initialization, cases where host->data is - * non-null but end_transfer is false would occur. Doing - * nothing in such cases results in no further interrupts, - * and initialization failure. - * TODO -- find the real cause. - */ - host->dma_done = 1; - goldfish_mmc_end_of_data(host, host->data); - } - - if (state_changed) { - u32 state = GOLDFISH_MMC_READ(host, MMC_STATE); - pr_info("%s: Card detect now %d\n", __func__, - (state & MMC_STATE_INSERTED)); - mmc_detect_change(mmc_from_priv(host), 0); - } - - if (!end_command && !end_transfer && !state_changed && !cmd_timeout) { - status = GOLDFISH_MMC_READ(host, MMC_INT_STATUS); - dev_info(mmc_dev(mmc_from_priv(host)), "spurious irq 0x%04x\n", - status); - if (status != 0) { - GOLDFISH_MMC_WRITE(host, MMC_INT_STATUS, status); - GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE, 0); - } - } - - return IRQ_HANDLED; -} - -static void goldfish_mmc_prepare_data(struct goldfish_mmc_host *host, - struct mmc_request *req) -{ - struct mmc_data *data = req->data; - int block_size; - unsigned sg_len; - enum dma_data_direction dma_data_dir; - - host->data = data; - if (data == NULL) { - GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, 0); - GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, 0); - host->dma_in_use = 0; - return; - } - - block_size = data->blksz; - - GOLDFISH_MMC_WRITE(host, MMC_BLOCK_COUNT, data->blocks - 1); - GOLDFISH_MMC_WRITE(host, MMC_BLOCK_LENGTH, block_size - 1); - - /* - * Cope with calling layer confusion; it issues "single - * block" writes using multi-block scatterlists. - */ - sg_len = (data->blocks == 1) ? 1 : data->sg_len; - - dma_data_dir = mmc_get_dma_dir(data); - - host->sg_len = dma_map_sg(mmc_dev(mmc_from_priv(host)), data->sg, - sg_len, dma_data_dir); - host->dma_done = 0; - host->dma_in_use = 1; - - if (dma_data_dir == DMA_TO_DEVICE) { - /* - * We don't really have DMA, so we need to copy to our - * platform driver buffer - */ - sg_copy_to_buffer(data->sg, 1, host->virt_base, - data->sg->length); - } -} - -static void goldfish_mmc_request(struct mmc_host *mmc, struct mmc_request *req) -{ - struct goldfish_mmc_host *host = mmc_priv(mmc); - - WARN_ON(host->mrq != NULL); - - host->mrq = req; - goldfish_mmc_prepare_data(host, req); - goldfish_mmc_start_command(host, req->cmd); -} - -static void goldfish_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) -{ - struct goldfish_mmc_host *host = mmc_priv(mmc); - - host->bus_mode = ios->bus_mode; - host->hw_bus_mode = host->bus_mode; -} - -static int goldfish_mmc_get_ro(struct mmc_host *mmc) -{ - uint32_t state; - struct goldfish_mmc_host *host = mmc_priv(mmc); - - state = GOLDFISH_MMC_READ(host, MMC_STATE); - return ((state & MMC_STATE_READ_ONLY) != 0); -} - -static const struct mmc_host_ops goldfish_mmc_ops = { - .request = goldfish_mmc_request, - .set_ios = goldfish_mmc_set_ios, - .get_ro = goldfish_mmc_get_ro, -}; - -static int goldfish_mmc_probe(struct platform_device *pdev) -{ - struct mmc_host *mmc; - struct goldfish_mmc_host *host = NULL; - struct resource *res; - int ret = 0; - int irq; - dma_addr_t buf_addr; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - irq = platform_get_irq(pdev, 0); - if (res == NULL || irq < 0) - return -ENXIO; - - mmc = mmc_alloc_host(sizeof(struct goldfish_mmc_host), &pdev->dev); - if (mmc == NULL) { - ret = -ENOMEM; - goto err_alloc_host_failed; - } - - host = mmc_priv(mmc); - - pr_err("mmc: Mapping %lX to %lX\n", (long)res->start, (long)res->end); - host->reg_base = ioremap(res->start, resource_size(res)); - if (host->reg_base == NULL) { - ret = -ENOMEM; - goto ioremap_failed; - } - host->virt_base = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE, - &buf_addr, GFP_KERNEL); - - if (host->virt_base == 0) { - ret = -ENOMEM; - goto dma_alloc_failed; - } - host->phys_base = buf_addr; - - host->id = pdev->id; - host->irq = irq; - - mmc->ops = &goldfish_mmc_ops; - mmc->f_min = 400000; - mmc->f_max = 24000000; - mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_4_BIT_DATA; - mmc->caps2 = MMC_CAP2_NO_SDIO; - - /* Use scatterlist DMA to reduce per-transfer costs. - * NOTE max_seg_size assumption that small blocks aren't - * normally used (except e.g. for reading SD registers). - */ - mmc->max_segs = 32; - mmc->max_blk_size = 2048; /* MMC_BLOCK_LENGTH is 11 bits (+1) */ - mmc->max_blk_count = 2048; /* MMC_BLOCK_COUNT is 11 bits (+1) */ - mmc->max_req_size = BUFFER_SIZE; - mmc->max_seg_size = mmc->max_req_size; - - ret = request_irq(host->irq, goldfish_mmc_irq, 0, DRIVER_NAME, host); - if (ret) { - dev_err(&pdev->dev, "Failed IRQ Adding goldfish MMC\n"); - goto err_request_irq_failed; - } - - host->dev = &pdev->dev; - platform_set_drvdata(pdev, host); - - ret = device_create_file(&pdev->dev, &dev_attr_cover_switch); - if (ret) - dev_warn(mmc_dev(mmc), "Unable to create sysfs attributes\n"); - - GOLDFISH_MMC_WRITE(host, MMC_SET_BUFFER, host->phys_base); - GOLDFISH_MMC_WRITE(host, MMC_INT_ENABLE, - MMC_STAT_END_OF_CMD | MMC_STAT_END_OF_DATA | - MMC_STAT_STATE_CHANGE | MMC_STAT_CMD_TIMEOUT); - - mmc_add_host(mmc); - return 0; - -err_request_irq_failed: - dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base, - host->phys_base); -dma_alloc_failed: - iounmap(host->reg_base); -ioremap_failed: - mmc_free_host(mmc); -err_alloc_host_failed: - return ret; -} - -static int goldfish_mmc_remove(struct platform_device *pdev) -{ - struct goldfish_mmc_host *host = platform_get_drvdata(pdev); - struct mmc_host *mmc = mmc_from_priv(host); - - BUG_ON(host == NULL); - - mmc_remove_host(mmc); - free_irq(host->irq, host); - dma_free_coherent(&pdev->dev, BUFFER_SIZE, host->virt_base, host->phys_base); - iounmap(host->reg_base); - mmc_free_host(mmc); - return 0; -} - -static struct platform_driver goldfish_mmc_driver = { - .probe = goldfish_mmc_probe, - .remove = goldfish_mmc_remove, - .driver = { - .name = DRIVER_NAME, - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, -}; - -module_platform_driver(goldfish_mmc_driver); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 444bd3a0a922..807177c953f3 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -1719,9 +1719,9 @@ static void atmci_detect_change(struct timer_list *t) } } -static void atmci_tasklet_func(unsigned long priv) +static void atmci_tasklet_func(struct tasklet_struct *t) { - struct atmel_mci *host = (struct atmel_mci *)priv; + struct atmel_mci *host = from_tasklet(host, t, tasklet); struct mmc_request *mrq = host->mrq; struct mmc_data *data = host->data; enum atmel_mci_state state = host->state; @@ -2401,45 +2401,45 @@ static void atmci_get_cap(struct atmel_mci *host) dev_info(&host->pdev->dev, "version: 0x%x\n", version); - host->caps.has_dma_conf_reg = 0; - host->caps.has_pdc = 1; - host->caps.has_cfg_reg = 0; - host->caps.has_cstor_reg = 0; - host->caps.has_highspeed = 0; - host->caps.has_rwproof = 0; - host->caps.has_odd_clk_div = 0; - host->caps.has_bad_data_ordering = 1; - host->caps.need_reset_after_xfer = 1; - host->caps.need_blksz_mul_4 = 1; - host->caps.need_notbusy_for_read_ops = 0; + host->caps.has_dma_conf_reg = false; + host->caps.has_pdc = true; + host->caps.has_cfg_reg = false; + host->caps.has_cstor_reg = false; + host->caps.has_highspeed = false; + host->caps.has_rwproof = false; + host->caps.has_odd_clk_div = false; + host->caps.has_bad_data_ordering = true; + host->caps.need_reset_after_xfer = true; + host->caps.need_blksz_mul_4 = true; + host->caps.need_notbusy_for_read_ops = false; /* keep only major version number */ switch (version & 0xf00) { case 0x600: case 0x500: - host->caps.has_odd_clk_div = 1; + host->caps.has_odd_clk_div = true; fallthrough; case 0x400: case 0x300: - host->caps.has_dma_conf_reg = 1; - host->caps.has_pdc = 0; - host->caps.has_cfg_reg = 1; - host->caps.has_cstor_reg = 1; - host->caps.has_highspeed = 1; + host->caps.has_dma_conf_reg = true; + host->caps.has_pdc = false; + host->caps.has_cfg_reg = true; + host->caps.has_cstor_reg = true; + host->caps.has_highspeed = true; fallthrough; case 0x200: - host->caps.has_rwproof = 1; - host->caps.need_blksz_mul_4 = 0; - host->caps.need_notbusy_for_read_ops = 1; + host->caps.has_rwproof = true; + host->caps.need_blksz_mul_4 = false; + host->caps.need_notbusy_for_read_ops = true; fallthrough; case 0x100: - host->caps.has_bad_data_ordering = 0; - host->caps.need_reset_after_xfer = 0; + host->caps.has_bad_data_ordering = false; + host->caps.need_reset_after_xfer = false; fallthrough; case 0x0: break; default: - host->caps.has_pdc = 0; + host->caps.has_pdc = false; dev_warn(&host->pdev->dev, "Unmanaged mci version, set minimum capabilities\n"); break; @@ -2496,7 +2496,7 @@ static int atmci_probe(struct platform_device *pdev) host->mapbase = regs->start; - tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)host); + tasklet_setup(&host->tasklet, atmci_tasklet_func); ret = request_irq(irq, atmci_interrupt, 0, dev_name(&pdev->dev), host); if (ret) { diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c index bd00515fbaba..0acc237843f7 100644 --- a/drivers/mmc/host/au1xmmc.c +++ b/drivers/mmc/host/au1xmmc.c @@ -253,9 +253,9 @@ static void au1xmmc_finish_request(struct au1xmmc_host *host) mmc_request_done(host->mmc, mrq); } -static void au1xmmc_tasklet_finish(unsigned long param) +static void au1xmmc_tasklet_finish(struct tasklet_struct *t) { - struct au1xmmc_host *host = (struct au1xmmc_host *) param; + struct au1xmmc_host *host = from_tasklet(host, t, finish_task); au1xmmc_finish_request(host); } @@ -363,9 +363,9 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) au1xmmc_finish_request(host); } -static void au1xmmc_tasklet_data(unsigned long param) +static void au1xmmc_tasklet_data(struct tasklet_struct *t) { - struct au1xmmc_host *host = (struct au1xmmc_host *)param; + struct au1xmmc_host *host = from_tasklet(host, t, data_task); u32 status = __raw_readl(HOST_STATUS(host)); au1xmmc_data_complete(host, status); @@ -1037,11 +1037,9 @@ static int au1xmmc_probe(struct platform_device *pdev) if (host->platdata) mmc->caps &= ~(host->platdata->mask_host_caps); - tasklet_init(&host->data_task, au1xmmc_tasklet_data, - (unsigned long)host); + tasklet_setup(&host->data_task, au1xmmc_tasklet_data); - tasklet_init(&host->finish_task, au1xmmc_tasklet_finish, - (unsigned long)host); + tasklet_setup(&host->finish_task, au1xmmc_tasklet_finish); if (has_dbdma()) { ret = au1xmmc_dbdma_init(host); diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index c5da3aaee334..4bb8f2800a2b 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -436,12 +436,11 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id) { struct cvm_mmc_host *host = dev_id; struct mmc_request *req; - unsigned long flags = 0; u64 emm_int, rsp_sts; bool host_done; if (host->need_irq_handler_lock) - spin_lock_irqsave(&host->irq_handler_lock, flags); + spin_lock(&host->irq_handler_lock); else __acquire(&host->irq_handler_lock); @@ -504,7 +503,7 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id) host->release_bus(host); out: if (host->need_irq_handler_lock) - spin_unlock_irqrestore(&host->irq_handler_lock, flags); + spin_unlock(&host->irq_handler_lock); else __release(&host->irq_handler_lock); return IRQ_RETVAL(emm_int != 0); diff --git a/drivers/mmc/host/cb710-mmc.c b/drivers/mmc/host/cb710-mmc.c index e84ed84ea4cc..6d623b2681c3 100644 --- a/drivers/mmc/host/cb710-mmc.c +++ b/drivers/mmc/host/cb710-mmc.c @@ -646,14 +646,14 @@ static int cb710_mmc_irq_handler(struct cb710_slot *slot) return 1; } -static void cb710_mmc_finish_request_tasklet(unsigned long data) +static void cb710_mmc_finish_request_tasklet(struct tasklet_struct *t) { - struct mmc_host *mmc = (void *)data; - struct cb710_mmc_reader *reader = mmc_priv(mmc); + struct cb710_mmc_reader *reader = from_tasklet(reader, t, + finish_req_tasklet); struct mmc_request *mrq = reader->mrq; reader->mrq = NULL; - mmc_request_done(mmc, mrq); + mmc_request_done(mmc_from_priv(reader), mrq); } static const struct mmc_host_ops cb710_mmc_host = { @@ -718,8 +718,8 @@ static int cb710_mmc_init(struct platform_device *pdev) reader = mmc_priv(mmc); - tasklet_init(&reader->finish_req_tasklet, - cb710_mmc_finish_request_tasklet, (unsigned long)mmc); + tasklet_setup(&reader->finish_req_tasklet, + cb710_mmc_finish_request_tasklet); spin_lock_init(&reader->irq_lock); cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); diff --git a/drivers/mmc/host/cqhci.c b/drivers/mmc/host/cqhci-core.c similarity index 94% rename from drivers/mmc/host/cqhci.c rename to drivers/mmc/host/cqhci-core.c index 697fe40756bf..93b0432bb601 100644 --- a/drivers/mmc/host/cqhci.c +++ b/drivers/mmc/host/cqhci-core.c @@ -18,6 +18,7 @@ #include #include "cqhci.h" +#include "cqhci-crypto.h" #define DCMD_SLOT 31 #define NUM_SLOTS 32 @@ -258,6 +259,9 @@ static void __cqhci_enable(struct cqhci_host *cq_host) if (cq_host->caps & CQHCI_TASK_DESC_SZ_128) cqcfg |= CQHCI_TASK_DESC_SZ; + if (mmc->caps2 & MMC_CAP2_CRYPTO) + cqcfg |= CQHCI_CRYPTO_GENERAL_ENABLE; + cqhci_writel(cq_host, cqcfg, CQHCI_CFG); cqhci_writel(cq_host, lower_32_bits(cq_host->desc_dma_base), @@ -408,13 +412,15 @@ static void cqhci_disable(struct mmc_host *mmc) } static void cqhci_prep_task_desc(struct mmc_request *mrq, - u64 *data, bool intr) + struct cqhci_host *cq_host, int tag) { + __le64 *task_desc = (__le64 __force *)get_desc(cq_host, tag); u32 req_flags = mrq->data->flags; + u64 desc0; - *data = CQHCI_VALID(1) | + desc0 = CQHCI_VALID(1) | CQHCI_END(1) | - CQHCI_INT(intr) | + CQHCI_INT(1) | CQHCI_ACT(0x5) | CQHCI_FORCED_PROG(!!(req_flags & MMC_DATA_FORCED_PRG)) | CQHCI_DATA_TAG(!!(req_flags & MMC_DATA_DAT_TAG)) | @@ -425,8 +431,19 @@ static void cqhci_prep_task_desc(struct mmc_request *mrq, CQHCI_BLK_COUNT(mrq->data->blocks) | CQHCI_BLK_ADDR((u64)mrq->data->blk_addr); - pr_debug("%s: cqhci: tag %d task descriptor 0x%016llx\n", - mmc_hostname(mrq->host), mrq->tag, (unsigned long long)*data); + task_desc[0] = cpu_to_le64(desc0); + + if (cq_host->caps & CQHCI_TASK_DESC_SZ_128) { + u64 desc1 = cqhci_crypto_prep_task_desc(mrq); + + task_desc[1] = cpu_to_le64(desc1); + + pr_debug("%s: cqhci: tag %d task descriptor 0x%016llx%016llx\n", + mmc_hostname(mrq->host), mrq->tag, desc1, desc0); + } else { + pr_debug("%s: cqhci: tag %d task descriptor 0x%016llx\n", + mmc_hostname(mrq->host), mrq->tag, desc0); + } } static int cqhci_dma_map(struct mmc_host *host, struct mmc_request *mrq) @@ -567,8 +584,6 @@ static inline int cqhci_tag(struct mmc_request *mrq) static int cqhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { int err = 0; - u64 data = 0; - u64 *task_desc = NULL; int tag = cqhci_tag(mrq); struct cqhci_host *cq_host = mmc->cqe_private; unsigned long flags; @@ -598,9 +613,8 @@ static int cqhci_request(struct mmc_host *mmc, struct mmc_request *mrq) } if (mrq->data) { - task_desc = (__le64 __force *)get_desc(cq_host, tag); - cqhci_prep_task_desc(mrq, &data, 1); - *task_desc = cpu_to_le64(data); + cqhci_prep_task_desc(mrq, cq_host, tag); + err = cqhci_prep_tran_desc(mrq, cq_host, tag); if (err) { pr_err("%s: cqhci: failed to setup tx desc: %d\n", @@ -671,6 +685,7 @@ static void cqhci_error_irq(struct mmc_host *mmc, u32 status, int cmd_error, struct cqhci_host *cq_host = mmc->cqe_private; struct cqhci_slot *slot; u32 terri; + u32 tdpe; int tag; spin_lock(&cq_host->lock); @@ -709,6 +724,30 @@ static void cqhci_error_irq(struct mmc_host *mmc, u32 status, int cmd_error, } } + /* + * Handle ICCE ("Invalid Crypto Configuration Error"). This should + * never happen, since the block layer ensures that all crypto-enabled + * I/O requests have a valid keyslot before they reach the driver. + * + * Note that GCE ("General Crypto Error") is different; it already got + * handled above by checking TERRI. + */ + if (status & CQHCI_IS_ICCE) { + tdpe = cqhci_readl(cq_host, CQHCI_TDPE); + WARN_ONCE(1, + "%s: cqhci: invalid crypto configuration error. IRQ status: 0x%08x TDPE: 0x%08x\n", + mmc_hostname(mmc), status, tdpe); + while (tdpe != 0) { + tag = __ffs(tdpe); + tdpe &= ~(1 << tag); + slot = &cq_host->slot[tag]; + if (!slot->mrq) + continue; + slot->flags = cqhci_error_flags(data_error, cmd_error); + cqhci_recovery_needed(mmc, slot->mrq, true); + } + } + if (!cq_host->recovery_halt) { /* * The only way to guarantee forward progress is to mark at @@ -774,7 +813,8 @@ irqreturn_t cqhci_irq(struct mmc_host *mmc, u32 intmask, int cmd_error, pr_debug("%s: cqhci: IRQ status: 0x%08x\n", mmc_hostname(mmc), status); - if ((status & CQHCI_IS_RED) || cmd_error || data_error) + if ((status & (CQHCI_IS_RED | CQHCI_IS_GCE | CQHCI_IS_ICCE)) || + cmd_error || data_error) cqhci_error_irq(mmc, status, cmd_error, data_error); if (status & CQHCI_IS_TCC) { @@ -1141,6 +1181,13 @@ int cqhci_init(struct cqhci_host *cq_host, struct mmc_host *mmc, goto out_err; } + err = cqhci_crypto_init(cq_host); + if (err) { + pr_err("%s: CQHCI crypto initialization failed\n", + mmc_hostname(mmc)); + goto out_err; + } + spin_lock_init(&cq_host->lock); init_completion(&cq_host->halt_comp); diff --git a/drivers/mmc/host/cqhci-crypto.c b/drivers/mmc/host/cqhci-crypto.c new file mode 100644 index 000000000000..6419cfbb4ab7 --- /dev/null +++ b/drivers/mmc/host/cqhci-crypto.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CQHCI crypto engine (inline encryption) support + * + * Copyright 2020 Google LLC + */ + +#include +#include +#include + +#include "cqhci-crypto.h" + +/* Map from blk-crypto modes to CQHCI crypto algorithm IDs and key sizes */ +static const struct cqhci_crypto_alg_entry { + enum cqhci_crypto_alg alg; + enum cqhci_crypto_key_size key_size; +} cqhci_crypto_algs[BLK_ENCRYPTION_MODE_MAX] = { + [BLK_ENCRYPTION_MODE_AES_256_XTS] = { + .alg = CQHCI_CRYPTO_ALG_AES_XTS, + .key_size = CQHCI_CRYPTO_KEY_SIZE_256, + }, +}; + +static inline struct cqhci_host * +cqhci_host_from_ksm(struct blk_keyslot_manager *ksm) +{ + struct mmc_host *mmc = container_of(ksm, struct mmc_host, ksm); + + return mmc->cqe_private; +} + +static int cqhci_crypto_program_key(struct cqhci_host *cq_host, + const union cqhci_crypto_cfg_entry *cfg, + int slot) +{ + u32 slot_offset = cq_host->crypto_cfg_register + slot * sizeof(*cfg); + int i; + + if (cq_host->ops->program_key) + return cq_host->ops->program_key(cq_host, cfg, slot); + + /* Clear CFGE */ + cqhci_writel(cq_host, 0, slot_offset + 16 * sizeof(cfg->reg_val[0])); + + /* Write the key */ + for (i = 0; i < 16; i++) { + cqhci_writel(cq_host, le32_to_cpu(cfg->reg_val[i]), + slot_offset + i * sizeof(cfg->reg_val[0])); + } + /* Write dword 17 */ + cqhci_writel(cq_host, le32_to_cpu(cfg->reg_val[17]), + slot_offset + 17 * sizeof(cfg->reg_val[0])); + /* Write dword 16, which includes the new value of CFGE */ + cqhci_writel(cq_host, le32_to_cpu(cfg->reg_val[16]), + slot_offset + 16 * sizeof(cfg->reg_val[0])); + return 0; +} + +static int cqhci_crypto_keyslot_program(struct blk_keyslot_manager *ksm, + const struct blk_crypto_key *key, + unsigned int slot) + +{ + struct cqhci_host *cq_host = cqhci_host_from_ksm(ksm); + const union cqhci_crypto_cap_entry *ccap_array = + cq_host->crypto_cap_array; + const struct cqhci_crypto_alg_entry *alg = + &cqhci_crypto_algs[key->crypto_cfg.crypto_mode]; + u8 data_unit_mask = key->crypto_cfg.data_unit_size / 512; + int i; + int cap_idx = -1; + union cqhci_crypto_cfg_entry cfg = {}; + int err; + + BUILD_BUG_ON(CQHCI_CRYPTO_KEY_SIZE_INVALID != 0); + for (i = 0; i < cq_host->crypto_capabilities.num_crypto_cap; i++) { + if (ccap_array[i].algorithm_id == alg->alg && + ccap_array[i].key_size == alg->key_size && + (ccap_array[i].sdus_mask & data_unit_mask)) { + cap_idx = i; + break; + } + } + if (WARN_ON(cap_idx < 0)) + return -EOPNOTSUPP; + + cfg.data_unit_size = data_unit_mask; + cfg.crypto_cap_idx = cap_idx; + cfg.config_enable = CQHCI_CRYPTO_CONFIGURATION_ENABLE; + + if (ccap_array[cap_idx].algorithm_id == CQHCI_CRYPTO_ALG_AES_XTS) { + /* In XTS mode, the blk_crypto_key's size is already doubled */ + memcpy(cfg.crypto_key, key->raw, key->size/2); + memcpy(cfg.crypto_key + CQHCI_CRYPTO_KEY_MAX_SIZE/2, + key->raw + key->size/2, key->size/2); + } else { + memcpy(cfg.crypto_key, key->raw, key->size); + } + + err = cqhci_crypto_program_key(cq_host, &cfg, slot); + + memzero_explicit(&cfg, sizeof(cfg)); + return err; +} + +static int cqhci_crypto_clear_keyslot(struct cqhci_host *cq_host, int slot) +{ + /* + * Clear the crypto cfg on the device. Clearing CFGE + * might not be sufficient, so just clear the entire cfg. + */ + union cqhci_crypto_cfg_entry cfg = {}; + + return cqhci_crypto_program_key(cq_host, &cfg, slot); +} + +static int cqhci_crypto_keyslot_evict(struct blk_keyslot_manager *ksm, + const struct blk_crypto_key *key, + unsigned int slot) +{ + struct cqhci_host *cq_host = cqhci_host_from_ksm(ksm); + + return cqhci_crypto_clear_keyslot(cq_host, slot); +} + +/* + * The keyslot management operations for CQHCI crypto. + * + * Note that the block layer ensures that these are never called while the host + * controller is runtime-suspended. However, the CQE won't necessarily be + * "enabled" when these are called, i.e. CQHCI_ENABLE might not be set in the + * CQHCI_CFG register. But the hardware allows that. + */ +static const struct blk_ksm_ll_ops cqhci_ksm_ops = { + .keyslot_program = cqhci_crypto_keyslot_program, + .keyslot_evict = cqhci_crypto_keyslot_evict, +}; + +static enum blk_crypto_mode_num +cqhci_find_blk_crypto_mode(union cqhci_crypto_cap_entry cap) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cqhci_crypto_algs); i++) { + BUILD_BUG_ON(CQHCI_CRYPTO_KEY_SIZE_INVALID != 0); + if (cqhci_crypto_algs[i].alg == cap.algorithm_id && + cqhci_crypto_algs[i].key_size == cap.key_size) + return i; + } + return BLK_ENCRYPTION_MODE_INVALID; +} + +/** + * cqhci_crypto_init - initialize CQHCI crypto support + * @cq_host: a cqhci host + * + * If the driver previously set MMC_CAP2_CRYPTO and the CQE declares + * CQHCI_CAP_CS, initialize the crypto support. This involves reading the + * crypto capability registers, initializing the keyslot manager, clearing all + * keyslots, and enabling 128-bit task descriptors. + * + * Return: 0 if crypto was initialized or isn't supported; whether + * MMC_CAP2_CRYPTO remains set indicates which one of those cases it is. + * Also can return a negative errno value on unexpected error. + */ +int cqhci_crypto_init(struct cqhci_host *cq_host) +{ + struct mmc_host *mmc = cq_host->mmc; + struct device *dev = mmc_dev(mmc); + struct blk_keyslot_manager *ksm = &mmc->ksm; + unsigned int num_keyslots; + unsigned int cap_idx; + enum blk_crypto_mode_num blk_mode_num; + unsigned int slot; + int err = 0; + + if (!(mmc->caps2 & MMC_CAP2_CRYPTO) || + !(cqhci_readl(cq_host, CQHCI_CAP) & CQHCI_CAP_CS)) + goto out; + + cq_host->crypto_capabilities.reg_val = + cpu_to_le32(cqhci_readl(cq_host, CQHCI_CCAP)); + + cq_host->crypto_cfg_register = + (u32)cq_host->crypto_capabilities.config_array_ptr * 0x100; + + cq_host->crypto_cap_array = + devm_kcalloc(dev, cq_host->crypto_capabilities.num_crypto_cap, + sizeof(cq_host->crypto_cap_array[0]), GFP_KERNEL); + if (!cq_host->crypto_cap_array) { + err = -ENOMEM; + goto out; + } + + /* + * CCAP.CFGC is off by one, so the actual number of crypto + * configurations (a.k.a. keyslots) is CCAP.CFGC + 1. + */ + num_keyslots = cq_host->crypto_capabilities.config_count + 1; + + err = devm_blk_ksm_init(dev, ksm, num_keyslots); + if (err) + goto out; + + ksm->ksm_ll_ops = cqhci_ksm_ops; + ksm->dev = dev; + + /* Unfortunately, CQHCI crypto only supports 32 DUN bits. */ + ksm->max_dun_bytes_supported = 4; + + /* + * Cache all the crypto capabilities and advertise the supported crypto + * modes and data unit sizes to the block layer. + */ + for (cap_idx = 0; cap_idx < cq_host->crypto_capabilities.num_crypto_cap; + cap_idx++) { + cq_host->crypto_cap_array[cap_idx].reg_val = + cpu_to_le32(cqhci_readl(cq_host, + CQHCI_CRYPTOCAP + + cap_idx * sizeof(__le32))); + blk_mode_num = cqhci_find_blk_crypto_mode( + cq_host->crypto_cap_array[cap_idx]); + if (blk_mode_num == BLK_ENCRYPTION_MODE_INVALID) + continue; + ksm->crypto_modes_supported[blk_mode_num] |= + cq_host->crypto_cap_array[cap_idx].sdus_mask * 512; + } + + /* Clear all the keyslots so that we start in a known state. */ + for (slot = 0; slot < num_keyslots; slot++) + cqhci_crypto_clear_keyslot(cq_host, slot); + + /* CQHCI crypto requires the use of 128-bit task descriptors. */ + cq_host->caps |= CQHCI_TASK_DESC_SZ_128; + + return 0; + +out: + mmc->caps2 &= ~MMC_CAP2_CRYPTO; + return err; +} diff --git a/drivers/mmc/host/cqhci-crypto.h b/drivers/mmc/host/cqhci-crypto.h new file mode 100644 index 000000000000..60b58ee0e625 --- /dev/null +++ b/drivers/mmc/host/cqhci-crypto.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * CQHCI crypto engine (inline encryption) support + * + * Copyright 2020 Google LLC + */ + +#ifndef LINUX_MMC_CQHCI_CRYPTO_H +#define LINUX_MMC_CQHCI_CRYPTO_H + +#include + +#include "cqhci.h" + +#ifdef CONFIG_MMC_CRYPTO + +int cqhci_crypto_init(struct cqhci_host *host); + +/* + * Returns the crypto bits that should be set in bits 64-127 of the + * task descriptor. + */ +static inline u64 cqhci_crypto_prep_task_desc(struct mmc_request *mrq) +{ + if (!mrq->crypto_enabled) + return 0; + + return CQHCI_CRYPTO_ENABLE_BIT | + CQHCI_CRYPTO_KEYSLOT(mrq->crypto_key_slot) | + mrq->data_unit_num; +} + +#else /* CONFIG_MMC_CRYPTO */ + +static inline int cqhci_crypto_init(struct cqhci_host *host) +{ + return 0; +} + +static inline u64 cqhci_crypto_prep_task_desc(struct mmc_request *mrq) +{ + return 0; +} + +#endif /* !CONFIG_MMC_CRYPTO */ + +#endif /* LINUX_MMC_CQHCI_CRYPTO_H */ diff --git a/drivers/mmc/host/cqhci.h b/drivers/mmc/host/cqhci.h index 89bf6adbce8c..ba9387ed90eb 100644 --- a/drivers/mmc/host/cqhci.h +++ b/drivers/mmc/host/cqhci.h @@ -22,10 +22,13 @@ /* capabilities */ #define CQHCI_CAP 0x04 +#define CQHCI_CAP_CS 0x10000000 /* Crypto Support */ + /* configuration */ #define CQHCI_CFG 0x08 #define CQHCI_DCMD 0x00001000 #define CQHCI_TASK_DESC_SZ 0x00000100 +#define CQHCI_CRYPTO_GENERAL_ENABLE 0x00000002 #define CQHCI_ENABLE 0x00000001 /* control */ @@ -39,8 +42,11 @@ #define CQHCI_IS_TCC BIT(1) #define CQHCI_IS_RED BIT(2) #define CQHCI_IS_TCL BIT(3) +#define CQHCI_IS_GCE BIT(4) /* General Crypto Error */ +#define CQHCI_IS_ICCE BIT(5) /* Invalid Crypto Config Error */ -#define CQHCI_IS_MASK (CQHCI_IS_TCC | CQHCI_IS_RED) +#define CQHCI_IS_MASK (CQHCI_IS_TCC | CQHCI_IS_RED | \ + CQHCI_IS_GCE | CQHCI_IS_ICCE) /* interrupt status enable */ #define CQHCI_ISTE 0x14 @@ -78,6 +84,9 @@ /* task clear */ #define CQHCI_TCLR 0x38 +/* task descriptor processing error */ +#define CQHCI_TDPE 0x3c + /* send status config 1 */ #define CQHCI_SSC1 0x40 #define CQHCI_SSC1_CBC_MASK GENMASK(19, 16) @@ -107,6 +116,10 @@ /* command response argument */ #define CQHCI_CRA 0x5C +/* crypto capabilities */ +#define CQHCI_CCAP 0x100 +#define CQHCI_CRYPTOCAP 0x104 + #define CQHCI_INT_ALL 0xF #define CQHCI_IC_DEFAULT_ICCTH 31 #define CQHCI_IC_DEFAULT_ICTOVAL 1 @@ -133,11 +146,70 @@ #define CQHCI_CMD_TIMING(x) (((x) & 1) << 22) #define CQHCI_RESP_TYPE(x) (((x) & 0x3) << 23) +/* crypto task descriptor fields (for bits 64-127 of task descriptor) */ +#define CQHCI_CRYPTO_ENABLE_BIT (1ULL << 47) +#define CQHCI_CRYPTO_KEYSLOT(x) ((u64)(x) << 32) + /* transfer descriptor fields */ #define CQHCI_DAT_LENGTH(x) (((x) & 0xFFFF) << 16) #define CQHCI_DAT_ADDR_LO(x) (((x) & 0xFFFFFFFF) << 32) #define CQHCI_DAT_ADDR_HI(x) (((x) & 0xFFFFFFFF) << 0) +/* CCAP - Crypto Capability 100h */ +union cqhci_crypto_capabilities { + __le32 reg_val; + struct { + u8 num_crypto_cap; + u8 config_count; + u8 reserved; + u8 config_array_ptr; + }; +}; + +enum cqhci_crypto_key_size { + CQHCI_CRYPTO_KEY_SIZE_INVALID = 0, + CQHCI_CRYPTO_KEY_SIZE_128 = 1, + CQHCI_CRYPTO_KEY_SIZE_192 = 2, + CQHCI_CRYPTO_KEY_SIZE_256 = 3, + CQHCI_CRYPTO_KEY_SIZE_512 = 4, +}; + +enum cqhci_crypto_alg { + CQHCI_CRYPTO_ALG_AES_XTS = 0, + CQHCI_CRYPTO_ALG_BITLOCKER_AES_CBC = 1, + CQHCI_CRYPTO_ALG_AES_ECB = 2, + CQHCI_CRYPTO_ALG_ESSIV_AES_CBC = 3, +}; + +/* x-CRYPTOCAP - Crypto Capability X */ +union cqhci_crypto_cap_entry { + __le32 reg_val; + struct { + u8 algorithm_id; + u8 sdus_mask; /* Supported data unit size mask */ + u8 key_size; + u8 reserved; + }; +}; + +#define CQHCI_CRYPTO_CONFIGURATION_ENABLE (1 << 7) +#define CQHCI_CRYPTO_KEY_MAX_SIZE 64 +/* x-CRYPTOCFG - Crypto Configuration X */ +union cqhci_crypto_cfg_entry { + __le32 reg_val[32]; + struct { + u8 crypto_key[CQHCI_CRYPTO_KEY_MAX_SIZE]; + u8 data_unit_size; + u8 crypto_cap_idx; + u8 reserved_1; + u8 config_enable; + u8 reserved_multi_host; + u8 reserved_2; + u8 vsb[2]; + u8 reserved_3[56]; + }; +}; + struct cqhci_host_ops; struct mmc_host; struct mmc_request; @@ -196,6 +268,12 @@ struct cqhci_host { struct completion halt_comp; wait_queue_head_t wait_queue; struct cqhci_slot *slot; + +#ifdef CONFIG_MMC_CRYPTO + union cqhci_crypto_capabilities crypto_capabilities; + union cqhci_crypto_cap_entry *crypto_cap_array; + u32 crypto_cfg_register; +#endif }; struct cqhci_host_ops { @@ -208,6 +286,10 @@ struct cqhci_host_ops { u64 *data); void (*pre_enable)(struct mmc_host *mmc); void (*post_disable)(struct mmc_host *mmc); +#ifdef CONFIG_MMC_CRYPTO + int (*program_key)(struct cqhci_host *cq_host, + const union cqhci_crypto_cfg_entry *cfg, int slot); +#endif }; static inline void cqhci_writel(struct cqhci_host *host, u32 val, int reg) diff --git a/drivers/mmc/host/dw_mmc-zx.c b/drivers/mmc/host/dw_mmc-zx.c deleted file mode 100644 index 51bcc6332f3a..000000000000 --- a/drivers/mmc/host/dw_mmc-zx.c +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * ZX Specific Extensions for Synopsys DW Multimedia Card Interface driver - * - * Copyright (C) 2016, Linaro Ltd. - * Copyright (C) 2016, ZTE Corp. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dw_mmc.h" -#include "dw_mmc-pltfm.h" -#include "dw_mmc-zx.h" - -struct dw_mci_zx_priv_data { - struct regmap *sysc_base; -}; - -enum delay_type { - DELAY_TYPE_READ, /* read dqs delay */ - DELAY_TYPE_CLK, /* clk sample delay */ -}; - -static int dw_mci_zx_emmc_set_delay(struct dw_mci *host, unsigned int delay, - enum delay_type dflag) -{ - struct dw_mci_zx_priv_data *priv = host->priv; - struct regmap *sysc_base = priv->sysc_base; - unsigned int clksel; - unsigned int loop = 1000; - int ret; - - if (!sysc_base) - return -EINVAL; - - ret = regmap_update_bits(sysc_base, LB_AON_EMMC_CFG_REG0, - PARA_HALF_CLK_MODE | PARA_DLL_BYPASS_MODE | - PARA_PHASE_DET_SEL_MASK | - PARA_DLL_LOCK_NUM_MASK | - DLL_REG_SET | PARA_DLL_START_MASK, - PARA_DLL_START(4) | PARA_DLL_LOCK_NUM(4)); - if (ret) - return ret; - - ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG1, &clksel); - if (ret) - return ret; - - if (dflag == DELAY_TYPE_CLK) { - clksel &= ~CLK_SAMP_DELAY_MASK; - clksel |= CLK_SAMP_DELAY(delay); - } else { - clksel &= ~READ_DQS_DELAY_MASK; - clksel |= READ_DQS_DELAY(delay); - } - - regmap_write(sysc_base, LB_AON_EMMC_CFG_REG1, clksel); - regmap_update_bits(sysc_base, LB_AON_EMMC_CFG_REG0, - PARA_DLL_START_MASK | PARA_DLL_LOCK_NUM_MASK | - DLL_REG_SET, - PARA_DLL_START(4) | PARA_DLL_LOCK_NUM(4) | - DLL_REG_SET); - - do { - ret = regmap_read(sysc_base, LB_AON_EMMC_CFG_REG2, &clksel); - if (ret) - return ret; - - } while (--loop && !(clksel & ZX_DLL_LOCKED)); - - if (!loop) { - dev_err(host->dev, "Error: %s dll lock fail\n", __func__); - return -EIO; - } - - return 0; -} - -static int dw_mci_zx_emmc_execute_tuning(struct dw_mci_slot *slot, u32 opcode) -{ - struct dw_mci *host = slot->host; - struct mmc_host *mmc = slot->mmc; - int ret, len = 0, start = 0, end = 0, delay, best = 0; - - for (delay = 1; delay < 128; delay++) { - ret = dw_mci_zx_emmc_set_delay(host, delay, DELAY_TYPE_CLK); - if (!ret && mmc_send_tuning(mmc, opcode, NULL)) { - if (start >= 0) { - end = delay - 1; - /* check and update longest good range */ - if ((end - start) > len) { - best = (start + end) >> 1; - len = end - start; - } - } - start = -1; - end = 0; - continue; - } - if (start < 0) - start = delay; - } - - if (start >= 0) { - end = delay - 1; - if ((end - start) > len) { - best = (start + end) >> 1; - len = end - start; - } - } - if (best < 0) - return -EIO; - - dev_info(host->dev, "%s best range: start %d end %d\n", __func__, - start, end); - return dw_mci_zx_emmc_set_delay(host, best, DELAY_TYPE_CLK); -} - -static int dw_mci_zx_prepare_hs400_tuning(struct dw_mci *host, - struct mmc_ios *ios) -{ - int ret; - - /* config phase shift as 90 degree */ - ret = dw_mci_zx_emmc_set_delay(host, 32, DELAY_TYPE_READ); - if (ret < 0) - return -EIO; - - return 0; -} - -static int dw_mci_zx_execute_tuning(struct dw_mci_slot *slot, u32 opcode) -{ - struct dw_mci *host = slot->host; - - if (host->verid == 0x290a) /* only for emmc */ - return dw_mci_zx_emmc_execute_tuning(slot, opcode); - /* TODO: Add 0x210a dedicated tuning for sd/sdio */ - - return 0; -} - -static int dw_mci_zx_parse_dt(struct dw_mci *host) -{ - struct device_node *np = host->dev->of_node; - struct device_node *node; - struct dw_mci_zx_priv_data *priv; - struct regmap *sysc_base; - - /* syscon is needed only by emmc */ - node = of_parse_phandle(np, "zte,aon-syscon", 0); - if (node) { - sysc_base = syscon_node_to_regmap(node); - of_node_put(node); - - if (IS_ERR(sysc_base)) - return dev_err_probe(host->dev, PTR_ERR(sysc_base), - "Can't get syscon\n"); - } else { - return 0; - } - - priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - priv->sysc_base = sysc_base; - host->priv = priv; - - return 0; -} - -static unsigned long zx_dwmmc_caps[3] = { - MMC_CAP_CMD23, - MMC_CAP_CMD23, - MMC_CAP_CMD23, -}; - -static const struct dw_mci_drv_data zx_drv_data = { - .caps = zx_dwmmc_caps, - .num_caps = ARRAY_SIZE(zx_dwmmc_caps), - .execute_tuning = dw_mci_zx_execute_tuning, - .prepare_hs400_tuning = dw_mci_zx_prepare_hs400_tuning, - .parse_dt = dw_mci_zx_parse_dt, -}; - -static const struct of_device_id dw_mci_zx_match[] = { - { .compatible = "zte,zx296718-dw-mshc", .data = &zx_drv_data}, - {}, -}; -MODULE_DEVICE_TABLE(of, dw_mci_zx_match); - -static int dw_mci_zx_probe(struct platform_device *pdev) -{ - const struct dw_mci_drv_data *drv_data; - const struct of_device_id *match; - - match = of_match_node(dw_mci_zx_match, pdev->dev.of_node); - drv_data = match->data; - - return dw_mci_pltfm_register(pdev, drv_data); -} - -static const struct dev_pm_ops dw_mci_zx_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) - SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend, - dw_mci_runtime_resume, - NULL) -}; - -static struct platform_driver dw_mci_zx_pltfm_driver = { - .probe = dw_mci_zx_probe, - .remove = dw_mci_pltfm_remove, - .driver = { - .name = "dwmmc_zx", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - .of_match_table = dw_mci_zx_match, - .pm = &dw_mci_zx_dev_pm_ops, - }, -}; - -module_platform_driver(dw_mci_zx_pltfm_driver); - -MODULE_DESCRIPTION("ZTE emmc/sd driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/dw_mmc-zx.h b/drivers/mmc/host/dw_mmc-zx.h deleted file mode 100644 index 09ac52766f14..000000000000 --- a/drivers/mmc/host/dw_mmc-zx.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _DW_MMC_ZX_H_ -#define _DW_MMC_ZX_H_ - -/* ZX296718 SoC specific DLL register offset. */ -#define LB_AON_EMMC_CFG_REG0 0x1B0 -#define LB_AON_EMMC_CFG_REG1 0x1B4 -#define LB_AON_EMMC_CFG_REG2 0x1B8 - -/* LB_AON_EMMC_CFG_REG0 register defines */ -#define PARA_DLL_START(x) ((x) & 0xFF) -#define PARA_DLL_START_MASK 0xFF -#define DLL_REG_SET BIT(8) -#define PARA_DLL_LOCK_NUM(x) (((x) & 7) << 16) -#define PARA_DLL_LOCK_NUM_MASK (7 << 16) -#define PARA_PHASE_DET_SEL(x) (((x) & 7) << 20) -#define PARA_PHASE_DET_SEL_MASK (7 << 20) -#define PARA_DLL_BYPASS_MODE BIT(23) -#define PARA_HALF_CLK_MODE BIT(24) - -/* LB_AON_EMMC_CFG_REG1 register defines */ -#define READ_DQS_DELAY(x) ((x) & 0x7F) -#define READ_DQS_DELAY_MASK (0x7F) -#define READ_DQS_BYPASS_MODE BIT(7) -#define CLK_SAMP_DELAY(x) (((x) & 0x7F) << 8) -#define CLK_SAMP_DELAY_MASK (0x7F << 8) -#define CLK_SAMP_BYPASS_MODE BIT(15) - -/* LB_AON_EMMC_CFG_REG2 register defines */ -#define ZX_DLL_LOCKED BIT(2) - -#endif /* _DW_MMC_ZX_H_ */ diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index a5244435556b..2f4de30f650b 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1952,9 +1952,9 @@ static bool dw_mci_clear_pending_data_complete(struct dw_mci *host) return true; } -static void dw_mci_tasklet_func(unsigned long priv) +static void dw_mci_tasklet_func(struct tasklet_struct *t) { - struct dw_mci *host = (struct dw_mci *)priv; + struct dw_mci *host = from_tasklet(host, t, tasklet); struct mmc_data *data; struct mmc_command *cmd; struct mmc_request *mrq; @@ -3308,7 +3308,7 @@ int dw_mci_probe(struct dw_mci *host) else host->fifo_reg = host->regs + DATA_240A_OFFSET; - tasklet_init(&host->tasklet, dw_mci_tasklet_func, (unsigned long)host); + tasklet_setup(&host->tasklet, dw_mci_tasklet_func); ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt, host->irq_flags, "dw-mci", host); if (ret) diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index a1f92fed2a55..b3c636edbb46 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -152,7 +152,6 @@ struct jz4740_mmc_host { enum jz4740_mmc_version version; int irq; - int card_detect_irq; void __iomem *base; struct resource *mem_res; diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 13f6a2c0ed04..eb6c02bc4a02 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -227,7 +227,6 @@ static void meson_mmc_get_transfer_mode(struct mmc_host *mmc, struct mmc_data *data = mrq->data; struct scatterlist *sg; int i; - bool use_desc_chain_mode = true; /* * When Controller DMA cannot directly access DDR memory, disable @@ -237,25 +236,33 @@ static void meson_mmc_get_transfer_mode(struct mmc_host *mmc, if (host->dram_access_quirk) return; - /* - * Broken SDIO with AP6255-based WiFi on Khadas VIM Pro has been - * reported. For some strange reason this occurs in descriptor - * chain mode only. So let's fall back to bounce buffer mode - * for command SD_IO_RW_EXTENDED. - */ - if (mrq->cmd->opcode == SD_IO_RW_EXTENDED) - return; - - for_each_sg(data->sg, sg, data->sg_len, i) - /* check for 8 byte alignment */ - if (sg->offset & 7) { - WARN_ONCE(1, "unaligned scatterlist buffer\n"); - use_desc_chain_mode = false; - break; + if (data->blocks > 1) { + /* + * In block mode DMA descriptor format, "length" field indicates + * number of blocks and there is no way to pass DMA size that + * is not multiple of SDIO block size, making it impossible to + * tie more than one memory buffer with single SDIO block. + * Block mode sg buffer size should be aligned with SDIO block + * size, otherwise chain mode could not be used. + */ + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg->length % data->blksz) { + WARN_ONCE(1, "unaligned sg len %u blksize %u\n", + sg->length, data->blksz); + return; + } } + } - if (use_desc_chain_mode) - data->host_cookie |= SD_EMMC_DESC_CHAIN_MODE; + for_each_sg(data->sg, sg, data->sg_len, i) { + /* check for 8 byte alignment */ + if (sg->offset % 8) { + WARN_ONCE(1, "unaligned scatterlist buffer\n"); + return; + } + } + + data->host_cookie |= SD_EMMC_DESC_CHAIN_MODE; } static inline bool meson_mmc_desc_chain_mode(const struct mmc_data *data) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index b5a41a7ce165..1bc674577ff9 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -1888,6 +1889,65 @@ static struct mmc_host_ops mmci_ops = { .start_signal_voltage_switch = mmci_sig_volt_switch, }; +static void mmci_probe_level_translator(struct mmc_host *mmc) +{ + struct device *dev = mmc_dev(mmc); + struct mmci_host *host = mmc_priv(mmc); + struct gpio_desc *cmd_gpio; + struct gpio_desc *ck_gpio; + struct gpio_desc *ckin_gpio; + int clk_hi, clk_lo; + + /* + * Assume the level translator is present if st,use-ckin is set. + * This is to cater for DTs which do not implement this test. + */ + host->clk_reg_add |= MCI_STM32_CLK_SELCKIN; + + cmd_gpio = gpiod_get(dev, "st,cmd", GPIOD_OUT_HIGH); + if (IS_ERR(cmd_gpio)) + goto exit_cmd; + + ck_gpio = gpiod_get(dev, "st,ck", GPIOD_OUT_HIGH); + if (IS_ERR(ck_gpio)) + goto exit_ck; + + ckin_gpio = gpiod_get(dev, "st,ckin", GPIOD_IN); + if (IS_ERR(ckin_gpio)) + goto exit_ckin; + + /* All GPIOs are valid, test whether level translator works */ + + /* Sample CKIN */ + clk_hi = !!gpiod_get_value(ckin_gpio); + + /* Set CK low */ + gpiod_set_value(ck_gpio, 0); + + /* Sample CKIN */ + clk_lo = !!gpiod_get_value(ckin_gpio); + + /* Tristate all */ + gpiod_direction_input(cmd_gpio); + gpiod_direction_input(ck_gpio); + + /* Level translator is present if CK signal is propagated to CKIN */ + if (!clk_hi || clk_lo) { + host->clk_reg_add &= ~MCI_STM32_CLK_SELCKIN; + dev_warn(dev, + "Level translator inoperable, CK signal not detected on CKIN, disabling.\n"); + } + + gpiod_put(ckin_gpio); + +exit_ckin: + gpiod_put(ck_gpio); +exit_ck: + gpiod_put(cmd_gpio); +exit_cmd: + pinctrl_select_default_state(dev); +} + static int mmci_of_parse(struct device_node *np, struct mmc_host *mmc) { struct mmci_host *host = mmc_priv(mmc); @@ -1913,7 +1973,7 @@ static int mmci_of_parse(struct device_node *np, struct mmc_host *mmc) if (of_get_property(np, "st,neg-edge", NULL)) host->clk_reg_add |= MCI_STM32_CLK_NEGEDGE; if (of_get_property(np, "st,use-ckin", NULL)) - host->clk_reg_add |= MCI_STM32_CLK_SELCKIN; + mmci_probe_level_translator(mmc); if (of_get_property(np, "mmc-cap-mmc-highspeed", NULL)) mmc->caps |= MMC_CAP_MMC_HIGHSPEED; @@ -1949,15 +2009,15 @@ static int mmci_probe(struct amba_device *dev, if (!mmc) return -ENOMEM; - ret = mmci_of_parse(np, mmc); - if (ret) - goto host_free; - host = mmc_priv(mmc); host->mmc = mmc; host->mmc_ops = &mmci_ops; mmc->ops = &mmci_ops; + ret = mmci_of_parse(np, mmc); + if (ret) + goto host_free; + /* * Some variant (STM32) doesn't have opendrain bit, nevertheless * pins can be set accordingly using pinctrl diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index de09c6347524..898ed1b023df 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -1127,13 +1127,13 @@ static void msdc_track_cmd_data(struct msdc_host *host, static void msdc_request_done(struct msdc_host *host, struct mmc_request *mrq) { unsigned long flags; - bool ret; - ret = cancel_delayed_work(&host->req_timeout); - if (!ret) { - /* delay work already running */ - return; - } + /* + * No need check the return value of cancel_delayed_work, as only ONE + * path will go here! + */ + cancel_delayed_work(&host->req_timeout); + spin_lock_irqsave(&host->lock, flags); host->mrq = NULL; spin_unlock_irqrestore(&host->lock, flags); @@ -1155,7 +1155,7 @@ static bool msdc_cmd_done(struct msdc_host *host, int events, bool done = false; bool sbc_error; unsigned long flags; - u32 *rsp = cmd->resp; + u32 *rsp; if (mrq->sbc && cmd == mrq->cmd && (events & (MSDC_INT_ACMDRDY | MSDC_INT_ACMDCRCERR @@ -1176,6 +1176,7 @@ static bool msdc_cmd_done(struct msdc_host *host, int events, if (done) return true; + rsp = cmd->resp; sdr_clr_bits(host->base + MSDC_INTEN, cmd_ints_mask); @@ -1363,7 +1364,7 @@ static void msdc_data_xfer_next(struct msdc_host *host, static bool msdc_data_xfer_done(struct msdc_host *host, u32 events, struct mmc_request *mrq, struct mmc_data *data) { - struct mmc_command *stop = data->stop; + struct mmc_command *stop; unsigned long flags; bool done; unsigned int check_data = events & @@ -1379,6 +1380,7 @@ static bool msdc_data_xfer_done(struct msdc_host *host, u32 events, if (done) return true; + stop = data->stop; if (check_data || (stop && stop->error)) { dev_dbg(host->dev, "DMA status: 0x%8X\n", diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c index 56bbc6cd9c84..947581de7860 100644 --- a/drivers/mmc/host/mxs-mmc.c +++ b/drivers/mmc/host/mxs-mmc.c @@ -628,7 +628,7 @@ static int mxs_mmc_probe(struct platform_device *pdev) ret = mmc_of_parse(mmc); if (ret) - goto out_clk_disable; + goto out_free_dma; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 6aa0537f1f84..5e5af34090f1 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -878,9 +878,9 @@ static void mmc_omap_cover_timer(struct timer_list *t) tasklet_schedule(&slot->cover_tasklet); } -static void mmc_omap_cover_handler(unsigned long param) +static void mmc_omap_cover_handler(struct tasklet_struct *t) { - struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param; + struct mmc_omap_slot *slot = from_tasklet(slot, t, cover_tasklet); int cover_open = mmc_omap_cover_is_open(slot); mmc_detect_change(slot->mmc, 0); @@ -1269,8 +1269,7 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) if (slot->pdata->get_cover_state != NULL) { timer_setup(&slot->cover_timer, mmc_omap_cover_timer, 0); - tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, - (unsigned long)slot); + tasklet_setup(&slot->cover_tasklet, mmc_omap_cover_handler); } r = mmc_add_host(mmc); diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index aa9cc49206d1..2f8038d69f67 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -177,7 +177,7 @@ struct omap_hsmmc_host { struct regulator *pbias; bool pbias_enabled; void __iomem *base; - int vqmmc_enabled; + bool vqmmc_enabled; resource_size_t mapbase; spinlock_t irq_lock; /* Prevent races with irq handler */ unsigned int dma_len; @@ -232,7 +232,7 @@ static int omap_hsmmc_enable_supply(struct mmc_host *mmc) dev_err(mmc_dev(mmc), "vmmc_aux reg enable failed\n"); goto err_vqmmc; } - host->vqmmc_enabled = 1; + host->vqmmc_enabled = true; } return 0; @@ -256,7 +256,7 @@ static int omap_hsmmc_disable_supply(struct mmc_host *mmc) dev_err(mmc_dev(mmc), "vmmc_aux reg disable failed\n"); return ret; } - host->vqmmc_enabled = 0; + host->vqmmc_enabled = false; } if (!IS_ERR(mmc->supply.vmmc)) { @@ -285,22 +285,22 @@ static int omap_hsmmc_set_pbias(struct omap_hsmmc_host *host, bool power_on) return 0; if (power_on) { - if (host->pbias_enabled == 0) { + if (!host->pbias_enabled) { ret = regulator_enable(host->pbias); if (ret) { dev_err(host->dev, "pbias reg enable fail\n"); return ret; } - host->pbias_enabled = 1; + host->pbias_enabled = true; } } else { - if (host->pbias_enabled == 1) { + if (host->pbias_enabled) { ret = regulator_disable(host->pbias); if (ret) { dev_err(host->dev, "pbias reg disable fail\n"); return ret; } - host->pbias_enabled = 0; + host->pbias_enabled = false; } } @@ -1861,8 +1861,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev) host->base = base + pdata->reg_offset; host->power_mode = MMC_POWER_OFF; host->next_data.cookie = 1; - host->pbias_enabled = 0; - host->vqmmc_enabled = 0; + host->pbias_enabled = false; + host->vqmmc_enabled = false; platform_set_drvdata(pdev, host); diff --git a/drivers/mmc/host/owl-mmc.c b/drivers/mmc/host/owl-mmc.c index 53b81582f1af..5490962dc8e5 100644 --- a/drivers/mmc/host/owl-mmc.c +++ b/drivers/mmc/host/owl-mmc.c @@ -640,7 +640,7 @@ static int owl_mmc_probe(struct platform_device *pdev) owl_host->irq = platform_get_irq(pdev, 0); if (owl_host->irq < 0) { ret = -EINVAL; - goto err_free_host; + goto err_release_channel; } ret = devm_request_irq(&pdev->dev, owl_host->irq, owl_irq_handler, @@ -648,19 +648,21 @@ static int owl_mmc_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "Failed to request irq %d\n", owl_host->irq); - goto err_free_host; + goto err_release_channel; } ret = mmc_add_host(mmc); if (ret) { dev_err(&pdev->dev, "Failed to add host\n"); - goto err_free_host; + goto err_release_channel; } dev_dbg(&pdev->dev, "Owl MMC Controller Initialized\n"); return 0; +err_release_channel: + dma_release_channel(owl_host->dma); err_free_host: mmc_free_host(mmc); @@ -674,6 +676,7 @@ static int owl_mmc_remove(struct platform_device *pdev) mmc_remove_host(mmc); disable_irq(owl_host->irq); + dma_release_channel(owl_host->dma); mmc_free_host(mmc); return 0; diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c index 38f028e70633..158c21e5a942 100644 --- a/drivers/mmc/host/renesas_sdhi_core.c +++ b/drivers/mmc/host/renesas_sdhi_core.c @@ -768,10 +768,12 @@ static bool renesas_sdhi_auto_correction(struct tmio_mmc_host *host) return false; } -static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host) +static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host, + struct mmc_request *mrq) { struct renesas_sdhi *priv = host_to_priv(host); bool use_4tap = priv->quirks && priv->quirks->hs400_4taps; + bool ret = false; /* * Skip checking SCC errors when running on 4 taps in HS400 mode as @@ -785,11 +787,19 @@ static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host) if (mmc_doing_tune(host->mmc)) return false; + if (((mrq->cmd->error == -ETIMEDOUT) || + (mrq->data && mrq->data->error == -ETIMEDOUT)) && + ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || + (host->ops.get_cd && host->ops.get_cd(host->mmc)))) + ret |= true; + if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) & SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN) - return renesas_sdhi_auto_correction(host); + ret |= renesas_sdhi_auto_correction(host); + else + ret |= renesas_sdhi_manual_correction(host, use_4tap); - return renesas_sdhi_manual_correction(host, use_4tap); + return ret; } static int renesas_sdhi_wait_idle(struct tmio_mmc_host *host, u32 bit) diff --git a/drivers/mmc/host/renesas_sdhi_internal_dmac.c b/drivers/mmc/host/renesas_sdhi_internal_dmac.c index fe13e1ea22dc..ff97f15e317c 100644 --- a/drivers/mmc/host/renesas_sdhi_internal_dmac.c +++ b/drivers/mmc/host/renesas_sdhi_internal_dmac.c @@ -56,6 +56,12 @@ #define INFO2_DTRANERR1 BIT(17) #define INFO2_DTRANERR0 BIT(16) +enum renesas_sdhi_dma_cookie { + COOKIE_UNMAPPED, + COOKIE_PRE_MAPPED, + COOKIE_MAPPED, +}; + /* * Specification of this driver: * - host->chan_{rx,tx} will be used as a flag of enabling/disabling the dma @@ -172,6 +178,50 @@ renesas_sdhi_internal_dmac_dataend_dma(struct tmio_mmc_host *host) { tasklet_schedule(&priv->dma_priv.dma_complete); } +/* + * renesas_sdhi_internal_dmac_map() will be called with two difference + * sg pointers in two mmc_data by .pre_req(), but tmio host can have a single + * sg_ptr only. So, renesas_sdhi_internal_dmac_{un}map() should use a sg + * pointer in a mmc_data instead of host->sg_ptr. + */ +static void +renesas_sdhi_internal_dmac_unmap(struct tmio_mmc_host *host, + struct mmc_data *data, + enum renesas_sdhi_dma_cookie cookie) +{ + bool unmap = cookie == COOKIE_UNMAPPED ? (data->host_cookie != cookie) : + (data->host_cookie == cookie); + + if (unmap) { + dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); + data->host_cookie = COOKIE_UNMAPPED; + } +} + +static bool +renesas_sdhi_internal_dmac_map(struct tmio_mmc_host *host, + struct mmc_data *data, + enum renesas_sdhi_dma_cookie cookie) +{ + if (data->host_cookie == COOKIE_PRE_MAPPED) + return true; + + if (!dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data))) + return false; + + data->host_cookie = cookie; + + /* This DMAC cannot handle if buffer is not 128-bytes alignment */ + if (!IS_ALIGNED(sg_dma_address(data->sg), 128)) { + renesas_sdhi_internal_dmac_unmap(host, data, cookie); + return false; + } + + return true; +} + static void renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host, struct mmc_data *data) @@ -182,14 +232,9 @@ renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host, if (!test_bit(SDHI_INTERNAL_DMAC_ADDR_MODE_FIXED_ONLY, &global_flags)) dtran_mode |= DTRAN_MODE_ADDR_MODE; - if (!dma_map_sg(&host->pdev->dev, sg, host->sg_len, - mmc_get_dma_dir(data))) + if (!renesas_sdhi_internal_dmac_map(host, data, COOKIE_MAPPED)) goto force_pio; - /* This DMAC cannot handle if buffer is not 8-bytes alignment */ - if (!IS_ALIGNED(sg_dma_address(sg), 8)) - goto force_pio_with_unmap; - if (data->flags & MMC_DATA_READ) { dtran_mode |= DTRAN_MODE_CH_NUM_CH1; if (test_bit(SDHI_INTERNAL_DMAC_ONE_RX_ONLY, &global_flags) && @@ -212,7 +257,7 @@ renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host, return; force_pio_with_unmap: - dma_unmap_sg(&host->pdev->dev, sg, host->sg_len, mmc_get_dma_dir(data)); + renesas_sdhi_internal_dmac_unmap(host, data, COOKIE_UNMAPPED); force_pio: renesas_sdhi_internal_dmac_enable_dma(host, false); @@ -245,7 +290,7 @@ static bool renesas_sdhi_internal_dmac_complete(struct tmio_mmc_host *host) dir = DMA_TO_DEVICE; renesas_sdhi_internal_dmac_enable_dma(host, false); - dma_unmap_sg(&host->pdev->dev, host->sg_ptr, host->sg_len, dir); + renesas_sdhi_internal_dmac_unmap(host, host->data, COOKIE_MAPPED); if (dir == DMA_FROM_DEVICE) clear_bit(SDHI_INTERNAL_DMAC_RX_IN_USE, &global_flags); @@ -274,6 +319,32 @@ static void renesas_sdhi_internal_dmac_end_dma(struct tmio_mmc_host *host) renesas_sdhi_internal_dmac_complete(host); } +static void renesas_sdhi_internal_dmac_post_req(struct mmc_host *mmc, + struct mmc_request *mrq, + int err) +{ + struct tmio_mmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + renesas_sdhi_internal_dmac_unmap(host, data, COOKIE_UNMAPPED); +} + +static void renesas_sdhi_internal_dmac_pre_req(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct tmio_mmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + data->host_cookie = COOKIE_UNMAPPED; + renesas_sdhi_internal_dmac_map(host, data, COOKIE_PRE_MAPPED); +} + static void renesas_sdhi_internal_dmac_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata) @@ -295,6 +366,10 @@ renesas_sdhi_internal_dmac_request_dma(struct tmio_mmc_host *host, tasklet_init(&host->dma_issue, renesas_sdhi_internal_dmac_issue_tasklet_fn, (unsigned long)host); + + /* Add pre_req and post_req */ + host->ops.pre_req = renesas_sdhi_internal_dmac_pre_req; + host->ops.post_req = renesas_sdhi_internal_dmac_post_req; } static void diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index e6f5bbce5685..4ca937415734 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -906,6 +906,8 @@ static int sd_power_on(struct realtek_pci_sdmmc *host) if (host->power_state == SDMMC_POWER_ON) return 0; + msleep(100); + rtsx_pci_init_cmd(pcr); rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SHARE_MODE, @@ -1425,7 +1427,8 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; if (pcr->rtd3_en) mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; - mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE; + mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | + MMC_CAP2_NO_SDIO; mmc->max_current_330 = 400; mmc->max_current_180 = 800; mmc->ops = &realtek_pci_sdmmc_ops; diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c index a33a7823c265..0ca6f6d30b75 100644 --- a/drivers/mmc/host/s3cmci.c +++ b/drivers/mmc/host/s3cmci.c @@ -540,9 +540,9 @@ static void do_pio_write(struct s3cmci_host *host) enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); } -static void pio_tasklet(unsigned long data) +static void pio_tasklet(struct tasklet_struct *t) { - struct s3cmci_host *host = (struct s3cmci_host *) data; + struct s3cmci_host *host = from_tasklet(host, t, pio_tasklet); s3cmci_disable_irq(host, true); @@ -1532,7 +1532,7 @@ static int s3cmci_probe(struct platform_device *pdev) host->pdata = pdev->dev.platform_data; spin_lock_init(&host->complete_lock); - tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host); + tasklet_setup(&host->pio_tasklet, pio_tasklet); if (host->is2440) { host->sdiimsk = S3C2440_SDIIMSK; diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 16ed19f47939..a20459744d21 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1666,9 +1666,10 @@ static int sdhci_esdhc_imx_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host); - int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + int dead; pm_runtime_get_sync(&pdev->dev); + dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); diff --git a/drivers/mmc/host/sdhci-iproc.c b/drivers/mmc/host/sdhci-iproc.c index c9434b461aab..ddeaf8e1f72f 100644 --- a/drivers/mmc/host/sdhci-iproc.c +++ b/drivers/mmc/host/sdhci-iproc.c @@ -296,9 +296,27 @@ static const struct of_device_id sdhci_iproc_of_match[] = { MODULE_DEVICE_TABLE(of, sdhci_iproc_of_match); #ifdef CONFIG_ACPI +/* + * This is a duplicate of bcm2835_(pltfrm_)data without caps quirks + * which are provided by the ACPI table. + */ +static const struct sdhci_pltfm_data sdhci_bcm_arasan_data = { + .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_NO_HISPD_BIT, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + .ops = &sdhci_iproc_32only_ops, +}; + +static const struct sdhci_iproc_data bcm_arasan_data = { + .pdata = &sdhci_bcm_arasan_data, +}; + static const struct acpi_device_id sdhci_iproc_acpi_ids[] = { { .id = "BRCM5871", .driver_data = (kernel_ulong_t)&iproc_cygnus_data }, { .id = "BRCM5872", .driver_data = (kernel_ulong_t)&iproc_data }, + { .id = "BCM2847", .driver_data = (kernel_ulong_t)&bcm_arasan_data }, + { .id = "BRCME88C", .driver_data = (kernel_ulong_t)&bcm2711_data }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(acpi, sdhci_iproc_acpi_ids); diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9c7927b03253..5e1da4df096f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -255,10 +256,12 @@ struct sdhci_msm_variant_info { struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ + void __iomem *ice_mem; /* MSM ICE mapped address (if available) */ int pwr_irq; /* power irq */ struct clk *bus_clk; /* SDHC bus voter clock */ struct clk *xo_clk; /* TCXO clk needed for FLL feature of cm_dll*/ - struct clk_bulk_data bulk_clks[4]; /* core, iface, cal, sleep clocks */ + /* core, iface, cal, sleep, and ice clocks */ + struct clk_bulk_data bulk_clks[5]; unsigned long clk_rate; struct mmc_host *mmc; struct opp_table *opp_table; @@ -327,8 +330,7 @@ static void sdhci_msm_v5_variant_writel_relaxed(u32 val, writel_relaxed(val, host->ioaddr + offset); } -static unsigned int msm_get_clock_rate_for_bus_mode(struct sdhci_host *host, - unsigned int clock) +static unsigned int msm_get_clock_mult_for_bus_mode(struct sdhci_host *host) { struct mmc_ios ios = host->mmc->ios; /* @@ -341,8 +343,8 @@ static unsigned int msm_get_clock_rate_for_bus_mode(struct sdhci_host *host, ios.timing == MMC_TIMING_MMC_DDR52 || ios.timing == MMC_TIMING_MMC_HS400 || host->flags & SDHCI_HS400_TUNING) - clock *= 2; - return clock; + return 2; + return 1; } static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host, @@ -352,20 +354,36 @@ static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host, struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); struct mmc_ios curr_ios = host->mmc->ios; struct clk *core_clk = msm_host->bulk_clks[0].clk; + unsigned long achieved_rate; + unsigned int desired_rate; + unsigned int mult; int rc; - clock = msm_get_clock_rate_for_bus_mode(host, clock); - rc = dev_pm_opp_set_rate(mmc_dev(host->mmc), clock); + mult = msm_get_clock_mult_for_bus_mode(host); + desired_rate = clock * mult; + rc = dev_pm_opp_set_rate(mmc_dev(host->mmc), desired_rate); if (rc) { pr_err("%s: Failed to set clock at rate %u at timing %d\n", - mmc_hostname(host->mmc), clock, - curr_ios.timing); + mmc_hostname(host->mmc), desired_rate, curr_ios.timing); return; } - msm_host->clk_rate = clock; + + /* + * Qualcomm clock drivers by default round clock _up_ if they can't + * make the requested rate. This is not good for SD. Yell if we + * encounter it. + */ + achieved_rate = clk_get_rate(core_clk); + if (achieved_rate > desired_rate) + pr_warn("%s: Card appears overclocked; req %u Hz, actual %lu Hz\n", + mmc_hostname(host->mmc), desired_rate, achieved_rate); + host->mmc->actual_clock = achieved_rate / mult; + + /* Stash the rate we requested to use in sdhci_msm_runtime_resume() */ + msm_host->clk_rate = desired_rate; + pr_debug("%s: Setting clock at rate %lu at timing %d\n", - mmc_hostname(host->mmc), clk_get_rate(core_clk), - curr_ios.timing); + mmc_hostname(host->mmc), achieved_rate, curr_ios.timing); } /* Platform specific tuning */ @@ -1744,13 +1762,6 @@ static unsigned int sdhci_msm_get_min_clock(struct sdhci_host *host) static void __sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) { u16 clk; - /* - * Keep actual_clock as zero - - * - since there is no divider used so no need of having actual_clock. - * - MSM controller uses SDCLK for data timeout calculation. If - * actual_clock is zero, host->clock is taken for calculation. - */ - host->mmc->actual_clock = 0; sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); @@ -1773,7 +1784,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); if (!clock) { - msm_host->clk_rate = clock; + host->mmc->actual_clock = msm_host->clk_rate = 0; goto out; } @@ -1784,6 +1795,246 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) __sdhci_msm_set_clock(host, clock); } +/*****************************************************************************\ + * * + * Inline Crypto Engine (ICE) support * + * * +\*****************************************************************************/ + +#ifdef CONFIG_MMC_CRYPTO + +#define AES_256_XTS_KEY_SIZE 64 + +/* QCOM ICE registers */ + +#define QCOM_ICE_REG_VERSION 0x0008 + +#define QCOM_ICE_REG_FUSE_SETTING 0x0010 +#define QCOM_ICE_FUSE_SETTING_MASK 0x1 +#define QCOM_ICE_FORCE_HW_KEY0_SETTING_MASK 0x2 +#define QCOM_ICE_FORCE_HW_KEY1_SETTING_MASK 0x4 + +#define QCOM_ICE_REG_BIST_STATUS 0x0070 +#define QCOM_ICE_BIST_STATUS_MASK 0xF0000000 + +#define QCOM_ICE_REG_ADVANCED_CONTROL 0x1000 + +#define sdhci_msm_ice_writel(host, val, reg) \ + writel((val), (host)->ice_mem + (reg)) +#define sdhci_msm_ice_readl(host, reg) \ + readl((host)->ice_mem + (reg)) + +static bool sdhci_msm_ice_supported(struct sdhci_msm_host *msm_host) +{ + struct device *dev = mmc_dev(msm_host->mmc); + u32 regval = sdhci_msm_ice_readl(msm_host, QCOM_ICE_REG_VERSION); + int major = regval >> 24; + int minor = (regval >> 16) & 0xFF; + int step = regval & 0xFFFF; + + /* For now this driver only supports ICE version 3. */ + if (major != 3) { + dev_warn(dev, "Unsupported ICE version: v%d.%d.%d\n", + major, minor, step); + return false; + } + + dev_info(dev, "Found QC Inline Crypto Engine (ICE) v%d.%d.%d\n", + major, minor, step); + + /* If fuses are blown, ICE might not work in the standard way. */ + regval = sdhci_msm_ice_readl(msm_host, QCOM_ICE_REG_FUSE_SETTING); + if (regval & (QCOM_ICE_FUSE_SETTING_MASK | + QCOM_ICE_FORCE_HW_KEY0_SETTING_MASK | + QCOM_ICE_FORCE_HW_KEY1_SETTING_MASK)) { + dev_warn(dev, "Fuses are blown; ICE is unusable!\n"); + return false; + } + return true; +} + +static inline struct clk *sdhci_msm_ice_get_clk(struct device *dev) +{ + return devm_clk_get(dev, "ice"); +} + +static int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host, + struct cqhci_host *cq_host) +{ + struct mmc_host *mmc = msm_host->mmc; + struct device *dev = mmc_dev(mmc); + struct resource *res; + int err; + + if (!(cqhci_readl(cq_host, CQHCI_CAP) & CQHCI_CAP_CS)) + return 0; + + res = platform_get_resource_byname(msm_host->pdev, IORESOURCE_MEM, + "ice"); + if (!res) { + dev_warn(dev, "ICE registers not found\n"); + goto disable; + } + + if (!qcom_scm_ice_available()) { + dev_warn(dev, "ICE SCM interface not found\n"); + goto disable; + } + + msm_host->ice_mem = devm_ioremap_resource(dev, res); + if (IS_ERR(msm_host->ice_mem)) { + err = PTR_ERR(msm_host->ice_mem); + dev_err(dev, "Failed to map ICE registers; err=%d\n", err); + return err; + } + + if (!sdhci_msm_ice_supported(msm_host)) + goto disable; + + mmc->caps2 |= MMC_CAP2_CRYPTO; + return 0; + +disable: + dev_warn(dev, "Disabling inline encryption support\n"); + return 0; +} + +static void sdhci_msm_ice_low_power_mode_enable(struct sdhci_msm_host *msm_host) +{ + u32 regval; + + regval = sdhci_msm_ice_readl(msm_host, QCOM_ICE_REG_ADVANCED_CONTROL); + /* + * Enable low power mode sequence + * [0]-0, [1]-0, [2]-0, [3]-E, [4]-0, [5]-0, [6]-0, [7]-0 + */ + regval |= 0x7000; + sdhci_msm_ice_writel(msm_host, regval, QCOM_ICE_REG_ADVANCED_CONTROL); +} + +static void sdhci_msm_ice_optimization_enable(struct sdhci_msm_host *msm_host) +{ + u32 regval; + + /* ICE Optimizations Enable Sequence */ + regval = sdhci_msm_ice_readl(msm_host, QCOM_ICE_REG_ADVANCED_CONTROL); + regval |= 0xD807100; + /* ICE HPG requires delay before writing */ + udelay(5); + sdhci_msm_ice_writel(msm_host, regval, QCOM_ICE_REG_ADVANCED_CONTROL); + udelay(5); +} + +/* + * Wait until the ICE BIST (built-in self-test) has completed. + * + * This may be necessary before ICE can be used. + * + * Note that we don't really care whether the BIST passed or failed; we really + * just want to make sure that it isn't still running. This is because (a) the + * BIST is a FIPS compliance thing that never fails in practice, (b) ICE is + * documented to reject crypto requests if the BIST fails, so we needn't do it + * in software too, and (c) properly testing storage encryption requires testing + * the full storage stack anyway, and not relying on hardware-level self-tests. + */ +static int sdhci_msm_ice_wait_bist_status(struct sdhci_msm_host *msm_host) +{ + u32 regval; + int err; + + err = readl_poll_timeout(msm_host->ice_mem + QCOM_ICE_REG_BIST_STATUS, + regval, !(regval & QCOM_ICE_BIST_STATUS_MASK), + 50, 5000); + if (err) + dev_err(mmc_dev(msm_host->mmc), + "Timed out waiting for ICE self-test to complete\n"); + return err; +} + +static void sdhci_msm_ice_enable(struct sdhci_msm_host *msm_host) +{ + if (!(msm_host->mmc->caps2 & MMC_CAP2_CRYPTO)) + return; + sdhci_msm_ice_low_power_mode_enable(msm_host); + sdhci_msm_ice_optimization_enable(msm_host); + sdhci_msm_ice_wait_bist_status(msm_host); +} + +static int __maybe_unused sdhci_msm_ice_resume(struct sdhci_msm_host *msm_host) +{ + if (!(msm_host->mmc->caps2 & MMC_CAP2_CRYPTO)) + return 0; + return sdhci_msm_ice_wait_bist_status(msm_host); +} + +/* + * Program a key into a QC ICE keyslot, or evict a keyslot. QC ICE requires + * vendor-specific SCM calls for this; it doesn't support the standard way. + */ +static int sdhci_msm_program_key(struct cqhci_host *cq_host, + const union cqhci_crypto_cfg_entry *cfg, + int slot) +{ + struct device *dev = mmc_dev(cq_host->mmc); + union cqhci_crypto_cap_entry cap; + union { + u8 bytes[AES_256_XTS_KEY_SIZE]; + u32 words[AES_256_XTS_KEY_SIZE / sizeof(u32)]; + } key; + int i; + int err; + + if (!(cfg->config_enable & CQHCI_CRYPTO_CONFIGURATION_ENABLE)) + return qcom_scm_ice_invalidate_key(slot); + + /* Only AES-256-XTS has been tested so far. */ + cap = cq_host->crypto_cap_array[cfg->crypto_cap_idx]; + if (cap.algorithm_id != CQHCI_CRYPTO_ALG_AES_XTS || + cap.key_size != CQHCI_CRYPTO_KEY_SIZE_256) { + dev_err_ratelimited(dev, + "Unhandled crypto capability; algorithm_id=%d, key_size=%d\n", + cap.algorithm_id, cap.key_size); + return -EINVAL; + } + + memcpy(key.bytes, cfg->crypto_key, AES_256_XTS_KEY_SIZE); + + /* + * The SCM call byte-swaps the 32-bit words of the key. So we have to + * do the same, in order for the final key be correct. + */ + for (i = 0; i < ARRAY_SIZE(key.words); i++) + __cpu_to_be32s(&key.words[i]); + + err = qcom_scm_ice_set_key(slot, key.bytes, AES_256_XTS_KEY_SIZE, + QCOM_SCM_ICE_CIPHER_AES_256_XTS, + cfg->data_unit_size); + memzero_explicit(&key, sizeof(key)); + return err; +} +#else /* CONFIG_MMC_CRYPTO */ +static inline struct clk *sdhci_msm_ice_get_clk(struct device *dev) +{ + return NULL; +} + +static inline int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host, + struct cqhci_host *cq_host) +{ + return 0; +} + +static inline void sdhci_msm_ice_enable(struct sdhci_msm_host *msm_host) +{ +} + +static inline int __maybe_unused +sdhci_msm_ice_resume(struct sdhci_msm_host *msm_host) +{ + return 0; +} +#endif /* !CONFIG_MMC_CRYPTO */ + /*****************************************************************************\ * * * MSM Command Queue Engine (CQE) * @@ -1802,6 +2053,16 @@ static u32 sdhci_msm_cqe_irq(struct sdhci_host *host, u32 intmask) return 0; } +static void sdhci_msm_cqe_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + + sdhci_cqe_enable(mmc); + sdhci_msm_ice_enable(msm_host); +} + static void sdhci_msm_cqe_disable(struct mmc_host *mmc, bool recovery) { struct sdhci_host *host = mmc_priv(mmc); @@ -1834,8 +2095,11 @@ static void sdhci_msm_cqe_disable(struct mmc_host *mmc, bool recovery) } static const struct cqhci_host_ops sdhci_msm_cqhci_ops = { - .enable = sdhci_cqe_enable, + .enable = sdhci_msm_cqe_enable, .disable = sdhci_msm_cqe_disable, +#ifdef CONFIG_MMC_CRYPTO + .program_key = sdhci_msm_program_key, +#endif }; static int sdhci_msm_cqe_add_host(struct sdhci_host *host, @@ -1871,6 +2135,10 @@ static int sdhci_msm_cqe_add_host(struct sdhci_host *host, dma64 = host->flags & SDHCI_USE_64_BIT_DMA; + ret = sdhci_msm_ice_init(msm_host, cq_host); + if (ret) + goto cleanup; + ret = cqhci_init(cq_host, host->mmc, dma64); if (ret) { dev_err(&pdev->dev, "%s: CQE init: failed (%d)\n", @@ -2311,6 +2579,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) clk = NULL; msm_host->bulk_clks[3].clk = clk; + clk = sdhci_msm_ice_get_clk(&pdev->dev); + if (IS_ERR(clk)) + clk = NULL; + msm_host->bulk_clks[4].clk = clk; + ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); if (ret) @@ -2524,12 +2797,15 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) * Whenever core-clock is gated dynamically, it's needed to * restore the SDR DLL settings when the clock is ungated. */ - if (msm_host->restore_dll_config && msm_host->clk_rate) + if (msm_host->restore_dll_config && msm_host->clk_rate) { ret = sdhci_msm_restore_sdr_dll_config(host); + if (ret) + return ret; + } dev_pm_opp_set_rate(dev, msm_host->clk_rate); - return ret; + return sdhci_msm_ice_resume(msm_host); } static const struct dev_pm_ops sdhci_msm_pm_ops = { diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 4f3774bcda94..839965f7c717 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -1380,26 +1380,25 @@ static void sdhci_arasan_unregister_sdclk(struct device *dev) /** * sdhci_arasan_update_support64b - Set SUPPORT_64B (64-bit System Bus Support) + * @host: The sdhci_host + * @value: The value to write * * This should be set based on the System Address Bus. * 0: the Core supports only 32-bit System Address Bus. * 1: the Core supports 64-bit System Address Bus. * - * NOTES: - * - For Keem Bay, it is required to clear this bit. Its default value is 1'b1. - * Keem Bay does not support 64-bit access. - * - * @host: The sdhci_host - * @value: The value to write + * NOTE: + * For Keem Bay, it is required to clear this bit. Its default value is 1'b1. + * Keem Bay does not support 64-bit access. */ static void sdhci_arasan_update_support64b(struct sdhci_host *host, u32 value) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); - const struct sdhci_arasan_soc_ctl_map *soc_ctl_map = - sdhci_arasan->soc_ctl_map; + const struct sdhci_arasan_soc_ctl_map *soc_ctl_map; /* Having a map is optional */ + soc_ctl_map = sdhci_arasan->soc_ctl_map; if (!soc_ctl_map) return; @@ -1508,17 +1507,16 @@ static int sdhci_arasan_add_host(struct sdhci_arasan_data *sdhci_arasan) static int sdhci_arasan_probe(struct platform_device *pdev) { int ret; - const struct of_device_id *match; struct device_node *node; struct clk *clk_xin; struct sdhci_host *host; struct sdhci_pltfm_host *pltfm_host; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct sdhci_arasan_data *sdhci_arasan; - struct device_node *np = pdev->dev.of_node; const struct sdhci_arasan_of_data *data; - match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node); - data = match->data; + data = of_device_get_match_data(dev); host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*sdhci_arasan)); if (IS_ERR(host)) @@ -1531,42 +1529,41 @@ static int sdhci_arasan_probe(struct platform_device *pdev) sdhci_arasan->soc_ctl_map = data->soc_ctl_map; sdhci_arasan->clk_ops = data->clk_ops; - node = of_parse_phandle(pdev->dev.of_node, "arasan,soc-ctl-syscon", 0); + node = of_parse_phandle(np, "arasan,soc-ctl-syscon", 0); if (node) { sdhci_arasan->soc_ctl_base = syscon_node_to_regmap(node); of_node_put(node); if (IS_ERR(sdhci_arasan->soc_ctl_base)) { - ret = dev_err_probe(&pdev->dev, + ret = dev_err_probe(dev, PTR_ERR(sdhci_arasan->soc_ctl_base), "Can't get syscon\n"); goto err_pltfm_free; } } - sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb"); + sdhci_arasan->clk_ahb = devm_clk_get(dev, "clk_ahb"); if (IS_ERR(sdhci_arasan->clk_ahb)) { - dev_err(&pdev->dev, "clk_ahb clock not found.\n"); - ret = PTR_ERR(sdhci_arasan->clk_ahb); + ret = dev_err_probe(dev, PTR_ERR(sdhci_arasan->clk_ahb), + "clk_ahb clock not found.\n"); goto err_pltfm_free; } - clk_xin = devm_clk_get(&pdev->dev, "clk_xin"); + clk_xin = devm_clk_get(dev, "clk_xin"); if (IS_ERR(clk_xin)) { - dev_err(&pdev->dev, "clk_xin clock not found.\n"); - ret = PTR_ERR(clk_xin); + ret = dev_err_probe(dev, PTR_ERR(clk_xin), "clk_xin clock not found.\n"); goto err_pltfm_free; } ret = clk_prepare_enable(sdhci_arasan->clk_ahb); if (ret) { - dev_err(&pdev->dev, "Unable to enable AHB clock.\n"); + dev_err(dev, "Unable to enable AHB clock.\n"); goto err_pltfm_free; } ret = clk_prepare_enable(clk_xin); if (ret) { - dev_err(&pdev->dev, "Unable to enable SD clock.\n"); + dev_err(dev, "Unable to enable SD clock.\n"); goto clk_dis_ahb; } @@ -1580,8 +1577,7 @@ static int sdhci_arasan_probe(struct platform_device *pdev) pltfm_host->clk = clk_xin; - if (of_device_is_compatible(pdev->dev.of_node, - "rockchip,rk3399-sdhci-5.1")) + if (of_device_is_compatible(np, "rockchip,rk3399-sdhci-5.1")) sdhci_arasan_update_clockmultiplier(host, 0x0); if (of_device_is_compatible(np, "intel,keembay-sdhci-5.1-emmc") || @@ -1595,7 +1591,7 @@ static int sdhci_arasan_probe(struct platform_device *pdev) sdhci_arasan_update_baseclkfreq(host); - ret = sdhci_arasan_register_sdclk(sdhci_arasan, clk_xin, &pdev->dev); + ret = sdhci_arasan_register_sdclk(sdhci_arasan, clk_xin, dev); if (ret) goto clk_disable_all; @@ -1604,29 +1600,26 @@ static int sdhci_arasan_probe(struct platform_device *pdev) arasan_zynqmp_execute_tuning; } - arasan_dt_parse_clk_phases(&pdev->dev, &sdhci_arasan->clk_data); + arasan_dt_parse_clk_phases(dev, &sdhci_arasan->clk_data); ret = mmc_of_parse(host->mmc); if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "parsing dt failed (%d)\n", ret); + ret = dev_err_probe(dev, ret, "parsing dt failed.\n"); goto unreg_clk; } sdhci_arasan->phy = ERR_PTR(-ENODEV); - if (of_device_is_compatible(pdev->dev.of_node, - "arasan,sdhci-5.1")) { - sdhci_arasan->phy = devm_phy_get(&pdev->dev, - "phy_arasan"); + if (of_device_is_compatible(np, "arasan,sdhci-5.1")) { + sdhci_arasan->phy = devm_phy_get(dev, "phy_arasan"); if (IS_ERR(sdhci_arasan->phy)) { - ret = PTR_ERR(sdhci_arasan->phy); - dev_err(&pdev->dev, "No phy for arasan,sdhci-5.1.\n"); + ret = dev_err_probe(dev, PTR_ERR(sdhci_arasan->phy), + "No phy for arasan,sdhci-5.1.\n"); goto unreg_clk; } ret = phy_init(sdhci_arasan->phy); if (ret < 0) { - dev_err(&pdev->dev, "phy_init err.\n"); + dev_err(dev, "phy_init err.\n"); goto unreg_clk; } @@ -1651,7 +1644,7 @@ static int sdhci_arasan_probe(struct platform_device *pdev) if (!IS_ERR(sdhci_arasan->phy)) phy_exit(sdhci_arasan->phy); unreg_clk: - sdhci_arasan_unregister_sdclk(&pdev->dev); + sdhci_arasan_unregister_sdclk(dev); clk_disable_all: clk_disable_unprepare(clk_xin); clk_dis_ahb: diff --git a/drivers/mmc/host/sdhci-of-aspeed-test.c b/drivers/mmc/host/sdhci-of-aspeed-test.c new file mode 100644 index 000000000000..bb67d159b7d8 --- /dev/null +++ b/drivers/mmc/host/sdhci-of-aspeed-test.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2020 IBM Corp. */ + +#include + +static void aspeed_sdhci_phase_ddr52(struct kunit *test) +{ + int rate = 52000000; + + KUNIT_EXPECT_EQ(test, 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 0)); + KUNIT_EXPECT_EQ(test, 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 1)); + KUNIT_EXPECT_EQ(test, 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 2)); + KUNIT_EXPECT_EQ(test, 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 3)); + KUNIT_EXPECT_EQ(test, 2, + aspeed_sdhci_phase_to_tap(NULL, rate, 4)); + KUNIT_EXPECT_EQ(test, 3, + aspeed_sdhci_phase_to_tap(NULL, rate, 5)); + KUNIT_EXPECT_EQ(test, 14, + aspeed_sdhci_phase_to_tap(NULL, rate, 23)); + KUNIT_EXPECT_EQ(test, 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 24)); + KUNIT_EXPECT_EQ(test, 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 25)); + + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 180)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 181)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 182)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 183)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 2, + aspeed_sdhci_phase_to_tap(NULL, rate, 184)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 3, + aspeed_sdhci_phase_to_tap(NULL, rate, 185)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 14, + aspeed_sdhci_phase_to_tap(NULL, rate, 203)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 204)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 205)); +} + +static void aspeed_sdhci_phase_hs200(struct kunit *test) +{ + int rate = 200000000; + + KUNIT_EXPECT_EQ(test, 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 0)); + KUNIT_EXPECT_EQ(test, 0, + aspeed_sdhci_phase_to_tap(NULL, rate, 5)); + KUNIT_EXPECT_EQ(test, 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 6)); + KUNIT_EXPECT_EQ(test, 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 7)); + KUNIT_EXPECT_EQ(test, 14, + aspeed_sdhci_phase_to_tap(NULL, rate, 89)); + KUNIT_EXPECT_EQ(test, 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 90)); + KUNIT_EXPECT_EQ(test, 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 91)); + KUNIT_EXPECT_EQ(test, 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 96)); + + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK, + aspeed_sdhci_phase_to_tap(NULL, rate, 180)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK, + aspeed_sdhci_phase_to_tap(NULL, rate, 185)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 186)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 1, + aspeed_sdhci_phase_to_tap(NULL, rate, 187)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 14, + aspeed_sdhci_phase_to_tap(NULL, rate, 269)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 270)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 271)); + KUNIT_EXPECT_EQ(test, (int)ASPEED_SDHCI_TAP_PARAM_INVERT_CLK | 15, + aspeed_sdhci_phase_to_tap(NULL, rate, 276)); +} + +static struct kunit_case aspeed_sdhci_test_cases[] = { + KUNIT_CASE(aspeed_sdhci_phase_ddr52), + KUNIT_CASE(aspeed_sdhci_phase_hs200), + {} +}; + +static struct kunit_suite aspeed_sdhci_test_suite = { + .name = "sdhci-of-aspeed", + .test_cases = aspeed_sdhci_test_cases, +}; + +static struct kunit_suite *aspeed_sdc_test_suite_array[] = { + &aspeed_sdhci_test_suite, + NULL, +}; + +static struct kunit_suite **aspeed_sdc_test_suites + __used __section(".kunit_test_suites") = aspeed_sdc_test_suite_array; diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c index 4f008ba3280e..7d8692e90996 100644 --- a/drivers/mmc/host/sdhci-of-aspeed.c +++ b/drivers/mmc/host/sdhci-of-aspeed.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,9 +17,19 @@ #include "sdhci-pltfm.h" -#define ASPEED_SDC_INFO 0x00 -#define ASPEED_SDC_S1MMC8 BIT(25) -#define ASPEED_SDC_S0MMC8 BIT(24) +#define ASPEED_SDC_INFO 0x00 +#define ASPEED_SDC_S1_MMC8 BIT(25) +#define ASPEED_SDC_S0_MMC8 BIT(24) +#define ASPEED_SDC_PHASE 0xf4 +#define ASPEED_SDC_S1_PHASE_IN GENMASK(25, 21) +#define ASPEED_SDC_S0_PHASE_IN GENMASK(20, 16) +#define ASPEED_SDC_S1_PHASE_OUT GENMASK(15, 11) +#define ASPEED_SDC_S1_PHASE_IN_EN BIT(10) +#define ASPEED_SDC_S1_PHASE_OUT_EN GENMASK(9, 8) +#define ASPEED_SDC_S0_PHASE_OUT GENMASK(7, 3) +#define ASPEED_SDC_S0_PHASE_IN_EN BIT(2) +#define ASPEED_SDC_S0_PHASE_OUT_EN GENMASK(1, 0) +#define ASPEED_SDC_PHASE_MAX 31 struct aspeed_sdc { struct clk *clk; @@ -28,9 +39,37 @@ struct aspeed_sdc { void __iomem *regs; }; +struct aspeed_sdhci_tap_param { + bool valid; + +#define ASPEED_SDHCI_TAP_PARAM_INVERT_CLK BIT(4) + u8 in; + u8 out; +}; + +struct aspeed_sdhci_tap_desc { + u32 tap_mask; + u32 enable_mask; + u8 enable_value; +}; + +struct aspeed_sdhci_phase_desc { + struct aspeed_sdhci_tap_desc in; + struct aspeed_sdhci_tap_desc out; +}; + +struct aspeed_sdhci_pdata { + unsigned int clk_div_start; + const struct aspeed_sdhci_phase_desc *phase_desc; + size_t nr_phase_descs; +}; + struct aspeed_sdhci { + const struct aspeed_sdhci_pdata *pdata; struct aspeed_sdc *parent; u32 width_mask; + struct mmc_clk_phase_map phase_map; + const struct aspeed_sdhci_phase_desc *phase_desc; }; static void aspeed_sdc_configure_8bit_mode(struct aspeed_sdc *sdc, @@ -50,14 +89,125 @@ static void aspeed_sdc_configure_8bit_mode(struct aspeed_sdc *sdc, spin_unlock(&sdc->lock); } +static u32 +aspeed_sdc_set_phase_tap(const struct aspeed_sdhci_tap_desc *desc, + u8 tap, bool enable, u32 reg) +{ + reg &= ~(desc->enable_mask | desc->tap_mask); + if (enable) { + reg |= tap << __ffs(desc->tap_mask); + reg |= desc->enable_value << __ffs(desc->enable_mask); + } + + return reg; +} + +static void +aspeed_sdc_set_phase_taps(struct aspeed_sdc *sdc, + const struct aspeed_sdhci_phase_desc *desc, + const struct aspeed_sdhci_tap_param *taps) +{ + u32 reg; + + spin_lock(&sdc->lock); + reg = readl(sdc->regs + ASPEED_SDC_PHASE); + + reg = aspeed_sdc_set_phase_tap(&desc->in, taps->in, taps->valid, reg); + reg = aspeed_sdc_set_phase_tap(&desc->out, taps->out, taps->valid, reg); + + writel(reg, sdc->regs + ASPEED_SDC_PHASE); + spin_unlock(&sdc->lock); +} + +#define PICOSECONDS_PER_SECOND 1000000000000ULL +#define ASPEED_SDHCI_NR_TAPS 15 +/* Measured value with *handwave* environmentals and static loading */ +#define ASPEED_SDHCI_MAX_TAP_DELAY_PS 1253 +static int aspeed_sdhci_phase_to_tap(struct device *dev, unsigned long rate_hz, + int phase_deg) +{ + u64 phase_period_ps; + u64 prop_delay_ps; + u64 clk_period_ps; + unsigned int tap; + u8 inverted; + + phase_deg %= 360; + + if (phase_deg >= 180) { + inverted = ASPEED_SDHCI_TAP_PARAM_INVERT_CLK; + phase_deg -= 180; + dev_dbg(dev, + "Inverting clock to reduce phase correction from %d to %d degrees\n", + phase_deg + 180, phase_deg); + } else { + inverted = 0; + } + + prop_delay_ps = ASPEED_SDHCI_MAX_TAP_DELAY_PS / ASPEED_SDHCI_NR_TAPS; + clk_period_ps = div_u64(PICOSECONDS_PER_SECOND, (u64)rate_hz); + phase_period_ps = div_u64((u64)phase_deg * clk_period_ps, 360ULL); + + tap = div_u64(phase_period_ps, prop_delay_ps); + if (tap > ASPEED_SDHCI_NR_TAPS) { + dev_warn(dev, + "Requested out of range phase tap %d for %d degrees of phase compensation at %luHz, clamping to tap %d\n", + tap, phase_deg, rate_hz, ASPEED_SDHCI_NR_TAPS); + tap = ASPEED_SDHCI_NR_TAPS; + } + + return inverted | tap; +} + +static void +aspeed_sdhci_phases_to_taps(struct device *dev, unsigned long rate, + const struct mmc_clk_phase *phases, + struct aspeed_sdhci_tap_param *taps) +{ + taps->valid = phases->valid; + + if (!phases->valid) + return; + + taps->in = aspeed_sdhci_phase_to_tap(dev, rate, phases->in_deg); + taps->out = aspeed_sdhci_phase_to_tap(dev, rate, phases->out_deg); +} + +static void +aspeed_sdhci_configure_phase(struct sdhci_host *host, unsigned long rate) +{ + struct aspeed_sdhci_tap_param _taps = {0}, *taps = &_taps; + struct mmc_clk_phase *params; + struct aspeed_sdhci *sdhci; + struct device *dev; + + dev = host->mmc->parent; + sdhci = sdhci_pltfm_priv(sdhci_priv(host)); + + if (!sdhci->phase_desc) + return; + + params = &sdhci->phase_map.phase[host->timing]; + aspeed_sdhci_phases_to_taps(dev, rate, params, taps); + aspeed_sdc_set_phase_taps(sdhci->parent, sdhci->phase_desc, taps); + dev_dbg(dev, + "Using taps [%d, %d] for [%d, %d] degrees of phase correction at %luHz (%d)\n", + taps->in & ASPEED_SDHCI_NR_TAPS, + taps->out & ASPEED_SDHCI_NR_TAPS, + params->in_deg, params->out_deg, rate, host->timing); +} + static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host; - unsigned long parent; + unsigned long parent, bus; + struct aspeed_sdhci *sdhci; int div; u16 clk; pltfm_host = sdhci_priv(host); + sdhci = sdhci_pltfm_priv(pltfm_host); + parent = clk_get_rate(pltfm_host->clk); sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); @@ -68,14 +218,34 @@ static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) if (WARN_ON(clock > host->max_clk)) clock = host->max_clk; - for (div = 2; div < 256; div *= 2) { - if ((parent / div) <= clock) + /* + * Regarding the AST2600: + * + * If (EMMC12C[7:6], EMMC12C[15:8] == 0) then + * period of SDCLK = period of SDMCLK. + * + * If (EMMC12C[7:6], EMMC12C[15:8] != 0) then + * period of SDCLK = period of SDMCLK * 2 * (EMMC12C[7:6], EMMC[15:8]) + * + * If you keep EMMC12C[7:6] = 0 and EMMC12C[15:8] as one-hot, + * 0x1/0x2/0x4/etc, you will find it is compatible to AST2400 or AST2500 + * + * Keep the one-hot behaviour for backwards compatibility except for + * supporting the value 0 in (EMMC12C[7:6], EMMC12C[15:8]), and capture + * the 0-value capability in clk_div_start. + */ + for (div = sdhci->pdata->clk_div_start; div < 256; div *= 2) { + bus = parent / div; + if (bus <= clock) break; } + div >>= 1; clk = div << SDHCI_DIVIDER_SHIFT; + aspeed_sdhci_configure_phase(host, bus); + sdhci_enable_clk(host, clk); } @@ -157,6 +327,7 @@ static inline int aspeed_sdhci_calculate_slot(struct aspeed_sdhci *dev, static int aspeed_sdhci_probe(struct platform_device *pdev) { + const struct aspeed_sdhci_pdata *aspeed_pdata; struct sdhci_pltfm_host *pltfm_host; struct aspeed_sdhci *dev; struct sdhci_host *host; @@ -164,12 +335,19 @@ static int aspeed_sdhci_probe(struct platform_device *pdev) int slot; int ret; + aspeed_pdata = of_device_get_match_data(&pdev->dev); + if (!aspeed_pdata) { + dev_err(&pdev->dev, "Missing platform configuration data\n"); + return -EINVAL; + } + host = sdhci_pltfm_init(pdev, &aspeed_sdhci_pdata, sizeof(*dev)); if (IS_ERR(host)) return PTR_ERR(host); pltfm_host = sdhci_priv(host); dev = sdhci_pltfm_priv(pltfm_host); + dev->pdata = aspeed_pdata; dev->parent = dev_get_drvdata(pdev->dev.parent); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -180,8 +358,17 @@ static int aspeed_sdhci_probe(struct platform_device *pdev) else if (slot >= 2) return -EINVAL; - dev_info(&pdev->dev, "Configuring for slot %d\n", slot); - dev->width_mask = !slot ? ASPEED_SDC_S0MMC8 : ASPEED_SDC_S1MMC8; + if (slot < dev->pdata->nr_phase_descs) { + dev->phase_desc = &dev->pdata->phase_desc[slot]; + } else { + dev_info(&pdev->dev, + "Phase control not supported for slot %d\n", slot); + dev->phase_desc = NULL; + } + + dev->width_mask = !slot ? ASPEED_SDC_S0_MMC8 : ASPEED_SDC_S1_MMC8; + + dev_info(&pdev->dev, "Configured for slot %d\n", slot); sdhci_get_of_property(pdev); @@ -199,6 +386,9 @@ static int aspeed_sdhci_probe(struct platform_device *pdev) if (ret) goto err_sdhci_add; + if (dev->phase_desc) + mmc_of_parse_clk_phase(host->mmc, &dev->phase_map); + ret = sdhci_add_host(host); if (ret) goto err_sdhci_add; @@ -230,10 +420,49 @@ static int aspeed_sdhci_remove(struct platform_device *pdev) return 0; } +static const struct aspeed_sdhci_pdata ast2400_sdhci_pdata = { + .clk_div_start = 2, +}; + +static const struct aspeed_sdhci_phase_desc ast2600_sdhci_phase[] = { + /* SDHCI/Slot 0 */ + [0] = { + .in = { + .tap_mask = ASPEED_SDC_S0_PHASE_IN, + .enable_mask = ASPEED_SDC_S0_PHASE_IN_EN, + .enable_value = 1, + }, + .out = { + .tap_mask = ASPEED_SDC_S0_PHASE_OUT, + .enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN, + .enable_value = 3, + }, + }, + /* SDHCI/Slot 1 */ + [1] = { + .in = { + .tap_mask = ASPEED_SDC_S1_PHASE_IN, + .enable_mask = ASPEED_SDC_S1_PHASE_IN_EN, + .enable_value = 1, + }, + .out = { + .tap_mask = ASPEED_SDC_S1_PHASE_OUT, + .enable_mask = ASPEED_SDC_S1_PHASE_OUT_EN, + .enable_value = 3, + }, + }, +}; + +static const struct aspeed_sdhci_pdata ast2600_sdhci_pdata = { + .clk_div_start = 1, + .phase_desc = ast2600_sdhci_phase, + .nr_phase_descs = ARRAY_SIZE(ast2600_sdhci_phase), +}; + static const struct of_device_id aspeed_sdhci_of_match[] = { - { .compatible = "aspeed,ast2400-sdhci", }, - { .compatible = "aspeed,ast2500-sdhci", }, - { .compatible = "aspeed,ast2600-sdhci", }, + { .compatible = "aspeed,ast2400-sdhci", .data = &ast2400_sdhci_pdata, }, + { .compatible = "aspeed,ast2500-sdhci", .data = &ast2400_sdhci_pdata, }, + { .compatible = "aspeed,ast2600-sdhci", .data = &ast2600_sdhci_pdata, }, { } }; @@ -327,6 +556,29 @@ static struct platform_driver aspeed_sdc_driver = { .remove = aspeed_sdc_remove, }; +#if defined(CONFIG_MMC_SDHCI_OF_ASPEED_TEST) +#include "sdhci-of-aspeed-test.c" + +static inline int aspeed_sdc_tests_init(void) +{ + return __kunit_test_suites_init(aspeed_sdc_test_suites); +} + +static inline void aspeed_sdc_tests_exit(void) +{ + __kunit_test_suites_exit(aspeed_sdc_test_suites); +} +#else +static inline int aspeed_sdc_tests_init(void) +{ + return 0; +} + +static inline void aspeed_sdc_tests_exit(void) +{ +} +#endif + static int __init aspeed_sdc_init(void) { int rc; @@ -337,7 +589,18 @@ static int __init aspeed_sdc_init(void) rc = platform_driver_register(&aspeed_sdc_driver); if (rc < 0) - platform_driver_unregister(&aspeed_sdhci_driver); + goto cleanup_sdhci; + + rc = aspeed_sdc_tests_init(); + if (rc < 0) { + platform_driver_unregister(&aspeed_sdc_driver); + goto cleanup_sdhci; + } + + return 0; + +cleanup_sdhci: + platform_driver_unregister(&aspeed_sdhci_driver); return rc; } @@ -345,6 +608,8 @@ module_init(aspeed_sdc_init); static void __exit aspeed_sdc_exit(void) { + aspeed_sdc_tests_exit(); + platform_driver_unregister(&aspeed_sdc_driver); platform_driver_unregister(&aspeed_sdhci_driver); } diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index d90020ed3622..59d8d96ce206 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -112,6 +112,7 @@ static const struct sdhci_ops sdhci_dwcmshc_ops = { static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { .ops = &sdhci_dwcmshc_ops, .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, }; static int dwcmshc_probe(struct platform_device *pdev) diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c index b85edd62e7f0..4a0f69b97a78 100644 --- a/drivers/mmc/host/sdhci-pci-gli.c +++ b/drivers/mmc/host/sdhci-pci-gli.c @@ -84,12 +84,21 @@ #define GLI_9763E_VHS_REV_W 0x2 #define PCIE_GLI_9763E_MB 0x888 #define GLI_9763E_MB_CMDQ_OFF BIT(19) +#define GLI_9763E_MB_ERP_ON BIT(7) #define PCIE_GLI_9763E_SCR 0x8E0 #define GLI_9763E_SCR_AXI_REQ BIT(9) +#define PCIE_GLI_9763E_CFG2 0x8A4 +#define GLI_9763E_CFG2_L1DLY GENMASK(28, 19) +#define GLI_9763E_CFG2_L1DLY_MAX 0x3FF + #define PCIE_GLI_9763E_MMC_CTRL 0x960 #define GLI_9763E_HS400_SLOW BIT(3) +#define PCIE_GLI_9763E_CLKRXDLY 0x934 +#define GLI_9763E_HS400_RXDLY GENMASK(31, 28) +#define GLI_9763E_HS400_RXDLY_5 0x5 + #define SDHCI_GLI_9763E_CQE_BASE_ADDR 0x200 #define GLI_9763E_CQE_TRNS_MODE (SDHCI_TRNS_MULTI | \ SDHCI_TRNS_BLK_CNT_EN | \ @@ -791,6 +800,17 @@ static void gli_set_gl9763e(struct sdhci_pci_slot *slot) value &= ~GLI_9763E_HS400_SLOW; pci_write_config_dword(pdev, PCIE_GLI_9763E_MMC_CTRL, value); + pci_read_config_dword(pdev, PCIE_GLI_9763E_CFG2, &value); + value &= ~GLI_9763E_CFG2_L1DLY; + /* set ASPM L1 entry delay to 260us */ + value |= FIELD_PREP(GLI_9763E_CFG2_L1DLY, GLI_9763E_CFG2_L1DLY_MAX); + pci_write_config_dword(pdev, PCIE_GLI_9763E_CFG2, value); + + pci_read_config_dword(pdev, PCIE_GLI_9763E_CLKRXDLY, &value); + value &= ~GLI_9763E_HS400_RXDLY; + value |= FIELD_PREP(GLI_9763E_HS400_RXDLY, GLI_9763E_HS400_RXDLY_5); + pci_write_config_dword(pdev, PCIE_GLI_9763E_CLKRXDLY, value); + pci_read_config_dword(pdev, PCIE_GLI_9763E_VHS, &value); value &= ~GLI_9763E_VHS_REV; value |= FIELD_PREP(GLI_9763E_VHS_REV, GLI_9763E_VHS_REV_R); @@ -814,7 +834,8 @@ static int gli_probe_slot_gl9763e(struct sdhci_pci_slot *slot) pci_read_config_dword(pdev, PCIE_GLI_9763E_MB, &value); if (!(value & GLI_9763E_MB_CMDQ_OFF)) - host->mmc->caps2 |= MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD; + if (value & GLI_9763E_MB_ERP_ON) + host->mmc->caps2 |= MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD; gli_pcie_enable_msi(slot); host->mmc_host_ops.hs400_enhanced_strobe = diff --git a/drivers/mmc/host/sdhci-pci-o2micro.c b/drivers/mmc/host/sdhci-pci-o2micro.c index fa76748d8929..94e3f72f6405 100644 --- a/drivers/mmc/host/sdhci-pci-o2micro.c +++ b/drivers/mmc/host/sdhci-pci-o2micro.c @@ -33,6 +33,8 @@ #define O2_SD_ADMA2 0xE7 #define O2_SD_INF_MOD 0xF1 #define O2_SD_MISC_CTRL4 0xFC +#define O2_SD_MISC_CTRL 0x1C0 +#define O2_SD_PWR_FORCE_L0 0x0002 #define O2_SD_TUNING_CTRL 0x300 #define O2_SD_PLL_SETTING 0x304 #define O2_SD_MISC_SETTING 0x308 @@ -300,6 +302,8 @@ static int sdhci_o2_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host = mmc_priv(mmc); int current_bus_width = 0; + u32 scratch32 = 0; + u16 scratch = 0; /* * This handler only implements the eMMC tuning that is specific to @@ -312,6 +316,17 @@ static int sdhci_o2_execute_tuning(struct mmc_host *mmc, u32 opcode) if (WARN_ON((opcode != MMC_SEND_TUNING_BLOCK_HS200) && (opcode != MMC_SEND_TUNING_BLOCK))) return -EINVAL; + + /* Force power mode enter L0 */ + scratch = sdhci_readw(host, O2_SD_MISC_CTRL); + scratch |= O2_SD_PWR_FORCE_L0; + sdhci_writew(host, scratch, O2_SD_MISC_CTRL); + + /* wait DLL lock, timeout value 5ms */ + if (readx_poll_timeout(sdhci_o2_pll_dll_wdt_control, host, + scratch32, (scratch32 & O2_DLL_LOCK_STATUS), 1, 5000)) + pr_warn("%s: DLL can't lock in 5ms after force L0 during tuning.\n", + mmc_hostname(host->mmc)); /* * Judge the tuning reason, whether caused by dll shift * If cause by dll shift, should call sdhci_o2_dll_recovery @@ -344,6 +359,11 @@ static int sdhci_o2_execute_tuning(struct mmc_host *mmc, u32 opcode) sdhci_set_bus_width(host, current_bus_width); } + /* Cancel force power mode enter L0 */ + scratch = sdhci_readw(host, O2_SD_MISC_CTRL); + scratch &= ~(O2_SD_PWR_FORCE_L0); + sdhci_writew(host, scratch, O2_SD_MISC_CTRL); + sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); diff --git a/drivers/mmc/host/sdhci-sirf.c b/drivers/mmc/host/sdhci-sirf.c deleted file mode 100644 index e9b347b3af7e..000000000000 --- a/drivers/mmc/host/sdhci-sirf.c +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * SDHCI support for SiRF primaII and marco SoCs - * - * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include -#include -#include -#include -#include -#include -#include "sdhci-pltfm.h" - -#define SDHCI_CLK_DELAY_SETTING 0x4C -#define SDHCI_SIRF_8BITBUS BIT(3) -#define SIRF_TUNING_COUNT 16384 - -static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width) -{ - u8 ctrl; - - ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); - ctrl &= ~(SDHCI_CTRL_4BITBUS | SDHCI_SIRF_8BITBUS); - - /* - * CSR atlas7 and prima2 SD host version is not 3.0 - * 8bit-width enable bit of CSR SD hosts is 3, - * while stardard hosts use bit 5 - */ - if (width == MMC_BUS_WIDTH_8) - ctrl |= SDHCI_SIRF_8BITBUS; - else if (width == MMC_BUS_WIDTH_4) - ctrl |= SDHCI_CTRL_4BITBUS; - - sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); -} - -static u32 sdhci_sirf_readl_le(struct sdhci_host *host, int reg) -{ - u32 val = readl(host->ioaddr + reg); - - if (unlikely((reg == SDHCI_CAPABILITIES_1) && - (host->mmc->caps & MMC_CAP_UHS_SDR50))) { - /* fake CAP_1 register */ - val = SDHCI_SUPPORT_DDR50 | - SDHCI_SUPPORT_SDR50 | SDHCI_USE_SDR50_TUNING; - } - - if (unlikely(reg == SDHCI_SLOT_INT_STATUS)) { - u32 prss = val; - /* fake chips as V3.0 host conreoller */ - prss &= ~(0xFF << 16); - val = prss | (SDHCI_SPEC_300 << 16); - } - return val; -} - -static u16 sdhci_sirf_readw_le(struct sdhci_host *host, int reg) -{ - u16 ret = 0; - - ret = readw(host->ioaddr + reg); - - if (unlikely(reg == SDHCI_HOST_VERSION)) { - ret = readw(host->ioaddr + SDHCI_HOST_VERSION); - ret |= SDHCI_SPEC_300; - } - - return ret; -} - -static int sdhci_sirf_execute_tuning(struct sdhci_host *host, u32 opcode) -{ - int tuning_seq_cnt = 3; - int phase; - u8 tuned_phase_cnt = 0; - int rc = 0, longest_range = 0; - int start = -1, end = 0, tuning_value = -1, range = 0; - u16 clock_setting; - struct mmc_host *mmc = host->mmc; - - clock_setting = sdhci_readw(host, SDHCI_CLK_DELAY_SETTING); - clock_setting &= ~0x3fff; - -retry: - phase = 0; - tuned_phase_cnt = 0; - do { - sdhci_writel(host, - clock_setting | phase, - SDHCI_CLK_DELAY_SETTING); - - if (!mmc_send_tuning(mmc, opcode, NULL)) { - /* Tuning is successful at this tuning point */ - tuned_phase_cnt++; - dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", - mmc_hostname(mmc), phase); - if (start == -1) - start = phase; - end = phase; - range++; - if (phase == (SIRF_TUNING_COUNT - 1) - && range > longest_range) - tuning_value = (start + end) / 2; - } else { - dev_dbg(mmc_dev(mmc), "%s: Found bad phase = %d\n", - mmc_hostname(mmc), phase); - if (range > longest_range) { - tuning_value = (start + end) / 2; - longest_range = range; - } - start = -1; - end = range = 0; - } - } while (++phase < SIRF_TUNING_COUNT); - - if (tuned_phase_cnt && tuning_value > 0) { - /* - * Finally set the selected phase in delay - * line hw block. - */ - phase = tuning_value; - sdhci_writel(host, - clock_setting | phase, - SDHCI_CLK_DELAY_SETTING); - - dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", - mmc_hostname(mmc), phase); - } else { - if (--tuning_seq_cnt) - goto retry; - /* Tuning failed */ - dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", - mmc_hostname(mmc)); - rc = -EIO; - } - - return rc; -} - -static const struct sdhci_ops sdhci_sirf_ops = { - .read_l = sdhci_sirf_readl_le, - .read_w = sdhci_sirf_readw_le, - .platform_execute_tuning = sdhci_sirf_execute_tuning, - .set_clock = sdhci_set_clock, - .get_max_clock = sdhci_pltfm_clk_get_max_clock, - .set_bus_width = sdhci_sirf_set_bus_width, - .reset = sdhci_reset, - .set_uhs_signaling = sdhci_set_uhs_signaling, -}; - -static const struct sdhci_pltfm_data sdhci_sirf_pdata = { - .ops = &sdhci_sirf_ops, - .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | - SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | - SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | - SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, -}; - -static int sdhci_sirf_probe(struct platform_device *pdev) -{ - struct sdhci_host *host; - struct sdhci_pltfm_host *pltfm_host; - struct clk *clk; - int ret; - - clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get clock"); - return PTR_ERR(clk); - } - - host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, 0); - if (IS_ERR(host)) - return PTR_ERR(host); - - pltfm_host = sdhci_priv(host); - pltfm_host->clk = clk; - - sdhci_get_of_property(pdev); - - ret = clk_prepare_enable(pltfm_host->clk); - if (ret) - goto err_clk_prepare; - - ret = sdhci_add_host(host); - if (ret) - goto err_sdhci_add; - - /* - * We must request the IRQ after sdhci_add_host(), as the tasklet only - * gets setup in sdhci_add_host() and we oops. - */ - ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, false, 0); - if (ret == -EPROBE_DEFER) - goto err_request_cd; - if (!ret) - mmc_gpiod_request_cd_irq(host->mmc); - - return 0; - -err_request_cd: - sdhci_remove_host(host, 0); -err_sdhci_add: - clk_disable_unprepare(pltfm_host->clk); -err_clk_prepare: - sdhci_pltfm_free(pdev); - return ret; -} - -static const struct of_device_id sdhci_sirf_of_match[] = { - { .compatible = "sirf,prima2-sdhc" }, - { } -}; -MODULE_DEVICE_TABLE(of, sdhci_sirf_of_match); - -static struct platform_driver sdhci_sirf_driver = { - .driver = { - .name = "sdhci-sirf", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - .of_match_table = sdhci_sirf_of_match, - .pm = &sdhci_pltfm_pmops, - }, - .probe = sdhci_sirf_probe, - .remove = sdhci_pltfm_unregister, -}; - -module_platform_driver(sdhci_sirf_driver); - -MODULE_DESCRIPTION("SDHCI driver for SiRFprimaII/SiRFmarco"); -MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c index f85171edabeb..5dc36efff47f 100644 --- a/drivers/mmc/host/sdhci-sprd.c +++ b/drivers/mmc/host/sdhci-sprd.c @@ -708,14 +708,14 @@ static int sdhci_sprd_remove(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host); - struct mmc_host *mmc = host->mmc; - mmc_remove_host(mmc); + sdhci_remove_host(host, 0); + clk_disable_unprepare(sprd_host->clk_sdio); clk_disable_unprepare(sprd_host->clk_enable); clk_disable_unprepare(sprd_host->clk_2x_enable); - mmc_free_host(mmc); + sdhci_pltfm_free(pdev); return 0; } diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c index d19eef5f725f..666cee4c7f7c 100644 --- a/drivers/mmc/host/sdhci-xenon.c +++ b/drivers/mmc/host/sdhci-xenon.c @@ -689,6 +689,7 @@ static const struct dev_pm_ops sdhci_xenon_dev_pm_ops = { static const struct of_device_id sdhci_xenon_dt_ids[] = { { .compatible = "marvell,armada-ap806-sdhci", .data = (void *)XENON_AP806}, + { .compatible = "marvell,armada-ap807-sdhci", .data = (void *)XENON_AP807}, { .compatible = "marvell,armada-cp110-sdhci", .data = (void *)XENON_CP110}, { .compatible = "marvell,armada-3700-sdhci", .data = (void *)XENON_A3700}, {} diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index a64ea143d185..7a34649b0754 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -514,6 +514,26 @@ static const struct sdhci_am654_driver_data sdhci_j721e_4bit_drvdata = { .flags = IOMUX_PRESENT, }; +static const struct sdhci_pltfm_data sdhci_am64_8bit_pdata = { + .ops = &sdhci_j721e_8bit_ops, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +}; + +static const struct sdhci_am654_driver_data sdhci_am64_8bit_drvdata = { + .pdata = &sdhci_am64_8bit_pdata, + .flags = DLL_PRESENT | DLL_CALIB, +}; + +static const struct sdhci_pltfm_data sdhci_am64_4bit_pdata = { + .ops = &sdhci_j721e_4bit_ops, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +}; + +static const struct sdhci_am654_driver_data sdhci_am64_4bit_drvdata = { + .pdata = &sdhci_am64_4bit_pdata, + .flags = IOMUX_PRESENT, +}; + static const struct soc_device_attribute sdhci_am654_devices[] = { { .family = "AM65X", .revision = "SR1.0", @@ -737,6 +757,14 @@ static const struct of_device_id sdhci_am654_of_match[] = { .compatible = "ti,j721e-sdhci-4bit", .data = &sdhci_j721e_4bit_drvdata, }, + { + .compatible = "ti,am64-sdhci-8bit", + .data = &sdhci_am64_8bit_drvdata, + }, + { + .compatible = "ti,am64-sdhci-4bit", + .data = &sdhci_am64_4bit_drvdata, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sdhci_am654_of_match); diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 6310693f2ac0..2702736a1c57 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -245,6 +245,7 @@ struct sunxi_idma_des { struct sunxi_mmc_cfg { u32 idma_des_size_bits; + u32 idma_des_shift; const struct sunxi_mmc_clk_delay *clk_delays; /* does the IP block support autocalibration? */ @@ -344,7 +345,7 @@ static int sunxi_mmc_init_host(struct sunxi_mmc_host *host) /* Enable CEATA support */ mmc_writel(host, REG_FUNS, SDXC_CEATA_ON); /* Set DMA descriptor list base address */ - mmc_writel(host, REG_DLBA, host->sg_dma); + mmc_writel(host, REG_DLBA, host->sg_dma >> host->cfg->idma_des_shift); rval = mmc_readl(host, REG_GCTRL); rval |= SDXC_INTERRUPT_ENABLE_BIT; @@ -374,8 +375,10 @@ static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host, next_desc += sizeof(struct sunxi_idma_des); pdes[i].buf_addr_ptr1 = - cpu_to_le32(sg_dma_address(&data->sg[i])); - pdes[i].buf_addr_ptr2 = cpu_to_le32((u32)next_desc); + cpu_to_le32(sg_dma_address(&data->sg[i]) >> + host->cfg->idma_des_shift); + pdes[i].buf_addr_ptr2 = cpu_to_le32((u32)next_desc >> + host->cfg->idma_des_shift); } pdes[0].config |= cpu_to_le32(SDXC_IDMAC_DES0_FD); @@ -1179,6 +1182,23 @@ static const struct sunxi_mmc_cfg sun50i_a64_emmc_cfg = { .needs_new_timings = true, }; +static const struct sunxi_mmc_cfg sun50i_a100_cfg = { + .idma_des_size_bits = 16, + .idma_des_shift = 2, + .clk_delays = NULL, + .can_calibrate = true, + .mask_data0 = true, + .needs_new_timings = true, +}; + +static const struct sunxi_mmc_cfg sun50i_a100_emmc_cfg = { + .idma_des_size_bits = 13, + .idma_des_shift = 2, + .clk_delays = NULL, + .can_calibrate = true, + .needs_new_timings = true, +}; + static const struct of_device_id sunxi_mmc_of_match[] = { { .compatible = "allwinner,sun4i-a10-mmc", .data = &sun4i_a10_cfg }, { .compatible = "allwinner,sun5i-a13-mmc", .data = &sun5i_a13_cfg }, @@ -1187,6 +1207,8 @@ static const struct of_device_id sunxi_mmc_of_match[] = { { .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg }, { .compatible = "allwinner,sun50i-a64-mmc", .data = &sun50i_a64_cfg }, { .compatible = "allwinner,sun50i-a64-emmc", .data = &sun50i_a64_emmc_cfg }, + { .compatible = "allwinner,sun50i-a100-mmc", .data = &sun50i_a100_cfg }, + { .compatible = "allwinner,sun50i-a100-emmc", .data = &sun50i_a100_emmc_cfg }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match); @@ -1507,6 +1529,8 @@ static int sunxi_mmc_runtime_suspend(struct device *dev) #endif static const struct dev_pm_ops sunxi_mmc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) SET_RUNTIME_PM_OPS(sunxi_mmc_runtime_suspend, sunxi_mmc_runtime_resume, NULL) diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c index fd8b72d3e02c..9fdf7ea06e3f 100644 --- a/drivers/mmc/host/tifm_sd.c +++ b/drivers/mmc/host/tifm_sd.c @@ -731,9 +731,9 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) mmc_request_done(mmc, mrq); } -static void tifm_sd_end_cmd(unsigned long data) +static void tifm_sd_end_cmd(struct tasklet_struct *t) { - struct tifm_sd *host = (struct tifm_sd*)data; + struct tifm_sd *host = from_tasklet(host, t, finish_tasklet); struct tifm_dev *sock = host->dev; struct mmc_host *mmc = tifm_get_drvdata(sock); struct mmc_request *mrq; @@ -968,8 +968,7 @@ static int tifm_sd_probe(struct tifm_dev *sock) */ mmc->max_busy_timeout = TIFM_MMCSD_REQ_TIMEOUT_MS; - tasklet_init(&host->finish_tasklet, tifm_sd_end_cmd, - (unsigned long)host); + tasklet_setup(&host->finish_tasklet, tifm_sd_end_cmd); timer_setup(&host->timer, tifm_sd_abort, 0); mmc->ops = &tifm_sd_ops; diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 784fa6ed5843..2d1db9396d4a 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -181,7 +181,7 @@ struct tmio_mmc_host { unsigned int direction, int blk_size); int (*write16_hook)(struct tmio_mmc_host *host, int addr); void (*reset)(struct tmio_mmc_host *host); - bool (*check_retune)(struct tmio_mmc_host *host); + bool (*check_retune)(struct tmio_mmc_host *host, struct mmc_request *mrq); void (*fixup_request)(struct tmio_mmc_host *host, struct mmc_request *mrq); unsigned int (*get_timeout_cycles)(struct tmio_mmc_host *host); diff --git a/drivers/mmc/host/tmio_mmc_core.c b/drivers/mmc/host/tmio_mmc_core.c index 942b8375179c..49c2d406c48e 100644 --- a/drivers/mmc/host/tmio_mmc_core.c +++ b/drivers/mmc/host/tmio_mmc_core.c @@ -477,8 +477,10 @@ static void tmio_mmc_data_irq(struct tmio_mmc_host *host, unsigned int stat) if (!data) goto out; - if (stat & TMIO_STAT_CRCFAIL || stat & TMIO_STAT_STOPBIT_ERR || - stat & TMIO_STAT_TXUNDERRUN) + if (stat & TMIO_STAT_DATATIMEOUT) + data->error = -ETIMEDOUT; + else if (stat & TMIO_STAT_CRCFAIL || stat & TMIO_STAT_STOPBIT_ERR || + stat & TMIO_STAT_TXUNDERRUN) data->error = -EILSEQ; if (host->dma_on && (data->flags & MMC_DATA_WRITE)) { u32 status = sd_ctrl_read16_and_16_as_32(host, CTL_STATUS); @@ -802,7 +804,7 @@ static void tmio_mmc_finish_request(struct tmio_mmc_host *host) } /* Error means retune, but executed command was still successful */ - if (host->check_retune && host->check_retune(host)) + if (host->check_retune && host->check_retune(host, mrq)) mmc_retune_needed(host->mmc); /* If SET_BLOCK_COUNT, continue with main command */ diff --git a/drivers/mmc/host/uniphier-sd.c b/drivers/mmc/host/uniphier-sd.c index a6cd16771d4e..2413b6750cec 100644 --- a/drivers/mmc/host/uniphier-sd.c +++ b/drivers/mmc/host/uniphier-sd.c @@ -81,9 +81,9 @@ static void uniphier_sd_dma_endisable(struct tmio_mmc_host *host, int enable) } /* external DMA engine */ -static void uniphier_sd_external_dma_issue(unsigned long arg) +static void uniphier_sd_external_dma_issue(struct tasklet_struct *t) { - struct tmio_mmc_host *host = (void *)arg; + struct tmio_mmc_host *host = from_tasklet(host, t, dma_issue); struct uniphier_sd_priv *priv = uniphier_sd_priv(host); uniphier_sd_dma_endisable(host, 1); @@ -190,8 +190,7 @@ static void uniphier_sd_external_dma_request(struct tmio_mmc_host *host, host->chan_rx = chan; host->chan_tx = chan; - tasklet_init(&host->dma_issue, uniphier_sd_external_dma_issue, - (unsigned long)host); + tasklet_setup(&host->dma_issue, uniphier_sd_external_dma_issue); } static void uniphier_sd_external_dma_release(struct tmio_mmc_host *host) @@ -228,9 +227,9 @@ static const struct tmio_mmc_dma_ops uniphier_sd_external_dma_ops = { .dataend = uniphier_sd_external_dma_dataend, }; -static void uniphier_sd_internal_dma_issue(unsigned long arg) +static void uniphier_sd_internal_dma_issue(struct tasklet_struct *t) { - struct tmio_mmc_host *host = (void *)arg; + struct tmio_mmc_host *host = from_tasklet(host, t, dma_issue); unsigned long flags; spin_lock_irqsave(&host->lock, flags); @@ -309,8 +308,7 @@ static void uniphier_sd_internal_dma_request(struct tmio_mmc_host *host, host->chan_tx = (void *)0xdeadbeaf; - tasklet_init(&host->dma_issue, uniphier_sd_internal_dma_issue, - (unsigned long)host); + tasklet_setup(&host->dma_issue, uniphier_sd_internal_dma_issue); } static void uniphier_sd_internal_dma_release(struct tmio_mmc_host *host) diff --git a/drivers/mmc/host/usdhi6rol0.c b/drivers/mmc/host/usdhi6rol0.c index e2d5112d809d..615f3d008af1 100644 --- a/drivers/mmc/host/usdhi6rol0.c +++ b/drivers/mmc/host/usdhi6rol0.c @@ -1858,10 +1858,12 @@ static int usdhi6_probe(struct platform_device *pdev) ret = mmc_add_host(mmc); if (ret < 0) - goto e_clk_off; + goto e_release_dma; return 0; +e_release_dma: + usdhi6_dma_release(host); e_clk_off: clk_disable_unprepare(host->clk); e_free_mmc: diff --git a/drivers/mmc/host/via-sdmmc.c b/drivers/mmc/host/via-sdmmc.c index 9b755ea0fa03..4f4c0813f9fd 100644 --- a/drivers/mmc/host/via-sdmmc.c +++ b/drivers/mmc/host/via-sdmmc.c @@ -959,14 +959,12 @@ static void via_sdc_timeout(struct timer_list *t) spin_unlock_irqrestore(&sdhost->lock, flags); } -static void via_sdc_tasklet_finish(unsigned long param) +static void via_sdc_tasklet_finish(struct tasklet_struct *t) { - struct via_crdr_mmc_host *host; + struct via_crdr_mmc_host *host = from_tasklet(host, t, finish_tasklet); unsigned long flags; struct mmc_request *mrq; - host = (struct via_crdr_mmc_host *)param; - spin_lock_irqsave(&host->lock, flags); del_timer(&host->timer); @@ -1050,8 +1048,7 @@ static void via_init_mmc_host(struct via_crdr_mmc_host *host) INIT_WORK(&host->carddet_work, via_sdc_card_detect); - tasklet_init(&host->finish_tasklet, via_sdc_tasklet_finish, - (unsigned long)host); + tasklet_setup(&host->finish_tasklet, via_sdc_tasklet_finish); addrbase = host->sdhc_mmiobase; writel(0x0, addrbase + VIA_CRDR_SDINTMASK); diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c index cd63ea865b77..67ecd342fe5f 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -987,9 +987,9 @@ static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host) return host->mrq->cmd->data; } -static void wbsd_tasklet_card(unsigned long param) +static void wbsd_tasklet_card(struct tasklet_struct *t) { - struct wbsd_host *host = (struct wbsd_host *)param; + struct wbsd_host *host = from_tasklet(host, t, card_tasklet); u8 csr; int delay = -1; @@ -1036,9 +1036,9 @@ static void wbsd_tasklet_card(unsigned long param) mmc_detect_change(host->mmc, msecs_to_jiffies(delay)); } -static void wbsd_tasklet_fifo(unsigned long param) +static void wbsd_tasklet_fifo(struct tasklet_struct *t) { - struct wbsd_host *host = (struct wbsd_host *)param; + struct wbsd_host *host = from_tasklet(host, t, fifo_tasklet); struct mmc_data *data; spin_lock(&host->lock); @@ -1067,9 +1067,9 @@ static void wbsd_tasklet_fifo(unsigned long param) spin_unlock(&host->lock); } -static void wbsd_tasklet_crc(unsigned long param) +static void wbsd_tasklet_crc(struct tasklet_struct *t) { - struct wbsd_host *host = (struct wbsd_host *)param; + struct wbsd_host *host = from_tasklet(host, t, crc_tasklet); struct mmc_data *data; spin_lock(&host->lock); @@ -1091,9 +1091,9 @@ static void wbsd_tasklet_crc(unsigned long param) spin_unlock(&host->lock); } -static void wbsd_tasklet_timeout(unsigned long param) +static void wbsd_tasklet_timeout(struct tasklet_struct *t) { - struct wbsd_host *host = (struct wbsd_host *)param; + struct wbsd_host *host = from_tasklet(host, t, timeout_tasklet); struct mmc_data *data; spin_lock(&host->lock); @@ -1115,9 +1115,9 @@ static void wbsd_tasklet_timeout(unsigned long param) spin_unlock(&host->lock); } -static void wbsd_tasklet_finish(unsigned long param) +static void wbsd_tasklet_finish(struct tasklet_struct *t) { - struct wbsd_host *host = (struct wbsd_host *)param; + struct wbsd_host *host = from_tasklet(host, t, finish_tasklet); struct mmc_data *data; spin_lock(&host->lock); @@ -1449,16 +1449,11 @@ static int wbsd_request_irq(struct wbsd_host *host, int irq) /* * Set up tasklets. Must be done before requesting interrupt. */ - tasklet_init(&host->card_tasklet, wbsd_tasklet_card, - (unsigned long)host); - tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo, - (unsigned long)host); - tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc, - (unsigned long)host); - tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, - (unsigned long)host); - tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, - (unsigned long)host); + tasklet_setup(&host->card_tasklet, wbsd_tasklet_card); + tasklet_setup(&host->fifo_tasklet, wbsd_tasklet_fifo); + tasklet_setup(&host->crc_tasklet, wbsd_tasklet_crc); + tasklet_setup(&host->timeout_tasklet, wbsd_tasklet_timeout); + tasklet_setup(&host->finish_tasklet, wbsd_tasklet_finish); /* * Allocate interrupt. diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index 3104680b7485..fc5aa1525d13 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -32,7 +32,14 @@ static struct cros_ec_platform pd_p = { .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), }; -static irqreturn_t ec_irq_handler(int irq, void *data) +/** + * cros_ec_irq_handler() - top half part of the interrupt handler + * @irq: IRQ id + * @data: (ec_dev) Device with events to process. + * + * Return: Wakeup the bottom half + */ +static irqreturn_t cros_ec_irq_handler(int irq, void *data) { struct cros_ec_device *ec_dev = data; @@ -51,7 +58,7 @@ static irqreturn_t ec_irq_handler(int irq, void *data) * Return: true if more events are still pending and this function should be * called again. */ -bool cros_ec_handle_event(struct cros_ec_device *ec_dev) +static bool cros_ec_handle_event(struct cros_ec_device *ec_dev) { bool wake_event; bool ec_has_more_events; @@ -73,9 +80,15 @@ bool cros_ec_handle_event(struct cros_ec_device *ec_dev) return ec_has_more_events; } -EXPORT_SYMBOL(cros_ec_handle_event); -static irqreturn_t ec_irq_thread(int irq, void *data) +/** + * cros_ec_irq_thread() - bottom half part of the interrupt handler + * @irq: IRQ id + * @data: (ec_dev) Device with events to process. + * + * Return: Interrupt handled. + */ +irqreturn_t cros_ec_irq_thread(int irq, void *data) { struct cros_ec_device *ec_dev = data; bool ec_has_more_events; @@ -86,6 +99,7 @@ static irqreturn_t ec_irq_thread(int irq, void *data) return IRQ_HANDLED; } +EXPORT_SYMBOL(cros_ec_irq_thread); static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) { @@ -194,8 +208,8 @@ int cros_ec_register(struct cros_ec_device *ec_dev) if (ec_dev->irq > 0) { err = devm_request_threaded_irq(dev, ec_dev->irq, - ec_irq_handler, - ec_irq_thread, + cros_ec_irq_handler, + cros_ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "chromeos-ec", ec_dev); if (err) { @@ -269,6 +283,13 @@ int cros_ec_register(struct cros_ec_device *ec_dev) dev_info(dev, "Chrome EC device registered\n"); + /* + * Unlock EC that may be waiting for AP to process MKBP events. + * If the AP takes to long to answer, the EC would stop sending events. + */ + if (ec_dev->mkbp_event_supported) + cros_ec_irq_thread(0, ec_dev); + return 0; } EXPORT_SYMBOL(cros_ec_register); diff --git a/drivers/platform/chrome/cros_ec.h b/drivers/platform/chrome/cros_ec.h index e69fc1ff68b4..78363dcfdf23 100644 --- a/drivers/platform/chrome/cros_ec.h +++ b/drivers/platform/chrome/cros_ec.h @@ -8,12 +8,14 @@ #ifndef __CROS_EC_H #define __CROS_EC_H +#include + int cros_ec_register(struct cros_ec_device *ec_dev); int cros_ec_unregister(struct cros_ec_device *ec_dev); int cros_ec_suspend(struct cros_ec_device *ec_dev); int cros_ec_resume(struct cros_ec_device *ec_dev); -bool cros_ec_handle_event(struct cros_ec_device *ec_dev); +irqreturn_t cros_ec_irq_thread(int irq, void *data); #endif /* __CROS_EC_H */ diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index 81364029af36..f00107017318 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -140,12 +140,8 @@ static void ish_evt_handler(struct work_struct *work) { struct ishtp_cl_data *client_data = container_of(work, struct ishtp_cl_data, work_ec_evt); - struct cros_ec_device *ec_dev = client_data->ec_dev; - bool ec_has_more_events; - do { - ec_has_more_events = cros_ec_handle_event(ec_dev); - } while (ec_has_more_events); + cros_ec_irq_thread(0, client_data->ec_dev); } /** diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index de8dfb12e486..469dfc7a4a03 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -523,7 +523,7 @@ static struct attribute *__lb_cmds_attrs[] = { NULL, }; -static struct attribute_group cros_ec_lightbar_attr_group = { +static const struct attribute_group cros_ec_lightbar_attr_group = { .name = "lightbar", .attrs = __lb_cmds_attrs, }; diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 7c92a6e22d75..aa7f7aa77297 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -526,11 +526,13 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) * power), not wake up. */ ec_dev->host_event_wake_mask = U32_MAX & - ~(BIT(EC_HOST_EVENT_AC_DISCONNECTED) | - BIT(EC_HOST_EVENT_BATTERY_LOW) | - BIT(EC_HOST_EVENT_BATTERY_CRITICAL) | - BIT(EC_HOST_EVENT_PD_MCU) | - BIT(EC_HOST_EVENT_BATTERY_STATUS)); + ~(EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_CLOSED) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_AC_DISCONNECTED) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_LOW) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_CRITICAL) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) | + EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_STATUS)); /* * Old ECs may not support this command. Complain about all * other errors. diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c index 30d0ba3b8889..d96d15b8ca94 100644 --- a/drivers/platform/chrome/cros_ec_rpmsg.c +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -149,12 +149,8 @@ cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work) struct cros_ec_rpmsg *ec_rpmsg = container_of(host_event_work, struct cros_ec_rpmsg, host_event_work); - struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev); - bool ec_has_more_events; - do { - ec_has_more_events = cros_ec_handle_event(ec_dev); - } while (ec_has_more_events); + cros_ec_irq_thread(0, dev_get_drvdata(&ec_rpmsg->rpdev->dev)); } static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data, diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index f521a5c65091..f07eabcf9494 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -28,7 +28,7 @@ static ssize_t reboot_show(struct device *dev, int count = 0; count += scnprintf(buf + count, PAGE_SIZE - count, - "ro|rw|cancel|cold|disable-jump|hibernate"); + "ro|rw|cancel|cold|disable-jump|hibernate|cold-ap-off"); count += scnprintf(buf + count, PAGE_SIZE - count, " [at-shutdown]\n"); return count; @@ -46,6 +46,7 @@ static ssize_t reboot_store(struct device *dev, {"cancel", EC_REBOOT_CANCEL, 0}, {"ro", EC_REBOOT_JUMP_RO, 0}, {"rw", EC_REBOOT_JUMP_RW, 0}, + {"cold-ap-off", EC_REBOOT_COLD_AP_OFF, 0}, {"cold", EC_REBOOT_COLD, 0}, {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, {"hibernate", EC_REBOOT_HIBERNATE, 0}, @@ -329,7 +330,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj, return a->mode; } -static struct attribute_group cros_ec_attr_group = { +static const struct attribute_group cros_ec_attr_group = { .attrs = __ec_attrs, .is_visible = cros_ec_ctrl_visible, }; diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 0abd21044882..0811562deecc 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -203,20 +203,26 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po } } -static void cros_typec_remove_partner(struct cros_typec_data *typec, - int port_num) +static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) { - struct cros_typec_port *port = typec->ports[port_num]; - - cros_typec_unregister_altmodes(typec, port_num, true); - port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; port->state.data = NULL; usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); - typec_mux_set(port->mux, &port->state); + + return typec_mux_set(port->mux, &port->state); +} + +static void cros_typec_remove_partner(struct cros_typec_data *typec, + int port_num) +{ + struct cros_typec_port *port = typec->ports[port_num]; + + cros_typec_unregister_altmodes(typec, port_num, true); + + cros_typec_usb_disconnect_state(port); typec_unregister_partner(port->partner); port->partner = NULL; @@ -536,8 +542,10 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, enum typec_orientation orientation; int ret; - if (!port->partner) - return 0; + if (mux_flags == USB_PD_MUX_NONE) { + ret = cros_typec_usb_disconnect_state(port); + goto mux_ack; + } if (mux_flags & USB_PD_MUX_POLARITY_INVERTED) orientation = TYPEC_ORIENTATION_REVERSE; @@ -572,6 +580,7 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, mux_flags); } +mux_ack: if (!typec->needs_mux_ack) return ret; @@ -638,9 +647,8 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, "Failed to register partner on port: %d\n", port_num); } else { - if (!typec->ports[port_num]->partner) - return; - cros_typec_remove_partner(typec, port_num); + if (typec->ports[port_num]->partner) + cros_typec_remove_partner(typec, port_num); if (typec->ports[port_num]->cable) cros_typec_remove_cable(typec, port_num); @@ -1060,6 +1068,7 @@ static int cros_ec_typec_event(struct notifier_block *nb, { struct cros_typec_data *typec = container_of(nb, struct cros_typec_data, nb); + flush_work(&typec->port_work); schedule_work(&typec->port_work); return NOTIFY_OK; diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c index f3a70a312b43..c859c862d7ac 100644 --- a/drivers/platform/chrome/cros_ec_vbc.c +++ b/drivers/platform/chrome/cros_ec_vbc.c @@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = { NULL }; -static struct attribute_group cros_ec_vbc_attr_group = { +static const struct attribute_group cros_ec_vbc_attr_group = { .name = "vbc", .bin_attrs = cros_ec_vbc_bin_attrs, }; diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c index 3c587b4054a5..79a5e8fa680f 100644 --- a/drivers/platform/chrome/wilco_ec/sysfs.c +++ b/drivers/platform/chrome/wilco_ec/sysfs.c @@ -236,7 +236,7 @@ static struct attribute *wilco_dev_attrs[] = { NULL, }; -static struct attribute_group wilco_dev_attr_group = { +static const struct attribute_group wilco_dev_attr_group = { .attrs = wilco_dev_attrs, }; diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index f64b82824db2..4ff5c3a12991 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -37,7 +37,6 @@ struct olpc_ec_priv { struct mutex cmd_lock; /* DCON regulator */ - struct regulator_dev *dcon_rdev; bool dcon_enabled; /* Pending EC commands */ @@ -387,24 +386,26 @@ static int dcon_regulator_is_enabled(struct regulator_dev *rdev) return ec->dcon_enabled ? 1 : 0; } -static struct regulator_ops dcon_regulator_ops = { +static const struct regulator_ops dcon_regulator_ops = { .enable = dcon_regulator_enable, .disable = dcon_regulator_disable, .is_enabled = dcon_regulator_is_enabled, }; static const struct regulator_desc dcon_desc = { - .name = "dcon", - .id = 0, - .ops = &dcon_regulator_ops, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, + .name = "dcon", + .id = 0, + .ops = &dcon_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 25000, }; static int olpc_ec_probe(struct platform_device *pdev) { struct olpc_ec_priv *ec; struct regulator_config config = { }; + struct regulator_dev *regulator; int err; if (!ec_driver) @@ -426,26 +427,26 @@ static int olpc_ec_probe(struct platform_device *pdev) /* get the EC revision */ err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1); - if (err) { - ec_priv = NULL; - kfree(ec); - return err; - } + if (err) + goto error; config.dev = pdev->dev.parent; config.driver_data = ec; ec->dcon_enabled = true; - ec->dcon_rdev = devm_regulator_register(&pdev->dev, &dcon_desc, - &config); - if (IS_ERR(ec->dcon_rdev)) { + regulator = devm_regulator_register(&pdev->dev, &dcon_desc, &config); + if (IS_ERR(regulator)) { dev_err(&pdev->dev, "failed to register DCON regulator\n"); - err = PTR_ERR(ec->dcon_rdev); - kfree(ec); - return err; + err = PTR_ERR(regulator); + goto error; } ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return 0; + +error: + ec_priv = NULL; + kfree(ec); return err; } diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 2c941cdac9ee..0847b2dc97bf 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -41,6 +41,42 @@ config SURFACE_3_POWER_OPREGION This driver provides support for ACPI operation region of the Surface 3 battery platform driver. +config SURFACE_ACPI_NOTIFY + tristate "Surface ACPI Notify Driver" + depends on SURFACE_AGGREGATOR + help + Surface ACPI Notify (SAN) driver for Microsoft Surface devices. + + This driver provides support for the ACPI interface (called SAN) of + the Surface System Aggregator Module (SSAM) EC. This interface is used + on 5th- and 6th-generation Microsoft Surface devices (including + Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in + reduced functionality on the Surface Laptop 3) to execute SSAM + requests directly from ACPI code, as well as receive SSAM events and + turn them into ACPI notifications. It essentially acts as a + translation layer between the SSAM controller and ACPI. + + Specifically, this driver may be needed for battery status reporting, + thermal sensor access, and real-time clock information, depending on + the Surface device in question. + +config SURFACE_AGGREGATOR_CDEV + tristate "Surface System Aggregator Module User-Space Interface" + depends on SURFACE_AGGREGATOR + help + Provides a misc-device interface to the Surface System Aggregator + Module (SSAM) controller. + + This option provides a module (called surface_aggregator_cdev), that, + when loaded, will add a client device (and its respective driver) to + the SSAM controller. Said client device manages a misc-device + interface (/dev/surface/aggregator), which can be used by user-space + tools to directly communicate with the SSAM EC by sending requests and + receiving the corresponding responses. + + The provided interface is intended for debugging and development only, + and should not be used otherwise. + config SURFACE_GPE tristate "Surface GPE/Lid Support Driver" depends on DMI @@ -50,10 +86,31 @@ config SURFACE_GPE accordingly. It is required on those devices to allow wake-ups from suspend by opening the lid. +config SURFACE_HOTPLUG + tristate "Surface Hot-Plug Driver" + depends on GPIOLIB + help + Driver for out-of-band hot-plug event signaling on Microsoft Surface + devices with hot-pluggable PCIe cards. + + This driver is used on Surface Book (2 and 3) devices with a + hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those + devices can enter D3cold, which prevents in-band (standard) PCIe + hot-plug signaling. Thus, without this driver, detaching the base + containing the dGPU will not correctly update the state of the + corresponding PCIe device if it is in D3cold. This driver adds support + for out-of-band hot-plug notifications, ensuring that the device state + is properly updated even when the device in question is in D3cold. + + Select M or Y here, if you want to (fully) support hot-plugging of + dGPU devices on the Surface Book 2 and/or 3 during D3cold. + config SURFACE_PRO3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" depends on INPUT help This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. +source "drivers/platform/surface/aggregator/Kconfig" + endif # SURFACE_PLATFORMS diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index cedfb027ded1..990424c5f0c9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -7,5 +7,9 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o +obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o +obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ +obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig new file mode 100644 index 000000000000..3aaeea9f0433 --- /dev/null +++ b/drivers/platform/surface/aggregator/Kconfig @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + +menuconfig SURFACE_AGGREGATOR + tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" + depends on SERIAL_DEV_BUS + select CRC_CCITT + help + The Surface System Aggregator Module (Surface SAM or SSAM) is an + embedded controller (EC) found on 5th- and later-generation Microsoft + Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, + and newer, with exception of Surface Go series devices). + + Depending on the device in question, this EC provides varying + functionality, including: + - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) + - battery status information (all devices) + - thermal sensor access (all devices) + - performance mode / cooling mode control (all devices) + - clipboard detachment system control (Surface Book 2 and 3) + - HID / keyboard input (Surface Laptops, Surface Book 3) + + This option controls whether the Surface SAM subsystem core will be + built. This includes a driver for the Surface Serial Hub (SSH), which + is the device responsible for the communication with the EC, and a + basic kernel interface exposing the EC functionality to other client + drivers, i.e. allowing them to make requests to the EC and receive + events from it. Selecting this option alone will not provide any + client drivers and therefore no functionality beyond the in-kernel + interface. Said functionality is the responsibility of the respective + client drivers. + + Note: While 4th-generation Surface devices also make use of a SAM EC, + due to a difference in the communication interface of the controller, + only 5th and later generations are currently supported. Specifically, + devices using SAM-over-SSH are supported, whereas devices using + SAM-over-HID, which is used on the 4th generation, are currently not + supported. + + Choose m if you want to build the SAM subsystem core and SSH driver as + module, y if you want to build it into the kernel and n if you don't + want it at all. + +config SURFACE_AGGREGATOR_BUS + bool "Surface System Aggregator Module Bus" + depends on SURFACE_AGGREGATOR + default y + help + Expands the Surface System Aggregator Module (SSAM) core driver by + providing a dedicated bus and client-device type. + + This bus and device type are intended to provide and simplify support + for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are + not auto-detectable via the conventional means (e.g. ACPI). + +config SURFACE_AGGREGATOR_ERROR_INJECTION + bool "Surface System Aggregator Module Error Injection Capabilities" + depends on SURFACE_AGGREGATOR + depends on FUNCTION_ERROR_INJECTION + help + Provides error-injection capabilities for the Surface System + Aggregator Module subsystem and Surface Serial Hub driver. + + Specifically, exports error injection hooks to be used with the + kernel's function error injection capabilities to simulate underlying + transport and communication problems, such as invalid data sent to or + received from the EC, dropped data, and communication timeouts. + Intended for development and debugging. diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile new file mode 100644 index 000000000000..c112e2c7112b --- /dev/null +++ b/drivers/platform/surface/aggregator/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + +# For include/trace/define_trace.h to include trace.h +CFLAGS_core.o = -I$(src) + +obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o + +surface_aggregator-objs := core.o +surface_aggregator-objs += ssh_parser.o +surface_aggregator-objs += ssh_packet_layer.o +surface_aggregator-objs += ssh_request_layer.o +surface_aggregator-objs += controller.o + +ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) +surface_aggregator-objs += bus.o +endif diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c new file mode 100644 index 000000000000..a9b660af0917 --- /dev/null +++ b/drivers/platform/surface/aggregator/bus.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module bus and device integration. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include + +#include +#include + +#include "bus.h" +#include "controller.h" + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", + sdev->uid.domain, sdev->uid.category, sdev->uid.target, + sdev->uid.instance, sdev->uid.function); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ssam_device_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ssam_device); + +static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", + sdev->uid.domain, sdev->uid.category, + sdev->uid.target, sdev->uid.instance, + sdev->uid.function); +} + +static void ssam_device_release(struct device *dev) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + ssam_controller_put(sdev->ctrl); + kfree(sdev); +} + +const struct device_type ssam_device_type = { + .name = "surface_aggregator_device", + .groups = ssam_device_groups, + .uevent = ssam_device_uevent, + .release = ssam_device_release, +}; +EXPORT_SYMBOL_GPL(ssam_device_type); + +/** + * ssam_device_alloc() - Allocate and initialize a SSAM client device. + * @ctrl: The controller under which the device should be added. + * @uid: The UID of the device to be added. + * + * Allocates and initializes a new client device. The parent of the device + * will be set to the controller device and the name will be set based on the + * UID. Note that the device still has to be added via ssam_device_add(). + * Refer to that function for more details. + * + * Return: Returns the newly allocated and initialized SSAM client device, or + * %NULL if it could not be allocated. + */ +struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + struct ssam_device_uid uid) +{ + struct ssam_device *sdev; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return NULL; + + device_initialize(&sdev->dev); + sdev->dev.bus = &ssam_bus_type; + sdev->dev.type = &ssam_device_type; + sdev->dev.parent = ssam_controller_device(ctrl); + sdev->ctrl = ssam_controller_get(ctrl); + sdev->uid = uid; + + dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", + sdev->uid.domain, sdev->uid.category, sdev->uid.target, + sdev->uid.instance, sdev->uid.function); + + return sdev; +} +EXPORT_SYMBOL_GPL(ssam_device_alloc); + +/** + * ssam_device_add() - Add a SSAM client device. + * @sdev: The SSAM client device to be added. + * + * Added client devices must be guaranteed to always have a valid and active + * controller. Thus, this function will fail with %-ENODEV if the controller + * of the device has not been initialized yet, has been suspended, or has been + * shut down. + * + * The caller of this function should ensure that the corresponding call to + * ssam_device_remove() is issued before the controller is shut down. If the + * added device is a direct child of the controller device (default), it will + * be automatically removed when the controller is shut down. + * + * By default, the controller device will become the parent of the newly + * created client device. The parent may be changed before ssam_device_add is + * called, but care must be taken that a) the correct suspend/resume ordering + * is guaranteed and b) the client device does not outlive the controller, + * i.e. that the device is removed before the controller is being shut down. + * In case these guarantees have to be manually enforced, please refer to the + * ssam_client_link() and ssam_client_bind() functions, which are intended to + * set up device-links for this purpose. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssam_device_add(struct ssam_device *sdev) +{ + int status; + + /* + * Ensure that we can only add new devices to a controller if it has + * been started and is not going away soon. This works in combination + * with ssam_controller_remove_clients to ensure driver presence for the + * controller device, i.e. it ensures that the controller (sdev->ctrl) + * is always valid and can be used for requests as long as the client + * device we add here is registered as child under it. This essentially + * guarantees that the client driver can always expect the preconditions + * for functions like ssam_request_sync (controller has to be started + * and is not suspended) to hold and thus does not have to check for + * them. + * + * Note that for this to work, the controller has to be a parent device. + * If it is not a direct parent, care has to be taken that the device is + * removed via ssam_device_remove(), as device_unregister does not + * remove child devices recursively. + */ + ssam_controller_statelock(sdev->ctrl); + + if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_stateunlock(sdev->ctrl); + return -ENODEV; + } + + status = device_add(&sdev->dev); + + ssam_controller_stateunlock(sdev->ctrl); + return status; +} +EXPORT_SYMBOL_GPL(ssam_device_add); + +/** + * ssam_device_remove() - Remove a SSAM client device. + * @sdev: The device to remove. + * + * Removes and unregisters the provided SSAM client device. + */ +void ssam_device_remove(struct ssam_device *sdev) +{ + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_GPL(ssam_device_remove); + +/** + * ssam_device_id_compatible() - Check if a device ID matches a UID. + * @id: The device ID as potential match. + * @uid: The device UID matching against. + * + * Check if the given ID is a match for the given UID, i.e. if a device with + * the provided UID is compatible to the given ID following the match rules + * described in its &ssam_device_id.match_flags member. + * + * Return: Returns %true if the given UID is compatible to the match rule + * described by the given ID, %false otherwise. + */ +static bool ssam_device_id_compatible(const struct ssam_device_id *id, + struct ssam_device_uid uid) +{ + if (id->domain != uid.domain || id->category != uid.category) + return false; + + if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) + return false; + + if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) + return false; + + if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) + return false; + + return true; +} + +/** + * ssam_device_id_is_null() - Check if a device ID is null. + * @id: The device ID to check. + * + * Check if a given device ID is null, i.e. all zeros. Used to check for the + * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. + * + * Return: Returns %true if the given ID represents a null ID, %false + * otherwise. + */ +static bool ssam_device_id_is_null(const struct ssam_device_id *id) +{ + return id->match_flags == 0 && + id->domain == 0 && + id->category == 0 && + id->target == 0 && + id->instance == 0 && + id->function == 0 && + id->driver_data == 0; +} + +/** + * ssam_device_id_match() - Find the matching ID table entry for the given UID. + * @table: The table to search in. + * @uid: The UID to matched against the individual table entries. + * + * Find the first match for the provided device UID in the provided ID table + * and return it. Returns %NULL if no match could be found. + */ +const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, + const struct ssam_device_uid uid) +{ + const struct ssam_device_id *id; + + for (id = table; !ssam_device_id_is_null(id); ++id) + if (ssam_device_id_compatible(id, uid)) + return id; + + return NULL; +} +EXPORT_SYMBOL_GPL(ssam_device_id_match); + +/** + * ssam_device_get_match() - Find and return the ID matching the device in the + * ID table of the bound driver. + * @dev: The device for which to get the matching ID table entry. + * + * Find the fist match for the UID of the device in the ID table of the + * currently bound driver and return it. Returns %NULL if the device does not + * have a driver bound to it, the driver does not have match_table (i.e. it is + * %NULL), or there is no match in the driver's match_table. + * + * This function essentially calls ssam_device_id_match() with the ID table of + * the bound device driver and the UID of the device. + * + * Return: Returns the first match for the UID of the device in the device + * driver's match table, or %NULL if no such match could be found. + */ +const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) +{ + const struct ssam_device_driver *sdrv; + + sdrv = to_ssam_device_driver(dev->dev.driver); + if (!sdrv) + return NULL; + + if (!sdrv->match_table) + return NULL; + + return ssam_device_id_match(sdrv->match_table, dev->uid); +} +EXPORT_SYMBOL_GPL(ssam_device_get_match); + +/** + * ssam_device_get_match_data() - Find the ID matching the device in the + * ID table of the bound driver and return its ``driver_data`` member. + * @dev: The device for which to get the match data. + * + * Find the fist match for the UID of the device in the ID table of the + * corresponding driver and return its driver_data. Returns %NULL if the + * device does not have a driver bound to it, the driver does not have + * match_table (i.e. it is %NULL), there is no match in the driver's + * match_table, or the match does not have any driver_data. + * + * This function essentially calls ssam_device_get_match() and, if any match + * could be found, returns its ``struct ssam_device_id.driver_data`` member. + * + * Return: Returns the driver data associated with the first match for the UID + * of the device in the device driver's match table, or %NULL if no such match + * could be found. + */ +const void *ssam_device_get_match_data(const struct ssam_device *dev) +{ + const struct ssam_device_id *id; + + id = ssam_device_get_match(dev); + if (!id) + return NULL; + + return (const void *)id->driver_data; +} +EXPORT_SYMBOL_GPL(ssam_device_get_match_data); + +static int ssam_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); + struct ssam_device *sdev = to_ssam_device(dev); + + if (!is_ssam_device(dev)) + return 0; + + return !!ssam_device_id_match(sdrv->match_table, sdev->uid); +} + +static int ssam_bus_probe(struct device *dev) +{ + return to_ssam_device_driver(dev->driver) + ->probe(to_ssam_device(dev)); +} + +static int ssam_bus_remove(struct device *dev) +{ + struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); + + if (sdrv->remove) + sdrv->remove(to_ssam_device(dev)); + + return 0; +} + +struct bus_type ssam_bus_type = { + .name = "surface_aggregator", + .match = ssam_bus_match, + .probe = ssam_bus_probe, + .remove = ssam_bus_remove, +}; +EXPORT_SYMBOL_GPL(ssam_bus_type); + +/** + * __ssam_device_driver_register() - Register a SSAM client device driver. + * @sdrv: The driver to register. + * @owner: The module owning the provided driver. + * + * Please refer to the ssam_device_driver_register() macro for the normal way + * to register a driver from inside its owning module. + */ +int __ssam_device_driver_register(struct ssam_device_driver *sdrv, + struct module *owner) +{ + sdrv->driver.owner = owner; + sdrv->driver.bus = &ssam_bus_type; + + /* force drivers to async probe so I/O is possible in probe */ + sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; + + return driver_register(&sdrv->driver); +} +EXPORT_SYMBOL_GPL(__ssam_device_driver_register); + +/** + * ssam_device_driver_unregister - Unregister a SSAM device driver. + * @sdrv: The driver to unregister. + */ +void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) +{ + driver_unregister(&sdrv->driver); +} +EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + +static int ssam_remove_device(struct device *dev, void *_data) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + if (is_ssam_device(dev)) + ssam_device_remove(sdev); + + return 0; +} + +/** + * ssam_controller_remove_clients() - Remove SSAM client devices registered as + * direct children under the given controller. + * @ctrl: The controller to remove all direct clients for. + * + * Remove all SSAM client devices registered as direct children under the + * given controller. Note that this only accounts for direct children of the + * controller device. This does not take care of any client devices where the + * parent device has been manually set before calling ssam_device_add. Refer + * to ssam_device_add()/ssam_device_remove() for more details on those cases. + * + * To avoid new devices being added in parallel to this call, the main + * controller lock (not statelock) must be held during this (and if + * necessary, any subsequent deinitialization) call. + */ +void ssam_controller_remove_clients(struct ssam_controller *ctrl) +{ + struct device *dev; + + dev = ssam_controller_device(ctrl); + device_for_each_child_reverse(dev, NULL, ssam_remove_device); +} + +/** + * ssam_bus_register() - Register and set-up the SSAM client device bus. + */ +int ssam_bus_register(void) +{ + return bus_register(&ssam_bus_type); +} + +/** + * ssam_bus_unregister() - Unregister the SSAM client device bus. + */ +void ssam_bus_unregister(void) +{ + return bus_unregister(&ssam_bus_type); +} diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h new file mode 100644 index 000000000000..7712baaed6a5 --- /dev/null +++ b/drivers/platform/surface/aggregator/bus.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module bus and device integration. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_BUS_H +#define _SURFACE_AGGREGATOR_BUS_H + +#include + +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +void ssam_controller_remove_clients(struct ssam_controller *ctrl); + +int ssam_bus_register(void); +void ssam_bus_unregister(void); + +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} +static inline int ssam_bus_register(void) { return 0; } +static inline void ssam_bus_unregister(void) {} + +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ +#endif /* _SURFACE_AGGREGATOR_BUS_H */ diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c new file mode 100644 index 000000000000..5bcb59ed579d --- /dev/null +++ b/drivers/platform/surface/aggregator/controller.c @@ -0,0 +1,2579 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Main SSAM/SSH controller structure and functionality. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "controller.h" +#include "ssh_msgb.h" +#include "ssh_request_layer.h" + +#include "trace.h" + + +/* -- Safe counters. -------------------------------------------------------- */ + +/** + * ssh_seq_reset() - Reset/initialize sequence ID counter. + * @c: The counter to reset. + */ +static void ssh_seq_reset(struct ssh_seq_counter *c) +{ + WRITE_ONCE(c->value, 0); +} + +/** + * ssh_seq_next() - Get next sequence ID. + * @c: The counter providing the sequence IDs. + * + * Return: Returns the next sequence ID of the counter. + */ +static u8 ssh_seq_next(struct ssh_seq_counter *c) +{ + u8 old = READ_ONCE(c->value); + u8 new = old + 1; + u8 ret; + + while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { + old = ret; + new = old + 1; + } + + return old; +} + +/** + * ssh_rqid_reset() - Reset/initialize request ID counter. + * @c: The counter to reset. + */ +static void ssh_rqid_reset(struct ssh_rqid_counter *c) +{ + WRITE_ONCE(c->value, 0); +} + +/** + * ssh_rqid_next() - Get next request ID. + * @c: The counter providing the request IDs. + * + * Return: Returns the next request ID of the counter, skipping any reserved + * request IDs. + */ +static u16 ssh_rqid_next(struct ssh_rqid_counter *c) +{ + u16 old = READ_ONCE(c->value); + u16 new = ssh_rqid_next_valid(old); + u16 ret; + + while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { + old = ret; + new = ssh_rqid_next_valid(old); + } + + return old; +} + + +/* -- Event notifier/callbacks. --------------------------------------------- */ +/* + * The notifier system is based on linux/notifier.h, specifically the SRCU + * implementation. The difference to that is, that some bits of the notifier + * call return value can be tracked across multiple calls. This is done so + * that handling of events can be tracked and a warning can be issued in case + * an event goes unhandled. The idea of that warning is that it should help + * discover and identify new/currently unimplemented features. + */ + +/** + * ssam_event_matches_notifier() - Test if an event matches a notifier. + * @n: The event notifier to test against. + * @event: The event to test. + * + * Return: Returns %true if the given event matches the given notifier + * according to the rules set in the notifier's event mask, %false otherwise. + */ +static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, + const struct ssam_event *event) +{ + bool match = n->event.id.target_category == event->target_category; + + if (n->event.mask & SSAM_EVENT_MASK_TARGET) + match &= n->event.reg.target_id == event->target_id; + + if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) + match &= n->event.id.instance == event->instance_id; + + return match; +} + +/** + * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. + * @nh: The notifier head for which the notifier callbacks should be called. + * @event: The event data provided to the callbacks. + * + * Call all registered notifier callbacks in order of their priority until + * either no notifier is left or a notifier returns a value with the + * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via + * ssam_notifier_from_errno() on any non-zero error value. + * + * Return: Returns the notifier status value, which contains the notifier + * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a + * potential error value returned from the last executed notifier callback. + * Use ssam_notifier_to_errno() to convert this value to the original error + * value. + */ +static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) +{ + struct ssam_event_notifier *nf; + int ret = 0, idx; + + idx = srcu_read_lock(&nh->srcu); + + list_for_each_entry_rcu(nf, &nh->head, base.node, + srcu_read_lock_held(&nh->srcu)) { + if (ssam_event_matches_notifier(nf, event)) { + ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); + if (ret & SSAM_NOTIF_STOP) + break; + } + } + + srcu_read_unlock(&nh->srcu, idx); + return ret; +} + +/** + * ssam_nfblk_insert() - Insert a new notifier block into the given notifier + * list. + * @nh: The notifier head into which the block should be inserted. + * @nb: The notifier block to add. + * + * Note: This function must be synchronized by the caller with respect to other + * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * + * Return: Returns zero on success, %-EEXIST if the notifier block has already + * been registered. + */ +static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) +{ + struct ssam_notifier_block *p; + struct list_head *h; + + /* Runs under lock, no need for RCU variant. */ + list_for_each(h, &nh->head) { + p = list_entry(h, struct ssam_notifier_block, node); + + if (unlikely(p == nb)) { + WARN(1, "double register detected"); + return -EEXIST; + } + + if (nb->priority > p->priority) + break; + } + + list_add_tail_rcu(&nb->node, h); + return 0; +} + +/** + * ssam_nfblk_find() - Check if a notifier block is registered on the given + * notifier head. + * list. + * @nh: The notifier head on which to search. + * @nb: The notifier block to search for. + * + * Note: This function must be synchronized by the caller with respect to other + * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * + * Return: Returns true if the given notifier block is registered on the given + * notifier head, false otherwise. + */ +static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) +{ + struct ssam_notifier_block *p; + + /* Runs under lock, no need for RCU variant. */ + list_for_each_entry(p, &nh->head, node) { + if (p == nb) + return true; + } + + return false; +} + +/** + * ssam_nfblk_remove() - Remove a notifier block from its notifier list. + * @nb: The notifier block to be removed. + * + * Note: This function must be synchronized by the caller with respect to + * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * Furthermore, the caller _must_ ensure SRCU synchronization by calling + * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to + * ensure that the removed notifier block is not in use any more. + */ +static void ssam_nfblk_remove(struct ssam_notifier_block *nb) +{ + list_del_rcu(&nb->node); +} + +/** + * ssam_nf_head_init() - Initialize the given notifier head. + * @nh: The notifier head to initialize. + */ +static int ssam_nf_head_init(struct ssam_nf_head *nh) +{ + int status; + + status = init_srcu_struct(&nh->srcu); + if (status) + return status; + + INIT_LIST_HEAD(&nh->head); + return 0; +} + +/** + * ssam_nf_head_destroy() - Deinitialize the given notifier head. + * @nh: The notifier head to deinitialize. + */ +static void ssam_nf_head_destroy(struct ssam_nf_head *nh) +{ + cleanup_srcu_struct(&nh->srcu); +} + + +/* -- Event/notification registry. ------------------------------------------ */ + +/** + * struct ssam_nf_refcount_key - Key used for event activation reference + * counting. + * @reg: The registry via which the event is enabled/disabled. + * @id: The ID uniquely describing the event. + */ +struct ssam_nf_refcount_key { + struct ssam_event_registry reg; + struct ssam_event_id id; +}; + +/** + * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event + * activations. + * @node: The node of this entry in the rb-tree. + * @key: The key of the event. + * @refcount: The reference-count of the event. + * @flags: The flags used when enabling the event. + */ +struct ssam_nf_refcount_entry { + struct rb_node node; + struct ssam_nf_refcount_key key; + int refcount; + u8 flags; +}; + +/** + * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given + * event. + * @nf: The notifier system reference. + * @reg: The registry used to enable/disable the event. + * @id: The event ID. + * + * Increments the reference-/activation-count associated with the specified + * event type/ID, allocating a new entry for this event ID if necessary. A + * newly allocated entry will have a refcount of one. + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns the refcount entry on success. Returns an error pointer + * with %-ENOSPC if there have already been %INT_MAX events of the specified + * ID and type registered, or %-ENOMEM if the entry could not be allocated. + */ +static struct ssam_nf_refcount_entry * +ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, + struct ssam_event_id id) +{ + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_refcount_key key; + struct rb_node **link = &nf->refcount.rb_node; + struct rb_node *parent = NULL; + int cmp; + + lockdep_assert_held(&nf->lock); + + key.reg = reg; + key.id = id; + + while (*link) { + entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); + parent = *link; + + cmp = memcmp(&key, &entry->key, sizeof(key)); + if (cmp < 0) { + link = &(*link)->rb_left; + } else if (cmp > 0) { + link = &(*link)->rb_right; + } else if (entry->refcount < INT_MAX) { + entry->refcount++; + return entry; + } else { + WARN_ON(1); + return ERR_PTR(-ENOSPC); + } + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return ERR_PTR(-ENOMEM); + + entry->key = key; + entry->refcount = 1; + + rb_link_node(&entry->node, parent, link); + rb_insert_color(&entry->node, &nf->refcount); + + return entry; +} + +/** + * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given + * event. + * @nf: The notifier system reference. + * @reg: The registry used to enable/disable the event. + * @id: The event ID. + * + * Decrements the reference-/activation-count of the specified event, + * returning its entry. If the returned entry has a refcount of zero, the + * caller is responsible for freeing it using kfree(). + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns the refcount entry on success or %NULL if the entry has not + * been found. + */ +static struct ssam_nf_refcount_entry * +ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, + struct ssam_event_id id) +{ + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_refcount_key key; + struct rb_node *node = nf->refcount.rb_node; + int cmp; + + lockdep_assert_held(&nf->lock); + + key.reg = reg; + key.id = id; + + while (node) { + entry = rb_entry(node, struct ssam_nf_refcount_entry, node); + + cmp = memcmp(&key, &entry->key, sizeof(key)); + if (cmp < 0) { + node = node->rb_left; + } else if (cmp > 0) { + node = node->rb_right; + } else { + entry->refcount--; + if (entry->refcount == 0) + rb_erase(&entry->node, &nf->refcount); + + return entry; + } + } + + return NULL; +} + +/** + * ssam_nf_refcount_empty() - Test if the notification system has any + * enabled/active events. + * @nf: The notification system. + */ +static bool ssam_nf_refcount_empty(struct ssam_nf *nf) +{ + return RB_EMPTY_ROOT(&nf->refcount); +} + +/** + * ssam_nf_call() - Call notification callbacks for the provided event. + * @nf: The notifier system + * @dev: The associated device, only used for logging. + * @rqid: The request ID of the event. + * @event: The event provided to the callbacks. + * + * Execute registered callbacks in order of their priority until either no + * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP + * bit set. Note that this bit is set automatically when converting non-zero + * error values via ssam_notifier_from_errno() to notifier values. + * + * Also note that any callback that could handle an event should return a value + * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go + * unhandled/ignored. In case no registered callback could handle an event, + * this function will emit a warning. + * + * In case a callback failed, this function will emit an error message. + */ +static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, + struct ssam_event *event) +{ + struct ssam_nf_head *nf_head; + int status, nf_ret; + + if (!ssh_rqid_is_event(rqid)) { + dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); + return; + } + + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + nf_ret = ssam_nfblk_call_chain(nf_head, event); + status = ssam_notifier_to_errno(nf_ret); + + if (status < 0) { + dev_err(dev, + "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", + status, event->target_category, event->target_id, + event->command_id, event->instance_id); + } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { + dev_warn(dev, + "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", + rqid, event->target_category, event->target_id, + event->command_id, event->instance_id); + } +} + +/** + * ssam_nf_init() - Initialize the notifier system. + * @nf: The notifier system to initialize. + */ +static int ssam_nf_init(struct ssam_nf *nf) +{ + int i, status; + + for (i = 0; i < SSH_NUM_EVENTS; i++) { + status = ssam_nf_head_init(&nf->head[i]); + if (status) + break; + } + + if (status) { + while (i--) + ssam_nf_head_destroy(&nf->head[i]); + + return status; + } + + mutex_init(&nf->lock); + return 0; +} + +/** + * ssam_nf_destroy() - Deinitialize the notifier system. + * @nf: The notifier system to deinitialize. + */ +static void ssam_nf_destroy(struct ssam_nf *nf) +{ + int i; + + for (i = 0; i < SSH_NUM_EVENTS; i++) + ssam_nf_head_destroy(&nf->head[i]); + + mutex_destroy(&nf->lock); +} + + +/* -- Event/async request completion system. -------------------------------- */ + +#define SSAM_CPLT_WQ_NAME "ssam_cpltq" + +/* + * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per + * work execution. Used to prevent livelocking of the workqueue. Value chosen + * via educated guess, may be adjusted. + */ +#define SSAM_CPLT_WQ_BATCH 10 + +/* + * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached + * &struct ssam_event_item. + * + * This length has been chosen to be accommodate standard touchpad and + * keyboard input events. Events with larger payloads will be allocated + * separately. + */ +#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 + +static struct kmem_cache *ssam_event_item_cache; + +/** + * ssam_event_item_cache_init() - Initialize the event item cache. + */ +int ssam_event_item_cache_init(void) +{ + const unsigned int size = sizeof(struct ssam_event_item) + + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; + const unsigned int align = __alignof__(struct ssam_event_item); + struct kmem_cache *cache; + + cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); + if (!cache) + return -ENOMEM; + + ssam_event_item_cache = cache; + return 0; +} + +/** + * ssam_event_item_cache_destroy() - Deinitialize the event item cache. + */ +void ssam_event_item_cache_destroy(void) +{ + kmem_cache_destroy(ssam_event_item_cache); + ssam_event_item_cache = NULL; +} + +static void __ssam_event_item_free_cached(struct ssam_event_item *item) +{ + kmem_cache_free(ssam_event_item_cache, item); +} + +static void __ssam_event_item_free_generic(struct ssam_event_item *item) +{ + kfree(item); +} + +/** + * ssam_event_item_free() - Free the provided event item. + * @item: The event item to free. + */ +static void ssam_event_item_free(struct ssam_event_item *item) +{ + trace_ssam_event_item_free(item); + item->ops.free(item); +} + +/** + * ssam_event_item_alloc() - Allocate an event item with the given payload size. + * @len: The event payload length. + * @flags: The flags used for allocation. + * + * Allocate an event item with the given payload size, preferring allocation + * from the event item cache if the payload is small enough (i.e. smaller than + * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload + * length values. The item free callback (``ops.free``) should not be + * overwritten after this call. + * + * Return: Returns the newly allocated event item. + */ +static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) +{ + struct ssam_event_item *item; + + if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { + item = kmem_cache_alloc(ssam_event_item_cache, flags); + if (!item) + return NULL; + + item->ops.free = __ssam_event_item_free_cached; + } else { + item = kzalloc(struct_size(item, event.data, len), flags); + if (!item) + return NULL; + + item->ops.free = __ssam_event_item_free_generic; + } + + item->event.length = len; + + trace_ssam_event_item_alloc(item, len); + return item; +} + +/** + * ssam_event_queue_push() - Push an event item to the event queue. + * @q: The event queue. + * @item: The item to add. + */ +static void ssam_event_queue_push(struct ssam_event_queue *q, + struct ssam_event_item *item) +{ + spin_lock(&q->lock); + list_add_tail(&item->node, &q->head); + spin_unlock(&q->lock); +} + +/** + * ssam_event_queue_pop() - Pop the next event item from the event queue. + * @q: The event queue. + * + * Returns and removes the next event item from the queue. Returns %NULL If + * there is no event item left. + */ +static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) +{ + struct ssam_event_item *item; + + spin_lock(&q->lock); + item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); + if (item) + list_del(&item->node); + spin_unlock(&q->lock); + + return item; +} + +/** + * ssam_event_queue_is_empty() - Check if the event queue is empty. + * @q: The event queue. + */ +static bool ssam_event_queue_is_empty(struct ssam_event_queue *q) +{ + bool empty; + + spin_lock(&q->lock); + empty = list_empty(&q->head); + spin_unlock(&q->lock); + + return empty; +} + +/** + * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. + * @cplt: The completion system on which to look for the queue. + * @tid: The target ID of the queue. + * @rqid: The request ID representing the event ID for which to get the queue. + * + * Return: Returns the event queue corresponding to the event type described + * by the given parameters. If the request ID does not represent an event, + * this function returns %NULL. If the target ID is not supported, this + * function will fall back to the default target ID (``tid = 1``). + */ +static +struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, + u8 tid, u16 rqid) +{ + u16 event = ssh_rqid_to_event(rqid); + u16 tidx = ssh_tid_to_index(tid); + + if (!ssh_rqid_is_event(rqid)) { + dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); + return NULL; + } + + if (!ssh_tid_is_valid(tid)) { + dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); + tidx = 0; + } + + return &cplt->event.target[tidx].queue[event]; +} + +/** + * ssam_cplt_submit() - Submit a work item to the completion system workqueue. + * @cplt: The completion system. + * @work: The work item to submit. + */ +static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) +{ + return queue_work(cplt->wq, work); +} + +/** + * ssam_cplt_submit_event() - Submit an event to the completion system. + * @cplt: The completion system. + * @item: The event item to submit. + * + * Submits the event to the completion system by queuing it on the event item + * queue and queuing the respective event queue work item on the completion + * workqueue, which will eventually complete the event. + * + * Return: Returns zero on success, %-EINVAL if there is no event queue that + * can handle the given event item. + */ +static int ssam_cplt_submit_event(struct ssam_cplt *cplt, + struct ssam_event_item *item) +{ + struct ssam_event_queue *evq; + + evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); + if (!evq) + return -EINVAL; + + ssam_event_queue_push(evq, item); + ssam_cplt_submit(cplt, &evq->work); + return 0; +} + +/** + * ssam_cplt_flush() - Flush the completion system. + * @cplt: The completion system. + * + * Flush the completion system by waiting until all currently submitted work + * items have been completed. + * + * Note: This function does not guarantee that all events will have been + * handled once this call terminates. In case of a larger number of + * to-be-completed events, the event queue work function may re-schedule its + * work item, which this flush operation will ignore. + * + * This operation is only intended to, during normal operation prior to + * shutdown, try to complete most events and requests to get them out of the + * system while the system is still fully operational. It does not aim to + * provide any guarantee that all of them have been handled. + */ +static void ssam_cplt_flush(struct ssam_cplt *cplt) +{ + flush_workqueue(cplt->wq); +} + +static void ssam_event_queue_work_fn(struct work_struct *work) +{ + struct ssam_event_queue *queue; + struct ssam_event_item *item; + struct ssam_nf *nf; + struct device *dev; + unsigned int iterations = SSAM_CPLT_WQ_BATCH; + + queue = container_of(work, struct ssam_event_queue, work); + nf = &queue->cplt->event.notif; + dev = queue->cplt->dev; + + /* Limit number of processed events to avoid livelocking. */ + do { + item = ssam_event_queue_pop(queue); + if (!item) + return; + + ssam_nf_call(nf, dev, item->rqid, &item->event); + ssam_event_item_free(item); + } while (--iterations); + + if (!ssam_event_queue_is_empty(queue)) + ssam_cplt_submit(queue->cplt, &queue->work); +} + +/** + * ssam_event_queue_init() - Initialize an event queue. + * @cplt: The completion system on which the queue resides. + * @evq: The event queue to initialize. + */ +static void ssam_event_queue_init(struct ssam_cplt *cplt, + struct ssam_event_queue *evq) +{ + evq->cplt = cplt; + spin_lock_init(&evq->lock); + INIT_LIST_HEAD(&evq->head); + INIT_WORK(&evq->work, ssam_event_queue_work_fn); +} + +/** + * ssam_cplt_init() - Initialize completion system. + * @cplt: The completion system to initialize. + * @dev: The device used for logging. + */ +static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) +{ + struct ssam_event_target *target; + int status, c, i; + + cplt->dev = dev; + + cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); + if (!cplt->wq) + return -ENOMEM; + + for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { + target = &cplt->event.target[c]; + + for (i = 0; i < ARRAY_SIZE(target->queue); i++) + ssam_event_queue_init(cplt, &target->queue[i]); + } + + status = ssam_nf_init(&cplt->event.notif); + if (status) + destroy_workqueue(cplt->wq); + + return status; +} + +/** + * ssam_cplt_destroy() - Deinitialize the completion system. + * @cplt: The completion system to deinitialize. + * + * Deinitialize the given completion system and ensure that all pending, i.e. + * yet-to-be-completed, event items and requests have been handled. + */ +static void ssam_cplt_destroy(struct ssam_cplt *cplt) +{ + /* + * Note: destroy_workqueue ensures that all currently queued work will + * be fully completed and the workqueue drained. This means that this + * call will inherently also free any queued ssam_event_items, thus we + * don't have to take care of that here explicitly. + */ + destroy_workqueue(cplt->wq); + ssam_nf_destroy(&cplt->event.notif); +} + + +/* -- Main SSAM device structures. ------------------------------------------ */ + +/** + * ssam_controller_device() - Get the &struct device associated with this + * controller. + * @c: The controller for which to get the device. + * + * Return: Returns the &struct device associated with this controller, + * providing its lower-level transport. + */ +struct device *ssam_controller_device(struct ssam_controller *c) +{ + return ssh_rtl_get_device(&c->rtl); +} +EXPORT_SYMBOL_GPL(ssam_controller_device); + +static void __ssam_controller_release(struct kref *kref) +{ + struct ssam_controller *ctrl = to_ssam_controller(kref, kref); + + /* + * The lock-call here is to satisfy lockdep. At this point we really + * expect this to be the last remaining reference to the controller. + * Anything else is a bug. + */ + ssam_controller_lock(ctrl); + ssam_controller_destroy(ctrl); + ssam_controller_unlock(ctrl); + + kfree(ctrl); +} + +/** + * ssam_controller_get() - Increment reference count of controller. + * @c: The controller. + * + * Return: Returns the controller provided as input. + */ +struct ssam_controller *ssam_controller_get(struct ssam_controller *c) +{ + if (c) + kref_get(&c->kref); + return c; +} +EXPORT_SYMBOL_GPL(ssam_controller_get); + +/** + * ssam_controller_put() - Decrement reference count of controller. + * @c: The controller. + */ +void ssam_controller_put(struct ssam_controller *c) +{ + if (c) + kref_put(&c->kref, __ssam_controller_release); +} +EXPORT_SYMBOL_GPL(ssam_controller_put); + +/** + * ssam_controller_statelock() - Lock the controller against state transitions. + * @c: The controller to lock. + * + * Lock the controller against state transitions. Holding this lock guarantees + * that the controller will not transition between states, i.e. if the + * controller is in state "started", when this lock has been acquired, it will + * remain in this state at least until the lock has been released. + * + * Multiple clients may concurrently hold this lock. In other words: The + * ``statelock`` functions represent the read-lock part of a r/w-semaphore. + * Actions causing state transitions of the controller must be executed while + * holding the write-part of this r/w-semaphore (see ssam_controller_lock() + * and ssam_controller_unlock() for that). + * + * See ssam_controller_stateunlock() for the corresponding unlock function. + */ +void ssam_controller_statelock(struct ssam_controller *c) +{ + down_read(&c->lock); +} +EXPORT_SYMBOL_GPL(ssam_controller_statelock); + +/** + * ssam_controller_stateunlock() - Unlock controller state transitions. + * @c: The controller to unlock. + * + * See ssam_controller_statelock() for the corresponding lock function. + */ +void ssam_controller_stateunlock(struct ssam_controller *c) +{ + up_read(&c->lock); +} +EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); + +/** + * ssam_controller_lock() - Acquire the main controller lock. + * @c: The controller to lock. + * + * This lock must be held for any state transitions, including transition to + * suspend/resumed states and during shutdown. See ssam_controller_statelock() + * for more details on controller locking. + * + * See ssam_controller_unlock() for the corresponding unlock function. + */ +void ssam_controller_lock(struct ssam_controller *c) +{ + down_write(&c->lock); +} + +/* + * ssam_controller_unlock() - Release the main controller lock. + * @c: The controller to unlock. + * + * See ssam_controller_lock() for the corresponding lock function. + */ +void ssam_controller_unlock(struct ssam_controller *c) +{ + up_write(&c->lock); +} + +static void ssam_handle_event(struct ssh_rtl *rtl, + const struct ssh_command *cmd, + const struct ssam_span *data) +{ + struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); + struct ssam_event_item *item; + + item = ssam_event_item_alloc(data->len, GFP_KERNEL); + if (!item) + return; + + item->rqid = get_unaligned_le16(&cmd->rqid); + item->event.target_category = cmd->tc; + item->event.target_id = cmd->tid_in; + item->event.command_id = cmd->cid; + item->event.instance_id = cmd->iid; + memcpy(&item->event.data[0], data->ptr, data->len); + + if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) + ssam_event_item_free(item); +} + +static const struct ssh_rtl_ops ssam_rtl_ops = { + .handle_event = ssam_handle_event, +}; + +static bool ssam_notifier_is_empty(struct ssam_controller *ctrl); +static void ssam_notifier_unregister_all(struct ssam_controller *ctrl); + +#define SSAM_SSH_DSM_REVISION 0 + +/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ +static const guid_t SSAM_SSH_DSM_GUID = + GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, + 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); + +enum ssh_dsm_fn { + SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, + SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, + SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, + SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, + SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, +}; + +static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) +{ + union acpi_object *obj; + u64 mask = 0; + int i; + + *funcs = 0; + + /* + * The _DSM function is only present on newer models. It is not + * present on 5th and 6th generation devices (i.e. up to and including + * Surface Pro 6, Surface Laptop 2, Surface Book 2). + * + * If the _DSM is not present, indicate that no function is supported. + * This will result in default values being set. + */ + if (!acpi_has_method(handle, "_DSM")) + return 0; + + obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, + SSAM_SSH_DSM_REVISION, 0, NULL, + ACPI_TYPE_BUFFER); + if (!obj) + return -EIO; + + for (i = 0; i < obj->buffer.length && i < 8; i++) + mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); + + if (mask & BIT(0)) + *funcs = mask; + + ACPI_FREE(obj); + return 0; +} + +static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) +{ + union acpi_object *obj; + u64 val; + + if (!(funcs & BIT(func))) + return 0; /* Not supported, leave *ret at its default value */ + + obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, + SSAM_SSH_DSM_REVISION, func, NULL, + ACPI_TYPE_INTEGER); + if (!obj) + return -EIO; + + val = obj->integer.value; + ACPI_FREE(obj); + + if (val > U32_MAX) + return -ERANGE; + + *ret = val; + return 0; +} + +/** + * ssam_controller_caps_load_from_acpi() - Load controller capabilities from + * ACPI _DSM. + * @handle: The handle of the ACPI controller/SSH device. + * @caps: Where to store the capabilities in. + * + * Initializes the given controller capabilities with default values, then + * checks and, if the respective _DSM functions are available, loads the + * actual capabilities from the _DSM. + * + * Return: Returns zero on success, a negative error code on failure. + */ +static +int ssam_controller_caps_load_from_acpi(acpi_handle handle, + struct ssam_controller_caps *caps) +{ + u32 d3_closes_handle = false; + u64 funcs; + int status; + + /* Set defaults. */ + caps->ssh_power_profile = U32_MAX; + caps->screen_on_sleep_idle_timeout = U32_MAX; + caps->screen_off_sleep_idle_timeout = U32_MAX; + caps->d3_closes_handle = false; + caps->ssh_buffer_size = U32_MAX; + + /* Pre-load supported DSM functions. */ + status = ssam_dsm_get_functions(handle, &funcs); + if (status) + return status; + + /* Load actual values from ACPI, if present. */ + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, + &caps->ssh_power_profile); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, + SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, + &caps->screen_on_sleep_idle_timeout); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, + SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, + &caps->screen_off_sleep_idle_timeout); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, + &d3_closes_handle); + if (status) + return status; + + caps->d3_closes_handle = !!d3_closes_handle; + + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, + &caps->ssh_buffer_size); + if (status) + return status; + + return 0; +} + +/** + * ssam_controller_init() - Initialize SSAM controller. + * @ctrl: The controller to initialize. + * @serdev: The serial device representing the underlying data transport. + * + * Initializes the given controller. Does neither start receiver nor + * transmitter threads. After this call, the controller has to be hooked up to + * the serdev core separately via &struct serdev_device_ops, relaying calls to + * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the + * controller has been hooked up, transmitter and receiver threads may be + * started via ssam_controller_start(). These setup steps need to be completed + * before controller can be used for requests. + */ +int ssam_controller_init(struct ssam_controller *ctrl, + struct serdev_device *serdev) +{ + acpi_handle handle = ACPI_HANDLE(&serdev->dev); + int status; + + init_rwsem(&ctrl->lock); + kref_init(&ctrl->kref); + + status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); + if (status) + return status; + + dev_dbg(&serdev->dev, + "device capabilities:\n" + " ssh_power_profile: %u\n" + " ssh_buffer_size: %u\n" + " screen_on_sleep_idle_timeout: %u\n" + " screen_off_sleep_idle_timeout: %u\n" + " d3_closes_handle: %u\n", + ctrl->caps.ssh_power_profile, + ctrl->caps.ssh_buffer_size, + ctrl->caps.screen_on_sleep_idle_timeout, + ctrl->caps.screen_off_sleep_idle_timeout, + ctrl->caps.d3_closes_handle); + + ssh_seq_reset(&ctrl->counter.seq); + ssh_rqid_reset(&ctrl->counter.rqid); + + /* Initialize event/request completion system. */ + status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); + if (status) + return status; + + /* Initialize request and packet transport layers. */ + status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); + if (status) { + ssam_cplt_destroy(&ctrl->cplt); + return status; + } + + /* + * Set state via write_once even though we expect to be in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); + return 0; +} + +/** + * ssam_controller_start() - Start the receiver and transmitter threads of the + * controller. + * @ctrl: The controller. + * + * Note: When this function is called, the controller should be properly + * hooked up to the serdev core via &struct serdev_device_ops. Please refer + * to ssam_controller_init() for more details on controller initialization. + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +int ssam_controller_start(struct ssam_controller *ctrl) +{ + int status; + + lockdep_assert_held_write(&ctrl->lock); + + if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) + return -EINVAL; + + status = ssh_rtl_start(&ctrl->rtl); + if (status) + return status; + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); + return 0; +} + +/* + * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during + * shutdown. + * + * Chosen to be larger than one full request timeout, including packets timing + * out. This value should give ample time to complete any outstanding requests + * during normal operation and account for the odd package timeout. + */ +#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) + +/** + * ssam_controller_shutdown() - Shut down the controller. + * @ctrl: The controller. + * + * Shuts down the controller by flushing all pending requests and stopping the + * transmitter and receiver threads. All requests submitted after this call + * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function + * is safe to use in parallel with ongoing request submission. + * + * In the course of this shutdown procedure, all currently registered + * notifiers will be unregistered. It is, however, strongly recommended to not + * rely on this behavior, and instead the party registering the notifier + * should unregister it before the controller gets shut down, e.g. via the + * SSAM bus which guarantees client devices to be removed before a shutdown. + * + * Note that events may still be pending after this call, but, due to the + * notifiers being unregistered, these events will be dropped when the + * controller is subsequently destroyed via ssam_controller_destroy(). + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +void ssam_controller_shutdown(struct ssam_controller *ctrl) +{ + enum ssam_controller_state s = ctrl->state; + int status; + + lockdep_assert_held_write(&ctrl->lock); + + if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) + return; + + /* + * Try to flush pending events and requests while everything still + * works. Note: There may still be packets and/or requests in the + * system after this call (e.g. via control packets submitted by the + * packet transport layer or flush timeout / failure, ...). Those will + * be handled with the ssh_rtl_shutdown() call below. + */ + status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); + if (status) { + ssam_err(ctrl, "failed to flush request transport layer: %d\n", + status); + } + + /* Try to flush all currently completing requests and events. */ + ssam_cplt_flush(&ctrl->cplt); + + /* + * We expect all notifiers to have been removed by the respective client + * driver that set them up at this point. If this warning occurs, some + * client driver has not done that... + */ + WARN_ON(!ssam_notifier_is_empty(ctrl)); + + /* + * Nevertheless, we should still take care of drivers that don't behave + * well. Thus disable all enabled events, unregister all notifiers. + */ + ssam_notifier_unregister_all(ctrl); + + /* + * Cancel remaining requests. Ensure no new ones can be queued and stop + * threads. + */ + ssh_rtl_shutdown(&ctrl->rtl); + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); + ctrl->rtl.ptl.serdev = NULL; +} + +/** + * ssam_controller_destroy() - Destroy the controller and free its resources. + * @ctrl: The controller. + * + * Ensures that all resources associated with the controller get freed. This + * function should only be called after the controller has been stopped via + * ssam_controller_shutdown(). In general, this function should not be called + * directly. The only valid place to call this function directly is during + * initialization, before the controller has been fully initialized and passed + * to other processes. This function is called automatically when the + * reference count of the controller reaches zero. + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +void ssam_controller_destroy(struct ssam_controller *ctrl) +{ + lockdep_assert_held_write(&ctrl->lock); + + if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) + return; + + WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); + + /* + * Note: New events could still have been received after the previous + * flush in ssam_controller_shutdown, before the request transport layer + * has been shut down. At this point, after the shutdown, we can be sure + * that no new events will be queued. The call to ssam_cplt_destroy will + * ensure that those remaining are being completed and freed. + */ + + /* Actually free resources. */ + ssam_cplt_destroy(&ctrl->cplt); + ssh_rtl_destroy(&ctrl->rtl); + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); +} + +/** + * ssam_controller_suspend() - Suspend the controller. + * @ctrl: The controller to suspend. + * + * Marks the controller as suspended. Note that display-off and D0-exit + * notifications have to be sent manually before transitioning the controller + * into the suspended state via this function. + * + * See ssam_controller_resume() for the corresponding resume function. + * + * Return: Returns %-EINVAL if the controller is currently not in the + * "started" state. + */ +int ssam_controller_suspend(struct ssam_controller *ctrl) +{ + ssam_controller_lock(ctrl); + + if (ctrl->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_unlock(ctrl); + return -EINVAL; + } + + ssam_dbg(ctrl, "pm: suspending controller\n"); + + /* + * Set state via write_once even though we're locked, due to + * smoke-testing in ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); + + ssam_controller_unlock(ctrl); + return 0; +} + +/** + * ssam_controller_resume() - Resume the controller from suspend. + * @ctrl: The controller to resume. + * + * Resume the controller from the suspended state it was put into via + * ssam_controller_suspend(). This function does not issue display-on and + * D0-entry notifications. If required, those have to be sent manually after + * this call. + * + * Return: Returns %-EINVAL if the controller is currently not suspended. + */ +int ssam_controller_resume(struct ssam_controller *ctrl) +{ + ssam_controller_lock(ctrl); + + if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { + ssam_controller_unlock(ctrl); + return -EINVAL; + } + + ssam_dbg(ctrl, "pm: resuming controller\n"); + + /* + * Set state via write_once even though we're locked, due to + * smoke-testing in ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); + + ssam_controller_unlock(ctrl); + return 0; +} + + +/* -- Top-level request interface ------------------------------------------- */ + +/** + * ssam_request_write_data() - Construct and write SAM request message to + * buffer. + * @buf: The buffer to write the data to. + * @ctrl: The controller via which the request will be sent. + * @spec: The request data and specification. + * + * Constructs a SAM/SSH request message and writes it to the provided buffer. + * The request and transport counters, specifically RQID and SEQ, will be set + * in this call. These counters are obtained from the controller. It is thus + * only valid to send the resulting message via the controller specified here. + * + * For calculation of the required buffer size, refer to the + * SSH_COMMAND_MESSAGE_LENGTH() macro. + * + * Return: Returns the number of bytes used in the buffer on success. Returns + * %-EINVAL if the payload length provided in the request specification is too + * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer + * is too small. + */ +ssize_t ssam_request_write_data(struct ssam_span *buf, + struct ssam_controller *ctrl, + const struct ssam_request *spec) +{ + struct msgbuf msgb; + u16 rqid; + u8 seq; + + if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) + return -EINVAL; + + if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) + return -EINVAL; + + msgb_init(&msgb, buf->ptr, buf->len); + seq = ssh_seq_next(&ctrl->counter.seq); + rqid = ssh_rqid_next(&ctrl->counter.rqid); + msgb_push_cmd(&msgb, seq, rqid, spec); + + return msgb_bytes_used(&msgb); +} +EXPORT_SYMBOL_GPL(ssam_request_write_data); + +static void ssam_request_sync_complete(struct ssh_request *rqst, + const struct ssh_command *cmd, + const struct ssam_span *data, int status) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + struct ssam_request_sync *r; + + r = container_of(rqst, struct ssam_request_sync, base); + r->status = status; + + if (r->resp) + r->resp->length = 0; + + if (status) { + rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); + return; + } + + if (!data) /* Handle requests without a response. */ + return; + + if (!r->resp || !r->resp->pointer) { + if (data->len) + rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); + return; + } + + if (data->len > r->resp->capacity) { + rtl_err(rtl, + "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", + r->resp->capacity, data->len); + r->status = -ENOSPC; + return; + } + + r->resp->length = data->len; + memcpy(r->resp->pointer, data->ptr, data->len); +} + +static void ssam_request_sync_release(struct ssh_request *rqst) +{ + complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); +} + +static const struct ssh_request_ops ssam_request_sync_ops = { + .release = ssam_request_sync_release, + .complete = ssam_request_sync_complete, +}; + +/** + * ssam_request_sync_alloc() - Allocate a synchronous request. + * @payload_len: The length of the request payload. + * @flags: Flags used for allocation. + * @rqst: Where to store the pointer to the allocated request. + * @buffer: Where to store the buffer descriptor for the message buffer of + * the request. + * + * Allocates a synchronous request with corresponding message buffer. The + * request still needs to be initialized ssam_request_sync_init() before + * it can be submitted, and the message buffer data must still be set to the + * returned buffer via ssam_request_sync_set_data() after it has been filled, + * if need be with adjusted message length. + * + * After use, the request and its corresponding message buffer should be freed + * via ssam_request_sync_free(). The buffer must not be freed separately. + * + * Return: Returns zero on success, %-ENOMEM if the request could not be + * allocated. + */ +int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, + struct ssam_request_sync **rqst, + struct ssam_span *buffer) +{ + size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); + + *rqst = kzalloc(sizeof(**rqst) + msglen, flags); + if (!*rqst) + return -ENOMEM; + + buffer->ptr = (u8 *)(*rqst + 1); + buffer->len = msglen; + + return 0; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_alloc); + +/** + * ssam_request_sync_free() - Free a synchronous request. + * @rqst: The request to be freed. + * + * Free a synchronous request and its corresponding buffer allocated with + * ssam_request_sync_alloc(). Do not use for requests allocated on the stack + * or via any other function. + * + * Warning: The caller must ensure that the request is not in use any more. + * I.e. the caller must ensure that it has the only reference to the request + * and the request is not currently pending. This means that the caller has + * either never submitted the request, request submission has failed, or the + * caller has waited until the submitted request has been completed via + * ssam_request_sync_wait(). + */ +void ssam_request_sync_free(struct ssam_request_sync *rqst) +{ + kfree(rqst); +} +EXPORT_SYMBOL_GPL(ssam_request_sync_free); + +/** + * ssam_request_sync_init() - Initialize a synchronous request struct. + * @rqst: The request to initialize. + * @flags: The request flags. + * + * Initializes the given request struct. Does not initialize the request + * message data. This has to be done explicitly after this call via + * ssam_request_sync_set_data() and the actual message data has to be written + * via ssam_request_write_data(). + * + * Return: Returns zero on success or %-EINVAL if the given flags are invalid. + */ +int ssam_request_sync_init(struct ssam_request_sync *rqst, + enum ssam_request_flags flags) +{ + int status; + + status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); + if (status) + return status; + + init_completion(&rqst->comp); + rqst->resp = NULL; + rqst->status = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_init); + +/** + * ssam_request_sync_submit() - Submit a synchronous request. + * @ctrl: The controller with which to submit the request. + * @rqst: The request to submit. + * + * Submit a synchronous request. The request has to be initialized and + * properly set up, including response buffer (may be %NULL if no response is + * expected) and command message data. This function does not wait for the + * request to be completed. + * + * If this function succeeds, ssam_request_sync_wait() must be used to ensure + * that the request has been completed before the response data can be + * accessed and/or the request can be freed. On failure, the request may + * immediately be freed. + * + * This function may only be used if the controller is active, i.e. has been + * initialized and not suspended. + */ +int ssam_request_sync_submit(struct ssam_controller *ctrl, + struct ssam_request_sync *rqst) +{ + int status; + + /* + * This is only a superficial check. In general, the caller needs to + * ensure that the controller is initialized and is not (and does not + * get) suspended during use, i.e. until the request has been completed + * (if _absolutely_ necessary, by use of ssam_controller_statelock/ + * ssam_controller_stateunlock, but something like ssam_client_link + * should be preferred as this needs to last until the request has been + * completed). + * + * Note that it is actually safe to use this function while the + * controller is in the process of being shut down (as ssh_rtl_submit + * is safe with regards to this), but it is generally discouraged to do + * so. + */ + if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { + ssh_request_put(&rqst->base); + return -ENODEV; + } + + status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); + ssh_request_put(&rqst->base); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_submit); + +/** + * ssam_request_sync() - Execute a synchronous request. + * @ctrl: The controller via which the request will be submitted. + * @spec: The request specification and payload. + * @rsp: The response buffer. + * + * Allocates a synchronous request with its message data buffer on the heap + * via ssam_request_sync_alloc(), fully initializes it via the provided + * request specification, submits it, and finally waits for its completion + * before freeing it and returning its status. + * + * Return: Returns the status of the request or any failure during setup. + */ +int ssam_request_sync(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp) +{ + struct ssam_request_sync *rqst; + struct ssam_span buf; + ssize_t len; + int status; + + status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); + if (status) + return status; + + status = ssam_request_sync_init(rqst, spec->flags); + if (status) + return status; + + ssam_request_sync_set_resp(rqst, rsp); + + len = ssam_request_write_data(&buf, ctrl, spec); + if (len < 0) { + ssam_request_sync_free(rqst); + return len; + } + + ssam_request_sync_set_data(rqst, buf.ptr, len); + + status = ssam_request_sync_submit(ctrl, rqst); + if (!status) + status = ssam_request_sync_wait(rqst); + + ssam_request_sync_free(rqst); + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync); + +/** + * ssam_request_sync_with_buffer() - Execute a synchronous request with the + * provided buffer as back-end for the message buffer. + * @ctrl: The controller via which the request will be submitted. + * @spec: The request specification and payload. + * @rsp: The response buffer. + * @buf: The buffer for the request message data. + * + * Allocates a synchronous request struct on the stack, fully initializes it + * using the provided buffer as message data buffer, submits it, and then + * waits for its completion before returning its status. The + * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required + * message buffer size. + * + * This function does essentially the same as ssam_request_sync(), but instead + * of dynamically allocating the request and message data buffer, it uses the + * provided message data buffer and stores the (small) request struct on the + * heap. + * + * Return: Returns the status of the request or any failure during setup. + */ +int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf) +{ + struct ssam_request_sync rqst; + ssize_t len; + int status; + + status = ssam_request_sync_init(&rqst, spec->flags); + if (status) + return status; + + ssam_request_sync_set_resp(&rqst, rsp); + + len = ssam_request_write_data(buf, ctrl, spec); + if (len < 0) + return len; + + ssam_request_sync_set_data(&rqst, buf->ptr, len); + + status = ssam_request_sync_submit(ctrl, &rqst); + if (!status) + status = ssam_request_sync_wait(&rqst); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); + + +/* -- Internal SAM requests. ------------------------------------------------ */ + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x13, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x15, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x16, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x33, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x34, + .instance_id = 0x00, +}); + +/** + * struct ssh_notification_params - Command payload to enable/disable SSH + * notifications. + * @target_category: The target category for which notifications should be + * enabled/disabled. + * @flags: Flags determining how notifications are being sent. + * @request_id: The request ID that is used to send these notifications. + * @instance_id: The specific instance in the given target category for + * which notifications should be enabled. + */ +struct ssh_notification_params { + u8 target_category; + u8 flags; + __le16 request_id; + u8 instance_id; +} __packed; + +static_assert(sizeof(struct ssh_notification_params) == 5); + +static int __ssam_ssh_event_request(struct ssam_controller *ctrl, + struct ssam_event_registry reg, u8 cid, + struct ssam_event_id id, u8 flags) +{ + struct ssh_notification_params params; + struct ssam_request rqst; + struct ssam_response result; + int status; + + u16 rqid = ssh_tc_to_rqid(id.target_category); + u8 buf = 0; + + /* Only allow RQIDs that lie within the event spectrum. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + params.target_category = id.target_category; + params.instance_id = id.instance; + params.flags = flags; + put_unaligned_le16(rqid, ¶ms.request_id); + + rqst.target_category = reg.target_category; + rqst.target_id = reg.target_id; + rqst.command_id = cid; + rqst.instance_id = 0x00; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(params); + rqst.payload = (u8 *)¶ms; + + result.capacity = sizeof(buf); + result.length = 0; + result.pointer = &buf; + + status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, + sizeof(params)); + + return status < 0 ? status : buf; +} + +/** + * ssam_ssh_event_enable() - Enable SSH event. + * @ctrl: The controller for which to enable the event. + * @reg: The event registry describing what request to use for enabling and + * disabling the event. + * @id: The event identifier. + * @flags: The event flags. + * + * Enables the specified event on the EC. This function does not manage + * reference counting of enabled events and is basically only a wrapper for + * the raw EC request. If the specified event is already enabled, the EC will + * ignore this request. + * + * Return: Returns the status of the executed SAM request (zero on success and + * negative on direct failure) or %-EPROTO if the request response indicates a + * failure. + */ +static int ssam_ssh_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + int status; + + status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); + + if (status < 0 && status != -EINVAL) { + ssam_err(ctrl, + "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", + id.target_category, id.instance, reg.target_category); + } + + if (status > 0) { + ssam_err(ctrl, + "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", + status, id.target_category, id.instance, reg.target_category); + return -EPROTO; + } + + return status; +} + +/** + * ssam_ssh_event_disable() - Disable SSH event. + * @ctrl: The controller for which to disable the event. + * @reg: The event registry describing what request to use for enabling and + * disabling the event (must be same as used when enabling the event). + * @id: The event identifier. + * @flags: The event flags (likely ignored for disabling of events). + * + * Disables the specified event on the EC. This function does not manage + * reference counting of enabled events and is basically only a wrapper for + * the raw EC request. If the specified event is already disabled, the EC will + * ignore this request. + * + * Return: Returns the status of the executed SAM request (zero on success and + * negative on direct failure) or %-EPROTO if the request response indicates a + * failure. + */ +static int ssam_ssh_event_disable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + int status; + + status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); + + if (status < 0 && status != -EINVAL) { + ssam_err(ctrl, + "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", + id.target_category, id.instance, reg.target_category); + } + + if (status > 0) { + ssam_err(ctrl, + "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", + status, id.target_category, id.instance, reg.target_category); + return -EPROTO; + } + + return status; +} + + +/* -- Wrappers for internal SAM requests. ----------------------------------- */ + +/** + * ssam_get_firmware_version() - Get the SAM/EC firmware version. + * @ctrl: The controller. + * @version: Where to store the version number. + * + * Return: Returns zero on success or the status of the executed SAM request + * if that request failed. + */ +int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) +{ + __le32 __version; + int status; + + status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); + if (status) + return status; + + *version = le32_to_cpu(__version); + return 0; +} + +/** + * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned + * off. + * @ctrl: The controller. + * + * Notify the EC that the display has been turned off and the driver may enter + * a lower-power state. This will prevent events from being sent directly. + * Rather, the EC signals an event by pulling the wakeup GPIO high for as long + * as there are pending events. The events then need to be manually released, + * one by one, via the GPIO callback request. All pending events accumulated + * during this state can also be released by issuing the display-on + * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset + * the GPIO. + * + * On some devices, specifically ones with an integrated keyboard, the keyboard + * backlight will be turned off by this call. + * + * This function will only send the display-off notification command if + * display notifications are supported by the EC. Currently all known devices + * support these notifications. + * + * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + ssam_dbg(ctrl, "pm: notifying display off\n"); + + status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. + * @ctrl: The controller. + * + * Notify the EC that the display has been turned back on and the driver has + * exited its lower-power state. This notification is the counterpart to the + * display-off notification sent via ssam_ctrl_notif_display_off() and will + * reverse its effects, including resetting events to their default behavior. + * + * This function will only send the display-on notification command if display + * notifications are supported by the EC. Currently all known devices support + * these notifications. + * + * See ssam_ctrl_notif_display_off() for more details. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + ssam_dbg(ctrl, "pm: notifying display on\n"); + + status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 + * power state. + * @ctrl: The controller + * + * Notifies the EC that the driver prepares to exit the D0 power state in + * favor of a lower-power state. Exact effects of this function related to the + * EC are currently unknown. + * + * This function will only send the D0-exit notification command if D0-state + * notifications are supported by the EC. Only newer Surface generations + * support these notifications. + * + * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 exit\n"); + + status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 + * power state. + * @ctrl: The controller + * + * Notifies the EC that the driver has exited a lower-power state and entered + * the D0 power state. Exact effects of this function related to the EC are + * currently unknown. + * + * This function will only send the D0-entry notification command if D0-state + * notifications are supported by the EC. Only newer Surface generations + * support these notifications. + * + * See ssam_ctrl_notif_d0_exit() for more details. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 entry\n"); + + status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + + +/* -- Top-level event registry interface. ----------------------------------- */ + +/** + * ssam_notifier_register() - Register an event notifier. + * @ctrl: The controller to register the notifier on. + * @n: The event notifier to register. + * + * Register an event notifier and increment the usage counter of the + * associated SAM event. If the event was previously not enabled, it will be + * enabled during this call. + * + * Return: Returns zero on success, %-ENOSPC if there have already been + * %INT_MAX notifiers for the event ID/type associated with the notifier block + * registered, %-ENOMEM if the corresponding event entry could not be + * allocated. If this is the first time that a notifier block is registered + * for the specific associated event, returns the status of the event-enable + * EC-command. + */ +int ssam_notifier_register(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_head *nf_head; + struct ssam_nf *nf; + int status; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + nf = &ctrl->cplt.event.notif; + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + + mutex_lock(&nf->lock); + + entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); + if (IS_ERR(entry)) { + mutex_unlock(&nf->lock); + return PTR_ERR(entry); + } + + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + n->event.reg.target_category, n->event.id.target_category, + n->event.id.instance, entry->refcount); + + status = ssam_nfblk_insert(nf_head, &n->base); + if (status) { + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (entry->refcount == 0) + kfree(entry); + + mutex_unlock(&nf->lock); + return status; + } + + if (entry->refcount == 1) { + status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, + n->event.flags); + if (status) { + ssam_nfblk_remove(&n->base); + kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); + mutex_unlock(&nf->lock); + synchronize_srcu(&nf_head->srcu); + return status; + } + + entry->flags = n->event.flags; + + } else if (entry->flags != n->event.flags) { + ssam_warn(ctrl, + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + n->event.flags, entry->flags, n->event.reg.target_category, + n->event.id.target_category, n->event.id.instance); + } + + mutex_unlock(&nf->lock); + return 0; +} +EXPORT_SYMBOL_GPL(ssam_notifier_register); + +/** + * ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier and decrement the usage counter of the + * associated SAM event. If the usage counter reaches zero, the event will be + * disabled. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_head *nf_head; + struct ssam_nf *nf; + int status = 0; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + nf = &ctrl->cplt.event.notif; + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + + mutex_lock(&nf->lock); + + if (!ssam_nfblk_find(nf_head, &n->base)) { + mutex_unlock(&nf->lock); + return -ENOENT; + } + + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (WARN_ON(!entry)) { + /* + * If this does not return an entry, there's a logic error + * somewhere: The notifier block is registered, but the event + * refcount entry is not there. Remove the notifier block + * anyways. + */ + status = -ENOENT; + goto remove; + } + + ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + n->event.reg.target_category, n->event.id.target_category, + n->event.id.instance, entry->refcount); + + if (entry->flags != n->event.flags) { + ssam_warn(ctrl, + "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + n->event.flags, entry->flags, n->event.reg.target_category, + n->event.id.target_category, n->event.id.instance); + } + + if (entry->refcount == 0) { + status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, + n->event.flags); + kfree(entry); + } + +remove: + ssam_nfblk_remove(&n->base); + mutex_unlock(&nf->lock); + synchronize_srcu(&nf_head->srcu); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_notifier_unregister); + +/** + * ssam_notifier_disable_registered() - Disable events for all registered + * notifiers. + * @ctrl: The controller for which to disable the notifiers/events. + * + * Disables events for all currently registered notifiers. In case of an error + * (EC command failing), all previously disabled events will be restored and + * the error code returned. + * + * This function is intended to disable all events prior to hibernation entry. + * See ssam_notifier_restore_registered() to restore/re-enable all events + * disabled with this function. + * + * Note that this function will not disable events for notifiers registered + * after calling this function. It should thus be made sure that no new + * notifiers are going to be added after this call and before the corresponding + * call to ssam_notifier_restore_registered(). + * + * Return: Returns zero on success. In case of failure returns the error code + * returned by the failed EC command to disable an event. + */ +int ssam_notifier_disable_registered(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct rb_node *n; + int status; + + mutex_lock(&nf->lock); + for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + status = ssam_ssh_event_disable(ctrl, e->key.reg, + e->key.id, e->flags); + if (status) + goto err; + } + mutex_unlock(&nf->lock); + + return 0; + +err: + for (n = rb_prev(n); n; n = rb_prev(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); + } + mutex_unlock(&nf->lock); + + return status; +} + +/** + * ssam_notifier_restore_registered() - Restore/re-enable events for all + * registered notifiers. + * @ctrl: The controller for which to restore the notifiers/events. + * + * Restores/re-enables all events for which notifiers have been registered on + * the given controller. In case of a failure, the error is logged and the + * function continues to try and enable the remaining events. + * + * This function is intended to restore/re-enable all registered events after + * hibernation. See ssam_notifier_disable_registered() for the counter part + * disabling the events and more details. + */ +void ssam_notifier_restore_registered(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct rb_node *n; + + mutex_lock(&nf->lock); + for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + + /* Ignore errors, will get logged in call. */ + ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); + } + mutex_unlock(&nf->lock); +} + +/** + * ssam_notifier_is_empty() - Check if there are any registered notifiers. + * @ctrl: The controller to check on. + * + * Return: Returns %true if there are currently no notifiers registered on the + * controller, %false otherwise. + */ +static bool ssam_notifier_is_empty(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + bool result; + + mutex_lock(&nf->lock); + result = ssam_nf_refcount_empty(nf); + mutex_unlock(&nf->lock); + + return result; +} + +/** + * ssam_notifier_unregister_all() - Unregister all currently registered + * notifiers. + * @ctrl: The controller to unregister the notifiers on. + * + * Unregisters all currently registered notifiers. This function is used to + * ensure that all notifiers will be unregistered and associated + * entries/resources freed when the controller is being shut down. + */ +static void ssam_notifier_unregister_all(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct ssam_nf_refcount_entry *e, *n; + + mutex_lock(&nf->lock); + rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { + /* Ignore errors, will get logged in call. */ + ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); + kfree(e); + } + nf->refcount = RB_ROOT; + mutex_unlock(&nf->lock); +} + + +/* -- Wakeup IRQ. ----------------------------------------------------------- */ + +static irqreturn_t ssam_irq_handle(int irq, void *dev_id) +{ + struct ssam_controller *ctrl = dev_id; + + ssam_dbg(ctrl, "pm: wake irq triggered\n"); + + /* + * Note: Proper wakeup detection is currently unimplemented. + * When the EC is in display-off or any other non-D0 state, it + * does not send events/notifications to the host. Instead it + * signals that there are events available via the wakeup IRQ. + * This driver is responsible for calling back to the EC to + * release these events one-by-one. + * + * This IRQ should not cause a full system resume by its own. + * Instead, events should be handled by their respective subsystem + * drivers, which in turn should signal whether a full system + * resume should be performed. + * + * TODO: Send GPIO callback command repeatedly to EC until callback + * returns 0x00. Return flag of callback is "has more events". + * Each time the command is sent, one event is "released". Once + * all events have been released (return = 0x00), the GPIO is + * re-armed. Detect wakeup events during this process, go back to + * sleep if no wakeup event has been received. + */ + + return IRQ_HANDLED; +} + +/** + * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. + * @ctrl: The controller for which the IRQ should be set up. + * + * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used + * to wake the device from a low power state. + * + * Note that this IRQ can only be triggered while the EC is in the display-off + * state. In this state, events are not sent to the host in the usual way. + * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending + * events and these events need to be released one-by-one via the GPIO + * callback request, either until there are no events left and the GPIO is + * reset, or all at once by transitioning the EC out of the display-off state, + * which will also clear the GPIO. + * + * Not all events, however, should trigger a full system wakeup. Instead the + * driver should, if necessary, inspect and forward each event to the + * corresponding subsystem, which in turn should decide if the system needs to + * be woken up. This logic has not been implemented yet, thus wakeup by this + * IRQ should be disabled by default to avoid spurious wake-ups, caused, for + * example, by the remaining battery percentage changing. Refer to comments in + * this function and comments in the corresponding IRQ handler for more + * details on how this should be implemented. + * + * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() + * for functions to transition the EC into and out of the display-off state as + * well as more details on it. + * + * The IRQ is disabled by default and has to be enabled before it can wake up + * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ + * should be freed via ssam_irq_free(). + */ +int ssam_irq_setup(struct ssam_controller *ctrl) +{ + struct device *dev = ssam_controller_device(ctrl); + struct gpio_desc *gpiod; + int irq; + int status; + + /* + * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. + * However, the GPIO line only gets reset by sending the GPIO callback + * command to SAM (or alternatively the display-on notification). As + * proper handling for this interrupt is not implemented yet, leaving + * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback + * never gets sent and thus the line never gets reset). To avoid this, + * mark the IRQ as TRIGGER_RISING for now, only creating a single + * interrupt, and let the SAM resume callback during the controller + * resume process clear it. + */ + const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; + + gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + irq = gpiod_to_irq(gpiod); + gpiod_put(gpiod); + + if (irq < 0) + return irq; + + status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, + "ssam_wakeup", ctrl); + if (status) + return status; + + ctrl->irq.num = irq; + disable_irq(ctrl->irq.num); + return 0; +} + +/** + * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. + * @ctrl: The controller for which the IRQ should be freed. + * + * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). + */ +void ssam_irq_free(struct ssam_controller *ctrl) +{ + free_irq(ctrl->irq.num, ctrl); + ctrl->irq.num = -1; +} + +/** + * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. + * @ctrl: The controller for which the IRQ should be armed. + * + * Sets up the IRQ so that it can be used to wake the device. Specifically, + * this function enables the irq and then, if the device is allowed to wake up + * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the + * corresponding function to disable the IRQ. + * + * This function is intended to arm the IRQ before entering S2idle suspend. + * + * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must + * be balanced. + */ +int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) +{ + struct device *dev = ssam_controller_device(ctrl); + int status; + + enable_irq(ctrl->irq.num); + if (device_may_wakeup(dev)) { + status = enable_irq_wake(ctrl->irq.num); + if (status) { + ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); + disable_irq(ctrl->irq.num); + return status; + } + + ctrl->irq.wakeup_enabled = true; + } else { + ctrl->irq.wakeup_enabled = false; + } + + return 0; +} + +/** + * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. + * @ctrl: The controller for which the IRQ should be disarmed. + * + * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). + * + * This function is intended to disarm the IRQ after exiting S2idle suspend. + * + * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must + * be balanced. + */ +void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) +{ + int status; + + if (ctrl->irq.wakeup_enabled) { + status = disable_irq_wake(ctrl->irq.num); + if (status) + ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); + + ctrl->irq.wakeup_enabled = false; + } + disable_irq(ctrl->irq.num); +} diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h new file mode 100644 index 000000000000..8297d34e7489 --- /dev/null +++ b/drivers/platform/surface/aggregator/controller.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Main SSAM/SSH controller structure and functionality. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H +#define _SURFACE_AGGREGATOR_CONTROLLER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_request_layer.h" + + +/* -- Safe counters. -------------------------------------------------------- */ + +/** + * struct ssh_seq_counter - Safe counter for SSH sequence IDs. + * @value: The current counter value. + */ +struct ssh_seq_counter { + u8 value; +}; + +/** + * struct ssh_rqid_counter - Safe counter for SSH request IDs. + * @value: The current counter value. + */ +struct ssh_rqid_counter { + u16 value; +}; + + +/* -- Event/notification system. -------------------------------------------- */ + +/** + * struct ssam_nf_head - Notifier head for SSAM events. + * @srcu: The SRCU struct for synchronization. + * @head: List-head for notifier blocks registered under this head. + */ +struct ssam_nf_head { + struct srcu_struct srcu; + struct list_head head; +}; + +/** + * struct ssam_nf - Notifier callback- and activation-registry for SSAM events. + * @lock: Lock guarding (de-)registration of notifier blocks. Note: This + * lock does not need to be held for notifier calls, only + * registration and deregistration. + * @refcount: The root of the RB-tree used for reference-counting enabled + * events/notifications. + * @head: The list of notifier heads for event/notification callbacks. + */ +struct ssam_nf { + struct mutex lock; + struct rb_root refcount; + struct ssam_nf_head head[SSH_NUM_EVENTS]; +}; + + +/* -- Event/async request completion system. -------------------------------- */ + +struct ssam_cplt; + +/** + * struct ssam_event_item - Struct for event queuing and completion. + * @node: The node in the queue. + * @rqid: The request ID of the event. + * @ops: Instance specific functions. + * @ops.free: Callback for freeing this event item. + * @event: Actual event data. + */ +struct ssam_event_item { + struct list_head node; + u16 rqid; + + struct { + void (*free)(struct ssam_event_item *event); + } ops; + + struct ssam_event event; /* must be last */ +}; + +/** + * struct ssam_event_queue - Queue for completing received events. + * @cplt: Reference to the completion system on which this queue is active. + * @lock: The lock for any operation on the queue. + * @head: The list-head of the queue. + * @work: The &struct work_struct performing completion work for this queue. + */ +struct ssam_event_queue { + struct ssam_cplt *cplt; + + spinlock_t lock; + struct list_head head; + struct work_struct work; +}; + +/** + * struct ssam_event_target - Set of queues for a single SSH target ID. + * @queue: The array of queues, one queue per event ID. + */ +struct ssam_event_target { + struct ssam_event_queue queue[SSH_NUM_EVENTS]; +}; + +/** + * struct ssam_cplt - SSAM event/async request completion system. + * @dev: The device with which this system is associated. Only used + * for logging. + * @wq: The &struct workqueue_struct on which all completion work + * items are queued. + * @event: Event completion management. + * @event.target: Array of &struct ssam_event_target, one for each target. + * @event.notif: Notifier callbacks and event activation reference counting. + */ +struct ssam_cplt { + struct device *dev; + struct workqueue_struct *wq; + + struct { + struct ssam_event_target target[SSH_NUM_TARGETS]; + struct ssam_nf notif; + } event; +}; + + +/* -- Main SSAM device structures. ------------------------------------------ */ + +/** + * enum ssam_controller_state - State values for &struct ssam_controller. + * @SSAM_CONTROLLER_UNINITIALIZED: + * The controller has not been initialized yet or has been deinitialized. + * @SSAM_CONTROLLER_INITIALIZED: + * The controller is initialized, but has not been started yet. + * @SSAM_CONTROLLER_STARTED: + * The controller has been started and is ready to use. + * @SSAM_CONTROLLER_STOPPED: + * The controller has been stopped. + * @SSAM_CONTROLLER_SUSPENDED: + * The controller has been suspended. + */ +enum ssam_controller_state { + SSAM_CONTROLLER_UNINITIALIZED, + SSAM_CONTROLLER_INITIALIZED, + SSAM_CONTROLLER_STARTED, + SSAM_CONTROLLER_STOPPED, + SSAM_CONTROLLER_SUSPENDED, +}; + +/** + * struct ssam_controller_caps - Controller device capabilities. + * @ssh_power_profile: SSH power profile. + * @ssh_buffer_size: SSH driver UART buffer size. + * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. + * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. + * @d3_closes_handle: SAM closes UART handle in D3. + * + * Controller and SSH device capabilities found in ACPI. + */ +struct ssam_controller_caps { + u32 ssh_power_profile; + u32 ssh_buffer_size; + u32 screen_on_sleep_idle_timeout; + u32 screen_off_sleep_idle_timeout; + u32 d3_closes_handle:1; +}; + +/** + * struct ssam_controller - SSAM controller device. + * @kref: Reference count of the controller. + * @lock: Main lock for the controller, used to guard state changes. + * @state: Controller state. + * @rtl: Request transport layer for SSH I/O. + * @cplt: Completion system for SSH/SSAM events and asynchronous requests. + * @counter: Safe SSH message ID counters. + * @counter.seq: Sequence ID counter. + * @counter.rqid: Request ID counter. + * @irq: Wakeup IRQ resources. + * @irq.num: The wakeup IRQ number. + * @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend. + * @caps: The controller device capabilities. + */ +struct ssam_controller { + struct kref kref; + + struct rw_semaphore lock; + enum ssam_controller_state state; + + struct ssh_rtl rtl; + struct ssam_cplt cplt; + + struct { + struct ssh_seq_counter seq; + struct ssh_rqid_counter rqid; + } counter; + + struct { + int num; + bool wakeup_enabled; + } irq; + + struct ssam_controller_caps caps; +}; + +#define to_ssam_controller(ptr, member) \ + container_of(ptr, struct ssam_controller, member) + +#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) + +/** + * ssam_controller_receive_buf() - Provide input-data to the controller. + * @ctrl: The controller. + * @buf: The input buffer. + * @n: The number of bytes in the input buffer. + * + * Provide input data to be evaluated by the controller, which has been + * received via the lower-level transport. + * + * Return: Returns the number of bytes consumed, or, if the packet transport + * layer of the controller has been shut down, %-ESHUTDOWN. + */ +static inline +int ssam_controller_receive_buf(struct ssam_controller *ctrl, + const unsigned char *buf, size_t n) +{ + return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); +} + +/** + * ssam_controller_write_wakeup() - Notify the controller that the underlying + * device has space available for data to be written. + * @ctrl: The controller. + */ +static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) +{ + ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl); +} + +int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); +int ssam_controller_start(struct ssam_controller *ctrl); +void ssam_controller_shutdown(struct ssam_controller *ctrl); +void ssam_controller_destroy(struct ssam_controller *ctrl); + +int ssam_notifier_disable_registered(struct ssam_controller *ctrl); +void ssam_notifier_restore_registered(struct ssam_controller *ctrl); + +int ssam_irq_setup(struct ssam_controller *ctrl); +void ssam_irq_free(struct ssam_controller *ctrl); +int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl); +void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl); + +void ssam_controller_lock(struct ssam_controller *c); +void ssam_controller_unlock(struct ssam_controller *c); + +int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version); +int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); +int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); +int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); +int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); + +int ssam_controller_suspend(struct ssam_controller *ctrl); +int ssam_controller_resume(struct ssam_controller *ctrl); + +int ssam_event_item_cache_init(void); +void ssam_event_item_cache_destroy(void); + +#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c new file mode 100644 index 000000000000..8dc2c267bcd6 --- /dev/null +++ b/drivers/platform/surface/aggregator/core.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Serial Hub (SSH) driver for communication with the Surface/System + * Aggregator Module (SSAM/SAM). + * + * Provides access to a SAM-over-SSH connected EC via a controller device. + * Handles communication via requests as well as enabling, disabling, and + * relaying of events. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bus.h" +#include "controller.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + + +/* -- Static controller reference. ------------------------------------------ */ + +/* + * Main controller reference. The corresponding lock must be held while + * accessing (reading/writing) the reference. + */ +static struct ssam_controller *__ssam_controller; +static DEFINE_SPINLOCK(__ssam_controller_lock); + +/** + * ssam_get_controller() - Get reference to SSAM controller. + * + * Returns a reference to the SSAM controller of the system or %NULL if there + * is none, it hasn't been set up yet, or it has already been unregistered. + * This function automatically increments the reference count of the + * controller, thus the calling party must ensure that ssam_controller_put() + * is called when it doesn't need the controller any more. + */ +struct ssam_controller *ssam_get_controller(void) +{ + struct ssam_controller *ctrl; + + spin_lock(&__ssam_controller_lock); + + ctrl = __ssam_controller; + if (!ctrl) + goto out; + + if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) + ctrl = NULL; + +out: + spin_unlock(&__ssam_controller_lock); + return ctrl; +} +EXPORT_SYMBOL_GPL(ssam_get_controller); + +/** + * ssam_try_set_controller() - Try to set the main controller reference. + * @ctrl: The controller to which the reference should point. + * + * Set the main controller reference to the given pointer if the reference + * hasn't been set already. + * + * Return: Returns zero on success or %-EEXIST if the reference has already + * been set. + */ +static int ssam_try_set_controller(struct ssam_controller *ctrl) +{ + int status = 0; + + spin_lock(&__ssam_controller_lock); + if (!__ssam_controller) + __ssam_controller = ctrl; + else + status = -EEXIST; + spin_unlock(&__ssam_controller_lock); + + return status; +} + +/** + * ssam_clear_controller() - Remove/clear the main controller reference. + * + * Clears the main controller reference, i.e. sets it to %NULL. This function + * should be called before the controller is shut down. + */ +static void ssam_clear_controller(void) +{ + spin_lock(&__ssam_controller_lock); + __ssam_controller = NULL; + spin_unlock(&__ssam_controller_lock); +} + +/** + * ssam_client_link() - Link an arbitrary client device to the controller. + * @c: The controller to link to. + * @client: The client device. + * + * Link an arbitrary client device to the controller by creating a device link + * between it as consumer and the controller device as provider. This function + * can be used for non-SSAM devices (or SSAM devices not registered as child + * under the controller) to guarantee that the controller is valid for as long + * as the driver of the client device is bound, and that proper suspend and + * resume ordering is guaranteed. + * + * The device link does not have to be destructed manually. It is removed + * automatically once the driver of the client device unbinds. + * + * Return: Returns zero on success, %-ENODEV if the controller is not ready or + * going to be removed soon, or %-ENOMEM if the device link could not be + * created for other reasons. + */ +int ssam_client_link(struct ssam_controller *c, struct device *client) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; + struct device_link *link; + struct device *ctrldev; + + ssam_controller_statelock(c); + + if (c->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + ctrldev = ssam_controller_device(c); + if (!ctrldev) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + link = device_link_add(client, ctrldev, flags); + if (!link) { + ssam_controller_stateunlock(c); + return -ENOMEM; + } + + /* + * Return -ENODEV if supplier driver is on its way to be removed. In + * this case, the controller won't be around for much longer and the + * device link is not going to save us any more, as unbinding is + * already in progress. + */ + if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + ssam_controller_stateunlock(c); + return 0; +} +EXPORT_SYMBOL_GPL(ssam_client_link); + +/** + * ssam_client_bind() - Bind an arbitrary client device to the controller. + * @client: The client device. + * + * Link an arbitrary client device to the controller by creating a device link + * between it as consumer and the main controller device as provider. This + * function can be used for non-SSAM devices to guarantee that the controller + * returned by this function is valid for as long as the driver of the client + * device is bound, and that proper suspend and resume ordering is guaranteed. + * + * This function does essentially the same as ssam_client_link(), except that + * it first fetches the main controller reference, then creates the link, and + * finally returns this reference. Note that this function does not increment + * the reference counter of the controller, as, due to the link, the + * controller lifetime is assured as long as the driver of the client device + * is bound. + * + * It is not valid to use the controller reference obtained by this method + * outside of the driver bound to the client device at the time of calling + * this function, without first incrementing the reference count of the + * controller via ssam_controller_get(). Even after doing this, care must be + * taken that requests are only submitted and notifiers are only + * (un-)registered when the controller is active and not suspended. In other + * words: The device link only lives as long as the client driver is bound and + * any guarantees enforced by this link (e.g. active controller state) can + * only be relied upon as long as this link exists and may need to be enforced + * in other ways afterwards. + * + * The created device link does not have to be destructed manually. It is + * removed automatically once the driver of the client device unbinds. + * + * Return: Returns the controller on success, an error pointer with %-ENODEV + * if the controller is not present, not ready or going to be removed soon, or + * %-ENOMEM if the device link could not be created for other reasons. + */ +struct ssam_controller *ssam_client_bind(struct device *client) +{ + struct ssam_controller *c; + int status; + + c = ssam_get_controller(); + if (!c) + return ERR_PTR(-ENODEV); + + status = ssam_client_link(c, client); + + /* + * Note that we can drop our controller reference in both success and + * failure cases: On success, we have bound the controller lifetime + * inherently to the client driver lifetime, i.e. it the controller is + * now guaranteed to outlive the client driver. On failure, we're not + * going to use the controller any more. + */ + ssam_controller_put(c); + + return status >= 0 ? c : ERR_PTR(status); +} +EXPORT_SYMBOL_GPL(ssam_client_bind); + + +/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ + +static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, + size_t n) +{ + struct ssam_controller *ctrl; + + ctrl = serdev_device_get_drvdata(dev); + return ssam_controller_receive_buf(ctrl, buf, n); +} + +static void ssam_write_wakeup(struct serdev_device *dev) +{ + ssam_controller_write_wakeup(serdev_device_get_drvdata(dev)); +} + +static const struct serdev_device_ops ssam_serdev_ops = { + .receive_buf = ssam_receive_buf, + .write_wakeup = ssam_write_wakeup, +}; + + +/* -- SysFS and misc. ------------------------------------------------------- */ + +static int ssam_log_firmware_version(struct ssam_controller *ctrl) +{ + u32 version, a, b, c; + int status; + + status = ssam_get_firmware_version(ctrl, &version); + if (status) + return status; + + a = (version >> 24) & 0xff; + b = ((version >> 8) & 0xffff); + c = version & 0xff; + + ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c); + return 0; +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ssam_controller *ctrl = dev_get_drvdata(dev); + u32 version, a, b, c; + int status; + + status = ssam_get_firmware_version(ctrl, &version); + if (status < 0) + return status; + + a = (version >> 24) & 0xff; + b = ((version >> 8) & 0xffff); + c = version & 0xff; + + return sysfs_emit(buf, "%u.%u.%u\n", a, b, c); +} +static DEVICE_ATTR_RO(firmware_version); + +static struct attribute *ssam_sam_attrs[] = { + &dev_attr_firmware_version.attr, + NULL +}; + +static const struct attribute_group ssam_sam_group = { + .name = "sam", + .attrs = ssam_sam_attrs, +}; + + +/* -- ACPI based device setup. ---------------------------------------------- */ + +static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, + void *ctx) +{ + struct serdev_device *serdev = ctx; + struct acpi_resource_common_serialbus *serial; + struct acpi_resource_uart_serialbus *uart; + bool flow_control; + int status = 0; + + if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) + return AE_OK; + + serial = &rsc->data.common_serial_bus; + if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) + return AE_OK; + + uart = &rsc->data.uart_serial_bus; + + /* Set up serdev device. */ + serdev_device_set_baudrate(serdev, uart->default_baud_rate); + + /* serdev currently only supports RTSCTS flow control. */ + if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) { + dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n", + uart->flow_control); + } + + /* Set RTSCTS flow control. */ + flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW; + serdev_device_set_flow_control(serdev, flow_control); + + /* serdev currently only supports EVEN/ODD parity. */ + switch (uart->parity) { + case ACPI_UART_PARITY_NONE: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + break; + case ACPI_UART_PARITY_EVEN: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); + break; + case ACPI_UART_PARITY_ODD: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); + break; + default: + dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n", + uart->parity); + break; + } + + if (status) { + dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n", + uart->parity, status); + return AE_ERROR; + } + + /* We've found the resource and are done. */ + return AE_CTRL_TERMINATE; +} + +static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, + struct serdev_device *serdev) +{ + return acpi_walk_resources(handle, METHOD_NAME__CRS, + ssam_serdev_setup_via_acpi_crs, serdev); +} + + +/* -- Power management. ----------------------------------------------------- */ + +static void ssam_serial_hub_shutdown(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to disable notifiers, signal display-off and D0-exit, ignore any + * errors. + * + * Note: It has not been established yet if this is actually + * necessary/useful for shutdown. + */ + + status = ssam_notifier_disable_registered(c); + if (status) { + ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n", + status); + } + + status = ssam_ctrl_notif_display_off(c); + if (status) + ssam_err(c, "pm: display-off notification failed: %d\n", status); + + status = ssam_ctrl_notif_d0_exit(c); + if (status) + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); +} + +#ifdef CONFIG_PM_SLEEP + +static int ssam_serial_hub_pm_prepare(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal display-off, This will quiesce events. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + status = ssam_ctrl_notif_display_off(c); + if (status) + ssam_err(c, "pm: display-off notification failed: %d\n", status); + + return status; +} + +static void ssam_serial_hub_pm_complete(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal display-on. This will restore events. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + status = ssam_ctrl_notif_display_on(c); + if (status) + ssam_err(c, "pm: display-on notification failed: %d\n", status); +} + +static int ssam_serial_hub_pm_suspend(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on + * error. + */ + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + goto err_notif; + } + + status = ssam_irq_arm_for_wakeup(c); + if (status) + goto err_irq; + + WARN_ON(ssam_controller_suspend(c)); + return 0; + +err_irq: + ssam_ctrl_notif_d0_entry(c); +err_notif: + ssam_ctrl_notif_display_on(c); + return status; +} + +static int ssam_serial_hub_pm_resume(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + WARN_ON(ssam_controller_resume(c)); + + /* + * Try to disable IRQ wakeup (if specified) and signal D0-entry. In + * case of errors, log them and try to restore normal operation state + * as far as possible. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + ssam_irq_disarm_wakeup(c); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + + return 0; +} + +static int ssam_serial_hub_pm_freeze(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * During hibernation image creation, we only have to ensure that the + * EC doesn't send us any events. This is done via the display-off + * and D0-exit notifications. Note that this sets up the wakeup IRQ + * on the EC side, however, we have disabled it by default on our side + * and won't enable it here. + * + * See ssam_serial_hub_poweroff() for more details on the hibernation + * process. + */ + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + ssam_ctrl_notif_display_on(c); + return status; + } + + WARN_ON(ssam_controller_suspend(c)); + return 0; +} + +static int ssam_serial_hub_pm_thaw(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + WARN_ON(ssam_controller_resume(c)); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + + return status; +} + +static int ssam_serial_hub_pm_poweroff(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * When entering hibernation and powering off the system, the EC, at + * least on some models, may disable events. Without us taking care of + * that, this leads to events not being enabled/restored when the + * system resumes from hibernation, resulting SAM-HID subsystem devices + * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being + * gone, etc. + * + * To avoid these issues, we disable all registered events here (this is + * likely not actually required) and restore them during the drivers PM + * restore callback. + * + * Wakeup from the EC interrupt is not supported during hibernation, + * so don't arm the IRQ here. + */ + + status = ssam_notifier_disable_registered(c); + if (status) { + ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n", + status); + return status; + } + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + ssam_notifier_restore_registered(c); + return status; + } + + WARN_ON(ssam_controller_suspend(c)); + return 0; +} + +static int ssam_serial_hub_pm_restore(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Ignore but log errors, try to restore state as much as possible in + * case of failures. See ssam_serial_hub_poweroff() for more details on + * the hibernation process. + */ + + WARN_ON(ssam_controller_resume(c)); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + + ssam_notifier_restore_registered(c); + return 0; +} + +static const struct dev_pm_ops ssam_serial_hub_pm_ops = { + .prepare = ssam_serial_hub_pm_prepare, + .complete = ssam_serial_hub_pm_complete, + .suspend = ssam_serial_hub_pm_suspend, + .resume = ssam_serial_hub_pm_resume, + .freeze = ssam_serial_hub_pm_freeze, + .thaw = ssam_serial_hub_pm_thaw, + .poweroff = ssam_serial_hub_pm_poweroff, + .restore = ssam_serial_hub_pm_restore, +}; + +#else /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops ssam_serial_hub_pm_ops = { }; + +#endif /* CONFIG_PM_SLEEP */ + + +/* -- Device/driver setup. -------------------------------------------------- */ + +static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; +static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; + +static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { + { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, + { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, + { }, +}; + +static int ssam_serial_hub_probe(struct serdev_device *serdev) +{ + struct ssam_controller *ctrl; + acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); + acpi_status astatus; + int status; + + if (gpiod_count(&serdev->dev, NULL) < 0) + return -ENODEV; + + status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); + if (status) + return status; + + /* Allocate controller. */ + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + /* Initialize controller. */ + status = ssam_controller_init(ctrl, serdev); + if (status) + goto err_ctrl_init; + + ssam_controller_lock(ctrl); + + /* Set up serdev device. */ + serdev_device_set_drvdata(serdev, ctrl); + serdev_device_set_client_ops(serdev, &ssam_serdev_ops); + status = serdev_device_open(serdev); + if (status) + goto err_devopen; + + astatus = ssam_serdev_setup_via_acpi(ssh, serdev); + if (ACPI_FAILURE(astatus)) { + status = -ENXIO; + goto err_devinit; + } + + /* Start controller. */ + status = ssam_controller_start(ctrl); + if (status) + goto err_devinit; + + ssam_controller_unlock(ctrl); + + /* + * Initial SAM requests: Log version and notify default/init power + * states. + */ + status = ssam_log_firmware_version(ctrl); + if (status) + goto err_initrq; + + status = ssam_ctrl_notif_d0_entry(ctrl); + if (status) + goto err_initrq; + + status = ssam_ctrl_notif_display_on(ctrl); + if (status) + goto err_initrq; + + status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); + if (status) + goto err_initrq; + + /* Set up IRQ. */ + status = ssam_irq_setup(ctrl); + if (status) + goto err_irq; + + /* Finally, set main controller reference. */ + status = ssam_try_set_controller(ctrl); + if (WARN_ON(status)) /* Currently, we're the only provider. */ + goto err_mainref; + + /* + * TODO: The EC can wake up the system via the associated GPIO interrupt + * in multiple situations. One of which is the remaining battery + * capacity falling below a certain threshold. Normally, we should + * use the device_init_wakeup function, however, the EC also seems + * to have other reasons for waking up the system and it seems + * that Windows has additional checks whether the system should be + * resumed. In short, this causes some spurious unwanted wake-ups. + * For now let's thus default power/wakeup to false. + */ + device_set_wakeup_capable(&serdev->dev, true); + acpi_walk_dep_device_list(ssh); + + return 0; + +err_mainref: + ssam_irq_free(ctrl); +err_irq: + sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); +err_initrq: + ssam_controller_lock(ctrl); + ssam_controller_shutdown(ctrl); +err_devinit: + serdev_device_close(serdev); +err_devopen: + ssam_controller_destroy(ctrl); + ssam_controller_unlock(ctrl); +err_ctrl_init: + kfree(ctrl); + return status; +} + +static void ssam_serial_hub_remove(struct serdev_device *serdev) +{ + struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); + int status; + + /* Clear static reference so that no one else can get a new one. */ + ssam_clear_controller(); + + /* Disable and free IRQ. */ + ssam_irq_free(ctrl); + + sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); + ssam_controller_lock(ctrl); + + /* Remove all client devices. */ + ssam_controller_remove_clients(ctrl); + + /* Act as if suspending to silence events. */ + status = ssam_ctrl_notif_display_off(ctrl); + if (status) { + dev_err(&serdev->dev, "display-off notification failed: %d\n", + status); + } + + status = ssam_ctrl_notif_d0_exit(ctrl); + if (status) { + dev_err(&serdev->dev, "D0-exit notification failed: %d\n", + status); + } + + /* Shut down controller and remove serdev device reference from it. */ + ssam_controller_shutdown(ctrl); + + /* Shut down actual transport. */ + serdev_device_wait_until_sent(serdev, 0); + serdev_device_close(serdev); + + /* Drop our controller reference. */ + ssam_controller_unlock(ctrl); + ssam_controller_put(ctrl); + + device_set_wakeup_capable(&serdev->dev, false); +} + +static const struct acpi_device_id ssam_serial_hub_match[] = { + { "MSHW0084", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); + +static struct serdev_device_driver ssam_serial_hub = { + .probe = ssam_serial_hub_probe, + .remove = ssam_serial_hub_remove, + .driver = { + .name = "surface_serial_hub", + .acpi_match_table = ssam_serial_hub_match, + .pm = &ssam_serial_hub_pm_ops, + .shutdown = ssam_serial_hub_shutdown, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + + +/* -- Module setup. --------------------------------------------------------- */ + +static int __init ssam_core_init(void) +{ + int status; + + status = ssam_bus_register(); + if (status) + goto err_bus; + + status = ssh_ctrl_packet_cache_init(); + if (status) + goto err_cpkg; + + status = ssam_event_item_cache_init(); + if (status) + goto err_evitem; + + status = serdev_device_driver_register(&ssam_serial_hub); + if (status) + goto err_register; + + return 0; + +err_register: + ssam_event_item_cache_destroy(); +err_evitem: + ssh_ctrl_packet_cache_destroy(); +err_cpkg: + ssam_bus_unregister(); +err_bus: + return status; +} +module_init(ssam_core_init); + +static void __exit ssam_core_exit(void) +{ + serdev_device_driver_unregister(&ssam_serial_hub); + ssam_event_item_cache_destroy(); + ssh_ctrl_packet_cache_destroy(); + ssam_bus_unregister(); +} +module_exit(ssam_core_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h new file mode 100644 index 000000000000..1221f642dda1 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH message builder functions. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H +#define _SURFACE_AGGREGATOR_SSH_MSGB_H + +#include +#include + +#include +#include + +/** + * struct msgbuf - Buffer struct to construct SSH messages. + * @begin: Pointer to the beginning of the allocated buffer space. + * @end: Pointer to the end (one past last element) of the allocated buffer + * space. + * @ptr: Pointer to the first free element in the buffer. + */ +struct msgbuf { + u8 *begin; + u8 *end; + u8 *ptr; +}; + +/** + * msgb_init() - Initialize the given message buffer struct. + * @msgb: The buffer struct to initialize + * @ptr: Pointer to the underlying memory by which the buffer will be backed. + * @cap: Size of the underlying memory. + * + * Initialize the given message buffer struct using the provided memory as + * backing. + */ +static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) +{ + msgb->begin = ptr; + msgb->end = ptr + cap; + msgb->ptr = ptr; +} + +/** + * msgb_bytes_used() - Return the current number of bytes used in the buffer. + * @msgb: The message buffer. + */ +static inline size_t msgb_bytes_used(const struct msgbuf *msgb) +{ + return msgb->ptr - msgb->begin; +} + +static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value) +{ + *msgb->ptr = value; + msgb->ptr += sizeof(u8); +} + +static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value) +{ + put_unaligned_le16(value, msgb->ptr); + msgb->ptr += sizeof(u16); +} + +/** + * msgb_push_u16() - Push a u16 value to the buffer. + * @msgb: The message buffer. + * @value: The value to push to the buffer. + */ +static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) +{ + if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) + return; + + __msgb_push_u16(msgb, value); +} + +/** + * msgb_push_syn() - Push SSH SYN bytes to the buffer. + * @msgb: The message buffer. + */ +static inline void msgb_push_syn(struct msgbuf *msgb) +{ + msgb_push_u16(msgb, SSH_MSG_SYN); +} + +/** + * msgb_push_buf() - Push raw data to the buffer. + * @msgb: The message buffer. + * @buf: The data to push to the buffer. + * @len: The length of the data to push to the buffer. + */ +static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) +{ + msgb->ptr = memcpy(msgb->ptr, buf, len) + len; +} + +/** + * msgb_push_crc() - Compute CRC and push it to the buffer. + * @msgb: The message buffer. + * @buf: The data for which the CRC should be computed. + * @len: The length of the data for which the CRC should be computed. + */ +static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) +{ + msgb_push_u16(msgb, ssh_crc(buf, len)); +} + +/** + * msgb_push_frame() - Push a SSH message frame header to the buffer. + * @msgb: The message buffer + * @ty: The type of the frame. + * @len: The length of the payload of the frame. + * @seq: The sequence ID of the frame/packet. + */ +static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) +{ + u8 *const begin = msgb->ptr; + + if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end)) + return; + + __msgb_push_u8(msgb, ty); /* Frame type. */ + __msgb_push_u16(msgb, len); /* Frame payload length. */ + __msgb_push_u8(msgb, seq); /* Frame sequence ID. */ + + msgb_push_crc(msgb, begin, msgb->ptr - begin); +} + +/** + * msgb_push_ack() - Push a SSH ACK frame to the buffer. + * @msgb: The message buffer + * @seq: The sequence ID of the frame/packet to be ACKed. + */ +static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) +{ + /* SYN. */ + msgb_push_syn(msgb); + + /* ACK-type frame + CRC. */ + msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); + + /* Payload CRC (ACK-type frames do not have a payload). */ + msgb_push_crc(msgb, msgb->ptr, 0); +} + +/** + * msgb_push_nak() - Push a SSH NAK frame to the buffer. + * @msgb: The message buffer + */ +static inline void msgb_push_nak(struct msgbuf *msgb) +{ + /* SYN. */ + msgb_push_syn(msgb); + + /* NAK-type frame + CRC. */ + msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); + + /* Payload CRC (ACK-type frames do not have a payload). */ + msgb_push_crc(msgb, msgb->ptr, 0); +} + +/** + * msgb_push_cmd() - Push a SSH command frame with payload to the buffer. + * @msgb: The message buffer. + * @seq: The sequence ID (SEQ) of the frame/packet. + * @rqid: The request ID (RQID) of the request contained in the frame. + * @rqst: The request to wrap in the frame. + */ +static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, + const struct ssam_request *rqst) +{ + const u8 type = SSH_FRAME_TYPE_DATA_SEQ; + u8 *cmd; + + /* SYN. */ + msgb_push_syn(msgb); + + /* Command frame + CRC. */ + msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq); + + /* Frame payload: Command struct + payload. */ + if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end)) + return; + + cmd = msgb->ptr; + + __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ + __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ + __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ + __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ + __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ + __msgb_push_u16(msgb, rqid); /* Request ID. */ + __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ + + /* Command payload. */ + msgb_push_buf(msgb, rqst->payload, rqst->length); + + /* CRC for command struct + payload. */ + msgb_push_crc(msgb, cmd, msgb->ptr - cmd); +} + +#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c new file mode 100644 index 000000000000..15d96eac6811 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -0,0 +1,2074 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH packet transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ssh_msgb.h" +#include "ssh_packet_layer.h" +#include "ssh_parser.h" + +#include "trace.h" + +/* + * To simplify reasoning about the code below, we define a few concepts. The + * system below is similar to a state-machine for packets, however, there are + * too many states to explicitly write them down. To (somewhat) manage the + * states and packets we rely on flags, reference counting, and some simple + * concepts. State transitions are triggered by actions. + * + * >> Actions << + * + * - submit + * - transmission start (process next item in queue) + * - transmission finished (guaranteed to never be parallel to transmission + * start) + * - ACK received + * - NAK received (this is equivalent to issuing re-submit for all pending + * packets) + * - timeout (this is equivalent to re-issuing a submit or canceling) + * - cancel (non-pending and pending) + * + * >> Data Structures, Packet Ownership, General Overview << + * + * The code below employs two main data structures: The packet queue, + * containing all packets scheduled for transmission, and the set of pending + * packets, containing all packets awaiting an ACK. + * + * Shared ownership of a packet is controlled via reference counting. Inside + * the transport system are a total of five packet owners: + * + * - the packet queue, + * - the pending set, + * - the transmitter thread, + * - the receiver thread (via ACKing), and + * - the timeout work item. + * + * Normal operation is as follows: The initial reference of the packet is + * obtained by submitting the packet and queuing it. The receiver thread takes + * packets from the queue. By doing this, it does not increment the refcount + * but takes over the reference (removing it from the queue). If the packet is + * sequenced (i.e. needs to be ACKed by the client), the transmitter thread + * sets-up the timeout and adds the packet to the pending set before starting + * to transmit it. As the timeout is handled by a reaper task, no additional + * reference for it is needed. After the transmit is done, the reference held + * by the transmitter thread is dropped. If the packet is unsequenced (i.e. + * does not need an ACK), the packet is completed by the transmitter thread + * before dropping that reference. + * + * On receival of an ACK, the receiver thread removes and obtains the + * reference to the packet from the pending set. The receiver thread will then + * complete the packet and drop its reference. + * + * On receival of a NAK, the receiver thread re-submits all currently pending + * packets. + * + * Packet timeouts are detected by the timeout reaper. This is a task, + * scheduled depending on the earliest packet timeout expiration date, + * checking all currently pending packets if their timeout has expired. If the + * timeout of a packet has expired, it is re-submitted and the number of tries + * of this packet is incremented. If this number reaches its limit, the packet + * will be completed with a failure. + * + * On transmission failure (such as repeated packet timeouts), the completion + * callback is immediately run by on thread on which the error was detected. + * + * To ensure that a packet eventually leaves the system it is marked as + * "locked" directly before it is going to be completed or when it is + * canceled. Marking a packet as "locked" has the effect that passing and + * creating new references of the packet is disallowed. This means that the + * packet cannot be added to the queue, the pending set, and the timeout, or + * be picked up by the transmitter thread or receiver thread. To remove a + * packet from the system it has to be marked as locked and subsequently all + * references from the data structures (queue, pending) have to be removed. + * References held by threads will eventually be dropped automatically as + * their execution progresses. + * + * Note that the packet completion callback is, in case of success and for a + * sequenced packet, guaranteed to run on the receiver thread, thus providing + * a way to reliably identify responses to the packet. The packet completion + * callback is only run once and it does not indicate that the packet has + * fully left the system (for this, one should rely on the release method, + * triggered when the reference count of the packet reaches zero). In case of + * re-submission (and with somewhat unlikely timing), it may be possible that + * the packet is being re-transmitted while the completion callback runs. + * Completion will occur both on success and internal error, as well as when + * the packet is canceled. + * + * >> Flags << + * + * Flags are used to indicate the state and progression of a packet. Some flags + * have stricter guarantees than other: + * + * - locked + * Indicates if the packet is locked. If the packet is locked, passing and/or + * creating additional references to the packet is forbidden. The packet thus + * may not be queued, dequeued, or removed or added to the pending set. Note + * that the packet state flags may still change (e.g. it may be marked as + * ACKed, transmitted, ...). + * + * - completed + * Indicates if the packet completion callback has been executed or is about + * to be executed. This flag is used to ensure that the packet completion + * callback is only run once. + * + * - queued + * Indicates if a packet is present in the submission queue or not. This flag + * must only be modified with the queue lock held, and must be coherent to the + * presence of the packet in the queue. + * + * - pending + * Indicates if a packet is present in the set of pending packets or not. + * This flag must only be modified with the pending lock held, and must be + * coherent to the presence of the packet in the pending set. + * + * - transmitting + * Indicates if the packet is currently transmitting. In case of + * re-transmissions, it is only safe to wait on the "transmitted" completion + * after this flag has been set. The completion will be set both in success + * and error case. + * + * - transmitted + * Indicates if the packet has been transmitted. This flag is not cleared by + * the system, thus it indicates the first transmission only. + * + * - acked + * Indicates if the packet has been acknowledged by the client. There are no + * other guarantees given. For example, the packet may still be canceled + * and/or the completion may be triggered an error even though this bit is + * set. Rely on the status provided to the completion callback instead. + * + * - canceled + * Indicates if the packet has been canceled from the outside. There are no + * other guarantees given. Specifically, the packet may be completed by + * another part of the system before the cancellation attempts to complete it. + * + * >> General Notes << + * + * - To avoid deadlocks, if both queue and pending locks are required, the + * pending lock must be acquired before the queue lock. + * + * - The packet priority must be accessed only while holding the queue lock. + * + * - The packet timestamp must be accessed only while holding the pending + * lock. + */ + +/* + * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. + * + * Maximum number of transmission attempts per sequenced packet in case of + * time-outs. Must be smaller than 16. If the packet times out after this + * amount of tries, the packet will be completed with %-ETIMEDOUT as status + * code. + */ +#define SSH_PTL_MAX_PACKET_TRIES 3 + +/* + * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. + * + * Timeout in jiffies for packet transmission via the underlying serial + * device. If transmitting the packet takes longer than this timeout, the + * packet will be completed with -ETIMEDOUT. It will not be re-submitted. + */ +#define SSH_PTL_TX_TIMEOUT HZ + +/* + * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. + * + * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this + * time-frame after starting transmission, the packet will be re-submitted. + */ +#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) + +/* + * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. + * + * Time-resolution for timeouts. Should be larger than one jiffy to avoid + * direct re-scheduling of reaper work_struct. + */ +#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) + +/* + * SSH_PTL_MAX_PENDING - Maximum number of pending packets. + * + * Maximum number of sequenced packets concurrently waiting for an ACK. + * Packets marked as blocking will not be transmitted while this limit is + * reached. + */ +#define SSH_PTL_MAX_PENDING 1 + +/* + * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. + */ +#define SSH_PTL_RX_BUF_LEN 4096 + +/* + * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. + */ +#define SSH_PTL_RX_FIFO_LEN 4096 + +#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. + * + * Useful to test detection and handling of automated re-transmits by the EC. + * Specifically of packets that the EC considers not-ACKed but the driver + * already considers ACKed (due to dropped ACK). In this case, the EC + * re-transmits the packet-to-be-ACKed and the driver should detect it as + * duplicate/already handled. Note that the driver should still send an ACK + * for the re-transmitted packet. + */ +static noinline bool ssh_ptl_should_drop_ack_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); + +/** + * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. + * + * Useful to test/force automated (timeout-based) re-transmit by the EC. + * Specifically, packets that have not reached the driver completely/with valid + * checksums. Only useful in combination with receival of (injected) bad data. + */ +static noinline bool ssh_ptl_should_drop_nak_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); + +/** + * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced + * data packet. + * + * Useful to test re-transmit timeout of the driver. If the data packet has not + * been ACKed after a certain time, the driver should re-transmit the packet up + * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. + */ +static noinline bool ssh_ptl_should_drop_dsq_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); + +/** + * ssh_ptl_should_fail_write() - Error injection hook to make + * serdev_device_write() fail. + * + * Hook to simulate errors in serdev_device_write when transmitting packets. + */ +static noinline int ssh_ptl_should_fail_write(void) +{ + return 0; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); + +/** + * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid + * data being sent to the EC. + * + * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. + * Causes the packet data to be actively corrupted by overwriting it with + * pre-defined values, such that it becomes invalid, causing the EC to respond + * with a NAK packet. Useful to test handling of NAK packets received by the + * driver. + */ +static noinline bool ssh_ptl_should_corrupt_tx_data(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); + +/** + * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid + * data being sent by the EC. + * + * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and + * test handling thereof in the driver. + */ +static noinline bool ssh_ptl_should_corrupt_rx_syn(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); + +/** + * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid + * data being sent by the EC. + * + * Hook to simulate invalid data/checksum of the message frame and test handling + * thereof in the driver. + */ +static noinline bool ssh_ptl_should_corrupt_rx_data(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); + +static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_ack_packet())) + return false; + + trace_ssam_ei_tx_drop_ack_packet(packet); + ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", + packet); + + return true; +} + +static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_nak_packet())) + return false; + + trace_ssam_ei_tx_drop_nak_packet(packet); + ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", + packet); + + return true; +} + +static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_dsq_packet())) + return false; + + trace_ssam_ei_tx_drop_dsq_packet(packet); + ptl_info(packet->ptl, + "packet error injection: dropping sequenced data packet %p\n", + packet); + + return true; +} + +static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ + /* Ignore packets that don't carry any data (i.e. flush). */ + if (!packet->data.ptr || !packet->data.len) + return false; + + switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { + case SSH_FRAME_TYPE_ACK: + return __ssh_ptl_should_drop_ack_packet(packet); + + case SSH_FRAME_TYPE_NAK: + return __ssh_ptl_should_drop_nak_packet(packet); + + case SSH_FRAME_TYPE_DATA_SEQ: + return __ssh_ptl_should_drop_dsq_packet(packet); + + default: + return false; + } +} + +static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, + const unsigned char *buf, size_t count) +{ + int status; + + status = ssh_ptl_should_fail_write(); + if (unlikely(status)) { + trace_ssam_ei_tx_fail_write(packet, status); + ptl_info(packet->ptl, + "packet error injection: simulating transmit error %d, packet %p\n", + status, packet); + + return status; + } + + return serdev_device_write_buf(ptl->serdev, buf, count); +} + +static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) +{ + /* Ignore packets that don't carry any data (i.e. flush). */ + if (!packet->data.ptr || !packet->data.len) + return; + + /* Only allow sequenced data packets to be modified. */ + if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) + return; + + if (likely(!ssh_ptl_should_corrupt_tx_data())) + return; + + trace_ssam_ei_tx_corrupt_data(packet); + ptl_info(packet->ptl, + "packet error injection: simulating invalid transmit data on packet %p\n", + packet); + + /* + * NB: The value 0xb3 has been chosen more or less randomly so that it + * doesn't have any (major) overlap with the SYN bytes (aa 55) and is + * non-trivial (i.e. non-zero, non-0xff). + */ + memset(packet->data.ptr, 0xb3, packet->data.len); +} + +static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, + struct ssam_span *data) +{ + struct ssam_span frame; + + /* Check if there actually is something to corrupt. */ + if (!sshp_find_syn(data, &frame)) + return; + + if (likely(!ssh_ptl_should_corrupt_rx_syn())) + return; + + trace_ssam_ei_rx_corrupt_syn(data->len); + + data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ +} + +static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, + struct ssam_span *frame) +{ + size_t payload_len, message_len; + struct ssh_frame *sshf; + + /* Ignore incomplete messages, will get handled once it's complete. */ + if (frame->len < SSH_MESSAGE_LENGTH(0)) + return; + + /* Ignore incomplete messages, part 2. */ + payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); + message_len = SSH_MESSAGE_LENGTH(payload_len); + if (frame->len < message_len) + return; + + if (likely(!ssh_ptl_should_corrupt_rx_data())) + return; + + sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; + trace_ssam_ei_rx_corrupt_data(sshf); + + /* + * Flip bits in first byte of payload checksum. This is basically + * equivalent to a payload/frame data error without us having to worry + * about (the, arguably pretty small, probability of) accidental + * checksum collisions. + */ + frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; +} + +#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + +static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ + return false; +} + +static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, + struct ssh_packet *packet, + const unsigned char *buf, + size_t count) +{ + return serdev_device_write_buf(ptl->serdev, buf, count); +} + +static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) +{ +} + +static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, + struct ssam_span *data) +{ +} + +static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, + struct ssam_span *frame) +{ +} + +#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + +static void __ssh_ptl_packet_release(struct kref *kref) +{ + struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); + + trace_ssam_packet_release(p); + + ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); + p->ops->release(p); +} + +/** + * ssh_packet_get() - Increment reference count of packet. + * @packet: The packet to increment the reference count of. + * + * Increments the reference count of the given packet. See ssh_packet_put() + * for the counter-part of this function. + * + * Return: Returns the packet provided as input. + */ +struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) +{ + if (packet) + kref_get(&packet->refcnt); + return packet; +} +EXPORT_SYMBOL_GPL(ssh_packet_get); + +/** + * ssh_packet_put() - Decrement reference count of packet. + * @packet: The packet to decrement the reference count of. + * + * If the reference count reaches zero, the ``release`` callback specified in + * the packet's &struct ssh_packet_ops, i.e. ``packet->ops->release``, will be + * called. + * + * See ssh_packet_get() for the counter-part of this function. + */ +void ssh_packet_put(struct ssh_packet *packet) +{ + if (packet) + kref_put(&packet->refcnt, __ssh_ptl_packet_release); +} +EXPORT_SYMBOL_GPL(ssh_packet_put); + +static u8 ssh_packet_get_seq(struct ssh_packet *packet) +{ + return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; +} + +/** + * ssh_packet_init() - Initialize SSH packet. + * @packet: The packet to initialize. + * @type: Type-flags of the packet. + * @priority: Priority of the packet. See SSH_PACKET_PRIORITY() for details. + * @ops: Packet operations. + * + * Initializes the given SSH packet. Sets the transmission buffer pointer to + * %NULL and the transmission buffer length to zero. For data-type packets, + * this buffer has to be set separately via ssh_packet_set_data() before + * submission, and must contain a valid SSH message, i.e. frame with optional + * payload of any type. + */ +void ssh_packet_init(struct ssh_packet *packet, unsigned long type, + u8 priority, const struct ssh_packet_ops *ops) +{ + kref_init(&packet->refcnt); + + packet->ptl = NULL; + INIT_LIST_HEAD(&packet->queue_node); + INIT_LIST_HEAD(&packet->pending_node); + + packet->state = type & SSH_PACKET_FLAGS_TY_MASK; + packet->priority = priority; + packet->timestamp = KTIME_MAX; + + packet->data.ptr = NULL; + packet->data.len = 0; + + packet->ops = ops; +} + +static struct kmem_cache *ssh_ctrl_packet_cache; + +/** + * ssh_ctrl_packet_cache_init() - Initialize the control packet cache. + */ +int ssh_ctrl_packet_cache_init(void) +{ + const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; + const unsigned int align = __alignof__(struct ssh_packet); + struct kmem_cache *cache; + + cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); + if (!cache) + return -ENOMEM; + + ssh_ctrl_packet_cache = cache; + return 0; +} + +/** + * ssh_ctrl_packet_cache_destroy() - Deinitialize the control packet cache. + */ +void ssh_ctrl_packet_cache_destroy(void) +{ + kmem_cache_destroy(ssh_ctrl_packet_cache); + ssh_ctrl_packet_cache = NULL; +} + +/** + * ssh_ctrl_packet_alloc() - Allocate packet from control packet cache. + * @packet: Where the pointer to the newly allocated packet should be stored. + * @buffer: The buffer corresponding to this packet. + * @flags: Flags used for allocation. + * + * Allocates a packet and corresponding transport buffer from the control + * packet cache. Sets the packet's buffer reference to the allocated buffer. + * The packet must be freed via ssh_ctrl_packet_free(), which will also free + * the corresponding buffer. The corresponding buffer must not be freed + * separately. Intended to be used with %ssh_ptl_ctrl_packet_ops as packet + * operations. + * + * Return: Returns zero on success, %-ENOMEM if the allocation failed. + */ +static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, + struct ssam_span *buffer, gfp_t flags) +{ + *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); + if (!*packet) + return -ENOMEM; + + buffer->ptr = (u8 *)(*packet + 1); + buffer->len = SSH_MSG_LEN_CTRL; + + trace_ssam_ctrl_packet_alloc(*packet, buffer->len); + return 0; +} + +/** + * ssh_ctrl_packet_free() - Free packet allocated from control packet cache. + * @p: The packet to free. + */ +static void ssh_ctrl_packet_free(struct ssh_packet *p) +{ + trace_ssam_ctrl_packet_free(p); + kmem_cache_free(ssh_ctrl_packet_cache, p); +} + +static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { + .complete = NULL, + .release = ssh_ctrl_packet_free, +}; + +static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, + ktime_t expires) +{ + unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); + ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); + + spin_lock(&ptl->rtx_timeout.lock); + + /* Re-adjust / schedule reaper only if it is above resolution delta. */ + if (ktime_before(aexp, ptl->rtx_timeout.expires)) { + ptl->rtx_timeout.expires = expires; + mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); + } + + spin_unlock(&ptl->rtx_timeout.lock); +} + +/* Must be called with queue lock held. */ +static void ssh_packet_next_try(struct ssh_packet *p) +{ + u8 base = ssh_packet_priority_get_base(p->priority); + u8 try = ssh_packet_priority_get_try(p->priority); + + lockdep_assert_held(&p->ptl->queue.lock); + + /* + * Ensure that we write the priority in one go via WRITE_ONCE() so we + * can access it via READ_ONCE() for tracing. Note that other access + * is guarded by the queue lock, so no need to use READ_ONCE() there. + */ + WRITE_ONCE(p->priority, __SSH_PACKET_PRIORITY(base, try + 1)); +} + +/* Must be called with queue lock held. */ +static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) +{ + struct list_head *head; + struct ssh_packet *q; + + lockdep_assert_held(&p->ptl->queue.lock); + + /* + * We generally assume that there are less control (ACK/NAK) packets + * and re-submitted data packets as there are normal data packets (at + * least in situations in which many packets are queued; if there + * aren't many packets queued the decision on how to iterate should be + * basically irrelevant; the number of control/data packets is more or + * less limited via the maximum number of pending packets). Thus, when + * inserting a control or re-submitted data packet, (determined by + * their priority), we search from front to back. Normal data packets + * are, usually queued directly at the tail of the queue, so for those + * search from back to front. + */ + + if (p->priority > SSH_PACKET_PRIORITY(DATA, 0)) { + list_for_each(head, &p->ptl->queue.head) { + q = list_entry(head, struct ssh_packet, queue_node); + + if (q->priority < p->priority) + break; + } + } else { + list_for_each_prev(head, &p->ptl->queue.head) { + q = list_entry(head, struct ssh_packet, queue_node); + + if (q->priority >= p->priority) { + head = head->next; + break; + } + } + } + + return head; +} + +/* Must be called with queue lock held. */ +static int __ssh_ptl_queue_push(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + struct list_head *head; + + lockdep_assert_held(&ptl->queue.lock); + + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return -ESHUTDOWN; + + /* Avoid further transitions when canceling/completing. */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) + return -EINVAL; + + /* If this packet has already been queued, do not add it. */ + if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) + return -EALREADY; + + head = __ssh_ptl_queue_find_entrypoint(packet); + + list_add_tail(&ssh_packet_get(packet)->queue_node, head); + return 0; +} + +static int ssh_ptl_queue_push(struct ssh_packet *packet) +{ + int status; + + spin_lock(&packet->ptl->queue.lock); + status = __ssh_ptl_queue_push(packet); + spin_unlock(&packet->ptl->queue.lock); + + return status; +} + +static void ssh_ptl_queue_remove(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + spin_lock(&ptl->queue.lock); + + if (!test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { + spin_unlock(&ptl->queue.lock); + return; + } + + list_del(&packet->queue_node); + + spin_unlock(&ptl->queue.lock); + ssh_packet_put(packet); +} + +static void ssh_ptl_pending_push(struct ssh_packet *p) +{ + struct ssh_ptl *ptl = p->ptl; + const ktime_t timestamp = ktime_get_coarse_boottime(); + const ktime_t timeout = ptl->rtx_timeout.timeout; + + /* + * Note: We can get the time for the timestamp before acquiring the + * lock as this is the only place we're setting it and this function + * is called only from the transmitter thread. Thus it is not possible + * to overwrite the timestamp with an outdated value below. + */ + + spin_lock(&ptl->pending.lock); + + /* If we are canceling/completing this packet, do not add it. */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) { + spin_unlock(&ptl->pending.lock); + return; + } + + /* + * On re-submission, the packet has already been added the pending + * set. We still need to update the timestamp as the packet timeout is + * reset for each (re-)submission. + */ + p->timestamp = timestamp; + + /* In case it is already pending (e.g. re-submission), do not add it. */ + if (!test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &p->state)) { + atomic_inc(&ptl->pending.count); + list_add_tail(&ssh_packet_get(p)->pending_node, &ptl->pending.head); + } + + spin_unlock(&ptl->pending.lock); + + /* Arm/update timeout reaper. */ + ssh_ptl_timeout_reaper_mod(ptl, timestamp, timestamp + timeout); +} + +static void ssh_ptl_pending_remove(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + spin_lock(&ptl->pending.lock); + + if (!test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { + spin_unlock(&ptl->pending.lock); + return; + } + + list_del(&packet->pending_node); + atomic_dec(&ptl->pending.count); + + spin_unlock(&ptl->pending.lock); + + ssh_packet_put(packet); +} + +/* Warning: Does not check/set "completed" bit. */ +static void __ssh_ptl_complete(struct ssh_packet *p, int status) +{ + struct ssh_ptl *ptl = READ_ONCE(p->ptl); + + trace_ssam_packet_complete(p, status); + ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); + + if (p->ops->complete) + p->ops->complete(p, status); +} + +static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) +{ + /* + * A call to this function should in general be preceded by + * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the + * packet to the structures it's going to be removed from. + * + * The set_bit call does not need explicit memory barriers as the + * implicit barrier of the test_and_set_bit() call below ensure that the + * flag is visible before we actually attempt to remove the packet. + */ + + if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + return; + + ssh_ptl_queue_remove(p); + ssh_ptl_pending_remove(p); + + __ssh_ptl_complete(p, status); +} + +static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) + return !atomic_read(&ptl->pending.count); + + /* We can always process non-blocking packets. */ + if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) + return true; + + /* If we are already waiting for this packet, send it again. */ + if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) + return true; + + /* Otherwise: Check if we have the capacity to send. */ + return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; +} + +static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) +{ + struct ssh_packet *packet = ERR_PTR(-ENOENT); + struct ssh_packet *p, *n; + + spin_lock(&ptl->queue.lock); + list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { + /* + * If we are canceling or completing this packet, ignore it. + * It's going to be removed from this queue shortly. + */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + continue; + + /* + * Packets should be ordered non-blocking/to-be-resent first. + * If we cannot process this packet, assume that we can't + * process any following packet either and abort. + */ + if (!ssh_ptl_tx_can_process(p)) { + packet = ERR_PTR(-EBUSY); + break; + } + + /* + * We are allowed to change the state now. Remove it from the + * queue and mark it as being transmitted. + */ + + list_del(&p->queue_node); + + set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); + + /* + * Update number of tries. This directly influences the + * priority in case the packet is re-submitted (e.g. via + * timeout/NAK). Note that all reads and writes to the + * priority after the first submission are guarded by the + * queue lock. + */ + ssh_packet_next_try(p); + + packet = p; + break; + } + spin_unlock(&ptl->queue.lock); + + return packet; +} + +static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) +{ + struct ssh_packet *p; + + p = ssh_ptl_tx_pop(ptl); + if (IS_ERR(p)) + return p; + + if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { + ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); + ssh_ptl_pending_push(p); + } else { + ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); + } + + return p; +} + +static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); + + /* Transition state to "transmitted". */ + set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); + + /* If the packet is unsequenced, we're done: Lock and complete. */ + if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); + ssh_ptl_remove_and_complete(packet, 0); + } + + /* + * Notify that a packet transmission has finished. In general we're only + * waiting for one packet (if any), so wake_up_all should be fine. + */ + wake_up_all(&ptl->tx.packet_wq); +} + +static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) +{ + /* Transmission failure: Lock the packet and try to complete it. */ + set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); + + ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); + ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); + + ssh_ptl_remove_and_complete(packet, status); + + /* + * Notify that a packet transmission has finished. In general we're only + * waiting for one packet (if any), so wake_up_all should be fine. + */ + wake_up_all(&packet->ptl->tx.packet_wq); +} + +static long ssh_ptl_tx_wait_packet(struct ssh_ptl *ptl) +{ + int status; + + status = wait_for_completion_interruptible(&ptl->tx.thread_cplt_pkt); + reinit_completion(&ptl->tx.thread_cplt_pkt); + + /* + * Ensure completion is cleared before continuing to avoid lost update + * problems. + */ + smp_mb__after_atomic(); + + return status; +} + +static long ssh_ptl_tx_wait_transfer(struct ssh_ptl *ptl, long timeout) +{ + long status; + + status = wait_for_completion_interruptible_timeout(&ptl->tx.thread_cplt_tx, + timeout); + reinit_completion(&ptl->tx.thread_cplt_tx); + + /* + * Ensure completion is cleared before continuing to avoid lost update + * problems. + */ + smp_mb__after_atomic(); + + return status; +} + +static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) +{ + long timeout = SSH_PTL_TX_TIMEOUT; + size_t offset = 0; + + /* Note: Flush-packets don't have any data. */ + if (unlikely(!packet->data.ptr)) + return 0; + + /* Error injection: drop packet to simulate transmission problem. */ + if (ssh_ptl_should_drop_packet(packet)) + return 0; + + /* Error injection: simulate invalid packet data. */ + ssh_ptl_tx_inject_invalid_data(packet); + + ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); + print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, + packet->data.ptr, packet->data.len, false); + + do { + ssize_t status, len; + u8 *buf; + + buf = packet->data.ptr + offset; + len = packet->data.len - offset; + + status = ssh_ptl_write_buf(ptl, packet, buf, len); + if (status < 0) + return status; + + if (status == len) + return 0; + + offset += status; + + timeout = ssh_ptl_tx_wait_transfer(ptl, timeout); + if (kthread_should_stop() || !atomic_read(&ptl->tx.running)) + return -ESHUTDOWN; + + if (timeout < 0) + return -EINTR; + + if (timeout == 0) + return -ETIMEDOUT; + } while (true); +} + +static int ssh_ptl_tx_threadfn(void *data) +{ + struct ssh_ptl *ptl = data; + + while (!kthread_should_stop() && atomic_read(&ptl->tx.running)) { + struct ssh_packet *packet; + int status; + + /* Try to get the next packet. */ + packet = ssh_ptl_tx_next(ptl); + + /* If no packet can be processed, we are done. */ + if (IS_ERR(packet)) { + ssh_ptl_tx_wait_packet(ptl); + continue; + } + + /* Transfer and complete packet. */ + status = ssh_ptl_tx_packet(ptl, packet); + if (status) + ssh_ptl_tx_compl_error(packet, status); + else + ssh_ptl_tx_compl_success(packet); + + ssh_packet_put(packet); + } + + return 0; +} + +/** + * ssh_ptl_tx_wakeup_packet() - Wake up packet transmitter thread for new + * packet. + * @ptl: The packet transport layer. + * + * Wakes up the packet transmitter thread, notifying it that a new packet has + * arrived and is ready for transfer. If the packet transport layer has been + * shut down, calls to this function will be ignored. + */ +static void ssh_ptl_tx_wakeup_packet(struct ssh_ptl *ptl) +{ + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return; + + complete(&ptl->tx.thread_cplt_pkt); +} + +/** + * ssh_ptl_tx_start() - Start packet transmitter thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_tx_start(struct ssh_ptl *ptl) +{ + atomic_set_release(&ptl->tx.running, 1); + + ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "ssam_serial_hub-tx"); + if (IS_ERR(ptl->tx.thread)) + return PTR_ERR(ptl->tx.thread); + + return 0; +} + +/** + * ssh_ptl_tx_stop() - Stop packet transmitter thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_tx_stop(struct ssh_ptl *ptl) +{ + int status = 0; + + if (!IS_ERR_OR_NULL(ptl->tx.thread)) { + /* Tell thread to stop. */ + atomic_set_release(&ptl->tx.running, 0); + + /* + * Wake up thread in case it is paused. Do not use wakeup + * helpers as this may be called when the shutdown bit has + * already been set. + */ + complete(&ptl->tx.thread_cplt_pkt); + complete(&ptl->tx.thread_cplt_tx); + + /* Finally, wait for thread to stop. */ + status = kthread_stop(ptl->tx.thread); + ptl->tx.thread = NULL; + } + + return status; +} + +static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) +{ + struct ssh_packet *packet = ERR_PTR(-ENOENT); + struct ssh_packet *p, *n; + + spin_lock(&ptl->pending.lock); + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + /* + * We generally expect packets to be in order, so first packet + * to be added to pending is first to be sent, is first to be + * ACKed. + */ + if (unlikely(ssh_packet_get_seq(p) != seq_id)) + continue; + + /* + * In case we receive an ACK while handling a transmission + * error completion. The packet will be removed shortly. + */ + if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { + packet = ERR_PTR(-EPERM); + break; + } + + /* + * Mark the packet as ACKed and remove it from pending by + * removing its node and decrementing the pending counter. + */ + set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + atomic_dec(&ptl->pending.count); + list_del(&p->pending_node); + packet = p; + + break; + } + spin_unlock(&ptl->pending.lock); + + return packet; +} + +static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) +{ + wait_event(packet->ptl->tx.packet_wq, + test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) || + test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); +} + +static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) +{ + struct ssh_packet *p; + + p = ssh_ptl_ack_pop(ptl, seq); + if (IS_ERR(p)) { + if (PTR_ERR(p) == -ENOENT) { + /* + * The packet has not been found in the set of pending + * packets. + */ + ptl_warn(ptl, "ptl: received ACK for non-pending packet\n"); + } else { + /* + * The packet is pending, but we are not allowed to take + * it because it has been locked. + */ + WARN_ON(PTR_ERR(p) != -EPERM); + } + return; + } + + ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); + + /* + * It is possible that the packet has been transmitted, but the state + * has not been updated from "transmitting" to "transmitted" yet. + * In that case, we need to wait for this transition to occur in order + * to determine between success or failure. + * + * On transmission failure, the packet will be locked after this call. + * On success, the transmitted bit will be set. + */ + ssh_ptl_wait_until_transmitted(p); + + /* + * The packet will already be locked in case of a transmission error or + * cancellation. Let the transmitter or cancellation issuer complete the + * packet. + */ + if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { + if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) + ptl_err(ptl, "ptl: received ACK before packet had been fully transmitted\n"); + + ssh_packet_put(p); + return; + } + + ssh_ptl_remove_and_complete(p, 0); + ssh_packet_put(p); + + if (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) + ssh_ptl_tx_wakeup_packet(ptl); +} + +/** + * ssh_ptl_submit() - Submit a packet to the transport layer. + * @ptl: The packet transport layer to submit the packet to. + * @p: The packet to submit. + * + * Submits a new packet to the transport layer, queuing it to be sent. This + * function should not be used for re-submission. + * + * Return: Returns zero on success, %-EINVAL if a packet field is invalid or + * the packet has been canceled prior to submission, %-EALREADY if the packet + * has already been submitted, or %-ESHUTDOWN if the packet transport layer + * has been shut down. + */ +int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) +{ + struct ssh_ptl *ptl_old; + int status; + + trace_ssam_packet_submit(p); + + /* Validate packet fields. */ + if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { + if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) + return -EINVAL; + } else if (!p->data.ptr) { + return -EINVAL; + } + + /* + * The ptl reference only gets set on or before the first submission. + * After the first submission, it has to be read-only. + * + * Note that ptl may already be set from upper-layer request + * submission, thus we cannot expect it to be NULL. + */ + ptl_old = READ_ONCE(p->ptl); + if (!ptl_old) + WRITE_ONCE(p->ptl, ptl); + else if (WARN_ON(ptl_old != ptl)) + return -EALREADY; /* Submitted on different PTL. */ + + status = ssh_ptl_queue_push(p); + if (status) + return status; + + if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state) || + (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING)) + ssh_ptl_tx_wakeup_packet(ptl); + + return 0; +} + +/* + * __ssh_ptl_resubmit() - Re-submit a packet to the transport layer. + * @packet: The packet to re-submit. + * + * Re-submits the given packet: Checks if it can be re-submitted and queues it + * if it can, resetting the packet timestamp in the process. Must be called + * with the pending lock held. + * + * Return: Returns %-ECANCELED if the packet has exceeded its number of tries, + * %-EINVAL if the packet has been locked, %-EALREADY if the packet is already + * on the queue, and %-ESHUTDOWN if the transmission layer has been shut down. + */ +static int __ssh_ptl_resubmit(struct ssh_packet *packet) +{ + int status; + u8 try; + + lockdep_assert_held(&packet->ptl->pending.lock); + + trace_ssam_packet_resubmit(packet); + + spin_lock(&packet->ptl->queue.lock); + + /* Check if the packet is out of tries. */ + try = ssh_packet_priority_get_try(packet->priority); + if (try >= SSH_PTL_MAX_PACKET_TRIES) { + spin_unlock(&packet->ptl->queue.lock); + return -ECANCELED; + } + + status = __ssh_ptl_queue_push(packet); + if (status) { + /* + * An error here indicates that the packet has either already + * been queued, been locked, or the transport layer is being + * shut down. In all cases: Ignore the error. + */ + spin_unlock(&packet->ptl->queue.lock); + return status; + } + + packet->timestamp = KTIME_MAX; + + spin_unlock(&packet->ptl->queue.lock); + return 0; +} + +static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) +{ + struct ssh_packet *p; + bool resub = false; + + /* + * Note: We deliberately do not remove/attempt to cancel and complete + * packets that are out of tires in this function. The packet will be + * eventually canceled and completed by the timeout. Removing the packet + * here could lead to overly eager cancellation if the packet has not + * been re-transmitted yet but the tries-counter already updated (i.e + * ssh_ptl_tx_next() removed the packet from the queue and updated the + * counter, but re-transmission for the last try has not actually + * started yet). + */ + + spin_lock(&ptl->pending.lock); + + /* Re-queue all pending packets. */ + list_for_each_entry(p, &ptl->pending.head, pending_node) { + /* + * Re-submission fails if the packet is out of tries, has been + * locked, is already queued, or the layer is being shut down. + * No need to re-schedule tx-thread in those cases. + */ + if (!__ssh_ptl_resubmit(p)) + resub = true; + } + + spin_unlock(&ptl->pending.lock); + + if (resub) + ssh_ptl_tx_wakeup_packet(ptl); +} + +/** + * ssh_ptl_cancel() - Cancel a packet. + * @p: The packet to cancel. + * + * Cancels a packet. There are no guarantees on when completion and release + * callbacks will be called. This may occur during execution of this function + * or may occur at any point later. + * + * Note that it is not guaranteed that the packet will actually be canceled if + * the packet is concurrently completed by another process. The only guarantee + * of this function is that the packet will be completed (with success, + * failure, or cancellation) and released from the transport layer in a + * reasonable time-frame. + * + * May be called before the packet has been submitted, in which case any later + * packet submission fails. + */ +void ssh_ptl_cancel(struct ssh_packet *p) +{ + if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) + return; + + trace_ssam_packet_cancel(p); + + /* + * Lock packet and commit with memory barrier. If this packet has + * already been locked, it's going to be removed and completed by + * another party, which should have precedence. + */ + if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + return; + + /* + * By marking the packet as locked and employing the implicit memory + * barrier of test_and_set_bit, we have guaranteed that, at this point, + * the packet cannot be added to the queue any more. + * + * In case the packet has never been submitted, packet->ptl is NULL. If + * the packet is currently being submitted, packet->ptl may be NULL or + * non-NULL. Due marking the packet as locked above and committing with + * the memory barrier, we have guaranteed that, if packet->ptl is NULL, + * the packet will never be added to the queue. If packet->ptl is + * non-NULL, we don't have any guarantees. + */ + + if (READ_ONCE(p->ptl)) { + ssh_ptl_remove_and_complete(p, -ECANCELED); + + if (atomic_read(&p->ptl->pending.count) < SSH_PTL_MAX_PENDING) + ssh_ptl_tx_wakeup_packet(p->ptl); + + } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { + __ssh_ptl_complete(p, -ECANCELED); + } +} + +/* Must be called with pending lock held */ +static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) +{ + lockdep_assert_held(&p->ptl->pending.lock); + + if (p->timestamp != KTIME_MAX) + return ktime_add(p->timestamp, timeout); + else + return KTIME_MAX; +} + +static void ssh_ptl_timeout_reap(struct work_struct *work) +{ + struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); + struct ssh_packet *p, *n; + LIST_HEAD(claimed); + ktime_t now = ktime_get_coarse_boottime(); + ktime_t timeout = ptl->rtx_timeout.timeout; + ktime_t next = KTIME_MAX; + bool resub = false; + int status; + + trace_ssam_ptl_timeout_reap(atomic_read(&ptl->pending.count)); + + /* + * Mark reaper as "not pending". This is done before checking any + * packets to avoid lost-update type problems. + */ + spin_lock(&ptl->rtx_timeout.lock); + ptl->rtx_timeout.expires = KTIME_MAX; + spin_unlock(&ptl->rtx_timeout.lock); + + spin_lock(&ptl->pending.lock); + + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + ktime_t expires = ssh_packet_get_expiration(p, timeout); + + /* + * Check if the timeout hasn't expired yet. Find out next + * expiration date to be handled after this run. + */ + if (ktime_after(expires, now)) { + next = ktime_before(expires, next) ? expires : next; + continue; + } + + trace_ssam_packet_timeout(p); + + status = __ssh_ptl_resubmit(p); + + /* + * Re-submission fails if the packet is out of tries, has been + * locked, is already queued, or the layer is being shut down. + * No need to re-schedule tx-thread in those cases. + */ + if (!status) + resub = true; + + /* Go to next packet if this packet is not out of tries. */ + if (status != -ECANCELED) + continue; + + /* No more tries left: Cancel the packet. */ + + /* + * If someone else has locked the packet already, don't use it + * and let the other party complete it. + */ + if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + continue; + + /* + * We have now marked the packet as locked. Thus it cannot be + * added to the pending list again after we've removed it here. + * We can therefore re-use the pending_node of this packet + * temporarily. + */ + + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + atomic_dec(&ptl->pending.count); + list_del(&p->pending_node); + + list_add_tail(&p->pending_node, &claimed); + } + + spin_unlock(&ptl->pending.lock); + + /* Cancel and complete the packet. */ + list_for_each_entry_safe(p, n, &claimed, pending_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { + ssh_ptl_queue_remove(p); + __ssh_ptl_complete(p, -ETIMEDOUT); + } + + /* + * Drop the reference we've obtained by removing it from + * the pending set. + */ + list_del(&p->pending_node); + ssh_packet_put(p); + } + + /* Ensure that reaper doesn't run again immediately. */ + next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); + if (next != KTIME_MAX) + ssh_ptl_timeout_reaper_mod(ptl, now, next); + + if (resub) + ssh_ptl_tx_wakeup_packet(ptl); +} + +static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) +{ + int i; + + /* + * Check if SEQ has been seen recently (i.e. packet was + * re-transmitted and we should ignore it). + */ + for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { + if (likely(ptl->rx.blocked.seqs[i] != seq)) + continue; + + ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); + return true; + } + + /* Update list of blocked sequence IDs. */ + ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; + ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) + % ARRAY_SIZE(ptl->rx.blocked.seqs); + + return false; +} + +static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, + const struct ssh_frame *frame, + const struct ssam_span *payload) +{ + if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) + return; + + ptl->ops.data_received(ptl, payload); +} + +static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) +{ + struct ssh_packet *packet; + struct ssam_span buf; + struct msgbuf msgb; + int status; + + status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); + if (status) { + ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); + return; + } + + ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(ACK, 0), + &ssh_ptl_ctrl_packet_ops); + + msgb_init(&msgb, buf.ptr, buf.len); + msgb_push_ack(&msgb, seq); + ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); + + ssh_ptl_submit(ptl, packet); + ssh_packet_put(packet); +} + +static void ssh_ptl_send_nak(struct ssh_ptl *ptl) +{ + struct ssh_packet *packet; + struct ssam_span buf; + struct msgbuf msgb; + int status; + + status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); + if (status) { + ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); + return; + } + + ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(NAK, 0), + &ssh_ptl_ctrl_packet_ops); + + msgb_init(&msgb, buf.ptr, buf.len); + msgb_push_nak(&msgb); + ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); + + ssh_ptl_submit(ptl, packet); + ssh_packet_put(packet); +} + +static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) +{ + struct ssh_frame *frame; + struct ssam_span payload; + struct ssam_span aligned; + bool syn_found; + int status; + + /* Error injection: Modify data to simulate corrupt SYN bytes. */ + ssh_ptl_rx_inject_invalid_syn(ptl, source); + + /* Find SYN. */ + syn_found = sshp_find_syn(source, &aligned); + + if (unlikely(aligned.ptr != source->ptr)) { + /* + * We expect aligned.ptr == source->ptr. If this is not the + * case, then aligned.ptr > source->ptr and we've encountered + * some unexpected data where we'd expect the start of a new + * message (i.e. the SYN sequence). + * + * This can happen when a CRC check for the previous message + * failed and we start actively searching for the next one + * (via the call to sshp_find_syn() above), or the first bytes + * of a message got dropped or corrupted. + * + * In any case, we issue a warning, send a NAK to the EC to + * request re-transmission of any data we haven't acknowledged + * yet, and finally, skip everything up to the next SYN + * sequence. + */ + + ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); + + /* + * Notes: + * - This might send multiple NAKs in case the communication + * starts with an invalid SYN and is broken down into multiple + * pieces. This should generally be handled fine, we just + * might receive duplicate data in this case, which is + * detected when handling data frames. + * - This path will also be executed on invalid CRCs: When an + * invalid CRC is encountered, the code below will skip data + * until directly after the SYN. This causes the search for + * the next SYN, which is generally not placed directly after + * the last one. + * + * Open question: Should we send this in case of invalid + * payload CRCs if the frame-type is non-sequential (current + * implementation) or should we drop that frame without + * telling the EC? + */ + ssh_ptl_send_nak(ptl); + } + + if (unlikely(!syn_found)) + return aligned.ptr - source->ptr; + + /* Error injection: Modify data to simulate corruption. */ + ssh_ptl_rx_inject_invalid_data(ptl, &aligned); + + /* Parse and validate frame. */ + status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, + SSH_PTL_RX_BUF_LEN); + if (status) /* Invalid frame: skip to next SYN. */ + return aligned.ptr - source->ptr + sizeof(u16); + if (!frame) /* Not enough data. */ + return aligned.ptr - source->ptr; + + trace_ssam_rx_frame_received(frame); + + switch (frame->type) { + case SSH_FRAME_TYPE_ACK: + ssh_ptl_acknowledge(ptl, frame->seq); + break; + + case SSH_FRAME_TYPE_NAK: + ssh_ptl_resubmit_pending(ptl); + break; + + case SSH_FRAME_TYPE_DATA_SEQ: + ssh_ptl_send_ack(ptl, frame->seq); + fallthrough; + + case SSH_FRAME_TYPE_DATA_NSQ: + ssh_ptl_rx_dataframe(ptl, frame, &payload); + break; + + default: + ptl_warn(ptl, "ptl: received frame with unknown type %#04x\n", + frame->type); + break; + } + + return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); +} + +static int ssh_ptl_rx_threadfn(void *data) +{ + struct ssh_ptl *ptl = data; + + while (true) { + struct ssam_span span; + size_t offs = 0; + size_t n; + + wait_event_interruptible(ptl->rx.wq, + !kfifo_is_empty(&ptl->rx.fifo) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Copy from fifo to evaluation buffer. */ + n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); + + ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); + print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, + ptl->rx.buf.ptr + ptl->rx.buf.len - n, + n, false); + + /* Parse until we need more bytes or buffer is empty. */ + while (offs < ptl->rx.buf.len) { + sshp_buf_span_from(&ptl->rx.buf, offs, &span); + n = ssh_ptl_rx_eval(ptl, &span); + if (n == 0) + break; /* Need more bytes. */ + + offs += n; + } + + /* Throw away the evaluated parts. */ + sshp_buf_drop(&ptl->rx.buf, offs); + } + + return 0; +} + +static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) +{ + wake_up(&ptl->rx.wq); +} + +/** + * ssh_ptl_rx_start() - Start packet transport layer receiver thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_rx_start(struct ssh_ptl *ptl) +{ + if (ptl->rx.thread) + return 0; + + ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, + "ssam_serial_hub-rx"); + if (IS_ERR(ptl->rx.thread)) + return PTR_ERR(ptl->rx.thread); + + return 0; +} + +/** + * ssh_ptl_rx_stop() - Stop packet transport layer receiver thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_rx_stop(struct ssh_ptl *ptl) +{ + int status = 0; + + if (ptl->rx.thread) { + status = kthread_stop(ptl->rx.thread); + ptl->rx.thread = NULL; + } + + return status; +} + +/** + * ssh_ptl_rx_rcvbuf() - Push data from lower-layer transport to the packet + * layer. + * @ptl: The packet transport layer. + * @buf: Pointer to the data to push to the layer. + * @n: Size of the data to push to the layer, in bytes. + * + * Pushes data from a lower-layer transport to the receiver fifo buffer of the + * packet layer and notifies the receiver thread. Calls to this function are + * ignored once the packet layer has been shut down. + * + * Return: Returns the number of bytes transferred (positive or zero) on + * success. Returns %-ESHUTDOWN if the packet layer has been shut down. + */ +int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) +{ + int used; + + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return -ESHUTDOWN; + + used = kfifo_in(&ptl->rx.fifo, buf, n); + if (used) + ssh_ptl_rx_wakeup(ptl); + + return used; +} + +/** + * ssh_ptl_shutdown() - Shut down the packet transport layer. + * @ptl: The packet transport layer. + * + * Shuts down the packet transport layer, removing and canceling all queued + * and pending packets. Packets canceled by this operation will be completed + * with %-ESHUTDOWN as status. Receiver and transmitter threads will be + * stopped. + * + * As a result of this function, the transport layer will be marked as shut + * down. Submission of packets after the transport layer has been shut down + * will fail with %-ESHUTDOWN. + */ +void ssh_ptl_shutdown(struct ssh_ptl *ptl) +{ + LIST_HEAD(complete_q); + LIST_HEAD(complete_p); + struct ssh_packet *p, *n; + int status; + + /* Ensure that no new packets (including ACK/NAK) can be submitted. */ + set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); + /* + * Ensure that the layer gets marked as shut-down before actually + * stopping it. In combination with the check in ssh_ptl_queue_push(), + * this guarantees that no new packets can be added and all already + * queued packets are properly canceled. In combination with the check + * in ssh_ptl_rx_rcvbuf(), this guarantees that received data is + * properly cut off. + */ + smp_mb__after_atomic(); + + status = ssh_ptl_rx_stop(ptl); + if (status) + ptl_err(ptl, "ptl: failed to stop receiver thread\n"); + + status = ssh_ptl_tx_stop(ptl); + if (status) + ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); + + cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); + + /* + * At this point, all threads have been stopped. This means that the + * only references to packets from inside the system are in the queue + * and pending set. + * + * Note: We still need locks here because someone could still be + * canceling packets. + * + * Note 2: We can re-use queue_node (or pending_node) if we mark the + * packet as locked an then remove it from the queue (or pending set + * respectively). Marking the packet as locked avoids re-queuing + * (which should already be prevented by having stopped the treads...) + * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a + * new list via other threads (e.g. cancellation). + * + * Note 3: There may be overlap between complete_p and complete_q. + * This is handled via test_and_set_bit() on the "completed" flag + * (also handles cancellation). + */ + + /* Mark queued packets as locked and move them to complete_q. */ + spin_lock(&ptl->queue.lock); + list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); + /* Ensure that state does not get zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); + + list_del(&p->queue_node); + list_add_tail(&p->queue_node, &complete_q); + } + spin_unlock(&ptl->queue.lock); + + /* Mark pending packets as locked and move them to complete_p. */ + spin_lock(&ptl->pending.lock); + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); + /* Ensure that state does not get zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + list_del(&p->pending_node); + list_add_tail(&p->pending_node, &complete_q); + } + atomic_set(&ptl->pending.count, 0); + spin_unlock(&ptl->pending.lock); + + /* Complete and drop packets on complete_q. */ + list_for_each_entry(p, &complete_q, queue_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + __ssh_ptl_complete(p, -ESHUTDOWN); + + ssh_packet_put(p); + } + + /* Complete and drop packets on complete_p. */ + list_for_each_entry(p, &complete_p, pending_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + __ssh_ptl_complete(p, -ESHUTDOWN); + + ssh_packet_put(p); + } + + /* + * At this point we have guaranteed that the system doesn't reference + * any packets any more. + */ +} + +/** + * ssh_ptl_init() - Initialize packet transport layer. + * @ptl: The packet transport layer to initialize. + * @serdev: The underlying serial device, i.e. the lower-level transport. + * @ops: Packet layer operations. + * + * Initializes the given packet transport layer. Transmitter and receiver + * threads must be started separately via ssh_ptl_tx_start() and + * ssh_ptl_rx_start(), after the packet-layer has been initialized and the + * lower-level transport layer has been set up. + * + * Return: Returns zero on success and a nonzero error code on failure. + */ +int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, + struct ssh_ptl_ops *ops) +{ + int i, status; + + ptl->serdev = serdev; + ptl->state = 0; + + spin_lock_init(&ptl->queue.lock); + INIT_LIST_HEAD(&ptl->queue.head); + + spin_lock_init(&ptl->pending.lock); + INIT_LIST_HEAD(&ptl->pending.head); + atomic_set_release(&ptl->pending.count, 0); + + ptl->tx.thread = NULL; + atomic_set(&ptl->tx.running, 0); + init_completion(&ptl->tx.thread_cplt_pkt); + init_completion(&ptl->tx.thread_cplt_tx); + init_waitqueue_head(&ptl->tx.packet_wq); + + ptl->rx.thread = NULL; + init_waitqueue_head(&ptl->rx.wq); + + spin_lock_init(&ptl->rtx_timeout.lock); + ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; + ptl->rtx_timeout.expires = KTIME_MAX; + INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); + + ptl->ops = *ops; + + /* Initialize list of recent/blocked SEQs with invalid sequence IDs. */ + for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) + ptl->rx.blocked.seqs[i] = U16_MAX; + ptl->rx.blocked.offset = 0; + + status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); + if (status) + return status; + + status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); + if (status) + kfifo_free(&ptl->rx.fifo); + + return status; +} + +/** + * ssh_ptl_destroy() - Deinitialize packet transport layer. + * @ptl: The packet transport layer to deinitialize. + * + * Deinitializes the given packet transport layer and frees resources + * associated with it. If receiver and/or transmitter threads have been + * started, the layer must first be shut down via ssh_ptl_shutdown() before + * this function can be called. + */ +void ssh_ptl_destroy(struct ssh_ptl *ptl) +{ + kfifo_free(&ptl->rx.fifo); + sshp_buf_free(&ptl->rx.buf); +} diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h new file mode 100644 index 000000000000..e8757d03f279 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH packet transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H +#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ssh_parser.h" + +/** + * enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl. + * + * @SSH_PTL_SF_SHUTDOWN_BIT: + * Indicates that the packet transport layer has been shut down or is + * being shut down and should not accept any new packets/data. + */ +enum ssh_ptl_state_flags { + SSH_PTL_SF_SHUTDOWN_BIT, +}; + +/** + * struct ssh_ptl_ops - Callback operations for packet transport layer. + * @data_received: Function called when a data-packet has been received. Both, + * the packet layer on which the packet has been received and + * the packet's payload data are provided to this function. + */ +struct ssh_ptl_ops { + void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); +}; + +/** + * struct ssh_ptl - SSH packet transport layer. + * @serdev: Serial device providing the underlying data transport. + * @state: State(-flags) of the transport layer. + * @queue: Packet submission queue. + * @queue.lock: Lock for modifying the packet submission queue. + * @queue.head: List-head of the packet submission queue. + * @pending: Set/list of pending packets. + * @pending.lock: Lock for modifying the pending set. + * @pending.head: List-head of the pending set/list. + * @pending.count: Number of currently pending packets. + * @tx: Transmitter subsystem. + * @tx.running: Flag indicating (desired) transmitter thread state. + * @tx.thread: Transmitter thread. + * @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer. + * @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets. + * @tx.packet_wq: Waitqueue-head for packet transmit completion. + * @rx: Receiver subsystem. + * @rx.thread: Receiver thread. + * @rx.wq: Waitqueue-head for receiver thread. + * @rx.fifo: Buffer for receiving data/pushing data to receiver thread. + * @rx.buf: Buffer for evaluating data on receiver thread. + * @rx.blocked: List of recent/blocked sequence IDs to detect retransmission. + * @rx.blocked.seqs: Array of blocked sequence IDs. + * @rx.blocked.offset: Offset indicating where a new ID should be inserted. + * @rtx_timeout: Retransmission timeout subsystem. + * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. + * @rtx_timeout.timeout: Timeout interval for retransmission. + * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. + * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. + * @ops: Packet layer operations. + */ +struct ssh_ptl { + struct serdev_device *serdev; + unsigned long state; + + struct { + spinlock_t lock; + struct list_head head; + } queue; + + struct { + spinlock_t lock; + struct list_head head; + atomic_t count; + } pending; + + struct { + atomic_t running; + struct task_struct *thread; + struct completion thread_cplt_tx; + struct completion thread_cplt_pkt; + struct wait_queue_head packet_wq; + } tx; + + struct { + struct task_struct *thread; + struct wait_queue_head wq; + struct kfifo fifo; + struct sshp_buf buf; + + struct { + u16 seqs[8]; + u16 offset; + } blocked; + } rx; + + struct { + spinlock_t lock; + ktime_t timeout; + ktime_t expires; + struct delayed_work reaper; + } rtx_timeout; + + struct ssh_ptl_ops ops; +}; + +#define __ssam_prcond(func, p, fmt, ...) \ + do { \ + typeof(p) __p = (p); \ + \ + if (__p) \ + func(__p, fmt, ##__VA_ARGS__); \ + } while (0) + +#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) + +#define to_ssh_ptl(ptr, member) \ + container_of(ptr, struct ssh_ptl, member) + +int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, + struct ssh_ptl_ops *ops); + +void ssh_ptl_destroy(struct ssh_ptl *ptl); + +/** + * ssh_ptl_get_device() - Get device associated with packet transport layer. + * @ptl: The packet transport layer. + * + * Return: Returns the device on which the given packet transport layer builds + * upon. + */ +static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) +{ + return ptl->serdev ? &ptl->serdev->dev : NULL; +} + +int ssh_ptl_tx_start(struct ssh_ptl *ptl); +int ssh_ptl_tx_stop(struct ssh_ptl *ptl); +int ssh_ptl_rx_start(struct ssh_ptl *ptl); +int ssh_ptl_rx_stop(struct ssh_ptl *ptl); +void ssh_ptl_shutdown(struct ssh_ptl *ptl); + +int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); +void ssh_ptl_cancel(struct ssh_packet *p); + +int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); + +/** + * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for + * transfer. + * @ptl: The packet transport layer. + * + * Wakes up the packet transmitter thread, notifying it that the underlying + * transport has more space for data to be transmitted. If the packet + * transport layer has been shut down, calls to this function will be ignored. + */ +static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) +{ + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return; + + complete(&ptl->tx.thread_cplt_tx); +} + +void ssh_packet_init(struct ssh_packet *packet, unsigned long type, + u8 priority, const struct ssh_packet_ops *ops); + +int ssh_ctrl_packet_cache_init(void); +void ssh_ctrl_packet_cache_destroy(void); + +#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c new file mode 100644 index 000000000000..e2dead8de94a --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_parser.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH message parser. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include + +#include +#include "ssh_parser.h" + +/** + * sshp_validate_crc() - Validate a CRC in raw message data. + * @src: The span of data over which the CRC should be computed. + * @crc: The pointer to the expected u16 CRC value. + * + * Computes the CRC of the provided data span (@src), compares it to the CRC + * stored at the given address (@crc), and returns the result of this + * comparison, i.e. %true if equal. This function is intended to run on raw + * input/message data. + * + * Return: Returns %true if the computed CRC matches the stored CRC, %false + * otherwise. + */ +static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) +{ + u16 actual = ssh_crc(src->ptr, src->len); + u16 expected = get_unaligned_le16(crc); + + return actual == expected; +} + +/** + * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. + * @src: The data span to check the start of. + */ +static bool sshp_starts_with_syn(const struct ssam_span *src) +{ + return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; +} + +/** + * sshp_find_syn() - Find SSH SYN bytes in the given data span. + * @src: The data span to search in. + * @rem: The span (output) indicating the remaining data, starting with SSH + * SYN bytes, if found. + * + * Search for SSH SYN bytes in the given source span. If found, set the @rem + * span to the remaining data, starting with the first SYN bytes and capped by + * the source span length, and return %true. This function does not copy any + * data, but rather only sets pointers to the respective start addresses and + * length values. + * + * If no SSH SYN bytes could be found, set the @rem span to the zero-length + * span at the end of the source span and return %false. + * + * If partial SSH SYN bytes could be found at the end of the source span, set + * the @rem span to cover these partial SYN bytes, capped by the end of the + * source span, and return %false. This function should then be re-run once + * more data is available. + * + * Return: Returns %true if a complete SSH SYN sequence could be found, + * %false otherwise. + */ +bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) +{ + size_t i; + + for (i = 0; i < src->len - 1; i++) { + if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { + rem->ptr = src->ptr + i; + rem->len = src->len - i; + return true; + } + } + + if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { + rem->ptr = src->ptr + src->len - 1; + rem->len = 1; + return false; + } + + rem->ptr = src->ptr + src->len; + rem->len = 0; + return false; +} + +/** + * sshp_parse_frame() - Parse SSH frame. + * @dev: The device used for logging. + * @source: The source to parse from. + * @frame: The parsed frame (output). + * @payload: The parsed payload (output). + * @maxlen: The maximum supported message length. + * + * Parses and validates a SSH frame, including its payload, from the given + * source. Sets the provided @frame pointer to the start of the frame and + * writes the limits of the frame payload to the provided @payload span + * pointer. + * + * This function does not copy any data, but rather only validates the message + * data and sets pointers (and length values) to indicate the respective parts. + * + * If no complete SSH frame could be found, the frame pointer will be set to + * the %NULL pointer and the payload span will be set to the null span (start + * pointer %NULL, size zero). + * + * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if + * the start of the message is invalid, %-EBADMSG if any (frame-header or + * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than + * the maximum message length specified in the @maxlen parameter. + */ +int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, + struct ssh_frame **frame, struct ssam_span *payload, + size_t maxlen) +{ + struct ssam_span sf; + struct ssam_span sp; + + /* Initialize output. */ + *frame = NULL; + payload->ptr = NULL; + payload->len = 0; + + if (!sshp_starts_with_syn(source)) { + dev_warn(dev, "rx: parser: invalid start of frame\n"); + return -ENOMSG; + } + + /* Check for minimum packet length. */ + if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { + dev_dbg(dev, "rx: parser: not enough data for frame\n"); + return 0; + } + + /* Pin down frame. */ + sf.ptr = source->ptr + sizeof(u16); + sf.len = sizeof(struct ssh_frame); + + /* Validate frame CRC. */ + if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { + dev_warn(dev, "rx: parser: invalid frame CRC\n"); + return -EBADMSG; + } + + /* Ensure packet does not exceed maximum length. */ + sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); + if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { + dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", + SSH_MESSAGE_LENGTH(sp.len)); + return -EMSGSIZE; + } + + /* Pin down payload. */ + sp.ptr = sf.ptr + sf.len + sizeof(u16); + + /* Check for frame + payload length. */ + if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { + dev_dbg(dev, "rx: parser: not enough data for payload\n"); + return 0; + } + + /* Validate payload CRC. */ + if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { + dev_warn(dev, "rx: parser: invalid payload CRC\n"); + return -EBADMSG; + } + + *frame = (struct ssh_frame *)sf.ptr; + *payload = sp; + + dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", + (*frame)->type, (*frame)->len); + + return 0; +} + +/** + * sshp_parse_command() - Parse SSH command frame payload. + * @dev: The device used for logging. + * @source: The source to parse from. + * @command: The parsed command (output). + * @command_data: The parsed command data/payload (output). + * + * Parses and validates a SSH command frame payload. Sets the @command pointer + * to the command header and the @command_data span to the command data (i.e. + * payload of the command). This will result in a zero-length span if the + * command does not have any associated data/payload. This function does not + * check the frame-payload-type field, which should be checked by the caller + * before calling this function. + * + * The @source parameter should be the complete frame payload, e.g. returned + * by the sshp_parse_frame() command. + * + * This function does not copy any data, but rather only validates the frame + * payload data and sets pointers (and length values) to indicate the + * respective parts. + * + * Return: Returns zero on success or %-ENOMSG if @source does not represent a + * valid command-type frame payload, i.e. is too short. + */ +int sshp_parse_command(const struct device *dev, const struct ssam_span *source, + struct ssh_command **command, + struct ssam_span *command_data) +{ + /* Check for minimum length. */ + if (unlikely(source->len < sizeof(struct ssh_command))) { + *command = NULL; + command_data->ptr = NULL; + command_data->len = 0; + + dev_err(dev, "rx: parser: command payload is too short\n"); + return -ENOMSG; + } + + *command = (struct ssh_command *)source->ptr; + command_data->ptr = source->ptr + sizeof(struct ssh_command); + command_data->len = source->len - sizeof(struct ssh_command); + + dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", + (*command)->tc, (*command)->cid); + + return 0; +} diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h new file mode 100644 index 000000000000..63c38d350988 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_parser.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH message parser. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H +#define _SURFACE_AGGREGATOR_SSH_PARSER_H + +#include +#include +#include +#include + +#include + +/** + * struct sshp_buf - Parser buffer for SSH messages. + * @ptr: Pointer to the beginning of the buffer. + * @len: Number of bytes used in the buffer. + * @cap: Maximum capacity of the buffer. + */ +struct sshp_buf { + u8 *ptr; + size_t len; + size_t cap; +}; + +/** + * sshp_buf_init() - Initialize a SSH parser buffer. + * @buf: The buffer to initialize. + * @ptr: The memory backing the buffer. + * @cap: The length of the memory backing the buffer, i.e. its capacity. + * + * Initializes the buffer with the given memory as backing and set its used + * length to zero. + */ +static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) +{ + buf->ptr = ptr; + buf->len = 0; + buf->cap = cap; +} + +/** + * sshp_buf_alloc() - Allocate and initialize a SSH parser buffer. + * @buf: The buffer to initialize/allocate to. + * @cap: The desired capacity of the buffer. + * @flags: The flags used for allocating the memory. + * + * Allocates @cap bytes and initializes the provided buffer struct with the + * allocated memory. + * + * Return: Returns zero on success and %-ENOMEM if allocation failed. + */ +static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) +{ + u8 *ptr; + + ptr = kzalloc(cap, flags); + if (!ptr) + return -ENOMEM; + + sshp_buf_init(buf, ptr, cap); + return 0; +} + +/** + * sshp_buf_free() - Free a SSH parser buffer. + * @buf: The buffer to free. + * + * Frees a SSH parser buffer by freeing the memory backing it and then + * resetting its pointer to %NULL and length and capacity to zero. Intended to + * free a buffer previously allocated with sshp_buf_alloc(). + */ +static inline void sshp_buf_free(struct sshp_buf *buf) +{ + kfree(buf->ptr); + buf->ptr = NULL; + buf->len = 0; + buf->cap = 0; +} + +/** + * sshp_buf_drop() - Drop data from the beginning of the buffer. + * @buf: The buffer to drop data from. + * @n: The number of bytes to drop. + * + * Drops the first @n bytes from the buffer. Re-aligns any remaining data to + * the beginning of the buffer. + */ +static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) +{ + memmove(buf->ptr, buf->ptr + n, buf->len - n); + buf->len -= n; +} + +/** + * sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer. + * @buf: The buffer to write the data into. + * @fifo: The fifo to read the data from. + * + * Transfers the data contained in the fifo to the buffer, removing it from + * the fifo. This function will try to transfer as much data as possible, + * limited either by the remaining space in the buffer or by the number of + * bytes available in the fifo. + * + * Return: Returns the number of bytes transferred. + */ +static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, + struct kfifo *fifo) +{ + size_t n; + + n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); + buf->len += n; + + return n; +} + +/** + * sshp_buf_span_from() - Initialize a span from the given buffer and offset. + * @buf: The buffer to create the span from. + * @offset: The offset in the buffer at which the span should start. + * @span: The span to initialize (output). + * + * Initializes the provided span to point to the memory at the given offset in + * the buffer, with the length of the span being capped by the number of bytes + * used in the buffer after the offset (i.e. bytes remaining after the + * offset). + * + * Warning: This function does not validate that @offset is less than or equal + * to the number of bytes used in the buffer or the buffer capacity. This must + * be guaranteed by the caller. + */ +static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, + struct ssam_span *span) +{ + span->ptr = buf->ptr + offset; + span->len = buf->len - offset; +} + +bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); + +int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, + struct ssh_frame **frame, struct ssam_span *payload, + size_t maxlen); + +int sshp_parse_command(const struct device *dev, const struct ssam_span *source, + struct ssh_command **command, + struct ssam_span *command_data); + +#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c new file mode 100644 index 000000000000..52a83a8fcf82 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -0,0 +1,1263 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH request transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_packet_layer.h" +#include "ssh_request_layer.h" + +#include "trace.h" + +/* + * SSH_RTL_REQUEST_TIMEOUT - Request timeout. + * + * Timeout as ktime_t delta for request responses. If we have not received a + * response in this time-frame after finishing the underlying packet + * transmission, the request will be completed with %-ETIMEDOUT as status + * code. + */ +#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) + +/* + * SSH_RTL_REQUEST_TIMEOUT_RESOLUTION - Request timeout granularity. + * + * Time-resolution for timeouts. Should be larger than one jiffy to avoid + * direct re-scheduling of reaper work_struct. + */ +#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) + +/* + * SSH_RTL_MAX_PENDING - Maximum number of pending requests. + * + * Maximum number of requests concurrently waiting to be completed (i.e. + * waiting for the corresponding packet transmission to finish if they don't + * have a response or waiting for a response if they have one). + */ +#define SSH_RTL_MAX_PENDING 3 + +/* + * SSH_RTL_TX_BATCH - Maximum number of requests processed per work execution. + * Used to prevent livelocking of the workqueue. Value chosen via educated + * guess, may be adjusted. + */ +#define SSH_RTL_TX_BATCH 10 + +#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_rtl_should_drop_response() - Error injection hook to drop request + * responses. + * + * Useful to cause request transmission timeouts in the driver by dropping the + * response to a request. + */ +static noinline bool ssh_rtl_should_drop_response(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); + +#else + +static inline bool ssh_rtl_should_drop_response(void) +{ + return false; +} + +#endif + +static u16 ssh_request_get_rqid(struct ssh_request *rqst) +{ + return get_unaligned_le16(rqst->packet.data.ptr + + SSH_MSGOFFSET_COMMAND(rqid)); +} + +static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) +{ + if (!rqst->packet.data.ptr) + return U32_MAX; + + return ssh_request_get_rqid(rqst); +} + +static void ssh_rtl_queue_remove(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->queue.lock); + + if (!test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state)) { + spin_unlock(&rtl->queue.lock); + return; + } + + list_del(&rqst->node); + + spin_unlock(&rtl->queue.lock); + ssh_request_put(rqst); +} + +static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) +{ + bool empty; + + spin_lock(&rtl->queue.lock); + empty = list_empty(&rtl->queue.head); + spin_unlock(&rtl->queue.lock); + + return empty; +} + +static void ssh_rtl_pending_remove(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->pending.lock); + + if (!test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return; + } + + atomic_dec(&rtl->pending.count); + list_del(&rqst->node); + + spin_unlock(&rtl->pending.lock); + + ssh_request_put(rqst); +} + +static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->pending.lock); + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return -EINVAL; + } + + if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return -EALREADY; + } + + atomic_inc(&rtl->pending.count); + list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); + + spin_unlock(&rtl->pending.lock); + return 0; +} + +static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + trace_ssam_request_complete(rqst, status); + + /* rtl/ptl may not be set if we're canceling before submitting. */ + rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", + ssh_request_get_rqid_safe(rqst), status); + + rqst->ops->complete(rqst, NULL, NULL, status); +} + +static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, + const struct ssh_command *cmd, + const struct ssam_span *data) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + trace_ssam_request_complete(rqst, 0); + + rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", + ssh_request_get_rqid(rqst)); + + rqst->ops->complete(rqst, cmd, data, 0); +} + +static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) + return !atomic_read(&rtl->pending.count); + + return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; +} + +static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) +{ + struct ssh_request *rqst = ERR_PTR(-ENOENT); + struct ssh_request *p, *n; + + spin_lock(&rtl->queue.lock); + + /* Find first non-locked request and remove it. */ + list_for_each_entry_safe(p, n, &rtl->queue.head, node) { + if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) + continue; + + if (!ssh_rtl_tx_can_process(p)) { + rqst = ERR_PTR(-EBUSY); + break; + } + + /* Remove from queue and mark as transmitting. */ + set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); + + list_del(&p->node); + + rqst = p; + break; + } + + spin_unlock(&rtl->queue.lock); + return rqst; +} + +static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) +{ + struct ssh_request *rqst; + int status; + + /* Get and prepare next request for transmit. */ + rqst = ssh_rtl_tx_next(rtl); + if (IS_ERR(rqst)) + return PTR_ERR(rqst); + + /* Add it to/mark it as pending. */ + status = ssh_rtl_tx_pending_push(rqst); + if (status) { + ssh_request_put(rqst); + return -EAGAIN; + } + + /* Submit packet. */ + status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); + if (status == -ESHUTDOWN) { + /* + * Packet has been refused due to the packet layer shutting + * down. Complete it here. + */ + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); + /* + * Note: A barrier is not required here, as there are only two + * references in the system at this point: The one that we have, + * and the other one that belongs to the pending set. Due to the + * request being marked as "transmitting", our process is the + * only one allowed to remove the pending node and change the + * state. Normally, the task would fall to the packet callback, + * but as this is a path where submission failed, this callback + * will never be executed. + */ + + ssh_rtl_pending_remove(rqst); + ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); + + ssh_request_put(rqst); + return -ESHUTDOWN; + + } else if (status) { + /* + * If submitting the packet failed and the packet layer isn't + * shutting down, the packet has either been submitted/queued + * before (-EALREADY, which cannot happen as we have + * guaranteed that requests cannot be re-submitted), or the + * packet was marked as locked (-EINVAL). To mark the packet + * locked at this stage, the request, and thus the packets + * itself, had to have been canceled. Simply drop the + * reference. Cancellation itself will remove it from the set + * of pending requests. + */ + + WARN_ON(status != -EINVAL); + + ssh_request_put(rqst); + return -EAGAIN; + } + + ssh_request_put(rqst); + return 0; +} + +static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) +{ + if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) + return false; + + if (ssh_rtl_queue_empty(rtl)) + return false; + + return schedule_work(&rtl->tx.work); +} + +static void ssh_rtl_tx_work_fn(struct work_struct *work) +{ + struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); + unsigned int iterations = SSH_RTL_TX_BATCH; + int status; + + /* + * Try to be nice and not block/live-lock the workqueue: Run a maximum + * of 10 tries, then re-submit if necessary. This should not be + * necessary for normal execution, but guarantee it anyway. + */ + do { + status = ssh_rtl_tx_try_process_one(rtl); + if (status == -ENOENT || status == -EBUSY) + return; /* No more requests to process. */ + + if (status == -ESHUTDOWN) { + /* + * Packet system shutting down. No new packets can be + * transmitted. Return silently, the party initiating + * the shutdown should handle the rest. + */ + return; + } + + WARN_ON(status != 0 && status != -EAGAIN); + } while (--iterations); + + /* Out of tries, reschedule. */ + ssh_rtl_tx_schedule(rtl); +} + +/** + * ssh_rtl_submit() - Submit a request to the transport layer. + * @rtl: The request transport layer. + * @rqst: The request to submit. + * + * Submits a request to the transport layer. A single request may not be + * submitted multiple times without reinitializing it. + * + * Return: Returns zero on success, %-EINVAL if the request type is invalid or + * the request has been canceled prior to submission, %-EALREADY if the + * request has already been submitted, or %-ESHUTDOWN in case the request + * transport layer has been shut down. + */ +int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) +{ + trace_ssam_request_submit(rqst); + + /* + * Ensure that requests expecting a response are sequenced. If this + * invariant ever changes, see the comment in ssh_rtl_complete() on what + * is required to be changed in the code. + */ + if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) + if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) + return -EINVAL; + + spin_lock(&rtl->queue.lock); + + /* + * Try to set ptl and check if this request has already been submitted. + * + * Must be inside lock as we might run into a lost update problem + * otherwise: If this were outside of the lock, cancellation in + * ssh_rtl_cancel_nonpending() may run after we've set the ptl + * reference but before we enter the lock. In that case, we'd detect + * that the request is being added to the queue and would try to remove + * it from that, but removal might fail because it hasn't actually been + * added yet. By putting this cmpxchg in the critical section, we + * ensure that the queuing detection only triggers when we are already + * in the critical section and the remove process will wait until the + * push operation has been completed (via lock) due to that. Only then, + * we can safely try to remove it. + */ + if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl)) { + spin_unlock(&rtl->queue.lock); + return -EALREADY; + } + + /* + * Ensure that we set ptl reference before we continue modifying state. + * This is required for non-pending cancellation. This barrier is paired + * with the one in ssh_rtl_cancel_nonpending(). + * + * By setting the ptl reference before we test for "locked", we can + * check if the "locked" test may have already run. See comments in + * ssh_rtl_cancel_nonpending() for more detail. + */ + smp_mb__after_atomic(); + + if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { + spin_unlock(&rtl->queue.lock); + return -ESHUTDOWN; + } + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { + spin_unlock(&rtl->queue.lock); + return -EINVAL; + } + + set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); + list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); + + spin_unlock(&rtl->queue.lock); + + ssh_rtl_tx_schedule(rtl); + return 0; +} + +static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, + ktime_t expires) +{ + unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); + ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); + + spin_lock(&rtl->rtx_timeout.lock); + + /* Re-adjust / schedule reaper only if it is above resolution delta. */ + if (ktime_before(aexp, rtl->rtx_timeout.expires)) { + rtl->rtx_timeout.expires = expires; + mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); + } + + spin_unlock(&rtl->rtx_timeout.lock); +} + +static void ssh_rtl_timeout_start(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + ktime_t timestamp = ktime_get_coarse_boottime(); + ktime_t timeout = rtl->rtx_timeout.timeout; + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) + return; + + /* + * Note: The timestamp gets set only once. This happens on the packet + * callback. All other access to it is read-only. + */ + WRITE_ONCE(rqst->timestamp, timestamp); + /* + * Ensure timestamp is set before starting the reaper. Paired with + * implicit barrier following check on ssh_request_get_expiration() in + * ssh_rtl_timeout_reap. + */ + smp_mb__after_atomic(); + + ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); +} + +static void ssh_rtl_complete(struct ssh_rtl *rtl, + const struct ssh_command *command, + const struct ssam_span *command_data) +{ + struct ssh_request *r = NULL; + struct ssh_request *p, *n; + u16 rqid = get_unaligned_le16(&command->rqid); + + trace_ssam_rx_response_received(command, command_data->len); + + /* + * Get request from pending based on request ID and mark it as response + * received and locked. + */ + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(p, n, &rtl->pending.head, node) { + /* We generally expect requests to be processed in order. */ + if (unlikely(ssh_request_get_rqid(p) != rqid)) + continue; + + /* Simulate response timeout. */ + if (ssh_rtl_should_drop_response()) { + spin_unlock(&rtl->pending.lock); + + trace_ssam_ei_rx_drop_response(p); + rtl_info(rtl, "request error injection: dropping response for request %p\n", + &p->packet); + return; + } + + /* + * Mark as "response received" and "locked" as we're going to + * complete it. + */ + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); + set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); + + atomic_dec(&rtl->pending.count); + list_del(&p->node); + + r = p; + break; + } + spin_unlock(&rtl->pending.lock); + + if (!r) { + rtl_warn(rtl, "rtl: dropping unexpected command message (rqid = %#06x)\n", + rqid); + return; + } + + /* If the request hasn't been completed yet, we will do this now. */ + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { + ssh_request_put(r); + ssh_rtl_tx_schedule(rtl); + return; + } + + /* + * Make sure the request has been transmitted. In case of a sequenced + * request, we are guaranteed that the completion callback will run on + * the receiver thread directly when the ACK for the packet has been + * received. Similarly, this function is guaranteed to run on the + * receiver thread. Thus we are guaranteed that if the packet has been + * successfully transmitted and received an ACK, the transmitted flag + * has been set and is visible here. + * + * We are currently not handling unsequenced packets here, as those + * should never expect a response as ensured in ssh_rtl_submit. If this + * ever changes, one would have to test for + * + * (r->state & (transmitting | transmitted)) + * + * on unsequenced packets to determine if they could have been + * transmitted. There are no synchronization guarantees as in the + * sequenced case, since, in this case, the callback function will not + * run on the same thread. Thus an exact determination is impossible. + */ + if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { + rtl_err(rtl, "rtl: received response before ACK for request (rqid = %#06x)\n", + rqid); + + /* + * NB: Timeout has already been canceled, request already been + * removed from pending and marked as locked and completed. As + * we receive a "false" response, the packet might still be + * queued though. + */ + ssh_rtl_queue_remove(r); + + ssh_rtl_complete_with_status(r, -EREMOTEIO); + ssh_request_put(r); + + ssh_rtl_tx_schedule(rtl); + return; + } + + /* + * NB: Timeout has already been canceled, request already been + * removed from pending and marked as locked and completed. The request + * can also not be queued any more, as it has been marked as + * transmitting and later transmitted. Thus no need to remove it from + * anywhere. + */ + + ssh_rtl_complete_with_rsp(r, command, command_data); + ssh_request_put(r); + + ssh_rtl_tx_schedule(rtl); +} + +static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) +{ + struct ssh_rtl *rtl; + unsigned long flags, fixed; + bool remove; + + /* + * Handle unsubmitted request: Try to mark the packet as locked, + * expecting the state to be zero (i.e. unsubmitted). Note that, if + * setting the state worked, we might still be adding the packet to the + * queue in a currently executing submit call. In that case, however, + * ptl reference must have been set previously, as locked is checked + * after setting ptl. Furthermore, when the ptl reference is set, the + * submission process is guaranteed to have entered the critical + * section. Thus only if we successfully locked this request and ptl is + * NULL, we have successfully removed the request, i.e. we are + * guaranteed that, due to the "locked" check in ssh_rtl_submit(), the + * packet will never be added. Otherwise, we need to try and grab it + * from the queue, where we are now guaranteed that the packet is or has + * been due to the critical section. + * + * Note that if the cmpxchg() fails, we are guaranteed that ptl has + * been set and is non-NULL, as states can only be nonzero after this + * has been set. Also note that we need to fetch the static (type) + * flags to ensure that they don't cause the cmpxchg() to fail. + */ + fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; + flags = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); + + /* + * Force correct ordering with regards to state and ptl reference access + * to safe-guard cancellation to concurrent submission against a + * lost-update problem. First try to exchange state, then also check + * ptl if that worked. This barrier is paired with the + * one in ssh_rtl_submit(). + */ + smp_mb__after_atomic(); + + if (flags == fixed && !READ_ONCE(r->packet.ptl)) { + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; + } + + rtl = ssh_request_rtl(r); + spin_lock(&rtl->queue.lock); + + /* + * Note: 1) Requests cannot be re-submitted. 2) If a request is + * queued, it cannot be "transmitting"/"pending" yet. Thus, if we + * successfully remove the request here, we have removed all its + * occurrences in the system. + */ + + remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); + if (!remove) { + spin_unlock(&rtl->queue.lock); + return false; + } + + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + list_del(&r->node); + + spin_unlock(&rtl->queue.lock); + + ssh_request_put(r); /* Drop reference obtained from queue. */ + + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; +} + +static bool ssh_rtl_cancel_pending(struct ssh_request *r) +{ + /* If the packet is already locked, it's going to be removed shortly. */ + if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) + return true; + + /* + * Now that we have locked the packet, we have guaranteed that it can't + * be added to the system any more. If ptl is NULL, the locked + * check in ssh_rtl_submit() has not been run and any submission, + * currently in progress or called later, won't add the packet. Thus we + * can directly complete it. + * + * The implicit memory barrier of test_and_set_bit() should be enough + * to ensure that the correct order (first lock, then check ptl) is + * ensured. This is paired with the barrier in ssh_rtl_submit(). + */ + if (!READ_ONCE(r->packet.ptl)) { + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; + } + + /* + * Try to cancel the packet. If the packet has not been completed yet, + * this will subsequently (and synchronously) call the completion + * callback of the packet, which will complete the request. + */ + ssh_ptl_cancel(&r->packet); + + /* + * If the packet has been completed with success, i.e. has not been + * canceled by the above call, the request may not have been completed + * yet (may be waiting for a response). Check if we need to do this + * here. + */ + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_queue_remove(r); + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, -ECANCELED); + + return true; +} + +/** + * ssh_rtl_cancel() - Cancel request. + * @rqst: The request to cancel. + * @pending: Whether to also cancel pending requests. + * + * Cancels the given request. If @pending is %false, this will not cancel + * pending requests, i.e. requests that have already been submitted to the + * packet layer but not been completed yet. If @pending is %true, this will + * cancel the given request regardless of the state it is in. + * + * If the request has been canceled by calling this function, both completion + * and release callbacks of the request will be executed in a reasonable + * time-frame. This may happen during execution of this function, however, + * there is no guarantee for this. For example, a request currently + * transmitting will be canceled/completed only after transmission has + * completed, and the respective callbacks will be executed on the transmitter + * thread, which may happen during, but also some time after execution of the + * cancel function. + * + * Return: Returns %true if the given request has been canceled or completed, + * either by this function or prior to calling this function, %false + * otherwise. If @pending is %true, this function will always return %true. + */ +bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) +{ + struct ssh_rtl *rtl; + bool canceled; + + if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) + return true; + + trace_ssam_request_cancel(rqst); + + if (pending) + canceled = ssh_rtl_cancel_pending(rqst); + else + canceled = ssh_rtl_cancel_nonpending(rqst); + + /* Note: rtl may be NULL if request has not been submitted yet. */ + rtl = ssh_request_rtl(rqst); + if (canceled && rtl) + ssh_rtl_tx_schedule(rtl); + + return canceled; +} + +static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) +{ + struct ssh_request *r = to_ssh_request(p); + + if (unlikely(status)) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return; + + /* + * The packet may get canceled even though it has not been + * submitted yet. The request may still be queued. Check the + * queue and remove it if necessary. As the timeout would have + * been started in this function on success, there's no need + * to cancel it here. + */ + ssh_rtl_queue_remove(r); + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, status); + + ssh_rtl_tx_schedule(ssh_request_rtl(r)); + return; + } + + /* Update state: Mark as transmitted and clear transmitting. */ + set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); + + /* If we expect a response, we just need to start the timeout. */ + if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { + /* + * Note: This is the only place where the timestamp gets set, + * all other access to it is read-only. + */ + ssh_rtl_timeout_start(r); + return; + } + + /* + * If we don't expect a response, lock, remove, and complete the + * request. Note that, at this point, the request is guaranteed to have + * left the queue and no timeout has been started. Thus we only need to + * remove it from pending. If the request has already been completed (it + * may have been canceled) return. + */ + + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return; + + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, 0); + + ssh_rtl_tx_schedule(ssh_request_rtl(r)); +} + +static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeout) +{ + ktime_t timestamp = READ_ONCE(r->timestamp); + + if (timestamp != KTIME_MAX) + return ktime_add(timestamp, timeout); + else + return KTIME_MAX; +} + +static void ssh_rtl_timeout_reap(struct work_struct *work) +{ + struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); + struct ssh_request *r, *n; + LIST_HEAD(claimed); + ktime_t now = ktime_get_coarse_boottime(); + ktime_t timeout = rtl->rtx_timeout.timeout; + ktime_t next = KTIME_MAX; + + trace_ssam_rtl_timeout_reap(atomic_read(&rtl->pending.count)); + + /* + * Mark reaper as "not pending". This is done before checking any + * requests to avoid lost-update type problems. + */ + spin_lock(&rtl->rtx_timeout.lock); + rtl->rtx_timeout.expires = KTIME_MAX; + spin_unlock(&rtl->rtx_timeout.lock); + + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(r, n, &rtl->pending.head, node) { + ktime_t expires = ssh_request_get_expiration(r, timeout); + + /* + * Check if the timeout hasn't expired yet. Find out next + * expiration date to be handled after this run. + */ + if (ktime_after(expires, now)) { + next = ktime_before(expires, next) ? expires : next; + continue; + } + + /* Avoid further transitions if locked. */ + if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) + continue; + + /* + * We have now marked the packet as locked. Thus it cannot be + * added to the pending or queued lists again after we've + * removed it here. We can therefore re-use the node of this + * packet temporarily. + */ + + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + + atomic_dec(&rtl->pending.count); + list_del(&r->node); + + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + + /* Cancel and complete the request. */ + list_for_each_entry_safe(r, n, &claimed, node) { + trace_ssam_request_timeout(r); + + /* + * At this point we've removed the packet from pending. This + * means that we've obtained the last (only) reference of the + * system to it. Thus we can just complete it. + */ + if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + ssh_rtl_complete_with_status(r, -ETIMEDOUT); + + /* + * Drop the reference we've obtained by removing it from the + * pending set. + */ + list_del(&r->node); + ssh_request_put(r); + } + + /* Ensure that the reaper doesn't run again immediately. */ + next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); + if (next != KTIME_MAX) + ssh_rtl_timeout_reaper_mod(rtl, now, next); + + ssh_rtl_tx_schedule(rtl); +} + +static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, + const struct ssam_span *data) +{ + trace_ssam_rx_event_received(cmd, data->len); + + rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", + get_unaligned_le16(&cmd->rqid)); + + rtl->ops.handle_event(rtl, cmd, data); +} + +static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) +{ + struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); + struct device *dev = &p->serdev->dev; + struct ssh_command *command; + struct ssam_span command_data; + + if (sshp_parse_command(dev, data, &command, &command_data)) + return; + + if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) + ssh_rtl_rx_event(rtl, command, &command_data); + else + ssh_rtl_complete(rtl, command, &command_data); +} + +static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) +{ + if (!data->len) { + ptl_err(p, "rtl: rx: no data frame payload\n"); + return; + } + + switch (data->ptr[0]) { + case SSH_PLD_TYPE_CMD: + ssh_rtl_rx_command(p, data); + break; + + default: + ptl_err(p, "rtl: rx: unknown frame payload type (type: %#04x)\n", + data->ptr[0]); + break; + } +} + +static void ssh_rtl_packet_release(struct ssh_packet *p) +{ + struct ssh_request *rqst; + + rqst = to_ssh_request(p); + rqst->ops->release(rqst); +} + +static const struct ssh_packet_ops ssh_rtl_packet_ops = { + .complete = ssh_rtl_packet_callback, + .release = ssh_rtl_packet_release, +}; + +/** + * ssh_request_init() - Initialize SSH request. + * @rqst: The request to initialize. + * @flags: Request flags, determining the type of the request. + * @ops: Request operations. + * + * Initializes the given SSH request and underlying packet. Sets the message + * buffer pointer to %NULL and the message buffer length to zero. This buffer + * has to be set separately via ssh_request_set_data() before submission and + * must contain a valid SSH request message. + * + * Return: Returns zero on success or %-EINVAL if the given flags are invalid. + */ +int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, + const struct ssh_request_ops *ops) +{ + unsigned long type = BIT(SSH_PACKET_TY_BLOCKING_BIT); + + /* Unsequenced requests cannot have a response. */ + if (flags & SSAM_REQUEST_UNSEQUENCED && flags & SSAM_REQUEST_HAS_RESPONSE) + return -EINVAL; + + if (!(flags & SSAM_REQUEST_UNSEQUENCED)) + type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); + + ssh_packet_init(&rqst->packet, type, SSH_PACKET_PRIORITY(DATA, 0), + &ssh_rtl_packet_ops); + + INIT_LIST_HEAD(&rqst->node); + + rqst->state = 0; + if (flags & SSAM_REQUEST_HAS_RESPONSE) + rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); + + rqst->timestamp = KTIME_MAX; + rqst->ops = ops; + + return 0; +} + +/** + * ssh_rtl_init() - Initialize request transport layer. + * @rtl: The request transport layer to initialize. + * @serdev: The underlying serial device, i.e. the lower-level transport. + * @ops: Request transport layer operations. + * + * Initializes the given request transport layer and associated packet + * transport layer. Transmitter and receiver threads must be started + * separately via ssh_rtl_start(), after the request-layer has been + * initialized and the lower-level serial device layer has been set up. + * + * Return: Returns zero on success and a nonzero error code on failure. + */ +int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, + const struct ssh_rtl_ops *ops) +{ + struct ssh_ptl_ops ptl_ops; + int status; + + ptl_ops.data_received = ssh_rtl_rx_data; + + status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); + if (status) + return status; + + spin_lock_init(&rtl->queue.lock); + INIT_LIST_HEAD(&rtl->queue.head); + + spin_lock_init(&rtl->pending.lock); + INIT_LIST_HEAD(&rtl->pending.head); + atomic_set_release(&rtl->pending.count, 0); + + INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); + + spin_lock_init(&rtl->rtx_timeout.lock); + rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; + rtl->rtx_timeout.expires = KTIME_MAX; + INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); + + rtl->ops = *ops; + + return 0; +} + +/** + * ssh_rtl_destroy() - Deinitialize request transport layer. + * @rtl: The request transport layer to deinitialize. + * + * Deinitializes the given request transport layer and frees resources + * associated with it. If receiver and/or transmitter threads have been + * started, the layer must first be shut down via ssh_rtl_shutdown() before + * this function can be called. + */ +void ssh_rtl_destroy(struct ssh_rtl *rtl) +{ + ssh_ptl_destroy(&rtl->ptl); +} + +/** + * ssh_rtl_start() - Start request transmitter and receiver. + * @rtl: The request transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_rtl_start(struct ssh_rtl *rtl) +{ + int status; + + status = ssh_ptl_tx_start(&rtl->ptl); + if (status) + return status; + + ssh_rtl_tx_schedule(rtl); + + status = ssh_ptl_rx_start(&rtl->ptl); + if (status) { + ssh_rtl_flush(rtl, msecs_to_jiffies(5000)); + ssh_ptl_tx_stop(&rtl->ptl); + return status; + } + + return 0; +} + +struct ssh_flush_request { + struct ssh_request base; + struct completion completion; + int status; +}; + +static void ssh_rtl_flush_request_complete(struct ssh_request *r, + const struct ssh_command *cmd, + const struct ssam_span *data, + int status) +{ + struct ssh_flush_request *rqst; + + rqst = container_of(r, struct ssh_flush_request, base); + rqst->status = status; +} + +static void ssh_rtl_flush_request_release(struct ssh_request *r) +{ + struct ssh_flush_request *rqst; + + rqst = container_of(r, struct ssh_flush_request, base); + complete_all(&rqst->completion); +} + +static const struct ssh_request_ops ssh_rtl_flush_request_ops = { + .complete = ssh_rtl_flush_request_complete, + .release = ssh_rtl_flush_request_release, +}; + +/** + * ssh_rtl_flush() - Flush the request transport layer. + * @rtl: request transport layer + * @timeout: timeout for the flush operation in jiffies + * + * Queue a special flush request and wait for its completion. This request + * will be completed after all other currently queued and pending requests + * have been completed. Instead of a normal data packet, this request submits + * a special flush packet, meaning that upon completion, also the underlying + * packet transport layer has been flushed. + * + * Flushing the request layer guarantees that all previously submitted + * requests have been fully completed before this call returns. Additionally, + * flushing blocks execution of all later submitted requests until the flush + * has been completed. + * + * If the caller ensures that no new requests are submitted after a call to + * this function, the request transport layer is guaranteed to have no + * remaining requests when this call returns. The same guarantee does not hold + * for the packet layer, on which control packets may still be queued after + * this call. + * + * Return: Returns zero on success, %-ETIMEDOUT if the flush timed out and has + * been canceled as a result of the timeout, or %-ESHUTDOWN if the packet + * and/or request transport layer has been shut down before this call. May + * also return %-EINTR if the underlying packet transmission has been + * interrupted. + */ +int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) +{ + const unsigned int init_flags = SSAM_REQUEST_UNSEQUENCED; + struct ssh_flush_request rqst; + int status; + + ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); + rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); + rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); + rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); + + init_completion(&rqst.completion); + + status = ssh_rtl_submit(rtl, &rqst.base); + if (status) + return status; + + ssh_request_put(&rqst.base); + + if (!wait_for_completion_timeout(&rqst.completion, timeout)) { + ssh_rtl_cancel(&rqst.base, true); + wait_for_completion(&rqst.completion); + } + + WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED && + rqst.status != -ESHUTDOWN && rqst.status != -EINTR); + + return rqst.status == -ECANCELED ? -ETIMEDOUT : rqst.status; +} + +/** + * ssh_rtl_shutdown() - Shut down request transport layer. + * @rtl: The request transport layer. + * + * Shuts down the request transport layer, removing and canceling all queued + * and pending requests. Requests canceled by this operation will be completed + * with %-ESHUTDOWN as status. Receiver and transmitter threads will be + * stopped, the lower-level packet layer will be shutdown. + * + * As a result of this function, the transport layer will be marked as shut + * down. Submission of requests after the transport layer has been shut down + * will fail with %-ESHUTDOWN. + */ +void ssh_rtl_shutdown(struct ssh_rtl *rtl) +{ + struct ssh_request *r, *n; + LIST_HEAD(claimed); + int pending; + + set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); + /* + * Ensure that the layer gets marked as shut-down before actually + * stopping it. In combination with the check in ssh_rtl_submit(), + * this guarantees that no new requests can be added and all already + * queued requests are properly canceled. + */ + smp_mb__after_atomic(); + + /* Remove requests from queue. */ + spin_lock(&rtl->queue.lock); + list_for_each_entry_safe(r, n, &rtl->queue.head, node) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); + + list_del(&r->node); + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->queue.lock); + + /* + * We have now guaranteed that the queue is empty and no more new + * requests can be submitted (i.e. it will stay empty). This means that + * calling ssh_rtl_tx_schedule() will not schedule tx.work any more. So + * we can simply call cancel_work_sync() on tx.work here and when that + * returns, we've locked it down. This also means that after this call, + * we don't submit any more packets to the underlying packet layer, so + * we can also shut that down. + */ + + cancel_work_sync(&rtl->tx.work); + ssh_ptl_shutdown(&rtl->ptl); + cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); + + /* + * Shutting down the packet layer should also have canceled all + * requests. Thus the pending set should be empty. Attempt to handle + * this gracefully anyways, even though this should be dead code. + */ + + pending = atomic_read(&rtl->pending.count); + if (WARN_ON(pending)) { + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(r, n, &rtl->pending.head, node) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + + list_del(&r->node); + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + } + + /* Finally, cancel and complete the requests we claimed before. */ + list_for_each_entry_safe(r, n, &claimed, node) { + /* + * We need test_and_set() because we still might compete with + * cancellation. + */ + if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + ssh_rtl_complete_with_status(r, -ESHUTDOWN); + + /* + * Drop the reference we've obtained by removing it from the + * lists. + */ + list_del(&r->node); + ssh_request_put(r); + } +} diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h new file mode 100644 index 000000000000..cb35815858d1 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH request transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H +#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_packet_layer.h" + +/** + * enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl. + * + * @SSH_RTL_SF_SHUTDOWN_BIT: + * Indicates that the request transport layer has been shut down or is + * being shut down and should not accept any new requests. + */ +enum ssh_rtl_state_flags { + SSH_RTL_SF_SHUTDOWN_BIT, +}; + +/** + * struct ssh_rtl_ops - Callback operations for request transport layer. + * @handle_event: Function called when a SSH event has been received. The + * specified function takes the request layer, received command + * struct, and corresponding payload as arguments. If the event + * has no payload, the payload span is empty (not %NULL). + */ +struct ssh_rtl_ops { + void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, + const struct ssam_span *data); +}; + +/** + * struct ssh_rtl - SSH request transport layer. + * @ptl: Underlying packet transport layer. + * @state: State(-flags) of the transport layer. + * @queue: Request submission queue. + * @queue.lock: Lock for modifying the request submission queue. + * @queue.head: List-head of the request submission queue. + * @pending: Set/list of pending requests. + * @pending.lock: Lock for modifying the request set. + * @pending.head: List-head of the pending set/list. + * @pending.count: Number of currently pending requests. + * @tx: Transmitter subsystem. + * @tx.work: Transmitter work item. + * @rtx_timeout: Retransmission timeout subsystem. + * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. + * @rtx_timeout.timeout: Timeout interval for retransmission. + * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. + * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. + * @ops: Request layer operations. + */ +struct ssh_rtl { + struct ssh_ptl ptl; + unsigned long state; + + struct { + spinlock_t lock; + struct list_head head; + } queue; + + struct { + spinlock_t lock; + struct list_head head; + atomic_t count; + } pending; + + struct { + struct work_struct work; + } tx; + + struct { + spinlock_t lock; + ktime_t timeout; + ktime_t expires; + struct delayed_work reaper; + } rtx_timeout; + + struct ssh_rtl_ops ops; +}; + +#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) +#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) + +#define to_ssh_rtl(ptr, member) \ + container_of(ptr, struct ssh_rtl, member) + +/** + * ssh_rtl_get_device() - Get device associated with request transport layer. + * @rtl: The request transport layer. + * + * Return: Returns the device on which the given request transport layer + * builds upon. + */ +static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) +{ + return ssh_ptl_get_device(&rtl->ptl); +} + +/** + * ssh_request_rtl() - Get request transport layer associated with request. + * @rqst: The request to get the request transport layer reference for. + * + * Return: Returns the &struct ssh_rtl associated with the given SSH request. + */ +static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) +{ + struct ssh_ptl *ptl; + + ptl = READ_ONCE(rqst->packet.ptl); + return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; +} + +int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); +bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); + +int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, + const struct ssh_rtl_ops *ops); + +int ssh_rtl_start(struct ssh_rtl *rtl); +int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); +void ssh_rtl_shutdown(struct ssh_rtl *rtl); +void ssh_rtl_destroy(struct ssh_rtl *rtl); + +int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, + const struct ssh_request_ops *ops); + +#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h new file mode 100644 index 000000000000..eb332bb53ae4 --- /dev/null +++ b/drivers/platform/surface/aggregator/trace.h @@ -0,0 +1,632 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Trace points for SSAM/SSH. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM surface_aggregator + +#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SURFACE_AGGREGATOR_TRACE_H + +#include + +#include +#include + +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); + +TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); + +TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); + +TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); +TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); + +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); + +TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); + +TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); +TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); + +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); + +#define SSAM_PTR_UID_LEN 9 +#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) +#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) +#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) +#define SSAM_SSH_TC_NOT_APPLICABLE 0 + +#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS +#define _SURFACE_AGGREGATOR_TRACE_HELPERS + +/** + * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. + * @ptr: The pointer to convert. + * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. + * + * Converts the given pointer into a UID string that is safe to be shared + * with userspace and logs, i.e. doesn't give away the real memory location. + */ +static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) +{ + char buf[2 * sizeof(void *) + 1]; + + BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN); + + snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); + memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], + SSAM_PTR_UID_LEN); +} + +/** + * ssam_trace_get_packet_seq() - Read the packet's sequence ID. + * @p: The packet. + * + * Return: Returns the packet's sequence ID (SEQ) field if present, or + * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). + */ +static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) + return SSAM_SEQ_NOT_APPLICABLE; + + return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; +} + +/** + * ssam_trace_get_request_id() - Read the packet's request ID. + * @p: The packet. + * + * Return: Returns the packet's request ID (RQID) field if the packet + * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not + * (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_RQID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); +} + +/** + * ssam_trace_get_request_tc() - Read the packet's request target category. + * @p: The packet. + * + * Return: Returns the packet's request target category (TC) field if the + * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TC_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); +} + +#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ + +#define ssam_trace_get_command_field_u8(packet, field) \ + ((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ + ? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) + +#define ssam_show_generic_u8_field(value) \ + __print_symbolic(value, \ + { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_frame_type(ty) \ + __print_symbolic(ty, \ + { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ + { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ + { SSH_FRAME_TYPE_ACK, "ACK" }, \ + { SSH_FRAME_TYPE_NAK, "NAK" } \ + ) + +#define ssam_show_packet_type(type) \ + __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ + { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ + { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ + { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ + ) + +#define ssam_show_packet_state(state) \ + __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ + { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ + { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ + { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ + { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ + { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ + { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ + { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ + { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ + ) + +#define ssam_show_packet_seq(seq) \ + __print_symbolic(seq, \ + { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_request_type(flags) \ + __print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \ + { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ + { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ + ) + +#define ssam_show_request_state(flags) \ + __print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \ + { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ + { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ + { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ + { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ + { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ + { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ + { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ + { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ + ) + +#define ssam_show_request_id(rqid) \ + __print_symbolic(rqid, \ + { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_ssh_tc(rqid) \ + __print_symbolic(rqid, \ + { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TC_SAM, "SAM" }, \ + { SSAM_SSH_TC_BAT, "BAT" }, \ + { SSAM_SSH_TC_TMP, "TMP" }, \ + { SSAM_SSH_TC_PMC, "PMC" }, \ + { SSAM_SSH_TC_FAN, "FAN" }, \ + { SSAM_SSH_TC_PoM, "PoM" }, \ + { SSAM_SSH_TC_DBG, "DBG" }, \ + { SSAM_SSH_TC_KBD, "KBD" }, \ + { SSAM_SSH_TC_FWU, "FWU" }, \ + { SSAM_SSH_TC_UNI, "UNI" }, \ + { SSAM_SSH_TC_LPC, "LPC" }, \ + { SSAM_SSH_TC_TCL, "TCL" }, \ + { SSAM_SSH_TC_SFL, "SFL" }, \ + { SSAM_SSH_TC_KIP, "KIP" }, \ + { SSAM_SSH_TC_EXT, "EXT" }, \ + { SSAM_SSH_TC_BLD, "BLD" }, \ + { SSAM_SSH_TC_BAS, "BAS" }, \ + { SSAM_SSH_TC_SEN, "SEN" }, \ + { SSAM_SSH_TC_SRQ, "SRQ" }, \ + { SSAM_SSH_TC_MCU, "MCU" }, \ + { SSAM_SSH_TC_HID, "HID" }, \ + { SSAM_SSH_TC_TCH, "TCH" }, \ + { SSAM_SSH_TC_BKL, "BKL" }, \ + { SSAM_SSH_TC_TAM, "TAM" }, \ + { SSAM_SSH_TC_ACC, "ACC" }, \ + { SSAM_SSH_TC_UFI, "UFI" }, \ + { SSAM_SSH_TC_USC, "USC" }, \ + { SSAM_SSH_TC_PEN, "PEN" }, \ + { SSAM_SSH_TC_VID, "VID" }, \ + { SSAM_SSH_TC_AUD, "AUD" }, \ + { SSAM_SSH_TC_SMC, "SMC" }, \ + { SSAM_SSH_TC_KPD, "KPD" }, \ + { SSAM_SSH_TC_REG, "REG" } \ + ) + +DECLARE_EVENT_CLASS(ssam_frame_class, + TP_PROTO(const struct ssh_frame *frame), + + TP_ARGS(frame), + + TP_STRUCT__entry( + __field(u8, type) + __field(u8, seq) + __field(u16, len) + ), + + TP_fast_assign( + __entry->type = frame->type; + __entry->seq = frame->seq; + __entry->len = get_unaligned_le16(&frame->len); + ), + + TP_printk("ty=%s, seq=%#04x, len=%u", + ssam_show_frame_type(__entry->type), + __entry->seq, + __entry->len + ) +); + +#define DEFINE_SSAM_FRAME_EVENT(name) \ + DEFINE_EVENT(ssam_frame_class, ssam_##name, \ + TP_PROTO(const struct ssh_frame *frame), \ + TP_ARGS(frame) \ + ) + +DECLARE_EVENT_CLASS(ssam_command_class, + TP_PROTO(const struct ssh_command *cmd, u16 len), + + TP_ARGS(cmd, len), + + TP_STRUCT__entry( + __field(u16, rqid) + __field(u16, len) + __field(u8, tc) + __field(u8, cid) + __field(u8, iid) + ), + + TP_fast_assign( + __entry->rqid = get_unaligned_le16(&cmd->rqid); + __entry->tc = cmd->tc; + __entry->cid = cmd->cid; + __entry->iid = cmd->iid; + __entry->len = len; + ), + + TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", + __entry->rqid, + ssam_show_ssh_tc(__entry->tc), + __entry->cid, + __entry->iid, + __entry->len + ) +); + +#define DEFINE_SSAM_COMMAND_EVENT(name) \ + DEFINE_EVENT(ssam_command_class, ssam_##name, \ + TP_PROTO(const struct ssh_command *cmd, u16 len), \ + TP_ARGS(cmd, len) \ + ) + +DECLARE_EVENT_CLASS(ssam_packet_class, + TP_PROTO(const struct ssh_packet *packet), + + TP_ARGS(packet), + + TP_STRUCT__entry( + __field(unsigned long, state) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, priority) + __field(u16, length) + __field(u16, seq) + ), + + TP_fast_assign( + __entry->state = READ_ONCE(packet->state); + ssam_trace_ptr_uid(packet, __entry->uid); + __entry->priority = READ_ONCE(packet->priority); + __entry->length = packet->data.len; + __entry->seq = ssam_trace_get_packet_seq(packet); + ), + + TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s", + __entry->uid, + ssam_show_packet_seq(__entry->seq), + ssam_show_packet_type(__entry->state), + __entry->priority, + __entry->length, + ssam_show_packet_state(__entry->state) + ) +); + +#define DEFINE_SSAM_PACKET_EVENT(name) \ + DEFINE_EVENT(ssam_packet_class, ssam_##name, \ + TP_PROTO(const struct ssh_packet *packet), \ + TP_ARGS(packet) \ + ) + +DECLARE_EVENT_CLASS(ssam_packet_status_class, + TP_PROTO(const struct ssh_packet *packet, int status), + + TP_ARGS(packet, status), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(int, status) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, priority) + __field(u16, length) + __field(u16, seq) + ), + + TP_fast_assign( + __entry->state = READ_ONCE(packet->state); + __entry->status = status; + ssam_trace_ptr_uid(packet, __entry->uid); + __entry->priority = READ_ONCE(packet->priority); + __entry->length = packet->data.len; + __entry->seq = ssam_trace_get_packet_seq(packet); + ), + + TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d", + __entry->uid, + ssam_show_packet_seq(__entry->seq), + ssam_show_packet_type(__entry->state), + __entry->priority, + __entry->length, + ssam_show_packet_state(__entry->state), + __entry->status + ) +); + +#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ + DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ + TP_PROTO(const struct ssh_packet *packet, int status), \ + TP_ARGS(packet, status) \ + ) + +DECLARE_EVENT_CLASS(ssam_request_class, + TP_PROTO(const struct ssh_request *request), + + TP_ARGS(request), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(u32, rqid) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, tc) + __field(u16, cid) + __field(u16, iid) + ), + + TP_fast_assign( + const struct ssh_packet *p = &request->packet; + + /* Use packet for UID so we can match requests to packets. */ + __entry->state = READ_ONCE(request->state); + __entry->rqid = ssam_trace_get_request_id(p); + ssam_trace_ptr_uid(p, __entry->uid); + __entry->tc = ssam_trace_get_request_tc(p); + __entry->cid = ssam_trace_get_command_field_u8(p, cid); + __entry->iid = ssam_trace_get_command_field_u8(p, iid); + ), + + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", + __entry->uid, + ssam_show_request_id(__entry->rqid), + ssam_show_request_type(__entry->state), + ssam_show_request_state(__entry->state), + ssam_show_ssh_tc(__entry->tc), + ssam_show_generic_u8_field(__entry->cid), + ssam_show_generic_u8_field(__entry->iid) + ) +); + +#define DEFINE_SSAM_REQUEST_EVENT(name) \ + DEFINE_EVENT(ssam_request_class, ssam_##name, \ + TP_PROTO(const struct ssh_request *request), \ + TP_ARGS(request) \ + ) + +DECLARE_EVENT_CLASS(ssam_request_status_class, + TP_PROTO(const struct ssh_request *request, int status), + + TP_ARGS(request, status), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(u32, rqid) + __field(int, status) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, tc) + __field(u16, cid) + __field(u16, iid) + ), + + TP_fast_assign( + const struct ssh_packet *p = &request->packet; + + /* Use packet for UID so we can match requests to packets. */ + __entry->state = READ_ONCE(request->state); + __entry->rqid = ssam_trace_get_request_id(p); + __entry->status = status; + ssam_trace_ptr_uid(p, __entry->uid); + __entry->tc = ssam_trace_get_request_tc(p); + __entry->cid = ssam_trace_get_command_field_u8(p, cid); + __entry->iid = ssam_trace_get_command_field_u8(p, iid); + ), + + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", + __entry->uid, + ssam_show_request_id(__entry->rqid), + ssam_show_request_type(__entry->state), + ssam_show_request_state(__entry->state), + ssam_show_ssh_tc(__entry->tc), + ssam_show_generic_u8_field(__entry->cid), + ssam_show_generic_u8_field(__entry->iid), + __entry->status + ) +); + +#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ + DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ + TP_PROTO(const struct ssh_request *request, int status),\ + TP_ARGS(request, status) \ + ) + +DECLARE_EVENT_CLASS(ssam_alloc_class, + TP_PROTO(void *ptr, size_t len), + + TP_ARGS(ptr, len), + + TP_STRUCT__entry( + __field(size_t, len) + __array(char, uid, SSAM_PTR_UID_LEN) + ), + + TP_fast_assign( + __entry->len = len; + ssam_trace_ptr_uid(ptr, __entry->uid); + ), + + TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) +); + +#define DEFINE_SSAM_ALLOC_EVENT(name) \ + DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ + TP_PROTO(void *ptr, size_t len), \ + TP_ARGS(ptr, len) \ + ) + +DECLARE_EVENT_CLASS(ssam_free_class, + TP_PROTO(void *ptr), + + TP_ARGS(ptr), + + TP_STRUCT__entry( + __array(char, uid, SSAM_PTR_UID_LEN) + ), + + TP_fast_assign( + ssam_trace_ptr_uid(ptr, __entry->uid); + ), + + TP_printk("uid=%s", __entry->uid) +); + +#define DEFINE_SSAM_FREE_EVENT(name) \ + DEFINE_EVENT(ssam_free_class, ssam_##name, \ + TP_PROTO(void *ptr), \ + TP_ARGS(ptr) \ + ) + +DECLARE_EVENT_CLASS(ssam_pending_class, + TP_PROTO(unsigned int pending), + + TP_ARGS(pending), + + TP_STRUCT__entry( + __field(unsigned int, pending) + ), + + TP_fast_assign( + __entry->pending = pending; + ), + + TP_printk("pending=%u", __entry->pending) +); + +#define DEFINE_SSAM_PENDING_EVENT(name) \ + DEFINE_EVENT(ssam_pending_class, ssam_##name, \ + TP_PROTO(unsigned int pending), \ + TP_ARGS(pending) \ + ) + +DECLARE_EVENT_CLASS(ssam_data_class, + TP_PROTO(size_t length), + + TP_ARGS(length), + + TP_STRUCT__entry( + __field(size_t, length) + ), + + TP_fast_assign( + __entry->length = length; + ), + + TP_printk("length=%zu", __entry->length) +); + +#define DEFINE_SSAM_DATA_EVENT(name) \ + DEFINE_EVENT(ssam_data_class, ssam_##name, \ + TP_PROTO(size_t length), \ + TP_ARGS(length) \ + ) + +DEFINE_SSAM_FRAME_EVENT(rx_frame_received); +DEFINE_SSAM_COMMAND_EVENT(rx_response_received); +DEFINE_SSAM_COMMAND_EVENT(rx_event_received); + +DEFINE_SSAM_PACKET_EVENT(packet_release); +DEFINE_SSAM_PACKET_EVENT(packet_submit); +DEFINE_SSAM_PACKET_EVENT(packet_resubmit); +DEFINE_SSAM_PACKET_EVENT(packet_timeout); +DEFINE_SSAM_PACKET_EVENT(packet_cancel); +DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); +DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap); + +DEFINE_SSAM_REQUEST_EVENT(request_submit); +DEFINE_SSAM_REQUEST_EVENT(request_timeout); +DEFINE_SSAM_REQUEST_EVENT(request_cancel); +DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); +DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); + +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); +DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); +DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); +DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn); +DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); +DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); + +DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); +DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); + +DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); +DEFINE_SSAM_FREE_EVENT(event_item_free); + +#endif /* _SURFACE_AGGREGATOR_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE + +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 130b6f52a600..fcd1d4fb94d5 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -57,12 +57,16 @@ static DEFINE_MUTEX(s3_wmi_lock); static int s3_wmi_query_block(const char *guid, int instance, int *ret) { struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj = NULL; acpi_status status; - union acpi_object *obj; int error = 0; mutex_lock(&s3_wmi_lock); status = wmi_query_block(guid, instance, &output); + if (ACPI_FAILURE(status)) { + error = -EIO; + goto out_free_unlock; + } obj = output.pointer; diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c new file mode 100644 index 000000000000..ef9c1f8e8336 --- /dev/null +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Surface ACPI Notify (SAN) interface/shim. + * + * Translates communication from ACPI to Surface System Aggregator Module + * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM + * events back to ACPI notifications. Allows handling of discrete GPU + * notifications sent from ACPI via the SAN interface by providing them to any + * registered external driver. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct san_data { + struct device *dev; + struct ssam_controller *ctrl; + + struct acpi_connection_info info; + + struct ssam_event_notifier nf_bat; + struct ssam_event_notifier nf_tmp; +}; + +#define to_san_data(ptr, member) \ + container_of(ptr, struct san_data, member) + + +/* -- dGPU notifier interface. ---------------------------------------------- */ + +struct san_rqsg_if { + struct rw_semaphore lock; + struct device *dev; + struct blocking_notifier_head nh; +}; + +static struct san_rqsg_if san_rqsg_if = { + .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), + .dev = NULL, + .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), +}; + +static int san_set_rqsg_interface_device(struct device *dev) +{ + int status = 0; + + down_write(&san_rqsg_if.lock); + if (!san_rqsg_if.dev && dev) + san_rqsg_if.dev = dev; + else + status = -EBUSY; + up_write(&san_rqsg_if.lock); + + return status; +} + +/** + * san_client_link() - Link client as consumer to SAN device. + * @client: The client to link. + * + * Sets up a device link between the provided client device as consumer and + * the SAN device as provider. This function can be used to ensure that the + * SAN interface has been set up and will be set up for as long as the driver + * of the client device is bound. This guarantees that, during that time, all + * dGPU events will be received by any registered notifier. + * + * The link will be automatically removed once the client device's driver is + * unbound. + * + * Return: Returns zero on success, %-ENXIO if the SAN interface has not been + * set up yet, and %-ENOMEM if device link creation failed. + */ +int san_client_link(struct device *client) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; + struct device_link *link; + + down_read(&san_rqsg_if.lock); + + if (!san_rqsg_if.dev) { + up_read(&san_rqsg_if.lock); + return -ENXIO; + } + + link = device_link_add(client, san_rqsg_if.dev, flags); + if (!link) { + up_read(&san_rqsg_if.lock); + return -ENOMEM; + } + + if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { + up_read(&san_rqsg_if.lock); + return -ENXIO; + } + + up_read(&san_rqsg_if.lock); + return 0; +} +EXPORT_SYMBOL_GPL(san_client_link); + +/** + * san_dgpu_notifier_register() - Register a SAN dGPU notifier. + * @nb: The notifier-block to register. + * + * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from + * ACPI. The registered notifier will be called with &struct san_dgpu_event + * as notifier data and the command ID of that event as notifier action. + */ +int san_dgpu_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); +} +EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); + +/** + * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. + * @nb: The notifier-block to unregister. + */ +int san_dgpu_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); +} +EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); + +static int san_dgpu_notifier_call(struct san_dgpu_event *evt) +{ + int ret; + + ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); + return notifier_to_errno(ret); +} + + +/* -- ACPI _DSM event relay. ------------------------------------------------ */ + +#define SAN_DSM_REVISION 0 + +/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ +static const guid_t SAN_DSM_UUID = + GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, + 0x48, 0x7c, 0x91, 0xab, 0x3c); + +enum san_dsm_event_fn { + SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, + SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, + SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, + SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, + SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, + SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, + SAN_DSM_EVENT_FN_THERMAL = 0x09, + SAN_DSM_EVENT_FN_DPTF = 0x0a, +}; + +enum sam_event_cid_bat { + SAM_EVENT_CID_BAT_BIX = 0x15, + SAM_EVENT_CID_BAT_BST = 0x16, + SAM_EVENT_CID_BAT_ADP = 0x17, + SAM_EVENT_CID_BAT_PROT = 0x18, + SAM_EVENT_CID_BAT_DPTF = 0x4f, +}; + +enum sam_event_cid_tmp { + SAM_EVENT_CID_TMP_TRIP = 0x0b, +}; + +struct san_event_work { + struct delayed_work work; + struct device *dev; + struct ssam_event event; /* must be last */ +}; + +static int san_acpi_notify_event(struct device *dev, u64 func, + union acpi_object *param) +{ + acpi_handle san = ACPI_HANDLE(dev); + union acpi_object *obj; + int status = 0; + + if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) + return 0; + + dev_dbg(dev, "notify event %#04llx\n", func); + + obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, + func, param, ACPI_TYPE_BUFFER); + if (!obj) + return -EFAULT; + + if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { + dev_err(dev, "got unexpected result from _DSM\n"); + status = -EPROTO; + } + + ACPI_FREE(obj); + return status; +} + +static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) +{ + int status; + + status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); + if (status) + return status; + + /* + * Ensure that the battery states get updated correctly. When the + * battery is fully charged and an adapter is plugged in, it sometimes + * is not updated correctly, instead showing it as charging. + * Explicitly trigger battery updates to fix this. + */ + + status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); + if (status) + return status; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); +} + +static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) +{ + enum san_dsm_event_fn fn; + + if (event->instance_id == 0x02) + fn = SAN_DSM_EVENT_FN_BAT2_INFO; + else + fn = SAN_DSM_EVENT_FN_BAT1_INFO; + + return san_acpi_notify_event(dev, fn, NULL); +} + +static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) +{ + enum san_dsm_event_fn fn; + + if (event->instance_id == 0x02) + fn = SAN_DSM_EVENT_FN_BAT2_STAT; + else + fn = SAN_DSM_EVENT_FN_BAT1_STAT; + + return san_acpi_notify_event(dev, fn, NULL); +} + +static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) +{ + union acpi_object payload; + + /* + * The Surface ACPI expects a buffer and not a package. It specifically + * checks for ObjectType (Arg3) == 0x03. This will cause a warning in + * acpica/nsarguments.c, but that warning can be safely ignored. + */ + payload.type = ACPI_TYPE_BUFFER; + payload.buffer.length = event->length; + payload.buffer.pointer = (u8 *)&event->data[0]; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); +} + +static unsigned long san_evt_bat_delay(u8 cid) +{ + switch (cid) { + case SAM_EVENT_CID_BAT_ADP: + /* + * Wait for battery state to update before signaling adapter + * change. + */ + return msecs_to_jiffies(5000); + + case SAM_EVENT_CID_BAT_BST: + /* Ensure we do not miss anything important due to caching. */ + return msecs_to_jiffies(2000); + + default: + return 0; + } +} + +static bool san_evt_bat(const struct ssam_event *event, struct device *dev) +{ + int status; + + switch (event->command_id) { + case SAM_EVENT_CID_BAT_BIX: + status = san_evt_bat_bix(dev, event); + break; + + case SAM_EVENT_CID_BAT_BST: + status = san_evt_bat_bst(dev, event); + break; + + case SAM_EVENT_CID_BAT_ADP: + status = san_evt_bat_adp(dev, event); + break; + + case SAM_EVENT_CID_BAT_PROT: + /* + * TODO: Implement support for battery protection status change + * event. + */ + return true; + + case SAM_EVENT_CID_BAT_DPTF: + status = san_evt_bat_dptf(dev, event); + break; + + default: + return false; + } + + if (status) { + dev_err(dev, "error handling power event (cid = %#04x)\n", + event->command_id); + } + + return true; +} + +static void san_evt_bat_workfn(struct work_struct *work) +{ + struct san_event_work *ev; + + ev = container_of(work, struct san_event_work, work.work); + san_evt_bat(&ev->event, ev->dev); + kfree(ev); +} + +static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, + const struct ssam_event *event) +{ + struct san_data *d = to_san_data(nf, nf_bat); + struct san_event_work *work; + unsigned long delay = san_evt_bat_delay(event->command_id); + + if (delay == 0) + return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; + + work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); + if (!work) + return ssam_notifier_from_errno(-ENOMEM); + + INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); + work->dev = d->dev; + + memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); + + schedule_delayed_work(&work->work, delay); + return SSAM_NOTIF_HANDLED; +} + +static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) +{ + union acpi_object param; + + /* + * The Surface ACPI expects an integer and not a package. This will + * cause a warning in acpica/nsarguments.c, but that warning can be + * safely ignored. + */ + param.type = ACPI_TYPE_INTEGER; + param.integer.value = event->instance_id; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); +} + +static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) +{ + int status; + + switch (event->command_id) { + case SAM_EVENT_CID_TMP_TRIP: + status = san_evt_tmp_trip(dev, event); + break; + + default: + return false; + } + + if (status) { + dev_err(dev, "error handling thermal event (cid = %#04x)\n", + event->command_id); + } + + return true; +} + +static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, + const struct ssam_event *event) +{ + struct san_data *d = to_san_data(nf, nf_tmp); + + return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; +} + + +/* -- ACPI GSB OperationRegion handler -------------------------------------- */ + +struct gsb_data_in { + u8 cv; +} __packed; + +struct gsb_data_rqsx { + u8 cv; /* Command value (san_gsb_request_cv). */ + u8 tc; /* Target category. */ + u8 tid; /* Target ID. */ + u8 iid; /* Instance ID. */ + u8 snc; /* Expect-response-flag. */ + u8 cid; /* Command ID. */ + u16 cdl; /* Payload length. */ + u8 pld[]; /* Payload. */ +} __packed; + +struct gsb_data_etwl { + u8 cv; /* Command value (should be 0x02). */ + u8 etw3; /* Unknown. */ + u8 etw4; /* Unknown. */ + u8 msg[]; /* Error message (ASCIIZ). */ +} __packed; + +struct gsb_data_out { + u8 status; /* _SSH communication status. */ + u8 len; /* _SSH payload length. */ + u8 pld[]; /* _SSH payload. */ +} __packed; + +union gsb_buffer_data { + struct gsb_data_in in; /* Common input. */ + struct gsb_data_rqsx rqsx; /* RQSX input. */ + struct gsb_data_etwl etwl; /* ETWL input. */ + struct gsb_data_out out; /* Output. */ +}; + +struct gsb_buffer { + u8 status; /* GSB AttribRawProcess status. */ + u8 len; /* GSB AttribRawProcess length. */ + union gsb_buffer_data data; +} __packed; + +#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) +#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) + +#define SAN_GSB_COMMAND 0 + +enum san_gsb_request_cv { + SAN_GSB_REQUEST_CV_RQST = 0x01, + SAN_GSB_REQUEST_CV_ETWL = 0x02, + SAN_GSB_REQUEST_CV_RQSG = 0x03, +}; + +#define SAN_REQUEST_NUM_TRIES 5 + +static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) +{ + struct gsb_data_etwl *etwl = &b->data.etwl; + + if (b->len < sizeof(struct gsb_data_etwl)) { + dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); + return AE_OK; + } + + dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, + (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), + (char *)etwl->msg); + + /* Indicate success. */ + b->status = 0x00; + b->len = 0x00; + + return AE_OK; +} + +static +struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, + struct gsb_buffer *b) +{ + struct gsb_data_rqsx *rqsx = &b->data.rqsx; + + if (b->len < sizeof(struct gsb_data_rqsx)) { + dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); + return NULL; + } + + if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { + dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", + type, b->len, get_unaligned(&rqsx->cdl)); + return NULL; + } + + if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { + dev_err(dev, "payload for %s package too large (cdl = %d)\n", + type, get_unaligned(&rqsx->cdl)); + return NULL; + } + + return rqsx; +} + +static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) +{ + gsb->status = 0x00; + gsb->len = 0x02; + gsb->data.out.status = (u8)(-status); + gsb->data.out.len = 0x00; +} + +static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) +{ + gsb->status = 0x00; + gsb->len = len + 2; + gsb->data.out.status = 0x00; + gsb->data.out.len = len; + + if (len) + memcpy(&gsb->data.out.pld[0], ptr, len); +} + +static acpi_status san_rqst_fixup_suspended(struct san_data *d, + struct ssam_request *rqst, + struct gsb_buffer *gsb) +{ + if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { + u8 base_state = 1; + + /* Base state quirk: + * The base state may be queried from ACPI when the EC is still + * suspended. In this case it will return '-EPERM'. This query + * will only be triggered from the ACPI lid GPE interrupt, thus + * we are either in laptop or studio mode (base status 0x01 or + * 0x02). Furthermore, we will only get here if the device (and + * EC) have been suspended. + * + * We now assume that the device is in laptop mode (0x01). This + * has the drawback that it will wake the device when unfolding + * it in studio mode, but it also allows us to avoid actively + * waiting for the EC to wake up, which may incur a notable + * delay. + */ + + dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); + + gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); + return AE_OK; + } + + gsb_rqsx_response_error(gsb, -ENXIO); + return AE_OK; +} + +static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) +{ + u8 rspbuf[SAN_GSB_MAX_RESPONSE]; + struct gsb_data_rqsx *gsb_rqst; + struct ssam_request rqst; + struct ssam_response rsp; + int status = 0; + + gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); + if (!gsb_rqst) + return AE_OK; + + rqst.target_category = gsb_rqst->tc; + rqst.target_id = gsb_rqst->tid; + rqst.command_id = gsb_rqst->cid; + rqst.instance_id = gsb_rqst->iid; + rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; + rqst.length = get_unaligned(&gsb_rqst->cdl); + rqst.payload = &gsb_rqst->pld[0]; + + rsp.capacity = ARRAY_SIZE(rspbuf); + rsp.length = 0; + rsp.pointer = &rspbuf[0]; + + /* Handle suspended device. */ + if (d->dev->power.is_suspended) { + dev_warn(d->dev, "rqst: device is suspended, not executing\n"); + return san_rqst_fixup_suspended(d, &rqst, buffer); + } + + status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, + d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); + + if (!status) { + gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); + } else { + dev_err(d->dev, "rqst: failed with error %d\n", status); + gsb_rqsx_response_error(buffer, status); + } + + return AE_OK; +} + +static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) +{ + struct gsb_data_rqsx *gsb_rqsg; + struct san_dgpu_event evt; + int status; + + gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); + if (!gsb_rqsg) + return AE_OK; + + evt.category = gsb_rqsg->tc; + evt.target = gsb_rqsg->tid; + evt.command = gsb_rqsg->cid; + evt.instance = gsb_rqsg->iid; + evt.length = get_unaligned(&gsb_rqsg->cdl); + evt.payload = &gsb_rqsg->pld[0]; + + status = san_dgpu_notifier_call(&evt); + if (!status) { + gsb_rqsx_response_success(buffer, NULL, 0); + } else { + dev_err(d->dev, "rqsg: failed with error %d\n", status); + gsb_rqsx_response_error(buffer, status); + } + + return AE_OK; +} + +static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, void *opreg_context, + void *region_context) +{ + struct san_data *d = to_san_data(opreg_context, info); + struct gsb_buffer *buffer = (struct gsb_buffer *)value64; + int accessor_type = (function & 0xFFFF0000) >> 16; + + if (command != SAN_GSB_COMMAND) { + dev_warn(d->dev, "unsupported command: %#04llx\n", command); + return AE_OK; + } + + if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { + dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); + return AE_OK; + } + + /* Buffer must have at least contain the command-value. */ + if (buffer->len == 0) { + dev_err(d->dev, "request-package too small\n"); + return AE_OK; + } + + switch (buffer->data.in.cv) { + case SAN_GSB_REQUEST_CV_RQST: + return san_rqst(d, buffer); + + case SAN_GSB_REQUEST_CV_ETWL: + return san_etwl(d, buffer); + + case SAN_GSB_REQUEST_CV_RQSG: + return san_rqsg(d, buffer); + + default: + dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", + buffer->data.in.cv); + return AE_OK; + } +} + + +/* -- Driver setup. --------------------------------------------------------- */ + +static int san_events_register(struct platform_device *pdev) +{ + struct san_data *d = platform_get_drvdata(pdev); + int status; + + d->nf_bat.base.priority = 1; + d->nf_bat.base.fn = san_evt_bat_nf; + d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; + d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; + d->nf_bat.event.id.instance = 0; + d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; + d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; + + d->nf_tmp.base.priority = 1; + d->nf_tmp.base.fn = san_evt_tmp_nf; + d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; + d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; + d->nf_tmp.event.id.instance = 0; + d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; + d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; + + status = ssam_notifier_register(d->ctrl, &d->nf_bat); + if (status) + return status; + + status = ssam_notifier_register(d->ctrl, &d->nf_tmp); + if (status) + ssam_notifier_unregister(d->ctrl, &d->nf_bat); + + return status; +} + +static void san_events_unregister(struct platform_device *pdev) +{ + struct san_data *d = platform_get_drvdata(pdev); + + ssam_notifier_unregister(d->ctrl, &d->nf_bat); + ssam_notifier_unregister(d->ctrl, &d->nf_tmp); +} + +#define san_consumer_printk(level, dev, handle, fmt, ...) \ +do { \ + char *path = ""; \ + struct acpi_buffer buffer = { \ + .length = ACPI_ALLOCATE_BUFFER, \ + .pointer = NULL, \ + }; \ + \ + if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ + path = buffer.pointer; \ + \ + dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ + kfree(buffer.pointer); \ +} while (0) + +#define san_consumer_dbg(dev, handle, fmt, ...) \ + san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) + +#define san_consumer_warn(dev, handle, fmt, ...) \ + san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) + +static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) +{ + struct acpi_handle_list dep_devices; + acpi_handle supplier = ACPI_HANDLE(&pdev->dev); + acpi_status status; + int i; + + if (!acpi_has_method(handle, "_DEP")) + return false; + + status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); + if (ACPI_FAILURE(status)) { + san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); + return false; + } + + for (i = 0; i < dep_devices.count; i++) { + if (dep_devices.handles[i] == supplier) + return true; + } + + return false; +} + +static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; + struct platform_device *pdev = context; + struct acpi_device *adev; + struct device_link *link; + + if (!is_san_consumer(pdev, handle)) + return AE_OK; + + /* Ignore ACPI devices that are not present. */ + if (acpi_bus_get_device(handle, &adev) != 0) + return AE_OK; + + san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); + + /* Try to set up device links, ignore but log errors. */ + link = device_link_add(&adev->dev, &pdev->dev, flags); + if (!link) { + san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); + return AE_OK; + } + + return AE_OK; +} + +static int san_consumer_links_setup(struct platform_device *pdev) +{ + acpi_status status; + + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, san_consumer_setup, NULL, + pdev, NULL); + + return status ? -EFAULT : 0; +} + +static int san_probe(struct platform_device *pdev) +{ + acpi_handle san = ACPI_HANDLE(&pdev->dev); + struct ssam_controller *ctrl; + struct san_data *data; + acpi_status astatus; + int status; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + status = san_consumer_links_setup(pdev); + if (status) + return status; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &pdev->dev; + data->ctrl = ctrl; + + platform_set_drvdata(pdev, data); + + astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler, NULL, + &data->info); + if (ACPI_FAILURE(astatus)) + return -ENXIO; + + status = san_events_register(pdev); + if (status) + goto err_enable_events; + + status = san_set_rqsg_interface_device(&pdev->dev); + if (status) + goto err_install_dev; + + acpi_walk_dep_device_list(san); + return 0; + +err_install_dev: + san_events_unregister(pdev); +err_enable_events: + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler); + return status; +} + +static int san_remove(struct platform_device *pdev) +{ + acpi_handle san = ACPI_HANDLE(&pdev->dev); + + san_set_rqsg_interface_device(NULL); + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler); + san_events_unregister(pdev); + + /* + * We have unregistered our event sources. Now we need to ensure that + * all delayed works they may have spawned are run to completion. + */ + flush_scheduled_work(); + + return 0; +} + +static const struct acpi_device_id san_match[] = { + { "MSHW0091" }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, san_match); + +static struct platform_driver surface_acpi_notify = { + .probe = san_probe, + .remove = san_remove, + .driver = { + .name = "surface_acpi_notify", + .acpi_match_table = san_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(surface_acpi_notify); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c new file mode 100644 index 000000000000..79e28fab7e40 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator + * misc device. Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + +struct ssam_cdev { + struct kref kref; + struct rw_semaphore lock; + struct ssam_controller *ctrl; + struct miscdevice mdev; +}; + +static void __ssam_cdev_release(struct kref *kref) +{ + kfree(container_of(kref, struct ssam_cdev, kref)); +} + +static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) +{ + if (cdev) + kref_get(&cdev->kref); + + return cdev; +} + +static void ssam_cdev_put(struct ssam_cdev *cdev) +{ + if (cdev) + kref_put(&cdev->kref, __ssam_cdev_release); +} + +static int ssam_cdev_device_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + + filp->private_data = ssam_cdev_get(cdev); + return stream_open(inode, filp); +} + +static int ssam_cdev_device_release(struct inode *inode, struct file *filp) +{ + ssam_cdev_put(filp->private_data); + return 0; +} + +static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) +{ + struct ssam_cdev_request __user *r; + struct ssam_cdev_request rqst; + struct ssam_request spec = {}; + struct ssam_response rsp = {}; + const void __user *plddata; + void __user *rspdata; + int status = 0, ret = 0, tmp; + + r = (struct ssam_cdev_request __user *)arg; + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; + + plddata = u64_to_user_ptr(rqst.payload.data); + rspdata = u64_to_user_ptr(rqst.response.data); + + /* Setup basic request fields. */ + spec.target_category = rqst.target_category; + spec.target_id = rqst.target_id; + spec.command_id = rqst.command_id; + spec.instance_id = rqst.instance_id; + spec.flags = 0; + spec.length = rqst.payload.length; + spec.payload = NULL; + + if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) + spec.flags |= SSAM_REQUEST_HAS_RESPONSE; + + if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) + spec.flags |= SSAM_REQUEST_UNSEQUENCED; + + rsp.capacity = rqst.response.length; + rsp.length = 0; + rsp.pointer = NULL; + + /* Get request payload from user-space. */ + if (spec.length) { + if (!plddata) { + ret = -EINVAL; + goto out; + } + + /* + * Note: spec.length is limited to U16_MAX bytes via struct + * ssam_cdev_request. This is slightly larger than the + * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the + * underlying protocol (note that nothing remotely this size + * should ever be allocated in any normal case). This size is + * validated later in ssam_request_sync(), for allocation the + * bound imposed by u16 should be enough. + */ + spec.payload = kzalloc(spec.length, GFP_KERNEL); + if (!spec.payload) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user((void *)spec.payload, plddata, spec.length)) { + ret = -EFAULT; + goto out; + } + } + + /* Allocate response buffer. */ + if (rsp.capacity) { + if (!rspdata) { + ret = -EINVAL; + goto out; + } + + /* + * Note: rsp.capacity is limited to U16_MAX bytes via struct + * ssam_cdev_request. This is slightly larger than the + * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the + * underlying protocol (note that nothing remotely this size + * should ever be allocated in any normal case). In later use, + * this capacity does not have to be strictly bounded, as it + * is only used as an output buffer to be written to. For + * allocation the bound imposed by u16 should be enough. + */ + rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); + if (!rsp.pointer) { + ret = -ENOMEM; + goto out; + } + } + + /* Perform request. */ + status = ssam_request_sync(cdev->ctrl, &spec, &rsp); + if (status) + goto out; + + /* Copy response to user-space. */ + if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) + ret = -EFAULT; + +out: + /* Always try to set response-length and status. */ + tmp = put_user(rsp.length, &r->response.length); + if (tmp) + ret = tmp; + + tmp = put_user(status, &r->status); + if (tmp) + ret = tmp; + + /* Cleanup. */ + kfree(spec.payload); + kfree(rsp.pointer); + + return ret; +} + +static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case SSAM_CDEV_REQUEST: + return ssam_cdev_request(cdev, arg); + + default: + return -ENOTTY; + } +} + +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ssam_cdev *cdev = file->private_data; + long status; + + /* Ensure that controller is valid for as long as we need it. */ + if (down_read_killable(&cdev->lock)) + return -ERESTARTSYS; + + if (!cdev->ctrl) { + up_read(&cdev->lock); + return -ENODEV; + } + + status = __ssam_cdev_device_ioctl(cdev, cmd, arg); + + up_read(&cdev->lock); + return status; +} + +static const struct file_operations ssam_controller_fops = { + .owner = THIS_MODULE, + .open = ssam_cdev_device_open, + .release = ssam_cdev_device_release, + .unlocked_ioctl = ssam_cdev_device_ioctl, + .compat_ioctl = ssam_cdev_device_ioctl, + .llseek = noop_llseek, +}; + +static int ssam_dbg_device_probe(struct platform_device *pdev) +{ + struct ssam_controller *ctrl; + struct ssam_cdev *cdev; + int status; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + kref_init(&cdev->kref); + init_rwsem(&cdev->lock); + cdev->ctrl = ctrl; + + cdev->mdev.parent = &pdev->dev; + cdev->mdev.minor = MISC_DYNAMIC_MINOR; + cdev->mdev.name = "surface_aggregator"; + cdev->mdev.nodename = "surface/aggregator"; + cdev->mdev.fops = &ssam_controller_fops; + + status = misc_register(&cdev->mdev); + if (status) { + kfree(cdev); + return status; + } + + platform_set_drvdata(pdev, cdev); + return 0; +} + +static int ssam_dbg_device_remove(struct platform_device *pdev) +{ + struct ssam_cdev *cdev = platform_get_drvdata(pdev); + + misc_deregister(&cdev->mdev); + + /* + * The controller is only guaranteed to be valid for as long as the + * driver is bound. Remove controller so that any lingering open files + * cannot access it any more after we're gone. + */ + down_write(&cdev->lock); + cdev->ctrl = NULL; + up_write(&cdev->lock); + + ssam_cdev_put(cdev); + return 0; +} + +static struct platform_device *ssam_cdev_device; + +static struct platform_driver ssam_cdev_driver = { + .probe = ssam_dbg_device_probe, + .remove = ssam_dbg_device_remove, + .driver = { + .name = SSAM_CDEV_DEVICE_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ssam_debug_init(void) +{ + int status; + + ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, + PLATFORM_DEVID_NONE); + if (!ssam_cdev_device) + return -ENOMEM; + + status = platform_device_add(ssam_cdev_device); + if (status) + goto err_device; + + status = platform_driver_register(&ssam_cdev_driver); + if (status) + goto err_driver; + + return 0; + +err_driver: + platform_device_del(ssam_cdev_device); +err_device: + platform_device_put(ssam_cdev_device); + return status; +} +module_init(ssam_debug_init); + +static void __exit ssam_debug_exit(void) +{ + platform_driver_unregister(&ssam_cdev_driver); + platform_device_unregister(ssam_cdev_device); +} +module_exit(ssam_debug_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c new file mode 100644 index 000000000000..cfcc15cfbacb --- /dev/null +++ b/drivers/platform/surface/surface_hotplug.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Book (2 and later) hot-plug driver. + * + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This + * driver is responsible for out-of-band hot-plug event signaling on these + * devices. It is specifically required when the hot-plug device is in D3cold + * and can thus not generate PCIe hot-plug events itself. + * + * Event signaling is handled via ACPI, which will generate the appropriate + * device-check notifications to be picked up by the PCIe hot-plug driver. + * + * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; +static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; + +static const struct acpi_gpio_mapping shps_acpi_gpios[] = { + { "base_presence-int-gpio", &shps_base_presence_int, 1 }, + { "base_presence-gpio", &shps_base_presence, 1 }, + { "device_power-int-gpio", &shps_device_power_int, 1 }, + { "device_power-gpio", &shps_device_power, 1 }, + { "device_presence-int-gpio", &shps_device_presence_int, 1 }, + { "device_presence-gpio", &shps_device_presence, 1 }, + { }, +}; + +/* 5515a847-ed55-4b27-8352-cd320e10360a */ +static const guid_t shps_dsm_guid = + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); + +#define SHPS_DSM_REVISION 1 + +enum shps_dsm_fn { + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, +}; + +enum shps_irq_type { + /* NOTE: Must be in order of enum shps_dsm_fn above. */ + SHPS_IRQ_TYPE_BASE_PRESENCE = 0, + SHPS_IRQ_TYPE_DEVICE_POWER = 1, + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, + SHPS_NUM_IRQS, +}; + +static const char *const shps_gpio_names[] = { + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", +}; + +struct shps_device { + struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ + struct gpio_desc *gpio[SHPS_NUM_IRQS]; + unsigned int irq[SHPS_NUM_IRQS]; +}; + +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) + +static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) +{ + return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; +} + +static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + union acpi_object *result; + union acpi_object param; + int value; + + mutex_lock(&sdev->lock[type]); + + value = gpiod_get_value_cansleep(sdev->gpio[type]); + if (value < 0) { + mutex_unlock(&sdev->lock[type]); + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); + return; + } + + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = value; + + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, + shps_dsm_fn_for_irq(type), ¶m); + + if (!result) { + dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->type != ACPI_TYPE_BUFFER) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", + type, value); + } + + mutex_unlock(&sdev->lock[type]); + + if (result) + ACPI_FREE(result); +} + +static irqreturn_t shps_handle_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct shps_device *sdev = platform_get_drvdata(pdev); + int type; + + /* Figure out which IRQ we're handling. */ + for (type = 0; type < SHPS_NUM_IRQS; type++) + if (irq == sdev->irq[type]) + break; + + /* We should have found our interrupt, if not: this is a bug. */ + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) + return IRQ_HANDLED; + + /* Forward interrupt to ACPI via DSM. */ + shps_dsm_notify_irq(pdev, type); + return IRQ_HANDLED; +} + +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + struct shps_device *sdev = platform_get_drvdata(pdev); + struct gpio_desc *gpiod; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + const char *irq_name; + const int dsm = shps_dsm_fn_for_irq(type); + int status, irq; + + /* + * Only set up interrupts that we actually need: The Surface Book 3 + * does not have a DSM for base presence, so don't set up an interrupt + * for that. + */ + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); + return 0; + } + + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + irq = gpiod_to_irq(gpiod); + if (irq < 0) + return irq; + + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); + if (!irq_name) + return -ENOMEM; + + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, + flags, irq_name, pdev); + if (status) + return status; + + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); + + sdev->gpio[type] = gpiod; + sdev->irq[type] = irq; + + return 0; +} + +static int surface_hotplug_remove(struct platform_device *pdev) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + int i; + + /* Ensure that IRQs have been fully handled and won't trigger any more. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + disable_irq(sdev->irq[i]); + + mutex_destroy(&sdev->lock[i]); + } + + return 0; +} + +static int surface_hotplug_probe(struct platform_device *pdev) +{ + struct shps_device *sdev; + int status, i; + + /* + * The MSHW0153 device is also present on the Surface Laptop 3, + * however that doesn't have a hot-pluggable PCIe device. It also + * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter + * it out here. + */ + if (gpiod_count(&pdev->dev, NULL) < 0) + return -ENODEV; + + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); + if (status) + return status; + + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + platform_set_drvdata(pdev, sdev); + + /* + * Initialize IRQs so that we can safely call surface_hotplug_remove() + * on errors. + */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; + + /* Set up IRQs. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + mutex_init(&sdev->lock[i]); + + status = shps_setup_irq(pdev, i); + if (status) { + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); + goto err; + } + } + + /* Ensure everything is up-to-date. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + shps_dsm_notify_irq(pdev, i); + + return 0; + +err: + surface_hotplug_remove(pdev); + return status; +} + +static const struct acpi_device_id surface_hotplug_acpi_match[] = { + { "MSHW0153", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); + +static struct platform_driver surface_hotplug_driver = { + .probe = surface_hotplug_probe, + .remove = surface_hotplug_remove, + .driver = { + .name = "surface_hotplug", + .acpi_match_table = surface_hotplug_acpi_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(surface_hotplug_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 91e6176cdfbd..56353e8c792a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -49,18 +49,6 @@ config WMI_BMOF To compile this driver as a module, choose M here: the module will be called wmi-bmof. -config ALIENWARE_WMI - tristate "Alienware Special feature control" - depends on ACPI - depends on LEDS_CLASS - depends on NEW_LEDS - depends on ACPI_WMI - help - This is a driver for controlling Alienware BIOS driven - features. It exposes an interface for controlling the AlienFX - zones on Alienware machines that don't contain a dedicated AlienFX - USB MCU such as the X51 and X51-R2. - config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" depends on ACPI_BATTERY @@ -327,169 +315,7 @@ config EEEPC_WMI If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M here. -config DCDBAS - tristate "Dell Systems Management Base Driver" - depends on X86 - help - The Dell Systems Management Base Driver provides a sysfs interface - for systems management software to perform System Management - Interrupts (SMIs) and Host Control Actions (system power cycle or - power off after OS shutdown) on certain Dell systems. - - See for more details on the driver - and the Dell systems on which Dell systems management software makes - use of this driver. - - Say Y or M here to enable the driver for use by Dell systems - management software such as Dell OpenManage. - -# -# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those -# backends are selected. The "depends" line prevents a configuration -# where DELL_SMBIOS=y while either of those dependencies =m. -# -config DELL_SMBIOS - tristate "Dell SMBIOS driver" - depends on DCDBAS || DCDBAS=n - depends on ACPI_WMI || ACPI_WMI=n - help - This provides support for the Dell SMBIOS calling interface. - If you have a Dell computer you should enable this option. - - Be sure to select at least one backend for it to work properly. - -config DELL_SMBIOS_WMI - bool "Dell SMBIOS driver WMI backend" - default y - depends on ACPI_WMI - select DELL_WMI_DESCRIPTOR - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over ACPI-WMI. - - If you have a Dell computer from >2007 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_SMBIOS_SMM - bool "Dell SMBIOS driver SMM backend" - default y - depends on DCDBAS - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over SMI/SMM. - - If you have a Dell computer from <=2017 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_LAPTOP - tristate "Dell Laptop Extras" - depends on DMI - depends on BACKLIGHT_CLASS_DEVICE - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on RFKILL || RFKILL = n - depends on SERIO_I8042 - depends on DELL_SMBIOS - select POWER_SUPPLY - select LEDS_CLASS - select NEW_LEDS - select LEDS_TRIGGERS - select LEDS_TRIGGER_AUDIO - help - This driver adds support for rfkill and backlight control to Dell - laptops (except for some models covered by the Compal driver). - -config DELL_RBTN - tristate "Dell Airplane Mode Switch driver" - depends on ACPI - depends on INPUT - depends on RFKILL - help - Say Y here if you want to support Dell Airplane Mode Switch ACPI - device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. - This driver register rfkill device or input hotkey device depending - on hardware type (hw switch slider or keyboard toggle button). For - rfkill devices it receive HW switch events and set correct hard - rfkill state. - - To compile this driver as a module, choose M here: the module will - be called dell-rbtn. - -config DELL_RBU - tristate "BIOS update support for DELL systems via sysfs" - depends on X86 - select FW_LOADER - select FW_LOADER_USER_HELPER - help - Say m if you want to have the option of updating the BIOS for your - DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) - supporting application to communicate with the BIOS regarding the new - image for the image update to take effect. - See for more details on the driver. - -config DELL_SMO8800 - tristate "Dell Latitude freefall driver (ACPI SMO88XX)" - depends on ACPI - help - Say Y here if you want to support SMO88XX freefall devices - on Dell Latitude laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-smo8800. - -config DELL_WMI - tristate "Dell WMI notifications" - depends on ACPI_WMI - depends on DMI - depends on INPUT - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on DELL_SMBIOS - select DELL_WMI_DESCRIPTOR - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi. - -config DELL_WMI_SYSMAN - tristate "Dell WMI-based Systems management driver" - depends on ACPI_WMI - depends on DMI - select NLS - help - This driver allows changing BIOS settings on many Dell machines from - 2018 and newer without the use of any additional software. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-sysman. - -config DELL_WMI_DESCRIPTOR - tristate - depends on ACPI_WMI - -config DELL_WMI_AIO - tristate "WMI Hotkeys for Dell All-In-One series" - depends on ACPI_WMI - depends on INPUT - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell - All-In-One machines. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-aio. - -config DELL_WMI_LED - tristate "External LED on Dell Business Netbooks" - depends on LEDS_CLASS - depends on ACPI_WMI - help - This adds support for the Latitude 2100 and similar - notebooks that have an external LED. +source "drivers/platform/x86/dell/Kconfig" config AMILO_RFKILL tristate "Fujitsu-Siemens Amilo rfkill support" @@ -624,7 +450,10 @@ config IDEAPAD_LAPTOP depends on BACKLIGHT_CLASS_DEVICE depends on ACPI_VIDEO || ACPI_VIDEO = n depends on ACPI_WMI || ACPI_WMI = n + depends on ACPI_PLATFORM_PROFILE select INPUT_SPARSEKMAP + select NEW_LEDS + select LEDS_CLASS help This is a driver for Lenovo IdeaPad netbooks contains drivers for rfkill switch, hotkey, fan control and backlight control. @@ -655,6 +484,7 @@ config THINKPAD_ACPI depends on RFKILL || RFKILL = n depends on ACPI_VIDEO || ACPI_VIDEO = n depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_PLATFORM_PROFILE select HWMON select NVRAM select NEW_LEDS @@ -1327,21 +1157,6 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. -config INTEL_MFLD_THERMAL - tristate "Thermal driver for Intel Medfield platform" - depends on MFD_INTEL_MSIC && THERMAL - help - Say Y here to enable thermal driver support for the Intel Medfield - platform. - -config INTEL_MID_POWER_BUTTON - tristate "power button driver for Intel MID platforms" - depends on INTEL_SCU && INPUT - help - This driver handles the power button on the Intel MID platforms. - - If unsure, say N. - config INTEL_MRFLD_PWRBTN tristate "Intel Merrifield Basin Cove power button driver" depends on INTEL_SOC_PMIC_MRFLD @@ -1369,7 +1184,7 @@ config INTEL_PMC_CORE - MPHY/PLL gating status (Sunrisepoint PCH only) config INTEL_PMT_CLASS - tristate "Intel Platform Monitoring Technology (PMT) Class driver" + tristate help The Intel Platform Monitoring Technology (PMT) class driver provides the basic sysfs interface and file hierarchy uses by PMT devices. @@ -1382,6 +1197,7 @@ config INTEL_PMT_CLASS config INTEL_PMT_TELEMETRY tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver" + depends on MFD_INTEL_PMT select INTEL_PMT_CLASS help The Intel Platform Monitory Technology (PMT) Telemetry driver provides @@ -1393,6 +1209,7 @@ config INTEL_PMT_TELEMETRY config INTEL_PMT_CRASHLOG tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver" + depends on MFD_INTEL_PMT select INTEL_PMT_CLASS help The Intel Platform Monitoring Technology (PMT) crashlog driver provides @@ -1439,6 +1256,14 @@ config INTEL_SCU_PLATFORM and SCU (sometimes called PMC as well). The driver currently supports Intel Elkhart Lake and compatible platforms. +config INTEL_SCU_WDT + bool + default INTEL_SCU_PCI + depends on INTEL_MID_WATCHDOG + help + This is a specific platform code to instantiate watchdog device + on ACPI-based Intel MID platforms. + config INTEL_SCU_IPC_UTIL tristate "Intel SCU IPC utility driver" depends on INTEL_SCU diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 581475f59819..60d554073749 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers -obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o @@ -37,20 +36,7 @@ obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o # Dell -obj-$(CONFIG_DCDBAS) += dcdbas.o -obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o -dell-smbios-objs := dell-smbios-base.o -dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o -dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o -obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o -obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o -obj-$(CONFIG_DELL_RBU) += dell_rbu.o -obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o -obj-$(CONFIG_DELL_WMI) += dell-wmi.o -obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o -obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o -obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ +obj-$(CONFIG_X86_PLATFORM_DRIVERS_DELL) += dell/ # Fujitsu obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o @@ -137,8 +123,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o -obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o -obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o @@ -148,6 +132,7 @@ obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o +obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ intel_telemetry_pltdrv.o \ diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index c1a5357da885..85db9403cc14 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -30,7 +30,6 @@ #include #include -ACPI_MODULE_NAME(KBUILD_MODNAME); MODULE_AUTHOR("Carlos Corbacho"); MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); MODULE_LICENSE("GPL"); @@ -1605,7 +1604,8 @@ static void acer_kbd_dock_get_initial_state(void) status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf); if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Error getting keyboard-dock initial status")); + pr_err("Error getting keyboard-dock initial status: %s\n", + acpi_format_exception(status)); return; } diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index b6aa6e5514f4..6b8b3ab8db48 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -336,7 +336,8 @@ static void acerhdf_check_param(struct thermal_zone_device *thermal) pr_notice("interval changed to: %d\n", interval); if (thermal) - thermal->polling_delay = interval*1000; + thermal->polling_delay_jiffies = + round_jiffies(msecs_to_jiffies(interval * 1000)); prev_interval = interval; } diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index ef8342572463..b9da58ee9b1e 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -210,31 +210,39 @@ static int amd_pmc_probe(struct platform_device *pdev) dev->dev = &pdev->dev; rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); - if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) + if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) { + pci_dev_put(rdev); return -ENODEV; + } dev->cpu_id = rdev->device; err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO); if (err) { dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + pci_dev_put(rdev); return pcibios_err_to_errno(err); } err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); - if (err) + if (err) { + pci_dev_put(rdev); return pcibios_err_to_errno(err); + } base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK; err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI); if (err) { dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + pci_dev_put(rdev); return pcibios_err_to_errno(err); } err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); - if (err) + if (err) { + pci_dev_put(rdev); return pcibios_err_to_errno(err); + } base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK; pci_dev_put(rdev); diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig new file mode 100644 index 000000000000..e0a55337f51a --- /dev/null +++ b/drivers/platform/x86/dell/Kconfig @@ -0,0 +1,207 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Dell X86 Platform Specific Drivers +# + +menuconfig X86_PLATFORM_DRIVERS_DELL + bool "Dell X86 Platform Specific Device Drivers" + default n + depends on X86_PLATFORM_DEVICES + help + Say Y here to get to see options for device drivers for various + Dell x86 platforms, including vendor-specific laptop extension drivers. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if X86_PLATFORM_DRIVERS_DELL + +config ALIENWARE_WMI + tristate "Alienware Special feature control" + default m + depends on ACPI + depends on LEDS_CLASS + depends on NEW_LEDS + depends on ACPI_WMI + help + This is a driver for controlling Alienware BIOS driven + features. It exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated AlienFX + USB MCU such as the X51 and X51-R2. + +config DCDBAS + tristate "Dell Systems Management Base Driver" + default m + depends on X86 + help + The Dell Systems Management Base Driver provides a sysfs interface + for systems management software to perform System Management + Interrupts (SMIs) and Host Control Actions (system power cycle or + power off after OS shutdown) on certain Dell systems. + + See for more details on the driver + and the Dell systems on which Dell systems management software makes + use of this driver. + + Say Y or M here to enable the driver for use by Dell systems + management software such as Dell OpenManage. + +config DELL_LAPTOP + tristate "Dell Laptop Extras" + default m + depends on DMI + depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on RFKILL || RFKILL = n + depends on SERIO_I8042 + depends on DELL_SMBIOS + select POWER_SUPPLY + select LEDS_CLASS + select NEW_LEDS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + help + This driver adds support for rfkill and backlight control to Dell + laptops (except for some models covered by the Compal driver). + +config DELL_RBU + tristate "BIOS update support for DELL systems via sysfs" + default m + depends on X86 + select FW_LOADER + select FW_LOADER_USER_HELPER + help + Say m if you want to have the option of updating the BIOS for your + DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) + supporting application to communicate with the BIOS regarding the new + image for the image update to take effect. + See for more details on the driver. + +config DELL_RBTN + tristate "Dell Airplane Mode Switch driver" + default m + depends on ACPI + depends on INPUT + depends on RFKILL + help + Say Y here if you want to support Dell Airplane Mode Switch ACPI + device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. + This driver register rfkill device or input hotkey device depending + on hardware type (hw switch slider or keyboard toggle button). For + rfkill devices it receive HW switch events and set correct hard + rfkill state. + + To compile this driver as a module, choose M here: the module will + be called dell-rbtn. + +# +# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those +# backends are selected. The "depends" line prevents a configuration +# where DELL_SMBIOS=y while either of those dependencies =m. +# +config DELL_SMBIOS + tristate "Dell SMBIOS driver" + default m + depends on DCDBAS || DCDBAS=n + depends on ACPI_WMI || ACPI_WMI=n + help + This provides support for the Dell SMBIOS calling interface. + If you have a Dell computer you should enable this option. + + Be sure to select at least one backend for it to work properly. + +config DELL_SMBIOS_WMI + bool "Dell SMBIOS driver WMI backend" + default y + depends on ACPI_WMI + select DELL_WMI_DESCRIPTOR + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over ACPI-WMI. + + If you have a Dell computer from >2007 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMBIOS_SMM + bool "Dell SMBIOS driver SMM backend" + default y + depends on DCDBAS + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over SMI/SMM. + + If you have a Dell computer from <=2017 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMO8800 + tristate "Dell Latitude freefall driver (ACPI SMO88XX)" + default m + depends on ACPI + help + Say Y here if you want to support SMO88XX freefall devices + on Dell Latitude laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-smo8800. + +config DELL_WMI + tristate "Dell WMI notifications" + default m + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on DELL_SMBIOS + select DELL_WMI_DESCRIPTOR + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi. + +config DELL_WMI_AIO + tristate "WMI Hotkeys for Dell All-In-One series" + default m + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell + All-In-One machines. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-aio. + +config DELL_WMI_DESCRIPTOR + tristate + default m + depends on ACPI_WMI + +config DELL_WMI_LED + tristate "External LED on Dell Business Netbooks" + default m + depends on LEDS_CLASS + depends on ACPI_WMI + help + This adds support for the Latitude 2100 and similar + notebooks that have an external LED. + +config DELL_WMI_SYSMAN + tristate "Dell WMI-based Systems management driver" + default m + depends on ACPI_WMI + depends on DMI + select NLS + help + This driver allows changing BIOS settings on many Dell machines from + 2018 and newer without the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-sysman. + +endif # X86_PLATFORM_DRIVERS_DELL diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile new file mode 100644 index 000000000000..d720a3e42ae3 --- /dev/null +++ b/drivers/platform/x86/dell/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/dell +# Dell x86 Platform-Specific Drivers +# + +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o +obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o +obj-$(CONFIG_DELL_RBU) += dell_rbu.o +obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o +dell-smbios-objs := dell-smbios-base.o +dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o +dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o +obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_WMI) += dell-wmi.o +obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o +obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o +obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c similarity index 100% rename from drivers/platform/x86/alienware-wmi.c rename to drivers/platform/x86/dell/alienware-wmi.c diff --git a/drivers/platform/x86/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c similarity index 100% rename from drivers/platform/x86/dcdbas.c rename to drivers/platform/x86/dell/dcdbas.c diff --git a/drivers/platform/x86/dcdbas.h b/drivers/platform/x86/dell/dcdbas.h similarity index 100% rename from drivers/platform/x86/dcdbas.h rename to drivers/platform/x86/dell/dcdbas.h diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c similarity index 100% rename from drivers/platform/x86/dell-laptop.c rename to drivers/platform/x86/dell/dell-laptop.c diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c similarity index 100% rename from drivers/platform/x86/dell-rbtn.c rename to drivers/platform/x86/dell/dell-rbtn.c diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell/dell-rbtn.h similarity index 100% rename from drivers/platform/x86/dell-rbtn.h rename to drivers/platform/x86/dell/dell-rbtn.h diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c similarity index 100% rename from drivers/platform/x86/dell-smbios-base.c rename to drivers/platform/x86/dell/dell-smbios-base.c diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c similarity index 100% rename from drivers/platform/x86/dell-smbios-smm.c rename to drivers/platform/x86/dell/dell-smbios-smm.c diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c similarity index 100% rename from drivers/platform/x86/dell-smbios-wmi.c rename to drivers/platform/x86/dell/dell-smbios-wmi.c diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h similarity index 100% rename from drivers/platform/x86/dell-smbios.h rename to drivers/platform/x86/dell/dell-smbios.h diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell/dell-smo8800.c similarity index 100% rename from drivers/platform/x86/dell-smo8800.c rename to drivers/platform/x86/dell/dell-smo8800.c diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c similarity index 100% rename from drivers/platform/x86/dell-wmi-aio.c rename to drivers/platform/x86/dell/dell-wmi-aio.c diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c similarity index 100% rename from drivers/platform/x86/dell-wmi-descriptor.c rename to drivers/platform/x86/dell/dell-wmi-descriptor.c diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell/dell-wmi-descriptor.h similarity index 100% rename from drivers/platform/x86/dell-wmi-descriptor.h rename to drivers/platform/x86/dell/dell-wmi-descriptor.h diff --git a/drivers/platform/x86/dell-wmi-led.c b/drivers/platform/x86/dell/dell-wmi-led.c similarity index 100% rename from drivers/platform/x86/dell-wmi-led.c rename to drivers/platform/x86/dell/dell-wmi-led.c diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/Makefile rename to drivers/platform/x86/dell/dell-wmi-sysman/Makefile diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c rename to drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h rename to drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/enum-attributes.c rename to drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/int-attributes.c rename to drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c rename to drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c diff --git a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c rename to drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/string-attributes.c rename to drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c similarity index 100% rename from drivers/platform/x86/dell-wmi-sysman/sysman.c rename to drivers/platform/x86/dell/dell-wmi-sysman/sysman.c diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c similarity index 100% rename from drivers/platform/x86/dell-wmi.c rename to drivers/platform/x86/dell/dell-wmi.c diff --git a/drivers/platform/x86/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c similarity index 100% rename from drivers/platform/x86/dell_rbu.c rename to drivers/platform/x86/dell/dell_rbu.c diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 5b81bafa5c16..6cb5ad4be231 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -8,33 +8,34 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include -#include -#include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include + #include -#define IDEAPAD_RFKILL_DEV_NUM (3) +#include -#define BM_CONSERVATION_BIT (5) -#define HA_FNLOCK_BIT (10) - -#define CFG_BT_BIT (16) -#define CFG_3G_BIT (17) -#define CFG_WIFI_BIT (18) -#define CFG_CAMERA_BIT (19) +#define IDEAPAD_RFKILL_DEV_NUM 3 #if IS_ENABLED(CONFIG_ACPI_WMI) static const char *const ideapad_wmi_fnesc_events[] = { @@ -44,10 +45,39 @@ static const char *const ideapad_wmi_fnesc_events[] = { #endif enum { - BMCMD_CONSERVATION_ON = 3, - BMCMD_CONSERVATION_OFF = 5, - HACMD_FNLOCK_ON = 0xe, - HACMD_FNLOCK_OFF = 0xf, + CFG_CAP_BT_BIT = 16, + CFG_CAP_3G_BIT = 17, + CFG_CAP_WIFI_BIT = 18, + CFG_CAP_CAM_BIT = 19, + CFG_CAP_TOUCHPAD_BIT = 30, +}; + +enum { + GBMD_CONSERVATION_STATE_BIT = 5, +}; + +enum { + SMBC_CONSERVATION_ON = 3, + SMBC_CONSERVATION_OFF = 5, +}; + +enum { + HALS_KBD_BL_SUPPORT_BIT = 4, + HALS_KBD_BL_STATE_BIT = 5, + HALS_USB_CHARGING_SUPPORT_BIT = 6, + HALS_USB_CHARGING_STATE_BIT = 7, + HALS_FNLOCK_SUPPORT_BIT = 9, + HALS_FNLOCK_STATE_BIT = 10, + HALS_HOTKEYS_PRIMARY_BIT = 11, +}; + +enum { + SALS_KBD_BL_ON = 0x8, + SALS_KBD_BL_OFF = 0x9, + SALS_USB_CHARGING_ON = 0xa, + SALS_USB_CHARGING_OFF = 0xb, + SALS_FNLOCK_ON = 0xe, + SALS_FNLOCK_OFF = 0xf, }; enum { @@ -77,6 +107,13 @@ enum { VPCCMD_W_BL_POWER = 0x33, }; +struct ideapad_dytc_priv { + enum platform_profile_option current_profile; + struct platform_profile_handler pprof; + struct mutex mutex; /* protects the DYTC interface */ + struct ideapad_private *priv; +}; + struct ideapad_rfk_priv { int dev; struct ideapad_private *priv; @@ -89,11 +126,25 @@ struct ideapad_private { struct platform_device *platform_device; struct input_dev *inputdev; struct backlight_device *blightdev; + struct ideapad_dytc_priv *dytc; struct dentry *debug; unsigned long cfg; - bool has_hw_rfkill_switch; - bool has_touchpad_switch; const char *fnesc_guid; + struct { + bool conservation_mode : 1; + bool dytc : 1; + bool fan_mode : 1; + bool fn_lock : 1; + bool hw_rfkill_switch : 1; + bool kbd_bl : 1; + bool touchpad_ctrl_via_ec : 1; + bool usb_charging : 1; + } features; + struct { + bool initialized; + struct led_classdev led; + unsigned int last_brightness; + } kbd_bl; }; static bool no_bt_rfkill; @@ -103,64 +154,82 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); /* * ACPI Helpers */ -#define IDEAPAD_EC_TIMEOUT (200) /* in ms */ +#define IDEAPAD_EC_TIMEOUT 200 /* in ms */ -static int read_method_int(acpi_handle handle, const char *method, int *val) +static int eval_int(acpi_handle handle, const char *name, unsigned long *res) { - acpi_status status; unsigned long long result; + acpi_status status; + + status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); + if (ACPI_FAILURE(status)) + return -EIO; + + *res = result; - status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); - if (ACPI_FAILURE(status)) { - *val = -1; - return -1; - } - *val = result; return 0; - } -static int method_gbmd(acpi_handle handle, unsigned long *ret) +static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) { - int result, val; + acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); - result = read_method_int(handle, "GBMD", &val); - *ret = val; - return result; + return ACPI_FAILURE(status) ? -EIO : 0; } -static int method_int1(acpi_handle handle, char *method, int cmd) +static int eval_gbmd(acpi_handle handle, unsigned long *res) { - acpi_status status; - - status = acpi_execute_simple_method(handle, method, cmd); - return ACPI_FAILURE(status) ? -1 : 0; + return eval_int(handle, "GBMD", res); } -static int method_vpcr(acpi_handle handle, int cmd, int *ret) +static int exec_smbc(acpi_handle handle, unsigned long arg) +{ + return exec_simple_method(handle, "SMBC", arg); +} + +static int eval_hals(acpi_handle handle, unsigned long *res) +{ + return eval_int(handle, "HALS", res); +} + +static int exec_sals(acpi_handle handle, unsigned long arg) +{ + return exec_simple_method(handle, "SALS", arg); +} + +static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) { - acpi_status status; - unsigned long long result; struct acpi_object_list params; + unsigned long long result; union acpi_object in_obj; + acpi_status status; params.count = 1; params.pointer = &in_obj; in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = cmd; + in_obj.integer.value = arg; - status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); + status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); + if (ACPI_FAILURE(status)) + return -EIO; + + if (res) + *res = result; - if (ACPI_FAILURE(status)) { - *ret = -1; - return -1; - } - *ret = result; return 0; - } -static int method_vpcw(acpi_handle handle, int cmd, int data) +static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) +{ + return eval_int_with_arg(handle, "DYTC", cmd, res); +} + +static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) +{ + return eval_int_with_arg(handle, "VPCR", cmd, res); +} + +static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) { struct acpi_object_list params; union acpi_object in_obj[2]; @@ -174,55 +243,68 @@ static int method_vpcw(acpi_handle handle, int cmd, int data) in_obj[1].integer.value = data; status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); - if (status != AE_OK) - return -1; + if (ACPI_FAILURE(status)) + return -EIO; + return 0; } -static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) +static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) { - int val; - unsigned long int end_jiffies; + unsigned long end_jiffies, val; + int err; - if (method_vpcw(handle, 1, cmd)) - return -1; + err = eval_vpcw(handle, 1, cmd); + if (err) + return err; - for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; - time_before(jiffies, end_jiffies);) { + end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; + + while (time_before(jiffies, end_jiffies)) { schedule(); - if (method_vpcr(handle, 1, &val)) - return -1; - if (val == 0) { - if (method_vpcr(handle, 0, &val)) - return -1; - *data = val; - return 0; - } + + err = eval_vpcr(handle, 1, &val); + if (err) + return err; + + if (val == 0) + return eval_vpcr(handle, 0, data); } - pr_err("timeout in %s\n", __func__); - return -1; + + acpi_handle_err(handle, "timeout in %s\n", __func__); + + return -ETIMEDOUT; } -static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) +static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) { - int val; - unsigned long int end_jiffies; + unsigned long end_jiffies, val; + int err; - if (method_vpcw(handle, 0, data)) - return -1; - if (method_vpcw(handle, 1, cmd)) - return -1; + err = eval_vpcw(handle, 0, data); + if (err) + return err; - for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; - time_before(jiffies, end_jiffies);) { + err = eval_vpcw(handle, 1, cmd); + if (err) + return err; + + end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; + + while (time_before(jiffies, end_jiffies)) { schedule(); - if (method_vpcr(handle, 1, &val)) - return -1; + + err = eval_vpcr(handle, 1, &val); + if (err) + return err; + if (val == 0) return 0; } - pr_err("timeout in %s\n", __func__); - return -1; + + acpi_handle_err(handle, "timeout in %s\n", __func__); + + return -ETIMEDOUT; } /* @@ -233,44 +315,37 @@ static int debugfs_status_show(struct seq_file *s, void *data) struct ideapad_private *priv = s->private; unsigned long value; - if (!priv) - return -EINVAL; - if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) - seq_printf(s, "Backlight max:\t%lu\n", value); + seq_printf(s, "Backlight max: %lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) - seq_printf(s, "Backlight now:\t%lu\n", value); + seq_printf(s, "Backlight now: %lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) - seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); - seq_printf(s, "=====================\n"); + seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); - if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) - seq_printf(s, "Radio status:\t%s(%lu)\n", - value ? "On" : "Off", value); - if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) - seq_printf(s, "Wifi status:\t%s(%lu)\n", - value ? "On" : "Off", value); - if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) - seq_printf(s, "BT status:\t%s(%lu)\n", - value ? "On" : "Off", value); - if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) - seq_printf(s, "3G status:\t%s(%lu)\n", - value ? "On" : "Off", value); - seq_printf(s, "=====================\n"); - - if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) - seq_printf(s, "Touchpad status:%s(%lu)\n", - value ? "On" : "Off", value); - if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) - seq_printf(s, "Camera status:\t%s(%lu)\n", - value ? "On" : "Off", value); seq_puts(s, "=====================\n"); - if (!method_gbmd(priv->adev->handle, &value)) { - seq_printf(s, "Conservation mode:\t%s(%lu)\n", - test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off", - value); - } + if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) + seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) + seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) + seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) + seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); + + seq_puts(s, "=====================\n"); + + if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) + seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) + seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); + + seq_puts(s, "=====================\n"); + + if (!eval_gbmd(priv->adev->handle, &value)) + seq_printf(s, "GBMD: %#010lx\n", value); + if (!eval_hals(priv->adev->handle, &value)) + seq_printf(s, "HALS: %#010lx\n", value); return 0; } @@ -280,39 +355,41 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) { struct ideapad_private *priv = s->private; - if (!priv) { - seq_printf(s, "cfg: N/A\n"); - } else { - seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", - priv->cfg); - if (test_bit(CFG_BT_BIT, &priv->cfg)) - seq_printf(s, "Bluetooth "); - if (test_bit(CFG_3G_BIT, &priv->cfg)) - seq_printf(s, "3G "); - if (test_bit(CFG_WIFI_BIT, &priv->cfg)) - seq_printf(s, "Wireless "); - if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) - seq_printf(s, "Camera "); - seq_printf(s, "\nGraphic: "); - switch ((priv->cfg)&0x700) { - case 0x100: - seq_printf(s, "Intel"); - break; - case 0x200: - seq_printf(s, "ATI"); - break; - case 0x300: - seq_printf(s, "Nvidia"); - break; - case 0x400: - seq_printf(s, "Intel and ATI"); - break; - case 0x500: - seq_printf(s, "Intel and Nvidia"); - break; - } - seq_printf(s, "\n"); + seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); + + seq_puts(s, "Capabilities:"); + if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) + seq_puts(s, " bluetooth"); + if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) + seq_puts(s, " 3G"); + if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) + seq_puts(s, " wifi"); + if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) + seq_puts(s, " camera"); + if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) + seq_puts(s, " touchpad"); + seq_puts(s, "\n"); + + seq_puts(s, "Graphics: "); + switch (priv->cfg & 0x700) { + case 0x100: + seq_puts(s, "Intel"); + break; + case 0x200: + seq_puts(s, "ATI"); + break; + case 0x300: + seq_puts(s, "Nvidia"); + break; + case 0x400: + seq_puts(s, "Intel and ATI"); + break; + case 0x500: + seq_puts(s, "Intel and Nvidia"); + break; } + seq_puts(s, "\n"); + return 0; } DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); @@ -324,8 +401,8 @@ static void ideapad_debugfs_init(struct ideapad_private *priv) dir = debugfs_create_dir("ideapad", NULL); priv->debug = dir; - debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); - debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); + debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); + debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); } static void ideapad_debugfs_exit(struct ideapad_private *priv) @@ -337,151 +414,130 @@ static void ideapad_debugfs_exit(struct ideapad_private *priv) /* * sysfs */ -static ssize_t show_ideapad_cam(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t camera_power_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long result; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!result); } -static ssize_t store_ideapad_cam(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t camera_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - int ret, state; struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); + if (err) + return err; - if (!count) - return 0; - if (sscanf(buf, "%i", &state) != 1) - return -EINVAL; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); - if (ret < 0) - return -EIO; return count; } -static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); +static DEVICE_ATTR_RW(camera_power); -static ssize_t show_ideapad_fan(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t conservation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long result; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + err = eval_gbmd(priv->adev->handle, &result); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); } -static ssize_t store_ideapad_fan(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t conservation_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - int ret, state; struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF); + if (err) + return err; - if (!count) - return 0; - if (sscanf(buf, "%i", &state) != 1) - return -EINVAL; - if (state < 0 || state > 4 || state == 3) - return -EINVAL; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); - if (ret < 0) - return -EIO; return count; } -static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); +static DEVICE_ATTR_RW(conservation_mode); -static ssize_t touchpad_show(struct device *dev, +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ideapad_private *priv = dev_get_drvdata(dev); unsigned long result; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); + if (err) + return err; + + return sysfs_emit(buf, "%lu\n", result); } -/* Switch to RO for now: It might be revisited in the future */ -static ssize_t __maybe_unused touchpad_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fan_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); - bool state; - int ret; + unsigned int state; + int err; - ret = kstrtobool(buf, &state); - if (ret) - return ret; + err = kstrtouint(buf, 0, &state); + if (err) + return err; + + if (state > 4 || state == 3) + return -EINVAL; + + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); + if (err) + return err; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); - if (ret < 0) - return -EIO; return count; } -static DEVICE_ATTR_RO(touchpad); - -static ssize_t conservation_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned long result; - - if (method_gbmd(priv->adev->handle, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); -} - -static ssize_t conservation_mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ideapad_private *priv = dev_get_drvdata(dev); - bool state; - int ret; - - ret = kstrtobool(buf, &state); - if (ret) - return ret; - - ret = method_int1(priv->adev->handle, "SBMC", state ? - BMCMD_CONSERVATION_ON : - BMCMD_CONSERVATION_OFF); - if (ret < 0) - return -EIO; - return count; -} - -static DEVICE_ATTR_RW(conservation_mode); +static DEVICE_ATTR_RW(fan_mode); static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned long result; - int hals; - int fail = read_method_int(priv->adev->handle, "HALS", &hals); + unsigned long hals; + int err; - if (fail) - return sprintf(buf, "-1\n"); + err = eval_hals(priv->adev->handle, &hals); + if (err) + return err; - result = hals; - return sprintf(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); } static ssize_t fn_lock_store(struct device *dev, @@ -490,56 +546,124 @@ static ssize_t fn_lock_store(struct device *dev, { struct ideapad_private *priv = dev_get_drvdata(dev); bool state; - int ret; + int err; - ret = kstrtobool(buf, &state); - if (ret) - return ret; + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); + if (err) + return err; - ret = method_int1(priv->adev->handle, "SALS", state ? - HACMD_FNLOCK_ON : - HACMD_FNLOCK_OFF); - if (ret < 0) - return -EIO; return count; } static DEVICE_ATTR_RW(fn_lock); +static ssize_t touchpad_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long result; + int err; + + err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!result); +} + +static ssize_t touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(touchpad); + +static ssize_t usb_charging_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long hals; + int err; + + err = eval_hals(priv->adev->handle, &hals); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); +} + +static ssize_t usb_charging_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(usb_charging); static struct attribute *ideapad_attributes[] = { &dev_attr_camera_power.attr, - &dev_attr_fan_mode.attr, - &dev_attr_touchpad.attr, &dev_attr_conservation_mode.attr, + &dev_attr_fan_mode.attr, &dev_attr_fn_lock.attr, + &dev_attr_touchpad.attr, + &dev_attr_usb_charging.attr, NULL }; static umode_t ideapad_is_visible(struct kobject *kobj, - struct attribute *attr, - int idx) + struct attribute *attr, + int idx) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct ideapad_private *priv = dev_get_drvdata(dev); - bool supported; + bool supported = true; if (attr == &dev_attr_camera_power.attr) - supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); - else if (attr == &dev_attr_fan_mode.attr) { - unsigned long value; - supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, - &value); - } else if (attr == &dev_attr_conservation_mode.attr) { - supported = acpi_has_method(priv->adev->handle, "GBMD") && - acpi_has_method(priv->adev->handle, "SBMC"); - } else if (attr == &dev_attr_fn_lock.attr) { - supported = acpi_has_method(priv->adev->handle, "HALS") && - acpi_has_method(priv->adev->handle, "SALS"); - } else if (attr == &dev_attr_touchpad.attr) - supported = priv->has_touchpad_switch; - else - supported = true; + supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); + else if (attr == &dev_attr_conservation_mode.attr) + supported = priv->features.conservation_mode; + else if (attr == &dev_attr_fan_mode.attr) + supported = priv->features.fan_mode; + else if (attr == &dev_attr_fn_lock.attr) + supported = priv->features.fn_lock; + else if (attr == &dev_attr_touchpad.attr) + supported = priv->features.touchpad_ctrl_via_ec && + test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); + else if (attr == &dev_attr_usb_charging.attr) + supported = priv->features.usb_charging; return supported ? attr->mode : 0; } @@ -549,6 +673,265 @@ static const struct attribute_group ideapad_attribute_group = { .attrs = ideapad_attributes }; +/* + * DYTC Platform profile + */ +#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ +#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ +#define DYTC_CMD_GET 2 /* To get current IC function and mode */ +#define DYTC_CMD_RESET 0x1ff /* To reset back to default */ + +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ + +#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ +#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ + +#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ +#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ +#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ + +#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ +#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ + +#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ +#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ + +#define DYTC_SET_COMMAND(function, mode, on) \ + (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ + (mode) << DYTC_SET_MODE_BIT | \ + (on) << DYTC_SET_VALID_BIT) + +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) + +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) + +static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) +{ + switch (dytcmode) { + case DYTC_MODE_LOW_POWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + + return 0; +} + +static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) +{ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + *perfmode = DYTC_MODE_LOW_POWER; + break; + case PLATFORM_PROFILE_BALANCED: + *perfmode = DYTC_MODE_BALANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + *perfmode = DYTC_MODE_PERFORM; + break; + default: /* Unknown profile */ + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * dytc_profile_get: Function to register with platform_profile + * handler. Returns current platform profile. + */ +static int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); + + *profile = dytc->current_profile; + return 0; +} + +/* + * Helper function - check if we are in CQL mode and if we are + * - disable CQL, + * - run the command + * - enable CQL + * If not in CQL mode, just run the command + */ +static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, + unsigned long *output) +{ + int err, cmd_err, cur_funcmode; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); + if (err) + return err; + + cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; + /* Check if we're OK to return immediately */ + if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) + return 0; + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); + if (err) + return err; + } + + cmd_err = eval_dytc(priv->adev->handle, cmd, output); + /* Check return condition after we've restored CQL state */ + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); + if (err) + return err; + } + + return cmd_err; +} + +/* + * dytc_profile_set: Function to register with platform_profile + * handler. Sets current platform profile. + */ +static int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); + struct ideapad_private *priv = dytc->priv; + int err; + + err = mutex_lock_interruptible(&dytc->mutex); + if (err) + return err; + + if (profile == PLATFORM_PROFILE_BALANCED) { + /* To get back to balanced mode we just issue a reset command */ + err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); + if (err) + goto unlock; + } else { + int perfmode; + + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + NULL); + if (err) + goto unlock; + } + + /* Success - update current profile */ + dytc->current_profile = profile; + +unlock: + mutex_unlock(&dytc->mutex); + + return err; +} + +static void dytc_profile_refresh(struct ideapad_private *priv) +{ + enum platform_profile_option profile; + unsigned long output; + int err, perfmode; + + mutex_lock(&priv->dytc->mutex); + err = dytc_cql_command(priv, DYTC_CMD_GET, &output); + mutex_unlock(&priv->dytc->mutex); + if (err) + return; + + perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; + + if (convert_dytc_to_profile(perfmode, &profile)) + return; + + if (profile != priv->dytc->current_profile) { + priv->dytc->current_profile = profile; + platform_profile_notify(); + } +} + +static int ideapad_dytc_profile_init(struct ideapad_private *priv) +{ + int err, dytc_version; + unsigned long output; + + if (!priv->features.dytc) + return -ENODEV; + + err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); + /* For all other errors we can flag the failure */ + if (err) + return err; + + /* Check DYTC is enabled and supports mode setting */ + if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) + return -ENODEV; + + dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + if (dytc_version < 5) + return -ENODEV; + + priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); + if (!priv->dytc) + return -ENOMEM; + + mutex_init(&priv->dytc->mutex); + + priv->dytc->priv = priv; + priv->dytc->pprof.profile_get = dytc_profile_get; + priv->dytc->pprof.profile_set = dytc_profile_set; + + /* Setup supported modes */ + set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); + set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); + + /* Create platform_profile structure and register */ + err = platform_profile_register(&priv->dytc->pprof); + if (err) + goto pp_reg_failed; + + /* Ensure initial values are correct */ + dytc_profile_refresh(priv); + + return 0; + +pp_reg_failed: + mutex_destroy(&priv->dytc->mutex); + kfree(priv->dytc); + priv->dytc = NULL; + + return err; +} + +static void ideapad_dytc_profile_exit(struct ideapad_private *priv) +{ + if (!priv->dytc) + return; + + platform_profile_remove(); + mutex_destroy(&priv->dytc->mutex); + kfree(priv->dytc); + + priv->dytc = NULL; +} + /* * Rfkill */ @@ -560,9 +943,9 @@ struct ideapad_rfk_data { }; static const struct ideapad_rfk_data ideapad_rfk_data[] = { - { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, - { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, - { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, + { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, + { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, + { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, }; static int ideapad_rfk_set(void *data, bool blocked) @@ -582,7 +965,7 @@ static void ideapad_sync_rfk_state(struct ideapad_private *priv) unsigned long hw_blocked = 0; int i; - if (priv->has_hw_rfkill_switch) { + if (priv->features.hw_rfkill_switch) { if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) return; hw_blocked = !hw_blocked; @@ -595,16 +978,15 @@ static void ideapad_sync_rfk_state(struct ideapad_private *priv) static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) { - int ret; - unsigned long sw_blocked; + unsigned long rf_enabled; + int err; - if (no_bt_rfkill && - (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { + if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { /* Force to enable bluetooth when no_bt_rfkill=1 */ - write_ec_cmd(priv->adev->handle, - ideapad_rfk_data[dev].opcode, 1); + write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); return 0; } + priv->rfk_priv[dev].dev = dev; priv->rfk_priv[dev].priv = priv; @@ -616,20 +998,17 @@ static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) if (!priv->rfk[dev]) return -ENOMEM; - if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, - &sw_blocked)) { - rfkill_init_sw_state(priv->rfk[dev], 0); - } else { - sw_blocked = !sw_blocked; - rfkill_init_sw_state(priv->rfk[dev], sw_blocked); - } + err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); + if (err) + rf_enabled = 1; - ret = rfkill_register(priv->rfk[dev]); - if (ret) { + rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); + + err = rfkill_register(priv->rfk[dev]); + if (err) rfkill_destroy(priv->rfk[dev]); - return ret; - } - return 0; + + return err; } static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) @@ -646,40 +1025,39 @@ static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) */ static int ideapad_sysfs_init(struct ideapad_private *priv) { - return sysfs_create_group(&priv->platform_device->dev.kobj, - &ideapad_attribute_group); + return device_add_group(&priv->platform_device->dev, + &ideapad_attribute_group); } static void ideapad_sysfs_exit(struct ideapad_private *priv) { - sysfs_remove_group(&priv->platform_device->dev.kobj, - &ideapad_attribute_group); + device_remove_group(&priv->platform_device->dev, + &ideapad_attribute_group); } /* * input device */ static const struct key_entry ideapad_keymap[] = { - { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, - { KE_KEY, 7, { KEY_CAMERA } }, - { KE_KEY, 8, { KEY_MICMUTE } }, - { KE_KEY, 11, { KEY_F16 } }, - { KE_KEY, 13, { KEY_WLAN } }, - { KE_KEY, 16, { KEY_PROG1 } }, - { KE_KEY, 17, { KEY_PROG2 } }, - { KE_KEY, 64, { KEY_PROG3 } }, - { KE_KEY, 65, { KEY_PROG4 } }, - { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, - { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 7, { KEY_CAMERA } }, + { KE_KEY, 8, { KEY_MICMUTE } }, + { KE_KEY, 11, { KEY_F16 } }, + { KE_KEY, 13, { KEY_WLAN } }, + { KE_KEY, 16, { KEY_PROG1 } }, + { KE_KEY, 17, { KEY_PROG2 } }, + { KE_KEY, 64, { KEY_PROG3 } }, + { KE_KEY, 65, { KEY_PROG4 } }, + { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, + { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, { KE_KEY, 128, { KEY_ESC } }, - - { KE_END, 0 }, + { KE_END }, }; static int ideapad_input_init(struct ideapad_private *priv) { struct input_dev *inputdev; - int error; + int err; inputdev = input_allocate_device(); if (!inputdev) @@ -690,24 +1068,28 @@ static int ideapad_input_init(struct ideapad_private *priv) inputdev->id.bustype = BUS_HOST; inputdev->dev.parent = &priv->platform_device->dev; - error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); - if (error) { - pr_err("Unable to setup input device keymap\n"); + err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); + if (err) { + dev_err(&priv->platform_device->dev, + "Could not set up input device keymap: %d\n", err); goto err_free_dev; } - error = input_register_device(inputdev); - if (error) { - pr_err("Unable to register input device\n"); + err = input_register_device(inputdev); + if (err) { + dev_err(&priv->platform_device->dev, + "Could not register input device: %d\n", err); goto err_free_dev; } priv->inputdev = inputdev; + return 0; err_free_dev: input_free_device(inputdev); - return error; + + return err; } static void ideapad_input_exit(struct ideapad_private *priv) @@ -728,6 +1110,7 @@ static void ideapad_input_novokey(struct ideapad_private *priv) if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) return; + if (long_pressed) ideapad_input_report(priv, 17); else @@ -738,24 +1121,24 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) { unsigned long bit, value; - read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); + if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) + return; - for (bit = 0; bit < 16; bit++) { - if (test_bit(bit, &value)) { - switch (bit) { - case 0: /* Z580 */ - case 6: /* Z570 */ - /* Thermal Management button */ - ideapad_input_report(priv, 65); - break; - case 1: - /* OneKey Theater button */ - ideapad_input_report(priv, 64); - break; - default: - pr_info("Unknown special button: %lu\n", bit); - break; - } + for_each_set_bit (bit, &value, 16) { + switch (bit) { + case 6: /* Z570 */ + case 0: /* Z580 */ + /* Thermal Management button */ + ideapad_input_report(priv, 65); + break; + case 1: + /* OneKey Theater button */ + ideapad_input_report(priv, 64); + break; + default: + dev_info(&priv->platform_device->dev, + "Unknown special button: %lu\n", bit); + break; } } } @@ -767,28 +1150,29 @@ static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) { struct ideapad_private *priv = bl_get_data(blightdev); unsigned long now; + int err; - if (!priv) - return -EINVAL; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); + if (err) + return err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) - return -EIO; return now; } static int ideapad_backlight_update_status(struct backlight_device *blightdev) { struct ideapad_private *priv = bl_get_data(blightdev); + int err; - if (!priv) - return -EINVAL; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, + blightdev->props.brightness); + if (err) + return err; - if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, - blightdev->props.brightness)) - return -EIO; - if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, - blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) - return -EIO; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, + blightdev->props.power != FB_BLANK_POWERDOWN); + if (err) + return err; return 0; } @@ -803,30 +1187,41 @@ static int ideapad_backlight_init(struct ideapad_private *priv) struct backlight_device *blightdev; struct backlight_properties props; unsigned long max, now, power; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) - return -EIO; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) - return -EIO; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) - return -EIO; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); + if (err) + return err; + + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); + if (err) + return err; + + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); + if (err) + return err; + + memset(&props, 0, sizeof(props)); - memset(&props, 0, sizeof(struct backlight_properties)); props.max_brightness = max; props.type = BACKLIGHT_PLATFORM; + blightdev = backlight_device_register("ideapad", &priv->platform_device->dev, priv, &ideapad_backlight_ops, &props); if (IS_ERR(blightdev)) { - pr_err("Could not register backlight device\n"); - return PTR_ERR(blightdev); + err = PTR_ERR(blightdev); + dev_err(&priv->platform_device->dev, + "Could not register backlight device: %d\n", err); + return err; } priv->blightdev = blightdev; blightdev->props.brightness = now; blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; + backlight_update_status(blightdev); return 0; @@ -840,13 +1235,15 @@ static void ideapad_backlight_exit(struct ideapad_private *priv) static void ideapad_backlight_notify_power(struct ideapad_private *priv) { - unsigned long power; struct backlight_device *blightdev = priv->blightdev; + unsigned long power; if (!blightdev) return; + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) return; + blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; } @@ -855,12 +1252,112 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) unsigned long now; /* if we control brightness via acpi video driver */ - if (priv->blightdev == NULL) { + if (!priv->blightdev) read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); - return; - } + else + backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); +} - backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); +/* + * keyboard backlight + */ +static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) +{ + unsigned long hals; + int err; + + err = eval_hals(priv->adev->handle, &hals); + if (err) + return err; + + return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); +} + +static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); + + return ideapad_kbd_bl_brightness_get(priv); +} + +static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) +{ + int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); + + if (err) + return err; + + priv->kbd_bl.last_brightness = brightness; + + return 0; +} + +static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); + + return ideapad_kbd_bl_brightness_set(priv, brightness); +} + +static void ideapad_kbd_bl_notify(struct ideapad_private *priv) +{ + int brightness; + + if (!priv->kbd_bl.initialized) + return; + + brightness = ideapad_kbd_bl_brightness_get(priv); + if (brightness < 0) + return; + + if (brightness == priv->kbd_bl.last_brightness) + return; + + priv->kbd_bl.last_brightness = brightness; + + led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); +} + +static int ideapad_kbd_bl_init(struct ideapad_private *priv) +{ + int brightness, err; + + if (!priv->features.kbd_bl) + return -ENODEV; + + if (WARN_ON(priv->kbd_bl.initialized)) + return -EEXIST; + + brightness = ideapad_kbd_bl_brightness_get(priv); + if (brightness < 0) + return brightness; + + priv->kbd_bl.last_brightness = brightness; + + priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; + priv->kbd_bl.led.max_brightness = 1; + priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; + priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; + priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; + + err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); + if (err) + return err; + + priv->kbd_bl.initialized = true; + + return 0; +} + +static void ideapad_kbd_bl_exit(struct ideapad_private *priv) +{ + if (!priv->kbd_bl.initialized) + return; + + priv->kbd_bl.initialized = false; + + led_classdev_unregister(&priv->kbd_bl.led); } /* @@ -870,71 +1367,77 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) { unsigned long value; - if (!priv->has_touchpad_switch) + if (!priv->features.touchpad_ctrl_via_ec) return; /* Without reading from EC touchpad LED doesn't switch state */ if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { - /* Some IdeaPads don't really turn off touchpad - they only + unsigned char param; + /* + * Some IdeaPads don't really turn off touchpad - they only * switch the LED state. We (de)activate KBC AUX port to turn * touchpad off and on. We send KEY_TOUCHPAD_OFF and - * KEY_TOUCHPAD_ON to not to get out of sync with LED */ - unsigned char param; - i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : - I8042_CMD_AUX_DISABLE); + * KEY_TOUCHPAD_ON to not to get out of sync with LED + */ + i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); ideapad_input_report(priv, value ? 67 : 66); + sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); } } static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) { struct ideapad_private *priv = data; - unsigned long vpc1, vpc2, vpc_bit; + unsigned long vpc1, vpc2, bit; if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) return; + if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) return; vpc1 = (vpc2 << 8) | vpc1; - for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { - if (test_bit(vpc_bit, &vpc1)) { - switch (vpc_bit) { - case 9: - ideapad_sync_rfk_state(priv); - break; - case 13: - case 11: - case 8: - case 7: - case 6: - ideapad_input_report(priv, vpc_bit); - break; - case 5: - ideapad_sync_touchpad_state(priv); - break; - case 4: - ideapad_backlight_notify_brightness(priv); - break; - case 3: - ideapad_input_novokey(priv); - break; - case 2: - ideapad_backlight_notify_power(priv); - break; - case 0: - ideapad_check_special_buttons(priv); - break; - case 1: - /* Some IdeaPads report event 1 every ~20 - * seconds while on battery power; some - * report this when changing to/from tablet - * mode. Squelch this event. - */ - break; - default: - pr_info("Unknown event: %lu\n", vpc_bit); - } + + for_each_set_bit (bit, &vpc1, 16) { + switch (bit) { + case 13: + case 11: + case 8: + case 7: + case 6: + ideapad_input_report(priv, bit); + break; + case 9: + ideapad_sync_rfk_state(priv); + break; + case 5: + ideapad_sync_touchpad_state(priv); + break; + case 4: + ideapad_backlight_notify_brightness(priv); + break; + case 3: + ideapad_input_novokey(priv); + break; + case 2: + ideapad_backlight_notify_power(priv); + break; + case 1: + /* + * Some IdeaPads report event 1 every ~20 + * seconds while on battery power; some + * report this when changing to/from tablet + * mode; some report this when the keyboard + * backlight has changed. + */ + ideapad_kbd_bl_notify(priv); + break; + case 0: + ideapad_check_special_buttons(priv); + break; + default: + dev_info(&priv->platform_device->dev, + "Unknown event: %lu\n", bit); } } } @@ -942,12 +1445,15 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) #if IS_ENABLED(CONFIG_ACPI_WMI) static void ideapad_wmi_notify(u32 value, void *context) { + struct ideapad_private *priv = context; + switch (value) { case 128: - ideapad_input_report(context, value); + ideapad_input_report(priv, value); break; default: - pr_info("Unknown WMI event %u\n", value); + dev_info(&priv->platform_device->dev, + "Unknown WMI event: %u\n", value); } } #endif @@ -971,18 +1477,52 @@ static const struct dmi_system_id hw_rfkill_list[] = { {} }; +static void ideapad_check_features(struct ideapad_private *priv) +{ + acpi_handle handle = priv->adev->handle; + unsigned long val; + + priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); + + /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ + priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); + + if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) + priv->features.fan_mode = true; + + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) + priv->features.conservation_mode = true; + + if (acpi_has_method(handle, "DYTC")) + priv->features.dytc = true; + + if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { + if (!eval_hals(handle, &val)) { + if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) + priv->features.fn_lock = true; + + if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) + priv->features.kbd_bl = true; + + if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) + priv->features.usb_charging = true; + } + } +} + static int ideapad_acpi_add(struct platform_device *pdev) { - int ret, i; - int cfg; struct ideapad_private *priv; struct acpi_device *adev; + acpi_status status; + unsigned long cfg; + int err, i; - ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); - if (ret) + err = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); + if (err) return -ENODEV; - if (read_method_int(adev->handle, "_CFG", &cfg)) + if (eval_int(adev->handle, "_CFG", &cfg)) return -ENODEV; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); @@ -990,33 +1530,40 @@ static int ideapad_acpi_add(struct platform_device *pdev) return -ENOMEM; dev_set_drvdata(&pdev->dev, priv); + priv->cfg = cfg; priv->adev = adev; priv->platform_device = pdev; - priv->has_hw_rfkill_switch = dmi_check_system(hw_rfkill_list); - /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ - priv->has_touchpad_switch = !acpi_dev_present("ELAN0634", NULL, -1); + ideapad_check_features(priv); - ret = ideapad_sysfs_init(priv); - if (ret) - return ret; + err = ideapad_sysfs_init(priv); + if (err) + return err; ideapad_debugfs_init(priv); - ret = ideapad_input_init(priv); - if (ret) + err = ideapad_input_init(priv); + if (err) goto input_failed; + err = ideapad_kbd_bl_init(priv); + if (err) { + if (err != -ENODEV) + dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); + else + dev_info(&pdev->dev, "Keyboard backlight control not available\n"); + } + /* * On some models without a hw-switch (the yoga 2 13 at least) * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. */ - if (!priv->has_hw_rfkill_switch) + if (!priv->features.hw_rfkill_switch) write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); /* The same for Touchpad */ - if (!priv->has_touchpad_switch) + if (!priv->features.touchpad_ctrl_via_ec) write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) @@ -1026,45 +1573,70 @@ static int ideapad_acpi_add(struct platform_device *pdev) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); + err = ideapad_dytc_profile_init(priv); + if (err) { + if (err != -ENODEV) + dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); + else + dev_info(&pdev->dev, "DYTC interface is not available\n"); + } + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { - ret = ideapad_backlight_init(priv); - if (ret && ret != -ENODEV) + err = ideapad_backlight_init(priv); + if (err && err != -ENODEV) goto backlight_failed; } - ret = acpi_install_notify_handler(adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); - if (ret) + + status = acpi_install_notify_handler(adev->handle, + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify, priv); + if (ACPI_FAILURE(status)) { + err = -EIO; goto notification_failed; + } #if IS_ENABLED(CONFIG_ACPI_WMI) for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { - ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], - ideapad_wmi_notify, priv); - if (ret == AE_OK) { + status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], + ideapad_wmi_notify, priv); + if (ACPI_SUCCESS(status)) { priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; break; } } - if (ret != AE_OK && ret != AE_NOT_EXIST) + + if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { + err = -EIO; goto notification_failed_wmi; + } #endif return 0; + #if IS_ENABLED(CONFIG_ACPI_WMI) notification_failed_wmi: acpi_remove_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify); #endif + notification_failed: ideapad_backlight_exit(priv); + backlight_failed: + ideapad_dytc_profile_exit(priv); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + + ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); + input_failed: ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); - return ret; + + return err; } static int ideapad_acpi_remove(struct platform_device *pdev) @@ -1076,38 +1648,44 @@ static int ideapad_acpi_remove(struct platform_device *pdev) if (priv->fnesc_guid) wmi_remove_notify_handler(priv->fnesc_guid); #endif + acpi_remove_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify); + ideapad_backlight_exit(priv); + ideapad_dytc_profile_exit(priv); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + + ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); - dev_set_drvdata(&pdev->dev, NULL); return 0; } #ifdef CONFIG_PM_SLEEP -static int ideapad_acpi_resume(struct device *device) +static int ideapad_acpi_resume(struct device *dev) { - struct ideapad_private *priv; - - if (!device) - return -EINVAL; - priv = dev_get_drvdata(device); + struct ideapad_private *priv = dev_get_drvdata(dev); ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); + + if (priv->dytc) + dytc_profile_refresh(priv); + return 0; } #endif static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); static const struct acpi_device_id ideapad_device_ids[] = { - { "VPC2004", 0}, - { "", 0}, + {"VPC2004", 0}, + {"", 0}, }; MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); diff --git a/drivers/platform/x86/intel-uncore-frequency.c b/drivers/platform/x86/intel-uncore-frequency.c index 12d5ab7e1f5d..3ee4c5c8a64f 100644 --- a/drivers/platform/x86/intel-uncore-frequency.c +++ b/drivers/platform/x86/intel-uncore-frequency.c @@ -377,6 +377,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = { X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), {} }; diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 30a9062d2b4b..8a8017f9ca91 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -44,6 +44,7 @@ static const struct key_entry intel_vbtn_keymap[] = { { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ + { KE_END } }; static const struct key_entry intel_vbtn_switchmap[] = { @@ -51,14 +52,15 @@ static const struct key_entry intel_vbtn_switchmap[] = { { KE_SW, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ + { KE_END } }; #define KEYMAP_LEN \ (ARRAY_SIZE(intel_vbtn_keymap) + ARRAY_SIZE(intel_vbtn_switchmap) + 1) struct intel_vbtn_priv { - struct key_entry keymap[KEYMAP_LEN]; - struct input_dev *input_dev; + struct input_dev *buttons_dev; + struct input_dev *switches_dev; bool has_buttons; bool has_switches; bool wakeup_mode; @@ -77,48 +79,62 @@ static void detect_tablet_mode(struct platform_device *device) return; m = !(vgbs & VGBS_TABLET_MODE_FLAGS); - input_report_switch(priv->input_dev, SW_TABLET_MODE, m); + input_report_switch(priv->switches_dev, SW_TABLET_MODE, m); m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; - input_report_switch(priv->input_dev, SW_DOCK, m); + input_report_switch(priv->switches_dev, SW_DOCK, m); } +/* + * Note this unconditionally creates the 2 input_dev-s and sets up + * the sparse-keymaps. Only the registration is conditional on + * have_buttons / have_switches. This is done so that the notify + * handler can always call sparse_keymap_entry_from_scancode() + * on the input_dev-s do determine the event type. + */ static int intel_vbtn_input_setup(struct platform_device *device) { struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); - int ret, keymap_len = 0; + int ret; - if (priv->has_buttons) { - memcpy(&priv->keymap[keymap_len], intel_vbtn_keymap, - ARRAY_SIZE(intel_vbtn_keymap) * - sizeof(struct key_entry)); - keymap_len += ARRAY_SIZE(intel_vbtn_keymap); - } - - if (priv->has_switches) { - memcpy(&priv->keymap[keymap_len], intel_vbtn_switchmap, - ARRAY_SIZE(intel_vbtn_switchmap) * - sizeof(struct key_entry)); - keymap_len += ARRAY_SIZE(intel_vbtn_switchmap); - } - - priv->keymap[keymap_len].type = KE_END; - - priv->input_dev = devm_input_allocate_device(&device->dev); - if (!priv->input_dev) + priv->buttons_dev = devm_input_allocate_device(&device->dev); + if (!priv->buttons_dev) return -ENOMEM; - ret = sparse_keymap_setup(priv->input_dev, priv->keymap, NULL); + ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL); if (ret) return ret; - priv->input_dev->dev.parent = &device->dev; - priv->input_dev->name = "Intel Virtual Button driver"; - priv->input_dev->id.bustype = BUS_HOST; + priv->buttons_dev->dev.parent = &device->dev; + priv->buttons_dev->name = "Intel Virtual Buttons"; + priv->buttons_dev->id.bustype = BUS_HOST; - if (priv->has_switches) + if (priv->has_buttons) { + ret = input_register_device(priv->buttons_dev); + if (ret) + return ret; + } + + priv->switches_dev = devm_input_allocate_device(&device->dev); + if (!priv->switches_dev) + return -ENOMEM; + + ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL); + if (ret) + return ret; + + priv->switches_dev->dev.parent = &device->dev; + priv->switches_dev->name = "Intel Virtual Switches"; + priv->switches_dev->id.bustype = BUS_HOST; + + if (priv->has_switches) { detect_tablet_mode(device); - return input_register_device(priv->input_dev); + ret = input_register_device(priv->switches_dev); + if (ret) + return ret; + } + + return 0; } static void notify_handler(acpi_handle handle, u32 event, void *context) @@ -127,48 +143,50 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); unsigned int val = !(event & 1); /* Even=press, Odd=release */ const struct key_entry *ke, *ke_rel; + struct input_dev *input_dev; bool autorelease; + int ret; - if (priv->wakeup_mode) { - ke = sparse_keymap_entry_from_scancode(priv->input_dev, event); - if (ke) { - pm_wakeup_hard_event(&device->dev); - - /* - * Switch events like tablet mode will wake the device - * and report the new switch position to the input - * subsystem. - */ - if (ke->type == KE_SW) - sparse_keymap_report_event(priv->input_dev, - event, - val, - 0); + if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { + if (!priv->has_buttons) { + dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n"); return; } - goto out_unknown; + input_dev = priv->buttons_dev; + } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { + if (!priv->has_switches) { + dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n"); + ret = input_register_device(priv->switches_dev); + if (ret) + return; + + priv->has_switches = true; + } + input_dev = priv->switches_dev; + } else { + dev_dbg(&device->dev, "unknown event index 0x%x\n", event); + return; + } + + if (priv->wakeup_mode) { + pm_wakeup_hard_event(&device->dev); + + /* + * Skip reporting an evdev event for button wake events, + * mirroring how the drivers/acpi/button.c code skips this too. + */ + if (ke->type == KE_KEY) + return; } /* * Even press events are autorelease if there is no corresponding odd * release event, or if the odd event is KE_IGNORE. */ - ke_rel = sparse_keymap_entry_from_scancode(priv->input_dev, event | 1); + ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1); autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); - if (sparse_keymap_report_event(priv->input_dev, event, val, autorelease)) - return; - -out_unknown: - dev_dbg(&device->dev, "unknown event index 0x%x\n", event); -} - -static bool intel_vbtn_has_buttons(acpi_handle handle) -{ - acpi_status status; - - status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); - return ACPI_SUCCESS(status); + sparse_keymap_report_event(input_dev, event, val, autorelease); } /* @@ -245,7 +263,7 @@ static int intel_vbtn_probe(struct platform_device *device) acpi_status status; int err; - has_buttons = intel_vbtn_has_buttons(handle); + has_buttons = acpi_has_method(handle, "VBDL"); has_switches = intel_vbtn_has_switches(handle); if (!has_buttons && !has_switches) { @@ -274,6 +292,12 @@ static int intel_vbtn_probe(struct platform_device *device) if (ACPI_FAILURE(status)) return -EBUSY; + if (has_buttons) { + status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); + if (ACPI_FAILURE(status)) + dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status); + } + device_init_wakeup(&device->dev, true); /* * In order for system wakeup to work, the EC GPE has to be marked as diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c deleted file mode 100644 index df434abbb66f..000000000000 --- a/drivers/platform/x86/intel_mid_powerbtn.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Power button driver for Intel MID platforms. - * - * Copyright (C) 2010,2017 Intel Corp - * - * Author: Hong Liu - * Author: Andy Shevchenko - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DRIVER_NAME "msic_power_btn" - -#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ - -/* - * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask - * power button interrupt - */ -#define MSIC_PWRBTNM (1 << 0) - -/* Intel Tangier */ -#define BCOVE_PB_LEVEL (1 << 4) /* 1 - release, 0 - press */ - -/* Basin Cove PMIC */ -#define BCOVE_PBIRQ 0x02 -#define BCOVE_IRQLVL1MSK 0x0c -#define BCOVE_PBIRQMASK 0x0d -#define BCOVE_PBSTATUS 0x27 - -struct mid_pb_ddata { - struct device *dev; - int irq; - struct input_dev *input; - unsigned short mirqlvl1_addr; - unsigned short pbstat_addr; - u8 pbstat_mask; - struct intel_scu_ipc_dev *scu; - int (*setup)(struct mid_pb_ddata *ddata); -}; - -static int mid_pbstat(struct mid_pb_ddata *ddata, int *value) -{ - struct input_dev *input = ddata->input; - int ret; - u8 pbstat; - - ret = intel_scu_ipc_dev_ioread8(ddata->scu, ddata->pbstat_addr, - &pbstat); - if (ret) - return ret; - - dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat); - - *value = !(pbstat & ddata->pbstat_mask); - return 0; -} - -static int mid_irq_ack(struct mid_pb_ddata *ddata) -{ - return intel_scu_ipc_dev_update(ddata->scu, ddata->mirqlvl1_addr, 0, - MSIC_PWRBTNM); -} - -static int mrfld_setup(struct mid_pb_ddata *ddata) -{ - /* Unmask the PBIRQ and MPBIRQ on Tangier */ - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQ, 0, MSIC_PWRBTNM); - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM); - - return 0; -} - -static irqreturn_t mid_pb_isr(int irq, void *dev_id) -{ - struct mid_pb_ddata *ddata = dev_id; - struct input_dev *input = ddata->input; - int value = 0; - int ret; - - ret = mid_pbstat(ddata, &value); - if (ret < 0) { - dev_err(input->dev.parent, - "Read error %d while reading MSIC_PB_STATUS\n", ret); - } else { - input_event(input, EV_KEY, KEY_POWER, value); - input_sync(input); - } - - mid_irq_ack(ddata); - return IRQ_HANDLED; -} - -static const struct mid_pb_ddata mfld_ddata = { - .mirqlvl1_addr = INTEL_MSIC_IRQLVL1MSK, - .pbstat_addr = INTEL_MSIC_PBSTATUS, - .pbstat_mask = MSIC_PB_LEVEL, -}; - -static const struct mid_pb_ddata mrfld_ddata = { - .mirqlvl1_addr = BCOVE_IRQLVL1MSK, - .pbstat_addr = BCOVE_PBSTATUS, - .pbstat_mask = BCOVE_PB_LEVEL, - .setup = mrfld_setup, -}; - -static const struct x86_cpu_id mid_pb_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_MID, &mfld_ddata), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, &mrfld_ddata), - {} -}; - -static int mid_pb_probe(struct platform_device *pdev) -{ - const struct x86_cpu_id *id; - struct mid_pb_ddata *ddata; - struct input_dev *input; - int irq = platform_get_irq(pdev, 0); - int error; - - id = x86_match_cpu(mid_pb_cpu_ids); - if (!id) - return -ENODEV; - - if (irq < 0) { - dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); - return irq; - } - - input = devm_input_allocate_device(&pdev->dev); - if (!input) - return -ENOMEM; - - input->name = pdev->name; - input->phys = "power-button/input0"; - input->id.bustype = BUS_HOST; - input->dev.parent = &pdev->dev; - - input_set_capability(input, EV_KEY, KEY_POWER); - - ddata = devm_kmemdup(&pdev->dev, (void *)id->driver_data, - sizeof(*ddata), GFP_KERNEL); - if (!ddata) - return -ENOMEM; - - ddata->dev = &pdev->dev; - ddata->irq = irq; - ddata->input = input; - - if (ddata->setup) { - error = ddata->setup(ddata); - if (error) - return error; - } - - ddata->scu = devm_intel_scu_ipc_dev_get(&pdev->dev); - if (!ddata->scu) - return -EPROBE_DEFER; - - error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr, - IRQF_ONESHOT, DRIVER_NAME, ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to request irq %d for MID power button\n", irq); - return error; - } - - error = input_register_device(input); - if (error) { - dev_err(&pdev->dev, - "Unable to register input dev, error %d\n", error); - return error; - } - - platform_set_drvdata(pdev, ddata); - - /* - * SCU firmware might send power button interrupts to IA core before - * kernel boots and doesn't get EOI from IA core. The first bit of - * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new - * power interrupt to Android kernel. Unmask the bit when probing - * power button in kernel. - * There is a very narrow race between irq handler and power button - * initialization. The race happens rarely. So we needn't worry - * about it. - */ - error = mid_irq_ack(ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to clear power button interrupt, error: %d\n", - error); - return error; - } - - device_init_wakeup(&pdev->dev, true); - dev_pm_set_wake_irq(&pdev->dev, irq); - - return 0; -} - -static int mid_pb_remove(struct platform_device *pdev) -{ - dev_pm_clear_wake_irq(&pdev->dev); - device_init_wakeup(&pdev->dev, false); - - return 0; -} - -static struct platform_driver mid_pb_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = mid_pb_probe, - .remove = mid_pb_remove, -}; - -module_platform_driver(mid_pb_driver); - -MODULE_AUTHOR("Hong Liu "); -MODULE_DESCRIPTION("Intel MID Power Button Driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c deleted file mode 100644 index f12f4e7bd971..000000000000 --- a/drivers/platform/x86/intel_mid_thermal.c +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID platform thermal driver - * - * Copyright (C) 2011 Intel Corporation - * - * Author: Durgadoss R - */ - -#define pr_fmt(fmt) "intel_mid_thermal: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Number of thermal sensors */ -#define MSIC_THERMAL_SENSORS 4 - -/* ADC1 - thermal registers */ -#define MSIC_ADC_ENBL 0x10 -#define MSIC_ADC_START 0x08 - -#define MSIC_ADCTHERM_ENBL 0x04 -#define MSIC_ADCRRDATA_ENBL 0x05 -#define MSIC_CHANL_MASK_VAL 0x0F - -#define MSIC_STOPBIT_MASK 16 -#define MSIC_ADCTHERM_MASK 4 -/* Number of ADC channels */ -#define ADC_CHANLS_MAX 15 -#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) - -/* ADC channel code values */ -#define SKIN_SENSOR0_CODE 0x08 -#define SKIN_SENSOR1_CODE 0x09 -#define SYS_SENSOR_CODE 0x0A -#define MSIC_DIE_SENSOR_CODE 0x03 - -#define SKIN_THERM_SENSOR0 0 -#define SKIN_THERM_SENSOR1 1 -#define SYS_THERM_SENSOR2 2 -#define MSIC_DIE_THERM_SENSOR3 3 - -/* ADC code range */ -#define ADC_MAX 977 -#define ADC_MIN 162 -#define ADC_VAL0C 887 -#define ADC_VAL20C 720 -#define ADC_VAL40C 508 -#define ADC_VAL60C 315 - -/* ADC base addresses */ -#define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */ -#define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */ - -/* MSIC die attributes */ -#define MSIC_DIE_ADC_MIN 488 -#define MSIC_DIE_ADC_MAX 1004 - -/* This holds the address of the first free ADC channel, - * among the 15 channels - */ -static int channel_index; - -struct platform_info { - struct platform_device *pdev; - struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; -}; - -struct thermal_device_info { - unsigned int chnl_addr; - int direct; - /* This holds the current temperature in millidegree celsius */ - long curr_temp; -}; - -/** - * to_msic_die_temp - converts adc_val to msic_die temperature - * @adc_val: ADC value to be converted - * - * Can sleep - */ -static int to_msic_die_temp(uint16_t adc_val) -{ - return (368 * (adc_val) / 1000) - 220; -} - -/** - * is_valid_adc - checks whether the adc code is within the defined range - * @min: minimum value for the sensor - * @max: maximum value for the sensor - * - * Can sleep - */ -static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) -{ - return (adc_val >= min) && (adc_val <= max); -} - -/** - * adc_to_temp - converts the ADC code to temperature in C - * @direct: true if ths channel is direct index - * @adc_val: the adc_val that needs to be converted - * @tp: temperature return value - * - * Linear approximation is used to covert the skin adc value into temperature. - * This technique is used to avoid very long look-up table to get - * the appropriate temp value from ADC value. - * The adc code vs sensor temp curve is split into five parts - * to achieve very close approximate temp value with less than - * 0.5C error - */ -static int adc_to_temp(int direct, uint16_t adc_val, int *tp) -{ - int temp; - - /* Direct conversion for die temperature */ - if (direct) { - if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { - *tp = to_msic_die_temp(adc_val) * 1000; - return 0; - } - return -ERANGE; - } - - if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) - return -ERANGE; - - /* Linear approximation for skin temperature */ - if (adc_val > ADC_VAL0C) - temp = 177 - (adc_val/5); - else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) - temp = 111 - (adc_val/8); - else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) - temp = 92 - (adc_val/10); - else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) - temp = 91 - (adc_val/10); - else - temp = 112 - (adc_val/6); - - /* Convert temperature in celsius to milli degree celsius */ - *tp = temp * 1000; - return 0; -} - -/** - * mid_read_temp - read sensors for temperature - * @temp: holds the current temperature for the sensor after reading - * - * reads the adc_code from the channel and converts it to real - * temperature. The converted value is stored in temp. - * - * Can sleep - */ -static int mid_read_temp(struct thermal_zone_device *tzd, int *temp) -{ - struct thermal_device_info *td_info = tzd->devdata; - uint16_t adc_val, addr; - uint8_t data = 0; - int ret; - int curr_temp; - - addr = td_info->chnl_addr; - - /* Enable the msic for conversion before reading */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); - if (ret) - return ret; - - /* Re-toggle the RRDATARD bit (temporary workaround) */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL); - if (ret) - return ret; - - /* Read the higher bits of data */ - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - - /* Shift bits to accommodate the lower two data bits */ - adc_val = (data << 2); - addr++; - - ret = intel_msic_reg_read(addr, &data);/* Read lower bits */ - if (ret) - return ret; - - /* Adding lower two bits to the higher bits */ - data &= 03; - adc_val += data; - - /* Convert ADC value to temperature */ - ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); - if (ret == 0) - *temp = td_info->curr_temp = curr_temp; - return ret; -} - -/** - * configure_adc - enables/disables the ADC for conversion - * @val: zero: disables the ADC non-zero:enables the ADC - * - * Enable/Disable the ADC depending on the argument - * - * Can sleep - */ -static int configure_adc(int val) -{ - int ret; - uint8_t data; - - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if (val) { - /* Enable and start the ADC */ - data |= (MSIC_ADC_ENBL | MSIC_ADC_START); - } else { - /* Just stop the ADC */ - data &= (~MSIC_ADC_START); - } - return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data); -} - -/** - * set_up_therm_channel - enable thermal channel for conversion - * @base_addr: index of free msic ADC channel - * - * Enable all the three channels for conversion - * - * Can sleep - */ -static int set_up_therm_channel(u16 base_addr) -{ - int ret; - - /* Enable all the sensor channels */ - ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE); - if (ret) - return ret; - - /* Since this is the last channel, set the stop bit - * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ - ret = intel_msic_reg_write(base_addr + 3, - (MSIC_DIE_SENSOR_CODE | 0x10)); - if (ret) - return ret; - - /* Enable ADC and start it */ - return configure_adc(1); -} - -/** - * reset_stopbit - sets the stop bit to 0 on the given channel - * @addr: address of the channel - * - * Can sleep - */ -static int reset_stopbit(uint16_t addr) -{ - int ret; - uint8_t data; - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - /* Set the stop bit to zero */ - return intel_msic_reg_write(addr, (data & 0xEF)); -} - -/** - * find_free_channel - finds an empty channel for conversion - * - * If the ADC is not enabled then start using 0th channel - * itself. Otherwise find an empty channel by looking for a - * channel in which the stopbit is set to 1. returns the index - * of the first free channel if succeeds or an error code. - * - * Context: can sleep - * - * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc - * code. - */ -static int find_free_channel(void) -{ - int ret; - int i; - uint8_t data; - - /* check whether ADC is enabled */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if ((data & MSIC_ADC_ENBL) == 0) - return 0; - - /* ADC is already enabled; Looking for an empty channel */ - for (i = 0; i < ADC_CHANLS_MAX; i++) { - ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data); - if (ret) - return ret; - - if (data & MSIC_STOPBIT_MASK) { - ret = i; - break; - } - } - return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; -} - -/** - * mid_initialize_adc - initializing the ADC - * @dev: our device structure - * - * Initialize the ADC for reading thermistor values. Can sleep. - */ -static int mid_initialize_adc(struct device *dev) -{ - u8 data; - u16 base_addr; - int ret; - - /* - * Ensure that adctherm is disabled before we - * initialize the ADC - */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data); - if (ret) - return ret; - - data &= ~MSIC_ADCTHERM_MASK; - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data); - if (ret) - return ret; - - /* Index of the first channel in which the stop bit is set */ - channel_index = find_free_channel(); - if (channel_index < 0) { - dev_err(dev, "No free ADC channels"); - return channel_index; - } - - base_addr = ADC_CHNL_START_ADDR + channel_index; - - if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { - /* Reset stop bit for channels other than 0 and 12 */ - ret = reset_stopbit(base_addr); - if (ret) - return ret; - - /* Index of the first free channel */ - base_addr++; - channel_index++; - } - - ret = set_up_therm_channel(base_addr); - if (ret) { - dev_err(dev, "unable to enable ADC"); - return ret; - } - dev_dbg(dev, "ADC initialization successful"); - return ret; -} - -/** - * initialize_sensor - sets default temp and timer ranges - * @index: index of the sensor - * - * Context: can sleep - */ -static struct thermal_device_info *initialize_sensor(int index) -{ - struct thermal_device_info *td_info = - kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); - - if (!td_info) - return NULL; - - /* Set the base addr of the channel for this sensor */ - td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); - /* Sensor 3 is direct conversion */ - if (index == 3) - td_info->direct = 1; - return td_info; -} - -#ifdef CONFIG_PM_SLEEP -/** - * mid_thermal_resume - resume routine - * @dev: device structure - * - * mid thermal resume: re-initializes the adc. Can sleep. - */ -static int mid_thermal_resume(struct device *dev) -{ - return mid_initialize_adc(dev); -} - -/** - * mid_thermal_suspend - suspend routine - * @dev: device structure - * - * mid thermal suspend implements the suspend functionality - * by stopping the ADC. Can sleep. - */ -static int mid_thermal_suspend(struct device *dev) -{ - /* - * This just stops the ADC and does not disable it. - * temporary workaround until we have a generic ADC driver. - * If 0 is passed, it disables the ADC. - */ - return configure_adc(0); -} -#endif - -static SIMPLE_DEV_PM_OPS(mid_thermal_pm, - mid_thermal_suspend, mid_thermal_resume); - -/** - * read_curr_temp - reads the current temperature and stores in temp - * @temp: holds the current temperature value after reading - * - * Can sleep - */ -static int read_curr_temp(struct thermal_zone_device *tzd, int *temp) -{ - WARN_ON(tzd == NULL); - return mid_read_temp(tzd, temp); -} - -/* Can't be const */ -static struct thermal_zone_device_ops tzd_ops = { - .get_temp = read_curr_temp, -}; - -/** - * mid_thermal_probe - mfld thermal initialize - * @pdev: platform device structure - * - * mid thermal probe initializes the hardware and registers - * all the sensors with the generic thermal framework. Can sleep. - */ -static int mid_thermal_probe(struct platform_device *pdev) -{ - static char *name[MSIC_THERMAL_SENSORS] = { - "skin0", "skin1", "sys", "msicdie" - }; - - int ret; - int i; - struct platform_info *pinfo; - - pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info), - GFP_KERNEL); - if (!pinfo) - return -ENOMEM; - - /* Initializing the hardware */ - ret = mid_initialize_adc(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "ADC init failed"); - return ret; - } - - /* Register each sensor with the generic thermal framework*/ - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - struct thermal_device_info *td_info = initialize_sensor(i); - - if (!td_info) { - ret = -ENOMEM; - goto err; - } - pinfo->tzd[i] = thermal_zone_device_register(name[i], - 0, 0, td_info, &tzd_ops, NULL, 0, 0); - if (IS_ERR(pinfo->tzd[i])) { - kfree(td_info); - ret = PTR_ERR(pinfo->tzd[i]); - goto err; - } - ret = thermal_zone_device_enable(pinfo->tzd[i]); - if (ret) { - kfree(td_info); - thermal_zone_device_unregister(pinfo->tzd[i]); - goto err; - } - } - - pinfo->pdev = pdev; - platform_set_drvdata(pdev, pinfo); - return 0; - -err: - while (--i >= 0) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - configure_adc(0); - return ret; -} - -/** - * mid_thermal_remove - mfld thermal finalize - * @dev: platform device structure - * - * MLFD thermal remove unregisters all the sensors from the generic - * thermal framework. Can sleep. - */ -static int mid_thermal_remove(struct platform_device *pdev) -{ - int i; - struct platform_info *pinfo = platform_get_drvdata(pdev); - - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - - /* Stop the ADC */ - return configure_adc(0); -} - -#define DRIVER_NAME "msic_thermal" - -static const struct platform_device_id therm_id_table[] = { - { DRIVER_NAME, 1 }, - { } -}; -MODULE_DEVICE_TABLE(platform, therm_id_table); - -static struct platform_driver mid_thermal_driver = { - .driver = { - .name = DRIVER_NAME, - .pm = &mid_thermal_pm, - }, - .probe = mid_thermal_probe, - .remove = mid_thermal_remove, - .id_table = therm_id_table, -}; - -module_platform_driver(mid_thermal_driver); - -MODULE_AUTHOR("Durgadoss R "); -MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index d9cf7f7602b0..9171a46a9e3f 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -75,7 +75,7 @@ struct intel_scu_ipc_dev { #define IPC_READ_BUFFER 0x90 /* Timeout in jiffies */ -#define IPC_TIMEOUT (3 * HZ) +#define IPC_TIMEOUT (5 * HZ) static struct intel_scu_ipc_dev *ipcdev; /* Only one for now */ static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */ diff --git a/arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c b/drivers/platform/x86/intel_scu_wdt.c similarity index 69% rename from arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c rename to drivers/platform/x86/intel_scu_wdt.c index 227218a8f98e..c2479777a1d6 100644 --- a/arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c +++ b/drivers/platform/x86/intel_scu_wdt.c @@ -11,8 +11,9 @@ #include #include +#include +#include #include -#include #include #include @@ -49,34 +50,26 @@ static struct intel_mid_wdt_pdata tangier_pdata = { .probe = tangier_probe, }; -static int wdt_scu_status_change(struct notifier_block *nb, - unsigned long code, void *data) -{ - if (code == SCU_DOWN) { - platform_device_unregister(&wdt_dev); - return 0; - } - - return platform_device_register(&wdt_dev); -} - -static struct notifier_block wdt_scu_notifier = { - .notifier_call = wdt_scu_status_change, +static const struct x86_cpu_id intel_mid_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, &tangier_pdata), + {} }; static int __init register_mid_wdt(void) { - if (intel_mid_identify_cpu() != INTEL_MID_CPU_CHIP_TANGIER) + const struct x86_cpu_id *id; + + id = x86_match_cpu(intel_mid_cpu_ids); + if (!id) return -ENODEV; - wdt_dev.dev.platform_data = &tangier_pdata; - - /* - * We need to be sure that the SCU IPC is ready before watchdog device - * can be registered: - */ - intel_scu_notifier_add(&wdt_scu_notifier); - - return 0; + wdt_dev.dev.platform_data = (struct intel_mid_wdt_pdata *)id->driver_data; + return platform_device_register(&wdt_dev); } arch_initcall(register_mid_wdt); + +static void __exit unregister_mid_wdt(void) +{ + platform_device_unregister(&wdt_dev); +} +__exitcall(unregister_mid_wdt); diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 64ee7819c9d3..fd318cdfe313 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -96,6 +96,8 @@ static int msi_wmi_query_block(int instance, int *ret) struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); + if (ACPI_FAILURE(status)) + return -EIO; obj = output.pointer; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f3e8eca8d86d..b881044b31b0 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -9855,16 +9856,27 @@ static bool has_lapsensor; static bool palm_state; static bool lap_state; -static int lapsensor_get(bool *present, bool *state) +static int dytc_command(int command, int *output) { acpi_handle dytc_handle; - int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) { + /* Platform doesn't support DYTC */ + return -ENODEV; + } + if (!acpi_evalf(dytc_handle, output, NULL, "dd", command)) + return -EIO; + return 0; +} + +static int lapsensor_get(bool *present, bool *state) +{ + int output, err; *present = false; - if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) - return -ENODEV; - if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET)) - return -EIO; + err = dytc_command(DYTC_CMD_GET, &output); + if (err) + return err; *present = true; /*If we get his far, we have lapmode support*/ *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false; @@ -9983,6 +9995,434 @@ static struct ibm_struct proxsensor_driver_data = { .exit = proxsensor_exit, }; +/************************************************************************* + * DYTC Platform Profile interface + */ + +#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ +#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ +#define DYTC_CMD_RESET 0x1ff /* To reset back to default */ + +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ + +#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ +#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ + +#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ +#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ +#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ + +#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ +#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ + +#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ + +#define DYTC_SET_COMMAND(function, mode, on) \ + (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ + (mode) << DYTC_SET_MODE_BIT | \ + (on) << DYTC_SET_VALID_BIT) + +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) + +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) + +static bool dytc_profile_available; +static enum platform_profile_option dytc_current_profile; +static atomic_t dytc_ignore_event = ATOMIC_INIT(0); +static DEFINE_MUTEX(dytc_mutex); + +static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) +{ + switch (dytcmode) { + case DYTC_MODE_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; +} + +static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) +{ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + *perfmode = DYTC_MODE_LOWPOWER; + break; + case PLATFORM_PROFILE_BALANCED: + *perfmode = DYTC_MODE_BALANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + *perfmode = DYTC_MODE_PERFORM; + break; + default: /* Unknown profile */ + return -EOPNOTSUPP; + } + return 0; +} + +/* + * dytc_profile_get: Function to register with platform_profile + * handler. Returns current platform profile. + */ +static int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + *profile = dytc_current_profile; + return 0; +} + +/* + * Helper function - check if we are in CQL mode and if we are + * - disable CQL, + * - run the command + * - enable CQL + * If not in CQL mode, just run the command + */ +static int dytc_cql_command(int command, int *output) +{ + int err, cmd_err, dummy; + int cur_funcmode; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_command(DYTC_CMD_GET, output); + if (err) + return err; + + cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; + /* Check if we're OK to return immediately */ + if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) + return 0; + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + atomic_inc(&dytc_ignore_event); + err = dytc_command(DYTC_DISABLE_CQL, &dummy); + if (err) + return err; + } + + cmd_err = dytc_command(command, output); + /* Check return condition after we've restored CQL state */ + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = dytc_command(DYTC_ENABLE_CQL, &dummy); + if (err) + return err; + } + + return cmd_err; +} + +/* + * dytc_profile_set: Function to register with platform_profile + * handler. Sets current platform profile. + */ +static int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int output; + int err; + + if (!dytc_profile_available) + return -ENODEV; + + err = mutex_lock_interruptible(&dytc_mutex); + if (err) + return err; + + if (profile == PLATFORM_PROFILE_BALANCED) { + /* To get back to balanced mode we just issue a reset command */ + err = dytc_command(DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + int perfmode; + + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); + if (err) + goto unlock; + } + /* Success - update current profile */ + dytc_current_profile = profile; +unlock: + mutex_unlock(&dytc_mutex); + return err; +} + +static void dytc_profile_refresh(void) +{ + enum platform_profile_option profile; + int output, err; + int perfmode; + + mutex_lock(&dytc_mutex); + err = dytc_cql_command(DYTC_CMD_GET, &output); + mutex_unlock(&dytc_mutex); + if (err) + return; + + perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; + convert_dytc_to_profile(perfmode, &profile); + if (profile != dytc_current_profile) { + dytc_current_profile = profile; + platform_profile_notify(); + } +} + +static struct platform_profile_handler dytc_profile = { + .profile_get = dytc_profile_get, + .profile_set = dytc_profile_set, +}; + +static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) +{ + int err, output; + + /* Setup supported modes */ + set_bit(PLATFORM_PROFILE_LOW_POWER, dytc_profile.choices); + set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices); + + dytc_profile_available = false; + err = dytc_command(DYTC_CMD_QUERY, &output); + /* + * If support isn't available (ENODEV) then don't return an error + * and don't create the sysfs group + */ + if (err == -ENODEV) + return 0; + /* For all other errors we can flag the failure */ + if (err) + return err; + + /* Check DYTC is enabled and supports mode setting */ + if (output & BIT(DYTC_QUERY_ENABLE_BIT)) { + /* Only DYTC v5.0 and later has this feature. */ + int dytc_version; + + dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + if (dytc_version >= 5) { + dbg_printk(TPACPI_DBG_INIT, + "DYTC version %d: thermal mode available\n", dytc_version); + /* Create platform_profile structure and register */ + err = platform_profile_register(&dytc_profile); + /* + * If for some reason platform_profiles aren't enabled + * don't quit terminally. + */ + if (err) + return 0; + + dytc_profile_available = true; + /* Ensure initial values are correct */ + dytc_profile_refresh(); + } + } + return 0; +} + +static void dytc_profile_exit(void) +{ + if (dytc_profile_available) { + dytc_profile_available = false; + platform_profile_remove(); + } +} + +static struct ibm_struct dytc_profile_driver_data = { + .name = "dytc-profile", + .exit = dytc_profile_exit, +}; + +/************************************************************************* + * Keyboard language interface + */ + +struct keyboard_lang_data { + const char *lang_str; + int lang_code; +}; + +static const struct keyboard_lang_data keyboard_lang_data[] = { + {"be", 0x080c}, + {"cz", 0x0405}, + {"da", 0x0406}, + {"de", 0x0c07}, + {"en", 0x0000}, + {"es", 0x2c0a}, + {"et", 0x0425}, + {"fr", 0x040c}, + {"fr-ch", 0x100c}, + {"hu", 0x040e}, + {"it", 0x0410}, + {"jp", 0x0411}, + {"nl", 0x0413}, + {"nn", 0x0414}, + {"pl", 0x0415}, + {"pt", 0x0816}, + {"sl", 0x041b}, + {"sv", 0x081d}, + {"tr", 0x041f}, +}; + +static int set_keyboard_lang_command(int command) +{ + acpi_handle sskl_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "SSKL", &sskl_handle))) { + /* Platform doesn't support SSKL */ + return -ENODEV; + } + + if (!acpi_evalf(sskl_handle, &output, NULL, "dd", command)) + return -EIO; + + return 0; +} + +static int get_keyboard_lang(int *output) +{ + acpi_handle gskl_handle; + int kbd_lang; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GSKL", &gskl_handle))) { + /* Platform doesn't support GSKL */ + return -ENODEV; + } + + if (!acpi_evalf(gskl_handle, &kbd_lang, NULL, "dd", 0x02000000)) + return -EIO; + + /* + * METHOD_ERR gets returned on devices where there are no special (e.g. '=', + * '(' and ')') keys which use layout dependent key-press emulation. + */ + if (kbd_lang & METHOD_ERR) + return -ENODEV; + + *output = kbd_lang; + + return 0; +} + +/* sysfs keyboard language entry */ +static ssize_t keyboard_lang_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int output, err, i, len = 0; + + err = get_keyboard_lang(&output); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { + if (i) + len += sysfs_emit_at(buf, len, "%s", " "); + + if (output == keyboard_lang_data[i].lang_code) { + len += sysfs_emit_at(buf, len, "[%s]", keyboard_lang_data[i].lang_str); + } else { + len += sysfs_emit_at(buf, len, "%s", keyboard_lang_data[i].lang_str); + } + } + len += sysfs_emit_at(buf, len, "\n"); + + return len; +} + +static ssize_t keyboard_lang_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, i; + bool lang_found = false; + int lang_code = 0; + + for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { + if (sysfs_streq(buf, keyboard_lang_data[i].lang_str)) { + lang_code = keyboard_lang_data[i].lang_code; + lang_found = true; + break; + } + } + + if (lang_found) { + lang_code = lang_code | 1 << 24; + + /* Set language code */ + err = set_keyboard_lang_command(lang_code); + if (err) + return err; + } else { + dev_err(&tpacpi_pdev->dev, "Unknown Keyboard language. Ignoring\n"); + return -EINVAL; + } + + tpacpi_disclose_usertask(attr->attr.name, + "keyboard language is set to %s\n", buf); + + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "keyboard_lang"); + + return count; +} +static DEVICE_ATTR_RW(keyboard_lang); + +static struct attribute *kbdlang_attributes[] = { + &dev_attr_keyboard_lang.attr, + NULL +}; + +static const struct attribute_group kbdlang_attr_group = { + .attrs = kbdlang_attributes, +}; + +static int tpacpi_kbdlang_init(struct ibm_init_struct *iibm) +{ + int err, output; + + err = get_keyboard_lang(&output); + /* + * If support isn't available (ENODEV) then don't return an error + * just don't create the sysfs group. + */ + if (err == -ENODEV) + return 0; + + if (err) + return err; + + /* Platform supports this feature - create the sysfs file */ + return sysfs_create_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); +} + +static void kbdlang_exit(void) +{ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); +} + +static struct ibm_struct kbdlang_driver_data = { + .name = "kbdlang", + .exit = kbdlang_exit, +}; + /**************************************************************************** **************************************************************************** * @@ -10031,8 +10471,12 @@ static void tpacpi_driver_event(const unsigned int hkey_event) mutex_unlock(&kbdlight_mutex); } - if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) + if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) { lapsensor_refresh(); + /* If we are already accessing DYTC then skip dytc update */ + if (!atomic_add_unless(&dytc_ignore_event, -1, 0)) + dytc_profile_refresh(); + } } static void hotkey_driver_event(const unsigned int scancode) @@ -10475,6 +10919,14 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_proxsensor_init, .data = &proxsensor_driver_data, }, + { + .init = tpacpi_dytc_profile_init, + .data = &dytc_profile_driver_data, + }, + { + .init = tpacpi_kbdlang_init, + .data = &kbdlang_driver_data, + }, }; static int __init set_ibm_param(const char *val, const struct kernel_param *kp) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index c4de932302d6..c44a6e8dceb8 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -382,6 +382,23 @@ static const struct ts_dmi_data jumper_ezpad_6_m4_data = { .properties = jumper_ezpad_6_m4_props, }; +static const struct property_entry jumper_ezpad_7_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 4), + PROPERTY_ENTRY_U32("touchscreen-min-y", 10), + PROPERTY_ENTRY_U32("touchscreen-size-x", 2044), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1526), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3680-jumper-ezpad-7.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,stuck-controller-bug"), + { } +}; + +static const struct ts_dmi_data jumper_ezpad_7_data = { + .acpi_name = "MSSL1680:00", + .properties = jumper_ezpad_7_props, +}; + static const struct property_entry jumper_ezpad_mini3_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 23), PROPERTY_ENTRY_U32("touchscreen-min-y", 16), @@ -1034,6 +1051,16 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_BIOS_VERSION, "Jumper8.S106x"), }, }, + { + /* Jumper EZpad 7 */ + .driver_data = (void *)&jumper_ezpad_7_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Jumper"), + DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"), + /* Jumper12x.WJ2012.bsBKRCP05 with the version dropped */ + DMI_MATCH(DMI_BIOS_VERSION, "Jumper12x.WJ2012.bsBKRCP"), + }, + }, { /* Jumper EZpad mini3 */ .driver_data = (void *)&jumper_ezpad_mini3_data, diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 5abdd29fb9f3..77c43134bc9e 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -122,15 +122,6 @@ config REGULATOR_AAT2870 If you have a AnalogicTech AAT2870 say Y to enable the regulator driver. -config REGULATOR_AB3100 - tristate "ST-Ericsson AB3100 Regulator functions" - depends on AB3100_CORE - default y if AB3100_CORE - help - These regulators correspond to functionality in the - AB3100 analog baseband dealing with power regulators - for the system. - config REGULATOR_AB8500 bool "ST-Ericsson AB8500 Power Regulators" depends on AB8500_CORE @@ -179,6 +170,14 @@ config REGULATOR_AS3722 AS3722 PMIC. This will enable support for all the software controllable DCDC/LDO regulators. +config REGULATOR_ATC260X + tristate "Actions Semi ATC260x PMIC Regulators" + depends on MFD_ATC260X + help + This driver provides support for the voltage regulators on the + ATC260x PMICs. This will enable support for all the software + controllable DCDC/LDO regulators. + config REGULATOR_AXP20X tristate "X-POWERS AXP20X PMIC Regulators" depends on MFD_AXP20X @@ -732,6 +731,16 @@ config REGULATOR_MT6311 This driver supports the control of different power rails of device through regulator interface. +config REGULATOR_MT6315 + tristate "MediaTek MT6315 PMIC" + depends on SPMI + select REGMAP_SPMI + help + Say y here to select this option to enable the power regulator of + MediaTek MT6315 PMIC. + This driver supports the control of different power rails of device + through regulator interface. + config REGULATOR_MT6323 tristate "MediaTek MT6323 PMIC" depends on MFD_MT6397 @@ -777,6 +786,16 @@ config REGULATOR_MT6397 This driver supports the control of different power rails of device through regulator interface. +config REGULATOR_MTK_DVFSRC + tristate "MediaTek DVFSRC regulator driver" + depends on MTK_DVFSRC + help + Say y here to control regulator by DVFSRC (dynamic voltage + and frequency scaling resource collector). + This driver supports to control regulators via the DVFSRC + of Mediatek. It allows for voting on regulator state + between multiple users. + config REGULATOR_PALMAS tristate "TI Palmas PMIC Regulators" depends on MFD_PALMAS @@ -828,6 +847,10 @@ config REGULATOR_PF8X00 Say y here to support the regulators found on the NXP PF8100/PF8121A/PF8200 PMIC. + Say M here if you want to support for the regulators found + on the NXP PF8100/PF8121A/PF8200 PMIC. The module will be named + "pf8x00-regulator". + config REGULATOR_PFUZE100 tristate "Freescale PFUZE100/200/3000/3001 regulator driver" depends on I2C && OF @@ -969,6 +992,16 @@ config REGULATOR_RT4801 This adds support for voltage regulators in Richtek RT4801 Display Bias IC. The device supports two regulators (DSVP/DSVN). +config REGULATOR_RT4831 + tristate "Richtek RT4831 DSV Regulators" + depends on MFD_RT4831 + help + This adds support for voltage regulators in Richtek RT4831. + There are three regulators (VLCM/DSVP/DSVN). + VLCM is a virtual voltage input for DSVP/DSVN inside IC. + And DSVP/DSVN is the real Vout range from 4V to 6.5V. + It's common used to provide the power for the display panel. + config REGULATOR_RT5033 tristate "Richtek RT5033 Regulators" depends on MFD_RT5033 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 680e539f6579..44d2f8bf4b74 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o obj-$(CONFIG_REGULATOR_CROS_EC) += cros-ec-regulator.o obj-$(CONFIG_REGULATOR_CPCAP) += cpcap-regulator.o obj-$(CONFIG_REGULATOR_AAT2870) += aat2870-regulator.o -obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500-ext.o ab8500.o obj-$(CONFIG_REGULATOR_ACT8865) += act8865-regulator.o obj-$(CONFIG_REGULATOR_ACT8945A) += act8945a-regulator.o @@ -27,6 +26,7 @@ obj-$(CONFIG_REGULATOR_ARIZONA_MICSUPP) += arizona-micsupp.o obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o +obj-$(CONFIG_REGULATOR_ATC260X) += atc260x-regulator.o obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o obj-$(CONFIG_REGULATOR_BD70528) += bd70528-regulator.o @@ -89,11 +89,13 @@ obj-$(CONFIG_REGULATOR_MP8859) += mp8859.o obj-$(CONFIG_REGULATOR_MP886X) += mp886x.o obj-$(CONFIG_REGULATOR_MPQ7920) += mpq7920.o obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o +obj-$(CONFIG_REGULATOR_MT6315) += mt6315-regulator.o obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o obj-$(CONFIG_REGULATOR_MT6358) += mt6358-regulator.o obj-$(CONFIG_REGULATOR_MT6360) += mt6360-regulator.o obj-$(CONFIG_REGULATOR_MT6380) += mt6380-regulator.o obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o +obj-$(CONFIG_REGULATOR_MTK_DVFSRC) += mtk-dvfsrc-regulator.o obj-$(CONFIG_REGULATOR_QCOM_LABIBB) += qcom-labibb-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o @@ -118,6 +120,7 @@ obj-$(CONFIG_REGULATOR_RK808) += rk808-regulator.o obj-$(CONFIG_REGULATOR_RN5T618) += rn5t618-regulator.o obj-$(CONFIG_REGULATOR_ROHM) += rohm-regulator.o obj-$(CONFIG_REGULATOR_RT4801) += rt4801-regulator.o +obj-$(CONFIG_REGULATOR_RT4831) += rt4831-regulator.o obj-$(CONFIG_REGULATOR_RT5033) += rt5033-regulator.o obj-$(CONFIG_REGULATOR_RTMV20) += rtmv20-regulator.o obj-$(CONFIG_REGULATOR_S2MPA01) += s2mpa01.o diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c deleted file mode 100644 index a544f45efe53..000000000000 --- a/drivers/regulator/ab3100.c +++ /dev/null @@ -1,724 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * drivers/regulator/ab3100.c - * - * Copyright (C) 2008-2009 ST-Ericsson AB - * Low-level control of the AB3100 IC Low Dropout (LDO) - * regulators, external regulator and buck converter - * Author: Mattias Wallin - * Author: Linus Walleij - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* LDO registers and some handy masking definitions for AB3100 */ -#define AB3100_LDO_A 0x40 -#define AB3100_LDO_C 0x41 -#define AB3100_LDO_D 0x42 -#define AB3100_LDO_E 0x43 -#define AB3100_LDO_E_SLEEP 0x44 -#define AB3100_LDO_F 0x45 -#define AB3100_LDO_G 0x46 -#define AB3100_LDO_H 0x47 -#define AB3100_LDO_H_SLEEP_MODE 0 -#define AB3100_LDO_H_SLEEP_EN 2 -#define AB3100_LDO_ON 4 -#define AB3100_LDO_H_VSEL_AC 5 -#define AB3100_LDO_K 0x48 -#define AB3100_LDO_EXT 0x49 -#define AB3100_BUCK 0x4A -#define AB3100_BUCK_SLEEP 0x4B -#define AB3100_REG_ON_MASK 0x10 - -/** - * struct ab3100_regulator - * A struct passed around the individual regulator functions - * @platform_device: platform device holding this regulator - * @dev: handle to the device - * @plfdata: AB3100 platform data passed in at probe time - * @regreg: regulator register number in the AB3100 - */ -struct ab3100_regulator { - struct device *dev; - struct ab3100_platform_data *plfdata; - u8 regreg; -}; - -/* The order in which registers are initialized */ -static const u8 ab3100_reg_init_order[AB3100_NUM_REGULATORS+2] = { - AB3100_LDO_A, - AB3100_LDO_C, - AB3100_LDO_E, - AB3100_LDO_E_SLEEP, - AB3100_LDO_F, - AB3100_LDO_G, - AB3100_LDO_H, - AB3100_LDO_K, - AB3100_LDO_EXT, - AB3100_BUCK, - AB3100_BUCK_SLEEP, - AB3100_LDO_D, -}; - -/* Preset (hardware defined) voltages for these regulators */ -#define LDO_A_VOLTAGE 2750000 -#define LDO_C_VOLTAGE 2650000 -#define LDO_D_VOLTAGE 2650000 - -static const unsigned int ldo_e_buck_typ_voltages[] = { - 1800000, - 1400000, - 1300000, - 1200000, - 1100000, - 1050000, - 900000, -}; - -static const unsigned int ldo_f_typ_voltages[] = { - 1800000, - 1400000, - 1300000, - 1200000, - 1100000, - 1050000, - 2500000, - 2650000, -}; - -static const unsigned int ldo_g_typ_voltages[] = { - 2850000, - 2750000, - 1800000, - 1500000, -}; - -static const unsigned int ldo_h_typ_voltages[] = { - 2750000, - 1800000, - 1500000, - 1200000, -}; - -static const unsigned int ldo_k_typ_voltages[] = { - 2750000, - 1800000, -}; - - -/* The regulator devices */ -static struct ab3100_regulator -ab3100_regulators[AB3100_NUM_REGULATORS] = { - { - .regreg = AB3100_LDO_A, - }, - { - .regreg = AB3100_LDO_C, - }, - { - .regreg = AB3100_LDO_D, - }, - { - .regreg = AB3100_LDO_E, - }, - { - .regreg = AB3100_LDO_F, - }, - { - .regreg = AB3100_LDO_G, - }, - { - .regreg = AB3100_LDO_H, - }, - { - .regreg = AB3100_LDO_K, - }, - { - .regreg = AB3100_LDO_EXT, - /* No voltages for the external regulator */ - }, - { - .regreg = AB3100_BUCK, - }, -}; - -/* - * General functions for enable, disable and is_enabled used for - * LDO: A,C,E,F,G,H,K,EXT and BUCK - */ -static int ab3100_enable_regulator(struct regulator_dev *reg) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - int err; - u8 regval; - - err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, - ®val); - if (err) { - dev_warn(®->dev, "failed to get regid %d value\n", - abreg->regreg); - return err; - } - - /* The regulator is already on, no reason to go further */ - if (regval & AB3100_REG_ON_MASK) - return 0; - - regval |= AB3100_REG_ON_MASK; - - err = abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, - regval); - if (err) { - dev_warn(®->dev, "failed to set regid %d value\n", - abreg->regreg); - return err; - } - - return 0; -} - -static int ab3100_disable_regulator(struct regulator_dev *reg) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - int err; - u8 regval; - - /* - * LDO D is a special regulator. When it is disabled, the entire - * system is shut down. So this is handled specially. - */ - pr_info("Called ab3100_disable_regulator\n"); - if (abreg->regreg == AB3100_LDO_D) { - dev_info(®->dev, "disabling LDO D - shut down system\n"); - /* Setting LDO D to 0x00 cuts the power to the SoC */ - return abx500_set_register_interruptible(abreg->dev, 0, - AB3100_LDO_D, 0x00U); - } - - /* - * All other regulators are handled here - */ - err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, - ®val); - if (err) { - dev_err(®->dev, "unable to get register 0x%x\n", - abreg->regreg); - return err; - } - regval &= ~AB3100_REG_ON_MASK; - return abx500_set_register_interruptible(abreg->dev, 0, abreg->regreg, - regval); -} - -static int ab3100_is_enabled_regulator(struct regulator_dev *reg) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - u8 regval; - int err; - - err = abx500_get_register_interruptible(abreg->dev, 0, abreg->regreg, - ®val); - if (err) { - dev_err(®->dev, "unable to get register 0x%x\n", - abreg->regreg); - return err; - } - - return regval & AB3100_REG_ON_MASK; -} - -static int ab3100_get_voltage_regulator(struct regulator_dev *reg) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - u8 regval; - int err; - - /* - * For variable types, read out setting and index into - * supplied voltage list. - */ - err = abx500_get_register_interruptible(abreg->dev, 0, - abreg->regreg, ®val); - if (err) { - dev_warn(®->dev, - "failed to get regulator value in register %02x\n", - abreg->regreg); - return err; - } - - /* The 3 highest bits index voltages */ - regval &= 0xE0; - regval >>= 5; - - if (regval >= reg->desc->n_voltages) { - dev_err(®->dev, - "regulator register %02x contains an illegal voltage setting\n", - abreg->regreg); - return -EINVAL; - } - - return reg->desc->volt_table[regval]; -} - -static int ab3100_set_voltage_regulator_sel(struct regulator_dev *reg, - unsigned selector) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - u8 regval; - int err; - - err = abx500_get_register_interruptible(abreg->dev, 0, - abreg->regreg, ®val); - if (err) { - dev_warn(®->dev, - "failed to get regulator register %02x\n", - abreg->regreg); - return err; - } - - /* The highest three bits control the variable regulators */ - regval &= ~0xE0; - regval |= (selector << 5); - - err = abx500_set_register_interruptible(abreg->dev, 0, - abreg->regreg, regval); - if (err) - dev_warn(®->dev, "failed to set regulator register %02x\n", - abreg->regreg); - - return err; -} - -static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg, - int uV) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - u8 regval; - int err; - int bestindex; - u8 targetreg; - - if (abreg->regreg == AB3100_LDO_E) - targetreg = AB3100_LDO_E_SLEEP; - else if (abreg->regreg == AB3100_BUCK) - targetreg = AB3100_BUCK_SLEEP; - else - return -EINVAL; - - /* LDO E and BUCK have special suspend voltages you can set */ - bestindex = regulator_map_voltage_iterate(reg, uV, uV); - - err = abx500_get_register_interruptible(abreg->dev, 0, - targetreg, ®val); - if (err) { - dev_warn(®->dev, - "failed to get regulator register %02x\n", - targetreg); - return err; - } - - /* The highest three bits control the variable regulators */ - regval &= ~0xE0; - regval |= (bestindex << 5); - - err = abx500_set_register_interruptible(abreg->dev, 0, - targetreg, regval); - if (err) - dev_warn(®->dev, "failed to set regulator register %02x\n", - abreg->regreg); - - return err; -} - -/* - * The external regulator can just define a fixed voltage. - */ -static int ab3100_get_voltage_regulator_external(struct regulator_dev *reg) -{ - struct ab3100_regulator *abreg = rdev_get_drvdata(reg); - - if (abreg->plfdata) - return abreg->plfdata->external_voltage; - else - /* TODO: encode external voltage into device tree */ - return 0; -} - -static const struct regulator_ops regulator_ops_fixed = { - .enable = ab3100_enable_regulator, - .disable = ab3100_disable_regulator, - .is_enabled = ab3100_is_enabled_regulator, -}; - -static const struct regulator_ops regulator_ops_variable = { - .enable = ab3100_enable_regulator, - .disable = ab3100_disable_regulator, - .is_enabled = ab3100_is_enabled_regulator, - .get_voltage = ab3100_get_voltage_regulator, - .set_voltage_sel = ab3100_set_voltage_regulator_sel, - .list_voltage = regulator_list_voltage_table, -}; - -static const struct regulator_ops regulator_ops_variable_sleepable = { - .enable = ab3100_enable_regulator, - .disable = ab3100_disable_regulator, - .is_enabled = ab3100_is_enabled_regulator, - .get_voltage = ab3100_get_voltage_regulator, - .set_voltage_sel = ab3100_set_voltage_regulator_sel, - .set_suspend_voltage = ab3100_set_suspend_voltage_regulator, - .list_voltage = regulator_list_voltage_table, -}; - -/* - * LDO EXT is an external regulator so it is really - * not possible to set any voltage locally here, AB3100 - * is an on/off switch plain an simple. The external - * voltage is defined in the board set-up if any. - */ -static const struct regulator_ops regulator_ops_external = { - .enable = ab3100_enable_regulator, - .disable = ab3100_disable_regulator, - .is_enabled = ab3100_is_enabled_regulator, - .get_voltage = ab3100_get_voltage_regulator_external, -}; - -static const struct regulator_desc -ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { - { - .name = "LDO_A", - .id = AB3100_LDO_A, - .ops = ®ulator_ops_fixed, - .n_voltages = 1, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .fixed_uV = LDO_A_VOLTAGE, - .enable_time = 200, - }, - { - .name = "LDO_C", - .id = AB3100_LDO_C, - .ops = ®ulator_ops_fixed, - .n_voltages = 1, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .fixed_uV = LDO_C_VOLTAGE, - .enable_time = 200, - }, - { - .name = "LDO_D", - .id = AB3100_LDO_D, - .ops = ®ulator_ops_fixed, - .n_voltages = 1, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .fixed_uV = LDO_D_VOLTAGE, - .enable_time = 200, - }, - { - .name = "LDO_E", - .id = AB3100_LDO_E, - .ops = ®ulator_ops_variable_sleepable, - .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), - .volt_table = ldo_e_buck_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 200, - }, - { - .name = "LDO_F", - .id = AB3100_LDO_F, - .ops = ®ulator_ops_variable, - .n_voltages = ARRAY_SIZE(ldo_f_typ_voltages), - .volt_table = ldo_f_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 600, - }, - { - .name = "LDO_G", - .id = AB3100_LDO_G, - .ops = ®ulator_ops_variable, - .n_voltages = ARRAY_SIZE(ldo_g_typ_voltages), - .volt_table = ldo_g_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 400, - }, - { - .name = "LDO_H", - .id = AB3100_LDO_H, - .ops = ®ulator_ops_variable, - .n_voltages = ARRAY_SIZE(ldo_h_typ_voltages), - .volt_table = ldo_h_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 200, - }, - { - .name = "LDO_K", - .id = AB3100_LDO_K, - .ops = ®ulator_ops_variable, - .n_voltages = ARRAY_SIZE(ldo_k_typ_voltages), - .volt_table = ldo_k_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 200, - }, - { - .name = "LDO_EXT", - .id = AB3100_LDO_EXT, - .ops = ®ulator_ops_external, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - }, - { - .name = "BUCK", - .id = AB3100_BUCK, - .ops = ®ulator_ops_variable_sleepable, - .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages), - .volt_table = ldo_e_buck_typ_voltages, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, - .enable_time = 1000, - }, -}; - -static int ab3100_regulator_register(struct platform_device *pdev, - struct ab3100_platform_data *plfdata, - struct regulator_init_data *init_data, - struct device_node *np, - unsigned long id) -{ - const struct regulator_desc *desc; - struct ab3100_regulator *reg; - struct regulator_dev *rdev; - struct regulator_config config = { }; - int err, i; - - for (i = 0; i < AB3100_NUM_REGULATORS; i++) { - desc = &ab3100_regulator_desc[i]; - if (desc->id == id) - break; - } - if (desc->id != id) - return -ENODEV; - - /* Same index used for this array */ - reg = &ab3100_regulators[i]; - - /* - * Initialize per-regulator struct. - * Inherit platform data, this comes down from the - * i2c boarddata, from the machine. So if you want to - * see what it looks like for a certain machine, go - * into the machine I2C setup. - */ - reg->dev = &pdev->dev; - if (plfdata) { - reg->plfdata = plfdata; - config.init_data = &plfdata->reg_constraints[i]; - } else if (np) { - config.of_node = np; - config.init_data = init_data; - } - config.dev = &pdev->dev; - config.driver_data = reg; - - rdev = devm_regulator_register(&pdev->dev, desc, &config); - if (IS_ERR(rdev)) { - err = PTR_ERR(rdev); - dev_err(&pdev->dev, - "%s: failed to register regulator %s err %d\n", - __func__, desc->name, - err); - return err; - } - - return 0; -} - -static struct of_regulator_match ab3100_regulator_matches[] = { - { .name = "ab3100_ldo_a", .driver_data = (void *) AB3100_LDO_A, }, - { .name = "ab3100_ldo_c", .driver_data = (void *) AB3100_LDO_C, }, - { .name = "ab3100_ldo_d", .driver_data = (void *) AB3100_LDO_D, }, - { .name = "ab3100_ldo_e", .driver_data = (void *) AB3100_LDO_E, }, - { .name = "ab3100_ldo_f", .driver_data = (void *) AB3100_LDO_F }, - { .name = "ab3100_ldo_g", .driver_data = (void *) AB3100_LDO_G }, - { .name = "ab3100_ldo_h", .driver_data = (void *) AB3100_LDO_H }, - { .name = "ab3100_ldo_k", .driver_data = (void *) AB3100_LDO_K }, - { .name = "ab3100_ext", .driver_data = (void *) AB3100_LDO_EXT }, - { .name = "ab3100_buck", .driver_data = (void *) AB3100_BUCK }, -}; - -/* - * Initial settings of ab3100 registers. - * Common for below LDO regulator settings are that - * bit 7-5 controls voltage. Bit 4 turns regulator ON(1) or OFF(0). - * Bit 3-2 controls sleep enable and bit 1-0 controls sleep mode. - */ -/* LDO_A 0x16: 2.75V, ON, SLEEP_A, SLEEP OFF GND */ -#define LDO_A_SETTING 0x16 -/* LDO_C 0x10: 2.65V, ON, SLEEP_A or B, SLEEP full power */ -#define LDO_C_SETTING 0x10 -/* LDO_D 0x10: 2.65V, ON, sleep mode not used */ -#define LDO_D_SETTING 0x10 -/* LDO_E 0x10: 1.8V, ON, SLEEP_A or B, SLEEP full power */ -#define LDO_E_SETTING 0x10 -/* LDO_E SLEEP 0x00: 1.8V, not used, SLEEP_A or B, not used */ -#define LDO_E_SLEEP_SETTING 0x00 -/* LDO_F 0xD0: 2.5V, ON, SLEEP_A or B, SLEEP full power */ -#define LDO_F_SETTING 0xD0 -/* LDO_G 0x00: 2.85V, OFF, SLEEP_A or B, SLEEP full power */ -#define LDO_G_SETTING 0x00 -/* LDO_H 0x18: 2.75V, ON, SLEEP_B, SLEEP full power */ -#define LDO_H_SETTING 0x18 -/* LDO_K 0x00: 2.75V, OFF, SLEEP_A or B, SLEEP full power */ -#define LDO_K_SETTING 0x00 -/* LDO_EXT 0x00: Voltage not set, OFF, not used, not used */ -#define LDO_EXT_SETTING 0x00 -/* BUCK 0x7D: 1.2V, ON, SLEEP_A and B, SLEEP low power */ -#define BUCK_SETTING 0x7D -/* BUCK SLEEP 0xAC: 1.05V, Not used, SLEEP_A and B, Not used */ -#define BUCK_SLEEP_SETTING 0xAC - -static const u8 ab3100_reg_initvals[] = { - LDO_A_SETTING, - LDO_C_SETTING, - LDO_E_SETTING, - LDO_E_SLEEP_SETTING, - LDO_F_SETTING, - LDO_G_SETTING, - LDO_H_SETTING, - LDO_K_SETTING, - LDO_EXT_SETTING, - BUCK_SETTING, - BUCK_SLEEP_SETTING, - LDO_D_SETTING, -}; - -static int -ab3100_regulator_of_probe(struct platform_device *pdev, struct device_node *np) -{ - int err, i; - - /* - * Set up the regulator registers, as was previously done with - * platform data. - */ - /* Set up regulators */ - for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { - err = abx500_set_register_interruptible(&pdev->dev, 0, - ab3100_reg_init_order[i], - ab3100_reg_initvals[i]); - if (err) { - dev_err(&pdev->dev, "regulator initialization failed with error %d\n", - err); - return err; - } - } - - for (i = 0; i < ARRAY_SIZE(ab3100_regulator_matches); i++) { - err = ab3100_regulator_register( - pdev, NULL, ab3100_regulator_matches[i].init_data, - ab3100_regulator_matches[i].of_node, - (unsigned long)ab3100_regulator_matches[i].driver_data); - if (err) - return err; - } - - return 0; -} - - -static int ab3100_regulators_probe(struct platform_device *pdev) -{ - struct ab3100_platform_data *plfdata = dev_get_platdata(&pdev->dev); - struct device_node *np = pdev->dev.of_node; - int err = 0; - u8 data; - int i; - - /* Check chip state */ - err = abx500_get_register_interruptible(&pdev->dev, 0, - AB3100_LDO_D, &data); - if (err) { - dev_err(&pdev->dev, "could not read initial status of LDO_D\n"); - return err; - } - if (data & 0x10) - dev_notice(&pdev->dev, - "chip is already in active mode (Warm start)\n"); - else - dev_notice(&pdev->dev, - "chip is in inactive mode (Cold start)\n"); - - if (np) { - err = of_regulator_match(&pdev->dev, np, - ab3100_regulator_matches, - ARRAY_SIZE(ab3100_regulator_matches)); - if (err < 0) { - dev_err(&pdev->dev, - "Error parsing regulator init data: %d\n", err); - return err; - } - return ab3100_regulator_of_probe(pdev, np); - } - - /* Set up regulators */ - for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) { - err = abx500_set_register_interruptible(&pdev->dev, 0, - ab3100_reg_init_order[i], - plfdata->reg_initvals[i]); - if (err) { - dev_err(&pdev->dev, "regulator initialization failed with error %d\n", - err); - return err; - } - } - - /* Register the regulators */ - for (i = 0; i < AB3100_NUM_REGULATORS; i++) { - const struct regulator_desc *desc = &ab3100_regulator_desc[i]; - - err = ab3100_regulator_register(pdev, plfdata, NULL, NULL, - desc->id); - if (err) - return err; - } - - return 0; -} - -static struct platform_driver ab3100_regulators_driver = { - .driver = { - .name = "ab3100-regulators", - }, - .probe = ab3100_regulators_probe, -}; - -static __init int ab3100_regulators_init(void) -{ - return platform_driver_register(&ab3100_regulators_driver); -} - -static __exit void ab3100_regulators_exit(void) -{ - platform_driver_unregister(&ab3100_regulators_driver); -} - -subsys_initcall(ab3100_regulators_init); -module_exit(ab3100_regulators_exit); - -MODULE_AUTHOR("Mattias Wallin "); -MODULE_DESCRIPTION("AB3100 Regulator driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:ab3100-regulators"); diff --git a/drivers/regulator/ab8500-ext.c b/drivers/regulator/ab8500-ext.c index 8bb43a671ded..4f26952caa56 100644 --- a/drivers/regulator/ab8500-ext.c +++ b/drivers/regulator/ab8500-ext.c @@ -22,403 +22,17 @@ #include #include #include -#include -static struct regulator_consumer_supply ab8500_vaux1_consumers[] = { - /* Main display, u8500 R3 uib */ - REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), - /* Main display, u8500 uib and ST uib */ - REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.0"), - /* Secondary display, ST uib */ - REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.1"), - /* SFH7741 proximity sensor */ - REGULATOR_SUPPLY("vcc", "gpio-keys.0"), - /* BH1780GLS ambient light sensor */ - REGULATOR_SUPPLY("vcc", "2-0029"), - /* lsm303dlh accelerometer */ - REGULATOR_SUPPLY("vdd", "2-0018"), - /* lsm303dlhc accelerometer */ - REGULATOR_SUPPLY("vdd", "2-0019"), - /* lsm303dlh magnetometer */ - REGULATOR_SUPPLY("vdd", "2-001e"), - /* Rohm BU21013 Touchscreen devices */ - REGULATOR_SUPPLY("avdd", "3-005c"), - REGULATOR_SUPPLY("avdd", "3-005d"), - /* Synaptics RMI4 Touchscreen device */ - REGULATOR_SUPPLY("vdd", "3-004b"), - /* L3G4200D Gyroscope device */ - REGULATOR_SUPPLY("vdd", "2-0068"), - /* Ambient light sensor device */ - REGULATOR_SUPPLY("vdd", "3-0029"), - /* Pressure sensor device */ - REGULATOR_SUPPLY("vdd", "2-005c"), - /* Cypress TrueTouch Touchscreen device */ - REGULATOR_SUPPLY("vcpin", "spi8.0"), - /* Camera device */ - REGULATOR_SUPPLY("vaux12v5", "mmio_camera"), +/* AB8500 external regulators */ +enum ab8500_ext_regulator_id { + AB8500_EXT_SUPPLY1, + AB8500_EXT_SUPPLY2, + AB8500_EXT_SUPPLY3, + AB8500_NUM_EXT_REGULATORS, }; -static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { - /* On-board eMMC power */ - REGULATOR_SUPPLY("vmmc", "sdi4"), - /* AB8500 audio codec */ - REGULATOR_SUPPLY("vcc-N2158", "ab8500-codec.0"), - /* AB8500 accessory detect 1 */ - REGULATOR_SUPPLY("vcc-N2158", "ab8500-acc-det.0"), - /* AB8500 Tv-out device */ - REGULATOR_SUPPLY("vcc-N2158", "mcde_tv_ab8500.4"), - /* AV8100 HDMI device */ - REGULATOR_SUPPLY("vcc-N2158", "av8100_hdmi.3"), -}; - -static struct regulator_consumer_supply ab8500_vaux3_consumers[] = { - REGULATOR_SUPPLY("v-SD-STM", "stm"), - /* External MMC slot power */ - REGULATOR_SUPPLY("vmmc", "sdi0"), -}; - -static struct regulator_consumer_supply ab8500_vtvout_consumers[] = { - /* TV-out DENC supply */ - REGULATOR_SUPPLY("vtvout", "ab8500-denc.0"), - /* Internal general-purpose ADC */ - REGULATOR_SUPPLY("vddadc", "ab8500-gpadc.0"), - /* ADC for charger */ - REGULATOR_SUPPLY("vddadc", "ab8500-charger.0"), - /* AB8500 Tv-out device */ - REGULATOR_SUPPLY("vtvout", "mcde_tv_ab8500.4"), -}; - -static struct regulator_consumer_supply ab8500_vaud_consumers[] = { - /* AB8500 audio-codec main supply */ - REGULATOR_SUPPLY("vaud", "ab8500-codec.0"), -}; - -static struct regulator_consumer_supply ab8500_vamic1_consumers[] = { - /* AB8500 audio-codec Mic1 supply */ - REGULATOR_SUPPLY("vamic1", "ab8500-codec.0"), -}; - -static struct regulator_consumer_supply ab8500_vamic2_consumers[] = { - /* AB8500 audio-codec Mic2 supply */ - REGULATOR_SUPPLY("vamic2", "ab8500-codec.0"), -}; - -static struct regulator_consumer_supply ab8500_vdmic_consumers[] = { - /* AB8500 audio-codec DMic supply */ - REGULATOR_SUPPLY("vdmic", "ab8500-codec.0"), -}; - -static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { - /* SoC core supply, no device */ - REGULATOR_SUPPLY("v-intcore", NULL), - /* USB Transceiver */ - REGULATOR_SUPPLY("vddulpivio18", "ab8500-usb.0"), - /* Handled by abx500 clk driver */ - REGULATOR_SUPPLY("v-intcore", "abx500-clk.0"), -}; - -static struct regulator_consumer_supply ab8500_vana_consumers[] = { - /* DB8500 DSI */ - REGULATOR_SUPPLY("vdddsi1v2", "mcde"), - REGULATOR_SUPPLY("vdddsi1v2", "b2r2_core"), - REGULATOR_SUPPLY("vdddsi1v2", "b2r2_1_core"), - REGULATOR_SUPPLY("vdddsi1v2", "dsilink.0"), - REGULATOR_SUPPLY("vdddsi1v2", "dsilink.1"), - REGULATOR_SUPPLY("vdddsi1v2", "dsilink.2"), - /* DB8500 CSI */ - REGULATOR_SUPPLY("vddcsi1v2", "mmio_camera"), -}; - -/* ab8500 regulator register initialization */ -static struct ab8500_regulator_reg_init ab8500_reg_init[] = { - /* - * VanaRequestCtrl = HP/LP depending on VxRequest - * VextSupply1RequestCtrl = HP/LP depending on VxRequest - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0xf0, 0x00), - /* - * VextSupply2RequestCtrl = HP/LP depending on VxRequest - * VextSupply3RequestCtrl = HP/LP depending on VxRequest - * Vaux1RequestCtrl = HP/LP depending on VxRequest - * Vaux2RequestCtrl = HP/LP depending on VxRequest - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0xff, 0x00), - /* - * Vaux3RequestCtrl = HP/LP depending on VxRequest - * SwHPReq = Control through SWValid disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x07, 0x00), - /* - * VanaSysClkReq1HPValid = disabled - * Vaux1SysClkReq1HPValid = disabled - * Vaux2SysClkReq1HPValid = disabled - * Vaux3SysClkReq1HPValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0xe8, 0x00), - /* - * VextSupply1SysClkReq1HPValid = disabled - * VextSupply2SysClkReq1HPValid = disabled - * VextSupply3SysClkReq1HPValid = SysClkReq1 controlled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x70, 0x40), - /* - * VanaHwHPReq1Valid = disabled - * Vaux1HwHPreq1Valid = disabled - * Vaux2HwHPReq1Valid = disabled - * Vaux3HwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0xe8, 0x00), - /* - * VextSupply1HwHPReq1Valid = disabled - * VextSupply2HwHPReq1Valid = disabled - * VextSupply3HwHPReq1Valid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x07, 0x00), - /* - * VanaHwHPReq2Valid = disabled - * Vaux1HwHPReq2Valid = disabled - * Vaux2HwHPReq2Valid = disabled - * Vaux3HwHPReq2Valid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0xe8, 0x00), - /* - * VextSupply1HwHPReq2Valid = disabled - * VextSupply2HwHPReq2Valid = disabled - * VextSupply3HwHPReq2Valid = HWReq2 controlled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x07, 0x04), - /* - * VanaSwHPReqValid = disabled - * Vaux1SwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0xa0, 0x00), - /* - * Vaux2SwHPReqValid = disabled - * Vaux3SwHPReqValid = disabled - * VextSupply1SwHPReqValid = disabled - * VextSupply2SwHPReqValid = disabled - * VextSupply3SwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x1f, 0x00), - /* - * SysClkReq2Valid1 = SysClkReq2 controlled - * SysClkReq3Valid1 = disabled - * SysClkReq4Valid1 = SysClkReq4 controlled - * SysClkReq5Valid1 = disabled - * SysClkReq6Valid1 = SysClkReq6 controlled - * SysClkReq7Valid1 = disabled - * SysClkReq8Valid1 = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0xfe, 0x2a), - /* - * SysClkReq2Valid2 = disabled - * SysClkReq3Valid2 = disabled - * SysClkReq4Valid2 = disabled - * SysClkReq5Valid2 = disabled - * SysClkReq6Valid2 = SysClkReq6 controlled - * SysClkReq7Valid2 = disabled - * SysClkReq8Valid2 = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0xfe, 0x20), - /* - * VTVoutEna = disabled - * Vintcore12Ena = disabled - * Vintcore12Sel = 1.25 V - * Vintcore12LP = inactive (HP) - * VTVoutLP = inactive (HP) - */ - INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0xfe, 0x10), - /* - * VaudioEna = disabled - * VdmicEna = disabled - * Vamic1Ena = disabled - * Vamic2Ena = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x1e, 0x00), - /* - * Vamic1_dzout = high-Z when Vamic1 is disabled - * Vamic2_dzout = high-Z when Vamic2 is disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x03, 0x00), - /* - * VPll = Hw controlled (NOTE! PRCMU bits) - * VanaRegu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x0f, 0x02), - /* - * VrefDDREna = disabled - * VrefDDRSleepMode = inactive (no pulldown) - */ - INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x03, 0x00), - /* - * VextSupply1Regu = force LP - * VextSupply2Regu = force OFF - * VextSupply3Regu = force HP (-> STBB2=LP and TPS=LP) - * ExtSupply2Bypass = ExtSupply12LPn ball is 0 when Ena is 0 - * ExtSupply3Bypass = ExtSupply3LPn ball is 0 when Ena is 0 - */ - INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0xff, 0x13), - /* - * Vaux1Regu = force HP - * Vaux2Regu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x0f, 0x01), - /* - * Vaux3Regu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x03, 0x00), - /* - * Vaux1Sel = 2.8 V - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x0f, 0x0C), - /* - * Vaux2Sel = 2.9 V - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0f, 0x0d), - /* - * Vaux3Sel = 2.91 V - */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07, 0x07), - /* - * VextSupply12LP = disabled (no LP) - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x01, 0x00), - /* - * Vaux1Disch = short discharge time - * Vaux2Disch = short discharge time - * Vaux3Disch = short discharge time - * Vintcore12Disch = short discharge time - * VTVoutDisch = short discharge time - * VaudioDisch = short discharge time - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0xfc, 0x00), - /* - * VanaDisch = short discharge time - * VdmicPullDownEna = pulldown disabled when Vdmic is disabled - * VdmicDisch = short discharge time - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x16, 0x00), -}; - -/* AB8500 regulators */ -static struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { - /* supplies to the display/camera */ - [AB8500_LDO_AUX1] = { - .supply_regulator = "ab8500-ext-supply3", - .constraints = { - .name = "V-DISPLAY", - .min_uV = 2800000, - .max_uV = 3300000, - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS, - .boot_on = 1, /* display is on at boot */ - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux1_consumers), - .consumer_supplies = ab8500_vaux1_consumers, - }, - /* supplies to the on-board eMMC */ - [AB8500_LDO_AUX2] = { - .supply_regulator = "ab8500-ext-supply3", - .constraints = { - .name = "V-eMMC1", - .min_uV = 1100000, - .max_uV = 3300000, - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS | - REGULATOR_CHANGE_MODE, - .valid_modes_mask = REGULATOR_MODE_NORMAL | - REGULATOR_MODE_IDLE, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux2_consumers), - .consumer_supplies = ab8500_vaux2_consumers, - }, - /* supply for VAUX3, supplies to SDcard slots */ - [AB8500_LDO_AUX3] = { - .supply_regulator = "ab8500-ext-supply3", - .constraints = { - .name = "V-MMC-SD", - .min_uV = 1100000, - .max_uV = 3300000, - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS | - REGULATOR_CHANGE_MODE, - .valid_modes_mask = REGULATOR_MODE_NORMAL | - REGULATOR_MODE_IDLE, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux3_consumers), - .consumer_supplies = ab8500_vaux3_consumers, - }, - /* supply for tvout, gpadc, TVOUT LDO */ - [AB8500_LDO_TVOUT] = { - .constraints = { - .name = "V-TVOUT", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vtvout_consumers), - .consumer_supplies = ab8500_vtvout_consumers, - }, - /* supply for ab8500-vaudio, VAUDIO LDO */ - [AB8500_LDO_AUDIO] = { - .constraints = { - .name = "V-AUD", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vaud_consumers), - .consumer_supplies = ab8500_vaud_consumers, - }, - /* supply for v-anamic1 VAMic1-LDO */ - [AB8500_LDO_ANAMIC1] = { - .constraints = { - .name = "V-AMIC1", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic1_consumers), - .consumer_supplies = ab8500_vamic1_consumers, - }, - /* supply for v-amic2, VAMIC2 LDO, reuse constants for AMIC1 */ - [AB8500_LDO_ANAMIC2] = { - .constraints = { - .name = "V-AMIC2", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic2_consumers), - .consumer_supplies = ab8500_vamic2_consumers, - }, - /* supply for v-dmic, VDMIC LDO */ - [AB8500_LDO_DMIC] = { - .constraints = { - .name = "V-DMIC", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vdmic_consumers), - .consumer_supplies = ab8500_vdmic_consumers, - }, - /* supply for v-intcore12, VINTCORE12 LDO */ - [AB8500_LDO_INTCORE] = { - .constraints = { - .name = "V-INTCORE", - .min_uV = 1250000, - .max_uV = 1350000, - .input_uV = 1800000, - .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS | - REGULATOR_CHANGE_MODE | - REGULATOR_CHANGE_DRMS, - .valid_modes_mask = REGULATOR_MODE_NORMAL | - REGULATOR_MODE_IDLE, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vintcore_consumers), - .consumer_supplies = ab8500_vintcore_consumers, - }, - /* supply for U8500 CSI-DSI, VANA LDO */ - [AB8500_LDO_ANA] = { - .constraints = { - .name = "V-CSI-DSI", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = ARRAY_SIZE(ab8500_vana_consumers), - .consumer_supplies = ab8500_vana_consumers, - }, +struct ab8500_ext_regulator_cfg { + bool hwreq; /* requires hw mode or high power mode */ }; /* supply for VextSupply3 */ @@ -465,15 +79,6 @@ static struct regulator_init_data ab8500_ext_regulators[] = { }, }; -static struct ab8500_regulator_platform_data ab8500_regulator_plat_data = { - .reg_init = ab8500_reg_init, - .num_reg_init = ARRAY_SIZE(ab8500_reg_init), - .regulator = ab8500_regulators, - .num_regulator = ARRAY_SIZE(ab8500_regulators), - .ext_regulator = ab8500_ext_regulators, - .num_ext_regulator = ARRAY_SIZE(ab8500_ext_regulators), -}; - /** * struct ab8500_ext_regulator_info - ab8500 regulator information * @dev: device pointer @@ -788,7 +393,6 @@ static struct ab8500_ext_regulator_info static int ab8500_ext_regulator_probe(struct platform_device *pdev) { struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); - struct ab8500_regulator_platform_data *pdata = &ab8500_regulator_plat_data; struct regulator_config config = { }; struct regulator_dev *rdev; int i; @@ -798,12 +402,6 @@ static int ab8500_ext_regulator_probe(struct platform_device *pdev) return -EINVAL; } - /* make sure the platform data has the correct size */ - if (pdata->num_ext_regulator != ARRAY_SIZE(ab8500_ext_regulator_info)) { - dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); - return -EINVAL; - } - /* check for AB8500 2.x */ if (is_ab8500_2p0_or_earlier(ab8500)) { struct ab8500_ext_regulator_info *info; @@ -823,11 +421,11 @@ static int ab8500_ext_regulator_probe(struct platform_device *pdev) info = &ab8500_ext_regulator_info[i]; info->dev = &pdev->dev; info->cfg = (struct ab8500_ext_regulator_cfg *) - pdata->ext_regulator[i].driver_data; + ab8500_ext_regulators[i].driver_data; config.dev = &pdev->dev; config.driver_data = info; - config.init_data = &pdata->ext_regulator[i]; + config.init_data = &ab8500_ext_regulators[i]; /* register regulator with framework */ rdev = devm_regulator_register(&pdev->dev, &info->desc, diff --git a/drivers/regulator/ab8500.c b/drivers/regulator/ab8500.c index 47b8b6f7b571..23a401734a98 100644 --- a/drivers/regulator/ab8500.c +++ b/drivers/regulator/ab8500.c @@ -25,9 +25,123 @@ #include #include #include -#include #include +/* AB8500 regulators */ +enum ab8500_regulator_id { + AB8500_LDO_AUX1, + AB8500_LDO_AUX2, + AB8500_LDO_AUX3, + AB8500_LDO_INTCORE, + AB8500_LDO_TVOUT, + AB8500_LDO_AUDIO, + AB8500_LDO_ANAMIC1, + AB8500_LDO_ANAMIC2, + AB8500_LDO_DMIC, + AB8500_LDO_ANA, + AB8500_NUM_REGULATORS, +}; + +/* AB8505 regulators */ +enum ab8505_regulator_id { + AB8505_LDO_AUX1, + AB8505_LDO_AUX2, + AB8505_LDO_AUX3, + AB8505_LDO_AUX4, + AB8505_LDO_AUX5, + AB8505_LDO_AUX6, + AB8505_LDO_INTCORE, + AB8505_LDO_ADC, + AB8505_LDO_AUDIO, + AB8505_LDO_ANAMIC1, + AB8505_LDO_ANAMIC2, + AB8505_LDO_AUX8, + AB8505_LDO_ANA, + AB8505_NUM_REGULATORS, +}; + +/* AB8500 registers */ +enum ab8500_regulator_reg { + AB8500_REGUREQUESTCTRL2, + AB8500_REGUREQUESTCTRL3, + AB8500_REGUREQUESTCTRL4, + AB8500_REGUSYSCLKREQ1HPVALID1, + AB8500_REGUSYSCLKREQ1HPVALID2, + AB8500_REGUHWHPREQ1VALID1, + AB8500_REGUHWHPREQ1VALID2, + AB8500_REGUHWHPREQ2VALID1, + AB8500_REGUHWHPREQ2VALID2, + AB8500_REGUSWHPREQVALID1, + AB8500_REGUSWHPREQVALID2, + AB8500_REGUSYSCLKREQVALID1, + AB8500_REGUSYSCLKREQVALID2, + AB8500_REGUMISC1, + AB8500_VAUDIOSUPPLY, + AB8500_REGUCTRL1VAMIC, + AB8500_VPLLVANAREGU, + AB8500_VREFDDR, + AB8500_EXTSUPPLYREGU, + AB8500_VAUX12REGU, + AB8500_VRF1VAUX3REGU, + AB8500_VAUX1SEL, + AB8500_VAUX2SEL, + AB8500_VRF1VAUX3SEL, + AB8500_REGUCTRL2SPARE, + AB8500_REGUCTRLDISCH, + AB8500_REGUCTRLDISCH2, + AB8500_NUM_REGULATOR_REGISTERS, +}; + +/* AB8505 registers */ +enum ab8505_regulator_reg { + AB8505_REGUREQUESTCTRL1, + AB8505_REGUREQUESTCTRL2, + AB8505_REGUREQUESTCTRL3, + AB8505_REGUREQUESTCTRL4, + AB8505_REGUSYSCLKREQ1HPVALID1, + AB8505_REGUSYSCLKREQ1HPVALID2, + AB8505_REGUHWHPREQ1VALID1, + AB8505_REGUHWHPREQ1VALID2, + AB8505_REGUHWHPREQ2VALID1, + AB8505_REGUHWHPREQ2VALID2, + AB8505_REGUSWHPREQVALID1, + AB8505_REGUSWHPREQVALID2, + AB8505_REGUSYSCLKREQVALID1, + AB8505_REGUSYSCLKREQVALID2, + AB8505_REGUVAUX4REQVALID, + AB8505_REGUMISC1, + AB8505_VAUDIOSUPPLY, + AB8505_REGUCTRL1VAMIC, + AB8505_VSMPSAREGU, + AB8505_VSMPSBREGU, + AB8505_VSAFEREGU, /* NOTE! PRCMU register */ + AB8505_VPLLVANAREGU, + AB8505_EXTSUPPLYREGU, + AB8505_VAUX12REGU, + AB8505_VRF1VAUX3REGU, + AB8505_VSMPSASEL1, + AB8505_VSMPSASEL2, + AB8505_VSMPSASEL3, + AB8505_VSMPSBSEL1, + AB8505_VSMPSBSEL2, + AB8505_VSMPSBSEL3, + AB8505_VSAFESEL1, /* NOTE! PRCMU register */ + AB8505_VSAFESEL2, /* NOTE! PRCMU register */ + AB8505_VSAFESEL3, /* NOTE! PRCMU register */ + AB8505_VAUX1SEL, + AB8505_VAUX2SEL, + AB8505_VRF1VAUX3SEL, + AB8505_VAUX4REQCTRL, + AB8505_VAUX4REGU, + AB8505_VAUX4SEL, + AB8505_REGUCTRLDISCH, + AB8505_REGUCTRLDISCH2, + AB8505_REGUCTRLDISCH3, + AB8505_CTRLVAUX5, + AB8505_CTRLVAUX6, + AB8505_NUM_REGULATOR_REGISTERS, +}; + /** * struct ab8500_shared_mode - is used when mode is shared between * two regulators. diff --git a/drivers/regulator/atc260x-regulator.c b/drivers/regulator/atc260x-regulator.c new file mode 100644 index 000000000000..d8b429955d33 --- /dev/null +++ b/drivers/regulator/atc260x-regulator.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Regulator driver for ATC260x PMICs +// +// Copyright (C) 2019 Manivannan Sadhasivam +// Copyright (C) 2020 Cristian Ciocaltea + +#include +#include +#include +#include +#include + +struct atc260x_regulator_data { + int voltage_time_dcdc; + int voltage_time_ldo; +}; + +static const struct linear_range atc2603c_dcdc_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(1300000, 0, 13, 50000), + REGULATOR_LINEAR_RANGE(1950000, 14, 15, 100000), +}; + +static const struct linear_range atc2609a_dcdc_voltage_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 127, 6250), + REGULATOR_LINEAR_RANGE(1400000, 128, 232, 25000), +}; + +static const struct linear_range atc2609a_ldo_voltage_ranges0[] = { + REGULATOR_LINEAR_RANGE(700000, 0, 15, 100000), + REGULATOR_LINEAR_RANGE(2100000, 16, 28, 100000), +}; + +static const struct linear_range atc2609a_ldo_voltage_ranges1[] = { + REGULATOR_LINEAR_RANGE(850000, 0, 15, 100000), + REGULATOR_LINEAR_RANGE(2100000, 16, 27, 100000), +}; + +static const unsigned int atc260x_ldo_voltage_range_sel[] = { + 0x0, 0x1, +}; + +static int atc260x_dcdc_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct atc260x_regulator_data *data = rdev_get_drvdata(rdev); + + if (new_selector > old_selector) + return data->voltage_time_dcdc; + + return 0; +} + +static int atc260x_ldo_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + struct atc260x_regulator_data *data = rdev_get_drvdata(rdev); + + if (new_selector > old_selector) + return data->voltage_time_ldo; + + return 0; +} + +static const struct regulator_ops atc260x_dcdc_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_dcdc_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_ldo_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_ldo_bypass_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_ldo_set_voltage_time_sel, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, +}; + +static const struct regulator_ops atc260x_ldo_bypass_discharge_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_ldo_set_voltage_time_sel, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, +}; + +static const struct regulator_ops atc260x_dcdc_range_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_dcdc_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_ldo_range_pick_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_pickable_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_pickable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_pickable_regmap, + .set_voltage_time_sel = atc260x_ldo_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_dcdc_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_dcdc_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_ldo_fixed_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = atc260x_ldo_set_voltage_time_sel, +}; + +static const struct regulator_ops atc260x_no_ops = { +}; + +/* + * Note LDO8 is not documented in datasheet (v2.4), but supported + * in the vendor's driver implementation (xapp-le-kernel). + */ +enum atc2603c_reg_ids { + ATC2603C_ID_DCDC1, + ATC2603C_ID_DCDC2, + ATC2603C_ID_DCDC3, + ATC2603C_ID_LDO1, + ATC2603C_ID_LDO2, + ATC2603C_ID_LDO3, + ATC2603C_ID_LDO5, + ATC2603C_ID_LDO6, + ATC2603C_ID_LDO7, + ATC2603C_ID_LDO8, + ATC2603C_ID_LDO11, + ATC2603C_ID_LDO12, + ATC2603C_ID_SWITCHLDO1, + ATC2603C_ID_MAX, +}; + +#define atc2603c_reg_desc_dcdc(num, min, step, n_volt, vsel_h, vsel_l) { \ + .name = "DCDC"#num, \ + .supply_name = "dcdc"#num, \ + .of_match = of_match_ptr("dcdc"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_DCDC##num, \ + .ops = &atc260x_dcdc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = n_volt, \ + .vsel_reg = ATC2603C_PMU_DC##num##_CTL0, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_reg = ATC2603C_PMU_DC##num##_CTL0, \ + .enable_mask = BIT(15), \ + .enable_time = 800, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_dcdc_range(num, vsel_h, vsel_l) { \ + .name = "DCDC"#num, \ + .supply_name = "dcdc"#num, \ + .of_match = of_match_ptr("dcdc"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_DCDC##num, \ + .ops = &atc260x_dcdc_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .n_voltages = 16, \ + .linear_ranges = atc2603c_dcdc_voltage_ranges, \ + .n_linear_ranges = ARRAY_SIZE(atc2603c_dcdc_voltage_ranges), \ + .vsel_reg = ATC2603C_PMU_DC##num##_CTL0, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_reg = ATC2603C_PMU_DC##num##_CTL0, \ + .enable_mask = BIT(15), \ + .enable_time = 800, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_dcdc_fixed(num, min, step, n_volt, vsel_h, vsel_l) { \ + .name = "DCDC"#num, \ + .supply_name = "dcdc"#num, \ + .of_match = of_match_ptr("dcdc"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_DCDC##num, \ + .ops = &atc260x_dcdc_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = n_volt, \ + .vsel_reg = ATC2603C_PMU_DC##num##_CTL0, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_time = 800, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_ldo(num, min, step, n_volt, vsel_h, vsel_l) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_LDO##num, \ + .ops = &atc260x_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = n_volt, \ + .vsel_reg = ATC2603C_PMU_LDO##num##_CTL, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_reg = ATC2603C_PMU_LDO##num##_CTL, \ + .enable_mask = BIT(0), \ + .enable_time = 2000, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_ldo_fixed(num, min, step, n_volt, vsel_h, vsel_l) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_LDO##num, \ + .ops = &atc260x_ldo_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = n_volt, \ + .vsel_reg = ATC2603C_PMU_LDO##num##_CTL, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_time = 2000, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_ldo_noops(num, vfixed) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_LDO##num, \ + .ops = &atc260x_no_ops, \ + .type = REGULATOR_VOLTAGE, \ + .fixed_uV = vfixed, \ + .n_voltages = 1, \ + .owner = THIS_MODULE, \ +} + +#define atc2603c_reg_desc_ldo_switch(num, min, step, n_volt, vsel_h, vsel_l) { \ + .name = "SWITCHLDO"#num, \ + .supply_name = "switchldo"#num, \ + .of_match = of_match_ptr("switchldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2603C_ID_SWITCHLDO##num, \ + .ops = &atc260x_ldo_bypass_discharge_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = min, \ + .uV_step = step, \ + .n_voltages = n_volt, \ + .vsel_reg = ATC2603C_PMU_SWITCH_CTL, \ + .vsel_mask = GENMASK(vsel_h, vsel_l), \ + .enable_reg = ATC2603C_PMU_SWITCH_CTL, \ + .enable_mask = BIT(15), \ + .enable_is_inverted = true, \ + .enable_time = 2000, \ + .bypass_reg = ATC2603C_PMU_SWITCH_CTL, \ + .bypass_mask = BIT(5), \ + .active_discharge_reg = ATC2603C_PMU_SWITCH_CTL, \ + .active_discharge_mask = BIT(1), \ + .owner = THIS_MODULE, \ +} + +static const struct regulator_desc atc2603c_reg[] = { + atc2603c_reg_desc_dcdc_fixed(1, 700000, 25000, 29, 11, 7), + atc2603c_reg_desc_dcdc_range(2, 12, 8), + atc2603c_reg_desc_dcdc_fixed(3, 2600000, 100000, 8, 11, 9), + atc2603c_reg_desc_ldo_fixed(1, 2600000, 100000, 8, 15, 13), + atc2603c_reg_desc_ldo_fixed(2, 2600000, 100000, 8, 15, 13), + atc2603c_reg_desc_ldo_fixed(3, 1500000, 100000, 6, 15, 13), + atc2603c_reg_desc_ldo(5, 2600000, 100000, 8, 15, 13), + atc2603c_reg_desc_ldo_fixed(6, 700000, 25000, 29, 15, 11), + atc2603c_reg_desc_ldo(7, 1500000, 100000, 6, 15, 13), + atc2603c_reg_desc_ldo(8, 2300000, 100000, 11, 15, 12), + atc2603c_reg_desc_ldo_fixed(11, 2600000, 100000, 8, 15, 13), + atc2603c_reg_desc_ldo_noops(12, 1800000), + atc2603c_reg_desc_ldo_switch(1, 3000000, 100000, 4, 4, 3), +}; + +static const struct regulator_desc atc2603c_reg_dcdc2_ver_b = + atc2603c_reg_desc_dcdc(2, 1000000, 50000, 18, 12, 8); + +enum atc2609a_reg_ids { + ATC2609A_ID_DCDC0, + ATC2609A_ID_DCDC1, + ATC2609A_ID_DCDC2, + ATC2609A_ID_DCDC3, + ATC2609A_ID_DCDC4, + ATC2609A_ID_LDO0, + ATC2609A_ID_LDO1, + ATC2609A_ID_LDO2, + ATC2609A_ID_LDO3, + ATC2609A_ID_LDO4, + ATC2609A_ID_LDO5, + ATC2609A_ID_LDO6, + ATC2609A_ID_LDO7, + ATC2609A_ID_LDO8, + ATC2609A_ID_LDO9, + ATC2609A_ID_MAX, +}; + +#define atc2609a_reg_desc_dcdc(num, en_bit) { \ + .name = "DCDC"#num, \ + .supply_name = "dcdc"#num, \ + .of_match = of_match_ptr("dcdc"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_DCDC##num, \ + .ops = &atc260x_dcdc_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = 600000, \ + .uV_step = 6250, \ + .n_voltages = 256, \ + .vsel_reg = ATC2609A_PMU_DC##num##_CTL0, \ + .vsel_mask = GENMASK(15, 8), \ + .enable_reg = ATC2609A_PMU_DC_OSC, \ + .enable_mask = BIT(en_bit), \ + .enable_time = 800, \ + .owner = THIS_MODULE, \ +} + +#define atc2609a_reg_desc_dcdc_range(num, en_bit) { \ + .name = "DCDC"#num, \ + .supply_name = "dcdc"#num, \ + .of_match = of_match_ptr("dcdc"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_DCDC##num, \ + .ops = &atc260x_dcdc_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .n_voltages = 233, \ + .linear_ranges = atc2609a_dcdc_voltage_ranges, \ + .n_linear_ranges = ARRAY_SIZE(atc2609a_dcdc_voltage_ranges), \ + .vsel_reg = ATC2609A_PMU_DC##num##_CTL0, \ + .vsel_mask = GENMASK(15, 8), \ + .enable_reg = ATC2609A_PMU_DC_OSC, \ + .enable_mask = BIT(en_bit), \ + .enable_time = 800, \ + .owner = THIS_MODULE, \ +} + +#define atc2609a_reg_desc_ldo(num) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_LDO##num, \ + .ops = &atc260x_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = 700000, \ + .uV_step = 100000, \ + .n_voltages = 16, \ + .vsel_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .vsel_mask = GENMASK(4, 1), \ + .enable_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .enable_mask = BIT(0), \ + .enable_time = 2000, \ + .owner = THIS_MODULE, \ +} + +#define atc2609a_reg_desc_ldo_bypass(num) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_LDO##num, \ + .ops = &atc260x_ldo_bypass_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = 2300000, \ + .uV_step = 100000, \ + .n_voltages = 12, \ + .vsel_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .vsel_mask = GENMASK(5, 2), \ + .enable_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .enable_mask = BIT(0), \ + .enable_time = 2000, \ + .bypass_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .bypass_mask = BIT(1), \ + .owner = THIS_MODULE, \ +} + +#define atc2609a_reg_desc_ldo_range_pick(num, n_range) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_LDO##num, \ + .ops = &atc260x_ldo_range_pick_ops, \ + .type = REGULATOR_VOLTAGE, \ + .linear_ranges = atc2609a_ldo_voltage_ranges##n_range, \ + .n_linear_ranges = ARRAY_SIZE(atc2609a_ldo_voltage_ranges##n_range), \ + .vsel_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .vsel_mask = GENMASK(4, 1), \ + .vsel_range_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .vsel_range_mask = BIT(5), \ + .linear_range_selectors = atc260x_ldo_voltage_range_sel, \ + .enable_reg = ATC2609A_PMU_LDO##num##_CTL0, \ + .enable_mask = BIT(0), \ + .enable_time = 2000, \ + .owner = THIS_MODULE, \ +} + +#define atc2609a_reg_desc_ldo_fixed(num) { \ + .name = "LDO"#num, \ + .supply_name = "ldo"#num, \ + .of_match = of_match_ptr("ldo"#num), \ + .regulators_node = of_match_ptr("regulators"), \ + .id = ATC2609A_ID_LDO##num, \ + .ops = &atc260x_ldo_fixed_ops, \ + .type = REGULATOR_VOLTAGE, \ + .min_uV = 2600000, \ + .uV_step = 100000, \ + .n_voltages = 8, \ + .vsel_reg = ATC2609A_PMU_LDO##num##_CTL, \ + .vsel_mask = GENMASK(15, 13), \ + .enable_time = 2000, \ + .owner = THIS_MODULE, \ +} + +static const struct regulator_desc atc2609a_reg[] = { + atc2609a_reg_desc_dcdc(0, 4), + atc2609a_reg_desc_dcdc(1, 5), + atc2609a_reg_desc_dcdc(2, 6), + atc2609a_reg_desc_dcdc_range(3, 7), + atc2609a_reg_desc_dcdc(4, 8), + atc2609a_reg_desc_ldo_bypass(0), + atc2609a_reg_desc_ldo_bypass(1), + atc2609a_reg_desc_ldo_bypass(2), + atc2609a_reg_desc_ldo_range_pick(3, 0), + atc2609a_reg_desc_ldo_range_pick(4, 0), + atc2609a_reg_desc_ldo(5), + atc2609a_reg_desc_ldo_range_pick(6, 1), + atc2609a_reg_desc_ldo_range_pick(7, 0), + atc2609a_reg_desc_ldo_range_pick(8, 0), + atc2609a_reg_desc_ldo_fixed(9), +}; + +static int atc260x_regulator_probe(struct platform_device *pdev) +{ + struct atc260x *atc260x = dev_get_drvdata(pdev->dev.parent); + struct device *dev = atc260x->dev; + struct atc260x_regulator_data *atc260x_data; + struct regulator_config config = {}; + struct regulator_dev *atc260x_rdev; + const struct regulator_desc *regulators; + bool atc2603c_ver_b = false; + int i, nregulators; + + atc260x_data = devm_kzalloc(&pdev->dev, sizeof(*atc260x_data), GFP_KERNEL); + if (!atc260x_data) + return -ENOMEM; + + atc260x_data->voltage_time_dcdc = 350; + atc260x_data->voltage_time_ldo = 800; + + switch (atc260x->ic_type) { + case ATC2603C: + regulators = atc2603c_reg; + nregulators = ATC2603C_ID_MAX; + atc2603c_ver_b = atc260x->ic_ver == ATC260X_B; + break; + case ATC2609A: + atc260x_data->voltage_time_dcdc = 250; + regulators = atc2609a_reg; + nregulators = ATC2609A_ID_MAX; + break; + default: + dev_err(dev, "unsupported ATC260X ID %d\n", atc260x->ic_type); + return -EINVAL; + } + + config.dev = dev; + config.regmap = atc260x->regmap; + config.driver_data = atc260x_data; + + /* Instantiate the regulators */ + for (i = 0; i < nregulators; i++) { + if (atc2603c_ver_b && regulators[i].id == ATC2603C_ID_DCDC2) + atc260x_rdev = devm_regulator_register(&pdev->dev, + &atc2603c_reg_dcdc2_ver_b, + &config); + else + atc260x_rdev = devm_regulator_register(&pdev->dev, + ®ulators[i], + &config); + if (IS_ERR(atc260x_rdev)) { + dev_err(dev, "failed to register regulator: %d\n", i); + return PTR_ERR(atc260x_rdev); + } + } + + return 0; +} + +static struct platform_driver atc260x_regulator_driver = { + .probe = atc260x_regulator_probe, + .driver = { + .name = "atc260x-regulator", + }, +}; + +module_platform_driver(atc260x_regulator_driver); + +MODULE_DESCRIPTION("Regulator driver for ATC260x PMICs"); +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_AUTHOR("Cristian Ciocaltea "); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index 90cb8445f721..d260c442b788 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -1070,7 +1070,7 @@ static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) static int axp20x_regulator_parse_dt(struct platform_device *pdev) { struct device_node *np, *regulators; - int ret; + int ret = 0; u32 dcdcfreq = 0; np = of_node_get(pdev->dev.parent->of_node); @@ -1085,13 +1085,12 @@ static int axp20x_regulator_parse_dt(struct platform_device *pdev) ret = axp20x_set_dcdc_freq(pdev, dcdcfreq); if (ret < 0) { dev_err(&pdev->dev, "Error setting dcdc frequency: %d\n", ret); - return ret; } - of_node_put(regulators); } - return 0; + of_node_put(np); + return ret; } static int axp20x_set_dcdc_workmode(struct regulator_dev *rdev, int id, u32 workmode) diff --git a/drivers/regulator/bd70528-regulator.c b/drivers/regulator/bd70528-regulator.c index d44adf7e875a..1f5f9482b209 100644 --- a/drivers/regulator/bd70528-regulator.c +++ b/drivers/regulator/bd70528-regulator.c @@ -244,19 +244,14 @@ static const struct regulator_desc bd70528_desc[] = { static int bd70528_probe(struct platform_device *pdev) { - struct rohm_regmap_dev *bd70528; int i; struct regulator_config config = { .dev = pdev->dev.parent, }; - bd70528 = dev_get_drvdata(pdev->dev.parent); - if (!bd70528) { - dev_err(&pdev->dev, "No MFD driver data\n"); - return -EINVAL; - } - - config.regmap = bd70528->regmap; + config.regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!config.regmap) + return -ENODEV; for (i = 0; i < ARRAY_SIZE(bd70528_desc); i++) { struct regulator_dev *rdev; diff --git a/drivers/regulator/bd71828-regulator.c b/drivers/regulator/bd71828-regulator.c index 85c0b9000963..6b12e963ed8f 100644 --- a/drivers/regulator/bd71828-regulator.c +++ b/drivers/regulator/bd71828-regulator.c @@ -749,19 +749,14 @@ static const struct bd71828_regulator_data bd71828_rdata[] = { static int bd71828_probe(struct platform_device *pdev) { - struct rohm_regmap_dev *bd71828; int i, j, ret; struct regulator_config config = { .dev = pdev->dev.parent, }; - bd71828 = dev_get_drvdata(pdev->dev.parent); - if (!bd71828) { - dev_err(&pdev->dev, "No MFD driver data\n"); - return -EINVAL; - } - - config.regmap = bd71828->regmap; + config.regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!config.regmap) + return -ENODEV; for (i = 0; i < ARRAY_SIZE(bd71828_rdata); i++) { struct regulator_dev *rdev; @@ -777,7 +772,7 @@ static int bd71828_probe(struct platform_device *pdev) return PTR_ERR(rdev); } for (j = 0; j < rd->reg_init_amnt; j++) { - ret = regmap_update_bits(bd71828->regmap, + ret = regmap_update_bits(config.regmap, rd->reg_inits[j].reg, rd->reg_inits[j].mask, rd->reg_inits[j].val); diff --git a/drivers/regulator/bd718x7-regulator.c b/drivers/regulator/bd718x7-regulator.c index 9309765d0450..8ff47ea522d6 100644 --- a/drivers/regulator/bd718x7-regulator.c +++ b/drivers/regulator/bd718x7-regulator.c @@ -1554,7 +1554,7 @@ static int get_special_regulators(struct device *dev, static int bd718xx_probe(struct platform_device *pdev) { - struct bd718xx *mfd; + struct regmap *regmap; struct regulator_config config = { 0 }; int i, j, err, omit_enable; bool use_snvs; @@ -1563,11 +1563,10 @@ static int bd718xx_probe(struct platform_device *pdev) enum rohm_chip_type chip = platform_get_device_id(pdev)->driver_data; const struct regulator_ops **swops, **hwops; - mfd = dev_get_drvdata(pdev->dev.parent); - if (!mfd) { + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { dev_err(&pdev->dev, "No MFD driver data\n"); - err = -EINVAL; - goto err; + return -EINVAL; } switch (chip) { @@ -1590,7 +1589,7 @@ static int bd718xx_probe(struct platform_device *pdev) } /* Register LOCK release */ - err = regmap_update_bits(mfd->chip.regmap, BD718XX_REG_REGLOCK, + err = regmap_update_bits(regmap, BD718XX_REG_REGLOCK, (REGLOCK_PWRSEQ | REGLOCK_VREG), 0); if (err) { dev_err(&pdev->dev, "Failed to unlock PMIC (%d)\n", err); @@ -1609,8 +1608,7 @@ static int bd718xx_probe(struct platform_device *pdev) * bit allowing HW defaults for power rails to be used */ if (!use_snvs) { - err = regmap_update_bits(mfd->chip.regmap, - BD718XX_REG_TRANS_COND1, + err = regmap_update_bits(regmap, BD718XX_REG_TRANS_COND1, BD718XX_ON_REQ_POWEROFF_MASK | BD718XX_SWRESET_POWEROFF_MASK | BD718XX_WDOG_POWEROFF_MASK | @@ -1626,7 +1624,7 @@ static int bd718xx_probe(struct platform_device *pdev) } config.dev = pdev->dev.parent; - config.regmap = mfd->chip.regmap; + config.regmap = regmap; /* * There are cases when we want to leave the enable-control for * the HW state machine and use this driver only for voltage control. @@ -1685,7 +1683,7 @@ static int bd718xx_probe(struct platform_device *pdev) if (!no_enable_control && (!use_snvs || !rdev->constraints->always_on || !rdev->constraints->boot_on)) { - err = regmap_update_bits(mfd->chip.regmap, r->init.reg, + err = regmap_update_bits(regmap, r->init.reg, r->init.mask, r->init.val); if (err) { dev_err(&pdev->dev, @@ -1695,7 +1693,7 @@ static int bd718xx_probe(struct platform_device *pdev) } } for (j = 0; j < r->additional_init_amnt; j++) { - err = regmap_update_bits(mfd->chip.regmap, + err = regmap_update_bits(regmap, r->additional_inits[j].reg, r->additional_inits[j].mask, r->additional_inits[j].val); diff --git a/drivers/regulator/bd9571mwv-regulator.c b/drivers/regulator/bd9571mwv-regulator.c index e690c2ce5b3c..7b0cd08db446 100644 --- a/drivers/regulator/bd9571mwv-regulator.c +++ b/drivers/regulator/bd9571mwv-regulator.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * ROHM BD9571MWV-M regulator driver + * ROHM BD9571MWV-M and BD9574MWF-M regulator driver * * Copyright (C) 2017 Marek Vasut * @@ -9,6 +9,7 @@ * NOTE: VD09 is missing */ +#include #include #include #include @@ -17,7 +18,7 @@ #include struct bd9571mwv_reg { - struct bd9571mwv *bd; + struct regmap *regmap; /* DDR Backup Power */ u8 bkup_mode_cnt_keepon; /* from "rohm,ddr-backup-power" */ @@ -137,26 +138,30 @@ static const struct regulator_desc regulators[] = { }; #ifdef CONFIG_PM_SLEEP -static int bd9571mwv_bkup_mode_read(struct bd9571mwv *bd, unsigned int *mode) +static int bd9571mwv_bkup_mode_read(struct bd9571mwv_reg *bdreg, + unsigned int *mode) { int ret; - ret = regmap_read(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode); + ret = regmap_read(bdreg->regmap, BD9571MWV_BKUP_MODE_CNT, mode); if (ret) { - dev_err(bd->dev, "failed to read backup mode (%d)\n", ret); + dev_err(regmap_get_device(bdreg->regmap), + "failed to read backup mode (%d)\n", ret); return ret; } return 0; } -static int bd9571mwv_bkup_mode_write(struct bd9571mwv *bd, unsigned int mode) +static int bd9571mwv_bkup_mode_write(struct bd9571mwv_reg *bdreg, + unsigned int mode) { int ret; - ret = regmap_write(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode); + ret = regmap_write(bdreg->regmap, BD9571MWV_BKUP_MODE_CNT, mode); if (ret) { - dev_err(bd->dev, "failed to configure backup mode 0x%x (%d)\n", + dev_err(regmap_get_device(bdreg->regmap), + "failed to configure backup mode 0x%x (%d)\n", mode, ret); return ret; } @@ -194,7 +199,7 @@ static ssize_t backup_mode_store(struct device *dev, * Configure DDR Backup Mode, to change the role of the accessory power * switch from a power switch to a wake-up switch, or vice versa */ - ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode); + ret = bd9571mwv_bkup_mode_read(bdreg, &mode); if (ret) return ret; @@ -202,7 +207,7 @@ static ssize_t backup_mode_store(struct device *dev, if (bdreg->bkup_mode_enabled) mode |= bdreg->bkup_mode_cnt_keepon; - ret = bd9571mwv_bkup_mode_write(bdreg->bd, mode); + ret = bd9571mwv_bkup_mode_write(bdreg, mode); if (ret) return ret; @@ -221,7 +226,7 @@ static int bd9571mwv_suspend(struct device *dev) return 0; /* Save DDR Backup Mode */ - ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode); + ret = bd9571mwv_bkup_mode_read(bdreg, &mode); if (ret) return ret; @@ -235,7 +240,7 @@ static int bd9571mwv_suspend(struct device *dev) mode |= bdreg->bkup_mode_cnt_keepon; if (mode != bdreg->bkup_mode_cnt_saved) - return bd9571mwv_bkup_mode_write(bdreg->bd, mode); + return bd9571mwv_bkup_mode_write(bdreg, mode); return 0; } @@ -248,7 +253,7 @@ static int bd9571mwv_resume(struct device *dev) return 0; /* Restore DDR Backup Mode */ - return bd9571mwv_bkup_mode_write(bdreg->bd, bdreg->bkup_mode_cnt_saved); + return bd9571mwv_bkup_mode_write(bdreg, bdreg->bkup_mode_cnt_saved); } static const struct dev_pm_ops bd9571mwv_pm = { @@ -268,51 +273,54 @@ static int bd9571mwv_regulator_remove(struct platform_device *pdev) static int bd9571mwv_regulator_probe(struct platform_device *pdev) { - struct bd9571mwv *bd = dev_get_drvdata(pdev->dev.parent); struct regulator_config config = { }; struct bd9571mwv_reg *bdreg; struct regulator_dev *rdev; unsigned int val; int i; + enum rohm_chip_type chip = platform_get_device_id(pdev)->driver_data; bdreg = devm_kzalloc(&pdev->dev, sizeof(*bdreg), GFP_KERNEL); if (!bdreg) return -ENOMEM; - bdreg->bd = bd; + bdreg->regmap = dev_get_regmap(pdev->dev.parent, NULL); platform_set_drvdata(pdev, bdreg); config.dev = &pdev->dev; - config.dev->of_node = bd->dev->of_node; - config.driver_data = bd; - config.regmap = bd->regmap; + config.dev->of_node = pdev->dev.parent->of_node; + config.driver_data = bdreg; + config.regmap = bdreg->regmap; for (i = 0; i < ARRAY_SIZE(regulators); i++) { + /* BD9574MWF supports DVFS only */ + if (chip == ROHM_CHIP_TYPE_BD9574 && regulators[i].id != DVFS) + continue; rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); if (IS_ERR(rdev)) { - dev_err(bd->dev, "failed to register %s regulator\n", + dev_err(&pdev->dev, "failed to register %s regulator\n", pdev->name); return PTR_ERR(rdev); } } val = 0; - of_property_read_u32(bd->dev->of_node, "rohm,ddr-backup-power", &val); + of_property_read_u32(config.dev->of_node, "rohm,ddr-backup-power", &val); if (val & ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK) { - dev_err(bd->dev, "invalid %s mode %u\n", + dev_err(&pdev->dev, "invalid %s mode %u\n", "rohm,ddr-backup-power", val); return -EINVAL; } bdreg->bkup_mode_cnt_keepon = val; - bdreg->rstbmode_level = of_property_read_bool(bd->dev->of_node, + bdreg->rstbmode_level = of_property_read_bool(config.dev->of_node, "rohm,rstbmode-level"); - bdreg->rstbmode_pulse = of_property_read_bool(bd->dev->of_node, + bdreg->rstbmode_pulse = of_property_read_bool(config.dev->of_node, "rohm,rstbmode-pulse"); if (bdreg->rstbmode_level && bdreg->rstbmode_pulse) { - dev_err(bd->dev, "only one rohm,rstbmode-* may be specified"); + dev_err(&pdev->dev, "only one rohm,rstbmode-* may be specified"); return -EINVAL; } @@ -336,7 +344,8 @@ static int bd9571mwv_regulator_probe(struct platform_device *pdev) } static const struct platform_device_id bd9571mwv_regulator_id_table[] = { - { "bd9571mwv-regulator", }, + { "bd9571mwv-regulator", ROHM_CHIP_TYPE_BD9571 }, + { "bd9574mwf-regulator", ROHM_CHIP_TYPE_BD9574 }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, bd9571mwv_regulator_id_table); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 67a768fe5b2a..16114aea099a 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1617,7 +1617,7 @@ static struct regulator *create_regulator(struct regulator_dev *rdev, const char *supply_name) { struct regulator *regulator; - int err; + int err = 0; if (dev) { char buf[REG_STR_SIZE]; @@ -1663,8 +1663,8 @@ static struct regulator *create_regulator(struct regulator_dev *rdev, } } - regulator->debugfs = debugfs_create_dir(supply_name, - rdev->debugfs); + if (err != -EEXIST) + regulator->debugfs = debugfs_create_dir(supply_name, rdev->debugfs); if (!regulator->debugfs) { rdev_dbg(rdev, "Failed to create debugfs directory\n"); } else { @@ -2042,7 +2042,7 @@ struct regulator *_regulator_get(struct device *dev, const char *id, * Returns a struct regulator corresponding to the regulator producer, * or IS_ERR() condition containing errno. * - * Use of supply names configured via regulator_set_device_supply() is + * Use of supply names configured via set_consumer_device_supply() is * strongly encouraged. It is recommended that the supply name used * should match the name used for the supply and/or the relevant * device pins in the datasheet. @@ -2069,7 +2069,7 @@ EXPORT_SYMBOL_GPL(regulator_get); * regulator off for correct operation of the hardware they are * controlling. * - * Use of supply names configured via regulator_set_device_supply() is + * Use of supply names configured via set_consumer_device_supply() is * strongly encouraged. It is recommended that the supply name used * should match the name used for the supply and/or the relevant * device pins in the datasheet. @@ -2095,7 +2095,7 @@ EXPORT_SYMBOL_GPL(regulator_get_exclusive); * disrupting the operation of drivers that can handle absent * supplies. * - * Use of supply names configured via regulator_set_device_supply() is + * Use of supply names configured via set_consumer_device_supply() is * strongly encouraged. It is recommended that the supply name used * should match the name used for the supply and/or the relevant * device pins in the datasheet. @@ -4153,7 +4153,11 @@ int regulator_sync_voltage(struct regulator *regulator) if (ret < 0) goto out; - ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + /* balance only, if regulator is coupled */ + if (rdev->coupling_desc.n_coupled > 1) + ret = regulator_balance_voltage(rdev, PM_SUSPEND_ON); + else + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); out: regulator_unlock(rdev); diff --git a/drivers/regulator/mcp16502.c b/drivers/regulator/mcp16502.c index 74ad92dc664a..88c6bd5b6c78 100644 --- a/drivers/regulator/mcp16502.c +++ b/drivers/regulator/mcp16502.c @@ -550,7 +550,7 @@ static int mcp16502_probe(struct i2c_client *client, config.regmap = rmap; config.driver_data = mcp; - mcp->lpm = devm_gpiod_get(dev, "lpm", GPIOD_OUT_LOW); + mcp->lpm = devm_gpiod_get_optional(dev, "lpm", GPIOD_OUT_LOW); if (IS_ERR(mcp->lpm)) { dev_err(dev, "failed to get lpm pin: %ld\n", PTR_ERR(mcp->lpm)); return PTR_ERR(mcp->lpm); diff --git a/drivers/regulator/mt6315-regulator.c b/drivers/regulator/mt6315-regulator.c new file mode 100644 index 000000000000..d49a1534d8e9 --- /dev/null +++ b/drivers/regulator/mt6315-regulator.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2021 MediaTek Inc. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT6315_BUCK_MODE_AUTO 0 +#define MT6315_BUCK_MODE_FORCE_PWM 1 +#define MT6315_BUCK_MODE_LP 2 + +struct mt6315_regulator_info { + struct regulator_desc desc; + u32 status_reg; + u32 lp_mode_mask; + u32 lp_mode_shift; +}; + +struct mt_regulator_init_data { + u32 modeset_mask[MT6315_VBUCK_MAX]; +}; + +struct mt6315_chip { + struct device *dev; + struct regmap *regmap; +}; + +#define MT_BUCK(_name, _bid, _vsel) \ +[_bid] = { \ + .desc = { \ + .name = _name, \ + .of_match = of_match_ptr(_name), \ + .regulators_node = "regulators", \ + .ops = &mt6315_volt_range_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _bid, \ + .owner = THIS_MODULE, \ + .n_voltages = 0xbf, \ + .linear_ranges = mt_volt_range1, \ + .n_linear_ranges = ARRAY_SIZE(mt_volt_range1), \ + .vsel_reg = _vsel, \ + .vsel_mask = 0xff, \ + .enable_reg = MT6315_BUCK_TOP_CON0, \ + .enable_mask = BIT(_bid), \ + .of_map_mode = mt6315_map_mode, \ + }, \ + .status_reg = _bid##_DBG4, \ + .lp_mode_mask = BIT(_bid), \ + .lp_mode_shift = _bid, \ +} + +static const struct linear_range mt_volt_range1[] = { + REGULATOR_LINEAR_RANGE(0, 0, 0xbf, 6250), +}; + +static unsigned int mt6315_map_mode(u32 mode) +{ + switch (mode) { + case MT6315_BUCK_MODE_AUTO: + return REGULATOR_MODE_NORMAL; + case MT6315_BUCK_MODE_FORCE_PWM: + return REGULATOR_MODE_FAST; + case MT6315_BUCK_MODE_LP: + return REGULATOR_MODE_IDLE; + default: + return -EINVAL; + } +} + +static unsigned int mt6315_regulator_get_mode(struct regulator_dev *rdev) +{ + struct mt_regulator_init_data *init = rdev_get_drvdata(rdev); + const struct mt6315_regulator_info *info; + int ret, regval; + u32 modeset_mask; + + info = container_of(rdev->desc, struct mt6315_regulator_info, desc); + modeset_mask = init->modeset_mask[rdev_get_id(rdev)]; + ret = regmap_read(rdev->regmap, MT6315_BUCK_TOP_4PHASE_ANA_CON42, ®val); + if (ret != 0) { + dev_notice(&rdev->dev, "Failed to get mode: %d\n", ret); + return ret; + } + + if ((regval & modeset_mask) == modeset_mask) + return REGULATOR_MODE_FAST; + + ret = regmap_read(rdev->regmap, MT6315_BUCK_TOP_CON1, ®val); + if (ret != 0) { + dev_notice(&rdev->dev, "Failed to get lp mode: %d\n", ret); + return ret; + } + + if (regval & info->lp_mode_mask) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int mt6315_regulator_set_mode(struct regulator_dev *rdev, + u32 mode) +{ + struct mt_regulator_init_data *init = rdev_get_drvdata(rdev); + const struct mt6315_regulator_info *info; + int ret, val, curr_mode; + u32 modeset_mask; + + info = container_of(rdev->desc, struct mt6315_regulator_info, desc); + modeset_mask = init->modeset_mask[rdev_get_id(rdev)]; + curr_mode = mt6315_regulator_get_mode(rdev); + switch (mode) { + case REGULATOR_MODE_FAST: + ret = regmap_update_bits(rdev->regmap, + MT6315_BUCK_TOP_4PHASE_ANA_CON42, + modeset_mask, + modeset_mask); + break; + case REGULATOR_MODE_NORMAL: + if (curr_mode == REGULATOR_MODE_FAST) { + ret = regmap_update_bits(rdev->regmap, + MT6315_BUCK_TOP_4PHASE_ANA_CON42, + modeset_mask, + 0); + } else if (curr_mode == REGULATOR_MODE_IDLE) { + ret = regmap_update_bits(rdev->regmap, + MT6315_BUCK_TOP_CON1, + info->lp_mode_mask, + 0); + usleep_range(100, 110); + } else { + ret = -EINVAL; + } + break; + case REGULATOR_MODE_IDLE: + val = MT6315_BUCK_MODE_LP >> 1; + val <<= info->lp_mode_shift; + ret = regmap_update_bits(rdev->regmap, + MT6315_BUCK_TOP_CON1, + info->lp_mode_mask, + val); + break; + default: + ret = -EINVAL; + dev_notice(&rdev->dev, "Unsupported mode: %d\n", mode); + break; + } + + if (ret != 0) { + dev_notice(&rdev->dev, "Failed to set mode: %d\n", ret); + return ret; + } + + return 0; +} + +static int mt6315_get_status(struct regulator_dev *rdev) +{ + const struct mt6315_regulator_info *info; + int ret; + u32 regval; + + info = container_of(rdev->desc, struct mt6315_regulator_info, desc); + ret = regmap_read(rdev->regmap, info->status_reg, ®val); + if (ret < 0) { + dev_notice(&rdev->dev, "Failed to get enable reg: %d\n", ret); + return ret; + } + + return (regval & BIT(0)) ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF; +} + +static const struct regulator_ops mt6315_volt_range_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_time_sel = regulator_set_voltage_time_sel, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_status = mt6315_get_status, + .set_mode = mt6315_regulator_set_mode, + .get_mode = mt6315_regulator_get_mode, +}; + +static const struct mt6315_regulator_info mt6315_regulators[MT6315_VBUCK_MAX] = { + MT_BUCK("vbuck1", MT6315_VBUCK1, MT6315_BUCK_TOP_ELR0), + MT_BUCK("vbuck2", MT6315_VBUCK2, MT6315_BUCK_TOP_ELR2), + MT_BUCK("vbuck3", MT6315_VBUCK3, MT6315_BUCK_TOP_ELR4), + MT_BUCK("vbuck4", MT6315_VBUCK4, MT6315_BUCK_TOP_ELR6), +}; + +static const struct regmap_config mt6315_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x16d0, + .fast_io = true, +}; + +static const struct of_device_id mt6315_of_match[] = { + { + .compatible = "mediatek,mt6315-regulator", + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, mt6315_of_match); + +static int mt6315_regulator_probe(struct spmi_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct mt6315_chip *chip; + struct mt_regulator_init_data *init_data; + struct regulator_config config = {}; + struct regulator_dev *rdev; + int i; + + regmap = devm_regmap_init_spmi_ext(pdev, &mt6315_regmap_config); + if (!regmap) + return -ENODEV; + + chip = devm_kzalloc(dev, sizeof(struct mt6315_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + init_data = devm_kzalloc(dev, sizeof(struct mt_regulator_init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + switch (pdev->usid) { + case MT6315_PP: + init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1) | BIT(MT6315_VBUCK2) | + BIT(MT6315_VBUCK4); + break; + case MT6315_SP: + case MT6315_RP: + init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1) | BIT(MT6315_VBUCK2); + break; + default: + init_data->modeset_mask[MT6315_VBUCK1] = BIT(MT6315_VBUCK1); + break; + } + for (i = MT6315_VBUCK2; i < MT6315_VBUCK_MAX; i++) + init_data->modeset_mask[i] = BIT(i); + + chip->dev = dev; + chip->regmap = regmap; + dev_set_drvdata(dev, chip); + + config.dev = dev; + config.regmap = regmap; + for (i = MT6315_VBUCK1; i < MT6315_VBUCK_MAX; i++) { + config.driver_data = init_data; + rdev = devm_regulator_register(dev, &mt6315_regulators[i].desc, &config); + if (IS_ERR(rdev)) { + dev_notice(dev, "Failed to register %s\n", mt6315_regulators[i].desc.name); + continue; + } + } + + return 0; +} + +static void mt6315_regulator_shutdown(struct spmi_device *pdev) +{ + struct mt6315_chip *chip = dev_get_drvdata(&pdev->dev); + int ret = 0; + + ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY_H, PROTECTION_KEY_H); + ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY, PROTECTION_KEY); + ret |= regmap_update_bits(chip->regmap, MT6315_TOP2_ELR7, 1, 1); + ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY, 0); + ret |= regmap_write(chip->regmap, MT6315_TOP_TMA_KEY_H, 0); + if (ret < 0) + dev_notice(&pdev->dev, "[%#x] Failed to enable power off sequence. %d\n", + pdev->usid, ret); +} + +static struct spmi_driver mt6315_regulator_driver = { + .driver = { + .name = "mt6315-regulator", + .of_match_table = mt6315_of_match, + }, + .probe = mt6315_regulator_probe, + .shutdown = mt6315_regulator_shutdown, +}; + +module_spmi_driver(mt6315_regulator_driver); + +MODULE_AUTHOR("Hsin-Hsiung Wang "); +MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6315 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/mtk-dvfsrc-regulator.c b/drivers/regulator/mtk-dvfsrc-regulator.c new file mode 100644 index 000000000000..d3d876198d6e --- /dev/null +++ b/drivers/regulator/mtk-dvfsrc-regulator.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 MediaTek Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DVFSRC_ID_VCORE 0 +#define DVFSRC_ID_VSCP 1 + +#define MT_DVFSRC_REGULAR(match, _name, _volt_table) \ +[DVFSRC_ID_##_name] = { \ + .desc = { \ + .name = match, \ + .of_match = of_match_ptr(match), \ + .ops = &dvfsrc_vcore_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = DVFSRC_ID_##_name, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(_volt_table), \ + .volt_table = _volt_table, \ + }, \ +} + +/* + * DVFSRC regulators' information + * + * @desc: standard fields of regulator description. + * @voltage_selector: Selector used for get_voltage_sel() and + * set_voltage_sel() callbacks + */ + +struct dvfsrc_regulator { + struct regulator_desc desc; +}; + +/* + * MTK DVFSRC regulators' init data + * + * @size: num of regulators + * @regulator_info: regulator info. + */ +struct dvfsrc_regulator_init_data { + u32 size; + struct dvfsrc_regulator *regulator_info; +}; + +static inline struct device *to_dvfsrc_dev(struct regulator_dev *rdev) +{ + return rdev_get_dev(rdev)->parent; +} + +static int dvfsrc_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct device *dvfsrc_dev = to_dvfsrc_dev(rdev); + int id = rdev_get_id(rdev); + + if (id == DVFSRC_ID_VCORE) + mtk_dvfsrc_send_request(dvfsrc_dev, + MTK_DVFSRC_CMD_VCORE_REQUEST, + selector); + else if (id == DVFSRC_ID_VSCP) + mtk_dvfsrc_send_request(dvfsrc_dev, + MTK_DVFSRC_CMD_VSCP_REQUEST, + selector); + else + return -EINVAL; + + return 0; +} + +static int dvfsrc_get_voltage_sel(struct regulator_dev *rdev) +{ + struct device *dvfsrc_dev = to_dvfsrc_dev(rdev); + int id = rdev_get_id(rdev); + int val, ret; + + if (id == DVFSRC_ID_VCORE) + ret = mtk_dvfsrc_query_info(dvfsrc_dev, + MTK_DVFSRC_CMD_VCORE_LEVEL_QUERY, + &val); + else if (id == DVFSRC_ID_VSCP) + ret = mtk_dvfsrc_query_info(dvfsrc_dev, + MTK_DVFSRC_CMD_VSCP_LEVEL_QUERY, + &val); + else + return -EINVAL; + + if (ret != 0) + return ret; + + return val; +} + +static const struct regulator_ops dvfsrc_vcore_ops = { + .list_voltage = regulator_list_voltage_table, + .get_voltage_sel = dvfsrc_get_voltage_sel, + .set_voltage_sel = dvfsrc_set_voltage_sel, +}; + +static const unsigned int mt8183_voltages[] = { + 725000, + 800000, +}; + +static struct dvfsrc_regulator mt8183_regulators[] = { + MT_DVFSRC_REGULAR("dvfsrc-vcore", VCORE, + mt8183_voltages), +}; + +static const struct dvfsrc_regulator_init_data regulator_mt8183_data = { + .size = ARRAY_SIZE(mt8183_regulators), + .regulator_info = &mt8183_regulators[0], +}; + +static const unsigned int mt6873_voltages[] = { + 575000, + 600000, + 650000, + 725000, +}; + +static struct dvfsrc_regulator mt6873_regulators[] = { + MT_DVFSRC_REGULAR("dvfsrc-vcore", VCORE, + mt6873_voltages), + MT_DVFSRC_REGULAR("dvfsrc-vscp", VSCP, + mt6873_voltages), +}; + +static const struct dvfsrc_regulator_init_data regulator_mt6873_data = { + .size = ARRAY_SIZE(mt6873_regulators), + .regulator_info = &mt6873_regulators[0], +}; + +static const struct of_device_id mtk_dvfsrc_regulator_match[] = { + { + .compatible = "mediatek,mt8183-dvfsrc", + .data = ®ulator_mt8183_data, + }, { + .compatible = "mediatek,mt8192-dvfsrc", + .data = ®ulator_mt6873_data, + }, { + .compatible = "mediatek,mt6873-dvfsrc", + .data = ®ulator_mt6873_data, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, mtk_dvfsrc_regulator_match); + +static int dvfsrc_vcore_regulator_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device *dev = &pdev->dev; + struct regulator_config config = { }; + struct regulator_dev *rdev; + const struct dvfsrc_regulator_init_data *regulator_init_data; + struct dvfsrc_regulator *mt_regulators; + int i; + + match = of_match_node(mtk_dvfsrc_regulator_match, dev->parent->of_node); + + if (!match) { + dev_err(dev, "invalid compatible string\n"); + return -ENODEV; + } + + regulator_init_data = match->data; + + mt_regulators = regulator_init_data->regulator_info; + for (i = 0; i < regulator_init_data->size; i++) { + config.dev = dev->parent; + config.driver_data = (mt_regulators + i); + rdev = devm_regulator_register(dev->parent, + &(mt_regulators + i)->desc, + &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", + (mt_regulators + i)->desc.name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver mtk_dvfsrc_regulator_driver = { + .driver = { + .name = "mtk-dvfsrc-regulator", + }, + .probe = dvfsrc_vcore_regulator_probe, +}; + +static int __init mtk_dvfsrc_regulator_init(void) +{ + return platform_driver_register(&mtk_dvfsrc_regulator_driver); +} +subsys_initcall(mtk_dvfsrc_regulator_init); + +static void __exit mtk_dvfsrc_regulator_exit(void) +{ + platform_driver_unregister(&mtk_dvfsrc_regulator_driver); +} +module_exit(mtk_dvfsrc_regulator_exit); + +MODULE_AUTHOR("Arvin wang "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/pca9450-regulator.c b/drivers/regulator/pca9450-regulator.c index cb29421d745a..833d398c6aa2 100644 --- a/drivers/regulator/pca9450-regulator.c +++ b/drivers/regulator/pca9450-regulator.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -32,6 +33,7 @@ struct pca9450_regulator_desc { struct pca9450 { struct device *dev; struct regmap *regmap; + struct gpio_desc *sd_vsel_gpio; enum pca9450_chip_type type; unsigned int rcnt; int irq; @@ -795,6 +797,26 @@ static int pca9450_i2c_probe(struct i2c_client *i2c, return ret; } + /* Set reset behavior on assertion of WDOG_B signal */ + ret = regmap_update_bits(pca9450->regmap, PCA9450_REG_RESET_CTRL, + WDOG_B_CFG_MASK, WDOG_B_CFG_COLD_LDO12); + if (ret) { + dev_err(&i2c->dev, "Failed to set WDOG_B reset behavior\n"); + return ret; + } + + /* + * The driver uses the LDO5CTRL_H register to control the LDO5 regulator. + * This is only valid if the SD_VSEL input of the PMIC is high. Let's + * check if the pin is available as GPIO and set it to high. + */ + pca9450->sd_vsel_gpio = gpiod_get_optional(pca9450->dev, "sd-vsel", GPIOD_OUT_HIGH); + + if (IS_ERR(pca9450->sd_vsel_gpio)) { + dev_err(&i2c->dev, "Failed to get SD_VSEL GPIO\n"); + return ret; + } + dev_info(&i2c->dev, "%s probed.\n", type == PCA9450_TYPE_PCA9450A ? "pca9450a" : "pca9450bc"); diff --git a/drivers/regulator/pf8x00-regulator.c b/drivers/regulator/pf8x00-regulator.c index af9918cd27aa..9b28bd63208d 100644 --- a/drivers/regulator/pf8x00-regulator.c +++ b/drivers/regulator/pf8x00-regulator.c @@ -114,7 +114,6 @@ enum swxilim_bits { #define PF8X00_SWXILIM_SHIFT 3 #define PF8X00_SWXILIM_MASK GENMASK(4, 3) #define PF8X00_SWXPHASE_MASK GENMASK(2, 0) -#define PF8X00_SWXPHASE_DEFAULT 0 #define PF8X00_SWXPHASE_SHIFT 7 enum pf8x00_devid { @@ -126,10 +125,12 @@ enum pf8x00_devid { #define PF8X00_DEVICE_FAM_MASK GENMASK(7, 4) #define PF8X00_DEVICE_ID_MASK GENMASK(3, 0) -struct pf8x00_regulator { +struct pf8x00_regulator_data { struct regulator_desc desc; - u8 ilim; - u8 phase_shift; + unsigned int suspend_enable_reg; + unsigned int suspend_enable_mask; + unsigned int suspend_voltage_reg; + unsigned int suspend_voltage_cache; }; struct pf8x00_chip { @@ -150,35 +151,16 @@ static const int pf8x00_ldo_voltages[] = { 3100000, 3150000, 3200000, 3300000, 3350000, 1650000, 1700000, 5000000, }; -#define SWV(i) (6250 * i + 400000) -#define SWV_LINE(i) SWV(i*8+0), SWV(i*8+1), SWV(i*8+2), SWV(i*8+3), \ - SWV(i*8+4), SWV(i*8+5), SWV(i*8+6), SWV(i*8+7) +/* Output: 2.1A to 4.5A */ +static const unsigned int pf8x00_sw_current_table[] = { + 2100000, 2600000, 3000000, 4500000, +}; /* Output: 0.4V to 1.8V */ -static const int pf8x00_sw1_to_6_voltages[] = { - SWV_LINE(0), - SWV_LINE(1), - SWV_LINE(2), - SWV_LINE(3), - SWV_LINE(4), - SWV_LINE(5), - SWV_LINE(6), - SWV_LINE(7), - SWV_LINE(8), - SWV_LINE(9), - SWV_LINE(10), - SWV_LINE(11), - SWV_LINE(12), - SWV_LINE(13), - SWV_LINE(14), - SWV_LINE(15), - SWV_LINE(16), - SWV_LINE(17), - SWV_LINE(18), - SWV_LINE(19), - SWV_LINE(20), - SWV_LINE(21), - 1500000, 1800000, +#define PF8XOO_SW1_6_VOLTAGE_NUM 0xB2 +static const struct linear_range pf8x00_sw1_to_6_voltages[] = { + REGULATOR_LINEAR_RANGE(400000, 0x00, 0xB0, 6250), + REGULATOR_LINEAR_RANGE(1800000, 0xB1, 0xB1, 0), }; /* Output: 1.0V to 4.1V */ @@ -194,15 +176,10 @@ static const int pf8x00_vsnvs_voltages[] = { 0, 1800000, 3000000, 3300000, }; -static struct pf8x00_regulator *desc_to_regulator(const struct regulator_desc *desc) +static void swxilim_select(struct pf8x00_chip *chip, int id, int ilim) { - return container_of(desc, struct pf8x00_regulator, desc); -} - -static void swxilim_select(const struct regulator_desc *desc, int ilim) -{ - struct pf8x00_regulator *data = desc_to_regulator(desc); u8 ilim_sel; + u8 reg = PF8X00_SW_BASE(id) + SW_CONFIG2; switch (ilim) { case 2100: @@ -222,43 +199,130 @@ static void swxilim_select(const struct regulator_desc *desc, int ilim) break; } - data->ilim = ilim_sel; + regmap_update_bits(chip->regmap, reg, + PF8X00_SWXILIM_MASK, + ilim_sel << PF8X00_SWXILIM_SHIFT); +} + +static void handle_ilim_property(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct pf8x00_chip *chip = config->driver_data; + int ret; + int val; + + if ((desc->id >= PF8X00_BUCK1) && (desc->id <= PF8X00_BUCK7)) { + ret = of_property_read_u32(np, "nxp,ilim-ma", &val); + if (ret) { + dev_dbg(chip->dev, "unspecified ilim for BUCK%d, use value stored in OTP\n", + desc->id - PF8X00_LDO4); + return; + } + + dev_warn(chip->dev, "nxp,ilim-ma is deprecated, please use regulator-max-microamp\n"); + swxilim_select(chip, desc->id, val); + + } else + dev_warn(chip->dev, "nxp,ilim-ma used with incorrect regulator (%d)\n", desc->id); +} + +static void handle_shift_property(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + unsigned char id = desc->id - PF8X00_LDO4; + unsigned char reg = PF8X00_SW_BASE(id) + SW_CONFIG2; + struct pf8x00_chip *chip = config->driver_data; + + int phase; + int val; + int ret; + if ((desc->id >= PF8X00_BUCK1) && (desc->id <= PF8X00_BUCK7)) { + ret = of_property_read_u32(np, "nxp,phase-shift", &val); + if (ret) { + dev_dbg(chip->dev, + "unspecified phase-shift for BUCK%d, using OTP configuration\n", + id); + return; + } + + if (val < 0 || val > 315 || val % 45 != 0) { + dev_warn(config->dev, + "invalid phase_shift %d for BUCK%d, using OTP configuration\n", + val, id); + return; + } + + phase = val / 45; + + if (phase >= 1) + phase -= 1; + else + phase = PF8X00_SWXPHASE_SHIFT; + + regmap_update_bits(chip->regmap, reg, + PF8X00_SWXPHASE_MASK, + phase); + } else + dev_warn(chip->dev, "nxp,phase-shift used with incorrect regulator (%d)\n", id); + } static int pf8x00_of_parse_cb(struct device_node *np, const struct regulator_desc *desc, struct regulator_config *config) { - struct pf8x00_regulator *data = desc_to_regulator(desc); - struct pf8x00_chip *chip = config->driver_data; - int phase; - int val; + + handle_ilim_property(np, desc, config); + handle_shift_property(np, desc, config); + + return 0; +} + +static int pf8x00_suspend_enable(struct regulator_dev *rdev) +{ + struct pf8x00_regulator_data *regl = rdev_get_drvdata(rdev); + struct regmap *rmap = rdev_get_regmap(rdev); + + return regmap_update_bits(rmap, regl->suspend_enable_reg, + regl->suspend_enable_mask, + regl->suspend_enable_mask); +} + +static int pf8x00_suspend_disable(struct regulator_dev *rdev) +{ + struct pf8x00_regulator_data *regl = rdev_get_drvdata(rdev); + struct regmap *rmap = rdev_get_regmap(rdev); + + return regmap_update_bits(rmap, regl->suspend_enable_reg, + regl->suspend_enable_mask, 0); +} + +static int pf8x00_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct pf8x00_regulator_data *regl = rdev_get_drvdata(rdev); int ret; - ret = of_property_read_u32(np, "nxp,ilim-ma", &val); - if (ret) - dev_dbg(chip->dev, "unspecified ilim for BUCK%d, use 2100 mA\n", - desc->id - PF8X00_LDO4); + if (regl->suspend_voltage_cache == uV) + return 0; - swxilim_select(desc, val); - - ret = of_property_read_u32(np, "nxp,phase-shift", &val); - if (ret) { - dev_dbg(chip->dev, - "unspecified phase-shift for BUCK%d, use 0 degrees\n", - desc->id - PF8X00_LDO4); - val = PF8X00_SWXPHASE_DEFAULT; + ret = regulator_map_voltage_iterate(rdev, uV, uV); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), "failed to map %i uV\n", uV); + return ret; } - phase = val / 45; - if ((phase * 45) != val) { - dev_warn(config->dev, - "invalid phase_shift %d for BUCK%d, use 0 degrees\n", - (phase * 45), desc->id - PF8X00_LDO4); - phase = PF8X00_SWXPHASE_SHIFT; + dev_dbg(rdev_get_dev(rdev), "uV: %i, reg: 0x%x, msk: 0x%x, val: 0x%x\n", + uV, regl->suspend_voltage_reg, regl->desc.vsel_mask, ret); + ret = regmap_update_bits(rdev->regmap, regl->suspend_voltage_reg, + regl->desc.vsel_mask, ret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), "failed to set %i uV\n", uV); + return ret; } - data->phase_shift = (phase >= 1) ? phase - 1 : PF8X00_SWXPHASE_SHIFT; + regl->suspend_voltage_cache = uV; return 0; } @@ -270,15 +334,37 @@ static const struct regulator_ops pf8x00_ldo_ops = { .list_voltage = regulator_list_voltage_table, .set_voltage_sel = regulator_set_voltage_sel_regmap, .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_suspend_enable = pf8x00_suspend_enable, + .set_suspend_disable = pf8x00_suspend_disable, + .set_suspend_voltage = pf8x00_set_suspend_voltage, }; -static const struct regulator_ops pf8x00_buck_ops = { + +static const struct regulator_ops pf8x00_buck1_6_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .get_current_limit = regulator_get_current_limit_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .set_suspend_enable = pf8x00_suspend_enable, + .set_suspend_disable = pf8x00_suspend_disable, + .set_suspend_voltage = pf8x00_set_suspend_voltage, +}; + +static const struct regulator_ops pf8x00_buck7_ops = { .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, .is_enabled = regulator_is_enabled_regmap, .list_voltage = regulator_list_voltage_table, .set_voltage_sel = regulator_set_voltage_sel_regmap, .get_voltage_sel = regulator_get_voltage_sel_regmap, + .get_current_limit = regulator_get_current_limit_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .set_suspend_enable = pf8x00_suspend_enable, + .set_suspend_disable = pf8x00_suspend_disable, }; static const struct regulator_ops pf8x00_vsnvs_ops = { @@ -310,6 +396,9 @@ static const struct regulator_ops pf8x00_vsnvs_ops = { .disable_val = 0x0, \ .enable_mask = 2, \ }, \ + .suspend_enable_reg = (base) + LDO_CONFIG2, \ + .suspend_enable_mask = 1, \ + .suspend_voltage_reg = (base) + LDO_STBY_VOLT, \ } #define PF8X00BUCK(_id, _name, base, voltages) \ @@ -319,14 +408,54 @@ static const struct regulator_ops pf8x00_vsnvs_ops = { .of_match = _name, \ .regulators_node = "regulators", \ .of_parse_cb = pf8x00_of_parse_cb, \ - .n_voltages = ARRAY_SIZE(voltages), \ - .ops = &pf8x00_buck_ops, \ + .n_voltages = PF8XOO_SW1_6_VOLTAGE_NUM, \ + .ops = &pf8x00_buck1_6_ops, \ .type = REGULATOR_VOLTAGE, \ .id = PF8X00_BUCK ## _id, \ .owner = THIS_MODULE, \ + .ramp_delay = 19000, \ + .linear_ranges = pf8x00_sw1_to_6_voltages, \ + .n_linear_ranges = \ + ARRAY_SIZE(pf8x00_sw1_to_6_voltages), \ + .vsel_reg = (base) + SW_RUN_VOLT, \ + .vsel_mask = 0xff, \ + .curr_table = pf8x00_sw_current_table, \ + .n_current_limits = \ + ARRAY_SIZE(pf8x00_sw_current_table), \ + .csel_reg = (base) + SW_CONFIG2, \ + .csel_mask = PF8X00_SWXILIM_MASK, \ + .enable_reg = (base) + SW_MODE1, \ + .enable_val = 0x3, \ + .disable_val = 0x0, \ + .enable_mask = 0x3, \ + .enable_time = 500, \ + }, \ + .suspend_enable_reg = (base) + SW_MODE1, \ + .suspend_enable_mask = 0xc, \ + .suspend_voltage_reg = (base) + SW_STBY_VOLT, \ + } + +#define PF8X00BUCK7(_name, base, voltages) \ + [PF8X00_BUCK7] = { \ + .desc = { \ + .name = _name, \ + .of_match = _name, \ + .regulators_node = "regulators", \ + .of_parse_cb = pf8x00_of_parse_cb, \ + .n_voltages = ARRAY_SIZE(voltages), \ + .ops = &pf8x00_buck7_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PF8X00_BUCK7, \ + .owner = THIS_MODULE, \ + .ramp_delay = 19000, \ .volt_table = voltages, \ .vsel_reg = (base) + SW_RUN_VOLT, \ .vsel_mask = 0xff, \ + .curr_table = pf8x00_sw_current_table, \ + .n_current_limits = \ + ARRAY_SIZE(pf8x00_sw_current_table), \ + .csel_reg = (base) + SW_CONFIG2, \ + .csel_mask = PF8X00_SWXILIM_MASK, \ .enable_reg = (base) + SW_MODE1, \ .enable_val = 0x3, \ .disable_val = 0x0, \ @@ -335,6 +464,7 @@ static const struct regulator_ops pf8x00_vsnvs_ops = { }, \ } + #define PF8X00VSNVS(_name, base, voltages) \ [PF8X00_VSNVS] = { \ .desc = { \ @@ -352,7 +482,7 @@ static const struct regulator_ops pf8x00_vsnvs_ops = { }, \ } -static struct pf8x00_regulator pf8x00_regulators_data[PF8X00_MAX_REGULATORS] = { +static struct pf8x00_regulator_data pf8x00_regs_data[PF8X00_MAX_REGULATORS] = { PF8X00LDO(1, "ldo1", PF8X00_LDO_BASE(PF8X00_LDO1), pf8x00_ldo_voltages), PF8X00LDO(2, "ldo2", PF8X00_LDO_BASE(PF8X00_LDO2), pf8x00_ldo_voltages), PF8X00LDO(3, "ldo3", PF8X00_LDO_BASE(PF8X00_LDO3), pf8x00_ldo_voltages), @@ -363,7 +493,7 @@ static struct pf8x00_regulator pf8x00_regulators_data[PF8X00_MAX_REGULATORS] = { PF8X00BUCK(4, "buck4", PF8X00_SW_BASE(PF8X00_BUCK4), pf8x00_sw1_to_6_voltages), PF8X00BUCK(5, "buck5", PF8X00_SW_BASE(PF8X00_BUCK5), pf8x00_sw1_to_6_voltages), PF8X00BUCK(6, "buck6", PF8X00_SW_BASE(PF8X00_BUCK6), pf8x00_sw1_to_6_voltages), - PF8X00BUCK(7, "buck7", PF8X00_SW_BASE(PF8X00_BUCK7), pf8x00_sw7_voltages), + PF8X00BUCK7("buck7", PF8X00_SW_BASE(PF8X00_BUCK7), pf8x00_sw7_voltages), PF8X00VSNVS("vsnvs", PF8X00_VSNVS_CONFIG1, pf8x00_vsnvs_voltages), }; @@ -399,7 +529,7 @@ static int pf8x00_identify(struct pf8x00_chip *chip) name = "PF8121A"; break; case PF8200: - name = "PF8100"; + name = "PF8200"; break; default: dev_err(chip->dev, "Unknown pf8x00 device id 0x%x\n", dev_id); @@ -437,12 +567,12 @@ static int pf8x00_i2c_probe(struct i2c_client *client) if (ret) return ret; - for (id = 0; id < ARRAY_SIZE(pf8x00_regulators_data); id++) { - struct pf8x00_regulator *data = &pf8x00_regulators_data[id]; + for (id = 0; id < ARRAY_SIZE(pf8x00_regs_data); id++) { + struct pf8x00_regulator_data *data = &pf8x00_regs_data[id]; struct regulator_dev *rdev; config.dev = chip->dev; - config.driver_data = chip; + config.driver_data = data; config.regmap = chip->regmap; rdev = devm_regulator_register(&client->dev, &data->desc, &config); @@ -451,18 +581,6 @@ static int pf8x00_i2c_probe(struct i2c_client *client) "failed to register %s regulator\n", data->desc.name); return PTR_ERR(rdev); } - - if ((id >= PF8X00_BUCK1) && (id <= PF8X00_BUCK7)) { - u8 reg = PF8X00_SW_BASE(id) + SW_CONFIG2; - - regmap_update_bits(chip->regmap, reg, - PF8X00_SWXPHASE_MASK, - data->phase_shift); - - regmap_update_bits(chip->regmap, reg, - PF8X00_SWXILIM_MASK, - data->ilim << PF8X00_SWXILIM_SHIFT); - } } return 0; diff --git a/drivers/regulator/qcom-labibb-regulator.c b/drivers/regulator/qcom-labibb-regulator.c index 8ccf572394a2..de25e3279b4b 100644 --- a/drivers/regulator/qcom-labibb-regulator.c +++ b/drivers/regulator/qcom-labibb-regulator.c @@ -17,11 +17,48 @@ #define PMI8998_LAB_REG_BASE 0xde00 #define PMI8998_IBB_REG_BASE 0xdc00 +#define PMI8998_IBB_LAB_REG_OFFSET 0x200 #define REG_LABIBB_STATUS1 0x08 + #define LABIBB_STATUS1_SC_BIT BIT(6) + #define LABIBB_STATUS1_VREG_OK_BIT BIT(7) + +#define REG_LABIBB_INT_SET_TYPE 0x11 +#define REG_LABIBB_INT_POLARITY_HIGH 0x12 +#define REG_LABIBB_INT_POLARITY_LOW 0x13 +#define REG_LABIBB_INT_LATCHED_CLR 0x14 +#define REG_LABIBB_INT_EN_SET 0x15 +#define REG_LABIBB_INT_EN_CLR 0x16 + #define LABIBB_INT_VREG_OK BIT(0) + #define LABIBB_INT_VREG_TYPE_LEVEL 0 + +#define REG_LABIBB_VOLTAGE 0x41 + #define LABIBB_VOLTAGE_OVERRIDE_EN BIT(7) + #define LAB_VOLTAGE_SET_MASK GENMASK(3, 0) + #define IBB_VOLTAGE_SET_MASK GENMASK(5, 0) + #define REG_LABIBB_ENABLE_CTL 0x46 -#define LABIBB_STATUS1_VREG_OK_BIT BIT(7) -#define LABIBB_CONTROL_ENABLE BIT(7) + #define LABIBB_CONTROL_ENABLE BIT(7) + +#define REG_LABIBB_PD_CTL 0x47 + #define LAB_PD_CTL_MASK GENMASK(1, 0) + #define IBB_PD_CTL_MASK (BIT(0) | BIT(7)) + #define LAB_PD_CTL_STRONG_PULL BIT(0) + #define IBB_PD_CTL_HALF_STRENGTH BIT(0) + #define IBB_PD_CTL_EN BIT(7) + +#define REG_LABIBB_CURRENT_LIMIT 0x4b + #define LAB_CURRENT_LIMIT_MASK GENMASK(2, 0) + #define IBB_CURRENT_LIMIT_MASK GENMASK(4, 0) + #define LAB_CURRENT_LIMIT_OVERRIDE_EN BIT(3) + #define LABIBB_CURRENT_LIMIT_EN BIT(7) + +#define REG_IBB_PWRUP_PWRDN_CTL_1 0x58 + #define IBB_CTL_1_DISCHARGE_EN BIT(2) + +#define REG_LABIBB_SOFT_START_CTL 0x5f +#define REG_LABIBB_SEC_ACCESS 0xd0 + #define LABIBB_SEC_UNLOCK_CODE 0xa5 #define LAB_ENABLE_CTL_MASK BIT(7) #define IBB_ENABLE_CTL_MASK (BIT(7) | BIT(6)) @@ -30,14 +67,35 @@ #define LAB_ENABLE_TIME (LABIBB_OFF_ON_DELAY * 2) #define IBB_ENABLE_TIME (LABIBB_OFF_ON_DELAY * 10) #define LABIBB_POLL_ENABLED_TIME 1000 +#define OCP_RECOVERY_INTERVAL_MS 500 +#define SC_RECOVERY_INTERVAL_MS 250 +#define LABIBB_MAX_OCP_COUNT 4 +#define LABIBB_MAX_SC_COUNT 3 +#define LABIBB_MAX_FATAL_COUNT 2 + +struct labibb_current_limits { + u32 uA_min; + u32 uA_step; + u8 ovr_val; +}; struct labibb_regulator { struct regulator_desc desc; struct device *dev; struct regmap *regmap; struct regulator_dev *rdev; + struct labibb_current_limits uA_limits; + struct delayed_work ocp_recovery_work; + struct delayed_work sc_recovery_work; u16 base; u8 type; + u8 dischg_sel; + u8 soft_start_sel; + int sc_irq; + int sc_count; + int ocp_irq; + int ocp_irq_count; + int fatal_count; }; struct labibb_regulator_data { @@ -47,10 +105,579 @@ struct labibb_regulator_data { const struct regulator_desc *desc; }; +static int qcom_labibb_ocp_hw_enable(struct regulator_dev *rdev) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + + /* Clear irq latch status to avoid spurious event */ + ret = regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_LATCHED_CLR, + LABIBB_INT_VREG_OK, 1); + if (ret) + return ret; + + /* Enable OCP HW interrupt */ + return regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_EN_SET, + LABIBB_INT_VREG_OK, 1); +} + +static int qcom_labibb_ocp_hw_disable(struct regulator_dev *rdev) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + + return regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_EN_CLR, + LABIBB_INT_VREG_OK, 1); +} + +/** + * qcom_labibb_check_ocp_status - Check the Over-Current Protection status + * @vreg: Main driver structure + * + * This function checks the STATUS1 register for the VREG_OK bit: if it is + * set, then there is no Over-Current event. + * + * Returns: Zero if there is no over-current, 1 if in over-current or + * negative number for error + */ +static int qcom_labibb_check_ocp_status(struct labibb_regulator *vreg) +{ + u32 cur_status; + int ret; + + ret = regmap_read(vreg->rdev->regmap, vreg->base + REG_LABIBB_STATUS1, + &cur_status); + if (ret) + return ret; + + return !(cur_status & LABIBB_STATUS1_VREG_OK_BIT); +} + +/** + * qcom_labibb_ocp_recovery_worker - Handle OCP event + * @work: OCP work structure + * + * This is the worker function to handle the Over Current Protection + * hardware event; This will check if the hardware is still + * signaling an over-current condition and will eventually stop + * the regulator if such condition is still signaled after + * LABIBB_MAX_OCP_COUNT times. + * + * If the driver that is consuming the regulator did not take action + * for the OCP condition, or the hardware did not stabilize, a cut + * of the LAB and IBB regulators will be forced (regulators will be + * disabled). + * + * As last, if the writes to shut down the LAB/IBB regulators fail + * for more than LABIBB_MAX_FATAL_COUNT, then a kernel panic will be + * triggered, as a last resort to protect the hardware from burning; + * this, however, is expected to never happen, but this is kept to + * try to further ensure that we protect the hardware at all costs. + */ +static void qcom_labibb_ocp_recovery_worker(struct work_struct *work) +{ + struct labibb_regulator *vreg; + const struct regulator_ops *ops; + int ret; + + vreg = container_of(work, struct labibb_regulator, + ocp_recovery_work.work); + ops = vreg->rdev->desc->ops; + + if (vreg->ocp_irq_count >= LABIBB_MAX_OCP_COUNT) { + /* + * If we tried to disable the regulator multiple times but + * we kept failing, there's only one last hope to save our + * hardware from the death: raise a kernel bug, reboot and + * hope that the bootloader kindly saves us. This, though + * is done only as paranoid checking, because failing the + * regmap write to disable the vreg is almost impossible, + * since we got here after multiple regmap R/W. + */ + BUG_ON(vreg->fatal_count > LABIBB_MAX_FATAL_COUNT); + dev_err(&vreg->rdev->dev, "LABIBB: CRITICAL: Disabling regulator\n"); + + /* Disable the regulator immediately to avoid damage */ + ret = ops->disable(vreg->rdev); + if (ret) { + vreg->fatal_count++; + goto reschedule; + } + enable_irq(vreg->ocp_irq); + vreg->fatal_count = 0; + return; + } + + ret = qcom_labibb_check_ocp_status(vreg); + if (ret != 0) { + vreg->ocp_irq_count++; + goto reschedule; + } + + ret = qcom_labibb_ocp_hw_enable(vreg->rdev); + if (ret) { + /* We cannot trust it without OCP enabled. */ + dev_err(vreg->dev, "Cannot enable OCP IRQ\n"); + vreg->ocp_irq_count++; + goto reschedule; + } + + enable_irq(vreg->ocp_irq); + /* Everything went fine: reset the OCP count! */ + vreg->ocp_irq_count = 0; + return; + +reschedule: + mod_delayed_work(system_wq, &vreg->ocp_recovery_work, + msecs_to_jiffies(OCP_RECOVERY_INTERVAL_MS)); +} + +/** + * qcom_labibb_ocp_isr - Interrupt routine for OverCurrent Protection + * @irq: Interrupt number + * @chip: Main driver structure + * + * Over Current Protection (OCP) will signal to the client driver + * that an over-current event has happened and then will schedule + * a recovery worker. + * + * Disabling and eventually re-enabling the regulator is expected + * to be done by the driver, as some hardware may be triggering an + * over-current condition only at first initialization or it may + * be expected only for a very brief amount of time, after which + * the attached hardware may be expected to stabilize its current + * draw. + * + * Returns: IRQ_HANDLED for success or IRQ_NONE for failure. + */ +static irqreturn_t qcom_labibb_ocp_isr(int irq, void *chip) +{ + struct labibb_regulator *vreg = chip; + const struct regulator_ops *ops = vreg->rdev->desc->ops; + int ret; + + /* If the regulator is not enabled, this is a fake event */ + if (!ops->is_enabled(vreg->rdev)) + return 0; + + /* If we tried to recover for too many times it's not getting better */ + if (vreg->ocp_irq_count > LABIBB_MAX_OCP_COUNT) + return IRQ_NONE; + + /* + * If we (unlikely) can't read this register, to prevent hardware + * damage at all costs, we assume that the overcurrent event was + * real; Moreover, if the status register is not signaling OCP, + * it was a spurious event, so it's all ok. + */ + ret = qcom_labibb_check_ocp_status(vreg); + if (ret == 0) { + vreg->ocp_irq_count = 0; + goto end; + } + vreg->ocp_irq_count++; + + /* + * Disable the interrupt temporarily, or it will fire continuously; + * we will re-enable it in the recovery worker function. + */ + disable_irq_nosync(irq); + + /* Warn the user for overcurrent */ + dev_warn(vreg->dev, "Over-Current interrupt fired!\n"); + + /* Disable the interrupt to avoid hogging */ + ret = qcom_labibb_ocp_hw_disable(vreg->rdev); + if (ret) + goto end; + + /* Signal overcurrent event to drivers */ + regulator_notifier_call_chain(vreg->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + +end: + /* Schedule the recovery work */ + schedule_delayed_work(&vreg->ocp_recovery_work, + msecs_to_jiffies(OCP_RECOVERY_INTERVAL_MS)); + if (ret) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int qcom_labibb_set_ocp(struct regulator_dev *rdev) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + char *ocp_irq_name; + u32 irq_flags = IRQF_ONESHOT; + int irq_trig_low, ret; + + /* If there is no OCP interrupt, there's nothing to set */ + if (vreg->ocp_irq <= 0) + return -EINVAL; + + ocp_irq_name = devm_kasprintf(vreg->dev, GFP_KERNEL, "%s-over-current", + vreg->desc.name); + if (!ocp_irq_name) + return -ENOMEM; + + /* IRQ polarities - LAB: trigger-low, IBB: trigger-high */ + switch (vreg->type) { + case QCOM_LAB_TYPE: + irq_flags |= IRQF_TRIGGER_LOW; + irq_trig_low = 1; + break; + case QCOM_IBB_TYPE: + irq_flags |= IRQF_TRIGGER_HIGH; + irq_trig_low = 0; + break; + default: + return -EINVAL; + } + + /* Activate OCP HW level interrupt */ + ret = regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_SET_TYPE, + LABIBB_INT_VREG_OK, + LABIBB_INT_VREG_TYPE_LEVEL); + if (ret) + return ret; + + /* Set OCP interrupt polarity */ + ret = regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_POLARITY_HIGH, + LABIBB_INT_VREG_OK, !irq_trig_low); + if (ret) + return ret; + ret = regmap_update_bits(rdev->regmap, + vreg->base + REG_LABIBB_INT_POLARITY_LOW, + LABIBB_INT_VREG_OK, irq_trig_low); + if (ret) + return ret; + + ret = qcom_labibb_ocp_hw_enable(rdev); + if (ret) + return ret; + + return devm_request_threaded_irq(vreg->dev, vreg->ocp_irq, NULL, + qcom_labibb_ocp_isr, irq_flags, + ocp_irq_name, vreg); +} + +/** + * qcom_labibb_check_sc_status - Check the Short Circuit Protection status + * @vreg: Main driver structure + * + * This function checks the STATUS1 register on both LAB and IBB regulators + * for the ShortCircuit bit: if it is set on *any* of them, then we have + * experienced a short-circuit event. + * + * Returns: Zero if there is no short-circuit, 1 if in short-circuit or + * negative number for error + */ +static int qcom_labibb_check_sc_status(struct labibb_regulator *vreg) +{ + u32 ibb_status, ibb_reg, lab_status, lab_reg; + int ret; + + /* We have to work on both regulators due to PBS... */ + lab_reg = ibb_reg = vreg->base + REG_LABIBB_STATUS1; + if (vreg->type == QCOM_LAB_TYPE) + ibb_reg -= PMI8998_IBB_LAB_REG_OFFSET; + else + lab_reg += PMI8998_IBB_LAB_REG_OFFSET; + + ret = regmap_read(vreg->rdev->regmap, lab_reg, &lab_status); + if (ret) + return ret; + ret = regmap_read(vreg->rdev->regmap, ibb_reg, &ibb_status); + if (ret) + return ret; + + return !!(lab_status & LABIBB_STATUS1_SC_BIT) || + !!(ibb_status & LABIBB_STATUS1_SC_BIT); +} + +/** + * qcom_labibb_sc_recovery_worker - Handle Short Circuit event + * @work: SC work structure + * + * This is the worker function to handle the Short Circuit Protection + * hardware event; This will check if the hardware is still + * signaling a short-circuit condition and will eventually never + * re-enable the regulator if such condition is still signaled after + * LABIBB_MAX_SC_COUNT times. + * + * If the driver that is consuming the regulator did not take action + * for the SC condition, or the hardware did not stabilize, this + * worker will stop rescheduling, leaving the regulators disabled + * as already done by the Portable Batch System (PBS). + * + * Returns: IRQ_HANDLED for success or IRQ_NONE for failure. + */ +static void qcom_labibb_sc_recovery_worker(struct work_struct *work) +{ + struct labibb_regulator *vreg; + const struct regulator_ops *ops; + u32 lab_reg, ibb_reg, lab_val, ibb_val, val; + bool pbs_cut = false; + int i, sc, ret; + + vreg = container_of(work, struct labibb_regulator, + sc_recovery_work.work); + ops = vreg->rdev->desc->ops; + + /* + * If we tried to check the regulator status multiple times but we + * kept failing, then just bail out, as the Portable Batch System + * (PBS) will disable the vregs for us, preventing hardware damage. + */ + if (vreg->fatal_count > LABIBB_MAX_FATAL_COUNT) + return; + + /* Too many short-circuit events. Throw in the towel. */ + if (vreg->sc_count > LABIBB_MAX_SC_COUNT) + return; + + /* + * The Portable Batch System (PBS) automatically disables LAB + * and IBB when a short-circuit event is detected, so we have to + * check and work on both of them at the same time. + */ + lab_reg = ibb_reg = vreg->base + REG_LABIBB_ENABLE_CTL; + if (vreg->type == QCOM_LAB_TYPE) + ibb_reg -= PMI8998_IBB_LAB_REG_OFFSET; + else + lab_reg += PMI8998_IBB_LAB_REG_OFFSET; + + sc = qcom_labibb_check_sc_status(vreg); + if (sc) + goto reschedule; + + for (i = 0; i < LABIBB_MAX_SC_COUNT; i++) { + ret = regmap_read(vreg->regmap, lab_reg, &lab_val); + if (ret) { + vreg->fatal_count++; + goto reschedule; + } + + ret = regmap_read(vreg->regmap, ibb_reg, &ibb_val); + if (ret) { + vreg->fatal_count++; + goto reschedule; + } + val = lab_val & ibb_val; + + if (!(val & LABIBB_CONTROL_ENABLE)) { + pbs_cut = true; + break; + } + usleep_range(5000, 6000); + } + if (pbs_cut) + goto reschedule; + + + /* + * If we have reached this point, we either have successfully + * recovered from the SC condition or we had a spurious SC IRQ, + * which means that we can re-enable the regulators, if they + * have ever been disabled by the PBS. + */ + ret = ops->enable(vreg->rdev); + if (ret) + goto reschedule; + + /* Everything went fine: reset the OCP count! */ + vreg->sc_count = 0; + enable_irq(vreg->sc_irq); + return; + +reschedule: + /* + * Now that we have done basic handling of the short-circuit, + * reschedule this worker in the regular system workqueue, as + * taking action is not truly urgent anymore. + */ + vreg->sc_count++; + mod_delayed_work(system_wq, &vreg->sc_recovery_work, + msecs_to_jiffies(SC_RECOVERY_INTERVAL_MS)); +} + +/** + * qcom_labibb_sc_isr - Interrupt routine for Short Circuit Protection + * @irq: Interrupt number + * @chip: Main driver structure + * + * Short Circuit Protection (SCP) will signal to the client driver + * that a regulation-out event has happened and then will schedule + * a recovery worker. + * + * The LAB and IBB regulators will be automatically disabled by the + * Portable Batch System (PBS) and they will be enabled again by + * the worker function if the hardware stops signaling the short + * circuit event. + * + * Returns: IRQ_HANDLED for success or IRQ_NONE for failure. + */ +static irqreturn_t qcom_labibb_sc_isr(int irq, void *chip) +{ + struct labibb_regulator *vreg = chip; + + if (vreg->sc_count > LABIBB_MAX_SC_COUNT) + return IRQ_NONE; + + /* Warn the user for short circuit */ + dev_warn(vreg->dev, "Short-Circuit interrupt fired!\n"); + + /* + * Disable the interrupt temporarily, or it will fire continuously; + * we will re-enable it in the recovery worker function. + */ + disable_irq_nosync(irq); + + /* Signal out of regulation event to drivers */ + regulator_notifier_call_chain(vreg->rdev, + REGULATOR_EVENT_REGULATION_OUT, NULL); + + /* Schedule the short-circuit handling as high-priority work */ + mod_delayed_work(system_highpri_wq, &vreg->sc_recovery_work, + msecs_to_jiffies(SC_RECOVERY_INTERVAL_MS)); + return IRQ_HANDLED; +} + + +static int qcom_labibb_set_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + struct regulator_desc *desc = &vreg->desc; + struct labibb_current_limits *lim = &vreg->uA_limits; + u32 mask, val; + int i, ret, sel = -1; + + if (min_uA < lim->uA_min || max_uA < lim->uA_min) + return -EINVAL; + + for (i = 0; i < desc->n_current_limits; i++) { + int uA_limit = (lim->uA_step * i) + lim->uA_min; + + if (max_uA >= uA_limit && min_uA <= uA_limit) + sel = i; + } + if (sel < 0) + return -EINVAL; + + /* Current limit setting needs secure access */ + ret = regmap_write(vreg->regmap, vreg->base + REG_LABIBB_SEC_ACCESS, + LABIBB_SEC_UNLOCK_CODE); + if (ret) + return ret; + + mask = desc->csel_mask | lim->ovr_val; + mask |= LABIBB_CURRENT_LIMIT_EN; + val = (u32)sel | lim->ovr_val; + val |= LABIBB_CURRENT_LIMIT_EN; + + return regmap_update_bits(vreg->regmap, desc->csel_reg, mask, val); +} + +static int qcom_labibb_get_current_limit(struct regulator_dev *rdev) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + struct regulator_desc *desc = &vreg->desc; + struct labibb_current_limits *lim = &vreg->uA_limits; + unsigned int cur_step; + int ret; + + ret = regmap_read(vreg->regmap, desc->csel_reg, &cur_step); + if (ret) + return ret; + cur_step &= desc->csel_mask; + + return (cur_step * lim->uA_step) + lim->uA_min; +} + +static int qcom_labibb_set_soft_start(struct regulator_dev *rdev) +{ + struct labibb_regulator *vreg = rdev_get_drvdata(rdev); + u32 val = 0; + + if (vreg->type == QCOM_IBB_TYPE) + val = vreg->dischg_sel; + else + val = vreg->soft_start_sel; + + return regmap_write(rdev->regmap, rdev->desc->soft_start_reg, val); +} + +static int qcom_labibb_get_table_sel(const int *table, int sz, u32 value) +{ + int i; + + for (i = 0; i < sz; i++) + if (table[i] == value) + return i; + return -EINVAL; +} + +/* IBB discharge resistor values in KOhms */ +static const int dischg_resistor_values[] = { 300, 64, 32, 16 }; + +/* Soft start time in microseconds */ +static const int soft_start_values[] = { 200, 400, 600, 800 }; + +static int qcom_labibb_of_parse_cb(struct device_node *np, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct labibb_regulator *vreg = config->driver_data; + u32 dischg_kohms, soft_start_time; + int ret; + + ret = of_property_read_u32(np, "qcom,discharge-resistor-kohms", + &dischg_kohms); + if (ret) + dischg_kohms = 300; + + ret = qcom_labibb_get_table_sel(dischg_resistor_values, + ARRAY_SIZE(dischg_resistor_values), + dischg_kohms); + if (ret < 0) + return ret; + vreg->dischg_sel = (u8)ret; + + ret = of_property_read_u32(np, "qcom,soft-start-us", + &soft_start_time); + if (ret) + soft_start_time = 200; + + ret = qcom_labibb_get_table_sel(soft_start_values, + ARRAY_SIZE(soft_start_values), + soft_start_time); + if (ret < 0) + return ret; + vreg->soft_start_sel = (u8)ret; + + return 0; +} + static const struct regulator_ops qcom_labibb_ops = { .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .set_active_discharge = regulator_set_active_discharge_regmap, + .set_pull_down = regulator_set_pull_down_regmap, + .set_current_limit = qcom_labibb_set_current_limit, + .get_current_limit = qcom_labibb_get_current_limit, + .set_soft_start = qcom_labibb_set_soft_start, + .set_over_current_protection = qcom_labibb_set_ocp, }; static const struct regulator_desc pmi8998_lab_desc = { @@ -59,10 +686,25 @@ static const struct regulator_desc pmi8998_lab_desc = { .enable_val = LABIBB_CONTROL_ENABLE, .enable_time = LAB_ENABLE_TIME, .poll_enabled_time = LABIBB_POLL_ENABLED_TIME, + .soft_start_reg = (PMI8998_LAB_REG_BASE + REG_LABIBB_SOFT_START_CTL), + .pull_down_reg = (PMI8998_LAB_REG_BASE + REG_LABIBB_PD_CTL), + .pull_down_mask = LAB_PD_CTL_MASK, + .pull_down_val_on = LAB_PD_CTL_STRONG_PULL, + .vsel_reg = (PMI8998_LAB_REG_BASE + REG_LABIBB_VOLTAGE), + .vsel_mask = LAB_VOLTAGE_SET_MASK, + .apply_reg = (PMI8998_LAB_REG_BASE + REG_LABIBB_VOLTAGE), + .apply_bit = LABIBB_VOLTAGE_OVERRIDE_EN, + .csel_reg = (PMI8998_LAB_REG_BASE + REG_LABIBB_CURRENT_LIMIT), + .csel_mask = LAB_CURRENT_LIMIT_MASK, + .n_current_limits = 8, .off_on_delay = LABIBB_OFF_ON_DELAY, .owner = THIS_MODULE, .type = REGULATOR_VOLTAGE, + .min_uV = 4600000, + .uV_step = 100000, + .n_voltages = 16, .ops = &qcom_labibb_ops, + .of_parse_cb = qcom_labibb_of_parse_cb, }; static const struct regulator_desc pmi8998_ibb_desc = { @@ -71,10 +713,29 @@ static const struct regulator_desc pmi8998_ibb_desc = { .enable_val = LABIBB_CONTROL_ENABLE, .enable_time = IBB_ENABLE_TIME, .poll_enabled_time = LABIBB_POLL_ENABLED_TIME, + .soft_start_reg = (PMI8998_IBB_REG_BASE + REG_LABIBB_SOFT_START_CTL), + .active_discharge_off = 0, + .active_discharge_on = IBB_CTL_1_DISCHARGE_EN, + .active_discharge_mask = IBB_CTL_1_DISCHARGE_EN, + .active_discharge_reg = (PMI8998_IBB_REG_BASE + REG_IBB_PWRUP_PWRDN_CTL_1), + .pull_down_reg = (PMI8998_IBB_REG_BASE + REG_LABIBB_PD_CTL), + .pull_down_mask = IBB_PD_CTL_MASK, + .pull_down_val_on = IBB_PD_CTL_HALF_STRENGTH | IBB_PD_CTL_EN, + .vsel_reg = (PMI8998_IBB_REG_BASE + REG_LABIBB_VOLTAGE), + .vsel_mask = IBB_VOLTAGE_SET_MASK, + .apply_reg = (PMI8998_IBB_REG_BASE + REG_LABIBB_VOLTAGE), + .apply_bit = LABIBB_VOLTAGE_OVERRIDE_EN, + .csel_reg = (PMI8998_IBB_REG_BASE + REG_LABIBB_CURRENT_LIMIT), + .csel_mask = IBB_CURRENT_LIMIT_MASK, + .n_current_limits = 32, .off_on_delay = LABIBB_OFF_ON_DELAY, .owner = THIS_MODULE, .type = REGULATOR_VOLTAGE, + .min_uV = 1400000, + .uV_step = 100000, + .n_voltages = 64, .ops = &qcom_labibb_ops, + .of_parse_cb = qcom_labibb_of_parse_cb, }; static const struct labibb_regulator_data pmi8998_labibb_data[] = { @@ -94,7 +755,7 @@ static int qcom_labibb_regulator_probe(struct platform_device *pdev) struct labibb_regulator *vreg; struct device *dev = &pdev->dev; struct regulator_config cfg = {}; - + struct device_node *reg_node; const struct of_device_id *match; const struct labibb_regulator_data *reg_data; struct regmap *reg_regmap; @@ -112,6 +773,8 @@ static int qcom_labibb_regulator_probe(struct platform_device *pdev) return -ENODEV; for (reg_data = match->data; reg_data->name; reg_data++) { + char *sc_irq_name; + int irq = 0; /* Validate if the type of regulator is indeed * what's mentioned in DT. @@ -134,10 +797,61 @@ static int qcom_labibb_regulator_probe(struct platform_device *pdev) if (!vreg) return -ENOMEM; + sc_irq_name = devm_kasprintf(dev, GFP_KERNEL, + "%s-short-circuit", + reg_data->name); + if (!sc_irq_name) + return -ENOMEM; + + reg_node = of_get_child_by_name(pdev->dev.of_node, + reg_data->name); + if (!reg_node) + return -EINVAL; + + /* The Short Circuit interrupt is critical */ + irq = of_irq_get_byname(reg_node, "sc-err"); + if (irq <= 0) { + if (irq == 0) + irq = -EINVAL; + + return dev_err_probe(vreg->dev, irq, + "Short-circuit irq not found.\n"); + } + vreg->sc_irq = irq; + + /* OverCurrent Protection IRQ is optional */ + irq = of_irq_get_byname(reg_node, "ocp"); + vreg->ocp_irq = irq; + vreg->ocp_irq_count = 0; + of_node_put(reg_node); + vreg->regmap = reg_regmap; vreg->dev = dev; vreg->base = reg_data->base; vreg->type = reg_data->type; + INIT_DELAYED_WORK(&vreg->sc_recovery_work, + qcom_labibb_sc_recovery_worker); + + if (vreg->ocp_irq > 0) + INIT_DELAYED_WORK(&vreg->ocp_recovery_work, + qcom_labibb_ocp_recovery_worker); + + switch (vreg->type) { + case QCOM_LAB_TYPE: + /* LAB Limits: 200-1600mA */ + vreg->uA_limits.uA_min = 200000; + vreg->uA_limits.uA_step = 200000; + vreg->uA_limits.ovr_val = LAB_CURRENT_LIMIT_OVERRIDE_EN; + break; + case QCOM_IBB_TYPE: + /* IBB Limits: 0-1550mA */ + vreg->uA_limits.uA_min = 0; + vreg->uA_limits.uA_step = 50000; + vreg->uA_limits.ovr_val = 0; /* No override bit */ + break; + default: + return -EINVAL; + } memcpy(&vreg->desc, reg_data->desc, sizeof(vreg->desc)); vreg->desc.of_match = reg_data->name; @@ -155,6 +869,14 @@ static int qcom_labibb_regulator_probe(struct platform_device *pdev) reg_data->name, ret); return PTR_ERR(vreg->rdev); } + + ret = devm_request_threaded_irq(vreg->dev, vreg->sc_irq, NULL, + qcom_labibb_sc_isr, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING, + sc_irq_name, vreg); + if (ret) + return ret; } return 0; diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c index c395a8dda6f7..79a554f1029d 100644 --- a/drivers/regulator/qcom-rpmh-regulator.c +++ b/drivers/regulator/qcom-rpmh-regulator.c @@ -732,6 +732,15 @@ static const struct rpmh_vreg_hw_data pmic5_hfsmps515 = { .of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode, }; +static const struct rpmh_vreg_hw_data pmic5_hfsmps515_1 = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(900000, 0, 4, 16000), + .n_voltages = 5, + .pmic_mode_map = pmic_mode_map_pmic5_smps, + .of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode, +}; + static const struct rpmh_vreg_hw_data pmic5_bob = { .regulator_type = VRM, .ops = &rpmh_regulator_vrm_bypass_ops, @@ -928,6 +937,19 @@ static const struct rpmh_vreg_init_data pm8009_vreg_data[] = { RPMH_VREG("ldo4", "ldo%s4", &pmic5_nldo, "vdd-l4"), RPMH_VREG("ldo5", "ldo%s5", &pmic5_pldo, "vdd-l5-l6"), RPMH_VREG("ldo6", "ldo%s6", &pmic5_pldo, "vdd-l5-l6"), + RPMH_VREG("ldo7", "ldo%s7", &pmic5_pldo_lv, "vdd-l7"), + {}, +}; + +static const struct rpmh_vreg_init_data pm8009_1_vreg_data[] = { + RPMH_VREG("smps1", "smp%s1", &pmic5_hfsmps510, "vdd-s1"), + RPMH_VREG("smps2", "smp%s2", &pmic5_hfsmps515_1, "vdd-s2"), + RPMH_VREG("ldo1", "ldo%s1", &pmic5_nldo, "vdd-l1"), + RPMH_VREG("ldo2", "ldo%s2", &pmic5_nldo, "vdd-l2"), + RPMH_VREG("ldo3", "ldo%s3", &pmic5_nldo, "vdd-l3"), + RPMH_VREG("ldo4", "ldo%s4", &pmic5_nldo, "vdd-l4"), + RPMH_VREG("ldo5", "ldo%s5", &pmic5_pldo, "vdd-l5-l6"), + RPMH_VREG("ldo6", "ldo%s6", &pmic5_pldo, "vdd-l5-l6"), RPMH_VREG("ldo7", "ldo%s6", &pmic5_pldo_lv, "vdd-l7"), {}, }; @@ -1057,6 +1079,10 @@ static const struct of_device_id __maybe_unused rpmh_regulator_match_table[] = { .compatible = "qcom,pm8009-rpmh-regulators", .data = pm8009_vreg_data, }, + { + .compatible = "qcom,pm8009-1-rpmh-regulators", + .data = pm8009_1_vreg_data, + }, { .compatible = "qcom,pm8150-rpmh-regulators", .data = pm8150_vreg_data, @@ -1089,6 +1115,14 @@ static const struct of_device_id __maybe_unused rpmh_regulator_match_table[] = { .compatible = "qcom,pm6150l-rpmh-regulators", .data = pm6150l_vreg_data, }, + { + .compatible = "qcom,pmc8180-rpmh-regulators", + .data = pm8150_vreg_data, + }, + { + .compatible = "qcom,pmc8180c-rpmh-regulators", + .data = pm8150l_vreg_data, + }, { .compatible = "qcom,pmx55-rpmh-regulators", .data = pmx55_vreg_data, diff --git a/drivers/regulator/rohm-regulator.c b/drivers/regulator/rohm-regulator.c index 399002383b28..5c558b153d55 100644 --- a/drivers/regulator/rohm-regulator.c +++ b/drivers/regulator/rohm-regulator.c @@ -52,9 +52,12 @@ int rohm_regulator_set_dvs_levels(const struct rohm_dvs_config *dvs, char *prop; unsigned int reg, mask, omask, oreg = desc->enable_reg; - for (i = 0; i < ROHM_DVS_LEVEL_MAX && !ret; i++) { - if (dvs->level_map & (1 << i)) { - switch (i + 1) { + for (i = 0; i < ROHM_DVS_LEVEL_VALID_AMOUNT && !ret; i++) { + int bit; + + bit = BIT(i); + if (dvs->level_map & bit) { + switch (bit) { case ROHM_DVS_LEVEL_RUN: prop = "rohm,dvs-run-voltage"; reg = dvs->run_reg; diff --git a/drivers/regulator/rt4831-regulator.c b/drivers/regulator/rt4831-regulator.c new file mode 100644 index 000000000000..3d4695ded629 --- /dev/null +++ b/drivers/regulator/rt4831-regulator.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + DSV_OUT_VLCM = 0, + DSV_OUT_VPOS, + DSV_OUT_VNEG, + DSV_OUT_MAX +}; + +#define RT4831_REG_DSVEN 0x09 +#define RT4831_REG_VLCM 0x0c +#define RT4831_REG_VPOS 0x0d +#define RT4831_REG_VNEG 0x0e +#define RT4831_REG_FLAGS 0x0f + +#define RT4831_VOLT_MASK GENMASK(5, 0) +#define RT4831_DSVMODE_SHIFT 5 +#define RT4831_DSVMODE_MASK GENMASK(7, 5) +#define RT4831_POSADEN_MASK BIT(4) +#define RT4831_NEGADEN_MASK BIT(3) +#define RT4831_POSEN_MASK BIT(2) +#define RT4831_NEGEN_MASK BIT(1) + +#define RT4831_OTP_MASK BIT(6) +#define RT4831_LCMOVP_MASK BIT(5) +#define RT4831_VPOSSCP_MASK BIT(3) +#define RT4831_VNEGSCP_MASK BIT(2) + +#define DSV_MODE_NORMAL (0x4 << RT4831_DSVMODE_SHIFT) +#define DSV_MODE_BYPASS (0x6 << RT4831_DSVMODE_SHIFT) +#define STEP_UV 50000 +#define VLCM_MIN_UV 4000000 +#define VLCM_MAX_UV 7150000 +#define VLCM_N_VOLTAGES ((VLCM_MAX_UV - VLCM_MIN_UV) / STEP_UV + 1) +#define VPN_MIN_UV 4000000 +#define VPN_MAX_UV 6500000 +#define VPN_N_VOLTAGES ((VPN_MAX_UV - VPN_MIN_UV) / STEP_UV + 1) + +static int rt4831_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + struct regmap *regmap = rdev_get_regmap(rdev); + int rid = rdev_get_id(rdev); + unsigned int val, events = 0; + int ret; + + ret = regmap_read(regmap, RT4831_REG_FLAGS, &val); + if (ret) + return ret; + + if (val & RT4831_OTP_MASK) + events |= REGULATOR_ERROR_OVER_TEMP; + + if (rid == DSV_OUT_VLCM && (val & RT4831_LCMOVP_MASK)) + events |= REGULATOR_ERROR_OVER_CURRENT; + + if (rid == DSV_OUT_VPOS && (val & RT4831_VPOSSCP_MASK)) + events |= REGULATOR_ERROR_OVER_CURRENT; + + if (rid == DSV_OUT_VNEG && (val & RT4831_VNEGSCP_MASK)) + events |= REGULATOR_ERROR_OVER_CURRENT; + + *flags = events; + return 0; +} + +static const struct regulator_ops rt4831_dsvlcm_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_bypass = regulator_set_bypass_regmap, + .get_bypass = regulator_get_bypass_regmap, + .get_error_flags = rt4831_get_error_flags, +}; + +static const struct regulator_ops rt4831_dsvpn_ops = { + .list_voltage = regulator_list_voltage_linear, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_active_discharge = regulator_set_active_discharge_regmap, + .get_error_flags = rt4831_get_error_flags, +}; + +static const struct regulator_desc rt4831_regulator_descs[] = { + { + .name = "DSVLCM", + .ops = &rt4831_dsvlcm_ops, + .of_match = of_match_ptr("DSVLCM"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .id = DSV_OUT_VLCM, + .n_voltages = VLCM_N_VOLTAGES, + .min_uV = VLCM_MIN_UV, + .uV_step = STEP_UV, + .vsel_reg = RT4831_REG_VLCM, + .vsel_mask = RT4831_VOLT_MASK, + .bypass_reg = RT4831_REG_DSVEN, + .bypass_val_on = DSV_MODE_BYPASS, + .bypass_val_off = DSV_MODE_NORMAL, + }, + { + .name = "DSVP", + .ops = &rt4831_dsvpn_ops, + .of_match = of_match_ptr("DSVP"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .id = DSV_OUT_VPOS, + .n_voltages = VPN_N_VOLTAGES, + .min_uV = VPN_MIN_UV, + .uV_step = STEP_UV, + .vsel_reg = RT4831_REG_VPOS, + .vsel_mask = RT4831_VOLT_MASK, + .enable_reg = RT4831_REG_DSVEN, + .enable_mask = RT4831_POSEN_MASK, + .active_discharge_reg = RT4831_REG_DSVEN, + .active_discharge_mask = RT4831_POSADEN_MASK, + }, + { + .name = "DSVN", + .ops = &rt4831_dsvpn_ops, + .of_match = of_match_ptr("DSVN"), + .regulators_node = of_match_ptr("regulators"), + .type = REGULATOR_VOLTAGE, + .id = DSV_OUT_VNEG, + .n_voltages = VPN_N_VOLTAGES, + .min_uV = VPN_MIN_UV, + .uV_step = STEP_UV, + .vsel_reg = RT4831_REG_VNEG, + .vsel_mask = RT4831_VOLT_MASK, + .enable_reg = RT4831_REG_DSVEN, + .enable_mask = RT4831_NEGEN_MASK, + .active_discharge_reg = RT4831_REG_DSVEN, + .active_discharge_mask = RT4831_NEGADEN_MASK, + } +}; + +static int rt4831_regulator_probe(struct platform_device *pdev) +{ + struct regmap *regmap; + struct regulator_dev *rdev; + struct regulator_config config = {}; + int i, ret; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "Failed to init regmap\n"); + return PTR_ERR(regmap); + } + + /* Configure DSV mode to normal by default */ + ret = regmap_update_bits(regmap, RT4831_REG_DSVEN, RT4831_DSVMODE_MASK, DSV_MODE_NORMAL); + if (ret) { + dev_err(&pdev->dev, "Failed to configure dsv mode to normal\n"); + return ret; + } + + config.dev = pdev->dev.parent; + config.regmap = regmap; + + for (i = 0; i < DSV_OUT_MAX; i++) { + rdev = devm_regulator_register(&pdev->dev, rt4831_regulator_descs + i, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register %d regulator\n", i); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id rt4831_regulator_match[] = { + { "rt4831-regulator", 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, rt4831_regulator_match); + +static struct platform_driver rt4831_regulator_driver = { + .driver = { + .name = "rt4831-regulator", + }, + .id_table = rt4831_regulator_match, + .probe = rt4831_regulator_probe, +}; +module_platform_driver(rt4831_regulator_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/s5m8767.c b/drivers/regulator/s5m8767.c index 3fa472127e9a..7c111bbdc2af 100644 --- a/drivers/regulator/s5m8767.c +++ b/drivers/regulator/s5m8767.c @@ -544,14 +544,18 @@ static int s5m8767_pmic_dt_parse_pdata(struct platform_device *pdev, rdata = devm_kcalloc(&pdev->dev, pdata->num_regulators, sizeof(*rdata), GFP_KERNEL); - if (!rdata) + if (!rdata) { + of_node_put(regulators_np); return -ENOMEM; + } rmode = devm_kcalloc(&pdev->dev, pdata->num_regulators, sizeof(*rmode), GFP_KERNEL); - if (!rmode) + if (!rmode) { + of_node_put(regulators_np); return -ENOMEM; + } pdata->regulators = rdata; pdata->opmode = rmode; @@ -573,10 +577,13 @@ static int s5m8767_pmic_dt_parse_pdata(struct platform_device *pdev, "s5m8767,pmic-ext-control", GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_NONEXCLUSIVE, "s5m8767"); - if (PTR_ERR(rdata->ext_control_gpiod) == -ENOENT) + if (PTR_ERR(rdata->ext_control_gpiod) == -ENOENT) { rdata->ext_control_gpiod = NULL; - else if (IS_ERR(rdata->ext_control_gpiod)) + } else if (IS_ERR(rdata->ext_control_gpiod)) { + of_node_put(reg_np); + of_node_put(regulators_np); return PTR_ERR(rdata->ext_control_gpiod); + } rdata->id = i; rdata->initdata = of_get_regulator_init_data( diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6123f9f4fbc9..2a402c10f8f1 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -973,18 +973,6 @@ config RTC_DRV_ALPHA Direct support for the real-time clock found on every Alpha system, specifically MC146818 compatibles. If in doubt, say Y. -config RTC_DRV_VRTC - tristate "Virtual RTC for Intel MID platforms" - depends on X86_INTEL_MID - default y if X86_INTEL_MID - - help - Say "yes" here to get direct support for the real time clock - found on Moorestown platforms. The VRTC is a emulated RTC that - derives its clock source from a real RTC in the PMIC. The MC146818 - style programming interface is mostly conserved, but any - updates are done via IPC calls to the system controller FW. - config RTC_DRV_DS1216 tristate "Dallas DS1216" depends on SNI_RM diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index bb8f319b09fb..f8ac4f574522 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -174,7 +174,6 @@ obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o -obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WILCO_EC) += rtc-wilco-ec.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o diff --git a/drivers/rtc/rtc-mrst.c b/drivers/rtc/rtc-mrst.c deleted file mode 100644 index 421b3b6071b6..000000000000 --- a/drivers/rtc/rtc-mrst.c +++ /dev/null @@ -1,521 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * rtc-mrst.c: Driver for Moorestown virtual RTC - * - * (C) Copyright 2009 Intel Corporation - * Author: Jacob Pan (jacob.jun.pan@intel.com) - * Feng Tang (feng.tang@intel.com) - * - * Note: - * VRTC is emulated by system controller firmware, the real HW - * RTC is located in the PMIC device. SCU FW shadows PMIC RTC - * in a memory mapped IO space that is visible to the host IA - * processor. - * - * This driver is based upon drivers/rtc/rtc-cmos.c - */ - -/* - * Note: - * * vRTC only supports binary mode and 24H mode - * * vRTC only support PIE and AIE, no UIE, and its PIE only happens - * at 23:59:59pm everyday, no support for adjustable frequency - * * Alarm function is also limited to hr/min/sec. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -struct mrst_rtc { - struct rtc_device *rtc; - struct device *dev; - int irq; - - u8 enabled_wake; - u8 suspend_ctrl; -}; - -static const char driver_name[] = "rtc_mrst"; - -#define RTC_IRQMASK (RTC_PF | RTC_AF) - -static inline int is_intr(u8 rtc_intr) -{ - if (!(rtc_intr & RTC_IRQF)) - return 0; - return rtc_intr & RTC_IRQMASK; -} - -static inline unsigned char vrtc_is_updating(void) -{ - unsigned char uip; - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - uip = (vrtc_cmos_read(RTC_FREQ_SELECT) & RTC_UIP); - spin_unlock_irqrestore(&rtc_lock, flags); - return uip; -} - -/* - * rtc_time's year contains the increment over 1900, but vRTC's YEAR - * register can't be programmed to value larger than 0x64, so vRTC - * driver chose to use 1972 (1970 is UNIX time start point) as the base, - * and does the translation at read/write time. - * - * Why not just use 1970 as the offset? it's because using 1972 will - * make it consistent in leap year setting for both vrtc and low-level - * physical rtc devices. Then why not use 1960 as the offset? If we use - * 1960, for a device's first use, its YEAR register is 0 and the system - * year will be parsed as 1960 which is not a valid UNIX time and will - * cause many applications to fail mysteriously. - */ -static int mrst_read_time(struct device *dev, struct rtc_time *time) -{ - unsigned long flags; - - if (vrtc_is_updating()) - msleep(20); - - spin_lock_irqsave(&rtc_lock, flags); - time->tm_sec = vrtc_cmos_read(RTC_SECONDS); - time->tm_min = vrtc_cmos_read(RTC_MINUTES); - time->tm_hour = vrtc_cmos_read(RTC_HOURS); - time->tm_mday = vrtc_cmos_read(RTC_DAY_OF_MONTH); - time->tm_mon = vrtc_cmos_read(RTC_MONTH); - time->tm_year = vrtc_cmos_read(RTC_YEAR); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* Adjust for the 1972/1900 */ - time->tm_year += 72; - time->tm_mon--; - return 0; -} - -static int mrst_set_time(struct device *dev, struct rtc_time *time) -{ - int ret; - unsigned long flags; - unsigned char mon, day, hrs, min, sec; - unsigned int yrs; - - yrs = time->tm_year; - mon = time->tm_mon + 1; /* tm_mon starts at zero */ - day = time->tm_mday; - hrs = time->tm_hour; - min = time->tm_min; - sec = time->tm_sec; - - if (yrs < 72 || yrs > 172) - return -EINVAL; - yrs -= 72; - - spin_lock_irqsave(&rtc_lock, flags); - - vrtc_cmos_write(yrs, RTC_YEAR); - vrtc_cmos_write(mon, RTC_MONTH); - vrtc_cmos_write(day, RTC_DAY_OF_MONTH); - vrtc_cmos_write(hrs, RTC_HOURS); - vrtc_cmos_write(min, RTC_MINUTES); - vrtc_cmos_write(sec, RTC_SECONDS); - - spin_unlock_irqrestore(&rtc_lock, flags); - - ret = intel_scu_ipc_simple_command(IPCMSG_VRTC, IPC_CMD_VRTC_SETTIME); - return ret; -} - -static int mrst_read_alarm(struct device *dev, struct rtc_wkalrm *t) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char rtc_control; - - if (mrst->irq <= 0) - return -EIO; - - /* vRTC only supports binary mode */ - spin_lock_irq(&rtc_lock); - t->time.tm_sec = vrtc_cmos_read(RTC_SECONDS_ALARM); - t->time.tm_min = vrtc_cmos_read(RTC_MINUTES_ALARM); - t->time.tm_hour = vrtc_cmos_read(RTC_HOURS_ALARM); - - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - t->enabled = !!(rtc_control & RTC_AIE); - t->pending = 0; - - return 0; -} - -static void mrst_checkintr(struct mrst_rtc *mrst, unsigned char rtc_control) -{ - unsigned char rtc_intr; - - /* - * NOTE after changing RTC_xIE bits we always read INTR_FLAGS; - * allegedly some older rtcs need that to handle irqs properly - */ - rtc_intr = vrtc_cmos_read(RTC_INTR_FLAGS); - rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; - if (is_intr(rtc_intr)) - rtc_update_irq(mrst->rtc, 1, rtc_intr); -} - -static void mrst_irq_enable(struct mrst_rtc *mrst, unsigned char mask) -{ - unsigned char rtc_control; - - /* - * Flush any pending IRQ status, notably for update irqs, - * before we enable new IRQs - */ - rtc_control = vrtc_cmos_read(RTC_CONTROL); - mrst_checkintr(mrst, rtc_control); - - rtc_control |= mask; - vrtc_cmos_write(rtc_control, RTC_CONTROL); - - mrst_checkintr(mrst, rtc_control); -} - -static void mrst_irq_disable(struct mrst_rtc *mrst, unsigned char mask) -{ - unsigned char rtc_control; - - rtc_control = vrtc_cmos_read(RTC_CONTROL); - rtc_control &= ~mask; - vrtc_cmos_write(rtc_control, RTC_CONTROL); - mrst_checkintr(mrst, rtc_control); -} - -static int mrst_set_alarm(struct device *dev, struct rtc_wkalrm *t) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char hrs, min, sec; - int ret = 0; - - if (!mrst->irq) - return -EIO; - - hrs = t->time.tm_hour; - min = t->time.tm_min; - sec = t->time.tm_sec; - - spin_lock_irq(&rtc_lock); - /* Next rtc irq must not be from previous alarm setting */ - mrst_irq_disable(mrst, RTC_AIE); - - /* Update alarm */ - vrtc_cmos_write(hrs, RTC_HOURS_ALARM); - vrtc_cmos_write(min, RTC_MINUTES_ALARM); - vrtc_cmos_write(sec, RTC_SECONDS_ALARM); - - spin_unlock_irq(&rtc_lock); - - ret = intel_scu_ipc_simple_command(IPCMSG_VRTC, IPC_CMD_VRTC_SETALARM); - if (ret) - return ret; - - spin_lock_irq(&rtc_lock); - if (t->enabled) - mrst_irq_enable(mrst, RTC_AIE); - - spin_unlock_irq(&rtc_lock); - - return 0; -} - -/* Currently, the vRTC doesn't support UIE ON/OFF */ -static int mrst_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - if (enabled) - mrst_irq_enable(mrst, RTC_AIE); - else - mrst_irq_disable(mrst, RTC_AIE); - spin_unlock_irqrestore(&rtc_lock, flags); - return 0; -} - - -#if IS_ENABLED(CONFIG_RTC_INTF_PROC) - -static int mrst_procfs(struct device *dev, struct seq_file *seq) -{ - unsigned char rtc_control; - - spin_lock_irq(&rtc_lock); - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - seq_printf(seq, - "periodic_IRQ\t: %s\n" - "alarm\t\t: %s\n" - "BCD\t\t: no\n" - "periodic_freq\t: daily (not adjustable)\n", - (rtc_control & RTC_PIE) ? "on" : "off", - (rtc_control & RTC_AIE) ? "on" : "off"); - - return 0; -} - -#else -#define mrst_procfs NULL -#endif - -static const struct rtc_class_ops mrst_rtc_ops = { - .read_time = mrst_read_time, - .set_time = mrst_set_time, - .read_alarm = mrst_read_alarm, - .set_alarm = mrst_set_alarm, - .proc = mrst_procfs, - .alarm_irq_enable = mrst_rtc_alarm_irq_enable, -}; - -static struct mrst_rtc mrst_rtc; - -/* - * When vRTC IRQ is captured by SCU FW, FW will clear the AIE bit in - * Reg B, so no need for this driver to clear it - */ -static irqreturn_t mrst_rtc_irq(int irq, void *p) -{ - u8 irqstat; - - spin_lock(&rtc_lock); - /* This read will clear all IRQ flags inside Reg C */ - irqstat = vrtc_cmos_read(RTC_INTR_FLAGS); - spin_unlock(&rtc_lock); - - irqstat &= RTC_IRQMASK | RTC_IRQF; - if (is_intr(irqstat)) { - rtc_update_irq(p, 1, irqstat); - return IRQ_HANDLED; - } - return IRQ_NONE; -} - -static int vrtc_mrst_do_probe(struct device *dev, struct resource *iomem, - int rtc_irq) -{ - int retval = 0; - unsigned char rtc_control; - - /* There can be only one ... */ - if (mrst_rtc.dev) - return -EBUSY; - - if (!iomem) - return -ENODEV; - - iomem = devm_request_mem_region(dev, iomem->start, resource_size(iomem), - driver_name); - if (!iomem) { - dev_dbg(dev, "i/o mem already in use.\n"); - return -EBUSY; - } - - mrst_rtc.irq = rtc_irq; - mrst_rtc.dev = dev; - dev_set_drvdata(dev, &mrst_rtc); - - mrst_rtc.rtc = devm_rtc_allocate_device(dev); - if (IS_ERR(mrst_rtc.rtc)) - return PTR_ERR(mrst_rtc.rtc); - - mrst_rtc.rtc->ops = &mrst_rtc_ops; - - rename_region(iomem, dev_name(&mrst_rtc.rtc->dev)); - - spin_lock_irq(&rtc_lock); - mrst_irq_disable(&mrst_rtc, RTC_PIE | RTC_AIE); - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - if (!(rtc_control & RTC_24H) || (rtc_control & (RTC_DM_BINARY))) - dev_dbg(dev, "TODO: support more than 24-hr BCD mode\n"); - - if (rtc_irq) { - retval = devm_request_irq(dev, rtc_irq, mrst_rtc_irq, - 0, dev_name(&mrst_rtc.rtc->dev), - mrst_rtc.rtc); - if (retval < 0) { - dev_dbg(dev, "IRQ %d is already in use, err %d\n", - rtc_irq, retval); - goto cleanup0; - } - } - - retval = devm_rtc_register_device(mrst_rtc.rtc); - if (retval) - goto cleanup0; - - dev_dbg(dev, "initialised\n"); - return 0; - -cleanup0: - mrst_rtc.dev = NULL; - dev_err(dev, "rtc-mrst: unable to initialise\n"); - return retval; -} - -static void rtc_mrst_do_shutdown(void) -{ - spin_lock_irq(&rtc_lock); - mrst_irq_disable(&mrst_rtc, RTC_IRQMASK); - spin_unlock_irq(&rtc_lock); -} - -static void rtc_mrst_do_remove(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - - rtc_mrst_do_shutdown(); - - mrst->rtc = NULL; - mrst->dev = NULL; -} - -#ifdef CONFIG_PM_SLEEP -static int mrst_suspend(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char tmp; - - /* Only the alarm might be a wakeup event source */ - spin_lock_irq(&rtc_lock); - mrst->suspend_ctrl = tmp = vrtc_cmos_read(RTC_CONTROL); - if (tmp & (RTC_PIE | RTC_AIE)) { - unsigned char mask; - - if (device_may_wakeup(dev)) - mask = RTC_IRQMASK & ~RTC_AIE; - else - mask = RTC_IRQMASK; - tmp &= ~mask; - vrtc_cmos_write(tmp, RTC_CONTROL); - - mrst_checkintr(mrst, tmp); - } - spin_unlock_irq(&rtc_lock); - - if (tmp & RTC_AIE) { - mrst->enabled_wake = 1; - enable_irq_wake(mrst->irq); - } - - dev_dbg(&mrst_rtc.rtc->dev, "suspend%s, ctrl %02x\n", - (tmp & RTC_AIE) ? ", alarm may wake" : "", - tmp); - - return 0; -} - -/* - * We want RTC alarms to wake us from the deep power saving state - */ -static inline int mrst_poweroff(struct device *dev) -{ - return mrst_suspend(dev); -} - -static int mrst_resume(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char tmp = mrst->suspend_ctrl; - - /* Re-enable any irqs previously active */ - if (tmp & RTC_IRQMASK) { - unsigned char mask; - - if (mrst->enabled_wake) { - disable_irq_wake(mrst->irq); - mrst->enabled_wake = 0; - } - - spin_lock_irq(&rtc_lock); - do { - vrtc_cmos_write(tmp, RTC_CONTROL); - - mask = vrtc_cmos_read(RTC_INTR_FLAGS); - mask &= (tmp & RTC_IRQMASK) | RTC_IRQF; - if (!is_intr(mask)) - break; - - rtc_update_irq(mrst->rtc, 1, mask); - tmp &= ~RTC_AIE; - } while (mask & RTC_AIE); - spin_unlock_irq(&rtc_lock); - } - - dev_dbg(&mrst_rtc.rtc->dev, "resume, ctrl %02x\n", tmp); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(mrst_pm_ops, mrst_suspend, mrst_resume); -#define MRST_PM_OPS (&mrst_pm_ops) - -#else -#define MRST_PM_OPS NULL - -static inline int mrst_poweroff(struct device *dev) -{ - return -ENOSYS; -} - -#endif - -static int vrtc_mrst_platform_probe(struct platform_device *pdev) -{ - return vrtc_mrst_do_probe(&pdev->dev, - platform_get_resource(pdev, IORESOURCE_MEM, 0), - platform_get_irq(pdev, 0)); -} - -static int vrtc_mrst_platform_remove(struct platform_device *pdev) -{ - rtc_mrst_do_remove(&pdev->dev); - return 0; -} - -static void vrtc_mrst_platform_shutdown(struct platform_device *pdev) -{ - if (system_state == SYSTEM_POWER_OFF && !mrst_poweroff(&pdev->dev)) - return; - - rtc_mrst_do_shutdown(); -} - -MODULE_ALIAS("platform:vrtc_mrst"); - -static struct platform_driver vrtc_mrst_platform_driver = { - .probe = vrtc_mrst_platform_probe, - .remove = vrtc_mrst_platform_remove, - .shutdown = vrtc_mrst_platform_shutdown, - .driver = { - .name = driver_name, - .pm = MRST_PM_OPS, - } -}; - -module_platform_driver(vrtc_mrst_platform_driver); - -MODULE_AUTHOR("Jacob Pan; Feng Tang"); -MODULE_DESCRIPTION("Driver for Moorestown virtual RTC"); -MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/ufs/ufshcd-crypto.c b/drivers/scsi/ufs/ufshcd-crypto.c index db1b1d9ab048..734dd09ffb88 100644 --- a/drivers/scsi/ufs/ufshcd-crypto.c +++ b/drivers/scsi/ufs/ufshcd-crypto.c @@ -187,8 +187,8 @@ int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba) } /* The actual number of configurations supported is (CFGC+1) */ - err = blk_ksm_init(&hba->ksm, - hba->crypto_capabilities.config_count + 1); + err = devm_blk_ksm_init(hba->dev, &hba->ksm, + hba->crypto_capabilities.config_count + 1); if (err) goto out_free_caps; @@ -247,8 +247,3 @@ void ufshcd_crypto_setup_rq_keyslot_manager(struct ufs_hba *hba, if (hba->caps & UFSHCD_CAP_CRYPTO) blk_ksm_register(&hba->ksm, q); } - -void ufshcd_crypto_destroy_keyslot_manager(struct ufs_hba *hba) -{ - blk_ksm_destroy(&hba->ksm); -} diff --git a/drivers/scsi/ufs/ufshcd-crypto.h b/drivers/scsi/ufs/ufshcd-crypto.h index b75d8ca12a6f..83b0e5078447 100644 --- a/drivers/scsi/ufs/ufshcd-crypto.h +++ b/drivers/scsi/ufs/ufshcd-crypto.h @@ -56,8 +56,6 @@ void ufshcd_init_crypto(struct ufs_hba *hba); void ufshcd_crypto_setup_rq_keyslot_manager(struct ufs_hba *hba, struct request_queue *q); -void ufshcd_crypto_destroy_keyslot_manager(struct ufs_hba *hba); - #else /* CONFIG_SCSI_UFS_CRYPTO */ static inline void ufshcd_prepare_lrbp_crypto(struct request *rq, @@ -85,9 +83,6 @@ static inline void ufshcd_init_crypto(struct ufs_hba *hba) { } static inline void ufshcd_crypto_setup_rq_keyslot_manager(struct ufs_hba *hba, struct request_queue *q) { } -static inline void ufshcd_crypto_destroy_keyslot_manager(struct ufs_hba *hba) -{ } - #endif /* CONFIG_SCSI_UFS_CRYPTO */ #endif /* _UFSHCD_CRYPTO_H */ diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 92f081a26aff..109231398fce 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -9173,7 +9173,6 @@ EXPORT_SYMBOL_GPL(ufshcd_remove); */ void ufshcd_dealloc_host(struct ufs_hba *hba) { - ufshcd_crypto_destroy_keyslot_manager(hba); scsi_host_put(hba->host); } EXPORT_SYMBOL_GPL(ufshcd_dealloc_host); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 74ea73a05981..09a263cf4ae2 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -203,7 +203,7 @@ config SPI_CADENCE config SPI_CADENCE_QUADSPI tristate "Cadence Quad SPI controller" - depends on OF && (ARM || ARM64 || COMPILE_TEST) + depends on OF && (ARM || ARM64 || X86 || COMPILE_TEST) help Enable support for the Cadence Quad SPI Flash controller. @@ -292,13 +292,6 @@ config SPI_DLN2 This driver can also be built as a module. If so, the module will be called spi-dln2. -config SPI_EFM32 - tristate "EFM32 SPI controller" - depends on OF && ARM && (ARCH_EFM32 || COMPILE_TEST) - select SPI_BITBANG - help - Driver for the spi controller found on Energy Micro's EFM32 SoCs. - config SPI_EP93XX tristate "Cirrus Logic EP93xx SPI controller" depends on ARCH_EP93XX || COMPILE_TEST @@ -649,7 +642,7 @@ config SPI_RPCIF tristate "Renesas RPC-IF SPI driver" depends on RENESAS_RPCIF help - SPI driver for Renesas R-Car Gen3 RPC-IF. + SPI driver for Renesas R-Car Gen3 or RZ/G2 RPC-IF. config SPI_RSPI tristate "Renesas RSPI/QSPI controller" @@ -750,13 +743,6 @@ config SPI_SIFIVE help This exposes the SPI controller IP from SiFive. -config SPI_SIRF - tristate "CSR SiRFprimaII SPI controller" - depends on SIRF_DMA - select SPI_BITBANG - help - SPI driver for CSR SiRFprimaII SoCs - config SPI_SLAVE_MT27XX tristate "MediaTek SPI slave device" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -842,6 +828,15 @@ config SPI_MXS help SPI driver for Freescale MXS devices. +config SPI_TEGRA210_QUAD + tristate "NVIDIA Tegra QSPI Controller" + depends on ARCH_TEGRA || COMPILE_TEST + depends on RESET_CONTROLLER + help + QSPI driver for NVIDIA Tegra QSPI Controller interface. This + controller is different from the SPI controller and is available + on Tegra SoCs starting from Tegra210. + config SPI_TEGRA114 tristate "NVIDIA Tegra114 SPI Controller" depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST @@ -884,12 +879,6 @@ config SPI_TOPCLIFF_PCH This driver also supports the ML7213/ML7223/ML7831, a companion chip for the Atom E6xx series and compatible with the Intel EG20T PCH. -config SPI_TXX9 - tristate "Toshiba TXx9 SPI controller" - depends on GPIOLIB && (CPU_TX49XX || COMPILE_TEST) - help - SPI driver for Toshiba TXx9 MIPS SoCs - config SPI_UNIPHIER tristate "Socionext UniPhier SPI Controller" depends on (ARCH_UNIPHIER || COMPILE_TEST) && OF diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 6fea5821662e..0f06fc0813c6 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -42,7 +42,6 @@ spi-dw-$(CONFIG_SPI_DW_DMA) += spi-dw-dma.o obj-$(CONFIG_SPI_DW_BT1) += spi-dw-bt1.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o -obj-$(CONFIG_SPI_EFM32) += spi-efm32.o obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o obj-$(CONFIG_SPI_FALCON) += spi-falcon.o obj-$(CONFIG_SPI_FSI) += spi-fsi.o @@ -94,6 +93,7 @@ obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom-qspi.o obj-$(CONFIG_SPI_QUP) += spi-qup.o obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o +obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o @@ -105,7 +105,6 @@ obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o -obj-$(CONFIG_SPI_SIRF) += spi-sirf.o obj-$(CONFIG_SPI_SLAVE_MT27XX) += spi-slave-mt27xx.o obj-$(CONFIG_SPI_SPRD) += spi-sprd.o obj-$(CONFIG_SPI_SPRD_ADI) += spi-sprd-adi.o @@ -115,6 +114,7 @@ obj-$(CONFIG_SPI_ST_SSC4) += spi-st-ssc4.o obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o obj-$(CONFIG_SPI_SUN6I) += spi-sun6i.o obj-$(CONFIG_SPI_SYNQUACER) += spi-synquacer.o +obj-$(CONFIG_SPI_TEGRA210_QUAD) += spi-tegra210-quad.o obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o obj-$(CONFIG_SPI_TEGRA20_SFLASH) += spi-tegra20-sflash.o obj-$(CONFIG_SPI_TEGRA20_SLINK) += spi-tegra20-slink.o @@ -122,7 +122,6 @@ obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o spi-thunderx-objs := spi-cavium.o spi-cavium-thunderx.o obj-$(CONFIG_SPI_THUNDERX) += spi-thunderx.o obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o -obj-$(CONFIG_SPI_TXX9) += spi-txx9.o obj-$(CONFIG_SPI_UNIPHIER) += spi-uniphier.o obj-$(CONFIG_SPI_XCOMM) += spi-xcomm.o obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c index 10bc5390ab91..95d4fa32c299 100644 --- a/drivers/spi/atmel-quadspi.c +++ b/drivers/spi/atmel-quadspi.c @@ -657,6 +657,7 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev) struct spi_controller *ctrl = dev_get_drvdata(dev); struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); + atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR); clk_disable_unprepare(aq->qspick); clk_disable_unprepare(aq->pclk); diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index 948396b382d7..f429436082af 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -1590,7 +1590,7 @@ static int atmel_spi_probe(struct platform_device *pdev) if (ret == 0) { as->use_dma = true; } else if (ret == -EPROBE_DEFER) { - return ret; + goto out_unmap_regs; } } else if (as->caps.has_pdc_support) { as->use_pdc = true; diff --git a/drivers/spi/spi-au1550.c b/drivers/spi/spi-au1550.c index dfb7196f4caf..4b59a1b1bf7e 100644 --- a/drivers/spi/spi-au1550.c +++ b/drivers/spi/spi-au1550.c @@ -26,7 +26,7 @@ #include -static unsigned usedma = 1; +static unsigned int usedma = 1; module_param(usedma, uint, 0644); /* @@ -43,9 +43,9 @@ struct au1550_spi { volatile psc_spi_t __iomem *regs; int irq; - unsigned len; - unsigned tx_count; - unsigned rx_count; + unsigned int len; + unsigned int tx_count; + unsigned int rx_count; const u8 *tx; u8 *rx; @@ -56,14 +56,14 @@ struct au1550_spi { struct completion master_done; - unsigned usedma; + unsigned int usedma; u32 dma_tx_id; u32 dma_rx_id; u32 dma_tx_ch; u32 dma_rx_ch; u8 *dma_rx_tmpbuf; - unsigned dma_rx_tmpbuf_size; + unsigned int dma_rx_tmpbuf_size; u32 dma_rx_tmpbuf_addr; struct spi_master *master; @@ -74,8 +74,7 @@ struct au1550_spi { /* we use an 8-bit memory device for dma transfers to/from spi fifo */ -static dbdev_tab_t au1550_spi_mem_dbdev = -{ +static dbdev_tab_t au1550_spi_mem_dbdev = { .dev_id = DBDMA_MEM_CHAN, .dev_flags = DEV_FLAGS_ANYUSE|DEV_FLAGS_SYNC, .dev_tsize = 0, @@ -99,7 +98,7 @@ static void au1550_spi_bits_handlers_set(struct au1550_spi *hw, int bpw); * BRG valid range is 4..63 * DIV valid range is 0..3 */ -static u32 au1550_spi_baudcfg(struct au1550_spi *hw, unsigned speed_hz) +static u32 au1550_spi_baudcfg(struct au1550_spi *hw, unsigned int speed_hz) { u32 mainclk_hz = hw->pdata->mainclk_hz; u32 div, brg; @@ -161,7 +160,7 @@ static void au1550_spi_reset_fifos(struct au1550_spi *hw) static void au1550_spi_chipsel(struct spi_device *spi, int value) { struct au1550_spi *hw = spi_master_get_devdata(spi->master); - unsigned cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; + unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; u32 cfg, stat; switch (value) { @@ -221,7 +220,7 @@ static void au1550_spi_chipsel(struct spi_device *spi, int value) static int au1550_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t) { struct au1550_spi *hw = spi_master_get_devdata(spi->master); - unsigned bpw, hz; + unsigned int bpw, hz; u32 cfg, stat; if (t) { @@ -276,7 +275,7 @@ static int au1550_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t) * spi master done event irq is not generated unless rx fifo is empty (emptied) * so we need rx tmp buffer to use for rx dma if user does not provide one */ -static int au1550_spi_dma_rxtmp_alloc(struct au1550_spi *hw, unsigned size) +static int au1550_spi_dma_rxtmp_alloc(struct au1550_spi *hw, unsigned int size) { hw->dma_rx_tmpbuf = kmalloc(size, GFP_KERNEL); if (!hw->dma_rx_tmpbuf) @@ -399,10 +398,10 @@ static int au1550_spi_dma_txrxb(struct spi_device *spi, struct spi_transfer *t) DMA_FROM_DEVICE); } /* unmap buffers if mapped above */ - if (t->rx_buf && t->rx_dma == 0 ) + if (t->rx_buf && t->rx_dma == 0) dma_unmap_single(hw->dev, dma_rx_addr, t->len, DMA_FROM_DEVICE); - if (t->tx_buf && t->tx_dma == 0 ) + if (t->tx_buf && t->tx_dma == 0) dma_unmap_single(hw->dev, dma_tx_addr, t->len, DMA_TO_DEVICE); @@ -447,8 +446,8 @@ static irqreturn_t au1550_spi_dma_irq_callback(struct au1550_spi *hw) "dma transfer: receive FIFO overflow!\n"); else dev_err(hw->dev, - "dma transfer: unexpected SPI error " - "(event=0x%x stat=0x%x)!\n", evnt, stat); + "dma transfer: unexpected SPI error (event=0x%x stat=0x%x)!\n", + evnt, stat); complete(&hw->master_done); return IRQ_HANDLED; @@ -493,12 +492,12 @@ static void au1550_spi_tx_word_##size(struct au1550_spi *hw) \ wmb(); /* drain writebuffer */ \ } -AU1550_SPI_RX_WORD(8,0xff) -AU1550_SPI_RX_WORD(16,0xffff) -AU1550_SPI_RX_WORD(32,0xffffff) -AU1550_SPI_TX_WORD(8,0xff) -AU1550_SPI_TX_WORD(16,0xffff) -AU1550_SPI_TX_WORD(32,0xffffff) +AU1550_SPI_RX_WORD(8, 0xff) +AU1550_SPI_RX_WORD(16, 0xffff) +AU1550_SPI_RX_WORD(32, 0xffffff) +AU1550_SPI_TX_WORD(8, 0xff) +AU1550_SPI_TX_WORD(16, 0xffff) +AU1550_SPI_TX_WORD(32, 0xffffff) static int au1550_spi_pio_txrxb(struct spi_device *spi, struct spi_transfer *t) { @@ -567,8 +566,8 @@ static irqreturn_t au1550_spi_pio_irq_callback(struct au1550_spi *hw) au1550_spi_mask_ack_all(hw); au1550_spi_reset_fifos(hw); dev_err(hw->dev, - "pio transfer: unexpected SPI error " - "(event=0x%x stat=0x%x)!\n", evnt, stat); + "pio transfer: unexpected SPI error (event=0x%x stat=0x%x)!\n", + evnt, stat); complete(&hw->master_done); return IRQ_HANDLED; } @@ -636,12 +635,14 @@ static irqreturn_t au1550_spi_pio_irq_callback(struct au1550_spi *hw) static int au1550_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) { struct au1550_spi *hw = spi_master_get_devdata(spi->master); + return hw->txrx_bufs(spi, t); } static irqreturn_t au1550_spi_irq(int irq, void *dev) { struct au1550_spi *hw = dev; + return hw->irq_callback(hw); } @@ -872,6 +873,7 @@ static int au1550_spi_probe(struct platform_device *pdev) { int min_div = (2 << 0) * (2 * (4 + 1)); int max_div = (2 << 3) * (2 * (63 + 1)); + master->max_speed_hz = hw->pdata->mainclk_hz / min_div; master->min_speed_hz = hw->pdata->mainclk_hz / (max_div + 1) + 1; @@ -972,8 +974,7 @@ static int __init au1550_spi_init(void) if (usedma) { ddma_memid = au1xxx_ddma_add_device(&au1550_spi_mem_dbdev); if (!ddma_memid) - printk(KERN_ERR "au1550-spi: cannot add memory" - "dbdma device\n"); + printk(KERN_ERR "au1550-spi: cannot add memory dbdma device\n"); } return platform_driver_register(&au1550_spi_drv); } diff --git a/drivers/spi/spi-bcm-qspi.c b/drivers/spi/spi-bcm-qspi.c index c028446c7460..707fe3a5d8ef 100644 --- a/drivers/spi/spi-bcm-qspi.c +++ b/drivers/spi/spi-bcm-qspi.c @@ -881,7 +881,7 @@ static int bcm_qspi_bspi_exec_mem_op(struct spi_device *spi, * when using flex mode we need to send * the upper address byte to bspi */ - if (bcm_qspi_bspi_ver_three(qspi) == false) { + if (!bcm_qspi_bspi_ver_three(qspi)) { addr = from & 0xff000000; bcm_qspi_write(qspi, BSPI, BSPI_BSPI_FLASH_UPPER_ADDR_BYTE, addr); diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 197485f2c2b2..8965fe61c8b4 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -386,7 +386,7 @@ static irqreturn_t bcm2835_spi_interrupt(int irq, void *dev_id) /* Transfer complete - reset SPI HW */ bcm2835_spi_reset_hw(bs); /* wake up the framework */ - complete(&bs->ctlr->xfer_completion); + spi_finalize_current_transfer(bs->ctlr); } return IRQ_HANDLED; @@ -608,7 +608,7 @@ static void bcm2835_spi_dma_rx_done(void *data) bcm2835_spi_reset_hw(bs); /* and mark as completed */; - complete(&ctlr->xfer_completion); + spi_finalize_current_transfer(ctlr); } /** @@ -640,7 +640,7 @@ static void bcm2835_spi_dma_tx_done(void *data) bcm2835_spi_undo_prologue(bs); bcm2835_spi_reset_hw(bs); - complete(&ctlr->xfer_completion); + spi_finalize_current_transfer(ctlr); } /** @@ -1307,6 +1307,8 @@ static int bcm2835_spi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(bs->clk), "could not get clk\n"); + ctlr->max_speed_hz = clk_get_rate(bs->clk) / 2; + bs->irq = platform_get_irq(pdev, 0); if (bs->irq <= 0) return bs->irq ? bs->irq : -ENODEV; diff --git a/drivers/spi/spi-bcm2835aux.c b/drivers/spi/spi-bcm2835aux.c index 1a26865c42f8..75589ac6e95f 100644 --- a/drivers/spi/spi-bcm2835aux.c +++ b/drivers/spi/spi-bcm2835aux.c @@ -254,7 +254,7 @@ static irqreturn_t bcm2835aux_spi_interrupt(int irq, void *dev_id) /* and if rx_len is 0 then disable interrupts and wake up completion */ if (!bs->rx_len) { bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]); - complete(&master->xfer_completion); + spi_finalize_current_transfer(master); } return IRQ_HANDLED; diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ba7d40c2922f..442cc7c53a47 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -52,6 +52,7 @@ struct cqspi_flash_pdata { u8 inst_width; u8 addr_width; u8 data_width; + bool dtr; u8 cs; }; @@ -75,6 +76,7 @@ struct cqspi_st { bool is_decoded_cs; u32 fifo_depth; u32 fifo_width; + u32 num_chipselect; bool rclk_en; u32 trigger_address; u32 wr_delay; @@ -111,6 +113,8 @@ struct cqspi_driver_platdata { #define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10 #define CQSPI_REG_CONFIG_DMA_MASK BIT(15) #define CQSPI_REG_CONFIG_BAUD_LSB 19 +#define CQSPI_REG_CONFIG_DTR_PROTO BIT(24) +#define CQSPI_REG_CONFIG_DUAL_OPCODE BIT(30) #define CQSPI_REG_CONFIG_IDLE_LSB 31 #define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF #define CQSPI_REG_CONFIG_BAUD_MASK 0xF @@ -173,6 +177,9 @@ struct cqspi_driver_platdata { #define CQSPI_REG_SDRAMLEVEL_RD_MASK 0xFFFF #define CQSPI_REG_SDRAMLEVEL_WR_MASK 0xFFFF +#define CQSPI_REG_WR_COMPLETION_CTRL 0x38 +#define CQSPI_REG_WR_DISABLE_AUTO_POLL BIT(14) + #define CQSPI_REG_IRQSTATUS 0x40 #define CQSPI_REG_IRQMASK 0x44 @@ -188,6 +195,7 @@ struct cqspi_driver_platdata { #define CQSPI_REG_CMDCTRL 0x90 #define CQSPI_REG_CMDCTRL_EXECUTE_MASK BIT(0) #define CQSPI_REG_CMDCTRL_INPROGRESS_MASK BIT(1) +#define CQSPI_REG_CMDCTRL_DUMMY_LSB 7 #define CQSPI_REG_CMDCTRL_WR_BYTES_LSB 12 #define CQSPI_REG_CMDCTRL_WR_EN_LSB 15 #define CQSPI_REG_CMDCTRL_ADD_BYTES_LSB 16 @@ -198,6 +206,7 @@ struct cqspi_driver_platdata { #define CQSPI_REG_CMDCTRL_WR_BYTES_MASK 0x7 #define CQSPI_REG_CMDCTRL_ADD_BYTES_MASK 0x3 #define CQSPI_REG_CMDCTRL_RD_BYTES_MASK 0x7 +#define CQSPI_REG_CMDCTRL_DUMMY_MASK 0x1F #define CQSPI_REG_INDIRECTWR 0x70 #define CQSPI_REG_INDIRECTWR_START_MASK BIT(0) @@ -214,6 +223,14 @@ struct cqspi_driver_platdata { #define CQSPI_REG_CMDWRITEDATALOWER 0xA8 #define CQSPI_REG_CMDWRITEDATAUPPER 0xAC +#define CQSPI_REG_POLLING_STATUS 0xB0 +#define CQSPI_REG_POLLING_STATUS_DUMMY_LSB 16 + +#define CQSPI_REG_OP_EXT_LOWER 0xE0 +#define CQSPI_REG_OP_EXT_READ_LSB 24 +#define CQSPI_REG_OP_EXT_WRITE_LSB 16 +#define CQSPI_REG_OP_EXT_STIG_LSB 0 + /* Interrupt status bits */ #define CQSPI_REG_IRQ_MODE_ERR BIT(0) #define CQSPI_REG_IRQ_UNDERFLOW BIT(1) @@ -288,6 +305,80 @@ static unsigned int cqspi_calc_rdreg(struct cqspi_flash_pdata *f_pdata) return rdreg; } +static unsigned int cqspi_calc_dummy(const struct spi_mem_op *op, bool dtr) +{ + unsigned int dummy_clk; + + dummy_clk = op->dummy.nbytes * (8 / op->dummy.buswidth); + if (dtr) + dummy_clk /= 2; + + return dummy_clk; +} + +static int cqspi_set_protocol(struct cqspi_flash_pdata *f_pdata, + const struct spi_mem_op *op) +{ + f_pdata->inst_width = CQSPI_INST_TYPE_SINGLE; + f_pdata->addr_width = CQSPI_INST_TYPE_SINGLE; + f_pdata->data_width = CQSPI_INST_TYPE_SINGLE; + f_pdata->dtr = op->data.dtr && op->cmd.dtr && op->addr.dtr; + + switch (op->data.buswidth) { + case 0: + break; + case 1: + f_pdata->data_width = CQSPI_INST_TYPE_SINGLE; + break; + case 2: + f_pdata->data_width = CQSPI_INST_TYPE_DUAL; + break; + case 4: + f_pdata->data_width = CQSPI_INST_TYPE_QUAD; + break; + case 8: + f_pdata->data_width = CQSPI_INST_TYPE_OCTAL; + break; + default: + return -EINVAL; + } + + /* Right now we only support 8-8-8 DTR mode. */ + if (f_pdata->dtr) { + switch (op->cmd.buswidth) { + case 0: + break; + case 8: + f_pdata->inst_width = CQSPI_INST_TYPE_OCTAL; + break; + default: + return -EINVAL; + } + + switch (op->addr.buswidth) { + case 0: + break; + case 8: + f_pdata->addr_width = CQSPI_INST_TYPE_OCTAL; + break; + default: + return -EINVAL; + } + + switch (op->data.buswidth) { + case 0: + break; + case 8: + f_pdata->data_width = CQSPI_INST_TYPE_OCTAL; + break; + default: + return -EINVAL; + } + } + + return 0; +} + static int cqspi_wait_idle(struct cqspi_st *cqspi) { const unsigned int poll_idle_retry = 3; @@ -345,19 +436,85 @@ static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg) return cqspi_wait_idle(cqspi); } +static int cqspi_setup_opcode_ext(struct cqspi_flash_pdata *f_pdata, + const struct spi_mem_op *op, + unsigned int shift) +{ + struct cqspi_st *cqspi = f_pdata->cqspi; + void __iomem *reg_base = cqspi->iobase; + unsigned int reg; + u8 ext; + + if (op->cmd.nbytes != 2) + return -EINVAL; + + /* Opcode extension is the LSB. */ + ext = op->cmd.opcode & 0xff; + + reg = readl(reg_base + CQSPI_REG_OP_EXT_LOWER); + reg &= ~(0xff << shift); + reg |= ext << shift; + writel(reg, reg_base + CQSPI_REG_OP_EXT_LOWER); + + return 0; +} + +static int cqspi_enable_dtr(struct cqspi_flash_pdata *f_pdata, + const struct spi_mem_op *op, unsigned int shift, + bool enable) +{ + struct cqspi_st *cqspi = f_pdata->cqspi; + void __iomem *reg_base = cqspi->iobase; + unsigned int reg; + int ret; + + reg = readl(reg_base + CQSPI_REG_CONFIG); + + /* + * We enable dual byte opcode here. The callers have to set up the + * extension opcode based on which type of operation it is. + */ + if (enable) { + reg |= CQSPI_REG_CONFIG_DTR_PROTO; + reg |= CQSPI_REG_CONFIG_DUAL_OPCODE; + + /* Set up command opcode extension. */ + ret = cqspi_setup_opcode_ext(f_pdata, op, shift); + if (ret) + return ret; + } else { + reg &= ~CQSPI_REG_CONFIG_DTR_PROTO; + reg &= ~CQSPI_REG_CONFIG_DUAL_OPCODE; + } + + writel(reg, reg_base + CQSPI_REG_CONFIG); + + return cqspi_wait_idle(cqspi); +} + static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata, const struct spi_mem_op *op) { struct cqspi_st *cqspi = f_pdata->cqspi; void __iomem *reg_base = cqspi->iobase; u8 *rxbuf = op->data.buf.in; - u8 opcode = op->cmd.opcode; + u8 opcode; size_t n_rx = op->data.nbytes; unsigned int rdreg; unsigned int reg; + unsigned int dummy_clk; size_t read_len; int status; + status = cqspi_set_protocol(f_pdata, op); + if (status) + return status; + + status = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB, + f_pdata->dtr); + if (status) + return status; + if (!n_rx || n_rx > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) { dev_err(&cqspi->pdev->dev, "Invalid input argument, len %zu rxbuf 0x%p\n", @@ -365,11 +522,24 @@ static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata, return -EINVAL; } + if (f_pdata->dtr) + opcode = op->cmd.opcode >> 8; + else + opcode = op->cmd.opcode; + reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB; rdreg = cqspi_calc_rdreg(f_pdata); writel(rdreg, reg_base + CQSPI_REG_RD_INSTR); + dummy_clk = cqspi_calc_dummy(op, f_pdata->dtr); + if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) + return -EOPNOTSUPP; + + if (dummy_clk) + reg |= (dummy_clk & CQSPI_REG_CMDCTRL_DUMMY_MASK) + << CQSPI_REG_CMDCTRL_DUMMY_LSB; + reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB); /* 0 means 1 byte. */ @@ -401,12 +571,22 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata, { struct cqspi_st *cqspi = f_pdata->cqspi; void __iomem *reg_base = cqspi->iobase; - const u8 opcode = op->cmd.opcode; + u8 opcode; const u8 *txbuf = op->data.buf.out; size_t n_tx = op->data.nbytes; unsigned int reg; unsigned int data; size_t write_len; + int ret; + + ret = cqspi_set_protocol(f_pdata, op); + if (ret) + return ret; + + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB, + f_pdata->dtr); + if (ret) + return ret; if (n_tx > CQSPI_STIG_DATA_LEN_MAX || (n_tx && !txbuf)) { dev_err(&cqspi->pdev->dev, @@ -415,6 +595,14 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata, return -EINVAL; } + reg = cqspi_calc_rdreg(f_pdata); + writel(reg, reg_base + CQSPI_REG_RD_INSTR); + + if (f_pdata->dtr) + opcode = op->cmd.opcode >> 8; + else + opcode = op->cmd.opcode; + reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB; if (op->addr.nbytes) { @@ -454,14 +642,27 @@ static int cqspi_read_setup(struct cqspi_flash_pdata *f_pdata, void __iomem *reg_base = cqspi->iobase; unsigned int dummy_clk = 0; unsigned int reg; + int ret; + u8 opcode; - reg = op->cmd.opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB; + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_READ_LSB, + f_pdata->dtr); + if (ret) + return ret; + + if (f_pdata->dtr) + opcode = op->cmd.opcode >> 8; + else + opcode = op->cmd.opcode; + + reg = opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB; reg |= cqspi_calc_rdreg(f_pdata); /* Setup dummy clock cycles */ - dummy_clk = op->dummy.nbytes * 8; + dummy_clk = cqspi_calc_dummy(op, f_pdata->dtr); + if (dummy_clk > CQSPI_DUMMY_CLKS_MAX) - dummy_clk = CQSPI_DUMMY_CLKS_MAX; + return -EOPNOTSUPP; if (dummy_clk) reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK) @@ -573,15 +774,43 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata, const struct spi_mem_op *op) { unsigned int reg; + int ret; struct cqspi_st *cqspi = f_pdata->cqspi; void __iomem *reg_base = cqspi->iobase; + u8 opcode; + + ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_WRITE_LSB, + f_pdata->dtr); + if (ret) + return ret; + + if (f_pdata->dtr) + opcode = op->cmd.opcode >> 8; + else + opcode = op->cmd.opcode; /* Set opcode. */ - reg = op->cmd.opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB; + reg = opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB; + reg |= f_pdata->data_width << CQSPI_REG_WR_INSTR_TYPE_DATA_LSB; + reg |= f_pdata->addr_width << CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB; writel(reg, reg_base + CQSPI_REG_WR_INSTR); reg = cqspi_calc_rdreg(f_pdata); writel(reg, reg_base + CQSPI_REG_RD_INSTR); + if (f_pdata->dtr) { + /* + * Some flashes like the cypress Semper flash expect a 4-byte + * dummy address with the Read SR command in DTR mode, but this + * controller does not support sending address with the Read SR + * command. So, disable write completion polling on the + * controller's side. spi-nor will take care of polling the + * status register. + */ + reg = readl(reg_base + CQSPI_REG_WR_COMPLETION_CTRL); + reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL; + writel(reg, reg_base + CQSPI_REG_WR_COMPLETION_CTRL); + } + reg = readl(reg_base + CQSPI_REG_SIZE); reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK; reg |= (op->addr.nbytes - 1); @@ -835,35 +1064,6 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata, cqspi_controller_enable(cqspi, 1); } -static int cqspi_set_protocol(struct cqspi_flash_pdata *f_pdata, - const struct spi_mem_op *op) -{ - f_pdata->inst_width = CQSPI_INST_TYPE_SINGLE; - f_pdata->addr_width = CQSPI_INST_TYPE_SINGLE; - f_pdata->data_width = CQSPI_INST_TYPE_SINGLE; - - if (op->data.dir == SPI_MEM_DATA_IN) { - switch (op->data.buswidth) { - case 1: - f_pdata->data_width = CQSPI_INST_TYPE_SINGLE; - break; - case 2: - f_pdata->data_width = CQSPI_INST_TYPE_DUAL; - break; - case 4: - f_pdata->data_width = CQSPI_INST_TYPE_QUAD; - break; - case 8: - f_pdata->data_width = CQSPI_INST_TYPE_OCTAL; - break; - default: - return -EINVAL; - } - } - - return 0; -} - static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata, const struct spi_mem_op *op) { @@ -881,7 +1081,16 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata, if (ret) return ret; - if (cqspi->use_direct_mode && ((to + len) <= cqspi->ahb_size)) { + /* + * Some flashes like the Cypress Semper flash expect a dummy 4-byte + * address (all 0s) with the read status register command in DTR mode. + * But this controller does not support sending dummy address bytes to + * the flash when it is polling the write completion register in DTR + * mode. So, we can not use direct mode when in DTR mode for writing + * data. + */ + if (!f_pdata->dtr && cqspi->use_direct_mode && + ((to + len) <= cqspi->ahb_size)) { memcpy_toio(cqspi->ahb_base + to, buf, len); return cqspi_wait_idle(cqspi); } @@ -942,7 +1151,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata, dma_async_issue_pending(cqspi->rx_chan); if (!wait_for_completion_timeout(&cqspi->rx_dma_complete, - msecs_to_jiffies(len))) { + msecs_to_jiffies(max_t(size_t, len, 500)))) { dmaengine_terminate_sync(cqspi->rx_chan); dev_err(dev, "DMA wait_for_completion_timeout\n"); ret = -ETIMEDOUT; @@ -1010,6 +1219,26 @@ static int cqspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) return ret; } +static bool cqspi_supports_mem_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + bool all_true, all_false; + + all_true = op->cmd.dtr && op->addr.dtr && op->dummy.dtr && + op->data.dtr; + all_false = !op->cmd.dtr && !op->addr.dtr && !op->dummy.dtr && + !op->data.dtr; + + /* Mixed DTR modes not supported. */ + if (!(all_true || all_false)) + return false; + + if (all_true) + return spi_mem_dtr_supports_op(mem, op); + else + return spi_mem_default_supports_op(mem, op); +} + static int cqspi_of_get_flash_pdata(struct platform_device *pdev, struct cqspi_flash_pdata *f_pdata, struct device_node *np) @@ -1070,6 +1299,9 @@ static int cqspi_of_get_pdata(struct cqspi_st *cqspi) return -ENXIO; } + if (of_property_read_u32(np, "num-cs", &cqspi->num_chipselect)) + cqspi->num_chipselect = CQSPI_MAX_CHIPSELECT; + cqspi->rclk_en = of_property_read_bool(np, "cdns,rclk-en"); return 0; @@ -1101,10 +1333,12 @@ static void cqspi_controller_init(struct cqspi_st *cqspi) writel(cqspi->fifo_depth * cqspi->fifo_width / 8, cqspi->iobase + CQSPI_REG_INDIRECTWRWATERMARK); - /* Enable Direct Access Controller */ - reg = readl(cqspi->iobase + CQSPI_REG_CONFIG); - reg |= CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL; - writel(reg, cqspi->iobase + CQSPI_REG_CONFIG); + /* Disable direct access controller */ + if (!cqspi->use_direct_mode) { + reg = readl(cqspi->iobase + CQSPI_REG_CONFIG); + reg &= ~CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL; + writel(reg, cqspi->iobase + CQSPI_REG_CONFIG); + } cqspi_controller_enable(cqspi, 1); } @@ -1138,6 +1372,7 @@ static const char *cqspi_get_name(struct spi_mem *mem) static const struct spi_controller_mem_ops cqspi_mem_ops = { .exec_op = cqspi_exec_mem_op, .get_name = cqspi_get_name, + .supports_op = cqspi_supports_mem_op, }; static int cqspi_setup_flash(struct cqspi_st *cqspi) @@ -1279,13 +1514,14 @@ static int cqspi_probe(struct platform_device *pdev) reset_control_deassert(rstc_ocp); cqspi->master_ref_clk_hz = clk_get_rate(cqspi->clk); + master->max_speed_hz = cqspi->master_ref_clk_hz; ddata = of_device_get_match_data(dev); if (ddata) { if (ddata->quirks & CQSPI_NEEDS_WR_DELAY) - cqspi->wr_delay = 5 * DIV_ROUND_UP(NSEC_PER_SEC, + cqspi->wr_delay = 50 * DIV_ROUND_UP(NSEC_PER_SEC, cqspi->master_ref_clk_hz); if (ddata->hwcaps_mask & CQSPI_SUPPORTS_OCTAL) - master->mode_bits |= SPI_RX_OCTAL; + master->mode_bits |= SPI_RX_OCTAL | SPI_TX_OCTAL; if (!(ddata->quirks & CQSPI_DISABLE_DAC_MODE)) cqspi->use_direct_mode = true; } @@ -1302,6 +1538,8 @@ static int cqspi_probe(struct platform_device *pdev) cqspi->current_cs = -1; cqspi->sclk = 0; + master->num_chipselect = cqspi->num_chipselect; + ret = cqspi_setup_flash(cqspi); if (ret) { dev_err(dev, "failed to setup flash parameters %d\n", ret); @@ -1390,6 +1628,10 @@ static const struct cqspi_driver_platdata am654_ospi = { .quirks = CQSPI_NEEDS_WR_DELAY, }; +static const struct cqspi_driver_platdata intel_lgm_qspi = { + .quirks = CQSPI_DISABLE_DAC_MODE, +}; + static const struct of_device_id cqspi_dt_ids[] = { { .compatible = "cdns,qspi-nor", @@ -1403,6 +1645,10 @@ static const struct of_device_id cqspi_dt_ids[] = { .compatible = "ti,am654-ospi", .data = &am654_ospi, }, + { + .compatible = "intel,lgm-qspi", + .data = &intel_lgm_qspi, + }, { /* end of table */ } }; @@ -1427,3 +1673,4 @@ MODULE_AUTHOR("Ley Foon Tan "); MODULE_AUTHOR("Graham Moore "); MODULE_AUTHOR("Vadivel Murugan R "); MODULE_AUTHOR("Vignesh Raghavendra "); +MODULE_AUTHOR("Pratyush Yadav "); diff --git a/drivers/spi/spi-clps711x.c b/drivers/spi/spi-clps711x.c index 5e900f228919..0bef5ce08094 100644 --- a/drivers/spi/spi-clps711x.c +++ b/drivers/spi/spi-clps711x.c @@ -104,7 +104,7 @@ static int spi_clps711x_probe(struct platform_device *pdev) master->use_gpio_descriptors = true; master->bus_num = -1; master->mode_bits = SPI_CPHA | SPI_CS_HIGH; - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8); + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8); master->dev.of_node = pdev->dev.of_node; master->prepare_message = spi_clps711x_prepare_message; master->transfer_one = spi_clps711x_transfer_one; diff --git a/drivers/spi/spi-dw-bt1.c b/drivers/spi/spi-dw-bt1.c index 4aa8596fb1f2..5be6b7b80c21 100644 --- a/drivers/spi/spi-dw-bt1.c +++ b/drivers/spi/spi-dw-bt1.c @@ -84,7 +84,7 @@ static void dw_spi_bt1_dirmap_copy_from_map(void *to, void __iomem *from, size_t if (shift) { chunk = min_t(size_t, 4 - shift, len); data = readl_relaxed(from - shift); - memcpy(to, &data + shift, chunk); + memcpy(to, (char *)&data + shift, chunk); from += chunk; to += chunk; len -= chunk; diff --git a/drivers/spi/spi-efm32.c b/drivers/spi/spi-efm32.c deleted file mode 100644 index ea6e4a7b3feb..000000000000 --- a/drivers/spi/spi-efm32.c +++ /dev/null @@ -1,462 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2012-2013 Uwe Kleine-Koenig for Pengutronix - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "efm32-spi" - -#define MASK_VAL(mask, val) ((val << __ffs(mask)) & mask) - -#define REG_CTRL 0x00 -#define REG_CTRL_SYNC 0x0001 -#define REG_CTRL_CLKPOL 0x0100 -#define REG_CTRL_CLKPHA 0x0200 -#define REG_CTRL_MSBF 0x0400 -#define REG_CTRL_TXBIL 0x1000 - -#define REG_FRAME 0x04 -#define REG_FRAME_DATABITS__MASK 0x000f -#define REG_FRAME_DATABITS(n) ((n) - 3) - -#define REG_CMD 0x0c -#define REG_CMD_RXEN 0x0001 -#define REG_CMD_RXDIS 0x0002 -#define REG_CMD_TXEN 0x0004 -#define REG_CMD_TXDIS 0x0008 -#define REG_CMD_MASTEREN 0x0010 - -#define REG_STATUS 0x10 -#define REG_STATUS_TXENS 0x0002 -#define REG_STATUS_TXC 0x0020 -#define REG_STATUS_TXBL 0x0040 -#define REG_STATUS_RXDATAV 0x0080 - -#define REG_CLKDIV 0x14 - -#define REG_RXDATAX 0x18 -#define REG_RXDATAX_RXDATA__MASK 0x01ff -#define REG_RXDATAX_PERR 0x4000 -#define REG_RXDATAX_FERR 0x8000 - -#define REG_TXDATA 0x34 - -#define REG_IF 0x40 -#define REG_IF_TXBL 0x0002 -#define REG_IF_RXDATAV 0x0004 - -#define REG_IFS 0x44 -#define REG_IFC 0x48 -#define REG_IEN 0x4c - -#define REG_ROUTE 0x54 -#define REG_ROUTE_RXPEN 0x0001 -#define REG_ROUTE_TXPEN 0x0002 -#define REG_ROUTE_CLKPEN 0x0008 -#define REG_ROUTE_LOCATION__MASK 0x0700 -#define REG_ROUTE_LOCATION(n) MASK_VAL(REG_ROUTE_LOCATION__MASK, (n)) - -struct efm32_spi_ddata { - struct spi_bitbang bitbang; - - spinlock_t lock; - - struct clk *clk; - void __iomem *base; - unsigned int rxirq, txirq; - struct efm32_spi_pdata pdata; - - /* irq data */ - struct completion done; - const u8 *tx_buf; - u8 *rx_buf; - unsigned tx_len, rx_len; -}; - -#define ddata_to_dev(ddata) (&(ddata->bitbang.master->dev)) -#define efm32_spi_vdbg(ddata, format, arg...) \ - dev_vdbg(ddata_to_dev(ddata), format, ##arg) - -static void efm32_spi_write32(struct efm32_spi_ddata *ddata, - u32 value, unsigned offset) -{ - writel_relaxed(value, ddata->base + offset); -} - -static u32 efm32_spi_read32(struct efm32_spi_ddata *ddata, unsigned offset) -{ - return readl_relaxed(ddata->base + offset); -} - -static int efm32_spi_setup_transfer(struct spi_device *spi, - struct spi_transfer *t) -{ - struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master); - - unsigned bpw = t->bits_per_word ?: spi->bits_per_word; - unsigned speed = t->speed_hz ?: spi->max_speed_hz; - unsigned long clkfreq = clk_get_rate(ddata->clk); - u32 clkdiv; - - efm32_spi_write32(ddata, REG_CTRL_SYNC | REG_CTRL_MSBF | - (spi->mode & SPI_CPHA ? REG_CTRL_CLKPHA : 0) | - (spi->mode & SPI_CPOL ? REG_CTRL_CLKPOL : 0), REG_CTRL); - - efm32_spi_write32(ddata, - REG_FRAME_DATABITS(bpw), REG_FRAME); - - if (2 * speed >= clkfreq) - clkdiv = 0; - else - clkdiv = 64 * (DIV_ROUND_UP(2 * clkfreq, speed) - 4); - - if (clkdiv > (1U << 21)) - return -EINVAL; - - efm32_spi_write32(ddata, clkdiv, REG_CLKDIV); - efm32_spi_write32(ddata, REG_CMD_MASTEREN, REG_CMD); - efm32_spi_write32(ddata, REG_CMD_RXEN | REG_CMD_TXEN, REG_CMD); - - return 0; -} - -static void efm32_spi_tx_u8(struct efm32_spi_ddata *ddata) -{ - u8 val = 0; - - if (ddata->tx_buf) { - val = *ddata->tx_buf; - ddata->tx_buf++; - } - - ddata->tx_len--; - efm32_spi_write32(ddata, val, REG_TXDATA); - efm32_spi_vdbg(ddata, "%s: tx 0x%x\n", __func__, val); -} - -static void efm32_spi_rx_u8(struct efm32_spi_ddata *ddata) -{ - u32 rxdata = efm32_spi_read32(ddata, REG_RXDATAX); - efm32_spi_vdbg(ddata, "%s: rx 0x%x\n", __func__, rxdata); - - if (ddata->rx_buf) { - *ddata->rx_buf = rxdata; - ddata->rx_buf++; - } - - ddata->rx_len--; -} - -static void efm32_spi_filltx(struct efm32_spi_ddata *ddata) -{ - while (ddata->tx_len && - ddata->tx_len + 2 > ddata->rx_len && - efm32_spi_read32(ddata, REG_STATUS) & REG_STATUS_TXBL) { - efm32_spi_tx_u8(ddata); - } -} - -static int efm32_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) -{ - struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master); - int ret = -EBUSY; - - spin_lock_irq(&ddata->lock); - - if (ddata->tx_buf || ddata->rx_buf) - goto out_unlock; - - ddata->tx_buf = t->tx_buf; - ddata->rx_buf = t->rx_buf; - ddata->tx_len = ddata->rx_len = - t->len * DIV_ROUND_UP(t->bits_per_word, 8); - - efm32_spi_filltx(ddata); - - reinit_completion(&ddata->done); - - efm32_spi_write32(ddata, REG_IF_TXBL | REG_IF_RXDATAV, REG_IEN); - - spin_unlock_irq(&ddata->lock); - - wait_for_completion(&ddata->done); - - spin_lock_irq(&ddata->lock); - - ret = t->len - max(ddata->tx_len, ddata->rx_len); - - efm32_spi_write32(ddata, 0, REG_IEN); - ddata->tx_buf = ddata->rx_buf = NULL; - -out_unlock: - spin_unlock_irq(&ddata->lock); - - return ret; -} - -static irqreturn_t efm32_spi_rxirq(int irq, void *data) -{ - struct efm32_spi_ddata *ddata = data; - irqreturn_t ret = IRQ_NONE; - - spin_lock(&ddata->lock); - - while (ddata->rx_len > 0 && - efm32_spi_read32(ddata, REG_STATUS) & - REG_STATUS_RXDATAV) { - efm32_spi_rx_u8(ddata); - - ret = IRQ_HANDLED; - } - - if (!ddata->rx_len) { - u32 ien = efm32_spi_read32(ddata, REG_IEN); - - ien &= ~REG_IF_RXDATAV; - - efm32_spi_write32(ddata, ien, REG_IEN); - - complete(&ddata->done); - } - - spin_unlock(&ddata->lock); - - return ret; -} - -static irqreturn_t efm32_spi_txirq(int irq, void *data) -{ - struct efm32_spi_ddata *ddata = data; - - efm32_spi_vdbg(ddata, - "%s: txlen = %u, rxlen = %u, if=0x%08x, stat=0x%08x\n", - __func__, ddata->tx_len, ddata->rx_len, - efm32_spi_read32(ddata, REG_IF), - efm32_spi_read32(ddata, REG_STATUS)); - - spin_lock(&ddata->lock); - - efm32_spi_filltx(ddata); - - efm32_spi_vdbg(ddata, "%s: txlen = %u, rxlen = %u\n", - __func__, ddata->tx_len, ddata->rx_len); - - if (!ddata->tx_len) { - u32 ien = efm32_spi_read32(ddata, REG_IEN); - - ien &= ~REG_IF_TXBL; - - efm32_spi_write32(ddata, ien, REG_IEN); - efm32_spi_vdbg(ddata, "disable TXBL\n"); - } - - spin_unlock(&ddata->lock); - - return IRQ_HANDLED; -} - -static u32 efm32_spi_get_configured_location(struct efm32_spi_ddata *ddata) -{ - u32 reg = efm32_spi_read32(ddata, REG_ROUTE); - - return (reg & REG_ROUTE_LOCATION__MASK) >> __ffs(REG_ROUTE_LOCATION__MASK); -} - -static void efm32_spi_probe_dt(struct platform_device *pdev, - struct spi_master *master, struct efm32_spi_ddata *ddata) -{ - struct device_node *np = pdev->dev.of_node; - u32 location; - int ret; - - ret = of_property_read_u32(np, "energymicro,location", &location); - - if (ret) - /* fall back to wrongly namespaced property */ - ret = of_property_read_u32(np, "efm32,location", &location); - - if (ret) - /* fall back to old and (wrongly) generic property "location" */ - ret = of_property_read_u32(np, "location", &location); - - if (!ret) { - dev_dbg(&pdev->dev, "using location %u\n", location); - } else { - /* default to location configured in hardware */ - location = efm32_spi_get_configured_location(ddata); - - dev_info(&pdev->dev, "fall back to location %u\n", location); - } - - ddata->pdata.location = location; -} - -static int efm32_spi_probe(struct platform_device *pdev) -{ - struct efm32_spi_ddata *ddata; - struct resource *res; - int ret; - struct spi_master *master; - struct device_node *np = pdev->dev.of_node; - - if (!np) - return -EINVAL; - - master = spi_alloc_master(&pdev->dev, sizeof(*ddata)); - if (!master) { - dev_dbg(&pdev->dev, - "failed to allocate spi master controller\n"); - return -ENOMEM; - } - platform_set_drvdata(pdev, master); - - master->dev.of_node = pdev->dev.of_node; - - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); - master->use_gpio_descriptors = true; - - ddata = spi_master_get_devdata(master); - - ddata->bitbang.master = master; - ddata->bitbang.setup_transfer = efm32_spi_setup_transfer; - ddata->bitbang.txrx_bufs = efm32_spi_txrx_bufs; - - spin_lock_init(&ddata->lock); - init_completion(&ddata->done); - - ddata->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(ddata->clk)) { - ret = PTR_ERR(ddata->clk); - dev_err(&pdev->dev, "failed to get clock: %d\n", ret); - goto err; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENODEV; - dev_err(&pdev->dev, "failed to determine base address\n"); - goto err; - } - - if (resource_size(res) < 0x60) { - ret = -EINVAL; - dev_err(&pdev->dev, "memory resource too small\n"); - goto err; - } - - ddata->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(ddata->base)) { - ret = PTR_ERR(ddata->base); - goto err; - } - - ret = platform_get_irq(pdev, 0); - if (ret <= 0) - goto err; - - ddata->rxirq = ret; - - ret = platform_get_irq(pdev, 1); - if (ret <= 0) - ret = ddata->rxirq + 1; - - ddata->txirq = ret; - - ret = clk_prepare_enable(ddata->clk); - if (ret < 0) { - dev_err(&pdev->dev, "failed to enable clock (%d)\n", ret); - goto err; - } - - efm32_spi_probe_dt(pdev, master, ddata); - - efm32_spi_write32(ddata, 0, REG_IEN); - efm32_spi_write32(ddata, REG_ROUTE_TXPEN | REG_ROUTE_RXPEN | - REG_ROUTE_CLKPEN | - REG_ROUTE_LOCATION(ddata->pdata.location), REG_ROUTE); - - ret = request_irq(ddata->rxirq, efm32_spi_rxirq, - 0, DRIVER_NAME " rx", ddata); - if (ret) { - dev_err(&pdev->dev, "failed to register rxirq (%d)\n", ret); - goto err_disable_clk; - } - - ret = request_irq(ddata->txirq, efm32_spi_txirq, - 0, DRIVER_NAME " tx", ddata); - if (ret) { - dev_err(&pdev->dev, "failed to register txirq (%d)\n", ret); - goto err_free_rx_irq; - } - - ret = spi_bitbang_start(&ddata->bitbang); - if (ret) { - dev_err(&pdev->dev, "spi_bitbang_start failed (%d)\n", ret); - - free_irq(ddata->txirq, ddata); -err_free_rx_irq: - free_irq(ddata->rxirq, ddata); -err_disable_clk: - clk_disable_unprepare(ddata->clk); -err: - spi_master_put(master); - } - - return ret; -} - -static int efm32_spi_remove(struct platform_device *pdev) -{ - struct spi_master *master = platform_get_drvdata(pdev); - struct efm32_spi_ddata *ddata = spi_master_get_devdata(master); - - spi_bitbang_stop(&ddata->bitbang); - - efm32_spi_write32(ddata, 0, REG_IEN); - - free_irq(ddata->txirq, ddata); - free_irq(ddata->rxirq, ddata); - clk_disable_unprepare(ddata->clk); - spi_master_put(master); - - return 0; -} - -static const struct of_device_id efm32_spi_dt_ids[] = { - { - .compatible = "energymicro,efm32-spi", - }, { - /* doesn't follow the "vendor,device" scheme, don't use */ - .compatible = "efm32,spi", - }, { - /* sentinel */ - } -}; -MODULE_DEVICE_TABLE(of, efm32_spi_dt_ids); - -static struct platform_driver efm32_spi_driver = { - .probe = efm32_spi_probe, - .remove = efm32_spi_remove, - - .driver = { - .name = DRIVER_NAME, - .of_match_table = efm32_spi_dt_ids, - }, -}; -module_platform_driver(efm32_spi_driver); - -MODULE_AUTHOR("Uwe Kleine-Koenig "); -MODULE_DESCRIPTION("EFM32 SPI driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index 6d8e0a05a535..e4a8d203f940 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -695,7 +695,7 @@ static void fsl_spi_cs_control(struct spi_device *spi, bool on) if (WARN_ON_ONCE(!pinfo->immr_spi_cs)) return; - iowrite32be(on ? SPI_BOOT_SEL_BIT : 0, pinfo->immr_spi_cs); + iowrite32be(on ? 0 : SPI_BOOT_SEL_BIT, pinfo->immr_spi_cs); } } diff --git a/drivers/spi/spi-hisi-sfc-v3xx.c b/drivers/spi/spi-hisi-sfc-v3xx.c index 4650b483a33d..385eb7bba05a 100644 --- a/drivers/spi/spi-hisi-sfc-v3xx.c +++ b/drivers/spi/spi-hisi-sfc-v3xx.c @@ -19,6 +19,8 @@ #define HISI_SFC_V3XX_VERSION (0x1f8) +#define HISI_SFC_V3XX_GLB_CFG (0x100) +#define HISI_SFC_V3XX_GLB_CFG_CS0_ADDR_MODE BIT(2) #define HISI_SFC_V3XX_RAW_INT_STAT (0x120) #define HISI_SFC_V3XX_INT_STAT (0x124) #define HISI_SFC_V3XX_INT_MASK (0x128) @@ -75,6 +77,7 @@ struct hisi_sfc_v3xx_host { void __iomem *regbase; int max_cmd_dword; struct completion *completion; + u8 address_mode; int irq; }; @@ -168,10 +171,18 @@ static int hisi_sfc_v3xx_adjust_op_size(struct spi_mem *mem, static bool hisi_sfc_v3xx_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) { + struct spi_device *spi = mem->spi; + struct hisi_sfc_v3xx_host *host; + + host = spi_controller_get_devdata(spi->master); + if (op->data.buswidth > 4 || op->dummy.buswidth > 4 || op->addr.buswidth > 4 || op->cmd.buswidth > 4) return false; + if (op->addr.nbytes != host->address_mode && op->addr.nbytes) + return false; + return spi_mem_default_supports_op(mem, op); } @@ -416,7 +427,7 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct hisi_sfc_v3xx_host *host; struct spi_controller *ctlr; - u32 version; + u32 version, glb_config; int ret; ctlr = spi_alloc_master(&pdev->dev, sizeof(*host)); @@ -463,16 +474,24 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev) ctlr->num_chipselect = 1; ctlr->mem_ops = &hisi_sfc_v3xx_mem_ops; + /* + * The address mode of the controller is either 3 or 4, + * which is indicated by the address mode bit in + * the global config register. The register is read only + * for the OS driver. + */ + glb_config = readl(host->regbase + HISI_SFC_V3XX_GLB_CFG); + if (glb_config & HISI_SFC_V3XX_GLB_CFG_CS0_ADDR_MODE) + host->address_mode = 4; + else + host->address_mode = 3; + version = readl(host->regbase + HISI_SFC_V3XX_VERSION); - switch (version) { - case 0x351: + if (version >= 0x351) host->max_cmd_dword = 64; - break; - default: + else host->max_cmd_dword = 16; - break; - } ret = devm_spi_register_controller(dev, ctlr); if (ret) diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 73ca821763d6..5dc4ea4b4450 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -1685,7 +1685,7 @@ static int spi_imx_probe(struct platform_device *pdev) master->dev.of_node = pdev->dev.of_node; ret = spi_bitbang_start(&spi_imx->bitbang); if (ret) { - dev_err(&pdev->dev, "bitbang start failed with %d\n", ret); + dev_err_probe(&pdev->dev, ret, "bitbang start failed\n"); goto out_bitbang_start; } diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index f3a3f196e628..dc713b0c3c4d 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -137,8 +137,8 @@ static int spi_check_buswidth_req(struct spi_mem *mem, u8 buswidth, bool tx) return -ENOTSUPP; } -bool spi_mem_default_supports_op(struct spi_mem *mem, - const struct spi_mem_op *op) +static bool spi_mem_check_buswidth(struct spi_mem *mem, + const struct spi_mem_op *op) { if (spi_check_buswidth_req(mem, op->cmd.buswidth, true)) return false; @@ -156,13 +156,29 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, op->data.dir == SPI_MEM_DATA_OUT)) return false; + return true; +} + +bool spi_mem_dtr_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (op->cmd.nbytes != 2) + return false; + + return spi_mem_check_buswidth(mem, op); +} +EXPORT_SYMBOL_GPL(spi_mem_dtr_supports_op); + +bool spi_mem_default_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) return false; if (op->cmd.nbytes != 1) return false; - return true; + return spi_mem_check_buswidth(mem, op); } EXPORT_SYMBOL_GPL(spi_mem_default_supports_op); @@ -354,6 +370,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1; xfers[xferpos].len = op->dummy.nbytes; xfers[xferpos].tx_nbits = op->dummy.buswidth; + xfers[xferpos].dummy_data = 1; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->dummy.nbytes; diff --git a/drivers/spi/spi-mpc52xx.c b/drivers/spi/spi-mpc52xx.c index ef2f24420460..36f941500676 100644 --- a/drivers/spi/spi-mpc52xx.c +++ b/drivers/spi/spi-mpc52xx.c @@ -248,7 +248,9 @@ static int mpc52xx_spi_fsmstate_transfer(int irq, struct mpc52xx_spi *ms, ms->len--; if (ms->len == 0) { ms->timestamp = get_tbl(); - ms->timestamp += ms->transfer->delay_usecs * tb_ticks_per_usec; + if (ms->transfer->delay.unit == SPI_DELAY_UNIT_USECS) + ms->timestamp += ms->transfer->delay.value * + tb_ticks_per_usec; ms->state = mpc52xx_spi_fsmstate_wait; return FSM_CONTINUE; } diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 5d643051bf3d..976f73b9e299 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -287,7 +287,7 @@ static void mtk_spi_set_cs(struct spi_device *spi, bool enable) static void mtk_spi_prepare_transfer(struct spi_master *master, struct spi_transfer *xfer) { - u32 spi_clk_hz, div, sck_time, cs_time, reg_val; + u32 spi_clk_hz, div, sck_time, reg_val; struct mtk_spi *mdata = spi_master_get_devdata(master); spi_clk_hz = clk_get_rate(mdata->spi_clk); @@ -297,32 +297,25 @@ static void mtk_spi_prepare_transfer(struct spi_master *master, div = 1; sck_time = (div + 1) / 2; - cs_time = sck_time * 2; if (mdata->dev_comp->enhance_timing) { - reg_val = (((sck_time - 1) & 0xffff) + reg_val = readl(mdata->base + SPI_CFG2_REG); + reg_val &= ~(0xffff << SPI_CFG2_SCK_HIGH_OFFSET); + reg_val |= (((sck_time - 1) & 0xffff) << SPI_CFG2_SCK_HIGH_OFFSET); + reg_val &= ~(0xffff << SPI_CFG2_SCK_LOW_OFFSET); reg_val |= (((sck_time - 1) & 0xffff) << SPI_CFG2_SCK_LOW_OFFSET); writel(reg_val, mdata->base + SPI_CFG2_REG); - reg_val = (((cs_time - 1) & 0xffff) - << SPI_ADJUST_CFG0_CS_HOLD_OFFSET); - reg_val |= (((cs_time - 1) & 0xffff) - << SPI_ADJUST_CFG0_CS_SETUP_OFFSET); - writel(reg_val, mdata->base + SPI_CFG0_REG); } else { - reg_val = (((sck_time - 1) & 0xff) + reg_val = readl(mdata->base + SPI_CFG0_REG); + reg_val &= ~(0xff << SPI_CFG0_SCK_HIGH_OFFSET); + reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_HIGH_OFFSET); + reg_val &= ~(0xff << SPI_CFG0_SCK_LOW_OFFSET); reg_val |= (((sck_time - 1) & 0xff) << SPI_CFG0_SCK_LOW_OFFSET); - reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET); - reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG0_CS_SETUP_OFFSET); writel(reg_val, mdata->base + SPI_CFG0_REG); } - - reg_val = readl(mdata->base + SPI_CFG1_REG); - reg_val &= ~SPI_CFG1_CS_IDLE_MASK; - reg_val |= (((cs_time - 1) & 0xff) << SPI_CFG1_CS_IDLE_OFFSET); - writel(reg_val, mdata->base + SPI_CFG1_REG); } static void mtk_spi_setup_packet(struct spi_master *master) @@ -513,6 +506,52 @@ static bool mtk_spi_can_dma(struct spi_master *master, (unsigned long)xfer->rx_buf % 4 == 0); } +static int mtk_spi_set_hw_cs_timing(struct spi_device *spi, + struct spi_delay *setup, + struct spi_delay *hold, + struct spi_delay *inactive) +{ + struct mtk_spi *mdata = spi_master_get_devdata(spi->master); + u16 setup_dly, hold_dly, inactive_dly; + u32 reg_val; + + if ((setup && setup->unit != SPI_DELAY_UNIT_SCK) || + (hold && hold->unit != SPI_DELAY_UNIT_SCK) || + (inactive && inactive->unit != SPI_DELAY_UNIT_SCK)) { + dev_err(&spi->dev, + "Invalid delay unit, should be SPI_DELAY_UNIT_SCK\n"); + return -EINVAL; + } + + setup_dly = setup ? setup->value : 1; + hold_dly = hold ? hold->value : 1; + inactive_dly = inactive ? inactive->value : 1; + + reg_val = readl(mdata->base + SPI_CFG0_REG); + if (mdata->dev_comp->enhance_timing) { + reg_val &= ~(0xffff << SPI_ADJUST_CFG0_CS_HOLD_OFFSET); + reg_val |= (((hold_dly - 1) & 0xffff) + << SPI_ADJUST_CFG0_CS_HOLD_OFFSET); + reg_val &= ~(0xffff << SPI_ADJUST_CFG0_CS_SETUP_OFFSET); + reg_val |= (((setup_dly - 1) & 0xffff) + << SPI_ADJUST_CFG0_CS_SETUP_OFFSET); + } else { + reg_val &= ~(0xff << SPI_CFG0_CS_HOLD_OFFSET); + reg_val |= (((hold_dly - 1) & 0xff) << SPI_CFG0_CS_HOLD_OFFSET); + reg_val &= ~(0xff << SPI_CFG0_CS_SETUP_OFFSET); + reg_val |= (((setup_dly - 1) & 0xff) + << SPI_CFG0_CS_SETUP_OFFSET); + } + writel(reg_val, mdata->base + SPI_CFG0_REG); + + reg_val = readl(mdata->base + SPI_CFG1_REG); + reg_val &= ~SPI_CFG1_CS_IDLE_MASK; + reg_val |= (((inactive_dly - 1) & 0xff) << SPI_CFG1_CS_IDLE_OFFSET); + writel(reg_val, mdata->base + SPI_CFG1_REG); + + return 0; +} + static int mtk_spi_setup(struct spi_device *spi) { struct mtk_spi *mdata = spi_master_get_devdata(spi->master); @@ -644,6 +683,7 @@ static int mtk_spi_probe(struct platform_device *pdev) master->transfer_one = mtk_spi_transfer_one; master->can_dma = mtk_spi_can_dma; master->setup = mtk_spi_setup; + master->set_cs_timing = mtk_spi_set_hw_cs_timing; of_id = of_match_node(mtk_spi_of_match, pdev->dev.of_node); if (!of_id) { diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index b57b8b3cc26e..68ed7fd64256 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -96,10 +96,16 @@ struct orion_spi { struct clk *clk; struct clk *axi_clk; const struct orion_spi_dev *devdata; + struct device *dev; struct orion_child_options child[ORION_NUM_CHIPSELECTS]; }; +#ifdef CONFIG_PM +static int orion_spi_runtime_suspend(struct device *dev); +static int orion_spi_runtime_resume(struct device *dev); +#endif + static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg) { return orion_spi->base + reg; @@ -369,8 +375,15 @@ orion_spi_write_read_8bit(struct spi_device *spi, { void __iomem *tx_reg, *rx_reg, *int_reg; struct orion_spi *orion_spi; + bool cs_single_byte; + + cs_single_byte = spi->mode & SPI_CS_WORD; orion_spi = spi_master_get_devdata(spi->master); + + if (cs_single_byte) + orion_spi_set_cs(spi, 0); + tx_reg = spi_reg(orion_spi, ORION_SPI_DATA_OUT_REG); rx_reg = spi_reg(orion_spi, ORION_SPI_DATA_IN_REG); int_reg = spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG); @@ -384,6 +397,11 @@ orion_spi_write_read_8bit(struct spi_device *spi, writel(0, tx_reg); if (orion_spi_wait_till_ready(orion_spi) < 0) { + if (cs_single_byte) { + orion_spi_set_cs(spi, 1); + /* Satisfy some SLIC devices requirements */ + udelay(4); + } dev_err(&spi->dev, "TXS timed out\n"); return -1; } @@ -391,6 +409,12 @@ orion_spi_write_read_8bit(struct spi_device *spi, if (rx_buf && *rx_buf) *(*rx_buf)++ = readl(rx_reg); + if (cs_single_byte) { + orion_spi_set_cs(spi, 1); + /* Satisfy some SLIC devices requirements */ + udelay(4); + } + return 1; } @@ -401,6 +425,11 @@ orion_spi_write_read_16bit(struct spi_device *spi, void __iomem *tx_reg, *rx_reg, *int_reg; struct orion_spi *orion_spi; + if (spi->mode & SPI_CS_WORD) { + dev_err(&spi->dev, "SPI_CS_WORD is only supported for 8 bit words\n"); + return -1; + } + orion_spi = spi_master_get_devdata(spi->master); tx_reg = spi_reg(orion_spi, ORION_SPI_DATA_OUT_REG); rx_reg = spi_reg(orion_spi, ORION_SPI_DATA_IN_REG); @@ -440,12 +469,13 @@ orion_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) orion_spi = spi_master_get_devdata(spi->master); /* - * Use SPI direct write mode if base address is available. Otherwise - * fall back to PIO mode for this transfer. + * Use SPI direct write mode if base address is available + * and SPI_CS_WORD flag is not set. + * Otherwise fall back to PIO mode for this transfer. */ vaddr = orion_spi->child[cs].direct_access.vaddr; - if (vaddr && xfer->tx_buf && word_len == 8) { + if (vaddr && xfer->tx_buf && word_len == 8 && (spi->mode & SPI_CS_WORD) == 0) { unsigned int cnt = count / 4; unsigned int rem = count % 4; @@ -507,7 +537,21 @@ static int orion_spi_transfer_one(struct spi_master *master, static int orion_spi_setup(struct spi_device *spi) { - return orion_spi_setup_transfer(spi, NULL); + int ret; +#ifdef CONFIG_PM + struct orion_spi *orion_spi = spi_master_get_devdata(spi->master); + struct device *dev = orion_spi->dev; + + orion_spi_runtime_resume(dev); +#endif + + ret = orion_spi_setup_transfer(spi, NULL); + +#ifdef CONFIG_PM + orion_spi_runtime_suspend(dev); +#endif + + return ret; } static int orion_spi_reset(struct orion_spi *orion_spi) @@ -616,7 +660,7 @@ static int orion_spi_probe(struct platform_device *pdev) } /* we support all 4 SPI modes and LSB first option */ - master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST; + master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST | SPI_CS_WORD; master->set_cs = orion_spi_set_cs; master->transfer_one = orion_spi_transfer_one; master->num_chipselect = ORION_NUM_CHIPSELECTS; @@ -630,6 +674,7 @@ static int orion_spi_probe(struct platform_device *pdev) spi = spi_master_get_devdata(master); spi->master = master; + spi->dev = &pdev->dev; of_id = of_match_device(orion_spi_of_match_table, &pdev->dev); devdata = (of_id) ? of_id->data : &orion_spi_dev_data; diff --git a/drivers/spi/spi-pxa2xx-pci.c b/drivers/spi/spi-pxa2xx-pci.c index f236e3034cf8..14fc41ed2361 100644 --- a/drivers/spi/spi-pxa2xx-pci.c +++ b/drivers/spi/spi-pxa2xx-pci.c @@ -21,7 +21,8 @@ enum { PORT_BSW1, PORT_BSW2, PORT_CE4100, - PORT_LPT, + PORT_LPT0, + PORT_LPT1, }; struct pxa_spi_info { @@ -57,8 +58,10 @@ static struct dw_dma_slave bsw1_rx_param = { .src_id = 7 }; static struct dw_dma_slave bsw2_tx_param = { .dst_id = 8 }; static struct dw_dma_slave bsw2_rx_param = { .src_id = 9 }; -static struct dw_dma_slave lpt_tx_param = { .dst_id = 0 }; -static struct dw_dma_slave lpt_rx_param = { .src_id = 1 }; +static struct dw_dma_slave lpt1_tx_param = { .dst_id = 0 }; +static struct dw_dma_slave lpt1_rx_param = { .src_id = 1 }; +static struct dw_dma_slave lpt0_tx_param = { .dst_id = 2 }; +static struct dw_dma_slave lpt0_rx_param = { .src_id = 3 }; static bool lpss_dma_filter(struct dma_chan *chan, void *param) { @@ -185,12 +188,19 @@ static struct pxa_spi_info spi_info_configs[] = { .num_chipselect = 1, .max_clk_rate = 50000000, }, - [PORT_LPT] = { + [PORT_LPT0] = { .type = LPSS_LPT_SSP, .port_id = 0, .setup = lpss_spi_setup, - .tx_param = &lpt_tx_param, - .rx_param = &lpt_rx_param, + .tx_param = &lpt0_tx_param, + .rx_param = &lpt0_rx_param, + }, + [PORT_LPT1] = { + .type = LPSS_LPT_SSP, + .port_id = 1, + .setup = lpss_spi_setup, + .tx_param = &lpt1_tx_param, + .rx_param = &lpt1_rx_param, }, }; @@ -285,8 +295,11 @@ static const struct pci_device_id pxa2xx_spi_pci_devices[] = { { PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 }, { PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 }, { PCI_VDEVICE(INTEL, 0x2e6a), PORT_CE4100 }, - { PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT }, - { }, + { PCI_VDEVICE(INTEL, 0x9c65), PORT_LPT0 }, + { PCI_VDEVICE(INTEL, 0x9c66), PORT_LPT1 }, + { PCI_VDEVICE(INTEL, 0x9ce5), PORT_LPT0 }, + { PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT1 }, + { } }; MODULE_DEVICE_TABLE(pci, pxa2xx_spi_pci_devices); diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c index bd2354fd438d..0cc767283674 100644 --- a/drivers/spi/spi-pxa2xx.c +++ b/drivers/spi/spi-pxa2xx.c @@ -1492,6 +1492,10 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = { { PCI_VDEVICE(INTEL, 0x43ab), LPSS_CNL_SSP }, { PCI_VDEVICE(INTEL, 0x43fb), LPSS_CNL_SSP }, { PCI_VDEVICE(INTEL, 0x43fd), LPSS_CNL_SSP }, + /* ADL-P */ + { PCI_VDEVICE(INTEL, 0x51aa), LPSS_CNL_SSP }, + { PCI_VDEVICE(INTEL, 0x51ab), LPSS_CNL_SSP }, + { PCI_VDEVICE(INTEL, 0x51fb), LPSS_CNL_SSP }, /* APL */ { PCI_VDEVICE(INTEL, 0x5ac2), LPSS_BXT_SSP }, { PCI_VDEVICE(INTEL, 0x5ac4), LPSS_BXT_SSP }, diff --git a/drivers/spi/spi-qcom-qspi.c b/drivers/spi/spi-qcom-qspi.c index 8863be370884..1dbcc410cd35 100644 --- a/drivers/spi/spi-qcom-qspi.c +++ b/drivers/spi/spi-qcom-qspi.c @@ -511,8 +511,7 @@ static int qcom_qspi_probe(struct platform_device *pdev) ret = platform_get_irq(pdev, 0); if (ret < 0) return ret; - ret = devm_request_irq(dev, ret, qcom_qspi_irq, - IRQF_TRIGGER_HIGH, dev_name(dev), ctrl); + ret = devm_request_irq(dev, ret, qcom_qspi_irq, 0, dev_name(dev), ctrl); if (ret) { dev_err(dev, "Failed to request irq %d\n", ret); return ret; diff --git a/drivers/spi/spi-realtek-rtl.c b/drivers/spi/spi-realtek-rtl.c new file mode 100644 index 000000000000..866b0477dbd7 --- /dev/null +++ b/drivers/spi/spi-realtek-rtl.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +struct rtspi { + void __iomem *base; +}; + +/* SPI Flash Configuration Register */ +#define RTL_SPI_SFCR 0x00 +#define RTL_SPI_SFCR_RBO BIT(28) +#define RTL_SPI_SFCR_WBO BIT(27) + +/* SPI Flash Control and Status Register */ +#define RTL_SPI_SFCSR 0x08 +#define RTL_SPI_SFCSR_CSB0 BIT(31) +#define RTL_SPI_SFCSR_CSB1 BIT(30) +#define RTL_SPI_SFCSR_RDY BIT(27) +#define RTL_SPI_SFCSR_CS BIT(24) +#define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28) +#define RTL_SPI_SFCSR_LEN1 (0x00 << 28) +#define RTL_SPI_SFCSR_LEN4 (0x03 << 28) + +/* SPI Flash Data Register */ +#define RTL_SPI_SFDR 0x0c + +#define REG(x) (rtspi->base + x) + + +static void rt_set_cs(struct spi_device *spi, bool active) +{ + struct rtspi *rtspi = spi_controller_get_devdata(spi->controller); + u32 value; + + /* CS0 bit is active low */ + value = readl(REG(RTL_SPI_SFCSR)); + if (active) + value |= RTL_SPI_SFCSR_CSB0; + else + value &= ~RTL_SPI_SFCSR_CSB0; + writel(value, REG(RTL_SPI_SFCSR)); +} + +static void set_size(struct rtspi *rtspi, int size) +{ + u32 value; + + value = readl(REG(RTL_SPI_SFCSR)); + value &= RTL_SPI_SFCSR_LEN_MASK; + if (size == 4) + value |= RTL_SPI_SFCSR_LEN4; + else if (size == 1) + value |= RTL_SPI_SFCSR_LEN1; + writel(value, REG(RTL_SPI_SFCSR)); +} + +static inline void wait_ready(struct rtspi *rtspi) +{ + while (!(readl(REG(RTL_SPI_SFCSR)) & RTL_SPI_SFCSR_RDY)) + cpu_relax(); +} +static void send4(struct rtspi *rtspi, const u32 *buf) +{ + wait_ready(rtspi); + set_size(rtspi, 4); + writel(*buf, REG(RTL_SPI_SFDR)); +} + +static void send1(struct rtspi *rtspi, const u8 *buf) +{ + wait_ready(rtspi); + set_size(rtspi, 1); + writel(buf[0] << 24, REG(RTL_SPI_SFDR)); +} + +static void rcv4(struct rtspi *rtspi, u32 *buf) +{ + wait_ready(rtspi); + set_size(rtspi, 4); + *buf = readl(REG(RTL_SPI_SFDR)); +} + +static void rcv1(struct rtspi *rtspi, u8 *buf) +{ + wait_ready(rtspi); + set_size(rtspi, 1); + *buf = readl(REG(RTL_SPI_SFDR)) >> 24; +} + +static int transfer_one(struct spi_controller *ctrl, struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct rtspi *rtspi = spi_controller_get_devdata(ctrl); + void *rx_buf; + const void *tx_buf; + int cnt; + + tx_buf = xfer->tx_buf; + rx_buf = xfer->rx_buf; + cnt = xfer->len; + if (tx_buf) { + while (cnt >= 4) { + send4(rtspi, tx_buf); + tx_buf += 4; + cnt -= 4; + } + while (cnt) { + send1(rtspi, tx_buf); + tx_buf++; + cnt--; + } + } else if (rx_buf) { + while (cnt >= 4) { + rcv4(rtspi, rx_buf); + rx_buf += 4; + cnt -= 4; + } + while (cnt) { + rcv1(rtspi, rx_buf); + rx_buf++; + cnt--; + } + } + + spi_finalize_current_transfer(ctrl); + + return 0; +} + +static void init_hw(struct rtspi *rtspi) +{ + u32 value; + + /* Turn on big-endian byte ordering */ + value = readl(REG(RTL_SPI_SFCR)); + value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO; + writel(value, REG(RTL_SPI_SFCR)); + + value = readl(REG(RTL_SPI_SFCSR)); + /* Permanently disable CS1, since it's never used */ + value |= RTL_SPI_SFCSR_CSB1; + /* Select CS0 for use */ + value &= RTL_SPI_SFCSR_CS; + writel(value, REG(RTL_SPI_SFCSR)); +} + +static int realtek_rtl_spi_probe(struct platform_device *pdev) +{ + struct spi_controller *ctrl; + struct rtspi *rtspi; + int err; + + ctrl = devm_spi_alloc_master(&pdev->dev, sizeof(*rtspi)); + if (!ctrl) { + dev_err(&pdev->dev, "Error allocating SPI controller\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, ctrl); + rtspi = spi_controller_get_devdata(ctrl); + + rtspi->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(rtspi->base)) { + dev_err(&pdev->dev, "Could not map SPI register address"); + return -ENOMEM; + } + + init_hw(rtspi); + + ctrl->dev.of_node = pdev->dev.of_node; + ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX; + ctrl->set_cs = rt_set_cs; + ctrl->transfer_one = transfer_one; + + err = devm_spi_register_controller(&pdev->dev, ctrl); + if (err) { + dev_err(&pdev->dev, "Could not register SPI controller\n"); + return -ENODEV; + } + + return 0; +} + + +static const struct of_device_id realtek_rtl_spi_of_ids[] = { + { .compatible = "realtek,rtl8380-spi" }, + { .compatible = "realtek,rtl8382-spi" }, + { .compatible = "realtek,rtl8391-spi" }, + { .compatible = "realtek,rtl8392-spi" }, + { .compatible = "realtek,rtl8393-spi" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, realtek_rtl_spi_of_ids); + +static struct platform_driver realtek_rtl_spi_driver = { + .probe = realtek_rtl_spi_probe, + .driver = { + .name = "realtek-rtl-spi", + .of_match_table = realtek_rtl_spi_of_ids, + }, +}; + +module_platform_driver(realtek_rtl_spi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bert Vermeulen "); +MODULE_DESCRIPTION("Realtek RTL SPI driver"); diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c index 09d8e92400eb..936ef54e0903 100644 --- a/drivers/spi/spi-rockchip.c +++ b/drivers/spi/spi-rockchip.c @@ -566,7 +566,7 @@ static int rockchip_spi_slave_abort(struct spi_controller *ctlr) struct rockchip_spi *rs = spi_controller_get_devdata(ctlr); rs->slave_abort = true; - complete(&ctlr->xfer_completion); + spi_finalize_current_transfer(ctlr); return 0; } diff --git a/drivers/spi/spi-rpc-if.c b/drivers/spi/spi-rpc-if.c index 3579675485a5..c53138ce0030 100644 --- a/drivers/spi/spi-rpc-if.c +++ b/drivers/spi/spi-rpc-if.c @@ -176,15 +176,14 @@ static int rpcif_spi_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP -static int rpcif_spi_suspend(struct device *dev) +static int __maybe_unused rpcif_spi_suspend(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); return spi_controller_suspend(ctlr); } -static int rpcif_spi_resume(struct device *dev) +static int __maybe_unused rpcif_spi_resume(struct device *dev) { struct spi_controller *ctlr = dev_get_drvdata(dev); @@ -192,17 +191,15 @@ static int rpcif_spi_resume(struct device *dev) } static SIMPLE_DEV_PM_OPS(rpcif_spi_pm_ops, rpcif_spi_suspend, rpcif_spi_resume); -#define DEV_PM_OPS (&rpcif_spi_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif static struct platform_driver rpcif_spi_driver = { .probe = rpcif_spi_probe, .remove = rpcif_spi_remove, .driver = { .name = "rpc-if-spi", - .pm = DEV_PM_OPS, +#ifdef CONFIG_PM_SLEEP + .pm = &rpcif_spi_pm_ops, +#endif }, }; module_platform_driver(rpcif_spi_driver); diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index b2579af0e3eb..41ed9ff8fad0 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -259,11 +259,13 @@ static const u32 sh_msiof_spi_div_array[] = { }; static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p, - unsigned long parent_rate, u32 spi_hz) + struct spi_transfer *t) { + unsigned long parent_rate = clk_get_rate(p->clk); + unsigned int div_pow = p->min_div_pow; + u32 spi_hz = t->speed_hz; unsigned long div; u32 brps, scr; - unsigned int div_pow = p->min_div_pow; if (!spi_hz || !parent_rate) { WARN(1, "Invalid clock rate parameters %lu and %u\n", @@ -292,6 +294,8 @@ static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p, brps = 32; } + t->effective_speed_hz = parent_rate / (brps << div_pow); + scr = sh_msiof_spi_div_array[div_pow] | SISCR_BRPS(brps); sh_msiof_write(p, SITSCR, scr); if (!(p->ctlr->flags & SPI_CONTROLLER_MUST_TX)) @@ -923,7 +927,7 @@ static int sh_msiof_transfer_one(struct spi_controller *ctlr, /* setup clocks (clock already enabled in chipselect()) */ if (!spi_controller_is_slave(p->ctlr)) - sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk), t->speed_hz); + sh_msiof_spi_set_clk_regs(p, t); while (ctlr->dma_tx && len > 15) { /* @@ -1258,6 +1262,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) const struct sh_msiof_chipdata *chipdata; struct sh_msiof_spi_info *info; struct sh_msiof_spi_priv *p; + unsigned long clksrc; int i; int ret; @@ -1333,6 +1338,9 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) /* init controller code */ ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; ctlr->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE; + clksrc = clk_get_rate(p->clk); + ctlr->min_speed_hz = DIV_ROUND_UP(clksrc, 1024); + ctlr->max_speed_hz = DIV_ROUND_UP(clksrc, 1 << p->min_div_pow); ctlr->flags = chipdata->ctlr_flags; ctlr->bus_num = pdev->id; ctlr->num_chipselect = p->info->num_chipselect; diff --git a/drivers/spi/spi-sirf.c b/drivers/spi/spi-sirf.c deleted file mode 100644 index 8419e6722e17..000000000000 --- a/drivers/spi/spi-sirf.c +++ /dev/null @@ -1,1236 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * SPI bus driver for CSR SiRFprimaII - * - * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "sirfsoc_spi" -/* SPI CTRL register defines */ -#define SIRFSOC_SPI_SLV_MODE BIT(16) -#define SIRFSOC_SPI_CMD_MODE BIT(17) -#define SIRFSOC_SPI_CS_IO_OUT BIT(18) -#define SIRFSOC_SPI_CS_IO_MODE BIT(19) -#define SIRFSOC_SPI_CLK_IDLE_STAT BIT(20) -#define SIRFSOC_SPI_CS_IDLE_STAT BIT(21) -#define SIRFSOC_SPI_TRAN_MSB BIT(22) -#define SIRFSOC_SPI_DRV_POS_EDGE BIT(23) -#define SIRFSOC_SPI_CS_HOLD_TIME BIT(24) -#define SIRFSOC_SPI_CLK_SAMPLE_MODE BIT(25) -#define SIRFSOC_SPI_TRAN_DAT_FORMAT_8 (0 << 26) -#define SIRFSOC_SPI_TRAN_DAT_FORMAT_12 (1 << 26) -#define SIRFSOC_SPI_TRAN_DAT_FORMAT_16 (2 << 26) -#define SIRFSOC_SPI_TRAN_DAT_FORMAT_32 (3 << 26) -#define SIRFSOC_SPI_CMD_BYTE_NUM(x) ((x & 3) << 28) -#define SIRFSOC_SPI_ENA_AUTO_CLR BIT(30) -#define SIRFSOC_SPI_MUL_DAT_MODE BIT(31) - -/* Interrupt Enable */ -#define SIRFSOC_SPI_RX_DONE_INT_EN BIT(0) -#define SIRFSOC_SPI_TX_DONE_INT_EN BIT(1) -#define SIRFSOC_SPI_RX_OFLOW_INT_EN BIT(2) -#define SIRFSOC_SPI_TX_UFLOW_INT_EN BIT(3) -#define SIRFSOC_SPI_RX_IO_DMA_INT_EN BIT(4) -#define SIRFSOC_SPI_TX_IO_DMA_INT_EN BIT(5) -#define SIRFSOC_SPI_RXFIFO_FULL_INT_EN BIT(6) -#define SIRFSOC_SPI_TXFIFO_EMPTY_INT_EN BIT(7) -#define SIRFSOC_SPI_RXFIFO_THD_INT_EN BIT(8) -#define SIRFSOC_SPI_TXFIFO_THD_INT_EN BIT(9) -#define SIRFSOC_SPI_FRM_END_INT_EN BIT(10) - -/* Interrupt status */ -#define SIRFSOC_SPI_RX_DONE BIT(0) -#define SIRFSOC_SPI_TX_DONE BIT(1) -#define SIRFSOC_SPI_RX_OFLOW BIT(2) -#define SIRFSOC_SPI_TX_UFLOW BIT(3) -#define SIRFSOC_SPI_RX_IO_DMA BIT(4) -#define SIRFSOC_SPI_RX_FIFO_FULL BIT(6) -#define SIRFSOC_SPI_TXFIFO_EMPTY BIT(7) -#define SIRFSOC_SPI_RXFIFO_THD_REACH BIT(8) -#define SIRFSOC_SPI_TXFIFO_THD_REACH BIT(9) -#define SIRFSOC_SPI_FRM_END BIT(10) - -/* TX RX enable */ -#define SIRFSOC_SPI_RX_EN BIT(0) -#define SIRFSOC_SPI_TX_EN BIT(1) -#define SIRFSOC_SPI_CMD_TX_EN BIT(2) - -#define SIRFSOC_SPI_IO_MODE_SEL BIT(0) -#define SIRFSOC_SPI_RX_DMA_FLUSH BIT(2) - -/* FIFO OPs */ -#define SIRFSOC_SPI_FIFO_RESET BIT(0) -#define SIRFSOC_SPI_FIFO_START BIT(1) - -/* FIFO CTRL */ -#define SIRFSOC_SPI_FIFO_WIDTH_BYTE (0 << 0) -#define SIRFSOC_SPI_FIFO_WIDTH_WORD (1 << 0) -#define SIRFSOC_SPI_FIFO_WIDTH_DWORD (2 << 0) -/* USP related */ -#define SIRFSOC_USP_SYNC_MODE BIT(0) -#define SIRFSOC_USP_SLV_MODE BIT(1) -#define SIRFSOC_USP_LSB BIT(4) -#define SIRFSOC_USP_EN BIT(5) -#define SIRFSOC_USP_RXD_FALLING_EDGE BIT(6) -#define SIRFSOC_USP_TXD_FALLING_EDGE BIT(7) -#define SIRFSOC_USP_CS_HIGH_VALID BIT(9) -#define SIRFSOC_USP_SCLK_IDLE_STAT BIT(11) -#define SIRFSOC_USP_TFS_IO_MODE BIT(14) -#define SIRFSOC_USP_TFS_IO_INPUT BIT(19) - -#define SIRFSOC_USP_RXD_DELAY_LEN_MASK 0xFF -#define SIRFSOC_USP_TXD_DELAY_LEN_MASK 0xFF -#define SIRFSOC_USP_RXD_DELAY_OFFSET 0 -#define SIRFSOC_USP_TXD_DELAY_OFFSET 8 -#define SIRFSOC_USP_RXD_DELAY_LEN 1 -#define SIRFSOC_USP_TXD_DELAY_LEN 1 -#define SIRFSOC_USP_CLK_DIVISOR_OFFSET 21 -#define SIRFSOC_USP_CLK_DIVISOR_MASK 0x3FF -#define SIRFSOC_USP_CLK_10_11_MASK 0x3 -#define SIRFSOC_USP_CLK_10_11_OFFSET 30 -#define SIRFSOC_USP_CLK_12_15_MASK 0xF -#define SIRFSOC_USP_CLK_12_15_OFFSET 24 - -#define SIRFSOC_USP_TX_DATA_OFFSET 0 -#define SIRFSOC_USP_TX_SYNC_OFFSET 8 -#define SIRFSOC_USP_TX_FRAME_OFFSET 16 -#define SIRFSOC_USP_TX_SHIFTER_OFFSET 24 - -#define SIRFSOC_USP_TX_DATA_MASK 0xFF -#define SIRFSOC_USP_TX_SYNC_MASK 0xFF -#define SIRFSOC_USP_TX_FRAME_MASK 0xFF -#define SIRFSOC_USP_TX_SHIFTER_MASK 0x1F - -#define SIRFSOC_USP_RX_DATA_OFFSET 0 -#define SIRFSOC_USP_RX_FRAME_OFFSET 8 -#define SIRFSOC_USP_RX_SHIFTER_OFFSET 16 - -#define SIRFSOC_USP_RX_DATA_MASK 0xFF -#define SIRFSOC_USP_RX_FRAME_MASK 0xFF -#define SIRFSOC_USP_RX_SHIFTER_MASK 0x1F -#define SIRFSOC_USP_CS_HIGH_VALUE BIT(1) - -#define SIRFSOC_SPI_FIFO_SC_OFFSET 0 -#define SIRFSOC_SPI_FIFO_LC_OFFSET 10 -#define SIRFSOC_SPI_FIFO_HC_OFFSET 20 - -#define SIRFSOC_SPI_FIFO_FULL_MASK(s) (1 << ((s)->fifo_full_offset)) -#define SIRFSOC_SPI_FIFO_EMPTY_MASK(s) (1 << ((s)->fifo_full_offset + 1)) -#define SIRFSOC_SPI_FIFO_THD_MASK(s) ((s)->fifo_size - 1) -#define SIRFSOC_SPI_FIFO_THD_OFFSET 2 -#define SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(s, val) \ - ((val) & (s)->fifo_level_chk_mask) - -enum sirf_spi_type { - SIRF_REAL_SPI, - SIRF_USP_SPI_P2, - SIRF_USP_SPI_A7, -}; - -/* - * only if the rx/tx buffer and transfer size are 4-bytes aligned, we use dma - * due to the limitation of dma controller - */ - -#define ALIGNED(x) (!((u32)x & 0x3)) -#define IS_DMA_VALID(x) (x && ALIGNED(x->tx_buf) && ALIGNED(x->rx_buf) && \ - ALIGNED(x->len) && (x->len < 2 * PAGE_SIZE)) - -#define SIRFSOC_MAX_CMD_BYTES 4 -#define SIRFSOC_SPI_DEFAULT_FRQ 1000000 - -struct sirf_spi_register { - /*SPI and USP-SPI common*/ - u32 tx_rx_en; - u32 int_en; - u32 int_st; - u32 tx_dma_io_ctrl; - u32 tx_dma_io_len; - u32 txfifo_ctrl; - u32 txfifo_level_chk; - u32 txfifo_op; - u32 txfifo_st; - u32 txfifo_data; - u32 rx_dma_io_ctrl; - u32 rx_dma_io_len; - u32 rxfifo_ctrl; - u32 rxfifo_level_chk; - u32 rxfifo_op; - u32 rxfifo_st; - u32 rxfifo_data; - /*SPI self*/ - u32 spi_ctrl; - u32 spi_cmd; - u32 spi_dummy_delay_ctrl; - /*USP-SPI self*/ - u32 usp_mode1; - u32 usp_mode2; - u32 usp_tx_frame_ctrl; - u32 usp_rx_frame_ctrl; - u32 usp_pin_io_data; - u32 usp_risc_dsp_mode; - u32 usp_async_param_reg; - u32 usp_irda_x_mode_div; - u32 usp_sm_cfg; - u32 usp_int_en_clr; -}; - -static const struct sirf_spi_register real_spi_register = { - .tx_rx_en = 0x8, - .int_en = 0xc, - .int_st = 0x10, - .tx_dma_io_ctrl = 0x100, - .tx_dma_io_len = 0x104, - .txfifo_ctrl = 0x108, - .txfifo_level_chk = 0x10c, - .txfifo_op = 0x110, - .txfifo_st = 0x114, - .txfifo_data = 0x118, - .rx_dma_io_ctrl = 0x120, - .rx_dma_io_len = 0x124, - .rxfifo_ctrl = 0x128, - .rxfifo_level_chk = 0x12c, - .rxfifo_op = 0x130, - .rxfifo_st = 0x134, - .rxfifo_data = 0x138, - .spi_ctrl = 0x0, - .spi_cmd = 0x4, - .spi_dummy_delay_ctrl = 0x144, -}; - -static const struct sirf_spi_register usp_spi_register = { - .tx_rx_en = 0x10, - .int_en = 0x14, - .int_st = 0x18, - .tx_dma_io_ctrl = 0x100, - .tx_dma_io_len = 0x104, - .txfifo_ctrl = 0x108, - .txfifo_level_chk = 0x10c, - .txfifo_op = 0x110, - .txfifo_st = 0x114, - .txfifo_data = 0x118, - .rx_dma_io_ctrl = 0x120, - .rx_dma_io_len = 0x124, - .rxfifo_ctrl = 0x128, - .rxfifo_level_chk = 0x12c, - .rxfifo_op = 0x130, - .rxfifo_st = 0x134, - .rxfifo_data = 0x138, - .usp_mode1 = 0x0, - .usp_mode2 = 0x4, - .usp_tx_frame_ctrl = 0x8, - .usp_rx_frame_ctrl = 0xc, - .usp_pin_io_data = 0x1c, - .usp_risc_dsp_mode = 0x20, - .usp_async_param_reg = 0x24, - .usp_irda_x_mode_div = 0x28, - .usp_sm_cfg = 0x2c, - .usp_int_en_clr = 0x140, -}; - -struct sirfsoc_spi { - struct spi_bitbang bitbang; - struct completion rx_done; - struct completion tx_done; - - void __iomem *base; - u32 ctrl_freq; /* SPI controller clock speed */ - struct clk *clk; - - /* rx & tx bufs from the spi_transfer */ - const void *tx; - void *rx; - - /* place received word into rx buffer */ - void (*rx_word) (struct sirfsoc_spi *); - /* get word from tx buffer for sending */ - void (*tx_word) (struct sirfsoc_spi *); - - /* number of words left to be tranmitted/received */ - unsigned int left_tx_word; - unsigned int left_rx_word; - - /* rx & tx DMA channels */ - struct dma_chan *rx_chan; - struct dma_chan *tx_chan; - dma_addr_t src_start; - dma_addr_t dst_start; - int word_width; /* in bytes */ - - /* - * if tx size is not more than 4 and rx size is NULL, use - * command model - */ - bool tx_by_cmd; - bool hw_cs; - enum sirf_spi_type type; - const struct sirf_spi_register *regs; - unsigned int fifo_size; - /* fifo empty offset is (fifo full offset + 1)*/ - unsigned int fifo_full_offset; - /* fifo_level_chk_mask is (fifo_size/4 - 1) */ - unsigned int fifo_level_chk_mask; - unsigned int dat_max_frm_len; -}; - -struct sirf_spi_comp_data { - const struct sirf_spi_register *regs; - enum sirf_spi_type type; - unsigned int dat_max_frm_len; - unsigned int fifo_size; - void (*hwinit)(struct sirfsoc_spi *sspi); -}; - -static void sirfsoc_usp_hwinit(struct sirfsoc_spi *sspi) -{ - /* reset USP and let USP can operate */ - writel(readl(sspi->base + sspi->regs->usp_mode1) & - ~SIRFSOC_USP_EN, sspi->base + sspi->regs->usp_mode1); - writel(readl(sspi->base + sspi->regs->usp_mode1) | - SIRFSOC_USP_EN, sspi->base + sspi->regs->usp_mode1); -} - -static void spi_sirfsoc_rx_word_u8(struct sirfsoc_spi *sspi) -{ - u32 data; - u8 *rx = sspi->rx; - - data = readl(sspi->base + sspi->regs->rxfifo_data); - - if (rx) { - *rx++ = (u8) data; - sspi->rx = rx; - } - - sspi->left_rx_word--; -} - -static void spi_sirfsoc_tx_word_u8(struct sirfsoc_spi *sspi) -{ - u32 data = 0; - const u8 *tx = sspi->tx; - - if (tx) { - data = *tx++; - sspi->tx = tx; - } - writel(data, sspi->base + sspi->regs->txfifo_data); - sspi->left_tx_word--; -} - -static void spi_sirfsoc_rx_word_u16(struct sirfsoc_spi *sspi) -{ - u32 data; - u16 *rx = sspi->rx; - - data = readl(sspi->base + sspi->regs->rxfifo_data); - - if (rx) { - *rx++ = (u16) data; - sspi->rx = rx; - } - - sspi->left_rx_word--; -} - -static void spi_sirfsoc_tx_word_u16(struct sirfsoc_spi *sspi) -{ - u32 data = 0; - const u16 *tx = sspi->tx; - - if (tx) { - data = *tx++; - sspi->tx = tx; - } - - writel(data, sspi->base + sspi->regs->txfifo_data); - sspi->left_tx_word--; -} - -static void spi_sirfsoc_rx_word_u32(struct sirfsoc_spi *sspi) -{ - u32 data; - u32 *rx = sspi->rx; - - data = readl(sspi->base + sspi->regs->rxfifo_data); - - if (rx) { - *rx++ = (u32) data; - sspi->rx = rx; - } - - sspi->left_rx_word--; - -} - -static void spi_sirfsoc_tx_word_u32(struct sirfsoc_spi *sspi) -{ - u32 data = 0; - const u32 *tx = sspi->tx; - - if (tx) { - data = *tx++; - sspi->tx = tx; - } - - writel(data, sspi->base + sspi->regs->txfifo_data); - sspi->left_tx_word--; -} - -static irqreturn_t spi_sirfsoc_irq(int irq, void *dev_id) -{ - struct sirfsoc_spi *sspi = dev_id; - u32 spi_stat; - - spi_stat = readl(sspi->base + sspi->regs->int_st); - if (sspi->tx_by_cmd && sspi->type == SIRF_REAL_SPI - && (spi_stat & SIRFSOC_SPI_FRM_END)) { - complete(&sspi->tx_done); - writel(0x0, sspi->base + sspi->regs->int_en); - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - return IRQ_HANDLED; - } - /* Error Conditions */ - if (spi_stat & SIRFSOC_SPI_RX_OFLOW || - spi_stat & SIRFSOC_SPI_TX_UFLOW) { - complete(&sspi->tx_done); - complete(&sspi->rx_done); - switch (sspi->type) { - case SIRF_REAL_SPI: - case SIRF_USP_SPI_P2: - writel(0x0, sspi->base + sspi->regs->int_en); - break; - case SIRF_USP_SPI_A7: - writel(~0UL, sspi->base + sspi->regs->usp_int_en_clr); - break; - } - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - return IRQ_HANDLED; - } - if (spi_stat & SIRFSOC_SPI_TXFIFO_EMPTY) - complete(&sspi->tx_done); - while (!(readl(sspi->base + sspi->regs->int_st) & - SIRFSOC_SPI_RX_IO_DMA)) - cpu_relax(); - complete(&sspi->rx_done); - switch (sspi->type) { - case SIRF_REAL_SPI: - case SIRF_USP_SPI_P2: - writel(0x0, sspi->base + sspi->regs->int_en); - break; - case SIRF_USP_SPI_A7: - writel(~0UL, sspi->base + sspi->regs->usp_int_en_clr); - break; - } - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - - return IRQ_HANDLED; -} - -static void spi_sirfsoc_dma_fini_callback(void *data) -{ - struct completion *dma_complete = data; - - complete(dma_complete); -} - -static void spi_sirfsoc_cmd_transfer(struct spi_device *spi, - struct spi_transfer *t) -{ - struct sirfsoc_spi *sspi; - int timeout = t->len * 10; - u32 cmd; - - sspi = spi_master_get_devdata(spi->master); - writel(SIRFSOC_SPI_FIFO_RESET, sspi->base + sspi->regs->txfifo_op); - writel(SIRFSOC_SPI_FIFO_START, sspi->base + sspi->regs->txfifo_op); - memcpy(&cmd, sspi->tx, t->len); - if (sspi->word_width == 1 && !(spi->mode & SPI_LSB_FIRST)) - cmd = cpu_to_be32(cmd) >> - ((SIRFSOC_MAX_CMD_BYTES - t->len) * 8); - if (sspi->word_width == 2 && t->len == 4 && - (!(spi->mode & SPI_LSB_FIRST))) - cmd = ((cmd & 0xffff) << 16) | (cmd >> 16); - writel(cmd, sspi->base + sspi->regs->spi_cmd); - writel(SIRFSOC_SPI_FRM_END_INT_EN, - sspi->base + sspi->regs->int_en); - writel(SIRFSOC_SPI_CMD_TX_EN, - sspi->base + sspi->regs->tx_rx_en); - if (wait_for_completion_timeout(&sspi->tx_done, timeout) == 0) { - dev_err(&spi->dev, "cmd transfer timeout\n"); - return; - } - sspi->left_rx_word -= t->len; -} - -static void spi_sirfsoc_dma_transfer(struct spi_device *spi, - struct spi_transfer *t) -{ - struct sirfsoc_spi *sspi; - struct dma_async_tx_descriptor *rx_desc, *tx_desc; - int timeout = t->len * 10; - - sspi = spi_master_get_devdata(spi->master); - writel(SIRFSOC_SPI_FIFO_RESET, sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_RESET, sspi->base + sspi->regs->txfifo_op); - switch (sspi->type) { - case SIRF_REAL_SPI: - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->txfifo_op); - writel(0, sspi->base + sspi->regs->int_en); - break; - case SIRF_USP_SPI_P2: - writel(0x0, sspi->base + sspi->regs->rxfifo_op); - writel(0x0, sspi->base + sspi->regs->txfifo_op); - writel(0, sspi->base + sspi->regs->int_en); - break; - case SIRF_USP_SPI_A7: - writel(0x0, sspi->base + sspi->regs->rxfifo_op); - writel(0x0, sspi->base + sspi->regs->txfifo_op); - writel(~0UL, sspi->base + sspi->regs->usp_int_en_clr); - break; - } - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - if (sspi->left_tx_word < sspi->dat_max_frm_len) { - switch (sspi->type) { - case SIRF_REAL_SPI: - writel(readl(sspi->base + sspi->regs->spi_ctrl) | - SIRFSOC_SPI_ENA_AUTO_CLR | - SIRFSOC_SPI_MUL_DAT_MODE, - sspi->base + sspi->regs->spi_ctrl); - writel(sspi->left_tx_word - 1, - sspi->base + sspi->regs->tx_dma_io_len); - writel(sspi->left_tx_word - 1, - sspi->base + sspi->regs->rx_dma_io_len); - break; - case SIRF_USP_SPI_P2: - case SIRF_USP_SPI_A7: - /*USP simulate SPI, tx/rx_dma_io_len indicates bytes*/ - writel(sspi->left_tx_word * sspi->word_width, - sspi->base + sspi->regs->tx_dma_io_len); - writel(sspi->left_tx_word * sspi->word_width, - sspi->base + sspi->regs->rx_dma_io_len); - break; - } - } else { - if (sspi->type == SIRF_REAL_SPI) - writel(readl(sspi->base + sspi->regs->spi_ctrl), - sspi->base + sspi->regs->spi_ctrl); - writel(0, sspi->base + sspi->regs->tx_dma_io_len); - writel(0, sspi->base + sspi->regs->rx_dma_io_len); - } - sspi->dst_start = dma_map_single(&spi->dev, sspi->rx, t->len, - (t->tx_buf != t->rx_buf) ? - DMA_FROM_DEVICE : DMA_BIDIRECTIONAL); - rx_desc = dmaengine_prep_slave_single(sspi->rx_chan, - sspi->dst_start, t->len, DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - rx_desc->callback = spi_sirfsoc_dma_fini_callback; - rx_desc->callback_param = &sspi->rx_done; - - sspi->src_start = dma_map_single(&spi->dev, (void *)sspi->tx, t->len, - (t->tx_buf != t->rx_buf) ? - DMA_TO_DEVICE : DMA_BIDIRECTIONAL); - tx_desc = dmaengine_prep_slave_single(sspi->tx_chan, - sspi->src_start, t->len, DMA_MEM_TO_DEV, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - tx_desc->callback = spi_sirfsoc_dma_fini_callback; - tx_desc->callback_param = &sspi->tx_done; - - dmaengine_submit(tx_desc); - dmaengine_submit(rx_desc); - dma_async_issue_pending(sspi->tx_chan); - dma_async_issue_pending(sspi->rx_chan); - writel(SIRFSOC_SPI_RX_EN | SIRFSOC_SPI_TX_EN, - sspi->base + sspi->regs->tx_rx_en); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) { - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->txfifo_op); - } - if (wait_for_completion_timeout(&sspi->rx_done, timeout) == 0) { - dev_err(&spi->dev, "transfer timeout\n"); - dmaengine_terminate_all(sspi->rx_chan); - } else - sspi->left_rx_word = 0; - /* - * we only wait tx-done event if transferring by DMA. for PIO, - * we get rx data by writing tx data, so if rx is done, tx has - * done earlier - */ - if (wait_for_completion_timeout(&sspi->tx_done, timeout) == 0) { - dev_err(&spi->dev, "transfer timeout\n"); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) - writel(0, sspi->base + sspi->regs->tx_rx_en); - dmaengine_terminate_all(sspi->tx_chan); - } - dma_unmap_single(&spi->dev, sspi->src_start, t->len, DMA_TO_DEVICE); - dma_unmap_single(&spi->dev, sspi->dst_start, t->len, DMA_FROM_DEVICE); - /* TX, RX FIFO stop */ - writel(0, sspi->base + sspi->regs->rxfifo_op); - writel(0, sspi->base + sspi->regs->txfifo_op); - if (sspi->left_tx_word >= sspi->dat_max_frm_len) - writel(0, sspi->base + sspi->regs->tx_rx_en); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) - writel(0, sspi->base + sspi->regs->tx_rx_en); -} - -static void spi_sirfsoc_pio_transfer(struct spi_device *spi, - struct spi_transfer *t) -{ - struct sirfsoc_spi *sspi; - int timeout = t->len * 10; - unsigned int data_units; - - sspi = spi_master_get_devdata(spi->master); - do { - writel(SIRFSOC_SPI_FIFO_RESET, - sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_RESET, - sspi->base + sspi->regs->txfifo_op); - switch (sspi->type) { - case SIRF_USP_SPI_P2: - writel(0x0, sspi->base + sspi->regs->rxfifo_op); - writel(0x0, sspi->base + sspi->regs->txfifo_op); - writel(0, sspi->base + sspi->regs->int_en); - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - writel(min((sspi->left_tx_word * sspi->word_width), - sspi->fifo_size), - sspi->base + sspi->regs->tx_dma_io_len); - writel(min((sspi->left_rx_word * sspi->word_width), - sspi->fifo_size), - sspi->base + sspi->regs->rx_dma_io_len); - break; - case SIRF_USP_SPI_A7: - writel(0x0, sspi->base + sspi->regs->rxfifo_op); - writel(0x0, sspi->base + sspi->regs->txfifo_op); - writel(~0UL, sspi->base + sspi->regs->usp_int_en_clr); - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - writel(min((sspi->left_tx_word * sspi->word_width), - sspi->fifo_size), - sspi->base + sspi->regs->tx_dma_io_len); - writel(min((sspi->left_rx_word * sspi->word_width), - sspi->fifo_size), - sspi->base + sspi->regs->rx_dma_io_len); - break; - case SIRF_REAL_SPI: - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->txfifo_op); - writel(0, sspi->base + sspi->regs->int_en); - writel(readl(sspi->base + sspi->regs->int_st), - sspi->base + sspi->regs->int_st); - writel(readl(sspi->base + sspi->regs->spi_ctrl) | - SIRFSOC_SPI_MUL_DAT_MODE | - SIRFSOC_SPI_ENA_AUTO_CLR, - sspi->base + sspi->regs->spi_ctrl); - data_units = sspi->fifo_size / sspi->word_width; - writel(min(sspi->left_tx_word, data_units) - 1, - sspi->base + sspi->regs->tx_dma_io_len); - writel(min(sspi->left_rx_word, data_units) - 1, - sspi->base + sspi->regs->rx_dma_io_len); - break; - } - while (!((readl(sspi->base + sspi->regs->txfifo_st) - & SIRFSOC_SPI_FIFO_FULL_MASK(sspi))) && - sspi->left_tx_word) - sspi->tx_word(sspi); - writel(SIRFSOC_SPI_TXFIFO_EMPTY_INT_EN | - SIRFSOC_SPI_TX_UFLOW_INT_EN | - SIRFSOC_SPI_RX_OFLOW_INT_EN | - SIRFSOC_SPI_RX_IO_DMA_INT_EN, - sspi->base + sspi->regs->int_en); - writel(SIRFSOC_SPI_RX_EN | SIRFSOC_SPI_TX_EN, - sspi->base + sspi->regs->tx_rx_en); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) { - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_START, - sspi->base + sspi->regs->txfifo_op); - } - if (!wait_for_completion_timeout(&sspi->tx_done, timeout) || - !wait_for_completion_timeout(&sspi->rx_done, timeout)) { - dev_err(&spi->dev, "transfer timeout\n"); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) - writel(0, sspi->base + sspi->regs->tx_rx_en); - break; - } - while (!((readl(sspi->base + sspi->regs->rxfifo_st) - & SIRFSOC_SPI_FIFO_EMPTY_MASK(sspi))) && - sspi->left_rx_word) - sspi->rx_word(sspi); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) - writel(0, sspi->base + sspi->regs->tx_rx_en); - writel(0, sspi->base + sspi->regs->rxfifo_op); - writel(0, sspi->base + sspi->regs->txfifo_op); - } while (sspi->left_tx_word != 0 || sspi->left_rx_word != 0); -} - -static int spi_sirfsoc_transfer(struct spi_device *spi, struct spi_transfer *t) -{ - struct sirfsoc_spi *sspi; - - sspi = spi_master_get_devdata(spi->master); - sspi->tx = t->tx_buf; - sspi->rx = t->rx_buf; - sspi->left_tx_word = sspi->left_rx_word = t->len / sspi->word_width; - reinit_completion(&sspi->rx_done); - reinit_completion(&sspi->tx_done); - /* - * in the transfer, if transfer data using command register with rx_buf - * null, just fill command data into command register and wait for its - * completion. - */ - if (sspi->type == SIRF_REAL_SPI && sspi->tx_by_cmd) - spi_sirfsoc_cmd_transfer(spi, t); - else if (IS_DMA_VALID(t)) - spi_sirfsoc_dma_transfer(spi, t); - else - spi_sirfsoc_pio_transfer(spi, t); - - return t->len - sspi->left_rx_word * sspi->word_width; -} - -static void spi_sirfsoc_chipselect(struct spi_device *spi, int value) -{ - struct sirfsoc_spi *sspi = spi_master_get_devdata(spi->master); - - if (sspi->hw_cs) { - u32 regval; - - switch (sspi->type) { - case SIRF_REAL_SPI: - regval = readl(sspi->base + sspi->regs->spi_ctrl); - switch (value) { - case BITBANG_CS_ACTIVE: - if (spi->mode & SPI_CS_HIGH) - regval |= SIRFSOC_SPI_CS_IO_OUT; - else - regval &= ~SIRFSOC_SPI_CS_IO_OUT; - break; - case BITBANG_CS_INACTIVE: - if (spi->mode & SPI_CS_HIGH) - regval &= ~SIRFSOC_SPI_CS_IO_OUT; - else - regval |= SIRFSOC_SPI_CS_IO_OUT; - break; - } - writel(regval, sspi->base + sspi->regs->spi_ctrl); - break; - case SIRF_USP_SPI_P2: - case SIRF_USP_SPI_A7: - regval = readl(sspi->base + - sspi->regs->usp_pin_io_data); - switch (value) { - case BITBANG_CS_ACTIVE: - if (spi->mode & SPI_CS_HIGH) - regval |= SIRFSOC_USP_CS_HIGH_VALUE; - else - regval &= ~(SIRFSOC_USP_CS_HIGH_VALUE); - break; - case BITBANG_CS_INACTIVE: - if (spi->mode & SPI_CS_HIGH) - regval &= ~(SIRFSOC_USP_CS_HIGH_VALUE); - else - regval |= SIRFSOC_USP_CS_HIGH_VALUE; - break; - } - writel(regval, - sspi->base + sspi->regs->usp_pin_io_data); - break; - } - } else { - switch (value) { - case BITBANG_CS_ACTIVE: - gpio_direction_output(spi->cs_gpio, - spi->mode & SPI_CS_HIGH ? 1 : 0); - break; - case BITBANG_CS_INACTIVE: - gpio_direction_output(spi->cs_gpio, - spi->mode & SPI_CS_HIGH ? 0 : 1); - break; - } - } -} - -static int spi_sirfsoc_config_mode(struct spi_device *spi) -{ - struct sirfsoc_spi *sspi; - u32 regval, usp_mode1; - - sspi = spi_master_get_devdata(spi->master); - regval = readl(sspi->base + sspi->regs->spi_ctrl); - usp_mode1 = readl(sspi->base + sspi->regs->usp_mode1); - if (!(spi->mode & SPI_CS_HIGH)) { - regval |= SIRFSOC_SPI_CS_IDLE_STAT; - usp_mode1 &= ~SIRFSOC_USP_CS_HIGH_VALID; - } else { - regval &= ~SIRFSOC_SPI_CS_IDLE_STAT; - usp_mode1 |= SIRFSOC_USP_CS_HIGH_VALID; - } - if (!(spi->mode & SPI_LSB_FIRST)) { - regval |= SIRFSOC_SPI_TRAN_MSB; - usp_mode1 &= ~SIRFSOC_USP_LSB; - } else { - regval &= ~SIRFSOC_SPI_TRAN_MSB; - usp_mode1 |= SIRFSOC_USP_LSB; - } - if (spi->mode & SPI_CPOL) { - regval |= SIRFSOC_SPI_CLK_IDLE_STAT; - usp_mode1 |= SIRFSOC_USP_SCLK_IDLE_STAT; - } else { - regval &= ~SIRFSOC_SPI_CLK_IDLE_STAT; - usp_mode1 &= ~SIRFSOC_USP_SCLK_IDLE_STAT; - } - /* - * Data should be driven at least 1/2 cycle before the fetch edge - * to make sure that data gets stable at the fetch edge. - */ - if (((spi->mode & SPI_CPOL) && (spi->mode & SPI_CPHA)) || - (!(spi->mode & SPI_CPOL) && !(spi->mode & SPI_CPHA))) { - regval &= ~SIRFSOC_SPI_DRV_POS_EDGE; - usp_mode1 |= (SIRFSOC_USP_TXD_FALLING_EDGE | - SIRFSOC_USP_RXD_FALLING_EDGE); - } else { - regval |= SIRFSOC_SPI_DRV_POS_EDGE; - usp_mode1 &= ~(SIRFSOC_USP_RXD_FALLING_EDGE | - SIRFSOC_USP_TXD_FALLING_EDGE); - } - writel((SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, sspi->fifo_size - 2) << - SIRFSOC_SPI_FIFO_SC_OFFSET) | - (SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, sspi->fifo_size / 2) << - SIRFSOC_SPI_FIFO_LC_OFFSET) | - (SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, 2) << - SIRFSOC_SPI_FIFO_HC_OFFSET), - sspi->base + sspi->regs->txfifo_level_chk); - writel((SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, 2) << - SIRFSOC_SPI_FIFO_SC_OFFSET) | - (SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, sspi->fifo_size / 2) << - SIRFSOC_SPI_FIFO_LC_OFFSET) | - (SIRFSOC_SPI_FIFO_LEVEL_CHK_MASK(sspi, sspi->fifo_size - 2) << - SIRFSOC_SPI_FIFO_HC_OFFSET), - sspi->base + sspi->regs->rxfifo_level_chk); - /* - * it should never set to hardware cs mode because in hardware cs mode, - * cs signal can't controlled by driver. - */ - switch (sspi->type) { - case SIRF_REAL_SPI: - regval |= SIRFSOC_SPI_CS_IO_MODE; - writel(regval, sspi->base + sspi->regs->spi_ctrl); - break; - case SIRF_USP_SPI_P2: - case SIRF_USP_SPI_A7: - usp_mode1 |= SIRFSOC_USP_SYNC_MODE; - usp_mode1 |= SIRFSOC_USP_TFS_IO_MODE; - usp_mode1 &= ~SIRFSOC_USP_TFS_IO_INPUT; - writel(usp_mode1, sspi->base + sspi->regs->usp_mode1); - break; - } - - return 0; -} - -static int -spi_sirfsoc_setup_transfer(struct spi_device *spi, struct spi_transfer *t) -{ - struct sirfsoc_spi *sspi; - u8 bits_per_word = 0; - int hz = 0; - u32 regval, txfifo_ctrl, rxfifo_ctrl, tx_frm_ctl, rx_frm_ctl, usp_mode2; - - sspi = spi_master_get_devdata(spi->master); - - bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word; - hz = t && t->speed_hz ? t->speed_hz : spi->max_speed_hz; - - usp_mode2 = regval = (sspi->ctrl_freq / (2 * hz)) - 1; - if (regval > 0xFFFF || regval < 0) { - dev_err(&spi->dev, "Speed %d not supported\n", hz); - return -EINVAL; - } - switch (bits_per_word) { - case 8: - regval |= SIRFSOC_SPI_TRAN_DAT_FORMAT_8; - sspi->rx_word = spi_sirfsoc_rx_word_u8; - sspi->tx_word = spi_sirfsoc_tx_word_u8; - break; - case 12: - case 16: - regval |= (bits_per_word == 12) ? - SIRFSOC_SPI_TRAN_DAT_FORMAT_12 : - SIRFSOC_SPI_TRAN_DAT_FORMAT_16; - sspi->rx_word = spi_sirfsoc_rx_word_u16; - sspi->tx_word = spi_sirfsoc_tx_word_u16; - break; - case 32: - regval |= SIRFSOC_SPI_TRAN_DAT_FORMAT_32; - sspi->rx_word = spi_sirfsoc_rx_word_u32; - sspi->tx_word = spi_sirfsoc_tx_word_u32; - break; - default: - dev_err(&spi->dev, "bpw %d not supported\n", bits_per_word); - return -EINVAL; - } - sspi->word_width = DIV_ROUND_UP(bits_per_word, 8); - txfifo_ctrl = (((sspi->fifo_size / 2) & - SIRFSOC_SPI_FIFO_THD_MASK(sspi)) - << SIRFSOC_SPI_FIFO_THD_OFFSET) | - (sspi->word_width >> 1); - rxfifo_ctrl = (((sspi->fifo_size / 2) & - SIRFSOC_SPI_FIFO_THD_MASK(sspi)) - << SIRFSOC_SPI_FIFO_THD_OFFSET) | - (sspi->word_width >> 1); - writel(txfifo_ctrl, sspi->base + sspi->regs->txfifo_ctrl); - writel(rxfifo_ctrl, sspi->base + sspi->regs->rxfifo_ctrl); - if (sspi->type == SIRF_USP_SPI_P2 || - sspi->type == SIRF_USP_SPI_A7) { - tx_frm_ctl = 0; - tx_frm_ctl |= ((bits_per_word - 1) & SIRFSOC_USP_TX_DATA_MASK) - << SIRFSOC_USP_TX_DATA_OFFSET; - tx_frm_ctl |= ((bits_per_word + 1 + SIRFSOC_USP_TXD_DELAY_LEN - - 1) & SIRFSOC_USP_TX_SYNC_MASK) << - SIRFSOC_USP_TX_SYNC_OFFSET; - tx_frm_ctl |= ((bits_per_word + 1 + SIRFSOC_USP_TXD_DELAY_LEN - + 2 - 1) & SIRFSOC_USP_TX_FRAME_MASK) << - SIRFSOC_USP_TX_FRAME_OFFSET; - tx_frm_ctl |= ((bits_per_word - 1) & - SIRFSOC_USP_TX_SHIFTER_MASK) << - SIRFSOC_USP_TX_SHIFTER_OFFSET; - rx_frm_ctl = 0; - rx_frm_ctl |= ((bits_per_word - 1) & SIRFSOC_USP_RX_DATA_MASK) - << SIRFSOC_USP_RX_DATA_OFFSET; - rx_frm_ctl |= ((bits_per_word + 1 + SIRFSOC_USP_RXD_DELAY_LEN - + 2 - 1) & SIRFSOC_USP_RX_FRAME_MASK) << - SIRFSOC_USP_RX_FRAME_OFFSET; - rx_frm_ctl |= ((bits_per_word - 1) - & SIRFSOC_USP_RX_SHIFTER_MASK) << - SIRFSOC_USP_RX_SHIFTER_OFFSET; - writel(tx_frm_ctl | (((usp_mode2 >> 10) & - SIRFSOC_USP_CLK_10_11_MASK) << - SIRFSOC_USP_CLK_10_11_OFFSET), - sspi->base + sspi->regs->usp_tx_frame_ctrl); - writel(rx_frm_ctl | (((usp_mode2 >> 12) & - SIRFSOC_USP_CLK_12_15_MASK) << - SIRFSOC_USP_CLK_12_15_OFFSET), - sspi->base + sspi->regs->usp_rx_frame_ctrl); - writel(readl(sspi->base + sspi->regs->usp_mode2) | - ((usp_mode2 & SIRFSOC_USP_CLK_DIVISOR_MASK) << - SIRFSOC_USP_CLK_DIVISOR_OFFSET) | - (SIRFSOC_USP_RXD_DELAY_LEN << - SIRFSOC_USP_RXD_DELAY_OFFSET) | - (SIRFSOC_USP_TXD_DELAY_LEN << - SIRFSOC_USP_TXD_DELAY_OFFSET), - sspi->base + sspi->regs->usp_mode2); - } - if (sspi->type == SIRF_REAL_SPI) - writel(regval, sspi->base + sspi->regs->spi_ctrl); - spi_sirfsoc_config_mode(spi); - if (sspi->type == SIRF_REAL_SPI) { - if (t && t->tx_buf && !t->rx_buf && - (t->len <= SIRFSOC_MAX_CMD_BYTES)) { - sspi->tx_by_cmd = true; - writel(readl(sspi->base + sspi->regs->spi_ctrl) | - (SIRFSOC_SPI_CMD_BYTE_NUM((t->len - 1)) | - SIRFSOC_SPI_CMD_MODE), - sspi->base + sspi->regs->spi_ctrl); - } else { - sspi->tx_by_cmd = false; - writel(readl(sspi->base + sspi->regs->spi_ctrl) & - ~SIRFSOC_SPI_CMD_MODE, - sspi->base + sspi->regs->spi_ctrl); - } - } - if (IS_DMA_VALID(t)) { - /* Enable DMA mode for RX, TX */ - writel(0, sspi->base + sspi->regs->tx_dma_io_ctrl); - writel(SIRFSOC_SPI_RX_DMA_FLUSH, - sspi->base + sspi->regs->rx_dma_io_ctrl); - } else { - /* Enable IO mode for RX, TX */ - writel(SIRFSOC_SPI_IO_MODE_SEL, - sspi->base + sspi->regs->tx_dma_io_ctrl); - writel(SIRFSOC_SPI_IO_MODE_SEL, - sspi->base + sspi->regs->rx_dma_io_ctrl); - } - return 0; -} - -static int spi_sirfsoc_setup(struct spi_device *spi) -{ - struct sirfsoc_spi *sspi; - int ret = 0; - - sspi = spi_master_get_devdata(spi->master); - if (spi->cs_gpio == -ENOENT) - sspi->hw_cs = true; - else { - sspi->hw_cs = false; - if (!spi_get_ctldata(spi)) { - void *cs = kmalloc(sizeof(int), GFP_KERNEL); - if (!cs) { - ret = -ENOMEM; - goto exit; - } - ret = gpio_is_valid(spi->cs_gpio); - if (!ret) { - dev_err(&spi->dev, "no valid gpio\n"); - ret = -ENOENT; - goto exit; - } - ret = gpio_request(spi->cs_gpio, DRIVER_NAME); - if (ret) { - dev_err(&spi->dev, "failed to request gpio\n"); - goto exit; - } - spi_set_ctldata(spi, cs); - } - } - spi_sirfsoc_config_mode(spi); - spi_sirfsoc_chipselect(spi, BITBANG_CS_INACTIVE); -exit: - return ret; -} - -static void spi_sirfsoc_cleanup(struct spi_device *spi) -{ - if (spi_get_ctldata(spi)) { - gpio_free(spi->cs_gpio); - kfree(spi_get_ctldata(spi)); - } -} - -static const struct sirf_spi_comp_data sirf_real_spi = { - .regs = &real_spi_register, - .type = SIRF_REAL_SPI, - .dat_max_frm_len = 64 * 1024, - .fifo_size = 256, -}; - -static const struct sirf_spi_comp_data sirf_usp_spi_p2 = { - .regs = &usp_spi_register, - .type = SIRF_USP_SPI_P2, - .dat_max_frm_len = 1024 * 1024, - .fifo_size = 128, - .hwinit = sirfsoc_usp_hwinit, -}; - -static const struct sirf_spi_comp_data sirf_usp_spi_a7 = { - .regs = &usp_spi_register, - .type = SIRF_USP_SPI_A7, - .dat_max_frm_len = 1024 * 1024, - .fifo_size = 512, - .hwinit = sirfsoc_usp_hwinit, -}; - -static const struct of_device_id spi_sirfsoc_of_match[] = { - { .compatible = "sirf,prima2-spi", .data = &sirf_real_spi}, - { .compatible = "sirf,prima2-usp-spi", .data = &sirf_usp_spi_p2}, - { .compatible = "sirf,atlas7-usp-spi", .data = &sirf_usp_spi_a7}, - {} -}; -MODULE_DEVICE_TABLE(of, spi_sirfsoc_of_match); - -static int spi_sirfsoc_probe(struct platform_device *pdev) -{ - struct sirfsoc_spi *sspi; - struct spi_master *master; - const struct sirf_spi_comp_data *spi_comp_data; - int irq; - int ret; - const struct of_device_id *match; - - ret = device_reset(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "SPI reset failed!\n"); - return ret; - } - - master = spi_alloc_master(&pdev->dev, sizeof(*sspi)); - if (!master) { - dev_err(&pdev->dev, "Unable to allocate SPI master\n"); - return -ENOMEM; - } - match = of_match_node(spi_sirfsoc_of_match, pdev->dev.of_node); - platform_set_drvdata(pdev, master); - sspi = spi_master_get_devdata(master); - sspi->fifo_full_offset = ilog2(sspi->fifo_size); - spi_comp_data = match->data; - sspi->regs = spi_comp_data->regs; - sspi->type = spi_comp_data->type; - sspi->fifo_level_chk_mask = (sspi->fifo_size / 4) - 1; - sspi->dat_max_frm_len = spi_comp_data->dat_max_frm_len; - sspi->fifo_size = spi_comp_data->fifo_size; - sspi->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(sspi->base)) { - ret = PTR_ERR(sspi->base); - goto free_master; - } - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - ret = -ENXIO; - goto free_master; - } - ret = devm_request_irq(&pdev->dev, irq, spi_sirfsoc_irq, 0, - DRIVER_NAME, sspi); - if (ret) - goto free_master; - - sspi->bitbang.master = master; - sspi->bitbang.chipselect = spi_sirfsoc_chipselect; - sspi->bitbang.setup_transfer = spi_sirfsoc_setup_transfer; - sspi->bitbang.txrx_bufs = spi_sirfsoc_transfer; - sspi->bitbang.master->setup = spi_sirfsoc_setup; - sspi->bitbang.master->cleanup = spi_sirfsoc_cleanup; - master->bus_num = pdev->id; - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_CS_HIGH; - master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(12) | - SPI_BPW_MASK(16) | SPI_BPW_MASK(32); - master->max_speed_hz = SIRFSOC_SPI_DEFAULT_FRQ; - master->flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX; - sspi->bitbang.master->dev.of_node = pdev->dev.of_node; - - /* request DMA channels */ - sspi->rx_chan = dma_request_chan(&pdev->dev, "rx"); - if (IS_ERR(sspi->rx_chan)) { - dev_err(&pdev->dev, "can not allocate rx dma channel\n"); - ret = PTR_ERR(sspi->rx_chan); - goto free_master; - } - sspi->tx_chan = dma_request_chan(&pdev->dev, "tx"); - if (IS_ERR(sspi->tx_chan)) { - dev_err(&pdev->dev, "can not allocate tx dma channel\n"); - ret = PTR_ERR(sspi->tx_chan); - goto free_rx_dma; - } - - sspi->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(sspi->clk)) { - ret = PTR_ERR(sspi->clk); - goto free_tx_dma; - } - clk_prepare_enable(sspi->clk); - if (spi_comp_data->hwinit) - spi_comp_data->hwinit(sspi); - sspi->ctrl_freq = clk_get_rate(sspi->clk); - - init_completion(&sspi->rx_done); - init_completion(&sspi->tx_done); - - ret = spi_bitbang_start(&sspi->bitbang); - if (ret) - goto free_clk; - dev_info(&pdev->dev, "registered, bus number = %d\n", master->bus_num); - - return 0; -free_clk: - clk_disable_unprepare(sspi->clk); - clk_put(sspi->clk); -free_tx_dma: - dma_release_channel(sspi->tx_chan); -free_rx_dma: - dma_release_channel(sspi->rx_chan); -free_master: - spi_master_put(master); - - return ret; -} - -static int spi_sirfsoc_remove(struct platform_device *pdev) -{ - struct spi_master *master; - struct sirfsoc_spi *sspi; - - master = platform_get_drvdata(pdev); - sspi = spi_master_get_devdata(master); - spi_bitbang_stop(&sspi->bitbang); - clk_disable_unprepare(sspi->clk); - clk_put(sspi->clk); - dma_release_channel(sspi->rx_chan); - dma_release_channel(sspi->tx_chan); - spi_master_put(master); - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int spi_sirfsoc_suspend(struct device *dev) -{ - struct spi_master *master = dev_get_drvdata(dev); - struct sirfsoc_spi *sspi = spi_master_get_devdata(master); - int ret; - - ret = spi_master_suspend(master); - if (ret) - return ret; - - clk_disable(sspi->clk); - return 0; -} - -static int spi_sirfsoc_resume(struct device *dev) -{ - struct spi_master *master = dev_get_drvdata(dev); - struct sirfsoc_spi *sspi = spi_master_get_devdata(master); - - clk_enable(sspi->clk); - writel(SIRFSOC_SPI_FIFO_RESET, sspi->base + sspi->regs->txfifo_op); - writel(SIRFSOC_SPI_FIFO_RESET, sspi->base + sspi->regs->rxfifo_op); - writel(SIRFSOC_SPI_FIFO_START, sspi->base + sspi->regs->txfifo_op); - writel(SIRFSOC_SPI_FIFO_START, sspi->base + sspi->regs->rxfifo_op); - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(spi_sirfsoc_pm_ops, spi_sirfsoc_suspend, - spi_sirfsoc_resume); - -static struct platform_driver spi_sirfsoc_driver = { - .driver = { - .name = DRIVER_NAME, - .pm = &spi_sirfsoc_pm_ops, - .of_match_table = spi_sirfsoc_of_match, - }, - .probe = spi_sirfsoc_probe, - .remove = spi_sirfsoc_remove, -}; -module_platform_driver(spi_sirfsoc_driver); -MODULE_DESCRIPTION("SiRF SoC SPI master driver"); -MODULE_AUTHOR("Zhiwu Song "); -MODULE_AUTHOR("Barry Song "); -MODULE_AUTHOR("Qipan Li "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index 6017209c6d2f..25c076461011 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -5,6 +5,7 @@ // Copyright (C) 2017, STMicroelectronics - All Rights Reserved // Author(s): Amelie Delaunay for STMicroelectronics. +#include #include #include #include @@ -94,27 +95,22 @@ #define STM32H7_SPI_CR1_SSI BIT(12) /* STM32H7_SPI_CR2 bit fields */ -#define STM32H7_SPI_CR2_TSIZE_SHIFT 0 #define STM32H7_SPI_CR2_TSIZE GENMASK(15, 0) +#define STM32H7_SPI_TSIZE_MAX GENMASK(15, 0) /* STM32H7_SPI_CFG1 bit fields */ -#define STM32H7_SPI_CFG1_DSIZE_SHIFT 0 #define STM32H7_SPI_CFG1_DSIZE GENMASK(4, 0) -#define STM32H7_SPI_CFG1_FTHLV_SHIFT 5 #define STM32H7_SPI_CFG1_FTHLV GENMASK(8, 5) #define STM32H7_SPI_CFG1_RXDMAEN BIT(14) #define STM32H7_SPI_CFG1_TXDMAEN BIT(15) -#define STM32H7_SPI_CFG1_MBR_SHIFT 28 #define STM32H7_SPI_CFG1_MBR GENMASK(30, 28) +#define STM32H7_SPI_CFG1_MBR_SHIFT 28 #define STM32H7_SPI_CFG1_MBR_MIN 0 #define STM32H7_SPI_CFG1_MBR_MAX (GENMASK(30, 28) >> 28) /* STM32H7_SPI_CFG2 bit fields */ -#define STM32H7_SPI_CFG2_MIDI_SHIFT 4 #define STM32H7_SPI_CFG2_MIDI GENMASK(7, 4) -#define STM32H7_SPI_CFG2_COMM_SHIFT 17 #define STM32H7_SPI_CFG2_COMM GENMASK(18, 17) -#define STM32H7_SPI_CFG2_SP_SHIFT 19 #define STM32H7_SPI_CFG2_SP GENMASK(21, 19) #define STM32H7_SPI_CFG2_MASTER BIT(22) #define STM32H7_SPI_CFG2_LSBFRST BIT(23) @@ -140,7 +136,6 @@ #define STM32H7_SPI_SR_OVR BIT(6) #define STM32H7_SPI_SR_MODF BIT(9) #define STM32H7_SPI_SR_SUSP BIT(11) -#define STM32H7_SPI_SR_RXPLVL_SHIFT 13 #define STM32H7_SPI_SR_RXPLVL GENMASK(14, 13) #define STM32H7_SPI_SR_RXWNE BIT(15) @@ -167,8 +162,6 @@ #define SPI_3WIRE_TX 3 #define SPI_3WIRE_RX 4 -#define SPI_1HZ_NS 1000000000 - /* * use PIO for small transfers, avoiding DMA setup/teardown overhead for drivers * without fifo buffers. @@ -268,7 +261,6 @@ struct stm32_spi_cfg { * @base: virtual memory area * @clk: hw kernel clock feeding the SPI clock generator * @clk_rate: rate of the hw kernel clock feeding the SPI clock generator - * @rst: SPI controller reset line * @lock: prevent I/O concurrent access * @irq: SPI controller interrupt line * @fifo_size: size of the embedded fifo in bytes @@ -294,7 +286,6 @@ struct stm32_spi { void __iomem *base; struct clk *clk; u32 clk_rate; - struct reset_control *rst; spinlock_t lock; /* prevent I/O concurrent access */ int irq; unsigned int fifo_size; @@ -417,9 +408,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi) stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE); cfg1 = readl_relaxed(spi->base + STM32H7_SPI_CFG1); - max_bpw = (cfg1 & STM32H7_SPI_CFG1_DSIZE) >> - STM32H7_SPI_CFG1_DSIZE_SHIFT; - max_bpw += 1; + max_bpw = FIELD_GET(STM32H7_SPI_CFG1_DSIZE, cfg1) + 1; spin_unlock_irqrestore(&spi->lock, flags); @@ -473,34 +462,14 @@ static int stm32_spi_prepare_mbr(struct stm32_spi *spi, u32 speed_hz, */ static u32 stm32h7_spi_prepare_fthlv(struct stm32_spi *spi, u32 xfer_len) { - u32 fthlv, half_fifo, packet; + u32 packet, bpw; /* data packet should not exceed 1/2 of fifo space */ - half_fifo = (spi->fifo_size / 2); - - /* data_packet should not exceed transfer length */ - if (half_fifo > xfer_len) - packet = xfer_len; - else - packet = half_fifo; - - if (spi->cur_bpw <= 8) - fthlv = packet; - else if (spi->cur_bpw <= 16) - fthlv = packet / 2; - else - fthlv = packet / 4; + packet = clamp(xfer_len, 1U, spi->fifo_size / 2); /* align packet size with data registers access */ - if (spi->cur_bpw > 8) - fthlv += (fthlv % 2) ? 1 : 0; - else - fthlv += (fthlv % 4) ? (4 - (fthlv % 4)) : 0; - - if (!fthlv) - fthlv = 1; - - return fthlv; + bpw = DIV_ROUND_UP(spi->cur_bpw, 8); + return DIV_ROUND_UP(packet, bpw); } /** @@ -607,8 +576,7 @@ static void stm32f4_spi_read_rx(struct stm32_spi *spi) static void stm32h7_spi_read_rxfifo(struct stm32_spi *spi, bool flush) { u32 sr = readl_relaxed(spi->base + STM32H7_SPI_SR); - u32 rxplvl = (sr & STM32H7_SPI_SR_RXPLVL) >> - STM32H7_SPI_SR_RXPLVL_SHIFT; + u32 rxplvl = FIELD_GET(STM32H7_SPI_SR_RXPLVL, sr); while ((spi->rx_len > 0) && ((sr & STM32H7_SPI_SR_RXP) || @@ -635,8 +603,7 @@ static void stm32h7_spi_read_rxfifo(struct stm32_spi *spi, bool flush) } sr = readl_relaxed(spi->base + STM32H7_SPI_SR); - rxplvl = (sr & STM32H7_SPI_SR_RXPLVL) >> - STM32H7_SPI_SR_RXPLVL_SHIFT; + rxplvl = FIELD_GET(STM32H7_SPI_SR_RXPLVL, sr); } dev_dbg(spi->dev, "%s%s: %d bytes left\n", __func__, @@ -928,8 +895,8 @@ static irqreturn_t stm32h7_spi_irq_thread(int irq, void *dev_id) mask |= STM32H7_SPI_SR_RXP; if (!(sr & mask)) { - dev_dbg(spi->dev, "spurious IT (sr=0x%08x, ier=0x%08x)\n", - sr, ier); + dev_warn(spi->dev, "spurious IT (sr=0x%08x, ier=0x%08x)\n", + sr, ier); spin_unlock_irqrestore(&spi->lock, flags); return IRQ_NONE; } @@ -956,15 +923,8 @@ static irqreturn_t stm32h7_spi_irq_thread(int irq, void *dev_id) } if (sr & STM32H7_SPI_SR_OVR) { - dev_warn(spi->dev, "Overrun: received value discarded\n"); - if (!spi->cur_usedma && (spi->rx_buf && (spi->rx_len > 0))) - stm32h7_spi_read_rxfifo(spi, false); - /* - * If overrun is detected while using DMA, it means that - * something went wrong, so stop the current transfer - */ - if (spi->cur_usedma) - end = true; + dev_err(spi->dev, "Overrun: RX data lost\n"); + end = true; } if (sr & STM32H7_SPI_SR_EOT) { @@ -1028,10 +988,24 @@ static int stm32_spi_prepare_msg(struct spi_master *master, clrb |= spi->cfg->regs->lsb_first.mask; dev_dbg(spi->dev, "cpol=%d cpha=%d lsb_first=%d cs_high=%d\n", - spi_dev->mode & SPI_CPOL, - spi_dev->mode & SPI_CPHA, - spi_dev->mode & SPI_LSB_FIRST, - spi_dev->mode & SPI_CS_HIGH); + !!(spi_dev->mode & SPI_CPOL), + !!(spi_dev->mode & SPI_CPHA), + !!(spi_dev->mode & SPI_LSB_FIRST), + !!(spi_dev->mode & SPI_CS_HIGH)); + + /* On STM32H7, messages should not exceed a maximum size setted + * afterward via the set_number_of_data function. In order to + * ensure that, split large messages into several messages + */ + if (spi->cfg->set_number_of_data) { + int ret; + + ret = spi_split_transfers_maxsize(master, msg, + STM32H7_SPI_TSIZE_MAX, + GFP_KERNEL | GFP_DMA); + if (ret) + return ret; + } spin_lock_irqsave(&spi->lock, flags); @@ -1405,15 +1379,13 @@ static void stm32h7_spi_set_bpw(struct stm32_spi *spi) bpw = spi->cur_bpw - 1; cfg1_clrb |= STM32H7_SPI_CFG1_DSIZE; - cfg1_setb |= (bpw << STM32H7_SPI_CFG1_DSIZE_SHIFT) & - STM32H7_SPI_CFG1_DSIZE; + cfg1_setb |= FIELD_PREP(STM32H7_SPI_CFG1_DSIZE, bpw); spi->cur_fthlv = stm32h7_spi_prepare_fthlv(spi, spi->cur_xferlen); fthlv = spi->cur_fthlv - 1; cfg1_clrb |= STM32H7_SPI_CFG1_FTHLV; - cfg1_setb |= (fthlv << STM32H7_SPI_CFG1_FTHLV_SHIFT) & - STM32H7_SPI_CFG1_FTHLV; + cfg1_setb |= FIELD_PREP(STM32H7_SPI_CFG1_FTHLV, fthlv); writel_relaxed( (readl_relaxed(spi->base + STM32H7_SPI_CFG1) & @@ -1431,8 +1403,7 @@ static void stm32_spi_set_mbr(struct stm32_spi *spi, u32 mbrdiv) u32 clrb = 0, setb = 0; clrb |= spi->cfg->regs->br.mask; - setb |= ((u32)mbrdiv << spi->cfg->regs->br.shift) & - spi->cfg->regs->br.mask; + setb |= (mbrdiv << spi->cfg->regs->br.shift) & spi->cfg->regs->br.mask; writel_relaxed((readl_relaxed(spi->base + spi->cfg->regs->br.reg) & ~clrb) | setb, @@ -1523,8 +1494,7 @@ static int stm32h7_spi_set_mode(struct stm32_spi *spi, unsigned int comm_type) } cfg2_clrb |= STM32H7_SPI_CFG2_COMM; - cfg2_setb |= (mode << STM32H7_SPI_CFG2_COMM_SHIFT) & - STM32H7_SPI_CFG2_COMM; + cfg2_setb |= FIELD_PREP(STM32H7_SPI_CFG2_COMM, mode); writel_relaxed( (readl_relaxed(spi->base + STM32H7_SPI_CFG2) & @@ -1546,15 +1516,16 @@ static void stm32h7_spi_data_idleness(struct stm32_spi *spi, u32 len) cfg2_clrb |= STM32H7_SPI_CFG2_MIDI; if ((len > 1) && (spi->cur_midi > 0)) { - u32 sck_period_ns = DIV_ROUND_UP(SPI_1HZ_NS, spi->cur_speed); - u32 midi = min((u32)DIV_ROUND_UP(spi->cur_midi, sck_period_ns), - (u32)STM32H7_SPI_CFG2_MIDI >> - STM32H7_SPI_CFG2_MIDI_SHIFT); + u32 sck_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, spi->cur_speed); + u32 midi = min_t(u32, + DIV_ROUND_UP(spi->cur_midi, sck_period_ns), + FIELD_GET(STM32H7_SPI_CFG2_MIDI, + STM32H7_SPI_CFG2_MIDI)); + dev_dbg(spi->dev, "period=%dns, midi=%d(=%dns)\n", sck_period_ns, midi, midi * sck_period_ns); - cfg2_setb |= (midi << STM32H7_SPI_CFG2_MIDI_SHIFT) & - STM32H7_SPI_CFG2_MIDI; + cfg2_setb |= FIELD_PREP(STM32H7_SPI_CFG2_MIDI, midi); } writel_relaxed((readl_relaxed(spi->base + STM32H7_SPI_CFG2) & @@ -1569,14 +1540,8 @@ static void stm32h7_spi_data_idleness(struct stm32_spi *spi, u32 len) */ static int stm32h7_spi_number_of_data(struct stm32_spi *spi, u32 nb_words) { - u32 cr2_clrb = 0, cr2_setb = 0; - - if (nb_words <= (STM32H7_SPI_CR2_TSIZE >> - STM32H7_SPI_CR2_TSIZE_SHIFT)) { - cr2_clrb |= STM32H7_SPI_CR2_TSIZE; - cr2_setb = nb_words << STM32H7_SPI_CR2_TSIZE_SHIFT; - writel_relaxed((readl_relaxed(spi->base + STM32H7_SPI_CR2) & - ~cr2_clrb) | cr2_setb, + if (nb_words <= STM32H7_SPI_TSIZE_MAX) { + writel_relaxed(FIELD_PREP(STM32H7_SPI_CR2_TSIZE, nb_words), spi->base + STM32H7_SPI_CR2); } else { return -EMSGSIZE; @@ -1677,6 +1642,10 @@ static int stm32_spi_transfer_one(struct spi_master *master, struct stm32_spi *spi = spi_master_get_devdata(master); int ret; + /* Don't do anything on 0 bytes transfers */ + if (transfer->len == 0) + return 0; + spi->tx_buf = transfer->tx_buf; spi->rx_buf = transfer->rx_buf; spi->tx_len = spi->tx_buf ? transfer->len : 0; @@ -1831,6 +1800,7 @@ static int stm32_spi_probe(struct platform_device *pdev) struct spi_master *master; struct stm32_spi *spi; struct resource *res; + struct reset_control *rst; int ret; master = spi_alloc_master(&pdev->dev, sizeof(struct stm32_spi)); @@ -1892,11 +1862,17 @@ static int stm32_spi_probe(struct platform_device *pdev) goto err_clk_disable; } - spi->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); - if (!IS_ERR(spi->rst)) { - reset_control_assert(spi->rst); + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (rst) { + if (IS_ERR(rst)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(rst), + "failed to get reset\n"); + goto err_clk_disable; + } + + reset_control_assert(rst); udelay(2); - reset_control_deassert(spi->rst); + reset_control_deassert(rst); } if (spi->cfg->has_fifo) @@ -1960,12 +1936,6 @@ static int stm32_spi_probe(struct platform_device *pdev) goto err_pm_disable; } - if (!master->cs_gpiods) { - dev_err(&pdev->dev, "no CS gpios available\n"); - ret = -EINVAL; - goto err_pm_disable; - } - dev_info(&pdev->dev, "driver initialized\n"); return 0; diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c index 8cdca6ab8098..ea706d9629cb 100644 --- a/drivers/spi/spi-synquacer.c +++ b/drivers/spi/spi-synquacer.c @@ -490,6 +490,10 @@ static void synquacer_spi_set_cs(struct spi_device *spi, bool enable) val &= ~(SYNQUACER_HSSPI_DMPSEL_CS_MASK << SYNQUACER_HSSPI_DMPSEL_CS_SHIFT); val |= spi->chip_select << SYNQUACER_HSSPI_DMPSEL_CS_SHIFT; + + if (!enable) + val |= SYNQUACER_HSSPI_DMSTOP_STOP; + writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); } diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c new file mode 100644 index 000000000000..2f806f4b2c34 --- /dev/null +++ b/drivers/spi/spi-tegra210-quad.c @@ -0,0 +1,1410 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 NVIDIA CORPORATION. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QSPI_COMMAND1 0x000 +#define QSPI_BIT_LENGTH(x) (((x) & 0x1f) << 0) +#define QSPI_PACKED BIT(5) +#define QSPI_INTERFACE_WIDTH_MASK (0x03 << 7) +#define QSPI_INTERFACE_WIDTH(x) (((x) & 0x03) << 7) +#define QSPI_INTERFACE_WIDTH_SINGLE QSPI_INTERFACE_WIDTH(0) +#define QSPI_INTERFACE_WIDTH_DUAL QSPI_INTERFACE_WIDTH(1) +#define QSPI_INTERFACE_WIDTH_QUAD QSPI_INTERFACE_WIDTH(2) +#define QSPI_SDR_DDR_SEL BIT(9) +#define QSPI_TX_EN BIT(11) +#define QSPI_RX_EN BIT(12) +#define QSPI_CS_SW_VAL BIT(20) +#define QSPI_CS_SW_HW BIT(21) +#define QSPI_CONTROL_MODE_0 (0 << 28) +#define QSPI_CONTROL_MODE_3 (3 << 28) +#define QSPI_CONTROL_MODE_MASK (3 << 28) +#define QSPI_M_S BIT(30) +#define QSPI_PIO BIT(31) + +#define QSPI_COMMAND2 0x004 +#define QSPI_TX_TAP_DELAY(x) (((x) & 0x3f) << 10) +#define QSPI_RX_TAP_DELAY(x) (((x) & 0xff) << 0) + +#define QSPI_CS_TIMING1 0x008 +#define QSPI_SETUP_HOLD(setup, hold) (((setup) << 4) | (hold)) + +#define QSPI_CS_TIMING2 0x00c +#define CYCLES_BETWEEN_PACKETS_0(x) (((x) & 0x1f) << 0) +#define CS_ACTIVE_BETWEEN_PACKETS_0 BIT(5) + +#define QSPI_TRANS_STATUS 0x010 +#define QSPI_BLK_CNT(val) (((val) >> 0) & 0xffff) +#define QSPI_RDY BIT(30) + +#define QSPI_FIFO_STATUS 0x014 +#define QSPI_RX_FIFO_EMPTY BIT(0) +#define QSPI_RX_FIFO_FULL BIT(1) +#define QSPI_TX_FIFO_EMPTY BIT(2) +#define QSPI_TX_FIFO_FULL BIT(3) +#define QSPI_RX_FIFO_UNF BIT(4) +#define QSPI_RX_FIFO_OVF BIT(5) +#define QSPI_TX_FIFO_UNF BIT(6) +#define QSPI_TX_FIFO_OVF BIT(7) +#define QSPI_ERR BIT(8) +#define QSPI_TX_FIFO_FLUSH BIT(14) +#define QSPI_RX_FIFO_FLUSH BIT(15) +#define QSPI_TX_FIFO_EMPTY_COUNT(val) (((val) >> 16) & 0x7f) +#define QSPI_RX_FIFO_FULL_COUNT(val) (((val) >> 23) & 0x7f) + +#define QSPI_FIFO_ERROR (QSPI_RX_FIFO_UNF | \ + QSPI_RX_FIFO_OVF | \ + QSPI_TX_FIFO_UNF | \ + QSPI_TX_FIFO_OVF) +#define QSPI_FIFO_EMPTY (QSPI_RX_FIFO_EMPTY | \ + QSPI_TX_FIFO_EMPTY) + +#define QSPI_TX_DATA 0x018 +#define QSPI_RX_DATA 0x01c + +#define QSPI_DMA_CTL 0x020 +#define QSPI_TX_TRIG(n) (((n) & 0x3) << 15) +#define QSPI_TX_TRIG_1 QSPI_TX_TRIG(0) +#define QSPI_TX_TRIG_4 QSPI_TX_TRIG(1) +#define QSPI_TX_TRIG_8 QSPI_TX_TRIG(2) +#define QSPI_TX_TRIG_16 QSPI_TX_TRIG(3) + +#define QSPI_RX_TRIG(n) (((n) & 0x3) << 19) +#define QSPI_RX_TRIG_1 QSPI_RX_TRIG(0) +#define QSPI_RX_TRIG_4 QSPI_RX_TRIG(1) +#define QSPI_RX_TRIG_8 QSPI_RX_TRIG(2) +#define QSPI_RX_TRIG_16 QSPI_RX_TRIG(3) + +#define QSPI_DMA_EN BIT(31) + +#define QSPI_DMA_BLK 0x024 +#define QSPI_DMA_BLK_SET(x) (((x) & 0xffff) << 0) + +#define QSPI_TX_FIFO 0x108 +#define QSPI_RX_FIFO 0x188 + +#define QSPI_FIFO_DEPTH 64 + +#define QSPI_INTR_MASK 0x18c +#define QSPI_INTR_RX_FIFO_UNF_MASK BIT(25) +#define QSPI_INTR_RX_FIFO_OVF_MASK BIT(26) +#define QSPI_INTR_TX_FIFO_UNF_MASK BIT(27) +#define QSPI_INTR_TX_FIFO_OVF_MASK BIT(28) +#define QSPI_INTR_RDY_MASK BIT(29) +#define QSPI_INTR_RX_TX_FIFO_ERR (QSPI_INTR_RX_FIFO_UNF_MASK | \ + QSPI_INTR_RX_FIFO_OVF_MASK | \ + QSPI_INTR_TX_FIFO_UNF_MASK | \ + QSPI_INTR_TX_FIFO_OVF_MASK) + +#define QSPI_MISC_REG 0x194 +#define QSPI_NUM_DUMMY_CYCLE(x) (((x) & 0xff) << 0) +#define QSPI_DUMMY_CYCLES_MAX 0xff + +#define DATA_DIR_TX BIT(0) +#define DATA_DIR_RX BIT(1) + +#define QSPI_DMA_TIMEOUT (msecs_to_jiffies(1000)) +#define DEFAULT_QSPI_DMA_BUF_LEN (64 * 1024) + +struct tegra_qspi_client_data { + int tx_clk_tap_delay; + int rx_clk_tap_delay; +}; + +struct tegra_qspi { + struct device *dev; + struct spi_master *master; + /* lock to protect data accessed by irq */ + spinlock_t lock; + + struct clk *clk; + struct reset_control *rst; + void __iomem *base; + phys_addr_t phys; + unsigned int irq; + + u32 cur_speed; + unsigned int cur_pos; + unsigned int words_per_32bit; + unsigned int bytes_per_word; + unsigned int curr_dma_words; + unsigned int cur_direction; + + unsigned int cur_rx_pos; + unsigned int cur_tx_pos; + + unsigned int dma_buf_size; + unsigned int max_buf_size; + bool is_curr_dma_xfer; + + struct completion rx_dma_complete; + struct completion tx_dma_complete; + + u32 tx_status; + u32 rx_status; + u32 status_reg; + bool is_packed; + bool use_dma; + + u32 command1_reg; + u32 dma_control_reg; + u32 def_command1_reg; + u32 def_command2_reg; + u32 spi_cs_timing1; + u32 spi_cs_timing2; + u8 dummy_cycles; + + struct completion xfer_completion; + struct spi_transfer *curr_xfer; + + struct dma_chan *rx_dma_chan; + u32 *rx_dma_buf; + dma_addr_t rx_dma_phys; + struct dma_async_tx_descriptor *rx_dma_desc; + + struct dma_chan *tx_dma_chan; + u32 *tx_dma_buf; + dma_addr_t tx_dma_phys; + struct dma_async_tx_descriptor *tx_dma_desc; +}; + +static inline u32 tegra_qspi_readl(struct tegra_qspi *tqspi, unsigned long offset) +{ + return readl(tqspi->base + offset); +} + +static inline void tegra_qspi_writel(struct tegra_qspi *tqspi, u32 value, unsigned long offset) +{ + writel(value, tqspi->base + offset); + + /* read back register to make sure that register writes completed */ + if (offset != QSPI_TX_FIFO) + readl(tqspi->base + QSPI_COMMAND1); +} + +static void tegra_qspi_mask_clear_irq(struct tegra_qspi *tqspi) +{ + u32 value; + + /* write 1 to clear status register */ + value = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + tegra_qspi_writel(tqspi, value, QSPI_TRANS_STATUS); + + value = tegra_qspi_readl(tqspi, QSPI_INTR_MASK); + if (!(value & QSPI_INTR_RDY_MASK)) { + value |= (QSPI_INTR_RDY_MASK | QSPI_INTR_RX_TX_FIFO_ERR); + tegra_qspi_writel(tqspi, value, QSPI_INTR_MASK); + } + + /* clear fifo status error if any */ + value = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + if (value & QSPI_ERR) + tegra_qspi_writel(tqspi, QSPI_ERR | QSPI_FIFO_ERROR, QSPI_FIFO_STATUS); +} + +static unsigned int +tegra_qspi_calculate_curr_xfer_param(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + unsigned int max_word, max_len, total_fifo_words; + unsigned int remain_len = t->len - tqspi->cur_pos; + unsigned int bits_per_word = t->bits_per_word; + + tqspi->bytes_per_word = DIV_ROUND_UP(bits_per_word, 8); + + /* + * Tegra QSPI controller supports packed or unpacked mode transfers. + * Packed mode is used for data transfers using 8, 16, or 32 bits per + * word with a minimum transfer of 1 word and for all other transfers + * unpacked mode will be used. + */ + + if ((bits_per_word == 8 || bits_per_word == 16 || + bits_per_word == 32) && t->len > 3) { + tqspi->is_packed = true; + tqspi->words_per_32bit = 32 / bits_per_word; + } else { + tqspi->is_packed = false; + tqspi->words_per_32bit = 1; + } + + if (tqspi->is_packed) { + max_len = min(remain_len, tqspi->max_buf_size); + tqspi->curr_dma_words = max_len / tqspi->bytes_per_word; + total_fifo_words = (max_len + 3) / 4; + } else { + max_word = (remain_len - 1) / tqspi->bytes_per_word + 1; + max_word = min(max_word, tqspi->max_buf_size / 4); + tqspi->curr_dma_words = max_word; + total_fifo_words = max_word; + } + + return total_fifo_words; +} + +static unsigned int +tegra_qspi_fill_tx_fifo_from_client_txbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + unsigned int written_words, fifo_words_left, count; + unsigned int len, tx_empty_count, max_n_32bit, i; + u8 *tx_buf = (u8 *)t->tx_buf + tqspi->cur_tx_pos; + u32 fifo_status; + + fifo_status = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + tx_empty_count = QSPI_TX_FIFO_EMPTY_COUNT(fifo_status); + + if (tqspi->is_packed) { + fifo_words_left = tx_empty_count * tqspi->words_per_32bit; + written_words = min(fifo_words_left, tqspi->curr_dma_words); + len = written_words * tqspi->bytes_per_word; + max_n_32bit = DIV_ROUND_UP(len, 4); + for (count = 0; count < max_n_32bit; count++) { + u32 x = 0; + + for (i = 0; (i < 4) && len; i++, len--) + x |= (u32)(*tx_buf++) << (i * 8); + tegra_qspi_writel(tqspi, x, QSPI_TX_FIFO); + } + + tqspi->cur_tx_pos += written_words * tqspi->bytes_per_word; + } else { + unsigned int write_bytes; + u8 bytes_per_word = tqspi->bytes_per_word; + + max_n_32bit = min(tqspi->curr_dma_words, tx_empty_count); + written_words = max_n_32bit; + len = written_words * tqspi->bytes_per_word; + if (len > t->len - tqspi->cur_pos) + len = t->len - tqspi->cur_pos; + write_bytes = len; + for (count = 0; count < max_n_32bit; count++) { + u32 x = 0; + + for (i = 0; len && (i < bytes_per_word); i++, len--) + x |= (u32)(*tx_buf++) << (i * 8); + tegra_qspi_writel(tqspi, x, QSPI_TX_FIFO); + } + + tqspi->cur_tx_pos += write_bytes; + } + + return written_words; +} + +static unsigned int +tegra_qspi_read_rx_fifo_to_client_rxbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + u8 *rx_buf = (u8 *)t->rx_buf + tqspi->cur_rx_pos; + unsigned int len, rx_full_count, count, i; + unsigned int read_words = 0; + u32 fifo_status, x; + + fifo_status = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + rx_full_count = QSPI_RX_FIFO_FULL_COUNT(fifo_status); + if (tqspi->is_packed) { + len = tqspi->curr_dma_words * tqspi->bytes_per_word; + for (count = 0; count < rx_full_count; count++) { + x = tegra_qspi_readl(tqspi, QSPI_RX_FIFO); + + for (i = 0; len && (i < 4); i++, len--) + *rx_buf++ = (x >> i * 8) & 0xff; + } + + read_words += tqspi->curr_dma_words; + tqspi->cur_rx_pos += tqspi->curr_dma_words * tqspi->bytes_per_word; + } else { + u32 rx_mask = ((u32)1 << t->bits_per_word) - 1; + u8 bytes_per_word = tqspi->bytes_per_word; + unsigned int read_bytes; + + len = rx_full_count * bytes_per_word; + if (len > t->len - tqspi->cur_pos) + len = t->len - tqspi->cur_pos; + read_bytes = len; + for (count = 0; count < rx_full_count; count++) { + x = tegra_qspi_readl(tqspi, QSPI_RX_FIFO) & rx_mask; + + for (i = 0; len && (i < bytes_per_word); i++, len--) + *rx_buf++ = (x >> (i * 8)) & 0xff; + } + + read_words += rx_full_count; + tqspi->cur_rx_pos += read_bytes; + } + + return read_words; +} + +static void +tegra_qspi_copy_client_txbuf_to_qspi_txbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + dma_sync_single_for_cpu(tqspi->dev, tqspi->tx_dma_phys, + tqspi->dma_buf_size, DMA_TO_DEVICE); + + /* + * In packed mode, each word in FIFO may contain multiple packets + * based on bits per word. So all bytes in each FIFO word are valid. + * + * In unpacked mode, each word in FIFO contains single packet and + * based on bits per word any remaining bits in FIFO word will be + * ignored by the hardware and are invalid bits. + */ + if (tqspi->is_packed) { + tqspi->cur_tx_pos += tqspi->curr_dma_words * tqspi->bytes_per_word; + } else { + u8 *tx_buf = (u8 *)t->tx_buf + tqspi->cur_tx_pos; + unsigned int i, count, consume, write_bytes; + + /* + * Fill tx_dma_buf to contain single packet in each word based + * on bits per word from SPI core tx_buf. + */ + consume = tqspi->curr_dma_words * tqspi->bytes_per_word; + if (consume > t->len - tqspi->cur_pos) + consume = t->len - tqspi->cur_pos; + write_bytes = consume; + for (count = 0; count < tqspi->curr_dma_words; count++) { + u32 x = 0; + + for (i = 0; consume && (i < tqspi->bytes_per_word); i++, consume--) + x |= (u32)(*tx_buf++) << (i * 8); + tqspi->tx_dma_buf[count] = x; + } + + tqspi->cur_tx_pos += write_bytes; + } + + dma_sync_single_for_device(tqspi->dev, tqspi->tx_dma_phys, + tqspi->dma_buf_size, DMA_TO_DEVICE); +} + +static void +tegra_qspi_copy_qspi_rxbuf_to_client_rxbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + dma_sync_single_for_cpu(tqspi->dev, tqspi->rx_dma_phys, + tqspi->dma_buf_size, DMA_FROM_DEVICE); + + if (tqspi->is_packed) { + tqspi->cur_rx_pos += tqspi->curr_dma_words * tqspi->bytes_per_word; + } else { + unsigned char *rx_buf = t->rx_buf + tqspi->cur_rx_pos; + u32 rx_mask = ((u32)1 << t->bits_per_word) - 1; + unsigned int i, count, consume, read_bytes; + + /* + * Each FIFO word contains single data packet. + * Skip invalid bits in each FIFO word based on bits per word + * and align bytes while filling in SPI core rx_buf. + */ + consume = tqspi->curr_dma_words * tqspi->bytes_per_word; + if (consume > t->len - tqspi->cur_pos) + consume = t->len - tqspi->cur_pos; + read_bytes = consume; + for (count = 0; count < tqspi->curr_dma_words; count++) { + u32 x = tqspi->rx_dma_buf[count] & rx_mask; + + for (i = 0; consume && (i < tqspi->bytes_per_word); i++, consume--) + *rx_buf++ = (x >> (i * 8)) & 0xff; + } + + tqspi->cur_rx_pos += read_bytes; + } + + dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, + tqspi->dma_buf_size, DMA_FROM_DEVICE); +} + +static void tegra_qspi_dma_complete(void *args) +{ + struct completion *dma_complete = args; + + complete(dma_complete); +} + +static int tegra_qspi_start_tx_dma(struct tegra_qspi *tqspi, struct spi_transfer *t, int len) +{ + dma_addr_t tx_dma_phys; + + reinit_completion(&tqspi->tx_dma_complete); + + if (tqspi->is_packed) + tx_dma_phys = t->tx_dma; + else + tx_dma_phys = tqspi->tx_dma_phys; + + tqspi->tx_dma_desc = dmaengine_prep_slave_single(tqspi->tx_dma_chan, tx_dma_phys, + len, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!tqspi->tx_dma_desc) { + dev_err(tqspi->dev, "Unable to get TX descriptor\n"); + return -EIO; + } + + tqspi->tx_dma_desc->callback = tegra_qspi_dma_complete; + tqspi->tx_dma_desc->callback_param = &tqspi->tx_dma_complete; + dmaengine_submit(tqspi->tx_dma_desc); + dma_async_issue_pending(tqspi->tx_dma_chan); + + return 0; +} + +static int tegra_qspi_start_rx_dma(struct tegra_qspi *tqspi, struct spi_transfer *t, int len) +{ + dma_addr_t rx_dma_phys; + + reinit_completion(&tqspi->rx_dma_complete); + + if (tqspi->is_packed) + rx_dma_phys = t->rx_dma; + else + rx_dma_phys = tqspi->rx_dma_phys; + + tqspi->rx_dma_desc = dmaengine_prep_slave_single(tqspi->rx_dma_chan, rx_dma_phys, + len, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!tqspi->rx_dma_desc) { + dev_err(tqspi->dev, "Unable to get RX descriptor\n"); + return -EIO; + } + + tqspi->rx_dma_desc->callback = tegra_qspi_dma_complete; + tqspi->rx_dma_desc->callback_param = &tqspi->rx_dma_complete; + dmaengine_submit(tqspi->rx_dma_desc); + dma_async_issue_pending(tqspi->rx_dma_chan); + + return 0; +} + +static int tegra_qspi_flush_fifos(struct tegra_qspi *tqspi, bool atomic) +{ + void __iomem *addr = tqspi->base + QSPI_FIFO_STATUS; + u32 val; + + val = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + if ((val & QSPI_FIFO_EMPTY) == QSPI_FIFO_EMPTY) + return 0; + + val |= QSPI_RX_FIFO_FLUSH | QSPI_TX_FIFO_FLUSH; + tegra_qspi_writel(tqspi, val, QSPI_FIFO_STATUS); + + if (!atomic) + return readl_relaxed_poll_timeout(addr, val, + (val & QSPI_FIFO_EMPTY) == QSPI_FIFO_EMPTY, + 1000, 1000000); + + return readl_relaxed_poll_timeout_atomic(addr, val, + (val & QSPI_FIFO_EMPTY) == QSPI_FIFO_EMPTY, + 1000, 1000000); +} + +static void tegra_qspi_unmask_irq(struct tegra_qspi *tqspi) +{ + u32 intr_mask; + + intr_mask = tegra_qspi_readl(tqspi, QSPI_INTR_MASK); + intr_mask &= ~(QSPI_INTR_RDY_MASK | QSPI_INTR_RX_TX_FIFO_ERR); + tegra_qspi_writel(tqspi, intr_mask, QSPI_INTR_MASK); +} + +static int tegra_qspi_dma_map_xfer(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + u8 *tx_buf = (u8 *)t->tx_buf + tqspi->cur_tx_pos; + u8 *rx_buf = (u8 *)t->rx_buf + tqspi->cur_rx_pos; + unsigned int len; + + len = DIV_ROUND_UP(tqspi->curr_dma_words * tqspi->bytes_per_word, 4) * 4; + + if (t->tx_buf) { + t->tx_dma = dma_map_single(tqspi->dev, (void *)tx_buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(tqspi->dev, t->tx_dma)) + return -ENOMEM; + } + + if (t->rx_buf) { + t->rx_dma = dma_map_single(tqspi->dev, (void *)rx_buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(tqspi->dev, t->rx_dma)) { + dma_unmap_single(tqspi->dev, t->tx_dma, len, DMA_TO_DEVICE); + return -ENOMEM; + } + } + + return 0; +} + +static void tegra_qspi_dma_unmap_xfer(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + unsigned int len; + + len = DIV_ROUND_UP(tqspi->curr_dma_words * tqspi->bytes_per_word, 4) * 4; + + dma_unmap_single(tqspi->dev, t->tx_dma, len, DMA_TO_DEVICE); + dma_unmap_single(tqspi->dev, t->rx_dma, len, DMA_FROM_DEVICE); +} + +static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct spi_transfer *t) +{ + struct dma_slave_config dma_sconfig = { 0 }; + unsigned int len; + u8 dma_burst; + int ret = 0; + u32 val; + + if (tqspi->is_packed) { + ret = tegra_qspi_dma_map_xfer(tqspi, t); + if (ret < 0) + return ret; + } + + val = QSPI_DMA_BLK_SET(tqspi->curr_dma_words - 1); + tegra_qspi_writel(tqspi, val, QSPI_DMA_BLK); + + tegra_qspi_unmask_irq(tqspi); + + if (tqspi->is_packed) + len = DIV_ROUND_UP(tqspi->curr_dma_words * tqspi->bytes_per_word, 4) * 4; + else + len = tqspi->curr_dma_words * 4; + + /* set attention level based on length of transfer */ + val = 0; + if (len & 0xf) { + val |= QSPI_TX_TRIG_1 | QSPI_RX_TRIG_1; + dma_burst = 1; + } else if (((len) >> 4) & 0x1) { + val |= QSPI_TX_TRIG_4 | QSPI_RX_TRIG_4; + dma_burst = 4; + } else { + val |= QSPI_TX_TRIG_8 | QSPI_RX_TRIG_8; + dma_burst = 8; + } + + tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); + tqspi->dma_control_reg = val; + + dma_sconfig.device_fc = true; + if (tqspi->cur_direction & DATA_DIR_TX) { + dma_sconfig.dst_addr = tqspi->phys + QSPI_TX_FIFO; + dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.dst_maxburst = dma_burst; + ret = dmaengine_slave_config(tqspi->tx_dma_chan, &dma_sconfig); + if (ret < 0) { + dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); + return ret; + } + + tegra_qspi_copy_client_txbuf_to_qspi_txbuf(tqspi, t); + ret = tegra_qspi_start_tx_dma(tqspi, t, len); + if (ret < 0) { + dev_err(tqspi->dev, "failed to starting TX DMA: %d\n", ret); + return ret; + } + } + + if (tqspi->cur_direction & DATA_DIR_RX) { + dma_sconfig.src_addr = tqspi->phys + QSPI_RX_FIFO; + dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.src_maxburst = dma_burst; + ret = dmaengine_slave_config(tqspi->rx_dma_chan, &dma_sconfig); + if (ret < 0) { + dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); + return ret; + } + + dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, + tqspi->dma_buf_size, + DMA_FROM_DEVICE); + + ret = tegra_qspi_start_rx_dma(tqspi, t, len); + if (ret < 0) { + dev_err(tqspi->dev, "failed to start RX DMA: %d\n", ret); + if (tqspi->cur_direction & DATA_DIR_TX) + dmaengine_terminate_all(tqspi->tx_dma_chan); + return ret; + } + } + + tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); + + tqspi->is_curr_dma_xfer = true; + tqspi->dma_control_reg = val; + val |= QSPI_DMA_EN; + tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); + + return ret; +} + +static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct spi_transfer *t) +{ + u32 val; + unsigned int cur_words; + + if (qspi->cur_direction & DATA_DIR_TX) + cur_words = tegra_qspi_fill_tx_fifo_from_client_txbuf(qspi, t); + else + cur_words = qspi->curr_dma_words; + + val = QSPI_DMA_BLK_SET(cur_words - 1); + tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + + tegra_qspi_unmask_irq(qspi); + + qspi->is_curr_dma_xfer = false; + val = qspi->command1_reg; + val |= QSPI_PIO; + tegra_qspi_writel(qspi, val, QSPI_COMMAND1); + + return 0; +} + +static void tegra_qspi_deinit_dma(struct tegra_qspi *tqspi) +{ + if (tqspi->tx_dma_buf) { + dma_free_coherent(tqspi->dev, tqspi->dma_buf_size, + tqspi->tx_dma_buf, tqspi->tx_dma_phys); + tqspi->tx_dma_buf = NULL; + } + + if (tqspi->tx_dma_chan) { + dma_release_channel(tqspi->tx_dma_chan); + tqspi->tx_dma_chan = NULL; + } + + if (tqspi->rx_dma_buf) { + dma_free_coherent(tqspi->dev, tqspi->dma_buf_size, + tqspi->rx_dma_buf, tqspi->rx_dma_phys); + tqspi->rx_dma_buf = NULL; + } + + if (tqspi->rx_dma_chan) { + dma_release_channel(tqspi->rx_dma_chan); + tqspi->rx_dma_chan = NULL; + } +} + +static int tegra_qspi_init_dma(struct tegra_qspi *tqspi) +{ + struct dma_chan *dma_chan; + dma_addr_t dma_phys; + u32 *dma_buf; + int err; + + dma_chan = dma_request_chan(tqspi->dev, "rx"); + if (IS_ERR(dma_chan)) { + err = PTR_ERR(dma_chan); + goto err_out; + } + + tqspi->rx_dma_chan = dma_chan; + + dma_buf = dma_alloc_coherent(tqspi->dev, tqspi->dma_buf_size, &dma_phys, GFP_KERNEL); + if (!dma_buf) { + err = -ENOMEM; + goto err_out; + } + + tqspi->rx_dma_buf = dma_buf; + tqspi->rx_dma_phys = dma_phys; + + dma_chan = dma_request_chan(tqspi->dev, "tx"); + if (IS_ERR(dma_chan)) { + err = PTR_ERR(dma_chan); + goto err_out; + } + + tqspi->tx_dma_chan = dma_chan; + + dma_buf = dma_alloc_coherent(tqspi->dev, tqspi->dma_buf_size, &dma_phys, GFP_KERNEL); + if (!dma_buf) { + err = -ENOMEM; + goto err_out; + } + + tqspi->tx_dma_buf = dma_buf; + tqspi->tx_dma_phys = dma_phys; + tqspi->use_dma = true; + + return 0; + +err_out: + tegra_qspi_deinit_dma(tqspi); + + if (err != -EPROBE_DEFER) { + dev_err(tqspi->dev, "cannot use DMA: %d\n", err); + dev_err(tqspi->dev, "falling back to PIO\n"); + return 0; + } + + return err; +} + +static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_transfer *t, + bool is_first_of_msg) +{ + struct tegra_qspi *tqspi = spi_master_get_devdata(spi->master); + struct tegra_qspi_client_data *cdata = spi->controller_data; + u32 command1, command2, speed = t->speed_hz; + u8 bits_per_word = t->bits_per_word; + u32 tx_tap = 0, rx_tap = 0; + int req_mode; + + if (speed != tqspi->cur_speed) { + clk_set_rate(tqspi->clk, speed); + tqspi->cur_speed = speed; + } + + tqspi->cur_pos = 0; + tqspi->cur_rx_pos = 0; + tqspi->cur_tx_pos = 0; + tqspi->curr_xfer = t; + + if (is_first_of_msg) { + tegra_qspi_mask_clear_irq(tqspi); + + command1 = tqspi->def_command1_reg; + command1 |= QSPI_BIT_LENGTH(bits_per_word - 1); + + command1 &= ~QSPI_CONTROL_MODE_MASK; + req_mode = spi->mode & 0x3; + if (req_mode == SPI_MODE_3) + command1 |= QSPI_CONTROL_MODE_3; + else + command1 |= QSPI_CONTROL_MODE_0; + + if (spi->mode & SPI_CS_HIGH) + command1 |= QSPI_CS_SW_VAL; + else + command1 &= ~QSPI_CS_SW_VAL; + tegra_qspi_writel(tqspi, command1, QSPI_COMMAND1); + + if (cdata && cdata->tx_clk_tap_delay) + tx_tap = cdata->tx_clk_tap_delay; + + if (cdata && cdata->rx_clk_tap_delay) + rx_tap = cdata->rx_clk_tap_delay; + + command2 = QSPI_TX_TAP_DELAY(tx_tap) | QSPI_RX_TAP_DELAY(rx_tap); + if (command2 != tqspi->def_command2_reg) + tegra_qspi_writel(tqspi, command2, QSPI_COMMAND2); + + } else { + command1 = tqspi->command1_reg; + command1 &= ~QSPI_BIT_LENGTH(~0); + command1 |= QSPI_BIT_LENGTH(bits_per_word - 1); + } + + command1 &= ~QSPI_SDR_DDR_SEL; + + return command1; +} + +static int tegra_qspi_start_transfer_one(struct spi_device *spi, + struct spi_transfer *t, u32 command1) +{ + struct tegra_qspi *tqspi = spi_master_get_devdata(spi->master); + unsigned int total_fifo_words; + u8 bus_width = 0; + int ret; + + total_fifo_words = tegra_qspi_calculate_curr_xfer_param(tqspi, t); + + command1 &= ~QSPI_PACKED; + if (tqspi->is_packed) + command1 |= QSPI_PACKED; + tegra_qspi_writel(tqspi, command1, QSPI_COMMAND1); + + tqspi->cur_direction = 0; + + command1 &= ~(QSPI_TX_EN | QSPI_RX_EN); + if (t->rx_buf) { + command1 |= QSPI_RX_EN; + tqspi->cur_direction |= DATA_DIR_RX; + bus_width = t->rx_nbits; + } + + if (t->tx_buf) { + command1 |= QSPI_TX_EN; + tqspi->cur_direction |= DATA_DIR_TX; + bus_width = t->tx_nbits; + } + + command1 &= ~QSPI_INTERFACE_WIDTH_MASK; + + if (bus_width == SPI_NBITS_QUAD) + command1 |= QSPI_INTERFACE_WIDTH_QUAD; + else if (bus_width == SPI_NBITS_DUAL) + command1 |= QSPI_INTERFACE_WIDTH_DUAL; + else + command1 |= QSPI_INTERFACE_WIDTH_SINGLE; + + tqspi->command1_reg = command1; + + tegra_qspi_writel(tqspi, QSPI_NUM_DUMMY_CYCLE(tqspi->dummy_cycles), QSPI_MISC_REG); + + ret = tegra_qspi_flush_fifos(tqspi, false); + if (ret < 0) + return ret; + + if (tqspi->use_dma && total_fifo_words > QSPI_FIFO_DEPTH) + ret = tegra_qspi_start_dma_based_transfer(tqspi, t); + else + ret = tegra_qspi_start_cpu_based_transfer(tqspi, t); + + return ret; +} + +static struct tegra_qspi_client_data *tegra_qspi_parse_cdata_dt(struct spi_device *spi) +{ + struct tegra_qspi_client_data *cdata; + struct device_node *slave_np = spi->dev.of_node; + + cdata = kzalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return NULL; + + of_property_read_u32(slave_np, "nvidia,tx-clk-tap-delay", + &cdata->tx_clk_tap_delay); + of_property_read_u32(slave_np, "nvidia,rx-clk-tap-delay", + &cdata->rx_clk_tap_delay); + return cdata; +} + +static void tegra_qspi_cleanup(struct spi_device *spi) +{ + struct tegra_qspi_client_data *cdata = spi->controller_data; + + spi->controller_data = NULL; + kfree(cdata); +} + +static int tegra_qspi_setup(struct spi_device *spi) +{ + struct tegra_qspi *tqspi = spi_master_get_devdata(spi->master); + struct tegra_qspi_client_data *cdata = spi->controller_data; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(tqspi->dev); + if (ret < 0) { + dev_err(tqspi->dev, "failed to get runtime PM: %d\n", ret); + return ret; + } + + if (!cdata) { + cdata = tegra_qspi_parse_cdata_dt(spi); + spi->controller_data = cdata; + } + + spin_lock_irqsave(&tqspi->lock, flags); + + /* keep default cs state to inactive */ + val = tqspi->def_command1_reg; + if (spi->mode & SPI_CS_HIGH) + val &= ~QSPI_CS_SW_VAL; + else + val |= QSPI_CS_SW_VAL; + + tqspi->def_command1_reg = val; + tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); + + spin_unlock_irqrestore(&tqspi->lock, flags); + + pm_runtime_put(tqspi->dev); + + return 0; +} + +static void tegra_qspi_dump_regs(struct tegra_qspi *tqspi) +{ + dev_dbg(tqspi->dev, "============ QSPI REGISTER DUMP ============\n"); + dev_dbg(tqspi->dev, "Command1: 0x%08x | Command2: 0x%08x\n", + tegra_qspi_readl(tqspi, QSPI_COMMAND1), + tegra_qspi_readl(tqspi, QSPI_COMMAND2)); + dev_dbg(tqspi->dev, "DMA_CTL: 0x%08x | DMA_BLK: 0x%08x\n", + tegra_qspi_readl(tqspi, QSPI_DMA_CTL), + tegra_qspi_readl(tqspi, QSPI_DMA_BLK)); + dev_dbg(tqspi->dev, "INTR_MASK: 0x%08x | MISC: 0x%08x\n", + tegra_qspi_readl(tqspi, QSPI_INTR_MASK), + tegra_qspi_readl(tqspi, QSPI_MISC_REG)); + dev_dbg(tqspi->dev, "TRANS_STAT: 0x%08x | FIFO_STATUS: 0x%08x\n", + tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS), + tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS)); +} + +static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) +{ + dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); + tegra_qspi_dump_regs(tqspi); + tegra_qspi_flush_fifos(tqspi, true); + reset_control_assert(tqspi->rst); + udelay(2); + reset_control_deassert(tqspi->rst); +} + +static void tegra_qspi_transfer_end(struct spi_device *spi) +{ + struct tegra_qspi *tqspi = spi_master_get_devdata(spi->master); + int cs_val = (spi->mode & SPI_CS_HIGH) ? 0 : 1; + + if (cs_val) + tqspi->command1_reg |= QSPI_CS_SW_VAL; + else + tqspi->command1_reg &= ~QSPI_CS_SW_VAL; + tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); + tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); +} + +static int tegra_qspi_transfer_one_message(struct spi_master *master, struct spi_message *msg) +{ + struct tegra_qspi *tqspi = spi_master_get_devdata(master); + struct spi_device *spi = msg->spi; + struct spi_transfer *transfer; + bool is_first_msg = true; + int ret; + + msg->status = 0; + msg->actual_length = 0; + tqspi->tx_status = 0; + tqspi->rx_status = 0; + + list_for_each_entry(transfer, &msg->transfers, transfer_list) { + struct spi_transfer *xfer = transfer; + u8 dummy_bytes = 0; + u32 cmd1; + + tqspi->dummy_cycles = 0; + /* + * Tegra QSPI hardware supports dummy bytes transfer after actual transfer + * bytes based on programmed dummy clock cycles in the QSPI_MISC register. + * So, check if the next transfer is dummy data transfer and program dummy + * clock cycles along with the current transfer and skip next transfer. + */ + if (!list_is_last(&xfer->transfer_list, &msg->transfers)) { + struct spi_transfer *next_xfer; + + next_xfer = list_next_entry(xfer, transfer_list); + if (next_xfer->dummy_data) { + u32 dummy_cycles = next_xfer->len * 8 / next_xfer->tx_nbits; + + if (dummy_cycles <= QSPI_DUMMY_CYCLES_MAX) { + tqspi->dummy_cycles = dummy_cycles; + dummy_bytes = next_xfer->len; + transfer = next_xfer; + } + } + } + + reinit_completion(&tqspi->xfer_completion); + + cmd1 = tegra_qspi_setup_transfer_one(spi, xfer, is_first_msg); + + ret = tegra_qspi_start_transfer_one(spi, xfer, cmd1); + if (ret < 0) { + dev_err(tqspi->dev, "failed to start transfer: %d\n", ret); + goto complete_xfer; + } + + is_first_msg = false; + ret = wait_for_completion_timeout(&tqspi->xfer_completion, + QSPI_DMA_TIMEOUT); + if (WARN_ON(ret == 0)) { + dev_err(tqspi->dev, "transfer timeout: %d\n", ret); + if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_TX)) + dmaengine_terminate_all(tqspi->tx_dma_chan); + if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_RX)) + dmaengine_terminate_all(tqspi->rx_dma_chan); + tegra_qspi_handle_error(tqspi); + ret = -EIO; + goto complete_xfer; + } + + if (tqspi->tx_status || tqspi->rx_status) { + tegra_qspi_handle_error(tqspi); + ret = -EIO; + goto complete_xfer; + } + + msg->actual_length += xfer->len + dummy_bytes; + +complete_xfer: + if (ret < 0) { + tegra_qspi_transfer_end(spi); + spi_transfer_delay_exec(xfer); + goto exit; + } + + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + /* de-activate CS after last transfer only when cs_change is not set */ + if (!xfer->cs_change) { + tegra_qspi_transfer_end(spi); + spi_transfer_delay_exec(xfer); + } + } else if (xfer->cs_change) { + /* de-activated CS between the transfers only when cs_change is set */ + tegra_qspi_transfer_end(spi); + spi_transfer_delay_exec(xfer); + } + } + + ret = 0; +exit: + msg->status = ret; + spi_finalize_current_message(master); + return ret; +} + +static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) +{ + struct spi_transfer *t = tqspi->curr_xfer; + unsigned long flags; + + spin_lock_irqsave(&tqspi->lock, flags); + + if (tqspi->tx_status || tqspi->rx_status) { + tegra_qspi_handle_error(tqspi); + complete(&tqspi->xfer_completion); + goto exit; + } + + if (tqspi->cur_direction & DATA_DIR_RX) + tegra_qspi_read_rx_fifo_to_client_rxbuf(tqspi, t); + + if (tqspi->cur_direction & DATA_DIR_TX) + tqspi->cur_pos = tqspi->cur_tx_pos; + else + tqspi->cur_pos = tqspi->cur_rx_pos; + + if (tqspi->cur_pos == t->len) { + complete(&tqspi->xfer_completion); + goto exit; + } + + tegra_qspi_calculate_curr_xfer_param(tqspi, t); + tegra_qspi_start_cpu_based_transfer(tqspi, t); +exit: + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) +{ + struct spi_transfer *t = tqspi->curr_xfer; + unsigned int total_fifo_words; + unsigned long flags; + long wait_status; + int err = 0; + + if (tqspi->cur_direction & DATA_DIR_TX) { + if (tqspi->tx_status) { + dmaengine_terminate_all(tqspi->tx_dma_chan); + err += 1; + } else { + wait_status = wait_for_completion_interruptible_timeout( + &tqspi->tx_dma_complete, QSPI_DMA_TIMEOUT); + if (wait_status <= 0) { + dmaengine_terminate_all(tqspi->tx_dma_chan); + dev_err(tqspi->dev, "failed TX DMA transfer\n"); + err += 1; + } + } + } + + if (tqspi->cur_direction & DATA_DIR_RX) { + if (tqspi->rx_status) { + dmaengine_terminate_all(tqspi->rx_dma_chan); + err += 2; + } else { + wait_status = wait_for_completion_interruptible_timeout( + &tqspi->rx_dma_complete, QSPI_DMA_TIMEOUT); + if (wait_status <= 0) { + dmaengine_terminate_all(tqspi->rx_dma_chan); + dev_err(tqspi->dev, "failed RX DMA transfer\n"); + err += 2; + } + } + } + + spin_lock_irqsave(&tqspi->lock, flags); + + if (err) { + tegra_qspi_dma_unmap_xfer(tqspi, t); + tegra_qspi_handle_error(tqspi); + complete(&tqspi->xfer_completion); + goto exit; + } + + if (tqspi->cur_direction & DATA_DIR_RX) + tegra_qspi_copy_qspi_rxbuf_to_client_rxbuf(tqspi, t); + + if (tqspi->cur_direction & DATA_DIR_TX) + tqspi->cur_pos = tqspi->cur_tx_pos; + else + tqspi->cur_pos = tqspi->cur_rx_pos; + + if (tqspi->cur_pos == t->len) { + tegra_qspi_dma_unmap_xfer(tqspi, t); + complete(&tqspi->xfer_completion); + goto exit; + } + + tegra_qspi_dma_unmap_xfer(tqspi, t); + + /* continue transfer in current message */ + total_fifo_words = tegra_qspi_calculate_curr_xfer_param(tqspi, t); + if (total_fifo_words > QSPI_FIFO_DEPTH) + err = tegra_qspi_start_dma_based_transfer(tqspi, t); + else + err = tegra_qspi_start_cpu_based_transfer(tqspi, t); + +exit: + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) +{ + struct tegra_qspi *tqspi = context_data; + + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + + if (tqspi->cur_direction & DATA_DIR_TX) + tqspi->tx_status = tqspi->status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + + if (tqspi->cur_direction & DATA_DIR_RX) + tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + + tegra_qspi_mask_clear_irq(tqspi); + + if (!tqspi->is_curr_dma_xfer) + return handle_cpu_based_xfer(tqspi); + + return handle_dma_based_xfer(tqspi); +} + +static const struct of_device_id tegra_qspi_of_match[] = { + { .compatible = "nvidia,tegra210-qspi", }, + { .compatible = "nvidia,tegra186-qspi", }, + { .compatible = "nvidia,tegra194-qspi", }, + {} +}; + +MODULE_DEVICE_TABLE(of, tegra_qspi_of_match); + +static int tegra_qspi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct tegra_qspi *tqspi; + struct resource *r; + int ret, qspi_irq; + int bus_num; + + master = devm_spi_alloc_master(&pdev->dev, sizeof(*tqspi)); + if (!master) + return -ENOMEM; + + platform_set_drvdata(pdev, master); + tqspi = spi_master_get_devdata(master); + + master->mode_bits = SPI_MODE_0 | SPI_MODE_3 | SPI_CS_HIGH | + SPI_TX_DUAL | SPI_RX_DUAL | SPI_TX_QUAD | SPI_RX_QUAD; + master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) | SPI_BPW_MASK(8); + master->setup = tegra_qspi_setup; + master->cleanup = tegra_qspi_cleanup; + master->transfer_one_message = tegra_qspi_transfer_one_message; + master->num_chipselect = 1; + master->auto_runtime_pm = true; + + bus_num = of_alias_get_id(pdev->dev.of_node, "spi"); + if (bus_num >= 0) + master->bus_num = bus_num; + + tqspi->master = master; + tqspi->dev = &pdev->dev; + spin_lock_init(&tqspi->lock); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tqspi->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(tqspi->base)) + return PTR_ERR(tqspi->base); + + tqspi->phys = r->start; + qspi_irq = platform_get_irq(pdev, 0); + tqspi->irq = qspi_irq; + + tqspi->clk = devm_clk_get(&pdev->dev, "qspi"); + if (IS_ERR(tqspi->clk)) { + ret = PTR_ERR(tqspi->clk); + dev_err(&pdev->dev, "failed to get clock: %d\n", ret); + return ret; + } + + tqspi->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(tqspi->rst)) { + ret = PTR_ERR(tqspi->rst); + dev_err(&pdev->dev, "failed to get reset control: %d\n", ret); + return ret; + } + + tqspi->max_buf_size = QSPI_FIFO_DEPTH << 2; + tqspi->dma_buf_size = DEFAULT_QSPI_DMA_BUF_LEN; + + ret = tegra_qspi_init_dma(tqspi); + if (ret < 0) + return ret; + + if (tqspi->use_dma) + tqspi->max_buf_size = tqspi->dma_buf_size; + + init_completion(&tqspi->tx_dma_complete); + init_completion(&tqspi->rx_dma_complete); + init_completion(&tqspi->xfer_completion); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get runtime PM: %d\n", ret); + goto exit_pm_disable; + } + + reset_control_assert(tqspi->rst); + udelay(2); + reset_control_deassert(tqspi->rst); + + tqspi->def_command1_reg = QSPI_M_S | QSPI_CS_SW_HW | QSPI_CS_SW_VAL; + tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); + tqspi->spi_cs_timing1 = tegra_qspi_readl(tqspi, QSPI_CS_TIMING1); + tqspi->spi_cs_timing2 = tegra_qspi_readl(tqspi, QSPI_CS_TIMING2); + tqspi->def_command2_reg = tegra_qspi_readl(tqspi, QSPI_COMMAND2); + + pm_runtime_put(&pdev->dev); + + ret = request_threaded_irq(tqspi->irq, NULL, + tegra_qspi_isr_thread, IRQF_ONESHOT, + dev_name(&pdev->dev), tqspi); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", tqspi->irq, ret); + goto exit_pm_disable; + } + + master->dev.of_node = pdev->dev.of_node; + ret = spi_register_master(master); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register master: %d\n", ret); + goto exit_free_irq; + } + + return 0; + +exit_free_irq: + free_irq(qspi_irq, tqspi); +exit_pm_disable: + pm_runtime_disable(&pdev->dev); + tegra_qspi_deinit_dma(tqspi); + return ret; +} + +static int tegra_qspi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct tegra_qspi *tqspi = spi_master_get_devdata(master); + + spi_unregister_master(master); + free_irq(tqspi->irq, tqspi); + pm_runtime_disable(&pdev->dev); + tegra_qspi_deinit_dma(tqspi); + + return 0; +} + +static int __maybe_unused tegra_qspi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + + return spi_master_suspend(master); +} + +static int __maybe_unused tegra_qspi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct tegra_qspi *tqspi = spi_master_get_devdata(master); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "failed to get runtime PM: %d\n", ret); + return ret; + } + + tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); + tegra_qspi_writel(tqspi, tqspi->def_command2_reg, QSPI_COMMAND2); + pm_runtime_put(dev); + + return spi_master_resume(master); +} + +static int __maybe_unused tegra_qspi_runtime_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct tegra_qspi *tqspi = spi_master_get_devdata(master); + + /* flush all write which are in PPSB queue by reading back */ + tegra_qspi_readl(tqspi, QSPI_COMMAND1); + + clk_disable_unprepare(tqspi->clk); + + return 0; +} + +static int __maybe_unused tegra_qspi_runtime_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct tegra_qspi *tqspi = spi_master_get_devdata(master); + int ret; + + ret = clk_prepare_enable(tqspi->clk); + if (ret < 0) + dev_err(tqspi->dev, "failed to enable clock: %d\n", ret); + + return ret; +} + +static const struct dev_pm_ops tegra_qspi_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_qspi_runtime_suspend, tegra_qspi_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra_qspi_suspend, tegra_qspi_resume) +}; + +static struct platform_driver tegra_qspi_driver = { + .driver = { + .name = "tegra-qspi", + .pm = &tegra_qspi_pm_ops, + .of_match_table = tegra_qspi_of_match, + }, + .probe = tegra_qspi_probe, + .remove = tegra_qspi_remove, +}; +module_platform_driver(tegra_qspi_driver); + +MODULE_ALIAS("platform:qspi-tegra"); +MODULE_DESCRIPTION("NVIDIA Tegra QSPI Controller Driver"); +MODULE_AUTHOR("Sowjanya Komatineni "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-txx9.c b/drivers/spi/spi-txx9.c deleted file mode 100644 index 3606232f190f..000000000000 --- a/drivers/spi/spi-txx9.c +++ /dev/null @@ -1,477 +0,0 @@ -/* - * TXx9 SPI controller driver. - * - * Based on linux/arch/mips/tx4938/toshiba_rbtx4938/spi_txx9.c - * Copyright (C) 2000-2001 Toshiba Corporation - * - * 2003-2005 (c) MontaVista Software, Inc. This file is licensed under the - * terms of the GNU General Public License version 2. This program is - * licensed "as is" without any warranty of any kind, whether express - * or implied. - * - * Support for TX4938 in 2.6 - Manish Lachwani (mlachwani@mvista.com) - * - * Convert to generic SPI framework - Atsushi Nemoto (anemo@mba.ocn.ne.jp) - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#define SPI_FIFO_SIZE 4 -#define SPI_MAX_DIVIDER 0xff /* Max. value for SPCR1.SER */ -#define SPI_MIN_DIVIDER 1 /* Min. value for SPCR1.SER */ - -#define TXx9_SPMCR 0x00 -#define TXx9_SPCR0 0x04 -#define TXx9_SPCR1 0x08 -#define TXx9_SPFS 0x0c -#define TXx9_SPSR 0x14 -#define TXx9_SPDR 0x18 - -/* SPMCR : SPI Master Control */ -#define TXx9_SPMCR_OPMODE 0xc0 -#define TXx9_SPMCR_CONFIG 0x40 -#define TXx9_SPMCR_ACTIVE 0x80 -#define TXx9_SPMCR_SPSTP 0x02 -#define TXx9_SPMCR_BCLR 0x01 - -/* SPCR0 : SPI Control 0 */ -#define TXx9_SPCR0_TXIFL_MASK 0xc000 -#define TXx9_SPCR0_RXIFL_MASK 0x3000 -#define TXx9_SPCR0_SIDIE 0x0800 -#define TXx9_SPCR0_SOEIE 0x0400 -#define TXx9_SPCR0_RBSIE 0x0200 -#define TXx9_SPCR0_TBSIE 0x0100 -#define TXx9_SPCR0_IFSPSE 0x0010 -#define TXx9_SPCR0_SBOS 0x0004 -#define TXx9_SPCR0_SPHA 0x0002 -#define TXx9_SPCR0_SPOL 0x0001 - -/* SPSR : SPI Status */ -#define TXx9_SPSR_TBSI 0x8000 -#define TXx9_SPSR_RBSI 0x4000 -#define TXx9_SPSR_TBS_MASK 0x3800 -#define TXx9_SPSR_RBS_MASK 0x0700 -#define TXx9_SPSR_SPOE 0x0080 -#define TXx9_SPSR_IFSD 0x0008 -#define TXx9_SPSR_SIDLE 0x0004 -#define TXx9_SPSR_STRDY 0x0002 -#define TXx9_SPSR_SRRDY 0x0001 - - -struct txx9spi { - struct work_struct work; - spinlock_t lock; /* protect 'queue' */ - struct list_head queue; - wait_queue_head_t waitq; - void __iomem *membase; - int baseclk; - struct clk *clk; - struct gpio_desc *last_chipselect; - int last_chipselect_val; -}; - -static u32 txx9spi_rd(struct txx9spi *c, int reg) -{ - return __raw_readl(c->membase + reg); -} -static void txx9spi_wr(struct txx9spi *c, u32 val, int reg) -{ - __raw_writel(val, c->membase + reg); -} - -static void txx9spi_cs_func(struct spi_device *spi, struct txx9spi *c, - int on, unsigned int cs_delay) -{ - /* - * The GPIO descriptor will track polarity inversion inside - * gpiolib. - */ - if (on) { - /* deselect the chip with cs_change hint in last transfer */ - if (c->last_chipselect) - gpiod_set_value(c->last_chipselect, - !c->last_chipselect_val); - c->last_chipselect = spi->cs_gpiod; - c->last_chipselect_val = on; - } else { - c->last_chipselect = NULL; - ndelay(cs_delay); /* CS Hold Time */ - } - gpiod_set_value(spi->cs_gpiod, on); - ndelay(cs_delay); /* CS Setup Time / CS Recovery Time */ -} - -static int txx9spi_setup(struct spi_device *spi) -{ - struct txx9spi *c = spi_master_get_devdata(spi->master); - - if (!spi->max_speed_hz) - return -EINVAL; - - /* deselect chip */ - spin_lock(&c->lock); - txx9spi_cs_func(spi, c, 0, (NSEC_PER_SEC / 2) / spi->max_speed_hz); - spin_unlock(&c->lock); - - return 0; -} - -static irqreturn_t txx9spi_interrupt(int irq, void *dev_id) -{ - struct txx9spi *c = dev_id; - - /* disable rx intr */ - txx9spi_wr(c, txx9spi_rd(c, TXx9_SPCR0) & ~TXx9_SPCR0_RBSIE, - TXx9_SPCR0); - wake_up(&c->waitq); - return IRQ_HANDLED; -} - -static void txx9spi_work_one(struct txx9spi *c, struct spi_message *m) -{ - struct spi_device *spi = m->spi; - struct spi_transfer *t; - unsigned int cs_delay; - unsigned int cs_change = 1; - int status = 0; - u32 mcr; - u32 prev_speed_hz = 0; - u8 prev_bits_per_word = 0; - - /* CS setup/hold/recovery time in nsec */ - cs_delay = 100 + (NSEC_PER_SEC / 2) / spi->max_speed_hz; - - mcr = txx9spi_rd(c, TXx9_SPMCR); - if (unlikely((mcr & TXx9_SPMCR_OPMODE) == TXx9_SPMCR_ACTIVE)) { - dev_err(&spi->dev, "Bad mode.\n"); - status = -EIO; - goto exit; - } - mcr &= ~(TXx9_SPMCR_OPMODE | TXx9_SPMCR_SPSTP | TXx9_SPMCR_BCLR); - - /* enter config mode */ - txx9spi_wr(c, mcr | TXx9_SPMCR_CONFIG | TXx9_SPMCR_BCLR, TXx9_SPMCR); - txx9spi_wr(c, TXx9_SPCR0_SBOS - | ((spi->mode & SPI_CPOL) ? TXx9_SPCR0_SPOL : 0) - | ((spi->mode & SPI_CPHA) ? TXx9_SPCR0_SPHA : 0) - | 0x08, - TXx9_SPCR0); - - list_for_each_entry(t, &m->transfers, transfer_list) { - const void *txbuf = t->tx_buf; - void *rxbuf = t->rx_buf; - u32 data; - unsigned int len = t->len; - unsigned int wsize; - u32 speed_hz = t->speed_hz; - u8 bits_per_word = t->bits_per_word; - - wsize = bits_per_word >> 3; /* in bytes */ - - if (prev_speed_hz != speed_hz - || prev_bits_per_word != bits_per_word) { - int n = DIV_ROUND_UP(c->baseclk, speed_hz) - 1; - - n = clamp(n, SPI_MIN_DIVIDER, SPI_MAX_DIVIDER); - /* enter config mode */ - txx9spi_wr(c, mcr | TXx9_SPMCR_CONFIG | TXx9_SPMCR_BCLR, - TXx9_SPMCR); - txx9spi_wr(c, (n << 8) | bits_per_word, TXx9_SPCR1); - /* enter active mode */ - txx9spi_wr(c, mcr | TXx9_SPMCR_ACTIVE, TXx9_SPMCR); - - prev_speed_hz = speed_hz; - prev_bits_per_word = bits_per_word; - } - - if (cs_change) - txx9spi_cs_func(spi, c, 1, cs_delay); - cs_change = t->cs_change; - while (len) { - unsigned int count = SPI_FIFO_SIZE; - int i; - u32 cr0; - - if (len < count * wsize) - count = len / wsize; - /* now tx must be idle... */ - while (!(txx9spi_rd(c, TXx9_SPSR) & TXx9_SPSR_SIDLE)) - cpu_relax(); - cr0 = txx9spi_rd(c, TXx9_SPCR0); - cr0 &= ~TXx9_SPCR0_RXIFL_MASK; - cr0 |= (count - 1) << 12; - /* enable rx intr */ - cr0 |= TXx9_SPCR0_RBSIE; - txx9spi_wr(c, cr0, TXx9_SPCR0); - /* send */ - for (i = 0; i < count; i++) { - if (txbuf) { - data = (wsize == 1) - ? *(const u8 *)txbuf - : *(const u16 *)txbuf; - txx9spi_wr(c, data, TXx9_SPDR); - txbuf += wsize; - } else - txx9spi_wr(c, 0, TXx9_SPDR); - } - /* wait all rx data */ - wait_event(c->waitq, - txx9spi_rd(c, TXx9_SPSR) & TXx9_SPSR_RBSI); - /* receive */ - for (i = 0; i < count; i++) { - data = txx9spi_rd(c, TXx9_SPDR); - if (rxbuf) { - if (wsize == 1) - *(u8 *)rxbuf = data; - else - *(u16 *)rxbuf = data; - rxbuf += wsize; - } - } - len -= count * wsize; - } - m->actual_length += t->len; - spi_transfer_delay_exec(t); - - if (!cs_change) - continue; - if (t->transfer_list.next == &m->transfers) - break; - /* sometimes a short mid-message deselect of the chip - * may be needed to terminate a mode or command - */ - txx9spi_cs_func(spi, c, 0, cs_delay); - } - -exit: - m->status = status; - if (m->complete) - m->complete(m->context); - - /* normally deactivate chipselect ... unless no error and - * cs_change has hinted that the next message will probably - * be for this chip too. - */ - if (!(status == 0 && cs_change)) - txx9spi_cs_func(spi, c, 0, cs_delay); - - /* enter config mode */ - txx9spi_wr(c, mcr | TXx9_SPMCR_CONFIG | TXx9_SPMCR_BCLR, TXx9_SPMCR); -} - -static void txx9spi_work(struct work_struct *work) -{ - struct txx9spi *c = container_of(work, struct txx9spi, work); - unsigned long flags; - - spin_lock_irqsave(&c->lock, flags); - while (!list_empty(&c->queue)) { - struct spi_message *m; - - m = container_of(c->queue.next, struct spi_message, queue); - list_del_init(&m->queue); - spin_unlock_irqrestore(&c->lock, flags); - - txx9spi_work_one(c, m); - - spin_lock_irqsave(&c->lock, flags); - } - spin_unlock_irqrestore(&c->lock, flags); -} - -static int txx9spi_transfer(struct spi_device *spi, struct spi_message *m) -{ - struct spi_master *master = spi->master; - struct txx9spi *c = spi_master_get_devdata(master); - struct spi_transfer *t; - unsigned long flags; - - m->actual_length = 0; - - /* check each transfer's parameters */ - list_for_each_entry(t, &m->transfers, transfer_list) { - if (!t->tx_buf && !t->rx_buf && t->len) - return -EINVAL; - } - - spin_lock_irqsave(&c->lock, flags); - list_add_tail(&m->queue, &c->queue); - schedule_work(&c->work); - spin_unlock_irqrestore(&c->lock, flags); - - return 0; -} - -/* - * Chip select uses GPIO only, further the driver is using the chip select - * numer (from the device tree "reg" property, and this can only come from - * device tree since this i MIPS and there is no way to pass platform data) as - * the GPIO number. As the platform has only one GPIO controller (the txx9 GPIO - * chip) it is thus using the chip select number as an offset into that chip. - * This chip has a maximum of 16 GPIOs 0..15 and this is what all platforms - * register. - * - * We modernized this behaviour by explicitly converting that offset to an - * offset on the GPIO chip using a GPIO descriptor machine table of the same - * size as the txx9 GPIO chip with a 1-to-1 mapping of chip select to GPIO - * offset. - * - * This is admittedly a hack, but it is countering the hack of using "reg" to - * contain a GPIO offset when it should be using "cs-gpios" as the SPI bindings - * state. - */ -static struct gpiod_lookup_table txx9spi_cs_gpio_table = { - .dev_id = "spi0", - .table = { - GPIO_LOOKUP_IDX("TXx9", 0, "cs", 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 1, "cs", 1, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 2, "cs", 2, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 3, "cs", 3, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 4, "cs", 4, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 5, "cs", 5, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 6, "cs", 6, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 7, "cs", 7, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 8, "cs", 8, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 9, "cs", 9, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 10, "cs", 10, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 11, "cs", 11, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 12, "cs", 12, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 13, "cs", 13, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 14, "cs", 14, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("TXx9", 15, "cs", 15, GPIO_ACTIVE_LOW), - { }, - }, -}; - -static int txx9spi_probe(struct platform_device *dev) -{ - struct spi_master *master; - struct txx9spi *c; - struct resource *res; - int ret = -ENODEV; - u32 mcr; - int irq; - - master = spi_alloc_master(&dev->dev, sizeof(*c)); - if (!master) - return ret; - c = spi_master_get_devdata(master); - platform_set_drvdata(dev, master); - - INIT_WORK(&c->work, txx9spi_work); - spin_lock_init(&c->lock); - INIT_LIST_HEAD(&c->queue); - init_waitqueue_head(&c->waitq); - - c->clk = devm_clk_get(&dev->dev, "spi-baseclk"); - if (IS_ERR(c->clk)) { - ret = PTR_ERR(c->clk); - c->clk = NULL; - goto exit; - } - ret = clk_prepare_enable(c->clk); - if (ret) { - c->clk = NULL; - goto exit; - } - c->baseclk = clk_get_rate(c->clk); - master->min_speed_hz = DIV_ROUND_UP(c->baseclk, SPI_MAX_DIVIDER + 1); - master->max_speed_hz = c->baseclk / (SPI_MIN_DIVIDER + 1); - - res = platform_get_resource(dev, IORESOURCE_MEM, 0); - c->membase = devm_ioremap_resource(&dev->dev, res); - if (IS_ERR(c->membase)) - goto exit_busy; - - /* enter config mode */ - mcr = txx9spi_rd(c, TXx9_SPMCR); - mcr &= ~(TXx9_SPMCR_OPMODE | TXx9_SPMCR_SPSTP | TXx9_SPMCR_BCLR); - txx9spi_wr(c, mcr | TXx9_SPMCR_CONFIG | TXx9_SPMCR_BCLR, TXx9_SPMCR); - - irq = platform_get_irq(dev, 0); - if (irq < 0) - goto exit_busy; - ret = devm_request_irq(&dev->dev, irq, txx9spi_interrupt, 0, - "spi_txx9", c); - if (ret) - goto exit; - - c->last_chipselect = NULL; - - dev_info(&dev->dev, "at %#llx, irq %d, %dMHz\n", - (unsigned long long)res->start, irq, - (c->baseclk + 500000) / 1000000); - - gpiod_add_lookup_table(&txx9spi_cs_gpio_table); - - /* the spi->mode bits understood by this driver: */ - master->mode_bits = SPI_CS_HIGH | SPI_CPOL | SPI_CPHA; - - master->bus_num = dev->id; - master->setup = txx9spi_setup; - master->transfer = txx9spi_transfer; - master->num_chipselect = (u16)UINT_MAX; /* any GPIO numbers */ - master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); - master->use_gpio_descriptors = true; - - ret = devm_spi_register_master(&dev->dev, master); - if (ret) - goto exit; - return 0; -exit_busy: - ret = -EBUSY; -exit: - clk_disable_unprepare(c->clk); - spi_master_put(master); - return ret; -} - -static int txx9spi_remove(struct platform_device *dev) -{ - struct spi_master *master = platform_get_drvdata(dev); - struct txx9spi *c = spi_master_get_devdata(master); - - flush_work(&c->work); - clk_disable_unprepare(c->clk); - return 0; -} - -/* work with hotplug and coldplug */ -MODULE_ALIAS("platform:spi_txx9"); - -static struct platform_driver txx9spi_driver = { - .probe = txx9spi_probe, - .remove = txx9spi_remove, - .driver = { - .name = "spi_txx9", - }, -}; - -static int __init txx9spi_init(void) -{ - return platform_driver_register(&txx9spi_driver); -} -subsys_initcall(txx9spi_init); - -static void __exit txx9spi_exit(void) -{ - platform_driver_unregister(&txx9spi_driver); -} -module_exit(txx9spi_exit); - -MODULE_DESCRIPTION("TXx9 SPI Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 22e0203cbc00..b2b6ef13c79b 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -810,7 +810,8 @@ static void spi_set_cs(struct spi_device *spi, bool enable) spi->controller->last_cs_enable = enable; spi->controller->last_cs_mode_high = spi->mode & SPI_CS_HIGH; - if (!spi->controller->set_cs_timing) { + if (spi->cs_gpiod || gpio_is_valid(spi->cs_gpio) || + !spi->controller->set_cs_timing) { if (enable1) spi_delay_exec(&spi->controller->cs_setup, NULL); else @@ -841,7 +842,8 @@ static void spi_set_cs(struct spi_device *spi, bool enable) spi->controller->set_cs(spi, !enable); } - if (!spi->controller->set_cs_timing) { + if (spi->cs_gpiod || gpio_is_valid(spi->cs_gpio) || + !spi->controller->set_cs_timing) { if (!enable1) spi_delay_exec(&spi->controller->cs_inactive, NULL); } @@ -1267,7 +1269,7 @@ static int spi_transfer_one_message(struct spi_controller *ctlr, ptp_read_system_prets(xfer->ptp_sts); } - if (xfer->tx_buf || xfer->rx_buf) { + if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) { reinit_completion(&ctlr->xfer_completion); fallback_pio: @@ -1945,6 +1947,9 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi, /* Device DUAL/QUAD mode */ if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) { switch (value) { + case 0: + spi->mode |= SPI_NO_TX; + break; case 1: break; case 2: @@ -1966,6 +1971,9 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi, if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) { switch (value) { + case 0: + spi->mode |= SPI_NO_RX; + break; case 1: break; case 2: @@ -3334,12 +3342,16 @@ int spi_setup(struct spi_device *spi) unsigned bad_bits, ugly_bits; int status; - /* check mode to prevent that DUAL and QUAD set at the same time + /* + * check mode to prevent that any two of DUAL, QUAD and NO_MOSI/MISO + * are set at the same time */ - if (((spi->mode & SPI_TX_DUAL) && (spi->mode & SPI_TX_QUAD)) || - ((spi->mode & SPI_RX_DUAL) && (spi->mode & SPI_RX_QUAD))) { + if ((hweight_long(spi->mode & + (SPI_TX_DUAL | SPI_TX_QUAD | SPI_NO_TX)) > 1) || + (hweight_long(spi->mode & + (SPI_RX_DUAL | SPI_RX_QUAD | SPI_NO_RX)) > 1)) { dev_err(&spi->dev, - "setup: can not select dual and quad at the same time\n"); + "setup: can not select any two of dual, quad and no-rx/tx at the same time\n"); return -EINVAL; } /* if it is SPI_3WIRE mode, DUAL and QUAD should be forbidden @@ -3353,7 +3365,8 @@ int spi_setup(struct spi_device *spi) * SPI_CS_WORD has a fallback software implementation, * so it is ignored here. */ - bad_bits = spi->mode & ~(spi->controller->mode_bits | SPI_CS_WORD); + bad_bits = spi->mode & ~(spi->controller->mode_bits | SPI_CS_WORD | + SPI_NO_TX | SPI_NO_RX); /* nothing prevents from working with active-high CS in case if it * is driven by GPIO. */ @@ -3450,11 +3463,31 @@ EXPORT_SYMBOL_GPL(spi_setup); int spi_set_cs_timing(struct spi_device *spi, struct spi_delay *setup, struct spi_delay *hold, struct spi_delay *inactive) { + struct device *parent = spi->controller->dev.parent; size_t len; + int status; - if (spi->controller->set_cs_timing) - return spi->controller->set_cs_timing(spi, setup, hold, - inactive); + if (spi->controller->set_cs_timing && + !(spi->cs_gpiod || gpio_is_valid(spi->cs_gpio))) { + if (spi->controller->auto_runtime_pm) { + status = pm_runtime_get_sync(parent); + if (status < 0) { + pm_runtime_put_noidle(parent); + dev_err(&spi->controller->dev, "Failed to power device: %d\n", + status); + return status; + } + + status = spi->controller->set_cs_timing(spi, setup, + hold, inactive); + pm_runtime_mark_last_busy(parent); + pm_runtime_put_autosuspend(parent); + return status; + } else { + return spi->controller->set_cs_timing(spi, setup, hold, + inactive); + } + } if ((setup && setup->unit == SPI_DELAY_UNIT_SCK) || (hold && hold->unit == SPI_DELAY_UNIT_SCK) || @@ -3616,6 +3649,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) * 2. check tx/rx_nbits match the mode in spi_device */ if (xfer->tx_buf) { + if (spi->mode & SPI_NO_TX) + return -EINVAL; if (xfer->tx_nbits != SPI_NBITS_SINGLE && xfer->tx_nbits != SPI_NBITS_DUAL && xfer->tx_nbits != SPI_NBITS_QUAD) @@ -3629,6 +3664,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) } /* check transfer rx_nbits */ if (xfer->rx_buf) { + if (spi->mode & SPI_NO_RX) + return -EINVAL; if (xfer->rx_nbits != SPI_NBITS_SINGLE && xfer->rx_nbits != SPI_NBITS_DUAL && xfer->rx_nbits != SPI_NBITS_QUAD) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 7edc8dc6bbab..d7f44deab5b1 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -450,15 +450,6 @@ depends on (ARCH_STI || ARCH_STM32) && OF source "drivers/thermal/st/Kconfig" endmenu -config TANGO_THERMAL - tristate "Tango thermal management" - depends on ARCH_TANGO || COMPILE_TEST - help - Enable the Tango thermal driver, which supports the primitive - temperature sensor embedded in Tango chips since the SMP8758. - This sensor only generates a 1-bit signal to indicate whether - the die temperature exceeds a programmable threshold. - source "drivers/thermal/tegra/Kconfig" config GENERIC_ADC_THERMAL @@ -476,14 +467,6 @@ depends on (ARCH_QCOM && OF) || COMPILE_TEST source "drivers/thermal/qcom/Kconfig" endmenu -config ZX2967_THERMAL - tristate "Thermal sensors on zx2967 SoC" - depends on ARCH_ZX || COMPILE_TEST - help - Enable the zx2967 thermal sensors driver, which supports - the primitive temperature sensor embedded in zx2967 SoCs. - This sensor generates the real time die temperature. - config UNIPHIER_THERMAL tristate "Socionext UniPhier thermal driver" depends on ARCH_UNIPHIER || COMPILE_TEST diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index b64dd50a6629..82fc3e616e54 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -42,7 +42,6 @@ obj-y += samsung/ obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o -obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o @@ -57,7 +56,6 @@ obj-y += tegra/ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o -obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index f5af2571f9b7..10af3341e5ea 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -485,7 +485,7 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, frequency = get_state_freq(cpufreq_cdev, state); ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency); - if (ret > 0) { + if (ret >= 0) { cpufreq_cdev->cpufreq_state = state; cpus = cpufreq_cdev->policy->cpus; max_capacity = arch_scale_cpu_capacity(cpumask_first(cpus)); diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c index 4d74994f160a..180edec34e07 100644 --- a/drivers/thermal/da9062-thermal.c +++ b/drivers/thermal/da9062-thermal.c @@ -95,7 +95,7 @@ static void da9062_thermal_poll_on(struct work_struct *work) thermal_zone_device_update(thermal->zone, THERMAL_EVENT_UNSPECIFIED); - delay = msecs_to_jiffies(thermal->zone->passive_delay); + delay = thermal->zone->passive_delay_jiffies; queue_delayed_work(system_freezable_wq, &thermal->work, delay); return; } @@ -245,7 +245,7 @@ static int da9062_thermal_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "TJUNC temperature polling period set at %d ms\n", - thermal->zone->passive_delay); + jiffies_to_msecs(thermal->zone->passive_delay_jiffies)); ret = platform_get_irq_byname(pdev, "THERMAL"); if (ret < 0) { diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c index 7a4170a0b51f..92acae53df49 100644 --- a/drivers/thermal/gov_power_allocator.c +++ b/drivers/thermal/gov_power_allocator.c @@ -258,7 +258,7 @@ static u32 pid_controller(struct thermal_zone_device *tz, * power being applied, slowing down the controller) */ d = mul_frac(tz->tzp->k_d, err - params->prev_err); - d = div_frac(d, tz->passive_delay); + d = div_frac(d, jiffies_to_msecs(tz->passive_delay_jiffies)); params->prev_err = err; power_range = p + i + d; @@ -589,6 +589,34 @@ static void allow_maximum_power(struct thermal_zone_device *tz) mutex_unlock(&tz->lock); } +/** + * check_power_actors() - Check all cooling devices and warn when they are + * not power actors + * @tz: thermal zone to operate on + * + * Check all cooling devices in the @tz and warn every time they are missing + * power actor API. The warning should help to investigate the issue, which + * could be e.g. lack of Energy Model for a given device. + * + * Return: 0 on success, -EINVAL if any cooling device does not implement + * the power actor API. + */ +static int check_power_actors(struct thermal_zone_device *tz) +{ + struct thermal_instance *instance; + int ret = 0; + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (!cdev_is_power_actor(instance->cdev)) { + dev_warn(&tz->device, "power_allocator: %s is not a power actor\n", + instance->cdev->type); + ret = -EINVAL; + } + } + + return ret; +} + /** * power_allocator_bind() - bind the power_allocator governor to a thermal zone * @tz: thermal zone to bind it to @@ -596,7 +624,8 @@ static void allow_maximum_power(struct thermal_zone_device *tz) * Initialize the PID controller parameters and bind it to the thermal * zone. * - * Return: 0 on success, or -ENOMEM if we ran out of memory. + * Return: 0 on success, or -ENOMEM if we ran out of memory, or -EINVAL + * when there are unsupported cooling devices in the @tz. */ static int power_allocator_bind(struct thermal_zone_device *tz) { @@ -604,6 +633,10 @@ static int power_allocator_bind(struct thermal_zone_device *tz) struct power_allocator_params *params; int control_temp; + ret = check_power_actors(tz); + if (ret) + return ret; + params = kzalloc(sizeof(*params), GFP_KERNEL); if (!params) return -ENOMEM; diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index 2ae7198d3067..12acb12aac50 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -109,7 +109,7 @@ static void update_passive_instance(struct thermal_zone_device *tz, * If value is +1, activate a passive instance. * If value is -1, deactivate a passive instance. */ - if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE) + if (type == THERMAL_TRIP_PASSIVE) tz->passive += value; } @@ -122,13 +122,8 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) bool throttle = false; int old_target; - if (trip == THERMAL_TRIPS_NONE) { - trip_temp = tz->forced_passive; - trip_type = THERMAL_TRIPS_NONE; - } else { - tz->ops->get_trip_temp(tz, trip, &trip_temp); - tz->ops->get_trip_type(tz, trip, &trip_type); - } + tz->ops->get_trip_temp(tz, trip, &trip_temp); + tz->ops->get_trip_type(tz, trip, &trip_type); trend = get_tz_trend(tz, trip); @@ -189,9 +184,6 @@ static int step_wise_throttle(struct thermal_zone_device *tz, int trip) thermal_zone_trip_update(tz, trip); - if (tz->forced_passive) - thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE); - mutex_lock(&tz->lock); list_for_each_entry(instance, &tz->thermal_instances, tz_node) diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c index 6e479deff76b..d1248ba943a4 100644 --- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c +++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c @@ -146,12 +146,18 @@ static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone, return 0; } +static void int340x_thermal_critical(struct thermal_zone_device *zone) +{ + dev_dbg(&zone->device, "%s: critical temperature reached\n", zone->type); +} + static struct thermal_zone_device_ops int340x_thermal_zone_ops = { .get_temp = int340x_thermal_get_zone_temp, .get_trip_temp = int340x_thermal_get_trip_temp, .get_trip_type = int340x_thermal_get_trip_type, .set_trip_temp = int340x_thermal_set_trip_temp, .get_trip_hyst = int340x_thermal_get_trip_hyst, + .critical = int340x_thermal_critical, }; static int int340x_thermal_get_trip_config(acpi_handle handle, char *name, diff --git a/drivers/thermal/intel/intel_pch_thermal.c b/drivers/thermal/intel/intel_pch_thermal.c index 41723c6c6c0c..527c91f5960b 100644 --- a/drivers/thermal/intel/intel_pch_thermal.c +++ b/drivers/thermal/intel/intel_pch_thermal.c @@ -326,10 +326,16 @@ static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *tem return 0; } +static void pch_critical(struct thermal_zone_device *tzd) +{ + dev_dbg(&tzd->device, "%s: critical temperature reached\n", tzd->type); +} + static struct thermal_zone_device_ops tzd_ops = { .get_temp = pch_thermal_get_temp, .get_trip_type = pch_get_trip_type, .get_trip_temp = pch_get_trip_temp, + .critical = pch_critical, }; enum board_ids { diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c index 9eadd2d6413e..d35e5313bea4 100644 --- a/drivers/thermal/khadas_mcu_fan.c +++ b/drivers/thermal/khadas_mcu_fan.c @@ -100,7 +100,6 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev) return ret; } ctx->cdev = cdev; - thermal_cdev_update(cdev); return 0; } diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index aa9c1d80fae4..8d5ac2df26dc 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -10,6 +10,17 @@ config QCOM_TSENS Also able to set threshold temperature for both hot and cold and update when a threshold is reached. +config QCOM_SPMI_ADC_TM5 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5" + depends on OF && SPMI && IIO + select REGMAP_SPMI + select QCOM_VADC_COMMON + help + This enables the thermal driver for the ADC thermal monitoring + device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index ec86eef7f6a6..252ea7d9da0b 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o +obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c new file mode 100644 index 000000000000..b460b56e981c --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Linaro Limited + * + * Based on original driver: + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each + * channel is programmed to use one of ADC channels for voltage comparison. + * Voltages are programmed using ADC codes, so we have to convert temp to + * voltage and then to ADC code value. + * + * Configuration of TM channels must match configuration of corresponding ADC + * channels. + */ + +#define ADC5_MAX_CHANNEL 0xc0 +#define ADC_TM5_NUM_CHANNELS 8 + +#define ADC_TM5_STATUS_LOW 0x0a + +#define ADC_TM5_STATUS_HIGH 0x0b + +#define ADC_TM5_NUM_BTM 0x0f + +#define ADC_TM5_ADC_DIG_PARAM 0x42 + +#define ADC_TM5_FAST_AVG_CTL (ADC_TM5_ADC_DIG_PARAM + 1) +#define ADC_TM5_FAST_AVG_EN BIT(7) + +#define ADC_TM5_MEAS_INTERVAL_CTL (ADC_TM5_ADC_DIG_PARAM + 2) +#define ADC_TM5_TIMER1 3 /* 3.9ms */ + +#define ADC_TM5_MEAS_INTERVAL_CTL2 (ADC_TM5_ADC_DIG_PARAM + 3) +#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK 0xf0 +#define ADC_TM5_TIMER2 10 /* 1 second */ +#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK 0xf +#define ADC_TM5_TIMER3 4 /* 4 second */ + +#define ADC_TM_EN_CTL1 0x46 +#define ADC_TM_EN BIT(7) +#define ADC_TM_CONV_REQ 0x47 +#define ADC_TM_CONV_REQ_EN BIT(7) + +#define ADC_TM5_M_CHAN_BASE 0x60 + +#define ADC_TM5_M_ADC_CH_SEL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 0) +#define ADC_TM5_M_LOW_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 1) +#define ADC_TM5_M_LOW_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 2) +#define ADC_TM5_M_HIGH_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 3) +#define ADC_TM5_M_HIGH_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 4) +#define ADC_TM5_M_MEAS_INTERVAL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 5) +#define ADC_TM5_M_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 6) +#define ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK 0xf +#define ADC_TM5_M_CTL_CAL_SEL_MASK 0x30 +#define ADC_TM5_M_CTL_CAL_VAL 0x40 +#define ADC_TM5_M_EN(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 7) +#define ADC_TM5_M_MEAS_EN BIT(7) +#define ADC_TM5_M_HIGH_THR_INT_EN BIT(1) +#define ADC_TM5_M_LOW_THR_INT_EN BIT(0) + +enum adc5_timer_select { + ADC5_TIMER_SEL_1 = 0, + ADC5_TIMER_SEL_2, + ADC5_TIMER_SEL_3, + ADC5_TIMER_SEL_NONE, +}; + +struct adc_tm5_data { + const u32 full_scale_code_volt; + unsigned int *decimation; + unsigned int *hw_settle; +}; + +enum adc_tm5_cal_method { + ADC_TM5_NO_CAL = 0, + ADC_TM5_RATIOMETRIC_CAL, + ADC_TM5_ABSOLUTE_CAL +}; + +struct adc_tm5_chip; + +/** + * struct adc_tm5_channel - ADC Thermal Monitoring channel data. + * @channel: channel number. + * @adc_channel: corresponding ADC channel number. + * @cal_method: calibration method. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @iio: IIO channel instance used by this channel. + * @chip: ADC TM chip instance. + * @tzd: thermal zone device used by this channel. + */ +struct adc_tm5_channel { + unsigned int channel; + unsigned int adc_channel; + enum adc_tm5_cal_method cal_method; + unsigned int prescale; + unsigned int hw_settle_time; + struct iio_channel *iio; + struct adc_tm5_chip *chip; + struct thermal_zone_device *tzd; +}; + +/** + * struct adc_tm5_chip - ADC Thermal Monitoring properties + * @regmap: SPMI ADC5 Thermal Monitoring peripheral register map field. + * @dev: SPMI ADC5 device. + * @data: software configuration data. + * @channels: array of ADC TM channel data. + * @nchannels: amount of channels defined/allocated + * @decimation: sampling rate supported for the channel. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + * @base: base address of TM registers. + */ +struct adc_tm5_chip { + struct regmap *regmap; + struct device *dev; + const struct adc_tm5_data *data; + struct adc_tm5_channel *channels; + unsigned int nchannels; + unsigned int decimation; + unsigned int avg_samples; + u16 base; +}; + +static const struct adc_tm5_data adc_tm5_data_pmic = { + .full_scale_code_volt = 0x70e4, + .decimation = (unsigned int []) { 250, 420, 840 }, + .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700, + 1000, 2000, 4000, 8000, 16000, 32000, + 64000, 128000 }, +}; + +static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len) +{ + return regmap_bulk_read(adc_tm->regmap, adc_tm->base + offset, data, len); +} + +static int adc_tm5_write(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len) +{ + return regmap_bulk_write(adc_tm->regmap, adc_tm->base + offset, data, len); +} + +static int adc_tm5_reg_update(struct adc_tm5_chip *adc_tm, u16 offset, u8 mask, u8 val) +{ + return regmap_write_bits(adc_tm->regmap, adc_tm->base + offset, mask, val); +} + +static irqreturn_t adc_tm5_isr(int irq, void *data) +{ + struct adc_tm5_chip *chip = data; + u8 status_low, status_high, ctl; + int ret, i; + + ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, sizeof(status_low)); + if (unlikely(ret)) { + dev_err(chip->dev, "read status low failed: %d\n", ret); + return IRQ_HANDLED; + } + + ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, sizeof(status_high)); + if (unlikely(ret)) { + dev_err(chip->dev, "read status high failed: %d\n", ret); + return IRQ_HANDLED; + } + + for (i = 0; i < chip->nchannels; i++) { + bool upper_set = false, lower_set = false; + unsigned int ch = chip->channels[i].channel; + + /* No TZD, we warned at the boot time */ + if (!chip->channels[i].tzd) + continue; + + ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, sizeof(ctl)); + if (unlikely(ret)) { + dev_err(chip->dev, "ctl read failed: %d, channel %d\n", ret, i); + continue; + } + + if (!(ctl & ADC_TM5_M_MEAS_EN)) + continue; + + lower_set = (status_low & BIT(ch)) && + (ctl & ADC_TM5_M_LOW_THR_INT_EN); + + upper_set = (status_high & BIT(ch)) && + (ctl & ADC_TM5_M_HIGH_THR_INT_EN); + + if (upper_set || lower_set) + thermal_zone_device_update(chip->channels[i].tzd, + THERMAL_EVENT_UNSPECIFIED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_get_temp(void *data, int *temp) +{ + struct adc_tm5_channel *channel = data; + int ret; + + if (!channel || !channel->iio) + return -EINVAL; + + ret = iio_read_channel_processed(channel->iio, temp); + if (ret < 0) + return ret; + + if (ret != IIO_VAL_INT) + return -EINVAL; + + return 0; +} + +static int adc_tm5_disable_channel(struct adc_tm5_channel *channel) +{ + struct adc_tm5_chip *chip = channel->chip; + unsigned int reg = ADC_TM5_M_EN(channel->channel); + + return adc_tm5_reg_update(chip, reg, + ADC_TM5_M_MEAS_EN | + ADC_TM5_M_HIGH_THR_INT_EN | + ADC_TM5_M_LOW_THR_INT_EN, + 0); +} + +static int adc_tm5_enable(struct adc_tm5_chip *chip) +{ + int ret; + u8 data; + + data = ADC_TM_EN; + ret = adc_tm5_write(chip, ADC_TM_EN_CTL1, &data, sizeof(data)); + if (ret < 0) { + dev_err(chip->dev, "adc-tm enable failed\n"); + return ret; + } + + data = ADC_TM_CONV_REQ_EN; + ret = adc_tm5_write(chip, ADC_TM_CONV_REQ, &data, sizeof(data)); + if (ret < 0) { + dev_err(chip->dev, "adc-tm request conversion failed\n"); + return ret; + } + + return 0; +} + +static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high) +{ + struct adc_tm5_chip *chip = channel->chip; + u8 buf[8]; + u16 reg = ADC_TM5_M_ADC_CH_SEL_CTL(channel->channel); + int ret; + + ret = adc_tm5_read(chip, reg, buf, sizeof(buf)); + if (ret) { + dev_err(chip->dev, "channel %d params read failed: %d\n", channel->channel, ret); + return ret; + } + + buf[0] = channel->adc_channel; + + /* High temperature corresponds to low voltage threshold */ + if (high != INT_MAX) { + u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, + chip->data->full_scale_code_volt, high); + + buf[1] = adc_code & 0xff; + buf[2] = adc_code >> 8; + buf[7] |= ADC_TM5_M_LOW_THR_INT_EN; + } else { + buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN; + } + + /* Low temperature corresponds to high voltage threshold */ + if (low != -INT_MAX) { + u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale, + chip->data->full_scale_code_volt, low); + + buf[3] = adc_code & 0xff; + buf[4] = adc_code >> 8; + buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN; + } else { + buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN; + } + + buf[5] = ADC5_TIMER_SEL_2; + + /* Set calibration select, hw_settle delay */ + buf[6] &= ~ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK; + buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK, channel->hw_settle_time); + buf[6] &= ~ADC_TM5_M_CTL_CAL_SEL_MASK; + buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_CAL_SEL_MASK, channel->cal_method); + + buf[7] |= ADC_TM5_M_MEAS_EN; + + ret = adc_tm5_write(chip, reg, buf, sizeof(buf)); + if (ret) { + dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret); + return ret; + } + + return adc_tm5_enable(chip); +} + +static int adc_tm5_set_trips(void *data, int low, int high) +{ + struct adc_tm5_channel *channel = data; + struct adc_tm5_chip *chip; + int ret; + + if (!channel) + return -EINVAL; + + chip = channel->chip; + dev_dbg(chip->dev, "%d:low(mdegC):%d, high(mdegC):%d\n", + channel->channel, low, high); + + if (high == INT_MAX && low <= -INT_MAX) + ret = adc_tm5_disable_channel(channel); + else + ret = adc_tm5_configure(channel, low, high); + + return ret; +} + +static struct thermal_zone_of_device_ops adc_tm5_ops = { + .get_temp = adc_tm5_get_temp, + .set_trips = adc_tm5_set_trips, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm) +{ + unsigned int i; + struct thermal_zone_device *tzd; + + for (i = 0; i < adc_tm->nchannels; i++) { + adc_tm->channels[i].chip = adc_tm; + + tzd = devm_thermal_zone_of_sensor_register(adc_tm->dev, + adc_tm->channels[i].channel, + &adc_tm->channels[i], + &adc_tm5_ops); + if (IS_ERR(tzd)) { + dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n", + adc_tm->channels[i].channel, PTR_ERR(tzd)); + return PTR_ERR(tzd); + } + adc_tm->channels[i].tzd = tzd; + } + + return 0; +} + +static int adc_tm5_init(struct adc_tm5_chip *chip) +{ + u8 buf[4], channels_available; + int ret; + unsigned int i; + + ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM, + &channels_available, sizeof(channels_available)); + if (ret) { + dev_err(chip->dev, "read failed for BTM channels\n"); + return ret; + } + + for (i = 0; i < chip->nchannels; i++) { + if (chip->channels[i].channel >= channels_available) { + dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel); + return -EINVAL; + } + } + + buf[0] = chip->decimation; + buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN; + buf[2] = ADC_TM5_TIMER1; + buf[3] = FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL2_MASK, ADC_TM5_TIMER2) | + FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL3_MASK, ADC_TM5_TIMER3); + + ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf)); + if (ret) { + dev_err(chip->dev, "block write failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm, + struct adc_tm5_channel *channel, + struct device_node *node) +{ + const char *name = node->name; + u32 chan, value, varr[2]; + int ret; + struct device *dev = adc_tm->dev; + struct of_phandle_args args; + + ret = of_property_read_u32(node, "reg", &chan); + if (ret) { + dev_err(dev, "%s: invalid channel number %d\n", name, ret); + return ret; + } + + if (chan >= ADC_TM5_NUM_CHANNELS) { + dev_err(dev, "%s: channel number too big: %d\n", name, chan); + return -EINVAL; + } + + channel->channel = chan; + + /* + * We are tied to PMIC's ADC controller, which always use single + * argument for channel number. So don't bother parsing + * #io-channel-cells, just enforce cell_count = 1. + */ + ret = of_parse_phandle_with_fixed_args(node, "io-channels", 1, 0, &args); + if (ret < 0) { + dev_err(dev, "%s: error parsing ADC channel number %d: %d\n", name, chan, ret); + return ret; + } + of_node_put(args.np); + + if (args.args_count != 1 || args.args[0] >= ADC5_MAX_CHANNEL) { + dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan); + return ret; + } + channel->adc_channel = args.args[0]; + + channel->iio = devm_of_iio_channel_get_by_name(adc_tm->dev, node, NULL); + if (IS_ERR(channel->iio)) { + ret = PTR_ERR(channel->iio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "%s: error getting channel: %d\n", name, ret); + return ret; + } + + ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%s: invalid pre-scaling <%d %d>\n", + name, varr[0], varr[1]); + return ret; + } + channel->prescale = ret; + } else { + /* 1:1 prescale is index 0 */ + channel->prescale = 0; + } + + ret = of_property_read_u32(node, "qcom,hw-settle-time-us", &value); + if (!ret) { + ret = qcom_adc5_hw_settle_time_from_dt(value, adc_tm->data->hw_settle); + if (ret < 0) { + dev_err(dev, "%s invalid hw-settle-time-us %d us\n", + name, value); + return ret; + } + channel->hw_settle_time = ret; + } else { + channel->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + if (of_property_read_bool(node, "qcom,ratiometric")) + channel->cal_method = ADC_TM5_RATIOMETRIC_CAL; + else + channel->cal_method = ADC_TM5_ABSOLUTE_CAL; + + return 0; +} + +static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node) +{ + struct adc_tm5_channel *channels; + struct device_node *child; + u32 value; + int ret; + struct device *dev = adc_tm->dev; + + adc_tm->nchannels = of_get_available_child_count(node); + if (!adc_tm->nchannels) + return -EINVAL; + + adc_tm->channels = devm_kcalloc(dev, adc_tm->nchannels, + sizeof(*adc_tm->channels), GFP_KERNEL); + if (!adc_tm->channels) + return -ENOMEM; + + channels = adc_tm->channels; + + adc_tm->data = of_device_get_match_data(dev); + if (!adc_tm->data) + adc_tm->data = &adc_tm5_data_pmic; + + ret = of_property_read_u32(node, "qcom,decimation", &value); + if (!ret) { + ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation); + if (ret < 0) { + dev_err(dev, "invalid decimation %d\n", value); + return ret; + } + adc_tm->decimation = ret; + } else { + adc_tm->decimation = ADC5_DECIMATION_DEFAULT; + } + + ret = of_property_read_u32(node, "qcom,avg-samples", &value); + if (!ret) { + ret = qcom_adc5_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "invalid avg-samples %d\n", value); + return ret; + } + adc_tm->avg_samples = ret; + } else { + adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + for_each_available_child_of_node(node, child) { + ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child); + if (ret) { + of_node_put(child); + return ret; + } + + channels++; + } + + return 0; +} + +static int adc_tm5_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct adc_tm5_chip *adc_tm; + struct regmap *regmap; + int ret, irq; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = of_property_read_u32(node, "reg", ®); + if (ret) + return ret; + + adc_tm = devm_kzalloc(&pdev->dev, sizeof(*adc_tm), GFP_KERNEL); + if (!adc_tm) + return -ENOMEM; + + adc_tm->regmap = regmap; + adc_tm->dev = dev; + adc_tm->base = reg; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "get_irq failed: %d\n", irq); + return irq; + } + + ret = adc_tm5_get_dt_data(adc_tm, node); + if (ret) { + dev_err(dev, "get dt data failed: %d\n", ret); + return ret; + } + + ret = adc_tm5_init(adc_tm); + if (ret) { + dev_err(dev, "adc-tm init failed\n"); + return ret; + } + + ret = adc_tm5_register_tzd(adc_tm); + if (ret) { + dev_err(dev, "tzd register failed\n"); + return ret; + } + + return devm_request_threaded_irq(dev, irq, NULL, adc_tm5_isr, + IRQF_ONESHOT, "pm-adc-tm5", adc_tm); +} + +static const struct of_device_id adc_tm5_match_table[] = { + { + .compatible = "qcom,spmi-adc-tm5", + .data = &adc_tm5_data_pmic, + }, + { } +}; +MODULE_DEVICE_TABLE(of, adc_tm5_match_table); + +static struct platform_driver adc_tm5_driver = { + .driver = { + .name = "qcom-spmi-adc-tm5", + .of_match_table = adc_tm5_match_table, + }, + .probe = adc_tm5_probe, +}; +module_platform_driver(adc_tm5_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/tango_thermal.c b/drivers/thermal/tango_thermal.c deleted file mode 100644 index 304b461e12aa..000000000000 --- a/drivers/thermal/tango_thermal.c +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include -#include -#include -#include -#include - -/* - * According to a data sheet draft, "this temperature sensor uses a bandgap - * type of circuit to compare a voltage which has a negative temperature - * coefficient with a voltage that is proportional to absolute temperature. - * A resistor bank allows 41 different temperature thresholds to be selected - * and the logic output will then indicate whether the actual die temperature - * lies above or below the selected threshold." - */ - -#define TEMPSI_CMD 0 -#define TEMPSI_RES 4 -#define TEMPSI_CFG 8 - -#define CMD_OFF 0 -#define CMD_ON 1 -#define CMD_READ 2 - -#define IDX_MIN 15 -#define IDX_MAX 40 - -struct tango_thermal_priv { - void __iomem *base; - int thresh_idx; -}; - -static bool temp_above_thresh(void __iomem *base, int thresh_idx) -{ - writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); - usleep_range(10, 20); - writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); - - return readl(base + TEMPSI_RES); -} - -static int tango_get_temp(void *arg, int *res) -{ - struct tango_thermal_priv *priv = arg; - int idx = priv->thresh_idx; - - if (temp_above_thresh(priv->base, idx)) { - /* Search upward by incrementing thresh_idx */ - while (idx < IDX_MAX && temp_above_thresh(priv->base, ++idx)) - cpu_relax(); - idx = idx - 1; /* always return lower bound */ - } else { - /* Search downward by decrementing thresh_idx */ - while (idx > IDX_MIN && !temp_above_thresh(priv->base, --idx)) - cpu_relax(); - } - - *res = (idx * 9 / 2 - 38) * 1000; /* millidegrees Celsius */ - priv->thresh_idx = idx; - - return 0; -} - -static const struct thermal_zone_of_device_ops ops = { - .get_temp = tango_get_temp, -}; - -static void tango_thermal_init(struct tango_thermal_priv *priv) -{ - writel(0, priv->base + TEMPSI_CFG); - writel(CMD_ON, priv->base + TEMPSI_CMD); -} - -static int tango_thermal_probe(struct platform_device *pdev) -{ - struct resource *res; - struct tango_thermal_priv *priv; - struct thermal_zone_device *tzdev; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); - - platform_set_drvdata(pdev, priv); - priv->thresh_idx = IDX_MIN; - tango_thermal_init(priv); - - tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops); - return PTR_ERR_OR_ZERO(tzdev); -} - -static int __maybe_unused tango_thermal_resume(struct device *dev) -{ - tango_thermal_init(dev_get_drvdata(dev)); - return 0; -} - -static SIMPLE_DEV_PM_OPS(tango_thermal_pm, NULL, tango_thermal_resume); - -static const struct of_device_id tango_sensor_ids[] = { - { - .compatible = "sigma,smp8758-thermal", - }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, tango_sensor_ids); - -static struct platform_driver tango_thermal_driver = { - .probe = tango_thermal_probe, - .driver = { - .name = "tango-thermal", - .of_match_table = tango_sensor_ids, - .pm = &tango_thermal_pm, - }, -}; - -module_platform_driver(tango_thermal_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sigma Designs"); -MODULE_DESCRIPTION("Tango temperature sensor"); diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 4a291d205d5c..996c038f83a4 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -289,16 +289,11 @@ static int __init thermal_register_governors(void) * - Critical trip point will cause a system shutdown. */ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, - int delay) + unsigned long delay) { - if (delay > 1000) + if (delay) mod_delayed_work(system_freezable_power_efficient_wq, - &tz->poll_queue, - round_jiffies(msecs_to_jiffies(delay))); - else if (delay) - mod_delayed_work(system_freezable_power_efficient_wq, - &tz->poll_queue, - msecs_to_jiffies(delay)); + &tz->poll_queue, delay); else cancel_delayed_work(&tz->poll_queue); } @@ -317,9 +312,9 @@ static void monitor_thermal_zone(struct thermal_zone_device *tz) mutex_lock(&tz->lock); if (!stop && tz->passive) - thermal_zone_device_set_polling(tz, tz->passive_delay); - else if (!stop && tz->polling_delay) - thermal_zone_device_set_polling(tz, tz->polling_delay); + thermal_zone_device_set_polling(tz, tz->passive_delay_jiffies); + else if (!stop && tz->polling_delay_jiffies) + thermal_zone_device_set_polling(tz, tz->polling_delay_jiffies); else thermal_zone_device_set_polling(tz, 0); @@ -412,9 +407,6 @@ static void handle_critical_trips(struct thermal_zone_device *tz, trace_thermal_zone_trip(tz, trip, trip_type); - if (tz->ops->notify) - tz->ops->notify(tz, trip, trip_type); - if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot) tz->ops->hot(tz); else if (trip_type == THERMAL_TRIP_CRITICAL) @@ -486,12 +478,6 @@ static void thermal_zone_device_init(struct thermal_zone_device *tz) pos->initialized = false; } -static void thermal_zone_device_reset(struct thermal_zone_device *tz) -{ - tz->passive = 0; - thermal_zone_device_init(tz); -} - static int thermal_zone_device_set_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode) { @@ -601,26 +587,6 @@ static void thermal_zone_device_check(struct work_struct *work) thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } -void thermal_zone_device_rebind_exception(struct thermal_zone_device *tz, - const char *cdev_type, size_t size) -{ - struct thermal_cooling_device *cdev = NULL; - - mutex_lock(&thermal_list_lock); - list_for_each_entry(cdev, &thermal_cdev_list, node) { - /* skip non matching cdevs */ - if (strncmp(cdev_type, cdev->type, size)) - continue; - - /* re binding the exception matching the type pattern */ - thermal_zone_bind_cooling_device(tz, THERMAL_TRIPS_NONE, cdev, - THERMAL_NO_LIMIT, - THERMAL_NO_LIMIT, - THERMAL_WEIGHT_DEFAULT); - } - mutex_unlock(&thermal_list_lock); -} - int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *), void *data) { @@ -688,23 +654,6 @@ struct thermal_zone_device *thermal_zone_get_by_id(int id) return match; } -void thermal_zone_device_unbind_exception(struct thermal_zone_device *tz, - const char *cdev_type, size_t size) -{ - struct thermal_cooling_device *cdev = NULL; - - mutex_lock(&thermal_list_lock); - list_for_each_entry(cdev, &thermal_cdev_list, node) { - /* skip non matching cdevs */ - if (strncmp(cdev_type, cdev->type, size)) - continue; - /* unbinding the exception matching the type pattern */ - thermal_zone_unbind_cooling_device(tz, THERMAL_TRIPS_NONE, - cdev); - } - mutex_unlock(&thermal_list_lock); -} - /* * Device management section: cooling devices, zones devices, and binding * @@ -750,7 +699,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, unsigned long max_state; int result, ret; - if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE)) + if (trip >= tz->trips || trip < 0) return -EINVAL; list_for_each_entry(pos1, &thermal_tz_list, node) { @@ -1352,8 +1301,9 @@ thermal_zone_device_register(const char *type, int trips, int mask, tz->device.class = &thermal_class; tz->devdata = devdata; tz->trips = trips; - tz->passive_delay = passive_delay; - tz->polling_delay = polling_delay; + + thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay); + thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay); /* sys I/F */ /* Add nodes that are always present via .groups */ @@ -1407,7 +1357,7 @@ thermal_zone_device_register(const char *type, int trips, int mask, INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check); - thermal_zone_device_reset(tz); + thermal_zone_device_init(tz); /* Update the new thermal zone and mark it as already updated. */ if (atomic_cmpxchg(&tz->need_update, 1, 0)) thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 8df600fa7b79..86b8cef7310e 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -65,6 +65,8 @@ static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev) cdev->ops->power2state; } +void thermal_cdev_update(struct thermal_cooling_device *); + /** * struct thermal_trip - representation of a point in temperature domain * @np: pointer to struct device_node that this trip point was created from @@ -118,15 +120,12 @@ struct thermal_instance { int thermal_register_governor(struct thermal_governor *); void thermal_unregister_governor(struct thermal_governor *); -void thermal_zone_device_rebind_exception(struct thermal_zone_device *, - const char *, size_t); -void thermal_zone_device_unbind_exception(struct thermal_zone_device *, - const char *, size_t); int thermal_zone_device_set_policy(struct thermal_zone_device *, char *); int thermal_build_list_of_policies(char *buf); /* Helpers */ void thermal_zone_set_trips(struct thermal_zone_device *tz); +void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms); /* sysfs I/F */ int thermal_zone_create_device_groups(struct thermal_zone_device *, int); diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c index c94bc824e5d3..7f50f412e02a 100644 --- a/drivers/thermal/thermal_helpers.c +++ b/drivers/thermal/thermal_helpers.c @@ -175,6 +175,13 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz) mutex_unlock(&tz->lock); } +void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms) +{ + *delay_jiffies = msecs_to_jiffies(delay_ms); + if (delay_ms > 1000) + *delay_jiffies = round_jiffies(*delay_jiffies); +} + static void thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int target) { diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 0866e949339b..345917a58f2f 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -216,49 +216,6 @@ trip_point_hyst_show(struct device *dev, struct device_attribute *attr, return ret ? ret : sprintf(buf, "%d\n", temperature); } -static ssize_t -passive_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct thermal_zone_device *tz = to_thermal_zone(dev); - int state; - - if (sscanf(buf, "%d\n", &state) != 1) - return -EINVAL; - - /* sanity check: values below 1000 millicelcius don't make sense - * and can cause the system to go into a thermal heart attack - */ - if (state && state < 1000) - return -EINVAL; - - if (state && !tz->forced_passive) { - if (!tz->passive_delay) - tz->passive_delay = 1000; - thermal_zone_device_rebind_exception(tz, "Processor", - sizeof("Processor")); - } else if (!state && tz->forced_passive) { - tz->passive_delay = 0; - thermal_zone_device_unbind_exception(tz, "Processor", - sizeof("Processor")); - } - - tz->forced_passive = state; - - thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); - - return count; -} - -static ssize_t -passive_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct thermal_zone_device *tz = to_thermal_zone(dev); - - return sprintf(buf, "%d\n", tz->forced_passive); -} - static ssize_t policy_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -403,7 +360,6 @@ static DEVICE_ATTR_RW(sustainable_power); /* These thermal zone device attributes are created based on conditions */ static DEVICE_ATTR_RW(mode); -static DEVICE_ATTR_RW(passive); /* These attributes are unconditionally added to a thermal zone */ static struct attribute *thermal_zone_dev_attrs[] = { @@ -438,45 +394,9 @@ static const struct attribute_group thermal_zone_mode_attribute_group = { .attrs = thermal_zone_mode_attrs, }; -/* We expose passive only if passive trips are present */ -static struct attribute *thermal_zone_passive_attrs[] = { - &dev_attr_passive.attr, - NULL, -}; - -static umode_t thermal_zone_passive_is_visible(struct kobject *kobj, - struct attribute *attr, - int attrno) -{ - struct device *dev = kobj_to_dev(kobj); - struct thermal_zone_device *tz; - enum thermal_trip_type trip_type; - int count, passive = 0; - - tz = container_of(dev, struct thermal_zone_device, device); - - for (count = 0; count < tz->trips && !passive; count++) { - tz->ops->get_trip_type(tz, count, &trip_type); - - if (trip_type == THERMAL_TRIP_PASSIVE) - passive = 1; - } - - if (!passive) - return attr->mode; - - return 0; -} - -static const struct attribute_group thermal_zone_passive_attribute_group = { - .attrs = thermal_zone_passive_attrs, - .is_visible = thermal_zone_passive_is_visible, -}; - static const struct attribute_group *thermal_zone_attribute_groups[] = { &thermal_zone_attribute_group, &thermal_zone_mode_attribute_group, - &thermal_zone_passive_attribute_group, /* This is not NULL terminated as we create the group dynamically */ }; @@ -955,10 +875,7 @@ trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) instance = container_of(attr, struct thermal_instance, attr); - if (instance->trip == THERMAL_TRIPS_NONE) - return sprintf(buf, "-1\n"); - else - return sprintf(buf, "%d\n", instance->trip); + return sprintf(buf, "%d\n", instance->trip); } ssize_t diff --git a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c index fdb8a495ab69..b4ef7340ac9b 100644 --- a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c @@ -24,7 +24,7 @@ omap4430_mpu_temp_sensor_registers = { .bgap_dtemp_mask = OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK, .bgap_mode_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET, - .mode_ctrl_mask = OMAP4430_SINGLE_MODE_MASK, + .mode_ctrl_mask = OMAP4430_CONTINUOUS_MODE_MASK, .bgap_efuse = OMAP4430_FUSE_OPP_BGAP, }; @@ -58,7 +58,8 @@ omap4430_adc_to_temp[OMAP4430_ADC_END_VALUE - OMAP4430_ADC_START_VALUE + 1] = { const struct ti_bandgap_data omap4430_data = { .features = TI_BANDGAP_FEATURE_MODE_CONFIG | TI_BANDGAP_FEATURE_CLK_CTRL | - TI_BANDGAP_FEATURE_POWER_SWITCH, + TI_BANDGAP_FEATURE_POWER_SWITCH | + TI_BANDGAP_FEATURE_CONT_MODE_ONLY, .fclock_name = "bandgap_fclk", .div_ck_name = "bandgap_fclk", .conv_table = omap4430_adc_to_temp, @@ -96,7 +97,7 @@ omap4460_mpu_temp_sensor_registers = { .mask_cold_mask = OMAP4460_MASK_COLD_MASK, .bgap_mode_ctrl = OMAP4460_BGAP_CTRL_OFFSET, - .mode_ctrl_mask = OMAP4460_SINGLE_MODE_MASK, + .mode_ctrl_mask = OMAP4460_CONTINUOUS_MODE_MASK, .bgap_counter = OMAP4460_BGAP_COUNTER_OFFSET, .counter_mask = OMAP4460_COUNTER_MASK, diff --git a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h index 9a3955c3853b..c63f439e01d6 100644 --- a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h +++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h @@ -40,7 +40,7 @@ /* OMAP4430.TEMP_SENSOR bits */ #define OMAP4430_BGAP_TEMPSOFF_MASK BIT(12) #define OMAP4430_BGAP_TSHUT_MASK BIT(11) -#define OMAP4430_SINGLE_MODE_MASK BIT(10) +#define OMAP4430_CONTINUOUS_MODE_MASK BIT(10) #define OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK BIT(9) #define OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK BIT(8) #define OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK (0xff << 0) @@ -113,7 +113,7 @@ #define OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK (0x3ff << 0) /* OMAP4460.BANDGAP_CTRL bits */ -#define OMAP4460_SINGLE_MODE_MASK BIT(31) +#define OMAP4460_CONTINUOUS_MODE_MASK BIT(31) #define OMAP4460_MASK_HOT_MASK BIT(1) #define OMAP4460_MASK_COLD_MASK BIT(0) diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index dcac99f327b0..8a3646e26ddd 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -602,36 +603,41 @@ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id) static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id) { - u32 counter = 1000; - struct temp_sensor_registers *tsr; + struct temp_sensor_registers *tsr = bgp->conf->sensors[id].registers; + void __iomem *temp_sensor_ctrl = bgp->base + tsr->temp_sensor_ctrl; + int error; + u32 val; - /* Select single conversion mode */ - if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) - RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0); - - /* Start of Conversion = 1 */ - RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1); - - /* Wait for EOCZ going up */ - tsr = bgp->conf->sensors[id].registers; - - while (--counter) { - if (ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) & - tsr->bgap_eocz_mask) - break; + /* Select continuous or single conversion mode */ + if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) { + if (TI_BANDGAP_HAS(bgp, CONT_MODE_ONLY)) + RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 1); + else + RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0); } - /* Start of Conversion = 0 */ - RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0); + /* Set Start of Conversion if available */ + if (tsr->bgap_soc_mask) { + RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1); - /* Wait for EOCZ going down */ - counter = 1000; - while (--counter) { - if (!(ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) & - tsr->bgap_eocz_mask)) - break; + /* Wait for EOCZ going up */ + error = readl_poll_timeout_atomic(temp_sensor_ctrl, val, + val & tsr->bgap_eocz_mask, + 1, 1000); + if (error) + dev_warn(bgp->dev, "eocz timed out waiting high\n"); + + /* Clear Start of Conversion if available */ + RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0); } + /* Wait for EOCZ going down, always needed even if no bgap_soc_mask */ + error = readl_poll_timeout_atomic(temp_sensor_ctrl, val, + !(val & tsr->bgap_eocz_mask), + 1, 1500); + if (error) + dev_warn(bgp->dev, "eocz timed out waiting low\n"); + return 0; } diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h index ed0ea4b17b25..1f4bbaf31675 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.h +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h @@ -280,6 +280,7 @@ struct ti_temp_sensor { * has Errata 814 * TI_BANDGAP_FEATURE_UNRELIABLE - used when the sensor readings are too * inaccurate. + * TI_BANDGAP_FEATURE_CONT_MODE_ONLY - used when single mode hangs the sensor * TI_BANDGAP_HAS(b, f) - macro to check if a bandgap device is capable of a * specific feature (above) or not. Return non-zero, if yes. */ @@ -295,6 +296,7 @@ struct ti_temp_sensor { #define TI_BANDGAP_FEATURE_HISTORY_BUFFER BIT(9) #define TI_BANDGAP_FEATURE_ERRATA_814 BIT(10) #define TI_BANDGAP_FEATURE_UNRELIABLE BIT(11) +#define TI_BANDGAP_FEATURE_CONT_MODE_ONLY BIT(12) #define TI_BANDGAP_HAS(b, f) \ ((b)->conf->features & TI_BANDGAP_FEATURE_ ## f) diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 2ce4b19f312a..f84375865c97 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -166,6 +166,7 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, char *domain) { struct ti_thermal_data *data; + int interval; data = ti_bandgap_get_sensor_data(bgp, id); @@ -183,9 +184,10 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, return PTR_ERR(data->ti_thermal); } + interval = jiffies_to_msecs(data->ti_thermal->polling_delay_jiffies); + ti_bandgap_set_sensor_data(bgp, id, data); - ti_bandgap_write_update_interval(bgp, data->sensor_id, - data->ti_thermal->polling_delay); + ti_bandgap_write_update_interval(bgp, data->sensor_id, interval); return 0; } diff --git a/drivers/thermal/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c deleted file mode 100644 index 8e3a2d3c2f9a..000000000000 --- a/drivers/thermal/zx2967_thermal.c +++ /dev/null @@ -1,256 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * ZTE's zx2967 family thermal sensor driver - * - * Copyright (C) 2017 ZTE Ltd. - * - * Author: Baoyou Xie - */ - -#include -#include -#include -#include -#include -#include -#include - -/* Power Mode: 0->low 1->high */ -#define ZX2967_THERMAL_POWER_MODE 0 -#define ZX2967_POWER_MODE_LOW 0 -#define ZX2967_POWER_MODE_HIGH 1 - -/* DCF Control Register */ -#define ZX2967_THERMAL_DCF 0x4 -#define ZX2967_DCF_EN BIT(1) -#define ZX2967_DCF_FREEZE BIT(0) - -/* Selection Register */ -#define ZX2967_THERMAL_SEL 0x8 - -/* Control Register */ -#define ZX2967_THERMAL_CTRL 0x10 - -#define ZX2967_THERMAL_READY BIT(12) -#define ZX2967_THERMAL_TEMP_MASK GENMASK(11, 0) -#define ZX2967_THERMAL_ID_MASK 0x18 -#define ZX2967_THERMAL_ID 0x10 - -#define ZX2967_GET_TEMP_TIMEOUT_US (100 * 1024) - -/** - * struct zx2967_thermal_priv - zx2967 thermal sensor private structure - * @tzd: struct thermal_zone_device where the sensor is registered - * @lock: prevents read sensor in parallel - * @clk_topcrm: topcrm clk structure - * @clk_apb: apb clk structure - * @regs: pointer to base address of the thermal sensor - * @dev: struct device pointer - */ - -struct zx2967_thermal_priv { - struct thermal_zone_device *tzd; - struct mutex lock; - struct clk *clk_topcrm; - struct clk *clk_apb; - void __iomem *regs; - struct device *dev; -}; - -static int zx2967_thermal_get_temp(void *data, int *temp) -{ - void __iomem *regs; - struct zx2967_thermal_priv *priv = data; - u32 val; - int ret; - - if (!priv->tzd) - return -EAGAIN; - - regs = priv->regs; - mutex_lock(&priv->lock); - writel_relaxed(ZX2967_POWER_MODE_LOW, - regs + ZX2967_THERMAL_POWER_MODE); - writel_relaxed(ZX2967_DCF_EN, regs + ZX2967_THERMAL_DCF); - - val = readl_relaxed(regs + ZX2967_THERMAL_SEL); - val &= ~ZX2967_THERMAL_ID_MASK; - val |= ZX2967_THERMAL_ID; - writel_relaxed(val, regs + ZX2967_THERMAL_SEL); - - /* - * Must wait for a while, surely it's a bit odd. - * otherwise temperature value we got has a few deviation, even if - * the THERMAL_READY bit is set. - */ - usleep_range(100, 300); - ret = readx_poll_timeout(readl, regs + ZX2967_THERMAL_CTRL, - val, val & ZX2967_THERMAL_READY, 300, - ZX2967_GET_TEMP_TIMEOUT_US); - if (ret) { - dev_err(priv->dev, "Thermal sensor data timeout\n"); - goto unlock; - } - - writel_relaxed(ZX2967_DCF_FREEZE | ZX2967_DCF_EN, - regs + ZX2967_THERMAL_DCF); - val = readl_relaxed(regs + ZX2967_THERMAL_CTRL) - & ZX2967_THERMAL_TEMP_MASK; - writel_relaxed(ZX2967_POWER_MODE_HIGH, - regs + ZX2967_THERMAL_POWER_MODE); - - /* - * Calculate temperature - * In dts, slope is multiplied by 1000. - */ - *temp = DIV_ROUND_CLOSEST(((s32)val + priv->tzd->tzp->offset) * 1000, - priv->tzd->tzp->slope); - -unlock: - mutex_unlock(&priv->lock); - return ret; -} - -static const struct thermal_zone_of_device_ops zx2967_of_thermal_ops = { - .get_temp = zx2967_thermal_get_temp, -}; - -static int zx2967_thermal_probe(struct platform_device *pdev) -{ - struct zx2967_thermal_priv *priv; - struct resource *res; - int ret; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->regs)) - return PTR_ERR(priv->regs); - - priv->clk_topcrm = devm_clk_get(&pdev->dev, "topcrm"); - if (IS_ERR(priv->clk_topcrm)) { - ret = PTR_ERR(priv->clk_topcrm); - dev_err(&pdev->dev, "failed to get topcrm clock: %d\n", ret); - return ret; - } - - ret = clk_prepare_enable(priv->clk_topcrm); - if (ret) { - dev_err(&pdev->dev, "failed to enable topcrm clock: %d\n", - ret); - return ret; - } - - priv->clk_apb = devm_clk_get(&pdev->dev, "apb"); - if (IS_ERR(priv->clk_apb)) { - ret = PTR_ERR(priv->clk_apb); - dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret); - goto disable_clk_topcrm; - } - - ret = clk_prepare_enable(priv->clk_apb); - if (ret) { - dev_err(&pdev->dev, "failed to enable apb clock: %d\n", - ret); - goto disable_clk_topcrm; - } - - mutex_init(&priv->lock); - priv->tzd = thermal_zone_of_sensor_register(&pdev->dev, - 0, priv, &zx2967_of_thermal_ops); - - if (IS_ERR(priv->tzd)) { - ret = PTR_ERR(priv->tzd); - dev_err(&pdev->dev, "failed to register sensor: %d\n", ret); - goto disable_clk_all; - } - - if (priv->tzd->tzp->slope == 0) { - thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); - dev_err(&pdev->dev, "coefficients of sensor is invalid\n"); - ret = -EINVAL; - goto disable_clk_all; - } - - priv->dev = &pdev->dev; - platform_set_drvdata(pdev, priv); - - return 0; - -disable_clk_all: - clk_disable_unprepare(priv->clk_apb); -disable_clk_topcrm: - clk_disable_unprepare(priv->clk_topcrm); - return ret; -} - -static int zx2967_thermal_exit(struct platform_device *pdev) -{ - struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); - - thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd); - clk_disable_unprepare(priv->clk_topcrm); - clk_disable_unprepare(priv->clk_apb); - - return 0; -} - -static const struct of_device_id zx2967_thermal_id_table[] = { - { .compatible = "zte,zx296718-thermal" }, - {} -}; -MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table); - -#ifdef CONFIG_PM_SLEEP -static int zx2967_thermal_suspend(struct device *dev) -{ - struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); - - if (priv && priv->clk_topcrm) - clk_disable_unprepare(priv->clk_topcrm); - - if (priv && priv->clk_apb) - clk_disable_unprepare(priv->clk_apb); - - return 0; -} - -static int zx2967_thermal_resume(struct device *dev) -{ - struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); - int error; - - error = clk_prepare_enable(priv->clk_topcrm); - if (error) - return error; - - error = clk_prepare_enable(priv->clk_apb); - if (error) { - clk_disable_unprepare(priv->clk_topcrm); - return error; - } - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops, - zx2967_thermal_suspend, zx2967_thermal_resume); - -static struct platform_driver zx2967_thermal_driver = { - .probe = zx2967_thermal_probe, - .remove = zx2967_thermal_exit, - .driver = { - .name = "zx2967_thermal", - .of_match_table = zx2967_thermal_id_table, - .pm = &zx2967_thermal_pm_ops, - }, -}; -module_platform_driver(zx2967_thermal_driver); - -MODULE_AUTHOR("Baoyou Xie "); -MODULE_DESCRIPTION("ZTE zx2967 thermal driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/ktd253-backlight.c b/drivers/video/backlight/ktd253-backlight.c index e3fee3f1f582..d7b287cffd5c 100644 --- a/drivers/video/backlight/ktd253-backlight.c +++ b/drivers/video/backlight/ktd253-backlight.c @@ -137,15 +137,7 @@ static int ktd253_backlight_probe(struct platform_device *pdev) brightness = max_brightness; } - if (brightness) - /* This will be the default ratio when the KTD253 is enabled */ - ktd253->ratio = KTD253_MAX_RATIO; - else - ktd253->ratio = 0; - - ktd253->gpiod = devm_gpiod_get(dev, "enable", - brightness ? GPIOD_OUT_HIGH : - GPIOD_OUT_LOW); + ktd253->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(ktd253->gpiod)) { ret = PTR_ERR(ktd253->gpiod); if (ret != -EPROBE_DEFER) @@ -153,6 +145,8 @@ static int ktd253_backlight_probe(struct platform_device *pdev) return ret; } gpiod_set_consumer_name(ktd253->gpiod, dev_name(dev)); + /* Bring backlight to a known off state */ + msleep(KTD253_T_OFF_MS); bl = devm_backlight_device_register(dev, dev_name(dev), dev, ktd253, &ktd253_backlight_ops, NULL); diff --git a/drivers/video/backlight/lms283gf05.c b/drivers/video/backlight/lms283gf05.c index 0e45685bcc1c..36856962ed83 100644 --- a/drivers/video/backlight/lms283gf05.c +++ b/drivers/video/backlight/lms283gf05.c @@ -9,16 +9,16 @@ #include #include #include -#include +#include #include #include -#include #include struct lms283gf05_state { struct spi_device *spi; struct lcd_device *ld; + struct gpio_desc *reset; }; struct lms283gf05_seq { @@ -90,13 +90,13 @@ static const struct lms283gf05_seq disp_pdwnseq[] = { }; -static void lms283gf05_reset(unsigned long gpio, bool inverted) +static void lms283gf05_reset(struct gpio_desc *gpiod) { - gpio_set_value(gpio, !inverted); + gpiod_set_value(gpiod, 0); /* De-asserted */ mdelay(100); - gpio_set_value(gpio, inverted); + gpiod_set_value(gpiod, 1); /* Asserted */ mdelay(20); - gpio_set_value(gpio, !inverted); + gpiod_set_value(gpiod, 0); /* De-asserted */ mdelay(20); } @@ -125,18 +125,15 @@ static int lms283gf05_power_set(struct lcd_device *ld, int power) { struct lms283gf05_state *st = lcd_get_data(ld); struct spi_device *spi = st->spi; - struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev); if (power <= FB_BLANK_NORMAL) { - if (pdata) - lms283gf05_reset(pdata->reset_gpio, - pdata->reset_inverted); + if (st->reset) + lms283gf05_reset(st->reset); lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq)); } else { lms283gf05_toggle(spi, disp_pdwnseq, ARRAY_SIZE(disp_pdwnseq)); - if (pdata) - gpio_set_value(pdata->reset_gpio, - pdata->reset_inverted); + if (st->reset) + gpiod_set_value(st->reset, 1); /* Asserted */ } return 0; @@ -150,24 +147,18 @@ static struct lcd_ops lms_ops = { static int lms283gf05_probe(struct spi_device *spi) { struct lms283gf05_state *st; - struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev); struct lcd_device *ld; - int ret = 0; - - if (pdata != NULL) { - ret = devm_gpio_request_one(&spi->dev, pdata->reset_gpio, - GPIOF_DIR_OUT | (!pdata->reset_inverted ? - GPIOF_INIT_HIGH : GPIOF_INIT_LOW), - "LMS283GF05 RESET"); - if (ret) - return ret; - } st = devm_kzalloc(&spi->dev, sizeof(struct lms283gf05_state), GFP_KERNEL); if (st == NULL) return -ENOMEM; + st->reset = gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(st->reset)) + return PTR_ERR(st->reset); + gpiod_set_consumer_name(st->reset, "LMS283GF05 RESET"); + ld = devm_lcd_device_register(&spi->dev, "lms283gf05", &spi->dev, st, &lms_ops); if (IS_ERR(ld)) @@ -179,8 +170,8 @@ static int lms283gf05_probe(struct spi_device *spi) spi_set_drvdata(spi, st); /* kick in the LCD */ - if (pdata) - lms283gf05_reset(pdata->reset_gpio, pdata->reset_inverted); + if (st->reset) + lms283gf05_reset(st->reset); lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq)); return 0; diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c index 3bc7800eb0a9..091f07e7c145 100644 --- a/drivers/video/backlight/qcom-wled.c +++ b/drivers/video/backlight/qcom-wled.c @@ -1692,7 +1692,7 @@ static int wled_probe(struct platform_device *pdev) static int wled_remove(struct platform_device *pdev) { - struct wled *wled = dev_get_drvdata(&pdev->dev); + struct wled *wled = platform_get_drvdata(pdev); mutex_destroy(&wled->lock); cancel_delayed_work_sync(&wled->ovp_work); diff --git a/drivers/video/backlight/sky81452-backlight.c b/drivers/video/backlight/sky81452-backlight.c index 8268ac43d54f..c95e0de7f4e7 100644 --- a/drivers/video/backlight/sky81452-backlight.c +++ b/drivers/video/backlight/sky81452-backlight.c @@ -291,7 +291,7 @@ static int sky81452_bl_probe(struct platform_device *pdev) } memset(&props, 0, sizeof(props)); - props.max_brightness = SKY81452_MAX_BRIGHTNESS, + props.max_brightness = SKY81452_MAX_BRIGHTNESS; name = pdata->name ? pdata->name : SKY81452_DEFAULT_NAME; bd = devm_backlight_device_register(dev, name, dev, regmap, &sky81452_bl_ops, &props); diff --git a/drivers/video/fbdev/amifb.c b/drivers/video/fbdev/amifb.c index 226682550b4b..6e07a97bbd31 100644 --- a/drivers/video/fbdev/amifb.c +++ b/drivers/video/fbdev/amifb.c @@ -3736,7 +3736,7 @@ static int __init amifb_probe(struct platform_device *pdev) if (err) goto free_irq; - dev_set_drvdata(&pdev->dev, info); + platform_set_drvdata(pdev, info); err = register_framebuffer(info); if (err) @@ -3764,7 +3764,7 @@ static int __init amifb_probe(struct platform_device *pdev) static int __exit amifb_remove(struct platform_device *pdev) { - struct fb_info *info = dev_get_drvdata(&pdev->dev); + struct fb_info *info = platform_get_drvdata(pdev); unregister_framebuffer(info); fb_dealloc_cmap(&info->cmap); diff --git a/drivers/video/fbdev/da8xx-fb.c b/drivers/video/fbdev/da8xx-fb.c index e38c0e3f9c61..005ac3c17aa1 100644 --- a/drivers/video/fbdev/da8xx-fb.c +++ b/drivers/video/fbdev/da8xx-fb.c @@ -1066,7 +1066,7 @@ static void lcd_da8xx_cpufreq_deregister(struct da8xx_fb_par *par) static int fb_remove(struct platform_device *dev) { - struct fb_info *info = dev_get_drvdata(&dev->dev); + struct fb_info *info = platform_get_drvdata(dev); struct da8xx_fb_par *par = info->par; int ret; @@ -1482,7 +1482,7 @@ static int fb_probe(struct platform_device *device) da8xx_fb_var.activate = FB_ACTIVATE_FORCE; fb_set_var(da8xx_fb_info, &da8xx_fb_var); - dev_set_drvdata(&device->dev, da8xx_fb_info); + platform_set_drvdata(device, da8xx_fb_info); /* initialize the vsync wait queue */ init_waitqueue_head(&par->vsync_wait); diff --git a/drivers/video/fbdev/imxfb.c b/drivers/video/fbdev/imxfb.c index 884b16efa7e8..7f8debd2da06 100644 --- a/drivers/video/fbdev/imxfb.c +++ b/drivers/video/fbdev/imxfb.c @@ -657,7 +657,7 @@ static int imxfb_activate_var(struct fb_var_screeninfo *var, struct fb_info *inf static int imxfb_init_fbinfo(struct platform_device *pdev) { struct imx_fb_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct fb_info *info = dev_get_drvdata(&pdev->dev); + struct fb_info *info = platform_get_drvdata(pdev); struct imxfb_info *fbi = info->par; struct device_node *np; diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-lgphilips-lb035q02.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-lgphilips-lb035q02.c index 0f93a260e432..1bec7a4422e8 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-lgphilips-lb035q02.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-lgphilips-lb035q02.c @@ -239,7 +239,7 @@ static struct omap_dss_driver lb035q02_ops = { static int lb035q02_probe_of(struct spi_device *spi) { struct device_node *node = spi->dev.of_node; - struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct panel_drv_data *ddata = spi_get_drvdata(spi); struct omap_dss_device *in; struct gpio_desc *gpio; @@ -277,7 +277,7 @@ static int lb035q02_panel_spi_probe(struct spi_device *spi) if (ddata == NULL) return -ENOMEM; - dev_set_drvdata(&spi->dev, ddata); + spi_set_drvdata(spi, ddata); ddata->spi = spi; @@ -318,7 +318,7 @@ static int lb035q02_panel_spi_probe(struct spi_device *spi) static int lb035q02_panel_spi_remove(struct spi_device *spi) { - struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); + struct panel_drv_data *ddata = spi_get_drvdata(spi); struct omap_dss_device *dssdev = &ddata->dssdev; struct omap_dss_device *in = ddata->in; diff --git a/drivers/video/fbdev/omap2/omapfb/dss/dpi.c b/drivers/video/fbdev/omap2/omapfb/dss/dpi.c index e2e7fe6f89ee..99ce6e955a46 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/dpi.c +++ b/drivers/video/fbdev/omap2/omapfb/dss/dpi.c @@ -55,7 +55,7 @@ static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev) /* only used in non-DT mode */ static struct dpi_data *dpi_get_data_from_pdev(struct platform_device *pdev) { - return dev_get_drvdata(&pdev->dev); + return platform_get_drvdata(pdev); } static struct dss_pll *dpi_get_pll(enum omap_channel channel) @@ -784,7 +784,7 @@ static int dpi_bind(struct device *dev, struct device *master, void *data) dpi->pdev = pdev; - dev_set_drvdata(&pdev->dev, dpi); + platform_set_drvdata(pdev, dpi); mutex_init(&dpi->lock); diff --git a/drivers/video/fbdev/omap2/omapfb/dss/dsi.c b/drivers/video/fbdev/omap2/omapfb/dss/dsi.c index 58c7aa279ab1..daa313f14335 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/dsi.c +++ b/drivers/video/fbdev/omap2/omapfb/dss/dsi.c @@ -399,7 +399,7 @@ module_param(dsi_perf, bool, 0644); static inline struct dsi_data *dsi_get_dsidrv_data(struct platform_device *dsidev) { - return dev_get_drvdata(&dsidev->dev); + return platform_get_drvdata(dsidev); } static inline struct platform_device *dsi_get_dsidev_from_dssdev(struct omap_dss_device *dssdev) @@ -5266,7 +5266,7 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) return -ENOMEM; dsi->pdev = dsidev; - dev_set_drvdata(&dsidev->dev, dsi); + platform_set_drvdata(dsidev, dsi); spin_lock_init(&dsi->irq_lock); spin_lock_init(&dsi->errors_lock); diff --git a/drivers/video/fbdev/omap2/omapfb/dss/hdmi4.c b/drivers/video/fbdev/omap2/omapfb/dss/hdmi4.c index 496b43bdad21..800bd108e834 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/hdmi4.c +++ b/drivers/video/fbdev/omap2/omapfb/dss/hdmi4.c @@ -672,7 +672,7 @@ static int hdmi4_bind(struct device *dev, struct device *master, void *data) int irq; hdmi.pdev = pdev; - dev_set_drvdata(&pdev->dev, &hdmi); + platform_set_drvdata(pdev, &hdmi); mutex_init(&hdmi.lock); spin_lock_init(&hdmi.audio_playing_lock); diff --git a/drivers/video/fbdev/omap2/omapfb/dss/hdmi5.c b/drivers/video/fbdev/omap2/omapfb/dss/hdmi5.c index e3d441ade241..2c03608addcd 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/hdmi5.c +++ b/drivers/video/fbdev/omap2/omapfb/dss/hdmi5.c @@ -713,7 +713,7 @@ static int hdmi5_bind(struct device *dev, struct device *master, void *data) int irq; hdmi.pdev = pdev; - dev_set_drvdata(&pdev->dev, &hdmi); + platform_set_drvdata(pdev, &hdmi); mutex_init(&hdmi.lock); spin_lock_init(&hdmi.audio_playing_lock); diff --git a/drivers/video/fbdev/xilinxfb.c b/drivers/video/fbdev/xilinxfb.c index ca4ff658cad0..ffbf900648d9 100644 --- a/drivers/video/fbdev/xilinxfb.c +++ b/drivers/video/fbdev/xilinxfb.c @@ -472,7 +472,7 @@ static int xilinxfb_of_probe(struct platform_device *pdev) if (of_find_property(pdev->dev.of_node, "rotate-display", NULL)) pdata.rotate_screen = 1; - dev_set_drvdata(&pdev->dev, drvdata); + platform_set_drvdata(pdev, drvdata); return xilinxfb_assign(pdev, drvdata, &pdata); } diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7ff941e71b79..1fe0042a48d2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -254,17 +254,6 @@ config MENZ069_WATCHDOG This driver can also be built as a module. If so the module will be called menz069_wdt. -config TANGOX_WATCHDOG - tristate "Sigma Designs SMP86xx/SMP87xx watchdog" - select WATCHDOG_CORE - depends on ARCH_TANGO || COMPILE_TEST - depends on HAS_IOMEM - help - Support for the watchdog in Sigma Designs SMP86xx (tango3) - and SMP87xx (tango4) family chips. - - This driver can be built as a module. The module name is tangox_wdt. - config WDAT_WDT tristate "ACPI Watchdog Action Table (WDAT)" depends on ACPI @@ -630,17 +619,6 @@ config SUNXI_WATCHDOG To compile this driver as a module, choose M here: the module will be called sunxi_wdt. -config COH901327_WATCHDOG - bool "ST-Ericsson COH 901 327 watchdog" - depends on ARCH_U300 || (ARM && COMMON_CLK && COMPILE_TEST) - default y if MACH_U300 - select WATCHDOG_CORE - help - Say Y here to include Watchdog timer support for the - watchdog embedded into the ST-Ericsson U300 series platforms. - This watchdog is used to reset the system and thus cannot be - compiled as a module. - config NPCM7XX_WATCHDOG tristate "Nuvoton NPCM750 watchdog" depends on ARCH_NPCM || COMPILE_TEST @@ -788,16 +766,6 @@ config MOXART_WDT To compile this driver as a module, choose M here: the module will be called moxart_wdt. -config SIRFSOC_WATCHDOG - tristate "SiRFSOC watchdog" - depends on HAS_IOMEM - depends on ARCH_SIRF || COMPILE_TEST - select WATCHDOG_CORE - default y - help - Support for CSR SiRFprimaII and SiRFatlasVI watchdog. When - the watchdog triggers the system will be reset. - config ST_LPC_WATCHDOG tristate "STMicroelectronics LPC Watchdog" depends on ARCH_STI || COMPILE_TEST @@ -900,16 +868,6 @@ config LPC18XX_WATCHDOG To compile this driver as a module, choose M here: the module will be called lpc18xx_wdt. -config ATLAS7_WATCHDOG - tristate "CSRatlas7 watchdog" - depends on ARCH_ATLAS7 || COMPILE_TEST - help - Say Y here to include Watchdog timer support for the watchdog - existing on the CSRatlas7 series platforms. - - To compile this driver as a module, choose M here: the - module will be called atlas7_wdt. - config RENESAS_WDT tristate "Renesas WDT Watchdog" depends on ARCH_RENESAS || COMPILE_TEST @@ -939,16 +897,6 @@ config ASPEED_WATCHDOG To compile this driver as a module, choose M here: the module will be called aspeed_wdt. -config ZX2967_WATCHDOG - tristate "ZTE zx2967 SoCs watchdog support" - depends on ARCH_ZX - select WATCHDOG_CORE - help - Say Y here to include support for the watchdog timer - in ZTE zx2967 SoCs. - To compile this driver as a module, choose M here: the - module will be called zx2967_wdt. - config STM32_WATCHDOG tristate "STM32 Independent WatchDoG (IWDG) support" depends on ARCH_STM32 @@ -1219,15 +1167,6 @@ config IE6XX_WDT To compile this driver as a module, choose M here: the module will be called ie6xx_wdt. -config INTEL_SCU_WATCHDOG - bool "Intel SCU Watchdog for Mobile Platforms" - depends on X86_INTEL_MID - help - Hardware driver for the watchdog time built into the Intel SCU - for Intel Mobile Platforms. - - To compile this driver as a module, choose M here. - config INTEL_MID_WATCHDOG tristate "Intel MID Watchdog Timer" depends on X86_INTEL_MID @@ -2155,4 +2094,17 @@ config USBPCWATCHDOG Most people will say N. +config KEEMBAY_WATCHDOG + tristate "Intel Keem Bay SoC non-secure watchdog" + depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST) + select WATCHDOG_CORE + help + This option enable support for an In-secure watchdog timer driver for + Intel Keem Bay SoC. This WDT has a 32 bit timer and decrements in every + count unit. An interrupt will be triggered, when the count crosses + the thershold configured in the register. + + To compile this driver as a module, choose M here: the + module will be called keembay_wdt. + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5c74ee19d441..f3a6540e725e 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -61,7 +61,6 @@ obj-$(CONFIG_K3_RTI_WATCHDOG) += rti_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o obj-$(CONFIG_SUNXI_WATCHDOG) += sunxi_wdt.o obj-$(CONFIG_RN5T618_WATCHDOG) += rn5t618_wdt.o -obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o obj-$(CONFIG_NPCM7XX_WATCHDOG) += npcm_wdt.o obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o obj-$(CONFIG_TS4800_WATCHDOG) += ts4800_wdt.o @@ -73,7 +72,6 @@ obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o -obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o obj-$(CONFIG_ST_LPC_WATCHDOG) += st_lpc_wdt.o obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o @@ -84,11 +82,9 @@ obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o -obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o -obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o @@ -140,12 +136,12 @@ obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o -obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o obj-$(CONFIG_MLX_WDT) += mlx_wdt.o +obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o # M68K Architecture obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o @@ -213,7 +209,6 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o -obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o diff --git a/drivers/watchdog/atlas7_wdt.c b/drivers/watchdog/atlas7_wdt.c deleted file mode 100644 index 9bfe650d802f..000000000000 --- a/drivers/watchdog/atlas7_wdt.c +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Watchdog driver for CSR Atlas7 - * - * Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define ATLAS7_TIMER_WDT_INDEX 5 -#define ATLAS7_WDT_DEFAULT_TIMEOUT 20 - -#define ATLAS7_WDT_CNT_CTRL (0 + 4 * ATLAS7_TIMER_WDT_INDEX) -#define ATLAS7_WDT_CNT_MATCH (0x18 + 4 * ATLAS7_TIMER_WDT_INDEX) -#define ATLAS7_WDT_CNT (0x48 + 4 * ATLAS7_TIMER_WDT_INDEX) -#define ATLAS7_WDT_CNT_EN (BIT(0) | BIT(1)) -#define ATLAS7_WDT_EN 0x64 - -static unsigned int timeout = ATLAS7_WDT_DEFAULT_TIMEOUT; -static bool nowayout = WATCHDOG_NOWAYOUT; - -module_param(timeout, uint, 0); -module_param(nowayout, bool, 0); - -MODULE_PARM_DESC(timeout, "Default watchdog timeout (in seconds)"); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -struct atlas7_wdog { - struct device *dev; - void __iomem *base; - unsigned long tick_rate; - struct clk *clk; -}; - -static unsigned int atlas7_wdt_gettimeleft(struct watchdog_device *wdd) -{ - struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); - u32 counter, match, delta; - - counter = readl(wdt->base + ATLAS7_WDT_CNT); - match = readl(wdt->base + ATLAS7_WDT_CNT_MATCH); - delta = match - counter; - - return delta / wdt->tick_rate; -} - -static int atlas7_wdt_ping(struct watchdog_device *wdd) -{ - struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); - u32 counter, match, delta; - - counter = readl(wdt->base + ATLAS7_WDT_CNT); - delta = wdd->timeout * wdt->tick_rate; - match = counter + delta; - - writel(match, wdt->base + ATLAS7_WDT_CNT_MATCH); - - return 0; -} - -static int atlas7_wdt_enable(struct watchdog_device *wdd) -{ - struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); - - atlas7_wdt_ping(wdd); - - writel(readl(wdt->base + ATLAS7_WDT_CNT_CTRL) | ATLAS7_WDT_CNT_EN, - wdt->base + ATLAS7_WDT_CNT_CTRL); - writel(1, wdt->base + ATLAS7_WDT_EN); - - return 0; -} - -static int atlas7_wdt_disable(struct watchdog_device *wdd) -{ - struct atlas7_wdog *wdt = watchdog_get_drvdata(wdd); - - writel(0, wdt->base + ATLAS7_WDT_EN); - writel(readl(wdt->base + ATLAS7_WDT_CNT_CTRL) & ~ATLAS7_WDT_CNT_EN, - wdt->base + ATLAS7_WDT_CNT_CTRL); - - return 0; -} - -static int atlas7_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) -{ - wdd->timeout = to; - - return 0; -} - -#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) - -static const struct watchdog_info atlas7_wdt_ident = { - .options = OPTIONS, - .firmware_version = 0, - .identity = "atlas7 Watchdog", -}; - -static const struct watchdog_ops atlas7_wdt_ops = { - .owner = THIS_MODULE, - .start = atlas7_wdt_enable, - .stop = atlas7_wdt_disable, - .get_timeleft = atlas7_wdt_gettimeleft, - .ping = atlas7_wdt_ping, - .set_timeout = atlas7_wdt_settimeout, -}; - -static struct watchdog_device atlas7_wdd = { - .info = &atlas7_wdt_ident, - .ops = &atlas7_wdt_ops, - .timeout = ATLAS7_WDT_DEFAULT_TIMEOUT, -}; - -static const struct of_device_id atlas7_wdt_ids[] = { - { .compatible = "sirf,atlas7-tick"}, - {} -}; - -static void atlas7_clk_disable_unprepare(void *data) -{ - clk_disable_unprepare(data); -} - -static int atlas7_wdt_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct atlas7_wdog *wdt; - struct clk *clk; - int ret; - - wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); - if (!wdt) - return -ENOMEM; - wdt->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(wdt->base)) - return PTR_ERR(wdt->base); - - clk = devm_clk_get(dev, NULL); - if (IS_ERR(clk)) - return PTR_ERR(clk); - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(dev, "clk enable failed\n"); - return ret; - } - ret = devm_add_action_or_reset(dev, atlas7_clk_disable_unprepare, clk); - if (ret) - return ret; - - /* disable watchdog hardware */ - writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL); - - wdt->tick_rate = clk_get_rate(clk); - if (!wdt->tick_rate) - return -EINVAL; - - wdt->clk = clk; - atlas7_wdd.min_timeout = 1; - atlas7_wdd.max_timeout = UINT_MAX / wdt->tick_rate; - - watchdog_init_timeout(&atlas7_wdd, 0, dev); - watchdog_set_nowayout(&atlas7_wdd, nowayout); - - watchdog_set_drvdata(&atlas7_wdd, wdt); - platform_set_drvdata(pdev, &atlas7_wdd); - - watchdog_stop_on_reboot(&atlas7_wdd); - watchdog_stop_on_unregister(&atlas7_wdd); - return devm_watchdog_register_device(dev, &atlas7_wdd); -} - -static int __maybe_unused atlas7_wdt_suspend(struct device *dev) -{ - /* - * NOTE:timer controller registers settings are saved - * and restored back by the timer-atlas7.c - */ - return 0; -} - -static int __maybe_unused atlas7_wdt_resume(struct device *dev) -{ - struct watchdog_device *wdd = dev_get_drvdata(dev); - - /* - * NOTE: Since timer controller registers settings are saved - * and restored back by the timer-atlas7.c, so we need not - * update WD settings except refreshing timeout. - */ - atlas7_wdt_ping(wdd); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(atlas7_wdt_pm_ops, - atlas7_wdt_suspend, atlas7_wdt_resume); - -MODULE_DEVICE_TABLE(of, atlas7_wdt_ids); - -static struct platform_driver atlas7_wdt_driver = { - .driver = { - .name = "atlas7-wdt", - .pm = &atlas7_wdt_pm_ops, - .of_match_table = atlas7_wdt_ids, - }, - .probe = atlas7_wdt_probe, -}; -module_platform_driver(atlas7_wdt_driver); - -MODULE_DESCRIPTION("CSRatlas7 watchdog driver"); -MODULE_AUTHOR("Guo Zeng "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:atlas7-wdt"); diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c deleted file mode 100644 index 260c50b08483..000000000000 --- a/drivers/watchdog/coh901327_wdt.c +++ /dev/null @@ -1,408 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * coh901327_wdt.c - * - * Copyright (C) 2008-2009 ST-Ericsson AB - * Watchdog driver for the ST-Ericsson AB COH 901 327 IP core - * Author: Linus Walleij - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "WDOG COH 901 327" - -/* - * COH 901 327 register definitions - */ - -/* WDOG_FEED Register 32bit (-/W) */ -#define U300_WDOG_FR 0x00 -#define U300_WDOG_FR_FEED_RESTART_TIMER 0xFEEDU -/* WDOG_TIMEOUT Register 32bit (R/W) */ -#define U300_WDOG_TR 0x04 -#define U300_WDOG_TR_TIMEOUT_MASK 0x7FFFU -/* WDOG_DISABLE1 Register 32bit (-/W) */ -#define U300_WDOG_D1R 0x08 -#define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER 0x2BADU -/* WDOG_DISABLE2 Register 32bit (R/W) */ -#define U300_WDOG_D2R 0x0C -#define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER 0xCAFEU -#define U300_WDOG_D2R_DISABLE_STATUS_DISABLED 0xDABEU -#define U300_WDOG_D2R_DISABLE_STATUS_ENABLED 0x0000U -/* WDOG_STATUS Register 32bit (R/W) */ -#define U300_WDOG_SR 0x10 -#define U300_WDOG_SR_STATUS_TIMED_OUT 0xCFE8U -#define U300_WDOG_SR_STATUS_NORMAL 0x0000U -#define U300_WDOG_SR_RESET_STATUS_RESET 0xE8B4U -/* WDOG_COUNT Register 32bit (R/-) */ -#define U300_WDOG_CR 0x14 -#define U300_WDOG_CR_VALID_IND 0x8000U -#define U300_WDOG_CR_VALID_STABLE 0x0000U -#define U300_WDOG_CR_COUNT_VALUE_MASK 0x7FFFU -/* WDOG_JTAGOVR Register 32bit (R/W) */ -#define U300_WDOG_JOR 0x18 -#define U300_WDOG_JOR_JTAG_MODE_IND 0x0002U -#define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE 0x0001U -/* WDOG_RESTART Register 32bit (-/W) */ -#define U300_WDOG_RR 0x1C -#define U300_WDOG_RR_RESTART_VALUE_RESUME 0xACEDU -/* WDOG_IRQ_EVENT Register 32bit (R/W) */ -#define U300_WDOG_IER 0x20 -#define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND 0x0001U -#define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE 0x0001U -/* WDOG_IRQ_MASK Register 32bit (R/W) */ -#define U300_WDOG_IMR 0x24 -#define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE 0x0001U -/* WDOG_IRQ_FORCE Register 32bit (R/W) */ -#define U300_WDOG_IFR 0x28 -#define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE 0x0001U - -/* Default timeout in seconds = 1 minute */ -#define U300_WDOG_DEFAULT_TIMEOUT 60 - -static unsigned int margin; -static int irq; -static void __iomem *virtbase; -static struct device *parent; - -static struct clk *clk; - -/* - * Enabling and disabling functions. - */ -static void coh901327_enable(u16 timeout) -{ - u16 val; - unsigned long freq; - unsigned long delay_ns; - - /* Restart timer if it is disabled */ - val = readw(virtbase + U300_WDOG_D2R); - if (val == U300_WDOG_D2R_DISABLE_STATUS_DISABLED) - writew(U300_WDOG_RR_RESTART_VALUE_RESUME, - virtbase + U300_WDOG_RR); - /* Acknowledge any pending interrupt so it doesn't just fire off */ - writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, - virtbase + U300_WDOG_IER); - /* - * The interrupt is cleared in the 32 kHz clock domain. - * Wait 3 32 kHz cycles for it to take effect - */ - freq = clk_get_rate(clk); - delay_ns = DIV_ROUND_UP(1000000000, freq); /* Freq to ns and round up */ - delay_ns = 3 * delay_ns; /* Wait 3 cycles */ - ndelay(delay_ns); - /* Enable the watchdog interrupt */ - writew(U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE, virtbase + U300_WDOG_IMR); - /* Activate the watchdog timer */ - writew(timeout, virtbase + U300_WDOG_TR); - /* Start the watchdog timer */ - writew(U300_WDOG_FR_FEED_RESTART_TIMER, virtbase + U300_WDOG_FR); - /* - * Extra read so that this change propagate in the watchdog. - */ - (void) readw(virtbase + U300_WDOG_CR); - val = readw(virtbase + U300_WDOG_D2R); - if (val != U300_WDOG_D2R_DISABLE_STATUS_ENABLED) - dev_err(parent, - "%s(): watchdog not enabled! D2R value %04x\n", - __func__, val); -} - -static void coh901327_disable(void) -{ - u16 val; - - /* Disable the watchdog interrupt if it is active */ - writew(0x0000U, virtbase + U300_WDOG_IMR); - /* If the watchdog is currently enabled, attempt to disable it */ - val = readw(virtbase + U300_WDOG_D2R); - if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) { - writew(U300_WDOG_D1R_DISABLE1_DISABLE_TIMER, - virtbase + U300_WDOG_D1R); - writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, - virtbase + U300_WDOG_D2R); - /* Write this twice (else problems occur) */ - writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, - virtbase + U300_WDOG_D2R); - } - val = readw(virtbase + U300_WDOG_D2R); - if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) - dev_err(parent, - "%s(): watchdog not disabled! D2R value %04x\n", - __func__, val); -} - -static int coh901327_start(struct watchdog_device *wdt_dev) -{ - coh901327_enable(wdt_dev->timeout * 100); - return 0; -} - -static int coh901327_stop(struct watchdog_device *wdt_dev) -{ - coh901327_disable(); - return 0; -} - -static int coh901327_ping(struct watchdog_device *wdd) -{ - /* Feed the watchdog */ - writew(U300_WDOG_FR_FEED_RESTART_TIMER, - virtbase + U300_WDOG_FR); - return 0; -} - -static int coh901327_settimeout(struct watchdog_device *wdt_dev, - unsigned int time) -{ - wdt_dev->timeout = time; - /* Set new timeout value */ - writew(time * 100, virtbase + U300_WDOG_TR); - /* Feed the dog */ - writew(U300_WDOG_FR_FEED_RESTART_TIMER, - virtbase + U300_WDOG_FR); - return 0; -} - -static unsigned int coh901327_gettimeleft(struct watchdog_device *wdt_dev) -{ - u16 val; - - /* Read repeatedly until the value is stable! */ - val = readw(virtbase + U300_WDOG_CR); - while (val & U300_WDOG_CR_VALID_IND) - val = readw(virtbase + U300_WDOG_CR); - val &= U300_WDOG_CR_COUNT_VALUE_MASK; - if (val != 0) - val /= 100; - - return val; -} - -/* - * This interrupt occurs 10 ms before the watchdog WILL bark. - */ -static irqreturn_t coh901327_interrupt(int irq, void *data) -{ - u16 val; - - /* - * Ack IRQ? If this occurs we're FUBAR anyway, so - * just acknowledge, disable the interrupt and await the imminent end. - * If you at some point need a host of callbacks to be called - * when the system is about to watchdog-reset, add them here! - * - * NOTE: on future versions of this IP-block, it will be possible - * to prevent a watchdog reset by feeding the watchdog at this - * point. - */ - val = readw(virtbase + U300_WDOG_IER); - if (val == U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND) - writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, - virtbase + U300_WDOG_IER); - writew(0x0000U, virtbase + U300_WDOG_IMR); - dev_crit(parent, "watchdog is barking!\n"); - return IRQ_HANDLED; -} - -static const struct watchdog_info coh901327_ident = { - .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = DRV_NAME, -}; - -static const struct watchdog_ops coh901327_ops = { - .owner = THIS_MODULE, - .start = coh901327_start, - .stop = coh901327_stop, - .ping = coh901327_ping, - .set_timeout = coh901327_settimeout, - .get_timeleft = coh901327_gettimeleft, -}; - -static struct watchdog_device coh901327_wdt = { - .info = &coh901327_ident, - .ops = &coh901327_ops, - /* - * Max timeout is 327 since the 10ms - * timeout register is max - * 0x7FFF = 327670ms ~= 327s. - */ - .min_timeout = 1, - .max_timeout = 327, - .timeout = U300_WDOG_DEFAULT_TIMEOUT, -}; - -static int __init coh901327_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - int ret; - u16 val; - - parent = dev; - - virtbase = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(virtbase)) - return PTR_ERR(virtbase); - - clk = clk_get(dev, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - dev_err(dev, "could not get clock\n"); - return ret; - } - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(dev, "could not prepare and enable clock\n"); - goto out_no_clk_enable; - } - - val = readw(virtbase + U300_WDOG_SR); - switch (val) { - case U300_WDOG_SR_STATUS_TIMED_OUT: - dev_info(dev, "watchdog timed out since last chip reset!\n"); - coh901327_wdt.bootstatus |= WDIOF_CARDRESET; - /* Status will be cleared below */ - break; - case U300_WDOG_SR_STATUS_NORMAL: - dev_info(dev, "in normal status, no timeouts have occurred.\n"); - break; - default: - dev_info(dev, "contains an illegal status code (%08x)\n", val); - break; - } - - val = readw(virtbase + U300_WDOG_D2R); - switch (val) { - case U300_WDOG_D2R_DISABLE_STATUS_DISABLED: - dev_info(dev, "currently disabled.\n"); - break; - case U300_WDOG_D2R_DISABLE_STATUS_ENABLED: - dev_info(dev, "currently enabled! (disabling it now)\n"); - coh901327_disable(); - break; - default: - dev_err(dev, "contains an illegal enable/disable code (%08x)\n", - val); - break; - } - - /* Reset the watchdog */ - writew(U300_WDOG_SR_RESET_STATUS_RESET, virtbase + U300_WDOG_SR); - - irq = platform_get_irq(pdev, 0); - if (request_irq(irq, coh901327_interrupt, 0, - DRV_NAME " Bark", pdev)) { - ret = -EIO; - goto out_no_irq; - } - - watchdog_init_timeout(&coh901327_wdt, margin, dev); - - coh901327_wdt.parent = dev; - ret = watchdog_register_device(&coh901327_wdt); - if (ret) - goto out_no_wdog; - - dev_info(dev, "initialized. (timeout=%d sec)\n", - coh901327_wdt.timeout); - return 0; - -out_no_wdog: - free_irq(irq, pdev); -out_no_irq: - clk_disable_unprepare(clk); -out_no_clk_enable: - clk_put(clk); - return ret; -} - -#ifdef CONFIG_PM - -static u16 wdogenablestore; -static u16 irqmaskstore; - -static int coh901327_suspend(struct platform_device *pdev, pm_message_t state) -{ - irqmaskstore = readw(virtbase + U300_WDOG_IMR) & 0x0001U; - wdogenablestore = readw(virtbase + U300_WDOG_D2R); - /* If watchdog is on, disable it here and now */ - if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) - coh901327_disable(); - return 0; -} - -static int coh901327_resume(struct platform_device *pdev) -{ - /* Restore the watchdog interrupt */ - writew(irqmaskstore, virtbase + U300_WDOG_IMR); - if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) { - /* Restart the watchdog timer */ - writew(U300_WDOG_RR_RESTART_VALUE_RESUME, - virtbase + U300_WDOG_RR); - writew(U300_WDOG_FR_FEED_RESTART_TIMER, - virtbase + U300_WDOG_FR); - } - return 0; -} -#else -#define coh901327_suspend NULL -#define coh901327_resume NULL -#endif - -/* - * Mistreating the watchdog is the only way to perform a software reset of the - * system on EMP platforms. So we implement this and export a symbol for it. - */ -void coh901327_watchdog_reset(void) -{ - /* Enable even if on JTAG too */ - writew(U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE, - virtbase + U300_WDOG_JOR); - /* - * Timeout = 5s, we have to wait for the watchdog reset to - * actually take place: the watchdog will be reloaded with the - * default value immediately, so we HAVE to reboot and get back - * into the kernel in 30s, or the device will reboot again! - * The boot loader will typically deactivate the watchdog, so we - * need time enough for the boot loader to get to the point of - * deactivating the watchdog before it is shut down by it. - * - * NOTE: on future versions of the watchdog, this restriction is - * gone: the watchdog will be reloaded with a default value (1 min) - * instead of last value, and you can conveniently set the watchdog - * timeout to 10ms (value = 1) without any problems. - */ - coh901327_enable(500); - /* Return and await doom */ -} - -static const struct of_device_id coh901327_dt_match[] = { - { .compatible = "stericsson,coh901327" }, - {}, -}; - -static struct platform_driver coh901327_driver = { - .driver = { - .name = "coh901327_wdog", - .of_match_table = coh901327_dt_match, - .suppress_bind_attrs = true, - }, - .suspend = coh901327_suspend, - .resume = coh901327_resume, -}; -builtin_platform_driver_probe(coh901327_driver, coh901327_probe); - -/* not really modular, but ... */ -module_param(margin, uint, 0); -MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index cbd1498ff015..22ddba3802ef 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -123,7 +123,7 @@ static int hpwdt_settimeout(struct watchdog_device *wdd, unsigned int val) if (val <= wdd->pretimeout) { dev_dbg(wdd->parent, "pretimeout < timeout. Setting to zero\n"); wdd->pretimeout = 0; - pretimeout = 0; + pretimeout = false; if (watchdog_active(wdd)) hpwdt_start(wdd); } @@ -336,13 +336,13 @@ static int hpwdt_init_one(struct pci_dev *dev, watchdog_init_timeout(&hpwdt_dev, soft_margin, NULL); if (is_kdump_kernel()) { - pretimeout = 0; + pretimeout = false; kdumptimeout = 0; } if (pretimeout && hpwdt_dev.timeout <= PRETIMEOUT_SEC) { dev_warn(&dev->dev, "timeout <= pretimeout. Setting pretimeout to zero\n"); - pretimeout = 0; + pretimeout = false; } hpwdt_dev.pretimeout = pretimeout ? PRETIMEOUT_SEC : 0; kdumptimeout = min(kdumptimeout, HPWDT_MAX_TIMER); diff --git a/drivers/watchdog/intel-mid_wdt.c b/drivers/watchdog/intel-mid_wdt.c index 1ae03b64ef8b..9b2173f765c8 100644 --- a/drivers/watchdog/intel-mid_wdt.c +++ b/drivers/watchdog/intel-mid_wdt.c @@ -154,6 +154,10 @@ static int mid_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(wdt_dev, WATCHDOG_NOWAYOUT); watchdog_set_drvdata(wdt_dev, mid); + mid->scu = devm_intel_scu_ipc_dev_get(dev); + if (!mid->scu) + return -EPROBE_DEFER; + ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq, IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog", wdt_dev); @@ -162,10 +166,6 @@ static int mid_wdt_probe(struct platform_device *pdev) return ret; } - mid->scu = devm_intel_scu_ipc_dev_get(dev); - if (!mid->scu) - return -EPROBE_DEFER; - /* * The firmware followed by U-Boot leaves the watchdog running * with the default threshold which may vary. When we get here diff --git a/drivers/watchdog/intel_scu_watchdog.c b/drivers/watchdog/intel_scu_watchdog.c deleted file mode 100644 index 804e35940983..000000000000 --- a/drivers/watchdog/intel_scu_watchdog.c +++ /dev/null @@ -1,533 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device - * for Intel part #(s): - * - AF82MP20 PCH - * - * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_scu_watchdog.h" - -/* Bounds number of times we will retry loading time count */ -/* This retry is a work around for a silicon bug. */ -#define MAX_RETRY 16 - -#define IPC_SET_WATCHDOG_TIMER 0xF8 - -static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN; -module_param(timer_margin, int, 0); -MODULE_PARM_DESC(timer_margin, - "Watchdog timer margin" - "Time between interrupt and resetting the system" - "The range is from 1 to 160" - "This is the time for all keep alives to arrive"); - -static int timer_set = DEFAULT_TIME; -module_param(timer_set, int, 0); -MODULE_PARM_DESC(timer_set, - "Default Watchdog timer setting" - "Complete cycle time" - "The range is from 1 to 170" - "This is the time for all keep alives to arrive"); - -/* After watchdog device is closed, check force_boot. If: - * force_boot == 0, then force boot on next watchdog interrupt after close, - * force_boot == 1, then force boot immediately when device is closed. - */ -static int force_boot; -module_param(force_boot, int, 0); -MODULE_PARM_DESC(force_boot, - "A value of 1 means that the driver will reboot" - "the system immediately if the /dev/watchdog device is closed" - "A value of 0 means that when /dev/watchdog device is closed" - "the watchdog timer will be refreshed for one more interval" - "of length: timer_set. At the end of this interval, the" - "watchdog timer will reset the system." - ); - -/* there is only one device in the system now; this can be made into - * an array in the future if we have more than one device */ - -static struct intel_scu_watchdog_dev watchdog_device; - -/* Forces restart, if force_reboot is set */ -static void watchdog_fire(void) -{ - if (force_boot) { - pr_crit("Initiating system reboot\n"); - emergency_restart(); - pr_crit("Reboot didn't ?????\n"); - } - - else { - pr_crit("Immediate Reboot Disabled\n"); - pr_crit("System will reset when watchdog timer times out!\n"); - } -} - -static int check_timer_margin(int new_margin) -{ - if ((new_margin < MIN_TIME_CYCLE) || - (new_margin > MAX_TIME - timer_set)) { - pr_debug("value of new_margin %d is out of the range %d to %d\n", - new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set); - return -EINVAL; - } - return 0; -} - -/* - * IPC operations - */ -static int watchdog_set_ipc(int soft_threshold, int threshold) -{ - u32 *ipc_wbuf; - u8 cbuf[16] = { '\0' }; - int ipc_ret = 0; - - ipc_wbuf = (u32 *)&cbuf; - ipc_wbuf[0] = soft_threshold; - ipc_wbuf[1] = threshold; - - ipc_ret = intel_scu_ipc_command( - IPC_SET_WATCHDOG_TIMER, - 0, - ipc_wbuf, - 2, - NULL, - 0); - - if (ipc_ret != 0) - pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret); - - return ipc_ret; -}; - -/* - * Intel_SCU operations - */ - -/* timer interrupt handler */ -static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id) -{ - int int_status; - int_status = ioread32(watchdog_device.timer_interrupt_status_addr); - - pr_debug("irq, int_status: %x\n", int_status); - - if (int_status != 0) - return IRQ_NONE; - - /* has the timer been started? If not, then this is spurious */ - if (watchdog_device.timer_started == 0) { - pr_debug("spurious interrupt received\n"); - return IRQ_HANDLED; - } - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* set the timer to the threshold */ - iowrite32(watchdog_device.threshold, - watchdog_device.timer_load_count_addr); - - /* allow the timer to run */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - return IRQ_HANDLED; -} - -static int intel_scu_keepalive(void) -{ - - /* read eoi register - clears interrupt */ - ioread32(watchdog_device.timer_clear_interrupt_addr); - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* set the timer to the soft_threshold */ - iowrite32(watchdog_device.soft_threshold, - watchdog_device.timer_load_count_addr); - - /* allow the timer to run */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - return 0; -} - -static int intel_scu_stop(void) -{ - iowrite32(0, watchdog_device.timer_control_addr); - return 0; -} - -static int intel_scu_set_heartbeat(u32 t) -{ - int ipc_ret; - int retry_count; - u32 soft_value; - u32 hw_value; - - watchdog_device.timer_set = t; - watchdog_device.threshold = - timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; - watchdog_device.soft_threshold = - (watchdog_device.timer_set - timer_margin) - * watchdog_device.timer_tbl_ptr->freq_hz; - - pr_debug("set_heartbeat: timer freq is %d\n", - watchdog_device.timer_tbl_ptr->freq_hz); - pr_debug("set_heartbeat: timer_set is %x (hex)\n", - watchdog_device.timer_set); - pr_debug("set_heartbeat: timer_margin is %x (hex)\n", timer_margin); - pr_debug("set_heartbeat: threshold is %x (hex)\n", - watchdog_device.threshold); - pr_debug("set_heartbeat: soft_threshold is %x (hex)\n", - watchdog_device.soft_threshold); - - /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */ - /* watchdog timing come out right. */ - watchdog_device.threshold = - watchdog_device.threshold / FREQ_ADJUSTMENT; - watchdog_device.soft_threshold = - watchdog_device.soft_threshold / FREQ_ADJUSTMENT; - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* send the threshold and soft_threshold via IPC to the processor */ - ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold, - watchdog_device.threshold); - - if (ipc_ret != 0) { - /* Make sure the watchdog timer is stopped */ - intel_scu_stop(); - return ipc_ret; - } - - /* Soft Threshold set loop. Early versions of silicon did */ - /* not always set this count correctly. This loop checks */ - /* the value and retries if it was not set correctly. */ - - retry_count = 0; - soft_value = watchdog_device.soft_threshold & 0xFFFF0000; - do { - - /* Make sure timer is stopped */ - intel_scu_stop(); - - if (MAX_RETRY < retry_count++) { - /* Unable to set timer value */ - pr_err("Unable to set timer\n"); - return -ENODEV; - } - - /* set the timer to the soft threshold */ - iowrite32(watchdog_device.soft_threshold, - watchdog_device.timer_load_count_addr); - - /* read count value before starting timer */ - ioread32(watchdog_device.timer_load_count_addr); - - /* Start the timer */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - /* read the value the time loaded into its count reg */ - hw_value = ioread32(watchdog_device.timer_load_count_addr); - hw_value = hw_value & 0xFFFF0000; - - - } while (soft_value != hw_value); - - watchdog_device.timer_started = 1; - - return 0; -} - -/* - * /dev/watchdog handling - */ - -static int intel_scu_open(struct inode *inode, struct file *file) -{ - - /* Set flag to indicate that watchdog device is open */ - if (test_and_set_bit(0, &watchdog_device.driver_open)) - return -EBUSY; - - /* Check for reopen of driver. Reopens are not allowed */ - if (watchdog_device.driver_closed) - return -EPERM; - - return stream_open(inode, file); -} - -static int intel_scu_release(struct inode *inode, struct file *file) -{ - /* - * This watchdog should not be closed, after the timer - * is started with the WDIPC_SETTIMEOUT ioctl - * If force_boot is set watchdog_fire() will cause an - * immediate reset. If force_boot is not set, the watchdog - * timer is refreshed for one more interval. At the end - * of that interval, the watchdog timer will reset the system. - */ - - if (!test_and_clear_bit(0, &watchdog_device.driver_open)) { - pr_debug("intel_scu_release, without open\n"); - return -ENOTTY; - } - - if (!watchdog_device.timer_started) { - /* Just close, since timer has not been started */ - pr_debug("closed, without starting timer\n"); - return 0; - } - - pr_crit("Unexpected close of /dev/watchdog!\n"); - - /* Since the timer was started, prevent future reopens */ - watchdog_device.driver_closed = 1; - - /* Refresh the timer for one more interval */ - intel_scu_keepalive(); - - /* Reboot system (if force_boot is set) */ - watchdog_fire(); - - /* We should only reach this point if force_boot is not set */ - return 0; -} - -static ssize_t intel_scu_write(struct file *file, - char const *data, - size_t len, - loff_t *ppos) -{ - - if (watchdog_device.timer_started) - /* Watchdog already started, keep it alive */ - intel_scu_keepalive(); - else - /* Start watchdog with timer value set by init */ - intel_scu_set_heartbeat(watchdog_device.timer_set); - - return len; -} - -static long intel_scu_ioctl(struct file *file, - unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - u32 __user *p = argp; - u32 new_margin; - - - static const struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT - | WDIOF_KEEPALIVEPING, - .firmware_version = 0, /* @todo Get from SCU via - ipc_get_scu_fw_version()? */ - .identity = "Intel_SCU IOH Watchdog" /* len < 32 */ - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, - &ident, - sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_KEEPALIVE: - intel_scu_keepalive(); - - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_margin, p)) - return -EFAULT; - - if (check_timer_margin(new_margin)) - return -EINVAL; - - if (intel_scu_set_heartbeat(new_margin)) - return -EINVAL; - return 0; - case WDIOC_GETTIMEOUT: - return put_user(watchdog_device.soft_threshold, p); - - default: - return -ENOTTY; - } -} - -/* - * Notifier for system down - */ -static int intel_scu_notify_sys(struct notifier_block *this, - unsigned long code, - void *another_unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - /* Turn off the watchdog timer. */ - intel_scu_stop(); - return NOTIFY_DONE; -} - -/* - * Kernel Interfaces - */ -static const struct file_operations intel_scu_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = intel_scu_write, - .unlocked_ioctl = intel_scu_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .open = intel_scu_open, - .release = intel_scu_release, -}; - -static int __init intel_scu_watchdog_init(void) -{ - int ret; - u32 __iomem *tmp_addr; - - /* - * We don't really need to check this as the SFI timer get will fail - * but if we do so we can exit with a clearer reason and no noise. - * - * If it isn't an intel MID device then it doesn't have this watchdog - */ - if (!intel_mid_identify_cpu()) - return -ENODEV; - - /* Check boot parameters to verify that their initial values */ - /* are in range. */ - /* Check value of timer_set boot parameter */ - if ((timer_set < MIN_TIME_CYCLE) || - (timer_set > MAX_TIME - MIN_TIME_CYCLE)) { - pr_err("value of timer_set %x (hex) is out of range from %x to %x (hex)\n", - timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE); - return -EINVAL; - } - - /* Check value of timer_margin boot parameter */ - if (check_timer_margin(timer_margin)) - return -EINVAL; - - watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1); - - if (watchdog_device.timer_tbl_ptr == NULL) { - pr_debug("timer is not available\n"); - return -ENODEV; - } - /* make sure the timer exists */ - if (watchdog_device.timer_tbl_ptr->phys_addr == 0) { - pr_debug("timer %d does not have valid physical memory\n", - sfi_mtimer_num); - return -ENODEV; - } - - if (watchdog_device.timer_tbl_ptr->irq == 0) { - pr_debug("timer %d invalid irq\n", sfi_mtimer_num); - return -ENODEV; - } - - tmp_addr = ioremap(watchdog_device.timer_tbl_ptr->phys_addr, - 20); - - if (tmp_addr == NULL) { - pr_debug("timer unable to ioremap\n"); - return -ENOMEM; - } - - watchdog_device.timer_load_count_addr = tmp_addr++; - watchdog_device.timer_current_value_addr = tmp_addr++; - watchdog_device.timer_control_addr = tmp_addr++; - watchdog_device.timer_clear_interrupt_addr = tmp_addr++; - watchdog_device.timer_interrupt_status_addr = tmp_addr++; - - /* Set the default time values in device structure */ - - watchdog_device.timer_set = timer_set; - watchdog_device.threshold = - timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; - watchdog_device.soft_threshold = - (watchdog_device.timer_set - timer_margin) - * watchdog_device.timer_tbl_ptr->freq_hz; - - - watchdog_device.intel_scu_notifier.notifier_call = - intel_scu_notify_sys; - - ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier); - if (ret) { - pr_err("cannot register notifier %d)\n", ret); - goto register_reboot_error; - } - - watchdog_device.miscdev.minor = WATCHDOG_MINOR; - watchdog_device.miscdev.name = "watchdog"; - watchdog_device.miscdev.fops = &intel_scu_fops; - - ret = misc_register(&watchdog_device.miscdev); - if (ret) { - pr_err("cannot register miscdev %d err =%d\n", - WATCHDOG_MINOR, ret); - goto misc_register_error; - } - - ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq, - watchdog_timer_interrupt, - IRQF_SHARED, "watchdog", - &watchdog_device.timer_load_count_addr); - if (ret) { - pr_err("error requesting irq %d\n", ret); - goto request_irq_error; - } - /* Make sure timer is disabled before returning */ - intel_scu_stop(); - return 0; - -/* error cleanup */ - -request_irq_error: - misc_deregister(&watchdog_device.miscdev); -misc_register_error: - unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); -register_reboot_error: - intel_scu_stop(); - iounmap(watchdog_device.timer_load_count_addr); - return ret; -} -late_initcall(intel_scu_watchdog_init); diff --git a/drivers/watchdog/intel_scu_watchdog.h b/drivers/watchdog/intel_scu_watchdog.h deleted file mode 100644 index fb12a25ee417..000000000000 --- a/drivers/watchdog/intel_scu_watchdog.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device - * for Intel part #(s): - * - AF82MP20 PCH - * - * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. - */ - -#ifndef __INTEL_SCU_WATCHDOG_H -#define __INTEL_SCU_WATCHDOG_H - -#define WDT_VER "0.3" - -/* minimum time between interrupts */ -#define MIN_TIME_CYCLE 1 - -/* Time from warning to reboot is 2 seconds */ -#define DEFAULT_SOFT_TO_HARD_MARGIN 2 - -#define MAX_TIME 170 - -#define DEFAULT_TIME 5 - -#define MAX_SOFT_TO_HARD_MARGIN (MAX_TIME-MIN_TIME_CYCLE) - -/* Ajustment to clock tick frequency to make timing come out right */ -#define FREQ_ADJUSTMENT 8 - -struct intel_scu_watchdog_dev { - ulong driver_open; - ulong driver_closed; - u32 timer_started; - u32 timer_set; - u32 threshold; - u32 soft_threshold; - u32 __iomem *timer_load_count_addr; - u32 __iomem *timer_current_value_addr; - u32 __iomem *timer_control_addr; - u32 __iomem *timer_clear_interrupt_addr; - u32 __iomem *timer_interrupt_status_addr; - struct sfi_timer_table_entry *timer_tbl_ptr; - struct notifier_block intel_scu_notifier; - struct miscdevice miscdev; -}; - -extern int sfi_mtimer_num; - -/* extern struct sfi_timer_table_entry *sfi_get_mtmr(int hint); */ -#endif /* __INTEL_SCU_WATCHDOG_H */ diff --git a/drivers/watchdog/it8712f_wdt.c b/drivers/watchdog/it8712f_wdt.c index 9b89d2f09568..3ce6a58bd81e 100644 --- a/drivers/watchdog/it8712f_wdt.c +++ b/drivers/watchdog/it8712f_wdt.c @@ -31,7 +31,6 @@ #include #include -#define DEBUG #define NAME "it8712f_wdt" MODULE_AUTHOR("Jorge Boncompte - DTI2 "); diff --git a/drivers/watchdog/keembay_wdt.c b/drivers/watchdog/keembay_wdt.c new file mode 100644 index 000000000000..547d3fea33ff --- /dev/null +++ b/drivers/watchdog/keembay_wdt.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Watchdog driver for Intel Keem Bay non-secure watchdog. + * + * Copyright (C) 2020 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Non-secure watchdog register offsets */ +#define TIM_WATCHDOG 0x0 +#define TIM_WATCHDOG_INT_THRES 0x4 +#define TIM_WDOG_EN 0x8 +#define TIM_SAFE 0xc + +#define WDT_ISR_MASK GENMASK(9, 8) +#define WDT_ISR_CLEAR 0x8200ff18 +#define WDT_UNLOCK 0xf1d0dead +#define WDT_LOAD_MAX U32_MAX +#define WDT_LOAD_MIN 1 +#define WDT_TIMEOUT 5 + +static unsigned int timeout = WDT_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout period in seconds (default = " + __MODULE_STRING(WDT_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default = " + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +struct keembay_wdt { + struct watchdog_device wdd; + struct clk *clk; + unsigned int rate; + int to_irq; + int th_irq; + void __iomem *base; +}; + +static inline u32 keembay_wdt_readl(struct keembay_wdt *wdt, u32 offset) +{ + return readl(wdt->base + offset); +} + +static inline void keembay_wdt_writel(struct keembay_wdt *wdt, u32 offset, u32 val) +{ + writel(WDT_UNLOCK, wdt->base + TIM_SAFE); + writel(val, wdt->base + offset); +} + +static void keembay_wdt_set_timeout_reg(struct watchdog_device *wdog) +{ + struct keembay_wdt *wdt = watchdog_get_drvdata(wdog); + + keembay_wdt_writel(wdt, TIM_WATCHDOG, wdog->timeout * wdt->rate); +} + +static void keembay_wdt_set_pretimeout_reg(struct watchdog_device *wdog) +{ + struct keembay_wdt *wdt = watchdog_get_drvdata(wdog); + u32 th_val = 0; + + if (wdog->pretimeout) + th_val = wdog->timeout - wdog->pretimeout; + + keembay_wdt_writel(wdt, TIM_WATCHDOG_INT_THRES, th_val * wdt->rate); +} + +static int keembay_wdt_start(struct watchdog_device *wdog) +{ + struct keembay_wdt *wdt = watchdog_get_drvdata(wdog); + + keembay_wdt_set_timeout_reg(wdog); + keembay_wdt_writel(wdt, TIM_WDOG_EN, 1); + + return 0; +} + +static int keembay_wdt_stop(struct watchdog_device *wdog) +{ + struct keembay_wdt *wdt = watchdog_get_drvdata(wdog); + + keembay_wdt_writel(wdt, TIM_WDOG_EN, 0); + + return 0; +} + +static int keembay_wdt_ping(struct watchdog_device *wdog) +{ + keembay_wdt_set_timeout_reg(wdog); + + return 0; +} + +static int keembay_wdt_set_timeout(struct watchdog_device *wdog, u32 t) +{ + wdog->timeout = t; + keembay_wdt_set_timeout_reg(wdog); + + return 0; +} + +static int keembay_wdt_set_pretimeout(struct watchdog_device *wdog, u32 t) +{ + if (t > wdog->timeout) + return -EINVAL; + + wdog->pretimeout = t; + keembay_wdt_set_pretimeout_reg(wdog); + + return 0; +} + +static unsigned int keembay_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct keembay_wdt *wdt = watchdog_get_drvdata(wdog); + + return keembay_wdt_readl(wdt, TIM_WATCHDOG) / wdt->rate; +} + +/* + * SMC call is used to clear the interrupt bits, because the TIM_GEN_CONFIG + * register is in the secure bank. + */ +static irqreturn_t keembay_wdt_to_isr(int irq, void *dev_id) +{ + struct keembay_wdt *wdt = dev_id; + struct arm_smccc_res res; + + keembay_wdt_writel(wdt, TIM_WATCHDOG, 1); + arm_smccc_smc(WDT_ISR_CLEAR, WDT_ISR_MASK, 0, 0, 0, 0, 0, 0, &res); + dev_crit(wdt->wdd.parent, "Intel Keem Bay non-sec wdt timeout.\n"); + emergency_restart(); + + return IRQ_HANDLED; +} + +static irqreturn_t keembay_wdt_th_isr(int irq, void *dev_id) +{ + struct keembay_wdt *wdt = dev_id; + struct arm_smccc_res res; + + arm_smccc_smc(WDT_ISR_CLEAR, WDT_ISR_MASK, 0, 0, 0, 0, 0, 0, &res); + dev_crit(wdt->wdd.parent, "Intel Keem Bay non-sec wdt pre-timeout.\n"); + watchdog_notify_pretimeout(&wdt->wdd); + + return IRQ_HANDLED; +} + +static const struct watchdog_info keembay_wdt_info = { + .identity = "Intel Keem Bay Watchdog Timer", + .options = WDIOF_SETTIMEOUT | + WDIOF_PRETIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, +}; + +static const struct watchdog_ops keembay_wdt_ops = { + .owner = THIS_MODULE, + .start = keembay_wdt_start, + .stop = keembay_wdt_stop, + .ping = keembay_wdt_ping, + .set_timeout = keembay_wdt_set_timeout, + .set_pretimeout = keembay_wdt_set_pretimeout, + .get_timeleft = keembay_wdt_get_timeleft, +}; + +static int keembay_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct keembay_wdt *wdt; + int ret; + + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + /* we do not need to enable the clock as it is enabled by default */ + wdt->clk = devm_clk_get(dev, NULL); + if (IS_ERR(wdt->clk)) + return dev_err_probe(dev, PTR_ERR(wdt->clk), "Failed to get clock\n"); + + wdt->rate = clk_get_rate(wdt->clk); + if (!wdt->rate) + return dev_err_probe(dev, -EINVAL, "Failed to get clock rate\n"); + + wdt->th_irq = platform_get_irq_byname(pdev, "threshold"); + if (wdt->th_irq < 0) + return dev_err_probe(dev, wdt->th_irq, "Failed to get IRQ for threshold\n"); + + ret = devm_request_irq(dev, wdt->th_irq, keembay_wdt_th_isr, 0, + "keembay-wdt", wdt); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ for threshold\n"); + + wdt->to_irq = platform_get_irq_byname(pdev, "timeout"); + if (wdt->to_irq < 0) + return dev_err_probe(dev, wdt->to_irq, "Failed to get IRQ for timeout\n"); + + ret = devm_request_irq(dev, wdt->to_irq, keembay_wdt_to_isr, 0, + "keembay-wdt", wdt); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ for timeout\n"); + + wdt->wdd.parent = dev; + wdt->wdd.info = &keembay_wdt_info; + wdt->wdd.ops = &keembay_wdt_ops; + wdt->wdd.min_timeout = WDT_LOAD_MIN; + wdt->wdd.max_timeout = WDT_LOAD_MAX / wdt->rate; + wdt->wdd.timeout = WDT_TIMEOUT; + + watchdog_set_drvdata(&wdt->wdd, wdt); + watchdog_set_nowayout(&wdt->wdd, nowayout); + watchdog_init_timeout(&wdt->wdd, timeout, dev); + keembay_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout); + + ret = devm_watchdog_register_device(dev, &wdt->wdd); + if (ret) + return dev_err_probe(dev, ret, "Failed to register watchdog device.\n"); + + platform_set_drvdata(pdev, wdt); + dev_info(dev, "Initial timeout %d sec%s.\n", + wdt->wdd.timeout, nowayout ? ", nowayout" : ""); + + return 0; +} + +static int __maybe_unused keembay_wdt_suspend(struct device *dev) +{ + struct keembay_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return keembay_wdt_stop(&wdt->wdd); + + return 0; +} + +static int __maybe_unused keembay_wdt_resume(struct device *dev) +{ + struct keembay_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return keembay_wdt_start(&wdt->wdd); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(keembay_wdt_pm_ops, keembay_wdt_suspend, + keembay_wdt_resume); + +static const struct of_device_id keembay_wdt_match[] = { + { .compatible = "intel,keembay-wdt" }, + { } +}; +MODULE_DEVICE_TABLE(of, keembay_wdt_match); + +static struct platform_driver keembay_wdt_driver = { + .probe = keembay_wdt_probe, + .driver = { + .name = "keembay_wdt", + .of_match_table = keembay_wdt_match, + .pm = &keembay_wdt_pm_ops, + }, +}; + +module_platform_driver(keembay_wdt_driver); + +MODULE_DESCRIPTION("Intel Keem Bay SoC watchdog driver"); +MODULE_AUTHOR("Wan Ahmad Zainie wdd, wdt); watchdog_stop_on_reboot(&wdt->wdd); + watchdog_stop_on_unregister(&wdt->wdd); ret = watchdog_register_device(&wdt->wdd); if (ret) diff --git a/drivers/watchdog/mtk_wdt.c b/drivers/watchdog/mtk_wdt.c index d6a6393f609d..97ca993bd009 100644 --- a/drivers/watchdog/mtk_wdt.c +++ b/drivers/watchdog/mtk_wdt.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -76,6 +77,10 @@ static const struct mtk_wdt_data mt8183_data = { .toprgu_sw_rst_num = MT8183_TOPRGU_SW_RST_NUM, }; +static const struct mtk_wdt_data mt8192_data = { + .toprgu_sw_rst_num = MT8192_TOPRGU_SW_RST_NUM, +}; + static int toprgu_reset_update(struct reset_controller_dev *rcdev, unsigned long id, bool assert) { @@ -195,6 +200,19 @@ static int mtk_wdt_set_timeout(struct watchdog_device *wdt_dev, return 0; } +static void mtk_wdt_init(struct watchdog_device *wdt_dev) +{ + struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); + void __iomem *wdt_base; + + wdt_base = mtk_wdt->wdt_base; + + if (readl(wdt_base + WDT_MODE) & WDT_MODE_EN) { + set_bit(WDOG_HW_RUNNING, &wdt_dev->status); + mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout); + } +} + static int mtk_wdt_stop(struct watchdog_device *wdt_dev) { struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); @@ -264,7 +282,7 @@ static int mtk_wdt_probe(struct platform_device *pdev) mtk_wdt->wdt_dev.info = &mtk_wdt_info; mtk_wdt->wdt_dev.ops = &mtk_wdt_ops; mtk_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; - mtk_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; + mtk_wdt->wdt_dev.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT * 1000; mtk_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; mtk_wdt->wdt_dev.parent = dev; @@ -274,7 +292,7 @@ static int mtk_wdt_probe(struct platform_device *pdev) watchdog_set_drvdata(&mtk_wdt->wdt_dev, mtk_wdt); - mtk_wdt_stop(&mtk_wdt->wdt_dev); + mtk_wdt_init(&mtk_wdt->wdt_dev); watchdog_stop_on_reboot(&mtk_wdt->wdt_dev); err = devm_watchdog_register_device(dev, &mtk_wdt->wdt_dev); @@ -322,6 +340,7 @@ static const struct of_device_id mtk_wdt_dt_ids[] = { { .compatible = "mediatek,mt2712-wdt", .data = &mt2712_data }, { .compatible = "mediatek,mt6589-wdt" }, { .compatible = "mediatek,mt8183-wdt", .data = &mt8183_data }, + { .compatible = "mediatek,mt8192-wdt", .data = &mt8192_data }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mtk_wdt_dt_ids); diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 7cf0f2ec649b..e38a87ffe5f5 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -22,7 +22,6 @@ enum wdt_reg { }; #define QCOM_WDT_ENABLE BIT(0) -#define QCOM_WDT_ENABLE_IRQ BIT(1) static const u32 reg_offset_data_apcs_tmr[] = { [WDT_RST] = 0x38, @@ -63,16 +62,6 @@ struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd) return container_of(wdd, struct qcom_wdt, wdd); } -static inline int qcom_get_enable(struct watchdog_device *wdd) -{ - int enable = QCOM_WDT_ENABLE; - - if (wdd->pretimeout) - enable |= QCOM_WDT_ENABLE_IRQ; - - return enable; -} - static irqreturn_t qcom_wdt_isr(int irq, void *arg) { struct watchdog_device *wdd = arg; @@ -91,7 +80,7 @@ static int qcom_wdt_start(struct watchdog_device *wdd) writel(1, wdt_addr(wdt, WDT_RST)); writel(bark * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME)); writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME)); - writel(qcom_get_enable(wdd), wdt_addr(wdt, WDT_EN)); + writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN)); return 0; } diff --git a/drivers/watchdog/renesas_wdt.c b/drivers/watchdog/renesas_wdt.c index 47fce4de0110..5791198960e6 100644 --- a/drivers/watchdog/renesas_wdt.c +++ b/drivers/watchdog/renesas_wdt.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ struct rwdt_priv { struct watchdog_device wdev; unsigned long clk_rate; u8 cks; + struct clk *clk; }; static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg) @@ -125,13 +127,33 @@ static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev) return DIV_BY_CLKS_PER_SEC(priv, 65536 - val); } +/* needs to be atomic - no RPM, no usleep_range, no scheduling! */ static int rwdt_restart(struct watchdog_device *wdev, unsigned long action, void *data) { struct rwdt_priv *priv = watchdog_get_drvdata(wdev); + u8 val; + + clk_prepare_enable(priv->clk); + + /* Stop the timer before we modify any register */ + val = readb_relaxed(priv->base + RWTCSRA) & ~RWTCSRA_TME; + rwdt_write(priv, val, RWTCSRA); + /* Delay 2 cycles before setting watchdog counter */ + udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate)); - rwdt_start(wdev); rwdt_write(priv, 0xffff, RWTCNT); + /* smallest divider to reboot soon */ + rwdt_write(priv, 0, RWTCSRA); + + readb_poll_timeout_atomic(priv->base + RWTCSRA, val, + !(val & RWTCSRA_WRFLG), 1, 100); + + rwdt_write(priv, RWTCSRA_TME, RWTCSRA); + + /* wait 2 cycles, so watchdog will trigger */ + udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate)); + return 0; } @@ -191,7 +213,6 @@ static int rwdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rwdt_priv *priv; - struct clk *clk; unsigned long clks_per_sec; int ret, i; u8 csra; @@ -207,13 +228,13 @@ static int rwdt_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); - clk = devm_clk_get(dev, NULL); - if (IS_ERR(clk)) - return PTR_ERR(clk); + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); pm_runtime_enable(dev); pm_runtime_get_sync(dev); - priv->clk_rate = clk_get_rate(clk); + priv->clk_rate = clk_get_rate(priv->clk); csra = readb_relaxed(priv->base + RWTCSRA); priv->wdev.bootstatus = csra & RWTCSRA_WOVF ? WDIOF_CARDRESET : 0; pm_runtime_put(dev); diff --git a/drivers/watchdog/sirfsoc_wdt.c b/drivers/watchdog/sirfsoc_wdt.c deleted file mode 100644 index 734cf2966ecb..000000000000 --- a/drivers/watchdog/sirfsoc_wdt.c +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Watchdog driver for CSR SiRFprimaII and SiRFatlasVI - * - * Copyright (c) 2013 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define CLOCK_FREQ 1000000 - -#define SIRFSOC_TIMER_COUNTER_LO 0x0000 -#define SIRFSOC_TIMER_MATCH_0 0x0008 -#define SIRFSOC_TIMER_INT_EN 0x0024 -#define SIRFSOC_TIMER_WATCHDOG_EN 0x0028 -#define SIRFSOC_TIMER_LATCH 0x0030 -#define SIRFSOC_TIMER_LATCHED_LO 0x0034 - -#define SIRFSOC_TIMER_WDT_INDEX 5 - -#define SIRFSOC_WDT_MIN_TIMEOUT 30 /* 30 secs */ -#define SIRFSOC_WDT_MAX_TIMEOUT (10 * 60) /* 10 mins */ -#define SIRFSOC_WDT_DEFAULT_TIMEOUT 30 /* 30 secs */ - -static unsigned int timeout; -static bool nowayout = WATCHDOG_NOWAYOUT; - -module_param(timeout, uint, 0); -module_param(nowayout, bool, 0); - -MODULE_PARM_DESC(timeout, "Default watchdog timeout (in seconds)"); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -static void __iomem *sirfsoc_wdt_base(struct watchdog_device *wdd) -{ - return (void __iomem __force *)watchdog_get_drvdata(wdd); -} - -static unsigned int sirfsoc_wdt_gettimeleft(struct watchdog_device *wdd) -{ - u32 counter, match; - void __iomem *wdt_base; - int time_left; - - wdt_base = sirfsoc_wdt_base(wdd); - counter = readl(wdt_base + SIRFSOC_TIMER_COUNTER_LO); - match = readl(wdt_base + - SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); - - time_left = match - counter; - - return time_left / CLOCK_FREQ; -} - -static int sirfsoc_wdt_updatetimeout(struct watchdog_device *wdd) -{ - u32 counter, timeout_ticks; - void __iomem *wdt_base; - - timeout_ticks = wdd->timeout * CLOCK_FREQ; - wdt_base = sirfsoc_wdt_base(wdd); - - /* Enable the latch before reading the LATCH_LO register */ - writel(1, wdt_base + SIRFSOC_TIMER_LATCH); - - /* Set the TO value */ - counter = readl(wdt_base + SIRFSOC_TIMER_LATCHED_LO); - - counter += timeout_ticks; - - writel(counter, wdt_base + - SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); - - return 0; -} - -static int sirfsoc_wdt_enable(struct watchdog_device *wdd) -{ - void __iomem *wdt_base = sirfsoc_wdt_base(wdd); - sirfsoc_wdt_updatetimeout(wdd); - - /* - * NOTE: If interrupt is not enabled - * then WD-Reset doesn't get generated at all. - */ - writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) - | (1 << SIRFSOC_TIMER_WDT_INDEX), - wdt_base + SIRFSOC_TIMER_INT_EN); - writel(1, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); - - return 0; -} - -static int sirfsoc_wdt_disable(struct watchdog_device *wdd) -{ - void __iomem *wdt_base = sirfsoc_wdt_base(wdd); - - writel(0, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); - writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) - & (~(1 << SIRFSOC_TIMER_WDT_INDEX)), - wdt_base + SIRFSOC_TIMER_INT_EN); - - return 0; -} - -static int sirfsoc_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) -{ - wdd->timeout = to; - sirfsoc_wdt_updatetimeout(wdd); - - return 0; -} - -#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) - -static const struct watchdog_info sirfsoc_wdt_ident = { - .options = OPTIONS, - .firmware_version = 0, - .identity = "SiRFSOC Watchdog", -}; - -static const struct watchdog_ops sirfsoc_wdt_ops = { - .owner = THIS_MODULE, - .start = sirfsoc_wdt_enable, - .stop = sirfsoc_wdt_disable, - .get_timeleft = sirfsoc_wdt_gettimeleft, - .ping = sirfsoc_wdt_updatetimeout, - .set_timeout = sirfsoc_wdt_settimeout, -}; - -static struct watchdog_device sirfsoc_wdd = { - .info = &sirfsoc_wdt_ident, - .ops = &sirfsoc_wdt_ops, - .timeout = SIRFSOC_WDT_DEFAULT_TIMEOUT, - .min_timeout = SIRFSOC_WDT_MIN_TIMEOUT, - .max_timeout = SIRFSOC_WDT_MAX_TIMEOUT, -}; - -static int sirfsoc_wdt_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - int ret; - void __iomem *base; - - base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(base)) - return PTR_ERR(base); - - watchdog_set_drvdata(&sirfsoc_wdd, (__force void *)base); - - watchdog_init_timeout(&sirfsoc_wdd, timeout, dev); - watchdog_set_nowayout(&sirfsoc_wdd, nowayout); - sirfsoc_wdd.parent = dev; - - watchdog_stop_on_reboot(&sirfsoc_wdd); - watchdog_stop_on_unregister(&sirfsoc_wdd); - ret = devm_watchdog_register_device(dev, &sirfsoc_wdd); - if (ret) - return ret; - - platform_set_drvdata(pdev, &sirfsoc_wdd); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int sirfsoc_wdt_suspend(struct device *dev) -{ - return 0; -} - -static int sirfsoc_wdt_resume(struct device *dev) -{ - struct watchdog_device *wdd = dev_get_drvdata(dev); - - /* - * NOTE: Since timer controller registers settings are saved - * and restored back by the timer-prima2.c, so we need not - * update WD settings except refreshing timeout. - */ - sirfsoc_wdt_updatetimeout(wdd); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(sirfsoc_wdt_pm_ops, - sirfsoc_wdt_suspend, sirfsoc_wdt_resume); - -static const struct of_device_id sirfsoc_wdt_of_match[] = { - { .compatible = "sirf,prima2-tick"}, - {}, -}; -MODULE_DEVICE_TABLE(of, sirfsoc_wdt_of_match); - -static struct platform_driver sirfsoc_wdt_driver = { - .driver = { - .name = "sirfsoc-wdt", - .pm = &sirfsoc_wdt_pm_ops, - .of_match_table = sirfsoc_wdt_of_match, - }, - .probe = sirfsoc_wdt_probe, -}; -module_platform_driver(sirfsoc_wdt_driver); - -MODULE_DESCRIPTION("SiRF SoC watchdog driver"); -MODULE_AUTHOR("Xianglong Du "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:sirfsoc-wdt"); diff --git a/drivers/watchdog/tangox_wdt.c b/drivers/watchdog/tangox_wdt.c deleted file mode 100644 index 1afb0e9d808c..000000000000 --- a/drivers/watchdog/tangox_wdt.c +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2015 Mans Rullgard - * SMP86xx/SMP87xx Watchdog driver - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_TIMEOUT 30 - -static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, - "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -static unsigned int timeout; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, "Watchdog timeout"); - -/* - * Counter counts down from programmed value. Reset asserts when - * the counter reaches 1. - */ -#define WD_COUNTER 0 - -#define WD_CONFIG 4 -#define WD_CONFIG_XTAL_IN BIT(0) -#define WD_CONFIG_DISABLE BIT(31) - -struct tangox_wdt_device { - struct watchdog_device wdt; - void __iomem *base; - unsigned long clk_rate; - struct clk *clk; -}; - -static int tangox_wdt_set_timeout(struct watchdog_device *wdt, - unsigned int new_timeout) -{ - wdt->timeout = new_timeout; - - return 0; -} - -static int tangox_wdt_start(struct watchdog_device *wdt) -{ - struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); - u32 ticks; - - ticks = 1 + wdt->timeout * dev->clk_rate; - writel(ticks, dev->base + WD_COUNTER); - - return 0; -} - -static int tangox_wdt_stop(struct watchdog_device *wdt) -{ - struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); - - writel(0, dev->base + WD_COUNTER); - - return 0; -} - -static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt) -{ - struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); - u32 count; - - count = readl(dev->base + WD_COUNTER); - - if (!count) - return 0; - - return (count - 1) / dev->clk_rate; -} - -static const struct watchdog_info tangox_wdt_info = { - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .identity = "tangox watchdog", -}; - -static int tangox_wdt_restart(struct watchdog_device *wdt, - unsigned long action, void *data) -{ - struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); - - writel(1, dev->base + WD_COUNTER); - - return 0; -} - -static const struct watchdog_ops tangox_wdt_ops = { - .start = tangox_wdt_start, - .stop = tangox_wdt_stop, - .set_timeout = tangox_wdt_set_timeout, - .get_timeleft = tangox_wdt_get_timeleft, - .restart = tangox_wdt_restart, -}; - -static void tangox_clk_disable_unprepare(void *data) -{ - clk_disable_unprepare(data); -} - -static int tangox_wdt_probe(struct platform_device *pdev) -{ - struct tangox_wdt_device *dev; - u32 config; - int err; - - dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - dev->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(dev->base)) - return PTR_ERR(dev->base); - - dev->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(dev->clk)) - return PTR_ERR(dev->clk); - - err = clk_prepare_enable(dev->clk); - if (err) - return err; - err = devm_add_action_or_reset(&pdev->dev, - tangox_clk_disable_unprepare, dev->clk); - if (err) - return err; - - dev->clk_rate = clk_get_rate(dev->clk); - if (!dev->clk_rate) - return -EINVAL; - - dev->wdt.parent = &pdev->dev; - dev->wdt.info = &tangox_wdt_info; - dev->wdt.ops = &tangox_wdt_ops; - dev->wdt.timeout = DEFAULT_TIMEOUT; - dev->wdt.min_timeout = 1; - dev->wdt.max_hw_heartbeat_ms = (U32_MAX - 1) / dev->clk_rate; - - watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); - watchdog_set_nowayout(&dev->wdt, nowayout); - watchdog_set_drvdata(&dev->wdt, dev); - - /* - * Deactivate counter if disable bit is set to avoid - * accidental reset. - */ - config = readl(dev->base + WD_CONFIG); - if (config & WD_CONFIG_DISABLE) - writel(0, dev->base + WD_COUNTER); - - writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG); - - /* - * Mark as active and restart with configured timeout if - * already running. - */ - if (readl(dev->base + WD_COUNTER)) { - set_bit(WDOG_HW_RUNNING, &dev->wdt.status); - tangox_wdt_start(&dev->wdt); - } - - watchdog_set_restart_priority(&dev->wdt, 128); - - watchdog_stop_on_unregister(&dev->wdt); - err = devm_watchdog_register_device(&pdev->dev, &dev->wdt); - if (err) - return err; - - platform_set_drvdata(pdev, dev); - - dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); - - return 0; -} - -static const struct of_device_id tangox_wdt_dt_ids[] = { - { .compatible = "sigma,smp8642-wdt" }, - { .compatible = "sigma,smp8759-wdt" }, - { } -}; -MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids); - -static struct platform_driver tangox_wdt_driver = { - .probe = tangox_wdt_probe, - .driver = { - .name = "tangox-wdt", - .of_match_table = tangox_wdt_dt_ids, - }, -}; - -module_platform_driver(tangox_wdt_driver); - -MODULE_AUTHOR("Mans Rullgard "); -MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 0e9a99559609..5df0a22e2cb4 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -158,7 +158,7 @@ static int watchdog_reboot_notifier(struct notifier_block *nb, wdd = container_of(nb, struct watchdog_device, reboot_nb); if (code == SYS_DOWN || code == SYS_HALT) { - if (watchdog_active(wdd)) { + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) { int ret; ret = wdd->ops->stop(wdd); diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index cab86a08456b..4297280807ca 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/drivers/watchdog/zx2967_wdt.c b/drivers/watchdog/zx2967_wdt.c deleted file mode 100644 index bf183e73671a..000000000000 --- a/drivers/watchdog/zx2967_wdt.c +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * watchdog driver for ZTE's zx2967 family - * - * Copyright (C) 2017 ZTE Ltd. - * - * Author: Baoyou Xie - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ZX2967_WDT_CFG_REG 0x4 -#define ZX2967_WDT_LOAD_REG 0x8 -#define ZX2967_WDT_REFRESH_REG 0x18 -#define ZX2967_WDT_START_REG 0x1c - -#define ZX2967_WDT_REFRESH_MASK GENMASK(5, 0) - -#define ZX2967_WDT_CFG_DIV(n) ((((n) & 0xff) - 1) << 8) -#define ZX2967_WDT_START_EN 0x1 - -/* - * Hardware magic number. - * When watchdog reg is written, the lowest 16 bits are valid, but - * the highest 16 bits should be always this number. - */ -#define ZX2967_WDT_WRITEKEY (0x1234 << 16) -#define ZX2967_WDT_VAL_MASK GENMASK(15, 0) - -#define ZX2967_WDT_DIV_DEFAULT 16 -#define ZX2967_WDT_DEFAULT_TIMEOUT 32 -#define ZX2967_WDT_MIN_TIMEOUT 1 -#define ZX2967_WDT_MAX_TIMEOUT 524 -#define ZX2967_WDT_MAX_COUNT 0xffff - -#define ZX2967_WDT_CLK_FREQ 0x8000 - -#define ZX2967_WDT_FLAG_REBOOT_MON BIT(0) - -struct zx2967_wdt { - struct watchdog_device wdt_device; - void __iomem *reg_base; - struct clk *clock; -}; - -static inline u32 zx2967_wdt_readl(struct zx2967_wdt *wdt, u16 reg) -{ - return readl_relaxed(wdt->reg_base + reg); -} - -static inline void zx2967_wdt_writel(struct zx2967_wdt *wdt, u16 reg, u32 val) -{ - writel_relaxed(val | ZX2967_WDT_WRITEKEY, wdt->reg_base + reg); -} - -static void zx2967_wdt_refresh(struct zx2967_wdt *wdt) -{ - u32 val; - - val = zx2967_wdt_readl(wdt, ZX2967_WDT_REFRESH_REG); - /* - * Bit 4-5, 1 and 2: refresh config info - * Bit 2-3, 1 and 2: refresh counter - * Bit 0-1, 1 and 2: refresh int-value - * we shift each group value between 1 and 2 to refresh all data. - */ - val ^= ZX2967_WDT_REFRESH_MASK; - zx2967_wdt_writel(wdt, ZX2967_WDT_REFRESH_REG, - val & ZX2967_WDT_VAL_MASK); -} - -static int -zx2967_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) -{ - struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); - unsigned int divisor = ZX2967_WDT_DIV_DEFAULT; - u32 count; - - count = timeout * ZX2967_WDT_CLK_FREQ; - if (count > divisor * ZX2967_WDT_MAX_COUNT) - divisor = DIV_ROUND_UP(count, ZX2967_WDT_MAX_COUNT); - count = DIV_ROUND_UP(count, divisor); - zx2967_wdt_writel(wdt, ZX2967_WDT_CFG_REG, - ZX2967_WDT_CFG_DIV(divisor) & ZX2967_WDT_VAL_MASK); - zx2967_wdt_writel(wdt, ZX2967_WDT_LOAD_REG, - count & ZX2967_WDT_VAL_MASK); - zx2967_wdt_refresh(wdt); - wdd->timeout = (count * divisor) / ZX2967_WDT_CLK_FREQ; - - return 0; -} - -static void __zx2967_wdt_start(struct zx2967_wdt *wdt) -{ - u32 val; - - val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); - val |= ZX2967_WDT_START_EN; - zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, - val & ZX2967_WDT_VAL_MASK); -} - -static void __zx2967_wdt_stop(struct zx2967_wdt *wdt) -{ - u32 val; - - val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); - val &= ~ZX2967_WDT_START_EN; - zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, - val & ZX2967_WDT_VAL_MASK); -} - -static int zx2967_wdt_start(struct watchdog_device *wdd) -{ - struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); - - zx2967_wdt_set_timeout(wdd, wdd->timeout); - __zx2967_wdt_start(wdt); - - return 0; -} - -static int zx2967_wdt_stop(struct watchdog_device *wdd) -{ - struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); - - __zx2967_wdt_stop(wdt); - - return 0; -} - -static int zx2967_wdt_keepalive(struct watchdog_device *wdd) -{ - struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); - - zx2967_wdt_refresh(wdt); - - return 0; -} - -#define ZX2967_WDT_OPTIONS \ - (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) -static const struct watchdog_info zx2967_wdt_ident = { - .options = ZX2967_WDT_OPTIONS, - .identity = "zx2967 watchdog", -}; - -static const struct watchdog_ops zx2967_wdt_ops = { - .owner = THIS_MODULE, - .start = zx2967_wdt_start, - .stop = zx2967_wdt_stop, - .ping = zx2967_wdt_keepalive, - .set_timeout = zx2967_wdt_set_timeout, -}; - -static void zx2967_wdt_reset_sysctrl(struct device *dev) -{ - int ret; - void __iomem *regmap; - unsigned int offset, mask, config; - struct of_phandle_args out_args; - - ret = of_parse_phandle_with_fixed_args(dev->of_node, - "zte,wdt-reset-sysctrl", 3, 0, &out_args); - if (ret) - return; - - offset = out_args.args[0]; - config = out_args.args[1]; - mask = out_args.args[2]; - - regmap = syscon_node_to_regmap(out_args.np); - if (IS_ERR(regmap)) { - of_node_put(out_args.np); - return; - } - - regmap_update_bits(regmap, offset, mask, config); - of_node_put(out_args.np); -} - -static void zx2967_clk_disable_unprepare(void *data) -{ - clk_disable_unprepare(data); -} - -static int zx2967_wdt_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct zx2967_wdt *wdt; - int ret; - struct reset_control *rstc; - - wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); - if (!wdt) - return -ENOMEM; - - platform_set_drvdata(pdev, wdt); - - wdt->wdt_device.info = &zx2967_wdt_ident; - wdt->wdt_device.ops = &zx2967_wdt_ops; - wdt->wdt_device.timeout = ZX2967_WDT_DEFAULT_TIMEOUT; - wdt->wdt_device.max_timeout = ZX2967_WDT_MAX_TIMEOUT; - wdt->wdt_device.min_timeout = ZX2967_WDT_MIN_TIMEOUT; - wdt->wdt_device.parent = dev; - - wdt->reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(wdt->reg_base)) - return PTR_ERR(wdt->reg_base); - - zx2967_wdt_reset_sysctrl(dev); - - wdt->clock = devm_clk_get(dev, NULL); - if (IS_ERR(wdt->clock)) { - dev_err(dev, "failed to find watchdog clock source\n"); - return PTR_ERR(wdt->clock); - } - - ret = clk_prepare_enable(wdt->clock); - if (ret < 0) { - dev_err(dev, "failed to enable clock\n"); - return ret; - } - ret = devm_add_action_or_reset(dev, zx2967_clk_disable_unprepare, - wdt->clock); - if (ret) - return ret; - clk_set_rate(wdt->clock, ZX2967_WDT_CLK_FREQ); - - rstc = devm_reset_control_get_exclusive(dev, NULL); - if (IS_ERR(rstc)) { - dev_err(dev, "failed to get rstc"); - return PTR_ERR(rstc); - } - - reset_control_assert(rstc); - reset_control_deassert(rstc); - - watchdog_set_drvdata(&wdt->wdt_device, wdt); - watchdog_init_timeout(&wdt->wdt_device, - ZX2967_WDT_DEFAULT_TIMEOUT, dev); - watchdog_set_nowayout(&wdt->wdt_device, WATCHDOG_NOWAYOUT); - - ret = devm_watchdog_register_device(dev, &wdt->wdt_device); - if (ret) - return ret; - - dev_info(dev, "watchdog enabled (timeout=%d sec, nowayout=%d)", - wdt->wdt_device.timeout, WATCHDOG_NOWAYOUT); - - return 0; -} - -static const struct of_device_id zx2967_wdt_match[] = { - { .compatible = "zte,zx296718-wdt", }, - {} -}; -MODULE_DEVICE_TABLE(of, zx2967_wdt_match); - -static struct platform_driver zx2967_wdt_driver = { - .probe = zx2967_wdt_probe, - .driver = { - .name = "zx2967-wdt", - .of_match_table = of_match_ptr(zx2967_wdt_match), - }, -}; -module_platform_driver(zx2967_wdt_driver); - -MODULE_AUTHOR("Baoyou Xie "); -MODULE_DESCRIPTION("ZTE zx2967 Watchdog Device Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/reset-controller/mt8192-resets.h b/include/dt-bindings/reset-controller/mt8192-resets.h new file mode 100644 index 000000000000..be9a7ca245b9 --- /dev/null +++ b/include/dt-bindings/reset-controller/mt8192-resets.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 MediaTek Inc. + * Author: Yong Liang + */ + +#ifndef _DT_BINDINGS_RESET_CONTROLLER_MT8192 +#define _DT_BINDINGS_RESET_CONTROLLER_MT8192 + +#define MT8192_TOPRGU_MM_SW_RST 1 +#define MT8192_TOPRGU_MFG_SW_RST 2 +#define MT8192_TOPRGU_VENC_SW_RST 3 +#define MT8192_TOPRGU_VDEC_SW_RST 4 +#define MT8192_TOPRGU_IMG_SW_RST 5 +#define MT8192_TOPRGU_MD_SW_RST 7 +#define MT8192_TOPRGU_CONN_SW_RST 9 +#define MT8192_TOPRGU_CONN_MCU_SW_RST 12 +#define MT8192_TOPRGU_IPU0_SW_RST 14 +#define MT8192_TOPRGU_IPU1_SW_RST 15 +#define MT8192_TOPRGU_AUDIO_SW_RST 17 +#define MT8192_TOPRGU_CAMSYS_SW_RST 18 +#define MT8192_TOPRGU_MJC_SW_RST 19 +#define MT8192_TOPRGU_C2K_S2_SW_RST 20 +#define MT8192_TOPRGU_C2K_SW_RST 21 +#define MT8192_TOPRGU_PERI_SW_RST 22 +#define MT8192_TOPRGU_PERI_AO_SW_RST 23 + +#define MT8192_TOPRGU_SW_RST_NUM 23 + +#endif /* _DT_BINDINGS_RESET_CONTROLLER_MT8192 */ diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index 58216124d89d..33f60f43e1aa 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -158,6 +158,9 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype, const struct adc5_data *data, u16 adc_code, int *result_mdec); +u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio, + u32 full_scale_code_volt, int temp); + int qcom_adc5_prescaling_from_dt(u32 num, u32 den); int qcom_adc5_hw_settle_time_from_dt(u32 value, const unsigned int *hw_settle); diff --git a/include/linux/keyslot-manager.h b/include/linux/keyslot-manager.h index 05bfaeb24e06..2eb44e2ad81d 100644 --- a/include/linux/keyslot-manager.h +++ b/include/linux/keyslot-manager.h @@ -107,6 +107,9 @@ struct blk_keyslot_manager { int blk_ksm_init(struct blk_keyslot_manager *ksm, unsigned int num_slots); +int devm_blk_ksm_init(struct device *dev, struct blk_keyslot_manager *ksm, + unsigned int num_slots); + blk_status_t blk_ksm_get_slot_for_key(struct blk_keyslot_manager *ksm, const struct blk_crypto_key *key, struct blk_ksm_keyslot **slot_ptr); diff --git a/include/linux/mfd/abx500/ab8500.h b/include/linux/mfd/abx500/ab8500.h index 524a7e4702c2..302a330c5c84 100644 --- a/include/linux/mfd/abx500/ab8500.h +++ b/include/linux/mfd/abx500/ab8500.h @@ -368,7 +368,6 @@ struct ab8500 { int it_latchhier_num; }; -struct ab8500_regulator_platform_data; struct ab8500_codec_platform_data; struct ab8500_sysctrl_platform_data; @@ -376,11 +375,9 @@ struct ab8500_sysctrl_platform_data; * struct ab8500_platform_data - AB8500 platform data * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used * @init: board-specific initialization after detection of ab8500 - * @regulator: machine-specific constraints for regulators */ struct ab8500_platform_data { void (*init) (struct ab8500 *); - struct ab8500_regulator_platform_data *regulator; struct ab8500_codec_platform_data *codec; struct ab8500_sysctrl_platform_data *sysctrl; }; diff --git a/include/linux/mfd/bd9571mwv.h b/include/linux/mfd/bd9571mwv.h index eb05569f752b..8efd99d07c9e 100644 --- a/include/linux/mfd/bd9571mwv.h +++ b/include/linux/mfd/bd9571mwv.h @@ -1,16 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* - * ROHM BD9571MWV-M driver + * ROHM BD9571MWV-M and BD9574MWF-M driver * * Copyright (C) 2017 Marek Vasut - * - * 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 "as is" WITHOUT ANY WARRANTY of any - * kind, whether expressed or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License version 2 for more details. + * Copyright (C) 2020 Renesas Electronics Corporation * * Based on the TPS65086 driver */ @@ -21,11 +14,12 @@ #include #include -/* List of registers for BD9571MWV */ +/* List of registers for BD9571MWV and BD9574MWF */ #define BD9571MWV_VENDOR_CODE 0x00 #define BD9571MWV_VENDOR_CODE_VAL 0xdb #define BD9571MWV_PRODUCT_CODE 0x01 -#define BD9571MWV_PRODUCT_CODE_VAL 0x60 +#define BD9571MWV_PRODUCT_CODE_BD9571MWV 0x60 +#define BD9571MWV_PRODUCT_CODE_BD9574MWF 0x74 #define BD9571MWV_PRODUCT_REVISION 0x02 #define BD9571MWV_I2C_FUSA_MODE 0x10 @@ -55,6 +49,7 @@ #define BD9571MWV_VD33_VID 0x44 #define BD9571MWV_DVFS_VINIT 0x50 +#define BD9574MWF_VD09_VINIT 0x51 #define BD9571MWV_DVFS_SETVMAX 0x52 #define BD9571MWV_DVFS_BOOSTVID 0x53 #define BD9571MWV_DVFS_SETVID 0x54 @@ -68,6 +63,7 @@ #define BD9571MWV_GPIO_INT_SET 0x64 #define BD9571MWV_GPIO_INT 0x65 #define BD9571MWV_GPIO_INTMASK 0x66 +#define BD9574MWF_GPIO_MUX 0x67 #define BD9571MWV_REG_KEEP(n) (0x70 + (n)) @@ -77,6 +73,8 @@ #define BD9571MWV_PROT_ERROR_STATUS2 0x83 #define BD9571MWV_PROT_ERROR_STATUS3 0x84 #define BD9571MWV_PROT_ERROR_STATUS4 0x85 +#define BD9574MWF_PROT_ERROR_STATUS5 0x86 +#define BD9574MWF_SYSTEM_ERROR_STATUS 0x87 #define BD9571MWV_INT_INTREQ 0x90 #define BD9571MWV_INT_INTREQ_MD1_INT BIT(0) @@ -89,6 +87,12 @@ #define BD9571MWV_INT_INTREQ_BKUP_TRG_INT BIT(7) #define BD9571MWV_INT_INTMASK 0x91 +#define BD9574MWF_SSCG_CNT 0xA0 +#define BD9574MWF_POFFB_MRB 0xA1 +#define BD9574MWF_SMRB_WR_PROT 0xA2 +#define BD9574MWF_SMRB_ASSERT 0xA3 +#define BD9574MWF_SMRB_STATUS 0xA4 + #define BD9571MWV_ACCESS_KEY 0xff /* Define the BD9571MWV IRQ numbers */ @@ -98,23 +102,8 @@ enum bd9571mwv_irqs { BD9571MWV_IRQ_MD2_E2, BD9571MWV_IRQ_PROT_ERR, BD9571MWV_IRQ_GP, - BD9571MWV_IRQ_128H_OF, + BD9571MWV_IRQ_128H_OF, /* BKUP_HOLD on BD9574MWF */ BD9571MWV_IRQ_WDT_OF, BD9571MWV_IRQ_BKUP_TRG, }; - -/** - * struct bd9571mwv - state holder for the bd9571mwv driver - * - * Device data may be used to access the BD9571MWV chip - */ -struct bd9571mwv { - struct device *dev; - struct regmap *regmap; - - /* IRQ Data */ - int irq; - struct regmap_irq_chip_data *irq_data; -}; - #endif /* __LINUX_MFD_BD9571MWV_H */ diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h index 4b35baa14d30..2009c4b936d9 100644 --- a/include/linux/mfd/core.h +++ b/include/linux/mfd/core.h @@ -28,13 +28,13 @@ .id = (_id), \ } -#define OF_MFD_CELL_REG(_name, _res, _pdata, _pdsize, _id, _compat, _of_reg) \ +#define MFD_CELL_OF_REG(_name, _res, _pdata, _pdsize, _id, _compat, _of_reg) \ MFD_CELL_ALL(_name, _res, _pdata, _pdsize, _id, _compat, _of_reg, true, NULL) -#define OF_MFD_CELL(_name, _res, _pdata, _pdsize, _id, _compat) \ +#define MFD_CELL_OF(_name, _res, _pdata, _pdsize, _id, _compat) \ MFD_CELL_ALL(_name, _res, _pdata, _pdsize, _id, _compat, 0, false, NULL) -#define ACPI_MFD_CELL(_name, _res, _pdata, _pdsize, _id, _match) \ +#define MFD_CELL_ACPI(_name, _res, _pdata, _pdsize, _id, _match) \ MFD_CELL_ALL(_name, _res, _pdata, _pdsize, _id, NULL, 0, false, _match) #define MFD_CELL_BASIC(_name, _res, _pdata, _pdsize, _id) \ diff --git a/include/linux/mfd/intel-m10-bmc.h b/include/linux/mfd/intel-m10-bmc.h index c8ef2f1654a4..74d4e193966a 100644 --- a/include/linux/mfd/intel-m10-bmc.h +++ b/include/linux/mfd/intel-m10-bmc.h @@ -15,6 +15,15 @@ /* Register offset of system registers */ #define NIOS2_FW_VERSION 0x0 +#define M10BMC_MAC_LOW 0x10 +#define M10BMC_MAC_BYTE4 GENMASK(7, 0) +#define M10BMC_MAC_BYTE3 GENMASK(15, 8) +#define M10BMC_MAC_BYTE2 GENMASK(23, 16) +#define M10BMC_MAC_BYTE1 GENMASK(31, 24) +#define M10BMC_MAC_HIGH 0x14 +#define M10BMC_MAC_BYTE6 GENMASK(7, 0) +#define M10BMC_MAC_BYTE5 GENMASK(15, 8) +#define M10BMC_MAC_COUNT GENMASK(23, 16) #define M10BMC_TEST_REG 0x3c #define M10BMC_BUILD_VER 0x68 #define M10BMC_VER_MAJOR_MSK GENMASK(23, 16) diff --git a/include/linux/mfd/iqs62x.h b/include/linux/mfd/iqs62x.h index 043d3b6de9ec..5ced55eae11b 100644 --- a/include/linux/mfd/iqs62x.h +++ b/include/linux/mfd/iqs62x.h @@ -28,7 +28,7 @@ #define IQS620_GLBL_EVENT_MASK_PMU BIT(6) #define IQS62X_NUM_KEYS 16 -#define IQS62X_NUM_EVENTS (IQS62X_NUM_KEYS + 5) +#define IQS62X_NUM_EVENTS (IQS62X_NUM_KEYS + 6) #define IQS62X_EVENT_SIZE 10 @@ -78,6 +78,7 @@ enum iqs62x_event_flag { /* everything else */ IQS62X_EVENT_SYS_RESET, + IQS62X_EVENT_SYS_ATI, }; struct iqs62x_event_data { @@ -97,12 +98,10 @@ struct iqs62x_dev_desc { const char *dev_name; const struct mfd_cell *sub_devs; int num_sub_devs; - u8 prod_num; u8 sw_num; const u8 *cal_regs; int num_cal_regs; - u8 prox_mask; u8 sar_mask; u8 hall_mask; @@ -110,16 +109,12 @@ struct iqs62x_dev_desc { u8 temp_mask; u8 als_mask; u8 ir_mask; - u8 prox_settings; u8 als_flags; u8 hall_flags; u8 hyst_shift; - u8 interval; u8 interval_div; - - u8 clk_div; const char *fw_name; const enum iqs62x_event_reg (*event_regs)[IQS62X_EVENT_SIZE]; }; @@ -130,8 +125,10 @@ struct iqs62x_core { struct regmap *regmap; struct blocking_notifier_head nh; struct list_head fw_blk_head; + struct completion ati_done; struct completion fw_done; enum iqs62x_ui_sel ui_sel; + unsigned long event_cache; }; extern const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS]; diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index 4283b5b33e04..66f673c35303 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -12,6 +12,8 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD71847, ROHM_CHIP_TYPE_BD70528, ROHM_CHIP_TYPE_BD71828, + ROHM_CHIP_TYPE_BD9571, + ROHM_CHIP_TYPE_BD9574, ROHM_CHIP_TYPE_AMOUNT }; @@ -20,14 +22,12 @@ struct rohm_regmap_dev { struct regmap *regmap; }; -enum { - ROHM_DVS_LEVEL_UNKNOWN, - ROHM_DVS_LEVEL_RUN, - ROHM_DVS_LEVEL_IDLE, - ROHM_DVS_LEVEL_SUSPEND, - ROHM_DVS_LEVEL_LPSR, - ROHM_DVS_LEVEL_MAX = ROHM_DVS_LEVEL_LPSR, -}; +#define ROHM_DVS_LEVEL_RUN BIT(0) +#define ROHM_DVS_LEVEL_IDLE BIT(1) +#define ROHM_DVS_LEVEL_SUSPEND BIT(2) +#define ROHM_DVS_LEVEL_LPSR BIT(3) +#define ROHM_DVS_LEVEL_VALID_AMOUNT 4 +#define ROHM_DVS_LEVEL_UNKNOWN 0 /** * struct rohm_dvs_config - dynamic voltage scaling register descriptions diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 42df06c6b19c..f9ad35dd6012 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -311,7 +311,6 @@ struct mmc_card { struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; - unsigned int bouncesz; /* Bounce buffer size */ struct workqueue_struct *complete_wq; /* Private workqueue */ }; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 29aa50711626..ab19245e9945 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -162,6 +162,12 @@ struct mmc_request { bool cap_cmd_during_tfr; int tag; + +#ifdef CONFIG_MMC_CRYPTO + bool crypto_enabled; + int crypto_key_slot; + u32 data_unit_num; +#endif }; struct mmc_card; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 01bba36545c5..26a3c7bc29ae 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -15,6 +15,7 @@ #include #include #include +#include struct mmc_ios { unsigned int clock; /* clock rate */ @@ -79,6 +80,17 @@ struct mmc_ios { bool enhanced_strobe; /* hs400es selection */ }; +struct mmc_clk_phase { + bool valid; + u16 in_deg; + u16 out_deg; +}; + +#define MMC_NUM_CLK_PHASES (MMC_TIMING_MMC_HS400 + 1) +struct mmc_clk_phase_map { + struct mmc_clk_phase phase[MMC_NUM_CLK_PHASES]; +}; + struct mmc_host; struct mmc_host_ops { @@ -384,6 +396,11 @@ struct mmc_host { #define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */ #define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */ #define MMC_CAP2_MERGE_CAPABLE (1 << 26) /* Host can merge a segment over the segment size */ +#ifdef CONFIG_MMC_CRYPTO +#define MMC_CAP2_CRYPTO (1 << 27) /* Host supports inline encryption */ +#else +#define MMC_CAP2_CRYPTO 0 +#endif int fixed_drv_type; /* fixed driver type for non-removable media */ @@ -412,7 +429,6 @@ struct mmc_host { unsigned int doing_retune:1; /* re-tuning in progress */ unsigned int retune_now:1; /* do re-tuning at next req */ unsigned int retune_paused:1; /* re-tuning is temporarily disabled */ - unsigned int use_blk_mq:1; /* use blk-mq */ unsigned int retune_crc_disable:1; /* don't trigger retune upon crc */ unsigned int can_dma_map_merge:1; /* merging can be used */ @@ -478,6 +494,11 @@ struct mmc_host { bool cqe_enabled; bool cqe_on; + /* Inline encryption support */ +#ifdef CONFIG_MMC_CRYPTO + struct blk_keyslot_manager ksm; +#endif + /* Host Software Queue support */ bool hsq_enabled; @@ -490,6 +511,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); void mmc_free_host(struct mmc_host *); +void mmc_of_parse_clk_phase(struct mmc_host *host, + struct mmc_clk_phase_map *map); int mmc_of_parse(struct mmc_host *host); int mmc_of_parse_voltage(struct device_node *np, u32 *mask); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index c425290b21e2..935060955152 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -846,4 +846,22 @@ struct auxiliary_device_id { kernel_ulong_t driver_data; }; +/* Surface System Aggregator Module */ + +#define SSAM_MATCH_TARGET 0x1 +#define SSAM_MATCH_INSTANCE 0x2 +#define SSAM_MATCH_FUNCTION 0x4 + +struct ssam_device_id { + __u8 match_flags; + + __u8 domain; + __u8 category; + __u8 target; + __u8 instance; + __u8 function; + + kernel_ulong_t driver_data; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index ca6f4fcad51f..5ff8597ceabd 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -4742,6 +4742,7 @@ enum ec_reboot_cmd { EC_REBOOT_DISABLE_JUMP = 5, /* Disable jump until next reboot */ EC_REBOOT_HIBERNATE = 6, /* Hibernate EC */ EC_REBOOT_HIBERNATE_CLEAR_AP_OFF = 7, /* and clears AP_OFF flag */ + EC_REBOOT_COLD_AP_OFF = 8, /* Cold-reboot and don't boot AP */ }; /* Flags for ec_params_reboot_ec.reboot_flags */ diff --git a/include/linux/platform_data/efm32-spi.h b/include/linux/platform_data/efm32-spi.h deleted file mode 100644 index a2c56fcd0534..000000000000 --- a/include/linux/platform_data/efm32-spi.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __LINUX_PLATFORM_DATA_EFM32_SPI_H__ -#define __LINUX_PLATFORM_DATA_EFM32_SPI_H__ - -#include - -/** - * struct efm32_spi_pdata - * @location: pinmux location for the I/O pins (to be written to the ROUTE - * register) - */ -struct efm32_spi_pdata { - u8 location; -}; -#endif /* ifndef __LINUX_PLATFORM_DATA_EFM32_SPI_H__ */ diff --git a/include/linux/platform_data/mlxcpld.h b/include/linux/platform_data/mlxcpld.h new file mode 100644 index 000000000000..d7610b528856 --- /dev/null +++ b/include/linux/platform_data/mlxcpld.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ +/* + * Mellanox I2C multiplexer support in CPLD + * + * Copyright (C) 2016-2020 Mellanox Technologies + */ + +#ifndef _LINUX_I2C_MLXCPLD_H +#define _LINUX_I2C_MLXCPLD_H + +/* Platform data for the CPLD I2C multiplexers */ + +/* mlxcpld_mux_plat_data - per mux data, used with i2c_register_board_info + * @chan_ids - channels array + * @num_adaps - number of adapters + * @sel_reg_addr - mux select register offset in CPLD space + * @reg_size: register size in bytes + * @handle: handle to be passed by callback + * @completion_notify: callback to notify when all the adapters are created + */ +struct mlxcpld_mux_plat_data { + int *chan_ids; + int num_adaps; + int sel_reg_addr; + u8 reg_size; + void *handle; + int (*completion_notify)(void *handle, struct i2c_adapter *parent, + struct i2c_adapter *adapters[]); +}; + +#endif /* _LINUX_I2C_MLXCPLD_H */ diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h index f0b8947e6b07..91051e9907f3 100644 --- a/include/linux/platform_data/mmc-omap.h +++ b/include/linux/platform_data/mmc-omap.h @@ -108,8 +108,7 @@ struct omap_mmc_platform_data { const char *name; u32 ocr_mask; - /* Card detection IRQs */ - int card_detect_irq; + /* Card detection */ int (*card_detect)(struct device *dev, int slot); unsigned int ban_openended:1; diff --git a/include/linux/platform_data/x86/mlxcpld.h b/include/linux/platform_data/x86/mlxcpld.h deleted file mode 100644 index b08dcb183fca..000000000000 --- a/include/linux/platform_data/x86/mlxcpld.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * mlxcpld.h - Mellanox I2C multiplexer support in CPLD - * - * Copyright (c) 2016 Mellanox Technologies. All rights reserved. - * Copyright (c) 2016 Michael Shych - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * Alternatively, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2 as published by the Free - * Software Foundation. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _LINUX_I2C_MLXCPLD_H -#define _LINUX_I2C_MLXCPLD_H - -/* Platform data for the CPLD I2C multiplexers */ - -/* mlxcpld_mux_plat_data - per mux data, used with i2c_register_board_info - * @adap_ids - adapter array - * @num_adaps - number of adapters - * @sel_reg_addr - mux select register offset in CPLD space - */ -struct mlxcpld_mux_plat_data { - int *adap_ids; - int num_adaps; - int sel_reg_addr; -}; - -#endif /* _LINUX_I2C_MLXCPLD_H */ diff --git a/include/linux/pmbus.h b/include/linux/pmbus.h index 1ea5bae708a1..12cbbf305969 100644 --- a/include/linux/pmbus.h +++ b/include/linux/pmbus.h @@ -34,6 +34,15 @@ */ #define PMBUS_WRITE_PROTECTED BIT(1) +/* + * PMBUS_NO_CAPABILITY + * + * Some PMBus chips don't respond with valid data when reading the CAPABILITY + * register. For such chips, this flag should be set so that the PMBus core + * driver doesn't use CAPABILITY to determine it's behavior. + */ +#define PMBUS_NO_CAPABILITY BIT(2) + struct pmbus_platform_data { u32 flags; /* Device specific flags */ diff --git a/include/linux/regulator/ab8500.h b/include/linux/regulator/ab8500.h deleted file mode 100644 index 3ab1ddf151a2..000000000000 --- a/include/linux/regulator/ab8500.h +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson SA 2010 - * - * Authors: Sundar Iyer for ST-Ericsson - * Bengt Jonsson for ST-Ericsson - * Daniel Willerud for ST-Ericsson - */ - -#ifndef __LINUX_MFD_AB8500_REGULATOR_H -#define __LINUX_MFD_AB8500_REGULATOR_H - -#include - -/* AB8500 regulators */ -enum ab8500_regulator_id { - AB8500_LDO_AUX1, - AB8500_LDO_AUX2, - AB8500_LDO_AUX3, - AB8500_LDO_INTCORE, - AB8500_LDO_TVOUT, - AB8500_LDO_AUDIO, - AB8500_LDO_ANAMIC1, - AB8500_LDO_ANAMIC2, - AB8500_LDO_DMIC, - AB8500_LDO_ANA, - AB8500_NUM_REGULATORS, -}; - -/* AB8505 regulators */ -enum ab8505_regulator_id { - AB8505_LDO_AUX1, - AB8505_LDO_AUX2, - AB8505_LDO_AUX3, - AB8505_LDO_AUX4, - AB8505_LDO_AUX5, - AB8505_LDO_AUX6, - AB8505_LDO_INTCORE, - AB8505_LDO_ADC, - AB8505_LDO_AUDIO, - AB8505_LDO_ANAMIC1, - AB8505_LDO_ANAMIC2, - AB8505_LDO_AUX8, - AB8505_LDO_ANA, - AB8505_NUM_REGULATORS, -}; - -/* AB8500 and AB8505 register initialization */ -struct ab8500_regulator_reg_init { - int id; - u8 mask; - u8 value; -}; - -#define INIT_REGULATOR_REGISTER(_id, _mask, _value) \ - { \ - .id = _id, \ - .mask = _mask, \ - .value = _value, \ - } - -/* AB8500 registers */ -enum ab8500_regulator_reg { - AB8500_REGUREQUESTCTRL2, - AB8500_REGUREQUESTCTRL3, - AB8500_REGUREQUESTCTRL4, - AB8500_REGUSYSCLKREQ1HPVALID1, - AB8500_REGUSYSCLKREQ1HPVALID2, - AB8500_REGUHWHPREQ1VALID1, - AB8500_REGUHWHPREQ1VALID2, - AB8500_REGUHWHPREQ2VALID1, - AB8500_REGUHWHPREQ2VALID2, - AB8500_REGUSWHPREQVALID1, - AB8500_REGUSWHPREQVALID2, - AB8500_REGUSYSCLKREQVALID1, - AB8500_REGUSYSCLKREQVALID2, - AB8500_REGUMISC1, - AB8500_VAUDIOSUPPLY, - AB8500_REGUCTRL1VAMIC, - AB8500_VPLLVANAREGU, - AB8500_VREFDDR, - AB8500_EXTSUPPLYREGU, - AB8500_VAUX12REGU, - AB8500_VRF1VAUX3REGU, - AB8500_VAUX1SEL, - AB8500_VAUX2SEL, - AB8500_VRF1VAUX3SEL, - AB8500_REGUCTRL2SPARE, - AB8500_REGUCTRLDISCH, - AB8500_REGUCTRLDISCH2, - AB8500_NUM_REGULATOR_REGISTERS, -}; - -/* AB8505 registers */ -enum ab8505_regulator_reg { - AB8505_REGUREQUESTCTRL1, - AB8505_REGUREQUESTCTRL2, - AB8505_REGUREQUESTCTRL3, - AB8505_REGUREQUESTCTRL4, - AB8505_REGUSYSCLKREQ1HPVALID1, - AB8505_REGUSYSCLKREQ1HPVALID2, - AB8505_REGUHWHPREQ1VALID1, - AB8505_REGUHWHPREQ1VALID2, - AB8505_REGUHWHPREQ2VALID1, - AB8505_REGUHWHPREQ2VALID2, - AB8505_REGUSWHPREQVALID1, - AB8505_REGUSWHPREQVALID2, - AB8505_REGUSYSCLKREQVALID1, - AB8505_REGUSYSCLKREQVALID2, - AB8505_REGUVAUX4REQVALID, - AB8505_REGUMISC1, - AB8505_VAUDIOSUPPLY, - AB8505_REGUCTRL1VAMIC, - AB8505_VSMPSAREGU, - AB8505_VSMPSBREGU, - AB8505_VSAFEREGU, /* NOTE! PRCMU register */ - AB8505_VPLLVANAREGU, - AB8505_EXTSUPPLYREGU, - AB8505_VAUX12REGU, - AB8505_VRF1VAUX3REGU, - AB8505_VSMPSASEL1, - AB8505_VSMPSASEL2, - AB8505_VSMPSASEL3, - AB8505_VSMPSBSEL1, - AB8505_VSMPSBSEL2, - AB8505_VSMPSBSEL3, - AB8505_VSAFESEL1, /* NOTE! PRCMU register */ - AB8505_VSAFESEL2, /* NOTE! PRCMU register */ - AB8505_VSAFESEL3, /* NOTE! PRCMU register */ - AB8505_VAUX1SEL, - AB8505_VAUX2SEL, - AB8505_VRF1VAUX3SEL, - AB8505_VAUX4REQCTRL, - AB8505_VAUX4REGU, - AB8505_VAUX4SEL, - AB8505_REGUCTRLDISCH, - AB8505_REGUCTRLDISCH2, - AB8505_REGUCTRLDISCH3, - AB8505_CTRLVAUX5, - AB8505_CTRLVAUX6, - AB8505_NUM_REGULATOR_REGISTERS, -}; - -/* AB8500 external regulators */ -struct ab8500_ext_regulator_cfg { - bool hwreq; /* requires hw mode or high power mode */ -}; - -enum ab8500_ext_regulator_id { - AB8500_EXT_SUPPLY1, - AB8500_EXT_SUPPLY2, - AB8500_EXT_SUPPLY3, - AB8500_NUM_EXT_REGULATORS, -}; - -/* AB8500 regulator platform data */ -struct ab8500_regulator_platform_data { - int num_reg_init; - struct ab8500_regulator_reg_init *reg_init; - int num_regulator; - struct regulator_init_data *regulator; - int num_ext_regulator; - struct regulator_init_data *ext_regulator; -}; - -#endif diff --git a/include/linux/regulator/mt6315-regulator.h b/include/linux/regulator/mt6315-regulator.h new file mode 100644 index 000000000000..3b80d3f3910c --- /dev/null +++ b/include/linux/regulator/mt6315-regulator.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#ifndef __LINUX_REGULATOR_MT6315_H +#define __LINUX_REGULATOR_MT6315_H + +#define MT6315_RP 3 +#define MT6315_PP 6 +#define MT6315_SP 7 + +enum { + MT6315_VBUCK1 = 0, + MT6315_VBUCK2, + MT6315_VBUCK3, + MT6315_VBUCK4, + MT6315_VBUCK_MAX, +}; + +/* Register */ +#define MT6315_TOP2_ELR7 0x139 +#define MT6315_TOP_TMA_KEY 0x39F +#define MT6315_TOP_TMA_KEY_H 0x3A0 +#define MT6315_BUCK_TOP_CON0 0x1440 +#define MT6315_BUCK_TOP_CON1 0x1443 +#define MT6315_BUCK_TOP_ELR0 0x1449 +#define MT6315_BUCK_TOP_ELR2 0x144B +#define MT6315_BUCK_TOP_ELR4 0x144D +#define MT6315_BUCK_TOP_ELR6 0x144F +#define MT6315_VBUCK1_DBG0 0x1499 +#define MT6315_VBUCK1_DBG4 0x149D +#define MT6315_VBUCK2_DBG0 0x1519 +#define MT6315_VBUCK2_DBG4 0x151D +#define MT6315_VBUCK3_DBG0 0x1599 +#define MT6315_VBUCK3_DBG4 0x159D +#define MT6315_VBUCK4_DBG0 0x1619 +#define MT6315_VBUCK4_DBG4 0x161D +#define MT6315_BUCK_TOP_4PHASE_ANA_CON42 0x16B1 + +#define PROTECTION_KEY_H 0x9C +#define PROTECTION_KEY 0xEA + +#endif /* __LINUX_REGULATOR_MT6315_H */ diff --git a/include/linux/regulator/pca9450.h b/include/linux/regulator/pca9450.h index 1bbd3014f906..ccdb5320a240 100644 --- a/include/linux/regulator/pca9450.h +++ b/include/linux/regulator/pca9450.h @@ -216,4 +216,11 @@ enum { #define IRQ_THERM_105 0x02 #define IRQ_THERM_125 0x01 +/* PCA9450_REG_RESET_CTRL bits */ +#define WDOG_B_CFG_MASK 0xC0 +#define WDOG_B_CFG_NONE 0x00 +#define WDOG_B_CFG_WARM 0x40 +#define WDOG_B_CFG_COLD_LDO12 0x80 +#define WDOG_B_CFG_COLD 0xC0 + #endif /* __LINUX_REG_PCA9450_H__ */ diff --git a/include/linux/sony-laptop.h b/include/linux/sony-laptop.h index 374d0fdb0743..1e3c92feea6e 100644 --- a/include/linux/sony-laptop.h +++ b/include/linux/sony-laptop.h @@ -31,7 +31,7 @@ #if IS_ENABLED(CONFIG_SONY_LAPTOP) int sony_pic_camera_command(int command, u8 value); #else -static inline int sony_pic_camera_command(int command, u8 value) { return 0; }; +static inline int sony_pic_camera_command(int command, u8 value) { return 0; } #endif #endif /* __KERNEL__ */ diff --git a/include/linux/spi/lms283gf05.h b/include/linux/spi/lms283gf05.h deleted file mode 100644 index f237b2d062e9..000000000000 --- a/include/linux/spi/lms283gf05.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * lms283gf05.h - Platform glue for Samsung LMS283GF05 LCD - * - * Copyright (C) 2009 Marek Vasut -*/ - -#ifndef _INCLUDE_LINUX_SPI_LMS283GF05_H_ -#define _INCLUDE_LINUX_SPI_LMS283GF05_H_ - -struct lms283gf05_pdata { - unsigned long reset_gpio; - bool reset_inverted; -}; - -#endif /* _INCLUDE_LINUX_SPI_LMS283GF05_H_ */ diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 159463cc659c..2b65c9edc34e 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -311,6 +311,9 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, bool spi_mem_default_supports_op(struct spi_mem *mem, const struct spi_mem_op *op); +bool spi_mem_dtr_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op); + #else static inline int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, @@ -334,6 +337,12 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, return false; } +static inline +bool spi_mem_dtr_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + return false; +} #endif /* CONFIG_SPI_MEM */ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op); diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index aa09fdc8042d..592897fa4f03 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -6,6 +6,7 @@ #ifndef __LINUX_SPI_H #define __LINUX_SPI_H +#include #include #include #include @@ -15,6 +16,8 @@ #include #include +#include + struct dma_chan; struct property_entry; struct spi_controller; @@ -164,28 +167,19 @@ struct spi_device { u8 chip_select; u8 bits_per_word; bool rt; +#define SPI_NO_TX BIT(31) /* no transmit wire */ +#define SPI_NO_RX BIT(30) /* no receive wire */ + /* + * All bits defined above should be covered by SPI_MODE_KERNEL_MASK. + * The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart, + * which is defined in 'include/uapi/linux/spi/spi.h'. + * The bits defined here are from bit 31 downwards, while in + * SPI_MODE_USER_MASK are from 0 upwards. + * These bits must not overlap. A static assert check should make sure of that. + * If adding extra bits, make sure to decrease the bit index below as well. + */ +#define SPI_MODE_KERNEL_MASK (~(BIT(30) - 1)) u32 mode; -#define SPI_CPHA 0x01 /* clock phase */ -#define SPI_CPOL 0x02 /* clock polarity */ -#define SPI_MODE_0 (0|0) /* (original MicroWire) */ -#define SPI_MODE_1 (0|SPI_CPHA) -#define SPI_MODE_2 (SPI_CPOL|0) -#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) -#define SPI_MODE_X_MASK (SPI_CPOL|SPI_CPHA) -#define SPI_CS_HIGH 0x04 /* chipselect active high? */ -#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */ -#define SPI_3WIRE 0x10 /* SI/SO signals shared */ -#define SPI_LOOP 0x20 /* loopback mode */ -#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ -#define SPI_READY 0x80 /* slave pulls low to pause */ -#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ -#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ -#define SPI_RX_DUAL 0x400 /* receive with 2 wires */ -#define SPI_RX_QUAD 0x800 /* receive with 4 wires */ -#define SPI_CS_WORD 0x1000 /* toggle cs after each word */ -#define SPI_TX_OCTAL 0x2000 /* transmit with 8 wires */ -#define SPI_RX_OCTAL 0x4000 /* receive with 8 wires */ -#define SPI_3WIRE_HIZ 0x8000 /* high impedance turnaround */ int irq; void *controller_state; void *controller_data; @@ -208,6 +202,10 @@ struct spi_device { */ }; +/* Make sure that SPI_MODE_KERNEL_MASK & SPI_MODE_USER_MASK don't overlap */ +static_assert((SPI_MODE_KERNEL_MASK & SPI_MODE_USER_MASK) == 0, + "SPI_MODE_USER_MASK & SPI_MODE_KERNEL_MASK must not overlap"); + static inline struct spi_device *to_spi_device(struct device *dev) { return dev ? container_of(dev, struct spi_device, dev) : NULL; @@ -624,7 +622,7 @@ struct spi_controller { /* * These hooks are for drivers that use a generic implementation - * of transfer_one_message() provied by the core. + * of transfer_one_message() provided by the core. */ void (*set_cs)(struct spi_device *spi, bool enable); int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, @@ -827,6 +825,7 @@ extern void spi_res_release(struct spi_controller *ctlr, * transfer. If 0 the default (from @spi_device) is used. * @bits_per_word: select a bits_per_word other than the device default * for this transfer. If 0 the default (from @spi_device) is used. + * @dummy_data: indicates transfer is dummy bytes transfer. * @cs_change: affects chipselect after this transfer completes * @cs_change_delay: delay between cs deassert and assert when * @cs_change is set and @spi_transfer is not the last in @spi_message @@ -939,6 +938,7 @@ struct spi_transfer { struct sg_table tx_sg; struct sg_table rx_sg; + unsigned dummy_data:1; unsigned cs_change:1; unsigned tx_nbits:3; unsigned rx_nbits:3; diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h new file mode 100644 index 000000000000..8e3e86c7d78c --- /dev/null +++ b/include/linux/surface_acpi_notify.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Interface for Surface ACPI Notify (SAN) driver. + * + * Provides access to discrete GPU notifications sent from ACPI via the SAN + * driver, which are not handled by this driver directly. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H +#define _LINUX_SURFACE_ACPI_NOTIFY_H + +#include +#include + +/** + * struct san_dgpu_event - Discrete GPU ACPI event. + * @category: Category of the event. + * @target: Target ID of the event source. + * @command: Command ID of the event. + * @instance: Instance ID of the event source. + * @length: Length of the event's payload data (in bytes). + * @payload: Pointer to the event's payload data. + */ +struct san_dgpu_event { + u8 category; + u8 target; + u8 command; + u8 instance; + u16 length; + u8 *payload; +}; + +int san_client_link(struct device *client); +int san_dgpu_notifier_register(struct notifier_block *nb); +int san_dgpu_notifier_unregister(struct notifier_block *nb); + +#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h new file mode 100644 index 000000000000..f4b1ba887384 --- /dev/null +++ b/include/linux/surface_aggregator/controller.h @@ -0,0 +1,824 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module (SSAM) controller interface. + * + * Main communication interface for the SSAM EC. Provides a controller + * managing access and communication to and from the SSAM EC, as well as main + * communication structures and definitions. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H +#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H + +#include +#include +#include + +#include + + +/* -- Main data types and definitions --------------------------------------- */ + +/** + * enum ssam_event_flags - Flags for enabling/disabling SAM events + * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. + */ +enum ssam_event_flags { + SSAM_EVENT_SEQUENCED = BIT(0), +}; + +/** + * struct ssam_event - SAM event sent from the EC to the host. + * @target_category: Target category of the event source. See &enum ssam_ssh_tc. + * @target_id: Target ID of the event source. + * @command_id: Command ID of the event. + * @instance_id: Instance ID of the event source. + * @length: Length of the event payload in bytes. + * @data: Event payload data. + */ +struct ssam_event { + u8 target_category; + u8 target_id; + u8 command_id; + u8 instance_id; + u16 length; + u8 data[]; +}; + +/** + * enum ssam_request_flags - Flags for SAM requests. + * + * @SSAM_REQUEST_HAS_RESPONSE: + * Specifies that the request expects a response. If not set, the request + * will be directly completed after its underlying packet has been + * transmitted. If set, the request transport system waits for a response + * of the request. + * + * @SSAM_REQUEST_UNSEQUENCED: + * Specifies that the request should be transmitted via an unsequenced + * packet. If set, the request must not have a response, meaning that this + * flag and the %SSAM_REQUEST_HAS_RESPONSE flag are mutually exclusive. + */ +enum ssam_request_flags { + SSAM_REQUEST_HAS_RESPONSE = BIT(0), + SSAM_REQUEST_UNSEQUENCED = BIT(1), +}; + +/** + * struct ssam_request - SAM request description. + * @target_category: Category of the request's target. See &enum ssam_ssh_tc. + * @target_id: ID of the request's target. + * @command_id: Command ID of the request. + * @instance_id: Instance ID of the request's target. + * @flags: Flags for the request. See &enum ssam_request_flags. + * @length: Length of the request payload in bytes. + * @payload: Request payload data. + * + * This struct fully describes a SAM request with payload. It is intended to + * help set up the actual transport struct, e.g. &struct ssam_request_sync, + * and specifically its raw message data via ssam_request_write_data(). + */ +struct ssam_request { + u8 target_category; + u8 target_id; + u8 command_id; + u8 instance_id; + u16 flags; + u16 length; + const u8 *payload; +}; + +/** + * struct ssam_response - Response buffer for SAM request. + * @capacity: Capacity of the buffer, in bytes. + * @length: Length of the actual data stored in the memory pointed to by + * @pointer, in bytes. Set by the transport system. + * @pointer: Pointer to the buffer's memory, storing the response payload data. + */ +struct ssam_response { + size_t capacity; + size_t length; + u8 *pointer; +}; + +struct ssam_controller; + +struct ssam_controller *ssam_get_controller(void); +struct ssam_controller *ssam_client_bind(struct device *client); +int ssam_client_link(struct ssam_controller *ctrl, struct device *client); + +struct device *ssam_controller_device(struct ssam_controller *c); + +struct ssam_controller *ssam_controller_get(struct ssam_controller *c); +void ssam_controller_put(struct ssam_controller *c); + +void ssam_controller_statelock(struct ssam_controller *c); +void ssam_controller_stateunlock(struct ssam_controller *c); + +ssize_t ssam_request_write_data(struct ssam_span *buf, + struct ssam_controller *ctrl, + const struct ssam_request *spec); + + +/* -- Synchronous request interface. ---------------------------------------- */ + +/** + * struct ssam_request_sync - Synchronous SAM request struct. + * @base: Underlying SSH request. + * @comp: Completion used to signal full completion of the request. After the + * request has been submitted, this struct may only be modified or + * deallocated after the completion has been signaled. + * request has been submitted, + * @resp: Buffer to store the response. + * @status: Status of the request, set after the base request has been + * completed or has failed. + */ +struct ssam_request_sync { + struct ssh_request base; + struct completion comp; + struct ssam_response *resp; + int status; +}; + +int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, + struct ssam_request_sync **rqst, + struct ssam_span *buffer); + +void ssam_request_sync_free(struct ssam_request_sync *rqst); + +int ssam_request_sync_init(struct ssam_request_sync *rqst, + enum ssam_request_flags flags); + +/** + * ssam_request_sync_set_data - Set message data of a synchronous request. + * @rqst: The request. + * @ptr: Pointer to the request message data. + * @len: Length of the request message data. + * + * Set the request message data of a synchronous request. The provided buffer + * needs to live until the request has been completed. + */ +static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, + u8 *ptr, size_t len) +{ + ssh_request_set_data(&rqst->base, ptr, len); +} + +/** + * ssam_request_sync_set_resp - Set response buffer of a synchronous request. + * @rqst: The request. + * @resp: The response buffer. + * + * Sets the response buffer of a synchronous request. This buffer will store + * the response of the request after it has been completed. May be %NULL if no + * response is expected. + */ +static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, + struct ssam_response *resp) +{ + rqst->resp = resp; +} + +int ssam_request_sync_submit(struct ssam_controller *ctrl, + struct ssam_request_sync *rqst); + +/** + * ssam_request_sync_wait - Wait for completion of a synchronous request. + * @rqst: The request to wait for. + * + * Wait for completion and release of a synchronous request. After this + * function terminates, the request is guaranteed to have left the transport + * system. After successful submission of a request, this function must be + * called before accessing the response of the request, freeing the request, + * or freeing any of the buffers associated with the request. + * + * This function must not be called if the request has not been submitted yet + * and may lead to a deadlock/infinite wait if a subsequent request submission + * fails in that case, due to the completion never triggering. + * + * Return: Returns the status of the given request, which is set on completion + * of the packet. This value is zero on success and negative on failure. + */ +static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) +{ + wait_for_completion(&rqst->comp); + return rqst->status; +} + +int ssam_request_sync(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp); + +int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf); + +/** + * ssam_request_sync_onstack - Execute a synchronous request on the stack. + * @ctrl: The controller via which the request is submitted. + * @rqst: The request specification. + * @rsp: The response buffer. + * @payload_len: The (maximum) request payload length. + * + * Allocates a synchronous request with specified payload length on the stack, + * fully initializes it via the provided request specification, submits it, + * and finally waits for its completion before returning its status. This + * helper macro essentially allocates the request message buffer on the stack + * and then calls ssam_request_sync_with_buffer(). + * + * Note: The @payload_len parameter specifies the maximum payload length, used + * for buffer allocation. The actual payload length may be smaller. + * + * Return: Returns the status of the request or any failure during setup, i.e. + * zero on success and a negative value on failure. + */ +#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ + ({ \ + u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ + struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ + \ + ssam_request_sync_with_buffer(ctrl, rqst, rsp, &__buf); \ + }) + +/** + * __ssam_retry - Retry request in case of I/O errors or timeouts. + * @request: The request function to execute. Must return an integer. + * @n: Number of tries. + * @args: Arguments for the request function. + * + * Executes the given request function, i.e. calls @request. In case the + * request returns %-EREMOTEIO (indicates I/O error) or %-ETIMEDOUT (request + * or underlying packet timed out), @request will be re-executed again, up to + * @n times in total. + * + * Return: Returns the return value of the last execution of @request. + */ +#define __ssam_retry(request, n, args...) \ + ({ \ + int __i, __s = 0; \ + \ + for (__i = (n); __i > 0; __i--) { \ + __s = request(args); \ + if (__s != -ETIMEDOUT && __s != -EREMOTEIO) \ + break; \ + } \ + __s; \ + }) + +/** + * ssam_retry - Retry request in case of I/O errors or timeouts up to three + * times in total. + * @request: The request function to execute. Must return an integer. + * @args: Arguments for the request function. + * + * Executes the given request function, i.e. calls @request. In case the + * request returns %-EREMOTEIO (indicates I/O error) or -%ETIMEDOUT (request + * or underlying packet timed out), @request will be re-executed again, up to + * three times in total. + * + * See __ssam_retry() for a more generic macro for this purpose. + * + * Return: Returns the return value of the last execution of @request. + */ +#define ssam_retry(request, args...) \ + __ssam_retry(request, 3, args) + +/** + * struct ssam_request_spec - Blue-print specification of SAM request. + * @target_category: Category of the request's target. See &enum ssam_ssh_tc. + * @target_id: ID of the request's target. + * @command_id: Command ID of the request. + * @instance_id: Instance ID of the request's target. + * @flags: Flags for the request. See &enum ssam_request_flags. + * + * Blue-print specification for a SAM request. This struct describes the + * unique static parameters of a request (i.e. type) without specifying any of + * its instance-specific data (e.g. payload). It is intended to be used as base + * for defining simple request functions via the + * ``SSAM_DEFINE_SYNC_REQUEST_x()`` family of macros. + */ +struct ssam_request_spec { + u8 target_category; + u8 target_id; + u8 command_id; + u8 instance_id; + u8 flags; +}; + +/** + * struct ssam_request_spec_md - Blue-print specification for multi-device SAM + * request. + * @target_category: Category of the request's target. See &enum ssam_ssh_tc. + * @command_id: Command ID of the request. + * @flags: Flags for the request. See &enum ssam_request_flags. + * + * Blue-print specification for a multi-device SAM request, i.e. a request + * that is applicable to multiple device instances, described by their + * individual target and instance IDs. This struct describes the unique static + * parameters of a request (i.e. type) without specifying any of its + * instance-specific data (e.g. payload) and without specifying any of its + * device specific IDs (i.e. target and instance ID). It is intended to be + * used as base for defining simple multi-device request functions via the + * ``SSAM_DEFINE_SYNC_REQUEST_MD_x()`` and ``SSAM_DEFINE_SYNC_REQUEST_CL_x()`` + * families of macros. + */ +struct ssam_request_spec_md { + u8 target_category; + u8 command_id; + u8 flags; +}; + +/** + * SSAM_DEFINE_SYNC_REQUEST_N() - Define synchronous SAM request function + * with neither argument nor return value. + * @name: Name of the generated function. + * @spec: Specification (&struct ssam_request_spec) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request having neither argument nor return value. The + * generated function takes care of setting up the request struct and buffer + * allocation, as well as execution of the request itself, returning once the + * request has been fully completed. The required transport buffer will be + * allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl)``, returning the status of the request, which is zero on success and + * negative on failure. The ``ctrl`` parameter is the controller via which the + * request is being sent. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ + int name(struct ssam_controller *ctrl) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = s.target_id; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = s.instance_id; \ + rqst.flags = s.flags; \ + rqst.length = 0; \ + rqst.payload = NULL; \ + \ + return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_W() - Define synchronous SAM request function with + * argument. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @spec: Specification (&struct ssam_request_spec) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking an argument of type @atype and having no + * return value. The generated function takes care of setting up the request + * struct, buffer allocation, as well as execution of the request itself, + * returning once the request has been fully completed. The required transport + * buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl, const atype *arg)``, returning the status of the request, which is + * zero on success and negative on failure. The ``ctrl`` parameter is the + * controller via which the request is sent. The request argument is specified + * via the ``arg`` pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ + int name(struct ssam_controller *ctrl, const atype *arg) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = s.target_id; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = s.instance_id; \ + rqst.flags = s.flags; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ + sizeof(atype)); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_R() - Define synchronous SAM request function with + * return value. + * @name: Name of the generated function. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking no argument but having a return value of + * type @rtype. The generated function takes care of setting up the request + * and response structs, buffer allocation, as well as execution of the + * request itself, returning once the request has been fully completed. The + * required transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl, rtype *ret)``, returning the status of the request, which is zero on + * success and negative on failure. The ``ctrl`` parameter is the controller + * via which the request is sent. The request's return value is written to the + * memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ + int name(struct ssam_controller *ctrl, rtype *ret) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = s.target_id; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = s.instance_id; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = 0; \ + rqst.payload = NULL; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM + * request function with neither argument nor return value. + * @name: Name of the generated function. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request having neither argument nor return value. Device + * specifying parameters are not hard-coded, but instead must be provided to + * the function. The generated function takes care of setting up the request + * struct, buffer allocation, as well as execution of the request itself, + * returning once the request has been fully completed. The required transport + * buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is + * zero on success and negative on failure. The ``ctrl`` parameter is the + * controller via which the request is sent, ``tid`` the target ID for the + * request, and ``iid`` the instance ID. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ + int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = tid; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = iid; \ + rqst.flags = s.flags; \ + rqst.length = 0; \ + rqst.payload = NULL; \ + \ + return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_MD_W() - Define synchronous multi-device SAM + * request function with argument. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking an argument of type @atype and having no + * return value. Device specifying parameters are not hard-coded, but instead + * must be provided to the function. The generated function takes care of + * setting up the request struct, buffer allocation, as well as execution of + * the request itself, returning once the request has been fully completed. + * The required transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the + * request, which is zero on success and negative on failure. The ``ctrl`` + * parameter is the controller via which the request is sent, ``tid`` the + * target ID for the request, and ``iid`` the instance ID. The request argument + * is specified via the ``arg`` pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ + int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = tid; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = iid; \ + rqst.flags = s.flags; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ + sizeof(atype)); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_MD_R() - Define synchronous multi-device SAM + * request function with return value. + * @name: Name of the generated function. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking no argument but having a return value of + * type @rtype. Device specifying parameters are not hard-coded, but instead + * must be provided to the function. The generated function takes care of + * setting up the request and response structs, buffer allocation, as well as + * execution of the request itself, returning once the request has been fully + * completed. The required transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_controller + * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, + * which is zero on success and negative on failure. The ``ctrl`` parameter is + * the controller via which the request is sent, ``tid`` the target ID for the + * request, and ``iid`` the instance ID. The request's return value is written + * to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ + int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = tid; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = iid; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = 0; \ + rqst.payload = NULL; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + + +/* -- Event notifier/callbacks. --------------------------------------------- */ + +#define SSAM_NOTIF_STATE_SHIFT 2 +#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) + +/** + * enum ssam_notif_flags - Flags used in return values from SSAM notifier + * callback functions. + * + * @SSAM_NOTIF_HANDLED: + * Indicates that the notification has been handled. This flag should be + * set by the handler if the handler can act/has acted upon the event + * provided to it. This flag should not be set if the handler is not a + * primary handler intended for the provided event. + * + * If this flag has not been set by any handler after the notifier chain + * has been traversed, a warning will be emitted, stating that the event + * has not been handled. + * + * @SSAM_NOTIF_STOP: + * Indicates that the notifier traversal should stop. If this flag is + * returned from a notifier callback, notifier chain traversal will + * immediately stop and any remaining notifiers will not be called. This + * flag is automatically set when ssam_notifier_from_errno() is called + * with a negative error value. + */ +enum ssam_notif_flags { + SSAM_NOTIF_HANDLED = BIT(0), + SSAM_NOTIF_STOP = BIT(1), +}; + +struct ssam_event_notifier; + +typedef u32 (*ssam_notifier_fn_t)(struct ssam_event_notifier *nf, + const struct ssam_event *event); + +/** + * struct ssam_notifier_block - Base notifier block for SSAM event + * notifications. + * @node: The node for the list of notifiers. + * @fn: The callback function of this notifier. This function takes the + * respective notifier block and event as input and should return + * a notifier value, which can either be obtained from the flags + * provided in &enum ssam_notif_flags, converted from a standard + * error value via ssam_notifier_from_errno(), or a combination of + * both (e.g. ``ssam_notifier_from_errno(e) | SSAM_NOTIF_HANDLED``). + * @priority: Priority value determining the order in which notifier callbacks + * will be called. A higher value means higher priority, i.e. the + * associated callback will be executed earlier than other (lower + * priority) callbacks. + */ +struct ssam_notifier_block { + struct list_head node; + ssam_notifier_fn_t fn; + int priority; +}; + +/** + * ssam_notifier_from_errno() - Convert standard error value to notifier + * return code. + * @err: The error code to convert, must be negative (in case of failure) or + * zero (in case of success). + * + * Return: Returns the notifier return value obtained by converting the + * specified @err value. In case @err is negative, the %SSAM_NOTIF_STOP flag + * will be set, causing notifier call chain traversal to abort. + */ +static inline u32 ssam_notifier_from_errno(int err) +{ + if (WARN_ON(err > 0) || err == 0) + return 0; + else + return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; +} + +/** + * ssam_notifier_to_errno() - Convert notifier return code to standard error + * value. + * @ret: The notifier return value to convert. + * + * Return: Returns the negative error value encoded in @ret or zero if @ret + * indicates success. + */ +static inline int ssam_notifier_to_errno(u32 ret) +{ + return -(ret >> SSAM_NOTIF_STATE_SHIFT); +} + + +/* -- Event/notification registry. ------------------------------------------ */ + +/** + * struct ssam_event_registry - Registry specification used for enabling events. + * @target_category: Target category for the event registry requests. + * @target_id: Target ID for the event registry requests. + * @cid_enable: Command ID for the event-enable request. + * @cid_disable: Command ID for the event-disable request. + * + * This struct describes a SAM event registry via the minimal collection of + * SAM IDs specifying the requests to use for enabling and disabling an event. + * The individual event to be enabled/disabled itself is specified via &struct + * ssam_event_id. + */ +struct ssam_event_registry { + u8 target_category; + u8 target_id; + u8 cid_enable; + u8 cid_disable; +}; + +/** + * struct ssam_event_id - Unique event ID used for enabling events. + * @target_category: Target category of the event source. + * @instance: Instance ID of the event source. + * + * This struct specifies the event to be enabled/disabled via an externally + * provided registry. It does not specify the registry to be used itself, this + * is done via &struct ssam_event_registry. + */ +struct ssam_event_id { + u8 target_category; + u8 instance; +}; + +/** + * enum ssam_event_mask - Flags specifying how events are matched to notifiers. + * + * @SSAM_EVENT_MASK_NONE: + * Run the callback for any event with matching target category. Do not + * do any additional filtering. + * + * @SSAM_EVENT_MASK_TARGET: + * In addition to filtering by target category, only execute the notifier + * callback for events with a target ID matching to the one of the + * registry used for enabling/disabling the event. + * + * @SSAM_EVENT_MASK_INSTANCE: + * In addition to filtering by target category, only execute the notifier + * callback for events with an instance ID matching to the instance ID + * used when enabling the event. + * + * @SSAM_EVENT_MASK_STRICT: + * Do all the filtering above. + */ +enum ssam_event_mask { + SSAM_EVENT_MASK_TARGET = BIT(0), + SSAM_EVENT_MASK_INSTANCE = BIT(1), + + SSAM_EVENT_MASK_NONE = 0, + SSAM_EVENT_MASK_STRICT = + SSAM_EVENT_MASK_TARGET + | SSAM_EVENT_MASK_INSTANCE, +}; + +/** + * SSAM_EVENT_REGISTRY() - Define a new event registry. + * @tc: Target category for the event registry requests. + * @tid: Target ID for the event registry requests. + * @cid_en: Command ID for the event-enable request. + * @cid_dis: Command ID for the event-disable request. + * + * Return: Returns the &struct ssam_event_registry specified by the given + * parameters. + */ +#define SSAM_EVENT_REGISTRY(tc, tid, cid_en, cid_dis) \ + ((struct ssam_event_registry) { \ + .target_category = (tc), \ + .target_id = (tid), \ + .cid_enable = (cid_en), \ + .cid_disable = (cid_dis), \ + }) + +#define SSAM_EVENT_REGISTRY_SAM \ + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) + +#define SSAM_EVENT_REGISTRY_KIP \ + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) + +#define SSAM_EVENT_REGISTRY_REG \ + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) + +/** + * struct ssam_event_notifier - Notifier block for SSAM events. + * @base: The base notifier block with callback function and priority. + * @event: The event for which this block will receive notifications. + * @event.reg: Registry via which the event will be enabled/disabled. + * @event.id: ID specifying the event. + * @event.mask: Flags determining how events are matched to the notifier. + * @event.flags: Flags used for enabling the event. + */ +struct ssam_event_notifier { + struct ssam_notifier_block base; + + struct { + struct ssam_event_registry reg; + struct ssam_event_id id; + enum ssam_event_mask mask; + u8 flags; + } event; +}; + +int ssam_notifier_register(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + +int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + +#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h new file mode 100644 index 000000000000..02f3e06c0a60 --- /dev/null +++ b/include/linux/surface_aggregator/device.h @@ -0,0 +1,423 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module (SSAM) bus and client-device subsystem. + * + * Main interface for the surface-aggregator bus, surface-aggregator client + * devices, and respective drivers building on top of the SSAM controller. + * Provides support for non-platform/non-ACPI SSAM clients via dedicated + * subsystem. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H +#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H + +#include +#include +#include + +#include + + +/* -- Surface System Aggregator Module bus. --------------------------------- */ + +/** + * enum ssam_device_domain - SAM device domain. + * @SSAM_DOMAIN_VIRTUAL: Virtual device. + * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. + */ +enum ssam_device_domain { + SSAM_DOMAIN_VIRTUAL = 0x00, + SSAM_DOMAIN_SERIALHUB = 0x01, +}; + +/** + * enum ssam_virtual_tc - Target categories for the virtual SAM domain. + * @SSAM_VIRTUAL_TC_HUB: Device hub category. + */ +enum ssam_virtual_tc { + SSAM_VIRTUAL_TC_HUB = 0x00, +}; + +/** + * struct ssam_device_uid - Unique identifier for SSAM device. + * @domain: Domain of the device. + * @category: Target category of the device. + * @target: Target ID of the device. + * @instance: Instance ID of the device. + * @function: Sub-function of the device. This field can be used to split a + * single SAM device into multiple virtual subdevices to separate + * different functionality of that device and allow one driver per + * such functionality. + */ +struct ssam_device_uid { + u8 domain; + u8 category; + u8 target; + u8 instance; + u8 function; +}; + +/* + * Special values for device matching. + * + * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and + * SSAM_SDEV() exclusively. Specifically, they are used to initialize the + * match_flags member of the device ID structure. Do not use them directly + * with struct ssam_device_id or struct ssam_device_uid. + */ +#define SSAM_ANY_TID 0xffff +#define SSAM_ANY_IID 0xffff +#define SSAM_ANY_FUN 0xffff + +/** + * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given + * parameters. + * @d: Domain of the device. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters. See &struct + * ssam_device_uid for details regarding the parameters. The special values + * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that + * matching should ignore target ID, instance ID, and/or sub-function, + * respectively. This macro initializes the ``match_flags`` field based on the + * given parameters. + * + * Note: The parameters @d and @cat must be valid &u8 values, the parameters + * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_DEVICE(d, cat, tid, iid, fun) \ + .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ + | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ + | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ + .domain = d, \ + .category = cat, \ + .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ + .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ + .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ + +/** + * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with + * the given parameters. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters in the + * virtual domain. See &struct ssam_device_uid for details regarding the + * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and + * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, + * instance ID, and/or sub-function, respectively. This macro initializes the + * ``match_flags`` field based on the given parameters. + * + * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, + * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_VDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) + +/** + * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device + * with the given parameters. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters in the SSH + * domain. See &struct ssam_device_uid for details regarding the parameters. + * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be + * used to specify that matching should ignore target ID, instance ID, and/or + * sub-function, respectively. This macro initializes the ``match_flags`` + * field based on the given parameters. + * + * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, + * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_SDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + +/** + * struct ssam_device - SSAM client device. + * @dev: Driver model representation of the device. + * @ctrl: SSAM controller managing this device. + * @uid: UID identifying the device. + */ +struct ssam_device { + struct device dev; + struct ssam_controller *ctrl; + + struct ssam_device_uid uid; +}; + +/** + * struct ssam_device_driver - SSAM client device driver. + * @driver: Base driver model structure. + * @match_table: Match table specifying which devices the driver should bind to. + * @probe: Called when the driver is being bound to a device. + * @remove: Called when the driver is being unbound from the device. + */ +struct ssam_device_driver { + struct device_driver driver; + + const struct ssam_device_id *match_table; + + int (*probe)(struct ssam_device *sdev); + void (*remove)(struct ssam_device *sdev); +}; + +extern struct bus_type ssam_bus_type; +extern const struct device_type ssam_device_type; + +/** + * is_ssam_device() - Check if the given device is a SSAM client device. + * @d: The device to test the type of. + * + * Return: Returns %true if the specified device is of type &struct + * ssam_device, i.e. the device type points to %ssam_device_type, and %false + * otherwise. + */ +static inline bool is_ssam_device(struct device *d) +{ + return d->type == &ssam_device_type; +} + +/** + * to_ssam_device() - Casts the given device to a SSAM client device. + * @d: The device to cast. + * + * Casts the given &struct device to a &struct ssam_device. The caller has to + * ensure that the given device is actually enclosed in a &struct ssam_device, + * e.g. by calling is_ssam_device(). + * + * Return: Returns a pointer to the &struct ssam_device wrapping the given + * device @d. + */ +static inline struct ssam_device *to_ssam_device(struct device *d) +{ + return container_of(d, struct ssam_device, dev); +} + +/** + * to_ssam_device_driver() - Casts the given device driver to a SSAM client + * device driver. + * @d: The driver to cast. + * + * Casts the given &struct device_driver to a &struct ssam_device_driver. The + * caller has to ensure that the given driver is actually enclosed in a + * &struct ssam_device_driver. + * + * Return: Returns the pointer to the &struct ssam_device_driver wrapping the + * given device driver @d. + */ +static inline +struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) +{ + return container_of(d, struct ssam_device_driver, driver); +} + +const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, + const struct ssam_device_uid uid); + +const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); + +const void *ssam_device_get_match_data(const struct ssam_device *dev); + +struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + struct ssam_device_uid uid); + +int ssam_device_add(struct ssam_device *sdev); +void ssam_device_remove(struct ssam_device *sdev); + +/** + * ssam_device_get() - Increment reference count of SSAM client device. + * @sdev: The device to increment the reference count of. + * + * Increments the reference count of the given SSAM client device by + * incrementing the reference count of the enclosed &struct device via + * get_device(). + * + * See ssam_device_put() for the counter-part of this function. + * + * Return: Returns the device provided as input. + */ +static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) +{ + return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; +} + +/** + * ssam_device_put() - Decrement reference count of SSAM client device. + * @sdev: The device to decrement the reference count of. + * + * Decrements the reference count of the given SSAM client device by + * decrementing the reference count of the enclosed &struct device via + * put_device(). + * + * See ssam_device_get() for the counter-part of this function. + */ +static inline void ssam_device_put(struct ssam_device *sdev) +{ + if (sdev) + put_device(&sdev->dev); +} + +/** + * ssam_device_get_drvdata() - Get driver-data of SSAM client device. + * @sdev: The device to get the driver-data from. + * + * Return: Returns the driver-data of the given device, previously set via + * ssam_device_set_drvdata(). + */ +static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) +{ + return dev_get_drvdata(&sdev->dev); +} + +/** + * ssam_device_set_drvdata() - Set driver-data of SSAM client device. + * @sdev: The device to set the driver-data of. + * @data: The data to set the device's driver-data pointer to. + */ +static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) +{ + dev_set_drvdata(&sdev->dev, data); +} + +int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); +void ssam_device_driver_unregister(struct ssam_device_driver *d); + +/** + * ssam_device_driver_register() - Register a SSAM client device driver. + * @drv: The driver to register. + */ +#define ssam_device_driver_register(drv) \ + __ssam_device_driver_register(drv, THIS_MODULE) + +/** + * module_ssam_device_driver() - Helper macro for SSAM device driver + * registration. + * @drv: The driver managed by this module. + * + * Helper macro to register a SSAM device driver via module_init() and + * module_exit(). This macro may only be used once per module and replaces the + * aforementioned definitions. + */ +#define module_ssam_device_driver(drv) \ + module_driver(drv, ssam_device_driver_register, \ + ssam_device_driver_unregister) + + +/* -- Helpers for client-device requests. ----------------------------------- */ + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM + * request function with neither argument nor return value. + * @name: Name of the generated function. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request having neither argument nor return value. Device + * specifying parameters are not hard-coded, but instead are provided via the + * client device, specifically its UID, supplied when calling this function. + * The generated function takes care of setting up the request struct, buffer + * allocation, as well as execution of the request itself, returning once the + * request has been fully completed. The required transport buffer will be + * allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev)``, + * returning the status of the request, which is zero on success and negative + * on failure. The ``sdev`` parameter specifies both the target device of the + * request and by association the controller via which the request is sent. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ + int name(struct ssam_device *sdev) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM + * request function with argument. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking an argument of type @atype and having no + * return value. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev, + * const atype *arg)``, returning the status of the request, which is zero on + * success and negative on failure. The ``sdev`` parameter specifies both the + * target device of the request and by association the controller via which + * the request is sent. The request's argument is specified via the ``arg`` + * pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ + int name(struct ssam_device *sdev, const atype *arg) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, arg); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM + * request function with return value. + * @name: Name of the generated function. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking no argument but having a return value of + * type @rtype. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev, + * rtype *ret)``, returning the status of the request, which is zero on + * success and negative on failure. The ``sdev`` parameter specifies both the + * target device of the request and by association the controller via which + * the request is sent. The request's return value is written to the memory + * pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ + int name(struct ssam_device *sdev, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, ret); \ + } + +#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h new file mode 100644 index 000000000000..64276fbfa1d5 --- /dev/null +++ b/include/linux/surface_aggregator/serial_hub.h @@ -0,0 +1,672 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface Serial Hub (SSH) protocol and communication interface. + * + * Lower-level communication layers and SSH protocol definitions for the + * Surface System Aggregator Module (SSAM). Provides the interface for basic + * packet- and request-based communication with the SSAM EC via SSH. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H +#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H + +#include +#include +#include +#include +#include + + +/* -- Data structures for SAM-over-SSH communication. ----------------------- */ + +/** + * enum ssh_frame_type - Frame types for SSH frames. + * + * @SSH_FRAME_TYPE_DATA_SEQ: + * Indicates a data frame, followed by a payload with the length specified + * in the ``struct ssh_frame.len`` field. This frame is sequenced, meaning + * that an ACK is required. + * + * @SSH_FRAME_TYPE_DATA_NSQ: + * Same as %SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, meaning that the + * message does not have to be ACKed. + * + * @SSH_FRAME_TYPE_ACK: + * Indicates an ACK message. + * + * @SSH_FRAME_TYPE_NAK: + * Indicates an error response for previously sent frame. In general, this + * means that the frame and/or payload is malformed, e.g. a CRC is wrong. + * For command-type payloads, this can also mean that the command is + * invalid. + */ +enum ssh_frame_type { + SSH_FRAME_TYPE_DATA_SEQ = 0x80, + SSH_FRAME_TYPE_DATA_NSQ = 0x00, + SSH_FRAME_TYPE_ACK = 0x40, + SSH_FRAME_TYPE_NAK = 0x04, +}; + +/** + * struct ssh_frame - SSH communication frame. + * @type: The type of the frame. See &enum ssh_frame_type. + * @len: The length of the frame payload directly following the CRC for this + * frame. Does not include the final CRC for that payload. + * @seq: The sequence number for this message/exchange. + */ +struct ssh_frame { + u8 type; + __le16 len; + u8 seq; +} __packed; + +static_assert(sizeof(struct ssh_frame) == 4); + +/* + * SSH_FRAME_MAX_PAYLOAD_SIZE - Maximum SSH frame payload length in bytes. + * + * This is the physical maximum length of the protocol. Implementations may + * set a more constrained limit. + */ +#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX + +/** + * enum ssh_payload_type - Type indicator for the SSH payload. + * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command + * payload. + */ +enum ssh_payload_type { + SSH_PLD_TYPE_CMD = 0x80, +}; + +/** + * struct ssh_command - Payload of a command-type frame. + * @type: The type of the payload. See &enum ssh_payload_type. Should be + * SSH_PLD_TYPE_CMD for this struct. + * @tc: Command target category. + * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) + * message. + * @tid_in: Input target ID. Should be zero if this is an outgoing (host to + * EC) message. + * @iid: Instance ID. + * @rqid: Request ID. Used to match requests with responses and differentiate + * between responses and events. + * @cid: Command ID. + */ +struct ssh_command { + u8 type; + u8 tc; + u8 tid_out; + u8 tid_in; + u8 iid; + __le16 rqid; + u8 cid; +} __packed; + +static_assert(sizeof(struct ssh_command) == 8); + +/* + * SSH_COMMAND_MAX_PAYLOAD_SIZE - Maximum SSH command payload length in bytes. + * + * This is the physical maximum length of the protocol. Implementations may + * set a more constrained limit. + */ +#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ + (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) + +/* + * SSH_MSG_LEN_BASE - Base-length of a SSH message. + * + * This is the minimum number of bytes required to form a message. The actual + * message length is SSH_MSG_LEN_BASE plus the length of the frame payload. + */ +#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) + +/* + * SSH_MSG_LEN_CTRL - Length of a SSH control message. + * + * This is the length of a SSH control message, which is equal to a SSH + * message without any payload. + */ +#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE + +/** + * SSH_MESSAGE_LENGTH() - Compute length of SSH message. + * @payload_size: Length of the payload inside the SSH frame. + * + * Return: Returns the length of a SSH message with payload of specified size. + */ +#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + (payload_size)) + +/** + * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. + * @payload_size: Length of the command payload. + * + * Return: Returns the length of a SSH command message with command payload of + * specified size. + */ +#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ + SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + (payload_size)) + +/** + * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in + * frame. + * @field: The field for which the offset should be computed. + * + * Return: Returns the offset of the specified &struct ssh_frame field in the + * raw SSH message data as. Takes SYN bytes (u16) preceding the frame into + * account. + */ +#define SSH_MSGOFFSET_FRAME(field) \ + (sizeof(u16) + offsetof(struct ssh_frame, field)) + +/** + * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field + * in command. + * @field: The field for which the offset should be computed. + * + * Return: Returns the offset of the specified &struct ssh_command field in + * the raw SSH message data. Takes SYN bytes (u16) preceding the frame and the + * frame CRC (u16) between frame and command into account. + */ +#define SSH_MSGOFFSET_COMMAND(field) \ + (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ + + offsetof(struct ssh_command, field)) + +/* + * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. + */ +#define SSH_MSG_SYN ((u16)0x55aa) + +/** + * ssh_crc() - Compute CRC for SSH messages. + * @buf: The pointer pointing to the data for which the CRC should be computed. + * @len: The length of the data for which the CRC should be computed. + * + * Return: Returns the CRC computed on the provided data, as used for SSH + * messages. + */ +static inline u16 ssh_crc(const u8 *buf, size_t len) +{ + return crc_ccitt_false(0xffff, buf, len); +} + +/* + * SSH_NUM_EVENTS - The number of reserved event IDs. + * + * The number of reserved event IDs, used for registering an SSH event + * handler. Valid event IDs are numbers below or equal to this value, with + * exception of zero, which is not an event ID. Thus, this is also the + * absolute maximum number of event handlers that can be registered. + */ +#define SSH_NUM_EVENTS 34 + +/* + * SSH_NUM_TARGETS - The number of communication targets used in the protocol. + */ +#define SSH_NUM_TARGETS 2 + +/** + * ssh_rqid_next_valid() - Return the next valid request ID. + * @rqid: The current request ID. + * + * Return: Returns the next valid request ID, following the current request ID + * provided to this function. This function skips any request IDs reserved for + * events. + */ +static inline u16 ssh_rqid_next_valid(u16 rqid) +{ + return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; +} + +/** + * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. + * @rqid: The request ID to convert. + */ +static inline u16 ssh_rqid_to_event(u16 rqid) +{ + return rqid - 1u; +} + +/** + * ssh_rqid_is_event() - Check if given request ID is a valid event ID. + * @rqid: The request ID to check. + */ +static inline bool ssh_rqid_is_event(u16 rqid) +{ + return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; +} + +/** + * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. + * @tc: The target category to convert. + */ +static inline u16 ssh_tc_to_rqid(u8 tc) +{ + return tc; +} + +/** + * ssh_tid_to_index() - Convert target ID to its corresponding target index. + * @tid: The target ID to convert. + */ +static inline u8 ssh_tid_to_index(u8 tid) +{ + return tid - 1u; +} + +/** + * ssh_tid_is_valid() - Check if target ID is valid/supported. + * @tid: The target ID to check. + */ +static inline bool ssh_tid_is_valid(u8 tid) +{ + return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; +} + +/** + * struct ssam_span - Reference to a buffer region. + * @ptr: Pointer to the buffer region. + * @len: Length of the buffer region. + * + * A reference to a (non-owned) buffer segment, consisting of pointer and + * length. Use of this struct indicates non-owned data, i.e. data of which the + * life-time is managed (i.e. it is allocated/freed) via another pointer. + */ +struct ssam_span { + u8 *ptr; + size_t len; +}; + +/* + * Known SSH/EC target categories. + * + * List of currently known target category values; "Known" as in we know they + * exist and are valid on at least some device/model. Detailed functionality + * or the full category name is only known for some of these categories and + * is detailed in the respective comment below. + * + * These values and abbreviations have been extracted from strings inside the + * Windows driver. + */ +enum ssam_ssh_tc { + /* Category 0x00 is invalid for EC use. */ + SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ + SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ + SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ + SSAM_SSH_TC_PMC = 0x04, + SSAM_SSH_TC_FAN = 0x05, + SSAM_SSH_TC_PoM = 0x06, + SSAM_SSH_TC_DBG = 0x07, + SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ + SSAM_SSH_TC_FWU = 0x09, + SSAM_SSH_TC_UNI = 0x0a, + SSAM_SSH_TC_LPC = 0x0b, + SSAM_SSH_TC_TCL = 0x0c, + SSAM_SSH_TC_SFL = 0x0d, + SSAM_SSH_TC_KIP = 0x0e, + SSAM_SSH_TC_EXT = 0x0f, + SSAM_SSH_TC_BLD = 0x10, + SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ + SSAM_SSH_TC_SEN = 0x12, + SSAM_SSH_TC_SRQ = 0x13, + SSAM_SSH_TC_MCU = 0x14, + SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ + SSAM_SSH_TC_TCH = 0x16, + SSAM_SSH_TC_BKL = 0x17, + SSAM_SSH_TC_TAM = 0x18, + SSAM_SSH_TC_ACC = 0x19, + SSAM_SSH_TC_UFI = 0x1a, + SSAM_SSH_TC_USC = 0x1b, + SSAM_SSH_TC_PEN = 0x1c, + SSAM_SSH_TC_VID = 0x1d, + SSAM_SSH_TC_AUD = 0x1e, + SSAM_SSH_TC_SMC = 0x1f, + SSAM_SSH_TC_KPD = 0x20, + SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ +}; + + +/* -- Packet transport layer (ptl). ----------------------------------------- */ + +/** + * enum ssh_packet_base_priority - Base priorities for &struct ssh_packet. + * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. + * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data packets. + * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. + * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. + */ +enum ssh_packet_base_priority { + SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ + SSH_PACKET_PRIORITY_DATA = 0, + SSH_PACKET_PRIORITY_NAK = 1, + SSH_PACKET_PRIORITY_ACK = 2, +}; + +/* + * Same as SSH_PACKET_PRIORITY() below, only with actual values. + */ +#define __SSH_PACKET_PRIORITY(base, try) \ + (((base) << 4) | ((try) & 0x0f)) + +/** + * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and + * number of tries. + * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. + * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. + * @try: The number of tries (must be less than 16). + * + * Compute the combined packet priority. The combined priority is dominated by + * the base priority, whereas the number of (re-)tries decides the precedence + * of packets with the same base priority, giving higher priority to packets + * that already have more tries. + * + * Return: Returns the computed priority as value fitting inside a &u8. A + * higher number means a higher priority. + */ +#define SSH_PACKET_PRIORITY(base, try) \ + __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) + +/** + * ssh_packet_priority_get_try() - Get number of tries from packet priority. + * @priority: The packet priority. + * + * Return: Returns the number of tries encoded in the specified packet + * priority. + */ +static inline u8 ssh_packet_priority_get_try(u8 priority) +{ + return priority & 0x0f; +} + +/** + * ssh_packet_priority_get_base - Get base priority from packet priority. + * @priority: The packet priority. + * + * Return: Returns the base priority encoded in the given packet priority. + */ +static inline u8 ssh_packet_priority_get_base(u8 priority) +{ + return (priority & 0xf0) >> 4; +} + +enum ssh_packet_flags { + /* state flags */ + SSH_PACKET_SF_LOCKED_BIT, + SSH_PACKET_SF_QUEUED_BIT, + SSH_PACKET_SF_PENDING_BIT, + SSH_PACKET_SF_TRANSMITTING_BIT, + SSH_PACKET_SF_TRANSMITTED_BIT, + SSH_PACKET_SF_ACKED_BIT, + SSH_PACKET_SF_CANCELED_BIT, + SSH_PACKET_SF_COMPLETED_BIT, + + /* type flags */ + SSH_PACKET_TY_FLUSH_BIT, + SSH_PACKET_TY_SEQUENCED_BIT, + SSH_PACKET_TY_BLOCKING_BIT, + + /* mask for state flags */ + SSH_PACKET_FLAGS_SF_MASK = + BIT(SSH_PACKET_SF_LOCKED_BIT) + | BIT(SSH_PACKET_SF_QUEUED_BIT) + | BIT(SSH_PACKET_SF_PENDING_BIT) + | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) + | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) + | BIT(SSH_PACKET_SF_ACKED_BIT) + | BIT(SSH_PACKET_SF_CANCELED_BIT) + | BIT(SSH_PACKET_SF_COMPLETED_BIT), + + /* mask for type flags */ + SSH_PACKET_FLAGS_TY_MASK = + BIT(SSH_PACKET_TY_FLUSH_BIT) + | BIT(SSH_PACKET_TY_SEQUENCED_BIT) + | BIT(SSH_PACKET_TY_BLOCKING_BIT), +}; + +struct ssh_ptl; +struct ssh_packet; + +/** + * struct ssh_packet_ops - Callback operations for a SSH packet. + * @release: Function called when the packet reference count reaches zero. + * This callback must be relied upon to ensure that the packet has + * left the transport system(s). + * @complete: Function called when the packet is completed, either with + * success or failure. In case of failure, the reason for the + * failure is indicated by the value of the provided status code + * argument. This value will be zero in case of success. Note that + * a call to this callback does not guarantee that the packet is + * not in use by the transport system any more. + */ +struct ssh_packet_ops { + void (*release)(struct ssh_packet *p); + void (*complete)(struct ssh_packet *p, int status); +}; + +/** + * struct ssh_packet - SSH transport packet. + * @ptl: Pointer to the packet transport layer. May be %NULL if the packet + * (or enclosing request) has not been submitted yet. + * @refcnt: Reference count of the packet. + * @priority: Priority of the packet. Must be computed via + * SSH_PACKET_PRIORITY(). Must only be accessed while holding the + * queue lock after first submission. + * @data: Raw message data. + * @data.len: Length of the raw message data. + * @data.ptr: Pointer to the raw message data buffer. + * @state: State and type flags describing current packet state (dynamic) + * and type (static). See &enum ssh_packet_flags for possible + * options. + * @timestamp: Timestamp specifying when the latest transmission of a + * currently pending packet has been started. May be %KTIME_MAX + * before or in-between transmission attempts. Used for the packet + * timeout implementation. Must only be accessed while holding the + * pending lock after first submission. + * @queue_node: The list node for the packet queue. + * @pending_node: The list node for the set of pending packets. + * @ops: Packet operations. + */ +struct ssh_packet { + struct ssh_ptl *ptl; + struct kref refcnt; + + u8 priority; + + struct { + size_t len; + u8 *ptr; + } data; + + unsigned long state; + ktime_t timestamp; + + struct list_head queue_node; + struct list_head pending_node; + + const struct ssh_packet_ops *ops; +}; + +struct ssh_packet *ssh_packet_get(struct ssh_packet *p); +void ssh_packet_put(struct ssh_packet *p); + +/** + * ssh_packet_set_data() - Set raw message data of packet. + * @p: The packet for which the message data should be set. + * @ptr: Pointer to the memory holding the message data. + * @len: Length of the message data. + * + * Sets the raw message data buffer of the packet to the provided memory. The + * memory is not copied. Instead, the caller is responsible for management + * (i.e. allocation and deallocation) of the memory. The caller must ensure + * that the provided memory is valid and contains a valid SSH message, + * starting from the time of submission of the packet until the ``release`` + * callback has been called. During this time, the memory may not be altered + * in any way. + */ +static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) +{ + p->data.ptr = ptr; + p->data.len = len; +} + + +/* -- Request transport layer (rtl). ---------------------------------------- */ + +enum ssh_request_flags { + /* state flags */ + SSH_REQUEST_SF_LOCKED_BIT, + SSH_REQUEST_SF_QUEUED_BIT, + SSH_REQUEST_SF_PENDING_BIT, + SSH_REQUEST_SF_TRANSMITTING_BIT, + SSH_REQUEST_SF_TRANSMITTED_BIT, + SSH_REQUEST_SF_RSPRCVD_BIT, + SSH_REQUEST_SF_CANCELED_BIT, + SSH_REQUEST_SF_COMPLETED_BIT, + + /* type flags */ + SSH_REQUEST_TY_FLUSH_BIT, + SSH_REQUEST_TY_HAS_RESPONSE_BIT, + + /* mask for state flags */ + SSH_REQUEST_FLAGS_SF_MASK = + BIT(SSH_REQUEST_SF_LOCKED_BIT) + | BIT(SSH_REQUEST_SF_QUEUED_BIT) + | BIT(SSH_REQUEST_SF_PENDING_BIT) + | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) + | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) + | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) + | BIT(SSH_REQUEST_SF_CANCELED_BIT) + | BIT(SSH_REQUEST_SF_COMPLETED_BIT), + + /* mask for type flags */ + SSH_REQUEST_FLAGS_TY_MASK = + BIT(SSH_REQUEST_TY_FLUSH_BIT) + | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), +}; + +struct ssh_rtl; +struct ssh_request; + +/** + * struct ssh_request_ops - Callback operations for a SSH request. + * @release: Function called when the request's reference count reaches zero. + * This callback must be relied upon to ensure that the request has + * left the transport systems (both, packet an request systems). + * @complete: Function called when the request is completed, either with + * success or failure. The command data for the request response + * is provided via the &struct ssh_command parameter (``cmd``), + * the command payload of the request response via the &struct + * ssh_span parameter (``data``). + * + * If the request does not have any response or has not been + * completed with success, both ``cmd`` and ``data`` parameters will + * be NULL. If the request response does not have any command + * payload, the ``data`` span will be an empty (zero-length) span. + * + * In case of failure, the reason for the failure is indicated by + * the value of the provided status code argument (``status``). This + * value will be zero in case of success and a regular errno + * otherwise. + * + * Note that a call to this callback does not guarantee that the + * request is not in use by the transport systems any more. + */ +struct ssh_request_ops { + void (*release)(struct ssh_request *rqst); + void (*complete)(struct ssh_request *rqst, + const struct ssh_command *cmd, + const struct ssam_span *data, int status); +}; + +/** + * struct ssh_request - SSH transport request. + * @packet: The underlying SSH transport packet. + * @node: List node for the request queue and pending set. + * @state: State and type flags describing current request state (dynamic) + * and type (static). See &enum ssh_request_flags for possible + * options. + * @timestamp: Timestamp specifying when we start waiting on the response of + * the request. This is set once the underlying packet has been + * completed and may be %KTIME_MAX before that, or when the request + * does not expect a response. Used for the request timeout + * implementation. + * @ops: Request Operations. + */ +struct ssh_request { + struct ssh_packet packet; + struct list_head node; + + unsigned long state; + ktime_t timestamp; + + const struct ssh_request_ops *ops; +}; + +/** + * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. + * @p: The packet to cast. + * + * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. + * The caller is responsible for making sure that the packet is actually + * wrapped in a &struct ssh_request. + * + * Return: Returns the &struct ssh_request wrapping the provided packet. + */ +static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) +{ + return container_of(p, struct ssh_request, packet); +} + +/** + * ssh_request_get() - Increment reference count of request. + * @r: The request to increment the reference count of. + * + * Increments the reference count of the given request by incrementing the + * reference count of the underlying &struct ssh_packet, enclosed in it. + * + * See also ssh_request_put(), ssh_packet_get(). + * + * Return: Returns the request provided as input. + */ +static inline struct ssh_request *ssh_request_get(struct ssh_request *r) +{ + return r ? to_ssh_request(ssh_packet_get(&r->packet)) : NULL; +} + +/** + * ssh_request_put() - Decrement reference count of request. + * @r: The request to decrement the reference count of. + * + * Decrements the reference count of the given request by decrementing the + * reference count of the underlying &struct ssh_packet, enclosed in it. If + * the reference count reaches zero, the ``release`` callback specified in the + * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be + * called. + * + * See also ssh_request_get(), ssh_packet_put(). + */ +static inline void ssh_request_put(struct ssh_request *r) +{ + if (r) + ssh_packet_put(&r->packet); +} + +/** + * ssh_request_set_data() - Set raw message data of request. + * @r: The request for which the message data should be set. + * @ptr: Pointer to the memory holding the message data. + * @len: Length of the message data. + * + * Sets the raw message data buffer of the underlying packet to the specified + * buffer. Does not copy the actual message data, just sets the buffer pointer + * and length. Refer to ssh_packet_set_data() for more details. + */ +static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) +{ + ssh_packet_set_data(&r->packet, ptr, len); +} + +#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */ diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 31b84404f047..6ac7bb1d2b1f 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -17,7 +17,6 @@ #include #include -#define THERMAL_TRIPS_NONE -1 #define THERMAL_MAX_TRIPS 12 /* invalid cooling state */ @@ -77,8 +76,6 @@ struct thermal_zone_device_ops { int (*set_emul_temp) (struct thermal_zone_device *, int); int (*get_trend) (struct thermal_zone_device *, int, enum thermal_trend *); - int (*notify) (struct thermal_zone_device *, int, - enum thermal_trip_type); void (*hot)(struct thermal_zone_device *); void (*critical)(struct thermal_zone_device *); }; @@ -118,9 +115,9 @@ struct thermal_cooling_device { * @devdata: private pointer for device private data * @trips: number of trip points the thermal zone supports * @trips_disabled; bitmap for disabled trips - * @passive_delay: number of milliseconds to wait between polls when + * @passive_delay_jiffies: number of jiffies to wait between polls when * performing passive cooling. - * @polling_delay: number of milliseconds to wait between polls when + * @polling_delay_jiffies: number of jiffies to wait between polls when * checking whether trip points have been crossed (0 for * interrupt driven systems) * @temperature: current temperature. This is only for core code, @@ -133,9 +130,6 @@ struct thermal_cooling_device { trip point. * @prev_high_trip: the above current temperature if you've crossed a passive trip point. - * @forced_passive: If > 0, temperature at which to switch on all ACPI - * processor cooling devices. Currently only used by the - * step-wise governor. * @need_update: if equals 1, thermal_zone_device_update needs to be invoked. * @ops: operations this &thermal_zone_device supports * @tzp: thermal zone parameters @@ -161,15 +155,14 @@ struct thermal_zone_device { void *devdata; int trips; unsigned long trips_disabled; /* bitmap for disabled trips */ - int passive_delay; - int polling_delay; + unsigned long passive_delay_jiffies; + unsigned long polling_delay_jiffies; int temperature; int last_temperature; int emul_temperature; int passive; int prev_low_trip; int prev_high_trip; - unsigned int forced_passive; atomic_t need_update; struct thermal_zone_device_ops *ops; struct thermal_zone_params *tzp; @@ -397,7 +390,6 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp); int thermal_zone_get_slope(struct thermal_zone_device *tz); int thermal_zone_get_offset(struct thermal_zone_device *tz); -void thermal_cdev_update(struct thermal_cooling_device *); void thermal_notify_framework(struct thermal_zone_device *, int); int thermal_zone_device_enable(struct thermal_zone_device *tz); int thermal_zone_device_disable(struct thermal_zone_device *tz); @@ -444,8 +436,6 @@ static inline int thermal_zone_get_offset( struct thermal_zone_device *tz) { return -ENODEV; } -static inline void thermal_cdev_update(struct thermal_cooling_device *cdev) -{ } static inline void thermal_notify_framework(struct thermal_zone_device *tz, int trip) { } diff --git a/include/uapi/linux/i2c-dev.h b/include/uapi/linux/i2c-dev.h index 85f8047afcf2..1c4cec4ddd84 100644 --- a/include/uapi/linux/i2c-dev.h +++ b/include/uapi/linux/i2c-dev.h @@ -1,25 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ /* - i2c-dev.h - i2c-bus driver, char device interface - - Copyright (C) 1995-97 Simon G. Vogl - Copyright (C) 1998-99 Frodo Looijaard - - 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 Street, Fifth Floor, Boston, - MA 02110-1301 USA. -*/ + * i2c-dev.h - I2C bus char device interface + * + * Copyright (C) 1995-97 Simon G. Vogl + * Copyright (C) 1998-99 Frodo Looijaard + */ #ifndef _UAPI_LINUX_I2C_DEV_H #define _UAPI_LINUX_I2C_DEV_H diff --git a/include/uapi/linux/i2c.h b/include/uapi/linux/i2c.h index f71a1751cacf..92326ebde350 100644 --- a/include/uapi/linux/i2c.h +++ b/include/uapi/linux/i2c.h @@ -1,29 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -/* ------------------------------------------------------------------------- */ -/* */ -/* i2c.h - definitions for the i2c-bus interface */ -/* */ -/* ------------------------------------------------------------------------- */ -/* Copyright (C) 1995-2000 Simon G. Vogl - - 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 Street, Fifth Floor, Boston, - MA 02110-1301 USA. */ -/* ------------------------------------------------------------------------- */ - -/* With some changes from Kyösti Mälkki and - Frodo Looijaard */ +/* + * i2c.h - definitions for the I2C bus interface + * + * Copyright (C) 1995-2000 Simon G. Vogl + * With some changes from Kyösti Mälkki and + * Frodo Looijaard + */ #ifndef _UAPI_LINUX_I2C_H #define _UAPI_LINUX_I2C_H @@ -32,18 +14,41 @@ /** * struct i2c_msg - an I2C transaction segment beginning with START - * @addr: Slave address, either seven or ten bits. When this is a ten - * bit address, I2C_M_TEN must be set in @flags and the adapter - * must support I2C_FUNC_10BIT_ADDR. - * @flags: I2C_M_RD is handled by all adapters. No other flags may be - * provided unless the adapter exported the relevant I2C_FUNC_* - * flags through i2c_check_functionality(). - * @len: Number of data bytes in @buf being read from or written to the - * I2C slave address. For read transactions where I2C_M_RECV_LEN - * is set, the caller guarantees that this buffer can hold up to - * 32 bytes in addition to the initial length byte sent by the - * slave (plus, if used, the SMBus PEC); and this value will be - * incremented by the number of block data bytes received. + * + * @addr: Slave address, either 7 or 10 bits. When this is a 10 bit address, + * %I2C_M_TEN must be set in @flags and the adapter must support + * %I2C_FUNC_10BIT_ADDR. + * + * @flags: + * Supported by all adapters: + * %I2C_M_RD: read data (from slave to master). Guaranteed to be 0x0001! + * + * Optional: + * %I2C_M_DMA_SAFE: the buffer of this message is DMA safe. Makes only sense + * in kernelspace, because userspace buffers are copied anyway + * + * Only if I2C_FUNC_10BIT_ADDR is set: + * %I2C_M_TEN: this is a 10 bit chip address + * + * Only if I2C_FUNC_SMBUS_READ_BLOCK_DATA is set: + * %I2C_M_RECV_LEN: message length will be first received byte + * + * Only if I2C_FUNC_NOSTART is set: + * %I2C_M_NOSTART: skip repeated start sequence + + * Only if I2C_FUNC_PROTOCOL_MANGLING is set: + * %I2C_M_NO_RD_ACK: in a read message, master ACK/NACK bit is skipped + * %I2C_M_IGNORE_NAK: treat NACK from client as ACK + * %I2C_M_REV_DIR_ADDR: toggles the Rd/Wr bit + * %I2C_M_STOP: force a STOP condition after the message + * + * @len: Number of data bytes in @buf being read from or written to the I2C + * slave address. For read transactions where %I2C_M_RECV_LEN is set, the + * caller guarantees that this buffer can hold up to %I2C_SMBUS_BLOCK_MAX + * bytes in addition to the initial length byte sent by the slave (plus, + * if used, the SMBus PEC); and this value will be incremented by the number + * of block data bytes received. + * * @buf: The buffer into which data is read, or from which it's written. * * An i2c_msg is the low level representation of one segment of an I2C @@ -60,40 +65,36 @@ * group, it is followed by a STOP. Otherwise it is followed by the next * @i2c_msg transaction segment, beginning with a (repeated) START. * - * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then + * Alternatively, when the adapter supports %I2C_FUNC_PROTOCOL_MANGLING then * passing certain @flags may have changed those standard protocol behaviors. * Those flags are only for use with broken/nonconforming slaves, and with - * adapters which are known to support the specific mangling options they - * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR). + * adapters which are known to support the specific mangling options they need. */ struct i2c_msg { - __u16 addr; /* slave address */ + __u16 addr; __u16 flags; -#define I2C_M_RD 0x0001 /* read data, from slave to master */ - /* I2C_M_RD is guaranteed to be 0x0001! */ -#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ -#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */ - /* makes only sense in kernelspace */ - /* userspace buffers are copied anyway */ -#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ -#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ -#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ -#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ -#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ -#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ - __u16 len; /* msg length */ - __u8 *buf; /* pointer to msg data */ +#define I2C_M_RD 0x0001 /* guaranteed to be 0x0001! */ +#define I2C_M_TEN 0x0010 /* use only if I2C_FUNC_10BIT_ADDR */ +#define I2C_M_DMA_SAFE 0x0200 /* use only in kernel space */ +#define I2C_M_RECV_LEN 0x0400 /* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */ +#define I2C_M_NO_RD_ACK 0x0800 /* use only if I2C_FUNC_PROTOCOL_MANGLING */ +#define I2C_M_IGNORE_NAK 0x1000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */ +#define I2C_M_REV_DIR_ADDR 0x2000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */ +#define I2C_M_NOSTART 0x4000 /* use only if I2C_FUNC_NOSTART */ +#define I2C_M_STOP 0x8000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */ + __u16 len; + __u8 *buf; }; /* To determine what functionality is present */ #define I2C_FUNC_I2C 0x00000001 -#define I2C_FUNC_10BIT_ADDR 0x00000002 -#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_IGNORE_NAK etc. */ +#define I2C_FUNC_10BIT_ADDR 0x00000002 /* required for I2C_M_TEN */ +#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* required for I2C_M_IGNORE_NAK etc. */ #define I2C_FUNC_SMBUS_PEC 0x00000008 -#define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */ +#define I2C_FUNC_NOSTART 0x00000010 /* required for I2C_M_NOSTART */ #define I2C_FUNC_SLAVE 0x00000020 -#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */ +#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 or later */ #define I2C_FUNC_SMBUS_QUICK 0x00010000 #define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 #define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 @@ -102,11 +103,11 @@ struct i2c_msg { #define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 #define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 #define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 -#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 +#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 /* required for I2C_M_RECV_LEN */ #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 #define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ -#define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000 +#define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000 /* SMBus 2.0 or later */ #define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | \ I2C_FUNC_SMBUS_WRITE_BYTE) @@ -128,6 +129,11 @@ struct i2c_msg { I2C_FUNC_SMBUS_I2C_BLOCK | \ I2C_FUNC_SMBUS_PEC) +/* if I2C_M_RECV_LEN is also supported */ +#define I2C_FUNC_SMBUS_EMUL_ALL (I2C_FUNC_SMBUS_EMUL | \ + I2C_FUNC_SMBUS_READ_BLOCK_DATA | \ + I2C_FUNC_SMBUS_BLOCK_PROC_CALL) + /* * Data for SMBus Messages */ diff --git a/include/uapi/linux/spi/spi.h b/include/uapi/linux/spi/spi.h new file mode 100644 index 000000000000..236a85f08ded --- /dev/null +++ b/include/uapi/linux/spi/spi.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +#ifndef _UAPI_SPI_H +#define _UAPI_SPI_H + +#include + +#define SPI_CPHA _BITUL(0) /* clock phase */ +#define SPI_CPOL _BITUL(1) /* clock polarity */ + +#define SPI_MODE_0 (0|0) /* (original MicroWire) */ +#define SPI_MODE_1 (0|SPI_CPHA) +#define SPI_MODE_2 (SPI_CPOL|0) +#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) +#define SPI_MODE_X_MASK (SPI_CPOL|SPI_CPHA) + +#define SPI_CS_HIGH _BITUL(2) /* chipselect active high? */ +#define SPI_LSB_FIRST _BITUL(3) /* per-word bits-on-wire */ +#define SPI_3WIRE _BITUL(4) /* SI/SO signals shared */ +#define SPI_LOOP _BITUL(5) /* loopback mode */ +#define SPI_NO_CS _BITUL(6) /* 1 dev/bus, no chipselect */ +#define SPI_READY _BITUL(7) /* slave pulls low to pause */ +#define SPI_TX_DUAL _BITUL(8) /* transmit with 2 wires */ +#define SPI_TX_QUAD _BITUL(9) /* transmit with 4 wires */ +#define SPI_RX_DUAL _BITUL(10) /* receive with 2 wires */ +#define SPI_RX_QUAD _BITUL(11) /* receive with 4 wires */ +#define SPI_CS_WORD _BITUL(12) /* toggle cs after each word */ +#define SPI_TX_OCTAL _BITUL(13) /* transmit with 8 wires */ +#define SPI_RX_OCTAL _BITUL(14) /* receive with 8 wires */ +#define SPI_3WIRE_HIZ _BITUL(15) /* high impedance turnaround */ + +/* + * All the bits defined above should be covered by SPI_MODE_USER_MASK. + * The SPI_MODE_USER_MASK has the SPI_MODE_KERNEL_MASK counterpart in + * 'include/linux/spi/spi.h'. The bits defined here are from bit 0 upwards + * while in SPI_MODE_KERNEL_MASK they are from the other end downwards. + * These bits must not overlap. A static assert check should make sure of that. + * If adding extra bits, make sure to increase the bit index below as well. + */ +#define SPI_MODE_USER_MASK (_BITUL(16) - 1) + +#endif /* _UAPI_SPI_H */ diff --git a/include/uapi/linux/spi/spidev.h b/include/uapi/linux/spi/spidev.h index d56427c0b3e0..0c3da08f2aff 100644 --- a/include/uapi/linux/spi/spidev.h +++ b/include/uapi/linux/spi/spidev.h @@ -25,35 +25,7 @@ #include #include - -/* User space versions of kernel symbols for SPI clocking modes, - * matching - */ - -#define SPI_CPHA 0x01 -#define SPI_CPOL 0x02 - -#define SPI_MODE_0 (0|0) -#define SPI_MODE_1 (0|SPI_CPHA) -#define SPI_MODE_2 (SPI_CPOL|0) -#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) - -#define SPI_CS_HIGH 0x04 -#define SPI_LSB_FIRST 0x08 -#define SPI_3WIRE 0x10 -#define SPI_LOOP 0x20 -#define SPI_NO_CS 0x40 -#define SPI_READY 0x80 -#define SPI_TX_DUAL 0x100 -#define SPI_TX_QUAD 0x200 -#define SPI_RX_DUAL 0x400 -#define SPI_RX_QUAD 0x800 -#define SPI_CS_WORD 0x1000 -#define SPI_TX_OCTAL 0x2000 -#define SPI_RX_OCTAL 0x4000 -#define SPI_3WIRE_HIZ 0x8000 - -/*---------------------------------------------------------------------------*/ +#include /* IOCTL commands */ diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h new file mode 100644 index 000000000000..fbcce04abfe9 --- /dev/null +++ b/include/uapi/linux/surface_aggregator/cdev.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Surface System Aggregator Module (SSAM) user-space EC interface. + * + * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc + * device. This device provides direct user-space access to the SSAM EC. + * Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H +#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H + +#include +#include + +/** + * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. + * + * @SSAM_CDEV_REQUEST_HAS_RESPONSE: + * Specifies that the request expects a response. If not set, the request + * will be directly completed after its underlying packet has been + * transmitted. If set, the request transport system waits for a response + * of the request. + * + * @SSAM_CDEV_REQUEST_UNSEQUENCED: + * Specifies that the request should be transmitted via an unsequenced + * packet. If set, the request must not have a response, meaning that this + * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually + * exclusive. + */ +enum ssam_cdev_request_flags { + SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, + SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, +}; + +/** + * struct ssam_cdev_request - Controller request IOCTL argument. + * @target_category: Target category of the SAM request. + * @target_id: Target ID of the SAM request. + * @command_id: Command ID of the SAM request. + * @instance_id: Instance ID of the SAM request. + * @flags: Request flags (see &enum ssam_cdev_request_flags). + * @status: Request status (output). + * @payload: Request payload (input data). + * @payload.data: Pointer to request payload data. + * @payload.length: Length of request payload data (in bytes). + * @response: Request response (output data). + * @response.data: Pointer to response buffer. + * @response.length: On input: Capacity of response buffer (in bytes). + * On output: Length of request response (number of bytes + * in the buffer that are actually used). + */ +struct ssam_cdev_request { + __u8 target_category; + __u8 target_id; + __u8 command_id; + __u8 instance_id; + __u16 flags; + __s16 status; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } payload; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } response; +} __attribute__((__packed__)); + +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) + +#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ diff --git a/lib/linear_ranges.c b/lib/linear_ranges.c index 9495ef3572b7..ced5c15d3f04 100644 --- a/lib/linear_ranges.c +++ b/lib/linear_ranges.c @@ -128,7 +128,7 @@ EXPORT_SYMBOL_GPL(linear_range_get_value_array); * @selector: address where found selector value is updated * @found: flag to indicate that given value was in the range * - * Return selector which which range value is closest match for given + * Return selector for which range value is closest match for given * input value. Value is matching if it is equal or smaller than given * value. If given value is in the range, then @found is set true. * @@ -168,11 +168,11 @@ EXPORT_SYMBOL_GPL(linear_range_get_selector_low); * @selector: address where found selector value is updated * @found: flag to indicate that given value was in the range * - * Scan array of ranges for selector which which range value matches given + * Scan array of ranges for selector for which range value matches given * input value. Value is matching if it is equal or smaller than given * value. If given value is found to be in a range scanning is stopped and * @found is set true. If a range with values smaller than given value is found - * but the range max is being smaller than given value, then the ranges + * but the range max is being smaller than given value, then the range's * biggest selector is updated to @selector but scanning ranges is continued * and @found is set to false. * @@ -209,7 +209,7 @@ EXPORT_SYMBOL_GPL(linear_range_get_selector_low_array); * @selector: address where found selector value is updated * @found: flag to indicate that given value was in the range * - * Return selector which which range value is closest match for given + * Return selector for which range value is closest match for given * input value. Value is matching if it is equal or higher than given * value. If given value is in the range, then @found is set true. * diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index e377f52dbfa3..f078eeb0a961 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -246,5 +246,13 @@ int main(void) DEVID(auxiliary_device_id); DEVID_FIELD(auxiliary_device_id, name); + DEVID(ssam_device_id); + DEVID_FIELD(ssam_device_id, match_flags); + DEVID_FIELD(ssam_device_id, domain); + DEVID_FIELD(ssam_device_id, category); + DEVID_FIELD(ssam_device_id, target); + DEVID_FIELD(ssam_device_id, instance); + DEVID_FIELD(ssam_device_id, function); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index fb4827027536..d21d2871387b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1375,6 +1375,28 @@ static int do_auxiliary_entry(const char *filename, void *symval, char *alias) return 1; } +/* + * Looks like: ssam:dNcNtNiNfN + * + * N is exactly 2 digits, where each is an upper-case hex digit. + */ +static int do_ssam_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, ssam_device_id, match_flags); + DEF_FIELD(symval, ssam_device_id, domain); + DEF_FIELD(symval, ssam_device_id, category); + DEF_FIELD(symval, ssam_device_id, target); + DEF_FIELD(symval, ssam_device_id, instance); + DEF_FIELD(symval, ssam_device_id, function); + + sprintf(alias, "ssam:d%02Xc%02X", domain, category); + ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); + ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); + ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); + + return 1; +} + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { @@ -1450,6 +1472,7 @@ static const struct devtable devtable[] = { {"wmi", SIZE_wmi_device_id, do_wmi_entry}, {"mhi", SIZE_mhi_device_id, do_mhi_entry}, {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, + {"ssam", SIZE_ssam_device_id, do_ssam_entry}, }; /* Create MODULE_ALIAS() statements. diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index 09cb3a6672f3..582feb88eca3 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -15,7 +15,7 @@ struct process_cmd_struct { int arg; }; -static const char *version_str = "v1.7"; +static const char *version_str = "v1.8"; static const int supported_api_ver = 1; static struct isst_if_platform_info isst_platform_info; static char *progname; @@ -2304,6 +2304,102 @@ static void get_clos_assoc(int arg) isst_ctdp_display_information_end(outf); } +static void set_turbo_mode_for_cpu(int cpu, int status) +{ + int base_freq; + + if (status) { + base_freq = get_cpufreq_base_freq(cpu); + set_cpufreq_scaling_min_max(cpu, 1, base_freq); + } else { + set_scaling_max_to_cpuinfo_max(cpu); + } + + if (status) { + isst_display_result(cpu, outf, "turbo-mode", "enable", 0); + } else { + isst_display_result(cpu, outf, "turbo-mode", "disable", 0); + } +} + +static void set_turbo_mode(int arg) +{ + int i, enable = arg; + + if (cmd_help) { + if (enable) + fprintf(stderr, "Set turbo mode enable\n"); + else + fprintf(stderr, "Set turbo mode disable\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + + for (i = 0; i < topo_max_cpus; ++i) { + int online; + + if (i) + online = parse_int_file( + 1, "/sys/devices/system/cpu/cpu%d/online", i); + else + online = + 1; /* online entry for CPU 0 needs some special configs */ + + if (online) + set_turbo_mode_for_cpu(i, enable); + + } + isst_ctdp_display_information_end(outf); +} + +static void get_set_trl(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + unsigned long long trl; + int set = *(int *)arg4; + int ret; + + if (set && !fact_trl) { + isst_display_error_info_message(1, "Invalid TRL. Specify with [-t|--trl]", 0, 0); + exit(0); + } + + if (set) { + ret = isst_set_trl(cpu, fact_trl); + isst_display_result(cpu, outf, "turbo-mode", "set-trl", ret); + return; + } + + ret = isst_get_trl(cpu, &trl); + if (ret) + isst_display_result(cpu, outf, "turbo-mode", "get-trl", ret); + else + isst_trl_display_information(cpu, outf, trl); +} + +static void process_trl(int arg) +{ + if (cmd_help) { + if (arg) { + fprintf(stderr, "Set TRL (turbo ratio limits)\n"); + fprintf(stderr, "\t t|--trl: Specify turbo ratio limit for setting TRL\n"); + } else { + fprintf(stderr, "Get TRL (turbo ratio limits)\n"); + } + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(get_set_trl, NULL, + NULL, NULL, &arg); + else + for_each_online_package_in_set(get_set_trl, NULL, + NULL, NULL, &arg); + isst_ctdp_display_information_end(outf); +} + static struct process_cmd_struct clx_n_cmds[] = { { "perf-profile", "info", dump_isst_config, 0 }, { "base-freq", "info", dump_pbf_config, 0 }, @@ -2334,6 +2430,10 @@ static struct process_cmd_struct isst_cmds[] = { { "core-power", "get-config", dump_clos_config, 0 }, { "core-power", "assoc", set_clos_assoc, 0 }, { "core-power", "get-assoc", get_clos_assoc, 0 }, + { "turbo-mode", "enable", set_turbo_mode, 0 }, + { "turbo-mode", "disable", set_turbo_mode, 1 }, + { "turbo-mode", "get-trl", process_trl, 0 }, + { "turbo-mode", "set-trl", process_trl, 1 }, { NULL, NULL, NULL } }; @@ -2549,6 +2649,16 @@ static void fact_help(void) printf("\tcommand : disable\n"); } +static void turbo_mode_help(void) +{ + printf("turbo-mode:\tEnables users to enable/disable turbo mode by adjusting frequency settings. Also allows to get and set turbo ratio limits (TRL).\n"); + printf("\tcommand : enable\n"); + printf("\tcommand : disable\n"); + printf("\tcommand : get-trl\n"); + printf("\tcommand : set-trl\n"); +} + + static void core_power_help(void) { printf("core-power:\tInterface that allows user to define per core/tile\n\ @@ -2573,6 +2683,7 @@ static struct process_cmd_help_struct isst_help_cmds[] = { { "base-freq", pbf_help }, { "turbo-freq", fact_help }, { "core-power", core_power_help }, + { "turbo-mode", turbo_mode_help }, { NULL, NULL } }; @@ -2636,7 +2747,7 @@ static void usage(void) if (is_clx_n_platform()) printf("\nFEATURE : [perf-profile|base-freq]\n"); else - printf("\nFEATURE : [perf-profile|base-freq|turbo-freq|core-power]\n"); + printf("\nFEATURE : [perf-profile|base-freq|turbo-freq|core-power|turbo-mode]\n"); printf("\nFor help on each feature, use -h|--help\n"); printf("\tFor example: intel-speed-select perf-profile -h\n"); diff --git a/tools/power/x86/intel-speed-select/isst-core.c b/tools/power/x86/intel-speed-select/isst-core.c index 8afd23407522..6a26d5769984 100644 --- a/tools/power/x86/intel-speed-select/isst-core.c +++ b/tools/power/x86/intel-speed-select/isst-core.c @@ -665,6 +665,17 @@ int isst_get_fact_info(int cpu, int level, int fact_bucket, struct isst_fact_inf return 0; } +int isst_get_trl(int cpu, unsigned long long *trl) +{ + int ret; + + ret = isst_send_msr_command(cpu, 0x1AD, 0, trl); + if (ret) + return ret; + + return 0; +} + int isst_set_trl(int cpu, unsigned long long trl) { int ret; diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c index e105fece47b6..8e54ce47648e 100644 --- a/tools/power/x86/intel-speed-select/isst-display.c +++ b/tools/power/x86/intel-speed-select/isst-display.c @@ -763,3 +763,21 @@ void isst_display_error_info_message(int error, char *msg, int arg_valid, int ar if (!start) format_and_print(outf, 0, NULL, NULL); } + +void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl) +{ + char header[256]; + char value[256]; + int level; + + level = print_package_info(cpu, outf); + + snprintf(header, sizeof(header), "get-trl"); + format_and_print(outf, level + 1, header, NULL); + + snprintf(header, sizeof(header), "trl"); + snprintf(value, sizeof(value), "0x%llx", trl); + format_and_print(outf, level + 2, header, value); + + format_and_print(outf, level, NULL, NULL); +} diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h index 60db0bb084d5..0cac6c54be87 100644 --- a/tools/power/x86/intel-speed-select/isst.h +++ b/tools/power/x86/intel-speed-select/isst.h @@ -228,6 +228,7 @@ extern void isst_fact_display_information(int cpu, FILE *outf, int level, int fact_bucket, int fact_avx, struct isst_fact_info *fact_info); extern int isst_set_trl(int cpu, unsigned long long trl); +extern int isst_get_trl(int cpu, unsigned long long *trl); extern int isst_set_trl_from_current_tdp(int cpu, unsigned long long trl); extern int isst_get_config_tdp_lock_status(int cpu); @@ -256,4 +257,5 @@ extern int get_cpufreq_base_freq(int cpu); extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap); extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); extern int is_skx_based_platform(void); +extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl); #endif