/* * Copyright (c) 2017 Rockchip Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include "clk-regmap.h" #define RK618_CRU_CLKSEL0 0x0058 #define RK618_CRU_CLKSEL1 0x005c #define RK618_CRU_CLKSEL2 0x0060 #define RK618_CRU_CLKSEL3 0x0064 #define RK618_CRU_PLL0_CON0 0x0068 #define RK618_CRU_PLL0_CON1 0x006c #define RK618_CRU_PLL0_CON2 0x0070 #define RK618_CRU_PLL1_CON0 0x0074 #define RK618_CRU_PLL1_CON1 0x0078 #define RK618_CRU_PLL1_CON2 0x007c enum { LCDC0_CLK = 1, LCDC1_CLK, VIF_PLLIN_CLK, SCALER_PLLIN_CLK, VIF_PLL_CLK, SCALER_PLL_CLK, VIF0_CLK, VIF1_CLK, SCALER_IN_CLK, SCALER_CLK, DITHER_CLK, HDMI_CLK, MIPI_CLK, LVDS_CLK, LVTTL_CLK, RGB_CLK, VIF0_PRE_CLK, VIF1_PRE_CLK, CODEC_CLK, NR_CLKS, }; struct rk618_cru { struct device *dev; struct rk618 *parent; struct regmap *regmap; struct clk_onecell_data clk_data; }; static char clkin_name[32] = "dummy"; static char lcdc0_dclkp_name[32] = "dummy"; static char lcdc1_dclkp_name[32] = "dummy"; #define PNAME(x) static const char *const x[] PNAME(mux_pll_in_p) = { "lcdc0_clk", "lcdc1_clk", clkin_name }; PNAME(mux_pll_src_p) = { "vif_pll_clk", "scaler_pll_clk", }; PNAME(mux_scaler_in_src_p) = { "vif0_clk", "vif1_clk" }; PNAME(mux_hdmi_src_p) = { "vif1_clk", "scaler_clk", "vif0_clk" }; PNAME(mux_dither_src_p) = { "vif0_clk", "scaler_clk" }; PNAME(mux_vif0_src_p) = { "vif0_pre_clk", lcdc0_dclkp_name }; PNAME(mux_vif1_src_p) = { "vif1_pre_clk", lcdc1_dclkp_name }; PNAME(mux_codec_src_p) = { "codec_pre_clk", clkin_name }; /* Two PLL, one for dual datarate input logic, the other for scaler */ static const struct clk_pll_data rk618_clk_plls[] = { RK618_PLL(VIF_PLL_CLK, "vif_pll_clk", "vif_pllin_clk", RK618_CRU_PLL0_CON0, 0), RK618_PLL(SCALER_PLL_CLK, "scaler_pll_clk", "scaler_pllin_clk", RK618_CRU_PLL1_CON0, 0), }; static const struct clk_mux_data rk618_clk_muxes[] = { MUX(VIF_PLLIN_CLK, "vif_pllin_clk", mux_pll_in_p, RK618_CRU_CLKSEL0, 6, 2, 0), MUX(SCALER_PLLIN_CLK, "scaler_pllin_clk", mux_pll_in_p, RK618_CRU_CLKSEL0, 8, 2, 0), MUX(SCALER_IN_CLK, "scaler_in_clk", mux_scaler_in_src_p, RK618_CRU_CLKSEL3, 15, 1, 0), MUX(DITHER_CLK, "dither_clk", mux_dither_src_p, RK618_CRU_CLKSEL3, 14, 1, 0), MUX(VIF0_CLK, "vif0_clk", mux_vif0_src_p, RK618_CRU_CLKSEL3, 1, 1, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), MUX(VIF1_CLK, "vif1_clk", mux_vif1_src_p, RK618_CRU_CLKSEL3, 7, 1, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), MUX(CODEC_CLK, "codec_clk", mux_codec_src_p, RK618_CRU_CLKSEL1, 1, 1, CLK_SET_RATE_PARENT), }; static const struct clk_divider_data rk618_clk_dividers[] = { DIV(LCDC0_CLK, "lcdc0_clk", lcdc0_dclkp_name, RK618_CRU_CLKSEL0, 0, 3, 0), DIV(LCDC1_CLK, "lcdc1_clk", lcdc1_dclkp_name, RK618_CRU_CLKSEL0, 3, 3, 0), }; static const struct clk_gate_data rk618_clk_gates[] = { GATE(MIPI_CLK, "mipi_clk", "dither_clk", RK618_CRU_CLKSEL1, 10, CLK_IGNORE_UNUSED), GATE(LVDS_CLK, "lvds_clk", "dither_clk", RK618_CRU_CLKSEL1, 9, CLK_IGNORE_UNUSED), GATE(LVTTL_CLK, "lvttl_clk", "dither_clk", RK618_CRU_CLKSEL1, 12, 0), GATE(RGB_CLK, "rgb_clk", "dither_clk", RK618_CRU_CLKSEL1, 11, 0), }; static const struct clk_composite_data rk618_clk_composites[] = { COMPOSITE(SCALER_CLK, "scaler_clk", mux_pll_src_p, RK618_CRU_CLKSEL1, 3, 1, RK618_CRU_CLKSEL1, 5, 3, RK618_CRU_CLKSEL1, 4, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), COMPOSITE_NODIV(HDMI_CLK, "hdmi_clk", mux_hdmi_src_p, RK618_CRU_CLKSEL3, 12, 2, RK618_CRU_CLKSEL1, 8, 0), COMPOSITE(VIF0_PRE_CLK, "vif0_pre_clk", mux_pll_src_p, RK618_CRU_CLKSEL3, 0, 1, RK618_CRU_CLKSEL3, 3, 3, RK618_CRU_CLKSEL3, 2, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), COMPOSITE(VIF1_PRE_CLK, "vif1_pre_clk", mux_pll_src_p, RK618_CRU_CLKSEL3, 6, 1, RK618_CRU_CLKSEL3, 9, 3, RK618_CRU_CLKSEL3, 8, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT), COMPOSITE_FRAC_NOGATE(0, "codec_pre_clk", mux_pll_src_p, RK618_CRU_CLKSEL1, 0, 1, RK618_CRU_CLKSEL2, 0), }; static void rk618_clk_add_lookup(struct rk618_cru *cru, struct clk *clk, unsigned int id) { if (cru->clk_data.clks && id) cru->clk_data.clks[id] = clk; } static void rk618_clk_register_muxes(struct rk618_cru *cru) { struct clk *clk; unsigned int i; for (i = 0; i < ARRAY_SIZE(rk618_clk_muxes); i++) { const struct clk_mux_data *data = &rk618_clk_muxes[i]; clk = devm_clk_regmap_register_mux(cru->dev, data->name, data->parent_names, data->num_parents, cru->regmap, data->reg, data->shift, data->width, data->flags); if (IS_ERR(clk)) { dev_err(cru->dev, "failed to register clock %s\n", data->name); continue; } rk618_clk_add_lookup(cru, clk, data->id); } } static void rk618_clk_register_dividers(struct rk618_cru *cru) { struct clk *clk; unsigned int i; for (i = 0; i < ARRAY_SIZE(rk618_clk_dividers); i++) { const struct clk_divider_data *data = &rk618_clk_dividers[i]; clk = devm_clk_regmap_register_divider(cru->dev, data->name, data->parent_name, cru->regmap, data->reg, data->shift, data->width, data->flags); if (IS_ERR(clk)) { dev_err(cru->dev, "failed to register clock %s\n", data->name); continue; } rk618_clk_add_lookup(cru, clk, data->id); } } static void rk618_clk_register_gates(struct rk618_cru *cru) { struct clk *clk; unsigned int i; for (i = 0; i < ARRAY_SIZE(rk618_clk_gates); i++) { const struct clk_gate_data *data = &rk618_clk_gates[i]; clk = devm_clk_regmap_register_gate(cru->dev, data->name, data->parent_name, cru->regmap, data->reg, data->shift, data->flags); if (IS_ERR(clk)) { dev_err(cru->dev, "failed to register clock %s\n", data->name); continue; } rk618_clk_add_lookup(cru, clk, data->id); } } static void rk618_clk_register_composites(struct rk618_cru *cru) { struct clk *clk; unsigned int i; for (i = 0; i < ARRAY_SIZE(rk618_clk_composites); i++) { const struct clk_composite_data *data = &rk618_clk_composites[i]; clk = devm_clk_regmap_register_composite(cru->dev, data->name, data->parent_names, data->num_parents, cru->regmap, data->mux_reg, data->mux_shift, data->mux_width, data->div_reg, data->div_shift, data->div_width, data->div_flags, data->gate_reg, data->gate_shift, data->flags); if (IS_ERR(clk)) { dev_err(cru->dev, "failed to register clock %s\n", data->name); continue; } rk618_clk_add_lookup(cru, clk, data->id); } } static void rk618_clk_register_plls(struct rk618_cru *cru) { struct clk *clk; unsigned int i; for (i = 0; i < ARRAY_SIZE(rk618_clk_plls); i++) { const struct clk_pll_data *data = &rk618_clk_plls[i]; clk = devm_clk_regmap_register_pll(cru->dev, data->name, data->parent_name, cru->regmap, data->reg, data->pd_shift, data->dsmpd_shift, data->lock_shift, data->flags); if (IS_ERR(clk)) { dev_err(cru->dev, "failed to register clock %s\n", data->name); continue; } rk618_clk_add_lookup(cru, clk, data->id); } } static int rk618_cru_probe(struct platform_device *pdev) { struct rk618 *rk618 = dev_get_drvdata(pdev->dev.parent); struct device *dev = &pdev->dev; struct rk618_cru *cru; struct clk **clk_table; const char *parent_name; struct clk *clk; int ret, i; if (!of_device_is_available(dev->of_node)) return -ENODEV; cru = devm_kzalloc(dev, sizeof(*cru), GFP_KERNEL); if (!cru) return -ENOMEM; clk_table = devm_kcalloc(dev, NR_CLKS, sizeof(struct clk *), GFP_KERNEL); if (!clk_table) return -ENOMEM; for (i = 0; i < NR_CLKS; i++) clk_table[i] = ERR_PTR(-ENOENT); cru->dev = dev; cru->parent = rk618; cru->regmap = rk618->regmap; cru->clk_data.clks = clk_table; cru->clk_data.clk_num = NR_CLKS; platform_set_drvdata(pdev, cru); clk = devm_clk_get(dev, "clkin"); if (IS_ERR(clk)) { ret = PTR_ERR(clk); dev_err(dev, "failed to get clkin: %d\n", ret); return ret; } strlcpy(clkin_name, __clk_get_name(clk), sizeof(clkin_name)); clk = devm_clk_get(dev, "lcdc0_dclkp"); if (IS_ERR(clk)) { if (PTR_ERR(clk) != -ENOENT) { ret = PTR_ERR(clk); dev_err(dev, "failed to get lcdc0_dclkp: %d\n", ret); return ret; } clk = NULL; } parent_name = __clk_get_name(clk); if (parent_name) strlcpy(lcdc0_dclkp_name, parent_name, sizeof(lcdc0_dclkp_name)); clk = devm_clk_get(dev, "lcdc1_dclkp"); if (IS_ERR(clk)) { if (PTR_ERR(clk) != -ENOENT) { ret = PTR_ERR(clk); dev_err(dev, "failed to get lcdc1_dclkp: %d\n", ret); return ret; } clk = NULL; } parent_name = __clk_get_name(clk); if (parent_name) strlcpy(lcdc1_dclkp_name, parent_name, sizeof(lcdc1_dclkp_name)); rk618_clk_register_plls(cru); rk618_clk_register_muxes(cru); rk618_clk_register_dividers(cru); rk618_clk_register_gates(cru); rk618_clk_register_composites(cru); return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, &cru->clk_data); } static int rk618_cru_remove(struct platform_device *pdev) { of_clk_del_provider(pdev->dev.of_node); return 0; } static const struct of_device_id rk618_cru_of_match[] = { { .compatible = "rockchip,rk618-cru", }, {}, }; MODULE_DEVICE_TABLE(of, rk618_cru_of_match); static struct platform_driver rk618_cru_driver = { .driver = { .name = "rk618-cru", .of_match_table = of_match_ptr(rk618_cru_of_match), }, .probe = rk618_cru_probe, .remove = rk618_cru_remove, }; module_platform_driver(rk618_cru_driver); MODULE_AUTHOR("Wyon Bi "); MODULE_DESCRIPTION("Rockchip rk618 CRU driver"); MODULE_LICENSE("GPL v2");