[RFC] Medfield_Current_Monitoring_Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Any views on the best way to approach this. It manages current overflow and
draw - ie its not a power meter. One possibility is to put it in hwmon with
some API tweaks as needed, but are there any analogous drivers or likely to
be ? The other is to move it out of hwmon and into the x86 platform code and
decide it's not an API/interface that can be usefully genericised.

My own preference is to make it a platform specific interface and leave it
that way but I wanted to run it past the hwmon folk first.

Alan



From: Durgadoss R <durgadoss.r@xxxxxxxxx>

This is the Intel Medfield Current Monitoring Driver patch.
The platform specific data required by the driver are provided by
adding necessary code in arch/x86/platform/mrst/mrst.c

This driver monitors the platform current usage and handles interrupts
when the configured current thresholds are crossed.
A detailed documentation for this, has been added in
Documentation/hwmon/current_monitor.

Signed-off-by: Durgadoss R <durgadoss.r@xxxxxxxxx>
Signed-off-by: Alan Cox <alan@xxxxxxxxxxxxxxx>
---

 Documentation/hwmon/intel_over_current_detector |   86 +++
 arch/x86/platform/mrst/mrst.c                   |   39 +
 drivers/hwmon/Kconfig                           |    9 
 drivers/hwmon/Makefile                          |    1 
 drivers/hwmon/intel_mid_ocd.c                   |  647 +++++++++++++++++++++++
 5 files changed, 771 insertions(+), 11 deletions(-)
 create mode 100644 Documentation/hwmon/intel_over_current_detector
 create mode 100644 drivers/hwmon/intel_mid_ocd.c


diff --git a/Documentation/hwmon/intel_over_current_detector b/Documentation/hwmon/intel_over_current_detector
new file mode 100644
index 0000000..d9483b0
--- /dev/null
+++ b/Documentation/hwmon/intel_over_current_detector
@@ -0,0 +1,86 @@
+Kernel driver over current detection driver
+===========================================
+
+Supported systems:
+  * Intel Medfield Platform
+
+Author: Durgadoss R
+
+Description
+-----------
+
+This driver monitors the current drawn by the platform, and detects the
+peak current conditions. When the current drawn is more than the
+configured limit for a period of time (which is specified by a timer), an
+interrupt is generated. The current limit and the timer value can be
+configured at run time.
+
+The current thresholds aka BATTCURRENTLIMITS(BC) are of two types:
+	1.warning threshold(BC1)
+		at which the system should take actions to bring the
+		current consumption down.
+	2.shutdown threshold(BC2)
+		at which the hardware does a COLDOFF.
+
+The timer thresholds are of three types:
+	1.warning threshold(T1)
+		This corresponds to the first flag for time that the battery
+		current has been above BC1. An interrupt is generated to allow
+		software to correct the situation based on use-case.
+	2.hardware action threshold(T2)
+		This corresponds to the second flag for time that the battery
+		current has been above BC1. It signifies that the system
+		should control high current subsystems through hardware.
+		Besides a general interrupt, audio_volume_crush, vibra_disable
+		signals are sent.
+	3.shutdown threshold(T3)
+		This final flag signifies that when the system current exceeds
+		the threshold for more than T3, system should shutdown.
+
+Following table summarizes the exported sysfs files:
+
+bcu_status(RW)	     -  To enable/disable burst control unit.
+			0 - enables bcu, 1 - disables bcu.
+accumulation_time(RW)-  Time since last clearing/overflow of warning_count
+			in milli seconds. Writing 0 resets the acc_time and
+			also clears both the warning counters.
+warning_count(RO)    -  Two space seperated values that indicate the number
+			of times the current thresholds have been crossed.
+action_mask(RW)	     -  A hex value to enable/disable specific
+			actions taken when current violation happens.
+			bits [b4 b3 b2 b1 b0] control [a4 a3 a2 a1 a0] actions
+			respectively. a4 - camera output, a3 - sys burst,
+			a2 - SOC burst enable, a1 - vibra, a0 - audio output.
+			bits [b7 b6 b5] - Reserved.
+			Default value:08 (sys burst output is enabled).
+action_status(RO)    -  A hex value showing the status of actions taken
+			since the __last__ interrupt.
+			b7 - T3 violation
+			b6 - T2 violation
+			b5 - T1 violation
+			b4 - camera action taken status
+			b3 - sys burst disable action taken status
+			b2 - SOC burst disable action taken status
+			b1 - vibra disable
+			b0 - audio volume crush
+			A '1' in a bit position indicates that particular
+			action has been taken.
+current_warning(RW)  -  This gives the current(in mA) at which a warning is
+			generated. Range: 1400 to 4800. Default:3000
+current_shutdown(RW) -  This gives the current(in mA) at which system shutdown
+			is initiated. Range:1800 to 5800. Default:3800
+timer_warning(RW)    -  Time above current_warning limit at which interrupts
+			are trigerred(so that software can bring the current
+			consumption down). Values are in micro seconds.
+			Range:200 to 7700 in steps of 500
+timer_hw_action(RW)  -  Time above current_warning limit at which the system
+			is trigerred into hardware control to bring down the
+			current consumption. Values are in micro seconds.
+			Range:200 to 7700 in steps of 500
+timer_shutdown(RW)   -  Time above current_warning limit at which a hardware
+			shutdown event is trigerred. The timer values are in
+			micro seconds.
+			Range:1000 to 15000 in steps of 1000
+For all timer interfaces, tolerance shall be 5% maximum.
+All timers start running concurrently once current_warning/current_shutdown
+is crossed.
diff --git a/arch/x86/platform/mrst/mrst.c b/arch/x86/platform/mrst/mrst.c
index 7ece498..6c62b6a 100644
--- a/arch/x86/platform/mrst/mrst.c
+++ b/arch/x86/platform/mrst/mrst.c
@@ -389,6 +389,18 @@ struct devs_id {
 	void *(*get_platform_data)(void *info);
 };
 
