root/drivers/clk/clk-loongson2.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Author: Yinbo Zhu <zhuyinbo@loongson.cn>
 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
 */

#include <linux/err.h>
#include <linux/init.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <dt-bindings/clock/loongson,ls2k-clk.h>

enum loongson2_clk_type {
        CLK_TYPE_PLL,
        CLK_TYPE_SCALE,
        CLK_TYPE_DIVIDER,
        CLK_TYPE_GATE,
        CLK_TYPE_FIXED,
        CLK_TYPE_NONE,
};

struct loongson2_clk_provider {
        void __iomem *base;
        struct device *dev;
        spinlock_t clk_lock;    /* protect access to DIV registers */

        /* Must be last --ends in a flexible-array member. */
        struct clk_hw_onecell_data clk_data;
};

struct loongson2_clk_data {
        struct clk_hw hw;
        void __iomem *reg;
        u8 div_shift;
        u8 div_width;
        u8 mult_shift;
        u8 mult_width;
        u8 bit_idx;
};

struct loongson2_clk_board_info {
        u8 id;
        enum loongson2_clk_type type;
        const char *name;
        const char *parent_name;
        unsigned long fixed_rate;
        unsigned long flags;
        u8 reg_offset;
        u8 div_shift;
        u8 div_width;
        u8 mult_shift;
        u8 mult_width;
        u8 bit_idx;
};

#define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth)  \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_DIVIDER,             \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .reg_offset     = _offset,                      \
                .div_shift      = _dshift,                      \
                .div_width      = _dwidth,                      \
        }

#define CLK_PLL(_id, _name, _offset, _mshift, _mwidth,          \
                _dshift, _dwidth)                               \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_PLL,                 \
                .name           = _name,                        \
                .parent_name    = NULL,                         \
                .reg_offset     = _offset,                      \
                .mult_shift     = _mshift,                      \
                .mult_width     = _mwidth,                      \
                .div_shift      = _dshift,                      \
                .div_width      = _dwidth,                      \
        }

#define CLK_SCALE(_id, _name, _pname, _offset,                  \
                  _dshift, _dwidth)                             \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_SCALE,               \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .reg_offset     = _offset,                      \
                .div_shift      = _dshift,                      \
                .div_width      = _dwidth,                      \
        }

#define CLK_SCALE_MODE(_id, _name, _pname, _offset,             \
                  _dshift, _dwidth, _midx)                      \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_SCALE,               \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .reg_offset     = _offset,                      \
                .div_shift      = _dshift,                      \
                .div_width      = _dwidth,                      \
                .bit_idx        = _midx + 1,                    \
        }

#define CLK_GATE(_id, _name, _pname, _offset, _bidx)            \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_GATE,                \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .reg_offset     = _offset,                      \
                .bit_idx        = _bidx,                        \
        }

#define CLK_GATE_FLAGS(_id, _name, _pname, _offset, _bidx,      \
                       _flags)                                  \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_GATE,                \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .reg_offset     = _offset,                      \
                .bit_idx        = _bidx,                        \
                .flags          = _flags                        \
        }

#define CLK_FIXED(_id, _name, _pname, _rate)                    \
        {                                                       \
                .id             = _id,                          \
                .type           = CLK_TYPE_FIXED,               \
                .name           = _name,                        \
                .parent_name    = _pname,                       \
                .fixed_rate     = _rate,                        \
        }

