root/drivers/mfd/intel_soc_pmic_bxtwc.c
// SPDX-License-Identifier: GPL-2.0
/*
 * MFD core driver for Intel Broxton Whiskey Cove PMIC
 *
 * Copyright (C) 2015-2017, 2022 Intel Corporation. All rights reserved.
 */

#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/kstrtox.h>
#include <linux/mfd/core.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/mfd/intel_soc_pmic_bxtwc.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_data/x86/intel_scu_ipc.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/sysfs.h>
#include <linux/types.h>

/* PMIC device registers */
#define REG_ADDR_MASK           GENMASK(15, 8)
#define REG_ADDR_SHIFT          8
#define REG_OFFSET_MASK         GENMASK(7, 0)

/* Interrupt Status Registers */
#define BXTWC_IRQLVL1           0x4E02

#define BXTWC_PWRBTNIRQ         0x4E03
#define BXTWC_THRM0IRQ          0x4E04
#define BXTWC_THRM1IRQ          0x4E05
#define BXTWC_THRM2IRQ          0x4E06
#define BXTWC_BCUIRQ            0x4E07
#define BXTWC_ADCIRQ            0x4E08
#define BXTWC_CHGR0IRQ          0x4E09
#define BXTWC_CHGR1IRQ          0x4E0A
#define BXTWC_GPIOIRQ0          0x4E0B
#define BXTWC_GPIOIRQ1          0x4E0C
#define BXTWC_CRITIRQ           0x4E0D
#define BXTWC_TMUIRQ            0x4FB6

/* Interrupt MASK Registers */
#define BXTWC_MIRQLVL1          0x4E0E
#define BXTWC_MIRQLVL1_MCHGR    BIT(5)

#define BXTWC_MPWRBTNIRQ        0x4E0F
#define BXTWC_MTHRM0IRQ         0x4E12
#define BXTWC_MTHRM1IRQ         0x4E13
#define BXTWC_MTHRM2IRQ         0x4E14
#define BXTWC_MBCUIRQ           0x4E15
#define BXTWC_MADCIRQ           0x4E16
#define BXTWC_MCHGR0IRQ         0x4E17
#define BXTWC_MCHGR1IRQ         0x4E18
#define BXTWC_MGPIO0IRQ         0x4E19
#define BXTWC_MGPIO1IRQ         0x4E1A
#define BXTWC_MCRITIRQ          0x4E1B
#define BXTWC_MTMUIRQ           0x4FB7

/* Whiskey Cove PMIC share same ACPI ID between different platforms */
#define BROXTON_PMIC_WC_HRV     4

#define PMC_PMIC_ACCESS         0xFF
#define PMC_PMIC_READ           0x0
#define PMC_PMIC_WRITE          0x1

enum bxtwc_irqs {
        BXTWC_PWRBTN_LVL1_IRQ = 0,
        BXTWC_TMU_LVL1_IRQ,
        BXTWC_THRM_LVL1_IRQ,
        BXTWC_BCU_LVL1_IRQ,
        BXTWC_ADC_LVL1_IRQ,
        BXTWC_CHGR_LVL1_IRQ,
        BXTWC_GPIO_LVL1_IRQ,
        BXTWC_CRIT_LVL1_IRQ,
};

enum bxtwc_irqs_pwrbtn {
        BXTWC_PWRBTN_IRQ = 0,
        BXTWC_UIBTN_IRQ,
};

enum bxtwc_irqs_bcu {
        BXTWC_BCU_IRQ = 0,
};

enum bxtwc_irqs_adc {
        BXTWC_ADC_IRQ = 0,
};

enum bxtwc_irqs_chgr {
        BXTWC_USBC_IRQ = 0,
        BXTWC_CHGR0_IRQ,
        BXTWC_CHGR1_IRQ,
};

enum bxtwc_irqs_tmu {
        BXTWC_TMU_IRQ = 0,
};

enum bxtwc_irqs_crit {
        BXTWC_CRIT_IRQ = 0,
};

static const struct regmap_irq bxtwc_regmap_irqs[] = {
        REGMAP_IRQ_REG(BXTWC_PWRBTN_LVL1_IRQ, 0, BIT(0)),
        REGMAP_IRQ_REG(BXTWC_TMU_LVL1_IRQ, 0, BIT(1)),
        REGMAP_IRQ_REG(BXTWC_THRM_LVL1_IRQ, 0, BIT(2)),
        REGMAP_IRQ_REG(BXTWC_BCU_LVL1_IRQ, 0, BIT(3)),
        REGMAP_IRQ_REG(BXTWC_ADC_LVL1_IRQ, 0, BIT(4)),
        REGMAP_IRQ_REG(BXTWC_CHGR_LVL1_IRQ, 0, BIT(5)),
        REGMAP_IRQ_REG(BXTWC_GPIO_LVL1_IRQ, 0, BIT(6)),
        REGMAP_IRQ_REG(BXTWC_CRIT_LVL1_IRQ, 0, BIT(7)),
};

