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
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* FXL6408 GPIO driver
*
* Copyright 2023 Toradex
*
* Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
*/
#include <linux/err.h>
#include <linux/gpio/regmap.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#define FXL6408_REG_DEVICE_ID 0x01
#define FXL6408_MF_FAIRCHILD 0b101
#define FXL6408_MF_SHIFT 5
/* Bits set here indicate that the GPIO is an output. */
#define FXL6408_REG_IO_DIR 0x03
/*
* Bits set here, when the corresponding bit of IO_DIR is set, drive
* the output high instead of low.
*/
#define FXL6408_REG_OUTPUT 0x05
/* Bits here make the output High-Z, instead of the OUTPUT value. */
#define FXL6408_REG_OUTPUT_HIGH_Z 0x07
/* Returns the current status (1 = HIGH) of the input pins. */
#define FXL6408_REG_INPUT_STATUS 0x0f
/*
* Return the current interrupt status
* This bit is HIGH if input GPIO != default state (register 09h).
* The flag is cleared after being read (bit returns to 0).
* The input must go back to default state and change again before this flag is raised again.
*/
#define FXL6408_REG_INT_STS 0x13
#define FXL6408_NGPIO 8
static const struct regmap_range rd_range[] = {
{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
{ FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
{ FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
};
static const struct regmap_range wr_range[] = {
{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
{ FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
{ FXL6408_REG_OUTPUT_HIGH_Z, FXL6408_REG_OUTPUT_HIGH_Z },
};
static const struct regmap_range volatile_range[] = {
{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
{ FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
};
static const struct regmap_access_table rd_table = {
.yes_ranges = rd_range,
.n_yes_ranges = ARRAY_SIZE(rd_range),
};
static const struct regmap_access_table wr_table = {
.yes_ranges = wr_range,
.n_yes_ranges = ARRAY_SIZE(wr_range),
};
static const struct regmap_access_table volatile_table = {
.yes_ranges = volatile_range,
.n_yes_ranges = ARRAY_SIZE(volatile_range),
};
static const struct regmap_config regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = FXL6408_REG_INT_STS,
.wr_table = &wr_table,
.rd_table = &rd_table,
.volatile_table = &volatile_table,
.cache_type = REGCACHE_MAPLE,
.num_reg_defaults_raw = FXL6408_REG_INT_STS + 1,
};
static int fxl6408_identify(struct device *dev, struct regmap *regmap)
{
int val, ret;
ret = regmap_read(regmap, FXL6408_REG_DEVICE_ID, &val);
if (ret)
return dev_err_probe(dev, ret, "error reading DEVICE_ID\n");
if (val >> FXL6408_MF_SHIFT != FXL6408_MF_FAIRCHILD)
return dev_err_probe(dev, -ENODEV, "invalid device id 0x%02x\n", val);
return 0;
}
static int fxl6408_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
int ret;
struct gpio_regmap_config gpio_config = {
.parent = dev,
.ngpio = FXL6408_NGPIO,
.reg_dat_base = GPIO_REGMAP_ADDR(FXL6408_REG_INPUT_STATUS),
.reg_set_base = GPIO_REGMAP_ADDR(FXL6408_REG_OUTPUT),
.reg_dir_out_base = GPIO_REGMAP_ADDR(FXL6408_REG_IO_DIR),
.ngpio_per_reg = FXL6408_NGPIO,
};
gpio_config.regmap = devm_regmap_init_i2c(client, ®map);
if (IS_ERR(gpio_config.regmap))
return dev_err_probe(dev, PTR_ERR(gpio_config.regmap),
"failed to allocate register map\n");
ret = fxl6408_identify(dev, gpio_config.regmap);
if (ret)
return ret;
/* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */
ret = regmap_write(gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, 0);
if (ret)
return dev_err_probe(dev, ret, "failed to write 'output high Z' register\n");
return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
}
static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = {
{ .compatible = "fcs,fxl6408" },
{ }
};
MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);
static const struct i2c_device_id fxl6408_id[] = {
{ "fxl6408" },
{ }
};
MODULE_DEVICE_TABLE(i2c, fxl6408_id);
static struct i2c_driver fxl6408_driver = {
.driver = {
.name = "fxl6408",
.of_match_table = fxl6408_dt_ids,
},
.probe = fxl6408_probe,
.id_table = fxl6408_id,
};
module_i2c_driver(fxl6408_driver);
MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
MODULE_DESCRIPTION("FXL6408 GPIO driver");
MODULE_LICENSE("GPL");
|