blob: c55ed09453ce4258157f208145edd4fa3d55a4f8 [file] [log] [blame]
Duy Truonge833aca2013-02-12 13:35:08 -08001/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
Michael Bohandd975e52012-07-25 11:04:55 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/types.h>
16#include <linux/spmi.h>
17#include <linux/slab.h>
18#include <linux/of.h>
19#include <linux/export.h>
20#include <linux/module.h>
21#include <linux/delay.h>
22#include <linux/err.h>
23#include <linux/time.h>
24#include <linux/qpnp/clkdiv.h>
25
26#define Q_MAX_DT_PROP_SIZE 32
27
28#define Q_REG_ADDR(q_clkdiv, reg_offset) \
29 ((q_clkdiv)->offset + reg_offset)
30
31#define Q_REG_DIV_CTL1 0x43
32#define Q_REG_EN_CTL 0x46
33
34#define Q_SET_EN BIT(7)
35
36#define Q_CXO_PERIOD_NS(_cxo_clk) (NSEC_PER_SEC / _cxo_clk)
37#define Q_DIV_PERIOD_NS(_cxo_clk, _div) (NSEC_PER_SEC / (_cxo_clk / _div))
38#define Q_ENABLE_DELAY_NS(_cxo_clk, _div) (2 * Q_CXO_PERIOD_NS(_cxo_clk) + \
39 3 * Q_DIV_PERIOD_NS(_cxo_clk, _div))
40#define Q_DISABLE_DELAY_NS(_cxo_clk, _div) (3 * Q_DIV_PERIOD_NS(_cxo_clk, _div))
41
42struct q_clkdiv {
43 uint32_t cxo_hz;
44 enum q_clkdiv_cfg cxo_div;
45 struct device_node *node;
46 uint16_t offset;
47 struct spmi_controller *ctrl;
48 bool enabled;
49 struct mutex lock;
50 struct list_head list;
51 uint8_t slave;
52};
53
54static LIST_HEAD(qpnp_clkdiv_devs);
55
56/**
57 * qpnp_clkdiv_get - get a clkdiv handle
58 * @dev: client device pointer.
59 * @name: client specific name for the clock in question.
60 *
61 * Return a clkdiv handle given a client specific name. This name be a prefix
62 * for a property naming that takes a phandle to the actual clkdiv device.
63 */
64struct q_clkdiv *qpnp_clkdiv_get(struct device *dev, const char *name)
65{
66 struct q_clkdiv *q_clkdiv;
67 struct device_node *divclk_node;
68 char prop_name[Q_MAX_DT_PROP_SIZE];
69 int n;
70
71 n = snprintf(prop_name, Q_MAX_DT_PROP_SIZE, "%s-clk", name);
72 if (n == Q_MAX_DT_PROP_SIZE)
73 return ERR_PTR(-EINVAL);
74
75 divclk_node = of_parse_phandle(dev->of_node, prop_name, 0);
76 if (divclk_node == NULL)
77 return ERR_PTR(-ENODEV);
78
79 list_for_each_entry(q_clkdiv, &qpnp_clkdiv_devs, list)
80 if (q_clkdiv->node == divclk_node)
81 return q_clkdiv;
82 return ERR_PTR(-EPROBE_DEFER);
83}
84EXPORT_SYMBOL(qpnp_clkdiv_get);
85
86static int __clkdiv_enable(struct q_clkdiv *q_clkdiv, bool enable)
87{
88 int rc;
89 char buf[1];
90
91 buf[0] = enable ? Q_SET_EN : 0;
92
93 mutex_lock(&q_clkdiv->lock);
94 rc = spmi_ext_register_writel(q_clkdiv->ctrl, q_clkdiv->slave,
95 Q_REG_ADDR(q_clkdiv, Q_REG_EN_CTL),
96 &buf[0], 1);
97 if (!rc)
98 q_clkdiv->enabled = enable;
99
100 mutex_unlock(&q_clkdiv->lock);
101
102 if (enable)
103 ndelay(Q_ENABLE_DELAY_NS(q_clkdiv->cxo_hz, q_clkdiv->cxo_div));
104 else
105 ndelay(Q_DISABLE_DELAY_NS(q_clkdiv->cxo_hz, q_clkdiv->cxo_div));
106
107 return rc;
108}
109
110/**
111 * qpnp_clkdiv_enable - enable a clkdiv
112 * @q_clkdiv: pointer to clkdiv handle
113 */
114int qpnp_clkdiv_enable(struct q_clkdiv *q_clkdiv)
115{
116 return __clkdiv_enable(q_clkdiv, true);
117}
118EXPORT_SYMBOL(qpnp_clkdiv_enable);
119
120/**
121 * qpnp_clkdiv_disable - disable a clkdiv
122 * @q_clkdiv: pointer to clkdiv handle
123 */
124int qpnp_clkdiv_disable(struct q_clkdiv *q_clkdiv)
125{
126 return __clkdiv_enable(q_clkdiv, false);
127}
128EXPORT_SYMBOL(qpnp_clkdiv_disable);
129
130/**
131 * @q_clkdiv: pointer to clkdiv handle
132 * @cfg: setting used to configure the output frequency
133 *
134 * Given a q_clkdiv_cfg setting, configure the corresponding clkdiv device
135 * for the desired output frequency.
136 */
137int qpnp_clkdiv_config(struct q_clkdiv *q_clkdiv, enum q_clkdiv_cfg cfg)
138{
139 int rc;
140 char buf[1];
141
142 if (cfg < 0 || cfg >= Q_CLKDIV_INVALID)
143 return -EINVAL;
144
145 buf[0] = cfg;
146
147 mutex_lock(&q_clkdiv->lock);
148
149 if (q_clkdiv->enabled) {
150 rc = __clkdiv_enable(q_clkdiv, false);
151 if (rc) {
152 pr_err("unable to disable clock\n");
153 goto cfg_err;
154 }
155 }
156
157 rc = spmi_ext_register_writel(q_clkdiv->ctrl, q_clkdiv->slave,
158 Q_REG_ADDR(q_clkdiv, Q_REG_DIV_CTL1), &buf[0], 1);
159 if (rc) {
160 pr_err("enable to write config\n");
161 q_clkdiv->enabled = 0;
162 goto cfg_err;
163 }
164
165 q_clkdiv->cxo_div = cfg;
166
167 if (q_clkdiv->enabled) {
168 rc = __clkdiv_enable(q_clkdiv, true);
169 if (rc) {
170 pr_err("unable to re-enable clock\n");
171 goto cfg_err;
172 }
173 }
174
175cfg_err:
176 mutex_unlock(&q_clkdiv->lock);
177 return rc;
178}
179EXPORT_SYMBOL(qpnp_clkdiv_config);
180
181static int __devinit qpnp_clkdiv_probe(struct spmi_device *spmi)
182{
183 struct q_clkdiv *q_clkdiv;
184 struct device_node *node = spmi->dev.of_node;
185 int rc;
186 uint32_t en;
187 struct resource *res;
188
189 q_clkdiv = devm_kzalloc(&spmi->dev, sizeof(*q_clkdiv), GFP_ATOMIC);
190 if (!q_clkdiv)
191 return -ENOMEM;
192
193 rc = of_property_read_u32(node, "qcom,cxo-freq",
194 &q_clkdiv->cxo_hz);
195 if (rc) {
196 dev_err(&spmi->dev,
197 "%s: unable to get qcom,cxo-freq property\n", __func__);
198 return rc;
199 }
200
201 res = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
202 if (!res) {
203 dev_err(&spmi->dev, "%s: unable to get device reg resource\n",
204 __func__);
205 }
206
207 q_clkdiv->slave = spmi->sid;
208 q_clkdiv->offset = res->start;
209 q_clkdiv->ctrl = spmi->ctrl;
210 q_clkdiv->node = node;
211 mutex_init(&q_clkdiv->lock);
212
213 rc = of_property_read_u32(node, "qcom,cxo-div",
214 &q_clkdiv->cxo_div);
215 if (rc && rc != -EINVAL) {
216 dev_err(&spmi->dev,
217 "%s: error getting qcom,cxo-div property\n",
218 __func__);
219 return rc;
220 }
221
222 if (!rc) {
223 rc = qpnp_clkdiv_config(q_clkdiv, q_clkdiv->cxo_div);
224 if (rc) {
225 dev_err(&spmi->dev,
226 "%s: unable to set default divide config\n",
227 __func__);
228 return rc;
229 }
230 }
231
232 rc = of_property_read_u32(node, "qcom,enable", &en);
233 if (rc && rc != -EINVAL) {
234 dev_err(&spmi->dev,
235 "%s: error getting qcom,enable property\n", __func__);
236 return rc;
237 }
238 if (!rc) {
239 rc = __clkdiv_enable(q_clkdiv, en);
240 dev_err(&spmi->dev,
241 "%s: unable to set default config\n", __func__);
242 return rc;
243 }
244
245 dev_set_drvdata(&spmi->dev, q_clkdiv);
246 list_add(&q_clkdiv->list, &qpnp_clkdiv_devs);
247
248 return 0;
249}
250
251static int __devexit qpnp_clkdiv_remove(struct spmi_device *spmi)
252{
253 struct q_clkdiv *q_clkdiv = dev_get_drvdata(&spmi->dev);
254 list_del(&q_clkdiv->list);
255 return 0;
256}
257
258static struct of_device_id spmi_match_table[] = {
259 { .compatible = "qcom,qpnp-clkdiv",
260 },
261 {}
262};
263
264static struct spmi_driver qpnp_clkdiv_driver = {
265 .driver = {
266 .name = "qcom,qpnp-clkdiv",
267 .of_match_table = spmi_match_table,
268 },
269 .probe = qpnp_clkdiv_probe,
270 .remove = __devexit_p(qpnp_clkdiv_remove),
271};
272
273static int __init qpnp_clkdiv_init(void)
274{
275 return spmi_driver_register(&qpnp_clkdiv_driver);
276}
277
278static void __exit qpnp_clkdiv_exit(void)
279{
280 return spmi_driver_unregister(&qpnp_clkdiv_driver);
281}
282
283MODULE_DESCRIPTION("QPNP PMIC clkdiv driver");
284MODULE_LICENSE("GPL v2");
285
286module_init(qpnp_clkdiv_init);
287module_exit(qpnp_clkdiv_exit);