Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * DRA7 ATL (Audio Tracking Logic) clock driver
0004  *
0005  * Copyright (C) 2013 Texas Instruments, Inc.
0006  *
0007  * Peter Ujfalusi <peter.ujfalusi@ti.com>
0008  */
0009 
0010 #include <linux/init.h>
0011 #include <linux/clk.h>
0012 #include <linux/clk-provider.h>
0013 #include <linux/slab.h>
0014 #include <linux/io.h>
0015 #include <linux/of.h>
0016 #include <linux/of_address.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/pm_runtime.h>
0019 #include <linux/clk/ti.h>
0020 
0021 #include "clock.h"
0022 
0023 #define DRA7_ATL_INSTANCES  4
0024 
0025 #define DRA7_ATL_PPMR_REG(id)       (0x200 + (id * 0x80))
0026 #define DRA7_ATL_BBSR_REG(id)       (0x204 + (id * 0x80))
0027 #define DRA7_ATL_ATLCR_REG(id)      (0x208 + (id * 0x80))
0028 #define DRA7_ATL_SWEN_REG(id)       (0x210 + (id * 0x80))
0029 #define DRA7_ATL_BWSMUX_REG(id)     (0x214 + (id * 0x80))
0030 #define DRA7_ATL_AWSMUX_REG(id)     (0x218 + (id * 0x80))
0031 #define DRA7_ATL_PCLKMUX_REG(id)    (0x21c + (id * 0x80))
0032 
0033 #define DRA7_ATL_SWEN           BIT(0)
0034 #define DRA7_ATL_DIVIDER_MASK       (0x1f)
0035 #define DRA7_ATL_PCLKMUX        BIT(0)
0036 struct dra7_atl_clock_info;
0037 
0038 struct dra7_atl_desc {
0039     struct clk *clk;
0040     struct clk_hw hw;
0041     struct dra7_atl_clock_info *cinfo;
0042     int id;
0043 
0044     bool probed;        /* the driver for the IP has been loaded */
0045     bool valid;     /* configured */
0046     bool enabled;
0047     u32 bws;        /* Baseband Word Select Mux */
0048     u32 aws;        /* Audio Word Select Mux */
0049     u32 divider;        /* Cached divider value */
0050 };
0051 
0052 struct dra7_atl_clock_info {
0053     struct device *dev;
0054     void __iomem *iobase;
0055 
0056     struct dra7_atl_desc *cdesc;
0057 };
0058 
0059 #define to_atl_desc(_hw)    container_of(_hw, struct dra7_atl_desc, hw)
0060 
0061 static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg,
0062                  u32 val)
0063 {
0064     __raw_writel(val, cinfo->iobase + reg);
0065 }
0066 
0067 static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg)
0068 {
0069     return __raw_readl(cinfo->iobase + reg);
0070 }
0071 
0072 static int atl_clk_enable(struct clk_hw *hw)
0073 {
0074     struct dra7_atl_desc *cdesc = to_atl_desc(hw);
0075 
0076     if (!cdesc->probed)
0077         goto out;
0078 
0079     if (unlikely(!cdesc->valid))
0080         dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n",
0081              cdesc->id);
0082     pm_runtime_get_sync(cdesc->cinfo->dev);
0083 
0084     atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id),
0085           cdesc->divider - 1);
0086     atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN);
0087 
0088 out:
0089     cdesc->enabled = true;
0090 
0091     return 0;
0092 }
0093 
0094 static void atl_clk_disable(struct clk_hw *hw)
0095 {
0096     struct dra7_atl_desc *cdesc = to_atl_desc(hw);
0097 
0098     if (!cdesc->probed)
0099         goto out;
0100 
0101     atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0);
0102     pm_runtime_put_sync(cdesc->cinfo->dev);
0103 
0104 out:
0105     cdesc->enabled = false;
0106 }
0107 
0108 static int atl_clk_is_enabled(struct clk_hw *hw)
0109 {
0110     struct dra7_atl_desc *cdesc = to_atl_desc(hw);
0111 
0112     return cdesc->enabled;
0113 }
0114 
0115 static unsigned long atl_clk_recalc_rate(struct clk_hw *hw,
0116                      unsigned long parent_rate)
0117 {
0118     struct dra7_atl_desc *cdesc = to_atl_desc(hw);
0119 
0120     return parent_rate / cdesc->divider;
0121 }
0122 
0123 static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate,
0124                    unsigned long *parent_rate)
0125 {
0126     unsigned divider;
0127 
0128     divider = (*parent_rate + rate / 2) / rate;
0129     if (divider > DRA7_ATL_DIVIDER_MASK + 1)
0130         divider = DRA7_ATL_DIVIDER_MASK + 1;
0131 
0132     return *parent_rate / divider;
0133 }
0134 
0135 static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
0136                 unsigned long parent_rate)
0137 {
0138     struct dra7_atl_desc *cdesc;
0139     u32 divider;
0140 
0141     if (!hw || !rate)
0142         return -EINVAL;
0143 
0144     cdesc = to_atl_desc(hw);
0145     divider = ((parent_rate + rate / 2) / rate) - 1;
0146     if (divider > DRA7_ATL_DIVIDER_MASK)
0147         divider = DRA7_ATL_DIVIDER_MASK;
0148 
0149     cdesc->divider = divider + 1;
0150 
0151     return 0;
0152 }
0153 
0154 static const struct clk_ops atl_clk_ops = {
0155     .enable     = atl_clk_enable,
0156     .disable    = atl_clk_disable,
0157     .is_enabled = atl_clk_is_enabled,
0158     .recalc_rate    = atl_clk_recalc_rate,
0159     .round_rate = atl_clk_round_rate,
0160     .set_rate   = atl_clk_set_rate,
0161 };
0162 
0163 static void __init of_dra7_atl_clock_setup(struct device_node *node)
0164 {
0165     struct dra7_atl_desc *clk_hw = NULL;
0166     struct clk_init_data init = { NULL };
0167     const char **parent_names = NULL;
0168     const char *name;
0169     struct clk *clk;
0170 
0171     clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
0172     if (!clk_hw) {
0173         pr_err("%s: could not allocate dra7_atl_desc\n", __func__);
0174         return;
0175     }
0176 
0177     clk_hw->hw.init = &init;
0178     clk_hw->divider = 1;
0179     name = ti_dt_clk_name(node);
0180     init.name = name;
0181     init.ops = &atl_clk_ops;
0182     init.flags = CLK_IGNORE_UNUSED;
0183     init.num_parents = of_clk_get_parent_count(node);
0184 
0185     if (init.num_parents != 1) {
0186         pr_err("%s: atl clock %pOFn must have 1 parent\n", __func__,
0187                node);
0188         goto cleanup;
0189     }
0190 
0191     parent_names = kzalloc(sizeof(char *), GFP_KERNEL);
0192 
0193     if (!parent_names)
0194         goto cleanup;
0195 
0196     parent_names[0] = of_clk_get_parent_name(node, 0);
0197 
0198     init.parent_names = parent_names;
0199 
0200     clk = ti_clk_register(NULL, &clk_hw->hw, name);
0201 
0202     if (!IS_ERR(clk)) {
0203         of_clk_add_provider(node, of_clk_src_simple_get, clk);
0204         kfree(parent_names);
0205         return;
0206     }
0207 cleanup:
0208     kfree(parent_names);
0209     kfree(clk_hw);
0210 }
0211 CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup);
0212 
0213 static int of_dra7_atl_clk_probe(struct platform_device *pdev)
0214 {
0215     struct device_node *node = pdev->dev.of_node;
0216     struct dra7_atl_clock_info *cinfo;
0217     int i;
0218     int ret = 0;
0219 
0220     if (!node)
0221         return -ENODEV;
0222 
0223     cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
0224     if (!cinfo)
0225         return -ENOMEM;
0226 
0227     cinfo->iobase = of_iomap(node, 0);
0228     cinfo->dev = &pdev->dev;
0229     pm_runtime_enable(cinfo->dev);
0230 
0231     pm_runtime_get_sync(cinfo->dev);
0232     atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX);
0233 
0234     for (i = 0; i < DRA7_ATL_INSTANCES; i++) {
0235         struct device_node *cfg_node;
0236         char prop[5];
0237         struct dra7_atl_desc *cdesc;
0238         struct of_phandle_args clkspec;
0239         struct clk *clk;
0240         int rc;
0241 
0242         rc = of_parse_phandle_with_args(node, "ti,provided-clocks",
0243                         NULL, i, &clkspec);
0244 
0245         if (rc) {
0246             pr_err("%s: failed to lookup atl clock %d\n", __func__,
0247                    i);
0248             return -EINVAL;
0249         }
0250 
0251         clk = of_clk_get_from_provider(&clkspec);
0252         if (IS_ERR(clk)) {
0253             pr_err("%s: failed to get atl clock %d from provider\n",
0254                    __func__, i);
0255             return PTR_ERR(clk);
0256         }
0257 
0258         cdesc = to_atl_desc(__clk_get_hw(clk));
0259         cdesc->cinfo = cinfo;
0260         cdesc->id = i;
0261 
0262         /* Get configuration for the ATL instances */
0263         snprintf(prop, sizeof(prop), "atl%u", i);
0264         cfg_node = of_get_child_by_name(node, prop);
0265         if (cfg_node) {
0266             ret = of_property_read_u32(cfg_node, "bws",
0267                            &cdesc->bws);
0268             ret |= of_property_read_u32(cfg_node, "aws",
0269                             &cdesc->aws);
0270             if (!ret) {
0271                 cdesc->valid = true;
0272                 atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i),
0273                       cdesc->bws);
0274                 atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i),
0275                       cdesc->aws);
0276             }
0277             of_node_put(cfg_node);
0278         }
0279 
0280         cdesc->probed = true;
0281         /*
0282          * Enable the clock if it has been asked prior to loading the
0283          * hw driver
0284          */
0285         if (cdesc->enabled)
0286             atl_clk_enable(__clk_get_hw(clk));
0287     }
0288     pm_runtime_put_sync(cinfo->dev);
0289 
0290     return ret;
0291 }
0292 
0293 static const struct of_device_id of_dra7_atl_clk_match_tbl[] = {
0294     { .compatible = "ti,dra7-atl", },
0295     {},
0296 };
0297 
0298 static struct platform_driver dra7_atl_clk_driver = {
0299     .driver = {
0300         .name = "dra7-atl",
0301         .suppress_bind_attrs = true,
0302         .of_match_table = of_dra7_atl_clk_match_tbl,
0303     },
0304     .probe = of_dra7_atl_clk_probe,
0305 };
0306 builtin_platform_driver(dra7_atl_clk_driver);