static const struct loongson2_clk_board_info ls2k0300_clks[] = {
        /* Reference Clock */
        CLK_PLL(LS2K0300_NODE_PLL, "pll_node",   0x00, 15, 9, 8, 7),
        CLK_PLL(LS2K0300_DDR_PLL,  "pll_ddr",    0x08, 15, 9, 8, 7),
        CLK_PLL(LS2K0300_PIX_PLL,  "pll_pix",    0x10, 15, 9, 8, 7),
        CLK_FIXED(LS2K0300_CLK_STABLE, "clk_stable", NULL, 100000000),
        CLK_FIXED(LS2K0300_CLK_THSENS, "clk_thsens", NULL, 10000000),
        /* Node PLL */
        CLK_DIV(LS2K0300_CLK_NODE_DIV, "clk_node_div", "pll_node", 0x00, 24, 7),
        CLK_DIV(LS2K0300_CLK_GMAC_DIV, "clk_gmac_div", "pll_node", 0x04, 0, 7),
        CLK_DIV(LS2K0300_CLK_I2S_DIV,  "clk_i2s_div",  "pll_node", 0x04, 8, 7),
        CLK_GATE(LS2K0300_CLK_NODE_PLL_GATE,   "clk_node_pll_gate", "clk_node_div", 0x00, 0),
        CLK_GATE(LS2K0300_CLK_GMAC_GATE,       "clk_gmac_gate",     "clk_gmac_div", 0x00, 1),
        CLK_GATE(LS2K0300_CLK_I2S_GATE,        "clk_i2s_gate",      "clk_i2s_div", 0x00, 2),
        CLK_GATE_FLAGS(LS2K0300_CLK_NODE_GATE, "clk_node_gate",     "clk_node_scale", 0x24, 0,
                       CLK_IS_CRITICAL),
        CLK_SCALE_MODE(LS2K0300_CLK_NODE_SCALE, "clk_node_scale", "clk_node_pll_gate", 0x20, 0, 3,
                       3),
        /* DDR PLL */
        CLK_DIV(LS2K0300_CLK_DDR_DIV, "clk_ddr_div", "pll_ddr", 0x08, 24, 7),
        CLK_DIV(LS2K0300_CLK_NET_DIV, "clk_net_div", "pll_ddr", 0x0c, 0, 7),
        CLK_DIV(LS2K0300_CLK_DEV_DIV, "clk_dev_div", "pll_ddr", 0x0c, 8, 7),
        CLK_GATE(LS2K0300_CLK_NET_GATE,         "clk_net_gate", "clk_net_div", 0x08, 1),
        CLK_GATE(LS2K0300_CLK_DEV_GATE,         "clk_dev_gate", "clk_dev_div", 0x08, 2),
        CLK_GATE_FLAGS(LS2K0300_CLK_DDR_GATE,   "clk_ddr_gate", "clk_ddr_div", 0x08, 0,
                       CLK_IS_CRITICAL),
        /* PIX PLL */
        CLK_DIV(LS2K0300_CLK_PIX_DIV,    "clk_pix_div",    "pll_pix", 0x10, 24, 7),
        CLK_DIV(LS2K0300_CLK_GMACBP_DIV, "clk_gmacbp_div", "pll_pix", 0x14, 0, 7),
        CLK_GATE(LS2K0300_CLK_PIX_PLL_GATE, "clk_pix_pll_gate", "clk_pix_div", 0x10, 0),
        CLK_GATE(LS2K0300_CLK_PIX_GATE,     "clk_pix_gate",     "clk_pix_scale", 0x24, 6),
        CLK_GATE(LS2K0300_CLK_GMACBP_GATE,  "clk_gmacbp_gate",  "clk_gmacbp_div", 0x10, 1),
        CLK_SCALE_MODE(LS2K0300_CLK_PIX_SCALE, "clk_pix_scale", "clk_pix_pll_gate", 0x20, 4, 3, 7),
        /* clk_dev_gate */
        CLK_DIV(LS2K0300_CLK_SDIO_SCALE, "clk_sdio_scale", "clk_dev_gate", 0x20, 24, 4),
        CLK_GATE(LS2K0300_CLK_USB_GATE,  "clk_usb_gate",        "clk_usb_scale", 0x24, 2),
        CLK_GATE(LS2K0300_CLK_SDIO_GATE, "clk_sdio_gate",       "clk_sdio_scale", 0x24, 4),
        CLK_GATE(LS2K0300_CLK_APB_GATE,  "clk_apb_gate",        "clk_apb_scale", 0x24, 3),
        CLK_GATE_FLAGS(LS2K0300_CLK_BOOT_GATE, "clk_boot_gate", "clk_boot_scale", 0x24, 1,
                       CLK_IS_CRITICAL),
        CLK_SCALE_MODE(LS2K0300_CLK_USB_SCALE,  "clk_usb_scale",  "clk_dev_gate", 0x20, 12, 3, 15),
        CLK_SCALE_MODE(LS2K0300_CLK_APB_SCALE,  "clk_apb_scale",  "clk_dev_gate", 0x20, 16, 3, 19),
        CLK_SCALE_MODE(LS2K0300_CLK_BOOT_SCALE, "clk_boot_scale", "clk_dev_gate", 0x20, 8, 3, 11),
};