static const struct regmap_irq bxtwc_regmap_irqs_pwrbtn[] = {
        REGMAP_IRQ_REG(BXTWC_PWRBTN_IRQ, 0, BIT(0)),
};

static const struct regmap_irq bxtwc_regmap_irqs_bcu[] = {
        REGMAP_IRQ_REG(BXTWC_BCU_IRQ, 0, GENMASK(4, 0)),
};

static const struct regmap_irq bxtwc_regmap_irqs_adc[] = {
        REGMAP_IRQ_REG(BXTWC_ADC_IRQ, 0, GENMASK(7, 0)),
};

static const struct regmap_irq bxtwc_regmap_irqs_chgr[] = {
        REGMAP_IRQ_REG(BXTWC_USBC_IRQ, 0, BIT(5)),
        REGMAP_IRQ_REG(BXTWC_CHGR0_IRQ, 0, GENMASK(4, 0)),
        REGMAP_IRQ_REG(BXTWC_CHGR1_IRQ, 1, GENMASK(4, 0)),
};

static const struct regmap_irq bxtwc_regmap_irqs_tmu[] = {
        REGMAP_IRQ_REG(BXTWC_TMU_IRQ, 0, GENMASK(2, 1)),
};

static const struct regmap_irq bxtwc_regmap_irqs_crit[] = {
        REGMAP_IRQ_REG(BXTWC_CRIT_IRQ, 0, GENMASK(1, 0)),
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip = {
        .name = "bxtwc_irq_chip",
        .status_base = BXTWC_IRQLVL1,
        .mask_base = BXTWC_MIRQLVL1,
        .irqs = bxtwc_regmap_irqs,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs),
        .num_regs = 1,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_pwrbtn = {
        .name = "bxtwc_irq_chip_pwrbtn",
        .domain_suffix = "PWRBTN",
        .status_base = BXTWC_PWRBTNIRQ,
        .mask_base = BXTWC_MPWRBTNIRQ,
        .irqs = bxtwc_regmap_irqs_pwrbtn,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_pwrbtn),
        .num_regs = 1,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_tmu = {
        .name = "bxtwc_irq_chip_tmu",
        .domain_suffix = "TMU",
        .status_base = BXTWC_TMUIRQ,
        .mask_base = BXTWC_MTMUIRQ,
        .irqs = bxtwc_regmap_irqs_tmu,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_tmu),
        .num_regs = 1,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_bcu = {
        .name = "bxtwc_irq_chip_bcu",
        .domain_suffix = "BCU",
        .status_base = BXTWC_BCUIRQ,
        .mask_base = BXTWC_MBCUIRQ,
        .irqs = bxtwc_regmap_irqs_bcu,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_bcu),
        .num_regs = 1,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_adc = {
        .name = "bxtwc_irq_chip_adc",
        .domain_suffix = "ADC",
        .status_base = BXTWC_ADCIRQ,
        .mask_base = BXTWC_MADCIRQ,
        .irqs = bxtwc_regmap_irqs_adc,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_adc),
        .num_regs = 1,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_chgr = {
        .name = "bxtwc_irq_chip_chgr",
        .domain_suffix = "CHGR",
        .status_base = BXTWC_CHGR0IRQ,
        .mask_base = BXTWC_MCHGR0IRQ,
        .irqs = bxtwc_regmap_irqs_chgr,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_chgr),
        .num_regs = 2,
};

static const struct regmap_irq_chip bxtwc_regmap_irq_chip_crit = {
        .name = "bxtwc_irq_chip_crit",
        .domain_suffix = "CRIT",
        .status_base = BXTWC_CRITIRQ,
        .mask_base = BXTWC_MCRITIRQ,
        .irqs = bxtwc_regmap_irqs_crit,
        .num_irqs = ARRAY_SIZE(bxtwc_regmap_irqs_crit),
        .num_regs = 1,
};

static const struct resource gpio_resources[] = {
        DEFINE_RES_IRQ_NAMED(BXTWC_GPIO_LVL1_IRQ, "GPIO"),
};

