Linux I2C development
 help / color / mirror / Atom feed
* [PATCHv1] hwmon: Add tc654 driver
From: Chris Packham @ 2016-10-06 21:36 UTC (permalink / raw)
  To: linux, linux-hwmon
  Cc: iwamoto, Joshua.Scott, Chris Packham, Kevin Tsai, Wolfram Sang,
	Rob Herring, Mark Rutland, Jean Delvare, linux-i2c, devicetree,
	linux-kernel

Add support for the tc654 and tc655 fan controllers from Microchip.

http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf

Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
---

Hi Gunter,
    
I realise this isn't using the new hwmon registration API. This is
essentially a forward port from an older kernel. I can attempt a
conversion as a follow up patch but I loose the ability to actually test
the driver.

 .../devicetree/bindings/i2c/trivial-devices.txt    |   2 +
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/tc654.c                              | 532 +++++++++++++++++++++
 4 files changed, 546 insertions(+)
 create mode 100644 drivers/hwmon/tc654.c

diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
index 1416c6a0d2cd..833fb9f133d3 100644
--- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt
+++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
@@ -122,6 +122,8 @@ microchip,mcp4662-502	Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem
 microchip,mcp4662-103	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k)
 microchip,mcp4662-503	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k)
 microchip,mcp4662-104	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k)
+microchip,tc654		PWM Fan Speed Controller With Fan Fault Detection
+microchip,tc655		PWM Fan Speed Controller With Fan Fault Detection
 national,lm63		Temperature sensor with integrated fan control
 national,lm75		I2C TEMP SENSOR
 national,lm80		Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d2c75c..8681bc65cde5 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -907,6 +907,17 @@ config SENSORS_MCP3021
 	  This driver can also be built as a module.  If so, the module
 	  will be called mcp3021.
 
+config SENSORS_TC654
+	tristate "Microchip TC654/TC655 and compatibles"
+	depends on I2C
+	help
+	  If you say yes here you get support for TC654 and TC655.
+	  The TC654 and TC655 are PWM mode fan speed controllers with
+	  FanSense technology for use with brushless DC fans.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called tc654.
+
 config SENSORS_MENF21BMC_HWMON
 	tristate "MEN 14F021P00 BMC Hardware Monitoring"
 	depends on MFD_MENF21BMC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba17460..c651f0f1d047 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -122,6 +122,7 @@ obj-$(CONFIG_SENSORS_MAX6697)	+= max6697.o
 obj-$(CONFIG_SENSORS_MAX31790)	+= max31790.o
 obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
 obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
+obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
new file mode 100644
index 000000000000..31e5f065183f
--- /dev/null
+++ b/drivers/hwmon/tc654.c
@@ -0,0 +1,532 @@
+/*
+ * tc654.c - Linux kernel modules for fan speed controller
+ *
+ * Copyright (C) 2016 Allied Telesis Labs NZ
+ *
+ * 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/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/jiffies.h>
+#include <linux/util_macros.h>
+
+enum tc654_regs {
+	TC654_REG_RPM1 = 0x00,	/* RPM Output 1 */
+	TC654_REG_RPM2 = 0x01,	/* RPM Output 2 */
+	TC654_REG_FAN_FAULT1 = 0x02,	/* Fan Fault 1 Threshold */
+	TC654_REG_FAN_FAULT2 = 0x03,	/* Fan Fault 2 Threshold */
+	TC654_REG_CONFIG = 0x04,	/* Configuration */
+	TC654_REG_STATUS = 0x05,	/* Status */
+	TC654_REG_DUTY_CYCLE = 0x06,	/* Fan Speed Duty Cycle */
+	TC654_REG_MFR_ID = 0x07,	/* Manufacturer Identification */
+	TC654_REG_VER_ID = 0x08,	/* Version Identification */
+};
+
+/* Macros to easily index the registers */
+#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx))
+#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx))
+
+/* Config register bits */
+#define TC654_REG_CONFIG_FFCLR 0x80	/* Fan Fault Clear */
+#define TC654_REG_CONFIG_RES 0x40	/* Resolution Selection */
+#define TC654_REG_CONFIG_DUTYC 0x20	/* Duty Cycle Control Method */
+#define TC654_REG_CONFIG_F2PPR 0x18	/* Fan 2 Pulses Per Rotation */
+#define TC654_REG_CONFIG_F1PPR 0x06	/* Fan 1 Pulses Per Rotation */
+#define TC654_REG_CONFIG_SDM 0x01	/* Shutdown Mode */
+
+/* Status register bits */
+#define TC654_REG_STATUS_OTF 0x20	/* Over-Temperature Fault Condition */
+#define TC654_REG_STATUS_R2CO 0x10	/* RPM2 Counter Overflow */
+#define TC654_REG_STATUS_R1CO 0x08	/* RPM1 Counter Overflow */
+#define TC654_REG_STATUS_VSTAT 0x04	/* V IN Input Status */
+#define TC654_REG_STATUS_F2F 0x02	/* Fan 2 Fault */
+#define TC654_REG_STATUS_F1F 0x01	/* Fan 1 Fault */
+
+/* RPM resolution for RPM Output registers */
+#define TC654_HIGH_RPM_RESOLUTION 25	/* 25 RPM resolution */
+#define TC654_LOW_RPM_RESOLUTION 50	/* 50 RPM resolution */
+
+/* Convert to the fan fault RPM threshold from register value */
+#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50)	/* 50 RPM resolution */
+
+/* Convert to register value from the fan fault RPM threshold */
+#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff)
+
+/* Register data is read (and cached) at most once per second. */
+#define TC654_UPDATE_INTERVAL	HZ
+
+struct tc654_data {
+	struct i2c_client *client;
+	struct device *hwmon_dev;
+
+	/* update mutex */
+	struct mutex update_lock;
+
+	/* tc654 register cache */
+	bool valid;
+	unsigned long last_updated;	/* in jiffies */
+
+	u8 rpm_output[2];	/* The fan RPM data for fans 1 and 2 is then
+				 * written to registers RPM1 and RPM2
+				 */
+	u8 fan_fault[2];	/* The Fan Fault Threshold Registers are used to
+				 * set the fan fault threshold levels for fan 1
+				 * and fan 2
+				 */
+	u8 config;	/* The Configuration Register is an 8-bit read/
+			 * writable multi-function control register
+			 *   7: Fan Fault Clear
+			 *      1 = Clear Fan Fault
+			 *      0 = Normal Operation (default)
+			 *   6: Resolution Selection for RPM Output Registers
+			 *      RPM Output Registers (RPM1 and RPM2) will be
+			 *      set for
+			 *      1 = 25 RPM (9-bit) resolution
+			 *      0 = 50 RPM (8-bit) resolution (default)
+			 *   5: Duty Cycle Control Method
+			 *      The V OUT duty cycle will be controlled via
+			 *      1 = the SMBus interface.
+			 *      0 = via the V IN analog input pin. (default)
+			 * 4,3: Fan 2 Pulses Per Rotation
+			 *      00 = 1
+			 *      01 = 2 (default)
+			 *      10 = 4
+			 *      11 = 8
+			 * 2,1: Fan 1 Pulses Per Rotation
+			 *      00 = 1
+			 *      01 = 2 (default)
+			 *      10 = 4
+			 *      11 = 8
+			 *   0: Shutdown Mode
+			 *      1 = Shutdown mode.
+			 *      0 = Normal operation. (default)
+			 */
+	u8 status;	/* The Status register provides all the information
+			 * about what is going on within the TC654/TC655
+			 * devices.
+			 * 7,6: Unimplemented, Read as '0'
+			 *   5: Over-Temperature Fault Condition
+			 *      1 = Over-Temperature condition has occurred
+			 *      0 = Normal operation. V IN is less than 2.6V
+			 *   4: RPM2 Counter Overflow
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   3: RPM1 Counter Overflow
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   2: V IN Input Status
+			 *      1 = V IN is open
+			 *      0 = Normal operation. voltage present at V IN
+			 *   1: Fan 2 Fault
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   0: Fan 1 Fault
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 */
+	u8 duty_cycle;	/* The DUTY_CYCLE register is a 4-bit read/
+			 * writable register used to control the duty
+			 * cycle of the V OUT output.
+			 */
+};
+
+/* helper to grab and cache data, at most one time per second */
+static struct tc654_data *tc654_update_client(struct device *dev)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	int ret = 0;
+
+	mutex_lock(&data->update_lock);
+	if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) &&
+	    likely(data->valid))
+		goto out;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0));
+	if (ret < 0)
+		goto out;
+	data->rpm_output[0] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1));
+	if (ret < 0)
+		goto out;
+	data->rpm_output[1] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0));
+	if (ret < 0)
+		goto out;
+	data->fan_fault[0] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1));
+	if (ret < 0)
+		goto out;
+	data->fan_fault[1] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG);
+	if (ret < 0)
+		goto out;
+	data->config = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS);
+	if (ret < 0)
+		goto out;
+	data->status = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE);
+	if (ret < 0)
+		goto out;
+	data->duty_cycle = ret;
+
+	data->last_updated = jiffies;
+	data->valid = true;
+out:
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)		/* upon error, encode it in return value */
+		data = ERR_PTR(ret);
+
+	return data;
+}
+
+/*
+ * sysfs attributes
+ */
+
+static ssize_t show_fan(struct device *dev, struct device_attribute *da,
+			char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	int val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (data->config & TC654_REG_CONFIG_RES)
+		val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION;
+	else
+		val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_fan_min(struct device *dev, struct device_attribute *da,
+			    char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	return sprintf(buf, "%d\n",
+		       TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr]));
+}
+
+static ssize_t set_fan_min(struct device *dev, struct device_attribute *da,
+			   const char *buf, size_t count)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	long val;
+
+	if (kstrtol(buf, 10, &val))
+		return -EINVAL;
+	if (val < 0 || val > 255)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+
+	data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val);
+	i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr),
+				  data->fan_fault[nr]);
+
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da,
+			      char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	int val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (nr == 0)
+		val = (data->status & TC654_REG_STATUS_F1F) ? 1 : 0;
+	else
+		val = (data->status & TC654_REG_STATUS_F2F) ? 1 : 0;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 };
+
+static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da,
+			      char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	u8 val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	val = (1 << ((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03));
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da,
+			     const char *buf, size_t count)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	u8 config;
+	long val;
+
+	if (kstrtol(buf, 10, &val))
+		return -EINVAL;
+
+	switch (val) {
+	case 1:
+		config = 0;
+		break;
+	case 2:
+		config = 1;
+		break;
+	case 4:
+		config = 2;
+		break;
+	case 8:
+		config = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&data->update_lock);
+
+	data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]);
+	data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]);
+	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_pwm_mode(struct device *dev,
+				       struct device_attribute *da, char *buf)
+{
+	struct tc654_data *data = tc654_update_client(dev);
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	return sprintf(buf, "%d\n", data->config & TC654_REG_CONFIG_DUTYC);
+}
+
+static ssize_t set_pwm_mode(struct device *dev,
+				      struct device_attribute *da,
+				      const char *buf, size_t count)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	long val;
+
+	if (kstrtol(buf, 10, &val))
+		return -EINVAL;
+
+	if (val != 0 && val != 1)
+
+	mutex_lock(&data->update_lock);
+
+	if (val)
+		data->config |= TC654_REG_CONFIG_DUTYC;
+	else
+		data->config &= ~TC654_REG_CONFIG_DUTYC;
+
+	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static const int tc654_pwm_map[16] = { 76, 88, 100, 112, 124, 141, 147, 171,
+		183, 195, 207, 219, 231, 243, 255 };
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
+			       char *buf)
+{
+	struct tc654_data *data = tc654_update_client(dev);
+	int pwm;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (data->config & TC654_REG_CONFIG_SDM)
+		pwm = 0;
+	else
+		pwm = tc654_pwm_map[data->duty_cycle];
+
+	return sprintf(buf, "%d\n", pwm);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
+			      const char *buf, size_t count)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	long val;
+
+	if (kstrtol(buf, 10, &val))
+		return -EINVAL;
+
+	val = clamp_val(val, 0, 255);
+
+	if (val == 0)
+		data->config |= TC654_REG_CONFIG_SDM;
+	else
+		data->config &= ~TC654_REG_CONFIG_SDM;
+
+	data->duty_cycle = find_closest(val, tc654_pwm_map,
+					ARRAY_SIZE(tc654_pwm_map));
+
+	mutex_lock(&data->update_lock);
+
+	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+
+	i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE,
+				  data->duty_cycle);
+
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
+			  set_fan_min, 0);
+static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
+			  set_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
+			  set_fan_pulses, 0);
+static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
+			  set_fan_pulses, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO,
+			  show_pwm_mode, set_pwm_mode, 0);
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm,
+			  set_pwm, 0);
+
+/* Driver data */
+static struct attribute *tc654_attrs[] = {
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan1_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan2_pulses.dev_attr.attr,
+	&sensor_dev_attr_pwm1_mode.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(tc654);
+
+/*
+ * device probe and removal
+ */
+
+static int tc654_probe(struct i2c_client *client,
+		       const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct tc654_data *data;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	data->hwmon_dev =
+	    hwmon_device_register_with_groups(dev, client->name, data,
+					      tc654_groups);
+	if (IS_ERR(data->hwmon_dev))
+		return PTR_ERR(data->hwmon_dev);
+
+	dev_info(dev, "%s: sensor '%s'\n",
+		 dev_name(data->hwmon_dev), client->name);
+
+	return 0;
+}
+
+static int tc654_remove(struct i2c_client *client)
+{
+	struct tc654_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id tc654_dt_match[] = {
+	{.compatible = "microchip,tc654"},
+	{.compatible = "microchip,tc655"},
+	{},
+};
+#endif
+
+static const struct i2c_device_id tc654_id[] = {
+	{"tc654", 0},
+	{"tc655", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, tc654_id);
+
+static struct i2c_driver tc654_driver = {
+	.driver = {
+		   .name = "tc654",
+		   .owner = THIS_MODULE,
+		   .of_match_table = of_match_ptr(tc654_dt_match),
+		   },
+	.probe = tc654_probe,
+	.remove = tc654_remove,
+	.id_table = tc654_id,
+};
+
+module_i2c_driver(tc654_driver);
+
+MODULE_AUTHOR("Allied Telesis Labs");
+MODULE_DESCRIPTION("Microchip TC654/TC655 driver");
+MODULE_LICENSE("GPL");
-- 
2.10.0.479.g7c56b16


^ permalink raw reply related

* i2c-xgene-slimpro.c : dma_buffer overrun
From: Phil Endecott @ 2016-10-06 21:22 UTC (permalink / raw)
  To: linux-i2c; +Cc: Feng Kan

Dear Experts,

Can someone please have a look at this declaration in i2c-xgene-slimpro.c:

struct slimpro_i2c_dev {
        struct i2c_adapter adapter;
        struct device *dev;
        struct mbox_chan *mbox_chan;
        struct mbox_client mbox_client;
        struct completion rd_complete;
        u8 dma_buffer[I2C_SMBUS_BLOCK_MAX];
        u32 *resp_msg;
};

It seems to me that the size of dma_buffer should be I2C_SMBUS_BLOCK_MAX+2, 
to match this in uapi/linux/i2c.h:

union i2c_smbus_data {
        __u8 byte;
        __u16 word;
        __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
                               /* and one more for user-space compatibility */
};

My debugging shows that 33 is often passed as readlen to dma_map_single() 
in slimpro_i2c_blkrd().  So either the buffer needs to be larger, or the 
caller is misbehaving.  Certainly increasing the size of dma_buffer by 2 
fixes crashes that I have been seeing.

This is the first time I've ever looked at anything in the i2c system so 
I may have completely misunderstood everything....

(If I'm right, is anyone aware of any static analysis method that should have 
been able to detect this?)


Thanks,  Phil.

^ permalink raw reply

* Re: [PATCHv1] hwmon: Add tc654 driver
From: Guenter Roeck @ 2016-10-06 22:57 UTC (permalink / raw)
  To: Chris Packham
  Cc: linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	iwamoto-sK/J6oeM9AhgKrQr38906+qrae++aQT8,
	Joshua.Scott-6g8wRflRTwXFdCa3tKVlE6U/zSkkHjvu, Kevin Tsai,
	Wolfram Sang, Rob Herring, Mark Rutland, Jean Delvare,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Fri, Oct 07, 2016 at 10:36:47AM +1300, Chris Packham wrote:
> Add support for the tc654 and tc655 fan controllers from Microchip.
> 
> http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf
> 
> Signed-off-by: Chris Packham <chris.packham-6g8wRflRTwXFdCa3tKVlE6U/zSkkHjvu@public.gmane.org>
> ---
> 
> Hi Gunter,
>     
> I realise this isn't using the new hwmon registration API. This is
> essentially a forward port from an older kernel. I can attempt a
> conversion as a follow up patch but I loose the ability to actually test
> the driver.
> 
No problem.

Can you send me a register dump for this chip ?

>  .../devicetree/bindings/i2c/trivial-devices.txt    |   2 +
>  drivers/hwmon/Kconfig                              |  11 +
>  drivers/hwmon/Makefile                             |   1 +
>  drivers/hwmon/tc654.c                              | 532 +++++++++++++++++++++

Please also add Documentation/hwmon/tc654.

>  4 files changed, 546 insertions(+)
>  create mode 100644 drivers/hwmon/tc654.c
> 
> diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
> index 1416c6a0d2cd..833fb9f133d3 100644
> --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt
> +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
> @@ -122,6 +122,8 @@ microchip,mcp4662-502	Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem
>  microchip,mcp4662-103	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k)
>  microchip,mcp4662-503	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k)
>  microchip,mcp4662-104	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k)
> +microchip,tc654		PWM Fan Speed Controller With Fan Fault Detection
> +microchip,tc655		PWM Fan Speed Controller With Fan Fault Detection
>  national,lm63		Temperature sensor with integrated fan control
>  national,lm75		I2C TEMP SENSOR
>  national,lm80		Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 45cef3d2c75c..8681bc65cde5 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -907,6 +907,17 @@ config SENSORS_MCP3021
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called mcp3021.
>  
> +config SENSORS_TC654
> +	tristate "Microchip TC654/TC655 and compatibles"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for TC654 and TC655.
> +	  The TC654 and TC655 are PWM mode fan speed controllers with
> +	  FanSense technology for use with brushless DC fans.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called tc654.
> +
>  config SENSORS_MENF21BMC_HWMON
>  	tristate "MEN 14F021P00 BMC Hardware Monitoring"
>  	depends on MFD_MENF21BMC
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index aecf4ba17460..c651f0f1d047 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -122,6 +122,7 @@ obj-$(CONFIG_SENSORS_MAX6697)	+= max6697.o
>  obj-$(CONFIG_SENSORS_MAX31790)	+= max31790.o
>  obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
>  obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
> +obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
>  obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
>  obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
>  obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
> diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
> new file mode 100644
> index 000000000000..31e5f065183f
> --- /dev/null
> +++ b/drivers/hwmon/tc654.c
> @@ -0,0 +1,532 @@
> +/*
> + * tc654.c - Linux kernel modules for fan speed controller
> + *
> + * Copyright (C) 2016 Allied Telesis Labs NZ
> + *
> + * 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/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/err.h>
> +#include <linux/mutex.h>
> +#include <linux/jiffies.h>
> +#include <linux/util_macros.h>
> +
> +enum tc654_regs {
> +	TC654_REG_RPM1 = 0x00,	/* RPM Output 1 */
> +	TC654_REG_RPM2 = 0x01,	/* RPM Output 2 */
> +	TC654_REG_FAN_FAULT1 = 0x02,	/* Fan Fault 1 Threshold */
> +	TC654_REG_FAN_FAULT2 = 0x03,	/* Fan Fault 2 Threshold */
> +	TC654_REG_CONFIG = 0x04,	/* Configuration */
> +	TC654_REG_STATUS = 0x05,	/* Status */
> +	TC654_REG_DUTY_CYCLE = 0x06,	/* Fan Speed Duty Cycle */
> +	TC654_REG_MFR_ID = 0x07,	/* Manufacturer Identification */
> +	TC654_REG_VER_ID = 0x08,	/* Version Identification */
> +};
> +
> +/* Macros to easily index the registers */
> +#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx))
> +#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx))
> +
> +/* Config register bits */
> +#define TC654_REG_CONFIG_FFCLR 0x80	/* Fan Fault Clear */
> +#define TC654_REG_CONFIG_RES 0x40	/* Resolution Selection */
> +#define TC654_REG_CONFIG_DUTYC 0x20	/* Duty Cycle Control Method */
> +#define TC654_REG_CONFIG_F2PPR 0x18	/* Fan 2 Pulses Per Rotation */
> +#define TC654_REG_CONFIG_F1PPR 0x06	/* Fan 1 Pulses Per Rotation */
> +#define TC654_REG_CONFIG_SDM 0x01	/* Shutdown Mode */
> +
> +/* Status register bits */
> +#define TC654_REG_STATUS_OTF 0x20	/* Over-Temperature Fault Condition */
> +#define TC654_REG_STATUS_R2CO 0x10	/* RPM2 Counter Overflow */
> +#define TC654_REG_STATUS_R1CO 0x08	/* RPM1 Counter Overflow */
> +#define TC654_REG_STATUS_VSTAT 0x04	/* V IN Input Status */
> +#define TC654_REG_STATUS_F2F 0x02	/* Fan 2 Fault */
> +#define TC654_REG_STATUS_F1F 0x01	/* Fan 1 Fault */
> +

Many of those defines are not used. Please drop the unused ones
(or use them).

> +/* RPM resolution for RPM Output registers */
> +#define TC654_HIGH_RPM_RESOLUTION 25	/* 25 RPM resolution */
> +#define TC654_LOW_RPM_RESOLUTION 50	/* 50 RPM resolution */
> +
> +/* Convert to the fan fault RPM threshold from register value */
> +#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50)	/* 50 RPM resolution */
> +
> +/* Convert to register value from the fan fault RPM threshold */
> +#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff)
> +
> +/* Register data is read (and cached) at most once per second. */
> +#define TC654_UPDATE_INTERVAL	HZ
> +
Another possibility would be to use regmap and let it cache the
static registers.

> +struct tc654_data {
> +	struct i2c_client *client;
> +	struct device *hwmon_dev;
> +
> +	/* update mutex */
> +	struct mutex update_lock;
> +
> +	/* tc654 register cache */
> +	bool valid;
> +	unsigned long last_updated;	/* in jiffies */
> +
> +	u8 rpm_output[2];	/* The fan RPM data for fans 1 and 2 is then
> +				 * written to registers RPM1 and RPM2
> +				 */
> +	u8 fan_fault[2];	/* The Fan Fault Threshold Registers are used to
> +				 * set the fan fault threshold levels for fan 1
> +				 * and fan 2
> +				 */
> +	u8 config;	/* The Configuration Register is an 8-bit read/
> +			 * writable multi-function control register
> +			 *   7: Fan Fault Clear
> +			 *      1 = Clear Fan Fault
> +			 *      0 = Normal Operation (default)
> +			 *   6: Resolution Selection for RPM Output Registers
> +			 *      RPM Output Registers (RPM1 and RPM2) will be
> +			 *      set for
> +			 *      1 = 25 RPM (9-bit) resolution
> +			 *      0 = 50 RPM (8-bit) resolution (default)
> +			 *   5: Duty Cycle Control Method
> +			 *      The V OUT duty cycle will be controlled via
> +			 *      1 = the SMBus interface.
> +			 *      0 = via the V IN analog input pin. (default)
> +			 * 4,3: Fan 2 Pulses Per Rotation
> +			 *      00 = 1
> +			 *      01 = 2 (default)
> +			 *      10 = 4
> +			 *      11 = 8
> +			 * 2,1: Fan 1 Pulses Per Rotation
> +			 *      00 = 1
> +			 *      01 = 2 (default)
> +			 *      10 = 4
> +			 *      11 = 8
> +			 *   0: Shutdown Mode
> +			 *      1 = Shutdown mode.
> +			 *      0 = Normal operation. (default)
> +			 */
> +	u8 status;	/* The Status register provides all the information
> +			 * about what is going on within the TC654/TC655
> +			 * devices.
> +			 * 7,6: Unimplemented, Read as '0'
> +			 *   5: Over-Temperature Fault Condition
> +			 *      1 = Over-Temperature condition has occurred
> +			 *      0 = Normal operation. V IN is less than 2.6V
> +			 *   4: RPM2 Counter Overflow
> +			 *      1 = Fault condition
> +			 *      0 = Normal operation
> +			 *   3: RPM1 Counter Overflow
> +			 *      1 = Fault condition
> +			 *      0 = Normal operation
> +			 *   2: V IN Input Status
> +			 *      1 = V IN is open
> +			 *      0 = Normal operation. voltage present at V IN
> +			 *   1: Fan 2 Fault
> +			 *      1 = Fault condition
> +			 *      0 = Normal operation
> +			 *   0: Fan 1 Fault
> +			 *      1 = Fault condition
> +			 *      0 = Normal operation
> +			 */
> +	u8 duty_cycle;	/* The DUTY_CYCLE register is a 4-bit read/
> +			 * writable register used to control the duty
> +			 * cycle of the V OUT output.
> +			 */
> +};
> +
> +/* helper to grab and cache data, at most one time per second */
> +static struct tc654_data *tc654_update_client(struct device *dev)
> +{
> +	struct tc654_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	int ret = 0;
> +
> +	mutex_lock(&data->update_lock);
> +	if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) &&
> +	    likely(data->valid))
> +		goto out;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0));
> +	if (ret < 0)
> +		goto out;
> +	data->rpm_output[0] = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1));
> +	if (ret < 0)
> +		goto out;
> +	data->rpm_output[1] = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0));
> +	if (ret < 0)
> +		goto out;
> +	data->fan_fault[0] = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1));
> +	if (ret < 0)
> +		goto out;
> +	data->fan_fault[1] = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG);
> +	if (ret < 0)
> +		goto out;
> +	data->config = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS);
> +	if (ret < 0)
> +		goto out;
> +	data->status = ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE);
> +	if (ret < 0)
> +		goto out;
> +	data->duty_cycle = ret;
> +
> +	data->last_updated = jiffies;
> +	data->valid = true;
> +out:
> +	mutex_unlock(&data->update_lock);
> +
> +	if (ret < 0)		/* upon error, encode it in return value */
> +		data = ERR_PTR(ret);
> +
> +	return data;
> +}
> +
> +/*
> + * sysfs attributes
> + */
> +
> +static ssize_t show_fan(struct device *dev, struct device_attribute *da,
> +			char *buf)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = tc654_update_client(dev);
> +	int val;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	if (data->config & TC654_REG_CONFIG_RES)
> +		val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION;
> +	else
> +		val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION;
> +
> +	return sprintf(buf, "%d\n", val);
> +}
> +
> +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da,
> +			    char *buf)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = tc654_update_client(dev);
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	return sprintf(buf, "%d\n",
> +		       TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr]));
> +}
> +
> +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da,
> +			   const char *buf, size_t count)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	long val;
> +
> +	if (kstrtol(buf, 10, &val))
> +		return -EINVAL;
kstrtoul ?

> +	if (val < 0 || val > 255)

It would be better to use clamp_val() here.

Is that really the speed in rpm (0 .. 255) ? Seems unlikely.
Looking into the code, the limit should probably be 255 * 50.

> +		return -EINVAL;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val);
> +	i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr),
> +				  data->fan_fault[nr]);
> +

Error check (here and elsewhere) ?

> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da,
> +			      char *buf)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = tc654_update_client(dev);
> +	int val;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	if (nr == 0)
> +		val = (data->status & TC654_REG_STATUS_F1F) ? 1 : 0;

		val = !!(data->status & TC654_REG_STATUS_F1F);

> +	else
> +		val = (data->status & TC654_REG_STATUS_F2F) ? 1 : 0;
> +
> +	return sprintf(buf, "%d\n", val);
> +}
> +
> +static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 };
> +
> +static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da,
> +			      char *buf)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = tc654_update_client(dev);
> +	u8 val;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	val = (1 << ((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03));

Extra ( ). Also, you could use BIT() here.

> +	return sprintf(buf, "%d\n", val);
> +}
> +
> +static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da,
> +			     const char *buf, size_t count)
> +{
> +	int nr = to_sensor_dev_attr(da)->index;
> +	struct tc654_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	u8 config;
> +	long val;
> +
> +	if (kstrtol(buf, 10, &val))
> +		return -EINVAL;
> +
> +	switch (val) {
> +	case 1:
> +		config = 0;
> +		break;
> +	case 2:
> +		config = 1;
> +		break;
> +	case 4:
> +		config = 2;
> +		break;
> +	case 8:
> +		config = 3;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&data->update_lock);
> +
> +	data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]);
> +	data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]);
> +	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
> +
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static ssize_t show_pwm_mode(struct device *dev,
> +				       struct device_attribute *da, char *buf)

Please align continuation lines with '('.

