0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/bits.h>
0010 #include <linux/bitfield.h>
0011 #include <linux/clk-provider.h>
0012 #include <linux/io.h>
0013 #include <linux/kernel.h>
0014 #include <linux/math64.h>
0015 #include <linux/module.h>
0016 #include <linux/of.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/spinlock.h>
0019
0020 #define NCO_CHANNEL_STRIDE 0x4000
0021 #define NCO_CHANNEL_REGSIZE 20
0022
0023 #define REG_CTRL 0
0024 #define CTRL_ENABLE BIT(31)
0025 #define REG_DIV 4
0026 #define DIV_FINE GENMASK(1, 0)
0027 #define DIV_COARSE GENMASK(12, 2)
0028 #define REG_INC1 8
0029 #define REG_INC2 12
0030 #define REG_ACCINIT 16
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053 #define LFSR_POLY 0xa01
0054 #define LFSR_INIT 0x7ff
0055 #define LFSR_LEN 11
0056 #define LFSR_PERIOD ((1 << LFSR_LEN) - 1)
0057 #define LFSR_TBLSIZE (1 << LFSR_LEN)
0058
0059
0060 #define COARSE_DIV_OFFSET 2
0061
0062 struct applnco_tables {
0063 u16 fwd[LFSR_TBLSIZE];
0064 u16 inv[LFSR_TBLSIZE];
0065 };
0066
0067 struct applnco_channel {
0068 void __iomem *base;
0069 struct applnco_tables *tbl;
0070 struct clk_hw hw;
0071
0072 spinlock_t lock;
0073 };
0074
0075 #define to_applnco_channel(_hw) container_of(_hw, struct applnco_channel, hw)
0076
0077 static void applnco_enable_nolock(struct clk_hw *hw)
0078 {
0079 struct applnco_channel *chan = to_applnco_channel(hw);
0080 u32 val;
0081
0082 val = readl_relaxed(chan->base + REG_CTRL);
0083 writel_relaxed(val | CTRL_ENABLE, chan->base + REG_CTRL);
0084 }
0085
0086 static void applnco_disable_nolock(struct clk_hw *hw)
0087 {
0088 struct applnco_channel *chan = to_applnco_channel(hw);
0089 u32 val;
0090
0091 val = readl_relaxed(chan->base + REG_CTRL);
0092 writel_relaxed(val & ~CTRL_ENABLE, chan->base + REG_CTRL);
0093 }
0094
0095 static int applnco_is_enabled(struct clk_hw *hw)
0096 {
0097 struct applnco_channel *chan = to_applnco_channel(hw);
0098
0099 return (readl_relaxed(chan->base + REG_CTRL) & CTRL_ENABLE) != 0;
0100 }
0101
0102 static void applnco_compute_tables(struct applnco_tables *tbl)
0103 {
0104 int i;
0105 u32 state = LFSR_INIT;
0106
0107
0108
0109
0110
0111 for (i = LFSR_PERIOD; i > 0; i--) {
0112 if (state & 1)
0113 state = (state >> 1) ^ (LFSR_POLY >> 1);
0114 else
0115 state = (state >> 1);
0116 tbl->fwd[i] = state;
0117 tbl->inv[state] = i;
0118 }
0119
0120
0121 tbl->fwd[0] = 0;
0122 tbl->inv[0] = 0;
0123 }
0124
0125 static bool applnco_div_out_of_range(unsigned int div)
0126 {
0127 unsigned int coarse = div / 4;
0128
0129 return coarse < COARSE_DIV_OFFSET ||
0130 coarse >= COARSE_DIV_OFFSET + LFSR_TBLSIZE;
0131 }
0132
0133 static u32 applnco_div_translate(struct applnco_tables *tbl, unsigned int div)
0134 {
0135 unsigned int coarse = div / 4;
0136
0137 if (WARN_ON(applnco_div_out_of_range(div)))
0138 return 0;
0139
0140 return FIELD_PREP(DIV_COARSE, tbl->fwd[coarse - COARSE_DIV_OFFSET]) |
0141 FIELD_PREP(DIV_FINE, div % 4);
0142 }
0143
0144 static unsigned int applnco_div_translate_inv(struct applnco_tables *tbl, u32 regval)
0145 {
0146 unsigned int coarse, fine;
0147
0148 coarse = tbl->inv[FIELD_GET(DIV_COARSE, regval)] + COARSE_DIV_OFFSET;
0149 fine = FIELD_GET(DIV_FINE, regval);
0150
0151 return coarse * 4 + fine;
0152 }
0153
0154 static int applnco_set_rate(struct clk_hw *hw, unsigned long rate,
0155 unsigned long parent_rate)
0156 {
0157 struct applnco_channel *chan = to_applnco_channel(hw);
0158 unsigned long flags;
0159 u32 div, inc1, inc2;
0160 bool was_enabled;
0161
0162 div = 2 * parent_rate / rate;
0163 inc1 = 2 * parent_rate - div * rate;
0164 inc2 = inc1 - rate;
0165
0166 if (applnco_div_out_of_range(div))
0167 return -EINVAL;
0168
0169 div = applnco_div_translate(chan->tbl, div);
0170
0171 spin_lock_irqsave(&chan->lock, flags);
0172 was_enabled = applnco_is_enabled(hw);
0173 applnco_disable_nolock(hw);
0174
0175 writel_relaxed(div, chan->base + REG_DIV);
0176 writel_relaxed(inc1, chan->base + REG_INC1);
0177 writel_relaxed(inc2, chan->base + REG_INC2);
0178
0179
0180 writel_relaxed(1 << 31, chan->base + REG_ACCINIT);
0181
0182 if (was_enabled)
0183 applnco_enable_nolock(hw);
0184 spin_unlock_irqrestore(&chan->lock, flags);
0185
0186 return 0;
0187 }
0188
0189 static unsigned long applnco_recalc_rate(struct clk_hw *hw,
0190 unsigned long parent_rate)
0191 {
0192 struct applnco_channel *chan = to_applnco_channel(hw);
0193 u32 div, inc1, inc2, incbase;
0194
0195 div = applnco_div_translate_inv(chan->tbl,
0196 readl_relaxed(chan->base + REG_DIV));
0197
0198 inc1 = readl_relaxed(chan->base + REG_INC1);
0199 inc2 = readl_relaxed(chan->base + REG_INC2);
0200
0201
0202
0203
0204
0205 if (inc1 >= (1 << 31) || inc2 < (1 << 31) || (inc1 == 0 && inc2 == 0))
0206 return 0;
0207
0208
0209 incbase = inc1 - inc2;
0210
0211 return div64_u64(((u64) parent_rate) * 2 * incbase,
0212 ((u64) div) * incbase + inc1);
0213 }
0214
0215 static long applnco_round_rate(struct clk_hw *hw, unsigned long rate,
0216 unsigned long *parent_rate)
0217 {
0218 unsigned long lo = *parent_rate / (COARSE_DIV_OFFSET + LFSR_TBLSIZE) + 1;
0219 unsigned long hi = *parent_rate / COARSE_DIV_OFFSET;
0220
0221 return clamp(rate, lo, hi);
0222 }
0223
0224 static int applnco_enable(struct clk_hw *hw)
0225 {
0226 struct applnco_channel *chan = to_applnco_channel(hw);
0227 unsigned long flags;
0228
0229 spin_lock_irqsave(&chan->lock, flags);
0230 applnco_enable_nolock(hw);
0231 spin_unlock_irqrestore(&chan->lock, flags);
0232
0233 return 0;
0234 }
0235
0236 static void applnco_disable(struct clk_hw *hw)
0237 {
0238 struct applnco_channel *chan = to_applnco_channel(hw);
0239 unsigned long flags;
0240
0241 spin_lock_irqsave(&chan->lock, flags);
0242 applnco_disable_nolock(hw);
0243 spin_unlock_irqrestore(&chan->lock, flags);
0244 }
0245
0246 static const struct clk_ops applnco_ops = {
0247 .set_rate = applnco_set_rate,
0248 .recalc_rate = applnco_recalc_rate,
0249 .round_rate = applnco_round_rate,
0250 .enable = applnco_enable,
0251 .disable = applnco_disable,
0252 .is_enabled = applnco_is_enabled,
0253 };
0254
0255 static int applnco_probe(struct platform_device *pdev)
0256 {
0257 struct device_node *np = pdev->dev.of_node;
0258 struct clk_parent_data pdata = { .index = 0 };
0259 struct clk_init_data init;
0260 struct clk_hw_onecell_data *onecell_data;
0261 void __iomem *base;
0262 struct resource *res;
0263 struct applnco_tables *tbl;
0264 unsigned int nchannels;
0265 int ret, i;
0266
0267 base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
0268 if (IS_ERR(base))
0269 return PTR_ERR(base);
0270
0271 if (resource_size(res) < NCO_CHANNEL_REGSIZE)
0272 return -EINVAL;
0273 nchannels = (resource_size(res) - NCO_CHANNEL_REGSIZE)
0274 / NCO_CHANNEL_STRIDE + 1;
0275
0276 onecell_data = devm_kzalloc(&pdev->dev, struct_size(onecell_data, hws,
0277 nchannels), GFP_KERNEL);
0278 if (!onecell_data)
0279 return -ENOMEM;
0280 onecell_data->num = nchannels;
0281
0282 tbl = devm_kzalloc(&pdev->dev, sizeof(*tbl), GFP_KERNEL);
0283 if (!tbl)
0284 return -ENOMEM;
0285 applnco_compute_tables(tbl);
0286
0287 for (i = 0; i < nchannels; i++) {
0288 struct applnco_channel *chan;
0289
0290 chan = devm_kzalloc(&pdev->dev, sizeof(*chan), GFP_KERNEL);
0291 if (!chan)
0292 return -ENOMEM;
0293 chan->base = base + NCO_CHANNEL_STRIDE * i;
0294 chan->tbl = tbl;
0295 spin_lock_init(&chan->lock);
0296
0297 memset(&init, 0, sizeof(init));
0298 init.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
0299 "%s-%d", np->name, i);
0300 init.ops = &applnco_ops;
0301 init.parent_data = &pdata;
0302 init.num_parents = 1;
0303 init.flags = 0;
0304
0305 chan->hw.init = &init;
0306 ret = devm_clk_hw_register(&pdev->dev, &chan->hw);
0307 if (ret)
0308 return ret;
0309
0310 onecell_data->hws[i] = &chan->hw;
0311 }
0312
0313 return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,
0314 onecell_data);
0315 }
0316
0317 static const struct of_device_id applnco_ids[] = {
0318 { .compatible = "apple,nco" },
0319 { }
0320 };
0321 MODULE_DEVICE_TABLE(of, applnco_ids);
0322
0323 static struct platform_driver applnco_driver = {
0324 .driver = {
0325 .name = "apple-nco",
0326 .of_match_table = applnco_ids,
0327 },
0328 .probe = applnco_probe,
0329 };
0330 module_platform_driver(applnco_driver);
0331
0332 MODULE_AUTHOR("Martin PoviĊĦer <povik+lin@cutebit.org>");
0333 MODULE_DESCRIPTION("Clock driver for NCO blocks on Apple SoCs");
0334 MODULE_LICENSE("GPL");