Subject: Re: [PATCH] Revert "clk: gpio: Use DT way of specifying parents"

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

From: Russell King (Oracle) <[email-masked]>

This reverts commit 9a9b5a4af066f1245d7848a5d5d5c8147c18aa28.
---
 drivers/clk/clk-gpio.c       | 172 ++++++++++++++++++++++++++++---------------
 include/linux/clk-provider.h |  38 ++++++++++
 2 files changed, 151 insertions(+), 59 deletions(-)

diff --git a/drivers/clk/clk-gpio.c b/drivers/clk/clk-gpio.c
index 5b114043771d..f53df89940d4 100644
--- a/drivers/clk/clk-gpio.c
+++ b/drivers/clk/clk-gpio.c
@@ -28,26 +28,6 @@
  * parent - fixed parent.  No clk_set_parent support
  */
 
-/**
- * struct clk_gpio - gpio gated clock
- *
- * @hw:		handle between common and hardware-specific interfaces
- * @gpiod:	gpio descriptor
- *
- * Clock with a gpio control for enabling and disabling the parent clock
- * or switching between two parents by asserting or deasserting the gpio.
- *
- * Implements .enable, .disable and .is_enabled or
- * .get_parent, .set_parent and .determine_rate depending on which clk_ops
- * is used.
- */
-struct clk_gpio {
-	struct clk_hw	hw;
-	struct gpio_desc *gpiod;
-};
-
-#define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw)
-
 static int clk_gpio_gate_enable(struct clk_hw *hw)
 {
 	struct clk_gpio *clk = to_clk_gpio(hw);
@@ -71,11 +51,12 @@ static int clk_gpio_gate_is_enabled(struct clk_hw *hw)
 	return gpiod_get_value(clk->gpiod);
 }
 
-static const struct clk_ops clk_gpio_gate_ops = {
+const struct clk_ops clk_gpio_gate_ops = {
 	.enable = clk_gpio_gate_enable,
 	.disable = clk_gpio_gate_disable,
 	.is_enabled = clk_gpio_gate_is_enabled,
 };
+EXPORT_SYMBOL_GPL(clk_gpio_gate_ops);
 
 static int clk_sleeping_gpio_gate_prepare(struct clk_hw *hw)
 {
@@ -130,49 +111,67 @@ static int clk_gpio_mux_set_parent(struct clk_hw *hw, u8 index)
 	return 0;
 }
 
-static const struct clk_ops clk_gpio_mux_ops = {
+const struct clk_ops clk_gpio_mux_ops = {
 	.get_parent = clk_gpio_mux_get_parent,
 	.set_parent = clk_gpio_mux_set_parent,
 	.determine_rate = __clk_mux_determine_rate,
 };
+EXPORT_SYMBOL_GPL(clk_gpio_mux_ops);
 
-static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents,
-					struct gpio_desc *gpiod,
-					const struct clk_ops *clk_gpio_ops)
+static struct clk_hw *clk_register_gpio(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags, const struct clk_ops *clk_gpio_ops)
 {
 	struct clk_gpio *clk_gpio;
 	struct clk_hw *hw;
 	struct clk_init_data init = {};
 	int err;
-	const struct clk_parent_data gpio_parent_data[] = {
-		{ .index = 0 },
-		{ .index = 1 },
-	};
 
-	clk_gpio = devm_kzalloc(dev, sizeof(*clk_gpio),	GFP_KERNEL);
+	if (dev)
+		clk_gpio = devm_kzalloc(dev, sizeof(*clk_gpio),	GFP_KERNEL);
+	else
+		clk_gpio = kzalloc(sizeof(*clk_gpio), GFP_KERNEL);
+
 	if (!clk_gpio)
 		return ERR_PTR(-ENOMEM);
 
-	init.name = dev->of_node->name;
+	init.name = name;
 	init.ops = clk_gpio_ops;
-	init.parent_data = gpio_parent_data;
+	init.flags = flags;
+	init.parent_names = parent_names;
 	init.num_parents = num_parents;
-	init.flags = CLK_SET_RATE_PARENT;
 
 	clk_gpio->gpiod = gpiod;
 	clk_gpio->hw.init = &init;
 
 	hw = &clk_gpio->hw;
-	err = devm_clk_hw_register(dev, hw);
-	if (err)
-		return ERR_PTR(err);
+	if (dev)
+		err = devm_clk_hw_register(dev, hw);
+	else
+		err = clk_hw_register(NULL, hw);
+
+	if (!err)
+		return hw;
+
+	if (!dev) {
+		kfree(clk_gpio);
+	}
 
-	return hw;
+	return ERR_PTR(err);
 }
 
-static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev,
-						int num_parents,
-						struct gpio_desc *gpiod)
+/**
+ * clk_hw_register_gpio_gate - register a gpio clock gate with the clock
+ * framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_name: name of this clock's parent
+ * @gpiod: gpio descriptor to gate this clock
+ * @flags: clock flags
+ */
+struct clk_hw *clk_hw_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags)
 {
 	const struct clk_ops *ops;
 
@@ -181,36 +180,88 @@ static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev,
 	else
 		ops = &clk_gpio_gate_ops;
 
-	return clk_register_gpio(dev, num_parents, gpiod, ops);
+	return clk_register_gpio(dev, name,
+			(parent_name ? &parent_name : NULL),
+			(parent_name ? 1 : 0), gpiod, flags, ops);
 }
+EXPORT_SYMBOL_GPL(clk_hw_register_gpio_gate);
 
-static struct clk_hw *clk_hw_register_gpio_mux(struct device *dev,
-					       struct gpio_desc *gpiod)
+struct clk *clk_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags)
 {
-	return clk_register_gpio(dev, 2, gpiod, &clk_gpio_mux_ops);
+	struct clk_hw *hw;
+
+	hw = clk_hw_register_gpio_gate(dev, name, parent_name, gpiod, flags);
+	if (IS_ERR(hw))
+		return ERR_CAST(hw);
+	return hw->clk;
 }
+EXPORT_SYMBOL_GPL(clk_register_gpio_gate);
+
+/**
+ * clk_hw_register_gpio_mux - register a gpio clock mux with the clock framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_names: names of this clock's parents
+ * @num_parents: number of parents listed in @parent_names
+ * @gpiod: gpio descriptor to gate this clock
+ * @flags: clock flags
+ */
+struct clk_hw *clk_hw_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags)
+{
+	if (num_parents != 2) {
+		pr_err("mux-clock %s must have 2 parents\n", name);
+		return ERR_PTR(-EINVAL);
+	}
+
+	return clk_register_gpio(dev, name, parent_names, num_parents,
+			gpiod, flags, &clk_gpio_mux_ops);
+}
+EXPORT_SYMBOL_GPL(clk_hw_register_gpio_mux);
+
+struct clk *clk_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags)
+{
+	struct clk_hw *hw;
+
+	hw = clk_hw_register_gpio_mux(dev, name, parent_names, num_parents,
+			gpiod, flags);
+	if (IS_ERR(hw))
+		return ERR_CAST(hw);
+	return hw->clk;
+}
+EXPORT_SYMBOL_GPL(clk_register_gpio_mux);
 
 static int gpio_clk_driver_probe(struct platform_device *pdev)
 {
-	struct device *dev = &pdev->dev;
-	struct device_node *node = dev->of_node;
-	const char *gpio_name;
+	struct device_node *node = pdev->dev.of_node;
+	const char **parent_names, *gpio_name;
 	unsigned int num_parents;
 	struct gpio_desc *gpiod;
-	struct clk_hw *hw;
+	struct clk *clk;
 	bool is_mux;
 	int ret;
 
-	is_mux = of_device_is_compatible(node, "gpio-mux-clock");
-
 	num_parents = of_clk_get_parent_count(node);
-	if (is_mux && num_parents != 2) {
-		dev_err(dev, "mux-clock must have 2 parents\n");
-		return -EINVAL;
+	if (num_parents) {
+		parent_names = devm_kcalloc(&pdev->dev, num_parents,
+					    sizeof(char *), GFP_KERNEL);
+		if (!parent_names)
+			return -ENOMEM;
+
+		of_clk_parent_fill(node, parent_names, num_parents);
+	} else {
+		parent_names = NULL;
 	}
 
+	is_mux = of_device_is_compatible(node, "gpio-mux-clock");
+
 	gpio_name = is_mux ? "select" : "enable";
-	gpiod = devm_gpiod_get(dev, gpio_name, GPIOD_OUT_LOW);
+	gpiod = devm_gpiod_get(&pdev->dev, gpio_name, GPIOD_OUT_LOW);
 	if (IS_ERR(gpiod)) {
 		ret = PTR_ERR(gpiod);
 		if (ret == -EPROBE_DEFER)
@@ -224,13 +275,16 @@ static int gpio_clk_driver_probe(struct platform_device *pdev)
 	}
 
 	if (is_mux)
-		hw = clk_hw_register_gpio_mux(dev, gpiod);
+		clk = clk_register_gpio_mux(&pdev->dev, node->name,
+				parent_names, num_parents, gpiod, 0);
 	else
-		hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod);
-	if (IS_ERR(hw))
-		return PTR_ERR(hw);
+		clk = clk_register_gpio_gate(&pdev->dev, node->name,
+				parent_names ?  parent_names[0] : NULL, gpiod,
+				CLK_SET_RATE_PARENT);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
 
-	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+	return of_clk_add_provider(node, of_clk_src_simple_get, clk);
 }
 
 static const struct of_device_id gpio_clk_match_table[] = {
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 4a537260f655..052be7d22f24 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -1306,6 +1306,44 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev,
 		unsigned long flags);
 void clk_hw_unregister_composite(struct clk_hw *hw);
 
+/**
+ * struct clk_gpio - gpio gated clock
+ *
+ * @hw:		handle between common and hardware-specific interfaces
+ * @gpiod:	gpio descriptor
+ *
+ * Clock with a gpio control for enabling and disabling the parent clock
+ * or switching between two parents by asserting or deasserting the gpio.
+ *
+ * Implements .enable, .disable and .is_enabled or
+ * .get_parent, .set_parent and .determine_rate depending on which clk_ops
+ * is used.
+ */
+struct clk_gpio {
+	struct clk_hw	hw;
+	struct gpio_desc *gpiod;
+};
+
+#define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw)
+
+extern const struct clk_ops clk_gpio_gate_ops;
+struct clk *clk_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags);
+struct clk_hw *clk_hw_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags);
+void clk_hw_unregister_gpio_gate(struct clk_hw *hw);
+
+extern const struct clk_ops clk_gpio_mux_ops;
+struct clk *clk_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags);
+struct clk_hw *clk_hw_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags);
+void clk_hw_unregister_gpio_mux(struct clk_hw *hw);
+
 struct clk *clk_register(struct device *dev, struct clk_hw *hw);
 struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

From: Russell King <[email-masked]>

Add the clock structure for the external SMC91x ethernet oscillator.
This oscillator has an enable signal which allows the oscillator to be
placed in low power mode when not required.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/neponset.c | 43 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/arch/arm/mach-sa1100/neponset.c b/arch/arm/mach-sa1100/neponset.c
index 0ef0ebbf31ac..36cf93df446f 100644
--- a/arch/arm/mach-sa1100/neponset.c
+++ b/arch/arm/mach-sa1100/neponset.c
@@ -2,10 +2,13 @@
 /*
  * linux/arch/arm/mach-sa1100/neponset.c
  */
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
 #include <linux/err.h>
 #include <linux/gpio/driver.h>
 #include <linux/gpio/gpio-reg.h>
 #include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
 #include <linux/init.h>
 #include <linux/ioport.h>
 #include <linux/irq.h>
