diff options
Diffstat (limited to 'drivers/media/platform/imx8/mxc-media-dev.c')
-rw-r--r-- | drivers/media/platform/imx8/mxc-media-dev.c | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/drivers/media/platform/imx8/mxc-media-dev.c b/drivers/media/platform/imx8/mxc-media-dev.c new file mode 100644 index 000000000000..ff20a9b8e520 --- /dev/null +++ b/drivers/media/platform/imx8/mxc-media-dev.c @@ -0,0 +1,710 @@ +/* + * Copyright 2017-2018 NXP + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/bug.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-of.h> +#include <media/media-device.h> + +#include "mxc-media-dev.h" +#include "mxc-isi-core.h" +#include "mxc-mipi-csi2.h" +#include "mxc-parallel-csi.h" + +/*create default links between registered entities */ +static int mxc_md_create_links(struct mxc_md *mxc_md) +{ + struct media_entity *source, *sink; + struct mxc_isi_dev *mxc_isi; + struct mxc_sensor_info *sensor; + struct mxc_mipi_csi2_dev *mipi_csi2; + struct mxc_parallel_csi_dev *pcsidev; + int i, j, ret = 0; + u16 source_pad, sink_pad; + u32 flags; + u32 mipi_vc = 0; + + /* Create links between each ISI's subdev and video node */ + flags = MEDIA_LNK_FL_ENABLED; + for (i = 0; i < MXC_ISI_MAX_DEVS; i++) { + mxc_isi = mxc_md->mxc_isi[i]; + if (!mxc_isi) + continue; + + /* Connect ISI source to video device */ + source = &mxc_isi->isi_cap.sd.entity; + sink = &mxc_isi->isi_cap.vdev.entity; + sink_pad = 0; + + switch (mxc_isi->interface[OUT_PORT]) { + case ISI_OUTPUT_INTERFACE_DC0: + source_pad = MXC_ISI_SD_PAD_SOURCE_DC0; + break; + case ISI_OUTPUT_INTERFACE_DC1: + source_pad = MXC_ISI_SD_PAD_SOURCE_DC1; + break; + case ISI_OUTPUT_INTERFACE_MEM: + source_pad = MXC_ISI_SD_PAD_SOURCE_MEM; + break; + default: + v4l2_err(&mxc_md->v4l2_dev, "Wrong output interface: %x\n", + mxc_isi->interface[OUT_PORT]); + return -EINVAL; + } + + ret = media_create_pad_link(source, source_pad, + sink, sink_pad, flags); + if (ret) { + v4l2_err(&mxc_md->v4l2_dev, "Failed created link [%s] %c> [%s]\n", + source->name, flags ? '=' : '-', sink->name); + break; + } + + /* Notify capture subdev entity ,ISI cap link setup */ + ret = media_entity_call(source, link_setup, &source->pads[source_pad], + &sink->pads[sink_pad], flags); + if (ret) { + v4l2_err(&mxc_md->v4l2_dev, "failed call link_setup [%s] %c> [%s]\n", + source->name, flags ? '=' : '-', sink->name); + break; + } + + v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n", + source->name, flags ? '=' : '-', sink->name); + + /* Connect MIPI/HDMI/Mem source to ISI sink */ + sink = &mxc_isi->isi_cap.sd.entity; + + switch (mxc_isi->interface[IN_PORT]) { + + case ISI_INPUT_INTERFACE_MIPI0_CSI2: + if (mxc_md->mipi_csi2[0] == NULL) + continue; + source = &mxc_md->mipi_csi2[0]->sd.entity; + + switch (mxc_isi->interface[SUB_IN_PORT]) { + case ISI_INPUT_SUB_INTERFACE_VC1: + source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC1; + break; + case ISI_INPUT_SUB_INTERFACE_VC2: + source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC2; + break; + case ISI_INPUT_SUB_INTERFACE_VC3: + source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC3; + break; + default: + source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC0; + break; + } + break; + + case ISI_INPUT_INTERFACE_MIPI1_CSI2: + if (mxc_md->mipi_csi2[1] == NULL) + continue; + source = &mxc_md->mipi_csi2[1]->sd.entity; + + switch (mxc_isi->interface[SUB_IN_PORT]) { + case ISI_INPUT_SUB_INTERFACE_VC1: + source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC1; + break; + case ISI_INPUT_SUB_INTERFACE_VC2: + source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC2; + break; + case ISI_INPUT_SUB_INTERFACE_VC3: + source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC3; + break; + default: + source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC0; + break; + } + break; + case ISI_INPUT_INTERFACE_PARALLEL_CSI: + if (mxc_md->pcsidev == NULL) + continue; + source = &mxc_md->pcsidev->sd.entity; + source_pad = MXC_PARALLEL_CSI_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_PARALLEL_CSI; + break; + + case ISI_INPUT_INTERFACE_HDMI: + if (mxc_md->hdmi_rx == NULL) + continue; + source = &mxc_md->hdmi_rx->sd.entity; + source_pad = MXC_HDMI_RX_PAD_SOURCE; + sink_pad = MXC_ISI_SD_PAD_SINK_HDMI; + break; + + case ISI_INPUT_INTERFACE_DC0: + case ISI_INPUT_INTERFACE_DC1: + case ISI_INPUT_INTERFACE_MEM: + default: + v4l2_err(&mxc_md->v4l2_dev, "Not support input interface: %x\n", + mxc_isi->interface[IN_PORT]); + return -EINVAL; + } + /* Create link MIPI/HDMI to ISI */ + ret = media_create_pad_link(source, source_pad, sink, sink_pad, flags); + if (ret) { + v4l2_err(&mxc_md->v4l2_dev, "created link [%s] %c> [%s] fail\n", + source->name, flags ? '=' : '-', sink->name); + break; + } + + /* Notify ISI subdev entity */ + ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad], + &source->pads[source_pad], 0); + if (ret) + break; + + /* Notify MIPI/HDMI entity */ + ret = media_entity_call(source, link_setup, &source->pads[source_pad], + &sink->pads[sink_pad], 0); + if (ret) + break; + + v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n", + source->name, flags ? '=' : '-', sink->name); + } + + /* Connect MIPI Sensor to MIPI CSI2 */ + for (i = 0; i < mxc_md->num_sensors; i++) { + sensor = &mxc_md->sensor[i]; + if (sensor == NULL || sensor->sd == NULL) + continue; + + if (mxc_md->parallel_csi && !sensor->mipi_mode) { + pcsidev = mxc_md->pcsidev; + if (pcsidev == NULL) + continue; + source = &sensor->sd->entity; + sink = &pcsidev->sd.entity; + + source_pad = 0; + sink_pad = MXC_PARALLEL_CSI_PAD_SINK; + + ret = media_create_pad_link(source, source_pad, sink, sink_pad, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + /* Notify MIPI subdev entity */ + ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad], + &source->pads[source_pad], 0); + if (ret) + return ret; + + /* Notify MIPI sensor subdev entity */ + ret = media_entity_call(source, link_setup, &source->pads[source_pad], + &sink->pads[sink_pad], 0); + if (ret) + return ret; + v4l2_info(&mxc_md->v4l2_dev, "created link [%s] => [%s]\n", + sensor->sd->entity.name, pcsidev->sd.entity.name); + } else if (mxc_md->mipi_csi2) { + mipi_csi2 = mxc_md->mipi_csi2[sensor->id]; + if (mipi_csi2 == NULL) + continue; + source = &sensor->sd->entity; + sink = &mipi_csi2->sd.entity; + + source_pad = 0; /* sensor source pad: MIPI_CSI2_SENS_VC0_PAD_SOURCE */ + sink_pad = source_pad; /* mipi sink pad: MXC_MIPI_CSI2_VC0_PAD_SINK; */ + + if (mipi_csi2->vchannel == true) + mipi_vc = 4; + else + mipi_vc = 1; + + for (j = 0; j < mipi_vc; j++) { + ret = media_create_pad_link(source, source_pad + j, sink, sink_pad + j, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + /* Notify MIPI subdev entity */ + ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad + j], + &source->pads[source_pad + j], 0); + if (ret) + return ret; + + /* Notify MIPI sensor subdev entity */ + ret = media_entity_call(source, link_setup, &source->pads[source_pad + j], + &sink->pads[sink_pad + j], 0); + if (ret) + return ret; + } + v4l2_info(&mxc_md->v4l2_dev, "created link [%s] => [%s]\n", + sensor->sd->entity.name, mipi_csi2->sd.entity.name); + } + } + dev_info(&mxc_md->pdev->dev, "%s\n", __func__); + return 0; +} + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct mxc_md *mxc_md = notifier_to_mxc_md(notifier); + struct mxc_sensor_info *sensor = NULL; + int i; + + dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__); + /* Find platform data for this sensor subdev */ + for (i = 0; i < ARRAY_SIZE(mxc_md->sensor); i++) { + if (mxc_md->sensor[i].asd.match.of.node == sd->dev->of_node) + sensor = &mxc_md->sensor[i]; + } + + if (sensor == NULL) + return -EINVAL; + + sd->grp_id = GRP_ID_MXC_SENSOR; + + sensor->sd = sd; + + mxc_md->num_sensors++; + + v4l2_info(&mxc_md->v4l2_dev, "Registered sensor subdevice: %s (%d)\n", + sd->name, mxc_md->num_sensors); + + return 0; +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct mxc_md *mxc_md = notifier_to_mxc_md(notifier); + int ret; + + dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__); + mutex_lock(&mxc_md->media_dev.graph_mutex); + + ret = mxc_md_create_links(mxc_md); + if (ret < 0) + goto unlock; + + ret = v4l2_device_register_subdev_nodes(&mxc_md->v4l2_dev); +unlock: + mutex_unlock(&mxc_md->media_dev.graph_mutex); + if (ret < 0) { + v4l2_err(&mxc_md->v4l2_dev, "%s error exit\n", __func__); + return ret; + } + + return media_device_register(&mxc_md->media_dev); +} + + +/** + * mxc_sensor_notify - v4l2_device notification from a sensor subdev + */ +void mxc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification, + void *arg) +{ + return; +} + +/* Register mipi sensor / Parallel CSI / HDMI Rx sub-devices */ +static int register_sensor_entities(struct mxc_md *mxc_md) +{ + struct device_node *parent = mxc_md->pdev->dev.of_node; + struct device_node *node, *ep, *rem; + struct v4l2_of_endpoint endpoint; + int index = 0; + + mxc_md->num_sensors = 0; + + /* Attach sensors linked to MIPI CSI2 / paralle csi / HDMI Rx */ + for_each_available_child_of_node(parent, node) { + struct device_node *port; + + if (!of_node_cmp(node->name, "hdmi_rx")) { + mxc_md->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF; + mxc_md->sensor[index].asd.match.of.node = node; + mxc_md->async_subdevs[index] = &mxc_md->sensor[index].asd; + + mxc_md->num_sensors++; + index++; + continue; + } + + if (of_node_cmp(node->name, "csi") && + of_node_cmp(node->name, "pcsi")) + continue; + + if (!of_device_is_available(node)) + continue; + + /* csi2 node have only port */ + port = of_get_next_child(node, NULL); + if (!port) + continue; + + /* port can have only endpoint */ + ep = of_get_next_child(port, NULL); + if (!ep) + return -EINVAL; + + v4l2_of_parse_endpoint(ep, &endpoint); + if (WARN_ON(endpoint.base.port >= MXC_MAX_SENSORS)) { + v4l2_err(&mxc_md->v4l2_dev, "Failed to get sensor endpoint\n"); + return -EINVAL; + } + + mxc_md->sensor[index].id = endpoint.base.port; + + if (!of_node_cmp(node->name, "csi")) + mxc_md->sensor[index].mipi_mode = true; + + /* remote port---sensor node */ + rem = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (rem == NULL) { + v4l2_info(&mxc_md->v4l2_dev, "Remote device at %s not found\n", + ep->full_name); + continue; + } + + mxc_md->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF; + mxc_md->sensor[index].asd.match.of.node = rem; + mxc_md->async_subdevs[index] = &mxc_md->sensor[index].asd; + + mxc_md->num_sensors++; + + index++; + } + + return 0; +} + +static int register_isi_entity(struct mxc_md *mxc_md, struct mxc_isi_dev *mxc_isi) +{ + struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd; + int ret; + + dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__); + if (WARN_ON(mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_md->mxc_isi[mxc_isi->id])) + return -EBUSY; + + sd->grp_id = GRP_ID_MXC_ISI; + + ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd); + if (!ret) + mxc_md->mxc_isi[mxc_isi->id] = mxc_isi; + else + v4l2_err(&mxc_md->v4l2_dev, "Failed to register ISI.%d (%d)\n", + mxc_isi->id, ret); + return ret; +} + +static int register_mipi_csi2_entity(struct mxc_md *mxc_md, + struct mxc_mipi_csi2_dev *mipi_csi2) +{ + struct v4l2_subdev *sd = &mipi_csi2->sd; + int ret; + + if (WARN_ON(mipi_csi2->id >= MXC_MIPI_CSI2_MAX_DEVS)) + return -ENOENT; + + sd->grp_id = GRP_ID_MXC_MIPI_CSI2; + ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd); + if (!ret) + mxc_md->mipi_csi2[mipi_csi2->id] = mipi_csi2; + else + v4l2_err(&mxc_md->v4l2_dev, + "Failed to register MIPI-CSIS.%d (%d)\n", mipi_csi2->id, ret); + return ret; +} + +static int register_parallel_csi_entity(struct mxc_md *mxc_md, + struct mxc_parallel_csi_dev *pcsidev) +{ + struct v4l2_subdev *sd = &pcsidev->sd; + int ret; + + sd->grp_id = GRP_ID_MXC_PARALLEL_CSI; + ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd); + if (!ret) + mxc_md->pcsidev = pcsidev; + else + v4l2_err(&mxc_md->v4l2_dev, + "Failed to register PARALLEL CSI ret=(%d)\n", ret); + return ret; +} + +static int register_hdmi_rx_entity(struct mxc_md *mxc_md, + struct mxc_hdmi_rx_dev *hdmi_rx) +{ + struct v4l2_subdev *sd = &hdmi_rx->sd;; + + dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__); + sd->grp_id = GRP_ID_MXC_HDMI_RX; + mxc_md->hdmi_rx = hdmi_rx; + + return 0; +} + +static int mxc_md_register_platform_entity(struct mxc_md *mxc_md, + struct platform_device *pdev, + int plat_entity) +{ + struct device *dev = &pdev->dev; + int ret = -EPROBE_DEFER; + void *drvdata; + + /* Lock to ensure dev->driver won't change. */ + device_lock(dev); + + if (!dev->driver || !try_module_get(dev->driver->owner)) + goto dev_unlock; + + drvdata = dev_get_drvdata(dev); + /* Some subdev didn't probe successfully id drvdata is NULL */ + if (drvdata) { + switch (plat_entity) { + case IDX_ISI: + ret = register_isi_entity(mxc_md, drvdata); + break; + case IDX_MIPI_CSI2: + ret = register_mipi_csi2_entity(mxc_md, drvdata); + break; + case IDX_PARALLEL_CSI: + ret = register_parallel_csi_entity(mxc_md, drvdata); + break; + case IDX_HDMI_RX: + ret = register_hdmi_rx_entity(mxc_md, drvdata); + break; + default: + ret = -ENODEV; + } + } + module_put(dev->driver->owner); + +dev_unlock: + device_unlock(dev); + if (ret == -EPROBE_DEFER) + dev_info(&mxc_md->pdev->dev, "deferring %s device registration\n", + dev_name(dev)); + else if (ret < 0) + dev_err(&mxc_md->pdev->dev, "%s device registration failed (%d)\n", + dev_name(dev), ret); + + return ret; +} + +/* Register ISI, MIPI CSI2 and HDMI Rx Media entities */ +static int mxc_md_register_platform_entities(struct mxc_md *mxc_md, + struct device_node *parent) +{ + struct device_node *node; + int ret = 0; + + for_each_available_child_of_node(parent, node) { + struct platform_device *pdev; + int plat_entity = -1; + + pdev = of_find_device_by_node(node); + if (!pdev) + continue; + + /* If driver of any entity isn't ready try all again later. */ + if (!strcmp(node->name, ISI_OF_NODE_NAME)) + plat_entity = IDX_ISI; + else if (!strcmp(node->name, MIPI_CSI2_OF_NODE_NAME)) + plat_entity = IDX_MIPI_CSI2; + else if (!strcmp(node->name, PARALLEL_CSI_OF_NODE_NAME)) + plat_entity = IDX_PARALLEL_CSI; + else if (!strcmp(node->name, MXC_HDMI_RX_NODE_NAME)) + plat_entity = IDX_HDMI_RX; + + if (plat_entity >= 0) + ret = mxc_md_register_platform_entity(mxc_md, pdev, + plat_entity); + put_device(&pdev->dev); + if (ret < 0) + break; + } + + return ret; +} + +static void mxc_md_unregister_entities(struct mxc_md *mxc_md) +{ + int i; + + for (i = 0; i < MXC_ISI_MAX_DEVS; i++) { + struct mxc_isi_dev *dev = mxc_md->mxc_isi[i]; + if (dev == NULL) + continue; + v4l2_device_unregister_subdev(&dev->isi_cap.sd); + mxc_md->mxc_isi[i] = NULL; + } + for (i = 0; i < MXC_MIPI_CSI2_MAX_DEVS; i++) { + if (mxc_md->mipi_csi2[i] == NULL) + continue; + v4l2_device_unregister_subdev(&mxc_md->mipi_csi2[i]->sd); + mxc_md->mipi_csi2[i] = NULL; + } + + if (mxc_md->pcsidev) { + v4l2_device_unregister_subdev(&mxc_md->pcsidev->sd); + mxc_md->pcsidev = NULL; + } + + v4l2_info(&mxc_md->v4l2_dev, "Unregistered all entities\n"); +} + +static int mxc_md_link_notify(struct media_link *link, unsigned int flags, + unsigned int notification) +{ + return 0; +} + +static const struct media_device_ops mxc_md_ops = { + .link_notify = mxc_md_link_notify, +}; + + +static int mxc_md_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev; + struct mxc_md *mxc_md; + int ret; + + mxc_md = devm_kzalloc(dev, sizeof(*mxc_md), GFP_KERNEL); + if (!mxc_md) + return -ENOMEM; + + mxc_md->pdev = pdev; + platform_set_drvdata(pdev, mxc_md); + + mxc_md->parallel_csi = of_property_read_bool(dev->of_node, "parallel_csi"); + + /* register media device */ + strlcpy(mxc_md->media_dev.model, "FSL Capture Media Deivce", + sizeof(mxc_md->media_dev.model)); + mxc_md->media_dev.ops = &mxc_md_ops; + mxc_md->media_dev.dev = dev; + + /* register v4l2 device */ + v4l2_dev = &mxc_md->v4l2_dev; + v4l2_dev->mdev = &mxc_md->media_dev; + v4l2_dev->notify = mxc_sensor_notify; + strlcpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name)); + + media_device_init(&mxc_md->media_dev); + + ret = v4l2_device_register(dev, &mxc_md->v4l2_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret); + goto err_md; + } + + ret = mxc_md_register_platform_entities(mxc_md, dev->of_node); + if (ret < 0) + goto err_v4l2_dev; + + ret = register_sensor_entities(mxc_md); + if (ret < 0) + goto err_m_ent; + + if (mxc_md->num_sensors > 0) { + mxc_md->subdev_notifier.subdevs = mxc_md->async_subdevs; + mxc_md->subdev_notifier.num_subdevs = mxc_md->num_sensors; + mxc_md->subdev_notifier.bound = subdev_notifier_bound; + mxc_md->subdev_notifier.complete = subdev_notifier_complete; + mxc_md->num_sensors = 0; + + ret = v4l2_async_notifier_register(&mxc_md->v4l2_dev, + &mxc_md->subdev_notifier); + if (ret < 0) { + dev_warn(&mxc_md->pdev->dev, "Sensor register failed\n"); + goto err_m_ent; + } + } + + return 0; + +err_m_ent: + mxc_md_unregister_entities(mxc_md); +err_v4l2_dev: + v4l2_device_unregister(&mxc_md->v4l2_dev); +err_md: + media_device_cleanup(&mxc_md->media_dev); + return ret; +} + +static int mxc_md_remove(struct platform_device *pdev) +{ + struct mxc_md *mxc_md = platform_get_drvdata(pdev); + + if (!mxc_md) + return 0; + + v4l2_async_notifier_unregister(&mxc_md->subdev_notifier); + + v4l2_device_unregister(&mxc_md->v4l2_dev); + mxc_md_unregister_entities(mxc_md); + media_device_unregister(&mxc_md->media_dev); + media_device_cleanup(&mxc_md->media_dev); + + return 0; +} + +static const struct of_device_id mxc_md_of_match[] = { + { .compatible = "fsl,mxc-md",}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mxc_md_of_match); + + +static struct platform_driver mxc_md_driver = { + .driver = { + .name = MXC_MD_DRIVER_NAME, + .of_match_table = mxc_md_of_match, + }, + .probe = mxc_md_probe, + .remove = mxc_md_remove, +}; + +module_platform_driver(mxc_md_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Media Device driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" MXC_MD_DRIVER_NAME); |