linux/drivers/mfd/88pm80x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * I2C driver for Marvell 88PM80x
   4 *
   5 * Copyright (C) 2012 Marvell International Ltd.
   6 * Haojian Zhuang <haojian.zhuang@marvell.com>
   7 * Joseph(Yossi) Hanin <yhanin@marvell.com>
   8 * Qiao Zhou <zhouqiao@marvell.com>
   9 */
  10#include <linux/kernel.h>
  11#include <linux/module.h>
  12#include <linux/i2c.h>
  13#include <linux/mfd/88pm80x.h>
  14#include <linux/slab.h>
  15#include <linux/uaccess.h>
  16#include <linux/err.h>
  17
  18/* 88pm80x chips have same definition for chip id register. */
  19#define PM80X_CHIP_ID                   (0x00)
  20#define PM80X_CHIP_ID_NUM(x)            (((x) >> 5) & 0x7)
  21#define PM80X_CHIP_ID_REVISION(x)       ((x) & 0x1F)
  22
  23struct pm80x_chip_mapping {
  24        unsigned int    id;
  25        int             type;
  26};
  27
  28static struct pm80x_chip_mapping chip_mapping[] = {
  29        /* 88PM800 chip id number */
  30        {0x3,   CHIP_PM800},
  31        /* 88PM805 chip id number */
  32        {0x0,   CHIP_PM805},
  33        /* 88PM860 chip id number */
  34        {0x4,   CHIP_PM860},
  35};
  36
  37/*
  38 * workaround: some registers needed by pm805 are defined in pm800, so
  39 * need to use this global variable to maintain the relation between
  40 * pm800 and pm805. would remove it after HW chip fixes the issue.
  41 */
  42static struct pm80x_chip *g_pm80x_chip;
  43
  44const struct regmap_config pm80x_regmap_config = {
  45        .reg_bits = 8,
  46        .val_bits = 8,
  47};
  48EXPORT_SYMBOL_GPL(pm80x_regmap_config);
  49
  50
  51int pm80x_init(struct i2c_client *client)
  52{
  53        struct pm80x_chip *chip;
  54        struct regmap *map;
  55        unsigned int val;
  56        int i, ret = 0;
  57
  58        chip =
  59            devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
  60        if (!chip)
  61                return -ENOMEM;
  62
  63        map = devm_regmap_init_i2c(client, &pm80x_regmap_config);
  64        if (IS_ERR(map)) {
  65                ret = PTR_ERR(map);
  66                dev_err(&client->dev, "Failed to allocate register map: %d\n",
  67                        ret);
  68                return ret;
  69        }
  70
  71        chip->client = client;
  72        chip->regmap = map;
  73
  74        chip->irq = client->irq;
  75
  76        chip->dev = &client->dev;
  77        dev_set_drvdata(chip->dev, chip);
  78        i2c_set_clientdata(chip->client, chip);
  79
  80        ret = regmap_read(chip->regmap, PM80X_CHIP_ID, &val);
  81        if (ret < 0) {
  82                dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
  83                return ret;
  84        }
  85
  86        for (i = 0; i < ARRAY_SIZE(chip_mapping); i++) {
  87                if (chip_mapping[i].id == PM80X_CHIP_ID_NUM(val)) {
  88                        chip->type = chip_mapping[i].type;
  89                        break;
  90                }
  91        }
  92
  93        if (i == ARRAY_SIZE(chip_mapping)) {
  94                dev_err(chip->dev,
  95                        "Failed to detect Marvell 88PM800:ChipID[0x%x]\n", val);
  96                return -EINVAL;
  97        }
  98
  99        device_init_wakeup(&client->dev, 1);
 100
 101        /*
 102         * workaround: set g_pm80x_chip to the first probed chip. if the
 103         * second chip is probed, just point to the companion to each
 104         * other so that pm805 can access those specific register. would
 105         * remove it after HW chip fixes the issue.
 106         */
 107        if (!g_pm80x_chip)
 108                g_pm80x_chip = chip;
 109        else {
 110                chip->companion = g_pm80x_chip->client;
 111                g_pm80x_chip->companion = chip->client;
 112        }
 113
 114        return 0;
 115}
 116EXPORT_SYMBOL_GPL(pm80x_init);
 117
 118int pm80x_deinit(void)
 119{
 120        /*
 121         * workaround: clear the dependency between pm800 and pm805.
 122         * would remove it after HW chip fixes the issue.
 123         */
 124        if (g_pm80x_chip->companion)
 125                g_pm80x_chip->companion = NULL;
 126        else
 127                g_pm80x_chip = NULL;
 128        return 0;
 129}
 130EXPORT_SYMBOL_GPL(pm80x_deinit);
 131
 132#ifdef CONFIG_PM_SLEEP
 133static int pm80x_suspend(struct device *dev)
 134{
 135        struct i2c_client *client = to_i2c_client(dev);
 136        struct pm80x_chip *chip = i2c_get_clientdata(client);
 137
 138        if (chip && chip->wu_flag)
 139                if (device_may_wakeup(chip->dev))
 140                        enable_irq_wake(chip->irq);
 141
 142        return 0;
 143}
 144
 145static int pm80x_resume(struct device *dev)
 146{
 147        struct i2c_client *client = to_i2c_client(dev);
 148        struct pm80x_chip *chip = i2c_get_clientdata(client);
 149
 150        if (chip && chip->wu_flag)
 151                if (device_may_wakeup(chip->dev))
 152                        disable_irq_wake(chip->irq);
 153
 154        return 0;
 155}
 156#endif
 157
 158SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
 159EXPORT_SYMBOL_GPL(pm80x_pm_ops);
 160
 161MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x");
 162MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
 163MODULE_LICENSE("GPL");
 164