0001
0002
0003
0004
0005
0006 #include <linux/clk.h>
0007 #include <linux/devfreq.h>
0008 #include <linux/device.h>
0009 #include <linux/module.h>
0010 #include <linux/of_device.h>
0011 #include <linux/pm_opp.h>
0012 #include <linux/platform_device.h>
0013 #include <linux/slab.h>
0014
0015 struct imx_bus {
0016 struct devfreq_dev_profile profile;
0017 struct devfreq *devfreq;
0018 struct clk *clk;
0019 struct platform_device *icc_pdev;
0020 };
0021
0022 static int imx_bus_target(struct device *dev,
0023 unsigned long *freq, u32 flags)
0024 {
0025 struct dev_pm_opp *new_opp;
0026 int ret;
0027
0028 new_opp = devfreq_recommended_opp(dev, freq, flags);
0029 if (IS_ERR(new_opp)) {
0030 ret = PTR_ERR(new_opp);
0031 dev_err(dev, "failed to get recommended opp: %d\n", ret);
0032 return ret;
0033 }
0034 dev_pm_opp_put(new_opp);
0035
0036 return dev_pm_opp_set_rate(dev, *freq);
0037 }
0038
0039 static int imx_bus_get_cur_freq(struct device *dev, unsigned long *freq)
0040 {
0041 struct imx_bus *priv = dev_get_drvdata(dev);
0042
0043 *freq = clk_get_rate(priv->clk);
0044
0045 return 0;
0046 }
0047
0048 static void imx_bus_exit(struct device *dev)
0049 {
0050 struct imx_bus *priv = dev_get_drvdata(dev);
0051
0052 dev_pm_opp_of_remove_table(dev);
0053 platform_device_unregister(priv->icc_pdev);
0054 }
0055
0056
0057 static int imx_bus_init_icc(struct device *dev)
0058 {
0059 struct imx_bus *priv = dev_get_drvdata(dev);
0060 const char *icc_driver_name;
0061
0062 if (!of_get_property(dev->of_node, "#interconnect-cells", NULL))
0063 return 0;
0064 if (!IS_ENABLED(CONFIG_INTERCONNECT_IMX)) {
0065 dev_warn(dev, "imx interconnect drivers disabled\n");
0066 return 0;
0067 }
0068
0069 icc_driver_name = of_device_get_match_data(dev);
0070 if (!icc_driver_name) {
0071 dev_err(dev, "unknown interconnect driver\n");
0072 return 0;
0073 }
0074
0075 priv->icc_pdev = platform_device_register_data(
0076 dev, icc_driver_name, -1, NULL, 0);
0077 if (IS_ERR(priv->icc_pdev)) {
0078 dev_err(dev, "failed to register icc provider %s: %ld\n",
0079 icc_driver_name, PTR_ERR(priv->icc_pdev));
0080 return PTR_ERR(priv->icc_pdev);
0081 }
0082
0083 return 0;
0084 }
0085
0086 static int imx_bus_probe(struct platform_device *pdev)
0087 {
0088 struct device *dev = &pdev->dev;
0089 struct imx_bus *priv;
0090 const char *gov = DEVFREQ_GOV_USERSPACE;
0091 int ret;
0092
0093 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
0094 if (!priv)
0095 return -ENOMEM;
0096
0097
0098
0099
0100
0101
0102
0103
0104
0105
0106 priv->clk = devm_clk_get(dev, NULL);
0107 if (IS_ERR(priv->clk)) {
0108 ret = PTR_ERR(priv->clk);
0109 dev_err(dev, "failed to fetch clk: %d\n", ret);
0110 return ret;
0111 }
0112 platform_set_drvdata(pdev, priv);
0113
0114 ret = dev_pm_opp_of_add_table(dev);
0115 if (ret < 0) {
0116 dev_err(dev, "failed to get OPP table\n");
0117 return ret;
0118 }
0119
0120 priv->profile.target = imx_bus_target;
0121 priv->profile.exit = imx_bus_exit;
0122 priv->profile.get_cur_freq = imx_bus_get_cur_freq;
0123 priv->profile.initial_freq = clk_get_rate(priv->clk);
0124
0125 priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
0126 gov, NULL);
0127 if (IS_ERR(priv->devfreq)) {
0128 ret = PTR_ERR(priv->devfreq);
0129 dev_err(dev, "failed to add devfreq device: %d\n", ret);
0130 goto err;
0131 }
0132
0133 ret = imx_bus_init_icc(dev);
0134 if (ret)
0135 goto err;
0136
0137 return 0;
0138
0139 err:
0140 dev_pm_opp_of_remove_table(dev);
0141 return ret;
0142 }
0143
0144 static const struct of_device_id imx_bus_of_match[] = {
0145 { .compatible = "fsl,imx8mq-noc", .data = "imx8mq-interconnect", },
0146 { .compatible = "fsl,imx8mm-noc", .data = "imx8mm-interconnect", },
0147 { .compatible = "fsl,imx8mn-noc", .data = "imx8mn-interconnect", },
0148 { .compatible = "fsl,imx8mp-noc", .data = "imx8mp-interconnect", },
0149 { .compatible = "fsl,imx8m-noc", },
0150 { .compatible = "fsl,imx8m-nic", },
0151 { },
0152 };
0153 MODULE_DEVICE_TABLE(of, imx_bus_of_match);
0154
0155 static struct platform_driver imx_bus_platdrv = {
0156 .probe = imx_bus_probe,
0157 .driver = {
0158 .name = "imx-bus-devfreq",
0159 .of_match_table = imx_bus_of_match,
0160 },
0161 };
0162 module_platform_driver(imx_bus_platdrv);
0163
0164 MODULE_DESCRIPTION("Generic i.MX bus frequency scaling driver");
0165 MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
0166 MODULE_LICENSE("GPL v2");