/* $OpenBSD: ofw_regulator.c,v 1.20 2024/06/14 20:00:32 kettenis Exp $ */ /* * Copyright (c) 2016 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 #define REGULATOR_VOLTAGE 0 #define REGULATOR_CURRENT 1 LIST_HEAD(, regulator_device) regulator_devices = LIST_HEAD_INITIALIZER(regulator_devices); LIST_HEAD(, regulator_notifier) regulator_notifiers = LIST_HEAD_INITIALIZER(regulator_notifiers); int regulator_type(int); uint32_t regulator_gpio_get(int); int regulator_gpio_set(int, uint32_t); void regulator_do_notify(uint32_t, uint32_t); void regulator_register(struct regulator_device *rd) { rd->rd_volt_min = OF_getpropint(rd->rd_node, "regulator-min-microvolt", 0); rd->rd_volt_max = OF_getpropint(rd->rd_node, "regulator-max-microvolt", ~0); KASSERT(rd->rd_volt_min <= rd->rd_volt_max); rd->rd_amp_min = OF_getpropint(rd->rd_node, "regulator-min-microamp", 0); rd->rd_amp_max = OF_getpropint(rd->rd_node, "regulator-max-microamp", ~0); KASSERT(rd->rd_amp_min <= rd->rd_amp_max); rd->rd_ramp_delay = OF_getpropint(rd->rd_node, "regulator-ramp-delay", 0); rd->rd_coupled = OF_getpropint(rd->rd_node, "regulator-coupled-with", 0); rd->rd_max_spread = OF_getpropint(rd->rd_node, "regulator-coupled-max-spread", 0); if (rd->rd_get_voltage && rd->rd_set_voltage) { uint32_t voltage = rd->rd_get_voltage(rd->rd_cookie); if (voltage < rd->rd_volt_min) rd->rd_set_voltage(rd->rd_cookie, rd->rd_volt_min); if (voltage > rd->rd_volt_max) rd->rd_set_voltage(rd->rd_cookie, rd->rd_volt_max); } if (rd->rd_get_current && rd->rd_set_current) { uint32_t current = rd->rd_get_current(rd->rd_cookie); if (current < rd->rd_amp_min) rd->rd_set_current(rd->rd_cookie, rd->rd_amp_min); if (current > rd->rd_amp_max) rd->rd_set_current(rd->rd_cookie, rd->rd_amp_max); } rd->rd_phandle = OF_getpropint(rd->rd_node, "phandle", 0); if (rd->rd_phandle == 0) return; LIST_INSERT_HEAD(®ulator_devices, rd, rd_list); if (rd->rd_get_voltage) { regulator_do_notify(rd->rd_phandle, regulator_get_voltage(rd->rd_phandle)); } if (rd->rd_get_current) { regulator_do_notify(rd->rd_phandle, regulator_get_current(rd->rd_phandle)); } } int regulator_type(int node) { char type[16] = { 0 }; OF_getprop(node, "regulator-type", type, sizeof(type)); if (strcmp(type, "current") == 0) return REGULATOR_CURRENT; return REGULATOR_VOLTAGE; } int regulator_fixed_set(int node, int enable) { uint32_t *gpio; uint32_t startup_delay; int len; char *prop = "gpio"; /* * This regulator may rely on another. That "parent" regulator * may be used by multiple other devices/regulators, so unless * we refcnt use of a regulator we can only turn it on. */ if (enable) regulator_enable(OF_getpropint(node, "vin-supply", 0)); pinctrl_byname(node, "default"); /* The "gpio"/"gpios" property is optional. */ len = OF_getproplen(node, prop); if (len < 0) { prop = "gpios"; len = OF_getproplen(node, prop); if (len < 0) return 0; } /* * We deliberately ignore the "enable-active-high" property * here. Its presence (or absence) is used to override the * polarity encoded by the GPIO flags in the device tree. But * supporting this behaviour is awkward since it would require * interpreting the GPIO flags here which would be a layer * violation since those flags may be driver-specific. In * practice the presence of "enable-active-high" is always * aligned with the polarity encoded by the GPIO flags and any * discrepancy is considered to be a bug by the Linux device * tree maintainers. */ gpio = malloc(len, M_TEMP, M_WAITOK); OF_getpropintarray(node, prop, gpio, len); gpio_controller_config_pin(gpio, GPIO_CONFIG_OUTPUT); if (enable) gpio_controller_set_pin(gpio, 1); else gpio_controller_set_pin(gpio, 0); free(gpio, M_TEMP, len); startup_delay = OF_getpropint(node, "startup-delay-us", 0); if (enable && startup_delay > 0) delay(startup_delay); return 0; } int regulator_set(uint32_t phandle, int enable) { struct regulator_device *rd; int node; if (phandle == 0) return ENODEV; node = OF_getnodebyphandle(phandle); if (node == 0) return ENODEV; /* Never turn off regulators that should always be on. */ if (OF_getproplen(node, "regulator-always-on") == 0 && !enable) return 0; LIST_FOREACH(rd, ®ulator_devices, rd_list) { if (rd->rd_phandle == phandle) break; } if (rd && rd->rd_enable) return rd->rd_enable(rd->rd_cookie, enable); if (OF_is_compatible(node, "regulator-fixed")) return regulator_fixed_set(node, enable); return ENODEV; } int regulator_enable(uint32_t phandle) { return regulator_set(phandle, 1); } int regulator_disable(uint32_t phandle) { return regulator_set(phandle, 0); } uint32_t regulator_get_voltage(uint32_t phandle) { struct regulator_device *rd; int node; if (phandle == 0) return 0; LIST_FOREACH(rd, ®ulator_devices, rd_list) { if (rd->rd_phandle == phandle) break; } if (rd && rd->rd_get_voltage) return rd->rd_get_voltage(rd->rd_cookie); node = OF_getnodebyphandle(phandle); if (node == 0) return 0; if (OF_is_compatible(node, "regulator-fixed")) return OF_getpropint(node, "regulator-min-microvolt", 0); if (OF_is_compatible(node, "regulator-gpio") && regulator_type(node) == REGULATOR_VOLTAGE) return regulator_gpio_get(node); return 0; } int regulator_set_voltage(uint32_t phandle, uint32_t voltage) { struct regulator_device *rd; uint32_t old, delta; int error, node; if (phandle == 0) return ENODEV; LIST_FOREACH(rd, ®ulator_devices, rd_list) { if (rd->rd_phandle == phandle) break; } /* Check limits. */ if (rd && (voltage < rd->rd_volt_min || voltage > rd->rd_volt_max)) return EINVAL; /* XXX Coupled regulators are unsupported for now. */ if (rd && rd->rd_coupled) return ENOTSUP; if (rd && rd->rd_set_voltage) { regulator_do_notify(rd->rd_phandle, voltage); old = rd->rd_get_voltage(rd->rd_cookie); error = rd->rd_set_voltage(rd->rd_cookie, voltage); if (voltage > old && rd->rd_ramp_delay > 0) { delta = voltage - old; delay(howmany(delta, rd->rd_ramp_delay)); } regulator_do_notify(rd->rd_phandle, voltage); return error; } node = OF_getnodebyphandle(phandle); if (node == 0) return ENODEV; if (OF_is_compatible(node, "regulator-fixed") && OF_getpropint(node, "regulator-min-microvolt", 0) == voltage) return 0; if (OF_is_compatible(node, "regulator-gpio") && regulator_type(node) == REGULATOR_VOLTAGE) return regulator_gpio_set(node, voltage); return ENODEV; } uint32_t regulator_get_current(uint32_t phandle) { struct regulator_device *rd; int node; if (phandle == 0) return 0; LIST_FOREACH(rd, ®ulator_devices, rd_list) { if (rd->rd_phandle == phandle) break; } if (rd && rd->rd_get_current) return rd->rd_get_current(rd->rd_cookie); node = OF_getnodebyphandle(phandle); if (node == 0) return 0; if (OF_is_compatible(node, "regulator-fixed")) return OF_getpropint(node, "regulator-min-microamp", 0); if (OF_is_compatible(node, "regulator-gpio") && regulator_type(node) == REGULATOR_CURRENT) return regulator_gpio_get(node); return 0; } int regulator_set_current(uint32_t phandle, uint32_t current) { struct regulator_device *rd; uint32_t old, delta; int error, node; if (phandle == 0) return ENODEV; LIST_FOREACH(rd, ®ulator_devices, rd_list) { if (rd->rd_phandle == phandle) break; } /* Check limits. */ if (rd && (current < rd->rd_amp_min || current > rd->rd_amp_max)) return EINVAL; if (rd && rd->rd_set_current) { regulator_do_notify(rd->rd_phandle, current); old = rd->rd_get_current(rd->rd_cookie); error = rd->rd_set_current(rd->rd_cookie, current); if (current > old && rd->rd_ramp_delay > 0) { delta = current - old; delay(howmany(delta, rd->rd_ramp_delay)); } regulator_do_notify(rd->rd_phandle, current); return error; } node = OF_getnodebyphandle(phandle); if (node == 0) return ENODEV; if (OF_is_compatible(node, "regulator-fixed") && OF_getpropint(node, "regulator-min-microamp", 0) == current) return 0; if (OF_is_compatible(node, "regulator-gpio") && regulator_type(node) == REGULATOR_CURRENT) return regulator_gpio_set(node, current); return ENODEV; } uint32_t regulator_gpio_get(int node) { uint32_t *gpio, *gpios, *states; uint32_t idx, value; int glen, slen, i; pinctrl_byname(node, "default"); if ((glen = OF_getproplen(node, "gpios")) <= 0) return EINVAL; if ((slen = OF_getproplen(node, "states")) <= 0) return EINVAL; if (slen % (2 * sizeof(uint32_t)) != 0) return EINVAL; gpios = malloc(glen, M_TEMP, M_WAITOK); states = malloc(slen, M_TEMP, M_WAITOK); OF_getpropintarray(node, "gpios", gpios, glen); OF_getpropintarray(node, "states", states, slen); i = 0; idx = 0; gpio = gpios; while (gpio && gpio < gpios + (glen / sizeof(uint32_t))) { if (gpio_controller_get_pin(gpio)) idx |= (1 << i); gpio = gpio_controller_next_pin(gpio); i++; } value = 0; for (i = 0; i < slen / (2 * sizeof(uint32_t)); i++) { if (states[2 * i + 1] == idx) { value = states[2 * i]; break; } } if (i >= slen / (2 * sizeof(uint32_t))) return 0; free(gpios, M_TEMP, glen); free(states, M_TEMP, slen); return value; } int regulator_gpio_set(int node, uint32_t value) { uint32_t phandle = OF_getpropint(node, "phandle", 0); uint32_t *gpio, *gpios, *states; uint32_t min, max; uint32_t idx; int glen, slen, i; pinctrl_byname(node, "default"); if (regulator_type(node) == REGULATOR_VOLTAGE) { min = OF_getpropint(node, "regulator-min-microvolt", 0); max = OF_getpropint(node, "regulator-max-microvolt", 0); } if (regulator_type(node) == REGULATOR_CURRENT) { min = OF_getpropint(node, "regulator-min-microamp", 0); max = OF_getpropint(node, "regulator-max-microamp", 0); } /* Check limits. */ if (value < min || value > max) return EINVAL; if ((glen = OF_getproplen(node, "gpios")) <= 0) return EINVAL; if ((slen = OF_getproplen(node, "states")) <= 0) return EINVAL; if (slen % (2 * sizeof(uint32_t)) != 0) return EINVAL; gpios = malloc(glen, M_TEMP, M_WAITOK); states = malloc(slen, M_TEMP, M_WAITOK); OF_getpropintarray(node, "gpios", gpios, glen); OF_getpropintarray(node, "states", states, slen); idx = 0; for (i = 0; i < slen / (2 * sizeof(uint32_t)); i++) { if (states[2 * i] < min || states[2 * i] > max) continue; if (states[2 * i] == value) { idx = states[2 * i + 1]; break; } } if (i >= slen / (2 * sizeof(uint32_t))) return EINVAL; regulator_do_notify(phandle, value); i = 0; gpio = gpios; while (gpio && gpio < gpios + (glen / sizeof(uint32_t))) { gpio_controller_set_pin(gpio, !!(idx & (1 << i))); gpio = gpio_controller_next_pin(gpio); i++; } regulator_do_notify(phandle, value); free(gpios, M_TEMP, glen); free(states, M_TEMP, slen); return 0; } void regulator_notify(struct regulator_notifier *rn) { LIST_INSERT_HEAD(®ulator_notifiers, rn, rn_list); } void regulator_do_notify(uint32_t phandle, uint32_t value) { struct regulator_notifier *rn; LIST_FOREACH(rn, ®ulator_notifiers, rn_list) { if (rn->rn_phandle == phandle) rn->rn_notify(rn->rn_cookie, value); } }