> +{
> +	struct tc654_data *data = tc654_update_client(dev);
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	return sprintf(buf, "%d\n", data->config & TC654_REG_CONFIG_DUTYC);
> +}
> +
> +static ssize_t set_pwm_mode(struct device *dev,
> +				      struct device_attribute *da,
> +				      const char *buf, size_t count)
> +{
> +	struct tc654_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	long val;
> +
> +	if (kstrtol(buf, 10, &val))

kstrtoul ?

> +		return -EINVAL;
> +
> +	if (val != 0 && val != 1)
> +
The error return is missing here.

> +	mutex_lock(&data->update_lock);
> +
> +	if (val)
> +		data->config |= TC654_REG_CONFIG_DUTYC;
> +	else
> +		data->config &= ~TC654_REG_CONFIG_DUTYC;
> +
This is different to the intended ABI. The ABI expects the _output_ to be
configured with this attribute. However, for this chip the output is always
pwm, and the bit determines if the pwm output is controlled via register
or via the Vin analog input.

It is ok with me, but it should be documented.

> +	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
> +
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static const int tc654_pwm_map[16] = { 76, 88, 100, 112, 124, 141, 147, 171,
> +		183, 195, 207, 219, 231, 243, 255 };
> +
> +static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
> +			       char *buf)
> +{
> +	struct tc654_data *data = tc654_update_client(dev);
> +	int pwm;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	if (data->config & TC654_REG_CONFIG_SDM)
> +		pwm = 0;
> +	else
> +		pwm = tc654_pwm_map[data->duty_cycle];
> +
> +	return sprintf(buf, "%d\n", pwm);
> +}
> +
> +static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
> +			      const char *buf, size_t count)
> +{
> +	struct tc654_data *data = dev_get_drvdata(dev);
> +	struct i2c_client *client = data->client;
> +	long val;
> +
> +	if (kstrtol(buf, 10, &val))

kstrtoul ?

> +		return -EINVAL;
> +
> +	val = clamp_val(val, 0, 255);

Please use strict value checking here. pwm value range is well defined.

> +
> +	if (val == 0)
> +		data->config |= TC654_REG_CONFIG_SDM;
> +	else
> +		data->config &= ~TC654_REG_CONFIG_SDM;
> +
> +	data->duty_cycle = find_closest(val, tc654_pwm_map,
> +					ARRAY_SIZE(tc654_pwm_map));
> +
> +	mutex_lock(&data->update_lock);
> +
> +	i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
> +
> +	i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE,
> +				  data->duty_cycle);
> +
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
> +			  set_fan_min, 0);
> +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
> +			  set_fan_min, 1);
> +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
> +			  set_fan_pulses, 0);
> +static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
> +			  set_fan_pulses, 1);
> +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO,
> +			  show_pwm_mode, set_pwm_mode, 0);
> +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm,
> +			  set_pwm, 0);
> +
> +/* Driver data */
> +static struct attribute *tc654_attrs[] = {
> +	&sensor_dev_attr_fan1_input.dev_attr.attr,
> +	&sensor_dev_attr_fan2_input.dev_attr.attr,
> +	&sensor_dev_attr_fan1_min.dev_attr.attr,
> +	&sensor_dev_attr_fan2_min.dev_attr.attr,
> +	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
> +	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
> +	&sensor_dev_attr_fan1_pulses.dev_attr.attr,
> +	&sensor_dev_attr_fan2_pulses.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_mode.dev_attr.attr,
> +	&sensor_dev_attr_pwm1.dev_attr.attr,
> +	NULL
> +};
> +
> +ATTRIBUTE_GROUPS(tc654);
> +
> +/*
> + * device probe and removal
> + */
> +
> +static int tc654_probe(struct i2c_client *client,
> +		       const struct i2c_device_id *id)
> +{
> +	struct device *dev = &client->dev;
> +	struct tc654_data *data;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> +		return -ENODEV;
> +
> +	data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	i2c_set_clientdata(client, data);
> +	mutex_init(&data->update_lock);
> +
> +	data->hwmon_dev =
> +	    hwmon_device_register_with_groups(dev, client->name, data,
> +					      tc654_groups);

Please use the devm_ function (and you can drop the remove function).

> +	if (IS_ERR(data->hwmon_dev))
> +		return PTR_ERR(data->hwmon_dev);
> +
> +	dev_info(dev, "%s: sensor '%s'\n",
> +		 dev_name(data->hwmon_dev), client->name);

dev_info() already displays the device name.

> +
> +	return 0;
> +}
> +
> +static int tc654_remove(struct i2c_client *client)
> +{
> +	struct tc654_data *data = i2c_get_clientdata(client);
> +
> +	hwmon_device_unregister(data->hwmon_dev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id tc654_dt_match[] = {
> +	{.compatible = "microchip,tc654"},
> +	{.compatible = "microchip,tc655"},
> +	{},
> +};
> +#endif
> +
Not needed.

> +static const struct i2c_device_id tc654_id[] = {
> +	{"tc654", 0},
> +	{"tc655", 0},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, tc654_id);
> +
> +static struct i2c_driver tc654_driver = {
> +	.driver = {
> +		   .name = "tc654",
> +		   .owner = THIS_MODULE,
> +		   .of_match_table = of_match_ptr(tc654_dt_match),

Not needed.

> +		   },
> +	.probe = tc654_probe,
> +	.remove = tc654_remove,
> +	.id_table = tc654_id,
> +};
> +
> +module_i2c_driver(tc654_driver);
> +
> +MODULE_AUTHOR("Allied Telesis Labs");
> +MODULE_DESCRIPTION("Microchip TC654/TC655 driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.10.0.479.g7c56b16
> 
--
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: [PATCHv1] hwmon: Add tc654 driver
From: Chris Packham @ 2016-10-06 23:13 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: linux-hwmon@vger.kernel.org, Masahiko Iwamoto, Joshua Scott,
	Kevin Tsai, Wolfram Sang, Rob Herring, Mark Rutland, Jean Delvare,
	linux-i2c@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <20161006225702.GA9365@roeck-us.net>

On 10/07/2016 11:57 AM, Guenter Roeck wrote:
> On Fri, Oct 07, 2016 at 10:36:47AM +1300, Chris Packham wrote:
>> Add support for the tc654 and tc655 fan controllers from Microchip.
>>
>> http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf
>>
>> Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
>> ---
>>
>> Hi Gunter,
>>
>> I realise this isn't using the new hwmon registration API. This is
>> essentially a forward port from an older kernel. I can attempt a
>> conversion as a follow up patch but I loose the ability to actually test
>> the driver.
>>
> No problem.
>
> Can you send me a register dump for this chip ?

Here you go

[root@awplus /flash]# i2cdump /dev/i2c-0 0x1b
Dump of device 0x1b on /dev/i2c-0
  0: 76 00 0a 0a 2a 02 00 54 00 b3 31 01 ff ff ff 00  v...*..T..1.....

I'll read through the rest of your comments and reply with a v2 later.



^ permalink raw reply

* [PATCHv2] hwmon: Add tc654 driver
From: Chris Packham @ 2016-10-07  1:38 UTC (permalink / raw)
  To: linux, linux-hwmon
  Cc: iwamoto, Joshua.Scott, Chris Packham, Kevin Tsai, Wolfram Sang,
	Rob Herring, Mark Rutland, Jean Delvare, Jonathan Corbet,
	linux-i2c, devicetree, linux-kernel, linux-doc
In-Reply-To: <20161006225702.GA9365@roeck-us.net>

Add support for the tc654 and tc655 fan controllers from Microchip.

http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf

Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
---

Changes in v2:
- Add Documentation/hwmon/tc654
- Incorporate most of the review comments from Guenter. Additional error
  handling is added. Unused/unnecessary code is removed. I decided not
  to go down the regmap path yet. I may circle back to it when I look at
  using regmap in the adm9240 driver.

 .../devicetree/bindings/i2c/trivial-devices.txt    |   2 +
 Documentation/hwmon/tc654                          |  26 ++
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/tc654.c                              | 513 +++++++++++++++++++++
 5 files changed, 553 insertions(+)
 create mode 100644 Documentation/hwmon/tc654
 create mode 100644 drivers/hwmon/tc654.c

diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
index 1416c6a0d2cd..833fb9f133d3 100644
--- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt
+++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
@@ -122,6 +122,8 @@ microchip,mcp4662-502	Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem
 microchip,mcp4662-103	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k)
 microchip,mcp4662-503	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k)
 microchip,mcp4662-104	Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k)
+microchip,tc654		PWM Fan Speed Controller With Fan Fault Detection
+microchip,tc655		PWM Fan Speed Controller With Fan Fault Detection
 national,lm63		Temperature sensor with integrated fan control
 national,lm75		I2C TEMP SENSOR
 national,lm80		Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
diff --git a/Documentation/hwmon/tc654 b/Documentation/hwmon/tc654
new file mode 100644
index 000000000000..93796c5c7e79
--- /dev/null
+++ b/Documentation/hwmon/tc654
@@ -0,0 +1,26 @@
+Kernel driver tc654
+===================
+
+Supported chips:
+  * Microship TC654 and TC655
+    Prefix: 'tc654'
+    Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf
+
+Authors:
+        Chris Packham <chris.packham@alliedtelesis.co.nz>
+        Masahiko Iwamoto <iwamoto@allied-telesis.co.jp>
+
+Description
+-----------
+This driver implements support for the Microchip TC654 and TC655.
+
+The TC654 used the 2-wire interface compatible with the SMBUS 2.0
+specification. The TC654 has two (2) inputs for measuring fan RPM and
+one (1) PWM output which can be used for fan control.
+
+Configuration Notes
+-------------------
+Ordinarily the pwm1_mode ABI is used for controlling the pwm output
+mode.  However, for this chip the output is always pwm, and the
+pwm1_mode determines if the pwm output is controlled via the pwm1 value
+or via the Vin analog input.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d2c75c..8681bc65cde5 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -907,6 +907,17 @@ config SENSORS_MCP3021
 	  This driver can also be built as a module.  If so, the module
 	  will be called mcp3021.
 
+config SENSORS_TC654
+	tristate "Microchip TC654/TC655 and compatibles"
+	depends on I2C
+	help
+	  If you say yes here you get support for TC654 and TC655.
+	  The TC654 and TC655 are PWM mode fan speed controllers with
+	  FanSense technology for use with brushless DC fans.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called tc654.
+
 config SENSORS_MENF21BMC_HWMON
 	tristate "MEN 14F021P00 BMC Hardware Monitoring"
 	depends on MFD_MENF21BMC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba17460..c651f0f1d047 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -122,6 +122,7 @@ obj-$(CONFIG_SENSORS_MAX6697)	+= max6697.o
 obj-$(CONFIG_SENSORS_MAX31790)	+= max31790.o
 obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
 obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
+obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
new file mode 100644
index 000000000000..cba31cbd3383
--- /dev/null
+++ b/drivers/hwmon/tc654.c
@@ -0,0 +1,513 @@
+/*
+ * tc654.c - Linux kernel modules for fan speed controller
+ *
+ * Copyright (C) 2016 Allied Telesis Labs NZ
+ *
+ * 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/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/jiffies.h>
+#include <linux/util_macros.h>
+
+enum tc654_regs {
+	TC654_REG_RPM1 = 0x00,	/* RPM Output 1 */
+	TC654_REG_RPM2 = 0x01,	/* RPM Output 2 */
+	TC654_REG_FAN_FAULT1 = 0x02,	/* Fan Fault 1 Threshold */
+	TC654_REG_FAN_FAULT2 = 0x03,	/* Fan Fault 2 Threshold */
+	TC654_REG_CONFIG = 0x04,	/* Configuration */
+	TC654_REG_STATUS = 0x05,	/* Status */
+	TC654_REG_DUTY_CYCLE = 0x06,	/* Fan Speed Duty Cycle */
+	TC654_REG_MFR_ID = 0x07,	/* Manufacturer Identification */
+	TC654_REG_VER_ID = 0x08,	/* Version Identification */
+};
+
+/* Macros to easily index the registers */
+#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx))
+#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx))
+
+/* Config register bits */
+#define TC654_REG_CONFIG_RES 0x40	/* Resolution Selection */
+#define TC654_REG_CONFIG_DUTYC 0x20	/* Duty Cycle Control Method */
+#define TC654_REG_CONFIG_SDM 0x01	/* Shutdown Mode */
+
+/* Status register bits */
+#define TC654_REG_STATUS_F2F 0x02	/* Fan 2 Fault */
+#define TC654_REG_STATUS_F1F 0x01	/* Fan 1 Fault */
+
+/* RPM resolution for RPM Output registers */
+#define TC654_HIGH_RPM_RESOLUTION 25	/* 25 RPM resolution */
+#define TC654_LOW_RPM_RESOLUTION 50	/* 50 RPM resolution */
+
+/* Convert to the fan fault RPM threshold from register value */
+#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50)	/* 50 RPM resolution */
+
+/* Convert to register value from the fan fault RPM threshold */
+#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff)
+
+/* Register data is read (and cached) at most once per second. */
+#define TC654_UPDATE_INTERVAL	HZ
+
+struct tc654_data {
+	struct i2c_client *client;
+	struct device *hwmon_dev;
+
+	/* update mutex */
+	struct mutex update_lock;
+
+	/* tc654 register cache */
+	bool valid;
+	unsigned long last_updated;	/* in jiffies */
+
+	u8 rpm_output[2];	/* The fan RPM data for fans 1 and 2 is then
+				 * written to registers RPM1 and RPM2
+				 */
+	u8 fan_fault[2];	/* The Fan Fault Threshold Registers are used to
+				 * set the fan fault threshold levels for fan 1
+				 * and fan 2
+				 */
+	u8 config;	/* The Configuration Register is an 8-bit read/
+			 * writable multi-function control register
+			 *   7: Fan Fault Clear
+			 *      1 = Clear Fan Fault
+			 *      0 = Normal Operation (default)
+			 *   6: Resolution Selection for RPM Output Registers
+			 *      RPM Output Registers (RPM1 and RPM2) will be
+			 *      set for
+			 *      1 = 25 RPM (9-bit) resolution
+			 *      0 = 50 RPM (8-bit) resolution (default)
+			 *   5: Duty Cycle Control Method
+			 *      The V OUT duty cycle will be controlled via
+			 *      1 = the SMBus interface.
+			 *      0 = via the V IN analog input pin. (default)
+			 * 4,3: Fan 2 Pulses Per Rotation
+			 *      00 = 1
+			 *      01 = 2 (default)
+			 *      10 = 4
+			 *      11 = 8
+			 * 2,1: Fan 1 Pulses Per Rotation
+			 *      00 = 1
+			 *      01 = 2 (default)
+			 *      10 = 4
+			 *      11 = 8
+			 *   0: Shutdown Mode
+			 *      1 = Shutdown mode.
+			 *      0 = Normal operation. (default)
+			 */
+	u8 status;	/* The Status register provides all the information
+			 * about what is going on within the TC654/TC655
+			 * devices.
+			 * 7,6: Unimplemented, Read as '0'
+			 *   5: Over-Temperature Fault Condition
+			 *      1 = Over-Temperature condition has occurred
+			 *      0 = Normal operation. V IN is less than 2.6V
+			 *   4: RPM2 Counter Overflow
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   3: RPM1 Counter Overflow
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   2: V IN Input Status
+			 *      1 = V IN is open
+			 *      0 = Normal operation. voltage present at V IN
+			 *   1: Fan 2 Fault
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 *   0: Fan 1 Fault
+			 *      1 = Fault condition
+			 *      0 = Normal operation
+			 */
+	u8 duty_cycle;	/* The DUTY_CYCLE register is a 4-bit read/
+			 * writable register used to control the duty
+			 * cycle of the V OUT output.
+			 */
+};
+
+/* helper to grab and cache data, at most one time per second */
+static struct tc654_data *tc654_update_client(struct device *dev)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	int ret = 0;
+
+	mutex_lock(&data->update_lock);
+	if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) &&
+	    likely(data->valid))
+		goto out;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0));
+	if (ret < 0)
+		goto out;
+	data->rpm_output[0] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1));
+	if (ret < 0)
+		goto out;
+	data->rpm_output[1] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0));
+	if (ret < 0)
+		goto out;
+	data->fan_fault[0] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1));
+	if (ret < 0)
+		goto out;
+	data->fan_fault[1] = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG);
+	if (ret < 0)
+		goto out;
+	data->config = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS);
+	if (ret < 0)
+		goto out;
+	data->status = ret;
+
+	ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE);
+	if (ret < 0)
+		goto out;
+	data->duty_cycle = ret;
+
+	data->last_updated = jiffies;
+	data->valid = true;
+out:
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)		/* upon error, encode it in return value */
+		data = ERR_PTR(ret);
+
+	return data;
+}
+
+/*
+ * sysfs attributes
+ */
+
+static ssize_t show_fan(struct device *dev, struct device_attribute *da,
+			char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	int val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (data->config & TC654_REG_CONFIG_RES)
+		val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION;
+	else
+		val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_fan_min(struct device *dev, struct device_attribute *da,
+			    char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	return sprintf(buf, "%d\n",
+		       TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr]));
+}
+
+static ssize_t set_fan_min(struct device *dev, struct device_attribute *da,
+			   const char *buf, size_t count)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 10, &val))
+		return -EINVAL;
+
+	clamp_val(val, 0, 12750);
+
+	mutex_lock(&data->update_lock);
+
+	data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val);
+	ret = i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr),
+				  data->fan_fault[nr]);
+
+	mutex_unlock(&data->update_lock);
+	return ret < 0 ? ret : count;
+}
+
+static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da,
+			      char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	int val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (nr == 0)
+		val = !!(data->status & TC654_REG_STATUS_F1F);
+	else
+		val = !!(data->status & TC654_REG_STATUS_F2F);
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 };
+
+static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da,
+			      char *buf)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = tc654_update_client(dev);
+	u8 val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	val = BIT((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03);
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da,
+			     const char *buf, size_t count)
+{
+	int nr = to_sensor_dev_attr(da)->index;
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	u8 config;
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 10, &val))
+		return -EINVAL;
+
+	switch (val) {
+	case 1:
+		config = 0;
+		break;
+	case 2:
+		config = 1;
+		break;
+	case 4:
+		config = 2;
+		break;
+	case 8:
+		config = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&data->update_lock);
+
+	data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]);
+	data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]);
+	ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+
+	mutex_unlock(&data->update_lock);
+	return ret < 0 ? ret : count;
+}
+
+static ssize_t show_pwm_mode(struct device *dev,
+			     struct device_attribute *da, char *buf)
+{
+	struct tc654_data *data = tc654_update_client(dev);
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	return sprintf(buf, "%d\n", data->config & TC654_REG_CONFIG_DUTYC);
+}
+
+static ssize_t set_pwm_mode(struct device *dev,
+			    struct device_attribute *da,
+			    const char *buf, size_t count)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 10, &val))
+		return -EINVAL;
+
+	if (val != 0 && val != 1)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+
+	if (val)
+		data->config |= TC654_REG_CONFIG_DUTYC;
+	else
+		data->config &= ~TC654_REG_CONFIG_DUTYC;
+
+	ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+
+	mutex_unlock(&data->update_lock);
+	return ret < 0 ? ret : count;
+}
+
+static const int tc654_pwm_map[16] = { 76, 88, 100, 112, 124, 141, 147, 171,
+		183, 195, 207, 219, 231, 243, 255 };
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
+			       char *buf)
+{
+	struct tc654_data *data = tc654_update_client(dev);
+	int pwm;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (data->config & TC654_REG_CONFIG_SDM)
+		pwm = 0;
+	else
+		pwm = tc654_pwm_map[data->duty_cycle];
+
+	return sprintf(buf, "%d\n", pwm);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
+			      const char *buf, size_t count)
+{
+	struct tc654_data *data = dev_get_drvdata(dev);
+	struct i2c_client *client = data->client;
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 10, &val))
+		return -EINVAL;
+	if (val > 255)
+		return -EINVAL;
+
+	if (val == 0)
+		data->config |= TC654_REG_CONFIG_SDM;
+	else
+		data->config &= ~TC654_REG_CONFIG_SDM;
+
+	data->duty_cycle = find_closest(val, tc654_pwm_map,
+					ARRAY_SIZE(tc654_pwm_map));
+
+	mutex_lock(&data->update_lock);
+
+	ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
+	if (ret < 0)
+		goto out;
+
+	ret = i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE,
+				  data->duty_cycle);
+	if (ret < 0)
+		goto out;
+
+out:
+	mutex_unlock(&data->update_lock);
+	return ret < 0 ? ret : count;
+}
+
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
+			  set_fan_min, 0);
+static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
+			  set_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
+			  set_fan_pulses, 0);
+static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
+			  set_fan_pulses, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO,
+			  show_pwm_mode, set_pwm_mode, 0);
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm,
+			  set_pwm, 0);
+
+/* Driver data */
+static struct attribute *tc654_attrs[] = {
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan1_pulses.dev_attr.attr,
+	&sensor_dev_attr_fan2_pulses.dev_attr.attr,
+	&sensor_dev_attr_pwm1_mode.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(tc654);
+
+/*
+ * device probe and removal
+ */
+
+static int tc654_probe(struct i2c_client *client,
+		       const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct tc654_data *data;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	data->hwmon_dev =
+	    devm_hwmon_device_register_with_groups(dev, client->name, data,
+						   tc654_groups);
+	if (IS_ERR(data->hwmon_dev))
+		return PTR_ERR(data->hwmon_dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id tc654_id[] = {
+	{"tc654", 0},
+	{"tc655", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, tc654_id);
+
+static struct i2c_driver tc654_driver = {
+	.driver = {
+		   .name = "tc654",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = tc654_probe,
+	.id_table = tc654_id,
+};
+
+module_i2c_driver(tc654_driver);
+
+MODULE_AUTHOR("Allied Telesis Labs");
+MODULE_DESCRIPTION("Microchip TC654/TC655 driver");
+MODULE_LICENSE("GPL");
-- 
2.10.0.479.g7c56b16


^ permalink raw reply related

* [PULL REQUEST] i2c for 4.9
From: Wolfram Sang @ 2016-10-07  9:32 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: linux-i2c, linux-kernel

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

Linus,

here is the 4.9 pull request from I2C including:

* centralized error messages when registering to the core
* improved lockdep annotations to prevent false positives
* DT support for muxes, gates, and arbitrators
* bus speeds can now be obtained from ACPI
* i2c-octeon got refactored and now supports ThunderX SoCs, too
* i2c-tegra and i2c-designware got a bigger bunch of updates
* a couple of standard driver fixes and improvements

Please pull.

Thanks,

   Wolfram


The following changes since commit fa8410b355251fd30341662a40ac6b22d3e38468:

  Linux 4.8-rc3 (2016-08-21 16:14:10 -0700)

are available in the git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git i2c/for-4.9

for you to fetch changes up to 662786a5429c3a992c6f884a647ee32424822358:

  i2c: axxia: disable clks in case of failure in probe (2016-09-24 11:26:55 +0200)

----------------------------------------------------------------
Alexey Khoroshilov (1):
      i2c: axxia: disable clks in case of failure in probe

Andy Shevchenko (2):
      i2c: core: put literals on one line in dev_*() calls
      i2c: i801: Add support for Kaby Lake PCH-H

Bartosz Golaszewski (5):
      eeprom: at24: check if the chip is functional in probe()
      i2c: export i2c_adapter_depth()
      lockdep: make MAX_LOCKDEP_SUBCLASSES unconditionally visible
      i2c: add a warning to i2c_adapter_depth()
      gpio: pca953x: fix an incorrect lockdep warning

David Wu (2):
      i2c: rk3x: Fix sparse warning
      i2c: rk3x: Fix variable 'min_total_ns' unused warning

Dmitry Bazhenov (2):
      i2c: octeon: Fix set SCL recovery function
      i2c: octeon: Avoid sending STOP during recovery

Geert Uytterhoeven (1):
      i2c: shmobile: Use ARCH_SHMOBILE instead of SUPERH

Jan Glauber (10):
      i2c: octeon: Rename driver to prepare for split
      i2c: octeon: Split the driver into two parts
      i2c: thunderx: Add i2c driver for ThunderX SOC
      i2c: thunderx: Add SMBUS alert support
      i2c: octeon,thunderx: Move register offsets to struct
      i2c: octeon: Sort include files alphabetically
      i2c: octeon: Use booleon values for booleon variables
      i2c: octeon: thunderx: Add MAINTAINERS entry
      i2c: octeon: Fix high-level controller status check
      i2c: octeon: thunderx: Limit register access retries

Jarkko Nikula (3):
      i2c: core: Cleanup I2C ACPI namespace, take 2
      i2c: core: Add function for finding the bus speed from ACPI, take 2
      i2c: designware: Find bus speed from ACPI

Jon Hunter (10):
      i2c: tegra: Fix lines over 80 characters
      i2c: tegra: Use BIT macro
      i2c: tegra: Fix missing blank lines after declarations
      i2c: tegra: Add missing new line characters
      i2c: tegra: Remove non device-tree support
      i2c: tegra: Use device name for adapter name
      i2c: tegra: Simplify I2C resume
      i2c: tegra: Add runtime power-management support
      i2c: tegra: Add pinctrl support
      i2c: tegra: Fix assignment of boolean variables

José Roberto de Souza (1):
      i2c: designware: wait for disable/enable only if necessary

Lucas De Marchi (3):
      i2c: designware: add common functions for locking
      i2c: designware: detect when dynamic tar update is possible
      i2c: designware: do not disable adapter after transfer

Masahiro Yamada (4):
      i2c: uniphier: avoid WARN_ON() of clk_disable() in failure path
      i2c: uniphier-f: avoid WARN_ON() of clk_disable() in failure path
      i2c: uniphier-f: set the adapter to master mode when probing
      i2c: uniphier-f: fix misdetection of incomplete STOP condition

Mika Westerberg (1):
      i2c / ACPI: Do not touch an I2C device if it belongs to another adapter

Neil Armstrong (1):
      i2c: meson: add gxbb compatible string

Peter Rosin (10):
      i2c: add i2c_trylock_bus wrapper, use it
      dt-bindings: i2c: add support for 'i2c-mux' subnode
      dt-bindings: i2c: add support for 'i2c-arb' subnode
      dt-bindings: i2c: add support for 'i2c-gate' subnode
      dt-bindings: i2c: add bindings for nxp,pca9541
      i2c: mux: add support for 'i2c-mux', 'i2c-arb' and 'i2c-gate' DT subnodes
      i2c: mux: inform the i2c mux core about how it is used
      i2c: pca9541: add device tree binding
      i2c: pca954x: add device tree binding
      i2c: move locking operations to their own struct

Shardar Shariff Md (5):
      i2c: tegra: use readl_poll_timeout after config_load reg programmed
      i2c: tegra: If fifo flush fails return error
      i2c: tegra: add separate function for config_load programing
      i2c: tegra: use atomic poll function during configuration
      i2c: tegra: proper handling of error cases

Ulrich Hecht (1):
      i2c: rcar: add support for r8a7796 (R-Car M3-W)

Weifeng Voon (5):
      i2c: designware: Move clk_freq into struct dw_i2c_dev
      i2c: designware: get fast plus and high speed *CNT configuration
      i2c: designware: Enable fast mode plus
      i2c: designware: set the common config before the if else
      i2c: designware: Enable high speed mode

Wolfram Sang (4):
      i2c: don't print error when adding adapter fails
      i2c: uniphier{-f}: don't print error when adding adapter fails
      Merge branch 'i2c-mux-dt-3' of https://github.com/peda-r/i2c-mux into i2c/for-4.9
      gpio: pca953x: variable 'id' was used twice

Yang Li (1):
      i2c: imx: make bus recovery through pinctrl optional

 .../bindings/i2c/i2c-arb-gpio-challenge.txt        |    8 +-
 Documentation/devicetree/bindings/i2c/i2c-arb.txt  |   35 +
 Documentation/devicetree/bindings/i2c/i2c-gate.txt |   41 +
 .../devicetree/bindings/i2c/i2c-meson.txt          |    2 +-
 Documentation/devicetree/bindings/i2c/i2c-mux.txt  |   23 +-
 Documentation/devicetree/bindings/i2c/i2c-rcar.txt |    1 +
 .../devicetree/bindings/i2c/nxp,pca9541.txt        |   29 +
 MAINTAINERS                                        |   10 +
 drivers/gpio/gpio-pca953x.c                        |   16 +-
 drivers/gpu/drm/drm_dp_helper.c                    |   10 +-
 drivers/i2c/busses/Kconfig                         |   13 +-
 drivers/i2c/busses/Makefile                        |    3 +
 drivers/i2c/busses/i2c-amd756.c                    |    5 +-
 drivers/i2c/busses/i2c-at91.c                      |    2 -
 drivers/i2c/busses/i2c-axxia.c                     |    8 +-
 drivers/i2c/busses/i2c-bcm-iproc.c                 |    8 +-
 drivers/i2c/busses/i2c-bcm-kona.c                  |    4 +-
 drivers/i2c/busses/i2c-bfin-twi.c                  |    4 +-
 drivers/i2c/busses/i2c-brcmstb.c                   |    4 +-
 drivers/i2c/busses/i2c-cadence.c                   |    4 +-
 drivers/i2c/busses/i2c-cpm.c                       |    4 +-
 drivers/i2c/busses/i2c-cros-ec-tunnel.c            |    4 +-
 drivers/i2c/busses/i2c-davinci.c                   |    4 +-
 drivers/i2c/busses/i2c-designware-core.c           |  196 +++-
 drivers/i2c/busses/i2c-designware-core.h           |   12 +
 drivers/i2c/busses/i2c-designware-platdrv.c        |   43 +-
 drivers/i2c/busses/i2c-diolan-u2c.c                |    4 +-
 drivers/i2c/busses/i2c-dln2.c                      |    4 +-
 drivers/i2c/busses/i2c-efm32.c                     |    1 -
 drivers/i2c/busses/i2c-exynos5.c                   |    4 +-
 drivers/i2c/busses/i2c-hix5hd2.c                   |    4 +-
 drivers/i2c/busses/i2c-i801.c                      |    5 +-
 drivers/i2c/busses/i2c-ibm_iic.c                   |    4 +-
 drivers/i2c/busses/i2c-img-scb.c                   |    4 +-
 drivers/i2c/busses/i2c-imx.c                       |   35 +-
 drivers/i2c/busses/i2c-isch.c                      |    4 +-
 drivers/i2c/busses/i2c-ismt.c                      |    4 +-
 drivers/i2c/busses/i2c-jz4780.c                    |    4 +-
 drivers/i2c/busses/i2c-lpc2k.c                     |    4 +-
 drivers/i2c/busses/i2c-meson.c                     |    2 +-
 drivers/i2c/busses/i2c-mpc.c                       |    4 +-
 drivers/i2c/busses/i2c-mt65xx.c                    |    4 +-
 drivers/i2c/busses/i2c-mxs.c                       |    1 -
 drivers/i2c/busses/i2c-nforce2.c                   |    1 -
 drivers/i2c/busses/i2c-nomadik.c                   |    4 +-
 drivers/i2c/busses/i2c-ocores.c                    |    4 +-
 .../i2c/busses/{i2c-octeon.c => i2c-octeon-core.c} | 1084 ++++++--------------
 drivers/i2c/busses/i2c-octeon-core.h               |  216 ++++
 drivers/i2c/busses/i2c-octeon-platdrv.c            |  286 ++++++
 drivers/i2c/busses/i2c-omap.c                      |    4 +-
 drivers/i2c/busses/i2c-piix4.c                     |    1 -
 drivers/i2c/busses/i2c-pmcmsp.c                    |    4 +-
 drivers/i2c/busses/i2c-pnx.c                       |    4 +-
 drivers/i2c/busses/i2c-puv3.c                      |    5 +-
 drivers/i2c/busses/i2c-pxa.c                       |    4 +-
 drivers/i2c/busses/i2c-rcar.c                      |    5 +-
 drivers/i2c/busses/i2c-riic.c                      |    4 +-
 drivers/i2c/busses/i2c-rk3x.c                      |    9 +-
 drivers/i2c/busses/i2c-s3c2410.c                   |    1 -
 drivers/i2c/busses/i2c-sh7760.c                    |    4 +-
 drivers/i2c/busses/i2c-sh_mobile.c                 |    1 -
 drivers/i2c/busses/i2c-sirf.c                      |    4 +-
 drivers/i2c/busses/i2c-st.c                        |    4 +-
 drivers/i2c/busses/i2c-stu300.c                    |    5 +-
 drivers/i2c/busses/i2c-tegra.c                     |  279 +++--
 drivers/i2c/busses/i2c-thunderx-pcidrv.c           |  259 +++++
 drivers/i2c/busses/i2c-uniphier-f.c                |  106 +-
 drivers/i2c/busses/i2c-uniphier.c                  |   78 +-
 drivers/i2c/busses/i2c-wmt.c                       |    4 +-
 drivers/i2c/busses/i2c-xgene-slimpro.c             |    1 -
 drivers/i2c/busses/i2c-xiic.c                      |    1 -
 drivers/i2c/busses/i2c-xlp9xx.c                    |    4 +-
 drivers/i2c/busses/i2c-xlr.c                       |    4 +-
 drivers/i2c/i2c-core.c                             |  272 +++--
 drivers/i2c/i2c-mux.c                              |   73 +-
 drivers/i2c/muxes/i2c-arb-gpio-challenge.c         |    2 +-
 drivers/i2c/muxes/i2c-mux-pca9541.c                |   11 +-
 drivers/i2c/muxes/i2c-mux-pca954x.c                |   46 +-
 drivers/misc/eeprom/at24.c                         |   15 +-
 include/linux/i2c-mux.h                            |    8 +-
 include/linux/i2c.h                                |   47 +-
 include/linux/lockdep.h                            |    4 +-
 82 files changed, 2121 insertions(+), 1332 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-arb.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-gate.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/nxp,pca9541.txt
 rename drivers/i2c/busses/{i2c-octeon.c => i2c-octeon-core.c} (56%)
 create mode 100644 drivers/i2c/busses/i2c-octeon-core.h
 create mode 100644 drivers/i2c/busses/i2c-octeon-platdrv.c
 create mode 100644 drivers/i2c/busses/i2c-thunderx-pcidrv.c

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

^ permalink raw reply

* Re: [PULL REQUEST] i2c for 4.9
From: Bartosz Golaszewski @ 2016-10-07 10:59 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: linux-i2c, LKML
In-Reply-To: <20161007093226.GA2058@katana>

2016-10-07 11:32 GMT+02:00 Wolfram Sang <wsa@the-dreams.de>:
> Linus,
>
> here is the 4.9 pull request from I2C including:
>
> * centralized error messages when registering to the core
> * improved lockdep annotations to prevent false positives
> * DT support for muxes, gates, and arbitrators
> * bus speeds can now be obtained from ACPI
> * i2c-octeon got refactored and now supports ThunderX SoCs, too
> * i2c-tegra and i2c-designware got a bigger bunch of updates
> * a couple of standard driver fixes and improvements
>
> Please pull.
>
> Thanks,
>
>    Wolfram
>
>
> The following changes since commit fa8410b355251fd30341662a40ac6b22d3e38468:
>
>   Linux 4.8-rc3 (2016-08-21 16:14:10 -0700)
>
> are available in the git repository at:
>
>   git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git i2c/for-4.9
>
> for you to fetch changes up to 662786a5429c3a992c6f884a647ee32424822358:
>
>   i2c: axxia: disable clks in case of failure in probe (2016-09-24 11:26:55 +0200)
>
> ----------------------------------------------------------------
> Alexey Khoroshilov (1):
>       i2c: axxia: disable clks in case of failure in probe
>
> Andy Shevchenko (2):
>       i2c: core: put literals on one line in dev_*() calls
>       i2c: i801: Add support for Kaby Lake PCH-H
>
> Bartosz Golaszewski (5):
>       eeprom: at24: check if the chip is functional in probe()
>       i2c: export i2c_adapter_depth()
>       lockdep: make MAX_LOCKDEP_SUBCLASSES unconditionally visible
>       i2c: add a warning to i2c_adapter_depth()
>       gpio: pca953x: fix an incorrect lockdep warning
>

Hi Wolfram,

I see you didn't pick up the follow-up patch ("gpio: pca953x: add a
comment explaining the need for a lockdep subclass"). Linus acked it
in the end and I'd really prefer for it to go into 4.9, as I don't
know if I'll have time to complete the solution for the lockdep issue
in the next development cycle. Without this explanation, the random
lockdep_set_subclass() call looks out of place.

Thanks,
Bartosz

^ permalink raw reply

* [PATCH 00/10] Introduce Juniper PTXPMB CPLD driver
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin

Add Juniper's PTXPMB FPGA CPLD driver. Those FPGAs
are present in Juniper's PTX series of routers.

The MFD driver provices watchdog/i2c/gpio/mtd devices.

There are full device tree binding documents for the
master mfd driver and for all slave drivers.

This patchset is against mainline as of today: v4.8-9431-g3477d16
and is dependent on the "Juniper prerequisites" and
"Juniper infrastructure" patchsets sent earlier.

Georgi Vlaev (5):
  mfd: ptxpmb-cpld: Add documentation for PTXPMB CPLD
  watchdog: ptxpmb-wdt: Add ptxpmb-wdt device tree bindings
  i2c: i2c-mux-ptxpmb-cpld: Add device tree bindings
  gpio: ptxpmb-cpld: Document bindings of PTXPMB's CPLD GPIO
  mtd: ngpmb_nvram: Add bindings for Juniper's ngpmb NVRAM

Guenter Roeck (4):
  mfd: Juniper PTXPMB CPLD Multi-function core driver
  watchdog: Add support for PTXPMB CPLD watchdog
  i2c/muxes: Juniper's PTXPMB CPLD I2C multiplexer
  gpio: ptxpmb-cpld: Add support for PTXPMB CPLD's GPIO

JawaharBalaji Thirumalaisamy (1):
  mtd: devices: Add driver for memory mapped NVRAM on FPC

 .../bindings/gpio/jnx,gpio-ptxpmb-cpld.txt         |  30 ++
 .../bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt       |  50 +++
 .../devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt    |  76 ++++
 .../devicetree/bindings/mtd/ngpmb-nvram.txt        |  22 ++
 .../bindings/watchdog/jnx-ptxpmb-wdt.txt           |  17 +
 drivers/gpio/Kconfig                               |  11 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-ptxpmb-cpld.c                    | 177 +++++++++
 drivers/i2c/muxes/Kconfig                          |  11 +
 drivers/i2c/muxes/Makefile                         |   1 +
 drivers/i2c/muxes/i2c-mux-ptxpmb.c                 | 299 +++++++++++++++
 drivers/mfd/Kconfig                                |  15 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/ptxpmb-cpld-core.c                     | 406 +++++++++++++++++++++
 drivers/mtd/devices/Kconfig                        |  11 +
 drivers/mtd/devices/Makefile                       |   1 +
 drivers/mtd/devices/jnx_pmb_nvram.c                | 191 ++++++++++
 drivers/watchdog/Kconfig                           |  12 +
 drivers/watchdog/Makefile                          |   1 +
 drivers/watchdog/ptxpmb_wdt.c                      | 283 ++++++++++++++
 include/linux/mfd/ptxpmb_cpld.h                    | 140 +++++++
 21 files changed, 1756 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-cpld.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt
 create mode 100644 Documentation/devicetree/bindings/mtd/ngpmb-nvram.txt
 create mode 100644 Documentation/devicetree/bindings/watchdog/jnx-ptxpmb-wdt.txt
 create mode 100644 drivers/gpio/gpio-ptxpmb-cpld.c
 create mode 100644 drivers/i2c/muxes/i2c-mux-ptxpmb.c
 create mode 100644 drivers/mfd/ptxpmb-cpld-core.c
 create mode 100644 drivers/mtd/devices/jnx_pmb_nvram.c
 create mode 100644 drivers/watchdog/ptxpmb_wdt.c
 create mode 100644 include/linux/mfd/ptxpmb_cpld.h

-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply

* [PATCH 01/10] mfd: Juniper PTXPMB CPLD Multi-function core driver
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

Add Juniper's PTXPMB FPGA CPLD driver. Those FPGAs
are present in Juniper's PTX series of routers.

There are two variants, the original which is found on the
PTXPMB_P2020, PTXPMB_P2020_SPMB based on a Freescale P2020 SoC,
and PTXPMB_P5040 based on a Freescale P5040 SoC.

The new variant NGPMB is present on a new line of x86 based
boards (currently only the Gladiator FPC).

Both variants provide a hardware watchdog, i2c mux and a
gpio block, with the i2c mux block being different.

Signed-off-by: Debjit Ghosh <dghosh@juniper.net>
Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
Signed-off-by: Tom Kavanagh <tkavanagh@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mfd/Kconfig             |  15 ++
 drivers/mfd/Makefile            |   1 +
 drivers/mfd/ptxpmb-cpld-core.c  | 406 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/ptxpmb_cpld.h | 140 ++++++++++++++
 4 files changed, 562 insertions(+)
 create mode 100644 drivers/mfd/ptxpmb-cpld-core.c
 create mode 100644 include/linux/mfd/ptxpmb_cpld.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2caf7e9..438666a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1340,6 +1340,21 @@ config TWL4030_POWER
 	  and load scripts controlling which resources are switched off/on
 	  or reset when a sleep, wakeup or warm reset event occurs.
 
+config MFD_JUNIPER_CPLD
+	tristate "Juniper PTX PMB CPLD"
+	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	select MFD_CORE
+	select I2C_MUX_PTXPMB
+	select GPIO_PTXPMB_CPLD
+	select JNX_PTXPMB_WDT
+	help
+	  Select this to enable the PTX PMB CPLD multi-function kernel driver
+	  for the applicable Juniper platforms.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "ptxpmb-cpld"
+
 config MFD_TWL4030_AUDIO
 	bool "TI TWL4030 Audio"
 	depends on TWL4030_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 2bf6a1a..62decc9 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -148,6 +148,7 @@ obj-$(CONFIG_AB3100_CORE)	+= ab3100-core.o
 obj-$(CONFIG_AB3100_OTP)	+= ab3100-otp.o
 obj-$(CONFIG_AB8500_DEBUG)	+= ab8500-debugfs.o
 obj-$(CONFIG_AB8500_GPADC)	+= ab8500-gpadc.o
+obj-$(CONFIG_MFD_JUNIPER_CPLD)	+= ptxpmb-cpld-core.o
 obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 # ab8500-core need to come after db8500-prcmu (which provides the channel)
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
diff --git a/drivers/mfd/ptxpmb-cpld-core.c b/drivers/mfd/ptxpmb-cpld-core.c
new file mode 100644
index 0000000..18e60a4
--- /dev/null
+++ b/drivers/mfd/ptxpmb-cpld-core.c
@@ -0,0 +1,406 @@
+/*
+ * Juniper PTX PMB CPLD multi-function core driver
+ *
+ * Copyright (C) 2012 Juniper Networks
+ *
+ * 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/delay.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/dmi.h>
+#include <linux/mfd/core.h>
+#include <linux/of_device.h>
+#include <linux/mfd/ptxpmb_cpld.h>
+#include <linux/jnx/jnx-subsys.h>
+#include <linux/jnx/board_ids.h>
+
+struct pmb_cpld_core {
+	struct device		*dev;
+	struct pmb_boot_cpld __iomem *cpld;
+	spinlock_t		lock;
+	int			irq;
+	wait_queue_head_t	wqh;
+};
+
+static const struct of_device_id pmb_cpld_of_ids[] = {
+	{ .compatible = "jnx,ptxpmb-cpld", .data = (void *)CPLD_TYPE_PTXPMB },
+	{ .compatible = "jnx,ngpmb-bcpld", .data = (void *)CPLD_TYPE_NGPMB },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pmb_cpld_of_ids);
+
+static struct dmi_system_id gld_2t_dmi_data[] = {
+	{
+		.ident = "Juniper Networks Gladiator 2T FPC",
+		.matches = {
+			    DMI_MATCH(DMI_SYS_VENDOR, "Juniper Networks Inc."),
+			    DMI_MATCH(DMI_PRODUCT_NAME, "0BF9"),
+			},
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(dmi, gld_2t_dmi_data);
+
+static struct dmi_system_id gld_3t_dmi_data[] = {
+	{
+		.ident = "Juniper Networks Gladiator 3T FPC",
+		.matches = {
+			    DMI_MATCH(DMI_SYS_VENDOR, "Juniper Networks Inc."),
+			    DMI_MATCH(DMI_PRODUCT_NAME, "0BFA"),
+			},
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(dmi, gld_3t_dmi_data);
+
+static int ptxpmb_cpld_get_master(void *data)
+{
+	struct pmb_cpld_core *cpld = data;
+	u8 s1;
+
+	s1 = ioread8(&cpld->cpld->i2c_host_sel) & CPLD_I2C_HOST_MSTR_MASK;
+
+	if ((s1 & CPLD_I2C_HOST0_MSTR) == CPLD_I2C_HOST0_MSTR)
+		return 0;
+
+	if ((s1 & CPLD_I2C_HOST1_MSTR) == CPLD_I2C_HOST1_MSTR)
+		return 1;
+
+	return -1;
+}
+
+static int ngpmb_cpld_get_master(void *data)
+{
+	struct pmb_cpld_core *cpld = data;
+
+	if (ioread8(&cpld->cpld->baseboard_status1) & NGPMB_MASTER_SELECT)
+		return 1;
+	else
+		return 0;
+}
+
+static irqreturn_t pmb_cpld_core_interrupt(int irq, void *dev_data)
+{
+	struct pmb_cpld_core *cpld = dev_data;
+
+	pr_info("pmb_cpld_core_interrupt %d\n", irq);
+
+	spin_lock(&cpld->wqh.lock);
+
+	/* clear interrupt, wake up any handlers */
+	wake_up_locked(&cpld->wqh);
+
+	spin_unlock(&cpld->wqh.lock);
+
+	return IRQ_HANDLED;
+}
+
+static struct resource pmb_cpld_resources[] = {
+	{
+		.start	= 0,
+		.end	= sizeof(struct pmb_boot_cpld) - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+};
+
+static struct mfd_cell pmb_cpld_cells[] = {
+	{
+		.name = "jnx-ptxpmb-wdt",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,ptxpmb-wdt",
+	},
+	{
+		.name = "i2c-mux-ptxpmb-cpld",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,i2c-mux-ptxpmb-cpld",
+	},
+	{
+		.name = "gpio-ptxpmb-cpld",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,gpio-ptxpmb-cpld",
+	},
+};
+
+static struct mfd_cell ngpmb_cpld_cells[] = {
+	{
+		.name = "jnx-ptxpmb-wdt",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,ptxpmb-wdt",
+	},
+	{
+		.name = "i2c-mux-ngpmb-bcpld",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,i2c-mux-ngpmb-bcpld",
+	},
+	{
+		.name = "gpio-ptxpmb-cpld",
+		.num_resources = ARRAY_SIZE(pmb_cpld_resources),
+		.resources = pmb_cpld_resources,
+		.of_compatible = "jnx,gpio-ptxpmb-cpld",
+	},
+};
+
+static void cpld_ngpmb_init(struct pmb_cpld_core *cpld,
+			    struct jnx_chassis_info *chinfo,
+			    struct jnx_card_info *cinfo)
+{
+	u8 s1, s2, val, chassis;
+
+	s1 = ioread8(&cpld->cpld->baseboard_status1);
+	s2 = ioread8(&cpld->cpld->baseboard_status2);
+	chassis = (ioread8(&cpld->cpld->board.ngpmb.chassis_type)
+		   & NGPMB_CHASSIS_TYPE_MASK) >> NGPMB_CHASSIS_TYPE_LSB;
+
+	dev_info(cpld->dev, "Revision 0x%02X chassis type %s (0x%02X)\n",
+		 ioread8(&cpld->cpld->cpld_rev),
+		 chassis == NGPMB_CHASSIS_TYPE_POLARIS ? "PTX-1000" :
+		 chassis == NGPMB_CHASSIS_TYPE_HENDRICKS ? "PTX-3000" :
+		 "Unknown", chassis);
+
+	/* Only the Gladiator 2t/3t FPC */
+	if (dmi_check_system(gld_2t_dmi_data) ||
+	    dmi_check_system(gld_3t_dmi_data)) {
+		/* Take SAM FPGA out of reset */
+		val = ioread8(&cpld->cpld->gpio_2);
+		iowrite8(val | NGPMB_GPIO2_TO_BASEBRD_LSB, &cpld->cpld->gpio_2);
+		mdelay(10);
+	} else {
+		/*
+		 * Get the PAM FPGA out of reset,
+		 * and wait for 100ms as per HW manual
+		 */
+		val = ioread8(&cpld->cpld->reset);
+		iowrite8(val & ~NGPMB_PCIE_OTHER_RESET, &cpld->cpld->reset);
+		mdelay(100);
+	}
+
+	/* No Card / Chassis info needed in stand alone mode */
+	if (!(s1 & NGPMB_PMB_STANDALONE) || !(s1 & NGPMB_BASEBRD_STANDALONE))
+		return;
+
+	cinfo->type = JNX_BOARD_TYPE_FPC;
+	cinfo->slot = (s1 & NGPMB_BASEBRD_SLOT_MASK) >> NGPMB_BASEBRD_SLOT_LSB;
+
+	if (((s2 & NGPMB_BASEBRD_TYPE_MASK) >> NGPMB_BASEBRD_TYPE_LSB) !=
+	    NGPMB_BASEBRD_TYPE_MX) {
+		if (dmi_check_system(gld_2t_dmi_data))
+			cinfo->assembly_id = JNX_ID_GLD_2T_FPC;
+		else if (dmi_check_system(gld_3t_dmi_data))
+			cinfo->assembly_id = JNX_ID_GLD_3T_FPC;
+		else
+			cinfo->assembly_id = JNX_ID_POLARIS_MLC;
+	}
+
+	/*
+	 * Multi chassis configuration. These bits are not
+	 * valid for Gladiator.
+	 */
+	if (!(dmi_check_system(gld_2t_dmi_data) ||
+	      dmi_check_system(gld_3t_dmi_data))) {
+		if (ioread8(&cpld->cpld->board.ngpmb.sys_config) &
+		    NGPMB_SYS_CONFIG_MULTI_CHASSIS) {
+			chinfo->multichassis = 1;
+			chinfo->chassis_no =
+			ioread8(&cpld->cpld->board.ngpmb.chassis_id);
+		}
+	}
+
+	switch (chassis) {
+	case NGPMB_CHASSIS_TYPE_POLARIS:
+		chinfo->platform = JNX_PRODUCT_POLARIS;
+		break;
+	case NGPMB_CHASSIS_TYPE_HENDRICKS:
+		chinfo->platform = JNX_PRODUCT_HENDRICKS;
+		break;
+	default:
+		chinfo->platform = 0;
+		break;
+	};
+	chinfo->get_master = ngpmb_cpld_get_master;
+}
+
+static void cpld_ptxpmb_init(struct pmb_cpld_core *cpld,
+			     struct jnx_chassis_info *chinfo,
+			     struct jnx_card_info *cinfo)
+{
+	u8 s1, s2;
+
+	s1 = ioread8(&cpld->cpld->baseboard_status1);
+	s2 = ioread8(&cpld->cpld->baseboard_status2);
+
+	dev_info(cpld->dev, "Revision 0x%02x carrier type 0x%x [%s]\n",
+		 ioread8(&cpld->cpld->cpld_rev), s2 & 0x1f,
+		 (s1 & 0X3F) == 0X1F ? "standalone"
+				     : (s2 & 0x10) ? "FPC" : "SPMB");
+
+	if ((s1 & 0x3f) != 0x1f) {	/* not standalone */
+		cinfo->slot = s1 & 0x0f;
+		if (s2 & 0x10) {	/* fpc */
+			cinfo->type = JNX_BOARD_TYPE_FPC;
+			switch (s2 & 0x0f) {
+			case 0x00:	/* Sangria */
+				cinfo->assembly_id = JNX_ID_SNG_VDV_BASE_P2;
+				chinfo->platform = JNX_PRODUCT_SANGRIA;
+				break;
+			case 0x01:	/* Tiny */
+				chinfo->platform = JNX_PRODUCT_TINY;
+				break;
+			case 0x02:	/* Hercules */
+				chinfo->platform = JNX_PRODUCT_HERCULES;
+				break;
+			case 0x03:      /* Hendricks */
+				cinfo->assembly_id = JNX_ID_HENDRICKS_FPC_P2;
+				chinfo->platform = JNX_PRODUCT_HENDRICKS;
+				break;
+			default:	/* unknown */
+				break;
+			}
+		} else {		/* spmb */
+			cinfo->type = JNX_BOARD_TYPE_SPMB;
+			switch (s2 & 0x0f) {
+			case 0x00:	/* Sangria */
+				cinfo->assembly_id = JNX_ID_SNG_PMB;
+				chinfo->platform = JNX_PRODUCT_SANGRIA;
+				break;
+			default:	/* unknown */
+				break;
+			}
+		}
+	}
+	chinfo->get_master = ptxpmb_cpld_get_master;
+}
+
+static int pmb_cpld_core_probe(struct platform_device *pdev)
+{
+	static struct pmb_cpld_core *cpld;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct ptxpmb_mux_data *pdata = dev->platform_data;
+	int i, error, mfd_size;
+	int cpld_type = CPLD_TYPE_PTXPMB;
+	const struct of_device_id *match;
+	struct mfd_cell *mfd_cells;
+
+	struct jnx_chassis_info chinfo = {
+		.chassis_no = 0,
+		.multichassis = 0,
+		.master_data = NULL,
+		.platform = -1,
+		.get_master = NULL,
+	};
+	struct jnx_card_info cinfo = {
+		.type = JNX_BOARD_TYPE_UNKNOWN,
+		.slot = -1,
+		.assembly_id = -1,
+	};
+
+	cpld = devm_kzalloc(dev, sizeof(*cpld), GFP_KERNEL);
+	if (!cpld)
+		return -ENOMEM;
+
+	cpld->dev = dev;
+	dev_set_drvdata(dev, cpld);
+
+	if (pdata) {
+		cpld_type = pdata->cpld_type;
+	} else {
+		match = of_match_device(pmb_cpld_of_ids, dev);
+		if (match)
+			cpld_type = (int)(unsigned long)match->data;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	cpld->cpld = devm_ioremap_resource(dev, res);
+	if (IS_ERR(cpld->cpld))
+		return PTR_ERR(cpld->cpld);
+
+	chinfo.master_data = cpld;
+
+	cpld->irq = platform_get_irq(pdev, 0);
+	if (cpld->irq >= 0) {
+		error = devm_request_threaded_irq(dev, cpld->irq, NULL,
+						  pmb_cpld_core_interrupt,
+						  IRQF_TRIGGER_RISING |
+						  IRQF_ONESHOT,
+						  dev_name(dev), cpld);
+		if (error < 0)
+			return error;
+	}
+
+	spin_lock_init(&cpld->lock);
+	init_waitqueue_head(&cpld->wqh);
+
+	mfd_cells = pmb_cpld_cells;
+	mfd_size = ARRAY_SIZE(pmb_cpld_cells);
+
+	switch (cpld_type) {
+	case CPLD_TYPE_PTXPMB:
+		cpld_ptxpmb_init(cpld, &chinfo, &cinfo);
+		break;
+	case CPLD_TYPE_NGPMB:
+		cpld_ngpmb_init(cpld, &chinfo, &cinfo);
+		mfd_cells = ngpmb_cpld_cells;
+		mfd_size = ARRAY_SIZE(ngpmb_cpld_cells);
+		break;
+	}
+
+	if (pdata) {
+		for (i = 0; i < mfd_size; i++) {
+			mfd_cells[i].platform_data = pdata;
+			mfd_cells[i].pdata_size = sizeof(*pdata);
+		}
+	}
+
+	error = mfd_add_devices(dev, pdev->id, mfd_cells,
+				mfd_size, res, 0, NULL);
+	if (error < 0)
+		return error;
+
+	jnx_register_chassis(&chinfo);
+	jnx_register_local_card(&cinfo);
+
+	return 0;
+}
+
+static int pmb_cpld_core_remove(struct platform_device *pdev)
+{
+	jnx_unregister_local_card();
+	jnx_unregister_chassis();
+	mfd_remove_devices(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver pmb_cpld_core_driver = {
+	.probe		= pmb_cpld_core_probe,
+	.remove		= pmb_cpld_core_remove,
+	.driver		= {
+		.name	= "ptxpmb-cpld",
+		.of_match_table = pmb_cpld_of_ids,
+		.owner	= THIS_MODULE,
+	}
+};
+
+module_platform_driver(pmb_cpld_core_driver);
+
+MODULE_DESCRIPTION("Juniper PTX PMB CPLD Core Driver");
+MODULE_AUTHOR("Guenter Roeck <groeck@juniper.net>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ptxpmb-cpld");
diff --git a/include/linux/mfd/ptxpmb_cpld.h b/include/linux/mfd/ptxpmb_cpld.h
new file mode 100644
index 0000000..e44afb4
--- /dev/null
+++ b/include/linux/mfd/ptxpmb_cpld.h
@@ -0,0 +1,140 @@
+/*---------------------------------------------------------------------------
+ *
+ * ptxpmb_cpld_core.h
+ *     Copyright (c) 2012 Juniper Networks
+ *
+ *---------------------------------------------------------------------------
+ */
+
+#ifndef PTXPMB_CPLD_CORE_H
+#define PTXPMB_CPLD_CORE_H
+
+struct pmb_boot_cpld {
+	u8 cpld_rev;		/* 0x00 */
+	u8 reset;
+#define CPLD_MAIN_RESET		(1 << 0)
+#define CPLD_PHYCB_RESET	(1 << 1)
+#define CPLD_PHYSW_RESET	(1 << 2)	/* P2020 only	*/
+#define NGPMB_PCIE_OTHER_RESET	(1 << 3)	/* PAM reset on MLC */
+	u8 reset_reason;
+#define NGPMB_REASON_MON_A_FAIL	(1 << 0)
+#define NGPMB_REASON_WDT1	(1 << 1)
+#define NGPMB_REASON_WDT2	(1 << 2)
+#define NGPMB_REASON_WDT3	(1 << 3)
+#define NGPMB_REASON_WDT4	(1 << 4)
+#define NGPMB_REASON_RE_HRST	(1 << 5)
+#define NGPMB_REASON_PWR_ON	(1 << 6)
+#define NGPMB_REASON_RE_SRST	(1 << 7)
+	u8 control;
+#define CPLD_CONTROL_BOOTED_LED	(1 << 0)
+#define CPLD_CONTROL_WATCHDOG	(1 << 6)
+#define CPLD_CONTROL_RTC	(1 << 7)
+#define NGPMB_FLASH_SELECT	(1 << 4)
+#define NGPMB_FLASH_SWIZZ_ENA	(1 << 5)
+	u8 sys_timer_cnt;
+	u8 watchdog_hbyte;
+	u8 watchdog_lbyte;
+	u8 unused1[1];
+	u8 baseboard_status1;	/* 0x08 */
+#define NGPMB_PMB_STANDALONE	(1 << 0)
+#define NGPMB_MASTER_SELECT	(1 << 1)
+#define NGPMB_BASEBRD_STANDALONE (1 << 2)
+#define NGPMB_BASEBRD_SLOT_LSB	3
+#define NGPMB_BASEBRD_SLOT_MASK	0xF8
+	u8 baseboard_status2;
+#define NGPMB_BASEBRD_TYPE_LSB	5
+#define NGPMB_BASEBRD_TYPE_MASK	0xE0
+#define NGPMB_BASEBRD_TYPE_MX	0
+	u8 chassis_number;
+	u8 sys_config;
+	u8 i2c_group_sel;	/* 0x0c */
+	u8 i2c_group_en;
+	u8 unused2[4];
+	u8 timer_irq_st;	/* 0x12 */
+	u8 timer_irq_en;
+	u8 unused3[12];
+	u8 prog_jtag_control;	/* 0x20 */
+	u8 gp_reset1;		/* 0x21 */
+#define CPLD_GP_RST1_PCISW	(1 << 0)
+#define CPLD_GP_RST1_SAM	(1 << 1)
+#define CPLD_GP_RST1_BRCM	(1 << 2)
+	u8 gp_reset2;		/* 0x22 */
+	u8 phy_control;
+	u8 gpio_1;
+	u8 gpio_2;
+#define NGPMB_GPIO2_TO_BASEBRD_LSB	(1 << 3)
+#define NGPMB_I2C_GRP_SEL_LSB	0
+#define NGPMB_I2C_GRP_SEL_MASK	0x03
+	u8 thermal_status;
+	u8 i2c_host_sel;
+#define CPLD_I2C_HOST0_MSTR     0x09
+#define CPLD_I2C_HOST1_MSTR     0x06
+#define CPLD_I2C_HOST_MSTR_MASK 0x0f
+	u8 scratch[3];
+	u8 misc_status;
+	u8 i2c_bus_control;	/* 0x2c */
+	union {
+		struct {
+			u8 mezz_present;
+			u8 unused1[4];
+			u8 i2c_group_sel_dbg;	/* 0x31 */
+			u8 i2c_group_en_dbg;	/* 0x32 */
+			u8 i2c_group_sel_force;	/* 0x33 */
+			u8 i2c_group_en_force;	/* 0x34 */
+			u8 unused2[0x4b];
+		} p2020;
+		struct {
+			u8 hdk_minor_version;	/* 0x2d */
+			u8 hdk_feature_ind;
+			u8 hdk_pmb_srds_mode;
+			u8 hdk_pwr_fail_status;
+			u8 hdk_pmb_pwr_status;
+			u8 hdk_pmb_mezz_status;
+			u8 cpld_self_reset;	/* 0x33 */
+			u8 unused[0x4c];
+			u8 hdk_bcpld_rcw[80];
+		} p5020;
+		struct {
+			u8 unused[3];
+			u8 chassis_id;		/* 0x30 */
+			u8 chassis_type;	/* 0x31 */
+#define NGPMB_CHASSIS_TYPE_LSB		0
+#define NGPMB_CHASSIS_TYPE_MASK		0x0F
+#define NGPMB_CHASSIS_TYPE_POLARIS	0x0B
+#define NGPMB_CHASSIS_TYPE_HENDRICKS	0x09
+			u8 sys_config;		/* 0x32 */
+#define NGPMB_SYS_CONFIG_MULTI_CHASSIS	0x01
+		} ngpmb;
+		struct {
+			u8 nv_win;		/* 0x2d */
+			u8 nv_addr1;
+			u8 nv_addr2;
+			u8 nv_wr_data;
+			u8 nv_rd_data;
+			u8 nv_cmd;
+			u8 nv_done_bit;
+		} nvram;
+	} board;
+};
+
+#ifdef CONFIG_P2020_PTXPMB
+#define CPLD_PHY_RESET	(CPLD_PHYCB_RESET | CPLD_PHYSW_RESET)
+#else
+#define CPLD_PHY_RESET	CPLD_PHYCB_RESET
+#endif
+
+#define i2c_group_sel_force board.p2020.i2c_group_sel_force
+#define i2c_group_en_force board.p2020.i2c_group_en_force
+
+struct ptxpmb_mux_data {
+	int cpld_type;
+#define CPLD_TYPE_PTXPMB    0	/* SPMB / Sangria FPC / Hendricks FPC */
+#define CPLD_TYPE_NGPMB     1	/* MLC / Stout / Gladiator... */
+	int num_enable;		/* Number of I2C enable pins		*/
+	int num_channels;	/* Number of I2C channels used in a mux chip */
+	int parent_bus_num;	/* parent i2c bus number		*/
+	int base_bus_num;	/* 1st bus number, 0 if undefined	*/
+	bool use_force;		/* Use i2c force registers if true	*/
+};
+
+#endif /* PTXPMB_CPLD_CORE_H */
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 02/10] mfd: ptxpmb-cpld: Add documentation for PTXPMB CPLD
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add DT bindings document for the PTXPMB CPLD MFD device.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt    | 76 ++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt

diff --git a/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt b/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt
new file mode 100644
index 0000000..cc3cbb9
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-cpld.txt
@@ -0,0 +1,76 @@
+* Device tree bindings for Juniper's PTXPMB CPLD FPGA MFD driver
+
+The device supports a number I2C muxes, hardware watchdog and a gpio block.
+Those devices bindings are described in the jnx-i2c-mux-ptxpmb-cpld,
+jnx-ptxpmb-wdt and jnx-gpio-ptxpmb-cpld documents.
+
+Required properties:
+
+- compatible:		"jnx,ptxpmb-cpld", "jnx,ngpmb-bcpld". They differ
+			in the i2c-mux blocks.
+
+- reg:			contains offset/length value for device state control
+			registers space.
+
+Optional properties:
+
+- interrupts:		The interrupt line(s) the /IRQ signal(s) for the device is
+			connected to.
+
+- interrupt-parent:	The parent interrupt controller.
+
+Example:
+
+cpld@0,0 {
+	compatible = "jnx,ptxpmb-cpld";
+	reg = <0x0 0 0x10000>;
+
+	cpld-i2c-mux {
+		compatible = "jnx,i2c-mux-ptxpmb-cpld";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		i2c-parent = <&i2c_master_0>;
+
+		num-enable = <1>;
+
+		i2c@0 {
+			reg = <0>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* PMB devices are accessed through FPC */
+
+			temp-sensor@1a { /* FPC */
+				compatible = "maxim,max6697";
+				reg = <0x1a>;
+				smbus-timeout-disable;
+				resistance-cancellation;
+				alert-mask = <0xff>;
+				over-temperature-mask = <0xff>;
+			};
+		};
+
+		pic0i2c: i2c@1 {
+			reg = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* PIC 0 */
+		};
+
+		pic1i2c: i2c@2 {
+			reg = <2>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* PIC 1 */
+		};
+	};
+
+	gpio_cpld: cpld_gpio {
+		compatible = "jnx,gpio-ptxpmb-cpld";
+		#gpio-cells = <2>;
+		gpio-controller;
+	};
+};
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 03/10] watchdog: Add support for PTXPMB CPLD watchdog
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

Add support for Junipers PTXPMB CPLD's watchdog device.

Signed-off-by: Guenter Roeck <groeck@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/watchdog/Kconfig      |  12 ++
 drivers/watchdog/Makefile     |   1 +
 drivers/watchdog/ptxpmb_wdt.c | 283 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 296 insertions(+)
 create mode 100644 drivers/watchdog/ptxpmb_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 50dbaa8..2554f47 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -131,6 +131,18 @@ config GPIO_WATCHDOG_ARCH_INITCALL
 	  arch_initcall.
 	  If in doubt, say N.
 
+config JNX_PTXPMB_WDT
+	tristate "Juniper PTX PMB CPLD watchdog"
+	depends on MFD_JUNIPER_CPLD
+	default y if MFD_JUNIPER_CPLD
+	select WATCHDOG_CORE
+	help
+	  Watchdog timer embedded into the boot CPLD on PTX PMB on
+	  relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called jnx-ptxpmb-wdt.
+
 config MENF21BMC_WATCHDOG
 	tristate "MEN 14F021P00 BMC Watchdog"
 	depends on MFD_MENF21BMC
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index cba0043..942b795 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
 endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
+obj-$(CONFIG_JNX_PTXPMB_WDT) += ptxpmb_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
 obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
diff --git a/drivers/watchdog/ptxpmb_wdt.c b/drivers/watchdog/ptxpmb_wdt.c
new file mode 100644
index 0000000..f04ac50
--- /dev/null
+++ b/drivers/watchdog/ptxpmb_wdt.c
@@ -0,0 +1,283 @@
+/*
+ * Watchdog driver for PTX PMB CPLD based watchdog
+ *
+ * Copyright (C) 2012 Juniper Networks
+ *
+ * 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.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/ptxpmb_cpld.h>
+
+#define DRV_NAME "jnx-ptxpmb-wdt"
+
+/*
+ * Since we can't really expect userspace to be responsive enough before a
+ * watchdog overflow happens, we maintain two separate timers .. One in
+ * the kernel for clearing out the watchdog every second, and another for
+ * monitoring userspace writes to the WDT device.
+ *
+ * As such, we currently use a configurable heartbeat interval which defaults
+ * to 30s. In this case, the userspace daemon is only responsible for periodic
+ * writes to the device before the next heartbeat is scheduled. If the daemon
+ * misses its deadline, the kernel timer will allow the WDT to overflow.
+ */
+
+#define WD_MIN_TIMEOUT		1
+#define WD_MAX_TIMEOUT		65535
+#define WD_DEFAULT_TIMEOUT	30	/* 30 sec default heartbeat */
+
+struct ptxpmb_wdt {
+	struct pmb_boot_cpld __iomem *cpld;
+	struct device		*dev;
+	spinlock_t		lock;
+	struct timer_list	timer;
+	unsigned long		next_heartbeat;
+};
+
+static void ptxpmb_wdt_enable(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+
+	wdt->next_heartbeat = jiffies + wdog->timeout * HZ;
+	mod_timer(&wdt->timer, jiffies + HZ);
+
+	iowrite8(0, &wdt->cpld->watchdog_hbyte);
+	iowrite8(0, &wdt->cpld->watchdog_lbyte);
+	iowrite8(ioread8(&wdt->cpld->control) | 0x40, &wdt->cpld->control);
+}
+
+static void ptxpmb_wdt_disable(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+
+	del_timer(&wdt->timer);
+
+	iowrite8(ioread8(&wdt->cpld->control) & ~0x40, &wdt->cpld->control);
+}
+
+static int ptxpmb_wdt_keepalive(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	wdt->next_heartbeat = jiffies + (wdog->timeout * HZ);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static int ptxpmb_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	wdog->timeout = t;
+	ptxpmb_wdt_enable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static void ptxpmb_wdt_ping(unsigned long data)
+{
+	struct ptxpmb_wdt *wdt = (struct ptxpmb_wdt *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	if (time_before(jiffies, wdt->next_heartbeat)) {
+		mod_timer(&wdt->timer, jiffies + HZ);
+		iowrite8(0, &wdt->cpld->watchdog_hbyte);
+		iowrite8(0, &wdt->cpld->watchdog_lbyte);
+	} else {
+		dev_warn(wdt->dev, "Heartbeat lost! Will not ping the watchdog\n");
+	}
+	spin_unlock_irqrestore(&wdt->lock, flags);
+}
+
+static int ptxpmb_wdt_start(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	ptxpmb_wdt_enable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static int ptxpmb_wdt_stop(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	ptxpmb_wdt_disable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static const struct watchdog_info ptxpmb_wdt_info = {
+	.options		= WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+	.identity		= "PTX PMB WDT",
+};
+
+static const struct watchdog_ops ptxpmb_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = ptxpmb_wdt_start,
+	.stop = ptxpmb_wdt_stop,
+	.ping = ptxpmb_wdt_keepalive,
+	.set_timeout = ptxpmb_wdt_set_timeout,
+};
+
+static struct watchdog_device *ptxpmb_wdog;
+
+static int ptxpmb_wdt_notify_sys(struct notifier_block *this,
+				 unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT)
+		ptxpmb_wdt_stop(ptxpmb_wdog);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ptxpmb_wdt_notifier = {
+	.notifier_call		= ptxpmb_wdt_notify_sys,
+};
+
+static int ptxpmb_wdt_probe(struct platform_device *pdev)
+{
+	struct ptxpmb_wdt *wdt;
+	bool nowayout = WATCHDOG_NOWAYOUT;
+	struct watchdog_device *wdog;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+	int rc;
+
+	wdog = devm_kzalloc(dev, sizeof(*wdog), GFP_KERNEL);
+	if (unlikely(!wdog))
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(!res))
+		return -EINVAL;
+
+#if 0
+	if (!devm_request_mem_region(dev, res->start,
+				     resource_size(res), DRV_NAME))
+		return -EBUSY;
+#endif
+
+	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+	if (unlikely(!wdt))
+		return -ENOMEM;
+
+	wdt->dev = dev;
+
+	wdt->cpld = devm_ioremap(dev, res->start, resource_size(res));
+	if (unlikely(!wdt->cpld))
+		return -ENXIO;
+
+	spin_lock_init(&wdt->lock);
+
+	wdog->info = &ptxpmb_wdt_info;
+	wdog->ops = &ptxpmb_wdt_ops;
+	wdog->min_timeout = WD_MIN_TIMEOUT;
+	wdog->max_timeout = WD_MAX_TIMEOUT;
+	wdog->timeout = WD_DEFAULT_TIMEOUT;
+	wdog->parent = dev;
+
+	watchdog_set_drvdata(wdog, wdt);
+	watchdog_set_nowayout(wdog, nowayout);
+	platform_set_drvdata(pdev, wdog);
+
+	ptxpmb_wdt_disable(wdog);
+	ptxpmb_wdog = wdog;
+
+	rc = register_reboot_notifier(&ptxpmb_wdt_notifier);
+	if (unlikely(rc)) {
+		dev_err(dev, "Can't register reboot notifier (err=%d)\n", rc);
+		return rc;
+	}
+
+	rc = watchdog_register_device(wdog);
+	if (rc)
+		goto out_unreg;
+
+	init_timer(&wdt->timer);
+	wdt->timer.function	= ptxpmb_wdt_ping;
+	wdt->timer.data		= (unsigned long)wdt;
+	wdt->timer.expires	= jiffies + HZ;
+
+	dev_info(dev, "initialized\n");
+
+	return 0;
+
+out_unreg:
+	unregister_reboot_notifier(&ptxpmb_wdt_notifier);
+	return rc;
+}
+
+static int ptxpmb_wdt_remove(struct platform_device *pdev)
+{
+	struct watchdog_device *wdog = platform_get_drvdata(pdev);
+
+	unregister_reboot_notifier(&ptxpmb_wdt_notifier);
+	watchdog_unregister_device(wdog);
+
+	return 0;
+}
+
+static const struct of_device_id ptxpmb_wdt_of_ids[] = {
+	{ .compatible = "jnx,ptxpmb-wdt", NULL },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ptxpmb_wdt_of_ids);
+
+static struct platform_driver ptxpmb_wdt_driver = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = ptxpmb_wdt_of_ids,
+	},
+
+	.probe	= ptxpmb_wdt_probe,
+	.remove	= ptxpmb_wdt_remove,
+};
+
+static int __init ptxpmb_wdt_init(void)
+{
+	return platform_driver_register(&ptxpmb_wdt_driver);
+}
+
+static void __exit ptxpmb_wdt_exit(void)
+{
+	platform_driver_unregister(&ptxpmb_wdt_driver);
+}
+module_init(ptxpmb_wdt_init);
+module_exit(ptxpmb_wdt_exit);
+
+MODULE_AUTHOR("Guenter Roeck <groeck@juniper.net>");
+MODULE_DESCRIPTION("Juniper PTX PMB CPLD watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 05/10] i2c/muxes: Juniper's PTXPMB CPLD I2C multiplexer
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Linus Walleij, Alexandre Courbot, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Wim Van Sebroeck, Guenter Roeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, JawaharBalaji Thirumalaisamy,
	Rajat Jain, Pantelis Antoniou, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, linux-i2c
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Introduce Juniper's PTX PMB CPLD I2C multiplexer driver.

Signed-off-by: Debjit Ghosh <dghosh-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Rajat Jain <rajatjain-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 drivers/i2c/muxes/Kconfig          |  11 ++
 drivers/i2c/muxes/Makefile         |   1 +
 drivers/i2c/muxes/i2c-mux-ptxpmb.c | 299 +++++++++++++++++++++++++++++++++++++
 3 files changed, 311 insertions(+)
 create mode 100644 drivers/i2c/muxes/i2c-mux-ptxpmb.c

diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
index e280c8e..f45a9cb 100644
--- a/drivers/i2c/muxes/Kconfig
+++ b/drivers/i2c/muxes/Kconfig
@@ -61,6 +61,17 @@ config I2C_MUX_PINCTRL
 	  This driver can also be built as a module. If so, the module will be
 	  called pinctrl-i2cmux.
 
+config I2C_MUX_PTXPMB
+	tristate "Juniper PTX PMB CPLD I2C multiplexer"
+	depends on MFD_JUNIPER_CPLD
+	default y if MFD_JUNIPER_CPLD
+	help
+	  Select this to enable the Juniper PTX PMB CPLD I2C multiplexer driver
+	  on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-mux-ptxpmb-cpld.
+
 config I2C_MUX_REG
 	tristate "Register-based I2C multiplexer"
 	help
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
index 7c267c2..78d8cba 100644
--- a/drivers/i2c/muxes/Makefile
+++ b/drivers/i2c/muxes/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_I2C_MUX_GPIO)	+= i2c-mux-gpio.o
 obj-$(CONFIG_I2C_MUX_PCA9541)	+= i2c-mux-pca9541.o
 obj-$(CONFIG_I2C_MUX_PCA954x)	+= i2c-mux-pca954x.o
 obj-$(CONFIG_I2C_MUX_PINCTRL)	+= i2c-mux-pinctrl.o
+obj-$(CONFIG_I2C_MUX_PTXPMB)	+= i2c-mux-ptxpmb.o
 obj-$(CONFIG_I2C_MUX_REG)	+= i2c-mux-reg.o
 
 ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/muxes/i2c-mux-ptxpmb.c b/drivers/i2c/muxes/i2c-mux-ptxpmb.c
new file mode 100644
index 0000000..e8c7aee
--- /dev/null
+++ b/drivers/i2c/muxes/i2c-mux-ptxpmb.c
@@ -0,0 +1,299 @@
+/*
+ * PTX PMB CPLD I2C multiplexer
+ *
+ * Copyright (c) 2012, Juniper Networks.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * 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/delay.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mfd/ptxpmb_cpld.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+
+struct i2c_mux_ptxpmb {
+	struct device *dev;
+	struct ptxpmb_mux_data *pdata;
+	struct pmb_boot_cpld __iomem *cpld;
+	struct i2c_adapter *parent;
+	int bus_count;
+	struct i2c_mux_core *muxc;
+};
+
+static const struct of_device_id i2c_mux_ptxpmb_of_match[] = {
+	{ .compatible = "jnx,i2c-mux-ptxpmb-cpld",
+	  .data = (void *)CPLD_TYPE_PTXPMB },
+	{ .compatible = "jnx,i2c-mux-ngpmb-bcpld",
+	  .data = (void *)CPLD_TYPE_NGPMB },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, i2c_mux_ptxpmb_of_match);
+
+#define I2C_GRP_FORCE_EN	0x80
+
+static int i2c_mux_ptxpmb_select(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct i2c_mux_ptxpmb *mux = i2c_mux_priv(muxc);
+	struct ptxpmb_mux_data *pdata = mux->pdata;
+	u8 group, enable, val;
+
+	switch (pdata->cpld_type) {
+	case CPLD_TYPE_PTXPMB:
+		group = chan % pdata->num_channels;
+		enable = 1 << (chan / pdata->num_channels);
+		/*
+		 * Writing into the enable register does not have an effect on
+		 * FPC with P2020. It is necessary for FPC with P5020/P5040.
+		 * The uKernel for SPMB uses undocumented CPLD registers to set
+		 * group enable values (i2c_group_sel_force and
+		 * i2c_group_en_force at offset 0x33 and 0x34). Bit 7 in
+		 * i2c_group_sel_force must be set for this to work.
+		 * i2c_group_en_force is active-low. This applies to SPMB with
+		 * P2020; behavior with P5020/P5040 is unknown at this time.
+		 */
+		if (pdata->use_force) {
+			iowrite8(group | I2C_GRP_FORCE_EN,
+				 &mux->cpld->i2c_group_sel_force);
+			iowrite8(~enable, &mux->cpld->i2c_group_en_force);
+		} else {
+			iowrite8(group, &mux->cpld->i2c_group_sel);
+			ioread8(&mux->cpld->i2c_group_sel);
+			iowrite8(enable, &mux->cpld->i2c_group_en);
+			ioread8(&mux->cpld->i2c_group_en);
+		}
+		break;
+	case CPLD_TYPE_NGPMB:
+		val = ioread8(&mux->cpld->gpio_2);
+		val &= ~NGPMB_I2C_GRP_SEL_MASK;
+		val |= (chan << NGPMB_I2C_GRP_SEL_LSB) & NGPMB_I2C_GRP_SEL_MASK;
+		iowrite8(val, &mux->cpld->gpio_2);
+		break;
+	}
+	udelay(50);
+
+	return 0;
+}
+
+static int i2c_mux_ptxpmb_deselect(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct i2c_mux_ptxpmb *mux = i2c_mux_priv(muxc);
+	u8 val;
+
+	switch (mux->pdata->cpld_type) {
+	case CPLD_TYPE_PTXPMB:
+		/*
+		 * Restore defaults. Note that setting i2c_group_en does not
+		 * have an effect on FPC with P2020, but is necessary for FPC
+		 * with P5020/P5040.
+		 */
+		if (mux->pdata->use_force) {
+			iowrite8(0 | I2C_GRP_FORCE_EN,
+				 &mux->cpld->i2c_group_sel_force);
+			iowrite8(0xff, &mux->cpld->i2c_group_en_force);
+		} else {
+			iowrite8(0, &mux->cpld->i2c_group_sel);
+			ioread8(&mux->cpld->i2c_group_sel);
+			iowrite8(0, &mux->cpld->i2c_group_en);
+			ioread8(&mux->cpld->i2c_group_en);
+		}
+		break;
+	case CPLD_TYPE_NGPMB:
+		/* Use the (unconnected) channel 3 to deselct */
+		val = ioread8(&mux->cpld->gpio_2);
+		val &= ~NGPMB_I2C_GRP_SEL_MASK;
+		val |= (3 << NGPMB_I2C_GRP_SEL_LSB) & NGPMB_I2C_GRP_SEL_MASK;
+		iowrite8(val, &mux->cpld->gpio_2);
+		break;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static int i2c_mux_ptxpmb_parse_dt(struct i2c_mux_ptxpmb *mux,
+				   struct device *dev)
+{
+	struct device_node *np = dev->of_node;
+	int ret;
+	struct device_node *adapter_np;
+	struct i2c_adapter *adapter;
+	const struct of_device_id *match;
+
+	if (!np)
+		return 0;
+
+	mux->pdata = devm_kzalloc(dev, sizeof(*mux->pdata), GFP_KERNEL);
+	if (!mux->pdata)
+		return -ENOMEM;
+
+	match = of_match_device(i2c_mux_ptxpmb_of_match, dev);
+	if (match)
+		mux->pdata->cpld_type = (int)(unsigned long)match->data;
+
+	ret = of_property_read_u32(np, "num-enable", &mux->pdata->num_enable);
+	if (ret) {
+		dev_err(dev, "num-enable missing\n");
+		return -ENODEV;
+	}
+
+	ret = of_property_read_u32(np, "num-channels",
+				   &mux->pdata->num_channels);
+	if (ret)
+		mux->pdata->num_channels = 8;
+
+	ret = of_property_read_u32(np, "base-bus-num",
+				   &mux->pdata->base_bus_num);
+	if (ret)
+		mux->pdata->base_bus_num = 0;
+
+	if (of_find_property(np, "use-force", NULL))
+		mux->pdata->use_force = true;
+
+	adapter_np = of_parse_phandle(np, "i2c-parent", 0);
+	if (!adapter_np) {
+		dev_err(dev, "Cannot parse i2c-parent\n");
+		return -ENODEV;
+	}
+	adapter = of_find_i2c_adapter_by_node(adapter_np);
+	if (!adapter) {
+		dev_err(dev, "Cannot find parent bus\n");
+		return -ENODEV;
+	}
+	mux->pdata->parent_bus_num = i2c_adapter_id(adapter);
+	put_device(&adapter->dev);
+
+	return 0;
+}
+#else
+static inline int i2c_mux_ptxpmb_parse_dt(struct i2c_mux_ptxpmb *mux,
+					  struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static int i2c_mux_ptxpmb_probe(struct platform_device *pdev)
+{
+	struct i2c_mux_ptxpmb *mux;
+	struct i2c_mux_core *muxc;
+	int i, ret;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+
+	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+	if (!mux)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, mux);
+
+	mux->dev = dev;
+
+	mux->pdata = dev->platform_data;
+	if (!mux->pdata) {
+		ret = i2c_mux_ptxpmb_parse_dt(mux, dev);
+		if (ret < 0)
+			return ret;
+	}
+	if (!mux->pdata) {
+		dev_err(dev, "No platform / devicetree data\n");
+		return -ENODEV;
+	}
+
+	if (mux->pdata->num_enable <= 0 || mux->pdata->num_enable > 8 ||
+	    mux->pdata->num_channels <= 0 || mux->pdata->num_channels > 8) {
+		dev_err(dev, "Invalid platform data\n");
+		return -EINVAL;
+	}
+
+	mux->bus_count = mux->pdata->num_enable * mux->pdata->num_channels;
+
+	mux->parent = i2c_get_adapter(mux->pdata->parent_bus_num);
+	if (!mux->parent) {
+		dev_err(dev, "Parent adapter (%d) not found\n",
+			mux->pdata->parent_bus_num);
+		return -ENODEV;
+	}
+
+	muxc = i2c_mux_alloc(mux->parent, dev, mux->bus_count, 0, 0,
+			     i2c_mux_ptxpmb_select, i2c_mux_ptxpmb_deselect);
+	if (!muxc) {
+		ret =  -ENOMEM;
+		goto err;
+	}
+	muxc->priv = mux;
+	mux->muxc = muxc;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "No memory resource\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	mux->cpld = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!mux->cpld) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	for (i = 0; i < mux->bus_count; i++) {
+		u32 bus = mux->pdata->base_bus_num ?
+			mux->pdata->base_bus_num + i : 0;
+
+		ret = i2c_mux_add_adapter(muxc, bus, i, 0);
+		if (ret) {
+			dev_err(dev, "Failed to add adapter %d\n", i);
+			goto err_del_adapter;
+		}
+	}
+
+	return 0;
+
+err_del_adapter:
+	i2c_mux_del_adapters(mux->muxc);
+err:
+	i2c_put_adapter(mux->parent);
+	return ret;
+}
+
+static int i2c_mux_ptxpmb_remove(struct platform_device *pdev)
+{
+	struct i2c_mux_ptxpmb *mux = platform_get_drvdata(pdev);
+
+	i2c_mux_del_adapters(mux->muxc);
+	i2c_put_adapter(mux->parent);
+
+	return 0;
+}
+
+static struct platform_driver i2c_mux_ptxpmb_driver = {
+	.driver	= {
+		.name	= "i2c-mux-ptxpmb-cpld",
+		.owner	= THIS_MODULE,
+		.of_match_table = i2c_mux_ptxpmb_of_match,
+	},
+	.probe	= i2c_mux_ptxpmb_probe,
+	.remove	= i2c_mux_ptxpmb_remove,
+};
+
+module_platform_driver(i2c_mux_ptxpmb_driver);
+
+MODULE_DESCRIPTION("ptxpmb CPLD I2C multiplexer driver");
+MODULE_AUTHOR("Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:i2c-mux-ptxpmb-cpld");
-- 
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 06/10] i2c: i2c-mux-ptxpmb-cpld: Add device tree bindings
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Linus Walleij, Alexandre Courbot, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Wim Van Sebroeck, Guenter Roeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, JawaharBalaji Thirumalaisamy,
	Rajat Jain, Pantelis Antoniou, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, linux-i2c
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Add binding document for the i2c driver of PTXPMB CPLD.

Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 .../bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt       | 50 ++++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt

diff --git a/Documentation/devicetree/bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt b/Documentation/devicetree/bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt
new file mode 100644
index 0000000..3b201f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/jnx,i2c-mux-ptxpmb-cpld.txt
@@ -0,0 +1,50 @@
+* Juniper PTXPMB CPLD I2C Mux
+
+I2C mux on the PTXPMB CPLD on Juniper series of routers.
+
+Required properties:
+
+  - compatible: Must contain one of the following.
+    "jnx,i2c-mux-ptxpmb-cpld", "jnx,i2c-mux-ngpmb-bcpld"
+  - num-enable: Number of muxes to enable.
+
+  The following required properties are defined externally:
+
+  - Standard I2C mux properties. See i2c-mux.txt in this directory.
+  - I2C child bus nodes. See i2c-mux.txt in this directory.
+
+Optional Properties:
+
+  - num-channels: Number of channels. If not present the default is 8.
+  - base-bus-num: Base bus number. If not present it is 0.
+  - use-force: Use the force method of the controller.
+
+
+Example:
+
+cpld-i2c-mux {
+	compatible = "jnx,i2c-mux-ptxpmb-cpld";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	i2c-parent = <&i2c1>;
+
+	num-enable = <1>;
+
+	i2c@0 {
+		reg = <0>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		/* PMB devices are accessed through FPC */
+
+		temp-sensor@1a { /* FPC */
+			compatible = "maxim,max6697";
+			reg = <0x1a>;
+			smbus-timeout-disable;
+			resistance-cancellation;
+			alert-mask = <0xff>;
+			over-temperature-mask = <0xff>;
+		};
+	};
+};
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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 07/10] gpio: ptxpmb-cpld: Add support for PTXPMB CPLD's GPIO
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

Support the GPIO block which is located in PTXPMB CPLDs
on relevant Juniper platforms.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/gpio/Kconfig            |  11 +++
 drivers/gpio/Makefile           |   1 +
 drivers/gpio/gpio-ptxpmb-cpld.c | 177 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 189 insertions(+)
 create mode 100644 drivers/gpio/gpio-ptxpmb-cpld.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 26ee00f..9c91de6 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -360,6 +360,17 @@ config GPIO_PL061
 	help
 	  Say yes here to support the PrimeCell PL061 GPIO device
 
+config GPIO_PTXPMB_CPLD
+	tristate "PTXPMB CPLD GPIO"
+	depends on MFD_JUNIPER_CPLD
+	default y if MFD_JUNIPER_CPLD
+	help
+	  This driver supports the GPIO interfaces on the PTXPMB CPLD which is
+	  present on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called gpio-ptxpmb-cpld.
+
 config GPIO_PXA
 	bool "PXA GPIO support"
 	depends on ARCH_PXA || ARCH_MMP
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index ab28a2d..d397ea5 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_GPIO_PCF857X)	+= gpio-pcf857x.o
 obj-$(CONFIG_GPIO_PCH)		+= gpio-pch.o
 obj-$(CONFIG_GPIO_PISOSR)	+= gpio-pisosr.o
 obj-$(CONFIG_GPIO_PL061)	+= gpio-pl061.o
+obj-$(CONFIG_GPIO_PTXPMB_CPLD)	+= gpio-ptxpmb-cpld.o
 obj-$(CONFIG_GPIO_PXA)		+= gpio-pxa.o
 obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
 obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
diff --git a/drivers/gpio/gpio-ptxpmb-cpld.c b/drivers/gpio/gpio-ptxpmb-cpld.c
new file mode 100644
index 0000000..e6fab7c
--- /dev/null
+++ b/drivers/gpio/gpio-ptxpmb-cpld.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <linux/mfd/ptxpmb_cpld.h>
+
+/**
+ * struct ptxpmb_cpld_gpio - GPIO private data structure.
+ * @base:			PCI base address of Memory mapped I/O register.
+ * @dev:			Pointer to device structure.
+ * @gpio:			Data for GPIO infrastructure.
+ */
+struct ptxpmb_cpld_gpio {
+	void __iomem *base;
+	struct device *dev;
+	struct gpio_chip gpio;
+	struct mutex lock;
+};
+
+static u8 *ptxpmb_cpld_gpio_get_addr(struct pmb_boot_cpld *cpld,
+				     unsigned int nr)
+{
+	if (nr < 8)			/* 0..7: reset			*/
+		return &cpld->reset;
+	else if (nr < 16)		/* 8..15: control		*/
+		return &cpld->control;
+	else if (nr < 24)		/* 16..23: gpio1		*/
+		return &cpld->gpio_1;
+	else if (nr < 32)		/* 24..31: gpio2		*/
+		return &cpld->gpio_2;
+	else if (nr < 40)		/* 32..39: gp_reset1		*/
+		return &cpld->gp_reset1;
+	return &cpld->thermal_status;	/* 40..41: thermal status	*/
+}
+
+static void ptxpmb_cpld_gpio_set(struct gpio_chip *gpio, unsigned int nr,
+				 int val)
+{
+	u32 reg;
+	u8 bit = 1 << (nr & 7);
+	struct ptxpmb_cpld_gpio *chip =
+	  container_of(gpio, struct ptxpmb_cpld_gpio, gpio);
+	u8 *addr = ptxpmb_cpld_gpio_get_addr(chip->base, nr);
+
+	mutex_lock(&chip->lock);
+	reg = ioread8(addr);
+	if (val)
+		reg |= bit;
+	else
+		reg &= ~bit;
+
+	iowrite8(reg, addr);
+	mutex_unlock(&chip->lock);
+}
+
+static int ptxpmb_cpld_gpio_get(struct gpio_chip *gpio, unsigned int nr)
+{
+	struct ptxpmb_cpld_gpio *chip = container_of(gpio,
+						     struct ptxpmb_cpld_gpio,
+						     gpio);
+	u8 *addr = ptxpmb_cpld_gpio_get_addr(chip->base, nr);
+	u8 bit = 1 << (nr & 7);
+
+	return !!(ioread8(addr) & bit);
+}
+
+static int ptxpmb_cpld_gpio_direction_output(struct gpio_chip *gpio,
+					     unsigned int nr, int val)
+{
+	return 0;
+}
+
+static int ptxpmb_cpld_gpio_direction_input(struct gpio_chip *gpio,
+					    unsigned int nr)
+{
+	return 0;
+}
+
+static void ptxpmb_cpld_gpio_setup(struct ptxpmb_cpld_gpio *chip)
+{
+	struct gpio_chip *gpio = &chip->gpio;
+
+	gpio->label = dev_name(chip->dev);
+	gpio->owner = THIS_MODULE;
+	gpio->direction_input = ptxpmb_cpld_gpio_direction_input;
+	gpio->get = ptxpmb_cpld_gpio_get;
+	gpio->direction_output = ptxpmb_cpld_gpio_direction_output;
+	gpio->set = ptxpmb_cpld_gpio_set;
+	gpio->dbg_show = NULL;
+	gpio->base = -1;
+	gpio->ngpio = 48;
+	gpio->can_sleep = 0;
+#ifdef CONFIG_OF_GPIO
+	gpio->of_node = chip->dev->of_node;
+#endif
+}
+
+static int ptxpmb_cpld_gpio_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct ptxpmb_cpld_gpio *chip;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+
+	chip->dev = dev;
+	platform_set_drvdata(pdev, chip);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	chip->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!chip->base)
+		return -ENOMEM;
+
+	mutex_init(&chip->lock);
+	ptxpmb_cpld_gpio_setup(chip);
+	ret = gpiochip_add(&chip->gpio);
+	if (ret) {
+		dev_err(dev, "CPLD gpio: Failed to register GPIO\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int ptxpmb_cpld_gpio_remove(struct platform_device *pdev)
+{
+	struct ptxpmb_cpld_gpio *chip = platform_get_drvdata(pdev);
+
+	gpiochip_remove(&chip->gpio);
+
+	return 0;
+}
+
+static const struct of_device_id ptxpmb_cpld_gpio_ids[] = {
+	{ .compatible = "jnx,gpio-ptxpmb-cpld", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ptxpmb_cpld_gpio_ids);
+
+static struct platform_driver ptxpmb_cpld_gpio_driver = {
+	.driver = {
+		.name = "gpio-ptxpmb-cpld",
+		.owner  = THIS_MODULE,
+		.of_match_table = ptxpmb_cpld_gpio_ids,
+	},
+	.probe = ptxpmb_cpld_gpio_probe,
+	.remove = ptxpmb_cpld_gpio_remove,
+};
+
+module_platform_driver(ptxpmb_cpld_gpio_driver);
+
+MODULE_DESCRIPTION("CPLD FPGA GPIO Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 08/10] gpio: ptxpmb-cpld: Document bindings of PTXPMB's CPLD GPIO
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add device tree bindings document for the GPIO driver of
Juniper's PTXPMB/NGPMB CPLD.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../bindings/gpio/jnx,gpio-ptxpmb-cpld.txt         | 30 ++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-cpld.txt

diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-cpld.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-cpld.txt
new file mode 100644
index 0000000..1122021
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-cpld.txt
@@ -0,0 +1,30 @@
+Juniper PTXPMB CPLD GPIO block
+
+Required properties:
+
+- compatible:
+    Must be "jnx,gpio-ptxpmb-cpld"
+
+- #gpio-cells:
+    Should be <2>.  The first cell is the pin number (within the controller's
+    pin space), and the second is used for the following flags:
+	bit[0]: direction (0 = out, 1 = in)
+	bit[1]: init high
+	bit[2]: active low
+
+- gpio-controller:
+    Specifies that the node is a GPIO controller.
+
+Optional properties:
+
+- reg:
+    Address and length of the register set for the device. Usually supplied by
+    the parent MFD driver
+
+Example:
+
+gpio_cpld: cpld_gpio {
+	compatible = "jnx,gpio-ptxpmb-cpld";
+	#gpio-cells = <2>;
+	gpio-controller;
+};
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 09/10] mtd: devices: Add driver for memory mapped NVRAM on FPC
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	linux-kernel, Wim Van Sebroeck, linux-mtd, linux-i2c,
	Frank Rowand, Alexandre Courbot, JawaharBalaji Thirumalaisamy,
	Guenter Roeck, devicetree, linux-watchdog, linux-gpio,
	Rob Herring, Debjit Ghosh, Georgi Vlaev, Rajat Jain,
	Guenter Roeck, Brian Norris, David Woodhouse, Peter Rosin
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>

Add MTD driver for NVRAM on Juniper Gladiator FPC PMB. This driver uses
indirect memory-mapped access facilitated by bootcpld. Requires
cpld_ver >= 0XC6 and DT support.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mtd/devices/Kconfig         |  11 +++
 drivers/mtd/devices/Makefile        |   1 +
 drivers/mtd/devices/jnx_pmb_nvram.c | 191 ++++++++++++++++++++++++++++++++++++
 3 files changed, 203 insertions(+)
 create mode 100644 drivers/mtd/devices/jnx_pmb_nvram.c

diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 58329d2..d4255fb 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -144,6 +144,17 @@ config MTD_LART
 	  not need any mapping/chip driver for LART. This one does it all
 	  for you, so go disable all of those if you enabled some of them (:
 
+config JNX_PMB_NVRAM
+	tristate "Juniper FPC PMB NVRAM Driver"
+	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	help
+	  This driver adds support for NVRAM on Gladiator 3T FPC which is connected
+	  to the BOOTCPLD (cpld_version >= C6).
+
+	  This driver can also be built as a module. When it is so the name of
+	  the module is ngpmb-nvram.
+
 config MTD_MTDRAM
 	tristate "Test driver using RAM"
 	help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 7912d3a..b407c5fc 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH)	+= bcm47xxsflash.o
 obj-$(CONFIG_MTD_ST_SPI_FSM)    += st_spi_fsm.o
 obj-$(CONFIG_MTD_POWERNV_FLASH)	+= powernv_flash.o
 
+obj-$(CONFIG_JNX_PMB_NVRAM)     += jnx_pmb_nvram.o
 
 CFLAGS_docg3.o			+= -I$(src)
diff --git a/drivers/mtd/devices/jnx_pmb_nvram.c b/drivers/mtd/devices/jnx_pmb_nvram.c
new file mode 100644
index 0000000..8a1e812
--- /dev/null
+++ b/drivers/mtd/devices/jnx_pmb_nvram.c
@@ -0,0 +1,191 @@
+/*
+ * Juniper Networks PTX1K RCB I2CS Boot FPGA MTD driver
+ * FPGA upgrades of the Spartan3AN/XC3S700 based I2CS.
+ *
+ * Copyright (C) 2015 Juniper Networks. All rights reserved.
+ * Author:	JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mfd/ptxpmb_cpld.h>
+
+struct nvram_mtd {
+	void __iomem *base;
+	struct device *dev;
+	struct mtd_info mtd;
+	struct mutex lock;
+};
+
+
+static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	struct nvram_mtd *nvram = container_of(mtd, struct nvram_mtd, mtd);
+
+	memset((char *)nvram->base + instr->addr, 0xff, instr->len);
+	instr->state = MTD_ERASE_DONE;
+	mtd_erase_callback(instr);
+	return 0;
+}
+
+static int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
+		     size_t *retlen, void **virt, resource_size_t *phys)
+{
+	struct nvram_mtd *nvram = container_of(mtd, struct nvram_mtd, mtd);
+
+	virt = nvram->base + from;
+	*retlen = len;
+	return 0;
+}
+
+static int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
+{
+	return 0;
+}
+
+static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
+		    size_t *retlen, u_char *buf)
+{
+	struct nvram_mtd *nvram = container_of(mtd, struct nvram_mtd, mtd);
+
+	memcpy(buf, nvram->base + from, len);
+	*retlen = len;
+	return 0;
+}
+
+static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
+		     size_t *retlen, const u_char *buf)
+{
+	struct nvram_mtd *nvram = container_of(mtd, struct nvram_mtd, mtd);
+
+	memcpy((char *)nvram->base + to, buf, len);
+	*retlen = len;
+	return 0;
+}
+
+int nvram_init_mtd_parse(struct platform_device *pdev, struct mtd_info *mtd)
+{
+	struct mtd_part_parser_data ppdata = {};
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	mtd->name = dev_name(dev);
+	mtd->type = MTD_RAM;
+	mtd->flags = MTD_CAP_RAM;
+	mtd->size = 0xFF00;
+	mtd->writesize = 1;
+	mtd->writebufsize = 64; /* Mimic CFI NOR flashes */
+	mtd->erasesize = 0x1000;
+	mtd->owner = THIS_MODULE;
+	mtd->_erase = ram_erase;
+	mtd->_point = ram_point;
+	mtd->_unpoint = ram_unpoint;
+	mtd->_read = ram_read;
+	mtd->_write = ram_write;
+	mtd->_panic_write = ram_write;
+
+	ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0);
+	if (ret) {
+		dev_err(dev, "mtd_device_parse_register returned %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+static int nvram_probe(struct platform_device *pdev)
+{
+	struct pmb_boot_cpld __iomem *cpld;
+	struct nvram_mtd *nvram;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int ret;
+
+	nvram = devm_kzalloc(dev, sizeof(*nvram), GFP_KERNEL);
+	if (!nvram)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "Failed to get nvram mmio resource\n");
+		return -ENOENT;
+	}
+	nvram->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!nvram->base) {
+		dev_err(dev, "Cannot map nvram\n");
+		return -EADDRNOTAVAIL;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!res) {
+		/* Always assume that we need cpld control */
+		dev_err(dev, "Failed to get cpld mmio resource\n");
+		return -ENOENT;
+	}
+	cpld = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!cpld) {
+		dev_err(dev, "Cannot map cpld\n");
+		return -EADDRNOTAVAIL;
+	}
+	nvram->dev = dev;
+	platform_set_drvdata(pdev, nvram);
+	ret = nvram_init_mtd_parse(pdev, &nvram->mtd);
+	if (ret)
+		return ret;
+
+	if (READ_ONCE(cpld->cpld_rev) < 0xC6)
+		dev_info(dev, "NVRAM requires atleast cpld_rev 0XC6\n");
+
+	/* Initialize the window register in the cpld*/
+	WRITE_ONCE(cpld->board.nvram.nv_win, 0x0);
+	dev_info(dev, "Initialized window:0x%x\n",
+			READ_ONCE(cpld->board.nvram.nv_win));
+	return ret;
+}
+
+static int nvram_remove(struct platform_device *pdev)
+{
+	struct nvram_mtd *nvram;
+
+	nvram = platform_get_drvdata(pdev);
+	mtd_device_unregister(&nvram->mtd);
+	return 0;
+}
+
+static const struct of_device_id ngpmb_mtd_ids[] = {
+	{ .compatible = "jnx,ngpmb-nvram", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ngpmb_mtd_ids);
+
+static struct platform_driver nvram_driver = {
+	.probe  = nvram_probe,
+	.remove = nvram_remove,
+	.driver = {
+		.name = "ngpmb-nvram",
+		.owner = THIS_MODULE,
+		.of_match_table = ngpmb_mtd_ids,
+	},
+};
+
+module_platform_driver(nvram_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>");
+MODULE_DESCRIPTION("EVO PTXPMB CPLD NVRAM Driver");
+MODULE_ALIAS("platform:ngpmb-nvram");
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 10/10] mtd: ngpmb_nvram: Add bindings for Juniper's ngpmb NVRAM
From: Pantelis Antoniou @ 2016-10-07 15:17 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Linus Walleij, Alexandre Courbot, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Wim Van Sebroeck, Guenter Roeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, JawaharBalaji Thirumalaisamy,
	Rajat Jain, Pantelis Antoniou, devicetree, linux-kernel,
	linux-gpio, linux-i2c
In-Reply-To: <1475853451-22121-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add device tree binging document for the ngpmb NVRAM device.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/mtd/ngpmb-nvram.txt        | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/ngpmb-nvram.txt

diff --git a/Documentation/devicetree/bindings/mtd/ngpmb-nvram.txt b/Documentation/devicetree/bindings/mtd/ngpmb-nvram.txt
new file mode 100644
index 0000000..297ad0e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/ngpmb-nvram.txt
@@ -0,0 +1,22 @@
+Non Volatile RAM device on a Juniper NG PMB FPGA
+
+These devices are found in the PTX series of Juniper routers.
+
+Required properties:
+- compatible : must be "jnx,ngpmb-nvram"
+
+Optional properties:
+- reg : memory address for the RAM, note that this is not
+required since usually the device is a subdevice of the SAM MFD
+driver which fills in the register fields.
+
+For the rest of the properties, see mtd-physmap.txt.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+ngpmb_nvram {
+	compatible = "jnx,ngpmb-nvram";
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH 00/10] Introduce Juniper SAM FPGA driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	Wim Van Sebroeck, linux-mtd, linux-i2c, Frank Rowand,
	Alexandre Courbot, Florian Fainelli, Maryam Seraj, Guenter Roeck,
	devicetree, linux-watchdog, linux-gpio, Rob Herring, Debjit Ghosh,
	Georgi Vlaev, netdev, linux-kernel, Brian Norris, David Woodhouse,
	Peter Rosin

Add Juniper's SAM FPGA driver. Those FPGAs
are present in Juniper's PTX series of routers.

The MFD driver provices i2c/gpio/mtd/mdio devices.

There are full device tree binding documents for the
master mfd driver and for all slave drivers.

This patchset is against mainline as of today: v4.8-9431-g3477d16
and is dependent on the "Juniper prerequisites" and
"Juniper infrastructure" patchsets sent earlier.

Georgi Vlaev (6):
  mfd: sam: Add documentation for the SAM FPGA
  i2c: i2c-sam: Add device tree bindings
  gpio: sam: Document bindings of SAM FPGA GPIO block
  mtd: flash-sam: Bindings for Juniper's SAM FPGA flash
  net: phy: Add MDIO driver for Juniper's SAM FPGA
  net: mdio-sam: Add device tree documentation for SAM MDIO

Guenter Roeck (2):
  gpio: Introduce SAM gpio driver
  mtd: Add SAM Flash driver

Maryam Seraj (2):
  mfd: Add Juniper SAM FPGA MFD driver
  i2c: Juniper SAM I2C driver

 .../devicetree/bindings/gpio/jnx,gpio-sam.txt      | 110 +++
 .../devicetree/bindings/i2c/i2c-sam-mux.txt        |  20 +
 Documentation/devicetree/bindings/i2c/i2c-sam.txt  |  44 +
 Documentation/devicetree/bindings/mfd/jnx-sam.txt  |  94 ++
 .../devicetree/bindings/mtd/flash-sam.txt          |  31 +
 Documentation/devicetree/bindings/net/mdio-sam.txt |  48 +
 drivers/gpio/Kconfig                               |  11 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-sam.c                            | 707 +++++++++++++++
 drivers/i2c/busses/Kconfig                         |  11 +
 drivers/i2c/busses/Makefile                        |   1 +
 drivers/i2c/busses/i2c-sam.c                       | 942 +++++++++++++++++++
 drivers/mfd/Kconfig                                |  16 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/sam-core.c                             | 997 +++++++++++++++++++++
 drivers/mtd/devices/Kconfig                        |  11 +
 drivers/mtd/devices/Makefile                       |   1 +
 drivers/mtd/devices/sam-flash.c                    | 642 +++++++++++++
 drivers/net/phy/Kconfig                            |   8 +
 drivers/net/phy/Makefile                           |   1 +
 drivers/net/phy/mdio-sam.c                         | 564 ++++++++++++
 include/linux/mfd/sam.h                            |  30 +
 22 files changed, 4291 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-sam.txt
 create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt
 create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt
 create mode 100644 drivers/gpio/gpio-sam.c
 create mode 100644 drivers/i2c/busses/i2c-sam.c
 create mode 100644 drivers/mfd/sam-core.c
 create mode 100644 drivers/mtd/devices/sam-flash.c
 create mode 100644 drivers/net/phy/mdio-sam.c
 create mode 100644 include/linux/mfd/sam.h

-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply

* [PATCH 01/10] mfd: Add Juniper SAM FPGA MFD driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	Wim Van Sebroeck, linux-mtd, linux-i2c, Frank Rowand,
	Alexandre Courbot, Florian Fainelli, Maryam Seraj, Guenter Roeck,
	devicetree, linux-watchdog, linux-gpio, Rob Herring, Debjit Ghosh,
	Georgi Vlaev, netdev, linux-kernel, Brian Norris, David Woodhouse,
	Peter Rosin
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Maryam Seraj <mseraj@juniper.net>

Add Juniper's SAM FPGA multi-function driver.

The SAM FPGAs are present on different FPC/SIB cards from the Juniper's
PTX series of routers. Depending on the card type and FPGA revision,
they include the following functional blocks:

* I2C SAM accelerator - multiple I2C masters and multiplexers
* GPIO
* Flash - hardware wrapper interface for the Altera's EPCS flashes
	(used for configuration flash updates)
* MDIO - multiple MDIO masters

Signed-off-by: Maryam Seraj <mseraj@juniper.net>
Signed-off-by: Debjit Ghosh <dghosh@juniper.net>
Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mfd/Kconfig     |  16 +
 drivers/mfd/Makefile    |   1 +
 drivers/mfd/sam-core.c  | 997 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/sam.h |  30 ++
 4 files changed, 1044 insertions(+)
 create mode 100644 drivers/mfd/sam-core.c
 create mode 100644 include/linux/mfd/sam.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 438666a..75b46a1 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1355,6 +1355,22 @@ config MFD_JUNIPER_CPLD
 	  This driver can be built as a module. If built as a module it will be
 	  called "ptxpmb-cpld"
 
+config MFD_JUNIPER_SAM
+	tristate "Juniper SAM FPGA"
+	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	select MFD_CORE
+	select I2C_SAM
+	select GPIO_SAM
+	select MTD_SAM_FLASH
+	select MDIO_SAM
+	help
+	  Select this to enable the SAM FPGA multi-function kernel driver.
+	  This FPGA is used on the PTX FPC board.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "sam-core"
+
 config MFD_TWL4030_AUDIO
 	bool "TI TWL4030 Audio"
 	depends on TWL4030_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 62decc9..71a8ba6 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -149,6 +149,7 @@ obj-$(CONFIG_AB3100_OTP)	+= ab3100-otp.o
 obj-$(CONFIG_AB8500_DEBUG)	+= ab8500-debugfs.o
 obj-$(CONFIG_AB8500_GPADC)	+= ab8500-gpadc.o
 obj-$(CONFIG_MFD_JUNIPER_CPLD)	+= ptxpmb-cpld-core.o
+obj-$(CONFIG_MFD_JUNIPER_SAM)	+= sam-core.o
 obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 # ab8500-core need to come after db8500-prcmu (which provides the channel)
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
diff --git a/drivers/mfd/sam-core.c b/drivers/mfd/sam-core.c
new file mode 100644
index 0000000..2ea2b1b
--- /dev/null
+++ b/drivers/mfd/sam-core.c
@@ -0,0 +1,997 @@
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/sam.h>
+#include <linux/jnx/pci_ids.h>
+#include <linux/of.h>
+
+#define DRIVER_DESC			"SAM FPGA MFD core driver"
+#define DRIVER_VERSION			"0.02.2"
+#define DRIVER_AUTHOR			"Maryam Seraj <mseraj@juniper.net>"
+
+#define SAM_FPGA_MODULE_NAME		"sam-mfd-core"
+#define FPGA_MEM_SIZE			0x20000
+
+#define SAM_NUM_IRQ			2
+#define SAM_NUM_MFD_CELLS		3
+#define SAM_NUM_RESOURCES		2
+#define SAM_NUM_RESOURCES_NOIRQ		1
+
+/* Minimum SAM revisions needed for i2c irq and PMA support */
+#define SAM_REVISION_I2C_IRQ_MIN	0x0065
+#define SAM_REVISION_PMA_MIN		0x004d
+#define SAM_REVISION_MASK		0x0000ffff
+#define SAM_REVISION(s)			((s)->fpga_rev & SAM_REVISION_MASK)
+
+#define SAM_BOARD_ID_MASK		0x000000ff
+#define SAM_BOARD_ID(s)			((s)->board_id & SAM_BOARD_ID_MASK)
+#define SAM_BOARD_ID_HENDRICKS_FPC	0x00
+#define SAM_BOARD_ID_CFP4		0x00
+#define SAM_BOARD_ID_QSFPP		0x00
+#define SAM_BOARD_ID_GPCAM		0x01
+#define SAM_BOARD_ID_SANGRIA_FPC	0x03
+#define SAM_BOARD_ID_QSFPP_OLD		0x03
+#define SAM_BOARD_ID_24x10GE_PIC	0x0B
+#define SAM_BOARD_ID_GLADIATOR_3T	0x11
+#define SAM_BOARD_ID_MLC		0x21
+
+#define SAM_IMG_ID_MASK			0x000000ff
+#define SAM_IMG_ID_SHIFT		16
+#define SAM_IMG_ID(s)			(((s)->board_id >> SAM_IMG_ID_SHIFT) & \
+					 SAM_IMG_ID_MASK)
+
+#define SAM_IMG_ID_QSFPP		0x00
+#define SAM_IMG_ID_GPCAM		0x01
+#define SAM_IMG_ID_SANGRIA_FPC		0x03
+#define SAM_IMG_ID_QSFPP_OLD		0x03
+#define SAM_IMG_ID_GLADIATOR_3T		0x03
+#define SAM_IMG_ID_HENDRICKS_FPC	0x05
+#define SAM_IMG_ID_24x10GE_PIC		0x0B
+#define SAM_IMG_ID_MLC			0x21
+#define SAM_IMG_ID_CFP4			0x22
+
+#define SANGRIA_FPC_PCIE_BUS		0x20
+
+struct sam_fpga_data {
+	void __iomem *membase;
+	struct pci_dev *pdev;
+	u32 fpga_rev;
+	u32 board_id;
+	u32 pma_lanes;
+	u32 pma_coefficients;
+	int irq_base;
+	u32 i2c_irq_mask;
+	u32 gpio_irq_mask;
+	int gpio_irq_shift;
+	spinlock_t irq_lock;
+	struct mfd_cell mfd_cells[SAM_NUM_MFD_CELLS];
+	struct resource mfd_i2c_resources[SAM_NUM_RESOURCES];
+	struct resource mfd_gpio_resources[SAM_NUM_RESOURCES];
+	struct resource mfd_mtd_resources[SAM_NUM_RESOURCES_NOIRQ];
+};
+
+#define VERSION_ADDR(s)		((s)->membase + 0x000)
+#define BOARD_ID_ADDR(s)	((s)->membase + 0x004)
+#define ICTRL_ADDR(s)		((s)->membase + 0x104)
+#define ISTAT_ADDR(s)		((s)->membase + 0x108)
+
+/* PMA */
+#define SAM_PMA_CONTROL_REG(s)	((s)->membase + 0x40)
+#define SAM_PMA_STATUS_REG(s)	((s)->membase + 0x44)
+
+#define SAM_PMA_CONTROL_WRITE	(1 << 31)
+#define SAM_PMA_CONTROL_READ	(1 << 30)
+#define SAM_PMA_LANE(lane)	(((lane) & 0x07) << 25)
+#define SAM_PMA_COEFF_MASK	((1 << 25) - 1)
+
+#define SAM_PMA_STATUS_BUSY	(1 << 31)
+#define SAM_PMA_STATUS_VALID	(1 << 30)
+
+#define SAM_PMA_RETRIES		40	/* observed to take 20 - 40 uS typ. */
+#define SAM_PMA_WAIT_TIME	10	/* uS */
+
+/* Constants used for FPGA upgrades */
+
+#define SAM_FPGA_FLASH_VALID_BIT		0xA5A5A5A5
+#define SAM_FPGA_FLASH_VALID_BIT_ADDR		0x7F0000
+
+/* FPGA remote upgrade registers */
+#define SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT	0x08000000
+#define SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY	0x01000000
+#define SAM_FPGA_REMOTE_UPGRADE_READ_PARAM	0x80000000
+#define SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM	0x40000000
+#define SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET	0x10000000
+
+#define SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL	(0x04 << 24)
+#define SAM_FPGA_REMOTE_UPGRADE_ANF		(0x05 << 24)
+#define SAM_FPGA_USER_IMAGE_BASE		0x400000
+
+#define SAM_FLASH_BASE				0x0300
+
+#define FLASH_ADDR_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x000)
+#define FLASH_COUNTER_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x004)
+#define FLASH_CONTROL_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x008)
+#define FLASH_STATUS_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define FLASH_WRITE_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x100)
+#define FLASH_READ_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x200)
+
+#define FLASH_STATUS_BUSY	0x01
+
+#define SAM_FLASH_IF_CONTROL_READ	0x00000001
+
+/* Upgrade control and management */
+#define SAM_UPGRADE_BASE	0x0200
+#define UPGRADE_CONTROL_REG(x)	((x)->membase + SAM_UPGRADE_BASE)
+#define UPGRADE_STATUS_REG(x)	((x)->membase + SAM_UPGRADE_BASE + 0x0004)
+
+/* List of discovered SAM devices */
+struct sam_core_list {
+	struct list_head node;
+	unsigned long insertion_time;
+	int bus;
+	int devfn;
+	u32 rev;
+	u32 id;
+};
+
+LIST_HEAD(sam_core_list);
+DEFINE_MUTEX(sam_core_list_mutex);
+
+static int sam_core_update_entry(struct sam_fpga_data *sam)
+{
+	struct sam_core_list *entry = NULL, *e;
+	int ret = 0;
+
+	mutex_lock(&sam_core_list_mutex);
+	list_for_each_entry(e, &sam_core_list, node) {
+		if (e->devfn == sam->pdev->devfn &&
+		    e->bus == sam->pdev->bus->number) {
+			entry = e;
+			break;
+		}
+	}
+
+	if (!entry) {
+		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+		if (!entry) {
+			ret = -ENOMEM;
+			goto abort;
+		}
+		list_add(&entry->node, &sam_core_list);
+	}
+
+	entry->bus = sam->pdev->bus->number;
+	entry->devfn = sam->pdev->devfn;
+	entry->rev = sam->fpga_rev;
+	entry->id = sam->board_id;
+	entry->insertion_time = jiffies;
+abort:
+	mutex_unlock(&sam_core_list_mutex);
+	return ret;
+}
+
+static bool sam_supports_i2c_irq(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		if (SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
+			return true;
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+	default:
+		/* others depend on image/board ID and FPGA version */
+		break;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_QSFPP:
+		/* QSFPP, GPQAM */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
+			return true;
+		break;
+	case SAM_IMG_ID_GPCAM:
+		/* GPCAM, GPQ28 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_GPCAM)
+			return true;
+		break;
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		/* Hendricks SAM FPGA version 15 still fails */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
+			return false;
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
+			return true;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP are the same.
+		 * Use PCIe bus number for disambiguation.
+		 * Bus number for QSFPP would be 0x10 or 0x30 (PIC bus numbers).
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN &&
+		    sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return true;
+		break;
+	default:
+		/*
+		 * For all others, play safe and assume that i2c interrupts
+		 * don't work.
+		 */
+		break;
+	}
+	return false;
+}
+
+static bool sam_supports_msi(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		/* unknown */
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+	default:
+		break;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
+			return false;
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC)
+			return false;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP are the same.
+		 * Use PCIe bus number for disambiguation.
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC
+		    && sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool sam_supports_pma(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		/* Note: marked HW use only on SHAM */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+		break;
+	default:
+		/* play safe */
+		return false;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_QSFPP:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
+			return true;
+		break;
+	case SAM_IMG_ID_CFP4:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_CFP4)
+			return true;
+		break;
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN)
+			return true;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP (old)
+		 * are the same. Use PCIe bus number for disambiguation.
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN &&
+		    sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return true;
+		break;
+	default:
+		/* unknown, play safe */
+		break;
+	}
+
+	return false;
+}
+
+/*
+ * Flash access commands (simplified)
+ */
+static bool sam_flash_busy(struct sam_fpga_data *sam)
+{
+	return ioread32(FLASH_STATUS_REG(sam)) & FLASH_STATUS_BUSY;
+}
+
+static int
+sam_flash_read(struct sam_fpga_data *sam, u_int32_t offset, u32 *data)
+{
+	if (sam_flash_busy(sam))
+		return -ETIMEDOUT;
+
+	iowrite32(sizeof(u32) - 1, FLASH_COUNTER_REG(sam));
+	iowrite32(offset, FLASH_ADDR_REG(sam));
+
+	/* trigger the read */
+	iowrite32(SAM_FLASH_IF_CONTROL_READ, FLASH_CONTROL_REG(sam));
+	ioread32(FLASH_CONTROL_REG(sam));
+
+	udelay(50);
+
+	if (sam_flash_busy(sam))
+		return  -ETIMEDOUT;
+
+	*data = ioread32(FLASH_READ_DATA_REG(sam));
+	return 0;
+}
+
+static u32 sam_irq_mask(struct sam_fpga_data *sam, enum sam_irq_type type,
+			u32 mask)
+{
+	switch (type) {
+	case SAM_IRQ_I2C:
+		mask &= sam->i2c_irq_mask;
+		break;
+	case SAM_IRQ_GPIO:
+		mask <<= sam->gpio_irq_shift;
+		mask &= sam->gpio_irq_mask;
+		break;
+	}
+	return mask;
+}
+
+static void sam_enable_irq(struct device *dev, enum sam_irq_type type, int irq,
+			   u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	unsigned long flags;
+	u32 s;
+
+	/* irq is one of the virqs passed to the driver */
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	spin_lock_irqsave(&sam->irq_lock, flags);
+	s = ioread32(ICTRL_ADDR(sam));
+	s |= mask;
+	iowrite32(s, ICTRL_ADDR(sam));
+	ioread32(ICTRL_ADDR(sam));
+	spin_unlock_irqrestore(&sam->irq_lock, flags);
+}
+
+static void sam_disable_irq(struct device *dev, enum sam_irq_type type, int irq,
+			    u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	unsigned long flags;
+	u32 s;
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	spin_lock_irqsave(&sam->irq_lock, flags);
+	s = ioread32(ICTRL_ADDR(sam));
+	s &= ~mask;
+	iowrite32(s, ICTRL_ADDR(sam));
+	ioread32(ICTRL_ADDR(sam));
+	spin_unlock_irqrestore(&sam->irq_lock, flags);
+}
+
+static u32 sam_irq_status(struct device *dev, enum sam_irq_type type, int irq)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	u32 status;
+
+	status = ioread32(ISTAT_ADDR(sam));
+
+	switch (type) {
+	case SAM_IRQ_I2C:
+		status &= sam->i2c_irq_mask;
+		break;
+	case SAM_IRQ_GPIO:
+		status &= sam->gpio_irq_mask;
+		status >>= sam->gpio_irq_shift;
+		break;
+	}
+	return status;
+}
+
+static void sam_irq_status_clear(struct device *dev, enum sam_irq_type type,
+				 int irq, u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	iowrite32(mask, ISTAT_ADDR(sam));
+	ioread32(ISTAT_ADDR(sam));
+}
+
+static irqreturn_t sam_irq_handler(int irq, void *data)
+{
+	struct sam_fpga_data *sam = data;
+	u32 status, mask;
+	int ret = IRQ_NONE;
+
+	/*
+	 * Shared interrupt handlers are called from the interrupt release
+	 * function, so be careful not to access already released resources.
+	 */
+	if (sam->irq_base) {
+		mask = ioread32(ICTRL_ADDR(sam));
+		status = ioread32(ISTAT_ADDR(sam));
+		status &= mask;
+		if (status & sam->i2c_irq_mask) {	/* i2c interrupt */
+			handle_nested_irq(sam->irq_base);
+			ret = IRQ_HANDLED;
+		}
+		if (status & sam->gpio_irq_mask) {	/* gpio interrupt */
+			handle_nested_irq(sam->irq_base + 1);
+			ret = IRQ_HANDLED;
+		}
+	}
+	return ret;
+}
+
+static int sam_irq_set_affinity(struct irq_data *d,
+				const struct cpumask *affinity, bool force)
+{
+	return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip sam_irq_chip = {
+	.name = "sam-core",
+	.irq_mask = noop,
+	.irq_unmask = noop,
+	.irq_set_affinity = sam_irq_set_affinity,
+};
+
+static int sam_attach_irq(struct sam_fpga_data *sam)
+{
+	int irq_base = irq_alloc_descs(-1, 0, SAM_NUM_IRQ, 0);
+	int irq;
+
+	if (irq_base < 0)
+		return irq_base;
+
+	for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
+		irq_set_noprobe(irq);
+		irq_set_chip_and_handler(irq, &sam_irq_chip, handle_level_irq);
+		irq_set_chip_data(irq, sam);
+		irq_set_status_flags(irq, IRQ_LEVEL);
+		irq_clear_status_flags(irq, IRQ_NOREQUEST);
+		irq_set_nested_thread(irq, true);
+	}
+	if (sam_supports_i2c_irq(sam)) {
+		sam->mfd_i2c_resources[1].start
+		  = sam->mfd_i2c_resources[1].end = irq_base;
+		sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES;
+	}
+	sam->mfd_gpio_resources[1].start = sam->mfd_gpio_resources[1].end
+	  = irq_base + 1;
+	sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES;
+	sam->irq_base = irq_base;
+
+	return 0;
+}
+
+static void sam_detach_irq(struct sam_fpga_data *sam)
+{
+	int irq, irq_base = sam->irq_base;
+
+	if (irq_base) {
+		sam->irq_base = 0;
+		for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
+			irq_set_handler_data(irq, NULL);
+			irq_set_chip(irq, NULL);
+			irq_set_chip_data(irq, NULL);
+		}
+		irq_free_descs(irq_base, SAM_NUM_IRQ);
+	}
+}
+
+static int sam_fpga_load_wait(struct sam_fpga_data *sam)
+{
+	unsigned long start = jiffies;
+	unsigned long timeout = start + msecs_to_jiffies(2000);
+
+	/* wait for up to two seconds for the command to complete */
+	do {
+		u32 status = ioread32(UPGRADE_STATUS_REG(sam));
+
+		if (!(status & SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY))
+			return 0;
+
+		usleep_range(500, 1000);
+	} while (time_before(jiffies, timeout));
+
+	return -ETIMEDOUT;
+}
+
+/*
+ * FPGA image download
+ */
+static int sam_fpga_load_image(struct device *dev, struct sam_fpga_data *sam)
+{
+	int ret;
+	u32 valid;
+	struct sam_core_list *entry;
+
+	/*
+	 * If the node exists, we have seen this device before.
+	 * Don't try to re-load it again unless the FPGA version changed,
+	 * a different board was inserted, or the board was inserted
+	 * more than a minute ago.
+	 * This check is necessary to ensure that we don't end up
+	 * in endless attempts to re-load SAM.
+	 */
+	mutex_lock(&sam_core_list_mutex);
+	list_for_each_entry(entry, &sam_core_list, node) {
+		if (entry->devfn == sam->pdev->devfn &&
+		    entry->bus == sam->pdev->bus->number &&
+		    entry->id == sam->board_id &&
+		    entry->rev == sam->fpga_rev &&
+		    time_before(entry->insertion_time, jiffies + HZ * 60)) {
+			mutex_unlock(&sam_core_list_mutex);
+			return 0;
+		}
+	}
+	mutex_unlock(&sam_core_list_mutex);
+
+	ret = sam_flash_read(sam, SAM_FPGA_FLASH_VALID_BIT_ADDR, &valid);
+	if (ret < 0 || valid != SAM_FPGA_FLASH_VALID_BIT)
+		return 0;
+
+	/* reset state machine and request upgrade */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET,
+		  UPGRADE_CONTROL_REG(sam));
+	usleep_range(10000, 20000);
+
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
+		  SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL |
+		  SAM_FPGA_USER_IMAGE_BASE,
+		  UPGRADE_CONTROL_REG(sam));
+	ioread32(UPGRADE_CONTROL_REG(sam));
+
+	ret = sam_fpga_load_wait(sam);
+	if (ret)
+		return 0;
+
+#if 0
+	/*
+	 * Request fallback to golden image if upgrade fails
+	 * Commented out in Sangria code, kept for reference
+	 */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
+		  SAM_FPGA_REMOTE_UPGRADE_ANF | 1,
+		  UPGRADE_CONTROL_REG(sam));
+
+	ret = sam_fpga_load_wait(sam);
+	if (ret)
+		return ret;
+#endif
+
+	/* Trigger reconfiguration */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT, UPGRADE_CONTROL_REG(sam));
+
+	sam_core_update_entry(sam);
+
+	/*
+	 * With a clean infrastructure, we could return -EPROBEDEFER here and
+	 * leave it up to the PCIe hotplug driver to detect that the device has
+	 * been removed and re-inserted. Without such a driver, user space
+	 * will have to take care of it.
+	 */
+	return -ENODEV;
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	return sprintf(buf, "0x%x\n", sam->fpga_rev);
+}
+
+static ssize_t board_id_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	return sprintf(buf, "0x%x\n", sam->board_id);
+}
+
+static DEVICE_ATTR(version, S_IRUGO, version_show, NULL);
+static DEVICE_ATTR(board_id, S_IRUGO, board_id_show, NULL);
+
+/* Initialize PMA coefficients */
+
+static int sam_pma_wait(struct sam_fpga_data *sam)
+{
+	int i;
+	u32 status;
+
+	for (i = 0; i < SAM_PMA_RETRIES; i++) {
+		udelay(SAM_PMA_WAIT_TIME);
+		status = ioread32(SAM_PMA_STATUS_REG(sam));
+		if (!(status & SAM_PMA_STATUS_BUSY))
+			return (status & SAM_PMA_STATUS_VALID) ? 0 : -EIO;
+	}
+	return -ETIMEDOUT;
+}
+
+static int sam_pma_write(struct sam_fpga_data *sam, u32 data, void *addr)
+{
+	iowrite32(data, addr);
+	ioread32(addr);
+	return sam_pma_wait(sam);
+}
+
+static int sam_pma_init(struct sam_fpga_data *sam)
+{
+	int lane;
+	int err;
+
+	for (lane = 0; lane < sam->pma_lanes; lane++) {
+		err = sam_pma_write(sam,
+				    SAM_PMA_CONTROL_READ | SAM_PMA_LANE(lane),
+				    SAM_PMA_CONTROL_REG(sam));
+		if (err)
+			return err;
+		err = sam_pma_write(sam,
+				    SAM_PMA_CONTROL_WRITE | SAM_PMA_LANE(lane) |
+				    sam->pma_coefficients,
+				    SAM_PMA_CONTROL_REG(sam));
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+static int sam_fpga_of_init(struct device *dev, struct sam_fpga_data *sam)
+{
+	int len;
+	const __be32 *pma_coefficients;
+
+	if (!dev->of_node) {
+		dev_warn(dev, "No SAM FDT node\n");
+		return of_have_populated_dt() ? -ENODEV : 0;
+	}
+
+	pma_coefficients = of_get_property(of_get_parent(dev->of_node),
+					   "pma-coefficients", &len);
+	if (pma_coefficients) {
+		if (len != 2 * sizeof(u32))
+			return -EINVAL;
+		sam->pma_lanes = be32_to_cpu(pma_coefficients[0]);
+		sam->pma_coefficients = be32_to_cpu(pma_coefficients[1]);
+
+		if (sam->pma_lanes > 7 ||
+		    (sam->pma_coefficients & ~SAM_PMA_COEFF_MASK))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/* SAM drivers interrupt handling */
+static struct sam_platform_data sam_plat_data = {
+	.enable_irq = sam_enable_irq,
+	.disable_irq = sam_disable_irq,
+	.irq_status = sam_irq_status,
+	.irq_status_clear = sam_irq_status_clear,
+	.i2c_mux_channels = 2,		/* MLC default */
+};
+
+/* Add a single cell from OF */
+static int sam_mfd_of_add_cell(struct device *dev, const char *compatible,
+				int id, struct resource *base)
+{
+	struct device_node *np;
+	struct mfd_cell cell = {0};
+	struct resource res = {0};
+	u32 reg;
+	int ret;
+
+	/* Note:
+	 * Due to limitations in the MFD core, we can't have more
+	 * than one compatible node - we can't match properly and bind
+	 * the of_nodes to mfd cells.
+	 */
+	np = of_find_compatible_node(dev->of_node, NULL, compatible);
+	if (!np)
+		return -ENODEV;
+
+	ret = of_property_read_u32(np, "reg", &reg);
+	if (ret)
+		return ret;
+
+	res.start = reg;
+	res.end = resource_size(base) - 1 - reg;
+	res.flags = IORESOURCE_MEM;
+	cell.name = np->name;
+	cell.of_compatible = compatible;
+	cell.resources = &res;
+	cell.num_resources = 1;
+
+	return mfd_add_devices(dev, id, &cell, 1, base, 0, NULL);
+}
+
+static void sam_init_irq_masks(struct sam_fpga_data *sam)
+{
+	if ((SAM_IMG_ID(sam) == SAM_IMG_ID_HENDRICKS_FPC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC) ||
+	    (SAM_IMG_ID(sam) == SAM_IMG_ID_24x10GE_PIC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC) ||
+	    (SAM_IMG_ID(sam) == SAM_IMG_ID_SANGRIA_FPC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+	     sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)) {
+		sam->i2c_irq_mask = 0x000000ff;
+		sam->gpio_irq_mask = 0x1ffff000;
+		sam->gpio_irq_shift = 12;
+	} else {
+		sam->i2c_irq_mask = 0x0000ffff;
+		sam->gpio_irq_mask = 0xffff0000;
+		sam->gpio_irq_shift = 16;
+	}
+}
+
+static int sam_fpga_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	int err;
+	struct sam_fpga_data *sam;
+	struct device *dev = &pdev->dev;
+
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (sam == NULL)
+		return -ENOMEM;
+
+	err = sam_fpga_of_init(dev, sam);
+	if (err < 0)
+		return err;
+
+	err = pci_enable_device(pdev);
+	if (err < 0) {
+		dev_err(dev, "pci_enable_device() failed: %d\n", err);
+		return err;
+	}
+
+	err = pci_request_regions(pdev, SAM_FPGA_MODULE_NAME);
+	if (err < 0) {
+		dev_err(dev, "pci_request_regions() failed: %d\n",
+			err);
+		goto err_disable;
+	}
+
+	sam->membase = pci_ioremap_bar(pdev, 0);
+	if (!sam->membase) {
+		dev_err(dev, "pci_ioremap_bar() failed\n");
+		err = -ENOMEM;
+		goto err_release;
+	}
+
+	sam->pdev = pdev;
+	pci_set_drvdata(pdev, sam);
+
+	/*
+	 * Try to upgrade FPGA image to user image if revision is too old
+	 * to support I2C interrupts
+	 */
+	sam->fpga_rev = ioread32(VERSION_ADDR(sam));
+	sam->board_id = ioread32(BOARD_ID_ADDR(sam));
+
+	if (!sam_supports_i2c_irq(sam)) {
+		dev_info(dev,
+			 "FPGA revision %u.%u doesn't support I2C interrupts, attempting firmware download\n",
+			 (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff);
+		err = sam_fpga_load_image(dev, sam);
+		if (err)
+			goto err_unmap;
+	}
+
+	if (sam_supports_pma(sam) && sam->pma_lanes) {
+		err = sam_pma_init(sam);
+		if (err < 0)
+			goto err_unmap;
+	}
+
+	sam_init_irq_masks(sam);
+
+	spin_lock_init(&sam->irq_lock);
+
+	sam->mfd_cells[0].name = "i2c-sam";
+	sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[0].resources = sam->mfd_i2c_resources;
+	sam->mfd_cells[0].of_compatible = "jnx,i2c-sam";
+	sam->mfd_cells[0].platform_data = &sam_plat_data;
+	sam->mfd_cells[0].pdata_size = sizeof(sam_plat_data);
+
+	sam->mfd_i2c_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_i2c_resources[0].flags = IORESOURCE_MEM;
+	sam->mfd_i2c_resources[1].flags = IORESOURCE_IRQ;
+
+	sam->mfd_cells[1].name = "gpio-sam";
+	sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[1].resources = sam->mfd_gpio_resources;
+	sam->mfd_cells[1].of_compatible = "jnx,gpio-sam";
+	sam->mfd_cells[1].platform_data = &sam_plat_data;
+	sam->mfd_cells[1].pdata_size = sizeof(sam_plat_data);
+
+	sam->mfd_gpio_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_gpio_resources[0].flags = IORESOURCE_MEM;
+	sam->mfd_gpio_resources[1].flags = IORESOURCE_IRQ;
+
+	sam->mfd_cells[2].name = "flash-sam";
+	sam->mfd_cells[2].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[2].resources = sam->mfd_mtd_resources;
+	sam->mfd_cells[2].of_compatible = "jnx,flash-sam";
+
+	sam->mfd_mtd_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_mtd_resources[0].flags = IORESOURCE_MEM;
+
+	/* Enable MSI, if it is supported by this version of SAM */
+	if (sam_supports_msi(sam) && !pci_enable_msi(pdev))
+		pci_set_master(pdev);
+
+	if (pdev->irq) {
+		err = devm_request_threaded_irq(dev, pdev->irq, NULL,
+						sam_irq_handler,
+						IRQF_ONESHOT,
+						dev_driver_string(dev), sam);
+		if (err) {
+			dev_err(dev, "failed to request irq %d\n", pdev->irq);
+			goto err_unmap;
+		}
+		err = sam_attach_irq(sam);
+		if (err) {
+			dev_err(dev, "failed to attach irq %d\n", pdev->irq);
+			goto err_irq;
+		}
+	}
+
+	err = mfd_add_devices(dev, pdev->bus->number, sam->mfd_cells,
+			      ARRAY_SIZE(sam->mfd_cells), &pdev->resource[0],
+			      0, NULL);
+	if (err < 0)
+		goto err_irq_attach;
+
+	/*
+	 * We don't know if this SAM supports MDIO.
+	 * Add client only if compatible node exists.
+	 */
+	sam_mfd_of_add_cell(dev, "jnx,mdio-sam", pdev->bus->number,
+				&pdev->resource[0]);
+
+	err = device_create_file(&pdev->dev, &dev_attr_version);
+	if (err < 0)
+		goto err_remove;
+
+	err = device_create_file(&pdev->dev, &dev_attr_board_id);
+	if (err < 0)
+		goto err_remove_files;
+
+	dev_info(dev,
+		 "SAM Jspec version %u.%u FPGA version %u.%u image 0x%x board 0x%x inserted\n",
+		 (sam->fpga_rev >> 24) & 0xff, (sam->fpga_rev >> 16) & 0xff,
+		 (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff,
+		 (sam->board_id >> 16) & 0xff,
+		 sam->board_id & 0xff);
+
+	return 0;
+
+err_remove_files:
+	device_remove_file(&pdev->dev, &dev_attr_version);
+	device_remove_file(&pdev->dev, &dev_attr_board_id);
+err_remove:
+	mfd_remove_devices(dev);
+err_irq_attach:
+	if (pdev->irq)
+		sam_detach_irq(sam);
+err_irq:
+	/* Call free_irq() before pci_disable_msi() */
+	if (pdev->irq)
+		devm_free_irq(&pdev->dev, pdev->irq, sam);
+err_unmap:
+	pci_disable_msi(pdev);
+	pci_iounmap(pdev, sam->membase);
+err_release:
+	pci_release_regions(pdev);
+err_disable:
+	pci_disable_device(pdev);
+	return err;
+}
+
+static void sam_fpga_remove(struct pci_dev *pdev)
+{
+	struct sam_fpga_data *sam = pci_get_drvdata(pdev);
+
+	mfd_remove_devices(&pdev->dev);
+	device_remove_file(&pdev->dev, &dev_attr_version);
+	device_remove_file(&pdev->dev, &dev_attr_board_id);
+	sam_disable_irq(&pdev->dev, SAM_IRQ_I2C, sam->irq_base, 0xffffffff);
+	sam_disable_irq(&pdev->dev, SAM_IRQ_GPIO, sam->irq_base, 0xffffffff);
+	if (pdev->irq) {
+		sam_detach_irq(sam);
+		devm_free_irq(&pdev->dev, pdev->irq, sam);
+	}
+	pci_disable_msi(pdev);
+	pci_iounmap(pdev, sam->membase);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+}
+
+static struct pci_device_id sam_fpga_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_X) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_OMEGA) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_PAM) },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, sam_fpga_ids);
+
+static struct pci_driver sam_fpga_driver = {
+	.name = SAM_FPGA_MODULE_NAME,
+	.id_table = sam_fpga_ids,
+	.probe = sam_fpga_probe,
+	.remove = sam_fpga_remove,
+};
+
+static int __init sam_fpga_init(void)
+{
+	pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+
+	return pci_register_driver(&sam_fpga_driver);
+}
+
+static void __exit sam_fpga_exit(void)
+{
+	struct sam_core_list *entry, *t;
+
+	list_for_each_entry_safe(entry, t, &sam_core_list, node) {
+		list_del(&entry->node);
+		kfree(entry);
+	}
+	pci_unregister_driver(&sam_fpga_driver);
+}
+
+module_init(sam_fpga_init);
+module_exit(sam_fpga_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
diff --git a/include/linux/mfd/sam.h b/include/linux/mfd/sam.h
new file mode 100644
index 0000000..d41b9fb
--- /dev/null
+++ b/include/linux/mfd/sam.h
@@ -0,0 +1,30 @@
+/*
+ * Functions exported from SAM mfd driver
+ *     Copyright (c) 2013 Juniper Networks <groeck@juniper.net>
+ *
+ * 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.
+ */
+
+#ifndef SAM_H
+#define SAM_H
+
+struct device;
+
+enum sam_irq_type {
+	SAM_IRQ_I2C = 0,
+	SAM_IRQ_GPIO,
+};
+
+struct sam_platform_data {
+	void (*enable_irq)(struct device *dev, enum sam_irq_type, int irq,
+			   u32 mask);
+	void (*disable_irq)(struct device *dev, enum sam_irq_type, int irq,
+			    u32 mask);
+	u32 (*irq_status)(struct device *dev, enum sam_irq_type, int irq);
+	void (*irq_status_clear)(struct device *dev, enum sam_irq_type, int irq,
+				 u32 mask);
+	int i2c_mux_channels;
+};
+#endif /* SAM_H */
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 02/10] mfd: sam: Add documentation for the SAM FPGA
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Add DT bindings document for the SAM MFD device.

Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 Documentation/devicetree/bindings/mfd/jnx-sam.txt | 94 +++++++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-sam.txt

