android_kernel_modules_leno.../drivers/hwmon/intel_mrfl_ocd.c

1146 lines
29 KiB
C

/*
* intel_mrfl_ocd.c - Intel Merrifield Platform Over Current Detection Driver
*
*
* Copyright (C) 2011 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>
*
* This driver monitors the voltage level of the system. When the voltage
* drops below a programmed threshold, it notifies the CPU of the drop.
* Also, the driver configures the HW to take some actions to prevent
* system crash due to sudden drop in voltage.
* DEVICE_NAME: Intel Merrifield platform - PMIC: Burst Control Unit
*/
#define pr_fmt(fmt) "intel_mrfl_ocd: " fmt
#include <linux/module.h>
#include <linux/init.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 <linux/rpmsg.h>
#include <linux/debugfs.h>
#include <linux/power_supply.h>
#include <asm/intel_scu_ipc.h>
#include <asm/intel_scu_pmic.h>
#include <asm/intel_basincove_ocd.h>
#include <linux/version.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
#include <linux/platform_data/intel_mid_remoteproc.h>
#else
#include <asm/intel_mid_remoteproc.h>
#endif
#define DRIVER_NAME "bcove_bcu"
#define CAMFLASH_STATE_NORMAL 0
#define CAMFLASH_STATE_CRITICAL 3
#define PMIC_ID_ADDR 0x00
#define PMIC_CHIP_ID_SC_B0_VAL 0x08
/* The registers listed below no longer exist for shadycove PMIC-BO */
#define PMIC_B0_BCU_NOP_ADDRx42 (6) /* VFLEXSRC_BEH_REG - 0x5E42 */
#define PMIC_B0_BCU_NOP_ADDRx43 (7) /* VFLEXDIS_BEH_REG - 0x5E43 */
#define PMIC_B0_BCU_NOP_ADDRx45 (9) /* CAMFLTORCH_BEH_REG - 0x5E45 */
#define PMIC_B0_BCU_NOP_ADDRX46 (10) /* CAMFLDIS_BEH_REG - 0x5E46 */
/* 'enum' of BCU events */
enum bcu_events { VWARN1, VWARN2, VCRIT, GSMPULSE, TXPWRTH, UNKNOWN, __COUNT };
static DEFINE_MUTEX(ocd_update_lock);
/* Warning levels for Voltage (in mV) */
static const unsigned long volt_thresholds[NUM_THRESHOLDS] = {
2550, 2600, 2700, 2750, 2800, 2900, 3000, 3100 };
/* Warning levels for Current (in mA) */
static const unsigned long curr_thresholds[NUM_THRESHOLDS] = {
1600, 2000, 2400, 2600, 3000, 3200, 3400, 3600 };
struct ocd_info {
struct device *dev;
struct platform_device *pdev;
struct delayed_work vwarn2_irq_work;
void *bcu_intr_addr;
int irq;
};
static uint8_t cam_flash_state;
static uint32_t intr_count_lvl1;
static uint32_t intr_count_lvl2;
static uint32_t intr_count_lvl3;
static void enable_volt_trip_points(void)
{
int i;
int ret;
/*
* Enable the Voltage comparator logic, so that the output
* signals are asserted when a voltage drop occurs.
*/
for (i = 0; i < NUM_VOLT_LEVELS; i++) {
ret = intel_scu_ipc_update_register(VWARN1_CFG + i,
VWARN_EN,
VWARN_EN_MASK);
if (ret)
pr_err("EM_BCU: Error in %s updating register 0x%x\n",
__func__, (VWARN1_CFG + i));
}
}
static void enable_current_trip_points(void)
{
int i;
int ret;
/*
* Enable the Current comparator logic, so that the output
* signals are asserted when the platform current surges.
*/
for (i = 0; i < NUM_CURR_LEVELS; i++) {
ret = intel_scu_ipc_update_register(ICCMAXVCC_CFG + i,
ICCMAXVCC_EN,
ICCMAXVCC_EN_MASK);
if (ret)
pr_err("EM_BCU: Error in %s updating reg 0x%0x\n",
__func__, (ICCMAXVCC_CFG + i));
}
}
static int find_threshold(const unsigned long *arr,
unsigned long value)
{
int pos = 0;
if (value < arr[0] || value > arr[NUM_THRESHOLDS - 1])
return -EINVAL;
/* Find the index of 'value' in the thresholds array */
while (pos < NUM_THRESHOLDS && value >= arr[pos])
++pos;
return pos - 1;
}
static int set_threshold(u16 reg_addr, int pos)
{
int ret;
uint8_t data;
mutex_lock(&ocd_update_lock);
ret = intel_scu_ipc_ioread8(reg_addr, &data);
if (ret)
goto ipc_fail;
/* Set bits [0-2] to value of pos */
data = (data & 0xF8) | pos;
ret = intel_scu_ipc_iowrite8(reg_addr, data);
ipc_fail:
mutex_unlock(&ocd_update_lock);
return ret;
}
static int program_bcu(void *ocd_smip_addr)
{
int ret, i;
u8 *smip_data;
uint8_t pmic_id;
if (!ocd_smip_addr)
return -ENXIO;
smip_data = (u8 *)ocd_smip_addr;
mutex_lock(&ocd_update_lock);
/* Get PMIC ID */
ret = intel_scu_ipc_ioread8(PMIC_ID_ADDR, &pmic_id);
if (ret) {
pr_err("Error reading PMIC ID register\n");
goto ipc_fail;
}
/* Skipping write to registers for shadycove B0 */
for (i = 0; i < NUM_SMIP_BYTES-1; i++, smip_data++) {
if ((i == PMIC_B0_BCU_NOP_ADDRx42 || i == PMIC_B0_BCU_NOP_ADDRx43 ||
i == PMIC_B0_BCU_NOP_ADDRx45 || i == PMIC_B0_BCU_NOP_ADDRX46)
&& (pmic_id == PMIC_CHIP_ID_SC_B0_VAL))
continue;
ret = intel_scu_ipc_iowrite8(VWARN1_CFG + i, *smip_data);
if (ret)
goto ipc_fail;
}
/* MBCUIRQ register address not consecutive with other BCU registers */
ret = intel_scu_ipc_iowrite8(MBCUIRQ, *smip_data);
if (ret) {
pr_err("EM_BCU: Inside %s error(%d) in writing addr 0x%02x\n",
__func__, ret, MBCUIRQ);
goto ipc_fail;
}
pr_debug("EM_BCU: Registers are programmed successfully.\n");
ipc_fail:
mutex_unlock(&ocd_update_lock);
return ret;
}
static ssize_t store_curr_thres(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned long curnt;
int pos, ret;
struct sensor_device_attribute_2 *s_attr =
to_sensor_dev_attr_2(attr);
if (kstrtoul(buf, 10, &curnt))
return -EINVAL;
pos = find_threshold(curr_thresholds, curnt);
if (pos < 0)
return -EINVAL;
/*
* Since VCC_CFG and VNN_CFG are consecutive registers, calculate the
* required register address using s_attr->nr.
*/
ret = set_threshold(ICCMAXVCC_CFG + s_attr->nr, pos);
return ret ? ret : count;
}
static ssize_t show_curr_thres(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
uint8_t data;
struct sensor_device_attribute_2 *s_attr =
to_sensor_dev_attr_2(attr);
mutex_lock(&ocd_update_lock);
ret = intel_scu_ipc_ioread8(ICCMAXVCC_CFG + s_attr->nr, &data);
mutex_unlock(&ocd_update_lock);
if (ret)
return ret;
/* Read bits [0-2] of data to get the index into the array */
return sprintf(buf, "%lu\n", curr_thresholds[data & 0x07]);
}
static ssize_t store_volt_thres(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned long volt;
int pos, ret;
struct sensor_device_attribute_2 *s_attr =
to_sensor_dev_attr_2(attr);
if (kstrtoul(buf, 10, &volt))
return -EINVAL;
pos = find_threshold(volt_thresholds, volt);
if (pos < 0)
return -EINVAL;
/*
* The voltage thresholds are in descending order in VWARN*_CFG
* registers. So calculate 'pos' by substracting from NUM_THRESHOLDS.
*/
pos = NUM_THRESHOLDS - pos - 1;
/*
* Since VWARN*_CFG are consecutive registers, calculate the
* required register address using s_attr->nr.
*/
ret = set_threshold(VWARN1_CFG + s_attr->nr, pos);
return ret ? ret : count;
}
static ssize_t show_volt_thres(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret, index;
uint8_t data;
struct sensor_device_attribute_2 *s_attr =
to_sensor_dev_attr_2(attr);
mutex_lock(&ocd_update_lock);
ret = intel_scu_ipc_ioread8(VWARN1_CFG + s_attr->nr, &data);
mutex_unlock(&ocd_update_lock);
if (ret)
return ret;
/* Read bits [0-2] of data to get the index into the array */
index = NUM_THRESHOLDS - (data & 0x07) - 1;
return sprintf(buf, "%lu\n", volt_thresholds[index]);
}
static ssize_t store_crit_shutdown(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
uint8_t data;
unsigned long flag;
if (kstrtoul(buf, 10, &flag) || (flag != 0 && flag != 1))
return -EINVAL;
mutex_lock(&ocd_update_lock);
ret = intel_scu_ipc_ioread8(VCRIT_CFG, &data);
if (ret)
goto ipc_fail;
/*
* flag:1 enables shutdown due to burst current
* flag:0 disables shutdown due to burst current
*/
if (flag)
data |= VCRIT_SHUTDOWN;
else
data &= ~VCRIT_SHUTDOWN;
ret = intel_scu_ipc_iowrite8(VCRIT_CFG, data);
if (!ret)
ret = count;
ipc_fail:
mutex_unlock(&ocd_update_lock);
return ret;
}
static ssize_t show_crit_shutdown(struct device *dev,
struct device_attribute *attr, char *buf)
{
int flag, ret;
uint8_t data;
mutex_lock(&ocd_update_lock);
ret = intel_scu_ipc_ioread8(VCRIT_CFG, &data);
if (!ret) {
/* 'flag' is 1 if CRIT_SHUTDOWN is enabled, 0 otherwise */
flag = !!(data & VCRIT_SHUTDOWN);
}
mutex_unlock(&ocd_update_lock);
return ret ? ret : sprintf(buf, "%d\n", flag);
}
static ssize_t show_intr_count(struct device *dev,
struct device_attribute *attr, char *buf)
{
uint32_t value;
int level = to_sensor_dev_attr(attr)->index;
switch (level) {
case VWARN1:
value = intr_count_lvl1;
break;
case VWARN2:
value = intr_count_lvl2;
break;
case VCRIT:
value = intr_count_lvl3;
break;
default:
return -EINVAL;
}
return sprintf(buf, "%d\n", value);
}
static ssize_t store_camflash_ctrl(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
uint8_t value;
if (kstrtou8(buf, 10, &value))
return -EINVAL;
if ((value < CAMFLASH_STATE_NORMAL) ||
(value > CAMFLASH_STATE_CRITICAL))
return -EINVAL;
cam_flash_state = value;
return count;
}
static ssize_t show_camflash_ctrl(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", cam_flash_state);
}
#ifdef CONFIG_DEBUG_FS
struct dentry *bcbcu_dbgfs_root;
static struct bcu_reg_info bcbcu_reg[] = {
reg_info(S_BCUINT),
reg_info(BCUIRQ),
reg_info(IRQLVL1),
reg_info(VWARN1_CFG),
reg_info(VWARN2_CFG),
reg_info(VCRIT_CFG),
reg_info(ICCMAXVSYS_CFG),
reg_info(ICCMAXVCC_CFG),
reg_info(ICCMAXVNN_CFG),
reg_info(VFLEXSRC_BEH),
reg_info(VFLEXDIS_BEH),
reg_info(VIBDIS_BEH),
reg_info(CAMFLTORCH_BEH),
reg_info(CAMFLDIS_BEH),
reg_info(BCUDISW2_BEH),
reg_info(BCUDISCRIT_BEH),
reg_info(S_BCUCTRL),
reg_info(MBCUIRQ),
reg_info(MIRQLVL1)
};
/**
* bcbcu_dbgfs_write - debugfs: write the new state to an endpoint.
* @file: The seq_file to write data to.
* @user_buf: the user data which is need to write to an endpoint
* @count: the size of the user data
* @pos: loff_t" is a "long offset", which is the current reading or writing
* position.
*
* Send data to the device. If NULL,-EINVAL/-EFAULT return to the write call to
* the calling program if it is non-negative return value represents the number
* of bytes successfully written.
*/
static ssize_t bcbcu_dbgfs_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *pos)
{
char buf[count];
u8 data;
u16 addr;
int ret;
struct seq_file *s = file->private_data;
if (!s) {
ret = -EINVAL;
goto error;
}
addr = *((u16 *)s->private);
if ((addr == BCUIRQ) || (addr == S_BCUINT) || (addr == IRQLVL1)) {
pr_err("EM_BCU: DEBUGFS no permission to write Addr(0x%04x)\n",
addr);
ret = -EIO;
goto error;
}
if (copy_from_user(buf, user_buf, count)) {
pr_err("EM_BCU: DEBUGFS unable to copy the user data.\n");
ret = -EFAULT;
goto error;
}
buf[count-1] = '\0';
if (kstrtou8(buf, 16, &data)) {
pr_err("EM_BCU: DEBUGFS invalid user data.\n");
ret = -EINVAL;
goto error;
}
ret = intel_scu_ipc_iowrite8(addr, data);
if (ret < 0) {
pr_err("EM_BCU: Dbgfs write error Addr: 0x%04x Data: 0x%02x\n",
addr, data);
goto error;
}
pr_debug("EM_BCU: DEBUGFS written Data: 0x%02x Addr: 0x%04x\n",
data, addr);
return count;
error:
return ret;
}
/**
* bcbcu_reg_show - debugfs: show the state of an endpoint.
* @s: The seq_file to read data from.
* @unused: not used
*
* This debugfs entry shows the content of the register
* given in the data parameter.
*/
static int bcbcu_reg_show(struct seq_file *s, void *unused)
{
u16 addr = 0;
u8 data = 0;
int ret;
addr = *((u16 *)s->private);
ret = intel_scu_ipc_ioread8(addr, &data);
if (ret) {
pr_err("EM_BCU: Error in reading 0x%04x register!!\n", addr);
return ret;
}
seq_printf(s, "0x%02x\n", data);
return 0;
}
/**
* bcbcu_dbgfs_open - debugfs: to open the endpoint for read/write operation.
* @inode: inode structure is used by the kernel internally to represent files.
* @file: It is created by the kernel on open and is passed to any function
* that operates on the file, until the last close. After all instances
* of the file are closed, the kernel releases the data structure.
*
* This is the first operation of the files on the device, does not require the
* driver to declare a corresponding method. If this is NULL, the device is
* turned on has been successful, but the driver will not be notified
*/
static int bcbcu_dbgfs_open(struct inode *inode, struct file *file)
{
return single_open(file, bcbcu_reg_show, inode->i_private);
}
static const struct file_operations bcbcu_dbgfs_fops = {
.owner = THIS_MODULE,
.open = bcbcu_dbgfs_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
.write = bcbcu_dbgfs_write,
};
static void bcbcu_create_debugfs(struct ocd_info *info)
{
char reg_name[MAX_REGNAME_LEN] = {0};
u32 idx;
u32 max_dbgfs_num = ARRAY_SIZE(bcbcu_reg);
struct dentry *entry;
bcbcu_dbgfs_root = debugfs_create_dir(DRIVER_NAME, NULL);
if (IS_ERR(bcbcu_dbgfs_root)) {
dev_warn(info->dev, "DEBUGFS directory(%s) create failed!\n",
DRIVER_NAME);
return;
}
for (idx = 0; idx < max_dbgfs_num; idx++) {
snprintf(reg_name, MAX_REGNAME_LEN, "%s", bcbcu_reg[idx].name);
entry = debugfs_create_file(reg_name,
bcbcu_reg[idx].mode,
bcbcu_dbgfs_root,
&bcbcu_reg[idx].addr,
&bcbcu_dbgfs_fops);
if (IS_ERR(entry)) {
debugfs_remove_recursive(bcbcu_dbgfs_root);
bcbcu_dbgfs_root = NULL;
dev_warn(info->dev, "DEBUGFS %s creation failed!!\n",
reg_name);
return;
}
}
dev_info(info->dev, "DEBUGFS %s created successfully.\n", DRIVER_NAME);
}
static inline void bcbcu_remove_debugfs(struct ocd_info *info)
{
if (bcbcu_dbgfs_root)
debugfs_remove_recursive(bcbcu_dbgfs_root);
}
#else
static inline void bcbcu_create_debugfs(struct ocd_info *info) { }
static inline void bcbcu_remove_debugfs(struct ocd_info *info) { }
#endif /* CONFIG_DEBUG_FS */
/**
* vwarn2_irq_enable_work: delayed work queue function, which is used to unmask
* (enable) the VWARN2 interrupt after the specified delay time while sceduling.
*/
static void vwarn2_irq_enable_work(struct work_struct *work)
{
int ret = 0;
struct ocd_info *info = container_of(work,
struct ocd_info,
vwarn2_irq_work.work);
dev_dbg(info->dev, "EM_BCU: Inside %s\n", __func__);
/* Unmasking BCU MVWARN2 Interrupt, to see the interrupt occurrence */
ret = intel_scu_ipc_update_register(MBCUIRQ, ~MVWARN2, MVWARN2_MASK);
if (ret) {
dev_err(info->dev, "EM_BCU: Error in %s updating reg 0x%x\n",
__func__, MBCUIRQ);
}
}
static inline struct power_supply *get_psy_battery(void)
{
struct class_dev_iter iter;
struct device *dev;
static struct power_supply *pst;
class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
while ((dev = class_dev_iter_next(&iter))) {
pst = (struct power_supply *)dev_get_drvdata(dev);
if (pst->type == POWER_SUPPLY_TYPE_BATTERY) {
class_dev_iter_exit(&iter);
return pst;
}
}
class_dev_iter_exit(&iter);
return NULL;
}
/* Reading the Voltage now value of the battery */
static inline int bcu_get_battery_voltage(int *volt)
{
struct power_supply *psy;
union power_supply_propval val;
int ret;
psy = get_psy_battery();
if (!psy)
return -EINVAL;
ret = psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
if (!ret)
*volt = (val.intval);
return ret;
}
/**
* Initiate Graceful Shutdown by setting the SOC to 0% via battery driver and
* post the power supply changed event to indicate the change in battery level.
*/
static inline int bcu_action_voltage_drop(void)
{
struct power_supply *psy;
union power_supply_propval val;
int ret;
psy = get_psy_battery();
if (!psy)
return -EINVAL;
/* setting battery capacity to 0 */
val.intval = 0;
ret = psy->set_property(psy, POWER_SUPPLY_PROP_CAPACITY, &val);
if (ret < 0)
return ret;
power_supply_changed(psy);
return 0;
}
static void handle_VW1_event(void *dev_data)
{
uint8_t irq_status;
struct ocd_info *cinfo = (struct ocd_info *)dev_data;
int ret;
dev_info(cinfo->dev, "EM_BCU: VWARN1 Event has occured\n");
/* Trigger graceful shutdown via battery driver by setting SOC to 0% */
dev_info(cinfo->dev, "EM_BCU: Trigger Graceful Shutdown\n");
ret = bcu_action_voltage_drop();
if (ret)
dev_err(cinfo->dev,
"EM_BCU: Error in Triggering Graceful Shutdown\n");
/**
* Masking the BCU MVWARN1 Interrupt, since software does graceful
* shutdown once VWARN1 interrupt occurs. So we never expect another
* VWARN1 interrupt.
*/
ret = intel_scu_ipc_update_register(MBCUIRQ, MVWARN1, MVWARN1_MASK);
if (ret) {
dev_err(cinfo->dev, "EM_BCU: Error in %s updating reg 0x%x\n",
__func__, MBCUIRQ);
goto ipc_fail;
}
ret = intel_scu_ipc_ioread8(S_BCUINT, &irq_status);
if (ret)
goto ipc_fail;
dev_dbg(cinfo->dev, "EM_BCU: S_BCUINT: %x\n", irq_status);
if (!(irq_status & SVWARN1)) {
/* Vsys is above WARN1 level */
dev_info(cinfo->dev, "EM_BCU: Recovered from VWARN1 Level\n");
}
return;
ipc_fail:
dev_err(cinfo->dev, "EM_BCU: ipc read/write failed:func:%s()\n",
__func__);
return;
}
static void handle_VW2_event(void *dev_data)
{
uint8_t irq_status, beh_data;
struct ocd_info *cinfo = (struct ocd_info *)dev_data;
int ret;
dev_info(cinfo->dev, "EM_BCU: VWARN2 Event has occured\n");
ret = intel_scu_ipc_ioread8(S_BCUINT, &irq_status);
if (ret)
goto ipc_fail;
dev_dbg(cinfo->dev, "EM_BCU: S_BCUINT: %x\n", irq_status);
/* If Vsys is below WARN2 level-No action required from driver */
if (!(irq_status & SVWARN2)) {
/* Vsys is above WARN2 level */
dev_info(cinfo->dev, "EM_BCU: Recovered from VWARN2 Level\n");
/* clearing BCUDISW2 signal if asserted */
ret = intel_scu_ipc_ioread8(BCUDISW2_BEH, &beh_data);
if (ret)
goto ipc_fail;
if (IS_ASSRT_ON_VW2(beh_data) && IS_STICKY(beh_data)) {
ret = intel_scu_ipc_update_register(S_BCUCTRL,
S_BCUDISW2, S_BCUDISW2_MASK);
if (ret)
goto ipc_fail;
}
/* clearing CAMFLDIS# signal if asserted */
ret = intel_scu_ipc_ioread8(CAMFLDIS_BEH, &beh_data);
if (ret)
goto ipc_fail;
if (IS_ASSRT_ON_VW2(beh_data) && IS_STICKY(beh_data)) {
ret = intel_scu_ipc_update_register(S_BCUCTRL,
S_CAMFLDIS, S_CAMFLDIS_MASK);
if (ret)
goto ipc_fail;
}
/* clearing CAMFLTORCH signal if asserted */
ret = intel_scu_ipc_ioread8(CAMFLTORCH_BEH, &beh_data);
if (ret)
goto ipc_fail;
if (IS_ASSRT_ON_VW2(beh_data) && IS_STICKY(beh_data)) {
ret = intel_scu_ipc_update_register(S_BCUCTRL,
S_CAMFLTORCH, S_CAMFLTORCH_MASK);
if (ret)
goto ipc_fail;
}
} else {
/**
* Masking BCU VWARN2 Interrupt, to avoid multiple VWARN2
* interrupt occurrence continuously.
*/
ret = intel_scu_ipc_update_register(MBCUIRQ,
MVWARN2,
MVWARN2_MASK);
if (ret) {
dev_err(cinfo->dev,
"EM_BCU: Error in %s updating reg 0x%x\n",
__func__, MBCUIRQ);
}
cancel_delayed_work_sync(&cinfo->vwarn2_irq_work);
/**
* Schedule the work to re-enable the VWARN2 interrupt after
* 30sec delay
*/
schedule_delayed_work(&cinfo->vwarn2_irq_work,
VWARN2_INTR_EN_DELAY);
}
return;
ipc_fail:
dev_err(cinfo->dev, "EM_BCU: ipc read/write failed:func:%s()\n",
__func__);
return;
}
static void handle_VC_event(void *dev_data)
{
struct ocd_info *cinfo = (struct ocd_info *)dev_data;
int ret = 0;
dev_info(cinfo->dev, "EM_BCU: VCRIT Event has occured\n");
/**
* Masking BCU VCRIT Interrupt, since hardware does critical hardware
* shutdown once VCRIT interrupt occurs. So we never expect another
* VCRIT interrupt.
*/
ret = intel_scu_ipc_update_register(MBCUIRQ, MVCRIT, MVCRIT_MASK);
if (ret)
dev_err(cinfo->dev, "EM_BCU: Error in %s updating reg 0x%x\n",
__func__, MBCUIRQ);
return;
}
static irqreturn_t ocd_intrpt_handler(int irq, void *dev_data)
{
return IRQ_WAKE_THREAD;
}
static irqreturn_t ocd_intrpt_thread_handler(int irq, void *dev_data)
{
int ret;
int bat_volt;
unsigned int irq_data;
struct ocd_info *cinfo = (struct ocd_info *)dev_data;
if (!cinfo)
return IRQ_NONE;
mutex_lock(&ocd_update_lock);
ret = bcu_get_battery_voltage(&bat_volt);
if (ret)
dev_err(cinfo->dev,
"EM_BCU: Error in getting battery voltage\n");
else
dev_info(cinfo->dev, "EM_BCU: Battery Volatge = %dmV\n",
(bat_volt/1000));
irq_data = ioread8(cinfo->bcu_intr_addr);
/* we are not handling(no action taken) GSMPULSE_IRQ and
TXPWRTH_IRQ event */
if (irq_data & VCRIT_IRQ) {
++intr_count_lvl3;
handle_VC_event(dev_data);
}
if (irq_data & VWARN2_IRQ) {
++intr_count_lvl2;
handle_VW2_event(dev_data);
}
if (irq_data & VWARN1_IRQ) {
++intr_count_lvl1;
handle_VW1_event(dev_data);
}
if (irq_data & GSMPULSE_IRQ) {
dev_info(cinfo->dev, "EM_BCU: GSMPULSE Event has occured\n");
}
if (irq_data & TXPWRTH_IRQ) {
dev_info(cinfo->dev, "EM_BCU: TXPWRTH Event has occured\n");
}
/* Unmask BCU Interrupt in the mask register */
ret = intel_scu_ipc_update_register(MIRQLVL1, 0x00, BCU_ALERT);
if (ret) {
dev_err(cinfo->dev,
"EM_BCU: Unmasking of BCU failed:%d\n", ret);
goto ipc_fail;
}
ret = IRQ_HANDLED;
ipc_fail:
mutex_unlock(&ocd_update_lock);
return ret;
}
static SENSOR_DEVICE_ATTR_2(volt_warn1, S_IRUGO | S_IWUSR,
show_volt_thres, store_volt_thres, 0, 0);
static SENSOR_DEVICE_ATTR_2(volt_warn2, S_IRUGO | S_IWUSR,
show_volt_thres, store_volt_thres, 1, 0);
static SENSOR_DEVICE_ATTR_2(volt_crit, S_IRUGO | S_IWUSR,
show_volt_thres, store_volt_thres, 2, 0);
static SENSOR_DEVICE_ATTR_2(core_current, S_IRUGO | S_IWUSR,
show_curr_thres, store_curr_thres, 0, 0);
static SENSOR_DEVICE_ATTR_2(uncore_current, S_IRUGO | S_IWUSR,
show_curr_thres, store_curr_thres, 1, 0);
static SENSOR_DEVICE_ATTR_2(enable_crit_shutdown, S_IRUGO | S_IWUSR,
show_crit_shutdown, store_crit_shutdown, 0, 0);
static SENSOR_DEVICE_ATTR(intr_count_level1, S_IRUGO,
show_intr_count, NULL, 0);
static SENSOR_DEVICE_ATTR(intr_count_level2, S_IRUGO,
show_intr_count, NULL, 1);
static SENSOR_DEVICE_ATTR(intr_count_level3, S_IRUGO,
show_intr_count, NULL, 2);
static SENSOR_DEVICE_ATTR(camflash_ctrl, S_IRUGO | S_IWUSR,
show_camflash_ctrl, store_camflash_ctrl, 0);
static struct attribute *mrfl_ocd_attrs[] = {
&sensor_dev_attr_core_current.dev_attr.attr,
&sensor_dev_attr_uncore_current.dev_attr.attr,
&sensor_dev_attr_volt_warn1.dev_attr.attr,
&sensor_dev_attr_volt_warn2.dev_attr.attr,
&sensor_dev_attr_volt_crit.dev_attr.attr,
&sensor_dev_attr_enable_crit_shutdown.dev_attr.attr,
&sensor_dev_attr_intr_count_level1.dev_attr.attr,
&sensor_dev_attr_intr_count_level2.dev_attr.attr,
&sensor_dev_attr_intr_count_level3.dev_attr.attr,
&sensor_dev_attr_camflash_ctrl.dev_attr.attr,
NULL
};
static struct attribute_group mrfl_ocd_gr = {
.attrs = mrfl_ocd_attrs
};
static int mrfl_ocd_probe(struct platform_device *pdev)
{
int ret;
struct ocd_platform_data *ocd_plat_data;
struct ocd_bcove_config_data ocd_config_data;
struct ocd_info *cinfo = devm_kzalloc(&pdev->dev,
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 mrfl_ocd_gr attributes */
ret = sysfs_create_group(&pdev->dev.kobj, &mrfl_ocd_gr);
if (ret) {
dev_err(&pdev->dev, "sysfs create group failed\n");
goto exit_free;
}
/* 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 exit_sysfs;
}
cinfo->bcu_intr_addr = ioremap_nocache(PMIC_SRAM_BCU_ADDR, IOMAP_LEN);
if (!cinfo->bcu_intr_addr) {
ret = -ENOMEM;
dev_err(&pdev->dev, "ioremap_nocache failed\n");
goto exit_hwmon;
}
/* Unmask 1st level BCU interrupt in the mask register */
ret = intel_scu_ipc_update_register(MIRQLVL1, 0x00, BCU_ALERT);
if (ret) {
dev_err(&pdev->dev,
"EM_BCU: Unmasking of BCU failed:%d\n", ret);
goto exit_ioremap;
}
/* Register for Interrupt Handler */
ret = request_threaded_irq(cinfo->irq, ocd_intrpt_handler,
ocd_intrpt_thread_handler,
IRQF_NO_SUSPEND,
DRIVER_NAME, cinfo);
if (ret) {
dev_err(&pdev->dev,
"EM_BCU: request_threaded_irq failed:%d\n", ret);
goto exit_ioremap;
}
/*Read BCU configuration values from smip*/
ocd_plat_data = pdev->dev.platform_data;
ret = ocd_plat_data->bcu_config_data(&ocd_config_data);
if (ret) {
dev_err(&pdev->dev, "EM_BCU: Read SMIP failed:%d\n", ret);
goto exit_freeirq;
}
/* Program the BCU with default values read from the smip*/
ret = program_bcu(&ocd_config_data);
if (ret) {
dev_err(&pdev->dev, "EM_BCU: program_bcu() failed:%d\n", ret);
goto exit_freeirq;
}
enable_volt_trip_points();
enable_current_trip_points();
cam_flash_state = CAMFLASH_STATE_NORMAL;
/* Initializing delayed work for re-enabling vwarn1 interrupt */
INIT_DELAYED_WORK(&cinfo->vwarn2_irq_work, vwarn2_irq_enable_work);
/* Create debufs for the basincove bcu registers */
bcbcu_create_debugfs(cinfo);
return 0;
exit_freeirq:
free_irq(cinfo->irq, cinfo);
exit_ioremap:
iounmap(cinfo->bcu_intr_addr);
exit_hwmon:
hwmon_device_unregister(cinfo->dev);
exit_sysfs:
sysfs_remove_group(&pdev->dev.kobj, &mrfl_ocd_gr);
exit_free:
kfree(cinfo);
return ret;
}
static int mrfl_ocd_resume(struct device *dev)
{
dev_info(dev, "Resume called.\n");
return 0;
}
static int mrfl_ocd_suspend(struct device *dev)
{
dev_info(dev, "Suspend called.\n");
return 0;
}
static int mrfl_ocd_remove(struct platform_device *pdev)
{
struct ocd_info *cinfo = platform_get_drvdata(pdev);
if (cinfo) {
flush_scheduled_work();
free_irq(cinfo->irq, cinfo);
iounmap(cinfo->bcu_intr_addr);
bcbcu_remove_debugfs(cinfo);
hwmon_device_unregister(cinfo->dev);
sysfs_remove_group(&pdev->dev.kobj, &mrfl_ocd_gr);
kfree(cinfo);
}
return 0;
}
/*********************************************************************
* Driver initialisation and finalization
*********************************************************************/
static const struct dev_pm_ops mrfl_ocd_pm_ops = {
.suspend = mrfl_ocd_suspend,
.resume = mrfl_ocd_resume,
};
static const struct platform_device_id mrfl_ocd_table[] = {
{DRIVER_NAME, 1 },
};
static struct platform_driver mrfl_over_curr_detect_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.pm = &mrfl_ocd_pm_ops,
},
.probe = mrfl_ocd_probe,
.remove = mrfl_ocd_remove,
.id_table = mrfl_ocd_table,
};
static int mrfl_ocd_module_init(void)
{
return platform_driver_register(&mrfl_over_curr_detect_driver);
}
static void mrfl_ocd_module_exit(void)
{
platform_driver_unregister(&mrfl_over_curr_detect_driver);
}
/* RPMSG related functionality */
static int mrfl_ocd_rpmsg_probe(struct rpmsg_channel *rpdev)
{
int ret = 0;
if (rpdev == NULL) {
pr_err("rpmsg channel not created\n");
ret = -ENODEV;
goto out;
}
dev_info(&rpdev->dev, "Probed mrfl_ocd rpmsg device\n");
ret = mrfl_ocd_module_init();
out:
return ret;
}
static void mrfl_ocd_rpmsg_remove(struct rpmsg_channel *rpdev)
{
mrfl_ocd_module_exit();
dev_info(&rpdev->dev, "Removed mrfl_ocd rpmsg device\n");
}
static void mrfl_ocd_rpmsg_cb(struct rpmsg_channel *rpdev, void *data,
int len, void *priv, u32 src)
{
dev_warn(&rpdev->dev, "unexpected, message\n");
print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
data, len, true);
}
static struct rpmsg_device_id mrfl_ocd_id_table[] = {
{ .name = "rpmsg_mrfl_ocd" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, mrfl_ocd_id_table);
static struct rpmsg_driver mrfl_ocd_rpmsg = {
.drv.name = DRIVER_NAME,
.drv.owner = THIS_MODULE,
.probe = mrfl_ocd_rpmsg_probe,
.callback = mrfl_ocd_rpmsg_cb,
.remove = mrfl_ocd_rpmsg_remove,
.id_table = mrfl_ocd_id_table,
};
static int __init mrfl_ocd_rpmsg_init(void)
{
return register_rpmsg_driver(&mrfl_ocd_rpmsg);
}
static void __init mrfl_ocd_rpmsg_exit(void)
{
unregister_rpmsg_driver(&mrfl_ocd_rpmsg);
}
module_init(mrfl_ocd_rpmsg_init);
module_exit(mrfl_ocd_rpmsg_exit);
MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
MODULE_DESCRIPTION("Intel Merrifield Over Current Detection Driver");
MODULE_LICENSE("GPL");