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