summaryrefslogtreecommitdiff
path: root/sys/dev/i2c/lm75.c
blob: 3145e1fa11e6db8c362568a498517ff58ef36184 (plain)
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
/*	$OpenBSD: lm75.c,v 1.18 2008/04/17 19:01:48 deraadt Exp $	*/
/*	$NetBSD: lm75.c,v 1.1 2003/09/30 00:35:31 thorpej Exp $	*/
/*
 * Copyright (c) 2006 Theo de Raadt <deraadt@openbsd.org>
 * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * National Semiconductor LM75/LM76/LM77 temperature sensor.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/sensors.h>

#include <dev/i2c/i2cvar.h>

#define	LM_MODEL_LM75	1
#define	LM_MODEL_LM77	2
#define	LM_MODEL_DS1775	3
#define	LM_MODEL_LM75A	4
#define	LM_MODEL_LM76	5

#define LM_POLLTIME	3	/* 3s */

#define	LM75_REG_TEMP			0x00
#define	LM75_REG_CONFIG			0x01
#define  LM75_CONFIG_SHUTDOWN		0x01
#define  LM75_CONFIG_CMPINT		0x02
#define  LM75_CONFIG_OSPOLARITY		0x04
#define  LM75_CONFIG_FAULT_QUEUE_MASK	0x18
#define  LM75_CONFIG_FAULT_QUEUE_1	(0 << 3)
#define  LM75_CONFIG_FAULT_QUEUE_2	(1 << 3)
#define  LM75_CONFIG_FAULT_QUEUE_4	(2 << 3)
#define  LM75_CONFIG_FAULT_QUEUE_6	(3 << 3)
#define  LM77_CONFIG_INTPOLARITY	0x08
#define  LM77_CONFIG_FAULT_QUEUE_4	0x10
#define  DS1755_CONFIG_RESOLUTION(i)	(9 + (((i) >> 5) & 3))
#define	LM75_REG_THYST_SET_POINT	0x02
#define	LM75_REG_TOS_SET_POINT		0x03
#define	LM77_REG_TLOW			0x04
#define	LM77_REG_THIGH			0x05

struct lmtemp_softc {
	struct device sc_dev;
	i2c_tag_t sc_tag;
	int	sc_addr;
	int	sc_model;
	int	sc_bits;
	int	sc_ratio;

	struct ksensor sc_sensor;
	struct ksensordev sc_sensordev;
};

int  lmtemp_match(struct device *, void *, void *);
void lmtemp_attach(struct device *, struct device *, void *);

struct cfattach lmtemp_ca = {
	sizeof(struct lmtemp_softc),
	lmtemp_match,
	lmtemp_attach
};

struct cfdriver lmtemp_cd = {
	NULL, "lmtemp", DV_DULL
};

/*
 * Temperature on the LM75 is represented by a 9-bit two's complement
 * integer in steps of 0.5C.  The following examples are taken from
 * the LM75 data sheet:
 *
 *	+125C	0 1111 1010	0x0fa
 *	+25C	0 0011 0010	0x032
 *	+0.5C	0 0000 0001	0x001
 *	0C	0 0000 0000	0x000
 *	-0.5C	1 1111 1111	0x1ff
 *	-25C	1 1100 1110	0x1ce
 *	-55C	1 1001 0010	0x192
 *
 * Temperature on the LM75A is represented by an 11-bit two's complement
 * integer in steps of 0.125C.  The LM75A can be treated like an LM75 if
 * the extra precision is not required.  The following examples are
 * taken from the LM75A data sheet:
 *
 *	+127.000C	011 1111 1000	0x3f8
 *	+126.875C	011 1111 0111	0x3f7
 *	+126.125C	011 1111 0001	0x3f1
 *	+125.000C	011 1110 1000	0x3e8
 *	+25.000C	000 1100 1000	0x0c8
 *	+0.125C		000 0000 0001	0x001
 *	0C		000 0000 0000	0x000
 *	-0.125C		111 1111 1111	0x7ff
 *	-25.000C	111 0011 1000	0x738
 *	-54.875C	110 0100 1001	0x649
 *	-55.000C	110 0100 1000	0x648
 *
 * Temperature on the LM77 is represented by a 13-bit two's complement
 * integer in steps of 0.5C.  The LM76 is similar, but the integer is
 * in steps of 0.065C
 *
 * LM75 temperature word:
 *
 * MSB Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 X X X X X X X
 * 15  14   13   12   11   10   9    8    7    6 5 4 3 2 1 0
 *
 *
 * LM75A temperature word:
 *
 * MSB Bit9 Bit8 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 X X X X X
 * 15  14   13   12   11   10   9    8    7    6    5    4 3 2 1 0
 *
 *
 * LM77 temperature word:
 *
 * Sign Sign Sign Sign MSB Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 Status bits
 * 15   14   13   12   11  10   9    8    7    6    5    4    3    2 1 0
 */

