/* * drivers/power/axp/axp22x/axp22x.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * driver of axp22x * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../axp-core.h" #include "../axp-charger.h" #include "../axp-regulator.h" #include "axp22x.h" #include "axp22x-regulator.h" static struct axp_dev *axp22x_pm_power; struct axp_config_info axp22x_config; struct wakeup_source *axp22x_ws; static int axp22x_pmu_num; static const struct axp_compatible_name_mapping axp22x_cn_mapping[] = { { .device_name = "axp221s", .mfd_name = { .powerkey_name = "axp221s-powerkey", .charger_name = "axp221s-charger", .regulator_name = "axp221s-regulator", .gpio_name = "axp221s-gpio", }, }, { .device_name = "axp223", .mfd_name = { .powerkey_name = "axp223-powerkey", .charger_name = "axp223-charger", .regulator_name = "axp223-regulator", .gpio_name = "axp223-gpio", }, }, { .device_name = "axp227", .mfd_name = { .powerkey_name = "axp227-powerkey", .charger_name = "axp227-charger", .regulator_name = "axp227-regulator", .gpio_name = "axp227-gpio", }, }, { .device_name = "axp233", .mfd_name = { .powerkey_name = "axp233-powerkey", .charger_name = "axp233-charger", .regulator_name = "axp233-regulator", .gpio_name = "axp233-gpio", }, } }; static struct axp_regmap_irq_chip axp22x_regmap_irq_chip = { .name = "axp22x_irq_chip", .status_base = AXP22X_INTSTS1, .enable_base = AXP22X_INTEN1, .num_regs = 5, }; static struct resource axp22x_pek_resources[] = { {AXP22X_IRQ_PEKRE, AXP22X_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,}, {AXP22X_IRQ_PEKFE, AXP22X_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,}, }; static struct resource axp22x_charger_resources[] = { {AXP22X_IRQ_USBIN, AXP22X_IRQ_USBIN, "usb in", IORESOURCE_IRQ,}, {AXP22X_IRQ_USBRE, AXP22X_IRQ_USBRE, "usb out", IORESOURCE_IRQ,}, {AXP22X_IRQ_ACIN, AXP22X_IRQ_ACIN, "ac in", IORESOURCE_IRQ,}, {AXP22X_IRQ_ACRE, AXP22X_IRQ_ACRE, "ac out", IORESOURCE_IRQ,}, {AXP22X_IRQ_BATIN, AXP22X_IRQ_BATIN, "bat in", IORESOURCE_IRQ,}, {AXP22X_IRQ_BATRE, AXP22X_IRQ_BATRE, "bat out", IORESOURCE_IRQ,}, #ifdef CONFIG_AW_AXP233 {AXP22X_IRQ_QBWUT, AXP22X_IRQ_QBWUT, "bat QWtemplow", IORESOURCE_IRQ,}, {AXP22X_IRQ_BWUT, AXP22X_IRQ_BWUT, "bat Wtemplow", IORESOURCE_IRQ,}, {AXP22X_IRQ_QBWOT, AXP22X_IRQ_QBWOT, "bat QWtempover", IORESOURCE_IRQ,}, {AXP22X_IRQ_BWUT, AXP22X_IRQ_BWUT, "bat Wtempover", IORESOURCE_IRQ,}, {AXP22X_IRQ_QBCUT, AXP22X_IRQ_QBCUT, "bat QCtemplow", IORESOURCE_IRQ,}, {AXP22X_IRQ_BCUT, AXP22X_IRQ_BCUT, "bat Ctemplow", IORESOURCE_IRQ,}, {AXP22X_IRQ_QBCOT, AXP22X_IRQ_QBCOT, "bat QCtempover", IORESOURCE_IRQ,}, {AXP22X_IRQ_BCOT, AXP22X_IRQ_BCOT, "bat Ctempover", IORESOURCE_IRQ,}, #else {AXP22X_IRQ_TEMLO, AXP22X_IRQ_TEMLO, "bat templow", IORESOURCE_IRQ,}, {AXP22X_IRQ_TEMOV, AXP22X_IRQ_TEMOV, "bat tempover", IORESOURCE_IRQ,}, #endif {AXP22X_IRQ_CHAST, AXP22X_IRQ_CHAST, "charging", IORESOURCE_IRQ,}, {AXP22X_IRQ_CHAOV, AXP22X_IRQ_CHAOV, "charge over", IORESOURCE_IRQ,}, {AXP22X_IRQ_LOWN1, AXP22X_IRQ_LOWN1, "low warning1", IORESOURCE_IRQ,}, {AXP22X_IRQ_LOWN2, AXP22X_IRQ_LOWN2, "low warning2", IORESOURCE_IRQ,}, }; static struct resource axp22x_gpio_resources[] = { {AXP22X_IRQ_GPIO0, AXP22X_IRQ_GPIO0, "gpio0", IORESOURCE_IRQ,}, {AXP22X_IRQ_GPIO1, AXP22X_IRQ_GPIO1, "gpio1", IORESOURCE_IRQ,}, }; static struct mfd_cell axp22x_cells[] = { { .name = "axp22x-powerkey", .num_resources = ARRAY_SIZE(axp22x_pek_resources), .resources = axp22x_pek_resources, }, { .name = "axp22x-regulator", }, { .name = "axp22x-charger", .num_resources = ARRAY_SIZE(axp22x_charger_resources), .resources = axp22x_charger_resources, }, { .name = "axp22x-gpio", .num_resources = ARRAY_SIZE(axp22x_gpio_resources), .resources = axp22x_gpio_resources, }, }; void axp22x_power_off(void) { uint8_t val; pr_info("[axp] send power-off command!\n"); mdelay(20); if (axp22x_config.power_start != 1) { axp_regmap_read(axp22x_pm_power->regmap, AXP22X_STATUS, &val); if (val & 0xF0) { axp_regmap_read(axp22x_pm_power->regmap, AXP22X_MODE_CHGSTATUS, &val); if ((axp22x_config.pmu_bat_unused == 0) && (val & 0x20) && (val & 0x10)) { pr_info("[axp] set flag!\n"); axp_regmap_read(axp22x_pm_power->regmap, AXP22X_BUFFERC, &val); if (0x0d != val) axp_regmap_write( axp22x_pm_power->regmap, AXP22X_BUFFERC, 0x0f); mdelay(20); pr_info("[axp] reboot!\n"); machine_restart(NULL); pr_warn("[axp] warning!!! arch can't reboot,\"\ \" maybe some error happend!\n"); } } } axp_regmap_read(axp22x_pm_power->regmap, AXP22X_BUFFERC, &val); if (0x0d != val) axp_regmap_write(axp22x_pm_power->regmap, AXP22X_BUFFERC, 0x00); mdelay(20); axp_regmap_set_bits(axp22x_pm_power->regmap, AXP22X_OFF_CTL, 0x80); mdelay(20); pr_warn("[axp] warning!!! axp can't power-off,\"\ \" maybe some error happend!\n"); } static int axp22x_init_chip(struct axp_dev *axp22x) { uint8_t chip_id, dcdc2_ctl; int err; err = axp_regmap_read(axp22x->regmap, AXP22X_IC_TYPE, &chip_id); if (err) { pr_err("[%s] try to read chip id failed!\n", axp_name[axp22x_pmu_num]); return err; } if (((chip_id & 0xc0) == 0x00) && (((chip_id & 0x0f) == 0x06) || ((chip_id & 0x0f) == 0x07) || ((chip_id & 0xff) == 0x42)) ) { if ((chip_id & 0x0f) == 0x07) axp22x_need_save_regulator = 1; pr_info("[%s] chip id detect 0x%x !\n", axp_name[axp22x_pmu_num], chip_id); } else { pr_info("[%s] chip id not detect 0x%x !\n", axp_name[axp22x_pmu_num], chip_id); } /* enable dcdc2 dvm */ err = axp_regmap_read(axp22x->regmap, AXP22X_DC23_DVM_CTL, &dcdc2_ctl); if (err) { pr_err("[%s] try to read dcdc2 dvm failed!\n", axp_name[axp22x_pmu_num]); return err; } dcdc2_ctl |= (0x1<<2); err = axp_regmap_write(axp22x->regmap, AXP22X_DC23_DVM_CTL, dcdc2_ctl); if (err) { pr_err("[%s] try to enable dcdc2 dvm failed!\n", axp_name[axp22x_pmu_num]); return err; } pr_info("[%s] enable dcdc2 dvm.\n", axp_name[axp22x_pmu_num]); /*init N_VBUSEN status*/ if (axp22x_config.pmu_vbusen_func) axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x10); else axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x10); /*init 16's reset pmu en */ if (axp22x_config.pmu_reset) axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x08); else axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x08); /*init irq wakeup en*/ if (axp22x_config.pmu_irq_wakeup) axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x80); else axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x80); /*init pmu over temperature protection*/ if (axp22x_config.pmu_hot_shutdown) axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x04); else axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x04); /*init inshort status*/ if (axp22x_config.pmu_inshort) axp_regmap_set_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x60); else axp_regmap_clr_bits(axp22x->regmap, AXP22X_HOTOVER_CTL, 0x60); return 0; } static void axp22x_wakeup_event(void) { __pm_wakeup_event(axp22x_ws, 0); } static s32 axp22x_usb_det(void) { u8 value = 0; int ret = 0; axp_regmap_read(axp22x_pm_power->regmap, AXP22X_STATUS, &value); if (value & 0x10) { axp_usb_connect = 1; ret = 1; } return ret; } static s32 axp22x_usb_vbus_output(int high) { u8 ret = 0; ret = axp_regmap_clr_bits_sync(axp22x_pm_power->regmap, AXP22X_HOTOVER_CTL, 0x10); if (ret) return ret; if (high) ret = axp_regmap_set_bits_sync(axp22x_pm_power->regmap, AXP22X_IPS_SET, 0x04); else ret = axp_regmap_clr_bits_sync(axp22x_pm_power->regmap, AXP22X_IPS_SET, 0x04); return ret; } static int axp22x_cfg_pmux_para(int num, struct aw_pm_info *api, int *pmu_id) { char name[8]; struct device_node *np; sprintf(name, "pmu%d", num); np = of_find_node_by_type(NULL, name); if (NULL == np) { pr_err("can not find device_type for %s\n", name); return -1; } if (!of_device_is_available(np)) { pr_err("can not find node for %s\n", name); return -1; } #ifdef CONFIG_AXP_TWI_USED if (api != NULL) { api->pmu_arg.twi_port = axp22x_pm_power->regmap->client->adapter->nr; api->pmu_arg.dev_addr = axp22x_pm_power->regmap->client->addr; } #endif *pmu_id = axp22x_config.pmu_id; return 0; } static const char *axp22x_get_pmu_name(void) { return axp_name[axp22x_pmu_num]; } static struct axp_dev *axp22x_get_pmu_dev(void) { return axp22x_pm_power; } struct axp_platform_ops axp22x_platform_ops = { .usb_det = axp22x_usb_det, .usb_vbus_output = axp22x_usb_vbus_output, .cfg_pmux_para = axp22x_cfg_pmux_para, .get_pmu_name = axp22x_get_pmu_name, .get_pmu_dev = axp22x_get_pmu_dev, .pmu_regulator_save = axp22x_regulator_save, .pmu_regulator_restore = axp22x_regulator_restore, }; static const struct i2c_device_id axp22x_id_table[] = { { "axp221s", 0 }, { "axp223", 0 }, { "axp227", 0 }, { "axp233", 0 }, {} }; static const struct of_device_id axp22x_dt_ids[] = { { .compatible = "axp221s", }, { .compatible = "axp223", }, { .compatible = "axp227", }, { .compatible = "axp233", }, {}, }; MODULE_DEVICE_TABLE(of, axp22x_dt_ids); #ifdef CONFIG_AXP_TWI_USED static int axp22x_probe(struct i2c_client *client, const struct i2c_device_id *id) #else static int axp22x_probe(struct platform_device *pdev) #endif { int ret; struct axp_dev *axp22x; struct device_node *node; struct device *device; #ifdef CONFIG_AXP_TWI_USED node = client->dev.of_node; device = &client->dev; #else node = pdev->dev.of_node; device = &pdev->dev; #endif axp22x_pmu_num = axp_get_pmu_num(axp22x_cn_mapping, ARRAY_SIZE(axp22x_cn_mapping)); if (axp22x_pmu_num < 0) { pr_err("%s get pmu num failed\n", __func__); return axp22x_pmu_num; } if (node) { /* get dt and sysconfig */ if (!of_device_is_available(node)) { axp22x_config.pmu_used = 0; pr_err("%s: pmu_used = %u\n", __func__, axp22x_config.pmu_used); return -EPERM; } else { axp22x_config.pmu_used = 1; ret = axp_dt_parse(node, axp22x_pmu_num, &axp22x_config); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } } } else { pr_err("AXP22x device tree err!\n"); return -EBUSY; } axp22x = devm_kzalloc(device, sizeof(*axp22x), GFP_KERNEL); if (!axp22x) return -ENOMEM; axp22x->dev = device; axp22x->nr_cells = ARRAY_SIZE(axp22x_cells); axp22x->cells = axp22x_cells; axp22x->pmu_num = axp22x_pmu_num; ret = axp_mfd_cell_name_init(axp22x_cn_mapping, ARRAY_SIZE(axp22x_cn_mapping), axp22x->pmu_num, axp22x->nr_cells, axp22x->cells); if (ret) return ret; #ifdef CONFIG_AXP_TWI_USED axp22x->regmap = axp_regmap_init_i2c(device); #else axp22x->regmap = axp_regmap_init_arisc_rsb(device, AXP22X_RSB_RTSADDR); #endif if (IS_ERR(axp22x->regmap)) { ret = PTR_ERR(axp22x->regmap); dev_err(device, "regmap init failed: %d\n", ret); return ret; } #ifdef CONFIG_AXP_TWI_USED i2c_set_clientdata(client, axp22x); #else platform_set_drvdata(pdev, axp22x); #endif ret = axp22x_init_chip(axp22x); if (ret) return ret; axp22x_pm_power = axp22x; axp_platform_ops_set(axp22x->pmu_num, &axp22x_platform_ops); ret = axp_mfd_add_devices(axp22x); if (ret) { dev_err(axp22x->dev, "failed to add MFD devices: %d\n", ret); return ret; } #ifdef CONFIG_AXP_TWI_USED axp22x->irq = client->irq; #else axp22x->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); #endif axp22x->irq_data = axp_irq_chip_register(axp22x->regmap, axp22x->irq, IRQF_SHARED | IRQF_NO_SUSPEND, &axp22x_regmap_irq_chip, axp22x_wakeup_event); if (IS_ERR(axp22x->irq_data)) { ret = PTR_ERR(axp22x->irq_data); dev_err(device, "axp init irq failed: %d\n", ret); return ret; } if (!pm_power_off) pm_power_off = axp22x_power_off; axp22x_ws = wakeup_source_register("axp22_wakeup_source"); return 0; } #ifdef CONFIG_AXP_TWI_USED static int axp22x_remove(struct i2c_client *client) #else static int axp22x_remove(struct platform_device *pdev) #endif { struct axp_dev *axp22x; #ifdef CONFIG_AXP_TWI_USED axp22x = i2c_get_clientdata(client); #else axp22x = platform_get_drvdata(pdev); #endif if (axp22x == axp22x_pm_power) { axp22x_pm_power = NULL; pm_power_off = NULL; } axp_mfd_remove_devices(axp22x); axp_irq_chip_unregister(axp22x->irq, axp22x->irq_data); return 0; } #ifdef CONFIG_AXP_TWI_USED static struct i2c_driver axp22x_driver = { #else static struct platform_driver axp22x_driver = { #endif .driver = { .name = "axp22x", .owner = THIS_MODULE, .of_match_table = axp22x_dt_ids, }, .probe = axp22x_probe, .remove = axp22x_remove, #ifdef CONFIG_AXP_TWI_USED .id_table = axp22x_id_table, #endif }; static int __init axp22x_init(void) { int ret; #ifdef CONFIG_AXP_TWI_USED ret = i2c_add_driver(&axp22x_driver); #else ret = platform_driver_register(&axp22x_driver); #endif if (ret != 0) pr_err("Failed to register axp22x I2C driver: %d\n", ret); return ret; } subsys_initcall_sync(axp22x_init); static void __exit axp22x_exit(void) { #ifdef CONFIG_AXP_TWI_USED i2c_del_driver(&axp22x_driver); #else platform_driver_unregister(&axp22x_driver); #endif } module_exit(axp22x_exit); MODULE_DESCRIPTION("Driver of AXP22X"); MODULE_AUTHOR("pannan"); MODULE_LICENSE("GPL");