[2/3] clk: twl: add clock driver for TWL6032

Message ID 20230819134147.456060-3-andreas@kemnade.info
State New
Headers
Series ARM: omap: omap4-embt2ws: 32K clock for WLAN |

Commit Message

Andreas Kemnade Aug. 19, 2023, 1:41 p.m. UTC
  The TWL6032 has some clock outputs which are controlled like
fixed-voltage regulators, in some drivers for these chips
found in the wild, just the regulator api is abused for controlling
them, so simply use something similar to the regulator functions.
Due to a lack of hardware available for testing, leave out the
TWL6030-specific part of those functions.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/clk/Kconfig   |   9 ++
 drivers/clk/Makefile  |   1 +
 drivers/clk/clk-twl.c | 205 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 215 insertions(+)
 create mode 100644 drivers/clk/clk-twl.c
  

Comments

Stephen Boyd Aug. 22, 2023, 10:34 p.m. UTC | #1
Quoting Andreas Kemnade (2023-08-19 06:41:46)
> diff --git a/drivers/clk/clk-twl.c b/drivers/clk/clk-twl.c
> new file mode 100644
> index 0000000000000..deb5742393bac
> --- /dev/null
> +++ b/drivers/clk/clk-twl.c
> @@ -0,0 +1,205 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Clock driver for twl device.
> + *
> + * inspired by the driver for the Palmas device
> + */
> +
> +#include <linux/clk.h>

Is this include used? Hopefully not. Please drop.

> +#include <linux/clk-provider.h>
> +#include <linux/mfd/twl.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>

Is this include used?

> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#define VREG_STATE              2
> +#define TWL6030_CFG_STATE_OFF   0x00
> +#define TWL6030_CFG_STATE_ON    0x01
> +#define TWL6030_CFG_STATE_MASK  0x03
> +
> +struct twl_clk32k_desc {
> +       const char *clk_name;
> +       u8 base;
> +};
> +
> +struct twl_clock_info {
> +       struct device *dev;
> +       struct clk_hw hw;
> +       const struct twl_clk32k_desc *clk_desc;
> +};
> +
> +static inline int
> +twlclk_read(struct twl_clock_info *info, unsigned int slave_subgp,
> +           unsigned int offset)
> +{
> +       u8 value;
> +       int status;
> +
> +       status = twl_i2c_read_u8(slave_subgp, &value,
> +                                info->clk_desc->base + offset);
> +       return (status < 0) ? status : value;
> +}
> +
> +static inline int
> +twlclk_write(struct twl_clock_info *info, unsigned int slave_subgp,
> +            unsigned int offset, u8 value)
> +{
> +       return twl_i2c_write_u8(slave_subgp, value,
> +                               info->clk_desc->base + offset);
> +}
> +
> +static inline struct twl_clock_info *to_twl_clks_info(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct twl_clock_info, hw);
> +}
> +
> +static unsigned long twl_clks_recalc_rate(struct clk_hw *hw,
> +                                         unsigned long parent_rate)
> +{
> +       return 32768;
> +}
> +
> +static int twl6032_clks_prepare(struct clk_hw *hw)
> +{
> +       struct twl_clock_info *cinfo = to_twl_clks_info(hw);
> +       int ret;
> +
> +       ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
> +                          TWL6030_CFG_STATE_ON);
> +       if (ret < 0)
> +               dev_err(cinfo->dev, "clk prepare failed\n");
> +
> +       return ret;
> +}
> +
> +static void twl6032_clks_unprepare(struct clk_hw *hw)
> +{
> +       struct twl_clock_info *cinfo = to_twl_clks_info(hw);
> +       int ret;
> +
> +       ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
> +                          TWL6030_CFG_STATE_OFF);
> +       if (ret < 0)
> +               dev_err(cinfo->dev, "clk unprepare failed\n");
> +}
> +
> +static int twl6032_clks_is_prepared(struct clk_hw *hw)
> +{
> +       struct twl_clock_info *cinfo = to_twl_clks_info(hw);
> +       int val;
> +
> +       val = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE);
> +       if (val < 0) {
> +               dev_err(cinfo->dev, "clk read failed\n");
> +               return val;
> +       }
> +
> +       val &= TWL6030_CFG_STATE_MASK;
> +
> +       return val == TWL6030_CFG_STATE_ON;
> +}
> +
> +static const struct clk_ops twl6032_clks_ops = {
> +       .prepare        = twl6032_clks_prepare,
> +       .unprepare      = twl6032_clks_unprepare,
> +       .is_prepared    = twl6032_clks_is_prepared,
> +       .recalc_rate    = twl_clks_recalc_rate,
> +};
> +
> +struct twl_clks_of_match_data {
> +       struct clk_init_data init;
> +       const struct twl_clk32k_desc desc;
> +};
> +
> +static const struct twl_clks_of_match_data twl6032_of_clk32kg = {
> +       .init = {
> +               .name = "clk32kg",
> +               .ops = &twl6032_clks_ops,
> +               .flags = CLK_IGNORE_UNUSED,
> +       },
> +       .desc = {
> +               .clk_name = "clk32kg",
> +               .base = 0x8C,
> +       },
> +};
> +
> +static const struct twl_clks_of_match_data twl6032_of_clk32kaudio = {
> +       .init = {
> +               .name = "clk32kaudio",
> +               .ops = &twl6032_clks_ops,
> +               .flags = CLK_IGNORE_UNUSED,
> +       },
> +       .desc = {
> +               .clk_name = "clk32kaudio",
> +               .base = 0x8F,
> +       },
> +};
> +
> +static const struct of_device_id twl_clks_of_match[] = {
> +       {
> +               .compatible = "ti,twl6032-clk32kg",
> +               .data = &twl6032_of_clk32kg,
> +       },
> +       {
> +               .compatible = "ti,twl6032-clk32kaudio",
> +               .data = &twl6032_of_clk32kaudio,
> +       },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, twl_clks_of_match);

