/* * drivers/power/axp/axp259/axp259.c * (C) Copyright 2010-2017 * Allwinner Technology Co., Ltd. * * driver of axp259 * * 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 "../axp-core.h" #include "../axp-charger.h" #include "axp259.h" static struct axp_dev *axp259_pm_power; struct axp_config_info axp259_config; struct wakeup_source *axp259_ws; static int axp259_pmu_num; static const struct axp_compatible_name_mapping axp259_cn_mapping[] = { { .device_name = "axp259", .mfd_name = { .powerkey_name = "axp259-powerkey", .charger_name = "axp259-charger", .regulator_name = "axp259-regulator", .gpio_name = "axp259-gpio", }, }, }; static struct axp_regmap_irq_chip axp259_regmap_irq_chip = { .name = "axp259_irq_chip", .status_base = AXP259_INTSTS1, .enable_base = AXP259_INTEN1, .num_regs = 5, }; static struct resource axp259_pek_resources[] = { {AXP259_IRQ_PEKRE, AXP259_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,}, {AXP259_IRQ_PEKFE, AXP259_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,}, }; static struct resource axp259_charger_resources[] = { {AXP259_IRQ_ACIN, AXP259_IRQ_ACIN, "ac in", IORESOURCE_IRQ,}, {AXP259_IRQ_ACRE, AXP259_IRQ_ACRE, "ac out", IORESOURCE_IRQ,}, {AXP259_IRQ_BATIN, AXP259_IRQ_BATIN, "bat in", IORESOURCE_IRQ,}, {AXP259_IRQ_BATRE, AXP259_IRQ_BATRE, "bat out", IORESOURCE_IRQ,}, {AXP259_IRQ_CHAST, AXP259_IRQ_CHAST, "charging", IORESOURCE_IRQ,}, {AXP259_IRQ_CHAOV, AXP259_IRQ_CHAOV, "charge over", IORESOURCE_IRQ,}, {AXP259_IRQ_LOWN1, AXP259_IRQ_LOWN1, "low warning1", IORESOURCE_IRQ,}, {AXP259_IRQ_LOWN2, AXP259_IRQ_LOWN2, "low warning2", IORESOURCE_IRQ,}, }; static struct mfd_cell axp259_cells[] = { { .name = "axp259-powerkey", .num_resources = ARRAY_SIZE(axp259_pek_resources), .resources = axp259_pek_resources, }, { .name = "axp259-charger", .num_resources = ARRAY_SIZE(axp259_charger_resources), .resources = axp259_charger_resources, }, { .name = "axp259-gpio", }, }; void axp259_power_off(void) { uint8_t val; int err; pr_info("[%s] send power-off command!\n", axp_name[axp259_pmu_num]); err = axp_regmap_read(axp259_pm_power->regmap, AXP259_STATUS, &val); if (err) { pr_err("[%s] try to read axp status failed!\n", axp_name[axp259_pmu_num]); } pr_info("[axp] AXP259_STATUS: 0x%x\n", val); if ((val & 0x11) == 0x11) { axp_regmap_write(axp259_pm_power->regmap, AXP259_BUFFER1, 0x0f); mdelay(20); pr_info("[axp] reboot!\n"); machine_restart(NULL); pr_warn("[axp] warning!!! arch can't power off,\"\ \" maybe some error happened!\n"); } axp_regmap_set_bits(axp259_pm_power->regmap, AXP259_OFF_CTL, 0x40); mdelay(20); pr_warn("[%s] warning!!! axp can't power-off,\"\ \" maybe some error happened!\n", axp_name[axp259_pmu_num]); } static int axp259_init_chip(struct axp_dev *axp259) { uint8_t chip_id; int err; err = axp_regmap_write(axp259->regmap, AXP259_ADDR_EXTENSION, 0x40); if (err) { pr_err("[%s] try to write addr failed!\n", axp_name[axp259_pmu_num]); return err; } err = axp_regmap_read(axp259->regmap, AXP259_IC_TYPE, &chip_id); if (err) { pr_err("[%s] try to read chip id failed!\n", axp_name[axp259_pmu_num]); return err; } if (((chip_id & 0xc0) == 0x00) && ((chip_id & 0x0f) == 0x04) ) { pr_info("[%s] chip id detect 0x%x !\n", axp_name[axp259_pmu_num], chip_id); } else { pr_info("[%s] chip id not detect 0x%x !\n", axp_name[axp259_pmu_num], chip_id); } /*Init 16's Reset PMU en */ if (axp259_config.pmu_reset) axp_regmap_set_bits(axp259->regmap, 0x29, 0x04); /* enable */ else axp_regmap_clr_bits(axp259->regmap, 0x29, 0x04); /* disable */ /*Init IRQ wakeup en*/ if (axp259_config.pmu_irq_wakeup) axp_regmap_set_bits(axp259->regmap, 0x26, 0x40); /* enable */ else axp_regmap_clr_bits(axp259->regmap, 0x26, 0x40); /* disable */ /*Init PMU Over Temperature protection*/ if (axp259_config.pmu_hot_shutdown) axp_regmap_set_bits(axp259->regmap, 0xf3, 0x08); /* enable */ else axp_regmap_clr_bits(axp259->regmap, 0xf3, 0x08); /* disable */ axp_regmap_write(axp259->regmap, AXP259_BUFFER1, 0x00); /*enable SW_5V*/ axp_regmap_set_bits(axp259->regmap, 0x10, 0x02); /* enable */ /*dc voltage setting to default value when wakeup*/ axp_regmap_set_bits(axp259->regmap, 0x28, 0x20); /*disable restart pmic when pwrok drive low*/ axp_regmap_clr_bits(axp259->regmap, 0x29, 0x10); return 0; } static void axp259_wakeup_event(void) { __pm_wakeup_event(axp259_ws, 0); } static s32 axp259_usb_det(void) { return 0; } static s32 axp259_usb_vbus_output(int high) { u8 ret = 0; return ret; } static int axp259_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 (np == NULL) { 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 = axp259_pm_power->regmap->client->adapter->nr; api->pmu_arg.dev_addr = axp259_pm_power->regmap->client->addr; } #endif *pmu_id = axp259_config.pmu_id; return 0; } static const char *axp259_get_pmu_name(void) { return axp_name[axp259_pmu_num]; } static struct axp_dev *axp259_get_pmu_dev(void) { return axp259_pm_power; } struct axp_platform_ops axp259_platform_ops = { .usb_det = axp259_usb_det, .usb_vbus_output = axp259_usb_vbus_output, .cfg_pmux_para = axp259_cfg_pmux_para, .get_pmu_name = axp259_get_pmu_name, .get_pmu_dev = axp259_get_pmu_dev, }; static const struct i2c_device_id axp259_id_table[] = { { "axp259", 0 }, {} }; static const struct of_device_id axp259_dt_ids[] = { { .compatible = "axp259", }, {}, }; MODULE_DEVICE_TABLE(of, axp259_dt_ids); static int axp259_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; struct axp_dev *axp259; struct device_node *node = client->dev.of_node; axp259_pmu_num = axp_get_pmu_num(axp259_cn_mapping, ARRAY_SIZE(axp259_cn_mapping)); if (axp259_pmu_num < 0) { pr_err("%s get pmu num failed\n", __func__); return axp259_pmu_num; } if (node) { /* get dt and sysconfig */ if (!of_device_is_available(node)) { axp259_config.pmu_used = 0; pr_err("%s: pmu_used = %u\n", __func__, axp259_config.pmu_used); return -EPERM; } else { axp259_config.pmu_used = 1; ret = axp_dt_parse(node, axp259_pmu_num, &axp259_config); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } } } else { pr_err("AXP22x device tree err!\n"); return -EBUSY; } axp259 = devm_kzalloc(&client->dev, sizeof(*axp259), GFP_KERNEL); if (!axp259) return -ENOMEM; axp259->dev = &client->dev; axp259->nr_cells = ARRAY_SIZE(axp259_cells); axp259->cells = axp259_cells; axp259->pmu_num = axp259_pmu_num; ret = axp_mfd_cell_name_init(axp259_cn_mapping, ARRAY_SIZE(axp259_cn_mapping), axp259->pmu_num, axp259->nr_cells, axp259->cells); if (ret) return ret; axp259->regmap = axp_regmap_init_i2c(&client->dev); if (IS_ERR(axp259->regmap)) { ret = PTR_ERR(axp259->regmap); dev_err(&client->dev, "regmap init failed: %d\n", ret); return ret; } ret = axp259_init_chip(axp259); if (ret) return ret; ret = axp_mfd_add_devices(axp259); if (ret) { dev_err(axp259->dev, "failed to add MFD devices: %d\n", ret); return ret; } axp259->irq_data = axp_irq_chip_register(axp259->regmap, client->irq, IRQF_SHARED | IRQF_NO_SUSPEND, &axp259_regmap_irq_chip, axp259_wakeup_event); if (IS_ERR(axp259->irq_data)) { ret = PTR_ERR(axp259->irq_data); dev_err(&client->dev, "axp init irq failed: %d\n", ret); return ret; } axp259_pm_power = axp259; if (!pm_power_off) pm_power_off = axp259_power_off; axp_platform_ops_set(axp259->pmu_num, &axp259_platform_ops); axp259_ws = wakeup_source_register("axp259_wakeup_source"); return 0; } static int axp259_remove(struct i2c_client *client) { struct axp_dev *axp259 = i2c_get_clientdata(client); if (axp259 == axp259_pm_power) { axp259_pm_power = NULL; pm_power_off = NULL; } axp_mfd_remove_devices(axp259); axp_irq_chip_unregister(client->irq, axp259->irq_data); return 0; } /* * enable restart pmic when pwrok drive low */ static void axp259_shutdown(struct i2c_client *client) { axp_regmap_set_bits(axp259_pm_power->regmap, 0x29, 0x10); } static struct i2c_driver axp259_driver = { .driver = { .name = "axp259", .owner = THIS_MODULE, .of_match_table = axp259_dt_ids, }, .probe = axp259_probe, .remove = axp259_remove, .shutdown = axp259_shutdown, .id_table = axp259_id_table, }; static int __init axp259_i2c_init(void) { int ret; ret = i2c_add_driver(&axp259_driver); if (ret != 0) pr_err("Failed to register axp259 I2C driver: %d\n", ret); return ret; } subsys_initcall(axp259_i2c_init); static void __exit axp259_i2c_exit(void) { i2c_del_driver(&axp259_driver); } module_exit(axp259_i2c_exit); MODULE_DESCRIPTION("PMIC Driver for AXP259"); MODULE_AUTHOR("Qin "); MODULE_LICENSE("GPL");