@@ -79,6 +82,9 @@ struct neponset_drvdata {
 	struct platform_device *smc91x;
 	unsigned irq_base;
 	struct gpio_chip *gpio[4];
+	struct clk_hw *enet_osc_ck;
+	struct clk_hw *enet_osc_gate;
+	struct clk_lookup *enet_osc_cl;
 };
 
 static struct gpiod_lookup_table neponset_uart1_gpio_table = {
@@ -218,6 +224,38 @@ static int neponset_init_gpio(struct gpio_chip **gcp,
 	return 0;
 }
 
+static int neponset_init_enet(struct device *dev, struct neponset_drvdata *d)
+{
+	struct gpio_desc *gpio;
+	int ret;
+
+	d->enet_osc_ck = clk_hw_register_fixed_rate(dev, "nep_enet_osc",
+						    NULL, 0, 20000000);
+	if (IS_ERR(d->enet_osc_ck))
+		return PTR_ERR(d->enet_osc_ck);
+
+	gpio = gpio_to_desc(d->gpio[0]->base + 3);
+	d->enet_osc_gate = clk_hw_register_gpio_gate(dev, "nep_enet_gate",
+						     "nep_enet_osc", gpio, 0);
+	if (IS_ERR(d->enet_osc_gate)) {
+		ret = PTR_ERR(d->enet_osc_gate);
+		goto err;
+	}
+
+	d->enet_osc_cl = clkdev_hw_create(d->enet_osc_gate, NULL, "smc91x.0");
+	if (!d->enet_osc_cl) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	if (d->enet_osc_ck)
+		clk_hw_unregister_fixed_rate(d->enet_osc_ck);
+	return ret;
+}
+
 static struct sa1111_platform_data sa1111_info = {
 	.disable_devs	= SA1111_DEVID_PS2_MSE,
 };
@@ -337,6 +375,8 @@ static int neponset_probe(struct platform_device *dev)
 	gpiod_add_lookup_table(&neponset_uart3_gpio_table);
 	gpiod_add_lookup_table(&neponset_pcmcia_table);
 
+	neponset_init_enet(&dev->dev, d);
+
 	/*
 	 * We would set IRQ_GPIO25 to be a wake-up IRQ, but unfortunately
 	 * something on the Neponset activates this IRQ on sleep (eth?)
@@ -386,6 +426,9 @@ static void neponset_remove(struct platform_device *dev)
 	if (!IS_ERR(d->smc91x))
 		platform_device_unregister(d->smc91x);
 
+	clkdev_drop(d->enet_osc_cl);
+	clk_hw_unregister_fixed_rate(d->enet_osc_ck);
+
 	gpiod_remove_lookup_table(&neponset_pcmcia_table);
 	gpiod_remove_lookup_table(&neponset_uart3_gpio_table);
 	gpiod_remove_lookup_table(&neponset_uart1_gpio_table);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

The smc91x can be operated with either a directly attached crystal, or
an externally supplied clock.  When used with an externally supplied
clock, this clock may require some configuration or control.  Add calls
to the driver to lookup, enable and disable this optional clock.

Acked-by: Nicolas Pitre <[email-masked]>
Acked-by: David S. Miller <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/smsc/smc91x.c | 35 +++++++++++++++++++++++++++++++++--
 drivers/net/ethernet/smsc/smc91x.h |  1 +
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c
index 78ff3af7911a..fdce1f69f0bd 100644
--- a/drivers/net/ethernet/smsc/smc91x.c
+++ b/drivers/net/ethernet/smsc/smc91x.c
@@ -62,6 +62,7 @@ static const char version[] =
 #include <linux/irq.h>
 #include <linux/errno.h>
 #include <linux/ioport.h>
+#include <linux/clk.h>
 #include <linux/crc32.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
@@ -2274,6 +2275,15 @@ static int smc_drv_probe(struct platform_device *pdev)
 		}
 	}
 
+	/*
+	 * Some platforms drive the xtal input from an external clock
+	 * source which may require some control.  Request the clock,
+	 * but allow probing to continue if no clock is specified.
+	 */
+	lp->xtal = devm_clk_get_optional(&pdev->dev, NULL);
+	if (IS_ERR(lp->xtal))
+		return PTR_ERR(lp->xtal);
+
 #if IS_BUILTIN(CONFIG_OF)
 	match = of_match_device(of_match_ptr(smc91x_match), &pdev->dev);
 	if (match) {
@@ -2370,15 +2380,20 @@ static int smc_drv_probe(struct platform_device *pdev)
 	if (machine_is_assabet() && machine_has_neponset())
 		neponset_ncr_set(NCR_ENET_OSC_EN);
 #endif
+	if (lp->xtal) {
+		ret = clk_prepare_enable(lp->xtal);
+		if (ret)
+			goto out_release_attrib;
+	}
 	platform_set_drvdata(pdev, ndev);
 	ret = smc_enable_device(pdev);
 	if (ret)
-		goto out_release_attrib;
+		goto out_disable_xtal;
 
 	addr = ioremap(res->start, SMC_IO_EXTENT);
 	if (!addr) {
 		ret = -ENOMEM;
-		goto out_release_attrib;
+		goto out_disable_xtal;
 	}
 
 #ifdef CONFIG_ARCH_PXA
@@ -2402,6 +2417,9 @@ static int smc_drv_probe(struct platform_device *pdev)
 	iounmap(addr);
  out_release_attrib:
 	smc_release_attrib(pdev, ndev);
+ out_disable_xtal:
+	if (lp->xtal)
+		clk_disable_unprepare(lp->xtal);
  out_release_io:
 	release_mem_region(res->start, SMC_IO_EXTENT);
  out_free_netdev:
@@ -2436,6 +2454,9 @@ static void smc_drv_remove(struct platform_device *pdev)
 		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	release_mem_region(res->start, SMC_IO_EXTENT);
 
+	if (lp->xtal)
+		clk_disable_unprepare(lp->xtal);
+
 	free_netdev(ndev);
 }
 
@@ -2445,9 +2466,14 @@ static int smc_drv_suspend(struct device *dev)
 
 	if (ndev) {
 		if (netif_running(ndev)) {
+			struct smc_local *lp = netdev_priv(ndev);
+
 			netif_device_detach(ndev);
 			smc_shutdown(ndev);
 			smc_phy_powerdown(ndev);
+
+			if (lp->xtal)
+				clk_disable_unprepare(lp->xtal);
 		}
 	}
 	return 0;
@@ -2460,6 +2486,11 @@ static int smc_drv_resume(struct device *dev)
 
 	if (ndev) {
 		struct smc_local *lp = netdev_priv(ndev);
+		if (lp->xtal) {
+			int ret = clk_prepare_enable(lp->xtal);
+			if (ret)
+				return ret;
+		}
 		smc_enable_device(pdev);
 		if (netif_running(ndev)) {
 			smc_reset(ndev);
diff --git a/drivers/net/ethernet/smsc/smc91x.h b/drivers/net/ethernet/smsc/smc91x.h
index 46eee747c699..c73e38eb3911 100644
--- a/drivers/net/ethernet/smsc/smc91x.h
+++ b/drivers/net/ethernet/smsc/smc91x.h
@@ -205,6 +205,7 @@ struct smc_local {
 
 	struct gpio_desc *power_gpio;
 	struct gpio_desc *reset_gpio;
+	struct clk *xtal;
 
 	/* version/revision of the SMC91x chip */
 	int	version;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Remove the neponset specific oscillator hack from the smc91x driver,
relying instead on the gpio controls provided.

Acked-by: Nicolas Pitre <[email-masked]>
Acked-by: David S. Miller <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/smsc/smc91x.c | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c
index fdce1f69f0bd..d36b60f3162f 100644
--- a/drivers/net/ethernet/smsc/smc91x.c
+++ b/drivers/net/ethernet/smsc/smc91x.c
@@ -80,11 +80,6 @@ static const char version[] =
 
 #include "smc91x.h"
 
-#if defined(CONFIG_ASSABET_NEPONSET)
-#include <mach/assabet.h>
-#include <mach/neponset.h>
-#endif
-
 #ifndef SMC_NOWAIT
 # define SMC_NOWAIT		0
 #endif
@@ -2376,10 +2371,6 @@ static int smc_drv_probe(struct platform_device *pdev)
 	ret = smc_request_attrib(pdev, ndev);
 	if (ret)
 		goto out_release_io;
-#if defined(CONFIG_ASSABET_NEPONSET)
-	if (machine_is_assabet() && machine_has_neponset())
-		neponset_ncr_set(NCR_ENET_OSC_EN);
-#endif
 	if (lp->xtal) {
 		ret = clk_prepare_enable(lp->xtal);
 		if (ret)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Remove the legacy neponset_ncr_*() interfaces and definitions, which
leaves the header empty apart from the header and footer.  Remove the
header as well.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/include/mach/neponset.h | 31 ----------------------------
 arch/arm/mach-sa1100/neponset.c              | 14 +------------
 2 files changed, 1 insertion(+), 44 deletions(-)
 delete mode 100644 arch/arm/mach-sa1100/include/mach/neponset.h

diff --git a/arch/arm/mach-sa1100/include/mach/neponset.h b/arch/arm/mach-sa1100/include/mach/neponset.h
deleted file mode 100644
index 18411cfdfe8e..000000000000
--- a/arch/arm/mach-sa1100/include/mach/neponset.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * arch/arm/mach-sa1100/include/mach/neponset.h
- *
- * Created 2000/06/05 by Nicolas Pitre <[email-masked]>
- *
- * This file contains the hardware specific definitions for Assabet
- * Only include this file from SA1100-specific files.
- *
- * 2000/05/23 John Dorsey <[email-masked]>
- *      Definitions for Neponset added.
- */
-#ifndef __ASM_ARCH_NEPONSET_H
-#define __ASM_ARCH_NEPONSET_H
-
-/*
- * Neponset definitions: 
- */
-#define NCR_GP01_OFF		(1<<0)
-#define NCR_TP_PWR_EN		(1<<1)
-#define NCR_MS_PWR_EN		(1<<2)
-#define NCR_ENET_OSC_EN		(1<<3)
-#define NCR_SPI_KB_WK_UP	(1<<4)
-#define NCR_A0VPP		(1<<5)
-#define NCR_A1VPP		(1<<6)
-
-void neponset_ncr_frob(unsigned int, unsigned int);
-#define neponset_ncr_set(v)	neponset_ncr_frob(0, v)
-#define neponset_ncr_clear(v)	neponset_ncr_frob(v, 0)
-
-#endif
diff --git a/arch/arm/mach-sa1100/neponset.c b/arch/arm/mach-sa1100/neponset.c
index 36cf93df446f..3b3bba8d1640 100644
--- a/arch/arm/mach-sa1100/neponset.c
+++ b/arch/arm/mach-sa1100/neponset.c
@@ -27,7 +27,6 @@
 
 #include <mach/hardware.h>
 #include <mach/assabet.h>
-#include <mach/neponset.h>
 #include <mach/irqs.h>
 
 #define NEP_IRQ_SMC91X	0
@@ -50,6 +49,7 @@
 #define IRR_USAR	(1 << 1)
 #define IRR_SA1111	(1 << 2)
 
+#define NCR_GP01_OFF	BIT(0)
 #define NCR_NGPIO	7
 #define MDM_CTL0_NGPIO	4
 #define MDM_CTL1_NGPIO	6
@@ -126,18 +126,6 @@ static struct gpiod_lookup_table neponset_pcmcia_table = {
 
 static struct neponset_drvdata *nep;
 
-void neponset_ncr_frob(unsigned int mask, unsigned int val)
-{
-	struct neponset_drvdata *n = nep;
-	unsigned long m = mask, v = val;
-
-	if (nep)
-		n->gpio[0]->set_multiple(n->gpio[0], &m, &v);
-	else
-		WARN(1, "nep unset\n");
-}
-EXPORT_SYMBOL(neponset_ncr_frob);
-
 /*
  * Install handler for Neponset IRQ.  Note that we have to loop here
  * since the ETHERNET and USAR IRQs are level based, and we need to

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Convert Neponset to use an irq domain rather than statically allocated
interrupt numbers, and switch to using the IRQ domain to lookup the
Linux interrupt number when decoding an interrupt.  Since interrupts
remain disabled across an interrupt handler, we can simplify the
handler code somewhat.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/neponset.c | 113 ++++++++++++++++++++++------------------
 1 file changed, 62 insertions(+), 51 deletions(-)

diff --git a/arch/arm/mach-sa1100/neponset.c b/arch/arm/mach-sa1100/neponset.c
index 3b3bba8d1640..8ffd3fda9470 100644
--- a/arch/arm/mach-sa1100/neponset.c
+++ b/arch/arm/mach-sa1100/neponset.c
@@ -78,9 +78,11 @@ static const char *neponset_aud_names[] = {
 
 struct neponset_drvdata {
 	void __iomem *base;
+	struct device *dev;
 	struct platform_device *sa1111;
 	struct platform_device *smc91x;
 	unsigned irq_base;
+	struct irq_domain *irq_domain;
 	struct gpio_chip *gpio[4];
 	struct clk_hw *enet_osc_ck;
 	struct clk_hw *enet_osc_gate;
@@ -126,6 +128,18 @@ static struct gpiod_lookup_table neponset_pcmcia_table = {

+static void neponset_handle_irq(struct neponset_drvdata *d, int irq)
+{
+	int virq = irq_find_mapping(d->irq_domain, irq);
+
+	if (!virq) {
+		dev_warn(d->dev, "spurious irq detected, hwirq %d irq %d\n",
+			 irq, virq);
+		return;
+	}
+	generic_handle_irq(virq);
+}
+
 /*
  * Install handler for Neponset IRQ.  Note that we have to loop here
  * since the ETHERNET and USAR IRQs are level based, and we need to
@@ -135,12 +149,11 @@ static struct neponset_drvdata *nep;
 static void neponset_irq_handler(struct irq_desc *desc)
 {
 	struct neponset_drvdata *d = irq_desc_get_handler_data(desc);
-	unsigned int irr;
+	unsigned long irr;
+	int irq;
 
 	while (1) {
-		/*
-		 * Acknowledge the parent IRQ.
-		 */
+		// Acknowledge the parent IRQ before reading the IRR.
 		desc->irq_data.chip->irq_ack(&desc->irq_data);
 
 		/*
@@ -151,36 +164,12 @@ static void neponset_irq_handler(struct irq_desc *desc)
 		irr = readb_relaxed(d->base + IRR);
 		irr ^= IRR_ETHERNET | IRR_USAR;
 
+		// If no pending interrupts, then we're done.
 		if ((irr & (IRR_ETHERNET | IRR_USAR | IRR_SA1111)) == 0)
 			break;
 
-		/*
-		 * Since there is no individual mask, we have to
-		 * mask the parent IRQ.  This is safe, since we'll
-		 * recheck the register for any pending IRQs.
-		 */
-		if (irr & (IRR_ETHERNET | IRR_USAR)) {
-			desc->irq_data.chip->irq_mask(&desc->irq_data);
-
-			/*
-			 * Ack the interrupt now to prevent re-entering
-			 * this neponset handler.  Again, this is safe
-			 * since we'll check the IRR register prior to
-			 * leaving.
-			 */
-			desc->irq_data.chip->irq_ack(&desc->irq_data);
-
-			if (irr & IRR_ETHERNET)
-				generic_handle_irq(d->irq_base + NEP_IRQ_SMC91X);
-
-			if (irr & IRR_USAR)
-				generic_handle_irq(d->irq_base + NEP_IRQ_USAR);
-
-			desc->irq_data.chip->irq_unmask(&desc->irq_data);
-		}
-
-		if (irr & IRR_SA1111)
-			generic_handle_irq(d->irq_base + NEP_IRQ_SA1111);
+		for_each_set_bit(irq, &irr, 8)
+			neponset_handle_irq(d, irq);
 	}
 }
 
@@ -196,6 +185,44 @@ static struct irq_chip nochip = {
 	.irq_unmask = nochip_noop,
 };
 
+static int neponset_irqdomain_map(struct irq_domain *d, unsigned int irq,
+	irq_hw_number_t hwirq)
+{
+	irq_set_chip_and_handler(irq, &nochip, handle_simple_irq);
+	irq_clear_status_flags(irq, IRQ_NOREQUEST);
+
+	return 0;
+}
+
+static const struct irq_domain_ops nep_irqdomain_ops = {
+	.map = neponset_irqdomain_map,
+	.xlate = irq_domain_xlate_onetwocell,
+};
+
+static int neponset_init_irr(struct device *dev, struct neponset_drvdata *d, int irq)
+{
+	int ret;
+
+	ret = irq_alloc_descs(-1, IRQ_BOARD_START, NEP_IRQ_NR, -1);
+	if (ret <= 0) {
+		dev_err(dev, "unable to allocate %u irqs: %d\n",
+			NEP_IRQ_NR, ret);
+		if (ret == 0)
+			ret = -ENOMEM;
+		return ret;
+	}
+
+	d->irq_base = ret;
+
+	d->irq_domain = irq_domain_add_simple(NULL, NEP_IRQ_NR, ret,
+					      &nep_irqdomain_ops, d);
+
+	irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
+	irq_set_chained_handler_and_data(irq, neponset_irq_handler, d);
+
+	return 0;
+}
+
 static int neponset_init_gpio(struct gpio_chip **gcp,
 	struct device *dev, const char *label, void __iomem *reg,
 	unsigned num, bool in, const char *const * names)
@@ -308,6 +335,8 @@ static int neponset_probe(struct platform_device *dev)
 		goto err_alloc;
 	}
 
+	d->dev = &dev->dev;
+
 	d->base = ioremap(nep_res->start, SZ_4K);
 	if (!d->base) {
 		ret = -ENOMEM;
@@ -321,27 +350,9 @@ static int neponset_probe(struct platform_device *dev)
 		goto err_id;
 	}
 
-	ret = irq_alloc_descs(-1, IRQ_BOARD_START, NEP_IRQ_NR, -1);
-	if (ret <= 0) {
-		dev_err(&dev->dev, "unable to allocate %u irqs: %d\n",
-			NEP_IRQ_NR, ret);
-		if (ret == 0)
-			ret = -ENOMEM;
+	ret = neponset_init_irr(&dev->dev, d, irq);
+	if (ret < 0)
 		goto err_irq_alloc;
-	}
-
-	d->irq_base = ret;
-
-	irq_set_chip_and_handler(d->irq_base + NEP_IRQ_SMC91X, &nochip,
-		handle_simple_irq);
-	irq_clear_status_flags(d->irq_base + NEP_IRQ_SMC91X, IRQ_NOREQUEST | IRQ_NOPROBE);
-	irq_set_chip_and_handler(d->irq_base + NEP_IRQ_USAR, &nochip,
-		handle_simple_irq);
-	irq_clear_status_flags(d->irq_base + NEP_IRQ_USAR, IRQ_NOREQUEST | IRQ_NOPROBE);
-	irq_set_chip(d->irq_base + NEP_IRQ_SA1111, &nochip);
-
-	irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
-	irq_set_chained_handler_and_data(irq, neponset_irq_handler, d);
 
 	/* Disable GPIO 0/1 drivers so the buttons work on the Assabet */
 	writeb_relaxed(NCR_GP01_OFF, d->base + NCR_0);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Use the device managed devm_kzalloc() to allocate the neponset device
private memory rather than manually allocating and freeing the data.
This simplifies the cleanup handling a little.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/neponset.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/arch/arm/mach-sa1100/neponset.c b/arch/arm/mach-sa1100/neponset.c
index 8ffd3fda9470..186da44d554e 100644
--- a/arch/arm/mach-sa1100/neponset.c
+++ b/arch/arm/mach-sa1100/neponset.c
@@ -319,20 +319,20 @@ static int neponset_probe(struct platform_device *dev)
 
 	irq = ret = platform_get_irq(dev, 0);
 	if (ret < 0)
-		goto err_alloc;
+		goto err;
 
 	nep_res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 	smc91x_res = platform_get_resource(dev, IORESOURCE_MEM, 1);
 	sa1111_res = platform_get_resource(dev, IORESOURCE_MEM, 2);
 	if (!nep_res || !smc91x_res || !sa1111_res) {
 		ret = -ENXIO;
-		goto err_alloc;
+		goto err;
 	}
 
-	d = kzalloc(sizeof(*d), GFP_KERNEL);
+	d = devm_kzalloc(&dev->dev, sizeof(*d), GFP_KERNEL);
 	if (!d) {
 		ret = -ENOMEM;
-		goto err_alloc;
+		goto err;
 	}
 
 	d->dev = &dev->dev;
@@ -340,7 +340,7 @@ static int neponset_probe(struct platform_device *dev)
 	d->base = ioremap(nep_res->start, SZ_4K);
 	if (!d->base) {
 		ret = -ENOMEM;
-		goto err_ioremap;
+		goto err;
 	}
 
 	if (readb_relaxed(d->base + WHOAMI) != 0x11) {
@@ -409,9 +409,7 @@ static int neponset_probe(struct platform_device *dev)
  err_irq_alloc:
  err_id:
 	iounmap(d->base);
- err_ioremap:
-	kfree(d);
- err_alloc:
+ err:
 	return ret;
 }
 
@@ -436,7 +434,6 @@ static void neponset_remove(struct platform_device *dev)
 	irq_free_descs(d->irq_base, NEP_IRQ_NR);
 	nep = NULL;
 	iounmap(d->base);
-	kfree(d);
 }
 
 #ifdef CONFIG_PM_SLEEP

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Move the Assabet BCR register address definition out of the header file
and into the board code, converting it to a iomem pointer rather than a
volatile accessor.  Also update the early IO maps to use the appropriate
definition.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c              | 7 +++++--
 arch/arm/mach-sa1100/include/mach/assabet.h | 3 ---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 2b833aa0212b..3085c42761c1 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -46,6 +46,9 @@
 
 #include "generic.h"
 
+#define ASSABET_BCR_BASE  0xf1000000
+#define ASSABET_BCR       IOMEM(ASSABET_BCR_BASE)
+
 #define ASSABET_BCR_DB1110 \
 	(ASSABET_BCR_SPK_OFF    | \
 	 ASSABET_BCR_LED_GREEN  | ASSABET_BCR_LED_RED   | \
@@ -665,7 +668,7 @@ static struct sa1100_port_fns assabet_port_fns __initdata = {
 
 static struct map_desc assabet_io_desc[] __initdata = {
   	{	/* Board Control Register */
-		.virtual	=  0xf1000000,
+		.virtual	= ASSABET_BCR_BASE,
 		.pfn		= __phys_to_pfn(0x12000000),
 		.length		= 0x00100000,
 		.type		= MT_DEVICE
@@ -726,7 +729,7 @@ static void __init assabet_init_irq(void)
 	 *
 	 * This must precede any driver calls to BCR_set() or BCR_clear().
 	 */
-	assabet_init_gpio((void *)&ASSABET_BCR, def_val);
+	assabet_init_gpio(ASSABET_BCR, def_val);
 }
 
 MACHINE_START(ASSABET, "Intel-Assabet")
diff --git a/arch/arm/mach-sa1100/include/mach/assabet.h b/arch/arm/mach-sa1100/include/mach/assabet.h
index 641a961653af..380a7640facf 100644
--- a/arch/arm/mach-sa1100/include/mach/assabet.h
+++ b/arch/arm/mach-sa1100/include/mach/assabet.h
@@ -35,9 +35,6 @@ extern unsigned long SCR_value;
 
 /* Board Control Register */
 
-#define ASSABET_BCR_BASE  0xf1000000
-#define ASSABET_BCR (*(volatile unsigned int *)(ASSABET_BCR_BASE))
-
 #define ASSABET_BCR_CF_PWR	(1<<0)	/* Compact Flash Power (1 = 3.3v, 0 = off) */
 #define ASSABET_BCR_CF_RST	(1<<1)	/* Compact Flash Reset (1 = power up reset) */
 #define ASSABET_BCR_NGFX_RST	(1<<1)	/* Graphics Accelerator Reset (0 = hold reset) */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

The assabet BSR register is treated as a GPIO register now, so the
register address and bit definitions are redundant.  Remove these
definitions.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/include/mach/assabet.h | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/arch/arm/mach-sa1100/include/mach/assabet.h b/arch/arm/mach-sa1100/include/mach/assabet.h
index 380a7640facf..829fc1839d35 100644
--- a/arch/arm/mach-sa1100/include/mach/assabet.h
+++ b/arch/arm/mach-sa1100/include/mach/assabet.h
@@ -72,19 +72,6 @@ extern void assabet_uda1341_reset(int set);
 #define ASSABET_BCR_set(x)	ASSABET_BCR_frob((x), (x))
 #define ASSABET_BCR_clear(x)	ASSABET_BCR_frob((x), 0)
 
-#define ASSABET_BSR_BASE	0xf1000000
-#define ASSABET_BSR (*(volatile unsigned int*)(ASSABET_BSR_BASE))
-
-#define ASSABET_BSR_RS232_VALID	(1 << 24)
-#define ASSABET_BSR_COM_DCD	(1 << 25)
-#define ASSABET_BSR_COM_CTS	(1 << 26)
-#define ASSABET_BSR_COM_DSR	(1 << 27)
-#define ASSABET_BSR_RAD_CTS	(1 << 28)
-#define ASSABET_BSR_RAD_DSR	(1 << 29)
-#define ASSABET_BSR_RAD_DCD	(1 << 30)
-#define ASSABET_BSR_RAD_RI	(1 << 31)
-
-
 /* GPIOs (bitmasks) for which the generic definition doesn't say much */
 #define ASSABET_GPIO_RADIO_IRQ		GPIO_GPIO (14)	/* Radio interrupt request  */
 #define ASSABET_GPIO_PS_MODE_SYNC	GPIO_GPIO (16)	/* Power supply mode/sync   */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:19 +0100

Nothing outside of assabet.c needs the board SCR register definitions,
nor does it need machine_has_neponset().  Remove these definitions.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c              | 19 +++++++++++++++++--
 arch/arm/mach-sa1100/include/mach/assabet.h | 20 --------------------
 2 files changed, 17 insertions(+), 22 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 3085c42761c1..0533107867ed 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -46,6 +46,22 @@
 
 #include "generic.h"
 
+enum {
+	/* System Configuration Register flags */
+	SCR_SDRAM_LOW	= BIT(2),  /* SDRAM size (low bit) */
+	SCR_SDRAM_HIGH	= BIT(3),  /* SDRAM size (high bit) */
+	SCR_FLASH_LOW	= BIT(4),  /* Flash size (low bit) */
+	SCR_FLASH_HIGH	= BIT(5),  /* Flash size (high bit) */
+	SCR_GFX		= BIT(8),  /* Graphics Accelerator (0=present) */
+	SCR_SA1111	= BIT(9),  /* Neponset (0=present) */
+};
+
+#ifdef CONFIG_ASSABET_NEPONSET
+#define machine_has_neponset()  ((SCR_value & SCR_SA1111) == 0)
+#else
+#define machine_has_neponset()	(0)
+#endif
+
 #define ASSABET_BCR_BASE  0xf1000000
 #define ASSABET_BCR       IOMEM(ASSABET_BCR_BASE)
 
@@ -62,8 +78,7 @@
 	 ASSABET_BCR_CF_BUS_OFF | ASSABET_BCR_STEREO_LB | \
 	 ASSABET_BCR_IRDA_MD0   | ASSABET_BCR_CF_RST)
 
-unsigned long SCR_value = ASSABET_SCR_INIT;
-EXPORT_SYMBOL(SCR_value);
+static unsigned long SCR_value = ~0UL;
 
 static struct gpio_chip *assabet_bcr_gc;
 
diff --git a/arch/arm/mach-sa1100/include/mach/assabet.h b/arch/arm/mach-sa1100/include/mach/assabet.h
index 829fc1839d35..53f05fced930 100644
--- a/arch/arm/mach-sa1100/include/mach/assabet.h
+++ b/arch/arm/mach-sa1100/include/mach/assabet.h
@@ -13,26 +13,6 @@
 #ifndef __ASM_ARCH_ASSABET_H
 #define __ASM_ARCH_ASSABET_H
 
-
-/* System Configuration Register flags */
-
-#define ASSABET_SCR_SDRAM_LOW	(1<<2)	/* SDRAM size (low bit) */
-#define ASSABET_SCR_SDRAM_HIGH	(1<<3)	/* SDRAM size (high bit) */
-#define ASSABET_SCR_FLASH_LOW	(1<<4)	/* Flash size (low bit) */
-#define ASSABET_SCR_FLASH_HIGH	(1<<5)	/* Flash size (high bit) */
-#define ASSABET_SCR_GFX		(1<<8)	/* Graphics Accelerator (0 = present) */
-#define ASSABET_SCR_SA1111	(1<<9)	/* Neponset (0 = present) */
-
-#define ASSABET_SCR_INIT	-1
-
-extern unsigned long SCR_value;
-
-#ifdef CONFIG_ASSABET_NEPONSET
-#define machine_has_neponset()  ((SCR_value & ASSABET_SCR_SA1111) == 0)
-#else
-#define machine_has_neponset()	(0)
-#endif
-
 /* Board Control Register */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Move GPIO board definitions to assabet.c

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c              | 8 ++++++++
 arch/arm/mach-sa1100/include/mach/assabet.h | 8 --------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 0533107867ed..fc21a8da7b3b 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -54,6 +54,14 @@ enum {
 	SCR_FLASH_HIGH	= BIT(5),  /* Flash size (high bit) */
 	SCR_GFX		= BIT(8),  /* Graphics Accelerator (0=present) */
 	SCR_SA1111	= BIT(9),  /* Neponset (0=present) */
+
+	/* GPIOs (bitmasks) for which the generic definition doesn't say much */
+	GPIO_RADIO_IRQ		= GPIO_GPIO(14), /* Radio interrupt request  */
+	GPIO_PS_MODE_SYNC	= GPIO_GPIO(16), /* Power supply mode/sync   */
+	GPIO_STEREO_64FS_CLK	= GPIO_GPIO(19), /* SSP UDA1341 clock input  */
+	GPIO_GFX_IRQ		= GPIO_GPIO(24), /* Graphics IRQ */
+	GPIO_BATT_LOW		= GPIO_GPIO(26), /* Low battery */
+	GPIO_SYS_CLK		= GPIO_GPIO(27),
 };
 
 #ifdef CONFIG_ASSABET_NEPONSET
diff --git a/arch/arm/mach-sa1100/include/mach/assabet.h b/arch/arm/mach-sa1100/include/mach/assabet.h
index 53f05fced930..2022fcae7791 100644
--- a/arch/arm/mach-sa1100/include/mach/assabet.h
+++ b/arch/arm/mach-sa1100/include/mach/assabet.h
@@ -52,12 +52,4 @@ extern void assabet_uda1341_reset(int set);
 #define ASSABET_BCR_set(x)	ASSABET_BCR_frob((x), (x))
 #define ASSABET_BCR_clear(x)	ASSABET_BCR_frob((x), 0)
 
-/* GPIOs (bitmasks) for which the generic definition doesn't say much */
-#define ASSABET_GPIO_RADIO_IRQ		GPIO_GPIO (14)	/* Radio interrupt request  */
-#define ASSABET_GPIO_PS_MODE_SYNC	GPIO_GPIO (16)	/* Power supply mode/sync   */
-#define ASSABET_GPIO_STEREO_64FS_CLK	GPIO_GPIO (19)	/* SSP UDA1341 clock input  */
-#define ASSABET_GPIO_GFX_IRQ		GPIO_GPIO (24)	/* Graphics IRQ */
-#define ASSABET_GPIO_BATT_LOW		GPIO_GPIO (26)	/* Low battery */
-#define ASSABET_GPIO_RCLK		GPIO_GPIO (26)	/* CCLK/2  */
-
 #endif

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index fc21a8da7b3b..93868167de4b 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -545,8 +545,8 @@ static void __init assabet_init(void)
 	/*
 	 * Ensure that the power supply is in "high power" mode.
 	 */
-	GPSR = GPIO_GPIO16;
-	GPDR |= GPIO_GPIO16;
+	GPSR = GPIO_PS_MODE_SYNC;
+	GPDR |= GPIO_PS_MODE_SYNC;
 
 	/*
 	 * Ensure that these pins are set as outputs and are driving
@@ -558,12 +558,12 @@ static void __init assabet_init(void)
 	GPDR |= GPIO_SSP_TXD | GPIO_SSP_SCLK | GPIO_SSP_SFRM;
 
 	/*
-	 * Also set GPIO27 as an output; this is used to clock UART3
-	 * via the FPGA and as otherwise has no pullups or pulldowns,
-	 * so stop it floating.
+	 * Also set GPIO27 as an output; this can be used supply an
+	 * external clock via the FPGA and GPIO18 to clock UART3 and
+	 * radio. It has no pullups or pulldowns, so stop it floating.
 	 */
-	GPCR = GPIO_GPIO27;
-	GPDR |= GPIO_GPIO27;
+	GPCR = GPIO_SYS_CLK;
+	GPDR |= GPIO_SYS_CLK;
 
 	/*
 	 * Set up registers for sleep mode.

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

From: Russell King <[email-masked]>

The ASoC generic dmaengine support takes a filter data parameter via
struct snd_dmaengine_dai_dma_data.  However, rather than passing the
filter data to the DMA engine request function, it passed the
struct snd_dmaengine_dai_dma_data itself.  Fix this.

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/soc-generic-dmaengine-pcm.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c
index a63e942fdc0b..6767d80dc489 100644
--- a/sound/soc/soc-generic-dmaengine-pcm.c
+++ b/sound/soc/soc-generic-dmaengine-pcm.c
@@ -185,14 +185,14 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel(
 		return NULL;
 	}
 
-	dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
-
 	if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0])
 		return pcm->chan[0];
 
 	if (pcm->config->compat_request_channel)
 		return pcm->config->compat_request_channel(rtd, substream);
 
+	dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
+
 	return snd_dmaengine_pcm_request_channel(pcm->config->compat_filter_fn,
 						 dma_data->filter_data);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Add a generic DMA engine platform driver to ASoC.  This allows a range
of DMA engines to be used to transfer data to a CPUs audio interface
without having to rewrite this code many times.

This driver supports both DMA_SLAVE and DMA_CYCLIC channels, preferring
DMA_CYCLIC channels over DMA_SLAVE channels.

Signed-off-by: Russell King <[email-masked]>
---
 include/sound/soc-dmaengine.h |  26 ++
 sound/soc/Kconfig             |   6 +
 sound/soc/Makefile            |   2 +
 sound/soc/soc-dmaengine.c     | 591 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 625 insertions(+)
 create mode 100644 include/sound/soc-dmaengine.h
 create mode 100644 sound/soc/soc-dmaengine.c

diff --git a/include/sound/soc-dmaengine.h b/include/sound/soc-dmaengine.h
new file mode 100644
index 000000000000..b435133de04d
--- /dev/null
+++ b/include/sound/soc-dmaengine.h
@@ -0,0 +1,26 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _LINUX_SND_SOC_DMAENGINE_H
+#define _LINUX_SND_SOC_DMAENGINE_H
+
+#include <linux/dmaengine.h>
+
+struct soc_dma_config {
+	dma_filter_fn filter;
+	void *data;
+
+	dma_addr_t reg;
+	enum dma_slave_buswidth width;
+	u32 maxburst;
+	size_t align;
+	size_t fifo_size;
+};
+
+#endif
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index a52afb423b46..0ae5f1f763f2 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -26,6 +26,9 @@ if SND_SOC
 config SND_SOC_AC97_BUS
 	bool
 
+config SND_SOC_DMAENGINE_PCM
+	bool
+
 config SND_SOC_GENERIC_DMAENGINE_PCM
 	bool
 	select SND_DMAENGINE_PCM
@@ -34,6 +37,9 @@ config SND_SOC_COMPRESS
 	bool
 	select SND_COMPRESS_OFFLOAD
 
+config SND_SOC_DMAENGINE
+	tristate
+
 config SND_SOC_TOPOLOGY
 	bool
 	select SND_DYNAMIC_MINORS
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index fd61847dd1eb..38e1d3255dac 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -2,6 +2,7 @@
 snd-soc-core-y := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o
 snd-soc-core-y += soc-pcm.o soc-devres.o soc-ops.o soc-link.o soc-card.o
 snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
+snd-soc-dmaengine-objs := soc-dmaengine.o
 
 ifneq ($(CONFIG_SND_SOC_TOPOLOGY),)
 snd-soc-core-y += soc-topology.o
@@ -36,6 +37,7 @@ endif
 obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o
 
 obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
+obj-$(CONFIG_SND_SOC_DMAENGINE) += snd-soc-dmaengine.o
 obj-$(CONFIG_SND_SOC)	+= codecs/
 obj-$(CONFIG_SND_SOC)	+= generic/
 obj-$(CONFIG_SND_SOC)	+= apple/
diff --git a/sound/soc/soc-dmaengine.c b/sound/soc/soc-dmaengine.c
new file mode 100644
index 000000000000..4b1bfce9e641
--- /dev/null
+++ b/sound/soc/soc-dmaengine.c
@@ -0,0 +1,591 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * We expect the DMA engine to give accurate residue information,
+ * returning the number of bytes left to complete to the requested
+ * cookie.  We also expect the DMA engine to be able to resume
+ * submitted descriptors after a suspend/resume cycle.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <sound/core.h>
+#include <sound/soc-dmaengine.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define BUFFER_SIZE_MAX		65536
+#define PERIOD_SIZE_MIN		32
+#define PERIOD_SIZE_MAX		16384
+#define PERIODS_MIN		2
+#define PERIODS_MAX		256
+
+struct buf_info {
+	struct scatterlist sg;
+	dma_cookie_t cookie;
+};
+
+struct soc_dma_chan {
+	const struct soc_dma_config *conf;
+	spinlock_t lock;
+	struct dma_chan *chan;
+	struct dma_slave_config cfg;
+	enum dma_transfer_direction dir;
+	unsigned nr_periods;
+	unsigned sg_index;
+	unsigned stopped;
+	unsigned cyclic;
+	dma_cookie_t cyclic_cookie;
+	struct buf_info buf[PERIODS_MAX];
+};
+
+struct soc_dma_info {
+	struct soc_dma_chan *chan[2];
+};
+
+static const struct snd_pcm_hardware soc_dmaengine_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.period_bytes_min	= PERIOD_SIZE_MIN,
+	.period_bytes_max	= PERIOD_SIZE_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= PERIODS_MAX,
+	.buffer_bytes_max	= BUFFER_SIZE_MAX,
+};
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+	struct soc_dma_chan *dma);
+
+static void soc_dmaengine_callback(void *data)
+{
+	struct snd_pcm_substream *substream = data;
+	struct soc_dma_chan *dma = substream->runtime->private_data;
+	int ret;
+
+	snd_pcm_period_elapsed(substream);
+
+	if (!dma->stopped && !dma->cyclic) {
+		spin_lock(&dma->lock);
+		ret = soc_dmaengine_submit(substream, dma);
+		spin_unlock(&dma->lock);
+
+		if (ret == 0)
+			dma_async_issue_pending(dma->chan);
+		else
+			pr_err("%s: failed to submit next DMA buffer\n", __func__);
+	}
+}
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+	struct soc_dma_chan *dma)
+{
+	struct dma_async_tx_descriptor *tx;
+	struct dma_chan *ch = dma->chan;
+	struct buf_info *buf;
+	unsigned sg_index;
+
+	sg_index = dma->sg_index;
+
+	buf = &dma->buf[sg_index];
+
+	tx = dmaengine_prep_slave_sg(ch, &buf->sg, 1, dma->dir,
+		DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (tx) {
+		tx->callback = soc_dmaengine_callback;
+		tx->callback_param = substream;
+
+		buf->cookie = dmaengine_submit(tx);
+
+		sg_index++;
+		if (sg_index >= dma->nr_periods)
+			sg_index = 0;
+		dma->sg_index = sg_index;
+	}
+
+	return tx ? 0 : -ENOMEM;
+}
+
+static int soc_dmaengine_start(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	unsigned long flags;
+	unsigned i;
+	int ret = 0;
+
+	if (!dma->cyclic) {
+		spin_lock_irqsave(&dma->lock, flags);
+		for (i = 0; i < dma->nr_periods; i++) {
+			ret = soc_dmaengine_submit(substream, dma);
+			if (ret)
+				break;
+		}
+		spin_unlock_irqrestore(&dma->lock, flags);
+		if (ret == 0) {
+			dma->stopped = 0;
+			dma_async_issue_pending(dma->chan);
+		} else {
+			dma->stopped = 1;
+			dmaengine_terminate_all(dma->chan);
+		}
+	} else {
+		struct dma_async_tx_descriptor *tx;
+
+		tx = dmaengine_prep_dma_cyclic(dma->chan, runtime->dma_addr,
+			snd_pcm_lib_buffer_bytes(substream),
+			snd_pcm_lib_period_bytes(substream), dma->dir,
+			DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+		if (tx) {
+			tx->callback = soc_dmaengine_callback;
+			tx->callback_param = substream;
+
+			dma->cyclic_cookie = dmaengine_submit(tx);
+			dma_async_issue_pending(dma->chan);
+		}
+	}
+
+	return ret;
+}
+
+static int soc_dmaengine_stop(struct snd_pcm_substream *substream)
+{
+	struct soc_dma_chan *dma = substream->runtime->private_data;
+
+	dma->stopped = 1;
+	dmaengine_terminate_all(dma->chan);
+
+	return 0;
+}
+
+static int soc_dmaengine_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct soc_dma_info *info = snd_soc_platform_get_drvdata(rtd->platform);
+	struct soc_dma_chan *dma = info->chan[substream->stream];
+	int ret = 0;
+
+	if (!dma)
+		return -EINVAL;
+
+	runtime->hw = soc_dmaengine_hardware;
+	runtime->hw.fifo_size = dma->conf->fifo_size;
+
+	if (dma->conf->align) {
+		/*
+		 * FIXME: Ideally, there should be some way to query
+		 * this from the DMA engine itself.
+		 *
+		 * It would also be helpful to know the maximum size
+		 * a scatterlist entry can be to set the period size.
+		 */
+		ret = snd_pcm_hw_constraint_step(runtime, 0,
+			SNDRV_PCM_HW_PARAM_PERIOD_BYTES, dma->conf->align);
+		if (ret)
+			goto err;
+
+		ret = snd_pcm_hw_constraint_step(runtime, 0,
+			SNDRV_PCM_HW_PARAM_BUFFER_BYTES, dma->conf->align);
+		if (ret)
+			goto err;
+	}
+
+	runtime->private_data = dma;
+
+ err:
+	return ret;
+}
+
+static int soc_dmaengine_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int soc_dmaengine_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	int ret = snd_pcm_lib_malloc_pages(substream,
+			params_buffer_bytes(params));
+
+	return ret < 0 ? ret : 0;
+}
+
+static int soc_dmaengine_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	size_t buf_size = snd_pcm_lib_buffer_bytes(substream);
+	size_t period = snd_pcm_lib_period_bytes(substream);
+	dma_addr_t addr = runtime->dma_addr;
+	unsigned i;
+
+	/* Create an array of sg entries, one for each period */
+	for (i = 0; i < PERIODS_MAX && buf_size; i++) {
+		BUG_ON(buf_size < period);
+
+		sg_dma_address(&dma->buf[i].sg) = addr;
+		sg_dma_len(&dma->buf[i].sg) = period;
+
+		addr += period;
+		buf_size -= period;
+	}
+
+	if (buf_size) {
+		pr_err("DMA buffer size not a multiple of the period size: residue=%zu\n", buf_size);
+		return -EINVAL;
+	}
+
+	dma->nr_periods = i;
+	dma->sg_index = 0;
+
+	return 0;
+}
+
+static int soc_dmaengine_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		ret = soc_dmaengine_start(substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		ret = soc_dmaengine_stop(substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ret = dmaengine_pause(dma->chan);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ret = dmaengine_resume(dma->chan);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t soc_dmaengine_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	struct dma_chan *ch = dma->chan;
+	struct dma_tx_state state;
+	unsigned bytes;
+
+	if (dma->cyclic) {
+		ch->device->device_tx_status(ch, dma->cyclic_cookie, &state);
+
+		bytes = snd_pcm_lib_buffer_bytes(substream) - state.residue;
+	} else {
+		unsigned index, pos;
+		size_t period = snd_pcm_lib_period_bytes(substream);
+		unsigned long flags;
+		enum dma_status status;
+		dma_cookie_t cookie;
+
+		/*
+		 * Get the next-to-be-submitted index, and the last submitted
+		 * cookie value.  We use this to obtain the DMA engine state.
+		 */
+		spin_lock_irqsave(&dma->lock, flags);
+		index = dma->sg_index;
+		do {
+			cookie = dma->buf[index].cookie;
+			status = ch->device->device_tx_status(ch, cookie, &state);
+			if (status == DMA_SUCCESS) {
+				index++;
+				if (index > dma->nr_periods)
+					index = 0;
+			}
+		} while (status == DMA_SUCCESS);
+		spin_unlock_irqrestore(&dma->lock, flags);
+
+		/* The end of the current DMA buffer */
+		pos = (index + 1) * period;
+		/* Now take off the residue */
+		bytes = pos - state.residue;
+		/* And correct for wrap-around */
+		if (bytes >= period * dma->nr_periods)
+			bytes -= period * dma->nr_periods;
+	}
+
+	return bytes_to_frames(runtime, bytes);
+}
+
+static int soc_dmaengine_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+	return dma_mmap_writecombine(buf->dev.dev, vma,
+		runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops soc_dmaengine_ops = {
+	.open = soc_dmaengine_open,
+	.close = soc_dmaengine_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = soc_dmaengine_hw_params,
+	.hw_free = snd_pcm_lib_free_pages,
+	.prepare = soc_dmaengine_prepare,
+	.trigger = soc_dmaengine_trigger,
+	.pointer = soc_dmaengine_pointer,
+	.mmap = soc_dmaengine_mmap,
+};
+
+static struct soc_dma_chan *soc_dmaengine_alloc(void)
+{
+	struct soc_dma_chan *dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+	unsigned i;
+
+	if (dma) {
+		spin_lock_init(&dma->lock);
+		for (i = 0; i < PERIODS_MAX; i++)
+			sg_init_table(&dma->buf[i].sg, 1);
+	}
+	return dma;
+}
+
+static struct dma_chan *soc_dmaengine_get(enum dma_transaction_type type,
+	struct soc_dma_config *cfg)
+{
+	dma_cap_mask_t m;
+
+	dma_cap_zero(m);
+	dma_cap_set(type, m);
+	return dma_request_channel(m, cfg->filter, cfg->data);
+}
+
+static int soc_dmaengine_request(struct soc_dma_chan *dma,
+	struct soc_dma_config *cfg, unsigned stream)
+{
+	int ret;
+
+	dma->conf = cfg;
+	dma->chan = soc_dmaengine_get(DMA_CYCLIC, cfg);
+	if (!dma->chan)
+		dma->chan = soc_dmaengine_get(DMA_SLAVE, cfg);
+	else
+		dma->cyclic = 1;
+	if (!dma->chan) {
+		ret = -ENXIO;
+		goto err_dma_req;
+	}
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma->dir = DMA_MEM_TO_DEV;
+		dma->cfg.direction = DMA_MEM_TO_DEV;
+		dma->cfg.dst_addr = cfg->reg;
+		dma->cfg.dst_addr_width = cfg->width;
+		dma->cfg.dst_maxburst = cfg->maxburst;
+	} else {
+		dma->dir = DMA_DEV_TO_MEM;
+		dma->cfg.direction = DMA_DEV_TO_MEM;
+		dma->cfg.src_addr = cfg->reg;
+		dma->cfg.src_addr_width = cfg->width;
+		dma->cfg.src_maxburst = cfg->maxburst;
+	}
+
+	ret = dmaengine_slave_config(dma->chan, &dma->cfg);
+	if (ret)
+		goto err_dma_cfg;
+
+	return 0;
+
+ err_dma_cfg:
+	dma_release_channel(dma->chan);
+	dma->chan = NULL;
+ err_dma_req:
+	return ret;
+}
+
+static void soc_dmaengine_release(struct soc_dma_chan *dma)
+{
+	if (dma && dma->chan)
+		dma_release_channel(dma->chan);
+	kfree(dma);
+}
+
+static int soc_dmaengine_preallocate_buffer(struct snd_pcm *pcm,
+	unsigned stream, struct soc_dma_chan *dma)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	int ret = 0;
+
+	if (substream) {
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		buf->dev.type = SNDRV_DMA_TYPE_DEV;
+		buf->dev.dev = dma->chan->device->dev;
+		buf->private_data = NULL;
+		buf->area = dma_alloc_writecombine(buf->dev.dev,
+				BUFFER_SIZE_MAX, &buf->addr, GFP_KERNEL);
+		if (!buf->area)
+			return -ENOMEM;
+
+		buf->bytes = BUFFER_SIZE_MAX;
+	}
+	return ret;
+}
+
+static int soc_dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct soc_dma_info *info;
+	unsigned stream;
+	int ret = 0;
+
+	if (!cpu_dai)
+		return -EINVAL;
+
+	if (!cpu_dai->playback_dma_data &&
+	    !cpu_dai->capture_dma_data) {
+		pr_err("soc_dmaengine: %s has no cpu_dai DMA data - incorrect probe ordering?\n",
+			rtd->card->name);
+		return -EINVAL;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+		struct soc_dma_config *cfg = NULL;
+		struct soc_dma_chan *dma;
+
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			cfg = cpu_dai->playback_dma_data;
+		else if (stream == SNDRV_PCM_STREAM_CAPTURE)
+			cfg = cpu_dai->capture_dma_data;
+
+		if (!cfg)
+			continue;
+
+		info->chan[stream] = dma = soc_dmaengine_alloc();
+		if (!dma) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		ret = soc_dmaengine_request(dma, cfg, stream);
+		if (ret)
+			return ret;
+
+		ret = soc_dmaengine_preallocate_buffer(pcm, stream, dma);
+		if (ret)
+			break;
+	}
+
+	if (ret) {
+		for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+			soc_dmaengine_release(info->chan[stream]);
+		kfree(info);
+	} else {
+		snd_soc_platform_set_drvdata(rtd->platform, info);
+	}
+
+	return ret;
+}
+
+/*
+ * Use write-combining memory here: the standard ALSA
+ * snd_free_dev_pages() doesn't support this.
+ */
+static void soc_dmaengine_pcm_free(struct snd_pcm *pcm)
+{
+	unsigned stream;
+
+	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+		struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+		struct snd_dma_buffer *buf;
+
+		if (!substream)
+			continue;
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		if (buf->dev.type == SNDRV_DMA_TYPE_DEV)
+			dma_free_writecombine(buf->dev.dev, buf->bytes,
+					      buf->area, buf->addr);
+		else
+			snd_dma_free_pages(buf);
+		buf->area = NULL;
+	}
+}
+
+/*
+ * This is annoying - we can't have symetry with .pcm_new because .pcm_free
+ * is called after the runtime information has been removed.  It would be
+ * better if we could find somewhere else to store our soc_dma_info pointer.
+ */
+static int soc_dmaengine_plat_remove(struct snd_soc_platform *platform)
+{
+	struct soc_dma_info *info = snd_soc_platform_get_drvdata(platform);
+	unsigned stream;
+
+	for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+		soc_dmaengine_release(info->chan[stream]);
+	kfree(info);
+
+	return 0;
+}
+
+static struct snd_soc_platform_driver soc_dmaengine_platform = {
+	.remove = soc_dmaengine_plat_remove,
+	.pcm_new = soc_dmaengine_pcm_new,
+	.pcm_free = soc_dmaengine_pcm_free,
+	.ops = &soc_dmaengine_ops,
+	/* Wait until the cpu_dai has been probed */
+	.probe_order = SND_SOC_COMP_ORDER_LATE,
+};
+
+static int soc_dmaengine_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &soc_dmaengine_platform);
+}
+
+static int soc_dmaengine_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver soc_dmaengine_driver = {
+	.driver = {
+		.name = "soc-dmaengine",
+		.owner = THIS_MODULE,
+	},
+	.probe = soc_dmaengine_probe,
+	.remove = soc_dmaengine_remove,
+};
+
+module_platform_driver(soc_dmaengine_driver);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("ALSA SoC DMA engine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:soc-dmaengine");

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Add a SA11x0 SSP SoC audio interface driver, which contains the SoC
specifics for handing audio data via the SSP interface.

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/Kconfig         |   1 +
 sound/soc/Makefile        |   1 +
 sound/soc/sa11x0/Kconfig  |   4 +
 sound/soc/sa11x0/Makefile |   2 +
 sound/soc/sa11x0/ssp.c    | 361 ++++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/sa11x0/ssp.h    |  11 ++
 6 files changed, 380 insertions(+)
 create mode 100644 sound/soc/sa11x0/Kconfig
 create mode 100644 sound/soc/sa11x0/Makefile
 create mode 100644 sound/soc/sa11x0/ssp.c
 create mode 100644 sound/soc/sa11x0/ssp.h

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 0ae5f1f763f2..e6eec854911c 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -113,6 +113,7 @@ source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
 source "sound/soc/rockchip/Kconfig"
