linux/drivers/mfd/intel_soc_pmic_chtwc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC
   4 *
   5 * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
   6 *
   7 * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
   8 * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
   9 */
  10
  11#include <linux/acpi.h>
  12#include <linux/delay.h>
  13#include <linux/err.h>
  14#include <linux/i2c.h>
  15#include <linux/interrupt.h>
  16#include <linux/kernel.h>
  17#include <linux/mfd/core.h>
  18#include <linux/mfd/intel_soc_pmic.h>
  19#include <linux/regmap.h>
  20
  21/* PMIC device registers */
  22#define REG_OFFSET_MASK         GENMASK(7, 0)
  23#define REG_ADDR_MASK           GENMASK(15, 8)
  24#define REG_ADDR_SHIFT          8
  25
  26#define CHT_WC_IRQLVL1          0x6e02
  27#define CHT_WC_IRQLVL1_MASK     0x6e0e
  28
  29/* Whiskey Cove PMIC share same ACPI ID between different platforms */
  30#define CHT_WC_HRV              3
  31
  32/* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */
  33enum {
  34        CHT_WC_PWRSRC_IRQ = 0,
  35        CHT_WC_THRM_IRQ,
  36        CHT_WC_BCU_IRQ,
  37        CHT_WC_ADC_IRQ,
  38        CHT_WC_EXT_CHGR_IRQ,
  39        CHT_WC_GPIO_IRQ,
  40        /* There is no irq 6 */
  41        CHT_WC_CRIT_IRQ = 7,
  42};
  43
  44static const struct resource cht_wc_pwrsrc_resources[] = {
  45        DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ),
  46};
  47
  48static const struct resource cht_wc_ext_charger_resources[] = {
  49        DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ),
  50};
  51
  52static struct mfd_cell cht_wc_dev[] = {
  53        {
  54                .name = "cht_wcove_pwrsrc",
  55                .num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources),
  56                .resources = cht_wc_pwrsrc_resources,
  57        }, {
  58                .name = "cht_wcove_ext_chgr",
  59                .num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources),
  60                .resources = cht_wc_ext_charger_resources,
  61        },
  62        {       .name = "cht_wcove_region", },
  63        {       .name = "cht_wcove_leds", },
  64};
  65
  66/*
  67 * The CHT Whiskey Cove covers multiple I2C addresses, with a 1 Byte
  68 * register address space per I2C address, so we use 16 bit register
  69 * addresses where the high 8 bits contain the I2C client address.
  70 */
  71static int cht_wc_byte_reg_read(void *context, unsigned int reg,
  72                                unsigned int *val)
  73{
  74        struct i2c_client *client = context;
  75        int ret, orig_addr = client->addr;
  76
  77        if (!(reg & REG_ADDR_MASK)) {
  78                dev_err(&client->dev, "Error I2C address not specified\n");
  79                return -EINVAL;
  80        }
  81
  82        client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
  83        ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK);
  84        client->addr = orig_addr;
  85
  86        if (ret < 0)
  87                return ret;
  88
  89        *val = ret;
  90        return 0;
  91}
  92
  93static int cht_wc_byte_reg_write(void *context, unsigned int reg,
  94                                 unsigned int val)
  95{
  96        struct i2c_client *client = context;
  97        int ret, orig_addr = client->addr;
  98
  99        if (!(reg & REG_ADDR_MASK)) {
 100                dev_err(&client->dev, "Error I2C address not specified\n");
 101                return -EINVAL;
 102        }
 103
 104        client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
 105        ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val);
 106        client->addr = orig_addr;
 107
 108        return ret;
 109}
 110
 111static const struct regmap_config cht_wc_regmap_cfg = {
 112        .reg_bits = 16,
 113        .val_bits = 8,
 114        .reg_write = cht_wc_byte_reg_write,
 115        .reg_read = cht_wc_byte_reg_read,
 116};
 117
 118static const struct regmap_irq cht_wc_regmap_irqs[] = {
 119        REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)),
 120        REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)),
 121        REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)),
 122        REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)),
 123        REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)),
 124        REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)),
 125        REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)),
 126};
 127
 128static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
 129        .name = "cht_wc_irq_chip",
 130        .status_base = CHT_WC_IRQLVL1,
 131        .mask_base = CHT_WC_IRQLVL1_MASK,
 132        .irqs = cht_wc_regmap_irqs,
 133        .num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs),
 134        .num_regs = 1,
 135};
 136
 137static int cht_wc_probe(struct i2c_client *client)
 138{
 139        struct device *dev = &client->dev;
 140        struct intel_soc_pmic *pmic;
 141        acpi_status status;
 142        unsigned long long hrv;
 143        int ret;
 144
 145        status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv);
 146        if (ACPI_FAILURE(status)) {
 147                dev_err(dev, "Failed to get PMIC hardware revision\n");
 148                return -ENODEV;
 149        }
 150        if (hrv != CHT_WC_HRV) {
 151                dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv);
 152                return -ENODEV;
 153        }
 154        if (client->irq < 0) {
 155                dev_err(dev, "Invalid IRQ\n");
 156                return -EINVAL;
 157        }
 158
 159        pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
 160        if (!pmic)
 161                return -ENOMEM;
 162
 163        pmic->irq = client->irq;
 164        pmic->dev = dev;
 165        i2c_set_clientdata(client, pmic);
 166
 167        pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg);
 168        if (IS_ERR(pmic->regmap))
 169                return PTR_ERR(pmic->regmap);
 170
 171        ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
 172                                       IRQF_ONESHOT | IRQF_SHARED, 0,
 173                                       &cht_wc_regmap_irq_chip,
 174                                       &pmic->irq_chip_data);
 175        if (ret)
 176                return ret;
 177
 178        return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
 179                                cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0,
 180                                regmap_irq_get_domain(pmic->irq_chip_data));
 181}
 182
 183static void cht_wc_shutdown(struct i2c_client *client)
 184{
 185        struct intel_soc_pmic *pmic = i2c_get_clientdata(client);
 186
 187        disable_irq(pmic->irq);
 188}
 189
 190static int __maybe_unused cht_wc_suspend(struct device *dev)
 191{
 192        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
 193
 194        disable_irq(pmic->irq);
 195
 196        return 0;
 197}
 198
 199static int __maybe_unused cht_wc_resume(struct device *dev)
 200{
 201        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
 202
 203        enable_irq(pmic->irq);
 204
 205        return 0;
 206}
 207static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume);
 208
 209static const struct i2c_device_id cht_wc_i2c_id[] = {
 210        { }
 211};
 212
 213static const struct acpi_device_id cht_wc_acpi_ids[] = {
 214        { "INT34D3", },
 215        { }
 216};
 217
 218static struct i2c_driver cht_wc_driver = {
 219        .driver = {
 220                .name   = "CHT Whiskey Cove PMIC",
 221                .pm     = &cht_wc_pm_ops,
 222                .acpi_match_table = cht_wc_acpi_ids,
 223        },
 224        .probe_new = cht_wc_probe,
 225        .shutdown = cht_wc_shutdown,
 226        .id_table = cht_wc_i2c_id,
 227};
 228builtin_i2c_driver(cht_wc_driver);
 229