static const struct loongson2_clk_board_info ls2k0500_clks[] = {
        CLK_PLL(LOONGSON2_NODE_PLL,   "pll_node", 0,    16, 8, 8, 6),
        CLK_PLL(LOONGSON2_DDR_PLL,    "pll_ddr",  0x8,  16, 8, 8, 6),
        CLK_PLL(LOONGSON2_DC_PLL,     "pll_soc",  0x10, 16, 8, 8, 6),
        CLK_PLL(LOONGSON2_PIX0_PLL,   "pll_pix0", 0x18, 16, 8, 8, 6),
        CLK_PLL(LOONGSON2_PIX1_PLL,   "pll_pix1", 0x20, 16, 8, 8, 6),
        CLK_DIV(LOONGSON2_NODE_CLK,   "clk_node", "pll_node", 0,    24, 6),
        CLK_DIV(LOONGSON2_DDR_CLK,    "clk_ddr",  "pll_ddr",  0x8,  24, 6),
        CLK_DIV(LOONGSON2_HDA_CLK,    "clk_hda",  "pll_ddr",  0xc,  8,  6),
        CLK_DIV(LOONGSON2_GPU_CLK,    "clk_gpu",  "pll_soc",  0x10, 24, 6),
        CLK_DIV(LOONGSON2_DC_CLK,     "clk_sb",   "pll_soc",  0x14, 0,  6),
        CLK_DIV(LOONGSON2_GMAC_CLK,   "clk_gmac", "pll_soc",  0x14, 8,  6),
        CLK_DIV(LOONGSON2_PIX0_CLK,   "clk_pix0", "pll_pix0", 0x18, 24, 6),
        CLK_DIV(LOONGSON2_PIX1_CLK,   "clk_pix1", "pll_pix1", 0x20, 24, 6),
        CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", "clk_sb",   0x28, 8,  3),
        CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_sb",   0x28, 12, 3),
        CLK_SCALE(LOONGSON2_USB_CLK,  "clk_usb",  "clk_sb",   0x28, 16, 3),
        CLK_SCALE(LOONGSON2_APB_CLK,  "clk_apb",  "clk_sb",   0x28, 20, 3),
        { /* Sentinel */ },
};

static const struct loongson2_clk_board_info ls2k1000_clks[] = {
        CLK_PLL(LOONGSON2_NODE_PLL,   "pll_node", 0,    32, 10, 26, 6),
        CLK_PLL(LOONGSON2_DDR_PLL,    "pll_ddr",  0x10, 32, 10, 26, 6),
        CLK_PLL(LOONGSON2_DC_PLL,     "pll_dc",   0x20, 32, 10, 26, 6),
        CLK_PLL(LOONGSON2_PIX0_PLL,   "pll_pix0", 0x30, 32, 10, 26, 6),
        CLK_PLL(LOONGSON2_PIX1_PLL,   "pll_pix1", 0x40, 32, 10, 26, 6),
        CLK_DIV(LOONGSON2_NODE_CLK,   "clk_node", "pll_node", 0x8,  0,  6),
        CLK_DIV(LOONGSON2_DDR_CLK,    "clk_ddr",  "pll_ddr",  0x18, 0,  6),
        CLK_DIV(LOONGSON2_GPU_CLK,    "clk_gpu",  "pll_ddr",  0x18, 22, 6),
        /*
         * The hda clk divisor in the upper 32bits and the clk-prodiver
         * layer code doesn't support 64bit io operation thus a conversion
         * is required that subtract shift by 32 and add 4byte to the hda
         * address
         */
        CLK_DIV(LOONGSON2_HDA_CLK,    "clk_hda",  "pll_ddr",  0x22, 12, 7),
        CLK_DIV(LOONGSON2_DC_CLK,     "clk_dc",   "pll_dc",   0x28, 0,  6),
        CLK_DIV(LOONGSON2_GMAC_CLK,   "clk_gmac", "pll_dc",   0x28, 22, 6),
        CLK_DIV(LOONGSON2_PIX0_CLK,   "clk_pix0", "pll_pix0", 0x38, 0,  6),
        CLK_DIV(LOONGSON2_PIX1_CLK,   "clk_pix1", "pll_pix1", 0x38, 0,  6),
        CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL,       0x50, 8,  3),
        CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_gmac", 0x50, 12, 3),
        CLK_SCALE(LOONGSON2_USB_CLK,  "clk_usb",  "clk_gmac", 0x50, 16, 3),
        CLK_SCALE(LOONGSON2_APB_CLK,  "clk_apb",  "clk_gmac", 0x50, 20, 3),
        { /* Sentinel */ },
};