+source "sound/soc/sa11x0/Kconfig"
 source "sound/soc/samsung/Kconfig"
 source "sound/soc/sh/Kconfig"
 source "sound/soc/sof/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 38e1d3255dac..aa8a90c40b8d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_SND_SOC)	+= kirkwood/
 obj-$(CONFIG_SND_SOC)	+= pxa/
 obj-$(CONFIG_SND_SOC)	+= qcom/
 obj-$(CONFIG_SND_SOC)	+= rockchip/
+obj-$(CONFIG_SND_SOC)	+= sa11x0/
 obj-$(CONFIG_SND_SOC)	+= samsung/
 obj-$(CONFIG_SND_SOC)	+= sh/
 obj-$(CONFIG_SND_SOC)	+= sof/
diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
new file mode 100644
index 000000000000..7a400f914c52
--- /dev/null
+++ b/sound/soc/sa11x0/Kconfig
@@ -0,0 +1,4 @@
+config SND_SOC_SA11X0_SSP
+	tristate
+	depends on DMA_SA11X0
+	select SND_SOC_DMAENGINE
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
new file mode 100644
index 000000000000..770ca0647343
--- /dev/null
+++ b/sound/soc/sa11x0/Makefile
@@ -0,0 +1,2 @@
+snd-soc-sa11x0-ssp-objs := ssp.o
+obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
diff --git a/sound/soc/sa11x0/ssp.c b/sound/soc/sa11x0/ssp.c
new file mode 100644
index 000000000000..1bdd4310a39d
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.c
@@ -0,0 +1,361 @@
+/*
+ * ASoC SA11x0 SSP DAI driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * The SA11x0 SSP interface gives us four signals: TXD, RXD, SFRM and SCLK,
+ * and SFRM pulses for each and every word transmitted.  These signals can
+ * be routed to GPIO10(TXD), GPIO11(RXD), GPIO12(SCLK), and GPIO13(SFRM)
+ * when the PPAR_SPR bit it set, along with the appropriate GAFR and GPDR
+ * configuration.  If PPAR_SPR is clear, the SSP shares the same pins with
+ * the MCP.
+ */
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dmaengine.h>
+#include <sound/soc.h>
+
+#include "ssp.h"
+
+#define SA11X0_SSP_CHANNELS_MIN	1
+#define SA11X0_SSP_CHANNELS_MAX	8
+
+/*
+ * This isn't really up to us - it depends how the board implements the
+ * clocking, whether the board uses the on-board clock source, or whether
+ * the SSP is clocked via GPIO19.
+ */
+#define SA11X0_SSP_RATES \
+	(SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_CONTINUOUS)
+#define SA11X0_SSP_FORMATS \
+	(SNDRV_PCM_FORMAT_S8		| SNDRV_PCM_FORMAT_U8		| \
+	 SNDRV_PCM_FORMAT_S16_LE	| SNDRV_PCM_FORMAT_U16_LE)
+
+#define SSCR0		0x60
+#define SSCR0_DSS	(15 << 0)	/* s */
+#define SSCR0_FRF_MOT	(0 << 4)
+#define SSCR0_FRF_TI	(1 << 4)
+#define SSCR0_FRF_NAT	(2 << 4)
+#define SSCR0_SSE	(1 << 7)	/* s */
+#define SSCR0_SCR	(0xff << 8)	/* s */
+#define SSCR1		0x64
+#define SSCR1_RIE	(1 << 0)
+#define SSCR1_TIE	(1 << 1)
+#define SSCR1_LBM	(1 << 2)
+#define SSCR1_SPO	(1 << 3)
+#define SSCR1_SP	(1 << 4)
+#define SSCR1_ECS	(1 << 5)	/* s */
+#define SSDR		0x6c
+#define SSSR		0x74
+
+struct ssp_priv {
+	void __iomem *base;
+	unsigned long clk_hz;
+	u32 cr0;
+	u32 cr1;
+};
+
+static struct soc_dma_config sa11x0_tx_cfg = {
+	.filter = sa11x0_dma_filter_fn,
+	.data = "Ser4SSPTr",
+	.reg = 0x80070000 + SSDR,
+	.width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+	.maxburst = 4,
+	.align = 32,
+	.fifo_size = 2 * 8,
+};
+
+static struct soc_dma_config sa11x0_rx_cfg = {
+	.filter = sa11x0_dma_filter_fn,
+	.data = "Ser4SSPRc",
+	.reg = 0x80070000 + SSDR,
+	.width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+	.maxburst = 4,
+	.align = 32,
+	.fifo_size = 2 * 12,
+};
+
+/*
+ * Constrain the rate according to the bitrate(s) that the interface can
+ * do, which is determined by its input clock and the range of divisors
+ * that we can program (1 to 256 with an initial /2).
+ */
+static int
+sa11x0_ssp_rate_constraint(struct snd_pcm_hw_params *params,
+	struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *p = hw_param_interval(params, rule->var);
+	struct ssp_priv *ssp = rule->private;
+	snd_pcm_format_t fmt = params_format(params);
+	struct snd_ratnum ratnum;
+
+	ratnum.num = ssp->clk_hz /
+		(2 * snd_pcm_format_width(fmt) * params_channels(params));
+	ratnum.den_min = 1;
+	ratnum.den_max = 256;
+	ratnum.den_step = 1;
+
+	return snd_interval_ratnum(p, 1, &ratnum, &params->rate_num,
+				   &params->rate_den);
+}
+
+static int
+sa11x0_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	if (!dai->active) {
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+		writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+		ssp->cr0 |= SSCR0_SSE;
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	}
+
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    sa11x0_ssp_rate_constraint, ssp,
+			    SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT,
+			    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
+	return 0;
+}
+
+static void sa11x0_ssp_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	if (!dai->active) {
+		struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+		ssp->cr0 &= ~SSCR0_SSE;
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	}
+}
+
+static int sa11x0_ssp_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+	snd_pcm_format_t fmt = params_format(params);
+	int width = snd_pcm_format_width(fmt);
+	unsigned int divisor;
+	u32 cr0;
+
+	if (width < 0)
+		return width;
+	if (width < 4 || width > 16)
+		return -EINVAL;
+
+	divisor = ssp->clk_hz / (2 * width * params_channels(params) *
+				params_rate(params));
+	if (divisor == 0)
+		return -EINVAL;
+
+	/* Set the bit-width for the format and the divisor */
+	cr0 = ssp->cr0 & ~(SSCR0_DSS | SSCR0_SCR);
+	/* rate_den is calculated by snd_interval_ratnum() above. */
+	cr0 |= (width - 1) | (divisor - 1) << 8;
+	if (cr0 != ssp->cr0) {
+		ssp->cr0 = cr0;
+		writel_relaxed(cr0, ssp->base + SSCR0);
+	}
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+	return 0;
+}
+
+static int sa11x0_ssp_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+	unsigned int freq, int dir)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	switch (clk_id) {
+	case SA11X0_SSP_CLK_INT:
+		ssp->cr1 &= ~SSCR1_ECS;
+		ssp->clk_hz = 3686400;
+		break;
+
+	case SA11X0_SSP_CLK_EXT:
+		/*
+		 * The SA11x0 developer's manual section 11.12.10.6 says the
+		 * external clock can be up to 3.6864MHz, but iPAQs supply
+		 * this with up to a 12.288MHz clock, which appears to work.
+		 * We trust that the frequency is correct.
+		 */
+		if (dir != SND_SOC_CLOCK_IN)
+			return -EINVAL;
+		ssp->cr1 |= SSCR1_ECS;
+		ssp->clk_hz = freq;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sa11x0_ssp_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	/* We always generate the clock and frm signals */
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		ssp->cr1 &= ~SSCR1_SPO;
+		break;
+
+	case SND_SOC_DAIFMT_IB_NF:
+		ssp->cr1 |= SSCR1_SPO;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops sa11x0_ssp_ops = {
+	.startup = sa11x0_ssp_startup,
+	.shutdown = sa11x0_ssp_shutdown,
+	.hw_params = sa11x0_ssp_hw_params,
+	.set_sysclk = sa11x0_ssp_set_sysclk,
+	.set_fmt = sa11x0_ssp_set_fmt,
+};
+
+static int sa11x0_ssp_probe(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	/* Default to TI mode and a bit-width of 16 */
+	ssp->cr0 = SSCR0_FRF_TI | 15;
+
+	/*
+	 * Set the DMA data now - it's needed for the dmaengine backend to
+	 * obtain its DMA channel, in turn its struct device, and therefore
+	 * a struct device to allocate DMA memory against.
+	 */
+	dai->playback_dma_data = &sa11x0_tx_cfg;
+	dai->capture_dma_data = &sa11x0_rx_cfg;
+
+	return 0;
+}
+
+static int sa11x0_ssp_remove(struct snd_soc_dai *dai)
+{
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int sa11x0_ssp_suspend(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+
+	return 0;
+}
+
+static int sa11x0_ssp_resume(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+	if (ssp->cr0 & SSCR0_SSE)
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+
+	return 0;
+}
+#else
+#define sa11x0_ssp_suspend NULL
+#define sa11x0_ssp_resume NULL
+#endif
+
+static struct snd_soc_dai_driver sa11x0_ssp_driver = {
+	.probe = sa11x0_ssp_probe,
+	.remove = sa11x0_ssp_remove,
+	.suspend = sa11x0_ssp_suspend,
+	.resume = sa11x0_ssp_resume,
+	.ops = &sa11x0_ssp_ops,
+	.capture = {
+		.channels_min = SA11X0_SSP_CHANNELS_MIN,
+		.channels_max = SA11X0_SSP_CHANNELS_MAX,
+		.rates = SA11X0_SSP_RATES,
+		.formats = SA11X0_SSP_FORMATS,
+	},
+	.playback = {
+		.channels_min = SA11X0_SSP_CHANNELS_MIN,
+		.channels_max = SA11X0_SSP_CHANNELS_MAX,
+		.rates = SA11X0_SSP_RATES,
+		.formats = SA11X0_SSP_FORMATS,
+	},
+	.symmetric_rates = 1,
+};
+
+static const struct snd_soc_component_driver sa11x0_ssp_component = {
+	.name = "sa11x0-ssp",
+};
+
+static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
+{
+	struct ssp_priv *ssp;
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
+	if (!ssp)
+		return -ENOMEM;
+
+	/* Assume internal clock, as that's what we program initially */
+	ssp->clk_hz = 3686400;
+
+	ssp->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(ssp->base))
+		return PTR_ERR(ssp->base);
+
+	writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+	platform_set_drvdata(pdev, ssp);
+	return snd_soc_register_component(&pdev->dev, &sa11x0_ssp_component,
+					  &sa11x0_ssp_driver, 1);
+}
+
+static int sa11x0_ssp_plat_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver sa11x0_ssp_plat_driver = {
+	.driver = {
+		.name = "sa11x0-ssp",
+		.owner = THIS_MODULE,
+	},
+	.probe = sa11x0_ssp_plat_probe,
+	.remove = sa11x0_ssp_plat_remove,
+};
+module_platform_driver(sa11x0_ssp_plat_driver);
+
+MODULE_AUTHOR("Russell King <[email-masked]>");
+MODULE_DESCRIPTION("SA11x0 SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sa11x0-ssp");
diff --git a/sound/soc/sa11x0/ssp.h b/sound/soc/sa11x0/ssp.h
new file mode 100644
index 000000000000..22f1e5b8f45c
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.h
@@ -0,0 +1,11 @@
+#ifndef SA11X0_SSP_H
+#define SA11X0_SSP_H
+
+/* SSP clock sources */
+#define SA11X0_SSP_CLK_INT	0x11000000
+#define SA11X0_SSP_CLK_EXT	0x11000001
+
+/* SSP audio dividers */
+#define SA11X0_SSP_DIV_SCR	0x11000100
+
+#endif

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Add the support code for the UDA1341 on the Assabet platform, which
uses the DMA engine and SA-11x0 SSP drivers.

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/sa11x0/Kconfig   |  10 +
 sound/soc/sa11x0/Makefile  |   3 +
 sound/soc/sa11x0/assabet.c | 522 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 535 insertions(+)
 create mode 100644 sound/soc/sa11x0/assabet.c

diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
index 7a400f914c52..f18fe9faa76d 100644
--- a/sound/soc/sa11x0/Kconfig
+++ b/sound/soc/sa11x0/Kconfig
@@ -2,3 +2,13 @@ config SND_SOC_SA11X0_SSP
 	tristate
 	depends on DMA_SA11X0
 	select SND_SOC_DMAENGINE
+
+config SND_SA11X0_ASSABET
+	tristate "SoC Audio support for the Intel Assabet board"
+	depends on SA1100_ASSABET && DMA_SA11X0
+	select SND_SOC_L3
+	select SND_SOC_UDA134X
+	select SND_SOC_SA11X0_SSP
+	help
+	  Enable support for the audio UDA1341 codec found on the Intel
+	  Assabet SA-1110 development board.
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
index 770ca0647343..9a2ab2c346d9 100644
--- a/sound/soc/sa11x0/Makefile
+++ b/sound/soc/sa11x0/Makefile
@@ -1,2 +1,5 @@
 snd-soc-sa11x0-ssp-objs := ssp.o
 obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
+
+snd-soc-sa11x0-assabet-objs := assabet.o
+obj-$(CONFIG_SND_SA11X0_ASSABET) += snd-soc-sa11x0-assabet.o
diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
new file mode 100644
index 000000000000..0dc5362c82a8
--- /dev/null
+++ b/sound/soc/sa11x0/assabet.c
@@ -0,0 +1,522 @@
+/*
+ * SA11x0 Assabet ASoC driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * The Assabet board uses an unused SDRAM clock output as the source
+ * clock for the UDA1341 audio subsystem.  This clock is supplied to
+ * via a CPLD divider to the SA11x0 GPIO19 (alternately the SSP block
+ * external clock input) and to the UDA1341 as the bit clock.
+ *
+ * As the SSPs TXD,RXD,SFRM,SCLK outputs are not directly compatible
+ * with the UDA1341 input, the CPLD implements logic to provide the
+ * WS (LRCK) signal to the UDA1341, and buffer the RXD and clock signals.
+ *
+ * The UDA1341 is powered by the AUDIO3P3V supply, which can be turned
+ * off.  This tristates the CPLD outputs, preventing power draining
+ * into the UDA1341.  However, revision 4 Assabets do not tristate the
+ * WS signal, and so need to be worked-around to place this at logic 0.
+ *
+ * A side effect of using the SDRAM clock is that this scales with the
+ * core CPU frequency.  Hence, the available sample rate depends on the
+ * CPU clock rate.  At present, we have no logic here to restrict the
+ * requested sample rate; the UDA1341 driver will just fail the preparation.
+ * We rely on userspace (at present) to ask for the correct sample rate.
+ */
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda134x.h>
+
+#include <asm/mach-types.h>
+#include <mach/assabet.h>
+#include <mach/hardware.h>
+#include "ssp.h"
+
+struct assabet_card {
+	struct snd_soc_card card;
+};
+
+#define GPIO_TXD	10
+#define GPIO_RXD	11
+#define GPIO_SCLK	12
+#define GPIO_SFRM	13
+#define GPIO_L3_DAT	15
+#define GPIO_L3_MODE	17
+#define GPIO_L3_SCK	18
+#define GPIO_CLK	19
+
+/*
+ * The UDA1341 wants a WS (or LRCK), and the Assabet derives this from
+ * the SFRM pulses from the SSP.  Unfortunately, some Assabets have a bug
+ * in the hardware where the WS output from the CPLD remains high even
+ * after the codec is powered down, causing power to drain through the
+ * CPLD to the UDA1341.
+ *
+ * Although the Assabet provides a reset signal to initialize WS to a
+ * known state, this presents two problems: (a) this forces the WS output
+ * high, (b) this signal also resets the UCB1300 device.
+ */
+static void assabet_asoc_uda1341_power(int on)
+{
+	static bool state;
+
+	if (state == !!on)
+		return;
+	state = !!on;
+
+	if (on) {
+		/*
+		 * Enable the power for the UDA1341 before fixing WS.
+		 * Also assert the mute signal.
+		 */
+		ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON | ASSABET_BCR_QMUTE);
+
+		/*
+		 * Toggle SFRM with the codec reset signal held active.
+		 * This will set LRCK high, which results in the left
+		 * sample being transmitted first.
+		 */
+		gpio_set_value(GPIO_SFRM, 1);
+		gpio_set_value(GPIO_SFRM, 0);
+
+		/*
+		 * If the reset was being held, release it now.  This
+		 * ensures that the above SFRM fiddling has no effect
+		 * should the reset be raised elsewhere.
+		 */
+		assabet_uda1341_reset(0);
+	} else {
+		/*
+		 * The SSP will have transmitted a whole number of 32-bit
+		 * words, so we know that LRCK will be left high at this
+		 * point.  Toggle SFRM once to kick the LRCK low before
+		 * powering down the codec.
+		 */
+		gpio_set_value(GPIO_SFRM, 1);
+		gpio_set_value(GPIO_SFRM, 0);
+
+		/* Finally, disable the audio power */
+		ASSABET_BCR_clear(ASSABET_BCR_AUDIO_ON);
+
+		/* And lower the QMUTE signal to stop power draining */
+		ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
+	}
+}
+
+static void assabet_asoc_init_clk(void)
+{
+	u32 mdrefr, mdrefr_old;
+
+	/*
+	 * The assabet board uses the SDRAM clock as the source clock for
+	 * audio. This is supplied to the SA11x0 from the CPLD on pin 19.
+	 * At 206MHz we need to run the audio clock (SDRAM bank 2) at half
+	 * speed.  This clock will scale with core frequency so the audio
+	 * sample rate will also scale. The CPLD on Assabet will need to
+	 * be programmed to match the core frequency.
+	 */
+	mdrefr_old = MDREFR;
+	mdrefr = mdrefr_old & ~(MDREFR_EAPD | MDREFR_KAPD);
+	mdrefr = mdrefr | MDREFR_K2DB2 | MDREFR_K2RUN;
+	if (mdrefr != mdrefr_old) {
+		MDREFR = mdrefr;
+		(void) MDREFR;
+	}
+}
+
+static int assabet_asoc_resume_pre(struct snd_soc_card *card)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	bool active = false;
+
+	list_for_each_entry(rtd, &card->rtd_list, list)
+		if (rtd->cpu_dai->active)
+			active = true;
+
+	if (active) {
+		unsigned long flags;
+
+		local_irq_save(flags);
+		assabet_asoc_init_clk();
+		local_irq_restore(flags);
+	}
+	return 0;
+}
+
+static int
+assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval range, *p = hw_param_interval(params, rule->var);
+	unsigned int sysclk = (cpufreq_get(0) / 18) * 1000;
+
+	snd_interval_any(&range);
+	range.min = UINT_MAX;
+	range.max = 0;
+
+	/* We do 512Fs and 256Fs only */
+	if (snd_interval_test(p, sysclk / 256)) {
+		range.min = min(range.min, sysclk / 256);
+		range.max = max(range.max, sysclk / 256);
+	}
+	if (snd_interval_test(p, sysclk / 512)) {
+		range.min = min(range.min, sysclk / 512);
+		range.max = max(range.max, sysclk / 512);
+	}
+	return snd_interval_refine(p, &range);
+}
+
+static int assabet_asoc_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	if (!cpu_dai->active) {
+		unsigned long flags;
+		unsigned int sysclk;
+		int ret;
+
+		/*
+		 * Hand the SFRM signal over to the SSP block as it is
+		 * now enabled.  This keeps SFRM low until the interface
+		 * starts outputting data.
+		 */
+		local_irq_save(flags);
+		GAFR |= GPIO_SSP_SFRM;
+
+		assabet_asoc_init_clk();
+		local_irq_restore(flags);
+
+		/*
+		 * Calculate the sysclk, which is the codec input clock.
+		 * This is supplied from the SDRAM bank 2 clock, divided by
+		 * 18 by the CPLD.  This is then further divided by four by
+		 * the CPLD, and supplied to the SA1110 GPIO19 to
+		 * synchronously drive the SSP block.
+		 *
+		 * Ideally, we would lock cpufreq against any transitions
+		 * which would upset the sample rate, or have userspace
+		 * renegotiate the sample rate, but let's keep this simple.
+		 *
+		 * We intentionally lose some precision here to avoid the
+		 * codec bombing out as it claims to only do standard rates.
+		 */
+		sysclk = (cpufreq_get(0) / 18) * 1000;
+
+		ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sysclk,
+					     SND_SOC_CLOCK_IN);
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_dai_set_sysclk(cpu_dai, SA11X0_SSP_CLK_EXT,
+					     sysclk / 4, SND_SOC_CLOCK_IN);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Release the mute */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
+
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    assabet_asoc_rate_constraint, NULL,
+			    SNDRV_PCM_HW_PARAM_RATE, -1);
+
+	return 0;
+}
+
+static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	/* Apply the output mute */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ASSABET_BCR_set(ASSABET_BCR_QMUTE);
+
+	if (!cpu_dai->active) {
+		unsigned long flags;
+
+		/*
+		 * Take the SFRM pin away from the SSP block before it
+		 * shuts down.  We must do this to keep SFRM low.
+		 */
+		local_irq_save(flags);
+		GAFR &= ~GPIO_SSP_SFRM;
+		local_irq_restore(flags);
+	}
+}
+
+static struct snd_soc_ops assabet_asoc_ops = {
+	.startup = assabet_asoc_startup,
+	.shutdown = assabet_asoc_shutdown,
+};
+
+static struct snd_soc_dai_link assabet_asoc_dai = {
+	.name = "Assabet",
+	.stream_name = "assabet-ssp",
+	.codec_name = "uda134x-codec",
+	.platform_name = "soc-dmaengine",
+	.cpu_dai_name = "sa11x0-ssp",
+	.codec_dai_name = "uda134x-hifi",
+	.ops = &assabet_asoc_ops,
+	.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+		   SND_SOC_DAIFMT_CBS_CFS,
+};
+
+static int
+assabet_pwramp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
+	int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		ASSABET_BCR_clear(ASSABET_BCR_SPK_OFF);
+	else
+		ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget assabet_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Power Amp", assabet_pwramp_event),
+};
+
+static const struct snd_soc_dapm_route assabet_dapm_routes[] = {
+	{ "Power Amp", NULL, "VOUTL" },
+	{ "Power Amp", NULL, "VOUTR" },
+};
+
+static struct snd_soc_card snd_soc_assabet = {
+	.name = "Assabet",
+	.resume_pre = assabet_asoc_resume_pre,
+	.dai_link = &assabet_asoc_dai,
+	.num_links = 1,
+	.dapm_widgets = assabet_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(assabet_dapm_widgets),
+	.dapm_routes = assabet_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(assabet_dapm_routes),
+};
+
+/*
+ * This is not-quite-right stuff: we have to _hope_ by a miracle that
+ * we don't stamp on the toes of I2C, which shares these pins.
+ */
+static void assabet_setdat(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_DAT, !!v);
+}
+
+static void assabet_setclk(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_SCK, !!v);
+}
+
+static void assabet_setmode(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_MODE, !!v);
+}
+
+static struct uda134x_platform_data uda1341_data = {
+	.l3 = {
+		.setdat = assabet_setdat,
+		.setclk = assabet_setclk,
+		.setmode = assabet_setmode,
+		.data_hold = 1,
+		.data_setup = 1,
+		.clock_high = 1,
+		.mode_hold = 1,
+		.mode = 1,
+		.mode_setup = 1,
+	},
+	.model = UDA134X_UDA1341,
+	.power = assabet_asoc_uda1341_power,
+};
+
+static struct gpio ssp_gpio[] = {
+	{
+		.gpio = GPIO_TXD,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_txd",
+	}, {
+		.gpio = GPIO_RXD,
+		.flags = GPIOF_IN,
+		.label = "ssp_rxd",
+	}, {
+		.gpio = GPIO_SCLK,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_sclk",
+	}, {
+		.gpio = GPIO_SFRM,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_sfrm",
+	}, {
+		.gpio = GPIO_CLK,
+		.flags = GPIOF_IN,
+		.label = "ssp_clk",
+	}, {
+		.gpio = GPIO_L3_MODE,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "l3_mode",
+	},
+};
+
+static int assabet_asoc_probe(struct platform_device *pdev)
+{
+	struct assabet_card *ac;
+
+	ac = devm_kzalloc(&pdev->dev, sizeof(*ac), GFP_KERNEL);
+	if (!ac)
+		return -ENOMEM;
+
+	memcpy(&ac->card, &snd_soc_assabet, sizeof(ac->card));
+	ac->card.dev = &pdev->dev;
+
+	snd_soc_card_set_drvdata(&ac->card, ac);
+
+	return snd_soc_register_card(&ac->card);
+}
+
+static int assabet_asoc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	return 0;
+}
+
+static struct platform_driver assabet_asoc_driver = {
+	.probe	= assabet_asoc_probe,
+	.remove	= assabet_asoc_remove,
+	.driver = {
+		.name = "assabet-asoc",
+		.owner = THIS_MODULE,
+	},
+};
+
+static struct platform_device *soc, *uda, *dma;
+
+static int assabet_init(void)
+{
+	unsigned long flags;
+	int ret;
+
+	if (!machine_is_assabet())
+		return -ENODEV;
+
+	ret = gpio_request_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+	if (ret)
+		return ret;
+
+	/*
+	 * Request these irrespective of whether they succeed.  Wish we
+	 * could have our shared I2C/L3 driver from v2.4 kernels, but
+	 * alas this would require a buggeration of the ASoC code.
+	 * Not only this, but we have no way to do any kind of locking
+	 * between the UDA134x L3 driver and I2C.
+	 */
+	gpio_request_one(GPIO_L3_DAT, GPIOF_OUT_INIT_LOW, "l3_dat");
+	gpio_request_one(GPIO_L3_SCK, GPIOF_OUT_INIT_LOW, "l3_sck");
+
+	/*
+	 * Put the LRCK signal into a known good state: early Assabets
+	 * do not mask the LRCK signal when the codec is powered down,
+	 * and it defaults to logic 1.  So, first release the reset,
+	 * and then toggle the SFRM signal to set LRCK to zero.
+	 */
+	ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
+	ASSABET_BCR_clear(ASSABET_BCR_STEREO_LB);
+	assabet_uda1341_reset(0);
+
+	gpio_set_value(GPIO_SFRM, 1);
+	gpio_set_value(GPIO_SFRM, 0);
+
+	local_irq_save(flags);
+	/*
+	 * Ensure SSP pin reassignment is enabled, so that the SSP appears
+	 * on the GPIO pins rather than the deddicated UART4 pins.
+	 */
+	PPAR |= PPAR_SPR;
+
+	/* Configure the SSP input clock, and most SSP output signals */
+	GAFR |= GPIO_SSP_TXD | GPIO_SSP_RXD | GPIO_SSP_SCLK | GPIO_SSP_CLK;
+
+	local_irq_restore(flags);
+
+	soc = platform_device_alloc("assabet-asoc", -1);
+	uda = platform_device_alloc(assabet_asoc_dai.codec_name, -1);
+	dma = platform_device_alloc("soc-dmaengine", -1);
+	if (!soc || !uda || !dma) {
+		ret = -ENOMEM;
+		goto err_dev_alloc;
+	}
+
+	/* Set some platform data for the UDA1341 codec driver... */
+	uda->dev.platform_data = &uda1341_data;
+
+	/* Set some sensible parents */
+	uda->dev.parent = &soc->dev;
+	dma->dev.parent = &soc->dev;
+
+	ret = platform_device_add(soc);
+	if (ret)
+		goto err_dev_alloc;
+	ret = platform_device_add(uda);
+	if (ret)
+		goto err_dev_add_soc;
+	ret = platform_device_add(dma);
+	if (ret)
+		goto err_dev_add_uda;
+	ret = platform_driver_register(&assabet_asoc_driver);
+	if (ret)
+		goto err_dev_add_dma;
+	return 0;
+
+ err_dev_add_dma:
+	platform_device_del(dma);
+ err_dev_add_uda:
+	platform_device_del(uda);
+ err_dev_add_soc:
+	platform_device_del(soc);
+ err_dev_alloc:
+	if (dma)
+		platform_device_put(dma);
+	if (uda)
+		platform_device_put(uda);
+	if (soc)
+		platform_device_put(soc);
+
+	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+
+	return ret;
+}
+module_init(assabet_init);
+
+static void assabet_exit(void)
+{
+	if (!machine_is_assabet())
+		return;
+
+	platform_driver_unregister(&assabet_asoc_driver);
+	platform_device_unregister(dma);
+	platform_device_unregister(uda);
+
+	/* Don't try to kfree this data */
+	soc->dev.platform_data = NULL;
+
+	platform_device_unregister(soc);
+	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+}
+module_exit(assabet_exit);
+
+MODULE_LICENSE("GPL");

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/sa11x0/Kconfig |   2 +-
 sound/soc/sa11x0/ssp.c   | 106 ++++++++++++++++++++++++++++++++++-------------
 2 files changed, 79 insertions(+), 29 deletions(-)

diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
index f18fe9faa76d..ae7930842e07 100644
--- a/sound/soc/sa11x0/Kconfig
+++ b/sound/soc/sa11x0/Kconfig
@@ -1,7 +1,7 @@
 config SND_SOC_SA11X0_SSP
 	tristate
 	depends on DMA_SA11X0
-	select SND_SOC_DMAENGINE
+	select SND_SOC_GENERIC_DMAENGINE_PCM
 
 config SND_SA11X0_ASSABET
 	tristate "SoC Audio support for the Intel Assabet board"
