1/*	$NetBSD: g760a.c,v 1.6 2020/11/26 12:53:03 skrll Exp $	*/
2
3/*-
4 * Copyright (C) 2008 A.Leo.
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 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * The driver for the G760A FAN Speed PWM controller.
31 */
32
33#include <sys/cdefs.h>
34
35__KERNEL_RCSID(0, "$NetBSD: g760a.c,v 1.6 2020/11/26 12:53:03 skrll Exp $");
36
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/device.h>
40#include <sys/kernel.h>
41#include <sys/conf.h>
42#include <sys/sysctl.h>
43
44#include <dev/sysmon/sysmonvar.h>
45
46#include <dev/i2c/i2cvar.h>
47#include <dev/i2c/g760areg.h>
48
49
50struct g760a_softc {
51	device_t sc_dev;
52	struct sysmon_envsys *sc_sme;
53
54	envsys_data_t sc_sensor;
55	i2c_tag_t sc_tag;
56	int sc_addr;
57};
58
59static int g760a_match(device_t, struct cfdata*, void*);
60static void g760a_attach(device_t, device_t, void*);
61static void g760a_setup(struct g760a_softc*);
62static uint8_t g760a_readreg(struct g760a_softc*, uint8_t);
63static void g760a_writereg(struct g760a_softc*, uint8_t, uint8_t);
64
65static int g760a_reg2rpm(int);
66
67static void g760a_refresh(struct sysmon_envsys*, envsys_data_t*);
68static int sysctl_g760a_rpm(SYSCTLFN_PROTO);
69
70CFATTACH_DECL_NEW(g760a, sizeof(struct g760a_softc),
71		g760a_match, g760a_attach, NULL, NULL);
72
73static int
74g760a_match(device_t parent, struct cfdata* cf, void* arg)
75{
76	struct i2c_attach_args* ia = arg;
77
78	if (ia->ia_addr == G760A_ADDR) {
79		/*
80		 * TODO: set up minimal speed?
81		 */
82		return I2C_MATCH_ADDRESS_ONLY;
83	}
84
85	return 0;
86}
87
88
89static void
90g760a_attach(device_t parent, device_t self, void* arg)
91{
92	struct i2c_attach_args* ia = arg;
93	struct g760a_softc* sc = device_private(self);
94
95	aprint_normal(": G760A Fan Controller\n");
96
97	sc->sc_dev = self;
98	sc->sc_tag = ia->ia_tag;
99	sc->sc_addr = ia->ia_addr;
100
101	g760a_setup(sc);
102}
103
104
105static int
106g760a_reg2rpm(int n)
107{
108	if(n == 255)
109		return 0;
110
111	if(n == 0)
112		return 255;
113
114	return G760A_N2RPM(n);
115}
116
117
118static uint8_t
119g760a_readreg(struct g760a_softc* sc, uint8_t reg)
120{
121	uint8_t data;
122
123	if (iic_acquire_bus(sc->sc_tag, 0)) {
124		aprint_error_dev(sc->sc_dev, "unable to acquire the iic bus\n");
125		return 0;
126	}
127
128	iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, &reg, 1,
129	    &data, 1, 0);
130	iic_release_bus(sc->sc_tag, 0);
131
132	return data;
133}
134
135
136static void
137g760a_writereg(struct g760a_softc* sc, uint8_t reg, uint8_t data)
138{
139
140	if (iic_acquire_bus(sc->sc_tag, 0)) {
141		aprint_error_dev(sc->sc_dev, "unable to acquire the iic bus\n");
142		return;
143	}
144
145	iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, &reg, 1,
146	    &data, 1, 0);
147	iic_release_bus(sc->sc_tag, 0);
148}
149
150
151SYSCTL_SETUP(sysctl_g760a_setup, "sysctl g760a subtree setup")
152{
153
154	sysctl_createv(NULL, 0, NULL, NULL,
155			CTLFLAG_PERMANENT,
156			CTLTYPE_NODE, "machdep", NULL,
157			NULL, 0, NULL, 0,
158			CTL_MACHDEP, CTL_EOL);
159}
160
161
162/*ARGUSED*/
163static int
164sysctl_g760a_rpm(SYSCTLFN_ARGS)
165{
166	int error, t;
167	struct sysctlnode node;
168	struct g760a_softc* sc;
169
170	node = *rnode;
171	sc = node.sysctl_data;
172
173	t = g760a_readreg(sc, G760A_REG_SET_CNT);
174	t = g760a_reg2rpm(t);
175
176	node.sysctl_data = &t;
177
178	error = sysctl_lookup(SYSCTLFN_CALL(&node));
179
180	if (error || newp == NULL)
181		return error;
182
183	if (t > 20000 || t < G760A_N2RPM(254))
184		return EINVAL;
185
186	t = g760a_reg2rpm(t);
187
188	g760a_writereg(sc, G760A_REG_SET_CNT, t);
189
190	return 0;
191}
192
193
194static void
195g760a_refresh(struct sysmon_envsys* sme, envsys_data_t* edata)
196{
197	struct g760a_softc* sc = sme->sme_cookie;
198
199	switch (edata->units) {
200		case ENVSYS_SFANRPM:
201			{
202				uint8_t n;
203
204				n = g760a_readreg(sc, G760A_REG_ACT_CNT);
205				edata->value_cur = g760a_reg2rpm(n);
206			}
207			break;
208		default:
209			aprint_error_dev(sc->sc_dev, "oops\n");
210	}
211
212	edata->state = ENVSYS_SVALID;
213}
214
215
216static void
217g760a_setup(struct g760a_softc* sc)
218{
219	int error;
220	int ret;
221	const struct sysctlnode *me, *node;
222
223	sc->sc_sme = sysmon_envsys_create();
224
225	ret = sysctl_createv(NULL, 0, NULL, &me,
226			CTLFLAG_READWRITE,
227			CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
228			NULL, 0, NULL, 0,
229			CTL_MACHDEP, CTL_CREATE, CTL_EOL);
230	if (ret)
231		goto sysctl_failed;
232
233	(void)strlcpy(sc->sc_sensor.desc, "sysfan rpm",
234			sizeof(sc->sc_sensor.desc));
235	sc->sc_sensor.units = ENVSYS_SFANRPM;
236	sc->sc_sensor.state = ENVSYS_SINVALID;
237
238	if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor))
239		goto out;
240
241	ret = sysctl_createv(NULL, 0, NULL, &node,
242			CTLFLAG_READWRITE,
243			CTLTYPE_INT, "rpm", sc->sc_sensor.desc,
244			sysctl_g760a_rpm, 0x42, (void*)sc, 0,
245			CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);
246
247	if (ret)
248		goto sysctl_failed;
249
250	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
251	sc->sc_sme->sme_cookie = sc;
252	sc->sc_sme->sme_refresh = g760a_refresh;
253
254	error = sysmon_envsys_register(sc->sc_sme);
255
256	if (error) {
257		aprint_error_dev(sc->sc_dev,
258		    "unable to register with sysmon. errorcode %i\n", error);
259		goto out;
260	}
261
262	return;
263
264sysctl_failed:
265	aprint_error_dev(sc->sc_dev,
266	    "couldn't create sysctl nodes (%d)\n", ret);
267
268out:
269	sysmon_envsys_destroy(sc->sc_sme);
270}
271