static const struct loongson2_clk_board_info ls2k2000_clks[] = {
        CLK_PLL(LOONGSON2_DC_PLL,     "pll_0",    0,    21, 9, 32, 6),
        CLK_PLL(LOONGSON2_DDR_PLL,    "pll_1",    0x10, 21, 9, 32, 6),
        CLK_PLL(LOONGSON2_NODE_PLL,   "pll_2",    0x20, 21, 9, 32, 6),
        CLK_PLL(LOONGSON2_PIX0_PLL,   "pll_pix0", 0x30, 21, 9, 32, 6),
        CLK_PLL(LOONGSON2_PIX1_PLL,   "pll_pix1", 0x40, 21, 9, 32, 6),
        CLK_GATE(LOONGSON2_OUT0_GATE, "out0_gate", "pll_0",    0,    40),
        CLK_GATE(LOONGSON2_GMAC_GATE, "gmac_gate", "pll_0",    0,    41),
        CLK_GATE(LOONGSON2_RIO_GATE,  "rio_gate",  "pll_0",    0,    42),
        CLK_GATE(LOONGSON2_DC_GATE,   "dc_gate",   "pll_1",    0x10, 40),
        CLK_GATE(LOONGSON2_DDR_GATE,  "ddr_gate",  "pll_1",    0x10, 41),
        CLK_GATE(LOONGSON2_GPU_GATE,  "gpu_gate",  "pll_1",    0x10, 42),
        CLK_GATE(LOONGSON2_HDA_GATE,  "hda_gate",  "pll_2",    0x20, 40),
        CLK_GATE(LOONGSON2_NODE_GATE, "node_gate", "pll_2",    0x20, 41),
        CLK_GATE(LOONGSON2_EMMC_GATE, "emmc_gate", "pll_2",    0x20, 42),
        CLK_GATE(LOONGSON2_PIX0_GATE, "pix0_gate", "pll_pix0", 0x30, 40),
        CLK_GATE(LOONGSON2_PIX1_GATE, "pix1_gate", "pll_pix1", 0x40, 40),
        CLK_DIV(LOONGSON2_OUT0_CLK,   "clk_out0", "out0_gate", 0,    0,  6),
        CLK_DIV(LOONGSON2_GMAC_CLK,   "clk_gmac", "gmac_gate", 0,    7,  6),
        CLK_DIV(LOONGSON2_RIO_CLK,    "clk_rio",  "rio_gate",  0,    14, 6),
        CLK_DIV(LOONGSON2_DC_CLK,     "clk_dc",   "dc_gate",   0x10, 0,  6),
        CLK_DIV(LOONGSON2_GPU_CLK,    "clk_gpu",  "gpu_gate",  0x10, 7,  6),
        CLK_DIV(LOONGSON2_DDR_CLK,    "clk_ddr",  "ddr_gate",  0x10, 14, 6),
        CLK_DIV(LOONGSON2_HDA_CLK,    "clk_hda",  "hda_gate",  0x20, 0,  6),
        CLK_DIV(LOONGSON2_NODE_CLK,   "clk_node", "node_gate", 0x20, 7,  6),
        CLK_DIV(LOONGSON2_EMMC_CLK,   "clk_emmc", "emmc_gate", 0x20, 14, 6),
        CLK_DIV(LOONGSON2_PIX0_CLK,   "clk_pix0", "pll_pix0",  0x30, 0,  6),
        CLK_DIV(LOONGSON2_PIX1_CLK,   "clk_pix1", "pll_pix1",  0x40, 0,  6),
        CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_out0",  0x50, 12, 3),
        CLK_SCALE(LOONGSON2_USB_CLK,  "clk_usb",  "clk_out0",  0x50, 16, 3),
        CLK_SCALE(LOONGSON2_APB_CLK,  "clk_apb",  "clk_node",  0x50, 20, 3),
        CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL,        0x50, 23, 3),
        CLK_SCALE(LOONGSON2_DES_CLK,  "clk_des",  "clk_node",  0x50, 40, 3),
        CLK_SCALE(LOONGSON2_I2S_CLK,  "clk_i2s",  "clk_node",  0x50, 44, 3),
        CLK_FIXED(LOONGSON2_MISC_CLK, "clk_misc", NULL, 50000000),
        { /* Sentinel */ },
};