diff --git a/Documentation/devicetree/bindings/mfd/jnx-sam.txt b/Documentation/devicetree/bindings/mfd/jnx-sam.txt
new file mode 100644
index 0000000..b4af7ea
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/jnx-sam.txt
@@ -0,0 +1,94 @@
+Device-Tree bindings for Juniper Networks SAM MFD
+
+Required properties:
+
+- compatible - Must be: "jnx,sam"
+
+Optional properties:
+
+- pma-coefficients: A set of tupples containing the configuration of the PMA.
+
+Device                   Description
+------                   -----------
+jnx,i2c-sam		: I2C mux driver
+jnx,gpio-sam		: GPIO block
+jnx,flash-sam		: MTD Flash
+jnx,mdio-sam		: MDIO interfaces
+
+All these optional nodes are described in their respective binding
+documents.
+
+Example node:
+
+pci-0000-10-00.0 {
+	compatible = "jnx,sam";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	pma-coefficients = <4 0x0>;
+
+	i2c-sam@10 {
+		compatible = "jnx,i2c-sam";
+		mux-channels = <2>;
+		master-offset = <0x10000>;
+	};
+
+	gpiogpqam0: gpio-sam@10 {
+		compatible = "jnx,gpio-sam";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-count = <297>;
+		interrupt-controller;
+		/*
+		* 1st cell: gpio interrupt status bit
+		* 2nd cell: 1st pin
+		* 3rd cell: # of pins
+		*/
+		gpio-interrupts =
+			<0 0 12>,   /* phy_int_monitor_en [16] */
+			<1 235 24>, /* qsfpp_fpga_int_monitor [17] */
+			<2 259 24>, /* qsfpp_fpga_modprs_monitor [18] */
+			<3 295 1>,  /* si5345_fpga_monitor [19] */
+			<4 294 1>;  /* fpc_pic_int_monitor [20] */
+	};
+
+	flash-sam@10 {
+		compatible = "jnx,flash-sam";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		partition@0 {
+		reg = <0x0 0x400000>;
+			label = "pic0-golden";
+			read-only;
+		};
+		partition@400000 {
+			reg = <0x400000 0x400000>;
+			label = "pic0-user";
+		};
+	};
+
+	mdio-sam@10 {
+		compatible = "jnx,mdio-sam";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0x40000>;
+
+		/* mii_bus types */
+		mdio0: mdio-sam@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0>;
+		};
+
+		mdio1: mdio-sam@4000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x4000>;
+		};
+
+		mdio2: mdio-sam@8000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x8000>;
+		};
+	};
+};
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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 03/10] i2c: Juniper SAM I2C driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Maryam Seraj <mseraj-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Introduce SAM I2C driver for the I2C interfaces on the Juniper
SAM FPGA.