diff --git a/sound/soc/sa11x0/ssp.c b/sound/soc/sa11x0/ssp.c
index 1bdd4310a39d..2656df529f96 100644
--- a/sound/soc/sa11x0/ssp.c
+++ b/sound/soc/sa11x0/ssp.c
@@ -22,9 +22,9 @@
 #include <linux/slab.h>
 
 #include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
-#include <sound/soc-dmaengine.h>
 #include <sound/soc.h>
 
 #include "ssp.h"
@@ -43,6 +43,12 @@
 	(SNDRV_PCM_FORMAT_S8		| SNDRV_PCM_FORMAT_U8		| \
 	 SNDRV_PCM_FORMAT_S16_LE	| SNDRV_PCM_FORMAT_U16_LE)
 
+#define BUFFER_SIZE_MAX		65536
+#define PERIOD_SIZE_MIN		32
+#define PERIOD_SIZE_MAX		16384
+#define PERIODS_MIN		2
+#define PERIODS_MAX		256
+
 #define SSCR0		0x60
 #define SSCR0_DSS	(15 << 0)	/* s */
 #define SSCR0_FRF_MOT	(0 << 4)
@@ -65,26 +71,23 @@ struct ssp_priv {
 	unsigned long clk_hz;
 	u32 cr0;
 	u32 cr1;
+	struct snd_dmaengine_dai_dma_data tx;
+	struct snd_dmaengine_dai_dma_data rx;
 };
 
-static struct soc_dma_config sa11x0_tx_cfg = {
-	.filter = sa11x0_dma_filter_fn,
-	.data = "Ser4SSPTr",
-	.reg = 0x80070000 + SSDR,
-	.width = DMA_SLAVE_BUSWIDTH_2_BYTES,
-	.maxburst = 4,
-	.align = 32,
-	.fifo_size = 2 * 8,
-};
-
-static struct soc_dma_config sa11x0_rx_cfg = {
-	.filter = sa11x0_dma_filter_fn,
-	.data = "Ser4SSPRc",
-	.reg = 0x80070000 + SSDR,
-	.width = DMA_SLAVE_BUSWIDTH_2_BYTES,
-	.maxburst = 4,
-	.align = 32,
-	.fifo_size = 2 * 12,
+static const struct snd_pcm_hardware sa11x0_ssp_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.period_bytes_min	= PERIOD_SIZE_MIN,
+	.period_bytes_max	= PERIOD_SIZE_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= PERIODS_MAX,
+	.buffer_bytes_max	= BUFFER_SIZE_MAX,
+	/* This is for playback only */
+	.fifo_size		= 8 * 2,
 };
 
 /*
@@ -115,6 +118,24 @@ static int
 sa11x0_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
 {
 	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_rule_add(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_RATE, sa11x0_ssp_rate_constraint, ssp,
+		SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT,
+		SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (ret)
+		return ret;
 
 	if (!dai->active) {
 		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
@@ -123,11 +144,6 @@ sa11x0_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
 		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
 	}
 
-	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-			    sa11x0_ssp_rate_constraint, ssp,
-			    SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT,
-			    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-
 	return 0;
 }
 
@@ -249,8 +265,8 @@ static int sa11x0_ssp_probe(struct snd_soc_dai *dai)
 	 * obtain its DMA channel, in turn its struct device, and therefore
 	 * a struct device to allocate DMA memory against.
 	 */
-	dai->playback_dma_data = &sa11x0_tx_cfg;
-	dai->capture_dma_data = &sa11x0_rx_cfg;
+	dai->playback_dma_data = &ssp->tx;
+	dai->capture_dma_data = &ssp->rx;
 
 	return 0;
 }
@@ -311,10 +327,17 @@ static const struct snd_soc_component_driver sa11x0_ssp_component = {
 	.name = "sa11x0-ssp",
 };
 
+static const struct snd_dmaengine_pcm_config sa11x0_ssp_pcm_config = {
+	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+	.pcm_hardware = &sa11x0_ssp_pcm_hardware,
+	.prealloc_buffer_size = SZ_64K,
+};
+
 static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
 {
 	struct ssp_priv *ssp;
 	struct resource *res;
+	int ret;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!res)
@@ -327,6 +350,22 @@ static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
 	/* Assume internal clock, as that's what we program initially */
 	ssp->clk_hz = 3686400;
 
+	/*
+	 * FIXME: The generic ASoC dmaengine backend does not allow us to set
+	 * a different FIFO size for the transmit and receive sides of the
+	 * SSP: the transmit has a 8 x 16bit FIFO, but the receive side has
+	 * a 12 x 16bit FIFO.
+	 */
+	ssp->tx.addr = res->start + SSDR;
+	ssp->tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	ssp->tx.maxburst = 4;
+	ssp->tx.filter_data = "Ser4SSPTr";
+
+	ssp->rx.addr = res->start + SSDR;
+	ssp->rx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	ssp->rx.maxburst = 4;
+	ssp->rx.filter_data = "Ser4SSPRc";
+
 	ssp->base = devm_ioremap_resource(&pdev->dev, res);
 	if (IS_ERR(ssp->base))
 		return PTR_ERR(ssp->base);
@@ -335,13 +374,24 @@ static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
 	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
 
 	platform_set_drvdata(pdev, ssp);
-	return snd_soc_register_component(&pdev->dev, &sa11x0_ssp_component,
-					  &sa11x0_ssp_driver, 1);
+
+	ret = snd_dmaengine_pcm_register(&pdev->dev, &sa11x0_ssp_pcm_config,
+					 SND_DMAENGINE_PCM_FLAG_COMPAT);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_register_component(&pdev->dev, &sa11x0_ssp_component,
+					 &sa11x0_ssp_driver, 1);
+	if (ret)
+		snd_dmaengine_pcm_unregister(&pdev->dev);
+
+	return ret;
 }
 
 static int sa11x0_ssp_plat_remove(struct platform_device *pdev)
 {
 	snd_soc_unregister_component(&pdev->dev);
+	snd_dmaengine_pcm_unregister(&pdev->dev);
 	return 0;
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/sa11x0/assabet.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
index 0dc5362c82a8..0e8bbb34ffb6 100644
--- a/sound/soc/sa11x0/assabet.c
+++ b/sound/soc/sa11x0/assabet.c
@@ -267,7 +267,7 @@ static struct snd_soc_dai_link assabet_asoc_dai = {
 	.name = "Assabet",
 	.stream_name = "assabet-ssp",
 	.codec_name = "uda134x-codec",
-	.platform_name = "soc-dmaengine",
+	.platform_name = "sa11x0-ssp",
 	.cpu_dai_name = "sa11x0-ssp",
 	.codec_dai_name = "uda134x-hifi",
 	.ops = &assabet_asoc_ops,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

From: Russell King (Oracle) <[email-masked]>
---
Patch suppressed due to merge commit

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:20 +0100

Switch SA1100 to the more accurate MPLL calculation.  Table 8-1 in the
SA1110 developers manual gives two sets of frequencies for two different
crystals - 3.6864MHz and 3.5795MHz.  Calculating the multiplication
factor for each entry in the table shows that the clock rates given are
are equal to the crystal frequency multiplied by 4(4 + PPCR), rounded to
100kHz.  This equation holds for the higher frequencies up to 280.2MHz
which have been omitted from the later manuals.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/clock.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/arch/arm/mach-sa1100/clock.c b/arch/arm/mach-sa1100/clock.c
index e8691921c69a..9ced1c559eca 100644
--- a/arch/arm/mach-sa1100/clock.c
+++ b/arch/arm/mach-sa1100/clock.c
@@ -12,7 +12,8 @@
 #include <linux/spinlock.h>
 
 #include <mach/hardware.h>
-#include <mach/generic.h>
+
+#include "generic.h"
 
 static const char * const clk_tucr_parents[] = {
 	"clk32768", "clk3686400",
@@ -66,12 +67,12 @@ static const struct clk_init_data clk_gpio27_init_data __initconst = {
  * Derived from the table 8-1 in the SA1110 manual, the MPLL appears to
  * multiply its input rate by 4 x (4 + PPCR).  This calculation gives
  * the exact rate.  The figures given in the table are the rates rounded
- * to 100kHz.  Stick with sa11x0_getspeed() for the time being.
+ * to 100kHz.
  */
 static unsigned long clk_mpll_recalc_rate(struct clk_hw *hw,
 	unsigned long prate)
 {
-	return sa11x0_getspeed(0) * 1000;
+	return prate * 4 * (4 + (PPCR & 15));
 }
 
 static const struct clk_ops clk_mpll_ops = {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 93868167de4b..f0bf85743b01 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -358,7 +358,7 @@ static void assabet_lcd_set_visual(u32 visual)
 #ifndef ASSABET_PAL_VIDEO
 static void assabet_lcd_backlight_power(int on)
 {
-	if (on)
+	if (on && 0)
 		ASSABET_BCR_set(ASSABET_BCR_LIGHT_ON);
 	else
 		ASSABET_BCR_clear(ASSABET_BCR_LIGHT_ON);
@@ -371,9 +371,10 @@ static void assabet_lcd_backlight_power(int on)
  */
 static void assabet_lcd_power(int on)
 {
-	if (on) {
+	if (on && 0) {
+		msleep(500);
 		ASSABET_BCR_set(ASSABET_BCR_LCD_ON);
-		udelay(500);
+		msleep(500);
 	} else
 		ASSABET_BCR_clear(ASSABET_BCR_LCD_ON);
 }
@@ -569,8 +570,8 @@ static void __init assabet_init(void)
 	 * Set up registers for sleep mode.
 	 */
 	PWER = PWER_GPIO0;
-	PGSR = 0;
-	PCFR = 0;
+	PGSR = GPIO_GPIO(17);
+	PCFR = PCFR_OPDE | PCFR_FP;
 	PSDR = 0;
 	PPDR |= PPC_TXD3 | PPC_TXD1;
 	PPSR |= PPC_TXD3 | PPC_TXD1;
@@ -663,6 +664,11 @@ static void __init get_assabet_scr(void)
 	SCR_value = scr;
 }
 
+static char neponset_cmdline[] __initdata = "console=ttySA0 "
+"cpufreq=221200 rw root=/dev/mtdblock2 "
+"mtdparts=sa1100:512K(boot),1M(kernel),2560K(initrd),4M(root) "
+"load_ramdisk=1 prompt_ramdisk=0 mem=32M noinitrd initrd=0xc0800000,3M profile=2";
+
 static void __init
 fixup_assabet(struct tag *tags, char **cmdline)
 {
@@ -670,8 +676,10 @@ fixup_assabet(struct tag *tags, char **cmdline)
 	map_sa1100_gpio_regs();
 	get_assabet_scr();
 
-	if (machine_has_neponset())
+	if (machine_has_neponset()) {
 		printk("Neponset expansion board detected\n");
+		*cmdline = neponset_cmdline;
+	}
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

From: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index f0bf85743b01..2a267122b4a1 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -721,9 +721,12 @@ static void __init assabet_map_io(void)
 	 * Its called GPCLKR0 in my SA1110 manual.
 	 */
 	Ser1SDCR0 |= SDCR0_SUS;
-	MSC1 = (MSC1 & ~0xffff) |
+	pr_info("MSC1 = 0x%08lx -> 0x%08lx\n", MSC1,
+//	MSC1 = (MSC1 & ~0xffff) |
+		(MSC1 & ~0xffff) |
 		MSC_NonBrst | MSC_32BitStMem |
-		MSC_RdAcc(2) | MSC_WrAcc(2) | MSC_Rec(0);
+		MSC_RdAcc(2) | MSC_WrAcc(2) | MSC_Rec(0)
+		);
 
 	if (!machine_has_neponset())
 		sa1100_register_uart_fns(&assabet_port_fns);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

This reverts commit 6dd11b945951315ba4986844f20e83a0c27c1d38.
---
 include/sound/l3.h         |  28 +++
 include/sound/uda134x.h    |  24 ++
 sound/soc/codecs/Kconfig   |   8 +
 sound/soc/codecs/Makefile  |   5 +-
 sound/soc/codecs/l3.c      | 132 ++++++++++
 sound/soc/codecs/uda134x.c | 587 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/uda134x.h |  33 +++
 7 files changed, 816 insertions(+), 1 deletion(-)
 create mode 100644 include/sound/l3.h
 create mode 100644 include/sound/uda134x.h
 create mode 100644 sound/soc/codecs/l3.c
 create mode 100644 sound/soc/codecs/uda134x.c
 create mode 100644 sound/soc/codecs/uda134x.h

diff --git a/include/sound/l3.h b/include/sound/l3.h
new file mode 100644
index 000000000000..b6f58072237a
--- /dev/null
+++ b/include/sound/l3.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _L3_H_
+#define _L3_H_ 1
+
+struct l3_pins {
+	void (*setdat)(struct l3_pins *, int);
+	void (*setclk)(struct l3_pins *, int);
+	void (*setmode)(struct l3_pins *, int);
+
+	int gpio_data;
+	int gpio_clk;
+	int gpio_mode;
+	int use_gpios;
+
+	int data_hold;
+	int data_setup;
+	int clock_high;
+	int mode_hold;
+	int mode;
+	int mode_setup;
+};
+
+struct device;
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len);
+int l3_set_gpio_ops(struct device *dev, struct l3_pins *adap);
+
+#endif
diff --git a/include/sound/uda134x.h b/include/sound/uda134x.h
new file mode 100644
index 000000000000..db82516da162
--- /dev/null
+++ b/include/sound/uda134x.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * uda134x.h  --  UDA134x ALSA SoC Codec driver
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ */
+
+#ifndef _UDA134X_H
+#define _UDA134X_H
+
+#include <sound/l3.h>
+
+struct uda134x_platform_data {
+	struct l3_pins l3;
+	void (*power) (int);
+	int model;
+#define UDA134X_UDA1340 1
+#define UDA134X_UDA1341 2
+#define UDA134X_UDA1344 3
+#define UDA134X_UDA1345 4
+};
+
+#endif /* _UDA134X_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b5e6d0a986c8..ffa14d3498ff 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -15,6 +15,7 @@ config SND_SOC_ALL_CODECS
 	tristate "Build all ASoC CODEC drivers"
 	depends on COMPILE_TEST
 	imply SND_SOC_88PM860X
+	imply SND_SOC_L3
 	imply SND_SOC_AB8500_CODEC
 	imply SND_SOC_AC97_CODEC
 	imply SND_SOC_AD1836
@@ -280,6 +281,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TWL4030
 	imply SND_SOC_TWL6040
 	imply SND_SOC_UDA1334
+	imply SND_SOC_UDA134X
 	imply SND_SOC_UDA1380
 	imply SND_SOC_WCD9335
 	imply SND_SOC_WCD934X
@@ -1088,6 +1090,9 @@ config SND_SOC_JZ4770_CODEC
 	  This driver can also be built as a module. If so, the module
 	  will be called snd-soc-jz4770-codec.
 
+config SND_SOC_L3
+	tristate
+
 config SND_SOC_DA7210
 	tristate
 	depends on SND_SOC_I2C_AND_SPI
@@ -2113,6 +2118,9 @@ config SND_SOC_UDA1334
 	  and has basic features such as de-emphasis (at 44.1 kHz sampling
 	  rate) and mute.
 
+config SND_SOC_UDA134X
+	tristate
+
 config SND_SOC_UDA1380
 	tristate
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 622e360f0086..0c7a7edaee6c 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,7 +57,6 @@ snd-soc-aw88399-y := aw88399.o
 snd-soc-bd28623-y := bd28623.o
 snd-soc-bt-sco-y := bt-sco.o
 snd-soc-chv3-codec-y := chv3-codec.o
-snd-soc-cpcap-y := cpcap.o
 snd-soc-cq93vc-y := cq93vc.o
 snd-soc-cros-ec-codec-y := cros_ec_codec.o
 snd-soc-cs-amp-lib-y := cs-amp-lib.o
@@ -142,6 +141,7 @@ snd-soc-jz4740-codec-y := jz4740.o
 snd-soc-jz4725b-codec-y := jz4725b.o
 snd-soc-jz4760-codec-y := jz4760.o
 snd-soc-jz4770-codec-y := jz4770.o
+snd-soc-l3-y := l3.o
 snd-soc-lm4857-y := lm4857.o
 snd-soc-lm49453-y := lm49453.o
 snd-soc-lochnagar-sc-y := lochnagar-sc.o
@@ -318,6 +318,7 @@ snd-soc-ts3a227e-y := ts3a227e.o
 snd-soc-twl4030-y := twl4030.o
 snd-soc-twl6040-y := twl6040.o
 snd-soc-uda1334-y := uda1334.o
+snd-soc-uda134x-y := uda134x.o
 snd-soc-uda1380-y := uda1380.o
 snd-soc-wcd-classh-y := wcd-clsh-v2.o
 snd-soc-wcd-mbhc-y := wcd-mbhc-v2.o
@@ -548,6 +549,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC)	+= snd-soc-jz4740-codec.o
 obj-$(CONFIG_SND_SOC_JZ4725B_CODEC)	+= snd-soc-jz4725b-codec.o
 obj-$(CONFIG_SND_SOC_JZ4760_CODEC)      += snd-soc-jz4760-codec.o
 obj-$(CONFIG_SND_SOC_JZ4770_CODEC)	+= snd-soc-jz4770-codec.o
+obj-$(CONFIG_SND_SOC_L3)	+= snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_LM4857)	+= snd-soc-lm4857.o
 obj-$(CONFIG_SND_SOC_LM49453)   += snd-soc-lm49453.o
 obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC)	+= snd-soc-lochnagar-sc.o
@@ -721,6 +723,7 @@ obj-$(CONFIG_SND_SOC_TS3A227E)	+= snd-soc-ts3a227e.o
 obj-$(CONFIG_SND_SOC_TWL4030)	+= snd-soc-twl4030.o
 obj-$(CONFIG_SND_SOC_TWL6040)	+= snd-soc-twl6040.o
 obj-$(CONFIG_SND_SOC_UDA1334)	+= snd-soc-uda1334.o
+obj-$(CONFIG_SND_SOC_UDA134X)	+= snd-soc-uda134x.o
 obj-$(CONFIG_SND_SOC_UDA1380)	+= snd-soc-uda1380.o
 obj-$(CONFIG_SND_SOC_WCD_CLASSH)	+= snd-soc-wcd-classh.o
 obj-$(CONFIG_SND_SOC_WCD_MBHC)	+= snd-soc-wcd-mbhc.o
diff --git a/sound/soc/codecs/l3.c b/sound/soc/codecs/l3.c
new file mode 100644
index 000000000000..b84f6f1f6800
--- /dev/null
+++ b/sound/soc/codecs/l3.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * L3 code
+ *
+ *  Copyright (C) 2008, Christian Pellegrin <[email-masked]>
+ *
+ * based on:
+ *
+ * L3 bus algorithm module.
+ *
+ *  Copyright (C) 2001 Russell King, All Rights Reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/l3.h>
+
+/*
+ * Send one byte of data to the chip.  Data is latched into the chip on
+ * the rising edge of the clock.
+ */
+static void sendbyte(struct l3_pins *adap, unsigned int byte)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		adap->setclk(adap, 0);
+		udelay(adap->data_hold);
+		adap->setdat(adap, byte & 1);
+		udelay(adap->data_setup);
+		adap->setclk(adap, 1);
+		udelay(adap->clock_high);
+		byte >>= 1;
+	}
+}
+
+/*
+ * Send a set of bytes to the chip.  We need to pulse the MODE line
+ * between each byte, but never at the start nor at the end of the
+ * transfer.
+ */
+static void sendbytes(struct l3_pins *adap, const u8 *buf,
+		      int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (i) {
+			udelay(adap->mode_hold);
+			adap->setmode(adap, 0);
+			udelay(adap->mode);
+		}
+		adap->setmode(adap, 1);
+		udelay(adap->mode_setup);
+		sendbyte(adap, buf[i]);
+	}
+}
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len)
+{
+	adap->setclk(adap, 1);
+	adap->setdat(adap, 1);
+	adap->setmode(adap, 1);
+	udelay(adap->mode);
+
+	adap->setmode(adap, 0);
+	udelay(adap->mode_setup);
+	sendbyte(adap, addr);
+	udelay(adap->mode_hold);
+
+	sendbytes(adap, data, len);
+
+	adap->setclk(adap, 1);
+	adap->setdat(adap, 1);
+	adap->setmode(adap, 0);
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(l3_write);
+
+
+static void l3_set_clk(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_clk, val);
+}
+
+static void l3_set_data(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_data, val);
+}
+
+static void l3_set_mode(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_mode, val);
+}
+
+int l3_set_gpio_ops(struct device *dev, struct l3_pins *adap)
+{
+	int ret;
+
+	if (!adap->use_gpios)
+		return -EINVAL;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_data,
+				GPIOF_OUT_INIT_LOW, "l3_data");
+	if (ret < 0)
+		return ret;
+	adap->setdat = l3_set_data;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_clk,
+				GPIOF_OUT_INIT_LOW, "l3_clk");
+	if (ret < 0)
+		return ret;
+	adap->setclk = l3_set_clk;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_mode,
+				GPIOF_OUT_INIT_LOW, "l3_mode");
+	if (ret < 0)
+		return ret;
+	adap->setmode = l3_set_mode;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l3_set_gpio_ops);
+
+MODULE_DESCRIPTION("L3 bit-banging driver");
+MODULE_AUTHOR("Christian Pellegrin <[email-masked]>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c
new file mode 100644
index 000000000000..1a62bec94005
--- /dev/null
+++ b/sound/soc/codecs/uda134x.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * uda134x.c  --  UDA134X ALSA SoC Codec driver
+ *
+ * Modifications by Christian Pellegrin <[email-masked]>
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * Based on the WM87xx drivers by Liam Girdwood and Richard Purdie
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <sound/uda134x.h>
+#include <sound/l3.h>
+
+#include "uda134x.h"
+
+
+#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000
+#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+		SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE)
+
+struct uda134x_priv {
+	int sysclk;
+	int dai_fmt;
+
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+
+	struct regmap *regmap;
+	struct uda134x_platform_data *pd;
+};
+
+static const struct reg_default uda134x_reg_defaults[] = {
+	{ UDA134X_EA000, 0x04 },
+	{ UDA134X_EA001, 0x04 },
+	{ UDA134X_EA010, 0x04 },
+	{ UDA134X_EA011, 0x00 },
+	{ UDA134X_EA100, 0x00 },
+	{ UDA134X_EA101, 0x00 },
+	{ UDA134X_EA110, 0x00 },
+	{ UDA134X_EA111, 0x00 },
+	{ UDA134X_STATUS0, 0x00 },
+	{ UDA134X_STATUS1, 0x03 },
+	{ UDA134X_DATA000, 0x00 },
+	{ UDA134X_DATA001, 0x00 },
+	{ UDA134X_DATA010, 0x00 },
+	{ UDA134X_DATA011, 0x00 },
+	{ UDA134X_DATA1, 0x00 },
+};
+
+/*
+ * Write to the uda134x registers
+ *
+ */
+static int uda134x_regmap_write(void *context, unsigned int reg,
+	unsigned int value)
+{
+	struct uda134x_platform_data *pd = context;
+	int ret;
+	u8 addr;
+	u8 data = value;
+
+	switch (reg) {
+	case UDA134X_STATUS0:
+	case UDA134X_STATUS1:
+		addr = UDA134X_STATUS_ADDR;
+		data |= (reg - UDA134X_STATUS0) << 7;
+		break;
+	case UDA134X_DATA000:
+	case UDA134X_DATA001:
+	case UDA134X_DATA010:
+	case UDA134X_DATA011:
+		addr = UDA134X_DATA0_ADDR;
+		data |= (reg - UDA134X_DATA000) << 6;
+		break;
+	case UDA134X_DATA1:
+		addr = UDA134X_DATA1_ADDR;
+		break;
+	default:
+		/* It's an extended address register */
+		addr =  (reg | UDA134X_EXTADDR_PREFIX);
+
+		ret = l3_write(&pd->l3,
+			       UDA134X_DATA0_ADDR, &addr, 1);
+		if (ret != 1)
+			return -EIO;
+
+		addr = UDA134X_DATA0_ADDR;
+		data = (value | UDA134X_EXTDATA_PREFIX);
+		break;
+	}
+
+	ret = l3_write(&pd->l3,
+		       addr, &data, 1);
+	if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static inline void uda134x_reset(struct snd_soc_component *component)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	unsigned int mask = 1<<6;
+
+	regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, mask);
+	msleep(1);
+	regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, 0);
+}
+
+static int uda134x_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(dai->component);
+	unsigned int mask = 1<<2;
+	unsigned int val;
+
+	pr_debug("%s mute: %d\n", __func__, mute);
+
+	if (mute)
+		val = mask;
+	else
+		val = 0;
+
+	return regmap_update_bits(uda134x->regmap, UDA134X_DATA010, mask, val);
+}
+
+static int uda134x_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct snd_pcm_runtime *master_runtime;
+
+	if (uda134x->master_substream) {
+		master_runtime = uda134x->master_substream->runtime;
+
+		pr_debug("%s constraining to %d bits at %d\n", __func__,
+			 master_runtime->sample_bits,
+			 master_runtime->rate);
+
+		snd_pcm_hw_constraint_single(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_RATE,
+					     master_runtime->rate);
+
+		snd_pcm_hw_constraint_single(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					     master_runtime->sample_bits);
+
+		uda134x->slave_substream = substream;
+	} else
+		uda134x->master_substream = substream;
+
+	return 0;
+}
+
+static void uda134x_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	if (uda134x->master_substream == substream)
+		uda134x->master_substream = uda134x->slave_substream;
+
+	uda134x->slave_substream = NULL;
+}
+
+static int uda134x_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	unsigned int hw_params = 0;
+
+	if (substream == uda134x->slave_substream) {
+		pr_debug("%s ignoring hw_params for slave substream\n",
+			 __func__);
+		return 0;
+	}
+
+	pr_debug("%s sysclk: %d, rate:%d\n", __func__,
+		 uda134x->sysclk, params_rate(params));
+
+	/* set SYSCLK / fs ratio */
+	switch (uda134x->sysclk / params_rate(params)) {
+	case 512:
+		break;
+	case 384:
+		hw_params |= (1<<4);
+		break;
+	case 256:
+		hw_params |= (1<<5);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported fs\n", __func__);
+		return -EINVAL;
+	}
+
+	pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__,
+		 uda134x->dai_fmt, params_format(params));
+
+	/* set DAI format and word length */
+	switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		switch (params_width(params)) {
+		case 16:
+			hw_params |= (1<<1);
+			break;
+		case 18:
+			hw_params |= (1<<2);
+			break;
+		case 20:
+			hw_params |= ((1<<2) | (1<<1));
+			break;
+		default:
+			printk(KERN_ERR "%s unsupported format (right)\n",
+			       __func__);
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hw_params |= (1<<3);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported format\n", __func__);
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(uda134x->regmap, UDA134X_STATUS0,
+		STATUS0_SYSCLK_MASK | STATUS0_DAIFMT_MASK, hw_params);
+}
+
+static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
+		 clk_id, freq, dir);
+
+	/* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
+	   because the codec is slave. Of course limitations of the clock
+	   master (the IIS controller) apply.
+	   We'll error out on set_hw_params if it's not OK */
+	if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
+		uda134x->sysclk = freq;
+		return 0;
+	}
+
+	printk(KERN_ERR "%s unsupported sysclk\n", __func__);
+	return -EINVAL;
+}
+
+static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			       unsigned int fmt)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	pr_debug("%s fmt: %08X\n", __func__, fmt);
+
+	/* codec supports only full consumer mode */
+	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) {
+		printk(KERN_ERR "%s unsupported clocking mode\n", __func__);
+		return -EINVAL;
+	}
+
+	/* no support for clock inversion */
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+		printk(KERN_ERR "%s unsupported clock inversion\n", __func__);
+		return -EINVAL;
+	}
+
+	/* We can't setup DAI format here as it depends on the word bit num */
+	/* so let's just store the value for later */
+	uda134x->dai_fmt = fmt;
+
+	return 0;
+}
+
+static int uda134x_set_bias_level(struct snd_soc_component *component,
+				  enum snd_soc_bias_level level)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct uda134x_platform_data *pd = uda134x->pd;
+	pr_debug("%s bias level %d\n", __func__, level);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* power on */
+		if (pd->power) {
+			pd->power(1);
+			regcache_sync(uda134x->regmap);
+		}
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* power off */
+		if (pd->power) {
+			pd->power(0);
+			regcache_mark_dirty(uda134x->regmap);
+		}
+		break;
+	}
+	return 0;
+}
+
+static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1",
+					    "Minimum2", "Maximum"};
+static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *uda134x_mixmode[] = {"Differential", "Analog1",
+					"Analog2", "Both"};
+
+static const struct soc_enum uda134x_mixer_enum[] = {
+SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting),
+SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph),
+SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode),
+};
+
+static const struct snd_kcontrol_new uda1341_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0),
+SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1),
+SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1),
+
+SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0),
+SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+SOC_ENUM("Input Mux", uda134x_mixer_enum[2]),
+
+SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0),
+SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1),
+SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0),
+
+SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0),
+SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0),
+SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0),
+SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0),
+SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0),
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1340_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1345_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+/* UDA1341 has the DAC/ADC power down in STATUS1 */
+static const struct snd_soc_dapm_widget uda1341_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_STATUS1, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_STATUS1, 1, 0),
+};
+
+/* UDA1340/4/5 has the DAC/ADC pwoer down in DATA0 11 */
+static const struct snd_soc_dapm_widget uda1340_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_DATA011, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_DATA011, 1, 0),
+};
+
+/* Common DAPM widgets */
+static const struct snd_soc_dapm_widget uda134x_dapm_widgets[] = {
+	SND_SOC_DAPM_INPUT("VINL1"),
+	SND_SOC_DAPM_INPUT("VINR1"),
+	SND_SOC_DAPM_INPUT("VINL2"),
+	SND_SOC_DAPM_INPUT("VINR2"),
+	SND_SOC_DAPM_OUTPUT("VOUTL"),
+	SND_SOC_DAPM_OUTPUT("VOUTR"),
+};
+
+static const struct snd_soc_dapm_route uda134x_dapm_routes[] = {
+	{ "ADC", NULL, "VINL1" },
+	{ "ADC", NULL, "VINR1" },
+	{ "ADC", NULL, "VINL2" },
+	{ "ADC", NULL, "VINR2" },
+	{ "VOUTL", NULL, "DAC" },
+	{ "VOUTR", NULL, "DAC" },
+};
+
+static const struct snd_soc_dai_ops uda134x_dai_ops = {
+	.startup	= uda134x_startup,
+	.shutdown	= uda134x_shutdown,
+	.hw_params	= uda134x_hw_params,
+	.mute_stream	= uda134x_mute,
+	.set_sysclk	= uda134x_set_dai_sysclk,
+	.set_fmt	= uda134x_set_dai_fmt,
+	.no_capture_mute = 1,
+};
+
+static struct snd_soc_dai_driver uda134x_dai = {
+	.name = "uda134x-hifi",
+	/* playback capabilities */
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* capture capabilities */
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* pcm operations */
+	.ops = &uda134x_dai_ops,
+};
+
+static int uda134x_soc_probe(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct uda134x_platform_data *pd = uda134x->pd;
+	const struct snd_soc_dapm_widget *widgets;
+	unsigned int num_widgets;
+	int ret;
+
+	printk(KERN_INFO "UDA134X SoC Audio Codec\n");
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1341:
+	case UDA134X_UDA1344:
+	case UDA134X_UDA1345:
+		break;
+	default:
+		printk(KERN_ERR "UDA134X SoC codec: "
+		       "unsupported model %d\n",
+			pd->model);
+		return -EINVAL;
+	}
+
+	if (pd->power)
+		pd->power(1);
+
+	uda134x_reset(component);
+
+	if (pd->model == UDA134X_UDA1341) {
+		widgets = uda1341_dapm_widgets;
+		num_widgets = ARRAY_SIZE(uda1341_dapm_widgets);
+	} else {
+		widgets = uda1340_dapm_widgets;
+		num_widgets = ARRAY_SIZE(uda1340_dapm_widgets);
+	}
+
+	ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets);
+	if (ret) {
+		printk(KERN_ERR "%s failed to register dapm controls: %d",
+			__func__, ret);
+		return ret;
+	}
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1344:
+		ret = snd_soc_add_component_controls(component, uda1340_snd_controls,
+					ARRAY_SIZE(uda1340_snd_controls));
+	break;
+	case UDA134X_UDA1341:
+		ret = snd_soc_add_component_controls(component, uda1341_snd_controls,
+					ARRAY_SIZE(uda1341_snd_controls));
+	break;
+	case UDA134X_UDA1345:
+		ret = snd_soc_add_component_controls(component, uda1345_snd_controls,
+					ARRAY_SIZE(uda1345_snd_controls));
+	break;
+	default:
+		printk(KERN_ERR "%s unknown codec type: %d",
+			__func__, pd->model);
+		return -EINVAL;
+	}
+
+	if (ret < 0) {
+		printk(KERN_ERR "UDA134X: failed to register controls\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_uda134x = {
+	.probe			= uda134x_soc_probe,
+	.set_bias_level		= uda134x_set_bias_level,
+	.dapm_widgets		= uda134x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(uda134x_dapm_widgets),
+	.dapm_routes		= uda134x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(uda134x_dapm_routes),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 1,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static const struct regmap_config uda134x_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = UDA134X_DATA1,
+	.reg_defaults = uda134x_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(uda134x_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+
+	.reg_write = uda134x_regmap_write,
+};
+
+static int uda134x_codec_probe(struct platform_device *pdev)
+{
+	struct uda134x_platform_data *pd = pdev->dev.platform_data;
+	struct uda134x_priv *uda134x;
+	int ret;
+
+	if (!pd) {
+		dev_err(&pdev->dev, "Missing L3 bitbang function\n");
+		return -ENODEV;
+	}
+
+	uda134x = devm_kzalloc(&pdev->dev, sizeof(*uda134x), GFP_KERNEL);
+	if (!uda134x)
+		return -ENOMEM;
+
+	uda134x->pd = pd;
+	platform_set_drvdata(pdev, uda134x);
+
+	if (pd->l3.use_gpios) {
+		ret = l3_set_gpio_ops(&pdev->dev, &uda134x->pd->l3);
+		if (ret < 0)
+			return ret;
+	}
+
+	uda134x->regmap = devm_regmap_init(&pdev->dev, NULL, pd,
+		&uda134x_regmap_config);
+	if (IS_ERR(uda134x->regmap))
+		return PTR_ERR(uda134x->regmap);
+
+	return devm_snd_soc_register_component(&pdev->dev,
+			&soc_component_dev_uda134x, &uda134x_dai, 1);
+}
+
+static struct platform_driver uda134x_codec_driver = {
+	.driver = {
+		.name = "uda134x-codec",
+	},
+	.probe = uda134x_codec_probe,
+};
+
+module_platform_driver(uda134x_codec_driver);
+
+MODULE_DESCRIPTION("UDA134X ALSA soc codec driver");
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <[email-masked]>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h
new file mode 100644
index 000000000000..664618c2571c
--- /dev/null
+++ b/sound/soc/codecs/uda134x.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _UDA134X_CODEC_H
+#define _UDA134X_CODEC_H
+
+#define UDA134X_L3ADDR	5
+#define UDA134X_DATA0_ADDR	((UDA134X_L3ADDR << 2) | 0)
+#define UDA134X_DATA1_ADDR	((UDA134X_L3ADDR << 2) | 1)
+#define UDA134X_STATUS_ADDR	((UDA134X_L3ADDR << 2) | 2)
+
+#define UDA134X_EXTADDR_PREFIX	0xC0
+#define UDA134X_EXTDATA_PREFIX	0xE0
+
+/* UDA134X registers */
+#define UDA134X_EA000	0
+#define UDA134X_EA001	1
+#define UDA134X_EA010	2
+#define UDA134X_EA011	3
+#define UDA134X_EA100	4
+#define UDA134X_EA101	5
+#define UDA134X_EA110	6
+#define UDA134X_EA111	7
+#define UDA134X_STATUS0 8
+#define UDA134X_STATUS1 9
+#define UDA134X_DATA000 10
+#define UDA134X_DATA001 11
+#define UDA134X_DATA010 12
+#define UDA134X_DATA011 13
+#define UDA134X_DATA1   14
+
+#define STATUS0_DAIFMT_MASK (~(7<<1))
+#define STATUS0_SYSCLK_MASK (~(3<<4))
+
+#endif

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Add support for the UDA134x QMUTE signal via the gpiolib layer.  We
assert QMUTE when powering up the device, keeping it muted until we
are starting up the playback stream.  The reverse happens when the
playback stream is shut down.

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/codecs/uda134x.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c
index 1a62bec94005..d07b98bda2d3 100644
--- a/sound/soc/codecs/uda134x.c
+++ b/sound/soc/codecs/uda134x.c
@@ -12,6 +12,7 @@
 
 #include <linux/module.h>
 #include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/slab.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -37,6 +38,8 @@ struct uda134x_priv {
 
 	struct regmap *regmap;
 	struct uda134x_platform_data *pd;
+
+	struct gpio_desc *qmute;
 };
 
 static const struct reg_default uda134x_reg_defaults[] = {
@@ -125,6 +128,9 @@ static int uda134x_mute(struct snd_soc_dai *dai, int mute, int direction)
 
 	pr_debug("%s mute: %d\n", __func__, mute);
 
+	if (uda134x->qmute)
+		gpiod_set_value_cansleep(uda134x->qmute, mute);
+
 	if (mute)
 		val = mask;
 	else
@@ -304,6 +310,8 @@ static int uda134x_set_bias_level(struct snd_soc_component *component,
 	case SND_SOC_BIAS_PREPARE:
 		/* power on */
 		if (pd->power) {
+			if (uda134x->qmute)
+				gpiod_set_value_cansleep(uda134x->qmute, 1);
 			pd->power(1);
 			regcache_sync(uda134x->regmap);
 		}
@@ -315,6 +323,8 @@ static int uda134x_set_bias_level(struct snd_soc_component *component,
 		if (pd->power) {
 			pd->power(0);
 			regcache_mark_dirty(uda134x->regmap);
+			if (uda134x->qmute)
+				gpiod_set_value_cansleep(uda134x->qmute, 0);
 		}
 		break;
 	}
@@ -524,7 +534,7 @@ static const struct snd_soc_component_driver soc_component_dev_uda134x = {
 	.dapm_routes		= uda134x_dapm_routes,
 	.num_dapm_routes	= ARRAY_SIZE(uda134x_dapm_routes),
 	.suspend_bias_off	= 1,
-	.idle_bias_on		= 1,
+	.idle_bias_on		= 0,
 	.use_pmdown_time	= 1,
 	.endianness		= 1,
 };
@@ -569,6 +579,11 @@ static int uda134x_codec_probe(struct platform_device *pdev)
 	if (IS_ERR(uda134x->regmap))
 		return PTR_ERR(uda134x->regmap);
 
+	uda134x->qmute = devm_gpiod_get_optional(&pdev->dev, "qmute",
+						 GPIOD_OUT_LOW);
+	if (IS_ERR(uda134x->qmute))
+		return PTR_ERR(uda134x->qmute);
+
 	return devm_snd_soc_register_component(&pdev->dev,
 			&soc_component_dev_uda134x, &uda134x_dai, 1);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 19 +++++++++
 arch/arm/mach-sa1100/clock.c   |  2 +
 sound/soc/sa11x0/assabet.c     | 92 +++++++++++++++++++++++++-----------------
 3 files changed, 75 insertions(+), 38 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 2a267122b4a1..84649207dbb9 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -438,6 +438,22 @@ static struct resource neponset_resources[] = {
 };
 #endif
 
+static struct gpiod_lookup_table assabet_uda1341_gpio_table = {
+	.dev_id = "uda134x-codec",
+	.table = {
+		GPIO_LOOKUP("assabet", 21, "qmute", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct gpiod_lookup_table assabet_audio_gpio_table = {
+	.dev_id = "assabet-asoc",
+	.table = {
+		GPIO_LOOKUP("assabet", 23, "spkr-shtdn", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 static struct gpiod_lookup_table assabet_cf_gpio_table = {
 	.dev_id = "sa11x0-pcmcia.1",
 	.table = {
@@ -543,6 +559,9 @@ static struct gpiod_lookup_table assabet_uart3_gpio_table = {
 
 static void __init assabet_init(void)
 {
+	gpiod_add_lookup_table(&assabet_uda1341_gpio_table);
+	gpiod_add_lookup_table(&assabet_audio_gpio_table);
+
 	/*
 	 * Ensure that the power supply is in "high power" mode.
 	 */
diff --git a/arch/arm/mach-sa1100/clock.c b/arch/arm/mach-sa1100/clock.c
index 9ced1c559eca..a104cfa4b123 100644
--- a/arch/arm/mach-sa1100/clock.c
+++ b/arch/arm/mach-sa1100/clock.c
@@ -124,6 +124,8 @@ int __init sa11xx_clk_init(void)
 	clk_hw_register_clkdev(hw, NULL, "sa11x0-pcmcia.1");
 	clk_hw_register_clkdev(hw, NULL, "1800");
 
+	clk_hw_register_clkdev(hw, NULL, "assabet-asoc");
+
 	hw = clk_hw_register_mux(NULL, "tucr-mux", clk_tucr_parents,
 				 ARRAY_SIZE(clk_tucr_parents), 0,
 				 (void __iomem *)&TUCR, FShft(TUCR_TSEL),
diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
index 0e8bbb34ffb6..d6c9cbefc1f6 100644
--- a/sound/soc/sa11x0/assabet.c
+++ b/sound/soc/sa11x0/assabet.c
@@ -27,9 +27,10 @@
  * requested sample rate; the UDA1341 driver will just fail the preparation.
  * We rely on userspace (at present) to ask for the correct sample rate.
  */
-#include <linux/cpufreq.h>
+#include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
 #include <linux/io.h>
 #include <linux/module.h>
 
@@ -46,6 +47,8 @@
 
 struct assabet_card {
 	struct snd_soc_card card;
+	struct clk *clk_cpu;
+	struct gpio_desc *gpio_spkr_shtdn;
 };
 
 #define GPIO_TXD	10
@@ -81,7 +84,7 @@ static void assabet_asoc_uda1341_power(int on)
 		 * Enable the power for the UDA1341 before fixing WS.
 		 * Also assert the mute signal.
 		 */
-		ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON | ASSABET_BCR_QMUTE);
+		ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON);
 
 		/*
 		 * Toggle SFRM with the codec reset signal held active.
@@ -109,9 +112,6 @@ static void assabet_asoc_uda1341_power(int on)
 
 		/* Finally, disable the audio power */
 		ASSABET_BCR_clear(ASSABET_BCR_AUDIO_ON);
-
-		/* And lower the QMUTE signal to stop power draining */
-		ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
 	}
 }
 
@@ -155,11 +155,30 @@ static int assabet_asoc_resume_pre(struct snd_soc_card *card)
 	return 0;
 }
 
+/*
+ * Calculate the sysclk, which is the codec input clock. This is supplied
+ * from the SDRAM bank 2 clock, divided by 18 by the CPLD.  This is then
+ * further divided by four by the CPLD, and supplied to the SA1110 GPIO19
+ * to synchronously drive the SSP block.
+ *
+ * Ideally, we would lock cpufreq against any transitions which would upset
+ * the sample rate, or have userspace renegotiate the sample rate, but let's
+ * keep this simple.
+ *
+ * We intentionally lose some precision here to avoid the codec bombing out
+ * as it claims to only do standard rates.
+ */
+static unsigned int assabet_asoc_get_sysclk(struct assabet_card *ac)
+{
+	return (clk_get_rate(ac->clk_cpu) / 18000) * 1000;
+}
+
 static int
 assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
 {
+	struct assabet_card *ac = rule->private;
 	struct snd_interval range, *p = hw_param_interval(params, rule->var);
-	unsigned int sysclk = (cpufreq_get(0) / 18) * 1000;
+	unsigned int sysclk = assabet_asoc_get_sysclk(ac);
 
 	snd_interval_any(&range);
 	range.min = UINT_MAX;
@@ -180,12 +199,17 @@ assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw
 static int assabet_asoc_startup(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
 
 	if (!cpu_dai->active) {
 		unsigned long flags;
 		unsigned int sysclk;
-		int ret;
+
+		ret = clk_prepare_enable(ac->clk_cpu);
+		if (ret)
+			return ret;
 
 		/*
 		 * Hand the SFRM signal over to the SSP block as it is
@@ -198,53 +222,36 @@ static int assabet_asoc_startup(struct snd_pcm_substream *substream)
 		assabet_asoc_init_clk();
 		local_irq_restore(flags);
 
-		/*
-		 * Calculate the sysclk, which is the codec input clock.
-		 * This is supplied from the SDRAM bank 2 clock, divided by
-		 * 18 by the CPLD.  This is then further divided by four by
-		 * the CPLD, and supplied to the SA1110 GPIO19 to
-		 * synchronously drive the SSP block.
-		 *
-		 * Ideally, we would lock cpufreq against any transitions
-		 * which would upset the sample rate, or have userspace
-		 * renegotiate the sample rate, but let's keep this simple.
-		 *
-		 * We intentionally lose some precision here to avoid the
-		 * codec bombing out as it claims to only do standard rates.
-		 */
-		sysclk = (cpufreq_get(0) / 18) * 1000;
+		sysclk = assabet_asoc_get_sysclk(ac);
 
 		ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sysclk,
 					     SND_SOC_CLOCK_IN);
 		if (ret < 0)
-			return ret;
+			goto err;
 
 		ret = snd_soc_dai_set_sysclk(cpu_dai, SA11X0_SSP_CLK_EXT,
 					     sysclk / 4, SND_SOC_CLOCK_IN);
 		if (ret < 0)
-			return ret;
+			goto err;
 	}
 
-	/* Release the mute */
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-		ASSABET_BCR_clear(ASSABET_BCR_QMUTE);
-
 	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-			    assabet_asoc_rate_constraint, NULL,
+			    assabet_asoc_rate_constraint, ac,
 			    SNDRV_PCM_HW_PARAM_RATE, -1);
 
 	return 0;
+
+err:
+	clk_disable_unprepare(ac->clk_cpu);
+	return ret;
 }
 
 static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 
-	/* Apply the output mute */
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-		ASSABET_BCR_set(ASSABET_BCR_QMUTE);
-
 	if (!cpu_dai->active) {
 		unsigned long flags;
 
@@ -255,6 +262,8 @@ static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
 		local_irq_save(flags);
 		GAFR &= ~GPIO_SSP_SFRM;
 		local_irq_restore(flags);
+
+		clk_disable_unprepare(ac->clk_cpu);
 	}
 }
 
@@ -279,10 +288,9 @@ static int
 assabet_pwramp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
 	int event)
 {
-	if (SND_SOC_DAPM_EVENT_ON(event))
-		ASSABET_BCR_clear(ASSABET_BCR_SPK_OFF);
-	else
-		ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
+	struct assabet_card *ac = snd_soc_card_get_drvdata(w->dapm->card);
+
+	gpiod_set_value(ac->gpio_spkr_shtdn, !SND_SOC_DAPM_EVENT_ON(event));
 
 	return 0;
 }
@@ -381,6 +389,15 @@ static int assabet_asoc_probe(struct platform_device *pdev)
 	memcpy(&ac->card, &snd_soc_assabet, sizeof(ac->card));
 	ac->card.dev = &pdev->dev;
 
+	ac->clk_cpu = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ac->clk_cpu))
+		return PTR_ERR(ac->clk_cpu);
+
+	ac->gpio_spkr_shtdn = devm_gpiod_get(&pdev->dev, "spkr-shtdn",
+					     GPIOD_OUT_HIGH);
+	if (IS_ERR(ac->gpio_spkr_shtdn))
+		return PTR_ERR(ac->gpio_spkr_shtdn);
+
 	snd_soc_card_set_drvdata(&ac->card, ac);
 
 	return snd_soc_register_card(&ac->card);
@@ -434,7 +451,6 @@ static int assabet_init(void)
 	 * and it defaults to logic 1.  So, first release the reset,
 	 * and then toggle the SFRM signal to set LRCK to zero.
 	 */
-	ASSABET_BCR_set(ASSABET_BCR_SPK_OFF);
 	ASSABET_BCR_clear(ASSABET_BCR_STEREO_LB);
 	assabet_uda1341_reset(0);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Switch to using gpiod APIs for controlling the LCD depth and power.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 59 ++++++++++++++++++++++++------------------
 1 file changed, 34 insertions(+), 25 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 84649207dbb9..5b4ed8cbba27 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -70,6 +70,9 @@ enum {
 #define machine_has_neponset()	(0)
 #endif
 
+/* The build phase of the hardware.  This isn't detectable */
+#define assabet_phase()		4
+
 #define ASSABET_BCR_BASE  0xf1000000
 #define ASSABET_BCR       IOMEM(ASSABET_BCR_BASE)
 
@@ -335,24 +338,20 @@ static struct mcp_plat_data assabet_mcp_data = {
 	.codec_pdata	= &assabet_ucb1x00_data,
 };
 
-static void assabet_lcd_set_visual(u32 visual)
-{
-	u_int is_true_color = visual == FB_VISUAL_TRUECOLOR;
+static struct gpiod_lookup_table assabet_platform_gpio_table = {
+	.table = {
+		GPIO_LOOKUP("assabet", 10, "lcd-depth", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("assabet", 11, "lcd-on", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
 
-	if (machine_is_assabet()) {
-#if 1		// phase 4 or newer Assabet's
-		if (is_true_color)
-			ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB);
-		else
-			ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB);
-#else
-		// older Assabet's
-		if (is_true_color)
-			ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB);
-		else
-			ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB);
-#endif
-	}
+static struct gpio_desc *gpio_lcd_depth;
+static struct gpio_desc *gpio_lcd_on;
+
+static void assabet_set_visual(u32 visual)
+{
+	gpiod_set_value(gpio_lcd_depth, visual == FB_VISUAL_TRUECOLOR);
 }
 
 #ifndef ASSABET_PAL_VIDEO
@@ -373,10 +372,11 @@ static void assabet_lcd_power(int on)
 {
 	if (on && 0) {
 		msleep(500);
-		ASSABET_BCR_set(ASSABET_BCR_LCD_ON);
+		gpiod_set_value(gpio_lcd_on, 1);
 		msleep(500);
-	} else
-		ASSABET_BCR_clear(ASSABET_BCR_LCD_ON);
+	} else {
+		gpiod_set_value(gpio_lcd_on, 0);
+	}
 }
 
 /*
@@ -399,7 +399,7 @@ static struct sa1100fb_mach_info lq039q2ds54_info = {
 
 	.backlight_power = assabet_lcd_backlight_power,
 	.lcd_power = assabet_lcd_power,
-	.set_visual = assabet_lcd_set_visual,
+	.set_visual = assabet_set_visual,
 };
 #else
 static void assabet_pal_backlight_power(int on)
@@ -409,7 +409,6 @@ static void assabet_pal_backlight_power(int on)
 
 static void assabet_pal_power(int on)
 {
-	ASSABET_BCR_clear(ASSABET_BCR_LCD_ON);
 }
 
 static struct sa1100fb_mach_info pal_info = {
@@ -425,7 +424,7 @@ static struct sa1100fb_mach_info pal_info = {
 
 	.backlight_power = assabet_pal_backlight_power,
 	.lcd_power = assabet_pal_power,
-	.set_visual = assabet_lcd_set_visual,
+	.set_visual = assabet_set_visual,
 };
 #endif
 
@@ -559,8 +558,13 @@ static struct gpiod_lookup_table assabet_uart3_gpio_table = {
 
 static void __init assabet_init(void)
 {
+	/* Assabets earler than Phase4 have an inverted signal */
+	if (assabet_phase() < 4)
+		assabet_platform_gpio_table.table[0].flags = GPIO_ACTIVE_LOW;
+
 	gpiod_add_lookup_table(&assabet_uda1341_gpio_table);
 	gpiod_add_lookup_table(&assabet_audio_gpio_table);
+	gpiod_add_lookup_table(&assabet_platform_gpio_table);
 
 	/*
 	 * Ensure that the power supply is in "high power" mode.
@@ -625,11 +629,16 @@ static void __init assabet_init(void)
 	gpiod_add_lookup_table(&assabet_leds_gpio_table);
 	gpio_led_register_device(-1, &assabet_leds_pdata);
 
+	gpio_lcd_depth = gpiod_get(NULL, "lcd-depth", GPIOD_OUT_HIGH);
+	gpio_lcd_on = gpiod_get(NULL, "lcd-on", GPIOD_OUT_LOW);
+	if (!IS_ERR(gpio_lcd_depth) && !IS_ERR(gpio_lcd_on)) {
 #ifndef ASSABET_PAL_VIDEO
-	sa11x0_register_lcd(&lq039q2ds54_info);
+		sa11x0_register_lcd(&lq039q2ds54_info);
 #else
-	sa11x0_register_lcd(&pal_video);
+		sa11x0_register_lcd(&pal_video);
 #endif
+	}
+
 	sa11x0_register_mtd(&assabet_flash_data, assabet_flash_resources,
 			    ARRAY_SIZE(assabet_flash_resources));
 	sa11x0_register_mcp(&assabet_mcp_data);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/3com/3c589_cs.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/net/ethernet/3com/3c589_cs.c b/drivers/net/ethernet/3com/3c589_cs.c
index be58dac0502a..c35573bf5dbf 100644
--- a/drivers/net/ethernet/3com/3c589_cs.c
+++ b/drivers/net/ethernet/3com/3c589_cs.c
@@ -587,7 +587,11 @@ static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
 	outw(skb->len, ioaddr + TX_FIFO);
 	outw(0x00, ioaddr + TX_FIFO);
 	/* ... and the packet rounded to a doubleword. */
+#if 0
 	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
+#else
+	outsw(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2 << 1);
+#endif
 
 	if (inw(ioaddr + TX_FREE) <= 1536) {
 		netif_stop_queue(dev);
@@ -867,8 +871,13 @@ static int el3_rx(struct net_device *dev)
 		       pkt_len, rx_status);
 			if (skb != NULL) {
 				skb_reserve(skb, 2);
+#if 0
 				insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len),
 			(pkt_len+3)>>2);
+#else
+				insw(ioaddr+RX_FIFO, skb_put(skb, pkt_len),
+				     (pkt_len+3)>>2 << 1);
+#endif
 				skb->protocol = eth_type_trans(skb, dev);
 				netif_rx(skb);
 				dev->stats.rx_packets++;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

Allow serial control GPIOs to be used as a wakeup source for the system.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/tty/serial/serial_mctrl_gpio.c | 19 +++++++++++++++++++
 drivers/tty/serial/serial_mctrl_gpio.h | 22 ++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/drivers/tty/serial/serial_mctrl_gpio.c b/drivers/tty/serial/serial_mctrl_gpio.c
index 8855688a5b6c..a03b476e785b 100644
--- a/drivers/tty/serial/serial_mctrl_gpio.c
+++ b/drivers/tty/serial/serial_mctrl_gpio.c
@@ -385,5 +385,24 @@ void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios)
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_disable_irq_wake);
 
+int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios, enum mctrl_gpio_idx gidx)
+{
+	int ret = -EINVAL;
+
+	if (gidx < UART_GPIO_MAX && gpios->irq[gidx])
+		ret = enable_irq_wake(gpios->irq[gidx]);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mctrl_gpio_enable_wake);
+
+void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+			     enum mctrl_gpio_idx gidx)
+{
+	if (gidx < UART_GPIO_MAX && gpios->irq[gidx])
+		disable_irq_wake(gpios->irq[gidx]);
+}
+EXPORT_SYMBOL_GPL(mctrl_gpio_disable_wake);
+
 MODULE_DESCRIPTION("Helpers for controlling modem lines via GPIO");
 MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/serial_mctrl_gpio.h b/drivers/tty/serial/serial_mctrl_gpio.h
