diff options
author | Durgadoss R <durgadoss.r@intel.com> | 2010-12-09 10:37:40 +0000 |
---|---|---|
committer | Alan Cox <alan@linux.intel.com> | 2010-12-09 10:37:40 +0000 |
commit | 44c2514121c88ea77f8a754531b38fe4c3393598 (patch) | |
tree | 3822f26643e3b809a952ed03ba5cd35e098a3a60 /drivers | |
parent | 7b58005274b195331dd4d57c069347fbc7e06d6c (diff) | |
download | mrst-s0i3-test-44c2514121c88ea77f8a754531b38fe4c3393598.tar.gz mrst-s0i3-test-44c2514121c88ea77f8a754531b38fe4c3393598.tar.xz mrst-s0i3-test-44c2514121c88ea77f8a754531b38fe4c3393598.zip |
Medfield_Current_Monitoring_Driver
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@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 9 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/intel_mid_ocd.c | 647 |
3 files changed, 657 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a56f6adf3b7..e5bdf246766 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 2479b3da272..68f4c99312d 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 00000000000..0e78d9c1414 --- /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@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/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@intel.com>"); +MODULE_DESCRIPTION("Intel Medfield Over Current Detection Driver"); +MODULE_LICENSE("GPL"); |