diff mbox

[V2] clk: palmas: add clock driver for palmas

Message ID 1381154751-4565-1-git-send-email-ldewangan@nvidia.com (mailing list archive)
State New, archived
Headers show

Commit Message

Laxman Dewangan Oct. 7, 2013, 2:05 p.m. UTC
Palmas devices has two clock output CLK32K_KG and CLK32K_KG_AUDIO
which can be nebale/disable through software.

Add clock driver support for the Palmas 32k clocks.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
---
Changes from V1:
- Call prepare if the clk is needed for boot-enable or sleep control.
- change is_enabled to is_prepared.
- Added ops palmas_clks_recalc_rate.
- Added of_clk_add_provider().

 .../devicetree/bindings/clock/clk-palmas.txt       |   45 +++
 drivers/clk/Kconfig                                |    7 +
 drivers/clk/Makefile                               |    2 +
 drivers/clk/clk-palmas.c                           |  336 ++++++++++++++++++++
 4 files changed, 390 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/clock/clk-palmas.txt
 create mode 100644 drivers/clk/clk-palmas.c

Comments

Laxman Dewangan Oct. 7, 2013, 2:23 p.m. UTC | #1
On Monday 07 October 2013 07:35 PM, Laxman Dewangan wrote:
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -43,3 +43,5 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>   obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
>   obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
>   obj-$(CONFIG_CLK_PPC_CORENET)	+= clk-ppc-corenet.o
> +obj-$(CONFIG_COMMON_CLK_PALMAS)	+= clk-palmas.o
> +obj-y += clk-test.o
>

Sorry, silly mistake, the line

+obj-y += clk-test.o

is not require. This was done for testing only.

I will respin the patch but wait for some more feedback if any to add on 
next version.