index fc76910fb105..07bec3aa2786 100644
--- a/drivers/tty/serial/serial_mctrl_gpio.h
+++ b/drivers/tty/serial/serial_mctrl_gpio.h
@@ -101,6 +101,17 @@ void mctrl_gpio_enable_irq_wake(struct mctrl_gpios *gpios);
  */
 void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios);
 
+/*
+ * Enable wakeup due to state change on the gpio
+ */
+int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios, enum mctrl_gpio_idx gidx);
+
+/*
+ * Disable wakeup due to state change on the gpio
+ */
+void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+			     enum mctrl_gpio_idx gidx);
+
 #else /* GPIOLIB */
 
 static inline
@@ -160,6 +171,17 @@ static inline void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios)
 {
 }
 
+static inline int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios,
+					 enum mctrl_gpio_idx gidx)
+{
+	return 0;
+}
+
+static inline void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+					   enum mctrl_gpio_idx gidx)
+{
+}
+
 #endif /* GPIOLIB */
 
 #endif

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:21 +0100

The iPAQ H3xxx platforms used to wake up on DCD or DSR transitions.
However, at some point this got broken and fails to work today.  Fix
this by using the newly added modem control gpio layer's wakeup
controls.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3xxx.c                | 22 ++-------
 drivers/tty/serial/sa1100.c                 | 76 +++++++++++++++++++++++++++--
 include/linux/platform_data/sa11x0-serial.h |  9 +++-
 3 files changed, 83 insertions(+), 24 deletions(-)

diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index d685f03f51f3..9afa12b1a580 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -96,27 +96,11 @@ static void h3xxx_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
 	}
 }
 
-/*
- * Enable/Disable wake up events for this serial port.
- * Obviously, we only support this on the normal COM port.
- */
-static int h3xxx_uart_set_wake(struct uart_port *port, u_int enable)
-{
-	int err = -EINVAL;
-
-	if (port->mapbase == _Ser3UTCR0) {
-		if (enable)
-			PWER |= PWER_GPIO23 | PWER_GPIO25; /* DCD and CTS */
-		else
-			PWER &= ~(PWER_GPIO23 | PWER_GPIO25); /* DCD and CTS */
-		err = 0;
-	}
-	return err;
-}
-
 static struct sa1100_port_fns h3xxx_port_fns __initdata = {
 	.pm		= h3xxx_uart_pm,
-	.set_wake	= h3xxx_uart_set_wake,
+	.wake		= {
+		[0] = SA11X0_WAKE_CTS | SA11X0_WAKE_DCD,
+	},
 };
 
 static struct gpiod_lookup_table h3xxx_uart3_gpio_table = {
diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c
index 79c794fa6545..93f3c64f4773 100644
--- a/drivers/tty/serial/sa1100.c
+++ b/drivers/tty/serial/sa1100.c
@@ -76,6 +76,7 @@ struct sa1100_port {
 	struct timer_list	timer;
 	unsigned int		old_status;
 	struct mctrl_gpios	*gpios;
+	unsigned		wake;
 };
 
 /*
@@ -622,16 +623,17 @@ static void __init sa1100_init_ports(void)
 
 void sa1100_register_uart_fns(struct sa1100_port_fns *fns)
 {
+	unsigned int i;
+
 	if (fns->get_mctrl)
 		sa1100_pops.get_mctrl = fns->get_mctrl;
 	if (fns->set_mctrl)
 		sa1100_pops.set_mctrl = fns->set_mctrl;
 
 	sa1100_pops.pm       = fns->pm;
-	/*
-	 * FIXME: fns->set_wake is unused - this should be called from
-	 * the suspend() callback if device_may_wakeup(dev)) is set.
-	 */
+
+	for (i = 0; i < NR_PORTS; i++)
+		sa1100_ports[i].wake = fns->wake[i];
 }
 
 void __init sa1100_register_uart(int idx, int port)
@@ -804,19 +806,83 @@ static struct uart_driver sa1100_reg = {
 	.cons			= SA1100_CONSOLE,
 };
 
