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
|
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2025 Oxide Computer Company
*/
/*
* Basic emulation of a PCA9548. We basically only support passing through a
* single bus so we treat this as a mux. This is definitely a bit of hack.
*/
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/sysmacros.h>
#include <stdbit.h>
#include <sys/debug.h>
#include "i2csimd.h"
#define PCA_NPORTS 8
typedef struct pca9548 {
uint8_t pca_data;
i2csimd_port_t *pca_root;
i2csimd_port_t *pca_ports;
} pca9548_t;
/*
* Only the last byte written is honored as a selection.
*/
static bool
pca9548_write(void *arg, uint32_t len, const uint8_t *buf)
{
pca9548_t *pca = arg;
if (len > 0) {
if (buf[len - 1] == pca->pca_data)
return (true);
if (pca->pca_data != 0) {
uint32_t idx = stdc_first_trailing_one_uc(
pca->pca_data) - 1;
VERIFY3U(idx, <, PCA_NPORTS);
i2csimd_port_t *port = &pca->pca_ports[idx];
for (size_t i = 0; i < ARRAY_SIZE(port->port_devs);
i++) {
if (port->port_devs[i] != NULL)
pca->pca_root->port_devs[i] = NULL;
}
}
pca->pca_data = buf[len - 1];
if (pca->pca_data != 0) {
uint32_t idx = stdc_first_trailing_one_uc(
pca->pca_data) - 1;
VERIFY3U(idx, <, PCA_NPORTS);
i2csimd_port_t *port = &pca->pca_ports[idx];
for (size_t i = 0; i < ARRAY_SIZE(port->port_devs);
i++) {
if (port->port_devs[i] != NULL)
pca->pca_root->port_devs[i] =
port->port_devs[i];
}
}
}
return (true);
}
/*
* It's not clear what happens if we try to read more bytes to one of these, so
* just pick a bad behavior which is fill the entire buffer.
*/
static bool
pca9548_read(void *arg, uint32_t len, uint8_t *buf)
{
pca9548_t *pca = arg;
if (len > 0) {
(void) memset(buf, pca->pca_data, len);
}
return (true);
}
static const i2csimd_ops_t pca9548_ops = {
.sop_write = pca9548_write,
.sop_read = pca9548_read
};
i2csimd_dev_t *
i2csimd_make_pca9548(uint8_t addr, i2csimd_port_t *root,
i2csimd_port_t ports[8])
{
pca9548_t *pca = calloc(1, sizeof (pca9548_t));
if (pca == NULL) {
err(EXIT_FAILURE, "failed to allocate a pca9548_t");
}
pca->pca_data = UINT8_MAX;
pca->pca_root = root;
pca->pca_ports = ports;
i2csimd_dev_t *dev = calloc(1, sizeof (i2csimd_dev_t));
if (dev == NULL) {
err(EXIT_FAILURE, "failed to allocate i2csimd_dev_t");
}
dev->dev_name = "pca9548";
dev->dev_addr = addr;
dev->dev_arg = pca;
dev->dev_ops = &pca9548_ops;
return (dev);
}
|