diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 08:06:56 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 08:06:56 -0700 |
commit | 4046136afbd1038d776bad9c59e1e4cca78186fb (patch) | |
tree | 1888ca7bd978c0bba891ac9ee51224fd06d1162e /drivers/mfd | |
parent | b55a0ff8df92646696c858a8fea4dbf38509f202 (diff) | |
parent | a100d88df1e924e5c9678fabf054d1bae7ab74fb (diff) |
Merge tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc into next
Pull char/misc driver patches from Greg KH:
"Here is the big char / misc driver update for 3.16-rc1.
Lots of different driver updates for a variety of different drivers
and minor driver subsystems.
All have been in linux-next with no reported issues"
* tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (79 commits)
hv: use correct order when freeing monitor_pages
spmi: of: fixup generic SPMI devicetree binding example
applicom: dereferencing NULL on error path
misc: genwqe: fix uninitialized return value in genwqe_free_sync_sgl()
miscdevice.h: Simple syntax fix to make pointers consistent.
MAINTAINERS: Add miscdevice.h to file list for char/misc drivers.
mcb: Add support for shared PCI IRQs
drivers: Remove duplicate conditionally included subdirs
misc: atmel_pwm: only build for supported platforms
mei: me: move probe quirk to cfg structure
mei: add per device configuration
mei: me: read H_CSR after asserting reset
mei: me: drop harmful wait optimization
mei: me: fix hw ready reset flow
mei: fix memory leak of mei_clients array
uio: fix vma io range check in mmap
drivers: uio_dmem_genirq: Fix memory leak in uio_dmem_genirq_probe()
w1: do not unlock unheld list_mutex in __w1_remove_master_device()
w1: optional bundling of netlink kernel replies
connector: allow multiple messages to be sent in one packet
...
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 6 | ||||
-rw-r--r-- | drivers/mfd/max14577.c | 315 |
2 files changed, 275 insertions, 46 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f04ac62dd76b..6deb8a11c12f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -331,15 +331,15 @@ config MFD_88PM860X battery-charger under the corresponding menus. config MFD_MAX14577 - bool "Maxim Semiconductor MAX14577 MUIC + Charger Support" + bool "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support" depends on I2C=y select MFD_CORE select REGMAP_I2C select REGMAP_IRQ select IRQ_DOMAIN help - Say yes here to add support for Maxim Semiconductor MAX14577. - This is a Micro-USB IC with Charger controls on chip. + Say yes here to add support for Maxim Semiconductor MAX14577 and + MAX77836 Micro-USB ICs with battery charger. This driver provides common support for accessing the device; additional drivers must be enabled in order to use the functionality of the device. diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 5f13cefe8def..484d372a4892 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -1,7 +1,7 @@ /* - * max14577.c - mfd core driver for the Maxim 14577 + * max14577.c - mfd core driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi <cw00.choi@samsung.com> * Krzysztof Kozlowski <k.kozlowski@samsung.com> * @@ -21,6 +21,7 @@ #include <linux/err.h> #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/of_device.h> #include <linux/mfd/core.h> #include <linux/mfd/max14577.h> #include <linux/mfd/max14577-private.h> @@ -37,7 +38,38 @@ static struct mfd_cell max14577_devs[] = { { .name = "max14577-charger", }, }; -static bool max14577_volatile_reg(struct device *dev, unsigned int reg) +static struct mfd_cell max77836_devs[] = { + { + .name = "max77836-muic", + .of_compatible = "maxim,max77836-muic", + }, + { + .name = "max77836-regulator", + .of_compatible = "maxim,max77836-regulator", + }, + { + .name = "max77836-charger", + .of_compatible = "maxim,max77836-charger", + }, + { + .name = "max77836-battery", + .of_compatible = "maxim,max77836-battery", + }, +}; + +static struct of_device_id max14577_dt_match[] = { + { + .compatible = "maxim,max14577", + .data = (void *)MAXIM_DEVICE_TYPE_MAX14577, + }, + { + .compatible = "maxim,max77836", + .data = (void *)MAXIM_DEVICE_TYPE_MAX77836, + }, + {}, +}; + +static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: @@ -48,49 +80,221 @@ static bool max14577_volatile_reg(struct device *dev, unsigned int reg) return false; } -static const struct regmap_config max14577_regmap_config = { +static bool max77836_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Any max14577 volatile registers are also max77836 volatile. */ + if (max14577_muic_volatile_reg(dev, reg)) + return true; + + switch (reg) { + case MAX77836_FG_REG_VCELL_MSB ... MAX77836_FG_REG_SOC_LSB: + case MAX77836_FG_REG_CRATE_MSB ... MAX77836_FG_REG_CRATE_LSB: + case MAX77836_FG_REG_STATUS_H ... MAX77836_FG_REG_STATUS_L: + case MAX77836_PMIC_REG_INTSRC: + case MAX77836_PMIC_REG_TOPSYS_INT: + case MAX77836_PMIC_REG_TOPSYS_STAT: + return true; + default: + break; + } + return false; +} + +static const struct regmap_config max14577_muic_regmap_config = { .reg_bits = 8, .val_bits = 8, - .volatile_reg = max14577_volatile_reg, + .volatile_reg = max14577_muic_volatile_reg, .max_register = MAX14577_REG_END, }; +static const struct regmap_config max77836_pmic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max77836_muic_volatile_reg, + .max_register = MAX77836_PMIC_REG_END, +}; + static const struct regmap_irq max14577_irqs[] = { /* INT1 interrupts */ - { .reg_offset = 0, .mask = INT1_ADC_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCLOW_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, /* INT2 interrupts */ - { .reg_offset = 1, .mask = INT2_CHGTYP_MASK, }, - { .reg_offset = 1, .mask = INT2_CHGDETRUN_MASK, }, - { .reg_offset = 1, .mask = INT2_DCDTMR_MASK, }, - { .reg_offset = 1, .mask = INT2_DBCHG_MASK, }, - { .reg_offset = 1, .mask = INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, /* INT3 interrupts */ - { .reg_offset = 2, .mask = INT3_EOC_MASK, }, - { .reg_offset = 2, .mask = INT3_CGMBC_MASK, }, - { .reg_offset = 2, .mask = INT3_OVP_MASK, }, - { .reg_offset = 2, .mask = INT3_MBCCHGERR_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, }; static const struct regmap_irq_chip max14577_irq_chip = { .name = "max14577", .status_base = MAX14577_REG_INT1, .mask_base = MAX14577_REG_INTMASK1, - .mask_invert = 1, + .mask_invert = true, .num_regs = 3, .irqs = max14577_irqs, .num_irqs = ARRAY_SIZE(max14577_irqs), }; +static const struct regmap_irq max77836_muic_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX77836_INT1_ADC1K_MASK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX77836_INT2_VIDRM_MASK, }, + /* INT3 interrupts */ + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, +}; + +static const struct regmap_irq_chip max77836_muic_irq_chip = { + .name = "max77836-muic", + .status_base = MAX14577_REG_INT1, + .mask_base = MAX14577_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77836_muic_irqs, + .num_irqs = ARRAY_SIZE(max77836_muic_irqs), +}; + +static const struct regmap_irq max77836_pmic_irqs[] = { + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T120C_MASK, }, + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T140C_MASK, }, +}; + +static const struct regmap_irq_chip max77836_pmic_irq_chip = { + .name = "max77836-pmic", + .status_base = MAX77836_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77836_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77836_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77836_pmic_irqs), +}; + +static void max14577_print_dev_type(struct max14577 *max14577) +{ + u8 reg_data, vendor_id, device_id; + int ret; + + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, + ®_data); + if (ret) { + dev_err(max14577->dev, + "Failed to read DEVICEID register: %d\n", ret); + return; + } + + vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> + DEVID_VENDORID_SHIFT); + device_id = ((reg_data & DEVID_DEVICEID_MASK) >> + DEVID_DEVICEID_SHIFT); + + dev_info(max14577->dev, "Device type: %u (ID: 0x%x, vendor: 0x%x)\n", + max14577->dev_type, device_id, vendor_id); +} + +/* + * Max77836 specific initialization code for driver probe. + * Adds new I2C dummy device, regmap and regmap IRQ chip. + * Unmasks Interrupt Source register. + * + * On success returns 0. + * On failure returns errno and reverts any changes done so far (e.g. remove + * I2C dummy device), except masking the INT SRC register. + */ +static int max77836_init(struct max14577 *max14577) +{ + int ret; + u8 intsrc_mask; + + max14577->i2c_pmic = i2c_new_dummy(max14577->i2c->adapter, + I2C_ADDR_PMIC); + if (!max14577->i2c_pmic) { + dev_err(max14577->dev, "Failed to register PMIC I2C device\n"); + return -ENODEV; + } + i2c_set_clientdata(max14577->i2c_pmic, max14577); + + max14577->regmap_pmic = devm_regmap_init_i2c(max14577->i2c_pmic, + &max77836_pmic_regmap_config); + if (IS_ERR(max14577->regmap_pmic)) { + ret = PTR_ERR(max14577->regmap_pmic); + dev_err(max14577->dev, "Failed to allocate PMIC register map: %d\n", + ret); + goto err; + } + + /* Un-mask MAX77836 Interrupt Source register */ + ret = max14577_read_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, &intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to read PMIC register\n"); + goto err; + } + + intsrc_mask &= ~(MAX77836_INTSRC_MASK_TOP_INT_MASK); + intsrc_mask &= ~(MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK); + ret = max14577_write_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to write PMIC register\n"); + goto err; + } + + ret = regmap_add_irq_chip(max14577->regmap_pmic, max14577->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77836_pmic_irq_chip, + &max14577->irq_data_pmic); + if (ret != 0) { + dev_err(max14577->dev, "Failed to request PMIC IRQ %d: %d\n", + max14577->irq, ret); + goto err; + } + + return 0; + +err: + i2c_unregister_device(max14577->i2c_pmic); + + return ret; +} + +/* + * Max77836 specific de-initialization code for driver remove. + */ +static void max77836_remove(struct max14577 *max14577) +{ + regmap_del_irq_chip(max14577->irq, max14577->irq_data_pmic); + i2c_unregister_device(max14577->i2c_pmic); +} + static int max14577_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct max14577 *max14577; struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); struct device_node *np = i2c->dev.of_node; - u8 reg_data; int ret = 0; + const struct regmap_irq_chip *irq_chip; + struct mfd_cell *mfd_devs; + unsigned int mfd_devs_size; + int irq_flags; if (np) { pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); @@ -113,7 +317,8 @@ static int max14577_i2c_probe(struct i2c_client *i2c, max14577->i2c = i2c; max14577->irq = i2c->irq; - max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config); + max14577->regmap = devm_regmap_init_i2c(i2c, + &max14577_muic_regmap_config); if (IS_ERR(max14577->regmap)) { ret = PTR_ERR(max14577->regmap); dev_err(max14577->dev, "Failed to allocate register map: %d\n", @@ -121,23 +326,36 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, - ®_data); - if (ret) { - dev_err(max14577->dev, "Device not found on this channel: %d\n", - ret); - return ret; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(max14577_dt_match, &i2c->dev); + if (of_id) + max14577->dev_type = (unsigned int)of_id->data; + } else { + max14577->dev_type = id->driver_data; + } + + max14577_print_dev_type(max14577); + + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_chip = &max77836_muic_irq_chip; + mfd_devs = max77836_devs; + mfd_devs_size = ARRAY_SIZE(max77836_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + irq_chip = &max14577_irq_chip; + mfd_devs = max14577_devs; + mfd_devs_size = ARRAY_SIZE(max14577_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + break; } - max14577->vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> - DEVID_VENDORID_SHIFT); - max14577->device_id = ((reg_data & DEVID_DEVICEID_MASK) >> - DEVID_DEVICEID_SHIFT); - dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n", - max14577->device_id, max14577->vendor_id); ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, - &max14577_irq_chip, + irq_flags, 0, irq_chip, &max14577->irq_data); if (ret != 0) { dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", @@ -145,8 +363,15 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = mfd_add_devices(max14577->dev, -1, max14577_devs, - ARRAY_SIZE(max14577_devs), NULL, 0, + /* Max77836 specific initialization code (additional regmap) */ + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) { + ret = max77836_init(max14577); + if (ret < 0) + goto err_max77836; + } + + ret = mfd_add_devices(max14577->dev, -1, mfd_devs, + mfd_devs_size, NULL, 0, regmap_irq_get_domain(max14577->irq_data)); if (ret < 0) goto err_mfd; @@ -156,6 +381,9 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return 0; err_mfd: + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); +err_max77836: regmap_del_irq_chip(max14577->irq, max14577->irq_data); return ret; @@ -167,12 +395,15 @@ static int max14577_i2c_remove(struct i2c_client *i2c) mfd_remove_devices(max14577->dev); regmap_del_irq_chip(max14577->irq, max14577->irq_data); + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); return 0; } static const struct i2c_device_id max14577_i2c_id[] = { - { "max14577", 0 }, + { "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836", MAXIM_DEVICE_TYPE_MAX77836, }, { } }; MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); @@ -215,11 +446,6 @@ static int max14577_resume(struct device *dev) } #endif /* CONFIG_PM_SLEEP */ -static struct of_device_id max14577_dt_match[] = { - { .compatible = "maxim,max14577", }, - {}, -}; - static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); static struct i2c_driver max14577_i2c_driver = { @@ -236,6 +462,9 @@ static struct i2c_driver max14577_i2c_driver = { static int __init max14577_i2c_init(void) { + BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); + BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); @@ -247,5 +476,5 @@ static void __exit max14577_i2c_exit(void) module_exit(max14577_i2c_exit); MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 multi-function core driver"); MODULE_LICENSE("GPL"); |