| /* |
| * Maxim MAX77620 MFD Driver |
| * |
| * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope 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. |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/ratelimit.h> |
| #include <linux/kthread.h> |
| #include <linux/mfd/core.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/delay.h> |
| #include <linux/uaccess.h> |
| #include <linux/module.h> |
| #include <linux/reboot.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_platform.h> |
| |
| #include <linux/mfd/max77620.h> |
| |
| |
| static struct resource gpio_resources[] = { |
| { |
| .start = MAX77620_IRQ_TOP_GPIO, |
| .end = MAX77620_IRQ_TOP_GPIO, |
| .flags = IORESOURCE_IRQ, |
| } |
| }; |
| |
| static struct resource rtc_resources[] = { |
| { |
| .start = MAX77620_IRQ_TOP_RTC, |
| .end = MAX77620_IRQ_TOP_RTC, |
| .flags = IORESOURCE_IRQ, |
| } |
| }; |
| |
| static struct resource thermal_resources[] = { |
| { |
| .start = MAX77620_IRQ_LBT_TJALRM1, |
| .end = MAX77620_IRQ_LBT_TJALRM1, |
| .flags = IORESOURCE_IRQ, |
| }, |
| { |
| .start = MAX77620_IRQ_LBT_TJALRM2, |
| .end = MAX77620_IRQ_LBT_TJALRM2, |
| .flags = IORESOURCE_IRQ, |
| } |
| }; |
| |
| static const struct regmap_irq max77620_top_lbt_irqs[] = { |
| [MAX77620_IRQ_TOP_SD] = { |
| .mask = MAX77620_IRQ_TOP_SD_MASK, |
| .reg_offset = 0, |
| }, |
| [MAX77620_IRQ_TOP_LDO] = { |
| .mask = MAX77620_IRQ_TOP_LDO_MASK, |
| .reg_offset = 0, |
| }, |
| [MAX77620_IRQ_TOP_GPIO] = { |
| .mask = MAX77620_IRQ_TOP_GPIO_MASK, |
| .reg_offset = 0, |
| }, |
| [MAX77620_IRQ_TOP_RTC] = { |
| .mask = MAX77620_IRQ_TOP_RTC_MASK, |
| .reg_offset = 0, |
| }, |
| [MAX77620_IRQ_TOP_32K] = { |
| .mask = MAX77620_IRQ_TOP_32K_MASK, |
| .reg_offset = 0, |
| }, |
| [MAX77620_IRQ_TOP_ONOFF] = { |
| .mask = MAX77620_IRQ_TOP_ONOFF_MASK, |
| .reg_offset = 0, |
| }, |
| |
| [MAX77620_IRQ_LBT_MBATLOW] = { |
| .mask = MAX77620_IRQ_LBM_MASK, |
| .reg_offset = 1, |
| }, |
| [MAX77620_IRQ_LBT_TJALRM1] = { |
| .mask = MAX77620_IRQ_TJALRM1_MASK, |
| .reg_offset = 1, |
| }, |
| [MAX77620_IRQ_LBT_TJALRM2] = { |
| .mask = MAX77620_IRQ_TJALRM2_MASK, |
| .reg_offset = 1, |
| }, |
| }; |
| |
| enum max77660_ids { |
| MAX77620_PMIC_ID, |
| MAX77620_GPIO_ID, |
| MAX77620_RTC_ID, |
| MAX77620_PINCTRL_ID, |
| MAX77620_CLK_ID, |
| MAX77620_POWER_OFF_ID, |
| MAX77620_WDT_ID, |
| MAX77620_THERMAL_ID, |
| }; |
| |
| static struct mfd_cell max77620_children[] = { |
| [MAX77620_GPIO_ID] = { |
| .name = "max77620-gpio", |
| .num_resources = ARRAY_SIZE(gpio_resources), |
| .resources = &gpio_resources[0], |
| .id = MAX77620_GPIO_ID, |
| }, |
| [MAX77620_PMIC_ID] = { |
| .name = "max77620-pmic", |
| .id = MAX77620_PMIC_ID, |
| }, |
| [MAX77620_RTC_ID] = { |
| .name = "max77620-rtc", |
| .num_resources = ARRAY_SIZE(rtc_resources), |
| .resources = &rtc_resources[0], |
| .id = MAX77620_RTC_ID, |
| }, |
| [MAX77620_PINCTRL_ID] = { |
| .name = "max77620-pinctrl", |
| .id = MAX77620_PINCTRL_ID, |
| }, |
| [MAX77620_CLK_ID] = { |
| .name = "max77620-clk", |
| .id = MAX77620_CLK_ID, |
| }, |
| [MAX77620_POWER_OFF_ID] = { |
| .name = "max77620-power-off", |
| .id = MAX77620_POWER_OFF_ID, |
| }, |
| [MAX77620_WDT_ID] = { |
| .name = "max77620-wdt", |
| .id = MAX77620_WDT_ID, |
| }, |
| [MAX77620_THERMAL_ID] = { |
| .name = "max77620-thermal", |
| .num_resources = ARRAY_SIZE(thermal_resources), |
| .resources = &thermal_resources[0], |
| .id = MAX77620_THERMAL_ID, |
| }, |
| }; |
| |
| int max77620_top_lbt_irq_chip_pre_irq(void *data) |
| { |
| struct max77620_chip *chip = data; |
| int ret = 0; |
| |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_IRQTOPM, MAX77620_IRQ_TOP_GLBL_MASK, |
| MAX77620_IRQ_TOP_GLBL_MASK); |
| if (ret < 0) |
| dev_err(chip->dev, "IRQ_GLBLM masking failed: %d\n", ret); |
| |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_INTENLBT, MAX77620_GLBLM_MASK, |
| MAX77620_GLBLM_MASK); |
| if (ret < 0) |
| dev_err(chip->dev, "GLBLM masking failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| int max77620_top_lbt_irq_chip_post_irq(void *data) |
| { |
| struct max77620_chip *chip = data; |
| int ret = 0; |
| |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_IRQTOPM, MAX77620_IRQ_TOP_GLBL_MASK, 0); |
| if (ret < 0) |
| dev_err(chip->dev, "IRQ_GLBLM unmasking failed: %d\n", ret); |
| |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_INTENLBT, MAX77620_GLBLM_MASK, 0); |
| if (ret < 0) |
| dev_err(chip->dev, "GLBLM unmasking failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static struct regmap_irq_chip max77620_top_lbt_irq_chip = { |
| .name = "max77620-top", |
| .irqs = max77620_top_lbt_irqs, |
| .num_irqs = ARRAY_SIZE(max77620_top_lbt_irqs), |
| .num_regs = 2, |
| .status_base = MAX77620_REG_IRQTOP, |
| .mask_base = MAX77620_REG_IRQTOPM, |
| .pre_irq = max77620_top_lbt_irq_chip_pre_irq, |
| .post_irq = max77620_top_lbt_irq_chip_post_irq, |
| }; |
| |
| static void max77620_regmap_config_lock(void *lock) |
| { |
| struct max77620_chip *chip = lock; |
| |
| if (chip->shutdown && (in_atomic() || irqs_disabled())) { |
| dev_info(chip->dev, "Xfer without lock\n"); |
| return; |
| } |
| |
| mutex_lock(&chip->mutex_config); |
| chip->mutex_config_locked = true; |
| } |
| |
| static void max77620_regmap_config_unlock(void *lock) |
| { |
| struct max77620_chip *chip = lock; |
| |
| if (!chip->mutex_config_locked) |
| return; |
| |
| chip->mutex_config_locked = false; |
| mutex_unlock(&chip->mutex_config); |
| } |
| |
| static const struct regmap_range max77620_readable_ranges[] = { |
| regmap_reg_range(MAX77620_REG_CNFGGLBL1, MAX77620_REG_DVSSD4), |
| }; |
| |
| static const struct regmap_access_table max77620_readable_table = { |
| .yes_ranges = max77620_readable_ranges, |
| .n_yes_ranges = ARRAY_SIZE(max77620_readable_ranges), |
| }; |
| static const struct regmap_range max77620_writable_ranges[] = { |
| regmap_reg_range(MAX77620_REG_CNFGGLBL1, MAX77620_REG_DVSSD4), |
| }; |
| |
| static const struct regmap_access_table max77620_writable_table = { |
| .yes_ranges = max77620_writable_ranges, |
| .n_yes_ranges = ARRAY_SIZE(max77620_writable_ranges), |
| }; |
| |
| static const struct regmap_range max77620_cacheable_ranges[] = { |
| regmap_reg_range(MAX77620_REG_SD0_CFG, MAX77620_REG_LDO_CFG3), |
| regmap_reg_range(MAX77620_REG_FPS_CFG0, MAX77620_REG_FPS_SD3), |
| }; |
| |
| static const struct regmap_access_table max77620_volatile_table = { |
| .no_ranges = max77620_cacheable_ranges, |
| .n_no_ranges = ARRAY_SIZE(max77620_cacheable_ranges), |
| }; |
| |
| static struct regmap_config max77620_regmap_config[] = { |
| [MAX77620_PWR_SLAVE] = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = MAX77620_REG_DVSSD4 + 1, |
| .num_reg_defaults_raw = MAX77620_REG_DVSSD4 + 1, |
| .lock = max77620_regmap_config_lock, |
| .unlock = max77620_regmap_config_unlock, |
| .cache_type = REGCACHE_FLAT, |
| .rd_table = &max77620_readable_table, |
| .wr_table = &max77620_writable_table, |
| .volatile_table = &max77620_volatile_table, |
| }, |
| [MAX77620_RTC_SLAVE] = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = 0x1b, |
| .lock = max77620_regmap_config_lock, |
| .unlock = max77620_regmap_config_unlock, |
| }, |
| }; |
| |
| static int max77620_slave_address[MAX77620_NUM_SLAVES] = { |
| MAX77620_PWR_I2C_ADDR, |
| MAX77620_RTC_I2C_ADDR, |
| }; |
| |
| static int max77620_initialise_fps(struct max77620_chip *chip, |
| struct device *dev) |
| { |
| struct device_node *node; |
| struct device_node *child; |
| u32 reg, pval; |
| int ret; |
| int time_period = 40; |
| enum max77620_regulator_fps_enable_source input_enable = FPS_EN_SRC_SW; |
| bool enable_fps = false; |
| unsigned int mask; |
| unsigned int config; |
| int i; |
| |
| node = of_get_child_by_name(dev->of_node, "fps"); |
| if (!node) |
| return 0; |
| |
| for_each_child_of_node(node, child) { |
| ret = of_property_read_u32(child, "reg", ®); |
| if (ret) { |
| dev_err(dev, "node %s does not have reg property\n", |
| child->name); |
| continue; |
| } |
| if (reg > 2) { |
| dev_err(dev, "FPS%d is not supported\n", reg); |
| continue; |
| } |
| |
| mask = 0; |
| ret = of_property_read_u32(child, "maxim,fps-time-period", |
| &pval); |
| if (!ret) { |
| time_period = min(pval, 5120U); |
| mask |= MAX77620_FPS_TIME_PERIOD_MASK; |
| } |
| |
| ret = of_property_read_u32(child, "maxim,fps-enable-input", |
| &pval); |
| if (!ret) { |
| if (pval > FPS_EN_SRC_SW) { |
| dev_err(dev, |
| "FPS enable-input %u is not supported\n", |
| pval); |
| } else { |
| input_enable = pval; |
| mask |= MAX77620_FPS_EN_SRC_MASK; |
| } |
| } |
| |
| if (input_enable == FPS_EN_SRC_SW) { |
| enable_fps = of_property_read_bool(child, |
| "maxim,fps-sw-enable"); |
| mask |= MAX77620_FPS_ENFPS_MASK; |
| } |
| |
| if (!chip->sleep_enable) |
| chip->sleep_enable = of_property_read_bool(child, |
| "maxim,enable-sleep"); |
| if (!chip->enable_global_lpm) |
| chip->enable_global_lpm = of_property_read_bool(child, |
| "maxim,enable-global-lpm"); |
| |
| for (i = 0; i < FPS_TIMER_PERIOD_NR - 1; ++i) { |
| int x = MAX77620_FPS_TIMER_PERIOD_COEFF * BIT(i); |
| if (x >= time_period) |
| break; |
| } |
| config = (i << MAX77620_FPS_TIME_PERIOD_SHIFT) & |
| MAX77620_FPS_TIME_PERIOD_MASK; |
| config |= (input_enable << MAX77620_FPS_EN_SRC_SHIFT) & |
| MAX77620_FPS_EN_SRC_MASK; |
| if (enable_fps) |
| config |= 1; |
| ret = max77620_reg_update(dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_FPS_CFG0 + reg, mask, config); |
| if (ret < 0) { |
| dev_err(dev, "Reg 0x%02x write failed, %d\n", |
| MAX77620_REG_FPS_CFG0 + reg, ret); |
| return ret; |
| } |
| } |
| |
| config = chip->enable_global_lpm ? MAX77620_ONOFFCNFG2_SLP_LPM_MSK : 0; |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_ONOFFCNFG2, |
| MAX77620_ONOFFCNFG2_SLP_LPM_MSK, config); |
| if (ret < 0) { |
| dev_err(dev, "Reg ONOFFCNFG2 update failed: %d\n", ret); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int max77620_init_backup_battery_charging(struct max77620_chip *chip, |
| struct device *dev) |
| { |
| struct device_node *np; |
| u8 val; |
| u8 config; |
| u32 charging_current = 50; |
| u32 charging_voltage = 2500000; |
| u32 resistor = 1000; |
| int ret; |
| |
| np = of_get_child_by_name(dev->of_node, "backup-battery"); |
| if (!np) { |
| dev_info(dev, "Backup battery charging support disabled\n"); |
| max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_CNFGBBC, MAX77620_CNFGBBC_ENABLE, 0); |
| return 0; |
| } |
| |
| ret = of_property_read_u32(np, |
| "maxim,backup-battery-charging-current", |
| &charging_current); |
| |
| ret = of_property_read_u32(np, |
| "maxim,backup-battery-charging-voltage", |
| &charging_voltage); |
| charging_voltage /= 1000; |
| |
| ret = of_property_read_u32(np, |
| "maxim,backup-battery-output-resistor", |
| &resistor); |
| |
| config = MAX77620_CNFGBBC_ENABLE; |
| |
| switch (charging_current) { |
| case 50: val = 0; break; |
| case 100: val = 3; break; |
| case 200: val = 0; break; |
| case 400: val = 3; break; |
| case 600: val = 1; break; |
| case 800: val = 2; break; |
| default: |
| dev_err(dev, "Backup battery current setting invalid\n"); |
| return -EINVAL; |
| }; |
| |
| config |= val << MAX77620_CNFGBBC_CURRENT_SHIFT; |
| |
| if (charging_current > 100) |
| config |= MAX77620_CNFGBBC_LOW_CURRENT_ENABLE; |
| |
| switch (charging_voltage) { |
| case 2500: val = 0; break; |
| case 3000: val = 1; break; |
| case 3300: val = 2; break; |
| case 3500: val = 3; break; |
| default: |
| dev_err(dev, "Backup battery voltage setting invalid\n"); |
| return -EINVAL; |
| } |
| |
| config |= val << MAX77620_CNFGBBC_VOLTAGE_SHIFT; |
| |
| switch (resistor) { |
| case 100: val = 0; break; |
| case 1000: val = 1; break; |
| case 3000: val = 2; break; |
| case 6000: val = 3; break; |
| default: |
| dev_err(dev, "Backup battery resistor setting invalid\n"); |
| return -EINVAL; |
| } |
| |
| config |= val << MAX77620_CNFGBBC_RESISTOR_SHIFT; |
| |
| ret = max77620_reg_write(dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_CNFGBBC, config); |
| if (ret < 0) { |
| dev_err(dev, "Reg 0x%02x write failed, %d\n", |
| MAX77620_REG_CNFGBBC, ret); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int max77620_read_es_version(struct max77620_chip *chip) |
| { |
| int ret; |
| u8 val; |
| u8 cid; |
| int i; |
| |
| for (i = MAX77620_REG_CID0; i <= MAX77620_REG_CID5; ++i) { |
| ret = max77620_reg_read(chip->dev, MAX77620_PWR_SLAVE, |
| i, &cid); |
| if (ret < 0) { |
| dev_err(chip->dev, "CID%d register read failed: %d\n", |
| i - MAX77620_REG_CID0, ret); |
| return ret; |
| } |
| dev_info(chip->dev, "CID%d: 0x%02x\n", |
| i - MAX77620_REG_CID0, cid); |
| } |
| |
| /* Read OTP Version */ |
| ret = max77620_reg_read(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_CID4, &val); |
| if (ret < 0) { |
| dev_err(chip->dev, "CID4 read failed: %d\n", ret); |
| return ret; |
| } |
| dev_info(chip->dev, "MAX77620 PMIC OTP Version is 0x%02X\n", val); |
| |
| /* Read ES version */ |
| ret = max77620_reg_read(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_CID5, &val); |
| if (ret < 0) { |
| dev_err(chip->dev, "CID5 read failed: %d\n", ret); |
| return ret; |
| } |
| chip->es_minor_version = MAX77620_CID5_DIDM(val); |
| chip->es_major_version = 1; |
| return ret; |
| } |
| |
| static int max77620_init_mbattlow(struct device *dev) |
| { |
| unsigned int mask = 0, lbval = 0; |
| u32 val; |
| int ret; |
| |
| if (!dev->of_node) { |
| dev_err(dev, "DT device is not found.\n"); |
| return -ENODEV; |
| } |
| |
| ret = of_property_read_u32(dev->of_node, |
| "maxim,lb-threshold-vol", &val); |
| if (ret) { |
| dev_warn(dev, "No Low-Battery threshold setting.\n"); |
| goto lbdac_en; |
| } |
| |
| switch (val) { |
| case 2700: lbval = 0x0; break; |
| case 2800: lbval = 0x2; break; |
| case 2900: lbval = 0x4; break; |
| case 3000: lbval = 0x6; break; |
| case 3100: lbval = 0x8; break; |
| case 3200: lbval = 0xa; break; |
| case 3300: lbval = 0xc; break; |
| case 3400: lbval = 0xe; break; |
| default: |
| dev_warn(dev, |
| "Low-Battery threshold invalid, use default value.\n"); |
| goto lbdac_en; |
| }; |
| |
| mask = MAX77620_CNFGGLBL1_LBDAC; |
| |
| lbdac_en: |
| lbval |= MAX77620_CNFGGLBL1_LBDAC_EN; |
| mask |= MAX77620_CNFGGLBL1_LBDAC_EN; |
| |
| ret = max77620_reg_update(dev, MAX77620_PWR_SLAVE, MAX77620_REG_CNFGGLBL1, |
| mask, lbval); |
| if (ret < 0) |
| dev_err(dev, "Reg CNFGGLBL1 update failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static irqreturn_t max77620_mbattlow_irq(int irq, void *data) |
| { |
| struct max77620_chip *max77620 = data; |
| |
| dev_info(max77620->dev, "MBATTLOW interrupt occurred\n"); |
| |
| kernel_power_off(); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static const struct of_device_id of_max77620_match_tbl[] = { |
| { |
| .compatible = "maxim,max77620", |
| }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, of_max77620_match_tbl); |
| |
| static int max77620_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct device_node *node = client->dev.of_node; |
| struct max77620_chip *chip; |
| int i = 0; |
| int ret = 0; |
| const struct of_device_id *match; |
| |
| if (!node) { |
| dev_err(&client->dev, "Device is not from DT\n"); |
| return -ENODEV; |
| } |
| |
| chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
| if (chip == NULL) { |
| dev_err(&client->dev, "Memory alloc for chip failed\n"); |
| return -ENOMEM; |
| } |
| |
| i2c_set_clientdata(client, chip); |
| chip->dev = &client->dev; |
| chip->irq_base = -1; |
| chip->chip_irq = client->irq; |
| chip->mutex_config_locked = false; |
| |
| match = of_match_device(of_max77620_match_tbl, &client->dev); |
| if (!match) |
| return -ENODATA; |
| |
| mutex_init(&chip->mutex_config); |
| |
| for (i = 0; i < MAX77620_NUM_SLAVES; i++) { |
| if (max77620_slave_address[i] == client->addr) |
| chip->clients[i] = client; |
| else |
| chip->clients[i] = i2c_new_dummy(client->adapter, |
| max77620_slave_address[i]); |
| if (!chip->clients[i]) { |
| dev_err(&client->dev, "can't attach client %d\n", i); |
| ret = -ENOMEM; |
| goto fail_client_reg; |
| } |
| |
| chip->clients[i]->dev.of_node = node; |
| i2c_set_clientdata(chip->clients[i], chip); |
| max77620_regmap_config[i].lock_arg = chip; |
| chip->rmap[i] = devm_regmap_init_i2c(chip->clients[i], |
| (const struct regmap_config *)&max77620_regmap_config[i]); |
| if (IS_ERR(chip->rmap[i])) { |
| ret = PTR_ERR(chip->rmap[i]); |
| dev_err(&client->dev, |
| "regmap %d init failed, err %d\n", i, ret); |
| goto fail_client_reg; |
| } |
| } |
| |
| ret = max77620_read_es_version(chip); |
| if (ret < 0) { |
| dev_err(chip->dev, "Chip revision init failed: %d\n", ret); |
| goto fail_client_reg; |
| } |
| |
| max77620_top_lbt_irq_chip.pre_post_irq_data = chip; |
| |
| ret = regmap_add_irq_chip(chip->rmap[MAX77620_PWR_SLAVE], |
| chip->chip_irq, IRQF_ONESHOT | IRQF_SHARED, chip->irq_base, |
| &max77620_top_lbt_irq_chip, &chip->top_lbt_irq_data); |
| if (ret < 0) { |
| dev_err(chip->dev, "Failed to add top irq_chip %d\n", ret); |
| goto fail_client_reg; |
| } |
| |
| ret = max77620_initialise_fps(chip, &client->dev); |
| if (ret < 0) { |
| dev_err(&client->dev, "FPS initialisation failed, %d\n", ret); |
| goto fail_free_irq; |
| } |
| |
| ret = max77620_init_backup_battery_charging(chip, &client->dev); |
| if (ret < 0) { |
| dev_err(&client->dev, |
| "Backup battery charging init failed, %d\n", ret); |
| goto fail_free_irq; |
| } |
| |
| ret = mfd_add_devices(&client->dev, -1, max77620_children, |
| ARRAY_SIZE(max77620_children), NULL, 0, |
| regmap_irq_get_domain(chip->top_lbt_irq_data)); |
| if (ret < 0) { |
| dev_err(&client->dev, "mfd add dev fail %d\n", ret); |
| goto fail_free_irq; |
| } |
| |
| ret = max77620_init_mbattlow(&client->dev); |
| if (ret < 0) |
| dev_err(&client->dev, |
| "Monitor Low-Battery initialize failed, use default setting.\n"); |
| chip->irq_mbattlow = max77620_irq_get_virq(chip->dev, |
| MAX77620_IRQ_LBT_MBATLOW); |
| if (chip->irq_mbattlow) { |
| ret = devm_request_threaded_irq(chip->dev, chip->irq_mbattlow, |
| NULL, max77620_mbattlow_irq, |
| IRQF_ONESHOT, dev_name(chip->dev), |
| chip); |
| if (ret < 0) |
| dev_err(&client->dev, "request irq %d failed: %d\n", |
| chip->irq_mbattlow, ret); |
| } |
| |
| dev_info(&client->dev, "max77620 probe successfully\n"); |
| return 0; |
| |
| fail_free_irq: |
| regmap_del_irq_chip(chip->chip_irq, chip->top_lbt_irq_data); |
| |
| fail_client_reg: |
| for (i = 0; i < MAX77620_NUM_SLAVES; i++) { |
| if (!chip->clients[i] || chip->clients[i] == client) |
| continue; |
| i2c_unregister_device(chip->clients[i]); |
| } |
| return ret; |
| } |
| |
| static int max77620_remove(struct i2c_client *client) |
| { |
| |
| struct max77620_chip *chip = i2c_get_clientdata(client); |
| int i; |
| |
| mfd_remove_devices(chip->dev); |
| regmap_del_irq_chip(chip->chip_irq, chip->top_lbt_irq_data); |
| |
| for (i = 0; i < MAX77620_NUM_SLAVES; i++) { |
| if (chip->clients[i] != client) |
| i2c_unregister_device(chip->clients[i]); |
| } |
| |
| return 0; |
| } |
| |
| static void max77620_shutdown(struct i2c_client *i2c) |
| { |
| struct max77620_chip *chip = i2c_get_clientdata(i2c); |
| |
| chip->shutdown = true; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int max77620_i2c_suspend(struct device *dev) |
| { |
| struct max77620_chip *chip = dev_get_drvdata(dev); |
| unsigned int config; |
| int ret; |
| |
| config = (chip->sleep_enable) ? MAX77620_ONOFFCNFG1_SLPEN : 0; |
| ret = max77620_reg_update(chip->dev, MAX77620_PWR_SLAVE, |
| MAX77620_REG_ONOFFCNFG1, MAX77620_ONOFFCNFG1_SLPEN, |
| config); |
| if (ret < 0) |
| dev_err(dev, "Reg ONOFFCNFG1 update failed: %d\n", ret); |
| return 0; |
| } |
| #endif |
| |
| static const struct i2c_device_id max77620_id[] = { |
| {"max77620", 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, max77620_id); |
| |
| static const struct dev_pm_ops max77620_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(max77620_i2c_suspend, NULL) |
| }; |
| |
| static struct i2c_driver max77620_driver = { |
| .driver = { |
| .name = "max77620", |
| .of_match_table = of_max77620_match_tbl, |
| .owner = THIS_MODULE, |
| .pm = &max77620_pm_ops, |
| }, |
| .probe = max77620_probe, |
| .remove = max77620_remove, |
| .shutdown = max77620_shutdown, |
| .id_table = max77620_id, |
| }; |
| |
| static int __init max77620_init(void) |
| { |
| return i2c_add_driver(&max77620_driver); |
| } |
| subsys_initcall(max77620_init); |
| |
| static void __exit max77620_exit(void) |
| { |
| i2c_del_driver(&max77620_driver); |
| } |
| module_exit(max77620_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("MAX77620 Multi Function Device Core Driver"); |
| MODULE_AUTHOR("Chaitanya Bandi <bandik@nvidia.com>"); |
| MODULE_ALIAS("i2c:max77620-core"); |