Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * AXI clkgen driver
0004  *
0005  * Copyright 2012-2013 Analog Devices Inc.
0006  *  Author: Lars-Peter Clausen <lars@metafoo.de>
0007  */
0008 
0009 #include <linux/platform_device.h>
0010 #include <linux/clk-provider.h>
0011 #include <linux/slab.h>
0012 #include <linux/io.h>
0013 #include <linux/of.h>
0014 #include <linux/module.h>
0015 #include <linux/err.h>
0016 
0017 #define AXI_CLKGEN_V2_REG_RESET     0x40
0018 #define AXI_CLKGEN_V2_REG_CLKSEL    0x44
0019 #define AXI_CLKGEN_V2_REG_DRP_CNTRL 0x70
0020 #define AXI_CLKGEN_V2_REG_DRP_STATUS    0x74
0021 
0022 #define AXI_CLKGEN_V2_RESET_MMCM_ENABLE BIT(1)
0023 #define AXI_CLKGEN_V2_RESET_ENABLE  BIT(0)
0024 
0025 #define AXI_CLKGEN_V2_DRP_CNTRL_SEL BIT(29)
0026 #define AXI_CLKGEN_V2_DRP_CNTRL_READ    BIT(28)
0027 
0028 #define AXI_CLKGEN_V2_DRP_STATUS_BUSY   BIT(16)
0029 
0030 #define MMCM_REG_CLKOUT5_2  0x07
0031 #define MMCM_REG_CLKOUT0_1  0x08
0032 #define MMCM_REG_CLKOUT0_2  0x09
0033 #define MMCM_REG_CLKOUT6_2  0x13
0034 #define MMCM_REG_CLK_FB1    0x14
0035 #define MMCM_REG_CLK_FB2    0x15
0036 #define MMCM_REG_CLK_DIV    0x16
0037 #define MMCM_REG_LOCK1      0x18
0038 #define MMCM_REG_LOCK2      0x19
0039 #define MMCM_REG_LOCK3      0x1a
0040 #define MMCM_REG_POWER      0x28
0041 #define MMCM_REG_FILTER1    0x4e
0042 #define MMCM_REG_FILTER2    0x4f
0043 
0044 #define MMCM_CLKOUT_NOCOUNT BIT(6)
0045 
0046 #define MMCM_CLK_DIV_DIVIDE BIT(11)
0047 #define MMCM_CLK_DIV_NOCOUNT    BIT(12)
0048 
0049 struct axi_clkgen_limits {
0050     unsigned int fpfd_min;
0051     unsigned int fpfd_max;
0052     unsigned int fvco_min;
0053     unsigned int fvco_max;
0054 };
0055 
0056 struct axi_clkgen {
0057     void __iomem *base;
0058     struct clk_hw clk_hw;
0059     struct axi_clkgen_limits limits;
0060 };
0061 
0062 static uint32_t axi_clkgen_lookup_filter(unsigned int m)
0063 {
0064     switch (m) {
0065     case 0:
0066         return 0x01001990;
0067     case 1:
0068         return 0x01001190;
0069     case 2:
0070         return 0x01009890;
0071     case 3:
0072         return 0x01001890;
0073     case 4:
0074         return 0x01008890;
0075     case 5 ... 8:
0076         return 0x01009090;
0077     case 9 ... 11:
0078         return 0x01000890;
0079     case 12:
0080         return 0x08009090;
0081     case 13 ... 22:
0082         return 0x01001090;
0083     case 23 ... 36:
0084         return 0x01008090;
0085     case 37 ... 46:
0086         return 0x08001090;
0087     default:
0088         return 0x08008090;
0089     }
0090 }
0091 
0092 static const uint32_t axi_clkgen_lock_table[] = {
0093     0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8,
0094     0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8,
0095     0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339,
0096     0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271,
0097     0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4,
0098     0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190,
0099     0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e,
0100     0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c,
0101     0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113,
0102 };
0103 
0104 static uint32_t axi_clkgen_lookup_lock(unsigned int m)
0105 {
0106     if (m < ARRAY_SIZE(axi_clkgen_lock_table))
0107         return axi_clkgen_lock_table[m];
0108     return 0x1f1f00fa;
0109 }
0110 
0111 static const struct axi_clkgen_limits axi_clkgen_zynqmp_default_limits = {
0112     .fpfd_min = 10000,
0113     .fpfd_max = 450000,
0114     .fvco_min = 800000,
0115     .fvco_max = 1600000,
0116 };
0117 
0118 static const struct axi_clkgen_limits axi_clkgen_zynq_default_limits = {
0119     .fpfd_min = 10000,
0120     .fpfd_max = 300000,
0121     .fvco_min = 600000,
0122     .fvco_max = 1200000,
0123 };
0124 
0125 static void axi_clkgen_calc_params(const struct axi_clkgen_limits *limits,
0126     unsigned long fin, unsigned long fout,
0127     unsigned int *best_d, unsigned int *best_m, unsigned int *best_dout)
0128 {
0129     unsigned long d, d_min, d_max, _d_min, _d_max;
0130     unsigned long m, m_min, m_max;
0131     unsigned long f, dout, best_f, fvco;
0132     unsigned long fract_shift = 0;
0133     unsigned long fvco_min_fract, fvco_max_fract;
0134 
0135     fin /= 1000;
0136     fout /= 1000;
0137 
0138     best_f = ULONG_MAX;
0139     *best_d = 0;
0140     *best_m = 0;
0141     *best_dout = 0;
0142 
0143     d_min = max_t(unsigned long, DIV_ROUND_UP(fin, limits->fpfd_max), 1);
0144     d_max = min_t(unsigned long, fin / limits->fpfd_min, 80);
0145 
0146 again:
0147     fvco_min_fract = limits->fvco_min << fract_shift;
0148     fvco_max_fract = limits->fvco_max << fract_shift;
0149 
0150     m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min_fract, fin) * d_min, 1);
0151     m_max = min_t(unsigned long, fvco_max_fract * d_max / fin, 64 << fract_shift);
0152 
0153     for (m = m_min; m <= m_max; m++) {
0154         _d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max_fract));
0155         _d_max = min(d_max, fin * m / fvco_min_fract);
0156 
0157         for (d = _d_min; d <= _d_max; d++) {
0158             fvco = fin * m / d;
0159 
0160             dout = DIV_ROUND_CLOSEST(fvco, fout);
0161             dout = clamp_t(unsigned long, dout, 1, 128 << fract_shift);
0162             f = fvco / dout;
0163             if (abs(f - fout) < abs(best_f - fout)) {
0164                 best_f = f;
0165                 *best_d = d;
0166                 *best_m = m << (3 - fract_shift);
0167                 *best_dout = dout << (3 - fract_shift);
0168                 if (best_f == fout)
0169                     return;
0170             }
0171         }
0172     }
0173 
0174     /* Lets see if we find a better setting in fractional mode */
0175     if (fract_shift == 0) {
0176         fract_shift = 3;
0177         goto again;
0178     }
0179 }
0180 
0181 struct axi_clkgen_div_params {
0182     unsigned int low;
0183     unsigned int high;
0184     unsigned int edge;
0185     unsigned int nocount;
0186     unsigned int frac_en;
0187     unsigned int frac;
0188     unsigned int frac_wf_f;
0189     unsigned int frac_wf_r;
0190     unsigned int frac_phase;
0191 };
0192 
0193 static void axi_clkgen_calc_clk_params(unsigned int divider,
0194     unsigned int frac_divider, struct axi_clkgen_div_params *params)
0195 {
0196 
0197     memset(params, 0x0, sizeof(*params));
0198 
0199     if (divider == 1) {
0200         params->nocount = 1;
0201         return;
0202     }
0203 
0204     if (frac_divider == 0) {
0205         params->high = divider / 2;
0206         params->edge = divider % 2;
0207         params->low = divider - params->high;
0208     } else {
0209         params->frac_en = 1;
0210         params->frac = frac_divider;
0211 
0212         params->high = divider / 2;
0213         params->edge = divider % 2;
0214         params->low = params->high;
0215 
0216         if (params->edge == 0) {
0217             params->high--;
0218             params->frac_wf_r = 1;
0219         }
0220 
0221         if (params->edge == 0 || frac_divider == 1)
0222             params->low--;
0223         if (((params->edge == 0) ^ (frac_divider == 1)) ||
0224             (divider == 2 && frac_divider == 1))
0225             params->frac_wf_f = 1;
0226 
0227         params->frac_phase = params->edge * 4 + frac_divider / 2;
0228     }
0229 }
0230 
0231 static void axi_clkgen_write(struct axi_clkgen *axi_clkgen,
0232     unsigned int reg, unsigned int val)
0233 {
0234     writel(val, axi_clkgen->base + reg);
0235 }
0236 
0237 static void axi_clkgen_read(struct axi_clkgen *axi_clkgen,
0238     unsigned int reg, unsigned int *val)
0239 {
0240     *val = readl(axi_clkgen->base + reg);
0241 }
0242 
0243 static int axi_clkgen_wait_non_busy(struct axi_clkgen *axi_clkgen)
0244 {
0245     unsigned int timeout = 10000;
0246     unsigned int val;
0247 
0248     do {
0249         axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_STATUS, &val);
0250     } while ((val & AXI_CLKGEN_V2_DRP_STATUS_BUSY) && --timeout);
0251 
0252     if (val & AXI_CLKGEN_V2_DRP_STATUS_BUSY)
0253         return -EIO;
0254 
0255     return val & 0xffff;
0256 }
0257 
0258 static int axi_clkgen_mmcm_read(struct axi_clkgen *axi_clkgen,
0259     unsigned int reg, unsigned int *val)
0260 {
0261     unsigned int reg_val;
0262     int ret;
0263 
0264     ret = axi_clkgen_wait_non_busy(axi_clkgen);
0265     if (ret < 0)
0266         return ret;
0267 
0268     reg_val = AXI_CLKGEN_V2_DRP_CNTRL_SEL | AXI_CLKGEN_V2_DRP_CNTRL_READ;
0269     reg_val |= (reg << 16);
0270 
0271     axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val);
0272 
0273     ret = axi_clkgen_wait_non_busy(axi_clkgen);
0274     if (ret < 0)
0275         return ret;
0276 
0277     *val = ret;
0278 
0279     return 0;
0280 }
0281 
0282 static int axi_clkgen_mmcm_write(struct axi_clkgen *axi_clkgen,
0283     unsigned int reg, unsigned int val, unsigned int mask)
0284 {
0285     unsigned int reg_val = 0;
0286     int ret;
0287 
0288     ret = axi_clkgen_wait_non_busy(axi_clkgen);
0289     if (ret < 0)
0290         return ret;
0291 
0292     if (mask != 0xffff) {
0293         axi_clkgen_mmcm_read(axi_clkgen, reg, &reg_val);
0294         reg_val &= ~mask;
0295     }
0296 
0297     reg_val |= AXI_CLKGEN_V2_DRP_CNTRL_SEL | (reg << 16) | (val & mask);
0298 
0299     axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val);
0300 
0301     return 0;
0302 }
0303 
0304 static void axi_clkgen_mmcm_enable(struct axi_clkgen *axi_clkgen,
0305     bool enable)
0306 {
0307     unsigned int val = AXI_CLKGEN_V2_RESET_ENABLE;
0308 
0309     if (enable)
0310         val |= AXI_CLKGEN_V2_RESET_MMCM_ENABLE;
0311 
0312     axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_RESET, val);
0313 }
0314 
0315 static struct axi_clkgen *clk_hw_to_axi_clkgen(struct clk_hw *clk_hw)
0316 {
0317     return container_of(clk_hw, struct axi_clkgen, clk_hw);
0318 }
0319 
0320 static void axi_clkgen_set_div(struct axi_clkgen *axi_clkgen,
0321     unsigned int reg1, unsigned int reg2, unsigned int reg3,
0322     struct axi_clkgen_div_params *params)
0323 {
0324     axi_clkgen_mmcm_write(axi_clkgen, reg1,
0325         (params->high << 6) | params->low, 0xefff);
0326     axi_clkgen_mmcm_write(axi_clkgen, reg2,
0327         (params->frac << 12) | (params->frac_en << 11) |
0328         (params->frac_wf_r << 10) | (params->edge << 7) |
0329         (params->nocount << 6), 0x7fff);
0330     if (reg3 != 0) {
0331         axi_clkgen_mmcm_write(axi_clkgen, reg3,
0332             (params->frac_phase << 11) | (params->frac_wf_f << 10), 0x3c00);
0333     }
0334 }
0335 
0336 static int axi_clkgen_set_rate(struct clk_hw *clk_hw,
0337     unsigned long rate, unsigned long parent_rate)
0338 {
0339     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0340     const struct axi_clkgen_limits *limits = &axi_clkgen->limits;
0341     unsigned int d, m, dout;
0342     struct axi_clkgen_div_params params;
0343     uint32_t power = 0;
0344     uint32_t filter;
0345     uint32_t lock;
0346 
0347     if (parent_rate == 0 || rate == 0)
0348         return -EINVAL;
0349 
0350     axi_clkgen_calc_params(limits, parent_rate, rate, &d, &m, &dout);
0351 
0352     if (d == 0 || dout == 0 || m == 0)
0353         return -EINVAL;
0354 
0355     if ((dout & 0x7) != 0 || (m & 0x7) != 0)
0356         power |= 0x9800;
0357 
0358     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_POWER, power, 0x9800);
0359 
0360     filter = axi_clkgen_lookup_filter(m - 1);
0361     lock = axi_clkgen_lookup_lock(m - 1);
0362 
0363     axi_clkgen_calc_clk_params(dout >> 3, dout & 0x7, &params);
0364     axi_clkgen_set_div(axi_clkgen,  MMCM_REG_CLKOUT0_1, MMCM_REG_CLKOUT0_2,
0365         MMCM_REG_CLKOUT5_2, &params);
0366 
0367     axi_clkgen_calc_clk_params(d, 0, &params);
0368     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_DIV,
0369         (params.edge << 13) | (params.nocount << 12) |
0370         (params.high << 6) | params.low, 0x3fff);
0371 
0372     axi_clkgen_calc_clk_params(m >> 3, m & 0x7, &params);
0373     axi_clkgen_set_div(axi_clkgen,  MMCM_REG_CLK_FB1, MMCM_REG_CLK_FB2,
0374         MMCM_REG_CLKOUT6_2, &params);
0375 
0376     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK1, lock & 0x3ff, 0x3ff);
0377     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK2,
0378         (((lock >> 16) & 0x1f) << 10) | 0x1, 0x7fff);
0379     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK3,
0380         (((lock >> 24) & 0x1f) << 10) | 0x3e9, 0x7fff);
0381     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER1, filter >> 16, 0x9900);
0382     axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER2, filter, 0x9900);
0383 
0384     return 0;
0385 }
0386 
0387 static long axi_clkgen_round_rate(struct clk_hw *hw, unsigned long rate,
0388     unsigned long *parent_rate)
0389 {
0390     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(hw);
0391     const struct axi_clkgen_limits *limits = &axi_clkgen->limits;
0392     unsigned int d, m, dout;
0393     unsigned long long tmp;
0394 
0395     axi_clkgen_calc_params(limits, *parent_rate, rate, &d, &m, &dout);
0396 
0397     if (d == 0 || dout == 0 || m == 0)
0398         return -EINVAL;
0399 
0400     tmp = (unsigned long long)*parent_rate * m;
0401     tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);
0402 
0403     return min_t(unsigned long long, tmp, LONG_MAX);
0404 }
0405 
0406 static unsigned int axi_clkgen_get_div(struct axi_clkgen *axi_clkgen,
0407     unsigned int reg1, unsigned int reg2)
0408 {
0409     unsigned int val1, val2;
0410     unsigned int div;
0411 
0412     axi_clkgen_mmcm_read(axi_clkgen, reg2, &val2);
0413     if (val2 & MMCM_CLKOUT_NOCOUNT)
0414         return 8;
0415 
0416     axi_clkgen_mmcm_read(axi_clkgen, reg1, &val1);
0417 
0418     div = (val1 & 0x3f) + ((val1 >> 6) & 0x3f);
0419     div <<= 3;
0420 
0421     if (val2 & MMCM_CLK_DIV_DIVIDE) {
0422         if ((val2 & BIT(7)) && (val2 & 0x7000) != 0x1000)
0423             div += 8;
0424         else
0425             div += 16;
0426 
0427         div += (val2 >> 12) & 0x7;
0428     }
0429 
0430     return div;
0431 }
0432 
0433 static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw,
0434     unsigned long parent_rate)
0435 {
0436     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0437     unsigned int d, m, dout;
0438     unsigned long long tmp;
0439     unsigned int val;
0440 
0441     dout = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLKOUT0_1,
0442         MMCM_REG_CLKOUT0_2);
0443     m = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLK_FB1,
0444         MMCM_REG_CLK_FB2);
0445 
0446     axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, &val);
0447     if (val & MMCM_CLK_DIV_NOCOUNT)
0448         d = 1;
0449     else
0450         d = (val & 0x3f) + ((val >> 6) & 0x3f);
0451 
0452     if (d == 0 || dout == 0)
0453         return 0;
0454 
0455     tmp = (unsigned long long)parent_rate * m;
0456     tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);
0457 
0458     return min_t(unsigned long long, tmp, ULONG_MAX);
0459 }
0460 
0461 static int axi_clkgen_enable(struct clk_hw *clk_hw)
0462 {
0463     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0464 
0465     axi_clkgen_mmcm_enable(axi_clkgen, true);
0466 
0467     return 0;
0468 }
0469 
0470 static void axi_clkgen_disable(struct clk_hw *clk_hw)
0471 {
0472     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0473 
0474     axi_clkgen_mmcm_enable(axi_clkgen, false);
0475 }
0476 
0477 static int axi_clkgen_set_parent(struct clk_hw *clk_hw, u8 index)
0478 {
0479     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0480 
0481     axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, index);
0482 
0483     return 0;
0484 }
0485 
0486 static u8 axi_clkgen_get_parent(struct clk_hw *clk_hw)
0487 {
0488     struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
0489     unsigned int parent;
0490 
0491     axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, &parent);
0492 
0493     return parent;
0494 }
0495 
0496 static const struct clk_ops axi_clkgen_ops = {
0497     .recalc_rate = axi_clkgen_recalc_rate,
0498     .round_rate = axi_clkgen_round_rate,
0499     .set_rate = axi_clkgen_set_rate,
0500     .enable = axi_clkgen_enable,
0501     .disable = axi_clkgen_disable,
0502     .set_parent = axi_clkgen_set_parent,
0503     .get_parent = axi_clkgen_get_parent,
0504 };
0505 
0506 static int axi_clkgen_probe(struct platform_device *pdev)
0507 {
0508     const struct axi_clkgen_limits *dflt_limits;
0509     struct axi_clkgen *axi_clkgen;
0510     struct clk_init_data init;
0511     const char *parent_names[2];
0512     const char *clk_name;
0513     unsigned int i;
0514     int ret;
0515 
0516     dflt_limits = device_get_match_data(&pdev->dev);
0517     if (!dflt_limits)
0518         return -ENODEV;
0519 
0520     axi_clkgen = devm_kzalloc(&pdev->dev, sizeof(*axi_clkgen), GFP_KERNEL);
0521     if (!axi_clkgen)
0522         return -ENOMEM;
0523 
0524     axi_clkgen->base = devm_platform_ioremap_resource(pdev, 0);
0525     if (IS_ERR(axi_clkgen->base))
0526         return PTR_ERR(axi_clkgen->base);
0527 
0528     init.num_parents = of_clk_get_parent_count(pdev->dev.of_node);
0529     if (init.num_parents < 1 || init.num_parents > 2)
0530         return -EINVAL;
0531 
0532     for (i = 0; i < init.num_parents; i++) {
0533         parent_names[i] = of_clk_get_parent_name(pdev->dev.of_node, i);
0534         if (!parent_names[i])
0535             return -EINVAL;
0536     }
0537 
0538     memcpy(&axi_clkgen->limits, dflt_limits, sizeof(axi_clkgen->limits));
0539 
0540     clk_name = pdev->dev.of_node->name;
0541     of_property_read_string(pdev->dev.of_node, "clock-output-names",
0542         &clk_name);
0543 
0544     init.name = clk_name;
0545     init.ops = &axi_clkgen_ops;
0546     init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
0547     init.parent_names = parent_names;
0548 
0549     axi_clkgen_mmcm_enable(axi_clkgen, false);
0550 
0551     axi_clkgen->clk_hw.init = &init;
0552     ret = devm_clk_hw_register(&pdev->dev, &axi_clkgen->clk_hw);
0553     if (ret)
0554         return ret;
0555 
0556     return of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_simple_get,
0557                       &axi_clkgen->clk_hw);
0558 }
0559 
0560 static int axi_clkgen_remove(struct platform_device *pdev)
0561 {
0562     of_clk_del_provider(pdev->dev.of_node);
0563 
0564     return 0;
0565 }
0566 
0567 static const struct of_device_id axi_clkgen_ids[] = {
0568     {
0569         .compatible = "adi,zynqmp-axi-clkgen-2.00.a",
0570         .data = &axi_clkgen_zynqmp_default_limits,
0571     },
0572     {
0573         .compatible = "adi,axi-clkgen-2.00.a",
0574         .data = &axi_clkgen_zynq_default_limits,
0575     },
0576     { }
0577 };
0578 MODULE_DEVICE_TABLE(of, axi_clkgen_ids);
0579 
0580 static struct platform_driver axi_clkgen_driver = {
0581     .driver = {
0582         .name = "adi-axi-clkgen",
0583         .of_match_table = axi_clkgen_ids,
0584     },
0585     .probe = axi_clkgen_probe,
0586     .remove = axi_clkgen_remove,
0587 };
0588 module_platform_driver(axi_clkgen_driver);
0589 
0590 MODULE_LICENSE("GPL v2");
0591 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
0592 MODULE_DESCRIPTION("Driver for the Analog Devices' AXI clkgen pcore clock generator");