mvebu: puzzle-m902: add driver for MCU driving LEDs, fan and buzzer
Backport MFD driver for communicating with the on-board MCU found on IEI World Puzzle appliances. Improve the driver to support multiple LEDs, apply a default state and let MCU take care of blinking if timing is within supported range. Wire up LEDs and fan for Puzzle M902 in device tree. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
		| @@ -42,8 +42,10 @@ CONFIG_HOLES_IN_ZONE=y | |||||||
| CONFIG_HW_RANDOM_OMAP=y | CONFIG_HW_RANDOM_OMAP=y | ||||||
| CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 | CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 | ||||||
| CONFIG_LEDS_IS31FL319X=y | CONFIG_LEDS_IS31FL319X=y | ||||||
|  | CONFIG_LEDS_IEI_WT61P803_PUZZLE=y | ||||||
| CONFIG_MARVELL_10G_PHY=y | CONFIG_MARVELL_10G_PHY=y | ||||||
| CONFIG_MDIO_DEVRES=y | CONFIG_MDIO_DEVRES=y | ||||||
|  | CONFIG_MFD_IEI_WT61P803_PUZZLE=y | ||||||
| CONFIG_MFD_SYSCON=y | CONFIG_MFD_SYSCON=y | ||||||
| CONFIG_MMC_SDHCI_XENON=y | CONFIG_MMC_SDHCI_XENON=y | ||||||
| CONFIG_MODULES_USE_ELF_RELA=y | CONFIG_MODULES_USE_ELF_RELA=y | ||||||
| @@ -73,8 +75,12 @@ CONFIG_POWER_SUPPLY=y | |||||||
| CONFIG_QUEUED_RWLOCKS=y | CONFIG_QUEUED_RWLOCKS=y | ||||||
| CONFIG_QUEUED_SPINLOCKS=y | CONFIG_QUEUED_SPINLOCKS=y | ||||||
| CONFIG_RAS=y | CONFIG_RAS=y | ||||||
|  | # CONFIG_RAVE_SP_CORE is not set | ||||||
| CONFIG_REGULATOR_GPIO=y | CONFIG_REGULATOR_GPIO=y | ||||||
| # CONFIG_RODATA_FULL_DEFAULT_ENABLED is not set | # CONFIG_RODATA_FULL_DEFAULT_ENABLED is not set | ||||||
|  | CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON=y | ||||||
|  | CONFIG_SERIAL_DEV_BUS=y | ||||||
|  | CONFIG_SERIAL_DEV_CTRL_TTYPORT=y | ||||||
| CONFIG_SPARSEMEM=y | CONFIG_SPARSEMEM=y | ||||||
| CONFIG_SPARSEMEM_EXTREME=y | CONFIG_SPARSEMEM_EXTREME=y | ||||||
| CONFIG_SPARSEMEM_MANUAL=y | CONFIG_SPARSEMEM_MANUAL=y | ||||||
|   | |||||||
| @@ -38,7 +38,10 @@ | |||||||
| 		ethernet8 = &cp2_eth2; | 		ethernet8 = &cp2_eth2; | ||||||
| 		spi1 = &cp0_spi0; | 		spi1 = &cp0_spi0; | ||||||
| 		spi2 = &cp0_spi1; | 		spi2 = &cp0_spi1; | ||||||
| 		serial1 = &cp0_uart0; | 		led-boot = &led_power; | ||||||
|  | 		led-failsafe = &led_info; | ||||||
|  | 		led-running = &led_power; | ||||||
|  | 		led-upgrade = &led_info; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	memory@00000000 { | 	memory@00000000 { | ||||||
| @@ -91,6 +94,75 @@ | |||||||
|  |  | ||||||
| &cp0_uart0 { | &cp0_uart0 { | ||||||
| 	status = "okay"; | 	status = "okay"; | ||||||
|  |  | ||||||
|  | 	puzzle-mcu { | ||||||
|  | 		compatible = "iei,wt61p803-puzzle"; | ||||||
|  | 		#address-cells = <1>; | ||||||
|  | 		#size-cells = <1>; | ||||||
|  | 		current-speed = <115200>; | ||||||
|  | 		enable-beep; | ||||||
|  | 		status = "okay"; | ||||||
|  |  | ||||||
|  | 		leds { | ||||||
|  | 			compatible = "iei,wt61p803-puzzle-leds"; | ||||||
|  | 			#address-cells = <1>; | ||||||
|  | 			#size-cells = <0>; | ||||||
|  | 			status = "okay"; | ||||||
|  |  | ||||||
|  | 			led@0 { | ||||||
|  | 				reg = <0>; | ||||||
|  | 				label = "white:network"; | ||||||
|  | 				active-low; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			led@1 { | ||||||
|  | 				reg = <1>; | ||||||
|  | 				label = "green:cloud"; | ||||||
|  | 				active-low; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			led_info: led@2 { | ||||||
|  | 				reg = <2>; | ||||||
|  | 				label = "orange:info"; | ||||||
|  | 				active-low; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			led_power: led@3 { | ||||||
|  | 				reg = <3>; | ||||||
|  | 				label = "yellow:power"; | ||||||
|  | 				active-low; | ||||||
|  | 				default-state = "on"; | ||||||
|  | 			}; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		hwmon { | ||||||
|  | 			compatible = "iei,wt61p803-puzzle-hwmon"; | ||||||
|  | 			#address-cells = <1>; | ||||||
|  | 			#size-cells = <0>; | ||||||
|  |  | ||||||
|  | 			chassis_fan_group0: fan-group@0 { | ||||||
|  | 				#cooling-cells = <2>; | ||||||
|  | 				reg = <0x00>; | ||||||
|  | 				cooling-levels = <64 102 170 230 250>; | ||||||
|  | 			}; | ||||||
|  | 		}; | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | &ap_thermal_cpu1 { | ||||||
|  | 	trips { | ||||||
|  | 		cpu_active: cpu-active { | ||||||
|  | 			temperature = <44000>; | ||||||
|  | 			hysteresis = <2000>; | ||||||
|  | 			type = "active"; | ||||||
|  | 		}; | ||||||
|  | 	}; | ||||||
|  | 	cooling-maps { | ||||||
|  | 		fan-map { | ||||||
|  | 			trip = <&cpu_active>; | ||||||
|  | 			cooling-device = <&chassis_fan_group0 64 THERMAL_NO_LIMIT>; | ||||||
|  | 		}; | ||||||
|  | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /* on-board eMMC - U9 */ | /* on-board eMMC - U9 */ | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								target/linux/mvebu/files/include/linux/mfd/puzzle.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								target/linux/mvebu/files/include/linux/mfd/puzzle.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | /* SPDX-License-Identifier: GPL-2.0+ */ | ||||||
|  |  | ||||||
|  | #ifndef _LINUX_PUZZLE_H_ | ||||||
|  | #define _LINUX_PUZZLE_H_ | ||||||
|  |  | ||||||
|  | struct puzzle; | ||||||
|  | int puzzle_led(struct puzzle *pz, u8 ledn, u8 ledmode); | ||||||
|  | int puzzle_fan(struct puzzle *pz, u8 speed); | ||||||
|  | int puzzle_buzzer(struct puzzle *pz, u8 len); | ||||||
|  |  | ||||||
|  | #endif /* _LINUX_PUZZLE_H_ */ | ||||||
| @@ -0,0 +1,218 @@ | |||||||
|  | From aa4a0ccc41997f2da172165c92803abace43bd1c Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:32 +0000 | ||||||
|  | Subject: [PATCH 1/7] dt-bindings: Add IEI vendor prefix and IEI WT61P803 | ||||||
|  |  PUZZLE driver bindings | ||||||
|  |  | ||||||
|  | Add the IEI WT61P803 PUZZLE Device Tree bindings for MFD, HWMON and LED | ||||||
|  | drivers. A new vendor prefix is also added accordingly for | ||||||
|  | IEI Integration Corp. | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  .../hwmon/iei,wt61p803-puzzle-hwmon.yaml      | 53 ++++++++++++ | ||||||
|  |  .../leds/iei,wt61p803-puzzle-leds.yaml        | 39 +++++++++ | ||||||
|  |  .../bindings/mfd/iei,wt61p803-puzzle.yaml     | 82 +++++++++++++++++++ | ||||||
|  |  .../devicetree/bindings/vendor-prefixes.yaml  |  2 + | ||||||
|  |  4 files changed, 176 insertions(+) | ||||||
|  |  create mode 100644 Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml | ||||||
|  |  create mode 100644 Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml | ||||||
|  |  create mode 100644 Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml | ||||||
|  |  | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml | ||||||
|  | @@ -0,0 +1,53 @@ | ||||||
|  | +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause | ||||||
|  | +%YAML 1.2 | ||||||
|  | +--- | ||||||
|  | +$id: http://devicetree.org/schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml# | ||||||
|  | +$schema: http://devicetree.org/meta-schemas/core.yaml# | ||||||
|  | + | ||||||
|  | +title: IEI WT61P803 PUZZLE MCU HWMON module from IEI Integration Corp. | ||||||
|  | + | ||||||
|  | +maintainers: | ||||||
|  | +  - Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + | ||||||
|  | +description: | | ||||||
|  | +  This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details | ||||||
|  | +  see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml. | ||||||
|  | + | ||||||
|  | +  The HWMON module is a sub-node of the MCU node in the Device Tree. | ||||||
|  | + | ||||||
|  | +properties: | ||||||
|  | +  compatible: | ||||||
|  | +    const: iei,wt61p803-puzzle-hwmon | ||||||
|  | + | ||||||
|  | +  "#address-cells": | ||||||
|  | +    const: 1 | ||||||
|  | + | ||||||
|  | +  "#size-cells": | ||||||
|  | +    const: 0 | ||||||
|  | + | ||||||
|  | +patternProperties: | ||||||
|  | +  "^fan-group@[0-1]$": | ||||||
|  | +    type: object | ||||||
|  | +    properties: | ||||||
|  | +      reg: | ||||||
|  | +        minimum: 0 | ||||||
|  | +        maximum: 1 | ||||||
|  | +        description: | ||||||
|  | +          Fan group ID | ||||||
|  | + | ||||||
|  | +      cooling-levels: | ||||||
|  | +        minItems: 1 | ||||||
|  | +        maxItems: 255 | ||||||
|  | +        description: | ||||||
|  | +          Cooling levels for the fans (PWM value mapping) | ||||||
|  | +    description: | | ||||||
|  | +      Properties for each fan group. | ||||||
|  | +    required: | ||||||
|  | +      - reg | ||||||
|  | + | ||||||
|  | +required: | ||||||
|  | +  - compatible | ||||||
|  | +  - "#address-cells" | ||||||
|  | +  - "#size-cells" | ||||||
|  | + | ||||||
|  | +additionalProperties: false | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml | ||||||
|  | @@ -0,0 +1,39 @@ | ||||||
|  | +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause | ||||||
|  | +%YAML 1.2 | ||||||
|  | +--- | ||||||
|  | +$id: http://devicetree.org/schemas/leds/iei,wt61p803-puzzle-leds.yaml# | ||||||
|  | +$schema: http://devicetree.org/meta-schemas/core.yaml# | ||||||
|  | + | ||||||
|  | +title: IEI WT61P803 PUZZLE MCU LED module from IEI Integration Corp. | ||||||
|  | + | ||||||
|  | +maintainers: | ||||||
|  | +  - Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + | ||||||
|  | +description: | | ||||||
|  | +  This module is a part of the IEI WT61P803 PUZZLE MFD device. For more details | ||||||
|  | +  see Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml. | ||||||
|  | + | ||||||
|  | +  The LED module is a sub-node of the MCU node in the Device Tree. | ||||||
|  | + | ||||||
|  | +properties: | ||||||
|  | +  compatible: | ||||||
|  | +    const: iei,wt61p803-puzzle-leds | ||||||
|  | + | ||||||
|  | +  "#address-cells": | ||||||
|  | +    const: 1 | ||||||
|  | + | ||||||
|  | +  "#size-cells": | ||||||
|  | +    const: 0 | ||||||
|  | + | ||||||
|  | +  led@0: | ||||||
|  | +    type: object | ||||||
|  | +    $ref: common.yaml | ||||||
|  | +    description: | | ||||||
|  | +      Properties for a single LED. | ||||||
|  | + | ||||||
|  | +required: | ||||||
|  | +  - compatible | ||||||
|  | +  - "#address-cells" | ||||||
|  | +  - "#size-cells" | ||||||
|  | + | ||||||
|  | +additionalProperties: false | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml | ||||||
|  | @@ -0,0 +1,82 @@ | ||||||
|  | +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause | ||||||
|  | +%YAML 1.2 | ||||||
|  | +--- | ||||||
|  | +$id: http://devicetree.org/schemas/mfd/iei,wt61p803-puzzle.yaml# | ||||||
|  | +$schema: http://devicetree.org/meta-schemas/core.yaml# | ||||||
|  | + | ||||||
|  | +title: IEI WT61P803 PUZZLE MCU from IEI Integration Corp. | ||||||
|  | + | ||||||
|  | +maintainers: | ||||||
|  | +  - Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + | ||||||
|  | +description: | | ||||||
|  | +  IEI WT61P803 PUZZLE MCU is embedded in some IEI Puzzle series boards. | ||||||
|  | +  It's used for controlling system power states, fans, LEDs and temperature | ||||||
|  | +  sensors. | ||||||
|  | + | ||||||
|  | +  For Device Tree bindings of other sub-modules (HWMON, LEDs) refer to the | ||||||
|  | +  binding documents under the respective subsystem directories. | ||||||
|  | + | ||||||
|  | +properties: | ||||||
|  | +  compatible: | ||||||
|  | +    const: iei,wt61p803-puzzle | ||||||
|  | + | ||||||
|  | +  current-speed: | ||||||
|  | +    description: | ||||||
|  | +      Serial bus speed in bps | ||||||
|  | +    maxItems: 1 | ||||||
|  | + | ||||||
|  | +  enable-beep: true | ||||||
|  | + | ||||||
|  | +  hwmon: | ||||||
|  | +    $ref: /schemas/hwmon/iei,wt61p803-puzzle-hwmon.yaml | ||||||
|  | + | ||||||
|  | +  leds: | ||||||
|  | +    $ref: /schemas/leds/iei,wt61p803-puzzle-leds.yaml | ||||||
|  | + | ||||||
|  | +required: | ||||||
|  | +  - compatible | ||||||
|  | +  - current-speed | ||||||
|  | + | ||||||
|  | +additionalProperties: false | ||||||
|  | + | ||||||
|  | +examples: | ||||||
|  | +  - | | ||||||
|  | +    #include <dt-bindings/leds/common.h> | ||||||
|  | +    serial { | ||||||
|  | +        mcu { | ||||||
|  | +            compatible = "iei,wt61p803-puzzle"; | ||||||
|  | +            current-speed = <115200>; | ||||||
|  | +            enable-beep; | ||||||
|  | + | ||||||
|  | +            leds { | ||||||
|  | +                compatible = "iei,wt61p803-puzzle-leds"; | ||||||
|  | +                #address-cells = <1>; | ||||||
|  | +                #size-cells = <0>; | ||||||
|  | + | ||||||
|  | +                led@0 { | ||||||
|  | +                    reg = <0>; | ||||||
|  | +                    function = LED_FUNCTION_POWER; | ||||||
|  | +                    color = <LED_COLOR_ID_BLUE>; | ||||||
|  | +                }; | ||||||
|  | +            }; | ||||||
|  | + | ||||||
|  | +            hwmon { | ||||||
|  | +                compatible = "iei,wt61p803-puzzle-hwmon"; | ||||||
|  | +                #address-cells = <1>; | ||||||
|  | +                #size-cells = <0>; | ||||||
|  | + | ||||||
|  | +                fan-group@0 { | ||||||
|  | +                    #cooling-cells = <2>; | ||||||
|  | +                    reg = <0x00>; | ||||||
|  | +                    cooling-levels = <64 102 170 230 250>; | ||||||
|  | +                }; | ||||||
|  | + | ||||||
|  | +                fan-group@1 { | ||||||
|  | +                    #cooling-cells = <2>; | ||||||
|  | +                    reg = <0x01>; | ||||||
|  | +                    cooling-levels = <64 102 170 230 250>; | ||||||
|  | +                }; | ||||||
|  | +            }; | ||||||
|  | +        }; | ||||||
|  | +    }; | ||||||
|  | --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml | ||||||
|  | +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml | ||||||
|  | @@ -475,6 +475,8 @@ patternProperties: | ||||||
|  |      description: IC Plus Corp. | ||||||
|  |    "^idt,.*": | ||||||
|  |      description: Integrated Device Technologies, Inc. | ||||||
|  | +  "^iei,.*": | ||||||
|  | +    description: IEI Integration Corp. | ||||||
|  |    "^ifi,.*": | ||||||
|  |      description: Ingenieurburo Fur Ic-Technologie (I/F/I) | ||||||
|  |    "^ilitek,.*": | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,469 @@ | |||||||
|  | From e3310a638cd310bfd93dbbc6d2732ab6aea18dd2 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:34 +0000 | ||||||
|  | Subject: [PATCH 3/7] drivers: hwmon: Add the IEI WT61P803 PUZZLE HWMON driver | ||||||
|  |  | ||||||
|  | Add the IEI WT61P803 PUZZLE HWMON driver, that handles the fan speed | ||||||
|  | control via PWM, reading fan speed and reading on-board temperature | ||||||
|  | sensors. | ||||||
|  |  | ||||||
|  | The driver registers a HWMON device and a simple thermal cooling device to | ||||||
|  | enable in-kernel fan management. | ||||||
|  |  | ||||||
|  | This driver depends on the IEI WT61P803 PUZZLE MFD driver. | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Acked-by: Guenter Roeck <linux@roeck-us.net> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  drivers/hwmon/Kconfig                     |   8 + | ||||||
|  |  drivers/hwmon/Makefile                    |   1 + | ||||||
|  |  drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | 413 ++++++++++++++++++++++ | ||||||
|  |  3 files changed, 422 insertions(+) | ||||||
|  |  create mode 100644 drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | ||||||
|  |  | ||||||
|  | --- a/drivers/hwmon/Kconfig | ||||||
|  | +++ b/drivers/hwmon/Kconfig | ||||||
|  | @@ -722,6 +722,14 @@ config SENSORS_IBMPOWERNV | ||||||
|  |  	  This driver can also be built as a module. If so, the module | ||||||
|  |  	  will be called ibmpowernv. | ||||||
|  |   | ||||||
|  | +config SENSORS_IEI_WT61P803_PUZZLE_HWMON | ||||||
|  | +	tristate "IEI WT61P803 PUZZLE MFD HWMON Driver" | ||||||
|  | +	depends on MFD_IEI_WT61P803_PUZZLE | ||||||
|  | +	help | ||||||
|  | +	  The IEI WT61P803 PUZZLE MFD HWMON Driver handles reading fan speed | ||||||
|  | +	  and writing fan PWM values. It also supports reading on-board | ||||||
|  | +	  temperature sensors. | ||||||
|  | + | ||||||
|  |  config SENSORS_IIO_HWMON | ||||||
|  |  	tristate "Hwmon driver that uses channels specified via iio maps" | ||||||
|  |  	depends on IIO | ||||||
|  | --- a/drivers/hwmon/Makefile | ||||||
|  | +++ b/drivers/hwmon/Makefile | ||||||
|  | @@ -83,6 +83,7 @@ obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130 | ||||||
|  |  obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o | ||||||
|  |  obj-$(CONFIG_SENSORS_I5500)	+= i5500_temp.o | ||||||
|  |  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o | ||||||
|  | +obj-$(CONFIG_SENSORS_IEI_WT61P803_PUZZLE_HWMON) += iei-wt61p803-puzzle-hwmon.o | ||||||
|  |  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o | ||||||
|  |  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o | ||||||
|  |  obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | ||||||
|  | @@ -0,0 +1,413 @@ | ||||||
|  | +// SPDX-License-Identifier: GPL-2.0-only | ||||||
|  | +/* IEI WT61P803 PUZZLE MCU HWMON Driver | ||||||
|  | + * | ||||||
|  | + * Copyright (C) 2020 Sartura Ltd. | ||||||
|  | + * Author: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + */ | ||||||
|  | + | ||||||
|  | +#include <linux/err.h> | ||||||
|  | +#include <linux/hwmon.h> | ||||||
|  | +#include <linux/interrupt.h> | ||||||
|  | +#include <linux/irq.h> | ||||||
|  | +#include <linux/math64.h> | ||||||
|  | +#include <linux/mfd/iei-wt61p803-puzzle.h> | ||||||
|  | +#include <linux/mod_devicetable.h> | ||||||
|  | +#include <linux/module.h> | ||||||
|  | +#include <linux/platform_device.h> | ||||||
|  | +#include <linux/property.h> | ||||||
|  | +#include <linux/slab.h> | ||||||
|  | +#include <linux/thermal.h> | ||||||
|  | + | ||||||
|  | +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM	2 | ||||||
|  | +#define IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL	255 | ||||||
|  | + | ||||||
|  | +/** | ||||||
|  | + * struct iei_wt61p803_puzzle_thermal_cooling_device - Thermal cooling device instance | ||||||
|  | + * @mcu_hwmon:		Parent driver struct pointer | ||||||
|  | + * @tcdev:		Thermal cooling device pointer | ||||||
|  | + * @name:		Thermal cooling device name | ||||||
|  | + * @pwm_channel:	Controlled PWM channel (0 or 1) | ||||||
|  | + * @cooling_levels:	Thermal cooling device cooling levels (DT) | ||||||
|  | + */ | ||||||
|  | +struct iei_wt61p803_puzzle_thermal_cooling_device { | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon; | ||||||
|  | +	struct thermal_cooling_device *tcdev; | ||||||
|  | +	char name[THERMAL_NAME_LENGTH]; | ||||||
|  | +	int pwm_channel; | ||||||
|  | +	u8 *cooling_levels; | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +/** | ||||||
|  | + * struct iei_wt61p803_puzzle_hwmon - MCU HWMON Driver | ||||||
|  | + * @mcu:				MCU struct pointer | ||||||
|  | + * @response_buffer			Global MCU response buffer | ||||||
|  | + * @thermal_cooling_dev_present:	Per-channel thermal cooling device control indicator | ||||||
|  | + * @cdev:				Per-channel thermal cooling device private structure | ||||||
|  | + */ | ||||||
|  | +struct iei_wt61p803_puzzle_hwmon { | ||||||
|  | +	struct iei_wt61p803_puzzle *mcu; | ||||||
|  | +	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE]; | ||||||
|  | +	bool thermal_cooling_dev_present[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM]; | ||||||
|  | +	struct iei_wt61p803_puzzle_thermal_cooling_device | ||||||
|  | +		*cdev[IEI_WT61P803_PUZZLE_HWMON_MAX_PWM]; | ||||||
|  | +	struct mutex lock; /* mutex to protect response_buffer array */ | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +#define raw_temp_to_milidegree_celsius(x) (((x) - 0x80) * 1000) | ||||||
|  | +static int iei_wt61p803_puzzle_read_temp_sensor(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, | ||||||
|  | +						int channel, long *value) | ||||||
|  | +{ | ||||||
|  | +	unsigned char *resp_buf = mcu_hwmon->response_buffer; | ||||||
|  | +	unsigned char temp_sensor_ntc_cmd[4] = { | ||||||
|  | +		IEI_WT61P803_PUZZLE_CMD_HEADER_START, | ||||||
|  | +		IEI_WT61P803_PUZZLE_CMD_TEMP, | ||||||
|  | +		IEI_WT61P803_PUZZLE_CMD_TEMP_ALL, | ||||||
|  | +	}; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	mutex_lock(&mcu_hwmon->lock); | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, temp_sensor_ntc_cmd, | ||||||
|  | +						sizeof(temp_sensor_ntc_cmd), resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | +	if (ret) | ||||||
|  | +		goto exit; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 7) { | ||||||
|  | +		ret = -EIO; | ||||||
|  | +		goto exit; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	/* Check the number of NTC values */ | ||||||
|  | +	if (resp_buf[3] != '2') { | ||||||
|  | +		ret = -EIO; | ||||||
|  | +		goto exit; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	*value = raw_temp_to_milidegree_celsius(resp_buf[4 + channel]); | ||||||
|  | +exit: | ||||||
|  | +	mutex_unlock(&mcu_hwmon->lock); | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +#define raw_fan_val_to_rpm(x, y) ((((x) << 8 | (y)) / 2) * 60) | ||||||
|  | +static int iei_wt61p803_puzzle_read_fan_speed(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, | ||||||
|  | +					      int channel, long *value) | ||||||
|  | +{ | ||||||
|  | +	unsigned char *resp_buf = mcu_hwmon->response_buffer; | ||||||
|  | +	unsigned char fan_speed_cmd[4] = {}; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	fan_speed_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  | +	fan_speed_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN; | ||||||
|  | +	fan_speed_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_RPM(channel); | ||||||
|  | + | ||||||
|  | +	mutex_lock(&mcu_hwmon->lock); | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, fan_speed_cmd, | ||||||
|  | +						sizeof(fan_speed_cmd), resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | +	if (ret) | ||||||
|  | +		goto exit; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 7) { | ||||||
|  | +		ret = -EIO; | ||||||
|  | +		goto exit; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	*value = raw_fan_val_to_rpm(resp_buf[3], resp_buf[4]); | ||||||
|  | +exit: | ||||||
|  | +	mutex_unlock(&mcu_hwmon->lock); | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_write_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, | ||||||
|  | +						 int channel, long pwm_set_val) | ||||||
|  | +{ | ||||||
|  | +	unsigned char *resp_buf = mcu_hwmon->response_buffer; | ||||||
|  | +	unsigned char pwm_set_cmd[6] = {}; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	pwm_set_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  | +	pwm_set_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN; | ||||||
|  | +	pwm_set_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE; | ||||||
|  | +	pwm_set_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel); | ||||||
|  | +	pwm_set_cmd[4] = pwm_set_val; | ||||||
|  | + | ||||||
|  | +	mutex_lock(&mcu_hwmon->lock); | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_set_cmd, | ||||||
|  | +						sizeof(pwm_set_cmd), resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | +	if (ret) | ||||||
|  | +		goto exit; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 3) { | ||||||
|  | +		ret = -EIO; | ||||||
|  | +		goto exit; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START && | ||||||
|  | +	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK && | ||||||
|  | +	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) { | ||||||
|  | +		ret = -EIO; | ||||||
|  | +		goto exit; | ||||||
|  | +	} | ||||||
|  | +exit: | ||||||
|  | +	mutex_unlock(&mcu_hwmon->lock); | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_read_pwm_channel(struct iei_wt61p803_puzzle_hwmon *mcu_hwmon, | ||||||
|  | +						int channel, long *value) | ||||||
|  | +{ | ||||||
|  | +	unsigned char *resp_buf = mcu_hwmon->response_buffer; | ||||||
|  | +	unsigned char pwm_get_cmd[5] = {}; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	pwm_get_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  | +	pwm_get_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FAN; | ||||||
|  | +	pwm_get_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ; | ||||||
|  | +	pwm_get_cmd[3] = IEI_WT61P803_PUZZLE_CMD_FAN_PWM(channel); | ||||||
|  | + | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(mcu_hwmon->mcu, pwm_get_cmd, | ||||||
|  | +						sizeof(pwm_get_cmd), resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 5) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	if (resp_buf[2] != IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	*value = resp_buf[3]; | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_read(struct device *dev, enum hwmon_sensor_types type, | ||||||
|  | +				    u32 attr, int channel, long *val) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent); | ||||||
|  | + | ||||||
|  | +	switch (type) { | ||||||
|  | +	case hwmon_pwm: | ||||||
|  | +		return iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, channel, val); | ||||||
|  | +	case hwmon_fan: | ||||||
|  | +		return iei_wt61p803_puzzle_read_fan_speed(mcu_hwmon, channel, val); | ||||||
|  | +	case hwmon_temp: | ||||||
|  | +		return iei_wt61p803_puzzle_read_temp_sensor(mcu_hwmon, channel, val); | ||||||
|  | +	default: | ||||||
|  | +		return -EINVAL; | ||||||
|  | +	} | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_write(struct device *dev, enum hwmon_sensor_types type, | ||||||
|  | +				     u32 attr, int channel, long val) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = dev_get_drvdata(dev->parent); | ||||||
|  | + | ||||||
|  | +	return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, channel, val); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static umode_t iei_wt61p803_puzzle_is_visible(const void *data, enum hwmon_sensor_types type, | ||||||
|  | +					      u32 attr, int channel) | ||||||
|  | +{ | ||||||
|  | +	const struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = data; | ||||||
|  | + | ||||||
|  | +	switch (type) { | ||||||
|  | +	case hwmon_pwm: | ||||||
|  | +		if (mcu_hwmon->thermal_cooling_dev_present[channel]) | ||||||
|  | +			return 0444; | ||||||
|  | +		if (attr == hwmon_pwm_input) | ||||||
|  | +			return 0644; | ||||||
|  | +		break; | ||||||
|  | +	case hwmon_fan: | ||||||
|  | +		if (attr == hwmon_fan_input) | ||||||
|  | +			return 0444; | ||||||
|  | +		break; | ||||||
|  | +	case hwmon_temp: | ||||||
|  | +		if (attr == hwmon_temp_input) | ||||||
|  | +			return 0444; | ||||||
|  | +		break; | ||||||
|  | +	default: | ||||||
|  | +		return 0; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static const struct hwmon_ops iei_wt61p803_puzzle_hwmon_ops = { | ||||||
|  | +	.is_visible = iei_wt61p803_puzzle_is_visible, | ||||||
|  | +	.read = iei_wt61p803_puzzle_read, | ||||||
|  | +	.write = iei_wt61p803_puzzle_write, | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +static const struct hwmon_channel_info *iei_wt61p803_puzzle_info[] = { | ||||||
|  | +	HWMON_CHANNEL_INFO(pwm, | ||||||
|  | +			   HWMON_PWM_INPUT, | ||||||
|  | +			   HWMON_PWM_INPUT), | ||||||
|  | +	HWMON_CHANNEL_INFO(fan, | ||||||
|  | +			   HWMON_F_INPUT, | ||||||
|  | +			   HWMON_F_INPUT, | ||||||
|  | +			   HWMON_F_INPUT, | ||||||
|  | +			   HWMON_F_INPUT, | ||||||
|  | +			   HWMON_F_INPUT), | ||||||
|  | +	HWMON_CHANNEL_INFO(temp, | ||||||
|  | +			   HWMON_T_INPUT, | ||||||
|  | +			   HWMON_T_INPUT), | ||||||
|  | +	NULL | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +static const struct hwmon_chip_info iei_wt61p803_puzzle_chip_info = { | ||||||
|  | +	.ops = &iei_wt61p803_puzzle_hwmon_ops, | ||||||
|  | +	.info = iei_wt61p803_puzzle_info, | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_get_max_state(struct thermal_cooling_device *tcdev, | ||||||
|  | +					     unsigned long *state) | ||||||
|  | +{ | ||||||
|  | +	*state = IEI_WT61P803_PUZZLE_HWMON_MAX_PWM_VAL; | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_get_cur_state(struct thermal_cooling_device *tcdev, | ||||||
|  | +					     unsigned long *state) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata; | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon; | ||||||
|  | +	long value; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	ret = iei_wt61p803_puzzle_read_pwm_channel(mcu_hwmon, cdev->pwm_channel, &value); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | +	*state = value; | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_set_cur_state(struct thermal_cooling_device *tcdev, | ||||||
|  | +					     unsigned long state) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev = tcdev->devdata; | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon = cdev->mcu_hwmon; | ||||||
|  | + | ||||||
|  | +	return iei_wt61p803_puzzle_write_pwm_channel(mcu_hwmon, cdev->pwm_channel, state); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static const struct thermal_cooling_device_ops iei_wt61p803_puzzle_cooling_ops = { | ||||||
|  | +	.get_max_state = iei_wt61p803_puzzle_get_max_state, | ||||||
|  | +	.get_cur_state = iei_wt61p803_puzzle_get_cur_state, | ||||||
|  | +	.set_cur_state = iei_wt61p803_puzzle_set_cur_state, | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +static int | ||||||
|  | +iei_wt61p803_puzzle_enable_thermal_cooling_dev(struct device *dev, | ||||||
|  | +					       struct fwnode_handle *child, | ||||||
|  | +					       struct iei_wt61p803_puzzle_hwmon *mcu_hwmon) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_thermal_cooling_device *cdev; | ||||||
|  | +	u32 pwm_channel; | ||||||
|  | +	u8 num_levels; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	ret = fwnode_property_read_u32(child, "reg", &pwm_channel); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	mcu_hwmon->thermal_cooling_dev_present[pwm_channel] = true; | ||||||
|  | + | ||||||
|  | +	num_levels = fwnode_property_count_u8(child, "cooling-levels"); | ||||||
|  | +	if (!num_levels) | ||||||
|  | +		return -EINVAL; | ||||||
|  | + | ||||||
|  | +	cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL); | ||||||
|  | +	if (!cdev) | ||||||
|  | +		return -ENOMEM; | ||||||
|  | + | ||||||
|  | +	cdev->cooling_levels = devm_kmalloc_array(dev, num_levels, sizeof(u8), GFP_KERNEL); | ||||||
|  | +	if (!cdev->cooling_levels) | ||||||
|  | +		return -ENOMEM; | ||||||
|  | + | ||||||
|  | +	ret = fwnode_property_read_u8_array(child, "cooling-levels", | ||||||
|  | +					    cdev->cooling_levels, | ||||||
|  | +					    num_levels); | ||||||
|  | +	if (ret) { | ||||||
|  | +		dev_err(dev, "Couldn't read property 'cooling-levels'\n"); | ||||||
|  | +		return ret; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	snprintf(cdev->name, THERMAL_NAME_LENGTH, "wt61p803_puzzle_%d", pwm_channel); | ||||||
|  | +	cdev->tcdev = devm_thermal_of_cooling_device_register(dev, NULL, cdev->name, cdev, | ||||||
|  | +							      &iei_wt61p803_puzzle_cooling_ops); | ||||||
|  | +	if (IS_ERR(cdev->tcdev)) | ||||||
|  | +		return PTR_ERR(cdev->tcdev); | ||||||
|  | + | ||||||
|  | +	cdev->mcu_hwmon = mcu_hwmon; | ||||||
|  | +	cdev->pwm_channel = pwm_channel; | ||||||
|  | +	mcu_hwmon->cdev[pwm_channel] = cdev; | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_hwmon_probe(struct platform_device *pdev) | ||||||
|  | +{ | ||||||
|  | +	struct device *dev = &pdev->dev; | ||||||
|  | +	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent); | ||||||
|  | +	struct iei_wt61p803_puzzle_hwmon *mcu_hwmon; | ||||||
|  | +	struct fwnode_handle *child; | ||||||
|  | +	struct device *hwmon_dev; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	mcu_hwmon = devm_kzalloc(dev, sizeof(*mcu_hwmon), GFP_KERNEL); | ||||||
|  | +	if (!mcu_hwmon) | ||||||
|  | +		return -ENOMEM; | ||||||
|  | + | ||||||
|  | +	mcu_hwmon->mcu = mcu; | ||||||
|  | +	platform_set_drvdata(pdev, mcu_hwmon); | ||||||
|  | +	mutex_init(&mcu_hwmon->lock); | ||||||
|  | + | ||||||
|  | +	hwmon_dev = devm_hwmon_device_register_with_info(dev, "iei_wt61p803_puzzle", | ||||||
|  | +							 mcu_hwmon, | ||||||
|  | +							 &iei_wt61p803_puzzle_chip_info, | ||||||
|  | +							 NULL); | ||||||
|  | +	if (IS_ERR(hwmon_dev)) | ||||||
|  | +		return PTR_ERR(hwmon_dev); | ||||||
|  | + | ||||||
|  | +	/* Control fans via PWM lines via Linux Kernel */ | ||||||
|  | +	if (IS_ENABLED(CONFIG_THERMAL)) { | ||||||
|  | +		device_for_each_child_node(dev, child) { | ||||||
|  | +			ret = iei_wt61p803_puzzle_enable_thermal_cooling_dev(dev, child, mcu_hwmon); | ||||||
|  | +			if (ret) { | ||||||
|  | +				dev_err(dev, "Enabling the PWM fan failed\n"); | ||||||
|  | +				fwnode_handle_put(child); | ||||||
|  | +				return ret; | ||||||
|  | +			} | ||||||
|  | +		} | ||||||
|  | +	} | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static const struct of_device_id iei_wt61p803_puzzle_hwmon_id_table[] = { | ||||||
|  | +	{ .compatible = "iei,wt61p803-puzzle-hwmon" }, | ||||||
|  | +	{} | ||||||
|  | +}; | ||||||
|  | +MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_hwmon_id_table); | ||||||
|  | + | ||||||
|  | +static struct platform_driver iei_wt61p803_puzzle_hwmon_driver = { | ||||||
|  | +	.driver = { | ||||||
|  | +		.name = "iei-wt61p803-puzzle-hwmon", | ||||||
|  | +		.of_match_table = iei_wt61p803_puzzle_hwmon_id_table, | ||||||
|  | +	}, | ||||||
|  | +	.probe = iei_wt61p803_puzzle_hwmon_probe, | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +module_platform_driver(iei_wt61p803_puzzle_hwmon_driver); | ||||||
|  | + | ||||||
|  | +MODULE_DESCRIPTION("IEI WT61P803 PUZZLE MCU HWMON Driver"); | ||||||
|  | +MODULE_AUTHOR("Luka Kovacic <luka.kovacic@sartura.hr>"); | ||||||
|  | +MODULE_LICENSE("GPL v2"); | ||||||
| @@ -0,0 +1,207 @@ | |||||||
|  | From f3b44eb69cc561cf05d00506dcec0dd9be003ed8 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:35 +0000 | ||||||
|  | Subject: [PATCH 4/7] drivers: leds: Add the IEI WT61P803 PUZZLE LED driver | ||||||
|  |  | ||||||
|  | Add support for the IEI WT61P803 PUZZLE LED driver. | ||||||
|  | Currently only the front panel power LED is supported, | ||||||
|  | since it is the only LED on this board wired through the | ||||||
|  | MCU. | ||||||
|  |  | ||||||
|  | The LED is wired directly to the on-board MCU controller | ||||||
|  | and is toggled using an MCU command. | ||||||
|  |  | ||||||
|  | Support for more LEDs is going to be added in case more | ||||||
|  | boards implement this microcontroller, as LEDs use many | ||||||
|  | different GPIOs. | ||||||
|  |  | ||||||
|  | This driver depends on the IEI WT61P803 PUZZLE MFD driver. | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  drivers/leds/Kconfig                    |   8 ++ | ||||||
|  |  drivers/leds/Makefile                   |   1 + | ||||||
|  |  drivers/leds/leds-iei-wt61p803-puzzle.c | 147 ++++++++++++++++++++++++ | ||||||
|  |  3 files changed, 156 insertions(+) | ||||||
|  |  create mode 100644 drivers/leds/leds-iei-wt61p803-puzzle.c | ||||||
|  |  | ||||||
|  | --- a/drivers/leds/Kconfig | ||||||
|  | +++ b/drivers/leds/Kconfig | ||||||
|  | @@ -333,6 +333,14 @@ config LEDS_IPAQ_MICRO | ||||||
|  |  	  Choose this option if you want to use the notification LED on | ||||||
|  |  	  Compaq/HP iPAQ h3100 and h3600. | ||||||
|  |   | ||||||
|  | +config LEDS_IEI_WT61P803_PUZZLE | ||||||
|  | +	tristate "LED Support for the IEI WT61P803 PUZZLE MCU" | ||||||
|  | +	depends on LEDS_CLASS | ||||||
|  | +	depends on MFD_IEI_WT61P803_PUZZLE | ||||||
|  | +	help | ||||||
|  | +	  This option enables support for LEDs controlled by the IEI WT61P803 | ||||||
|  | +	  M801 MCU. | ||||||
|  | + | ||||||
|  |  config LEDS_HP6XX | ||||||
|  |  	tristate "LED Support for the HP Jornada 6xx" | ||||||
|  |  	depends on LEDS_CLASS | ||||||
|  | --- a/drivers/leds/Makefile | ||||||
|  | +++ b/drivers/leds/Makefile | ||||||
|  | @@ -35,6 +35,7 @@ obj-$(CONFIG_LEDS_HP6XX)		+= leds-hp6xx. | ||||||
|  |  obj-$(CONFIG_LEDS_INTEL_SS4200)		+= leds-ss4200.o | ||||||
|  |  obj-$(CONFIG_LEDS_IP30)			+= leds-ip30.o | ||||||
|  |  obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o | ||||||
|  | +obj-$(CONFIG_LEDS_IEI_WT61P803_PUZZLE)	+= leds-iei-wt61p803-puzzle.o | ||||||
|  |  obj-$(CONFIG_LEDS_IS31FL319X)		+= leds-is31fl319x.o | ||||||
|  |  obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o | ||||||
|  |  obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/drivers/leds/leds-iei-wt61p803-puzzle.c | ||||||
|  | @@ -0,0 +1,147 @@ | ||||||
|  | +// SPDX-License-Identifier: GPL-2.0-only | ||||||
|  | +/* IEI WT61P803 PUZZLE MCU LED Driver | ||||||
|  | + * | ||||||
|  | + * Copyright (C) 2020 Sartura Ltd. | ||||||
|  | + * Author: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + */ | ||||||
|  | + | ||||||
|  | +#include <linux/leds.h> | ||||||
|  | +#include <linux/mfd/iei-wt61p803-puzzle.h> | ||||||
|  | +#include <linux/mod_devicetable.h> | ||||||
|  | +#include <linux/module.h> | ||||||
|  | +#include <linux/platform_device.h> | ||||||
|  | +#include <linux/property.h> | ||||||
|  | +#include <linux/slab.h> | ||||||
|  | + | ||||||
|  | +enum iei_wt61p803_puzzle_led_state { | ||||||
|  | +	IEI_LED_OFF = 0x30, | ||||||
|  | +	IEI_LED_ON = 0x31, | ||||||
|  | +	IEI_LED_BLINK_5HZ = 0x32, | ||||||
|  | +	IEI_LED_BLINK_1HZ = 0x33, | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +/** | ||||||
|  | + * struct iei_wt61p803_puzzle_led - MCU LED Driver | ||||||
|  | + * @cdev:		LED classdev | ||||||
|  | + * @mcu:		MCU struct pointer | ||||||
|  | + * @response_buffer	Global MCU response buffer | ||||||
|  | + * @lock:		General mutex lock to protect simultaneous R/W access to led_power_state | ||||||
|  | + * @led_power_state:	State of the front panel power LED | ||||||
|  | + */ | ||||||
|  | +struct iei_wt61p803_puzzle_led { | ||||||
|  | +	struct led_classdev cdev; | ||||||
|  | +	struct iei_wt61p803_puzzle *mcu; | ||||||
|  | +	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE]; | ||||||
|  | +	struct mutex lock; /* mutex to protect led_power_state */ | ||||||
|  | +	int led_power_state; | ||||||
|  | +}; | ||||||
|  | + | ||||||
|  | +static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led | ||||||
|  | +	(struct led_classdev *led_cdev) | ||||||
|  | +{ | ||||||
|  | +	return container_of(led_cdev, struct iei_wt61p803_puzzle_led, cdev); | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_led_brightness_set_blocking(struct led_classdev *cdev, | ||||||
|  | +							   enum led_brightness brightness) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev); | ||||||
|  | +	unsigned char *resp_buf = priv->response_buffer; | ||||||
|  | +	unsigned char led_power_cmd[5] = {}; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  | +	led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED; | ||||||
|  | +	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER; | ||||||
|  | +	led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON; | ||||||
|  | + | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd, | ||||||
|  | +						sizeof(led_power_cmd), | ||||||
|  | +						resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 3) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START && | ||||||
|  | +	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK && | ||||||
|  | +	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	mutex_lock(&priv->lock); | ||||||
|  | +	priv->led_power_state = brightness; | ||||||
|  | +	mutex_unlock(&priv->lock); | ||||||
|  | + | ||||||
|  | +	return 0; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static enum led_brightness iei_wt61p803_puzzle_led_brightness_get(struct led_classdev *cdev) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev); | ||||||
|  | +	int led_state; | ||||||
|  | + | ||||||
|  | +	mutex_lock(&priv->lock); | ||||||
|  | +	led_state = priv->led_power_state; | ||||||
|  | +	mutex_unlock(&priv->lock); | ||||||
|  | + | ||||||
|  | +	return led_state; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev) | ||||||
|  | +{ | ||||||
|  | +	struct device *dev = &pdev->dev; | ||||||
|  | +	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent); | ||||||
|  | +	struct iei_wt61p803_puzzle_led *priv; | ||||||
|  | +	struct led_init_data init_data = {}; | ||||||
|  | +	struct fwnode_handle *child; | ||||||
|  | +	int ret; | ||||||
|  | + | ||||||
|  | +	if (device_get_child_node_count(dev) != 1) | ||||||
|  | +		return -EINVAL; | ||||||
|  | + | ||||||
|  | +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||||||
|  | +	if (!priv) | ||||||
|  | +		return -ENOMEM; | ||||||
|  | + | ||||||
|  | +	priv->mcu = mcu; | ||||||
|  | +	priv->led_power_state = 1; | ||||||
|  | +	mutex_init(&priv->lock); | ||||||
|  | +	dev_set_drvdata(dev, priv); | ||||||
|  | + | ||||||
|  | +	child = device_get_next_child_node(dev, NULL); | ||||||
|  | +	init_data.fwnode = child; | ||||||
|  | + | ||||||
|  | +	priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking; | ||||||
|  | +	priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get; | ||||||
|  | +	priv->cdev.max_brightness = 1; | ||||||
|  | + | ||||||
|  | +	ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data); | ||||||
|  | +	if (ret) | ||||||
|  | +		dev_err(dev, "Could not register LED\n"); | ||||||
|  | + | ||||||
|  | +	fwnode_handle_put(child); | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static const struct of_device_id iei_wt61p803_puzzle_led_of_match[] = { | ||||||
|  | +	{ .compatible = "iei,wt61p803-puzzle-leds" }, | ||||||
|  | +	{ } | ||||||
|  | +}; | ||||||
|  | +MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_led_of_match); | ||||||
|  | + | ||||||
|  | +static struct platform_driver iei_wt61p803_puzzle_led_driver = { | ||||||
|  | +	.driver = { | ||||||
|  | +		.name = "iei-wt61p803-puzzle-led", | ||||||
|  | +		.of_match_table = iei_wt61p803_puzzle_led_of_match, | ||||||
|  | +	}, | ||||||
|  | +	.probe = iei_wt61p803_puzzle_led_probe, | ||||||
|  | +}; | ||||||
|  | +module_platform_driver(iei_wt61p803_puzzle_led_driver); | ||||||
|  | + | ||||||
|  | +MODULE_DESCRIPTION("IEI WT61P803 PUZZLE front panel LED driver"); | ||||||
|  | +MODULE_AUTHOR("Luka Kovacic <luka.kovacic@sartura.hr>"); | ||||||
|  | +MODULE_LICENSE("GPL v2"); | ||||||
|  | +MODULE_ALIAS("platform:leds-iei-wt61p803-puzzle"); | ||||||
| @@ -0,0 +1,82 @@ | |||||||
|  | From 2fab3b4956c5b2f83c1e1abffc1df39de2933d83 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:36 +0000 | ||||||
|  | Subject: [PATCH 5/7] Documentation/ABI: Add iei-wt61p803-puzzle driver sysfs | ||||||
|  |  interface documentation | ||||||
|  |  | ||||||
|  | Add the iei-wt61p803-puzzle driver sysfs interface documentation to allow | ||||||
|  | monitoring and control of the microcontroller from user space. | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  .../testing/sysfs-driver-iei-wt61p803-puzzle  | 61 +++++++++++++++++++ | ||||||
|  |  1 file changed, 61 insertions(+) | ||||||
|  |  create mode 100644 Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle | ||||||
|  |  | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/Documentation/ABI/testing/sysfs-driver-iei-wt61p803-puzzle | ||||||
|  | @@ -0,0 +1,61 @@ | ||||||
|  | +What:		/sys/bus/serial/devices/.../mac_address_* | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RW) Internal factory assigned MAC address values | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../serial_number | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RW) Internal factory assigned serial number | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../version | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Internal MCU firmware version | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../protocol_version | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Internal MCU communication protocol version | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../power_loss_recovery | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RW) Host platform power loss recovery settings | ||||||
|  | +		Value mapping: 0 - Always-On, 1 - Always-Off, 2 - Always-AC, 3 - Always-WA | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../bootloader_mode | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Internal MCU bootloader mode status | ||||||
|  | +		Value mapping: | ||||||
|  | +		0 - normal mode | ||||||
|  | +		1 - bootloader mode | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../power_status | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Power status indicates the host platform power on method. | ||||||
|  | +		Value mapping (bitwise list): | ||||||
|  | +		0x80 - Null | ||||||
|  | +		0x40 - Firmware flag | ||||||
|  | +		0x20 - Power loss detection flag (powered off) | ||||||
|  | +		0x10 - Power loss detection flag (AC mode) | ||||||
|  | +		0x08 - Button power on | ||||||
|  | +		0x04 - Wake-on-LAN power on | ||||||
|  | +		0x02 - RTC alarm power on | ||||||
|  | +		0x01 - AC recover power on | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../build_info | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Internal MCU firmware build date | ||||||
|  | +		Format: yyyy/mm/dd hh:mm | ||||||
|  | + | ||||||
|  | +What:		/sys/bus/serial/devices/.../ac_recovery_status | ||||||
|  | +Date:		September 2020 | ||||||
|  | +Contact:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +Description:	(RO) Host platform AC recovery status value | ||||||
|  | +		Value mapping: | ||||||
|  | +		0 - board has not been recovered from power down | ||||||
|  | +		1 - board has been recovered from power down | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | From 0aff3e5923fecc6842473ad07a688d6e2f2c2d55 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:37 +0000 | ||||||
|  | Subject: [PATCH 6/7] Documentation/hwmon: Add iei-wt61p803-puzzle hwmon driver | ||||||
|  |  documentation | ||||||
|  |  | ||||||
|  | Add the iei-wt61p803-puzzle driver hwmon driver interface documentation. | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  .../hwmon/iei-wt61p803-puzzle-hwmon.rst       | 43 +++++++++++++++++++ | ||||||
|  |  Documentation/hwmon/index.rst                 |  1 + | ||||||
|  |  2 files changed, 44 insertions(+) | ||||||
|  |  create mode 100644 Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst | ||||||
|  |  | ||||||
|  | --- /dev/null | ||||||
|  | +++ b/Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst | ||||||
|  | @@ -0,0 +1,43 @@ | ||||||
|  | +.. SPDX-License-Identifier: GPL-2.0-only | ||||||
|  | + | ||||||
|  | +Kernel driver iei-wt61p803-puzzle-hwmon | ||||||
|  | +======================================= | ||||||
|  | + | ||||||
|  | +Supported chips: | ||||||
|  | + * IEI WT61P803 PUZZLE for IEI Puzzle M801 | ||||||
|  | + | ||||||
|  | +   Prefix: 'iei-wt61p803-puzzle-hwmon' | ||||||
|  | + | ||||||
|  | +Author: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | + | ||||||
|  | + | ||||||
|  | +Description | ||||||
|  | +----------- | ||||||
|  | + | ||||||
|  | +This driver adds fan and temperature sensor reading for some IEI Puzzle | ||||||
|  | +series boards. | ||||||
|  | + | ||||||
|  | +Sysfs attributes | ||||||
|  | +---------------- | ||||||
|  | + | ||||||
|  | +The following attributes are supported: | ||||||
|  | + | ||||||
|  | +- IEI WT61P803 PUZZLE for IEI Puzzle M801 | ||||||
|  | + | ||||||
|  | +/sys files in hwmon subsystem | ||||||
|  | +----------------------------- | ||||||
|  | + | ||||||
|  | +================= == ===================================================== | ||||||
|  | +fan[1-5]_input    RO files for fan speed (in RPM) | ||||||
|  | +pwm[1-2]          RW files for fan[1-2] target duty cycle (0..255) | ||||||
|  | +temp[1-2]_input   RO files for temperature sensors, in millidegree Celsius | ||||||
|  | +================= == ===================================================== | ||||||
|  | + | ||||||
|  | +/sys files in thermal subsystem | ||||||
|  | +------------------------------- | ||||||
|  | + | ||||||
|  | +================= == ===================================================== | ||||||
|  | +cur_state         RW file for current cooling state of the cooling device | ||||||
|  | +                     (0..max_state) | ||||||
|  | +max_state         RO file for maximum cooling state of the cooling device | ||||||
|  | +================= == ===================================================== | ||||||
|  | --- a/Documentation/hwmon/index.rst | ||||||
|  | +++ b/Documentation/hwmon/index.rst | ||||||
|  | @@ -71,6 +71,7 @@ Hardware Monitoring Kernel Drivers | ||||||
|  |     ibmaem | ||||||
|  |     ibm-cffps | ||||||
|  |     ibmpowernv | ||||||
|  | +   iei-wt61p803-puzzle-hwmon | ||||||
|  |     ina209 | ||||||
|  |     ina2xx | ||||||
|  |     ina3221 | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | From 12479baad28d2a08c6cb9e83471057635fa1635c Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Luka Kovacic <luka.kovacic () sartura ! hr> | ||||||
|  | Date: Tue, 24 Aug 2021 12:44:38 +0000 | ||||||
|  | Subject: [PATCH 7/7] MAINTAINERS: Add an entry for the IEI WT61P803 PUZZLE | ||||||
|  |  driver | ||||||
|  |  | ||||||
|  | Add an entry for the IEI WT61P803 PUZZLE driver (MFD, HWMON, LED drivers). | ||||||
|  |  | ||||||
|  | Signed-off-by: Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | Signed-off-by: Pavo Banicevic <pavo.banicevic@sartura.hr> | ||||||
|  | Cc: Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | Cc: Robert Marko <robert.marko@sartura.hr> | ||||||
|  | --- | ||||||
|  |  MAINTAINERS | 16 ++++++++++++++++ | ||||||
|  |  1 file changed, 16 insertions(+) | ||||||
|  |  | ||||||
|  | --- a/MAINTAINERS | ||||||
|  | +++ b/MAINTAINERS | ||||||
|  | @@ -8538,6 +8538,22 @@ F:	include/net/nl802154.h | ||||||
|  |  F:	net/ieee802154/ | ||||||
|  |  F:	net/mac802154/ | ||||||
|  |   | ||||||
|  | +IEI WT61P803 M801 MFD DRIVER | ||||||
|  | +M:	Luka Kovacic <luka.kovacic@sartura.hr> | ||||||
|  | +M:	Luka Perkov <luka.perkov@sartura.hr> | ||||||
|  | +M:	Goran Medic <goran.medic@sartura.hr> | ||||||
|  | +L:	linux-kernel@vger.kernel.org | ||||||
|  | +S:	Maintained | ||||||
|  | +F:	Documentation/ABI/stable/sysfs-driver-iei-wt61p803-puzzle | ||||||
|  | +F:	Documentation/devicetree/bindings/hwmon/iei,wt61p803-puzzle-hwmon.yaml | ||||||
|  | +F:	Documentation/devicetree/bindings/leds/iei,wt61p803-puzzle-leds.yaml | ||||||
|  | +F:	Documentation/devicetree/bindings/mfd/iei,wt61p803-puzzle.yaml | ||||||
|  | +F:	Documentation/hwmon/iei-wt61p803-puzzle-hwmon.rst | ||||||
|  | +F:	drivers/hwmon/iei-wt61p803-puzzle-hwmon.c | ||||||
|  | +F:	drivers/leds/leds-iei-wt61p803-puzzle.c | ||||||
|  | +F:	drivers/mfd/iei-wt61p803-puzzle.c | ||||||
|  | +F:	include/linux/mfd/iei-wt61p803-puzzle.h | ||||||
|  | + | ||||||
|  |  IFE PROTOCOL | ||||||
|  |  M:	Yotam Gigi <yotam.gi@gmail.com> | ||||||
|  |  M:	Jamal Hadi Salim <jhs@mojatatu.com> | ||||||
| @@ -0,0 +1,247 @@ | |||||||
|  | --- a/drivers/leds/leds-iei-wt61p803-puzzle.c | ||||||
|  | +++ b/drivers/leds/leds-iei-wt61p803-puzzle.c | ||||||
|  | @@ -9,10 +9,13 @@ | ||||||
|  |  #include <linux/mfd/iei-wt61p803-puzzle.h> | ||||||
|  |  #include <linux/mod_devicetable.h> | ||||||
|  |  #include <linux/module.h> | ||||||
|  | +#include <linux/of.h> | ||||||
|  |  #include <linux/platform_device.h> | ||||||
|  |  #include <linux/property.h> | ||||||
|  |  #include <linux/slab.h> | ||||||
|  |   | ||||||
|  | +#define IEI_LEDS_MAX		4 | ||||||
|  | + | ||||||
|  |  enum iei_wt61p803_puzzle_led_state { | ||||||
|  |  	IEI_LED_OFF = 0x30, | ||||||
|  |  	IEI_LED_ON = 0x31, | ||||||
|  | @@ -34,6 +37,9 @@ struct iei_wt61p803_puzzle_led { | ||||||
|  |  	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE]; | ||||||
|  |  	struct mutex lock; /* mutex to protect led_power_state */ | ||||||
|  |  	int led_power_state; | ||||||
|  | +	int id; | ||||||
|  | +	bool blinking; | ||||||
|  | +	bool active_low; | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led | ||||||
|  | @@ -51,10 +57,20 @@ static int iei_wt61p803_puzzle_led_brigh | ||||||
|  |  	size_t reply_size; | ||||||
|  |  	int ret; | ||||||
|  |   | ||||||
|  | +	mutex_lock(&priv->lock); | ||||||
|  | +	if (priv->blinking) { | ||||||
|  | +		if (brightness == LED_OFF) | ||||||
|  | +			priv->blinking = false; | ||||||
|  | +		else | ||||||
|  | +			return 0; | ||||||
|  | +	} | ||||||
|  | +	mutex_unlock(&priv->lock); | ||||||
|  | + | ||||||
|  |  	led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  |  	led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED; | ||||||
|  | -	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER; | ||||||
|  | -	led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON; | ||||||
|  | +	led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id); | ||||||
|  | +	led_power_cmd[3] = ((brightness == LED_OFF) ^ priv->active_low) ? | ||||||
|  | +				IEI_LED_OFF : IEI_LED_ON; | ||||||
|  |   | ||||||
|  |  	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd, | ||||||
|  |  						sizeof(led_power_cmd), | ||||||
|  | @@ -90,39 +106,164 @@ static enum led_brightness iei_wt61p803_ | ||||||
|  |  	return led_state; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static int iei_wt61p803_puzzle_led_set_blink(struct led_classdev *cdev, | ||||||
|  | +					     unsigned long *delay_on, | ||||||
|  | +					     unsigned long *delay_off) | ||||||
|  | +{ | ||||||
|  | +	struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev); | ||||||
|  | +	unsigned char led_blink_cmd[5] = {}; | ||||||
|  | +	unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE]; | ||||||
|  | +	size_t reply_size; | ||||||
|  | +	int ret = 0; | ||||||
|  | + | ||||||
|  | +	/* set defaults */ | ||||||
|  | +	if (!*delay_on && !*delay_off) { | ||||||
|  | +		*delay_on = 500; | ||||||
|  | +		*delay_off = 500; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	/* minimum delay for soft-driven blinking is 50ms to keep load low */ | ||||||
|  | +	if (*delay_on < 50) | ||||||
|  | +		*delay_on = 50; | ||||||
|  | + | ||||||
|  | +	if (*delay_off < 50) | ||||||
|  | +		*delay_off = 50; | ||||||
|  | + | ||||||
|  | +	if (*delay_on != *delay_off) | ||||||
|  | +		return -EINVAL; | ||||||
|  | + | ||||||
|  | +	/* aggressively offload blinking to hardware, if possible */ | ||||||
|  | +	if (*delay_on < 100) { | ||||||
|  | +		return -EINVAL; | ||||||
|  | +	} else if (*delay_on < 200) { | ||||||
|  | +		*delay_on = 100; | ||||||
|  | +		*delay_off = 100; | ||||||
|  | +	} else if (*delay_on <= 500) { | ||||||
|  | +		*delay_on = 500; | ||||||
|  | +		*delay_off = 500; | ||||||
|  | +	} else { | ||||||
|  | +		return -EINVAL; | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	led_blink_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; | ||||||
|  | +	led_blink_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED; | ||||||
|  | +	led_blink_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id); | ||||||
|  | +	led_blink_cmd[3] = (*delay_on == 100)?IEI_LED_BLINK_5HZ:IEI_LED_BLINK_1HZ; | ||||||
|  | + | ||||||
|  | +	ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd, | ||||||
|  | +						sizeof(led_blink_cmd), | ||||||
|  | +						resp_buf, | ||||||
|  | +						&reply_size); | ||||||
|  | + | ||||||
|  | +	if (ret) | ||||||
|  | +		return ret; | ||||||
|  | + | ||||||
|  | +	if (reply_size != 3) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START && | ||||||
|  | +	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK && | ||||||
|  | +	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) | ||||||
|  | +		return -EIO; | ||||||
|  | + | ||||||
|  | +	mutex_lock(&priv->lock); | ||||||
|  | +	priv->blinking = true; | ||||||
|  | +	mutex_unlock(&priv->lock); | ||||||
|  | + | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  | +static int iei_wt61p803_puzzle_led_set_dt_default(struct led_classdev *cdev, | ||||||
|  | +				     struct device_node *np) | ||||||
|  | +{ | ||||||
|  | +	const char *state; | ||||||
|  | +	int ret = 0; | ||||||
|  | + | ||||||
|  | +	state = of_get_property(np, "default-state", NULL); | ||||||
|  | +	if (state) { | ||||||
|  | +		if (!strcmp(state, "on")) { | ||||||
|  | +			ret = | ||||||
|  | +			iei_wt61p803_puzzle_led_brightness_set_blocking( | ||||||
|  | +				cdev, cdev->max_brightness); | ||||||
|  | +		} else  { | ||||||
|  | +			ret = iei_wt61p803_puzzle_led_brightness_set_blocking( | ||||||
|  | +				cdev, LED_OFF); | ||||||
|  | +		} | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	return ret; | ||||||
|  | +} | ||||||
|  | + | ||||||
|  |  static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev) | ||||||
|  |  { | ||||||
|  |  	struct device *dev = &pdev->dev; | ||||||
|  | +	struct device_node *np = dev_of_node(dev); | ||||||
|  | +	struct device_node *child; | ||||||
|  |  	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent); | ||||||
|  |  	struct iei_wt61p803_puzzle_led *priv; | ||||||
|  | -	struct led_init_data init_data = {}; | ||||||
|  | -	struct fwnode_handle *child; | ||||||
|  |  	int ret; | ||||||
|  | +	u32 reg; | ||||||
|  |   | ||||||
|  | -	if (device_get_child_node_count(dev) != 1) | ||||||
|  | +	if (device_get_child_node_count(dev) > IEI_LEDS_MAX) | ||||||
|  |  		return -EINVAL; | ||||||
|  |   | ||||||
|  | -	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||||||
|  | -	if (!priv) | ||||||
|  | -		return -ENOMEM; | ||||||
|  | - | ||||||
|  | -	priv->mcu = mcu; | ||||||
|  | -	priv->led_power_state = 1; | ||||||
|  | -	mutex_init(&priv->lock); | ||||||
|  | -	dev_set_drvdata(dev, priv); | ||||||
|  | - | ||||||
|  | -	child = device_get_next_child_node(dev, NULL); | ||||||
|  | -	init_data.fwnode = child; | ||||||
|  | - | ||||||
|  | -	priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking; | ||||||
|  | -	priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get; | ||||||
|  | -	priv->cdev.max_brightness = 1; | ||||||
|  | +	for_each_available_child_of_node(np, child) { | ||||||
|  | +		struct led_init_data init_data = {}; | ||||||
|  |   | ||||||
|  | -	ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data); | ||||||
|  | -	if (ret) | ||||||
|  | -		dev_err(dev, "Could not register LED\n"); | ||||||
|  | +		ret = of_property_read_u32(child, "reg", ®); | ||||||
|  | +		if (ret) { | ||||||
|  | +			dev_err(dev, "Failed to read led 'reg' property\n"); | ||||||
|  | +			goto put_child_node; | ||||||
|  | +		} | ||||||
|  | + | ||||||
|  | +		if (reg > IEI_LEDS_MAX) { | ||||||
|  | +			dev_err(dev, "Invalid led reg %u\n", reg); | ||||||
|  | +			ret = -EINVAL; | ||||||
|  | +			goto put_child_node; | ||||||
|  | +		} | ||||||
|  | + | ||||||
|  | +		priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||||||
|  | +		if (!priv) { | ||||||
|  | +			ret = -ENOMEM; | ||||||
|  | +			goto put_child_node; | ||||||
|  | +		} | ||||||
|  | + | ||||||
|  | +		mutex_init(&priv->lock); | ||||||
|  | + | ||||||
|  | +		dev_set_drvdata(dev, priv); | ||||||
|  | + | ||||||
|  | +		if (of_property_read_bool(child, "active-low")) | ||||||
|  | +			priv->active_low = true; | ||||||
|  | + | ||||||
|  | +		priv->mcu = mcu; | ||||||
|  | +		priv->id = reg; | ||||||
|  | +		priv->led_power_state = 1; | ||||||
|  | +		priv->blinking = false; | ||||||
|  | +		init_data.fwnode = of_fwnode_handle(child); | ||||||
|  | + | ||||||
|  | +		priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking; | ||||||
|  | +		priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get; | ||||||
|  | +		priv->cdev.blink_set = iei_wt61p803_puzzle_led_set_blink; | ||||||
|  | + | ||||||
|  | +		priv->cdev.max_brightness = 1; | ||||||
|  | + | ||||||
|  | +		ret = iei_wt61p803_puzzle_led_set_dt_default(&priv->cdev, child); | ||||||
|  | +		if (ret) { | ||||||
|  | +			dev_err(dev, "Could apply default from DT\n"); | ||||||
|  | +			goto put_child_node; | ||||||
|  | +		} | ||||||
|  | + | ||||||
|  | +		ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data); | ||||||
|  | +		if (ret) { | ||||||
|  | +			dev_err(dev, "Could not register LED\n"); | ||||||
|  | +			goto put_child_node; | ||||||
|  | +		} | ||||||
|  | +	} | ||||||
|  | + | ||||||
|  | +	return ret; | ||||||
|  |   | ||||||
|  | -	fwnode_handle_put(child); | ||||||
|  | +put_child_node: | ||||||
|  | +	of_node_put(child); | ||||||
|  |  	return ret; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | --- a/include/linux/mfd/iei-wt61p803-puzzle.h | ||||||
|  | +++ b/include/linux/mfd/iei-wt61p803-puzzle.h | ||||||
|  | @@ -36,7 +36,7 @@ | ||||||
|  |  #define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */ | ||||||
|  |   | ||||||
|  |  #define IEI_WT61P803_PUZZLE_CMD_LED			0x52 /* R */ | ||||||
|  | -#define IEI_WT61P803_PUZZLE_CMD_LED_POWER		0x31 /* 1 */ | ||||||
|  | +#define IEI_WT61P803_PUZZLE_CMD_LED_SET(n)		(0x30 | (n)) | ||||||
|  |   | ||||||
|  |  #define IEI_WT61P803_PUZZLE_CMD_TEMP			0x54 /* T */ | ||||||
|  |  #define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL		0x41 /* A */ | ||||||
		Reference in New Issue
	
	Block a user
	 Daniel Golle
					Daniel Golle