/* * Regulators driver for allwinnertech AXP15X * * Copyright (C) 2014 allwinnertech Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include "../axp-core.h" #include "../axp-regulator.h" #include "axp15.h" #include "axp15-regu.h" /* Reverse engineered partly from Platformx drivers */ enum axp_regls { VCC_DCDC1, VCC_DCDC2, VCC_DCDC3, VCC_DCDC4, VCC_LDO1, VCC_LDO2, VCC_LDO3, VCC_LDO4, VCC_LDO5, VCC_LDO6, VCC_LDO7, VCC_15_MAX, }; struct axp15_regulators { struct regulator_dev *regulators[VCC_15_MAX]; struct axp_dev *chip; }; static const int axp15_ldo0_table[] = { 5000, 3300, 2800, 2500 }; static const int axp15_dcdc1_table[] = { 1700, 1800, 1900, 2000, 2100, 2400, 2500, 2600, 2700, 2800, 3000, 3100, 3200, 3300, 3400, 3500 }; static const int axp15_aldo12_table[] = { 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2500, 2700, 2800, 3000, 3100, 3200, 3300 }; #define AXP15_LDO(_id, min, max, step1, vreg, shift, nbits,\ ereg, emask, enval, disval, switch_vol, step2, new_level,\ mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag)\ AXP_LDO(AXP15, _id, min, max, step1, vreg, shift, nbits,\ ereg, emask, enval, disval, switch_vol, step2, new_level,\ mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) #define AXP15_LDO_SEL(_id, min, max, vreg, shift, nbits,\ ereg, emask, enval, disval, vtable,\ mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \ AXP_LDO_SEL(AXP15, _id, min, max, vreg, shift, nbits,\ ereg, emask, enval, disval, vtable,\ mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) #define AXP15_DCDC(_id, min, max, step1, vreg, shift, nbits,\ ereg, emask, enval, disval, switch_vol, step2, new_level,\ mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \ AXP_DCDC(AXP15, _id, min, max, step1, vreg, shift, nbits,\ ereg, emask, enval, disval, switch_vol, step2, new_level,\ mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) #define AXP15_DCDC_SEL(_id, min, max, vreg, shift, nbits,\ ereg, emask, enval, disval, vtable,\ mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \ AXP_DCDC_SEL(AXP15, _id, min, max, vreg, shift, nbits,\ ereg, emask, enval, disval, vtable,\ mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) static struct axp_regulator_info axp15_regulator_info[] = { AXP15_DCDC_SEL(1, 1700, 3500, DCDC1, 0, 4, DCDC1EN, 0x80, 0x80, 0, axp15_dcdc1, 0x80, 0x08, 0x37, 0, 0, 0), AXP15_DCDC(2, 700, 2275, 25, DCDC2, 0, 6, DCDC2EN, 0x40, 0x40, 0, 0, 0, 0, 0x80, 0x04, 0x37, 0x25, 2, 0), AXP15_DCDC(3, 700, 3500, 50, DCDC3, 0, 6, DCDC3EN, 0x20, 0x20, 0, 0, 0, 0, 0x80, 0x02, 0x37, 0, 0, 0), AXP15_DCDC(4, 700, 3500, 25, DCDC4, 0, 7, DCDC4EN, 0x10, 0x10, 0, 0, 0, 0, 0x80, 0x01, 0x37, 0, 0, 0), AXP15_LDO_SEL(0, 2500, 5000, LDO0, 4, 2, LDO0EN, 0x80, 0x80, 0, axp15_ldo0, 0, 0, 0, 0, 0), AXP15_LDO(1, 3100, 3100, 0, RTC, 0, 0, RTCLDOEN, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0), AXP15_LDO_SEL(2, 1200, 3300, ALDO1, 4, 4, ALDO1EN, 0x08, 0x08, 0, axp15_aldo12, 0, 0, 0, 0, 0), AXP15_LDO_SEL(3, 1200, 3300, ALDO2, 0, 4, ALDO2EN, 0x04, 0x04, 0, axp15_aldo12, 0, 0, 0, 0, 0), AXP15_LDO(4, 700, 3500, 100, DLDO1, 0, 5, DLDO1EN, 0x02, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0), AXP15_LDO(5, 700, 3500, 100, DLDO2, 0, 5, DLDO2EN, 0x01, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0), AXP15_LDO(IO0, 1800, 3300, 100, LDOIO0, 0, 4, LDOI0EN, 0x07, 0x02, 0x07, 0, 0, 0, 0, 0, 0, 0, 0), }; static struct regulator_init_data axp_regl_init_data[] = { [VCC_DCDC1] = { .constraints = { .name = "axp15_dcdc1", .min_uV = 1700000, .max_uV = 3500000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_DCDC2] = { .constraints = { .name = "axp15_dcdc2", .min_uV = 700000, .max_uV = 2275000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_DCDC3] = { .constraints = { .name = "axp15_dcdc3", .min_uV = 700000, .max_uV = 3500000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_DCDC4] = { .constraints = { .name = "axp15_dcdc4", .min_uV = 700000, .max_uV = 3500000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO1] = { .constraints = { .name = "axp15_ldo0", .min_uV = 2500000, .max_uV = 5000000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO2] = { .constraints = { .name = "axp15_rtc", .min_uV = 3100000, .max_uV = 3100000, }, }, [VCC_LDO3] = { .constraints = { .name = "axp15_aldo1", .min_uV = 1200000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO4] = { .constraints = { .name = "axp15_aldo2", .min_uV = 1200000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO5] = { .constraints = { .name = "axp15_dldo1", .min_uV = 700000, .max_uV = 3500000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO6] = { .constraints = { .name = "axp15_dldo2", .min_uV = 700000, .max_uV = 3500000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, [VCC_LDO7] = { .constraints = { .name = "axp15_gpioldo", .min_uV = 1800000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, }, }, }; static ssize_t workmode_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = axp_regmap_read(regmap, info->mode_reg, &val); if (ret) return sprintf(buf, "IO ERROR\n"); if ((val & info->mode_mask) == info->mode_mask) return sprintf(buf, "PWM\n"); else return sprintf(buf, "AUTO\n"); } static ssize_t workmode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; unsigned int mode; int ret; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = sscanf(buf, "%u", &mode); if (ret != 1) return -EINVAL; val = !!mode; if (val) axp_regmap_set_bits(regmap, info->mode_reg, info->mode_mask); else axp_regmap_clr_bits(regmap, info->mode_reg, info->mode_mask); return count; } static ssize_t frequency_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; uint8_t val; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; ret = axp_regmap_read(regmap, info->freq_reg, &val); if (ret) return ret; ret = val & 0x0F; return sprintf(buf, "%d\n", (ret * 5 + 50)); } static ssize_t frequency_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint8_t val, tmp; int var, err; struct regulator_dev *rdev; struct axp_regulator_info *info; struct axp_regmap *regmap; rdev = container_of(dev, struct regulator_dev, dev); info = rdev_get_drvdata(rdev); regmap = info->regmap; err = kstrtoint(buf, 10, &var); if (err) return err; if (var < 50) var = 50; if (var > 100) var = 100; val = (var - 50) / 5; val &= 0x0F; axp_regmap_read(regmap, info->freq_reg, &tmp); tmp &= 0xF0; val |= tmp; axp_regmap_write(regmap, info->freq_reg, val); return count; } static struct device_attribute axp_regu_attrs[] = { AXP_REGU_ATTR(workmode), AXP_REGU_ATTR(frequency), }; static int axp_regu_create_attrs(struct device *dev) { int j, ret; for (j = 0; j < ARRAY_SIZE(axp_regu_attrs); j++) { ret = device_create_file(dev, &axp_regu_attrs[j]); if (ret) goto sysfs_failed; } return 0; sysfs_failed: while (j--) device_remove_file(dev, &axp_regu_attrs[j]); return ret; } static s32 axp15_regu_dependence(const char *ldo_name) { s32 axp15_dependence = 0; if (strstr(ldo_name, "dcdc1") != NULL) axp15_dependence |= AXP15X_DCDC1; else if (strstr(ldo_name, "dcdc2") != NULL) axp15_dependence |= AXP15X_DCDC2; else if (strstr(ldo_name, "dcdc3") != NULL) axp15_dependence |= AXP15X_DCDC3; else if (strstr(ldo_name, "dcdc4") != NULL) axp15_dependence |= AXP15X_DCDC4; else if (strstr(ldo_name, "aldo1") != NULL) axp15_dependence |= AXP15X_ALDO1; else if (strstr(ldo_name, "aldo2") != NULL) axp15_dependence |= AXP15X_ALDO2; else if (strstr(ldo_name, "dldo1") != NULL) axp15_dependence |= AXP15X_DLDO1; else if (strstr(ldo_name, "dldo2") != NULL) axp15_dependence |= AXP15X_DLDO2; else if (strstr(ldo_name, "ldoio0") != NULL) axp15_dependence |= AXP15X_LDOIO0; else if (strstr(ldo_name, "ldo0") != NULL) axp15_dependence |= AXP15X_LDO0; else if (strstr(ldo_name, "rtc") != NULL) axp15_dependence |= AXP15X_RTC; else return -1; return axp15_dependence; } static int axp15_regulator_probe(struct platform_device *pdev) { s32 i, ret = 0; struct axp_regulator_info *info; struct axp15_regulators *regu_data; struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); if (pdev->dev.of_node) { ret = axp_regulator_dt_parse(pdev->dev.of_node, axp_regl_init_data, axp15_regu_dependence); if (ret) { pr_err("%s parse device tree err\n", __func__); return -EINVAL; } } else { pr_err("axp15 regulator device tree err!\n"); return -EBUSY; } regu_data = devm_kzalloc(&pdev->dev, sizeof(*regu_data), GFP_KERNEL); if (!regu_data) return -ENOMEM; regu_data->chip = axp_dev; platform_set_drvdata(pdev, regu_data); for (i = 0; i < VCC_15_MAX; i++) { info = &axp15_regulator_info[i]; info->pmu_num = axp_dev->pmu_num; if (info->desc.id == AXP15_ID_LDO4 || info->desc.id == AXP15_ID_LDO5 || info->desc.id == AXP15_ID_DCDC2 || info->desc.id == AXP15_ID_DCDC3 || info->desc.id == AXP15_ID_DCDC4 || info->desc.id == AXP15_ID_LDO1 || info->desc.id == AXP15_ID_LDOIO0) { regu_data->regulators[i] = axp_regulator_register( &pdev->dev, axp_dev->regmap, &axp_regl_init_data[i], info); } else if (info->desc.id == AXP15_ID_DCDC1 || info->desc.id == AXP15_ID_LDO0 || info->desc.id == AXP15_ID_LDO3 || info->desc.id == AXP15_ID_LDO2) { regu_data->regulators[i] = axp_regulator_sel_register (&pdev->dev, axp_dev->regmap, &axp_regl_init_data[i], info); } if (IS_ERR(regu_data->regulators[i])) { dev_err(&pdev->dev, "failed to register regulator %s\n", info->desc.name); while (--i >= 0) axp_regulator_unregister( regu_data->regulators[i]); return -1; } if (info->desc.id >= AXP_DCDC_ID_START) { ret = axp_regu_create_attrs( ®u_data->regulators[i]->dev); if (ret) dev_err(&pdev->dev, "failed to register regulator attr %s\n", info->desc.name); } } return 0; } static int axp15_regulator_remove(struct platform_device *pdev) { struct axp15_regulators *regu_data = platform_get_drvdata(pdev); int i; for (i = 0; i < VCC_15_MAX; i++) regulator_unregister(regu_data->regulators[i]); return 0; } static int axp15_regulator_suspend(struct platform_device *pdev, pm_message_t state) { struct axp15_regulators *regu_data = platform_get_drvdata(pdev); struct axp_dev *axp15 = regu_data->chip; /* disable dcdc2 dvm */ axp_regmap_clr_bits(axp15->regmap, AXP15_DCDC2_DVM_CTRL, 0x04); return 0; } static int axp15_regulator_resume(struct platform_device *pdev) { struct axp15_regulators *regu_data = platform_get_drvdata(pdev); struct axp_dev *axp15 = regu_data->chip; /* enable dcdc2 dvm */ axp_regmap_set_bits(axp15->regmap, AXP15_DCDC2_DVM_CTRL, 0x04); return 0; } static const struct of_device_id axp15_regu_dt_ids[] = { { .compatible = "axp157-regulator", }, {}, }; MODULE_DEVICE_TABLE(of, axp15_regu_dt_ids); static struct platform_driver axp15_regulator_driver = { .driver = { .name = "axp15-regulator", .of_match_table = axp15_regu_dt_ids, }, .probe = axp15_regulator_probe, .remove = axp15_regulator_remove, .suspend = axp15_regulator_suspend, .resume = axp15_regulator_resume, }; static int __init axp15_regulator_initcall(void) { int ret; ret = platform_driver_register(&axp15_regulator_driver); if (IS_ERR_VALUE(ret)) { pr_err("%s: failed, errno %d\n", __func__, ret); return -EINVAL; } return 0; } subsys_initcall(axp15_regulator_initcall); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Qin "); MODULE_DESCRIPTION("Regulator Driver for axp15 PMIC"); MODULE_ALIAS("platform:axp15-regulator");