Skip to content

Commit b1a7433

Browse files
mbriandlag-linaro
authored andcommitted
gpio: max7360: Add MAX7360 gpio support
Add driver for Maxim Integrated MAX7360 GPIO/GPO controller. Two sets of GPIOs are provided by the device: - Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities. These GPIOs also provide interrupts on input changes. - Up to 6 GPOs, on unused keypad columns pins. Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Link: https://lore.kernel.org/r/20250824-mdb-max7360-support-v14-7-435cfda2b1ea@bootlin.com Signed-off-by: Lee Jones <lee@kernel.org>
1 parent 0627b71 commit b1a7433

3 files changed

Lines changed: 270 additions & 0 deletions

File tree

drivers/gpio/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,18 @@ config GPIO_MADERA
14921492
help
14931493
Support for GPIOs on Cirrus Logic Madera class codecs.
14941494

1495+
config GPIO_MAX7360
1496+
tristate "MAX7360 GPIO support"
1497+
depends on MFD_MAX7360
1498+
select GPIO_REGMAP
1499+
select REGMAP_IRQ
1500+
help
1501+
Allows to use MAX7360 I/O Expander PWM lines as GPIO and keypad COL
1502+
lines as GPO.
1503+
1504+
This driver can also be built as a module. If so, the module will be
1505+
called gpio-max7360.
1506+
14951507
config GPIO_MAX77620
14961508
tristate "GPIO support for PMIC MAX77620 and MAX20024"
14971509
depends on MFD_MAX77620

drivers/gpio/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
106106
obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o
107107
obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
108108
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
109+
obj-$(CONFIG_GPIO_MAX7360) += gpio-max7360.o
109110
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
110111
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
111112
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o