+static void __init install_irq_resource(struct platform_device *pdev,
+							char *name, int irq)
+{
+	/* Single threaded */
+	static struct resource __initdata res = {
+		.flags = IORESOURCE_IRQ,
+	};
+	res.name = name;
+	res.start = irq;
+	platform_device_add_resources(pdev, &res, 1);
+}
+
 /* the offset for the mapping of global gpio pin to irq */
 #define MRST_IRQ_OFFSET 0x100
 
@@ -478,6 +490,20 @@ static void __init *emc1403_platform_data(void *info)
 	return &intr2nd_pdata;
 }
 
+static void __init *msic_ocd_platform_data(void *info)
+{
+	/* Platform Specific data for Current Monitoring Driver */
+	int gpio_base;
+	struct platform_device *pdev = (struct platform_device *)info;
+
+	gpio_base = get_gpio_by_name("msic_ocd");
+	if (gpio_base == -1)
+		return NULL;
+
+	install_irq_resource(pdev, "msic_ocd", gpio_base + MRST_IRQ_OFFSET);
+	return NULL;
+}
+
 static void __init *lis331dl_platform_data(void *info)
 {
 	static short intr2nd_pdata;
@@ -496,6 +522,7 @@ static void __init *lis331dl_platform_data(void *info)
 
 static const struct devs_id __initconst device_ids[] = {
 	{"pmic_gpio", SFI_DEV_TYPE_SPI, 1, &pmic_gpio_platform_data},
+	{"msic_ocd", SFI_DEV_TYPE_IPC, 1, &msic_ocd_platform_data},
 	{"spi_max3111", SFI_DEV_TYPE_SPI, 0, &max3111_platform_data},
 	{"i2c_max7315", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data},
 	{"i2c_max7315_2", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data},
@@ -604,16 +631,6 @@ void intel_scu_devices_destroy(void)
 }
 EXPORT_SYMBOL_GPL(intel_scu_devices_destroy);
 
-static void __init install_irq_resource(struct platform_device *pdev, int irq)
-{
-	/* Single threaded */
-	static struct resource __initdata res = {
-		.name = "IRQ",
-		.flags = IORESOURCE_IRQ,
-	};
-	res.start = irq;
-	platform_device_add_resources(pdev, &res, 1);
-}
 
 static void __init sfi_handle_ipc_dev(struct platform_device *pdev)
 {
@@ -711,7 +728,7 @@ static int __init sfi_parse_devs(struct sfi_table_header *table)
 							pentry->name);
 				continue;
 			}