Signed-off-by: Maryam Seraj <mseraj-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Debjit Ghosh <dghosh-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Rajat Jain <rajatjain-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 drivers/i2c/busses/Kconfig   |  11 +
 drivers/i2c/busses/Makefile  |   1 +
 drivers/i2c/busses/i2c-sam.c | 942 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 954 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-sam.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..eeac4b2 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -833,6 +833,17 @@ config I2C_SH7760
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-sh7760.
 
+config I2C_SAM
+	tristate "Juniper SAM FPGA I2C Controller"
+	select I2C_MUX
+	depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+	help
+	  This driver supports the I2C interfaces on the Juniper SAM FPGA
+	  which is present on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-sam.
+
 config I2C_SH_MOBILE
 	tristate "SuperH Mobile I2C Controller"
 	depends on HAS_DMA
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..b99b229 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -79,6 +79,7 @@ obj-$(CONFIG_I2C_QUP)		+= i2c-qup.o
 obj-$(CONFIG_I2C_RIIC)		+= i2c-riic.o
 obj-$(CONFIG_I2C_RK3X)		+= i2c-rk3x.o
 obj-$(CONFIG_I2C_S3C2410)	+= i2c-s3c2410.o
+obj-$(CONFIG_I2C_SAM)		+= i2c-sam.o
 obj-$(CONFIG_I2C_SH7760)	+= i2c-sh7760.o
 obj-$(CONFIG_I2C_SH_MOBILE)	+= i2c-sh_mobile.o
 obj-$(CONFIG_I2C_SIMTEC)	+= i2c-simtec.o
