Devicetree
 help / color / mirror / Atom feed
* [PATCH 4/5] Input: mpr121 - use matrix keypad helper API
From: Akinobu Mita @ 2017-01-11 17:42 UTC (permalink / raw)
  To: linux-input, devicetree; +Cc: Akinobu Mita, Dmitry Torokhov
In-Reply-To: <1484156549-26585-1-git-send-email-akinobu.mita@gmail.com>

Convert to use matrix_kaypad_build_keymap() helper.

This is preparation for adding device tree support.  This change enables
to share code between legacy platform data probe and device tree probe.

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
---
 drivers/input/keyboard/Kconfig           |  1 +
 drivers/input/keyboard/mpr121_touchkey.c | 64 +++++++++++++++++++++-----------
 2 files changed, 43 insertions(+), 22 deletions(-)

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index cbd75cf..b436e71 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -407,6 +407,7 @@ config KEYBOARD_MCS
 config KEYBOARD_MPR121
 	tristate "Freescale MPR121 Touchkey"
 	depends on I2C
+	select INPUT_MATRIXKMAP
 	help
 	  Say Y here if you have Freescale MPR121 touchkey controller
 	  chip in your system.
diff --git a/drivers/input/keyboard/mpr121_touchkey.c b/drivers/input/keyboard/mpr121_touchkey.c
index c809f70..7e85512 100644
--- a/drivers/input/keyboard/mpr121_touchkey.c
+++ b/drivers/input/keyboard/mpr121_touchkey.c
@@ -20,6 +20,7 @@
 #include <linux/bitops.h>
 #include <linux/interrupt.h>
 #include <linux/i2c/mpr121_touchkey.h>
+#include <linux/input/matrix_keypad.h>
 
 /* Register definitions */
 #define ELE_TOUCH_STATUS_0_ADDR	0x0
@@ -61,7 +62,6 @@ struct mpr121_touchkey {
 	struct input_dev	*input_dev;
 	unsigned int		statusbits;
 	unsigned int		keycount;
-	u16			keycodes[MPR121_MAX_KEY_COUNT];
 };
 
 struct mpr121_init_register {
@@ -88,6 +88,7 @@ static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id)
 	struct input_dev *input = mpr121->input_dev;
 	unsigned int bit_changed;
 	unsigned int key_num;
+	const unsigned short *keycode = input->keycode;
 	int reg;
 
 	reg = i2c_smbus_read_byte_data(client, ELE_TOUCH_STATUS_1_ADDR);
@@ -114,7 +115,7 @@ static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id)
 			continue;
 
 		pressed = reg & (1 << key_num);
-		key_val = mpr121->keycodes[key_num];
+		key_val = keycode[key_num];
 
 		input_event(input, EV_MSC, MSC_SCAN, key_num);
 		input_report_key(input, key_val, pressed);
