Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only OR MIT
0002 /*
0003  * Driver for an SoC block (Numerically Controlled Oscillator)
0004  * found on t8103 (M1) and other Apple chips
0005  *
0006  * Copyright (C) The Asahi Linux Contributors
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  * Theory of operation (postulated)
0034  *
0035  * The REG_DIV register indirectly expresses a base integer divisor, roughly
0036  * corresponding to twice the desired ratio of input to output clock. This
0037  * base divisor is adjusted on a cycle-by-cycle basis based on the state of a
0038  * 32-bit phase accumulator to achieve a desired precise clock ratio over the
0039  * long term.
0040  *
0041  * Specifically an output clock cycle is produced after (REG_DIV divisor)/2
0042  * or (REG_DIV divisor + 1)/2 input cycles, the latter taking effect when top
0043  * bit of the 32-bit accumulator is set. The accumulator is incremented each
0044  * produced output cycle, by the value from either REG_INC1 or REG_INC2, which
0045  * of the two is selected depending again on the accumulator's current top bit.
0046  *
0047  * Because the NCO hardware implements counting of input clock cycles in part
0048  * in a Galois linear-feedback shift register, the higher bits of divisor
0049  * are programmed into REG_DIV by picking an appropriate LFSR state. See
0050  * applnco_compute_tables/applnco_div_translate for details on this.
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 /* The minimal attainable coarse divisor (first value in table) */
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      * Go through the states of a Galois LFSR and build
0109      * a coarse divisor translation table.
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     /* Zero value is special-cased */
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     /* Presumably a neutral initial value for accumulator */
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      * We don't support wraparound of accumulator
0203      * nor the edge case of both increments being zero
0204      */
0205     if (inc1 >= (1 << 31) || inc2 < (1 << 31) || (inc1 == 0 && inc2 == 0))
0206         return 0;
0207 
0208     /* Scale both sides of division by incbase to maintain precision */
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");