This array can be moved next to the driver so that it isn't tempting to
access the variable directly.

> +
> +static int twl_clks_probe(struct platform_device *pdev)
> +{
> +       struct device_node *node = pdev->dev.of_node;
> +       const struct twl_clks_of_match_data *match_data;
> +       struct twl_clock_info *cinfo;
> +       int ret;
> +
> +       match_data = of_device_get_match_data(&pdev->dev);

Use device_get_match_data() instead.

> +       if (!match_data)
> +               return 1;
> +
> +       cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
> +       if (!cinfo)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, cinfo);
> +
> +       cinfo->dev = &pdev->dev;
> +
> +       cinfo->clk_desc = &match_data->desc;
> +       cinfo->hw.init = &match_data->init;
> +       ret = devm_clk_hw_register(&pdev->dev, &cinfo->hw);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Fail to register clock %s, %d\n",
> +                       match_data->desc.clk_name, ret);
> +               return ret;
> +       }
> +
> +       ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw);

Use devm?

> +       if (ret < 0)
> +               dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);
> +       return ret;
> +}
> +
> +static void twl_clks_remove(struct platform_device *pdev)
> +{
> +       of_clk_del_provider(pdev->dev.of_node);
> +}

And get rid of this entirely?

> +
> +static struct platform_driver twl_clks_driver = {
> +       .driver = {
> +               .name = "twl-clk",
> +               .of_match_table = twl_clks_of_match,
> +       },
> +       .probe = twl_clks_probe,
> +       .remove_new = twl_clks_remove,
> +};
> +
> +module_platform_driver(twl_clks_driver);
> +
> +MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
> +MODULE_ALIAS("platform:twl-clk");

This alias is unnecessary?

> +MODULE_LICENSE("GPL");
  
Andreas Kemnade Aug. 23, 2023, 2:51 p.m. UTC | #2
On Tue, 22 Aug 2023 15:34:59 -0700
Stephen Boyd <sboyd@kernel.org> wrote:

> Quoting Andreas Kemnade (2023-08-19 06:41:46)
> > diff --git a/drivers/clk/clk-twl.c b/drivers/clk/clk-twl.c
> > new file mode 100644
> > index 0000000000000..deb5742393bac
> > --- /dev/null
> > +++ b/drivers/clk/clk-twl.c
[...]
> > +
> > +static struct platform_driver twl_clks_driver = {
> > +       .driver = {
> > +               .name = "twl-clk",
> > +               .of_match_table = twl_clks_of_match,
> > +       },
> > +       .probe = twl_clks_probe,
> > +       .remove_new = twl_clks_remove,
> > +};
> > +
> > +module_platform_driver(twl_clks_driver);
> > +
> > +MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
> > +MODULE_ALIAS("platform:twl-clk");  
> 
> This alias is unnecessary?
> 
The question is whether this driver should have a separate dt
node (and if a separate node, then one per clock as the clk-palmas
driver) or not. See Rob's review of the binding document.
So we have basically #clock-cells = <1>; in the twl parent
and a call to mfd_add_device() there in the former case and I guess
that alias is needed then.