static inline struct loongson2_clk_data *to_loongson2_clk(struct clk_hw *hw)
{
        return container_of(hw, struct loongson2_clk_data, hw);
}

static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)
{
        return (val & GENMASK(shift + width - 1, shift)) >> shift;
}

static unsigned long loongson2_pll_recalc_rate(struct clk_hw *hw,
                                               unsigned long parent_rate)
{
        u64 val, mult, div;
        struct loongson2_clk_data *clk = to_loongson2_clk(hw);

        val  = readq(clk->reg);
        mult = loongson2_rate_part(val, clk->mult_shift, clk->mult_width);
        div  = loongson2_rate_part(val, clk->div_shift,  clk->div_width);

        return div_u64((u64)parent_rate * mult, div);
}

static const struct clk_ops loongson2_pll_recalc_ops = {
        .recalc_rate = loongson2_pll_recalc_rate,
};

static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw *hw,
                                                     unsigned long parent_rate)
{
        u64 val, scale;
        u32 mode = 0;
        struct loongson2_clk_data *clk = to_loongson2_clk(hw);

        val  = readq(clk->reg);
        scale = loongson2_rate_part(val, clk->div_shift, clk->div_width) + 1;

        if (clk->bit_idx)
                mode = val & BIT(clk->bit_idx - 1);

        return mode == 0 ? div_u64((u64)parent_rate * scale, 8) :
                           div_u64((u64)parent_rate, scale);
}

static const struct clk_ops loongson2_freqscale_recalc_ops = {
        .recalc_rate = loongson2_freqscale_recalc_rate,
};

static struct clk_hw *loongson2_clk_register(const char *parent,
                                             struct loongson2_clk_provider *clp,
                                             const struct loongson2_clk_board_info *cld,
                                             const struct clk_ops *ops)
{
        int ret;
        struct clk_hw *hw;
        struct loongson2_clk_data *clk;
        struct clk_init_data init = { };

        clk = devm_kzalloc(clp->dev, sizeof(*clk), GFP_KERNEL);
        if (!clk)
                return ERR_PTR(-ENOMEM);

        init.name  = cld->name;
        init.ops   = ops;
        init.flags = 0;
        init.num_parents = 1;
        init.parent_names = &parent;

        clk->reg        = clp->base + cld->reg_offset;
        clk->div_shift  = cld->div_shift;
        clk->div_width  = cld->div_width;
        clk->mult_shift = cld->mult_shift;
        clk->mult_width = cld->mult_width;
        clk->bit_idx    = cld->bit_idx;
        clk->hw.init    = &init;

        hw = &clk->hw;
        ret = devm_clk_hw_register(clp->dev, hw);
        if (ret)
                clk = ERR_PTR(ret);

        return hw;
}