static const struct resource adc_resources[] = {
        DEFINE_RES_IRQ_NAMED(BXTWC_ADC_IRQ, "ADC"),
};

static const struct resource usbc_resources[] = {
        DEFINE_RES_IRQ(BXTWC_USBC_IRQ),
};

static const struct resource charger_resources[] = {
        DEFINE_RES_IRQ_NAMED(BXTWC_CHGR0_IRQ, "CHARGER"),
        DEFINE_RES_IRQ_NAMED(BXTWC_CHGR1_IRQ, "CHARGER1"),
};

static const struct resource thermal_resources[] = {
        DEFINE_RES_IRQ(BXTWC_THRM_LVL1_IRQ),
};

static const struct resource bcu_resources[] = {
        DEFINE_RES_IRQ_NAMED(BXTWC_BCU_IRQ, "BCU"),
};

static const struct resource tmu_resources[] = {
        DEFINE_RES_IRQ_NAMED(BXTWC_TMU_IRQ, "TMU"),
};

static struct mfd_cell bxt_wc_dev[] = {
        {
                .name = "bxt_wcove_thermal",
                .num_resources = ARRAY_SIZE(thermal_resources),
                .resources = thermal_resources,
        },
        {
                .name = "bxt_wcove_gpio",
                .num_resources = ARRAY_SIZE(gpio_resources),
                .resources = gpio_resources,
        },
        {
                .name = "bxt_wcove_region",
        },
};

static const struct mfd_cell bxt_wc_tmu_dev[] = {
        {
                .name = "bxt_wcove_tmu",
                .num_resources = ARRAY_SIZE(tmu_resources),
                .resources = tmu_resources,
        },
};

static const struct mfd_cell bxt_wc_bcu_dev[] = {
        {
                .name = "bxt_wcove_bcu",
                .num_resources = ARRAY_SIZE(bcu_resources),
                .resources = bcu_resources,
        },
};

static const struct mfd_cell bxt_wc_adc_dev[] = {
        {
                .name = "bxt_wcove_gpadc",
                .num_resources = ARRAY_SIZE(adc_resources),
                .resources = adc_resources,
        },
};

static struct mfd_cell bxt_wc_chgr_dev[] = {
        {
                .name = "bxt_wcove_usbc",
                .num_resources = ARRAY_SIZE(usbc_resources),
                .resources = usbc_resources,
        },
        {
                .name = "bxt_wcove_ext_charger",
                .num_resources = ARRAY_SIZE(charger_resources),
                .resources = charger_resources,
        },
};

static int regmap_ipc_byte_reg_read(void *context, unsigned int reg,
                                    unsigned int *val)
{
        int ret;
        int i2c_addr;
        u8 ipc_in[2];
        u8 ipc_out[4];
        struct intel_soc_pmic *pmic = context;

        if (!pmic)
                return -EINVAL;

        if (reg & REG_ADDR_MASK)
                i2c_addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
        else
                i2c_addr = BXTWC_DEVICE1_ADDR;

        reg &= REG_OFFSET_MASK;

        ipc_in[0] = reg;
        ipc_in[1] = i2c_addr;
        ret = intel_scu_ipc_dev_command(pmic->scu, PMC_PMIC_ACCESS,
                                        PMC_PMIC_READ, ipc_in, sizeof(ipc_in),
                                        ipc_out, sizeof(ipc_out));
        if (ret)
                return ret;

        *val = ipc_out[0];

        return 0;
}

static int regmap_ipc_byte_reg_write(void *context, unsigned int reg,
                                       unsigned int val)
{
        int i2c_addr;
        u8 ipc_in[3];
        struct intel_soc_pmic *pmic = context;

        if (!pmic)
                return -EINVAL;

        if (reg & REG_ADDR_MASK)
                i2c_addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
        else
                i2c_addr = BXTWC_DEVICE1_ADDR;

        reg &= REG_OFFSET_MASK;

        ipc_in[0] = reg;
        ipc_in[1] = i2c_addr;
        ipc_in[2] = val;
        return intel_scu_ipc_dev_command(pmic->scu, PMC_PMIC_ACCESS,
                                         PMC_PMIC_WRITE, ipc_in, sizeof(ipc_in),
                                         NULL, 0);
}

/* sysfs interfaces to r/w PMIC registers, required by initial script */
static unsigned long bxtwc_reg_addr;
static ssize_t addr_show(struct device *dev,
                         struct device_attribute *attr, char *buf)
{
        return sysfs_emit(buf, "0x%lx\n", bxtwc_reg_addr);
}

static ssize_t addr_store(struct device *dev,
                          struct device_attribute *attr, const char *buf, size_t count)
{
        int ret;

        ret = kstrtoul(buf, 0, &bxtwc_reg_addr);
        if (ret)
                return ret;

        return count;
}
static DEVICE_ATTR_ADMIN_RW(addr);

static ssize_t val_show(struct device *dev,
                        struct device_attribute *attr, char *buf)
{
        int ret;
        unsigned int val;
        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);

        ret = regmap_read(pmic->regmap, bxtwc_reg_addr, &val);
        if (ret) {
                dev_err(dev, "Failed to read 0x%lx\n", bxtwc_reg_addr);
                return ret;
        }

        return sysfs_emit(buf, "0x%02x\n", val);
}

static ssize_t val_store(struct device *dev,
                         struct device_attribute *attr, const char *buf, size_t count)
{
        int ret;
        unsigned int val;
        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);

        ret = kstrtouint(buf, 0, &val);
        if (ret)
                return ret;

        ret = regmap_write(pmic->regmap, bxtwc_reg_addr, val);
        if (ret) {
                dev_err(dev, "Failed to write value 0x%02x to address 0x%lx",
                        val, bxtwc_reg_addr);
                return ret;
        }
        return count;
}
static DEVICE_ATTR_ADMIN_RW(val);

static struct attribute *bxtwc_attrs[] = {
        &dev_attr_addr.attr,
        &dev_attr_val.attr,
        NULL
};
ATTRIBUTE_GROUPS(bxtwc);

static const struct regmap_config bxtwc_regmap_config = {
        .reg_bits = 16,
        .val_bits = 8,
        .reg_write = regmap_ipc_byte_reg_write,
        .reg_read = regmap_ipc_byte_reg_read,
};

static int bxtwc_add_chained_irq_chip(struct intel_soc_pmic *pmic,
                                struct regmap_irq_chip_data *pdata,
                                int pirq, int irq_flags,
                                const struct regmap_irq_chip *chip,
                                struct regmap_irq_chip_data **data)
{
        struct device *dev = pmic->dev;
        int irq, ret;

        irq = regmap_irq_get_virq(pdata, pirq);
        if (irq < 0)
                return dev_err_probe(dev, irq, "Failed to get parent vIRQ(%d) for chip %s\n",
                                     pirq, chip->name);

        ret = devm_regmap_add_irq_chip(dev, pmic->regmap, irq, irq_flags, 0, chip, data);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to add %s IRQ chip\n", chip->name);

        return 0;
}

static int bxtwc_add_chained_devices(struct intel_soc_pmic *pmic,
                                     const struct mfd_cell *cells, int n_devs,
                                     struct regmap_irq_chip_data *pdata,
                                     int pirq, int irq_flags,
                                     const struct regmap_irq_chip *chip,
                                     struct regmap_irq_chip_data **data)
{
        struct device *dev = pmic->dev;
        struct irq_domain *domain;
        int ret;

        ret = bxtwc_add_chained_irq_chip(pmic, pdata, pirq, irq_flags, chip, data);
        if (ret)
                return ret;

        domain = regmap_irq_get_domain(*data);

        return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, cells, n_devs, NULL, 0, domain);
}

