| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. |
| * Author: James.Qian.Wang <james.qian.wang@arm.com> |
| * |
| */ |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/platform_device.h> |
| #include <linux/component.h> |
| #include <linux/pm_runtime.h> |
| #include <drm/drm_of.h> |
| #include "komeda_dev.h" |
| #include "komeda_kms.h" |
| |
| struct komeda_drv { |
| struct komeda_dev *mdev; |
| struct komeda_kms_dev *kms; |
| }; |
| |
| struct komeda_dev *dev_to_mdev(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| |
| return mdrv ? mdrv->mdev : NULL; |
| } |
| |
| static void komeda_unbind(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| |
| if (!mdrv) |
| return; |
| |
| komeda_kms_detach(mdrv->kms); |
| |
| if (pm_runtime_enabled(dev)) |
| pm_runtime_disable(dev); |
| else |
| komeda_dev_suspend(mdrv->mdev); |
| |
| komeda_dev_destroy(mdrv->mdev); |
| |
| dev_set_drvdata(dev, NULL); |
| devm_kfree(dev, mdrv); |
| } |
| |
| static int komeda_bind(struct device *dev) |
| { |
| struct komeda_drv *mdrv; |
| int err; |
| |
| mdrv = devm_kzalloc(dev, sizeof(*mdrv), GFP_KERNEL); |
| if (!mdrv) |
| return -ENOMEM; |
| |
| mdrv->mdev = komeda_dev_create(dev); |
| if (IS_ERR(mdrv->mdev)) { |
| err = PTR_ERR(mdrv->mdev); |
| goto free_mdrv; |
| } |
| |
| pm_runtime_enable(dev); |
| if (!pm_runtime_enabled(dev)) |
| komeda_dev_resume(mdrv->mdev); |
| |
| mdrv->kms = komeda_kms_attach(mdrv->mdev); |
| if (IS_ERR(mdrv->kms)) { |
| err = PTR_ERR(mdrv->kms); |
| goto destroy_mdev; |
| } |
| |
| dev_set_drvdata(dev, mdrv); |
| |
| return 0; |
| |
| destroy_mdev: |
| if (pm_runtime_enabled(dev)) |
| pm_runtime_disable(dev); |
| else |
| komeda_dev_suspend(mdrv->mdev); |
| |
| komeda_dev_destroy(mdrv->mdev); |
| |
| free_mdrv: |
| devm_kfree(dev, mdrv); |
| return err; |
| } |
| |
| static const struct component_master_ops komeda_master_ops = { |
| .bind = komeda_bind, |
| .unbind = komeda_unbind, |
| }; |
| |
| static int compare_of(struct device *dev, void *data) |
| { |
| return dev->of_node == data; |
| } |
| |
| static void komeda_add_slave(struct device *master, |
| struct component_match **match, |
| struct device_node *np, |
| u32 port, u32 endpoint) |
| { |
| struct device_node *remote; |
| |
| remote = of_graph_get_remote_node(np, port, endpoint); |
| if (remote) { |
| drm_of_component_match_add(master, match, compare_of, remote); |
| of_node_put(remote); |
| } |
| } |
| |
| static int komeda_platform_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct component_match *match = NULL; |
| struct device_node *child; |
| |
| if (!dev->of_node) |
| return -ENODEV; |
| |
| for_each_available_child_of_node(dev->of_node, child) { |
| if (of_node_cmp(child->name, "pipeline") != 0) |
| continue; |
| |
| /* add connector */ |
| komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 0); |
| komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 1); |
| } |
| |
| return component_master_add_with_match(dev, &komeda_master_ops, match); |
| } |
| |
| static int komeda_platform_remove(struct platform_device *pdev) |
| { |
| component_master_del(&pdev->dev, &komeda_master_ops); |
| return 0; |
| } |
| |
| static const struct of_device_id komeda_of_match[] = { |
| { .compatible = "arm,mali-d71", .data = d71_identify, }, |
| { .compatible = "arm,mali-d32", .data = d71_identify, }, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, komeda_of_match); |
| |
| static int __maybe_unused komeda_rt_pm_suspend(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| |
| return komeda_dev_suspend(mdrv->mdev); |
| } |
| |
| static int __maybe_unused komeda_rt_pm_resume(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| |
| return komeda_dev_resume(mdrv->mdev); |
| } |
| |
| static int __maybe_unused komeda_pm_suspend(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| int res; |
| |
| res = drm_mode_config_helper_suspend(&mdrv->kms->base); |
| |
| if (!pm_runtime_status_suspended(dev)) |
| komeda_dev_suspend(mdrv->mdev); |
| |
| return res; |
| } |
| |
| static int __maybe_unused komeda_pm_resume(struct device *dev) |
| { |
| struct komeda_drv *mdrv = dev_get_drvdata(dev); |
| |
| if (!pm_runtime_status_suspended(dev)) |
| komeda_dev_resume(mdrv->mdev); |
| |
| return drm_mode_config_helper_resume(&mdrv->kms->base); |
| } |
| |
| static const struct dev_pm_ops komeda_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(komeda_pm_suspend, komeda_pm_resume) |
| SET_RUNTIME_PM_OPS(komeda_rt_pm_suspend, komeda_rt_pm_resume, NULL) |
| }; |
| |
| static struct platform_driver komeda_platform_driver = { |
| .probe = komeda_platform_probe, |
| .remove = komeda_platform_remove, |
| .driver = { |
| .name = "komeda", |
| .of_match_table = komeda_of_match, |
| .pm = &komeda_pm_ops, |
| }, |
| }; |
| |
| module_platform_driver(komeda_platform_driver); |
| |
| MODULE_AUTHOR("James.Qian.Wang <james.qian.wang@arm.com>"); |
| MODULE_DESCRIPTION("Komeda KMS driver"); |
| MODULE_LICENSE("GPL v2"); |