Commit | Line | Data |
---|---|---|
2b27bdcc | 1 | // SPDX-License-Identifier: GPL-2.0-only |
f9f8c043 PU |
2 | /* |
3 | * TWL6040 clock module driver for OMAP4 McPDM functional clock | |
4 | * | |
5 | * Copyright (C) 2012 Texas Instruments Inc. | |
6 | * Peter Ujfalusi <peter.ujfalusi@ti.com> | |
f9f8c043 PU |
7 | */ |
8 | ||
f9f8c043 PU |
9 | #include <linux/module.h> |
10 | #include <linux/slab.h> | |
11 | #include <linux/platform_device.h> | |
12 | #include <linux/mfd/twl6040.h> | |
13 | #include <linux/clk-provider.h> | |
14 | ||
7e37deb7 | 15 | struct twl6040_pdmclk { |
f9f8c043 PU |
16 | struct twl6040 *twl6040; |
17 | struct device *dev; | |
7e37deb7 | 18 | struct clk_hw pdmclk_hw; |
f9f8c043 PU |
19 | int enabled; |
20 | }; | |
21 | ||
7e37deb7 | 22 | static int twl6040_pdmclk_is_prepared(struct clk_hw *hw) |
f9f8c043 | 23 | { |
7e37deb7 PU |
24 | struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, |
25 | pdmclk_hw); | |
26 | ||
27 | return pdmclk->enabled; | |
f9f8c043 PU |
28 | } |
29 | ||
5ae51d67 TL |
30 | static int twl6040_pdmclk_reset_one_clock(struct twl6040_pdmclk *pdmclk, |
31 | unsigned int reg) | |
32 | { | |
33 | const u8 reset_mask = TWL6040_HPLLRST; /* Same for HPPLL and LPPLL */ | |
34 | int ret; | |
35 | ||
36 | ret = twl6040_set_bits(pdmclk->twl6040, reg, reset_mask); | |
37 | if (ret < 0) | |
38 | return ret; | |
39 | ||
40 | ret = twl6040_clear_bits(pdmclk->twl6040, reg, reset_mask); | |
41 | if (ret < 0) | |
42 | return ret; | |
43 | ||
44 | return 0; | |
45 | } | |
46 | ||
47 | /* | |
48 | * TWL6040A2 Phoenix Audio IC erratum #6: "PDM Clock Generation Issue At | |
49 | * Cold Temperature". This affects cold boot and deeper idle states it | |
50 | * seems. The workaround consists of resetting HPPLL and LPPLL. | |
51 | */ | |
52 | static int twl6040_pdmclk_quirk_reset_clocks(struct twl6040_pdmclk *pdmclk) | |
53 | { | |
54 | int ret; | |
55 | ||
56 | ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_HPPLLCTL); | |
57 | if (ret) | |
58 | return ret; | |
59 | ||
60 | ret = twl6040_pdmclk_reset_one_clock(pdmclk, TWL6040_REG_LPPLLCTL); | |
61 | if (ret) | |
62 | return ret; | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
7e37deb7 | 67 | static int twl6040_pdmclk_prepare(struct clk_hw *hw) |
f9f8c043 | 68 | { |
7e37deb7 PU |
69 | struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, |
70 | pdmclk_hw); | |
f9f8c043 PU |
71 | int ret; |
72 | ||
7e37deb7 | 73 | ret = twl6040_power(pdmclk->twl6040, 1); |
5ae51d67 TL |
74 | if (ret) |
75 | return ret; | |
76 | ||
77 | ret = twl6040_pdmclk_quirk_reset_clocks(pdmclk); | |
78 | if (ret) | |
79 | goto out_err; | |
80 | ||
81 | pdmclk->enabled = 1; | |
82 | ||
83 | return 0; | |
84 | ||
85 | out_err: | |
86 | dev_err(pdmclk->dev, "%s: error %i\n", __func__, ret); | |
87 | twl6040_power(pdmclk->twl6040, 0); | |
f9f8c043 PU |
88 | |
89 | return ret; | |
90 | } | |
91 | ||
7e37deb7 | 92 | static void twl6040_pdmclk_unprepare(struct clk_hw *hw) |
f9f8c043 | 93 | { |
7e37deb7 PU |
94 | struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, |
95 | pdmclk_hw); | |
f9f8c043 PU |
96 | int ret; |
97 | ||
7e37deb7 | 98 | ret = twl6040_power(pdmclk->twl6040, 0); |
f9f8c043 | 99 | if (!ret) |
7e37deb7 PU |
100 | pdmclk->enabled = 0; |
101 | ||
102 | } | |
103 | ||
104 | static unsigned long twl6040_pdmclk_recalc_rate(struct clk_hw *hw, | |
105 | unsigned long parent_rate) | |
106 | { | |
107 | struct twl6040_pdmclk *pdmclk = container_of(hw, struct twl6040_pdmclk, | |
108 | pdmclk_hw); | |
109 | ||
110 | return twl6040_get_sysclk(pdmclk->twl6040); | |
f9f8c043 PU |
111 | } |
112 | ||
7e37deb7 PU |
113 | static const struct clk_ops twl6040_pdmclk_ops = { |
114 | .is_prepared = twl6040_pdmclk_is_prepared, | |
115 | .prepare = twl6040_pdmclk_prepare, | |
116 | .unprepare = twl6040_pdmclk_unprepare, | |
117 | .recalc_rate = twl6040_pdmclk_recalc_rate, | |
f9f8c043 PU |
118 | }; |
119 | ||
0777066d | 120 | static const struct clk_init_data twl6040_pdmclk_init = { |
7e37deb7 PU |
121 | .name = "pdmclk", |
122 | .ops = &twl6040_pdmclk_ops, | |
123 | .flags = CLK_GET_RATE_NOCACHE, | |
f9f8c043 PU |
124 | }; |
125 | ||
7e37deb7 | 126 | static int twl6040_pdmclk_probe(struct platform_device *pdev) |
f9f8c043 PU |
127 | { |
128 | struct twl6040 *twl6040 = dev_get_drvdata(pdev->dev.parent); | |
7e37deb7 | 129 | struct twl6040_pdmclk *clkdata; |
f5b3715e | 130 | int ret; |
f9f8c043 PU |
131 | |
132 | clkdata = devm_kzalloc(&pdev->dev, sizeof(*clkdata), GFP_KERNEL); | |
133 | if (!clkdata) | |
134 | return -ENOMEM; | |
135 | ||
136 | clkdata->dev = &pdev->dev; | |
137 | clkdata->twl6040 = twl6040; | |
138 | ||
7e37deb7 | 139 | clkdata->pdmclk_hw.init = &twl6040_pdmclk_init; |
f5b3715e SB |
140 | ret = devm_clk_hw_register(&pdev->dev, &clkdata->pdmclk_hw); |
141 | if (ret) | |
142 | return ret; | |
f9f8c043 | 143 | |
c0431037 | 144 | platform_set_drvdata(pdev, clkdata); |
f9f8c043 | 145 | |
654dea6e MV |
146 | return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, |
147 | &clkdata->pdmclk_hw); | |
f9f8c043 PU |
148 | } |
149 | ||
7e37deb7 | 150 | static struct platform_driver twl6040_pdmclk_driver = { |
f9f8c043 | 151 | .driver = { |
7e37deb7 | 152 | .name = "twl6040-pdmclk", |
f9f8c043 | 153 | }, |
7e37deb7 | 154 | .probe = twl6040_pdmclk_probe, |
f9f8c043 PU |
155 | }; |
156 | ||
7e37deb7 | 157 | module_platform_driver(twl6040_pdmclk_driver); |
f9f8c043 PU |
158 | |
159 | MODULE_DESCRIPTION("TWL6040 clock driver for McPDM functional clock"); | |
160 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); | |
7e37deb7 | 161 | MODULE_ALIAS("platform:twl6040-pdmclk"); |
f9f8c043 | 162 | MODULE_LICENSE("GPL"); |