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