+static unsigned int sa1100_translate_mctrl_wake_mask(unsigned int wake_bits)
+{
+	unsigned int wake = 0;
+
+	if (wake_bits & SA11X0_WAKE_CTS)
+		wake |= BIT(UART_GPIO_CTS);
+	if (wake_bits & SA11X0_WAKE_DCD)
+		wake |= BIT(UART_GPIO_DCD);
+	if (wake_bits & SA11X0_WAKE_DSR)
+		wake |= BIT(UART_GPIO_DSR);
+	if (wake_bits & SA11X0_WAKE_RI)
+		wake |= BIT(UART_GPIO_RI);
+
+	return wake;
+}
+
 static int sa1100_serial_suspend(struct platform_device *dev, pm_message_t state)
 {
 	struct sa1100_port *sport = platform_get_drvdata(dev);
+	unsigned int i, enabled = 0, wake;
+	int ret;
+
+	wake = sa1100_translate_mctrl_wake_mask(sport->wake);
+
+	for (i = 0; i < UART_GPIO_MAX; i++) {
+		struct gpio_desc *g;
+		int irq;
+
+		if (!(wake & BIT(i)))
+			continue;
+
+		g = mctrl_gpio_to_gpiod(sport->gpios, i);
+		if (!g)
+			continue;
+
+		irq = gpiod_to_irq(g);
+		if (irq > 0) {
+			ret = enable_irq_wake(irq);
+			if (ret)
+				goto disable;
+		}
+
+		enabled |= BIT(i);
+	}
 
 	if (sport)
 		uart_suspend_port(&sa1100_reg, &sport->port);
 
 	return 0;
+
+ disable:
+	for (i = 0; i < UART_GPIO_MAX; i++)
+		if (enabled & BIT(i))
+			mctrl_gpio_disable_wake(sport->gpios, i);
+	return ret;
 }
 
 static int sa1100_serial_resume(struct platform_device *dev)
 {
 	struct sa1100_port *sport = platform_get_drvdata(dev);
+	unsigned int i, wake = sa1100_translate_mctrl_wake_mask(sport->wake);
+
+	for (i = 0; i < UART_GPIO_MAX; i++) {
+		struct gpio_desc *g;
+		int irq;
+
+		if (!(wake & BIT(i)))
+			continue;
+
+		g = mctrl_gpio_to_gpiod(sport->gpios, i);
+		if (!g)
+			continue;
+
+		irq = gpiod_to_irq(g);
+		if (irq > 0)
+			disable_irq_wake(irq);
+	}
 
 	if (sport)
 		uart_resume_port(&sa1100_reg, &sport->port);
@@ -845,6 +911,8 @@ static int sa1100_serial_add_one_port(struct sa1100_port *sport, struct platform
 		sport->gpios = NULL;
 	}
 
+	device_init_wakeup(sport->port.dev, !!sport->wake);
+
 	platform_set_drvdata(dev, sport);
 
 	return uart_add_one_port(&sa1100_reg, &sport->port);
diff --git a/include/linux/platform_data/sa11x0-serial.h b/include/linux/platform_data/sa11x0-serial.h
index 8b79ab08af45..319fcc566626 100644
--- a/include/linux/platform_data/sa11x0-serial.h
+++ b/include/linux/platform_data/sa11x0-serial.h
@@ -12,6 +12,13 @@
 struct uart_port;
 struct uart_info;
 
+enum {
+	SA11X0_WAKE_CTS = BIT(0),
+	SA11X0_WAKE_DCD = BIT(1),
+	SA11X0_WAKE_DSR = BIT(2),
+	SA11X0_WAKE_RI  = BIT(3),
+};
+
 /*
  * This is a temporary structure for registering these
  * functions; it is intended to be discarded after boot.
@@ -20,7 +27,7 @@ struct sa1100_port_fns {
 	void	(*set_mctrl)(struct uart_port *, u_int);
 	u_int	(*get_mctrl)(struct uart_port *);
 	void	(*pm)(struct uart_port *, u_int, u_int);
-	int	(*set_wake)(struct uart_port *, u_int);
+	unsigned wake[3];
 };
 
 #ifdef CONFIG_SERIAL_SA1100

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Add support for a transceiver enable GPIO signal, which allows the
external RS232 transceiver on several platforms to be force-enabled
or force-disabled according to the system state and the hardware
capabilities.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/tty/serial/sa1100.c | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c
index 93f3c64f4773..9593c299b80f 100644
--- a/drivers/tty/serial/sa1100.c
+++ b/drivers/tty/serial/sa1100.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/gpio/consumer.h>
 #include <linux/ioport.h>
 #include <linux/init.h>
 #include <linux/console.h>
@@ -76,6 +77,7 @@ struct sa1100_port {
 	struct timer_list	timer;
 	unsigned int		old_status;
 	struct mctrl_gpios	*gpios;
+	struct gpio_desc	*xcvr_enable;
 	unsigned		wake;
 };
 
@@ -562,6 +564,16 @@ sa1100_verify_port(struct uart_port *port, struct serial_struct *ser)
 	return ret;
 }
 
+static void sa1100_pm(struct uart_port *port, unsigned int state,
+	unsigned int old)
+{
+	struct sa1100_port *sport =
+		container_of(port, struct sa1100_port, port);
+
+	if (sport->xcvr_enable)
+		gpiod_direction_output(sport->xcvr_enable, !state);
+}
+
 static struct uart_ops sa1100_pops = {
 	.tx_empty	= sa1100_tx_empty,
 	.set_mctrl	= sa1100_set_mctrl,
@@ -579,6 +591,7 @@ static struct uart_ops sa1100_pops = {
 	.request_port	= sa1100_request_port,
 	.config_port	= sa1100_config_port,
 	.verify_port	= sa1100_verify_port,
+	.pm		= sa1100_pm,
 };
 
 static struct sa1100_port sa1100_ports[NR_PORTS];
@@ -911,6 +924,20 @@ static int sa1100_serial_add_one_port(struct sa1100_port *sport, struct platform
 		sport->gpios = NULL;
 	}
 
+	sport->xcvr_enable = devm_gpiod_get_optional(sport->port.dev,
+						     "xcvr-enable", GPIOD_ASIS);
+	if (IS_ERR(sport->xcvr_enable)) {
+		int err = PTR_ERR(sport->xcvr_enable);
+
+		dev_err(sport->port.dev, "failed to get xcvr gpio: %d\n",
+			err);
+
+		if (err == -EPROBE_DEFER)
+			return err;
+
+		sport->xcvr_enable = NULL;
+	}
+
 	device_init_wakeup(sport->port.dev, !!sport->wake);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/tty/serial/sa1100.c                 | 2 +-
 include/linux/platform_data/sa11x0-serial.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c
index 9593c299b80f..6a88cd1e3607 100644
--- a/drivers/tty/serial/sa1100.c
+++ b/drivers/tty/serial/sa1100.c
@@ -634,7 +634,7 @@ static void __init sa1100_init_ports(void)
 	PPSR |= PPC_TXD1 | PPC_TXD3;
 }
 
-void sa1100_register_uart_fns(struct sa1100_port_fns *fns)
+void sa1100_register_uart_fns(const struct sa1100_port_fns *fns)
 {
 	unsigned int i;
 
diff --git a/include/linux/platform_data/sa11x0-serial.h b/include/linux/platform_data/sa11x0-serial.h
index 319fcc566626..9ecd9611d90f 100644
--- a/include/linux/platform_data/sa11x0-serial.h
+++ b/include/linux/platform_data/sa11x0-serial.h
@@ -31,10 +31,10 @@ struct sa1100_port_fns {
 };
 
 #ifdef CONFIG_SERIAL_SA1100
-void sa1100_register_uart_fns(struct sa1100_port_fns *fns);
+void sa1100_register_uart_fns(const struct sa1100_port_fns *fns);
 void sa1100_register_uart(int idx, int port);
 #else
-static inline void sa1100_register_uart_fns(struct sa1100_port_fns *fns)
+static inline void sa1100_register_uart_fns(const struct sa1100_port_fns *fns)
 {
 }
 static inline void sa1100_register_uart(int idx, int port)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Constify the sa1100_port_fns structures in all boards.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 2 +-
 arch/arm/mach-sa1100/collie.c  | 2 +-
 arch/arm/mach-sa1100/h3xxx.c   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 5b4ed8cbba27..d22c5b7ff74b 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -721,7 +721,7 @@ static void assabet_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
 	}
 }
 
-static struct sa1100_port_fns assabet_port_fns __initdata = {
+static const struct sa1100_port_fns assabet_port_fns __initconst = {
 	.pm		= assabet_uart_pm,
 };
 
diff --git a/arch/arm/mach-sa1100/collie.c b/arch/arm/mach-sa1100/collie.c
index 466d755d5702..ed4792a50569 100644
--- a/arch/arm/mach-sa1100/collie.c
+++ b/arch/arm/mach-sa1100/collie.c
@@ -182,7 +182,7 @@ static u_int collie_uart_get_mctrl(struct uart_port *port)
 	return ret;
 }
 
-static struct sa1100_port_fns collie_port_fns __initdata = {
+static const struct sa1100_port_fns collie_port_fns __initconst = {
 	.set_mctrl	= collie_uart_set_mctrl,
 	.get_mctrl	= collie_uart_get_mctrl,
 };
diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index 9afa12b1a580..9197019a6f20 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -96,7 +96,7 @@ static void h3xxx_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
 	}
 }
 
-static struct sa1100_port_fns h3xxx_port_fns __initdata = {
+static const struct sa1100_port_fns h3xxx_port_fns __initconst = {
 	.pm		= h3xxx_uart_pm,
 	.wake		= {
 		[0] = SA11X0_WAKE_CTS | SA11X0_WAKE_DCD,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 18 +-----------------
 1 file changed, 1 insertion(+), 17 deletions(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index d22c5b7ff74b..30723da9712a 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -536,6 +536,7 @@ static const struct gpio_keys_platform_data assabet_keys_pdata = {
 static struct gpiod_lookup_table assabet_uart1_gpio_table = {
 	.dev_id = "sa11x0-uart.1",
 	.table = {
+		GPIO_LOOKUP("assabet", 12, "xcvr-enable", GPIO_ACTIVE_HIGH),
 		GPIO_LOOKUP("assabet", 16, "dtr", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("assabet", 17, "rts", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("assabet", 25, "dcd", GPIO_ACTIVE_LOW),
@@ -711,20 +712,6 @@ fixup_assabet(struct tag *tags, char **cmdline)
 }

-static void assabet_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
-{
-	if (port->mapbase == _Ser1UTCR0) {
-		if (state)
-			ASSABET_BCR_clear(ASSABET_BCR_RS232EN);
-		else
-			ASSABET_BCR_set(ASSABET_BCR_RS232EN);
-	}
-}
-
-static const struct sa1100_port_fns assabet_port_fns __initconst = {
-	.pm		= assabet_uart_pm,
-};
-
 static struct map_desc assabet_io_desc[] __initdata = {
   	{	/* Board Control Register */
 		.virtual	= ASSABET_BCR_BASE,
@@ -756,9 +743,6 @@ static void __init assabet_map_io(void)
 		MSC_RdAcc(2) | MSC_WrAcc(2) | MSC_Rec(0)
 		);
 
-	if (!machine_has_neponset())
-		sa1100_register_uart_fns(&assabet_port_fns);
-
 	/*
 	 * When Neponset is attached, the first UART should be
 	 * UART3.  That's what Angel is doing and many documents

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3xxx.c | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index 9197019a6f20..2e43f0ff9948 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -83,21 +83,7 @@ static struct resource h3xxx_flash_resource =
 /*
  * H3xxx uart support
  */
-static void h3xxx_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
-{
-	if (port->mapbase == _Ser3UTCR0) {
-		if (!gpio_request(H3XXX_EGPIO_RS232_ON, "RS232 transceiver")) {
-			gpio_direction_output(H3XXX_EGPIO_RS232_ON, !state);
-			gpio_free(H3XXX_EGPIO_RS232_ON);
-		} else {
-			pr_err("%s: can't request H3XXX_EGPIO_RS232_ON\n",
-				__func__);
-		}
-	}
-}
-
 static const struct sa1100_port_fns h3xxx_port_fns __initconst = {
-	.pm		= h3xxx_uart_pm,
 	.wake		= {
 		[0] = SA11X0_WAKE_CTS | SA11X0_WAKE_DCD,
 	},
@@ -106,6 +92,7 @@ static const struct sa1100_port_fns h3xxx_port_fns __initconst = {
 static struct gpiod_lookup_table h3xxx_uart3_gpio_table = {
 	.dev_id = "sa11x0-uart.3",
 	.table = {
+		GPIO_LOOKUP("htc-egpio", 7, "xcvr-enable", GPIO_ACTIVE_HIGH),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_DCD, "dcd", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_CTS, "cts", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_RTS, "rts", GPIO_ACTIVE_LOW),

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Remove the explicit PWER initialisation: gpio-keys already enables
wake-up for its keys, and so this setting is redundant.  In any case,
the conversion of the SA1100 GPIOs to gpiolib has made this setting
ineffectual.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/assabet.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 30723da9712a..3d8aeda35e1b 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -593,7 +593,6 @@ static void __init assabet_init(void)
 	/*
 	 * Set up registers for sleep mode.
 	 */
-	PWER = PWER_GPIO0;
 	PGSR = GPIO_GPIO(17);
 	PCFR = PCFR_OPDE | PCFR_FP;
 	PSDR = 0;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/common/sa1111.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/arm/common/sa1111.c b/arch/arm/common/sa1111.c
index 550978dc3c50..82dafeb05695 100644
--- a/arch/arm/common/sa1111.c
+++ b/arch/arm/common/sa1111.c
@@ -1177,6 +1177,7 @@ static unsigned char opdiv_table[] = { 1, 4, 2, 8 };
 
 static unsigned int __sa1111_pll_clock(struct sa1111 *sachip)
 {
+	unsigned long rate = clk_get_rate(sachip->clk);
 	unsigned int skcdr, fbdiv, ipdiv, opdiv;
 
 	skcdr = readl_relaxed(sachip->base + SA1111_SKCDR);
@@ -1185,7 +1186,7 @@ static unsigned int __sa1111_pll_clock(struct sa1111 *sachip)
 	ipdiv = ((skcdr & 0x0f80) >> 7) + 2;
 	opdiv = opdiv_table[(skcdr & 0x3000) >> 12];
 
-	return 3686400 * fbdiv / (ipdiv * opdiv);
+	return rate * fbdiv / (ipdiv * opdiv);
 }
 
 /**

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/clock.c |  3 +++
 drivers/tty/serial/sa1100.c  | 16 +++++++++++++++-
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/arch/arm/mach-sa1100/clock.c b/arch/arm/mach-sa1100/clock.c
index a104cfa4b123..d40ce6b0ae05 100644
--- a/arch/arm/mach-sa1100/clock.c
+++ b/arch/arm/mach-sa1100/clock.c
@@ -107,6 +107,9 @@ int __init sa11xx_clk_init(void)
 		return PTR_ERR(hw);
 
 	clk_hw_register_clkdev(hw, "OSTIMER0", NULL);
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.1");
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.2");
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.3");
 
 	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
 	if (!hw)
diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c
index 6a88cd1e3607..77671d589f57 100644
--- a/drivers/tty/serial/sa1100.c
+++ b/drivers/tty/serial/sa1100.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/clk.h>
 #include <linux/gpio/consumer.h>
 #include <linux/ioport.h>
 #include <linux/init.h>
@@ -76,6 +77,7 @@ struct sa1100_port {
 	struct uart_port	port;
 	struct timer_list	timer;
 	unsigned int		old_status;
+	struct clk		*clk;
 	struct mctrl_gpios	*gpios;
 	struct gpio_desc	*xcvr_enable;
 	unsigned		wake;
@@ -340,13 +342,19 @@ static int sa1100_startup(struct uart_port *port)
 		container_of(port, struct sa1100_port, port);
 	int retval;
 
+	retval = clk_prepare_enable(sport->clk);
+	if (retval)
+		return retval;
+
 	/*
 	 * Allocate the IRQ
 	 */
 	retval = request_irq(sport->port.irq, sa1100_int, 0,
 			     "sa11x0-uart", sport);
-	if (retval)
+	if (retval) {
+		clk_disable_unprepare(sport->clk);
 		return retval;
+	}
 
 	/*
 	 * Finally, clear and enable interrupts
@@ -383,6 +391,8 @@ static void sa1100_shutdown(struct uart_port *port)
 	 * Disable all interrupts, port and break condition.
 	 */
 	UART_PUT_UTCR3(sport, 0);
+
+	clk_disable_unprepare(sport->clk);
 }
 
 static void
@@ -938,6 +948,10 @@ static int sa1100_serial_add_one_port(struct sa1100_port *sport, struct platform
 		sport->xcvr_enable = NULL;
 	}
 
+	sport->clk = devm_clk_get(sport->port.dev, NULL);
+	if (IS_ERR(sport->clk))
+		return PTR_ERR(sport->clk);
+
 	device_init_wakeup(sport->port.dev, !!sport->wake);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:22 +0100

From: Russell King <[email-masked]>
---
 drivers/mfd/ipaq-micro.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mfd/ipaq-micro.c b/drivers/mfd/ipaq-micro.c
index c964ea6539aa..f5420e625bf9 100644
--- a/drivers/mfd/ipaq-micro.c
+++ b/drivers/mfd/ipaq-micro.c
@@ -88,6 +88,7 @@ static void micro_rx_msg(struct ipaq_micro *micro, u8 id, int len, u8 *data)
 	case MSG_BACKLIGHT:
 	case MSG_NOTIFY_LED:
 	case MSG_THERMAL_SENSOR:
+	case MSG_SPI_READ:
 	case MSG_BATTERY:
 		/* Handle synchronous messages */
 		if (micro->msg && micro->msg->id == id) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

From: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3xxx.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index 2e43f0ff9948..baf62ef2ad70 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -204,9 +204,22 @@ static struct gpiod_lookup_table h3xxx_pcmcia_gpio_table = {
 	},
 };
 
+static struct gpiod_lookup_table h3xxx_pcmcia_socket0_gpio_table = {
+	.dev_id = "sa11x0-pcmcia",
+	.table = {
+		GPIO_LOOKUP("gpio", H3XXX_GPIO_PCMCIA_CD0,
+			    "detect", GPIO_ACTIVE_LOW),
+		GPIO_LOOKUP("gpio", H3XXX_GPIO_PCMCIA_IRQ0,
+			    "ready", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-gpio", 1, "reset", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 void __init h3xxx_mach_init(void)
 {
 	gpiod_add_lookup_table(&h3xxx_pcmcia_gpio_table);
+	gpiod_add_lookup_table(&h3xxx_pcmcia_socket0_gpio_table);
 	gpiod_add_lookup_table(&h3xxx_uart3_gpio_table);
 	sa1100_register_uart_fns(&h3xxx_port_fns);
 	sa11x0_register_mtd(&h3xxx_flash_data, &h3xxx_flash_resource, 1);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

Add support for iPAQ sleeve device table and module alias support, so
that sleeve drivers can be automatically modprobed into the kernel at
run time.

Signed-off-by: Russell King <[email-masked]>
---
 include/linux/mod_devicetable.h   | 10 ++++++++++
 scripts/mod/devicetable-offsets.c |  4 ++++
 scripts/mod/file2alias.c          | 11 +++++++++++
 3 files changed, 25 insertions(+)

diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 4338b1b4ac44..a7bd02606bb3 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -652,6 +652,16 @@ struct isapnp_device_id {
 	kernel_ulong_t driver_data;	/* data private to the driver */
 };
 
+/**
+ * struct ipaq_option_id - iPAQ sleeve option id
+ * @vendor: 16-bit vendor identifier
+ * @device: 16-bit device identifier
+ */
+struct ipaq_option_id {
+	__u16 vendor;
+	__u16 device;
+};
+
 /**
  * struct amba_id - identifies a device on an AMBA bus
  * @id: The significant bits if the hardware device ID
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c
index 518200813d4e..d2f0872d9f3c 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -178,6 +178,10 @@ int main(void)
 	DEVID_FIELD(ipack_device_id, vendor);
 	DEVID_FIELD(ipack_device_id, device);
 
+	DEVID(ipaq_option_id);
+	DEVID_FIELD(ipaq_option_id, vendor);
+	DEVID_FIELD(ipaq_option_id, device);
+
 	DEVID(amba_id);
 	DEVID_FIELD(amba_id, id);
 	DEVID_FIELD(amba_id, mask);
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 5d1c61fa5a55..af2fd24b441a 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1140,6 +1140,16 @@ static void append_nibble_mask(char **outp,
 	*outp = p;
 }
 
+/* Looks like: ipaq:vNdN */
+static int do_ipaq_entry(const char *filename, void *symval, char *alias)
+{
+	DEF_FIELD(symval, ipaq_option_id, vendor);
+	DEF_FIELD(symval, ipaq_option_id, device);
+
+	sprintf(alias, "ipaq:v%04Xd%04X", vendor, device);
+	return 1;
+}
+
 /*
  * looks like: "amba:dN"
  *
@@ -1562,6 +1572,7 @@ static const struct devtable devtable[] = {
 	{"zorro", SIZE_zorro_device_id, do_zorro_entry},
 	{"isapnp", SIZE_isapnp_device_id, do_isapnp_entry},
 	{"ipack", SIZE_ipack_device_id, do_ipack_entry},
+	{"ipaq", SIZE_ipaq_option_id, do_ipaq_entry},
 	{"amba", SIZE_amba_id, do_amba_entry},
 	{"mipscdmm", SIZE_mips_cdmm_device_id, do_mips_cdmm_entry},
 	{"x86cpu", SIZE_x86_cpu_id, do_x86cpu_entry},

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/Makefile           |   3 +
 arch/arm/mach-sa1100/h3600.c            |  14 ++
 arch/arm/mach-sa1100/h3xxx-sleeve.c     | 341 ++++++++++++++++++++++++++++++++
 arch/arm/mach-sa1100/h3xxx.c            |   6 +
 arch/arm/mach-sa1100/ipaq-sleeve-bus.c  | 124 ++++++++++++
 arch/arm/mach-sa1100/ipaq-sleeve-cf.c   |  70 +++++++
 arch/arm/mach-sa1100/ipaq-sleeve-dual.c |  68 +++++++
 drivers/pcmcia/sa1100_h3600.c           |  60 +-----
 include/linux/ipaq-sleeve.h             |  34 ++++
 9 files changed, 661 insertions(+), 59 deletions(-)
 create mode 100644 arch/arm/mach-sa1100/h3xxx-sleeve.c
 create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-bus.c
 create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-cf.c
 create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-dual.c
 create mode 100644 include/linux/ipaq-sleeve.h

diff --git a/arch/arm/mach-sa1100/Makefile b/arch/arm/mach-sa1100/Makefile
index b5816d675152..1ece35b1cc2d 100644
--- a/arch/arm/mach-sa1100/Makefile
+++ b/arch/arm/mach-sa1100/Makefile
@@ -11,6 +11,9 @@ obj-$(CONFIG_SA1100_ASSABET)		+= assabet.o
 obj-$(CONFIG_ASSABET_NEPONSET)		+= neponset.o
 obj-$(CONFIG_SA1100_COLLIE)		+= collie.o
 obj-$(CONFIG_SA1100_H3600)		+= h3600.o h3xxx.o
+obj-m					+= h3xxx-sleeve.o ipaq-sleeve-bus.o \
+					   ipaq-sleeve-cf.o ipaq-sleeve-dual.o
+
 obj-$(CONFIG_SA1100_JORNADA720)		+= jornada720.o
 obj-$(CONFIG_SA1100_JORNADA720_SSP)	+= jornada720_ssp.o
 
diff --git a/arch/arm/mach-sa1100/h3600.c b/arch/arm/mach-sa1100/h3600.c
index 1cfc0b1fa41c..2663ced6452e 100644
--- a/arch/arm/mach-sa1100/h3600.c
+++ b/arch/arm/mach-sa1100/h3600.c
@@ -114,8 +114,22 @@ static void __init h3600_map_io(void)
 	h3xxx_map_io();
 }
 
+static struct gpiod_lookup_table h3600_sleeve_gpio_table = {
+	.dev_id = "ipaq-h3xxx-sleeve",
+	.table = {
+		GPIO_LOOKUP("gpio", H3600_GPIO_OPT_DET,
+			    "option-detect", GPIO_ACTIVE_LOW),
+		GPIO_LOOKUP("htc-egpio", 2, "option-reset", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 4, "nvram-on", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 5, "option-on", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 static void __init h3600_mach_init(void)
 {
+	gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
+
 	h3xxx_mach_init();
 
 	sa11x0_register_lcd(&h3600_lcd_info);
diff --git a/arch/arm/mach-sa1100/h3xxx-sleeve.c b/arch/arm/mach-sa1100/h3xxx-sleeve.c
new file mode 100644
index 000000000000..ed0edaf43d9c
--- /dev/null
+++ b/arch/arm/mach-sa1100/h3xxx-sleeve.c
@@ -0,0 +1,341 @@
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/ipaq-micro.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/ipaq-sleeve.h>
+
+#define SLEEVE_POWER_DELAY_MS 10
+#define SLEEVE_NVRAM_POWER_MS 5
+
+struct opt_info {
+	struct device *dev;
+	struct ipaq_option_id id;
+	struct delayed_work detect_work;
+	bool detect_disabled;
+	bool sleeve_changed;
+	bool sleeve_present;
+	bool sleeve_wakeup;
+	struct gpio_desc *detect;
+	struct gpio_desc *nvram_on;
+	struct gpio_desc *opt_on;
+	struct gpio_desc *opt_reset;
+};
+
+static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
+{
+	struct device *mdev = bus_find_device_by_name(&platform_bus_type, NULL,
+						      "ipaq-h3xxx-micro");
+	struct ipaq_micro *micro;
+	struct ipaq_micro_msg msg;
+
+	memset(buf, 0, len);
+
+	if (!mdev || !mdev->driver)
+		return -EINVAL;
+
+	micro = dev_get_drvdata(mdev);
+	if (!micro)
+		return -EINVAL;
+
+	memset(&msg, 0, sizeof(msg));
+
+	while (len) {
+		size_t this_len = len;
+
+		if (this_len > 8)
+			this_len = 8;
+
+		msg.id = MSG_SPI_READ;
+		msg.tx_len = 3;
+		msg.tx_data[0] = addr;
+		msg.tx_data[1] = addr >> 8;
+		msg.tx_data[2] = this_len;
+
+		ipaq_micro_tx_msg_sync(micro, &msg);
+
+		memcpy(buf, msg.rx_data, msg.rx_len);
+
+		buf += msg.rx_len;
+		len -= msg.rx_len;
+		addr += msg.rx_len;
+	}
+
+	return 0;
+}
+
+static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id)
+{
+	__le16 vendor, device;
+	int ret;
+
+	/* Enable the CF bus */
+	gpiod_set_value_cansleep(opt->nvram_on, 1);
+
+	/* Wait for power to stabilise */
+	msleep(SLEEVE_NVRAM_POWER_MS);
+
+	ret = ipaq_option_spi_read(&vendor, 6, 2);
+	if (ret)
+		goto err;
+
+	ret = ipaq_option_spi_read(&device, 8, 2);
+	if (ret)
+		goto err;
+
+	id->vendor = le16_to_cpu(vendor);
+	id->device = le16_to_cpu(device);
+
+ err:
+	gpiod_set_value_cansleep(opt->nvram_on, 0);
+	return ret;
+}
+
+static void ipaq_option_power_on(struct opt_info *opt, bool noirq)
+{
+	gpiod_set_value(opt->opt_reset, 1);
+	gpiod_set_value(opt->opt_on, 1);
+	gpiod_set_value(opt->opt_reset, 0);
+
+	if (noirq)
+		mdelay(SLEEVE_POWER_DELAY_MS);
+	else
+		msleep(SLEEVE_POWER_DELAY_MS);
+}
+
+static void ipaq_option_power_off(struct opt_info *opt, bool noirq)
+{
+	gpiod_set_value(opt->opt_reset, 1);
+	gpiod_set_value(opt->opt_on, 0);
+}
+
+static void ipaq_option_insert(struct opt_info *opt)
+{
+	int ret;
+
+	ret = ipaq_option_read_id(opt, &opt->id);
+	if (ret)
+		return;
+
+	dev_info(opt->dev, "Option pack %04x:%04x detected\n",
+		 opt->id.vendor, opt->id.device);
+
+	ipaq_option_power_on(opt, false);
+
+	if (!opt->sleeve_present)
+		ipaq_option_device_add(opt->dev, opt->id);
+	opt->sleeve_present = true;
+}
+
+static void ipaq_option_remove(struct opt_info *opt)
+{
+	if (opt->sleeve_present)
+		ipaq_option_device_del();
+	opt->sleeve_present = false;
+
+	ipaq_option_power_off(opt, false);
+}
+
+static void ipaq_option_work(struct work_struct *work)
+{
+	struct opt_info *opt = container_of(work, struct opt_info, detect_work.work);
+	int present = gpiod_get_value_cansleep(opt->detect);
+
+	if (opt->sleeve_changed)
+		ipaq_option_remove(opt);
+
+	if (present)
+		ipaq_option_insert(opt);
+	else
+		ipaq_option_remove(opt);
+}
+
+static irqreturn_t ipaq_option_irq(int irq, void *data)
+{
+	struct opt_info *opt = data;
+
+	dev_info(opt->dev, "irq: option pack: %u\n", gpiod_get_value(opt->detect));
+
+	if (!opt->detect_disabled)
+		mod_delayed_work(system_wq, &opt->detect_work,
+				 msecs_to_jiffies(100));
+
+	return IRQ_HANDLED;
+}
+
+static int ipaq_h3xxx_sleeve_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct opt_info *opt;
+	int irq, ret;
+
+	opt = devm_kzalloc(dev, sizeof(*opt), GFP_KERNEL);
+	if (!opt)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, opt);
+	opt->dev = dev;
+	INIT_DELAYED_WORK(&opt->detect_work, ipaq_option_work);
+
+	opt->nvram_on = devm_gpiod_get(dev, "nvram-on", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->nvram_on))
+		return PTR_ERR(opt->nvram_on);
+
+	opt->opt_on = devm_gpiod_get(dev, "option-on", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->opt_on))
+		return PTR_ERR(opt->opt_on);
+
+	opt->opt_reset = devm_gpiod_get(dev, "option-reset", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->opt_reset))
+		return PTR_ERR(opt->opt_reset);
+
+	opt->detect = devm_gpiod_get_optional(dev, "option-detect", GPIOD_IN);
+	if (IS_ERR(opt->detect))
+		return PTR_ERR(opt->detect);
+
+	if (opt->detect) {
+		irq = gpiod_to_irq(opt->detect);
+		if (irq < 0)
+			return irq;
+
+		ret = devm_request_irq(dev, irq, ipaq_option_irq,
+					IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+					dev_name(dev), opt);
+		if (ret)
+			return ret;
+	}
+
+	schedule_delayed_work(&opt->detect_work, 0);
+	flush_delayed_work(&opt->detect_work);
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_remove(struct platform_device *pdev)
+{
+	struct opt_info *opt = platform_get_drvdata(pdev);
+
+	opt->detect_disabled = true;
+	cancel_delayed_work_sync(&opt->detect_work);
+
+	ipaq_option_remove(opt);
+
+	return 0;
+}
+
+static int ipaq_h3xxx_check_wakeup(struct device *dev, void *data)
+{
+	if (device_may_wakeup(dev))
+		return 1;
+
+	return device_for_each_child(dev, data, ipaq_h3xxx_check_wakeup);
+}
+
+static int ipaq_h3xxx_sleeve_suspend(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+	int may_wakeup;
+
+	opt->detect_disabled = true;
+	cancel_delayed_work_sync(&opt->detect_work);
+
+	/* Disable sleeve power if device isn't a wakeup source */
+	may_wakeup = device_for_each_child(dev, NULL, ipaq_h3xxx_check_wakeup);
+
+	opt->sleeve_wakeup = may_wakeup;
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_suspend_noirq(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+
+	if (!opt->sleeve_wakeup && opt->sleeve_present) {
+		ipaq_option_power_off(opt, true);
+
+		/*
+		 * Give the power time to collapse before bringing the
+		 * reset signal to logic 0.
+		 */
+		mdelay(1);
+		gpiod_set_raw_value(opt->opt_reset, 0);
+	}
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+
+	if (!opt->sleeve_wakeup && gpiod_get_value(opt->detect)) {
+		/*
+		 * We can't check the ID of the sleeve here, so assume that
+		 * it is the same sleeve inserted, and power it up.  This
+		 * avoids an eject/insert cycle on any inserted PCMCIA card.
+		 */
+		ipaq_option_power_on(opt, true);
+	}
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_resume(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+	struct ipaq_option_id id;
+	int ret;
+
+	if (opt->sleeve_present && gpiod_get_value_cansleep(opt->detect)) {
+		ret = ipaq_option_read_id(opt, &id);
+		if (ret) {
+			dev_warn(opt->dev, "unable to read sleeve id: %d\n",
+				 ret);
+			opt->sleeve_changed = true;
+			goto finish;
+		}
+
+		if (opt->id.vendor != id.vendor ||
+		    opt->id.device != id.device) {
+			/* sleeve changed */
+			dev_warn(opt->dev, "sleeve changed\n");
+			opt->sleeve_changed = true;
+		} else {
+			dev_info(opt->dev, "same sleeve type inserted\n");
+			opt->detect_disabled = false;
+			return 0;
+		}
+	}
+
+finish:
+	opt->detect_disabled = false;
+	schedule_delayed_work(&opt->detect_work, 0);
+	flush_delayed_work(&opt->detect_work);
+
+	return 0;
+}
+
+static struct dev_pm_ops ipaq_h3xxx_sleeve_pm = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend_noirq,
+				      ipaq_h3xxx_sleeve_resume_noirq)
+	SET_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend,
+				ipaq_h3xxx_sleeve_resume)
+};
+
+static struct platform_driver ipaq_h3xxx_sleeve = {
+	.driver = {
+		.name = "ipaq-h3xxx-sleeve",
+		.pm = &ipaq_h3xxx_sleeve_pm,
+	},
+	.probe = ipaq_h3xxx_sleeve_probe,
+	.remove = ipaq_h3xxx_sleeve_remove,
+};
+
+module_platform_driver(ipaq_h3xxx_sleeve);
+
+MODULE_ALIAS("platform:ipaq-h3xxx-sleeve");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index baf62ef2ad70..9a44a31626b9 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -183,10 +183,16 @@ struct platform_device h3xxx_micro_asic = {
 	.num_resources = ARRAY_SIZE(h3xxx_micro_resources),
 };
 
