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