All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/4] Add support for the Gateworks System Controller
@ 2018-03-28 15:13 ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:13 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog

This series adds support for the Gateworks System Controller used on Gateworks
Laguna, Ventana, and Newport product families.

The GSC is an MSP430 I2C slave controller whose firmware embeds the following
features:
 - I/O expander (16 GPIO's emulating a PCA955x)
 - EEPROM (enumating AT24)
 - RTC (enumating DS1672)
 - HWMON
 - Interrupt controller with tamper detect, user pushbotton
 - Watchdog controller capable of full board power-cycle
 - Power Control capable of full board power-cycle

see http://trac.gateworks.com/wiki/gsc for more details
---
v3:
 - removed unnecessary input driver
 - added wdt driver
 - bindings: encorporated feedback from mailng list
 - hwmon:
 - encoroprated feedback from mailng list
 - added support for raw ADC voltage input used in newer GSC firmware

v2:
 - change license comment block style
 - remove COMPILE_TEST
 - fixed whitespace issues
 - replaced a printk with dev_err
 - remove DEBUG
 - simplify regmap_bulk_read err check
 - remove break after returns in switch statement
 - fix fan setpoint buffer address
 - remove unnecessary parens
 - consistently use struct device *dev pointer
 - add validation for hwmon child node props
 - move parsing of of to own function
 - use strlcpy to ensure null termination
 - fix static array sizes and removed unnecessary initializers
 - dynamically allocate channels
 - fix fan input label
 - support platform data

Tim Harvey (4):
  dt-bindings: mfd: Add Gateworks System Controller bindings
  mfd: add Gateworks System Controller core driver
  hwmon: add Gateworks System Controller support
  watchdog: add Gateworks System Controller support

 .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 ++++++++
 drivers/hwmon/Kconfig                              |   9 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/gsc-hwmon.c                          | 368 +++++++++++++++++++++
 drivers/mfd/Kconfig                                |  13 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/gateworks-gsc.c                        | 285 ++++++++++++++++
 drivers/watchdog/Kconfig                           |  10 +
 drivers/watchdog/Makefile                          |   1 +
 drivers/watchdog/gsc_wdt.c                         | 146 ++++++++
 include/linux/mfd/gsc.h                            |  75 +++++
 include/linux/platform_data/gsc_hwmon.h            |  43 +++
 12 files changed, 1087 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
 create mode 100644 drivers/hwmon/gsc-hwmon.c
 create mode 100644 drivers/mfd/gateworks-gsc.c
 create mode 100644 drivers/watchdog/gsc_wdt.c
 create mode 100644 include/linux/mfd/gsc.h
 create mode 100644 include/linux/platform_data/gsc_hwmon.h

-- 
2.7.4

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 0/4] Add support for the Gateworks System Controller
@ 2018-03-28 15:13 ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:13 UTC (permalink / raw)
  To: linux-arm-kernel

This series adds support for the Gateworks System Controller used on Gateworks
Laguna, Ventana, and Newport product families.

The GSC is an MSP430 I2C slave controller whose firmware embeds the following
features:
 - I/O expander (16 GPIO's emulating a PCA955x)
 - EEPROM (enumating AT24)
 - RTC (enumating DS1672)
 - HWMON
 - Interrupt controller with tamper detect, user pushbotton
 - Watchdog controller capable of full board power-cycle
 - Power Control capable of full board power-cycle

see http://trac.gateworks.com/wiki/gsc for more details
---
v3:
 - removed unnecessary input driver
 - added wdt driver
 - bindings: encorporated feedback from mailng list
 - hwmon:
 - encoroprated feedback from mailng list
 - added support for raw ADC voltage input used in newer GSC firmware

v2:
 - change license comment block style
 - remove COMPILE_TEST
 - fixed whitespace issues
 - replaced a printk with dev_err
 - remove DEBUG
 - simplify regmap_bulk_read err check
 - remove break after returns in switch statement
 - fix fan setpoint buffer address
 - remove unnecessary parens
 - consistently use struct device *dev pointer
 - add validation for hwmon child node props
 - move parsing of of to own function
 - use strlcpy to ensure null termination
 - fix static array sizes and removed unnecessary initializers
 - dynamically allocate channels
 - fix fan input label
 - support platform data

Tim Harvey (4):
  dt-bindings: mfd: Add Gateworks System Controller bindings
  mfd: add Gateworks System Controller core driver
  hwmon: add Gateworks System Controller support
  watchdog: add Gateworks System Controller support

 .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 ++++++++
 drivers/hwmon/Kconfig                              |   9 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/gsc-hwmon.c                          | 368 +++++++++++++++++++++
 drivers/mfd/Kconfig                                |  13 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/gateworks-gsc.c                        | 285 ++++++++++++++++
 drivers/watchdog/Kconfig                           |  10 +
 drivers/watchdog/Makefile                          |   1 +
 drivers/watchdog/gsc_wdt.c                         | 146 ++++++++
 include/linux/mfd/gsc.h                            |  75 +++++
 include/linux/platform_data/gsc_hwmon.h            |  43 +++
 12 files changed, 1087 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
 create mode 100644 drivers/hwmon/gsc-hwmon.c
 create mode 100644 drivers/mfd/gateworks-gsc.c
 create mode 100644 drivers/watchdog/gsc_wdt.c
 create mode 100644 include/linux/mfd/gsc.h
 create mode 100644 include/linux/platform_data/gsc_hwmon.h

-- 
2.7.4

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 15:13 ` Tim Harvey
  (?)
@ 2018-03-28 15:14   ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog

This patch adds documentation of device-tree bindings for the
Gateworks System Controller (GSC).

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
 - replaced _ with -
 - remove input bindings
 - added full description of hwmon
 - fix unit address of hwmon child nodes

---
 .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt

diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
new file mode 100644
index 0000000..8f530ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
@@ -0,0 +1,135 @@
+Gateworks System Controller multi-function device
+
+The GSC is a Multifunction I2C slave device with the following submodules:
+- WDT
+- GPIO
+- Pushbutton controller
+- HWMON
+
+Required properties:
+- compatible : Must be "gw,gsc"
+- reg: I2C address of the device
+- interrupts: interrupt triggered by GSC_IRQ# signal
+- interrupt-parent: Interrupt controller GSC is connected to
+- #interrupt-cells: should be <1>, index of the interrupt within the
+  controller, in accordance with the "one cell" variant of
+  <devicetree/bindings/interrupt-controller/interrupt.txt>
+
+Optional nodes:
+* watchdog:
+The GSC provides a Watchdog monitor which can power cycle the board's
+primary power supply on most board models when tripped.
+
+Required watchdog properties:
+- compatible: must be "gw,gsc-watchdog"
+
+* hwmon:
+The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
+temperature and/or voltage monitoring.
+
+Required hwmon properties:
+- compatible: must be "gw,gsc-hwmon"
+
+Optional hwmon properties:
+- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
+- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
+
+Each hwmon child node defines an ADC input on the chip which the GSC may
+report cooked values (ie temperature sensor based on thermister), raw values,
+(ie voltage rail with a pre-scaling resistor divider), or a fan controller
+setpoint.
+
+Required hwmon child properties:
+- type: one of the following ADC types:
+  "gw,hwmon-temperature" - reports temperature in C*10
+  "gw,hwmon-voltage" - reports a pre-scaled voltage value
+  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
+       vreference, resolution, and optional resistor divider
+  "gw,hwmon-fan" - a fan temperature setpoint in C*10
+- reg: offset of the ADC register
+- label: name of the ADC input or FAN setpoint
+
+Optional hwmon child properties:
+- gw,voltage-divider: An array of two integers containing the resistor
+  values R1 and R2 of the optinal resistor divider on a raw ADC
+- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
+  compensate for a diode drop)
+
+Example:
+
+	gsc: gsc@20 {
+		compatible = "gw,gsc";
+		reg = <0x20>;
+		interrupt-parent = <&gpio1>;
+		interrupts = <4 GPIO_ACTIVE_LOW>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		watchdog {
+			compatible = "gw,gsc-watchdog";
+		};
+
+		hwmon {
+			compatible = "gw,gsc-hwmon";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			gw,reference-voltage = <2500>;
+			gw,resolution = <4096>;
+
+			hwmon@0 { /* A0: Board Temperature */
+				type = "gw,hwmon-temperature";
+				reg = <0x00>;
+				label = "temp";
+			};
+
+			hwmon@2 { /* A1: Input Voltage (raw ADC) */
+				type = "gw,hwmon-voltage-raw";
+				reg = <0x02>;
+				label = "vdd_vin";
+				gw,voltage-divider = <22100 1000>;
+				gw,voltage-offset = <800>;
+			};
+
+			hwmon@b { /* A2: Battery voltage */
+				type = "gw,hwmon-voltage";
+				reg = <0x0b>;
+				label = "vdd_bat";
+			};
+
+			hwmon@2c { /* fan temperature setpoint for 50% duty */
+				type = "gw,hwmon-fan";
+				reg = <0x2c>;
+				label = "fan_50p";
+			};
+
+			hwmon@2e { /* fan1 */
+				type = "gw,hwmon-fan";
+				reg = <0x2e>;
+				label = "fan_60p";
+			};
+
+			hwmon@30 { /* fan2 */
+				type = "gw,hwmon-fan";
+				reg = <0x30>;
+				label = "fan_70p";
+			};
+
+			hwmon@32 { /* fan3 */
+				type = "gw,hwmon-fan";
+				reg = <0x32>;
+				label = "fan_80p";
+			};
+
+			hwmon@34 { /* fan4 */
+				type = "gw,hwmon-fan";
+				reg = <0x34>;
+				label = "fan_90p";
+			};
+
+			hwmon@36 { /* fan5 */
+				type = "gw,hwmon-fan";
+				reg = <0x36>;
+				label = "fan_100p";
+			};
+		};
+	};
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 15:14   ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-hwmon, devicetree, linux-watchdog, linux-kernel,
	linux-input, linux-arm-kernel

This patch adds documentation of device-tree bindings for the
Gateworks System Controller (GSC).

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
 - replaced _ with -
 - remove input bindings
 - added full description of hwmon
 - fix unit address of hwmon child nodes

---
 .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt

diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
new file mode 100644
index 0000000..8f530ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
@@ -0,0 +1,135 @@
+Gateworks System Controller multi-function device
+
+The GSC is a Multifunction I2C slave device with the following submodules:
+- WDT
+- GPIO
+- Pushbutton controller
+- HWMON
+
+Required properties:
+- compatible : Must be "gw,gsc"
+- reg: I2C address of the device
+- interrupts: interrupt triggered by GSC_IRQ# signal
+- interrupt-parent: Interrupt controller GSC is connected to
+- #interrupt-cells: should be <1>, index of the interrupt within the
+  controller, in accordance with the "one cell" variant of
+  <devicetree/bindings/interrupt-controller/interrupt.txt>
+
+Optional nodes:
+* watchdog:
+The GSC provides a Watchdog monitor which can power cycle the board's
+primary power supply on most board models when tripped.
+
+Required watchdog properties:
+- compatible: must be "gw,gsc-watchdog"
+
+* hwmon:
+The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
+temperature and/or voltage monitoring.
+
+Required hwmon properties:
+- compatible: must be "gw,gsc-hwmon"
+
+Optional hwmon properties:
+- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
+- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
+
+Each hwmon child node defines an ADC input on the chip which the GSC may
+report cooked values (ie temperature sensor based on thermister), raw values,
+(ie voltage rail with a pre-scaling resistor divider), or a fan controller
+setpoint.
+
+Required hwmon child properties:
+- type: one of the following ADC types:
+  "gw,hwmon-temperature" - reports temperature in C*10
+  "gw,hwmon-voltage" - reports a pre-scaled voltage value
+  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
+       vreference, resolution, and optional resistor divider
+  "gw,hwmon-fan" - a fan temperature setpoint in C*10
+- reg: offset of the ADC register
+- label: name of the ADC input or FAN setpoint
+
+Optional hwmon child properties:
+- gw,voltage-divider: An array of two integers containing the resistor
+  values R1 and R2 of the optinal resistor divider on a raw ADC
+- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
+  compensate for a diode drop)
+
+Example:
+
+	gsc: gsc@20 {
+		compatible = "gw,gsc";
+		reg = <0x20>;
+		interrupt-parent = <&gpio1>;
+		interrupts = <4 GPIO_ACTIVE_LOW>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		watchdog {
+			compatible = "gw,gsc-watchdog";
+		};
+
+		hwmon {
+			compatible = "gw,gsc-hwmon";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			gw,reference-voltage = <2500>;
+			gw,resolution = <4096>;
+
+			hwmon@0 { /* A0: Board Temperature */
+				type = "gw,hwmon-temperature";
+				reg = <0x00>;
+				label = "temp";
+			};
+
+			hwmon@2 { /* A1: Input Voltage (raw ADC) */
+				type = "gw,hwmon-voltage-raw";
+				reg = <0x02>;
+				label = "vdd_vin";
+				gw,voltage-divider = <22100 1000>;
+				gw,voltage-offset = <800>;
+			};
+
+			hwmon@b { /* A2: Battery voltage */
+				type = "gw,hwmon-voltage";
+				reg = <0x0b>;
+				label = "vdd_bat";
+			};
+
+			hwmon@2c { /* fan temperature setpoint for 50% duty */
+				type = "gw,hwmon-fan";
+				reg = <0x2c>;
+				label = "fan_50p";
+			};
+
+			hwmon@2e { /* fan1 */
+				type = "gw,hwmon-fan";
+				reg = <0x2e>;
+				label = "fan_60p";
+			};
+
+			hwmon@30 { /* fan2 */
+				type = "gw,hwmon-fan";
+				reg = <0x30>;
+				label = "fan_70p";
+			};
+
+			hwmon@32 { /* fan3 */
+				type = "gw,hwmon-fan";
+				reg = <0x32>;
+				label = "fan_80p";
+			};
+
+			hwmon@34 { /* fan4 */
+				type = "gw,hwmon-fan";
+				reg = <0x34>;
+				label = "fan_90p";
+			};
+
+			hwmon@36 { /* fan5 */
+				type = "gw,hwmon-fan";
+				reg = <0x36>;
+				label = "fan_100p";
+			};
+		};
+	};
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 15:14   ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds documentation of device-tree bindings for the
Gateworks System Controller (GSC).

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
 - replaced _ with -
 - remove input bindings
 - added full description of hwmon
 - fix unit address of hwmon child nodes

---
 .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt

diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
new file mode 100644
index 0000000..8f530ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
@@ -0,0 +1,135 @@
+Gateworks System Controller multi-function device
+
+The GSC is a Multifunction I2C slave device with the following submodules:
+- WDT
+- GPIO
+- Pushbutton controller
+- HWMON
+
+Required properties:
+- compatible : Must be "gw,gsc"
+- reg: I2C address of the device
+- interrupts: interrupt triggered by GSC_IRQ# signal
+- interrupt-parent: Interrupt controller GSC is connected to
+- #interrupt-cells: should be <1>, index of the interrupt within the
+  controller, in accordance with the "one cell" variant of
+  <devicetree/bindings/interrupt-controller/interrupt.txt>
+
+Optional nodes:
+* watchdog:
+The GSC provides a Watchdog monitor which can power cycle the board's
+primary power supply on most board models when tripped.
+
+Required watchdog properties:
+- compatible: must be "gw,gsc-watchdog"
+
+* hwmon:
+The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
+temperature and/or voltage monitoring.
+
+Required hwmon properties:
+- compatible: must be "gw,gsc-hwmon"
+
+Optional hwmon properties:
+- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
+- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
+
+Each hwmon child node defines an ADC input on the chip which the GSC may
+report cooked values (ie temperature sensor based on thermister), raw values,
+(ie voltage rail with a pre-scaling resistor divider), or a fan controller
+setpoint.
+
+Required hwmon child properties:
+- type: one of the following ADC types:
+  "gw,hwmon-temperature" - reports temperature in C*10
+  "gw,hwmon-voltage" - reports a pre-scaled voltage value
+  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
+       vreference, resolution, and optional resistor divider
+  "gw,hwmon-fan" - a fan temperature setpoint in C*10
+- reg: offset of the ADC register
+- label: name of the ADC input or FAN setpoint
+
+Optional hwmon child properties:
+- gw,voltage-divider: An array of two integers containing the resistor
+  values R1 and R2 of the optinal resistor divider on a raw ADC
+- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
+  compensate for a diode drop)
+
+Example:
+
+	gsc: gsc at 20 {
+		compatible = "gw,gsc";
+		reg = <0x20>;
+		interrupt-parent = <&gpio1>;
+		interrupts = <4 GPIO_ACTIVE_LOW>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		watchdog {
+			compatible = "gw,gsc-watchdog";
+		};
+
+		hwmon {
+			compatible = "gw,gsc-hwmon";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			gw,reference-voltage = <2500>;
+			gw,resolution = <4096>;
+
+			hwmon at 0 { /* A0: Board Temperature */
+				type = "gw,hwmon-temperature";
+				reg = <0x00>;
+				label = "temp";
+			};
+
+			hwmon at 2 { /* A1: Input Voltage (raw ADC) */
+				type = "gw,hwmon-voltage-raw";
+				reg = <0x02>;
+				label = "vdd_vin";
+				gw,voltage-divider = <22100 1000>;
+				gw,voltage-offset = <800>;
+			};
+
+			hwmon at b { /* A2: Battery voltage */
+				type = "gw,hwmon-voltage";
+				reg = <0x0b>;
+				label = "vdd_bat";
+			};
+
+			hwmon at 2c { /* fan temperature setpoint for 50% duty */
+				type = "gw,hwmon-fan";
+				reg = <0x2c>;
+				label = "fan_50p";
+			};
+
+			hwmon at 2e { /* fan1 */
+				type = "gw,hwmon-fan";
+				reg = <0x2e>;
+				label = "fan_60p";
+			};
+
+			hwmon at 30 { /* fan2 */
+				type = "gw,hwmon-fan";
+				reg = <0x30>;
+				label = "fan_70p";
+			};
+
+			hwmon at 32 { /* fan3 */
+				type = "gw,hwmon-fan";
+				reg = <0x32>;
+				label = "fan_80p";
+			};
+
+			hwmon at 34 { /* fan4 */
+				type = "gw,hwmon-fan";
+				reg = <0x34>;
+				label = "fan_90p";
+			};
+
+			hwmon at 36 { /* fan5 */
+				type = "gw,hwmon-fan";
+				reg = <0x36>;
+				label = "fan_100p";
+			};
+		};
+	};
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-03-28 15:13 ` Tim Harvey
@ 2018-03-28 15:14   ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog, Randy Dunlap

The Gateworks System Controller (GSC) is an I2C slave controller
implemented with an MSP430 micro-controller whose firmware embeds the
following features:
 - I/O expander (16 GPIO's) using PCA955x protocol
 - Real Time Clock using DS1672 protocol
 - User EEPROM using AT24 protocol
 - HWMON using custom protocol
 - Interrupt controller with tamper detect, user pushbotton
 - Watchdog controller capable of full board power-cycle
 - Power Control capable of full board power-cycle

see http://trac.gateworks.com/wiki/gsc for more details

Cc: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
- rename gsc->gateworks-gsc
- remove uncecessary include for linux/mfd/core.h
- upercase I2C in comments
- remove i2c debug
- remove uncecessary comments
- don't use KBUILD_MODNAME for name
- remove unnecessary v1/v2/v3 tracking
- unregister hwmon i2c adapter on remove

v2:
- change license comment block style
- remove COMPILE_TEST (Randy)
- fixed whitespace issues
- replaced a printk with dev_err

---
 drivers/mfd/Kconfig         |  13 ++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/gateworks-gsc.c | 285 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/gsc.h     |  75 ++++++++++++
 4 files changed, 374 insertions(+)
 create mode 100644 drivers/mfd/gateworks-gsc.c
 create mode 100644 include/linux/mfd/gsc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1d20a80..013df63 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -341,6 +341,19 @@ config MFD_EXYNOS_LPASS
 	  Select this option to enable support for Samsung Exynos Low Power
 	  Audio Subsystem.
 
+config MFD_GATEWORKS_GSC
+	tristate "Gateworks System Controller"
+	depends on (I2C && OF)
+	select MFD_CORE
+	select REGMAP_I2C
+	select REGMAP_IRQ
+	help
+	  Enable support for the Gateworks System Controller found
+	  on Gateworks Single Board Computers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gsc.
+
 config MFD_MC13XXX
 	tristate
 	depends on (SPI_MASTER || I2C)
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d9474ad..da0868ff 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MFD_CROS_EC)	+= cros_ec_core.o
 obj-$(CONFIG_MFD_CROS_EC_I2C)	+= cros_ec_i2c.o
 obj-$(CONFIG_MFD_CROS_EC_SPI)	+= cros_ec_spi.o
 obj-$(CONFIG_MFD_EXYNOS_LPASS)	+= exynos-lpass.o
+obj-$(CONFIG_MFD_GATEWORKS_GSC)	+= gateworks-gsc.o
 
 rtsx_pci-objs			:= rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o
 obj-$(CONFIG_MFD_RTSX_PCI)	+= rtsx_pci.o
diff --git a/drivers/mfd/gateworks-gsc.c b/drivers/mfd/gateworks-gsc.c
new file mode 100644
index 0000000..e081613
--- /dev/null
+++ b/drivers/mfd/gateworks-gsc.c
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * The Gateworks System Controller (GSC) is a family of a multi-function
+ * "Power Management and System Companion Device" chips originally designed for
+ * use in Gateworks Single Board Computers. The control interface is I2C,
+ * at 100kbps, with an interrupt.
+ *
+ */
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The GSC suffers from an errata where occasionally during
+ * ADC cycles the chip can NAK I2C transactions. To ensure we have reliable
+ * register access we place retries around register access.
+ */
+#define I2C_RETRIES	3
+
+static int gsc_regmap_regwrite(void *context, unsigned int reg,
+			       unsigned int val)
+{
+	struct i2c_client *client = context;
+	int retry, ret;
+
+	for (retry = 0; retry < I2C_RETRIES; retry++) {
+		ret = i2c_smbus_write_byte_data(client, reg, val);
+		/*
+		 * -EAGAIN returned when the i2c host controller is busy
+		 * -EIO returned when i2c device is busy
+		 */
+		if (ret != -EAGAIN && ret != -EIO)
+			break;
+	}
+
+	return 0;
+}
+
+static int gsc_regmap_regread(void *context, unsigned int reg,
+			      unsigned int *val)
+{
+	struct i2c_client *client = context;
+	int retry, ret;
+
+	for (retry = 0; retry < I2C_RETRIES; retry++) {
+		ret = i2c_smbus_read_byte_data(client, reg);
+		/*
+		 * -EAGAIN returned when the i2c host controller is busy
+		 * -EIO returned when i2c device is busy
+		 */
+		if (ret != -EAGAIN && ret != -EIO)
+			break;
+	}
+	*val = ret & 0xff;
+
+	return 0;
+}
+
+static struct regmap_bus regmap_gsc = {
+	.reg_write = gsc_regmap_regwrite,
+	.reg_read = gsc_regmap_regread,
+};
+
+/*
+ * gsc_powerdown - API to use GSC to power down board for a specific time
+ *
+ * secs - number of seconds to remain powered off
+ */
+static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
+{
+	int ret;
+	unsigned char regs[4];
+
+	dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
+		 secs);
+	regs[0] = secs & 0xff;
+	regs[1] = (secs >> 8) & 0xff;
+	regs[2] = (secs >> 16) & 0xff;
+	regs[3] = (secs >> 24) & 0xff;
+	ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
+
+	return ret;
+}
+
+static ssize_t gsc_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(dev);
+	const char *name = attr->attr.name;
+	int rz = 0;
+
+	if (strcasecmp(name, "fw_version") == 0)
+		rz = sprintf(buf, "%d\n", gsc->fwver);
+	else if (strcasecmp(name, "fw_crc") == 0)
+		rz = sprintf(buf, "0x%04x\n", gsc->fwcrc);
+
+	return rz;
+}
+
+static ssize_t gsc_store(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(dev);
+	const char *name = attr->attr.name;
+	int ret;
+
+	if (strcasecmp(name, "powerdown") == 0) {
+		long value;
+
+		ret = kstrtol(buf, 0, &value);
+		if (ret == 0)
+			gsc_powerdown(gsc, value);
+	} else
+		dev_err(dev, "invalid name '%s\n", name);
+
+	return count;
+}
+
+static struct device_attribute attr_fwver =
+	__ATTR(fw_version, 0440, gsc_show, NULL);
+static struct device_attribute attr_fwcrc =
+	__ATTR(fw_crc, 0440, gsc_show, NULL);
+static struct device_attribute attr_pwrdown =
+	__ATTR(powerdown, 0220, NULL, gsc_store);
+
+static struct attribute *gsc_attrs[] = {
+	&attr_fwver.attr,
+	&attr_fwcrc.attr,
+	&attr_pwrdown.attr,
+	NULL,
+};
+
+static struct attribute_group attr_group = {
+	.attrs = gsc_attrs,
+};
+
+static const struct of_device_id gsc_of_match[] = {
+	{ .compatible = "gw,gsc", },
+	{ }
+};
+
+static const struct regmap_config gsc_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.max_register = 0xf,
+};
+
+static const struct regmap_config gsc_regmap_hwmon_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.max_register = 0x37,
+};
+
+static const struct regmap_irq gsc_irqs[] = {
+	REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)),
+	REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)),
+	REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)),
+	REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)),
+	REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)),
+	REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)),
+	REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)),
+	REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)),
+};
+
+static const struct regmap_irq_chip gsc_irq_chip = {
+	.name = "gateworks-gsc",
+	.irqs = gsc_irqs,
+	.num_irqs = ARRAY_SIZE(gsc_irqs),
+	.num_regs = 1,
+	.status_base = GSC_IRQ_STATUS,
+	.mask_base = GSC_IRQ_ENABLE,
+	.mask_invert = true,
+	.ack_base = GSC_IRQ_STATUS,
+	.ack_invert = true,
+};
+
+static int
+gsc_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct gsc_dev *gsc;
+	int ret;
+	unsigned int reg;
+
+	gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL);
+	if (!gsc)
+		return -ENOMEM;
+
+	gsc->dev = &client->dev;
+	gsc->i2c = client;
+	gsc->irq = client->irq;
+	i2c_set_clientdata(client, gsc);
+
+	gsc->regmap = devm_regmap_init(dev, &regmap_gsc, client,
+				       &gsc_regmap_config);
+	if (IS_ERR(gsc->regmap))
+		return PTR_ERR(gsc->regmap);
+
+	if (regmap_read(gsc->regmap, GSC_FW_VER, &reg))
+		return -EIO;
+	gsc->fwver = reg;
+
+	regmap_read(gsc->regmap, GSC_FW_CRC, &reg);
+	gsc->fwcrc = reg;
+	regmap_read(gsc->regmap, GSC_FW_CRC + 1, &reg);
+	gsc->fwcrc |= reg << 8;
+
+	gsc->i2c_hwmon = i2c_new_dummy(client->adapter, GSC_HWMON);
+	if (!gsc->i2c_hwmon) {
+		dev_err(dev, "Failed to allocate I2C device for HWMON\n");
+		return -ENODEV;
+	}
+	i2c_set_clientdata(gsc->i2c_hwmon, gsc);
+
+	gsc->regmap_hwmon = devm_regmap_init(dev, &regmap_gsc, gsc->i2c_hwmon,
+					     &gsc_regmap_hwmon_config);
+	if (IS_ERR(gsc->regmap_hwmon)) {
+		ret = PTR_ERR(gsc->regmap_hwmon);
+		dev_err(dev, "failed to allocate register map: %d\n", ret);
+		goto err_regmap;
+	}
+
+	ret = devm_regmap_add_irq_chip(dev, gsc->regmap, gsc->irq,
+				       IRQF_ONESHOT | IRQF_SHARED |
+				       IRQF_TRIGGER_FALLING, 0,
+				       &gsc_irq_chip, &gsc->irq_chip_data);
+	if (ret)
+		goto err_regmap;
+
+	dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n",
+		 gsc->fwver, gsc->fwcrc);
+
+	ret = sysfs_create_group(&dev->kobj, &attr_group);
+	if (ret)
+		dev_err(dev, "failed to create sysfs attrs\n");
+
+	ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+err_sysfs:
+	sysfs_remove_group(&dev->kobj, &attr_group);
+err_regmap:
+	i2c_unregister_device(gsc->i2c_hwmon);
+
+	return ret;
+}
+
+static int gsc_remove(struct i2c_client *client)
+{
+	struct gsc_dev *gsc = i2c_get_clientdata(client);
+
+	sysfs_remove_group(&client->dev.kobj, &attr_group);
+	i2c_unregister_device(gsc->i2c_hwmon);
+
+	return 0;
+}
+
+static struct i2c_driver gsc_driver = {
+	.driver = {
+		.name	= "gateworks-gsc",
+		.of_match_table = of_match_ptr(gsc_of_match),
+	},
+	.probe		= gsc_probe,
+	.remove		= gsc_remove,
+};
+
+module_i2c_driver(gsc_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("I2C Core interface for GSC");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/gsc.h b/include/linux/mfd/gsc.h
new file mode 100644
index 0000000..dcae61b
--- /dev/null
+++ b/include/linux/mfd/gsc.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ */
+#ifndef __LINUX_MFD_GSC_H_
+#define __LINUX_MFD_GSC_H_
+
+/* Device Addresses */
+#define GSC_MISC	0x20
+#define GSC_UPDATE	0x21
+#define GSC_GPIO	0x23
+#define GSC_HWMON	0x29
+#define GSC_EEPROM0	0x50
+#define GSC_EEPROM1	0x51
+#define GSC_EEPROM2	0x52
+#define GSC_EEPROM3	0x53
+#define GSC_RTC		0x68
+
+/* Register offsets */
+#define GSC_CTRL_0	0x00
+#define GSC_CTRL_1	0x01
+#define GSC_TIME	0x02
+#define GSC_TIME_ADD	0x06
+#define GSC_IRQ_STATUS	0x0A
+#define GSC_IRQ_ENABLE	0x0B
+#define GSC_FW_CRC	0x0C
+#define GSC_FW_VER	0x0E
+#define GSC_WP		0x0F
+
+/* Bit definitions */
+#define GSC_CTRL_0_PB_HARD_RESET	0
+#define GSC_CTRL_0_PB_CLEAR_SECURE_KEY	1
+#define GSC_CTRL_0_PB_SOFT_POWER_DOWN	2
+#define GSC_CTRL_0_PB_BOOT_ALTERNATE	3
+#define GSC_CTRL_0_PERFORM_CRC		4
+#define GSC_CTRL_0_TAMPER_DETECT	5
+#define GSC_CTRL_0_SWITCH_HOLD		6
+
+#define GSC_CTRL_1_SLEEP_ENABLE		0
+#define GSC_CTRL_1_ACTIVATE_SLEEP	1
+#define GSC_CTRL_1_LATCH_SLEEP_ADD	2
+#define GSC_CTRL_1_SLEEP_NOWAKEPB	3
+#define GSC_CTRL_1_WDT_TIME		4
+#define GSC_CTRL_1_WDT_ENABLE		5
+#define GSC_CTRL_1_SWITCH_BOOT_ENABLE	6
+#define GSC_CTRL_1_SWITCH_BOOT_CLEAR	7
+
+#define GSC_IRQ_PB			0
+#define GSC_IRQ_KEY_ERASED		1
+#define GSC_IRQ_EEPROM_WP		2
+#define GSC_IRQ_RESV			3
+#define GSC_IRQ_GPIO			4
+#define GSC_IRQ_TAMPER			5
+#define GSC_IRQ_WDT_TIMEOUT		6
+#define GSC_IRQ_SWITCH_HOLD		7
+
+/* Max registers */
+#define GSC_HWMON_MAX_REG	56
+
+struct gsc_dev {
+	struct device *dev;
+
+	struct i2c_client *i2c;		/* 0x20: interrupt controller, WDT */
+	struct i2c_client *i2c_hwmon;	/* 0x29: hwmon, fan controller */
+
+	struct regmap *regmap;
+	struct regmap *regmap_hwmon;
+	struct regmap_irq_chip_data *irq_chip_data;
+
+	int irq;
+	unsigned int fwver;
+	unsigned short fwcrc;
+};
+
+#endif /* __LINUX_MFD_GSC_H_ */
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-03-28 15:14   ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: linux-arm-kernel

The Gateworks System Controller (GSC) is an I2C slave controller
implemented with an MSP430 micro-controller whose firmware embeds the
following features:
 - I/O expander (16 GPIO's) using PCA955x protocol
 - Real Time Clock using DS1672 protocol
 - User EEPROM using AT24 protocol
 - HWMON using custom protocol
 - Interrupt controller with tamper detect, user pushbotton
 - Watchdog controller capable of full board power-cycle
 - Power Control capable of full board power-cycle

see http://trac.gateworks.com/wiki/gsc for more details

Cc: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
- rename gsc->gateworks-gsc
- remove uncecessary include for linux/mfd/core.h
- upercase I2C in comments
- remove i2c debug
- remove uncecessary comments
- don't use KBUILD_MODNAME for name
- remove unnecessary v1/v2/v3 tracking
- unregister hwmon i2c adapter on remove

v2:
- change license comment block style
- remove COMPILE_TEST (Randy)
- fixed whitespace issues
- replaced a printk with dev_err

---
 drivers/mfd/Kconfig         |  13 ++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/gateworks-gsc.c | 285 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/gsc.h     |  75 ++++++++++++
 4 files changed, 374 insertions(+)
 create mode 100644 drivers/mfd/gateworks-gsc.c
 create mode 100644 include/linux/mfd/gsc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1d20a80..013df63 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -341,6 +341,19 @@ config MFD_EXYNOS_LPASS
 	  Select this option to enable support for Samsung Exynos Low Power
 	  Audio Subsystem.
 
+config MFD_GATEWORKS_GSC
+	tristate "Gateworks System Controller"
+	depends on (I2C && OF)
+	select MFD_CORE
+	select REGMAP_I2C
+	select REGMAP_IRQ
+	help
+	  Enable support for the Gateworks System Controller found
+	  on Gateworks Single Board Computers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gsc.
+
 config MFD_MC13XXX
 	tristate
 	depends on (SPI_MASTER || I2C)
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d9474ad..da0868ff 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MFD_CROS_EC)	+= cros_ec_core.o
 obj-$(CONFIG_MFD_CROS_EC_I2C)	+= cros_ec_i2c.o
 obj-$(CONFIG_MFD_CROS_EC_SPI)	+= cros_ec_spi.o
 obj-$(CONFIG_MFD_EXYNOS_LPASS)	+= exynos-lpass.o
+obj-$(CONFIG_MFD_GATEWORKS_GSC)	+= gateworks-gsc.o
 
 rtsx_pci-objs			:= rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o
 obj-$(CONFIG_MFD_RTSX_PCI)	+= rtsx_pci.o
diff --git a/drivers/mfd/gateworks-gsc.c b/drivers/mfd/gateworks-gsc.c
new file mode 100644
index 0000000..e081613
--- /dev/null
+++ b/drivers/mfd/gateworks-gsc.c
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * The Gateworks System Controller (GSC) is a family of a multi-function
+ * "Power Management and System Companion Device" chips originally designed for
+ * use in Gateworks Single Board Computers. The control interface is I2C,
+ * at 100kbps, with an interrupt.
+ *
+ */
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The GSC suffers from an errata where occasionally during
+ * ADC cycles the chip can NAK I2C transactions. To ensure we have reliable
+ * register access we place retries around register access.
+ */
+#define I2C_RETRIES	3
+
+static int gsc_regmap_regwrite(void *context, unsigned int reg,
+			       unsigned int val)
+{
+	struct i2c_client *client = context;
+	int retry, ret;
+
+	for (retry = 0; retry < I2C_RETRIES; retry++) {
+		ret = i2c_smbus_write_byte_data(client, reg, val);
+		/*
+		 * -EAGAIN returned when the i2c host controller is busy
+		 * -EIO returned when i2c device is busy
+		 */
+		if (ret != -EAGAIN && ret != -EIO)
+			break;
+	}
+
+	return 0;
+}
+
+static int gsc_regmap_regread(void *context, unsigned int reg,
+			      unsigned int *val)
+{
+	struct i2c_client *client = context;
+	int retry, ret;
+
+	for (retry = 0; retry < I2C_RETRIES; retry++) {
+		ret = i2c_smbus_read_byte_data(client, reg);
+		/*
+		 * -EAGAIN returned when the i2c host controller is busy
+		 * -EIO returned when i2c device is busy
+		 */
+		if (ret != -EAGAIN && ret != -EIO)
+			break;
+	}
+	*val = ret & 0xff;
+
+	return 0;
+}
+
+static struct regmap_bus regmap_gsc = {
+	.reg_write = gsc_regmap_regwrite,
+	.reg_read = gsc_regmap_regread,
+};
+
+/*
+ * gsc_powerdown - API to use GSC to power down board for a specific time
+ *
+ * secs - number of seconds to remain powered off
+ */
+static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
+{
+	int ret;
+	unsigned char regs[4];
+
+	dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
+		 secs);
+	regs[0] = secs & 0xff;
+	regs[1] = (secs >> 8) & 0xff;
+	regs[2] = (secs >> 16) & 0xff;
+	regs[3] = (secs >> 24) & 0xff;
+	ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
+
+	return ret;
+}
+
+static ssize_t gsc_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(dev);
+	const char *name = attr->attr.name;
+	int rz = 0;
+
+	if (strcasecmp(name, "fw_version") == 0)
+		rz = sprintf(buf, "%d\n", gsc->fwver);
+	else if (strcasecmp(name, "fw_crc") == 0)
+		rz = sprintf(buf, "0x%04x\n", gsc->fwcrc);
+
+	return rz;
+}
+
+static ssize_t gsc_store(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(dev);
+	const char *name = attr->attr.name;
+	int ret;
+
+	if (strcasecmp(name, "powerdown") == 0) {
+		long value;
+
+		ret = kstrtol(buf, 0, &value);
+		if (ret == 0)
+			gsc_powerdown(gsc, value);
+	} else
+		dev_err(dev, "invalid name '%s\n", name);
+
+	return count;
+}
+
+static struct device_attribute attr_fwver =
+	__ATTR(fw_version, 0440, gsc_show, NULL);
+static struct device_attribute attr_fwcrc =
+	__ATTR(fw_crc, 0440, gsc_show, NULL);
+static struct device_attribute attr_pwrdown =
+	__ATTR(powerdown, 0220, NULL, gsc_store);
+
+static struct attribute *gsc_attrs[] = {
+	&attr_fwver.attr,
+	&attr_fwcrc.attr,
+	&attr_pwrdown.attr,
+	NULL,
+};
+
+static struct attribute_group attr_group = {
+	.attrs = gsc_attrs,
+};
+
+static const struct of_device_id gsc_of_match[] = {
+	{ .compatible = "gw,gsc", },
+	{ }
+};
+
+static const struct regmap_config gsc_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.max_register = 0xf,
+};
+
+static const struct regmap_config gsc_regmap_hwmon_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.max_register = 0x37,
+};
+
+static const struct regmap_irq gsc_irqs[] = {
+	REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)),
+	REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)),
+	REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)),
+	REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)),
+	REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)),
+	REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)),
+	REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)),
+	REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)),
+};
+
+static const struct regmap_irq_chip gsc_irq_chip = {
+	.name = "gateworks-gsc",
+	.irqs = gsc_irqs,
+	.num_irqs = ARRAY_SIZE(gsc_irqs),
+	.num_regs = 1,
+	.status_base = GSC_IRQ_STATUS,
+	.mask_base = GSC_IRQ_ENABLE,
+	.mask_invert = true,
+	.ack_base = GSC_IRQ_STATUS,
+	.ack_invert = true,
+};
+
+static int
+gsc_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct gsc_dev *gsc;
+	int ret;
+	unsigned int reg;
+
+	gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL);
+	if (!gsc)
+		return -ENOMEM;
+
+	gsc->dev = &client->dev;
+	gsc->i2c = client;
+	gsc->irq = client->irq;
+	i2c_set_clientdata(client, gsc);
+
+	gsc->regmap = devm_regmap_init(dev, &regmap_gsc, client,
+				       &gsc_regmap_config);
+	if (IS_ERR(gsc->regmap))
+		return PTR_ERR(gsc->regmap);
+
+	if (regmap_read(gsc->regmap, GSC_FW_VER, &reg))
+		return -EIO;
+	gsc->fwver = reg;
+
+	regmap_read(gsc->regmap, GSC_FW_CRC, &reg);
+	gsc->fwcrc = reg;
+	regmap_read(gsc->regmap, GSC_FW_CRC + 1, &reg);
+	gsc->fwcrc |= reg << 8;
+
+	gsc->i2c_hwmon = i2c_new_dummy(client->adapter, GSC_HWMON);
+	if (!gsc->i2c_hwmon) {
+		dev_err(dev, "Failed to allocate I2C device for HWMON\n");
+		return -ENODEV;
+	}
+	i2c_set_clientdata(gsc->i2c_hwmon, gsc);
+
+	gsc->regmap_hwmon = devm_regmap_init(dev, &regmap_gsc, gsc->i2c_hwmon,
+					     &gsc_regmap_hwmon_config);
+	if (IS_ERR(gsc->regmap_hwmon)) {
+		ret = PTR_ERR(gsc->regmap_hwmon);
+		dev_err(dev, "failed to allocate register map: %d\n", ret);
+		goto err_regmap;
+	}
+
+	ret = devm_regmap_add_irq_chip(dev, gsc->regmap, gsc->irq,
+				       IRQF_ONESHOT | IRQF_SHARED |
+				       IRQF_TRIGGER_FALLING, 0,
+				       &gsc_irq_chip, &gsc->irq_chip_data);
+	if (ret)
+		goto err_regmap;
+
+	dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n",
+		 gsc->fwver, gsc->fwcrc);
+
+	ret = sysfs_create_group(&dev->kobj, &attr_group);
+	if (ret)
+		dev_err(dev, "failed to create sysfs attrs\n");
+
+	ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+	if (ret)
+		goto err_sysfs;
+
+	return 0;
+
+err_sysfs:
+	sysfs_remove_group(&dev->kobj, &attr_group);
+err_regmap:
+	i2c_unregister_device(gsc->i2c_hwmon);
+
+	return ret;
+}
+
+static int gsc_remove(struct i2c_client *client)
+{
+	struct gsc_dev *gsc = i2c_get_clientdata(client);
+
+	sysfs_remove_group(&client->dev.kobj, &attr_group);
+	i2c_unregister_device(gsc->i2c_hwmon);
+
+	return 0;
+}
+
+static struct i2c_driver gsc_driver = {
+	.driver = {
+		.name	= "gateworks-gsc",
+		.of_match_table = of_match_ptr(gsc_of_match),
+	},
+	.probe		= gsc_probe,
+	.remove		= gsc_remove,
+};
+
+module_i2c_driver(gsc_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("I2C Core interface for GSC");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/gsc.h b/include/linux/mfd/gsc.h
new file mode 100644
index 0000000..dcae61b
--- /dev/null
+++ b/include/linux/mfd/gsc.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ */
+#ifndef __LINUX_MFD_GSC_H_
+#define __LINUX_MFD_GSC_H_
+
+/* Device Addresses */
+#define GSC_MISC	0x20
+#define GSC_UPDATE	0x21
+#define GSC_GPIO	0x23
+#define GSC_HWMON	0x29
+#define GSC_EEPROM0	0x50
+#define GSC_EEPROM1	0x51
+#define GSC_EEPROM2	0x52
+#define GSC_EEPROM3	0x53
+#define GSC_RTC		0x68
+
+/* Register offsets */
+#define GSC_CTRL_0	0x00
+#define GSC_CTRL_1	0x01
+#define GSC_TIME	0x02
+#define GSC_TIME_ADD	0x06
+#define GSC_IRQ_STATUS	0x0A
+#define GSC_IRQ_ENABLE	0x0B
+#define GSC_FW_CRC	0x0C
+#define GSC_FW_VER	0x0E
+#define GSC_WP		0x0F
+
+/* Bit definitions */
+#define GSC_CTRL_0_PB_HARD_RESET	0
+#define GSC_CTRL_0_PB_CLEAR_SECURE_KEY	1
+#define GSC_CTRL_0_PB_SOFT_POWER_DOWN	2
+#define GSC_CTRL_0_PB_BOOT_ALTERNATE	3
+#define GSC_CTRL_0_PERFORM_CRC		4
+#define GSC_CTRL_0_TAMPER_DETECT	5
+#define GSC_CTRL_0_SWITCH_HOLD		6
+
+#define GSC_CTRL_1_SLEEP_ENABLE		0
+#define GSC_CTRL_1_ACTIVATE_SLEEP	1
+#define GSC_CTRL_1_LATCH_SLEEP_ADD	2
+#define GSC_CTRL_1_SLEEP_NOWAKEPB	3
+#define GSC_CTRL_1_WDT_TIME		4
+#define GSC_CTRL_1_WDT_ENABLE		5
+#define GSC_CTRL_1_SWITCH_BOOT_ENABLE	6
+#define GSC_CTRL_1_SWITCH_BOOT_CLEAR	7
+
+#define GSC_IRQ_PB			0
+#define GSC_IRQ_KEY_ERASED		1
+#define GSC_IRQ_EEPROM_WP		2
+#define GSC_IRQ_RESV			3
+#define GSC_IRQ_GPIO			4
+#define GSC_IRQ_TAMPER			5
+#define GSC_IRQ_WDT_TIMEOUT		6
+#define GSC_IRQ_SWITCH_HOLD		7
+
+/* Max registers */
+#define GSC_HWMON_MAX_REG	56
+
+struct gsc_dev {
+	struct device *dev;
+
+	struct i2c_client *i2c;		/* 0x20: interrupt controller, WDT */
+	struct i2c_client *i2c_hwmon;	/* 0x29: hwmon, fan controller */
+
+	struct regmap *regmap;
+	struct regmap *regmap_hwmon;
+	struct regmap_irq_chip_data *irq_chip_data;
+
+	int irq;
+	unsigned int fwver;
+	unsigned short fwcrc;
+};
+
+#endif /* __LINUX_MFD_GSC_H_ */
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 3/4] hwmon: add Gateworks System Controller support
  2018-03-28 15:13 ` Tim Harvey
@ 2018-03-28 15:14   ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog

The Gateworks System Controller has a hwmon sub-component that exposes
up to 16 ADC's, some of which are temperature sensors, others which are
voltage inputs. The ADC configuration (register mapping and name) is
configured via device-tree and varies board to board.

Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
- add voltage_raw input type and supporting fields
- add channel validation to is_visible function
- remove unnecessary channel validation from read/write functions

v2:
- change license comment style
- remove DEBUG
- simplify regmap_bulk_read err check
- remove break after returns in switch statement
- fix fan setpoint buffer address
- remove unnecessary parens
- consistently use struct device *dev pointer
- change license/comment block
- add validation for hwmon child node props
- move parsing of of to own function
- use strlcpy to ensure null termination
- fix static array sizes and removed unnecessary initializers
- dynamically allocate channels
- fix fan input label
- support platform data
- fixed whitespace issues

 drivers/hwmon/Kconfig                   |   9 +
 drivers/hwmon/Makefile                  |   1 +
 drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
 include/linux/platform_data/gsc_hwmon.h |  43 ++++
 4 files changed, 421 insertions(+)
 create mode 100644 drivers/hwmon/gsc-hwmon.c
 create mode 100644 include/linux/platform_data/gsc_hwmon.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 7ad0176..85d9aa3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -475,6 +475,15 @@ config SENSORS_F75375S
 	  This driver can also be built as a module.  If so, the module
 	  will be called f75375s.
 
+config SENSORS_GSC
+        tristate "Gateworks System Controller ADC"
+        depends on MFD_GATEWORKS_GSC
+        help
+          Support for the Gateworks System Controller A/D converters.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called gsc-hwmon.
+
 config SENSORS_MC13783_ADC
         tristate "Freescale MC13783/MC13892 ADC"
         depends on MFD_MC13XXX
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0fe489f..835a536 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
 obj-$(CONFIG_SENSORS_G762)	+= g762.o
 obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
+obj-$(CONFIG_SENSORS_GSC)	+= gsc-hwmon.o
 obj-$(CONFIG_SENSORS_GPIO_FAN)	+= gpio-fan.o
 obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130.o
 obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c
new file mode 100644
index 0000000..10da46f
--- /dev/null
+++ b/drivers/hwmon/gsc-hwmon.c
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * This driver registers Linux HWMON attributes for GSC ADC's
+ */
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <linux/platform_data/gsc_hwmon.h>
+
+#define GSC_HWMON_MAX_TEMP_CH	16
+#define GSC_HWMON_MAX_IN_CH	16
+#define GSC_HWMON_MAX_FAN_CH	6
+
+struct gsc_hwmon_data {
+	struct gsc_dev *gsc;
+	struct device *dev;
+	struct gsc_hwmon_platform_data *pdata;
+	const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH];
+	const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH];
+	const struct gsc_hwmon_channel *fan_ch[GSC_HWMON_MAX_FAN_CH];
+	u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1];
+	u32 in_config[GSC_HWMON_MAX_IN_CH + 1];
+	u32 fan_config[GSC_HWMON_MAX_FAN_CH + 1];
+	struct hwmon_channel_info temp_info;
+	struct hwmon_channel_info in_info;
+	struct hwmon_channel_info fan_info;
+	const struct hwmon_channel_info *info[4];
+	struct hwmon_chip_info chip;
+	int vreference;
+	int resolution;
+};
+
+static int
+gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+	       int channel, long *val)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+	struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
+	const struct gsc_hwmon_channel *ch;
+	int sz, ret;
+	u8 buf[3];
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_in:
+		ch = hwmon->in_ch[channel];
+		break;
+	case hwmon_temp:
+		ch = hwmon->temp_ch[channel];
+		break;
+	case hwmon_fan:
+		ch = hwmon->fan_ch[channel];
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	sz = (ch->type == type_voltage) ? 3 : 2;
+	ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
+	if (ret)
+		return ret;
+
+	*val = 0;
+	while (sz-- > 0)
+		*val |= (buf[sz] << (8*sz));
+
+	switch (ch->type) {
+	case type_temperature:
+		if ((type == hwmon_temp) && *val > 0x8000)
+			*val -= 0xffff;
+		break;
+	case type_voltage_raw:
+		/* scale based on ref voltage and resolution */
+		if (pdata->vreference && pdata->resolution) {
+			*val *= pdata->vreference;
+			*val /= pdata->resolution;
+		}
+		/* scale based on optional voltage divider */
+		if (ch->vdiv[0] && ch->vdiv[1]) {
+			*val *= (ch->vdiv[0] + ch->vdiv[1]);
+			*val /= ch->vdiv[1];
+		}
+		/* adjust by offset */
+		*val += ch->voffset;
+		break;
+	}
+
+	return 0;
+}
+
+static int
+gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+		      u32 attr, int channel, const char **buf)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_in:
+		*buf = hwmon->in_ch[channel]->name;
+		break;
+	case hwmon_temp:
+		*buf = hwmon->temp_ch[channel]->name;
+		break;
+	case hwmon_fan:
+		*buf = hwmon->fan_ch[channel]->name;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static int
+gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+		int channel, long val)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+	u8 buf[2];
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_fan:
+		buf[0] = val & 0xff;
+		buf[1] = (val >> 8) & 0xff;
+		return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
+					 hwmon->fan_ch[channel]->reg, buf, 2);
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static umode_t
+gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+		     int ch)
+{
+	const struct gsc_hwmon_data *hwmon = _data;
+	struct device *dev = hwmon->gsc->dev;
+	umode_t mode = 0;
+
+	switch (type) {
+	case hwmon_fan:
+		if (ch >= GSC_HWMON_MAX_FAN_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		if (attr == hwmon_fan_input)
+			mode |= S_IWUSR;
+		break;
+	case hwmon_temp:
+		if (ch >= GSC_HWMON_MAX_TEMP_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		break;
+	case hwmon_in:
+		if (ch >= GSC_HWMON_MAX_IN_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
+		attr, ch, mode);
+
+	return mode;
+}
+
+static const struct hwmon_ops gsc_hwmon_ops = {
+	.is_visible = gsc_hwmon_is_visible,
+	.read = gsc_hwmon_read,
+	.read_string = gsc_hwmon_read_string,
+	.write = gsc_hwmon_write,
+};
+
+static struct gsc_hwmon_platform_data *
+gsc_hwmon_get_devtree_pdata(struct device *dev)
+{
+	struct gsc_hwmon_platform_data *pdata;
+	struct gsc_hwmon_channel *ch;
+	struct fwnode_handle *child;
+	const char *type;
+	int nchannels;
+
+	nchannels = device_get_child_node_count(dev);
+	dev_dbg(dev, "channels=%d\n", nchannels);
+	if (nchannels == 0)
+		return ERR_PTR(-ENODEV);
+
+	pdata = devm_kzalloc(dev,
+			     sizeof(*pdata) + nchannels * sizeof(*ch),
+			     GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+	ch = (struct gsc_hwmon_channel *)(pdata + 1);
+	pdata->channels = ch;
+	pdata->nchannels = nchannels;
+
+	device_property_read_u32(dev, "gw,reference-voltage",
+				 &pdata->vreference);
+	device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
+
+	/* allocate structures for channels and count instances of each type */
+	device_for_each_child_node(dev, child) {
+		if (fwnode_property_read_string(child, "label", &ch->name)) {
+			dev_err(dev, "channel without label\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
+			dev_err(dev, "channel without reg\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (fwnode_property_read_string(child, "type", &type)) {
+			dev_err(dev, "channel without type\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (!strcasecmp(type, "gw,hwmon-temperature"))
+			ch->type = type_temperature;
+		else if (!strcasecmp(type, "gw,hwmon-voltage"))
+			ch->type = type_voltage;
+		else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
+			ch->type = type_voltage_raw;
+		else if (!strcasecmp(type, "gw,hwmon-fan"))
+			ch->type = type_fan;
+		else {
+			dev_err(dev, "channel without type\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+
+		fwnode_property_read_u32(child, "gw,voltage-offset",
+			&ch->voffset);
+		fwnode_property_read_u32_array(child, "gw,voltage-divider",
+			ch->vdiv, ARRAY_SIZE(ch->vdiv));
+		dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
+			ch->name);
+		ch++;
+	}
+
+	return pdata;
+}
+
+static int gsc_hwmon_probe(struct platform_device *pdev)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
+	struct gsc_hwmon_data *hwmon;
+	int i, i_in, i_temp, i_fan;
+
+	if (!pdata) {
+		pdata = gsc_hwmon_get_devtree_pdata(dev);
+		if (IS_ERR(pdata))
+			return PTR_ERR(pdata);
+	}
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+	hwmon->gsc = gsc;
+	hwmon->pdata = pdata;
+
+	for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
+	     i < hwmon->pdata->nchannels; i++) {
+		const struct gsc_hwmon_channel *ch = &pdata->channels[i];
+
+		if (ch->reg > GSC_HWMON_MAX_REG) {
+			dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
+			return -EINVAL;
+		}
+		switch (ch->type) {
+		case type_temperature:
+			if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
+				dev_err(dev, "too many temp channels\n");
+				return -EINVAL;
+			}
+			hwmon->temp_ch[i_temp] = ch;
+			hwmon->temp_config[i_temp] = HWMON_T_INPUT |
+						     HWMON_T_LABEL;
+			i_temp++;
+			break;
+		case type_voltage:
+		case type_voltage_raw:
+			if (i_in == GSC_HWMON_MAX_IN_CH) {
+				dev_err(dev, "too many voltage channels\n");
+				return -EINVAL;
+			}
+			hwmon->in_ch[i_in] = ch;
+			hwmon->in_config[i_in] =
+				HWMON_I_INPUT | HWMON_I_LABEL;
+			i_in++;
+			break;
+		case type_fan:
+			if (i_fan == GSC_HWMON_MAX_FAN_CH) {
+				dev_err(dev, "too many voltage channels\n");
+				return -EINVAL;
+			}
+			hwmon->fan_ch[i_fan] = ch;
+			hwmon->fan_config[i_fan] =
+				HWMON_F_INPUT | HWMON_F_LABEL;
+			i_fan++;
+			break;
+		default:
+			dev_err(dev, "invalid type: %d\n", ch->type);
+			return -EINVAL;
+		}
+		dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
+			ch->type, ch->name);
+	}
+
+	/* terminate channel config lists */
+	hwmon->temp_config[i_temp] = 0;
+	hwmon->in_config[i_in] = 0;
+	hwmon->fan_config[i_fan] = 0;
+
+	/* setup config structures */
+	hwmon->chip.ops = &gsc_hwmon_ops;
+	hwmon->chip.info = hwmon->info;
+	hwmon->info[0] = &hwmon->temp_info;
+	hwmon->info[1] = &hwmon->in_info;
+	hwmon->info[2] = &hwmon->fan_info;
+	hwmon->temp_info.type = hwmon_temp;
+	hwmon->temp_info.config = hwmon->temp_config;
+	hwmon->in_info.type = hwmon_in;
+	hwmon->in_info.config = hwmon->in_config;
+	hwmon->fan_info.type = hwmon_fan;
+	hwmon->fan_info.config = hwmon->fan_config;
+
+	hwmon->dev = devm_hwmon_device_register_with_info(dev,
+							  KBUILD_MODNAME, hwmon,
+							  &hwmon->chip, NULL);
+	return PTR_ERR_OR_ZERO(hwmon->dev);
+}
+
+static const struct of_device_id gsc_hwmon_of_match[] = {
+	{ .compatible = "gw,gsc-hwmon", },
+	{}
+};
+
+static struct platform_driver gsc_hwmon_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.of_match_table = gsc_hwmon_of_match,
+	},
+	.probe = gsc_hwmon_probe,
+};
+
+module_platform_driver(gsc_hwmon_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("GSC hardware monitor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
new file mode 100644
index 0000000..5e59846
--- /dev/null
+++ b/include/linux/platform_data/gsc_hwmon.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _GSC_HWMON_H
+#define _GSC_HWMON_H
+
+enum gsc_hwmon_type {
+	type_temperature,
+	type_voltage,
+	type_voltage_raw,
+	type_fan,
+};
+
+/**
+ * struct gsc_hwmon_channel - configuration parameters
+ * @reg:  I2C register offset
+ * @type: channel type
+ * @name: channel name
+ * @voffset: voltage offset (mV)
+ * @vdiv: voltage divider array (2 resistor values in ohms)
+ */
+struct gsc_hwmon_channel {
+	unsigned int reg;
+	unsigned int type;
+	const char *name;
+	unsigned int voffset;
+	unsigned int vdiv[2];
+};
+
+/**
+ * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
+ * @channels:	pointer to array of gsc_hwmon_channel structures
+ *		describing channels
+ * @nchannels:	number of elements in @channels array
+ * @vreference: voltage reference (mV)
+ * @resolution: ADC resolution
+ */
+struct gsc_hwmon_platform_data {
+	const struct gsc_hwmon_channel *channels;
+	int nchannels;
+	unsigned int resolution;
+	unsigned int vreference;
+};
+
+#endif
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 3/4] hwmon: add Gateworks System Controller support
@ 2018-03-28 15:14   ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: linux-arm-kernel

The Gateworks System Controller has a hwmon sub-component that exposes
up to 16 ADC's, some of which are temperature sensors, others which are
voltage inputs. The ADC configuration (register mapping and name) is
configured via device-tree and varies board to board.

Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
v3:
- add voltage_raw input type and supporting fields
- add channel validation to is_visible function
- remove unnecessary channel validation from read/write functions

v2:
- change license comment style
- remove DEBUG
- simplify regmap_bulk_read err check
- remove break after returns in switch statement
- fix fan setpoint buffer address
- remove unnecessary parens
- consistently use struct device *dev pointer
- change license/comment block
- add validation for hwmon child node props
- move parsing of of to own function
- use strlcpy to ensure null termination
- fix static array sizes and removed unnecessary initializers
- dynamically allocate channels
- fix fan input label
- support platform data
- fixed whitespace issues

 drivers/hwmon/Kconfig                   |   9 +
 drivers/hwmon/Makefile                  |   1 +
 drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
 include/linux/platform_data/gsc_hwmon.h |  43 ++++
 4 files changed, 421 insertions(+)
 create mode 100644 drivers/hwmon/gsc-hwmon.c
 create mode 100644 include/linux/platform_data/gsc_hwmon.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 7ad0176..85d9aa3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -475,6 +475,15 @@ config SENSORS_F75375S
 	  This driver can also be built as a module.  If so, the module
 	  will be called f75375s.
 
+config SENSORS_GSC
+        tristate "Gateworks System Controller ADC"
+        depends on MFD_GATEWORKS_GSC
+        help
+          Support for the Gateworks System Controller A/D converters.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called gsc-hwmon.
+
 config SENSORS_MC13783_ADC
         tristate "Freescale MC13783/MC13892 ADC"
         depends on MFD_MC13XXX
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0fe489f..835a536 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
 obj-$(CONFIG_SENSORS_G762)	+= g762.o
 obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
+obj-$(CONFIG_SENSORS_GSC)	+= gsc-hwmon.o
 obj-$(CONFIG_SENSORS_GPIO_FAN)	+= gpio-fan.o
 obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130.o
 obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c
new file mode 100644
index 0000000..10da46f
--- /dev/null
+++ b/drivers/hwmon/gsc-hwmon.c
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * This driver registers Linux HWMON attributes for GSC ADC's
+ */
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <linux/platform_data/gsc_hwmon.h>
+
+#define GSC_HWMON_MAX_TEMP_CH	16
+#define GSC_HWMON_MAX_IN_CH	16
+#define GSC_HWMON_MAX_FAN_CH	6
+
+struct gsc_hwmon_data {
+	struct gsc_dev *gsc;
+	struct device *dev;
+	struct gsc_hwmon_platform_data *pdata;
+	const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH];
+	const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH];
+	const struct gsc_hwmon_channel *fan_ch[GSC_HWMON_MAX_FAN_CH];
+	u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1];
+	u32 in_config[GSC_HWMON_MAX_IN_CH + 1];
+	u32 fan_config[GSC_HWMON_MAX_FAN_CH + 1];
+	struct hwmon_channel_info temp_info;
+	struct hwmon_channel_info in_info;
+	struct hwmon_channel_info fan_info;
+	const struct hwmon_channel_info *info[4];
+	struct hwmon_chip_info chip;
+	int vreference;
+	int resolution;
+};
+
+static int
+gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+	       int channel, long *val)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+	struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
+	const struct gsc_hwmon_channel *ch;
+	int sz, ret;
+	u8 buf[3];
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_in:
+		ch = hwmon->in_ch[channel];
+		break;
+	case hwmon_temp:
+		ch = hwmon->temp_ch[channel];
+		break;
+	case hwmon_fan:
+		ch = hwmon->fan_ch[channel];
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	sz = (ch->type == type_voltage) ? 3 : 2;
+	ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
+	if (ret)
+		return ret;
+
+	*val = 0;
+	while (sz-- > 0)
+		*val |= (buf[sz] << (8*sz));
+
+	switch (ch->type) {
+	case type_temperature:
+		if ((type == hwmon_temp) && *val > 0x8000)
+			*val -= 0xffff;
+		break;
+	case type_voltage_raw:
+		/* scale based on ref voltage and resolution */
+		if (pdata->vreference && pdata->resolution) {
+			*val *= pdata->vreference;
+			*val /= pdata->resolution;
+		}
+		/* scale based on optional voltage divider */
+		if (ch->vdiv[0] && ch->vdiv[1]) {
+			*val *= (ch->vdiv[0] + ch->vdiv[1]);
+			*val /= ch->vdiv[1];
+		}
+		/* adjust by offset */
+		*val += ch->voffset;
+		break;
+	}
+
+	return 0;
+}
+
+static int
+gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+		      u32 attr, int channel, const char **buf)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_in:
+		*buf = hwmon->in_ch[channel]->name;
+		break;
+	case hwmon_temp:
+		*buf = hwmon->temp_ch[channel]->name;
+		break;
+	case hwmon_fan:
+		*buf = hwmon->fan_ch[channel]->name;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static int
+gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+		int channel, long val)
+{
+	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
+	u8 buf[2];
+
+	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
+		channel);
+	switch (type) {
+	case hwmon_fan:
+		buf[0] = val & 0xff;
+		buf[1] = (val >> 8) & 0xff;
+		return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
+					 hwmon->fan_ch[channel]->reg, buf, 2);
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static umode_t
+gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+		     int ch)
+{
+	const struct gsc_hwmon_data *hwmon = _data;
+	struct device *dev = hwmon->gsc->dev;
+	umode_t mode = 0;
+
+	switch (type) {
+	case hwmon_fan:
+		if (ch >= GSC_HWMON_MAX_FAN_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		if (attr == hwmon_fan_input)
+			mode |= S_IWUSR;
+		break;
+	case hwmon_temp:
+		if (ch >= GSC_HWMON_MAX_TEMP_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		break;
+	case hwmon_in:
+		if (ch >= GSC_HWMON_MAX_IN_CH)
+			return -EOPNOTSUPP;
+		mode = S_IRUGO;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
+		attr, ch, mode);
+
+	return mode;
+}
+
+static const struct hwmon_ops gsc_hwmon_ops = {
+	.is_visible = gsc_hwmon_is_visible,
+	.read = gsc_hwmon_read,
+	.read_string = gsc_hwmon_read_string,
+	.write = gsc_hwmon_write,
+};
+
+static struct gsc_hwmon_platform_data *
+gsc_hwmon_get_devtree_pdata(struct device *dev)
+{
+	struct gsc_hwmon_platform_data *pdata;
+	struct gsc_hwmon_channel *ch;
+	struct fwnode_handle *child;
+	const char *type;
+	int nchannels;
+
+	nchannels = device_get_child_node_count(dev);
+	dev_dbg(dev, "channels=%d\n", nchannels);
+	if (nchannels == 0)
+		return ERR_PTR(-ENODEV);
+
+	pdata = devm_kzalloc(dev,
+			     sizeof(*pdata) + nchannels * sizeof(*ch),
+			     GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+	ch = (struct gsc_hwmon_channel *)(pdata + 1);
+	pdata->channels = ch;
+	pdata->nchannels = nchannels;
+
+	device_property_read_u32(dev, "gw,reference-voltage",
+				 &pdata->vreference);
+	device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
+
+	/* allocate structures for channels and count instances of each type */
+	device_for_each_child_node(dev, child) {
+		if (fwnode_property_read_string(child, "label", &ch->name)) {
+			dev_err(dev, "channel without label\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
+			dev_err(dev, "channel without reg\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (fwnode_property_read_string(child, "type", &type)) {
+			dev_err(dev, "channel without type\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+		if (!strcasecmp(type, "gw,hwmon-temperature"))
+			ch->type = type_temperature;
+		else if (!strcasecmp(type, "gw,hwmon-voltage"))
+			ch->type = type_voltage;
+		else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
+			ch->type = type_voltage_raw;
+		else if (!strcasecmp(type, "gw,hwmon-fan"))
+			ch->type = type_fan;
+		else {
+			dev_err(dev, "channel without type\n");
+			fwnode_handle_put(child);
+			return ERR_PTR(-EINVAL);
+		}
+
+		fwnode_property_read_u32(child, "gw,voltage-offset",
+			&ch->voffset);
+		fwnode_property_read_u32_array(child, "gw,voltage-divider",
+			ch->vdiv, ARRAY_SIZE(ch->vdiv));
+		dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
+			ch->name);
+		ch++;
+	}
+
+	return pdata;
+}
+
+static int gsc_hwmon_probe(struct platform_device *pdev)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
+	struct gsc_hwmon_data *hwmon;
+	int i, i_in, i_temp, i_fan;
+
+	if (!pdata) {
+		pdata = gsc_hwmon_get_devtree_pdata(dev);
+		if (IS_ERR(pdata))
+			return PTR_ERR(pdata);
+	}
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+	hwmon->gsc = gsc;
+	hwmon->pdata = pdata;
+
+	for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
+	     i < hwmon->pdata->nchannels; i++) {
+		const struct gsc_hwmon_channel *ch = &pdata->channels[i];
+
+		if (ch->reg > GSC_HWMON_MAX_REG) {
+			dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
+			return -EINVAL;
+		}
+		switch (ch->type) {
+		case type_temperature:
+			if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
+				dev_err(dev, "too many temp channels\n");
+				return -EINVAL;
+			}
+			hwmon->temp_ch[i_temp] = ch;
+			hwmon->temp_config[i_temp] = HWMON_T_INPUT |
+						     HWMON_T_LABEL;
+			i_temp++;
+			break;
+		case type_voltage:
+		case type_voltage_raw:
+			if (i_in == GSC_HWMON_MAX_IN_CH) {
+				dev_err(dev, "too many voltage channels\n");
+				return -EINVAL;
+			}
+			hwmon->in_ch[i_in] = ch;
+			hwmon->in_config[i_in] =
+				HWMON_I_INPUT | HWMON_I_LABEL;
+			i_in++;
+			break;
+		case type_fan:
+			if (i_fan == GSC_HWMON_MAX_FAN_CH) {
+				dev_err(dev, "too many voltage channels\n");
+				return -EINVAL;
+			}
+			hwmon->fan_ch[i_fan] = ch;
+			hwmon->fan_config[i_fan] =
+				HWMON_F_INPUT | HWMON_F_LABEL;
+			i_fan++;
+			break;
+		default:
+			dev_err(dev, "invalid type: %d\n", ch->type);
+			return -EINVAL;
+		}
+		dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
+			ch->type, ch->name);
+	}
+
+	/* terminate channel config lists */
+	hwmon->temp_config[i_temp] = 0;
+	hwmon->in_config[i_in] = 0;
+	hwmon->fan_config[i_fan] = 0;
+
+	/* setup config structures */
+	hwmon->chip.ops = &gsc_hwmon_ops;
+	hwmon->chip.info = hwmon->info;
+	hwmon->info[0] = &hwmon->temp_info;
+	hwmon->info[1] = &hwmon->in_info;
+	hwmon->info[2] = &hwmon->fan_info;
+	hwmon->temp_info.type = hwmon_temp;
+	hwmon->temp_info.config = hwmon->temp_config;
+	hwmon->in_info.type = hwmon_in;
+	hwmon->in_info.config = hwmon->in_config;
+	hwmon->fan_info.type = hwmon_fan;
+	hwmon->fan_info.config = hwmon->fan_config;
+
+	hwmon->dev = devm_hwmon_device_register_with_info(dev,
+							  KBUILD_MODNAME, hwmon,
+							  &hwmon->chip, NULL);
+	return PTR_ERR_OR_ZERO(hwmon->dev);
+}
+
+static const struct of_device_id gsc_hwmon_of_match[] = {
+	{ .compatible = "gw,gsc-hwmon", },
+	{}
+};
+
+static struct platform_driver gsc_hwmon_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.of_match_table = gsc_hwmon_of_match,
+	},
+	.probe = gsc_hwmon_probe,
+};
+
+module_platform_driver(gsc_hwmon_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("GSC hardware monitor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
new file mode 100644
index 0000000..5e59846
--- /dev/null
+++ b/include/linux/platform_data/gsc_hwmon.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _GSC_HWMON_H
+#define _GSC_HWMON_H
+
+enum gsc_hwmon_type {
+	type_temperature,
+	type_voltage,
+	type_voltage_raw,
+	type_fan,
+};
+
+/**
+ * struct gsc_hwmon_channel - configuration parameters
+ * @reg:  I2C register offset
+ * @type: channel type
+ * @name: channel name
+ * @voffset: voltage offset (mV)
+ * @vdiv: voltage divider array (2 resistor values in ohms)
+ */
+struct gsc_hwmon_channel {
+	unsigned int reg;
+	unsigned int type;
+	const char *name;
+	unsigned int voffset;
+	unsigned int vdiv[2];
+};
+
+/**
+ * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
+ * @channels:	pointer to array of gsc_hwmon_channel structures
+ *		describing channels
+ * @nchannels:	number of elements in @channels array
+ * @vreference: voltage reference (mV)
+ * @resolution: ADC resolution
+ */
+struct gsc_hwmon_platform_data {
+	const struct gsc_hwmon_channel *channels;
+	int nchannels;
+	unsigned int resolution;
+	unsigned int vreference;
+};
+
+#endif
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 4/4] watchdog: add Gateworks System Controller support
  2018-03-28 15:13 ` Tim Harvey
