fanpwr.c revision 1.9
1/*	$OpenBSD: fanpwr.c,v 1.9 2024/05/26 18:06:21 kettenis Exp $	*/
2/*
3 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22
23#include <dev/ofw/openfirm.h>
24#include <dev/ofw/ofw_pinctrl.h>
25#include <dev/ofw/ofw_regulator.h>
26#include <dev/ofw/fdt.h>
27
28#include <dev/i2c/i2cvar.h>
29
30/* Registers */
31#define FAN53555_VSEL0			0x00
32#define FAN53555_VSEL1			0x01
33#define  FAN53555_VSEL_BUCK_EN		0x80
34#define  FAN53555_VSEL_NSEL_MASK	0x3f
35#define FAN53555_CONTROL		0x02
36#define  FAN53555_CONTROL_SLEW_MASK	(0x7 << 4)
37#define  FAN53555_CONTROL_SLEW_SHIFT	4
38#define FAN53555_ID1			0x03
39#define FAN53555_ID2			0x04
40
41#define TCS4525_VSEL1			0x10
42#define TCS4525_VSEL0			0x11
43#define  TCS4525_VSEL_NSEL_MASK		0x7f
44#define TCS4525_TIME			0x13
45#define  TCS4525_TIME_SLEW_MASK		(0x3 << 3)
46#define  TCS4525_TIME_SLEW_SHIFT	3
47
48#define RK8602_VSEL0			0x06
49#define RK8602_VSEL1			0x07
50#define  RK8602_VSEL_NSEL_MASK		0xff
51
52/* Distinguish between Fairchild original and Silergy clones. */
53enum fanpwr_id {
54	FANPWR_FAN53555,	/* Fairchild FAN53555 */
55	FANPWR_RK8602,		/* Rockchip RK8602 */
56	FANPWR_SYR827,		/* Silergy SYR827 */
57	FANPWR_SYR828,		/* Silergy SYR828 */
58	FANPWR_TCS4525,		/* TCS TCS4525 */
59};
60
61struct fanpwr_softc {
62	struct device	sc_dev;
63	i2c_tag_t	sc_tag;
64	i2c_addr_t	sc_addr;
65
66	enum fanpwr_id	sc_id;
67	uint8_t		sc_vsel;
68	uint8_t		sc_vsel_nsel_mask;
69
70	struct regulator_device sc_rd;
71	uint32_t	sc_vbase;
72	uint32_t	sc_vstep;
73};
74
75int	fanpwr_match(struct device *, void *, void *);
76void	fanpwr_attach(struct device *, struct device *, void *);
77
78const struct cfattach fanpwr_ca = {
79	sizeof(struct fanpwr_softc), fanpwr_match, fanpwr_attach
80};
81
82struct cfdriver fanpwr_cd = {
83	NULL, "fanpwr", DV_DULL
84};
85
86uint8_t	fanpwr_read(struct fanpwr_softc *, int);
87void	fanpwr_write(struct fanpwr_softc *, int, uint8_t);
88uint32_t fanpwr_get_voltage(void *);
89int	fanpwr_set_voltage(void *, uint32_t);
90
91int
92fanpwr_match(struct device *parent, void *match, void *aux)
93{
94	struct i2c_attach_args *ia = aux;
95
96	return (strcmp(ia->ia_name, "fcs,fan53555") == 0 ||
97	    strcmp(ia->ia_name, "rockchip,rk8602") == 0 ||
98	    strcmp(ia->ia_name, "rockchip,rk8603") == 0 ||
99	    strcmp(ia->ia_name, "silergy,syr827") == 0 ||
100	    strcmp(ia->ia_name, "silergy,syr828") == 0 ||
101	    strcmp(ia->ia_name, "tcs,tcs4525") == 0);
102}
103
104void
105fanpwr_attach(struct device *parent, struct device *self, void *aux)
106{
107	struct fanpwr_softc *sc = (struct fanpwr_softc *)self;
108	struct i2c_attach_args *ia = aux;
109	int node = *(int *)ia->ia_cookie;
110	uint32_t voltage, ramp_delay;
111	uint8_t vsel_sleep_en;
112	uint8_t vsel_sleep;
113	uint8_t id1, id2;
114	uint8_t vsel;
115	int snode;
116
117	pinctrl_byname(node, "default");
118
119	sc->sc_tag = ia->ia_tag;
120	sc->sc_addr = ia->ia_addr;
121
122	if (OF_is_compatible(node, "rockchip,rk8602") ||
123	    OF_is_compatible(node, "rockchip,rk8603")) {
124		printf(": RK8602");
125		sc->sc_id = FANPWR_RK8602;
126	} else if (OF_is_compatible(node, "silergy,syr827")) {
127		printf(": SYR827");
128		sc->sc_id = FANPWR_SYR827;
129	} else if (OF_is_compatible(node, "silergy,syr828")) {
130		printf(": SYR828");
131		sc->sc_id = FANPWR_SYR828;
132	} else if (OF_is_compatible(node, "tcs,tcs4525")) {
133		printf(": TCS4525");
134		sc->sc_id = FANPWR_TCS4525;
135	} else {
136		printf(": FAN53555");
137		sc->sc_id = FANPWR_FAN53555;
138	}
139
140	if (sc->sc_id == FANPWR_TCS4525) {
141		if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0)) {
142			sc->sc_vsel = TCS4525_VSEL0;
143			vsel_sleep = TCS4525_VSEL1;
144		} else {
145			sc->sc_vsel = TCS4525_VSEL1;
146			vsel_sleep = TCS4525_VSEL0;
147		}
148		sc->sc_vsel_nsel_mask = TCS4525_VSEL_NSEL_MASK;
149		vsel_sleep_en = vsel_sleep;
150	} else if (sc->sc_id == FANPWR_RK8602) {
151		if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0)) {
152			sc->sc_vsel = RK8602_VSEL0;
153			vsel_sleep = RK8602_VSEL1;
154			vsel_sleep_en = FAN53555_VSEL1;
155		} else {
156			sc->sc_vsel = RK8602_VSEL1;
157			vsel_sleep = RK8602_VSEL0;
158			vsel_sleep_en = FAN53555_VSEL0;
159		}
160		sc->sc_vsel_nsel_mask = RK8602_VSEL_NSEL_MASK;
161	} else {
162		if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0)) {
163			sc->sc_vsel = FAN53555_VSEL0;
164			vsel_sleep = FAN53555_VSEL1;
165		} else {
166			sc->sc_vsel = FAN53555_VSEL1;
167			vsel_sleep = FAN53555_VSEL0;
168		}
169		sc->sc_vsel_nsel_mask = FAN53555_VSEL_NSEL_MASK;
170		vsel_sleep_en = vsel_sleep;
171	}
172
173	id1 = fanpwr_read(sc, FAN53555_ID1);
174	id2 = fanpwr_read(sc, FAN53555_ID2);
175
176	switch (sc->sc_id) {
177	case FANPWR_FAN53555:
178		switch (id1 << 8 | id2) {
179		case 0x8003:	/* 00 Option */
180		case 0x8103:	/* 01 Option */
181		case 0x8303:	/* 03 Option */
182		case 0x8503:	/* 05 Option */
183		case 0x8801:	/* 08, 18 Options */
184		case 0x880f:	/* BUC08, BUC18 Options */
185		case 0x8108:	/* 79 Option */
186			sc->sc_vbase = 600000;
187			sc->sc_vstep = 10000;
188			break;
189		case 0x840f:	/* 04 Option */
190		case 0x8c0f:	/* 09 Option */
191			sc->sc_vbase = 603000;
192			sc->sc_vstep = 12826;
193			break;
194		case 0x800f:	/* 13 Option */
195			sc->sc_vbase = 800000;
196			sc->sc_vstep = 10000;
197			break;
198		case 0x800c:	/* 23 Option */
199			sc->sc_vbase = 600000;
200			sc->sc_vstep = 12500;
201			break;
202		case 0x8004:	/* 24 Option */
203			sc->sc_vbase = 603000;
204			sc->sc_vstep = 12967;
205			break;
206		default:
207			printf(", unknown ID1 0x%02x ID2 0x%02x\n", id1, id2);
208			return;
209		}
210		break;
211	case FANPWR_RK8602:
212		sc->sc_vbase = 500000;
213		sc->sc_vstep = 6250;
214		break;
215	case FANPWR_SYR827:
216	case FANPWR_SYR828:
217		sc->sc_vbase = 712500;
218		sc->sc_vstep = 12500;
219		break;
220	case FANPWR_TCS4525:
221		sc->sc_vbase = 600000;
222		sc->sc_vstep = 6250;
223		break;
224	}
225
226	voltage = fanpwr_get_voltage(sc);
227	printf(", %d.%02d VDC", voltage / 1000000,
228		    (voltage % 1000000) / 10000);
229
230	ramp_delay = OF_getpropint(node, "regulator-ramp-delay", 0);
231	if (ramp_delay > 0) {
232		if (sc->sc_id == FANPWR_TCS4525) {
233			uint8_t ctrl, slew;
234
235			if (ramp_delay >= 18700)
236				slew = 0;
237			else if (ramp_delay >= 9300)
238				slew = 1;
239			else if (ramp_delay >= 4600)
240				slew = 2;
241			else
242				slew = 3;
243			ctrl = fanpwr_read(sc, TCS4525_TIME);
244			ctrl &= ~TCS4525_TIME_SLEW_MASK;
245			ctrl |= slew << TCS4525_TIME_SLEW_SHIFT;
246			fanpwr_write(sc, TCS4525_TIME, ctrl);
247		} else {
248			uint8_t ctrl, slew;
249
250			for (slew = 7; slew > 0; slew--)
251				if ((64000 >> slew) >= ramp_delay)
252					break;
253			ctrl = fanpwr_read(sc, FAN53555_CONTROL);
254			ctrl &= ~FAN53555_CONTROL_SLEW_MASK;
255			ctrl |= slew << FAN53555_CONTROL_SLEW_SHIFT;
256			fanpwr_write(sc, FAN53555_CONTROL, ctrl);
257		}
258	}
259
260	sc->sc_rd.rd_node = node;
261	sc->sc_rd.rd_cookie = sc;
262	sc->sc_rd.rd_get_voltage = fanpwr_get_voltage;
263	sc->sc_rd.rd_set_voltage = fanpwr_set_voltage;
264	regulator_register(&sc->sc_rd);
265
266	printf("\n");
267
268	snode = OF_getnodebyname(node, "regulator-state-mem");
269	if (snode) {
270		vsel = fanpwr_read(sc, vsel_sleep_en);
271		if (OF_getpropbool(snode, "regulator-on-in-suspend"))
272			vsel |= FAN53555_VSEL_BUCK_EN;
273		if (OF_getpropbool(snode, "regulator-off-in-suspend"))
274			vsel &= ~FAN53555_VSEL_BUCK_EN;
275		fanpwr_write(sc, vsel_sleep_en, vsel);
276
277		voltage = OF_getpropint(snode,
278		    "regulator-suspend-min-microvolt", 0);
279		voltage = OF_getpropint(snode,
280		    "regulator-suspend-microvolt", voltage);
281		if (voltage > 0) {
282			vsel = fanpwr_read(sc, vsel_sleep);
283			vsel &= ~sc->sc_vsel_nsel_mask;
284			vsel |= (voltage - vsel_sleep) / sc->sc_vstep;
285			fanpwr_write(sc, vsel_sleep, vsel);
286		}
287	}
288}
289
290uint8_t
291fanpwr_read(struct fanpwr_softc *sc, int reg)
292{
293	uint8_t cmd = reg;
294	uint8_t val;
295	int error;
296
297	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
298	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
299	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
300	iic_release_bus(sc->sc_tag, I2C_F_POLL);
301
302	if (error) {
303		printf("error %d\n", error);
304		printf("%s: can't read register 0x%02x\n",
305		    sc->sc_dev.dv_xname, reg);
306		val = 0xff;
307	}
308
309	return val;
310}
311
312void
313fanpwr_write(struct fanpwr_softc *sc, int reg, uint8_t val)
314{
315	uint8_t cmd = reg;
316	int error;
317
318	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
319	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
320	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
321	iic_release_bus(sc->sc_tag, I2C_F_POLL);
322
323	if (error) {
324		printf("%s: can't write register 0x%02x\n",
325		    sc->sc_dev.dv_xname, reg);
326	}
327}
328
329uint32_t
330fanpwr_get_voltage(void *cookie)
331{
332	struct fanpwr_softc *sc = cookie;
333	uint8_t vsel;
334
335	vsel = fanpwr_read(sc, sc->sc_vsel);
336	return sc->sc_vbase + (vsel & sc->sc_vsel_nsel_mask) * sc->sc_vstep;
337}
338
339int
340fanpwr_set_voltage(void *cookie, uint32_t voltage)
341{
342	struct fanpwr_softc *sc = cookie;
343	uint32_t vmin = sc->sc_vbase;
344	uint32_t vmax = vmin + sc->sc_vsel_nsel_mask * sc->sc_vstep;
345	uint8_t vsel;
346
347	if (voltage < vmin || voltage > vmax)
348		return EINVAL;
349
350	vsel = fanpwr_read(sc, sc->sc_vsel);
351	vsel &= ~sc->sc_vsel_nsel_mask;
352	vsel |= (voltage - sc->sc_vbase) / sc->sc_vstep;
353	fanpwr_write(sc, sc->sc_vsel, vsel);
354
355	return 0;
356}
357