1/* $NetBSD: smscmon.c,v 1.3 2018/06/16 21:22:13 thorpej Exp $ */
2
3/*
4 * Copyright (c) 2009 Takahiro Hayashi
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: smscmon.c,v 1.3 2018/06/16 21:22:13 thorpej Exp $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/device.h>
35
36#include <dev/i2c/i2cvar.h>
37#include <dev/sysmon/sysmonvar.h>
38#include <dev/i2c/smscmonvar.h>
39
40/*
41 * A driver for SMSC LPC47M192 hardware monitor at SMBus.
42 * This driver supports 8 Voltage and 3 Temperature sensors.
43 * Fan RPM monitoring is not supported in this driver because
44 * they are seen on ISA bus.
45 *
46 * Datasheet available (as of Feb. 20, 2010) at
47 * http://pdf1.alldatasheet.com/datasheet-pdf/view/109752/SMSC/LPC47M192-NC.html
48 */
49
50static int smscmon_match(device_t, cfdata_t, void *);
51static void smscmon_attach(device_t, device_t, void *);
52static uint8_t smscmon_readreg(struct smscmon_sc *, int);
53static void smscmon_writereg(struct smscmon_sc *, int, int);
54static void smscmon_sensors_setup(struct smscmon_sc *, struct smscmon_sensor *);
55static void smscmon_refresh_volt(struct smscmon_sc *, envsys_data_t *);
56static void smscmon_refresh_temp(struct smscmon_sc *, envsys_data_t *);
57static void smscmon_refresh(struct sysmon_envsys *, envsys_data_t *);
58
59CFATTACH_DECL_NEW(smscmon, sizeof(struct smscmon_sc),
60    smscmon_match, smscmon_attach, NULL, NULL);
61
62static struct smscmon_sensor smscmon_lpc47m192[] = {
63	{
64		.desc = "+2.5V",
65		.type = ENVSYS_SVOLTS_DC,
66		.reg = 0x20,
67		.refresh = smscmon_refresh_volt,
68		.vmin =   13000,
69		.vmax = 3320000
70	},
71	{
72		.desc = "Vccp",
73		.type = ENVSYS_SVOLTS_DC,
74		.reg = 0x21,
75		.refresh = smscmon_refresh_volt,
76		.vmin =   12000,
77		.vmax = 2988000
78	},
79	{
80		.desc = "+3.3V",
81		.type = ENVSYS_SVOLTS_DC,
82		.reg = 0x22,
83		.refresh = smscmon_refresh_volt,
84		.vmin =   17000,
85		.vmax = 4383000
86	},
87	{
88		.desc = "+5V",
89		.type = ENVSYS_SVOLTS_DC,
90		.reg = 0x23,
91		.refresh = smscmon_refresh_volt,
92		.vmin =   26000,
93		.vmax = 6640000
94	},
95	{
96		.desc = "+12V",
97		.type = ENVSYS_SVOLTS_DC,
98		.reg = 0x24,
99		.refresh = smscmon_refresh_volt,
100		.vmin =    62000,
101		.vmax = 15938000
102	},
103	{
104		.desc = "Vcc",
105		.type = ENVSYS_SVOLTS_DC,
106		.reg = 0x25,
107		.refresh = smscmon_refresh_volt,
108		.vmin =   17000,
109		.vmax = 4383000
110	},
111	{
112		.desc = "+1.5V",
113		.type = ENVSYS_SVOLTS_DC,
114		.reg = 0x50,
115		.refresh = smscmon_refresh_volt,
116		.vmin =    8000,
117		.vmax = 1992000
118	},
119	{
120		.desc = "+1.8V",
121		.type = ENVSYS_SVOLTS_DC,
122		.reg = 0x51,
123		.refresh = smscmon_refresh_volt,
124		.vmin =    9000,
125		.vmax = 2391000
126	},
127	{
128		.desc = "Remote Temp1",
129		.type = ENVSYS_STEMP,
130		.reg = 0x26,
131		.refresh = smscmon_refresh_temp,
132		.vmin = 0,
133		.vmax = 0
134	},
135	{
136		.desc = "Ambient Temp",
137		.type = ENVSYS_STEMP,
138		.reg = 0x27,
139		.refresh = smscmon_refresh_temp,
140		.vmin = 0,
141		.vmax = 0
142	},
143	{
144		.desc = "Remote Temp2",
145		.type = ENVSYS_STEMP,
146		.reg = 0x52,
147		.refresh = smscmon_refresh_temp,
148		.vmax = 0,
149		.vmin = 0
150	},
151
152	{ .desc = NULL }
153};
154
155static int
156smscmon_match(device_t parent, cfdata_t match, void *aux)
157{
158	struct i2c_attach_args *ia = aux;
159	uint8_t cmd, cid, rev;
160
161	/* Address is hardwired to 010_110x */
162	if ((ia->ia_addr & SMSCMON_ADDR_MASK) != SMSCMON_ADDR)
163		return 0;
164
165	iic_acquire_bus(ia->ia_tag, 0);
166
167	cmd = SMSCMON_REG_COMPANY;
168	if (iic_exec(ia->ia_tag, I2C_OP_READ_WITH_STOP,
169	    ia->ia_addr, &cmd, sizeof cmd, &cid, sizeof cid, 0)) {
170		iic_release_bus(ia->ia_tag, 0);
171		return 0;
172	}
173	cmd = SMSCMON_REG_STEPPING;
174	if (iic_exec(ia->ia_tag, I2C_OP_READ_WITH_STOP,
175	    ia->ia_addr, &cmd, sizeof cmd, &rev, sizeof rev, 0)) {
176		iic_release_bus(ia->ia_tag, 0);
177		return 0;
178	}
179
180	if ( cid != SMSC_CID_47M192 || rev != SMSC_REV_47M192) {
181		iic_release_bus(ia->ia_tag, 0);
182		return 0;
183	}
184
185	iic_release_bus(ia->ia_tag, 0);
186	return I2C_MATCH_ADDRESS_AND_PROBE;
187}
188
189static void
190smscmon_attach(device_t parent, device_t self, void *aux)
191{
192	struct smscmon_sc *sc = device_private(self);
193	struct i2c_attach_args *ia = aux;
194	uint8_t cid, rev;
195	int i;
196
197	sc->sc_dev = self;
198	sc->sc_tag = ia->ia_tag;
199	sc->sc_addr = ia->ia_addr;
200	sc->smscmon_readreg = smscmon_readreg;
201	sc->smscmon_writereg = smscmon_writereg;
202	sc->smscmon_sensors = NULL;
203
204	cid = sc->smscmon_readreg(sc, SMSCMON_REG_COMPANY);
205	rev = sc->smscmon_readreg(sc, SMSCMON_REG_STEPPING);
206	switch (cid) {
207	case SMSC_CID_47M192:
208		if (rev == SMSC_REV_47M192) {
209			smscmon_sensors_setup(sc, smscmon_lpc47m192);
210			aprint_normal(": LPC47M192 hardware monitor\n");
211		}
212		break;
213	default:
214		/* unknown chip */
215		break;
216	}
217
218	if (sc->smscmon_sensors == NULL) {
219		aprint_normal(": unknown chip: cid 0x%02x rev 0x%02x\n",
220		    cid, rev);
221		return;
222	}
223
224	if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
225		aprint_error_dev(sc->sc_dev,
226		    "unable to create sysmon structure\n");
227		return;
228	}
229
230	for (i = 0; i < sc->numsensors; i++) {
231		if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sensors[i])) {
232			aprint_error_dev(sc->sc_dev,
233			    "unable to attach sensor\n");
234			sysmon_envsys_destroy(sc->sc_sme);
235			return;
236		}
237	}
238
239	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
240	sc->sc_sme->sme_cookie = sc;
241	sc->sc_sme->sme_refresh = smscmon_refresh;
242	if (sysmon_envsys_register(sc->sc_sme)) {
243		aprint_error_dev(sc->sc_dev,
244		    "unable to register with sysmon\n");
245		sysmon_envsys_destroy(sc->sc_sme);
246		return;
247	}
248}
249
250static uint8_t
251smscmon_readreg(struct smscmon_sc *sc, int reg)
252{
253	uint8_t cmd, data;
254
255	iic_acquire_bus(sc->sc_tag, 0);
256
257	cmd = reg;
258	iic_smbus_read_byte(sc->sc_tag, sc->sc_addr, cmd, &data, 0);
259
260	iic_release_bus(sc->sc_tag, 0);
261
262	return data;
263}
264
265static void
266smscmon_writereg(struct smscmon_sc *sc, int reg, int val)
267{
268	uint8_t cmd, data;
269
270	iic_acquire_bus(sc->sc_tag, 0);
271
272	cmd = reg;
273	data = val;
274	iic_smbus_write_byte(sc->sc_tag, sc->sc_addr, cmd, data, 0);
275
276	iic_release_bus(sc->sc_tag, 0);
277}
278
279static void
280smscmon_sensors_setup(struct smscmon_sc *sc, struct smscmon_sensor *sens)
281{
282	int i;
283
284	for (i = 0; sens[i].desc; i++) {
285		strlcpy(sc->sensors[i].desc, sens[i].desc,
286		    sizeof(sc->sensors[i].desc));
287		sc->sensors[i].units = sens[i].type;
288		sc->sensors[i].state = ENVSYS_SINVALID;
289		sc->numsensors++;
290	}
291	sc->smscmon_sensors = sens;
292}
293
294static void
295smscmon_refresh_volt(struct smscmon_sc *sc, envsys_data_t *edata)
296{
297	struct smscmon_sensor *sens = &sc->smscmon_sensors[edata->sensor];
298	int data;
299
300	data = (*sc->smscmon_readreg)(sc, sens->reg);
301	if (data == 0xff) {
302		edata->state = ENVSYS_SINVALID;
303	} else {
304		edata->value_cur =
305		    (sens->vmax - sens->vmin)/255 * data + sens->vmin;
306		edata->state = ENVSYS_SVALID;
307	}
308}
309
310static void
311smscmon_refresh_temp(struct smscmon_sc *sc, envsys_data_t *edata)
312{
313	struct smscmon_sensor *sens = &sc->smscmon_sensors[edata->sensor];
314	int data;
315
316	data = (*sc->smscmon_readreg)(sc, sens->reg);
317	if (data == 0xff) {
318		edata->state = ENVSYS_SINVALID;
319	} else {
320		/* convert data(degC) to uK */
321		edata->value_cur = 273150000 + 1000000 * data;
322		edata->state = ENVSYS_SVALID;
323	}
324}
325
326static void
327smscmon_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
328{
329	struct smscmon_sc *sc = sme->sme_cookie;
330
331	sc->smscmon_sensors[edata->sensor].refresh(sc, edata);
332}
333