1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16 /*
17 * Device driver for the PCA954x and compatible devices.
18 *
19 * The PCA954x is a series of classic I2C switches and muxes with relatively
20 * similar programming interfaces. The device is fairly simple. It has a single
21 * control register with a number of bits that correspond to each channel. On
22 * some families when there are less than 8 channels, some of the upper bits are
23 * used for forwarding information about alerts. These are currently not used.
24 *
25 * The following table summarizes supported device families:
26 *
27 * DEVICE PORTS TYPE NOTES
28 * ------ ----- ---- -----
29 * PCA9543 2 Switch Has alert bits in the upper nibble
30 * PCA9545 4 Switch Has alert bits in the upper nibble
31 * PCA9546 4 Switch -
32 * PCA9548 8 Switch -
33 * PCA9846 4 Switch Has device ID register (0x10b)
34 * PCA9848 8 Switch Has device ID register (0x10a)
35 *
36 * It would also be reasonable in the future to add support for the variants
37 * that have an explicit enable field.
38 */
39
40 #include <sys/modctl.h>
41 #include <sys/conf.h>
42 #include <sys/devops.h>
43 #include <sys/ddi.h>
44 #include <sys/sunddi.h>
45 #include <sys/sysmacros.h>
46 #include <sys/bitext.h>
47
48 #include <sys/i2c/mux.h>
49 #include <sys/i2c/client.h>
50
51 /*
52 * The PCA954x family that we suppot is arranged such that it has a single byte
53 * control register. There is no index to read or write here. We use the SMBus
54 * interface in this driver for simplicity.
55 */
56 #define PCA_954X_SET_CHAN(r, idx, v) bitset8(r, idx, idx, v)
57 #define PCA_954X_CHAN_DIS 0
58 #define PCA_954X_CHAN_EN 1
59 #define PCA_954X_GET_INT(r, idx, v) bitx8(r, idx + 4, idx + 4)
60
61 /*
62 * This represents the common names that the PCA94x can use. The first name is
63 * the common name that an administrator manually creating the device might use.
64 * The second is a the compatible-style entry that is commonly found in the wild
65 * for the various device trees.
66 */
67 typedef struct {
68 const char *pi_name;
69 const char *pi_compat;
70 uint32_t pi_nports;
71 bool pi_intr;
72 } pca954x_ident_t;
73
74 static const pca954x_ident_t pca954x_idents[] = {
75 { "pca9543", "nxp,pca9543", 2, true },
76 { "pca9545", "nxp,pca9545", 4, true },
77 { "pca9546", "nxp,pca9546", 4, false },
78 { "pca9548", "nxp,pca9548", 8, false },
79 { "pca9846", "nxp,pca9846", 4, false },
80 { "pca9848", "nxp,pca9848", 4, false }
81 };
82
83 typedef struct pca954x {
84 dev_info_t *pca_dip;
85 const pca954x_ident_t *pca_ident;
86 i2c_client_t *pca_client;
87 i2c_mux_hdl_t *pca_mux;
88 } pca954x_t;
89
90 static bool
pca954x_port_enable(void * arg,i2c_txn_t * txn,uint32_t port,uint32_t flags,i2c_error_t * err)91 pca954x_port_enable(void *arg, i2c_txn_t *txn, uint32_t port, uint32_t flags,
92 i2c_error_t *err)
93 {
94 pca954x_t *pca = arg;
95
96 if (flags != 0) {
97 return (i2c_io_error(err, I2C_MUX_E_BAD_FLAG, 0));
98 }
99
100 VERIFY3U(port, !=, I2C_MUX_PORT_ALL);
101 VERIFY3U(port, <, pca->pca_ident->pi_nports);
102 uint8_t val = PCA_954X_SET_CHAN(0, port, PCA_954X_CHAN_EN);
103
104
105 if (!smbus_client_send_byte(txn, pca->pca_client, val, err)) {
106 return (false);
107 }
108
109 return (true);
110 }
111
112 static bool
pca954x_port_disable(void * arg,i2c_txn_t * txn,uint32_t port,uint32_t flags,i2c_error_t * err)113 pca954x_port_disable(void *arg, i2c_txn_t *txn, uint32_t port, uint32_t flags,
114 i2c_error_t *err)
115 {
116 pca954x_t *pca = arg;
117 uint8_t val = 0;
118
119 if (flags != 0) {
120 return (i2c_io_error(err, I2C_MUX_E_BAD_FLAG, 0));
121 }
122
123 VERIFY3U(port, ==, I2C_MUX_PORT_ALL);
124 for (uint32_t i = 0; i < pca->pca_ident->pi_nports; i++) {
125 val = PCA_954X_SET_CHAN(val, i, PCA_954X_CHAN_DIS);
126 }
127
128 if (!smbus_client_send_byte(txn, pca->pca_client, 0, err)) {
129 return (false);
130 }
131
132 return (true);
133 }
134
135 static const i2c_mux_ops_t pca954x_mux_ops = {
136 .mux_port_name_f = i2c_mux_port_name_portno,
137 .mux_port_enable_f = pca954x_port_enable,
138 .mux_port_disable_f = pca954x_port_disable
139 };
140
141 /*
142 * Attempt to match an instance of the driver using the binding name and its
143 * actual name itself.
144 */
145 static bool
pca954x_identify(pca954x_t * pca)146 pca954x_identify(pca954x_t *pca)
147 {
148 const char *bind = ddi_binding_name(pca->pca_dip);
149 const char *name = ddi_node_name(pca->pca_dip);
150
151 for (size_t i = 0; i < ARRAY_SIZE(pca954x_idents); i++) {
152 if (strcmp(bind, pca954x_idents[i].pi_name) == 0 ||
153 strcmp(bind, pca954x_idents[i].pi_compat) == 0 ||
154 strcmp(name, pca954x_idents[i].pi_name) == 0 ||
155 strcmp(name, pca954x_idents[i].pi_compat) == 0) {
156 pca->pca_ident = &pca954x_idents[i];
157 return (true);
158 }
159 }
160
161 dev_err(pca->pca_dip, CE_WARN, "failed to match against node name %s "
162 "and binding name %s", name, bind);
163 return (false);
164 }
165
166 static bool
pca954x_i2c_init(pca954x_t * pca)167 pca954x_i2c_init(pca954x_t *pca)
168 {
169 i2c_errno_t err;
170
171 if ((err = i2c_client_init(pca->pca_dip, 0, &pca->pca_client)) !=
172 I2C_CORE_E_OK) {
173 dev_err(pca->pca_dip, CE_WARN, "failed to create i2c client: "
174 "0x%x", err);
175 return (false);
176 }
177
178 return (true);
179 }
180
181 static bool
pca954x_mux_init(pca954x_t * pca)182 pca954x_mux_init(pca954x_t *pca)
183 {
184 i2c_mux_reg_error_t ret;
185 i2c_mux_register_t *regp;
186
187 ret = i2c_mux_register_alloc(I2C_MUX_PROVIDER, ®p);
188 if (ret != I2C_MUX_REG_E_OK) {
189 dev_err(pca->pca_dip, CE_WARN, "failed to get mux reister "
190 "structure: 0x%x", ret);
191 return (false);
192 }
193
194 regp->mr_nports = pca->pca_ident->pi_nports;
195 regp->mr_dip = pca->pca_dip;
196 regp->mr_drv = pca;
197 regp->mr_ops = &pca954x_mux_ops;
198
199 ret = i2c_mux_register(regp, &pca->pca_mux);
200 i2c_mux_register_free(regp);
201 if (ret != I2C_MUX_REG_E_OK) {
202 dev_err(pca->pca_dip, CE_WARN, "failed to register with i2c "
203 "mux framework: 0x%x", ret);
204 return (false);
205 }
206
207 return (true);
208 }
209
210 static void
pca954x_cleanup(pca954x_t * pca)211 pca954x_cleanup(pca954x_t *pca)
212 {
213 i2c_client_destroy(pca->pca_client);
214 ddi_set_driver_private(pca->pca_dip, NULL);
215 pca->pca_dip = NULL;
216 kmem_free(pca, sizeof (pca954x_t));
217 }
218
219 int
pca954x_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)220 pca954x_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
221 {
222 pca954x_t *pca;
223
224 switch (cmd) {
225 case DDI_ATTACH:
226 break;
227 case DDI_RESUME:
228 return (DDI_SUCCESS);
229 default:
230 return (DDI_FAILURE);
231 }
232
233 pca = kmem_zalloc(sizeof (pca954x_t), KM_SLEEP);
234 pca->pca_dip = dip;
235 ddi_set_driver_private(dip, pca);
236
237 if (!pca954x_identify(pca))
238 goto cleanup;
239
240 if (!pca954x_i2c_init(pca))
241 goto cleanup;
242
243 if (!pca954x_mux_init(pca))
244 goto cleanup;
245
246 return (DDI_SUCCESS);
247
248 cleanup:
249 pca954x_cleanup(pca);
250 return (DDI_FAILURE);
251 }
252
253 int
pca954x_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)254 pca954x_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
255 {
256 pca954x_t *pca;
257
258 switch (cmd) {
259 case DDI_DETACH:
260 break;
261 case DDI_SUSPEND:
262 return (DDI_SUCCESS);
263 default:
264 return (DDI_FAILURE);
265 }
266
267 pca = ddi_get_driver_private(dip);
268 if (pca == NULL) {
269 dev_err(dip, CE_WARN, "asked to detach, but missing private "
270 "data");
271 return (DDI_FAILURE);
272 }
273 VERIFY3P(pca->pca_dip, ==, dip);
274
275
276 if (i2c_mux_unregister(pca->pca_mux) != I2C_MUX_REG_E_OK) {
277 return (DDI_FAILURE);
278 }
279
280 pca954x_cleanup(pca);
281
282 return (DDI_SUCCESS);
283 }
284
285 static struct dev_ops pca954x_dev_ops = {
286 .devo_rev = DEVO_REV,
287 .devo_refcnt = 0,
288 .devo_identify = nulldev,
289 .devo_probe = nulldev,
290 .devo_attach = pca954x_attach,
291 .devo_detach = pca954x_detach,
292 .devo_reset = nodev,
293 .devo_quiesce = ddi_quiesce_not_needed
294 };
295
296 static struct modldrv pca954x_modldrv = {
297 .drv_modops = &mod_driverops,
298 .drv_linkinfo = "PCA954x I2C Switch",
299 .drv_dev_ops = &pca954x_dev_ops
300 };
301
302 static struct modlinkage pca954x_modlinkage = {
303 .ml_rev = MODREV_1,
304 .ml_linkage = { &pca954x_modldrv, NULL }
305 };
306
307 int
_init(void)308 _init(void)
309 {
310 int ret;
311
312 i2c_mux_mod_init(&pca954x_dev_ops);
313 if ((ret = mod_install(&pca954x_modlinkage)) != 0) {
314 i2c_mux_mod_fini(&pca954x_dev_ops);
315 }
316
317 return (ret);
318 }
319
320 int
_info(struct modinfo * modinfop)321 _info(struct modinfo *modinfop)
322 {
323 return (mod_info(&pca954x_modlinkage, modinfop));
324 }
325
326 int
_fini(void)327 _fini(void)
328 {
329 int ret;
330
331 if ((ret = mod_remove(&pca954x_modlinkage)) == 0) {
332 i2c_mux_mod_fini(&pca954x_dev_ops);
333 }
334
335 return (ret);
336 }
337