Thanks,
Laxman
Mike Turquette Oct. 8, 2013, 1:02 a.m. UTC | #2
Quoting Laxman Dewangan (2013-10-07 07:05:51)
> Palmas devices has two clock output CLK32K_KG and CLK32K_KG_AUDIO
> which can be nebale/disable through software.
> 
> Add clock driver support for the Palmas 32k clocks.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
> Changes from V1:
> - Call prepare if the clk is needed for boot-enable or sleep control.
> - change is_enabled to is_prepared.
> - Added ops palmas_clks_recalc_rate.
> - Added of_clk_add_provider().
> 
>  .../devicetree/bindings/clock/clk-palmas.txt       |   45 +++
>  drivers/clk/Kconfig                                |    7 +
>  drivers/clk/Makefile                               |    2 +
>  drivers/clk/clk-palmas.c                           |  336 ++++++++++++++++++++
>  4 files changed, 390 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/clock/clk-palmas.txt
>  create mode 100644 drivers/clk/clk-palmas.c
> 
> diff --git a/Documentation/devicetree/bindings/clock/clk-palmas.txt b/Documentation/devicetree/bindings/clock/clk-palmas.txt
> new file mode 100644
> index 0000000..c247538
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/clk-palmas.txt
> @@ -0,0 +1,45 @@
> +* Palmas 32KHz clocks *
> +
> +Palmas device has two clock output pins for 32KHz, KG and KG_AUDIO.
> +
> +This binding uses the common clock binding ./clock-bindings.txt.
> +
> +Clock 32KHz KG is output 0 of the driver and clock 32KHz is output 1.
> +
> +Required properties:
> +- compatible : shall be "ti,palmas-clk".
> +- #clock-cells : from common clock binding; shall be set to 1.
> +
> +Optional subnode:
> +       The clock node has optional subnode to provide the init configuration of
> +       clocks like boot enable, sleep control.
> +
> +       The subnode name is fixed and it should be
> +               clk32k_kg for the 32KHz KG clock.
> +               clk32k_kg_audio for the 32KHz KG_AUDIO clock.
> +
> +       Optional subnode properties:
> +       ti,clock-boot-enable: Enable clock at the time of booting.
> +       ti,external-sleep-control: The clock is enable/disabled by state
> +               of external enable input pins ENABLE, ENABLE2 and NSLEEP.
> +               The valid value for the external pins are:
> +                       1 for ENABLE1
> +                       2 for ENABLE2
> +                       3 for NSLEEP.
> +               Option 0 or missing this property means the clock is
> +               enabled/disabled via register access and these pins do
> +               not have any control.
> +
> +Example:
> +       clock {
> +               compatible = "ti,palmas-clk";
> +               #clock-cells = <1>;
> +               clk32k_kg {
> +                       ti,clock-boot-enable;
> +                       ti,external-sleep-control = <3>;
> +               };
> +
> +               clk32k_kg_audio {
> +                       ti,clock-boot-enable;
> +               };
> +       };
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 279407a..6b8c233 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -93,6 +93,13 @@ config CLK_PPC_CORENET
>           This adds the clock driver support for Freescale PowerPC corenet
>           platforms using common clock framework.
>  
> +config COMMON_CLK_PALMAS
> +       tristate "Clock driver for TI Palmas devices"
> +       depends on MFD_PALMAS
> +       ---help---
> +         This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO
> +         using common clock framework.
> +
>  endmenu
>  
>  source "drivers/clk/mvebu/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 7b11106..f168ff7 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -43,3 +43,5 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
>  obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
>  obj-$(CONFIG_CLK_TWL6040)      += clk-twl6040.o
>  obj-$(CONFIG_CLK_PPC_CORENET)  += clk-ppc-corenet.o
> +obj-$(CONFIG_COMMON_CLK_PALMAS)        += clk-palmas.o
> +obj-y += clk-test.o
> diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c
> new file mode 100644
> index 0000000..804c76e
> --- /dev/null
> +++ b/drivers/clk/clk-palmas.c
> @@ -0,0 +1,336 @@
> +/*
> + * Clock driver for Palmas device.
> + *
> + * Copyright (c) 2013, NVIDIA Corporation.
> + *
> + * Author: Laxman Dewangan <ldewangan@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
> + * whether express or implied; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
> + * 02111-1307, USA
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/mfd/palmas.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +enum PALMAS_CLOCK32K {
> +       PALMAS_CLOCK32KG,
> +       PALMAS_CLOCK32KG_AUDIO,
> +
> +       /* Last entry */
> +       PALMAS_CLOCK32K_NR,
> +};
> +
> +struct palmas_clks;
> +
> +struct palmas_clk32k_desc {
> +       const char *clk_name;
> +       unsigned int control_reg;
> +       unsigned int enable_mask;
> +       unsigned int sleep_mask;
> +       unsigned int sleep_reqstr_id;
> +};
> +
> +struct palmas_clock_info {
> +       struct clk *clk;
> +       struct clk_hw hw;
> +       struct palmas_clk32k_desc *clk_desc;
> +       struct palmas_clks *palmas_clk;
> +       int ext_control_pin;
> +       bool boot_enable;
> +};
> +
> +struct palmas_clks {
> +       struct device *dev;
> +       struct palmas *palmas;
> +       struct clk_onecell_data clk_data;
> +       struct palmas_clock_info clk_info[PALMAS_CLOCK32K_NR];
> +};
> +
> +static struct palmas_clk32k_desc palmas_clk32k_descs[] = {
> +       {
> +               .clk_name = "clk32k_kg",
> +               .control_reg = PALMAS_CLK32KG_CTRL,
> +               .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
> +               .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
> +               .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG,
> +       }, {
> +               .clk_name = "clk32k_kg_audio",
> +               .control_reg = PALMAS_CLK32KGAUDIO_CTRL,
> +               .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
> +               .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
> +               .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO,
> +       },
> +};
> +
> +static inline struct palmas_clock_info *to_palmas_clks_info(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct palmas_clock_info, hw);
> +}
> +
> +static unsigned long palmas_clks_recalc_rate(struct clk_hw *hw,
> +       unsigned long parent_rate)
> +{
> +       return 32768;
> +}
> +
> +static int palmas_clks_prepare(struct clk_hw *hw)
> +{
> +       struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
> +       struct palmas_clks *palmas_clks = cinfo->palmas_clk;
> +       int ret;
> +
> +       ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
> +                       cinfo->clk_desc->control_reg,
> +                       cinfo->clk_desc->enable_mask,
> +                       cinfo->clk_desc->enable_mask);
> +       if (ret < 0)
> +               dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
> +                       cinfo->clk_desc->control_reg, ret);
> +
> +       return ret;
> +}
> +
> +static void palmas_clks_unprepare(struct clk_hw *hw)
> +{
> +       struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
> +       struct palmas_clks *palmas_clks = cinfo->palmas_clk;
> +       int ret;
> +
> +       /*
> +        * Clock can be disabled through external pin if it is externally
> +        * controlled.
> +        */
> +       if (cinfo->ext_control_pin)
> +               return;
> +
> +       ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
> +                       cinfo->clk_desc->control_reg,
> +                       cinfo->clk_desc->enable_mask, 0);
> +       if (ret < 0)
> +               dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
> +                       cinfo->clk_desc->control_reg, ret);
> +
> +}
> +
> +static int palmas_clks_is_prepared(struct clk_hw *hw)
> +{
> +       struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
> +       struct palmas_clks *palmas_clks = cinfo->palmas_clk;
> +       int ret;
> +       u32 val;
> +
> +       if (cinfo->ext_control_pin)
> +               return 1;
> +
> +       ret = palmas_read(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
> +                       cinfo->clk_desc->control_reg, &val);
> +       if (ret < 0) {
> +               dev_err(palmas_clks->dev, "Reg 0x%02x read failed, %d\n",
> +                               cinfo->clk_desc->control_reg, ret);
> +               return ret;
> +       }
> +       return !!(val & cinfo->clk_desc->enable_mask);
> +}
> +
> +static struct clk_ops palmas_clks_ops = {
> +       .prepare        = palmas_clks_prepare,
> +       .unprepare      = palmas_clks_unprepare,
> +       .is_prepared    = palmas_clks_is_prepared,
> +       .recalc_rate    = palmas_clks_recalc_rate,
> +};
> +
> +static struct clk_init_data palmas_clks_hw_init[PALMAS_CLOCK32K_NR] = {
> +       [PALMAS_CLOCK32KG] = {
> +               .name = "clk32k_kg",
> +               .ops = &palmas_clks_ops,
> +               .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED,
> +       },
> +       [PALMAS_CLOCK32KG_AUDIO] = {
> +               .name = "clk32k_kg_audio",
> +               .ops = &palmas_clks_ops,
> +               .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED,
> +       },
> +};
> +
> +static int palmas_clks_get_clk_data(struct platform_device *pdev,
> +       struct palmas_clks *palmas_clks)
> +{
> +       struct device_node *node = pdev->dev.of_node;
> +       struct device_node *child;
> +       struct palmas_clock_info *cinfo;
> +       unsigned int prop;
> +       int ret;
> +       int i;
> +
> +       for (i = 0; i < PALMAS_CLOCK32K_NR; ++i) {
> +               child = of_get_child_by_name(node,
> +                               palmas_clk32k_descs[i].clk_name);
> +               if (!child)
> +                       continue;
> +
> +               cinfo = &palmas_clks->clk_info[i];
> +               cinfo->boot_enable = of_property_read_bool(child,
> +                                               "ti,clock-boot-enable");
> +               ret = of_property_read_u32(child, "ti,external-sleep-control",
> +                                       &prop);
> +               if (!ret) {
> +                       switch (prop) {
> +                       case 1:
> +                               prop = PALMAS_EXT_CONTROL_ENABLE1;
> +                               break;
> +                       case 2:
> +                               prop = PALMAS_EXT_CONTROL_ENABLE2;
> +                               break;
> +                       case 3:
> +                               prop = PALMAS_EXT_CONTROL_NSLEEP;
> +                               break;

Can magic numbers be replaced with defines?

> +                       default:
> +                               WARN_ON(1);
> +                               dev_warn(&pdev->dev,
> +                                       "%s: Invalid ext control option: %u\n",
> +                                       child->name, prop);
> +                               prop = 0;
> +                               break;
> +                       }
> +                       cinfo->ext_control_pin = prop;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int palmas_clks_init_configure(struct palmas_clock_info *cinfo)
> +{
> +       struct palmas_clks *palmas_clks = cinfo->palmas_clk;
> +       int ret;
> +
> +       if (cinfo->boot_enable || cinfo->ext_control_pin) {
> +               ret = clk_prepare(cinfo->clk);
> +               if (ret < 0) {
> +                       dev_err(palmas_clks->dev,
> +                               "Clock prep failed, %d\n", ret);
> +                       return ret;
> +               }
> +       }
> +
> +       ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
> +                       cinfo->clk_desc->control_reg,
> +                       cinfo->clk_desc->sleep_mask, 0);

What does this call to palmas_update_bits do?

> +       if (ret < 0) {
> +               dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
> +                       cinfo->clk_desc->control_reg, ret);
> +               return ret;
> +       }
> +
> +       if (cinfo->ext_control_pin) {
> +               ret = palmas_ext_control_req_config(palmas_clks->palmas,
> +                               cinfo->clk_desc->sleep_reqstr_id,
> +                               cinfo->ext_control_pin, true);
> +               if (ret < 0) {
> +                       dev_err(palmas_clks->dev,
> +                               "Ext config for %s failed, %d\n",
> +                               cinfo->clk_desc->clk_name, ret);
> +                       return ret;
> +               }
> +       }
> +
> +       return ret;
> +}
> +
> +static int palmas_clks_probe(struct platform_device *pdev)
> +{
> +       struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
> +       struct palmas_clock_info *cinfo;
> +       struct palmas_clks *palmas_clks;
> +       struct clk *clk;
> +       int i, ret;
> +
> +       palmas_clks = devm_kzalloc(&pdev->dev, sizeof(*palmas_clks),
> +                               GFP_KERNEL);
> +       if (!palmas_clks)
> +               return -ENOMEM;
> +
> +       palmas_clks->clk_data.clks = devm_kzalloc(&pdev->dev,
> +                       PALMAS_CLOCK32K_NR * sizeof(palmas_clks->clk_data.clks),
> +                       GFP_KERNEL);
> +       if (!palmas_clks->clk_data.clks)
> +               return -ENOMEM;
> +
> +       palmas_clks_get_clk_data(pdev, palmas_clks);
> +       platform_set_drvdata(pdev, palmas_clks);
> +
> +       palmas_clks->dev = &pdev->dev;
> +       palmas_clks->palmas = palmas;
> +
> +       for (i = 0; i < PALMAS_CLOCK32K_NR; i++) {
> +               cinfo = &palmas_clks->clk_info[i];
> +               cinfo->clk_desc = &palmas_clk32k_descs[i];
> +               cinfo->hw.init = &palmas_clks_hw_init[i];
> +               cinfo->palmas_clk = palmas_clks;
> +               clk = devm_clk_register(&pdev->dev, &cinfo->hw);
> +               if (IS_ERR(clk)) {
> +                       ret = PTR_ERR(clk);
> +                       dev_err(&pdev->dev, "Fail to register clock %s, %d\n",
> +                               palmas_clk32k_descs[i].clk_name, ret);
> +                       return ret;
> +               }
> +
> +               cinfo->clk = clk;
> +               palmas_clks->clk_data.clks[i] = clk;
> +               palmas_clks->clk_data.clk_num++;
> +               palmas_clks_init_configure(cinfo);
> +       }
> +
> +       ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get,
> +                       &palmas_clks->clk_data);
> +       if (ret < 0)
> +               dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);
> +       return ret;
> +}
> +
> +static int palmas_clks_remove(struct platform_device *pdev)
> +{
> +       of_clk_del_provider(pdev->dev.of_node);

I'll be taking Sylwester's clock deregistration series after he
publishes the next version, so if you want to call clk_unregister here
(based on the new call) you could.

If you want to add that in a later patch it is OK.

Regards,
Mike

> +       return 0;
> +}
> +
> +static struct of_device_id of_palmas_clks_match_tbl[] = {
> +       { .compatible = "ti,palmas-clk", },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, of_palmas_clks_match_tbl);
> +
> +static struct platform_driver palmas_clks_driver = {
> +       .driver = {
> +               .name = "palmas-clk",
> +               .owner = THIS_MODULE,
> +               .of_match_table = of_palmas_clks_match_tbl,
> +       },
> +       .probe = palmas_clks_probe,
> +       .remove = palmas_clks_remove,
> +};
> +
> +module_platform_driver(palmas_clks_driver);
> +
> +MODULE_DESCRIPTION("Clock driver for Palmas Series Devices");
> +MODULE_ALIAS("platform:palmas-clk");
> +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.7.1.1
Laxman Dewangan Oct. 8, 2013, 1:15 p.m. UTC | #3
On Tuesday 08 October 2013 06:32 AM, Mike Turquette wrote:
> Quoting Laxman Dewangan (2013-10-07 07:05:51)
>> +               if (!ret) {
>> +                       switch (prop) {
>> +                       case 1:
>> +                               prop = PALMAS_EXT_CONTROL_ENABLE1;
>> +                               break;
>> +                       case 2:
>> +                               prop = PALMAS_EXT_CONTROL_ENABLE2;
>> +                               break;
>> +                       case 3:
>> +                               prop = PALMAS_EXT_CONTROL_NSLEEP;
>> +                               break;
> Can magic numbers be replaced with defines?
Fine, will do in V3.

>
>
> +
> +       ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
> +                       cinfo->clk_desc->control_reg,
> +                       cinfo->clk_desc->sleep_mask, 0);
> What does this call to palmas_update_bits do?

This APIs update the selected bits based on mask. This is wrapper over 
the regmap_update_bits() to provide the interface at the Palmas register 
access.
The palmas registers are paged on different i2c address and offset. The 
i2c address and offset is decided based on base_address and offset.
This APIs does all calculation to get the correct i2c slave address and 
offset address based on argument.

>> +static int palmas_clks_remove(struct platform_device *pdev)
>> +{
>> +       of_clk_del_provider(pdev->dev.of_node);
> I'll be taking Sylwester's clock deregistration series after he
> publishes the next version, so if you want to call clk_unregister here
> (based on the new call) you could.
>
> If you want to add that in a later patch it is OK.

Thanks for pointing me this changes. I like to add this on my follow on 
(later) patch, not on this. -- 1.7.1.1
Mark Rutland Oct. 8, 2013, 2:15 p.m. UTC | #4
On Mon, Oct 07, 2013 at 03:05:51PM +0100, Laxman Dewangan wrote:
> Palmas devices has two clock output CLK32K_KG and CLK32K_KG_AUDIO
> which can be nebale/disable through software.
> 
> Add clock driver support for the Palmas 32k clocks.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
> Changes from V1:
> - Call prepare if the clk is needed for boot-enable or sleep control.
> - change is_enabled to is_prepared.
> - Added ops palmas_clks_recalc_rate.
> - Added of_clk_add_provider().
> 
>  .../devicetree/bindings/clock/clk-palmas.txt       |   45 +++
>  drivers/clk/Kconfig                                |    7 +
>  drivers/clk/Makefile                               |    2 +
>  drivers/clk/clk-palmas.c                           |  336 ++++++++++++++++++++
>  4 files changed, 390 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/clock/clk-palmas.txt
>  create mode 100644 drivers/clk/clk-palmas.c
> 
> diff --git a/Documentation/devicetree/bindings/clock/clk-palmas.txt b/Documentation/devicetree/bindings/clock/clk-palmas.txt
> new file mode 100644
> index 0000000..c247538
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/clk-palmas.txt
> @@ -0,0 +1,45 @@
> +* Palmas 32KHz clocks *
> +
> +Palmas device has two clock output pins for 32KHz, KG and KG_AUDIO.
> +
> +This binding uses the common clock binding ./clock-bindings.txt.
> +
> +Clock 32KHz KG is output 0 of the driver and clock 32KHz is output 1.
> +
> +Required properties:
> +- compatible : shall be "ti,palmas-clk".
> +- #clock-cells : from common clock binding; shall be set to 1.

It would make sense to describe the value values here, rather than
above.

> +
> +Optional subnode:
> +       The clock node has optional subnode to provide the init configuration of
> +       clocks like boot enable, sleep control.
> +
> +       The subnode name is fixed and it should be
> +               clk32k_kg for the 32KHz KG clock.
> +               clk32k_kg_audio for the 32KHz KG_AUDIO clock.
> +
> +       Optional subnode properties:
> +       ti,clock-boot-enable: Enable clock at the time of booting.

Why is this needed?

If drivers need clocks, they should request them. If they don't
currently, that's a bug in the driver.

> +       ti,external-sleep-control: The clock is enable/disabled by state
> +               of external enable input pins ENABLE, ENABLE2 and NSLEEP.
> +               The valid value for the external pins are:
> +                       1 for ENABLE1
> +                       2 for ENABLE2
> +                       3 for NSLEEP.
> +               Option 0 or missing this property means the clock is
> +               enabled/disabled via register access and these pins do
> +               not have any control.

What actually drives these pins to control the clock?

> +
> +Example:
> +       clock {
> +               compatible = "ti,palmas-clk";
> +               #clock-cells = <1>;
> +               clk32k_kg {
> +                       ti,clock-boot-enable;
> +                       ti,external-sleep-control = <3>;
> +               };
> +
> +               clk32k_kg_audio {
> +                       ti,clock-boot-enable;
> +               };
> +       };

How does the palmas-clk driver control these? No description of
registers, gpio, or any other component that could be used for control
is dfined.

Is the palmas-clk intended to be a subnode of the palmas mfd (the
palmas-rtc binding example suggests this)? It seems odd to describe
components of a device separately with no linkage between them.

I also note that the palmas MFD appears to be an I2C slave, but the
binding doesn't define that (which makes it somewhat confuding for
someone unfamiliar with the hardware).

Thanks,
Mark.
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/clock/clk-palmas.txt b/Documentation/devicetree/bindings/clock/clk-palmas.txt
new file mode 100644
index 0000000..c247538
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/clk-palmas.txt
@@ -0,0 +1,45 @@ 
+* Palmas 32KHz clocks *
+
+Palmas device has two clock output pins for 32KHz, KG and KG_AUDIO.
+
+This binding uses the common clock binding ./clock-bindings.txt.
+
+Clock 32KHz KG is output 0 of the driver and clock 32KHz is output 1.
+
+Required properties:
+- compatible : shall be "ti,palmas-clk".
+- #clock-cells : from common clock binding; shall be set to 1.
+
+Optional subnode:
+	The clock node has optional subnode to provide the init configuration of
+	clocks like boot enable, sleep control.
+
+	The subnode name is fixed and it should be
+		clk32k_kg for the 32KHz KG clock.
+		clk32k_kg_audio for the 32KHz KG_AUDIO clock.
+
+	Optional subnode properties:
+	ti,clock-boot-enable: Enable clock at the time of booting.
+	ti,external-sleep-control: The clock is enable/disabled by state
+		of external enable input pins ENABLE, ENABLE2 and NSLEEP.
+		The valid value for the external pins are:
+			1 for ENABLE1
+			2 for ENABLE2
+			3 for NSLEEP.
+		Option 0 or missing this property means the clock is
+		enabled/disabled via register access and these pins do
+		not have any control.
+
+Example:
+	clock {
+		compatible = "ti,palmas-clk";
+		#clock-cells = <1>;
+		clk32k_kg {
+			ti,clock-boot-enable;
+			ti,external-sleep-control = <3>;
+		};
+
+		clk32k_kg_audio {
+			ti,clock-boot-enable;
+		};
+	};
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 279407a..6b8c233 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -93,6 +93,13 @@  config CLK_PPC_CORENET
 	  This adds the clock driver support for Freescale PowerPC corenet
 	  platforms using common clock framework.
 
+config COMMON_CLK_PALMAS
+	tristate "Clock driver for TI Palmas devices"
+	depends on MFD_PALMAS
+	---help---
+	  This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO
+	  using common clock framework.
+
 endmenu
 
 source "drivers/clk/mvebu/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 7b11106..f168ff7 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -43,3 +43,5 @@  obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
 obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
 obj-$(CONFIG_CLK_TWL6040)	+= clk-twl6040.o
 obj-$(CONFIG_CLK_PPC_CORENET)	+= clk-ppc-corenet.o
+obj-$(CONFIG_COMMON_CLK_PALMAS)	+= clk-palmas.o
+obj-y += clk-test.o
diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c
new file mode 100644
index 0000000..804c76e
--- /dev/null
+++ b/drivers/clk/clk-palmas.c
@@ -0,0 +1,336 @@ 
+/*
+ * Clock driver for Palmas device.
+ *
+ * Copyright (c) 2013, NVIDIA Corporation.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/palmas.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+enum PALMAS_CLOCK32K {
+	PALMAS_CLOCK32KG,
+	PALMAS_CLOCK32KG_AUDIO,
+
+	/* Last entry */
+	PALMAS_CLOCK32K_NR,
+};
+
+struct palmas_clks;
+
+struct palmas_clk32k_desc {
+	const char *clk_name;
+	unsigned int control_reg;
+	unsigned int enable_mask;
+	unsigned int sleep_mask;
+	unsigned int sleep_reqstr_id;
+};
+
+struct palmas_clock_info {
+	struct clk *clk;
+	struct clk_hw hw;
+	struct palmas_clk32k_desc *clk_desc;
+	struct palmas_clks *palmas_clk;
+	int ext_control_pin;
+	bool boot_enable;
+};
+
+struct palmas_clks {
+	struct device *dev;
+	struct palmas *palmas;
+	struct clk_onecell_data clk_data;
+	struct palmas_clock_info clk_info[PALMAS_CLOCK32K_NR];
+};
+
+static struct palmas_clk32k_desc palmas_clk32k_descs[] = {
+	{
+		.clk_name = "clk32k_kg",
+		.control_reg = PALMAS_CLK32KG_CTRL,
+		.enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
+		.sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
+		.sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG,
+	}, {
+		.clk_name = "clk32k_kg_audio",
+		.control_reg = PALMAS_CLK32KGAUDIO_CTRL,
+		.enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
+		.sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
+		.sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO,
+	},
+};
+
+static inline struct palmas_clock_info *to_palmas_clks_info(struct clk_hw *hw)
+{
+	return container_of(hw, struct palmas_clock_info, hw);
+}
+
+static unsigned long palmas_clks_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	return 32768;
+}
+
+static int palmas_clks_prepare(struct clk_hw *hw)
+{
+	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
+	struct palmas_clks *palmas_clks = cinfo->palmas_clk;
+	int ret;
+
+	ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
+			cinfo->clk_desc->control_reg,
+			cinfo->clk_desc->enable_mask,
+			cinfo->clk_desc->enable_mask);
+	if (ret < 0)
+		dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
+			cinfo->clk_desc->control_reg, ret);
+
+	return ret;
+}
+
+static void palmas_clks_unprepare(struct clk_hw *hw)
+{
+	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
+	struct palmas_clks *palmas_clks = cinfo->palmas_clk;
+	int ret;
+
+	/*
+	 * Clock can be disabled through external pin if it is externally
+	 * controlled.
+	 */
+	if (cinfo->ext_control_pin)
+		return;
+
+	ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
+			cinfo->clk_desc->control_reg,
+			cinfo->clk_desc->enable_mask, 0);
+	if (ret < 0)
+		dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
+			cinfo->clk_desc->control_reg, ret);
+
+}
+
+static int palmas_clks_is_prepared(struct clk_hw *hw)
+{
+	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
+	struct palmas_clks *palmas_clks = cinfo->palmas_clk;
+	int ret;
+	u32 val;
+
+	if (cinfo->ext_control_pin)
+		return 1;
+
+	ret = palmas_read(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
+			cinfo->clk_desc->control_reg, &val);
+	if (ret < 0) {
+		dev_err(palmas_clks->dev, "Reg 0x%02x read failed, %d\n",
+				cinfo->clk_desc->control_reg, ret);
+		return ret;
+	}
+	return !!(val & cinfo->clk_desc->enable_mask);
+}
+
+static struct clk_ops palmas_clks_ops = {
+	.prepare	= palmas_clks_prepare,
+	.unprepare	= palmas_clks_unprepare,
+	.is_prepared	= palmas_clks_is_prepared,
+	.recalc_rate	= palmas_clks_recalc_rate,
+};
+
+static struct clk_init_data palmas_clks_hw_init[PALMAS_CLOCK32K_NR] = {
+	[PALMAS_CLOCK32KG] = {
+		.name = "clk32k_kg",
+		.ops = &palmas_clks_ops,
+		.flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED,
+	},
+	[PALMAS_CLOCK32KG_AUDIO] = {
+		.name = "clk32k_kg_audio",
+		.ops = &palmas_clks_ops,
+		.flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED,
+	},
+};
+
+static int palmas_clks_get_clk_data(struct platform_device *pdev,
+	struct palmas_clks *palmas_clks)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device_node *child;
+	struct palmas_clock_info *cinfo;
+	unsigned int prop;
+	int ret;
+	int i;
+
+	for (i = 0; i < PALMAS_CLOCK32K_NR; ++i) {
+		child = of_get_child_by_name(node,
+				palmas_clk32k_descs[i].clk_name);
+		if (!child)
+			continue;
+
+		cinfo = &palmas_clks->clk_info[i];
+		cinfo->boot_enable = of_property_read_bool(child,
+						"ti,clock-boot-enable");
+		ret = of_property_read_u32(child, "ti,external-sleep-control",
+					&prop);
+		if (!ret) {
+			switch (prop) {
+			case 1:
+				prop = PALMAS_EXT_CONTROL_ENABLE1;
+				break;
+			case 2:
+				prop = PALMAS_EXT_CONTROL_ENABLE2;
+				break;
+			case 3:
+				prop = PALMAS_EXT_CONTROL_NSLEEP;
+				break;
+			default:
+				WARN_ON(1);
+				dev_warn(&pdev->dev,
+					"%s: Invalid ext control option: %u\n",
+					child->name, prop);
+				prop = 0;
+				break;
+			}
+			cinfo->ext_control_pin = prop;
+		}
+	}
+
+	return 0;
+}
+
+static int palmas_clks_init_configure(struct palmas_clock_info *cinfo)
+{
+	struct palmas_clks *palmas_clks = cinfo->palmas_clk;
+	int ret;
+
+	if (cinfo->boot_enable || cinfo->ext_control_pin) {
+		ret = clk_prepare(cinfo->clk);
+		if (ret < 0) {
+			dev_err(palmas_clks->dev,
+				"Clock prep failed, %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = palmas_update_bits(palmas_clks->palmas, PALMAS_RESOURCE_BASE,
+			cinfo->clk_desc->control_reg,
+			cinfo->clk_desc->sleep_mask, 0);
+	if (ret < 0) {
+		dev_err(palmas_clks->dev, "Reg 0x%02x update failed, %d\n",
+			cinfo->clk_desc->control_reg, ret);
+		return ret;
+	}
+
+	if (cinfo->ext_control_pin) {
+		ret = palmas_ext_control_req_config(palmas_clks->palmas,
+				cinfo->clk_desc->sleep_reqstr_id,
+				cinfo->ext_control_pin, true);
+		if (ret < 0) {
+			dev_err(palmas_clks->dev,
+				"Ext config for %s failed, %d\n",
+				cinfo->clk_desc->clk_name, ret);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int palmas_clks_probe(struct platform_device *pdev)
+{
+	struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
+	struct palmas_clock_info *cinfo;
+	struct palmas_clks *palmas_clks;
+	struct clk *clk;
+	int i, ret;
+
+	palmas_clks = devm_kzalloc(&pdev->dev, sizeof(*palmas_clks),
+				GFP_KERNEL);
+	if (!palmas_clks)
+		return -ENOMEM;
+
+	palmas_clks->clk_data.clks = devm_kzalloc(&pdev->dev,
+			PALMAS_CLOCK32K_NR * sizeof(palmas_clks->clk_data.clks),
+			GFP_KERNEL);
+	if (!palmas_clks->clk_data.clks)
+		return -ENOMEM;
+
+	palmas_clks_get_clk_data(pdev, palmas_clks);
+	platform_set_drvdata(pdev, palmas_clks);
+
+	palmas_clks->dev = &pdev->dev;
+	palmas_clks->palmas = palmas;
+
+	for (i = 0; i < PALMAS_CLOCK32K_NR; i++) {
+		cinfo = &palmas_clks->clk_info[i];
+		cinfo->clk_desc = &palmas_clk32k_descs[i];
+		cinfo->hw.init = &palmas_clks_hw_init[i];
+		cinfo->palmas_clk = palmas_clks;
+		clk = devm_clk_register(&pdev->dev, &cinfo->hw);
+		if (IS_ERR(clk)) {
+			ret = PTR_ERR(clk);
+			dev_err(&pdev->dev, "Fail to register clock %s, %d\n",
+				palmas_clk32k_descs[i].clk_name, ret);
+			return ret;
+		}
+
+		cinfo->clk = clk;
+		palmas_clks->clk_data.clks[i] = clk;
+		palmas_clks->clk_data.clk_num++;
+		palmas_clks_init_configure(cinfo);
+	}
+
+	ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get,
+			&palmas_clks->clk_data);
+	if (ret < 0)
+		dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);
+	return ret;
+}
+
+static int palmas_clks_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+	return 0;
+}
+
+static struct of_device_id of_palmas_clks_match_tbl[] = {
+	{ .compatible = "ti,palmas-clk", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_palmas_clks_match_tbl);
+
+static struct platform_driver palmas_clks_driver = {
+	.driver = {
+		.name = "palmas-clk",
+		.owner = THIS_MODULE,
+		.of_match_table = of_palmas_clks_match_tbl,
+	},
+	.probe = palmas_clks_probe,
+	.remove = palmas_clks_remove,
+};
+
+module_platform_driver(palmas_clks_driver);
+
+MODULE_DESCRIPTION("Clock driver for Palmas Series Devices");
+MODULE_ALIAS("platform:palmas-clk");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");