- 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:
Linus Torvalds 2025-01-22 09:16:02 -08:00
commit 909fd2b89f
49 changed files with 2407 additions and 254 deletions

View 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>;
};
};
};
...

View file

@ -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>;
};

View file

@ -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>;
};

View file

@ -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>;
};

View file

@ -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>;
};

View file

@ -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

View 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>;
};
};
};

View file

@ -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 {

View file

@ -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>;

View file

@ -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

View file

@ -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>;
};
};
};
...

View file

@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers
pxe1610
pwm-fan
q54sj108a2
qnap-mcu-hwmon
raspberrypi-hwmon
sbrmi
sbtsi_temp

View 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
=============== ======= =======================================================

View file

@ -28,4 +28,5 @@ LEDs
leds-mlxcpld
leds-mt6370-rgb
leds-sc27xx
leds-st1202.rst
leds-qcom-lpg

View 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.

View file

@ -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

View file

@ -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);
}

View file

@ -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)

View file

@ -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

View 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");

View file

@ -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

View file

@ -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

View 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");

View file

@ -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

View file

@ -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

View 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
View 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", &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");

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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);

View file

@ -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,
};

View file

@ -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);

View file

@ -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 */

View file

@ -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;
}

View file

@ -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,
};

View file

@ -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
View 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");

View file

@ -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;

View file

@ -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);

View file

@ -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
View 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");

View file

@ -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>

View file

@ -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 */

View file

@ -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);
};

View 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_ */

View file

@ -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

View 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 */

View file

@ -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