mbox series

[0/3] ARM: omap: omap4-embt2ws: 32K clock for WLAN

Message ID 20230819134147.456060-1-andreas@kemnade.info
Headers show
Series ARM: omap: omap4-embt2ws: 32K clock for WLAN | expand

Message

Andreas Kemnade Aug. 19, 2023, 1:41 p.m. UTC
To have WLAN working properly, enable a 32K clock of the TWL6032.
In earlier tests, it was still enabled from a previous boot into
the vendor system.

Andreas Kemnade (3):
  dt-bindings: clock: add TWL6032 32K clocks
  clk: twl: add clock driver for TWL6032
  ARM: dts: omap4-embt2ws: enable 32K clock on WLAN

 .../bindings/clock/ti,twl6032-clk.yaml        |  38 ++++
 .../boot/dts/ti/omap/omap4-epson-embt2ws.dts  |  12 +
 drivers/clk/Kconfig                           |   9 +
 drivers/clk/Makefile                          |   1 +
 drivers/clk/clk-twl.c                         | 205 ++++++++++++++++++
 5 files changed, 265 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/ti,twl6032-clk.yaml
 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");
Krzysztof Kozlowski Aug. 28, 2023, 5:04 p.m. UTC | #2
On 28/08/2023 18:24, Andreas Kemnade wrote:
> 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.

Whether "general" I don't know, but I was removing it quite a lot in the
past. I think I removed all at some point, now I guess we have them back :/.

MODULE_ALIAS is not the correct way to solve module matching problem. ID
table with the correct way. Alias is just a workaround which now works,
but later might stop (e.g. ID table will come with additional features).

Best regards,
Krzysztof