-			install_irq_resource(pdev, pentry->irq);
+			install_irq_resource(pdev, "IRQ", pentry->irq);
 			pr_debug("info[%2d]: IPC bus, name = %16.16s, "
 				"irq = 0x%2x\n", i, pentry->name, pentry->irq);
 			sfi_handle_ipc_dev(pdev);
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index a56f6ad..e5bdf24 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1171,6 +1171,15 @@ config SENSORS_MC13783_ADC
         help
           Support for the A/D converter on MC13783 PMIC.
 
+config SENSORS_MID_CURRENT
+	tristate "Current monitoring driver for the Intel Medfield platform"
+	depends on INTEL_SCU_IPC
+	help
+	  Say Y here to enable the current monitoring driver on the Intel
+	  Medfield platform. This provides over-current and power
+	  monitoring on medfield devices. Say Y here if you have a Medfield
+	  based MID device, otherwise N.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2479b3d..68f4c99 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_SENSORS_MAX1111)	+= max1111.o
 obj-$(CONFIG_SENSORS_MAX1619)	+= max1619.o
 obj-$(CONFIG_SENSORS_MAX6650)	+= max6650.o
 obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
+obj-$(CONFIG_SENSORS_MID_CURRENT)+= intel_mid_ocd.o
 obj-$(CONFIG_SENSORS_PC87360)	+= pc87360.o
 obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
