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 | 42 | - qcom,tcsr-apq8064 |
| 43 | 43 | - qcom,tcsr-apq8084 |
| 44 | 44 | - qcom,tcsr-ipq5332 |
| 45 | + - qcom,tcsr-ipq5424 | |
| 45 | 46 | - qcom,tcsr-ipq6018 |
| 46 | 47 | - qcom,tcsr-ipq8064 |
| 47 | 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 | 50 | minimum: 0 |
| 51 | 51 | maximum: 1 |
| 52 | 52 | |
| 53 | − rohm,charger-sense-resistor-ohms: | |
| 54 | − minimum: 10000000 | |
| 55 | − maximum: 50000000 | |
| 53 | + rohm,charger-sense-resistor-micro-ohms: | |
| 54 | + minimum: 10000 | |
| 55 | + maximum: 50000 | |
| 56 | 56 | description: | |
| 57 | − BD71827 and BD71828 have SAR ADC for measuring charging currents. | |
| 58 | − External sense resistor (RSENSE in data sheet) should be used. If | |
| 59 | − something other but 30MOhm resistor is used the resistance value | |
| 60 | − should be given here in Ohms. | |
| 61 | − default: 30000000 | |
| 57 | + BD71815 has SAR ADC for measuring charging currents. External sense | |
| 58 | + resistor (RSENSE in data sheet) should be used. If something other | |
| 59 | + but a 30 mOhm resistor is used the resistance value should be given | |
| 60 | + here in micro Ohms. | |
| 61 | + default: 30000 | |
| 62 | 62 | |
| 63 | 63 | regulators: |
| 64 | 64 | $ref: /schemas/regulator/rohm,bd71815-regulator.yaml |
| @@ -67,7 +67,7 @@ properties: · 2 unmodified lines | ||
| 67 | 67 | |
| 68 | 68 | gpio-reserved-ranges: |
| 69 | 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 | 71 | used to mark the pins which should not be configured for GPIO. Please see |
| 72 | 72 | the ../gpio/gpio.txt for more information. |
| 73 | 73 | |
| @@ -113,7 +113,7 @@ examples: · 39 unmodified lines | ||
| 113 | 113 | gpio-controller; |
| 114 | 114 | #gpio-cells = <2>; |
| 115 | 115 | |
| 116 | − rohm,charger-sense-resistor-ohms = <10000000>; | |
| 116 | + rohm,charger-sense-resistor-micro-ohms = <10000>; | |
| 117 | 117 | |
| 118 | 118 | regulators { |
| 119 | 119 | buck1: buck1 { |
| @@ -67,15 +67,7 @@ patternProperties: · 66 unmodified lines | ||
| 67 | 67 | |
| 68 | 68 | "^efuse@[0-9a-f]+$": |
| 69 | 69 | type: object |
| 70 | − additionalProperties: true | |
| 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 | |
| 70 | + $ref: /schemas/nvmem/sprd,sc2731-efuse.yaml# | |
| 79 | 71 | |
| 80 | 72 | "^fuel-gauge@[0-9a-f]+$": |
| 81 | 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 | 195 | compatible = "sprd,sc2731-adc"; |
| 204 | 196 | reg = <0x480>; |
| 205 | 197 | interrupt-parent = <&sc2731_pmic>; |
| @@ -27,7 +27,7 @@ select: · 26 unmodified lines | ||
| 27 | 27 | compatible: |
| 28 | 28 | contains: |
| 29 | 29 | enum: |
| 30 | − - al,alpine-sysfabric-servic | |
| 30 | + - al,alpine-sysfabric-service | |
| 31 | 31 | - allwinner,sun8i-a83t-system-controller |
| 32 | 32 | - allwinner,sun8i-h3-system-controller |
| 33 | 33 | - allwinner,sun8i-v3s-system-controller |
| @@ -103,6 +103,7 @@ select: · 69 unmodified lines | ||
| 103 | 103 | - rockchip,rk3288-qos |
| 104 | 104 | - rockchip,rk3368-qos |
| 105 | 105 | - rockchip,rk3399-qos |
| 106 | + - rockchip,rk3562-qos | |
| 106 | 107 | - rockchip,rk3568-qos |
| 107 | 108 | - rockchip,rk3576-qos |
| 108 | 109 | - rockchip,rk3588-qos |
| @@ -201,6 +202,7 @@ properties: · 92 unmodified lines | ||
| 201 | 202 | - rockchip,rk3288-qos |
| 202 | 203 | - rockchip,rk3368-qos |
| 203 | 204 | - rockchip,rk3399-qos |
| 205 | + - rockchip,rk3562-qos | |
| 204 | 206 | - rockchip,rk3568-qos |
| 205 | 207 | - rockchip,rk3576-qos |
| 206 | 208 | - rockchip,rk3588-qos |
| @@ -213,6 +215,7 @@ properties: · 6 unmodified lines | ||
| 213 | 215 | - ti,am625-dss-oldi-io-ctrl |
| 214 | 216 | - ti,am62p-cpsw-mac-efuse |
| 215 | 217 | - ti,am654-dss-oldi-io-ctrl |
| 218 | + - ti,j784s4-acspcie-proxy-ctrl | |
| 216 | 219 | - ti,j784s4-pcie-ctrl |
| 217 | 220 | - ti,keystone-pllctrl |
| 218 | 221 | - const: syscon |
| @@ -36,33 +36,4 @@ allOf: · 35 unmodified lines | ||
| 36 | 36 | - $ref: nvmem-deprecated-cells.yaml# |
| 37 | 37 | |
| 38 | 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 | 201 | pxe1610 |
| 202 | 202 | pwm-fan |
| 203 | 203 | q54sj108a2 |
| 204 | + qnap-mcu-hwmon | |
| 204 | 205 | raspberrypi-hwmon |
| 205 | 206 | sbrmi |
| 206 | 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 | 28 | leds-mlxcpld |
| 29 | 29 | leds-mt6370-rgb |
| 30 | 30 | leds-sc27xx |
| 31 | + leds-st1202.rst | |
| 31 | 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 | 19158 | S: Odd Fixes |
| 19159 | 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 | 19170 | QNX4 FILESYSTEM |
| 19162 | 19171 | M: Anders Larsen <al@alarsen.net> |
| 19163 | 19172 | S: Maintained |
| @@ -730,23 +730,30 @@ err_stop_hw: · 729 unmodified lines | ||
| 730 | 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 | 743 | static void sensor_hub_remove(struct hid_device *hdev) |
| 734 | 744 | { |
| 735 | 745 | struct sensor_hub_data *data = hid_get_drvdata(hdev); |
| 736 | 746 | unsigned long flags; |
| 737 | − int i; | |
| 738 | 747 | |
| 739 | 748 | hid_dbg(hdev, " hardware removed\n"); |
| 740 | 749 | hid_hw_close(hdev); |
| 741 | 750 | hid_hw_stop(hdev); |
| 751 | + | |
| 742 | 752 | spin_lock_irqsave(&data->lock, flags); |
| 743 | − for (i = 0; i < data->hid_sensor_client_cnt; ++i) { | |
| 744 | − struct hid_sensor_hub_device *hsdev = | |
| 745 | − data->hid_sensor_hub_client_devs[i].platform_data; | |
| 746 | − if (hsdev->pending.status) | |
| 747 | − complete(&hsdev->pending.ready); | |
| 748 | − } | |
| 753 | + device_for_each_child(&hdev->dev, NULL, | |
| 754 | + sensor_hub_finalize_pending_fn); | |
| 749 | 755 | spin_unlock_irqrestore(&data->lock, flags); |
| 756 | + | |
| 750 | 757 | mfd_remove_devices(&hdev->dev); |
| 751 | 758 | mutex_destroy(&data->mutex); |
| 752 | 759 | } |
| @@ -1822,6 +1822,18 @@ config SENSORS_PWM_FAN · 1821 unmodified lines | ||
| 1822 | 1822 | This driver can also be built as a module. If so, the module |
| 1823 | 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 | 1837 | config SENSORS_RASPBERRYPI_HWMON |
| 1826 | 1838 | tristate "Raspberry Pi voltage monitor" |
| 1827 | 1839 | depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) |
| @@ -189,6 +189,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o · 188 unmodified lines | ||
| 189 | 189 | obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o |
| 190 | 190 | obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o |
| 191 | 191 | obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o |
| 192 | +obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o | |
| 192 | 193 | obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o |
| 193 | 194 | obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o |
| 194 | 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 | 917 | To compile this driver as a module, choose M here: the |
| 918 | 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 | 932 | config INPUT_RAVE_SP_PWRBUTTON |
| 921 | 933 | tristate "RAVE SP Power button Driver" |
| 922 | 934 | depends on RAVE_SP_CORE |
| @@ -68,6 +68,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o · 67 unmodified lines | ||
| 68 | 68 | obj-$(CONFIG_INPUT_POWERMATE) += powermate.o |
| 69 | 69 | obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o |
| 70 | 70 | obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o |
| 71 | +obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o | |
| 71 | 72 | obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o |
| 72 | 73 | obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o |
| 73 | 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 | 580 | LED driver chips accessed via the I2C bus. Supported |
| 581 | 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 | 594 | config LEDS_WM831X_STATUS |
| 584 | 595 | tristate "LED support for status LEDs on WM831x PMICs" |
| 585 | 596 | depends on LEDS_CLASS |
| @@ -931,6 +942,16 @@ config LEDS_LM36274 · 345 unmodified lines | ||
| 931 | 942 | Say Y to enable the LM36274 LED driver for TI LMU devices. |
| 932 | 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 | 955 | config LEDS_TPS6105X |
| 935 | 956 | tristate "LED support for TI TPS6105X" |
| 936 | 957 | depends on LEDS_CLASS |
| @@ -79,6 +79,7 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o · 78 unmodified lines | ||
| 79 | 79 | obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o |
| 80 | 80 | obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o |
| 81 | 81 | obj-$(CONFIG_LEDS_PWM) += leds-pwm.o |
| 82 | +obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o | |
| 82 | 83 | obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o |
| 83 | 84 | obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o |
| 84 | 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 | 2386 | additional drivers must be enabled in order to use the functionality |
| 2387 | 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 | 2402 | config MFD_RSMU_I2C |
| 2390 | 2403 | tristate "Renesas Synchronization Management Unit with I2C" |
| 2391 | 2404 | depends on I2C && OF |
| @@ -2414,5 +2427,17 @@ config MFD_RSMU_SPI · 22 unmodified lines | ||
| 2414 | 2427 | Additional drivers must be enabled in order to use the functionality |
| 2415 | 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 | 2442 | endmenu |
| 2418 | 2443 | endif |
| @@ -288,5 +288,9 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC_PMCI) += intel-m10-bmc-pmci.o · 287 unmodified lines | ||
| 288 | 288 | obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o |
| 289 | 289 | obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o |
| 290 | 290 | |
| 291 | +obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o | |
| 292 | + | |
| 291 | 293 | obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o |
| 292 | 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 | 1449 | axp20x->nr_cells, NULL, 0, NULL); |
| 1450 | 1450 | |
| 1451 | 1451 | if (ret) { |
| @@ -1455,10 +1455,7 @@ int axp20x_device_probe(struct axp20x_dev *axp20x) · 3 unmodified lines | ||
| 1455 | 1455 | } |
| 1456 | 1456 | |
| 1457 | 1457 | if (axp20x->variant != AXP288_ID) |
| 1458 | − devm_register_sys_off_handler(axp20x->dev, | |
| 1459 | − SYS_OFF_MODE_POWER_OFF, | |
| 1460 | − SYS_OFF_PRIO_DEFAULT, | |
| 1461 | − axp20x_power_off, axp20x); | |
| 1458 | + devm_register_power_off_handler(axp20x->dev, axp20x_power_off, axp20x); | |
| 1462 | 1459 | |
| 1463 | 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 | 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 | 59 | #if IS_ENABLED(CONFIG_OF) |
| 67 | 60 | static const struct of_device_id cs42l43_of_match[] = { |
| 68 | 61 | { .compatible = "cirrus,cs42l43", }, |
| @@ -88,7 +81,6 @@ static struct i2c_driver cs42l43_i2c_driver = { · 19 unmodified lines | ||
| 88 | 81 | }, |
| 89 | 82 | |
| 90 | 83 | .probe = cs42l43_i2c_probe, |
| 91 | − .remove = cs42l43_i2c_remove, | |
| 92 | 84 | }; |
| 93 | 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 | 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 | 190 | static const struct sdw_device_id cs42l43_sdw_id[] = { |
| 200 | 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 | 202 | .probe = cs42l43_sdw_probe, |
| 212 | − .remove = cs42l43_sdw_remove, | |
| 213 | 203 | .id_table = cs42l43_sdw_id, |
| 214 | 204 | .ops = &cs42l43_sdw_ops, |
| 215 | 205 | }; |
| @@ -29,7 +29,7 @@ · 28 unmodified lines | ||
| 29 | 29 | |
| 30 | 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 | 33 | #define CS42L43_SDW_DETACH_TIMEOUT_MS 100 |
| 34 | 34 | |
| 35 | 35 | #define CS42L43_MCU_BOOT_STAGE1 1 |
| @@ -48,6 +48,7 @@ · 12 unmodified lines | ||
| 48 | 48 | |
| 49 | 49 | #define CS42L43_MCU_SUPPORTED_REV 0x2105 |
| 50 | 50 | #define CS42L43_MCU_SHADOW_REGS_REQUIRED_REV 0x2200 |
| 51 | +#define CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV 0x1002 | |
| 51 | 52 | #define CS42L43_MCU_SUPPORTED_BIOS_REV 0x0001 |
| 52 | 53 | |
| 53 | 54 | #define CS42L43_VDDP_DELAY_US 50 |
| @@ -773,7 +774,8 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) · 719 unmodified lines | ||
| 773 | 774 | * Later versions of the firmwware require the driver to access some |
| 774 | 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 | 780 | ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_CONTROL, &secure_cfg); |
| 779 | 781 | if (ret) { |
| @@ -982,7 +984,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) · 202 unmodified lines | ||
| 982 | 984 | /* vdd-p must be on for 50uS before any other supply */ |
| 983 | 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 | 989 | ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); |
| 988 | 990 | if (ret) { |
| @@ -1003,7 +1005,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) · 14 unmodified lines | ||
| 1003 | 1005 | err_core_supplies: |
| 1004 | 1006 | regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); |
| 1005 | 1007 | err_reset: |
| 1006 | − gpiod_set_value_cansleep(cs42l43->reset, 0); | |
| 1008 | + gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | |
| 1007 | 1009 | regulator_disable(cs42l43->vdd_p); |
| 1008 | 1010 | |
| 1009 | 1011 | return ret; |
| @@ -1025,7 +1027,7 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) · 15 unmodified lines | ||
| 1025 | 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 | 1032 | ret = regulator_disable(cs42l43->vdd_p); |
| 1031 | 1033 | if (ret) { |
| @@ -1036,6 +1038,15 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) · 4 unmodified lines | ||
| 1036 | 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 | 1050 | int cs42l43_dev_probe(struct cs42l43 *cs42l43) |
| 1040 | 1051 | { |
| 1041 | 1052 | int i, ret; |
| @@ -1050,11 +1061,13 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) · 8 unmodified lines | ||
| 1050 | 1061 | |
| 1051 | 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 | 1065 | if (IS_ERR(cs42l43->reset)) |
| 1055 | 1066 | return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->reset), |
| 1056 | 1067 | "Failed to get reset\n"); |
| 1057 | 1068 | |
| 1069 | + gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | |
| 1070 | + | |
| 1058 | 1071 | cs42l43->vdd_p = devm_regulator_get(cs42l43->dev, "vdd-p"); |
| 1059 | 1072 | if (IS_ERR(cs42l43->vdd_p)) |
| 1060 | 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 | 1093 | if (ret) |
| 1081 | 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 | 1100 | pm_runtime_set_autosuspend_delay(cs42l43->dev, CS42L43_AUTOSUSPEND_TIME_MS); |
| 1084 | 1101 | pm_runtime_use_autosuspend(cs42l43->dev); |
| 1085 | 1102 | pm_runtime_set_active(cs42l43->dev); |
| @@ -1098,14 +1115,6 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) · 12 unmodified lines | ||
| 1098 | 1115 | } |
| 1099 | 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 | 1118 | static int cs42l43_suspend(struct device *dev) |
| 1110 | 1119 | { |
| 1111 | 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 | 25 | bool cs42l43_volatile_register(struct device *dev, unsigned int reg); |
| 26 | 26 | |
| 27 | 27 | int cs42l43_dev_probe(struct cs42l43 *cs42l43); |
| 28 | −void cs42l43_dev_remove(struct cs42l43 *cs42l43); | |
| 29 | 28 | |
| 30 | 29 | #endif /* CS42L43_CORE_INT_H */ |
| @@ -585,6 +585,7 @@ static int da9052_clear_fault_log(struct da9052 *da9052) · 584 unmodified lines | ||
| 585 | 585 | "Cannot reset FAULT_LOG values %d\n", ret); |
| 586 | 586 | } |
| 587 | 587 | |
| 588 | + da9052->fault_log = fault_log; | |
| 588 | 589 | return ret; |
| 589 | 590 | } |
| 590 | 591 | |
| @@ -81,7 +81,7 @@ static struct mfd_cell chtdc_ti_dev[] = { · 80 unmodified lines | ||
| 81 | 81 | static const struct regmap_config chtdc_ti_regmap_config = { |
| 82 | 82 | .reg_bits = 8, |
| 83 | 83 | .val_bits = 8, |
| 84 | − .max_register = 128, | |
| 84 | + .max_register = 0xff, | |
| 85 | 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 | 834 | { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME}, |
| 835 | 835 | { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9}, |
| 836 | 836 | { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M}, |
| 837 | − { PCI_VDEVICE(INTEL, 0x3197), LPC_GLK}, | |
| 838 | 837 | { PCI_VDEVICE(INTEL, 0x2b9c), LPC_COUGARMOUNTAIN}, |
| 838 | + { PCI_VDEVICE(INTEL, 0x3197), LPC_GLK}, | |
| 839 | + { PCI_VDEVICE(INTEL, 0x31e8), LPC_GLK}, | |
| 839 | 840 | { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO}, |
| 840 | 841 | { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R}, |
| 841 | 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 | 170 | return ret; |
| 171 | 171 | } |
| 172 | 172 | |
| 173 | − ret = devm_register_sys_off_handler(ddata->dev, | |
| 174 | − SYS_OFF_MODE_POWER_OFF, | |
| 175 | − SYS_OFF_PRIO_DEFAULT, | |
| 176 | − stpmic1_power_off, | |
| 177 | − ddata); | |
| 173 | + ret = devm_register_power_off_handler(ddata->dev, stpmic1_power_off, ddata); | |
| 178 | 174 | if (ret) { |
| 179 | 175 | dev_err(ddata->dev, "failed to register sys-off handler: %d\n", ret); |
| 180 | 176 | return ret; |
| @@ -12,22 +12,16 @@ · 11 unmodified lines | ||
| 12 | 12 | #include <linux/clk.h> |
| 13 | 13 | #include <linux/err.h> |
| 14 | 14 | #include <linux/hwspinlock.h> |
| 15 | −#include <linux/io.h> | |
| 16 | −#include <linux/init.h> | |
| 17 | 15 | #include <linux/list.h> |
| 16 | +#include <linux/mutex.h> | |
| 18 | 17 | #include <linux/of.h> |
| 19 | 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 | 19 | #include <linux/regmap.h> |
| 24 | 20 | #include <linux/reset.h> |
| 25 | 21 | #include <linux/mfd/syscon.h> |
| 26 | 22 | #include <linux/slab.h> |
| 27 | 23 | |
| 28 | −static struct platform_driver syscon_driver; | |
| 29 | − | |
| 30 | −static DEFINE_SPINLOCK(syscon_list_slock); | |
| 24 | +static DEFINE_MUTEX(syscon_list_lock); | |
| 31 | 25 | static LIST_HEAD(syscon_list); |
| 32 | 26 | |
| 33 | 27 | struct syscon { |
| @@ -54,6 +48,8 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res) · 20 unmodified lines | ||
| 54 | 48 | struct resource res; |
| 55 | 49 | struct reset_control *reset; |
| 56 | 50 | |
| 51 | + WARN_ON(!mutex_is_locked(&syscon_list_lock)); | |
| 52 | + | |
| 57 | 53 | struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL); |
| 58 | 54 | if (!syscon) |
| 59 | 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 | 142 | syscon->regmap = regmap; |
| 147 | 143 | syscon->np = np; |
| 148 | 144 | |
| 149 | − spin_lock(&syscon_list_slock); | |
| 150 | 145 | list_add_tail(&syscon->list, &syscon_list); |
| 151 | − spin_unlock(&syscon_list_slock); | |
| 152 | 146 | |
| 153 | 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 | 164 | struct syscon *entry, *syscon = NULL; |
| 171 | 165 | |
| 172 | − spin_lock(&syscon_list_slock); | |
| 166 | + mutex_lock(&syscon_list_lock); | |
| 173 | 167 | |
| 174 | 168 | list_for_each_entry(entry, &syscon_list, list) |
| 175 | 169 | if (entry->np == np) { |
| @@ -177,10 +171,13 @@ static struct regmap *device_node_get_regmap(struct device_node *np, · 1 unmodified line | ||
| 177 | 171 | break; |
| 178 | 172 | } |
| 179 | 173 | |
| 180 | − spin_unlock(&syscon_list_slock); | |
| 181 | − | |
| 182 | − if (!syscon) | |
| 183 | − syscon = of_syscon_register(np, check_res); | |
| 174 | + if (!syscon) { | |
| 175 | + if (of_device_is_compatible(np, "syscon")) | |
| 176 | + syscon = of_syscon_register(np, check_res); | |
| 177 | + else | |
| 178 | + syscon = ERR_PTR(-EINVAL); | |
| 179 | + } | |
| 180 | + mutex_unlock(&syscon_list_lock); | |
| 184 | 181 | |
| 185 | 182 | if (IS_ERR(syscon)) |
| 186 | 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 | 209 | return -ENOMEM; |
| 213 | 210 | |
| 214 | 211 | /* check if syscon entry already exists */ |
| 215 | − spin_lock(&syscon_list_slock); | |
| 212 | + mutex_lock(&syscon_list_lock); | |
| 216 | 213 | |
| 217 | 214 | list_for_each_entry(entry, &syscon_list, list) |
| 218 | 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 | 223 | /* register the regmap in syscon list */ |
| 227 | 224 | list_add_tail(&syscon->list, &syscon_list); |
| 228 | − spin_unlock(&syscon_list_slock); | |
| 225 | + mutex_unlock(&syscon_list_lock); | |
| 229 | 226 | |
| 230 | 227 | return 0; |
| 231 | 228 | |
| 232 | 229 | err_unlock: |
| 233 | − spin_unlock(&syscon_list_slock); | |
| 230 | + mutex_unlock(&syscon_list_lock); | |
| 234 | 231 | kfree(syscon); |
| 235 | 232 | return ret; |
| 236 | 233 | } |
| @@ -244,9 +241,6 @@ EXPORT_SYMBOL_GPL(device_node_to_regmap); · 7 unmodified lines | ||
| 244 | 241 | |
| 245 | 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 | 244 | return device_node_get_regmap(np, true); |
| 251 | 245 | } |
| 252 | 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 | 330 | return regmap; |
| 337 | 331 | } |
| 338 | 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 | 112 | static const struct mfd_cell tps65219_cells[] = { |
| 113 | − { | |
| 114 | − .name = "tps65219-regulator", | |
| 115 | − .resources = tps65219_regulator_resources, | |
| 116 | − .num_resources = ARRAY_SIZE(tps65219_regulator_resources), | |
| 117 | − }, | |
| 118 | − { .name = "tps65219-gpio", }, | |
| 113 | + MFD_CELL_RES("tps65219-regulator", tps65219_regulator_resources), | |
| 114 | + MFD_CELL_NAME("tps65219-gpio"), | |
| 119 | 115 | }; |
| 120 | 116 | |
| 121 | −static const struct mfd_cell tps65219_pwrbutton_cell = { | |
| 122 | − .name = "tps65219-pwrbutton", | |
| 123 | − .resources = tps65219_pwrbutton_resources, | |
| 124 | − .num_resources = ARRAY_SIZE(tps65219_pwrbutton_resources), | |
| 125 | −}; | |
| 117 | +static const struct mfd_cell tps65219_pwrbutton_cell = | |
| 118 | + MFD_CELL_RES("tps65219-pwrbutton", tps65219_pwrbutton_resources); | |
| 126 | 119 | |
| 127 | 120 | static const struct regmap_config tps65219_regmap_config = { |
| 128 | 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 | 10 | #include <linux/mfd/core.h> |
| 11 | 11 | #include <linux/module.h> |
| 12 | 12 | #include <linux/of_platform.h> |
| 13 | −#include <linux/platform_data/syscon.h> | |
| 14 | 13 | #include <linux/platform_device.h> |
| 15 | 14 | #include <linux/slab.h> |
| 16 | 15 | #include <linux/stat.h> |
| @@ -72,7 +72,7 @@ struct mfd_cell { · 71 unmodified lines | ||
| 72 | 72 | int (*resume)(struct platform_device *dev); |
| 73 | 73 | |
| 74 | 74 | /* platform data passed to the sub devices drivers */ |
| 75 | − void *platform_data; | |
| 75 | + const void *platform_data; | |
| 76 | 76 | size_t pdata_size; |
| 77 | 77 | |
| 78 | 78 | /* Matches ACPI */ |
| @@ -93,6 +93,8 @@ struct da9052 { · 92 unmodified lines | ||
| 93 | 93 | |
| 94 | 94 | int chip_irq; |
| 95 | 95 | |
| 96 | + int fault_log; | |
| 97 | + | |
| 96 | 98 | /* SOC I/O transfer related fixes for DA9052/53 */ |
| 97 | 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 | 11 | #include <linux/bitops.h> |
| 12 | 12 | #include <linux/notifier.h> |
| 13 | +#include <linux/regmap.h> | |
| 13 | 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 | 16 | /* TPS chip id list */ |
| 22 | 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 | |