But if the overall structure stays as in this version,
then I doubt that we need that alias.

Regards,
Andreas
  
Andreas Kemnade Aug. 28, 2023, 4:24 p.m. UTC | #3
Hi,

On Thu, 24 Aug 2023 09:04:28 +0200
Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> wrote:

> >>> +
> >>> +MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
> >>> +MODULE_ALIAS("platform:twl-clk");    
> >>
> >> This alias is unnecessary?
> >>  
> > The question is whether this driver should have a separate dt
> > node (and if a separate node, then one per clock as the clk-palmas
> > driver) or not. See Rob's review of the binding document.
> > So we have basically #clock-cells = <1>; in the twl parent
> > and a call to mfd_add_device() there in the former case and I guess
> > that alias is needed then.
> >   
> 
> You should not need the alias in any of these cases. platform alias for
> platform driver means you have incomplete tables and use alias instead
> of tables. Preference is to use tables.

Is there a general consensus that MODULE_ALIAS("platform:.*") should be
exorcised? Of course for this new driver I will avoid it now anyways.

Regards,
Andreas
  

Patch

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 6b3b424addab4..e927f88d6014c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -277,6 +277,15 @@  config COMMON_CLK_S2MPS11
 	  clock. These multi-function devices have two (S2MPS14) or three
 	  (S2MPS11, S5M8767) fixed-rate oscillators, clocked at 32KHz each.
 
+config CLK_TWL
+	tristate "Clock driver for the TWL PMIC family"
+	depends on TWL4030_CORE
+	help
+	  Enable support for controlling the clock resources on TWL family
+	  PMICs. These devices have some 32K clock outputs which can be
+	  controlled by software. For now, only the TWL6032 clocks are
+	  supported.
+
 config CLK_TWL6040
 	tristate "External McPDM functional clock from twl6040"
 	depends on TWL6040_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 7cb000549b612..31c04e23b7a90 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -73,6 +73,7 @@  obj-$(CONFIG_COMMON_CLK_STM32H7)	+= clk-stm32h7.o
 obj-$(CONFIG_COMMON_CLK_STM32MP157)	+= clk-stm32mp1.o
 obj-$(CONFIG_COMMON_CLK_TPS68470)      += clk-tps68470.o
 obj-$(CONFIG_CLK_TWL6040)		+= clk-twl6040.o
+obj-$(CONFIG_CLK_TWL)			+= clk-twl.o
 obj-$(CONFIG_ARCH_VT8500)		+= clk-vt8500.o
 obj-$(CONFIG_COMMON_CLK_RS9_PCIE)	+= clk-renesas-pcie.o
 obj-$(CONFIG_COMMON_CLK_SI521XX)	+= clk-si521xx.o