diff --git a/drivers/hwmon/intel_mid_ocd.c b/drivers/hwmon/intel_mid_ocd.c
new file mode 100644
index 0000000..0e78d9c
--- /dev/null
+++ b/drivers/hwmon/intel_mid_ocd.c
@@ -0,0 +1,647 @@
+/*
+ * intel_mid_ocd.c - Intel Medfield Platform Over Current Detection Driver
+ *
+ *
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Durgadoss R <durgadoss.r@xxxxxxxxx>
+ */
+
+#define pr_fmt(fmt)  "intel_mid_ocd: " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#include <asm/intel_scu_ipc.h>
+
+#define DRIVER_NAME "msic_ocd"
+
+/* Registers that govern current monitoring */
+#define BATTCURRENTLIMIT12	0x102
+#define BATTTIMELIMIT12		0x103
+#define BATTTIMELIMIT3		0x104
+#define BRSTCONFIGOUTPUTS	0x106
+#define BRSTCONFIGACTIONS	0x107
+#define BRSTCONTROLSTATUS	0x108
+
+#define BCUSTATUS		(1 << 7)
+
+/* Bits that enable sys burst input & output */
+#define SYSACTEN		(1 << 3)
+#define SYSOUTEN		(1 << 3)
+
+/* Status register */
+#define CAMSTAT			(1 << 4)
+#define SYSSTAT			(0x0F << 0)
+#define OVER_TIMER1		(1 << 5)
+#define OVER_TIMER2		(1 << 6)
+
+#define NUM_CURR_LIMITS		8
+#define NUM_TIME_LIMITS		15
+
+/* Base and offset for every time limit */
+#define TIME_LIMIT12_BASE	200
+#define TIME_LIMIT12_OFFSET	500
+#define TIME_LIMIT12_MAX	(TIME_LIMIT12_BASE + \
+				(TIME_LIMIT12_OFFSET * NUM_TIME_LIMITS))
+
+#define TIME_LIMIT3_BASE	200
+#define TIME_LIMIT3_OFFSET	1000
+#define TIME_LIMIT3_MAX		(TIME_LIMIT3_OFFSET * NUM_TIME_LIMITS)
+
+#define MAX_COUNT		0xFFFFFFFF
+
+static DEFINE_MUTEX(ocd_update_lock);
+
+/* stores the current thresholds(in mA) at which
+ * row 0: warning is generated
+ * row 1: system shut down is initiated
+ */
+static const int curr_thresholds[][NUM_CURR_LIMITS] = {
+			{1400, 1800, 2200, 2800, 3000, 3400, 3800, 4800},
+			{1800, 2200, 2800, 3000, 3800, 4800, 5800, 5800} };
+
+struct ocd_info {
+	unsigned long timer1_count;
+	unsigned long timer2_count;
+	unsigned long acc_time;
+	unsigned int irq;
+	uint8_t intrpt_status;
+	struct device *dev;
+	struct platform_device *pdev;
+};
+
+static int configure_bcu(int flag)
+{
+	int ret;
+	uint8_t data;
+
+	mutex_lock(&ocd_update_lock);
+
+	ret = intel_scu_ipc_ioread8(BRSTCONFIGACTIONS, &data);
+	if (ret)
+		goto ipc_fail;
+
+	/* Zero enables BCU and non-zero disables BCU */
+	if (!flag)
+		data &= (~BCUSTATUS);	/* enable bcu */
+	else
+		data |= BCUSTATUS;	/* disable bcu */
+
+	ret = intel_scu_ipc_iowrite8(BRSTCONFIGACTIONS, data);
+
+ipc_fail:
+	mutex_unlock(&ocd_update_lock);
+	return ret;
+}
+
+static ssize_t store_bcu_status(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+
+	return configure_bcu(val) ? -EINVAL : count;
+}
+
+static ssize_t show_bcu_status(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int ret;
+	uint8_t data;
+
+	ret = intel_scu_ipc_ioread8(BRSTCONFIGACTIONS, &data);
+	if (ret)
+		return ret;
+
+	ret = (data & BCUSTATUS) ? 1 : 0;
+
+	return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t store_action_mask(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int ret;
+	unsigned long data;
+
+	if (strict_strtoul(buf, 16, &data))
+		return -EINVAL;
+
+	ret = intel_scu_ipc_iowrite8(BRSTCONFIGOUTPUTS, (uint8_t)data);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t show_action_mask(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	int ret;
+	uint8_t data;
+
+	ret = intel_scu_ipc_ioread8(BRSTCONFIGOUTPUTS, &data);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%.2x\n", data);
+}
+
+static ssize_t show_action_status(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ocd_info *cinfo = dev_get_drvdata(dev);
+
+	/* This shows the status of actions taken since the last interrupt */
+	return sprintf(buf, "%.2x\n", cinfo->intrpt_status);
+}
+
+static int get_current_value(int index, int value)
+{
+	int pos = 0;
+
+	if (index != 0 && index != 1)
+		return -EINVAL;
+
+	if (value < curr_thresholds[index][0] ||
+			value > curr_thresholds[index][NUM_CURR_LIMITS-1])
+		return -EINVAL;
+
+	/* Find the index of 'value' in the thresholds array */
+	while (pos < NUM_CURR_LIMITS && value >= curr_thresholds[index][pos])
+		++pos;
+
+	return pos - 1;
+}
+
+static ssize_t store_curr_thres(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int ret;
+	uint8_t data;
+	long curnt;
+	int pos;
+	struct sensor_device_attribute_2 *s_attr =
+					to_sensor_dev_attr_2(attr);
+
+	if (strict_strtol(buf, 10, &curnt))
+		return -EINVAL;
+
+	mutex_lock(&ocd_update_lock);
+
+	pos = get_current_value(s_attr->nr, (int)curnt);
+	if (pos < 0) {
+		ret = pos;
+		goto ipc_fail;
+	}
+
+	ret = intel_scu_ipc_ioread8(BATTCURRENTLIMIT12, &data);
+	if (ret)
+		goto ipc_fail;
+
+	if (s_attr->nr == 0)
+		/* set bits [0-2] to value of pos */
+		data = (data & 0xF8) | pos;
+	else
+		/* set bits [3-5] to value of pos */
+		data = (data & 0xC7) | (pos << 3);
+
+	ret = intel_scu_ipc_iowrite8(BATTCURRENTLIMIT12, data);
+	if (ret)
+		goto ipc_fail;
+
+	ret = count;
+
+ipc_fail:
+	mutex_unlock(&ocd_update_lock);
+	return ret;
+}
+
+static ssize_t show_curr_thres(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int ret, indx;
+	uint8_t data;
+	struct sensor_device_attribute_2 *s_attr =
+					to_sensor_dev_attr_2(attr);
+
+	WARN_ON(s_attr->nr != 0 && s_attr->nr != 1);
+
+	ret = intel_scu_ipc_ioread8(BATTCURRENTLIMIT12, &data);
+	if (ret)
+		return ret;
+
+	/* read bits [0-2] or [3-5] of data */
+	indx = (data >> (3 * s_attr->nr)) & 0x07;
+
+	return sprintf(buf, "%d\n", curr_thresholds[s_attr->nr][indx]);
+}
+
+static int get_timer_threshold(unsigned long time, int index)
+{
+	if (index == 0 || index == 1) {
+		if (time < TIME_LIMIT12_BASE || time > TIME_LIMIT12_MAX)
+			return -EINVAL;
+		return (time - TIME_LIMIT12_BASE) / TIME_LIMIT12_OFFSET;
+	} else {
+		if (time < TIME_LIMIT3_BASE || time > TIME_LIMIT3_MAX)
+			return -EINVAL;
+		return time / TIME_LIMIT3_OFFSET;
+	}
+}
+
+static ssize_t store_timer_thres(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long time;
+	uint8_t data;
+	int ret, val;
+	struct sensor_device_attribute_2 *s_attr =
+					to_sensor_dev_attr_2(attr);
+
+	if (strict_strtoul(buf, 10, &time))
+		return -EINVAL;
+
+	val = get_timer_threshold(time, s_attr->nr);
+	if (val < 0)
+		ret = -EINVAL;
+
+	mutex_lock(&ocd_update_lock);
+
+	if (s_attr->nr == 2) {
+		ret = intel_scu_ipc_ioread8(BATTTIMELIMIT3, &data);
+		if (ret)
+			goto ipc_fail;
+		/* set bits [0-3] to val */
+		data = (data & 0xF0) | val;
+
+		ret = intel_scu_ipc_iowrite8(BATTTIMELIMIT3, data);
+		if (ret)
+			goto ipc_fail;
+	} else {
+		ret = intel_scu_ipc_ioread8(BATTTIMELIMIT12, &data);
+		if (ret)
+			goto ipc_fail;
+
+		if (s_attr->nr == 0)
+			/* set bits [0-3] to val */
+			data = (data & 0xF0) | val;
+		else
+			/* set bits [4-7] to val */
+			data = (data & 0x0F) | (val << 4);
+
+		ret = intel_scu_ipc_iowrite8(BATTTIMELIMIT12, data);
+		if (ret)
+			goto ipc_fail;
+	}
+	ret = count;
+
+ipc_fail:
+	mutex_unlock(&ocd_update_lock);
+	return ret;
+}
+
+static ssize_t show_timer_thres(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int ret, val;
+	uint8_t data;
+	int time;
+	struct sensor_device_attribute_2 *s_attr =
+					to_sensor_dev_attr_2(attr);
+
+	if (s_attr->nr == 0 || s_attr->nr == 1) {
+		ret = intel_scu_ipc_ioread8(BATTTIMELIMIT12, &data);
+		if (ret)
+			return ret;
+
+		val = s_attr->nr ? ((data >> 4) & 0x0F) : (data & 0x0F);
+		time = TIME_LIMIT12_BASE + val * TIME_LIMIT12_OFFSET;
+
+	} else if (s_attr->nr == 2) {
+		ret = intel_scu_ipc_ioread8(BATTTIMELIMIT3, &data);
+		if (ret)
+			return ret;
+
+		val = data & 0x0F;
+		time = (val) ? (val * TIME_LIMIT3_OFFSET) : TIME_LIMIT3_BASE;
+	} else
+		return -EINVAL;
+
+	return sprintf(buf, "%d\n", time);
+}
+
+static ssize_t show_warn_count(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ocd_info *cinfo = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%ld %ld\n", cinfo->timer1_count,
+							cinfo->timer2_count);
+}
+
+static ssize_t store_acc_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct ocd_info *cinfo = dev_get_drvdata(dev);
+	unsigned long time;
+
+	if (strict_strtoul(buf, 10, &time))
+		return -EINVAL;
+
+	/* Only 0 can be written to clear, otherwise return*/
+	if (time)
+		return -EINVAL;
+
+	/* Set the acc_time to 'now' */
+	cinfo->acc_time = jiffies;
+
+	/* Clear warning counters */
+	cinfo->timer1_count = cinfo->timer2_count = 0;
+
+	return count;
+}
+static ssize_t show_acc_time(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ocd_info *cinfo = dev_get_drvdata(dev);
+	long time_gap;
+
+	if (cinfo->acc_time == 0)
+		return sprintf(buf, "0\n");
+
+	/* Calculate the time gap in jiffies */
+	time_gap = jiffies - cinfo->acc_time;
+
+	/* Convert to milli secs and print */
+	return sprintf(buf, "%u\n", jiffies_to_msecs(time_gap));
+}
+
+static irqreturn_t ocd_handle_intrpt(int irq, void *dev_data)
+{
+	int ret;
+	uint8_t data;
+	struct ocd_info *cinfo = (struct ocd_info *)dev_data;
+
+	if (!cinfo)
+		return IRQ_NONE;
+
+	mutex_lock(&ocd_update_lock);
+
+	/* Interrupt came now */
+	cinfo->acc_time = jiffies;
+
+	/* Read the interrupt status register */
+	ret = intel_scu_ipc_ioread8(BRSTCONTROLSTATUS, &data);
+	if (ret)
+		goto ipc_fail;
+
+	/* Cache the interrupt status register */
+	cinfo->intrpt_status = data;
+
+	/* It's a timer1 interrupt. Increment the counter.
+	 * Reset timer1 and camera status bits */
+	if (data & OVER_TIMER1) {
+		cinfo->timer1_count++;
+		data &= (~(OVER_TIMER1 | CAMSTAT));
+	}
+
+	/* It's a timer2 interrupt. Increment the counter.
+	 * Reset timer2 and sys burst status bits */
+	if (data & OVER_TIMER2) {
+		cinfo->timer2_count++;
+		data &= (~(OVER_TIMER2 | SYSSTAT));
+	}
+
+	if (cinfo->timer1_count == MAX_COUNT ||
+				cinfo->timer2_count == MAX_COUNT) {
+		cinfo->timer1_count = cinfo->timer2_count = 0;
+		cinfo->acc_time = jiffies;
+	}
+
+	/* Write the masked data */
+	ret = intel_scu_ipc_iowrite8(BRSTCONTROLSTATUS, data);
+	if (ret)
+		goto ipc_fail;
+
+	mutex_unlock(&ocd_update_lock);
+	return IRQ_HANDLED;
+
+ipc_fail:
+	mutex_unlock(&ocd_update_lock);
+	dev_err(cinfo->dev, "ipc read/write failed");
+	return ret;
+}
+
+static int initialize_hw(struct ocd_info *cinfo)
+{
+	int ret;
+	uint8_t data;
+
+	ret = intel_scu_ipc_ioread8(BRSTCONFIGOUTPUTS, &data);
+	if (ret)
+		return ret;
+
+	/* Enable sys burst output signal */
+	ret = intel_scu_ipc_iowrite8(BRSTCONFIGOUTPUTS, (data | SYSOUTEN));
+	if (ret)
+		return ret;
+
+	ret = intel_scu_ipc_ioread8(BRSTCONFIGACTIONS, &data);
+	if (ret)
+		return ret;
+
+	/* Enable sys burst action signal*/
+	return intel_scu_ipc_iowrite8(BRSTCONFIGACTIONS, (data | SYSACTEN));
+}
+
+static SENSOR_DEVICE_ATTR_2(bcu_status, S_IRUGO | S_IWUSR,
+				show_bcu_status, store_bcu_status, 0, 0);
+
+static SENSOR_DEVICE_ATTR_2(action_mask, S_IRUGO | S_IWUSR,
+				show_action_mask, store_action_mask, 0, 0);
+
+static SENSOR_DEVICE_ATTR_2(current_warning, S_IRUGO | S_IWUSR,
+				show_curr_thres, store_curr_thres, 0, 0);
+static SENSOR_DEVICE_ATTR_2(current_shutdown, S_IRUGO | S_IWUSR,
+				show_curr_thres, store_curr_thres, 1, 0);
+
+static SENSOR_DEVICE_ATTR_2(timer_warning, S_IRUGO | S_IWUSR,
+				show_timer_thres, store_timer_thres, 0, 0);
+static SENSOR_DEVICE_ATTR_2(timer_hw_action, S_IRUGO | S_IWUSR,
+				show_timer_thres, store_timer_thres, 1, 0);
+static SENSOR_DEVICE_ATTR_2(timer_shutdown, S_IRUGO | S_IWUSR,
+				show_timer_thres, store_timer_thres, 2, 0);
+
+static SENSOR_DEVICE_ATTR_2(accumulation_time, S_IRUGO | S_IWUSR,
+					show_acc_time, store_acc_time, 0, 0);
+
+static SENSOR_DEVICE_ATTR_2(warning_count, S_IRUGO, show_warn_count,
+								NULL, 0, 0);
+static SENSOR_DEVICE_ATTR_2(action_status, S_IRUGO, show_action_status,
+								NULL, 0, 0);
+
+static struct attribute *mid_ocd_attrs[] = {
+	&sensor_dev_attr_bcu_status.dev_attr.attr,
+	&sensor_dev_attr_action_mask.dev_attr.attr,
+	&sensor_dev_attr_current_warning.dev_attr.attr,
+	&sensor_dev_attr_current_shutdown.dev_attr.attr,
+	&sensor_dev_attr_timer_warning.dev_attr.attr,
+	&sensor_dev_attr_timer_hw_action.dev_attr.attr,
+	&sensor_dev_attr_timer_shutdown.dev_attr.attr,
+	&sensor_dev_attr_warning_count.dev_attr.attr,
+	&sensor_dev_attr_accumulation_time.dev_attr.attr,
+	&sensor_dev_attr_action_status.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group mid_ocd_gr = {
+	.name = "msic_current",
+	.attrs = mid_ocd_attrs
+};
+
+static int mid_ocd_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct ocd_info *cinfo = kzalloc(sizeof(struct ocd_info), GFP_KERNEL);
+
+	if (!cinfo) {
+		dev_err(&pdev->dev, "kzalloc failed\n");
+		return -ENOMEM;
+	}
+
+	cinfo->pdev = pdev;
+	cinfo->irq = pdev->id;
+	platform_set_drvdata(pdev, cinfo);
+
+	/* Creating a sysfs group with mid_ocd_gr attributes */
+	ret = sysfs_create_group(&pdev->dev.kobj, &mid_ocd_gr);
+	if (ret) {
+		dev_err(&pdev->dev, "sysfs create group failed\n");
+		goto ocd_error1;
+	}
+
+	/* Registering with hwmon class */
+	cinfo->dev = hwmon_device_register(&pdev->dev);
+	if (IS_ERR(cinfo->dev)) {
+		ret = PTR_ERR(cinfo->dev);
+		cinfo->dev = NULL;
+		dev_err(&pdev->dev, "hwmon_dev_regs failed\n");
+		goto ocd_error2;
+	}
+
+	/* Enable Interrupt */
+	ret = request_threaded_irq(cinfo->irq, NULL, ocd_handle_intrpt,
+						0, DRIVER_NAME, cinfo);
+	if (ret) {
+		dev_err(cinfo->dev, "request_threaded_irq failed:%d\n", ret);
+		goto ocd_error2;
+	}
+
+	ret = initialize_hw(cinfo);
+	if (ret)
+		goto ocd_error2;
+
+	ret = configure_bcu(1);
+	if (ret)
+		goto ocd_error2;
+
+	return 0;
+
+ocd_error2:
+	sysfs_remove_group(&pdev->dev.kobj, &mid_ocd_gr);
+ocd_error1:
+	kfree(cinfo);
+	return ret;
+}
+
+static int mid_ocd_resume(struct platform_device *pdev)
+{
+	int ret;
+	struct ocd_info *cinfo = platform_get_drvdata(pdev);
+
+	ret = initialize_hw(cinfo);
+	if (ret)
+		return ret;
+
+	return configure_bcu(0);
+}
+
+static int mid_ocd_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return configure_bcu(1);
+}
+
+static int mid_ocd_remove(struct platform_device *pdev)
+{
+	struct ocd_info *cinfo = platform_get_drvdata(pdev);
+
+	if (cinfo) {
+		hwmon_device_unregister(cinfo->dev);
+		sysfs_remove_group(&pdev->dev.kobj, &mid_ocd_gr);
+		kfree(cinfo);
+	}
+	return 0;
+}
+
+/*********************************************************************
+ *		Driver initialisation and finalization
+ *********************************************************************/
+static const struct platform_device_id ocd_id_table[] = {
+	{ DRIVER_NAME, 1 },
+};
+
+static struct platform_driver mid_over_curr_detect_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = mid_ocd_probe,
+	.suspend = mid_ocd_suspend,
+	.resume = mid_ocd_resume,
+	.remove = __devexit_p(mid_ocd_remove),
+	.id_table = ocd_id_table,
+};
+
+static int __init mid_ocd_module_init(void)
+{
+	return platform_driver_register(&mid_over_curr_detect_driver);
+}
+
+static void __exit mid_ocd_module_exit(void)
+{
+	platform_driver_unregister(&mid_over_curr_detect_driver);
+}
+
+module_init(mid_ocd_module_init);
+module_exit(mid_ocd_module_exit);
+
+MODULE_AUTHOR("Durgadoss R <durgadoss.r@xxxxxxxxx>");
+MODULE_DESCRIPTION("Intel Medfield Over Current Detection Driver");
+MODULE_LICENSE("GPL");


_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux