1 /* $NetBSD: smscmon.c,v 1.2 2011/06/20 20:16:19 pgoyette 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.2 2011/06/20 20:16:19 pgoyette 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 
50 static int smscmon_match(device_t, cfdata_t, void *);
51 static void smscmon_attach(device_t, device_t, void *);
52 static uint8_t smscmon_readreg(struct smscmon_sc *, int);
53 static void smscmon_writereg(struct smscmon_sc *, int, int);
54 static void smscmon_sensors_setup(struct smscmon_sc *, struct smscmon_sensor *);
55 static void smscmon_refresh_volt(struct smscmon_sc *, envsys_data_t *);
56 static void smscmon_refresh_temp(struct smscmon_sc *, envsys_data_t *);
57 static void smscmon_refresh(struct sysmon_envsys *, envsys_data_t *);
58 
59 CFATTACH_DECL_NEW(smscmon, sizeof(struct smscmon_sc),
60     smscmon_match, smscmon_attach, NULL, NULL);
61 
62 static 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 
155 static int
smscmon_match(device_t parent,cfdata_t match,void * aux)156 smscmon_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 1;
187 }
188 
189 static void
smscmon_attach(device_t parent,device_t self,void * aux)190 smscmon_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 
250 static uint8_t
smscmon_readreg(struct smscmon_sc * sc,int reg)251 smscmon_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 
265 static void
smscmon_writereg(struct smscmon_sc * sc,int reg,int val)266 smscmon_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 
279 static void
smscmon_sensors_setup(struct smscmon_sc * sc,struct smscmon_sensor * sens)280 smscmon_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 
294 static void
smscmon_refresh_volt(struct smscmon_sc * sc,envsys_data_t * edata)295 smscmon_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 
310 static void
smscmon_refresh_temp(struct smscmon_sc * sc,envsys_data_t * edata)311 smscmon_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 
326 static void
smscmon_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)327 smscmon_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