/* * drivers/power/axp/axp80x/axp80x.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * driver of axp80x * * 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 "../axp-core.h" #include "axp80x.h" static struct axp_dev *axp80x_pm_power; struct axp_config_info axp80x_config; struct wakeup_source *axp80x_ws; static int axp80x_pmu_num; static const struct axp_compatible_name_mapping axp80x_cn_mapping[] = { { .device_name = "axp806", .mfd_name = { .powerkey_name = "axp806-powerkey", .regulator_name = "axp806-regulator", .gpio_name = "axp806-gpio", }, }, { .device_name = "axp808", .mfd_name = { .powerkey_name = "axp808-powerkey", .regulator_name = "axp808-regulator", .gpio_name = "axp808-gpio", }, }, }; static struct axp_regmap_irq_chip axp80x_regmap_irq_chip = { .name = "axp80x_irq_chip", .status_base = AXP80X_INTSTS1, .enable_base = AXP80X_INTEN1, .num_regs = 2, }; static struct resource axp80x_pek_resources[] = { {AXP80X_IRQ_PEKRE, AXP80X_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,}, {AXP80X_IRQ_PEKFE, AXP80X_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,}, }; static struct mfd_cell axp80x_cells[] = { { .name = "axp80x-powerkey", .num_resources = ARRAY_SIZE(axp80x_pek_resources), .resources = axp80x_pek_resources, }, { .name = "axp80x-regulator", }, }; void axp80x_power_off(void) { pr_info("[axp] send power-off command!\n"); axp_regmap_set_bits(axp80x_pm_power->regmap, AXP80X_OFF_CTL, 0x80); mdelay(20); pr_warn("[axp] warning!!! axp can't power-off!\n"); } static int axp80x_init_chip(struct axp_dev *axp80x) { uint8_t chip_id; int err; err = axp_regmap_read(axp80x->regmap, AXP80X_IC_TYPE, &chip_id); if (err) { pr_err("[%s] try to read chip id failed!\n", axp_name[axp80x_pmu_num]); return err; } if (((((chip_id & 0xc0) >> 6) << 4) | (chip_id & 0xf)) == 0x10) pr_info("[%s] chip id detect 0x%x !\n", axp_name[axp80x_pmu_num], chip_id); else pr_info("[%s] chip id not detect 0x%x !\n", axp_name[axp80x_pmu_num], chip_id); /*init irq wakeup en*/ if (axp80x_config.pmu_irq_wakeup) axp_regmap_set_bits(axp80x->regmap, AXP80X_IFQ_PWROK_SET, 0x80); else axp_regmap_clr_bits(axp80x->regmap, AXP80X_IFQ_PWROK_SET, 0x80); /*init pmu over temperature protection*/ if (axp80x_config.pmu_hot_shutdown) axp_regmap_set_bits(axp80x->regmap, AXP80X_OFF_CTL, 0x02); else axp_regmap_clr_bits(axp80x->regmap, AXP80X_OFF_CTL, 0x02); return 0; } static void axp80x_wakeup_event(void) { __pm_wakeup_event(axp80x_ws, 0); } static s32 axp80x_usb_det(void) { return 0; } static s32 axp80x_usb_vbus_output(int high) { return 0; } static int axp80x_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; } if (api != NULL) { #ifdef CONFIG_AXP_TWI_USED api->pmu_arg.twi_port = axp80x_pm_power->regmap->client->adapter->nr; api->pmu_arg.dev_addr = axp80x_pm_power->regmap->client->addr; #else api->pmu_arg.twi_port = 0; api->pmu_arg.dev_addr = 0x3; #endif } *pmu_id = axp80x_config.pmu_id; return 0; } static const char *axp80x_get_pmu_name(void) { return axp_name[axp80x_pmu_num]; } static struct axp_dev *axp80x_get_pmu_dev(void) { return axp80x_pm_power; } static struct axp_platform_ops axp80x_platform_ops = { .usb_det = axp80x_usb_det, .usb_vbus_output = axp80x_usb_vbus_output, .cfg_pmux_para = axp80x_cfg_pmux_para, .get_pmu_name = axp80x_get_pmu_name, .get_pmu_dev = axp80x_get_pmu_dev, }; static int axp80x_probe(struct platform_device *pdev) { int ret; struct axp_dev *axp80x; struct device_node *node = pdev->dev.of_node; axp80x_pmu_num = axp_get_pmu_num(axp80x_cn_mapping, ARRAY_SIZE(axp80x_cn_mapping)); if (axp80x_pmu_num < 0) { pr_err("%s get pmu num failed\n", __func__); return axp80x_pmu_num; } if (!node) { pr_err("AXP80x device tree err!\n"); return -EBUSY; } /* get dt and sysconfig */ if (!of_device_is_available(node)) { axp80x_config.pmu_used = 0; pr_err("%s: pmu_used = %u\n", __func__, axp80x_config.pmu_used); return -EPERM; } axp80x_config.pmu_used = 1; ret = axp_dt_parse(node, axp80x_pmu_num, &axp80x_config); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } axp80x = devm_kzalloc(&pdev->dev, sizeof(*axp80x), GFP_KERNEL); if (!axp80x) return -ENOMEM; axp80x->dev = &pdev->dev; axp80x->nr_cells = ARRAY_SIZE(axp80x_cells); axp80x->cells = axp80x_cells; axp80x->pmu_num = axp80x_pmu_num; ret = axp_mfd_cell_name_init(axp80x_cn_mapping, ARRAY_SIZE(axp80x_cn_mapping), axp80x->pmu_num, axp80x->nr_cells, axp80x->cells); if (ret) return ret; axp80x->regmap = axp_regmap_init_arisc_twi(&pdev->dev); if (IS_ERR(axp80x->regmap)) { ret = PTR_ERR(axp80x->regmap); dev_err(&pdev->dev, "regmap init failed: %d\n", ret); return ret; } platform_set_drvdata(pdev, axp80x); ret = axp80x_init_chip(axp80x); if (ret) return ret; ret = axp_mfd_add_devices(axp80x); if (ret) { dev_err(axp80x->dev, "failed to add MFD devices: %d\n", ret); return ret; } axp80x->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); axp80x->irq_data = axp_irq_chip_register(axp80x->regmap, axp80x->irq, IRQF_SHARED | IRQF_NO_SUSPEND, &axp80x_regmap_irq_chip, axp80x_wakeup_event); if (IS_ERR(axp80x->irq_data)) { ret = PTR_ERR(axp80x->irq_data); dev_err(&pdev->dev, "axp init irq failed: %d\n", ret); return ret; } axp80x_pm_power = axp80x; if (!pm_power_off) pm_power_off = axp80x_power_off; axp_platform_ops_set(axp80x->pmu_num, &axp80x_platform_ops); axp80x_ws = wakeup_source_register("axp80x_wakeup_source"); return 0; } static int axp80x_remove(struct platform_device *pdev) { struct axp_dev *axp80x = platform_get_drvdata(pdev); if (axp80x == axp80x_pm_power) { axp80x_pm_power = NULL; pm_power_off = NULL; } axp_mfd_remove_devices(axp80x); axp_irq_chip_unregister(axp80x->irq, axp80x->irq_data); return 0; } static const struct of_device_id axp80x_dt_ids[] = { { .compatible = "axp806", }, { .compatible = "axp808", }, {}, }; MODULE_DEVICE_TABLE(of, axp80x_dt_ids); static struct platform_driver axp80x_driver = { .driver = { .name = "axp80x", .owner = THIS_MODULE, .of_match_table = axp80x_dt_ids, }, .probe = axp80x_probe, .remove = axp80x_remove, }; static int __init axp80x_init(void) { int ret; ret = platform_driver_register(&axp80x_driver); if (ret != 0) pr_err("Failed to register axp80x driver: %d\n", ret); return ret; } subsys_initcall(axp80x_init); static void __exit axp80x_exit(void) { platform_driver_unregister(&axp80x_driver); } module_exit(axp80x_exit); MODULE_DESCRIPTION("Driver of AXP80X"); MODULE_AUTHOR("pannan"); MODULE_LICENSE("GPL");