Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752437AbbHYFIb (ORCPT ); Tue, 25 Aug 2015 01:08:31 -0400 Received: from mailout4.w1.samsung.com ([210.118.77.14]:8896 "EHLO mailout4.w1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750983AbbHYFI2 (ORCPT ); Tue, 25 Aug 2015 01:08:28 -0400 X-AuditID: cbfec7f5-f794b6d000001495-86-55dbf84883fb Message-id: <55DBF845.4070700@samsung.com> Date: Tue, 25 Aug 2015 14:08:21 +0900 From: Krzysztof Kozlowski User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.8.0 MIME-version: 1.0 To: Vaibhav Hiremath , linux-arm-kernel@lists.infradead.org Cc: robh+dt@kernel.org, mturquette@baylibre.com, lee.jones@linaro.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org Subject: Re: [PATCH-v2 4/5] clk: 88pm800: Add clk provider driver for 88pm800 family of devices References: <1440442594-3102-1-git-send-email-vaibhav.hiremath@linaro.org> <1440442594-3102-6-git-send-email-vaibhav.hiremath@linaro.org> In-reply-to: <1440442594-3102-6-git-send-email-vaibhav.hiremath@linaro.org> Content-type: text/plain; charset=windows-1252 Content-transfer-encoding: 7bit X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFnrBLMWRmVeSWpSXmKPExsVy+t/xy7oeP26HGtxZpWEx/8g5VovXLwwt 7n89ymix6fE1VouPPfdYLS7vmsNmcfGUq0Xr3iPsFtevbmRx4PR4f6OV3WPTqk42jzvX9rB5 bF5S79G3ZRWjx+dNcgFsUVw2Kak5mWWpRfp2CVwZuyfcZS+4FV1xcPUqtgbGcx5djJwcEgIm El/3T2WFsMUkLtxbz9bFyMUhJLCUUeLzz2VMEM4XRon38zpYQKp4BbQknq47wwZiswioSjSf /sYMYrMJGEtsXr4ELC4qECGxfPVJRoh6QYkfk++B9YoIREns2neWHWQos8A8RonfXfPAEsIC iRIzTzxghNjWzShx6cAJsEmcAt4Srz/dAdrAAdShJ3H/ohZImFlAXmLzmrfMExgFZiHZMQuh ahaSqgWMzKsYRVNLkwuKk9JzjfSKE3OLS/PS9ZLzczcxQsL/6w7GpcesDjEKcDAq8fDOmH87 VIg1say4MvcQowQHs5II7/ONQCHelMTKqtSi/Pii0pzU4kOM0hwsSuK8M3e9DxESSE8sSc1O TS1ILYLJMnFwSjUwKld4MX6dLtoUlXmhx2T5pDS7qsd7/ZUcN+z2/LPr9JX6CTGLkzP+zlC4 cttzZeMPke2P5t6V23vr6rXdq/jsl1yYri8hcKKBu/yj2kSj6MIPretOSV399JK5zePFi3Cj y2I/X+74aKGy7t81s4JvknM+B0u2dMXlPTijy3X1oda7qerGxcl5J5RYijMSDbWYi4oTARyX RiR7AgAA Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 11586 Lines: 402 On 25.08.2015 03:56, Vaibhav Hiremath wrote: > 88PM800 family of devices supports buffered 32KHz clock output, > for example, > > 88PM800: CLK32k_1, CLK32k_2 and CLK32k_3 > 88PM860: CLK32K_1 and CLK32K_2 > > This patch adds new clk provider driver to support enable/disable > of the 32KHz clock output from 88PM800 family of devices. > > Signed-off-by: Vaibhav Hiremath > --- > drivers/clk/Kconfig | 8 ++ > drivers/clk/Makefile | 1 + > drivers/clk/clk-88pm800.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 350 insertions(+) > create mode 100644 drivers/clk/clk-88pm800.c > > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig > index 42f7120..c34c14d 100644 > --- a/drivers/clk/Kconfig > +++ b/drivers/clk/Kconfig > @@ -167,6 +167,14 @@ config COMMON_CLK_CDCE706 > ---help--- > This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. > > +config COMMON_CLK_88PM800 > + tristate "Clock driver for 88PM800 MFD" > + depends on MFD_88PM800 > + ---help--- > + This driver supports 88PM800 & 88PM860 crystal oscillator > + clock. These multi-function devices have two (88PM860) or three > + (88PM800) fixed-rate output, clocked at 32KHz each. > + > source "drivers/clk/bcm/Kconfig" > source "drivers/clk/hisilicon/Kconfig" > source "drivers/clk/qcom/Kconfig" > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile > index c4cf075..5248cd3 100644 > --- a/drivers/clk/Makefile > +++ b/drivers/clk/Makefile > @@ -80,3 +80,4 @@ obj-$(CONFIG_X86) += x86/ > obj-$(CONFIG_ARCH_ZX) += zte/ > obj-$(CONFIG_ARCH_ZYNQ) += zynq/ > obj-$(CONFIG_H8300) += h8300/ > +obj-$(CONFIG_COMMON_CLK_88PM800) += clk-88pm800.o > diff --git a/drivers/clk/clk-88pm800.c b/drivers/clk/clk-88pm800.c > new file mode 100644 > index 0000000..4c045e1 > --- /dev/null > +++ b/drivers/clk/clk-88pm800.c > @@ -0,0 +1,341 @@ > +/* > + * clk-88pm800.c - Clock driver for 88PM800 family of devices > + * > + * This driver is created based on clk-s2mps11.c > + * > + * Copyright (C) 2015 Linaro Ltd. > + * > + * 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; either version 2 of the License, or (at your > + * option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* Number of clocks output from 88pm800 family of device */ > +enum { > + PM800_CLK32K_1 = 0, > + PM800_CLK32K_2, > + PM800_CLK32K_3, > + PM800_CLKS_NUM, > +}; > + > +struct pm800_clk { > + struct pm80x_chip *chip; > + struct device_node *clk_np; > + struct clk_hw hw; > + struct clk *clk; > + struct clk_lookup *lookup; > + u32 mask; > + u32 shift; > + unsigned int reg; > +}; > + > +static struct pm800_clk *to_pm800_clk(struct clk_hw *hw) > +{ > + return container_of(hw, struct pm800_clk, hw); > +} > + > +static int pm800_clk_prepare(struct clk_hw *hw) > +{ > + struct pm800_clk *pm800 = to_pm800_clk(hw); > + > + /* We always want to use XO clock */ > + return regmap_update_bits(pm800->chip->regmap, > + pm800->reg, > + pm800->mask, > + PM800_32K_OUTX_SEL_XO_32KHZ << pm800->shift); > +} > + > +static void pm800_clk_unprepare(struct clk_hw *hw) > +{ > + struct pm800_clk *pm800 = to_pm800_clk(hw); > + > + regmap_update_bits(pm800->chip->regmap, pm800->reg, > + pm800->mask, ~pm800->mask); > +} > + > +static int pm800_clk_is_prepared(struct clk_hw *hw) > +{ > + int ret; > + u32 val; > + struct pm800_clk *pm800 = to_pm800_clk(hw); > + > + ret = regmap_read(pm800->chip->regmap, > + pm800->reg, &val); > + if (ret < 0) > + return -EINVAL; > + > + return (val & pm800->mask) >> pm800->shift; > +} > + > +static unsigned long pm800_clk_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + return 32768; > +} > + > +static const struct clk_ops pm800_clk_ops = { > + .prepare = pm800_clk_prepare, > + .unprepare = pm800_clk_unprepare, > + .is_prepared = pm800_clk_is_prepared, > + .recalc_rate = pm800_clk_recalc_rate, > +}; > + > +static struct clk_init_data pm800_clks_init[PM800_CLKS_NUM] = { > + [PM800_CLK32K_1] = { > + .name = "pm800_clk32k_1", > + .ops = &pm800_clk_ops, > + .flags = CLK_IS_ROOT, > + }, > + [PM800_CLK32K_2] = { > + .name = "pm800_clk32k_2", > + .ops = &pm800_clk_ops, > + .flags = CLK_IS_ROOT, > + }, > + [PM800_CLK32K_3] = { > + .name = "pm800_clk32k_3", > + .ops = &pm800_clk_ops, > + .flags = CLK_IS_ROOT, > + }, > +}; > + > +static struct clk_init_data pm860_clks_init[PM800_CLKS_NUM] = { > + [PM800_CLK32K_1] = { > + .name = "pm800_clk32k_1", > + .ops = &pm800_clk_ops, > + .flags = CLK_IS_ROOT, > + }, > + [PM800_CLK32K_2] = { > + .name = "pm800_clk32k_2", > + .ops = &pm800_clk_ops, > + .flags = CLK_IS_ROOT, > + }, > +}; > + > +static int pm800_init_clk(struct pm800_clk *pm800_clks) > +{ > + int ret; > + > + /* Enable XO_LJ : Low jitter clock of 32KHz from XO */ > + ret = regmap_update_bits(pm800_clks->chip->regmap, PM800_LOW_POWER2, > + PM800_LOW_POWER2_XO_LJ_EN, PM800_LOW_POWER2_XO_LJ_EN); > + if (ret) > + return ret; > + > + /* Enable USE_XO : Use XO clock for all internal timing reference */ > + ret = regmap_update_bits(pm800_clks->chip->regmap, PM800_RTC_CONTROL, > + PM800_RTC1_USE_XO, PM800_RTC1_USE_XO); > + if (ret) > + return ret; > + > + /* OSC_FREERUN: Enable Osc free running mode by clearing the bit */ > + ret = regmap_update_bits(pm800_clks->chip->regmap, PM800_OSC_CNTRL1, > + PM800_OSC_CNTRL1_OSC_FREERUN_EN, 0); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static struct device_node *pm800_clk_parse_dt(struct platform_device *pdev, > + struct clk_init_data *clks_init) > +{ > + struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); > + struct device_node *clk_np; > + int i; > + > + if (!chip->dev->of_node) > + return ERR_PTR(-EINVAL); > + > + clk_np = of_get_child_by_name(chip->dev->of_node, "clocks"); > + if (!clk_np) { > + dev_err(&pdev->dev, "could not find clock sub-node\n"); > + return ERR_PTR(-EINVAL); > + } > + > + for (i = 0; i < PM800_CLKS_NUM; i++) { > + if (!clks_init[i].name) > + continue; /* Skip clocks not present in some devices */ > + > + of_property_read_string_index(clk_np, "clock-output-names", i, > + &clks_init[i].name); > + } > + > + return clk_np; > +} > + > +static int pm800_clk_probe(struct platform_device *pdev) > +{ > + struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); > + struct pm800_clk *pm800_clks; > + struct clk_init_data *clks_init; > + static struct clk **clk_table; > + static struct clk_onecell_data *of_clk_data; > + int i, ret; > + > + pm800_clks = devm_kcalloc(&pdev->dev, PM800_CLKS_NUM, > + sizeof(*pm800_clks), GFP_KERNEL); > + if (!pm800_clks) > + return -ENOMEM; > + > + clk_table = devm_kcalloc(&pdev->dev, PM800_CLKS_NUM, > + sizeof(struct clk *), GFP_KERNEL); > + if (!clk_table) > + return -ENOMEM; > + > + switch (platform_get_device_id(pdev)->driver_data) { > + case CHIP_PM800: > + clks_init = pm800_clks_init; > + break; > + case CHIP_PM860: > + clks_init = pm860_clks_init; > + break; > + default: > + dev_err(&pdev->dev, "Invalid device type\n"); > + return -EINVAL; > + } > + > + /* Store clocks of_node in first element of pm800_clks array */ > + pm800_clks->clk_np = pm800_clk_parse_dt(pdev, clks_init); > + if (IS_ERR(pm800_clks->clk_np)) > + return PTR_ERR(pm800_clks->clk_np); > + > + of_clk_data = devm_kzalloc(&pdev->dev, sizeof(*of_clk_data), GFP_KERNEL); > + if (!of_clk_data) { > + ret = -ENOMEM; > + goto err_clk_np; > + } > + > + for (i = 0; i < PM800_CLKS_NUM; i++) { > + if (!clks_init[i].name) > + continue; /* Skip clocks not present in some devices */ > + > + pm800_clks[i].chip = chip; > + pm800_clks[i].hw.init = &clks_init[i]; > + /* > + * As of now, mask and shift formula below works for both > + * 88PM800 and it's family of devices, > + * > + * PM800_RTC_MISC2: > + * 1:0 = CK_32K_OUT1_SEL > + * 3:2 = CK_32K_OUT2_SEL > + * 5:4 = CK_32K_OUT3_SEL > + */ > + pm800_clks[i].shift = i * 2; > + pm800_clks[i].mask = PM800_32K_OUTX_SEL_MASK << pm800_clks[i].shift; > + pm800_clks[i].reg = PM800_RTC_MISC2; > + > + pm800_clks[i].clk = devm_clk_register(&pdev->dev, > + &pm800_clks[i].hw); > + if (IS_ERR(pm800_clks[i].clk)) { > + dev_err(&pdev->dev, "Fail to register : %s\n", > + clks_init[i].name); > + ret = PTR_ERR(pm800_clks[i].clk); > + goto err; > + } > + > + pm800_clks[i].lookup = clkdev_create(pm800_clks[i].clk, > + clks_init[i].name, NULL); > + if (!pm800_clks[i].lookup) { > + ret = -ENOMEM; > + goto err; > + } > + > + clk_table[i] = pm800_clks[i].clk; > + } > + > + of_clk_data->clks = clk_table; > + of_clk_data->clk_num = PM800_CLKS_NUM; > + ret = of_clk_add_provider(pm800_clks->clk_np, of_clk_src_onecell_get, > + of_clk_data); > + if (ret) { > + dev_err(&pdev->dev, "Fail to add OF clk provider : %d\n", ret); > + goto err; > + } > + > + /* Common for all 32KHz clock output */ > + ret = pm800_init_clk(&pm800_clks[0]); > + if (ret) { > + dev_err(&pdev->dev, "Failed to initialize clk : %d\n", ret); > + goto err; You're missing here of_clk_del_provider(). > + } > + > + platform_set_drvdata(pdev, pm800_clks); > + > + return 0; > + > +err: > + for (i = 0; i < PM800_CLKS_NUM; i++) { > + if (pm800_clks[i].lookup) > + clkdev_drop(pm800_clks[i].lookup); > + } > +err_clk_np: > + /* Drop the reference obtained in pm800_clk_parse_dt */ > + of_node_put(pm800_clks[0].clk_np); > + > + return ret; > +} > + > +static int pm800_clk_remove(struct platform_device *pdev) > +{ > + struct pm800_clk *pm800_clks = platform_get_drvdata(pdev); > + int i; > + > + of_clk_del_provider(pm800_clks[0].clk_np); > + /* Drop the reference obtained in pm800_clk_parse_dt */ > + of_node_put(pm800_clks[0].clk_np); > + > + for (i = 0; i < PM800_CLKS_NUM; i++) { > + if (pm800_clks[i].lookup) > + clkdev_drop(pm800_clks[i].lookup); > + } > + > + return 0; > +} > + > +static const struct platform_device_id pm800_clk_id[] = { > + { "88pm800-clk", CHIP_PM800}, > + { "88pm860-clk", CHIP_PM860}, > + { }, > +}; > +MODULE_DEVICE_TABLE(platform, pm800_clk_id); > + > +static struct platform_driver pm800_clk_driver = { > + .driver = { > + .name = "88pm80x-clk", > + }, > + .probe = pm800_clk_probe, > + .remove = pm800_clk_remove, > + .id_table = pm800_clk_id, > +}; > + > +static int __init pm800_clk_init(void) > +{ > + return platform_driver_register(&pm800_clk_driver); > +} > +subsys_initcall(pm800_clk_init); Does it have to be subsys_initcall instead of regular module_platform_driver? Strict ordering of modules should be avoided. Do you require it? Rest looks fine (actually Stephen pointed some issue during previous review so I had easier job). Best regards, Krzysztof -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/