+static struct platform_device h3xxx_sleeve = {
+	.name = "ipaq-h3xxx-sleeve",
+	.id = -1,
+};
+
 static struct platform_device *h3xxx_devices[] = {
 	&h3xxx_egpio,
 	&h3xxx_keys,
 	&h3xxx_micro_asic,
+	&h3xxx_sleeve,
 };
 
 static struct gpiod_lookup_table h3xxx_pcmcia_gpio_table = {
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
new file mode 100644
index 000000000000..9bf3201002b6
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
@@ -0,0 +1,124 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define to_ipaq_option_driver(x) \
+	container_of(x, struct ipaq_option_driver, driver)
+
+static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%04x\n", idev->id.vendor);
+}
+static DEVICE_ATTR_RO(vendor);
+
+static ssize_t device_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%04x\n", idev->id.device);
+}
+static DEVICE_ATTR_RO(device);
+
+static struct attribute *ipaq_option_sleeve_attrs[] = {
+	&dev_attr_vendor.attr,
+	&dev_attr_device.attr,
+	NULL,
+};
+
+static const struct attribute_group ipaq_option_sleeve_dev_group = {
+	.attrs = ipaq_option_sleeve_attrs,
+};
+
+static const struct attribute_group *ipaq_option_sleeve_dev_groups[] = {
+	&ipaq_option_sleeve_dev_group,
+	NULL,
+};
+
+static int ipaq_option_sleeve_match(struct device *dev, struct device_driver *drv)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_option_driver *idrv = to_ipaq_option_driver(drv);
+	const struct ipaq_option_id *id;
+
+	for (id = idrv->id_table; id->vendor || id->device; id++)
+		if (id->vendor == idev->id.vendor &&
+		    id->device == idev->id.device)
+			return 1;
+
+	return 0;
+}
+
+static int ipaq_option_sleeve_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+
+	if (!dev)
+		return -ENODEV;
+
+	if (add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
+			   idev->id.vendor, idev->id.device))
+		return -ENOMEM;
+
+	return 0;
+}
+
+struct bus_type ipaq_option_sleeve_bus = {
+	.name = "ipaq-sleeve",
+	.dev_groups = ipaq_option_sleeve_dev_groups,
+	.match = ipaq_option_sleeve_match,
+	.uevent = ipaq_option_sleeve_uevent,
+};
+EXPORT_SYMBOL_GPL(ipaq_option_sleeve_bus);
+
+static void ipaq_option_release(struct device *dev)
+{
+	struct ipaq_option_device *opt_dev = to_ipaq_option_device(dev);
+
+	kfree(opt_dev);
+}
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
+{
+	struct ipaq_option_device *idev;
+
+	idev = kzalloc(sizeof(*idev), GFP_KERNEL);
+	if (!idev)
+		return -ENOMEM;
+
+	idev->id = id;
+	device_initialize(&idev->dev);
+	dev_set_name(&idev->dev, "sleeve");
+	idev->dev.parent = parent;
+	idev->dev.release = ipaq_option_release;
+	idev->dev.bus = &ipaq_option_sleeve_bus;
+
+	return device_add(&idev->dev);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_add);
+
+static int ipaq_option_device_remove(struct device *dev, void *data)
+{
+	device_unregister(dev);
+	return 0;
+}
+
+void ipaq_option_device_del(void)
+{
+	bus_for_each_dev(&ipaq_option_sleeve_bus, NULL, NULL,
+			 ipaq_option_device_remove);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_del);
+
+static int ipaq_option_sleeve_init(void)
+{
+	return bus_register(&ipaq_option_sleeve_bus);
+}
+core_initcall(ipaq_option_sleeve_init);
+
+static void ipaq_option_sleeve_exit(void)
+{
+	bus_unregister(&ipaq_option_sleeve_bus);
+}
+module_exit(ipaq_option_sleeve_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-cf.c b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
new file mode 100644
index 000000000000..8f0b8ae8b75c
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
@@ -0,0 +1,70 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_cf_data {
+	struct platform_device *skt;
+};
+
+static int ipaq_sleeve_cf_probe(struct device *dev)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_sleeve_cf_data *cf;
+	struct platform_device_info pinfo = {
+		.parent = dev,
+		.name = "sa11x0-pcmcia",
+		.id = 0,
+		.data = &idev->id,
+		.size_data = sizeof(idev->id),
+	};
+
+	cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+	if (!cf)
+		return -ENOMEM;
+
+	cf->skt = platform_device_register_full(&pinfo);
+	if (IS_ERR(cf->skt)) {
+		dev_err(dev, "unable to register pcmcia device: %ld\n",
+			PTR_ERR(cf->skt));
+		return PTR_ERR(cf->skt);
+	}
+
+	dev_set_drvdata(dev, cf);
+
+	return 0;
+}
+
+static int ipaq_sleeve_cf_remove(struct device *dev)
+{
+	struct ipaq_sleeve_cf_data *cf = dev_get_drvdata(dev);
+
+	platform_device_unregister(cf->skt);
+
+	return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_cf_ids[] = {
+	{	/* Compaq CompactFlash sleeve */
+		.vendor = 0x1125,
+		.device = 0xcf11,
+	}, {	/* Compaq single PCMCIA sleeve */
+		.vendor = 0x1125,
+		.device = 0xd7c3,
+	}, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_cf = {
+	.id_table = ipaq_sleeve_cf_ids,
+	.driver = {
+		.name = "ipaq-sleeve-cf",
+		.probe = ipaq_sleeve_cf_probe,
+		.remove = ipaq_sleeve_cf_remove,
+	},
+};
+
+module_driver(ipaq_sleeve_cf, ipaq_option_driver_register,
+	ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_cf_ids);
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-dual.c b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
new file mode 100644
index 000000000000..dd4c3f756885
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
@@ -0,0 +1,68 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_dual_data {
+	struct platform_device *skt;
+};
+
+static int ipaq_sleeve_dual_probe(struct device *dev)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_sleeve_dual_data *cf;
+	struct platform_device_info pinfo = {
+		.parent = dev,
+		.name = "sa11x0-pcmcia",
+		.id = -1,
+	};
+
+	cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+	if (!cf)
+		return -ENOMEM;
+
+	pinfo.data = &idev->id;
+	pinfo.size_data = sizeof(idev->id);
+
+	cf->skt = platform_device_register_full(&pinfo);
+	if (IS_ERR(cf->skt)) {
+		dev_err(dev, "unable to register pcmcia device: %ld\n",
+			PTR_ERR(cf->skt));
+		return PTR_ERR(cf->skt);
+	}
+
+	dev_set_drvdata(dev, cf);
+
+	return 0;
+}
+
+static int ipaq_sleeve_dual_remove(struct device *dev)
+{
+	struct ipaq_sleeve_dual_data *cf = dev_get_drvdata(dev);
+
+	platform_device_unregister(cf->skt);
+
+	return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_dual_ids[] = {
+	{	/* Compaq dual PCMCIA sleeve */
+		.vendor = 0x1125,
+		.device = 0x0001,
+	}, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_dual = {
+	.id_table = ipaq_sleeve_dual_ids,
+	.driver = {
+		.name = "ipaq-sleeve-dual",
+		.probe = ipaq_sleeve_dual_probe,
+		.remove = ipaq_sleeve_dual_remove,
+	},
+};
+
+module_driver(ipaq_sleeve_dual, ipaq_option_driver_register,
+	ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_dual_ids);
diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c
index 10cb99c20a7f..91d4b5ab48c6 100644
--- a/drivers/pcmcia/sa1100_h3600.c
+++ b/drivers/pcmcia/sa1100_h3600.c
@@ -33,24 +33,6 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 
 	switch (skt->nr) {
 	case 0:
-		err = gpio_request(H3XXX_EGPIO_OPT_NVRAM_ON, "OPT NVRAM ON");
-		if (err)
-			goto err01;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		if (err)
-			goto err03;
-		err = gpio_request(H3XXX_EGPIO_OPT_ON, "OPT ON");
-		if (err)
-			goto err03;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_ON, 0);
-		if (err)
-			goto err04;
-		err = gpio_request(H3XXX_EGPIO_OPT_RESET, "OPT RESET");
-		if (err)
-			goto err04;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_RESET, 0);
-		if (err)
-			goto err05;
 		err = gpio_request(H3XXX_EGPIO_CARD_RESET, "PCMCIA CARD RESET");
 		if (err)
 			goto err05;
@@ -64,11 +46,7 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 	return 0;
 
 err06:	gpio_free(H3XXX_EGPIO_CARD_RESET);
-err05:	gpio_free(H3XXX_EGPIO_OPT_RESET);
-err04:	gpio_free(H3XXX_EGPIO_OPT_ON);
-err03:	gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
-err01:	gpio_free(H3XXX_GPIO_PCMCIA_IRQ0);
-	return err;
+err05:	return err;
 }
 
 static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
@@ -76,14 +54,7 @@ static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
 	switch (skt->nr) {
 	case 0:
 		/* Disable CF bus: */
-		gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
-
 		gpio_free(H3XXX_EGPIO_CARD_RESET);
-		gpio_free(H3XXX_EGPIO_OPT_RESET);
-		gpio_free(H3XXX_EGPIO_OPT_ON);
-		gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
 		break;
 	case 1:
 		break;
@@ -115,41 +86,12 @@ h3600_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_
 	return 0;
 }
 
-static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
-{
-	/* Enable CF bus: */
-	gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 1);
-	gpio_set_value(H3XXX_EGPIO_OPT_ON, 1);
-	gpio_set_value(H3XXX_EGPIO_OPT_RESET, 0);
-
-	msleep(10);
-}
-
-static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
-{
-	/*
-	 * FIXME:  This doesn't fit well.  We don't have the mechanism in
-	 * the generic PCMCIA layer to deal with the idea of two sockets
-	 * on one bus.  We rely on the cs.c behaviour shutting down
-	 * socket 0 then socket 1.
-	 */
-	if (skt->nr == 1) {
-		gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		/* hmm, does this suck power? */
-		gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
-	}
-}
-
 struct pcmcia_low_level h3600_pcmcia_ops = { 
 	.owner			= THIS_MODULE,
 	.hw_init		= h3600_pcmcia_hw_init,
 	.hw_shutdown		= h3600_pcmcia_hw_shutdown,
 	.socket_state		= h3600_pcmcia_socket_state,
 	.configure_socket	= h3600_pcmcia_configure_socket,
-
-	.socket_init		= h3600_pcmcia_socket_init,
-	.socket_suspend		= h3600_pcmcia_socket_suspend,
 };
 
 int pcmcia_h3600_init(struct device *dev)
diff --git a/include/linux/ipaq-sleeve.h b/include/linux/ipaq-sleeve.h
new file mode 100644
index 000000000000..fb669fad0c4e
--- /dev/null
+++ b/include/linux/ipaq-sleeve.h
@@ -0,0 +1,34 @@
+#ifndef LINUX_IPAQ_SLEEVE_H
+#define LINUX_IPAQ_SLEEVE_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+struct ipaq_option_device {
+	struct ipaq_option_id id;
+	struct device dev;
+};
+#define to_ipaq_option_device(x) container_of(x, struct ipaq_option_device, dev)
+
+struct ipaq_option_driver {
+	const struct ipaq_option_id *id_table;
+	struct device_driver driver;
+};
+
+extern struct bus_type ipaq_option_sleeve_bus;
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id);
+void ipaq_option_device_del(void);
+
+static inline int ipaq_option_driver_register(struct ipaq_option_driver *drv)
+{
+	drv->driver.bus = &ipaq_option_sleeve_bus;
+	return driver_register(&drv->driver);
+}
+
+static inline void ipaq_option_driver_unregister(struct ipaq_option_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+
+#endif

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

From: Russell King <[email-masked]>
---
 drivers/pcmcia/Makefile       |   2 +-
 drivers/pcmcia/l1110.c        | 109 ++++++++++++++++++++++++++++++++++++++++++
 drivers/pcmcia/l1110.h        |  15 ++++++
 drivers/pcmcia/sa1100_h3600.c |  93 ++++++++++++-----------------------
 4 files changed, 156 insertions(+), 63 deletions(-)
 create mode 100644 drivers/pcmcia/l1110.c
 create mode 100644 drivers/pcmcia/l1110.h

diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
index c9d51b150682..0fbed0d213ab 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -40,7 +40,7 @@ sa1111_cs-$(CONFIG_SA1100_JORNADA720)		+= sa1111_jornada720.o
 
 sa1100_cs-y					+= sa1100_generic.o
 sa1100_cs-$(CONFIG_SA1100_COLLIE)		+= pxa2xx_sharpsl.o
-sa1100_cs-$(CONFIG_SA1100_H3600)		+= sa1100_h3600.o
+sa1100_cs-$(CONFIG_SA1100_H3600)		+= sa1100_h3600.o l1110.o
 
 pxa2xx-obj-$(CONFIG_PXA_SHARPSL)		+= pxa2xx_sharpsl.o
 obj-$(CONFIG_PCMCIA_PXA2XX)			+= pxa2xx_base.o $(pxa2xx-obj-y)
diff --git a/drivers/pcmcia/l1110.c b/drivers/pcmcia/l1110.c
new file mode 100644
index 000000000000..67507019d3db
--- /dev/null
+++ b/drivers/pcmcia/l1110.c
@@ -0,0 +1,109 @@
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include "l1110.h"
+#include "soc_common.h"
+
+enum {
+	PRS_S1 = BIT(0),
+	PRS_S2 = BIT(1),
+	PRS_S3 = BIT(2),
+	PRS_S4 = BIT(3),
+	PRS_BVD1 = BIT(4),
+	PRS_BVD2 = BIT(5),
+	PRS_VS1 = BIT(6),
+	PRS_VS2 = BIT(7),
+	PRS_RDY = BIT(8),
+	PRS_CD1 = BIT(9),
+	PRS_CD2 = BIT(10),
+
+	PRC_S1 = PRS_S1,	/* x1vcc - TPS2205 D2/D7 */
+	PRC_S2 = PRS_S2,	/* x0vcc - TPS2205 D3/D6 */
+	PRC_S3 = PRS_S3,	/* x0vpp - TPS2205 D1/D5 */
+	PRC_S4 = PRS_S4,	/* x1vpp - TPS2205 D0/D4 */
+	PRC_RST = BIT(4),
+	PRC_APOE = BIT(5), /* auto power off enable */
+	PRC_CFE = BIT(6), /* CF enable: A25:11 driven high */
+	PRC_SOE = BIT(7), /* signal output enable */
+	PRC_SSP = BIT(8), /* socket select polarity */
+};
+
+struct l1110 {
+	void __iomem *reg;
+	u16 prc;
+};
+
+int l1110_init(struct device *dev, struct l1110 **ptr, resource_size_t base,
+	unsigned int ssp)
+{
+	struct l1110 *l;
+
+	l = devm_kzalloc(dev, sizeof(*l), GFP_KERNEL);
+	if (!l)
+		return -ENOMEM;
+
+	l->prc = PRC_APOE | (ssp ? PRC_SSP : 0);
+	l->reg = devm_ioremap(dev, base, 2);
+	if (!l->reg)
+		return -ENOMEM;
+
+	*ptr = l;
+
+	writew_relaxed(l->prc, l->reg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l1110_init);
+
+void l1110_socket_state(struct l1110 *l, struct pcmcia_state *state)
+{
+	u16 prs = readw_relaxed(l->reg);
+
+	state->bvd1 = !!(prs & PRS_BVD1);
+	state->bvd2 = !!(prs & PRS_BVD2);
+	state->vs_3v = !(prs & PRS_VS1);
+	state->vs_Xv = !(prs & PRS_VS2);
+}
+EXPORT_SYMBOL_GPL(l1110_socket_state);
+
+int l1110_configure_socket(struct l1110 *l, const socket_state_t *state)
+{
+	u16 prc = l->prc;
+
+	switch (state->Vcc) {
+	case 0:
+		break;
+	case 33:
+		prc |= PRC_S1;
+		break;
+	case 50:
+		prc |= PRC_S2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (state->Vpp) {
+		if (state->Vpp == 12)
+			prc |= PRC_S4;
+		else if (state->Vpp == state->Vcc)
+			prc |= PRC_S3;
+		else
+			return -EINVAL;
+	}
+
+	if (state->flags & SS_RESET)
+		prc |= PRC_RST;
+
+	if (state->flags & SS_OUTPUT_ENA)
+		prc |= PRC_SOE;
+
+	writew_relaxed(prc, l->reg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l1110_configure_socket);
+
+MODULE_AUTHOR("Russell King <[email-masked]>");
+MODULE_DESCRIPTION("LinkUp Systems L1110 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/l1110.h b/drivers/pcmcia/l1110.h
new file mode 100644
index 000000000000..2e404f64836a
--- /dev/null
+++ b/drivers/pcmcia/l1110.h
@@ -0,0 +1,15 @@
+#ifndef L1110_H
+#define L1110_H
+
+#include <linux/types.h>
+#include <pcmcia/ss.h>
+
+struct l1110;
+struct pcmcia_state;
+
+int l1110_init(struct device *dev, struct l1110 **ptr, resource_size_t base,
+	unsigned int ssp);
+void l1110_socket_state(struct l1110 *l, struct pcmcia_state *state);
+int l1110_configure_socket(struct l1110 *l, const socket_state_t *state);
+
+#endif
diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c
index 91d4b5ab48c6..8e25942010c6 100644
--- a/drivers/pcmcia/sa1100_h3600.c
+++ b/drivers/pcmcia/sa1100_h3600.c
@@ -5,23 +5,19 @@
  * PCMCIA implementation routines for H3600
  *
  */
-#include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/device.h>
-#include <linux/interrupt.h>
-#include <linux/init.h>
-#include <linux/delay.h>
-#include <linux/gpio.h>
+#include <linux/mod_devicetable.h>
 
-#include <mach/hardware.h>
-#include <asm/irq.h>
 #include <asm/mach-types.h>
-#include <mach/h3xxx.h>
 
+#include "l1110.h"
 #include "sa1100_generic.h"
 
-static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+static int l1110_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 {
+	struct device *dev = skt->socket.dev.parent;
+	struct l1110 *l;
 	int err;
 
 	skt->stat[SOC_STAT_CD].name = skt->nr ? "pcmcia1-detect" : "pcmcia0-detect";
@@ -31,75 +27,48 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 	if (err)
 		return err;
 
-	switch (skt->nr) {
-	case 0:
-		err = gpio_request(H3XXX_EGPIO_CARD_RESET, "PCMCIA CARD RESET");
-		if (err)
-			goto err05;
-		err = gpio_direction_output(H3XXX_EGPIO_CARD_RESET, 0);
-		if (err)
-			goto err06;
-		break;
-	case 1:
-		break;
-	}
-	return 0;
+	err = l1110_init(dev, &l, skt->nr ? 0x19000000 : 0x1a000000,
+			 skt->nr);
+	if (err)
+		return err;
 
-err06:	gpio_free(H3XXX_EGPIO_CARD_RESET);
-err05:	return err;
-}
+	skt->socket.driver_data = l;
 
-static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
-{
-	switch (skt->nr) {
-	case 0:
-		/* Disable CF bus: */
-		gpio_free(H3XXX_EGPIO_CARD_RESET);
-		break;
-	case 1:
-		break;
-	}
+	return 0;
 }
 
-static void
-h3600_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state)
+static void l1110_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+	struct pcmcia_state *state)
 {
-	state->bvd1 = 0;
-	state->bvd2 = 0;
-	state->vs_3v = 0;
-	state->vs_Xv = 0;
+	struct l1110 *l = skt->socket.driver_data;
+
+	l1110_socket_state(l, state);
 }
 
-static int
-h3600_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state)
+static int l1110_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+	const socket_state_t *state)
 {
-	if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) {
-		printk(KERN_ERR "h3600_pcmcia: unrecognized Vcc %u.%uV\n",
-		       state->Vcc / 10, state->Vcc % 10);
-		return -1;
-	}
-
-	gpio_set_value(H3XXX_EGPIO_CARD_RESET, !!(state->flags & SS_RESET));
-
-	/* Silently ignore Vpp, output enable, speaker enable. */
+	struct l1110 *l = skt->socket.driver_data;
 
-	return 0;
+	return l1110_configure_socket(l, state);
 }
 
-struct pcmcia_low_level h3600_pcmcia_ops = { 
+static struct pcmcia_low_level l1110_pcmcia_ops = {
 	.owner			= THIS_MODULE,
-	.hw_init		= h3600_pcmcia_hw_init,
-	.hw_shutdown		= h3600_pcmcia_hw_shutdown,
-	.socket_state		= h3600_pcmcia_socket_state,
-	.configure_socket	= h3600_pcmcia_configure_socket,
+	.hw_init		= l1110_pcmcia_hw_init,
+	.socket_state		= l1110_pcmcia_socket_state,
+	.configure_socket	= l1110_pcmcia_configure_socket,
 };
 
 int pcmcia_h3600_init(struct device *dev)
 {
-	int ret = -ENODEV;
+	struct ipaq_option_id *id = dev->platform_data;
+
+	if (!machine_is_h3600() || !id)
+		return -ENODEV;
 
-	if (machine_is_h3600())
-		ret = sa11xx_drv_pcmcia_probe(dev, &h3600_pcmcia_ops, 0, 2);
+	if (id->vendor != 0x1125 || id->device != 0x0001)
+		return -ENODEV;
 
-	return ret;
+	return sa11xx_drv_pcmcia_probe(dev, &l1110_pcmcia_ops, 0, 2);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

Convert the 3c589_cs driver to NAPI to ease the interrupt workload.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/3com/3c589_cs.c | 117 ++++++++++++++++++++++++++---------
 1 file changed, 88 insertions(+), 29 deletions(-)

diff --git a/drivers/net/ethernet/3com/3c589_cs.c b/drivers/net/ethernet/3com/3c589_cs.c
index c35573bf5dbf..dc1f8ccca5ec 100644
--- a/drivers/net/ethernet/3com/3c589_cs.c
+++ b/drivers/net/ethernet/3com/3c589_cs.c
@@ -137,6 +137,8 @@ struct el3_private {
 	u16			fast_poll;
 	unsigned long		last_irq;
 	spinlock_t		lock;
+	u16			pending;
+	struct napi_struct	napi;
 };
 
 static const char *if_names[] = { "auto", "10baseT", "10base2", "AUI" };
@@ -176,6 +178,7 @@ static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue);
 static void set_rx_mode(struct net_device *dev);
 static void set_multicast_list(struct net_device *dev);
 static const struct ethtool_ops netdev_ethtool_ops;
+static int el3_napi_poll(struct napi_struct *napi, int budget);
 
 static void tc589_detach(struct pcmcia_device *p_dev);
 
@@ -219,6 +222,8 @@ static int tc589_probe(struct pcmcia_device *link)
 
 	dev->ethtool_ops = &netdev_ethtool_ops;
 
+	netif_napi_add(dev, &lp->napi, el3_napi_poll);
+
 	ret = tc589_config(link);
 	if (ret)
 		goto err_free_netdev;
@@ -527,6 +532,8 @@ static int el3_open(struct net_device *dev)
 	timer_setup(&lp->media, media_check, 0);
 	mod_timer(&lp->media, jiffies + HZ);
 
+	napi_enable(&lp->napi);
+
 	dev_dbg(&link->dev, "%s: opened, status %4.4x.\n",
 	  dev->name, inw(dev->base_addr + EL3_STATUS));
 
@@ -547,10 +554,10 @@ static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue)
 	netif_wake_queue(dev);
 }
 
-static void pop_tx_status(struct net_device *dev)
+static int pop_tx_status(struct net_device *dev)
 {
 	unsigned int ioaddr = dev->base_addr;
-	int i;
+	int i, work = 0;
 
 	/* Clear the Tx status stack. */
 	for (i = 32; i > 0; i--) {
@@ -566,7 +573,9 @@ static void pop_tx_status(struct net_device *dev)
 			dev->stats.tx_aborted_errors++;
 		}
 		outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */
+		work++;
 	}
+	return work;
 }
 
 static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
@@ -606,47 +615,38 @@ static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
 	return NETDEV_TX_OK;
 }
 
-/* The EL3 interrupt handler. */
-static irqreturn_t el3_interrupt(int irq, void *dev_id)
+static int el3_napi_handle(struct net_device *dev, int budget)
 {
-	struct net_device *dev = (struct net_device *) dev_id;
 	struct el3_private *lp = netdev_priv(dev);
-	unsigned int ioaddr;
+	unsigned int ioaddr = dev->base_addr;
 	__u16 status;
-	int i = 0, handled = 1;
-
-	if (!netif_device_present(dev))
-		return IRQ_NONE;
-
-	ioaddr = dev->base_addr;
-
-	netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+	int work = 0;
 
-	spin_lock(&lp->lock);
-	while ((status = inw(ioaddr + EL3_STATUS)) &
+	while ((status = (inw(ioaddr + EL3_STATUS) | lp->pending)) &
 	(IntLatch | RxComplete | StatsFull)) {
+		lp->pending = 0;
 		if ((status & 0xe000) != 0x2000) {
 			netdev_dbg(dev, "interrupt from dead card\n");
-			handled = 0;
-			break;
+			return 0;
 		}
 		if (status & RxComplete)
-			el3_rx(dev);
+			work += el3_rx(dev);
 		if (status & TxAvailable) {
 			netdev_dbg(dev, "    TX room bit was handled.\n");
 			/* There's room in the FIFO for a full-sized packet. */
 			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD);
 			netif_wake_queue(dev);
+			work++;
 		}
 		if (status & TxComplete)
-			pop_tx_status(dev);
+			work += pop_tx_status(dev);
 		if (status & (AdapterFailure | RxEarly | StatsFull)) {
 			/* Handle all uncommon interrupts. */
 			if (status & StatsFull)		/* Empty statistics. */
 				update_stats(dev);
 			if (status & RxEarly) {
 				/* Rx early is unused. */
-				el3_rx(dev);
+				work += el3_rx(dev);
 				outw(AckIntr | RxEarly, ioaddr + EL3_CMD);
 			}
 			if (status & AdapterFailure) {
@@ -670,17 +670,74 @@ static irqreturn_t el3_interrupt(int irq, void *dev_id)
 				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD);
 			}
 		}
-		if (++i > 10) {
-			netdev_err(dev, "infinite loop in interrupt, status %4.4x.\n",
-					status);
-			/* Clear all interrupts */
-			outw(AckIntr | 0xFF, ioaddr + EL3_CMD);
-			break;
-		}
 		/* Acknowledge the IRQ. */
 		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
 	}
 	lp->last_irq = jiffies;
+
+	return work;
+}
+
+static int el3_napi_poll(struct napi_struct *napi, int budget)
+{
+	struct net_device *dev = napi->dev;
+	struct el3_private *lp = netdev_priv(dev);
+	unsigned int ioaddr = dev->base_addr;
+	int work = 0;
+
+	if (!netif_running(dev)) {
+		napi_complete(napi);
+		return 0;
+	}
+
+	netdev_dbg(dev, "napi poll, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+
+	spin_lock(&lp->lock);
+	work = el3_napi_handle(dev, budget);
+	spin_unlock(&lp->lock);
+
+	netdev_dbg(dev, "exiting napi poll, status %4.4x.\n",
+			inw(ioaddr + EL3_STATUS));
+
+	budget -= work;
+	if (budget > 0) {
+		napi_complete(napi);
+		outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete
+		 | StatsFull | AdapterFailure, ioaddr + EL3_CMD);
+	}
+
+	return work;
+}
+
+/* The EL3 interrupt handler. */
+static irqreturn_t el3_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev = (struct net_device *) dev_id;
+	struct el3_private *lp = netdev_priv(dev);
+	unsigned int ioaddr;
+	__u16 status;
+	int handled = 1;
+
+	if (!netif_device_present(dev))
+		return IRQ_NONE;
+
+	ioaddr = dev->base_addr;
+
+	netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+
+	spin_lock(&lp->lock);
+	status = inw(ioaddr + EL3_STATUS);
+	if ((status & 0xe000) != 0x2000) {
+		netdev_dbg(dev, "interrupt from dead card\n");
+		handled = 0;
+	} else {
+		lp->pending |= status;
+		outw(SetIntrEnb, ioaddr + EL3_CMD);
+		status &= IntLatch | TxAvailable | RxComplete
+		 | StatsFull | AdapterFailure;
+		outw(AckIntr | status, ioaddr + EL3_CMD);
+		napi_schedule(&lp->napi);
+	}
 	spin_unlock(&lp->lock);
 	netdev_dbg(dev, "exiting interrupt, status %4.4x.\n",
 			inw(ioaddr + EL3_STATUS));
@@ -893,7 +950,7 @@ static int el3_rx(struct net_device *dev)
 	}
 	if (worklimit == 0)
 		netdev_warn(dev, "too much work in el3_rx!\n");
-	return 0;
+	return 32 - worklimit;
 }
 
 static void set_rx_mode(struct net_device *dev)
@@ -953,6 +1010,8 @@ static int el3_close(struct net_device *dev)
 			update_stats(dev);
 	}
 
+	napi_disable(&lp->napi);
+
 	link->open--;
 	netif_stop_queue(dev);
 	del_timer_sync(&lp->media);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

Add clock driver for iPAQ audio sysclock.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3600.c |  18 ++++++
 drivers/clk/Makefile         |   1 +
 drivers/clk/clk-ipaq.c       | 135 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 154 insertions(+)
 create mode 100644 drivers/clk/clk-ipaq.c

index 2663ced6452e..5078ed3da3e6 100644
@@ -9,6 +9,7 @@
 
 #include <video/sa1100fb.h>
 
@@ -126,9 +127,26 @@ static struct gpiod_lookup_table h3600_sleeve_gpio_table = {
 	},
 };
 
