1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * |
4 | * x9250.c -- Renesas X9250 potentiometers IIO driver |
5 | * |
6 | * Copyright 2023 CS GROUP France |
7 | * |
8 | * Author: Herve Codina <herve.codina@bootlin.com> |
9 | */ |
10 | |
11 | #include <linux/delay.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/iio/iio.h> |
14 | #include <linux/limits.h> |
15 | #include <linux/module.h> |
16 | #include <linux/regulator/consumer.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/spi/spi.h> |
19 | |
20 | struct x9250_cfg { |
21 | const char *name; |
22 | int kohms; |
23 | }; |
24 | |
25 | struct x9250 { |
26 | struct spi_device *spi; |
27 | const struct x9250_cfg *cfg; |
28 | struct gpio_desc *wp_gpio; |
29 | }; |
30 | |
31 | #define X9250_ID 0x50 |
32 | #define X9250_CMD_RD_WCR(_p) (0x90 | (_p)) |
33 | #define X9250_CMD_WR_WCR(_p) (0xa0 | (_p)) |
34 | |
35 | static int x9250_write8(struct x9250 *x9250, u8 cmd, u8 val) |
36 | { |
37 | u8 txbuf[3]; |
38 | |
39 | txbuf[0] = X9250_ID; |
40 | txbuf[1] = cmd; |
41 | txbuf[2] = val; |
42 | |
43 | return spi_write_then_read(spi: x9250->spi, txbuf, ARRAY_SIZE(txbuf), NULL, n_rx: 0); |
44 | } |
45 | |
46 | static int x9250_read8(struct x9250 *x9250, u8 cmd, u8 *val) |
47 | { |
48 | u8 txbuf[2]; |
49 | |
50 | txbuf[0] = X9250_ID; |
51 | txbuf[1] = cmd; |
52 | |
53 | return spi_write_then_read(spi: x9250->spi, txbuf, ARRAY_SIZE(txbuf), rxbuf: val, n_rx: 1); |
54 | } |
55 | |
56 | #define X9250_CHANNEL(ch) { \ |
57 | .type = IIO_RESISTANCE, \ |
58 | .indexed = 1, \ |
59 | .output = 1, \ |
60 | .channel = (ch), \ |
61 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
62 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
63 | .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \ |
64 | } |
65 | |
66 | static const struct iio_chan_spec x9250_channels[] = { |
67 | X9250_CHANNEL(0), |
68 | X9250_CHANNEL(1), |
69 | X9250_CHANNEL(2), |
70 | X9250_CHANNEL(3), |
71 | }; |
72 | |
73 | static int x9250_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, |
74 | int *val, int *val2, long mask) |
75 | { |
76 | struct x9250 *x9250 = iio_priv(indio_dev); |
77 | int ch = chan->channel; |
78 | int ret; |
79 | u8 v; |
80 | |
81 | switch (mask) { |
82 | case IIO_CHAN_INFO_RAW: |
83 | ret = x9250_read8(x9250, X9250_CMD_RD_WCR(ch), val: &v); |
84 | if (ret) |
85 | return ret; |
86 | *val = v; |
87 | return IIO_VAL_INT; |
88 | |
89 | case IIO_CHAN_INFO_SCALE: |
90 | *val = 1000 * x9250->cfg->kohms; |
91 | *val2 = U8_MAX; |
92 | return IIO_VAL_FRACTIONAL; |
93 | } |
94 | |
95 | return -EINVAL; |
96 | } |
97 | |
98 | static int x9250_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, |
99 | const int **vals, int *type, int *length, long mask) |
100 | { |
101 | static const int range[] = {0, 1, 255}; /* min, step, max */ |
102 | |
103 | switch (mask) { |
104 | case IIO_CHAN_INFO_RAW: |
105 | *length = ARRAY_SIZE(range); |
106 | *vals = range; |
107 | *type = IIO_VAL_INT; |
108 | return IIO_AVAIL_RANGE; |
109 | } |
110 | |
111 | return -EINVAL; |
112 | } |
113 | |
114 | static int x9250_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, |
115 | int val, int val2, long mask) |
116 | { |
117 | struct x9250 *x9250 = iio_priv(indio_dev); |
118 | int ch = chan->channel; |
119 | int ret; |
120 | |
121 | if (mask != IIO_CHAN_INFO_RAW) |
122 | return -EINVAL; |
123 | |
124 | if (val > U8_MAX || val < 0) |
125 | return -EINVAL; |
126 | |
127 | gpiod_set_value_cansleep(desc: x9250->wp_gpio, value: 0); |
128 | ret = x9250_write8(x9250, X9250_CMD_WR_WCR(ch), val); |
129 | gpiod_set_value_cansleep(desc: x9250->wp_gpio, value: 1); |
130 | |
131 | return ret; |
132 | } |
133 | |
134 | static const struct iio_info x9250_info = { |
135 | .read_raw = x9250_read_raw, |
136 | .read_avail = x9250_read_avail, |
137 | .write_raw = x9250_write_raw, |
138 | }; |
139 | |
140 | enum x9250_type { |
141 | X9250T, |
142 | X9250U, |
143 | }; |
144 | |
145 | static const struct x9250_cfg x9250_cfg[] = { |
146 | [X9250T] = { .name = "x9250t" , .kohms = 100, }, |
147 | [X9250U] = { .name = "x9250u" , .kohms = 50, }, |
148 | }; |
149 | |
150 | static const char *const x9250_regulator_names[] = { |
151 | "vcc" , |
152 | "avp" , |
153 | "avn" , |
154 | }; |
155 | |
156 | static int x9250_probe(struct spi_device *spi) |
157 | { |
158 | struct iio_dev *indio_dev; |
159 | struct x9250 *x9250; |
160 | int ret; |
161 | |
162 | ret = devm_regulator_bulk_get_enable(dev: &spi->dev, ARRAY_SIZE(x9250_regulator_names), |
163 | id: x9250_regulator_names); |
164 | if (ret) |
165 | return dev_err_probe(dev: &spi->dev, err: ret, fmt: "Failed to get regulators\n" ); |
166 | |
167 | /* |
168 | * The x9250 needs a 5ms maximum delay after the power-supplies are set |
169 | * before performing the first write (1ms for the first read). |
170 | */ |
171 | usleep_range(min: 5000, max: 6000); |
172 | |
173 | indio_dev = devm_iio_device_alloc(parent: &spi->dev, sizeof_priv: sizeof(*x9250)); |
174 | if (!indio_dev) |
175 | return -ENOMEM; |
176 | |
177 | x9250 = iio_priv(indio_dev); |
178 | x9250->spi = spi; |
179 | x9250->cfg = spi_get_device_match_data(sdev: spi); |
180 | x9250->wp_gpio = devm_gpiod_get_optional(dev: &spi->dev, con_id: "wp" , flags: GPIOD_OUT_LOW); |
181 | if (IS_ERR(ptr: x9250->wp_gpio)) |
182 | return dev_err_probe(dev: &spi->dev, err: PTR_ERR(ptr: x9250->wp_gpio), |
183 | fmt: "failed to get wp gpio\n" ); |
184 | |
185 | indio_dev->info = &x9250_info; |
186 | indio_dev->channels = x9250_channels; |
187 | indio_dev->num_channels = ARRAY_SIZE(x9250_channels); |
188 | indio_dev->name = x9250->cfg->name; |
189 | |
190 | return devm_iio_device_register(&spi->dev, indio_dev); |
191 | } |
192 | |
193 | static const struct of_device_id x9250_of_match[] = { |
194 | { .compatible = "renesas,x9250t" , .data = &x9250_cfg[X9250T]}, |
195 | { .compatible = "renesas,x9250u" , .data = &x9250_cfg[X9250U]}, |
196 | { } |
197 | }; |
198 | MODULE_DEVICE_TABLE(of, x9250_of_match); |
199 | |
200 | static const struct spi_device_id x9250_id_table[] = { |
201 | { "x9250t" , (kernel_ulong_t)&x9250_cfg[X9250T] }, |
202 | { "x9250u" , (kernel_ulong_t)&x9250_cfg[X9250U] }, |
203 | { } |
204 | }; |
205 | MODULE_DEVICE_TABLE(spi, x9250_id_table); |
206 | |
207 | static struct spi_driver x9250_spi_driver = { |
208 | .driver = { |
209 | .name = "x9250" , |
210 | .of_match_table = x9250_of_match, |
211 | }, |
212 | .id_table = x9250_id_table, |
213 | .probe = x9250_probe, |
214 | }; |
215 | |
216 | module_spi_driver(x9250_spi_driver); |
217 | |
218 | MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>" ); |
219 | MODULE_DESCRIPTION("X9250 ALSA SoC driver" ); |
220 | MODULE_LICENSE("GPL" ); |
221 | |