linux/drivers/mfd/adp5520.c
<<
>>
Prefs
   1/*
   2 * Base driver for Analog Devices ADP5520/ADP5501 MFD PMICs
   3 * LCD Backlight: drivers/video/backlight/adp5520_bl
   4 * LEDs         : drivers/led/leds-adp5520
   5 * GPIO         : drivers/gpio/adp5520-gpio (ADP5520 only)
   6 * Keys         : drivers/input/keyboard/adp5520-keys (ADP5520 only)
   7 *
   8 * Copyright 2009 Analog Devices Inc.
   9 *
  10 * Derived from da903x:
  11 * Copyright (C) 2008 Compulab, Ltd.
  12 *      Mike Rapoport <mike@compulab.co.il>
  13 *
  14 * Copyright (C) 2006-2008 Marvell International Ltd.
  15 *      Eric Miao <eric.miao@marvell.com>
  16 *
  17 * Licensed under the GPL-2 or later.
  18 */
  19
  20#include <linux/kernel.h>
  21#include <linux/module.h>
  22#include <linux/platform_device.h>
  23#include <linux/slab.h>
  24#include <linux/interrupt.h>
  25#include <linux/irq.h>
  26#include <linux/err.h>
  27#include <linux/i2c.h>
  28
  29#include <linux/mfd/adp5520.h>
  30
  31struct adp5520_chip {
  32        struct i2c_client *client;
  33        struct device *dev;
  34        struct mutex lock;
  35        struct blocking_notifier_head notifier_list;
  36        int irq;
  37        unsigned long id;
  38        uint8_t mode;
  39};
  40
  41static int __adp5520_read(struct i2c_client *client,
  42                                int reg, uint8_t *val)
  43{
  44        int ret;
  45
  46        ret = i2c_smbus_read_byte_data(client, reg);
  47        if (ret < 0) {
  48                dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
  49                return ret;
  50        }
  51
  52        *val = (uint8_t)ret;
  53        return 0;
  54}
  55
  56static int __adp5520_write(struct i2c_client *client,
  57                                 int reg, uint8_t val)
  58{
  59        int ret;
  60
  61        ret = i2c_smbus_write_byte_data(client, reg, val);
  62        if (ret < 0) {
  63                dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
  64                                val, reg);
  65                return ret;
  66        }
  67        return 0;
  68}
  69
  70static int __adp5520_ack_bits(struct i2c_client *client, int reg,
  71                              uint8_t bit_mask)
  72{
  73        struct adp5520_chip *chip = i2c_get_clientdata(client);
  74        uint8_t reg_val;
  75        int ret;
  76
  77        mutex_lock(&chip->lock);
  78
  79        ret = __adp5520_read(client, reg, &reg_val);
  80
  81        if (!ret) {
  82                reg_val |= bit_mask;
  83                ret = __adp5520_write(client, reg, reg_val);
  84        }
  85
  86        mutex_unlock(&chip->lock);
  87        return ret;
  88}
  89
  90int adp5520_write(struct device *dev, int reg, uint8_t val)
  91{
  92        return __adp5520_write(to_i2c_client(dev), reg, val);
  93}
  94EXPORT_SYMBOL_GPL(adp5520_write);
  95
  96int adp5520_read(struct device *dev, int reg, uint8_t *val)
  97{
  98        return __adp5520_read(to_i2c_client(dev), reg, val);
  99}
 100EXPORT_SYMBOL_GPL(adp5520_read);
 101
 102int adp5520_set_bits(struct device *dev, int reg, uint8_t bit_mask)
 103{
 104        struct adp5520_chip *chip = dev_get_drvdata(dev);
 105        uint8_t reg_val;
 106        int ret;
 107
 108        mutex_lock(&chip->lock);
 109
 110        ret = __adp5520_read(chip->client, reg, &reg_val);
 111
 112        if (!ret && ((reg_val & bit_mask) != bit_mask)) {
 113                reg_val |= bit_mask;
 114                ret = __adp5520_write(chip->client, reg, reg_val);
 115        }
 116
 117        mutex_unlock(&chip->lock);
 118        return ret;
 119}
 120EXPORT_SYMBOL_GPL(adp5520_set_bits);
 121
 122int adp5520_clr_bits(struct device *dev, int reg, uint8_t bit_mask)
 123{
 124        struct adp5520_chip *chip = dev_get_drvdata(dev);
 125        uint8_t reg_val;
 126        int ret;
 127
 128        mutex_lock(&chip->lock);
 129
 130        ret = __adp5520_read(chip->client, reg, &reg_val);
 131
 132        if (!ret && (reg_val & bit_mask)) {
 133                reg_val &= ~bit_mask;
 134                ret = __adp5520_write(chip->client, reg, reg_val);
 135        }
 136
 137        mutex_unlock(&chip->lock);
 138        return ret;
 139}
 140EXPORT_SYMBOL_GPL(adp5520_clr_bits);
 141
 142int adp5520_register_notifier(struct device *dev, struct notifier_block *nb,
 143                                unsigned int events)
 144{
 145        struct adp5520_chip *chip = dev_get_drvdata(dev);
 146
 147        if (chip->irq) {
 148                adp5520_set_bits(chip->dev, ADP5520_INTERRUPT_ENABLE,
 149                        events & (ADP5520_KP_IEN | ADP5520_KR_IEN |
 150                        ADP5520_OVP_IEN | ADP5520_CMPR_IEN));
 151
 152                return blocking_notifier_chain_register(&chip->notifier_list,
 153                         nb);
 154        }
 155
 156        return -ENODEV;
 157}
 158EXPORT_SYMBOL_GPL(adp5520_register_notifier);
 159
 160int adp5520_unregister_notifier(struct device *dev, struct notifier_block *nb,
 161                                unsigned int events)
 162{
 163        struct adp5520_chip *chip = dev_get_drvdata(dev);
 164
 165        adp5520_clr_bits(chip->dev, ADP5520_INTERRUPT_ENABLE,
 166                events & (ADP5520_KP_IEN | ADP5520_KR_IEN |
 167                ADP5520_OVP_IEN | ADP5520_CMPR_IEN));
 168
 169        return blocking_notifier_chain_unregister(&chip->notifier_list, nb);
 170}
 171EXPORT_SYMBOL_GPL(adp5520_unregister_notifier);
 172
 173static irqreturn_t adp5520_irq_thread(int irq, void *data)
 174{
 175        struct adp5520_chip *chip = data;
 176        unsigned int events;
 177        uint8_t reg_val;
 178        int ret;
 179
 180        ret = __adp5520_read(chip->client, ADP5520_MODE_STATUS, &reg_val);
 181        if (ret)
 182                goto out;
 183
 184        events =  reg_val & (ADP5520_OVP_INT | ADP5520_CMPR_INT |
 185                ADP5520_GPI_INT | ADP5520_KR_INT | ADP5520_KP_INT);
 186
 187        blocking_notifier_call_chain(&chip->notifier_list, events, NULL);
 188        /* ACK, Sticky bits are W1C */
 189        __adp5520_ack_bits(chip->client, ADP5520_MODE_STATUS, events);
 190
 191out:
 192        return IRQ_HANDLED;
 193}
 194
 195static int __remove_subdev(struct device *dev, void *unused)
 196{
 197        platform_device_unregister(to_platform_device(dev));
 198        return 0;
 199}
 200
 201static int adp5520_remove_subdevs(struct adp5520_chip *chip)
 202{
 203        return device_for_each_child(chip->dev, NULL, __remove_subdev);
 204}
 205
 206static int adp5520_probe(struct i2c_client *client,
 207                                        const struct i2c_device_id *id)
 208{
 209        struct adp5520_platform_data *pdata = dev_get_platdata(&client->dev);
 210        struct platform_device *pdev;
 211        struct adp5520_chip *chip;
 212        int ret;
 213
 214        if (!i2c_check_functionality(client->adapter,
 215                                        I2C_FUNC_SMBUS_BYTE_DATA)) {
 216                dev_err(&client->dev, "SMBUS Word Data not Supported\n");
 217                return -EIO;
 218        }
 219
 220        if (pdata == NULL) {
 221                dev_err(&client->dev, "missing platform data\n");
 222                return -ENODEV;
 223        }
 224
 225        chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
 226        if (!chip)
 227                return -ENOMEM;
 228
 229        i2c_set_clientdata(client, chip);
 230        chip->client = client;
 231
 232        chip->dev = &client->dev;
 233        chip->irq = client->irq;
 234        chip->id = id->driver_data;
 235        mutex_init(&chip->lock);
 236
 237        if (chip->irq) {
 238                BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list);
 239
 240                ret = request_threaded_irq(chip->irq, NULL, adp5520_irq_thread,
 241                                IRQF_TRIGGER_LOW | IRQF_ONESHOT,
 242                                "adp5520", chip);
 243                if (ret) {
 244                        dev_err(&client->dev, "failed to request irq %d\n",
 245                                        chip->irq);
 246                        return ret;
 247                }
 248        }
 249
 250        ret = adp5520_write(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY);
 251        if (ret) {
 252                dev_err(&client->dev, "failed to write\n");
 253                goto out_free_irq;
 254        }
 255
 256        if (pdata->keys) {
 257                pdev = platform_device_register_data(chip->dev, "adp5520-keys",
 258                                chip->id, pdata->keys, sizeof(*pdata->keys));
 259                if (IS_ERR(pdev)) {
 260                        ret = PTR_ERR(pdev);
 261                        goto out_remove_subdevs;
 262                }
 263        }
 264
 265        if (pdata->gpio) {
 266                pdev = platform_device_register_data(chip->dev, "adp5520-gpio",
 267                                chip->id, pdata->gpio, sizeof(*pdata->gpio));
 268                if (IS_ERR(pdev)) {
 269                        ret = PTR_ERR(pdev);
 270                        goto out_remove_subdevs;
 271                }
 272        }
 273
 274        if (pdata->leds) {
 275                pdev = platform_device_register_data(chip->dev, "adp5520-led",
 276                                chip->id, pdata->leds, sizeof(*pdata->leds));
 277                if (IS_ERR(pdev)) {
 278                        ret = PTR_ERR(pdev);
 279                        goto out_remove_subdevs;
 280                }
 281        }
 282
 283        if (pdata->backlight) {
 284                pdev = platform_device_register_data(chip->dev,
 285                                                "adp5520-backlight",
 286                                                chip->id,
 287                                                pdata->backlight,
 288                                                sizeof(*pdata->backlight));
 289                if (IS_ERR(pdev)) {
 290                        ret = PTR_ERR(pdev);
 291                        goto out_remove_subdevs;
 292                }
 293        }
 294
 295        return 0;
 296
 297out_remove_subdevs:
 298        adp5520_remove_subdevs(chip);
 299
 300out_free_irq:
 301        if (chip->irq)
 302                free_irq(chip->irq, chip);
 303
 304        return ret;
 305}
 306
 307static int adp5520_remove(struct i2c_client *client)
 308{
 309        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 310
 311        if (chip->irq)
 312                free_irq(chip->irq, chip);
 313
 314        adp5520_remove_subdevs(chip);
 315        adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0);
 316        return 0;
 317}
 318
 319#ifdef CONFIG_PM_SLEEP
 320static int adp5520_suspend(struct device *dev)
 321{
 322        struct i2c_client *client = to_i2c_client(dev);
 323        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 324
 325        adp5520_read(chip->dev, ADP5520_MODE_STATUS, &chip->mode);
 326        /* All other bits are W1C */
 327        chip->mode &= ADP5520_BL_EN | ADP5520_DIM_EN | ADP5520_nSTNBY;
 328        adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0);
 329        return 0;
 330}
 331
 332static int adp5520_resume(struct device *dev)
 333{
 334        struct i2c_client *client = to_i2c_client(dev);
 335        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 336
 337        adp5520_write(chip->dev, ADP5520_MODE_STATUS, chip->mode);
 338        return 0;
 339}
 340#endif
 341
 342static SIMPLE_DEV_PM_OPS(adp5520_pm, adp5520_suspend, adp5520_resume);
 343
 344static const struct i2c_device_id adp5520_id[] = {
 345        { "pmic-adp5520", ID_ADP5520 },
 346        { "pmic-adp5501", ID_ADP5501 },
 347        { }
 348};
 349MODULE_DEVICE_TABLE(i2c, adp5520_id);
 350
 351static struct i2c_driver adp5520_driver = {
 352        .driver = {
 353                .name   = "adp5520",
 354                .pm     = &adp5520_pm,
 355        },
 356        .probe          = adp5520_probe,
 357        .remove         = adp5520_remove,
 358        .id_table       = adp5520_id,
 359};
 360
 361module_i2c_driver(adp5520_driver);
 362
 363MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
 364MODULE_DESCRIPTION("ADP5520(01) PMIC-MFD Driver");
 365MODULE_LICENSE("GPL");
 366