summaryrefslogtreecommitdiff
path: root/drivers/mfd/88pm860x-core.c
diff options
context:
space:
mode:
authorHaojian Zhuang <haojian.zhuang@marvell.com>2009-12-15 16:01:47 -0500
committerSamuel Ortiz <sameo@linux.intel.com>2010-03-07 22:17:03 +0100
commit5c42e8c4a9c86ea26ed4ecb732a842dea0dfb6b6 (patch)
treeffac1c091a0bedde01c802123e7a602945fd6f62 /drivers/mfd/88pm860x-core.c
parent2cc50bee9934deb6dfe32929a4c1742cf83d6db3 (diff)
downloadlwn-5c42e8c4a9c86ea26ed4ecb732a842dea0dfb6b6.tar.gz
lwn-5c42e8c4a9c86ea26ed4ecb732a842dea0dfb6b6.zip
mfd: Add irq support in 88pm860x
88PM860x is a complex PMIC device. It contains touch, charger, sound, rtc, backlight, led, and so on. Host communicates to 88PM860x by I2C bus. Use thread irq to support this usage case. Signed-off-by: Haojian Zhuang <haojian.zhuang@marvell.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd/88pm860x-core.c')
-rw-r--r--drivers/mfd/88pm860x-core.c226
1 files changed, 219 insertions, 7 deletions
diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c
index 72b00304dc3a..9185f0d945f4 100644
--- a/drivers/mfd/88pm860x-core.c
+++ b/drivers/mfd/88pm860x-core.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
@@ -67,15 +68,209 @@ static struct mfd_cell pm8607_devs[] = {
PM8607_REG_DEVS(ldo14, LDO14),
};
-static void device_8606_init(struct pm860x_chip *chip, struct i2c_client *i2c,
- struct pm860x_platform_data *pdata)
+#define CHECK_IRQ(irq) \
+do { \
+ if ((irq < 0) || (irq >= PM860X_NUM_IRQ)) \
+ return -EINVAL; \
+} while (0)
+
+/* IRQs only occur on 88PM8607 */
+int pm860x_mask_irq(struct pm860x_chip *chip, int irq)
+{
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ int offset, data, ret;
+
+ CHECK_IRQ(irq);
+
+ offset = (irq >> 3) + PM8607_INT_MASK_1;
+ data = 1 << (irq % 8);
+ ret = pm860x_set_bits(i2c, offset, data, 0);
+
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_mask_irq);
+
+int pm860x_unmask_irq(struct pm860x_chip *chip, int irq)
+{
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ int offset, data, ret;
+
+ CHECK_IRQ(irq);
+
+ offset = (irq >> 3) + PM8607_INT_MASK_1;
+ data = 1 << (irq % 8);
+ ret = pm860x_set_bits(i2c, offset, data, data);
+
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_unmask_irq);
+
+#define INT_STATUS_NUM (3)
+
+static irqreturn_t pm8607_irq_thread(int irq, void *data)
+{
+ DECLARE_BITMAP(irq_status, PM860X_NUM_IRQ);
+ struct pm860x_chip *chip = data;
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ unsigned char status_buf[INT_STATUS_NUM << 1];
+ unsigned long value;
+ int i, ret;
+
+ irq_status[0] = 0;
+
+ /* read out status register */
+ ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
+ INT_STATUS_NUM << 1, status_buf);
+ if (ret < 0)
+ goto out;
+ if (chip->irq_mode) {
+ /* 0, clear by read. 1, clear by write */
+ ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1,
+ INT_STATUS_NUM, status_buf);
+ if (ret < 0)
+ goto out;
+ }
+
+ /* clear masked interrupt status */
+ for (i = 0, value = 0; i < INT_STATUS_NUM; i++) {
+ status_buf[i] &= status_buf[i + INT_STATUS_NUM];
+ irq_status[0] |= status_buf[i] << (i * 8);
+ }
+
+ while (!bitmap_empty(irq_status, PM860X_NUM_IRQ)) {
+ irq = find_first_bit(irq_status, PM860X_NUM_IRQ);
+ clear_bit(irq, irq_status);
+ dev_dbg(chip->dev, "Servicing IRQ #%d\n", irq);
+
+ mutex_lock(&chip->irq_lock);
+ if (chip->irq[irq].handler)
+ chip->irq[irq].handler(irq, chip->irq[irq].data);
+ else {
+ pm860x_mask_irq(chip, irq);
+ dev_err(chip->dev, "Nobody cares IRQ %d. "
+ "Now mask it.\n", irq);
+ for (i = 0; i < (INT_STATUS_NUM << 1); i++) {
+ dev_err(chip->dev, "status[%d]:%x\n", i,
+ status_buf[i]);
+ }
+ }
+ mutex_unlock(&chip->irq_lock);
+ }
+out:
+ return IRQ_HANDLED;
+}
+
+int pm860x_request_irq(struct pm860x_chip *chip, int irq,
+ irq_handler_t handler, void *data)
{
+ CHECK_IRQ(irq);
+ if (!handler)
+ return -EINVAL;
+
+ mutex_lock(&chip->irq_lock);
+ chip->irq[irq].handler = handler;
+ chip->irq[irq].data = data;
+ mutex_unlock(&chip->irq_lock);
+
+ return 0;
}
+EXPORT_SYMBOL(pm860x_request_irq);
-static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c,
- struct pm860x_platform_data *pdata)
+int pm860x_free_irq(struct pm860x_chip *chip, int irq)
{
- int i, count;
+ CHECK_IRQ(irq);
+
+ mutex_lock(&chip->irq_lock);
+ chip->irq[irq].handler = NULL;
+ chip->irq[irq].data = NULL;
+ mutex_unlock(&chip->irq_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(pm860x_free_irq);
+
+static int __devinit device_irq_init(struct pm860x_chip *chip,
+ struct pm860x_platform_data *pdata)
+{
+ struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
+ : chip->companion;
+ unsigned char status_buf[INT_STATUS_NUM];
+ int data, mask, ret = -EINVAL;
+
+ mutex_init(&chip->irq_lock);
+
+ mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR
+ | PM8607_B0_MISC1_INT_MASK;
+ data = 0;
+ chip->irq_mode = 0;
+ if (pdata && pdata->irq_mode) {
+ /*
+ * irq_mode defines the way of clearing interrupt. If it's 1,
+ * clear IRQ by write. Otherwise, clear it by read.
+ * This control bit is valid from 88PM8607 B0 steping.
+ */
+ data |= PM8607_B0_MISC1_INT_CLEAR;
+ chip->irq_mode = 1;
+ }
+ ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data);
+ if (ret < 0)
+ goto out;
+
+ /* mask all IRQs */
+ memset(status_buf, 0, INT_STATUS_NUM);
+ ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1,
+ INT_STATUS_NUM, status_buf);
+ if (ret < 0)
+ goto out;
+
+ if (chip->irq_mode) {
+ /* clear interrupt status by write */
+ memset(status_buf, 0xFF, INT_STATUS_NUM);
+ ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1,
+ INT_STATUS_NUM, status_buf);
+ } else {
+ /* clear interrupt status by read */
+ ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
+ INT_STATUS_NUM, status_buf);
+ }
+ if (ret < 0)
+ goto out;
+
+ memset(chip->irq, 0, sizeof(struct pm860x_irq) * PM860X_NUM_IRQ);
+
+ ret = request_threaded_irq(i2c->irq, NULL, pm8607_irq_thread,
+ IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ "88PM8607", chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq);
+ goto out;
+ }
+ chip->chip_irq = i2c->irq;
+ return 0;
+out:
+ return ret;
+}
+
+static void __devexit device_irq_exit(struct pm860x_chip *chip)
+{
+ if (chip->chip_irq >= 0)
+ free_irq(chip->chip_irq, chip);
+}
+
+static void __devinit device_8606_init(struct pm860x_chip *chip,
+ struct i2c_client *i2c,
+ struct pm860x_platform_data *pdata)
+{
+}
+
+static void __devinit device_8607_init(struct pm860x_chip *chip,
+ struct i2c_client *i2c,
+ struct pm860x_platform_data *pdata)
+{
+ int i, count, data;
int ret;
ret = pm860x_reg_read(i2c, PM8607_CHIP_ID);
@@ -91,7 +286,6 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c,
"Chip ID: %02x\n", ret);
goto out;
}
- chip->chip_version = ret;
ret = pm860x_reg_read(i2c, PM8607_BUCK3);
if (ret < 0) {
@@ -101,12 +295,26 @@ static void device_8607_init(struct pm860x_chip *chip, struct i2c_client *i2c,
if (ret & PM8607_BUCK3_DOUBLE)
chip->buck3_double = 1;
- ret = pm860x_reg_read(i2c, PM8607_MISC1);
+ ret = pm860x_reg_read(i2c, PM8607_B0_MISC1);
if (ret < 0) {
dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret);
goto out;
}
+ if (pdata && (pdata->i2c_port == PI2C_PORT))
+ data = PM8607_B0_MISC1_PI2C;
+ else
+ data = 0;
+ ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to access MISC1:%d\n", ret);
+ goto out;
+ }
+
+ ret = device_irq_init(chip, pdata);
+ if (ret < 0)
+ goto out;
+
count = ARRAY_SIZE(pm8607_devs);
for (i = 0; i < count; i++) {
ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i],
@@ -123,6 +331,8 @@ out:
int pm860x_device_init(struct pm860x_chip *chip,
struct pm860x_platform_data *pdata)
{
+ chip->chip_irq = -EINVAL;
+
switch (chip->id) {
case CHIP_PM8606:
device_8606_init(chip, chip->client, pdata);
@@ -142,11 +352,13 @@ int pm860x_device_init(struct pm860x_chip *chip,
break;
}
}
+
return 0;
}
void pm860x_device_exit(struct pm860x_chip *chip)
{
+ device_irq_exit(chip);
mfd_remove_devices(chip->dev);
}