@@ -9,6 +9,7 @@
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/regulator/consumer.h>
+#include <linux/interconnect.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic.h>
@@ -225,6 +226,10 @@ struct simpledrm_device {
struct device **pwr_dom_devs;
struct device_link **pwr_dom_links;
#endif
+#if defined CONFIG_OF && defined CONFIG_INTERCONNECT
+ unsigned int icc_count;
+ struct icc_path **icc_paths;
+#endif
/* modesetting */
u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)];
@@ -547,6 +552,81 @@ static int simpledrm_device_attach_genpd(struct simpledrm_device *sdev)
}
#endif
+#if defined CONFIG_OF && defined CONFIG_PM_GENERIC_DOMAINS
+/*
+ * Generic interconnect path handling code.
+ */
+static void simpledrm_device_detach_icc(void *res)
+{
+ struct simpledrm_device *sdev = res;
+ int i;
+
+ for (i = sdev->icc_count - 1; i >= 0; i--) {
+ if (!IS_ERR_OR_NULL(sdev->icc_paths[i]))
+ icc_put(sdev->icc_paths[i]);
+ }
+}
+
+static int simpledrm_device_attach_icc(struct simpledrm_device *sdev)
+{
+ struct device *dev = sdev->sysfb.dev.dev;
+ int ret, count, i;
+
+ count = of_count_phandle_with_args(dev->of_node, "interconnects",
+ "#interconnect-cells");
+ if (count < 0)
+ return 0;
+
+ /* An interconnect path consists of two elements */
+ if (count % 2) {
+ drm_err(&sdev->sysfb.dev,
+ "invalid interconnects value\n");
+ return -EINVAL;
+ }
+ sdev->icc_count = count / 2;
+
+ sdev->icc_paths = devm_kcalloc(dev, sdev->icc_count,
+ sizeof(*sdev->icc_paths),
+ GFP_KERNEL);
+ if (!sdev->icc_paths)
+ return -ENOMEM;
+
+ for (i = 0; i < sdev->icc_count; i++) {
+ sdev->icc_paths[i] = of_icc_get_by_index(dev, i);
+ if (IS_ERR_OR_NULL(sdev->icc_paths[i])) {
+ ret = PTR_ERR(sdev->icc_paths[i]);
+ if (ret == -EPROBE_DEFER)
+ goto err;
+ drm_err(&sdev->sysfb.dev, "failed to get interconnect path %u: %d\n",
+ i, ret);
+ continue;
+ }
+
+ ret = icc_set_bw(sdev->icc_paths[i], 0, UINT_MAX);
+ if (ret) {
+ drm_err(&sdev->sysfb.dev, "failed to set interconnect bandwidth %u: %d\n",
+ i, ret);
+ continue;
+ }
+ }
+
+ return devm_add_action_or_reset(dev, simpledrm_device_detach_icc, sdev);
+
+err:
+ while (i) {
+ --i;
+ if (!IS_ERR_OR_NULL(sdev->icc_paths[i]))
+ icc_put(sdev->icc_paths[i]);
+ }
+ return ret;
+}
+#else
+static int simpledrm_device_attach_icc(struct simpledrm_device *sdev)
+{
+ return 0;
+}
+#endif
+
/*
* Modesetting
*/
@@ -633,6 +713,9 @@ static struct simpledrm_device *simpledrm_device_create(struct drm_driver *drv,
if (ret)
return ERR_PTR(ret);
ret = simpledrm_device_attach_genpd(sdev);
+ if (ret)
+ return ERR_PTR(ret);
+ ret = simpledrm_device_attach_icc(sdev);
if (ret)
return ERR_PTR(ret);
Some devices might require keeping an interconnect path alive so that the framebuffer continues working. Add support for that by setting the bandwidth requirements appropriately for all provided interconnect paths. Signed-off-by: Luca Weiss <luca.weiss@fairphone.com> --- drivers/gpu/drm/sysfb/simpledrm.c | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+)