18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* Copyright (c) 2013, The Linux Foundation. All rights reserved. 38c2ecf20Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications Inc. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/kernel.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci#include <linux/of.h> 108c2ecf20Sopenharmony_ci#include <linux/regmap.h> 118c2ecf20Sopenharmony_ci#include <linux/of_device.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistruct qcom_coincell { 158c2ecf20Sopenharmony_ci struct device *dev; 168c2ecf20Sopenharmony_ci struct regmap *regmap; 178c2ecf20Sopenharmony_ci u32 base_addr; 188c2ecf20Sopenharmony_ci}; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define QCOM_COINCELL_REG_RSET 0x44 218c2ecf20Sopenharmony_ci#define QCOM_COINCELL_REG_VSET 0x45 228c2ecf20Sopenharmony_ci#define QCOM_COINCELL_REG_ENABLE 0x46 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define QCOM_COINCELL_ENABLE BIT(7) 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic const int qcom_rset_map[] = { 2100, 1700, 1200, 800 }; 278c2ecf20Sopenharmony_cistatic const int qcom_vset_map[] = { 2500, 3200, 3100, 3000 }; 288c2ecf20Sopenharmony_ci/* NOTE: for pm8921 and others, voltage of 2500 is 16 (10000b), not 0 */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* if enable==0, rset and vset are ignored */ 318c2ecf20Sopenharmony_cistatic int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, 328c2ecf20Sopenharmony_ci int vset, bool enable) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci int i, j, rc; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci /* if disabling, just do that and skip other operations */ 378c2ecf20Sopenharmony_ci if (!enable) 388c2ecf20Sopenharmony_ci return regmap_write(chgr->regmap, 398c2ecf20Sopenharmony_ci chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci /* find index for current-limiting resistor */ 428c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++) 438c2ecf20Sopenharmony_ci if (rset == qcom_rset_map[i]) 448c2ecf20Sopenharmony_ci break; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (i >= ARRAY_SIZE(qcom_rset_map)) { 478c2ecf20Sopenharmony_ci dev_err(chgr->dev, "invalid rset-ohms value %d\n", rset); 488c2ecf20Sopenharmony_ci return -EINVAL; 498c2ecf20Sopenharmony_ci } 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci /* find index for charge voltage */ 528c2ecf20Sopenharmony_ci for (j = 0; j < ARRAY_SIZE(qcom_vset_map); j++) 538c2ecf20Sopenharmony_ci if (vset == qcom_vset_map[j]) 548c2ecf20Sopenharmony_ci break; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (j >= ARRAY_SIZE(qcom_vset_map)) { 578c2ecf20Sopenharmony_ci dev_err(chgr->dev, "invalid vset-millivolts value %d\n", vset); 588c2ecf20Sopenharmony_ci return -EINVAL; 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci rc = regmap_write(chgr->regmap, 628c2ecf20Sopenharmony_ci chgr->base_addr + QCOM_COINCELL_REG_RSET, i); 638c2ecf20Sopenharmony_ci if (rc) { 648c2ecf20Sopenharmony_ci /* 658c2ecf20Sopenharmony_ci * This is mainly to flag a bad base_addr (reg) from dts. 668c2ecf20Sopenharmony_ci * Other failures writing to the registers should be 678c2ecf20Sopenharmony_ci * extremely rare, or indicative of problems that 688c2ecf20Sopenharmony_ci * should be reported elsewhere (eg. spmi failure). 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci dev_err(chgr->dev, "could not write to RSET register\n"); 718c2ecf20Sopenharmony_ci return rc; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci rc = regmap_write(chgr->regmap, 758c2ecf20Sopenharmony_ci chgr->base_addr + QCOM_COINCELL_REG_VSET, j); 768c2ecf20Sopenharmony_ci if (rc) 778c2ecf20Sopenharmony_ci return rc; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* set 'enable' register */ 808c2ecf20Sopenharmony_ci return regmap_write(chgr->regmap, 818c2ecf20Sopenharmony_ci chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 828c2ecf20Sopenharmony_ci QCOM_COINCELL_ENABLE); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int qcom_coincell_probe(struct platform_device *pdev) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 888c2ecf20Sopenharmony_ci struct qcom_coincell chgr; 898c2ecf20Sopenharmony_ci u32 rset = 0; 908c2ecf20Sopenharmony_ci u32 vset = 0; 918c2ecf20Sopenharmony_ci bool enable; 928c2ecf20Sopenharmony_ci int rc; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci chgr.dev = &pdev->dev; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL); 978c2ecf20Sopenharmony_ci if (!chgr.regmap) { 988c2ecf20Sopenharmony_ci dev_err(chgr.dev, "Unable to get regmap\n"); 998c2ecf20Sopenharmony_ci return -EINVAL; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci rc = of_property_read_u32(node, "reg", &chgr.base_addr); 1038c2ecf20Sopenharmony_ci if (rc) 1048c2ecf20Sopenharmony_ci return rc; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci enable = !of_property_read_bool(node, "qcom,charger-disable"); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (enable) { 1098c2ecf20Sopenharmony_ci rc = of_property_read_u32(node, "qcom,rset-ohms", &rset); 1108c2ecf20Sopenharmony_ci if (rc) { 1118c2ecf20Sopenharmony_ci dev_err(chgr.dev, 1128c2ecf20Sopenharmony_ci "can't find 'qcom,rset-ohms' in DT block"); 1138c2ecf20Sopenharmony_ci return rc; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci rc = of_property_read_u32(node, "qcom,vset-millivolts", &vset); 1178c2ecf20Sopenharmony_ci if (rc) { 1188c2ecf20Sopenharmony_ci dev_err(chgr.dev, 1198c2ecf20Sopenharmony_ci "can't find 'qcom,vset-millivolts' in DT block"); 1208c2ecf20Sopenharmony_ci return rc; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci return qcom_coincell_chgr_config(&chgr, rset, vset, enable); 1258c2ecf20Sopenharmony_ci} 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic const struct of_device_id qcom_coincell_match_table[] = { 1288c2ecf20Sopenharmony_ci { .compatible = "qcom,pm8941-coincell", }, 1298c2ecf20Sopenharmony_ci {} 1308c2ecf20Sopenharmony_ci}; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_coincell_match_table); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic struct platform_driver qcom_coincell_driver = { 1358c2ecf20Sopenharmony_ci .driver = { 1368c2ecf20Sopenharmony_ci .name = "qcom-spmi-coincell", 1378c2ecf20Sopenharmony_ci .of_match_table = qcom_coincell_match_table, 1388c2ecf20Sopenharmony_ci }, 1398c2ecf20Sopenharmony_ci .probe = qcom_coincell_probe, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cimodule_platform_driver(qcom_coincell_driver); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver"); 1458c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 146