/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #define REVID_REVISION1 0x0 #define REVID_REVISION2 0x1 #define REVID_REVISION3 0x2 #define REVID_REVISION4 0x3 #define REVID_TYPE 0x4 #define REVID_SUBTYPE 0x5 #define REVID_STATUS1 0x8 #define REVID_SPARE_0 0x60 #define REVID_TP_REV 0xf1 #define REVID_FAB_ID 0xf2 #define QPNP_REVID_DEV_NAME "qcom,qpnp-revid" static const char *const pmic_names[] = { [0] = "Unknown PMIC", [PM8941_SUBTYPE] = "PM8941", [PM8841_SUBTYPE] = "PM8841", [PM8019_SUBTYPE] = "PM8019", [PM8226_SUBTYPE] = "PM8226", [PM8110_SUBTYPE] = "PM8110", [PMA8084_SUBTYPE] = "PMA8084", [PMI8962_SUBTYPE] = "PMI8962", [PMD9635_SUBTYPE] = "PMD9635", [PM8994_SUBTYPE] = "PM8994", [PMI8994_SUBTYPE] = "PMI8994", [PM8916_SUBTYPE] = "PM8916", [PM8004_SUBTYPE] = "PM8004", [PM8909_SUBTYPE] = "PM8909", [PM2433_SUBTYPE] = "PM2433", [PMD9655_SUBTYPE] = "PMD9655", [PM8950_SUBTYPE] = "PM8950", [PMI8950_SUBTYPE] = "PMI8950", [PMK8001_SUBTYPE] = "PMK8001", [PMI8996_SUBTYPE] = "PMI8996", [PM8998_SUBTYPE] = "PM8998", [PMI8998_SUBTYPE] = "PMI8998", [PM8005_SUBTYPE] = "PM8005", [PM8937_SUBTYPE] = "PM8937", [PM660L_SUBTYPE] = "PM660L", [PM660_SUBTYPE] = "PM660", [PMI8937_SUBTYPE] = "PMI8937", }; struct revid_chip { struct list_head link; struct device_node *dev_node; struct pmic_revid_data data; }; static LIST_HEAD(revid_chips); static DEFINE_MUTEX(revid_chips_lock); static const struct of_device_id qpnp_revid_match_table[] = { { .compatible = QPNP_REVID_DEV_NAME }, {} }; static u8 qpnp_read_byte(struct regmap *regmap, u16 addr) { int rc; int val; rc = regmap_read(regmap, addr, &val); if (rc) { pr_err("read failed rc=%d\n", rc); return 0; } return (u8)val; } /** * get_revid_data - Return the revision information of PMIC * @dev_node: Pointer to the revid peripheral of the PMIC for which * revision information is seeked * * CONTEXT: Should be called in non atomic context * * RETURNS: pointer to struct pmic_revid_data filled with the information * about the PMIC revision */ struct pmic_revid_data *get_revid_data(struct device_node *dev_node) { struct revid_chip *revid_chip; if (!dev_node) return ERR_PTR(-EINVAL); mutex_lock(&revid_chips_lock); list_for_each_entry(revid_chip, &revid_chips, link) { if (dev_node == revid_chip->dev_node) { mutex_unlock(&revid_chips_lock); return &revid_chip->data; } } mutex_unlock(&revid_chips_lock); return ERR_PTR(-EINVAL); } EXPORT_SYMBOL(get_revid_data); #define PM8941_PERIPHERAL_SUBTYPE 0x01 #define PM8226_PERIPHERAL_SUBTYPE 0x04 #define PMD9655_PERIPHERAL_SUBTYPE 0x0F #define PMI8950_PERIPHERAL_SUBTYPE 0x11 #define PMI8937_PERIPHERAL_SUBTYPE 0x37 static size_t build_pmic_string(char *buf, size_t n, int sid, u8 subtype, u8 rev1, u8 rev2, u8 rev3, u8 rev4) { size_t pos = 0; /* * In early versions of PM8941 and PM8226, the major revision number * started incrementing from 0 (eg 0 = v1.0, 1 = v2.0). * Increment the major revision number here if the chip is an early * version of PM8941 or PM8226. */ if (((int)subtype == PM8941_PERIPHERAL_SUBTYPE || (int)subtype == PM8226_PERIPHERAL_SUBTYPE) && rev4 < 0x02) rev4++; pos += snprintf(buf + pos, n - pos, "PMIC@SID%d", sid); if (subtype >= ARRAY_SIZE(pmic_names) || subtype == 0) pos += snprintf(buf + pos, n - pos, ": %s (subtype: 0x%02X)", pmic_names[0], subtype); else pos += snprintf(buf + pos, n - pos, ": %s", pmic_names[subtype]); pos += snprintf(buf + pos, n - pos, " v%d.%d", rev4, rev3); if (rev2 || rev1) pos += snprintf(buf + pos, n - pos, ".%d", rev2); if (rev1) pos += snprintf(buf + pos, n - pos, ".%d", rev1); return pos; } #define PMIC_PERIPHERAL_TYPE 0x51 #define PMIC_STRING_MAXLENGTH 80 static int qpnp_revid_probe(struct platform_device *pdev) { u8 rev1, rev2, rev3, rev4, pmic_type, pmic_subtype, pmic_status; u8 option1, option2, option3, option4, spare0; unsigned int base; int rc, fab_id, tp_rev; char pmic_string[PMIC_STRING_MAXLENGTH] = {'\0'}; struct revid_chip *revid_chip; struct regmap *regmap; regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!regmap) { dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); return -EINVAL; } rc = of_property_read_u32(pdev->dev.of_node, "reg", &base); if (rc < 0) { dev_err(&pdev->dev, "Couldn't find reg in node = %s rc = %d\n", pdev->dev.of_node->full_name, rc); return rc; } pmic_type = qpnp_read_byte(regmap, base + REVID_TYPE); if (pmic_type != PMIC_PERIPHERAL_TYPE) { pr_err("Invalid REVID peripheral type: %02X\n", pmic_type); return -EINVAL; } rev1 = qpnp_read_byte(regmap, base + REVID_REVISION1); rev2 = qpnp_read_byte(regmap, base + REVID_REVISION2); rev3 = qpnp_read_byte(regmap, base + REVID_REVISION3); rev4 = qpnp_read_byte(regmap, base + REVID_REVISION4); pmic_subtype = qpnp_read_byte(regmap, base + REVID_SUBTYPE); if (pmic_subtype != PMD9655_PERIPHERAL_SUBTYPE) pmic_status = qpnp_read_byte(regmap, base + REVID_STATUS1); else pmic_status = 0; /* special case for PMI8937 */ if (pmic_subtype == PMI8950_PERIPHERAL_SUBTYPE) { /* read spare register */ spare0 = qpnp_read_byte(regmap, base + REVID_SPARE_0); if (spare0) pmic_subtype = PMI8937_PERIPHERAL_SUBTYPE; } if (of_property_read_bool(pdev->dev.of_node, "qcom,fab-id-valid")) fab_id = qpnp_read_byte(regmap, base + REVID_FAB_ID); else fab_id = -EINVAL; if (of_property_read_bool(pdev->dev.of_node, "qcom,tp-rev-valid")) tp_rev = qpnp_read_byte(regmap, base + REVID_TP_REV); else tp_rev = -EINVAL; revid_chip = devm_kzalloc(&pdev->dev, sizeof(struct revid_chip), GFP_KERNEL); if (!revid_chip) return -ENOMEM; revid_chip->dev_node = pdev->dev.of_node; revid_chip->data.rev1 = rev1; revid_chip->data.rev2 = rev2; revid_chip->data.rev3 = rev3; revid_chip->data.rev4 = rev4; revid_chip->data.pmic_subtype = pmic_subtype; revid_chip->data.pmic_type = pmic_type; revid_chip->data.fab_id = fab_id; revid_chip->data.tp_rev = tp_rev; if (pmic_subtype < ARRAY_SIZE(pmic_names)) revid_chip->data.pmic_name = pmic_names[pmic_subtype]; else revid_chip->data.pmic_name = pmic_names[0]; mutex_lock(&revid_chips_lock); list_add(&revid_chip->link, &revid_chips); mutex_unlock(&revid_chips_lock); option1 = pmic_status & 0x3; option2 = (pmic_status >> 2) & 0x3; option3 = (pmic_status >> 4) & 0x3; option4 = (pmic_status >> 6) & 0x3; build_pmic_string(pmic_string, PMIC_STRING_MAXLENGTH, to_spmi_device(pdev->dev.parent)->usid, pmic_subtype, rev1, rev2, rev3, rev4); pr_info("%s options: %d, %d, %d, %d\n", pmic_string, option1, option2, option3, option4); return 0; } static struct platform_driver qpnp_revid_driver = { .probe = qpnp_revid_probe, .driver = { .name = QPNP_REVID_DEV_NAME, .owner = THIS_MODULE, .of_match_table = qpnp_revid_match_table, }, }; static int __init qpnp_revid_init(void) { return platform_driver_register(&qpnp_revid_driver); } static void __exit qpnp_revid_exit(void) { return platform_driver_unregister(&qpnp_revid_driver); } subsys_initcall(qpnp_revid_init); module_exit(qpnp_revid_exit); MODULE_DESCRIPTION("QPNP REVID DRIVER"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" QPNP_REVID_DEV_NAME);