+static struct gpiod_lookup_table h3600_audio_clk_gpio_table = {
+	.dev_id = "ipaq-audio-clk",
+	.table = {
+		GPIO_LOOKUP("gpio", 12, "clk0", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("gpio", 13, "clk1", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct platform_device_info h3600_audio_clk_device = {
+	.name = "ipaq-audio-clk",
+	.id = -1,
+};
+
 static void __init h3600_mach_init(void)
 {
 	gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
+	gpiod_add_lookup_table(&h3600_audio_clk_gpio_table);
+
+	platform_device_register_full(&h3600_audio_clk_device);
 
 	h3xxx_mach_init();
 
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f793a16cad40..04f727081e23 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_COMMON_CLK_ASPEED)		+= clk-aspeed.o
 obj-$(CONFIG_MACH_ASPEED_G6)		+= clk-ast2600.o
 obj-$(CONFIG_ARCH_HIGHBANK)		+= clk-highbank.o
 obj-$(CONFIG_CLK_HSDK)			+= clk-hsdk-pll.o
+obj-$(CONFIG_SA1100_H3600)		+= clk-ipaq.o
 obj-$(CONFIG_COMMON_CLK_K210)		+= clk-k210.o
 obj-$(CONFIG_LMK04832)			+= clk-lmk04832.o
 obj-$(CONFIG_COMMON_CLK_LAN966X)	+= clk-lan966x.o
diff --git a/drivers/clk/clk-ipaq.c b/drivers/clk/clk-ipaq.c
new file mode 100644
index 000000000000..af48cdb7da53
--- /dev/null
+++ b/drivers/clk/clk-ipaq.c
@@ -0,0 +1,135 @@
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct ipaq_audio_clk {
+	struct clk_hw clk;
+	struct clk_lookup *cl;
+	struct gpio_desc *gpios[2];
+};
+#define to_ipaq_audio_clk(x) container_of(x, struct ipaq_audio_clk, clk)
+
+static const char *clk_names[2] = { "clk0", "clk1" };
+
+static unsigned long ipaq_audio_clk_rates[] = {
+	12288000,
+	11289600,
+	4096000,
+	5624500,
+};
+
+static unsigned long ipaq_audio_clk_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+	unsigned int clk0 = !!gpiod_get_value(ia->gpios[0]);
+	unsigned int clk1 = !!gpiod_get_value(ia->gpios[1]);
+	unsigned int idx = clk1 * 2 + clk0;
+
+	return ipaq_audio_clk_rates[idx];
+}
+
+static long ipaq_audio_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+	unsigned long *parent_rate)
+{
+	int i, best = -1;
+	long diff, best_diff = LONG_MAX;
+
+	for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+		diff = ipaq_audio_clk_rates[i] - rate;
+		if (diff < 0)
+			diff = -diff;
+		if (diff < best_diff) {
+			best_diff = diff;
+			best = i;
+		}
+	}
+
+	return best == -1 ? -EINVAL : ipaq_audio_clk_rates[best];
+}
+
+static int ipaq_audio_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+	unsigned long parent_rate)
+{
+	struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+	DECLARE_BITMAP(values, 2) = { 0, };
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+		if (ipaq_audio_clk_rates[i] == rate) {
+			__assign_bit(0, values, !!(i & 1));
+			__assign_bit(1, values, !!(i & 2));
+
+			gpiod_set_array_value(2, ia->gpios, NULL, values);
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static const struct clk_ops ipaq_audio_clk_ops = {
+	.recalc_rate = ipaq_audio_clk_recalc_rate,
+	.round_rate = ipaq_audio_clk_round_rate,
+	.set_rate = ipaq_audio_clk_set_rate,
+};
+
+static struct clk_init_data ipaq_audio_clk_init = {
+	.name = "ipaq-audio",
+	.ops = &ipaq_audio_clk_ops,
+};
+
+static int ipaq_audio_clk_probe(struct platform_device *pdev)
+{
+	struct ipaq_audio_clk *ia;
+	int i, ret;
+
+	ia = devm_kzalloc(&pdev->dev, sizeof(*ia), GFP_KERNEL);
+	if (!ia)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(clk_names); i++) {
+		ia->gpios[i] = devm_gpiod_get(&pdev->dev, clk_names[i],
+					      GPIOD_OUT_LOW);
+		if (IS_ERR(ia->gpios[i]))
+			return PTR_ERR(ia->gpios[i]);
+	}
+
+	platform_set_drvdata(pdev, ia);
+
+	ia->clk.init = &ipaq_audio_clk_init;
+
+	ret = devm_clk_hw_register(&pdev->dev, &ia->clk);
+	if (ret)
+		return ret;
+
+	ia->cl = clkdev_hw_create(&ia->clk, "ext", "h3xxx-asoc");
+
+	return ia->cl ? 0 : -ENOMEM;
+}
+
+static int ipaq_audio_clk_remove(struct platform_device *pdev)
+{
+	struct ipaq_audio_clk *ia = platform_get_drvdata(pdev);
+
+	clkdev_drop(ia->cl);
+
+	return 0;
+}
+
+static struct platform_driver ipaq_audio_clk_driver = {
+	.probe = ipaq_audio_clk_probe,
+	.remove = ipaq_audio_clk_remove,
+	.driver = {
+		.name = "ipaq-audio-clk",
+	},
+};
+
+module_platform_driver(ipaq_audio_clk_driver);
+
+MODULE_AUTHOR("Russell King <[email-masked]>");
+MODULE_DESCRIPTION("iPAQ Audio clock driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ipaq-audio-clock");

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

From: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3600.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/arch/arm/mach-sa1100/h3600.c b/arch/arm/mach-sa1100/h3600.c
index 5078ed3da3e6..2b26a5c76e13 100644
--- a/arch/arm/mach-sa1100/h3600.c
+++ b/arch/arm/mach-sa1100/h3600.c
@@ -141,10 +141,30 @@ static struct platform_device_info h3600_audio_clk_device = {
 	.id = -1,
 };
 
+static struct gpiod_lookup_table h3600_uda134x_gpio_table = {
+	.dev_id = "uda134x-codec",
+	.table = {
+		GPIO_LOOKUP("htc-egpio", 12, "qmute", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct gpiod_lookup_table h3600_asoc_gpio_table = {
+	.dev_id = "h3xxx-asoc",
+	.table = {
+		GPIO_LOOKUP("htc-egpio", 3, "audio-reset", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 10, "amp-power", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 11, "audio-power", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 static void __init h3600_mach_init(void)
 {
 	gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
 	gpiod_add_lookup_table(&h3600_audio_clk_gpio_table);
+	gpiod_add_lookup_table(&h3600_uda134x_gpio_table);
+	gpiod_add_lookup_table(&h3600_asoc_gpio_table);
 
 	platform_device_register_full(&h3600_audio_clk_device);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

Add support for the UDA1341 on the iPAQ H3600 and H3100 devices.

Signed-off-by: Russell King <[email-masked]>
---
 sound/soc/sa11x0/Kconfig  |  10 ++
 sound/soc/sa11x0/Makefile |   3 +
 sound/soc/sa11x0/h3xxx.c  | 317 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 330 insertions(+)
 create mode 100644 sound/soc/sa11x0/h3xxx.c

diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
index ae7930842e07..81ba6f9bccc8 100644
--- a/sound/soc/sa11x0/Kconfig
+++ b/sound/soc/sa11x0/Kconfig
@@ -12,3 +12,13 @@ config SND_SA11X0_ASSABET
 	help
 	  Enable support for the audio UDA1341 codec found on the Intel
 	  Assabet SA-1110 development board.
+
+config SND_SA11X0_H3XXX
+	tristate "SoC Audio support for H3xxx iPAQ"
+	depends on (SA1100_H3100 || SA1100_H3600) && DMA_SA11X0
+	select SND_SOC_L3
+	select SND_SOC_UDA134X
+	select SND_SOC_SA11X0_SSP
+	help
+	  Enable support for the audio UDA1341 codec found on the iPAQ
+	  H3100 and H3600 platforms.
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
index 9a2ab2c346d9..dab7e2deff6e 100644
--- a/sound/soc/sa11x0/Makefile
+++ b/sound/soc/sa11x0/Makefile
@@ -3,3 +3,6 @@ obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
 
 snd-soc-sa11x0-assabet-objs := assabet.o
 obj-$(CONFIG_SND_SA11X0_ASSABET) += snd-soc-sa11x0-assabet.o
+
+snd-soc-sa11x0-h3xxx-objs := h3xxx.o
+obj-$(CONFIG_SND_SA11X0_H3XXX) += snd-soc-sa11x0-h3xxx.o
diff --git a/sound/soc/sa11x0/h3xxx.c b/sound/soc/sa11x0/h3xxx.c
new file mode 100644
index 000000000000..baed22e2dc2a
--- /dev/null
+++ b/sound/soc/sa11x0/h3xxx.c
@@ -0,0 +1,317 @@
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h> /* HACK */
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda134x.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include "ssp.h"
+
+struct h3xxx_card {
+	struct snd_soc_card card;
+	struct clk *clk_ext;
+	struct gpio_desc *reset;
+	struct gpio_desc *aud_pwr;
+	struct gpio_desc *amp_pwr;
+};
+
+static int h3xxx_asoc_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+	int ret = 0;
+
+	if (!rtd->cpu_dai->active)
+		ret = clk_prepare_enable(hc->clk_ext);
+
+	return ret;
+}
+
+static int h3xxx_asoc_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+	unsigned long rate = params_rate(params);
+	unsigned long sclk;
+	int ret;
+
+	switch (rate) {
+	case 24000:
+	case 32000:
+	case 48000:
+		sclk = 12288000;
+		break;
+	case 22050:
+	case 29400:
+	case 44100:
+		sclk = 11289600;
+		break;
+	case 8000:
+	case 10666:
+	case 16000:
+		sclk = 4096000;
+		break;
+	case 10985:
+	case 14647:
+	case 21970:
+		sclk = 5624500;
+		break;
+	default:
+		pr_warn("invalid rate: %luHz\n", rate);
+		return -EINVAL;
+	}
+
+	ret = clk_set_rate(hc->clk_ext, sclk);
+	if (ret) {
+		pr_warn("clk_set_rate: %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sclk, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		pr_warn("snd_soc_dai_set_sysclk(codec): %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SA11X0_SSP_CLK_EXT,
+				     sclk, SND_SOC_CLOCK_IN);
+	if (ret < 0)  {
+		pr_warn("snd_soc_dai_set_sysclk(cpu): %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	return ret;
+}
+
+static void h3xxx_asoc_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+
+	if (!rtd->cpu_dai->active)
+		clk_disable_unprepare(hc->clk_ext);
+}
+
+static const struct snd_soc_ops h3xxx_asoc_ops = {
+	.startup = h3xxx_asoc_startup,
+	.hw_params = h3xxx_asoc_hw_params,
+	.shutdown = h3xxx_asoc_shutdown,
+};
+
+static struct snd_soc_dai_link h3xxx_asoc_dais[1] = {
+	{
+		.name = "h3xxx",
+		.stream_name = "iPAQ Playback/Capture",
+		.cpu_dai_name = "sa11x0-ssp",
+		.codec_name = "uda134x-codec",
+		.codec_dai_name = "uda134x-hifi",
+		.platform_name = "sa11x0-ssp",
+		.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+		.ops = &h3xxx_asoc_ops,
+	},
+};
+
+static int h3xxx_asoc_pwramp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(w->dapm->card);
+
+	gpiod_set_value(hc->amp_pwr, SND_SOC_DAPM_EVENT_ON(event));
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget h3xxx_asoc_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Power Amp", h3xxx_asoc_pwramp_event),
+};
+
+static const struct snd_soc_dapm_route h3xxx_asoc_dapm_routes[] = {
+	{ "Power Amp", NULL, "VOUTL" },
+	{ "Power Amp", NULL, "VOUTR" },
+};
+
+static const struct snd_soc_card h3xxx_asoc_card_template = {
+	.name = "H3xxx",
+	.long_name = "iPAQ H3xxx HiFi Audio",
+	.owner = THIS_MODULE,
+	.dai_link = h3xxx_asoc_dais,
+	.num_links = ARRAY_SIZE(h3xxx_asoc_dais),
+	.dapm_widgets = h3xxx_asoc_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(h3xxx_asoc_dapm_widgets),
+	.dapm_routes = h3xxx_asoc_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(h3xxx_asoc_dapm_routes),
+};
+
+static struct h3xxx_card *card; /* HACK */
+
+static int h3xxx_asoc_probe(struct platform_device *pdev)
+{
+	struct h3xxx_card *hc;
+
+	GAFR |= GPIO_SSP_CLK;
+	GPDR &= ~GPIO_SSP_CLK;
+
+	hc = devm_kzalloc(&pdev->dev, sizeof(*hc), GFP_KERNEL);
+	if (!hc)
+		return -ENOMEM;
+
+	memcpy(&hc->card, &h3xxx_asoc_card_template, sizeof(hc->card));
+	hc->card.dev = &pdev->dev;
+
+	hc->reset = devm_gpiod_get(&pdev->dev, "audio-reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->reset))
+		return PTR_ERR(hc->reset);
+
+	/* should be regulators? */
+	hc->aud_pwr = devm_gpiod_get(&pdev->dev, "audio-power", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->aud_pwr))
+		return PTR_ERR(hc->aud_pwr);
+
+	hc->amp_pwr = devm_gpiod_get(&pdev->dev, "amp-power", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->amp_pwr))
+		return PTR_ERR(hc->amp_pwr);
+
+	hc->clk_ext = devm_clk_get(&pdev->dev, "ext");
+	if (IS_ERR(hc->clk_ext))
+		return PTR_ERR(hc->clk_ext);
+
+	snd_soc_card_set_drvdata(&hc->card, hc);
+
+	card = hc;
+
+	return snd_soc_register_card(&hc->card);
+}
+
+static int h3xxx_asoc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	card = NULL;
+
+	return 0;
+}
+
+static struct platform_driver h3xxx_asoc_driver = {
+	.probe = h3xxx_asoc_probe,
+	.remove = h3xxx_asoc_remove,
+	.driver = {
+		.name = "h3xxx-asoc",
+	},
+};
+
+/* HACK */
+static void h3xxx_asoc_setdat(struct l3_pins *adap, int v)
+{
+	gpio_set_value(14, v);
+}
+
+static void h3xxx_asoc_setclk(struct l3_pins *adap, int v)
+{
+	gpio_set_value(16, v);
+}
+
+static void h3xxx_asoc_setmode(struct l3_pins *adap, int v)
+{
+	gpio_set_value(15, v);
+}
+
+static void h3xxx_asoc_power(int on)
+{
+	struct h3xxx_card *hc = card;
+
+	if (!hc)
+		return;
+
+	if (on) {
+		gpiod_set_value_cansleep(hc->aud_pwr, 1);
+		gpiod_set_value_cansleep(hc->reset, 1);
+		mdelay(1);
+	} else {
+		gpiod_set_value_cansleep(hc->reset, 0);
+		gpiod_set_value_cansleep(hc->aud_pwr, 0);
+	}
+}
+
+static struct uda134x_platform_data uda134x_data = {
+	.l3 = {
+		.setdat = h3xxx_asoc_setdat,
+		.setclk = h3xxx_asoc_setclk,
+		.setmode = h3xxx_asoc_setmode,
+		.data_hold = 1,
+		.data_setup = 1,
+		.clock_high = 1,
+		.mode_hold = 1,
+		.mode = 1,
+		.mode_setup = 1,
+	},
+	.model = UDA134X_UDA1341,
+	.power = h3xxx_asoc_power,
+};
+static struct platform_device *h3xxx_asoc, *h3xxx_uda;
+static void h3xxx_hack_init(void)
+{
+	gpio_direction_output(14, 0);
+	gpio_direction_output(15, 0);
+	gpio_direction_output(16, 0);
+
+	h3xxx_asoc = platform_device_register_simple("h3xxx-asoc", -1,
+						     NULL, 0);
+	if (IS_ERR(h3xxx_asoc))
+		h3xxx_asoc = NULL;
+	h3xxx_uda = platform_device_register_data(NULL, "uda134x-codec", -1,
+						    &uda134x_data,
+						    sizeof(uda134x_data));
+	if (IS_ERR(h3xxx_uda))
+		h3xxx_uda = NULL;
+}
+
+static void h3xxx_hack_exit(void)
+{
+	if (h3xxx_asoc) {
+		platform_device_unregister(h3xxx_asoc);
+		h3xxx_asoc = NULL;
+	}
+	if (h3xxx_uda) {
+		platform_device_unregister(h3xxx_uda);
+		h3xxx_uda = NULL;
+	}
+}
+
+static int h3xxx_asoc_init(void)
+{
+	int ret;
+
+	if (machine_is_h3100() || machine_is_h3600())
+		h3xxx_hack_init();
+
+	ret = platform_driver_register(&h3xxx_asoc_driver);
+	if (ret)
+		goto err_driver;
+
+	return 0;
+
+err_driver:
+	h3xxx_hack_exit();
+	return ret;
+}
+module_init(h3xxx_asoc_init);
+
+static void h3xxx_asoc_exit(void)
+{
+	platform_driver_unregister(&h3xxx_asoc_driver);
+	h3xxx_hack_exit();
+}
+module_exit(h3xxx_asoc_exit)
+
+MODULE_LICENSE("GPL");

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:23 +0100

From: Russell King <[email-masked]>
---
 sound/soc/sa11x0/assabet.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
index d6c9cbefc1f6..5b11856d5abf 100644
--- a/sound/soc/sa11x0/assabet.c
+++ b/sound/soc/sa11x0/assabet.c
@@ -272,16 +272,18 @@ static struct snd_soc_ops assabet_asoc_ops = {
 	.shutdown = assabet_asoc_shutdown,
 };
 
-static struct snd_soc_dai_link assabet_asoc_dai = {
-	.name = "Assabet",
-	.stream_name = "assabet-ssp",
-	.codec_name = "uda134x-codec",
-	.platform_name = "sa11x0-ssp",
-	.cpu_dai_name = "sa11x0-ssp",
-	.codec_dai_name = "uda134x-hifi",
-	.ops = &assabet_asoc_ops,
-	.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
-		   SND_SOC_DAIFMT_CBS_CFS,
+static struct snd_soc_dai_link assabet_asoc_dais[] = {
+	{
+		.name = "Assabet",
+		.stream_name = "System Playback/Capture",
+		.cpu_dai_name = "sa11x0-ssp",
+		.codec_name = "uda134x-codec",
+		.codec_dai_name = "uda134x-hifi",
+		.platform_name = "sa11x0-ssp",
+		.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+		.ops = &assabet_asoc_ops,
+	},
 };
 
 static int
@@ -307,8 +309,8 @@ static const struct snd_soc_dapm_route assabet_dapm_routes[] = {
 static struct snd_soc_card snd_soc_assabet = {
 	.name = "Assabet",
 	.resume_pre = assabet_asoc_resume_pre,
-	.dai_link = &assabet_asoc_dai,
-	.num_links = 1,
+	.dai_link = assabet_asoc_dais,
+	.num_links = ARRAY_SIZE(assabet_asoc_dais),
 	.dapm_widgets = assabet_dapm_widgets,
 	.num_dapm_widgets = ARRAY_SIZE(assabet_dapm_widgets),
 	.dapm_routes = assabet_dapm_routes,
@@ -470,7 +472,7 @@ static int assabet_init(void)
 	local_irq_restore(flags);
 
 	soc = platform_device_alloc("assabet-asoc", -1);
-	uda = platform_device_alloc(assabet_asoc_dai.codec_name, -1);
+	uda = platform_device_alloc(assabet_asoc_dais[0].codec_name, -1);
 	dma = platform_device_alloc("soc-dmaengine", -1);
 	if (!soc || !uda || !dma) {
 		ret = -ENOMEM;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:09:24 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/mach-sa1100/h3xxx-sleeve.c    | 189 +++++++++++++++++++++++++++++----
 arch/arm/mach-sa1100/ipaq-sleeve-bus.c |  27 ++++-
 include/linux/ipaq-sleeve.h            |   4 +-
 3 files changed, 192 insertions(+), 28 deletions(-)

diff --git a/arch/arm/mach-sa1100/h3xxx-sleeve.c b/arch/arm/mach-sa1100/h3xxx-sleeve.c
index ed0edaf43d9c..8aef18062e40 100644
--- a/arch/arm/mach-sa1100/h3xxx-sleeve.c
+++ b/arch/arm/mach-sa1100/h3xxx-sleeve.c
@@ -1,3 +1,4 @@
+#include <linux/ctype.h>
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/interrupt.h>
@@ -7,13 +8,31 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <linux/ipaq-sleeve.h>
+#include <asm/unaligned.h>
 
-#define SLEEVE_POWER_DELAY_MS 10
 #define SLEEVE_NVRAM_POWER_MS 5
 
+enum {
+	MAX_HEADER = 256,
+	TYPE_I = 1,
+	TYPE_II = 2,
+	NO_RESET = 0xffff,
+};
+
+struct ipaq_sleeve_id {
+	struct ipaq_option_id id;
+	const char *description;
+	u8 version;
+	u8 extended_battery;
+	u16 reset_width;
+	u16 reset_wait;
+	u16 skt_type[2];
+	u8 opt_on_ctl;
+	u8 nvram[256];
+};
+
 struct opt_info {
 	struct device *dev;
-	struct ipaq_option_id id;
 	struct delayed_work detect_work;
 	bool detect_disabled;
 	bool sleeve_changed;
@@ -23,6 +42,8 @@ struct opt_info {
 	struct gpio_desc *nvram_on;
 	struct gpio_desc *opt_on;
 	struct gpio_desc *opt_reset;
+
+	struct ipaq_sleeve_id id;
 };
 
 static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
@@ -67,9 +88,54 @@ static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
 	return 0;
 }
 
-static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id)
+static char *ipaq_option_get_string(u8 **ptr, u8 *end)
 {
-	__le16 vendor, device;
+	u8 *p = *ptr;
+	unsigned int i;
+
+	for (i = 0; p[i] && &p[i] < end; i++)
+		if (!isprint(p[i]) || &p[i] == end)
+			return NULL;
+
+	*ptr = p + i + 1;
+
+	return kstrndup(p, i, GFP_KERNEL);
+}
+
+static u8 *ipaq_option_get_ptr(u8 **ptr, u8 *end, size_t size)
+{
+	u8 *p = *ptr;
+
+	if (p - end < size)
+		return NULL;
+
+	*ptr += size;
+
+	return p;
+}
+
+static u8 ipaq_option_get_u8(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 1);
+	return p ? *p : 0;
+}
+
+static u16 ipaq_option_get_le16(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 2);
+	return p ? get_unaligned_le16(p) : 0;
+}
+
+static u32 ipaq_option_get_le32(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 4);
+	return p ? get_unaligned_le32(p) : 0;
+}
+
+static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_sleeve_id *id)
+{
+	unsigned int length;
+	u8 header[5], *ptr, *end;
 	int ret;
 
 	/* Enable the CF bus */
@@ -78,32 +144,99 @@ static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id)
 	/* Wait for power to stabilise */
 	msleep(SLEEVE_NVRAM_POWER_MS);
 
-	ret = ipaq_option_spi_read(&vendor, 6, 2);
+	/* Read the first few bytes from the NVRAM */
+	ret = ipaq_option_spi_read(header, 0, sizeof(header));
 	if (ret)
 		goto err;
 
-	ret = ipaq_option_spi_read(&device, 8, 2);
+	ptr = header;
+	end = header + sizeof(header);
+	if (ipaq_option_get_u8(&ptr, end) != 0xaa) {
+		dev_err(opt->dev, "invalid ID structure\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	length = ipaq_option_get_le32(&ptr, end);
+	if (length < sizeof(header) || length >= MAX_HEADER - 1) {
+		dev_err(opt->dev, "invalid ID length\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* Read the full NVRAM contents */
+	ret = ipaq_option_spi_read(id->nvram, 0, length + 1);
 	if (ret)
 		goto err;
 
-	id->vendor = le16_to_cpu(vendor);
-	id->device = le16_to_cpu(device);
+	/* reset the pointer */
+	ptr = id->nvram + (ptr - header);
+	end = ptr + length + 1;
+
+	id->version = ipaq_option_get_u8(&ptr, end);
+	id->id.vendor = ipaq_option_get_le16(&ptr, end);
+	id->id.device = ipaq_option_get_le16(&ptr, end);
+
+	id->description = ipaq_option_get_string(&ptr, end);
+	if (!id->description) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+	id->extended_battery = ipaq_option_get_u8(&ptr, end);
+	ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+	id->reset_width = ipaq_option_get_le16(&ptr, end);
+	id->reset_wait = ipaq_option_get_le16(&ptr, end);
+	ipaq_option_get_le16(&ptr, end); /* bootstrap */
+	ipaq_option_get_le16(&ptr, end); /* OEM table */
+
+	if (id->version == TYPE_II) {
+		id->skt_type[0] = ipaq_option_get_le16(&ptr, end);
+		id->skt_type[1] = ipaq_option_get_le16(&ptr, end);
+		id->opt_on_ctl = ipaq_option_get_u8(&ptr, end);
+	} else {
+		id->skt_type[0] = 0;
+		id->skt_type[1] = 0;
+		id->opt_on_ctl = 1;
+	}
+
+	if (ipaq_option_get_le32(&ptr, end) != 0x0f0f0f0f) {
+		dev_err(opt->dev, "missing ID terminator\n");
+		ret = -EINVAL;
+		goto err;
+	}
 
  err:
 	gpiod_set_value_cansleep(opt->nvram_on, 0);
 	return ret;
 }
 
-static void ipaq_option_power_on(struct opt_info *opt, bool noirq)
+static void __ipaq_option_power_on(struct opt_info *opt, bool noirq)
 {
 	gpiod_set_value(opt->opt_reset, 1);
 	gpiod_set_value(opt->opt_on, 1);
+
+	if (opt->id.reset_wait != NO_RESET) {
+		if (noirq)
+			mdelay(opt->id.reset_width);
+		else
+			msleep(opt->id.reset_width);
+	}
+
 	gpiod_set_value(opt->opt_reset, 0);
 
-	if (noirq)
-		mdelay(SLEEVE_POWER_DELAY_MS);
-	else
-		msleep(SLEEVE_POWER_DELAY_MS);
+	if (opt->id.reset_wait != NO_RESET) {
+		if (noirq)
+			mdelay(opt->id.reset_wait);
+		else
+			msleep(opt->id.reset_wait);
+	}
+}
+
+static void ipaq_option_power_on(struct opt_info *opt)
+{
+	__ipaq_option_power_on(opt, false);
 }
 
 static void ipaq_option_power_off(struct opt_info *opt, bool noirq)
@@ -120,13 +253,14 @@ static void ipaq_option_insert(struct opt_info *opt)
 	if (ret)
 		return;
 
-	dev_info(opt->dev, "Option pack %04x:%04x detected\n",
-		 opt->id.vendor, opt->id.device);
+	dev_info(opt->dev, "%s sleeve inserted\n", opt->id.description);
 
-	ipaq_option_power_on(opt, false);
+	if (opt->id.opt_on_ctl)
+		ipaq_option_power_on(opt);
 
 	if (!opt->sleeve_present)
-		ipaq_option_device_add(opt->dev, opt->id);
+		ipaq_option_device_add(opt->dev, opt->id.id,
+				       opt->id.description);
 	opt->sleeve_present = true;
 }
 
@@ -137,6 +271,9 @@ static void ipaq_option_remove(struct opt_info *opt)
 	opt->sleeve_present = false;
 
 	ipaq_option_power_off(opt, false);
+
+	kfree(opt->id.description);
+	memset(&opt->id, 0, sizeof(opt->id));
 }
 
 static void ipaq_option_work(struct work_struct *work)
@@ -211,6 +348,9 @@ static int ipaq_h3xxx_sleeve_probe(struct platform_device *pdev)
 	schedule_delayed_work(&opt->detect_work, 0);
 	flush_delayed_work(&opt->detect_work);
 
+	device_set_wakeup_capable(dev, true);
+	device_set_wakeup_enable(dev, false);
+
 	return 0;
 }
 
@@ -247,6 +387,9 @@ static int ipaq_h3xxx_sleeve_suspend(struct device *dev)
 
 	opt->sleeve_wakeup = may_wakeup;
 
+	if (device_may_wakeup(dev))
+		enable_irq_wake(gpiod_to_irq(opt->detect));
+
 	return 0;
 }
 
@@ -278,7 +421,7 @@ static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
 		 * it is the same sleeve inserted, and power it up.  This
 		 * avoids an eject/insert cycle on any inserted PCMCIA card.
 		 */
-		ipaq_option_power_on(opt, true);
+		__ipaq_option_power_on(opt, true);
 	}
 
 	return 0;
@@ -287,10 +430,14 @@ static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
 static int ipaq_h3xxx_sleeve_resume(struct device *dev)
 {
 	struct opt_info *opt = dev_get_drvdata(dev);
-	struct ipaq_option_id id;
+	struct ipaq_sleeve_id id;
 	int ret;
 
+	if (device_may_wakeup(dev))
+		disable_irq_wake(gpiod_to_irq(opt->detect));
+
 	if (opt->sleeve_present && gpiod_get_value_cansleep(opt->detect)) {
+		memset(&id, 0, sizeof(id));
 		ret = ipaq_option_read_id(opt, &id);
 		if (ret) {
 			dev_warn(opt->dev, "unable to read sleeve id: %d\n",
@@ -299,13 +446,11 @@ static int ipaq_h3xxx_sleeve_resume(struct device *dev)
 			goto finish;
 		}
 
-		if (opt->id.vendor != id.vendor ||
-		    opt->id.device != id.device) {
+		if (memcmp(opt->id.nvram, id.nvram, sizeof(id.nvram))) {
 			/* sleeve changed */
 			dev_warn(opt->dev, "sleeve changed\n");
 			opt->sleeve_changed = true;
 		} else {
-			dev_info(opt->dev, "same sleeve type inserted\n");
 			opt->detect_disabled = false;
 			return 0;
 		}
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
index 9bf3201002b6..31299589d4a9 100644
--- a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
@@ -19,9 +19,17 @@ static ssize_t device_show(struct device *dev, struct device_attribute *attr, ch
 }
 static DEVICE_ATTR_RO(device);
 
+static ssize_t description_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%s\n", idev->description);
+}
+static DEVICE_ATTR_RO(description);
+
 static struct attribute *ipaq_option_sleeve_attrs[] = {
-	&dev_attr_vendor.attr,
+	&dev_attr_description.attr,
 	&dev_attr_device.attr,
+	&dev_attr_vendor.attr,
 	NULL,
 };
 
@@ -55,7 +63,8 @@ static int ipaq_option_sleeve_uevent(struct device *dev, struct kobj_uevent_env
 	if (!dev)
 		return -ENODEV;
 
-	if (add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
+	if (add_uevent_var(env, "DESCRIPTION=%s", idev->description) ||
+	    add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
 			   idev->id.vendor, idev->id.device))
 		return -ENOMEM;
 
@@ -72,12 +81,14 @@ EXPORT_SYMBOL_GPL(ipaq_option_sleeve_bus);
 
 static void ipaq_option_release(struct device *dev)
 {
-	struct ipaq_option_device *opt_dev = to_ipaq_option_device(dev);
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
 
-	kfree(opt_dev);
+	kfree(idev->description);
+	kfree(idev);
 }
 
-int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+	const char *description)
 {
 	struct ipaq_option_device *idev;
 
@@ -85,6 +96,12 @@ int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
 	if (!idev)
 		return -ENOMEM;
 
+	idev->description = kstrdup(description, GFP_KERNEL);
+	if (!idev->description) {
+		kfree(idev);
+		return -ENOMEM;
+	}
+
 	idev->id = id;
 	device_initialize(&idev->dev);
 	dev_set_name(&idev->dev, "sleeve");
diff --git a/include/linux/ipaq-sleeve.h b/include/linux/ipaq-sleeve.h
index fb669fad0c4e..42352e942c3c 100644
--- a/include/linux/ipaq-sleeve.h
+++ b/include/linux/ipaq-sleeve.h
@@ -6,6 +6,7 @@
 
 struct ipaq_option_device {
 	struct ipaq_option_id id;
+	const char *description;
 	struct device dev;
 };
 #define to_ipaq_option_device(x) container_of(x, struct ipaq_option_device, dev)
@@ -17,7 +18,8 @@ struct ipaq_option_driver {
 
 extern struct bus_type ipaq_option_sleeve_bus;
 
-int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id);
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+			   const char *description);
 void ipaq_option_device_del(void);
 
 static inline int ipaq_option_driver_register(struct ipaq_option_driver *drv)