diff options
author | Haojian Zhuang <haojian.zhuang@marvell.com> | 2010-01-08 14:29:23 +0300 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2010-03-08 00:17:06 +0300 |
commit | d50f8f339f6901fccc9d4292b65ce8b69d7413d4 (patch) | |
tree | da0256c7151f96177209c392c49e1a9b058f2f23 /drivers | |
parent | 34a4b2391e9fbd12de9817de4ae409528bd7d7b6 (diff) | |
download | linux-d50f8f339f6901fccc9d4292b65ce8b69d7413d4.tar.xz |
mfd: Initial max8925 support
Basic Max8925 support, which is a power management IC from Maxim
Semiconductor.
Signed-off-by: Haojian Zhuang <haojian.zhuang@marvell.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/max8925-core.c | 262 | ||||
-rw-r--r-- | drivers/mfd/max8925-i2c.c | 210 |
4 files changed, 483 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 815907eb70a4..ee416eefb8e9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -194,6 +194,15 @@ config PMIC_ADP5520 individual components like LCD backlight, LEDs, GPIOs and Kepad under the corresponding menus. +config MFD_MAX8925 + tristate "Maxim Semiconductor MAX8925 PMIC Support" + depends on I2C + help + Say yes here to support for Maxim Semiconductor MAX8925. This is + a Power Management IC. This driver provies common support for + accessing the device, additional drivers must be enabled in order + to use the functionality of the device. + config MFD_WM8400 tristate "Support Wolfson Microelectronics WM8400" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 1e3ae062c1f6..261635700243 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -49,6 +49,8 @@ endif obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o obj-$(CONFIG_PMIC_DA903X) += da903x.o +max8925-objs := max8925-core.o max8925-i2c.o +obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c new file mode 100644 index 000000000000..3e26267960b1 --- /dev/null +++ b/drivers/mfd/max8925-core.c @@ -0,0 +1,262 @@ +/* + * Base driver for Maxim MAX8925 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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> +#include <linux/mfd/max8925.h> + +#define IRQ_MODE_STATUS 0 +#define IRQ_MODE_MASK 1 + +static int __get_irq_offset(struct max8925_chip *chip, int irq, int mode, + int *offset, int *bit) +{ + if (!offset || !bit) + return -EINVAL; + + switch (chip->chip_id) { + case MAX8925_GPM: + *bit = irq % BITS_PER_BYTE; + if (irq < (BITS_PER_BYTE << 1)) { /* irq = [0,15] */ + *offset = (mode) ? MAX8925_CHG_IRQ1_MASK + : MAX8925_CHG_IRQ1; + if (irq >= BITS_PER_BYTE) + (*offset)++; + } else { /* irq = [16,31] */ + *offset = (mode) ? MAX8925_ON_OFF_IRQ1_MASK + : MAX8925_ON_OFF_IRQ1; + if (irq >= (BITS_PER_BYTE * 3)) + (*offset)++; + } + break; + case MAX8925_ADC: + *bit = irq % BITS_PER_BYTE; + *offset = (mode) ? MAX8925_TSC_IRQ_MASK : MAX8925_TSC_IRQ; + break; + default: + goto out; + } + return 0; +out: + dev_err(chip->dev, "Wrong irq #%d is assigned\n", irq); + return -EINVAL; +} + +static int __check_irq(int irq) +{ + if ((irq < 0) || (irq >= MAX8925_NUM_IRQ)) + return -EINVAL; + return 0; +} + +int max8925_mask_irq(struct max8925_chip *chip, int irq) +{ + int offset, bit, ret; + + ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); + if (ret < 0) + return ret; + ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 1 << bit); + return ret; +} + +int max8925_unmask_irq(struct max8925_chip *chip, int irq) +{ + int offset, bit, ret; + + ret = __get_irq_offset(chip, irq, IRQ_MODE_MASK, &offset, &bit); + if (ret < 0) + return ret; + ret = max8925_set_bits(chip->i2c, offset, 1 << bit, 0); + return ret; +} + +#define INT_STATUS_NUM (MAX8925_NUM_IRQ / BITS_PER_BYTE) + +static irqreturn_t max8925_irq_thread(int irq, void *data) +{ + struct max8925_chip *chip = data; + unsigned long irq_status[INT_STATUS_NUM]; + unsigned char status_buf[INT_STATUS_NUM << 1]; + int i, ret; + + memset(irq_status, 0, sizeof(unsigned long) * INT_STATUS_NUM); + + /* all these interrupt status registers are read-only */ + switch (chip->chip_id) { + case MAX8925_GPM: + ret = max8925_bulk_read(chip->i2c, MAX8925_CHG_IRQ1, + 4, status_buf); + if (ret < 0) + goto out; + ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ1, + 2, &status_buf[4]); + if (ret < 0) + goto out; + ret = max8925_bulk_read(chip->i2c, MAX8925_ON_OFF_IRQ2, + 2, &status_buf[6]); + if (ret < 0) + goto out; + /* clear masked interrupt status */ + status_buf[0] &= (~status_buf[2] & CHG_IRQ1_MASK); + irq_status[0] |= status_buf[0]; + status_buf[1] &= (~status_buf[3] & CHG_IRQ2_MASK); + irq_status[0] |= (status_buf[1] << BITS_PER_BYTE); + status_buf[4] &= (~status_buf[5] & ON_OFF_IRQ1_MASK); + irq_status[0] |= (status_buf[4] << (BITS_PER_BYTE * 2)); + status_buf[6] &= (~status_buf[7] & ON_OFF_IRQ2_MASK); + irq_status[0] |= (status_buf[6] << (BITS_PER_BYTE * 3)); + break; + case MAX8925_ADC: + ret = max8925_bulk_read(chip->i2c, MAX8925_TSC_IRQ, + 2, status_buf); + if (ret < 0) + goto out; + /* clear masked interrupt status */ + status_buf[0] &= (~status_buf[1] & TSC_IRQ_MASK); + irq_status[0] |= status_buf[0]; + break; + default: + goto out; + } + + for_each_bit(i, &irq_status[0], MAX8925_NUM_IRQ) { + clear_bit(i, irq_status); + dev_dbg(chip->dev, "Servicing IRQ #%d in %s\n", i, chip->name); + + mutex_lock(&chip->irq_lock); + if (chip->irq[i].handler) + chip->irq[i].handler(i, chip->irq[i].data); + else { + max8925_mask_irq(chip, i); + dev_err(chip->dev, "Noboday cares IRQ #%d in %s. " + "Now mask it.\n", i, chip->name); + } + mutex_unlock(&chip->irq_lock); + } +out: + return IRQ_HANDLED; +} + +int max8925_request_irq(struct max8925_chip *chip, int irq, + irq_handler_t handler, void *data) +{ + if ((__check_irq(irq) < 0) || !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(max8925_request_irq); + +int max8925_free_irq(struct max8925_chip *chip, int irq) +{ + if (__check_irq(irq) < 0) + return -EINVAL; + + mutex_lock(&chip->irq_lock); + chip->irq[irq].handler = NULL; + chip->irq[irq].data = NULL; + mutex_unlock(&chip->irq_lock); + return 0; +} +EXPORT_SYMBOL(max8925_free_irq); + +static int __devinit device_gpm_init(struct max8925_chip *chip, + struct i2c_client *i2c, + struct max8925_platform_data *pdata) +{ + int ret; + + /* mask all IRQs */ + ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ1_MASK, 0x7, 0x7); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_CHG_IRQ2_MASK, 0xff, 0xff); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff, 0xff); + if (ret < 0) + goto out; + ret = max8925_set_bits(i2c, MAX8925_ON_OFF_IRQ2_MASK, 0x3, 0x3); + if (ret < 0) + goto out; + + chip->name = "GPM"; + memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); + ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "max8925-gpm", chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + goto out; + } + chip->chip_irq = i2c->irq; + + /* enable hard-reset for ONKEY power-off */ + max8925_set_bits(i2c, MAX8925_SYSENSEL, 0x80, 0x80); +out: + return ret; +} + +static int __devinit device_adc_init(struct max8925_chip *chip, + struct i2c_client *i2c, + struct max8925_platform_data *pdata) +{ + int ret; + + /* mask all IRQs */ + ret = max8925_set_bits(i2c, MAX8925_TSC_IRQ_MASK, 3, 3); + + chip->name = "ADC"; + memset(chip->irq, 0, sizeof(struct max8925_irq) * MAX8925_NUM_IRQ); + ret = request_threaded_irq(i2c->irq, NULL, max8925_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "max8925-adc", chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ #%d.\n", i2c->irq); + goto out; + } + chip->chip_irq = i2c->irq; +out: + return ret; +} + +int __devinit max8925_device_init(struct max8925_chip *chip, + struct max8925_platform_data *pdata) +{ + switch (chip->chip_id) { + case MAX8925_GPM: + device_gpm_init(chip, chip->i2c, pdata); + break; + case MAX8925_ADC: + device_adc_init(chip, chip->i2c, pdata); + break; + } + return 0; +} + +void max8925_device_exit(struct max8925_chip *chip) +{ + if (chip->chip_irq >= 0) + free_irq(chip->chip_irq, chip); +} + +MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max8925-i2c.c b/drivers/mfd/max8925-i2c.c new file mode 100644 index 000000000000..942068e730f9 --- /dev/null +++ b/drivers/mfd/max8925-i2c.c @@ -0,0 +1,210 @@ +/* + * I2C driver for Maxim MAX8925 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/max8925.h> + +static inline int max8925_read_device(struct i2c_client *i2c, + int reg, int bytes, void *dest) +{ + unsigned char data; + unsigned char *buf; + int ret; + + buf = kzalloc(bytes + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + data = (unsigned char)reg; + ret = i2c_master_send(i2c, &data, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + memcpy(dest, buf, bytes); + return 0; +} + +static inline int max8925_write_device(struct i2c_client *i2c, + int reg, int bytes, void *src) +{ + unsigned char buf[bytes + 1]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int max8925_reg_read(struct i2c_client *i2c, int reg) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(max8925_reg_read); + +int max8925_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_reg_write); + +int max8925_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_read); + +int max8925_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_write); + +int max8925_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = max8925_write_device(i2c, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(max8925_set_bits); + + +static const struct i2c_device_id max8925_id_table[] = { + { "max8925", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max8925_id_table); + +static int __devinit max8925_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max8925_platform_data *pdata = client->dev.platform_data; + struct max8925_chip *chip; + + if (!pdata) { + pr_info("%s: platform data is missing\n", __func__); + return -EINVAL; + } + if ((pdata->chip_id <= MAX8925_INVALID) + || (pdata->chip_id >= MAX8925_MAX)) { + pr_info("#%s: wrong chip identification\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8925_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->i2c = client; + chip->chip_id = pdata->chip_id; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + max8925_device_init(chip, pdata); + + return 0; +} + +static int __devexit max8925_remove(struct i2c_client *client) +{ + struct max8925_chip *chip = i2c_get_clientdata(client); + + max8925_device_exit(chip); + i2c_set_clientdata(client, NULL); + kfree(chip); + return 0; +} + +static struct i2c_driver max8925_driver = { + .driver = { + .name = "max8925", + .owner = THIS_MODULE, + }, + .probe = max8925_probe, + .remove = __devexit_p(max8925_remove), + .id_table = max8925_id_table, +}; + +static int __init max8925_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&max8925_driver); + if (ret != 0) + pr_err("Failed to register MAX8925 I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(max8925_i2c_init); + +static void __exit max8925_i2c_exit(void) +{ + i2c_del_driver(&max8925_driver); +} +module_exit(max8925_i2c_exit); + +MODULE_DESCRIPTION("I2C Driver for Maxim 8925"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); |