/* * drivers/power/axp/axp2101/axp2101.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * driver of axp2101 * * 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 #include "../axp-core.h" #include "../axp-charger.h" #include "../axp-regulator.h" #include "axp2101.h" #include "axp2101-regu.h" static struct axp_dev *axp2101_pm_power; struct axp_config_info axp2101_config; struct wakeup_source *axp2101_ws; static int axp2101_pmu_num; /* * irq_en to record irq_en register in suspend. * irq_cfg configure can wakeup system irq. */ struct axp2101_dev { struct axp_dev axp_dev; /* which resume system */ uint32_t resume; uint8_t irq_en0; uint8_t irq_en1; uint8_t irq_en2; uint8_t irq_cfg0; uint8_t irq_cfg1; uint8_t irq_cfg2; }; #define to_axp2101_dev(x) container_of(x, struct axp2101_dev, axp_dev) static struct axp_regmap_irq_chip axp2101_regmap_irq_chip = { .name = "axp2101_irq_chip", .status_base = axp2101_INTSTS1, .enable_base = axp2101_INTEN1, .num_regs = 3, }; static struct resource axp2101_pek_resources[] = { { axp2101_IRQ_PONN, axp2101_IRQ_PONN, "PEK_DBF", IORESOURCE_IRQ, }, { axp2101_IRQ_PONP, axp2101_IRQ_PONP, "PEK_DBR", IORESOURCE_IRQ, }, }; static struct resource axp2101_charger_resources[] = { { axp2101_IRQ_VINSET, axp2101_IRQ_VINSET, "usb in", IORESOURCE_IRQ, }, { axp2101_IRQ_VREMOV, axp2101_IRQ_VREMOV, "usb out", IORESOURCE_IRQ, }, { axp2101_IRQ_BINSERT, axp2101_IRQ_BINSERT, "bat in", IORESOURCE_IRQ, }, { axp2101_IRQ_BREMOV, axp2101_IRQ_BREMOV, "bat out", IORESOURCE_IRQ, }, /* { */ /* axp2101_IRQ_BWUT, */ /* axp2101_IRQ_BWUT, */ /* "bat untemp work", */ /* IORESOURCE_IRQ, */ /* }, */ /* { */ /* axp2101_IRQ_BWOT, */ /* axp2101_IRQ_BWOT, */ /* "bat ovtemp work", */ /* IORESOURCE_IRQ, */ /* }, */ { axp2101_IRQ_BCUT, axp2101_IRQ_BCUT, "quit bat untemp chg", IORESOURCE_IRQ, }, { axp2101_IRQ_BCOT, axp2101_IRQ_BCOT, "quit bat ovtemp chg", IORESOURCE_IRQ, }, { axp2101_IRQ_CHGST, axp2101_IRQ_CHGST, "charging", IORESOURCE_IRQ, }, { axp2101_IRQ_CHGDN, axp2101_IRQ_CHGDN, "charge over", IORESOURCE_IRQ, }, { axp2101_IRQ_SOCWL1, axp2101_IRQ_SOCWL1, "low warning1", IORESOURCE_IRQ, }, { axp2101_IRQ_SOCWL2, axp2101_IRQ_SOCWL2, "low warning2", IORESOURCE_IRQ, }, }; static struct mfd_cell axp2101_cells[] = { { .name = "axp2101-powerkey", .num_resources = ARRAY_SIZE(axp2101_pek_resources), .resources = axp2101_pek_resources, }, { .name = "axp2101-regulator", }, { .name = "axp2101-charger", .num_resources = ARRAY_SIZE(axp2101_charger_resources), .resources = axp2101_charger_resources, .of_compatible = "axp2101-charger", }, { .name = "axp2101-gpio", }, }; static const struct axp_compatible_name_mapping axp2101_mapping[] = { { .device_name = "axp2101", .mfd_name = { .powerkey_name = "axp2101-powerkey", .charger_name = "axp2101-charger", .regulator_name = "axp2101-regulator", .gpio_name = "axp2101-gpio", }, }, }; void axp2101_power_off(void) { uint8_t val; pr_info("[axp] send power-off command!\n"); mdelay(20); if (axp2101_config.power_start != 1) { axp_regmap_read(axp2101_pm_power->regmap, axp2101_COMM_STAT1, &val); if (val & 0x10) { axp_regmap_read(axp2101_pm_power->regmap, axp2101_COMM_STAT1, &val); if ((axp2101_config.pmu_bat_unused == 0) && (val & 0x20) && (val & 0x10)) { pr_info("[axp] set flag!\n"); axp_regmap_read(axp2101_pm_power->regmap, axp2101_BUFFERC, &val); if (0x0d != val) axp_regmap_write( axp2101_pm_power->regmap, axp2101_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(axp2101_pm_power->regmap, axp2101_BUFFERC, &val); if (0x0d != val) axp_regmap_write(axp2101_pm_power->regmap, axp2101_BUFFERC, 0x00); mdelay(20); axp_regmap_set_bits(axp2101_pm_power->regmap, axp2101_COMM_CFG, 0x01); mdelay(20); pr_warn("[axp] warning!!! axp can't power-off," " maybe some error happend!\n"); } static int axp2101_init_chip(struct axp_dev *axp2101) { uint8_t chip_id, dcdc2_ctl; int err; err = axp_regmap_read(axp2101->regmap, axp2101_CHIP_ID, &chip_id); if (err) { pr_err("[%s] try to read chip id failed!\n", axp_name[axp2101_pmu_num]); return err; } /* * axp210 has two, one is 0x47 is bug, * 0x4a fix it */ if (chip_id == 0x47) { pr_info("[%s] chip id detect 0x%x !\n", axp_name[axp2101_pmu_num], chip_id); axp2101->axp_sub = 0; } else if (chip_id == 0x4a) { pr_info("[%s] chip id detect 0x%x !\n", axp_name[axp2101_pmu_num], chip_id); axp2101->axp_sub = 1; } else pr_info("[%s] chip id not detect 0x%x !\n", axp_name[axp2101_pmu_num], chip_id); /* enable dcdc2 dvm */ err = axp_regmap_read(axp2101->regmap, axp2101_DCDC2_CFG, &dcdc2_ctl); if (err) { pr_err("[%s] try to read dcdc dvm failed!\n", axp_name[axp2101_pmu_num]); return err; } dcdc2_ctl |= (0x1 << 7); err = axp_regmap_write(axp2101->regmap, axp2101_DCDC2_CFG, dcdc2_ctl); if (err) { pr_err("[%s] try to enable dcdc2 dvm failed!\n", axp_name[axp2101_pmu_num]); return err; } pr_info("[%s] enable dcdc2 dvm.\n", axp_name[axp2101_pmu_num]); /* init 16's reset pmu en */ if (axp2101_config.pmu_reset) axp_regmap_set_bits(axp2101->regmap, axp2101_COMM_CFG, 0x04); else axp_regmap_clr_bits(axp2101->regmap, axp2101_COMM_CFG, 0x04); /* enable pwrok pin pull low to restart the system */ axp_regmap_set_bits(axp2101->regmap, axp2101_COMM_CFG, 0x08); /* init irq wakeup en */ if (axp2101_config.pmu_irq_wakeup) axp_regmap_set_bits(axp2101->regmap, axp2101_SLEEP_CFG, 0x80); else axp_regmap_clr_bits(axp2101->regmap, axp2101_SLEEP_CFG, 0x80); /* init pmu over temperature protection */ if (axp2101_config.pmu_hot_shutdown) axp_regmap_set_bits(axp2101->regmap, axp2101_PWROFF_EN, 0x04); else axp_regmap_clr_bits(axp2101->regmap, axp2101_PWROFF_EN, 0x04); /* 85% low voltage turn off pmic function */ /* axp_regmap_write(axp2101->regmap, axp2101_DCDC_PWROFF_EN, 0x3f); */ /* set 2.6v for battery poweroff */ axp_regmap_write(axp2101->regmap, axp2101_VOFF_THLD, 0x00); /* set delay of powerok after all power output good to 8ms */ axp_regmap_update(axp2101->regmap, axp2101_PWR_TIME_CTRL, 0x00, 0x03); return 0; } static void axp2101_wakeup_event(void) { __pm_wakeup_event(axp2101_ws, 0); } static s32 axp2101_usb_det(void) { u8 value = 0; int ret = 0; axp_regmap_read(axp2101_pm_power->regmap, axp2101_COMM_STAT0, &value); if (value & BIT(5)) { axp_usb_connect = 1; ret = 1; } return ret; } static int axp2101_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; } api->pmu_arg.twi_port = axp2101_pm_power->regmap->client->adapter->nr; api->pmu_arg.dev_addr = axp2101_pm_power->regmap->client->addr; *pmu_id = axp2101_config.pmu_id; return 0; } static const char *axp2101_get_pmu_name(void) { return axp_name[axp2101_pmu_num]; } static struct axp_dev *axp2101_get_pmu_dev(void) { return axp2101_pm_power; } struct axp_platform_ops axp2101_platform_ops = { .usb_det = axp2101_usb_det, .cfg_pmux_para = axp2101_cfg_pmux_para, .get_pmu_name = axp2101_get_pmu_name, .get_pmu_dev = axp2101_get_pmu_dev, /* .powerkey_name = { "axp2101-powerkey" }, */ /* .charger_name = { "axp2101-charger" }, */ /* .regulator_name = { "axp2101-regulator" }, */ /* .gpio_name = { "axp2101-gpio" }, */ }; static const struct of_device_id axp2101_dt_ids[] = { { .compatible = "axp2101", }, {}, }; MODULE_DEVICE_TABLE(of, axp2101_dt_ids); /* * axp2101_parse_wakeup_source - configure which int can wakeup system */ static void axp2101_parse_wakeup_source(struct axp_dev *adev) { struct axp2101_dev *dev_2101 = to_axp2101_dev(adev); uint32_t prop = 0xff; /* prop only modified with read pmu_ws_cfg\d sucess */ of_property_read_u32(adev->dev->of_node, "pmu_ws_cfg0", &prop); dev_2101->irq_cfg0 = prop; of_property_read_u32(adev->dev->of_node, "pmu_ws_cfg1", &prop); dev_2101->irq_cfg1 = prop; of_property_read_u32(adev->dev->of_node, "pmu_ws_cfg2", &prop); dev_2101->irq_cfg2 = prop; } static ssize_t pmu_ws_show(struct device *dev, struct device_attribute *attr, char *buf) { struct axp_dev *axp_dev = dev_get_drvdata(dev); struct axp2101_dev *axp = to_axp2101_dev(axp_dev); return sprintf( buf, "irq_cfg0 = 0x%02x\nirq_cfg1 = 0x%02x\nirq_cfg2 = 0x%02x\n", axp->irq_cfg0, axp->irq_cfg1, axp->irq_cfg2); } static ssize_t pmu_ws_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct axp_dev *axp_dev = dev_get_drvdata(dev); struct axp2101_dev *axp = to_axp2101_dev(axp_dev); uint32_t v; if (kstrtou32(buf, 0, &v)) return -EINVAL; axp->irq_cfg0 = v & 0xff; axp->irq_cfg1 = (v >> 8) & 0xff; axp->irq_cfg2 = (v >> 16) & 0xff; return count; } DEVICE_ATTR_RW(pmu_ws); ssize_t axp_wakeup_event_show(struct device *dev, struct device_attribute *attr, char *buf) { struct axp_dev *axp_dev = dev_get_drvdata(dev); struct axp2101_dev *axp = to_axp2101_dev(axp_dev); return sprintf(buf, "0x%06x\n", axp->resume); } DEVICE_ATTR_RO(axp_wakeup_event); #ifdef CONFIG_AXP_TWI_USED static int axp2101_probe(struct i2c_client *client, const struct i2c_device_id *id) #else static int axp2101_probe(struct platform_device *pdev) #endif { int ret; struct axp2101_dev *axp2101_dev; struct axp_dev *axp2101; 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 axp2101_pmu_num = axp_get_pmu_num(axp2101_mapping, ARRAY_SIZE(axp2101_mapping)); if (axp2101_pmu_num < 0) { pr_err("%s get pmu num failed\n", __func__); return axp2101_pmu_num; } if (node) { /* get dt and sysconfig */ if (!of_device_is_available(node)) { axp2101_config.pmu_used = 0; pr_err("%s: pmu_used = %u\n", __func__, axp2101_config.pmu_used); return -EPERM; } else { axp2101_config.pmu_used = 1; ret = axp_dt_parse(node, axp2101_pmu_num, &axp2101_config); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } } } else { pr_err("axp2101x device tree err!\n"); return -EBUSY; } axp2101_dev = devm_kzalloc(device, sizeof(*axp2101_dev), GFP_KERNEL); if (!axp2101_dev) return -ENOMEM; axp2101 = &axp2101_dev->axp_dev; axp2101->dev = device; axp2101->nr_cells = ARRAY_SIZE(axp2101_cells); axp2101->cells = axp2101_cells; axp2101->pmu_num = axp2101_pmu_num; axp2101_parse_wakeup_source(axp2101); ret = axp_mfd_cell_name_init(axp2101_mapping, ARRAY_SIZE(axp2101_mapping), axp2101->pmu_num, axp2101->nr_cells, axp2101->cells); if (ret) return ret; #ifdef CONFIG_AXP_TWI_USED axp2101->regmap = axp_regmap_init_i2c(device); #else axp2101->regmap = axp_regmap_init_arisc_rsb(device, axp2101_RSB_RTSADDR); #endif if (IS_ERR(axp2101->regmap)) { ret = PTR_ERR(axp2101->regmap); dev_err(device, "regmap init failed: %d\n", ret); return ret; } #ifdef CONFIG_AXP_TWI_USED i2c_set_clientdata(client, axp2101); #else platform_set_drvdata(pdev, axp2101); #endif ret = axp2101_init_chip(axp2101); if (ret) return ret; axp2101_pm_power = axp2101; axp_platform_ops_set(axp2101->pmu_num, &axp2101_platform_ops); ret = axp_mfd_add_devices(axp2101); if (ret) { dev_err(axp2101->dev, "failed to add MFD devices: %d\n", ret); return ret; } #ifdef CONFIG_AXP_TWI_USED axp2101->irq = client->irq; #else axp2101->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); #endif axp2101->irq_data = axp_irq_chip_register( axp2101->regmap, axp2101->irq, IRQF_SHARED, &axp2101_regmap_irq_chip, axp2101_wakeup_event); if (IS_ERR(axp2101->irq_data)) { ret = PTR_ERR(axp2101->irq_data); dev_err(device, "axp init irq failed: %d\n", ret); return ret; } if (!pm_power_off) pm_power_off = axp2101_power_off; device_create_file(&pdev->dev, &dev_attr_pmu_ws); device_create_file(&pdev->dev, &dev_attr_axp_wakeup_event); /* wakeup-source process */ #ifndef CONFIG_AXP_TWI_USED if (of_property_read_bool(node, "wakeup-source")) axp2101_ws = axp_wakeup_source_init(axp2101->dev, axp2101->irq); else #endif axp2101_ws = wakeup_source_register("axp2101_wakeup_source"); return 0; } #ifdef CONFIG_AXP_TWI_USED static int axp2101_remove(struct i2c_client *client) #else static int axp2101_remove(struct platform_device *pdev) #endif { struct axp_dev *axp2101; #ifdef CONFIG_AXP_TWI_USED axp2101 = i2c_get_clientdata(client); #else axp2101 = platform_get_drvdata(pdev); #endif if (axp2101 == axp2101_pm_power) { axp2101_pm_power = NULL; pm_power_off = NULL; } axp_mfd_remove_devices(axp2101); axp_irq_chip_unregister(axp2101->irq, axp2101->irq_data); return 0; } /** * axp2101_suspend - suspend function to support wakeup source alone * * this version we easy add in suspend and resume * and laterr use wakeup source in irqdomain. */ static int axp2101_suspend(struct device *ddev) { struct platform_device *pdev = to_platform_device(ddev); struct axp_dev *adev = platform_get_drvdata(pdev); struct axp2101_dev *dev = to_axp2101_dev(adev); axp_regmap_read(adev->regmap, axp2101_INTEN1, &dev->irq_en0); axp_regmap_read(adev->regmap, axp2101_INTEN2, &dev->irq_en1); axp_regmap_read(adev->regmap, axp2101_INTEN3, &dev->irq_en2); /* only configure enable irq can wakeup system */ axp_regmap_write_sync(adev->regmap, axp2101_INTEN1, dev->irq_en0 & dev->irq_cfg0); axp_regmap_write_sync(adev->regmap, axp2101_INTEN2, dev->irq_en1 & dev->irq_cfg1); axp_regmap_write_sync(adev->regmap, axp2101_INTEN3, dev->irq_en2 & dev->irq_cfg2); return 0; } /** * axp2101_resume - resume function to support wakeup source alone * * this version we easy add in suspend and resume * and laterr use wakeup source in irqdomain. */ static int axp2101_resume(struct device *ddev) { struct platform_device *pdev = to_platform_device(ddev); struct axp_dev *adev = platform_get_drvdata(pdev); struct axp2101_dev *dev = to_axp2101_dev(adev); axp_regmap_write(adev->regmap, axp2101_INTEN1, dev->irq_en0); axp_regmap_write(adev->regmap, axp2101_INTEN2, dev->irq_en1); axp_regmap_write(adev->regmap, axp2101_INTEN3, dev->irq_en2); return 0; } /** * axp2101_resume - resume function to support wakeup source alone * * this version we easy add in suspend and resume * and laterr use wakeup source in irqdomain. */ static int axp2101_resume_noirq(struct device *ddev) { struct platform_device *pdev = to_platform_device(ddev); struct axp_dev *adev = platform_get_drvdata(pdev); struct axp2101_dev *dev = to_axp2101_dev(adev); /* wakeup irq is the same as pmu irq */ if (pm_wakeup_irq == adev->irq) { uint8_t sts0, sts1, sts2; axp_regmap_read(adev->regmap, axp2101_INTSTS1, &sts0); axp_regmap_read(adev->regmap, axp2101_INTSTS2, &sts1); axp_regmap_read(adev->regmap, axp2101_INTSTS3, &sts2); dev->resume = (sts0 & dev->irq_en0 & dev->irq_cfg0) | ((sts1 & dev->irq_en1 & dev->irq_cfg1) << 8) | ((sts2 & dev->irq_en2 & dev->irq_cfg2) << 16); } else { dev->resume = 0x80000000; } return 0; } struct dev_pm_ops axp2101_pm_ops = { .suspend = axp2101_suspend, .resume_noirq = axp2101_resume_noirq, .resume = axp2101_resume, }; static const struct i2c_device_id axp2101_id_table[] = { { "axp2101", 0 }, {} }; #ifdef CONFIG_AXP_TWI_USED static struct i2c_driver axp2101_driver = { #else static struct platform_driver axp2101_driver = { #endif .driver = { .name = "axp2101", .owner = THIS_MODULE, .of_match_table = axp2101_dt_ids, .pm = &axp2101_pm_ops, }, .probe = axp2101_probe, .remove = axp2101_remove, #ifdef CONFIG_AXP_TWI_USED .id_table = axp2101_id_table, #endif }; static int __init axp2101_init(void) { int ret; #ifdef CONFIG_AXP_TWI_USED ret = i2c_add_driver(&axp2101_driver); #else ret = platform_driver_register(&axp2101_driver); #endif if (ret != 0) pr_err("Failed to register axp2101x driver: %d\n", ret); return ret; } subsys_initcall_sync(axp2101_init); static void __exit axp2101_exit(void) { #ifdef CONFIG_AXP_TWI_USED i2c_del_driver(&axp2101_driver); #else platform_driver_unregister(&axp2101_driver); #endif } module_exit(axp2101_exit); MODULE_DESCRIPTION("Driver of axp2101"); MODULE_AUTHOR("HZJ"); MODULE_LICENSE("GPL");