Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /* Copyright (c) 2013, The Linux Foundation. All rights reserved.
0003  * Copyright (c) 2015, Sony Mobile Communications Inc.
0004  */
0005 
0006 #include <linux/kernel.h>
0007 #include <linux/module.h>
0008 #include <linux/slab.h>
0009 #include <linux/of.h>
0010 #include <linux/regmap.h>
0011 #include <linux/of_device.h>
0012 #include <linux/platform_device.h>
0013 
0014 struct qcom_coincell {
0015     struct device   *dev;
0016     struct regmap   *regmap;
0017     u32     base_addr;
0018 };
0019 
0020 #define QCOM_COINCELL_REG_RSET      0x44
0021 #define QCOM_COINCELL_REG_VSET      0x45
0022 #define QCOM_COINCELL_REG_ENABLE    0x46
0023 
0024 #define QCOM_COINCELL_ENABLE        BIT(7)
0025 
0026 static const int qcom_rset_map[] = { 2100, 1700, 1200, 800 };
0027 static const int qcom_vset_map[] = { 2500, 3200, 3100, 3000 };
0028 /* NOTE: for pm8921 and others, voltage of 2500 is 16 (10000b), not 0 */
0029 
0030 /* if enable==0, rset and vset are ignored */
0031 static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset,
0032                      int vset, bool enable)
0033 {
0034     int i, j, rc;
0035 
0036     /* if disabling, just do that and skip other operations */
0037     if (!enable)
0038         return regmap_write(chgr->regmap,
0039               chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0);
0040 
0041     /* find index for current-limiting resistor */
0042     for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++)
0043         if (rset == qcom_rset_map[i])
0044             break;
0045 
0046     if (i >= ARRAY_SIZE(qcom_rset_map)) {
0047         dev_err(chgr->dev, "invalid rset-ohms value %d\n", rset);
0048         return -EINVAL;
0049     }
0050 
0051     /* find index for charge voltage */
0052     for (j = 0; j < ARRAY_SIZE(qcom_vset_map); j++)
0053         if (vset == qcom_vset_map[j])
0054             break;
0055 
0056     if (j >= ARRAY_SIZE(qcom_vset_map)) {
0057         dev_err(chgr->dev, "invalid vset-millivolts value %d\n", vset);
0058         return -EINVAL;
0059     }
0060 
0061     rc = regmap_write(chgr->regmap,
0062               chgr->base_addr + QCOM_COINCELL_REG_RSET, i);
0063     if (rc) {
0064         /*
0065          * This is mainly to flag a bad base_addr (reg) from dts.
0066          * Other failures writing to the registers should be
0067          * extremely rare, or indicative of problems that
0068          * should be reported elsewhere (eg. spmi failure).
0069          */
0070         dev_err(chgr->dev, "could not write to RSET register\n");
0071         return rc;
0072     }
0073 
0074     rc = regmap_write(chgr->regmap,
0075         chgr->base_addr + QCOM_COINCELL_REG_VSET, j);
0076     if (rc)
0077         return rc;
0078 
0079     /* set 'enable' register */
0080     return regmap_write(chgr->regmap,
0081                 chgr->base_addr + QCOM_COINCELL_REG_ENABLE,
0082                 QCOM_COINCELL_ENABLE);
0083 }
0084 
0085 static int qcom_coincell_probe(struct platform_device *pdev)
0086 {
0087     struct device_node *node = pdev->dev.of_node;
0088     struct qcom_coincell chgr;
0089     u32 rset = 0;
0090     u32 vset = 0;
0091     bool enable;
0092     int rc;
0093 
0094     chgr.dev = &pdev->dev;
0095 
0096     chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL);
0097     if (!chgr.regmap) {
0098         dev_err(chgr.dev, "Unable to get regmap\n");
0099         return -EINVAL;
0100     }
0101 
0102     rc = of_property_read_u32(node, "reg", &chgr.base_addr);
0103     if (rc)
0104         return rc;
0105 
0106     enable = !of_property_read_bool(node, "qcom,charger-disable");
0107 
0108     if (enable) {
0109         rc = of_property_read_u32(node, "qcom,rset-ohms", &rset);
0110         if (rc) {
0111             dev_err(chgr.dev,
0112                 "can't find 'qcom,rset-ohms' in DT block");
0113             return rc;
0114         }
0115 
0116         rc = of_property_read_u32(node, "qcom,vset-millivolts", &vset);
0117         if (rc) {
0118             dev_err(chgr.dev,
0119                 "can't find 'qcom,vset-millivolts' in DT block");
0120             return rc;
0121         }
0122     }
0123 
0124     return qcom_coincell_chgr_config(&chgr, rset, vset, enable);
0125 }
0126 
0127 static const struct of_device_id qcom_coincell_match_table[] = {
0128     { .compatible = "qcom,pm8941-coincell", },
0129     {}
0130 };
0131 
0132 MODULE_DEVICE_TABLE(of, qcom_coincell_match_table);
0133 
0134 static struct platform_driver qcom_coincell_driver = {
0135     .driver = {
0136         .name       = "qcom-spmi-coincell",
0137         .of_match_table = qcom_coincell_match_table,
0138     },
0139     .probe      = qcom_coincell_probe,
0140 };
0141 
0142 module_platform_driver(qcom_coincell_driver);
0143 
0144 MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver");
0145 MODULE_LICENSE("GPL v2");