drivers/gpio/gpio-max7360.c

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright 2025 Bootlin
4+
*
5+
* Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
6+
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
7+
*/
8+
9+
#include <linux/bitfield.h>
10+
#include <linux/bitmap.h>
11+
#include <linux/err.h>
12+
#include <linux/gpio/driver.h>
13+
#include <linux/gpio/regmap.h>
14+
#include <linux/init.h>
15+
#include <linux/interrupt.h>
16+
#include <linux/mfd/max7360.h>
17+
#include <linux/minmax.h>
18+
#include <linux/mod_devicetable.h>
19+
#include <linux/module.h>
20+
#include <linux/platform_device.h>
21+
#include <linux/property.h>
22+
#include <linux/regmap.h>
23+
24+
#define MAX7360_GPIO_PORT 1
25+
#define MAX7360_GPIO_COL 2
26+
27+
struct max7360_gpio_plat_data {
28+
unsigned int function;
29+
};
30+
31+
static struct max7360_gpio_plat_data max7360_gpio_port_plat = { .function = MAX7360_GPIO_PORT };
32+
static struct max7360_gpio_plat_data max7360_gpio_col_plat = { .function = MAX7360_GPIO_COL };
33+
34+
static int max7360_get_available_gpos(struct device *dev, unsigned int *available_gpios)
35+
{
36+
u32 columns;
37+
int ret;
38+
39+
ret = device_property_read_u32(dev->parent, "keypad,num-columns", &columns);
40+
if (ret) {
41+
dev_err(dev, "Failed to read columns count\n");
42+
return ret;
43+
}
44+
45+
*available_gpios = min(MAX7360_MAX_GPO, MAX7360_MAX_KEY_COLS - columns);
46+
47+
return 0;
48+
}
49+
50+
static int max7360_gpo_init_valid_mask(struct gpio_chip *gc,
51+
unsigned long *valid_mask,
52+
unsigned int ngpios)
53+
{
54+
unsigned int available_gpios;
55+
int ret;
56+
57+
ret = max7360_get_available_gpos(gc->parent, &available_gpios);
58+
if (ret)
59+
return ret;
60+
61+
bitmap_clear(valid_mask, 0, MAX7360_MAX_KEY_COLS - available_gpios);
62+
63+
return 0;
64+
}
65+
66+
static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap)
67+
{
68+
/*
69+
* MAX7360 COL0 to COL7 pins can be used either as keypad columns,
70+
* general purpose output or a mix of both.
71+
* By default, all pins are used as keypad, here we update this
72+
* configuration to allow to use some of them as GPIOs.
73+
*/
74+
unsigned int available_gpios;
75+
unsigned int val;
76+
int ret;
77+
78+
ret = max7360_get_available_gpos(dev, &available_gpios);
79+
if (ret)
80+
return ret;
81+
82+
/*
83+
* Configure which GPIOs will be used for keypad.
84+
* MAX7360_REG_DEBOUNCE contains configuration both for keypad debounce
85+
* timings and gpos/keypad columns repartition. Only the later is
86+
* modified here.
87+
*/
88+
val = FIELD_PREP(MAX7360_PORTS, available_gpios);
89+
ret = regmap_write_bits(regmap, MAX7360_REG_DEBOUNCE, MAX7360_PORTS, val);
90+
if (ret)
91+
dev_err(dev, "Failed to write max7360 columns/gpos configuration");
92+
93+
return ret;
94+
}
95+
96+
static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
97+
unsigned int base, unsigned int offset,
98+
unsigned int *reg, unsigned int *mask)
99+
{
100+
if (base == MAX7360_REG_PWMBASE) {
101+
/*
102+
* GPIO output is using PWM duty cycle registers: one register
103+
* per line, with value being either 0 or 255.
104+
*/
105+
*reg = base + offset;
106+
*mask = GENMASK(7, 0);
107+
} else {
108+
*reg = base;
109+
*mask = BIT(offset);
110+
}
111+
112+
return 0;
113+
}
114+
115+
static const struct regmap_irq max7360_regmap_irqs[MAX7360_MAX_GPIO] = {
116+
REGMAP_IRQ_REG(0, 0, BIT(0)),
117+
REGMAP_IRQ_REG(1, 0, BIT(1)),
118+
REGMAP_IRQ_REG(2, 0, BIT(2)),
119+
REGMAP_IRQ_REG(3, 0, BIT(3)),
120+
REGMAP_IRQ_REG(4, 0, BIT(4)),
121+
REGMAP_IRQ_REG(5, 0, BIT(5)),
122+
REGMAP_IRQ_REG(6, 0, BIT(6)),
123+
REGMAP_IRQ_REG(7, 0, BIT(7)),
124+
};
125+
126+
static int max7360_handle_mask_sync(const int index,
127+
const unsigned int mask_buf_def,
128+
const unsigned int mask_buf,
129+
void *const irq_drv_data)
130+
{
131+
struct regmap *regmap = irq_drv_data;
132+
int ret;
133+
134+
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
135+
ret = regmap_assign_bits(regmap, MAX7360_REG_PWMCFG(i),
136+
MAX7360_PORT_CFG_INTERRUPT_MASK, mask_buf & BIT(i));
137+
if (ret)
138+
return ret;
139+
}
140+
141+
return 0;
142+
}
143+
144+
static int max7360_gpio_probe(struct platform_device *pdev)
145+
{
146+
const struct max7360_gpio_plat_data *plat_data;
147+
struct gpio_regmap_config gpio_config = { };
148+
struct regmap_irq_chip *irq_chip;
149+
struct device *dev = &pdev->dev;
150+
struct regmap *regmap;
151+
unsigned int outconf;
152+
int ret;
153+
154+
regmap = dev_get_regmap(dev->parent, NULL);
155+
if (!regmap)
156+
return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");
157+
158+
plat_data = device_get_match_data(dev);
159+
if (plat_data->function == MAX7360_GPIO_PORT) {
160+
if (device_property_read_bool(dev, "interrupt-controller")) {
161+
/*
162+
* Port GPIOs with interrupt-controller property: add IRQ
163+
* controller.
164+
*/
165+
gpio_config.regmap_irq_flags = IRQF_ONESHOT | IRQF_SHARED;
166+
gpio_config.regmap_irq_line =
167+
fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
168+
if (gpio_config.regmap_irq_line < 0)
169+
return dev_err_probe(dev, gpio_config.regmap_irq_line,
170+
"Failed to get IRQ\n");
171+
172+
/* Create custom IRQ configuration. */
173+
irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
174+
gpio_config.regmap_irq_chip = irq_chip;
175+
if (!irq_chip)
176+
return -ENOMEM;
177+
178+
irq_chip->name = dev_name(dev);
179+
irq_chip->status_base = MAX7360_REG_GPIOIN;
180+
irq_chip->status_is_level = true;
181+
irq_chip->num_regs = 1;
182+
irq_chip->num_irqs = MAX7360_MAX_GPIO;
183+
irq_chip->irqs = max7360_regmap_irqs;
184+
irq_chip->handle_mask_sync = max7360_handle_mask_sync;
185+
irq_chip->irq_drv_data = regmap;
186+
187+
for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
188+
ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
189+
MAX7360_PORT_CFG_INTERRUPT_EDGES,
190+
MAX7360_PORT_CFG_INTERRUPT_EDGES);
191+
if (ret)
192+
return dev_err_probe(dev, ret,
193+
"Failed to enable interrupts\n");
194+
}
195+
}
196+
197+
/*
198+
* Port GPIOs: set output mode configuration (constant-current or not).
199+
* This property is optional.
200+
*/
201+
ret = device_property_read_u32(dev, "maxim,constant-current-disable", &outconf);
202+
if (!ret) {
203+
ret = regmap_write(regmap, MAX7360_REG_GPIOOUTM, outconf);
204+
if (ret)
205+
return dev_err_probe(dev, ret,
206+
"Failed to set constant-current configuration\n");
207+
}
208+
}
209+
210+
/* Add gpio device. */
211+
gpio_config.parent = dev;
212+
gpio_config.regmap = regmap;
213+
if (plat_data->function == MAX7360_GPIO_PORT) {
214+
gpio_config.ngpio = MAX7360_MAX_GPIO;
215+
gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOIN);
216+
gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PWMBASE);
217+
gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOCTRL);
218+
gpio_config.ngpio_per_reg = MAX7360_MAX_GPIO;
219+
gpio_config.reg_mask_xlate = max7360_gpio_reg_mask_xlate;
220+
} else {
221+
ret = max7360_set_gpos_count(dev, regmap);
222+
if (ret)
223+
return dev_err_probe(dev, ret, "Failed to set GPOS pin count\n");
224+
225+
gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PORTS);
226+
gpio_config.ngpio = MAX7360_MAX_KEY_COLS;
227+
gpio_config.init_valid_mask = max7360_gpo_init_valid_mask;
228+
}
229+
230+
return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
231+
}
232+
233+
static const struct of_device_id max7360_gpio_of_match[] = {
234+
{
235+
.compatible = "maxim,max7360-gpo",
236+
.data = &max7360_gpio_col_plat
237+
}, {
238+
.compatible = "maxim,max7360-gpio",
239+
.data = &max7360_gpio_port_plat
240+
}, {
241+
}
242+
};
243+
MODULE_DEVICE_TABLE(of, max7360_gpio_of_match);
244+
245+
static struct platform_driver max7360_gpio_driver = {
246+
.driver = {
247+
.name = "max7360-gpio",
248+
.of_match_table = max7360_gpio_of_match,
249+
},
250+
.probe = max7360_gpio_probe,
251+
};
252+
module_platform_driver(max7360_gpio_driver);
253+
254+
MODULE_DESCRIPTION("MAX7360 GPIO driver");
255+
MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
256+
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
257+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)