[PATCH v2 4/4] mux: lan966: Add support for flexcom mux controller
Kavyasree.Kotagiri at microchip.com
Kavyasree.Kotagiri at microchip.com
Wed May 11 07:28:12 PDT 2022
> 2022-05-10 at 16:59, Kavyasree.Kotagiri at microchip.com wrote:
> >>> LAN966 SoC have 5 flexcoms. Each flexcom has 2 chip-selects.
> >>> For each chip select of each flexcom there is a configuration
> >>> register FLEXCOM_SHARED[0-4]:SS_MASK[0-1]. The width of
> >>> configuration register is 21 because there are 21 shared pins
> >>> on each of which the chip select can be mapped. Each bit of the
> >>> register represents a different FLEXCOM_SHARED pin.
> >>>
> >>> Signed-off-by: Kavyasree Kotagiri <kavyasree.kotagiri at microchip.com>
> >>> ---
> >>> arch/arm/mach-at91/Kconfig | 2 +
> >>> drivers/mfd/atmel-flexcom.c | 55 +++++++++++++++-
> >>> drivers/mux/Kconfig | 12 ++++
> >>> drivers/mux/Makefile | 2 +
> >>> drivers/mux/lan966-flx.c | 121
> >> ++++++++++++++++++++++++++++++++++++
> >>> 5 files changed, 191 insertions(+), 1 deletion(-)
> >>> create mode 100644 drivers/mux/lan966-flx.c
> >>>
> >>> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> >>> index 279810381256..26fb0f4e1b79 100644
> >>> --- a/arch/arm/mach-at91/Kconfig
> >>> +++ b/arch/arm/mach-at91/Kconfig
> >>> @@ -74,6 +74,8 @@ config SOC_LAN966
> >>> select DW_APB_TIMER_OF
> >>> select ARM_GIC
> >>> select MEMORY
> >>> + select MULTIPLEXER
> >>> + select MUX_LAN966
> >>> help
> >>> This enables support for ARMv7 based Microchip LAN966 SoC
> family.
> >>>
> >>> diff --git a/drivers/mfd/atmel-flexcom.c b/drivers/mfd/atmel-flexcom.c
> >>> index 559eb4d352b6..7cfd0fc3f4f0 100644
> >>> --- a/drivers/mfd/atmel-flexcom.c
> >>> +++ b/drivers/mfd/atmel-flexcom.c
> >>> @@ -17,6 +17,7 @@
> >>> #include <linux/io.h>
> >>> #include <linux/clk.h>
> >>> #include <dt-bindings/mfd/atmel-flexcom.h>
> >>> +#include <linux/mux/consumer.h>
> >>>
> >>> /* I/O register offsets */
> >>> #define FLEX_MR 0x0 /* Mode Register */
> >>> @@ -28,6 +29,10 @@
> >>> #define FLEX_MR_OPMODE(opmode) (((opmode) <<
> >> FLEX_MR_OPMODE_OFFSET) & \
> >>> FLEX_MR_OPMODE_MASK)
> >>>
> >>> +struct atmel_flex_caps {
> >>> + bool has_flx_mux;
> >>> +};
> >>> +
> >>> struct atmel_flexcom {
> >>> void __iomem *base;
> >>> u32 opmode;
> >>> @@ -37,6 +42,7 @@ struct atmel_flexcom {
> >>> static int atmel_flexcom_probe(struct platform_device *pdev)
> >>> {
> >>> struct device_node *np = pdev->dev.of_node;
> >>> + const struct atmel_flex_caps *caps;
> >>> struct resource *res;
> >>> struct atmel_flexcom *ddata;
> >>> int err;
> >>> @@ -76,13 +82,60 @@ static int atmel_flexcom_probe(struct
> >> platform_device *pdev)
> >>> */
> >>> writel(FLEX_MR_OPMODE(ddata->opmode), ddata->base +
> FLEX_MR);
> >>>
> >>> + caps = of_device_get_match_data(&pdev->dev);
> >>> + if (!caps) {
> >>> + dev_err(&pdev->dev, "Could not retrieve flexcom caps\n");
> >>> + return -EINVAL;
> >>> + }
> >>> +
> >>> + /* Flexcom Mux */
> >>> + if (caps->has_flx_mux && of_property_read_bool(np, "mux-
> controls"))
> >> {
> >>> + struct mux_control *flx_mux;
> >>> + struct of_phandle_args args;
> >>> + int i, count;
> >>> +
> >>> + flx_mux = devm_mux_control_get(&pdev->dev, NULL);
> >>> + if (IS_ERR(flx_mux))
> >>> + return PTR_ERR(flx_mux);
> >>> +
> >>> + count = of_property_count_strings(np, "mux-control-names");
> >>> + for (i = 0; i < count; i++) {
> >>> + err = of_parse_phandle_with_fixed_args(np, "mux-
> controls",
> >> 1, i, &args);
> >>> + if (err)
> >>> + break;
> >>> +
> >>> + err = mux_control_select(flx_mux, args.args[0]);
> >>> + if (!err) {
> >>> + mux_control_deselect(flx_mux);
> >>
> >> This is suspect. When you deselect the mux you rely on the mux to be
> >> configured with "as-is" as the idle state. But you do not document that.
> >> This is also fragile in that you silently rely on noone else selecting
> >> the mux to some unwanted state behind your back (mux controls are not
> >> exclusive the way e.g. gpio pins or pwms are). The protocol is that
> >> others may get a ref to the mux control and select it as long as noone
> >> else has selected it.
> >>
> >> The only sane thing to do is to keep the mux selected for the whole
> >> duration when you rely on it to be in your desired state.
> >>
> >
> > Yes, mux is kept selected until configuring register is done. Please see
> below log where
> > I added debug prints just for understanding:
> > # dmesg | grep KK
> > [ 0.779827] KK: Func: atmel_flexcom_probe ***** [START flx muxing]
> ********
> > [ 0.779875] KK: Func: atmel_flexcom_probe i = 0 args[0] = 0
> > [ 0.779890] KK: Func: mux_control_select [Entered]
> > [ 0.779902] KK: Func: mux_lan966x_set [Entered] state = 0
> > [ 0.779977] KK: Func: mux_lan966x_set [Read] = 0x1fffef <<<----- setting
> mux_lan966x[state].ss_pin "4" which is passed from dts
> > [ 0.779992] KK: Func: mux_lan966x_set [Exit]
> > [ 0.780002] KK: Func: mux_control_select [Exit]
> > [ 0.780011] KK: Func: mux_control_deselect [Entered]
> > [ 0.780021] KK: Func: mux_control_deselect [Exit]
>
> You misunderstand. The mux control is only "selected" between the call
> to mux_control_select() and the following call to
> mux_control_deselect().
>
> After that, the mux control is "idle". However, in your case the
> "idle-state" is configured to "as-is" (which is the default), so the
> mux control (naturally) remains in the previously selected state while
> idle. But you are not documenting that limitation, and with this
> implementation you *must* have a mux control that uses "as-is" as its
> idle state. But that is an unexpected and broken limitation, and a
> much better solution is to simply keep the mux control "selected" for
> the complete duration when you rely on it to be in whatever state you
> need it to be in.
>
I am new to mux drivers.
Let me try to explain why I have mux_control_deselect if there is no err from mux_control_select()
For example,
1. When I have one only chip-select CS0 for flexcom3 to be mapped to flexcom shared pin 9:
As per previously shared log, FLEXCOM_SHARED[3]:SS_MASK[0] is being configured to expected value
even before mux_control_deseletc().
2. When I have to map two chip-selects of flx3 - CS0 to flexcom shared 9
CS1 to flexcom shared pin 7
FLEXCOM_SHARED[3]:SS_MASK[0] is set to expected value 0x1fffef
I see console hangs at mux_control_select() if I don’t call mux_control_deselect
while mapping second chip-select FLEXCOM_SHARED[3]:SS_MASK[1].
After reading below description from mux_control_select() :
" * On successfully selecting the mux-control state, it will be locked until
* there is a call to mux_control_deselect()."
Following this help text, I called mux_control_deselect() if there is no error from mux_control_select()
and then it worked. FLEXCOM_SHARED[3]:SS_MASK[1] is now set to expected value 0x1fffbf.
Please explain me if I am missing something.
> >>> + } else {
> >>> + dev_err(&pdev->dev, "Failed to select FLEXCOM
> mux\n");
> >>> + return err;
> >>> + }
> >>> + }
> >>> + }
> >>> +
> >>> clk_disable_unprepare(ddata->clk);
> >>>
> >>> return devm_of_platform_populate(&pdev->dev);
> >>> }
> >>>
> >>> +static const struct atmel_flex_caps atmel_flexcom_caps = {};
> >>> +
> >>> +static const struct atmel_flex_caps lan966x_flexcom_caps = {
> >>> + .has_flx_mux = true,
> >>> +};
> >>> +
> >>> static const struct of_device_id atmel_flexcom_of_match[] = {
> >>> - { .compatible = "atmel,sama5d2-flexcom" },
> >>> + {
> >>> + .compatible = "atmel,sama5d2-flexcom",
> >>> + .data = &atmel_flexcom_caps,
> >>> + },
> >>> +
> >>> + {
> >>> + .compatible = "microchip,lan966-flexcom",
> >>> + .data = &lan966x_flexcom_caps,
> >>> + },
> >>> +
> >>> { /* sentinel */ }
> >>> };
> >>> MODULE_DEVICE_TABLE(of, atmel_flexcom_of_match);
> >>> diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
> >>> index e5c571fd232c..ea09f474bc2f 100644
> >>> --- a/drivers/mux/Kconfig
> >>> +++ b/drivers/mux/Kconfig
> >>> @@ -45,6 +45,18 @@ config MUX_GPIO
> >>> To compile the driver as a module, choose M here: the module will
> >>> be called mux-gpio.
> >>>
> >>> +config MUX_LAN966
> >>> + tristate "LAN966 Flexcom multiplexer"
> >>> + depends on OF || COMPILE_TEST
> >>> + help
> >>> + Lan966 Flexcom Multiplexer controller.
> >>> +
> >>> + The driver supports mapping 2 chip-selects of each of the lan966
> >>> + flexcoms to 21 flexcom shared pins.
> >>> +
> >>> + To compile the driver as a module, choose M here: the module will
> >>> + be called mux-lan966.
> >>> +
> >>> config MUX_MMIO
> >>> tristate "MMIO/Regmap register bitfield-controlled Multiplexer"
> >>> depends on OF || COMPILE_TEST
> >>> diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
> >>> index 6e9fa47daf56..53a9840d96fa 100644
> >>> --- a/drivers/mux/Makefile
> >>> +++ b/drivers/mux/Makefile
> >>> @@ -7,10 +7,12 @@ mux-core-objs := core.o
> >>> mux-adg792a-objs := adg792a.o
> >>> mux-adgs1408-objs := adgs1408.o
> >>> mux-gpio-objs := gpio.o
> >>> +mux-lan966-objs := lan966-flx.o
> >>> mux-mmio-objs := mmio.o
> >>>
> >>> obj-$(CONFIG_MULTIPLEXER) += mux-core.o
> >>> obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
> >>> obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o
> >>> obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
> >>> +obj-$(CONFIG_MUX_LAN966) += mux-lan966.o
> >>> obj-$(CONFIG_MUX_MMIO) += mux-mmio.o
> >>> diff --git a/drivers/mux/lan966-flx.c b/drivers/mux/lan966-flx.c
> >>> new file mode 100644
> >>> index 000000000000..2c7dab616a6a
> >>> --- /dev/null
> >>> +++ b/drivers/mux/lan966-flx.c
> >>> @@ -0,0 +1,121 @@
> >>> +// SPDX-License-Identifier: GPL-2.0
> >>> +/*
> >>> + * LAN966 Flexcom MUX driver
> >>> + *
> >>> + * Copyright (c) 2022 Microchip Inc.
> >>> + *
> >>> + * Author: Kavyasree Kotagiri <kavyasree.kotagiri at microchip.com>
> >>
> >> Looks like it is based on mmio.c?
> >>
> > Yes, I took mmio.c driver as reference.
> >
>
> So, then the above copyright and authorship info is not complete.
>
> >>> + */
> >>> +
> >>> +#include <linux/err.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/of_platform.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/property.h>
> >>> +#include <linux/mux/driver.h>
> >>> +#include <linux/io.h>
> >>> +
> >>> +#define FLEX_SHRD_MASK 0x1FFFFF
> >>> +#define LAN966_MAX_CS 21
> >>> +
> >>> +static void __iomem *flx_shared_base;
> >>
> >> I would much prefer to store the combined address (base+offset)
> >> in the mux private data instead of only storing the offset and
> >> then unnecessarily rely on some piece of global state (that
> >> will be clobbered by other instances).
> >>
> > Ok. I will try to see if this is relevant and change accordingly.
> >
> >>> +struct mux_lan966x {
> >>
> >> Why is the file named lan966, but then everything inside lan966x?
> >>
> >>> + u32 offset;
> >>> + u32 ss_pin;
> >>> +};
> >>> +
> >>> +static int mux_lan966x_set(struct mux_control *mux, int state)
> >>> +{
> >>> + struct mux_lan966x *mux_lan966x = mux_chip_priv(mux->chip);
> >>> + u32 val;
> >>> +
> >>> + val = ~(1 << mux_lan966x[state].ss_pin) & FLEX_SHRD_MASK;
> >>> + writel(val, flx_shared_base + mux_lan966x[state].offset);
> >>
> >> This reads memory you have not allocated, if you select a state
> >> other than zero.
> >>
> > Ok. I will return error condition in case of trying to access none existing
> entry.
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static const struct mux_control_ops mux_lan966x_ops = {
> >>> + .set = mux_lan966x_set,
> >>> +};
> >>> +
> >>> +static const struct of_device_id mux_lan966x_dt_ids[] = {
> >>> + { .compatible = "microchip,lan966-flx-mux", },
> >>> + { /* sentinel */ }
> >>> +};
> >>> +MODULE_DEVICE_TABLE(of, mux_lan966x_dt_ids);
> >>> +
> >>> +static int mux_lan966x_probe(struct platform_device *pdev)
> >>> +{
> >>> + struct device_node *np = pdev->dev.of_node;
> >>> + struct device *dev = &pdev->dev;
> >>> + struct mux_lan966x *mux_lan966x;
> >>> + struct mux_chip *mux_chip;
> >>> + int ret, num_fields, i;
> >>> +
> >>> + ret = of_property_count_u32_elems(np, "mux-offset-pin");
> >>> + if (ret == 0 || ret % 2)
> >>> + ret = -EINVAL;
> >>> + if (ret < 0)
> >>> + return dev_err_probe(dev, ret,
> >>> + "mux-offset-pin property missing or invalid");
> >>> + num_fields = ret / 2;
> >>> +
> >>> + mux_chip = devm_mux_chip_alloc(dev, num_fields,
> >> sizeof(*mux_lan966x));
> >>
> >> I might be thoroughly mistaken and confused by the code, but it seems
> >> very strange that a subsequenct select is not undoing what a previous
> >> select once did. Each state seems to write to its own register offset,
> >> and there is nothing that restores the first register offset with you
> >> switch states.
> >>
> >> Care to explain how muxing works and what you are expected to do to
> >> manipulate the mux? Is there some link to public documentation? I did
> >> a quick search for lan966 but came up with nothing relevant.
> >>
> > LAN966 has 5 flexcoms(which can be used as USART/SPI/I2C interface).
> > FLEXCOM has two chip-select I/O lines namely CS0 and CS1
> > in SPI mode, CTS and RTS in USART mode. These FLEXCOM pins are
> optional.
> > These chip-selects can be mapped to flexcom shared pin [0-21] which can
> be
> > done by configuring flexcom multiplexer register(FLEXCOM_SHARED[0-
> 4]:SS_MASK[0-1])
> > Driver explanation:
> > "flx_shared_base" is used to get base address of Flexcom shared
> multiplexer
> > "mux-offset-pin" property is used to get cs0/cs1 offset and flexcom shared
> pin[0-21] of a flexcom.
> > state value passed from atmel-flexcom is used to configure respective
> > FLEXCOM_SHARED[0-4]:SS_MASK[0-1] register with offset and flexcom
> shared pin.
>
> Ok, let me try to interpret that. You wish enable a "fan out" of these
> two chip-selects for any of the 5 flexcoms (in SPI mode?) such that
> these 10 internal chip-selects can be connected to any of 21 external
> pins?
>
> If that's correct and if you wish to interface with e.g. 20 chips,
> then it would be possible to have 20 states for one mux control and
> then reach the 20 chips using only CS0 from FLEXCOM0, or it would be
> possible to have 2 states for 10 mux controls, one each for CS0/CS1 of
> all five flexcoms and also reach 20 chips. Or any wild combo you
> imagine is useful.
>
> Is that correctly understood?
>
> Assuming so, then you can have a maximum of 10 mux controls, and for
> each mux control you need a variable number of legitimate states. Each
> mux control would also need to know at what address/offset its SS_MASK
> register is at and what pins/states are valid.
>
> But your code does not implemnent the above. You allocate num_fields
> mux controls, which is what confuses the hell out of me. num_fields is
> the number of states, not the number of mux controls! And you also
> need to specify an individual offset for each state, and that makes no
> sense at all, at least not to me.
>
> So, instead, I think you want to have:
>
> struct mux_lan966x {
> u32 ss_mask;
> int num_pins;
> u32 ss_pin[];
> };
>
> And then do:
>
> mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_lan966x) +
> num_pins * sizeof(u32));
>
> (or however that size is best spelled, I haven't kept up)
>
> The set operation would be along the lines of:
>
> static int mux_lan966x_set(struct mux_control *mux, int state)
> {
> struct mux_lan966x *mux_lan966x = mux_chip_priv(mux->chip);
> u32 val;
>
> if (state < 0 || state >= mux_lan966x->num_pins)
> return -1;
>
> val = ~(1 << mux_lan966x->ss_pin[state]) & FLEX_SHRD_MASK;
> writel(val, flx_shared_base + mux_lan966x->ss_mask);
>
> return 0;
> }
>
> Because, one mux control should only ever need to know about one offset,
> as it should only ever write to its own SS_MASK register. And you will
> need some private space such that you can keep track of which states
> are legit.
>
> I would also separate out the SS_MASK offset from the mux-offset-pin
> property and have one property for that value and then a straight
> array for the valid pin numbers in another property (no longer named
> mux-offset-pin of course).
>
> Or something like that and assuming I understand how the FLEXCOMs work
> and what you want to do etc.
>
Thank you for your comments
I agree, I will change number of mux controls to 1 in devm_mux_chip_alloc()
I would like to use mux-offset-pin property because
each chip-select must be mapped to a unique flexcom shared pin.
For example,
mux-offset-pin = <0x18 9>, /* 0: flexcom3 CS0 mapped to pin-9 */
<0x1c 7>, /* 1: flexcom3 CS1 mapped to pin-7 */
<0x20 4>; /* 2: flexcom4 CS0 mapped to pin-4 */};
I want to use mux-offset-pin property to be clear about which offset is mapped to which
flexcom shared pin. Here, 0, 1, 2.. entries represents state passed from mux_control_select(mux, state).
Please provide your comments.
> Cheers,
> Peter
>
>
> >>> + if (IS_ERR(mux_chip))
> >>> + return dev_err_probe(dev, PTR_ERR(mux_chip),
> >>> + "failed to allocate mux_chips\n");
> >>> +
> >>> + mux_lan966x = mux_chip_priv(mux_chip);
> >>> +
> >>> + flx_shared_base =
> devm_platform_get_and_ioremap_resource(pdev,
> >> 0, NULL);
> >>> + if (IS_ERR(flx_shared_base))
> >>> + return dev_err_probe(dev, PTR_ERR(flx_shared_base),
> >>> + "failed to get flexcom shared base address\n");
> >>> +
> >>> + for (i = 0; i < num_fields; i++) {
> >>> + struct mux_control *mux = &mux_chip->mux[i];
> >>> + u32 offset, shared_pin;
> >>> +
> >>> + ret = of_property_read_u32_index(np, "mux-offset-pin",
> >>> + 2 * i, &offset);
> >>> + if (ret == 0)
> >>> + ret = of_property_read_u32_index(np, "mux-offset-pin",
> >>> + 2 * i + 1,
> >>> + &shared_pin);
> >>> + if (ret < 0)
> >>> + return dev_err_probe(dev, ret,
> >>> + "failed to read mux-offset-pin property: %d", i);
> >>> +
> >>> + if (shared_pin >= LAN966_MAX_CS)
> >>> + return -EINVAL;
> >>> +
> >>> + mux_lan966x[i].offset = offset;
> >>> + mux_lan966x[i].ss_pin = shared_pin;
> >>
> >> This clobbers memory you have not allocated, if num_fields >= 1.
> >>
> >> Cheers,
> >> Peter
> >>
> >>> +
> >>> + mux->states = LAN966_MAX_CS;
> >>> + }
> >>> +
> >>> + mux_chip->ops = &mux_lan966x_ops;
> >>> +
> >>> + ret = devm_mux_chip_register(dev, mux_chip);
> >>> + if (ret < 0)
> >>> + return ret;
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> +static struct platform_driver mux_lan966x_driver = {
> >>> + .driver = {
> >>> + .name = "lan966-mux",
> >>> + .of_match_table = of_match_ptr(mux_lan966x_dt_ids),
> >>> + },
> >>> + .probe = mux_lan966x_probe,
> >>> +};
> >>> +
> >>> +module_platform_driver(mux_lan966x_driver);
> >>> +
> >>> +MODULE_DESCRIPTION("LAN966 Flexcom multiplexer driver");
> >>> +MODULE_AUTHOR("Kavyasree Kotagiri
> >> <kavyasree.kotagiri at microchip.com>");
> >>> +MODULE_LICENSE("GPL v2");
> >>> +
More information about the linux-arm-kernel
mailing list