@ 2018-03-28 15:14   ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
 drivers/watchdog/Kconfig   |  10 ++++
 drivers/watchdog/Makefile  |   1 +
 drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 157 insertions(+)
 create mode 100644 drivers/watchdog/gsc_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index ca200d1..c9d4b2e 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
 	  arch_initcall.
 	  If in doubt, say N.
 
+config GSC_WATCHDOG
+	tristate "Gateworks System Controller (GSC) Watchdog support"
+	depends on MFD_GATEWORKS_GSC
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include support for the GSC Watchdog.
+
+	  This driver can also be built as a module. If so the module
+	  will be called gsc_wdt.
+
 config MENF21BMC_WATCHDOG
 	tristate "MEN 14F021P00 BMC Watchdog"
 	depends on MFD_MENF21BMC || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 715a210..499327e 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
 obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
 obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
 obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
+obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
 obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
 obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
 obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
new file mode 100644
index 0000000..b43d083
--- /dev/null
+++ b/drivers/watchdog/gsc_wdt.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * This driver registers a Linux Watchdog for the GSC
+ */
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT	60
+
+struct gsc_wdt {
+	struct watchdog_device wdt_dev;
+	struct gsc_dev *gsc;
+};
+
+static int gsc_wdt_start(struct watchdog_device *wdd)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
+	int ret;
+
+	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);
+
+	/* clear first as regmap_update_bits will not write if no change */
+	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
+	if (ret)
+		return ret;
+	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
+}
+
+static int gsc_wdt_stop(struct watchdog_device *wdd)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
+
+	dev_dbg(wdd->parent, "%s\n", __func__);
+
+	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
+}
+
+static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
+			       unsigned int timeout)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int long_sel = 0;
+
+	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
+
+	switch (timeout) {
+	case 60:
+		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
+	case 30:
+		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
+				   (1 << GSC_CTRL_1_WDT_TIME),
+				   (long_sel << GSC_CTRL_1_WDT_TIME));
+		wdd->timeout = timeout;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct watchdog_info gsc_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "GSC Watchdog"
+};
+
+static const struct watchdog_ops gsc_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= gsc_wdt_start,
+	.stop		= gsc_wdt_stop,
+	.set_timeout	= gsc_wdt_set_timeout,
+};
+
+static int gsc_wdt_probe(struct platform_device *pdev)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	struct gsc_wdt *wdt;
+	int ret;
+	unsigned int reg;
+
+	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+	if (!wdt)
+		return -ENOMEM;
+
+	/* ensure GSC fw supports WD functionality */
+	if (gsc->fwver < 44) {
+		dev_err(dev, "fw v44 or newer required for wdt function\n");
+		return -EINVAL;
+	}
+
+	/* ensure WD bit enabled */
+	if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
+		return -EIO;
+	if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
+		dev_err(dev, "not enabled - must be manually enabled\n");
+		return -EINVAL;
+	}
+
+	platform_set_drvdata(pdev, wdt);
+
+	wdt->gsc = gsc;
+	wdt->wdt_dev.info = &gsc_wdt_info;
+	wdt->wdt_dev.ops = &gsc_wdt_ops;
+	wdt->wdt_dev.status = 0;
+	wdt->wdt_dev.min_timeout = 30;
+	wdt->wdt_dev.max_timeout = 60;
+	wdt->wdt_dev.parent = dev;
+
+	watchdog_set_nowayout(&wdt->wdt_dev, 1);
+	watchdog_init_timeout(&wdt->wdt_dev, WDT_DEFAULT_TIMEOUT, dev);
+
+	watchdog_set_drvdata(&wdt->wdt_dev, wdt);
+	ret = devm_watchdog_register_device(dev, &wdt->wdt_dev);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "watchdog driver (timeout=%d sec)\n",
+		 wdt->wdt_dev.timeout);
+
+	return 0;
+}
+
+static const struct of_device_id gsc_wdt_dt_ids[] = {
+	{ .compatible = "gw,gsc-watchdog", },
+	{}
+};
+
+static struct platform_driver gsc_wdt_driver = {
+	.probe		= gsc_wdt_probe,
+	.driver		= {
+		.name	= "gsc-wdt",
+		.of_match_table = gsc_wdt_dt_ids,
+	},
+};
+
+module_platform_driver(gsc_wdt_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("Gateworks System Controller Watchdog driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [PATCH v3 4/4] watchdog: add Gateworks System Controller support
@ 2018-03-28 15:14   ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 15:14 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
 drivers/watchdog/Kconfig   |  10 ++++
 drivers/watchdog/Makefile  |   1 +
 drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 157 insertions(+)
 create mode 100644 drivers/watchdog/gsc_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index ca200d1..c9d4b2e 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
 	  arch_initcall.
 	  If in doubt, say N.
 
+config GSC_WATCHDOG
+	tristate "Gateworks System Controller (GSC) Watchdog support"
+	depends on MFD_GATEWORKS_GSC
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include support for the GSC Watchdog.
+
+	  This driver can also be built as a module. If so the module
+	  will be called gsc_wdt.
+
 config MENF21BMC_WATCHDOG
 	tristate "MEN 14F021P00 BMC Watchdog"
 	depends on MFD_MENF21BMC || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 715a210..499327e 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
 obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
 obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
 obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
+obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
 obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
 obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
 obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
new file mode 100644
index 0000000..b43d083
--- /dev/null
+++ b/drivers/watchdog/gsc_wdt.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 Gateworks Corporation
+ *
+ * This driver registers a Linux Watchdog for the GSC
+ */
+#include <linux/mfd/gsc.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT	60
+
+struct gsc_wdt {
+	struct watchdog_device wdt_dev;
+	struct gsc_dev *gsc;
+};
+
+static int gsc_wdt_start(struct watchdog_device *wdd)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
+	int ret;
+
+	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);
+
+	/* clear first as regmap_update_bits will not write if no change */
+	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
+	if (ret)
+		return ret;
+	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
+}
+
+static int gsc_wdt_stop(struct watchdog_device *wdd)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
+
+	dev_dbg(wdd->parent, "%s\n", __func__);
+
+	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
+}
+
+static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
+			       unsigned int timeout)
+{
+	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned int long_sel = 0;
+
+	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
+
+	switch (timeout) {
+	case 60:
+		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
+	case 30:
+		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
+				   (1 << GSC_CTRL_1_WDT_TIME),
+				   (long_sel << GSC_CTRL_1_WDT_TIME));
+		wdd->timeout = timeout;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct watchdog_info gsc_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "GSC Watchdog"
+};
+
+static const struct watchdog_ops gsc_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= gsc_wdt_start,
+	.stop		= gsc_wdt_stop,
+	.set_timeout	= gsc_wdt_set_timeout,
+};
+
+static int gsc_wdt_probe(struct platform_device *pdev)
+{
+	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	struct gsc_wdt *wdt;
+	int ret;
+	unsigned int reg;
+
+	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+	if (!wdt)
+		return -ENOMEM;
+
+	/* ensure GSC fw supports WD functionality */
+	if (gsc->fwver < 44) {
+		dev_err(dev, "fw v44 or newer required for wdt function\n");
+		return -EINVAL;
+	}
+
+	/* ensure WD bit enabled */
+	if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
+		return -EIO;
+	if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
+		dev_err(dev, "not enabled - must be manually enabled\n");
+		return -EINVAL;
+	}
+
+	platform_set_drvdata(pdev, wdt);
+
+	wdt->gsc = gsc;
+	wdt->wdt_dev.info = &gsc_wdt_info;
+	wdt->wdt_dev.ops = &gsc_wdt_ops;
+	wdt->wdt_dev.status = 0;
+	wdt->wdt_dev.min_timeout = 30;
+	wdt->wdt_dev.max_timeout = 60;
+	wdt->wdt_dev.parent = dev;
+
+	watchdog_set_nowayout(&wdt->wdt_dev, 1);
+	watchdog_init_timeout(&wdt->wdt_dev, WDT_DEFAULT_TIMEOUT, dev);
+
+	watchdog_set_drvdata(&wdt->wdt_dev, wdt);
+	ret = devm_watchdog_register_device(dev, &wdt->wdt_dev);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "watchdog driver (timeout=%d sec)\n",
+		 wdt->wdt_dev.timeout);
+
+	return 0;
+}
+
+static const struct of_device_id gsc_wdt_dt_ids[] = {
+	{ .compatible = "gw,gsc-watchdog", },
+	{}
+};
+
+static struct platform_driver gsc_wdt_driver = {
+	.probe		= gsc_wdt_probe,
+	.driver		= {
+		.name	= "gsc-wdt",
+		.of_match_table = gsc_wdt_dt_ids,
+	},
+};
+
+module_platform_driver(gsc_wdt_driver);
+
+MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
+MODULE_DESCRIPTION("Gateworks System Controller Watchdog driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 15:14   ` Tim Harvey
@ 2018-03-28 16:24     ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 16:24 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> This patch adds documentation of device-tree bindings for the
> Gateworks System Controller (GSC).
> 
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
> v3:
>  - replaced _ with -
>  - remove input bindings
>  - added full description of hwmon
>  - fix unit address of hwmon child nodes
> 
> ---
>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>  1 file changed, 135 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> 
> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> new file mode 100644
> index 0000000..8f530ed
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> @@ -0,0 +1,135 @@
> +Gateworks System Controller multi-function device
> +
> +The GSC is a Multifunction I2C slave device with the following submodules:
> +- WDT
> +- GPIO
> +- Pushbutton controller
> +- HWMON
> +
> +Required properties:
> +- compatible : Must be "gw,gsc"
> +- reg: I2C address of the device
> +- interrupts: interrupt triggered by GSC_IRQ# signal
> +- interrupt-parent: Interrupt controller GSC is connected to
> +- #interrupt-cells: should be <1>, index of the interrupt within the
> +  controller, in accordance with the "one cell" variant of
> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> +
> +Optional nodes:
> +* watchdog:
> +The GSC provides a Watchdog monitor which can power cycle the board's
> +primary power supply on most board models when tripped.
> +
> +Required watchdog properties:
> +- compatible: must be "gw,gsc-watchdog"
> +
> +* hwmon:
> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> +temperature and/or voltage monitoring.
> +
> +Required hwmon properties:
> +- compatible: must be "gw,gsc-hwmon"
> +

"hwmon" is a very Linux specific term. It might make sense to find a more
generic term.

> +Optional hwmon properties:
> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs

AFAIK devicetree likes to specify voltages in uV.

> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
> +

4096 what ?

> +Each hwmon child node defines an ADC input on the chip which the GSC may
> +report cooked values (ie temperature sensor based on thermister), raw values,
> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
> +setpoint.
> +
> +Required hwmon child properties:
> +- type: one of the following ADC types:
> +  "gw,hwmon-temperature" - reports temperature in C*10
> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
> +       vreference, resolution, and optional resistor divider
> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10

What is a "fan temperature setpoint" ?

> +- reg: offset of the ADC register
> +- label: name of the ADC input or FAN setpoint
> +
> +Optional hwmon child properties:
> +- gw,voltage-divider: An array of two integers containing the resistor
> +  values R1 and R2 of the optinal resistor divider on a raw ADC
> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
> +  compensate for a diode drop)
> +
> +Example:
> +
> +	gsc: gsc@20 {
> +		compatible = "gw,gsc";
> +		reg = <0x20>;
> +		interrupt-parent = <&gpio1>;
> +		interrupts = <4 GPIO_ACTIVE_LOW>;
> +		interrupt-controller;
> +		#interrupt-cells = <1>;
> +
> +		watchdog {
> +			compatible = "gw,gsc-watchdog";
> +		};
> +
> +		hwmon {
> +			compatible = "gw,gsc-hwmon";
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +			gw,reference-voltage = <2500>;
> +			gw,resolution = <4096>;
> +
> +			hwmon@0 { /* A0: Board Temperature */
> +				type = "gw,hwmon-temperature";
> +				reg = <0x00>;
> +				label = "temp";
> +			};
> +
> +			hwmon@2 { /* A1: Input Voltage (raw ADC) */
> +				type = "gw,hwmon-voltage-raw";
> +				reg = <0x02>;
> +				label = "vdd_vin";
> +				gw,voltage-divider = <22100 1000>;
> +				gw,voltage-offset = <800>;
> +			};
> +
> +			hwmon@b { /* A2: Battery voltage */
> +				type = "gw,hwmon-voltage";
> +				reg = <0x0b>;
> +				label = "vdd_bat";
> +			};
> +
> +			hwmon@2c { /* fan temperature setpoint for 50% duty */
> +				type = "gw,hwmon-fan";
> +				reg = <0x2c>;
> +				label = "fan_50p";
> +			};
> +
> +			hwmon@2e { /* fan1 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x2e>;
> +				label = "fan_60p";
> +			};
> +
> +			hwmon@30 { /* fan2 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x30>;
> +				label = "fan_70p";
> +			};
> +
> +			hwmon@32 { /* fan3 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x32>;
> +				label = "fan_80p";
> +			};
> +
> +			hwmon@34 { /* fan4 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x34>;
> +				label = "fan_90p";
> +			};
> +
> +			hwmon@36 { /* fan5 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x36>;
> +				label = "fan_100p";
> +			};

No idea what this is supposed to be doing, but whatever it is,
it appears to be wrong. I'll comment more on it in the hwmon driver.

Guenter

> +		};
> +	};
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 16:24     ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 16:24 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> This patch adds documentation of device-tree bindings for the
> Gateworks System Controller (GSC).
> 
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
> v3:
>  - replaced _ with -
>  - remove input bindings
>  - added full description of hwmon
>  - fix unit address of hwmon child nodes
> 
> ---
>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>  1 file changed, 135 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> 
> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> new file mode 100644
> index 0000000..8f530ed
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> @@ -0,0 +1,135 @@
> +Gateworks System Controller multi-function device
> +
> +The GSC is a Multifunction I2C slave device with the following submodules:
> +- WDT
> +- GPIO
> +- Pushbutton controller
> +- HWMON
> +
> +Required properties:
> +- compatible : Must be "gw,gsc"
> +- reg: I2C address of the device
> +- interrupts: interrupt triggered by GSC_IRQ# signal
> +- interrupt-parent: Interrupt controller GSC is connected to
> +- #interrupt-cells: should be <1>, index of the interrupt within the
> +  controller, in accordance with the "one cell" variant of
> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> +
> +Optional nodes:
> +* watchdog:
> +The GSC provides a Watchdog monitor which can power cycle the board's
> +primary power supply on most board models when tripped.
> +
> +Required watchdog properties:
> +- compatible: must be "gw,gsc-watchdog"
> +
> +* hwmon:
> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> +temperature and/or voltage monitoring.
> +
> +Required hwmon properties:
> +- compatible: must be "gw,gsc-hwmon"
> +

"hwmon" is a very Linux specific term. It might make sense to find a more
generic term.

> +Optional hwmon properties:
> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs

AFAIK devicetree likes to specify voltages in uV.

> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
> +

4096 what ?

> +Each hwmon child node defines an ADC input on the chip which the GSC may
> +report cooked values (ie temperature sensor based on thermister), raw values,
> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
> +setpoint.
> +
> +Required hwmon child properties:
> +- type: one of the following ADC types:
> +  "gw,hwmon-temperature" - reports temperature in C*10
> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
> +       vreference, resolution, and optional resistor divider
> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10

What is a "fan temperature setpoint" ?

> +- reg: offset of the ADC register
> +- label: name of the ADC input or FAN setpoint
> +
> +Optional hwmon child properties:
> +- gw,voltage-divider: An array of two integers containing the resistor
> +  values R1 and R2 of the optinal resistor divider on a raw ADC
> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
> +  compensate for a diode drop)
> +
> +Example:
> +
> +	gsc: gsc at 20 {
> +		compatible = "gw,gsc";
> +		reg = <0x20>;
> +		interrupt-parent = <&gpio1>;
> +		interrupts = <4 GPIO_ACTIVE_LOW>;
> +		interrupt-controller;
> +		#interrupt-cells = <1>;
> +
> +		watchdog {
> +			compatible = "gw,gsc-watchdog";
> +		};
> +
> +		hwmon {
> +			compatible = "gw,gsc-hwmon";
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +			gw,reference-voltage = <2500>;
> +			gw,resolution = <4096>;
> +
> +			hwmon at 0 { /* A0: Board Temperature */
> +				type = "gw,hwmon-temperature";
> +				reg = <0x00>;
> +				label = "temp";
> +			};
> +
> +			hwmon at 2 { /* A1: Input Voltage (raw ADC) */
> +				type = "gw,hwmon-voltage-raw";
> +				reg = <0x02>;
> +				label = "vdd_vin";
> +				gw,voltage-divider = <22100 1000>;
> +				gw,voltage-offset = <800>;
> +			};
> +
> +			hwmon at b { /* A2: Battery voltage */
> +				type = "gw,hwmon-voltage";
> +				reg = <0x0b>;
> +				label = "vdd_bat";
> +			};
> +
> +			hwmon at 2c { /* fan temperature setpoint for 50% duty */
> +				type = "gw,hwmon-fan";
> +				reg = <0x2c>;
> +				label = "fan_50p";
> +			};
> +
> +			hwmon at 2e { /* fan1 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x2e>;
> +				label = "fan_60p";
> +			};
> +
> +			hwmon at 30 { /* fan2 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x30>;
> +				label = "fan_70p";
> +			};
> +
> +			hwmon at 32 { /* fan3 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x32>;
> +				label = "fan_80p";
> +			};
> +
> +			hwmon at 34 { /* fan4 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x34>;
> +				label = "fan_90p";
> +			};
> +
> +			hwmon at 36 { /* fan5 */
> +				type = "gw,hwmon-fan";
> +				reg = <0x36>;
> +				label = "fan_100p";
> +			};

No idea what this is supposed to be doing, but whatever it is,
it appears to be wrong. I'll comment more on it in the hwmon driver.

Guenter

> +		};
> +	};
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 3/4] hwmon: add Gateworks System Controller support
  2018-03-28 15:14   ` Tim Harvey
@ 2018-03-28 17:00     ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 17:00 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
> The Gateworks System Controller has a hwmon sub-component that exposes
> up to 16 ADC's, some of which are temperature sensors, others which are
> voltage inputs. The ADC configuration (register mapping and name) is
> configured via device-tree and varies board to board.
> 
> Cc: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
> v3:
> - add voltage_raw input type and supporting fields
> - add channel validation to is_visible function
> - remove unnecessary channel validation from read/write functions
> 
> v2:
> - change license comment style
> - remove DEBUG
> - simplify regmap_bulk_read err check
> - remove break after returns in switch statement
> - fix fan setpoint buffer address
> - remove unnecessary parens
> - consistently use struct device *dev pointer
> - change license/comment block
> - add validation for hwmon child node props
> - move parsing of of to own function
> - use strlcpy to ensure null termination
> - fix static array sizes and removed unnecessary initializers
> - dynamically allocate channels
> - fix fan input label
> - support platform data
> - fixed whitespace issues
> 
>  drivers/hwmon/Kconfig                   |   9 +
>  drivers/hwmon/Makefile                  |   1 +
>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++

This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
attributes.

>  include/linux/platform_data/gsc_hwmon.h |  43 ++++
>  4 files changed, 421 insertions(+)
>  create mode 100644 drivers/hwmon/gsc-hwmon.c
>  create mode 100644 include/linux/platform_data/gsc_hwmon.h
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 7ad0176..85d9aa3 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -475,6 +475,15 @@ config SENSORS_F75375S
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called f75375s.
>  
> +config SENSORS_GSC
> +        tristate "Gateworks System Controller ADC"
> +        depends on MFD_GATEWORKS_GSC
> +        help
> +          Support for the Gateworks System Controller A/D converters.
> +
> +	  To compile this driver as a module, choose M here:
> +	  the module will be called gsc-hwmon.
> +
>  config SENSORS_MC13783_ADC
>          tristate "Freescale MC13783/MC13892 ADC"
>          depends on MFD_MC13XXX
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0fe489f..835a536 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
>  obj-$(CONFIG_SENSORS_G762)	+= g762.o
>  obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
>  obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
> +obj-$(CONFIG_SENSORS_GSC)	+= gsc-hwmon.o
>  obj-$(CONFIG_SENSORS_GPIO_FAN)	+= gpio-fan.o
>  obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130.o
>  obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c
> new file mode 100644
> index 0000000..10da46f
> --- /dev/null
> +++ b/drivers/hwmon/gsc-hwmon.c
> @@ -0,0 +1,368 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Copyright (C) 2018 Gateworks Corporation
> + *
> + * This driver registers Linux HWMON attributes for GSC ADC's
> + */
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mfd/gsc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <linux/platform_data/gsc_hwmon.h>
> +
> +#define GSC_HWMON_MAX_TEMP_CH	16
> +#define GSC_HWMON_MAX_IN_CH	16
> +#define GSC_HWMON_MAX_FAN_CH	6
> +
> +struct gsc_hwmon_data {
> +	struct gsc_dev *gsc;
> +	struct device *dev;
> +	struct gsc_hwmon_platform_data *pdata;
> +	const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH];
> +	const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH];
> +	const struct gsc_hwmon_channel *fan_ch[GSC_HWMON_MAX_FAN_CH];
> +	u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1];
> +	u32 in_config[GSC_HWMON_MAX_IN_CH + 1];
> +	u32 fan_config[GSC_HWMON_MAX_FAN_CH + 1];
> +	struct hwmon_channel_info temp_info;
> +	struct hwmon_channel_info in_info;
> +	struct hwmon_channel_info fan_info;
> +	const struct hwmon_channel_info *info[4];
> +	struct hwmon_chip_info chip;
> +	int vreference;
> +	int resolution;
> +};
> +
> +static int
> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +	       int channel, long *val)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +	struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
> +	const struct gsc_hwmon_channel *ch;
> +	int sz, ret;
> +	u8 buf[3];
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);
> +	switch (type) {
> +	case hwmon_in:
> +		ch = hwmon->in_ch[channel];
> +		break;
> +	case hwmon_temp:
> +		ch = hwmon->temp_ch[channel];
> +		break;
> +	case hwmon_fan:
> +		ch = hwmon->fan_ch[channel];
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	sz = (ch->type == type_voltage) ? 3 : 2;
> +	ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
> +	if (ret)
> +		return ret;
> +
> +	*val = 0;
> +	while (sz-- > 0)
> +		*val |= (buf[sz] << (8*sz));

Please use spaces before and after operators.

> +
> +	switch (ch->type) {
> +	case type_temperature:
> +		if ((type == hwmon_temp) && *val > 0x8000)

Please no unnecessary ( ).

Is there ever a situation where ch->type == type_temperature and type !=
hwmon_temp ? Wouldn't that be a bug ?

> +			*val -= 0xffff;
> +		break;
> +	case type_voltage_raw:
> +		/* scale based on ref voltage and resolution */
> +		if (pdata->vreference && pdata->resolution) {
> +			*val *= pdata->vreference;
> +			*val /= pdata->resolution;
> +		}
> +		/* scale based on optional voltage divider */
> +		if (ch->vdiv[0] && ch->vdiv[1]) {
> +			*val *= (ch->vdiv[0] + ch->vdiv[1]);
> +			*val /= ch->vdiv[1];
> +		}

This accepts both types of scaling. Is that intentional ?

I don't see any protection against overflows. What if pdata->vreference is
larger than 256 on a system with sizeof(long) == 4 and the raw voltage
as reported by the chip is 0xffffff ?

> +		/* adjust by offset */
> +		*val += ch->voffset;

Similar to the above, this can result in an overflow on systems with
sizeof(long) == 4.

> +		break;

There should be a default case as well as 'case type_voltage:'
with a break; statement and a comment indicating that no adjustment
is needed.

> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
> +		      u32 attr, int channel, const char **buf)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);

I seriously wonder if all those dev_dbg() statements add value.

> +	switch (type) {
> +	case hwmon_in:
> +		*buf = hwmon->in_ch[channel]->name;
> +		break;
> +	case hwmon_temp:
> +		*buf = hwmon->temp_ch[channel]->name;
> +		break;
> +	case hwmon_fan:
> +		*buf = hwmon->fan_ch[channel]->name;
> +		break;
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +		int channel, long val)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +	u8 buf[2];
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);
> +	switch (type) {
> +	case hwmon_fan:
> +		buf[0] = val & 0xff;
> +		buf[1] = (val >> 8) & 0xff;
> +		return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
> +					 hwmon->fan_ch[channel]->reg, buf, 2);

fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
I have no idea what this is supposed to achieve, but whatever it is, it is wrong.

Please stick with the ABI.

> +	default:
> +		break;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static umode_t
> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> +		     int ch)
> +{
> +	const struct gsc_hwmon_data *hwmon = _data;
> +	struct device *dev = hwmon->gsc->dev;
> +	umode_t mode = 0;
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		if (ch >= GSC_HWMON_MAX_FAN_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		if (attr == hwmon_fan_input)
> +			mode |= S_IWUSR;

fanX_input is a read-only attribute per ABI.

> +		break;
> +	case hwmon_temp:
> +		if (ch >= GSC_HWMON_MAX_TEMP_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		break;
> +	case hwmon_in:
> +		if (ch >= GSC_HWMON_MAX_IN_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +	dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
> +		attr, ch, mode);
> +
> +	return mode;
> +}
> +
> +static const struct hwmon_ops gsc_hwmon_ops = {
> +	.is_visible = gsc_hwmon_is_visible,
> +	.read = gsc_hwmon_read,
> +	.read_string = gsc_hwmon_read_string,
> +	.write = gsc_hwmon_write,
> +};
> +
> +static struct gsc_hwmon_platform_data *
> +gsc_hwmon_get_devtree_pdata(struct device *dev)
> +{
> +	struct gsc_hwmon_platform_data *pdata;
> +	struct gsc_hwmon_channel *ch;
> +	struct fwnode_handle *child;
> +	const char *type;
> +	int nchannels;
> +
> +	nchannels = device_get_child_node_count(dev);
> +	dev_dbg(dev, "channels=%d\n", nchannels);
> +	if (nchannels == 0)
> +		return ERR_PTR(-ENODEV);
> +
> +	pdata = devm_kzalloc(dev,
> +			     sizeof(*pdata) + nchannels * sizeof(*ch),
> +			     GFP_KERNEL);
> +	if (!pdata)
> +		return ERR_PTR(-ENOMEM);
> +	ch = (struct gsc_hwmon_channel *)(pdata + 1);
> +	pdata->channels = ch;
> +	pdata->nchannels = nchannels;
> +
> +	device_property_read_u32(dev, "gw,reference-voltage",
> +				 &pdata->vreference);
> +	device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
> +
> +	/* allocate structures for channels and count instances of each type */
> +	device_for_each_child_node(dev, child) {
> +		if (fwnode_property_read_string(child, "label", &ch->name)) {
> +			dev_err(dev, "channel without label\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
> +			dev_err(dev, "channel without reg\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (fwnode_property_read_string(child, "type", &type)) {
> +			dev_err(dev, "channel without type\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (!strcasecmp(type, "gw,hwmon-temperature"))
> +			ch->type = type_temperature;
> +		else if (!strcasecmp(type, "gw,hwmon-voltage"))
> +			ch->type = type_voltage;
> +		else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
> +			ch->type = type_voltage_raw;
> +		else if (!strcasecmp(type, "gw,hwmon-fan"))
> +			ch->type = type_fan;
> +		else {
> +			dev_err(dev, "channel without type\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +
> +		fwnode_property_read_u32(child, "gw,voltage-offset",
> +			&ch->voffset);

Note that while it is technically ok to keep voltages internally in mV,
devicetree will likely require specificayion in uV. For accuracy, it might be
better to perform any calculations on that base and convert to mV for display
purposes.

> +		fwnode_property_read_u32_array(child, "gw,voltage-divider",
> +			ch->vdiv, ARRAY_SIZE(ch->vdiv));
> +		dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
> +			ch->name);
> +		ch++;
> +	}
> +
> +	return pdata;
> +}
> +
> +static int gsc_hwmon_probe(struct platform_device *pdev)
> +{
> +	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
> +	struct gsc_hwmon_data *hwmon;
> +	int i, i_in, i_temp, i_fan;
> +
> +	if (!pdata) {
> +		pdata = gsc_hwmon_get_devtree_pdata(dev);
> +		if (IS_ERR(pdata))
> +			return PTR_ERR(pdata);
> +	}
> +
> +	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> +	if (!hwmon)
> +		return -ENOMEM;
> +	hwmon->gsc = gsc;
> +	hwmon->pdata = pdata;
> +
> +	for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
> +	     i < hwmon->pdata->nchannels; i++) {
> +		const struct gsc_hwmon_channel *ch = &pdata->channels[i];
> +
> +		if (ch->reg > GSC_HWMON_MAX_REG) {
> +			dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
> +			return -EINVAL;
> +		}
> +		switch (ch->type) {
> +		case type_temperature:
> +			if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
> +				dev_err(dev, "too many temp channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->temp_ch[i_temp] = ch;
> +			hwmon->temp_config[i_temp] = HWMON_T_INPUT |
> +						     HWMON_T_LABEL;
> +			i_temp++;
> +			break;
> +		case type_voltage:
> +		case type_voltage_raw:
> +			if (i_in == GSC_HWMON_MAX_IN_CH) {
> +				dev_err(dev, "too many voltage channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->in_ch[i_in] = ch;
> +			hwmon->in_config[i_in] =
> +				HWMON_I_INPUT | HWMON_I_LABEL;
> +			i_in++;
> +			break;
> +		case type_fan:
> +			if (i_fan == GSC_HWMON_MAX_FAN_CH) {
> +				dev_err(dev, "too many voltage channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->fan_ch[i_fan] = ch;
> +			hwmon->fan_config[i_fan] =
> +				HWMON_F_INPUT | HWMON_F_LABEL;
> +			i_fan++;
> +			break;
> +		default:
> +			dev_err(dev, "invalid type: %d\n", ch->type);
> +			return -EINVAL;
> +		}
> +		dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
> +			ch->type, ch->name);
> +	}
> +
> +	/* terminate channel config lists */
> +	hwmon->temp_config[i_temp] = 0;
> +	hwmon->in_config[i_in] = 0;
> +	hwmon->fan_config[i_fan] = 0;

'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
is unnecessary.

> +
> +	/* setup config structures */
> +	hwmon->chip.ops = &gsc_hwmon_ops;
> +	hwmon->chip.info = hwmon->info;
> +	hwmon->info[0] = &hwmon->temp_info;
> +	hwmon->info[1] = &hwmon->in_info;
> +	hwmon->info[2] = &hwmon->fan_info;
> +	hwmon->temp_info.type = hwmon_temp;
> +	hwmon->temp_info.config = hwmon->temp_config;
> +	hwmon->in_info.type = hwmon_in;
> +	hwmon->in_info.config = hwmon->in_config;
> +	hwmon->fan_info.type = hwmon_fan;
> +	hwmon->fan_info.config = hwmon->fan_config;
> +
> +	hwmon->dev = devm_hwmon_device_register_with_info(dev,
> +							  KBUILD_MODNAME, hwmon,
> +							  &hwmon->chip, NULL);
> +	return PTR_ERR_OR_ZERO(hwmon->dev);
> +}
> +
> +static const struct of_device_id gsc_hwmon_of_match[] = {
> +	{ .compatible = "gw,gsc-hwmon", },
> +	{}
> +};
> +
> +static struct platform_driver gsc_hwmon_driver = {
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = gsc_hwmon_of_match,
> +	},
> +	.probe = gsc_hwmon_probe,
> +};
> +
> +module_platform_driver(gsc_hwmon_driver);
> +
> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> +MODULE_DESCRIPTION("GSC hardware monitor driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
> new file mode 100644
> index 0000000..5e59846
> --- /dev/null
> +++ b/include/linux/platform_data/gsc_hwmon.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _GSC_HWMON_H
> +#define _GSC_HWMON_H
> +
> +enum gsc_hwmon_type {
> +	type_temperature,
> +	type_voltage,
> +	type_voltage_raw,
> +	type_fan,
> +};
> +
> +/**
> + * struct gsc_hwmon_channel - configuration parameters
> + * @reg:  I2C register offset
> + * @type: channel type
> + * @name: channel name
> + * @voffset: voltage offset (mV)
> + * @vdiv: voltage divider array (2 resistor values in ohms)
> + */
> +struct gsc_hwmon_channel {
> +	unsigned int reg;
> +	unsigned int type;
> +	const char *name;
> +	unsigned int voffset;

It is interesting that only positive offset values are supported.
Is that intentional ?

> +	unsigned int vdiv[2];
> +};
> +
> +/**
> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
> + * @channels:	pointer to array of gsc_hwmon_channel structures
> + *		describing channels
> + * @nchannels:	number of elements in @channels array
> + * @vreference: voltage reference (mV)
> + * @resolution: ADC resolution

Resolution in what ?

> + */
> +struct gsc_hwmon_platform_data {
> +	const struct gsc_hwmon_channel *channels;
> +	int nchannels;
> +	unsigned int resolution;
> +	unsigned int vreference;
> +};
> +
> +#endif
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 3/4] hwmon: add Gateworks System Controller support
@ 2018-03-28 17:00     ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 17:00 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
> The Gateworks System Controller has a hwmon sub-component that exposes
> up to 16 ADC's, some of which are temperature sensors, others which are
> voltage inputs. The ADC configuration (register mapping and name) is
> configured via device-tree and varies board to board.
> 
> Cc: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
> v3:
> - add voltage_raw input type and supporting fields
> - add channel validation to is_visible function
> - remove unnecessary channel validation from read/write functions
> 
> v2:
> - change license comment style
> - remove DEBUG
> - simplify regmap_bulk_read err check
> - remove break after returns in switch statement
> - fix fan setpoint buffer address
> - remove unnecessary parens
> - consistently use struct device *dev pointer
> - change license/comment block
> - add validation for hwmon child node props
> - move parsing of of to own function
> - use strlcpy to ensure null termination
> - fix static array sizes and removed unnecessary initializers
> - dynamically allocate channels
> - fix fan input label
> - support platform data
> - fixed whitespace issues
> 
>  drivers/hwmon/Kconfig                   |   9 +
>  drivers/hwmon/Makefile                  |   1 +
>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++

This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
attributes.

>  include/linux/platform_data/gsc_hwmon.h |  43 ++++
>  4 files changed, 421 insertions(+)
>  create mode 100644 drivers/hwmon/gsc-hwmon.c
>  create mode 100644 include/linux/platform_data/gsc_hwmon.h
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 7ad0176..85d9aa3 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -475,6 +475,15 @@ config SENSORS_F75375S
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called f75375s.
>  
> +config SENSORS_GSC
> +        tristate "Gateworks System Controller ADC"
> +        depends on MFD_GATEWORKS_GSC
> +        help
> +          Support for the Gateworks System Controller A/D converters.
> +
> +	  To compile this driver as a module, choose M here:
> +	  the module will be called gsc-hwmon.
> +
>  config SENSORS_MC13783_ADC
>          tristate "Freescale MC13783/MC13892 ADC"
>          depends on MFD_MC13XXX
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0fe489f..835a536 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
>  obj-$(CONFIG_SENSORS_G762)	+= g762.o
>  obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
>  obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
> +obj-$(CONFIG_SENSORS_GSC)	+= gsc-hwmon.o
>  obj-$(CONFIG_SENSORS_GPIO_FAN)	+= gpio-fan.o
>  obj-$(CONFIG_SENSORS_HIH6130)	+= hih6130.o
>  obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c
> new file mode 100644
> index 0000000..10da46f
> --- /dev/null
> +++ b/drivers/hwmon/gsc-hwmon.c
> @@ -0,0 +1,368 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Copyright (C) 2018 Gateworks Corporation
> + *
> + * This driver registers Linux HWMON attributes for GSC ADC's
> + */
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mfd/gsc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <linux/platform_data/gsc_hwmon.h>
> +
> +#define GSC_HWMON_MAX_TEMP_CH	16
> +#define GSC_HWMON_MAX_IN_CH	16
> +#define GSC_HWMON_MAX_FAN_CH	6
> +
> +struct gsc_hwmon_data {
> +	struct gsc_dev *gsc;
> +	struct device *dev;
> +	struct gsc_hwmon_platform_data *pdata;
> +	const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH];
> +	const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH];
> +	const struct gsc_hwmon_channel *fan_ch[GSC_HWMON_MAX_FAN_CH];
> +	u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1];
> +	u32 in_config[GSC_HWMON_MAX_IN_CH + 1];
> +	u32 fan_config[GSC_HWMON_MAX_FAN_CH + 1];
> +	struct hwmon_channel_info temp_info;
> +	struct hwmon_channel_info in_info;
> +	struct hwmon_channel_info fan_info;
> +	const struct hwmon_channel_info *info[4];
> +	struct hwmon_chip_info chip;
> +	int vreference;
> +	int resolution;
> +};
> +
> +static int
> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +	       int channel, long *val)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +	struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
> +	const struct gsc_hwmon_channel *ch;
> +	int sz, ret;
> +	u8 buf[3];
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);
> +	switch (type) {
> +	case hwmon_in:
> +		ch = hwmon->in_ch[channel];
> +		break;
> +	case hwmon_temp:
> +		ch = hwmon->temp_ch[channel];
> +		break;
> +	case hwmon_fan:
> +		ch = hwmon->fan_ch[channel];
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	sz = (ch->type == type_voltage) ? 3 : 2;
> +	ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
> +	if (ret)
> +		return ret;
> +
> +	*val = 0;
> +	while (sz-- > 0)
> +		*val |= (buf[sz] << (8*sz));

Please use spaces before and after operators.

> +
> +	switch (ch->type) {
> +	case type_temperature:
> +		if ((type == hwmon_temp) && *val > 0x8000)

Please no unnecessary ( ).

Is there ever a situation where ch->type == type_temperature and type !=
hwmon_temp ? Wouldn't that be a bug ?

> +			*val -= 0xffff;
> +		break;
> +	case type_voltage_raw:
> +		/* scale based on ref voltage and resolution */
> +		if (pdata->vreference && pdata->resolution) {
> +			*val *= pdata->vreference;
> +			*val /= pdata->resolution;
> +		}
> +		/* scale based on optional voltage divider */
> +		if (ch->vdiv[0] && ch->vdiv[1]) {
> +			*val *= (ch->vdiv[0] + ch->vdiv[1]);
> +			*val /= ch->vdiv[1];
> +		}

This accepts both types of scaling. Is that intentional ?

I don't see any protection against overflows. What if pdata->vreference is
larger than 256 on a system with sizeof(long) == 4 and the raw voltage
as reported by the chip is 0xffffff ?

> +		/* adjust by offset */
> +		*val += ch->voffset;

Similar to the above, this can result in an overflow on systems with
sizeof(long) == 4.

> +		break;

There should be a default case as well as 'case type_voltage:'
with a break; statement and a comment indicating that no adjustment
is needed.

> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
> +		      u32 attr, int channel, const char **buf)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);

I seriously wonder if all those dev_dbg() statements add value.

> +	switch (type) {
> +	case hwmon_in:
> +		*buf = hwmon->in_ch[channel]->name;
> +		break;
> +	case hwmon_temp:
> +		*buf = hwmon->temp_ch[channel]->name;
> +		break;
> +	case hwmon_fan:
> +		*buf = hwmon->fan_ch[channel]->name;
> +		break;
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +		int channel, long val)
> +{
> +	struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> +	u8 buf[2];
> +
> +	dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> +		channel);
> +	switch (type) {
> +	case hwmon_fan:
> +		buf[0] = val & 0xff;
> +		buf[1] = (val >> 8) & 0xff;
> +		return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
> +					 hwmon->fan_ch[channel]->reg, buf, 2);

fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
I have no idea what this is supposed to achieve, but whatever it is, it is wrong.

Please stick with the ABI.

> +	default:
> +		break;
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static umode_t
> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> +		     int ch)
> +{
> +	const struct gsc_hwmon_data *hwmon = _data;
> +	struct device *dev = hwmon->gsc->dev;
> +	umode_t mode = 0;
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		if (ch >= GSC_HWMON_MAX_FAN_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		if (attr == hwmon_fan_input)
> +			mode |= S_IWUSR;

fanX_input is a read-only attribute per ABI.

> +		break;
> +	case hwmon_temp:
> +		if (ch >= GSC_HWMON_MAX_TEMP_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		break;
> +	case hwmon_in:
> +		if (ch >= GSC_HWMON_MAX_IN_CH)
> +			return -EOPNOTSUPP;

Can that ever happen ?

> +		mode = S_IRUGO;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +	dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
> +		attr, ch, mode);
> +
> +	return mode;
> +}
> +
> +static const struct hwmon_ops gsc_hwmon_ops = {
> +	.is_visible = gsc_hwmon_is_visible,
> +	.read = gsc_hwmon_read,
> +	.read_string = gsc_hwmon_read_string,
> +	.write = gsc_hwmon_write,
> +};
> +
> +static struct gsc_hwmon_platform_data *
> +gsc_hwmon_get_devtree_pdata(struct device *dev)
> +{
> +	struct gsc_hwmon_platform_data *pdata;
> +	struct gsc_hwmon_channel *ch;
> +	struct fwnode_handle *child;
> +	const char *type;
> +	int nchannels;
> +
> +	nchannels = device_get_child_node_count(dev);
> +	dev_dbg(dev, "channels=%d\n", nchannels);
> +	if (nchannels == 0)
> +		return ERR_PTR(-ENODEV);
> +
> +	pdata = devm_kzalloc(dev,
> +			     sizeof(*pdata) + nchannels * sizeof(*ch),
> +			     GFP_KERNEL);
> +	if (!pdata)
> +		return ERR_PTR(-ENOMEM);
> +	ch = (struct gsc_hwmon_channel *)(pdata + 1);
> +	pdata->channels = ch;
> +	pdata->nchannels = nchannels;
> +
> +	device_property_read_u32(dev, "gw,reference-voltage",
> +				 &pdata->vreference);
> +	device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
> +
> +	/* allocate structures for channels and count instances of each type */
> +	device_for_each_child_node(dev, child) {
> +		if (fwnode_property_read_string(child, "label", &ch->name)) {
> +			dev_err(dev, "channel without label\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
> +			dev_err(dev, "channel without reg\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (fwnode_property_read_string(child, "type", &type)) {
> +			dev_err(dev, "channel without type\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		if (!strcasecmp(type, "gw,hwmon-temperature"))
> +			ch->type = type_temperature;
> +		else if (!strcasecmp(type, "gw,hwmon-voltage"))
> +			ch->type = type_voltage;
> +		else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
> +			ch->type = type_voltage_raw;
> +		else if (!strcasecmp(type, "gw,hwmon-fan"))
> +			ch->type = type_fan;
> +		else {
> +			dev_err(dev, "channel without type\n");
> +			fwnode_handle_put(child);
> +			return ERR_PTR(-EINVAL);
> +		}
> +
> +		fwnode_property_read_u32(child, "gw,voltage-offset",
> +			&ch->voffset);

Note that while it is technically ok to keep voltages internally in mV,
devicetree will likely require specificayion in uV. For accuracy, it might be
better to perform any calculations on that base and convert to mV for display
purposes.

> +		fwnode_property_read_u32_array(child, "gw,voltage-divider",
> +			ch->vdiv, ARRAY_SIZE(ch->vdiv));
> +		dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
> +			ch->name);
> +		ch++;
> +	}
> +
> +	return pdata;
> +}
> +
> +static int gsc_hwmon_probe(struct platform_device *pdev)
> +{
> +	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
> +	struct gsc_hwmon_data *hwmon;
> +	int i, i_in, i_temp, i_fan;
> +
> +	if (!pdata) {
> +		pdata = gsc_hwmon_get_devtree_pdata(dev);
> +		if (IS_ERR(pdata))
> +			return PTR_ERR(pdata);
> +	}
> +
> +	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> +	if (!hwmon)
> +		return -ENOMEM;
> +	hwmon->gsc = gsc;
> +	hwmon->pdata = pdata;
> +
> +	for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
> +	     i < hwmon->pdata->nchannels; i++) {
> +		const struct gsc_hwmon_channel *ch = &pdata->channels[i];
> +
> +		if (ch->reg > GSC_HWMON_MAX_REG) {
> +			dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
> +			return -EINVAL;
> +		}
> +		switch (ch->type) {
> +		case type_temperature:
> +			if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
> +				dev_err(dev, "too many temp channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->temp_ch[i_temp] = ch;
> +			hwmon->temp_config[i_temp] = HWMON_T_INPUT |
> +						     HWMON_T_LABEL;
> +			i_temp++;
> +			break;
> +		case type_voltage:
> +		case type_voltage_raw:
> +			if (i_in == GSC_HWMON_MAX_IN_CH) {
> +				dev_err(dev, "too many voltage channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->in_ch[i_in] = ch;
> +			hwmon->in_config[i_in] =
> +				HWMON_I_INPUT | HWMON_I_LABEL;
> +			i_in++;
> +			break;
> +		case type_fan:
> +			if (i_fan == GSC_HWMON_MAX_FAN_CH) {
> +				dev_err(dev, "too many voltage channels\n");
> +				return -EINVAL;
> +			}
> +			hwmon->fan_ch[i_fan] = ch;
> +			hwmon->fan_config[i_fan] =
> +				HWMON_F_INPUT | HWMON_F_LABEL;
> +			i_fan++;
> +			break;
> +		default:
> +			dev_err(dev, "invalid type: %d\n", ch->type);
> +			return -EINVAL;
> +		}
> +		dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
> +			ch->type, ch->name);
> +	}
> +
> +	/* terminate channel config lists */
> +	hwmon->temp_config[i_temp] = 0;
> +	hwmon->in_config[i_in] = 0;
> +	hwmon->fan_config[i_fan] = 0;

'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
is unnecessary.

> +
> +	/* setup config structures */
> +	hwmon->chip.ops = &gsc_hwmon_ops;
> +	hwmon->chip.info = hwmon->info;
> +	hwmon->info[0] = &hwmon->temp_info;
> +	hwmon->info[1] = &hwmon->in_info;
> +	hwmon->info[2] = &hwmon->fan_info;
> +	hwmon->temp_info.type = hwmon_temp;
> +	hwmon->temp_info.config = hwmon->temp_config;
> +	hwmon->in_info.type = hwmon_in;
> +	hwmon->in_info.config = hwmon->in_config;
> +	hwmon->fan_info.type = hwmon_fan;
> +	hwmon->fan_info.config = hwmon->fan_config;
> +
> +	hwmon->dev = devm_hwmon_device_register_with_info(dev,
> +							  KBUILD_MODNAME, hwmon,
> +							  &hwmon->chip, NULL);
> +	return PTR_ERR_OR_ZERO(hwmon->dev);
> +}
> +
> +static const struct of_device_id gsc_hwmon_of_match[] = {
> +	{ .compatible = "gw,gsc-hwmon", },
> +	{}
> +};
> +
> +static struct platform_driver gsc_hwmon_driver = {
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = gsc_hwmon_of_match,
> +	},
> +	.probe = gsc_hwmon_probe,
> +};
> +
> +module_platform_driver(gsc_hwmon_driver);
> +
> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> +MODULE_DESCRIPTION("GSC hardware monitor driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
> new file mode 100644
> index 0000000..5e59846
> --- /dev/null
> +++ b/include/linux/platform_data/gsc_hwmon.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _GSC_HWMON_H
> +#define _GSC_HWMON_H
> +
> +enum gsc_hwmon_type {
> +	type_temperature,
> +	type_voltage,
> +	type_voltage_raw,
> +	type_fan,
> +};
> +
> +/**
> + * struct gsc_hwmon_channel - configuration parameters
> + * @reg:  I2C register offset
> + * @type: channel type
> + * @name: channel name
> + * @voffset: voltage offset (mV)
> + * @vdiv: voltage divider array (2 resistor values in ohms)
> + */
> +struct gsc_hwmon_channel {
> +	unsigned int reg;
> +	unsigned int type;
> +	const char *name;
> +	unsigned int voffset;

It is interesting that only positive offset values are supported.
Is that intentional ?

> +	unsigned int vdiv[2];
> +};
> +
> +/**
> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
> + * @channels:	pointer to array of gsc_hwmon_channel structures
> + *		describing channels
> + * @nchannels:	number of elements in @channels array
> + * @vreference: voltage reference (mV)
> + * @resolution: ADC resolution

Resolution in what ?

> + */
> +struct gsc_hwmon_platform_data {
> +	const struct gsc_hwmon_channel *channels;
> +	int nchannels;
> +	unsigned int resolution;
> +	unsigned int vreference;
> +};
> +
> +#endif
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 16:24     ` Guenter Roeck
@ 2018-03-28 19:17       ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 19:17 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
>> This patch adds documentation of device-tree bindings for the
>> Gateworks System Controller (GSC).
>>
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>> v3:
>>  - replaced _ with -
>>  - remove input bindings
>>  - added full description of hwmon
>>  - fix unit address of hwmon child nodes
>>
>> ---
>>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>>  1 file changed, 135 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> new file mode 100644
>> index 0000000..8f530ed
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> @@ -0,0 +1,135 @@
>> +Gateworks System Controller multi-function device
>> +
>> +The GSC is a Multifunction I2C slave device with the following submodules:
>> +- WDT
>> +- GPIO
>> +- Pushbutton controller
>> +- HWMON
>> +
>> +Required properties:
>> +- compatible : Must be "gw,gsc"
>> +- reg: I2C address of the device
>> +- interrupts: interrupt triggered by GSC_IRQ# signal
>> +- interrupt-parent: Interrupt controller GSC is connected to
>> +- #interrupt-cells: should be <1>, index of the interrupt within the
>> +  controller, in accordance with the "one cell" variant of
>> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
>> +
>> +Optional nodes:
>> +* watchdog:
>> +The GSC provides a Watchdog monitor which can power cycle the board's
>> +primary power supply on most board models when tripped.
>> +
>> +Required watchdog properties:
>> +- compatible: must be "gw,gsc-watchdog"
>> +
>> +* hwmon:
>> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
>> +temperature and/or voltage monitoring.
>> +
>> +Required hwmon properties:
>> +- compatible: must be "gw,gsc-hwmon"
>> +
>
> "hwmon" is a very Linux specific term. It might make sense to find a more
> generic term.

