1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * GPIO driver for Analog Devices ADP5520 MFD PMICs
4 *
5 * Copyright 2009 Analog Devices Inc.
6 */
7
8#include <linux/module.h>
9#include <linux/slab.h>
10#include <linux/kernel.h>
11#include <linux/init.h>
12#include <linux/platform_device.h>
13#include <linux/mfd/adp5520.h>
14#include <linux/gpio/driver.h>
15
16struct adp5520_gpio {
17	struct device *master;
18	struct gpio_chip gpio_chip;
19	unsigned char lut[ADP5520_MAXGPIOS];
20	unsigned long output;
21};
22
23static int adp5520_gpio_get_value(struct gpio_chip *chip, unsigned off)
24{
25	struct adp5520_gpio *dev;
26	uint8_t reg_val;
27
28	dev = gpiochip_get_data(chip);
29
30	/*
31	 * There are dedicated registers for GPIO IN/OUT.
32	 * Make sure we return the right value, even when configured as output
33	 */
34
35	if (test_bit(off, &dev->output))
36		adp5520_read(dev->master, ADP5520_GPIO_OUT, &reg_val);
37	else
38		adp5520_read(dev->master, ADP5520_GPIO_IN, &reg_val);
39
40	return !!(reg_val & dev->lut[off]);
41}
42
43static void adp5520_gpio_set_value(struct gpio_chip *chip,
44		unsigned off, int val)
45{
46	struct adp5520_gpio *dev;
47	dev = gpiochip_get_data(chip);
48
49	if (val)
50		adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]);
51	else
52		adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]);
53}
54
55static int adp5520_gpio_direction_input(struct gpio_chip *chip, unsigned off)
56{
57	struct adp5520_gpio *dev;
58	dev = gpiochip_get_data(chip);
59
60	clear_bit(off, &dev->output);
61
62	return adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_2,
63				dev->lut[off]);
64}
65
66static int adp5520_gpio_direction_output(struct gpio_chip *chip,
67		unsigned off, int val)
68{
69	struct adp5520_gpio *dev;
70	int ret = 0;
71	dev = gpiochip_get_data(chip);
72
73	set_bit(off, &dev->output);
74
75	if (val)
76		ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_OUT,
77					dev->lut[off]);
78	else
79		ret |= adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT,
80					dev->lut[off]);
81
82	ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_CFG_2,
83					dev->lut[off]);
84
85	return ret;
86}
87
88static int adp5520_gpio_probe(struct platform_device *pdev)
89{
90	struct adp5520_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
91	struct adp5520_gpio *dev;
92	struct gpio_chip *gc;
93	int ret, i, gpios;
94	unsigned char ctl_mask = 0;
95
96	if (pdata == NULL) {
97		dev_err(&pdev->dev, "missing platform data\n");
98		return -ENODEV;
99	}
100
101	if (pdev->id != ID_ADP5520) {
102		dev_err(&pdev->dev, "only ADP5520 supports GPIO\n");
103		return -ENODEV;
104	}
105
106	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
107	if (dev == NULL)
108		return -ENOMEM;
109
110	dev->master = pdev->dev.parent;
111
112	for (gpios = 0, i = 0; i < ADP5520_MAXGPIOS; i++)
113		if (pdata->gpio_en_mask & (1 << i))
114			dev->lut[gpios++] = 1 << i;
115
116	if (gpios < 1)
117		return -EINVAL;
118
119	gc = &dev->gpio_chip;
120	gc->direction_input  = adp5520_gpio_direction_input;
121	gc->direction_output = adp5520_gpio_direction_output;
122	gc->get = adp5520_gpio_get_value;
123	gc->set = adp5520_gpio_set_value;
124	gc->can_sleep = true;
125
126	gc->base = pdata->gpio_start;
127	gc->ngpio = gpios;
128	gc->label = pdev->name;
129	gc->owner = THIS_MODULE;
130
131	ret = adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_1,
132		pdata->gpio_en_mask);
133
134	if (pdata->gpio_en_mask & ADP5520_GPIO_C3)
135		ctl_mask |= ADP5520_C3_MODE;
136
137	if (pdata->gpio_en_mask & ADP5520_GPIO_R3)
138		ctl_mask |= ADP5520_R3_MODE;
139
140	if (ctl_mask)
141		ret = adp5520_set_bits(dev->master, ADP5520_LED_CONTROL,
142			ctl_mask);
143
144	ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_PULLUP,
145		pdata->gpio_pullup_mask);
146
147	if (ret) {
148		dev_err(&pdev->dev, "failed to write\n");
149		return ret;
150	}
151
152	return devm_gpiochip_add_data(&pdev->dev, &dev->gpio_chip, dev);
153}
154
155static struct platform_driver adp5520_gpio_driver = {
156	.driver	= {
157		.name	= "adp5520-gpio",
158	},
159	.probe		= adp5520_gpio_probe,
160};
161
162module_platform_driver(adp5520_gpio_driver);
163
164MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
165MODULE_DESCRIPTION("GPIO ADP5520 Driver");
166MODULE_LICENSE("GPL");
167MODULE_ALIAS("platform:adp5520-gpio");
168