/* $OpenBSD: axppmic.c,v 1.1 2017/12/17 18:25:25 kettenis Exp $ */ /* * Copyright (c) 2017 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #define AXP806_REG_ADDR_EXT 0xff #define AXP806_REG_ADDR_EXT_MASTER_MODE (0 << 4) #define AXP806_REG_ADDR_EXT_SLAVE_MODE (1 << 4) struct axppmic_regdata { const char *name; uint8_t ereg, emask, eval, dval; uint8_t vreg, vmask; uint32_t base, delta; uint32_t base2, delta2; }; struct axppmic_regdata axp806_regdata[] = { { "dcdca", 0x10, (1 << 0), (1 << 0), (0 << 0), 0x12, 0x7f, 600000, 10000, 1120000, 20000 }, { "dcdcb", 0x10, (1 << 1), (1 << 1), (0 << 1), 0x13, 0x1f, 1000000, 50000 }, { "dcdcc", 0x10, (1 << 2), (1 << 2), (0 << 2), 0x14, 0x7f, 600000, 10000, 1120000, 20000 }, { "dcdcd", 0x10, (1 << 3), (1 << 3), (0 << 3), 0x15, 0x3f, 600000, 20000, 1600000, 100000 }, { "dcdce", 0x10, (1 << 4), (1 << 4), (0 << 4), 0x16, 0x1f, 1100000, 100000 }, { "aldo1", 0x10, (1 << 5), (1 << 5), (0 << 5), 0x17, 0x1f, 700000, 100000 }, { "aldo2", 0x10, (1 << 6), (1 << 6), (0 << 6), 0x18, 0x1f, 700000, 100000 }, { "aldo3", 0x10, (1 << 7), (1 << 7), (0 << 7), 0x19, 0x1f, 700000, 100000 }, { "bldo1", 0x11, (1 << 0), (1 << 0), (0 << 0), 0x20, 0x0f, 700000, 100000 }, { "bldo2", 0x11, (1 << 1), (1 << 1), (0 << 1), 0x21, 0x0f, 700000, 100000 }, { "bldo3", 0x11, (1 << 2), (1 << 2), (0 << 2), 0x22, 0x0f, 700000, 100000 }, { "bldo4", 0x11, (1 << 3), (1 << 3), (0 << 3), 0x23, 0x0f, 700000, 100000 }, { "cldo1", 0x11, (1 << 4), (1 << 4), (0 << 4), 0x24, 0x1f, 700000, 100000 }, { "cldo2", 0x11, (1 << 5), (1 << 5), (0 << 5), 0x25, 0x1f, 700000, 100000, 3600000, 200000 }, { "cldo3", 0x11, (1 << 6), (1 << 6), (0 << 6), 0x26, 0x1f, 700000, 100000 }, { "sw", 0x11, (1 << 7), (1 << 7), (0 << 7) }, { NULL } }; struct axppmic_regdata axp809_regdata[] = { { "dcdc1", 0x10, (1 << 1), (1 << 1), (0 << 1), 0x21, 0x1f, 1600000, 100000 }, { "dcdc2", 0x10, (1 << 2), (1 << 2), (0 << 2), 0x22, 0x3f, 600000, 20000 }, { "dcdc3", 0x10, (1 << 3), (1 << 3), (0 << 3), 0x23, 0x3f, 600000, 20000 }, { "dcdc4", 0x10, (1 << 4), (1 << 4), (0 << 4), 0x24, 0x3f, 600000, 20000, 1800000, 100000 }, { "dcdc5", 0x10, (1 << 5), (1 << 5), (0 << 5), 0x25, 0x1f, 1000000, 50000 }, { "dc5ldo", 0x10, (1 << 0), (1 << 0), (0 << 0), 0x1c, 0x07, 700000, 100000 }, { "aldo1", 0x10, (1 << 6), (1 << 6), (0 << 6), 0x28, 0x1f, 700000, 100000 }, { "aldo2", 0x10, (1 << 7), (1 << 7), (0 << 7), 0x28, 0x1f, 700000, 100000 }, { "aldo3", 0x12, (1 << 5), (1 << 5), (0 << 5), 0x28, 0x1f, 700000, 100000 }, { "dldo1", 0x12, (1 << 3), (1 << 3), (0 << 3), 0x15, 0x1f, 700000, 100000 }, { "dldo2", 0x12, (1 << 4), (1 << 4), (0 << 4), 0x16, 0x1f, 700000, 100000 }, { "eldo1", 0x12, (1 << 0), (1 << 0), (0 << 0), 0x19, 0x1f, 700000, 100000 }, { "eldo2", 0x12, (1 << 1), (1 << 1), (0 << 1), 0x1a, 0x1f, 700000, 100000 }, { "eldo3", 0x12, (1 << 2), (1 << 2), (0 << 2), 0x19, 0x1f, 700000, 100000 }, { "ldo_io0", 0x90, 0x07, 0x03, 0x04, 0x91, 0x1f, 700000, 100000 }, { "ldo_io1", 0x92, 0x07, 0x03, 0x04, 0x93, 0x1f, 700000, 100000 }, { NULL } }; struct axppmic_device { const char *name; const char *chip; struct axppmic_regdata *regdata; }; struct axppmic_device axppmic_devices[] = { { "x-powers,axp806", "AXP806", axp806_regdata }, { "x-powers,axp809", "AXP809", axp809_regdata } }; const struct axppmic_device * axppmic_lookup(const char *name) { int i; for (i = 0; i < nitems(axppmic_devices); i++) { if (strcmp(name, axppmic_devices[i].name) == 0) return &axppmic_devices[i]; } return NULL; } struct axppmic_softc { struct device sc_dev; void *sc_cookie; uint16_t sc_rta; struct axppmic_regdata *sc_regdata; }; inline uint8_t axppmic_read_reg(struct axppmic_softc *sc, uint8_t reg) { return rsb_read_1(sc->sc_cookie, sc->sc_rta, reg); } inline void axppmic_write_reg(struct axppmic_softc *sc, uint8_t reg, uint8_t value) { rsb_write_1(sc->sc_cookie, sc->sc_rta, reg, value); } int axppmic_match(struct device *, void *, void *); void axppmic_attach(struct device *, struct device *, void *); struct cfattach axppmic_rsb_ca = { sizeof(struct axppmic_softc), axppmic_match, axppmic_attach }; struct cfdriver axppmic_rsb_cd = { NULL, "axppmic", DV_DULL }; void axppmic_attach_regulator(struct axppmic_softc *, int); int axppmic_match(struct device *parent, void *match, void *aux) { struct rsb_attach_args *ra = aux; if (axppmic_lookup(ra->ra_name)) return 1; return 0; } void axppmic_attach(struct device *parent, struct device *self, void *aux) { struct axppmic_softc *sc = (struct axppmic_softc *)self; const struct axppmic_device *device; struct rsb_attach_args *ra = aux; int node; sc->sc_cookie = ra->ra_cookie; sc->sc_rta = ra->ra_rta; device = axppmic_lookup(ra->ra_name); printf(": %s\n", device->chip); sc->sc_regdata = device->regdata; /* Switch AXP806 into master or slave mode. */ if (strcmp(ra->ra_name, "x-powers,axp806") == 0) { if (OF_getproplen(ra->ra_node, "x-powers,master-mode") == 0) { axppmic_write_reg(sc, AXP806_REG_ADDR_EXT, AXP806_REG_ADDR_EXT_MASTER_MODE); } else { axppmic_write_reg(sc, AXP806_REG_ADDR_EXT, AXP806_REG_ADDR_EXT_SLAVE_MODE); } } node = OF_getnodebyname(ra->ra_node, "regulators"); if (node == 0) return; for (node = OF_child(node); node; node = OF_peer(node)) axppmic_attach_regulator(sc, node); } struct axppmic_regulator { struct axppmic_softc *ar_sc; uint8_t ar_ereg, ar_emask; uint8_t ar_eval, ar_dval; uint8_t ar_vreg, ar_vmask; uint32_t ar_base, ar_delta; uint32_t ar_base2, ar_delta2; struct regulator_device ar_rd; }; uint32_t axppmic_get_voltage(void *); int axppmic_set_voltage(void *, uint32_t); int axppmic_enable(void *, int); void axppmic_attach_regulator(struct axppmic_softc *sc, int node) { struct axppmic_regulator *ar; char name[32]; int i; name[0] = 0; OF_getprop(node, "name", name, sizeof(name)); name[sizeof(name) - 1] = 0; for (i = 0; sc->sc_regdata[i].name; i++) { if (strcmp(sc->sc_regdata[i].name, name) == 0) break; } if (sc->sc_regdata[i].name == NULL) return; ar = malloc(sizeof(*ar), M_DEVBUF, M_WAITOK | M_ZERO); ar->ar_sc = sc; ar->ar_ereg = sc->sc_regdata[i].ereg; ar->ar_emask = sc->sc_regdata[i].emask; ar->ar_eval = sc->sc_regdata[i].eval; ar->ar_dval = sc->sc_regdata[i].dval; ar->ar_vreg = sc->sc_regdata[i].vreg; ar->ar_vmask = sc->sc_regdata[i].vmask; ar->ar_base = sc->sc_regdata[i].base; ar->ar_delta = sc->sc_regdata[i].delta; ar->ar_rd.rd_node = node; ar->ar_rd.rd_cookie = ar; ar->ar_rd.rd_get_voltage = axppmic_get_voltage; ar->ar_rd.rd_set_voltage = axppmic_set_voltage; ar->ar_rd.rd_enable = axppmic_enable; regulator_register(&ar->ar_rd); } uint32_t axppmic_get_voltage(void *cookie) { struct axppmic_regulator *ar = cookie; uint32_t voltage; uint8_t value; value = axppmic_read_reg(ar->ar_sc, ar->ar_vreg); value &= ar->ar_vmask; voltage = ar->ar_base + value * ar->ar_delta; if (ar->ar_base2 > 0 && voltage > ar->ar_base2) { value -= (ar->ar_base2 - ar->ar_base) / ar->ar_delta; voltage = ar->ar_base2 + value * ar->ar_delta2; } return voltage; } int axppmic_set_voltage(void *cookie, uint32_t voltage) { struct axppmic_regulator *ar = cookie; uint32_t value, reg; if (voltage < ar->ar_base) return EINVAL; value = (voltage - ar->ar_base) / ar->ar_delta; if (ar->ar_base2 > 0 && voltage > ar->ar_base2) { value = (ar->ar_base2 - ar->ar_base) / ar->ar_delta; value += (voltage - ar->ar_base2) / ar->ar_delta2; } if (value > ar->ar_vmask) return EINVAL; reg = axppmic_read_reg(ar->ar_sc, ar->ar_vreg); reg &= ar->ar_vmask; axppmic_write_reg(ar->ar_sc, ar->ar_vreg, reg | value); return 0; } int axppmic_enable(void *cookie, int on) { struct axppmic_regulator *ar = cookie; uint8_t reg; reg = axppmic_read_reg(ar->ar_sc, ar->ar_ereg); reg &= ~ar->ar_emask; if (on) reg |= ar->ar_eval; else reg |= ar->ar_dval; axppmic_write_reg(ar->ar_sc, ar->ar_ereg, reg); return 0; }