mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-18 22:14:16 +00:00
- 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. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmePbHkACgkQUa+KL4f8 d2HQNw//fM3+JpyTUKjUtBl8YH0ilgZ2iKiXLHyJ6vMIqv+RfLlPAliM8f1bkEj8 x/9kKp6ytdyAhQ/oAJW/vxRhWZVsfh9T7WSy/Q/WNChcGJ0KtUwtjWWuezdDJoyr 6Ju4PhKHVz46cBW56u3OCbcogN8AHi1gZiAh8APwVhldUO6swaisPBvxw/4u4WMs HGviTLueXEptF4/actnArQ1YJ6tctY7C5tV06f6Irxn6hSz8EpGBRQ47S7Hom+mx aIyRLkERqhZ1FNBDfk2YERqaOyxzQRYy+PMNr6AtQ/TE+7pVBOz7xoSI3VH3k7mM k4u8Gau9qbnVi/pUkabgcdKOCGUYeZzXZgJT/Ttq5O9ddPE8sHMcLrxKMtUP2CBE 6Zf0EljscGFOWise/ocNUblpy0rfXPl+wMiibxY5C+X56QkluDn2Q8ObGODt0bbR ACgPFL2S8m2MLEOTzBKDlSgeqoMZFKVWkqAry+yNUgIKbtM6pIdaM5ZCUhgdJ5GL CxGP6uw68oAUnr1lRCytgGR5XtmcIXS/Jo2u/T7cmucD3inuxQyt5TrZxma48QbL 2+P2vmymn6ZWQb82OptrnKCMsBEXnNbWv+2BiRYoKXQt48BxIocVrsyEQIh4qGcP KPUB1aCxP/zJ2H7rTaK4QqM+fgOloShs5nI21v5EWSCQKvTN5ko= =3HQm -----END PGP SIGNATURE----- Merge tag 'mfd-next-6.14' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd 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 ...
This commit is contained in:
commit
909fd2b89f
49 changed files with 2407 additions and 254 deletions
132
Documentation/devicetree/bindings/leds/st,led1202.yaml
Normal file
132
Documentation/devicetree/bindings/leds/st,led1202.yaml
Normal file
|
@ -0,0 +1,132 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/st,led1202.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: ST LED1202 LED controllers
|
||||
|
||||
maintainers:
|
||||
- Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
|
||||
|
||||
description: |
|
||||
The LED1202 is a 12-channel low quiescent current LED controller
|
||||
programmable via I2C; The output current can be adjusted separately
|
||||
for each channel by 8-bit analog and 12-bit digital dimming control.
|
||||
Datasheet available at
|
||||
https://www.st.com/en/power-management/led1202.html
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: st,led1202
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
minimum: 0
|
||||
maximum: 11
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@58 {
|
||||
compatible = "st,led1202";
|
||||
reg = <0x58>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@0 {
|
||||
reg = <0x0>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
function-enumerator = <1>;
|
||||
};
|
||||
|
||||
led@1 {
|
||||
reg = <0x1>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
function-enumerator = <2>;
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <0x2>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
function-enumerator = <3>;
|
||||
};
|
||||
|
||||
led@3 {
|
||||
reg = <0x3>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
function-enumerator = <4>;
|
||||
};
|
||||
|
||||
led@4 {
|
||||
reg = <0x4>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
function-enumerator = <5>;
|
||||
};
|
||||
|
||||
led@5 {
|
||||
reg = <0x5>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
function-enumerator = <6>;
|
||||
};
|
||||
|
||||
led@6 {
|
||||
reg = <0x6>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
function-enumerator = <7>;
|
||||
};
|
||||
|
||||
led@7 {
|
||||
reg = <0x7>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
function-enumerator = <8>;
|
||||
};
|
||||
|
||||
led@8 {
|
||||
reg = <0x8>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
function-enumerator = <9>;
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,44 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-gpbr.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Microchip AT91 General Purpose Backup Registers
|
||||
|
||||
maintainers:
|
||||
- Nicolas Ferre <nicolas.ferre@microchip.com>
|
||||
|
||||
description:
|
||||
The system controller embeds 256 bits of General Purpose Backup
|
||||
registers organized as 8 32-bit registers.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- atmel,at91sam9260-gpbr
|
||||
- const: syscon
|
||||
- items:
|
||||
- enum:
|
||||
- microchip,sam9x60-gpbr
|
||||
- microchip,sam9x7-gpbr
|
||||
- const: atmel,at91sam9260-gpbr
|
||||
- const: syscon
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
syscon@fffffd50 {
|
||||
compatible = "atmel,at91sam9260-gpbr", "syscon";
|
||||
reg = <0xfffffd50 0x10>;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-matrix.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Microchip AT91 Bus Matrix
|
||||
|
||||
maintainers:
|
||||
- Nicolas Ferre <nicolas.ferre@microchip.com>
|
||||
|
||||
description:
|
||||
The Bus Matrix (MATRIX) implements a multi-layer AHB, based on the
|
||||
AHB-Lite protocol, that enables parallel access paths between multiple
|
||||
masters and slaves in a system, thus increasing the overall bandwidth.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- atmel,at91sam9260-matrix
|
||||
- atmel,at91sam9261-matrix
|
||||
- atmel,at91sam9263-matrix
|
||||
- atmel,at91sam9rl-matrix
|
||||
- atmel,at91sam9g45-matrix
|
||||
- atmel,at91sam9n12-matrix
|
||||
- atmel,at91sam9x5-matrix
|
||||
- atmel,sama5d3-matrix
|
||||
- const: syscon
|
||||
- items:
|
||||
- enum:
|
||||
- microchip,sam9x60-matrix
|
||||
- microchip,sam9x7-matrix
|
||||
- const: atmel,at91sam9x5-matrix
|
||||
- const: syscon
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
syscon@ffffec00 {
|
||||
compatible = "atmel,sama5d3-matrix", "syscon";
|
||||
reg = <0xffffec00 0x200>;
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
* Device tree bindings for Atmel GPBR (General Purpose Backup Registers)
|
||||
|
||||
The GPBR are a set of battery-backed registers.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be one of the following:
|
||||
"atmel,at91sam9260-gpbr", "syscon"
|
||||
"microchip,sam9x60-gpbr", "syscon"
|
||||
"microchip,sam9x7-gpbr", "microchip,sam9x60-gpbr", "syscon"
|
||||
- reg: contains offset/length value of the GPBR memory
|
||||
region.
|
||||
|
||||
Example:
|
||||
|
||||
gpbr: gpbr@fffffd50 {
|
||||
compatible = "atmel,at91sam9260-gpbr", "syscon";
|
||||
reg = <0xfffffd50 0x10>;
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
* Device tree bindings for Atmel Bus Matrix
|
||||
|
||||
The Bus Matrix registers are used to configure Atmel SoCs internal bus
|
||||
behavior (master/slave priorities, undefined burst length type, ...)
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be one of the following
|
||||
"atmel,at91sam9260-matrix", "syscon"
|
||||
"atmel,at91sam9261-matrix", "syscon"
|
||||
"atmel,at91sam9263-matrix", "syscon"
|
||||
"atmel,at91sam9rl-matrix", "syscon"
|
||||
"atmel,at91sam9g45-matrix", "syscon"
|
||||
"atmel,at91sam9n12-matrix", "syscon"
|
||||
"atmel,at91sam9x5-matrix", "syscon"
|
||||
"atmel,sama5d3-matrix", "syscon"
|
||||
"microchip,sam9x60-matrix", "syscon"
|
||||
"microchip,sam9x7-matrix", "atmel,at91sam9x5-matrix", "syscon"
|
||||
- reg: Contains offset/length value of the Bus Matrix
|
||||
memory region.
|
||||
|
||||
Example:
|
||||
|
||||
matrix: matrix@ffffec00 {
|
||||
compatible = "atmel,sama5d3-matrix", "syscon";
|
||||
reg = <0xffffec00 0x200>;
|
||||
};
|
|
@ -42,6 +42,7 @@ properties:
|
|||
- qcom,tcsr-apq8064
|
||||
- qcom,tcsr-apq8084
|
||||
- qcom,tcsr-ipq5332
|
||||
- qcom,tcsr-ipq5424
|
||||
- qcom,tcsr-ipq6018
|
||||
- qcom,tcsr-ipq8064
|
||||
- qcom,tcsr-ipq8074
|
||||
|
|
42
Documentation/devicetree/bindings/mfd/qnap,ts433-mcu.yaml
Normal file
42
Documentation/devicetree/bindings/mfd/qnap,ts433-mcu.yaml
Normal file
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mfd/qnap,ts433-mcu.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: QNAP NAS on-board Microcontroller
|
||||
|
||||
maintainers:
|
||||
- Heiko Stuebner <heiko@sntech.de>
|
||||
|
||||
description:
|
||||
QNAP embeds a microcontroller on their NAS devices adding system feature
|
||||
as PWM Fan control, additional LEDs, power button status and more.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qnap,ts433-mcu
|
||||
|
||||
patternProperties:
|
||||
"^fan-[0-9]+$":
|
||||
$ref: /schemas/hwmon/fan-common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
uart {
|
||||
mcu {
|
||||
compatible = "qnap,ts433-mcu";
|
||||
|
||||
fan-0 {
|
||||
#cooling-cells = <2>;
|
||||
cooling-levels = <0 64 89 128 166 204 221 238>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -50,15 +50,15 @@ properties:
|
|||
minimum: 0
|
||||
maximum: 1
|
||||
|
||||
rohm,charger-sense-resistor-ohms:
|
||||
minimum: 10000000
|
||||
maximum: 50000000
|
||||
rohm,charger-sense-resistor-micro-ohms:
|
||||
minimum: 10000
|
||||
maximum: 50000
|
||||
description: |
|
||||
BD71827 and BD71828 have SAR ADC for measuring charging currents.
|
||||
External sense resistor (RSENSE in data sheet) should be used. If
|
||||
something other but 30MOhm resistor is used the resistance value
|
||||
should be given here in Ohms.
|
||||
default: 30000000
|
||||
BD71815 has SAR ADC for measuring charging currents. External sense
|
||||
resistor (RSENSE in data sheet) should be used. If something other
|
||||
but a 30 mOhm resistor is used the resistance value should be given
|
||||
here in micro Ohms.
|
||||
default: 30000
|
||||
|
||||
regulators:
|
||||
$ref: /schemas/regulator/rohm,bd71815-regulator.yaml
|
||||
|
@ -67,7 +67,7 @@ properties:
|
|||
|
||||
gpio-reserved-ranges:
|
||||
description: |
|
||||
Usage of BD71828 GPIO pins can be changed via OTP. This property can be
|
||||
Usage of BD71815 GPIO pins can be changed via OTP. This property can be
|
||||
used to mark the pins which should not be configured for GPIO. Please see
|
||||
the ../gpio/gpio.txt for more information.
|
||||
|
||||
|
@ -113,7 +113,7 @@ examples:
|
|||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
|
||||
rohm,charger-sense-resistor-ohms = <10000000>;
|
||||
rohm,charger-sense-resistor-micro-ohms = <10000>;
|
||||
|
||||
regulators {
|
||||
buck1: buck1 {
|
||||
|
|
|
@ -67,15 +67,7 @@ patternProperties:
|
|||
|
||||
"^efuse@[0-9a-f]+$":
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- sprd,sc2720-efuse
|
||||
- sprd,sc2721-efuse
|
||||
- sprd,sc2723-efuse
|
||||
- sprd,sc2730-efuse
|
||||
- sprd,sc2731-efuse
|
||||
$ref: /schemas/nvmem/sprd,sc2731-efuse.yaml#
|
||||
|
||||
"^fuel-gauge@[0-9a-f]+$":
|
||||
type: object
|
||||
|
@ -199,7 +191,7 @@ examples:
|
|||
};
|
||||
};
|
||||
|
||||
adc@480 {
|
||||
pmic_adc: adc@480 {
|
||||
compatible = "sprd,sc2731-adc";
|
||||
reg = <0x480>;
|
||||
interrupt-parent = <&sc2731_pmic>;
|
||||
|
|
|
@ -27,7 +27,7 @@ select:
|
|||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- al,alpine-sysfabric-servic
|
||||
- al,alpine-sysfabric-service
|
||||
- allwinner,sun8i-a83t-system-controller
|
||||
- allwinner,sun8i-h3-system-controller
|
||||
- allwinner,sun8i-v3s-system-controller
|
||||
|
@ -103,6 +103,7 @@ select:
|
|||
- rockchip,rk3288-qos
|
||||
- rockchip,rk3368-qos
|
||||
- rockchip,rk3399-qos
|
||||
- rockchip,rk3562-qos
|
||||
- rockchip,rk3568-qos
|
||||
- rockchip,rk3576-qos
|
||||
- rockchip,rk3588-qos
|
||||
|
@ -201,6 +202,7 @@ properties:
|
|||
- rockchip,rk3288-qos
|
||||
- rockchip,rk3368-qos
|
||||
- rockchip,rk3399-qos
|
||||
- rockchip,rk3562-qos
|
||||
- rockchip,rk3568-qos
|
||||
- rockchip,rk3576-qos
|
||||
- rockchip,rk3588-qos
|
||||
|
@ -213,6 +215,7 @@ properties:
|
|||
- ti,am625-dss-oldi-io-ctrl
|
||||
- ti,am62p-cpsw-mac-efuse
|
||||
- ti,am654-dss-oldi-io-ctrl
|
||||
- ti,j784s4-acspcie-proxy-ctrl
|
||||
- ti,j784s4-pcie-ctrl
|
||||
- ti,keystone-pllctrl
|
||||
- const: syscon
|
||||
|
|
|
@ -36,33 +36,4 @@ allOf:
|
|||
- $ref: nvmem-deprecated-cells.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
pmic {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
efuse@380 {
|
||||
compatible = "sprd,sc2731-efuse";
|
||||
reg = <0x380>;
|
||||
hwlocks = <&hwlock 12>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
/* Data cells */
|
||||
fgu_calib: calib@6 {
|
||||
reg = <0x6 0x2>;
|
||||
bits = <0 9>;
|
||||
};
|
||||
|
||||
adc_big_scale: calib@24 {
|
||||
reg = <0x24 0x2>;
|
||||
};
|
||||
|
||||
adc_small_scale: calib@26 {
|
||||
reg = <0x26 0x2>;
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
|
@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers
|
|||
pxe1610
|
||||
pwm-fan
|
||||
q54sj108a2
|
||||
qnap-mcu-hwmon
|
||||
raspberrypi-hwmon
|
||||
sbrmi
|
||||
sbtsi_temp
|
||||
|
|
27
Documentation/hwmon/qnap-mcu-hwmon.rst
Normal file
27
Documentation/hwmon/qnap-mcu-hwmon.rst
Normal file
|
@ -0,0 +1,27 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver qnap-mcu-hwmon
|
||||
============================
|
||||
|
||||
This driver enables the use of the hardware monitoring and fan control
|
||||
of the MCU used on some QNAP network attached storage devices.
|
||||
|
||||
Author: Heiko Stuebner <heiko@sntech.de>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The driver implements a simple interface for driving the fan controlled by
|
||||
setting its PWM output value and exposes the fan rpm and case-temperature
|
||||
to user space through hwmon's sysfs interface.
|
||||
|
||||
The fan rotation speed returned via the optional 'fan1_input' is calculated
|
||||
inside the MCU device.
|
||||
|
||||
The driver provides the following sensor accesses in sysfs:
|
||||
|
||||
=============== ======= =======================================================
|
||||
fan1_input ro fan tachometer speed in RPM
|
||||
pwm1 rw relative speed (0-255), 255=max. speed.
|
||||
temp1_input ro Measured temperature in millicelsius
|
||||
=============== ======= =======================================================
|
|
@ -28,4 +28,5 @@ LEDs
|
|||
leds-mlxcpld
|
||||
leds-mt6370-rgb
|
||||
leds-sc27xx
|
||||
leds-st1202.rst
|
||||
leds-qcom-lpg
|
||||
|
|
34
Documentation/leds/leds-st1202.rst
Normal file
34
Documentation/leds/leds-st1202.rst
Normal file
|
@ -0,0 +1,34 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
============================================
|
||||
Kernel driver for STMicroelectronics LED1202
|
||||
============================================
|
||||
|
||||
/sys/class/leds/<led>/hw_pattern
|
||||
--------------------------------
|
||||
|
||||
Specify a hardware pattern for the ST1202 LED. The LED controller
|
||||
implements 12 low-side current generators with independent dimming
|
||||
control. Internal volatile memory allows the user to store up to 8
|
||||
different patterns. Each pattern is a particular output configuration
|
||||
in terms of PWM duty-cycle and duration (ms).
|
||||
|
||||
To be compatible with the hardware pattern format, maximum 8 tuples of
|
||||
brightness (PWM) and duration must be written to hw_pattern.
|
||||
|
||||
- Min pattern duration: 22 ms
|
||||
- Max pattern duration: 5660 ms
|
||||
|
||||
The format of the hardware pattern values should be:
|
||||
"brightness duration brightness duration ..."
|
||||
|
||||
/sys/class/leds/<led>/repeat
|
||||
----------------------------
|
||||
|
||||
Specify a pattern repeat number, which is common for all channels.
|
||||
Default is 1; negative numbers and 0 are invalid.
|
||||
|
||||
This file will always return the originally written repeat number.
|
||||
|
||||
When the 255 value is written to it, all patterns will repeat
|
||||
indefinitely.
|
|
@ -19158,6 +19158,15 @@ L: linux-media@vger.kernel.org
|
|||
S: Odd Fixes
|
||||
F: drivers/media/tuners/qm1d1c0042*
|
||||
|
||||
QNAP MCU DRIVER
|
||||
M: Heiko Stuebner <heiko@sntech.de>
|
||||
S: Maintained
|
||||
F: drivers/hwmon/qnap-mcu-hwmon.c
|
||||
F: drivers/input/misc/qnap-mcu-input.c
|
||||
F: drivers/leds/leds-qnap-mcu.c
|
||||
F: drivers/mfd/qnap-mcu.c
|
||||
F: include/linux/mfd/qnap-mcu.h
|
||||
|
||||
QNX4 FILESYSTEM
|
||||
M: Anders Larsen <al@alarsen.net>
|
||||
S: Maintained
|
||||
|
|
|
@ -730,23 +730,30 @@ err_stop_hw:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int sensor_hub_finalize_pending_fn(struct device *dev, void *data)
|
||||
{
|
||||
struct hid_sensor_hub_device *hsdev = dev->platform_data;
|
||||
|
||||
if (hsdev->pending.status)
|
||||
complete(&hsdev->pending.ready);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sensor_hub_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct sensor_hub_data *data = hid_get_drvdata(hdev);
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
hid_dbg(hdev, " hardware removed\n");
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
for (i = 0; i < data->hid_sensor_client_cnt; ++i) {
|
||||
struct hid_sensor_hub_device *hsdev =
|
||||
data->hid_sensor_hub_client_devs[i].platform_data;
|
||||
if (hsdev->pending.status)
|
||||
complete(&hsdev->pending.ready);
|
||||
}
|
||||
device_for_each_child(&hdev->dev, NULL,
|
||||
sensor_hub_finalize_pending_fn);
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
|
||||
mfd_remove_devices(&hdev->dev);
|
||||
mutex_destroy(&data->mutex);
|
||||
}
|
||||
|
|
|
@ -1822,6 +1822,18 @@ config SENSORS_PWM_FAN
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called pwm-fan.
|
||||
|
||||
config SENSORS_QNAP_MCU_HWMON
|
||||
tristate "QNAP MCU hardware monitoring"
|
||||
depends on MFD_QNAP_MCU
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
Say yes here to enable support for fan and temperature sensor
|
||||
connected to a QNAP MCU, as found in a number of QNAP network
|
||||
attached storage devices.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called qnap-mcu-hwmon.
|
||||
|
||||
config SENSORS_RASPBERRYPI_HWMON
|
||||
tristate "Raspberry Pi voltage monitor"
|
||||
depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
|
||||
|
|
|
@ -189,6 +189,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
|
|||
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
|
||||
obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
|
||||
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
|
||||
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
|
||||
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
|
||||
|
|
364
drivers/hwmon/qnap-mcu-hwmon.c
Normal file
364
drivers/hwmon/qnap-mcu-hwmon.c
Normal file
|
@ -0,0 +1,364 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* Driver for hwmon elements found on QNAP-MCU devices
|
||||
*
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/mfd/qnap-mcu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
struct qnap_mcu_hwmon {
|
||||
struct qnap_mcu *mcu;
|
||||
struct device *dev;
|
||||
|
||||
unsigned int pwm_min;
|
||||
unsigned int pwm_max;
|
||||
|
||||
struct fwnode_handle *fan_node;
|
||||
unsigned int fan_state;
|
||||
unsigned int fan_max_state;
|
||||
unsigned int *fan_cooling_levels;
|
||||
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct hwmon_chip_info info;
|
||||
};
|
||||
|
||||
static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm)
|
||||
{
|
||||
static const u8 cmd[] = { '@', 'F', 'A' };
|
||||
u8 reply[6];
|
||||
int ret;
|
||||
|
||||
/* poll the fan rpm */
|
||||
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* First 2 bytes must mirror the sent command */
|
||||
if (memcmp(cmd, reply, 2))
|
||||
return -EIO;
|
||||
|
||||
return reply[4] * 30;
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_get_pwm(struct qnap_mcu_hwmon *hwm)
|
||||
{
|
||||
static const u8 cmd[] = { '@', 'F', 'Z', '0' }; /* 0 = fan-id? */
|
||||
u8 reply[4];
|
||||
int ret;
|
||||
|
||||
/* poll the fan pwm */
|
||||
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* First 3 bytes must mirror the sent command */
|
||||
if (memcmp(cmd, reply, 3))
|
||||
return -EIO;
|
||||
|
||||
return reply[3];
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_set_pwm(struct qnap_mcu_hwmon *hwm, u8 pwm)
|
||||
{
|
||||
const u8 cmd[] = { '@', 'F', 'W', '0', pwm }; /* 0 = fan-id?, pwm 0-255 */
|
||||
|
||||
/* set the fan pwm */
|
||||
return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm)
|
||||
{
|
||||
static const u8 cmd[] = { '@', 'T', '3' };
|
||||
u8 reply[4];
|
||||
int ret;
|
||||
|
||||
/* poll the fan rpm */
|
||||
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* First bytes must mirror the sent command */
|
||||
if (memcmp(cmd, reply, sizeof(cmd)))
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* There is an unknown bit set in bit7.
|
||||
* Bits [6:0] report the actual temperature as returned by the
|
||||
* original qnap firmware-tools, so just drop bit7 for now.
|
||||
*/
|
||||
return (reply[3] & 0x7f) * 1000;
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
if (val < 0 || val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
if (val != 0)
|
||||
val = clamp_val(val, hwm->pwm_min, hwm->pwm_max);
|
||||
|
||||
return qnap_mcu_hwmon_set_pwm(hwm, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
ret = qnap_mcu_hwmon_get_pwm(hwm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case hwmon_fan:
|
||||
ret = qnap_mcu_hwmon_get_rpm(hwm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
return 0;
|
||||
case hwmon_temp:
|
||||
ret = qnap_mcu_hwmon_get_temp(hwm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t qnap_mcu_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return 0444;
|
||||
|
||||
case hwmon_pwm:
|
||||
return 0644;
|
||||
|
||||
case hwmon_fan:
|
||||
return 0444;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = {
|
||||
.is_visible = qnap_mcu_hwmon_is_visible,
|
||||
.read = qnap_mcu_hwmon_read,
|
||||
.write = qnap_mcu_hwmon_write,
|
||||
};
|
||||
|
||||
/* thermal cooling device callbacks */
|
||||
static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = cdev->devdata;
|
||||
|
||||
if (!hwm)
|
||||
return -EINVAL;
|
||||
|
||||
*state = hwm->fan_max_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = cdev->devdata;
|
||||
|
||||
if (!hwm)
|
||||
return -EINVAL;
|
||||
|
||||
*state = hwm->fan_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = cdev->devdata;
|
||||
int ret;
|
||||
|
||||
if (!hwm || state > hwm->fan_max_state)
|
||||
return -EINVAL;
|
||||
|
||||
if (state == hwm->fan_state)
|
||||
return 0;
|
||||
|
||||
ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->fan_cooling_levels[state]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hwm->fan_state = state;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = {
|
||||
.get_max_state = qnap_mcu_hwmon_get_max_state,
|
||||
.get_cur_state = qnap_mcu_hwmon_get_cur_state,
|
||||
.set_cur_state = qnap_mcu_hwmon_set_cur_state,
|
||||
};
|
||||
|
||||
static void devm_fan_node_release(void *data)
|
||||
{
|
||||
struct qnap_mcu_hwmon *hwm = data;
|
||||
|
||||
fwnode_handle_put(hwm->fan_node);
|
||||
}
|
||||
|
||||
static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm)
|
||||
{
|
||||
struct fwnode_handle *fwnode;
|
||||
int num, i, ret;
|
||||
|
||||
fwnode = device_get_named_child_node(dev->parent, "fan-0");
|
||||
if (!fwnode)
|
||||
return 0;
|
||||
|
||||
/* if we found the fan-node, we're keeping it until device-unbind */
|
||||
hwm->fan_node = fwnode;
|
||||
ret = devm_add_action_or_reset(dev, devm_fan_node_release, hwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
num = fwnode_property_count_u32(fwnode, "cooling-levels");
|
||||
if (num <= 0)
|
||||
return dev_err_probe(dev, num ? : -EINVAL,
|
||||
"Failed to count elements in 'cooling-levels'\n");
|
||||
|
||||
hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!hwm->fan_cooling_levels)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = fwnode_property_read_u32_array(fwnode, "cooling-levels",
|
||||
hwm->fan_cooling_levels, num);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to read 'cooling-levels'\n");
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (hwm->fan_cooling_levels[i] > hwm->pwm_max)
|
||||
return dev_err_probe(dev, -EINVAL, "fan state[%d]:%d > %d\n", i,
|
||||
hwm->fan_cooling_levels[i], hwm->pwm_max);
|
||||
}
|
||||
|
||||
hwm->fan_max_state = num - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = {
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static int qnap_mcu_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
|
||||
const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
|
||||
struct qnap_mcu_hwmon *hwm;
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device *hwmon;
|
||||
int ret;
|
||||
|
||||
hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL);
|
||||
if (!hwm)
|
||||
return -ENOMEM;
|
||||
|
||||
hwm->mcu = mcu;
|
||||
hwm->dev = &pdev->dev;
|
||||
hwm->pwm_min = variant->fan_pwm_min;
|
||||
hwm->pwm_max = variant->fan_pwm_max;
|
||||
|
||||
platform_set_drvdata(pdev, hwm);
|
||||
|
||||
/*
|
||||
* Set duty cycle to maximum allowed.
|
||||
*/
|
||||
ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->pwm_max);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops;
|
||||
hwm->info.info = qnap_mcu_hwmon_channels;
|
||||
|
||||
ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hwm->fan_state = hwm->fan_max_state;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu",
|
||||
hwm, &hwm->info, NULL);
|
||||
if (IS_ERR(hwmon))
|
||||
return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n");
|
||||
|
||||
/*
|
||||
* Only register cooling device when we found cooling-levels.
|
||||
* qnap_mcu_hwmon_get_cooling_data() will fail when reading malformed
|
||||
* levels and only succeed with either no or correct cooling levels.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) {
|
||||
cdev = devm_thermal_of_cooling_device_register(dev,
|
||||
to_of_node(hwm->fan_node), "qnap-mcu-hwmon",
|
||||
hwm, &qnap_mcu_hwmon_cooling_ops);
|
||||
if (IS_ERR(cdev))
|
||||
return dev_err_probe(dev, PTR_ERR(cdev),
|
||||
"Failed to register qnap-mcu-hwmon as cooling device\n");
|
||||
hwm->cdev = cdev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver qnap_mcu_hwmon_driver = {
|
||||
.probe = qnap_mcu_hwmon_probe,
|
||||
.driver = {
|
||||
.name = "qnap-mcu-hwmon",
|
||||
},
|
||||
};
|
||||
module_platform_driver(qnap_mcu_hwmon_driver);
|
||||
|
||||
MODULE_ALIAS("platform:qnap-mcu-hwmon");
|
||||
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
|
||||
MODULE_DESCRIPTION("QNAP MCU hwmon driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -917,6 +917,18 @@ config INPUT_HISI_POWERKEY
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called hisi_powerkey.
|
||||
|
||||
config INPUT_QNAP_MCU
|
||||
tristate "Input Support for QNAP MCU controllers"
|
||||
depends on MFD_QNAP_MCU
|
||||
help
|
||||
This option enables support for input elements available on
|
||||
embedded controllers used in QNAP NAS devices.
|
||||
|
||||
This includes a polled power-button as well as a beeper.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called qnap-mcu-input.
|
||||
|
||||
config INPUT_RAVE_SP_PWRBUTTON
|
||||
tristate "RAVE SP Power button Driver"
|
||||
depends on RAVE_SP_CORE
|
||||
|
|
|
@ -68,6 +68,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
|
|||
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
|
||||
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
|
||||
obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o
|
||||
obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o
|
||||
obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o
|
||||
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
|
||||
obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o
|
||||
|
|
153
drivers/input/misc/qnap-mcu-input.c
Normal file
153
drivers/input/misc/qnap-mcu-input.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* Driver for input events on QNAP-MCUs
|
||||
*
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/mfd/qnap-mcu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <uapi/linux/input-event-codes.h>
|
||||
|
||||
/*
|
||||
* The power-key needs to be pressed for a while to create an event,
|
||||
* so there is no use for overly frequent polling.
|
||||
*/
|
||||
#define POLL_INTERVAL 500
|
||||
|
||||
struct qnap_mcu_input_dev {
|
||||
struct input_dev *input;
|
||||
struct qnap_mcu *mcu;
|
||||
struct device *dev;
|
||||
|
||||
struct work_struct beep_work;
|
||||
int beep_type;
|
||||
};
|
||||
|
||||
static void qnap_mcu_input_poll(struct input_dev *input)
|
||||
{
|
||||
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
|
||||
static const u8 cmd[] = { '@', 'C', 'V' };
|
||||
u8 reply[4];
|
||||
int state, ret;
|
||||
|
||||
/* poll the power button */
|
||||
ret = qnap_mcu_exec(idev->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
/* First bytes must mirror the sent command */
|
||||
if (memcmp(cmd, reply, sizeof(cmd))) {
|
||||
dev_err(idev->dev, "malformed data received\n");
|
||||
return;
|
||||
}
|
||||
|
||||
state = reply[3] - 0x30;
|
||||
input_event(input, EV_KEY, KEY_POWER, state);
|
||||
input_sync(input);
|
||||
}
|
||||
|
||||
static void qnap_mcu_input_beeper_work(struct work_struct *work)
|
||||
{
|
||||
struct qnap_mcu_input_dev *idev =
|
||||
container_of(work, struct qnap_mcu_input_dev, beep_work);
|
||||
const u8 cmd[] = { '@', 'C', (idev->beep_type == SND_TONE) ? '3' : '2' };
|
||||
|
||||
qnap_mcu_exec_with_ack(idev->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_input_event(struct input_dev *input, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
|
||||
|
||||
if (type != EV_SND || (code != SND_BELL && code != SND_TONE))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* beep runtime is determined by the MCU */
|
||||
if (value == 0)
|
||||
return 0;
|
||||
|
||||
/* Schedule work to actually turn the beeper on */
|
||||
idev->beep_type = code;
|
||||
schedule_work(&idev->beep_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qnap_mcu_input_close(struct input_dev *input)
|
||||
{
|
||||
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
|
||||
|
||||
cancel_work_sync(&idev->beep_work);
|
||||
}
|
||||
|
||||
static int qnap_mcu_input_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
|
||||
struct qnap_mcu_input_dev *idev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct input_dev *input;
|
||||
int ret;
|
||||
|
||||
idev = devm_kzalloc(dev, sizeof(*idev), GFP_KERNEL);
|
||||
if (!idev)
|
||||
return -ENOMEM;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return dev_err_probe(dev, -ENOMEM, "no memory for input device\n");
|
||||
|
||||
idev->input = input;
|
||||
idev->dev = dev;
|
||||
idev->mcu = mcu;
|
||||
|
||||
input_set_drvdata(input, idev);
|
||||
|
||||
input->name = "qnap-mcu";
|
||||
input->phys = "qnap-mcu-input/input0";
|
||||
input->id.bustype = BUS_HOST;
|
||||
input->id.vendor = 0x0001;
|
||||
input->id.product = 0x0001;
|
||||
input->id.version = 0x0100;
|
||||
input->event = qnap_mcu_input_event;
|
||||
input->close = qnap_mcu_input_close;
|
||||
|
||||
input_set_capability(input, EV_KEY, KEY_POWER);
|
||||
input_set_capability(input, EV_SND, SND_BELL);
|
||||
input_set_capability(input, EV_SND, SND_TONE);
|
||||
|
||||
INIT_WORK(&idev->beep_work, qnap_mcu_input_beeper_work);
|
||||
|
||||
ret = input_setup_polling(input, qnap_mcu_input_poll);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "unable to set up polling\n");
|
||||
|
||||
input_set_poll_interval(input, POLL_INTERVAL);
|
||||
|
||||
ret = input_register_device(input);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "unable to register input device\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver qnap_mcu_input_driver = {
|
||||
.probe = qnap_mcu_input_probe,
|
||||
.driver = {
|
||||
.name = "qnap-mcu-input",
|
||||
},
|
||||
};
|
||||
module_platform_driver(qnap_mcu_input_driver);
|
||||
|
||||
MODULE_ALIAS("platform:qnap-mcu-input");
|
||||
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
|
||||
MODULE_DESCRIPTION("QNAP MCU input driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -580,6 +580,17 @@ config LEDS_PCA995X
|
|||
LED driver chips accessed via the I2C bus. Supported
|
||||
devices include PCA9955BTW, PCA9952TW and PCA9955TW.
|
||||
|
||||
config LEDS_QNAP_MCU
|
||||
tristate "LED Support for QNAP MCU controllers"
|
||||
depends on LEDS_CLASS
|
||||
depends on MFD_QNAP_MCU
|
||||
help
|
||||
This option enables support for LEDs available on embedded
|
||||
controllers used in QNAP NAS devices.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called qnap-mcu-leds.
|
||||
|
||||
config LEDS_WM831X_STATUS
|
||||
tristate "LED support for status LEDs on WM831x PMICs"
|
||||
depends on LEDS_CLASS
|
||||
|
@ -931,6 +942,16 @@ config LEDS_LM36274
|
|||
Say Y to enable the LM36274 LED driver for TI LMU devices.
|
||||
This supports the LED device LM36274.
|
||||
|
||||
config LEDS_ST1202
|
||||
tristate "LED Support for STMicroelectronics LED1202 I2C chips"
|
||||
depends on LEDS_CLASS
|
||||
depends on I2C
|
||||
depends on OF
|
||||
select LEDS_TRIGGERS
|
||||
help
|
||||
Say Y to enable support for LEDs connected to LED1202
|
||||
LED driver chips accessed via the I2C bus.
|
||||
|
||||
config LEDS_TPS6105X
|
||||
tristate "LED support for TI TPS6105X"
|
||||
depends on LEDS_CLASS
|
||||
|
|
|
@ -79,6 +79,7 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o
|
|||
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
|
||||
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
|
||||
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
|
||||
obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o
|
||||
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
|
||||
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
|
||||
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
|
||||
|
|
227
drivers/leds/leds-qnap-mcu.c
Normal file
227
drivers/leds/leds-qnap-mcu.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Driver for LEDs found on QNAP MCU devices
|
||||
*
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#include <linux/leds.h>
|
||||
#include <linux/mfd/qnap-mcu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <uapi/linux/uleds.h>
|
||||
|
||||
enum qnap_mcu_err_led_mode {
|
||||
QNAP_MCU_ERR_LED_ON = 0,
|
||||
QNAP_MCU_ERR_LED_OFF = 1,
|
||||
QNAP_MCU_ERR_LED_BLINK_FAST = 2,
|
||||
QNAP_MCU_ERR_LED_BLINK_SLOW = 3,
|
||||
};
|
||||
|
||||
struct qnap_mcu_err_led {
|
||||
struct qnap_mcu *mcu;
|
||||
struct led_classdev cdev;
|
||||
char name[LED_MAX_NAME_SIZE];
|
||||
u8 num;
|
||||
u8 mode;
|
||||
};
|
||||
|
||||
static inline struct qnap_mcu_err_led *
|
||||
cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct qnap_mcu_err_led, cdev);
|
||||
}
|
||||
|
||||
static int qnap_mcu_err_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
|
||||
u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
|
||||
|
||||
/* Don't disturb a possible set blink-mode if LED stays on */
|
||||
if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST)
|
||||
return 0;
|
||||
|
||||
err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF;
|
||||
cmd[3] = '0' + err_led->mode;
|
||||
|
||||
return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
|
||||
u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
|
||||
|
||||
/* LED is off, nothing to do */
|
||||
if (err_led->mode == QNAP_MCU_ERR_LED_OFF)
|
||||
return 0;
|
||||
|
||||
if (*delay_on < 500) {
|
||||
*delay_on = 100;
|
||||
*delay_off = 100;
|
||||
err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST;
|
||||
} else {
|
||||
*delay_on = 500;
|
||||
*delay_off = 500;
|
||||
err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW;
|
||||
}
|
||||
|
||||
cmd[3] = '0' + err_led->mode;
|
||||
|
||||
return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led)
|
||||
{
|
||||
struct qnap_mcu_err_led *err_led;
|
||||
int ret;
|
||||
|
||||
err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL);
|
||||
if (!err_led)
|
||||
return -ENOMEM;
|
||||
|
||||
err_led->mcu = mcu;
|
||||
err_led->num = num_err_led;
|
||||
err_led->mode = QNAP_MCU_ERR_LED_OFF;
|
||||
|
||||
scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1);
|
||||
err_led->cdev.name = err_led->name;
|
||||
|
||||
err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set;
|
||||
err_led->cdev.blink_set = qnap_mcu_err_led_blink_set;
|
||||
err_led->cdev.brightness = 0;
|
||||
err_led->cdev.max_brightness = 1;
|
||||
|
||||
ret = devm_led_classdev_register(dev, &err_led->cdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return qnap_mcu_err_led_set(&err_led->cdev, 0);
|
||||
}
|
||||
|
||||
enum qnap_mcu_usb_led_mode {
|
||||
QNAP_MCU_USB_LED_ON = 1,
|
||||
QNAP_MCU_USB_LED_OFF = 3,
|
||||
QNAP_MCU_USB_LED_BLINK = 2,
|
||||
};
|
||||
|
||||
struct qnap_mcu_usb_led {
|
||||
struct qnap_mcu *mcu;
|
||||
struct led_classdev cdev;
|
||||
u8 mode;
|
||||
};
|
||||
|
||||
static inline struct qnap_mcu_usb_led *
|
||||
cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct qnap_mcu_usb_led, cdev);
|
||||
}
|
||||
|
||||
static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
|
||||
u8 cmd[] = { '@', 'C', 0 };
|
||||
|
||||
/* Don't disturb a possible set blink-mode if LED stays on */
|
||||
if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK)
|
||||
return 0;
|
||||
|
||||
usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF;
|
||||
|
||||
/*
|
||||
* Byte 3 is shared between the usb led target on/off/blink
|
||||
* and also the buzzer control (in the input driver)
|
||||
*/
|
||||
cmd[2] = 'D' + usb_led->mode;
|
||||
|
||||
return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
|
||||
u8 cmd[] = { '@', 'C', 0 };
|
||||
|
||||
/* LED is off, nothing to do */
|
||||
if (usb_led->mode == QNAP_MCU_USB_LED_OFF)
|
||||
return 0;
|
||||
|
||||
*delay_on = 250;
|
||||
*delay_off = 250;
|
||||
usb_led->mode = QNAP_MCU_USB_LED_BLINK;
|
||||
|
||||
/*
|
||||
* Byte 3 is shared between the USB LED target on/off/blink
|
||||
* and also the buzzer control (in the input driver)
|
||||
*/
|
||||
cmd[2] = 'D' + usb_led->mode;
|
||||
|
||||
return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu)
|
||||
{
|
||||
struct qnap_mcu_usb_led *usb_led;
|
||||
int ret;
|
||||
|
||||
usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL);
|
||||
if (!usb_led)
|
||||
return -ENOMEM;
|
||||
|
||||
usb_led->mcu = mcu;
|
||||
usb_led->mode = QNAP_MCU_USB_LED_OFF;
|
||||
usb_led->cdev.name = "usb:blue:disk";
|
||||
usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set;
|
||||
usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set;
|
||||
usb_led->cdev.brightness = 0;
|
||||
usb_led->cdev.max_brightness = 1;
|
||||
|
||||
ret = devm_led_classdev_register(dev, &usb_led->cdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return qnap_mcu_usb_led_set(&usb_led->cdev, 0);
|
||||
}
|
||||
|
||||
static int qnap_mcu_leds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
|
||||
const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < variant->num_drives; i++) {
|
||||
ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"failed to register error LED %d\n", i);
|
||||
}
|
||||
|
||||
if (variant->usb_led) {
|
||||
ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"failed to register USB LED\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver qnap_mcu_leds_driver = {
|
||||
.probe = qnap_mcu_leds_probe,
|
||||
.driver = {
|
||||
.name = "qnap-mcu-leds",
|
||||
},
|
||||
};
|
||||
module_platform_driver(qnap_mcu_leds_driver);
|
||||
|
||||
MODULE_ALIAS("platform:qnap-mcu-leds");
|
||||
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
|
||||
MODULE_DESCRIPTION("QNAP MCU LEDs driver");
|
||||
MODULE_LICENSE("GPL");
|
416
drivers/leds/leds-st1202.c
Normal file
416
drivers/leds/leds-st1202.c
Normal file
|
@ -0,0 +1,416 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* LED driver for STMicroelectronics LED1202 chip
|
||||
*
|
||||
* Copyright (C) 2024 Remote-Tech Ltd. UK
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#define ST1202_CHAN_DISABLE_ALL 0x00
|
||||
#define ST1202_CHAN_ENABLE_HIGH 0x03
|
||||
#define ST1202_CHAN_ENABLE_LOW 0x02
|
||||
#define ST1202_CONFIG_REG 0x04
|
||||
/* PATS: Pattern sequence feature enable */
|
||||
#define ST1202_CONFIG_REG_PATS BIT(7)
|
||||
/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
|
||||
#define ST1202_CONFIG_REG_PATSR BIT(6)
|
||||
#define ST1202_CONFIG_REG_SHFT BIT(3)
|
||||
#define ST1202_DEV_ENABLE 0x01
|
||||
#define ST1202_DEV_ENABLE_ON BIT(0)
|
||||
#define ST1202_DEV_ENABLE_RESET BIT(7)
|
||||
#define ST1202_DEVICE_ID 0x00
|
||||
#define ST1202_ILED_REG0 0x09
|
||||
#define ST1202_MAX_LEDS 12
|
||||
#define ST1202_MAX_PATTERNS 8
|
||||
#define ST1202_MILLIS_PATTERN_DUR_MAX 5660
|
||||
#define ST1202_MILLIS_PATTERN_DUR_MIN 22
|
||||
#define ST1202_PATTERN_DUR 0x16
|
||||
#define ST1202_PATTERN_PWM 0x1E
|
||||
#define ST1202_PATTERN_REP 0x15
|
||||
|
||||
struct st1202_led {
|
||||
struct fwnode_handle *fwnode;
|
||||
struct led_classdev led_cdev;
|
||||
struct st1202_chip *chip;
|
||||
bool is_active;
|
||||
int led_num;
|
||||
};
|
||||
|
||||
struct st1202_chip {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
struct st1202_led leds[ST1202_MAX_LEDS];
|
||||
};
|
||||
|
||||
static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
|
||||
{
|
||||
return container_of(cdev, struct st1202_led, led_cdev);
|
||||
}
|
||||
|
||||
static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
|
||||
{
|
||||
struct device *dev = &chip->client->dev;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(chip->client, reg);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = (uint8_t)ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
|
||||
{
|
||||
struct device *dev = &chip->client->dev;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(chip->client, reg, val);
|
||||
if (ret != 0)
|
||||
dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
|
||||
{
|
||||
return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1;
|
||||
}
|
||||
|
||||
static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
|
||||
int pattern, unsigned int value)
|
||||
{
|
||||
u8 value_l, value_h;
|
||||
int ret;
|
||||
|
||||
value_l = (u8)value;
|
||||
value_h = (u8)(value >> 8);
|
||||
|
||||
/*
|
||||
* Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
|
||||
* where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
|
||||
* and y is the pattern number in hexadecimal (y = 00h .. 07h)
|
||||
*/
|
||||
ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
|
||||
value_l);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh),
|
||||
* where x is the channel number in hexadecimal (x = 00h .. 0Bh)
|
||||
* and y is the pattern number in hexadecimal (y = 00h .. 07h)
|
||||
*/
|
||||
ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern),
|
||||
value_h);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
|
||||
unsigned int value)
|
||||
{
|
||||
return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
|
||||
st1202_prescalar_to_miliseconds(value));
|
||||
}
|
||||
|
||||
static void st1202_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
|
||||
}
|
||||
|
||||
static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(led_cdev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
u8 value = 0;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
|
||||
{
|
||||
u8 chan_low, chan_high;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
if (led_num <= 7) {
|
||||
ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num);
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
} else {
|
||||
ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
chan_high = active ? chan_high | (BIT(led_num) >> 8) :
|
||||
chan_high & ~(BIT(led_num) >> 8);
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(ldev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
|
||||
return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true);
|
||||
}
|
||||
|
||||
static int st1202_led_pattern_clear(struct led_classdev *ldev)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(ldev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
|
||||
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_led_pattern_set(struct led_classdev *ldev,
|
||||
struct led_pattern *pattern,
|
||||
u32 len, int repeat)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(ldev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
int ret;
|
||||
|
||||
if (len > ST1202_MAX_PATTERNS)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
for (int patt = 0; patt < len; patt++) {
|
||||
if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
|
||||
pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
|
||||
ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_dt_init(struct st1202_chip *chip)
|
||||
{
|
||||
struct device *dev = &chip->client->dev;
|
||||
struct st1202_led *led;
|
||||
int err, reg;
|
||||
|
||||
for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
|
||||
struct led_init_data init_data = {};
|
||||
|
||||
err = of_property_read_u32(child, "reg", ®);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Invalid register\n");
|
||||
|
||||
led = &chip->leds[reg];
|
||||
led->is_active = true;
|
||||
led->fwnode = of_fwnode_handle(child);
|
||||
|
||||
led->led_cdev.max_brightness = U8_MAX;
|
||||
led->led_cdev.brightness_set_blocking = st1202_led_set;
|
||||
led->led_cdev.pattern_set = st1202_led_pattern_set;
|
||||
led->led_cdev.pattern_clear = st1202_led_pattern_clear;
|
||||
led->led_cdev.default_trigger = "pattern";
|
||||
|
||||
init_data.fwnode = led->fwnode;
|
||||
init_data.devicename = "st1202";
|
||||
init_data.default_label = ":";
|
||||
|
||||
err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
|
||||
if (err < 0)
|
||||
return dev_err_probe(dev, err, "Failed to register LED class device\n");
|
||||
|
||||
led->led_cdev.brightness_set = st1202_brightness_set;
|
||||
led->led_cdev.brightness_get = st1202_brightness_get;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_setup(struct st1202_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
/*
|
||||
* Once the supply voltage is applied, the LED1202 executes some internal checks,
|
||||
* afterwords it stops the oscillator and puts the internal LDO in quiescent mode.
|
||||
* To start the device, EN bit must be set inside the “Device Enable” register at
|
||||
* address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
|
||||
* from the internal non-volatile memory and performs an auto-calibration procedure
|
||||
* in order to increase the output current precision.
|
||||
* Such initialization lasts about 6.5 ms.
|
||||
*/
|
||||
|
||||
/* Reset the chip during setup */
|
||||
ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Enable phase-shift delay feature */
|
||||
ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Enable the device */
|
||||
ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Duration of initialization */
|
||||
usleep_range(6500, 10000);
|
||||
|
||||
/* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
|
||||
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
|
||||
ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st1202_probe(struct i2c_client *client)
|
||||
{
|
||||
struct st1202_chip *chip;
|
||||
struct st1202_led *led;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n");
|
||||
|
||||
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
devm_mutex_init(&client->dev, &chip->lock);
|
||||
chip->client = client;
|
||||
|
||||
ret = st1202_dt_init(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_setup(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < ST1202_MAX_LEDS; i++) {
|
||||
led = &chip->leds[i];
|
||||
led->chip = chip;
|
||||
led->led_num = i;
|
||||
|
||||
if (!led->is_active)
|
||||
continue;
|
||||
|
||||
ret = st1202_channel_set(led->chip, led->led_num, true);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to activate LED channel\n");
|
||||
|
||||
ret = st1202_led_pattern_clear(&led->led_cdev);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to clear LED pattern\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id st1202_id[] = {
|
||||
{ "st1202-i2c" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st1202_id);
|
||||
|
||||
static const struct of_device_id st1202_dt_ids[] = {
|
||||
{ .compatible = "st,led1202" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st1202_dt_ids);
|
||||
|
||||
static struct i2c_driver st1202_driver = {
|
||||
.driver = {
|
||||
.name = "leds-st1202",
|
||||
.of_match_table = of_match_ptr(st1202_dt_ids),
|
||||
},
|
||||
.probe = st1202_probe,
|
||||
.id_table = st1202_id,
|
||||
};
|
||||
module_i2c_driver(st1202_driver);
|
||||
|
||||
MODULE_AUTHOR("Remote Tech LTD");
|
||||
MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -2386,6 +2386,19 @@ config MFD_INTEL_M10_BMC_PMCI
|
|||
additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
config MFD_QNAP_MCU
|
||||
tristate "QNAP microcontroller unit core driver"
|
||||
depends on SERIAL_DEV_BUS
|
||||
select MFD_CORE
|
||||
help
|
||||
Select this to get support for the QNAP MCU device found in
|
||||
several devices of QNAP network attached storage products that
|
||||
implements additional functionality for the device, like fan
|
||||
and LED control.
|
||||
|
||||
This driver implements the base serial protocol to talk to the
|
||||
device and provides functions for the other parts to hook into.
|
||||
|
||||
config MFD_RSMU_I2C
|
||||
tristate "Renesas Synchronization Management Unit with I2C"
|
||||
depends on I2C && OF
|
||||
|
@ -2414,5 +2427,17 @@ config MFD_RSMU_SPI
|
|||
Additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
config MFD_UPBOARD_FPGA
|
||||
tristate "Support for the AAeon UP board FPGA"
|
||||
depends on (X86 && ACPI)
|
||||
select MFD_CORE
|
||||
help
|
||||
Select this option to enable the AAEON UP and UP^2 onboard FPGA.
|
||||
This is the core driver of this FPGA, which has a pin controller and a
|
||||
LED controller.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called upboard-fpga.
|
||||
|
||||
endmenu
|
||||
endif
|
||||
|
|
|
@ -288,5 +288,9 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC_PMCI) += intel-m10-bmc-pmci.o
|
|||
obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o
|
||||
obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o
|
||||
|
||||
obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o
|
||||
|
||||
obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o
|
||||
obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
|
||||
|
||||
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
|
||||
|
|
|
@ -1445,7 +1445,7 @@ int axp20x_device_probe(struct axp20x_dev *axp20x)
|
|||
}
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(axp20x->dev, PLATFORM_DEVID_AUTO, axp20x->cells,
|
||||
ret = mfd_add_devices(axp20x->dev, PLATFORM_DEVID_NONE, axp20x->cells,
|
||||
axp20x->nr_cells, NULL, 0, NULL);
|
||||
|
||||
if (ret) {
|
||||
|
@ -1455,10 +1455,7 @@ int axp20x_device_probe(struct axp20x_dev *axp20x)
|
|||
}
|
||||
|
||||
if (axp20x->variant != AXP288_ID)
|
||||
devm_register_sys_off_handler(axp20x->dev,
|
||||
SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
axp20x_power_off, axp20x);
|
||||
devm_register_power_off_handler(axp20x->dev, axp20x_power_off, axp20x);
|
||||
|
||||
dev_info(axp20x->dev, "AXP20X driver loaded\n");
|
||||
|
||||
|
|
|
@ -56,13 +56,6 @@ static int cs42l43_i2c_probe(struct i2c_client *i2c)
|
|||
return cs42l43_dev_probe(cs42l43);
|
||||
}
|
||||
|
||||
static void cs42l43_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct cs42l43 *cs42l43 = dev_get_drvdata(&i2c->dev);
|
||||
|
||||
cs42l43_dev_remove(cs42l43);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_OF)
|
||||
static const struct of_device_id cs42l43_of_match[] = {
|
||||
{ .compatible = "cirrus,cs42l43", },
|
||||
|
@ -88,7 +81,6 @@ static struct i2c_driver cs42l43_i2c_driver = {
|
|||
},
|
||||
|
||||
.probe = cs42l43_i2c_probe,
|
||||
.remove = cs42l43_i2c_remove,
|
||||
};
|
||||
module_i2c_driver(cs42l43_i2c_driver);
|
||||
|
||||
|
|
|
@ -187,15 +187,6 @@ static int cs42l43_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *
|
|||
return cs42l43_dev_probe(cs42l43);
|
||||
}
|
||||
|
||||
static int cs42l43_sdw_remove(struct sdw_slave *sdw)
|
||||
{
|
||||
struct cs42l43 *cs42l43 = dev_get_drvdata(&sdw->dev);
|
||||
|
||||
cs42l43_dev_remove(cs42l43);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sdw_device_id cs42l43_sdw_id[] = {
|
||||
SDW_SLAVE_ENTRY(0x01FA, 0x4243, 0),
|
||||
{}
|
||||
|
@ -209,7 +200,6 @@ static struct sdw_driver cs42l43_sdw_driver = {
|
|||
},
|
||||
|
||||
.probe = cs42l43_sdw_probe,
|
||||
.remove = cs42l43_sdw_remove,
|
||||
.id_table = cs42l43_sdw_id,
|
||||
.ops = &cs42l43_sdw_ops,
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
#define CS42L43_RESET_DELAY_MS 20
|
||||
|
||||
#define CS42L43_SDW_ATTACH_TIMEOUT_MS 500
|
||||
#define CS42L43_SDW_ATTACH_TIMEOUT_MS 5000
|
||||
#define CS42L43_SDW_DETACH_TIMEOUT_MS 100
|
||||
|
||||
#define CS42L43_MCU_BOOT_STAGE1 1
|
||||
|
@ -48,6 +48,7 @@
|
|||
|
||||
#define CS42L43_MCU_SUPPORTED_REV 0x2105
|
||||
#define CS42L43_MCU_SHADOW_REGS_REQUIRED_REV 0x2200
|
||||
#define CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV 0x1002
|
||||
#define CS42L43_MCU_SUPPORTED_BIOS_REV 0x0001
|
||||
|
||||
#define CS42L43_VDDP_DELAY_US 50
|
||||
|
@ -773,7 +774,8 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43)
|
|||
* Later versions of the firmwware require the driver to access some
|
||||
* features through a set of shadow registers.
|
||||
*/
|
||||
shadow = mcu_rev >= CS42L43_MCU_SHADOW_REGS_REQUIRED_REV;
|
||||
shadow = (mcu_rev >= CS42L43_MCU_SHADOW_REGS_REQUIRED_REV) ||
|
||||
(bios_rev >= CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV);
|
||||
|
||||
ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_CONTROL, &secure_cfg);
|
||||
if (ret) {
|
||||
|
@ -982,7 +984,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43)
|
|||
/* vdd-p must be on for 50uS before any other supply */
|
||||
usleep_range(CS42L43_VDDP_DELAY_US, 2 * CS42L43_VDDP_DELAY_US);
|
||||
|
||||
gpiod_set_value_cansleep(cs42l43->reset, 1);
|
||||
gpiod_set_raw_value_cansleep(cs42l43->reset, 1);
|
||||
|
||||
ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies);
|
||||
if (ret) {
|
||||
|
@ -1003,7 +1005,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43)
|
|||
err_core_supplies:
|
||||
regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies);
|
||||
err_reset:
|
||||
gpiod_set_value_cansleep(cs42l43->reset, 0);
|
||||
gpiod_set_raw_value_cansleep(cs42l43->reset, 0);
|
||||
regulator_disable(cs42l43->vdd_p);
|
||||
|
||||
return ret;
|
||||
|
@ -1025,7 +1027,7 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43)
|
|||
return ret;
|
||||
}
|
||||
|
||||
gpiod_set_value_cansleep(cs42l43->reset, 0);
|
||||
gpiod_set_raw_value_cansleep(cs42l43->reset, 0);
|
||||
|
||||
ret = regulator_disable(cs42l43->vdd_p);
|
||||
if (ret) {
|
||||
|
@ -1036,6 +1038,15 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void cs42l43_dev_remove(void *data)
|
||||
{
|
||||
struct cs42l43 *cs42l43 = data;
|
||||
|
||||
cancel_work_sync(&cs42l43->boot_work);
|
||||
|
||||
cs42l43_power_down(cs42l43);
|
||||
}
|
||||
|
||||
int cs42l43_dev_probe(struct cs42l43 *cs42l43)
|
||||
{
|
||||
int i, ret;
|
||||
|
@ -1050,11 +1061,13 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43)
|
|||
|
||||
regcache_cache_only(cs42l43->regmap, true);
|
||||
|
||||
cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_LOW);
|
||||
cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(cs42l43->reset))
|
||||
return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->reset),
|
||||
"Failed to get reset\n");
|
||||
|
||||
gpiod_set_raw_value_cansleep(cs42l43->reset, 0);
|
||||
|
||||
cs42l43->vdd_p = devm_regulator_get(cs42l43->dev, "vdd-p");
|
||||
if (IS_ERR(cs42l43->vdd_p))
|
||||
return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->vdd_p),
|
||||
|
@ -1080,6 +1093,10 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(cs42l43->dev, cs42l43_dev_remove, cs42l43);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_set_autosuspend_delay(cs42l43->dev, CS42L43_AUTOSUSPEND_TIME_MS);
|
||||
pm_runtime_use_autosuspend(cs42l43->dev);
|
||||
pm_runtime_set_active(cs42l43->dev);
|
||||
|
@ -1098,14 +1115,6 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43)
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43");
|
||||
|
||||
void cs42l43_dev_remove(struct cs42l43 *cs42l43)
|
||||
{
|
||||
cancel_work_sync(&cs42l43->boot_work);
|
||||
|
||||
cs42l43_power_down(cs42l43);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(cs42l43_dev_remove, "MFD_CS42L43");
|
||||
|
||||
static int cs42l43_suspend(struct device *dev)
|
||||
{
|
||||
struct cs42l43 *cs42l43 = dev_get_drvdata(dev);
|
||||
|
|
|
@ -25,6 +25,5 @@ bool cs42l43_precious_register(struct device *dev, unsigned int reg);
|
|||
bool cs42l43_volatile_register(struct device *dev, unsigned int reg);
|
||||
|
||||
int cs42l43_dev_probe(struct cs42l43 *cs42l43);
|
||||
void cs42l43_dev_remove(struct cs42l43 *cs42l43);
|
||||
|
||||
#endif /* CS42L43_CORE_INT_H */
|
||||
|
|
|
@ -585,6 +585,7 @@ static int da9052_clear_fault_log(struct da9052 *da9052)
|
|||
"Cannot reset FAULT_LOG values %d\n", ret);
|
||||
}
|
||||
|
||||
da9052->fault_log = fault_log;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ static struct mfd_cell chtdc_ti_dev[] = {
|
|||
static const struct regmap_config chtdc_ti_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 128,
|
||||
.max_register = 0xff,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
|
|
|
@ -834,8 +834,9 @@ static const struct pci_device_id lpc_ich_ids[] = {
|
|||
{ PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME},
|
||||
{ PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9},
|
||||
{ PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M},
|
||||
{ PCI_VDEVICE(INTEL, 0x3197), LPC_GLK},
|
||||
{ PCI_VDEVICE(INTEL, 0x2b9c), LPC_COUGARMOUNTAIN},
|
||||
{ PCI_VDEVICE(INTEL, 0x3197), LPC_GLK},
|
||||
{ PCI_VDEVICE(INTEL, 0x31e8), LPC_GLK},
|
||||
{ PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO},
|
||||
{ PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R},
|
||||
{ PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10},
|
||||
|
|
338
drivers/mfd/qnap-mcu.c
Normal file
338
drivers/mfd/qnap-mcu.c
Normal file
|
@ -0,0 +1,338 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Core driver for the microcontroller unit in QNAP NAS devices that is
|
||||
* connected via a dedicated UART port.
|
||||
*
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/qnap-mcu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/serdev.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* The longest command found so far is 5 bytes long */
|
||||
#define QNAP_MCU_MAX_CMD_SIZE 5
|
||||
#define QNAP_MCU_MAX_DATA_SIZE 36
|
||||
#define QNAP_MCU_CHECKSUM_SIZE 1
|
||||
|
||||
#define QNAP_MCU_RX_BUFFER_SIZE \
|
||||
(QNAP_MCU_MAX_DATA_SIZE + QNAP_MCU_CHECKSUM_SIZE)
|
||||
|
||||
#define QNAP_MCU_TX_BUFFER_SIZE \
|
||||
(QNAP_MCU_MAX_CMD_SIZE + QNAP_MCU_CHECKSUM_SIZE)
|
||||
|
||||
#define QNAP_MCU_ACK_LEN 2
|
||||
#define QNAP_MCU_VERSION_LEN 4
|
||||
|
||||
#define QNAP_MCU_TIMEOUT_MS 500
|
||||
|
||||
/**
|
||||
* struct qnap_mcu_reply - Reply to a command
|
||||
*
|
||||
* @data: Buffer to store reply payload in
|
||||
* @length: Expected reply length, including the checksum
|
||||
* @received: Received number of bytes, so far
|
||||
* @done: Triggered when the entire reply has been received
|
||||
*/
|
||||
struct qnap_mcu_reply {
|
||||
u8 *data;
|
||||
size_t length;
|
||||
size_t received;
|
||||
struct completion done;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct qnap_mcu - QNAP NAS embedded controller
|
||||
*
|
||||
* @serdev: Pointer to underlying serdev
|
||||
* @bus_lock: Lock to serialize access to the device
|
||||
* @reply: Reply data structure
|
||||
* @variant: Device variant specific information
|
||||
* @version: MCU firmware version
|
||||
*/
|
||||
struct qnap_mcu {
|
||||
struct serdev_device *serdev;
|
||||
struct mutex bus_lock;
|
||||
struct qnap_mcu_reply reply;
|
||||
const struct qnap_mcu_variant *variant;
|
||||
u8 version[QNAP_MCU_VERSION_LEN];
|
||||
};
|
||||
|
||||
/*
|
||||
* The QNAP-MCU uses a basic XOR checksum.
|
||||
* It is always the last byte and XORs the whole previous message.
|
||||
*/
|
||||
static u8 qnap_mcu_csum(const u8 *buf, size_t size)
|
||||
{
|
||||
u8 csum = 0;
|
||||
|
||||
while (size--)
|
||||
csum ^= *buf++;
|
||||
|
||||
return csum;
|
||||
}
|
||||
|
||||
static int qnap_mcu_write(struct qnap_mcu *mcu, const u8 *data, u8 data_size)
|
||||
{
|
||||
unsigned char tx[QNAP_MCU_TX_BUFFER_SIZE];
|
||||
size_t length = data_size + QNAP_MCU_CHECKSUM_SIZE;
|
||||
|
||||
if (length > sizeof(tx)) {
|
||||
dev_err(&mcu->serdev->dev, "data too big for transmit buffer");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memcpy(tx, data, data_size);
|
||||
tx[data_size] = qnap_mcu_csum(data, data_size);
|
||||
|
||||
serdev_device_write_flush(mcu->serdev);
|
||||
|
||||
return serdev_device_write(mcu->serdev, tx, length, HZ);
|
||||
}
|
||||
|
||||
static size_t qnap_mcu_receive_buf(struct serdev_device *serdev, const u8 *buf, size_t size)
|
||||
{
|
||||
struct device *dev = &serdev->dev;
|
||||
struct qnap_mcu *mcu = dev_get_drvdata(dev);
|
||||
struct qnap_mcu_reply *reply = &mcu->reply;
|
||||
const u8 *src = buf;
|
||||
const u8 *end = buf + size;
|
||||
|
||||
if (!reply->length) {
|
||||
dev_warn(dev, "Received %zu bytes, we were not waiting for\n", size);
|
||||
return size;
|
||||
}
|
||||
|
||||
while (src < end) {
|
||||
reply->data[reply->received] = *src++;
|
||||
reply->received++;
|
||||
|
||||
if (reply->received == reply->length) {
|
||||
/* We don't expect any characters from the device now */
|
||||
reply->length = 0;
|
||||
|
||||
complete(&reply->done);
|
||||
|
||||
/*
|
||||
* We report the consumed number of bytes. If there
|
||||
* are still bytes remaining (though there shouldn't)
|
||||
* the serdev layer will re-execute this handler with
|
||||
* the remainder of the Rx bytes.
|
||||
*/
|
||||
return src - buf;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The only way to get out of the above loop and end up here
|
||||
* is through consuming all of the supplied data, so here we
|
||||
* report that we processed it all.
|
||||
*/
|
||||
return size;
|
||||
}
|
||||
|
||||
static const struct serdev_device_ops qnap_mcu_serdev_device_ops = {
|
||||
.receive_buf = qnap_mcu_receive_buf,
|
||||
.write_wakeup = serdev_device_write_wakeup,
|
||||
};
|
||||
|
||||
int qnap_mcu_exec(struct qnap_mcu *mcu,
|
||||
const u8 *cmd_data, size_t cmd_data_size,
|
||||
u8 *reply_data, size_t reply_data_size)
|
||||
{
|
||||
unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE];
|
||||
size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE;
|
||||
struct qnap_mcu_reply *reply = &mcu->reply;
|
||||
int ret = 0;
|
||||
|
||||
if (length > sizeof(rx)) {
|
||||
dev_err(&mcu->serdev->dev, "expected data too big for receive buffer");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&mcu->bus_lock);
|
||||
|
||||
reply->data = rx,
|
||||
reply->length = length,
|
||||
reply->received = 0,
|
||||
reinit_completion(&reply->done);
|
||||
|
||||
qnap_mcu_write(mcu, cmd_data, cmd_data_size);
|
||||
|
||||
serdev_device_wait_until_sent(mcu->serdev, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS));
|
||||
|
||||
if (!wait_for_completion_timeout(&reply->done, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS))) {
|
||||
dev_err(&mcu->serdev->dev, "Command timeout\n");
|
||||
ret = -ETIMEDOUT;
|
||||
} else {
|
||||
u8 crc = qnap_mcu_csum(rx, reply_data_size);
|
||||
|
||||
if (crc != rx[reply_data_size]) {
|
||||
dev_err(&mcu->serdev->dev,
|
||||
"Invalid Checksum received\n");
|
||||
ret = -EIO;
|
||||
} else {
|
||||
memcpy(reply_data, rx, reply_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&mcu->bus_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(qnap_mcu_exec);
|
||||
|
||||
int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu,
|
||||
const u8 *cmd_data, size_t cmd_data_size)
|
||||
{
|
||||
u8 ack[QNAP_MCU_ACK_LEN];
|
||||
int ret;
|
||||
|
||||
ret = qnap_mcu_exec(mcu, cmd_data, cmd_data_size, ack, sizeof(ack));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Should return @0 */
|
||||
if (ack[0] != '@' || ack[1] != '0') {
|
||||
dev_err(&mcu->serdev->dev, "Did not receive ack\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(qnap_mcu_exec_with_ack);
|
||||
|
||||
static int qnap_mcu_get_version(struct qnap_mcu *mcu)
|
||||
{
|
||||
const u8 cmd[] = { '%', 'V' };
|
||||
u8 rx[14];
|
||||
int ret;
|
||||
|
||||
/* Reply is the 2 command-bytes + 4 bytes describing the version */
|
||||
ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, QNAP_MCU_VERSION_LEN + 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memcpy(mcu->version, &rx[2], QNAP_MCU_VERSION_LEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The MCU controls power to the peripherals but not the CPU.
|
||||
*
|
||||
* So using the PMIC to power off the system keeps the MCU and hard-drives
|
||||
* running. This also then prevents the system from turning back on until
|
||||
* the MCU is turned off by unplugging the power cable.
|
||||
* Turning off the MCU alone on the other hand turns off the hard drives,
|
||||
* LEDs, etc while the main SoC stays running - including its network ports.
|
||||
*/
|
||||
static int qnap_mcu_power_off(struct sys_off_data *data)
|
||||
{
|
||||
const u8 cmd[] = { '@', 'C', '0' };
|
||||
struct qnap_mcu *mcu = data->cb_data;
|
||||
int ret;
|
||||
|
||||
ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd));
|
||||
if (ret) {
|
||||
dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret);
|
||||
return NOTIFY_STOP;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static const struct qnap_mcu_variant qnap_ts433_mcu = {
|
||||
.baud_rate = 115200,
|
||||
.num_drives = 4,
|
||||
.fan_pwm_min = 51, /* Specified in original model.conf */
|
||||
.fan_pwm_max = 255,
|
||||
.usb_led = true,
|
||||
};
|
||||
|
||||
static struct mfd_cell qnap_mcu_cells[] = {
|
||||
{ .name = "qnap-mcu-input", },
|
||||
{ .name = "qnap-mcu-leds", },
|
||||
{ .name = "qnap-mcu-hwmon", }
|
||||
};
|
||||
|
||||
static int qnap_mcu_probe(struct serdev_device *serdev)
|
||||
{
|
||||
struct device *dev = &serdev->dev;
|
||||
struct qnap_mcu *mcu;
|
||||
int ret;
|
||||
|
||||
mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
|
||||
if (!mcu)
|
||||
return -ENOMEM;
|
||||
|
||||
mcu->serdev = serdev;
|
||||
dev_set_drvdata(dev, mcu);
|
||||
|
||||
mcu->variant = of_device_get_match_data(dev);
|
||||
if (!mcu->variant)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_init(&mcu->bus_lock);
|
||||
init_completion(&mcu->reply.done);
|
||||
|
||||
serdev_device_set_client_ops(serdev, &qnap_mcu_serdev_device_ops);
|
||||
ret = devm_serdev_device_open(dev, serdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
serdev_device_set_baudrate(serdev, mcu->variant->baud_rate);
|
||||
serdev_device_set_flow_control(serdev, false);
|
||||
|
||||
ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to set parity\n");
|
||||
|
||||
ret = qnap_mcu_get_version(mcu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_register_sys_off_handler(dev,
|
||||
SYS_OFF_MODE_POWER_OFF_PREPARE,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
&qnap_mcu_power_off, mcu);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to register poweroff handler\n");
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(qnap_mcu_cells); i++) {
|
||||
qnap_mcu_cells[i].platform_data = mcu->variant;
|
||||
qnap_mcu_cells[i].pdata_size = sizeof(*mcu->variant);
|
||||
}
|
||||
|
||||
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, qnap_mcu_cells,
|
||||
ARRAY_SIZE(qnap_mcu_cells), NULL, 0, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to add child devices\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qnap_mcu_dt_ids[] = {
|
||||
{ .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qnap_mcu_dt_ids);
|
||||
|
||||
static struct serdev_device_driver qnap_mcu_drv = {
|
||||
.probe = qnap_mcu_probe,
|
||||
.driver = {
|
||||
.name = "qnap-mcu",
|
||||
.of_match_table = qnap_mcu_dt_ids,
|
||||
},
|
||||
};
|
||||
module_serdev_device_driver(qnap_mcu_drv);
|
||||
|
||||
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
|
||||
MODULE_DESCRIPTION("QNAP MCU core driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -170,11 +170,7 @@ static int stpmic1_probe(struct i2c_client *i2c)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_register_sys_off_handler(ddata->dev,
|
||||
SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
stpmic1_power_off,
|
||||
ddata);
|
||||
ret = devm_register_power_off_handler(ddata->dev, stpmic1_power_off, ddata);
|
||||
if (ret) {
|
||||
dev_err(ddata->dev, "failed to register sys-off handler: %d\n", ret);
|
||||
return ret;
|
||||
|
|
|
@ -12,22 +12,16 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwspinlock.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_data/syscon.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static struct platform_driver syscon_driver;
|
||||
|
||||
static DEFINE_SPINLOCK(syscon_list_slock);
|
||||
static DEFINE_MUTEX(syscon_list_lock);
|
||||
static LIST_HEAD(syscon_list);
|
||||
|
||||
struct syscon {
|
||||
|
@ -54,6 +48,8 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res)
|
|||
struct resource res;
|
||||
struct reset_control *reset;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&syscon_list_lock));
|
||||
|
||||
struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL);
|
||||
if (!syscon)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
@ -146,9 +142,7 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res)
|
|||
syscon->regmap = regmap;
|
||||
syscon->np = np;
|
||||
|
||||
spin_lock(&syscon_list_slock);
|
||||
list_add_tail(&syscon->list, &syscon_list);
|
||||
spin_unlock(&syscon_list_slock);
|
||||
|
||||
return_ptr(syscon);
|
||||
|
||||
|
@ -169,7 +163,7 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
|
|||
{
|
||||
struct syscon *entry, *syscon = NULL;
|
||||
|
||||
spin_lock(&syscon_list_slock);
|
||||
mutex_lock(&syscon_list_lock);
|
||||
|
||||
list_for_each_entry(entry, &syscon_list, list)
|
||||
if (entry->np == np) {
|
||||
|
@ -177,10 +171,13 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
|
|||
break;
|
||||
}
|
||||
|
||||
spin_unlock(&syscon_list_slock);
|
||||
|
||||
if (!syscon)
|
||||
syscon = of_syscon_register(np, check_res);
|
||||
if (!syscon) {
|
||||
if (of_device_is_compatible(np, "syscon"))
|
||||
syscon = of_syscon_register(np, check_res);
|
||||
else
|
||||
syscon = ERR_PTR(-EINVAL);
|
||||
}
|
||||
mutex_unlock(&syscon_list_lock);
|
||||
|
||||
if (IS_ERR(syscon))
|
||||
return ERR_CAST(syscon);
|
||||
|
@ -212,7 +209,7 @@ int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
|
|||
return -ENOMEM;
|
||||
|
||||
/* check if syscon entry already exists */
|
||||
spin_lock(&syscon_list_slock);
|
||||
mutex_lock(&syscon_list_lock);
|
||||
|
||||
list_for_each_entry(entry, &syscon_list, list)
|
||||
if (entry->np == np) {
|
||||
|
@ -225,12 +222,12 @@ int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
|
|||
|
||||
/* register the regmap in syscon list */
|
||||
list_add_tail(&syscon->list, &syscon_list);
|
||||
spin_unlock(&syscon_list_slock);
|
||||
mutex_unlock(&syscon_list_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unlock:
|
||||
spin_unlock(&syscon_list_slock);
|
||||
mutex_unlock(&syscon_list_lock);
|
||||
kfree(syscon);
|
||||
return ret;
|
||||
}
|
||||
|
@ -244,9 +241,6 @@ EXPORT_SYMBOL_GPL(device_node_to_regmap);
|
|||
|
||||
struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||
{
|
||||
if (!of_device_is_compatible(np, "syscon"))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return device_node_get_regmap(np, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
|
||||
|
@ -336,62 +330,3 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
|
|||
return regmap;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional);
|
||||
|
||||
static int syscon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct syscon_platform_data *pdata = dev_get_platdata(dev);
|
||||
struct syscon *syscon;
|
||||
struct regmap_config syscon_config = syscon_regmap_config;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
|
||||
syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
|
||||
if (!syscon)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENOENT;
|
||||
|
||||
base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
syscon_config.max_register = resource_size(res) - 4;
|
||||
if (!syscon_config.max_register)
|
||||
syscon_config.max_register_is_0 = true;
|
||||
|
||||
if (pdata)
|
||||
syscon_config.name = pdata->label;
|
||||
syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config);
|
||||
if (IS_ERR(syscon->regmap)) {
|
||||
dev_err(dev, "regmap init failed\n");
|
||||
return PTR_ERR(syscon->regmap);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, syscon);
|
||||
|
||||
dev_dbg(dev, "regmap %pR registered\n", res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id syscon_ids[] = {
|
||||
{ "syscon", },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver syscon_driver = {
|
||||
.driver = {
|
||||
.name = "syscon",
|
||||
},
|
||||
.probe = syscon_probe,
|
||||
.id_table = syscon_ids,
|
||||
};
|
||||
|
||||
static int __init syscon_init(void)
|
||||
{
|
||||
return platform_driver_register(&syscon_driver);
|
||||
}
|
||||
postcore_initcall(syscon_init);
|
||||
|
|
|
@ -110,19 +110,12 @@ static const struct resource tps65219_regulator_resources[] = {
|
|||
};
|
||||
|
||||
static const struct mfd_cell tps65219_cells[] = {
|
||||
{
|
||||
.name = "tps65219-regulator",
|
||||
.resources = tps65219_regulator_resources,
|
||||
.num_resources = ARRAY_SIZE(tps65219_regulator_resources),
|
||||
},
|
||||
{ .name = "tps65219-gpio", },
|
||||
MFD_CELL_RES("tps65219-regulator", tps65219_regulator_resources),
|
||||
MFD_CELL_NAME("tps65219-gpio"),
|
||||
};
|
||||
|
||||
static const struct mfd_cell tps65219_pwrbutton_cell = {
|
||||
.name = "tps65219-pwrbutton",
|
||||
.resources = tps65219_pwrbutton_resources,
|
||||
.num_resources = ARRAY_SIZE(tps65219_pwrbutton_resources),
|
||||
};
|
||||
static const struct mfd_cell tps65219_pwrbutton_cell =
|
||||
MFD_CELL_RES("tps65219-pwrbutton", tps65219_pwrbutton_resources);
|
||||
|
||||
static const struct regmap_config tps65219_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
|
|
325
drivers/mfd/upboard-fpga.c
Normal file
325
drivers/mfd/upboard-fpga.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* UP Board FPGA driver.
|
||||
*
|
||||
* FPGA provides more GPIO driving power, LEDS and pin mux function.
|
||||
*
|
||||
* Copyright (c) AAEON. All rights reserved.
|
||||
* Copyright (C) 2024 Bootlin
|
||||
*
|
||||
* Author: Gary Wang <garywang@aaeon.com.tw>
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/upboard-fpga.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define UPBOARD_AAEON_MANUFACTURER_ID 0x01
|
||||
#define UPBOARD_MANUFACTURER_ID_MASK GENMASK(7, 0)
|
||||
|
||||
#define UPBOARD_ADDRESS_SIZE 7
|
||||
#define UPBOARD_REGISTER_SIZE 16
|
||||
|
||||
#define UPBOARD_READ_FLAG BIT(UPBOARD_ADDRESS_SIZE)
|
||||
|
||||
#define UPBOARD_FW_ID_MAJOR_SUPPORTED 0x0
|
||||
|
||||
#define UPBOARD_FW_ID_BUILD_MASK GENMASK(15, 12)
|
||||
#define UPBOARD_FW_ID_MAJOR_MASK GENMASK(11, 8)
|
||||
#define UPBOARD_FW_ID_MINOR_MASK GENMASK(7, 4)
|
||||
#define UPBOARD_FW_ID_PATCH_MASK GENMASK(3, 0)
|
||||
|
||||
static int upboard_fpga_read(void *context, unsigned int reg, unsigned int *val)
|
||||
{
|
||||
struct upboard_fpga *fpga = context;
|
||||
int i;
|
||||
|
||||
/* Clear to start new transaction */
|
||||
gpiod_set_value(fpga->clear_gpio, 0);
|
||||
gpiod_set_value(fpga->clear_gpio, 1);
|
||||
|
||||
reg |= UPBOARD_READ_FLAG;
|
||||
|
||||
/* Send clock and addr from strobe & datain pins */
|
||||
for (i = UPBOARD_ADDRESS_SIZE; i >= 0; i--) {
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
gpiod_set_value(fpga->datain_gpio, !!(reg & BIT(i)));
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
}
|
||||
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
*val = 0;
|
||||
|
||||
/* Read data from dataout pin */
|
||||
for (i = UPBOARD_REGISTER_SIZE - 1; i >= 0; i--) {
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
*val |= gpiod_get_value(fpga->dataout_gpio) << i;
|
||||
}
|
||||
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upboard_fpga_write(void *context, unsigned int reg, unsigned int val)
|
||||
{
|
||||
struct upboard_fpga *fpga = context;
|
||||
int i;
|
||||
|
||||
/* Clear to start new transcation */
|
||||
gpiod_set_value(fpga->clear_gpio, 0);
|
||||
gpiod_set_value(fpga->clear_gpio, 1);
|
||||
|
||||
/* Send clock and addr from strobe & datain pins */
|
||||
for (i = UPBOARD_ADDRESS_SIZE; i >= 0; i--) {
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
gpiod_set_value(fpga->datain_gpio, !!(reg & BIT(i)));
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
}
|
||||
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
|
||||
/* Write data to datain pin */
|
||||
for (i = UPBOARD_REGISTER_SIZE - 1; i >= 0; i--) {
|
||||
gpiod_set_value(fpga->datain_gpio, !!(val & BIT(i)));
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
gpiod_set_value(fpga->strobe_gpio, 0);
|
||||
}
|
||||
|
||||
gpiod_set_value(fpga->strobe_gpio, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_range upboard_up_readable_ranges[] = {
|
||||
regmap_reg_range(UPBOARD_REG_PLATFORM_ID, UPBOARD_REG_FIRMWARE_ID),
|
||||
regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN0),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN1),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR1),
|
||||
};
|
||||
|
||||
static const struct regmap_range upboard_up_writable_ranges[] = {
|
||||
regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN0),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN1),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR1),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table upboard_up_readable_table = {
|
||||
.yes_ranges = upboard_up_readable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(upboard_up_readable_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table upboard_up_writable_table = {
|
||||
.yes_ranges = upboard_up_writable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(upboard_up_writable_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config upboard_up_regmap_config = {
|
||||
.reg_bits = UPBOARD_ADDRESS_SIZE,
|
||||
.val_bits = UPBOARD_REGISTER_SIZE,
|
||||
.max_register = UPBOARD_REG_MAX,
|
||||
.reg_read = upboard_fpga_read,
|
||||
.reg_write = upboard_fpga_write,
|
||||
.fast_io = false,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
.rd_table = &upboard_up_readable_table,
|
||||
.wr_table = &upboard_up_writable_table,
|
||||
};
|
||||
|
||||
static const struct regmap_range upboard_up2_readable_ranges[] = {
|
||||
regmap_reg_range(UPBOARD_REG_PLATFORM_ID, UPBOARD_REG_FIRMWARE_ID),
|
||||
regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN1),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN2),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR2),
|
||||
};
|
||||
|
||||
static const struct regmap_range upboard_up2_writable_ranges[] = {
|
||||
regmap_reg_range(UPBOARD_REG_FUNC_EN0, UPBOARD_REG_FUNC_EN1),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_EN0, UPBOARD_REG_GPIO_EN2),
|
||||
regmap_reg_range(UPBOARD_REG_GPIO_DIR0, UPBOARD_REG_GPIO_DIR2),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table upboard_up2_readable_table = {
|
||||
.yes_ranges = upboard_up2_readable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(upboard_up2_readable_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table upboard_up2_writable_table = {
|
||||
.yes_ranges = upboard_up2_writable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(upboard_up2_writable_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config upboard_up2_regmap_config = {
|
||||
.reg_bits = UPBOARD_ADDRESS_SIZE,
|
||||
.val_bits = UPBOARD_REGISTER_SIZE,
|
||||
.max_register = UPBOARD_REG_MAX,
|
||||
.reg_read = upboard_fpga_read,
|
||||
.reg_write = upboard_fpga_write,
|
||||
.fast_io = false,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
.rd_table = &upboard_up2_readable_table,
|
||||
.wr_table = &upboard_up2_writable_table,
|
||||
};
|
||||
|
||||
static const struct mfd_cell upboard_up_mfd_cells[] = {
|
||||
{ .name = "upboard-pinctrl" },
|
||||
{ .name = "upboard-leds" },
|
||||
};
|
||||
|
||||
static const struct upboard_fpga_data upboard_up_fpga_data = {
|
||||
.type = UPBOARD_UP_FPGA,
|
||||
.regmap_config = &upboard_up_regmap_config,
|
||||
};
|
||||
|
||||
static const struct upboard_fpga_data upboard_up2_fpga_data = {
|
||||
.type = UPBOARD_UP2_FPGA,
|
||||
.regmap_config = &upboard_up2_regmap_config,
|
||||
};
|
||||
|
||||
static int upboard_fpga_gpio_init(struct upboard_fpga *fpga)
|
||||
{
|
||||
fpga->enable_gpio = devm_gpiod_get(fpga->dev, "enable", GPIOD_ASIS);
|
||||
if (IS_ERR(fpga->enable_gpio))
|
||||
return PTR_ERR(fpga->enable_gpio);
|
||||
|
||||
fpga->clear_gpio = devm_gpiod_get(fpga->dev, "clear", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(fpga->clear_gpio))
|
||||
return PTR_ERR(fpga->clear_gpio);
|
||||
|
||||
fpga->strobe_gpio = devm_gpiod_get(fpga->dev, "strobe", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(fpga->strobe_gpio))
|
||||
return PTR_ERR(fpga->strobe_gpio);
|
||||
|
||||
fpga->datain_gpio = devm_gpiod_get(fpga->dev, "datain", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(fpga->datain_gpio))
|
||||
return PTR_ERR(fpga->datain_gpio);
|
||||
|
||||
fpga->dataout_gpio = devm_gpiod_get(fpga->dev, "dataout", GPIOD_IN);
|
||||
if (IS_ERR(fpga->dataout_gpio))
|
||||
return PTR_ERR(fpga->dataout_gpio);
|
||||
|
||||
gpiod_set_value(fpga->enable_gpio, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upboard_fpga_get_firmware_version(struct upboard_fpga *fpga)
|
||||
{
|
||||
unsigned int platform_id, manufacturer_id;
|
||||
int ret;
|
||||
|
||||
if (!fpga)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = regmap_read(fpga->regmap, UPBOARD_REG_PLATFORM_ID, &platform_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
manufacturer_id = platform_id & UPBOARD_MANUFACTURER_ID_MASK;
|
||||
if (manufacturer_id != UPBOARD_AAEON_MANUFACTURER_ID)
|
||||
return dev_err_probe(fpga->dev, -ENODEV,
|
||||
"driver not compatible with custom FPGA FW from manufacturer id %#02x.",
|
||||
manufacturer_id);
|
||||
|
||||
ret = regmap_read(fpga->regmap, UPBOARD_REG_FIRMWARE_ID, &fpga->firmware_version);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version) !=
|
||||
UPBOARD_FW_ID_MAJOR_SUPPORTED)
|
||||
return dev_err_probe(fpga->dev, -ENODEV,
|
||||
"unsupported FPGA FW v%lu.%lu.%lu build %#02lx",
|
||||
FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_MINOR_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_PATCH_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_BUILD_MASK, fpga->firmware_version));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t upboard_fpga_version_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct upboard_fpga *fpga = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "FPGA FW v%lu.%lu.%lu build %#02lx\n",
|
||||
FIELD_GET(UPBOARD_FW_ID_MAJOR_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_MINOR_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_PATCH_MASK, fpga->firmware_version),
|
||||
FIELD_GET(UPBOARD_FW_ID_BUILD_MASK, fpga->firmware_version));
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(upboard_fpga_version);
|
||||
|
||||
static struct attribute *upboard_fpga_attrs[] = {
|
||||
&dev_attr_upboard_fpga_version.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(upboard_fpga);
|
||||
|
||||
static int upboard_fpga_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct upboard_fpga *fpga;
|
||||
int ret;
|
||||
|
||||
fpga = devm_kzalloc(dev, sizeof(*fpga), GFP_KERNEL);
|
||||
if (!fpga)
|
||||
return -ENOMEM;
|
||||
|
||||
fpga->fpga_data = device_get_match_data(dev);
|
||||
|
||||
fpga->dev = dev;
|
||||
|
||||
platform_set_drvdata(pdev, fpga);
|
||||
|
||||
fpga->regmap = devm_regmap_init(dev, NULL, fpga, fpga->fpga_data->regmap_config);
|
||||
if (IS_ERR(fpga->regmap))
|
||||
return PTR_ERR(fpga->regmap);
|
||||
|
||||
ret = upboard_fpga_gpio_init(fpga);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to initialize FPGA common GPIOs");
|
||||
|
||||
ret = upboard_fpga_get_firmware_version(fpga);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, upboard_up_mfd_cells,
|
||||
ARRAY_SIZE(upboard_up_mfd_cells), NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static const struct acpi_device_id upboard_fpga_acpi_match[] = {
|
||||
{ "AANT0F01", (kernel_ulong_t)&upboard_up2_fpga_data },
|
||||
{ "AANT0F04", (kernel_ulong_t)&upboard_up_fpga_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, upboard_fpga_acpi_match);
|
||||
|
||||
static struct platform_driver upboard_fpga_driver = {
|
||||
.driver = {
|
||||
.name = "upboard-fpga",
|
||||
.acpi_match_table = ACPI_PTR(upboard_fpga_acpi_match),
|
||||
.dev_groups = upboard_fpga_groups,
|
||||
},
|
||||
.probe = upboard_fpga_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(upboard_fpga_driver);
|
||||
|
||||
MODULE_AUTHOR("Gary Wang <garywang@aaeon.com.tw>");
|
||||
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||
MODULE_DESCRIPTION("UP Board FPGA driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -10,7 +10,6 @@
|
|||
#include <linux/mfd/core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_data/syscon.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
|
|
|
@ -72,7 +72,7 @@ struct mfd_cell {
|
|||
int (*resume)(struct platform_device *dev);
|
||||
|
||||
/* platform data passed to the sub devices drivers */
|
||||
void *platform_data;
|
||||
const void *platform_data;
|
||||
size_t pdata_size;
|
||||
|
||||
/* Matches ACPI */
|
||||
|
|
|
@ -93,6 +93,8 @@ struct da9052 {
|
|||
|
||||
int chip_irq;
|
||||
|
||||
int fault_log;
|
||||
|
||||
/* SOC I/O transfer related fixes for DA9052/53 */
|
||||
int (*fix_io) (struct da9052 *da9052, unsigned char reg);
|
||||
};
|
||||
|
|
26
include/linux/mfd/qnap-mcu.h
Normal file
26
include/linux/mfd/qnap-mcu.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Core definitions for QNAP MCU MFD driver.
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_QNAP_MCU_H_
|
||||
#define _LINUX_QNAP_MCU_H_
|
||||
|
||||
struct qnap_mcu;
|
||||
|
||||
struct qnap_mcu_variant {
|
||||
u32 baud_rate;
|
||||
int num_drives;
|
||||
int fan_pwm_min;
|
||||
int fan_pwm_max;
|
||||
bool usb_led;
|
||||
};
|
||||
|
||||
int qnap_mcu_exec(struct qnap_mcu *mcu,
|
||||
const u8 *cmd_data, size_t cmd_data_size,
|
||||
u8 *reply_data, size_t reply_data_size);
|
||||
int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu,
|
||||
const u8 *cmd_data, size_t cmd_data_size);
|
||||
|
||||
#endif /* _LINUX_QNAP_MCU_H_ */
|
|
@ -10,14 +10,9 @@
|
|||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
|
||||
struct regmap;
|
||||
struct regmap_irq_chip_data;
|
||||
|
||||
#define TPS65219_1V35 1350000
|
||||
#define TPS65219_1V8 1800000
|
||||
|
||||
/* TPS chip id list */
|
||||
#define TPS65219 0xF0
|
||||
|
||||
|
|
55
include/linux/mfd/upboard-fpga.h
Normal file
55
include/linux/mfd/upboard-fpga.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* UP Board CPLD/FPGA driver
|
||||
*
|
||||
* Copyright (c) AAEON. All rights reserved.
|
||||
* Copyright (C) 2024 Bootlin
|
||||
*
|
||||
* Author: Gary Wang <garywang@aaeon.com.tw>
|
||||
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_UPBOARD_FPGA_H
|
||||
#define __LINUX_MFD_UPBOARD_FPGA_H
|
||||
|
||||
#define UPBOARD_REGISTER_SIZE 16
|
||||
|
||||
enum upboard_fpgareg {
|
||||
UPBOARD_REG_PLATFORM_ID = 0x10,
|
||||
UPBOARD_REG_FIRMWARE_ID = 0x11,
|
||||
UPBOARD_REG_FUNC_EN0 = 0x20,
|
||||
UPBOARD_REG_FUNC_EN1 = 0x21,
|
||||
UPBOARD_REG_GPIO_EN0 = 0x30,
|
||||
UPBOARD_REG_GPIO_EN1 = 0x31,
|
||||
UPBOARD_REG_GPIO_EN2 = 0x32,
|
||||
UPBOARD_REG_GPIO_DIR0 = 0x40,
|
||||
UPBOARD_REG_GPIO_DIR1 = 0x41,
|
||||
UPBOARD_REG_GPIO_DIR2 = 0x42,
|
||||
UPBOARD_REG_MAX,
|
||||
};
|
||||
|
||||
enum upboard_fpga_type {
|
||||
UPBOARD_UP_FPGA,
|
||||
UPBOARD_UP2_FPGA,
|
||||
};
|
||||
|
||||
struct upboard_fpga_data {
|
||||
enum upboard_fpga_type type;
|
||||
const struct regmap_config *regmap_config;
|
||||
};
|
||||
|
||||
struct upboard_fpga {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct gpio_desc *enable_gpio;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct gpio_desc *clear_gpio;
|
||||
struct gpio_desc *strobe_gpio;
|
||||
struct gpio_desc *datain_gpio;
|
||||
struct gpio_desc *dataout_gpio;
|
||||
unsigned int firmware_version;
|
||||
const struct upboard_fpga_data *fpga_data;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_MFD_UPBOARD_FPGA_H */
|
|
@ -1,9 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef PLATFORM_DATA_SYSCON_H
|
||||
#define PLATFORM_DATA_SYSCON_H
|
||||
|
||||
struct syscon_platform_data {
|
||||
const char *label;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue