diff options
| -rw-r--r-- | drivers/clk/ti/Makefile | 3 | ||||
| -rw-r--r-- | drivers/clk/ti/clk-dra7-atl.c | 312 |
2 files changed, 314 insertions, 1 deletions
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index 4afeaed9e9ba..ed4d0aaf8916 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile | |||
| @@ -7,6 +7,7 @@ obj-$(CONFIG_ARCH_OMAP2) += $(clk-common) interface.o clk-2xxx.o | |||
| 7 | obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o clk-3xxx.o | 7 | obj-$(CONFIG_ARCH_OMAP3) += $(clk-common) interface.o clk-3xxx.o |
| 8 | obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o | 8 | obj-$(CONFIG_ARCH_OMAP4) += $(clk-common) clk-44xx.o |
| 9 | obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o | 9 | obj-$(CONFIG_SOC_OMAP5) += $(clk-common) clk-54xx.o |
| 10 | obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o | 10 | obj-$(CONFIG_SOC_DRA7XX) += $(clk-common) clk-7xx.o \ |
| 11 | clk-dra7-atl.o | ||
| 11 | obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o | 12 | obj-$(CONFIG_SOC_AM43XX) += $(clk-common) clk-43xx.o |
| 12 | endif | 13 | 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..4a65b410e4d5 --- /dev/null +++ b/drivers/clk/ti/clk-dra7-atl.c | |||
| @@ -0,0 +1,312 @@ | |||
| 1 | /* | ||
| 2 | * DRA7 ATL (Audio Tracking Logic) clock driver | ||
| 3 | * | ||
| 4 | * Copyright (C) 2013 Texas Instruments, Inc. | ||
| 5 | * | ||
| 6 | * Peter Ujfalusi <peter.ujfalusi@ti.com> | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or modify | ||
| 9 | * it under the terms of the GNU General Public License version 2 as | ||
| 10 | * published by the Free Software Foundation. | ||
| 11 | * | ||
| 12 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
| 13 | * kind, whether express or implied; without even the implied warranty | ||
| 14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | * GNU General Public License for more details. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <linux/module.h> | ||
| 19 | #include <linux/clk-provider.h> | ||
| 20 | #include <linux/slab.h> | ||
| 21 | #include <linux/io.h> | ||
| 22 | #include <linux/of.h> | ||
| 23 | #include <linux/of_address.h> | ||
| 24 | #include <linux/platform_device.h> | ||
| 25 | #include <linux/pm_runtime.h> | ||
| 26 | |||
| 27 | #define DRA7_ATL_INSTANCES 4 | ||
| 28 | |||
| 29 | #define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) | ||
| 30 | #define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) | ||
| 31 | #define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) | ||
| 32 | #define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) | ||
| 33 | #define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) | ||
| 34 | #define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) | ||
| 35 | #define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) | ||
| 36 | |||
| 37 | #define DRA7_ATL_SWEN BIT(0) | ||
| 38 | #define DRA7_ATL_DIVIDER_MASK (0x1f) | ||
| 39 | #define DRA7_ATL_PCLKMUX BIT(0) | ||
| 40 | struct dra7_atl_clock_info; | ||
| 41 | |||
| 42 | struct dra7_atl_desc { | ||
| 43 | struct clk *clk; | ||
| 44 | struct clk_hw hw; | ||
| 45 | struct dra7_atl_clock_info *cinfo; | ||
| 46 | int id; | ||
| 47 | |||
| 48 | bool probed; /* the driver for the IP has been loaded */ | ||
| 49 | bool valid; /* configured */ | ||
| 50 | bool enabled; | ||
| 51 | u32 bws; /* Baseband Word Select Mux */ | ||
| 52 | u32 aws; /* Audio Word Select Mux */ | ||
| 53 | u32 divider; /* Cached divider value */ | ||
| 54 | }; | ||
| 55 | |||
| 56 | struct dra7_atl_clock_info { | ||
| 57 | struct device *dev; | ||
| 58 | void __iomem *iobase; | ||
| 59 | |||
| 60 | struct dra7_atl_desc *cdesc; | ||
| 61 | }; | ||
| 62 | |||
| 63 | #define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) | ||
| 64 | |||
| 65 | static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, | ||
| 66 | u32 val) | ||
| 67 | { | ||
| 68 | __raw_writel(val, cinfo->iobase + reg); | ||
| 69 | } | ||
| 70 | |||
| 71 | static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) | ||
| 72 | { | ||
| 73 | return __raw_readl(cinfo->iobase + reg); | ||
| 74 | } | ||
| 75 | |||
| 76 | static int atl_clk_enable(struct clk_hw *hw) | ||
| 77 | { | ||
| 78 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); | ||
| 79 | |||
| 80 | if (!cdesc->probed) | ||
| 81 | goto out; | ||
| 82 | |||
| 83 | if (unlikely(!cdesc->valid)) | ||
| 84 | dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n", | ||
| 85 | cdesc->id); | ||
| 86 | pm_runtime_get_sync(cdesc->cinfo->dev); | ||
| 87 | |||
| 88 | atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), | ||
| 89 | cdesc->divider - 1); | ||
| 90 | atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); | ||
| 91 | |||
| 92 | out: | ||
| 93 | cdesc->enabled = true; | ||
| 94 | |||
| 95 | return 0; | ||
| 96 | } | ||
| 97 | |||
| 98 | static void atl_clk_disable(struct clk_hw *hw) | ||
| 99 | { | ||
| 100 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); | ||
| 101 | |||
| 102 | if (!cdesc->probed) | ||
| 103 | goto out; | ||
| 104 | |||
| 105 | atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0); | ||
| 106 | pm_runtime_put_sync(cdesc->cinfo->dev); | ||
| 107 | |||
| 108 | out: | ||
| 109 | cdesc->enabled = false; | ||
| 110 | } | ||
| 111 | |||
| 112 | static int atl_clk_is_enabled(struct clk_hw *hw) | ||
| 113 | { | ||
| 114 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); | ||
| 115 | |||
| 116 | return cdesc->enabled; | ||
| 117 | } | ||
| 118 | |||
| 119 | static unsigned long atl_clk_recalc_rate(struct clk_hw *hw, | ||
| 120 | unsigned long parent_rate) | ||
| 121 | { | ||
| 122 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); | ||
| 123 | |||
| 124 | return parent_rate / cdesc->divider; | ||
| 125 | } | ||
| 126 | |||
| 127 | static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, | ||
| 128 | unsigned long *parent_rate) | ||
| 129 | { | ||
| 130 | unsigned divider; | ||
| 131 | |||
| 132 | divider = (*parent_rate + rate / 2) / rate; | ||
| 133 | if (divider > DRA7_ATL_DIVIDER_MASK + 1) | ||
| 134 | divider = DRA7_ATL_DIVIDER_MASK + 1; | ||
| 135 | |||
| 136 | return *parent_rate / divider; | ||
| 137 | } | ||
| 138 | |||
| 139 | static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, | ||
| 140 | unsigned long parent_rate) | ||
| 141 | { | ||
| 142 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); | ||
| 143 | u32 divider; | ||
| 144 | |||
| 145 | divider = ((parent_rate + rate / 2) / rate) - 1; | ||
| 146 | if (divider > DRA7_ATL_DIVIDER_MASK) | ||
| 147 | divider = DRA7_ATL_DIVIDER_MASK; | ||
| 148 | |||
| 149 | cdesc->divider = divider + 1; | ||
| 150 | |||
| 151 | return 0; | ||
| 152 | } | ||
| 153 | |||
| 154 | const struct clk_ops atl_clk_ops = { | ||
| 155 | .enable = atl_clk_enable, | ||
| 156 | .disable = atl_clk_disable, | ||
| 157 | .is_enabled = atl_clk_is_enabled, | ||
| 158 | .recalc_rate = atl_clk_recalc_rate, | ||
| 159 | .round_rate = atl_clk_round_rate, | ||
| 160 | .set_rate = atl_clk_set_rate, | ||
| 161 | }; | ||
| 162 | |||
| 163 | static void __init of_dra7_atl_clock_setup(struct device_node *node) | ||
| 164 | { | ||
| 165 | struct dra7_atl_desc *clk_hw = NULL; | ||
| 166 | struct clk_init_data init = { 0 }; | ||
| 167 | const char **parent_names = NULL; | ||
| 168 | struct clk *clk; | ||
| 169 | |||
| 170 | clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); | ||
| 171 | if (!clk_hw) { | ||
| 172 | pr_err("%s: could not allocate dra7_atl_desc\n", __func__); | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | |||
| 176 | clk_hw->hw.init = &init; | ||
| 177 | clk_hw->divider = 1; | ||
| 178 | init.name = node->name; | ||
| 179 | init.ops = &atl_clk_ops; | ||
| 180 | init.flags = CLK_IGNORE_UNUSED; | ||
| 181 | init.num_parents = of_clk_get_parent_count(node); | ||
| 182 | |||
| 183 | if (init.num_parents != 1) { | ||
| 184 | pr_err("%s: atl clock %s must have 1 parent\n", __func__, | ||
| 185 | node->name); | ||
| 186 | goto cleanup; | ||
| 187 | } | ||
| 188 | |||
| 189 | parent_names = kzalloc(sizeof(char *), GFP_KERNEL); | ||
| 190 | |||
| 191 | if (!parent_names) | ||
| 192 | goto cleanup; | ||
| 193 | |||
| 194 | parent_names[0] = of_clk_get_parent_name(node, 0); | ||
| 195 | |||
| 196 | init.parent_names = parent_names; | ||
| 197 | |||
| 198 | clk = clk_register(NULL, &clk_hw->hw); | ||
| 199 | |||
| 200 | if (!IS_ERR(clk)) { | ||
| 201 | of_clk_add_provider(node, of_clk_src_simple_get, clk); | ||
| 202 | return; | ||
| 203 | } | ||
| 204 | cleanup: | ||
| 205 | kfree(parent_names); | ||
| 206 | kfree(clk_hw); | ||
| 207 | } | ||
| 208 | CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup); | ||
| 209 | |||
| 210 | static int of_dra7_atl_clk_probe(struct platform_device *pdev) | ||
| 211 | { | ||
| 212 | struct device_node *node = pdev->dev.of_node; | ||
| 213 | struct dra7_atl_clock_info *cinfo; | ||
| 214 | int i; | ||
| 215 | int ret = 0; | ||
| 216 | |||
| 217 | if (!node) | ||
| 218 | return -ENODEV; | ||
| 219 | |||
| 220 | cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL); | ||
| 221 | if (!cinfo) | ||
| 222 | return -ENOMEM; | ||
| 223 | |||
| 224 | cinfo->iobase = of_iomap(node, 0); | ||
| 225 | cinfo->dev = &pdev->dev; | ||
| 226 | pm_runtime_enable(cinfo->dev); | ||
| 227 | |||
| 228 | pm_runtime_get_sync(cinfo->dev); | ||
| 229 | atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); | ||
| 230 | |||
| 231 | for (i = 0; i < DRA7_ATL_INSTANCES; i++) { | ||
| 232 | struct device_node *cfg_node; | ||
| 233 | char prop[5]; | ||
| 234 | struct dra7_atl_desc *cdesc; | ||
| 235 | struct of_phandle_args clkspec; | ||
| 236 | struct clk *clk; | ||
| 237 | int rc; | ||
| 238 | |||
| 239 | rc = of_parse_phandle_with_args(node, "ti,provided-clocks", | ||
| 240 | NULL, i, &clkspec); | ||
| 241 | |||
| 242 | if (rc) { | ||
| 243 | pr_err("%s: failed to lookup atl clock %d\n", __func__, | ||
| 244 | i); | ||
| 245 | return -EINVAL; | ||
| 246 | } | ||
| 247 | |||
| 248 | clk = of_clk_get_from_provider(&clkspec); | ||
| 249 | |||
| 250 | cdesc = to_atl_desc(__clk_get_hw(clk)); | ||
| 251 | cdesc->cinfo = cinfo; | ||
| 252 | cdesc->id = i; | ||
| 253 | |||
| 254 | /* Get configuration for the ATL instances */ | ||
| 255 | snprintf(prop, sizeof(prop), "atl%u", i); | ||
| 256 | cfg_node = of_find_node_by_name(node, prop); | ||
| 257 | if (cfg_node) { | ||
| 258 | ret = of_property_read_u32(cfg_node, "bws", | ||
| 259 | &cdesc->bws); | ||
| 260 | ret |= of_property_read_u32(cfg_node, "aws", | ||
| 261 | &cdesc->aws); | ||
| 262 | if (!ret) { | ||
| 263 | cdesc->valid = true; | ||
| 264 | atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), | ||
| 265 | cdesc->bws); | ||
| 266 | atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), | ||
| 267 | cdesc->aws); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | cdesc->probed = true; | ||
| 272 | /* | ||
| 273 | * Enable the clock if it has been asked prior to loading the | ||
| 274 | * hw driver | ||
| 275 | */ | ||
| 276 | if (cdesc->enabled) | ||
| 277 | atl_clk_enable(__clk_get_hw(clk)); | ||
| 278 | } | ||
| 279 | pm_runtime_put_sync(cinfo->dev); | ||
| 280 | |||
| 281 | return ret; | ||
| 282 | } | ||
| 283 | |||
| 284 | static int of_dra7_atl_clk_remove(struct platform_device *pdev) | ||
| 285 | { | ||
| 286 | pm_runtime_disable(&pdev->dev); | ||
| 287 | |||
| 288 | return 0; | ||
| 289 | } | ||
| 290 | |||
| 291 | static struct of_device_id of_dra7_atl_clk_match_tbl[] = { | ||
| 292 | { .compatible = "ti,dra7-atl", }, | ||
| 293 | {}, | ||
| 294 | }; | ||
| 295 | MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl); | ||
| 296 | |||
| 297 | static struct platform_driver dra7_atl_clk_driver = { | ||
| 298 | .driver = { | ||
| 299 | .name = "dra7-atl", | ||
| 300 | .owner = THIS_MODULE, | ||
| 301 | .of_match_table = of_dra7_atl_clk_match_tbl, | ||
| 302 | }, | ||
| 303 | .probe = of_dra7_atl_clk_probe, | ||
| 304 | .remove = of_dra7_atl_clk_remove, | ||
| 305 | }; | ||
| 306 | |||
| 307 | module_platform_driver(dra7_atl_clk_driver); | ||
| 308 | |||
| 309 | MODULE_DESCRIPTION("Clock driver for DRA7 Audio Tracking Logic"); | ||
| 310 | MODULE_ALIAS("platform:dra7-atl-clock"); | ||
| 311 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); | ||
| 312 | MODULE_LICENSE("GPL v2"); | ||