int  lmtemp_temp_read(struct lmtemp_softc *, uint8_t, int *);
void lmtemp_refresh_sensor_data(void *);

int
lmtemp_match(struct device *parent, void *match, void *aux)
{
	struct i2c_attach_args *ia = aux;

	if (strcmp(ia->ia_name, "lm75") == 0 ||
	    strcmp(ia->ia_name, "lm76") == 0 ||
	    strcmp(ia->ia_name, "lm77") == 0 ||
	    strcmp(ia->ia_name, "ds1775") == 0 ||
	    strcmp(ia->ia_name, "lm75a") == 0)
		return (1);
	return (0);
}

void
lmtemp_attach(struct device *parent, struct device *self, void *aux)
{
	struct lmtemp_softc *sc = (struct lmtemp_softc *)self;
	struct i2c_attach_args *ia = aux;
	u_int8_t cmd, data;

	sc->sc_tag = ia->ia_tag;
	sc->sc_addr = ia->ia_addr;

	printf(": %s", ia->ia_name);

	/* If in SHUTDOWN mode, wake it up */
	iic_acquire_bus(sc->sc_tag, 0);
	cmd = LM75_REG_CONFIG;
	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0)) {
		iic_release_bus(sc->sc_tag, 0);
		printf(", fails to respond\n");
		return;
	}
	if (data & LM75_CONFIG_SHUTDOWN) {
		data &= ~LM75_CONFIG_SHUTDOWN;
		if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
		    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0)) {
			printf(", cannot wake up\n");
			iic_release_bus(sc->sc_tag, 0);
			return;
		}
		printf(", woken up");
	}
	iic_release_bus(sc->sc_tag, 0);

	sc->sc_model = LM_MODEL_LM75;
	sc->sc_bits = 9;
	sc->sc_ratio = 500000;		/* 0.5 degC for LSB */
	if (strcmp(ia->ia_name, "lm77") == 0) {
		sc->sc_model = LM_MODEL_LM77;
		sc->sc_bits = 13;
	} else if (strcmp(ia->ia_name, "lm76") == 0) {
		sc->sc_model = LM_MODEL_LM76;
		sc->sc_bits = 13;
		sc->sc_ratio = 62500;	/* 0.0625 degC for LSB */
	} else if (strcmp(ia->ia_name, "ds1775") == 0) {
		sc->sc_model = LM_MODEL_DS1775;
		//sc->sc_bits = DS1755_CONFIG_RESOLUTION(data);
	} else if (strcmp(ia->ia_name, "lm75a") == 0) {
		/* For simplicity's sake, treat the LM75A as an LM75 */
		sc->sc_model = LM_MODEL_LM75A;
	}

	printf("\n");

	/* Initialize sensor data */
	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
	    sizeof(sc->sc_sensordev.xname));
	sc->sc_sensor.type = SENSOR_TEMP;

	/* Hook into the hw.sensors sysctl */
	sensor_attach(&sc->sc_sensordev, &sc->sc_sensor);
	sensordev_install(&sc->sc_sensordev);

	sensor_task_register(sc, lmtemp_refresh_sensor_data, LM_POLLTIME);
}

int
lmtemp_temp_read(struct lmtemp_softc *sc, uint8_t which, int *valp)
{
	u_int8_t cmd;
	u_int16_t data = 0x0000;
	int error;

	cmd = which;
	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &cmd, sizeof cmd, &data, sizeof data, 0);
	if (error)
		return (error);

	/* Some chips return transient 0's.. we try next time */
	if (data == 0x0000)
		return (1);

	/* convert to half-degrees C */
	*valp = betoh16(data) / (1 << (16 - sc->sc_bits));
	return (0);
}

void
lmtemp_refresh_sensor_data(void *aux)
{
	struct lmtemp_softc *sc = aux;
	int val;
	int error;

	error = lmtemp_temp_read(sc, LM75_REG_TEMP, &val);
	if (error) {
#if 0
		printf("%s: unable to read temperature, error = %d\n",
		    sc->sc_dev.dv_xname, error);
#endif
		sc->sc_sensor.flags |= SENSOR_FINVALID;
		return;
	}

	sc->sc_sensor.value = val * sc->sc_ratio + 273150000;
	sc->sc_sensor.flags &= ~SENSOR_FINVALID;
}