diff options
Diffstat (limited to 'drivers/media/platform/rcar-vin')
-rw-r--r-- | drivers/media/platform/rcar-vin/rcar-core.c | 959 | ||||
-rw-r--r-- | drivers/media/platform/rcar-vin/rcar-csi2.c | 241 | ||||
-rw-r--r-- | drivers/media/platform/rcar-vin/rcar-dma.c | 40 | ||||
-rw-r--r-- | drivers/media/platform/rcar-vin/rcar-v4l2.c | 25 | ||||
-rw-r--r-- | drivers/media/platform/rcar-vin/rcar-vin.h | 25 |
5 files changed, 831 insertions, 459 deletions
diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c index 33957cc9118c..1d92cc8ede8f 100644 --- a/drivers/media/platform/rcar-vin/rcar-core.c +++ b/drivers/media/platform/rcar-vin/rcar-core.c @@ -45,188 +45,7 @@ #define v4l2_dev_to_vin(d) container_of(d, struct rvin_dev, v4l2_dev) /* ----------------------------------------------------------------------------- - * Media Controller link notification - */ - -/* group lock should be held when calling this function. */ -static int rvin_group_entity_to_csi_id(struct rvin_group *group, - struct media_entity *entity) -{ - struct v4l2_subdev *sd; - unsigned int i; - - sd = media_entity_to_v4l2_subdev(entity); - - for (i = 0; i < RVIN_CSI_MAX; i++) - if (group->csi[i].subdev == sd) - return i; - - return -ENODEV; -} - -static unsigned int rvin_group_get_mask(struct rvin_dev *vin, - enum rvin_csi_id csi_id, - unsigned char channel) -{ - const struct rvin_group_route *route; - unsigned int mask = 0; - - for (route = vin->info->routes; route->mask; route++) { - if (route->vin == vin->id && - route->csi == csi_id && - route->channel == channel) { - vin_dbg(vin, - "Adding route: vin: %d csi: %d channel: %d\n", - route->vin, route->csi, route->channel); - mask |= route->mask; - } - } - - return mask; -} - -/* - * Link setup for the links between a VIN and a CSI-2 receiver is a bit - * complex. The reason for this is that the register controlling routing - * is not present in each VIN instance. There are special VINs which - * control routing for themselves and other VINs. There are not many - * different possible links combinations that can be enabled at the same - * time, therefor all already enabled links which are controlled by a - * master VIN need to be taken into account when making the decision - * if a new link can be enabled or not. - * - * 1. Find out which VIN the link the user tries to enable is connected to. - * 2. Lookup which master VIN controls the links for this VIN. - * 3. Start with a bitmask with all bits set. - * 4. For each previously enabled link from the master VIN bitwise AND its - * route mask (see documentation for mask in struct rvin_group_route) - * with the bitmask. - * 5. Bitwise AND the mask for the link the user tries to enable to the bitmask. - * 6. If the bitmask is not empty at this point the new link can be enabled - * while keeping all previous links enabled. Update the CHSEL value of the - * master VIN and inform the user that the link could be enabled. - * - * Please note that no link can be enabled if any VIN in the group is - * currently open. - */ -static int rvin_group_link_notify(struct media_link *link, u32 flags, - unsigned int notification) -{ - struct rvin_group *group = container_of(link->graph_obj.mdev, - struct rvin_group, mdev); - unsigned int master_id, channel, mask_new, i; - unsigned int mask = ~0; - struct media_entity *entity; - struct video_device *vdev; - struct media_pad *csi_pad; - struct rvin_dev *vin = NULL; - int csi_id, ret; - - ret = v4l2_pipeline_link_notify(link, flags, notification); - if (ret) - return ret; - - /* Only care about link enablement for VIN nodes. */ - if (!(flags & MEDIA_LNK_FL_ENABLED) || - !is_media_entity_v4l2_video_device(link->sink->entity)) - return 0; - - /* - * Don't allow link changes if any entity in the graph is - * streaming, modifying the CHSEL register fields can disrupt - * running streams. - */ - media_device_for_each_entity(entity, &group->mdev) - if (entity->stream_count) - return -EBUSY; - - mutex_lock(&group->lock); - - /* Find the master VIN that controls the routes. */ - vdev = media_entity_to_video_device(link->sink->entity); - vin = container_of(vdev, struct rvin_dev, vdev); - master_id = rvin_group_id_to_master(vin->id); - - if (WARN_ON(!group->vin[master_id])) { - ret = -ENODEV; - goto out; - } - - /* Build a mask for already enabled links. */ - for (i = master_id; i < master_id + 4; i++) { - if (!group->vin[i]) - continue; - - /* Get remote CSI-2, if any. */ - csi_pad = media_entity_remote_pad( - &group->vin[i]->vdev.entity.pads[0]); - if (!csi_pad) - continue; - - csi_id = rvin_group_entity_to_csi_id(group, csi_pad->entity); - channel = rvin_group_csi_pad_to_channel(csi_pad->index); - - mask &= rvin_group_get_mask(group->vin[i], csi_id, channel); - } - - /* Add the new link to the existing mask and check if it works. */ - csi_id = rvin_group_entity_to_csi_id(group, link->source->entity); - - if (csi_id == -ENODEV) { - struct v4l2_subdev *sd; - - /* - * Make sure the source entity subdevice is registered as - * a parallel input of one of the enabled VINs if it is not - * one of the CSI-2 subdevices. - * - * No hardware configuration required for parallel inputs, - * we can return here. - */ - sd = media_entity_to_v4l2_subdev(link->source->entity); - for (i = 0; i < RCAR_VIN_NUM; i++) { - if (group->vin[i] && - group->vin[i]->parallel.subdev == sd) { - group->vin[i]->is_csi = false; - ret = 0; - goto out; - } - } - - vin_err(vin, "Subdevice %s not registered to any VIN\n", - link->source->entity->name); - ret = -ENODEV; - goto out; - } - - channel = rvin_group_csi_pad_to_channel(link->source->index); - mask_new = mask & rvin_group_get_mask(vin, csi_id, channel); - vin_dbg(vin, "Try link change mask: 0x%x new: 0x%x\n", mask, mask_new); - - if (!mask_new) { - ret = -EMLINK; - goto out; - } - - /* New valid CHSEL found, set the new value. */ - ret = rvin_set_channel_routing(group->vin[master_id], __ffs(mask_new)); - if (ret) - goto out; - - vin->is_csi = true; - -out: - mutex_unlock(&group->lock); - - return ret; -} - -static const struct media_device_ops rvin_media_ops = { - .link_notify = rvin_group_link_notify, -}; - -/* ----------------------------------------------------------------------------- - * Gen3 CSI2 Group Allocator + * Gen3 Group Allocator */ /* FIXME: This should if we find a system that supports more @@ -247,7 +66,9 @@ static void rvin_group_cleanup(struct rvin_group *group) mutex_destroy(&group->lock); } -static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin) +static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin, + int (*link_setup)(struct rvin_dev *), + const struct media_device_ops *ops) { struct media_device *mdev = &group->mdev; const struct of_device_id *match; @@ -263,8 +84,10 @@ static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin) vin_dbg(vin, "found %u enabled VIN's in DT", group->count); + group->link_setup = link_setup; + mdev->dev = vin->dev; - mdev->ops = &rvin_media_ops; + mdev->ops = ops; match = of_match_node(vin->dev->driver->of_match_table, vin->dev->of_node); @@ -295,7 +118,9 @@ static void rvin_group_release(struct kref *kref) mutex_unlock(&rvin_group_lock); } -static int rvin_group_get(struct rvin_dev *vin) +static int rvin_group_get(struct rvin_dev *vin, + int (*link_setup)(struct rvin_dev *), + const struct media_device_ops *ops) { struct rvin_group *group; u32 id; @@ -327,7 +152,7 @@ static int rvin_group_get(struct rvin_dev *vin) goto err_group; } - ret = rvin_group_init(group, vin); + ret = rvin_group_init(group, vin, link_setup, ops); if (ret) { kfree(group); vin_err(vin, "Failed to initialize group\n"); @@ -383,6 +208,213 @@ out: kref_put(&group->refcount, rvin_group_release); } +/* group lock should be held when calling this function. */ +static int rvin_group_entity_to_remote_id(struct rvin_group *group, + struct media_entity *entity) +{ + struct v4l2_subdev *sd; + unsigned int i; + + sd = media_entity_to_v4l2_subdev(entity); + + for (i = 0; i < RVIN_REMOTES_MAX; i++) + if (group->remotes[i].subdev == sd) + return i; + + return -ENODEV; +} + +static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + int ret; + + ret = media_device_register(&vin->group->mdev); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); + if (ret) { + vin_err(vin, "Failed to register subdev nodes\n"); + return ret; + } + + /* Register all video nodes for the group. */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (vin->group->vin[i] && + !video_is_registered(&vin->group->vin[i]->vdev)) { + ret = rvin_v4l2_register(vin->group->vin[i]); + if (ret) + return ret; + } + } + + return vin->group->link_setup(vin); +} + +static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + + for (i = 0; i < RCAR_VIN_NUM; i++) + if (vin->group->vin[i]) + rvin_v4l2_unregister(vin->group->vin[i]); + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->remotes[i].asd != asd) + continue; + vin->group->remotes[i].subdev = NULL; + vin_dbg(vin, "Unbind %s from slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); + + media_device_unregister(&vin->group->mdev); +} + +static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->remotes[i].asd != asd) + continue; + vin->group->remotes[i].subdev = subdev; + vin_dbg(vin, "Bound %s to slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); + + return 0; +} + +static const struct v4l2_async_notifier_operations rvin_group_notify_ops = { + .bound = rvin_group_notify_bound, + .unbind = rvin_group_notify_unbind, + .complete = rvin_group_notify_complete, +}; + +static int rvin_group_parse_of(struct rvin_dev *vin, unsigned int port, + unsigned int id) +{ + struct fwnode_handle *ep, *fwnode; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_subdev *asd; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), port, id, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + asd = v4l2_async_nf_add_fwnode(&vin->group->notifier, fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + vin->group->remotes[vep.base.id].asd = asd; + + vin_dbg(vin, "Add group OF device %pOF to slot %u\n", + to_of_node(fwnode), vep.base.id); +out: + fwnode_handle_put(fwnode); + + return ret; +} + +static void rvin_group_notifier_cleanup(struct rvin_dev *vin) +{ + mutex_lock(&vin->group->lock); + if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { + v4l2_async_nf_unregister(&vin->group->notifier); + v4l2_async_nf_cleanup(&vin->group->notifier); + } + mutex_unlock(&vin->group->lock); +} + +static int rvin_group_notifier_init(struct rvin_dev *vin, unsigned int port, + unsigned int max_id) +{ + unsigned int count = 0, vin_mask = 0; + unsigned int i, id; + int ret; + + mutex_lock(&vin->group->lock); + + /* If not all VIN's are registered don't register the notifier. */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (vin->group->vin[i]) { + count++; + vin_mask |= BIT(i); + } + } + + if (vin->group->count != count) { + mutex_unlock(&vin->group->lock); + return 0; + } + + mutex_unlock(&vin->group->lock); + + v4l2_async_nf_init(&vin->group->notifier); + + /* + * Some subdevices may overlap but the parser function can handle it and + * each subdevice will only be registered once with the group notifier. + */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (!(vin_mask & BIT(i))) + continue; + + for (id = 0; id < max_id; id++) { + if (vin->group->remotes[id].asd) + continue; + + ret = rvin_group_parse_of(vin->group->vin[i], port, id); + if (ret) + return ret; + } + } + + if (list_empty(&vin->group->notifier.asd_list)) + return 0; + + vin->group->notifier.ops = &rvin_group_notify_ops; + ret = v4l2_async_nf_register(&vin->v4l2_dev, &vin->group->notifier); + if (ret < 0) { + vin_err(vin, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&vin->group->notifier); + return ret; + } + + return 0; +} + /* ----------------------------------------------------------------------------- * Controls */ @@ -405,6 +437,45 @@ static const struct v4l2_ctrl_ops rvin_ctrl_ops = { .s_ctrl = rvin_s_ctrl, }; +static void rvin_free_controls(struct rvin_dev *vin) +{ + v4l2_ctrl_handler_free(&vin->ctrl_handler); + vin->vdev.ctrl_handler = NULL; +} + +static int rvin_create_controls(struct rvin_dev *vin, struct v4l2_subdev *subdev) +{ + int ret; + + ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + if (ret < 0) + return ret; + + /* The VIN directly deals with alpha component. */ + v4l2_ctrl_new_std(&vin->ctrl_handler, &rvin_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); + + if (vin->ctrl_handler.error) { + ret = vin->ctrl_handler.error; + rvin_free_controls(vin); + return ret; + } + + /* For the non-MC mode add controls from the subdevice. */ + if (subdev) { + ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, + subdev->ctrl_handler, NULL, true); + if (ret < 0) { + rvin_free_controls(vin); + return ret; + } + } + + vin->vdev.ctrl_handler = &vin->ctrl_handler; + + return 0; +} + /* ----------------------------------------------------------------------------- * Async notifier */ @@ -490,28 +561,10 @@ static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, return ret; /* Add the controls */ - ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + ret = rvin_create_controls(vin, subdev); if (ret < 0) return ret; - v4l2_ctrl_new_std(&vin->ctrl_handler, &rvin_ctrl_ops, - V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); - - if (vin->ctrl_handler.error) { - ret = vin->ctrl_handler.error; - v4l2_ctrl_handler_free(&vin->ctrl_handler); - return ret; - } - - ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, subdev->ctrl_handler, - NULL, true); - if (ret < 0) { - v4l2_ctrl_handler_free(&vin->ctrl_handler); - return ret; - } - - vin->vdev.ctrl_handler = &vin->ctrl_handler; - vin->parallel.subdev = subdev; return 0; @@ -522,10 +575,8 @@ static void rvin_parallel_subdevice_detach(struct rvin_dev *vin) rvin_v4l2_unregister(vin); vin->parallel.subdev = NULL; - if (!vin->info->use_mc) { - v4l2_ctrl_handler_free(&vin->ctrl_handler); - vin->vdev.ctrl_handler = NULL; - } + if (!vin->info->use_mc) + rvin_free_controls(vin); } static int rvin_parallel_notify_complete(struct v4l2_async_notifier *notifier) @@ -641,8 +692,8 @@ static int rvin_parallel_parse_of(struct rvin_dev *vin) goto out; } - asd = v4l2_async_notifier_add_fwnode_subdev(&vin->notifier, fwnode, - struct v4l2_async_subdev); + asd = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, + struct v4l2_async_subdev); if (IS_ERR(asd)) { ret = PTR_ERR(asd); goto out; @@ -657,28 +708,33 @@ out: return ret; } +static void rvin_parallel_cleanup(struct rvin_dev *vin) +{ + v4l2_async_nf_unregister(&vin->notifier); + v4l2_async_nf_cleanup(&vin->notifier); +} + static int rvin_parallel_init(struct rvin_dev *vin) { int ret; - v4l2_async_notifier_init(&vin->notifier); + v4l2_async_nf_init(&vin->notifier); ret = rvin_parallel_parse_of(vin); if (ret) return ret; - /* If using mc, it's fine not to have any input registered. */ if (!vin->parallel.asd) - return vin->info->use_mc ? 0 : -ENODEV; + return -ENODEV; vin_dbg(vin, "Found parallel subdevice %pOF\n", to_of_node(vin->parallel.asd->match.fwnode)); vin->notifier.ops = &rvin_parallel_notify_ops; - ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier); + ret = v4l2_async_nf_register(&vin->v4l2_dev, &vin->notifier); if (ret < 0) { vin_err(vin, "Notifier registration failed\n"); - v4l2_async_notifier_cleanup(&vin->notifier); + v4l2_async_nf_cleanup(&vin->notifier); return ret; } @@ -686,36 +742,175 @@ static int rvin_parallel_init(struct rvin_dev *vin) } /* ----------------------------------------------------------------------------- - * Group async notifier + * CSI-2 */ -static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) +static unsigned int rvin_csi2_get_mask(struct rvin_dev *vin, + enum rvin_csi_id csi_id, + unsigned char channel) { - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); const struct rvin_group_route *route; - unsigned int i; - int ret; + unsigned int mask = 0; - ret = media_device_register(&vin->group->mdev); + for (route = vin->info->routes; route->mask; route++) { + if (route->vin == vin->id && + route->csi == csi_id && + route->channel == channel) { + vin_dbg(vin, + "Adding route: vin: %d csi: %d channel: %d\n", + route->vin, route->csi, route->channel); + mask |= route->mask; + } + } + + return mask; +} + +/* + * Link setup for the links between a VIN and a CSI-2 receiver is a bit + * complex. The reason for this is that the register controlling routing + * is not present in each VIN instance. There are special VINs which + * control routing for themselves and other VINs. There are not many + * different possible links combinations that can be enabled at the same + * time, therefor all already enabled links which are controlled by a + * master VIN need to be taken into account when making the decision + * if a new link can be enabled or not. + * + * 1. Find out which VIN the link the user tries to enable is connected to. + * 2. Lookup which master VIN controls the links for this VIN. + * 3. Start with a bitmask with all bits set. + * 4. For each previously enabled link from the master VIN bitwise AND its + * route mask (see documentation for mask in struct rvin_group_route) + * with the bitmask. + * 5. Bitwise AND the mask for the link the user tries to enable to the bitmask. + * 6. If the bitmask is not empty at this point the new link can be enabled + * while keeping all previous links enabled. Update the CHSEL value of the + * master VIN and inform the user that the link could be enabled. + * + * Please note that no link can be enabled if any VIN in the group is + * currently open. + */ +static int rvin_csi2_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct rvin_group *group = container_of(link->graph_obj.mdev, + struct rvin_group, mdev); + unsigned int master_id, channel, mask_new, i; + unsigned int mask = ~0; + struct media_entity *entity; + struct video_device *vdev; + struct media_pad *csi_pad; + struct rvin_dev *vin = NULL; + int csi_id, ret; + + ret = v4l2_pipeline_link_notify(link, flags, notification); if (ret) return ret; - ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); - if (ret) { - vin_err(vin, "Failed to register subdev nodes\n"); - return ret; + /* Only care about link enablement for VIN nodes. */ + if (!(flags & MEDIA_LNK_FL_ENABLED) || + !is_media_entity_v4l2_video_device(link->sink->entity)) + return 0; + + /* + * Don't allow link changes if any entity in the graph is + * streaming, modifying the CHSEL register fields can disrupt + * running streams. + */ + media_device_for_each_entity(entity, &group->mdev) + if (entity->stream_count) + return -EBUSY; + + mutex_lock(&group->lock); + + /* Find the master VIN that controls the routes. */ + vdev = media_entity_to_video_device(link->sink->entity); + vin = container_of(vdev, struct rvin_dev, vdev); + master_id = rvin_group_id_to_master(vin->id); + + if (WARN_ON(!group->vin[master_id])) { + ret = -ENODEV; + goto out; } - /* Register all video nodes for the group. */ - for (i = 0; i < RCAR_VIN_NUM; i++) { - if (vin->group->vin[i] && - !video_is_registered(&vin->group->vin[i]->vdev)) { - ret = rvin_v4l2_register(vin->group->vin[i]); - if (ret) - return ret; + /* Build a mask for already enabled links. */ + for (i = master_id; i < master_id + 4; i++) { + if (!group->vin[i]) + continue; + + /* Get remote CSI-2, if any. */ + csi_pad = media_entity_remote_pad( + &group->vin[i]->vdev.entity.pads[0]); + if (!csi_pad) + continue; + + csi_id = rvin_group_entity_to_remote_id(group, csi_pad->entity); + channel = rvin_group_csi_pad_to_channel(csi_pad->index); + + mask &= rvin_csi2_get_mask(group->vin[i], csi_id, channel); + } + + /* Add the new link to the existing mask and check if it works. */ + csi_id = rvin_group_entity_to_remote_id(group, link->source->entity); + + if (csi_id == -ENODEV) { + struct v4l2_subdev *sd; + + /* + * Make sure the source entity subdevice is registered as + * a parallel input of one of the enabled VINs if it is not + * one of the CSI-2 subdevices. + * + * No hardware configuration required for parallel inputs, + * we can return here. + */ + sd = media_entity_to_v4l2_subdev(link->source->entity); + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (group->vin[i] && + group->vin[i]->parallel.subdev == sd) { + group->vin[i]->is_csi = false; + ret = 0; + goto out; + } } + + vin_err(vin, "Subdevice %s not registered to any VIN\n", + link->source->entity->name); + ret = -ENODEV; + goto out; } + channel = rvin_group_csi_pad_to_channel(link->source->index); + mask_new = mask & rvin_csi2_get_mask(vin, csi_id, channel); + vin_dbg(vin, "Try link change mask: 0x%x new: 0x%x\n", mask, mask_new); + + if (!mask_new) { + ret = -EMLINK; + goto out; + } + + /* New valid CHSEL found, set the new value. */ + ret = rvin_set_channel_routing(group->vin[master_id], __ffs(mask_new)); + if (ret) + goto out; + + vin->is_csi = true; + +out: + mutex_unlock(&group->lock); + + return ret; +} + +static const struct media_device_ops rvin_csi2_media_ops = { + .link_notify = rvin_csi2_link_notify, +}; + +static int rvin_csi2_setup_links(struct rvin_dev *vin) +{ + const struct rvin_group_route *route; + int ret = -EINVAL; + /* Create all media device links between VINs and CSI-2's. */ mutex_lock(&vin->group->lock); for (route = vin->info->routes; route->mask; route++) { @@ -732,10 +927,10 @@ static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) continue; /* Check that CSI-2 is part of the group. */ - if (!vin->group->csi[route->csi].subdev) + if (!vin->group->remotes[route->csi].subdev) continue; - source = &vin->group->csi[route->csi].subdev->entity; + source = &vin->group->remotes[route->csi].subdev->entity; source_idx = rvin_group_csi_channel_to_pad(route->channel); source_pad = &source->pads[source_idx]; @@ -758,167 +953,107 @@ static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) return ret; } -static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_subdev *asd) -{ - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - unsigned int i; - - for (i = 0; i < RCAR_VIN_NUM; i++) - if (vin->group->vin[i]) - rvin_v4l2_unregister(vin->group->vin[i]); - - mutex_lock(&vin->group->lock); - - for (i = 0; i < RVIN_CSI_MAX; i++) { - if (vin->group->csi[i].asd != asd) - continue; - vin->group->csi[i].subdev = NULL; - vin_dbg(vin, "Unbind CSI-2 %s from slot %u\n", subdev->name, i); - break; - } - - mutex_unlock(&vin->group->lock); - - media_device_unregister(&vin->group->mdev); -} - -static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_subdev *asd) +static void rvin_csi2_cleanup(struct rvin_dev *vin) { - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - unsigned int i; - - mutex_lock(&vin->group->lock); - - for (i = 0; i < RVIN_CSI_MAX; i++) { - if (vin->group->csi[i].asd != asd) - continue; - vin->group->csi[i].subdev = subdev; - vin_dbg(vin, "Bound CSI-2 %s to slot %u\n", subdev->name, i); - break; - } - - mutex_unlock(&vin->group->lock); - - return 0; + rvin_parallel_cleanup(vin); + rvin_group_notifier_cleanup(vin); + rvin_group_put(vin); + rvin_free_controls(vin); } -static const struct v4l2_async_notifier_operations rvin_group_notify_ops = { - .bound = rvin_group_notify_bound, - .unbind = rvin_group_notify_unbind, - .complete = rvin_group_notify_complete, -}; - -static int rvin_mc_parse_of(struct rvin_dev *vin, unsigned int id) +static int rvin_csi2_init(struct rvin_dev *vin) { - struct fwnode_handle *ep, *fwnode; - struct v4l2_fwnode_endpoint vep = { - .bus_type = V4L2_MBUS_CSI2_DPHY, - }; - struct v4l2_async_subdev *asd; int ret; - ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), 1, id, 0); - if (!ep) - return 0; + vin->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); + if (ret) + return ret; - fwnode = fwnode_graph_get_remote_endpoint(ep); - ret = v4l2_fwnode_endpoint_parse(ep, &vep); - fwnode_handle_put(ep); - if (ret) { - vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); - ret = -EINVAL; - goto out; - } + ret = rvin_create_controls(vin, NULL); + if (ret < 0) + return ret; - if (!of_device_is_available(to_of_node(fwnode))) { - vin_dbg(vin, "OF device %pOF disabled, ignoring\n", - to_of_node(fwnode)); - ret = -ENOTCONN; - goto out; - } + ret = rvin_group_get(vin, rvin_csi2_setup_links, &rvin_csi2_media_ops); + if (ret) + goto err_controls; - asd = v4l2_async_notifier_add_fwnode_subdev(&vin->group->notifier, - fwnode, - struct v4l2_async_subdev); - if (IS_ERR(asd)) { - ret = PTR_ERR(asd); - goto out; - } + /* It's OK to not have a parallel subdevice. */ + ret = rvin_parallel_init(vin); + if (ret && ret != -ENODEV) + goto err_group; - vin->group->csi[vep.base.id].asd = asd; + ret = rvin_group_notifier_init(vin, 1, RVIN_CSI_MAX); + if (ret) + goto err_parallel; - vin_dbg(vin, "Add group OF device %pOF to slot %u\n", - to_of_node(fwnode), vep.base.id); -out: - fwnode_handle_put(fwnode); + return 0; +err_parallel: + rvin_parallel_cleanup(vin); +err_group: + rvin_group_put(vin); +err_controls: + rvin_free_controls(vin); return ret; } -static int rvin_mc_parse_of_graph(struct rvin_dev *vin) +/* ----------------------------------------------------------------------------- + * ISP + */ + +static int rvin_isp_setup_links(struct rvin_dev *vin) { - unsigned int count = 0, vin_mask = 0; - unsigned int i, id; - int ret; + unsigned int i; + int ret = -EINVAL; + /* Create all media device links between VINs and ISP's. */ mutex_lock(&vin->group->lock); - - /* If not all VIN's are registered don't register the notifier. */ for (i = 0; i < RCAR_VIN_NUM; i++) { - if (vin->group->vin[i]) { - count++; - vin_mask |= BIT(i); - } - } + struct media_pad *source_pad, *sink_pad; + struct media_entity *source, *sink; + unsigned int source_slot = i / 8; + unsigned int source_idx = i % 8 + 1; - if (vin->group->count != count) { - mutex_unlock(&vin->group->lock); - return 0; - } + if (!vin->group->vin[i]) + continue; - mutex_unlock(&vin->group->lock); + /* Check that ISP is part of the group. */ + if (!vin->group->remotes[source_slot].subdev) + continue; - v4l2_async_notifier_init(&vin->group->notifier); + source = &vin->group->remotes[source_slot].subdev->entity; + source_pad = &source->pads[source_idx]; - /* - * Have all VIN's look for CSI-2 subdevices. Some subdevices will - * overlap but the parser function can handle it, so each subdevice - * will only be registered once with the group notifier. - */ - for (i = 0; i < RCAR_VIN_NUM; i++) { - if (!(vin_mask & BIT(i))) - continue; + sink = &vin->group->vin[i]->vdev.entity; + sink_pad = &sink->pads[0]; - for (id = 0; id < RVIN_CSI_MAX; id++) { - if (vin->group->csi[id].asd) - continue; + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + continue; - ret = rvin_mc_parse_of(vin->group->vin[i], id); - if (ret) - return ret; + ret = media_create_pad_link(source, source_idx, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + vin_err(vin, "Error adding link from %s to %s\n", + source->name, sink->name); + break; } } + mutex_unlock(&vin->group->lock); - if (list_empty(&vin->group->notifier.asd_list)) - return 0; - - vin->group->notifier.ops = &rvin_group_notify_ops; - ret = v4l2_async_notifier_register(&vin->v4l2_dev, - &vin->group->notifier); - if (ret < 0) { - vin_err(vin, "Notifier registration failed\n"); - v4l2_async_notifier_cleanup(&vin->group->notifier); - return ret; - } + return ret; +} - return 0; +static void rvin_isp_cleanup(struct rvin_dev *vin) +{ + rvin_group_notifier_cleanup(vin); + rvin_group_put(vin); + rvin_free_controls(vin); } -static int rvin_mc_init(struct rvin_dev *vin) +static int rvin_isp_init(struct rvin_dev *vin) { int ret; @@ -927,28 +1062,23 @@ static int rvin_mc_init(struct rvin_dev *vin) if (ret) return ret; - ret = rvin_group_get(vin); - if (ret) - return ret; - - ret = rvin_mc_parse_of_graph(vin); - if (ret) - rvin_group_put(vin); - - ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 1); + ret = rvin_create_controls(vin, NULL); if (ret < 0) return ret; - v4l2_ctrl_new_std(&vin->ctrl_handler, &rvin_ctrl_ops, - V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); + ret = rvin_group_get(vin, rvin_isp_setup_links, NULL); + if (ret) + goto err_controls; - if (vin->ctrl_handler.error) { - ret = vin->ctrl_handler.error; - v4l2_ctrl_handler_free(&vin->ctrl_handler); - return ret; - } + ret = rvin_group_notifier_init(vin, 2, RVIN_ISP_MAX); + if (ret) + goto err_group; - vin->vdev.ctrl_handler = &vin->ctrl_handler; + return 0; +err_group: + rvin_group_put(vin); +err_controls: + rvin_free_controls(vin); return ret; } @@ -1325,6 +1455,15 @@ static const struct rvin_info rcar_info_r8a77995 = { .routes = rcar_info_r8a77995_routes, }; +static const struct rvin_info rcar_info_r8a779a0 = { + .model = RCAR_GEN3, + .use_mc = true, + .use_isp = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, +}; + static const struct of_device_id rvin_of_id_table[] = { { .compatible = "renesas,vin-r8a774a1", @@ -1386,6 +1525,10 @@ static const struct of_device_id rvin_of_id_table[] = { .compatible = "renesas,vin-r8a77995", .data = &rcar_info_r8a77995, }, + { + .compatible = "renesas,vin-r8a779a0", + .data = &rcar_info_r8a779a0, + }, { /* Sentinel */ }, }; MODULE_DEVICE_TABLE(of, rvin_of_id_table); @@ -1434,38 +1577,22 @@ static int rcar_vin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vin); - if (vin->info->use_mc) { - ret = rvin_mc_init(vin); - if (ret) - goto error_dma_unregister; - } + if (vin->info->use_isp) + ret = rvin_isp_init(vin); + else if (vin->info->use_mc) + ret = rvin_csi2_init(vin); + else + ret = rvin_parallel_init(vin); - ret = rvin_parallel_init(vin); - if (ret) - goto error_group_unregister; + if (ret) { + rvin_dma_unregister(vin); + return ret; + } pm_suspend_ignore_children(&pdev->dev, true); pm_runtime_enable(&pdev->dev); return 0; - -error_group_unregister: - v4l2_ctrl_handler_free(&vin->ctrl_handler); - - if (vin->info->use_mc) { - mutex_lock(&vin->group->lock); - if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { - v4l2_async_notifier_unregister(&vin->group->notifier); - v4l2_async_notifier_cleanup(&vin->group->notifier); - } - mutex_unlock(&vin->group->lock); - rvin_group_put(vin); - } - -error_dma_unregister: - rvin_dma_unregister(vin); - - return ret; } static int rcar_vin_remove(struct platform_device *pdev) @@ -1476,16 +1603,12 @@ static int rcar_vin_remove(struct platform_device *pdev) rvin_v4l2_unregister(vin); - v4l2_async_notifier_unregister(&vin->notifier); - v4l2_async_notifier_cleanup(&vin->notifier); - - if (vin->info->use_mc) { - v4l2_async_notifier_unregister(&vin->group->notifier); - v4l2_async_notifier_cleanup(&vin->group->notifier); - rvin_group_put(vin); - } - - v4l2_ctrl_handler_free(&vin->ctrl_handler); + if (vin->info->use_isp) + rvin_isp_cleanup(vin); + else if (vin->info->use_mc) + rvin_csi2_cleanup(vin); + else + rvin_parallel_cleanup(vin); rvin_dma_unregister(vin); diff --git a/drivers/media/platform/rcar-vin/rcar-csi2.c b/drivers/media/platform/rcar-vin/rcar-csi2.c index e28eff039688..11848d0c4a55 100644 --- a/drivers/media/platform/rcar-vin/rcar-csi2.c +++ b/drivers/media/platform/rcar-vin/rcar-csi2.c @@ -126,6 +126,12 @@ struct rcar_csi2; #define PHTW_CWEN BIT(8) #define PHTW_TESTDIN_CODE(n) ((n & 0xff)) +#define PHYFRX_REG 0x64 +#define PHYFRX_FORCERX_MODE_3 BIT(3) +#define PHYFRX_FORCERX_MODE_2 BIT(2) +#define PHYFRX_FORCERX_MODE_1 BIT(1) +#define PHYFRX_FORCERX_MODE_0 BIT(0) + struct phtw_value { u16 data; u16 code; @@ -136,6 +142,31 @@ struct rcsi2_mbps_reg { u16 reg; }; +static const struct rcsi2_mbps_reg phtw_mbps_v3u[] = { + { .mbps = 1500, .reg = 0xcc }, + { .mbps = 1550, .reg = 0x1d }, + { .mbps = 1600, .reg = 0x27 }, + { .mbps = 1650, .reg = 0x30 }, + { .mbps = 1700, .reg = 0x39 }, + { .mbps = 1750, .reg = 0x42 }, + { .mbps = 1800, .reg = 0x4b }, + { .mbps = 1850, .reg = 0x55 }, + { .mbps = 1900, .reg = 0x5e }, + { .mbps = 1950, .reg = 0x67 }, + { .mbps = 2000, .reg = 0x71 }, + { .mbps = 2050, .reg = 0x79 }, + { .mbps = 2100, .reg = 0x83 }, + { .mbps = 2150, .reg = 0x8c }, + { .mbps = 2200, .reg = 0x95 }, + { .mbps = 2250, .reg = 0x9e }, + { .mbps = 2300, .reg = 0xa7 }, + { .mbps = 2350, .reg = 0xb0 }, + { .mbps = 2400, .reg = 0xba }, + { .mbps = 2450, .reg = 0xc3 }, + { .mbps = 2500, .reg = 0xcc }, + { /* sentinel */ }, +}; + static const struct rcsi2_mbps_reg phtw_mbps_h3_v3h_m3n[] = { { .mbps = 80, .reg = 0x86 }, { .mbps = 90, .reg = 0x86 }, @@ -200,6 +231,72 @@ static const struct rcsi2_mbps_reg phtw_mbps_v3m_e3[] = { #define PHYPLL_REG 0x68 #define PHYPLL_HSFREQRANGE(n) ((n) << 16) +static const struct rcsi2_mbps_reg hsfreqrange_v3u[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x25 }, + { .mbps = 350, .reg = 0x35 }, + { .mbps = 400, .reg = 0x05 }, + { .mbps = 450, .reg = 0x16 }, + { .mbps = 500, .reg = 0x26 }, + { .mbps = 550, .reg = 0x37 }, + { .mbps = 600, .reg = 0x07 }, + { .mbps = 650, .reg = 0x18 }, + { .mbps = 700, .reg = 0x28 }, + { .mbps = 750, .reg = 0x39 }, + { .mbps = 800, .reg = 0x09 }, + { .mbps = 850, .reg = 0x19 }, + { .mbps = 900, .reg = 0x29 }, + { .mbps = 950, .reg = 0x3a }, + { .mbps = 1000, .reg = 0x0a }, + { .mbps = 1050, .reg = 0x1a }, + { .mbps = 1100, .reg = 0x2a }, + { .mbps = 1150, .reg = 0x3b }, + { .mbps = 1200, .reg = 0x0b }, + { .mbps = 1250, .reg = 0x1b }, + { .mbps = 1300, .reg = 0x2b }, + { .mbps = 1350, .reg = 0x3c }, + { .mbps = 1400, .reg = 0x0c }, + { .mbps = 1450, .reg = 0x1c }, + { .mbps = 1500, .reg = 0x2c }, + { .mbps = 1550, .reg = 0x3d }, + { .mbps = 1600, .reg = 0x0d }, + { .mbps = 1650, .reg = 0x1d }, + { .mbps = 1700, .reg = 0x2e }, + { .mbps = 1750, .reg = 0x3e }, + { .mbps = 1800, .reg = 0x0e }, + { .mbps = 1850, .reg = 0x1e }, + { .mbps = 1900, .reg = 0x2f }, + { .mbps = 1950, .reg = 0x3f }, + { .mbps = 2000, .reg = 0x0f }, + { .mbps = 2050, .reg = 0x40 }, + { .mbps = 2100, .reg = 0x41 }, + { .mbps = 2150, .reg = 0x42 }, + { .mbps = 2200, .reg = 0x43 }, + { .mbps = 2300, .reg = 0x45 }, + { .mbps = 2350, .reg = 0x46 }, + { .mbps = 2400, .reg = 0x47 }, + { .mbps = 2450, .reg = 0x48 }, + { .mbps = 2500, .reg = 0x49 }, + { /* sentinel */ }, +}; + static const struct rcsi2_mbps_reg hsfreqrange_h3_v3h_m3n[] = { { .mbps = 80, .reg = 0x00 }, { .mbps = 90, .reg = 0x10 }, @@ -355,6 +452,7 @@ struct rcar_csi2_info { unsigned int csi0clkfreqrange; unsigned int num_channels; bool clear_ulps; + bool use_isp; }; struct rcar_csi2 { @@ -370,9 +468,8 @@ struct rcar_csi2 { struct v4l2_subdev *remote; unsigned int remote_pad; + struct mutex lock; /* Protects mf and stream_count. */ struct v4l2_mbus_framefmt mf; - - struct mutex lock; int stream_count; unsigned short lanes; @@ -553,6 +650,8 @@ static int rcsi2_start_receiver(struct rcar_csi2 *priv) /* Code is validated in set_fmt. */ format = rcsi2_code_to_fmt(priv->mf.code); + if (!format) + return -EINVAL; /* * Enable all supported CSI-2 channels with virtual channel and @@ -609,9 +708,12 @@ static int rcsi2_start_receiver(struct rcar_csi2 *priv) rcsi2_write(priv, PHTC_REG, 0); /* Configure */ - rcsi2_write(priv, VCDT_REG, vcdt); - if (vcdt2) - rcsi2_write(priv, VCDT2_REG, vcdt2); + if (!priv->info->use_isp) { + rcsi2_write(priv, VCDT_REG, vcdt); + if (vcdt2) + rcsi2_write(priv, VCDT2_REG, vcdt2); + } + /* Lanes are zero indexed. */ rcsi2_write(priv, LSWAP_REG, LSWAP_L0SEL(priv->lane_swap[0] - 1) | @@ -636,6 +738,11 @@ static int rcsi2_start_receiver(struct rcar_csi2 *priv) rcsi2_write(priv, CSI0CLKFCPR_REG, CSI0CLKFREQRANGE(priv->info->csi0clkfreqrange)); + if (priv->info->use_isp) + rcsi2_write(priv, PHYFRX_REG, + PHYFRX_FORCERX_MODE_3 | PHYFRX_FORCERX_MODE_2 | + PHYFRX_FORCERX_MODE_1 | PHYFRX_FORCERX_MODE_0); + rcsi2_write(priv, PHYCNT_REG, phycnt); rcsi2_write(priv, LINKCNT_REG, LINKCNT_MONITOR_EN | LINKCNT_REG_MONI_PACT_EN | LINKCNT_ICLK_NONSTOP); @@ -647,6 +754,9 @@ static int rcsi2_start_receiver(struct rcar_csi2 *priv) if (ret) return ret; + if (priv->info->use_isp) + rcsi2_write(priv, PHYFRX_REG, 0); + /* Run post PHY start initialization, if needed. */ if (priv->info->phy_post_init) { ret = priv->info->phy_post_init(priv); @@ -725,6 +835,8 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, struct rcar_csi2 *priv = sd_to_csi2(sd); struct v4l2_mbus_framefmt *framefmt; + mutex_lock(&priv->lock); + if (!rcsi2_code_to_fmt(format->format.code)) format->format.code = rcar_csi2_formats[0].code; @@ -735,6 +847,8 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, *framefmt = format->format; } + mutex_unlock(&priv->lock); + return 0; } @@ -744,11 +858,15 @@ static int rcsi2_get_pad_format(struct v4l2_subdev *sd, { struct rcar_csi2 *priv = sd_to_csi2(sd); + mutex_lock(&priv->lock); + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) format->format = priv->mf; else format->format = *v4l2_subdev_get_try_format(sd, sd_state, 0); + mutex_unlock(&priv->lock); + return 0; } @@ -917,19 +1035,18 @@ static int rcsi2_parse_dt(struct rcar_csi2 *priv) dev_dbg(priv->dev, "Found '%pOF'\n", to_of_node(fwnode)); - v4l2_async_notifier_init(&priv->notifier); + v4l2_async_nf_init(&priv->notifier); priv->notifier.ops = &rcar_csi2_notify_ops; - asd = v4l2_async_notifier_add_fwnode_subdev(&priv->notifier, fwnode, - struct v4l2_async_subdev); + asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode, + struct v4l2_async_subdev); fwnode_handle_put(fwnode); if (IS_ERR(asd)) return PTR_ERR(asd); - ret = v4l2_async_subdev_notifier_register(&priv->subdev, - &priv->notifier); + ret = v4l2_async_subdev_nf_register(&priv->subdev, &priv->notifier); if (ret) - v4l2_async_notifier_cleanup(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); return ret; } @@ -1063,6 +1180,62 @@ static int rcsi2_phy_post_init_v3m_e3(struct rcar_csi2 *priv) return rcsi2_phtw_write_array(priv, step1); } +static int rcsi2_init_phtw_v3u(struct rcar_csi2 *priv, + unsigned int mbps) +{ + /* In case of 1500Mbps or less */ + static const struct phtw_value step1[] = { + { .data = 0xcc, .code = 0xe2 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step2[] = { + { .data = 0x01, .code = 0xe3 }, + { .data = 0x11, .code = 0xe4 }, + { .data = 0x01, .code = 0xe5 }, + { /* sentinel */ }, + }; + + /* In case of 1500Mbps or less */ + static const struct phtw_value step3[] = { + { .data = 0x38, .code = 0x08 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step4[] = { + { .data = 0x01, .code = 0x00 }, + { .data = 0x4b, .code = 0xac }, + { .data = 0x03, .code = 0x00 }, + { .data = 0x80, .code = 0x07 }, + { /* sentinel */ }, + }; + + int ret; + + if (mbps != 0 && mbps <= 1500) + ret = rcsi2_phtw_write_array(priv, step1); + else + ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_v3u, 0xe2); + if (ret) + return ret; + + ret = rcsi2_phtw_write_array(priv, step2); + if (ret) + return ret; + + if (mbps != 0 && mbps <= 1500) { + ret = rcsi2_phtw_write_array(priv, step3); + if (ret) + return ret; + } + + ret = rcsi2_phtw_write_array(priv, step4); + if (ret) + return ret; + + return ret; +} + /* ----------------------------------------------------------------------------- * Platform Device Driver. */ @@ -1074,11 +1247,9 @@ static const struct media_entity_operations rcar_csi2_entity_ops = { static int rcsi2_probe_resources(struct rcar_csi2 *priv, struct platform_device *pdev) { - struct resource *res; int irq, ret; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(&pdev->dev, res); + priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); @@ -1155,6 +1326,14 @@ static const struct rcar_csi2_info rcar_csi2_info_r8a77990 = { .num_channels = 2, }; +static const struct rcar_csi2_info rcar_csi2_info_r8a779a0 = { + .init_phtw = rcsi2_init_phtw_v3u, + .hsfreqrange = hsfreqrange_v3u, + .csi0clkfreqrange = 0x20, + .clear_ulps = true, + .use_isp = true, +}; + static const struct of_device_id rcar_csi2_of_table[] = { { .compatible = "renesas,r8a774a1-csi2", @@ -1200,6 +1379,10 @@ static const struct of_device_id rcar_csi2_of_table[] = { .compatible = "renesas,r8a77990-csi2", .data = &rcar_csi2_info_r8a77990, }, + { + .compatible = "renesas,r8a779a0-csi2", + .data = &rcar_csi2_info_r8a779a0, + }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, rcar_csi2_of_table); @@ -1220,7 +1403,7 @@ static int rcsi2_probe(struct platform_device *pdev) { const struct soc_device_attribute *attr; struct rcar_csi2 *priv; - unsigned int i; + unsigned int i, num_pads; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); @@ -1245,14 +1428,14 @@ static int rcsi2_probe(struct platform_device *pdev) ret = rcsi2_probe_resources(priv, pdev); if (ret) { dev_err(priv->dev, "Failed to get resources\n"); - return ret; + goto error_mutex; } platform_set_drvdata(pdev, priv); ret = rcsi2_parse_dt(priv); if (ret) - return ret; + goto error_mutex; priv->subdev.owner = THIS_MODULE; priv->subdev.dev = &pdev->dev; @@ -1265,28 +1448,32 @@ static int rcsi2_probe(struct platform_device *pdev) priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; priv->subdev.entity.ops = &rcar_csi2_entity_ops; + num_pads = priv->info->use_isp ? 2 : NR_OF_RCAR_CSI2_PAD; + priv->pads[RCAR_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; - for (i = RCAR_CSI2_SOURCE_VC0; i < NR_OF_RCAR_CSI2_PAD; i++) + for (i = RCAR_CSI2_SOURCE_VC0; i < num_pads; i++) priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; - ret = media_entity_pads_init(&priv->subdev.entity, NR_OF_RCAR_CSI2_PAD, + ret = media_entity_pads_init(&priv->subdev.entity, num_pads, priv->pads); if (ret) - goto error; + goto error_async; pm_runtime_enable(&pdev->dev); ret = v4l2_async_register_subdev(&priv->subdev); if (ret < 0) - goto error; + goto error_async; dev_info(priv->dev, "%d lanes found\n", priv->lanes); return 0; -error: - v4l2_async_notifier_unregister(&priv->notifier); - v4l2_async_notifier_cleanup(&priv->notifier); +error_async: + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +error_mutex: + mutex_destroy(&priv->lock); return ret; } @@ -1295,12 +1482,14 @@ static int rcsi2_remove(struct platform_device *pdev) { struct rcar_csi2 *priv = platform_get_drvdata(pdev); - v4l2_async_notifier_unregister(&priv->notifier); - v4l2_async_notifier_cleanup(&priv->notifier); + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); v4l2_async_unregister_subdev(&priv->subdev); pm_runtime_disable(&pdev->dev); + mutex_destroy(&priv->lock); + return 0; } diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c index f5f722ab1d4e..25ead9333d00 100644 --- a/drivers/media/platform/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/rcar-vin/rcar-dma.c @@ -114,6 +114,7 @@ /* Video n Data Mode Register bits */ #define VNDMR_A8BIT(n) (((n) & 0xff) << 24) #define VNDMR_A8BIT_MASK (0xff << 24) +#define VNDMR_YMODE_Y8 (1 << 12) #define VNDMR_EXRGB (1 << 8) #define VNDMR_BPSM (1 << 4) #define VNDMR_ABIT (1 << 2) @@ -603,6 +604,7 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_SGRBG8: case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_GREY: stride /= 2; break; default: @@ -695,6 +697,7 @@ static int rvin_setup(struct rvin_dev *vin) case MEDIA_BUS_FMT_SGBRG8_1X8: case MEDIA_BUS_FMT_SGRBG8_1X8: case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_Y8_1X8: vnmc |= VNMC_INF_RAW8; break; default: @@ -774,6 +777,14 @@ static int rvin_setup(struct rvin_dev *vin) case V4L2_PIX_FMT_SRGGB8: dmr = 0; break; + case V4L2_PIX_FMT_GREY: + if (input_is_yuv) { + dmr = VNDMR_DTMD_YCSEP | VNDMR_YMODE_Y8; + output_is_yuv = true; + } else { + dmr = 0; + } + break; default: vin_err(vin, "Invalid pixelformat (0x%x)\n", vin->format.pixelformat); @@ -783,16 +794,18 @@ static int rvin_setup(struct rvin_dev *vin) /* Always update on field change */ vnmc |= VNMC_VUP; - /* If input and output use the same colorspace, use bypass mode */ - if (input_is_yuv == output_is_yuv) - vnmc |= VNMC_BPS; - - if (vin->info->model == RCAR_GEN3) { - /* Select between CSI-2 and parallel input */ - if (vin->is_csi) - vnmc &= ~VNMC_DPINE; - else - vnmc |= VNMC_DPINE; + if (!vin->info->use_isp) { + /* If input and output use the same colorspace, use bypass mode */ + if (input_is_yuv == output_is_yuv) + vnmc |= VNMC_BPS; + + if (vin->info->model == RCAR_GEN3) { + /* Select between CSI-2 and parallel input */ + if (vin->is_csi) + vnmc &= ~VNMC_DPINE; + else + vnmc |= VNMC_DPINE; + } } /* Progressive or interlaced mode */ @@ -904,7 +917,8 @@ static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) vin->format.sizeimage / 2; break; } - } else if (vin->state != RUNNING || list_empty(&vin->buf_list)) { + } else if ((vin->state != STOPPED && vin->state != RUNNING) || + list_empty(&vin->buf_list)) { vin->buf_hw[slot].buffer = NULL; vin->buf_hw[slot].type = FULL; phys_addr = vin->scratch_phys; @@ -1145,6 +1159,10 @@ static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, if (vin->format.pixelformat != V4L2_PIX_FMT_SRGGB8) return -EPIPE; break; + case MEDIA_BUS_FMT_Y8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_GREY) + return -EPIPE; + break; default: return -EPIPE; } diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 0d141155f0e3..a5bfa76fdac6 100644 --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c @@ -82,6 +82,10 @@ static const struct rvin_video_format rvin_formats[] = { .fourcc = V4L2_PIX_FMT_SRGGB8, .bpp = 1, }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .bpp = 1, + }, }; const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin, @@ -523,6 +527,24 @@ static int rvin_s_selection(struct file *file, void *fh, return 0; } +static int rvin_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_g_parm_cap(&vin->vdev, sd, parm); +} + +static int rvin_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_s_parm_cap(&vin->vdev, sd, parm); +} + static int rvin_g_pixelaspect(struct file *file, void *priv, int type, struct v4l2_fract *f) { @@ -739,6 +761,9 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = { .vidioc_g_selection = rvin_g_selection, .vidioc_s_selection = rvin_s_selection, + .vidioc_g_parm = rvin_g_parm, + .vidioc_s_parm = rvin_s_parm, + .vidioc_g_pixelaspect = rvin_g_pixelaspect, .vidioc_enum_input = rvin_enum_input, diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h index b263ead4db2b..6c06320174a2 100644 --- a/drivers/media/platform/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/rcar-vin/rcar-vin.h @@ -29,7 +29,7 @@ #define HW_BUFFER_MASK 0x7f /* Max number on VIN instances that can be in a system */ -#define RCAR_VIN_NUM 8 +#define RCAR_VIN_NUM 32 struct rvin_group; @@ -48,6 +48,18 @@ enum rvin_csi_id { RVIN_CSI_MAX, }; +enum rvin_isp_id { + RVIN_ISP0, + RVIN_ISP1, + RVIN_ISP2, + RVIN_ISP4, + RVIN_ISP_MAX, +}; + +#define RVIN_REMOTES_MAX \ + (((unsigned int)RVIN_CSI_MAX) > ((unsigned int)RVIN_ISP_MAX) ? \ + RVIN_CSI_MAX : RVIN_ISP_MAX) + /** * enum rvin_dma_state - DMA states * @STOPPED: No operation in progress @@ -147,6 +159,7 @@ struct rvin_group_route { * struct rvin_info - Information about the particular VIN implementation * @model: VIN model * @use_mc: use media controller instead of controlling subdevice + * @use_isp: the VIN is connected to the ISP and not to the CSI-2 * @nv12: support outputing NV12 pixel format * @max_width: max input width the VIN supports * @max_height: max input height the VIN supports @@ -156,6 +169,7 @@ struct rvin_group_route { struct rvin_info { enum model_id model; bool use_mc; + bool use_isp; bool nv12; unsigned int max_width; @@ -267,8 +281,9 @@ struct rvin_dev { * @count: number of enabled VIN instances found in DT * @notifier: group notifier for CSI-2 async subdevices * @vin: VIN instances which are part of the group - * @csi: array of pairs of fwnode and subdev pointers - * to all CSI-2 subdevices. + * @link_setup: Callback to create all links for the media graph + * @remotes: array of pairs of fwnode and subdev pointers + * to all remote subdevices. */ struct rvin_group { struct kref refcount; @@ -280,10 +295,12 @@ struct rvin_group { struct v4l2_async_notifier notifier; struct rvin_dev *vin[RCAR_VIN_NUM]; + int (*link_setup)(struct rvin_dev *vin); + struct { struct v4l2_async_subdev *asd; struct v4l2_subdev *subdev; - } csi[RVIN_CSI_MAX]; + } remotes[RVIN_REMOTES_MAX]; }; int rvin_dma_register(struct rvin_dev *vin, int irq); |