FreeBSD kernel IICBUS device code
pcf8591.c
Go to the documentation of this file.
1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) Andriy Gapon
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions, and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include "opt_platform.h"
33
34#include <sys/param.h>
35#include <sys/bus.h>
36#include <sys/kernel.h>
37#include <sys/module.h>
38#include <sys/sysctl.h>
39#include <sys/systm.h>
40
41#include <machine/bus.h>
42
43#include <dev/iicbus/iicbus.h>
44#include <dev/iicbus/iiconf.h>
45
46#ifdef FDT
47#include <dev/ofw/ofw_bus.h>
48#include <dev/ofw/ofw_bus_subr.h>
49#endif
50
51/*
52 * Driver for PCF8591 I2C 8-bit ADC and DAC.
53 */
54#define CTRL_CH_SELECT_MASK 0x03
55#define CTRL_AUTOINC_EN 0x04
56#define CTRL_CH_CONFIG_MASK 0x30
57#define CTRL_CH_CONFIG_4_SINGLE 0x00
58#define CTRL_CH_CONFIG_3_DIFF 0x10
59#define CTRL_CH_CONFIG_2_SINGLE_1_DIFF 0x20
60#define CTRL_CH_CONFIG_2_DIFF 0x30
61#define CTRL_OUTPUT_EN 0x40
62
64 device_t sc_dev;
66 uint8_t sc_addr;
67 uint8_t sc_cfg;
68 uint8_t sc_output;
69};
70
71#ifdef FDT
72static struct ofw_compat_data compat_data[] = {
73 { "nxp,pcf8591", true },
74 { NULL, false }
75};
76#endif
77
78static int
80{
81
82 struct iic_msg msg;
83 uint8_t data[2];
84 struct pcf8591_softc *sc;
85 int error;
86
87 sc = device_get_softc(dev);
88 data[0] = sc->sc_cfg;
89 data[1] = sc->sc_output;
90 msg.slave = sc->sc_addr;
91 msg.flags = IIC_M_WR;
92 msg.len = nitems(data);
93 msg.buf = data;
94
95 error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
96 return (error);
97}
98
99static int
100pcf8591_get_reading(device_t dev, uint8_t *reading)
101{
102 struct iic_msg msg;
103 struct pcf8591_softc *sc;
104 int error;
105
106 sc = device_get_softc(dev);
107
108 msg.slave = sc->sc_addr;
109 msg.flags = IIC_M_RD;
110 msg.len = 1;
111 msg.buf = reading;
112
113 error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
114 return (error);
115}
116
117static int
118pcf8591_select_channel(device_t dev, int channel)
119{
120 struct pcf8591_softc *sc;
121 int error;
122 uint8_t unused;
123
124 sc = device_get_softc(dev);
125 if (channel >= sc->sc_ch_count)
126 return (EINVAL);
127 sc->sc_cfg &= ~CTRL_CH_SELECT_MASK;
128 sc->sc_cfg += channel;
129 error = pcf8591_set_config(dev);
130 if (error != 0)
131 return (error);
132
133 /*
134 * The next read is still for the old channel,
135 * so do it and discard.
136 */
137 error = pcf8591_get_reading(dev, &unused);
138 return (error);
139}
140
141static int
142pcf8591_channel_sysctl(SYSCTL_HANDLER_ARGS)
143{
144 device_t dev;
145 int error, channel, val;
146 uint8_t reading;
147
148 dev = arg1;
149 channel = arg2;
150
151 if (req->oldptr != NULL) {
152 error = pcf8591_select_channel(dev, channel);
153 if (error != 0)
154 return (EIO);
155 error = pcf8591_get_reading(dev, &reading);
156 if (error != 0)
157 return (EIO);
158 val = reading;
159 }
160 error = sysctl_handle_int(oidp, &val, 0, req);
161 return (error);
162}
163
164static void
166{
167 device_t dev;
168 struct pcf8591_softc *sc;
169 struct sysctl_ctx_list *ctx;
170 struct sysctl_oid *tree_node;
171 struct sysctl_oid_list *tree;
172 struct sysctl_oid *inputs_node;
173 struct sysctl_oid_list *inputs;
174
175 sc = arg;
176 dev = sc->sc_dev;
177
178 /*
179 * Set initial -- and, for the time being, fixed -- configuration.
180 * Channel auto-incrementi is disabled, although it could be more
181 * performant and precise for bulk channel queries.
182 * The inputs are configured as four single channels.
183 * The output is disabled.
184 */
185 sc->sc_cfg = 0;
186 sc->sc_output = 0;
187 sc->sc_ch_count = 4;
188 (void)pcf8591_set_config(dev);
189
190 ctx = device_get_sysctl_ctx(dev);
191 tree_node = device_get_sysctl_tree(dev);
192 tree = SYSCTL_CHILDREN(tree_node);
193
194 inputs_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "inputs",
195 CTLTYPE_NODE, NULL, "Input channels");
196 inputs = SYSCTL_CHILDREN(inputs_node);
197 for (int i = 0; i < sc->sc_ch_count; i++) {
198 char buf[4];
199
200 snprintf(buf, sizeof(buf), "%d", i);
201 SYSCTL_ADD_PROC(ctx, inputs, OID_AUTO, buf,
202 CTLTYPE_INT | CTLFLAG_RD, dev, i,
203 pcf8591_channel_sysctl, "I", "Input level from 0 to 255 "
204 "(relative to Vref)");
205 }
206}
207
208static int
210{
211#ifdef FDT
212 if (!ofw_bus_status_okay(dev))
213 return (ENXIO);
214#endif
215 device_set_desc(dev, "PCF8591 8-bit ADC / DAC");
216#ifdef FDT
217 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data)
218 return (BUS_PROBE_GENERIC);
219#endif
220 return (BUS_PROBE_NOWILDCARD);
221}
222
223static int
225{
226 struct pcf8591_softc *sc;
227
228 sc = device_get_softc(dev);
229 sc->sc_dev = dev;
230 sc->sc_addr = iicbus_get_addr(dev);
231
232 /*
233 * We have to wait until interrupts are enabled. Usually I2C read
234 * and write only works when the interrupts are available.
235 */
236 config_intrhook_oneshot(pcf8591_start, sc);
237 return (0);
238}
239
240static int
242{
243 return (0);
244}
245
246static device_method_t pcf8591_methods[] = {
247 /* Device interface */
248 DEVMETHOD(device_probe, pcf8591_probe),
249 DEVMETHOD(device_attach, pcf8591_attach),
250 DEVMETHOD(device_detach, pcf8591_detach),
251
252 DEVMETHOD_END
253};
254
255static driver_t pcf8591_driver = {
256 "pcf8591",
258 sizeof(struct pcf8591_softc)
259};
260
261static devclass_t pcf8591_devclass;
262
265MODULE_VERSION(pcf8591, 1);
266#ifdef FDT
268#endif
#define IIC_M_WR
Definition: ad7418.c:45
static ds13_compat_data compat_data[]
Definition: ds13rtc.c:187
#define IIC_M_RD
Definition: iic.h:42
caddr_t data
Definition: iicbb_if.m:61
int val
Definition: iicbb_if.m:83
#define IICBUS_FDT_PNP_INFO(t)
Definition: iicbus.h:77
char * buf
Definition: iicbus_if.m:55
INTERFACE iicbus
Definition: iicbus_if.m:32
int iicbus_transfer_excl(device_t dev, struct iic_msg *msgs, uint32_t nmsgs, int how)
Definition: iiconf.c:449
#define IICBUS_MINVER
Definition: iiconf.h:171
#define IIC_INTRWAIT
Definition: iiconf.h:48
#define IICBUS_MAXVER
Definition: iiconf.h:172
#define IICBUS_PREFVER
Definition: iiconf.h:173
device_t dev
Definition: ofw_iicbus_if.m:38
static device_method_t pcf8591_methods[]
Definition: pcf8591.c:246
static int pcf8591_detach(device_t dev)
Definition: pcf8591.c:241
static devclass_t pcf8591_devclass
Definition: pcf8591.c:261
static void pcf8591_start(void *arg)
Definition: pcf8591.c:165
static int pcf8591_get_reading(device_t dev, uint8_t *reading)
Definition: pcf8591.c:100
static int pcf8591_probe(device_t dev)
Definition: pcf8591.c:209
static int pcf8591_channel_sysctl(SYSCTL_HANDLER_ARGS)
Definition: pcf8591.c:142
static int pcf8591_select_channel(device_t dev, int channel)
Definition: pcf8591.c:118
static driver_t pcf8591_driver
Definition: pcf8591.c:255
__FBSDID("$FreeBSD$")
DRIVER_MODULE(pcf8591, iicbus, pcf8591_driver, pcf8591_devclass, 0, 0)
MODULE_VERSION(pcf8591, 1)
static int pcf8591_attach(device_t dev)
Definition: pcf8591.c:224
MODULE_DEPEND(pcf8591, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER)
static int pcf8591_set_config(device_t dev)
Definition: pcf8591.c:79
Definition: iic.h:38
uint16_t slave
Definition: iic.h:39
uint16_t len
Definition: iic.h:45
uint8_t * buf
Definition: iic.h:46
uint16_t flags
Definition: iic.h:40
uint8_t sc_output
Definition: pcf8591.c:68
uint8_t sc_cfg
Definition: pcf8591.c:67
device_t sc_dev
Definition: pcf8591.c:64
int sc_ch_count
Definition: pcf8591.c:65
uint8_t sc_addr
Definition: pcf8591.c:66