diff --git a/drivers/i2c/busses/i2c-sam.c b/drivers/i2c/busses/i2c-sam.c
new file mode 100644
index 0000000..1ec930a
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sam.c
@@ -0,0 +1,942 @@
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/i2c-mux.h>
+#include <linux/mfd/sam.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#define SAM_DEB1(dev, fmt, args...) do { \
+	if (sam_debug >= 1) \
+		dev_err(dev, fmt, ## args); \
+	} while (0)
+#define SAM_DEB2(dev, fmt, args...) do { \
+	if (sam_debug >= 2) \
+		dev_err(dev, fmt, ## args); \
+	} while (0)
+#define SAM_DEB3(dev, fmt, args...) do { \
+	if (sam_debug >= 3) \
+		dev_err(dev, fmt, ## args); } \
+	while (0)
+
+static int sam_debug;
+
+#define DRIVER_DESC			"SAM FPGA I2C Driver"
+#define DRIVER_VERSION			"0.2"
+#define DRIVER_AUTHOR			"Maryam Seraj <mseraj-3r7Miqu9kMnR7s880joybQ@public.gmane.org>"
+
+#define SAM_FPGA_MODULE_NAME		"i2c-sam"
+
+#define SAM_I2C_MUX_MAX_CHAN		8
+
+#define SAM_I2C_DEV_ADDR_MASK		0x7f
+#define SAM_I2C_TBL_ENTRY_CMDS_NUM	2
+
+#define SAM_I2C_CMD_TABLE_SZ		256
+
+#define SAM_I2C_STS_DONE		(1 << 0)
+#define SAM_I2C_STS_PRIO_DONE		(1 << 1)
+#define SAM_I2C_STS_RUNNING		(1 << 2)
+#define SAM_I2C_STS_PRIO_RUNNING	(1 << 3)
+#define SAM_I2C_STS_ERR			(1 << 4)
+#define SAM_I2C_STS_PRIO_ERR		(1 << 5)
+#define SAM_I2C_STS_RDY			(1 << 6)
+#define SAM_I2C_STS_TR_TIMEOUT		(1 << 7)
+#define SAM_I2C_STS_CMD_TIMEOUT		(1 << 8)
+#define SAM_I2C_STS_CMD_TABLE_TIMEOUT	(1 << 9)
+
+#define SAM_I2C_STS_CLEAR_MASK		(SAM_I2C_STS_DONE \
+					 | SAM_I2C_STS_PRIO_DONE \
+					 | SAM_I2C_STS_TR_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TABLE_TIMEOUT \
+					 | SAM_I2C_STS_ERR \
+					 | SAM_I2C_STS_PRIO_ERR)
+#define SAM_I2C_STS_CLEAR(x)	(((x) & ~0x3fb3) | SAM_I2C_STS_CLEAR_MASK)
+
+#define SAM_I2C_STS_TIMEOUT		(SAM_I2C_STS_TR_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TABLE_TIMEOUT)
+
+#define SAM_I2C_DONE(s)		((s) & (SAM_I2C_STS_DONE | SAM_I2C_STS_ERR \
+					| SAM_I2C_STS_TIMEOUT))
+
+#define SAM_I2C_CTRL_RESET		(1 << 0)
+#define SAM_I2C_CTRL_GO			(1 << 1)
+#define SAM_I2C_CTRL_PRIO_GO		(1 << 2)
+#define SAM_I2C_CTRL_ABORT		(1 << 3)
+#define SAM_I2C_CTRL_PRIO_ABORT		(1 << 4)
+
+/* Priority ctrl & status bits are offset +1 from the regular */
+#define STS_DONE(p)			BIT(0 + (p))
+#define STS_RUNNING(p)			BIT(2 + (p))
+#define STS_ERR(p)			BIT(4 + (p))
+#define CTRL_GO(p)			BIT(1 + (p))
+#define CTRL_ABORT(p)			BIT(3 + (p))
+#define STS_I2C_DONE(s, p)		((s) & (STS_DONE(p) | STS_ERR(p) \
+					 | SAM_I2C_STS_TIMEOUT))
+
+struct sam_i2c_data {
+	void __iomem *membase;
+	void __iomem *masterbase;
+	int first_master;
+	int num_master;
+	int mux_channels;
+	u32 *speed;		/* bit mask, 1 for high speed */
+	bool prio;
+	bool reverse_fill;
+	struct i2c_adapter **adap;
+	struct device *dev;
+	int irq;
+	struct sam_platform_data *pdata;
+};
+
+struct sam_i2c_adapdata {
+	void __iomem *membase;
+	void __iomem *masterbase;
+	int channel;
+	int mux_channels;
+	int mux_select;
+	u32 speed;			/* bit mask, same as above */
+	int prio;
+	bool reverse_fill;
+	struct i2c_adapter adap;
+	struct i2c_mux_core *muxc;
+	wait_queue_head_t wait;
+	u32 status;
+	u32 control;
+	bool done;
+	bool polling;
+};
+
+/**************************** i2c stuff *****************************/
+
+#define SAM_FPGA_MUX_NAME    "sam-fpga-mux"
+
+enum i2c_accel_cmd_bits_s {
+	I2C_WRITE,		/* 000 */
+	I2C_READ,		/* 001 */
+	I2C_WRITE_STOP,		/* 010 */
+	I2C_READ_STOP,		/* 011 */
+	I2C_WRITE_REPSTART,	/* 100 */
+	I2C_READ_REPSTART	/* 101 */
+};
+
+#define I2C_CMD_STOP_BIT	(1 << 1)
+#define I2C_CMD_REPSTART_BIT	(1 << 2)
+
+#define PER_MASTER_MEM		0x1000
+#define I2C_OPTIONS_BASE	0x2000
+#define MASTER_MEM_BASE		0x8000
+
+#define CMD_ADDR(s, idx)	((s)->masterbase + 0x0000 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define RES_ADDR(s, idx)	((s)->masterbase + 0x0400 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define CMD_PRI_ADDR(s, idx)	((s)->masterbase + 0x0800 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define RES_PRI_ADDR(s, idx)	((s)->masterbase + 0x0c00 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define CTRL_ADDR(s)		((s)->membase + 0x2400 + 8 * (s)->channel)
+#define STAT_ADDR(s)		((s)->membase + 0x2404 + 8 * (s)->channel)
+
+#define sam_i2c_stat_clear(adata, val)					\
+	do {								\
+		iowrite32(SAM_I2C_STS_CLEAR(val), STAT_ADDR(adata));	\
+		ioread32(STAT_ADDR(adata));				\
+	} while (0)
+
+static int sam_i2c_wait_rdy(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	u32 val;
+	unsigned long timeout;
+
+	val = ioread32(STAT_ADDR(adata));
+	if ((val & SAM_I2C_STS_RDY) && !(val & STS_RUNNING(adata->prio)))
+		return 0;
+
+	if (val & STS_RUNNING(adata->prio)) {
+		iowrite32(adata->control | CTRL_ABORT(adata->prio),
+			  CTRL_ADDR(adata));
+		ioread32(CTRL_ADDR(adata));
+		udelay(10);
+		iowrite32(adata->control, CTRL_ADDR(adata));
+		ioread32(CTRL_ADDR(adata));
+		udelay(100);
+	}
+
+	timeout = jiffies + adap->timeout;
+	adata->status = 0;
+	do {
+		val = ioread32(STAT_ADDR(adata)) | adata->status;
+		if ((val & SAM_I2C_STS_RDY) &&
+		    !(val & STS_RUNNING(adata->prio)))
+			return 0;
+
+		if (adata->polling) {
+			udelay(50);
+		} else {
+			adata->done = false;
+			adata->status = 0;
+			wait_event_timeout(adata->wait, adata->done,
+					   adap->timeout);
+		}
+
+	} while (time_before(jiffies, timeout));
+
+	return -EBUSY;
+}
+
+int sam_i2c_calc_start_entry(struct i2c_msg *msgs, int num, int reverse_fill)
+{
+	int i, len = 0;
+
+	/* Filling the table from start (offset 0) ? */
+	if (!reverse_fill)
+		return 0;
+
+	/* Calculate required table size from the message sizes */
+	if (num == 1 && msgs[0].len == 0)
+		len = 1;
+	else
+		for (i = 0; i < num; i++)
+			if (msgs[i].flags & I2C_M_RECV_LEN)
+				len += (I2C_SMBUS_BLOCK_MAX + 1);
+			else
+				len += msgs[i].len;
+
+	if (len > SAM_I2C_CMD_TABLE_SZ * 2)
+		return -E2BIG;
+
+	/* Always start from Command 0 */
+	return (SAM_I2C_CMD_TABLE_SZ * 2 - len) / 2;
+}
+
+static int sam_i2c_cmd_init(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			    int num)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	struct device *dev = &adap->dev;
+	struct i2c_msg *msg;
+	int curmsg, cmds = 0;
+	int addr, i, len = 0, table_offset = 0;
+	u32 cmd_entry;
+	bool read;
+	u8 group;
+	u32 idx;
+	u8 cmd;
+
+	group = adata->mux_select;
+	addr = msgs[0].addr & SAM_I2C_DEV_ADDR_MASK;
+	cmd_entry = (group << 29) | (addr << 22);
+
+	/* Zero CMD table */
+	memset_io(CMD_ADDR(adata, 0), 0, SAM_I2C_CMD_TABLE_SZ * sizeof(u32));
+
+	idx = sam_i2c_calc_start_entry(msgs, num, adata->reverse_fill);
+	if (idx < 0)
+		return idx;
+
+	table_offset = idx;
+
+	for (curmsg = 0; curmsg < num; curmsg++) {
+		msg = &msgs[curmsg];
+		read = msg->flags & I2C_M_RD;
+
+		SAM_DEB1(dev, "  [%02d] %s %d bytes addr %#02x\n",
+			 curmsg, read ? "RD" : "WR",
+			 msg->len, addr);
+
+		len = msg->len;
+		if (len == 0 && curmsg == 0 && num == 1) {
+			/*
+			 * SMBus quick command, special case
+			 *
+			 * Always read; we don't want to risk that
+			 * a "WRITE_STOP" command as only command
+			 * would actually write anything into the chip.
+			 */
+			cmd = I2C_READ_STOP;
+			cmd_entry |= cmd << 19;
+			cmds = 1;
+			break;
+		}
+		/*
+		 * If the message is a SMBus block read message, read up to the
+		 * maximum block length. The command should succeed at least up
+		 * to the real block length, which will be returned in the first
+		 * data byte.
+		 */
+		if (read && (msg->flags & I2C_M_RECV_LEN))
+			len = I2C_SMBUS_BLOCK_MAX + 1;
+		for (i = 0; i < len; i++) {
+			cmd = read ? I2C_READ : I2C_WRITE;
+			if (i == len - 1) {
+				if (curmsg == num - 1)
+					cmd |= I2C_CMD_STOP_BIT;
+				else
+					cmd |= I2C_CMD_REPSTART_BIT;
+			}
+
+			if ((cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM) == 0) {
+				/* cmd0/data0 */
+				cmd_entry |= cmd << 19;
+				if (!read)
+					cmd_entry |= (msg->buf[i] << 11);
+			} else {
+				/* cmd1/data1 */
+				cmd_entry |= cmd << 8;
+				if (!read)
+					cmd_entry |= msg->buf[i];
+			}
+			cmds++;
+			if (cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM == 0) {
+				/*
+				 * One command entry is done!
+				 * Write it to the command table and start
+				 * putting together the next entry for
+				 * the same current i2c command, if needed.
+				 */
+				SAM_DEB2(dev,
+					 "reg-offset cmd_entry = 0x%08x, cmds = %d, @ %p\n",
+					 cmd_entry, cmds, CMD_ADDR(adata, idx));
+
+				iowrite32(cmd_entry, CMD_ADDR(adata, idx));
+				ioread32(CMD_ADDR(adata, idx));
+				idx++;
+
+				/* clean out everything but group and address */
+				cmd_entry &= 0xFFC00000;
+			}
+		}
+	}
+	/*
+	 * Zero out any remaining cmd/data part of the last
+	 * command entry into the command table of the given
+	 * master before kicking off the i2c engine for this
+	 * master.
+	 */
+	if (cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM != 0) {
+		cmd_entry &= 0xFFFFF800;
+
+		SAM_DEB1(dev, "rest of cmd_entry = 0x%08x, cmds = %d, @ %p\n",
+			 cmd_entry, cmds, CMD_ADDR(adata, idx));
+
+		if (idx >= SAM_I2C_CMD_TABLE_SZ)
+			return -E2BIG;
+
+		iowrite32(cmd_entry, CMD_ADDR(adata, idx));
+		ioread32(CMD_ADDR(adata, idx));
+		idx++;
+		if (idx < SAM_I2C_CMD_TABLE_SZ) {
+			iowrite32(0, CMD_ADDR(adata, idx));
+			ioread32(CMD_ADDR(adata, idx));
+		}
+	}
+	return table_offset;
+}
+
+static u32 i2c_sam_wait_results(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	struct device *dev = &adap->dev;
+	u32 val;
+
+	if (adata->polling) {
+		unsigned long timeout = jiffies + adap->timeout;
+
+		/*
+		 * Poll for results.
+		 * Only wait a short time per loop to avoid long access times.
+		 * At 100kHz, a single byte transfer takes about 100 uS,
+		 * so we don't want to wait much longer than that.
+		 */
+		do {
+			/*
+			 * We should really use usleep_range() here, but that
+			 * does not work and causes the system to lock up.
+			 * msleep() is slow, so use an active wait loop instead.
+			 */
+			udelay(50);
+			val = ioread32(STAT_ADDR(adata));
+			SAM_DEB1(dev, "status = 0x%08x @%p\n",
+				 val, STAT_ADDR(adata));
+			if (STS_I2C_DONE(val, adata->prio))
+				break;
+		} while (time_before(jiffies, timeout));
+	} else {
+		if (!wait_event_timeout(adata->wait, adata->done,
+					adap->timeout))
+			val = ioread32(STAT_ADDR(adata));
+		else
+			val = ioread32(STAT_ADDR(adata)) | adata->status;
+	}
+
+	sam_i2c_stat_clear(adata, val);
+
+	return val;
+}
+
+static int sam_i2c_read_data(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			     int num, int table_offset)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int offset = table_offset * 2;
+	struct device *dev = &adap->dev;
+	struct i2c_msg *msg;
+	u32 val, data;
+	bool valid;
+	int i, len;
+
+	msg = &msgs[num - 1];
+	len = msg->len;
+
+	if (num > 1)
+		offset += msgs[0].len;
+
+	SAM_DEB1(dev, "Reading %d bytes\n", len);
+
+	for (i = offset & 0xfffffffe; i < len + offset; i++) {
+		val = ioread32(RES_ADDR(adata, i / 2));
+		SAM_DEB2(dev, "data = 0x%08x @%p\n",
+			 val, RES_ADDR(adata, i / 2));
+		if (i >= offset) {
+			data = (val >> 11) & 0xff;	/* data_0  */
+			valid = val & 0x00200000;	/* valid_0 */
+			if (!valid)
+				return -EIO;
+			msg->buf[i - offset] = data;
+			if (i == offset &&
+			    (msg->flags & I2C_M_RECV_LEN)) {
+				if (data == 0 ||
+				    data > I2C_SMBUS_BLOCK_MAX)
+					return -EPROTO;
+				SAM_DEB1(dev, "SMBus block data, %d bytes\n",
+					 data);
+				len += data;
+			}
+		}
+		if (++i >= len + offset)
+			break;
+		if (i >= offset) {
+			data = val & 0xff;		/* data_1  */
+			valid = val & 0x00000400;	/* valid_1 */
+			if (!valid)
+				return -EIO;
+			msg->buf[i - offset] = data;
+			if (i == offset &&
+			    (msg->flags & I2C_M_RECV_LEN)) {
+				if (data == 0 ||
+				    data > I2C_SMBUS_BLOCK_MAX)
+					return -EPROTO;
+				SAM_DEB1(dev, "SMBus block data, %d bytes\n",
+					 data);
+				len += data;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int sam_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int ret, table_offset;
+	u32 val;
+
+	ret = sam_i2c_wait_rdy(adap);
+	if (ret < 0)
+		return ret;
+
+	ret = sam_i2c_cmd_init(adap, msgs, num);
+	if (ret < 0)
+		return ret;
+	table_offset = ret & 0xff;
+
+	sam_i2c_stat_clear(adata, ioread32(STAT_ADDR(adata)));
+
+	/*
+	 * Done with setting up the command table, now kick
+	 * off this transaction before waiting for the results.
+	 */
+
+	adata->done = false;
+	adata->status = 0;
+
+	iowrite32(adata->control | CTRL_GO(adata->prio) | table_offset << 24,
+		  CTRL_ADDR(adata));
+	ioread32(CTRL_ADDR(adata));	/* read back to flush */
+
+	val = i2c_sam_wait_results(adap);
+	if (val & STS_ERR(adata->prio)) {
+		dev_err(&adap->dev, "i2c transaction error\n");
+		return -EIO;
+	}
+	if ((val & SAM_I2C_STS_TIMEOUT) || !(val & STS_DONE(adata->prio))) {
+		SAM_DEB1(&adap->dev,
+			 "i2c transaction timeout, status=0x%x\n", val);
+		return -ETIMEDOUT;
+	}
+
+	SAM_DEB1(&adap->dev, "i2c transaction completed!!!\n");
+
+	/* SMBus quick command, special case */
+	if (num == 1 && msgs[0].len == 0) {
+		val = ioread32(RES_ADDR(adata, table_offset));
+		SAM_DEB1(&adap->dev, "quick cmd: data = 0x%08x\n", val);
+		return val & 0x00200000 ? 1 : -EIO;
+	}
+	/*
+	 * If this was a "read" request, go get the data.
+	 * Otherwise, we're done here!
+	 */
+	if (msgs[num - 1].flags & I2C_M_RD) {
+		ret = sam_i2c_read_data(adap, msgs, num, table_offset);
+		if (ret < 0)
+			return ret;
+	}
+
+	SAM_DEB1(&adap->dev, "Returning %d\n", num);
+	return num;
+}
+
+static u32 sam_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
+	  | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm sam_i2c_algo = {
+	.master_xfer = sam_i2c_xfer,
+	.functionality = sam_i2c_func,
+};
+
+/*
+ * This is where the SAM I2C-accel needs to be initialized.
+ */
+static int sam_i2c_init_adap(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	u32 val;
+
+	SAM_DEB1(&adap->dev, "bus_mstr = %d\n", adata->channel);
+
+	val = 0x00000f00;
+	val |= (adata->speed << 12) & 0x0000f000;
+	adata->control = val;
+	/*
+	 * Set the i2c speed for ALL ports of this master and enable them all
+	 */
+	iowrite32(val, CTRL_ADDR(adata));
+
+	return 0;
+}
+
+int sam_i2c_add_numbered_bus(struct i2c_adapter *adap)
+{
+	int rval;
+
+	SAM_DEB1(&adap->dev, "%s", __func__);
+
+	rval = sam_i2c_init_adap(adap);
+	if (rval)
+		return rval;
+
+	return i2c_add_numbered_adapter(adap);
+}
+/********** end of i2c adapter/accel stuff ************************************/
+
+/********** start of i2c accel mux/group stuff ********************************/
+static int sam_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct sam_i2c_adapdata *adata = i2c_mux_priv(muxc);
+
+	if (!adata)
+		return -ENODEV;
+	adata->mux_select = chan;
+
+	return 0;
+}
+
+static int sam_i2c_mux_init(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int chan, ret;
+
+	SAM_DEB1(&adap->dev, "%s Begin\n", __func__);
+
+	adata->muxc = i2c_mux_alloc(adap, &adap->dev, adata->mux_channels, 0, 0,
+				   sam_i2c_mux_select, NULL);
+	if (!adata->muxc)
+		return -ENOMEM;
+	adata->muxc->priv = adata;
+
+	for (chan = 0; chan < adata->mux_channels; chan++) {
+		ret = i2c_mux_add_adapter(adata->muxc, 0, chan, 0);
+		if (ret) {
+			dev_err(&adap->dev, "Failed to add adapter %d\n", chan);
+			i2c_mux_del_adapters(adata->muxc);
+			return ret;
+		}
+	}
+
+	SAM_DEB1(&adap->dev, "%s End\n", __func__);
+
+	return 0;
+}
+
+/********** end of i2c accel mux/group stuff **********************************/
+
+static void sam_core_of_init_options(struct device *dev,
+				     struct sam_i2c_data *sam)
+{
+	const __be32 *opt;
+	int len, i;
+
+	opt = of_get_property(dev->of_node, "i2c-options", &len);
+	if (!opt || len > 4 * sizeof(u32))
+		return;
+
+	for (i = 0; i < len; i += sizeof(u32))
+		iowrite32(be32_to_cpup(opt++),
+			  sam->membase + I2C_OPTIONS_BASE + i);
+}
+
+static int sam_core_of_init(struct device *dev, struct sam_i2c_data *sam,
+			    struct resource *res)
+{
+	int err;
+	int num_master, max_masters;
+	u32 speed, mux_channels, val, master_offset = MASTER_MEM_BASE;
+	int i, len;
+	const __be32 *bus_range, *regs;
+	struct device_node *child;
+	u32 master, mux;
+
+	num_master = ioread32(sam->membase + 0x0c);
+	sam->first_master = -1;
+
+	err = of_property_read_u32(dev->of_node, "master-offset", &val);
+	if (!err)
+		if (val + PER_MASTER_MEM <= resource_size(res)) {
+			master_offset = val;
+			dev_info(dev, "Master offset changed to 0x%08x", val);
+		}
+
+	sam->masterbase = sam->membase + master_offset;
+	max_masters = (resource_size(res) - master_offset) / PER_MASTER_MEM;
+
+	if (num_master <= 0 || num_master > max_masters)
+		return -EINVAL;
+	sam->num_master = num_master;
+
+	bus_range = of_get_property(dev->of_node, "i2c-bus-range", &len);
+	if (bus_range) {
+		if (len != 2 * sizeof(u32))
+			return -EINVAL;
+		sam->first_master = be32_to_cpu(bus_range[0]);
+		num_master = be32_to_cpu(bus_range[1]) - sam->first_master + 1;
+		if (num_master <= 0 || num_master > sam->num_master)
+			return -EINVAL;
+		sam->num_master = num_master;
+	}
+
+	sam->speed = devm_kcalloc(dev, sam->num_master, sizeof(u32),
+				  GFP_KERNEL);
+	if (!sam->speed)
+		return -ENOMEM;
+
+	sam->adap = devm_kcalloc(dev, sam->num_master,
+				 sizeof(struct i2c_adapter *), GFP_KERNEL);
+	if (!sam->adap)
+		return -ENOMEM;
+
+	/* Set default i2c speed to 100kHz for all channels */
+	for (i = 0; i < sam->num_master; i++)
+		sam->speed[i] = (1 << SAM_I2C_MUX_MAX_CHAN) - 1;
+
+	if (!dev->of_node) {
+		/*
+		 * Use default from platform data if the there is no FDT.
+		 * TODO: Use FDT once it is available
+		 */
+		sam->mux_channels = sam->pdata ?
+			sam->pdata->i2c_mux_channels : 2;
+		dev_warn(dev,
+			 "No FDT node for SAM, using default (%d)\n",
+			 sam->mux_channels);
+		return 0;
+	}
+
+	err = of_property_read_u32(dev->of_node, "mux-channels", &mux_channels);
+	if (err || !mux_channels || mux_channels > SAM_I2C_MUX_MAX_CHAN)
+		return -EINVAL;
+	sam->mux_channels = mux_channels;
+	sam->reverse_fill = of_property_read_bool(dev->of_node, "reverse-fill");
+	sam->prio = of_property_read_bool(dev->of_node, "priority-tables");
+	/* Priority tables are offset with 0x800 from the regular tables */
+	if (sam->prio)
+		sam->masterbase += 0x800;
+
+	for_each_child_of_node(dev->of_node, child) {
+		regs = of_get_property(child, "reg", &len);
+		if (!regs || len != 2 * sizeof(u32)) {
+			dev_err(dev, "did not find reg property or bad size\n");
+			return -EINVAL;
+		}
+		err = of_property_read_u32(child, "speed", &speed);
+		if (err || !speed)
+			continue;
+		if (speed != 100000 && speed != 400000) {
+			dev_err(dev, "Bad speed %d\n", speed);
+			return -EINVAL;
+		}
+		master = be32_to_cpu(regs[0]);
+		mux = be32_to_cpu(regs[1]);
+		if (master >= sam->num_master || mux >= sam->mux_channels) {
+			dev_err(dev,
+				"master/mux %d/%d out of range\n",
+				master, mux);
+			return -EINVAL;
+		}
+		if (speed == 400000)
+			sam->speed[master] &= ~(1 << mux);
+	}
+
+	sam_core_of_init_options(dev, sam);
+
+	return 0;
+}
+
+static struct i2c_adapter *sam_i2c_init_one(struct sam_i2c_data *sam,
+					    int channel)
+{
+	struct device *dev = sam->dev;
+	struct sam_i2c_adapdata *adata;
+	int err;
+
+	adata = devm_kzalloc(dev, sizeof(*adata), GFP_KERNEL);
+	if (!adata)
+		return ERR_PTR(-ENOMEM);
+
+	init_waitqueue_head(&adata->wait);
+	adata->adap.owner = THIS_MODULE;
+	adata->adap.algo = &sam_i2c_algo;
+	adata->adap.nr = (sam->first_master >= 0) ?
+			  sam->first_master + channel : -1;
+	adata->adap.timeout = HZ / 5;
+	adata->channel = channel;
+	adata->mux_channels = sam->mux_channels;
+	adata->membase = sam->membase;
+	adata->masterbase = sam->masterbase;
+	adata->speed = sam->speed[channel];
+	adata->polling = (sam->irq < 0);
+	adata->reverse_fill = sam->reverse_fill;
+	adata->prio = sam->prio & 1;
+	i2c_set_adapdata(&adata->adap, adata);
+	snprintf(adata->adap.name, sizeof(adata->adap.name),
+		 "%s:%d", dev_name(dev), channel);
+
+	adata->adap.dev.parent = dev;
+	err = sam_i2c_add_numbered_bus(&adata->adap);
+	if (err)
+		goto error;
+
+	err = sam_i2c_mux_init(&adata->adap);
+	if (err)
+		goto err_remove;
+
+	return &adata->adap;
+
+err_remove:
+	i2c_del_adapter(&adata->adap);
+error:
+	return ERR_PTR(err);
+}
+
+static void sam_i2c_cleanup_one(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+
+	i2c_mux_del_adapters(adata->muxc);
+	i2c_del_adapter(adap);
+}
+
+static irqreturn_t sam_i2c_irq_handler(int irq, void *data)
+{
+	struct sam_i2c_data *sam = data;
+	struct sam_platform_data *pdata = sam->pdata;
+	struct sam_i2c_adapdata *adata;
+	int bit;
+	u32 val, status, wake_status;
+	u32 mask = (1 << sam->num_master) - 1;
+
+	status = pdata->irq_status(sam->dev->parent, SAM_IRQ_I2C,
+				   sam->irq) & mask;
+	do {
+		wake_status = status;
+		/* Clear the 'done' bits */
+		while (status) {
+			bit = __ffs(status);
+			status &= ~BIT(bit);
+			adata = i2c_get_adapdata(sam->adap[bit]);
+			val = ioread32(STAT_ADDR(adata));
+			if (STS_I2C_DONE(val, adata->prio)) {
+				sam_i2c_stat_clear(adata, val);
+				if (!adata->done) {
+					adata->done = true;
+					adata->status = val;
+				}
+			}
+		}
+		/*
+		 * Clear the status bits *after* the done status is cleared,
+		 * as otherwise this will generate another unused interrupt.
+		 * On the CBC this will also clear the MSI INT_ACCELL.
+		 */
+		pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_I2C, sam->irq,
+					wake_status);
+
+		/* Now wake the blocked transactions */
+		while (wake_status) {
+			bit = __ffs(wake_status);
+			wake_status &= ~BIT(bit);
+			adata = i2c_get_adapdata(sam->adap[bit]);
+			wake_up(&adata->wait);
+		}
+
+		/* Recheck the status again, as we might miss an MSI in
+		 * the window from the last check and the clear of the
+		 * pending interrupts. This does not affect the SAM INTx.
+		 */
+		status = pdata->irq_status(sam->dev->parent, SAM_IRQ_I2C,
+					   sam->irq) & mask;
+	} while (status);
+
+	return IRQ_HANDLED;
+}
+
+static const struct of_device_id sam_i2c_of_match[] = {
+	{ .compatible = "jnx,i2c-sam",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, sam_i2c_of_match);
+
+
+static int sam_i2c_probe(struct platform_device *pdev)
+{
+	int err;
+	int i;
+	struct sam_i2c_data *sam;
+	struct i2c_adapter *adap;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+	struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+	/*
+	 * Allocate memory for the SAM FPGA info
+	 */
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (!sam)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, sam);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	if (pdata)
+		sam->irq = platform_get_irq(pdev, 0);
+
+	sam->membase = devm_ioremap_nocache(dev, res->start,
+					    resource_size(res));
+	if (!sam->membase)
+		return -ENOMEM;
+
+	sam->dev = dev;
+	sam->pdata = pdata;
+
+	err = sam_core_of_init(dev, sam, res);
+	if (err)
+		return err;
+
+	/***** i2c init *****/
+
+	for (i = 0; i < sam->num_master; i++) {
+		adap = sam_i2c_init_one(sam, i);
+		if (IS_ERR(adap)) {
+			err = PTR_ERR(adap);
+			dev_err(dev,
+				"Failed to initialize adapter %d [base %d, index %d]: %d\n",
+				sam->first_master + i, sam->first_master, i,
+				err);
+			goto err_remove;
+		}
+		sam->adap[i] = adap;
+	}
+
+	if (sam->irq >= 0) {
+		err = devm_request_any_context_irq(dev, sam->irq,
+						sam_i2c_irq_handler, 0,
+						dev_name(dev), sam);
+		if (err < 0) {
+			dev_err(dev, "Failed to request interrupt %d: %d\n",
+				sam->irq, err);
+			goto err_remove;
+		}
+		pdata->enable_irq(dev->parent, SAM_IRQ_I2C, sam->irq,
+				  (1 << sam->num_master) - 1);
+	}
+
+	return 0;
+
+err_remove:
+	for (i--; i >= 0; i--)
+		sam_i2c_cleanup_one(sam->adap[i]);
+	return err;
+}
+
+static int sam_i2c_remove(struct platform_device *pdev)
+{
+	struct sam_i2c_data *sam = platform_get_drvdata(pdev);
+	struct sam_platform_data *pdata = sam->pdata;
+	int i;
+
+	if (sam->irq >= 0 && pdata)
+		pdata->disable_irq(pdev->dev.parent, SAM_IRQ_I2C, sam->irq,
+			(1 << sam->num_master) - 1);
+	for (i = 0; i < sam->num_master; i++)
+		sam_i2c_cleanup_one(sam->adap[i]);
+
+	return 0;
+}
+
+static struct platform_driver sam_i2c_driver = {
+	.driver = {
+		.name   = "i2c-sam",
+		.owner  = THIS_MODULE,
+		.of_match_table = sam_i2c_of_match,
+	},
+	.probe  = sam_i2c_probe,
+	.remove = sam_i2c_remove,
+};
+
+module_platform_driver(sam_i2c_driver);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+
+module_param(sam_debug, int, S_IWUSR | S_IRUGO);
-- 
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 05/10] gpio: Introduce SAM gpio driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

