1*f057eb46Skettenis /* $OpenBSD: fanpwr.c,v 1.10 2024/05/26 22:04:52 kettenis Exp $ */
28bda9086Skettenis /*
38bda9086Skettenis * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
48bda9086Skettenis *
58bda9086Skettenis * Permission to use, copy, modify, and distribute this software for any
68bda9086Skettenis * purpose with or without fee is hereby granted, provided that the above
78bda9086Skettenis * copyright notice and this permission notice appear in all copies.
88bda9086Skettenis *
98bda9086Skettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
108bda9086Skettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
118bda9086Skettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
128bda9086Skettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
138bda9086Skettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
148bda9086Skettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
158bda9086Skettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
168bda9086Skettenis */
178bda9086Skettenis
188bda9086Skettenis #include <sys/param.h>
198bda9086Skettenis #include <sys/systm.h>
208bda9086Skettenis #include <sys/device.h>
218bda9086Skettenis #include <sys/malloc.h>
228bda9086Skettenis
238bda9086Skettenis #include <dev/ofw/openfirm.h>
24edd07788Skettenis #include <dev/ofw/ofw_pinctrl.h>
258bda9086Skettenis #include <dev/ofw/ofw_regulator.h>
268bda9086Skettenis #include <dev/ofw/fdt.h>
278bda9086Skettenis
288bda9086Skettenis #include <dev/i2c/i2cvar.h>
298bda9086Skettenis
308bda9086Skettenis /* Registers */
318bda9086Skettenis #define FAN53555_VSEL0 0x00
328bda9086Skettenis #define FAN53555_VSEL1 0x01
338bda9086Skettenis #define FAN53555_VSEL_NSEL_MASK 0x3f
348bda9086Skettenis #define FAN53555_CONTROL 0x02
358bda9086Skettenis #define FAN53555_CONTROL_SLEW_MASK (0x7 << 4)
368bda9086Skettenis #define FAN53555_CONTROL_SLEW_SHIFT 4
378bda9086Skettenis #define FAN53555_ID1 0x03
388bda9086Skettenis #define FAN53555_ID2 0x04
398bda9086Skettenis
40f478b554Skettenis #define TCS4525_VSEL1 0x10
41f478b554Skettenis #define TCS4525_VSEL0 0x11
42f478b554Skettenis #define TCS4525_VSEL_NSEL_MASK 0x7f
43f478b554Skettenis #define TCS4525_TIME 0x13
44f478b554Skettenis #define TCS4525_TIME_SLEW_MASK (0x3 << 3)
45f478b554Skettenis #define TCS4525_TIME_SLEW_SHIFT 3
46f478b554Skettenis
47a90343f6Spatrick #define RK8602_VSEL0 0x06
48a90343f6Spatrick #define RK8602_VSEL1 0x07
49a90343f6Spatrick #define RK8602_VSEL_NSEL_MASK 0xff
50a90343f6Spatrick
519bec9e43Sjsg /* Distinguish between Fairchild original and Silergy clones. */
528bda9086Skettenis enum fanpwr_id {
538bda9086Skettenis FANPWR_FAN53555, /* Fairchild FAN53555 */
54a90343f6Spatrick FANPWR_RK8602, /* Rockchip RK8602 */
558bda9086Skettenis FANPWR_SYR827, /* Silergy SYR827 */
56f478b554Skettenis FANPWR_SYR828, /* Silergy SYR828 */
57f478b554Skettenis FANPWR_TCS4525, /* TCS TCS4525 */
588bda9086Skettenis };
598bda9086Skettenis
608bda9086Skettenis struct fanpwr_softc {
618bda9086Skettenis struct device sc_dev;
628bda9086Skettenis i2c_tag_t sc_tag;
638bda9086Skettenis i2c_addr_t sc_addr;
648bda9086Skettenis
658bda9086Skettenis enum fanpwr_id sc_id;
668bda9086Skettenis uint8_t sc_vsel;
67f478b554Skettenis uint8_t sc_vsel_nsel_mask;
688bda9086Skettenis
698bda9086Skettenis struct regulator_device sc_rd;
708bda9086Skettenis uint32_t sc_vbase;
718bda9086Skettenis uint32_t sc_vstep;
728bda9086Skettenis };
738bda9086Skettenis
748bda9086Skettenis int fanpwr_match(struct device *, void *, void *);
758bda9086Skettenis void fanpwr_attach(struct device *, struct device *, void *);
768bda9086Skettenis
779fdf0c62Smpi const struct cfattach fanpwr_ca = {
788bda9086Skettenis sizeof(struct fanpwr_softc), fanpwr_match, fanpwr_attach
798bda9086Skettenis };
808bda9086Skettenis
818bda9086Skettenis struct cfdriver fanpwr_cd = {
828bda9086Skettenis NULL, "fanpwr", DV_DULL
838bda9086Skettenis };
848bda9086Skettenis
858bda9086Skettenis uint8_t fanpwr_read(struct fanpwr_softc *, int);
868bda9086Skettenis void fanpwr_write(struct fanpwr_softc *, int, uint8_t);
878bda9086Skettenis uint32_t fanpwr_get_voltage(void *);
88ff8aec25Skettenis int fanpwr_set_voltage(void *, uint32_t);
898bda9086Skettenis
908bda9086Skettenis int
fanpwr_match(struct device * parent,void * match,void * aux)918bda9086Skettenis fanpwr_match(struct device *parent, void *match, void *aux)
928bda9086Skettenis {
938bda9086Skettenis struct i2c_attach_args *ia = aux;
948bda9086Skettenis
951ac62f4eSpatrick return (strcmp(ia->ia_name, "fcs,fan53555") == 0 ||
96a90343f6Spatrick strcmp(ia->ia_name, "rockchip,rk8602") == 0 ||
97a90343f6Spatrick strcmp(ia->ia_name, "rockchip,rk8603") == 0 ||
981ac62f4eSpatrick strcmp(ia->ia_name, "silergy,syr827") == 0 ||
99f478b554Skettenis strcmp(ia->ia_name, "silergy,syr828") == 0 ||
100f478b554Skettenis strcmp(ia->ia_name, "tcs,tcs4525") == 0);
1018bda9086Skettenis }
1028bda9086Skettenis
1038bda9086Skettenis void
fanpwr_attach(struct device * parent,struct device * self,void * aux)1048bda9086Skettenis fanpwr_attach(struct device *parent, struct device *self, void *aux)
1058bda9086Skettenis {
1068bda9086Skettenis struct fanpwr_softc *sc = (struct fanpwr_softc *)self;
1078bda9086Skettenis struct i2c_attach_args *ia = aux;
1088bda9086Skettenis int node = *(int *)ia->ia_cookie;
1098bda9086Skettenis uint32_t voltage, ramp_delay;
1108bda9086Skettenis uint8_t id1, id2;
1118bda9086Skettenis
112edd07788Skettenis pinctrl_byname(node, "default");
113edd07788Skettenis
1148bda9086Skettenis sc->sc_tag = ia->ia_tag;
1158bda9086Skettenis sc->sc_addr = ia->ia_addr;
1168bda9086Skettenis
117a90343f6Spatrick if (OF_is_compatible(node, "rockchip,rk8602") ||
118a90343f6Spatrick OF_is_compatible(node, "rockchip,rk8603")) {
119a90343f6Spatrick printf(": RK8602");
120a90343f6Spatrick sc->sc_id = FANPWR_RK8602;
121a90343f6Spatrick } else if (OF_is_compatible(node, "silergy,syr827")) {
1228bda9086Skettenis printf(": SYR827");
1238bda9086Skettenis sc->sc_id = FANPWR_SYR827;
1248bda9086Skettenis } else if (OF_is_compatible(node, "silergy,syr828")) {
1258bda9086Skettenis printf(": SYR828");
1268bda9086Skettenis sc->sc_id = FANPWR_SYR828;
127f478b554Skettenis } else if (OF_is_compatible(node, "tcs,tcs4525")) {
128f478b554Skettenis printf(": TCS4525");
129f478b554Skettenis sc->sc_id = FANPWR_TCS4525;
1308bda9086Skettenis } else {
1318bda9086Skettenis printf(": FAN53555");
1328bda9086Skettenis sc->sc_id = FANPWR_FAN53555;
1338bda9086Skettenis }
1348bda9086Skettenis
135f478b554Skettenis if (sc->sc_id == FANPWR_TCS4525) {
136*f057eb46Skettenis if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0))
137f478b554Skettenis sc->sc_vsel = TCS4525_VSEL0;
138*f057eb46Skettenis else
139f478b554Skettenis sc->sc_vsel = TCS4525_VSEL1;
140f478b554Skettenis sc->sc_vsel_nsel_mask = TCS4525_VSEL_NSEL_MASK;
141a90343f6Spatrick } else if (sc->sc_id == FANPWR_RK8602) {
142*f057eb46Skettenis if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0))
143a90343f6Spatrick sc->sc_vsel = RK8602_VSEL0;
144*f057eb46Skettenis else
145a90343f6Spatrick sc->sc_vsel = RK8602_VSEL1;
146a90343f6Spatrick sc->sc_vsel_nsel_mask = RK8602_VSEL_NSEL_MASK;
147f478b554Skettenis } else {
148*f057eb46Skettenis if (OF_getpropint(node, "fcs,suspend-voltage-selector", 0))
149f478b554Skettenis sc->sc_vsel = FAN53555_VSEL0;
150*f057eb46Skettenis else
151f478b554Skettenis sc->sc_vsel = FAN53555_VSEL1;
152f478b554Skettenis sc->sc_vsel_nsel_mask = FAN53555_VSEL_NSEL_MASK;
153f478b554Skettenis }
154f478b554Skettenis
1558bda9086Skettenis id1 = fanpwr_read(sc, FAN53555_ID1);
1568bda9086Skettenis id2 = fanpwr_read(sc, FAN53555_ID2);
1578bda9086Skettenis
1588bda9086Skettenis switch (sc->sc_id) {
1598bda9086Skettenis case FANPWR_FAN53555:
1608bda9086Skettenis switch (id1 << 8 | id2) {
1618bda9086Skettenis case 0x8003: /* 00 Option */
1628bda9086Skettenis case 0x8103: /* 01 Option */
1638bda9086Skettenis case 0x8303: /* 03 Option */
1648bda9086Skettenis case 0x8503: /* 05 Option */
1658bda9086Skettenis case 0x8801: /* 08, 18 Options */
1668bda9086Skettenis case 0x880f: /* BUC08, BUC18 Options */
1678bda9086Skettenis case 0x8108: /* 79 Option */
1688bda9086Skettenis sc->sc_vbase = 600000;
1698bda9086Skettenis sc->sc_vstep = 10000;
1708bda9086Skettenis break;
1718bda9086Skettenis case 0x840f: /* 04 Option */
1728bda9086Skettenis case 0x8c0f: /* 09 Option */
1738bda9086Skettenis sc->sc_vbase = 603000;
1748bda9086Skettenis sc->sc_vstep = 12826;
1758bda9086Skettenis break;
1768bda9086Skettenis case 0x800f: /* 13 Option */
1778bda9086Skettenis sc->sc_vbase = 800000;
1788bda9086Skettenis sc->sc_vstep = 10000;
1798bda9086Skettenis break;
1808bda9086Skettenis case 0x800c: /* 23 Option */
1818bda9086Skettenis sc->sc_vbase = 600000;
1828bda9086Skettenis sc->sc_vstep = 12500;
1838bda9086Skettenis break;
1848bda9086Skettenis case 0x8004: /* 24 Option */
1858bda9086Skettenis sc->sc_vbase = 603000;
1868bda9086Skettenis sc->sc_vstep = 12967;
1878bda9086Skettenis break;
1888bda9086Skettenis default:
1898bda9086Skettenis printf(", unknown ID1 0x%02x ID2 0x%02x\n", id1, id2);
1908bda9086Skettenis return;
1918bda9086Skettenis }
1928bda9086Skettenis break;
193a90343f6Spatrick case FANPWR_RK8602:
194a90343f6Spatrick sc->sc_vbase = 500000;
195a90343f6Spatrick sc->sc_vstep = 6250;
196a90343f6Spatrick break;
1978bda9086Skettenis case FANPWR_SYR827:
1988bda9086Skettenis case FANPWR_SYR828:
1998bda9086Skettenis sc->sc_vbase = 712500;
2008bda9086Skettenis sc->sc_vstep = 12500;
2018bda9086Skettenis break;
202f478b554Skettenis case FANPWR_TCS4525:
203f478b554Skettenis sc->sc_vbase = 600000;
204f478b554Skettenis sc->sc_vstep = 6250;
205f478b554Skettenis break;
2068bda9086Skettenis }
2078bda9086Skettenis
2088bda9086Skettenis voltage = fanpwr_get_voltage(sc);
2098bda9086Skettenis printf(", %d.%02d VDC", voltage / 1000000,
2108bda9086Skettenis (voltage % 1000000) / 10000);
2118bda9086Skettenis
2128bda9086Skettenis ramp_delay = OF_getpropint(node, "regulator-ramp-delay", 0);
2138bda9086Skettenis if (ramp_delay > 0) {
214f478b554Skettenis if (sc->sc_id == FANPWR_TCS4525) {
215f478b554Skettenis uint8_t ctrl, slew;
216f478b554Skettenis
217f478b554Skettenis if (ramp_delay >= 18700)
218f478b554Skettenis slew = 0;
219f478b554Skettenis else if (ramp_delay >= 9300)
220f478b554Skettenis slew = 1;
221f478b554Skettenis else if (ramp_delay >= 4600)
222f478b554Skettenis slew = 2;
223f478b554Skettenis else
224f478b554Skettenis slew = 3;
225f478b554Skettenis ctrl = fanpwr_read(sc, TCS4525_TIME);
226f478b554Skettenis ctrl &= ~TCS4525_TIME_SLEW_MASK;
227f478b554Skettenis ctrl |= slew << TCS4525_TIME_SLEW_SHIFT;
228f478b554Skettenis fanpwr_write(sc, TCS4525_TIME, ctrl);
229f478b554Skettenis } else {
2308bda9086Skettenis uint8_t ctrl, slew;
2318bda9086Skettenis
2328bda9086Skettenis for (slew = 7; slew > 0; slew--)
2338bda9086Skettenis if ((64000 >> slew) >= ramp_delay)
2348bda9086Skettenis break;
2358bda9086Skettenis ctrl = fanpwr_read(sc, FAN53555_CONTROL);
2368bda9086Skettenis ctrl &= ~FAN53555_CONTROL_SLEW_MASK;
2378bda9086Skettenis ctrl |= slew << FAN53555_CONTROL_SLEW_SHIFT;
2388bda9086Skettenis fanpwr_write(sc, FAN53555_CONTROL, ctrl);
2398bda9086Skettenis }
240f478b554Skettenis }
2418bda9086Skettenis
2428bda9086Skettenis sc->sc_rd.rd_node = node;
2438bda9086Skettenis sc->sc_rd.rd_cookie = sc;
2448bda9086Skettenis sc->sc_rd.rd_get_voltage = fanpwr_get_voltage;
2458bda9086Skettenis sc->sc_rd.rd_set_voltage = fanpwr_set_voltage;
2468bda9086Skettenis regulator_register(&sc->sc_rd);
2478bda9086Skettenis
2488bda9086Skettenis printf("\n");
2498bda9086Skettenis }
2508bda9086Skettenis
2518bda9086Skettenis uint8_t
fanpwr_read(struct fanpwr_softc * sc,int reg)2528bda9086Skettenis fanpwr_read(struct fanpwr_softc *sc, int reg)
2538bda9086Skettenis {
2548bda9086Skettenis uint8_t cmd = reg;
2558bda9086Skettenis uint8_t val;
2568bda9086Skettenis int error;
2578bda9086Skettenis
2588bda9086Skettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
2598bda9086Skettenis error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
2608bda9086Skettenis &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
2618bda9086Skettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
2628bda9086Skettenis
2638bda9086Skettenis if (error) {
2648bda9086Skettenis printf("error %d\n", error);
2658bda9086Skettenis printf("%s: can't read register 0x%02x\n",
2668bda9086Skettenis sc->sc_dev.dv_xname, reg);
2678bda9086Skettenis val = 0xff;
2688bda9086Skettenis }
2698bda9086Skettenis
2708bda9086Skettenis return val;
2718bda9086Skettenis }
2728bda9086Skettenis
2738bda9086Skettenis void
fanpwr_write(struct fanpwr_softc * sc,int reg,uint8_t val)2748bda9086Skettenis fanpwr_write(struct fanpwr_softc *sc, int reg, uint8_t val)
2758bda9086Skettenis {
2768bda9086Skettenis uint8_t cmd = reg;
2778bda9086Skettenis int error;
2788bda9086Skettenis
2798bda9086Skettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
2808bda9086Skettenis error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
2818bda9086Skettenis &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
2828bda9086Skettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
2838bda9086Skettenis
2848bda9086Skettenis if (error) {
2858bda9086Skettenis printf("%s: can't write register 0x%02x\n",
2868bda9086Skettenis sc->sc_dev.dv_xname, reg);
2878bda9086Skettenis }
2888bda9086Skettenis }
2898bda9086Skettenis
2908bda9086Skettenis uint32_t
fanpwr_get_voltage(void * cookie)2918bda9086Skettenis fanpwr_get_voltage(void *cookie)
2928bda9086Skettenis {
2938bda9086Skettenis struct fanpwr_softc *sc = cookie;
2948bda9086Skettenis uint8_t vsel;
2958bda9086Skettenis
2968bda9086Skettenis vsel = fanpwr_read(sc, sc->sc_vsel);
297f478b554Skettenis return sc->sc_vbase + (vsel & sc->sc_vsel_nsel_mask) * sc->sc_vstep;
2988bda9086Skettenis }
2998bda9086Skettenis
3008bda9086Skettenis int
fanpwr_set_voltage(void * cookie,uint32_t voltage)3018bda9086Skettenis fanpwr_set_voltage(void *cookie, uint32_t voltage)
3028bda9086Skettenis {
3038bda9086Skettenis struct fanpwr_softc *sc = cookie;
3048bda9086Skettenis uint32_t vmin = sc->sc_vbase;
305f478b554Skettenis uint32_t vmax = vmin + sc->sc_vsel_nsel_mask * sc->sc_vstep;
3068bda9086Skettenis uint8_t vsel;
3078bda9086Skettenis
3088bda9086Skettenis if (voltage < vmin || voltage > vmax)
3098bda9086Skettenis return EINVAL;
3108bda9086Skettenis
3118bda9086Skettenis vsel = fanpwr_read(sc, sc->sc_vsel);
312f478b554Skettenis vsel &= ~sc->sc_vsel_nsel_mask;
313ff8aec25Skettenis vsel |= (voltage - sc->sc_vbase) / sc->sc_vstep;
3148bda9086Skettenis fanpwr_write(sc, sc->sc_vsel, vsel);
3158bda9086Skettenis
3168bda9086Skettenis return 0;
3178bda9086Skettenis }
318