1164419Ssam/*-
2164419Ssam * Copyright (c) 2006 Sam Leffler.  All rights reserved.
3164419Ssam *
4164419Ssam * Redistribution and use in source and binary forms, with or without
5164419Ssam * modification, are permitted provided that the following conditions
6164419Ssam * are met:
7164419Ssam * 1. Redistributions of source code must retain the above copyright
8164419Ssam *    notice, this list of conditions and the following disclaimer.
9164419Ssam * 2. Redistributions in binary form must reproduce the above copyright
10164419Ssam *    notice, this list of conditions and the following disclaimer in the
11164419Ssam *    documentation and/or other materials provided with the distribution.
12164419Ssam *
13164419Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14164419Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15164419Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16164419Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17164419Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18164419Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19164419Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20164419Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21164419Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22164419Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23164419Ssam */
24164419Ssam
25164419Ssam#include <sys/cdefs.h>
26164419Ssam__FBSDID("$FreeBSD: stable/11/sys/dev/iicbus/ad7418.c 323421 2017-09-11 02:56:45Z ian $");
27164419Ssam/*
28164419Ssam * Analog Devices AD7418 chip sitting on the I2C bus.
29164419Ssam */
30164419Ssam#include <sys/param.h>
31164419Ssam#include <sys/systm.h>
32164419Ssam#include <sys/kernel.h>
33164419Ssam#include <sys/lock.h>
34164419Ssam#include <sys/module.h>
35164419Ssam#include <sys/bus.h>
36164419Ssam#include <sys/resource.h>
37164419Ssam#include <sys/rman.h>
38164419Ssam#include <sys/sysctl.h>
39181305Sjhb#include <sys/sx.h>
40164419Ssam
41164419Ssam#include <machine/bus.h>
42164419Ssam#include <machine/cpu.h>
43164419Ssam#include <machine/cpufunc.h>
44164419Ssam#include <machine/frame.h>
45164419Ssam#include <machine/resource.h>
46164419Ssam#include <machine/intr.h>
47164419Ssam
48164419Ssam#include <dev/iicbus/iiconf.h>
49164419Ssam
50164419Ssam#include "iicbus_if.h"
51164419Ssam
52164419Ssam#define	IIC_M_WR	0	/* write operation */
53164419Ssam
54164419Ssam#define	AD7418_ADDR	0x50	/* slave address */
55164419Ssam
56164419Ssam#define	AD7418_TEMP	0	/* Temperature Value (r/o) */
57164419Ssam#define	AD7418_CONF	1	/* Config Register (r/w) */
58164419Ssam#define	AD7418_CONF_SHUTDOWN	0x01
59164419Ssam#define	AD7418_CONF_CHAN	0xe0	/* channel select mask */
60164419Ssam#define	AD7418_CHAN_TEMP	0x00	/* temperature channel */
61164419Ssam#define	AD7418_CHAN_VOLT	0x80	/* voltage channel */
62164419Ssam#define	AD7418_THYST	2	/* Thyst Setpoint (r/o) */
63164419Ssam#define	AD7418_TOTI	3	/* Toti Setpoint */
64164419Ssam#define	AD7418_VOLT	4	/* ADC aka Voltage (r/o) */
65164419Ssam#define	AD7418_CONF2	5	/* Config2 Register (r/w) */
66164419Ssam
67164419Ssamstruct ad7418_softc {
68164419Ssam	device_t	sc_dev;
69181305Sjhb	struct sx	sc_lock;
70164419Ssam	int		sc_curchan;	/* current channel */
71164419Ssam	int		sc_curtemp;
72164419Ssam	int		sc_curvolt;
73164419Ssam	int		sc_lastupdate;	/* in ticks */
74164419Ssam};
75164419Ssam
76164419Ssamstatic void ad7418_update(struct ad7418_softc *);
77164419Ssamstatic int ad7418_read_1(device_t dev, int reg);
78164419Ssamstatic int ad7418_write_1(device_t dev, int reg, int v);
79164419Ssam
80164419Ssamstatic int
81164419Ssamad7418_probe(device_t dev)
82164419Ssam{
83164419Ssam	/* XXX really probe? */
84164419Ssam	device_set_desc(dev, "Analog Devices AD7418 ADC");
85186833Snwhitehorn	return (BUS_PROBE_NOWILDCARD);
86164419Ssam}
87164419Ssam
88164419Ssamstatic int
89164419Ssamad7418_sysctl_temp(SYSCTL_HANDLER_ARGS)
90164419Ssam{
91164419Ssam	struct ad7418_softc *sc = arg1;
92164419Ssam	int temp;
93164419Ssam
94181305Sjhb	sx_xlock(&sc->sc_lock);
95164419Ssam	ad7418_update(sc);
96164419Ssam	temp = (sc->sc_curtemp / 64) * 25;
97181305Sjhb	sx_xunlock(&sc->sc_lock);
98164419Ssam	return sysctl_handle_int(oidp, &temp, 0, req);
99164419Ssam}
100164419Ssam
101164419Ssamstatic int
102164419Ssamad7418_sysctl_voltage(SYSCTL_HANDLER_ARGS)
103164419Ssam{
104164419Ssam	struct ad7418_softc *sc = arg1;
105164419Ssam	int volt;
106164419Ssam
107181305Sjhb	sx_xlock(&sc->sc_lock);
108164419Ssam	ad7418_update(sc);
109164419Ssam	volt = (sc->sc_curvolt >> 6) * 564 / 10;
110181305Sjhb	sx_xunlock(&sc->sc_lock);
111164419Ssam	return sysctl_handle_int(oidp, &volt, 0, req);
112164419Ssam}
113164419Ssam
114164419Ssamstatic int
115164419Ssamad7418_attach(device_t dev)
116164419Ssam{
117164419Ssam	struct ad7418_softc *sc = device_get_softc(dev);
118164419Ssam	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
119164419Ssam	struct sysctl_oid *tree = device_get_sysctl_tree(dev);
120164419Ssam	int conf;
121164419Ssam
122164419Ssam	sc->sc_dev = dev;
123323421Sian	sc->sc_lastupdate = ticks - hz;
124323421Sian
125181682Sed	sx_init(&sc->sc_lock, "ad7418");
126164419Ssam
127164419Ssam	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
128164419Ssam		"temp", CTLTYPE_INT | CTLFLAG_RD, sc, 0,
129164419Ssam		ad7418_sysctl_temp, "I", "operating temperature");
130164419Ssam	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
131164419Ssam		"volt", CTLTYPE_INT | CTLFLAG_RD, sc, 0,
132164419Ssam		ad7418_sysctl_voltage, "I", "input voltage");
133164419Ssam
134164419Ssam	/* enable chip if configured in shutdown mode */
135164419Ssam	conf = ad7418_read_1(dev, AD7418_CONF);
136164419Ssam	if (conf >= 0 && (conf & AD7418_CONF_SHUTDOWN))
137164419Ssam		ad7418_write_1(dev, AD7418_CONF, conf &~ AD7418_CONF_SHUTDOWN);
138164419Ssam
139164419Ssam	return (0);
140164419Ssam}
141164419Ssam
142164419Ssamstatic int
143164419Ssamad7418_read_1(device_t dev, int reg)
144164419Ssam{
145164419Ssam	uint8_t addr = reg;
146164419Ssam	uint8_t data[1];
147164419Ssam	struct iic_msg msgs[2] = {
148164419Ssam	     { AD7418_ADDR, IIC_M_WR, 1, &addr },
149164419Ssam	     { AD7418_ADDR, IIC_M_RD, 1, data },
150164419Ssam	};
151164419Ssam	return iicbus_transfer(dev, msgs, 2) != 0 ? -1 : data[0];
152164419Ssam}
153164419Ssam
154164419Ssamstatic int
155164419Ssamad7418_write_1(device_t dev, int reg, int v)
156164419Ssam{
157164419Ssam	/* NB: register pointer precedes actual data */
158164419Ssam	uint8_t data[2];
159164419Ssam	struct iic_msg msgs[1] = {
160164419Ssam	     { AD7418_ADDR, IIC_M_WR, 2, data },
161164419Ssam	};
162164419Ssam	data[0] = reg;
163164419Ssam	data[1] = v & 0xff;
164164419Ssam	return iicbus_transfer(dev, msgs, 1);
165164419Ssam}
166164419Ssam
167164419Ssamstatic void
168164419Ssamad7418_set_channel(struct ad7418_softc *sc, int chan)
169164419Ssam{
170164419Ssam	if (sc->sc_curchan == chan)
171164419Ssam		return;
172164419Ssam	ad7418_write_1(sc->sc_dev, AD7418_CONF,
173164419Ssam	    (ad7418_read_1(sc->sc_dev, AD7418_CONF) &~ AD7418_CONF_CHAN)|chan);
174164419Ssam	sc->sc_curchan = chan;
175164419Ssam#if 0
176164419Ssam	/*
177164419Ssam	 * NB: Linux driver delays here but chip data sheet
178164419Ssam	 *     says nothing and things appear to work fine w/o
179181305Sjhb	 *     a delay on channel change.
180164419Ssam	 */
181164419Ssam	/* let channel change settle, 1 tick should be 'nuf (need ~1ms) */
182181305Sjhb	tsleep(sc, 0, "ad7418", hz/1000);
183164419Ssam#endif
184164419Ssam}
185164419Ssam
186164419Ssamstatic int
187164419Ssamad7418_read_2(device_t dev, int reg)
188164419Ssam{
189164419Ssam	uint8_t addr = reg;
190164419Ssam	uint8_t data[2];
191164419Ssam	struct iic_msg msgs[2] = {
192164419Ssam	     { AD7418_ADDR, IIC_M_WR, 1, &addr },
193164419Ssam	     { AD7418_ADDR, IIC_M_RD, 2, data },
194164419Ssam	};
195164419Ssam	/* NB: D15..D8 precede D7..D0 per data sheet (Fig 12) */
196164419Ssam	return iicbus_transfer(dev, msgs, 2) != 0 ?
197164419Ssam		-1 : ((data[0] << 8) | data[1]);
198164419Ssam}
199164419Ssam
200164419Ssamstatic void
201164419Ssamad7418_update(struct ad7418_softc *sc)
202164419Ssam{
203164419Ssam	int v;
204164419Ssam
205181305Sjhb	sx_assert(&sc->sc_lock, SA_XLOCKED);
206164419Ssam	/* NB: no point in updating any faster than the chip */
207164419Ssam	if (ticks - sc->sc_lastupdate > hz) {
208164419Ssam		ad7418_set_channel(sc, AD7418_CHAN_TEMP);
209164419Ssam		v = ad7418_read_2(sc->sc_dev, AD7418_TEMP);
210164419Ssam		if (v >= 0)
211164419Ssam			sc->sc_curtemp = v;
212164419Ssam		ad7418_set_channel(sc, AD7418_CHAN_VOLT);
213164419Ssam		v = ad7418_read_2(sc->sc_dev, AD7418_VOLT);
214164419Ssam		if (v >= 0)
215164419Ssam			sc->sc_curvolt = v;
216164419Ssam		sc->sc_lastupdate = ticks;
217164419Ssam	}
218164419Ssam}
219164419Ssam
220164419Ssamstatic device_method_t ad7418_methods[] = {
221164419Ssam	DEVMETHOD(device_probe,		ad7418_probe),
222164419Ssam	DEVMETHOD(device_attach,	ad7418_attach),
223164419Ssam
224246128Ssbz	DEVMETHOD_END
225164419Ssam};
226164419Ssam
227164419Ssamstatic driver_t ad7418_driver = {
228164419Ssam	"ad7418",
229164419Ssam	ad7418_methods,
230164419Ssam	sizeof(struct ad7418_softc),
231164419Ssam};
232164419Ssamstatic devclass_t ad7418_devclass;
233164419Ssam
234164419SsamDRIVER_MODULE(ad7418, iicbus, ad7418_driver, ad7418_devclass, 0, 0);
235164419SsamMODULE_VERSION(ad7418, 1);
236164419SsamMODULE_DEPEND(ad7418, iicbus, 1, 1, 1);
237