linux/drivers/misc/qcom-coincell.c
<<
>>
Prefs
   1/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
   2 * Copyright (c) 2015, Sony Mobile Communications Inc.
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 and
   6 * only version 2 as published by the Free Software Foundation.
   7 *
   8 * This program is distributed in the hope that it will be useful,
   9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11 * GNU General Public License for more details.
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/slab.h>
  17#include <linux/of.h>
  18#include <linux/regmap.h>
  19#include <linux/of_device.h>
  20#include <linux/platform_device.h>
  21
  22struct qcom_coincell {
  23        struct device   *dev;
  24        struct regmap   *regmap;
  25        u32             base_addr;
  26};
  27
  28#define QCOM_COINCELL_REG_RSET          0x44
  29#define QCOM_COINCELL_REG_VSET          0x45
  30#define QCOM_COINCELL_REG_ENABLE        0x46
  31
  32#define QCOM_COINCELL_ENABLE            BIT(7)
  33
  34static const int qcom_rset_map[] = { 2100, 1700, 1200, 800 };
  35static const int qcom_vset_map[] = { 2500, 3200, 3100, 3000 };
  36/* NOTE: for pm8921 and others, voltage of 2500 is 16 (10000b), not 0 */
  37
  38/* if enable==0, rset and vset are ignored */
  39static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset,
  40                                     int vset, bool enable)
  41{
  42        int i, j, rc;
  43
  44        /* if disabling, just do that and skip other operations */
  45        if (!enable)
  46                return regmap_write(chgr->regmap,
  47                          chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0);
  48
  49        /* find index for current-limiting resistor */
  50        for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++)
  51                if (rset == qcom_rset_map[i])
  52                        break;
  53
  54        if (i >= ARRAY_SIZE(qcom_rset_map)) {
  55                dev_err(chgr->dev, "invalid rset-ohms value %d\n", rset);
  56                return -EINVAL;
  57        }
  58
  59        /* find index for charge voltage */
  60        for (j = 0; j < ARRAY_SIZE(qcom_vset_map); j++)
  61                if (vset == qcom_vset_map[j])
  62                        break;
  63
  64        if (j >= ARRAY_SIZE(qcom_vset_map)) {
  65                dev_err(chgr->dev, "invalid vset-millivolts value %d\n", vset);
  66                return -EINVAL;
  67        }
  68
  69        rc = regmap_write(chgr->regmap,
  70                          chgr->base_addr + QCOM_COINCELL_REG_RSET, i);
  71        if (rc) {
  72                /*
  73                 * This is mainly to flag a bad base_addr (reg) from dts.
  74                 * Other failures writing to the registers should be
  75                 * extremely rare, or indicative of problems that
  76                 * should be reported elsewhere (eg. spmi failure).
  77                 */
  78                dev_err(chgr->dev, "could not write to RSET register\n");
  79                return rc;
  80        }
  81
  82        rc = regmap_write(chgr->regmap,
  83                chgr->base_addr + QCOM_COINCELL_REG_VSET, j);
  84        if (rc)
  85                return rc;
  86
  87        /* set 'enable' register */
  88        return regmap_write(chgr->regmap,
  89                            chgr->base_addr + QCOM_COINCELL_REG_ENABLE,
  90                            QCOM_COINCELL_ENABLE);
  91}
  92
  93static int qcom_coincell_probe(struct platform_device *pdev)
  94{
  95        struct device_node *node = pdev->dev.of_node;
  96        struct qcom_coincell chgr;
  97        u32 rset = 0;
  98        u32 vset = 0;
  99        bool enable;
 100        int rc;
 101
 102        chgr.dev = &pdev->dev;
 103
 104        chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL);
 105        if (!chgr.regmap) {
 106                dev_err(chgr.dev, "Unable to get regmap\n");
 107                return -EINVAL;
 108        }
 109
 110        rc = of_property_read_u32(node, "reg", &chgr.base_addr);
 111        if (rc)
 112                return rc;
 113
 114        enable = !of_property_read_bool(node, "qcom,charger-disable");
 115
 116        if (enable) {
 117                rc = of_property_read_u32(node, "qcom,rset-ohms", &rset);
 118                if (rc) {
 119                        dev_err(chgr.dev,
 120                                "can't find 'qcom,rset-ohms' in DT block");
 121                        return rc;
 122                }
 123
 124                rc = of_property_read_u32(node, "qcom,vset-millivolts", &vset);
 125                if (rc) {
 126                        dev_err(chgr.dev,
 127                            "can't find 'qcom,vset-millivolts' in DT block");
 128                        return rc;
 129                }
 130        }
 131
 132        return qcom_coincell_chgr_config(&chgr, rset, vset, enable);
 133}
 134
 135static const struct of_device_id qcom_coincell_match_table[] = {
 136        { .compatible = "qcom,pm8941-coincell", },
 137        {}
 138};
 139
 140MODULE_DEVICE_TABLE(of, qcom_coincell_match_table);
 141
 142static struct platform_driver qcom_coincell_driver = {
 143        .driver = {
 144                .name           = "qcom-spmi-coincell",
 145                .of_match_table = qcom_coincell_match_table,
 146        },
 147        .probe          = qcom_coincell_probe,
 148};
 149
 150module_platform_driver(qcom_coincell_driver);
 151
 152MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver");
 153MODULE_LICENSE("GPL v2");
 154