#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/fdt/fdt_pinctrl.h>
#include "qcom_tlmm_var.h"
#include "qcom_tlmm_debug.h"
#include "qcom_tlmm_ipq4018_hw.h"
#include "gpio_if.h"
static const struct qcom_tlmm_prop_name prop_names[] = {
{ "bias-disable", PIN_ID_BIAS_DISABLE, 0 },
{ "bias-high-impedance", PIN_ID_BIAS_HIGH_IMPEDANCE, 0 },
{ "bias-bus-hold", PIN_ID_BIAS_BUS_HOLD, 0 },
{ "bias-pull-up", PIN_ID_BIAS_PULL_UP, 0 },
{ "bias-pull-down", PIN_ID_BIAS_PULL_DOWN, 0 },
{ "bias-pull-pin-default", PIN_ID_BIAS_PULL_PIN_DEFAULT, 0 },
{ "drive-push-pull", PIN_ID_DRIVE_PUSH_PULL, 0 },
{ "drive-open-drain", PIN_ID_DRIVE_OPEN_DRAIN, 0 },
{ "drive-open-source", PIN_ID_DRIVE_OPEN_SOURCE, 0 },
{ "drive-strength", PIN_ID_DRIVE_STRENGTH, 1 },
{ "input-enable", PIN_ID_INPUT_ENABLE, 0 },
{ "input-disable", PIN_ID_INPUT_DISABLE, 0 },
{ "input-schmitt-enable", PIN_ID_INPUT_SCHMITT_ENABLE, 0 },
{ "input-schmitt-disable", PIN_ID_INPUT_SCHMITT_DISABLE, 0 },
{ "input-debounce", PIN_ID_INPUT_DEBOUNCE, 0 },
{ "power-source", PIN_ID_POWER_SOURCE, 0 },
{ "slew-rate", PIN_ID_SLEW_RATE, 0},
{ "low-power-enable", PIN_ID_LOW_POWER_MODE_ENABLE, 0 },
{ "low-power-disable", PIN_ID_LOW_POWER_MODE_DISABLE, 0 },
{ "output-low", PIN_ID_OUTPUT_LOW, 0, },
{ "output-high", PIN_ID_OUTPUT_HIGH, 0, },
{ "vm-enable", PIN_ID_VM_ENABLE, 0, },
{ "vm-disable", PIN_ID_VM_DISABLE, 0, },
};
static const struct qcom_tlmm_spec_pin *
qcom_tlmm_pinctrl_search_spin(struct qcom_tlmm_softc *sc, char *pin_name)
{
int i;
if (sc->spec_pins == NULL)
return (NULL);
for (i = 0; sc->spec_pins[i].name != NULL; i++) {
if (strcmp(pin_name, sc->spec_pins[i].name) == 0)
return (&sc->spec_pins[i]);
}
return (NULL);
}
static int
qcom_tlmm_pinctrl_config_spin(struct qcom_tlmm_softc *sc,
char *pin_name, const struct qcom_tlmm_spec_pin *spin,
struct qcom_tlmm_pinctrl_cfg *cfg)
{
device_printf(sc->dev, "%s: TODO: called; pin_name=%s\n",
__func__, pin_name);
return (0);
}
static const struct qcom_tlmm_gpio_mux *
qcom_tlmm_pinctrl_search_gmux(struct qcom_tlmm_softc *sc, char *pin_name)
{
int i;
if (sc->gpio_muxes == NULL)
return (NULL);
for (i = 0; sc->gpio_muxes[i].id >= 0; i++) {
if (strcmp(pin_name, sc->gpio_muxes[i].name) == 0)
return (&sc->gpio_muxes[i]);
}
return (NULL);
}
static int
qcom_tlmm_pinctrl_gmux_function(const struct qcom_tlmm_gpio_mux *gmux,
char *fnc_name)
{
int i;
for (i = 0; i < 16; i++) {
if ((gmux->functions[i] != NULL) &&
(strcmp(fnc_name, gmux->functions[i]) == 0))
return (i);
}
return (-1);
}
static int
qcom_tlmm_pinctrl_read_node(struct qcom_tlmm_softc *sc,
phandle_t node, struct qcom_tlmm_pinctrl_cfg *cfg, char **pins,
int *lpins)
{
int rv, i;
*lpins = OF_getprop_alloc(node, "pins", (void **)pins);
if (*lpins <= 0)
return (ENOENT);
rv = OF_getprop_alloc(node, "function", (void **)&cfg->function);
if (rv <= 0)
cfg->function = NULL;
for (i = 0; i < PROP_ID_MAX_ID; i++) {
rv = OF_getencprop(node, prop_names[i].name, &cfg->params[i],
sizeof(cfg->params[i]));
if (prop_names[i].have_value) {
if (rv == 0) {
device_printf(sc->dev,
"WARNING: Missing value for propety"
" \"%s\"\n",
prop_names[i].name);
cfg->params[i] = 0;
}
} else {
cfg->params[i] = 0;
}
if (rv < 0)
cfg->params[i] = -1;
}
return (0);
}
static int
qcom_tlmm_pinctrl_config_gmux(struct qcom_tlmm_softc *sc, char *pin_name,
const struct qcom_tlmm_gpio_mux *gmux, struct qcom_tlmm_pinctrl_cfg *cfg)
{
int err = 0, i;
QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
"%s: called; pin=%s, function %s\n",
__func__, pin_name, cfg->function);
GPIO_LOCK(sc);
if (cfg->function != NULL) {
uint32_t tmp;
tmp = qcom_tlmm_pinctrl_gmux_function(gmux, cfg->function);
if (tmp == -1) {
device_printf(sc->dev,
"%s: pin=%s, function=%s, unknown!\n",
__func__,
pin_name,
cfg->function);
err = EINVAL;
goto done;
}
QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
"%s: pin id=%u, new function=%u\n",
__func__,
gmux->id,
tmp);
err = sc->sc_hw->qcom_tlmm_hw_pin_set_function(sc, gmux->id,
tmp);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set function (%d)\n",
__func__, gmux->id, err);
goto done;
}
}
for (i = 0; i < PROP_ID_MAX_ID; i++) {
if (cfg->params[i] == -1)
continue;
QCOM_TLMM_DPRINTF(sc, QCOM_TLMM_DEBUG_PINMUX,
"%s: pin_id=%u, param=%d, val=%d\n",
__func__,
gmux->id,
i,
cfg->params[i]);
switch (i) {
case PIN_ID_BIAS_DISABLE:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_pupd_config(sc,
gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set pupd(DISABLE):"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_BIAS_PULL_DOWN:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_pupd_config(sc,
gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set pupd(PD):"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_BIAS_BUS_HOLD:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_pupd_config(sc,
gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set pupd(HOLD):"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_BIAS_PULL_UP:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_pupd_config(sc,
gmux->id, QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set pupd(PU):"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_OUTPUT_LOW:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_oe_output(sc,
gmux->id);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set OE:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
err = sc->sc_hw->qcom_tlmm_hw_pin_set_output_value(
sc, gmux->id, 0);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set output value:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_OUTPUT_HIGH:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_oe_output(sc,
gmux->id);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set OE:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
err = sc->sc_hw->qcom_tlmm_hw_pin_set_output_value(
sc, gmux->id, 1);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set output value:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_DRIVE_STRENGTH:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_drive_strength(sc,
gmux->id, cfg->params[i]);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set drive"
" strength %d (%d)\n",
__func__, gmux->id,
cfg->params[i], err);
goto done;
}
break;
case PIN_ID_VM_ENABLE:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_vm(sc,
gmux->id, true);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set VM enable:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_VM_DISABLE:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_vm(sc,
gmux->id, false);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set VM disable:"
" %d\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_DRIVE_OPEN_DRAIN:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_open_drain(sc,
gmux->id, true);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set open drain"
" (%d)\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_INPUT_ENABLE:
err = sc->sc_hw->qcom_tlmm_hw_pin_set_oe_input(sc,
gmux->id);
if (err != 0) {
device_printf(sc->dev,
"%s: pin=%d: failed to set pin as input"
" (%d)\n",
__func__, gmux->id, err);
goto done;
}
break;
case PIN_ID_INPUT_DISABLE:
err = ENXIO;
goto done;
break;
case PIN_ID_BIAS_HIGH_IMPEDANCE:
case PIN_ID_INPUT_SCHMITT_ENABLE:
case PIN_ID_INPUT_SCHMITT_DISABLE:
case PIN_ID_INPUT_DEBOUNCE:
case PIN_ID_SLEW_RATE:
case PIN_ID_LOW_POWER_MODE_ENABLE:
case PIN_ID_LOW_POWER_MODE_DISABLE:
case PIN_ID_BIAS_PULL_PIN_DEFAULT:
case PIN_ID_DRIVE_PUSH_PULL:
case PIN_ID_DRIVE_OPEN_SOURCE:
case PIN_ID_POWER_SOURCE:
default:
device_printf(sc->dev,
"%s: ERROR: unknown/unsupported param: "
" pin_id=%u, param=%d, val=%d\n",
__func__,
gmux->id,
i,
cfg->params[i]);
err = ENXIO;
goto done;
}
}
done:
GPIO_UNLOCK(sc);
return (0);
}
static int
qcom_tlmm_pinctrl_config_node(struct qcom_tlmm_softc *sc,
char *pin_name, struct qcom_tlmm_pinctrl_cfg *cfg)
{
const struct qcom_tlmm_gpio_mux *gmux;
const struct qcom_tlmm_spec_pin *spin;
int rv;
gmux = qcom_tlmm_pinctrl_search_gmux(sc, pin_name);
if (gmux != NULL) {
rv = qcom_tlmm_pinctrl_config_gmux(sc, pin_name, gmux, cfg);
return (rv);
}
spin = qcom_tlmm_pinctrl_search_spin(sc, pin_name);
if (spin != NULL) {
rv = qcom_tlmm_pinctrl_config_spin(sc, pin_name, spin, cfg);
return (rv);
}
device_printf(sc->dev, "Unknown pin: %s\n", pin_name);
return (ENXIO);
}
static int
qcom_tlmm_pinctrl_process_node(struct qcom_tlmm_softc *sc,
phandle_t node)
{
struct qcom_tlmm_pinctrl_cfg cfg;
char *pins, *pname;
int i, len, lpins, rv;
rv = qcom_tlmm_pinctrl_read_node(sc, node, &cfg, &pins, &lpins);
if (rv != 0)
return (rv);
len = 0;
pname = pins;
do {
i = strlen(pname) + 1;
rv = qcom_tlmm_pinctrl_config_node(sc, pname, &cfg);
if (rv != 0)
device_printf(sc->dev,
"Cannot configure pin: %s: %d\n", pname, rv);
len += i;
pname += i;
} while (len < lpins);
if (pins != NULL)
free(pins, M_OFWPROP);
if (cfg.function != NULL)
free(cfg.function, M_OFWPROP);
return (rv);
}
int
qcom_tlmm_pinctrl_configure(device_t dev, phandle_t cfgxref)
{
struct qcom_tlmm_softc *sc;
phandle_t node, cfgnode;
int rv;
sc = device_get_softc(dev);
cfgnode = OF_node_from_xref(cfgxref);
for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) {
if (!ofw_bus_node_status_okay(node))
continue;
rv = qcom_tlmm_pinctrl_process_node(sc, node);
if (rv != 0)
device_printf(dev, "Pin config failed: %d\n", rv);
}
return (0);
}