aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/intel_mid_ocd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/intel_mid_ocd.c')
-rw-r--r--drivers/platform/x86/intel_mid_ocd.c640
1 files changed, 640 insertions, 0 deletions
diff --git a/drivers/platform/x86/intel_mid_ocd.c b/drivers/platform/x86/intel_mid_ocd.c
new file mode 100644
index 00000000000..a1284a06793
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_ocd.c
@@ -0,0 +1,640 @@
+/*
+ * 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@intel.com>
+ */
+
+#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/interrupt.h>
+#include <linux/gpio.h>
+
+#include <asm/intel_scu_ipc.h>
+
+#define DRIVER_NAME "intel_mid_ocd"
+#define FIRMWARE_NAME "msic_cmd"
+
+/* 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;
+
+ if (val != 0 && val != 1)
+ 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)
+ return -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 = platform_get_irq(pdev, 0);
+ 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;
+ }
+
+ /* 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) {
+ 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[] = {
+ { FIRMWARE_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@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Over Current Detection Driver");
+MODULE_LICENSE("GPL");