root/drivers/mfd/as3711.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * AS3711 PMIC MFC driver
 *
 * Copyright (C) 2012 Renesas Electronics Corporation
 * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
 */

#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mfd/as3711.h>
#include <linux/mfd/core.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/slab.h>

enum {
        AS3711_REGULATOR,
        AS3711_BACKLIGHT,
};

/*
 * Ok to have it static: it is only used during probing and multiple I2C devices
 * cannot be probed simultaneously. Just make sure to avoid stale data.
 */
static struct mfd_cell as3711_subdevs[] = {
        [AS3711_REGULATOR] = {.name = "as3711-regulator",},
        [AS3711_BACKLIGHT] = {.name = "as3711-backlight",},
};

static bool as3711_volatile_reg(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case AS3711_GPIO_SIGNAL_IN:
        case AS3711_INTERRUPT_STATUS_1:
        case AS3711_INTERRUPT_STATUS_2:
        case AS3711_INTERRUPT_STATUS_3:
        case AS3711_CHARGER_STATUS_1:
        case AS3711_CHARGER_STATUS_2:
        case AS3711_REG_STATUS:
                return true;
        }
        return false;
}

static bool as3711_precious_reg(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case AS3711_INTERRUPT_STATUS_1:
        case AS3711_INTERRUPT_STATUS_2:
        case AS3711_INTERRUPT_STATUS_3:
                return true;
        }
        return false;
}

static bool as3711_readable_reg(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case AS3711_SD_1_VOLTAGE:
        case AS3711_SD_2_VOLTAGE:
        case AS3711_SD_3_VOLTAGE:
        case AS3711_SD_4_VOLTAGE:
        case AS3711_LDO_1_VOLTAGE:
        case AS3711_LDO_2_VOLTAGE:
        case AS3711_LDO_3_VOLTAGE:
        case AS3711_LDO_4_VOLTAGE:
        case AS3711_LDO_5_VOLTAGE:
        case AS3711_LDO_6_VOLTAGE:
        case AS3711_LDO_7_VOLTAGE:
        case AS3711_LDO_8_VOLTAGE:
        case AS3711_SD_CONTROL:
        case AS3711_GPIO_SIGNAL_OUT:
        case AS3711_GPIO_SIGNAL_IN:
        case AS3711_SD_CONTROL_1:
        case AS3711_SD_CONTROL_2:
        case AS3711_CURR_CONTROL:
        case AS3711_CURR1_VALUE:
        case AS3711_CURR2_VALUE:
        case AS3711_CURR3_VALUE:
        case AS3711_STEPUP_CONTROL_1:
        case AS3711_STEPUP_CONTROL_2:
        case AS3711_STEPUP_CONTROL_4:
        case AS3711_STEPUP_CONTROL_5:
        case AS3711_REG_STATUS:
        case AS3711_INTERRUPT_STATUS_1:
        case AS3711_INTERRUPT_STATUS_2:
        case AS3711_INTERRUPT_STATUS_3:
        case AS3711_CHARGER_STATUS_1:
        case AS3711_CHARGER_STATUS_2:
        case AS3711_ASIC_ID_1:
        case AS3711_ASIC_ID_2:
                return true;
        }
        return false;
}

static const struct regmap_config as3711_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
        .volatile_reg = as3711_volatile_reg,
        .readable_reg = as3711_readable_reg,
        .precious_reg = as3711_precious_reg,
        .max_register = AS3711_MAX_REG,
        .num_reg_defaults_raw = AS3711_NUM_REGS,
        .cache_type = REGCACHE_MAPLE,
};

#ifdef CONFIG_OF
static const struct of_device_id as3711_of_match[] = {
        {.compatible = "ams,as3711",},
        {}
};
#endif

static int as3711_i2c_probe(struct i2c_client *client)
{
        struct as3711 *as3711;
        struct as3711_platform_data *pdata;
        unsigned int id1, id2;
        int ret;

        if (!client->dev.of_node) {
                pdata = dev_get_platdata(&client->dev);
                if (!pdata)
                        dev_dbg(&client->dev, "Platform data not found\n");
        } else {
                pdata = devm_kzalloc(&client->dev,
                                     sizeof(*pdata), GFP_KERNEL);
                if (!pdata)
                        return -ENOMEM;
        }

        as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL);
        if (!as3711)
                return -ENOMEM;

        as3711->dev = &client->dev;
        i2c_set_clientdata(client, as3711);

        if (client->irq)
                dev_notice(&client->dev, "IRQ not supported yet\n");

        as3711->regmap = devm_regmap_init_i2c(client, &as3711_regmap_config);
        if (IS_ERR(as3711->regmap)) {
                ret = PTR_ERR(as3711->regmap);
                dev_err(&client->dev,
                        "regmap initialization failed: %d\n", ret);
                return ret;
        }

        ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_1, &id1);
        if (!ret)
                ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_2, &id2);
        if (ret < 0) {
                dev_err(&client->dev, "regmap_read() failed: %d\n", ret);
                return ret;
        }
        if (id1 != 0x8b)
                return -ENODEV;
        dev_info(as3711->dev, "AS3711 detected: %x:%x\n", id1, id2);

        /*
         * We can reuse as3711_subdevs[],
         * it will be copied in mfd_add_devices()
         */
        if (pdata) {
                as3711_subdevs[AS3711_REGULATOR].platform_data =
                        &pdata->regulator;
                as3711_subdevs[AS3711_REGULATOR].pdata_size =
                        sizeof(pdata->regulator);
                as3711_subdevs[AS3711_BACKLIGHT].platform_data =
                        &pdata->backlight;
                as3711_subdevs[AS3711_BACKLIGHT].pdata_size =
                        sizeof(pdata->backlight);
        } else {
                as3711_subdevs[AS3711_REGULATOR].platform_data = NULL;
                as3711_subdevs[AS3711_REGULATOR].pdata_size = 0;
                as3711_subdevs[AS3711_BACKLIGHT].platform_data = NULL;
                as3711_subdevs[AS3711_BACKLIGHT].pdata_size = 0;
        }

        ret = devm_mfd_add_devices(as3711->dev, -1, as3711_subdevs,
                                   ARRAY_SIZE(as3711_subdevs), NULL, 0, NULL);
        if (ret < 0)
                dev_err(&client->dev, "add mfd devices failed: %d\n", ret);

        return ret;
}

static const struct i2c_device_id as3711_i2c_id[] = {
        {.name = "as3711", .driver_data = 0},
        {}
};

static struct i2c_driver as3711_i2c_driver = {
        .driver = {
                   .name = "as3711",
                   .of_match_table = of_match_ptr(as3711_of_match),
        },
        .probe = as3711_i2c_probe,
        .id_table = as3711_i2c_id,
};

static int __init as3711_i2c_init(void)
{
        return i2c_add_driver(&as3711_i2c_driver);
}
/* Initialise early */
subsys_initcall(as3711_i2c_init);