| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2020 DisplayLink (UK) 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "evdi_platform_dev.h" |
| #include <linux/dma-mapping.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| |
| #include "evdi_platform_drv.h" |
| #include "evdi_debug.h" |
| #include "evdi_drm_drv.h" |
| |
| struct evdi_platform_device_data { |
| struct drm_device *drm_dev; |
| struct device *parent; |
| bool symlinked; |
| }; |
| |
| struct platform_device *evdi_platform_dev_create(struct platform_device_info *info) |
| { |
| struct platform_device *platform_dev = NULL; |
| |
| platform_dev = platform_device_register_full(info); |
| if (dma_set_mask(&platform_dev->dev, DMA_BIT_MASK(64))) { |
| EVDI_WARN("Unable to change dma mask to 64 bit. "); |
| EVDI_WARN("Sticking with 32 bit\n"); |
| } |
| |
| EVDI_INFO("Evdi platform_device create\n"); |
| |
| return platform_dev; |
| } |
| |
| void evdi_platform_dev_destroy(struct platform_device *dev) |
| { |
| platform_device_unregister(dev); |
| EVDI_INFO("Evdi platform_device destroy\n"); |
| } |
| |
| int evdi_platform_device_probe(struct platform_device *pdev) |
| { |
| struct drm_device *dev; |
| struct evdi_platform_device_data *data; |
| |
| EVDI_CHECKPT(); |
| data = kzalloc(sizeof(struct evdi_platform_device_data), GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| dev = evdi_drm_device_create(&pdev->dev); |
| if (IS_ERR_OR_NULL(dev)) |
| goto err_free; |
| |
| data->drm_dev = dev; |
| data->symlinked = false; |
| platform_set_drvdata(pdev, data); |
| return PTR_ERR_OR_ZERO(dev); |
| |
| err_free: |
| kfree(data); |
| return PTR_ERR_OR_ZERO(dev); |
| } |
| |
| int evdi_platform_device_remove(struct platform_device *pdev) |
| { |
| struct evdi_platform_device_data *data = platform_get_drvdata(pdev); |
| |
| EVDI_CHECKPT(); |
| |
| evdi_drm_device_remove(data->drm_dev); |
| kfree(data); |
| return 0; |
| } |
| |
| bool evdi_platform_device_is_free(struct platform_device *pdev) |
| { |
| struct evdi_platform_device_data *data = platform_get_drvdata(pdev); |
| struct evdi_device *evdi = data->drm_dev->dev_private; |
| |
| if (evdi && !evdi_painter_is_connected(evdi->painter) && |
| !data->symlinked) |
| return true; |
| return false; |
| } |
| |
| void evdi_platform_device_link(struct platform_device *pdev, |
| struct device *parent) |
| { |
| struct evdi_platform_device_data *data = NULL; |
| int ret = 0; |
| |
| if (!parent || !pdev) |
| return; |
| |
| data = platform_get_drvdata(pdev); |
| if (!evdi_platform_device_is_free(pdev)) { |
| EVDI_FATAL("Device is already attached can't symlink again\n"); |
| return; |
| } |
| |
| ret = sysfs_create_link(&pdev->dev.kobj, &parent->kobj, "device"); |
| if (ret) { |
| EVDI_FATAL("Failed to create sysfs link from evdi to parent device\n"); |
| } else { |
| data->symlinked = true; |
| data->parent = parent; |
| } |
| } |
| |
| void evdi_platform_device_unlink_if_linked_with(struct platform_device *pdev, |
| struct device *parent) |
| { |
| struct evdi_platform_device_data *data = platform_get_drvdata(pdev); |
| |
| if (parent && data->parent == parent) { |
| sysfs_remove_link(&pdev->dev.kobj, "device"); |
| data->symlinked = false; |
| data->parent = NULL; |
| EVDI_INFO("Detached from parent device\n"); |
| } |
| } |