The SAM GPIO IP block is present in the Juniper PTX series
of routers as part of the SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/gpio/Kconfig    |  11 +
 drivers/gpio/Makefile   |   1 +
 drivers/gpio/gpio-sam.c | 707 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 719 insertions(+)
 create mode 100644 drivers/gpio/gpio-sam.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9c91de6..c25dbe9 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -384,6 +384,17 @@ config GPIO_RCAR
 	help
 	  Say yes here to support GPIO on Renesas R-Car SoCs.
 
+config GPIO_SAM
+	tristate "SAM FPGA GPIO"
+	depends on MFD_JUNIPER_SAM
+	default y if MFD_JUNIPER_SAM
+	help
+	  This driver supports the GPIO interfaces on the SAM FPGA which is
+	  present on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called gpio-sam.
+
 config GPIO_SPEAR_SPICS
 	bool "ST SPEAr13xx SPI Chip Select as GPIO support"
 	depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d397ea5..6691d8c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
 obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
 obj-$(CONFIG_GPIO_RCAR)		+= gpio-rcar.o
 obj-$(CONFIG_ARCH_SA1100)	+= gpio-sa1100.o
+obj-$(CONFIG_GPIO_SAM)		+= gpio-sam.o
 obj-$(CONFIG_GPIO_SCH)		+= gpio-sch.o
 obj-$(CONFIG_GPIO_SCH311X)	+= gpio-sch311x.o
 obj-$(CONFIG_GPIO_SODAVILLE)	+= gpio-sodaville.o
