[PATCH 05/12] pinctrl: cirrus: Add support for CS48L31/32/33 codecs

Richard Fitzgerald
2022-11-09T16:56:15Z

From: Piotr Stankiewicz <piotrs@opensource.cirrus.com>

Codecs in this family have multiple digital I/O functions for audio,
DSP subsystem, GPIO and various special functions. All muxable pins
are selectable as either a GPIO or an alternate function.

Signed-off-by: Piotr Stankiewicz <piotrs@opensource.cirrus.com>
Signed-off-by: Qi Zhou <qi.zhou@cirrus.com>
Signed-off-by: Stuart Henderson <stuarth@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
MAINTAINERS | 1 +
drivers/pinctrl/cirrus/Kconfig | 5 +
drivers/pinctrl/cirrus/Makefile | 2 +
drivers/pinctrl/cirrus/pinctrl-cs48l32.c | 932 +++++++++++++++++++++++
drivers/pinctrl/cirrus/pinctrl-cs48l32.h | 62 ++
5 files changed, 1002 insertions(+)
create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.c
create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.h

diff --git a/MAINTAINERS b/MAINTAINERS
index f1d696f29f11..cd1773d39dd8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5011,6 +5011,7 @@ W: https://github.com/CirrusLogic/linux-drivers/wiki
T: git https://github.com/CirrusLogic/linux-drivers.git
F: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
F: Documentation/devicetree/bindings/mfd/cirrus,madera.yaml
+F: Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml
F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml
F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml
F: drivers/gpio/gpio-madera*
diff --git a/drivers/pinctrl/cirrus/Kconfig b/drivers/pinctrl/cirrus/Kconfig
index 530426a74f75..c51192bde87a 100644
--- a/drivers/pinctrl/cirrus/Kconfig
+++ b/drivers/pinctrl/cirrus/Kconfig
@@ -30,3 +30,8 @@ config PINCTRL_CS47L90

config PINCTRL_CS47L92
bool
+
+config PINCTRL_CS48L32
+ tristate
+ select PINMUX
+ select GENERIC_PINCONF
diff --git a/drivers/pinctrl/cirrus/Makefile b/drivers/pinctrl/cirrus/Makefile
index a484518c840e..18290f6be00c 100644
--- a/drivers/pinctrl/cirrus/Makefile
+++ b/drivers/pinctrl/cirrus/Makefile
@@ -19,4 +19,6 @@ ifeq ($(CONFIG_PINCTRL_CS47L92),y)
pinctrl-madera-objs += pinctrl-cs47l92.o
endif

