1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Maxim MAX7360 Core Driver
*
* Copyright 2025 Bootlin
*
* Authors:
* Kamel Bouhara <kamel.bouhara@bootlin.com>
* Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device/devres.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mfd/core.h>
#include <linux/mfd/max7360.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/types.h>
static const struct mfd_cell max7360_cells[] = {
{ .name = "max7360-pinctrl" },
{ .name = "max7360-pwm" },
{ .name = "max7360-keypad" },
{ .name = "max7360-rotary" },
{
.name = "max7360-gpo",
.of_compatible = "maxim,max7360-gpo",
},
{
.name = "max7360-gpio",
.of_compatible = "maxim,max7360-gpio",
},
};
static const struct regmap_range max7360_volatile_ranges[] = {
regmap_reg_range(MAX7360_REG_KEYFIFO, MAX7360_REG_KEYFIFO),
regmap_reg_range(MAX7360_REG_I2C_TIMEOUT, MAX7360_REG_RTR_CNT),
};
static const struct regmap_access_table max7360_volatile_table = {
.yes_ranges = max7360_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(max7360_volatile_ranges),
};
static const struct regmap_config max7360_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX7360_REG_PWMCFG(MAX7360_PORT_PWM_COUNT - 1),
.volatile_table = &max7360_volatile_table,
.cache_type = REGCACHE_MAPLE,
};
static int max7360_mask_irqs(struct regmap *regmap)
{
struct device *dev = regmap_get_device(regmap);
unsigned int val;
int ret;
/*
* GPIO/PWM interrupts are not masked on reset: as the MAX7360 "INTI"
* interrupt line is shared between GPIOs and rotary encoder, this could
* result in repeated spurious interrupts on the rotary encoder driver
* if the GPIO driver is not loaded. Mask them now to avoid this
* situation.
*/
for (unsigned int i = 0; i < MAX7360_PORT_PWM_COUNT; i++) {
ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
MAX7360_PORT_CFG_INTERRUPT_MASK,
MAX7360_PORT_CFG_INTERRUPT_MASK);
if (ret)
return dev_err_probe(dev, ret,
"Failed to write MAX7360 port configuration\n");
}
/* Read GPIO in register, to ACK any pending IRQ. */
ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
if (ret)
return dev_err_probe(dev, ret, "Failed to read GPIO values\n");
return 0;
}
static int max7360_reset(struct regmap *regmap)
{
struct device *dev = regmap_get_device(regmap);
int ret;
ret = regmap_write(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_GPIO_RST);
if (ret) {
dev_err(dev, "Failed to reset GPIO configuration: %x\n", ret);
return ret;
}
ret = regcache_drop_region(regmap, MAX7360_REG_GPIOCFG, MAX7360_REG_GPIO_LAST);
if (ret) {
dev_err(dev, "Failed to drop regmap cache: %x\n", ret);
return ret;
}
ret = regmap_write(regmap, MAX7360_REG_SLEEP, 0);
if (ret) {
dev_err(dev, "Failed to reset autosleep configuration: %x\n", ret);
return ret;
}
ret = regmap_write(regmap, MAX7360_REG_DEBOUNCE, 0);
if (ret)
dev_err(dev, "Failed to reset GPO port count: %x\n", ret);
return ret;
}
static int max7360_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct regmap *regmap;
int ret;
regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
ret = max7360_reset(regmap);
if (ret)
return dev_err_probe(dev, ret, "Failed to reset device\n");
/* Get the device out of shutdown mode. */
ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCFG,
MAX7360_GPIO_CFG_GPIO_EN,
MAX7360_GPIO_CFG_GPIO_EN);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable GPIO and PWM module\n");
ret = max7360_mask_irqs(regmap);
if (ret)
return dev_err_probe(dev, ret, "Could not mask interrupts\n");
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
max7360_cells, ARRAY_SIZE(max7360_cells),
NULL, 0, NULL);
if (ret)
return dev_err_probe(dev, ret, "Failed to register child devices\n");
return 0;
}
static const struct of_device_id max7360_dt_match[] = {
{ .compatible = "maxim,max7360" },
{}
};
MODULE_DEVICE_TABLE(of, max7360_dt_match);
static struct i2c_driver max7360_driver = {
.driver = {
.name = "max7360",
.of_match_table = max7360_dt_match,
},
.probe = max7360_probe,
};
module_i2c_driver(max7360_driver);
MODULE_DESCRIPTION("Maxim MAX7360 I2C IO Expander core driver");
MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
MODULE_LICENSE("GPL");
|