| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * FXL6408 GPIO driver |
| 4 | * |
| 5 | * Copyright 2023 Toradex |
| 6 | * |
| 7 | * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com> |
| 8 | */ |
| 9 | |
| 10 | #include <linux/err.h> |
| 11 | #include <linux/gpio/regmap.h> |
| 12 | #include <linux/i2c.h> |
| 13 | #include <linux/kernel.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/regmap.h> |
| 16 | |
| 17 | #define FXL6408_REG_DEVICE_ID 0x01 |
| 18 | #define FXL6408_MF_FAIRCHILD 0b101 |
| 19 | #define FXL6408_MF_SHIFT 5 |
| 20 | |
| 21 | /* Bits set here indicate that the GPIO is an output. */ |
| 22 | #define FXL6408_REG_IO_DIR 0x03 |
| 23 | |
| 24 | /* |
| 25 | * Bits set here, when the corresponding bit of IO_DIR is set, drive |
| 26 | * the output high instead of low. |
| 27 | */ |
| 28 | #define FXL6408_REG_OUTPUT 0x05 |
| 29 | |
| 30 | /* Bits here make the output High-Z, instead of the OUTPUT value. */ |
| 31 | #define FXL6408_REG_OUTPUT_HIGH_Z 0x07 |
| 32 | |
| 33 | /* Returns the current status (1 = HIGH) of the input pins. */ |
| 34 | #define FXL6408_REG_INPUT_STATUS 0x0f |
| 35 | |
| 36 | /* |
| 37 | * Return the current interrupt status |
| 38 | * This bit is HIGH if input GPIO != default state (register 09h). |
| 39 | * The flag is cleared after being read (bit returns to 0). |
| 40 | * The input must go back to default state and change again before this flag is raised again. |
| 41 | */ |
| 42 | #define FXL6408_REG_INT_STS 0x13 |
| 43 | |
| 44 | #define FXL6408_NGPIO 8 |
| 45 | |
| 46 | static const struct regmap_range rd_range[] = { |
| 47 | { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID }, |
| 48 | { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT }, |
| 49 | { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS }, |
| 50 | }; |
| 51 | |
| 52 | static const struct regmap_range wr_range[] = { |
| 53 | { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID }, |
| 54 | { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT }, |
| 55 | { FXL6408_REG_OUTPUT_HIGH_Z, FXL6408_REG_OUTPUT_HIGH_Z }, |
| 56 | }; |
| 57 | |
| 58 | static const struct regmap_range volatile_range[] = { |
| 59 | { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID }, |
| 60 | { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS }, |
| 61 | }; |
| 62 | |
| 63 | static const struct regmap_access_table rd_table = { |
| 64 | .yes_ranges = rd_range, |
| 65 | .n_yes_ranges = ARRAY_SIZE(rd_range), |
| 66 | }; |
| 67 | |
| 68 | static const struct regmap_access_table wr_table = { |
| 69 | .yes_ranges = wr_range, |
| 70 | .n_yes_ranges = ARRAY_SIZE(wr_range), |
| 71 | }; |
| 72 | |
| 73 | static const struct regmap_access_table volatile_table = { |
| 74 | .yes_ranges = volatile_range, |
| 75 | .n_yes_ranges = ARRAY_SIZE(volatile_range), |
| 76 | }; |
| 77 | |
| 78 | static const struct regmap_config regmap = { |
| 79 | .reg_bits = 8, |
| 80 | .val_bits = 8, |
| 81 | |
| 82 | .max_register = FXL6408_REG_INT_STS, |
| 83 | .wr_table = &wr_table, |
| 84 | .rd_table = &rd_table, |
| 85 | .volatile_table = &volatile_table, |
| 86 | |
| 87 | .cache_type = REGCACHE_MAPLE, |
| 88 | .num_reg_defaults_raw = FXL6408_REG_INT_STS + 1, |
| 89 | }; |
| 90 | |
| 91 | static int fxl6408_identify(struct device *dev, struct regmap *regmap) |
| 92 | { |
| 93 | int val, ret; |
| 94 | |
| 95 | ret = regmap_read(map: regmap, FXL6408_REG_DEVICE_ID, val: &val); |
| 96 | if (ret) |
| 97 | return dev_err_probe(dev, err: ret, fmt: "error reading DEVICE_ID\n" ); |
| 98 | if (val >> FXL6408_MF_SHIFT != FXL6408_MF_FAIRCHILD) |
| 99 | return dev_err_probe(dev, err: -ENODEV, fmt: "invalid device id 0x%02x\n" , val); |
| 100 | |
| 101 | return 0; |
| 102 | } |
| 103 | |
| 104 | static int fxl6408_probe(struct i2c_client *client) |
| 105 | { |
| 106 | struct device *dev = &client->dev; |
| 107 | int ret; |
| 108 | struct gpio_regmap_config gpio_config = { |
| 109 | .parent = dev, |
| 110 | .ngpio = FXL6408_NGPIO, |
| 111 | .reg_dat_base = GPIO_REGMAP_ADDR(FXL6408_REG_INPUT_STATUS), |
| 112 | .reg_set_base = GPIO_REGMAP_ADDR(FXL6408_REG_OUTPUT), |
| 113 | .reg_dir_out_base = GPIO_REGMAP_ADDR(FXL6408_REG_IO_DIR), |
| 114 | .ngpio_per_reg = FXL6408_NGPIO, |
| 115 | }; |
| 116 | |
| 117 | gpio_config.regmap = devm_regmap_init_i2c(client, ®map); |
| 118 | if (IS_ERR(ptr: gpio_config.regmap)) |
| 119 | return dev_err_probe(dev, err: PTR_ERR(ptr: gpio_config.regmap), |
| 120 | fmt: "failed to allocate register map\n" ); |
| 121 | |
| 122 | ret = fxl6408_identify(dev, regmap: gpio_config.regmap); |
| 123 | if (ret) |
| 124 | return ret; |
| 125 | |
| 126 | i2c_set_clientdata(client, data: gpio_config.regmap); |
| 127 | |
| 128 | /* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */ |
| 129 | ret = regmap_write(map: gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, val: 0); |
| 130 | if (ret) |
| 131 | return dev_err_probe(dev, err: ret, fmt: "failed to write 'output high Z' register\n" ); |
| 132 | |
| 133 | return PTR_ERR_OR_ZERO(ptr: devm_gpio_regmap_register(dev, config: &gpio_config)); |
| 134 | } |
| 135 | |
| 136 | static int fxl6408_resume(struct device *dev) |
| 137 | { |
| 138 | struct regmap *regmap = dev_get_drvdata(dev); |
| 139 | |
| 140 | regcache_mark_dirty(map: regmap); |
| 141 | return regcache_sync(map: regmap); |
| 142 | } |
| 143 | |
| 144 | static DEFINE_SIMPLE_DEV_PM_OPS(fxl6408_pm_ops, NULL, fxl6408_resume); |
| 145 | |
| 146 | static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = { |
| 147 | { .compatible = "fcs,fxl6408" }, |
| 148 | { } |
| 149 | }; |
| 150 | MODULE_DEVICE_TABLE(of, fxl6408_dt_ids); |
| 151 | |
| 152 | static const struct i2c_device_id fxl6408_id[] = { |
| 153 | { "fxl6408" }, |
| 154 | { } |
| 155 | }; |
| 156 | MODULE_DEVICE_TABLE(i2c, fxl6408_id); |
| 157 | |
| 158 | static struct i2c_driver fxl6408_driver = { |
| 159 | .driver = { |
| 160 | .name = "fxl6408" , |
| 161 | .pm = pm_sleep_ptr(&fxl6408_pm_ops), |
| 162 | .of_match_table = fxl6408_dt_ids, |
| 163 | }, |
| 164 | .probe = fxl6408_probe, |
| 165 | .id_table = fxl6408_id, |
| 166 | }; |
| 167 | module_i2c_driver(fxl6408_driver); |
| 168 | |
| 169 | MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>" ); |
| 170 | MODULE_DESCRIPTION("FXL6408 GPIO driver" ); |
| 171 | MODULE_LICENSE("GPL" ); |
| 172 | |