+obj-$(CONFIG_PINCTRL_CS48L32) += pinctrl-cs48l32.o
+
obj-$(CONFIG_PINCTRL_MADERA) += pinctrl-madera.o
diff --git a/drivers/pinctrl/cirrus/pinctrl-cs48l32.c b/drivers/pinctrl/cirrus/pinctrl-cs48l32.c
new file mode 100644
index 000000000000..cb5031d6d0ce
--- /dev/null
+++ b/drivers/pinctrl/cirrus/pinctrl-cs48l32.c
@@ -0,0 +1,932 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pinctrl for Cirrus Logic CS48L32
+ *
+ * Copyright (C) 2017-2018, 2020, 2022 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+#include <linux/err.h>
+#include <linux/mfd/cs48l32/core.h>
+#include <linux/mfd/cs48l32/registers.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "../pinctrl-utils.h"
+#include "pinctrl-cs48l32.h"
+
+/*
+ * Pins are named after their GPIO number
+ * NOTE: IDs are zero-indexed for coding convenience
+ */
+static const struct pinctrl_pin_desc cs48l32_pins[] = {
+ PINCTRL_PIN(0, "gpio1"),
+ PINCTRL_PIN(1, "gpio2"),
+ PINCTRL_PIN(2, "gpio3"),
+ PINCTRL_PIN(3, "gpio4"),
+ PINCTRL_PIN(4, "gpio5"),
+ PINCTRL_PIN(5, "gpio6"),
+ PINCTRL_PIN(6, "gpio7"),
+ PINCTRL_PIN(7, "gpio8"),
+ PINCTRL_PIN(8, "gpio9"),
+ PINCTRL_PIN(9, "gpio10"),
+ PINCTRL_PIN(10, "gpio11"),
+ PINCTRL_PIN(11, "gpio12"),
+ PINCTRL_PIN(12, "gpio13"),
+ PINCTRL_PIN(13, "gpio14"),
+ PINCTRL_PIN(14, "gpio15"),
+ PINCTRL_PIN(15, "gpio16"),
+};
+
+/*
+ * All single-pin functions can be mapped to any GPIO, however pinmux applies
+ * functions to pin groups and only those groups declared as supporting that
+ * function. To make this work we must put each pin in its own dummy group so
+ * that the functions can be described as applying to all pins.
+ * Since these do not correspond to anything in the actual hardware - they are
+ * merely an adaptation to pinctrl's view of the world - we use the same name
+ * as the pin to avoid confusion when comparing with datasheet instructions
+ */
+static const char * const cs48l32_pin_single_group_names[] = {
+ "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7",
+ "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14",
+ "gpio15", "gpio16",
+};
+
+/* set of pin numbers for single-pin groups */
+static const unsigned int cs48l32_pin_single_group_pins[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+};
+
+static const char * const cs48l32_asp1_group_names[] = { "asp1" };
+static const char * const cs48l32_asp2_group_names[] = { "asp2" };
+static const char * const cs48l32_in1pdm_group_names[] = { "in1-pdm" };
+static const char * const cs48l32_in2pdm_group_names[] = { "in2-pdm" };
+static const char * const cs48l32_spi2_group_names[] = { "spi2" };
+
+/*
+ * alt-functions always apply to only one group, other functions always
+ * apply to all pins
+ */
+static const struct {
+ const char *name;
+ const char * const *group_names;
+ u32 func;
+} cs48l32_mux_funcs[] = {
+ {
+ .name = "asp1",
+ .group_names = cs48l32_asp1_group_names,
+ .func = 0x000
+ },
+ {
+ .name = "asp2",
+ .group_names = cs48l32_asp2_group_names,
+ .func = 0x000
+ },
+ {
+ .name = "in1-pdm",
+ .group_names = cs48l32_in1pdm_group_names,
+ .func = 0x000
+ },
+ {
+ .name = "in2-pdm",
+ .group_names = cs48l32_in2pdm_group_names,
+ .func = 0x000,
+ },
+ {
+ .name = "spi2",
+ .group_names = cs48l32_spi2_group_names,
+ .func = 0x000
+ },
+ {
+ .name = "io",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x001
+ },
+ {
+ .name = "dsp-gpio",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x002
+ },
+ {
+ .name = "irq1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x003
+ },
+ {
+ .name = "fll1-clk",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x010
+ },
+ {
+ .name = "fll1-lock",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x018
+ },
+ {
+ .name = "opclk",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x048
+ },
+ {
+ .name = "opclk-dsp",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x04a
+ },
+ {
+ .name = "uart",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x04c
+ },
+ {
+ .name = "input-path-signal-detect",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x08c
+ },
+ {
+ .name = "ultrasonic-in1-activity-detect",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x090
+ },
+ {
+ .name = "ultrasonic-in2-activity-detect",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x092
+ },
+ {
+ .name = "dma-ch0-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x190
+ },
+ {
+ .name = "dma-ch1-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x191
+ },
+ {
+ .name = "dma-ch2-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x192
+ },
+ {
+ .name = "dma-ch3-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x193
+ },
+ {
+ .name = "dma-ch4-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x194
+ },
+ {
+ .name = "dma-ch5-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x195
+ },
+ {
+ .name = "dma-ch6-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x196
+ },
+ {
+ .name = "dma-ch7-programmable-transfer-complete",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x197
+ },
+ {
+ .name = "sample-rate-change-trigger-a",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x214
+ },
+ {
+ .name = "sample-rate-change-trigger-b",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x215
+ },
+ {
+ .name = "sample-rate-change-trigger-c",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x216
+ },
+ {
+ .name = "sample-rate-change-trigger-d",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x217
+ },
+ {
+ .name = "timer1-irq-ch1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x230
+ },
+ {
+ .name = "timer1-irq-ch2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x231
+ },
+ {
+ .name = "timer1-irq-ch3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x232
+ },
+ {
+ .name = "timer1-irq-ch4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x233
+ },
+ {
+ .name = "timer2-irq-ch1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x234
+ },
+ {
+ .name = "timer2-irq-ch2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x235
+ },
+ {
+ .name = "timer2-irq-ch3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x236
+ },
+ {
+ .name = "timer2-irq-ch4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x237
+ },
+ {
+ .name = "timer3-irq-ch1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x238
+ },
+ {
+ .name = "timer3-irq-ch2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x239
+ },
+ {
+ .name = "timer3-irq-ch3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23a
+ },
+ {
+ .name = "timer3-irq-ch4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23b
+ },
+ {
+ .name = "timer4-irq-ch1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23c
+ },
+ {
+ .name = "timer4-irq-ch2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23d
+ },
+ {
+ .name = "timer4-irq-ch3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23e
+ },
+ {
+ .name = "timer4-irq-ch4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x23f
+ },
+ {
+ .name = "timer5-irq-ch1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x240
+ },
+ {
+ .name = "timer5-irq-ch2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x241
+ },
+ {
+ .name = "timer5-irq-ch3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x242
+ },
+ {
+ .name = "timer5-irq-ch4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x243
+ },
+ {
+ .name = "timer-1",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x250
+ },
+ {
+ .name = "timer-2",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x251
+ },
+ {
+ .name = "timer-3",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x252
+ },
+ {
+ .name = "timer-4",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x253
+ },
+ {
+ .name = "timer-5",
+ .group_names = cs48l32_pin_single_group_names,
+ .func = 0x254
+ },
+};
+
+/* Note - all 1 less than in datasheet because these are zero-indexed */
+static const unsigned int cs48l32_asp1_pins[] = { 2, 3, 4, 5 };
+static const unsigned int cs48l32_asp2_pins[] = { 6, 7, 8, 9 };
+static const unsigned int cs48l32_spi2_pins[] = { 10, 11, 12, 13, 14, 15 };
+
+static const struct cs48l32_pin_groups cs48l32_pin_groups[] = {
+ { "asp1", cs48l32_asp1_pins, ARRAY_SIZE(cs48l32_asp1_pins) },
+ { "asp2", cs48l32_asp2_pins, ARRAY_SIZE(cs48l32_asp2_pins) },
+ { "spi2", cs48l32_spi2_pins, ARRAY_SIZE(cs48l32_spi2_pins) },
+};
+
+static const struct cs48l32_pin_chip cs48l32_pin_chip = {
+ .n_pins = CS48L32_NUM_GPIOS,
+ .pin_groups = cs48l32_pin_groups,
+ .n_pin_groups = ARRAY_SIZE(cs48l32_pin_groups),
+};
+
+static unsigned int cs48l32_pin_make_drv_str(struct cs48l32_pin_private *priv,
+ unsigned int milliamps)
+{
+ switch (milliamps) {
+ case 4:
+ return 0;
+ case 8:
+ return 1 << CS48L32_GP_DRV_STR_SHIFT;
+ default:
+ break;
+ }
+
+ dev_warn(priv->dev, "%u mA is not a valid drive strength\n", milliamps);
+
+ return 0;
+}
+
+static unsigned int cs48l32_pin_unmake_drv_str(struct cs48l32_pin_private *priv,
+ unsigned int regval)
+{
+ regval = (regval & CS48L32_GP_DRV_STR_MASK) >> CS48L32_GP_DRV_STR_SHIFT;
+
+ switch (regval) {
+ case 0:
+ return 4;
+ case 1:
+ return 8;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int cs48l32_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ /* Number of alt function groups plus number of single-pin groups */
+ return priv->chip->n_pin_groups + priv->chip->n_pins;
+}
+
+static const char *cs48l32_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ if (selector < priv->chip->n_pin_groups)
+ return priv->chip->pin_groups[selector].name;
+
+ selector -= priv->chip->n_pin_groups;
+
+ return cs48l32_pin_single_group_names[selector];
+}
+
+static int cs48l32_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ if (selector < priv->chip->n_pin_groups) {
+ *pins = priv->chip->pin_groups[selector].pins;
+ *num_pins = priv->chip->pin_groups[selector].n_pins;
+ } else {
+ /* return the dummy group for a single pin */
+ selector -= priv->chip->n_pin_groups;
+ *pins = &cs48l32_pin_single_group_pins[selector];
+ *num_pins = 1;
+ }
+
+ return 0;
+}
+
+static void cs48l32_pin_dbg_show_fn(struct cs48l32_pin_private *priv,
+ struct seq_file *s,
+ unsigned int pin, unsigned int fn)
+{
+ const struct cs48l32_pin_chip *chip = priv->chip;
+ int i, g_pin;
+
+ if (fn != 0) {
+ for (i = 0; i < ARRAY_SIZE(cs48l32_mux_funcs); ++i) {
+ if (cs48l32_mux_funcs[i].func == fn) {
+ seq_printf(s, " FN=%s", cs48l32_mux_funcs[i].name);
+ return;
+ }
+ }
+ return; /* ignore unknown function values */
+ }
+
+ /* alt function */
+ for (i = 0; i < chip->n_pin_groups; ++i) {
+ for (g_pin = 0; g_pin < chip->pin_groups[i].n_pins; ++g_pin) {
+ if (chip->pin_groups[i].pins[g_pin] == pin) {
+ seq_printf(s, " FN=%s", chip->pin_groups[i].name);
+ return;
+ }
+ }
+ }
+}
+
+static void cs48l32_pin_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin);
+ unsigned int conf, fn;
+ int ret;
+
+ ret = regmap_read(priv->mfd->regmap, reg, &conf);
+ if (ret)
+ return;
+
+ seq_printf(s, "%08x", conf);
+
+ fn = (conf & CS48L32_GP_FN_MASK) >> CS48L32_GP_FN_SHIFT;
+ cs48l32_pin_dbg_show_fn(priv, s, pin, fn);
+
+ /* State of direction bit is only relevant if function==1 */
+ if (fn == 1) {
+ if (conf & CS48L32_GP_DIR_MASK)
+ seq_puts(s, " IN");
+ else
+ seq_puts(s, " OUT");
+ }
+
+ if (conf & CS48L32_GP_PU_MASK)
+ seq_puts(s, " PU");
+
+ if (conf & CS48L32_GP_PD_MASK)
+ seq_puts(s, " PD");
+
+ if (conf & CS48L32_GP_DB_MASK)
+ seq_puts(s, " DB");
+
+ if (conf & CS48L32_GP_OP_CFG_MASK)
+ seq_puts(s, " OD");
+ else
+ seq_puts(s, " CMOS");
+
+ seq_printf(s, " DRV=%umA", cs48l32_pin_unmake_drv_str(priv, conf));
+}
+
+
+static const struct pinctrl_ops cs48l32_pin_group_ops = {
+ .get_groups_count = &cs48l32_get_groups_count,
+ .get_group_name = &cs48l32_get_group_name,
+ .get_group_pins = &cs48l32_get_group_pins,
+ .pin_dbg_show = &cs48l32_pin_dbg_show,
+ .dt_node_to_map = &pinconf_generic_dt_node_to_map_all,
+ .dt_free_map = &pinctrl_utils_free_map,
+};
+
+static int cs48l32_mux_get_funcs_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(cs48l32_mux_funcs);
+}
+
+static const char *cs48l32_mux_get_func_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ return cs48l32_mux_funcs[selector].name;
+}
+
+static int cs48l32_mux_get_groups(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ const char * const **groups,
+ unsigned int * const num_groups)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = cs48l32_mux_funcs[selector].group_names;
+
+ if (cs48l32_mux_funcs[selector].func == 0) {
+ /* alt func always maps to a single group */
+ *num_groups = 1;
+ } else {
+ /* other funcs map to all available gpio pins */
+ *num_groups = priv->chip->n_pins;
+ }
+
+ return 0;
+}
+
+static int cs48l32_mux_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
+ unsigned int group)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ struct cs48l32_mfd *mfd = priv->mfd;
+ const struct cs48l32_pin_groups *pin_group = priv->chip->pin_groups;
+ unsigned int n_chip_groups = priv->chip->n_pin_groups;
+ const char *func_name = cs48l32_mux_funcs[selector].name;
+ unsigned int reg;
+ int i, ret;
+
+ dev_dbg(priv->dev, "%s selecting %u (%s) for group %u (%s)\n",
+ __func__, selector, func_name, group,
+ cs48l32_get_group_name(pctldev, group));
+
+ if (cs48l32_mux_funcs[selector].func == 0) {
+ /* alt func pin assignments are codec-specific */
+ for (i = 0; i < n_chip_groups; ++i) {
+ if (strcmp(func_name, pin_group->name) == 0)
+ break;
+
+ ++pin_group;
+ }
+
+ if (i == n_chip_groups)
+ return -EINVAL;
+
+ for (i = 0; i < pin_group->n_pins; ++i) {
+ reg = CS48L32_GPIO1_CTRL1 + (4 * pin_group->pins[i]);
+
+ dev_dbg(priv->dev, "%s setting 0x%x func bits to 0\n", __func__, reg);
+
+ ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 0);
+ if (ret)
+ break;
+
+ }
+ } else {
+ /*
+ * for other funcs the group will be the gpio number and will
+ * be offset by the number of chip-specific functions at the
+ * start of the group list
+ */
+ group -= n_chip_groups;
+ reg = CS48L32_GPIO1_CTRL1 + (4 * group);
+
+ dev_dbg(priv->dev, "%s setting 0x%x func bits to 0x%x\n",
+ __func__, reg, cs48l32_mux_funcs[selector].func);
+
+ ret = regmap_update_bits(mfd->regmap,
+ reg,
+ CS48L32_GP_FN_MASK,
+ cs48l32_mux_funcs[selector].func);
+ }
+
+ if (ret)
+ dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret);
+
+ return ret;
+}
+
+static int cs48l32_gpio_set_direction(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin,
+ bool input)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ struct cs48l32_mfd *mfd = priv->mfd;
+ unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin);
+ unsigned int val;
+ int ret;
+
+ if (input)
+ val = CS48L32_GP_DIR_MASK;
+ else
+ val = 0;
+
+ ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_DIR_MASK, val);
+ if (ret)
+ dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret);
+
+ return ret;
+}
+
+static int cs48l32_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ struct cs48l32_mfd *mfd = priv->mfd;
+ unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin);
+ int ret;
+
+ /* put the pin into GPIO mode */
+ ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 1);
+ if (ret)
+ dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret);
+
+ return ret;
+}
+
+static void cs48l32_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ struct cs48l32_mfd *mfd = priv->mfd;
+ unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin);
+ int ret;
+
+ /* disable GPIO by setting to GPIO IN */
+ cs48l32_gpio_set_direction(pctldev, range, pin, true);
+
+ ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 1);
+ if (ret)
+ dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret);
+}
+static const struct pinmux_ops cs48l32_pin_mux_ops = {
+ .get_functions_count = &cs48l32_mux_get_funcs_count,
+ .get_function_name = &cs48l32_mux_get_func_name,
+ .get_function_groups = &cs48l32_mux_get_groups,
+ .set_mux = &cs48l32_mux_set_mux,
+ .gpio_request_enable = &cs48l32_gpio_request_enable,
+ .gpio_disable_free = &cs48l32_gpio_disable_free,
+ .gpio_set_direction = &cs48l32_gpio_set_direction,
+ .strict = true, /* GPIO and other functions are exclusive */
+};
+
+static int cs48l32_pin_conf_get(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *config)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int param = pinconf_to_config_param(*config);
+ unsigned int result = 0;
+ unsigned int conf;
+ int ret;
+
+ ret = regmap_read(priv->mfd->regmap, CS48L32_GPIO1_CTRL1 + (4 * pin), &conf);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read GP%d conf (%d)\n", pin + 1, ret);
+ return ret;
+ }
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_BUS_HOLD:
+ conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ if (conf == (CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK))
+ result = 1;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ if (!conf)
+ result = 1;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ if (conf == CS48L32_GP_PD_MASK)
+ result = 1;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ if (conf == CS48L32_GP_PU_MASK)
+ result = 1;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (conf & CS48L32_GP_OP_CFG_MASK)
+ result = 1;
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ if (!(conf & CS48L32_GP_OP_CFG_MASK))
+ result = 1;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ result = cs48l32_pin_unmake_drv_str(priv, conf);
+ break;
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ dev_dbg(priv->dev, "Input debounce time not supported.");
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ if (conf & CS48L32_GP_DIR_MASK)
+ result = 1;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ if ((conf & CS48L32_GP_DIR_MASK) && (conf & CS48L32_GP_LVL_MASK))
+ result = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *config = pinconf_to_config_packed(param, result);
+
+ return 0;
+}
+
+static int cs48l32_pin_conf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int conf = 0;
+ unsigned int mask = 0;
+ unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin);
+ unsigned int val;
+ int ret;
+
+ while (num_configs) {
+ dev_dbg(priv->dev, "%s config 0x%lx\n", __func__, *configs);
+
+ switch (pinconf_to_config_param(*configs)) {
+ case PIN_CONFIG_BIAS_BUS_HOLD:
+ mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ conf |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ conf &= ~(CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK);
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ conf |= CS48L32_GP_PD_MASK;
+ conf &= ~CS48L32_GP_PU_MASK;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK;
+ conf |= CS48L32_GP_PU_MASK;
+ conf &= ~CS48L32_GP_PD_MASK;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ mask |= CS48L32_GP_OP_CFG_MASK;
+ conf |= CS48L32_GP_OP_CFG_MASK;
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ mask |= CS48L32_GP_OP_CFG_MASK;
+ conf &= ~CS48L32_GP_OP_CFG_MASK;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ val = pinconf_to_config_argument(*configs);
+ mask |= CS48L32_GP_DRV_STR_MASK;
+ conf &= ~CS48L32_GP_DRV_STR_MASK;
+ conf |= cs48l32_pin_make_drv_str(priv, val);
+ break;
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ dev_dbg(priv->dev, "Input debounce time not supported.");
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ val = pinconf_to_config_argument(*configs);
+ mask |= CS48L32_GP_DIR_MASK;
+ if (val)
+ conf |= CS48L32_GP_DIR_MASK;
+ else
+ conf &= ~CS48L32_GP_DIR_MASK;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ val = pinconf_to_config_argument(*configs);
+ mask |= CS48L32_GP_LVL_MASK;
+ if (val)
+ conf |= CS48L32_GP_LVL_MASK;
+ else
+ conf &= ~CS48L32_GP_LVL_MASK;
+
+ mask |= CS48L32_GP_DIR_MASK;
+ conf &= ~CS48L32_GP_DIR_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ++configs;
+ --num_configs;
+ }
+
+ dev_dbg(priv->dev, "%s gpio%d 0x%x:0x%x\n", __func__, pin + 1, reg, conf);
+
+ ret = regmap_update_bits(priv->mfd->regmap, reg, mask, conf);
+ if (ret)
+ dev_err(priv->dev, "Failed to write GPIO%d conf (%d) reg 0x%x\n",
+ pin + 1, ret, reg);
+
+ return ret;
+}
+
+static int cs48l32_pin_conf_group_set(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ unsigned long *configs,
+ unsigned int num_configs)
+{
+ struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev);
+ const struct cs48l32_pin_groups *pin_group;
+ unsigned int n_groups = priv->chip->n_pin_groups;
+ int i, ret;
+
+ dev_dbg(priv->dev, "%s setting group %s\n", __func__,
+ cs48l32_get_group_name(pctldev, selector));
+
+ if (selector >= n_groups) {
+ /* group is a single pin, convert to pin number and set */
+ return cs48l32_pin_conf_set(pctldev,
+ selector - n_groups,
+ configs,
+ num_configs);
+ } else {
+ pin_group = &priv->chip->pin_groups[selector];
+
+ for (i = 0; i < pin_group->n_pins; ++i) {
+ ret = cs48l32_pin_conf_set(pctldev,
+ pin_group->pins[i],
+ configs,
+ num_configs);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct pinconf_ops cs48l32_pin_conf_ops = {
+ .is_generic = true,
+ .pin_config_get = &cs48l32_pin_conf_get,
+ .pin_config_set = &cs48l32_pin_conf_set,
+ .pin_config_group_set = &cs48l32_pin_conf_group_set,
+
+};
+
+static struct pinctrl_desc cs48l32_pin_desc = {
+ .name = "cs48l32-pinctrl",
+ .pins = cs48l32_pins,
+ .pctlops = &cs48l32_pin_group_ops,
+ .pmxops = &cs48l32_pin_mux_ops,
+ .confops = &cs48l32_pin_conf_ops,
+ .owner = THIS_MODULE,
+};
+
+static int cs48l32_pin_probe(struct platform_device *pdev)
+{
+ struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent);
+ struct cs48l32_pin_private *priv;
+ int ret;
+
+ BUILD_BUG_ON(ARRAY_SIZE(cs48l32_pin_single_group_names) !=
+ ARRAY_SIZE(cs48l32_pin_single_group_pins));
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+ priv->mfd = mfd;
+ /* Composite MFD device so shares the parent OF node. */
+ pdev->dev.of_node = mfd->dev->of_node;
+
+ priv->chip = &cs48l32_pin_chip;
+ cs48l32_pin_desc.npins = priv->chip->n_pins;
+
+ ret = devm_pinctrl_register_and_init(&pdev->dev, &cs48l32_pin_desc, priv, &priv->pctl);
+ if (ret)
+ return dev_err_probe(priv->dev, ret, "Failed pinctrl register\n");
+
+ ret = pinctrl_enable(priv->pctl);
+ if (ret)
+ return dev_err_probe(priv->dev, ret, "Failed to enable pinctrl\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ dev_dbg(priv->dev, "pinctrl registered\n");
+
+ return 0;
+}
+
+static struct platform_driver cs48l32_pin_driver = {
+ .probe = &cs48l32_pin_probe,
+ .driver = {
+ .name = "cs48l32-pinctrl",
+ },
+};
+
+module_platform_driver(cs48l32_pin_driver);
+
+MODULE_DESCRIPTION("CS48L32 pinctrl driver");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/cirrus/pinctrl-cs48l32.h b/drivers/pinctrl/cirrus/pinctrl-cs48l32.h
new file mode 100644
index 000000000000..2193c7558dd3
--- /dev/null
+++ b/drivers/pinctrl/cirrus/pinctrl-cs48l32.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pinctrl for Cirrus Logic CS48L32
+ *
+ * Copyright (C) 2020, 2022 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef PINCTRL_CS48L32_H
+#define PINCTRL_CS48L32_H
+
+#include <linux/device.h>
+#include <linux/mfd/cs48l32/core.h>
+
+struct pinctrl_dev;
+
+#define CS48L32_GP_DIR_MASK 0x80000000
+#define CS48L32_GP_DIR_SHIFT 31
+#define CS48L32_GP_PU_MASK 0x40000000
+#define CS48L32_GP_PU_SHIFT 30
+#define CS48L32_GP_PD_MASK 0x20000000
+#define CS48L32_GP_PD_SHIFT 29
+#define CS48L32_GP_DRV_STR_MASK 0x03000000
+#define CS48L32_GP_DRV_STR_SHIFT 24
+#define CS48L32_GP_DBTIME_MASK 0x000f0000
+#define CS48L32_GP_DBTIME_SHIFT 16
+#define CS48L32_GP_LVL_MASK 0x00008000
+#define CS48L32_GP_LVL_SHIFT 15
+#define CS48L32_GP_OP_CFG_MASK 0x00004000
+#define CS48L32_GP_OP_CFG_SHIFT 14
+#define CS48L32_GP_DB_MASK 0x00002000
+#define CS48L32_GP_DB_SHIFT 13
+#define CS48L32_GP_POL_MASK 0x00001000
+#define CS48L32_GP_POL_SHIFT 12
+#define CS48L32_GP_FN_MASK 0x000007ff
+#define CS48L32_GP_FN_SHIFT 0
+
+#define CS48L32_NUM_GPIOS 16
+
+struct cs48l32_pin_groups {
+ const char *name;
+ const unsigned int *pins;
+ unsigned int n_pins;
+};
+
+struct cs48l32_pin_chip {
+ unsigned int n_pins;
+
+ const struct cs48l32_pin_groups *pin_groups;
+ unsigned int n_pin_groups;
+};
+
+struct cs48l32_pin_private {
+ struct cs48l32_mfd *mfd;
+
+ const struct cs48l32_pin_chip *chip; /* chip-specific groups */
+
+ struct device *dev;
+ struct pinctrl_dev *pctl;
+};
+
+#endif
--
2.30.2