diff options
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/tps65910-irq.c | 218 | ||||
-rw-r--r-- | drivers/mfd/tps65910.c | 229 | ||||
-rw-r--r-- | drivers/mfd/tps65911-comparator.c | 188 |
5 files changed, 645 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8344fc0..b6c2677 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -719,6 +719,15 @@ config MFD_PM8XXX_IRQ This is required to use certain other PM 8xxx features, such as GPIO and MPP. +config MFD_TPS65910 + bool "TPS65910 Power Management chip" + depends on I2C=y + select MFD_CORE + select GPIO_TPS65910 + help + if you say yes here you get support for the TPS65910 series of + Power Management chips. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 1acb8f2..efe3cc3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -93,3 +93,4 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o +obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-irq.o diff --git a/drivers/mfd/tps65910-irq.c b/drivers/mfd/tps65910-irq.c new file mode 100644 index 0000000..2bfad5c --- /dev/null +++ b/drivers/mfd/tps65910-irq.c @@ -0,0 +1,218 @@ +/* + * tps65910-irq.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +static inline int irq_to_tps65910_irq(struct tps65910 *tps65910, + int irq) +{ + return (irq - tps65910->irq_base); +} + +/* + * This is a threaded IRQ handler so can access I2C/SPI. Since all + * interrupts are clear on read the IRQ line will be reasserted and + * the physical IRQ will be handled again if another interrupt is + * asserted while we run - in the normal course of events this is a + * rare occurrence so we save I2C/SPI reads. We're also assuming that + * it's rare to get lots of interrupts firing simultaneously so try to + * minimise I/O. + */ +static irqreturn_t tps65910_irq(int irq, void *irq_data) +{ + struct tps65910 *tps65910 = irq_data; + u32 irq_sts; + u32 irq_mask; + u8 reg; + int i; + + tps65910->read(tps65910, TPS65910_INT_STS, 1, ®); + irq_sts = reg; + tps65910->read(tps65910, TPS65910_INT_STS2, 1, ®); + irq_sts |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_STS3, 1, ®); + irq_sts |= reg << 16; + } + + tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); + irq_mask = reg; + tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); + irq_mask |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_MSK3, 1, ®); + irq_mask |= reg << 16; + } + + irq_sts &= ~irq_mask; + + if (!irq_sts) + return IRQ_NONE; + + for (i = 0; i < tps65910->irq_num; i++) { + + if (!(irq_sts & (1 << i))) + continue; + + handle_nested_irq(tps65910->irq_base + i); + } + + /* Write the STS register back to clear IRQs we handled */ + reg = irq_sts & 0xFF; + irq_sts >>= 8; + tps65910->write(tps65910, TPS65910_INT_STS, 1, ®); + reg = irq_sts & 0xFF; + tps65910->write(tps65910, TPS65910_INT_STS2, 1, ®); + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + reg = irq_sts >> 8; + tps65910->write(tps65910, TPS65910_INT_STS3, 1, ®); + } + + return IRQ_HANDLED; +} + +static void tps65910_irq_lock(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + mutex_lock(&tps65910->irq_lock); +} + +static void tps65910_irq_sync_unlock(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + u32 reg_mask; + u8 reg; + + tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); + reg_mask = reg; + tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); + reg_mask |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_MSK3, 1, ®); + reg_mask |= reg << 16; + } + + if (tps65910->irq_mask != reg_mask) { + reg = tps65910->irq_mask & 0xFF; + tps65910->write(tps65910, TPS65910_INT_MSK, 1, ®); + reg = tps65910->irq_mask >> 8 & 0xFF; + tps65910->write(tps65910, TPS65910_INT_MSK2, 1, ®); + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + reg = tps65910->irq_mask >> 16; + tps65910->write(tps65910, TPS65910_INT_MSK3, 1, ®); + } + } + mutex_unlock(&tps65910->irq_lock); +} + +static void tps65910_irq_enable(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + tps65910->irq_mask &= ~( 1 << irq_to_tps65910_irq(tps65910, data->irq)); +} + +static void tps65910_irq_disable(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + tps65910->irq_mask |= ( 1 << irq_to_tps65910_irq(tps65910, data->irq)); +} + +static struct irq_chip tps65910_irq_chip = { + .name = "tps65910", + .irq_bus_lock = tps65910_irq_lock, + .irq_bus_sync_unlock = tps65910_irq_sync_unlock, + .irq_disable = tps65910_irq_disable, + .irq_enable = tps65910_irq_enable, +}; + +int tps65910_irq_init(struct tps65910 *tps65910, int irq, + struct tps65910_platform_data *pdata) +{ + int ret, cur_irq; + int flags = IRQF_ONESHOT; + + if (!irq) { + dev_warn(tps65910->dev, "No interrupt support, no core IRQ\n"); + return -EINVAL; + } + + if (!pdata || !pdata->irq_base) { + dev_warn(tps65910->dev, "No interrupt support, no IRQ base\n"); + return -EINVAL; + } + + tps65910->irq_mask = 0xFFFFFF; + + mutex_init(&tps65910->irq_lock); + tps65910->chip_irq = irq; + tps65910->irq_base = pdata->irq_base; + + switch (tps65910_chip_id(tps65910)) { + case TPS65910: + tps65910->irq_num = TPS65910_NUM_IRQ; + case TPS65911: + tps65910->irq_num = TPS65911_NUM_IRQ; + } + + /* Register with genirq */ + for (cur_irq = tps65910->irq_base; + cur_irq < tps65910->irq_num + tps65910->irq_base; + cur_irq++) { + irq_set_chip_data(cur_irq, tps65910); + irq_set_chip_and_handler(cur_irq, &tps65910_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(irq, NULL, tps65910_irq, flags, + "tps65910", tps65910); + + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); + + if (ret != 0) + dev_err(tps65910->dev, "Failed to request IRQ: %d\n", ret); + + return ret; +} + +int tps65910_irq_exit(struct tps65910 *tps65910) +{ + free_irq(tps65910->chip_irq, tps65910); + return 0; +} diff --git a/drivers/mfd/tps65910.c b/drivers/mfd/tps65910.c new file mode 100644 index 0000000..2229e66 --- /dev/null +++ b/drivers/mfd/tps65910.c @@ -0,0 +1,229 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65910.h> + +static struct mfd_cell tps65910s[] = { + { + .name = "tps65910-pmic", + }, + { + .name = "tps65910-rtc", + }, + { + .name = "tps65910-power", + }, +}; + + +static int tps65910_i2c_read(struct tps65910 *tps65910, u8 reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = tps65910->i2c_client; + struct i2c_msg xfer[2]; + int ret; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = bytes; + xfer[1].buf = dest; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +static int tps65910_i2c_write(struct tps65910 *tps65910, u8 reg, + int bytes, void *src) +{ + struct i2c_client *i2c = tps65910->i2c_client; + /* we add 1 byte for device register */ + u8 msg[TPS65910_MAX_REGISTER + 1]; + int ret; + + if (bytes > TPS65910_MAX_REGISTER) + return -EINVAL; + + msg[0] = reg; + memcpy(&msg[1], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 1); + if (ret < 0) + return ret; + if (ret != bytes + 1) + return -EIO; + return 0; +} + +int tps65910_set_bits(struct tps65910 *tps65910, u8 reg, u8 mask) +{ + u8 data; + int err; + + mutex_lock(&tps65910->io_mutex); + err = tps65910_i2c_read(tps65910, reg, 1, &data); + if (err) { + dev_err(tps65910->dev, "read from reg %x failed\n", reg); + goto out; + } + + data |= mask; + err = tps65910_i2c_write(tps65910, reg, 1, &data); + if (err) + dev_err(tps65910->dev, "write to reg %x failed\n", reg); + +out: + mutex_unlock(&tps65910->io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(tps65910_set_bits); + +int tps65910_clear_bits(struct tps65910 *tps65910, u8 reg, u8 mask) +{ + u8 data; + int err; + + mutex_lock(&tps65910->io_mutex); + err = tps65910_i2c_read(tps65910, reg, 1, &data); + if (err) { + dev_err(tps65910->dev, "read from reg %x failed\n", reg); + goto out; + } + + data &= mask; + err = tps65910_i2c_write(tps65910, reg, 1, &data); + if (err) + dev_err(tps65910->dev, "write to reg %x failed\n", reg); + +out: + mutex_unlock(&tps65910->io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(tps65910_clear_bits); + +static int tps65910_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tps65910 *tps65910; + struct tps65910_board *pmic_plat_data; + struct tps65910_platform_data *init_data; + int ret = 0; + + pmic_plat_data = dev_get_platdata(&i2c->dev); + if (!pmic_plat_data) + return -EINVAL; + + init_data = kzalloc(sizeof(struct tps65910_platform_data), GFP_KERNEL); + if (init_data == NULL) + return -ENOMEM; + + init_data->irq = pmic_plat_data->irq; + init_data->irq_base = pmic_plat_data->irq; + + tps65910 = kzalloc(sizeof(struct tps65910), GFP_KERNEL); + if (tps65910 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, tps65910); + tps65910->dev = &i2c->dev; + tps65910->i2c_client = i2c; + tps65910->id = id->driver_data; + tps65910->read = tps65910_i2c_read; + tps65910->write = tps65910_i2c_write; + mutex_init(&tps65910->io_mutex); + + ret = mfd_add_devices(tps65910->dev, -1, + tps65910s, ARRAY_SIZE(tps65910s), + NULL, 0); + if (ret < 0) + goto err; + + tps65910_gpio_init(tps65910, pmic_plat_data->gpio_base); + + ret = tps65910_irq_init(tps65910, init_data->irq, init_data); + if (ret < 0) + goto err; + + return ret; + +err: + mfd_remove_devices(tps65910->dev); + kfree(tps65910); + return ret; +} + +static int tps65910_i2c_remove(struct i2c_client *i2c) +{ + struct tps65910 *tps65910 = i2c_get_clientdata(i2c); + + mfd_remove_devices(tps65910->dev); + kfree(tps65910); + + return 0; +} + +static const struct i2c_device_id tps65910_i2c_id[] = { + { "tps65910", TPS65910 }, + { "tps65911", TPS65911 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65910_i2c_id); + + +static struct i2c_driver tps65910_i2c_driver = { + .driver = { + .name = "tps65910", + .owner = THIS_MODULE, + }, + .probe = tps65910_i2c_probe, + .remove = tps65910_i2c_remove, + .id_table = tps65910_i2c_id, +}; + +static int __init tps65910_i2c_init(void) +{ + return i2c_add_driver(&tps65910_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps65910_i2c_init); + +static void __exit tps65910_i2c_exit(void) +{ + i2c_del_driver(&tps65910_i2c_driver); +} +module_exit(tps65910_i2c_exit); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS6591x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/tps65911-comparator.c b/drivers/mfd/tps65911-comparator.c new file mode 100644 index 0000000..3d2dc56 --- /dev/null +++ b/drivers/mfd/tps65911-comparator.c @@ -0,0 +1,188 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +#define COMP 0 +#define COMP1 1 +#define COMP2 2 + +/* Comparator 1 voltage selection table in milivolts */ +static const u16 COMP_VSEL_TABLE[] = { + 0, 2500, 2500, 2500, 2500, 2550, 2600, 2650, + 2700, 2750, 2800, 2850, 2900, 2950, 3000, 3050, + 3100, 3150, 3200, 3250, 3300, 3350, 3400, 3450, + 3500, +}; + +struct comparator { + const char *name; + int reg; + int uV_max; + const u16 *vsel_table; +}; + +static struct comparator tps_comparators[] = { + { + .name = "COMP1", + .reg = TPS65911_VMBCH, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, + { + .name = "COMP2", + .reg = TPS65911_VMBCH2, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, +}; + +static int comp_threshold_set(struct tps65910 *tps65910, int id, int voltage) +{ + struct comparator tps_comp = tps_comparators[id]; + int curr_voltage = 0; + int ret; + u8 index = 0, val; + + if (id == COMP) + return 0; + + while (curr_voltage < tps_comp.uV_max) { + curr_voltage = tps_comp.vsel_table[index]; + if (curr_voltage >= voltage) + break; + else if (curr_voltage < voltage) + index ++; + } + + if (curr_voltage > tps_comp.uV_max) + return -EINVAL; + + val = index << 1; + ret = tps65910->write(tps65910, tps_comp.reg, 1, &val); + + return ret; +} + +static int comp_threshold_get(struct tps65910 *tps65910, int id) +{ + struct comparator tps_comp = tps_comparators[id]; + int ret; + u8 val; + + if (id == COMP) + return 0; + + ret = tps65910->read(tps65910, tps_comp.reg, 1, &val); + if (ret < 0) + return ret; + + val >>= 1; + return tps_comp.vsel_table[val]; +} + +static ssize_t comp_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tps65910 *tps65910 = dev_get_drvdata(dev->parent); + struct attribute comp_attr = attr->attr; + int id, uVolt; + + if (!strcmp(comp_attr.name, "comp1_threshold")) + id = COMP1; + else if (!strcmp(comp_attr.name, "comp2_threshold")) + id = COMP2; + else + return -EINVAL; + + uVolt = comp_threshold_get(tps65910, id); + + return sprintf(buf, "%d\n", uVolt); +} + +static DEVICE_ATTR(comp1_threshold, S_IRUGO, comp_threshold_show, NULL); +static DEVICE_ATTR(comp2_threshold, S_IRUGO, comp_threshold_show, NULL); + +static __devinit int tps65911_comparator_probe(struct platform_device *pdev) +{ + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct tps65910_platform_data *pdata = dev_get_platdata(tps65910->dev); + int ret; + + ret = comp_threshold_set(tps65910, COMP1, pdata->vmbch_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP1 threshold\n"); + return ret; + } + + ret = comp_threshold_set(tps65910, COMP2, pdata->vmbch2_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP2 theshold\n"); + return ret; + } + + /* Create sysfs entry */ + ret = device_create_file(&pdev->dev, &dev_attr_comp1_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP1 sysfs file\n"); + + ret = device_create_file(&pdev->dev, &dev_attr_comp2_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP2 sysfs file\n"); + + return ret; +} + +static __devexit int tps65911_comparator_remove(struct platform_device *pdev) +{ + struct tps65910 *tps65910; + + tps65910 = dev_get_drvdata(pdev->dev.parent); + + return 0; +} + +static struct platform_driver tps65911_comparator_driver = { + .driver = { + .name = "tps65911-comparator", + .owner = THIS_MODULE, + }, + .probe = tps65911_comparator_probe, + .remove = __devexit_p(tps65911_comparator_remove), +}; + +static int __init tps65911_comparator_init(void) +{ + return platform_driver_register(&tps65911_comparator_driver); +} +subsys_initcall(tps65911_comparator_init); + +static void __exit tps65911_comparator_exit(void) +{ + platform_driver_unregister(&tps65911_comparator_driver); +} +module_exit(tps65911_comparator_exit); + +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65911 comparator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65911-comparator"); |