Quoting Peter Ujfalusi (2014-05-07 03:20:47) > Audio Tracking Logic is designed to be used by HD Radio applications to > synchronize the audio output clocks to the baseband clock. ATL can be also > used to track errors between two reference clocks (BWS, AWS) and generate a modulated > clock output which averages to some desired frequency. > In essence ATL is generating a clock to be used by an audio codec and also > to be used by the SoC as MCLK. > > To be able to integrate the ATL provided clocks to the clock tree we need > two types of DT binding: > - DT clock nodes to represent the ATL clocks towards the CCF > - binding for the ATL IP itself which is going to handle the hw > configuration > > The reason for this type of setup is that ATL itself is a separate device > in the SoC, it has it's own address space and clock domain. Other IPs can > use the ATL generated clock as their functional clock (McASPs for example) > and external components like audio codecs can also use the very same clock > as their MCLK. > > The ATL IP in DRA7 contains 4 ATL instences. > > Signed-off-by: Peter Ujfalusi <peter.ujfalusi@xxxxxx> Looks good to me. Regards, Mike > --- > drivers/clk/ti/Makefile | 3 +- > drivers/clk/ti/clk-dra7-atl.c | 313 ++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 315 insertions(+), 1 deletion(-) > create mode 100644 drivers/clk/ti/clk-dra7-atl.c > > diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile > index 4319d4031aa3..18e2443224e6 100644 > --- a/drivers/clk/ti/Makefile > +++ b/drivers/clk/ti/Makefile > @@ -6,6 +6,7 @@ obj-$(CONFIG_SOC_AM33XX) += $(clk-common) clk-33xx.o > obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o clk-3xxx.o > obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o > obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o > -obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o > +obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o \ > + clk-dra7-atl.o > obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o > endif > diff --git a/drivers/clk/ti/clk-dra7-atl.c b/drivers/clk/ti/clk-dra7-atl.c > new file mode 100644 > index 000000000000..97a8992eebb7 > --- /dev/null > +++ b/drivers/clk/ti/clk-dra7-atl.c > @@ -0,0 +1,313 @@ > +/* > + * DRA7 ATL (Audio Tracking Logic) clock driver > + * > + * Copyright (C) 2013 Texas Instruments, Inc. > + * > + * Peter Ujfalusi <peter.ujfalusi@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * 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. > + */ > + > +#include <linux/module.h> > +#include <linux/clk-provider.h> > +#include <linux/slab.h> > +#include <linux/io.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > + > +#define DRA7_ATL_INSTANCES 4 > + > +#define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) > +#define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) > +#define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) > +#define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) > +#define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) > +#define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) > +#define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) > + > +#define DRA7_ATL_SWEN BIT(0) > +#define DRA7_ATL_DIVIDER_MASK (0x1f) > +#define DRA7_ATL_PCLKMUX BIT(0) > +struct dra7_atl_clock_info; > + > +struct dra7_atl_desc { > + struct clk *clk; > + struct clk_hw hw; > + struct dra7_atl_clock_info *cinfo; > + int id; > + > + bool probed; /* the driver for the IP has been loaded */ > + bool valid; /* configured */ > + bool enabled; > + u32 bws; /* Baseband Word Select Mux */ > + u32 aws; /* Audio Word Select Mux */ > + u32 divider; /* Cached divider value */ > +}; > + > +struct dra7_atl_clock_info { > + struct device *dev; > + void __iomem *iobase; > + > + struct dra7_atl_desc *cdesc; > +}; > + > +#define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) > + > +static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, > + u32 val) > +{ > + __raw_writel(val, cinfo->iobase + reg); > +} > + > +static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) > +{ > + return __raw_readl(cinfo->iobase + reg); > +} > + > +static int atl_clk_enable(struct clk_hw *hw) > +{ > + struct dra7_atl_desc *cdesc = to_atl_desc(hw); > + > + if (!cdesc->probed) > + goto out; > + > + if (unlikely(!cdesc->valid)) > + dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n", > + cdesc->id); > + pm_runtime_get_sync(cdesc->cinfo->dev); > + > + atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), > + cdesc->divider - 1); > + atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); > + > +out: > + cdesc->enabled = true; > + > + return 0; > +} > + > +static void atl_clk_disable(struct clk_hw *hw) > +{ > + struct dra7_atl_desc *cdesc = to_atl_desc(hw); > + > + if (!cdesc->probed) > + goto out; > + > + atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0); > + pm_runtime_put_sync(cdesc->cinfo->dev); > + > +out: > + cdesc->enabled = false; > +} > + > +static int atl_clk_is_enabled(struct clk_hw *hw) > +{ > + struct dra7_atl_desc *cdesc = to_atl_desc(hw); > + > + return cdesc->enabled; > +} > + > +static unsigned long atl_clk_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct dra7_atl_desc *cdesc = to_atl_desc(hw); > + > + return parent_rate / cdesc->divider; > +} > + > +static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long *parent_rate) > +{ > + unsigned divider; > + > + divider = (*parent_rate + rate / 2) / rate; > + if (divider > DRA7_ATL_DIVIDER_MASK + 1) > + divider = DRA7_ATL_DIVIDER_MASK + 1; > + > + return *parent_rate / divider; > +} > + > +static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate) > +{ > + struct dra7_atl_desc *cdesc = to_atl_desc(hw); > + u32 divider; > + > + divider = ((parent_rate + rate / 2) / rate) - 1; > + if (divider > DRA7_ATL_DIVIDER_MASK) > + divider = DRA7_ATL_DIVIDER_MASK; > + > + cdesc->divider = divider + 1; > + > + return 0; > +} > + > +const struct clk_ops atl_clk_ops = { > + .enable = atl_clk_enable, > + .disable = atl_clk_disable, > + .is_enabled = atl_clk_is_enabled, > + .recalc_rate = atl_clk_recalc_rate, > + .round_rate = atl_clk_round_rate, > + .set_rate = atl_clk_set_rate, > +}; > + > +static void __init of_dra7_atl_clock_setup(struct device_node *node) > +{ > + struct dra7_atl_desc *clk_hw = NULL; > + struct clk_init_data init = { 0 }; > + const char **parent_names = NULL; > + struct clk *clk; > + > + clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); > + if (!clk_hw) { > + pr_err("%s: could not allocate dra7_atl_desc\n", __func__); > + return; > + } > + > + clk_hw->hw.init = &init; > + clk_hw->divider = 1; > + init.name = node->name; > + init.ops = &atl_clk_ops; > + init.flags = CLK_IGNORE_UNUSED; > + init.num_parents = of_clk_get_parent_count(node); > + > + if (init.num_parents != 1) { > + pr_err("%s: atl clock %s must have 1 parent\n", __func__, > + node->name); > + goto cleanup; > + } > + > + parent_names = kzalloc(sizeof(char *), GFP_KERNEL); > + > + if (!parent_names) > + goto cleanup; > + > + parent_names[0] = of_clk_get_parent_name(node, 0); > + > + init.parent_names = parent_names; > + > + clk = clk_register(NULL, &clk_hw->hw); > + > + if (!IS_ERR(clk)) { > + of_clk_add_provider(node, of_clk_src_simple_get, clk); > + return; > + } > +cleanup: > + kfree(parent_names); > + kfree(clk_hw); > +} > +CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup); > + > +static int of_dra7_atl_clk_probe(struct platform_device *pdev) > +{ > + struct device_node *node = pdev->dev.of_node; > + struct dra7_atl_clock_info *cinfo; > + int i; > + int ret = 0; > + > + if (!node) > + return -ENODEV; > + > + cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL); > + if (!cinfo) > + return -ENOMEM; > + > + cinfo->iobase = of_iomap(node, 0); > + cinfo->dev = &pdev->dev; > + pm_runtime_enable(cinfo->dev); > + > + pm_runtime_get_sync(cinfo->dev); > + atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); > + > + for (i = 0; i < DRA7_ATL_INSTANCES; i++) { > + struct device_node *cfg_node; > + char prop[5]; > + struct dra7_atl_desc *cdesc; > + struct of_phandle_args clkspec; > + struct clk *clk; > + int rc; > + > + rc = of_parse_phandle_with_args(node, "ti,provided-clocks", > + NULL, i, &clkspec); > + > + if (rc) { > + pr_err("%s: failed to lookup atl clock %d\n", __func__, > + i); > + return -EINVAL; > + } > + > + clk = of_clk_get_from_provider(&clkspec); > + > + cdesc = to_atl_desc(__clk_get_hw(clk)); > + cdesc->cinfo = cinfo; > + cdesc->id = i; > + > + /* Get configuration for the ATL instances */ > + snprintf(prop, sizeof(prop), "atl%u", i); > + cfg_node = of_find_node_by_name(node, prop); > + if (cfg_node) { > + ret = of_property_read_u32(cfg_node, "bws", > + &cdesc->bws); > + ret |= of_property_read_u32(cfg_node, "aws", > + &cdesc->aws); > + if (!ret) { > + cdesc->valid = true; > + atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), > + cdesc->bws); > + atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), > + cdesc->aws); > + } > + } > + > + cdesc->probed = true; > + /* > + * Enable the clock if it has been asked prior to loading the > + * hw driver > + */ > + if (cdesc->enabled) > + atl_clk_enable(__clk_get_hw(clk)); > + } > + pm_runtime_put_sync(cinfo->dev); > + > + return ret; > +} > + > +static int of_dra7_atl_clk_remove(struct platform_device *pdev) > +{ > + pm_runtime_disable(&pdev->dev); > + > + return 0; > +} > + > +static struct of_device_id of_dra7_atl_clk_match_tbl[] = { > + { .compatible = "ti,dra7-atl", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl); > + > +static struct platform_driver dra7_atl_clk_driver = { > + .driver = { > + .name = "dra7-atl", > + .owner = THIS_MODULE, > + .of_match_table = of_dra7_atl_clk_match_tbl, > + }, > + .probe = of_dra7_atl_clk_probe, > + .remove = of_dra7_atl_clk_remove, > +}; > + > +module_platform_driver(dra7_atl_clk_driver); > + > +MODULE_DESCRIPTION("Clock driver for DRA7 Audio Tracking Logic"); > +MODULE_ALIAS("platform:dra7-atl-clock"); > +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@xxxxxx>"); > +MODULE_LICENSE("GPL v2"); > + > -- > 1.9.2 > -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html