diff --git a/drivers/clk/clk-twl.c b/drivers/clk/clk-twl.c
new file mode 100644
index 0000000000000..deb5742393bac
--- /dev/null
+++ b/drivers/clk/clk-twl.c
@@ -0,0 +1,205 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock driver for twl device.
+ *
+ * inspired by the driver for the Palmas device
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/twl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define VREG_STATE              2
+#define TWL6030_CFG_STATE_OFF   0x00
+#define TWL6030_CFG_STATE_ON    0x01
+#define TWL6030_CFG_STATE_MASK  0x03
+
+struct twl_clk32k_desc {
+	const char *clk_name;
+	u8 base;
+};
+
+struct twl_clock_info {
+	struct device *dev;
+	struct clk_hw hw;
+	const struct twl_clk32k_desc *clk_desc;
+};
+
+static inline int
+twlclk_read(struct twl_clock_info *info, unsigned int slave_subgp,
+	    unsigned int offset)
+{
+	u8 value;
+	int status;
+
+	status = twl_i2c_read_u8(slave_subgp, &value,
+				 info->clk_desc->base + offset);
+	return (status < 0) ? status : value;
+}
+
+static inline int
+twlclk_write(struct twl_clock_info *info, unsigned int slave_subgp,
+	     unsigned int offset, u8 value)
+{
+	return twl_i2c_write_u8(slave_subgp, value,
+				info->clk_desc->base + offset);
+}
+
+static inline struct twl_clock_info *to_twl_clks_info(struct clk_hw *hw)
+{
+	return container_of(hw, struct twl_clock_info, hw);
+}
+
+static unsigned long twl_clks_recalc_rate(struct clk_hw *hw,
+					  unsigned long parent_rate)
+{
+	return 32768;
+}
+
+static int twl6032_clks_prepare(struct clk_hw *hw)
+{
+	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
+	int ret;
+
+	ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
+			   TWL6030_CFG_STATE_ON);
+	if (ret < 0)
+		dev_err(cinfo->dev, "clk prepare failed\n");
+
+	return ret;
+}
+
+static void twl6032_clks_unprepare(struct clk_hw *hw)
+{
+	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
+	int ret;
+
+	ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
+			   TWL6030_CFG_STATE_OFF);
+	if (ret < 0)
+		dev_err(cinfo->dev, "clk unprepare failed\n");
+}
+
+static int twl6032_clks_is_prepared(struct clk_hw *hw)
+{
+	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
+	int val;
+
+	val = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE);
+	if (val < 0) {
+		dev_err(cinfo->dev, "clk read failed\n");
+		return val;
+	}
+
+	val &= TWL6030_CFG_STATE_MASK;
+
+	return val == TWL6030_CFG_STATE_ON;
+}
+
+static const struct clk_ops twl6032_clks_ops = {
+	.prepare	= twl6032_clks_prepare,
+	.unprepare	= twl6032_clks_unprepare,
+	.is_prepared	= twl6032_clks_is_prepared,
+	.recalc_rate	= twl_clks_recalc_rate,
+};
+
+struct twl_clks_of_match_data {
+	struct clk_init_data init;
+	const struct twl_clk32k_desc desc;
+};
+
+static const struct twl_clks_of_match_data twl6032_of_clk32kg = {
+	.init = {
+		.name = "clk32kg",
+		.ops = &twl6032_clks_ops,
+		.flags = CLK_IGNORE_UNUSED,
+	},
+	.desc = {
+		.clk_name = "clk32kg",
+		.base = 0x8C,
+	},
+};
+
+static const struct twl_clks_of_match_data twl6032_of_clk32kaudio = {
+	.init = {
+		.name = "clk32kaudio",
+		.ops = &twl6032_clks_ops,
+		.flags = CLK_IGNORE_UNUSED,
+	},
+	.desc = {
+		.clk_name = "clk32kaudio",
+		.base = 0x8F,
+	},
+};
+
+static const struct of_device_id twl_clks_of_match[] = {
+	{
+		.compatible = "ti,twl6032-clk32kg",
+		.data = &twl6032_of_clk32kg,
+	},
+	{
+		.compatible = "ti,twl6032-clk32kaudio",
+		.data = &twl6032_of_clk32kaudio,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, twl_clks_of_match);
+
+static int twl_clks_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	const struct twl_clks_of_match_data *match_data;
+	struct twl_clock_info *cinfo;
+	int ret;
+
+	match_data = of_device_get_match_data(&pdev->dev);
+	if (!match_data)
+		return 1;
+
+	cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
+	if (!cinfo)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, cinfo);
+
+	cinfo->dev = &pdev->dev;
+
+	cinfo->clk_desc = &match_data->desc;
+	cinfo->hw.init = &match_data->init;
+	ret = devm_clk_hw_register(&pdev->dev, &cinfo->hw);
+	if (ret) {
+		dev_err(&pdev->dev, "Fail to register clock %s, %d\n",
+			match_data->desc.clk_name, ret);
+		return ret;
+	}
+
+	ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw);
+	if (ret < 0)
+		dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);
+	return ret;
+}
+
+static void twl_clks_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+}
+
+static struct platform_driver twl_clks_driver = {
+	.driver = {
+		.name = "twl-clk",
+		.of_match_table = twl_clks_of_match,
+	},
+	.probe = twl_clks_probe,
+	.remove_new = twl_clks_remove,
+};
+
+module_platform_driver(twl_clks_driver);
+
+MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
+MODULE_ALIAS("platform:twl-clk");
+MODULE_LICENSE("GPL");