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, vset;
  98        bool enable;
  99        int rc;
 100
 101        chgr.dev = &pdev->dev;
 102
 103        chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL);
 104        if (!chgr.regmap) {
 105                dev_err(chgr.dev, "Unable to get regmap\n");
 106                return -EINVAL;
 107        }
 108
 109        rc = of_property_read_u32(node, "reg", &chgr.base_addr);
 110        if (rc)
 111                return rc;
 112
 113        enable = !of_property_read_bool(node, "qcom,charger-disable");
 114
 115        if (enable) {
 116                rc = of_property_read_u32(node, "qcom,rset-ohms", &rset);
 117                if (rc) {
 118                        dev_err(chgr.dev,
 119                                "can't find 'qcom,rset-ohms' in DT block");
 120                        return rc;
 121                }
 122
 123                rc = of_property_read_u32(node, "qcom,vset-millivolts", &vset);
 124                if (rc) {
 125                        dev_err(chgr.dev,
 126                            "can't find 'qcom,vset-millivolts' in DT block");
 127                        return rc;
 128                }
 129        }
 130
 131        return qcom_coincell_chgr_config(&chgr, rset, vset, enable);
 132}
 133
 134static const struct of_device_id qcom_coincell_match_table[] = {
 135        { .compatible = "qcom,pm8941-coincell", },
 136        {}
 137};
 138
 139MODULE_DEVICE_TABLE(of, qcom_coincell_match_table);
 140
 141static struct platform_driver qcom_coincell_driver = {
 142        .driver = {
 143                .name           = "qcom-spmi-coincell",
 144                .of_match_table = qcom_coincell_match_table,
 145        },
 146        .probe          = qcom_coincell_probe,
 147};
 148
 149module_platform_driver(qcom_coincell_driver);
 150
 151MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver");
 152MODULE_LICENSE("GPL v2");
 153