diff --git a/drivers/gpio/gpio-sam.c b/drivers/gpio/gpio-sam.c
new file mode 100644
index 0000000..5082050
--- /dev/null
+++ b/drivers/gpio/gpio-sam.c
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2012 - 2015 Juniper Networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mfd/sam.h>
+
+/* gpio status/configuration */
+#define SAM_GPIO_NEG_EDGE	(1 << 8)
+#define SAM_GPIO_NEG_EDGE_EN	(1 << 7)
+#define SAM_GPIO_POS_EDGE	(1 << 6)
+#define SAM_GPIO_POS_EDGE_EN	(1 << 5)
+#define SAM_GPIO_BLINK		(1 << 4)
+#define SAM_GPIO_OUT		(1 << 3)
+#define SAM_GPIO_OUT_TS		(1 << 2)
+#define SAM_GPIO_DEBOUNCE_EN	(1 << 1)
+#define SAM_GPIO_IN		(1 << 0)
+
+#define SAM_GPIO_BASE		0x1000
+
+#define SAM_MAX_NGPIO		512
+
+#define SAM_GPIO_ADDR(addr, nr)	((addr) + SAM_GPIO_BASE + (nr) * sizeof(u32))
+
+struct sam_gpio_irq_group {
+	int start;		/* 1st gpio pin */
+	int count;		/* # of pins in group */
+	int num_enabled;	/* # of enabled interrupts */
+};
+
+/**
+ * struct sam_gpio - GPIO private data structure.
+ * @base:			PCI base address of Memory mapped I/O register.
+ * @dev:			Pointer to device structure.
+ * @gpio:			Data for GPIO infrastructure.
+ * @gpio_base:			1st gpio pin
+ * @gpio_count:			# of gpio pins
+ * @irq_lock:			Lock used by interrupt subsystem
+ * @domain:			Pointer to interrupt domain
+ * @irq:			Interrupt # from parent
+ * @irq_high:			Second interrupt # from parent
+ *				(currently unused)
+ * @irq_group:			Interrupt group descriptions
+ *				(one group per interrupt bit)
+ * @irq_type:			The interrupt type for each gpio pin
+ */
+struct sam_gpio {
+	void __iomem *base;
+	struct device *dev;
+	struct gpio_chip gpio;
+	int gpio_base;
+	int gpio_count;
+	struct mutex irq_lock;
+	struct irq_domain *domain;
+	int irq;
+	int irq_high;
+	struct sam_gpio_irq_group irq_group[18];
+	u8 irq_type[SAM_MAX_NGPIO];
+	struct sam_platform_data *pdata;
+	const char **names;
+	u32 *export_flags;
+};
+#define to_sam(chip)	container_of((chip), struct sam_gpio, gpio)
+
+static void sam_gpio_bitop(struct sam_gpio *sam, unsigned int nr,
+			   u32 bit, bool set)
+{
+	u32 reg;
+
+	reg = ioread32(SAM_GPIO_ADDR(sam->base, nr));
+	if (set)
+		reg |= bit;
+	else
+		reg &= ~bit;
+	iowrite32(reg, SAM_GPIO_ADDR(sam->base, nr));
+	ioread32(SAM_GPIO_ADDR(sam->base, nr));
+}
+
+static int sam_gpio_debounce(struct gpio_chip *chip, unsigned int nr,
+			     unsigned int debounce)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_DEBOUNCE_EN, debounce);
+
+	return 0;
+}
+
+static void sam_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+}
+
+static int sam_gpio_get(struct gpio_chip *chip, unsigned int nr)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	return !!(ioread32(SAM_GPIO_ADDR(sam->base, nr)) & SAM_GPIO_IN);
+}
+
+static int sam_gpio_direction_output(struct gpio_chip *chip, unsigned int nr,
+				     int val)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, false);
+	return 0;
+}
+
+static int sam_gpio_direction_input(struct gpio_chip *chip, unsigned int nr)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, true);
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, false);
+	return 0;
+}
+
+static void sam_gpio_setup(struct sam_gpio *sam)
+{
+	struct gpio_chip *chip = &sam->gpio;
+
+	chip->parent = sam->dev;
+	chip->label = dev_name(sam->dev);
+	chip->owner = THIS_MODULE;
+	chip->direction_input = sam_gpio_direction_input;
+	chip->get = sam_gpio_get;
+	chip->direction_output = sam_gpio_direction_output;
+	chip->set = sam_gpio_set;
+	chip->set_debounce = sam_gpio_debounce;
+	chip->dbg_show = NULL;
+	chip->base = sam->gpio_base;
+	chip->ngpio = sam->gpio_count;
+#ifdef CONFIG_OF_GPIO
+	chip->of_node = sam->dev->of_node;
+#endif
+	chip->names = sam->names;
+}
+
+static int sam_of_get_exports(struct device *dev, struct sam_gpio *sam)
+{
+	struct device_node *child, *exports;
+	int err = 0;
+
+	if (dev->of_node == NULL)
+		return 0;	/* No FDT node, we are done */
+
+	exports = of_get_child_by_name(dev->of_node, "gpio-exports");
+	if (exports == NULL)
+		return 0;	/* No exports, we are done */
+
+	if (of_get_child_count(exports) == 0)
+		return 0;	/* No children, we are done */
+
+	sam->names = devm_kzalloc(dev, sizeof(char *) * sam->gpio_count,
+				  GFP_KERNEL);
+	if (sam->names == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+	sam->export_flags =
+		devm_kzalloc(dev, sizeof(u32) * sam->gpio_count, GFP_KERNEL);
+	if (sam->export_flags == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+	for_each_child_of_node(exports, child) {
+		const char *label;
+		u32 pin, flags;
+
+		label = of_get_property(child, "label", NULL) ? : child->name;
+		err = of_property_read_u32_index(child, "pin", 0, &pin);
+		if (err)
+			break;
+		if (pin >= sam->gpio_count) {
+			err = -EINVAL;
+			break;
+		}
+		err = of_property_read_u32_index(child, "pin", 1, &flags);
+		if (err)
+			break;
+		/*
+		 * flags:
+		 * GPIOF_DIR_IN			bit 0=1
+		 * GPIOF_DIR_OUT		bit 0=0
+		 *	GPIOF_INIT_HIGH		bit 1=1
+		 * GPIOF_ACTIVE_LOW		bit 2=1
+		 * GPIOF_OPEN_DRAIN		bit 3=1
+		 * GPIOF_OPEN_SOURCE		bit 4=1
+		 * GPIOF_EXPORT			bit 5=1
+		 * GPIOF_EXPORT_CHANGEABLE	bit 6=1
+		 */
+		sam->names[pin] = label;
+		sam->export_flags[pin] = flags;
+	}
+error:
+	of_node_put(exports);
+	return err;
+}
+
+static int sam_gpio_of_init(struct device *dev, struct sam_gpio *sam)
+{
+	int err;
+	u32 val;
+	const u32 *igroup;
+	u32 group, start, count;
+	int i, iglen, ngpio;
+
+	if (of_have_populated_dt() && !dev->of_node) {
+		dev_err(dev, "No device node\n");
+		return -ENODEV;
+	}
+
+	err = of_property_read_u32(dev->of_node, "gpio-base", &val);
+	if (err)
+		val = -1;
+	sam->gpio_base = val;
+
+	err = of_property_read_u32(dev->of_node, "gpio-count", &val);
+	if (!err) {
+		if (val > SAM_MAX_NGPIO)
+			val = SAM_MAX_NGPIO;
+		sam->gpio_count = val;
+	}
+	/* validate gpio_count against chip data. Abort if chip data is bad. */
+	ngpio = ioread32(sam->base + 2 * sizeof(u32)) & 0xffff;
+	if (!ngpio || ngpio > SAM_MAX_NGPIO)
+		return -ENODEV;
+
+	if (!sam->gpio_count || sam->gpio_count > ngpio)
+		sam->gpio_count = ngpio;
+
+	igroup = of_get_property(dev->of_node, "gpio-interrupts", &iglen);
+	if (igroup) {
+		iglen /= sizeof(u32);
+		if (iglen < 3 || iglen % 3)
+			return -EINVAL;
+		iglen /= 3;
+		for (i = 0; i < iglen; i++) {
+			group = be32_to_cpu(igroup[i * 3]);
+			if (group >= ARRAY_SIZE(sam->irq_group))
+				return -EINVAL;
+			start = be32_to_cpu(igroup[i * 3 + 1]);
+			count = be32_to_cpu(igroup[i * 3 + 2]);
+			if (start >= sam->gpio_count || count == 0 ||
+			    start + count > sam->gpio_count)
+				return -EINVAL;
+			sam->irq_group[group].start = start;
+			sam->irq_group[group].count = count;
+		}
+	}
+
+	err = sam_of_get_exports(dev, sam);
+	return err;
+}
+
+static int sam_gpio_pin_to_irq_bit(struct sam_gpio *sam, int pin)
+{
+	int bit;
+
+	for (bit = 0; bit < ARRAY_SIZE(sam->irq_group); bit++) {
+		struct sam_gpio_irq_group *irq_group = &sam->irq_group[bit];
+
+		if (irq_group->count &&
+		    pin >= irq_group->start &&
+		    pin <= irq_group->start + irq_group->count)
+			return bit;
+	}
+	return -EINVAL;
+}
+
+static bool sam_gpio_irq_handle_group(struct sam_gpio *sam,
+				      struct sam_gpio_irq_group *irq_group)
+{
+	unsigned int virq = 0;
+	bool handled = false;
+	bool repeat;
+	int i;
+
+	/* no irq_group for the interrupt bit */
+	if (!irq_group->count)
+		return false;
+
+	WARN_ON(irq_group->num_enabled == 0);
+	do {
+		repeat = false;
+		for (i = 0; i < irq_group->count; i++) {
+			int pin = irq_group->start + i;
+			bool low, high;
+			u32 regval;
+			u8 type;
+
+			regval = ioread32(SAM_GPIO_ADDR(sam->base, pin));
+			/*
+			 * write back status to clear POS_EDGE and NEG_EDGE
+			 * status for this GPIO pin (status bits are
+			 * clear-on-one). This is necessary to clear the
+			 * high level interrupt status.
+			 * Also consider the interrupt to be handled in that
+			 * case, even if there is no taker.
+			 */
+			if (regval & (SAM_GPIO_POS_EDGE | SAM_GPIO_NEG_EDGE)) {
+				iowrite32(regval,
+					  SAM_GPIO_ADDR(sam->base, pin));
+				ioread32(SAM_GPIO_ADDR(sam->base, pin));
+				handled = true;
+			}
+
+			/*
+			 * Check if the pin changed its state.
+			 * If it did, and if the expected condition applies,
+			 * generate a virtual interrupt.
+			 * A pin can only generate an interrupt if
+			 * - interrupts are enabled for it
+			 * - it is configured as input
+			 */
+
+			if (!sam->irq_type[pin])
+				continue;
+			if (!(regval & SAM_GPIO_OUT_TS))
+				continue;
+
+			high = regval & (SAM_GPIO_IN | SAM_GPIO_POS_EDGE);
+			low = !(regval & SAM_GPIO_IN) ||
+				(regval & SAM_GPIO_NEG_EDGE);
+			type = sam->irq_type[pin];
+			if (((type & IRQ_TYPE_EDGE_RISING) &&
+			     (regval & SAM_GPIO_POS_EDGE)) ||
+			    ((type & IRQ_TYPE_EDGE_FALLING) &&
+			     (regval & SAM_GPIO_NEG_EDGE)) ||
+			    ((type & IRQ_TYPE_LEVEL_LOW) && low) ||
+			    ((type & IRQ_TYPE_LEVEL_HIGH) && high)) {
+				virq = irq_find_mapping(sam->domain, pin);
+				handle_nested_irq(virq);
+				if (type & (IRQ_TYPE_LEVEL_LOW
+					    | IRQ_TYPE_LEVEL_HIGH))
+					repeat = true;
+			}
+		}
+		schedule();
+	} while (repeat);
+
+	return handled;
+}
+
+static irqreturn_t sam_gpio_irq_handler(int irq, void *data)
+{
+	struct sam_gpio *sam = data;
+	struct sam_platform_data *pdata = sam->pdata;
+	irqreturn_t ret = IRQ_NONE;
+	bool handled;
+	u32 status;
+
+	do {
+		handled = false;
+		status = pdata->irq_status(sam->dev->parent, SAM_IRQ_GPIO,
+					   sam->irq);
+		pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_GPIO,
+					sam->irq, status);
+		while (status) {
+			unsigned int bit;
+
+			bit = __ffs(status);
+			status &= ~(1 << bit);
+			handled =
+			  sam_gpio_irq_handle_group(sam, &sam->irq_group[bit]);
+			if (handled)
+				ret = IRQ_HANDLED;
+		}
+	} while (handled);
+
+	return ret;
+}
+
+static int sam_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	return irq_create_mapping(sam->domain, offset);
+}
+
+static void sam_irq_mask(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	struct sam_platform_data *pdata = sam->pdata;
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return;
+
+	if (--sam->irq_group[bit].num_enabled <= 0) {
+		pdata->disable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq,
+				   1 << bit);
+	}
+}
+
+static void sam_irq_unmask(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	struct sam_platform_data *pdata = sam->pdata;
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return;
+
+	sam->irq_group[bit].num_enabled++;
+	pdata->enable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq, 1 << bit);
+}
+
+static int sam_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return bit;
+
+	sam->irq_type[data->hwirq] = type & 0x0f;
+	sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_OUT_TS, true);
+	sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_DEBOUNCE_EN, type & 0x10);
+	sam_gpio_bitop(sam, data->hwirq,
+		       SAM_GPIO_POS_EDGE_EN | SAM_GPIO_POS_EDGE,
+		       type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH));
+	sam_gpio_bitop(sam, data->hwirq,
+		       SAM_GPIO_NEG_EDGE_EN | SAM_GPIO_NEG_EDGE,
+		       type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW));
+
+	return 0;
+}
+
+static void sam_irq_bus_lock(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&sam->irq_lock);
+}
+
+static void sam_irq_bus_unlock(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+	/* Synchronize interrupts to chip */
+
+	mutex_unlock(&sam->irq_lock);
+}
+
+static struct irq_chip sam_irq_chip = {
+	.name = "gpio-sam",
+	.irq_mask = sam_irq_mask,
+	.irq_unmask = sam_irq_unmask,
+	.irq_set_type = sam_irq_set_type,
+	.irq_bus_lock = sam_irq_bus_lock,
+	.irq_bus_sync_unlock = sam_irq_bus_unlock,
+};
+
+static int sam_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
+			    irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip(irq, &sam_irq_chip);
+	irq_set_nested_thread(irq, true);
+
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops sam_gpio_irq_domain_ops = {
+	.map = sam_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static int sam_gpio_irq_setup(struct device *dev, struct sam_gpio *sam)
+{
+	int ret;
+
+	sam->domain = irq_domain_add_linear(dev->of_node,
+					    sam->gpio_count,
+					    &sam_gpio_irq_domain_ops,
+					    sam);
+	if (sam->domain == NULL)
+		return -ENOMEM;
+
+	ret = devm_request_threaded_irq(dev, sam->irq, NULL,
+					sam_gpio_irq_handler,
+					IRQF_ONESHOT,
+					dev_name(dev), sam);
+	if (ret)
+		goto out_remove_domain;
+
+	sam->gpio.to_irq = sam_gpio_to_irq;
+
+	if (!try_module_get(dev->parent->driver->owner)) {
+		ret = -EINVAL;
+		goto out_remove_domain;
+	}
+
+	return 0;
+
+out_remove_domain:
+	irq_domain_remove(sam->domain);
+	sam->domain = NULL;
+	return ret;
+}
+
+static void sam_gpio_irq_teardown(struct device *dev, struct sam_gpio *sam)
+{
+	int i, irq;
+	struct sam_platform_data *pdata = sam->pdata;
+
+	pdata->disable_irq(dev->parent, SAM_IRQ_GPIO, sam->irq, 0xffffffff);
+
+	for (i = 0; i < sam->gpio_count; i++) {
+		irq = irq_find_mapping(sam->domain, i);
+		if (irq > 0)
+			irq_dispose_mapping(irq);
+	}
+	irq_domain_remove(sam->domain);
+	module_put(dev->parent->driver->owner);
+}
+
+static int sam_gpio_unexport(struct sam_gpio *sam)
+{
+	int i;
+
+	if (!sam->export_flags)
+		return 0;
+
+	/* un-export all auto-exported pins */
+	for (i = 0; i < sam->gpio_count; i++) {
+		struct gpio_desc *desc = gpio_to_desc(sam->gpio.base + i);
+
+		if (desc == NULL)
+			continue;
+
+		if (sam->export_flags[i] & GPIOF_EXPORT)
+			gpiochip_free_own_desc(desc);
+	}
+	return 0;
+}
+
+static int sam_gpio_export(struct sam_gpio *sam)
+{
+	int i, ret;
+
+	if (!sam->export_flags)
+		return 0;
+
+	/* auto-export pins as requested */
+
+	for (i = 0; i < sam->gpio_count; i++) {
+		u32 flags = sam->export_flags[i];
+		struct gpio_desc *desc;
+
+		/* request and initialize exported pins */
+		if (!(flags & GPIOF_EXPORT))
+			continue;
+
+		desc  = gpiochip_request_own_desc(&sam->gpio, i, "sam-export");
+		if (IS_ERR(desc)) {
+			ret = PTR_ERR(desc);
+			goto error;
+		}
+		if (flags & GPIOF_DIR_IN) {
+			ret = gpiod_direction_input(desc);
+			if (ret)
+				goto error;
+		} else {
+			ret = gpiod_direction_output(desc, flags &
+						    (GPIOF_OUT_INIT_HIGH |
+						     GPIOF_ACTIVE_LOW));
+			if (ret)
+				goto error;
+		}
+		ret = gpiod_export(desc, flags & GPIOF_EXPORT_CHANGEABLE);
+
+		if (ret)
+			goto error;
+	}
+	return 0;
+
+error:
+	sam_gpio_unexport(sam);
+	return ret;
+}
+
+static int sam_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sam_gpio *sam;
+	struct resource *res;
+	int ret;
+	struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (sam == NULL)
+		return -ENOMEM;
+
+	sam->dev = dev;
+	sam->pdata = pdata;
+	platform_set_drvdata(pdev, sam);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	sam->irq = platform_get_irq(pdev, 0);
+	sam->irq_high = platform_get_irq(pdev, 1);
+
+	sam->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!sam->base)
+		return -ENOMEM;
+
+	mutex_init(&sam->irq_lock);
+
+	ret = sam_gpio_of_init(dev, sam);
+	if (ret)
+		return ret;
+
+	sam_gpio_setup(sam);
+
+	if (pdata && sam->irq >= 0 && of_find_property(dev->of_node,
+					      "interrupt-controller", NULL)) {
+		ret = sam_gpio_irq_setup(dev, sam);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = gpiochip_add(&sam->gpio);
+	if (ret)
+		goto teardown;
+
+	ret = sam_gpio_export(sam);
+	if (ret)
+		goto teardown_remove;
+
+	return 0;
+
+teardown_remove:
+	gpiochip_remove(&sam->gpio);
+
+teardown:
+	if (sam->domain)
+		sam_gpio_irq_teardown(dev, sam);
+	return ret;
+}
+
+static int sam_gpio_remove(struct platform_device *pdev)
+{
+	struct sam_gpio *sam = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+
+	dev_info(dev, "remove\n");
+
+	sam_gpio_unexport(sam);
+
+	if (sam->domain)
+		sam_gpio_irq_teardown(dev, sam);
+
+	gpiochip_remove(&sam->gpio);
+
+	return 0;
+}
+
+static const struct of_device_id sam_gpio_ids[] = {
+	{ .compatible = "jnx,gpio-sam", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sam_gpio_ids);
+
+static struct platform_driver sam_gpio_driver = {
+	.driver = {
+		.name = "gpio-sam",
+		.owner  = THIS_MODULE,
+		.of_match_table = sam_gpio_ids,
+	},
+	.probe = sam_gpio_probe,
+	.remove = sam_gpio_remove,
+};
+
+module_platform_driver(sam_gpio_driver);
+
+MODULE_DESCRIPTION("SAM FPGA GPIO Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1


^ permalink raw reply related

* [PATCH 06/10] gpio: sam: Document bindings of SAM FPGA GPIO block
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add device tree bindings document for the GPIO driver of
Juniper's SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/gpio/jnx,gpio-sam.txt      | 110 +++++++++++++++++++++
 1 file changed, 110 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt

diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
new file mode 100644
index 0000000..514c350
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
@@ -0,0 +1,110 @@
+Juniper SAM FPGA GPIO block
+
+The controller's registers are organized as sets of eight 32-bit
+registers with each set controlling a bank of up to 32 pins.  A single
+interrupt is shared for all of the banks handled by the controller.
+
+Required properties:
+
+- compatible:
+    Must be "jnx,gpio-sam"
+
+- #gpio-cells:
+    Should be <2>.  The first cell is the pin number (within the controller's
+    pin space), and the second is used for the following flags:
+	bit[0]: direction (0 = out, 1 = in)
+	bit[1]: init high
+	bit[2]: active low
+	bit[3]: open drain
+	bit[4]: open drain
+
+- gpio-controller:
+    Specifies that the node is a GPIO controller.
+
+Optional properties:
+
+- reg:
+    This driver is part of the SAM FPGA MFD driver, so the
+    address range is supplied by that driver. However you can
+    override using this property.
+
+- gpio-base:
+    Base of the GPIO pins of this instance. If not present use system allocated.
+
+- gpio-count:
+    Number of GPIO pins of this instance. If not present read the number from
+    the one configured in the FPGA data. Maximum number is 512.
+
+- #interrupt-cells:
+    Should be <2>.  The first cell is the GPIO number, the second should specify
+    flags.  The following subset of flags is supported:
+    - bits[16,4:0] trigger type and level flags
+	bit  0: rising edge interrupt
+	bit  1: falling edge interrupt
+	bit  2: active high interrupt
+	bit  3: active low interrupt
+	bit  4: enable debounce
+	bit 16: signal is active low
+    See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+
+- gpio-interrupts:
+    A number of triples that define the mapping of interrupt groupsb to a range of
+    pins. The first cell defines the interrupt group, the second is the start of
+    the pin range and the third the number of pins in the range.
+
+- gpio-exports:
+    A subnode containing the list of pins that will be exported to user-space.
+    Each subnode contains:
+    Required properties:
+	- pin: The gpio to be exported and the relevant flags.
+    Optional properties:
+        - label: The label to use for export; if not supplied use the node name.
+
+Example:
+
+gpio20: gpio-sam {
+	compatible = "jnx,gpio-sam";
+	gpio-controller;
+	interrupt-controller;
+	/* 1st cell: gpio pin
+	 * 2nd cell: flags (bit mask)
+	 * bit  0: rising edge interrupt
+	 * bit  1: falling edge interrupt
+	 * bit  2: active high interrupt
+	 * bit  3: active low interrupt
+	 * bit  4: enable debounce
+	 * bit 16: signal is active low
+	 */
+	#interrupt-cells = <2>;
+	#gpio-cells = <2>;
+	gpio-count = <340>;
+	/* 1st cell: gpio interrupt status bit
+	 * 2nd cell: 1st pin
+	 * 3rd cell: # of pins
+	 */
+	gpio-interrupts =
+		<0 0 32>,	/* TL / TQ */
+		<1 32 32>,	/* PIC 1 */
+		<2 32 32>,	/* PIC 1 spare */
+		<7 148 32>,	/* PIC 0 */
+		<8 170 32>,	/* PIC 0 spare */
+		<16 318 22>;	/* FPC */
+
+	gpio-exports {
+		/*
+		 * flags:
+		 * GPIOF_DIR_IN			bit 0=1
+		 * GPIOF_DIR_OUT		bit 0=0
+		 * GPIOF_INIT_HIGH		bit 1=1
+		 *   GPIOF_INIT_HIGH is raw, not translated
+		 * GPIOF_ACTIVE_LOW		bit 2=1
+		 * GPIOF_OPEN_DRAIN		bit 3=1
+		 * GPIOF_OPEN_SOURCE		bit 4=1
+		 * GPIOF_EXPORT			bit 5=1
+		 * GPIOF_EXPORT_CHANGEABLE      bit 6=1
+		 */
+		tl0-rst {
+			pin = < 8 0x24 >;
+		};
+	};
+};
-- 
1.9.1


^ permalink raw reply related

* [PATCH 07/10] mtd: Add SAM Flash driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

Add driver for the flash block in Juniper's SAM FPGA.

This driver is used for updating the Altera's EPCS(64,256)
configuration flash devices via a Juniper defined hardware
interface.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mtd/devices/Kconfig     |  11 +
 drivers/mtd/devices/Makefile    |   1 +
 drivers/mtd/devices/sam-flash.c | 642 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 654 insertions(+)
 create mode 100644 drivers/mtd/devices/sam-flash.c

diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index d4255fb..f5a9032 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -144,6 +144,17 @@ config MTD_LART
 	  not need any mapping/chip driver for LART. This one does it all
 	  for you, so go disable all of those if you enabled some of them (:
 
+config MTD_SAM_FLASH
+	tristate "Juniper SAM Flash driver"
+	depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+	default y if MFD_JUNIPER_SAM
+	help
+	  This enables the flash driver for the SAM FPGA which is present
+	  on relevant Juniper platforms.
+
+	  This driver can also be built as a module. When it is so the name of
+	  the module is flash-sam.
+
 config JNX_PMB_NVRAM
 	tristate "Juniper FPC PMB NVRAM Driver"
 	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index b407c5fc..7556311 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH)	+= bcm47xxsflash.o
 obj-$(CONFIG_MTD_ST_SPI_FSM)    += st_spi_fsm.o
 obj-$(CONFIG_MTD_POWERNV_FLASH)	+= powernv_flash.o
 
+obj-$(CONFIG_MTD_SAM_FLASH)	+= sam-flash.o
 obj-$(CONFIG_JNX_PMB_NVRAM)     += jnx_pmb_nvram.o
 
 CFLAGS_docg3.o			+= -I$(src)
diff --git a/drivers/mtd/devices/sam-flash.c b/drivers/mtd/devices/sam-flash.c
new file mode 100644
index 0000000..5f071e6
--- /dev/null
+++ b/drivers/mtd/devices/sam-flash.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#define SAM_FLASH_DEBUG_ENABLED
+#undef  T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+#define SAM_FLASH_IF_READ_MAX_SIZE		32	/* 256?! */
+
+#define SAM_FLASH_BASE		0x300
+
+#define ADDR_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x000)
+#define COUNTER_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x004)
+#define CONTROL_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x008)
+#define STATUS_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define WRITE_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x100)
+#define READ_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x200)
+
+static int sam_flash_if_read_max_size = SAM_FLASH_IF_READ_MAX_SIZE;
+module_param(sam_flash_if_read_max_size, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_if_read_max_size,
+		 "maximum read size done by SAM flash IF");
+
+#ifdef SAM_FLASH_DEBUG_ENABLED
+
+static int sam_flash_debug;
+module_param(sam_flash_debug, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_debug, "enable debugging information");
+
+#define SAM_FLASH_DEBUG(dev, fmt, args...)			\
+	do {							\
+		if (sam_flash_debug) {				\
+			dev_info(dev, fmt, ## args);		\
+		}						\
+	} while (0)
+#else /* SAM_FLASH_DEBUG_ENABLED */
+#define SAM_FLASH_DEBUG(dev, fmt, args...)      {}
+#endif /* SAM_FLASH_DEBUG_ENABLED */
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS_EXT_STS_ID(sts)		((u8)((sts >> 8) & 0xff))
+#define EPCS_EXT_STS_RDSTS(sts)		((u8)((sts >> 16) & 0xff))
+#define EPCS_EXT_STS_SID(sts)		((u8)((sts >> 24) & 0xff))
+/* EPCS Device "read status" bits */
+#define EPCS_STS_WIP_BIT		0x01
+#define EPCS_STS_WLE_BIT		0x02
+#define EPCS_STS_BP_BITS(status)	((status >> 2) & 0x7)
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS64_S_ID			0x16
+#define EPCS64_NAME			"Altera EPCS64"
+#define EPCS64_SECT_SZ_SHIFT		16
+#define EPCS64_SECTOR_SIZE		(1 << EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_SECTORS			128
+#define EPCS64_ADDR_TO_SECTOR(_addr)	((_addr) >> EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_PAGE_SIZE		256
+#define EPCS64_PAGES			32768
+#define EPCS64_SIZE			(EPCS64_PAGE_SIZE * EPCS64_PAGES)
+#define EPCS64_MIN_SECT(bp_bits)	(EPCS64_SECTORS - (1 << bp_bits))
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ *  timeout for busy:   t5e-pic/t5e_flash.c
+ */
+#define EPCS_TIMEOUT_BUSY		1
+#define EPCS_TIMEOUT_SINGLE_BYTE_READ	3
+#define EPCS_TIMEOUT_READ_ID		3
+#define EPCS_TIMEOUT_READ_STATUS	3
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ *  timeout for waiting completion:   t5e-pic/t5e_flash.c
+ */
+#define EPCS_RD_TIMEO		20
+#define EPCS_WR_TIMEO		25
+#define EPCS_BLK_WR_TIMEO	25
+#define EPCS_SC_ER_TIMEO	(10 * 1000)
+#define EPCS_SC_PRT_TIMEO	35
+#define EPCS_CH_ER_TIMEO	(200 * 1000)
+/* */
+#define EPCS_STS_BSY_BIT	0x01
+#define EPCS_ILLEGAL_WR_BIT	0x02
+#define EPCS_ILLEGAL_RD_BIT	0x04
+#define EPCS_ILLEGAL		(EPCS_ILLEGAL_WR_BIT | EPCS_ILLEGAL_RD_BIT)
+#define EPCS_STATUS_BUSY(s)	((s) & EPCS_STS_BSY_BIT)
+
+/*
+ *  Ref t5e-pic/t5e_flash.c
+ */
+#define EPCS_BUSY_POLLING_START_DELAY		100	/* us */
+#define EPCS_BUSY_POLLING_START_DELAY_CNT	10
+#define EPCS_BUSY_POLLING_DELAY		(EPCS_BUSY_POLLING_START_DELAY_CNT * \
+					 EPCS_BUSY_POLLING_START_DELAY)
+
+/*
+ * FPGA flash control register: t5e-pic/t5e_fpga.h
+ */
+#define SAM_FLASH_IF_CONTROL_READ_SID		0x00000080
+#define SAM_FLASH_IF_CONTROL_CHIP_ERASE		0x00000040
+#define SAM_FLASH_IF_CONTROL_SECTOR_ERASE	0x00000020
+#define SAM_FLASH_IF_CONTROL_SECTOR_PROTECT	0x00000010
+#define SAM_FLASH_IF_CONTROL_READ_STATUS	0x00000008
+#define SAM_FLASH_IF_CONTROL_READ_ID		0x00000004
+#define SAM_FLASH_IF_CONTROL_WRITE		0x00000002
+#define SAM_FLASH_IF_CONTROL_READ		0x00000001
+#define SAM_FLASH_IF_WRITE_REG_SIZE		sizeof(u32)
+#define SAM_FLASH_IF_READ_REG_SIZE		sizeof(u32)
+
+struct sam_flash_info {
+	const char *name;
+	u8 device_id;
+	size_t flash_size;
+	size_t page_size;
+	size_t nr_pages;
+	size_t nr_sectors;
+	size_t erasesize;
+	size_t writesize;
+	size_t writebufsize;
+};
+
+static struct sam_flash_info sam_flash_info_db[] = {
+	{
+		.name = EPCS64_NAME,
+		.device_id = EPCS64_S_ID,
+		.flash_size = EPCS64_SIZE,
+		.page_size = EPCS64_PAGE_SIZE,
+		.nr_pages = EPCS64_PAGES,
+		.nr_sectors = EPCS64_SECTORS,
+		.erasesize = EPCS64_SECTOR_SIZE,
+		.writesize = 4,
+		.writebufsize = 4,
+	},
+};
+
+#define SAM_FLASH_INFO_DB_SIZE	ARRAY_SIZE(sam_flash_info_db)
+
+/**
+ * struct sam_flash - SAM FLASH private data structure.
+ * @membase:		PCI base address of Memory mapped I/O register.
+ * @reg:		Memory mapped PCH GPIO register list.
+ * @dev:		Pointer to device structure.
+ */
+struct sam_flash {
+	void __iomem *membase;
+	struct mutex lock;
+	struct device *dev;
+	struct sam_flash_info *info;
+	struct mtd_info mtd_info;
+};
+
+#define mtd_to_sam_flash(mtd) container_of(mtd, struct sam_flash, mtd_info)
+
+static bool sam_flash_if_busy(struct sam_flash *sam_flash, int retry)
+{
+	u32 status;
+
+	do {
+		status = ioread32(STATUS_REG(sam_flash));
+		if (!EPCS_STATUS_BUSY(status))
+			return false;
+		if (retry <= 1)
+			return true;
+		usleep_range(50, 100);
+	} while (--retry >= 0);
+
+	return true;
+}
+
+static int
+sam_flash_if_busy_wait(struct sam_flash *sam_flash, unsigned int max_wait_msec)
+{
+	unsigned long timeout;
+	u32 status;
+
+	timeout = jiffies + msecs_to_jiffies(max_wait_msec);
+	udelay(50);
+
+	do {
+		status = ioread32(STATUS_REG(sam_flash));
+		if (!EPCS_STATUS_BUSY(status))
+			return 0;
+
+		if (status & EPCS_ILLEGAL)
+			return -EACCES;
+
+		usleep_range(50, 100);
+	} while (time_before(jiffies, timeout));
+
+	return -ETIMEDOUT;
+}
+
+static int
+sam_flash_mem_read(struct sam_flash *sam_flash, u32 offset,
+		   u8 *data, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	void __iomem *io_addr;
+	u32 io_data;
+	int i, cnt;
+
+	if (offset >= info->flash_size || offset + len > info->flash_size)
+		return -EINVAL;
+
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY))
+		return -ETIMEDOUT;
+
+	iowrite32(len - 1, COUNTER_REG(sam_flash));
+	iowrite32(offset, ADDR_REG(sam_flash));
+
+	/* trigger the read */
+	iowrite32(SAM_FLASH_IF_CONTROL_READ, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	/*
+	 * Before we start polling the busy bit, wait for some time,
+	 * so that, the busy bit will go high
+	 */
+#ifdef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+	udelay(50);
+#else /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+	udelay(50 * ((len >> 2) + 1));	/* 50 usec every 4 bytes */
+#endif /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_SINGLE_BYTE_READ))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(COUNTER_REG(sam_flash));
+	if (io_data != len - 1)
+		return -EIO;
+
+	SAM_FLASH_DEBUG(sam_flash->dev,
+			"%s BYTE_CNT: len: %u, io_data: %u.\n",
+			__func__, (unsigned int)len, io_data);
+
+	io_addr = READ_DATA_REG(sam_flash);
+	for (cnt = 0; cnt < len; io_addr += sizeof(u32)) {
+		io_data = ioread32(io_addr);
+		for (i = 0; i < sizeof(u32) && cnt < len; i++, cnt++)
+			*(data++) = (io_data >> (i << 3)) & 0xff;
+	}
+
+	return 0;
+}
+
+static int sam_flash_read_sid(struct sam_flash *sam_flash)
+{
+	u32 io_data;
+
+	iowrite32(SAM_FLASH_IF_CONTROL_READ_SID, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	/*
+	 * Before we start polling the busy bit, wait for some time
+	 * to ensure that busy bit is high.
+	 */
+	udelay(EPCS_BUSY_POLLING_DELAY);
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(STATUS_REG(sam_flash));
+
+	return EPCS_EXT_STS_SID(io_data);
+}
+
+static struct sam_flash_info *sam_flash_get_info(struct sam_flash *sam_flash)
+{
+	struct sam_flash_info *info;
+	u8 sid;
+	int idx;
+
+	sid = sam_flash_read_sid(sam_flash);
+	if (sid < 0)
+		return ERR_PTR(sid);
+
+	info = ERR_PTR(-EINVAL);
+	for (idx = 0; idx < SAM_FLASH_INFO_DB_SIZE; idx++) {
+		if (sam_flash_info_db[idx].device_id == sid) {
+			info = &sam_flash_info_db[idx];
+			break;
+		}
+	}
+	return info;
+}
+
+static inline int sam_flash_get_page_num(struct sam_flash *sam_flash,
+					 u32 offset)
+{
+	return offset / sam_flash->info->page_size;
+}
+
+static int sam_flash_mem_write(struct sam_flash *sam_flash, u32 offset,
+			       const u8 *data, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	int status, bytes_in_reg, cnt, cnt2;
+	int start_page, end_page;
+	void __iomem *io_addr;
+	u32 io_data;
+	const u8 *buf;
+
+	start_page = sam_flash_get_page_num(sam_flash, offset);
+	end_page = sam_flash_get_page_num(sam_flash, offset + len - 1);
+
+	/*
+	 *  Based on Altera EPCS Device Datasheet,
+	 *  Writing with multiple byte must be in the __SAME__ page.
+	 *  Not sure if SAM FPGA takes that so ...
+	 */
+	if (len > info->page_size ||
+	    start_page != end_page ||
+	    start_page >= info->nr_pages) {
+		dev_err(sam_flash->dev, "Bad write length / offset\n");
+		return -EINVAL;
+	}
+
+	/* check if FPGA is ready to accept new command */
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY)) {
+		dev_err(sam_flash->dev, "chip is busy\n");
+		return -ETIMEDOUT;
+	}
+
+	iowrite32(len - 1, COUNTER_REG(sam_flash));
+
+	/* copy the data to WRITE_DATA register */
+	io_addr = WRITE_DATA_REG(sam_flash);
+	for (buf = data, cnt = 0; cnt < len;) {
+		bytes_in_reg = len - cnt;
+		if (bytes_in_reg > SAM_FLASH_IF_WRITE_REG_SIZE)
+			bytes_in_reg = SAM_FLASH_IF_WRITE_REG_SIZE;
+		io_data = 0;
+		for (cnt2 = 0; cnt2 < bytes_in_reg; cnt2++, buf++)
+			io_data |= *buf << (cnt2 << 3);
+
+		iowrite32(io_data, io_addr);
+		cnt += bytes_in_reg;
+		io_addr += bytes_in_reg;
+	}
+
+	iowrite32(offset, ADDR_REG(sam_flash));
+	/* trigger the write */
+	iowrite32(SAM_FLASH_IF_CONTROL_WRITE, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	status = sam_flash_if_busy_wait(sam_flash, EPCS_WR_TIMEO);
+	return status;
+}
+
+static int sam_flash_mem_is_protected(struct sam_flash *sam_flash,
+				      u32 offset, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	u32 flash_status;
+	u32 sector;
+	u32 io_data;
+	int status = 0;
+
+	iowrite32(SAM_FLASH_IF_CONTROL_READ_STATUS, CONTROL_REG(sam_flash));
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(STATUS_REG(sam_flash));
+	flash_status = EPCS_EXT_STS_RDSTS(io_data);
+	sector = EPCS64_ADDR_TO_SECTOR(offset);
+	if (EPCS_STS_BP_BITS(flash_status) &&
+	    sector < info->erasesize &&
+	    sector >= EPCS64_MIN_SECT(EPCS_STS_BP_BITS(flash_status))) {
+		status = -EACCES;
+		SAM_FLASH_DEBUG(sam_flash->dev,
+				"%s offset: 0x%x, len: %u: PROTECTED(0x%x): %d.\n",
+				__func__, offset, (unsigned int)len,
+				flash_status, status);
+	}
+
+	return status;
+}
+
+static int sam_flash_erase_sector(struct sam_flash *sam_flash, u32 offset)
+{
+	iowrite32(offset, ADDR_REG(sam_flash));
+	iowrite32(SAM_FLASH_IF_CONTROL_SECTOR_ERASE, CONTROL_REG(sam_flash));
+
+	return sam_flash_if_busy_wait(sam_flash, EPCS_SC_ER_TIMEO);
+}
+
+static int sam_flash_erase(struct mtd_info *mtd_info,
+			   struct erase_info *erase_info)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	u32 len, start, end, offset;
+	int status = 0;
+
+	len = (u32) erase_info->len;
+	start = (u32) erase_info->addr;
+	end = start + len - 1;
+
+	offset = start;
+	mutex_lock(&sam_flash->lock);
+	erase_info->state = MTD_ERASE_DONE;
+	while (offset < end) {
+		status = sam_flash_erase_sector(sam_flash, offset);
+		if (status) {
+			erase_info->state = MTD_ERASE_FAILED;
+			break;
+		}
+		offset += mtd_info->erasesize;
+	}
+	mutex_unlock(&sam_flash->lock);
+
+	mtd_erase_callback(erase_info);
+
+	return status;
+}
+
+static int sam_flash_read(struct mtd_info *mtd_info, loff_t from, size_t len,
+			  size_t *retlen, unsigned char *buf)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	int cnt, max_cnt;
+	int status = 0;
+
+	*retlen = 0;
+
+	max_cnt = len / sam_flash_if_read_max_size;
+	if (len % sam_flash_if_read_max_size != 0)
+		max_cnt++;
+	mutex_lock(&sam_flash->lock);
+	for (cnt = 0; cnt < max_cnt; cnt++) {
+		u32 from2;
+		size_t len2;
+		u8 *buf2;
+
+		from2 = from + *retlen;
+		buf2 = buf + *retlen;
+		len2 = len - *retlen;
+		if (len2 > sam_flash_if_read_max_size)
+			len2 = sam_flash_if_read_max_size;
+
+		status = sam_flash_mem_read(sam_flash, from2, buf2, len2);
+		if (status != 0) {
+			dev_err(sam_flash->dev,
+				"RD: cnt: %04d(%04d): from: %u(%u), len: %u(%u) failed: %d.\n",
+				cnt, max_cnt, from2, (u32) from,
+				(unsigned int)len2, (unsigned int)len, status);
+			break;
+		}
+		*retlen += len2;
+	}
+	mutex_unlock(&sam_flash->lock);
+
+	return status;
+}
+
+static int sam_flash_write(struct mtd_info *mtd_info, loff_t to,
+			   size_t len, size_t *retlen, const unsigned char *buf)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	int status, done, to_be_done;
+
+	mutex_lock(&sam_flash->lock);
+	status = sam_flash_mem_is_protected(sam_flash, to, len);
+	if (status)
+		goto abort;
+
+	for (done = 0; done < len; done += to_be_done) {
+		to_be_done = to & (sam_flash->info->page_size - 1);
+		if (to_be_done == 0) {
+			/* 'to' is page aligned */
+			to_be_done = len - done;
+			if (to_be_done > sam_flash->info->page_size)
+				to_be_done = sam_flash->info->page_size;
+		} else {
+			to_be_done = sam_flash->info->page_size - to_be_done;
+		}
+
+		SAM_FLASH_DEBUG(sam_flash->dev,
+				"%s to: 0x%x, buf: 0x%p, to_be_done: %d, done: %d, len: %d.\n",
+				__func__,
+				(u32) to, buf, to_be_done,
+				done, (unsigned int)len);
+		status = sam_flash_mem_write(sam_flash, to, buf, to_be_done);
+		if (status) {
+			dev_err(sam_flash->dev,
+				"WR: failed to 0x%x, buf: 0x%p, done: %d(%d), to_be_done: %d: %d.\n",
+				(u32)to, buf, done, (unsigned int)len,
+				to_be_done, status);
+			break;
+		}
+
+		to += to_be_done;
+		buf += to_be_done;
+	}
+
+	if (!status)
+		*retlen = len;
+
+abort:
+	mutex_unlock(&sam_flash->lock);
+	return status;
+}
+
+static int sam_flash_mtd_attach(struct platform_device *pdev,
+				struct sam_flash *sam_flash)
+{
+	struct mtd_part_parser_data ppdata = {};
+	struct sam_flash_info *info;
+	struct device *dev = sam_flash->dev;
+	struct mtd_info *mtd_info;
+	int ret;
+
+	info = sam_flash_get_info(sam_flash);
+	if (IS_ERR(info))
+		return PTR_ERR(info);
+
+	sam_flash->info = info;
+
+	mtd_info = &sam_flash->mtd_info;
+	mtd_info->name = dev_name(dev);
+	mtd_info->type = MTD_NORFLASH;
+	mtd_info->flags = MTD_CAP_NORFLASH;
+	mtd_info->erasesize = info->erasesize;
+	mtd_info->writesize = info->writesize;
+	mtd_info->writebufsize = info->writebufsize;
+	mtd_info->size = info->flash_size;
+	mtd_info->_erase = sam_flash_erase;
+	mtd_info->_read = sam_flash_read;
+	mtd_info->_write = sam_flash_write;
+
+	ret = mtd_device_parse_register(mtd_info, NULL, &ppdata, NULL, 0);
+	if (ret) {
+		dev_err(dev, "mtd_device_parse_register returned %d\n", ret);
+		return ret;
+	}
+
+	dev_info(dev,
+		 "ATTACH: name: \"%s\" type: %d, flags: 0x%x.\n",
+		 mtd_info->name, mtd_info->type, mtd_info->flags);
+	dev_info(dev,
+		 "ATTACH: erasesize: %d, writesize: %d, writebufsize: %d\n",
+		 mtd_info->erasesize, mtd_info->writesize,
+		 mtd_info->writebufsize);
+	dev_info(dev,
+		 "ATTACH: size: %llu.%u(%llu KB).\n",
+		 mtd_info->size,
+		 (unsigned int)info->flash_size,
+		 (long long)mtd_info->size >> 10);
+
+	return 0;
+}
+
+static int sam_flash_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sam_flash *sam_flash;
+	struct resource *res;
+
+	sam_flash = devm_kzalloc(dev, sizeof(*sam_flash), GFP_KERNEL);
+	if (sam_flash == NULL)
+		return -ENOMEM;
+
+	sam_flash->dev = dev;
+	platform_set_drvdata(pdev, sam_flash);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENOMEM;
+
+	sam_flash->membase = devm_ioremap(dev, res->start, resource_size(res));
+	if (!sam_flash->membase)
+		return -ENOMEM;
+
+	mutex_init(&sam_flash->lock);
+
+	return sam_flash_mtd_attach(pdev, sam_flash);
+}
+
+static int sam_flash_remove(struct platform_device *pdev)
+{
+	struct sam_flash *sam_flash = platform_get_drvdata(pdev);
+
+	mtd_device_unregister(&sam_flash->mtd_info);
+
+	return 0;
+}
+
+static const struct of_device_id sam_flash_ids[] = {
+	{ .compatible = "jnx,flash-sam", },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, sam_flash_ids);
+
+static struct platform_driver sam_flash_driver = {
+	.driver = {
+		   .name = "flash-sam",
+		   .owner = THIS_MODULE,
+		   .of_match_table = sam_flash_ids,
+		   },
+	.probe = sam_flash_probe,
+	.remove = sam_flash_remove,
+};
+
+static int __init sam_flash_init(void)
+{
+	return platform_driver_register(&sam_flash_driver);
+}
+
+static void __exit sam_flash_exit(void)
+{
+	platform_driver_unregister(&sam_flash_driver);
+}
+
+module_init(sam_flash_init);
+module_exit(sam_flash_exit);
+
+MODULE_DESCRIPTION("SAM-FPGA FLASH Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

^ permalink raw reply related

* [PATCH 08/10] mtd: flash-sam: Bindings for Juniper's SAM FPGA flash
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add binding document for Junipers Flash IP block present
in the SAM FPGA on PTX series of routers.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/mtd/flash-sam.txt          | 31 ++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt

diff --git a/Documentation/devicetree/bindings/mtd/flash-sam.txt b/Documentation/devicetree/bindings/mtd/flash-sam.txt
new file mode 100644
index 0000000..bdf1d78
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/flash-sam.txt
@@ -0,0 +1,31 @@
+Flash device on a Juniper SAM FPGA
+
+These flash chips are found in the PTX series of Juniper routers.
+
+They are regular CFI compatible (Intel or AMD extended) flash chips with
+some special write protect/VPP bits that can be controlled by the machine's
+system controller.
+
+Required properties:
+- compatible : must be "jnx,flash-sam"
+
+Optional properties:
+- reg : memory address for the flash chip, note that this is not
+required since usually the device is a subdevice of the SAM MFD
+driver which fills in the register fields.
+
+For the rest of the properties, see mtd-physmap.txt.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+flash_sam {
+	compatible = "jnx,flash-sam";
+	partition@0 {
+		reg = <0x0 0x400000>;
+		label = "pic0-golden";
+		read-only;
+	};
+};
-- 
1.9.1


^ permalink raw reply related


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