1*04a1c1a1SRobert Mustacchi /*
2*04a1c1a1SRobert Mustacchi * This file and its contents are supplied under the terms of the
3*04a1c1a1SRobert Mustacchi * Common Development and Distribution License ("CDDL"), version 1.0.
4*04a1c1a1SRobert Mustacchi * You may only use this file in accordance with the terms of version
5*04a1c1a1SRobert Mustacchi * 1.0 of the CDDL.
6*04a1c1a1SRobert Mustacchi *
7*04a1c1a1SRobert Mustacchi * A full copy of the text of the CDDL should have accompanied this
8*04a1c1a1SRobert Mustacchi * source. A copy of the CDDL is also available via the Internet at
9*04a1c1a1SRobert Mustacchi * http://www.illumos.org/license/CDDL.
10*04a1c1a1SRobert Mustacchi */
11*04a1c1a1SRobert Mustacchi
12*04a1c1a1SRobert Mustacchi /*
13*04a1c1a1SRobert Mustacchi * Copyright 2025 Oxide Computer Company
14*04a1c1a1SRobert Mustacchi */
15*04a1c1a1SRobert Mustacchi
16*04a1c1a1SRobert Mustacchi /*
17*04a1c1a1SRobert Mustacchi * DDR5 SPD5118 Hub Driver.
18*04a1c1a1SRobert Mustacchi *
19*04a1c1a1SRobert Mustacchi * The Hub has an integrated temperature sensor and a 1024 KiB EEPROM. This is
20*04a1c1a1SRobert Mustacchi * based on JESD300-5B.01, Version 1.5.1, May 2023. The device uses the lower
21*04a1c1a1SRobert Mustacchi * 7-bits of registers to retrieve access to the current page. The upper 7-bits
22*04a1c1a1SRobert Mustacchi * is used to get access to the current page of NVM data. The current page is
23*04a1c1a1SRobert Mustacchi * controlled through one of the volatile registers. There is also a second mode
24*04a1c1a1SRobert Mustacchi * that allows the device to be put into a 2-byte mode where you can access all
25*04a1c1a1SRobert Mustacchi * of the memory, but we just opt for the traditional paged mode.
26*04a1c1a1SRobert Mustacchi */
27*04a1c1a1SRobert Mustacchi
28*04a1c1a1SRobert Mustacchi #include <sys/modctl.h>
29*04a1c1a1SRobert Mustacchi #include <sys/conf.h>
30*04a1c1a1SRobert Mustacchi #include <sys/devops.h>
31*04a1c1a1SRobert Mustacchi #include <sys/ddi.h>
32*04a1c1a1SRobert Mustacchi #include <sys/sunddi.h>
33*04a1c1a1SRobert Mustacchi #include <sys/bitext.h>
34*04a1c1a1SRobert Mustacchi #include <sys/sysmacros.h>
35*04a1c1a1SRobert Mustacchi #include <sys/i2c/client.h>
36*04a1c1a1SRobert Mustacchi #include <eedev.h>
37*04a1c1a1SRobert Mustacchi
38*04a1c1a1SRobert Mustacchi /*
39*04a1c1a1SRobert Mustacchi * Hub Device Registers. This is a subset of the registers that are useful for
40*04a1c1a1SRobert Mustacchi * us. Note, this uses the same thermal readout mechanism as the spd5118 driver.
41*04a1c1a1SRobert Mustacchi * See that driver for more information on the temperature logic.
42*04a1c1a1SRobert Mustacchi */
43*04a1c1a1SRobert Mustacchi #define HUB_R_TYPE_MSB 0x00
44*04a1c1a1SRobert Mustacchi #define HUB_R_TYPE_LSB 0x01
45*04a1c1a1SRobert Mustacchi #define HUB_R_REV 0x02
46*04a1c1a1SRobert Mustacchi #define HUB_R_VID0 0x03
47*04a1c1a1SRobert Mustacchi #define HUB_R_VID1 0x04
48*04a1c1a1SRobert Mustacchi #define HUB_R_CAP 0x05
49*04a1c1a1SRobert Mustacchi #define HUB_R_CAP_GET_TS_SUP(r) bitx8(r, 1, 1)
50*04a1c1a1SRobert Mustacchi #define HUB_R_CAP_GET_HUB(r) bitx8(r, 0, 0)
51*04a1c1a1SRobert Mustacchi #define HUB_R_I2C_CFG 0x0b
52*04a1c1a1SRobert Mustacchi #define HUB_R_I2C_CFG_GET_MODE(r) bitx8(r, 3, 3)
53*04a1c1a1SRobert Mustacchi #define HUB_R_I2C_CFG_GET_PAGE(r) bitx8(r, 2, 0)
54*04a1c1a1SRobert Mustacchi #define HUB_R_I2C_CFG_SET_PAGE(r, v) bitset8(r, 2, 0, v)
55*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_LSB 0x31
56*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_LSB_GET_TEMP(v) bitx8(v, 7, 2)
57*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_MSB 0x32
58*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_MSB_GET_TEMP(v) bitx8(v, 3, 0)
59*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_MSB_GET_SIGN(v) bitx8(v, 4, 4)
60*04a1c1a1SRobert Mustacchi #define HUB_R_TEMP_MSB_SHIFT 6
61*04a1c1a1SRobert Mustacchi #define HUB_R_NVM_BASE 0x80
62*04a1c1a1SRobert Mustacchi #define HUB_R_REG_MAX UINT8_MAX
63*04a1c1a1SRobert Mustacchi
64*04a1c1a1SRobert Mustacchi /*
65*04a1c1a1SRobert Mustacchi * The temperature is measured in units of 0.25 degrees.
66*04a1c1a1SRobert Mustacchi */
67*04a1c1a1SRobert Mustacchi #define HUB_TEMP_RES 4
68*04a1c1a1SRobert Mustacchi
69*04a1c1a1SRobert Mustacchi /*
70*04a1c1a1SRobert Mustacchi * Attributes of the device's size.
71*04a1c1a1SRobert Mustacchi */
72*04a1c1a1SRobert Mustacchi #define HUB_NVM_NPAGES 8
73*04a1c1a1SRobert Mustacchi #define HUB_NVM_PAGE_SIZE 128
74*04a1c1a1SRobert Mustacchi
75*04a1c1a1SRobert Mustacchi typedef struct spd5118 {
76*04a1c1a1SRobert Mustacchi dev_info_t *spd_dip;
77*04a1c1a1SRobert Mustacchi i2c_client_t *spd_client;
78*04a1c1a1SRobert Mustacchi i2c_reg_hdl_t *spd_regs;
79*04a1c1a1SRobert Mustacchi uint8_t spd_vid[2];
80*04a1c1a1SRobert Mustacchi uint8_t spd_rev;
81*04a1c1a1SRobert Mustacchi uint8_t spd_cap;
82*04a1c1a1SRobert Mustacchi eedev_hdl_t *spd_eehdl;
83*04a1c1a1SRobert Mustacchi id_t spd_ksensor;
84*04a1c1a1SRobert Mustacchi kmutex_t spd_mutex;
85*04a1c1a1SRobert Mustacchi uint8_t spd_buf[I2C_REQ_MAX];
86*04a1c1a1SRobert Mustacchi uint8_t spd_raw[2];
87*04a1c1a1SRobert Mustacchi int64_t spd_temp;
88*04a1c1a1SRobert Mustacchi } spd5118_t;
89*04a1c1a1SRobert Mustacchi
90*04a1c1a1SRobert Mustacchi static const i2c_reg_acc_attr_t spd5118_reg_attr = {
91*04a1c1a1SRobert Mustacchi .i2cacc_version = I2C_REG_ACC_ATTR_V0,
92*04a1c1a1SRobert Mustacchi .i2cacc_addr_len = 1,
93*04a1c1a1SRobert Mustacchi .i2cacc_reg_len = 1,
94*04a1c1a1SRobert Mustacchi .i2cacc_addr_max = HUB_R_REG_MAX
95*04a1c1a1SRobert Mustacchi };
96*04a1c1a1SRobert Mustacchi
97*04a1c1a1SRobert Mustacchi static bool
spd5118_page_change(i2c_txn_t * txn,spd5118_t * spd,uint32_t page)98*04a1c1a1SRobert Mustacchi spd5118_page_change(i2c_txn_t *txn, spd5118_t *spd, uint32_t page)
99*04a1c1a1SRobert Mustacchi {
100*04a1c1a1SRobert Mustacchi uint8_t cfg;
101*04a1c1a1SRobert Mustacchi i2c_error_t err;
102*04a1c1a1SRobert Mustacchi
103*04a1c1a1SRobert Mustacchi VERIFY(MUTEX_HELD(&spd->spd_mutex));
104*04a1c1a1SRobert Mustacchi
105*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(txn, spd->spd_regs, HUB_R_I2C_CFG, &cfg, sizeof (cfg),
106*04a1c1a1SRobert Mustacchi &err)) {
107*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read cap register: "
108*04a1c1a1SRobert Mustacchi "0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
109*04a1c1a1SRobert Mustacchi return (false);
110*04a1c1a1SRobert Mustacchi }
111*04a1c1a1SRobert Mustacchi
112*04a1c1a1SRobert Mustacchi if (HUB_R_I2C_CFG_GET_PAGE(cfg) != page) {
113*04a1c1a1SRobert Mustacchi cfg = HUB_R_I2C_CFG_SET_PAGE(cfg, page);
114*04a1c1a1SRobert Mustacchi if (!i2c_reg_put(txn, spd->spd_regs, HUB_R_I2C_CFG, &cfg,
115*04a1c1a1SRobert Mustacchi sizeof (cfg), &err)) {
116*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to write cap "
117*04a1c1a1SRobert Mustacchi "register: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
118*04a1c1a1SRobert Mustacchi return (false);
119*04a1c1a1SRobert Mustacchi }
120*04a1c1a1SRobert Mustacchi }
121*04a1c1a1SRobert Mustacchi
122*04a1c1a1SRobert Mustacchi return (true);
123*04a1c1a1SRobert Mustacchi }
124*04a1c1a1SRobert Mustacchi
125*04a1c1a1SRobert Mustacchi static int
spd5118_temp_read(void * arg,sensor_ioctl_scalar_t * scalar)126*04a1c1a1SRobert Mustacchi spd5118_temp_read(void *arg, sensor_ioctl_scalar_t *scalar)
127*04a1c1a1SRobert Mustacchi {
128*04a1c1a1SRobert Mustacchi int ret;
129*04a1c1a1SRobert Mustacchi uint8_t val[2];
130*04a1c1a1SRobert Mustacchi i2c_txn_t *txn;
131*04a1c1a1SRobert Mustacchi i2c_error_t err;
132*04a1c1a1SRobert Mustacchi spd5118_t *spd = arg;
133*04a1c1a1SRobert Mustacchi
134*04a1c1a1SRobert Mustacchi mutex_enter(&spd->spd_mutex);
135*04a1c1a1SRobert Mustacchi if (i2c_bus_lock(spd->spd_client, 0, &txn) != I2C_CORE_E_OK) {
136*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
137*04a1c1a1SRobert Mustacchi return (EINTR);
138*04a1c1a1SRobert Mustacchi }
139*04a1c1a1SRobert Mustacchi
140*04a1c1a1SRobert Mustacchi /*
141*04a1c1a1SRobert Mustacchi * The hub specification is a bit unclear. It seems to suggest that that
142*04a1c1a1SRobert Mustacchi * you shouldn't access other registers when you're not on page 0. As
143*04a1c1a1SRobert Mustacchi * such, we always change back to page 0 out of an abundance of caution.
144*04a1c1a1SRobert Mustacchi */
145*04a1c1a1SRobert Mustacchi if (!spd5118_page_change(txn, spd, 0)) {
146*04a1c1a1SRobert Mustacchi ret = EIO;
147*04a1c1a1SRobert Mustacchi goto done;
148*04a1c1a1SRobert Mustacchi }
149*04a1c1a1SRobert Mustacchi
150*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(txn, spd->spd_regs, HUB_R_TEMP_LSB, val, sizeof (val),
151*04a1c1a1SRobert Mustacchi &err)) {
152*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read temp "
153*04a1c1a1SRobert Mustacchi "registers: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
154*04a1c1a1SRobert Mustacchi return (EIO);
155*04a1c1a1SRobert Mustacchi }
156*04a1c1a1SRobert Mustacchi
157*04a1c1a1SRobert Mustacchi bcopy(val, spd->spd_raw, sizeof (val));
158*04a1c1a1SRobert Mustacchi uint64_t u64 = HUB_R_TEMP_LSB_GET_TEMP(spd->spd_raw[0]) |
159*04a1c1a1SRobert Mustacchi (HUB_R_TEMP_MSB_GET_TEMP(spd->spd_raw[1]) << HUB_R_TEMP_MSB_SHIFT);
160*04a1c1a1SRobert Mustacchi if (HUB_R_TEMP_MSB_GET_SIGN(spd->spd_raw[1]) == 1) {
161*04a1c1a1SRobert Mustacchi u64 |= UINT64_MAX & ~((1 << 10) - 1);
162*04a1c1a1SRobert Mustacchi }
163*04a1c1a1SRobert Mustacchi spd->spd_temp = (int64_t)u64;
164*04a1c1a1SRobert Mustacchi scalar->sis_value = spd->spd_temp;
165*04a1c1a1SRobert Mustacchi
166*04a1c1a1SRobert Mustacchi /*
167*04a1c1a1SRobert Mustacchi * The sensor is in units 0.25 Degrees C. According to the Table 65
168*04a1c1a1SRobert Mustacchi * Temperature Sensor Performance, there are there accuracy ranges:
169*04a1c1a1SRobert Mustacchi *
170*04a1c1a1SRobert Mustacchi * TYP 0.5, MAX 1.0 75 <= T~A~ <= 95
171*04a1c1a1SRobert Mustacchi * TYP 1.0, MAX 2.0 40 <= T~A~ <= 125
172*04a1c1a1SRobert Mustacchi * TYP 2.0, MAX 3.0 -40 <= T~A~ <= 125
173*04a1c1a1SRobert Mustacchi */
174*04a1c1a1SRobert Mustacchi scalar->sis_unit = SENSOR_UNIT_CELSIUS;
175*04a1c1a1SRobert Mustacchi scalar->sis_gran = HUB_TEMP_RES;
176*04a1c1a1SRobert Mustacchi int64_t prec_temp = scalar->sis_value / HUB_TEMP_RES;
177*04a1c1a1SRobert Mustacchi if (75 <= prec_temp && prec_temp <= 95) {
178*04a1c1a1SRobert Mustacchi scalar->sis_prec = 1 * scalar->sis_gran;
179*04a1c1a1SRobert Mustacchi } else if (40 <= prec_temp && prec_temp <= 125) {
180*04a1c1a1SRobert Mustacchi scalar->sis_prec = 2 * scalar->sis_gran;
181*04a1c1a1SRobert Mustacchi } else {
182*04a1c1a1SRobert Mustacchi scalar->sis_prec = 3 * scalar->sis_gran;
183*04a1c1a1SRobert Mustacchi }
184*04a1c1a1SRobert Mustacchi ret = 0;
185*04a1c1a1SRobert Mustacchi
186*04a1c1a1SRobert Mustacchi done:
187*04a1c1a1SRobert Mustacchi i2c_bus_unlock(txn);
188*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
189*04a1c1a1SRobert Mustacchi return (ret);
190*04a1c1a1SRobert Mustacchi }
191*04a1c1a1SRobert Mustacchi
192*04a1c1a1SRobert Mustacchi static const ksensor_ops_t spd5118_temp_ops = {
193*04a1c1a1SRobert Mustacchi .kso_kind = ksensor_kind_temperature,
194*04a1c1a1SRobert Mustacchi .kso_scalar = spd5118_temp_read
195*04a1c1a1SRobert Mustacchi };
196*04a1c1a1SRobert Mustacchi
197*04a1c1a1SRobert Mustacchi static int
spd5118_read(void * arg,struct uio * uio,uint32_t page,uint32_t pageoff,uint32_t nbytes)198*04a1c1a1SRobert Mustacchi spd5118_read(void *arg, struct uio *uio, uint32_t page, uint32_t pageoff,
199*04a1c1a1SRobert Mustacchi uint32_t nbytes)
200*04a1c1a1SRobert Mustacchi {
201*04a1c1a1SRobert Mustacchi int ret;
202*04a1c1a1SRobert Mustacchi i2c_txn_t *txn;
203*04a1c1a1SRobert Mustacchi i2c_error_t err;
204*04a1c1a1SRobert Mustacchi spd5118_t *spd = arg;
205*04a1c1a1SRobert Mustacchi
206*04a1c1a1SRobert Mustacchi VERIFY3U(page, <, HUB_NVM_NPAGES);
207*04a1c1a1SRobert Mustacchi VERIFY3U(pageoff, <, HUB_NVM_PAGE_SIZE);
208*04a1c1a1SRobert Mustacchi
209*04a1c1a1SRobert Mustacchi mutex_enter(&spd->spd_mutex);
210*04a1c1a1SRobert Mustacchi if (i2c_bus_lock(spd->spd_client, 0, &txn) != I2C_CORE_E_OK) {
211*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
212*04a1c1a1SRobert Mustacchi return (EINTR);
213*04a1c1a1SRobert Mustacchi }
214*04a1c1a1SRobert Mustacchi
215*04a1c1a1SRobert Mustacchi if (!spd5118_page_change(txn, spd, page)) {
216*04a1c1a1SRobert Mustacchi ret = EIO;
217*04a1c1a1SRobert Mustacchi goto done;
218*04a1c1a1SRobert Mustacchi }
219*04a1c1a1SRobert Mustacchi
220*04a1c1a1SRobert Mustacchi /*
221*04a1c1a1SRobert Mustacchi * We need to adjust the page offset to get us into the correct part of
222*04a1c1a1SRobert Mustacchi * the register space.
223*04a1c1a1SRobert Mustacchi */
224*04a1c1a1SRobert Mustacchi pageoff += HUB_R_NVM_BASE;
225*04a1c1a1SRobert Mustacchi if (i2c_reg_get(txn, spd->spd_regs, pageoff, spd->spd_buf, nbytes,
226*04a1c1a1SRobert Mustacchi &err)) {
227*04a1c1a1SRobert Mustacchi ret = uiomove(spd->spd_buf, nbytes, UIO_READ, uio);
228*04a1c1a1SRobert Mustacchi } else {
229*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read %u bytes of "
230*04a1c1a1SRobert Mustacchi "NVM at 0x%x on page %u: 0x%x/0x%x", nbytes, pageoff, page,
231*04a1c1a1SRobert Mustacchi err.i2c_error, err.i2c_ctrl);
232*04a1c1a1SRobert Mustacchi ret = EIO;
233*04a1c1a1SRobert Mustacchi }
234*04a1c1a1SRobert Mustacchi
235*04a1c1a1SRobert Mustacchi done:
236*04a1c1a1SRobert Mustacchi i2c_bus_unlock(txn);
237*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
238*04a1c1a1SRobert Mustacchi return (ret);
239*04a1c1a1SRobert Mustacchi }
240*04a1c1a1SRobert Mustacchi
241*04a1c1a1SRobert Mustacchi static const eedev_ops_t spd5118_eedev_ops = {
242*04a1c1a1SRobert Mustacchi .eo_read = spd5118_read
243*04a1c1a1SRobert Mustacchi };
244*04a1c1a1SRobert Mustacchi
245*04a1c1a1SRobert Mustacchi static bool
spd5118_i2c_init(spd5118_t * spd)246*04a1c1a1SRobert Mustacchi spd5118_i2c_init(spd5118_t *spd)
247*04a1c1a1SRobert Mustacchi {
248*04a1c1a1SRobert Mustacchi i2c_errno_t err;
249*04a1c1a1SRobert Mustacchi
250*04a1c1a1SRobert Mustacchi if ((err = i2c_client_init(spd->spd_dip, 0, &spd->spd_client)) !=
251*04a1c1a1SRobert Mustacchi I2C_CORE_E_OK) {
252*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "failed to create i2c client: "
253*04a1c1a1SRobert Mustacchi "0x%x", err);
254*04a1c1a1SRobert Mustacchi return (false);
255*04a1c1a1SRobert Mustacchi }
256*04a1c1a1SRobert Mustacchi
257*04a1c1a1SRobert Mustacchi if ((err = i2c_reg_handle_init(spd->spd_client, &spd5118_reg_attr,
258*04a1c1a1SRobert Mustacchi &spd->spd_regs)) != I2C_CORE_E_OK) {
259*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "failed to create register "
260*04a1c1a1SRobert Mustacchi "handle: %s (0x%x)", i2c_client_errtostr(spd->spd_client,
261*04a1c1a1SRobert Mustacchi err), err);
262*04a1c1a1SRobert Mustacchi return (false);
263*04a1c1a1SRobert Mustacchi }
264*04a1c1a1SRobert Mustacchi
265*04a1c1a1SRobert Mustacchi return (true);
266*04a1c1a1SRobert Mustacchi }
267*04a1c1a1SRobert Mustacchi
268*04a1c1a1SRobert Mustacchi /*
269*04a1c1a1SRobert Mustacchi * Read the MSB device type register to make sure that this is an SPD5118
270*04a1c1a1SRobert Mustacchi * device.
271*04a1c1a1SRobert Mustacchi */
272*04a1c1a1SRobert Mustacchi static bool
spd5118_ident(spd5118_t * spd)273*04a1c1a1SRobert Mustacchi spd5118_ident(spd5118_t *spd)
274*04a1c1a1SRobert Mustacchi {
275*04a1c1a1SRobert Mustacchi uint8_t type[2];
276*04a1c1a1SRobert Mustacchi i2c_error_t err;
277*04a1c1a1SRobert Mustacchi
278*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_TYPE_MSB, type,
279*04a1c1a1SRobert Mustacchi sizeof (type), &err)) {
280*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read type "
281*04a1c1a1SRobert Mustacchi "registers: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
282*04a1c1a1SRobert Mustacchi return (false);
283*04a1c1a1SRobert Mustacchi }
284*04a1c1a1SRobert Mustacchi
285*04a1c1a1SRobert Mustacchi /*
286*04a1c1a1SRobert Mustacchi * The hub specification is a bit unclear. It seems to suggest that that
287*04a1c1a1SRobert Mustacchi * you shouldn't access other registers when you're not on page 0. This
288*04a1c1a1SRobert Mustacchi * may mean that we can't get the device ID. So if we read a zero ID,
289*04a1c1a1SRobert Mustacchi * set the page to page 0 and try to read again.
290*04a1c1a1SRobert Mustacchi */
291*04a1c1a1SRobert Mustacchi if (type[0] == 0 && type[1] == 0) {
292*04a1c1a1SRobert Mustacchi mutex_enter(&spd->spd_mutex);
293*04a1c1a1SRobert Mustacchi if (!spd5118_page_change(NULL, spd, 0)) {
294*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
295*04a1c1a1SRobert Mustacchi return (false);
296*04a1c1a1SRobert Mustacchi }
297*04a1c1a1SRobert Mustacchi mutex_exit(&spd->spd_mutex);
298*04a1c1a1SRobert Mustacchi
299*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_TYPE_MSB, type,
300*04a1c1a1SRobert Mustacchi sizeof (type), &err)) {
301*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read type "
302*04a1c1a1SRobert Mustacchi "registers: 0x%x/0x%x", err.i2c_error,
303*04a1c1a1SRobert Mustacchi err.i2c_ctrl);
304*04a1c1a1SRobert Mustacchi return (false);
305*04a1c1a1SRobert Mustacchi }
306*04a1c1a1SRobert Mustacchi }
307*04a1c1a1SRobert Mustacchi
308*04a1c1a1SRobert Mustacchi if (type[0] != 0x51 || type[1] != 0x18) {
309*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "encountered unsupported device "
310*04a1c1a1SRobert Mustacchi "type: 0x%x/0x%x", type[0], type[1]);
311*04a1c1a1SRobert Mustacchi return (false);
312*04a1c1a1SRobert Mustacchi }
313*04a1c1a1SRobert Mustacchi
314*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_VID0, spd->spd_vid,
315*04a1c1a1SRobert Mustacchi sizeof (spd->spd_vid), &err)) {
316*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read vid registers: "
317*04a1c1a1SRobert Mustacchi "0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
318*04a1c1a1SRobert Mustacchi return (false);
319*04a1c1a1SRobert Mustacchi }
320*04a1c1a1SRobert Mustacchi
321*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_REV, &spd->spd_rev,
322*04a1c1a1SRobert Mustacchi sizeof (spd->spd_rev), &err)) {
323*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read rev register: "
324*04a1c1a1SRobert Mustacchi "0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
325*04a1c1a1SRobert Mustacchi return (false);
326*04a1c1a1SRobert Mustacchi }
327*04a1c1a1SRobert Mustacchi
328*04a1c1a1SRobert Mustacchi if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_CAP, &spd->spd_cap,
329*04a1c1a1SRobert Mustacchi sizeof (spd->spd_cap), &err)) {
330*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "!failed to read cap register: "
331*04a1c1a1SRobert Mustacchi "0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
332*04a1c1a1SRobert Mustacchi return (false);
333*04a1c1a1SRobert Mustacchi }
334*04a1c1a1SRobert Mustacchi
335*04a1c1a1SRobert Mustacchi return (true);
336*04a1c1a1SRobert Mustacchi }
337*04a1c1a1SRobert Mustacchi
338*04a1c1a1SRobert Mustacchi static bool
spd5118_eedev_init(spd5118_t * spd)339*04a1c1a1SRobert Mustacchi spd5118_eedev_init(spd5118_t *spd)
340*04a1c1a1SRobert Mustacchi {
341*04a1c1a1SRobert Mustacchi int ret;
342*04a1c1a1SRobert Mustacchi eedev_reg_t reg;
343*04a1c1a1SRobert Mustacchi
344*04a1c1a1SRobert Mustacchi bzero(®, sizeof (reg));
345*04a1c1a1SRobert Mustacchi reg.ereg_vers = EEDEV_REG_VERS;
346*04a1c1a1SRobert Mustacchi reg.ereg_size = HUB_NVM_NPAGES * HUB_NVM_PAGE_SIZE;
347*04a1c1a1SRobert Mustacchi reg.ereg_seg = HUB_NVM_PAGE_SIZE;
348*04a1c1a1SRobert Mustacchi reg.ereg_read_gran = 1;
349*04a1c1a1SRobert Mustacchi reg.ereg_ro = true;
350*04a1c1a1SRobert Mustacchi reg.ereg_dip = spd->spd_dip;
351*04a1c1a1SRobert Mustacchi reg.ereg_driver = spd;
352*04a1c1a1SRobert Mustacchi reg.ereg_name = NULL;
353*04a1c1a1SRobert Mustacchi reg.ereg_ops = &spd5118_eedev_ops;
354*04a1c1a1SRobert Mustacchi reg.ereg_max_read = MIN(i2c_reg_max_read(spd->spd_regs),
355*04a1c1a1SRobert Mustacchi I2C_REQ_MAX / 2);
356*04a1c1a1SRobert Mustacchi
357*04a1c1a1SRobert Mustacchi if ((ret = eedev_create(®, &spd->spd_eehdl)) != 0) {
358*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "failed to create eedev device: "
359*04a1c1a1SRobert Mustacchi "%d", ret);
360*04a1c1a1SRobert Mustacchi return (false);
361*04a1c1a1SRobert Mustacchi }
362*04a1c1a1SRobert Mustacchi
363*04a1c1a1SRobert Mustacchi return (true);
364*04a1c1a1SRobert Mustacchi }
365*04a1c1a1SRobert Mustacchi
366*04a1c1a1SRobert Mustacchi static void
spd5118_cleanup(spd5118_t * spd)367*04a1c1a1SRobert Mustacchi spd5118_cleanup(spd5118_t *spd)
368*04a1c1a1SRobert Mustacchi {
369*04a1c1a1SRobert Mustacchi (void) ksensor_remove(spd->spd_dip, KSENSOR_ALL_IDS);
370*04a1c1a1SRobert Mustacchi eedev_fini(spd->spd_eehdl);
371*04a1c1a1SRobert Mustacchi i2c_reg_handle_destroy(spd->spd_regs);
372*04a1c1a1SRobert Mustacchi i2c_client_destroy(spd->spd_client);
373*04a1c1a1SRobert Mustacchi mutex_destroy(&spd->spd_mutex);
374*04a1c1a1SRobert Mustacchi ddi_set_driver_private(spd->spd_dip, NULL);
375*04a1c1a1SRobert Mustacchi spd->spd_dip = NULL;
376*04a1c1a1SRobert Mustacchi kmem_free(spd, sizeof (spd5118_t));
377*04a1c1a1SRobert Mustacchi }
378*04a1c1a1SRobert Mustacchi
379*04a1c1a1SRobert Mustacchi static int
spd5118_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)380*04a1c1a1SRobert Mustacchi spd5118_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
381*04a1c1a1SRobert Mustacchi {
382*04a1c1a1SRobert Mustacchi int ret;
383*04a1c1a1SRobert Mustacchi spd5118_t *spd;
384*04a1c1a1SRobert Mustacchi
385*04a1c1a1SRobert Mustacchi switch (cmd) {
386*04a1c1a1SRobert Mustacchi case DDI_ATTACH:
387*04a1c1a1SRobert Mustacchi break;
388*04a1c1a1SRobert Mustacchi case DDI_RESUME:
389*04a1c1a1SRobert Mustacchi return (DDI_SUCCESS);
390*04a1c1a1SRobert Mustacchi default:
391*04a1c1a1SRobert Mustacchi return (DDI_FAILURE);
392*04a1c1a1SRobert Mustacchi }
393*04a1c1a1SRobert Mustacchi
394*04a1c1a1SRobert Mustacchi spd = kmem_zalloc(sizeof (spd5118_t), KM_SLEEP);
395*04a1c1a1SRobert Mustacchi spd->spd_dip = dip;
396*04a1c1a1SRobert Mustacchi ddi_set_driver_private(dip, spd);
397*04a1c1a1SRobert Mustacchi mutex_init(&spd->spd_mutex, NULL, MUTEX_DRIVER, NULL);
398*04a1c1a1SRobert Mustacchi
399*04a1c1a1SRobert Mustacchi if (!spd5118_i2c_init(spd))
400*04a1c1a1SRobert Mustacchi goto cleanup;
401*04a1c1a1SRobert Mustacchi
402*04a1c1a1SRobert Mustacchi if (!spd5118_ident(spd))
403*04a1c1a1SRobert Mustacchi goto cleanup;
404*04a1c1a1SRobert Mustacchi
405*04a1c1a1SRobert Mustacchi if (!spd5118_eedev_init(spd))
406*04a1c1a1SRobert Mustacchi goto cleanup;
407*04a1c1a1SRobert Mustacchi
408*04a1c1a1SRobert Mustacchi if ((ret = i2c_client_ksensor_create_scalar(spd->spd_client,
409*04a1c1a1SRobert Mustacchi SENSOR_KIND_TEMPERATURE, &spd5118_temp_ops, spd, "temp",
410*04a1c1a1SRobert Mustacchi &spd->spd_ksensor)) != 0) {
411*04a1c1a1SRobert Mustacchi dev_err(spd->spd_dip, CE_WARN, "failed to create ksensor: %d",
412*04a1c1a1SRobert Mustacchi ret);
413*04a1c1a1SRobert Mustacchi goto cleanup;
414*04a1c1a1SRobert Mustacchi }
415*04a1c1a1SRobert Mustacchi
416*04a1c1a1SRobert Mustacchi return (DDI_SUCCESS);
417*04a1c1a1SRobert Mustacchi
418*04a1c1a1SRobert Mustacchi cleanup:
419*04a1c1a1SRobert Mustacchi spd5118_cleanup(spd);
420*04a1c1a1SRobert Mustacchi return (DDI_FAILURE);
421*04a1c1a1SRobert Mustacchi }
422*04a1c1a1SRobert Mustacchi
423*04a1c1a1SRobert Mustacchi static int
spd5118_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)424*04a1c1a1SRobert Mustacchi spd5118_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
425*04a1c1a1SRobert Mustacchi {
426*04a1c1a1SRobert Mustacchi spd5118_t *spd;
427*04a1c1a1SRobert Mustacchi
428*04a1c1a1SRobert Mustacchi switch (cmd) {
429*04a1c1a1SRobert Mustacchi case DDI_DETACH:
430*04a1c1a1SRobert Mustacchi break;
431*04a1c1a1SRobert Mustacchi case DDI_SUSPEND:
432*04a1c1a1SRobert Mustacchi return (DDI_SUCCESS);
433*04a1c1a1SRobert Mustacchi default:
434*04a1c1a1SRobert Mustacchi return (DDI_FAILURE);
435*04a1c1a1SRobert Mustacchi }
436*04a1c1a1SRobert Mustacchi
437*04a1c1a1SRobert Mustacchi spd = ddi_get_driver_private(dip);
438*04a1c1a1SRobert Mustacchi if (spd == NULL) {
439*04a1c1a1SRobert Mustacchi dev_err(dip, CE_WARN, "asked to detach, but missing private "
440*04a1c1a1SRobert Mustacchi "data");
441*04a1c1a1SRobert Mustacchi return (DDI_FAILURE);
442*04a1c1a1SRobert Mustacchi }
443*04a1c1a1SRobert Mustacchi VERIFY3P(spd->spd_dip, ==, dip);
444*04a1c1a1SRobert Mustacchi
445*04a1c1a1SRobert Mustacchi spd5118_cleanup(spd);
446*04a1c1a1SRobert Mustacchi return (DDI_SUCCESS);
447*04a1c1a1SRobert Mustacchi }
448*04a1c1a1SRobert Mustacchi
449*04a1c1a1SRobert Mustacchi static struct dev_ops spd5118_dev_ops = {
450*04a1c1a1SRobert Mustacchi .devo_rev = DEVO_REV,
451*04a1c1a1SRobert Mustacchi .devo_refcnt = 0,
452*04a1c1a1SRobert Mustacchi .devo_identify = nulldev,
453*04a1c1a1SRobert Mustacchi .devo_probe = nulldev,
454*04a1c1a1SRobert Mustacchi .devo_attach = spd5118_attach,
455*04a1c1a1SRobert Mustacchi .devo_detach = spd5118_detach,
456*04a1c1a1SRobert Mustacchi .devo_reset = nodev,
457*04a1c1a1SRobert Mustacchi .devo_quiesce = ddi_quiesce_not_needed
458*04a1c1a1SRobert Mustacchi };
459*04a1c1a1SRobert Mustacchi
460*04a1c1a1SRobert Mustacchi static struct modldrv spd5118_modldrv = {
461*04a1c1a1SRobert Mustacchi .drv_modops = &mod_driverops,
462*04a1c1a1SRobert Mustacchi .drv_linkinfo = "SPD5118 driver",
463*04a1c1a1SRobert Mustacchi .drv_dev_ops = &spd5118_dev_ops
464*04a1c1a1SRobert Mustacchi };
465*04a1c1a1SRobert Mustacchi
466*04a1c1a1SRobert Mustacchi static struct modlinkage spd5118_modlinkage = {
467*04a1c1a1SRobert Mustacchi .ml_rev = MODREV_1,
468*04a1c1a1SRobert Mustacchi .ml_linkage = { &spd5118_modldrv, NULL }
469*04a1c1a1SRobert Mustacchi };
470*04a1c1a1SRobert Mustacchi
471*04a1c1a1SRobert Mustacchi
472*04a1c1a1SRobert Mustacchi int
_init(void)473*04a1c1a1SRobert Mustacchi _init(void)
474*04a1c1a1SRobert Mustacchi {
475*04a1c1a1SRobert Mustacchi return (mod_install(&spd5118_modlinkage));
476*04a1c1a1SRobert Mustacchi }
477*04a1c1a1SRobert Mustacchi
478*04a1c1a1SRobert Mustacchi int
_info(struct modinfo * modinfop)479*04a1c1a1SRobert Mustacchi _info(struct modinfo *modinfop)
480*04a1c1a1SRobert Mustacchi {
481*04a1c1a1SRobert Mustacchi return (mod_info(&spd5118_modlinkage, modinfop));
482*04a1c1a1SRobert Mustacchi }
483*04a1c1a1SRobert Mustacchi
484*04a1c1a1SRobert Mustacchi int
_fini(void)485*04a1c1a1SRobert Mustacchi _fini(void)
486*04a1c1a1SRobert Mustacchi {
487*04a1c1a1SRobert Mustacchi return (mod_remove(&spd5118_modlinkage));
488*04a1c1a1SRobert Mustacchi }
489