static int bxtwc_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        int ret;
        acpi_status status;
        unsigned long long hrv;
        struct intel_soc_pmic *pmic;

        status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv);
        if (ACPI_FAILURE(status))
                return dev_err_probe(dev, -ENODEV, "Failed to get PMIC hardware revision\n");
        if (hrv != BROXTON_PMIC_WC_HRV)
                return dev_err_probe(dev, -ENODEV, "Invalid PMIC hardware revision: %llu\n", hrv);

        pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
        if (!pmic)
                return -ENOMEM;

        ret = platform_get_irq(pdev, 0);
        if (ret < 0)
                return ret;
        pmic->irq = ret;

        platform_set_drvdata(pdev, pmic);
        pmic->dev = dev;

        pmic->scu = devm_intel_scu_ipc_dev_get(dev);
        if (!pmic->scu)
                return -EPROBE_DEFER;

        pmic->regmap = devm_regmap_init(dev, NULL, pmic, &bxtwc_regmap_config);
        if (IS_ERR(pmic->regmap))
                return dev_err_probe(dev, PTR_ERR(pmic->regmap), "Failed to initialise regmap\n");

        ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
                                       IRQF_ONESHOT | IRQF_SHARED,
                                       0, &bxtwc_regmap_irq_chip,
                                       &pmic->irq_chip_data);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to add IRQ chip\n");

        ret = bxtwc_add_chained_devices(pmic, bxt_wc_tmu_dev, ARRAY_SIZE(bxt_wc_tmu_dev),
                                        pmic->irq_chip_data,
                                        BXTWC_TMU_LVL1_IRQ,
                                        IRQF_ONESHOT,
                                        &bxtwc_regmap_irq_chip_tmu,
                                        &pmic->irq_chip_data_tmu);
        if (ret)
                return ret;

        ret = bxtwc_add_chained_irq_chip(pmic, pmic->irq_chip_data,
                                         BXTWC_PWRBTN_LVL1_IRQ,
                                         IRQF_ONESHOT,
                                         &bxtwc_regmap_irq_chip_pwrbtn,
                                         &pmic->irq_chip_data_pwrbtn);
        if (ret)
                return ret;

        ret = bxtwc_add_chained_devices(pmic, bxt_wc_bcu_dev, ARRAY_SIZE(bxt_wc_bcu_dev),
                                        pmic->irq_chip_data,
                                        BXTWC_BCU_LVL1_IRQ,
                                        IRQF_ONESHOT,
                                        &bxtwc_regmap_irq_chip_bcu,
                                        &pmic->irq_chip_data_bcu);
        if (ret)
                return ret;

        ret = bxtwc_add_chained_devices(pmic, bxt_wc_adc_dev, ARRAY_SIZE(bxt_wc_adc_dev),
                                        pmic->irq_chip_data,
                                        BXTWC_ADC_LVL1_IRQ,
                                        IRQF_ONESHOT,
                                        &bxtwc_regmap_irq_chip_adc,
                                        &pmic->irq_chip_data_adc);
        if (ret)
                return ret;

        ret = bxtwc_add_chained_devices(pmic, bxt_wc_chgr_dev, ARRAY_SIZE(bxt_wc_chgr_dev),
                                        pmic->irq_chip_data,
                                        BXTWC_CHGR_LVL1_IRQ,
                                        IRQF_ONESHOT,
                                        &bxtwc_regmap_irq_chip_chgr,
                                        &pmic->irq_chip_data_chgr);
        if (ret)
                return ret;

        /* Add chained IRQ handler for CRIT IRQs */
        ret = bxtwc_add_chained_irq_chip(pmic, pmic->irq_chip_data,
                                         BXTWC_CRIT_LVL1_IRQ,
                                         IRQF_ONESHOT,
                                         &bxtwc_regmap_irq_chip_crit,
                                         &pmic->irq_chip_data_crit);
        if (ret)
                return ret;

        ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, bxt_wc_dev, ARRAY_SIZE(bxt_wc_dev),
                                   NULL, 0, NULL);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to add devices\n");

        /*
         * There is a known H/W bug. Upon reset, BIT 5 of register
         * BXTWC_CHGR_LVL1_IRQ is 0 which is the expected value. However,
         * later it's set to 1(masked) automatically by hardware. So we
         * place the software workaround here to unmask it again in order
         * to re-enable the charger interrupt.
         */
        regmap_update_bits(pmic->regmap, BXTWC_MIRQLVL1, BXTWC_MIRQLVL1_MCHGR, 0);

        return 0;
}

static void bxtwc_shutdown(struct platform_device *pdev)
{
        struct intel_soc_pmic *pmic = platform_get_drvdata(pdev);

        disable_irq(pmic->irq);
}

static int bxtwc_suspend(struct device *dev)
{
        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);

        disable_irq(pmic->irq);

        return 0;
}

static int bxtwc_resume(struct device *dev)
{
        struct intel_soc_pmic *pmic = dev_get_drvdata(dev);

        enable_irq(pmic->irq);
        return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(bxtwc_pm_ops, bxtwc_suspend, bxtwc_resume);

static const struct acpi_device_id bxtwc_acpi_ids[] = {
        { "INT34D3", },
        { }
};
MODULE_DEVICE_TABLE(acpi, bxtwc_acpi_ids);

static struct platform_driver bxtwc_driver = {
        .probe = bxtwc_probe,
        .shutdown = bxtwc_shutdown,
        .driver = {
                .name   = "intel_soc_pmic_bxtwc",
                .pm     = pm_sleep_ptr(&bxtwc_pm_ops),
                .acpi_match_table = bxtwc_acpi_ids,
                .dev_groups = bxtwc_groups,
        },
};

module_platform_driver(bxtwc_driver);

MODULE_DESCRIPTION("Intel Broxton Whiskey Cove PMIC MFD core driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Qipeng Zha <qipeng.zha@intel.com>");