static int loongson2_clk_probe(struct platform_device *pdev)
{
        int i, clks_num = 0;
        struct clk_hw *hw;
        struct device *dev = &pdev->dev;
        struct loongson2_clk_provider *clp;
        const struct loongson2_clk_board_info *p, *data;
        const char *refclk_name, *parent_name;

        data = device_get_match_data(dev);
        if (!data)
                return -EINVAL;

        refclk_name = of_clk_get_parent_name(dev->of_node, 0);
        if (IS_ERR(refclk_name))
                return dev_err_probe(dev, PTR_ERR(refclk_name),
                                     "failed to get refclk name\n");

        for (p = data; p->name; p++)
                clks_num = max(clks_num, p->id + 1);

        clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
                           GFP_KERNEL);
        if (!clp)
                return -ENOMEM;

        clp->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(clp->base))
                return PTR_ERR(clp->base);

        spin_lock_init(&clp->clk_lock);
        clp->clk_data.num = clks_num;
        clp->dev = dev;

        /* Avoid returning NULL for unused id */
        memset_p((void **)clp->clk_data.hws, ERR_PTR(-ENOENT), clks_num);

        for (i = 0; i < clks_num; i++) {
                p = &data[i];
                parent_name = p->parent_name ? p->parent_name : refclk_name;

                switch (p->type) {
                case CLK_TYPE_PLL:
                        hw = loongson2_clk_register(parent_name, clp, p,
                                                    &loongson2_pll_recalc_ops);
                        break;
                case CLK_TYPE_SCALE:
                        hw = loongson2_clk_register(parent_name, clp, p,
                                                    &loongson2_freqscale_recalc_ops);
                        break;
                case CLK_TYPE_DIVIDER:
                        hw = devm_clk_hw_register_divider(dev, p->name,
                                                          parent_name, 0,
                                                          clp->base + p->reg_offset,
                                                          p->div_shift, p->div_width,
                                                          CLK_DIVIDER_ONE_BASED |
                                                          CLK_DIVIDER_ALLOW_ZERO,
                                                          &clp->clk_lock);
                        break;
                case CLK_TYPE_GATE:
                        hw = devm_clk_hw_register_gate(dev, p->name, parent_name,
                                                       p->flags,
                                                       clp->base + p->reg_offset,
                                                       p->bit_idx, 0,
                                                       &clp->clk_lock);
                        break;
                case CLK_TYPE_FIXED:
                        hw = devm_clk_hw_register_fixed_rate(dev, p->name, parent_name,
                                                             0, p->fixed_rate);
                        break;
                default:
                        return dev_err_probe(dev, -EINVAL, "Invalid clk type\n");
                }

                if (IS_ERR(hw))
                        return dev_err_probe(dev, PTR_ERR(hw),
                                             "Register clk: %s, type: %u failed!\n",
                                             p->name, p->type);

                clp->clk_data.hws[p->id] = hw;
        }

        return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clp->clk_data);
}

static const struct of_device_id loongson2_clk_match_table[] = {
        { .compatible = "loongson,ls2k0300-clk", .data = &ls2k0300_clks },
        { .compatible = "loongson,ls2k0500-clk", .data = &ls2k0500_clks },
        { .compatible = "loongson,ls2k-clk", .data = &ls2k1000_clks },
        { .compatible = "loongson,ls2k2000-clk", .data = &ls2k2000_clks },
        { }
};
MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);

static struct platform_driver loongson2_clk_driver = {
        .probe  = loongson2_clk_probe,
        .driver = {
                .name   = "loongson2-clk",
                .of_match_table = loongson2_clk_match_table,
        },
};
module_platform_driver(loongson2_clk_driver);

MODULE_DESCRIPTION("Loongson2 clock driver");
MODULE_AUTHOR("Loongson Technology Corporation Limited");
MODULE_LICENSE("GPL");