xref: /illumos-gate/usr/src/uts/common/io/i2c/mux/pca954x/pca954x.c (revision a3ebb524df668b0fc3a38f33d0049380f5f11ec1)
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, &regp);
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