From patchwork Mon Aug 24 18:56:33 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vaibhav Hiremath X-Patchwork-Id: 52648 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-la0-f70.google.com (mail-la0-f70.google.com [209.85.215.70]) by patches.linaro.org (Postfix) with ESMTPS id B994122DD4 for ; Mon, 24 Aug 2015 18:59:34 +0000 (UTC) Received: by labia3 with SMTP id ia3sf19103497lab.3 for ; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:delivered-to:from:to:cc:subject :date:message-id:in-reply-to:references:sender:precedence:list-id :x-original-sender:x-original-authentication-results:mailing-list :list-post:list-help:list-archive:list-unsubscribe; bh=5mCfLuCw8i7OKfR0yBupABVdbPvwYZ5Et3/hBUrraO0=; b=OVBh4Og6svGshZyZ3G3HphaiekzauWCaX9RF6MeXtPHQp2Ui9m0aHau6MpNEpzXmxA oIfMKz4EjgKZ0X5qo3ec+bdpKBSmmAUdsnSNYGFX7KVmViIS+TkXOakVFcUOfTjAq6n+ PUj9YnGNB0P3itztStnlixlmFkkqSOg7cqi2fGL9zOdOqu7kTZKdgfantcwISqR5NI0k Hdnz8tjhrvlNXFD+ofOS737sgyX2VOnafH5coRDupzV9B3UAZEF8jHJvz8ifObaBns6H uQ4JVGU/oxEOLTSjhW7hyWTXzioXRpgjWEnlCKbjXt3tTx5axaBm+qXtzUrFwhS0vZUp v6Ig== X-Gm-Message-State: ALoCoQlfTWvtZvwDR/07qUoUHUTZXWBh5y8Inb9C69j42ZRrXbuBvhVIcm3jbys/Fmz7j3GQG5gt X-Received: by 10.152.4.69 with SMTP id i5mr7220815lai.10.1440442773738; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.152.3.200 with SMTP id e8ls604296lae.4.gmail; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) X-Received: by 10.112.219.165 with SMTP id pp5mr21749941lbc.23.1440442773582; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) Received: from mail-lb0-f176.google.com (mail-lb0-f176.google.com. [209.85.217.176]) by mx.google.com with ESMTPS id li5si13987404lab.84.2015.08.24.11.59.33 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 24 Aug 2015 11:59:33 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.217.176 as permitted sender) client-ip=209.85.217.176; Received: by lbcbn3 with SMTP id bn3so85656886lbc.2 for ; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) X-Received: by 10.112.199.133 with SMTP id jk5mr22342282lbc.32.1440442773469; Mon, 24 Aug 2015 11:59:33 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.112.162.200 with SMTP id yc8csp2669367lbb; Mon, 24 Aug 2015 11:59:32 -0700 (PDT) X-Received: by 10.68.254.200 with SMTP id ak8mr48050628pbd.53.1440442767813; Mon, 24 Aug 2015 11:59:27 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id lz4si28918357pab.96.2015.08.24.11.59.26; Mon, 24 Aug 2015 11:59:27 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755368AbbHXS7N (ORCPT + 8 others); Mon, 24 Aug 2015 14:59:13 -0400 Received: from mail-pa0-f52.google.com ([209.85.220.52]:33475 "EHLO mail-pa0-f52.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755366AbbHXS7L (ORCPT ); Mon, 24 Aug 2015 14:59:11 -0400 Received: by pacti10 with SMTP id ti10so30124185pac.0 for ; Mon, 24 Aug 2015 11:59:11 -0700 (PDT) X-Received: by 10.66.121.74 with SMTP id li10mr47552231pab.73.1440442751224; Mon, 24 Aug 2015 11:59:11 -0700 (PDT) Received: from localhost.localdomain ([202.62.77.106]) by smtp.gmail.com with ESMTPSA id hh3sm18260418pbc.8.2015.08.24.11.59.07 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 24 Aug 2015 11:59:10 -0700 (PDT) From: Vaibhav Hiremath To: linux-arm-kernel@lists.infradead.org Cc: robh+dt@kernel.org, mturquette@baylibre.com, lee.jones@linaro.org, k.kozlowski@samsung.com, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org, Vaibhav Hiremath Subject: [PATCH-v2 4/5] clk: 88pm800: Add clk provider driver for 88pm800 family of devices Date: Tue, 25 Aug 2015 00:26:33 +0530 Message-Id: <1440442594-3102-6-git-send-email-vaibhav.hiremath@linaro.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1440442594-3102-1-git-send-email-vaibhav.hiremath@linaro.org> References: <1440442594-3102-1-git-send-email-vaibhav.hiremath@linaro.org> Sender: devicetree-owner@vger.kernel.org Precedence: list List-ID: X-Mailing-List: devicetree@vger.kernel.org X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: vaibhav.hiremath@linaro.org X-Original-Authentication-Results: mx.google.com; spf=pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.217.176 as permitted sender) smtp.mailfrom=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , 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; + } + + 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); + +static void __exit pm800_clk_cleanup(void) +{ + platform_driver_unregister(&pm800_clk_driver); +} +module_exit(pm800_clk_cleanup); + +MODULE_DESCRIPTION("88PM800 Clock Driver"); +MODULE_AUTHOR("Vaibhav Hiremath "); +MODULE_LICENSE("GPL");