@@ -196,23 +197,46 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 {
 	const struct mpr121_platform_data *pdata =
 			dev_get_platdata(&client->dev);
+	struct device *dev = &client->dev;
+	unsigned int keymap_size;
+	struct matrix_keymap_data *keymap_data;
 	struct mpr121_touchkey *mpr121;
 	struct input_dev *input_dev;
 	int error;
 	int i;
 
-	if (!pdata) {
-		dev_err(&client->dev, "no platform data defined\n");
-		return -EINVAL;
-	}
+	if (pdata) {
+		uint32_t *keymap;
 
-	if (!pdata->keymap || !pdata->keymap_size) {
-		dev_err(&client->dev, "missing keymap data\n");
-		return -EINVAL;
-	}
+		if (!pdata->keymap || !pdata->keymap_size) {
+			dev_err(dev, "missing keymap data\n");
+			return -EINVAL;
+		}
+
+		if (pdata->keymap_size > MPR121_MAX_KEY_COUNT) {
+			dev_err(dev, "too many keys defined\n");
+			return -EINVAL;
+		}
+
+		keymap_size = pdata->keymap_size;
+
+		keymap = devm_kcalloc(dev, keymap_size,
+				sizeof(keymap_data->keymap[0]), GFP_KERNEL);
+		if (!keymap)
+			return -ENOMEM;
 
-	if (pdata->keymap_size > MPR121_MAX_KEY_COUNT) {
-		dev_err(&client->dev, "too many keys defined\n");
+		for (i = 0; i < keymap_size; i++)
+			keymap[i] = KEY(0, i, pdata->keymap[i]);
+
+		keymap_data = devm_kzalloc(dev, sizeof(*keymap_data),
+					GFP_KERNEL);
+		if (!keymap_data)
+			return -ENOMEM;
+
+		keymap_data->keymap_size = keymap_size;
+		keymap_data->keymap = keymap;
+	} else {
+		dev_err(&client->dev, "no platform data defined\n");
 		return -EINVAL;
 	}
 
@@ -232,22 +256,18 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 
 	mpr121->client = client;
 	mpr121->input_dev = input_dev;
-	mpr121->keycount = pdata->keymap_size;
+	mpr121->keycount = keymap_size;
 
 	input_dev->name = "Freescale MPR121 Touchkey";
 	input_dev->id.bustype = BUS_I2C;
 	input_dev->dev.parent = &client->dev;
-	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+	input_dev->evbit[0] = BIT_MASK(EV_REP);
 	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
 
-	input_dev->keycode = mpr121->keycodes;
-	input_dev->keycodesize = sizeof(mpr121->keycodes[0]);
-	input_dev->keycodemax = mpr121->keycount;
-
-	for (i = 0; i < pdata->keymap_size; i++) {
-		input_set_capability(input_dev, EV_KEY, pdata->keymap[i]);
-		mpr121->keycodes[i] = pdata->keymap[i];
-	}
+	error = matrix_keypad_build_keymap(keymap_data, NULL, 1, keymap_size,
+						NULL, input_dev);
+	if (error)
+		return error;
 
 	error = mpr121_phys_init(pdata, mpr121, client);
 	if (error) {
-- 
2.7.4


^ permalink raw reply related

* [PATCH 5/5] Input: mpr121 - add device tree support
From: Akinobu Mita @ 2017-01-11 17:42 UTC (permalink / raw)
  To: linux-input-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Akinobu Mita, Dmitry Torokhov, Rob Herring
In-Reply-To: <1484156549-26585-1-git-send-email-akinobu.mita-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

This adds device tree support to mpr121 driver which currently only
supports legacy platform data probe.

Cc: Dmitry Torokhov <dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Akinobu Mita <akinobu.mita-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
 .../devicetree/bindings/input/mpr121-touchkey.txt  | 44 +++++++++++
 drivers/input/keyboard/mpr121_touchkey.c           | 87 +++++++++++++++++++---
 2 files changed, 121 insertions(+), 10 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/input/mpr121-touchkey.txt

diff --git a/Documentation/devicetree/bindings/input/mpr121-touchkey.txt b/Documentation/devicetree/bindings/input/mpr121-touchkey.txt
new file mode 100644
index 0000000..2c4a53c
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/mpr121-touchkey.txt
@@ -0,0 +1,44 @@
+* Freescale MPR121 Controllor
+
+Required Properties:
+- compatible:		Should be "fsl,mpr121-touchkey"
+- reg:			The I2C slave address of the device.
+- interrupts:		The interrupt number to the cpu.
+- vdd-supply:		Phandle to the Vdd power supply.
+- linux,keymap:		The keymap for keys as described in the binding document
+			devicetree/bindings/input/matrix-keymap.txt.
+- keypad,num-rows:	Must be 1
+- keypad,num-columns:	Number of lines connected to the keypad controller.
+
+Optional Properties:
+- wakeup-source:	Use any event on keypad as wakeup event.
+- autorepeat:		Enable autorepeat feature.
+
+Example:
+
+#include "dt-bindings/input/input.h"
+
+	touchkey: mpr121@5a {
+		compatible = "fsl,mpr121-touchkey";
+		reg = <0x5a>;
+		interrupt-parent = <&gpio1>;
+		interrupts = <28 2>;
+		autorepeat;
+		vdd-supply = <&ldo4_reg>;
+		keypad,num-rows = <1>;
+		keypad,num-columns = <12>;
+		linux,keymap = <
+			MATRIX_KEY(0x00, 0x00, KEY_0)
+			MATRIX_KEY(0x00, 0x01, KEY_1)
+			MATRIX_KEY(0x00, 0x02, KEY_2)
+			MATRIX_KEY(0x00, 0x03, KEY_3)
+			MATRIX_KEY(0x00, 0x04, KEY_4)
+			MATRIX_KEY(0x00, 0x05, KEY_5)
+			MATRIX_KEY(0x00, 0x06, KEY_6)
+			MATRIX_KEY(0x00, 0x07, KEY_7)
+			MATRIX_KEY(0x00, 0x08, KEY_8)
+			MATRIX_KEY(0x00, 0x09, KEY_9)
+			MATRIX_KEY(0x00, 0x0a, KEY_A)
+			MATRIX_KEY(0x00, 0x0b, KEY_B)
+		>;
+	};
diff --git a/drivers/input/keyboard/mpr121_touchkey.c b/drivers/input/keyboard/mpr121_touchkey.c
index 7e85512..2eb0900 100644
--- a/drivers/input/keyboard/mpr121_touchkey.c
+++ b/drivers/input/keyboard/mpr121_touchkey.c
@@ -20,6 +20,7 @@
 #include <linux/bitops.h>
 #include <linux/interrupt.h>
 #include <linux/i2c/mpr121_touchkey.h>
+#include <linux/regulator/consumer.h>
 #include <linux/input/matrix_keypad.h>
 
 /* Register definitions */
@@ -81,6 +82,42 @@ static const struct mpr121_init_register init_reg_table[] = {
 	{ AUTO_CONFIG_CTRL_ADDR, 0x0b },
 };
 
+static void mpr121_vdd_supply_disable(void *data)
+{
+	struct regulator *vdd_supply = data;
+
+	regulator_disable(vdd_supply);
+}
+
+static struct regulator *mpr121_vdd_supply_init(struct device *dev)
+{
+	struct regulator *vdd_supply;
+	int err;
+
+	vdd_supply = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(vdd_supply)) {
+		dev_err(dev, "failed to get vdd regulator: %ld\n",
+			PTR_ERR(vdd_supply));
+		return vdd_supply;
+	}
+
+	err = regulator_enable(vdd_supply);
+	if (err) {
+		dev_err(dev, "failed to enable vdd regulator: %d\n", err);
+		return ERR_PTR(err);
+	}
+
+	err = devm_add_action(dev, mpr121_vdd_supply_disable, vdd_supply);
+	if (err) {
+		regulator_disable(vdd_supply);
+		dev_err(dev, "failed to add disable regulator action: %d\n",
+			err);
+		return ERR_PTR(err);
+	}
+
+	return vdd_supply;
+}
+
 static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id)
 {
 	struct mpr121_touchkey *mpr121 = dev_id;
@@ -130,9 +167,8 @@ static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int mpr121_phys_init(const struct mpr121_platform_data *pdata,
-				      struct mpr121_touchkey *mpr121,
-				      struct i2c_client *client)
+static int mpr121_phys_init(int vdd_uv, struct mpr121_touchkey *mpr121,
+				struct i2c_client *client)
 {
 	const struct mpr121_init_register *reg;
 	unsigned char usl, lsl, tl, eleconf;
@@ -162,9 +198,9 @@ static int mpr121_phys_init(const struct mpr121_platform_data *pdata,
 	/*
 	 * Capacitance on sensing input varies and needs to be compensated.
 	 * The internal MPR121-auto-configuration can do this if it's
-	 * registers are set properly (based on pdata->vdd_uv).
+	 * registers are set properly (based on vdd_uv).
 	 */
-	vdd = pdata->vdd_uv / 1000;
+	vdd = vdd_uv / 1000;
 	usl = ((vdd - 700) * 256) / vdd;
 	lsl = (usl * 65) / 100;
 	tl = (usl * 90) / 100;
@@ -198,6 +234,9 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 	const struct mpr121_platform_data *pdata =
 			dev_get_platdata(&client->dev);
 	struct device *dev = &client->dev;
+	bool autorepeat;
+	bool wakeup;
+	int vdd_uv;
 	unsigned int keymap_size;
 	struct matrix_keymap_data *keymap_data;
 	struct mpr121_touchkey *mpr121;
@@ -218,6 +257,9 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 			return -EINVAL;
 		}
 
+		autorepeat = true;
+		wakeup = pdata->wakeup;
+		vdd_uv = pdata->vdd_uv;
 		keymap_size = pdata->keymap_size;
 
 		keymap = devm_kcalloc(dev, keymap_size,
@@ -236,8 +278,23 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 		keymap_data->keymap_size = keymap_size;
 		keymap_data->keymap = keymap;
 	} else {
-		dev_err(&client->dev, "no platform data defined\n");
-		return -EINVAL;
+		unsigned int rows;
+		struct regulator *vdd_supply;
+
+		error = matrix_keypad_parse_of_params(dev, &rows, &keymap_size);
+		if (rows != 1) {
+			dev_err(dev, "number of keypad rows must be 1\n");
+			return -EINVAL;
+		}
+
+		vdd_supply = mpr121_vdd_supply_init(dev);
+		if (IS_ERR(vdd_supply))
+			return PTR_ERR(vdd_supply);
+
+		vdd_uv = regulator_get_voltage(vdd_supply);
+		autorepeat = device_property_present(dev, "autorepeat");
+		wakeup = device_property_read_bool(dev, "wakeup-source");
+		keymap_data = NULL;
 	}
 
 	if (!client->irq) {
@@ -261,15 +318,16 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 	input_dev->name = "Freescale MPR121 Touchkey";
 	input_dev->id.bustype = BUS_I2C;
 	input_dev->dev.parent = &client->dev;
-	input_dev->evbit[0] = BIT_MASK(EV_REP);
 	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+	if (autorepeat)
+		__set_bit(EV_REP, input_dev->evbit);
 
 	error = matrix_keypad_build_keymap(keymap_data, NULL, 1, keymap_size,
 						NULL, input_dev);
 	if (error)
 		return error;
 
-	error = mpr121_phys_init(pdata, mpr121, client);
+	error = mpr121_phys_init(vdd_uv, mpr121, client);
 	if (error) {
 		dev_err(&client->dev, "Failed to init register\n");
 		return error;
@@ -289,7 +347,7 @@ static int mpr_touchkey_probe(struct i2c_client *client,
 		return error;
 
 	i2c_set_clientdata(client, mpr121);
-	device_init_wakeup(&client->dev, pdata->wakeup);
+	device_init_wakeup(dev, wakeup);
 
 	return 0;
 }
@@ -330,10 +388,19 @@ static const struct i2c_device_id mpr121_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, mpr121_id);
 
+#ifdef CONFIG_OF
+static const struct of_device_id mpr121_touchkey_dt_match_table[] = {
+	{ .compatible = "fsl,mpr121-touchkey" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mpr121_touchkey_dt_match_table);
+#endif
+
 static struct i2c_driver mpr_touchkey_driver = {
 	.driver = {
 		.name	= "mpr121",
 		.pm	= &mpr121_touchkey_pm_ops,
+		.of_match_table = of_match_ptr(mpr121_touchkey_dt_match_table),
 	},
 	.id_table	= mpr121_id,
 	.probe		= mpr_touchkey_probe,
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 0/4] Amlogic Meson SAR ADC support
From: Martin Blumenstingl @ 2017-01-11 17:43 UTC (permalink / raw)
  To: jic23-DgEjT+Ai2ygdnm+yROfE0A, knaack.h-Mmb7MZpHnFY,
	lars-Qo5EllUWu/uELgA04lAiVw, pmeerw-jW+XmwGofnusTnJN9+BGXg,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	khilman-rdvid1DuHRBWk0Htik3J/w, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA
  Cc: carlo-KA+7E9HrN00dnm+yROfE0A, catalin.marinas-5wv7dgnIgG8,
	will.deacon-5wv7dgnIgG8, mturquette-rdvid1DuHRBWk0Htik3J/w,
	sboyd-sgV2jX0FEOL9JmXXK+q4OQ, narmstrong-rdvid1DuHRBWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Martin Blumenstingl

This series add support for the SAR ADC on Amlogic Meson GXBB, GXL and
GXM SoCs.
The hardware on GXBB provides 10-bit ADC results, while GXL and GXM are
providing 12-bit results. Support for older SoCs (Meson8b and Meson8)
can be added with little effort, most of which is testing I guess (I
don't have any pre-GXBB hardware so I can't say).

A new set of clocks had to be added to the GXBB clock controller (used
by the GXBB/GXL/GXM SoCs) which are required to get the ADC working.

The ADC itself can sample multiple channels at the same time and allows
capturing multiple samples (which can be used for filtering/averaging).
The ADC results are stored inside a FIFO register. More details on what
the driver supports (or doesn't) can be found in the description of
patch #3.

The code is based on the public S805 (Meson8b) and S905 (GXBB)
datasheets, as well as by reading (various versions of) the vendor
driver and by inspecting the registers on the vendor kernels of my
testing-hardware.

Typical use-cases for the ADC on the Meson GX SoCs are:
- adc-keys ("ADC attached resistor ladder buttons")
- SoC temperature measurement (not supported by this driver yet as
  the system firmware does this already and provides the values via the
  SCPI protocol)
- "version-strapping" (different resistor values are used to indicate
  the board-revision)
- and of course typical ADC measurements


Martin Blumenstingl (4):
  Documentation: dt-bindings: add the Amlogic Meson SAR ADC
    documentation
  clk: gxbb: add the SAR ADC clocks and expose them
  iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
  ARM64: dts: meson: meson-gx: add the SAR ADC

 .../bindings/iio/adc/amlogic,meson-saradc.txt      |  31 +
 arch/arm64/boot/dts/amlogic/meson-gx.dtsi          |   8 +
 arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi        |  10 +
 arch/arm64/boot/dts/amlogic/meson-gxl.dtsi         |  10 +
 drivers/clk/meson/gxbb.c                           |  48 ++
 drivers/clk/meson/gxbb.h                           |   9 +-
 drivers/iio/adc/Kconfig                            |  12 +
 drivers/iio/adc/Makefile                           |   1 +
 drivers/iio/adc/meson_saradc.c                     | 860 +++++++++++++++++++++
 include/dt-bindings/clock/gxbb-clkc.h              |   4 +
 10 files changed, 990 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt
 create mode 100644 drivers/iio/adc/meson_saradc.c

-- 
2.11.0

^ permalink raw reply

* [PATCH 1/4] Documentation: dt-bindings: add the Amlogic Meson SAR ADC documentation
From: Martin Blumenstingl @ 2017-01-11 17:43 UTC (permalink / raw)
  To: jic23-DgEjT+Ai2ygdnm+yROfE0A, knaack.h-Mmb7MZpHnFY,
	lars-Qo5EllUWu/uELgA04lAiVw, pmeerw-jW+XmwGofnusTnJN9+BGXg,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	khilman-rdvid1DuHRBWk0Htik3J/w, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA
  Cc: carlo-KA+7E9HrN00dnm+yROfE0A, catalin.marinas-5wv7dgnIgG8,
	will.deacon-5wv7dgnIgG8, mturquette-rdvid1DuHRBWk0Htik3J/w,
	sboyd-sgV2jX0FEOL9JmXXK+q4OQ, narmstrong-rdvid1DuHRBWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Martin Blumenstingl
In-Reply-To: <20170111174334.24343-1-martin.blumenstingl-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

This adds the devicetree binding documentation for the SAR ADC found in
Amlogic Meson SoCs.
Currently only the GXBB, GXL and GXM SoCs are supported.

Signed-off-by: Martin Blumenstingl <martin.blumenstingl-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>
---
 .../bindings/iio/adc/amlogic,meson-saradc.txt      | 31 ++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt

diff --git a/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt b/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt
new file mode 100644
index 000000000000..9a0bec7afc63
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt
@@ -0,0 +1,31 @@
+* Amlogic Meson SAR (Successive Approximation Register) A/D converter
+
+Required properties:
+- compatible:	depending on the SoC this should be one of:
+			- "amlogic,meson-gxbb-saradc" for GXBB
+			- "amlogic,meson-gxl-saradc" for GXL and GXM
+		along with the generic "amlogic,meson-saradc"
+- reg:		the physical base address and length of the registers
+- clocks:	phandle and clock identifier (see clock-names)
+- clock-names:	mandatory clocks:
+			- "clkin" for the reference clock (typically XTAL)
+			- "core" for the SAR ADC core clock
+		optional clocks:
+			- "sana" for the analog clock
+			- "adc_clk" for the ADC (sampling) clock
+			- "adc_sel" for the ADC (sampling) clock mux
+- vref-supply:	the regulator supply for the ADC reference voltage
+- #io-channel-cells: must be 1, see ../iio-bindings.txt
+
+Example:
+	saradc: adc@8680 {
+		compatible = "amlogic,meson-gxl-saradc", "amlogic,meson-saradc";
+		#io-channel-cells = <1>;
+		reg = <0x0 0x8680 0x0 0x34>;
+		clocks = <&xtal>,
+			 <&clkc CLKID_SAR_ADC>,
+			 <&clkc CLKID_SANA>,
+			 <&clkc CLKID_SAR_ADC_CLK>,
+			 <&clkc CLKID_SAR_ADC_SEL>;
+		clock-names = "clkin", "core", "sana", "adc_clk", "adc_sel";
+	};
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH 2/4] clk: gxbb: add the SAR ADC clocks and expose them
From: Martin Blumenstingl @ 2017-01-11 17:43 UTC (permalink / raw)
  To: jic23-DgEjT+Ai2ygdnm+yROfE0A, knaack.h-Mmb7MZpHnFY,
	lars-Qo5EllUWu/uELgA04lAiVw, pmeerw-jW+XmwGofnusTnJN9+BGXg,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	khilman-rdvid1DuHRBWk0Htik3J/w, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA
  Cc: carlo-KA+7E9HrN00dnm+yROfE0A, catalin.marinas-5wv7dgnIgG8,
	will.deacon-5wv7dgnIgG8, mturquette-rdvid1DuHRBWk0Htik3J/w,
	sboyd-sgV2jX0FEOL9JmXXK+q4OQ, narmstrong-rdvid1DuHRBWk0Htik3J/w,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	Martin Blumenstingl
In-Reply-To: <20170111174334.24343-1-martin.blumenstingl-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

The HHI_SAR_CLK_CNTL contains three SAR ADC specific clocks:
- a mux clock to choose between different ADC reference clocks (this is
  2-bit wide, but the datasheet only lists the parents for the first
  bit)
- a divider for the input/reference clock
- a gate which enables the ADC clock

Additionally this exposes the ADC core clock (CLKID_SAR_ADC) and
CLKID_SANA (which seems to enable the analog inputs, but unfortunately
there is no documentation for this - we just mimic what the vendor
driver does).

Signed-off-by: Martin Blumenstingl <martin.blumenstingl-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>
---
 drivers/clk/meson/gxbb.c              | 48 +++++++++++++++++++++++++++++++++++
 drivers/clk/meson/gxbb.h              |  9 ++++---
 include/dt-bindings/clock/gxbb-clkc.h |  4 +++
 3 files changed, 58 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c
index 9d9af446bafc..1c1ec137a3cc 100644
--- a/drivers/clk/meson/gxbb.c
+++ b/drivers/clk/meson/gxbb.c
@@ -564,6 +564,46 @@ static struct clk_gate gxbb_clk81 = {
 	},
 };
 
+static struct clk_mux gxbb_sar_adc_clk_sel = {
+	.reg = (void *)HHI_SAR_CLK_CNTL,
+	.mask = 0x3,
+	.shift = 9,
+	.lock = &clk_lock,
+	.hw.init = &(struct clk_init_data){
+		.name = "sar_adc_clk_sel",
+		.ops = &clk_mux_ops,
+		/* NOTE: The datasheet doesn't list the parents for bit 10 */
+		.parent_names = (const char *[]){ "xtal", "clk81", },
+		.num_parents = 2,
+	},
+};
+
+static struct clk_divider gxbb_sar_adc_clk_div = {
+	.reg = (void *)HHI_SAR_CLK_CNTL,
+	.shift = 0,
+	.width = 8,
+	.lock = &clk_lock,
+	.hw.init = &(struct clk_init_data){
+		.name = "sar_adc_clk_div",
+		.ops = &clk_divider_ops,
+		.parent_names = (const char *[]){ "sar_adc_clk_sel" },
+		.num_parents = 1,
+	},
+};
+
+static struct clk_gate gxbb_sar_adc_clk = {
+	.reg = (void *)HHI_SAR_CLK_CNTL,
+	.bit_idx = 8,
+	.lock = &clk_lock,
+	.hw.init = &(struct clk_init_data){
+		.name = "sar_adc_clk",
+		.ops = &clk_gate_ops,
+		.parent_names = (const char *[]){ "sar_adc_clk_div" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+};
+
 /* Everything Else (EE) domain gates */
 static MESON_GATE(gxbb_ddr, HHI_GCLK_MPEG0, 0);
 static MESON_GATE(gxbb_dos, HHI_GCLK_MPEG0, 1);
@@ -754,6 +794,9 @@ static struct clk_hw_onecell_data gxbb_hw_onecell_data = {
 		[CLKID_SD_EMMC_A]	    = &gxbb_emmc_a.hw,
 		[CLKID_SD_EMMC_B]	    = &gxbb_emmc_b.hw,
 		[CLKID_SD_EMMC_C]	    = &gxbb_emmc_c.hw,
+		[CLKID_SAR_ADC_CLK]	    = &gxbb_sar_adc_clk.hw,
+		[CLKID_SAR_ADC_SEL]	    = &gxbb_sar_adc_clk_sel.hw,
+		[CLKID_SAR_ADC_DIV]	    = &gxbb_sar_adc_clk_div.hw,
 	},
 	.num = NR_CLKS,
 };
@@ -856,6 +899,7 @@ static struct clk_gate *gxbb_clk_gates[] = {
 	&gxbb_emmc_a,
 	&gxbb_emmc_b,
 	&gxbb_emmc_c,
+	&gxbb_sar_adc_clk,
 };
 
 static int gxbb_clkc_probe(struct platform_device *pdev)
@@ -888,6 +932,10 @@ static int gxbb_clkc_probe(struct platform_device *pdev)
 	gxbb_mpeg_clk_sel.reg = clk_base + (u64)gxbb_mpeg_clk_sel.reg;
 	gxbb_mpeg_clk_div.reg = clk_base + (u64)gxbb_mpeg_clk_div.reg;
 
+	/* Populate the base address for the SAR ADC clks */
+	gxbb_sar_adc_clk_sel.reg = clk_base + (u64)gxbb_sar_adc_clk_sel.reg;
+	gxbb_sar_adc_clk_div.reg = clk_base + (u64)gxbb_sar_adc_clk_div.reg;
+
 	/* Populate base address for gates */
 	for (i = 0; i < ARRAY_SIZE(gxbb_clk_gates); i++)
 		gxbb_clk_gates[i]->reg = clk_base +
diff --git a/drivers/clk/meson/gxbb.h b/drivers/clk/meson/gxbb.h
index 0252939ba58f..d90052d74abd 100644
--- a/drivers/clk/meson/gxbb.h
+++ b/drivers/clk/meson/gxbb.h
@@ -191,7 +191,7 @@
 #define CLKID_PERIPHS		  20
 #define CLKID_SPICC		  21
 /* CLKID_I2C */
-#define CLKID_SAR_ADC		  23
+/* #define CLKID_SAR_ADC */
 #define CLKID_SMART_CARD	  24
 #define CLKID_RNG0		  25
 #define CLKID_UART0		  26
@@ -237,7 +237,7 @@
 #define CLKID_MMC_PCLK		  66
 #define CLKID_DVIN		  67
 #define CLKID_UART2		  68
-#define CLKID_SANA		  69
+/* #define CLKID_SANA */
 #define CLKID_VPU_INTR		  70
 #define CLKID_SEC_AHB_AHB3_BRIDGE 71
 #define CLKID_CLK81_A53		  72
@@ -265,8 +265,11 @@
 /* CLKID_SD_EMMC_A */
 /* CLKID_SD_EMMC_B */
 /* CLKID_SD_EMMC_C */
+/* CLKID_SAR_ADC_CLK */
+/* CLKID_SAR_ADC_SEL */
+#define CLKID_SAR_ADC_DIV	  99
 
-#define NR_CLKS			  97
+#define NR_CLKS			  100
 
 /* include the CLKIDs that have been made part of the stable DT binding */
 #include <dt-bindings/clock/gxbb-clkc.h>
diff --git a/include/dt-bindings/clock/gxbb-clkc.h b/include/dt-bindings/clock/gxbb-clkc.h
index baade6f429d0..c2e93676010d 100644
--- a/include/dt-bindings/clock/gxbb-clkc.h
+++ b/include/dt-bindings/clock/gxbb-clkc.h
@@ -14,15 +14,19 @@
 #define CLKID_MPLL2		15
 #define CLKID_SPI		34
 #define CLKID_I2C		22
+#define CLKID_SAR_ADC		23
 #define CLKID_ETH		36
 #define CLKID_USB0		50
 #define CLKID_USB1		51
 #define CLKID_USB		55
 #define CLKID_USB1_DDR_BRIDGE	64
 #define CLKID_USB0_DDR_BRIDGE	65
+#define CLKID_SANA		69
 #define CLKID_AO_I2C		93
 #define CLKID_SD_EMMC_A		94
 #define CLKID_SD_EMMC_B		95
 #define CLKID_SD_EMMC_C		96
+#define CLKID_SAR_ADC_CLK	97
+#define CLKID_SAR_ADC_SEL	98
 
 #endif /* __GXBB_CLKC_H */
-- 
2.11.0

^ permalink raw reply related

* [PATCH 3/4] iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
From: Martin Blumenstingl @ 2017-01-11 17:43 UTC (permalink / raw)
  To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland, khilman,
	linux-iio, devicetree, linux-amlogic, linux-clk
  Cc: carlo, catalin.marinas, will.deacon, mturquette, sboyd,
	narmstrong, linux-arm-kernel, Martin Blumenstingl
In-Reply-To: <20170111174334.24343-1-martin.blumenstingl@googlemail.com>

This adds support for the SAR (Successive Approximation Register) ADC
on the Amlogic Meson SoCs.

The code is based on the public S805 (Meson8b) and S905 (GXBB)
datasheets, as well as by reading (various versions of) the vendor
driver and by inspecting the registers on the vendor kernels of my
testing-hardware.

Currently the GXBB, GXL and GXM SoCs are supported. GXBB hardware has
10-bit ADC resolution, while GXL and GXM have 12-bit ADC resolution.
The code was written to support older SoCs (Meson8 and Meson8b) as well,
but due to lack of actual testing-hardware no of_device_id was added for
these.

Two "features" from the vendor driver are currently missing:
- the vendor driver uses channel #7 for calibration (this improves the
  accuracy of the results - in my tests the results were less than 3%
  off without calibration compared to the vendor driver). Adding support
  for this should be easy, but is not required for most applications.
- channel #6 is connected to the SoCs internal temperature sensor.
  Adding support for this is probably not so easy since (based on the
  u-boot sources) most SoC versions are using different registers and
  algorithms for the conversion from "ADC value" to temperature.

Supported by the hardware but currently not supported by the driver:
- reading multiple channels at the same time (the hardware has a FIFO
  buffer which stores multiple results)
- continuous sampling (this would require a way to enable this
  individually because otherwise the ADC would be drawing power
  constantly)
- interrupt support (similar to the vendor driver this new driver is
  polling the results. It is unclear if the IRQ-mode is supported on
  older (Meson6 or Meson8) hardware as well or if there are any errata)

Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
 drivers/iio/adc/Kconfig        |  12 +
 drivers/iio/adc/Makefile       |   1 +
 drivers/iio/adc/meson_saradc.c | 860 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 873 insertions(+)
 create mode 100644 drivers/iio/adc/meson_saradc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 9c8b558ba19e..86059b9b91bf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -371,6 +371,18 @@ config MEN_Z188_ADC
 	  This driver can also be built as a module. If so, the module will be
 	  called men_z188_adc.
 
+config MESON_SARADC
+	tristate "Amlogic Meson SAR ADC driver"
+	default ARCH_MESON
+	depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
+	select REGMAP_MMIO
+	help
+	  Say yes here to build support for the SAR ADC found in Amlogic Meson
+	  SoCs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called meson_saradc.
+
 config MXS_LRADC
         tristate "Freescale i.MX23/i.MX28 LRADC"
         depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index d36c4be8d1fc..de05b9e75f8f 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
 obj-$(CONFIG_MCP3422) += mcp3422.o
 obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o
 obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
+obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
 obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
new file mode 100644
index 000000000000..06e8ac620385
--- /dev/null
+++ b/drivers/iio/adc/meson_saradc.c
@@ -0,0 +1,860 @@
+/*
+ * Amlogic Meson Successive Approximation Register (SAR) A/D Converter
+ *
+ * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/iio.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/reset.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define SAR_ADC_REG0						0x00
+	#define SAR_ADC_REG0_PANEL_DETECT			BIT(31)
+	#define SAR_ADC_REG0_BUSY_MASK				GENMASK(30, 28)
+	#define SAR_ADC_REG0_DELTA_BUSY				BIT(30)
+	#define SAR_ADC_REG0_AVG_BUSY				BIT(29)
+	#define SAR_ADC_REG0_SAMPLE_BUSY			BIT(28)
+	#define SAR_ADC_REG0_FIFO_FULL				BIT(27)
+	#define SAR_ADC_REG0_FIFO_EMPTY				BIT(26)
+	#define SAR_ADC_REG0_FIFO_COUNT_MASK			GENMASK(25, 21)
+	#define SAR_ADC_REG0_ADC_BIAS_CTRL_MASK			GENMASK(20, 19)
+	#define SAR_ADC_REG0_CURR_CHAN_ID_MASK			GENMASK(18, 16)
+	#define SAR_ADC_REG0_ADC_TEMP_SEN_SEL			BIT(15)
+	#define SAR_ADC_REG0_SAMPLING_STOP			BIT(14)
+	#define SAR_ADC_REG0_CHAN_DELTA_EN_MASK			GENMASK(13, 12)
+	#define SAR_ADC_REG0_DETECT_IRQ_POL			BIT(10)
+	#define SAR_ADC_REG0_DETECT_IRQ_EN			BIT(9)
+	#define SAR_ADC_REG0_FIFO_CNT_IRQ_MASK			GENMASK(8, 4)
+	#define SAR_ADC_REG0_FIFO_IRQ_EN			BIT(3)
+	#define SAR_ADC_REG0_SAMPLING_START			BIT(2)
+	#define SAR_ADC_REG0_CONTINUOUS_EN			BIT(1)
+	#define SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE		BIT(0)
+
+#define SAR_ADC_CHAN_LIST					0x04
+	#define SAR_ADC_CHAN_LIST_MAX_INDEX_MASK		GENMASK(26, 24)
+	#define SAR_ADC_CHAN_CHAN_ENTRY_MASK(_chan)		\
+					(GENMASK(2, 0) << (_chan * 3))
+
+#define SAR_ADC_AVG_CNTL					0x08
+	#define SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan)		\
+					(16 + (_chan * 2))
+	#define SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan)		\
+					(GENMASK(17, 16) << (_chan * 2))
+	#define SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan)	\
+					(0 + (_chan * 2))
+	#define SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan)	\
+					(GENMASK(1, 0) << (_chan * 2))
+
+#define SAR_ADC_REG3						0x0c
+	#define SAR_ADC_REG3_CNTL_USE_SC_DLY			BIT(31)
+	#define SAR_ADC_REG3_CLK_EN				BIT(30)
+	#define SAR_ADC_REG3_BL30_INITIALIZED			BIT(28)
+	#define SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN		BIT(27)
+	#define SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE		BIT(26)
+	#define SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK		GENMASK(25, 23)
+	#define SAR_ADC_REG3_DETECT_EN				BIT(22)
+	#define SAR_ADC_REG3_ADC_EN				BIT(21)
+	#define SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK		GENMASK(20, 18)
+	#define SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK	GENMASK(17, 16)
+	#define SAR_ADC_REG3_ADC_CLK_DIV_SHIFT			10
+	#define SAR_ADC_REG3_ADC_CLK_DIV_WIDTH			5
+	#define SAR_ADC_REG3_ADC_CLK_DIV_MASK			GENMASK(15, 10)
+	#define SAR_ADC_REG3_BLOCK_DLY_SEL_MASK			GENMASK(9, 8)
+	#define SAR_ADC_REG3_BLOCK_DLY_MASK			GENMASK(7, 0)
+
+#define SAR_ADC_DELAY						0x10
+	#define SAR_ADC_DELAY_INPUT_DLY_SEL_MASK		GENMASK(25, 24)
+	#define SAR_ADC_DELAY_BL30_BUSY				BIT(15)
+	#define SAR_ADC_DELAY_KERNEL_BUSY			BIT(14)
+	#define SAR_ADC_DELAY_INPUT_DLY_CNT_MASK		GENMASK(23, 16)
+	#define SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK		GENMASK(9, 8)
+	#define SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK		GENMASK(7, 0)
+
+#define SAR_ADC_LAST_RD						0x14
+	#define SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK		GENMASK(23, 16)
+	#define SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK		GENMASK(9, 0)
+
+#define SAR_ADC_FIFO_RD						0x18
+	#define SAR_ADC_FIFO_RD_CHAN_ID_MASK			GENMASK(14, 12)
+	#define SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK		GENMASK(11, 0)
+
+#define SAR_ADC_AUX_SW						0x1c
+	#define SAR_ADC_AUX_SW_MUX_SEL_CHAN_MASK(_chan)		\
+					(GENMASK(10, 8) << ((_chan - 2) * 2))
+	#define SAR_ADC_AUX_SW_VREF_P_MUX			BIT(6)
+	#define SAR_ADC_AUX_SW_VREF_N_MUX			BIT(5)
+	#define SAR_ADC_AUX_SW_MODE_SEL				BIT(4)
+	#define SAR_ADC_AUX_SW_YP_DRIVE_SW			BIT(3)
+	#define SAR_ADC_AUX_SW_XP_DRIVE_SW			BIT(2)
+	#define SAR_ADC_AUX_SW_YM_DRIVE_SW			BIT(1)
+	#define SAR_ADC_AUX_SW_XM_DRIVE_SW			BIT(0)
+
+#define SAR_ADC_CHAN_10_SW					0x20
+	#define SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK		GENMASK(25, 23)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX		BIT(22)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX		BIT(21)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL		BIT(20)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW		BIT(19)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW		BIT(18)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW		BIT(17)
+	#define SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW		BIT(16)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK		GENMASK(9, 7)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX		BIT(6)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX		BIT(5)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL		BIT(4)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW		BIT(3)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW		BIT(2)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW		BIT(1)
+	#define SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW		BIT(0)
+
+#define SAR_ADC_DETECT_IDLE_SW					0x24
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN		BIT(26)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK	GENMASK(25, 23)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_P_MUX	BIT(22)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_N_MUX	BIT(21)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL		BIT(20)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YP_DRIVE_SW	BIT(19)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XP_DRIVE_SW	BIT(18)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YM_DRIVE_SW	BIT(17)
+	#define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XM_DRIVE_SW	BIT(16)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK	GENMASK(9, 7)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_P_MUX	BIT(6)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_N_MUX	BIT(5)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL		BIT(4)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YP_DRIVE_SW	BIT(3)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XP_DRIVE_SW	BIT(2)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YM_DRIVE_SW	BIT(1)
+	#define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XM_DRIVE_SW	BIT(0)
+
+#define SAR_ADC_DELTA_10					0x28
+	#define SAR_ADC_DELTA_10_TEMP_SEL			BIT(27)
+	#define SAR_ADC_DELTA_10_TS_REVE1			BIT(26)
+	#define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_SHIFT	16
+	#define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK		GENMASK(25, 16)
+	#define SAR_ADC_DELTA_10_TS_REVE0			BIT(15)
+	#define SAR_ADC_DELTA_10_TS_C_SHIFT			11
+	#define SAR_ADC_DELTA_10_TS_C_MASK			GENMASK(14, 11)
+	#define SAR_ADC_DELTA_10_TS_VBG_EN			BIT(10)
+	#define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_SHIFT	0
+	#define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK		GENMASK(9, 0)
+
+/* NOTE: registers from here are undocumented (the vendor Linux kernel driver
+ * and u-boot source served as reference). These only seem to be relevant on
+ * GXBB and newer.
+ */
+#define SAR_ADC_REG11						0x2c
+	#define SAR_ADC_REG11_BANDGAP_EN			BIT(13)
+
+#define SAR_ADC_REG13						0x34
+	#define SAR_ADC_REG13_12BIT_CALIBRATION_MASK		GENMASK(13, 8)
+
+#define SAR_ADC_MAX_FIFO_SIZE		32
+#define SAR_ADC_NUM_CHANNELS		ARRAY_SIZE(meson_saradc_iio_channels)
+#define SAR_ADC_VALUE_MASK(_priv)	(BIT(_priv->resolution) - 1)
+
+#define MESON_SAR_ADC_CHAN(_chan, _type) {				\
+	.type = _type,							\
+	.indexed = true,						\
+	.channel = _chan,						\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
+				BIT(IIO_CHAN_INFO_AVERAGE_RAW),		\
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),		\
+	.datasheet_name = "SAR_ADC_CH"#_chan,				\
+}
+
+/* TODO: the hardware supports IIO_TEMP for channel 6 as well which is
+ * currently not supported by this driver.
+ */
+static const struct iio_chan_spec meson_saradc_iio_channels[] = {
+	MESON_SAR_ADC_CHAN(0, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(1, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(2, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(3, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(4, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(5, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(6, IIO_VOLTAGE),
+	MESON_SAR_ADC_CHAN(7, IIO_VOLTAGE),
+	IIO_CHAN_SOFT_TIMESTAMP(8),
+};
+
+enum meson_saradc_avg_mode {
+	NO_AVERAGING = 0x0,
+	MEAN_AVERAGING = 0x1,
+	MEDIAN_AVERAGING = 0x2,
+};
+
+enum meson_saradc_num_samples {
+	ONE_SAMPLE = 0x0,
+	TWO_SAMPLES = 0x1,
+	FOUR_SAMPLES = 0x2,
+	EIGHT_SAMPLES = 0x3,
+};
+
+enum meson_saradc_chan7_mux_sel {
+	CHAN7_MUX_VSS = 0x0,
+	CHAN7_MUX_VDD_DIV4 = 0x1,
+	CHAN7_MUX_VDD_DIV2 = 0x2,
+	CHAN7_MUX_VDD_MUL3_DIV4 = 0x3,
+	CHAN7_MUX_VDD = 0x4,
+	CHAN7_MUX_CH7_INPUT = 0x7,
+};
+
+struct meson_saradc_priv {
+	struct regmap			*regmap;
+	struct clk			*clkin;
+	struct clk			*core_clk;
+	struct clk			*sana_clk;
+	struct clk			*adc_sel_clk;
+	struct clk			*adc_clk;
+	struct clk_gate			clk_gate;
+	struct clk			*adc_div_clk;
+	struct clk_divider		clk_div;
+	struct regulator		*vref;
+	struct completion		completion;
+	u8				resolution;
+};
+
+static const struct regmap_config meson_saradc_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = SAR_ADC_REG13,
+};
+
+static unsigned int meson_saradc_get_fifo_count(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	u32 regval;
+
+	regmap_read(priv->regmap, SAR_ADC_REG0, &regval);
+
+	return FIELD_GET(SAR_ADC_REG0_FIFO_COUNT_MASK, regval);
+}
+
+static int meson_saradc_wait_busy_clear(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int regval, timeout = 10000;
+
+	do {
+		udelay(1);
+		regmap_read(priv->regmap, SAR_ADC_REG0, &regval);
+	} while (FIELD_GET(SAR_ADC_REG0_BUSY_MASK, regval) && timeout--);
+
+	if (timeout < 0)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int meson_saradc_read_raw_sample(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan,
+					int *val)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0;
+
+	ret = meson_saradc_wait_busy_clear(indio_dev);
+	if (ret)
+		return ret;
+
+	regmap_read(priv->regmap, SAR_ADC_REG0, &regval);
+
+	while (meson_saradc_get_fifo_count(indio_dev) > 0 &&
+	       count < SAR_ADC_MAX_FIFO_SIZE) {
+		regmap_read(priv->regmap, SAR_ADC_FIFO_RD, &regval);
+
+		fifo_chan = FIELD_GET(SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
+		if (fifo_chan == chan->channel) {
+			fifo_val = FIELD_GET(SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
+					     regval) & SAR_ADC_VALUE_MASK(priv);
+			sum += fifo_val;
+			count++;
+		}
+	}
+
+	if (!count)
+		return -ENOENT;
+
+	*val = sum / count;
+
+	return 0;
+}
+
+static void meson_saradc_set_averaging(struct iio_dev *indio_dev,
+				       const struct iio_chan_spec *chan,
+				       enum meson_saradc_avg_mode mode,
+				       enum meson_saradc_num_samples samples)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	u32 val;
+
+	val = samples << SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(chan->channel);
+	regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
+			   SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(chan->channel),
+			   val);
+
+	val = mode << SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(chan->channel);
+	regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
+			   SAR_ADC_AVG_CNTL_AVG_MODE_MASK(chan->channel), val);
+}
+
+static void meson_saradc_enable_channel(struct iio_dev *indio_dev,
+					const struct iio_chan_spec *chan)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	u32 regval;
+
+	/* the SAR ADC engine allows sampling multiple channels at the same
+	 * time. to keep it simple we're only working with one *internal*
+	 * channel, which starts counting at index 0 (which means: count = 1).
+	 */
+	regval = FIELD_PREP(SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
+	regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
+			   SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
+
+	/* map channel index 0 to the channel which we want to read */
+	regval = FIELD_PREP(SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), chan->channel);
+	regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
+			   SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), regval);
+
+	regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
+			    chan->channel);
+	regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
+			   SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
+			   regval);
+
+	regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
+			    chan->channel);
+	regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
+			   SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
+			   regval);
+
+	if (chan->channel == 6)
+		regmap_update_bits(priv->regmap, SAR_ADC_DELTA_10,
+				   SAR_ADC_DELTA_10_TEMP_SEL, 0);
+}
+
+static void meson_saradc_set_channel7_mux(struct iio_dev *indio_dev,
+					  enum meson_saradc_chan7_mux_sel sel)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	u32 regval;
+
+	regval = FIELD_PREP(SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
+	regmap_update_bits(priv->regmap, SAR_ADC_REG3,
+			   SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
+
+	usleep_range(10, 20);
+}
+
+static void meson_saradc_start_sample_engine(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG0,
+			   SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
+			   SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG0,
+			   SAR_ADC_REG0_SAMPLING_START,
+			   SAR_ADC_REG0_SAMPLING_START);
+}
+
+static void meson_saradc_stop_sample_engine(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG0,
+			   SAR_ADC_REG0_SAMPLING_STOP,
+			   SAR_ADC_REG0_SAMPLING_STOP);
+
+	/* wait until all modules are stopped */
+	meson_saradc_wait_busy_clear(indio_dev);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG0,
+			   SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
+}
+
+static void meson_saradc_lock(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int val;
+
+	mutex_lock(&indio_dev->mlock);
+
+	/* prevent BL30 from using the SAR ADC while we are using it */
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_KERNEL_BUSY,
+			   SAR_ADC_DELAY_KERNEL_BUSY);
+
+	/* wait until BL30 releases it's lock (so we can use the SAR ADC) */
+	do {
+		udelay(1);
+		regmap_read(priv->regmap, SAR_ADC_DELAY, &val);
+	} while (val & SAR_ADC_DELAY_BL30_BUSY);
+}
+
+static void meson_saradc_unlock(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+
+	/* allow BL30 to use the SAR ADC again */
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_KERNEL_BUSY, 0);
+
+	mutex_unlock(&indio_dev->mlock);
+}
+
+static int meson_saradc_get_sample(struct iio_dev *indio_dev,
+				   const struct iio_chan_spec *chan,
+				   enum meson_saradc_avg_mode avg_mode,
+				   enum meson_saradc_num_samples avg_samples,
+				   int *val)
+{
+	int ret, tmp;
+
+	meson_saradc_lock(indio_dev);
+
+	/* clear old values from the FIFO buffer, ignoring errors */
+	meson_saradc_read_raw_sample(indio_dev, chan, &tmp);
+
+	meson_saradc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
+
+	meson_saradc_enable_channel(indio_dev, chan);
+
+	meson_saradc_start_sample_engine(indio_dev);
+	ret = meson_saradc_read_raw_sample(indio_dev, chan, val);
+	meson_saradc_stop_sample_engine(indio_dev);
+
+	meson_saradc_unlock(indio_dev);
+
+	if (ret) {
+		dev_warn(&indio_dev->dev,
+			 "failed to read sample for channel %d: %d\n",
+			 chan->channel, ret);
+		return ret;
+	}
+
+	return IIO_VAL_INT;
+}
+
+static int meson_saradc_iio_info_read_raw(struct iio_dev *indio_dev,
+					  const struct iio_chan_spec *chan,
+					  int *val, int *val2, long mask)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		return meson_saradc_get_sample(indio_dev, chan, NO_AVERAGING,
+					       ONE_SAMPLE, val);
+		break;
+
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		return meson_saradc_get_sample(indio_dev, chan, MEAN_AVERAGING,
+					       EIGHT_SAMPLES, val);
+		break;
+
+	case IIO_CHAN_INFO_SCALE:
+		ret = regulator_get_voltage(priv->vref);
+		if (ret < 0) {
+			dev_err(&indio_dev->dev,
+				"failed to get vref voltage: %d\n", ret);
+			return ret;
+		}
+
+		*val = ret / 1000;
+		*val2 = priv->resolution;
+		return IIO_VAL_FRACTIONAL_LOG2;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int meson_saradc_clk_init(struct iio_dev *indio_dev, void __iomem *base)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	struct clk_init_data init;
+	char clk_name[32];
+	const char *clk_parents[1];
+
+	snprintf(clk_name, sizeof(clk_name), "%s#adc_div",
+		 of_node_full_name(indio_dev->dev.of_node));
+	init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
+	init.flags = 0;
+	init.ops = &clk_divider_ops;
+	clk_parents[0] = __clk_get_name(priv->clkin);
+	init.parent_names = clk_parents;
+	init.num_parents = 1;
+
+	priv->clk_div.reg = base + SAR_ADC_REG3;
+	priv->clk_div.shift = SAR_ADC_REG3_ADC_CLK_DIV_SHIFT;
+	priv->clk_div.width = SAR_ADC_REG3_ADC_CLK_DIV_WIDTH;
+	priv->clk_div.hw.init = &init;
+	priv->clk_div.flags = 0;
+
+	priv->adc_div_clk = devm_clk_register(&indio_dev->dev,
+					      &priv->clk_div.hw);
+	if (WARN_ON(IS_ERR(priv->adc_div_clk)))
+		return PTR_ERR(priv->adc_div_clk);
+
+	snprintf(clk_name, sizeof(clk_name), "%s#adc_en",
+		 of_node_full_name(indio_dev->dev.of_node));
+	init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
+	init.flags = CLK_SET_RATE_PARENT;
+	init.ops = &clk_gate_ops;
+	clk_parents[0] = __clk_get_name(priv->adc_div_clk);
+	init.parent_names = clk_parents;
+	init.num_parents = 1;
+
+	priv->clk_gate.reg = base + SAR_ADC_REG3;
+	priv->clk_gate.bit_idx = fls(SAR_ADC_REG3_CLK_EN);
+	priv->clk_gate.hw.init = &init;
+
+	priv->adc_clk = devm_clk_register(&indio_dev->dev, &priv->clk_gate.hw);
+	if (WARN_ON(IS_ERR(priv->adc_clk)))
+		return PTR_ERR(priv->adc_clk);
+
+	return 0;
+}
+
+static int meson_saradc_init(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int regval, ret;
+
+	/* make sure we start at CH7 input */
+	meson_saradc_set_channel7_mux(indio_dev, CHAN7_MUX_CH7_INPUT);
+
+	regmap_read(priv->regmap, SAR_ADC_REG3, &regval);
+	if (regval & SAR_ADC_REG3_BL30_INITIALIZED) {
+		dev_info(&indio_dev->dev, "already initialized by BL30\n");
+		return 0;
+	}
+
+	dev_info(&indio_dev->dev, "initializing SAR ADC\n");
+
+	meson_saradc_stop_sample_engine(indio_dev);
+
+	/* update the channel 6 MUX to select the temperature sensor */
+	regmap_update_bits(priv->regmap, SAR_ADC_REG0,
+			SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
+			SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
+
+	/* disable all channels by default */
+	regmap_write(priv->regmap, SAR_ADC_CHAN_LIST, 0x0);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG3,
+			   SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0);
+	regmap_update_bits(priv->regmap, SAR_ADC_REG3,
+			   SAR_ADC_REG3_CNTL_USE_SC_DLY,
+			   SAR_ADC_REG3_CNTL_USE_SC_DLY);
+
+	/* delay between two samples = (10+1) * 1uS */
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
+			   FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, 10));
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
+			   FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, 0));
+
+	/* delay between two samples = (10+1) * 1uS */
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
+			   FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, 10));
+	regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
+			   SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
+			   FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, 1));
+
+	ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
+	if (ret) {
+		dev_err(&indio_dev->dev,
+			"failed to set adc parent to clkin\n");
+		return ret;
+	}
+
+	ret = clk_set_rate(priv->adc_clk, 1200000);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set adc clock rate\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int meson_saradc_hw_enable(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+	int ret;
+
+	meson_saradc_lock(indio_dev);
+
+	ret = regulator_enable(priv->vref);
+	if (ret < 0) {
+		dev_err(&indio_dev->dev, "failed to enable vref regulator\n");
+		goto err_vref;
+	}
+
+	ret = clk_prepare_enable(priv->core_clk);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to enable core clk\n");
+		goto err_core_clk;
+	}
+
+	ret = clk_prepare_enable(priv->sana_clk);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to enable sana clk\n");
+		goto err_sana_clk;
+	}
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG11,
+			   SAR_ADC_REG11_BANDGAP_EN, SAR_ADC_REG11_BANDGAP_EN);
+	regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN,
+			   SAR_ADC_REG3_ADC_EN);
+
+	udelay(5);
+
+	ret = clk_prepare_enable(priv->adc_clk);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to enable adc_en clk\n");
+		goto err_adc_clk;
+	}
+
+	meson_saradc_unlock(indio_dev);
+
+	return 0;
+
+err_adc_clk:
+	clk_disable_unprepare(priv->sana_clk);
+err_sana_clk:
+	clk_disable_unprepare(priv->core_clk);
+err_core_clk:
+	regulator_disable(priv->vref);
+err_vref:
+	meson_saradc_unlock(indio_dev);
+	return ret;
+}
+
+static void meson_saradc_hw_disable(struct iio_dev *indio_dev)
+{
+	struct meson_saradc_priv *priv = iio_priv(indio_dev);
+
+	meson_saradc_lock(indio_dev);
+
+	clk_disable_unprepare(priv->adc_clk);
+
+	regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN, 0);
+	regmap_update_bits(priv->regmap, SAR_ADC_REG11,
+			   SAR_ADC_REG11_BANDGAP_EN, 0);
+
+	clk_disable_unprepare(priv->sana_clk);
+	clk_disable_unprepare(priv->core_clk);
+
+	regulator_disable(priv->vref);
+
+	meson_saradc_unlock(indio_dev);
+}
+
+static const struct iio_info meson_saradc_iio_info = {
+	.read_raw = meson_saradc_iio_info_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static const struct of_device_id meson_saradc_of_match[] = {
+	{
+		.compatible = "amlogic,meson-gxbb-saradc",
+		.data = (void *)10,
+	}, {
+		.compatible = "amlogic,meson-gxl-saradc",
+		.data = (void *)12,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, meson_saradc_of_match);
+
+static int meson_saradc_probe(struct platform_device *pdev)
+{
+	struct meson_saradc_priv *priv;
+	struct iio_dev *indio_dev;
+	struct resource *res;
+	void __iomem *base;
+	const struct of_device_id *match;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	priv = iio_priv(indio_dev);
+
+	match = of_match_device(meson_saradc_of_match, &pdev->dev);
+	priv->resolution = (unsigned long)match->data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+					     &meson_saradc_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	init_completion(&priv->completion);
+
+	priv->clkin = devm_clk_get(&pdev->dev, "clkin");
+	if (IS_ERR(priv->clkin)) {
+		dev_err(&pdev->dev, "failed to get clkin\n");
+		return PTR_ERR(priv->clkin);
+	}
+
+	priv->core_clk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(priv->core_clk)) {
+		dev_err(&pdev->dev, "failed to get core clk\n");
+		return PTR_ERR(priv->core_clk);
+	}
+
+	priv->sana_clk = devm_clk_get(&pdev->dev, "sana");
+	if (IS_ERR(priv->sana_clk)) {
+		if (PTR_ERR(priv->sana_clk) == -ENOENT) {
+			priv->sana_clk = NULL;
+		} else {
+			dev_err(&pdev->dev, "failed to get sana clk\n");
+			return PTR_ERR(priv->sana_clk);
+		}
+	}
+
+	priv->adc_clk = devm_clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(priv->adc_clk)) {
+		if (PTR_ERR(priv->adc_clk) == -ENOENT) {
+			priv->adc_clk = NULL;
+		} else {
+			dev_err(&pdev->dev, "failed to get adc clk\n");
+			return PTR_ERR(priv->adc_clk);
+		}
+	}
+
+	priv->adc_sel_clk = devm_clk_get(&pdev->dev, "adc_sel");
+	if (IS_ERR(priv->adc_sel_clk)) {
+		if (PTR_ERR(priv->adc_sel_clk) == -ENOENT) {
+			priv->adc_sel_clk = NULL;
+		} else {
+			dev_err(&pdev->dev, "failed to get adc_sel clk\n");
+			return PTR_ERR(priv->adc_sel_clk);
+		}
+	}
+
+	/* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */
+	if (!priv->adc_clk) {
+		ret = meson_saradc_clk_init(indio_dev, base);
+		if (ret)
+			return ret;
+	}
+
+	priv->vref = devm_regulator_get(&pdev->dev, "vref");
+	if (IS_ERR(priv->vref)) {
+		dev_err(&pdev->dev, "failed to get vref regulator\n");
+		return PTR_ERR(priv->vref);
+	}
+
+	ret = meson_saradc_init(indio_dev);
+	if (ret)
+		goto err;
+
+	ret = meson_saradc_hw_enable(indio_dev);
+	if (ret)
+		goto err;
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &meson_saradc_iio_info;
+
+	indio_dev->channels = meson_saradc_iio_channels;
+	indio_dev->num_channels = SAR_ADC_NUM_CHANNELS;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_hw;
+
+	return 0;
+
+err_hw:
+	meson_saradc_hw_disable(indio_dev);
+err:
+	return ret;
+}
+
+static int meson_saradc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+	meson_saradc_hw_disable(indio_dev);
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int meson_saradc_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+	meson_saradc_hw_disable(indio_dev);
+
+	return 0;
+}
+
+static int meson_saradc_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+	return meson_saradc_hw_enable(indio_dev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(meson_saradc_pm_ops,
+			 meson_saradc_suspend, meson_saradc_resume);
+
+static struct platform_driver meson_saradc_driver = {
+	.probe		= meson_saradc_probe,
+	.remove		= meson_saradc_remove,
+	.driver		= {
+		.name	= "meson-saradc",
+		.of_match_table = meson_saradc_of_match,
+		.pm = &meson_saradc_pm_ops,
+	},
+};
+
+module_platform_driver(meson_saradc_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver");
+MODULE_LICENSE("GPL v2");
-- 
2.11.0


^ permalink raw reply related

* [PATCH 4/4] ARM64: dts: meson: meson-gx: add the SAR ADC
From: Martin Blumenstingl @ 2017-01-11 17:43 UTC (permalink / raw)
  To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland, khilman,
	linux-iio, devicetree, linux-amlogic, linux-clk
  Cc: carlo, catalin.marinas, will.deacon, mturquette, sboyd,
	narmstrong, linux-arm-kernel, Martin Blumenstingl
In-Reply-To: <20170111174334.24343-1-martin.blumenstingl@googlemail.com>

Add the SAR ADC to meson-gxbb.dtsi and meson-gxl.dtsi. GXBB provides a
10-bit ADC while GXL (and GXM, which uses the same ADC as GXL) provides
a 12-bit ADC.
Some boards use resistor ladder buttons connected through one of the ADC
channels. On newer devices (GXL and GXM) some boards use pull-ups/downs
to change the resistance (and thus the ADC value) on of the ADC channels
to indicate the board revision.

Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
 arch/arm64/boot/dts/amlogic/meson-gx.dtsi   |  8 ++++++++
 arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 10 ++++++++++
 arch/arm64/boot/dts/amlogic/meson-gxl.dtsi  | 10 ++++++++++
 3 files changed, 28 insertions(+)

diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
index cddad8c795ec..ed3bf29eb76a 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
@@ -237,6 +237,14 @@
 				status = "disabled";
 			};
 
+			saradc: adc@8680 {
+				compatible = "amlogic,meson-saradc";
+				#io-channel-cells = <1>;
+				status = "disabled";
+				reg = <0x0 0x8680 0x0 0x34>;
+				interrupts = <GIC_SPI 9 IRQ_TYPE_EDGE_RISING>;
+			};
+
 			pwm_ef: pwm@86c0 {
 				compatible = "amlogic,meson-gx-pwm", "amlogic,meson-gxbb-pwm";
 				reg = <0x0 0x086c0 0x0 0x10>;
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
index 5d686334f692..114d7e1c9fc0 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
@@ -429,6 +429,16 @@
 	clocks = <&clkc CLKID_I2C>;
 };
 
+&saradc {
+	compatible = "amlogic,meson-gxbb-saradc", "amlogic,meson-saradc";
+	clocks = <&xtal>,
+		 <&clkc CLKID_SAR_ADC>,
+		 <&clkc CLKID_SANA>,
+		 <&clkc CLKID_SAR_ADC_CLK>,
+		 <&clkc CLKID_SAR_ADC_SEL>;
+	clock-names = "clkin", "core", "sana", "adc_clk", "adc_sel";
+};
+
 &sd_emmc_a {
 	clocks = <&clkc CLKID_SD_EMMC_A>,
 		 <&xtal>,
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
index bb2842f8a08f..6b63296b6c60 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
@@ -273,6 +273,16 @@
 	clocks = <&clkc CLKID_I2C>;
 };
 
+&saradc {
+	compatible = "amlogic,meson-gxl-saradc", "amlogic,meson-saradc";
+	clocks = <&xtal>,
+		 <&clkc CLKID_SAR_ADC>,
+		 <&clkc CLKID_SANA>,
+		 <&clkc CLKID_SAR_ADC_CLK>,
+		 <&clkc CLKID_SAR_ADC_SEL>;
+	clock-names = "clkin", "core", "sana", "adc_clk", "adc_sel";
+};
+
 &sd_emmc_a {
 	clocks = <&clkc CLKID_SD_EMMC_A>,
 		 <&xtal>,
-- 
2.11.0


^ permalink raw reply related

* [PATCH 0/3] ti,ads7950 device tree bindings
From: David Lechner @ 2017-01-11 17:52 UTC (permalink / raw)
  To: devicetree, linux-iio
  Cc: David Lechner, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	Mark Rutland, linux-kernel

This series adds device tree bindings for the TI ADS7950 family of A/DC chips.
The series includes the bindings documentation and some fixes to the iio driver
to make it work with the device tree bindings.

FYI, the ads7950 driver has not made it into mainline yet, so no worries about
breaking anyone with these changes.

David Lechner (3):
  DT/bindings: Add bindings for TI ADS7950 A/DC chips
  iio: adc: ti-ads7950: Drop "ti-" prefix from module name
  iio: adc: ti-ads7950: Change regulator matching string to "vref"

 .../devicetree/bindings/iio/adc/ti-ads7950.txt     | 23 ++++++++++++++++
 drivers/iio/adc/ti-ads7950.c                       | 32 +++++++++++-----------
 2 files changed, 39 insertions(+), 16 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti-ads7950.txt

-- 
2.7.4

^ permalink raw reply

* [PATCH 1/3] DT/bindings: Add bindings for TI ADS7950 A/DC chips
From: David Lechner @ 2017-01-11 17:52 UTC (permalink / raw)
  To: devicetree, linux-iio
  Cc: David Lechner, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	Mark Rutland, linux-kernel
In-Reply-To: <1484157171-15571-1-git-send-email-david@lechnology.com>

This adds device tree bindings for the TI ADS7950 family of A/DC chips.

Signed-off-by: David Lechner <david@lechnology.com>
---
 .../devicetree/bindings/iio/adc/ti-ads7950.txt     | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/ti-ads7950.txt

diff --git a/Documentation/devicetree/bindings/iio/adc/ti-ads7950.txt b/Documentation/devicetree/bindings/iio/adc/ti-ads7950.txt
new file mode 100644
index 0000000..e77a6f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti-ads7950.txt
@@ -0,0 +1,23 @@
+* Texas Instruments ADS7950 family of A/DC chips
+
+Required properties:
+ - compatible: Must be one of "ti,ads7950", "ti,ads7951", "ti,ads7952",
+   "ti,ads7953", "ti,ads7954", "ti,ads7955", "ti,ads7956", "ti,ads7957",
+   "ti,ads7958", "ti,ads7959", "ti,ads7960", or "ti,ads7961"
+ - reg: SPI chip select number for the device
+ - #io-channel-cells: Must be 1 as per ../iio-bindings.txt
+ - vref-supply: phandle to a regulator node that supplies the 2.5V or 5V
+   reference voltage
+
+Recommended properties:
+ - spi-max-frequency: Definition as per
+		Documentation/devicetree/bindings/spi/spi-bus.txt
+
+Example:
+adc@0 {
+	compatible = "ti,ads7957";
+	reg = <0>;
+	#io-channel-cells = <1>;
+	vref-supply = <&refin_supply>;
+	spi-max-frequency = <10000000>;
+};
-- 
2.7.4

^ permalink raw reply related

* [PATCH 2/3] iio: adc: ti-ads7950: Drop "ti-" prefix from module name
From: David Lechner @ 2017-01-11 17:52 UTC (permalink / raw)
  To: devicetree, linux-iio
  Cc: David Lechner, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	Mark Rutland, linux-kernel
In-Reply-To: <1484157171-15571-1-git-send-email-david@lechnology.com>

This drops the "ti-" prefix from the module name. It makes the module name
consistent with other iio ti-ads* drivers and it makes the driver work
with device tree (the spi subsystem drops the "ti," prefix when matching
compatible strings from device tree).

Tested working on LEGO MINDSTORMS EV3 with the following device tree node:

	adc@3 {
		compatible = "ti,ads7957";
		reg = <3>;
		#io-channel-cells = <1>;
		spi-max-frequency = <10000000>;
		vref-supply = <&adc_ref>;
	};

Signed-off-by: David Lechner <david@lechnology.com>
---
 drivers/iio/adc/ti-ads7950.c | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c
index 0330361..b587fa6 100644
--- a/drivers/iio/adc/ti-ads7950.c
+++ b/drivers/iio/adc/ti-ads7950.c
@@ -459,25 +459,25 @@ static int ti_ads7950_remove(struct spi_device *spi)
 }
 
 static const struct spi_device_id ti_ads7950_id[] = {
-	{"ti-ads7950", TI_ADS7950},
-	{"ti-ads7951", TI_ADS7951},
-	{"ti-ads7952", TI_ADS7952},
-	{"ti-ads7953", TI_ADS7953},
-	{"ti-ads7954", TI_ADS7954},
-	{"ti-ads7955", TI_ADS7955},
-	{"ti-ads7956", TI_ADS7956},
-	{"ti-ads7957", TI_ADS7957},
-	{"ti-ads7958", TI_ADS7958},
-	{"ti-ads7959", TI_ADS7959},
-	{"ti-ads7960", TI_ADS7960},
-	{"ti-ads7961", TI_ADS7961},
+	{ "ads7950", TI_ADS7950 },
+	{ "ads7951", TI_ADS7951 },
+	{ "ads7952", TI_ADS7952 },
+	{ "ads7953", TI_ADS7953 },
+	{ "ads7954", TI_ADS7954 },
+	{ "ads7955", TI_ADS7955 },
+	{ "ads7956", TI_ADS7956 },
+	{ "ads7957", TI_ADS7957 },
+	{ "ads7958", TI_ADS7958 },
+	{ "ads7959", TI_ADS7959 },
+	{ "ads7960", TI_ADS7960 },
+	{ "ads7961", TI_ADS7961 },
 	{ }
 };
 MODULE_DEVICE_TABLE(spi, ti_ads7950_id);
 
 static struct spi_driver ti_ads7950_driver = {
 	.driver = {
-		.name	= "ti-ads7950",
+		.name	= "ads7950",
 	},
 	.probe		= ti_ads7950_probe,
 	.remove		= ti_ads7950_remove,
-- 
2.7.4

^ permalink raw reply related

* [PATCH 3/3] iio: adc: ti-ads7950: Change regulator matching string to "vref"
From: David Lechner @ 2017-01-11 17:52 UTC (permalink / raw)
  To: devicetree, linux-iio
  Cc: David Lechner, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	Mark Rutland, linux-kernel
In-Reply-To: <1484157171-15571-1-git-send-email-david@lechnology.com>

This changes the reference voltage regulator matching string from "refin"
to "vref". This is to be consistent with other A/DC chips that also use
"vref-supply" in their device tree bindings.

Signed-off-by: David Lechner <david@lechnology.com>
---
 drivers/iio/adc/ti-ads7950.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c
index b587fa6..16a0663 100644
--- a/drivers/iio/adc/ti-ads7950.c
+++ b/drivers/iio/adc/ti-ads7950.c
@@ -411,15 +411,15 @@ static int ti_ads7950_probe(struct spi_device *spi)
 	spi_message_init_with_transfers(&st->scan_single_msg,
 					st->scan_single_xfer, 3);
 
-	st->reg = devm_regulator_get(&spi->dev, "refin");
+	st->reg = devm_regulator_get(&spi->dev, "vref");
 	if (IS_ERR(st->reg)) {
-		dev_err(&spi->dev, "Failed get get regulator \"refin\"\n");
+		dev_err(&spi->dev, "Failed get get regulator \"vref\"\n");
 		return PTR_ERR(st->reg);
 	}
 
 	ret = regulator_enable(st->reg);
 	if (ret) {
-		dev_err(&spi->dev, "Failed to enable regulator \"refin\"\n");
+		dev_err(&spi->dev, "Failed to enable regulator \"vref\"\n");
 		return ret;
 	}
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH linux v2 0/6] drivers: hwmon: Add On-Chip Controller driver
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

This patchset adds a hwmon driver to support the OCC (On-Chip Controller)
on the IBM POWER8 and POWER9 processors, from a BMC (Baseboard Management
Controller). The OCC is an embedded processor that provides real time
power and thermal monitoring.

The driver provides an interface on a BMC to poll OCC sensor data, set
user power caps, and perform some basic OCC error handling. It interfaces
with userspace through hwmon.

The driver is currently functional only for the OCC on POWER8 chips.
Communicating with the POWER9 OCC requries FSI support.

Edward A. James (6):
  hwmon: Add core On-Chip Controller support for POWER CPUs
  hwmon: occ: Add sysfs interface
  hwmon: occ: Add I2C transport implementation for SCOM operations
  hwmon: occ: Add callbacks for parsing P8 OCC datastructures
  hwmon: occ: Add hwmon implementation for the P8 OCC
  hwmon: occ: Add callbacks for parsing P9 OCC datastructures

 Documentation/devicetree/bindings/hwmon/occ.txt |  13 +
 Documentation/hwmon/occ                         | 114 +++++
 drivers/hwmon/Kconfig                           |   2 +
 drivers/hwmon/Makefile                          |   1 +
 drivers/hwmon/occ/Kconfig                       |  29 ++
 drivers/hwmon/occ/Makefile                      |   2 +
 drivers/hwmon/occ/occ.c                         | 533 ++++++++++++++++++++++++
 drivers/hwmon/occ/occ.h                         |  83 ++++
 drivers/hwmon/occ/occ_p8.c                      | 254 +++++++++++
 drivers/hwmon/occ/occ_p8.h                      |  31 ++
 drivers/hwmon/occ/occ_p9.c                      | 314 ++++++++++++++
 drivers/hwmon/occ/occ_p9.h                      |  31 ++
 drivers/hwmon/occ/occ_scom_i2c.c                |  73 ++++
 drivers/hwmon/occ/occ_scom_i2c.h                |  26 ++
 drivers/hwmon/occ/occ_sysfs.c                   | 274 ++++++++++++
 drivers/hwmon/occ/occ_sysfs.h                   |  44 ++
 drivers/hwmon/occ/p8_occ_i2c.c                  | 123 ++++++
 drivers/hwmon/occ/scom.h                        |  47 +++
 18 files changed, 1994 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/occ.txt
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/occ.c
 create mode 100644 drivers/hwmon/occ/occ.h
 create mode 100644 drivers/hwmon/occ/occ_p8.c
 create mode 100644 drivers/hwmon/occ/occ_p8.h
 create mode 100644 drivers/hwmon/occ/occ_p9.c
 create mode 100644 drivers/hwmon/occ/occ_p9.h
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.c
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.h
 create mode 100644 drivers/hwmon/occ/occ_sysfs.c
 create mode 100644 drivers/hwmon/occ/occ_sysfs.h
 create mode 100644 drivers/hwmon/occ/p8_occ_i2c.c
 create mode 100644 drivers/hwmon/occ/scom.h

-- 
1.9.1

^ permalink raw reply

* [PATCH linux v2 1/6] hwmon: Add core On-Chip Controller support for POWER CPUs
From: eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux-0h96xk9xTtrk1uMJSBkQmQ
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, jdelvare-IBi9RG/b67k,
	corbet-T1hC0tSOHrs, linux-doc-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, mark.rutland-5wv7dgnIgG8,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, wsa-z923LK4zBo2bacvFa/9K2g,
	andrew-zrmu5oMJ5Fs, joel-U3u1mxZcP9KHXe+LvDLADg,
	benh-XVmvHMARGAS8U2dJNN8I7kB+6BGkLq7r, Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

Add core support for polling the OCC for it's sensor data and parsing that
data into sensor-specific information.

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Andrew Jeffery <andrew-zrmu5oMJ5Fs@public.gmane.org>
---
 Documentation/hwmon/occ    |  40 ++++
 drivers/hwmon/Kconfig      |   2 +
 drivers/hwmon/Makefile     |   1 +
 drivers/hwmon/occ/Kconfig  |  15 ++
 drivers/hwmon/occ/Makefile |   1 +
 drivers/hwmon/occ/occ.c    | 533 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ.h    |  83 +++++++
 drivers/hwmon/occ/scom.h   |  47 ++++
 8 files changed, 722 insertions(+)
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/occ.c
 create mode 100644 drivers/hwmon/occ/occ.h
 create mode 100644 drivers/hwmon/occ/scom.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
new file mode 100644
index 0000000..79d1642
--- /dev/null
+++ b/Documentation/hwmon/occ
@@ -0,0 +1,40 @@
+Kernel driver occ
+=================
+
+Supported chips:
+ * ASPEED AST2400
+ * ASPEED AST2500
+
+Please note that the chip must be connected to a POWER8 or POWER9 processor
+(see the BMC - Host Communications section).
+
+Author: Eddie James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
+
+Description
+-----------
+
+This driver implements support for the OCC (On-Chip Controller) on the IBM
+POWER8 and POWER9 processors, from a BMC (Baseboard Management Controller). The
+OCC is an embedded processor that provides real time power and thermal
+monitoring.
+
+This driver provides an interface on a BMC to poll OCC sensor data, set user
+power caps, and perform some basic OCC error handling.
+
+Currently, all versions of the OCC support four types of sensor data: power,
+temperature, frequency, and "caps," which indicate limits and thresholds used
+internally on the OCC.
+
+BMC - Host Communications
+-------------------------
+
+For the POWER8 application, the BMC can communicate with the P8 over I2C bus.
+However, to access the OCC register space, any data transfer must use a SCOM
+operation. SCOM is a procedure to initiate a data transfer, typically of 8
+bytes. SCOMs consist of writing a 32-bit command register and then
+reading/writing two 32-bit data registers. This driver implements these
+SCOM operations over I2C bus in order to communicate with the OCC.
+
+For the POWER9 application, the BMC can communicate with the P9 over FSI bus
+and SBE engine. Once again, SCOM operations are required. This driver will
+implement SCOM ops over FSI/SBE. This will require the FSI driver.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 190d270..e80ca81 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1240,6 +1240,8 @@ config SENSORS_NSA320
 	  This driver can also be built as a module. If so, the module
 	  will be called nsa320-hwmon.
 
+source drivers/hwmon/occ/Kconfig
+
 config SENSORS_PCF8591
 	tristate "Philips PCF8591 ADC/DAC"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d2cb7e8..c7ec5d4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -169,6 +169,7 @@ obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
 obj-$(CONFIG_SENSORS_XGENE)	+= xgene-hwmon.o
 
+obj-$(CONFIG_SENSORS_PPC_OCC)	+= occ/
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
 ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig
new file mode 100644
index 0000000..cdb64a7
--- /dev/null
+++ b/drivers/hwmon/occ/Kconfig
@@ -0,0 +1,15 @@
+#
+# On Chip Controller configuration
+#
+
+menuconfig SENSORS_PPC_OCC
+	bool "PPC On-Chip Controller"
+	help
+	  If you say yes here you get support to monitor Power CPU
+	  sensors via the On-Chip Controller (OCC).
+
+	  Generally this is used by management controllers such as a BMC
+	  on an OpenPower system.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called occ.
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
new file mode 100644
index 0000000..93cb52f
--- /dev/null
+++ b/drivers/hwmon/occ/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
diff --git a/drivers/hwmon/occ/occ.c b/drivers/hwmon/occ/occ.c
new file mode 100644
index 0000000..31e6164
--- /dev/null
+++ b/drivers/hwmon/occ/occ.c
@@ -0,0 +1,533 @@
+/*
+ * occ.c - OCC hwmon driver
+ *
+ * This file contains the methods and data structures for the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <asm/unaligned.h>
+
+#include "occ.h"
+
+#define OCC_DATA_MAX		4096
+#define OCC_BMC_TIMEOUT_MS	20000
+
+/* To generate attn to OCC */
+#define ATTN_DATA		0x0006B035
+
+/* For BMC to read/write SRAM */
+#define OCB_ADDRESS		0x0006B070
+#define OCB_DATA		0x0006B075
+#define OCB_STATUS_CONTROL_AND	0x0006B072
+#define OCB_STATUS_CONTROL_OR	0x0006B073
+
+/* To init OCB */
+#define OCB_AND_INIT0		0xFBFFFFFF
+#define OCB_AND_INIT1		0xFFFFFFFF
+#define OCB_OR_INIT0		0x08000000
+#define OCB_OR_INIT1		0x00000000
+
+/* To generate attention on OCC */
+#define ATTN0			0x01010000
+#define ATTN1			0x00000000
+
+/* OCC return status */
+#define RESP_RETURN_CMD_IN_PRG	0xFF
+#define RESP_RETURN_SUCCESS	0
+#define RESP_RETURN_CMD_INVAL	0x11
+#define RESP_RETURN_CMD_LEN	0x12
+#define RESP_RETURN_DATA_INVAL	0x13
+#define RESP_RETURN_CHKSUM	0x14
+#define RESP_RETURN_OCC_ERR	0x15
+#define RESP_RETURN_STATE	0x16
+
+/* time interval to retry on "command in progress" return status */
+#define CMD_IN_PRG_INT_MS	100
+#define CMD_IN_PRG_RETRIES	(OCC_BMC_TIMEOUT_MS / CMD_IN_PRG_INT_MS)
+
+/* OCC command definitions */
+#define OCC_POLL		0
+#define OCC_SET_USER_POWR_CAP	0x22
+
+/* OCC poll command data */
+#define OCC_POLL_STAT_SENSOR	0x10
+
+/* OCC response data offsets */
+#define RESP_RETURN_STATUS	2
+#define RESP_DATA_LENGTH	3
+#define RESP_HEADER_OFFSET	5
+#define SENSOR_STR_OFFSET	37
+#define SENSOR_BLOCK_NUM_OFFSET	43
+#define SENSOR_BLOCK_OFFSET	45
+
+/* occ_poll_header
+ * structure to match the raw occ poll response data
+ */
+struct occ_poll_header {
+	u8 status;
+	u8 ext_status;
+	u8 occs_present;
+	u8 config;
+	u8 occ_state;
+	u8 mode;
+	u8 ips_status;
+	u8 error_log_id;
+	u32 error_log_addr_start;
+	u16 error_log_length;
+	u8 reserved2;
+	u8 reserved3;
+	u8 occ_code_level[16];
+	u8 sensor_eye_catcher[6];
+	u8 sensor_block_num;
+	u8 sensor_data_version;
+} __attribute__((packed, aligned(4)));
+
+struct occ_response {
+	struct occ_poll_header header;
+	struct occ_blocks data;
+};
+
+struct occ {
+	struct device *dev;
+	void *bus;
+	struct occ_bus_ops bus_ops;
+	struct occ_ops ops;
+	struct occ_config config;
+	unsigned long update_interval;
+	unsigned long last_updated;
+	struct mutex update_lock;
+	struct occ_response response;
+	bool valid;
+};
+
+static void deinit_occ_resp_buf(struct occ_response *resp)
+{
+	int i;
+
+	if (!resp)
+		return;
+
+	if (!resp->data.blocks)
+		return;
+
+	for (i = 0; i < resp->header.sensor_block_num; ++i)
+		kfree(resp->data.blocks[i].sensors);
+
+	kfree(resp->data.blocks);
+
+	memset(resp, 0, sizeof(struct occ_response));
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->data.sensor_block_id[i] = -1;
+}
+
+static void *occ_get_sensor_by_type(struct occ_response *resp,
+				    enum sensor_type t)
+{
+	if (!resp->data.blocks)
+		return NULL;
+
+	if (resp->data.sensor_block_id[t] == -1)
+		return NULL;
+
+	return resp->data.blocks[resp->data.sensor_block_id[t]].sensors;
+}
+
+static int occ_check_sensor(struct occ *driver, u8 sensor_length,
+			    u8 sensor_num, enum sensor_type t, int block)
+{
+	void *sensor;
+	int type_block_id;
+	struct occ_response *resp = &driver->response;
+
+	sensor = occ_get_sensor_by_type(resp, t);
+
+	/* empty sensor block, release older sensor data */
+	if (sensor_num == 0 || sensor_length == 0) {
+		kfree(sensor);
+		dev_err(driver->dev, "no sensor blocks available\n");
+		return -ENODATA;
+	}
+
+	type_block_id = resp->data.sensor_block_id[t];
+	if (!sensor || sensor_num !=
+	    resp->data.blocks[type_block_id].header.sensor_num) {
+		kfree(sensor);
+		resp->data.blocks[block].sensors =
+			driver->ops.alloc_sensor(t, sensor_num);
+		if (!resp->data.blocks[block].sensors)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int parse_occ_response(struct occ *driver, u8 *data,
+			      struct occ_response *resp)
+{
+	int b;
+	int s;
+	int rc;
+	int offset = SENSOR_BLOCK_OFFSET;
+	int sensor_type;
+	u8 sensor_block_num;
+	char sensor_type_string[5] = { 0 };
+	struct sensor_data_block_header *block;
+	struct device *dev = driver->dev;
+
+	/* check if the data is valid */
+	if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+		dev_err(dev, "no SENSOR string in response\n");
+		rc = -ENODATA;
+		goto err;
+	}
+
+	sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET];
+	if (sensor_block_num == 0) {
+		dev_err(dev, "no sensor blocks available\n");
+		rc = -ENODATA;
+		goto err;
+	}
+
+	/* if number of sensor block has changed, re-malloc */
+	if (sensor_block_num != resp->header.sensor_block_num) {
+		deinit_occ_resp_buf(resp);
+		resp->data.blocks = kcalloc(sensor_block_num,
+					    sizeof(struct sensor_data_block),
+					    GFP_KERNEL);
+		if (!resp->data.blocks)
+			return -ENOMEM;
+	}
+
+	memcpy(&resp->header, &data[RESP_HEADER_OFFSET],
+	       sizeof(struct occ_poll_header));
+	resp->header.error_log_addr_start =
+		be32_to_cpu(resp->header.error_log_addr_start);
+	resp->header.error_log_length =
+		be16_to_cpu(resp->header.error_log_length);
+
+	dev_dbg(dev, "Reading %d sensor blocks\n",
+		resp->header.sensor_block_num);
+	for (b = 0; b < sensor_block_num; b++) {
+		block = (struct sensor_data_block_header *)&data[offset];
+		/* copy to a null terminated string */
+		strncpy(sensor_type_string, block->sensor_type, 4);
+		offset += 8;
+
+		dev_dbg(dev, "sensor block[%d]: type: %s, sensor_num: %d\n", b,
+			sensor_type_string, block->sensor_num);
+
+		if (strncmp(block->sensor_type, "FREQ", 4) == 0)
+			sensor_type = FREQ;
+		else if (strncmp(block->sensor_type, "TEMP", 4) == 0)
+			sensor_type = TEMP;
+		else if (strncmp(block->sensor_type, "POWR", 4) == 0)
+			sensor_type = POWER;
+		else if (strncmp(block->sensor_type, "CAPS", 4) == 0)
+			sensor_type = CAPS;
+		else {
+			dev_err(dev, "sensor type not supported %s\n",
+				sensor_type_string);
+			continue;
+		}
+
+		rc = occ_check_sensor(driver, block->sensor_length,
+				      block->sensor_num, sensor_type, b);
+		if (rc == -ENOMEM)
+			goto err;
+		else if (rc)
+			continue;
+
+		resp->data.sensor_block_id[sensor_type] = b;
+		for (s = 0; s < block->sensor_num; s++) {
+			driver->ops.parse_sensor(data,
+						 resp->data.blocks[b].sensors,
+						 sensor_type, offset, s);
+			offset += block->sensor_length;
+		}
+
+		/* copy block data over to response pointer */
+		resp->data.blocks[b].header = *block;
+	}
+
+	return 0;
+err:
+	deinit_occ_resp_buf(resp);
+	return rc;
+}
+
+static u8 occ_send_cmd(struct occ *driver, u8 seq, u8 type, u16 length,
+		       const u8 *data, u8 *resp)
+{
+	u32 cmd1, cmd2;
+	u16 checksum = 0;
+	u16 length_le = cpu_to_le16(length);
+	bool retry = 0;
+	int i, rc, tries = 0;
+
+	cmd1 = (seq << 24) | (type << 16) | length_le;
+	memcpy(&cmd2, data, length);
+	cmd2 <<= ((4 - length) * 8);
+
+	/* checksum: sum of every bytes of cmd1, cmd2 */
+	for (i = 0; i < 4; i++) {
+		checksum += (cmd1 >> (i * 8)) & 0xFF;
+		checksum += (cmd2 >> (i * 8)) & 0xFF;
+	}
+
+	cmd2 |= checksum << ((2 - length) * 8);
+
+	/* Init OCB */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_OR,
+				     OCB_OR_INIT0, OCB_OR_INIT1);
+	if (rc)
+		goto err;
+
+	rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_AND,
+				     OCB_AND_INIT0, OCB_AND_INIT1);
+	if (rc)
+		goto err;
+
+	/* Send command, 2nd half of the 64-bit addr is unused (write 0) */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+				     driver->config.command_addr, 0);
+	if (rc)
+		goto err;
+
+	rc = driver->bus_ops.putscom(driver->bus, OCB_DATA, cmd1, cmd2);
+	if (rc)
+		goto err;
+
+	/* Trigger attention */
+	rc = driver->bus_ops.putscom(driver->bus, ATTN_DATA, ATTN0, ATTN1);
+	if (rc)
+		goto err;
+
+	/* Get response data */
+	rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+				     driver->config.response_addr, 0);
+	if (rc)
+		goto err;
+
+	do {
+		if (retry) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(msecs_to_jiffies(CMD_IN_PRG_INT_MS));
+		}
+
+		rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+					     (u64 *)resp);
+		if (rc)
+			goto err;
+
+		/* retry if we get "command in progress" return status */
+		retry = (resp[RESP_RETURN_STATUS] == RESP_RETURN_CMD_IN_PRG) &&
+			(tries++ < CMD_IN_PRG_RETRIES);
+	} while (retry);
+
+	switch (resp[RESP_RETURN_STATUS]) {
+	case RESP_RETURN_CMD_IN_PRG:
+		rc = -EALREADY;
+		break;
+	case RESP_RETURN_SUCCESS:
+		rc = 0;
+		break;
+	case RESP_RETURN_CMD_INVAL:
+	case RESP_RETURN_CMD_LEN:
+	case RESP_RETURN_DATA_INVAL:
+	case RESP_RETURN_CHKSUM:
+		rc = -EINVAL;
+		break;
+	case RESP_RETURN_OCC_ERR:
+		rc = -EREMOTE;
+		break;
+	default:
+		rc = -EFAULT;
+	}
+
+	return rc;
+
+err:
+	dev_err(driver->dev, "scom op failed rc:%d\n", rc);
+	return rc;
+}
+
+static int occ_get_all(struct occ *driver)
+{
+	int i = 0, rc;
+	u8 *occ_data;
+	u16 num_bytes;
+	const u8 poll_cmd_data = OCC_POLL_STAT_SENSOR;
+	struct device *dev = driver->dev;
+	struct occ_response *resp = &driver->response;
+
+	occ_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
+	if (!occ_data)
+		return -ENOMEM;
+
+	rc = occ_send_cmd(driver, 0, OCC_POLL, 1, &poll_cmd_data, occ_data);
+	if (rc) {
+		dev_err(dev, "OCC poll failed: %d\n", rc);
+		goto out;
+	}
+
+	num_bytes = get_unaligned((u16 *)&occ_data[RESP_DATA_LENGTH]);
+	num_bytes = be16_to_cpu(num_bytes);
+	dev_dbg(dev, "OCC data length: %d\n", num_bytes);
+
+	if (num_bytes > OCC_DATA_MAX) {
+		dev_err(dev, "OCC data length must be < 4KB\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (num_bytes <= 0) {
+		dev_err(dev, "OCC data length is zero\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	/* read remaining data */
+	for (i = 8; i < num_bytes + 8; i += 8) {
+		rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+					     (u64 *)&occ_data[i]);
+		if (rc) {
+			dev_err(dev, "scom op failed rc:%d\n", rc);
+			goto out;
+		}
+	}
+
+	/* don't need more sanity checks; buffer is alloc'd for max response
+	 * size so we just check for valid data in parse_occ_response
+	 */
+	rc = parse_occ_response(driver, occ_data, resp);
+
+out:
+	devm_kfree(dev, occ_data);
+	return rc;
+}
+
+int occ_update_device(struct occ *driver)
+{
+	int rc = 0;
+
+	mutex_lock(&driver->update_lock);
+
+	if (time_after(jiffies, driver->last_updated + driver->update_interval)
+	    || !driver->valid) {
+		driver->valid = 1;
+
+		rc = occ_get_all(driver);
+		if (rc)
+			driver->valid = 0;
+
+		driver->last_updated = jiffies;
+	}
+
+	mutex_unlock(&driver->update_lock);
+
+	return rc;
+}
+EXPORT_SYMBOL(occ_update_device);
+
+void *occ_get_sensor(struct occ *driver, int sensor_type)
+{
+	int rc;
+
+	/* occ_update_device locks the update lock */
+	rc = occ_update_device(driver);
+	if (rc) {
+		dev_err(driver->dev, "cannot get occ sensor data: %d\n",
+			rc);
+		return NULL;
+	}
+
+	return occ_get_sensor_by_type(&driver->response, sensor_type);
+}
+EXPORT_SYMBOL(occ_get_sensor);
+
+int occ_get_sensor_value(struct occ *occ, int sensor_type, int sensor_num,
+			 u32 hwmon, long *val)
+{
+	return occ->ops.get_sensor(occ, sensor_type, sensor_num, hwmon, val);
+}
+EXPORT_SYMBOL(occ_get_sensor_value);
+
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks)
+{
+	*blocks = &occ->response.data;
+}
+EXPORT_SYMBOL(occ_get_response_blocks);
+
+void occ_set_update_interval(struct occ *occ, unsigned long interval)
+{
+	occ->update_interval = msecs_to_jiffies(interval);
+}
+EXPORT_SYMBOL(occ_set_update_interval);
+
+int occ_set_user_powercap(struct occ *occ, u16 cap)
+{
+	u8 resp[8];
+
+	cap = cpu_to_be16(cap);
+
+	return occ_send_cmd(occ, 0, OCC_SET_USER_POWR_CAP, 2, (const u8 *)&cap,
+			    resp);
+}
+EXPORT_SYMBOL(occ_set_user_powercap);
+
+struct occ *occ_start(struct device *dev, void *bus,
+		      struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+		      const struct occ_config *config)
+{
+	struct occ *driver = devm_kzalloc(dev, sizeof(struct occ), GFP_KERNEL);
+
+	if (!driver)
+		return ERR_PTR(-ENOMEM);
+
+	driver->dev = dev;
+	driver->bus = bus;
+	driver->bus_ops = *bus_ops;
+	driver->ops = *ops;
+	driver->config = *config;
+
+	driver->update_interval = HZ;
+	mutex_init(&driver->update_lock);
+
+	return driver;
+}
+EXPORT_SYMBOL(occ_start);
+
+int occ_stop(struct occ *occ)
+{
+	devm_kfree(occ->dev, occ);
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_stop);
+
+MODULE_AUTHOR("Eddie James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("OCC hwmon core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ.h b/drivers/hwmon/occ/occ.h
new file mode 100644
index 0000000..be869bc
--- /dev/null
+++ b/drivers/hwmon/occ/occ.h
@@ -0,0 +1,83 @@
+/*
+ * occ.h - hwmon OCC driver
+ *
+ * This file contains data structures and function prototypes for common access
+ * between different bus protocols and host systems.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_H__
+#define __OCC_H__
+
+#include "scom.h"
+
+struct device;
+struct occ;
+
+/* sensor_data_block_header
+ * structure to match the raw occ sensor block header
+ */
+struct sensor_data_block_header {
+	u8 sensor_type[4];
+	u8 reserved0;
+	u8 sensor_format;
+	u8 sensor_length;
+	u8 sensor_num;
+} __attribute__((packed, aligned(4)));
+
+struct sensor_data_block {
+	struct sensor_data_block_header header;
+	void *sensors;
+};
+
+enum sensor_type {
+	FREQ = 0,
+	TEMP,
+	POWER,
+	CAPS,
+	MAX_OCC_SENSOR_TYPE
+};
+
+struct occ_ops {
+	void (*parse_sensor)(u8 *data, void *sensor, int sensor_type, int off,
+			     int snum);
+	void *(*alloc_sensor)(int sensor_type, int num_sensors);
+	int (*get_sensor)(struct occ *driver, int sensor_type, int sensor_num,
+			  u32 hwmon, long *val);
+};
+
+struct occ_config {
+	u32 command_addr;
+	u32 response_addr;
+};
+
+struct occ_blocks {
+	int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+	struct sensor_data_block *blocks;
+};
+
+struct occ *occ_start(struct device *dev, void *bus,
+		      struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+		      const struct occ_config *config);
+int occ_stop(struct occ *occ);
+
+void *occ_get_sensor(struct occ *occ, int sensor_type);
+int occ_get_sensor_value(struct occ *occ, int sensor_type, int sensor_num,
+			 u32 hwmon, long *val);
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks);
+int occ_update_device(struct occ *driver);
+void occ_set_update_interval(struct occ *occ, unsigned long interval);
+int occ_set_user_powercap(struct occ *occ, u16 cap);
+
+#endif /* __OCC_H__ */
diff --git a/drivers/hwmon/occ/scom.h b/drivers/hwmon/occ/scom.h
new file mode 100644
index 0000000..c1da645
--- /dev/null
+++ b/drivers/hwmon/occ/scom.h
@@ -0,0 +1,47 @@
+/*
+ * scom.h - hwmon OCC driver
+ *
+ * This file contains data structures for scom operations to the OCC
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SCOM_H__
+#define __SCOM_H__
+
+/*
+ * occ_bus_ops - represent the low-level transfer methods to communicate with
+ * the OCC.
+ *
+ * getscom - OCC scom read
+ * @bus: handle to slave device
+ * @address: address
+ * @data: where to store data read from slave; buffer size must be at least
+ * eight bytes.
+ *
+ * Returns 0 on success or a negative errno on error
+ *
+ * putscom - OCC scom write
+ * @bus: handle to slave device
+ * @address: address
+ * @data0: first data byte to write
+ * @data1: second data byte to write
+ *
+ * Returns 0 on success or a negative errno on error
+ */
+struct occ_bus_ops {
+	int (*getscom)(void *bus, u32 address, u64 *data);
+	int (*putscom)(void *bus, u32 address, u32 data0, u32 data1);
+};
+
+#endif /* __SCOM_H__ */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [PATCH linux v2 2/6] hwmon: occ: Add sysfs interface
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add a generic mechanism to expose the sensors provided by the OCC in
sysfs.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ       |  62 ++++++++++
 drivers/hwmon/occ/Makefile    |   2 +-
 drivers/hwmon/occ/occ_sysfs.c | 274 ++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_sysfs.h |  44 +++++++
 4 files changed, 381 insertions(+), 1 deletion(-)
 create mode 100644 drivers/hwmon/occ/occ_sysfs.c
 create mode 100644 drivers/hwmon/occ/occ_sysfs.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index 79d1642..d0bdf06 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -25,6 +25,68 @@ Currently, all versions of the OCC support four types of sensor data: power,
 temperature, frequency, and "caps," which indicate limits and thresholds used
 internally on the OCC.
 
+sysfs Entries
+-------------
+
+The OCC driver uses the hwmon sysfs framework to provide data to userspace.
+
+The driver exports a number of sysfs files for each type of sensor. The
+sensor-specific files vary depending on the processor type, though many of the
+attributes are common for both the POWER8 and POWER9.
+
+The hwmon interface cannot define every type of sensor that may be used.
+Therefore, the frequency sensor on the OCC uses the "input" type sensor defined
+by the hwmon interface, rather than defining a new type of custom sensor.
+
+Below are detailed the names and meaning of each sensor file for both types of
+processors. All sensors are read-only unless otherwise specified. <x> indicates
+the hwmon index. sensor id indicates the unique internal OCC identifer. Please
+see the POWER OCC specification for details on all these sensor values.
+
+frequency:
+	all processors:
+		in<x>_input - frequency value
+		in<x>_label - sensor id
+temperature:
+	POWER8:
+		temp<x>_input - temperature value
+		temp<x>_label - sensor id
+	POWER9 (in addition to above):
+		temp<x>_type - FRU type
+
+power:
+	POWER8:
+		power<x>_input - power value
+		power<x>_label - sensor id
+		power<x>_average - accumulator
+		power<x>_average_interval - update tag (number of samples in
+			accumulator)
+	POWER9:
+		power<x>_input - power value
+		power<x>_label - sensor id
+		power<x>_average_min - accumulator[0]
+		power<x>_average_max - accumulator[1] (64 bits total)
+		power<x>_average_interval - update tag
+		power<x>_reset_history - (function_id | (apss_channel << 8)
+
+caps:
+	POWER8:
+		power<x>_cap - current powercap
+		power<x>_cap_max - max powercap
+		power<x>_cap_min - min powercap
+		power<x>_max - normal powercap
+		power<x>_alarm - user powercap, r/w
+	POWER9:
+		power<x>_cap_alarm - user powercap source
+
+The driver also provides two sysfs entries through hwmon to better
+control the driver and monitor the master OCC. Though there may be multiple
+OCCs present on the system, these two files are only present for the "master"
+OCC.
+	name - read the name of the driver
+	update_interval - read or write the minimum interval for polling the
+		OCC.
+
 BMC - Host Communications
 -------------------------
 
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
index 93cb52f..a6881f9 100644
--- a/drivers/hwmon/occ/Makefile
+++ b/drivers/hwmon/occ/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
+obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
diff --git a/drivers/hwmon/occ/occ_sysfs.c b/drivers/hwmon/occ/occ_sysfs.c
new file mode 100644
index 0000000..e846b0c
--- /dev/null
+++ b/drivers/hwmon/occ/occ_sysfs.c
@@ -0,0 +1,274 @@
+/*
+ * occ_sysfs.c - OCC sysfs interface
+ *
+ * This file contains the methods and data structures for implementing the OCC
+ * hwmon sysfs entries.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include "occ.h"
+#include "occ_sysfs.h"
+
+#define RESP_RETURN_CMD_INVAL	0x13
+
+static int occ_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			  u32 attr, int channel, long *val)
+{
+	int rc = 0;
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+	struct occ *occ = driver->occ;
+
+	switch (type) {
+	case hwmon_in:
+		rc = occ_get_sensor_value(occ, FREQ, channel, attr, val);
+		break;
+	case hwmon_temp:
+		rc = occ_get_sensor_value(occ, TEMP, channel, attr, val);
+		break;
+	case hwmon_power:
+		rc = occ_get_sensor_value(occ, POWER, channel, attr, val);
+		break;
+	default:
+		rc = -EOPNOTSUPP;
+	}
+
+	return rc;
+}
+
+static int occ_hwmon_read_string(struct device *dev,
+				 enum hwmon_sensor_types type, u32 attr,
+				 int channel, char **str)
+{
+	int rc;
+	unsigned long val = 0;
+
+	if (!((type == hwmon_in && attr == hwmon_in_label) ||
+	    (type == hwmon_temp && attr == hwmon_temp_label) ||
+	    (type == hwmon_power && attr == hwmon_power_label)))
+		return -EOPNOTSUPP;
+
+	rc = occ_hwmon_read(dev, type, attr, channel, &val);
+	if (rc < 0)
+		return rc;
+
+	rc = snprintf(*str, PAGE_SIZE - 1, "%ld", val);
+	if (rc > 0)
+		rc = 0;
+
+	return rc;
+}
+
+static int occ_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+			   u32 attr, int channel, long val)
+{
+	int rc = 0;
+	struct occ_sysfs *driver = dev_get_drvdata(dev);
+
+	if (type == hwmon_chip && attr == hwmon_chip_update_interval) {
+		occ_set_update_interval(driver->occ, val);
+		return 0;
+	} else if (type == hwmon_power && attr == hwmon_power_alarm) {
+		rc = occ_set_user_powercap(driver->occ, val);
+		if (rc) {
+			if (rc == RESP_RETURN_CMD_INVAL) {
+				dev_err(dev,
+					"set invalid powercap value: %ld\n",
+					val);
+				return -EINVAL;
+			}
+
+			dev_err(dev, "set user powercap failed: 0x:%x\n", rc);
+			return rc;
+		}
+
+		driver->user_powercap = val;
+
+		return rc;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static umode_t occ_is_visible(const void *data, enum hwmon_sensor_types type,
+			      u32 attr, int channel)
+{
+	const struct occ_sysfs *driver = data;
+
+	switch (type) {
+	case hwmon_chip:
+		if (attr == hwmon_chip_update_interval)
+			return S_IRUGO | S_IWUSR;
+		break;
+	case hwmon_in:
+		if (BIT(attr) & driver->sensor_hwmon_configs[0])
+			return S_IRUGO;
+		break;
+	case hwmon_temp:
+		if (BIT(attr) & driver->sensor_hwmon_configs[1])
+			return S_IRUGO;
+		break;
+	case hwmon_power:
+		/* user power limit */
+		if (attr == hwmon_power_alarm)
+			return S_IRUGO | S_IWUSR;
+		else if ((BIT(attr) & driver->sensor_hwmon_configs[2]) ||
+			 (BIT(attr) & driver->sensor_hwmon_configs[3]))
+			return S_IRUGO;
+		break;
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static const struct hwmon_ops occ_hwmon_ops = {
+	.is_visible = occ_is_visible,
+	.read = occ_hwmon_read,
+	.read_string = occ_hwmon_read_string,
+	.write = occ_hwmon_write,
+};
+
+static const u32 occ_chip_config[] = {
+	HWMON_C_UPDATE_INTERVAL,
+	0
+};
+
+static const struct hwmon_channel_info occ_chip = {
+	.type = hwmon_chip,
+	.config = occ_chip_config
+};
+
+static const enum hwmon_sensor_types occ_sensor_types[MAX_OCC_SENSOR_TYPE] = {
+	hwmon_in,
+	hwmon_temp,
+	hwmon_power,
+	hwmon_power
+};
+
+struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
+				  const u32 *sensor_hwmon_configs,
+				  const char *name)
+{
+	bool master_occ = false;
+	int rc, i, j, sensor_num, index = 0, id;
+	char *brk;
+	struct occ_blocks *resp = NULL;
+	u32 *sensor_config;
+	struct occ_sysfs *hwmon = devm_kzalloc(dev, sizeof(struct occ_sysfs),
+					       GFP_KERNEL);
+	if (!hwmon)
+		return ERR_PTR(-ENOMEM);
+
+	/* need space for null-termination and occ chip */
+	hwmon->occ_sensors =
+		devm_kzalloc(dev, sizeof(struct hwmon_channel_info *) *
+			     (MAX_OCC_SENSOR_TYPE + 2), GFP_KERNEL);
+	if (!hwmon->occ_sensors)
+		return ERR_PTR(-ENOMEM);
+
+	hwmon->occ = occ;
+	hwmon->sensor_hwmon_configs = (u32 *)sensor_hwmon_configs;
+	hwmon->occ_info.ops = &occ_hwmon_ops;
+	hwmon->occ_info.info =
+		(const struct hwmon_channel_info **)hwmon->occ_sensors;
+
+	dev_set_drvdata(dev, hwmon);
+
+	occ_get_response_blocks(occ, &resp);
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->sensor_block_id[i] = -1;
+
+	/* read sensor data from occ */
+	rc = occ_update_device(occ);
+	if (rc) {
+		dev_err(dev, "cannot get occ sensor data: %d\n", rc);
+		return ERR_PTR(rc);
+	}
+	if (!resp->blocks)
+		return ERR_PTR(-ENOMEM);
+
+	master_occ = resp->sensor_block_id[CAPS] >= 0;
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
+		id = resp->sensor_block_id[i];
+		if (id < 0)
+			continue;
+
+		sensor_num = resp->blocks[id].header.sensor_num;
+		/* need null-termination */
+		sensor_config = devm_kzalloc(dev,
+					     sizeof(u32) * (sensor_num + 1),
+					     GFP_KERNEL);
+		if (!sensor_config)
+			return ERR_PTR(-ENOMEM);
+
+		for (j = 0; j < sensor_num; j++)
+			sensor_config[j] = sensor_hwmon_configs[i];
+
+		hwmon->occ_sensors[index] =
+			devm_kzalloc(dev, sizeof(struct hwmon_channel_info),
+				     GFP_KERNEL);
+		if (!hwmon->occ_sensors[index])
+			return ERR_PTR(-ENOMEM);
+
+		hwmon->occ_sensors[index]->type = occ_sensor_types[i];
+		hwmon->occ_sensors[index]->config = sensor_config;
+		index++;
+	}
+
+	/* only need one of these for any number of occs */
+	if (master_occ)
+		hwmon->occ_sensors[index] =
+			(struct hwmon_channel_info *)&occ_chip;
+
+	/* search for bad chars */
+	strncpy(hwmon->hwmon_name, name, OCC_HWMON_NAME_LENGTH);
+	brk = strpbrk(hwmon->hwmon_name, "-* \t\n");
+	while (brk) {
+		*brk = '_';
+		brk = strpbrk(brk,  "-* \t\n");
+	}
+
+	hwmon->dev = devm_hwmon_device_register_with_info(dev,
+							  hwmon->hwmon_name,
+							  hwmon,
+							  &hwmon->occ_info,
+							  NULL);
+	if (IS_ERR(hwmon->dev)) {
+		dev_err(dev, "cannot register hwmon device %s: %ld\n",
+			hwmon->hwmon_name, PTR_ERR(hwmon->dev));
+		return ERR_PTR(PTR_ERR(hwmon->dev));
+	}
+
+	return hwmon;
+}
+EXPORT_SYMBOL(occ_sysfs_start);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("OCC sysfs driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_sysfs.h b/drivers/hwmon/occ/occ_sysfs.h
new file mode 100644
index 0000000..7de92e7
--- /dev/null
+++ b/drivers/hwmon/occ/occ_sysfs.h
@@ -0,0 +1,44 @@
+/*
+ * occ_sysfs.h - OCC sysfs interface
+ *
+ * This file contains the data structures and function prototypes for the OCC
+ * hwmon sysfs entries.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_SYSFS_H__
+#define __OCC_SYSFS_H__
+
+#include <linux/hwmon.h>
+
+struct occ;
+struct device;
+
+#define OCC_HWMON_NAME_LENGTH	32
+
+struct occ_sysfs {
+	struct device *dev;
+	struct occ *occ;
+
+	char hwmon_name[OCC_HWMON_NAME_LENGTH + 1];
+	u32 *sensor_hwmon_configs;
+	struct hwmon_channel_info **occ_sensors;
+	struct hwmon_chip_info occ_info;
+	u16 user_powercap;
+};
+
+struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
+				  const u32 *sensor_hwmon_configs,
+				  const char *name);
+#endif /* __OCC_SYSFS_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux v2 3/6] hwmon: occ: Add I2C transport implementation for SCOM operations
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add functions to send SCOM operations over I2C bus. The BMC can
communicate with the Power8 host processor over I2C, but needs to use SCOM
operations in order to access the OCC register space.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 drivers/hwmon/occ/occ_scom_i2c.c | 73 ++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_scom_i2c.h | 26 ++++++++++++++
 2 files changed, 99 insertions(+)
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.c
 create mode 100644 drivers/hwmon/occ/occ_scom_i2c.h

diff --git a/drivers/hwmon/occ/occ_scom_i2c.c b/drivers/hwmon/occ/occ_scom_i2c.c
new file mode 100644
index 0000000..a922f83
--- /dev/null
+++ b/drivers/hwmon/occ/occ_scom_i2c.c
@@ -0,0 +1,73 @@
+/*
+ * occ_scom_i2c.c - hwmon OCC driver
+ *
+ * This file contains the functions for performing SCOM operations over I2C bus
+ * to access the OCC.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "scom.h"
+#include "occ_scom_i2c.h"
+
+int occ_i2c_getscom(void *bus, u32 address, u64 *data)
+{
+	ssize_t rc;
+	u64 buf;
+	struct i2c_client *client = bus;
+
+	rc = i2c_master_send(client, (const char *)&address, sizeof(u32));
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u32))
+		return -EIO;
+
+	rc = i2c_master_recv(client, (char *)&buf, sizeof(u64));
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u64))
+		return -EIO;
+
+	*data = be64_to_cpu(buf);
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_i2c_getscom);
+
+int occ_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1)
+{
+	u32 buf[3];
+	ssize_t rc;
+	struct i2c_client *client = bus;
+
+	buf[0] = address;
+	buf[1] = data1;
+	buf[2] = data0;
+
+	rc = i2c_master_send(client, (const char *)buf, sizeof(u32) * 3);
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(u32) * 3)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL(occ_i2c_putscom);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("I2C OCC SCOM transport");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_scom_i2c.h b/drivers/hwmon/occ/occ_scom_i2c.h
new file mode 100644
index 0000000..945739c
--- /dev/null
+++ b/drivers/hwmon/occ/occ_scom_i2c.h
@@ -0,0 +1,26 @@
+/*
+ * occ_scom_i2c.h - hwmon OCC driver
+ *
+ * This file contains function protoypes for peforming SCOM operations over I2C
+ * bus to access the OCC.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_SCOM_I2C_H__
+#define __OCC_SCOM_I2C_H__
+
+int occ_i2c_getscom(void *bus, u32 address, u64 *data);
+int occ_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1);
+
+#endif /* __OCC_SCOM_I2C_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux v2 4/6] hwmon: occ: Add callbacks for parsing P8 OCC datastructures
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add functions to parse the data structures that are specific to the OCC on
the POWER8 processor. These are the sensor data structures, including
temperature, frequency, power, and "caps."

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ    |   9 ++
 drivers/hwmon/occ/occ_p8.c | 254 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_p8.h |  31 ++++++
 3 files changed, 294 insertions(+)
 create mode 100644 drivers/hwmon/occ/occ_p8.c
 create mode 100644 drivers/hwmon/occ/occ_p8.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index d0bdf06..143951e 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -25,6 +25,15 @@ Currently, all versions of the OCC support four types of sensor data: power,
 temperature, frequency, and "caps," which indicate limits and thresholds used
 internally on the OCC.
 
+The format for the POWER8 OCC sensor data can be found in the P8 OCC
+specification:
+github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf
+This document provides the details of the OCC sensors: power, frequency,
+temperature, and caps. These sensor formats are specific to the POWER8 OCC. A
+number of data structures, such as command format, response headers, and the
+like, are also defined in this specification, and are common to both POWER8 and
+POWER9 OCCs.
+
 sysfs Entries
 -------------
 
diff --git a/drivers/hwmon/occ/occ_p8.c b/drivers/hwmon/occ/occ_p8.c
new file mode 100644
index 0000000..6673da2
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p8.c
@@ -0,0 +1,254 @@
+/*
+ * occ_p8.c - OCC hwmon driver
+ *
+ * This file contains the Power8-specific methods and data structures for
+ * the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/unaligned.h>
+#include <linux/hwmon.h>
+
+#include "occ.h"
+#include "occ_p8.h"
+
+/* P8 OCC sensor data format */
+struct p8_occ_sensor {
+	u16 sensor_id;
+	u16 value;
+};
+
+struct p8_power_sensor {
+	u16 sensor_id;
+	u32 update_tag;
+	u32 accumulator;
+	u16 value;
+};
+
+struct p8_caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+};
+
+static const u32 p8_sensor_hwmon_configs[MAX_OCC_SENSOR_TYPE] = {
+	HWMON_I_INPUT | HWMON_I_LABEL,	/* freq: value | label */
+	HWMON_T_INPUT | HWMON_T_LABEL,	/* temp: value | label */
+	/* power: value | label | accumulator | update_tag */
+	HWMON_P_INPUT | HWMON_P_LABEL | HWMON_P_AVERAGE |
+		HWMON_P_AVERAGE_INTERVAL,
+	/* caps: curr | max | min | norm | user */
+	HWMON_P_CAP | HWMON_P_CAP_MAX | HWMON_P_CAP_MIN | HWMON_P_MAX |
+		HWMON_P_ALARM,
+};
+
+void p8_parse_sensor(u8 *data, void *sensor, int sensor_type, int off,
+		     int snum)
+{
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+	{
+		struct p8_occ_sensor *os =
+			&(((struct p8_occ_sensor *)sensor)[snum]);
+
+		os->sensor_id = be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		os->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+	}
+		break;
+	case POWER:
+	{
+		struct p8_power_sensor *ps =
+			&(((struct p8_power_sensor *)sensor)[snum]);
+
+		ps->sensor_id = be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		ps->update_tag =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 2]));
+		ps->accumulator =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 6]));
+		ps->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+	}
+		break;
+	case CAPS:
+	{
+		struct p8_caps_sensor *cs =
+			&(((struct p8_caps_sensor *)sensor)[snum]);
+
+		cs->curr_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		cs->curr_powerreading =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+		cs->norm_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+		cs->max_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 6]));
+		cs->min_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 8]));
+		cs->user_powerlimit =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+	}
+		break;
+	};
+}
+
+void *p8_alloc_sensor(int sensor_type, int num_sensors)
+{
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+		return kcalloc(num_sensors, sizeof(struct p8_occ_sensor),
+			       GFP_KERNEL);
+	case POWER:
+		return kcalloc(num_sensors, sizeof(struct p8_power_sensor),
+			       GFP_KERNEL);
+	case CAPS:
+		return kcalloc(num_sensors, sizeof(struct p8_caps_sensor),
+			       GFP_KERNEL);
+	default:
+		return NULL;
+	}
+}
+
+int p8_get_sensor(struct occ *driver, int sensor_type, int sensor_num,
+		  u32 hwmon, long *val)
+{
+	int rc = 0;
+	void *sensor;
+
+	if (sensor_type == POWER) {
+		if (hwmon == hwmon_power_cap || hwmon == hwmon_power_cap_max ||
+		    hwmon == hwmon_power_cap_min || hwmon == hwmon_power_max ||
+		    hwmon == hwmon_power_alarm)
+			sensor_type = CAPS;
+	}
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+	case TEMP:
+	{
+		struct p8_occ_sensor *os =
+			&(((struct p8_occ_sensor *)sensor)[sensor_num]);
+
+		if (hwmon == hwmon_in_input || hwmon == hwmon_temp_input)
+			*val = os->value;
+		else if (hwmon == hwmon_in_label || hwmon == hwmon_temp_label)
+			*val = os->sensor_id;
+		else
+			rc = -EOPNOTSUPP;
+	}
+		break;
+	case POWER:
+	{
+		struct p8_power_sensor *ps =
+			&(((struct p8_power_sensor *)sensor)[sensor_num]);
+
+		switch (hwmon) {
+		case hwmon_power_input:
+			*val = ps->value;
+			break;
+		case hwmon_power_label:
+			*val = ps->sensor_id;
+			break;
+		case hwmon_power_average:
+			*val = ps->accumulator;
+			break;
+		case hwmon_power_average_interval:
+			*val = ps->update_tag;
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	case CAPS:
+	{
+		struct p8_caps_sensor *cs =
+			&(((struct p8_caps_sensor *)sensor)[sensor_num]);
+
+		switch (hwmon) {
+		case hwmon_power_cap:
+			*val = cs->curr_powercap;
+			break;
+		case hwmon_power_cap_max:
+			*val = cs->max_powercap;
+			break;
+		case hwmon_power_cap_min:
+			*val = cs->min_powercap;
+			break;
+		case hwmon_power_max:
+			*val = cs->norm_powercap;
+			break;
+		case hwmon_power_alarm:
+			*val = cs->user_powerlimit;
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static const struct occ_ops p8_ops = {
+	.parse_sensor = p8_parse_sensor,
+	.alloc_sensor = p8_alloc_sensor,
+	.get_sensor = p8_get_sensor,
+};
+
+static const struct occ_config p8_config = {
+	.command_addr = 0xFFFF6000,
+	.response_addr = 0xFFFF7000,
+};
+
+const u32 *p8_get_sensor_hwmon_configs()
+{
+	return p8_sensor_hwmon_configs;
+}
+EXPORT_SYMBOL(p8_get_sensor_hwmon_configs);
+
+struct occ *p8_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops)
+{
+	return occ_start(dev, bus, bus_ops, &p8_ops, &p8_config);
+}
+EXPORT_SYMBOL(p8_occ_start);
+
+int p8_occ_stop(struct occ *occ)
+{
+	return occ_stop(occ);
+}
+EXPORT_SYMBOL(p8_occ_stop);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("P8 OCC sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_p8.h b/drivers/hwmon/occ/occ_p8.h
new file mode 100644
index 0000000..54ce907
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p8.h
@@ -0,0 +1,31 @@
+/*
+ * occ_p8.h - OCC hwmon driver
+ *
+ * This file contains Power8-specific function prototypes
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_P8_H__
+#define __OCC_P8_H__
+
+#include "scom.h"
+
+struct device;
+
+const u32 *p8_get_sensor_hwmon_configs(void);
+struct occ *p8_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops);
+int p8_occ_stop(struct occ *occ);
+
+#endif /* __OCC_P8_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux v2 5/6] hwmon: occ: Add hwmon implementation for the P8 OCC
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add code to tie the hwmon sysfs code and the POWER8 OCC code together, as
well as probe the entire driver from the I2C bus. I2C is the communication
method between the BMC and the P8 OCC.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/devicetree/bindings/hwmon/occ.txt |  13 +++
 drivers/hwmon/occ/Kconfig                       |  14 +++
 drivers/hwmon/occ/Makefile                      |   1 +
 drivers/hwmon/occ/p8_occ_i2c.c                  | 123 ++++++++++++++++++++++++
 4 files changed, 151 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/occ.txt
 create mode 100644 drivers/hwmon/occ/p8_occ_i2c.c

diff --git a/Documentation/devicetree/bindings/hwmon/occ.txt b/Documentation/devicetree/bindings/hwmon/occ.txt
new file mode 100644
index 0000000..b0d2b36
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/occ.txt
@@ -0,0 +1,13 @@
+HWMON I2C driver for IBM POWER CPU OCC (On Chip Controller)
+
+Required properties:
+ - compatible: must be "ibm,p8-occ-i2c"
+ - reg: physical address
+
+Example:
+i2c3: i2c-bus@100 {
+	occ@50 {
+		compatible = "ibm,p8-occ-i2c";
+		reg = <0x50>;
+	};
+};
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig
index cdb64a7..3a5188f 100644
--- a/drivers/hwmon/occ/Kconfig
+++ b/drivers/hwmon/occ/Kconfig
@@ -13,3 +13,17 @@ menuconfig SENSORS_PPC_OCC
 
 	  This driver can also be built as a module. If so, the module
 	  will be called occ.
+
+if SENSORS_PPC_OCC
+
+config SENSORS_PPC_OCC_P8_I2C
+	tristate "POWER8 OCC hwmon support"
+	depends on I2C
+	help
+	 Provide a hwmon sysfs interface for the POWER8 On-Chip Controller,
+	 exposing temperature, frequency and power measurements.
+
+	 This driver can also be built as a module. If so, the module will be
+	 called p8-occ-i2c.
+
+endif
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
index a6881f9..9294b58 100644
--- a/drivers/hwmon/occ/Makefile
+++ b/drivers/hwmon/occ/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
+obj-$(CONFIG_SENSORS_PPC_OCC_P8_I2C) += occ_scom_i2c.o occ_p8.o p8_occ_i2c.o
diff --git a/drivers/hwmon/occ/p8_occ_i2c.c b/drivers/hwmon/occ/p8_occ_i2c.c
new file mode 100644
index 0000000..4515c68
--- /dev/null
+++ b/drivers/hwmon/occ/p8_occ_i2c.c
@@ -0,0 +1,123 @@
+/*
+ * p8_occ_i2c.c - hwmon OCC driver
+ *
+ * This file contains the i2c layer for accessing the P8 OCC over i2c bus.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include "scom.h"
+#include "occ_scom_i2c.h"
+#include "occ_p8.h"
+#include "occ_sysfs.h"
+
+#define P8_OCC_I2C_NAME	"p8-occ-i2c"
+
+int p8_i2c_getscom(void *bus, u32 address, u64 *data)
+{
+	/* P8 i2c slave requires address to be shifted by 1 */
+	address = address << 1;
+
+	return occ_i2c_getscom(bus, address, data);
+}
+
+int p8_i2c_putscom(void *bus, u32 address, u32 data0, u32 data1)
+{
+	/* P8 i2c slave requires address to be shifted by 1 */
+	address = address << 1;
+
+	return occ_i2c_putscom(bus, address, data0, data1);
+}
+
+static struct occ_bus_ops p8_bus_ops = {
+	.getscom = p8_i2c_getscom,
+	.putscom = p8_i2c_putscom,
+};
+
+static int p8_occ_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct occ *occ;
+	struct occ_sysfs *hwmon;
+	const u32 *sensor_hwmon_configs = p8_get_sensor_hwmon_configs();
+
+	occ = p8_occ_start(&client->dev, client, &p8_bus_ops);
+	if (IS_ERR(occ))
+		return PTR_ERR(occ);
+
+	hwmon = occ_sysfs_start(&client->dev, occ, sensor_hwmon_configs,
+				P8_OCC_I2C_NAME);
+	if (IS_ERR(hwmon))
+		return PTR_ERR(hwmon);
+
+	i2c_set_clientdata(client, occ);
+
+	return 0;
+}
+
+static int p8_occ_remove(struct i2c_client *client)
+{
+	struct occ *occ = i2c_get_clientdata(client);
+
+	return p8_occ_stop(occ);
+}
+
+/* used by old-style board info. */
+static const struct i2c_device_id occ_ids[] = {
+	{ P8_OCC_I2C_NAME, 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, occ_ids);
+
+/* used by device table */
+static const struct of_device_id occ_of_match[] = {
+	{ .compatible = "ibm,p8-occ-i2c" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, occ_of_match);
+
+/*
+ * i2c-core uses i2c-detect() to detect device in below address list.
+ * If exists, address will be assigned to client.
+ * It is also possible to read address from device table.
+ */
+static const unsigned short normal_i2c[] = {0x50, 0x51, I2C_CLIENT_END };
+
+static struct i2c_driver p8_occ_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name = P8_OCC_I2C_NAME,
+		.pm = NULL,
+		.of_match_table = occ_of_match,
+	},
+	.probe = p8_occ_probe,
+	.remove = p8_occ_remove,
+	.id_table = occ_ids,
+	.address_list = normal_i2c,
+};
+
+module_i2c_driver(p8_occ_driver);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

^ permalink raw reply related

* [PATCH linux v2 6/6] hwmon: occ: Add callbacks for parsing P9 OCC datastructures
From: eajames.ibm @ 2017-01-11 18:10 UTC (permalink / raw)
  To: linux
  Cc: devicetree, jdelvare, corbet, linux-doc, linux-hwmon, linux-i2c,
	linux-kernel, mark.rutland, robh+dt, wsa, andrew, joel, benh,
	Edward A. James
In-Reply-To: <1484158237-10014-1-git-send-email-eajames.ibm@gmail.com>

From: "Edward A. James" <eajames@us.ibm.com>

Add functions to parse the data structures that are specific to the OCC on
the POWER9 processor. These are the sensor data structures, including
temperature, frequency, power, and "caps."

Signed-off-by: Edward A. James <eajames@us.ibm.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 Documentation/hwmon/occ    |   3 +
 drivers/hwmon/occ/occ_p9.c | 314 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/occ_p9.h |  31 +++++
 3 files changed, 348 insertions(+)
 create mode 100644 drivers/hwmon/occ/occ_p9.c
 create mode 100644 drivers/hwmon/occ/occ_p9.h

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
index 143951e..6cea853 100644
--- a/Documentation/hwmon/occ
+++ b/Documentation/hwmon/occ
@@ -34,6 +34,9 @@ number of data structures, such as command format, response headers, and the
 like, are also defined in this specification, and are common to both POWER8 and
 POWER9 OCCs.
 
+There is currently no public P9 OCC specification, and the data structures
+defined in the POWER9 OCC driver are subject to change.
+
 sysfs Entries
 -------------
 
diff --git a/drivers/hwmon/occ/occ_p9.c b/drivers/hwmon/occ/occ_p9.c
new file mode 100644
index 0000000..8b351a0
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p9.c
@@ -0,0 +1,314 @@
+/*
+ * occ_p9.c - OCC hwmon driver
+ *
+ * This file contains the Power9-specific methods and data structures for
+ * the OCC hwmon driver.
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <asm/unaligned.h>
+
+#include "occ.h"
+#include "occ_p9.h"
+
+/* P9 OCC sensor data format */
+struct p9_temp_sensor {
+	u32 sensor_id;
+	u8 fru_type;
+	u8 value;
+};
+
+struct p9_freq_sensor {
+	u32 sensor_id;
+	u16 value;
+};
+
+struct p9_power_sensor {
+	u32 sensor_id;
+	u8 function_id;
+	u8 apss_channel;
+	u16 reserved;
+	u32 update_tag;
+	u64 accumulator;
+	u16 value;
+};
+
+struct p9_caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+	u8 user_powerlimit_source;
+};
+
+static const u32 p9_sensor_hwmon_configs[MAX_OCC_SENSOR_TYPE] = {
+	HWMON_I_INPUT | HWMON_I_LABEL,	/* freq: value | label */
+	/* temp: value | label | fru_type */
+	HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_TYPE,
+	/* power: value | label | accum[0] | accum[1] | update_tag |
+	 *	 (function_id | (apss_channel << 8))
+	 */
+	HWMON_P_INPUT | HWMON_P_LABEL | HWMON_P_AVERAGE_MIN |
+		HWMON_P_AVERAGE_MAX | HWMON_P_AVERAGE_INTERVAL |
+		HWMON_P_RESET_HISTORY,
+	/* caps: curr | max | min | norm | user | source */
+	HWMON_P_CAP | HWMON_P_CAP_MAX | HWMON_P_CAP_MIN | HWMON_P_MAX |
+		HWMON_P_ALARM | HWMON_P_CAP_ALARM,
+};
+
+void p9_parse_sensor(u8 *data, void *sensor, int sensor_type, int off,
+		     int snum)
+{
+	switch (sensor_type) {
+	case FREQ:
+	{
+		struct p9_freq_sensor *fs =
+			&(((struct p9_freq_sensor *)sensor)[snum]);
+
+		fs->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		fs->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+	}
+		break;
+	case TEMP:
+	{
+		struct p9_temp_sensor *ts =
+			&(((struct p9_temp_sensor *)sensor)[snum]);
+
+		ts->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		fs->fru_type = data[off + 4];
+		fs->value = data[off + 5];
+	}
+		break;
+	case POWER:
+	{
+		struct p9_power_sensor *ps =
+			&(((struct p9_power_sensor *)sensor)[snum]);
+
+		ps->sensor_id = be32_to_cpu(get_unaligned((u32 *)&data[off]));
+		ps->function_id = data[off + 4];
+		ps->apss_channel = data[off + 5];
+		ps->update_tag =
+			be32_to_cpu(get_unaligned((u32 *)&data[off + 8]));
+		ps->accumulator =
+			be64_to_cpu(get_unaligned((u64 *)&data[off + 12]));
+		ps->value = be16_to_cpu(get_unaligned((u16 *)&data[off + 20]));
+	}
+		break;
+	case CAPS:
+	{
+		struct p9_caps_sensor *cs =
+			&(((struct p9_caps_sensor *)sensor)[snum]);
+
+		cs->curr_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off]));
+		cs->curr_powerreading =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 2]));
+		cs->norm_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 4]));
+		cs->max_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 6]));
+		cs->min_powercap =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 8]));
+		cs->user_powerlimit =
+			be16_to_cpu(get_unaligned((u16 *)&data[off + 10]));
+		cs->user_powerlimit_source = data[off + 12];
+	}
+		break;
+	};
+}
+
+void *p9_alloc_sensor(int sensor_type, int num_sensors)
+{
+	switch (sensor_type) {
+	case FREQ:
+		return kcalloc(num_sensors, sizeof(struct p9_freq_sensor),
+			       GFP_KERNEL);
+	case TEMP:
+		return kcalloc(num_sensors, sizeof(struct p9_temp_sensor),
+			       GFP_KERNEL);
+	case POWER:
+		return kcalloc(num_sensors, sizeof(struct p9_power_sensor),
+			       GFP_KERNEL);
+	case CAPS:
+		return kcalloc(num_sensors, sizeof(struct p9_caps_sensor),
+			       GFP_KERNEL);
+	default:
+		return NULL;
+	}
+}
+
+int p9_get_sensor(struct occ *driver, int sensor_type, int sensor_num,
+		  u32 hwmon, long *val)
+{
+	int rc = 0;
+	void *sensor;
+
+	if (sensor_type == POWER) {
+		if (hwmon == hwmon_power_cap || hwmon == hwmon_power_cap_max ||
+		    hwmon == hwmon_power_cap_min || hwmon == hwmon_power_max ||
+		    hwmon == hwmon_power_alarm ||
+		    hwmon == hwmon_power_cap_alarm)
+			sensor_type = CAPS;
+	}
+
+	sensor = occ_get_sensor(driver, sensor_type);
+	if (!sensor)
+		return -ENODEV;
+
+	switch (sensor_type) {
+	case FREQ:
+	{
+		struct p9_freq_sensor *fs =
+			&(((struct p9_freq_sensor *)sensor)[snum]);
+
+		switch (hwmon) {
+		case hwmon_in_input:
+			*val = fs->value;
+			break;
+		case hwmon_in_label:
+			*val = fs->sensor_id;
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	case TEMP:
+	{
+		struct p9_temp_sensor *ts =
+			&(((struct p9_temp_sensor *)sensor)[snum]);
+
+		switch (hwmon) {
+		case hwmon_temp_input:
+			*val = ts->value;
+			break;
+		case hwmon_temp_type:
+			*val = ts->fru_type;
+			break;
+		case hwmon_temp_label:
+			*val = ts->sensor_id;
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	case POWER:
+	{
+		struct p9_power_sensor *ps =
+			&(((struct p9_power_sensor *)sensor)[snum]);
+
+		switch (hwmon) {
+		case hwmon_power_input:
+			*val = ps->value;
+			break;
+		case hwmon_power_label:
+			*val = ps->sensor_id;
+			break;
+		case hwmon_power_average_min:
+			*val = ((u32 *)(&ps->accumulator))[0];
+			break;
+		case hwmon_power_average_max:
+			*val = ((u32 *)(&ps->accumulator))[1];
+			break;
+		case hwmon_power_average_interval:
+			*val = ps->update_tag;
+			break;
+		case hwmon_power_reset_history:
+			*val = ps->function_id | (ps->apss_channel << 8);
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	case CAPS:
+	{
+		struct p9_caps_sensor *cs =
+			&(((struct p9_caps_sensor *)sensor)[snum]);
+
+		switch (hwmon) {
+		case hwmon_power_cap:
+			*val = cs->curr_powercap;
+			break;
+		case hwmon_power_cap_max:
+			*val = cs->max_powercap;
+			break;
+		case hwmon_power_cap_min:
+			*val = cs->min_powercap;
+			break;
+		case hwmon_power_max:
+			*val = cs->norm_powercap;
+			break;
+		case hwmon_power_alarm:
+			*val = cs->user_powerlimit;
+			break;
+		case hwmon_power_cap_alarm:
+			*val = cs->user_powerlimit_source;
+			break;
+		default:
+			rc = -EOPNOTSUPP;
+		}
+	}
+		break;
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static const struct occ_ops p9_ops = {
+	.parse_sensor = p9_parse_sensor,
+	.alloc_sensor = p9_alloc_sensor,
+	.get_sensor = p9_get_sensor,
+};
+
+static const struct occ_config p9_config = {
+	.command_addr = 0xFFFBE000,
+	.response_addr = 0xFFFBF000,
+};
+
+const u32 *p9_get_sensor_hwmon_configs()
+{
+	return p9_sensor_hwmon_configs;
+}
+EXPORT_SYMBOL(p9_get_sensor_hwmon_configs);
+
+struct occ *p9_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops)
+{
+	return occ_start(dev, bus, bus_ops, &p9_ops, &p9_config);
+}
+EXPORT_SYMBOL(p9_occ_start);
+
+int p9_occ_stop(struct occ *occ)
+{
+	return occ_stop(occ);
+}
+EXPORT_SYMBOL(p9_occ_stop);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("P9 OCC sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ_p9.h b/drivers/hwmon/occ/occ_p9.h
new file mode 100644
index 0000000..b1c01d6
--- /dev/null
+++ b/drivers/hwmon/occ/occ_p9.h
@@ -0,0 +1,31 @@
+/*
+ * occ_p9.h - OCC hwmon driver
+ *
+ * This file contains Power9-specific function prototypes
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_P9_H__
+#define __OCC_P9_H__
+
+#include "scom.h"
+
+struct device;
+
+const u32 *p9_get_sensor_hwmon_configs(void);
+struct occ *p9_occ_start(struct device *dev, void *bus,
+			 struct occ_bus_ops *bus_ops);
+int p9_occ_stop(struct occ *occ);
+
+#endif /* __OCC_P9_H__ */
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH v2 2/4] usb: dwc2: Add binding for AHB burst
From: Christian Lamparter @ 2017-01-11 18:22 UTC (permalink / raw)
  To: John Youn
  Cc: Felipe Balbi, linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <f2b75acc-4773-e420-53ff-a77d0c9bce31-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>

On Tuesday, January 10, 2017 3:23:24 PM CET John Youn wrote:
> On 1/10/2017 3:03 PM, Christian Lamparter wrote:
> > On Tuesday, January 10, 2017 1:46:56 PM CET John Youn wrote:
> >> On 12/19/2016 6:49 AM, Christian Lamparter wrote:
> >>> (Lot's of old stuff, that doesn't matter anymore)
> > 
> > Hello John,
> >  
> >> This should be fixed against the latest dwc2 param rework series [1]
> >> which i hope to get queued for 4.11. If you can give it a test, that
> >> would be great.
> > 
> > oh Ok. I see you added it to
> > "[PATCH 16/21] usb: dwc2: Remove platform static params" [0].
> > Yes, I think this should work nicely. Thank you very much for
> > your time, even though it's just for like "one old board" :-).
> 
> No problem. Sorry for the delay getting the param stuff sorted.
> 
> > 
> > Do you have a public git tree with your patches that I can
> > clone/checkout?  If not, I'll take some time on the weekend
> > for this and write back on monday. But yeah, this should 
> > work.
> Yes check here on branch 'next'
> 
> https://github.com/synopsys-usb/linux.git
Ok thanks. I cloned it and built a new kernel for the thing. 

>From the (attached) bootlog:
GAHBCFG   @0xD1210008 : 0x0000002E
0x2E = Bit 5 | (Bit 3 | Bit 2 | Bit 1)
     = GAHBCFG_DMA_EN |
       (GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT)

I've attached an old 1GiB USB-Stick to the dwc2 managed port
and it does work as expected. The same goes for a usb-3.0 HDD
and a USB 2.0 11n WLAN stick. (All while the DWC SATA is
copying data).

Tested-by: Christian Lamparter <chunkeey-gM/Ye1E23mwN+BqQ9rBEUg@public.gmane.org>

Again, thank you for your work!

Regards,
Christian
---
These are the kernel messages.

dwc2 4bff80000.usbotg: mapped PA bff80000 to VA d1210000
dwc2 4bff80000.usbotg: registering common handler for irq35
dwc2 4bff80000.usbotg: Forcing mode to host
dwc2 4bff80000.usbotg: Core Release: 2.90a (snpsid=4f54290a)
dwc2 4bff80000.usbotg: Forcing mode to host
dwc2 4bff80000.usbotg: DWC OTG HCD INIT
dwc2 4bff80000.usbotg: hcfg=00000200
dwc2 4bff80000.usbotg: dwc2_core_init(ca890810)
dwc2 4bff80000.usbotg: HS UTMI+ PHY selected
dwc2 4bff80000.usbotg: Internal DMA Mode
dwc2 4bff80000.usbotg: host_dma:1 dma_desc_enable:0
dwc2 4bff80000.usbotg: Using Buffer DMA mode
dwc2 4bff80000.usbotg: Host Mode
dwc2 4bff80000.usbotg: DWC OTG Controller
dwc2 4bff80000.usbotg: new USB bus registered, assigned bus number 1
dwc2 4bff80000.usbotg: irq 35, io mem 0x00000000
dwc2 4bff80000.usbotg: DWC OTG HCD START
dwc2 4bff80000.usbotg: dwc2_core_host_init(ca890810)
dwc2 4bff80000.usbotg: Initializing HCFG.FSLSPClkSel to 00000000
dwc2 4bff80000.usbotg: initial grxfsiz=00000213
dwc2 4bff80000.usbotg: new grxfsiz=00000213
dwc2 4bff80000.usbotg: initial gnptxfsiz=01000213
dwc2 4bff80000.usbotg: new gnptxfsiz=01000213
dwc2 4bff80000.usbotg: initial hptxfsiz=01000313
dwc2 4bff80000.usbotg: new hptxfsiz=01000313
dwc2 4bff80000.usbotg: dwc2_core_host_init: Halt channel 0
dwc2 4bff80000.usbotg: dwc2_core_host_init: Halt channel 1
dwc2 4bff80000.usbotg: dwc2_core_host_init: Halt channel 2
dwc2 4bff80000.usbotg: dwc2_core_host_init: Halt channel 3
dwc2 4bff80000.usbotg: Init: Port Power? op_state=9
dwc2 4bff80000.usbotg: Init: Power Port (0)
dwc2 4bff80000.usbotg: dwc2_enable_host_interrupts()
dwc2 4bff80000.usbotg: DWC OTG HCD Has Root Hub
dwc2 4bff80000.usbotg: DWC OTG HCD EP RESET: bEndpointAddress=0x81
hub 1-0:1.0: USB hub found
dwc2 4bff80000.usbotg: GetHubDescriptor
hub 1-0:1.0: 1 port detected
dwc2 4bff80000.usbotg: GetHubStatus
dwc2 4bff80000.usbotg: SetPortFeature
dwc2 4bff80000.usbotg: 
dwc2 4bff80000.usbotg: ************************************************************
dwc2 4bff80000.usbotg: HCD State:
dwc2 4bff80000.usbotg:   Num channels: 4
dwc2 4bff80000.usbotg:   Channel 0:
dwc2 4bff80000.usbotg:     dev_addr: 0, ep_num: 0, ep_is_in: 0
dwc2 4bff80000.usbotg:     speed: 0
dwc2 4bff80000.usbotg:     ep_type: 0
dwc2 4bff80000.usbotg:     max_packet: 0
dwc2 4bff80000.usbotg:     data_pid_start: 0
dwc2 4bff80000.usbotg:     multi_count: 0
dwc2 4bff80000.usbotg:     xfer_started: 0
dwc2 4bff80000.usbotg:     xfer_buf:   (null)
dwc2 4bff80000.usbotg:     xfer_dma: 00000000
dwc2 4bff80000.usbotg:     xfer_len: 0
dwc2 4bff80000.usbotg:     xfer_count: 0
dwc2 4bff80000.usbotg:     halt_on_queue: 0
dwc2 4bff80000.usbotg:     halt_pending: 0
dwc2 4bff80000.usbotg:     halt_status: 0
dwc2 4bff80000.usbotg:     do_split: 0
dwc2 4bff80000.usbotg:     complete_split: 0
dwc2 4bff80000.usbotg:     hub_addr: 0
dwc2 4bff80000.usbotg:     hub_port: 0
dwc2 4bff80000.usbotg:     xact_pos: 0
dwc2 4bff80000.usbotg:     requests: 0
dwc2 4bff80000.usbotg:     qh:   (null)
dwc2 4bff80000.usbotg:   Channel 1:
dwc2 4bff80000.usbotg:     dev_addr: 0, ep_num: 0, ep_is_in: 0
dwc2 4bff80000.usbotg:     speed: 0
dwc2 4bff80000.usbotg:     ep_type: 0
dwc2 4bff80000.usbotg:     max_packet: 0
dwc2 4bff80000.usbotg:     data_pid_start: 0
dwc2 4bff80000.usbotg:     multi_count: 0
dwc2 4bff80000.usbotg:     xfer_started: 0
dwc2 4bff80000.usbotg:     xfer_buf:   (null)
dwc2 4bff80000.usbotg:     xfer_dma: 00000000
dwc2 4bff80000.usbotg:     xfer_len: 0
dwc2 4bff80000.usbotg:     xfer_count: 0
dwc2 4bff80000.usbotg:     halt_on_queue: 0
dwc2 4bff80000.usbotg:     halt_pending: 0
dwc2 4bff80000.usbotg:     halt_status: 0
dwc2 4bff80000.usbotg:     do_split: 0
dwc2 4bff80000.usbotg:     complete_split: 0
dwc2 4bff80000.usbotg:     hub_addr: 0
dwc2 4bff80000.usbotg:     hub_port: 0
dwc2 4bff80000.usbotg:     xact_pos: 0
dwc2 4bff80000.usbotg:     requests: 0
dwc2 4bff80000.usbotg:     qh:   (null)
dwc2 4bff80000.usbotg:   Channel 2:
dwc2 4bff80000.usbotg:     dev_addr: 0, ep_num: 0, ep_is_in: 0
dwc2 4bff80000.usbotg:     speed: 0
dwc2 4bff80000.usbotg:     ep_type: 0
dwc2 4bff80000.usbotg:     max_packet: 0
dwc2 4bff80000.usbotg:     data_pid_start: 0
dwc2 4bff80000.usbotg:     multi_count: 0
dwc2 4bff80000.usbotg:     xfer_started: 0
dwc2 4bff80000.usbotg:     xfer_buf:   (null)
dwc2 4bff80000.usbotg:     xfer_dma: 00000000
dwc2 4bff80000.usbotg:     xfer_len: 0
dwc2 4bff80000.usbotg:     xfer_count: 0
dwc2 4bff80000.usbotg:     halt_on_queue: 0
dwc2 4bff80000.usbotg:     halt_pending: 0
dwc2 4bff80000.usbotg:     halt_status: 0
dwc2 4bff80000.usbotg:     do_split: 0
dwc2 4bff80000.usbotg:     complete_split: 0
dwc2 4bff80000.usbotg:     hub_addr: 0
dwc2 4bff80000.usbotg:     hub_port: 0
dwc2 4bff80000.usbotg:     xact_pos: 0
dwc2 4bff80000.usbotg:     requests: 0
dwc2 4bff80000.usbotg:     qh:   (null)
dwc2 4bff80000.usbotg:   Channel 3:
dwc2 4bff80000.usbotg:     dev_addr: 0, ep_num: 0, ep_is_in: 0
dwc2 4bff80000.usbotg:     speed: 0
dwc2 4bff80000.usbotg:     ep_type: 0
dwc2 4bff80000.usbotg:     max_packet: 0
dwc2 4bff80000.usbotg:     data_pid_start: 0
dwc2 4bff80000.usbotg:     multi_count: 0
dwc2 4bff80000.usbotg:     xfer_started: 0
dwc2 4bff80000.usbotg:     xfer_buf:   (null)
dwc2 4bff80000.usbotg:     xfer_dma: 00000000
dwc2 4bff80000.usbotg:     xfer_len: 0
dwc2 4bff80000.usbotg:     xfer_count: 0
dwc2 4bff80000.usbotg:     halt_on_queue: 0
dwc2 4bff80000.usbotg:     halt_pending: 0
dwc2 4bff80000.usbotg:     halt_status: 0
dwc2 4bff80000.usbotg:     do_split: 0
dwc2 4bff80000.usbotg:     complete_split: 0
dwc2 4bff80000.usbotg:     hub_addr: 0
dwc2 4bff80000.usbotg:     hub_port: 0
dwc2 4bff80000.usbotg:     xact_pos: 0
dwc2 4bff80000.usbotg:     requests: 0
dwc2 4bff80000.usbotg:     qh:   (null)
dwc2 4bff80000.usbotg:   non_periodic_channels: 0
dwc2 4bff80000.usbotg:   periodic_channels: 0
dwc2 4bff80000.usbotg:   periodic_usecs: 0
dwc2 4bff80000.usbotg:   NP Tx Req Queue Space Avail: 8
dwc2 4bff80000.usbotg:   NP Tx FIFO Space Avail: 256
dwc2 4bff80000.usbotg:   P Tx Req Queue Space Avail: 8
dwc2 4bff80000.usbotg:   P Tx FIFO Space Avail: 256
dwc2 4bff80000.usbotg: Core Global Registers
dwc2 4bff80000.usbotg: GOTGCTL   @0xD1210000 : 0x001E0001
dwc2 4bff80000.usbotg: GOTGINT   @0xD1210004 : 0x00080000
dwc2 4bff80000.usbotg: GAHBCFG   @0xD1210008 : 0x0000002E
dwc2 4bff80000.usbotg: GUSBCFG   @0xD121000C : 0x20001708
dwc2 4bff80000.usbotg: GRSTCTL   @0xD1210010 : 0x80000000
dwc2 4bff80000.usbotg: GINTSTS   @0xD1210014 : 0x05000025
dwc2 4bff80000.usbotg: GINTMSK   @0xD1210018 : 0xF3000806
dwc2 4bff80000.usbotg: GRXSTSR   @0xD121001C : 0x025B34D8
dwc2 4bff80000.usbotg: GRXFSIZ   @0xD1210024 : 0x00000213
dwc2 4bff80000.usbotg: GNPTXFSIZ         @0xD1210028 : 0x01000213
dwc2 4bff80000.usbotg: GNPTXSTS  @0xD121002C : 0x00080100
dwc2 4bff80000.usbotg: GI2CCTL   @0xD1210030 : 0x00000000
dwc2 4bff80000.usbotg: GPVNDCTL  @0xD1210034 : 0x00000000
dwc2 4bff80000.usbotg: GGPIO     @0xD1210038 : 0x00000000
dwc2 4bff80000.usbotg: GUID      @0xD121003C : 0x00000000
dwc2 4bff80000.usbotg: GSNPSID   @0xD1210040 : 0x4F54290A
dwc2 4bff80000.usbotg: GHWCFG1   @0xD1210044 : 0x00000000
dwc2 4bff80000.usbotg: GHWCFG2   @0xD1210048 : 0x228CC850
dwc2 4bff80000.usbotg: GHWCFG3   @0xD121004C : 0x07FA0CE8
dwc2 4bff80000.usbotg: GHWCFG4   @0xD1210050 : 0x09F04011
dwc2 4bff80000.usbotg: GLPMCFG   @0xD1210054 : 0x00000000
dwc2 4bff80000.usbotg: GPWRDN    @0xD1210058 : 0x00000000
dwc2 4bff80000.usbotg: GDFIFOCFG         @0xD121005C : 0x00000000
dwc2 4bff80000.usbotg: HPTXFSIZ  @0xD1210100 : 0x01000313
dwc2 4bff80000.usbotg: PCGCTL    @0xD1210E00 : 0x00000000
dwc2 4bff80000.usbotg: Host Global Registers
dwc2 4bff80000.usbotg: HCFG      @0xD1210400 : 0x00000200
dwc2 4bff80000.usbotg: HFIR      @0xD1210404 : 0x0000EA60
dwc2 4bff80000.usbotg: HFNUM     @0xD1210408 : 0xEA603FFF
dwc2 4bff80000.usbotg: HPTXSTS   @0xD1210410 : 0x00080100
dwc2 4bff80000.usbotg: HAINT     @0xD1210414 : 0x00000000
dwc2 4bff80000.usbotg: HAINTMSK  @0xD1210418 : 0x00000000
dwc2 4bff80000.usbotg: HPRT0     @0xD1210440 : 0x00021403
dwc2 4bff80000.usbotg: Host Channel 0 Specific Registers
dwc2 4bff80000.usbotg: HCCHAR    @0xD1210500 : 0x00000000
dwc2 4bff80000.usbotg: HCSPLT    @0xD1210504 : 0x00000000
dwc2 4bff80000.usbotg: HCINT     @0xD1210508 : 0x00000002
dwc2 4bff80000.usbotg: HCINTMSK  @0xD121050C : 0x00000000
dwc2 4bff80000.usbotg: HCTSIZ    @0xD1210510 : 0x00000000
dwc2 4bff80000.usbotg: HCDMA     @0xD1210514 : 0x8D289912
dwc2 4bff80000.usbotg: Host Channel 1 Specific Registers
dwc2 4bff80000.usbotg: HCCHAR    @0xD1210520 : 0x00000000
dwc2 4bff80000.usbotg: HCSPLT    @0xD1210524 : 0x00000000
dwc2 4bff80000.usbotg: HCINT     @0xD1210528 : 0x00000002
dwc2 4bff80000.usbotg: HCINTMSK  @0xD121052C : 0x00000000
dwc2 4bff80000.usbotg: HCTSIZ    @0xD1210530 : 0x00000000
dwc2 4bff80000.usbotg: HCDMA     @0xD1210534 : 0xBBC5AD7A
dwc2 4bff80000.usbotg: Host Channel 2 Specific Registers
dwc2 4bff80000.usbotg: HCCHAR    @0xD1210540 : 0x00000000
dwc2 4bff80000.usbotg: HCSPLT    @0xD1210544 : 0x00000000
dwc2 4bff80000.usbotg: HCINT     @0xD1210548 : 0x00000002
dwc2 4bff80000.usbotg: HCINTMSK  @0xD121054C : 0x00000000
dwc2 4bff80000.usbotg: HCTSIZ    @0xD1210550 : 0x00000000
dwc2 4bff80000.usbotg: HCDMA     @0xD1210554 : 0xC7D821B3
dwc2 4bff80000.usbotg: Host Channel 3 Specific Registers
dwc2 4bff80000.usbotg: HCCHAR    @0xD1210560 : 0x00000000
dwc2 4bff80000.usbotg: HCSPLT    @0xD1210564 : 0x00000000
dwc2 4bff80000.usbotg: HCINT     @0xD1210568 : 0x00000002
dwc2 4bff80000.usbotg: HCINTMSK  @0xD121056C : 0x00000000
dwc2 4bff80000.usbotg: HCTSIZ    @0xD1210570 : 0x00000000
dwc2 4bff80000.usbotg: HCDMA     @0xD1210574 : 0x7DEDDB55
dwc2 4bff80000.usbotg: ************************************************************
dwc2 4bff80000.usbotg: 
dwc2 4bff80000.usbotg: gintsts=05000025  gintmsk=f3000806
dwc2 4bff80000.usbotg: ++OTG Interrupt gotgint=80000 [a_host]
dwc2 4bff80000.usbotg:  ++OTG Interrupt: Debounce Done++
dwc2 4bff80000.usbotg: ClearPortFeature USB_PORT_FEAT_C_CONNECTION
root@lede:/tmp# dwc2 4bff80000.usbotg: SetPortFeature
dwc2 4bff80000.usbotg: SetPortFeature - USB_PORT_FEAT_RESET
dwc2 4bff80000.usbotg: In host mode, hprt0=00021501
dwc2 4bff80000.usbotg: gintsts=05000021  gintmsk=f3000806
dwc2 4bff80000.usbotg: ClearPortFeature USB_PORT_FEAT_C_RESET
usb 1-1: new high-speed USB device number 2 using dwc2
dwc2 4bff80000.usbotg: SetPortFeature
dwc2 4bff80000.usbotg: SetPortFeature - USB_PORT_FEAT_RESET
dwc2 4bff80000.usbotg: In host mode, hprt0=00001101
dwc2 4bff80000.usbotg: gintsts=05000029  gintmsk=f3000806
dwc2 4bff80000.usbotg: gintsts=05000029  gintmsk=f3000806
dwc2 4bff80000.usbotg: ClearPortFeature USB_PORT_FEAT_C_RESET
dwc2 4bff80000.usbotg: DWC OTG HCD EP DISABLE: bEndpointAddress=0x00, ep->hcpriv=ca80e6c0
dwc2 4bff80000.usbotg: DWC OTG HCD EP DISABLE: bEndpointAddress=0x00, ep->hcpriv=  (null)
dwc2 4bff80000.usbotg: DWC OTG HCD EP RESET: bEndpointAddress=0x00
dwc2 4bff80000.usbotg: DWC OTG HCD HUB STATUS DATA: Root port status changed
dwc2 4bff80000.usbotg:   port_connect_status_change: 0
dwc2 4bff80000.usbotg:   port_reset_change: 0
dwc2 4bff80000.usbotg:   port_enable_change: 1
dwc2 4bff80000.usbotg:   port_suspend_change: 0
dwc2 4bff80000.usbotg:   port_over_current_change: 0
dwc2 4bff80000.usbotg: DWC OTG HCD EP RESET: bEndpointAddress=0x81
dwc2 4bff80000.usbotg: DWC OTG HCD EP RESET: bEndpointAddress=0x02
usb-storage 1-1:1.0: USB Mass Storage device detected
scsi host2: usb-storage 1-1:1.0
dwc2 4bff80000.usbotg: ClearPortFeature USB_PORT_FEAT_C_ENABLE
scsi 2:0:0:0: Direct-Access              FlashPen Fancy   1100 PQ: 0 ANSI: 0 CCS
sd 2:0:0:0: [sda] 1957888 512-byte logical blocks: (1.00 GB/956 MiB)
sd 2:0:0:0: [sda] Write Protect is off
sd 2:0:0:0: [sda] Mode Sense: 43 00 00 00
sd 2:0:0:0: [sda] No Caching mode page found
sd 2:0:0:0: [sda] Assuming drive cache: write through
 sda: sda1
sd 2:0:0:0: [sda] Attached SCSI removable disk
random: crng init done


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH v2] PCI: rockchip: Add quirk to disable RC's ASPM L0s
From: Bjorn Helgaas @ 2017-01-11 18:28 UTC (permalink / raw)
  To: Shawn Lin
  Cc: Bjorn Helgaas, Rob Herring, linux-pci-u79uwXL29TY76Z2rM5mHXA,
	linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Wenrui Li,
	Brian Norris, Jeffy Chen, devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1481543487-33152-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>

On Mon, Dec 12, 2016 at 07:51:27PM +0800, Shawn Lin wrote:
> Rockchip's RC outputs 100MHz reference clock but there are
> two methods for PHY to generate it.
> 
> (1)One of them is to use system PLL to generate 100MHz clock and
> the PHY will relock it and filter signal noise then outputs the
> reference clock.
> 
> (2)Another way is to share Soc's 24MHZ crystal oscillator with
> PHY and force PHY's DLL to generate 100MHz internally.
> 
> When using case(2), the exit from L0s doesn't work fine occasionally
> due to the broken design of RC receiver's logical circuit. So even if
> we use extended-synch, it still fails for PHY to relock the bits from
> FTS sometimes. This will hang the system.
> 
> Maybe we could argue that why not use case(1) to avoid it? The reason
> is that as we could see the reference clock is derived from system PLL
> and the path from it to PHY isn't so clean which means there are some
> noise introduced by power-domain and other buses can't be filterd out
> by PHY and we could see noise from the frequency spectrum by
> oscilloscope. This makes the TX compatibility test a little difficult
> to pass the spec. So case(1) and case(2) are both used indeed now. If
> using case(2), we should disable RC's L0s support, and that is why we
> need this property to indicate this quirk.
> 
> Also after checking quirk.c, I noticed there is already a quirk for
> disabling L0s unconditionally, quirk_disable_aspm_l0s. But obviously we
> shouldn't do that as mentioned above that case(1) could still works fine
> with L0s.
> 
> Reported-by: Jeffy Chen <jeffy.chen-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> Cc: Brian Norris <briannorris-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
> Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
> 
> ---
> 
> Changes in v2:
> - drop the quirk prefix
> 
>  Documentation/devicetree/bindings/pci/rockchip-pcie.txt | 2 ++
>  drivers/pci/host/pcie-rockchip.c                        | 9 +++++++++
>  2 files changed, 11 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/pci/rockchip-pcie.txt b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt
> index 71aeda1..1453a73 100644
> --- a/Documentation/devicetree/bindings/pci/rockchip-pcie.txt
> +++ b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt
> @@ -43,6 +43,8 @@ Required properties:
>  - interrupt-map-mask and interrupt-map: standard PCI properties
>  
>  Optional Property:
> +- aspm-no-l0s: RC won't support ASPM L0s. This property is needed if
> +	using 24MHz OSC for RC's PHY.
>  - ep-gpios: contain the entry for pre-reset gpio
>  - num-lanes: number of lanes to use
>  - vpcie3v3-supply: The phandle to the 3.3v regulator to use for PCIe.
> diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c
> index f2dca7b..35988fc 100644
> --- a/drivers/pci/host/pcie-rockchip.c
> +++ b/drivers/pci/host/pcie-rockchip.c
> @@ -140,6 +140,8 @@
>  #define   PCIE_RC_CONFIG_DCR_CSPL_SHIFT		18
>  #define   PCIE_RC_CONFIG_DCR_CSPL_LIMIT		0xff
>  #define   PCIE_RC_CONFIG_DCR_CPLS_SHIFT		26
> +#define PCIE_RC_CONFIG_LINK_CAP		(PCIE_RC_CONFIG_BASE + 0xcc)
> +#define   PCIE_RC_CONFIG_LINK_CAP_L0S		BIT(10)
>  #define PCIE_RC_CONFIG_LCS		(PCIE_RC_CONFIG_BASE + 0xd0)
>  #define PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2 (PCIE_RC_CONFIG_BASE + 0x90c)
>  #define PCIE_RC_CONFIG_THP_CAP		(PCIE_RC_CONFIG_BASE + 0x274)
> @@ -653,6 +655,13 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip)
>  	status &= ~PCIE_RC_CONFIG_THP_CAP_NEXT_MASK;
>  	rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_THP_CAP);
>  
> +	/* Clear L0s from RC's link cap */
> +	if (of_property_read_bool(dev->of_node, "apsm-no-l0s")) {

Did you test this?  This string ("apsm-no-l0s") doesn't match the
"aspm-no-l0s" documented above.  The current tree doesn't contain either
string in any DTS.

> +		status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LINK_CAP);
> +		status &= ~PCIE_RC_CONFIG_LINK_CAP_L0S;
> +		rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LINK_CAP);
> +	}
> +
>  	rockchip_pcie_write(rockchip, 0x0, PCIE_RC_BAR_CONF);
>  
>  	rockchip_pcie_write(rockchip,
> -- 
> 1.9.1
> 
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-pci" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 1/5] pinctrl: core: Use delayed work for hogs
From: Tony Lindgren @ 2017-01-11 18:31 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Geert Uytterhoeven, Haojian Zhuang, Masahiro Yamada,
	Grygorii Strashko, Nishanth Menon, linux-gpio@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-omap@vger.kernel.org, Linux-Renesas
In-Reply-To: <20170111162808.GW2630@atomide.com>

* Tony Lindgren <tony@atomide.com> [170111 08:29]:
> * Linus Walleij <linus.walleij@linaro.org> [170111 07:34]:
> > On Tue, Jan 10, 2017 at 8:19 PM, Tony Lindgren <tony@atomide.com> wrote:
> > 
> > > Below is an experimental fix to intorduce pinctrl_start() that I've
> > > tested with pinctrl-single. Then we should probably make all pin controller
> > > drivers call pinctrl_start() to properly fix the issue of struct pinctrl_dev
> > > handle not being initialized before driver functions are called.
> > 
> > Hm I guess that could work, but can we keep pinctrl_register() with the old
> > semantics and add a separate pinctrl_register_and_defer()
> > for those who just wanna start it later by a separate call?
> > 
> > Then we don't need any special flags.
> 
> OK I'll take a look.
> 
> > > Or do you guys have any better ideas?
> > 
> > Not really. So you mean revert the previous patch and apply something
> > like this instead?
> 
> Let me first take a look to see if we can fix it by making drivers using
> GENERIC_PINCTRL_GROUPS or GENERIC_PINMUX_FUNCTIONS register with
> pinctrl_register_and_defer(). I'll post a patch for that today.

Yeah we can fix this by reverting the late_init parts of the earlier
attempt and introducing a new pinctrl_register_and_init() for controllers
to use:

extern int pinctrl_register_and_init(struct pinctrl_desc *pctldesc,
				     struct device *dev, void *driver_data,
				     struct pinctrl_dev **pctldev);

> Then maybe for v4.12 we can attempt to move all pin controller drivers
> to using it so we can fix the problem for good.

And that will also make converting existing drivers to use it later on
trivial.

Will post a patch shortly after some more testing.

Regards,

Tony

^ permalink raw reply

* Re: [PATCH v2] PCI: rockchip: Add quirk to disable RC's ASPM L0s
From: Brian Norris @ 2017-01-11 18:38 UTC (permalink / raw)
  To: Bjorn Helgaas
  Cc: Shawn Lin, Bjorn Helgaas, Rob Herring, linux-pci, linux-rockchip,
	Wenrui Li, Jeffy Chen, devicetree, Heiko Stuebner
In-Reply-To: <20170111182822.GC14532@bhelgaas-glaptop.roam.corp.google.com>

+ Heiko

On Wed, Jan 11, 2017 at 12:28:22PM -0600, Bjorn Helgaas wrote:
> On Mon, Dec 12, 2016 at 07:51:27PM +0800, Shawn Lin wrote:
> > diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c
> > index f2dca7b..35988fc 100644
> > --- a/drivers/pci/host/pcie-rockchip.c
> > +++ b/drivers/pci/host/pcie-rockchip.c
> > @@ -140,6 +140,8 @@
> >  #define   PCIE_RC_CONFIG_DCR_CSPL_SHIFT		18
> >  #define   PCIE_RC_CONFIG_DCR_CSPL_LIMIT		0xff
> >  #define   PCIE_RC_CONFIG_DCR_CPLS_SHIFT		26
> > +#define PCIE_RC_CONFIG_LINK_CAP		(PCIE_RC_CONFIG_BASE + 0xcc)
> > +#define   PCIE_RC_CONFIG_LINK_CAP_L0S		BIT(10)
> >  #define PCIE_RC_CONFIG_LCS		(PCIE_RC_CONFIG_BASE + 0xd0)
> >  #define PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2 (PCIE_RC_CONFIG_BASE + 0x90c)
> >  #define PCIE_RC_CONFIG_THP_CAP		(PCIE_RC_CONFIG_BASE + 0x274)
> > @@ -653,6 +655,13 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip)
> >  	status &= ~PCIE_RC_CONFIG_THP_CAP_NEXT_MASK;
> >  	rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_THP_CAP);
> >  
> > +	/* Clear L0s from RC's link cap */
> > +	if (of_property_read_bool(dev->of_node, "apsm-no-l0s")) {
> 
> Did you test this?  This string ("apsm-no-l0s") doesn't match the
> "aspm-no-l0s" documented above.  The current tree doesn't contain either
> string in any DTS.

Ha, wow. FWIW in the tree I'm using, I have both this patch and a DTS
patch that uses the matching (but improperly-spelled) property. So *I*
have tested it. But I obviously didn't read it well enough. Or maybe I'm
mildly dyslexic?

Notably, Shawn sent a NON-matching DTS patch already here:

https://patchwork.kernel.org/patch/9477651/

So this definitely needs to get straightened out. Preferably by
s/apsm/aspm/ in this patch.

Regards,
Brian

^ permalink raw reply

* Re: [PATCH v2 pci/next] PCI: rcar: Add compatible string for r8a7796
From: Bjorn Helgaas @ 2017-01-11 18:54 UTC (permalink / raw)
  To: Yoshihiro Kaneko
  Cc: linux-pci, Bjorn Helgaas, Simon Horman, Magnus Damm,
	Geert Uytterhoeven, linux-renesas-soc, devicetree
In-Reply-To: <1482259026-7180-1-git-send-email-ykaneko0929@gmail.com>

On Wed, Dec 21, 2016 at 03:37:06AM +0900, Yoshihiro Kaneko wrote:
> From: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
> 
> This patch adds support for r8a7796.
> 
> Signed-off-by: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
> Signed-off-by: Yoshihiro Kaneko <ykaneko0929@gmail.com>
> Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>

Applied to pci/host-rcar for v4.11 with acks from Simon and Rob, thanks!

> ---
> 
> This patch is based on the next branch of the pci tree.
> 
> v2 [Yoshihiro Kaneko]
> * As suggested by Geert Uytterhoeven
>   Dropped the update of the driver.
> 
>  Documentation/devicetree/bindings/pci/rcar-pci.txt | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/Documentation/devicetree/bindings/pci/rcar-pci.txt b/Documentation/devicetree/bindings/pci/rcar-pci.txt
> index eee518d..34712d6 100644
> --- a/Documentation/devicetree/bindings/pci/rcar-pci.txt
> +++ b/Documentation/devicetree/bindings/pci/rcar-pci.txt
> @@ -6,6 +6,7 @@ compatible: "renesas,pcie-r8a7779" for the R8A7779 SoC;
>  	    "renesas,pcie-r8a7791" for the R8A7791 SoC;
>  	    "renesas,pcie-r8a7793" for the R8A7793 SoC;
>  	    "renesas,pcie-r8a7795" for the R8A7795 SoC;
> +	    "renesas,pcie-r8a7796" for the R8A7796 SoC;
>  	    "renesas,pcie-rcar-gen2" for a generic R-Car Gen2 compatible device.
>  	    "renesas,pcie-rcar-gen3" for a generic R-Car Gen3 compatible device.
>  
> -- 
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-pci" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 2/5] clk: sunxi-ng: add support for V3s CCU
From: Icenowy Zheng @ 2017-01-11 19:39 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Stephen Boyd, Linus Walleij,
	linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-clk-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
In-Reply-To: <20170110181006.yrd6moy2mctzoc4c@lukather>



11.01.2017, 02:10, "Maxime Ripard" <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>:
> On Tue, Jan 03, 2017 at 11:16:26PM +0800, Icenowy Zheng wrote:
>>  V3s has a similar but cut-down CCU to H3.
>>
>>  Add support for it.
>>
>>  Signed-off-by: Icenowy Zheng <icenowy-ymACFijhrKM@public.gmane.org>
>
> It looks like there's nothing different but the clocks that you
> register with the H3, please just use the H3 driver.

Nope.

It has a different PLL (PLL_ISP) at different address, and some
different muxes.

>
> Thanks!
> Maxime
>
> --
> Maxime Ripard, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [PATCH 1/5] arm: sunxi: add support for V3s SoC
From: Icenowy Zheng @ 2017-01-11 19:40 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Stephen Boyd, Linus Walleij,
	linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-clk-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
In-Reply-To: <20170110180926.ilqtygttpfgbetvu@lukather>



11.01.2017, 02:09, "Maxime Ripard" <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>:
> On Tue, Jan 03, 2017 at 11:16:25PM +0800, Icenowy Zheng wrote:
>>  Allwinner V3s is a low-end single-core Cortex-A7 SoC, with 64MB
>>  integrated DRAM, and several peripherals.
>>
>>  Signed-off-by: Icenowy Zheng <icenowy-ymACFijhrKM@public.gmane.org>
>>  ---
>>   Documentation/arm/sunxi/README | 4 ++++
>>   arch/arm/mach-sunxi/sunxi.c | 1 +
>>   2 files changed, 5 insertions(+)
>>
>>  diff --git a/Documentation/arm/sunxi/README b/Documentation/arm/sunxi/README
>>  index cd0243302bc1..91ec8f2055be 100644
>>  --- a/Documentation/arm/sunxi/README
>>  +++ b/Documentation/arm/sunxi/README
>>  @@ -67,6 +67,10 @@ SunXi family
>>           + Datasheet
>>             http://dl.linux-sunxi.org/H3/Allwinner_H3_Datasheet_V1.0.pdf
>>
>>  + - Allwinner V3s (sun8i)
>>  + + Datasheet
>>  + https://www.goprawn.com/forum/allwinner-cams/783-allwinner-v3s-soc-datasheet
>>  +
>
> Please don't put random links in there, but at least something that we
> know will be there in a couple of weeks/monthes/years

Is http://linux-sunxi.org/File:Allwinner_V3s_Datasheet_V1.0.pdf acceptable?

>
> Thanks,
> Maxime
>
> --
> Maxime Ripard, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox