Pull mfd updates from Lee Jones: - Fix race in device_node_get_regmap() using more extensive locking - Remove unused platform driver support for syscon - Allow syscon nodes to be registered without a "syscon" compatible string - Make `platform_data` pointer const in struct mfd_cell - Revert support for multiple AXP PMICs to avoid regressions - Increase SoundWire attach timeout and use gpiod_set_raw() for GPIO operation - Store the result from fault_log() for use by other sub-components - Fix an invalid regmap-config max_register value - Add another Gemini Lake ISA bridge PCI device ID - Use devm_register_power_off_handler() to simplify code - Add support for QNAP microcontroller units, including LEDs, input, and hwmon - Use MFD_CELL macros and remove unused code - Add support for AAEON UP board FPGA - Remove unused includes - Fix various typos and compatibility issues in multiple bindings - Add new bindings for rk3562 QoS, LED1202, and qcom,tcsr-ipq5424 - Convert several bindings to YAML schema - Update sprd,sc2731 bindings to reference sprd,sc2731-efuse bindings directly - Fix rohm,bd71815 bindings by correcting resistor values and typos - Documentation improvements: - Add documentation for LED1202 and qnap-mcu-hwmon - Adjust the file entry for the qnap-mcu header in MAINTAINERS * tag 'mfd-next-6.14' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (35 commits) MAINTAINERS: Adjust the file entry for the qnap-mcu header dt-bindings: mfd: syscon: Fix ti,j784s4-acspcie-proxy-ctrl compatible dt-bindings: mfd: syscon: Fix al,alpine-sysfabric-service compatible Revert "mfd: axp20x: Allow multiple regulators" dt-bindings: mfd: syscon: Add rk3562 QoS register compatible mfd: syscon: Allow syscon nodes without a "syscon" compatible mfd: syscon: Remove the platform driver support mfd: syscon: Fix race in device_node_get_regmap() dt-bindings: mfd: atmel: Convert to YAML schema dt-bindings: mfd: atmel,at91sam9260: Convert to YAML schema dt-bindings: mfd: sprd,sc2731: Reference sprd,sc2731-efuse bindings mfd: tps65219: Remove unused macros & add regmap.h mfd: tps65219: Use MFD_CELL macros leds: Add LED1202 I2C driver dt-bindings: leds: Add LED1202 LED Controller Documentation:leds: Add leds-st1202.rst mfd: Add support for AAEON UP board FPGA mfd: da9052: Store result from fault_log mfd: intel_soc_pmic_chtdc_ti: Fix invalid regmap-config max_register value mfd: cs42l43: Use devres for remove as well ...
Unified · Split
| @@ -0,0 +1,132 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause | ||
| 2 | %YAML 1.2 | ||
| 3 | --- | ||
| 4 | $id: http://devicetree.org/schemas/leds/st,led1202.yaml# | ||
| 5 | $schema: http://devicetree.org/meta-schemas/core.yaml# | ||
| 6 | |||
| 7 | title: ST LED1202 LED controllers | ||
| 8 | |||
| 9 | maintainers: | ||
| 10 | - Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk> | ||
| 11 | |||
| 12 | description: | | ||
| 13 | The LED1202 is a 12-channel low quiescent current LED controller | ||
| 14 | programmable via I2C; The output current can be adjusted separately | ||
| 15 | for each channel by 8-bit analog and 12-bit digital dimming control. | ||
| 16 | Datasheet available at | ||
| 17 | https://www.st.com/en/power-management/led1202.html | ||
| 18 | |||
| 19 | properties: | ||
| 20 | compatible: | ||
| 21 | const: st,led1202 | ||
| 22 | |||
| 23 | reg: | ||
| 24 | maxItems: 1 | ||
| 25 | |||
| 26 | "#address-cells": | ||
| 27 | const: 1 | ||
| 28 | |||
| 29 | "#size-cells": | ||
| 30 | const: 0 | ||
| 31 | |||
| 32 | patternProperties: | ||
| 33 | "^led@[0-9a-f]$": | ||
| 34 | type: object | ||
| 35 | $ref: common.yaml# | ||
| 36 | unevaluatedProperties: false | ||
| 37 | |||
| 38 | properties: | ||
| 39 | reg: | ||
| 40 | minimum: 0 | ||
| 41 | maximum: 11 | ||
| 42 | |||
| 43 | required: | ||
| 44 | - reg | ||
| 45 | |||
| 46 | required: | ||
| 47 | - compatible | ||
| 48 | - reg | ||
| 49 | - "#address-cells" | ||
| 50 | - "#size-cells" | ||
| 51 | |||
| 52 | additionalProperties: false | ||
| 53 | |||
| 54 | examples: | ||
| 55 | - | | ||
| 56 | #include <dt-bindings/leds/common.h> | ||
| 57 | |||
| 58 | i2c { | ||
| 59 | #address-cells = <1>; | ||
| 60 | #size-cells = <0>; | ||
| 61 | |||
| 62 | led-controller@58 { | ||
| 63 | compatible = "st,led1202"; | ||
| 64 | reg = <0x58>; | ||
| 65 | #address-cells = <1>; | ||
| 66 | #size-cells = <0>; | ||
| 67 | |||
| 68 | led@0 { | ||
| 69 | reg = <0x0>; | ||
| 70 | function = LED_FUNCTION_STATUS; | ||
| 71 | color = <LED_COLOR_ID_RED>; | ||
| 72 | function-enumerator = <1>; | ||
| 73 | }; | ||
| 74 | |||
| 75 | led@1 { | ||
| 76 | reg = <0x1>; | ||
| 77 | function = LED_FUNCTION_STATUS; | ||
| 78 | color = <LED_COLOR_ID_GREEN>; | ||
| 79 | function-enumerator = <2>; | ||
| 80 | }; | ||
| 81 | |||
| 82 | led@2 { | ||
| 83 | reg = <0x2>; | ||
| 84 | function = LED_FUNCTION_STATUS; | ||
| 85 | color = <LED_COLOR_ID_BLUE>; | ||
| 86 | function-enumerator = <3>; | ||
| 87 | }; | ||
| 88 | |||
| 89 | led@3 { | ||
| 90 | reg = <0x3>; | ||
| 91 | function = LED_FUNCTION_STATUS; | ||
| 92 | color = <LED_COLOR_ID_RED>; | ||
| 93 | function-enumerator = <4>; | ||
| 94 | }; | ||
| 95 | |||
| 96 | led@4 { | ||
| 97 | reg = <0x4>; | ||
| 98 | function = LED_FUNCTION_STATUS; | ||
| 99 | color = <LED_COLOR_ID_GREEN>; | ||
| 100 | function-enumerator = <5>; | ||
| 101 | }; | ||
| 102 | |||
| 103 | led@5 { | ||
| 104 | reg = <0x5>; | ||
| 105 | function = LED_FUNCTION_STATUS; | ||
| 106 | color = <LED_COLOR_ID_BLUE>; | ||
| 107 | function-enumerator = <6>; | ||
| 108 | }; | ||
| 109 | |||
| 110 | led@6 { | ||
| 111 | reg = <0x6>; | ||
| 112 | function = LED_FUNCTION_STATUS; | ||
| 113 | color = <LED_COLOR_ID_RED>; | ||
| 114 | function-enumerator = <7>; | ||
| 115 | }; | ||
| 116 | |||
| 117 | led@7 { | ||
| 118 | reg = <0x7>; | ||
| 119 | function = LED_FUNCTION_STATUS; | ||
| 120 | color = <LED_COLOR_ID_GREEN>; | ||
| 121 | function-enumerator = <8>; | ||
| 122 | }; | ||
| 123 | |||
| 124 | led@8 { | ||
| 125 | reg = <0x8>; | ||
| 126 | function = LED_FUNCTION_STATUS; | ||
| 127 | color = <LED_COLOR_ID_BLUE>; | ||
| 128 | function-enumerator = <9>; | ||
| 129 | }; | ||
| 130 | }; | ||
| 131 | }; | ||
| 132 | ... | ||
| @@ -0,0 +1,44 @@ | |||
| 1 | # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | ||
| 2 | %YAML 1.2 | ||
| 3 | --- | ||
| 4 | $id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-gpbr.yaml# | ||
| 5 | $schema: http://devicetree.org/meta-schemas/core.yaml# | ||
| 6 | |||
| 7 | title: Microchip AT91 General Purpose Backup Registers | ||
| 8 | |||
| 9 | maintainers: | ||
| 10 | - Nicolas Ferre <nicolas.ferre@microchip.com> | ||
| 11 | |||
| 12 | description: | ||
| 13 | The system controller embeds 256 bits of General Purpose Backup | ||
| 14 | registers organized as 8 32-bit registers. | ||
| 15 | |||
| 16 | properties: | ||
| 17 | compatible: | ||
| 18 | oneOf: | ||
| 19 | - items: | ||
| 20 | - enum: | ||
| 21 | - atmel,at91sam9260-gpbr | ||
| 22 | - const: syscon | ||
| 23 | - items: | ||
| 24 | - enum: | ||
| 25 | - microchip,sam9x60-gpbr | ||
| 26 | - microchip,sam9x7-gpbr | ||
| 27 | - const: atmel,at91sam9260-gpbr | ||
| 28 | - const: syscon | ||
| 29 | |||
| 30 | reg: | ||
| 31 | maxItems: 1 | ||
| 32 | |||
| 33 | required: | ||
| 34 | - compatible | ||
| 35 | - reg | ||
| 36 | |||
| 37 | additionalProperties: false | ||
| 38 | |||
| 39 | examples: | ||
| 40 | - | | ||
| 41 | syscon@fffffd50 { | ||
| 42 | compatible = "atmel,at91sam9260-gpbr", "syscon"; | ||
| 43 | reg = <0xfffffd50 0x10>; | ||
| 44 | }; | ||
| @@ -0,0 +1,52 @@ | |||
| 1 | # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | ||
| 2 | %YAML 1.2 | ||
| 3 | --- | ||
| 4 | $id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-matrix.yaml# | ||
| 5 | $schema: http://devicetree.org/meta-schemas/core.yaml# | ||
| 6 | |||
| 7 | title: Microchip AT91 Bus Matrix | ||
| 8 | |||
| 9 | maintainers: | ||
| 10 | - Nicolas Ferre <nicolas.ferre@microchip.com> | ||
| 11 | |||
| 12 | description: | ||
| 13 | The Bus Matrix (MATRIX) implements a multi-layer AHB, based on the | ||
| 14 | AHB-Lite protocol, that enables parallel access paths between multiple | ||
| 15 | masters and slaves in a system, thus increasing the overall bandwidth. | ||
| 16 | |||
| 17 | properties: | ||
| 18 | compatible: | ||
| 19 | oneOf: | ||
| 20 | - items: | ||
| 21 | - enum: | ||
| 22 | - atmel,at91sam9260-matrix | ||
| 23 | - atmel,at91sam9261-matrix | ||
| 24 | - atmel,at91sam9263-matrix | ||
| 25 | - atmel,at91sam9rl-matrix | ||
| 26 | - atmel,at91sam9g45-matrix | ||
| 27 | - atmel,at91sam9n12-matrix | ||
| 28 | - atmel,at91sam9x5-matrix | ||
| 29 | - atmel,sama5d3-matrix | ||
| 30 | - const: syscon | ||
| 31 | - items: | ||
| 32 | - enum: | ||
| 33 | - microchip,sam9x60-matrix | ||
| 34 | - microchip,sam9x7-matrix | ||
| 35 | - const: atmel,at91sam9x5-matrix | ||
| 36 | - const: syscon | ||
| 37 | |||
| 38 | reg: | ||
| 39 | maxItems: 1 | ||
| 40 | |||
| 41 | required: | ||
| 42 | - compatible | ||
| 43 | - reg | ||
| 44 | |||
| 45 | additionalProperties: false | ||
| 46 | |||
| 47 | examples: | ||
| 48 | - | | ||
| 49 | syscon@ffffec00 { | ||
| 50 | compatible = "atmel,sama5d3-matrix", "syscon"; | ||
| 51 | reg = <0xffffec00 0x200>; | ||
| 52 | }; | ||
| @@ -1,18 +0,0 @@ | |||
| 1 | * Device tree bindings for Atmel GPBR (General Purpose Backup Registers) | ||
| 2 | |||
| 3 | The GPBR are a set of battery-backed registers. | ||
| 4 | |||
| 5 | Required properties: | ||
| 6 | - compatible: Should be one of the following: | ||
| 7 | "atmel,at91sam9260-gpbr", "syscon" | ||
| 8 | "microchip,sam9x60-gpbr", "syscon" | ||
| 9 | "microchip,sam9x7-gpbr", "microchip,sam9x60-gpbr", "syscon" | ||
| 10 | - reg: contains offset/length value of the GPBR memory | ||
| 11 | region. | ||
| 12 | |||
| 13 | Example: | ||
| 14 | |||
| 15 | gpbr: gpbr@fffffd50 { | ||
| 16 | compatible = "atmel,at91sam9260-gpbr", "syscon"; | ||
| 17 | reg = <0xfffffd50 0x10>; | ||
| 18 | }; | ||
| @@ -1,26 +0,0 @@ | |||
| 1 | * Device tree bindings for Atmel Bus Matrix | ||
| 2 | |||
| 3 | The Bus Matrix registers are used to configure Atmel SoCs internal bus | ||
| 4 | behavior (master/slave priorities, undefined burst length type, ...) | ||
| 5 | |||
| 6 | Required properties: | ||
| 7 | - compatible: Should be one of the following | ||
| 8 | "atmel,at91sam9260-matrix", "syscon" | ||
| 9 | "atmel,at91sam9261-matrix", "syscon" | ||
| 10 | "atmel,at91sam9263-matrix", "syscon" | ||
| 11 | "atmel,at91sam9rl-matrix", "syscon" | ||
| 12 | "atmel,at91sam9g45-matrix", "syscon" | ||
| 13 | "atmel,at91sam9n12-matrix", "syscon" | ||
| 14 | "atmel,at91sam9x5-matrix", "syscon" | ||
| 15 | "atmel,sama5d3-matrix", "syscon" | ||
| 16 | "microchip,sam9x60-matrix", "syscon" | ||
| 17 | "microchip,sam9x7-matrix", "atmel,at91sam9x5-matrix", "syscon" | ||
| 18 | - reg: Contains offset/length value of the Bus Matrix | ||
| 19 | memory region. | ||
| 20 | |||
| 21 | Example: | ||
| 22 | |||
| 23 | matrix: matrix@ffffec00 { | ||
| 24 | compatible = "atmel,sama5d3-matrix", "syscon"; | ||
| 25 | reg = <0xffffec00 0x200>; | ||
| 26 | }; | ||
| @@ -42,6 +42,7 @@ properties: · 41 unmodified lines | |||
| 42 | - qcom,tcsr-apq8064 | 42 | - qcom,tcsr-apq8064 |
| 43 | - qcom,tcsr-apq8084 | 43 | - qcom,tcsr-apq8084 |
| 44 | - qcom,tcsr-ipq5332 | 44 | - qcom,tcsr-ipq5332 |
| 45 | - qcom,tcsr-ipq5424 | ||
| 45 | - qcom,tcsr-ipq6018 | 46 | - qcom,tcsr-ipq6018 |
| 46 | - qcom,tcsr-ipq8064 | 47 | - qcom,tcsr-ipq8064 |
| 47 | - qcom,tcsr-ipq8074 | 48 | - qcom,tcsr-ipq8074 |
| @@ -0,0 +1,42 @@ | |||
| 1 | # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | ||
| 2 | %YAML 1.2 | ||
| 3 | --- | ||
| 4 | $id: http://devicetree.org/schemas/mfd/qnap,ts433-mcu.yaml# | ||
| 5 | $schema: http://devicetree.org/meta-schemas/core.yaml# | ||
| 6 | |||
| 7 | title: QNAP NAS on-board Microcontroller | ||
| 8 | |||
| 9 | maintainers: | ||
| 10 | - Heiko Stuebner <heiko@sntech.de> | ||
| 11 | |||
| 12 | description: | ||
| 13 | QNAP embeds a microcontroller on their NAS devices adding system feature | ||
| 14 | as PWM Fan control, additional LEDs, power button status and more. | ||
| 15 | |||
| 16 | properties: | ||
| 17 | compatible: | ||
| 18 | enum: | ||
| 19 | - qnap,ts433-mcu | ||
| 20 | |||
| 21 | patternProperties: | ||
| 22 | "^fan-[0-9]+$": | ||
| 23 | $ref: /schemas/hwmon/fan-common.yaml# | ||
| 24 | unevaluatedProperties: false | ||
| 25 | |||
| 26 | required: | ||
| 27 | - compatible | ||
| 28 | |||
| 29 | additionalProperties: false | ||
| 30 | |||
| 31 | examples: | ||
| 32 | - | | ||
| 33 | uart { | ||
| 34 | mcu { | ||
| 35 | compatible = "qnap,ts433-mcu"; | ||
| 36 | |||
| 37 | fan-0 { | ||
| 38 | #cooling-cells = <2>; | ||
| 39 | cooling-levels = <0 64 89 128 166 204 221 238>; | ||
| 40 | }; | ||
| 41 | }; | ||
| 42 | }; | ||
| @@ -50,15 +50,15 @@ properties: · 49 unmodified lines | |||
| 50 | minimum: 0 | 50 | minimum: 0 |
| 51 | maximum: 1 | 51 | maximum: 1 |
| 52 | 52 | ||
| 53 | rohm,charger-sense-resistor-ohms: | 53 | rohm,charger-sense-resistor-micro-ohms: |
| 54 | minimum: 10000000 | 54 | minimum: 10000 |
| 55 | maximum: 50000000 | 55 | maximum: 50000 |
| 56 | description: | | 56 | description: | |
| 57 | BD71827 and BD71828 have SAR ADC for measuring charging currents. | 57 | BD71815 has SAR ADC for measuring charging currents. External sense |
| 58 | External sense resistor (RSENSE in data sheet) should be used. If | 58 | resistor (RSENSE in data sheet) should be used. If something other |
| 59 | something other but 30MOhm resistor is used the resistance value | 59 | but a 30 mOhm resistor is used the resistance value should be given |
| 60 | should be given here in Ohms. | 60 | here in micro Ohms. |
| 61 | default: 30000000 | 61 | default: 30000 |
| 62 | 62 | ||
| 63 | regulators: | 63 | regulators: |
| 64 | $ref: /schemas/regulator/rohm,bd71815-regulator.yaml | 64 | $ref: /schemas/regulator/rohm,bd71815-regulator.yaml |
| @@ -67,7 +67,7 @@ properties: · 2 unmodified lines | |||
| 67 | 67 | ||
| 68 | gpio-reserved-ranges: | 68 | gpio-reserved-ranges: |
| 69 | description: | | 69 | description: | |
| 70 | Usage of BD71828 GPIO pins can be changed via OTP. This property can be | 70 | Usage of BD71815 GPIO pins can be changed via OTP. This property can be |
| 71 | used to mark the pins which should not be configured for GPIO. Please see | 71 | used to mark the pins which should not be configured for GPIO. Please see |
| 72 | the ../gpio/gpio.txt for more information. | 72 | the ../gpio/gpio.txt for more information. |
| 73 | 73 | ||
| @@ -113,7 +113,7 @@ examples: · 39 unmodified lines | |||
| 113 | gpio-controller; | 113 | gpio-controller; |
| 114 | #gpio-cells = <2>; | 114 | #gpio-cells = <2>; |
| 115 | 115 | ||
| 116 | rohm,charger-sense-resistor-ohms = <10000000>; | 116 | rohm,charger-sense-resistor-micro-ohms = <10000>; |
| 117 | 117 | ||
| 118 | regulators { | 118 | regulators { |
| 119 | buck1: buck1 { | 119 | buck1: buck1 { |
| @@ -67,15 +67,7 @@ patternProperties: · 66 unmodified lines | |||
| 67 | 67 | ||
| 68 | "^efuse@[0-9a-f]+$": | 68 | "^efuse@[0-9a-f]+$": |
| 69 | type: object | 69 | type: object |
| 70 | additionalProperties: true | 70 | $ref: /schemas/nvmem/sprd,sc2731-efuse.yaml# |
| 71 | properties: | ||
| 72 | compatible: | ||
| 73 | enum: | ||
| 74 | - sprd,sc2720-efuse | ||
| 75 | - sprd,sc2721-efuse | ||
| 76 | - sprd,sc2723-efuse | ||
| 77 | - sprd,sc2730-efuse | ||
| 78 | - sprd,sc2731-efuse | ||
| 79 | 71 | ||
| 80 | "^fuel-gauge@[0-9a-f]+$": | 72 | "^fuel-gauge@[0-9a-f]+$": |
| 81 | type: object | 73 | type: object |
| @@ -199,7 +191,7 @@ examples: · 117 unmodified lines | |||
| 199 | }; | 191 | }; |
| 200 | }; | 192 | }; |
| 201 | 193 | ||
| 202 | adc@480 { | 194 | pmic_adc: adc@480 { |
| 203 | compatible = "sprd,sc2731-adc"; | 195 | compatible = "sprd,sc2731-adc"; |
| 204 | reg = <0x480>; | 196 | reg = <0x480>; |
| 205 | interrupt-parent = <&sc2731_pmic>; | 197 | interrupt-parent = <&sc2731_pmic>; |
| @@ -27,7 +27,7 @@ select: · 26 unmodified lines | |||
| 27 | compatible: | 27 | compatible: |
| 28 | contains: | 28 | contains: |
| 29 | enum: | 29 | enum: |
| 30 | - al,alpine-sysfabric-servic | 30 | - al,alpine-sysfabric-service |
| 31 | - allwinner,sun8i-a83t-system-controller | 31 | - allwinner,sun8i-a83t-system-controller |
| 32 | - allwinner,sun8i-h3-system-controller | 32 | - allwinner,sun8i-h3-system-controller |
| 33 | - allwinner,sun8i-v3s-system-controller | 33 | - allwinner,sun8i-v3s-system-controller |
| @@ -103,6 +103,7 @@ select: · 69 unmodified lines | |||
| 103 | - rockchip,rk3288-qos | 103 | - rockchip,rk3288-qos |
| 104 | - rockchip,rk3368-qos | 104 | - rockchip,rk3368-qos |
| 105 | - rockchip,rk3399-qos | 105 | - rockchip,rk3399-qos |
| 106 | - rockchip,rk3562-qos | ||
| 106 | - rockchip,rk3568-qos | 107 | - rockchip,rk3568-qos |
| 107 | - rockchip,rk3576-qos | 108 | - rockchip,rk3576-qos |
| 108 | - rockchip,rk3588-qos | 109 | - rockchip,rk3588-qos |
| @@ -201,6 +202,7 @@ properties: · 92 unmodified lines | |||
| 201 | - rockchip,rk3288-qos | 202 | - rockchip,rk3288-qos |
| 202 | - rockchip,rk3368-qos | 203 | - rockchip,rk3368-qos |
| 203 | - rockchip,rk3399-qos | 204 | - rockchip,rk3399-qos |
| 205 | - rockchip,rk3562-qos | ||
| 204 | - rockchip,rk3568-qos | 206 | - rockchip,rk3568-qos |
| 205 | - rockchip,rk3576-qos | 207 | - rockchip,rk3576-qos |
| 206 | - rockchip,rk3588-qos | 208 | - rockchip,rk3588-qos |
| @@ -213,6 +215,7 @@ properties: · 6 unmodified lines | |||
| 213 | - ti,am625-dss-oldi-io-ctrl | 215 | - ti,am625-dss-oldi-io-ctrl |
| 214 | - ti,am62p-cpsw-mac-efuse | 216 | - ti,am62p-cpsw-mac-efuse |
| 215 | - ti,am654-dss-oldi-io-ctrl | 217 | - ti,am654-dss-oldi-io-ctrl |
| 218 | - ti,j784s4-acspcie-proxy-ctrl | ||
| 216 | - ti,j784s4-pcie-ctrl | 219 | - ti,j784s4-pcie-ctrl |
| 217 | - ti,keystone-pllctrl | 220 | - ti,keystone-pllctrl |
| 218 | - const: syscon | 221 | - const: syscon |
| @@ -36,33 +36,4 @@ allOf: · 35 unmodified lines | |||
| 36 | - $ref: nvmem-deprecated-cells.yaml# | 36 | - $ref: nvmem-deprecated-cells.yaml# |
| 37 | 37 | ||
| 38 | unevaluatedProperties: false | 38 | unevaluatedProperties: false |
| 39 | |||
| 40 | examples: | ||
| 41 | - | | ||
| 42 | pmic { | ||
| 43 | #address-cells = <1>; | ||
| 44 | #size-cells = <0>; | ||
| 45 | |||
| 46 | efuse@380 { | ||
| 47 | compatible = "sprd,sc2731-efuse"; | ||
| 48 | reg = <0x380>; | ||
| 49 | hwlocks = <&hwlock 12>; | ||
| 50 | #address-cells = <1>; | ||
| 51 | #size-cells = <1>; | ||
| 52 | |||
| 53 | /* Data cells */ | ||
| 54 | fgu_calib: calib@6 { | ||
| 55 | reg = <0x6 0x2>; | ||
| 56 | bits = <0 9>; | ||
| 57 | }; | ||
| 58 | |||
| 59 | adc_big_scale: calib@24 { | ||
| 60 | reg = <0x24 0x2>; | ||
| 61 | }; | ||
| 62 | |||
| 63 | adc_small_scale: calib@26 { | ||
| 64 | reg = <0x26 0x2>; | ||
| 65 | }; | ||
| 66 | }; | ||
| 67 | }; | ||
| 68 | ... | 39 | ... |
| @@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers · 200 unmodified lines | |||
| 201 | pxe1610 | 201 | pxe1610 |
| 202 | pwm-fan | 202 | pwm-fan |
| 203 | q54sj108a2 | 203 | q54sj108a2 |
| 204 | qnap-mcu-hwmon | ||
| 204 | raspberrypi-hwmon | 205 | raspberrypi-hwmon |
| 205 | sbrmi | 206 | sbrmi |
| 206 | sbtsi_temp | 207 | sbtsi_temp |
| @@ -0,0 +1,27 @@ | |||
| 1 | .. SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | |||
| 3 | Kernel driver qnap-mcu-hwmon | ||
| 4 | ============================ | ||
| 5 | |||
| 6 | This driver enables the use of the hardware monitoring and fan control | ||
| 7 | of the MCU used on some QNAP network attached storage devices. | ||
| 8 | |||
| 9 | Author: Heiko Stuebner <heiko@sntech.de> | ||
| 10 | |||
| 11 | Description | ||
| 12 | ----------- | ||
| 13 | |||
| 14 | The driver implements a simple interface for driving the fan controlled by | ||
| 15 | setting its PWM output value and exposes the fan rpm and case-temperature | ||
| 16 | to user space through hwmon's sysfs interface. | ||
| 17 | |||
| 18 | The fan rotation speed returned via the optional 'fan1_input' is calculated | ||
| 19 | inside the MCU device. | ||
| 20 | |||
| 21 | The driver provides the following sensor accesses in sysfs: | ||
| 22 | |||
| 23 | =============== ======= ======================================================= | ||
| 24 | fan1_input ro fan tachometer speed in RPM | ||
| 25 | pwm1 rw relative speed (0-255), 255=max. speed. | ||
| 26 | temp1_input ro Measured temperature in millicelsius | ||
| 27 | =============== ======= ======================================================= | ||
| @@ -28,4 +28,5 @@ LEDs · 27 unmodified lines | |||
| 28 | leds-mlxcpld | 28 | leds-mlxcpld |
| 29 | leds-mt6370-rgb | 29 | leds-mt6370-rgb |
| 30 | leds-sc27xx | 30 | leds-sc27xx |
| 31 | leds-st1202.rst | ||
| 31 | leds-qcom-lpg | 32 | leds-qcom-lpg |
| @@ -0,0 +1,34 @@ | |||
| 1 | .. SPDX-License-Identifier: GPL-2.0 | ||
| 2 | |||
| 3 | ============================================ | ||
| 4 | Kernel driver for STMicroelectronics LED1202 | ||
| 5 | ============================================ | ||
| 6 | |||
| 7 | /sys/class/leds/<led>/hw_pattern | ||
| 8 | -------------------------------- | ||
| 9 | |||
| 10 | Specify a hardware pattern for the ST1202 LED. The LED controller | ||
| 11 | implements 12 low-side current generators with independent dimming | ||
| 12 | control. Internal volatile memory allows the user to store up to 8 | ||
| 13 | different patterns. Each pattern is a particular output configuration | ||
| 14 | in terms of PWM duty-cycle and duration (ms). | ||
| 15 | |||
| 16 | To be compatible with the hardware pattern format, maximum 8 tuples of | ||
| 17 | brightness (PWM) and duration must be written to hw_pattern. | ||
| 18 | |||
| 19 | - Min pattern duration: 22 ms | ||
| 20 | - Max pattern duration: 5660 ms | ||
| 21 | |||
| 22 | The format of the hardware pattern values should be: | ||
| 23 | "brightness duration brightness duration ..." | ||
| 24 | |||
| 25 | /sys/class/leds/<led>/repeat | ||
| 26 | ---------------------------- | ||
| 27 | |||
| 28 | Specify a pattern repeat number, which is common for all channels. | ||
| 29 | Default is 1; negative numbers and 0 are invalid. | ||
| 30 | |||
| 31 | This file will always return the originally written repeat number. | ||
| 32 | |||
| 33 | When the 255 value is written to it, all patterns will repeat | ||
| 34 | indefinitely. | ||
| @@ -19158,6 +19158,15 @@ L: linux-media@vger.kernel.org · 19157 unmodified lines | |||
| 19158 | S: Odd Fixes | 19158 | S: Odd Fixes |
| 19159 | F: drivers/media/tuners/qm1d1c0042* | 19159 | F: drivers/media/tuners/qm1d1c0042* |
| 19160 | 19160 | ||
| 19161 | QNAP MCU DRIVER | ||
| 19162 | M: Heiko Stuebner <heiko@sntech.de> | ||
| 19163 | S: Maintained | ||
| 19164 | F: drivers/hwmon/qnap-mcu-hwmon.c | ||
| 19165 | F: drivers/input/misc/qnap-mcu-input.c | ||
| 19166 | F: drivers/leds/leds-qnap-mcu.c | ||
| 19167 | F: drivers/mfd/qnap-mcu.c | ||
| 19168 | F: include/linux/mfd/qnap-mcu.h | ||
| 19169 | |||
| 19161 | QNX4 FILESYSTEM | 19170 | QNX4 FILESYSTEM |
| 19162 | M: Anders Larsen <al@alarsen.net> | 19171 | M: Anders Larsen <al@alarsen.net> |
| 19163 | S: Maintained | 19172 | S: Maintained |
| @@ -730,23 +730,30 @@ err_stop_hw: · 729 unmodified lines | |||
| 730 | return ret; | 730 | return ret; |
| 731 | } | 731 | } |
| 732 | 732 | ||
| 733 | static int sensor_hub_finalize_pending_fn(struct device *dev, void *data) | ||
| 734 | { | ||
| 735 | struct hid_sensor_hub_device *hsdev = dev->platform_data; | ||
| 736 | |||
| 737 | if (hsdev->pending.status) | ||
| 738 | complete(&hsdev->pending.ready); | ||
| 739 | |||
| 740 | return 0; | ||
| 741 | } | ||
| 742 | |||
| 733 | static void sensor_hub_remove(struct hid_device *hdev) | 743 | static void sensor_hub_remove(struct hid_device *hdev) |
| 734 | { | 744 | { |
| 735 | struct sensor_hub_data *data = hid_get_drvdata(hdev); | 745 | struct sensor_hub_data *data = hid_get_drvdata(hdev); |
| 736 | unsigned long flags; | 746 | unsigned long flags; |
| 737 | int i; | ||
| 738 | 747 | ||
| 739 | hid_dbg(hdev, " hardware removed\n"); | 748 | hid_dbg(hdev, " hardware removed\n"); |
| 740 | hid_hw_close(hdev); | 749 | hid_hw_close(hdev); |
| 741 | hid_hw_stop(hdev); | 750 | hid_hw_stop(hdev); |
| 751 | |||
| 742 | spin_lock_irqsave(&data->lock, flags); | 752 | spin_lock_irqsave(&data->lock, flags); |
| 743 | for (i = 0; i < data->hid_sensor_client_cnt; ++i) { | 753 | device_for_each_child(&hdev->dev, NULL, |
| 744 | struct hid_sensor_hub_device *hsdev = | 754 | sensor_hub_finalize_pending_fn); |
| 745 | data->hid_sensor_hub_client_devs[i].platform_data; | ||
| 746 | if (hsdev->pending.status) | ||
| 747 | complete(&hsdev->pending.ready); | ||
| 748 | } | ||
| 749 | spin_unlock_irqrestore(&data->lock, flags); | 755 | spin_unlock_irqrestore(&data->lock, flags); |
| 756 | |||
| 750 | mfd_remove_devices(&hdev->dev); | 757 | mfd_remove_devices(&hdev->dev); |
| 751 | mutex_destroy(&data->mutex); | 758 | mutex_destroy(&data->mutex); |
| 752 | } | 759 | } |
| @@ -1822,6 +1822,18 @@ config SENSORS_PWM_FAN · 1821 unmodified lines | |||
| 1822 | This driver can also be built as a module. If so, the module | 1822 | This driver can also be built as a module. If so, the module |
| 1823 | will be called pwm-fan. | 1823 | will be called pwm-fan. |
| 1824 | 1824 | ||
| 1825 | config SENSORS_QNAP_MCU_HWMON | ||
| 1826 | tristate "QNAP MCU hardware monitoring" | ||
| 1827 | depends on MFD_QNAP_MCU | ||
| 1828 | depends on THERMAL || THERMAL=n | ||
| 1829 | help | ||
| 1830 | Say yes here to enable support for fan and temperature sensor | ||
| 1831 | connected to a QNAP MCU, as found in a number of QNAP network | ||
| 1832 | attached storage devices. | ||
| 1833 | |||
| 1834 | This driver can also be built as a module. If so, the module | ||
| 1835 | will be called qnap-mcu-hwmon. | ||
| 1836 | |||
| 1825 | config SENSORS_RASPBERRYPI_HWMON | 1837 | config SENSORS_RASPBERRYPI_HWMON |
| 1826 | tristate "Raspberry Pi voltage monitor" | 1838 | tristate "Raspberry Pi voltage monitor" |
| 1827 | depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) | 1839 | depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) |
| @@ -189,6 +189,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o · 188 unmodified lines | |||
| 189 | obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o | 189 | obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o |
| 190 | obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o | 190 | obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o |
| 191 | obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o | 191 | obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o |
| 192 | obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o | ||
| 192 | obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o | 193 | obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o |
| 193 | obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o | 194 | obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o |
| 194 | obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o | 195 | obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o |
| @@ -0,0 +1,364 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | |||
| 3 | /* | ||
| 4 | * Driver for hwmon elements found on QNAP-MCU devices | ||
| 5 | * | ||
| 6 | * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/fwnode.h> | ||
| 10 | #include <linux/hwmon.h> | ||
| 11 | #include <linux/mfd/qnap-mcu.h> | ||
| 12 | #include <linux/module.h> | ||
| 13 | #include <linux/platform_device.h> | ||
| 14 | #include <linux/property.h> | ||
| 15 | #include <linux/thermal.h> | ||
| 16 | |||
| 17 | struct qnap_mcu_hwmon { | ||
| 18 | struct qnap_mcu *mcu; | ||
| 19 | struct device *dev; | ||
| 20 | |||
| 21 | unsigned int pwm_min; | ||
| 22 | unsigned int pwm_max; | ||
| 23 | |||
| 24 | struct fwnode_handle *fan_node; | ||
| 25 | unsigned int fan_state; | ||
| 26 | unsigned int fan_max_state; | ||
| 27 | unsigned int *fan_cooling_levels; | ||
| 28 | |||
| 29 | struct thermal_cooling_device *cdev; | ||
| 30 | struct hwmon_chip_info info; | ||
| 31 | }; | ||
| 32 | |||
| 33 | static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm) | ||
| 34 | { | ||
| 35 | static const u8 cmd[] = { '@', 'F', 'A' }; | ||
| 36 | u8 reply[6]; | ||
| 37 | int ret; | ||
| 38 | |||
| 39 | /* poll the fan rpm */ | ||
| 40 | ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); | ||
| 41 | if (ret) | ||
| 42 | return ret; | ||
| 43 | |||
| 44 | /* First 2 bytes must mirror the sent command */ | ||
| 45 | if (memcmp(cmd, reply, 2)) | ||
| 46 | return -EIO; | ||
| 47 | |||
| 48 | return reply[4] * 30; | ||
| 49 | } | ||
| 50 | |||
| 51 | static int qnap_mcu_hwmon_get_pwm(struct qnap_mcu_hwmon *hwm) | ||
| 52 | { | ||
| 53 | static const u8 cmd[] = { '@', 'F', 'Z', '0' }; /* 0 = fan-id? */ | ||
| 54 | u8 reply[4]; | ||
| 55 | int ret; | ||
| 56 | |||
| 57 | /* poll the fan pwm */ | ||
| 58 | ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); | ||
| 59 | if (ret) | ||
| 60 | return ret; | ||
| 61 | |||
| 62 | /* First 3 bytes must mirror the sent command */ | ||
| 63 | if (memcmp(cmd, reply, 3)) | ||
| 64 | return -EIO; | ||
| 65 | |||
| 66 | return reply[3]; | ||
| 67 | } | ||
| 68 | |||
| 69 | static int qnap_mcu_hwmon_set_pwm(struct qnap_mcu_hwmon *hwm, u8 pwm) | ||
| 70 | { | ||
| 71 | const u8 cmd[] = { '@', 'F', 'W', '0', pwm }; /* 0 = fan-id?, pwm 0-255 */ | ||
| 72 | |||
| 73 | /* set the fan pwm */ | ||
| 74 | return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd)); | ||
| 75 | } | ||
| 76 | |||
| 77 | static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm) | ||
| 78 | { | ||
| 79 | static const u8 cmd[] = { '@', 'T', '3' }; | ||
| 80 | u8 reply[4]; | ||
| 81 | int ret; | ||
| 82 | |||
| 83 | /* poll the fan rpm */ | ||
| 84 | ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); | ||
| 85 | if (ret) | ||
| 86 | return ret; | ||
| 87 | |||
| 88 | /* First bytes must mirror the sent command */ | ||
| 89 | if (memcmp(cmd, reply, sizeof(cmd))) | ||
| 90 | return -EIO; | ||
| 91 | |||
| 92 | /* | ||
| 93 | * There is an unknown bit set in bit7. | ||
| 94 | * Bits [6:0] report the actual temperature as returned by the | ||
| 95 | * original qnap firmware-tools, so just drop bit7 for now. | ||
| 96 | */ | ||
| 97 | return (reply[3] & 0x7f) * 1000; | ||
| 98 | } | ||
| 99 | |||
| 100 | static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type, | ||
| 101 | u32 attr, int channel, long val) | ||
| 102 | { | ||
| 103 | struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); | ||
| 104 | |||
| 105 | switch (attr) { | ||
| 106 | case hwmon_pwm_input: | ||
| 107 | if (val < 0 || val > 255) | ||
| 108 | return -EINVAL; | ||
| 109 | |||
| 110 | if (val != 0) | ||
| 111 | val = clamp_val(val, hwm->pwm_min, hwm->pwm_max); | ||
| 112 | |||
| 113 | return qnap_mcu_hwmon_set_pwm(hwm, val); | ||
| 114 | default: | ||
| 115 | return -EOPNOTSUPP; | ||
| 116 | } | ||
| 117 | |||
| 118 | return 0; | ||
| 119 | } | ||
| 120 | |||
| 121 | static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | ||
| 122 | u32 attr, int channel, long *val) | ||
| 123 | { | ||
| 124 | struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); | ||
| 125 | int ret; | ||
| 126 | |||
| 127 | switch (type) { | ||
| 128 | case hwmon_pwm: | ||
| 129 | switch (attr) { | ||
| 130 | case hwmon_pwm_input: | ||
| 131 | ret = qnap_mcu_hwmon_get_pwm(hwm); | ||
| 132 | if (ret < 0) | ||
| 133 | return ret; | ||
| 134 | |||
| 135 | *val = ret; | ||
| 136 | return 0; | ||
| 137 | default: | ||
| 138 | return -EOPNOTSUPP; | ||
| 139 | } | ||
| 140 | case hwmon_fan: | ||
| 141 | ret = qnap_mcu_hwmon_get_rpm(hwm); | ||
| 142 | if (ret < 0) | ||
| 143 | return ret; | ||
| 144 | |||
| 145 | *val = ret; | ||
| 146 | return 0; | ||
| 147 | case hwmon_temp: | ||
| 148 | ret = qnap_mcu_hwmon_get_temp(hwm); | ||
| 149 | if (ret < 0) | ||
| 150 | return ret; | ||
| 151 | |||
| 152 | *val = ret; | ||
| 153 | return 0; | ||
| 154 | default: | ||
| 155 | return -EOPNOTSUPP; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | static umode_t qnap_mcu_hwmon_is_visible(const void *data, | ||
| 160 | enum hwmon_sensor_types type, | ||
| 161 | u32 attr, int channel) | ||
| 162 | { | ||
| 163 | switch (type) { | ||
| 164 | case hwmon_temp: | ||
| 165 | return 0444; | ||
| 166 | |||
| 167 | case hwmon_pwm: | ||
| 168 | return 0644; | ||
| 169 | |||
| 170 | case hwmon_fan: | ||
| 171 | return 0444; | ||
| 172 | |||
| 173 | default: | ||
| 174 | return 0; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = { | ||
| 179 | .is_visible = qnap_mcu_hwmon_is_visible, | ||
| 180 | .read = qnap_mcu_hwmon_read, | ||
| 181 | .write = qnap_mcu_hwmon_write, | ||
| 182 | }; | ||
| 183 | |||
| 184 | /* thermal cooling device callbacks */ | ||
| 185 | static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev, | ||
| 186 | unsigned long *state) | ||
| 187 | { | ||
| 188 | struct qnap_mcu_hwmon *hwm = cdev->devdata; | ||
| 189 | |||
| 190 | if (!hwm) | ||
| 191 | return -EINVAL; | ||
| 192 | |||
| 193 | *state = hwm->fan_max_state; | ||
| 194 | |||
| 195 | return 0; | ||
| 196 | } | ||
| 197 | |||
| 198 | static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev, | ||
| 199 | unsigned long *state) | ||
| 200 | { | ||
| 201 | struct qnap_mcu_hwmon *hwm = cdev->devdata; | ||
| 202 | |||
| 203 | if (!hwm) | ||
| 204 | return -EINVAL; | ||
| 205 | |||
| 206 | *state = hwm->fan_state; | ||
| 207 | |||
| 208 | return 0; | ||
| 209 | } | ||
| 210 | |||
| 211 | static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev, | ||
| 212 | unsigned long state) | ||
| 213 | { | ||
| 214 | struct qnap_mcu_hwmon *hwm = cdev->devdata; | ||
| 215 | int ret; | ||
| 216 | |||
| 217 | if (!hwm || state > hwm->fan_max_state) | ||
| 218 | return -EINVAL; | ||
| 219 | |||
| 220 | if (state == hwm->fan_state) | ||
| 221 | return 0; | ||
| 222 | |||
| 223 | ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->fan_cooling_levels[state]); | ||
| 224 | if (ret) | ||
| 225 | return ret; | ||
| 226 | |||
| 227 | hwm->fan_state = state; | ||
| 228 | |||
| 229 | return ret; | ||
| 230 | } | ||
| 231 | |||
| 232 | static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = { | ||
| 233 | .get_max_state = qnap_mcu_hwmon_get_max_state, | ||
| 234 | .get_cur_state = qnap_mcu_hwmon_get_cur_state, | ||
| 235 | .set_cur_state = qnap_mcu_hwmon_set_cur_state, | ||
| 236 | }; | ||
| 237 | |||
| 238 | static void devm_fan_node_release(void *data) | ||
| 239 | { | ||
| 240 | struct qnap_mcu_hwmon *hwm = data; | ||
| 241 | |||
| 242 | fwnode_handle_put(hwm->fan_node); | ||
| 243 | } | ||
| 244 | |||
| 245 | static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm) | ||
| 246 | { | ||
| 247 | struct fwnode_handle *fwnode; | ||
| 248 | int num, i, ret; | ||
| 249 | |||
| 250 | fwnode = device_get_named_child_node(dev->parent, "fan-0"); | ||
| 251 | if (!fwnode) | ||
| 252 | return 0; | ||
| 253 | |||
| 254 | /* if we found the fan-node, we're keeping it until device-unbind */ | ||
| 255 | hwm->fan_node = fwnode; | ||
| 256 | ret = devm_add_action_or_reset(dev, devm_fan_node_release, hwm); | ||
| 257 | if (ret) | ||
| 258 | return ret; | ||
| 259 | |||
| 260 | num = fwnode_property_count_u32(fwnode, "cooling-levels"); | ||
| 261 | if (num <= 0) | ||
| 262 | return dev_err_probe(dev, num ? : -EINVAL, | ||
| 263 | "Failed to count elements in 'cooling-levels'\n"); | ||
| 264 | |||
| 265 | hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32), | ||
| 266 | GFP_KERNEL); | ||
| 267 | if (!hwm->fan_cooling_levels) | ||
| 268 | return -ENOMEM; | ||
| 269 | |||
| 270 | ret = fwnode_property_read_u32_array(fwnode, "cooling-levels", | ||
| 271 | hwm->fan_cooling_levels, num); | ||
| 272 | if (ret) | ||
| 273 | return dev_err_probe(dev, ret, "Failed to read 'cooling-levels'\n"); | ||
| 274 | |||
| 275 | for (i = 0; i < num; i++) { | ||
| 276 | if (hwm->fan_cooling_levels[i] > hwm->pwm_max) | ||
| 277 | return dev_err_probe(dev, -EINVAL, "fan state[%d]:%d > %d\n", i, | ||
| 278 | hwm->fan_cooling_levels[i], hwm->pwm_max); | ||
| 279 | } | ||
| 280 | |||
| 281 | hwm->fan_max_state = num - 1; | ||
| 282 | |||
| 283 | return 0; | ||
| 284 | } | ||
| 285 | |||
| 286 | static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = { | ||
| 287 | HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT), | ||
| 288 | HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), | ||
| 289 | HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), | ||
| 290 | NULL | ||
| 291 | }; | ||
| 292 | |||
| 293 | static int qnap_mcu_hwmon_probe(struct platform_device *pdev) | ||
| 294 | { | ||
| 295 | struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); | ||
| 296 | const struct qnap_mcu_variant *variant = pdev->dev.platform_data; | ||
| 297 | struct qnap_mcu_hwmon *hwm; | ||
| 298 | struct thermal_cooling_device *cdev; | ||
| 299 | struct device *dev = &pdev->dev; | ||
| 300 | struct device *hwmon; | ||
| 301 | int ret; | ||
| 302 | |||
| 303 | hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL); | ||
| 304 | if (!hwm) | ||
| 305 | return -ENOMEM; | ||
| 306 | |||
| 307 | hwm->mcu = mcu; | ||
| 308 | hwm->dev = &pdev->dev; | ||
| 309 | hwm->pwm_min = variant->fan_pwm_min; | ||
| 310 | hwm->pwm_max = variant->fan_pwm_max; | ||
| 311 | |||
| 312 | platform_set_drvdata(pdev, hwm); | ||
| 313 | |||
| 314 | /* | ||
| 315 | * Set duty cycle to maximum allowed. | ||
| 316 | */ | ||
| 317 | ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->pwm_max); | ||
| 318 | if (ret) | ||
| 319 | return ret; | ||
| 320 | |||
| 321 | hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops; | ||
| 322 | hwm->info.info = qnap_mcu_hwmon_channels; | ||
| 323 | |||
| 324 | ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm); | ||
| 325 | if (ret) | ||
| 326 | return ret; | ||
| 327 | |||
| 328 | hwm->fan_state = hwm->fan_max_state; | ||
| 329 | |||
| 330 | hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu", | ||
| 331 | hwm, &hwm->info, NULL); | ||
| 332 | if (IS_ERR(hwmon)) | ||
| 333 | return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n"); | ||
| 334 | |||
| 335 | /* | ||
| 336 | * Only register cooling device when we found cooling-levels. | ||
| 337 | * qnap_mcu_hwmon_get_cooling_data() will fail when reading malformed | ||
| 338 | * levels and only succeed with either no or correct cooling levels. | ||
| 339 | */ | ||
| 340 | if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) { | ||
| 341 | cdev = devm_thermal_of_cooling_device_register(dev, | ||
| 342 | to_of_node(hwm->fan_node), "qnap-mcu-hwmon", | ||
| 343 | hwm, &qnap_mcu_hwmon_cooling_ops); | ||
| 344 | if (IS_ERR(cdev)) | ||
| 345 | return dev_err_probe(dev, PTR_ERR(cdev), | ||
| 346 | "Failed to register qnap-mcu-hwmon as cooling device\n"); | ||
| 347 | hwm->cdev = cdev; | ||
| 348 | } | ||
| 349 | |||
| 350 | return 0; | ||
| 351 | } | ||
| 352 | |||
| 353 | static struct platform_driver qnap_mcu_hwmon_driver = { | ||
| 354 | .probe = qnap_mcu_hwmon_probe, | ||
| 355 | .driver = { | ||
| 356 | .name = "qnap-mcu-hwmon", | ||
| 357 | }, | ||
| 358 | }; | ||
| 359 | module_platform_driver(qnap_mcu_hwmon_driver); | ||
| 360 | |||
| 361 | MODULE_ALIAS("platform:qnap-mcu-hwmon"); | ||
| 362 | MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); | ||
| 363 | MODULE_DESCRIPTION("QNAP MCU hwmon driver"); | ||
| 364 | MODULE_LICENSE("GPL"); | ||
| @@ -917,6 +917,18 @@ config INPUT_HISI_POWERKEY · 916 unmodified lines | |||
| 917 | To compile this driver as a module, choose M here: the | 917 | To compile this driver as a module, choose M here: the |
| 918 | module will be called hisi_powerkey. | 918 | module will be called hisi_powerkey. |
| 919 | 919 | ||
| 920 | config INPUT_QNAP_MCU | ||
| 921 | tristate "Input Support for QNAP MCU controllers" | ||
| 922 | depends on MFD_QNAP_MCU | ||
| 923 | help | ||
| 924 | This option enables support for input elements available on | ||
| 925 | embedded controllers used in QNAP NAS devices. | ||
| 926 | |||
| 927 | This includes a polled power-button as well as a beeper. | ||
| 928 | |||
| 929 | To compile this driver as a module, choose M here: the | ||
| 930 | module will be called qnap-mcu-input. | ||
| 931 | |||
| 920 | config INPUT_RAVE_SP_PWRBUTTON | 932 | config INPUT_RAVE_SP_PWRBUTTON |
| 921 | tristate "RAVE SP Power button Driver" | 933 | tristate "RAVE SP Power button Driver" |
| 922 | depends on RAVE_SP_CORE | 934 | depends on RAVE_SP_CORE |
| @@ -68,6 +68,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o · 67 unmodified lines | |||
| 68 | obj-$(CONFIG_INPUT_POWERMATE) += powermate.o | 68 | obj-$(CONFIG_INPUT_POWERMATE) += powermate.o |
| 69 | obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o | 69 | obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o |
| 70 | obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o | 70 | obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o |
| 71 | obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o | ||
| 71 | obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o | 72 | obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o |
| 72 | obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o | 73 | obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o |
| 73 | obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o | 74 | obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o |
| @@ -0,0 +1,153 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | |||
| 3 | /* | ||
| 4 | * Driver for input events on QNAP-MCUs | ||
| 5 | * | ||
| 6 | * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/input.h> | ||
| 10 | #include <linux/mfd/qnap-mcu.h> | ||
| 11 | #include <linux/module.h> | ||
| 12 | #include <linux/platform_device.h> | ||
| 13 | #include <linux/slab.h> | ||
| 14 | #include <uapi/linux/input-event-codes.h> | ||
| 15 | |||
| 16 | /* | ||
| 17 | * The power-key needs to be pressed for a while to create an event, | ||
| 18 | * so there is no use for overly frequent polling. | ||
| 19 | */ | ||
| 20 | #define POLL_INTERVAL 500 | ||
| 21 | |||
| 22 | struct qnap_mcu_input_dev { | ||
| 23 | struct input_dev *input; | ||
| 24 | struct qnap_mcu *mcu; | ||
| 25 | struct device *dev; | ||
| 26 | |||
| 27 | struct work_struct beep_work; | ||
| 28 | int beep_type; | ||
| 29 | }; | ||
| 30 | |||
| 31 | static void qnap_mcu_input_poll(struct input_dev *input) | ||
| 32 | { | ||
| 33 | struct qnap_mcu_input_dev *idev = input_get_drvdata(input); | ||
| 34 | static const u8 cmd[] = { '@', 'C', 'V' }; | ||
| 35 | u8 reply[4]; | ||
| 36 | int state, ret; | ||
| 37 | |||
| 38 | /* poll the power button */ | ||
| 39 | ret = qnap_mcu_exec(idev->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); | ||
| 40 | if (ret) | ||
| 41 | return; | ||
| 42 | |||
| 43 | /* First bytes must mirror the sent command */ | ||
| 44 | if (memcmp(cmd, reply, sizeof(cmd))) { | ||
| 45 | dev_err(idev->dev, "malformed data received\n"); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | state = reply[3] - 0x30; | ||
| 50 | input_event(input, EV_KEY, KEY_POWER, state); | ||
| 51 | input_sync(input); | ||
| 52 | } | ||
| 53 | |||
| 54 | static void qnap_mcu_input_beeper_work(struct work_struct *work) | ||
| 55 | { | ||
| 56 | struct qnap_mcu_input_dev *idev = | ||
| 57 | container_of(work, struct qnap_mcu_input_dev, beep_work); | ||
| 58 | const u8 cmd[] = { '@', 'C', (idev->beep_type == SND_TONE) ? '3' : '2' }; | ||
| 59 | |||
| 60 | qnap_mcu_exec_with_ack(idev->mcu, cmd, sizeof(cmd)); | ||
| 61 | } | ||
| 62 | |||
| 63 | static int qnap_mcu_input_event(struct input_dev *input, unsigned int type, | ||
| 64 | unsigned int code, int value) | ||
| 65 | { | ||
| 66 | struct qnap_mcu_input_dev *idev = input_get_drvdata(input); | ||
| 67 | |||
| 68 | if (type != EV_SND || (code != SND_BELL && code != SND_TONE)) | ||
| 69 | return -EOPNOTSUPP; | ||
| 70 | |||
| 71 | if (value < 0) | ||
| 72 | return -EINVAL; | ||
| 73 | |||
| 74 | /* beep runtime is determined by the MCU */ | ||
| 75 | if (value == 0) | ||
| 76 | return 0; | ||
| 77 | |||
| 78 | /* Schedule work to actually turn the beeper on */ | ||
| 79 | idev->beep_type = code; | ||
| 80 | schedule_work(&idev->beep_work); | ||
| 81 | |||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | static void qnap_mcu_input_close(struct input_dev *input) | ||
| 86 | { | ||
| 87 | struct qnap_mcu_input_dev *idev = input_get_drvdata(input); | ||
| 88 | |||
| 89 | cancel_work_sync(&idev->beep_work); | ||
| 90 | } | ||
| 91 | |||
| 92 | static int qnap_mcu_input_probe(struct platform_device *pdev) | ||
| 93 | { | ||
| 94 | struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); | ||
| 95 | struct qnap_mcu_input_dev *idev; | ||
| 96 | struct device *dev = &pdev->dev; | ||
| 97 | struct input_dev *input; | ||
| 98 | int ret; | ||
| 99 | |||
| 100 | idev = devm_kzalloc(dev, sizeof(*idev), GFP_KERNEL); | ||
| 101 | if (!idev) | ||
| 102 | return -ENOMEM; | ||
| 103 | |||
| 104 | input = devm_input_allocate_device(dev); | ||
| 105 | if (!input) | ||
| 106 | return dev_err_probe(dev, -ENOMEM, "no memory for input device\n"); | ||
| 107 | |||
| 108 | idev->input = input; | ||
| 109 | idev->dev = dev; | ||
| 110 | idev->mcu = mcu; | ||
| 111 | |||
| 112 | input_set_drvdata(input, idev); | ||
| 113 | |||
| 114 | input->name = "qnap-mcu"; | ||
| 115 | input->phys = "qnap-mcu-input/input0"; | ||
| 116 | input->id.bustype = BUS_HOST; | ||
| 117 | input->id.vendor = 0x0001; | ||
| 118 | input->id.product = 0x0001; | ||
| 119 | input->id.version = 0x0100; | ||
| 120 | input->event = qnap_mcu_input_event; | ||
| 121 | input->close = qnap_mcu_input_close; | ||
| 122 | |||
| 123 | input_set_capability(input, EV_KEY, KEY_POWER); | ||
| 124 | input_set_capability(input, EV_SND, SND_BELL); | ||
| 125 | input_set_capability(input, EV_SND, SND_TONE); | ||
| 126 | |||
| 127 | INIT_WORK(&idev->beep_work, qnap_mcu_input_beeper_work); | ||
| 128 | |||
| 129 | ret = input_setup_polling(input, qnap_mcu_input_poll); | ||
| 130 | if (ret) | ||
| 131 | return dev_err_probe(dev, ret, "unable to set up polling\n"); | ||
| 132 | |||
| 133 | input_set_poll_interval(input, POLL_INTERVAL); | ||
| 134 | |||
| 135 | ret = input_register_device(input); | ||
| 136 | if (ret) | ||
| 137 | return dev_err_probe(dev, ret, "unable to register input device\n"); | ||
| 138 | |||
| 139 | return 0; | ||
| 140 | } | ||
| 141 | |||
| 142 | static struct platform_driver qnap_mcu_input_driver = { | ||
| 143 | .probe = qnap_mcu_input_probe, | ||
| 144 | .driver = { | ||
| 145 | .name = "qnap-mcu-input", | ||
| 146 | }, | ||
| 147 | }; | ||
| 148 | module_platform_driver(qnap_mcu_input_driver); | ||
| 149 | |||
| 150 | MODULE_ALIAS("platform:qnap-mcu-input"); | ||
| 151 | MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); | ||
| 152 | MODULE_DESCRIPTION("QNAP MCU input driver"); | ||
| 153 | MODULE_LICENSE("GPL"); | ||
| @@ -580,6 +580,17 @@ config LEDS_PCA995X · 579 unmodified lines | |||
| 580 | LED driver chips accessed via the I2C bus. Supported | 580 | LED driver chips accessed via the I2C bus. Supported |
| 581 | devices include PCA9955BTW, PCA9952TW and PCA9955TW. | 581 | devices include PCA9955BTW, PCA9952TW and PCA9955TW. |
| 582 | 582 | ||
| 583 | config LEDS_QNAP_MCU | ||
| 584 | tristate "LED Support for QNAP MCU controllers" | ||
| 585 | depends on LEDS_CLASS | ||
| 586 | depends on MFD_QNAP_MCU | ||
| 587 | help | ||
| 588 | This option enables support for LEDs available on embedded | ||
| 589 | controllers used in QNAP NAS devices. | ||
| 590 | |||
| 591 | This driver can also be built as a module. If so, the module | ||
| 592 | will be called qnap-mcu-leds. | ||
| 593 | |||
| 583 | config LEDS_WM831X_STATUS | 594 | config LEDS_WM831X_STATUS |
| 584 | tristate "LED support for status LEDs on WM831x PMICs" | 595 | tristate "LED support for status LEDs on WM831x PMICs" |
| 585 | depends on LEDS_CLASS | 596 | depends on LEDS_CLASS |
| @@ -931,6 +942,16 @@ config LEDS_LM36274 · 345 unmodified lines | |||
| 931 | Say Y to enable the LM36274 LED driver for TI LMU devices. | 942 | Say Y to enable the LM36274 LED driver for TI LMU devices. |
| 932 | This supports the LED device LM36274. | 943 | This supports the LED device LM36274. |
| 933 | 944 | ||
| 945 | config LEDS_ST1202 | ||
| 946 | tristate "LED Support for STMicroelectronics LED1202 I2C chips" | ||
| 947 | depends on LEDS_CLASS | ||
| 948 | depends on I2C | ||
| 949 | depends on OF | ||
| 950 | select LEDS_TRIGGERS | ||
| 951 | help | ||
| 952 | Say Y to enable support for LEDs connected to LED1202 | ||
| 953 | LED driver chips accessed via the I2C bus. | ||
| 954 | |||
| 934 | config LEDS_TPS6105X | 955 | config LEDS_TPS6105X |
| 935 | tristate "LED support for TI TPS6105X" | 956 | tristate "LED support for TI TPS6105X" |
| 936 | depends on LEDS_CLASS | 957 | depends on LEDS_CLASS |
| @@ -79,6 +79,7 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o · 78 unmodified lines | |||
| 79 | obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o | 79 | obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o |
| 80 | obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o | 80 | obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o |
| 81 | obj-$(CONFIG_LEDS_PWM) += leds-pwm.o | 81 | obj-$(CONFIG_LEDS_PWM) += leds-pwm.o |
| 82 | obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o | ||
| 82 | obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o | 83 | obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o |
| 83 | obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o | 84 | obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o |
| 84 | obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o | 85 | obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o |
| @@ -0,0 +1,227 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Driver for LEDs found on QNAP MCU devices | ||
| 4 | * | ||
| 5 | * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> | ||
| 6 | */ | ||
| 7 | |||
| 8 | #include <linux/leds.h> | ||
| 9 | #include <linux/mfd/qnap-mcu.h> | ||
| 10 | #include <linux/module.h> | ||
| 11 | #include <linux/platform_device.h> | ||
| 12 | #include <linux/slab.h> | ||
| 13 | #include <uapi/linux/uleds.h> | ||
| 14 | |||
| 15 | enum qnap_mcu_err_led_mode { | ||
| 16 | QNAP_MCU_ERR_LED_ON = 0, | ||
| 17 | QNAP_MCU_ERR_LED_OFF = 1, | ||
| 18 | QNAP_MCU_ERR_LED_BLINK_FAST = 2, | ||
| 19 | QNAP_MCU_ERR_LED_BLINK_SLOW = 3, | ||
| 20 | }; | ||
| 21 | |||
| 22 | struct qnap_mcu_err_led { | ||
| 23 | struct qnap_mcu *mcu; | ||
| 24 | struct led_classdev cdev; | ||
| 25 | char name[LED_MAX_NAME_SIZE]; | ||
| 26 | u8 num; | ||
| 27 | u8 mode; | ||
| 28 | }; | ||
| 29 | |||
| 30 | static inline struct qnap_mcu_err_led * | ||
| 31 | cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev) | ||
| 32 | { | ||
| 33 | return container_of(led_cdev, struct qnap_mcu_err_led, cdev); | ||
| 34 | } | ||
| 35 | |||
| 36 | static int qnap_mcu_err_led_set(struct led_classdev *led_cdev, | ||
| 37 | enum led_brightness brightness) | ||
| 38 | { | ||
| 39 | struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); | ||
| 40 | u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' }; | ||
| 41 | |||
| 42 | /* Don't disturb a possible set blink-mode if LED stays on */ | ||
| 43 | if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST) | ||
| 44 | return 0; | ||
| 45 | |||
| 46 | err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF; | ||
| 47 | cmd[3] = '0' + err_led->mode; | ||
| 48 | |||
| 49 | return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); | ||
| 50 | } | ||
| 51 | |||
| 52 | static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev, | ||
| 53 | unsigned long *delay_on, | ||
| 54 | unsigned long *delay_off) | ||
| 55 | { | ||
| 56 | struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); | ||
| 57 | u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' }; | ||
| 58 | |||
| 59 | /* LED is off, nothing to do */ | ||
| 60 | if (err_led->mode == QNAP_MCU_ERR_LED_OFF) | ||
| 61 | return 0; | ||
| 62 | |||
| 63 | if (*delay_on < 500) { | ||
| 64 | *delay_on = 100; | ||
| 65 | *delay_off = 100; | ||
| 66 | err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST; | ||
| 67 | } else { | ||
| 68 | *delay_on = 500; | ||
| 69 | *delay_off = 500; | ||
| 70 | err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW; | ||
| 71 | } | ||
| 72 | |||
| 73 | cmd[3] = '0' + err_led->mode; | ||
| 74 | |||
| 75 | return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); | ||
| 76 | } | ||
| 77 | |||
| 78 | static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led) | ||
| 79 | { | ||
| 80 | struct qnap_mcu_err_led *err_led; | ||
| 81 | int ret; | ||
| 82 | |||
| 83 | err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL); | ||
| 84 | if (!err_led) | ||
| 85 | return -ENOMEM; | ||
| 86 | |||
| 87 | err_led->mcu = mcu; | ||
| 88 | err_led->num = num_err_led; | ||
| 89 | err_led->mode = QNAP_MCU_ERR_LED_OFF; | ||
| 90 | |||
| 91 | scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1); | ||
| 92 | err_led->cdev.name = err_led->name; | ||
| 93 | |||
| 94 | err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set; | ||
| 95 | err_led->cdev.blink_set = qnap_mcu_err_led_blink_set; | ||
| 96 | err_led->cdev.brightness = 0; | ||
| 97 | err_led->cdev.max_brightness = 1; | ||
| 98 | |||
| 99 | ret = devm_led_classdev_register(dev, &err_led->cdev); | ||
| 100 | if (ret) | ||
| 101 | return ret; | ||
| 102 | |||
| 103 | return qnap_mcu_err_led_set(&err_led->cdev, 0); | ||
| 104 | } | ||
| 105 | |||
| 106 | enum qnap_mcu_usb_led_mode { | ||
| 107 | QNAP_MCU_USB_LED_ON = 1, | ||
| 108 | QNAP_MCU_USB_LED_OFF = 3, | ||
| 109 | QNAP_MCU_USB_LED_BLINK = 2, | ||
| 110 | }; | ||
| 111 | |||
| 112 | struct qnap_mcu_usb_led { | ||
| 113 | struct qnap_mcu *mcu; | ||
| 114 | struct led_classdev cdev; | ||
| 115 | u8 mode; | ||
| 116 | }; | ||
| 117 | |||
| 118 | static inline struct qnap_mcu_usb_led * | ||
| 119 | cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev) | ||
| 120 | { | ||
| 121 | return container_of(led_cdev, struct qnap_mcu_usb_led, cdev); | ||
| 122 | } | ||
| 123 | |||
| 124 | static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev, | ||
| 125 | enum led_brightness brightness) | ||
| 126 | { | ||
| 127 | struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); | ||
| 128 | u8 cmd[] = { '@', 'C', 0 }; | ||
| 129 | |||
| 130 | /* Don't disturb a possible set blink-mode if LED stays on */ | ||
| 131 | if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK) | ||
| 132 | return 0; | ||
| 133 | |||
| 134 | usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF; | ||
| 135 | |||
| 136 | /* | ||
| 137 | * Byte 3 is shared between the usb led target on/off/blink | ||
| 138 | * and also the buzzer control (in the input driver) | ||
| 139 | */ | ||
| 140 | cmd[2] = 'D' + usb_led->mode; | ||
| 141 | |||
| 142 | return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); | ||
| 143 | } | ||
| 144 | |||
| 145 | static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev, | ||
| 146 | unsigned long *delay_on, | ||
| 147 | unsigned long *delay_off) | ||
| 148 | { | ||
| 149 | struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); | ||
| 150 | u8 cmd[] = { '@', 'C', 0 }; | ||
| 151 | |||
| 152 | /* LED is off, nothing to do */ | ||
| 153 | if (usb_led->mode == QNAP_MCU_USB_LED_OFF) | ||
| 154 | return 0; | ||
| 155 | |||
| 156 | *delay_on = 250; | ||
| 157 | *delay_off = 250; | ||
| 158 | usb_led->mode = QNAP_MCU_USB_LED_BLINK; | ||
| 159 | |||
| 160 | /* | ||
| 161 | * Byte 3 is shared between the USB LED target on/off/blink | ||
| 162 | * and also the buzzer control (in the input driver) | ||
| 163 | */ | ||
| 164 | cmd[2] = 'D' + usb_led->mode; | ||
| 165 | |||
| 166 | return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); | ||
| 167 | } | ||
| 168 | |||
| 169 | static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu) | ||
| 170 | { | ||
| 171 | struct qnap_mcu_usb_led *usb_led; | ||
| 172 | int ret; | ||
| 173 | |||
| 174 | usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL); | ||
| 175 | if (!usb_led) | ||
| 176 | return -ENOMEM; | ||
| 177 | |||
| 178 | usb_led->mcu = mcu; | ||
| 179 | usb_led->mode = QNAP_MCU_USB_LED_OFF; | ||
| 180 | usb_led->cdev.name = "usb:blue:disk"; | ||
| 181 | usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set; | ||
| 182 | usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set; | ||
| 183 | usb_led->cdev.brightness = 0; | ||
| 184 | usb_led->cdev.max_brightness = 1; | ||
| 185 | |||
| 186 | ret = devm_led_classdev_register(dev, &usb_led->cdev); | ||
| 187 | if (ret) | ||
| 188 | return ret; | ||
| 189 | |||
| 190 | return qnap_mcu_usb_led_set(&usb_led->cdev, 0); | ||
| 191 | } | ||
| 192 | |||
| 193 | static int qnap_mcu_leds_probe(struct platform_device *pdev) | ||
| 194 | { | ||
| 195 | struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); | ||
| 196 | const struct qnap_mcu_variant *variant = pdev->dev.platform_data; | ||
| 197 | int ret; | ||
| 198 | |||
| 199 | for (int i = 0; i < variant->num_drives; i++) { | ||
| 200 | ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i); | ||
| 201 | if (ret) | ||
| 202 | return dev_err_probe(&pdev->dev, ret, | ||
| 203 | "failed to register error LED %d\n", i); | ||
| 204 | } | ||
| 205 | |||
| 206 | if (variant->usb_led) { | ||
| 207 | ret = qnap_mcu_register_usb_led(&pdev->dev, mcu); | ||
| 208 | if (ret) | ||
| 209 | return dev_err_probe(&pdev->dev, ret, | ||
| 210 | "failed to register USB LED\n"); | ||
| 211 | } | ||
| 212 | |||
| 213 | return 0; | ||
| 214 | } | ||
| 215 | |||
| 216 | static struct platform_driver qnap_mcu_leds_driver = { | ||
| 217 | .probe = qnap_mcu_leds_probe, | ||
| 218 | .driver = { | ||
| 219 | .name = "qnap-mcu-leds", | ||
| 220 | }, | ||
| 221 | }; | ||
| 222 | module_platform_driver(qnap_mcu_leds_driver); | ||
| 223 | |||
| 224 | MODULE_ALIAS("platform:qnap-mcu-leds"); | ||
| 225 | MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); | ||
| 226 | MODULE_DESCRIPTION("QNAP MCU LEDs driver"); | ||
| 227 | MODULE_LICENSE("GPL"); | ||
| @@ -0,0 +1,416 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * LED driver for STMicroelectronics LED1202 chip | ||
| 4 | * | ||
| 5 | * Copyright (C) 2024 Remote-Tech Ltd. UK | ||
| 6 | */ | ||
| 7 | |||
| 8 | #include <linux/cleanup.h> | ||
| 9 | #include <linux/ctype.h> | ||
| 10 | #include <linux/delay.h> | ||
| 11 | #include <linux/err.h> | ||
| 12 | #include <linux/gpio.h> | ||
| 13 | #include <linux/i2c.h> | ||
| 14 | #include <linux/leds.h> | ||
| 15 | #include <linux/module.h> | ||
| 16 | #include <linux/slab.h> | ||
| 17 | #include <linux/string.h> | ||
| 18 | |||
| 19 | #define ST1202_CHAN_DISABLE_ALL 0x00 | ||
| 20 | #define ST1202_CHAN_ENABLE_HIGH 0x03 | ||
| 21 | #define ST1202_CHAN_ENABLE_LOW 0x02 | ||
| 22 | #define ST1202_CONFIG_REG 0x04 | ||
| 23 | /* PATS: Pattern sequence feature enable */ | ||
| 24 | #define ST1202_CONFIG_REG_PATS BIT(7) | ||
| 25 | /* PATSR: Pattern sequence runs (self-clear when sequence is finished) */ | ||
| 26 | #define ST1202_CONFIG_REG_PATSR BIT(6) | ||
| 27 | #define ST1202_CONFIG_REG_SHFT BIT(3) | ||
| 28 | #define ST1202_DEV_ENABLE 0x01 | ||
| 29 | #define ST1202_DEV_ENABLE_ON BIT(0) | ||
| 30 | #define ST1202_DEV_ENABLE_RESET BIT(7) | ||
| 31 | #define ST1202_DEVICE_ID 0x00 | ||
| 32 | #define ST1202_ILED_REG0 0x09 | ||
| 33 | #define ST1202_MAX_LEDS 12 | ||
| 34 | #define ST1202_MAX_PATTERNS 8 | ||
| 35 | #define ST1202_MILLIS_PATTERN_DUR_MAX 5660 | ||
| 36 | #define ST1202_MILLIS_PATTERN_DUR_MIN 22 | ||
| 37 | #define ST1202_PATTERN_DUR 0x16 | ||
| 38 | #define ST1202_PATTERN_PWM 0x1E | ||
| 39 | #define ST1202_PATTERN_REP 0x15 | ||
| 40 | |||
| 41 | struct st1202_led { | ||
| 42 | struct fwnode_handle *fwnode; | ||
| 43 | struct led_classdev led_cdev; | ||
| 44 | struct st1202_chip *chip; | ||
| 45 | bool is_active; | ||
| 46 | int led_num; | ||
| 47 | }; | ||
| 48 | |||
| 49 | struct st1202_chip { | ||
| 50 | struct i2c_client *client; | ||
| 51 | struct mutex lock; | ||
| 52 | struct st1202_led leds[ST1202_MAX_LEDS]; | ||
| 53 | }; | ||
| 54 | |||
| 55 | static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev) | ||
| 56 | { | ||
| 57 | return container_of(cdev, struct st1202_led, led_cdev); | ||
| 58 | } | ||
| 59 | |||
| 60 | static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val) | ||
| 61 | { | ||
| 62 | struct device *dev = &chip->client->dev; | ||
| 63 | int ret; | ||
| 64 | |||
| 65 | ret = i2c_smbus_read_byte_data(chip->client, reg); | ||
| 66 | if (ret < 0) { | ||
| 67 | dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret); | ||
| 68 | return ret; | ||
| 69 | } | ||
| 70 | |||
| 71 | *val = (uint8_t)ret; | ||
| 72 | return 0; | ||
| 73 | } | ||
| 74 | |||
| 75 | static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) | ||
| 76 | { | ||
| 77 | struct device *dev = &chip->client->dev; | ||
| 78 | int ret; | ||
| 79 | |||
| 80 | ret = i2c_smbus_write_byte_data(chip->client, reg, val); | ||
| 81 | if (ret != 0) | ||
| 82 | dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret); | ||
| 83 | |||
| 84 | return ret; | ||
| 85 | } | ||
| 86 | |||
| 87 | static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) | ||
| 88 | { | ||
| 89 | return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1; | ||
| 90 | } | ||
| 91 | |||
| 92 | static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, | ||
| 93 | int pattern, unsigned int value) | ||
| 94 | { | ||
| 95 | u8 value_l, value_h; | ||
| 96 | int ret; | ||
| 97 | |||
| 98 | value_l = (u8)value; | ||
| 99 | value_h = (u8)(value >> 8); | ||
| 100 | |||
| 101 | /* | ||
| 102 | * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh), | ||
| 103 | * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh) | ||
| 104 | * and y is the pattern number in hexadecimal (y = 00h .. 07h) | ||
| 105 | */ | ||
| 106 | ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern), | ||
| 107 | value_l); | ||
| 108 | if (ret != 0) | ||
| 109 | return ret; | ||
| 110 | |||
| 111 | /* | ||
| 112 | * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh), | ||
| 113 | * where x is the channel number in hexadecimal (x = 00h .. 0Bh) | ||
| 114 | * and y is the pattern number in hexadecimal (y = 00h .. 07h) | ||
| 115 | */ | ||
| 116 | ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern), | ||
| 117 | value_h); | ||
| 118 | if (ret != 0) | ||
| 119 | return ret; | ||
| 120 | |||
| 121 | return 0; | ||
| 122 | } | ||
| 123 | |||
| 124 | static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, | ||
| 125 | unsigned int value) | ||
| 126 | { | ||
| 127 | return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern), | ||
| 128 | st1202_prescalar_to_miliseconds(value)); | ||
| 129 | } | ||
| 130 | |||
| 131 | static void st1202_brightness_set(struct led_classdev *led_cdev, | ||
| 132 | enum led_brightness value) | ||
| 133 | { | ||
| 134 | struct st1202_led *led = cdev_to_st1202_led(led_cdev); | ||
| 135 | struct st1202_chip *chip = led->chip; | ||
| 136 | |||
| 137 | guard(mutex)(&chip->lock); | ||
| 138 | |||
| 139 | st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); | ||
| 140 | } | ||
| 141 | |||
| 142 | static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) | ||
| 143 | { | ||
| 144 | struct st1202_led *led = cdev_to_st1202_led(led_cdev); | ||
| 145 | struct st1202_chip *chip = led->chip; | ||
| 146 | u8 value = 0; | ||
| 147 | |||
| 148 | guard(mutex)(&chip->lock); | ||
| 149 | |||
| 150 | st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); | ||
| 151 | |||
| 152 | return value; | ||
| 153 | } | ||
| 154 | |||
| 155 | static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) | ||
| 156 | { | ||
| 157 | u8 chan_low, chan_high; | ||
| 158 | int ret; | ||
| 159 | |||
| 160 | guard(mutex)(&chip->lock); | ||
| 161 | |||
| 162 | if (led_num <= 7) { | ||
| 163 | ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low); | ||
| 164 | if (ret < 0) | ||
| 165 | return ret; | ||
| 166 | |||
| 167 | chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num); | ||
| 168 | |||
| 169 | ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low); | ||
| 170 | if (ret < 0) | ||
| 171 | return ret; | ||
| 172 | |||
| 173 | } else { | ||
| 174 | ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high); | ||
| 175 | if (ret < 0) | ||
| 176 | return ret; | ||
| 177 | |||
| 178 | chan_high = active ? chan_high | (BIT(led_num) >> 8) : | ||
| 179 | chan_high & ~(BIT(led_num) >> 8); | ||
| 180 | |||
| 181 | ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high); | ||
| 182 | if (ret < 0) | ||
| 183 | return ret; | ||
| 184 | } | ||
| 185 | |||
| 186 | return 0; | ||
| 187 | } | ||
| 188 | |||
| 189 | static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) | ||
| 190 | { | ||
| 191 | struct st1202_led *led = cdev_to_st1202_led(ldev); | ||
| 192 | struct st1202_chip *chip = led->chip; | ||
| 193 | |||
| 194 | return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true); | ||
| 195 | } | ||
| 196 | |||
| 197 | static int st1202_led_pattern_clear(struct led_classdev *ldev) | ||
| 198 | { | ||
| 199 | struct st1202_led *led = cdev_to_st1202_led(ldev); | ||
| 200 | struct st1202_chip *chip = led->chip; | ||
| 201 | int ret; | ||
| 202 | |||
| 203 | guard(mutex)(&chip->lock); | ||
| 204 | |||
| 205 | for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { | ||
| 206 | ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); | ||
| 207 | if (ret != 0) | ||
| 208 | return ret; | ||
| 209 | |||
| 210 | ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN); | ||
| 211 | if (ret != 0) | ||
| 212 | return ret; | ||
| 213 | } | ||
| 214 | |||
| 215 | return 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | static int st1202_led_pattern_set(struct led_classdev *ldev, | ||
| 219 | struct led_pattern *pattern, | ||
| 220 | u32 len, int repeat) | ||
| 221 | { | ||
| 222 | struct st1202_led *led = cdev_to_st1202_led(ldev); | ||
| 223 | struct st1202_chip *chip = led->chip; | ||
| 224 | int ret; | ||
| 225 | |||
| 226 | if (len > ST1202_MAX_PATTERNS) | ||
| 227 | return -EINVAL; | ||
| 228 | |||
| 229 | guard(mutex)(&chip->lock); | ||
| 230 | |||
| 231 | for (int patt = 0; patt < len; patt++) { | ||
| 232 | if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || | ||
| 233 | pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) | ||
| 234 | return -EINVAL; | ||
| 235 | |||
| 236 | ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); | ||
| 237 | if (ret != 0) | ||
| 238 | return ret; | ||
| 239 | |||
| 240 | ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t); | ||
| 241 | if (ret != 0) | ||
| 242 | return ret; | ||
| 243 | } | ||
| 244 | |||
| 245 | ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat); | ||
| 246 | if (ret != 0) | ||
| 247 | return ret; | ||
| 248 | |||
| 249 | ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | | ||
| 250 | ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); | ||
| 251 | if (ret != 0) | ||
| 252 | return ret; | ||
| 253 | |||
| 254 | return 0; | ||
| 255 | } | ||
| 256 | |||
| 257 | static int st1202_dt_init(struct st1202_chip *chip) | ||
| 258 | { | ||
| 259 | struct device *dev = &chip->client->dev; | ||
| 260 | struct st1202_led *led; | ||
| 261 | int err, reg; | ||
| 262 | |||
| 263 | for_each_available_child_of_node_scoped(dev_of_node(dev), child) { | ||
| 264 | struct led_init_data init_data = {}; | ||
| 265 | |||
| 266 | err = of_property_read_u32(child, "reg", ®); | ||
| 267 | if (err) | ||
| 268 | return dev_err_probe(dev, err, "Invalid register\n"); | ||
| 269 | |||
| 270 | led = &chip->leds[reg]; | ||
| 271 | led->is_active = true; | ||
| 272 | led->fwnode = of_fwnode_handle(child); | ||
| 273 | |||
| 274 | led->led_cdev.max_brightness = U8_MAX; | ||
| 275 | led->led_cdev.brightness_set_blocking = st1202_led_set; | ||
| 276 | led->led_cdev.pattern_set = st1202_led_pattern_set; | ||
| 277 | led->led_cdev.pattern_clear = st1202_led_pattern_clear; | ||
| 278 | led->led_cdev.default_trigger = "pattern"; | ||
| 279 | |||
| 280 | init_data.fwnode = led->fwnode; | ||
| 281 | init_data.devicename = "st1202"; | ||
| 282 | init_data.default_label = ":"; | ||
| 283 | |||
| 284 | err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data); | ||
| 285 | if (err < 0) | ||
| 286 | return dev_err_probe(dev, err, "Failed to register LED class device\n"); | ||
| 287 | |||
| 288 | led->led_cdev.brightness_set = st1202_brightness_set; | ||
| 289 | led->led_cdev.brightness_get = st1202_brightness_get; | ||
| 290 | } | ||
| 291 | |||
| 292 | return 0; | ||
| 293 | } | ||
| 294 | |||
| 295 | static int st1202_setup(struct st1202_chip *chip) | ||
| 296 | { | ||
| 297 | int ret; | ||
| 298 | |||
| 299 | guard(mutex)(&chip->lock); | ||
| 300 | |||
| 301 | /* | ||
| 302 | * Once the supply voltage is applied, the LED1202 executes some internal checks, | ||
| 303 | * afterwords it stops the oscillator and puts the internal LDO in quiescent mode. | ||
| 304 | * To start the device, EN bit must be set inside the “Device Enable” register at | ||
| 305 | * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters | ||
| 306 | * from the internal non-volatile memory and performs an auto-calibration procedure | ||
| 307 | * in order to increase the output current precision. | ||
| 308 | * Such initialization lasts about 6.5 ms. | ||
| 309 | */ | ||
| 310 | |||
| 311 | /* Reset the chip during setup */ | ||
| 312 | ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET); | ||
| 313 | if (ret < 0) | ||
| 314 | return ret; | ||
| 315 | |||
| 316 | /* Enable phase-shift delay feature */ | ||
| 317 | ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); | ||
| 318 | if (ret < 0) | ||
| 319 | return ret; | ||
| 320 | |||
| 321 | /* Enable the device */ | ||
| 322 | ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON); | ||
| 323 | if (ret < 0) | ||
| 324 | return ret; | ||
| 325 | |||
| 326 | /* Duration of initialization */ | ||
| 327 | usleep_range(6500, 10000); | ||
| 328 | |||
| 329 | /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */ | ||
| 330 | ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL); | ||
| 331 | if (ret < 0) | ||
| 332 | return ret; | ||
| 333 | |||
| 334 | ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL); | ||
| 335 | if (ret < 0) | ||
| 336 | return ret; | ||
| 337 | |||
| 338 | ret = st1202_write_reg(chip, ST1202_CONFIG_REG, | ||
| 339 | ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR); | ||
| 340 | if (ret < 0) | ||
| 341 | return ret; | ||
| 342 | |||
| 343 | return 0; | ||
| 344 | } | ||
| 345 | |||
| 346 | static int st1202_probe(struct i2c_client *client) | ||
| 347 | { | ||
| 348 | struct st1202_chip *chip; | ||
| 349 | struct st1202_led *led; | ||
| 350 | int ret; | ||
| 351 | |||
| 352 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
| 353 | return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n"); | ||
| 354 | |||
| 355 | chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); | ||
| 356 | if (!chip) | ||
| 357 | return -ENOMEM; | ||
| 358 | |||
| 359 | devm_mutex_init(&client->dev, &chip->lock); | ||
| 360 | chip->client = client; | ||
| 361 | |||
| 362 | ret = st1202_dt_init(chip); | ||
| 363 | if (ret < 0) | ||
| 364 | return ret; | ||
| 365 | |||
| 366 | ret = st1202_setup(chip); | ||
| 367 | if (ret < 0) | ||
| 368 | return ret; | ||
| 369 | |||
| 370 | for (int i = 0; i < ST1202_MAX_LEDS; i++) { | ||
| 371 | led = &chip->leds[i]; | ||
| 372 | led->chip = chip; | ||
| 373 | led->led_num = i; | ||
| 374 | |||
| 375 | if (!led->is_active) | ||
| 376 | continue; | ||
| 377 | |||
| 378 | ret = st1202_channel_set(led->chip, led->led_num, true); | ||
| 379 | if (ret < 0) | ||
| 380 | return dev_err_probe(&client->dev, ret, | ||
| 381 | "Failed to activate LED channel\n"); | ||
| 382 | |||
| 383 | ret = st1202_led_pattern_clear(&led->led_cdev); | ||
| 384 | if (ret < 0) | ||
| 385 | return dev_err_probe(&client->dev, ret, | ||
| 386 | "Failed to clear LED pattern\n"); | ||
| 387 | } | ||
| 388 | |||
| 389 | return 0; | ||
| 390 | } | ||
| 391 | |||
| 392 | static const struct i2c_device_id st1202_id[] = { | ||
| 393 | { "st1202-i2c" }, | ||
| 394 | { /* sentinel */ } | ||
| 395 | }; | ||
| 396 | MODULE_DEVICE_TABLE(i2c, st1202_id); | ||
| 397 | |||
| 398 | static const struct of_device_id st1202_dt_ids[] = { | ||
| 399 | { .compatible = "st,led1202" }, | ||
| 400 | { /* sentinel */ } | ||
| 401 | }; | ||
| 402 | MODULE_DEVICE_TABLE(of, st1202_dt_ids); | ||
| 403 | |||
| 404 | static struct i2c_driver st1202_driver = { | ||
| 405 | .driver = { | ||
| 406 | .name = "leds-st1202", | ||
| 407 | .of_match_table = of_match_ptr(st1202_dt_ids), | ||
| 408 | }, | ||
| 409 | .probe = st1202_probe, | ||
| 410 | .id_table = st1202_id, | ||
| 411 | }; | ||
| 412 | module_i2c_driver(st1202_driver); | ||
| 413 | |||
| 414 | MODULE_AUTHOR("Remote Tech LTD"); | ||
| 415 | MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver"); | ||
| 416 | MODULE_LICENSE("GPL"); | ||
| @@ -2386,6 +2386,19 @@ config MFD_INTEL_M10_BMC_PMCI · 2385 unmodified lines | |||
| 2386 | additional drivers must be enabled in order to use the functionality | 2386 | additional drivers must be enabled in order to use the functionality |
| 2387 | of the device. | 2387 | of the device. |
| 2388 | 2388 | ||
| 2389 | config MFD_QNAP_MCU | ||
| 2390 | tristate "QNAP microcontroller unit core driver" | ||
| 2391 | depends on SERIAL_DEV_BUS | ||
| 2392 | select MFD_CORE | ||
| 2393 | help | ||
| 2394 | Select this to get support for the QNAP MCU device found in | ||
| 2395 | several devices of QNAP network attached storage products that | ||
| 2396 | implements additional functionality for the device, like fan | ||
| 2397 | and LED control. | ||
| 2398 | |||
| 2399 | This driver implements the base serial protocol to talk to the | ||
| 2400 | device and provides functions for the other parts to hook into. | ||
| 2401 | |||
| 2389 | config MFD_RSMU_I2C | 2402 | config MFD_RSMU_I2C |
| 2390 | tristate "Renesas Synchronization Management Unit with I2C" | 2403 | tristate "Renesas Synchronization Management Unit with I2C" |
| 2391 | depends on I2C && OF | 2404 | depends on I2C && OF |
| @@ -2414,5 +2427,17 @@ config MFD_RSMU_SPI · 22 unmodified lines | |||
| 2414 | Additional drivers must be enabled in order to use the functionality | 2427 | Additional drivers must be enabled in order to use the functionality |
| 2415 | of the device. | 2428 | of the device. |
| 2416 | 2429 | ||
| 2430 | config MFD_UPBOARD_FPGA | ||
| 2431 | tristate "Support for the AAeon UP board FPGA" | ||
| 2432 | depends on (X86 && ACPI) | ||
| 2433 | select MFD_CORE | ||
| 2434 | help | ||
| 2435 | Select this option to enable the AAEON UP and UP^2 onboard FPGA. | ||
| 2436 | This is the core driver of this FPGA, which has a pin controller and a | ||
| 2437 | LED controller. | ||
| 2438 | |||
| 2439 | To compile this driver as a module, choose M here: the module will be | ||
| 2440 | called upboard-fpga. | ||
| 2441 | |||
| 2417 | endmenu | 2442 | endmenu |
| 2418 | endif | 2443 | endif |
| @@ -288,5 +288,9 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC_PMCI) += intel-m10-bmc-pmci.o · 287 unmodified lines | |||
| 288 | obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o | 288 | obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o |
| 289 | obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o | 289 | obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o |
| 290 | 290 | ||
| 291 | obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o | ||
| 292 | |||
| 291 | obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o | 293 | obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o |
| 292 | obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o | 294 | obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o |
| 295 | |||
| 296 | obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o | ||
| @@ -1445,7 +1445,7 @@ int axp20x_device_probe(struct axp20x_dev *axp20x) · 1444 unmodified lines | |||
| 1445 | } | 1445 | } |
| 1446 | } | 1446 | } |
| 1447 | 1447 | ||
| 1448 | ret = mfd_add_devices(axp20x->dev, PLATFORM_DEVID_AUTO, axp20x->cells, | 1448 | ret = mfd_add_devices(axp20x->dev, PLATFORM_DEVID_NONE, axp20x->cells, |
| 1449 | axp20x->nr_cells, NULL, 0, NULL); | 1449 | axp20x->nr_cells, NULL, 0, NULL); |
| 1450 | 1450 | ||
| 1451 | if (ret) { | 1451 | if (ret) { |
| @@ -1455,10 +1455,7 @@ int axp20x_device_probe(struct axp20x_dev *axp20x) · 3 unmodified lines | |||
| 1455 | } | 1455 | } |
| 1456 | 1456 | ||
| 1457 | if (axp20x->variant != AXP288_ID) | 1457 | if (axp20x->variant != AXP288_ID) |
| 1458 | devm_register_sys_off_handler(axp20x->dev, | 1458 | devm_register_power_off_handler(axp20x->dev, axp20x_power_off, axp20x); |
| 1459 | SYS_OFF_MODE_POWER_OFF, | ||
| 1460 | SYS_OFF_PRIO_DEFAULT, | ||
| 1461 | axp20x_power_off, axp20x); | ||
| 1462 | 1459 | ||
| 1463 | dev_info(axp20x->dev, "AXP20X driver loaded\n"); | 1460 | dev_info(axp20x->dev, "AXP20X driver loaded\n"); |
| 1464 | 1461 | ||
| @@ -56,13 +56,6 @@ static int cs42l43_i2c_probe(struct i2c_client *i2c) · 55 unmodified lines | |||
| 56 | return cs42l43_dev_probe(cs42l43); | 56 | return cs42l43_dev_probe(cs42l43); |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | static void cs42l43_i2c_remove(struct i2c_client *i2c) | ||
| 60 | { | ||
| 61 | struct cs42l43 *cs42l43 = dev_get_drvdata(&i2c->dev); | ||
| 62 | |||
| 63 | cs42l43_dev_remove(cs42l43); | ||
| 64 | } | ||
| 65 | |||
| 66 | #if IS_ENABLED(CONFIG_OF) | 59 | #if IS_ENABLED(CONFIG_OF) |
| 67 | static const struct of_device_id cs42l43_of_match[] = { | 60 | static const struct of_device_id cs42l43_of_match[] = { |
| 68 | { .compatible = "cirrus,cs42l43", }, | 61 | { .compatible = "cirrus,cs42l43", }, |
| @@ -88,7 +81,6 @@ static struct i2c_driver cs42l43_i2c_driver = { · 19 unmodified lines | |||
| 88 | }, | 81 | }, |
| 89 | 82 | ||
| 90 | .probe = cs42l43_i2c_probe, | 83 | .probe = cs42l43_i2c_probe, |
| 91 | .remove = cs42l43_i2c_remove, | ||
| 92 | }; | 84 | }; |
| 93 | module_i2c_driver(cs42l43_i2c_driver); | 85 | module_i2c_driver(cs42l43_i2c_driver); |
| 94 | 86 | ||
| @@ -187,15 +187,6 @@ static int cs42l43_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id * · 186 unmodified lines | |||
| 187 | return cs42l43_dev_probe(cs42l43); | 187 | return cs42l43_dev_probe(cs42l43); |
| 188 | } | 188 | } |
| 189 | 189 | ||
| 190 | static int cs42l43_sdw_remove(struct sdw_slave *sdw) | ||
| 191 | { | ||
| 192 | struct cs42l43 *cs42l43 = dev_get_drvdata(&sdw->dev); | ||
| 193 | |||
| 194 | cs42l43_dev_remove(cs42l43); | ||
| 195 | |||
| 196 | return 0; | ||
| 197 | } | ||
| 198 | |||
| 199 | static const struct sdw_device_id cs42l43_sdw_id[] = { | 190 | static const struct sdw_device_id cs42l43_sdw_id[] = { |
| 200 | SDW_SLAVE_ENTRY(0x01FA, 0x4243, 0), | 191 | SDW_SLAVE_ENTRY(0x01FA, 0x4243, 0), |
| 201 | {} | 192 | {} |
| @@ -209,7 +200,6 @@ static struct sdw_driver cs42l43_sdw_driver = { · 7 unmodified lines | |||
| 209 | }, | 200 | }, |
| 210 | 201 | ||
| 211 | .probe = cs42l43_sdw_probe, | 202 | .probe = cs42l43_sdw_probe, |
| 212 | .remove = cs42l43_sdw_remove, | ||
| 213 | .id_table = cs42l43_sdw_id, | 203 | .id_table = cs42l43_sdw_id, |
| 214 | .ops = &cs42l43_sdw_ops, | 204 | .ops = &cs42l43_sdw_ops, |
| 215 | }; | 205 | }; |
| @@ -29,7 +29,7 @@ · 28 unmodified lines | |||
| 29 | 29 | ||
| 30 | #define CS42L43_RESET_DELAY_MS 20 | 30 | #define CS42L43_RESET_DELAY_MS 20 |
| 31 | 31 | ||
| 32 | #define CS42L43_SDW_ATTACH_TIMEOUT_MS 500 | 32 | #define CS42L43_SDW_ATTACH_TIMEOUT_MS 5000 |
| 33 | #define CS42L43_SDW_DETACH_TIMEOUT_MS 100 | 33 | #define CS42L43_SDW_DETACH_TIMEOUT_MS 100 |
| 34 | 34 | ||
| 35 | #define CS42L43_MCU_BOOT_STAGE1 1 | 35 | #define CS42L43_MCU_BOOT_STAGE1 1 |
| @@ -48,6 +48,7 @@ · 12 unmodified lines | |||
| 48 | 48 | ||
| 49 | #define CS42L43_MCU_SUPPORTED_REV 0x2105 | 49 | #define CS42L43_MCU_SUPPORTED_REV 0x2105 |
| 50 | #define CS42L43_MCU_SHADOW_REGS_REQUIRED_REV 0x2200 | 50 | #define CS42L43_MCU_SHADOW_REGS_REQUIRED_REV 0x2200 |
| 51 | #define CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV 0x1002 | ||
| 51 | #define CS42L43_MCU_SUPPORTED_BIOS_REV 0x0001 | 52 | #define CS42L43_MCU_SUPPORTED_BIOS_REV 0x0001 |
| 52 | 53 | ||
| 53 | #define CS42L43_VDDP_DELAY_US 50 | 54 | #define CS42L43_VDDP_DELAY_US 50 |
| @@ -773,7 +774,8 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) · 719 unmodified lines | |||
| 773 | * Later versions of the firmwware require the driver to access some | 774 | * Later versions of the firmwware require the driver to access some |
| 774 | * features through a set of shadow registers. | 775 | * features through a set of shadow registers. |
| 775 | */ | 776 | */ |
| 776 | shadow = mcu_rev >= CS42L43_MCU_SHADOW_REGS_REQUIRED_REV; | 777 | shadow = (mcu_rev >= CS42L43_MCU_SHADOW_REGS_REQUIRED_REV) || |
| 778 | (bios_rev >= CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV); | ||
| 777 | 779 | ||
| 778 | ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_CONTROL, &secure_cfg); | 780 | ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_CONTROL, &secure_cfg); |
| 779 | if (ret) { | 781 | if (ret) { |
| @@ -982,7 +984,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) · 202 unmodified lines | |||
| 982 | /* vdd-p must be on for 50uS before any other supply */ | 984 | /* vdd-p must be on for 50uS before any other supply */ |
| 983 | usleep_range(CS42L43_VDDP_DELAY_US, 2 * CS42L43_VDDP_DELAY_US); | 985 | usleep_range(CS42L43_VDDP_DELAY_US, 2 * CS42L43_VDDP_DELAY_US); |
| 984 | 986 | ||
| 985 | gpiod_set_value_cansleep(cs42l43->reset, 1); | 987 | gpiod_set_raw_value_cansleep(cs42l43->reset, 1); |
| 986 | 988 | ||
| 987 | ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); | 989 | ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); |
| 988 | if (ret) { | 990 | if (ret) { |
| @@ -1003,7 +1005,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) · 14 unmodified lines | |||
| 1003 | err_core_supplies: | 1005 | err_core_supplies: |
| 1004 | regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); | 1006 | regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); |
| 1005 | err_reset: | 1007 | err_reset: |
| 1006 | gpiod_set_value_cansleep(cs42l43->reset, 0); | 1008 | gpiod_set_raw_value_cansleep(cs42l43->reset, 0); |
| 1007 | regulator_disable(cs42l43->vdd_p); | 1009 | regulator_disable(cs42l43->vdd_p); |
| 1008 | 1010 | ||
| 1009 | return ret; | 1011 | return ret; |
| @@ -1025,7 +1027,7 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) · 15 unmodified lines | |||
| 1025 | return ret; | 1027 | return ret; |
| 1026 | } | 1028 | } |
| 1027 | 1029 | ||
| 1028 | gpiod_set_value_cansleep(cs42l43->reset, 0); | 1030 | gpiod_set_raw_value_cansleep(cs42l43->reset, 0); |
| 1029 | 1031 | ||
| 1030 | ret = regulator_disable(cs42l43->vdd_p); | 1032 | ret = regulator_disable(cs42l43->vdd_p); |
| 1031 | if (ret) { | 1033 | if (ret) { |
| @@ -1036,6 +1038,15 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) · 4 unmodified lines | |||
| 1036 | return 0; | 1038 | return 0; |
| 1037 | } | 1039 | } |
| 1038 | 1040 | ||
| 1041 | static void cs42l43_dev_remove(void *data) | ||
| 1042 | { | ||
| 1043 | struct cs42l43 *cs42l43 = data; | ||
| 1044 | |||
| 1045 | cancel_work_sync(&cs42l43->boot_work); | ||
| 1046 | |||
| 1047 | cs42l43_power_down(cs42l43); | ||
| 1048 | } | ||
| 1049 | |||
| 1039 | int cs42l43_dev_probe(struct cs42l43 *cs42l43) | 1050 | int cs42l43_dev_probe(struct cs42l43 *cs42l43) |
| 1040 | { | 1051 | { |
| 1041 | int i, ret; | 1052 | int i, ret; |
| @@ -1050,11 +1061,13 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) · 8 unmodified lines | |||
| 1050 | 1061 | ||
| 1051 | regcache_cache_only(cs42l43->regmap, true); | 1062 | regcache_cache_only(cs42l43->regmap, true); |
| 1052 | 1063 | ||
| 1053 | cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_LOW); | 1064 | cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_HIGH); |
| 1054 | if (IS_ERR(cs42l43->reset)) | 1065 | if (IS_ERR(cs42l43->reset)) |
| 1055 | return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->reset), | 1066 | return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->reset), |
| 1056 | "Failed to get reset\n"); | 1067 | "Failed to get reset\n"); |
| 1057 | 1068 | ||
| 1069 | gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | ||
| 1070 | |||
| 1058 | cs42l43->vdd_p = devm_regulator_get(cs42l43->dev, "vdd-p"); | 1071 | cs42l43->vdd_p = devm_regulator_get(cs42l43->dev, "vdd-p"); |
| 1059 | if (IS_ERR(cs42l43->vdd_p)) | 1072 | if (IS_ERR(cs42l43->vdd_p)) |
| 1060 | return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->vdd_p), | 1073 | return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->vdd_p), |
| @@ -1080,6 +1093,10 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) · 19 unmodified lines | |||
| 1080 | if (ret) | 1093 | if (ret) |
| 1081 | return ret; | 1094 | return ret; |
| 1082 | 1095 | ||
| 1096 | ret = devm_add_action_or_reset(cs42l43->dev, cs42l43_dev_remove, cs42l43); | ||
| 1097 | if (ret) | ||
| 1098 | return ret; | ||
| 1099 | |||
| 1083 | pm_runtime_set_autosuspend_delay(cs42l43->dev, CS42L43_AUTOSUSPEND_TIME_MS); | 1100 | pm_runtime_set_autosuspend_delay(cs42l43->dev, CS42L43_AUTOSUSPEND_TIME_MS); |
| 1084 | pm_runtime_use_autosuspend(cs42l43->dev); | 1101 | pm_runtime_use_autosuspend(cs42l43->dev); |
| 1085 | pm_runtime_set_active(cs42l43->dev); | 1102 | pm_runtime_set_active(cs42l43->dev); |
| @@ -1098,14 +1115,6 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) · 12 unmodified lines | |||
| 1098 | } | 1115 | } |
| 1099 | EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43"); | 1116 | EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43"); |
| 1100 | 1117 | ||
| 1101 | void cs42l43_dev_remove(struct cs42l43 *cs42l43) | ||
| 1102 | { | ||
| 1103 | cancel_work_sync(&cs42l43->boot_work); | ||
| 1104 | |||
| 1105 | cs42l43_power_down(cs42l43); | ||
| 1106 | } | ||
| 1107 | EXPORT_SYMBOL_NS_GPL(cs42l43_dev_remove, "MFD_CS42L43"); | ||
| 1108 | |||
| 1109 | static int cs42l43_suspend(struct device *dev) | 1118 | static int cs42l43_suspend(struct device *dev) |
| 1110 | { | 1119 | { |
| 1111 | struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | 1120 | struct cs42l43 *cs42l43 = dev_get_drvdata(dev); |
| @@ -25,6 +25,5 @@ bool cs42l43_precious_register(struct device *dev, unsigned int reg); · 24 unmodified lines | |||
| 25 | bool cs42l43_volatile_register(struct device *dev, unsigned int reg); | 25 | bool cs42l43_volatile_register(struct device *dev, unsigned int reg); |
| 26 | 26 | ||
| 27 | int cs42l43_dev_probe(struct cs42l43 *cs42l43); | 27 | int cs42l43_dev_probe(struct cs42l43 *cs42l43); |
| 28 | void cs42l43_dev_remove(struct cs42l43 *cs42l43); | ||
| 29 | 28 | ||
| 30 | #endif /* CS42L43_CORE_INT_H */ | 29 | #endif /* CS42L43_CORE_INT_H */ |
| @@ -585,6 +585,7 @@ static int da9052_clear_fault_log(struct da9052 *da9052) · 584 unmodified lines | |||
| 585 | "Cannot reset FAULT_LOG values %d\n", ret); | 585 | "Cannot reset FAULT_LOG values %d\n", ret); |
| 586 | } | 586 | } |
| 587 | 587 | ||
| 588 | da9052->fault_log = fault_log; | ||
| 588 | return ret; | 589 | return ret; |
| 589 | } | 590 | } |
| 590 | 591 | ||
| @@ -81,7 +81,7 @@ static struct mfd_cell chtdc_ti_dev[] = { · 80 unmodified lines | |||
| 81 | static const struct regmap_config chtdc_ti_regmap_config = { | 81 | static const struct regmap_config chtdc_ti_regmap_config = { |
| 82 | .reg_bits = 8, | 82 | .reg_bits = 8, |
| 83 | .val_bits = 8, | 83 | .val_bits = 8, |
| 84 | .max_register = 128, | 84 | .max_register = 0xff, |
| 85 | .cache_type = REGCACHE_NONE, | 85 | .cache_type = REGCACHE_NONE, |
| 86 | }; | 86 | }; |
| 87 | 87 | ||
| @@ -834,8 +834,9 @@ static const struct pci_device_id lpc_ich_ids[] = { · 833 unmodified lines | |||
| 834 | { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME}, | 834 | { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME}, |
| 835 | { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9}, | 835 | { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9}, |
| 836 | { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M}, | 836 | { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M}, |
| 837 | { PCI_VDEVICE(INTEL, 0x3197), LPC_GLK}, | ||
| 838 | { PCI_VDEVICE(INTEL, 0x2b9c), LPC_COUGARMOUNTAIN}, | 837 | { PCI_VDEVICE(INTEL, 0x2b9c), LPC_COUGARMOUNTAIN}, |
| 838 | { PCI_VDEVICE(INTEL, 0x3197), LPC_GLK}, | ||
| 839 | { PCI_VDEVICE(INTEL, 0x31e8), LPC_GLK}, | ||
| 839 | { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO}, | 840 | { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO}, |
| 840 | { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R}, | 841 | { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R}, |
| 841 | { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10}, | 842 | { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10}, |
| @@ -0,0 +1,338 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Core driver for the microcontroller unit in QNAP NAS devices that is | ||
| 4 | * connected via a dedicated UART port. | ||
| 5 | * | ||
| 6 | * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/cleanup.h> | ||
| 10 | #include <linux/export.h> | ||
| 11 | #include <linux/mfd/core.h> | ||
| 12 | #include <linux/mfd/qnap-mcu.h> | ||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/of.h> | ||
| 15 | #include <linux/reboot.h> | ||
| 16 | #include <linux/serdev.h> | ||
| 17 | #include <linux/slab.h> | ||
| 18 | |||
| 19 | /* The longest command found so far is 5 bytes long */ | ||
| 20 | #define QNAP_MCU_MAX_CMD_SIZE 5 | ||
| 21 | #define QNAP_MCU_MAX_DATA_SIZE 36 | ||
| 22 | #define QNAP_MCU_CHECKSUM_SIZE 1 | ||
| 23 | |||
| 24 | #define QNAP_MCU_RX_BUFFER_SIZE \ | ||
| 25 | (QNAP_MCU_MAX_DATA_SIZE + QNAP_MCU_CHECKSUM_SIZE) | ||
| 26 | |||
| 27 | #define QNAP_MCU_TX_BUFFER_SIZE \ | ||
| 28 | (QNAP_MCU_MAX_CMD_SIZE + QNAP_MCU_CHECKSUM_SIZE) | ||
| 29 | |||
| 30 | #define QNAP_MCU_ACK_LEN 2 | ||
| 31 | #define QNAP_MCU_VERSION_LEN 4 | ||
| 32 | |||
| 33 | #define QNAP_MCU_TIMEOUT_MS 500 | ||
| 34 | |||
| 35 | /** | ||
| 36 | * struct qnap_mcu_reply - Reply to a command | ||
| 37 | * | ||
| 38 | * @data: Buffer to store reply payload in | ||
| 39 | * @length: Expected reply length, including the checksum | ||
| 40 | * @received: Received number of bytes, so far | ||
| 41 | * @done: Triggered when the entire reply has been received | ||
| 42 | */ | ||
| 43 | struct qnap_mcu_reply { | ||
| 44 | u8 *data; | ||
| 45 | size_t length; | ||
| 46 | size_t received; | ||
| 47 | struct completion done; | ||
| 48 | }; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * struct qnap_mcu - QNAP NAS embedded controller | ||
| 52 | * | ||
| 53 | * @serdev: Pointer to underlying serdev | ||
| 54 | * @bus_lock: Lock to serialize access to the device | ||
| 55 | * @reply: Reply data structure | ||
| 56 | * @variant: Device variant specific information | ||
| 57 | * @version: MCU firmware version | ||
| 58 | */ | ||
| 59 | struct qnap_mcu { | ||
| 60 | struct serdev_device *serdev; | ||
| 61 | struct mutex bus_lock; | ||
| 62 | struct qnap_mcu_reply reply; | ||
| 63 | const struct qnap_mcu_variant *variant; | ||
| 64 | u8 version[QNAP_MCU_VERSION_LEN]; | ||
| 65 | }; | ||
| 66 | |||
| 67 | /* | ||
| 68 | * The QNAP-MCU uses a basic XOR checksum. | ||
| 69 | * It is always the last byte and XORs the whole previous message. | ||
| 70 | */ | ||
| 71 | static u8 qnap_mcu_csum(const u8 *buf, size_t size) | ||
| 72 | { | ||
| 73 | u8 csum = 0; | ||
| 74 | |||
| 75 | while (size--) | ||
| 76 | csum ^= *buf++; | ||
| 77 | |||
| 78 | return csum; | ||
| 79 | } | ||
| 80 | |||
| 81 | static int qnap_mcu_write(struct qnap_mcu *mcu, const u8 *data, u8 data_size) | ||
| 82 | { | ||
| 83 | unsigned char tx[QNAP_MCU_TX_BUFFER_SIZE]; | ||
| 84 | size_t length = data_size + QNAP_MCU_CHECKSUM_SIZE; | ||
| 85 | |||
| 86 | if (length > sizeof(tx)) { | ||
| 87 | dev_err(&mcu->serdev->dev, "data too big for transmit buffer"); | ||
| 88 | return -EINVAL; | ||
| 89 | } | ||
| 90 | |||
| 91 | memcpy(tx, data, data_size); | ||
| 92 | tx[data_size] = qnap_mcu_csum(data, data_size); | ||
| 93 | |||
| 94 | serdev_device_write_flush(mcu->serdev); | ||
| 95 | |||
| 96 | return serdev_device_write(mcu->serdev, tx, length, HZ); | ||
| 97 | } | ||
| 98 | |||
| 99 | static size_t qnap_mcu_receive_buf(struct serdev_device *serdev, const u8 *buf, size_t size) | ||
| 100 | { | ||
| 101 | struct device *dev = &serdev->dev; | ||
| 102 | struct qnap_mcu *mcu = dev_get_drvdata(dev); | ||
| 103 | struct qnap_mcu_reply *reply = &mcu->reply; | ||
| 104 | const u8 *src = buf; | ||
| 105 | const u8 *end = buf + size; | ||
| 106 | |||
| 107 | if (!reply->length) { | ||
| 108 | dev_warn(dev, "Received %zu bytes, we were not waiting for\n", size); | ||
| 109 | return size; | ||
| 110 | } | ||
| 111 | |||
| 112 | while (src < end) { | ||
| 113 | reply->data[reply->received] = *src++; | ||
| 114 | reply->received++; | ||
| 115 | |||
| 116 | if (reply->received == reply->length) { | ||
| 117 | /* We don't expect any characters from the device now */ | ||
| 118 | reply->length = 0; | ||
| 119 | |||
| 120 | complete(&reply->done); | ||
| 121 | |||
| 122 | /* | ||
| 123 | * We report the consumed number of bytes. If there | ||
| 124 | * are still bytes remaining (though there shouldn't) | ||
| 125 | * the serdev layer will re-execute this handler with | ||
| 126 | * the remainder of the Rx bytes. | ||
| 127 | */ | ||
| 128 | return src - buf; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /* | ||
| 133 | * The only way to get out of the above loop and end up here | ||
| 134 | * is through consuming all of the supplied data, so here we | ||
| 135 | * report that we processed it all. | ||
| 136 | */ | ||
| 137 | return size; | ||
| 138 | } | ||
| 139 | |||
| 140 | static const struct serdev_device_ops qnap_mcu_serdev_device_ops = { | ||
| 141 | .receive_buf = qnap_mcu_receive_buf, | ||
| 142 | .write_wakeup = serdev_device_write_wakeup, | ||
| 143 | }; | ||
| 144 | |||
| 145 | int qnap_mcu_exec(struct qnap_mcu *mcu, | ||
| 146 | const u8 *cmd_data, size_t cmd_data_size, | ||
| 147 | u8 *reply_data, size_t reply_data_size) | ||
| 148 | { | ||
| 149 | unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE]; | ||
| 150 | size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE; | ||
| 151 | struct qnap_mcu_reply *reply = &mcu->reply; | ||
| 152 | int ret = 0; | ||
| 153 | |||
| 154 | if (length > sizeof(rx)) { | ||
| 155 | dev_err(&mcu->serdev->dev, "expected data too big for receive buffer"); | ||
| 156 | return -EINVAL; | ||
| 157 | } | ||
| 158 | |||
| 159 | mutex_lock(&mcu->bus_lock); | ||
| 160 | |||
| 161 | reply->data = rx, | ||
| 162 | reply->length = length, | ||
| 163 | reply->received = 0, | ||
| 164 | reinit_completion(&reply->done); | ||
| 165 | |||
| 166 | qnap_mcu_write(mcu, cmd_data, cmd_data_size); | ||
| 167 | |||
| 168 | serdev_device_wait_until_sent(mcu->serdev, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS)); | ||
| 169 | |||
| 170 | if (!wait_for_completion_timeout(&reply->done, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS))) { | ||
| 171 | dev_err(&mcu->serdev->dev, "Command timeout\n"); | ||
| 172 | ret = -ETIMEDOUT; | ||
| 173 | } else { | ||
| 174 | u8 crc = qnap_mcu_csum(rx, reply_data_size); | ||
| 175 | |||
| 176 | if (crc != rx[reply_data_size]) { | ||
| 177 | dev_err(&mcu->serdev->dev, | ||
| 178 | "Invalid Checksum received\n"); | ||
| 179 | ret = -EIO; | ||
| 180 | } else { | ||
| 181 | memcpy(reply_data, rx, reply_data_size); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | mutex_unlock(&mcu->bus_lock); | ||
| 186 | return ret; | ||
| 187 | } | ||
| 188 | EXPORT_SYMBOL_GPL(qnap_mcu_exec); | ||
| 189 | |||
| 190 | int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu, | ||
| 191 | const u8 *cmd_data, size_t cmd_data_size) | ||
| 192 | { | ||
| 193 | u8 ack[QNAP_MCU_ACK_LEN]; | ||
| 194 | int ret; | ||
| 195 | |||
| 196 | ret = qnap_mcu_exec(mcu, cmd_data, cmd_data_size, ack, sizeof(ack)); | ||
| 197 | if (ret) | ||
| 198 | return ret; | ||
| 199 | |||
| 200 | /* Should return @0 */ | ||
| 201 | if (ack[0] != '@' || ack[1] != '0') { | ||
| 202 | dev_err(&mcu->serdev->dev, "Did not receive ack\n"); | ||
| 203 | return -EIO; | ||
| 204 | } | ||
| 205 | |||
| 206 | return 0; | ||
| 207 | } | ||
| 208 | EXPORT_SYMBOL_GPL(qnap_mcu_exec_with_ack); | ||
| 209 | |||
| 210 | static int qnap_mcu_get_version(struct qnap_mcu *mcu) | ||
| 211 | { | ||
| 212 | const u8 cmd[] = { '%', 'V' }; | ||
| 213 | u8 rx[14]; | ||
| 214 | int ret; | ||
| 215 | |||
| 216 | /* Reply is the 2 command-bytes + 4 bytes describing the version */ | ||
| 217 | ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, QNAP_MCU_VERSION_LEN + 2); | ||
| 218 | if (ret) | ||
| 219 | return ret; | ||
| 220 | |||
| 221 | memcpy(mcu->version, &rx[2], QNAP_MCU_VERSION_LEN); | ||
| 222 | |||
| 223 | return 0; | ||
| 224 | } | ||
| 225 | |||
| 226 | /* | ||
| 227 | * The MCU controls power to the peripherals but not the CPU. | ||
| 228 | * | ||
| 229 | * So using the PMIC to power off the system keeps the MCU and hard-drives | ||
| 230 | * running. This also then prevents the system from turning back on until | ||
| 231 | * the MCU is turned off by unplugging the power cable. | ||
| 232 | * Turning off the MCU alone on the other hand turns off the hard drives, | ||
| 233 | * LEDs, etc while the main SoC stays running - including its network ports. | ||
| 234 | */ | ||
| 235 | static int qnap_mcu_power_off(struct sys_off_data *data) | ||
| 236 | { | ||
| 237 | const u8 cmd[] = { '@', 'C', '0' }; | ||
| 238 | struct qnap_mcu *mcu = data->cb_data; | ||
| 239 | int ret; | ||
| 240 | |||
| 241 | ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); | ||
| 242 | if (ret) { | ||
| 243 | dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret); | ||
| 244 | return NOTIFY_STOP; | ||
| 245 | } | ||
| 246 | |||
| 247 | return NOTIFY_DONE; | ||
| 248 | } | ||
| 249 | |||
| 250 | static const struct qnap_mcu_variant qnap_ts433_mcu = { | ||
| 251 | .baud_rate = 115200, | ||
| 252 | .num_drives = 4, | ||
| 253 | .fan_pwm_min = 51, /* Specified in original model.conf */ | ||
| 254 | .fan_pwm_max = 255, | ||
| 255 | .usb_led = true, | ||
| 256 | }; | ||
| 257 | |||
| 258 | static struct mfd_cell qnap_mcu_cells[] = { | ||
| 259 | { .name = "qnap-mcu-input", }, | ||
| 260 | { .name = "qnap-mcu-leds", }, | ||
| 261 | { .name = "qnap-mcu-hwmon", } | ||
| 262 | }; | ||
| 263 | |||
| 264 | static int qnap_mcu_probe(struct serdev_device *serdev) | ||
| 265 | { | ||
| 266 | struct device *dev = &serdev->dev; | ||
| 267 | struct qnap_mcu *mcu; | ||
| 268 | int ret; | ||
| 269 | |||
| 270 | mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); | ||
| 271 | if (!mcu) | ||
| 272 | return -ENOMEM; | ||
| 273 | |||
| 274 | mcu->serdev = serdev; | ||
| 275 | dev_set_drvdata(dev, mcu); | ||
| 276 | |||
| 277 | mcu->variant = of_device_get_match_data(dev); | ||
| 278 | if (!mcu->variant) | ||
| 279 | return -ENODEV; | ||
| 280 | |||
| 281 | mutex_init(&mcu->bus_lock); | ||
| 282 | init_completion(&mcu->reply.done); | ||
| 283 | |||
| 284 | serdev_device_set_client_ops(serdev, &qnap_mcu_serdev_device_ops); | ||
| 285 | ret = devm_serdev_device_open(dev, serdev); | ||
| 286 | if (ret) | ||
| 287 | return ret; | ||
| 288 | |||
| 289 | serdev_device_set_baudrate(serdev, mcu->variant->baud_rate); | ||
| 290 | serdev_device_set_flow_control(serdev, false); | ||
| 291 | |||
| 292 | ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); | ||
| 293 | if (ret) | ||
| 294 | return dev_err_probe(dev, ret, "Failed to set parity\n"); | ||
| 295 | |||
| 296 | ret = qnap_mcu_get_version(mcu); | ||
| 297 | if (ret) | ||
| 298 | return ret; | ||
| 299 | |||
| 300 | ret = devm_register_sys_off_handler(dev, | ||
| 301 | SYS_OFF_MODE_POWER_OFF_PREPARE, | ||
| 302 | SYS_OFF_PRIO_DEFAULT, | ||
| 303 | &qnap_mcu_power_off, mcu); | ||
| 304 | if (ret) | ||
| 305 | return dev_err_probe(dev, ret, | ||
| 306 | "Failed to register poweroff handler\n"); | ||
| 307 | |||
| 308 | for (int i = 0; i < ARRAY_SIZE(qnap_mcu_cells); i++) { | ||
| 309 | qnap_mcu_cells[i].platform_data = mcu->variant; | ||
| 310 | qnap_mcu_cells[i].pdata_size = sizeof(*mcu->variant); | ||
| 311 | } | ||
| 312 | |||
| 313 | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, qnap_mcu_cells, | ||
| 314 | ARRAY_SIZE(qnap_mcu_cells), NULL, 0, NULL); | ||
| 315 | if (ret) | ||
| 316 | return dev_err_probe(dev, ret, "Failed to add child devices\n"); | ||
| 317 | |||
| 318 | return 0; | ||
| 319 | } | ||
| 320 | |||
| 321 | static const struct of_device_id qnap_mcu_dt_ids[] = { | ||
| 322 | { .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu }, | ||
| 323 | { /* sentinel */ } | ||
| 324 | }; | ||
| 325 | MODULE_DEVICE_TABLE(of, qnap_mcu_dt_ids); | ||
| 326 | |||
| 327 | static struct serdev_device_driver qnap_mcu_drv = { | ||
| 328 | .probe = qnap_mcu_probe, | ||
| 329 | .driver = { | ||
| 330 | .name = "qnap-mcu", | ||
| 331 | .of_match_table = qnap_mcu_dt_ids, | ||
| 332 | }, | ||
| 333 | }; | ||
| 334 | module_serdev_device_driver(qnap_mcu_drv); | ||
| 335 | |||
| 336 | MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); | ||
| 337 | MODULE_DESCRIPTION("QNAP MCU core driver"); | ||
| 338 | MODULE_LICENSE("GPL"); | ||
| @@ -170,11 +170,7 @@ static int stpmic1_probe(struct i2c_client *i2c) · 169 unmodified lines | |||
| 170 | return ret; | 170 | return ret; |
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | ret = devm_register_sys_off_handler(ddata->dev, | 173 | ret = devm_register_power_off_handler(ddata->dev, stpmic1_power_off, ddata); |
| 174 | SYS_OFF_MODE_POWER_OFF, | ||
| 175 | SYS_OFF_PRIO_DEFAULT, | ||
| 176 | stpmic1_power_off, | ||
| 177 | ddata); | ||
| 178 | if (ret) { | 174 | if (ret) { |
| 179 | dev_err(ddata->dev, "failed to register sys-off handler: %d\n", ret); | 175 | dev_err(ddata->dev, "failed to register sys-off handler: %d\n", ret); |
| 180 | return ret; | 176 | return ret; |
| @@ -12,22 +12,16 @@ · 11 unmodified lines | |||
| 12 | #include <linux/clk.h> | 12 | #include <linux/clk.h> |
| 13 | #include <linux/err.h> | 13 | #include <linux/err.h> |
| 14 | #include <linux/hwspinlock.h> | 14 | #include <linux/hwspinlock.h> |
| 15 | #include <linux/io.h> | ||
| 16 | #include <linux/init.h> | ||
| 17 | #include <linux/list.h> | 15 | #include <linux/list.h> |
| 16 | #include <linux/mutex.h> | ||
| 18 | #include <linux/of.h> | 17 | #include <linux/of.h> |
| 19 | #include <linux/of_address.h> | 18 | #include <linux/of_address.h> |
| 20 | #include <linux/of_platform.h> | ||
| 21 | #include <linux/platform_data/syscon.h> | ||
| 22 | #include <linux/platform_device.h> | ||
| 23 | #include <linux/regmap.h> | 19 | #include <linux/regmap.h> |
| 24 | #include <linux/reset.h> | 20 | #include <linux/reset.h> |
| 25 | #include <linux/mfd/syscon.h> | 21 | #include <linux/mfd/syscon.h> |
| 26 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
| 27 | 23 | ||
| 28 | static struct platform_driver syscon_driver; | 24 | static DEFINE_MUTEX(syscon_list_lock); |
| 29 | |||
| 30 | static DEFINE_SPINLOCK(syscon_list_slock); | ||
| 31 | static LIST_HEAD(syscon_list); | 25 | static LIST_HEAD(syscon_list); |
| 32 | 26 | ||
| 33 | struct syscon { | 27 | struct syscon { |
| @@ -54,6 +48,8 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res) · 20 unmodified lines | |||
| 54 | struct resource res; | 48 | struct resource res; |
| 55 | struct reset_control *reset; | 49 | struct reset_control *reset; |
| 56 | 50 | ||
| 51 | WARN_ON(!mutex_is_locked(&syscon_list_lock)); | ||
| 52 | |||
| 57 | struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL); | 53 | struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL); |
| 58 | if (!syscon) | 54 | if (!syscon) |
| 59 | return ERR_PTR(-ENOMEM); | 55 | return ERR_PTR(-ENOMEM); |
| @@ -146,9 +142,7 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res) · 86 unmodified lines | |||
| 146 | syscon->regmap = regmap; | 142 | syscon->regmap = regmap; |
| 147 | syscon->np = np; | 143 | syscon->np = np; |
| 148 | 144 | ||
| 149 | spin_lock(&syscon_list_slock); | ||
| 150 | list_add_tail(&syscon->list, &syscon_list); | 145 | list_add_tail(&syscon->list, &syscon_list); |
| 151 | spin_unlock(&syscon_list_slock); | ||
| 152 | 146 | ||
| 153 | return_ptr(syscon); | 147 | return_ptr(syscon); |
| 154 | 148 | ||
| @@ -169,7 +163,7 @@ static struct regmap *device_node_get_regmap(struct device_node *np, · 14 unmodified lines | |||
| 169 | { | 163 | { |
| 170 | struct syscon *entry, *syscon = NULL; | 164 | struct syscon *entry, *syscon = NULL; |
| 171 | 165 | ||
| 172 | spin_lock(&syscon_list_slock); | 166 | mutex_lock(&syscon_list_lock); |
| 173 | 167 | ||
| 174 | list_for_each_entry(entry, &syscon_list, list) | 168 | list_for_each_entry(entry, &syscon_list, list) |
| 175 | if (entry->np == np) { | 169 | if (entry->np == np) { |
| @@ -177,10 +171,13 @@ static struct regmap *device_node_get_regmap(struct device_node *np, · 1 unmodified line | |||
| 177 | break; | 171 | break; |
| 178 | } | 172 | } |
| 179 | 173 | ||
| 180 | spin_unlock(&syscon_list_slock); | 174 | if (!syscon) { |
| 181 | 175 | if (of_device_is_compatible(np, "syscon")) | |
| 182 | if (!syscon) | 176 | syscon = of_syscon_register(np, check_res); |
| 183 | syscon = of_syscon_register(np, check_res); | 177 | else |
| 178 | syscon = ERR_PTR(-EINVAL); | ||
| 179 | } | ||
| 180 | mutex_unlock(&syscon_list_lock); | ||
| 184 | 181 | ||
| 185 | if (IS_ERR(syscon)) | 182 | if (IS_ERR(syscon)) |
| 186 | return ERR_CAST(syscon); | 183 | return ERR_CAST(syscon); |
| @@ -212,7 +209,7 @@ int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) · 25 unmodified lines | |||
| 212 | return -ENOMEM; | 209 | return -ENOMEM; |
| 213 | 210 | ||
| 214 | /* check if syscon entry already exists */ | 211 | /* check if syscon entry already exists */ |
| 215 | spin_lock(&syscon_list_slock); | 212 | mutex_lock(&syscon_list_lock); |
| 216 | 213 | ||
| 217 | list_for_each_entry(entry, &syscon_list, list) | 214 | list_for_each_entry(entry, &syscon_list, list) |
| 218 | if (entry->np == np) { | 215 | if (entry->np == np) { |
| @@ -225,12 +222,12 @@ int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) · 6 unmodified lines | |||
| 225 | 222 | ||
| 226 | /* register the regmap in syscon list */ | 223 | /* register the regmap in syscon list */ |
| 227 | list_add_tail(&syscon->list, &syscon_list); | 224 | list_add_tail(&syscon->list, &syscon_list); |
| 228 | spin_unlock(&syscon_list_slock); | 225 | mutex_unlock(&syscon_list_lock); |
| 229 | 226 | ||
| 230 | return 0; | 227 | return 0; |
| 231 | 228 | ||
| 232 | err_unlock: | 229 | err_unlock: |
| 233 | spin_unlock(&syscon_list_slock); | 230 | mutex_unlock(&syscon_list_lock); |
| 234 | kfree(syscon); | 231 | kfree(syscon); |
| 235 | return ret; | 232 | return ret; |
| 236 | } | 233 | } |
| @@ -244,9 +241,6 @@ EXPORT_SYMBOL_GPL(device_node_to_regmap); · 7 unmodified lines | |||
| 244 | 241 | ||
| 245 | struct regmap *syscon_node_to_regmap(struct device_node *np) | 242 | struct regmap *syscon_node_to_regmap(struct device_node *np) |
| 246 | { | 243 | { |
| 247 | if (!of_device_is_compatible(np, "syscon")) | ||
| 248 | return ERR_PTR(-EINVAL); | ||
| 249 | |||
| 250 | return device_node_get_regmap(np, true); | 244 | return device_node_get_regmap(np, true); |
| 251 | } | 245 | } |
| 252 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); | 246 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); |
| @@ -336,62 +330,3 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, · 83 unmodified lines | |||
| 336 | return regmap; | 330 | return regmap; |
| 337 | } | 331 | } |
| 338 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); | 332 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); |
| 339 | |||
| 340 | static int syscon_probe(struct platform_device *pdev) | ||
| 341 | { | ||
| 342 | struct device *dev = &pdev->dev; | ||
| 343 | struct syscon_platform_data *pdata = dev_get_platdata(dev); | ||
| 344 | struct syscon *syscon; | ||
| 345 | struct regmap_config syscon_config = syscon_regmap_config; | ||
| 346 | struct resource *res; | ||
| 347 | void __iomem *base; | ||
| 348 | |||
| 349 | syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); | ||
| 350 | if (!syscon) | ||
| 351 | return -ENOMEM; | ||
| 352 | |||
| 353 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 354 | if (!res) | ||
| 355 | return -ENOENT; | ||
| 356 | |||
| 357 | base = devm_ioremap(dev, res->start, resource_size(res)); | ||
| 358 | if (!base) | ||
| 359 | return -ENOMEM; | ||
| 360 | |||
| 361 | syscon_config.max_register = resource_size(res) - 4; | ||
| 362 | if (!syscon_config.max_register) | ||
| 363 | syscon_config.max_register_is_0 = true; | ||
| 364 | |||
| 365 | if (pdata) | ||
| 366 | syscon_config.name = pdata->label; | ||
| 367 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); | ||
| 368 | if (IS_ERR(syscon->regmap)) { | ||
| 369 | dev_err(dev, "regmap init failed\n"); | ||
| 370 | return PTR_ERR(syscon->regmap); | ||
| 371 | } | ||
| 372 | |||
| 373 | platform_set_drvdata(pdev, syscon); | ||
| 374 | |||
| 375 | dev_dbg(dev, "regmap %pR registered\n", res); | ||
| 376 | |||
| 377 | return 0; | ||
| 378 | } | ||
| 379 | |||
| 380 | static const struct platform_device_id syscon_ids[] = { | ||
| 381 | { "syscon", }, | ||
| 382 | { } | ||
| 383 | }; | ||
| 384 | |||
| 385 | static struct platform_driver syscon_driver = { | ||
| 386 | .driver = { | ||
| 387 | .name = "syscon", | ||
| 388 | }, | ||
| 389 | .probe = syscon_probe, | ||
| 390 | .id_table = syscon_ids, | ||
| 391 | }; | ||
| 392 | |||
| 393 | static int __init syscon_init(void) | ||
| 394 | { | ||
| 395 | return platform_driver_register(&syscon_driver); | ||
| 396 | } | ||
| 397 | postcore_initcall(syscon_init); | ||
| @@ -110,19 +110,12 @@ static const struct resource tps65219_regulator_resources[] = { · 109 unmodified lines | |||
| 110 | }; | 110 | }; |
| 111 | 111 | ||
| 112 | static const struct mfd_cell tps65219_cells[] = { | 112 | static const struct mfd_cell tps65219_cells[] = { |
| 113 | { | 113 | MFD_CELL_RES("tps65219-regulator", tps65219_regulator_resources), |
| 114 | .name = "tps65219-regulator", | 114 | MFD_CELL_NAME("tps65219-gpio"), |
| 115 | .resources = tps65219_regulator_resources, | ||
| 116 | .num_resources = ARRAY_SIZE(tps65219_regulator_resources), | ||
| 117 | }, | ||
| 118 | { .name = "tps65219-gpio", }, | ||
| 119 | }; | 115 | }; |
| 120 | 116 | ||
| 121 | static const struct mfd_cell tps65219_pwrbutton_cell = { | 117 | static const struct mfd_cell tps65219_pwrbutton_cell = |
| 122 | .name = "tps65219-pwrbutton", | 118 | MFD_CELL_RES("tps65219-pwrbutton", tps65219_pwrbutton_resources); |
| 123 | .resources = tps65219_pwrbutton_resources, | ||
| 124 | .num_resources = ARRAY_SIZE(tps65219_pwrbutton_resources), | ||
| 125 | }; | ||
| 126 | 119 | ||
| 127 | static const struct regmap_config tps65219_regmap_config = { | 120 | static const struct regmap_config tps65219_regmap_config = { |
| 128 | .reg_bits = 8, | 121 | .reg_bits = 8, |
| @@ -0,0 +1,325 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * UP Board FPGA driver. | ||
| 4 | * | ||
| 5 | * FPGA provides more GPIO driving power, LEDS and pin mux function. | ||
| 6 | * | ||
| 7 | * Copyright (c) AAEON. All rights reserved. | ||
| 8 | * Copyright (C) 2024 Bootlin | ||
| 9 | * | ||
| 10 | * Author: Gary Wang <garywang@aaeon.com.tw> | ||
| 11 | * Author: Thomas Richard <thomas.richard@bootlin.com> | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/acpi.h> | ||
| 15 | #include <linux/bitfield.h> | ||
| 16 | #include <linux/device.h> | ||
| 17 | #include <linux/err.h> | ||
| 18 | #include <linux/gpio/consumer.h> | ||
| 19 | #include <linux/mfd/core.h> | ||
| 20 | #include <linux/mfd/upboard-fpga.h> | ||
| 21 | #include <linux/module.h> | ||
| 22 | #include <linux/mod_devicetable.h> | ||
| 23 | #include <linux/platform_device.h> | ||
| 24 | #include <linux/property.h> | ||
| 25 | #include <linux/regmap.h> | ||
| 26 | #include <linux/sysfs.h> | ||
| 27 | |||
| 28 | #define UPBOARD_AAEON_MANUFACTURER_ID 0x01 | ||
| 29 | #define UPBOARD_MANUFACTURER_ID_MASK GENMASK(7, 0) | ||
| 30 | |||
| 31 | #define UPBOARD_ADDRESS_SIZE 7 | ||
| 32 | #define UPBOARD_REGISTER_SIZE 16 | ||
| 33 | |||
| 34 | #define UPBOARD_READ_FLAG BIT(UPBOARD_ADDRESS_SIZE) | ||
| 35 | |||
| 36 | #define UPBOARD_FW_ID_MAJOR_SUPPORTED 0x0 | ||
| 37 | |||
| 38 | #define UPBOARD_FW_ID_BUILD_MASK GENMASK(15, 12) | ||
| 39 | #define UPBOARD_FW_ID_MAJOR_MASK GENMASK(11, 8) | ||
| 40 | #define UPBOARD_FW_ID_MINOR_MASK GENMASK(7, 4) | ||
| 41 | #define UPBOARD_FW_ID_PATCH_MASK GENMASK(3, 0) | ||
| 42 | |||
| 43 | static int upboard_fpga_read(void *context, unsigned int reg, unsigned int *val) | ||
| 44 | { | ||
| 45 | struct upboard_fpga *fpga = context; | ||
| 46 | int i; | ||
| 47 | |||
| 48 | /* Clear to start new transaction */ | ||
| 49 | gpiod_set_value(fpga->clear_gpio, 0); | ||
| 50 | gpiod_set_value(fpga->clear_gpio, 1); | ||
| 51 | |||
| 52 | reg |= UPBOARD_READ_FLAG; | ||
| 53 | |||
| 54 | /* Send clock and addr from strobe & datain pins */ | ||
| 55 | for (i = UPBOARD_ADDRESS_SIZE; i >= 0; i--) { | ||
| 56 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 57 | gpiod_set_value(fpga->datain_gpio, !!(reg & BIT(i))); | ||
| 58 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 59 | } | ||
| 60 | |||
| 61 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 62 | *val = 0; | ||
| 63 | |||
| 64 | /* Read data from dataout pin */ | ||
| 65 | for (i = UPBOARD_REGISTER_SIZE - 1; i >= 0; i--) { | ||
| 66 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 67 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 68 | *val |= gpiod_get_value(fpga->dataout_gpio) << i; | ||
| 69 | } | ||
| 70 | |||
| 71 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 72 | |||
| 73 | return 0; | ||
| 74 | } | ||
| 75 | |||
| 76 | static int upboard_fpga_write(void *context, unsigned int reg, unsigned int val) | ||
| 77 | { | ||
| 78 | struct upboard_fpga *fpga = context; | ||
| 79 | int i; | ||
| 80 | |||
| 81 | /* Clear to start new transcation */ | ||
| 82 | gpiod_set_value(fpga->clear_gpio, 0); | ||
| 83 | gpiod_set_value(fpga->clear_gpio, 1); | ||
| 84 | |||
| 85 | /* Send clock and addr from strobe & datain pins */ | ||
| 86 | for (i = UPBOARD_ADDRESS_SIZE; i >= 0; i--) { | ||
| 87 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 88 | gpiod_set_value(fpga->datain_gpio, !!(reg & BIT(i))); | ||
| 89 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 90 | } | ||
| 91 | |||
| 92 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 93 | |||
| 94 | /* Write data to datain pin */ | ||
| 95 | for (i = UPBOARD_REGISTER_SIZE - 1; i >= 0; i--) { | ||
| 96 | gpiod_set_value(fpga->datain_gpio, !!(val & BIT(i))); | ||
| 97 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 98 | gpiod_set_value(fpga->strobe_gpio, 0); | ||
| 99 | } | ||
| 100 | |||
| 101 | gpiod_set_value(fpga->strobe_gpio, 1); | ||
| 102 | |||
| 103 | return 0; | ||
| 104 | } | ||
| 105 | |||
| 106 | static const struct regmap_range upboard_up_readable_ranges[] = { | ||
| 107 | regmap_reg_range(UPBOARD_REG_PLATFORM_ID, UPBOARD_REG_FIRMWARE_ID), | ||
| 108 | regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN0), | ||
| 109 | regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN1), | ||
| 110 | regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR1), | ||
| 111 | }; | ||
| 112 | |||
| 113 | static const struct regmap_range upboard_up_writable_ranges[] = { | ||
| 114 | regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN0), | ||
| 115 | regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN1), | ||
| 116 | regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR1), | ||
| 117 | }; | ||
| 118 | |||
| 119 | static const struct regmap_access_table upboard_up_readable_table = { | ||
| 120 | .yes_ranges = upboard_up_readable_ranges, | ||
| 121 | .n_yes_ranges = ARRAY_SIZE(upboard_up_readable_ranges), | ||
| 122 | }; | ||
| 123 | |||
| 124 | static const struct regmap_access_table upboard_up_writable_table = { | ||
| 125 | .yes_ranges = upboard_up_writable_ranges, | ||
| 126 | .n_yes_ranges = ARRAY_SIZE(upboard_up_writable_ranges), | ||
| 127 | }; | ||
| 128 | |||
| 129 | static const struct regmap_config upboard_up_regmap_config = { | ||
| 130 | .reg_bits = UPBOARD_ADDRESS_SIZE, | ||
| 131 | .val_bits = UPBOARD_REGISTER_SIZE, | ||
| 132 | .max_register = UPBOARD_REG_MAX, | ||
| 133 | .reg_read = upboard_fpga_read, | ||
| 134 | .reg_write = upboard_fpga_write, | ||
| 135 | .fast_io = false, | ||
| 136 | .cache_type = REGCACHE_NONE, | ||
| 137 | .rd_table = &upboard_up_readable_table, | ||
| 138 | .wr_table = &upboard_up_writable_table, | ||
| 139 | }; | ||
| 140 | |||
| 141 | static const struct regmap_range upboard_up2_readable_ranges[] = { | ||
| 142 | regmap_reg_range(UPBOARD_REG_PLATFORM_ID, UPBOARD_REG_FIRMWARE_ID), | ||
| 143 | regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN1), | ||
| 144 | regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN2), | ||
| 145 | regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR2), | ||
| 146 | }; | ||
| 147 | |||
| 148 | static const struct regmap_range upboard_up2_writable_ranges[] = { | ||
| 149 | regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN1), | ||
| 150 | regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN2), | ||
| 151 | regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR2), | ||
| 152 | }; | ||
| 153 | |||
| 154 | static const struct regmap_access_table upboard_up2_readable_table = { | ||
| 155 | .yes_ranges = upboard_up2_readable_ranges, | ||
| 156 | .n_yes_ranges = ARRAY_SIZE(upboard_up2_readable_ranges), | ||
| 157 | }; | ||
| 158 | |||
| 159 | static const struct regmap_access_table upboard_up2_writable_table = { | ||
| 160 | .yes_ranges = upboard_up2_writable_ranges, | ||
| 161 | .n_yes_ranges = ARRAY_SIZE(upboard_up2_writable_ranges), | ||
| 162 | }; | ||
| 163 | |||
| 164 | static const struct regmap_config upboard_up2_regmap_config = { | ||
| 165 | .reg_bits = UPBOARD_ADDRESS_SIZE, | ||
| 166 | .val_bits = UPBOARD_REGISTER_SIZE, | ||
| 167 | .max_register = UPBOARD_REG_MAX, | ||
| 168 | .reg_read = upboard_fpga_read, | ||
| 169 | .reg_write = upboard_fpga_write, | ||
| 170 | .fast_io = false, | ||
| 171 | .cache_type = REGCACHE_NONE, | ||
| 172 | .rd_table = &upboard_up2_readable_table, | ||
| 173 | .wr_table = &upboard_up2_writable_table, | ||
| 174 | }; | ||
| 175 | |||
| 176 | static const struct mfd_cell upboard_up_mfd_cells[] = { | ||
| 177 | { .name = "upboard-pinctrl" }, | ||
| 178 | { .name = "upboard-leds" }, | ||
| 179 | }; | ||
| 180 | |||
| 181 | static const struct upboard_fpga_data upboard_up_fpga_data = { | ||
| 182 | .type = UPBOARD_UP_FPGA, | ||
| 183 | .regmap_config = &upboard_up_regmap_config, | ||
| 184 | }; | ||
| 185 | |||
| 186 | static const struct upboard_fpga_data upboard_up2_fpga_data = { | ||
| 187 | .type = UPBOARD_UP2_FPGA, | ||
| 188 | .regmap_config = &upboard_up2_regmap_config, | ||
| 189 | }; | ||
| 190 | |||
| 191 | static int upboard_fpga_gpio_init(struct upboard_fpga *fpga) | ||
| 192 | { | ||
| 193 | fpga->enable_gpio = devm_gpiod_get(fpga->dev, "enable", GPIOD_ASIS); | ||
| 194 | if (IS_ERR(fpga->enable_gpio)) | ||
| 195 | return PTR_ERR(fpga->enable_gpio); | ||
| 196 | |||
| 197 | fpga->clear_gpio = devm_gpiod_get(fpga->dev, "clear", GPIOD_OUT_LOW); | ||
| 198 | if (IS_ERR(fpga->clear_gpio)) | ||
| 199 | return PTR_ERR(fpga->clear_gpio); | ||
| 200 | |||
| 201 | fpga->strobe_gpio = devm_gpiod_get(fpga->dev, "strobe", GPIOD_OUT_LOW); | ||
| 202 | if (IS_ERR(fpga->strobe_gpio)) | ||
| 203 | return PTR_ERR(fpga->strobe_gpio); | ||
| 204 | |||
| 205 | fpga->datain_gpio = devm_gpiod_get(fpga->dev, "datain", GPIOD_OUT_LOW); | ||
| 206 | if (IS_ERR(fpga->datain_gpio)) | ||
| 207 | return PTR_ERR(fpga->datain_gpio); | ||
| 208 | |||
| 209 | fpga->dataout_gpio = devm_gpiod_get(fpga->dev, "dataout", GPIOD_IN); | ||
| 210 | if (IS_ERR(fpga->dataout_gpio)) | ||
| 211 | return PTR_ERR(fpga->dataout_gpio); | ||
| 212 | |||
| 213 | gpiod_set_value(fpga->enable_gpio, 1); | ||
| 214 | |||
| 215 | return 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | static int upboard_fpga_get_firmware_version(struct upboard_fpga *fpga) | ||
| 219 | { | ||
| 220 | unsigned int platform_id, manufacturer_id; | ||
| 221 | int ret; | ||
| 222 | |||
| 223 | if (!fpga) | ||
| 224 | return -ENOMEM; | ||
| 225 | |||
| 226 | ret = regmap_read(fpga->regmap, UPBOARD_REG_PLATFORM_ID, &platform_id); | ||
| 227 | if (ret) | ||
| 228 | return ret; | ||
| 229 | |||
| 230 | manufacturer_id = platform_id & UPBOARD_MANUFACTURER_ID_MASK; | ||
| 231 | if (manufacturer_id != UPBOARD_AAEON_MANUFACTURER_ID) | ||
| 232 | return dev_err_probe(fpga->dev, -ENODEV, | ||
| 233 | "driver not compatible with custom FPGA FW from manufacturer id %#02x.", | ||
| 234 | manufacturer_id); | ||
| 235 | |||
| 236 | ret = regmap_read(fpga->regmap, UPBOARD_REG_FIRMWARE_ID, &fpga->firmware_version); | ||
| 237 | if (ret) | ||
| 238 | return ret; | ||
| 239 | |||
| 240 | if (FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version) != | ||
| 241 | UPBOARD_FW_ID_MAJOR_SUPPORTED) | ||
| 242 | return dev_err_probe(fpga->dev, -ENODEV, | ||
| 243 | "unsupported FPGA FW v%lu.%lu.%lu build %#02lx", | ||
| 244 | FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version), | ||
| 245 | FIELD_GET(UPBOARD_FW_ID_MINOR_MASK, fpga->firmware_version), | ||
| 246 | FIELD_GET(UPBOARD_FW_ID_PATCH_MASK, fpga->firmware_version), | ||
| 247 | FIELD_GET(UPBOARD_FW_ID_BUILD_MASK, fpga->firmware_version)); | ||
| 248 | return 0; | ||
| 249 | } | ||
| 250 | |||
| 251 | static ssize_t upboard_fpga_version_show(struct device *dev, struct device_attribute *attr, | ||
| 252 | char *buf) | ||
| 253 | { | ||
| 254 | struct upboard_fpga *fpga = dev_get_drvdata(dev); | ||
| 255 | |||
| 256 | return sysfs_emit(buf, "FPGA FW v%lu.%lu.%lu build %#02lx\n", | ||
| 257 | FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version), | ||
| 258 | FIELD_GET(UPBOARD_FW_ID_MINOR_MASK, fpga->firmware_version), | ||
| 259 | FIELD_GET(UPBOARD_FW_ID_PATCH_MASK, fpga->firmware_version), | ||
| 260 | FIELD_GET(UPBOARD_FW_ID_BUILD_MASK, fpga->firmware_version)); | ||
| 261 | } | ||
| 262 | |||
| 263 | static DEVICE_ATTR_RO(upboard_fpga_version); | ||
| 264 | |||
| 265 | static struct attribute *upboard_fpga_attrs[] = { | ||
| 266 | &dev_attr_upboard_fpga_version.attr, | ||
| 267 | NULL | ||
| 268 | }; | ||
| 269 | |||
| 270 | ATTRIBUTE_GROUPS(upboard_fpga); | ||
| 271 | |||
| 272 | static int upboard_fpga_probe(struct platform_device *pdev) | ||
| 273 | { | ||
| 274 | struct device *dev = &pdev->dev; | ||
| 275 | struct upboard_fpga *fpga; | ||
| 276 | int ret; | ||
| 277 | |||
| 278 | fpga = devm_kzalloc(dev, sizeof(*fpga), GFP_KERNEL); | ||
| 279 | if (!fpga) | ||
| 280 | return -ENOMEM; | ||
| 281 | |||
| 282 | fpga->fpga_data = device_get_match_data(dev); | ||
| 283 | |||
| 284 | fpga->dev = dev; | ||
| 285 | |||
| 286 | platform_set_drvdata(pdev, fpga); | ||
| 287 | |||
| 288 | fpga->regmap = devm_regmap_init(dev, NULL, fpga, fpga->fpga_data->regmap_config); | ||
| 289 | if (IS_ERR(fpga->regmap)) | ||
| 290 | return PTR_ERR(fpga->regmap); | ||
| 291 | |||
| 292 | ret = upboard_fpga_gpio_init(fpga); | ||
| 293 | if (ret) | ||
| 294 | return dev_err_probe(dev, ret, "Failed to initialize FPGA common GPIOs"); | ||
| 295 | |||
| 296 | ret = upboard_fpga_get_firmware_version(fpga); | ||
| 297 | if (ret) | ||
| 298 | return ret; | ||
| 299 | |||
| 300 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, upboard_up_mfd_cells, | ||
| 301 | ARRAY_SIZE(upboard_up_mfd_cells), NULL, 0, NULL); | ||
| 302 | } | ||
| 303 | |||
| 304 | static const struct acpi_device_id upboard_fpga_acpi_match[] = { | ||
| 305 | { "AANT0F01", (kernel_ulong_t)&upboard_up2_fpga_data }, | ||
| 306 | { "AANT0F04", (kernel_ulong_t)&upboard_up_fpga_data }, | ||
| 307 | {} | ||
| 308 | }; | ||
| 309 | MODULE_DEVICE_TABLE(acpi, upboard_fpga_acpi_match); | ||
| 310 | |||
| 311 | static struct platform_driver upboard_fpga_driver = { | ||
| 312 | .driver = { | ||
| 313 | .name = "upboard-fpga", | ||
| 314 | .acpi_match_table = ACPI_PTR(upboard_fpga_acpi_match), | ||
| 315 | .dev_groups = upboard_fpga_groups, | ||
| 316 | }, | ||
| 317 | .probe = upboard_fpga_probe, | ||
| 318 | }; | ||
| 319 | |||
| 320 | module_platform_driver(upboard_fpga_driver); | ||
| 321 | |||
| 322 | MODULE_AUTHOR("Gary Wang <garywang@aaeon.com.tw>"); | ||
| 323 | MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>"); | ||
| 324 | MODULE_DESCRIPTION("UP Board FPGA driver"); | ||
| 325 | MODULE_LICENSE("GPL"); | ||
| @@ -10,7 +10,6 @@ · 9 unmodified lines | |||
| 10 | #include <linux/mfd/core.h> | 10 | #include <linux/mfd/core.h> |
| 11 | #include <linux/module.h> | 11 | #include <linux/module.h> |
| 12 | #include <linux/of_platform.h> | 12 | #include <linux/of_platform.h> |
| 13 | #include <linux/platform_data/syscon.h> | ||
| 14 | #include <linux/platform_device.h> | 13 | #include <linux/platform_device.h> |
| 15 | #include <linux/slab.h> | 14 | #include <linux/slab.h> |
| 16 | #include <linux/stat.h> | 15 | #include <linux/stat.h> |
| @@ -72,7 +72,7 @@ struct mfd_cell { · 71 unmodified lines | |||
| 72 | int (*resume)(struct platform_device *dev); | 72 | int (*resume)(struct platform_device *dev); |
| 73 | 73 | ||
| 74 | /* platform data passed to the sub devices drivers */ | 74 | /* platform data passed to the sub devices drivers */ |
| 75 | void *platform_data; | 75 | const void *platform_data; |
| 76 | size_t pdata_size; | 76 | size_t pdata_size; |
| 77 | 77 | ||
| 78 | /* Matches ACPI */ | 78 | /* Matches ACPI */ |
| @@ -93,6 +93,8 @@ struct da9052 { · 92 unmodified lines | |||
| 93 | 93 | ||
| 94 | int chip_irq; | 94 | int chip_irq; |
| 95 | 95 | ||
| 96 | int fault_log; | ||
| 97 | |||
| 96 | /* SOC I/O transfer related fixes for DA9052/53 */ | 98 | /* SOC I/O transfer related fixes for DA9052/53 */ |
| 97 | int (*fix_io) (struct da9052 *da9052, unsigned char reg); | 99 | int (*fix_io) (struct da9052 *da9052, unsigned char reg); |
| 98 | }; | 100 | }; |
| @@ -0,0 +1,26 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0+ */ | ||
| 2 | /* | ||
| 3 | * Core definitions for QNAP MCU MFD driver. | ||
| 4 | * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> | ||
| 5 | */ | ||
| 6 | |||
| 7 | #ifndef _LINUX_QNAP_MCU_H_ | ||
| 8 | #define _LINUX_QNAP_MCU_H_ | ||
| 9 | |||
| 10 | struct qnap_mcu; | ||
| 11 | |||
| 12 | struct qnap_mcu_variant { | ||
| 13 | u32 baud_rate; | ||
| 14 | int num_drives; | ||
| 15 | int fan_pwm_min; | ||
| 16 | int fan_pwm_max; | ||
| 17 | bool usb_led; | ||
| 18 | }; | ||
| 19 | |||
| 20 | int qnap_mcu_exec(struct qnap_mcu *mcu, | ||
| 21 | const u8 *cmd_data, size_t cmd_data_size, | ||
| 22 | u8 *reply_data, size_t reply_data_size); | ||
| 23 | int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu, | ||
| 24 | const u8 *cmd_data, size_t cmd_data_size); | ||
| 25 | |||
| 26 | #endif /* _LINUX_QNAP_MCU_H_ */ | ||
| @@ -10,14 +10,9 @@ · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | #include <linux/bitops.h> | 11 | #include <linux/bitops.h> |
| 12 | #include <linux/notifier.h> | 12 | #include <linux/notifier.h> |
| 13 | #include <linux/regmap.h> | ||
| 13 | #include <linux/regulator/driver.h> | 14 | #include <linux/regulator/driver.h> |
| 14 | 15 | ||
| 15 | struct regmap; | ||
| 16 | struct regmap_irq_chip_data; | ||
| 17 | |||
| 18 | #define TPS65219_1V35 1350000 | ||
| 19 | #define TPS65219_1V8 1800000 | ||
| 20 | |||
| 21 | /* TPS chip id list */ | 16 | /* TPS chip id list */ |
| 22 | #define TPS65219 0xF0 | 17 | #define TPS65219 0xF0 |
| 23 | 18 | ||
| @@ -0,0 +1,55 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * UP Board CPLD/FPGA driver | ||
| 4 | * | ||
| 5 | * Copyright (c) AAEON. All rights reserved. | ||
| 6 | * Copyright (C) 2024 Bootlin | ||
| 7 | * | ||
| 8 | * Author: Gary Wang <garywang@aaeon.com.tw> | ||
| 9 | * Author: Thomas Richard <thomas.richard@bootlin.com> | ||
| 10 | * | ||
| 11 | */ | ||
| 12 | |||
| 13 | #ifndef __LINUX_MFD_UPBOARD_FPGA_H | ||
| 14 | #define __LINUX_MFD_UPBOARD_FPGA_H | ||
| 15 | |||
| 16 | #define UPBOARD_REGISTER_SIZE 16 | ||
| 17 | |||
| 18 | enum upboard_fpgareg { | ||
| 19 | UPBOARD_REG_PLATFORM_ID = 0x10, | ||
| 20 | UPBOARD_REG_FIRMWARE_ID = 0x11, | ||
| 21 | UPBOARD_REG_FUNC_EN0 = 0x20, | ||
| 22 | UPBOARD_REG_FUNC_EN1 = 0x21, | ||
| 23 | UPBOARD_REG_GPIO_EN0 = 0x30, | ||
| 24 | UPBOARD_REG_GPIO_EN1 = 0x31, | ||
| 25 | UPBOARD_REG_GPIO_EN2 = 0x32, | ||
| 26 | UPBOARD_REG_GPIO_DIR0 = 0x40, | ||
| 27 | UPBOARD_REG_GPIO_DIR1 = 0x41, | ||
| 28 | UPBOARD_REG_GPIO_DIR2 = 0x42, | ||
| 29 | UPBOARD_REG_MAX, | ||
| 30 | }; | ||
| 31 | |||
| 32 | enum upboard_fpga_type { | ||
| 33 | UPBOARD_UP_FPGA, | ||
| 34 | UPBOARD_UP2_FPGA, | ||
| 35 | }; | ||
| 36 | |||
| 37 | struct upboard_fpga_data { | ||
| 38 | enum upboard_fpga_type type; | ||
| 39 | const struct regmap_config *regmap_config; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct upboard_fpga { | ||
| 43 | struct device *dev; | ||
| 44 | struct regmap *regmap; | ||
| 45 | struct gpio_desc *enable_gpio; | ||
| 46 | struct gpio_desc *reset_gpio; | ||
| 47 | struct gpio_desc *clear_gpio; | ||
| 48 | struct gpio_desc *strobe_gpio; | ||
| 49 | struct gpio_desc *datain_gpio; | ||
| 50 | struct gpio_desc *dataout_gpio; | ||
| 51 | unsigned int firmware_version; | ||
| 52 | const struct upboard_fpga_data *fpga_data; | ||
| 53 | }; | ||
| 54 | |||
| 55 | #endif /* __LINUX_MFD_UPBOARD_FPGA_H */ | ||
| @@ -1,9 +0,0 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
| 2 | #ifndef PLATFORM_DATA_SYSCON_H | ||
| 3 | #define PLATFORM_DATA_SYSCON_H | ||
| 4 | |||
| 5 | struct syscon_platform_data { | ||
| 6 | const char *label; | ||
| 7 | }; | ||
| 8 | |||
| 9 | #endif | ||