The 'hwmon' driver supports child nodes that fall into the following category:
 - temperature sensor (GSC internal temperature sensor - i2c registers
returns value in C*10)
 - voltage rails (two types here; cooked: i2c registers return
pre-scaled value in mV), raw: i2c registers return a raw ADC value
that must be scaled based on ADC internal ref voltage and resolution
and adjusted for a voltage divider to convert to mV
 - fan setpoints (I'll explain these below)

I called the node 'gw,gsc-hwmon' because the driver fits into the
'hwmon' API. Isn't that appropriate here for the driver compatible
string?

>
>> +Optional hwmon properties:
>> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
>
> AFAIK devicetree likes to specify voltages in uV.

There are currently plenty of dt props specified in mV (grep -r mV
Documentation/devicetree/bindings/).

>
>> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
>> +
>
> 4096 what ?

reference-voltage and resolution are used to scale the values from the
nodes that report a raw ADC value:

V = Vadc * (reference-voltage / resolution)

I can provide that in bits if it makes more sense? I can also hard
code both the resolution and the vref in the hwmon driver and remove
it from dt as currently the only GSC that uses raw ADC values is 12bit
with 2.5V ref.

>
>> +Each hwmon child node defines an ADC input on the chip which the GSC may
>> +report cooked values (ie temperature sensor based on thermister), raw values,
>> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
>> +setpoint.
>> +
>> +Required hwmon child properties:
>> +- type: one of the following ADC types:
>> +  "gw,hwmon-temperature" - reports temperature in C*10
>> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
>> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
>> +       vreference, resolution, and optional resistor divider
>> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
>
> What is a "fan temperature setpoint" ?
>

The GSC supports a fan controller which drives a PWM signal to vary
the speed of a fan based on the GSC internal temperature sensor. The
FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
the temperature at which those setpoints kick in can be varies via
registers at the 0x29 slave address (same slave address as the
temperature sensor and voltage inputs which is why I have it in the
hwmon driver):

fan0_point - 50% PWM (default 300)
fan1_point - 60% PWM (default 330)
fan2_point - 70% PWM (default 360)
fan3_point - 80% PWM (default 390)
fan4_point - 90% PWM (default 420)
fan5_point - 100% PWM (default 450)

The values are C/10 thus if the internal GSC temp sensor is below 30C
the fan output will be 0% duty cycle and if it hits 30C it will go to
50% until it hits 60% at 33C etc.

That is the hardware implementation that I'm trying to abstract and
define here. You pointed out the fact that the fan*_input ABI is
read-only fan PWM and I see that now. What do you suggest I use for
this feature I'm trying to implement driver support for?

I did notice that nouveau_hwmon.c has a single temperature setpoint
similar to this that they support with SENSOR_DEVICE_ATTR:
https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/nouveau/nouveau_hwmon.c#L63

>> +- reg: offset of the ADC register
>> +- label: name of the ADC input or FAN setpoint
>> +
>> +Optional hwmon child properties:
>> +- gw,voltage-divider: An array of two integers containing the resistor
>> +  values R1 and R2 of the optinal resistor divider on a raw ADC
>> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
>> +  compensate for a diode drop)
>> +
>> +Example:
>> +
>> +     gsc: gsc@20 {
>> +             compatible = "gw,gsc";
>> +             reg = <0x20>;
>> +             interrupt-parent = <&gpio1>;
>> +             interrupts = <4 GPIO_ACTIVE_LOW>;
>> +             interrupt-controller;
>> +             #interrupt-cells = <1>;
>> +
>> +             watchdog {
>> +                     compatible = "gw,gsc-watchdog";
>> +             };
>> +
>> +             hwmon {
>> +                     compatible = "gw,gsc-hwmon";
>> +                     #address-cells = <1>;
>> +                     #size-cells = <0>;
>> +                     gw,reference-voltage = <2500>;
>> +                     gw,resolution = <4096>;
>> +
>> +                     hwmon@0 { /* A0: Board Temperature */
>> +                             type = "gw,hwmon-temperature";
>> +                             reg = <0x00>;
>> +                             label = "temp";
>> +                     };
>> +
>> +                     hwmon@2 { /* A1: Input Voltage (raw ADC) */
>> +                             type = "gw,hwmon-voltage-raw";
>> +                             reg = <0x02>;
>> +                             label = "vdd_vin";
>> +                             gw,voltage-divider = <22100 1000>;
>> +                             gw,voltage-offset = <800>;
>> +                     };
>> +
>> +                     hwmon@b { /* A2: Battery voltage */
>> +                             type = "gw,hwmon-voltage";
>> +                             reg = <0x0b>;
>> +                             label = "vdd_bat";
>> +                     };
>> +
>> +                     hwmon@2c { /* fan temperature setpoint for 50% duty */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x2c>;
>> +                             label = "fan_50p";
>> +                     };
>> +
>> +                     hwmon@2e { /* fan1 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x2e>;
>> +                             label = "fan_60p";
>> +                     };
>> +
>> +                     hwmon@30 { /* fan2 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x30>;
>> +                             label = "fan_70p";
>> +                     };
>> +
>> +                     hwmon@32 { /* fan3 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x32>;
>> +                             label = "fan_80p";
>> +                     };
>> +
>> +                     hwmon@34 { /* fan4 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x34>;
>> +                             label = "fan_90p";
>> +                     };
>> +
>> +                     hwmon@36 { /* fan5 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x36>;
>> +                             label = "fan_100p";
>> +                     };
>
> No idea what this is supposed to be doing, but whatever it is,
> it appears to be wrong. I'll comment more on it in the hwmon driver.
>

ok - I'll respond to that thread.

Thanks for the review!

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 19:17       ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 19:17 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
>> This patch adds documentation of device-tree bindings for the
>> Gateworks System Controller (GSC).
>>
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>> v3:
>>  - replaced _ with -
>>  - remove input bindings
>>  - added full description of hwmon
>>  - fix unit address of hwmon child nodes
>>
>> ---
>>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>>  1 file changed, 135 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> new file mode 100644
>> index 0000000..8f530ed
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> @@ -0,0 +1,135 @@
>> +Gateworks System Controller multi-function device
>> +
>> +The GSC is a Multifunction I2C slave device with the following submodules:
>> +- WDT
>> +- GPIO
>> +- Pushbutton controller
>> +- HWMON
>> +
>> +Required properties:
>> +- compatible : Must be "gw,gsc"
>> +- reg: I2C address of the device
>> +- interrupts: interrupt triggered by GSC_IRQ# signal
>> +- interrupt-parent: Interrupt controller GSC is connected to
>> +- #interrupt-cells: should be <1>, index of the interrupt within the
>> +  controller, in accordance with the "one cell" variant of
>> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
>> +
>> +Optional nodes:
>> +* watchdog:
>> +The GSC provides a Watchdog monitor which can power cycle the board's
>> +primary power supply on most board models when tripped.
>> +
>> +Required watchdog properties:
>> +- compatible: must be "gw,gsc-watchdog"
>> +
>> +* hwmon:
>> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
>> +temperature and/or voltage monitoring.
>> +
>> +Required hwmon properties:
>> +- compatible: must be "gw,gsc-hwmon"
>> +
>
> "hwmon" is a very Linux specific term. It might make sense to find a more
> generic term.

The 'hwmon' driver supports child nodes that fall into the following category:
 - temperature sensor (GSC internal temperature sensor - i2c registers
returns value in C*10)
 - voltage rails (two types here; cooked: i2c registers return
pre-scaled value in mV), raw: i2c registers return a raw ADC value
that must be scaled based on ADC internal ref voltage and resolution
and adjusted for a voltage divider to convert to mV
 - fan setpoints (I'll explain these below)

I called the node 'gw,gsc-hwmon' because the driver fits into the
'hwmon' API. Isn't that appropriate here for the driver compatible
string?

>
>> +Optional hwmon properties:
>> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
>
> AFAIK devicetree likes to specify voltages in uV.

There are currently plenty of dt props specified in mV (grep -r mV
Documentation/devicetree/bindings/).

>
>> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
>> +
>
> 4096 what ?

reference-voltage and resolution are used to scale the values from the
nodes that report a raw ADC value:

V = Vadc * (reference-voltage / resolution)

I can provide that in bits if it makes more sense? I can also hard
code both the resolution and the vref in the hwmon driver and remove
it from dt as currently the only GSC that uses raw ADC values is 12bit
with 2.5V ref.

>
>> +Each hwmon child node defines an ADC input on the chip which the GSC may
>> +report cooked values (ie temperature sensor based on thermister), raw values,
>> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
>> +setpoint.
>> +
>> +Required hwmon child properties:
>> +- type: one of the following ADC types:
>> +  "gw,hwmon-temperature" - reports temperature in C*10
>> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
>> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
>> +       vreference, resolution, and optional resistor divider
>> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
>
> What is a "fan temperature setpoint" ?
>

The GSC supports a fan controller which drives a PWM signal to vary
the speed of a fan based on the GSC internal temperature sensor. The
FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
the temperature at which those setpoints kick in can be varies via
registers at the 0x29 slave address (same slave address as the
temperature sensor and voltage inputs which is why I have it in the
hwmon driver):

fan0_point - 50% PWM (default 300)
fan1_point - 60% PWM (default 330)
fan2_point - 70% PWM (default 360)
fan3_point - 80% PWM (default 390)
fan4_point - 90% PWM (default 420)
fan5_point - 100% PWM (default 450)

The values are C/10 thus if the internal GSC temp sensor is below 30C
the fan output will be 0% duty cycle and if it hits 30C it will go to
50% until it hits 60% at 33C etc.

That is the hardware implementation that I'm trying to abstract and
define here. You pointed out the fact that the fan*_input ABI is
read-only fan PWM and I see that now. What do you suggest I use for
this feature I'm trying to implement driver support for?

I did notice that nouveau_hwmon.c has a single temperature setpoint
similar to this that they support with SENSOR_DEVICE_ATTR:
https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/nouveau/nouveau_hwmon.c#L63

>> +- reg: offset of the ADC register
>> +- label: name of the ADC input or FAN setpoint
>> +
>> +Optional hwmon child properties:
>> +- gw,voltage-divider: An array of two integers containing the resistor
>> +  values R1 and R2 of the optinal resistor divider on a raw ADC
>> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
>> +  compensate for a diode drop)
>> +
>> +Example:
>> +
>> +     gsc: gsc at 20 {
>> +             compatible = "gw,gsc";
>> +             reg = <0x20>;
>> +             interrupt-parent = <&gpio1>;
>> +             interrupts = <4 GPIO_ACTIVE_LOW>;
>> +             interrupt-controller;
>> +             #interrupt-cells = <1>;
>> +
>> +             watchdog {
>> +                     compatible = "gw,gsc-watchdog";
>> +             };
>> +
>> +             hwmon {
>> +                     compatible = "gw,gsc-hwmon";
>> +                     #address-cells = <1>;
>> +                     #size-cells = <0>;
>> +                     gw,reference-voltage = <2500>;
>> +                     gw,resolution = <4096>;
>> +
>> +                     hwmon at 0 { /* A0: Board Temperature */
>> +                             type = "gw,hwmon-temperature";
>> +                             reg = <0x00>;
>> +                             label = "temp";
>> +                     };
>> +
>> +                     hwmon at 2 { /* A1: Input Voltage (raw ADC) */
>> +                             type = "gw,hwmon-voltage-raw";
>> +                             reg = <0x02>;
>> +                             label = "vdd_vin";
>> +                             gw,voltage-divider = <22100 1000>;
>> +                             gw,voltage-offset = <800>;
>> +                     };
>> +
>> +                     hwmon at b { /* A2: Battery voltage */
>> +                             type = "gw,hwmon-voltage";
>> +                             reg = <0x0b>;
>> +                             label = "vdd_bat";
>> +                     };
>> +
>> +                     hwmon at 2c { /* fan temperature setpoint for 50% duty */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x2c>;
>> +                             label = "fan_50p";
>> +                     };
>> +
>> +                     hwmon at 2e { /* fan1 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x2e>;
>> +                             label = "fan_60p";
>> +                     };
>> +
>> +                     hwmon at 30 { /* fan2 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x30>;
>> +                             label = "fan_70p";
>> +                     };
>> +
>> +                     hwmon at 32 { /* fan3 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x32>;
>> +                             label = "fan_80p";
>> +                     };
>> +
>> +                     hwmon at 34 { /* fan4 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x34>;
>> +                             label = "fan_90p";
>> +                     };
>> +
>> +                     hwmon at 36 { /* fan5 */
>> +                             type = "gw,hwmon-fan";
>> +                             reg = <0x36>;
>> +                             label = "fan_100p";
>> +                     };
>
> No idea what this is supposed to be doing, but whatever it is,
> it appears to be wrong. I'll comment more on it in the hwmon driver.
>

ok - I'll respond to that thread.

Thanks for the review!

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 19:17       ` Tim Harvey
@ 2018-03-28 20:23         ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 20:23 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> >> This patch adds documentation of device-tree bindings for the
> >> Gateworks System Controller (GSC).
> >>
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >> v3:
> >>  - replaced _ with -
> >>  - remove input bindings
> >>  - added full description of hwmon
> >>  - fix unit address of hwmon child nodes
> >>
> >> ---
> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
> >>  1 file changed, 135 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> new file mode 100644
> >> index 0000000..8f530ed
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> @@ -0,0 +1,135 @@
> >> +Gateworks System Controller multi-function device
> >> +
> >> +The GSC is a Multifunction I2C slave device with the following submodules:
> >> +- WDT
> >> +- GPIO
> >> +- Pushbutton controller
> >> +- HWMON
> >> +
> >> +Required properties:
> >> +- compatible : Must be "gw,gsc"
> >> +- reg: I2C address of the device
> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
> >> +- interrupt-parent: Interrupt controller GSC is connected to
> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
> >> +  controller, in accordance with the "one cell" variant of
> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> >> +
> >> +Optional nodes:
> >> +* watchdog:
> >> +The GSC provides a Watchdog monitor which can power cycle the board's
> >> +primary power supply on most board models when tripped.
> >> +
> >> +Required watchdog properties:
> >> +- compatible: must be "gw,gsc-watchdog"
> >> +
> >> +* hwmon:
> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> >> +temperature and/or voltage monitoring.
> >> +
> >> +Required hwmon properties:
> >> +- compatible: must be "gw,gsc-hwmon"
> >> +
> >
> > "hwmon" is a very Linux specific term. It might make sense to find a more
> > generic term.
> 
> The 'hwmon' driver supports child nodes that fall into the following category:
>  - temperature sensor (GSC internal temperature sensor - i2c registers
> returns value in C*10)
>  - voltage rails (two types here; cooked: i2c registers return
> pre-scaled value in mV), raw: i2c registers return a raw ADC value
> that must be scaled based on ADC internal ref voltage and resolution
> and adjusted for a voltage divider to convert to mV
>  - fan setpoints (I'll explain these below)
> 
> I called the node 'gw,gsc-hwmon' because the driver fits into the
> 'hwmon' API. Isn't that appropriate here for the driver compatible
> string?
> 

Devicetree properties are supposed to be OS independent.

> >
> >> +Optional hwmon properties:
> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
> >
> > AFAIK devicetree likes to specify voltages in uV.
> 
> There are currently plenty of dt props specified in mV (grep -r mV
> Documentation/devicetree/bindings/).
> 

"But so many others are speeding, why do I get a ticket ?"

Please discuss with Rob.

> >
> >> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
> >> +
> >
> > 4096 what ?
> 
> reference-voltage and resolution are used to scale the values from the
> nodes that report a raw ADC value:
> 
> V = Vadc * (reference-voltage / resolution)
> 
> I can provide that in bits if it makes more sense? I can also hard

Yes, I think that would make more sense, and please describe what it means.

> code both the resolution and the vref in the hwmon driver and remove
> it from dt as currently the only GSC that uses raw ADC values is 12bit
> with 2.5V ref.
> 

That would be even better.

> >
> >> +Each hwmon child node defines an ADC input on the chip which the GSC may
> >> +report cooked values (ie temperature sensor based on thermister), raw values,
> >> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
> >> +setpoint.
> >> +
> >> +Required hwmon child properties:
> >> +- type: one of the following ADC types:
> >> +  "gw,hwmon-temperature" - reports temperature in C*10
> >> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
> >> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
> >> +       vreference, resolution, and optional resistor divider
> >> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
> >
> > What is a "fan temperature setpoint" ?
> >
> 
> The GSC supports a fan controller which drives a PWM signal to vary
> the speed of a fan based on the GSC internal temperature sensor. The
> FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
> the temperature at which those setpoints kick in can be varies via
> registers at the 0x29 slave address (same slave address as the
> temperature sensor and voltage inputs which is why I have it in the
> hwmon driver):
> 
> fan0_point - 50% PWM (default 300)
> fan1_point - 60% PWM (default 330)
> fan2_point - 70% PWM (default 360)
> fan3_point - 80% PWM (default 390)
> fan4_point - 90% PWM (default 420)
> fan5_point - 100% PWM (default 450)
> 
> The values are C/10 thus if the internal GSC temp sensor is below 30C
> the fan output will be 0% duty cycle and if it hits 30C it will go to
> 50% until it hits 60% at 33C etc.
> 
Please do not define your own scaling factors. pwm values are 0..255,
and temperatures are in milli-degrees C.

> That is the hardware implementation that I'm trying to abstract and
> define here. You pointed out the fact that the fan*_input ABI is
> read-only fan PWM and I see that now. What do you suggest I use for

No, it isn't. It is the fan speed in RPM.

> this feature I'm trying to implement driver support for?
> 

pwm[1-*]_auto_point[1-*]_pwm
pwm[1-*]_auto_point[1-*]_temp
pwm[1-*]_auto_point[1-*]_temp_hyst

may be relevant. From the context, something like

pwm1_auto_point1_pwm	read-only, set to 128
pwm1_auto_point1_temp	30000
pwm1_auto_point2_pwm	read-only, set to 153
pwm1_auto_point2_temp	33000
pwm1_auto_point3_pwm	read-only, set to 179
pwm1_auto_point3_temp	36000
pwm1_auto_point4_pwm	read-only, set to 204
pwm1_auto_point4_temp	39000
pwm1_auto_point5_pwm	read-only, set to 230
pwm1_auto_point5_temp	42000
pwm1_auto_point6_pwm	read-only, set to 255
pwm1_auto_point6_temp	45000

might make sense.

> I did notice that nouveau_hwmon.c has a single temperature setpoint
> similar to this that they support with SENSOR_DEVICE_ATTR:
> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/nouveau/nouveau_hwmon.c#L63
> 
Whatever nouveau_hwmon.c does is, for all practical purposes, absolutely
irrelevant. This code has never been reviewed by a hwmon maintainer.
It may (or may not) be complete junk. Please do not use anything outside
drivers/hwmon as example.

> >> +- reg: offset of the ADC register
> >> +- label: name of the ADC input or FAN setpoint
> >> +
> >> +Optional hwmon child properties:
> >> +- gw,voltage-divider: An array of two integers containing the resistor
> >> +  values R1 and R2 of the optinal resistor divider on a raw ADC
> >> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
> >> +  compensate for a diode drop)
> >> +
> >> +Example:
> >> +
> >> +     gsc: gsc@20 {
> >> +             compatible = "gw,gsc";
> >> +             reg = <0x20>;
> >> +             interrupt-parent = <&gpio1>;
> >> +             interrupts = <4 GPIO_ACTIVE_LOW>;
> >> +             interrupt-controller;
> >> +             #interrupt-cells = <1>;
> >> +
> >> +             watchdog {
> >> +                     compatible = "gw,gsc-watchdog";
> >> +             };
> >> +
> >> +             hwmon {
> >> +                     compatible = "gw,gsc-hwmon";
> >> +                     #address-cells = <1>;
> >> +                     #size-cells = <0>;
> >> +                     gw,reference-voltage = <2500>;
> >> +                     gw,resolution = <4096>;
> >> +
> >> +                     hwmon@0 { /* A0: Board Temperature */
> >> +                             type = "gw,hwmon-temperature";
> >> +                             reg = <0x00>;
> >> +                             label = "temp";
> >> +                     };
> >> +
> >> +                     hwmon@2 { /* A1: Input Voltage (raw ADC) */
> >> +                             type = "gw,hwmon-voltage-raw";
> >> +                             reg = <0x02>;
> >> +                             label = "vdd_vin";
> >> +                             gw,voltage-divider = <22100 1000>;
> >> +                             gw,voltage-offset = <800>;
> >> +                     };
> >> +
> >> +                     hwmon@b { /* A2: Battery voltage */
> >> +                             type = "gw,hwmon-voltage";
> >> +                             reg = <0x0b>;
> >> +                             label = "vdd_bat";
> >> +                     };
> >> +
> >> +                     hwmon@2c { /* fan temperature setpoint for 50% duty */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x2c>;
> >> +                             label = "fan_50p";
> >> +                     };
> >> +
> >> +                     hwmon@2e { /* fan1 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x2e>;
> >> +                             label = "fan_60p";
> >> +                     };
> >> +
> >> +                     hwmon@30 { /* fan2 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x30>;
> >> +                             label = "fan_70p";
> >> +                     };
> >> +
> >> +                     hwmon@32 { /* fan3 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x32>;
> >> +                             label = "fan_80p";
> >> +                     };
> >> +
> >> +                     hwmon@34 { /* fan4 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x34>;
> >> +                             label = "fan_90p";
> >> +                     };
> >> +
> >> +                     hwmon@36 { /* fan5 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x36>;
> >> +                             label = "fan_100p";
> >> +                     };
> >
> > No idea what this is supposed to be doing, but whatever it is,
> > it appears to be wrong. I'll comment more on it in the hwmon driver.
> >
> 
> ok - I'll respond to that thread.
> 
> Thanks for the review!
> 
> Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 20:23         ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 20:23 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> >> This patch adds documentation of device-tree bindings for the
> >> Gateworks System Controller (GSC).
> >>
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >> v3:
> >>  - replaced _ with -
> >>  - remove input bindings
> >>  - added full description of hwmon
> >>  - fix unit address of hwmon child nodes
> >>
> >> ---
> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
> >>  1 file changed, 135 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> new file mode 100644
> >> index 0000000..8f530ed
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> @@ -0,0 +1,135 @@
> >> +Gateworks System Controller multi-function device
> >> +
> >> +The GSC is a Multifunction I2C slave device with the following submodules:
> >> +- WDT
> >> +- GPIO
> >> +- Pushbutton controller
> >> +- HWMON
> >> +
> >> +Required properties:
> >> +- compatible : Must be "gw,gsc"
> >> +- reg: I2C address of the device
> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
> >> +- interrupt-parent: Interrupt controller GSC is connected to
> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
> >> +  controller, in accordance with the "one cell" variant of
> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> >> +
> >> +Optional nodes:
> >> +* watchdog:
> >> +The GSC provides a Watchdog monitor which can power cycle the board's
> >> +primary power supply on most board models when tripped.
> >> +
> >> +Required watchdog properties:
> >> +- compatible: must be "gw,gsc-watchdog"
> >> +
> >> +* hwmon:
> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> >> +temperature and/or voltage monitoring.
> >> +
> >> +Required hwmon properties:
> >> +- compatible: must be "gw,gsc-hwmon"
> >> +
> >
> > "hwmon" is a very Linux specific term. It might make sense to find a more
> > generic term.
> 
> The 'hwmon' driver supports child nodes that fall into the following category:
>  - temperature sensor (GSC internal temperature sensor - i2c registers
> returns value in C*10)
>  - voltage rails (two types here; cooked: i2c registers return
> pre-scaled value in mV), raw: i2c registers return a raw ADC value
> that must be scaled based on ADC internal ref voltage and resolution
> and adjusted for a voltage divider to convert to mV
>  - fan setpoints (I'll explain these below)
> 
> I called the node 'gw,gsc-hwmon' because the driver fits into the
> 'hwmon' API. Isn't that appropriate here for the driver compatible
> string?
> 

Devicetree properties are supposed to be OS independent.

> >
> >> +Optional hwmon properties:
> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
> >
> > AFAIK devicetree likes to specify voltages in uV.
> 
> There are currently plenty of dt props specified in mV (grep -r mV
> Documentation/devicetree/bindings/).
> 

"But so many others are speeding, why do I get a ticket ?"

Please discuss with Rob.

> >
> >> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
> >> +
> >
> > 4096 what ?
> 
> reference-voltage and resolution are used to scale the values from the
> nodes that report a raw ADC value:
> 
> V = Vadc * (reference-voltage / resolution)
> 
> I can provide that in bits if it makes more sense? I can also hard

Yes, I think that would make more sense, and please describe what it means.

> code both the resolution and the vref in the hwmon driver and remove
> it from dt as currently the only GSC that uses raw ADC values is 12bit
> with 2.5V ref.
> 

That would be even better.

> >
> >> +Each hwmon child node defines an ADC input on the chip which the GSC may
> >> +report cooked values (ie temperature sensor based on thermister), raw values,
> >> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
> >> +setpoint.
> >> +
> >> +Required hwmon child properties:
> >> +- type: one of the following ADC types:
> >> +  "gw,hwmon-temperature" - reports temperature in C*10
> >> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
> >> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
> >> +       vreference, resolution, and optional resistor divider
> >> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
> >
> > What is a "fan temperature setpoint" ?
> >
> 
> The GSC supports a fan controller which drives a PWM signal to vary
> the speed of a fan based on the GSC internal temperature sensor. The
> FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
> the temperature at which those setpoints kick in can be varies via
> registers at the 0x29 slave address (same slave address as the
> temperature sensor and voltage inputs which is why I have it in the
> hwmon driver):
> 
> fan0_point - 50% PWM (default 300)
> fan1_point - 60% PWM (default 330)
> fan2_point - 70% PWM (default 360)
> fan3_point - 80% PWM (default 390)
> fan4_point - 90% PWM (default 420)
> fan5_point - 100% PWM (default 450)
> 
> The values are C/10 thus if the internal GSC temp sensor is below 30C
> the fan output will be 0% duty cycle and if it hits 30C it will go to
> 50% until it hits 60% at 33C etc.
> 
Please do not define your own scaling factors. pwm values are 0..255,
and temperatures are in milli-degrees C.

> That is the hardware implementation that I'm trying to abstract and
> define here. You pointed out the fact that the fan*_input ABI is
> read-only fan PWM and I see that now. What do you suggest I use for

No, it isn't. It is the fan speed in RPM.

> this feature I'm trying to implement driver support for?
> 

pwm[1-*]_auto_point[1-*]_pwm
pwm[1-*]_auto_point[1-*]_temp
pwm[1-*]_auto_point[1-*]_temp_hyst

may be relevant. From the context, something like

pwm1_auto_point1_pwm	read-only, set to 128
pwm1_auto_point1_temp	30000
pwm1_auto_point2_pwm	read-only, set to 153
pwm1_auto_point2_temp	33000
pwm1_auto_point3_pwm	read-only, set to 179
pwm1_auto_point3_temp	36000
pwm1_auto_point4_pwm	read-only, set to 204
pwm1_auto_point4_temp	39000
pwm1_auto_point5_pwm	read-only, set to 230
pwm1_auto_point5_temp	42000
pwm1_auto_point6_pwm	read-only, set to 255
pwm1_auto_point6_temp	45000

might make sense.

> I did notice that nouveau_hwmon.c has a single temperature setpoint
> similar to this that they support with SENSOR_DEVICE_ATTR:
> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/nouveau/nouveau_hwmon.c#L63
> 
Whatever nouveau_hwmon.c does is, for all practical purposes, absolutely
irrelevant. This code has never been reviewed by a hwmon maintainer.
It may (or may not) be complete junk. Please do not use anything outside
drivers/hwmon as example.

> >> +- reg: offset of the ADC register
> >> +- label: name of the ADC input or FAN setpoint
> >> +
> >> +Optional hwmon child properties:
> >> +- gw,voltage-divider: An array of two integers containing the resistor
> >> +  values R1 and R2 of the optinal resistor divider on a raw ADC
> >> +- gw,voltage-offset: a mV voltage offset to apply to a raw ADC (ie to
> >> +  compensate for a diode drop)
> >> +
> >> +Example:
> >> +
> >> +     gsc: gsc at 20 {
> >> +             compatible = "gw,gsc";
> >> +             reg = <0x20>;
> >> +             interrupt-parent = <&gpio1>;
> >> +             interrupts = <4 GPIO_ACTIVE_LOW>;
> >> +             interrupt-controller;
> >> +             #interrupt-cells = <1>;
> >> +
> >> +             watchdog {
> >> +                     compatible = "gw,gsc-watchdog";
> >> +             };
> >> +
> >> +             hwmon {
> >> +                     compatible = "gw,gsc-hwmon";
> >> +                     #address-cells = <1>;
> >> +                     #size-cells = <0>;
> >> +                     gw,reference-voltage = <2500>;
> >> +                     gw,resolution = <4096>;
> >> +
> >> +                     hwmon at 0 { /* A0: Board Temperature */
> >> +                             type = "gw,hwmon-temperature";
> >> +                             reg = <0x00>;
> >> +                             label = "temp";
> >> +                     };
> >> +
> >> +                     hwmon at 2 { /* A1: Input Voltage (raw ADC) */
> >> +                             type = "gw,hwmon-voltage-raw";
> >> +                             reg = <0x02>;
> >> +                             label = "vdd_vin";
> >> +                             gw,voltage-divider = <22100 1000>;
> >> +                             gw,voltage-offset = <800>;
> >> +                     };
> >> +
> >> +                     hwmon at b { /* A2: Battery voltage */
> >> +                             type = "gw,hwmon-voltage";
> >> +                             reg = <0x0b>;
> >> +                             label = "vdd_bat";
> >> +                     };
> >> +
> >> +                     hwmon at 2c { /* fan temperature setpoint for 50% duty */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x2c>;
> >> +                             label = "fan_50p";
> >> +                     };
> >> +
> >> +                     hwmon at 2e { /* fan1 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x2e>;
> >> +                             label = "fan_60p";
> >> +                     };
> >> +
> >> +                     hwmon at 30 { /* fan2 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x30>;
> >> +                             label = "fan_70p";
> >> +                     };
> >> +
> >> +                     hwmon at 32 { /* fan3 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x32>;
> >> +                             label = "fan_80p";
> >> +                     };
> >> +
> >> +                     hwmon at 34 { /* fan4 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x34>;
> >> +                             label = "fan_90p";
> >> +                     };
> >> +
> >> +                     hwmon at 36 { /* fan5 */
> >> +                             type = "gw,hwmon-fan";
> >> +                             reg = <0x36>;
> >> +                             label = "fan_100p";
> >> +                     };
> >
> > No idea what this is supposed to be doing, but whatever it is,
> > it appears to be wrong. I'll comment more on it in the hwmon driver.
> >
> 
> ok - I'll respond to that thread.
> 
> Thanks for the review!
> 
> Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 3/4] hwmon: add Gateworks System Controller support
  2018-03-28 17:00     ` Guenter Roeck
@ 2018-03-28 20:23       ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 20:23 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 10:00 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
>> The Gateworks System Controller has a hwmon sub-component that exposes
>> up to 16 ADC's, some of which are temperature sensors, others which are
>> voltage inputs. The ADC configuration (register mapping and name) is
>> configured via device-tree and varies board to board.
>>
>> Cc: Guenter Roeck <linux@roeck-us.net>
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>> v3:
>> - add voltage_raw input type and supporting fields
>> - add channel validation to is_visible function
>> - remove unnecessary channel validation from read/write functions
>>
>> v2:
>> - change license comment style
>> - remove DEBUG
>> - simplify regmap_bulk_read err check
>> - remove break after returns in switch statement
>> - fix fan setpoint buffer address
>> - remove unnecessary parens
>> - consistently use struct device *dev pointer
>> - change license/comment block
>> - add validation for hwmon child node props
>> - move parsing of of to own function
>> - use strlcpy to ensure null termination
>> - fix static array sizes and removed unnecessary initializers
>> - dynamically allocate channels
>> - fix fan input label
>> - support platform data
>> - fixed whitespace issues
>>
>>  drivers/hwmon/Kconfig                   |   9 +
>>  drivers/hwmon/Makefile                  |   1 +
>>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
>
> This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
> attributes.

ok - will add in next submission

>
<snip>
>>
>> +static int
>> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>> +            int channel, long *val)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +     struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
>> +     const struct gsc_hwmon_channel *ch;
>> +     int sz, ret;
>> +     u8 buf[3];
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>> +     switch (type) {
>> +     case hwmon_in:
>> +             ch = hwmon->in_ch[channel];
>> +             break;
>> +     case hwmon_temp:
>> +             ch = hwmon->temp_ch[channel];
>> +             break;
>> +     case hwmon_fan:
>> +             ch = hwmon->fan_ch[channel];
>> +             break;
>> +     default:
>> +             return -EOPNOTSUPP;
>> +     }
>> +
>> +     sz = (ch->type == type_voltage) ? 3 : 2;
>> +     ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
>> +     if (ret)
>> +             return ret;
>> +
>> +     *val = 0;
>> +     while (sz-- > 0)
>> +             *val |= (buf[sz] << (8*sz));
>
> Please use spaces before and after operators.

ok

>
>> +
>> +     switch (ch->type) {
>> +     case type_temperature:
>> +             if ((type == hwmon_temp) && *val > 0x8000)
>
> Please no unnecessary ( ).
>

ok

> Is there ever a situation where ch->type == type_temperature and type !=
> hwmon_temp ? Wouldn't that be a bug ?

I should not have been checking (type == hwmon_temp) there... will remove that.

>
>> +                     *val -= 0xffff;
>> +             break;
>> +     case type_voltage_raw:
>> +             /* scale based on ref voltage and resolution */
>> +             if (pdata->vreference && pdata->resolution) {
>> +                     *val *= pdata->vreference;
>> +                     *val /= pdata->resolution;
>> +             }
>> +             /* scale based on optional voltage divider */
>> +             if (ch->vdiv[0] && ch->vdiv[1]) {
>> +                     *val *= (ch->vdiv[0] + ch->vdiv[1]);
>> +                     *val /= ch->vdiv[1];
>> +             }
>
> This accepts both types of scaling. Is that intentional ?

yes that is intentional. One version of the GSC reports cooked
pre-scaled values that won't fall into either of these. Another
version of the GSC will report raw ADC values which will need the
vref/res scaling and those may have an optional voltage divider.

>
> I don't see any protection against overflows. What if pdata->vreference is
> larger than 256 on a system with sizeof(long) == 4 and the raw voltage
> as reported by the chip is 0xffffff ?
>
>> +             /* adjust by offset */
>> +             *val += ch->voffset;
>
> Similar to the above, this can result in an overflow on systems with
> sizeof(long) == 4.
>

true - I will add some overflow checking

>> +             break;
>
> There should be a default case as well as 'case type_voltage:'
> with a break; statement and a comment indicating that no adjustment
> is needed.
>

ok

>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int
>> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
>> +                   u32 attr, int channel, const char **buf)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>
> I seriously wonder if all those dev_dbg() statements add value.

only to me while I was writing/testing; I will remove them

>
>> +     switch (type) {
>> +     case hwmon_in:
>> +             *buf = hwmon->in_ch[channel]->name;
>> +             break;
>> +     case hwmon_temp:
>> +             *buf = hwmon->temp_ch[channel]->name;
>> +             break;
>> +     case hwmon_fan:
>> +             *buf = hwmon->fan_ch[channel]->name;
>> +             break;
>> +     default:
>> +             return -ENOTSUPP;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int
>> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>> +             int channel, long val)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +     u8 buf[2];
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>> +     switch (type) {
>> +     case hwmon_fan:
>> +             buf[0] = val & 0xff;
>> +             buf[1] = (val >> 8) & 0xff;
>> +             return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
>> +                                      hwmon->fan_ch[channel]->reg, buf, 2);
>
> fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
> I have no idea what this is supposed to achieve, but whatever it is, it is wrong.
>
> Please stick with the ABI.

ok, I see that now. Do you have any recommendation of what I should
use for these temperature setpoints that control when the fan pwm is
adjusted?

>
>> +     default:
>> +             break;
>> +     }
>> +
>> +     return -EOPNOTSUPP;
>> +}
>> +
>> +static umode_t
>> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
>> +                  int ch)
>> +{
>> +     const struct gsc_hwmon_data *hwmon = _data;
>> +     struct device *dev = hwmon->gsc->dev;
>> +     umode_t mode = 0;
>> +
>> +     switch (type) {
>> +     case hwmon_fan:
>> +             if (ch >= GSC_HWMON_MAX_FAN_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?

No, I suppose not because the ch input comes from a hwmon node that
was created by the driver registering the entities and a check was
done there to avoid overflow. I will remove these.

>
>> +             mode = S_IRUGO;
>> +             if (attr == hwmon_fan_input)
>> +                     mode |= S_IWUSR;
>
> fanX_input is a read-only attribute per ABI.
>
>> +             break;
>> +     case hwmon_temp:
>> +             if (ch >= GSC_HWMON_MAX_TEMP_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?
>
>> +             mode = S_IRUGO;
>> +             break;
>> +     case hwmon_in:
>> +             if (ch >= GSC_HWMON_MAX_IN_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?
>
>> +             mode = S_IRUGO;
>> +             break;
>> +     default:
>> +             return -EOPNOTSUPP;
>> +     }
>> +     dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
>> +             attr, ch, mode);
>> +
>> +     return mode;
>> +}
>> +
>> +static const struct hwmon_ops gsc_hwmon_ops = {
>> +     .is_visible = gsc_hwmon_is_visible,
>> +     .read = gsc_hwmon_read,
>> +     .read_string = gsc_hwmon_read_string,
>> +     .write = gsc_hwmon_write,
>> +};
>> +
>> +static struct gsc_hwmon_platform_data *
>> +gsc_hwmon_get_devtree_pdata(struct device *dev)
>> +{
>> +     struct gsc_hwmon_platform_data *pdata;
>> +     struct gsc_hwmon_channel *ch;
>> +     struct fwnode_handle *child;
>> +     const char *type;
>> +     int nchannels;
>> +
>> +     nchannels = device_get_child_node_count(dev);
>> +     dev_dbg(dev, "channels=%d\n", nchannels);
>> +     if (nchannels == 0)
>> +             return ERR_PTR(-ENODEV);
>> +
>> +     pdata = devm_kzalloc(dev,
>> +                          sizeof(*pdata) + nchannels * sizeof(*ch),
>> +                          GFP_KERNEL);
>> +     if (!pdata)
>> +             return ERR_PTR(-ENOMEM);
>> +     ch = (struct gsc_hwmon_channel *)(pdata + 1);
>> +     pdata->channels = ch;
>> +     pdata->nchannels = nchannels;
>> +
>> +     device_property_read_u32(dev, "gw,reference-voltage",
>> +                              &pdata->vreference);
>> +     device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
>> +
>> +     /* allocate structures for channels and count instances of each type */
>> +     device_for_each_child_node(dev, child) {
>> +             if (fwnode_property_read_string(child, "label", &ch->name)) {
>> +                     dev_err(dev, "channel without label\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
>> +                     dev_err(dev, "channel without reg\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (fwnode_property_read_string(child, "type", &type)) {
>> +                     dev_err(dev, "channel without type\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (!strcasecmp(type, "gw,hwmon-temperature"))
>> +                     ch->type = type_temperature;
>> +             else if (!strcasecmp(type, "gw,hwmon-voltage"))
>> +                     ch->type = type_voltage;
>> +             else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
>> +                     ch->type = type_voltage_raw;
>> +             else if (!strcasecmp(type, "gw,hwmon-fan"))
>> +                     ch->type = type_fan;
>> +             else {
>> +                     dev_err(dev, "channel without type\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +
>> +             fwnode_property_read_u32(child, "gw,voltage-offset",
>> +                     &ch->voffset);
>
> Note that while it is technically ok to keep voltages internally in mV,
> devicetree will likely require specificayion in uV. For accuracy, it might be
> better to perform any calculations on that base and convert to mV for display
> purposes.

I'm happy to change them if that really is the standard but it doesn't
look like it is a standard?

>
>> +             fwnode_property_read_u32_array(child, "gw,voltage-divider",
>> +                     ch->vdiv, ARRAY_SIZE(ch->vdiv));
>> +             dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
>> +                     ch->name);
>> +             ch++;
>> +     }
>> +
>> +     return pdata;
>> +}
>> +
>> +static int gsc_hwmon_probe(struct platform_device *pdev)
>> +{
>> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> +     struct device *dev = &pdev->dev;
>> +     struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
>> +     struct gsc_hwmon_data *hwmon;
>> +     int i, i_in, i_temp, i_fan;
>> +
>> +     if (!pdata) {
>> +             pdata = gsc_hwmon_get_devtree_pdata(dev);
>> +             if (IS_ERR(pdata))
>> +                     return PTR_ERR(pdata);
>> +     }
>> +
>> +     hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
>> +     if (!hwmon)
>> +             return -ENOMEM;
>> +     hwmon->gsc = gsc;
>> +     hwmon->pdata = pdata;
>> +
>> +     for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
>> +          i < hwmon->pdata->nchannels; i++) {
>> +             const struct gsc_hwmon_channel *ch = &pdata->channels[i];
>> +
>> +             if (ch->reg > GSC_HWMON_MAX_REG) {
>> +                     dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
>> +                     return -EINVAL;
>> +             }
>> +             switch (ch->type) {
>> +             case type_temperature:
>> +                     if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
>> +                             dev_err(dev, "too many temp channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->temp_ch[i_temp] = ch;
>> +                     hwmon->temp_config[i_temp] = HWMON_T_INPUT |
>> +                                                  HWMON_T_LABEL;
>> +                     i_temp++;
>> +                     break;
>> +             case type_voltage:
>> +             case type_voltage_raw:
>> +                     if (i_in == GSC_HWMON_MAX_IN_CH) {
>> +                             dev_err(dev, "too many voltage channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->in_ch[i_in] = ch;
>> +                     hwmon->in_config[i_in] =
>> +                             HWMON_I_INPUT | HWMON_I_LABEL;
>> +                     i_in++;
>> +                     break;
>> +             case type_fan:
>> +                     if (i_fan == GSC_HWMON_MAX_FAN_CH) {
>> +                             dev_err(dev, "too many voltage channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->fan_ch[i_fan] = ch;
>> +                     hwmon->fan_config[i_fan] =
>> +                             HWMON_F_INPUT | HWMON_F_LABEL;
>> +                     i_fan++;
>> +                     break;
>> +             default:
>> +                     dev_err(dev, "invalid type: %d\n", ch->type);
>> +                     return -EINVAL;
>> +             }
>> +             dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
>> +                     ch->type, ch->name);
>> +     }
>> +
>> +     /* terminate channel config lists */
>> +     hwmon->temp_config[i_temp] = 0;
>> +     hwmon->in_config[i_in] = 0;
>> +     hwmon->fan_config[i_fan] = 0;
>
> 'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
> is unnecessary.

right - will remove

>
>> +
>> +     /* setup config structures */
>> +     hwmon->chip.ops = &gsc_hwmon_ops;
>> +     hwmon->chip.info = hwmon->info;
>> +     hwmon->info[0] = &hwmon->temp_info;
>> +     hwmon->info[1] = &hwmon->in_info;
>> +     hwmon->info[2] = &hwmon->fan_info;
>> +     hwmon->temp_info.type = hwmon_temp;
>> +     hwmon->temp_info.config = hwmon->temp_config;
>> +     hwmon->in_info.type = hwmon_in;
>> +     hwmon->in_info.config = hwmon->in_config;
>> +     hwmon->fan_info.type = hwmon_fan;
>> +     hwmon->fan_info.config = hwmon->fan_config;
>> +
>> +     hwmon->dev = devm_hwmon_device_register_with_info(dev,
>> +                                                       KBUILD_MODNAME, hwmon,
>> +                                                       &hwmon->chip, NULL);
>> +     return PTR_ERR_OR_ZERO(hwmon->dev);
>> +}
>> +
>> +static const struct of_device_id gsc_hwmon_of_match[] = {
>> +     { .compatible = "gw,gsc-hwmon", },
>> +     {}
>> +};
>> +
>> +static struct platform_driver gsc_hwmon_driver = {
>> +     .driver = {
>> +             .name = KBUILD_MODNAME,
>> +             .of_match_table = gsc_hwmon_of_match,
>> +     },
>> +     .probe = gsc_hwmon_probe,
>> +};
>> +
>> +module_platform_driver(gsc_hwmon_driver);
>> +
>> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
>> +MODULE_DESCRIPTION("GSC hardware monitor driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
>> new file mode 100644
>> index 0000000..5e59846
>> --- /dev/null
>> +++ b/include/linux/platform_data/gsc_hwmon.h
>> @@ -0,0 +1,43 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#ifndef _GSC_HWMON_H
>> +#define _GSC_HWMON_H
>> +
>> +enum gsc_hwmon_type {
>> +     type_temperature,
>> +     type_voltage,
>> +     type_voltage_raw,
>> +     type_fan,
>> +};
>> +
>> +/**
>> + * struct gsc_hwmon_channel - configuration parameters
>> + * @reg:  I2C register offset
>> + * @type: channel type
>> + * @name: channel name
>> + * @voffset: voltage offset (mV)
>> + * @vdiv: voltage divider array (2 resistor values in ohms)
>> + */
>> +struct gsc_hwmon_channel {
>> +     unsigned int reg;
>> +     unsigned int type;
>> +     const char *name;
>> +     unsigned int voffset;
>
> It is interesting that only positive offset values are supported.
> Is that intentional ?

yes, they are only used for voltage rails that have a diode drop to
adjust for and will only be positive. They are not used to fine tune
offsets.

>
>> +     unsigned int vdiv[2];
>> +};
>> +
>> +/**
>> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
>> + * @channels:        pointer to array of gsc_hwmon_channel structures
>> + *           describing channels
>> + * @nchannels:       number of elements in @channels array
>> + * @vreference: voltage reference (mV)
>> + * @resolution: ADC resolution
>
> Resolution in what ?

That's the bit-resolution of the ADC so I'll switch to that notation
and document it as such. I just realized that the vref/resolution can
technically change per ADC rail so I'll leave them in but move them
down into the child nodes and switch resolution to be in bits.

Thanks for the review!

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 3/4] hwmon: add Gateworks System Controller support
@ 2018-03-28 20:23       ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 20:23 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 10:00 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
>> The Gateworks System Controller has a hwmon sub-component that exposes
>> up to 16 ADC's, some of which are temperature sensors, others which are
>> voltage inputs. The ADC configuration (register mapping and name) is
>> configured via device-tree and varies board to board.
>>
>> Cc: Guenter Roeck <linux@roeck-us.net>
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>> v3:
>> - add voltage_raw input type and supporting fields
>> - add channel validation to is_visible function
>> - remove unnecessary channel validation from read/write functions
>>
>> v2:
>> - change license comment style
>> - remove DEBUG
>> - simplify regmap_bulk_read err check
>> - remove break after returns in switch statement
>> - fix fan setpoint buffer address
>> - remove unnecessary parens
>> - consistently use struct device *dev pointer
>> - change license/comment block
>> - add validation for hwmon child node props
>> - move parsing of of to own function
>> - use strlcpy to ensure null termination
>> - fix static array sizes and removed unnecessary initializers
>> - dynamically allocate channels
>> - fix fan input label
>> - support platform data
>> - fixed whitespace issues
>>
>>  drivers/hwmon/Kconfig                   |   9 +
>>  drivers/hwmon/Makefile                  |   1 +
>>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
>
> This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
> attributes.

ok - will add in next submission

>
<snip>
>>
>> +static int
>> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>> +            int channel, long *val)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +     struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
>> +     const struct gsc_hwmon_channel *ch;
>> +     int sz, ret;
>> +     u8 buf[3];
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>> +     switch (type) {
>> +     case hwmon_in:
>> +             ch = hwmon->in_ch[channel];
>> +             break;
>> +     case hwmon_temp:
>> +             ch = hwmon->temp_ch[channel];
>> +             break;
>> +     case hwmon_fan:
>> +             ch = hwmon->fan_ch[channel];
>> +             break;
>> +     default:
>> +             return -EOPNOTSUPP;
>> +     }
>> +
>> +     sz = (ch->type == type_voltage) ? 3 : 2;
>> +     ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
>> +     if (ret)
>> +             return ret;
>> +
>> +     *val = 0;
>> +     while (sz-- > 0)
>> +             *val |= (buf[sz] << (8*sz));
>
> Please use spaces before and after operators.

ok

>
>> +
>> +     switch (ch->type) {
>> +     case type_temperature:
>> +             if ((type == hwmon_temp) && *val > 0x8000)
>
> Please no unnecessary ( ).
>

ok

> Is there ever a situation where ch->type == type_temperature and type !=
> hwmon_temp ? Wouldn't that be a bug ?

I should not have been checking (type == hwmon_temp) there... will remove that.

>
>> +                     *val -= 0xffff;
>> +             break;
>> +     case type_voltage_raw:
>> +             /* scale based on ref voltage and resolution */
>> +             if (pdata->vreference && pdata->resolution) {
>> +                     *val *= pdata->vreference;
>> +                     *val /= pdata->resolution;
>> +             }
>> +             /* scale based on optional voltage divider */
>> +             if (ch->vdiv[0] && ch->vdiv[1]) {
>> +                     *val *= (ch->vdiv[0] + ch->vdiv[1]);
>> +                     *val /= ch->vdiv[1];
>> +             }
>
> This accepts both types of scaling. Is that intentional ?

yes that is intentional. One version of the GSC reports cooked
pre-scaled values that won't fall into either of these. Another
version of the GSC will report raw ADC values which will need the
vref/res scaling and those may have an optional voltage divider.

>
> I don't see any protection against overflows. What if pdata->vreference is
> larger than 256 on a system with sizeof(long) == 4 and the raw voltage
> as reported by the chip is 0xffffff ?
>
>> +             /* adjust by offset */
>> +             *val += ch->voffset;
>
> Similar to the above, this can result in an overflow on systems with
> sizeof(long) == 4.
>

true - I will add some overflow checking

>> +             break;
>
> There should be a default case as well as 'case type_voltage:'
> with a break; statement and a comment indicating that no adjustment
> is needed.
>

ok

>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int
>> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
>> +                   u32 attr, int channel, const char **buf)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>
> I seriously wonder if all those dev_dbg() statements add value.

only to me while I was writing/testing; I will remove them

>
>> +     switch (type) {
>> +     case hwmon_in:
>> +             *buf = hwmon->in_ch[channel]->name;
>> +             break;
>> +     case hwmon_temp:
>> +             *buf = hwmon->temp_ch[channel]->name;
>> +             break;
>> +     case hwmon_fan:
>> +             *buf = hwmon->fan_ch[channel]->name;
>> +             break;
>> +     default:
>> +             return -ENOTSUPP;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int
>> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>> +             int channel, long val)
>> +{
>> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
>> +     u8 buf[2];
>> +
>> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
>> +             channel);
>> +     switch (type) {
>> +     case hwmon_fan:
>> +             buf[0] = val & 0xff;
>> +             buf[1] = (val >> 8) & 0xff;
>> +             return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
>> +                                      hwmon->fan_ch[channel]->reg, buf, 2);
>
> fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
> I have no idea what this is supposed to achieve, but whatever it is, it is wrong.
>
> Please stick with the ABI.

ok, I see that now. Do you have any recommendation of what I should
use for these temperature setpoints that control when the fan pwm is
adjusted?

>
>> +     default:
>> +             break;
>> +     }
>> +
>> +     return -EOPNOTSUPP;
>> +}
>> +
>> +static umode_t
>> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
>> +                  int ch)
>> +{
>> +     const struct gsc_hwmon_data *hwmon = _data;
>> +     struct device *dev = hwmon->gsc->dev;
>> +     umode_t mode = 0;
>> +
>> +     switch (type) {
>> +     case hwmon_fan:
>> +             if (ch >= GSC_HWMON_MAX_FAN_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?

No, I suppose not because the ch input comes from a hwmon node that
was created by the driver registering the entities and a check was
done there to avoid overflow. I will remove these.

>
>> +             mode = S_IRUGO;
>> +             if (attr == hwmon_fan_input)
>> +                     mode |= S_IWUSR;
>
> fanX_input is a read-only attribute per ABI.
>
>> +             break;
>> +     case hwmon_temp:
>> +             if (ch >= GSC_HWMON_MAX_TEMP_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?
>
>> +             mode = S_IRUGO;
>> +             break;
>> +     case hwmon_in:
>> +             if (ch >= GSC_HWMON_MAX_IN_CH)
>> +                     return -EOPNOTSUPP;
>
> Can that ever happen ?
>
>> +             mode = S_IRUGO;
>> +             break;
>> +     default:
>> +             return -EOPNOTSUPP;
>> +     }
>> +     dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
>> +             attr, ch, mode);
>> +
>> +     return mode;
>> +}
>> +
>> +static const struct hwmon_ops gsc_hwmon_ops = {
>> +     .is_visible = gsc_hwmon_is_visible,
>> +     .read = gsc_hwmon_read,
>> +     .read_string = gsc_hwmon_read_string,
>> +     .write = gsc_hwmon_write,
>> +};
>> +
>> +static struct gsc_hwmon_platform_data *
>> +gsc_hwmon_get_devtree_pdata(struct device *dev)
>> +{
>> +     struct gsc_hwmon_platform_data *pdata;
>> +     struct gsc_hwmon_channel *ch;
>> +     struct fwnode_handle *child;
>> +     const char *type;
>> +     int nchannels;
>> +
>> +     nchannels = device_get_child_node_count(dev);
>> +     dev_dbg(dev, "channels=%d\n", nchannels);
>> +     if (nchannels == 0)
>> +             return ERR_PTR(-ENODEV);
>> +
>> +     pdata = devm_kzalloc(dev,
>> +                          sizeof(*pdata) + nchannels * sizeof(*ch),
>> +                          GFP_KERNEL);
>> +     if (!pdata)
>> +             return ERR_PTR(-ENOMEM);
>> +     ch = (struct gsc_hwmon_channel *)(pdata + 1);
>> +     pdata->channels = ch;
>> +     pdata->nchannels = nchannels;
>> +
>> +     device_property_read_u32(dev, "gw,reference-voltage",
>> +                              &pdata->vreference);
>> +     device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
>> +
>> +     /* allocate structures for channels and count instances of each type */
>> +     device_for_each_child_node(dev, child) {
>> +             if (fwnode_property_read_string(child, "label", &ch->name)) {
>> +                     dev_err(dev, "channel without label\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
>> +                     dev_err(dev, "channel without reg\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (fwnode_property_read_string(child, "type", &type)) {
>> +                     dev_err(dev, "channel without type\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +             if (!strcasecmp(type, "gw,hwmon-temperature"))
>> +                     ch->type = type_temperature;
>> +             else if (!strcasecmp(type, "gw,hwmon-voltage"))
>> +                     ch->type = type_voltage;
>> +             else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
>> +                     ch->type = type_voltage_raw;
>> +             else if (!strcasecmp(type, "gw,hwmon-fan"))
>> +                     ch->type = type_fan;
>> +             else {
>> +                     dev_err(dev, "channel without type\n");
>> +                     fwnode_handle_put(child);
>> +                     return ERR_PTR(-EINVAL);
>> +             }
>> +
>> +             fwnode_property_read_u32(child, "gw,voltage-offset",
>> +                     &ch->voffset);
>
> Note that while it is technically ok to keep voltages internally in mV,
> devicetree will likely require specificayion in uV. For accuracy, it might be
> better to perform any calculations on that base and convert to mV for display
> purposes.

I'm happy to change them if that really is the standard but it doesn't
look like it is a standard?

>
>> +             fwnode_property_read_u32_array(child, "gw,voltage-divider",
>> +                     ch->vdiv, ARRAY_SIZE(ch->vdiv));
>> +             dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
>> +                     ch->name);
>> +             ch++;
>> +     }
>> +
>> +     return pdata;
>> +}
>> +
>> +static int gsc_hwmon_probe(struct platform_device *pdev)
>> +{
>> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> +     struct device *dev = &pdev->dev;
>> +     struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
>> +     struct gsc_hwmon_data *hwmon;
>> +     int i, i_in, i_temp, i_fan;
>> +
>> +     if (!pdata) {
>> +             pdata = gsc_hwmon_get_devtree_pdata(dev);
>> +             if (IS_ERR(pdata))
>> +                     return PTR_ERR(pdata);
>> +     }
>> +
>> +     hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
>> +     if (!hwmon)
>> +             return -ENOMEM;
>> +     hwmon->gsc = gsc;
>> +     hwmon->pdata = pdata;
>> +
>> +     for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
>> +          i < hwmon->pdata->nchannels; i++) {
>> +             const struct gsc_hwmon_channel *ch = &pdata->channels[i];
>> +
>> +             if (ch->reg > GSC_HWMON_MAX_REG) {
>> +                     dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
>> +                     return -EINVAL;
>> +             }
>> +             switch (ch->type) {
>> +             case type_temperature:
>> +                     if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
>> +                             dev_err(dev, "too many temp channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->temp_ch[i_temp] = ch;
>> +                     hwmon->temp_config[i_temp] = HWMON_T_INPUT |
>> +                                                  HWMON_T_LABEL;
>> +                     i_temp++;
>> +                     break;
>> +             case type_voltage:
>> +             case type_voltage_raw:
>> +                     if (i_in == GSC_HWMON_MAX_IN_CH) {
>> +                             dev_err(dev, "too many voltage channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->in_ch[i_in] = ch;
>> +                     hwmon->in_config[i_in] =
>> +                             HWMON_I_INPUT | HWMON_I_LABEL;
>> +                     i_in++;
>> +                     break;
>> +             case type_fan:
>> +                     if (i_fan == GSC_HWMON_MAX_FAN_CH) {
>> +                             dev_err(dev, "too many voltage channels\n");
>> +                             return -EINVAL;
>> +                     }
>> +                     hwmon->fan_ch[i_fan] = ch;
>> +                     hwmon->fan_config[i_fan] =
>> +                             HWMON_F_INPUT | HWMON_F_LABEL;
>> +                     i_fan++;
>> +                     break;
>> +             default:
>> +                     dev_err(dev, "invalid type: %d\n", ch->type);
>> +                     return -EINVAL;
>> +             }
>> +             dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
>> +                     ch->type, ch->name);
>> +     }
>> +
>> +     /* terminate channel config lists */
>> +     hwmon->temp_config[i_temp] = 0;
>> +     hwmon->in_config[i_in] = 0;
>> +     hwmon->fan_config[i_fan] = 0;
>
> 'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
> is unnecessary.

right - will remove

>
>> +
>> +     /* setup config structures */
>> +     hwmon->chip.ops = &gsc_hwmon_ops;
>> +     hwmon->chip.info = hwmon->info;
>> +     hwmon->info[0] = &hwmon->temp_info;
>> +     hwmon->info[1] = &hwmon->in_info;
>> +     hwmon->info[2] = &hwmon->fan_info;
>> +     hwmon->temp_info.type = hwmon_temp;
>> +     hwmon->temp_info.config = hwmon->temp_config;
>> +     hwmon->in_info.type = hwmon_in;
>> +     hwmon->in_info.config = hwmon->in_config;
>> +     hwmon->fan_info.type = hwmon_fan;
>> +     hwmon->fan_info.config = hwmon->fan_config;
>> +
>> +     hwmon->dev = devm_hwmon_device_register_with_info(dev,
>> +                                                       KBUILD_MODNAME, hwmon,
>> +                                                       &hwmon->chip, NULL);
>> +     return PTR_ERR_OR_ZERO(hwmon->dev);
>> +}
>> +
>> +static const struct of_device_id gsc_hwmon_of_match[] = {
>> +     { .compatible = "gw,gsc-hwmon", },
>> +     {}
>> +};
>> +
>> +static struct platform_driver gsc_hwmon_driver = {
>> +     .driver = {
>> +             .name = KBUILD_MODNAME,
>> +             .of_match_table = gsc_hwmon_of_match,
>> +     },
>> +     .probe = gsc_hwmon_probe,
>> +};
>> +
>> +module_platform_driver(gsc_hwmon_driver);
>> +
>> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
>> +MODULE_DESCRIPTION("GSC hardware monitor driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
>> new file mode 100644
>> index 0000000..5e59846
>> --- /dev/null
>> +++ b/include/linux/platform_data/gsc_hwmon.h
>> @@ -0,0 +1,43 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#ifndef _GSC_HWMON_H
>> +#define _GSC_HWMON_H
>> +
>> +enum gsc_hwmon_type {
>> +     type_temperature,
>> +     type_voltage,
>> +     type_voltage_raw,
>> +     type_fan,
>> +};
>> +
>> +/**
>> + * struct gsc_hwmon_channel - configuration parameters
>> + * @reg:  I2C register offset
>> + * @type: channel type
>> + * @name: channel name
>> + * @voffset: voltage offset (mV)
>> + * @vdiv: voltage divider array (2 resistor values in ohms)
>> + */
>> +struct gsc_hwmon_channel {
>> +     unsigned int reg;
>> +     unsigned int type;
>> +     const char *name;
>> +     unsigned int voffset;
>
> It is interesting that only positive offset values are supported.
> Is that intentional ?

yes, they are only used for voltage rails that have a diode drop to
adjust for and will only be positive. They are not used to fine tune
offsets.

>
>> +     unsigned int vdiv[2];
>> +};
>> +
>> +/**
>> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
>> + * @channels:        pointer to array of gsc_hwmon_channel structures
>> + *           describing channels
>> + * @nchannels:       number of elements in @channels array
>> + * @vreference: voltage reference (mV)
>> + * @resolution: ADC resolution
>
> Resolution in what ?

That's the bit-resolution of the ADC so I'll switch to that notation
and document it as such. I just realized that the vref/resolution can
technically change per ADC rail so I'll leave them in but move them
down into the child nodes and switch resolution to be in bits.

Thanks for the review!

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 3/4] hwmon: add Gateworks System Controller support
  2018-03-28 20:23       ` Tim Harvey
@ 2018-03-28 20:33         ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 20:33 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 01:23:59PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 10:00 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
> >> The Gateworks System Controller has a hwmon sub-component that exposes
> >> up to 16 ADC's, some of which are temperature sensors, others which are
> >> voltage inputs. The ADC configuration (register mapping and name) is
> >> configured via device-tree and varies board to board.
> >>
> >> Cc: Guenter Roeck <linux@roeck-us.net>
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >> v3:
> >> - add voltage_raw input type and supporting fields
> >> - add channel validation to is_visible function
> >> - remove unnecessary channel validation from read/write functions
> >>
> >> v2:
> >> - change license comment style
> >> - remove DEBUG
> >> - simplify regmap_bulk_read err check
> >> - remove break after returns in switch statement
> >> - fix fan setpoint buffer address
> >> - remove unnecessary parens
> >> - consistently use struct device *dev pointer
> >> - change license/comment block
> >> - add validation for hwmon child node props
> >> - move parsing of of to own function
> >> - use strlcpy to ensure null termination
> >> - fix static array sizes and removed unnecessary initializers
> >> - dynamically allocate channels
> >> - fix fan input label
> >> - support platform data
> >> - fixed whitespace issues
> >>
> >>  drivers/hwmon/Kconfig                   |   9 +
> >>  drivers/hwmon/Makefile                  |   1 +
> >>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
> >
> > This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
> > attributes.
> 
> ok - will add in next submission
> 
> >
> <snip>
> >>
> >> +static int
> >> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> >> +            int channel, long *val)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +     struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
> >> +     const struct gsc_hwmon_channel *ch;
> >> +     int sz, ret;
> >> +     u8 buf[3];
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >> +     switch (type) {
> >> +     case hwmon_in:
> >> +             ch = hwmon->in_ch[channel];
> >> +             break;
> >> +     case hwmon_temp:
> >> +             ch = hwmon->temp_ch[channel];
> >> +             break;
> >> +     case hwmon_fan:
> >> +             ch = hwmon->fan_ch[channel];
> >> +             break;
> >> +     default:
> >> +             return -EOPNOTSUPP;
> >> +     }
> >> +
> >> +     sz = (ch->type == type_voltage) ? 3 : 2;
> >> +     ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
> >> +     if (ret)
> >> +             return ret;
> >> +
> >> +     *val = 0;
> >> +     while (sz-- > 0)
> >> +             *val |= (buf[sz] << (8*sz));
> >
> > Please use spaces before and after operators.
> 
> ok
> 
> >
> >> +
> >> +     switch (ch->type) {
> >> +     case type_temperature:
> >> +             if ((type == hwmon_temp) && *val > 0x8000)
> >
> > Please no unnecessary ( ).
> >
> 
> ok
> 
> > Is there ever a situation where ch->type == type_temperature and type !=
> > hwmon_temp ? Wouldn't that be a bug ?
> 
> I should not have been checking (type == hwmon_temp) there... will remove that.
> 
> >
> >> +                     *val -= 0xffff;
> >> +             break;
> >> +     case type_voltage_raw:
> >> +             /* scale based on ref voltage and resolution */
> >> +             if (pdata->vreference && pdata->resolution) {
> >> +                     *val *= pdata->vreference;
> >> +                     *val /= pdata->resolution;
> >> +             }
> >> +             /* scale based on optional voltage divider */
> >> +             if (ch->vdiv[0] && ch->vdiv[1]) {
> >> +                     *val *= (ch->vdiv[0] + ch->vdiv[1]);
> >> +                     *val /= ch->vdiv[1];
> >> +             }
> >
> > This accepts both types of scaling. Is that intentional ?
> 
> yes that is intentional. One version of the GSC reports cooked
> pre-scaled values that won't fall into either of these. Another
> version of the GSC will report raw ADC values which will need the
> vref/res scaling and those may have an optional voltage divider.
> 
> >
> > I don't see any protection against overflows. What if pdata->vreference is
> > larger than 256 on a system with sizeof(long) == 4 and the raw voltage
> > as reported by the chip is 0xffffff ?
> >
> >> +             /* adjust by offset */
> >> +             *val += ch->voffset;
> >
> > Similar to the above, this can result in an overflow on systems with
> > sizeof(long) == 4.
> >
> 
> true - I will add some overflow checking
> 
> >> +             break;
> >
> > There should be a default case as well as 'case type_voltage:'
> > with a break; statement and a comment indicating that no adjustment
> > is needed.
> >
> 
> ok
> 
> >> +     }
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int
> >> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
> >> +                   u32 attr, int channel, const char **buf)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >
> > I seriously wonder if all those dev_dbg() statements add value.
> 
> only to me while I was writing/testing; I will remove them
> 
> >
> >> +     switch (type) {
> >> +     case hwmon_in:
> >> +             *buf = hwmon->in_ch[channel]->name;
> >> +             break;
> >> +     case hwmon_temp:
> >> +             *buf = hwmon->temp_ch[channel]->name;
> >> +             break;
> >> +     case hwmon_fan:
> >> +             *buf = hwmon->fan_ch[channel]->name;
> >> +             break;
> >> +     default:
> >> +             return -ENOTSUPP;
> >> +     }
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int
> >> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> >> +             int channel, long val)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +     u8 buf[2];
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >> +     switch (type) {
> >> +     case hwmon_fan:
> >> +             buf[0] = val & 0xff;
> >> +             buf[1] = (val >> 8) & 0xff;
> >> +             return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
> >> +                                      hwmon->fan_ch[channel]->reg, buf, 2);
> >
> > fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
> > I have no idea what this is supposed to achieve, but whatever it is, it is wrong.
> >
> > Please stick with the ABI.
> 
> ok, I see that now. Do you have any recommendation of what I should
> use for these temperature setpoints that control when the fan pwm is
> adjusted?
> 

As mentioned in the other thread,

pwm[1-*]_auto_point[1-*]_pwm
pwm[1-*]_auto_point[1-*]_temp

seems to be the best fit. Downside is that we don't have those supported in
the new ABI. You have two options: add the attributes to the ABI and use them,
or define your own sysfs group for it. Either way is fine with me (I would
prefer to have it added to the API, though).

> >
> >> +     default:
> >> +             break;
> >> +     }
> >> +
> >> +     return -EOPNOTSUPP;
> >> +}
> >> +
> >> +static umode_t
> >> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> >> +                  int ch)
> >> +{
> >> +     const struct gsc_hwmon_data *hwmon = _data;
> >> +     struct device *dev = hwmon->gsc->dev;
> >> +     umode_t mode = 0;
> >> +
> >> +     switch (type) {
> >> +     case hwmon_fan:
> >> +             if (ch >= GSC_HWMON_MAX_FAN_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> 
> No, I suppose not because the ch input comes from a hwmon node that
> was created by the driver registering the entities and a check was
> done there to avoid overflow. I will remove these.
> 
> >
> >> +             mode = S_IRUGO;
> >> +             if (attr == hwmon_fan_input)
> >> +                     mode |= S_IWUSR;
> >
> > fanX_input is a read-only attribute per ABI.
> >
> >> +             break;
> >> +     case hwmon_temp:
> >> +             if (ch >= GSC_HWMON_MAX_TEMP_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> >
> >> +             mode = S_IRUGO;
> >> +             break;
> >> +     case hwmon_in:
> >> +             if (ch >= GSC_HWMON_MAX_IN_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> >
> >> +             mode = S_IRUGO;
> >> +             break;
> >> +     default:
> >> +             return -EOPNOTSUPP;
> >> +     }
> >> +     dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
> >> +             attr, ch, mode);
> >> +
> >> +     return mode;
> >> +}
> >> +
> >> +static const struct hwmon_ops gsc_hwmon_ops = {
> >> +     .is_visible = gsc_hwmon_is_visible,
> >> +     .read = gsc_hwmon_read,
> >> +     .read_string = gsc_hwmon_read_string,
> >> +     .write = gsc_hwmon_write,
> >> +};
> >> +
> >> +static struct gsc_hwmon_platform_data *
> >> +gsc_hwmon_get_devtree_pdata(struct device *dev)
> >> +{
> >> +     struct gsc_hwmon_platform_data *pdata;
> >> +     struct gsc_hwmon_channel *ch;
> >> +     struct fwnode_handle *child;
> >> +     const char *type;
> >> +     int nchannels;
> >> +
> >> +     nchannels = device_get_child_node_count(dev);
> >> +     dev_dbg(dev, "channels=%d\n", nchannels);
> >> +     if (nchannels == 0)
> >> +             return ERR_PTR(-ENODEV);
> >> +
> >> +     pdata = devm_kzalloc(dev,
> >> +                          sizeof(*pdata) + nchannels * sizeof(*ch),
> >> +                          GFP_KERNEL);
> >> +     if (!pdata)
> >> +             return ERR_PTR(-ENOMEM);
> >> +     ch = (struct gsc_hwmon_channel *)(pdata + 1);
> >> +     pdata->channels = ch;
> >> +     pdata->nchannels = nchannels;
> >> +
> >> +     device_property_read_u32(dev, "gw,reference-voltage",
> >> +                              &pdata->vreference);
> >> +     device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
> >> +
> >> +     /* allocate structures for channels and count instances of each type */
> >> +     device_for_each_child_node(dev, child) {
> >> +             if (fwnode_property_read_string(child, "label", &ch->name)) {
> >> +                     dev_err(dev, "channel without label\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
> >> +                     dev_err(dev, "channel without reg\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (fwnode_property_read_string(child, "type", &type)) {
> >> +                     dev_err(dev, "channel without type\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (!strcasecmp(type, "gw,hwmon-temperature"))
> >> +                     ch->type = type_temperature;
> >> +             else if (!strcasecmp(type, "gw,hwmon-voltage"))
> >> +                     ch->type = type_voltage;
> >> +             else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
> >> +                     ch->type = type_voltage_raw;
> >> +             else if (!strcasecmp(type, "gw,hwmon-fan"))
> >> +                     ch->type = type_fan;
> >> +             else {
> >> +                     dev_err(dev, "channel without type\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +
> >> +             fwnode_property_read_u32(child, "gw,voltage-offset",
> >> +                     &ch->voffset);
> >
> > Note that while it is technically ok to keep voltages internally in mV,
> > devicetree will likely require specificayion in uV. For accuracy, it might be
> > better to perform any calculations on that base and convert to mV for display
> > purposes.
> 
> I'm happy to change them if that really is the standard but it doesn't
> look like it is a standard?
> 

Again, please discuss with Rob.

> >
> >> +             fwnode_property_read_u32_array(child, "gw,voltage-divider",
> >> +                     ch->vdiv, ARRAY_SIZE(ch->vdiv));
> >> +             dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
> >> +                     ch->name);
> >> +             ch++;
> >> +     }
> >> +
> >> +     return pdata;
> >> +}
> >> +
> >> +static int gsc_hwmon_probe(struct platform_device *pdev)
> >> +{
> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> >> +     struct device *dev = &pdev->dev;
> >> +     struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
> >> +     struct gsc_hwmon_data *hwmon;
> >> +     int i, i_in, i_temp, i_fan;
> >> +
> >> +     if (!pdata) {
> >> +             pdata = gsc_hwmon_get_devtree_pdata(dev);
> >> +             if (IS_ERR(pdata))
> >> +                     return PTR_ERR(pdata);
> >> +     }
> >> +
> >> +     hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> >> +     if (!hwmon)
> >> +             return -ENOMEM;
> >> +     hwmon->gsc = gsc;
> >> +     hwmon->pdata = pdata;
> >> +
> >> +     for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
> >> +          i < hwmon->pdata->nchannels; i++) {
> >> +             const struct gsc_hwmon_channel *ch = &pdata->channels[i];
> >> +
> >> +             if (ch->reg > GSC_HWMON_MAX_REG) {
> >> +                     dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
> >> +                     return -EINVAL;
> >> +             }
> >> +             switch (ch->type) {
> >> +             case type_temperature:
> >> +                     if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
> >> +                             dev_err(dev, "too many temp channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->temp_ch[i_temp] = ch;
> >> +                     hwmon->temp_config[i_temp] = HWMON_T_INPUT |
> >> +                                                  HWMON_T_LABEL;
> >> +                     i_temp++;
> >> +                     break;
> >> +             case type_voltage:
> >> +             case type_voltage_raw:
> >> +                     if (i_in == GSC_HWMON_MAX_IN_CH) {
> >> +                             dev_err(dev, "too many voltage channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->in_ch[i_in] = ch;
> >> +                     hwmon->in_config[i_in] =
> >> +                             HWMON_I_INPUT | HWMON_I_LABEL;
> >> +                     i_in++;
> >> +                     break;
> >> +             case type_fan:
> >> +                     if (i_fan == GSC_HWMON_MAX_FAN_CH) {
> >> +                             dev_err(dev, "too many voltage channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->fan_ch[i_fan] = ch;
> >> +                     hwmon->fan_config[i_fan] =
> >> +                             HWMON_F_INPUT | HWMON_F_LABEL;
> >> +                     i_fan++;
> >> +                     break;
> >> +             default:
> >> +                     dev_err(dev, "invalid type: %d\n", ch->type);
> >> +                     return -EINVAL;
> >> +             }
> >> +             dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
> >> +                     ch->type, ch->name);
> >> +     }
> >> +
> >> +     /* terminate channel config lists */
> >> +     hwmon->temp_config[i_temp] = 0;
> >> +     hwmon->in_config[i_in] = 0;
> >> +     hwmon->fan_config[i_fan] = 0;
> >
> > 'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
> > is unnecessary.
> 
> right - will remove
> 
> >
> >> +
> >> +     /* setup config structures */
> >> +     hwmon->chip.ops = &gsc_hwmon_ops;
> >> +     hwmon->chip.info = hwmon->info;
> >> +     hwmon->info[0] = &hwmon->temp_info;
> >> +     hwmon->info[1] = &hwmon->in_info;
> >> +     hwmon->info[2] = &hwmon->fan_info;
> >> +     hwmon->temp_info.type = hwmon_temp;
> >> +     hwmon->temp_info.config = hwmon->temp_config;
> >> +     hwmon->in_info.type = hwmon_in;
> >> +     hwmon->in_info.config = hwmon->in_config;
> >> +     hwmon->fan_info.type = hwmon_fan;
> >> +     hwmon->fan_info.config = hwmon->fan_config;
> >> +
> >> +     hwmon->dev = devm_hwmon_device_register_with_info(dev,
> >> +                                                       KBUILD_MODNAME, hwmon,
> >> +                                                       &hwmon->chip, NULL);
> >> +     return PTR_ERR_OR_ZERO(hwmon->dev);
> >> +}
> >> +
> >> +static const struct of_device_id gsc_hwmon_of_match[] = {
> >> +     { .compatible = "gw,gsc-hwmon", },
> >> +     {}
> >> +};
> >> +
> >> +static struct platform_driver gsc_hwmon_driver = {
> >> +     .driver = {
> >> +             .name = KBUILD_MODNAME,
> >> +             .of_match_table = gsc_hwmon_of_match,
> >> +     },
> >> +     .probe = gsc_hwmon_probe,
> >> +};
> >> +
> >> +module_platform_driver(gsc_hwmon_driver);
> >> +
> >> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> >> +MODULE_DESCRIPTION("GSC hardware monitor driver");
> >> +MODULE_LICENSE("GPL v2");
> >> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
> >> new file mode 100644
> >> index 0000000..5e59846
> >> --- /dev/null
> >> +++ b/include/linux/platform_data/gsc_hwmon.h
> >> @@ -0,0 +1,43 @@
> >> +/* SPDX-License-Identifier: GPL-2.0 */
> >> +#ifndef _GSC_HWMON_H
> >> +#define _GSC_HWMON_H
> >> +
> >> +enum gsc_hwmon_type {
> >> +     type_temperature,
> >> +     type_voltage,
> >> +     type_voltage_raw,
> >> +     type_fan,
> >> +};
> >> +
> >> +/**
> >> + * struct gsc_hwmon_channel - configuration parameters
> >> + * @reg:  I2C register offset
> >> + * @type: channel type
> >> + * @name: channel name
> >> + * @voffset: voltage offset (mV)
> >> + * @vdiv: voltage divider array (2 resistor values in ohms)
> >> + */
> >> +struct gsc_hwmon_channel {
> >> +     unsigned int reg;
> >> +     unsigned int type;
> >> +     const char *name;
> >> +     unsigned int voffset;
> >
> > It is interesting that only positive offset values are supported.
> > Is that intentional ?
> 
> yes, they are only used for voltage rails that have a diode drop to
> adjust for and will only be positive. They are not used to fine tune
> offsets.
> 
> >
> >> +     unsigned int vdiv[2];
> >> +};
> >> +
> >> +/**
> >> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
> >> + * @channels:        pointer to array of gsc_hwmon_channel structures
> >> + *           describing channels
> >> + * @nchannels:       number of elements in @channels array
> >> + * @vreference: voltage reference (mV)
> >> + * @resolution: ADC resolution
> >
> > Resolution in what ?
> 
> That's the bit-resolution of the ADC so I'll switch to that notation
> and document it as such. I just realized that the vref/resolution can
> technically change per ADC rail so I'll leave them in but move them
> down into the child nodes and switch resolution to be in bits.
> 
> Thanks for the review!
> 
> Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 3/4] hwmon: add Gateworks System Controller support
@ 2018-03-28 20:33         ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-28 20:33 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 01:23:59PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 10:00 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:02AM -0700, Tim Harvey wrote:
> >> The Gateworks System Controller has a hwmon sub-component that exposes
> >> up to 16 ADC's, some of which are temperature sensors, others which are
> >> voltage inputs. The ADC configuration (register mapping and name) is
> >> configured via device-tree and varies board to board.
> >>
> >> Cc: Guenter Roeck <linux@roeck-us.net>
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >> v3:
> >> - add voltage_raw input type and supporting fields
> >> - add channel validation to is_visible function
> >> - remove unnecessary channel validation from read/write functions
> >>
> >> v2:
> >> - change license comment style
> >> - remove DEBUG
> >> - simplify regmap_bulk_read err check
> >> - remove break after returns in switch statement
> >> - fix fan setpoint buffer address
> >> - remove unnecessary parens
> >> - consistently use struct device *dev pointer
> >> - change license/comment block
> >> - add validation for hwmon child node props
> >> - move parsing of of to own function
> >> - use strlcpy to ensure null termination
> >> - fix static array sizes and removed unnecessary initializers
> >> - dynamically allocate channels
> >> - fix fan input label
> >> - support platform data
> >> - fixed whitespace issues
> >>
> >>  drivers/hwmon/Kconfig                   |   9 +
> >>  drivers/hwmon/Makefile                  |   1 +
> >>  drivers/hwmon/gsc-hwmon.c               | 368 ++++++++++++++++++++++++++++++++
> >
> > This will require a matching Documentation/hwmon/gsc-hwmon to explain supported
> > attributes.
> 
> ok - will add in next submission
> 
> >
> <snip>
> >>
> >> +static int
> >> +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> >> +            int channel, long *val)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +     struct gsc_hwmon_platform_data *pdata = hwmon->pdata;
> >> +     const struct gsc_hwmon_channel *ch;
> >> +     int sz, ret;
> >> +     u8 buf[3];
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >> +     switch (type) {
> >> +     case hwmon_in:
> >> +             ch = hwmon->in_ch[channel];
> >> +             break;
> >> +     case hwmon_temp:
> >> +             ch = hwmon->temp_ch[channel];
> >> +             break;
> >> +     case hwmon_fan:
> >> +             ch = hwmon->fan_ch[channel];
> >> +             break;
> >> +     default:
> >> +             return -EOPNOTSUPP;
> >> +     }
> >> +
> >> +     sz = (ch->type == type_voltage) ? 3 : 2;
> >> +     ret = regmap_bulk_read(hwmon->gsc->regmap_hwmon, ch->reg, buf, sz);
> >> +     if (ret)
> >> +             return ret;
> >> +
> >> +     *val = 0;
> >> +     while (sz-- > 0)
> >> +             *val |= (buf[sz] << (8*sz));
> >
> > Please use spaces before and after operators.
> 
> ok
> 
> >
> >> +
> >> +     switch (ch->type) {
> >> +     case type_temperature:
> >> +             if ((type == hwmon_temp) && *val > 0x8000)
> >
> > Please no unnecessary ( ).
> >
> 
> ok
> 
> > Is there ever a situation where ch->type == type_temperature and type !=
> > hwmon_temp ? Wouldn't that be a bug ?
> 
> I should not have been checking (type == hwmon_temp) there... will remove that.
> 
> >
> >> +                     *val -= 0xffff;
> >> +             break;
> >> +     case type_voltage_raw:
> >> +             /* scale based on ref voltage and resolution */
> >> +             if (pdata->vreference && pdata->resolution) {
> >> +                     *val *= pdata->vreference;
> >> +                     *val /= pdata->resolution;
> >> +             }
> >> +             /* scale based on optional voltage divider */
> >> +             if (ch->vdiv[0] && ch->vdiv[1]) {
> >> +                     *val *= (ch->vdiv[0] + ch->vdiv[1]);
> >> +                     *val /= ch->vdiv[1];
> >> +             }
> >
> > This accepts both types of scaling. Is that intentional ?
> 
> yes that is intentional. One version of the GSC reports cooked
> pre-scaled values that won't fall into either of these. Another
> version of the GSC will report raw ADC values which will need the
> vref/res scaling and those may have an optional voltage divider.
> 
> >
> > I don't see any protection against overflows. What if pdata->vreference is
> > larger than 256 on a system with sizeof(long) == 4 and the raw voltage
> > as reported by the chip is 0xffffff ?
> >
> >> +             /* adjust by offset */
> >> +             *val += ch->voffset;
> >
> > Similar to the above, this can result in an overflow on systems with
> > sizeof(long) == 4.
> >
> 
> true - I will add some overflow checking
> 
> >> +             break;
> >
> > There should be a default case as well as 'case type_voltage:'
> > with a break; statement and a comment indicating that no adjustment
> > is needed.
> >
> 
> ok
> 
> >> +     }
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int
> >> +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
> >> +                   u32 attr, int channel, const char **buf)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >
> > I seriously wonder if all those dev_dbg() statements add value.
> 
> only to me while I was writing/testing; I will remove them
> 
> >
> >> +     switch (type) {
> >> +     case hwmon_in:
> >> +             *buf = hwmon->in_ch[channel]->name;
> >> +             break;
> >> +     case hwmon_temp:
> >> +             *buf = hwmon->temp_ch[channel]->name;
> >> +             break;
> >> +     case hwmon_fan:
> >> +             *buf = hwmon->fan_ch[channel]->name;
> >> +             break;
> >> +     default:
> >> +             return -ENOTSUPP;
> >> +     }
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int
> >> +gsc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> >> +             int channel, long val)
> >> +{
> >> +     struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
> >> +     u8 buf[2];
> >> +
> >> +     dev_dbg(dev, "%s type=%d attr=%d channel=%d\n", __func__, type, attr,
> >> +             channel);
> >> +     switch (type) {
> >> +     case hwmon_fan:
> >> +             buf[0] = val & 0xff;
> >> +             buf[1] = (val >> 8) & 0xff;
> >> +             return regmap_bulk_write(hwmon->gsc->regmap_hwmon,
> >> +                                      hwmon->fan_ch[channel]->reg, buf, 2);
> >
> > fanX_input reports the fan speed. By its nature, a fan speed is not writeable.
> > I have no idea what this is supposed to achieve, but whatever it is, it is wrong.
> >
> > Please stick with the ABI.
> 
> ok, I see that now. Do you have any recommendation of what I should
> use for these temperature setpoints that control when the fan pwm is
> adjusted?
> 

As mentioned in the other thread,

pwm[1-*]_auto_point[1-*]_pwm
pwm[1-*]_auto_point[1-*]_temp

seems to be the best fit. Downside is that we don't have those supported in
the new ABI. You have two options: add the attributes to the ABI and use them,
or define your own sysfs group for it. Either way is fine with me (I would
prefer to have it added to the API, though).

> >
> >> +     default:
> >> +             break;
> >> +     }
> >> +
> >> +     return -EOPNOTSUPP;
> >> +}
> >> +
> >> +static umode_t
> >> +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> >> +                  int ch)
> >> +{
> >> +     const struct gsc_hwmon_data *hwmon = _data;
> >> +     struct device *dev = hwmon->gsc->dev;
> >> +     umode_t mode = 0;
> >> +
> >> +     switch (type) {
> >> +     case hwmon_fan:
> >> +             if (ch >= GSC_HWMON_MAX_FAN_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> 
> No, I suppose not because the ch input comes from a hwmon node that
> was created by the driver registering the entities and a check was
> done there to avoid overflow. I will remove these.
> 
> >
> >> +             mode = S_IRUGO;
> >> +             if (attr == hwmon_fan_input)
> >> +                     mode |= S_IWUSR;
> >
> > fanX_input is a read-only attribute per ABI.
> >
> >> +             break;
> >> +     case hwmon_temp:
> >> +             if (ch >= GSC_HWMON_MAX_TEMP_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> >
> >> +             mode = S_IRUGO;
> >> +             break;
> >> +     case hwmon_in:
> >> +             if (ch >= GSC_HWMON_MAX_IN_CH)
> >> +                     return -EOPNOTSUPP;
> >
> > Can that ever happen ?
> >
> >> +             mode = S_IRUGO;
> >> +             break;
> >> +     default:
> >> +             return -EOPNOTSUPP;
> >> +     }
> >> +     dev_dbg(dev, "%s type=%d attr=%d ch=%d mode=0x%x\n", __func__, type,
> >> +             attr, ch, mode);
> >> +
> >> +     return mode;
> >> +}
> >> +
> >> +static const struct hwmon_ops gsc_hwmon_ops = {
> >> +     .is_visible = gsc_hwmon_is_visible,
> >> +     .read = gsc_hwmon_read,
> >> +     .read_string = gsc_hwmon_read_string,
> >> +     .write = gsc_hwmon_write,
> >> +};
> >> +
> >> +static struct gsc_hwmon_platform_data *
> >> +gsc_hwmon_get_devtree_pdata(struct device *dev)
> >> +{
> >> +     struct gsc_hwmon_platform_data *pdata;
> >> +     struct gsc_hwmon_channel *ch;
> >> +     struct fwnode_handle *child;
> >> +     const char *type;
> >> +     int nchannels;
> >> +
> >> +     nchannels = device_get_child_node_count(dev);
> >> +     dev_dbg(dev, "channels=%d\n", nchannels);
> >> +     if (nchannels == 0)
> >> +             return ERR_PTR(-ENODEV);
> >> +
> >> +     pdata = devm_kzalloc(dev,
> >> +                          sizeof(*pdata) + nchannels * sizeof(*ch),
> >> +                          GFP_KERNEL);
> >> +     if (!pdata)
> >> +             return ERR_PTR(-ENOMEM);
> >> +     ch = (struct gsc_hwmon_channel *)(pdata + 1);
> >> +     pdata->channels = ch;
> >> +     pdata->nchannels = nchannels;
> >> +
> >> +     device_property_read_u32(dev, "gw,reference-voltage",
> >> +                              &pdata->vreference);
> >> +     device_property_read_u32(dev, "gw,resolution", &pdata->resolution);
> >> +
> >> +     /* allocate structures for channels and count instances of each type */
> >> +     device_for_each_child_node(dev, child) {
> >> +             if (fwnode_property_read_string(child, "label", &ch->name)) {
> >> +                     dev_err(dev, "channel without label\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
> >> +                     dev_err(dev, "channel without reg\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (fwnode_property_read_string(child, "type", &type)) {
> >> +                     dev_err(dev, "channel without type\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +             if (!strcasecmp(type, "gw,hwmon-temperature"))
> >> +                     ch->type = type_temperature;
> >> +             else if (!strcasecmp(type, "gw,hwmon-voltage"))
> >> +                     ch->type = type_voltage;
> >> +             else if (!strcasecmp(type, "gw,hwmon-voltage-raw"))
> >> +                     ch->type = type_voltage_raw;
> >> +             else if (!strcasecmp(type, "gw,hwmon-fan"))
> >> +                     ch->type = type_fan;
> >> +             else {
> >> +                     dev_err(dev, "channel without type\n");
> >> +                     fwnode_handle_put(child);
> >> +                     return ERR_PTR(-EINVAL);
> >> +             }
> >> +
> >> +             fwnode_property_read_u32(child, "gw,voltage-offset",
> >> +                     &ch->voffset);
> >
> > Note that while it is technically ok to keep voltages internally in mV,
> > devicetree will likely require specificayion in uV. For accuracy, it might be
> > better to perform any calculations on that base and convert to mV for display
> > purposes.
> 
> I'm happy to change them if that really is the standard but it doesn't
> look like it is a standard?
> 

Again, please discuss with Rob.

> >
> >> +             fwnode_property_read_u32_array(child, "gw,voltage-divider",
> >> +                     ch->vdiv, ARRAY_SIZE(ch->vdiv));
> >> +             dev_dbg(dev, "of: reg=0x%02x type=%d %s\n", ch->reg, ch->type,
> >> +                     ch->name);
> >> +             ch++;
> >> +     }
> >> +
> >> +     return pdata;
> >> +}
> >> +
> >> +static int gsc_hwmon_probe(struct platform_device *pdev)
> >> +{
> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> >> +     struct device *dev = &pdev->dev;
> >> +     struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
> >> +     struct gsc_hwmon_data *hwmon;
> >> +     int i, i_in, i_temp, i_fan;
> >> +
> >> +     if (!pdata) {
> >> +             pdata = gsc_hwmon_get_devtree_pdata(dev);
> >> +             if (IS_ERR(pdata))
> >> +                     return PTR_ERR(pdata);
> >> +     }
> >> +
> >> +     hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> >> +     if (!hwmon)
> >> +             return -ENOMEM;
> >> +     hwmon->gsc = gsc;
> >> +     hwmon->pdata = pdata;
> >> +
> >> +     for (i = 0, i_in = 0, i_temp = 0, i_fan = 0;
> >> +          i < hwmon->pdata->nchannels; i++) {
> >> +             const struct gsc_hwmon_channel *ch = &pdata->channels[i];
> >> +
> >> +             if (ch->reg > GSC_HWMON_MAX_REG) {
> >> +                     dev_err(dev, "invalid reg: 0x%02x\n", ch->reg);
> >> +                     return -EINVAL;
> >> +             }
> >> +             switch (ch->type) {
> >> +             case type_temperature:
> >> +                     if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
> >> +                             dev_err(dev, "too many temp channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->temp_ch[i_temp] = ch;
> >> +                     hwmon->temp_config[i_temp] = HWMON_T_INPUT |
> >> +                                                  HWMON_T_LABEL;
> >> +                     i_temp++;
> >> +                     break;
> >> +             case type_voltage:
> >> +             case type_voltage_raw:
> >> +                     if (i_in == GSC_HWMON_MAX_IN_CH) {
> >> +                             dev_err(dev, "too many voltage channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->in_ch[i_in] = ch;
> >> +                     hwmon->in_config[i_in] =
> >> +                             HWMON_I_INPUT | HWMON_I_LABEL;
> >> +                     i_in++;
> >> +                     break;
> >> +             case type_fan:
> >> +                     if (i_fan == GSC_HWMON_MAX_FAN_CH) {
> >> +                             dev_err(dev, "too many voltage channels\n");
> >> +                             return -EINVAL;
> >> +                     }
> >> +                     hwmon->fan_ch[i_fan] = ch;
> >> +                     hwmon->fan_config[i_fan] =
> >> +                             HWMON_F_INPUT | HWMON_F_LABEL;
> >> +                     i_fan++;
> >> +                     break;
> >> +             default:
> >> +                     dev_err(dev, "invalid type: %d\n", ch->type);
> >> +                     return -EINVAL;
> >> +             }
> >> +             dev_dbg(dev, "pdata: reg=0x%02x type=%d %s\n", ch->reg,
> >> +                     ch->type, ch->name);
> >> +     }
> >> +
> >> +     /* terminate channel config lists */
> >> +     hwmon->temp_config[i_temp] = 0;
> >> +     hwmon->in_config[i_in] = 0;
> >> +     hwmon->fan_config[i_fan] = 0;
> >
> > 'hwmon' was alocated with devm_kzalloc(). Initializing any of its members with 0
> > is unnecessary.
> 
> right - will remove
> 
> >
> >> +
> >> +     /* setup config structures */
> >> +     hwmon->chip.ops = &gsc_hwmon_ops;
> >> +     hwmon->chip.info = hwmon->info;
> >> +     hwmon->info[0] = &hwmon->temp_info;
> >> +     hwmon->info[1] = &hwmon->in_info;
> >> +     hwmon->info[2] = &hwmon->fan_info;
> >> +     hwmon->temp_info.type = hwmon_temp;
> >> +     hwmon->temp_info.config = hwmon->temp_config;
> >> +     hwmon->in_info.type = hwmon_in;
> >> +     hwmon->in_info.config = hwmon->in_config;
> >> +     hwmon->fan_info.type = hwmon_fan;
> >> +     hwmon->fan_info.config = hwmon->fan_config;
> >> +
> >> +     hwmon->dev = devm_hwmon_device_register_with_info(dev,
> >> +                                                       KBUILD_MODNAME, hwmon,
> >> +                                                       &hwmon->chip, NULL);
> >> +     return PTR_ERR_OR_ZERO(hwmon->dev);
> >> +}
> >> +
> >> +static const struct of_device_id gsc_hwmon_of_match[] = {
> >> +     { .compatible = "gw,gsc-hwmon", },
> >> +     {}
> >> +};
> >> +
> >> +static struct platform_driver gsc_hwmon_driver = {
> >> +     .driver = {
> >> +             .name = KBUILD_MODNAME,
> >> +             .of_match_table = gsc_hwmon_of_match,
> >> +     },
> >> +     .probe = gsc_hwmon_probe,
> >> +};
> >> +
> >> +module_platform_driver(gsc_hwmon_driver);
> >> +
> >> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> >> +MODULE_DESCRIPTION("GSC hardware monitor driver");
> >> +MODULE_LICENSE("GPL v2");
> >> diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h
> >> new file mode 100644
> >> index 0000000..5e59846
> >> --- /dev/null
> >> +++ b/include/linux/platform_data/gsc_hwmon.h
> >> @@ -0,0 +1,43 @@
> >> +/* SPDX-License-Identifier: GPL-2.0 */
> >> +#ifndef _GSC_HWMON_H
> >> +#define _GSC_HWMON_H
> >> +
> >> +enum gsc_hwmon_type {
> >> +     type_temperature,
> >> +     type_voltage,
> >> +     type_voltage_raw,
> >> +     type_fan,
> >> +};
> >> +
> >> +/**
> >> + * struct gsc_hwmon_channel - configuration parameters
> >> + * @reg:  I2C register offset
> >> + * @type: channel type
> >> + * @name: channel name
> >> + * @voffset: voltage offset (mV)
> >> + * @vdiv: voltage divider array (2 resistor values in ohms)
> >> + */
> >> +struct gsc_hwmon_channel {
> >> +     unsigned int reg;
> >> +     unsigned int type;
> >> +     const char *name;
> >> +     unsigned int voffset;
> >
> > It is interesting that only positive offset values are supported.
> > Is that intentional ?
> 
> yes, they are only used for voltage rails that have a diode drop to
> adjust for and will only be positive. They are not used to fine tune
> offsets.
> 
> >
> >> +     unsigned int vdiv[2];
> >> +};
> >> +
> >> +/**
> >> + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
> >> + * @channels:        pointer to array of gsc_hwmon_channel structures
> >> + *           describing channels
> >> + * @nchannels:       number of elements in @channels array
> >> + * @vreference: voltage reference (mV)
> >> + * @resolution: ADC resolution
> >
> > Resolution in what ?
> 
> That's the bit-resolution of the ADC so I'll switch to that notation
> and document it as such. I just realized that the vref/resolution can
> technically change per ADC rail so I'll leave them in but move them
> down into the child nodes and switch resolution to be in bits.
> 
> Thanks for the review!
> 
> Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 20:23         ` Guenter Roeck
@ 2018-03-28 20:53           ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 20:53 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 1:23 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
>> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
>> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
>> >> This patch adds documentation of device-tree bindings for the
>> >> Gateworks System Controller (GSC).
>> >>
>> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> >> ---
>> >> v3:
>> >>  - replaced _ with -
>> >>  - remove input bindings
>> >>  - added full description of hwmon
>> >>  - fix unit address of hwmon child nodes
>> >>
>> >> ---
>> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>> >>  1 file changed, 135 insertions(+)
>> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >>
>> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >> new file mode 100644
>> >> index 0000000..8f530ed
>> >> --- /dev/null
>> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >> @@ -0,0 +1,135 @@
>> >> +Gateworks System Controller multi-function device
>> >> +
>> >> +The GSC is a Multifunction I2C slave device with the following submodules:
>> >> +- WDT
>> >> +- GPIO
>> >> +- Pushbutton controller
>> >> +- HWMON
>> >> +
>> >> +Required properties:
>> >> +- compatible : Must be "gw,gsc"
>> >> +- reg: I2C address of the device
>> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
>> >> +- interrupt-parent: Interrupt controller GSC is connected to
>> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
>> >> +  controller, in accordance with the "one cell" variant of
>> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
>> >> +
>> >> +Optional nodes:
>> >> +* watchdog:
>> >> +The GSC provides a Watchdog monitor which can power cycle the board's
>> >> +primary power supply on most board models when tripped.
>> >> +
>> >> +Required watchdog properties:
>> >> +- compatible: must be "gw,gsc-watchdog"
>> >> +
>> >> +* hwmon:
>> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
>> >> +temperature and/or voltage monitoring.
>> >> +
>> >> +Required hwmon properties:
>> >> +- compatible: must be "gw,gsc-hwmon"
>> >> +
>> >
>> > "hwmon" is a very Linux specific term. It might make sense to find a more
>> > generic term.
>>
>> The 'hwmon' driver supports child nodes that fall into the following category:
>>  - temperature sensor (GSC internal temperature sensor - i2c registers
>> returns value in C*10)
>>  - voltage rails (two types here; cooked: i2c registers return
>> pre-scaled value in mV), raw: i2c registers return a raw ADC value
>> that must be scaled based on ADC internal ref voltage and resolution
>> and adjusted for a voltage divider to convert to mV
>>  - fan setpoints (I'll explain these below)
>>
>> I called the node 'gw,gsc-hwmon' because the driver fits into the
>> 'hwmon' API. Isn't that appropriate here for the driver compatible
>> string?
>>
>
> Devicetree properties are supposed to be OS independent.
>
>> >
>> >> +Optional hwmon properties:
>> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
>> >
>> > AFAIK devicetree likes to specify voltages in uV.
>>
>> There are currently plenty of dt props specified in mV (grep -r mV
>> Documentation/devicetree/bindings/).
>>
>
> "But so many others are speeding, why do I get a ticket ?"
>
> Please discuss with Rob.

Yes - hoping for feedback on mV vs uV as well as naming of hwmon mfd child node.

>
>> >
>> >> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
>> >> +
>> >
>> > 4096 what ?
>>
>> reference-voltage and resolution are used to scale the values from the
>> nodes that report a raw ADC value:
>>
>> V = Vadc * (reference-voltage / resolution)
>>
>> I can provide that in bits if it makes more sense? I can also hard
>
> Yes, I think that would make more sense, and please describe what it means.
>
>> code both the resolution and the vref in the hwmon driver and remove
>> it from dt as currently the only GSC that uses raw ADC values is 12bit
>> with 2.5V ref.
>>
>
> That would be even better.
>
>> >
>> >> +Each hwmon child node defines an ADC input on the chip which the GSC may
>> >> +report cooked values (ie temperature sensor based on thermister), raw values,
>> >> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
>> >> +setpoint.
>> >> +
>> >> +Required hwmon child properties:
>> >> +- type: one of the following ADC types:
>> >> +  "gw,hwmon-temperature" - reports temperature in C*10
>> >> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
>> >> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
>> >> +       vreference, resolution, and optional resistor divider
>> >> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
>> >
>> > What is a "fan temperature setpoint" ?
>> >
>>
>> The GSC supports a fan controller which drives a PWM signal to vary
>> the speed of a fan based on the GSC internal temperature sensor. The
>> FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
>> the temperature at which those setpoints kick in can be varies via
>> registers at the 0x29 slave address (same slave address as the
>> temperature sensor and voltage inputs which is why I have it in the
>> hwmon driver):
>>
>> fan0_point - 50% PWM (default 300)
>> fan1_point - 60% PWM (default 330)
>> fan2_point - 70% PWM (default 360)
>> fan3_point - 80% PWM (default 390)
>> fan4_point - 90% PWM (default 420)
>> fan5_point - 100% PWM (default 450)
>>
>> The values are C/10 thus if the internal GSC temp sensor is below 30C
>> the fan output will be 0% duty cycle and if it hits 30C it will go to
>> 50% until it hits 60% at 33C etc.
>>
> Please do not define your own scaling factors. pwm values are 0..255,
> and temperatures are in milli-degrees C.
>
>> That is the hardware implementation that I'm trying to abstract and
>> define here. You pointed out the fact that the fan*_input ABI is
>> read-only fan PWM and I see that now. What do you suggest I use for
>
> No, it isn't. It is the fan speed in RPM.
>
>> this feature I'm trying to implement driver support for?
>>
>
> pwm[1-*]_auto_point[1-*]_pwm
> pwm[1-*]_auto_point[1-*]_temp
> pwm[1-*]_auto_point[1-*]_temp_hyst
>
> may be relevant. From the context, something like
>
> pwm1_auto_point1_pwm    read-only, set to 128
> pwm1_auto_point1_temp   30000
> pwm1_auto_point2_pwm    read-only, set to 153
> pwm1_auto_point2_temp   33000
> pwm1_auto_point3_pwm    read-only, set to 179
> pwm1_auto_point3_temp   36000
> pwm1_auto_point4_pwm    read-only, set to 204
> pwm1_auto_point4_temp   39000
> pwm1_auto_point5_pwm    read-only, set to 230
> pwm1_auto_point5_temp   42000
> pwm1_auto_point6_pwm    read-only, set to 255
> pwm1_auto_point6_temp   45000
>
> might make sense.

I like that idea! The details of the setpoints do not need to be in
the dts at all. I will need to add a single property to signify that
the board has a fan controller as some don't.

Thanks,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-03-28 20:53           ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-28 20:53 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 1:23 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
>> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
>> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
>> >> This patch adds documentation of device-tree bindings for the
>> >> Gateworks System Controller (GSC).
>> >>
>> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> >> ---
>> >> v3:
>> >>  - replaced _ with -
>> >>  - remove input bindings
>> >>  - added full description of hwmon
>> >>  - fix unit address of hwmon child nodes
>> >>
>> >> ---
>> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
>> >>  1 file changed, 135 insertions(+)
>> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >>
>> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >> new file mode 100644
>> >> index 0000000..8f530ed
>> >> --- /dev/null
>> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
>> >> @@ -0,0 +1,135 @@
>> >> +Gateworks System Controller multi-function device
>> >> +
>> >> +The GSC is a Multifunction I2C slave device with the following submodules:
>> >> +- WDT
>> >> +- GPIO
>> >> +- Pushbutton controller
>> >> +- HWMON
>> >> +
>> >> +Required properties:
>> >> +- compatible : Must be "gw,gsc"
>> >> +- reg: I2C address of the device
>> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
>> >> +- interrupt-parent: Interrupt controller GSC is connected to
>> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
>> >> +  controller, in accordance with the "one cell" variant of
>> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
>> >> +
>> >> +Optional nodes:
>> >> +* watchdog:
>> >> +The GSC provides a Watchdog monitor which can power cycle the board's
>> >> +primary power supply on most board models when tripped.
>> >> +
>> >> +Required watchdog properties:
>> >> +- compatible: must be "gw,gsc-watchdog"
>> >> +
>> >> +* hwmon:
>> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
>> >> +temperature and/or voltage monitoring.
>> >> +
>> >> +Required hwmon properties:
>> >> +- compatible: must be "gw,gsc-hwmon"
>> >> +
>> >
>> > "hwmon" is a very Linux specific term. It might make sense to find a more
>> > generic term.
>>
>> The 'hwmon' driver supports child nodes that fall into the following category:
>>  - temperature sensor (GSC internal temperature sensor - i2c registers
>> returns value in C*10)
>>  - voltage rails (two types here; cooked: i2c registers return
>> pre-scaled value in mV), raw: i2c registers return a raw ADC value
>> that must be scaled based on ADC internal ref voltage and resolution
>> and adjusted for a voltage divider to convert to mV
>>  - fan setpoints (I'll explain these below)
>>
>> I called the node 'gw,gsc-hwmon' because the driver fits into the
>> 'hwmon' API. Isn't that appropriate here for the driver compatible
>> string?
>>
>
> Devicetree properties are supposed to be OS independent.
>
>> >
>> >> +Optional hwmon properties:
>> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
>> >
>> > AFAIK devicetree likes to specify voltages in uV.
>>
>> There are currently plenty of dt props specified in mV (grep -r mV
>> Documentation/devicetree/bindings/).
>>
>
> "But so many others are speeding, why do I get a ticket ?"
>
> Please discuss with Rob.

Yes - hoping for feedback on mV vs uV as well as naming of hwmon mfd child node.

>
>> >
>> >> +- gw,resolution: ADC resolution (ie 4096) used in scaling raw ADCs
>> >> +
>> >
>> > 4096 what ?
>>
>> reference-voltage and resolution are used to scale the values from the
>> nodes that report a raw ADC value:
>>
>> V = Vadc * (reference-voltage / resolution)
>>
>> I can provide that in bits if it makes more sense? I can also hard
>
> Yes, I think that would make more sense, and please describe what it means.
>
>> code both the resolution and the vref in the hwmon driver and remove
>> it from dt as currently the only GSC that uses raw ADC values is 12bit
>> with 2.5V ref.
>>
>
> That would be even better.
>
>> >
>> >> +Each hwmon child node defines an ADC input on the chip which the GSC may
>> >> +report cooked values (ie temperature sensor based on thermister), raw values,
>> >> +(ie voltage rail with a pre-scaling resistor divider), or a fan controller
>> >> +setpoint.
>> >> +
>> >> +Required hwmon child properties:
>> >> +- type: one of the following ADC types:
>> >> +  "gw,hwmon-temperature" - reports temperature in C*10
>> >> +  "gw,hwmon-voltage" - reports a pre-scaled voltage value
>> >> +  "gw,hwmon-voltage-raw" - reports a raw ADC that is scaled with
>> >> +       vreference, resolution, and optional resistor divider
>> >> +  "gw,hwmon-fan" - a fan temperature setpoint in C*10
>> >
>> > What is a "fan temperature setpoint" ?
>> >
>>
>> The GSC supports a fan controller which drives a PWM signal to vary
>> the speed of a fan based on the GSC internal temperature sensor. The
>> FAN controller has 6 setpoints each having a fixed PWM duty-cycle but
>> the temperature at which those setpoints kick in can be varies via
>> registers at the 0x29 slave address (same slave address as the
>> temperature sensor and voltage inputs which is why I have it in the
>> hwmon driver):
>>
>> fan0_point - 50% PWM (default 300)
>> fan1_point - 60% PWM (default 330)
>> fan2_point - 70% PWM (default 360)
>> fan3_point - 80% PWM (default 390)
>> fan4_point - 90% PWM (default 420)
>> fan5_point - 100% PWM (default 450)
>>
>> The values are C/10 thus if the internal GSC temp sensor is below 30C
>> the fan output will be 0% duty cycle and if it hits 30C it will go to
>> 50% until it hits 60% at 33C etc.
>>
> Please do not define your own scaling factors. pwm values are 0..255,
> and temperatures are in milli-degrees C.
>
>> That is the hardware implementation that I'm trying to abstract and
>> define here. You pointed out the fact that the fan*_input ABI is
>> read-only fan PWM and I see that now. What do you suggest I use for
>
> No, it isn't. It is the fan speed in RPM.
>
>> this feature I'm trying to implement driver support for?
>>
>
> pwm[1-*]_auto_point[1-*]_pwm
> pwm[1-*]_auto_point[1-*]_temp
> pwm[1-*]_auto_point[1-*]_temp_hyst
>
> may be relevant. From the context, something like
>
> pwm1_auto_point1_pwm    read-only, set to 128
> pwm1_auto_point1_temp   30000
> pwm1_auto_point2_pwm    read-only, set to 153
> pwm1_auto_point2_temp   33000
> pwm1_auto_point3_pwm    read-only, set to 179
> pwm1_auto_point3_temp   36000
> pwm1_auto_point4_pwm    read-only, set to 204
> pwm1_auto_point4_temp   39000
> pwm1_auto_point5_pwm    read-only, set to 230
> pwm1_auto_point5_temp   42000
> pwm1_auto_point6_pwm    read-only, set to 255
> pwm1_auto_point6_temp   45000
>
> might make sense.

I like that idea! The details of the setpoints do not need to be in
the dts at all. I will need to add a single property to signify that
the board has a fan controller as some don't.

Thanks,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-03-28 15:14   ` Tim Harvey
@ 2018-03-30  1:07     ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-30  1:07 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
>  drivers/watchdog/Kconfig   |  10 ++++
>  drivers/watchdog/Makefile  |   1 +
>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 157 insertions(+)
>  create mode 100644 drivers/watchdog/gsc_wdt.c
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index ca200d1..c9d4b2e 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
>  	  arch_initcall.
>  	  If in doubt, say N.
>  
> +config GSC_WATCHDOG
> +	tristate "Gateworks System Controller (GSC) Watchdog support"
> +	depends on MFD_GATEWORKS_GSC
> +	select WATCHDOG_CORE
> +	help
> +	  Say Y here to include support for the GSC Watchdog.
> +
> +	  This driver can also be built as a module. If so the module
> +	  will be called gsc_wdt.
> +
>  config MENF21BMC_WATCHDOG
>  	tristate "MEN 14F021P00 BMC Watchdog"
>  	depends on MFD_MENF21BMC || COMPILE_TEST
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 715a210..499327e 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
>  obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
>  obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
>  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
> +obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
>  obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
>  obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
>  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
> diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
> new file mode 100644
> index 0000000..b43d083
> --- /dev/null
> +++ b/drivers/watchdog/gsc_wdt.c
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Copyright (C) 2018 Gateworks Corporation
> + *
> + * This driver registers a Linux Watchdog for the GSC
> + */
> +#include <linux/mfd/gsc.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/watchdog.h>
> +
> +#define WDT_DEFAULT_TIMEOUT	60
> +
> +struct gsc_wdt {
> +	struct watchdog_device wdt_dev;
> +	struct gsc_dev *gsc;
> +};
> +
> +static int gsc_wdt_start(struct watchdog_device *wdd)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);

Please use BIT().

> +	int ret;
> +
> +	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);

I don't think those debug messages add any value.

> +
> +	/* clear first as regmap_update_bits will not write if no change */
> +	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> +	if (ret)
> +		return ret;
> +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
> +}
> +
> +static int gsc_wdt_stop(struct watchdog_device *wdd)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> +

BIT(). You might as well drop the variable and just use
BIT(GSC_CTRL_1_WDT_ENABLE) below.

> +	dev_dbg(wdd->parent, "%s\n", __func__);
> +
> +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> +}
> +
> +static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
> +			       unsigned int timeout)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int long_sel = 0;
> +
> +	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
> +
> +	switch (timeout) {
> +	case 60:
> +		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
> +	case 30:
> +		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
> +				   (1 << GSC_CTRL_1_WDT_TIME),

BIT()

> +				   (long_sel << GSC_CTRL_1_WDT_TIME));
> +		wdd->timeout = timeout;
> +		return 0;
> +	}

Please use rounding and accept other values as well. We don't want to let
user space guessing valid timeouts. 

> +
> +	return -EINVAL;
> +}
> +
> +static const struct watchdog_info gsc_wdt_info = {
> +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,

Please confirm that WDIOF_MAGICCLOSE is not set on purpose.

> +	.identity = "GSC Watchdog"
> +};
> +
> +static const struct watchdog_ops gsc_wdt_ops = {
> +	.owner		= THIS_MODULE,
> +	.start		= gsc_wdt_start,
> +	.stop		= gsc_wdt_stop,
> +	.set_timeout	= gsc_wdt_set_timeout,
> +};
> +
> +static int gsc_wdt_probe(struct platform_device *pdev)
> +{
> +	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	struct gsc_wdt *wdt;
> +	int ret;
> +	unsigned int reg;
> +
> +	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
> +	if (!wdt)
> +		return -ENOMEM;
> +
> +	/* ensure GSC fw supports WD functionality */
> +	if (gsc->fwver < 44) {

What if gsc is NULL ?

> +		dev_err(dev, "fw v44 or newer required for wdt function\n");
> +		return -EINVAL;
> +	}
> +
> +	/* ensure WD bit enabled */
> +	if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
> +		return -EIO;
> +	if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {

BIT()

> +		dev_err(dev, "not enabled - must be manually enabled\n");

This doesn't make sense. Bail out if the watchdog is disabled ? Why ?

> +		return -EINVAL;
> +	}
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	wdt->gsc = gsc;
> +	wdt->wdt_dev.info = &gsc_wdt_info;
> +	wdt->wdt_dev.ops = &gsc_wdt_ops;
> +	wdt->wdt_dev.status = 0;

Initializing a data structure which is known to be 0 is not needed
and discouraged.

> +	wdt->wdt_dev.min_timeout = 30;
> +	wdt->wdt_dev.max_timeout = 60;
> +	wdt->wdt_dev.parent = dev;
> +
> +	watchdog_set_nowayout(&wdt->wdt_dev, 1);

WATCHDOG_NOWAYOUT ?

> +	watchdog_init_timeout(&wdt->wdt_dev, WDT_DEFAULT_TIMEOUT, dev);

This is quite pointless. If you don't want to support setting the timeout
through devicetree or through a module parameter, just set the timeout
directly above.

> +
> +	watchdog_set_drvdata(&wdt->wdt_dev, wdt);
> +	ret = devm_watchdog_register_device(dev, &wdt->wdt_dev);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(dev, "watchdog driver (timeout=%d sec)\n",
> +		 wdt->wdt_dev.timeout);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id gsc_wdt_dt_ids[] = {
> +	{ .compatible = "gw,gsc-watchdog", },
> +	{}
> +};
> +
> +static struct platform_driver gsc_wdt_driver = {
> +	.probe		= gsc_wdt_probe,
> +	.driver		= {
> +		.name	= "gsc-wdt",
> +		.of_match_table = gsc_wdt_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(gsc_wdt_driver);
> +
> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> +MODULE_DESCRIPTION("Gateworks System Controller Watchdog driver");
> +MODULE_LICENSE("GPL v2");

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-03-30  1:07     ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-30  1:07 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> ---
>  drivers/watchdog/Kconfig   |  10 ++++
>  drivers/watchdog/Makefile  |   1 +
>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 157 insertions(+)
>  create mode 100644 drivers/watchdog/gsc_wdt.c
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index ca200d1..c9d4b2e 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
>  	  arch_initcall.
>  	  If in doubt, say N.
>  
> +config GSC_WATCHDOG
> +	tristate "Gateworks System Controller (GSC) Watchdog support"
> +	depends on MFD_GATEWORKS_GSC
> +	select WATCHDOG_CORE
> +	help
> +	  Say Y here to include support for the GSC Watchdog.
> +
> +	  This driver can also be built as a module. If so the module
> +	  will be called gsc_wdt.
> +
>  config MENF21BMC_WATCHDOG
>  	tristate "MEN 14F021P00 BMC Watchdog"
>  	depends on MFD_MENF21BMC || COMPILE_TEST
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 715a210..499327e 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
>  obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
>  obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
>  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
> +obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
>  obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
>  obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
>  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
> diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
> new file mode 100644
> index 0000000..b43d083
> --- /dev/null
> +++ b/drivers/watchdog/gsc_wdt.c
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Copyright (C) 2018 Gateworks Corporation
> + *
> + * This driver registers a Linux Watchdog for the GSC
> + */
> +#include <linux/mfd/gsc.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/watchdog.h>
> +
> +#define WDT_DEFAULT_TIMEOUT	60
> +
> +struct gsc_wdt {
> +	struct watchdog_device wdt_dev;
> +	struct gsc_dev *gsc;
> +};
> +
> +static int gsc_wdt_start(struct watchdog_device *wdd)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);

Please use BIT().

> +	int ret;
> +
> +	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);

I don't think those debug messages add any value.

> +
> +	/* clear first as regmap_update_bits will not write if no change */
> +	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> +	if (ret)
> +		return ret;
> +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
> +}
> +
> +static int gsc_wdt_stop(struct watchdog_device *wdd)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> +

BIT(). You might as well drop the variable and just use
BIT(GSC_CTRL_1_WDT_ENABLE) below.

> +	dev_dbg(wdd->parent, "%s\n", __func__);
> +
> +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> +}
> +
> +static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
> +			       unsigned int timeout)
> +{
> +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> +	unsigned int long_sel = 0;
> +
> +	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
> +
> +	switch (timeout) {
> +	case 60:
> +		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
> +	case 30:
> +		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
> +				   (1 << GSC_CTRL_1_WDT_TIME),

BIT()

> +				   (long_sel << GSC_CTRL_1_WDT_TIME));
> +		wdd->timeout = timeout;
> +		return 0;
> +	}

Please use rounding and accept other values as well. We don't want to let
user space guessing valid timeouts. 

> +
> +	return -EINVAL;
> +}
> +
> +static const struct watchdog_info gsc_wdt_info = {
> +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,

Please confirm that WDIOF_MAGICCLOSE is not set on purpose.

> +	.identity = "GSC Watchdog"
> +};
> +
> +static const struct watchdog_ops gsc_wdt_ops = {
> +	.owner		= THIS_MODULE,
> +	.start		= gsc_wdt_start,
> +	.stop		= gsc_wdt_stop,
> +	.set_timeout	= gsc_wdt_set_timeout,
> +};
> +
> +static int gsc_wdt_probe(struct platform_device *pdev)
> +{
> +	struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	struct gsc_wdt *wdt;
> +	int ret;
> +	unsigned int reg;
> +
> +	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
> +	if (!wdt)
> +		return -ENOMEM;
> +
> +	/* ensure GSC fw supports WD functionality */
> +	if (gsc->fwver < 44) {

What if gsc is NULL ?

> +		dev_err(dev, "fw v44 or newer required for wdt function\n");
> +		return -EINVAL;
> +	}
> +
> +	/* ensure WD bit enabled */
> +	if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
> +		return -EIO;
> +	if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {

BIT()

> +		dev_err(dev, "not enabled - must be manually enabled\n");

This doesn't make sense. Bail out if the watchdog is disabled ? Why ?

> +		return -EINVAL;
> +	}
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	wdt->gsc = gsc;
> +	wdt->wdt_dev.info = &gsc_wdt_info;
> +	wdt->wdt_dev.ops = &gsc_wdt_ops;
> +	wdt->wdt_dev.status = 0;

Initializing a data structure which is known to be 0 is not needed
and discouraged.

> +	wdt->wdt_dev.min_timeout = 30;
> +	wdt->wdt_dev.max_timeout = 60;
> +	wdt->wdt_dev.parent = dev;
> +
> +	watchdog_set_nowayout(&wdt->wdt_dev, 1);

WATCHDOG_NOWAYOUT ?

> +	watchdog_init_timeout(&wdt->wdt_dev, WDT_DEFAULT_TIMEOUT, dev);

This is quite pointless. If you don't want to support setting the timeout
through devicetree or through a module parameter, just set the timeout
directly above.

> +
> +	watchdog_set_drvdata(&wdt->wdt_dev, wdt);
> +	ret = devm_watchdog_register_device(dev, &wdt->wdt_dev);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(dev, "watchdog driver (timeout=%d sec)\n",
> +		 wdt->wdt_dev.timeout);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id gsc_wdt_dt_ids[] = {
> +	{ .compatible = "gw,gsc-watchdog", },
> +	{}
> +};
> +
> +static struct platform_driver gsc_wdt_driver = {
> +	.probe		= gsc_wdt_probe,
> +	.driver		= {
> +		.name	= "gsc-wdt",
> +		.of_match_table = gsc_wdt_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(gsc_wdt_driver);
> +
> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
> +MODULE_DESCRIPTION("Gateworks System Controller Watchdog driver");
> +MODULE_LICENSE("GPL v2");

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-03-30  1:07     ` Guenter Roeck
@ 2018-03-30 17:48       ` Dmitry Torokhov
  -1 siblings, 0 replies; 51+ messages in thread
From: Dmitry Torokhov @ 2018-03-30 17:48 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Tim Harvey, Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Wim Van Sebroeck, linux-kernel, devicetree, linux-arm-kernel,
	linux-hwmon, linux-input, linux-watchdog

On Thu, Mar 29, 2018 at 06:07:57PM -0700, Guenter Roeck wrote:
> On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> > Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> > ---
> >  drivers/watchdog/Kconfig   |  10 ++++
> >  drivers/watchdog/Makefile  |   1 +
> >  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 157 insertions(+)
> >  create mode 100644 drivers/watchdog/gsc_wdt.c
> > 
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index ca200d1..c9d4b2e 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
> >  	  arch_initcall.
> >  	  If in doubt, say N.
> >  
> > +config GSC_WATCHDOG
> > +	tristate "Gateworks System Controller (GSC) Watchdog support"
> > +	depends on MFD_GATEWORKS_GSC
> > +	select WATCHDOG_CORE
> > +	help
> > +	  Say Y here to include support for the GSC Watchdog.
> > +
> > +	  This driver can also be built as a module. If so the module
> > +	  will be called gsc_wdt.
> > +
> >  config MENF21BMC_WATCHDOG
> >  	tristate "MEN 14F021P00 BMC Watchdog"
> >  	depends on MFD_MENF21BMC || COMPILE_TEST
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index 715a210..499327e 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> >  obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
> >  obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
> >  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
> > +obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
> >  obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
> >  obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
> >  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
> > diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
> > new file mode 100644
> > index 0000000..b43d083
> > --- /dev/null
> > +++ b/drivers/watchdog/gsc_wdt.c
> > @@ -0,0 +1,146 @@
> > +/* SPDX-License-Identifier: GPL-2.0
> > + *
> > + * Copyright (C) 2018 Gateworks Corporation
> > + *
> > + * This driver registers a Linux Watchdog for the GSC
> > + */
> > +#include <linux/mfd/gsc.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +#include <linux/watchdog.h>
> > +
> > +#define WDT_DEFAULT_TIMEOUT	60
> > +
> > +struct gsc_wdt {
> > +	struct watchdog_device wdt_dev;
> > +	struct gsc_dev *gsc;
> > +};
> > +
> > +static int gsc_wdt_start(struct watchdog_device *wdd)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> 
> Please use BIT().
> 
> > +	int ret;
> > +
> > +	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);
> 
> I don't think those debug messages add any value.
> 
> > +
> > +	/* clear first as regmap_update_bits will not write if no change */
> > +	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> > +	if (ret)
> > +		return ret;
> > +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
> > +}
> > +
> > +static int gsc_wdt_stop(struct watchdog_device *wdd)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> > +
> 
> BIT(). You might as well drop the variable and just use
> BIT(GSC_CTRL_1_WDT_ENABLE) below.
> 
> > +	dev_dbg(wdd->parent, "%s\n", __func__);
> > +
> > +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> > +}
> > +
> > +static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
> > +			       unsigned int timeout)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int long_sel = 0;
> > +
> > +	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
> > +
> > +	switch (timeout) {
> > +	case 60:
> > +		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
> > +	case 30:
> > +		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
> > +				   (1 << GSC_CTRL_1_WDT_TIME),
> 
> BIT()
> 
> > +				   (long_sel << GSC_CTRL_1_WDT_TIME));

This looks wrong. Are you sure that in case of 60 timeout you want to
actually write

	((1 << GSC_CTRL_1_WDT_TIME) << GSC_CTRL_1_WDT_TIME)

to the register?

Or you meant to write:

	long_sel = timeout > 30;
	regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
			   (long_sel << GSC_CTRL_1_WDT_TIME),

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-03-30 17:48       ` Dmitry Torokhov
  0 siblings, 0 replies; 51+ messages in thread
From: Dmitry Torokhov @ 2018-03-30 17:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Mar 29, 2018 at 06:07:57PM -0700, Guenter Roeck wrote:
> On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> > Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> > ---
> >  drivers/watchdog/Kconfig   |  10 ++++
> >  drivers/watchdog/Makefile  |   1 +
> >  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 157 insertions(+)
> >  create mode 100644 drivers/watchdog/gsc_wdt.c
> > 
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index ca200d1..c9d4b2e 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -150,6 +150,16 @@ config GPIO_WATCHDOG_ARCH_INITCALL
> >  	  arch_initcall.
> >  	  If in doubt, say N.
> >  
> > +config GSC_WATCHDOG
> > +	tristate "Gateworks System Controller (GSC) Watchdog support"
> > +	depends on MFD_GATEWORKS_GSC
> > +	select WATCHDOG_CORE
> > +	help
> > +	  Say Y here to include support for the GSC Watchdog.
> > +
> > +	  This driver can also be built as a module. If so the module
> > +	  will be called gsc_wdt.
> > +
> >  config MENF21BMC_WATCHDOG
> >  	tristate "MEN 14F021P00 BMC Watchdog"
> >  	depends on MFD_MENF21BMC || COMPILE_TEST
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index 715a210..499327e 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -215,6 +215,7 @@ obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> >  obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
> >  obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
> >  obj-$(CONFIG_GPIO_WATCHDOG)	+= gpio_wdt.o
> > +obj-$(CONFIG_GSC_WATCHDOG)	+= gsc_wdt.o
> >  obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
> >  obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
> >  obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
> > diff --git a/drivers/watchdog/gsc_wdt.c b/drivers/watchdog/gsc_wdt.c
> > new file mode 100644
> > index 0000000..b43d083
> > --- /dev/null
> > +++ b/drivers/watchdog/gsc_wdt.c
> > @@ -0,0 +1,146 @@
> > +/* SPDX-License-Identifier: GPL-2.0
> > + *
> > + * Copyright (C) 2018 Gateworks Corporation
> > + *
> > + * This driver registers a Linux Watchdog for the GSC
> > + */
> > +#include <linux/mfd/gsc.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +#include <linux/watchdog.h>
> > +
> > +#define WDT_DEFAULT_TIMEOUT	60
> > +
> > +struct gsc_wdt {
> > +	struct watchdog_device wdt_dev;
> > +	struct gsc_dev *gsc;
> > +};
> > +
> > +static int gsc_wdt_start(struct watchdog_device *wdd)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> 
> Please use BIT().
> 
> > +	int ret;
> > +
> > +	dev_dbg(wdd->parent, "%s timeout=%d\n", __func__, wdd->timeout);
> 
> I don't think those debug messages add any value.
> 
> > +
> > +	/* clear first as regmap_update_bits will not write if no change */
> > +	ret = regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> > +	if (ret)
> > +		return ret;
> > +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, reg);
> > +}
> > +
> > +static int gsc_wdt_stop(struct watchdog_device *wdd)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int reg = (1 << GSC_CTRL_1_WDT_ENABLE);
> > +
> 
> BIT(). You might as well drop the variable and just use
> BIT(GSC_CTRL_1_WDT_ENABLE) below.
> 
> > +	dev_dbg(wdd->parent, "%s\n", __func__);
> > +
> > +	return regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1, reg, 0);
> > +}
> > +
> > +static int gsc_wdt_set_timeout(struct watchdog_device *wdd,
> > +			       unsigned int timeout)
> > +{
> > +	struct gsc_wdt *wdt = watchdog_get_drvdata(wdd);
> > +	unsigned int long_sel = 0;
> > +
> > +	dev_dbg(wdd->parent, "%s: %d\n", __func__, timeout);
> > +
> > +	switch (timeout) {
> > +	case 60:
> > +		long_sel = (1 << GSC_CTRL_1_WDT_TIME);
> > +	case 30:
> > +		regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
> > +				   (1 << GSC_CTRL_1_WDT_TIME),
> 
> BIT()
> 
> > +				   (long_sel << GSC_CTRL_1_WDT_TIME));

This looks wrong. Are you sure that in case of 60 timeout you want to
actually write

	((1 << GSC_CTRL_1_WDT_TIME) << GSC_CTRL_1_WDT_TIME)

to the register?

Or you meant to write:

	long_sel = timeout > 30;
	regmap_update_bits(wdt->gsc->regmap, GSC_CTRL_1,
			   (long_sel << GSC_CTRL_1_WDT_TIME),

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-03-30  1:07     ` Guenter Roeck
@ 2018-03-30 17:49       ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-30 17:49 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>>  drivers/watchdog/Kconfig   |  10 ++++
>>  drivers/watchdog/Makefile  |   1 +
>>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 157 insertions(+)
>>  create mode 100644 drivers/watchdog/gsc_wdt.c
>>
<snip>
>> +
>> +static const struct watchdog_info gsc_wdt_info = {
>> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
>
> Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
>
>> +     .identity = "GSC Watchdog"
>> +};
>> +
<snip>
>> +
>> +static int gsc_wdt_probe(struct platform_device *pdev)
>> +{
>> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> +     struct device *dev = &pdev->dev;
>> +     struct gsc_wdt *wdt;
>> +     int ret;
>> +     unsigned int reg;
>> +
<snip>
>> +     /* ensure WD bit enabled */
>> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
>> +             return -EIO;
>> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
>
> BIT()
>
>> +             dev_err(dev, "not enabled - must be manually enabled\n");
>
> This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
>
>> +             return -EINVAL;
>> +     }
>> +
<snip>
>> +
>> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
>
> WATCHDOG_NOWAYOUT ?
>

Guenter,

Thanks for the review!

The watchdog implementation of the GSC is such that it is enabled and
reset via a single non-volatile I2C register bit. If this bit is set
the watchdog will start ticking down automatically on board power up.
The register definitions don't provide a condition where it can be
enabled in a volatile way such that after board power-cycle it is
disabled again nor do they provide a separate register for enable vs
reset.

In the typical case the user boots the board, driver registers
watchdog, userspace watchdog daemon enables watchdog and it starts
ticking. User now powers down the board and later powers it back up.
The watchdog was enabled previously by userspace and the register is
non-volatile so the watchdog starts ticking before the kernel driver
and watchdog daemon yet the user breaks out into the bootloader or
boots a different OS without a watchdog daemon and the board resets
without them expecting it. The feature that the watchdog starts
ticking at board power-up before the CPU has even fetched code was
part of its design and was put there to work around some SoC errata
that can cause the CPU to fail to fetch boot code. This has caused me
to implement a watchdog driver that never actually 'enables' or
'disables' the watchdog which is why there is no MAGIC CLOSE and why I
always set nowayout. Its possible this is a fairly unique case of a
watchdog. The probe failure if the watchdog isn't enabled is because I
don't want a non-enabled watchdog to get enabled just because the
driver/daemon were there.

I agree it's a very strange behavior and I'm not sure how to best
document or support it with the Linux watchdog API. I welcome any
recomendations!

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-03-30 17:49       ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-03-30 17:49 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>>  drivers/watchdog/Kconfig   |  10 ++++
>>  drivers/watchdog/Makefile  |   1 +
>>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 157 insertions(+)
>>  create mode 100644 drivers/watchdog/gsc_wdt.c
>>
<snip>
>> +
>> +static const struct watchdog_info gsc_wdt_info = {
>> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
>
> Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
>
>> +     .identity = "GSC Watchdog"
>> +};
>> +
<snip>
>> +
>> +static int gsc_wdt_probe(struct platform_device *pdev)
>> +{
>> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> +     struct device *dev = &pdev->dev;
>> +     struct gsc_wdt *wdt;
>> +     int ret;
>> +     unsigned int reg;
>> +
<snip>
>> +     /* ensure WD bit enabled */
>> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
>> +             return -EIO;
>> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
>
> BIT()
>
>> +             dev_err(dev, "not enabled - must be manually enabled\n");
>
> This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
>
>> +             return -EINVAL;
>> +     }
>> +
<snip>
>> +
>> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
>
> WATCHDOG_NOWAYOUT ?
>

Guenter,

Thanks for the review!

The watchdog implementation of the GSC is such that it is enabled and
reset via a single non-volatile I2C register bit. If this bit is set
the watchdog will start ticking down automatically on board power up.
The register definitions don't provide a condition where it can be
enabled in a volatile way such that after board power-cycle it is
disabled again nor do they provide a separate register for enable vs
reset.

In the typical case the user boots the board, driver registers
watchdog, userspace watchdog daemon enables watchdog and it starts
ticking. User now powers down the board and later powers it back up.
The watchdog was enabled previously by userspace and the register is
non-volatile so the watchdog starts ticking before the kernel driver
and watchdog daemon yet the user breaks out into the bootloader or
boots a different OS without a watchdog daemon and the board resets
without them expecting it. The feature that the watchdog starts
ticking at board power-up before the CPU has even fetched code was
part of its design and was put there to work around some SoC errata
that can cause the CPU to fail to fetch boot code. This has caused me
to implement a watchdog driver that never actually 'enables' or
'disables' the watchdog which is why there is no MAGIC CLOSE and why I
always set nowayout. Its possible this is a fairly unique case of a
watchdog. The probe failure if the watchdog isn't enabled is because I
don't want a non-enabled watchdog to get enabled just because the
driver/daemon were there.

I agree it's a very strange behavior and I'm not sure how to best
document or support it with the Linux watchdog API. I welcome any
recomendations!

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-03-30 17:49       ` Tim Harvey
@ 2018-03-30 18:19         ` Guenter Roeck
  -1 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-30 18:19 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Fri, Mar 30, 2018 at 10:49:38AM -0700, Tim Harvey wrote:
> On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >>  drivers/watchdog/Kconfig   |  10 ++++
> >>  drivers/watchdog/Makefile  |   1 +
> >>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
> >>  3 files changed, 157 insertions(+)
> >>  create mode 100644 drivers/watchdog/gsc_wdt.c
> >>
> <snip>
> >> +
> >> +static const struct watchdog_info gsc_wdt_info = {
> >> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> >
> > Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
> >
> >> +     .identity = "GSC Watchdog"
> >> +};
> >> +
> <snip>
> >> +
> >> +static int gsc_wdt_probe(struct platform_device *pdev)
> >> +{
> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> >> +     struct device *dev = &pdev->dev;
> >> +     struct gsc_wdt *wdt;
> >> +     int ret;
> >> +     unsigned int reg;
> >> +
> <snip>
> >> +     /* ensure WD bit enabled */
> >> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
> >> +             return -EIO;
> >> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
> >
> > BIT()
> >
> >> +             dev_err(dev, "not enabled - must be manually enabled\n");
> >
> > This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
> >
> >> +             return -EINVAL;
> >> +     }
> >> +
> <snip>
> >> +
> >> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
> >
> > WATCHDOG_NOWAYOUT ?
> >
> 
> Guenter,
> 
> Thanks for the review!
> 
> The watchdog implementation of the GSC is such that it is enabled and
> reset via a single non-volatile I2C register bit. If this bit is set
> the watchdog will start ticking down automatically on board power up.
> The register definitions don't provide a condition where it can be
> enabled in a volatile way such that after board power-cycle it is
> disabled again nor do they provide a separate register for enable vs
> reset.
> 
> In the typical case the user boots the board, driver registers
> watchdog, userspace watchdog daemon enables watchdog and it starts
> ticking. User now powers down the board and later powers it back up.
> The watchdog was enabled previously by userspace and the register is
> non-volatile so the watchdog starts ticking before the kernel driver
> and watchdog daemon yet the user breaks out into the bootloader or
> boots a different OS without a watchdog daemon and the board resets
> without them expecting it. The feature that the watchdog starts
> ticking at board power-up before the CPU has even fetched code was
> part of its design and was put there to work around some SoC errata
> that can cause the CPU to fail to fetch boot code. This has caused me
> to implement a watchdog driver that never actually 'enables' or
> 'disables' the watchdog which is why there is no MAGIC CLOSE and why I

Yet the driver does enable and disable the watchdog in its start and stop
functions. And I have no idea what that has to do with the MAGICCLOSE
functionality, which is quite orthogonal to the start/stop functionality.

> always set nowayout. Its possible this is a fairly unique case of a
> watchdog. The probe failure if the watchdog isn't enabled is because I
> don't want a non-enabled watchdog to get enabled just because the
> driver/daemon were there.
> 
Huh ? The whole purpose of a watchdog is for it to be enabled when
the watchdog device is opened.

> I agree it's a very strange behavior and I'm not sure how to best
> document or support it with the Linux watchdog API. I welcome any
> recomendations!
> 

Sorry, I fail to understand your logic.

You do not explain why your code bails out if the watchdog is not already
running. That does not make sense.

You are saying that you don't want the watchdog driver to enable the watchdog.
Since its whole purpose is to enable the watchdog if/when the watchdog device
is opened, that doesn't make sense either.

At the same time, you do not tell the watchdog core that the watchdog is
already running, meaning the system _will_ reboot unless the watchdog
device is opened within the watchdog timeout period. Again, that does not
make sense.

Maybe it all makes sense to you, but not to me, sorry.

Guenter

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-03-30 18:19         ` Guenter Roeck
  0 siblings, 0 replies; 51+ messages in thread
From: Guenter Roeck @ 2018-03-30 18:19 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Mar 30, 2018 at 10:49:38AM -0700, Tim Harvey wrote:
> On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> ---
> >>  drivers/watchdog/Kconfig   |  10 ++++
> >>  drivers/watchdog/Makefile  |   1 +
> >>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
> >>  3 files changed, 157 insertions(+)
> >>  create mode 100644 drivers/watchdog/gsc_wdt.c
> >>
> <snip>
> >> +
> >> +static const struct watchdog_info gsc_wdt_info = {
> >> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> >
> > Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
> >
> >> +     .identity = "GSC Watchdog"
> >> +};
> >> +
> <snip>
> >> +
> >> +static int gsc_wdt_probe(struct platform_device *pdev)
> >> +{
> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
> >> +     struct device *dev = &pdev->dev;
> >> +     struct gsc_wdt *wdt;
> >> +     int ret;
> >> +     unsigned int reg;
> >> +
> <snip>
> >> +     /* ensure WD bit enabled */
> >> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
> >> +             return -EIO;
> >> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
> >
> > BIT()
> >
> >> +             dev_err(dev, "not enabled - must be manually enabled\n");
> >
> > This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
> >
> >> +             return -EINVAL;
> >> +     }
> >> +
> <snip>
> >> +
> >> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
> >
> > WATCHDOG_NOWAYOUT ?
> >
> 
> Guenter,
> 
> Thanks for the review!
> 
> The watchdog implementation of the GSC is such that it is enabled and
> reset via a single non-volatile I2C register bit. If this bit is set
> the watchdog will start ticking down automatically on board power up.
> The register definitions don't provide a condition where it can be
> enabled in a volatile way such that after board power-cycle it is
> disabled again nor do they provide a separate register for enable vs
> reset.
> 
> In the typical case the user boots the board, driver registers
> watchdog, userspace watchdog daemon enables watchdog and it starts
> ticking. User now powers down the board and later powers it back up.
> The watchdog was enabled previously by userspace and the register is
> non-volatile so the watchdog starts ticking before the kernel driver
> and watchdog daemon yet the user breaks out into the bootloader or
> boots a different OS without a watchdog daemon and the board resets
> without them expecting it. The feature that the watchdog starts
> ticking at board power-up before the CPU has even fetched code was
> part of its design and was put there to work around some SoC errata
> that can cause the CPU to fail to fetch boot code. This has caused me
> to implement a watchdog driver that never actually 'enables' or
> 'disables' the watchdog which is why there is no MAGIC CLOSE and why I

Yet the driver does enable and disable the watchdog in its start and stop
functions. And I have no idea what that has to do with the MAGICCLOSE
functionality, which is quite orthogonal to the start/stop functionality.

> always set nowayout. Its possible this is a fairly unique case of a
> watchdog. The probe failure if the watchdog isn't enabled is because I
> don't want a non-enabled watchdog to get enabled just because the
> driver/daemon were there.
> 
Huh ? The whole purpose of a watchdog is for it to be enabled when
the watchdog device is opened.

> I agree it's a very strange behavior and I'm not sure how to best
> document or support it with the Linux watchdog API. I welcome any
> recomendations!
> 

Sorry, I fail to understand your logic.

You do not explain why your code bails out if the watchdog is not already
running. That does not make sense.

You are saying that you don't want the watchdog driver to enable the watchdog.
Since its whole purpose is to enable the watchdog if/when the watchdog device
is opened, that doesn't make sense either.

At the same time, you do not tell the watchdog core that the watchdog is
already running, meaning the system _will_ reboot unless the watchdog
device is opened within the watchdog timeout period. Again, that does not
make sense.

Maybe it all makes sense to you, but not to me, sorry.

Guenter

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-03-30 18:19         ` Guenter Roeck
@ 2018-04-02 16:07           ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-02 16:07 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Fri, Mar 30, 2018 at 11:19 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Fri, Mar 30, 2018 at 10:49:38AM -0700, Tim Harvey wrote:
>> On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
>> > On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
>> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> >> ---
>> >>  drivers/watchdog/Kconfig   |  10 ++++
>> >>  drivers/watchdog/Makefile  |   1 +
>> >>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>> >>  3 files changed, 157 insertions(+)
>> >>  create mode 100644 drivers/watchdog/gsc_wdt.c
>> >>
>> <snip>
>> >> +
>> >> +static const struct watchdog_info gsc_wdt_info = {
>> >> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
>> >
>> > Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
>> >
>> >> +     .identity = "GSC Watchdog"
>> >> +};
>> >> +
>> <snip>
>> >> +
>> >> +static int gsc_wdt_probe(struct platform_device *pdev)
>> >> +{
>> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> >> +     struct device *dev = &pdev->dev;
>> >> +     struct gsc_wdt *wdt;
>> >> +     int ret;
>> >> +     unsigned int reg;
>> >> +
>> <snip>
>> >> +     /* ensure WD bit enabled */
>> >> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
>> >> +             return -EIO;
>> >> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
>> >
>> > BIT()
>> >
>> >> +             dev_err(dev, "not enabled - must be manually enabled\n");
>> >
>> > This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
>> >
>> >> +             return -EINVAL;
>> >> +     }
>> >> +
>> <snip>
>> >> +
>> >> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
>> >
>> > WATCHDOG_NOWAYOUT ?
>> >
>>
>> Guenter,
>>
>> Thanks for the review!
>>
>> The watchdog implementation of the GSC is such that it is enabled and
>> reset via a single non-volatile I2C register bit. If this bit is set
>> the watchdog will start ticking down automatically on board power up.
>> The register definitions don't provide a condition where it can be
>> enabled in a volatile way such that after board power-cycle it is
>> disabled again nor do they provide a separate register for enable vs
>> reset.
>>
>> In the typical case the user boots the board, driver registers
>> watchdog, userspace watchdog daemon enables watchdog and it starts
>> ticking. User now powers down the board and later powers it back up.
>> The watchdog was enabled previously by userspace and the register is
>> non-volatile so the watchdog starts ticking before the kernel driver
>> and watchdog daemon yet the user breaks out into the bootloader or
>> boots a different OS without a watchdog daemon and the board resets
>> without them expecting it. The feature that the watchdog starts
>> ticking at board power-up before the CPU has even fetched code was
>> part of its design and was put there to work around some SoC errata
>> that can cause the CPU to fail to fetch boot code. This has caused me
>> to implement a watchdog driver that never actually 'enables' or
>> 'disables' the watchdog which is why there is no MAGIC CLOSE and why I
>
> Yet the driver does enable and disable the watchdog in its start and stop
> functions. And I have no idea what that has to do with the MAGICCLOSE
> functionality, which is quite orthogonal to the start/stop functionality.
>
>> always set nowayout. Its possible this is a fairly unique case of a
>> watchdog. The probe failure if the watchdog isn't enabled is because I
>> don't want a non-enabled watchdog to get enabled just because the
>> driver/daemon were there.
>>
> Huh ? The whole purpose of a watchdog is for it to be enabled when
> the watchdog device is opened.
>
>> I agree it's a very strange behavior and I'm not sure how to best
>> document or support it with the Linux watchdog API. I welcome any
>> recomendations!
>>
>
> Sorry, I fail to understand your logic.
>
> You do not explain why your code bails out if the watchdog is not already
> running. That does not make sense.
>
> You are saying that you don't want the watchdog driver to enable the watchdog.
> Since its whole purpose is to enable the watchdog if/when the watchdog device
> is opened, that doesn't make sense either.
>
> At the same time, you do not tell the watchdog core that the watchdog is
> already running, meaning the system _will_ reboot unless the watchdog
> device is opened within the watchdog timeout period. Again, that does not
> make sense.
>
> Maybe it all makes sense to you, but not to me, sorry.

Guenter,

Right, I'm likely not explaining it well. Let me show the registers
and describe the feature from the GSC perspective:

I2C registers: non-volatile registers (battery backed)
0x01: GSC_CTRL_1: Sleep Wakeup Timer Control
 bit 4: WATCHDOG_TIME: 0=30 second timeout, 1=60 second timeout
 bit 5: WATCHDOG_ENABLE: 0=disable watchdog, 1=enable/reset watchdog timer

The GSC has the ability to enable/disable the primary board power
supply. In the event that the watchdog timer is enabled and reaches 0
it will power cycle the board by disabling the primary power supply
for 1 second then enabling it again. The GSC_CTRL_1 bits retain their
state during power cycles thus if WATCHDOG_ENABLE=1 and the board
power cycles the watchdog starts counting down immediately and host
software must either disable it or start resetting it before the
timeout period.

The 'use case' we have been using this in for a couple years is that
users who want to use this watchdog will enable it externally (we have
a command in the bootloader) and if enabled the kernel driver (that
I'm proposing here which we've been using out-of-tree) will register
the watchdog device and the userspace watchdog process can open the
device and start tickling it. If the watchdog is never enabled (or
disabled via the bootloader command) the kernel driver fails to probe
and the SoC's watchdog can be used. The reason this feature was added
to the GSC is that we had some errata with one of the SoC's we use
such that it's internal reset was not resetting enough of the chip and
in some cases we also wanted an external PMIC to be reset as well
which is accomplished by cycling the primary power supply.

What I'm proposing here is a watchdog driver that only has the ability
to 'reset' the watchdog timer 'if enabled'. Because the same register
is used to enable as well as reset the timer I don't want to
enable/reset it if it isn't already enabled.

Because start/stop are mandatory I suppose I could make stop a nop and
make start only set WATCHDOG_TIME if it's already set. I was thinking
that I would simply have the driver probe fail if the watchdog wasn't
externally already enabled and let /dev/watchdog0 be the sometimes
lesser valued SoC watchdog.

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-04-02 16:07           ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-02 16:07 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Mar 30, 2018 at 11:19 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Fri, Mar 30, 2018 at 10:49:38AM -0700, Tim Harvey wrote:
>> On Thu, Mar 29, 2018 at 6:07 PM, Guenter Roeck <linux@roeck-us.net> wrote:
>> > On Wed, Mar 28, 2018 at 08:14:03AM -0700, Tim Harvey wrote:
>> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> >> ---
>> >>  drivers/watchdog/Kconfig   |  10 ++++
>> >>  drivers/watchdog/Makefile  |   1 +
>> >>  drivers/watchdog/gsc_wdt.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
>> >>  3 files changed, 157 insertions(+)
>> >>  create mode 100644 drivers/watchdog/gsc_wdt.c
>> >>
>> <snip>
>> >> +
>> >> +static const struct watchdog_info gsc_wdt_info = {
>> >> +     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
>> >
>> > Please confirm that WDIOF_MAGICCLOSE is not set on purpose.
>> >
>> >> +     .identity = "GSC Watchdog"
>> >> +};
>> >> +
>> <snip>
>> >> +
>> >> +static int gsc_wdt_probe(struct platform_device *pdev)
>> >> +{
>> >> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> >> +     struct device *dev = &pdev->dev;
>> >> +     struct gsc_wdt *wdt;
>> >> +     int ret;
>> >> +     unsigned int reg;
>> >> +
>> <snip>
>> >> +     /* ensure WD bit enabled */
>> >> +     if (regmap_read(gsc->regmap, GSC_CTRL_1, &reg))
>> >> +             return -EIO;
>> >> +     if (!(reg & (1 << GSC_CTRL_1_WDT_ENABLE))) {
>> >
>> > BIT()
>> >
>> >> +             dev_err(dev, "not enabled - must be manually enabled\n");
>> >
>> > This doesn't make sense. Bail out if the watchdog is disabled ? Why ?
>> >
>> >> +             return -EINVAL;
>> >> +     }
>> >> +
>> <snip>
>> >> +
>> >> +     watchdog_set_nowayout(&wdt->wdt_dev, 1);
>> >
>> > WATCHDOG_NOWAYOUT ?
>> >
>>
>> Guenter,
>>
>> Thanks for the review!
>>
>> The watchdog implementation of the GSC is such that it is enabled and
>> reset via a single non-volatile I2C register bit. If this bit is set
>> the watchdog will start ticking down automatically on board power up.
>> The register definitions don't provide a condition where it can be
>> enabled in a volatile way such that after board power-cycle it is
>> disabled again nor do they provide a separate register for enable vs
>> reset.
>>
>> In the typical case the user boots the board, driver registers
>> watchdog, userspace watchdog daemon enables watchdog and it starts
>> ticking. User now powers down the board and later powers it back up.
>> The watchdog was enabled previously by userspace and the register is
>> non-volatile so the watchdog starts ticking before the kernel driver
>> and watchdog daemon yet the user breaks out into the bootloader or
>> boots a different OS without a watchdog daemon and the board resets
>> without them expecting it. The feature that the watchdog starts
>> ticking at board power-up before the CPU has even fetched code was
>> part of its design and was put there to work around some SoC errata
>> that can cause the CPU to fail to fetch boot code. This has caused me
>> to implement a watchdog driver that never actually 'enables' or
>> 'disables' the watchdog which is why there is no MAGIC CLOSE and why I
>
> Yet the driver does enable and disable the watchdog in its start and stop
> functions. And I have no idea what that has to do with the MAGICCLOSE
> functionality, which is quite orthogonal to the start/stop functionality.
>
>> always set nowayout. Its possible this is a fairly unique case of a
>> watchdog. The probe failure if the watchdog isn't enabled is because I
>> don't want a non-enabled watchdog to get enabled just because the
>> driver/daemon were there.
>>
> Huh ? The whole purpose of a watchdog is for it to be enabled when
> the watchdog device is opened.
>
>> I agree it's a very strange behavior and I'm not sure how to best
>> document or support it with the Linux watchdog API. I welcome any
>> recomendations!
>>
>
> Sorry, I fail to understand your logic.
>
> You do not explain why your code bails out if the watchdog is not already
> running. That does not make sense.
>
> You are saying that you don't want the watchdog driver to enable the watchdog.
> Since its whole purpose is to enable the watchdog if/when the watchdog device
> is opened, that doesn't make sense either.
>
> At the same time, you do not tell the watchdog core that the watchdog is
> already running, meaning the system _will_ reboot unless the watchdog
> device is opened within the watchdog timeout period. Again, that does not
> make sense.
>
> Maybe it all makes sense to you, but not to me, sorry.

Guenter,

Right, I'm likely not explaining it well. Let me show the registers
and describe the feature from the GSC perspective:

I2C registers: non-volatile registers (battery backed)
0x01: GSC_CTRL_1: Sleep Wakeup Timer Control
 bit 4: WATCHDOG_TIME: 0=30 second timeout, 1=60 second timeout
 bit 5: WATCHDOG_ENABLE: 0=disable watchdog, 1=enable/reset watchdog timer

The GSC has the ability to enable/disable the primary board power
supply. In the event that the watchdog timer is enabled and reaches 0
it will power cycle the board by disabling the primary power supply
for 1 second then enabling it again. The GSC_CTRL_1 bits retain their
state during power cycles thus if WATCHDOG_ENABLE=1 and the board
power cycles the watchdog starts counting down immediately and host
software must either disable it or start resetting it before the
timeout period.

The 'use case' we have been using this in for a couple years is that
users who want to use this watchdog will enable it externally (we have
a command in the bootloader) and if enabled the kernel driver (that
I'm proposing here which we've been using out-of-tree) will register
the watchdog device and the userspace watchdog process can open the
device and start tickling it. If the watchdog is never enabled (or
disabled via the bootloader command) the kernel driver fails to probe
and the SoC's watchdog can be used. The reason this feature was added
to the GSC is that we had some errata with one of the SoC's we use
such that it's internal reset was not resetting enough of the chip and
in some cases we also wanted an external PMIC to be reset as well
which is accomplished by cycling the primary power supply.

What I'm proposing here is a watchdog driver that only has the ability
to 'reset' the watchdog timer 'if enabled'. Because the same register
is used to enable as well as reset the timer I don't want to
enable/reset it if it isn't already enabled.

Because start/stop are mandatory I suppose I could make stop a nop and
make start only set WATCHDOG_TIME if it's already set. I was thinking
that I would simply have the driver probe fail if the watchdog wasn't
externally already enabled and let /dev/watchdog0 be the sometimes
lesser valued SoC watchdog.

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-04-02 16:07           ` Tim Harvey
@ 2018-04-02 16:32             ` Andrew Lunn
  -1 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-02 16:32 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Guenter Roeck, Mark Rutland, devicetree, linux-watchdog,
	Dmitry Torokhov, linux-kernel, Rob Herring, Wim Van Sebroeck,
	Mark Brown, linux-input, linux-hwmon, Lee Jones,
	linux-arm-kernel

> The 'use case' we have been using this in for a couple years is that
> users who want to use this watchdog will enable it externally (we have
> a command in the bootloader) and if enabled the kernel driver (that
> I'm proposing here which we've been using out-of-tree) will register
> the watchdog device and the userspace watchdog process can open the
> device and start tickling it. If the watchdog is never enabled (or
> disabled via the bootloader command) the kernel driver fails to probe
> and the SoC's watchdog can be used.

Hi Tim

Is there any reason not to give the user the choice to use both
watchdogs? Normally you write drivers to expose the hardware, and then
let the users choice if they want to use it.

	Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-04-02 16:32             ` Andrew Lunn
  0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-02 16:32 UTC (permalink / raw)
  To: linux-arm-kernel

> The 'use case' we have been using this in for a couple years is that
> users who want to use this watchdog will enable it externally (we have
> a command in the bootloader) and if enabled the kernel driver (that
> I'm proposing here which we've been using out-of-tree) will register
> the watchdog device and the userspace watchdog process can open the
> device and start tickling it. If the watchdog is never enabled (or
> disabled via the bootloader command) the kernel driver fails to probe
> and the SoC's watchdog can be used.

Hi Tim

Is there any reason not to give the user the choice to use both
watchdogs? Normally you write drivers to expose the hardware, and then
let the users choice if they want to use it.

	Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-03-28 15:14   ` Tim Harvey
@ 2018-04-03 15:48     ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-03 15:48 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-hwmon,
	linux-input, linux-watchdog, Randy Dunlap

On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
> The Gateworks System Controller (GSC) is an I2C slave controller
> implemented with an MSP430 micro-controller whose firmware embeds the
> following features:
>  - I/O expander (16 GPIO's) using PCA955x protocol
>  - Real Time Clock using DS1672 protocol
>  - User EEPROM using AT24 protocol
>  - HWMON using custom protocol
>  - Interrupt controller with tamper detect, user pushbotton
>  - Watchdog controller capable of full board power-cycle
>  - Power Control capable of full board power-cycle
>
> see http://trac.gateworks.com/wiki/gsc for more details
>
<snip>
> +
> +/*
> + * gsc_powerdown - API to use GSC to power down board for a specific time
> + *
> + * secs - number of seconds to remain powered off
> + */
> +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
> +{
> +       int ret;
> +       unsigned char regs[4];
> +
> +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
> +                secs);
> +       regs[0] = secs & 0xff;
> +       regs[1] = (secs >> 8) & 0xff;
> +       regs[2] = (secs >> 16) & 0xff;
> +       regs[3] = (secs >> 24) & 0xff;
> +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
> +
> +       return ret;
> +}

Any feedback on the 'powerdown' sysfs attribute that hooks to this
function? This allows the GSC to disable the board primary power
supply for 2^32 seconds and is often used to 'reset' the board
although it could also be used to put the board in a power down state
longer. I'm wondering if there is a more appropriate API for this in
the kernel that I don't know about.

I would also like to register a restart handler using this but I
believe that ARM restart handlers currently can not use I2C - is that
correct?

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-04-03 15:48     ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-03 15:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
> The Gateworks System Controller (GSC) is an I2C slave controller
> implemented with an MSP430 micro-controller whose firmware embeds the
> following features:
>  - I/O expander (16 GPIO's) using PCA955x protocol
>  - Real Time Clock using DS1672 protocol
>  - User EEPROM using AT24 protocol
>  - HWMON using custom protocol
>  - Interrupt controller with tamper detect, user pushbotton
>  - Watchdog controller capable of full board power-cycle
>  - Power Control capable of full board power-cycle
>
> see http://trac.gateworks.com/wiki/gsc for more details
>
<snip>
> +
> +/*
> + * gsc_powerdown - API to use GSC to power down board for a specific time
> + *
> + * secs - number of seconds to remain powered off
> + */
> +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
> +{
> +       int ret;
> +       unsigned char regs[4];
> +
> +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
> +                secs);
> +       regs[0] = secs & 0xff;
> +       regs[1] = (secs >> 8) & 0xff;
> +       regs[2] = (secs >> 16) & 0xff;
> +       regs[3] = (secs >> 24) & 0xff;
> +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
> +
> +       return ret;
> +}

Any feedback on the 'powerdown' sysfs attribute that hooks to this
function? This allows the GSC to disable the board primary power
supply for 2^32 seconds and is often used to 'reset' the board
although it could also be used to put the board in a power down state
longer. I'm wondering if there is a more appropriate API for this in
the kernel that I don't know about.

I would also like to register a restart handler using this but I
believe that ARM restart handlers currently can not use I2C - is that
correct?

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-04-03 15:48     ` Tim Harvey
@ 2018-04-03 16:47       ` Andrew Lunn
  -1 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-03 16:47 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck, linux-hwmon,
	devicetree, linux-watchdog, Randy Dunlap, linux-kernel,
	linux-input, linux-arm-kernel

On Tue, Apr 03, 2018 at 08:48:27AM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
> > The Gateworks System Controller (GSC) is an I2C slave controller
> > implemented with an MSP430 micro-controller whose firmware embeds the
> > following features:
> >  - I/O expander (16 GPIO's) using PCA955x protocol
> >  - Real Time Clock using DS1672 protocol
> >  - User EEPROM using AT24 protocol
> >  - HWMON using custom protocol
> >  - Interrupt controller with tamper detect, user pushbotton
> >  - Watchdog controller capable of full board power-cycle
> >  - Power Control capable of full board power-cycle
> >
> > see http://trac.gateworks.com/wiki/gsc for more details
> >
> <snip>
> > +
> > +/*
> > + * gsc_powerdown - API to use GSC to power down board for a specific time
> > + *
> > + * secs - number of seconds to remain powered off
> > + */
> > +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
> > +{
> > +       int ret;
> > +       unsigned char regs[4];
> > +
> > +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
> > +                secs);
> > +       regs[0] = secs & 0xff;
> > +       regs[1] = (secs >> 8) & 0xff;
> > +       regs[2] = (secs >> 16) & 0xff;
> > +       regs[3] = (secs >> 24) & 0xff;
> > +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
> > +
> > +       return ret;
> > +}
> 
> Any feedback on the 'powerdown' sysfs attribute that hooks to this
> function? This allows the GSC to disable the board primary power
> supply for 2^32 seconds and is often used to 'reset' the board
> although it could also be used to put the board in a power down state
> longer. I'm wondering if there is a more appropriate API for this in
> the kernel that I don't know about.

Hi Tim

RTC can cause wakeup when an alarm is set. It looks like the DS1672
does not have this. But you are emulating the DS1672 right? You could
add a second emulated RTC which does support an alarm? DS3232?

> I would also like to register a restart handler using this but I
> believe that ARM restart handlers currently can not use I2C - is that
> correct?

There are plenty which use GPIOs, or UARTs. Not seen any which use
i2c. What do you think does not work at this point?

     Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-04-03 16:47       ` Andrew Lunn
  0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-03 16:47 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Apr 03, 2018 at 08:48:27AM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
> > The Gateworks System Controller (GSC) is an I2C slave controller
> > implemented with an MSP430 micro-controller whose firmware embeds the
> > following features:
> >  - I/O expander (16 GPIO's) using PCA955x protocol
> >  - Real Time Clock using DS1672 protocol
> >  - User EEPROM using AT24 protocol
> >  - HWMON using custom protocol
> >  - Interrupt controller with tamper detect, user pushbotton
> >  - Watchdog controller capable of full board power-cycle
> >  - Power Control capable of full board power-cycle
> >
> > see http://trac.gateworks.com/wiki/gsc for more details
> >
> <snip>
> > +
> > +/*
> > + * gsc_powerdown - API to use GSC to power down board for a specific time
> > + *
> > + * secs - number of seconds to remain powered off
> > + */
> > +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
> > +{
> > +       int ret;
> > +       unsigned char regs[4];
> > +
> > +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
> > +                secs);
> > +       regs[0] = secs & 0xff;
> > +       regs[1] = (secs >> 8) & 0xff;
> > +       regs[2] = (secs >> 16) & 0xff;
> > +       regs[3] = (secs >> 24) & 0xff;
> > +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
> > +
> > +       return ret;
> > +}
> 
> Any feedback on the 'powerdown' sysfs attribute that hooks to this
> function? This allows the GSC to disable the board primary power
> supply for 2^32 seconds and is often used to 'reset' the board
> although it could also be used to put the board in a power down state
> longer. I'm wondering if there is a more appropriate API for this in
> the kernel that I don't know about.

Hi Tim

RTC can cause wakeup when an alarm is set. It looks like the DS1672
does not have this. But you are emulating the DS1672 right? You could
add a second emulated RTC which does support an alarm? DS3232?

> I would also like to register a restart handler using this but I
> believe that ARM restart handlers currently can not use I2C - is that
> correct?

There are plenty which use GPIOs, or UARTs. Not seen any which use
i2c. What do you think does not work at this point?

     Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-04-03 16:47       ` Andrew Lunn
@ 2018-04-03 17:29         ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-03 17:29 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck, linux-hwmon,
	devicetree, linux-watchdog, Randy Dunlap, linux-kernel,
	linux-input, linux-arm-kernel

On Tue, Apr 3, 2018 at 9:47 AM, Andrew Lunn <andrew@lunn.ch> wrote:
> On Tue, Apr 03, 2018 at 08:48:27AM -0700, Tim Harvey wrote:
>> On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
>> > The Gateworks System Controller (GSC) is an I2C slave controller
>> > implemented with an MSP430 micro-controller whose firmware embeds the
>> > following features:
>> >  - I/O expander (16 GPIO's) using PCA955x protocol
>> >  - Real Time Clock using DS1672 protocol
>> >  - User EEPROM using AT24 protocol
>> >  - HWMON using custom protocol
>> >  - Interrupt controller with tamper detect, user pushbotton
>> >  - Watchdog controller capable of full board power-cycle
>> >  - Power Control capable of full board power-cycle
>> >
>> > see http://trac.gateworks.com/wiki/gsc for more details
>> >
>> <snip>
>> > +
>> > +/*
>> > + * gsc_powerdown - API to use GSC to power down board for a specific time
>> > + *
>> > + * secs - number of seconds to remain powered off
>> > + */
>> > +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
>> > +{
>> > +       int ret;
>> > +       unsigned char regs[4];
>> > +
>> > +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
>> > +                secs);
>> > +       regs[0] = secs & 0xff;
>> > +       regs[1] = (secs >> 8) & 0xff;
>> > +       regs[2] = (secs >> 16) & 0xff;
>> > +       regs[3] = (secs >> 24) & 0xff;
>> > +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
>> > +
>> > +       return ret;
>> > +}
>>
>> Any feedback on the 'powerdown' sysfs attribute that hooks to this
>> function? This allows the GSC to disable the board primary power
>> supply for 2^32 seconds and is often used to 'reset' the board
>> although it could also be used to put the board in a power down state
>> longer. I'm wondering if there is a more appropriate API for this in
>> the kernel that I don't know about.
>
> Hi Tim
>
> RTC can cause wakeup when an alarm is set. It looks like the DS1672
> does not have this. But you are emulating the DS1672 right? You could
> add a second emulated RTC which does support an alarm? DS3232?

Andrew,

Thanks for the response!

An RTC alarm may indeed be a good route for the overall sleep
functionality I will look into that.

What about the 'reset' functionality? Is there something in the power
supply API for hooking in a GPIO based power switch (in my case it
would be i2c) as I would think that would be common for ATX supplies?
I didn't see anything in Documentation/power.

This is what led me to the restart handler idea. Ultimately when
someone issues a 'reboot' I would like it to use the GSC to
power-cycle the board.

>
>> I would also like to register a restart handler using this but I
>> believe that ARM restart handlers currently can not use I2C - is that
>> correct?
>
> There are plenty which use GPIOs, or UARTs. Not seen any which use
> i2c. What do you think does not work at this point?

I'll have to dig around for the e-mail thread. I recall someone else
trying to implement a restart handler for something hanging off i2c
and the issue was that by the time the (ARM) restart handler got
called interrupts were disabled making i2c unreliable. I have hooked
the ARM restart handler to my GSC powerdown in some kernels and have
had mixed results. When the handler gets called from a clean 'reboot'
things seem fine but if its called from some error condition that
halts the kernel it seems that i2c may not be reliable anymore.

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-04-03 17:29         ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-03 17:29 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Apr 3, 2018 at 9:47 AM, Andrew Lunn <andrew@lunn.ch> wrote:
> On Tue, Apr 03, 2018 at 08:48:27AM -0700, Tim Harvey wrote:
>> On Wed, Mar 28, 2018 at 8:14 AM, Tim Harvey <tharvey@gateworks.com> wrote:
>> > The Gateworks System Controller (GSC) is an I2C slave controller
>> > implemented with an MSP430 micro-controller whose firmware embeds the
>> > following features:
>> >  - I/O expander (16 GPIO's) using PCA955x protocol
>> >  - Real Time Clock using DS1672 protocol
>> >  - User EEPROM using AT24 protocol
>> >  - HWMON using custom protocol
>> >  - Interrupt controller with tamper detect, user pushbotton
>> >  - Watchdog controller capable of full board power-cycle
>> >  - Power Control capable of full board power-cycle
>> >
>> > see http://trac.gateworks.com/wiki/gsc for more details
>> >
>> <snip>
>> > +
>> > +/*
>> > + * gsc_powerdown - API to use GSC to power down board for a specific time
>> > + *
>> > + * secs - number of seconds to remain powered off
>> > + */
>> > +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
>> > +{
>> > +       int ret;
>> > +       unsigned char regs[4];
>> > +
>> > +       dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
>> > +                secs);
>> > +       regs[0] = secs & 0xff;
>> > +       regs[1] = (secs >> 8) & 0xff;
>> > +       regs[2] = (secs >> 16) & 0xff;
>> > +       regs[3] = (secs >> 24) & 0xff;
>> > +       ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
>> > +
>> > +       return ret;
>> > +}
>>
>> Any feedback on the 'powerdown' sysfs attribute that hooks to this
>> function? This allows the GSC to disable the board primary power
>> supply for 2^32 seconds and is often used to 'reset' the board
>> although it could also be used to put the board in a power down state
>> longer. I'm wondering if there is a more appropriate API for this in
>> the kernel that I don't know about.
>
> Hi Tim
>
> RTC can cause wakeup when an alarm is set. It looks like the DS1672
> does not have this. But you are emulating the DS1672 right? You could
> add a second emulated RTC which does support an alarm? DS3232?

Andrew,

Thanks for the response!

An RTC alarm may indeed be a good route for the overall sleep
functionality I will look into that.

What about the 'reset' functionality? Is there something in the power
supply API for hooking in a GPIO based power switch (in my case it
would be i2c) as I would think that would be common for ATX supplies?
I didn't see anything in Documentation/power.

This is what led me to the restart handler idea. Ultimately when
someone issues a 'reboot' I would like it to use the GSC to
power-cycle the board.

>
>> I would also like to register a restart handler using this but I
>> believe that ARM restart handlers currently can not use I2C - is that
>> correct?
>
> There are plenty which use GPIOs, or UARTs. Not seen any which use
> i2c. What do you think does not work at this point?

I'll have to dig around for the e-mail thread. I recall someone else
trying to implement a restart handler for something hanging off i2c
and the issue was that by the time the (ARM) restart handler got
called interrupts were disabled making i2c unreliable. I have hooked
the ARM restart handler to my GSC powerdown in some kernels and have
had mixed results. When the handler gets called from a clean 'reboot'
things seem fine but if its called from some error condition that
halts the kernel it seems that i2c may not be reliable anymore.

Regards,

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-04-03 17:29         ` Tim Harvey
@ 2018-04-04 13:12           ` Andrew Lunn
  -1 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-04 13:12 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck, linux-hwmon,
	devicetree, linux-watchdog, Randy Dunlap, linux-kernel,
	linux-input, linux-arm-kernel

> What about the 'reset' functionality? Is there something in the power
> supply API for hooking in a GPIO based power switch (in my case it
> would be i2c) as I would think that would be common for ATX supplies?
> I didn't see anything in Documentation/power.
> 
> This is what led me to the restart handler idea. Ultimately when
> someone issues a 'reboot' I would like it to use the GSC to
> power-cycle the board.

Hi Tim

I think you end up with the same problem. By the time you need to turn
the power supply off, too much of the kernel is shut down to be able
to use I2C. And if you are in the middle of an Oops, you have no idea
of the current state. Another I2C transaction could be under way etc.
All the current reset drivers are pretty much self contained, atomic
and use KISS hardware like a GPIO.

Maybe you best bet is to see if you can find any other I2C PMICs which
the kernel supports.

    Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-04-04 13:12           ` Andrew Lunn
  0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2018-04-04 13:12 UTC (permalink / raw)
  To: linux-arm-kernel

> What about the 'reset' functionality? Is there something in the power
> supply API for hooking in a GPIO based power switch (in my case it
> would be i2c) as I would think that would be common for ATX supplies?
> I didn't see anything in Documentation/power.
> 
> This is what led me to the restart handler idea. Ultimately when
> someone issues a 'reboot' I would like it to use the GSC to
> power-cycle the board.

Hi Tim

I think you end up with the same problem. By the time you need to turn
the power supply off, too much of the kernel is shut down to be able
to use I2C. And if you are in the middle of an Oops, you have no idea
of the current state. Another I2C transaction could be under way etc.
All the current reset drivers are pretty much self contained, atomic
and use KISS hardware like a GPIO.

Maybe you best bet is to see if you can find any other I2C PMICs which
the kernel supports.

    Andrew

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
  2018-04-04 13:12           ` Andrew Lunn
@ 2018-04-04 14:41             ` Mark Brown
  -1 siblings, 0 replies; 51+ messages in thread
From: Mark Brown @ 2018-04-04 14:41 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Tim Harvey, Lee Jones, Rob Herring, Mark Rutland,
	Dmitry Torokhov, Wim Van Sebroeck, Guenter Roeck, linux-hwmon,
	devicetree, linux-watchdog, Randy Dunlap, linux-kernel,
	linux-input, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 1337 bytes --]

On Wed, Apr 04, 2018 at 03:12:39PM +0200, Andrew Lunn wrote:
> > What about the 'reset' functionality? Is there something in the power
> > supply API for hooking in a GPIO based power switch (in my case it
> > would be i2c) as I would think that would be common for ATX supplies?
> > I didn't see anything in Documentation/power.

> > This is what led me to the restart handler idea. Ultimately when
> > someone issues a 'reboot' I would like it to use the GSC to
> > power-cycle the board.

> I think you end up with the same problem. By the time you need to turn
> the power supply off, too much of the kernel is shut down to be able
> to use I2C. And if you are in the middle of an Oops, you have no idea
> of the current state. Another I2C transaction could be under way etc.
> All the current reset drivers are pretty much self contained, atomic
> and use KISS hardware like a GPIO.

> Maybe you best bet is to see if you can find any other I2C PMICs which
> the kernel supports.

Most systems have a handshake for final power down via asserting signals
rather than using register writes, the final power down sequence usually
runs way after software.  There's a few things that don't which just
unceremoniously cut the power earlier on without completing the full
power down sequence which for all practical purposes mostly works.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 2/4] mfd: add Gateworks System Controller core driver
@ 2018-04-04 14:41             ` Mark Brown
  0 siblings, 0 replies; 51+ messages in thread
From: Mark Brown @ 2018-04-04 14:41 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Apr 04, 2018 at 03:12:39PM +0200, Andrew Lunn wrote:
> > What about the 'reset' functionality? Is there something in the power
> > supply API for hooking in a GPIO based power switch (in my case it
> > would be i2c) as I would think that would be common for ATX supplies?
> > I didn't see anything in Documentation/power.

> > This is what led me to the restart handler idea. Ultimately when
> > someone issues a 'reboot' I would like it to use the GSC to
> > power-cycle the board.

> I think you end up with the same problem. By the time you need to turn
> the power supply off, too much of the kernel is shut down to be able
> to use I2C. And if you are in the middle of an Oops, you have no idea
> of the current state. Another I2C transaction could be under way etc.
> All the current reset drivers are pretty much self contained, atomic
> and use KISS hardware like a GPIO.

> Maybe you best bet is to see if you can find any other I2C PMICs which
> the kernel supports.

Most systems have a handshake for final power down via asserting signals
rather than using register writes, the final power down sequence usually
runs way after software.  There's a few things that don't which just
unceremoniously cut the power earlier on without completing the full
power down sequence which for all practical purposes mostly works.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180404/57136718/attachment.sig>

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [v3,4/4] watchdog: add Gateworks System Controller support
  2018-04-02 16:32             ` Andrew Lunn
@ 2018-04-04 16:57               ` Tim Harvey
  -1 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-04 16:57 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Guenter Roeck, Mark Rutland, devicetree, linux-watchdog,
	Dmitry Torokhov, linux-kernel, Rob Herring, Wim Van Sebroeck,
	Mark Brown, linux-input, linux-hwmon, Lee Jones,
	linux-arm-kernel

On Mon, Apr 2, 2018 at 9:32 AM, Andrew Lunn <andrew@lunn.ch> wrote:
>> The 'use case' we have been using this in for a couple years is that
>> users who want to use this watchdog will enable it externally (we have
>> a command in the bootloader) and if enabled the kernel driver (that
>> I'm proposing here which we've been using out-of-tree) will register
>> the watchdog device and the userspace watchdog process can open the
>> device and start tickling it. If the watchdog is never enabled (or
>> disabled via the bootloader command) the kernel driver fails to probe
>> and the SoC's watchdog can be used.
>
> Hi Tim
>
> Is there any reason not to give the user the choice to use both
> watchdogs? Normally you write drivers to expose the hardware, and then
> let the users choice if they want to use it.
>

Andrew,

I don't know what value the SoC watchdog has if you have the GSC
watchdog available that can power cycle the board but I would agree
normally you would expose all of them. I suppose this may be a
different case because I'm expecting the user to go and manually
enable the watchdog instead of using the kernel to do it which means
if its not enabled its not really available to be used.

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [v3,4/4] watchdog: add Gateworks System Controller support
@ 2018-04-04 16:57               ` Tim Harvey
  0 siblings, 0 replies; 51+ messages in thread
From: Tim Harvey @ 2018-04-04 16:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Apr 2, 2018 at 9:32 AM, Andrew Lunn <andrew@lunn.ch> wrote:
>> The 'use case' we have been using this in for a couple years is that
>> users who want to use this watchdog will enable it externally (we have
>> a command in the bootloader) and if enabled the kernel driver (that
>> I'm proposing here which we've been using out-of-tree) will register
>> the watchdog device and the userspace watchdog process can open the
>> device and start tickling it. If the watchdog is never enabled (or
>> disabled via the bootloader command) the kernel driver fails to probe
>> and the SoC's watchdog can be used.
>
> Hi Tim
>
> Is there any reason not to give the user the choice to use both
> watchdogs? Normally you write drivers to expose the hardware, and then
> let the users choice if they want to use it.
>

Andrew,

I don't know what value the SoC watchdog has if you have the GSC
watchdog available that can power cycle the board but I would agree
normally you would expose all of them. I suppose this may be a
different case because I'm expecting the user to go and manually
enable the watchdog instead of using the kernel to do it which means
if its not enabled its not really available to be used.

Tim

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
  2018-03-28 20:53           ` Tim Harvey
@ 2018-04-09 19:24             ` Rob Herring
  -1 siblings, 0 replies; 51+ messages in thread
From: Rob Herring @ 2018-04-09 19:24 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Guenter Roeck, Lee Jones, Mark Rutland, Mark Brown,
	Dmitry Torokhov, Wim Van Sebroeck, linux-kernel, devicetree,
	linux-arm-kernel, linux-hwmon, linux-input, linux-watchdog

On Wed, Mar 28, 2018 at 01:53:53PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 1:23 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
> >> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> >> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> >> >> This patch adds documentation of device-tree bindings for the
> >> >> Gateworks System Controller (GSC).
> >> >>
> >> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> >> ---
> >> >> v3:
> >> >>  - replaced _ with -
> >> >>  - remove input bindings
> >> >>  - added full description of hwmon
> >> >>  - fix unit address of hwmon child nodes
> >> >>
> >> >> ---
> >> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
> >> >>  1 file changed, 135 insertions(+)
> >> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >>
> >> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >> new file mode 100644
> >> >> index 0000000..8f530ed
> >> >> --- /dev/null
> >> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >> @@ -0,0 +1,135 @@
> >> >> +Gateworks System Controller multi-function device
> >> >> +
> >> >> +The GSC is a Multifunction I2C slave device with the following submodules:
> >> >> +- WDT
> >> >> +- GPIO
> >> >> +- Pushbutton controller
> >> >> +- HWMON
> >> >> +
> >> >> +Required properties:
> >> >> +- compatible : Must be "gw,gsc"
> >> >> +- reg: I2C address of the device
> >> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
> >> >> +- interrupt-parent: Interrupt controller GSC is connected to
> >> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
> >> >> +  controller, in accordance with the "one cell" variant of
> >> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> >> >> +
> >> >> +Optional nodes:
> >> >> +* watchdog:
> >> >> +The GSC provides a Watchdog monitor which can power cycle the board's
> >> >> +primary power supply on most board models when tripped.
> >> >> +
> >> >> +Required watchdog properties:
> >> >> +- compatible: must be "gw,gsc-watchdog"
> >> >> +
> >> >> +* hwmon:
> >> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> >> >> +temperature and/or voltage monitoring.
> >> >> +
> >> >> +Required hwmon properties:
> >> >> +- compatible: must be "gw,gsc-hwmon"
> >> >> +
> >> >
> >> > "hwmon" is a very Linux specific term. It might make sense to find a more
> >> > generic term.
> >>
> >> The 'hwmon' driver supports child nodes that fall into the following category:
> >>  - temperature sensor (GSC internal temperature sensor - i2c registers
> >> returns value in C*10)
> >>  - voltage rails (two types here; cooked: i2c registers return
> >> pre-scaled value in mV), raw: i2c registers return a raw ADC value
> >> that must be scaled based on ADC internal ref voltage and resolution
> >> and adjusted for a voltage divider to convert to mV
> >>  - fan setpoints (I'll explain these below)
> >>
> >> I called the node 'gw,gsc-hwmon' because the driver fits into the
> >> 'hwmon' API. Isn't that appropriate here for the driver compatible
> >> string?
> >>
> >
> > Devicetree properties are supposed to be OS independent.

+1

> >
> >> >
> >> >> +Optional hwmon properties:
> >> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
> >> >
> >> > AFAIK devicetree likes to specify voltages in uV.
> >>
> >> There are currently plenty of dt props specified in mV (grep -r mV
> >> Documentation/devicetree/bindings/).
> >>
> >
> > "But so many others are speeding, why do I get a ticket ?"
> >
> > Please discuss with Rob.
> 
> Yes - hoping for feedback on mV vs uV as well as naming of hwmon mfd child node.

Use what is defined in 
Documentation/devicetree/bindings/property-units.txt. If you don't like 
what is there, then add to it first. But generally you should have some 
reason why what's there doesn't work for you.

Rob

^ permalink raw reply	[flat|nested] 51+ messages in thread

* [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings
@ 2018-04-09 19:24             ` Rob Herring
  0 siblings, 0 replies; 51+ messages in thread
From: Rob Herring @ 2018-04-09 19:24 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Mar 28, 2018 at 01:53:53PM -0700, Tim Harvey wrote:
> On Wed, Mar 28, 2018 at 1:23 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> > On Wed, Mar 28, 2018 at 12:17:34PM -0700, Tim Harvey wrote:
> >> On Wed, Mar 28, 2018 at 9:24 AM, Guenter Roeck <linux@roeck-us.net> wrote:
> >> > On Wed, Mar 28, 2018 at 08:14:00AM -0700, Tim Harvey wrote:
> >> >> This patch adds documentation of device-tree bindings for the
> >> >> Gateworks System Controller (GSC).
> >> >>
> >> >> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
> >> >> ---
> >> >> v3:
> >> >>  - replaced _ with -
> >> >>  - remove input bindings
> >> >>  - added full description of hwmon
> >> >>  - fix unit address of hwmon child nodes
> >> >>
> >> >> ---
> >> >>  .../devicetree/bindings/mfd/gateworks-gsc.txt      | 135 +++++++++++++++++++++
> >> >>  1 file changed, 135 insertions(+)
> >> >>  create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >>
> >> >> diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >> new file mode 100644
> >> >> index 0000000..8f530ed
> >> >> --- /dev/null
> >> >> +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.txt
> >> >> @@ -0,0 +1,135 @@
> >> >> +Gateworks System Controller multi-function device
> >> >> +
> >> >> +The GSC is a Multifunction I2C slave device with the following submodules:
> >> >> +- WDT
> >> >> +- GPIO
> >> >> +- Pushbutton controller
> >> >> +- HWMON
> >> >> +
> >> >> +Required properties:
> >> >> +- compatible : Must be "gw,gsc"
> >> >> +- reg: I2C address of the device
> >> >> +- interrupts: interrupt triggered by GSC_IRQ# signal
> >> >> +- interrupt-parent: Interrupt controller GSC is connected to
> >> >> +- #interrupt-cells: should be <1>, index of the interrupt within the
> >> >> +  controller, in accordance with the "one cell" variant of
> >> >> +  <devicetree/bindings/interrupt-controller/interrupt.txt>
> >> >> +
> >> >> +Optional nodes:
> >> >> +* watchdog:
> >> >> +The GSC provides a Watchdog monitor which can power cycle the board's
> >> >> +primary power supply on most board models when tripped.
> >> >> +
> >> >> +Required watchdog properties:
> >> >> +- compatible: must be "gw,gsc-watchdog"
> >> >> +
> >> >> +* hwmon:
> >> >> +The GSC provides a set of Analog to Digitcal Converter (ADC) pins used for
> >> >> +temperature and/or voltage monitoring.
> >> >> +
> >> >> +Required hwmon properties:
> >> >> +- compatible: must be "gw,gsc-hwmon"
> >> >> +
> >> >
> >> > "hwmon" is a very Linux specific term. It might make sense to find a more
> >> > generic term.
> >>
> >> The 'hwmon' driver supports child nodes that fall into the following category:
> >>  - temperature sensor (GSC internal temperature sensor - i2c registers
> >> returns value in C*10)
> >>  - voltage rails (two types here; cooked: i2c registers return
> >> pre-scaled value in mV), raw: i2c registers return a raw ADC value
> >> that must be scaled based on ADC internal ref voltage and resolution
> >> and adjusted for a voltage divider to convert to mV
> >>  - fan setpoints (I'll explain these below)
> >>
> >> I called the node 'gw,gsc-hwmon' because the driver fits into the
> >> 'hwmon' API. Isn't that appropriate here for the driver compatible
> >> string?
> >>
> >
> > Devicetree properties are supposed to be OS independent.

+1

> >
> >> >
> >> >> +Optional hwmon properties:
> >> >> +- gw,reference-voltage: ADC reference voltage (mV) used in scaling raw ADCs
> >> >
> >> > AFAIK devicetree likes to specify voltages in uV.
> >>
> >> There are currently plenty of dt props specified in mV (grep -r mV
> >> Documentation/devicetree/bindings/).
> >>
> >
> > "But so many others are speeding, why do I get a ticket ?"
> >
> > Please discuss with Rob.
> 
> Yes - hoping for feedback on mV vs uV as well as naming of hwmon mfd child node.

Use what is defined in 
Documentation/devicetree/bindings/property-units.txt. If you don't like 
what is there, then add to it first. But generally you should have some 
reason why what's there doesn't work for you.

Rob

^ permalink raw reply	[flat|nested] 51+ messages in thread

end of thread, other threads:[~2018-04-09 19:24 UTC | newest]

Thread overview: 51+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-03-28 15:13 [PATCH v3 0/4] Add support for the Gateworks System Controller Tim Harvey
2018-03-28 15:13 ` Tim Harvey
2018-03-28 15:14 ` [PATCH v3 1/4] dt-bindings: mfd: Add Gateworks System Controller bindings Tim Harvey
2018-03-28 15:14   ` Tim Harvey
2018-03-28 15:14   ` Tim Harvey
2018-03-28 16:24   ` Guenter Roeck
2018-03-28 16:24     ` Guenter Roeck
2018-03-28 19:17     ` Tim Harvey
2018-03-28 19:17       ` Tim Harvey
2018-03-28 20:23       ` Guenter Roeck
2018-03-28 20:23         ` Guenter Roeck
2018-03-28 20:53         ` Tim Harvey
2018-03-28 20:53           ` Tim Harvey
2018-04-09 19:24           ` Rob Herring
2018-04-09 19:24             ` Rob Herring
2018-03-28 15:14 ` [PATCH v3 2/4] mfd: add Gateworks System Controller core driver Tim Harvey
2018-03-28 15:14   ` Tim Harvey
2018-04-03 15:48   ` Tim Harvey
2018-04-03 15:48     ` Tim Harvey
2018-04-03 16:47     ` Andrew Lunn
2018-04-03 16:47       ` Andrew Lunn
2018-04-03 17:29       ` Tim Harvey
2018-04-03 17:29         ` Tim Harvey
2018-04-04 13:12         ` Andrew Lunn
2018-04-04 13:12           ` Andrew Lunn
2018-04-04 14:41           ` Mark Brown
2018-04-04 14:41             ` Mark Brown
2018-03-28 15:14 ` [PATCH v3 3/4] hwmon: add Gateworks System Controller support Tim Harvey
2018-03-28 15:14   ` Tim Harvey
2018-03-28 17:00   ` Guenter Roeck
2018-03-28 17:00     ` Guenter Roeck
2018-03-28 20:23     ` Tim Harvey
2018-03-28 20:23       ` Tim Harvey
2018-03-28 20:33       ` Guenter Roeck
2018-03-28 20:33         ` Guenter Roeck
2018-03-28 15:14 ` [PATCH v3 4/4] watchdog: " Tim Harvey
2018-03-28 15:14   ` Tim Harvey
2018-03-30  1:07   ` [v3,4/4] " Guenter Roeck
2018-03-30  1:07     ` Guenter Roeck
2018-03-30 17:48     ` Dmitry Torokhov
2018-03-30 17:48       ` Dmitry Torokhov
2018-03-30 17:49     ` Tim Harvey
2018-03-30 17:49       ` Tim Harvey
2018-03-30 18:19       ` Guenter Roeck
2018-03-30 18:19         ` Guenter Roeck
2018-04-02 16:07         ` Tim Harvey
2018-04-02 16:07           ` Tim Harvey
2018-04-02 16:32           ` Andrew Lunn
2018-04-02 16:32             ` Andrew Lunn
2018-04-04 16:57             ` Tim Harvey
2018-04-04 16:57               ` Tim Harvey

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.