Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
0003 
0004 
0005 #include <linux/bitfield.h>
0006 #include <linux/component.h>
0007 #include <linux/device.h>
0008 #include <linux/io.h>
0009 #include <linux/module.h>
0010 #include <linux/of_device.h>
0011 #include <linux/of_graph.h>
0012 #include <linux/platform_device.h>
0013 
0014 #include <dt-bindings/clock/sun8i-tcon-top.h>
0015 
0016 #include "sun8i_tcon_top.h"
0017 
0018 struct sun8i_tcon_top_quirks {
0019     bool has_tcon_tv1;
0020     bool has_dsi;
0021 };
0022 
0023 static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node)
0024 {
0025     return !!of_match_node(sun8i_tcon_top_of_table, node);
0026 }
0027 
0028 int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon)
0029 {
0030     struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
0031     unsigned long flags;
0032     u32 val;
0033 
0034     if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
0035         dev_err(dev, "Device is not TCON TOP!\n");
0036         return -EINVAL;
0037     }
0038 
0039     if (tcon < 2 || tcon > 3) {
0040         dev_err(dev, "TCON index must be 2 or 3!\n");
0041         return -EINVAL;
0042     }
0043 
0044     spin_lock_irqsave(&tcon_top->reg_lock, flags);
0045 
0046     val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG);
0047     val &= ~TCON_TOP_HDMI_SRC_MSK;
0048     val |= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, tcon - 1);
0049     writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG);
0050 
0051     spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
0052 
0053     return 0;
0054 }
0055 EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src);
0056 
0057 int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon)
0058 {
0059     struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
0060     unsigned long flags;
0061     u32 reg;
0062 
0063     if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
0064         dev_err(dev, "Device is not TCON TOP!\n");
0065         return -EINVAL;
0066     }
0067 
0068     if (mixer > 1) {
0069         dev_err(dev, "Mixer index is too high!\n");
0070         return -EINVAL;
0071     }
0072 
0073     if (tcon > 3) {
0074         dev_err(dev, "TCON index is too high!\n");
0075         return -EINVAL;
0076     }
0077 
0078     spin_lock_irqsave(&tcon_top->reg_lock, flags);
0079 
0080     reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG);
0081     if (mixer == 0) {
0082         reg &= ~TCON_TOP_PORT_DE0_MSK;
0083         reg |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, tcon);
0084     } else {
0085         reg &= ~TCON_TOP_PORT_DE1_MSK;
0086         reg |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, tcon);
0087     }
0088     writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG);
0089 
0090     spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
0091 
0092     return 0;
0093 }
0094 EXPORT_SYMBOL(sun8i_tcon_top_de_config);
0095 
0096 
0097 static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
0098                            const char *parent,
0099                            void __iomem *regs,
0100                            spinlock_t *lock,
0101                            u8 bit, int name_index)
0102 {
0103     const char *clk_name, *parent_name;
0104     int ret, index;
0105 
0106     index = of_property_match_string(dev->of_node, "clock-names", parent);
0107     if (index < 0)
0108         return ERR_PTR(index);
0109 
0110     parent_name = of_clk_get_parent_name(dev->of_node, index);
0111 
0112     ret = of_property_read_string_index(dev->of_node,
0113                         "clock-output-names", name_index,
0114                         &clk_name);
0115     if (ret)
0116         return ERR_PTR(ret);
0117 
0118     return clk_hw_register_gate(dev, clk_name, parent_name,
0119                     CLK_SET_RATE_PARENT,
0120                     regs + TCON_TOP_GATE_SRC_REG,
0121                     bit, 0, lock);
0122 };
0123 
0124 static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
0125                    void *data)
0126 {
0127     struct platform_device *pdev = to_platform_device(dev);
0128     struct clk_hw_onecell_data *clk_data;
0129     struct sun8i_tcon_top *tcon_top;
0130     const struct sun8i_tcon_top_quirks *quirks;
0131     void __iomem *regs;
0132     int ret, i;
0133 
0134     quirks = of_device_get_match_data(&pdev->dev);
0135 
0136     tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
0137     if (!tcon_top)
0138         return -ENOMEM;
0139 
0140     clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, CLK_NUM),
0141                 GFP_KERNEL);
0142     if (!clk_data)
0143         return -ENOMEM;
0144     tcon_top->clk_data = clk_data;
0145 
0146     spin_lock_init(&tcon_top->reg_lock);
0147 
0148     tcon_top->rst = devm_reset_control_get(dev, NULL);
0149     if (IS_ERR(tcon_top->rst)) {
0150         dev_err(dev, "Couldn't get our reset line\n");
0151         return PTR_ERR(tcon_top->rst);
0152     }
0153 
0154     tcon_top->bus = devm_clk_get(dev, "bus");
0155     if (IS_ERR(tcon_top->bus)) {
0156         dev_err(dev, "Couldn't get the bus clock\n");
0157         return PTR_ERR(tcon_top->bus);
0158     }
0159 
0160     regs = devm_platform_ioremap_resource(pdev, 0);
0161     tcon_top->regs = regs;
0162     if (IS_ERR(regs))
0163         return PTR_ERR(regs);
0164 
0165     ret = reset_control_deassert(tcon_top->rst);
0166     if (ret) {
0167         dev_err(dev, "Could not deassert ctrl reset control\n");
0168         return ret;
0169     }
0170 
0171     ret = clk_prepare_enable(tcon_top->bus);
0172     if (ret) {
0173         dev_err(dev, "Could not enable bus clock\n");
0174         goto err_assert_reset;
0175     }
0176 
0177     /*
0178      * At least on H6, some registers have some bits set by default
0179      * which may cause issues. Clear them here.
0180      */
0181     writel(0, regs + TCON_TOP_PORT_SEL_REG);
0182     writel(0, regs + TCON_TOP_GATE_SRC_REG);
0183 
0184     /*
0185      * TCON TOP has two muxes, which select parent clock for each TCON TV
0186      * channel clock. Parent could be either TCON TV or TVE clock. For now
0187      * we leave this fixed to TCON TV, since TVE driver for R40 is not yet
0188      * implemented. Once it is, graph needs to be traversed to determine
0189      * if TVE is active on each TCON TV. If it is, mux should be switched
0190      * to TVE clock parent.
0191      */
0192     i = 0;
0193     clk_data->hws[CLK_TCON_TOP_TV0] =
0194         sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs,
0195                          &tcon_top->reg_lock,
0196                          TCON_TOP_TCON_TV0_GATE, i++);
0197 
0198     if (quirks->has_tcon_tv1)
0199         clk_data->hws[CLK_TCON_TOP_TV1] =
0200             sun8i_tcon_top_register_gate(dev, "tcon-tv1", regs,
0201                              &tcon_top->reg_lock,
0202                              TCON_TOP_TCON_TV1_GATE, i++);
0203 
0204     if (quirks->has_dsi)
0205         clk_data->hws[CLK_TCON_TOP_DSI] =
0206             sun8i_tcon_top_register_gate(dev, "dsi", regs,
0207                              &tcon_top->reg_lock,
0208                              TCON_TOP_TCON_DSI_GATE, i++);
0209 
0210     for (i = 0; i < CLK_NUM; i++)
0211         if (IS_ERR(clk_data->hws[i])) {
0212             ret = PTR_ERR(clk_data->hws[i]);
0213             goto err_unregister_gates;
0214         }
0215 
0216     clk_data->num = CLK_NUM;
0217 
0218     ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
0219                      clk_data);
0220     if (ret)
0221         goto err_unregister_gates;
0222 
0223     dev_set_drvdata(dev, tcon_top);
0224 
0225     return 0;
0226 
0227 err_unregister_gates:
0228     for (i = 0; i < CLK_NUM; i++)
0229         if (!IS_ERR_OR_NULL(clk_data->hws[i]))
0230             clk_hw_unregister_gate(clk_data->hws[i]);
0231     clk_disable_unprepare(tcon_top->bus);
0232 err_assert_reset:
0233     reset_control_assert(tcon_top->rst);
0234 
0235     return ret;
0236 }
0237 
0238 static void sun8i_tcon_top_unbind(struct device *dev, struct device *master,
0239                   void *data)
0240 {
0241     struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
0242     struct clk_hw_onecell_data *clk_data = tcon_top->clk_data;
0243     int i;
0244 
0245     of_clk_del_provider(dev->of_node);
0246     for (i = 0; i < CLK_NUM; i++)
0247         if (clk_data->hws[i])
0248             clk_hw_unregister_gate(clk_data->hws[i]);
0249 
0250     clk_disable_unprepare(tcon_top->bus);
0251     reset_control_assert(tcon_top->rst);
0252 }
0253 
0254 static const struct component_ops sun8i_tcon_top_ops = {
0255     .bind   = sun8i_tcon_top_bind,
0256     .unbind = sun8i_tcon_top_unbind,
0257 };
0258 
0259 static int sun8i_tcon_top_probe(struct platform_device *pdev)
0260 {
0261     return component_add(&pdev->dev, &sun8i_tcon_top_ops);
0262 }
0263 
0264 static int sun8i_tcon_top_remove(struct platform_device *pdev)
0265 {
0266     component_del(&pdev->dev, &sun8i_tcon_top_ops);
0267 
0268     return 0;
0269 }
0270 
0271 static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks = {
0272     .has_tcon_tv1   = true,
0273     .has_dsi    = true,
0274 };
0275 
0276 static const struct sun8i_tcon_top_quirks sun20i_d1_tcon_top_quirks = {
0277     .has_dsi    = true,
0278 };
0279 
0280 static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks = {
0281     /* Nothing special */
0282 };
0283 
0284 /* sun4i_drv uses this list to check if a device node is a TCON TOP */
0285 const struct of_device_id sun8i_tcon_top_of_table[] = {
0286     {
0287         .compatible = "allwinner,sun8i-r40-tcon-top",
0288         .data = &sun8i_r40_tcon_top_quirks
0289     },
0290     {
0291         .compatible = "allwinner,sun20i-d1-tcon-top",
0292         .data = &sun20i_d1_tcon_top_quirks
0293     },
0294     {
0295         .compatible = "allwinner,sun50i-h6-tcon-top",
0296         .data = &sun50i_h6_tcon_top_quirks
0297     },
0298     { /* sentinel */ }
0299 };
0300 MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table);
0301 EXPORT_SYMBOL(sun8i_tcon_top_of_table);
0302 
0303 static struct platform_driver sun8i_tcon_top_platform_driver = {
0304     .probe      = sun8i_tcon_top_probe,
0305     .remove     = sun8i_tcon_top_remove,
0306     .driver     = {
0307         .name       = "sun8i-tcon-top",
0308         .of_match_table = sun8i_tcon_top_of_table,
0309     },
0310 };
0311 module_platform_driver(sun8i_tcon_top_platform_driver);
0312 
0313 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
0314 MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
0315 MODULE_LICENSE("GPL");