Commit 4f1d3079 authored by Tom Rini's avatar Tom Rini

Merge branch '2020-10-28-mux-driver-framework'

- Add a framework for mux drivers
parents 39dd7c1d 15995ac3
......@@ -196,7 +196,7 @@ config SYS_MALLOC_F_LEN
hex "Size of malloc() pool before relocation"
depends on SYS_MALLOC_F
default 0x1000 if AM33XX
default 0x2800 if SANDBOX
default 0x4000 if SANDBOX
default 0x2000 if (ARCH_IMX8 || ARCH_IMX8M || ARCH_MX7 || \
ARCH_MX7ULP || ARCH_MX6 || ARCH_MX5 || \
ARCH_LS1012A || ARCH_LS1021A || ARCH_LS1043A || \
......
......@@ -3,6 +3,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/gpio/sandbox-gpio.h>
#include <dt-bindings/pinctrl/sandbox-pinmux.h>
#include <dt-bindings/mux/mux.h>
/ {
model = "sandbox";
......@@ -133,6 +134,12 @@
interrupts-extended = <&irq 3 0>;
acpi,name = "GHIJ";
phandle-value = <&gpio_c 10>, <0xFFFFFFFF 20>, <&gpio_a 30>;
mux-controls = <&muxcontroller0 0>, <&muxcontroller0 1>,
<&muxcontroller0 2>, <&muxcontroller0 3>,
<&muxcontroller1>;
mux-control-names = "mux0", "mux1", "mux2", "mux3", "mux4";
mux-syscon = <&syscon3>;
};
junk {
......@@ -170,6 +177,9 @@
compatible = "denx,u-boot-fdt-test";
ping-expect = <3>;
ping-add = <3>;
mux-controls = <&muxcontroller0 0>;
mux-control-names = "mux0";
};
phy_provider0: gen_phy@0 {
......@@ -884,6 +894,29 @@
0x58 8>;
};
syscon3: syscon@3 {
compatible = "simple-mfd", "syscon";
reg = <0x000100 0x10>;
muxcontroller0: a-mux-controller {
compatible = "mmio-mux";
#mux-control-cells = <1>;
mux-reg-masks = <0x0 0x30>, /* 0: reg 0x0, bits 5:4 */
<0xc 0x1E>, /* 1: reg 0xc, bits 4:1 */
<0x4 0xFF>; /* 2: reg 0x4, bits 7:0 */
idle-states = <MUX_IDLE_AS_IS>, <0x02>, <0x73>;
u-boot,mux-autoprobe;
};
};
muxcontroller1: emul-mux-controller {
compatible = "mux-emul";
#mux-control-cells = <0>;
u-boot,mux-autoprobe;
idle-state = <0xabcd>;
};
timer@0 {
compatible = "sandbox,timer";
clock-frequency = <1000000>;
......
......@@ -1081,6 +1081,12 @@ config CMD_MTD
help
MTD commands support.
config CMD_MUX
bool "mux"
depends on MULTIPLEXER
help
List, select, and deselect mux controllers on the fly.
config CMD_NAND
bool "nand"
default y if NAND_SUNXI
......
......@@ -105,6 +105,7 @@ obj-$(CONFIG_CMD_CLONE) += clone.o
ifneq ($(CONFIG_CMD_NAND)$(CONFIG_CMD_SF),)
obj-y += legacy-mtd-utils.o
endif
obj-$(CONFIG_CMD_MUX) += mux.o
obj-$(CONFIG_CMD_NAND) += nand.o
obj-$(CONFIG_CMD_NET) += net.o
obj-$(CONFIG_CMD_NVEDIT_EFI) += nvedit_efi.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* List, select, and deselect mux controllers on the fly.
*
* Copyright (c) 2020 Texas Instruments Inc.
* Author: Pratyush Yadav <p.yadav@ti.com>
*/
#include <common.h>
#include <command.h>
#include <errno.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <mux.h>
#include <mux-internal.h>
#include <linux/err.h>
#include <dt-bindings/mux/mux.h>
#define COLUMN_SIZE 16
/*
* Print a member of a column. The total size of the text printed, including
* trailing whitespace, will always be COLUMN_SIZE.
*/
#define PRINT_COLUMN(fmt, args...) do { \
char buf[COLUMN_SIZE + 1]; \
snprintf(buf, COLUMN_SIZE + 1, fmt, ##args); \
printf("%-*s", COLUMN_SIZE, buf); \
} while (0)
/*
* Find a mux based on its device name in argv[1] and index in the chip in
* argv[2].
*/
static struct mux_control *cmd_mux_find(char *const argv[])
{
struct udevice *dev;
struct mux_chip *chip;
int ret;
unsigned long id;
ret = strict_strtoul(argv[2], 10, &id);
if (ret)
return ERR_PTR(ret);
ret = uclass_get_device_by_name(UCLASS_MUX, argv[1], &dev);
if (ret)
return ERR_PTR(ret);
chip = dev_get_uclass_priv(dev);
if (!chip)
return ERR_PTR(ret);
if (id >= chip->controllers)
return ERR_PTR(-EINVAL);
return &chip->mux[id];
}
/*
* Print the details of a mux. The columns printed correspond to: "Selected",
* "Current State", "Idle State", and "Num States".
*/
static void print_mux(struct mux_control *mux)
{
PRINT_COLUMN("%s", mux->in_use ? "yes" : "no");
if (mux->cached_state == MUX_IDLE_AS_IS)
PRINT_COLUMN("%s", "unknown");
else
PRINT_COLUMN("0x%x", mux->cached_state);
if (mux->idle_state == MUX_IDLE_AS_IS)
PRINT_COLUMN("%s", "as-is");
else if (mux->idle_state == MUX_IDLE_DISCONNECT)
PRINT_COLUMN("%s", "disconnect");
else
PRINT_COLUMN("0x%x", mux->idle_state);
PRINT_COLUMN("0x%x", mux->states);
printf("\n");
}
static int do_mux_list(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct udevice *dev;
struct mux_chip *chip;
int j;
for (uclass_first_device(UCLASS_MUX, &dev);
dev;
uclass_next_device(&dev)) {
chip = dev_get_uclass_priv(dev);
if (!chip) {
dev_err(dev, "can't find mux chip\n");
continue;
}
printf("%s:\n", dev->name);
printf(" ");
PRINT_COLUMN("ID");
PRINT_COLUMN("Selected");
PRINT_COLUMN("Current State");
PRINT_COLUMN("Idle State");
PRINT_COLUMN("Num States");
printf("\n");
for (j = 0; j < chip->controllers; j++) {
printf(" ");
PRINT_COLUMN("%d", j);
print_mux(&chip->mux[j]);
}
printf("\n");
}
return 0;
}
static int do_mux_select(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct mux_control *mux;
int ret;
unsigned long state;
if (argc != 4)
return CMD_RET_USAGE;
mux = cmd_mux_find(argv);
if (IS_ERR_OR_NULL(mux)) {
printf("Failed to find the specified mux\n");
return CMD_RET_FAILURE;
}
ret = strict_strtoul(argv[3], 16, &state);
if (ret) {
printf("Invalid state\n");
return CMD_RET_FAILURE;
}
ret = mux_control_select(mux, state);
if (ret) {
printf("Failed to select requested state\n");
return CMD_RET_FAILURE;
}
return CMD_RET_SUCCESS;
}
static int do_mux_deselect(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct mux_control *mux;
int ret;
if (argc != 3)
return CMD_RET_USAGE;
mux = cmd_mux_find(argv);
if (IS_ERR_OR_NULL(mux)) {
printf("Failed to find the specified mux\n");
return CMD_RET_FAILURE;
}
ret = mux_control_deselect(mux);
if (ret) {
printf("Failed to deselect mux\n");
return CMD_RET_FAILURE;
}
return CMD_RET_SUCCESS;
}
static char mux_help_text[] =
"list - List all Muxes and their states\n"
"select <chip> <id> <state> - Select the given mux state\n"
"deselect <chip> <id> - Deselect the given mux and reset it to its idle state";
U_BOOT_CMD_WITH_SUBCMDS(mux, "List, select, and deselect muxes", mux_help_text,
U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mux_list),
U_BOOT_SUBCMD_MKENT(select, 4, 0, do_mux_select),
U_BOOT_SUBCMD_MKENT(deselect, 3, 0, do_mux_deselect));
......@@ -46,6 +46,7 @@
#include <miiphy.h>
#endif
#include <mmc.h>
#include <mux.h>
#include <nand.h>
#include <of_live.h>
#include <onenand_uboot.h>
......@@ -341,6 +342,17 @@ static int initr_dm_devices(void)
return ret;
}
if (IS_ENABLED(CONFIG_MULTIPLEXER)) {
/*
* Initialize the multiplexer controls to their default state.
* This must be done early as other drivers may unknowingly
* rely on it.
*/
ret = dm_mux_init();
if (ret)
return ret;
}
return 0;
}
......
......@@ -55,6 +55,7 @@ CONFIG_CMD_GPT_RENAME=y
CONFIG_CMD_IDE=y
CONFIG_CMD_I2C=y
CONFIG_CMD_LSBLK=y
CONFIG_CMD_MUX=y
CONFIG_CMD_OSD=y
CONFIG_CMD_PCI=y
CONFIG_CMD_READ=y
......@@ -179,6 +180,8 @@ CONFIG_SPI_FLASH_SPANSION=y
CONFIG_SPI_FLASH_STMICRO=y
CONFIG_SPI_FLASH_SST=y
CONFIG_SPI_FLASH_WINBOND=y
CONFIG_MULTIPLEXER=y
CONFIG_MUX_MMIO=y
CONFIG_DM_ETH=y
CONFIG_NVME=y
CONFIG_PCI=y
......
......@@ -64,6 +64,8 @@ source "drivers/mmc/Kconfig"
source "drivers/mtd/Kconfig"
source "drivers/mux/Kconfig"
source "drivers/net/Kconfig"
source "drivers/nvme/Kconfig"
......
......@@ -14,6 +14,7 @@ obj-$(CONFIG_$(SPL_TPL_)INPUT) += input/
obj-$(CONFIG_$(SPL_TPL_)LED) += led/
obj-$(CONFIG_$(SPL_TPL_)MMC_SUPPORT) += mmc/
obj-y += mtd/
obj-$(CONFIG_$(SPL_)MULTIPLEXER) += mux/
obj-$(CONFIG_$(SPL_TPL_)PCH_SUPPORT) += pch/
obj-$(CONFIG_$(SPL_TPL_)PCI) += pci/
obj-$(CONFIG_$(SPL_TPL_)PHY) += phy/
......
menu "Multiplexer drivers"
config MULTIPLEXER
bool "Multiplexer Support"
depends on DM
help
The mux framework is a minimalistic subsystem that handles multiplexer
controllers. It provides the same API as Linux and mux drivers should
be portable with a minimum effort.
if MULTIPLEXER
config MUX_MMIO
bool "MMIO register bitfield-controlled Multiplexer"
depends on MULTIPLEXER && SYSCON
help
MMIO register bitfield-controlled Multiplexer controller.
The driver builds multiplexer controllers for bitfields in a syscon
register. For N bit wide bitfields, there will be 2^N possible
multiplexer states.
endif
endmenu
# SPDX-License-Identifier: GPL-2.0+
#
# (C) Copyright 2019
# Jean-Jacques Hiblot <jjhiblot@ti.com>
obj-$(CONFIG_$(SPL_)MULTIPLEXER) += mux-uclass.o
obj-$(CONFIG_$(SPL_)MUX_MMIO) += mmio.o
// SPDX-License-Identifier: GPL-2.0
/*
* MMIO register bitfield-controlled multiplexer driver
* Based on the linux mmio multiplexer driver
*
* Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de>
* Copyright (C) 2019 Texas Instrument, Jean-jacques Hiblot <jjhiblot@ti.com>
*/
#include <common.h>
#include <dm.h>
#include <mux-internal.h>
#include <regmap.h>
#include <syscon.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <dm/read.h>
#include <dm/devres.h>
#include <dt-bindings/mux/mux.h>
#include <linux/bitops.h>
static int mux_mmio_set(struct mux_control *mux, int state)
{
struct regmap_field **fields = dev_get_priv(mux->dev);
return regmap_field_write(fields[mux_control_get_index(mux)], state);
}
static const struct mux_control_ops mux_mmio_ops = {
.set = mux_mmio_set,
};
static const struct udevice_id mmio_mux_of_match[] = {
{ .compatible = "mmio-mux" },
{ /* sentinel */ },
};
static int mmio_mux_probe(struct udevice *dev)
{
struct regmap_field **fields;
struct mux_chip *mux_chip = dev_get_uclass_priv(dev);
struct regmap *regmap;
u32 *mux_reg_masks;
u32 *idle_states;
int num_fields;
int ret;
int i;
regmap = syscon_node_to_regmap(dev_ofnode(dev->parent));
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
dev_err(dev, "failed to get regmap: %d\n", ret);
return ret;
}
num_fields = dev_read_size(dev, "mux-reg-masks");
if (num_fields < 0)
return log_msg_ret("mux-reg-masks missing", -EINVAL);
num_fields /= sizeof(u32);
if (num_fields == 0 || num_fields % 2)
ret = -EINVAL;
num_fields = num_fields / 2;
ret = mux_alloc_controllers(dev, num_fields);
if (ret < 0)
return log_msg_ret("mux_alloc_controllers", ret);
fields = devm_kmalloc(dev, num_fields * sizeof(*fields), __GFP_ZERO);
if (!fields)
return -ENOMEM;
dev->priv = fields;
mux_reg_masks = devm_kmalloc(dev, num_fields * 2 * sizeof(u32),
__GFP_ZERO);
if (!mux_reg_masks)
return -ENOMEM;
ret = dev_read_u32_array(dev, "mux-reg-masks", mux_reg_masks,
num_fields * 2);
if (ret < 0)
return log_msg_ret("mux-reg-masks read", ret);
idle_states = devm_kmalloc(dev, num_fields * sizeof(u32), __GFP_ZERO);
if (!idle_states)
return -ENOMEM;
ret = dev_read_u32_array(dev, "idle-states", idle_states, num_fields);
if (ret < 0) {
log_err("idle-states");
devm_kfree(dev, idle_states);
idle_states = NULL;
}
for (i = 0; i < num_fields; i++) {
struct mux_control *mux = &mux_chip->mux[i];
struct reg_field field;
u32 reg, mask;
int bits;
reg = mux_reg_masks[2 * i];
mask = mux_reg_masks[2 * i + 1];
field.reg = reg;
field.msb = fls(mask) - 1;
field.lsb = ffs(mask) - 1;
if (mask != GENMASK(field.msb, field.lsb))
return log_msg_ret("invalid mask", -EINVAL);
fields[i] = devm_regmap_field_alloc(dev, regmap, field);
if (IS_ERR(fields[i])) {
ret = PTR_ERR(fields[i]);
return log_msg_ret("regmap_field_alloc", ret);
}
bits = 1 + field.msb - field.lsb;
mux->states = 1 << bits;
if (!idle_states)
continue;
if (idle_states[i] != MUX_IDLE_AS_IS &&
idle_states[i] >= mux->states)
return log_msg_ret("idle-states range", -EINVAL);
mux->idle_state = idle_states[i];
}
devm_kfree(dev, mux_reg_masks);
if (idle_states)
devm_kfree(dev, idle_states);
return 0;
}
U_BOOT_DRIVER(mmio_mux) = {
.name = "mmio-mux",
.id = UCLASS_MUX,
.of_match = mmio_mux_of_match,
.probe = mmio_mux_probe,
.ops = &mux_mmio_ops,
};
// SPDX-License-Identifier: GPL-2.0
/*
* Multiplexer subsystem
*
* Based on the linux multiplexer framework
*
* Copyright (C) 2017 Axentia Technologies AB
* Author: Peter Rosin <peda@axentia.se>
*
* Copyright (C) 2017-2018 Texas Instruments Incorporated - http://www.ti.com/
* Jean-Jacques Hiblot <jjhiblot@ti.com>
*/
#include <common.h>
#include <dm.h>
#include <mux-internal.h>
#include <dm/device-internal.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <dt-bindings/mux/mux.h>
#include <linux/bug.h>
/*
* The idle-as-is "state" is not an actual state that may be selected, it
* only implies that the state should not be changed. So, use that state
* as indication that the cached state of the multiplexer is unknown.
*/
#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
/**
* mux_control_ops() - Get the mux_control ops.
* @dev: The client device.
*
* Return: A pointer to the 'mux_control_ops' of the device.
*/
static inline const struct mux_control_ops *mux_dev_ops(struct udevice *dev)
{
return (const struct mux_control_ops *)dev->driver->ops;
}
/**
* mux_control_set() - Set the state of the given mux controller.
* @mux: A multiplexer control
* @state: The new requested state.
*
* Return: 0 if OK, or a negative error code.
*/
static int mux_control_set(struct mux_control *mux, int state)
{
int ret = mux_dev_ops(mux->dev)->set(mux, state);
mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
return ret;
}
unsigned int mux_control_states(struct mux_control *mux)
{
return mux->states;
}
/**
* __mux_control_select() - Select the given multiplexer state.
* @mux: The mux-control to request a change of state from.
* @state: The new requested state.
*
* Try to set the mux to the requested state. If not, try to revert if
* appropriate.
*/
static int __mux_control_select(struct mux_control *mux, int state)
{
int ret;
if (WARN_ON(state < 0 || state >= mux->states))
return -EINVAL;
if (mux->cached_state == state)
return 0;
ret = mux_control_set(mux, state);
if (ret >= 0)
return 0;
/* The mux update failed, try to revert if appropriate... */
if (mux->idle_state != MUX_IDLE_AS_IS)
mux_control_set(mux, mux->idle_state);
return ret;
}
int mux_control_select(struct mux_control *mux, unsigned int state)
{
int ret;
if (mux->in_use)
return -EBUSY;
ret = __mux_control_select(mux, state);
if (ret < 0)
return ret;
mux->in_use = true;
return 0;
}
int mux_control_deselect(struct mux_control *mux)
{
int ret = 0;
if (mux->idle_state != MUX_IDLE_AS_IS &&
mux->idle_state != mux->cached_state)
ret = mux_control_set(mux, mux->idle_state);
mux->in_use = false;
return ret;