Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Generic video config states
#
config VIDEO_V4L2_I2C
bool
depends on I2C && VIDEO_DEV
default y
config VIDEO_V4L2_SUBDEV_API
bool
depends on VIDEO_DEV && MEDIA_CONTROLLER
help
Enables the V4L2 sub-device pad-level userspace API used to configure
video format, size and frame rate between hardware blocks.
This API is mostly used by camera interfaces in embedded platforms.
config VIDEO_ADV_DEBUG
bool "Enable advanced debug functionality on V4L2 drivers"
help
Say Y here to enable advanced debugging functionality on some
V4L devices.
In doubt, say N.
config VIDEO_FIXED_MINOR_RANGES
bool "Enable old-style fixed minor ranges on drivers/video devices"
help
Say Y here to enable the old-style fixed-range minor assignments.
Only useful if you rely on the old behavior and use mknod instead of udev.
When in doubt, say N.
# Used by drivers that need tuner.ko
config VIDEO_TUNER
tristate
# Used by drivers that need v4l2-jpeg.ko
config V4L2_JPEG_HELPER
tristate
# Used by drivers that need v4l2-h264.ko
config V4L2_H264
tristate
# Used by drivers that need v4l2-vp9.ko
config V4L2_VP9
tristate
# Used by drivers that need v4l2-mem2mem.ko
config V4L2_MEM2MEM_DEV
tristate
depends on VIDEOBUF2_CORE
# Used by LED subsystem flash drivers
config V4L2_FLASH_LED_CLASS
tristate "V4L2 flash API for LED flash class devices"
depends on VIDEO_DEV
depends on LEDS_CLASS_FLASH
select MEDIA_CONTROLLER
select V4L2_ASYNC
select VIDEO_V4L2_SUBDEV_API
help
Say Y here to enable V4L2 flash API support for LED flash
class drivers.
When in doubt, say N.
config V4L2_FWNODE
tristate
select V4L2_ASYNC
config V4L2_ASYNC
tristate
config V4L2_CCI
tristate
config V4L2_CCI_I2C
tristate
depends on I2C
select REGMAP_I2C
select V4L2_CCI
config V4L2_ISP
tristate
depends on VIDEOBUF2_CORE
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the V4L2 core
#
ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
ccflags-y += -I$(srctree)/drivers/media/tuners
tuner-objs := tuner-core.o
videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \
v4l2-event.o v4l2-subdev.o v4l2-common.o \
v4l2-ctrls-core.o v4l2-ctrls-api.o \
v4l2-ctrls-request.o v4l2-ctrls-defs.o
# Please keep it alphabetically sorted by Kconfig name
# (e. g. LC_ALL=C sort Makefile)
videodev-$(CONFIG_COMPAT) += v4l2-compat-ioctl32.o
videodev-$(CONFIG_MEDIA_CONTROLLER) += v4l2-mc.o
videodev-$(CONFIG_SPI) += v4l2-spi.o
videodev-$(CONFIG_TRACEPOINTS) += v4l2-trace.o
videodev-$(CONFIG_VIDEO_V4L2_I2C) += v4l2-i2c.o
# Please keep it alphabetically sorted by Kconfig name
# (e. g. LC_ALL=C sort Makefile)
obj-$(CONFIG_V4L2_ASYNC) += v4l2-async.o
obj-$(CONFIG_V4L2_CCI) += v4l2-cci.o
obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o
obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o
obj-$(CONFIG_V4L2_H264) += v4l2-h264.o
obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o
obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o
obj-$(CONFIG_VIDEO_TUNER) += tuner.o
obj-$(CONFIG_VIDEO_DEV) += v4l2-dv-timings.o videodev.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,993 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* V4L2 asynchronous subdevice registration API
*
* Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <media/v4l2-async.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>
#include "v4l2-subdev-priv.h"
static int v4l2_async_nf_call_bound(struct v4l2_async_notifier *n,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asc)
{
if (!n->ops || !n->ops->bound)
return 0;
return n->ops->bound(n, subdev, asc);
}
static void v4l2_async_nf_call_unbind(struct v4l2_async_notifier *n,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asc)
{
if (!n->ops || !n->ops->unbind)
return;
n->ops->unbind(n, subdev, asc);
}
static int v4l2_async_nf_call_complete(struct v4l2_async_notifier *n)
{
if (!n->ops || !n->ops->complete)
return 0;
return n->ops->complete(n);
}
static void v4l2_async_nf_call_destroy(struct v4l2_async_notifier *n,
struct v4l2_async_connection *asc)
{
if (!n->ops || !n->ops->destroy)
return;
n->ops->destroy(asc);
}
static bool match_i2c(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_match_desc *match)
{
#if IS_ENABLED(CONFIG_I2C)
struct i2c_client *client = i2c_verify_client(sd->dev);
return client &&
match->i2c.adapter_id == client->adapter->nr &&
match->i2c.address == client->addr;
#else
return false;
#endif
}
static struct device *notifier_dev(struct v4l2_async_notifier *notifier)
{
if (notifier->sd)
return notifier->sd->dev;
if (notifier->v4l2_dev)
return notifier->v4l2_dev->dev;
return NULL;
}
static bool
match_fwnode_one(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd, struct fwnode_handle *sd_fwnode,
struct v4l2_async_match_desc *match)
{
struct fwnode_handle *asd_dev_fwnode;
bool ret;
dev_dbg(notifier_dev(notifier),
"v4l2-async: fwnode match: need %pfw, trying %pfw\n",
sd_fwnode, match->fwnode);
if (sd_fwnode == match->fwnode) {
dev_dbg(notifier_dev(notifier),
"v4l2-async: direct match found\n");
return true;
}
if (!fwnode_graph_is_endpoint(match->fwnode)) {
dev_dbg(notifier_dev(notifier),
"v4l2-async: direct match not found\n");
return false;
}
asd_dev_fwnode = fwnode_graph_get_port_parent(match->fwnode);
ret = sd_fwnode == asd_dev_fwnode;
fwnode_handle_put(asd_dev_fwnode);
dev_dbg(notifier_dev(notifier),
"v4l2-async: device--endpoint match %sfound\n",
ret ? "" : "not ");
return ret;
}
static bool match_fwnode(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_match_desc *match)
{
dev_dbg(notifier_dev(notifier),
"v4l2-async: matching for notifier %pfw, sd fwnode %pfw\n",
dev_fwnode(notifier_dev(notifier)), sd->fwnode);
if (!list_empty(&sd->async_subdev_endpoint_list)) {
struct v4l2_async_subdev_endpoint *ase;
dev_dbg(sd->dev,
"v4l2-async: endpoint fwnode list available, looking for %pfw\n",
match->fwnode);
list_for_each_entry(ase, &sd->async_subdev_endpoint_list,
async_subdev_endpoint_entry) {
bool matched = ase->endpoint == match->fwnode;
dev_dbg(sd->dev,
"v4l2-async: endpoint-endpoint match %sfound with %pfw\n",
matched ? "" : "not ", ase->endpoint);
if (matched)
return true;
}
dev_dbg(sd->dev, "async: no endpoint matched\n");
return false;
}
if (match_fwnode_one(notifier, sd, sd->fwnode, match))
return true;
/* Also check the secondary fwnode. */
if (IS_ERR_OR_NULL(sd->fwnode->secondary))
return false;
dev_dbg(notifier_dev(notifier),
"v4l2-async: trying secondary fwnode match\n");
return match_fwnode_one(notifier, sd, sd->fwnode->secondary, match);
}
static LIST_HEAD(subdev_list);
static LIST_HEAD(notifier_list);
static DEFINE_MUTEX(list_lock);
static struct v4l2_async_connection *
v4l2_async_find_match(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd)
{
bool (*match)(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_match_desc *match);
struct v4l2_async_connection *asc;
list_for_each_entry(asc, &notifier->waiting_list, asc_entry) {
/* bus_type has been verified valid before */
switch (asc->match.type) {
case V4L2_ASYNC_MATCH_TYPE_I2C:
match = match_i2c;
break;
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
match = match_fwnode;
break;
default:
/* Cannot happen, unless someone breaks us */
WARN_ON(true);
return NULL;
}
/* match cannot be NULL here */
if (match(notifier, sd, &asc->match))
return asc;
}
return NULL;
}
/* Compare two async match descriptors for equivalence */
static bool v4l2_async_match_equal(struct v4l2_async_match_desc *match1,
struct v4l2_async_match_desc *match2)
{
if (match1->type != match2->type)
return false;
switch (match1->type) {
case V4L2_ASYNC_MATCH_TYPE_I2C:
return match1->i2c.adapter_id == match2->i2c.adapter_id &&
match1->i2c.address == match2->i2c.address;
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
return match1->fwnode == match2->fwnode;
default:
break;
}
return false;
}
/* Find the sub-device notifier registered by a sub-device driver. */
static struct v4l2_async_notifier *
v4l2_async_find_subdev_notifier(struct v4l2_subdev *sd)
{
struct v4l2_async_notifier *n;
list_for_each_entry(n, &notifier_list, notifier_entry)
if (n->sd == sd)
return n;
return NULL;
}
/* Get v4l2_device related to the notifier if one can be found. */
static struct v4l2_device *
v4l2_async_nf_find_v4l2_dev(struct v4l2_async_notifier *notifier)
{
while (notifier->parent)
notifier = notifier->parent;
return notifier->v4l2_dev;
}
/*
* Return true if all child sub-device notifiers are complete, false otherwise.
*/
static bool
v4l2_async_nf_can_complete(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_connection *asc;
if (!list_empty(&notifier->waiting_list))
return false;
list_for_each_entry(asc, &notifier->done_list, asc_entry) {
struct v4l2_async_notifier *subdev_notifier =
v4l2_async_find_subdev_notifier(asc->sd);
if (subdev_notifier &&
!v4l2_async_nf_can_complete(subdev_notifier))
return false;
}
return true;
}
/*
* Complete the master notifier if possible. This is done when all async
* sub-devices have been bound; v4l2_device is also available then.
*/
static int
v4l2_async_nf_try_complete(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_notifier *__notifier = notifier;
/* Quick check whether there are still more sub-devices here. */
if (!list_empty(&notifier->waiting_list))
return 0;
if (notifier->sd)
dev_dbg(notifier_dev(notifier),
"v4l2-async: trying to complete\n");
/* Check the entire notifier tree; find the root notifier first. */
while (notifier->parent)
notifier = notifier->parent;
/* This is root if it has v4l2_dev. */
if (!notifier->v4l2_dev) {
dev_dbg(notifier_dev(__notifier),
"v4l2-async: V4L2 device not available\n");
return 0;
}
/* Is everything ready? */
if (!v4l2_async_nf_can_complete(notifier))
return 0;
dev_dbg(notifier_dev(__notifier), "v4l2-async: complete\n");
return v4l2_async_nf_call_complete(notifier);
}
static int
v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier);
static int v4l2_async_create_ancillary_links(struct v4l2_async_notifier *n,
struct v4l2_subdev *sd)
{
#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
struct media_link *link;
if (sd->entity.function != MEDIA_ENT_F_LENS &&
sd->entity.function != MEDIA_ENT_F_FLASH)
return 0;
if (!n->sd) {
dev_warn(notifier_dev(n),
"not a sub-device notifier, not creating an ancillary link for %s!\n",
dev_name(sd->dev));
return 0;
}
link = media_create_ancillary_link(&n->sd->entity, &sd->entity);
return IS_ERR(link) ? PTR_ERR(link) : 0;
#else
return 0;
#endif
}
static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd,
struct v4l2_async_connection *asc)
{
bool registered = false;
int ret;
if (list_empty(&sd->asc_list)) {
ret = __v4l2_device_register_subdev(v4l2_dev, sd, sd->owner);
if (ret < 0)
return ret;
registered = true;
}
ret = v4l2_async_nf_call_bound(notifier, sd, asc);
if (ret < 0) {
if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
dev_dbg(notifier_dev(notifier),
"failed binding %pfw (%d)\n",
asc->match.fwnode, ret);
goto err_unregister_subdev;
}
if (registered) {
/*
* Depending of the function of the entities involved, we may
* want to create links between them (for example between a
* sensor and its lens or between a sensor's source pad and the
* connected device's sink pad).
*/
ret = v4l2_async_create_ancillary_links(notifier, sd);
if (ret) {
if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
dev_dbg(notifier_dev(notifier),
"failed creating links for %pfw (%d)\n",
asc->match.fwnode, ret);
goto err_call_unbind;
}
}
list_add(&asc->asc_subdev_entry, &sd->asc_list);
asc->sd = sd;
/* Move from the waiting list to notifier's done */
list_move(&asc->asc_entry, &notifier->done_list);
dev_dbg(notifier_dev(notifier), "v4l2-async: %s bound (ret %d)\n",
dev_name(sd->dev), ret);
return 0;
err_call_unbind:
v4l2_async_nf_call_unbind(notifier, sd, asc);
list_del(&asc->asc_subdev_entry);
err_unregister_subdev:
if (registered)
v4l2_device_unregister_subdev(sd);
return ret;
}
static int
v4l2_async_nf_try_subdev_notifier(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd)
{
struct v4l2_async_notifier *subdev_notifier;
/*
* See if the sub-device has a notifier. If not, return here.
*/
subdev_notifier = v4l2_async_find_subdev_notifier(sd);
if (!subdev_notifier || subdev_notifier->parent)
return 0;
/*
* Proceed with checking for the sub-device notifier's async
* sub-devices, and return the result. The error will be handled by the
* caller.
*/
subdev_notifier->parent = notifier;
return v4l2_async_nf_try_all_subdevs(subdev_notifier);
}
/* Test all async sub-devices in a notifier for a match. */
static int
v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier)
{
struct v4l2_device *v4l2_dev =
v4l2_async_nf_find_v4l2_dev(notifier);
struct v4l2_subdev *sd;
if (!v4l2_dev)
return 0;
dev_dbg(notifier_dev(notifier), "v4l2-async: trying all sub-devices\n");
again:
list_for_each_entry(sd, &subdev_list, async_list) {
struct v4l2_async_connection *asc;
int ret;
asc = v4l2_async_find_match(notifier, sd);
if (!asc)
continue;
dev_dbg(notifier_dev(notifier),
"v4l2-async: match found, subdev %s\n", sd->name);
ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asc);
if (ret < 0)
return ret;
ret = v4l2_async_nf_try_subdev_notifier(notifier, sd);
if (ret < 0)
return ret;
/*
* v4l2_async_match_notify() may lead to registering a
* new notifier and thus changing the async subdevs
* list. In order to proceed safely from here, restart
* parsing the list from the beginning.
*/
goto again;
}
return 0;
}
static void v4l2_async_unbind_subdev_one(struct v4l2_async_notifier *notifier,
struct v4l2_async_connection *asc)
{
list_move_tail(&asc->asc_entry, &notifier->waiting_list);
if (list_is_singular(&asc->asc_subdev_entry)) {
v4l2_async_nf_call_unbind(notifier, asc->sd, asc);
v4l2_device_unregister_subdev(asc->sd);
asc->sd = NULL;
}
list_del(&asc->asc_subdev_entry);
}
/* Unbind all sub-devices in the notifier tree. */
static void
v4l2_async_nf_unbind_all_subdevs(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_connection *asc, *asc_tmp;
list_for_each_entry_safe(asc, asc_tmp, &notifier->done_list,
asc_entry) {
struct v4l2_async_notifier *subdev_notifier =
v4l2_async_find_subdev_notifier(asc->sd);
if (subdev_notifier)
v4l2_async_nf_unbind_all_subdevs(subdev_notifier);
v4l2_async_unbind_subdev_one(notifier, asc);
}
notifier->parent = NULL;
}
/* See if an async sub-device can be found in a notifier's lists. */
static bool
v4l2_async_nf_has_async_match_entry(struct v4l2_async_notifier *notifier,
struct v4l2_async_match_desc *match)
{
struct v4l2_async_connection *asc;
list_for_each_entry(asc, &notifier->waiting_list, asc_entry)
if (v4l2_async_match_equal(&asc->match, match))
return true;
list_for_each_entry(asc, &notifier->done_list, asc_entry)
if (v4l2_async_match_equal(&asc->match, match))
return true;
return false;
}
/*
* Find out whether an async sub-device was set up already or whether it exists
* in a given notifier.
*/
static bool
v4l2_async_nf_has_async_match(struct v4l2_async_notifier *notifier,
struct v4l2_async_match_desc *match)
{
struct list_head *heads[] = {
&notifier->waiting_list,
&notifier->done_list,
};
unsigned int i;
lockdep_assert_held(&list_lock);
/* Check that an asd is not being added more than once. */
for (i = 0; i < ARRAY_SIZE(heads); i++) {
struct v4l2_async_connection *asc;
list_for_each_entry(asc, heads[i], asc_entry) {
if (&asc->match == match)
continue;
if (v4l2_async_match_equal(&asc->match, match))
return true;
}
}
/* Check that an asc does not exist in other notifiers. */
list_for_each_entry(notifier, &notifier_list, notifier_entry)
if (v4l2_async_nf_has_async_match_entry(notifier, match))
return true;
return false;
}
static int v4l2_async_nf_match_valid(struct v4l2_async_notifier *notifier,
struct v4l2_async_match_desc *match)
{
struct device *dev = notifier_dev(notifier);
switch (match->type) {
case V4L2_ASYNC_MATCH_TYPE_I2C:
case V4L2_ASYNC_MATCH_TYPE_FWNODE:
if (v4l2_async_nf_has_async_match(notifier, match)) {
dev_dbg(dev, "v4l2-async: match descriptor already listed in a notifier\n");
return -EEXIST;
}
break;
default:
dev_err(dev, "v4l2-async: Invalid match type %u on %p\n",
match->type, match);
return -EINVAL;
}
return 0;
}
void v4l2_async_nf_init(struct v4l2_async_notifier *notifier,
struct v4l2_device *v4l2_dev)
{
INIT_LIST_HEAD(&notifier->waiting_list);
INIT_LIST_HEAD(&notifier->done_list);
INIT_LIST_HEAD(&notifier->notifier_entry);
notifier->v4l2_dev = v4l2_dev;
}
EXPORT_SYMBOL(v4l2_async_nf_init);
void v4l2_async_subdev_nf_init(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd)
{
INIT_LIST_HEAD(&notifier->waiting_list);
INIT_LIST_HEAD(&notifier->done_list);
INIT_LIST_HEAD(&notifier->notifier_entry);
notifier->sd = sd;
}
EXPORT_SYMBOL_GPL(v4l2_async_subdev_nf_init);
static int __v4l2_async_nf_register(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_connection *asc;
int ret;
mutex_lock(&list_lock);
list_for_each_entry(asc, &notifier->waiting_list, asc_entry) {
ret = v4l2_async_nf_match_valid(notifier, &asc->match);
if (ret)
goto err_unlock;
}
ret = v4l2_async_nf_try_all_subdevs(notifier);
if (ret < 0)
goto err_unbind;
ret = v4l2_async_nf_try_complete(notifier);
if (ret < 0)
goto err_unbind;
/* Keep also completed notifiers on the list */
list_add(&notifier->notifier_entry, &notifier_list);
mutex_unlock(&list_lock);
return 0;
err_unbind:
/*
* On failure, unbind all sub-devices registered through this notifier.
*/
v4l2_async_nf_unbind_all_subdevs(notifier);
err_unlock:
mutex_unlock(&list_lock);
return ret;
}
int v4l2_async_nf_register(struct v4l2_async_notifier *notifier)
{
if (WARN_ON(!notifier->v4l2_dev == !notifier->sd))
return -EINVAL;
return __v4l2_async_nf_register(notifier);
}
EXPORT_SYMBOL(v4l2_async_nf_register);
static void
__v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier)
{
if (!notifier || (!notifier->v4l2_dev && !notifier->sd))
return;
v4l2_async_nf_unbind_all_subdevs(notifier);
list_del_init(&notifier->notifier_entry);
}
void v4l2_async_nf_unregister(struct v4l2_async_notifier *notifier)
{
mutex_lock(&list_lock);
__v4l2_async_nf_unregister(notifier);
mutex_unlock(&list_lock);
}
EXPORT_SYMBOL(v4l2_async_nf_unregister);
static void __v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
{
struct v4l2_async_connection *asc, *tmp;
if (!notifier || !notifier->waiting_list.next)
return;
WARN_ON(!list_empty(&notifier->done_list));
list_for_each_entry_safe(asc, tmp, &notifier->waiting_list, asc_entry) {
list_del(&asc->asc_entry);
v4l2_async_nf_call_destroy(notifier, asc);
if (asc->match.type == V4L2_ASYNC_MATCH_TYPE_FWNODE)
fwnode_handle_put(asc->match.fwnode);
kfree(asc);
}
notifier->sd = NULL;
notifier->v4l2_dev = NULL;
}
void v4l2_async_nf_cleanup(struct v4l2_async_notifier *notifier)
{
mutex_lock(&list_lock);
__v4l2_async_nf_cleanup(notifier);
mutex_unlock(&list_lock);
}
EXPORT_SYMBOL_GPL(v4l2_async_nf_cleanup);
static void __v4l2_async_nf_add_connection(struct v4l2_async_notifier *notifier,
struct v4l2_async_connection *asc)
{
mutex_lock(&list_lock);
list_add_tail(&asc->asc_entry, &notifier->waiting_list);
mutex_unlock(&list_lock);
}
struct v4l2_async_connection *
__v4l2_async_nf_add_fwnode(struct v4l2_async_notifier *notifier,
struct fwnode_handle *fwnode,
unsigned int asc_struct_size)
{
struct v4l2_async_connection *asc;
asc = kzalloc(asc_struct_size, GFP_KERNEL);
if (!asc)
return ERR_PTR(-ENOMEM);
asc->notifier = notifier;
asc->match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
asc->match.fwnode = fwnode_handle_get(fwnode);
__v4l2_async_nf_add_connection(notifier, asc);
return asc;
}
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_fwnode);
struct v4l2_async_connection *
__v4l2_async_nf_add_fwnode_remote(struct v4l2_async_notifier *notif,
struct fwnode_handle *endpoint,
unsigned int asc_struct_size)
{
struct v4l2_async_connection *asc;
struct fwnode_handle *remote;
remote = fwnode_graph_get_remote_endpoint(endpoint);
if (!remote)
return ERR_PTR(-ENOTCONN);
asc = __v4l2_async_nf_add_fwnode(notif, remote, asc_struct_size);
/*
* Calling __v4l2_async_nf_add_fwnode grabs a refcount,
* so drop the one we got in fwnode_graph_get_remote_port_parent.
*/
fwnode_handle_put(remote);
return asc;
}
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_fwnode_remote);
struct v4l2_async_connection *
__v4l2_async_nf_add_i2c(struct v4l2_async_notifier *notifier, int adapter_id,
unsigned short address, unsigned int asc_struct_size)
{
struct v4l2_async_connection *asc;
asc = kzalloc(asc_struct_size, GFP_KERNEL);
if (!asc)
return ERR_PTR(-ENOMEM);
asc->notifier = notifier;
asc->match.type = V4L2_ASYNC_MATCH_TYPE_I2C;
asc->match.i2c.adapter_id = adapter_id;
asc->match.i2c.address = address;
__v4l2_async_nf_add_connection(notifier, asc);
return asc;
}
EXPORT_SYMBOL_GPL(__v4l2_async_nf_add_i2c);
int v4l2_async_subdev_endpoint_add(struct v4l2_subdev *sd,
struct fwnode_handle *fwnode)
{
struct v4l2_async_subdev_endpoint *ase;
ase = kmalloc_obj(*ase);
if (!ase)
return -ENOMEM;
ase->endpoint = fwnode;
list_add(&ase->async_subdev_endpoint_entry,
&sd->async_subdev_endpoint_list);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_async_subdev_endpoint_add);
struct v4l2_async_connection *
v4l2_async_connection_unique(struct v4l2_subdev *sd)
{
if (!list_is_singular(&sd->asc_list))
return NULL;
return list_first_entry(&sd->asc_list,
struct v4l2_async_connection, asc_subdev_entry);
}
EXPORT_SYMBOL_GPL(v4l2_async_connection_unique);
int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module)
{
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_async_notifier *notifier;
struct v4l2_async_connection *asc;
int ret;
INIT_LIST_HEAD(&sd->asc_list);
/*
* No reference taken. The reference is held by the device (struct
* v4l2_subdev.dev), and async sub-device does not exist independently
* of the device at any point of time.
*
* The async sub-device shall always be registered for its device node,
* not the endpoint node.
*/
if (!sd->fwnode && sd->dev) {
sd->fwnode = dev_fwnode(sd->dev);
} else if (fwnode_graph_is_endpoint(sd->fwnode)) {
dev_warn(sd->dev, "sub-device fwnode is an endpoint!\n");
return -EINVAL;
}
sd->owner = module;
mutex_lock(&list_lock);
list_for_each_entry(notifier, &notifier_list, notifier_entry) {
struct v4l2_device *v4l2_dev =
v4l2_async_nf_find_v4l2_dev(notifier);
if (!v4l2_dev)
continue;
while ((asc = v4l2_async_find_match(notifier, sd))) {
ret = v4l2_async_match_notify(notifier, v4l2_dev, sd,
asc);
if (ret)
goto err_unlock;
ret = v4l2_async_nf_try_subdev_notifier(notifier, sd);
if (ret)
goto err_unbind_one;
ret = v4l2_async_nf_try_complete(notifier);
if (ret)
goto err_unbind;
}
}
/* None matched, wait for hot-plugging */
list_add(&sd->async_list, &subdev_list);
mutex_unlock(&list_lock);
return 0;
err_unbind:
/*
* Complete failed. Unbind the sub-devices bound through registering
* this async sub-device.
*/
subdev_notifier = v4l2_async_find_subdev_notifier(sd);
if (subdev_notifier)
v4l2_async_nf_unbind_all_subdevs(subdev_notifier);
err_unbind_one:
v4l2_async_unbind_subdev_one(notifier, asc);
err_unlock:
mutex_unlock(&list_lock);
sd->owner = NULL;
return ret;
}
EXPORT_SYMBOL(__v4l2_async_register_subdev);
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
{
struct v4l2_async_connection *asc, *asc_tmp;
if (!sd->async_list.next)
return;
v4l2_subdev_put_privacy_led(sd);
mutex_lock(&list_lock);
__v4l2_async_nf_unregister(sd->subdev_notifier);
__v4l2_async_nf_cleanup(sd->subdev_notifier);
kfree(sd->subdev_notifier);
sd->subdev_notifier = NULL;
if (sd->asc_list.next) {
list_for_each_entry_safe(asc, asc_tmp, &sd->asc_list,
asc_subdev_entry) {
v4l2_async_unbind_subdev_one(asc->notifier, asc);
}
}
list_del(&sd->async_list);
sd->async_list.next = NULL;
mutex_unlock(&list_lock);
}
EXPORT_SYMBOL(v4l2_async_unregister_subdev);
static void print_waiting_match(struct seq_file *s,
struct v4l2_async_match_desc *match)
{
switch (match->type) {
case V4L2_ASYNC_MATCH_TYPE_I2C:
seq_printf(s, " [i2c] dev=%d-%04x\n", match->i2c.adapter_id,
match->i2c.address);
break;
case V4L2_ASYNC_MATCH_TYPE_FWNODE: {
struct fwnode_handle *devnode, *fwnode = match->fwnode;
devnode = fwnode_graph_is_endpoint(fwnode) ?
fwnode_graph_get_port_parent(fwnode) :
fwnode_handle_get(fwnode);
seq_printf(s, " [fwnode] dev=%s, node=%pfw\n",
devnode->dev ? dev_name(devnode->dev) : "nil",
fwnode);
fwnode_handle_put(devnode);
break;
}
}
}
static const char *
v4l2_async_nf_name(struct v4l2_async_notifier *notifier)
{
if (notifier->v4l2_dev)
return notifier->v4l2_dev->name;
else if (notifier->sd)
return notifier->sd->name;
else
return "nil";
}
static int pending_subdevs_show(struct seq_file *s, void *data)
{
struct v4l2_async_notifier *notif;
struct v4l2_async_connection *asc;
mutex_lock(&list_lock);
list_for_each_entry(notif, &notifier_list, notifier_entry) {
seq_printf(s, "%s:\n", v4l2_async_nf_name(notif));
list_for_each_entry(asc, &notif->waiting_list, asc_entry)
print_waiting_match(s, &asc->match);
}
mutex_unlock(&list_lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pending_subdevs);
static struct dentry *v4l2_async_debugfs_dir;
static int __init v4l2_async_init(void)
{
v4l2_async_debugfs_dir = debugfs_create_dir("v4l2-async", NULL);
debugfs_create_file("pending_async_subdevices", 0444,
v4l2_async_debugfs_dir, NULL,
&pending_subdevs_fops);
return 0;
}
static void __exit v4l2_async_exit(void)
{
debugfs_remove_recursive(v4l2_async_debugfs_dir);
}
subsys_initcall(v4l2_async_init);
module_exit(v4l2_async_exit);
MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
MODULE_AUTHOR("Ezequiel Garcia <ezequiel@collabora.com>");
MODULE_DESCRIPTION("V4L2 asynchronous subdevice registration API");
MODULE_LICENSE("GPL");
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MIPI Camera Control Interface (CCI) register access helpers.
*
* Copyright (C) 2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <media/v4l2-cci.h>
int cci_read(struct regmap *map, u32 reg, u64 *val, int *err)
{
bool little_endian;
unsigned int len;
u8 buf[8];
int ret;
/*
* TODO: Fix smatch. Assign *val to 0 here in order to avoid
* failing a smatch check on caller when the caller proceeds to
* read *val without initialising it on caller's side. *val is set
* to a valid value whenever this function returns 0 but smatch
* can't figure that out currently.
*/
*val = 0;
if (err && *err)
return *err;
little_endian = reg & CCI_REG_LE;
len = CCI_REG_WIDTH_BYTES(reg);
reg = CCI_REG_ADDR(reg);
ret = regmap_bulk_read(map, reg, buf, len);
if (ret) {
dev_err(regmap_get_device(map), "Error reading reg 0x%04x: %d\n",
reg, ret);
goto out;
}
switch (len) {
case 1:
*val = buf[0];
break;
case 2:
if (little_endian)
*val = get_unaligned_le16(buf);
else
*val = get_unaligned_be16(buf);
break;
case 3:
if (little_endian)
*val = get_unaligned_le24(buf);
else
*val = get_unaligned_be24(buf);
break;
case 4:
if (little_endian)
*val = get_unaligned_le32(buf);
else
*val = get_unaligned_be32(buf);
break;
case 8:
if (little_endian)
*val = get_unaligned_le64(buf);
else
*val = get_unaligned_be64(buf);
break;
default:
dev_err(regmap_get_device(map), "Error invalid reg-width %u for reg 0x%04x\n",
len, reg);
ret = -EINVAL;
break;
}
out:
if (ret && err)
*err = ret;
return ret;
}
EXPORT_SYMBOL_GPL(cci_read);
int cci_write(struct regmap *map, u32 reg, u64 val, int *err)
{
bool little_endian;
unsigned int len;
u8 buf[8];
int ret;
if (err && *err)
return *err;
little_endian = reg & CCI_REG_LE;
len = CCI_REG_WIDTH_BYTES(reg);
reg = CCI_REG_ADDR(reg);
switch (len) {
case 1:
buf[0] = val;
break;
case 2:
if (little_endian)
put_unaligned_le16(val, buf);
else
put_unaligned_be16(val, buf);
break;
case 3:
if (little_endian)
put_unaligned_le24(val, buf);
else
put_unaligned_be24(val, buf);
break;
case 4:
if (little_endian)
put_unaligned_le32(val, buf);
else
put_unaligned_be32(val, buf);
break;
case 8:
if (little_endian)
put_unaligned_le64(val, buf);
else
put_unaligned_be64(val, buf);
break;
default:
dev_err(regmap_get_device(map), "Error invalid reg-width %u for reg 0x%04x\n",
len, reg);
ret = -EINVAL;
goto out;
}
ret = regmap_bulk_write(map, reg, buf, len);
if (ret)
dev_err(regmap_get_device(map), "Error writing reg 0x%04x: %d\n",
reg, ret);
out:
if (ret && err)
*err = ret;
return ret;
}
EXPORT_SYMBOL_GPL(cci_write);
int cci_update_bits(struct regmap *map, u32 reg, u64 mask, u64 val, int *err)
{
u64 readval;
int ret;
ret = cci_read(map, reg, &readval, err);
if (ret)
return ret;
val = (readval & ~mask) | (val & mask);
return cci_write(map, reg, val, err);
}
EXPORT_SYMBOL_GPL(cci_update_bits);
int cci_multi_reg_write(struct regmap *map, const struct cci_reg_sequence *regs,
unsigned int num_regs, int *err)
{
unsigned int i;
int ret;
for (i = 0; i < num_regs; i++) {
ret = cci_write(map, regs[i].reg, regs[i].val, err);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(cci_multi_reg_write);
#if IS_ENABLED(CONFIG_V4L2_CCI_I2C)
struct regmap *devm_cci_regmap_init_i2c(struct i2c_client *client,
int reg_addr_bits)
{
struct regmap_config config = {
.reg_bits = reg_addr_bits,
.val_bits = 8,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.disable_locking = true,
};
return devm_regmap_init_i2c(client, &config);
}
EXPORT_SYMBOL_GPL(devm_cci_regmap_init_i2c);
#endif
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("MIPI Camera Control Interface (CCI) support");
@@ -0,0 +1,869 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Video for Linux Two
*
* A generic video device interface for the LINUX operating system
* using a set of device structures/vectors for low level operations.
*
* This file replaces the videodev.c file that comes with the
* regular kernel distribution.
*
* Author: Bill Dirks <bill@thedirks.org>
* based on code by Alan Cox, <alan@cymru.net>
*/
/*
* Video capture interface for Linux
*
* A generic video device interface for the LINUX operating system
* using a set of device structures/vectors for low level operations.
*
* Author: Alan Cox, <alan@lxorguk.ukuu.org.uk>
*
* Fixes:
*/
/*
* Video4linux 1/2 integration by Justin Schoeman
* <justin@suntiger.ee.up.ac.za>
* 2.4 PROCFS support ported from 2.4 kernels by
* Iñaki García Etxebarria <garetxe@euskalnet.net>
* Makefile fix by "W. Michael Petullo" <mike@flyn.org>
* 2.4 devfs support ported from 2.4 kernels by
* Dan Merillat <dan@merillat.org>
* Added Gerd Knorrs v4l1 enhancements (Justin Schoeman)
*/
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
#include <linux/videodev2.h>
/*
*
* V 4 L 2 D R I V E R H E L P E R A P I
*
*/
/*
* Video Standard Operations (contributed by Michael Schimek)
*/
/* Helper functions for control handling */
/* Fill in a struct v4l2_queryctrl */
int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 _min, s32 _max, s32 _step, s32 _def)
{
const char *name;
s64 min = _min;
s64 max = _max;
u64 step = _step;
s64 def = _def;
v4l2_ctrl_fill(qctrl->id, &name, &qctrl->type,
&min, &max, &step, &def, &qctrl->flags);
if (name == NULL)
return -EINVAL;
qctrl->minimum = min;
qctrl->maximum = max;
qctrl->step = step;
qctrl->default_value = def;
qctrl->reserved[0] = qctrl->reserved[1] = 0;
strscpy(qctrl->name, name, sizeof(qctrl->name));
return 0;
}
EXPORT_SYMBOL(v4l2_ctrl_query_fill);
/* Clamp x to be between min and max, aligned to a multiple of 2^align. min
* and max don't have to be aligned, but there must be at least one valid
* value. E.g., min=17,max=31,align=4 is not allowed as there are no multiples
* of 16 between 17 and 31. */
static unsigned int clamp_align(unsigned int x, unsigned int min,
unsigned int max, unsigned int align)
{
/* Bits that must be zero to be aligned */
unsigned int mask = ~((1 << align) - 1);
/* Clamp to aligned min and max */
x = clamp(x, (min + ~mask) & mask, max & mask);
/* Round to nearest aligned value */
if (align)
x = (x + (1 << (align - 1))) & mask;
return x;
}
static unsigned int clamp_roundup(unsigned int x, unsigned int min,
unsigned int max, unsigned int alignment)
{
x = clamp(x, min, max);
if (alignment)
x = round_up(x, alignment);
return x;
}
void v4l_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax,
unsigned int walign,
u32 *h, unsigned int hmin, unsigned int hmax,
unsigned int halign, unsigned int salign)
{
*w = clamp_align(*w, wmin, wmax, walign);
*h = clamp_align(*h, hmin, hmax, halign);
/* Usually we don't need to align the size and are done now. */
if (!salign)
return;
/* How much alignment do we have? */
walign = __ffs(*w);
halign = __ffs(*h);
/* Enough to satisfy the image alignment? */
if (walign + halign < salign) {
/* Max walign where there is still a valid width */
unsigned int wmaxa = __fls(wmax ^ (wmin - 1));
/* Max halign where there is still a valid height */
unsigned int hmaxa = __fls(hmax ^ (hmin - 1));
/* up the smaller alignment until we have enough */
do {
if (halign >= hmaxa ||
(walign <= halign && walign < wmaxa)) {
*w = clamp_align(*w, wmin, wmax, walign + 1);
walign = __ffs(*w);
} else {
*h = clamp_align(*h, hmin, hmax, halign + 1);
halign = __ffs(*h);
}
} while (halign + walign < salign);
}
}
EXPORT_SYMBOL_GPL(v4l_bound_align_image);
const void *
__v4l2_find_nearest_size_conditional(const void *array, size_t array_size,
size_t entry_size, size_t width_offset,
size_t height_offset, s32 width,
s32 height,
bool (*func)(const void *array,
size_t index,
const void *context),
const void *context)
{
u32 error, min_error = U32_MAX;
const void *best = NULL;
size_t i;
if (!array)
return NULL;
for (i = 0; i < array_size; i++, array += entry_size) {
const u32 *entry_width = array + width_offset;
const u32 *entry_height = array + height_offset;
if (func && !func(array, i, context))
continue;
error = abs(*entry_width - width) + abs(*entry_height - height);
if (error > min_error)
continue;
min_error = error;
best = array;
if (!error)
break;
}
return best;
}
EXPORT_SYMBOL_GPL(__v4l2_find_nearest_size_conditional);
int v4l2_g_parm_cap(struct video_device *vdev,
struct v4l2_subdev *sd, struct v4l2_streamparm *a)
{
struct v4l2_subdev_frame_interval ival = { 0 };
int ret;
if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
return -EINVAL;
if (vdev->device_caps & V4L2_CAP_READWRITE)
a->parm.capture.readbuffers = 2;
if (v4l2_subdev_has_op(sd, pad, get_frame_interval))
a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
ret = v4l2_subdev_call_state_active(sd, pad, get_frame_interval, &ival);
if (!ret)
a->parm.capture.timeperframe = ival.interval;
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_g_parm_cap);
int v4l2_s_parm_cap(struct video_device *vdev,
struct v4l2_subdev *sd, struct v4l2_streamparm *a)
{
struct v4l2_subdev_frame_interval ival = {
.interval = a->parm.capture.timeperframe
};
int ret;
if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
return -EINVAL;
memset(&a->parm, 0, sizeof(a->parm));
if (vdev->device_caps & V4L2_CAP_READWRITE)
a->parm.capture.readbuffers = 2;
else
a->parm.capture.readbuffers = 0;
if (v4l2_subdev_has_op(sd, pad, get_frame_interval))
a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
ret = v4l2_subdev_call_state_active(sd, pad, set_frame_interval, &ival);
if (!ret)
a->parm.capture.timeperframe = ival.interval;
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_s_parm_cap);
const struct v4l2_format_info *v4l2_format_info(u32 format)
{
static const struct v4l2_format_info formats[] = {
/* RGB formats (1 or 2 bytes per pixel) */
{ .format = V4L2_PIX_FMT_RGB332, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ARGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ABGR444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGRA444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX444, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ARGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ABGR555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGRA555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX555, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB565, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ARGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB555X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB565X, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
/* RGB formats (3 or 4 bytes per pixel) */
{ .format = V4L2_PIX_FMT_BGR666, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGR24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ABGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XBGR32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGRA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_BGRX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBA32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_RGBX32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ARGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_XRGB32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBX1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGBA1010102, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
{ .format = V4L2_PIX_FMT_ARGB2101010, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
/* RGB formats (6 or 8 bytes per pixel) */
{ .format = V4L2_PIX_FMT_BGR48_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_BGR48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RGB48, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_ABGR64_12, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1, .has_alpha = true },
/* HSV formats */
{ .format = V4L2_PIX_FMT_HSV24, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_HSV32, .pixel_enc = V4L2_PIXEL_ENC_RGB, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
/* YUV packed formats */
{ .format = V4L2_PIX_FMT_YUYV, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YVYU, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_UYVY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_VYUY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_Y210, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_Y212, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_Y216, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 4, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YUV48_12, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 6, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YUV24, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_MT2110T, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 2,
.block_w = { 16, 8, 0, 0 }, .block_h = { 32, 16, 0, 0 }},
{ .format = V4L2_PIX_FMT_MT2110R, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 2,
.block_w = { 16, 8, 0, 0 }, .block_h = { 32, 16, 0, 0 }},
/* YUV planar formats */
{ .format = V4L2_PIX_FMT_NV12, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV21, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV15, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV61, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV20, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV24, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV42, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_P010, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_P012, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_YUV410, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 4, .vdiv = 4 },
{ .format = V4L2_PIX_FMT_YVU410, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 4, .vdiv = 4 },
{ .format = V4L2_PIX_FMT_YUV411P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 4, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YUV420, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_YVU420, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_YUV422P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_GREY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
/* Tiled YUV formats */
{ .format = V4L2_PIX_FMT_NV12_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV15_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 2,
.block_w = { 4, 2, 0, 0 }, .block_h = { 1, 1, 0, 0 }},
{ .format = V4L2_PIX_FMT_P010_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
/* YUV planar formats, non contiguous variant */
{ .format = V4L2_PIX_FMT_YUV420M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_YVU420M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_YUV422M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YVU422M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YUV444M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_YVU444M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 3, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV12M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV21M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
{ .format = V4L2_PIX_FMT_NV16M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_NV61M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_P012M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
/* Tiled YUV formats, non contiguous variant */
{ .format = V4L2_PIX_FMT_NV12MT, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2,
.block_w = { 64, 32, 0, 0 }, .block_h = { 32, 16, 0, 0 }},
{ .format = V4L2_PIX_FMT_NV12MT_16X16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2,
.block_w = { 16, 8, 0, 0 }, .block_h = { 16, 8, 0, 0 }},
/* Bayer RGB formats */
{ .format = V4L2_PIX_FMT_SBGGR8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR10DPCM8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG10DPCM8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG10DPCM8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB10DPCM8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SBGGR16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGBRG16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SGRBG16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_SRGGB16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
/* Renesas Camera Data Receiver Unit formats, bayer order agnostic */
{ .format = V4L2_PIX_FMT_RAW_CRU10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 6, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RAW_CRU12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 5, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RAW_CRU14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
{ .format = V4L2_PIX_FMT_RAW_CRU20, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 3, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(formats); ++i)
if (formats[i].format == format)
return &formats[i];
return NULL;
}
EXPORT_SYMBOL(v4l2_format_info);
static inline unsigned int v4l2_format_block_width(const struct v4l2_format_info *info, int plane)
{
if (!info->block_w[plane])
return 1;
return info->block_w[plane];
}
static inline unsigned int v4l2_format_block_height(const struct v4l2_format_info *info, int plane)
{
if (!info->block_h[plane])
return 1;
return info->block_h[plane];
}
static inline unsigned int v4l2_format_plane_stride(const struct v4l2_format_info *info, int plane,
unsigned int width, unsigned int byte_alignment)
{
unsigned int hdiv = plane ? info->hdiv : 1;
unsigned int aligned_width =
ALIGN(width, v4l2_format_block_width(info, plane));
/*
* Formats with a single memory plane derive the stride of the
* other planes from the y stride. To avoid hardware or software
* deriving a different stride for the composite plane,
* multiply the alignment accordingly.
*
* It assumes the following format properties:
* - bpp_div[0] == bpp_div[1]
* - The multiplication factor doesn't differ between the non y planes
* - The multiplication factor is a power of 2
*/
if (info->mem_planes == 1 && info->comp_planes > 1) {
if (plane == 0)
byte_alignment *= DIV_ROUND_UP(info->hdiv * info->bpp[0], info->bpp[1]);
else
byte_alignment *= DIV_ROUND_UP(info->bpp[1], info->hdiv * info->bpp[0]);
}
return ALIGN(DIV_ROUND_UP(aligned_width, hdiv) * info->bpp[plane] /
info->bpp_div[plane],
byte_alignment);
}
static inline unsigned int v4l2_format_plane_height(const struct v4l2_format_info *info, int plane,
unsigned int height)
{
unsigned int vdiv = plane ? info->vdiv : 1;
unsigned int aligned_height =
ALIGN(height, v4l2_format_block_height(info, plane));
return DIV_ROUND_UP(aligned_height, vdiv);
}
static inline unsigned int v4l2_format_plane_size(const struct v4l2_format_info *info, int plane,
unsigned int width, unsigned int height,
u8 stride_alignment)
{
return v4l2_format_plane_stride(info, plane, width, stride_alignment) *
v4l2_format_plane_height(info, plane, height);
}
void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
const struct v4l2_frmsize_stepwise *frmsize)
{
if (!frmsize)
return;
/*
* Clamp width/height to meet min/max constraints and round it up to
* macroblock alignment.
*/
*width = clamp_roundup(*width, frmsize->min_width, frmsize->max_width,
frmsize->step_width);
*height = clamp_roundup(*height, frmsize->min_height, frmsize->max_height,
frmsize->step_height);
}
EXPORT_SYMBOL_GPL(v4l2_apply_frmsize_constraints);
int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt,
u32 pixelformat, u32 width, u32 height,
u8 stride_alignment)
{
const struct v4l2_format_info *info;
struct v4l2_plane_pix_format *plane;
int i;
info = v4l2_format_info(pixelformat);
if (!info)
return -EINVAL;
pixfmt->width = width;
pixfmt->height = height;
pixfmt->pixelformat = pixelformat;
pixfmt->num_planes = info->mem_planes;
if (info->mem_planes == 1) {
plane = &pixfmt->plane_fmt[0];
plane->bytesperline = v4l2_format_plane_stride(info, 0, width,
stride_alignment);
plane->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
plane->sizeimage +=
v4l2_format_plane_size(info, i, width, height,
stride_alignment);
} else {
for (i = 0; i < info->comp_planes; i++) {
plane = &pixfmt->plane_fmt[i];
plane->bytesperline =
v4l2_format_plane_stride(info, i, width,
stride_alignment);
plane->sizeimage = plane->bytesperline *
v4l2_format_plane_height(info, i, height);
}
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned);
int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
u32 pixelformat, u32 width, u32 height)
{
return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat,
width, height, 1);
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp);
int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
u32 width, u32 height)
{
const struct v4l2_format_info *info;
int i;
info = v4l2_format_info(pixelformat);
if (!info)
return -EINVAL;
/* Single planar API cannot be used for multi plane formats. */
if (info->mem_planes > 1)
return -EINVAL;
pixfmt->width = width;
pixfmt->height = height;
pixfmt->pixelformat = pixelformat;
pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, 1);
pixfmt->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
pixfmt->sizeimage +=
v4l2_format_plane_size(info, i, width, height, 1);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
#ifdef CONFIG_MEDIA_CONTROLLER
static s64 v4l2_get_link_freq_ctrl(struct v4l2_ctrl_handler *handler,
unsigned int mul, unsigned int div)
{
struct v4l2_ctrl *ctrl;
s64 freq;
ctrl = v4l2_ctrl_find(handler, V4L2_CID_LINK_FREQ);
if (ctrl) {
struct v4l2_querymenu qm = { .id = V4L2_CID_LINK_FREQ };
int ret;
qm.index = v4l2_ctrl_g_ctrl(ctrl);
ret = v4l2_querymenu(handler, &qm);
if (ret)
return -ENOENT;
freq = qm.value;
} else {
if (!mul || !div)
return -ENOENT;
ctrl = v4l2_ctrl_find(handler, V4L2_CID_PIXEL_RATE);
if (!ctrl)
return -ENOENT;
freq = div_u64(v4l2_ctrl_g_ctrl_int64(ctrl) * mul, div);
pr_warn_once("%s: Link frequency estimated using pixel rate: result might be inaccurate\n",
__func__);
pr_warn_once("%s: Consider implementing support for V4L2_CID_LINK_FREQ in the transmitter driver\n",
__func__);
}
return freq > 0 ? freq : -EINVAL;
}
s64 v4l2_get_link_freq(const struct media_pad *pad, unsigned int mul,
unsigned int div)
{
struct v4l2_mbus_config mbus_config = {};
struct v4l2_subdev *sd;
int ret;
sd = media_entity_to_v4l2_subdev(pad->entity);
ret = v4l2_subdev_call(sd, pad, get_mbus_config, pad->index,
&mbus_config);
if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
if (mbus_config.link_freq)
return mbus_config.link_freq;
/*
* Fall back to using the link frequency control if the media bus config
* doesn't provide a link frequency.
*/
return v4l2_get_link_freq_ctrl(sd->ctrl_handler, mul, div);
}
EXPORT_SYMBOL_GPL(v4l2_get_link_freq);
int v4l2_get_active_data_lanes(const struct media_pad *pad,
unsigned int max_data_lanes)
{
struct v4l2_mbus_config mbus_config = {};
struct v4l2_subdev *sd;
unsigned int lanes;
int ret;
sd = media_entity_to_v4l2_subdev(pad->entity);
ret = v4l2_subdev_call(sd, pad, get_mbus_config, pad->index,
&mbus_config);
if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
/* This relies on the mbus_config being zeroed at init time */
lanes = mbus_config.bus.mipi_csi2.num_data_lanes;
if (!lanes)
return max_data_lanes;
if (lanes > max_data_lanes) {
dev_dbg(sd->dev, "Active data lanes (%u) exceeds max (%u)\n",
lanes, max_data_lanes);
return -EINVAL;
}
return lanes;
}
EXPORT_SYMBOL_GPL(v4l2_get_active_data_lanes);
#endif
/*
* Simplify a fraction using a simple continued fraction decomposition. The
* idea here is to convert fractions such as 333333/10000000 to 1/30 using
* 32 bit arithmetic only. The algorithm is not perfect and relies upon two
* arbitrary parameters to remove non-significative terms from the simple
* continued fraction decomposition. Using 8 and 333 for n_terms and threshold
* respectively seems to give nice results.
*/
void v4l2_simplify_fraction(u32 *numerator, u32 *denominator,
unsigned int n_terms, unsigned int threshold)
{
u32 *an;
u32 x, y, r;
unsigned int i, n;
an = kmalloc_array(n_terms, sizeof(*an), GFP_KERNEL);
if (an == NULL)
return;
/*
* Convert the fraction to a simple continued fraction. See
* https://en.wikipedia.org/wiki/Continued_fraction
* Stop if the current term is bigger than or equal to the given
* threshold.
*/
x = *numerator;
y = *denominator;
for (n = 0; n < n_terms && y != 0; ++n) {
an[n] = x / y;
if (an[n] >= threshold) {
if (n < 2)
n++;
break;
}
r = x - an[n] * y;
x = y;
y = r;
}
/* Expand the simple continued fraction back to an integer fraction. */
x = 0;
y = 1;
for (i = n; i > 0; --i) {
r = y;
y = an[i-1] * y + x;
x = r;
}
*numerator = y;
*denominator = x;
kfree(an);
}
EXPORT_SYMBOL_GPL(v4l2_simplify_fraction);
/*
* Convert a fraction to a frame interval in 100ns multiples. The idea here is
* to compute numerator / denominator * 10000000 using 32 bit fixed point
* arithmetic only.
*/
u32 v4l2_fraction_to_interval(u32 numerator, u32 denominator)
{
u32 multiplier;
/* Saturate the result if the operation would overflow. */
if (denominator == 0 ||
numerator/denominator >= ((u32)-1)/10000000)
return (u32)-1;
/*
* Divide both the denominator and the multiplier by two until
* numerator * multiplier doesn't overflow. If anyone knows a better
* algorithm please let me know.
*/
multiplier = 10000000;
while (numerator > ((u32)-1)/multiplier) {
multiplier /= 2;
denominator /= 2;
}
return denominator ? numerator * multiplier / denominator : 0;
}
EXPORT_SYMBOL_GPL(v4l2_fraction_to_interval);
int v4l2_link_freq_to_bitmap(struct device *dev, const u64 *fw_link_freqs,
unsigned int num_of_fw_link_freqs,
const s64 *driver_link_freqs,
unsigned int num_of_driver_link_freqs,
unsigned long *bitmap)
{
unsigned int i;
*bitmap = 0;
if (!num_of_fw_link_freqs) {
dev_err(dev, "no link frequencies in firmware\n");
return -ENODATA;
}
for (i = 0; i < num_of_fw_link_freqs; i++) {
unsigned int j;
for (j = 0; j < num_of_driver_link_freqs; j++) {
if (fw_link_freqs[i] != driver_link_freqs[j])
continue;
dev_dbg(dev, "enabling link frequency %lld Hz\n",
driver_link_freqs[j]);
*bitmap |= BIT(j);
break;
}
}
if (!*bitmap) {
dev_err(dev, "no matching link frequencies found\n");
dev_dbg(dev, "specified in firmware:\n");
for (i = 0; i < num_of_fw_link_freqs; i++)
dev_dbg(dev, "\t%llu Hz\n", fw_link_freqs[i]);
dev_dbg(dev, "driver supported:\n");
for (i = 0; i < num_of_driver_link_freqs; i++)
dev_dbg(dev, "\t%lld Hz\n", driver_link_freqs[i]);
return -ENOENT;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_link_freq_to_bitmap);
struct clk *__devm_v4l2_sensor_clk_get(struct device *dev, const char *id,
bool legacy, bool fixed_rate,
unsigned long clk_rate)
{
bool of_node = is_of_node(dev_fwnode(dev));
const char *clk_id __free(kfree) = NULL;
struct clk_hw *clk_hw;
struct clk *clk;
u32 rate = clk_rate;
int ret = 0;
clk = devm_clk_get_optional(dev, id);
if (IS_ERR(clk))
return clk;
/*
* If the caller didn't request a fixed rate, retrieve it from the
* clock-frequency property. -EINVAL indicates the property is absent,
* and is not a failure. Other errors, or success with a clock-frequency
* value of 0, are hard failures.
*/
if (!fixed_rate || !clk_rate) {
ret = device_property_read_u32(dev, "clock-frequency", &rate);
if ((ret && ret != -EINVAL) || (!ret && !rate))
return ERR_PTR(-EINVAL);
}
if (clk) {
/*
* On non-OF platforms, or when legacy behaviour is requested,
* set the clock rate if a rate has been specified by the caller
* or by the clock-frequency property.
*/
if (rate && (!of_node || legacy)) {
ret = clk_set_rate(clk, rate);
if (ret) {
dev_err(dev, "Failed to set clock rate: %u\n",
rate);
return ERR_PTR(ret);
}
}
return clk;
}
/*
* Register a dummy fixed clock on non-OF platforms or when legacy
* behaviour is requested. This required the common clock framework.
*/
if (!IS_ENABLED(CONFIG_COMMON_CLK) || (of_node && !legacy))
return ERR_PTR(-ENOENT);
/* We need a rate to create a clock. */
if (ret)
return ERR_PTR(ret == -EINVAL ? -EPROBE_DEFER : ret);
if (id)
clk_id = kasprintf(GFP_KERNEL, "clk-%s-%s", dev_name(dev), id);
else
clk_id = kasprintf(GFP_KERNEL, "clk-%s", dev_name(dev));
if (!clk_id)
return ERR_PTR(-ENOMEM);
clk_hw = devm_clk_hw_register_fixed_rate(dev, clk_id, NULL, 0, rate);
if (IS_ERR(clk_hw))
return ERR_CAST(clk_hw);
return clk_hw->clk;
}
EXPORT_SYMBOL_GPL(__devm_v4l2_sensor_clk_get);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* V4L2 controls framework private header.
*
* Copyright (C) 2010-2021 Hans Verkuil <hverkuil@kernel.org>
*/
#ifndef _V4L2_CTRLS_PRIV_H_
#define _V4L2_CTRLS_PRIV_H_
#define dprintk(vdev, fmt, arg...) do { \
if (!WARN_ON(!(vdev)) && ((vdev)->dev_debug & V4L2_DEV_DEBUG_CTRL)) \
printk(KERN_DEBUG pr_fmt("%s: %s: " fmt), \
__func__, video_device_node_name(vdev), ##arg); \
} while (0)
#define has_op(master, op) \
((master)->ops && (master)->ops->op)
#define call_op(master, op) \
(has_op(master, op) ? (master)->ops->op(master) : 0)
static inline u32 node2id(struct list_head *node)
{
return list_entry(node, struct v4l2_ctrl_ref, node)->ctrl->id;
}
/*
* Small helper function to determine if the autocluster is set to manual
* mode.
*/
static inline bool is_cur_manual(const struct v4l2_ctrl *master)
{
return master->is_auto && master->cur.val == master->manual_mode_value;
}
/*
* Small helper function to determine if the autocluster will be set to manual
* mode.
*/
static inline bool is_new_manual(const struct v4l2_ctrl *master)
{
return master->is_auto && master->val == master->manual_mode_value;
}
static inline u32 user_flags(const struct v4l2_ctrl *ctrl)
{
u32 flags = ctrl->flags;
if (ctrl->is_ptr)
flags |= V4L2_CTRL_FLAG_HAS_PAYLOAD;
return flags;
}
/* v4l2-ctrls-core.c */
void cur_to_new(struct v4l2_ctrl *ctrl);
void cur_to_req(struct v4l2_ctrl_ref *ref);
void new_to_cur(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 ch_flags);
void new_to_req(struct v4l2_ctrl_ref *ref);
int req_to_new(struct v4l2_ctrl_ref *ref);
void send_initial_event(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl);
void send_event(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 changes);
int handler_new_ref(struct v4l2_ctrl_handler *hdl,
struct v4l2_ctrl *ctrl,
struct v4l2_ctrl_ref **ctrl_ref,
bool from_other_dev, bool allocate_req);
struct v4l2_ctrl_ref *find_ref(struct v4l2_ctrl_handler *hdl, u32 id);
struct v4l2_ctrl_ref *find_ref_lock(struct v4l2_ctrl_handler *hdl, u32 id);
int check_range(enum v4l2_ctrl_type type,
s64 min, s64 max, u64 step, s64 def);
void update_from_auto_cluster(struct v4l2_ctrl *master);
int try_or_set_cluster(struct v4l2_fh *fh, struct v4l2_ctrl *master,
bool set, u32 ch_flags);
/* v4l2-ctrls-api.c */
int v4l2_g_ext_ctrls_common(struct v4l2_ctrl_handler *hdl,
struct v4l2_ext_controls *cs,
struct video_device *vdev);
int try_set_ext_ctrls_common(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct v4l2_ext_controls *cs,
struct video_device *vdev, bool set);
/* v4l2-ctrls-request.c */
void v4l2_ctrl_handler_init_request(struct v4l2_ctrl_handler *hdl);
void v4l2_ctrl_handler_free_request(struct v4l2_ctrl_handler *hdl);
int v4l2_g_ext_ctrls_request(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
struct media_device *mdev, struct v4l2_ext_controls *cs);
int try_set_ext_ctrls_request(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs, bool set);
#endif
@@ -0,0 +1,505 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* V4L2 controls framework Request API implementation.
*
* Copyright (C) 2018-2021 Hans Verkuil <hverkuil@kernel.org>
*/
#define pr_fmt(fmt) "v4l2-ctrls: " fmt
#include <linux/export.h>
#include <linux/slab.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>
#include "v4l2-ctrls-priv.h"
/* Initialize the request-related fields in a control handler */
void v4l2_ctrl_handler_init_request(struct v4l2_ctrl_handler *hdl)
{
INIT_LIST_HEAD(&hdl->requests);
INIT_LIST_HEAD(&hdl->requests_queued);
hdl->request_is_queued = false;
media_request_object_init(&hdl->req_obj);
}
/* Free the request-related fields in a control handler */
void v4l2_ctrl_handler_free_request(struct v4l2_ctrl_handler *hdl)
{
struct v4l2_ctrl_handler *req, *next_req;
/*
* Do nothing if this isn't the main handler or the main
* handler is not used in any request.
*
* The main handler can be identified by having a NULL ops pointer in
* the request object.
*/
if (hdl->req_obj.ops || list_empty(&hdl->requests))
return;
/*
* If the main handler is freed and it is used by handler objects in
* outstanding requests, then unbind and put those objects before
* freeing the main handler.
*/
list_for_each_entry_safe(req, next_req, &hdl->requests, requests) {
media_request_object_unbind(&req->req_obj);
media_request_object_put(&req->req_obj);
}
}
static int v4l2_ctrl_request_clone(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_handler *from)
{
struct v4l2_ctrl_ref *ref;
int err = 0;
if (WARN_ON(!hdl || hdl == from))
return -EINVAL;
if (hdl->error)
return hdl->error;
WARN_ON(hdl->lock != &hdl->_lock);
mutex_lock(from->lock);
list_for_each_entry(ref, &from->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl_ref *new_ref;
/* Skip refs inherited from other devices */
if (ref->from_other_dev)
continue;
err = handler_new_ref(hdl, ctrl, &new_ref, false, true);
if (err)
break;
}
mutex_unlock(from->lock);
return err;
}
static void v4l2_ctrl_request_queue(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
struct v4l2_ctrl_handler *main_hdl = obj->priv;
mutex_lock(main_hdl->lock);
list_add_tail(&hdl->requests_queued, &main_hdl->requests_queued);
hdl->request_is_queued = true;
mutex_unlock(main_hdl->lock);
}
static void v4l2_ctrl_request_unbind(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
struct v4l2_ctrl_handler *main_hdl = obj->priv;
mutex_lock(main_hdl->lock);
list_del_init(&hdl->requests);
if (hdl->request_is_queued) {
list_del_init(&hdl->requests_queued);
hdl->request_is_queued = false;
}
mutex_unlock(main_hdl->lock);
}
static void v4l2_ctrl_request_release(struct media_request_object *obj)
{
struct v4l2_ctrl_handler *hdl =
container_of(obj, struct v4l2_ctrl_handler, req_obj);
v4l2_ctrl_handler_free(hdl);
kfree(hdl);
}
static const struct media_request_object_ops req_ops = {
.queue = v4l2_ctrl_request_queue,
.unbind = v4l2_ctrl_request_unbind,
.release = v4l2_ctrl_request_release,
};
struct v4l2_ctrl_handler *v4l2_ctrl_request_hdl_find(struct media_request *req,
struct v4l2_ctrl_handler *parent)
{
struct media_request_object *obj;
if (WARN_ON(req->state != MEDIA_REQUEST_STATE_VALIDATING &&
req->state != MEDIA_REQUEST_STATE_QUEUED))
return NULL;
obj = media_request_object_find(req, &req_ops, parent);
if (obj)
return container_of(obj, struct v4l2_ctrl_handler, req_obj);
return NULL;
}
EXPORT_SYMBOL_GPL(v4l2_ctrl_request_hdl_find);
struct v4l2_ctrl *
v4l2_ctrl_request_hdl_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id)
{
struct v4l2_ctrl_ref *ref = find_ref_lock(hdl, id);
return (ref && ref->p_req_valid) ? ref->ctrl : NULL;
}
EXPORT_SYMBOL_GPL(v4l2_ctrl_request_hdl_ctrl_find);
static int v4l2_ctrl_request_bind(struct media_request *req,
struct v4l2_ctrl_handler *hdl,
struct v4l2_ctrl_handler *from)
{
int ret;
ret = v4l2_ctrl_request_clone(hdl, from);
if (!ret) {
ret = media_request_object_bind(req, &req_ops,
from, false, &hdl->req_obj);
if (!ret) {
mutex_lock(from->lock);
list_add_tail(&hdl->requests, &from->requests);
mutex_unlock(from->lock);
}
}
return ret;
}
static struct media_request_object *
v4l2_ctrls_find_req_obj(struct v4l2_ctrl_handler *hdl,
struct media_request *req, bool set)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *new_hdl;
int ret;
if (IS_ERR(req))
return ERR_CAST(req);
if (set && WARN_ON(req->state != MEDIA_REQUEST_STATE_UPDATING))
return ERR_PTR(-EBUSY);
obj = media_request_object_find(req, &req_ops, hdl);
if (obj)
return obj;
/*
* If there are no controls in this completed request,
* then that can only happen if:
*
* 1) no controls were present in the queued request, and
* 2) v4l2_ctrl_request_complete() could not allocate a
* control handler object to store the completed state in.
*
* So return ENOMEM to indicate that there was an out-of-memory
* error.
*/
if (!set)
return ERR_PTR(-ENOMEM);
new_hdl = kzalloc_obj(*new_hdl);
if (!new_hdl)
return ERR_PTR(-ENOMEM);
obj = &new_hdl->req_obj;
ret = v4l2_ctrl_handler_init(new_hdl, (hdl->nr_of_buckets - 1) * 8);
if (!ret)
ret = v4l2_ctrl_request_bind(req, new_hdl, hdl);
if (ret) {
v4l2_ctrl_handler_free(new_hdl);
kfree(new_hdl);
return ERR_PTR(ret);
}
media_request_object_get(obj);
return obj;
}
int v4l2_g_ext_ctrls_request(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
struct media_device *mdev, struct v4l2_ext_controls *cs)
{
struct media_request_object *obj = NULL;
struct media_request *req = NULL;
int ret;
if (!mdev || cs->request_fd < 0)
return -EINVAL;
req = media_request_get_by_fd(mdev, cs->request_fd);
if (IS_ERR(req))
return PTR_ERR(req);
if (req->state != MEDIA_REQUEST_STATE_COMPLETE) {
media_request_put(req);
return -EACCES;
}
ret = media_request_lock_for_access(req);
if (ret) {
media_request_put(req);
return ret;
}
obj = v4l2_ctrls_find_req_obj(hdl, req, false);
if (IS_ERR(obj)) {
media_request_unlock_for_access(req);
media_request_put(req);
return PTR_ERR(obj);
}
hdl = container_of(obj, struct v4l2_ctrl_handler,
req_obj);
ret = v4l2_g_ext_ctrls_common(hdl, cs, vdev);
media_request_unlock_for_access(req);
media_request_object_put(obj);
media_request_put(req);
return ret;
}
int try_set_ext_ctrls_request(struct v4l2_fh *fh,
struct v4l2_ctrl_handler *hdl,
struct video_device *vdev,
struct media_device *mdev,
struct v4l2_ext_controls *cs, bool set)
{
struct media_request_object *obj = NULL;
struct media_request *req = NULL;
int ret;
if (!mdev) {
dprintk(vdev, "%s: missing media device\n",
video_device_node_name(vdev));
return -EINVAL;
}
if (cs->request_fd < 0) {
dprintk(vdev, "%s: invalid request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
return -EINVAL;
}
req = media_request_get_by_fd(mdev, cs->request_fd);
if (IS_ERR(req)) {
dprintk(vdev, "%s: cannot find request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
return PTR_ERR(req);
}
ret = media_request_lock_for_update(req);
if (ret) {
dprintk(vdev, "%s: cannot lock request fd %d\n",
video_device_node_name(vdev), cs->request_fd);
media_request_put(req);
return ret;
}
obj = v4l2_ctrls_find_req_obj(hdl, req, set);
if (IS_ERR(obj)) {
dprintk(vdev,
"%s: cannot find request object for request fd %d\n",
video_device_node_name(vdev),
cs->request_fd);
media_request_unlock_for_update(req);
media_request_put(req);
return PTR_ERR(obj);
}
hdl = container_of(obj, struct v4l2_ctrl_handler,
req_obj);
ret = try_set_ext_ctrls_common(fh, hdl, cs, vdev, set);
if (ret)
dprintk(vdev,
"%s: try_set_ext_ctrls_common failed (%d)\n",
video_device_node_name(vdev), ret);
media_request_unlock_for_update(req);
media_request_object_put(obj);
media_request_put(req);
return ret;
}
void v4l2_ctrl_request_complete(struct media_request *req,
struct v4l2_ctrl_handler *main_hdl)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *hdl;
struct v4l2_ctrl_ref *ref;
if (!req || !main_hdl)
return;
/*
* Note that it is valid if nothing was found. It means
* that this request doesn't have any controls and so just
* wants to leave the controls unchanged.
*/
obj = media_request_object_find(req, &req_ops, main_hdl);
if (!obj) {
int ret;
/* Create a new request so the driver can return controls */
hdl = kzalloc_obj(*hdl);
if (!hdl)
return;
ret = v4l2_ctrl_handler_init(hdl, (main_hdl->nr_of_buckets - 1) * 8);
if (!ret)
ret = v4l2_ctrl_request_bind(req, hdl, main_hdl);
if (ret)
goto error;
hdl->request_is_queued = true;
obj = media_request_object_find(req, &req_ops, main_hdl);
if (!obj)
goto error;
}
hdl = container_of(obj, struct v4l2_ctrl_handler, req_obj);
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl *master = ctrl->cluster[0];
unsigned int i;
if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) {
v4l2_ctrl_lock(master);
/* g_volatile_ctrl will update the current control values */
for (i = 0; i < master->ncontrols; i++)
cur_to_new(master->cluster[i]);
call_op(master, g_volatile_ctrl);
new_to_req(ref);
v4l2_ctrl_unlock(master);
continue;
}
if (ref->p_req_valid)
continue;
/* Copy the current control value into the request */
v4l2_ctrl_lock(ctrl);
cur_to_req(ref);
v4l2_ctrl_unlock(ctrl);
}
mutex_lock(main_hdl->lock);
WARN_ON(!hdl->request_is_queued);
list_del_init(&hdl->requests_queued);
hdl->request_is_queued = false;
mutex_unlock(main_hdl->lock);
media_request_object_complete(obj);
media_request_object_put(obj);
return;
error:
v4l2_ctrl_handler_free(hdl);
kfree(hdl);
}
EXPORT_SYMBOL(v4l2_ctrl_request_complete);
int v4l2_ctrl_request_setup(struct media_request *req,
struct v4l2_ctrl_handler *main_hdl)
{
struct media_request_object *obj;
struct v4l2_ctrl_handler *hdl;
struct v4l2_ctrl_ref *ref;
int ret = 0;
if (!req || !main_hdl)
return 0;
if (WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED))
return -EBUSY;
/*
* Note that it is valid if nothing was found. It means
* that this request doesn't have any controls and so just
* wants to leave the controls unchanged.
*/
obj = media_request_object_find(req, &req_ops, main_hdl);
if (!obj)
return 0;
if (obj->completed) {
media_request_object_put(obj);
return -EBUSY;
}
hdl = container_of(obj, struct v4l2_ctrl_handler, req_obj);
list_for_each_entry(ref, &hdl->ctrl_refs, node)
ref->req_done = false;
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
struct v4l2_ctrl *ctrl = ref->ctrl;
struct v4l2_ctrl *master = ctrl->cluster[0];
bool have_new_data = false;
int i;
/*
* Skip if this control was already handled by a cluster.
* Skip button controls and read-only controls.
*/
if (ref->req_done || (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY))
continue;
v4l2_ctrl_lock(master);
for (i = 0; i < master->ncontrols; i++) {
if (master->cluster[i]) {
struct v4l2_ctrl_ref *r =
find_ref(hdl, master->cluster[i]->id);
if (r->p_req_valid) {
have_new_data = true;
break;
}
}
}
if (!have_new_data) {
v4l2_ctrl_unlock(master);
continue;
}
for (i = 0; i < master->ncontrols; i++) {
if (master->cluster[i]) {
struct v4l2_ctrl_ref *r =
find_ref(hdl, master->cluster[i]->id);
ret = req_to_new(r);
if (ret) {
v4l2_ctrl_unlock(master);
goto error;
}
master->cluster[i]->is_new = 1;
r->req_done = true;
}
}
/*
* For volatile autoclusters that are currently in auto mode
* we need to discover if it will be set to manual mode.
* If so, then we have to copy the current volatile values
* first since those will become the new manual values (which
* may be overwritten by explicit new values from this set
* of controls).
*/
if (master->is_auto && master->has_volatiles &&
!is_cur_manual(master)) {
s32 new_auto_val = *master->p_new.p_s32;
/*
* If the new value == the manual value, then copy
* the current volatile values.
*/
if (new_auto_val == master->manual_mode_value)
update_from_auto_cluster(master);
}
ret = try_or_set_cluster(NULL, master, true, 0);
v4l2_ctrl_unlock(master);
if (ret)
break;
}
error:
media_request_object_put(obj);
return ret;
}
EXPORT_SYMBOL(v4l2_ctrl_request_setup);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,295 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
V4L2 device support.
Copyright (C) 2008 Hans Verkuil <hverkuil@kernel.org>
*/
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
/* Set name to driver name + device name if it is empty. */
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);
static void v4l2_device_release(struct kref *ref)
{
struct v4l2_device *v4l2_dev =
container_of(ref, struct v4l2_device, ref);
if (v4l2_dev->release)
v4l2_dev->release(v4l2_dev);
}
int v4l2_device_put(struct v4l2_device *v4l2_dev)
{
return kref_put(&v4l2_dev->ref, v4l2_device_release);
}
EXPORT_SYMBOL_GPL(v4l2_device_put);
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
atomic_t *instance)
{
int num = atomic_inc_return(instance) - 1;
int len = strlen(basename);
if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s-%d", basename, num);
else
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s%d", basename, num);
return num;
}
EXPORT_SYMBOL_GPL(v4l2_device_set_name);
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
{
if (v4l2_dev->dev == NULL)
return;
if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
dev_set_drvdata(v4l2_dev->dev, NULL);
put_device(v4l2_dev->dev);
v4l2_dev->dev = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
{
struct v4l2_subdev *sd, *next;
/* Just return if v4l2_dev is NULL or if it was already
* unregistered before. */
if (v4l2_dev == NULL || !v4l2_dev->name[0])
return;
v4l2_device_disconnect(v4l2_dev);
/* Unregister subdevs */
list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
v4l2_device_unregister_subdev(sd);
if (sd->flags & V4L2_SUBDEV_FL_IS_I2C)
v4l2_i2c_subdev_unregister(sd);
else if (sd->flags & V4L2_SUBDEV_FL_IS_SPI)
v4l2_spi_subdev_unregister(sd);
}
/* Mark as unregistered, thus preventing duplicate unregistrations */
v4l2_dev->name[0] = '\0';
}
EXPORT_SYMBOL_GPL(v4l2_device_unregister);
int __v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd, struct module *module)
{
int err;
/* Check for valid input */
if (!v4l2_dev || !sd || sd->v4l2_dev || !sd->name[0])
return -EINVAL;
/*
* The reason to acquire the module here is to avoid unloading
* a module of sub-device which is registered to a media
* device. To make it possible to unload modules for media
* devices that also register sub-devices, do not
* try_module_get() such sub-device owners.
*/
sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
module == v4l2_dev->dev->driver->owner;
if (!sd->owner_v4l2_dev && !try_module_get(module))
return -ENODEV;
sd->v4l2_dev = v4l2_dev;
/* This just returns 0 if either of the two args is NULL */
err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler,
NULL, true);
if (err)
goto error_module;
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Register the entity. */
if (v4l2_dev->mdev) {
err = media_device_register_entity(v4l2_dev->mdev, &sd->entity);
if (err < 0)
goto error_module;
}
#endif
if (sd->internal_ops && sd->internal_ops->registered) {
err = sd->internal_ops->registered(sd);
if (err)
goto error_unregister;
}
sd->owner = module;
spin_lock(&v4l2_dev->lock);
list_add_tail(&sd->list, &v4l2_dev->subdevs);
spin_unlock(&v4l2_dev->lock);
return 0;
error_unregister:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_device_unregister_entity(&sd->entity);
#endif
error_module:
if (!sd->owner_v4l2_dev)
module_put(sd->owner);
sd->v4l2_dev = NULL;
return err;
}
EXPORT_SYMBOL_GPL(__v4l2_device_register_subdev);
static void v4l2_subdev_release(struct v4l2_subdev *sd)
{
struct module *owner = !sd->owner_v4l2_dev ? sd->owner : NULL;
if (sd->internal_ops && sd->internal_ops->release)
sd->internal_ops->release(sd);
sd->devnode = NULL;
module_put(owner);
}
static void v4l2_device_release_subdev_node(struct video_device *vdev)
{
v4l2_subdev_release(video_get_drvdata(vdev));
kfree(vdev);
}
int __v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev,
bool read_only)
{
struct video_device *vdev;
struct v4l2_subdev *sd;
int err;
/* Register a device node for every subdev marked with the
* V4L2_SUBDEV_FL_HAS_DEVNODE flag.
*/
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
continue;
if (sd->devnode)
continue;
vdev = kzalloc_obj(*vdev);
if (!vdev) {
err = -ENOMEM;
goto clean_up;
}
video_set_drvdata(vdev, sd);
strscpy(vdev->name, sd->name, sizeof(vdev->name));
vdev->dev_parent = sd->dev;
vdev->v4l2_dev = v4l2_dev;
vdev->fops = &v4l2_subdev_fops;
vdev->release = v4l2_device_release_subdev_node;
vdev->ctrl_handler = sd->ctrl_handler;
if (read_only)
set_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags);
sd->devnode = vdev;
err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
sd->owner);
if (err < 0) {
sd->devnode = NULL;
kfree(vdev);
goto clean_up;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
sd->entity.info.dev.major = VIDEO_MAJOR;
sd->entity.info.dev.minor = vdev->minor;
/* Interface is created by __video_register_device() */
if (vdev->v4l2_dev->mdev) {
struct media_link *link;
link = media_create_intf_link(&sd->entity,
&vdev->intf_devnode->intf,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (!link) {
err = -ENOMEM;
goto clean_up;
}
}
#endif
}
return 0;
clean_up:
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
if (!sd->devnode)
break;
video_unregister_device(sd->devnode);
}
return err;
}
EXPORT_SYMBOL_GPL(__v4l2_device_register_subdev_nodes);
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
{
struct v4l2_device *v4l2_dev;
/* return if it isn't registered */
if (sd == NULL || sd->v4l2_dev == NULL)
return;
v4l2_dev = sd->v4l2_dev;
spin_lock(&v4l2_dev->lock);
list_del(&sd->list);
spin_unlock(&v4l2_dev->lock);
if (sd->internal_ops && sd->internal_ops->unregistered)
sd->internal_ops->unregistered(sd);
sd->v4l2_dev = NULL;
#if defined(CONFIG_MEDIA_CONTROLLER)
if (v4l2_dev->mdev) {
/*
* No need to explicitly remove links, as both pads and
* links are removed by the function below, in the right order
*/
media_device_unregister_entity(&sd->entity);
}
#endif
if (sd->devnode)
video_unregister_device(sd->devnode);
else
v4l2_subdev_release(sd);
}
EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* v4l2-event.c
*
* V4L2 events.
*
* Copyright (C) 2009--2010 Nokia Corporation.
*
* Contact: Sakari Ailus <sakari.ailus@iki.fi>
*/
#include <media/v4l2-dev.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/export.h>
static unsigned int sev_pos(const struct v4l2_subscribed_event *sev, unsigned int idx)
{
idx += sev->first;
return idx >= sev->elems ? idx - sev->elems : idx;
}
static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
{
struct v4l2_kevent *kev;
struct timespec64 ts;
unsigned long flags;
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
if (list_empty(&fh->available)) {
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
return -ENOENT;
}
WARN_ON(fh->navailable == 0);
kev = list_first_entry(&fh->available, struct v4l2_kevent, list);
list_del(&kev->list);
fh->navailable--;
kev->event.pending = fh->navailable;
*event = kev->event;
ts = ns_to_timespec64(kev->ts);
event->timestamp.tv_sec = ts.tv_sec;
event->timestamp.tv_nsec = ts.tv_nsec;
kev->sev->first = sev_pos(kev->sev, 1);
kev->sev->in_use--;
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
return 0;
}
int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
int nonblocking)
{
int ret;
if (nonblocking)
return __v4l2_event_dequeue(fh, event);
/* Release the vdev lock while waiting */
if (fh->vdev->lock)
mutex_unlock(fh->vdev->lock);
do {
ret = wait_event_interruptible(fh->wait,
fh->navailable != 0);
if (ret < 0)
break;
ret = __v4l2_event_dequeue(fh, event);
} while (ret == -ENOENT);
if (fh->vdev->lock)
mutex_lock(fh->vdev->lock);
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_event_dequeue);
/* Caller must hold fh->vdev->fh_lock! */
static struct v4l2_subscribed_event *v4l2_event_subscribed(
struct v4l2_fh *fh, u32 type, u32 id)
{
struct v4l2_subscribed_event *sev;
assert_spin_locked(&fh->vdev->fh_lock);
list_for_each_entry(sev, &fh->subscribed, list)
if (sev->type == type && sev->id == id)
return sev;
return NULL;
}
static void __v4l2_event_queue_fh(struct v4l2_fh *fh,
const struct v4l2_event *ev, u64 ts)
{
struct v4l2_subscribed_event *sev;
struct v4l2_kevent *kev;
bool copy_payload = true;
/* Are we subscribed? */
sev = v4l2_event_subscribed(fh, ev->type, ev->id);
if (sev == NULL)
return;
/* Increase event sequence number on fh. */
fh->sequence++;
/* Do we have any free events? */
if (sev->in_use == sev->elems) {
/* no, remove the oldest one */
kev = sev->events + sev_pos(sev, 0);
list_del(&kev->list);
sev->in_use--;
sev->first = sev_pos(sev, 1);
fh->navailable--;
if (sev->elems == 1) {
if (sev->ops && sev->ops->replace) {
sev->ops->replace(&kev->event, ev);
copy_payload = false;
}
} else if (sev->ops && sev->ops->merge) {
struct v4l2_kevent *second_oldest =
sev->events + sev_pos(sev, 0);
sev->ops->merge(&kev->event, &second_oldest->event);
}
}
/* Take one and fill it. */
kev = sev->events + sev_pos(sev, sev->in_use);
kev->event.type = ev->type;
if (copy_payload)
kev->event.u = ev->u;
kev->event.id = ev->id;
kev->ts = ts;
kev->event.sequence = fh->sequence;
sev->in_use++;
list_add_tail(&kev->list, &fh->available);
fh->navailable++;
wake_up_all(&fh->wait);
}
void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
{
struct v4l2_fh *fh;
unsigned long flags;
u64 ts;
if (vdev == NULL)
return;
ts = ktime_get_ns();
spin_lock_irqsave(&vdev->fh_lock, flags);
list_for_each_entry(fh, &vdev->fh_list, list)
__v4l2_event_queue_fh(fh, ev, ts);
spin_unlock_irqrestore(&vdev->fh_lock, flags);
}
EXPORT_SYMBOL_GPL(v4l2_event_queue);
void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev)
{
unsigned long flags;
u64 ts = ktime_get_ns();
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
__v4l2_event_queue_fh(fh, ev, ts);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
}
EXPORT_SYMBOL_GPL(v4l2_event_queue_fh);
int v4l2_event_pending(struct v4l2_fh *fh)
{
return fh->navailable;
}
EXPORT_SYMBOL_GPL(v4l2_event_pending);
void v4l2_event_wake_all(struct video_device *vdev)
{
struct v4l2_fh *fh;
unsigned long flags;
if (!vdev)
return;
spin_lock_irqsave(&vdev->fh_lock, flags);
list_for_each_entry(fh, &vdev->fh_list, list)
wake_up_all(&fh->wait);
spin_unlock_irqrestore(&vdev->fh_lock, flags);
}
EXPORT_SYMBOL_GPL(v4l2_event_wake_all);
static void __v4l2_event_unsubscribe(struct v4l2_subscribed_event *sev)
{
struct v4l2_fh *fh = sev->fh;
unsigned int i;
lockdep_assert_held(&fh->subscribe_lock);
assert_spin_locked(&fh->vdev->fh_lock);
/* Remove any pending events for this subscription */
for (i = 0; i < sev->in_use; i++) {
list_del(&sev->events[sev_pos(sev, i)].list);
fh->navailable--;
}
list_del(&sev->list);
}
int v4l2_event_subscribe(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub, unsigned int elems,
const struct v4l2_subscribed_event_ops *ops)
{
struct v4l2_subscribed_event *sev, *found_ev;
unsigned long flags;
unsigned int i;
int ret = 0;
if (sub->type == V4L2_EVENT_ALL)
return -EINVAL;
if (elems < 1)
elems = 1;
sev = kvzalloc_flex(*sev, events, elems);
if (!sev)
return -ENOMEM;
sev->elems = elems;
for (i = 0; i < elems; i++)
sev->events[i].sev = sev;
sev->type = sub->type;
sev->id = sub->id;
sev->flags = sub->flags;
sev->fh = fh;
sev->ops = ops;
mutex_lock(&fh->subscribe_lock);
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
found_ev = v4l2_event_subscribed(fh, sub->type, sub->id);
if (!found_ev)
list_add(&sev->list, &fh->subscribed);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
if (found_ev) {
/* Already listening */
kvfree(sev);
} else if (sev->ops && sev->ops->add) {
ret = sev->ops->add(sev, elems);
if (ret) {
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
__v4l2_event_unsubscribe(sev);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
kvfree(sev);
}
}
mutex_unlock(&fh->subscribe_lock);
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_event_subscribe);
void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
{
struct v4l2_event_subscription sub;
struct v4l2_subscribed_event *sev;
unsigned long flags;
do {
sev = NULL;
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
if (!list_empty(&fh->subscribed)) {
sev = list_first_entry(&fh->subscribed,
struct v4l2_subscribed_event, list);
sub.type = sev->type;
sub.id = sev->id;
}
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
if (sev)
v4l2_event_unsubscribe(fh, &sub);
} while (sev);
}
EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe_all);
int v4l2_event_unsubscribe(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
struct v4l2_subscribed_event *sev;
unsigned long flags;
if (sub->type == V4L2_EVENT_ALL) {
v4l2_event_unsubscribe_all(fh);
return 0;
}
mutex_lock(&fh->subscribe_lock);
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
sev = v4l2_event_subscribed(fh, sub->type, sub->id);
if (sev != NULL)
__v4l2_event_unsubscribe(sev);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
if (sev && sev->ops && sev->ops->del)
sev->ops->del(sev);
mutex_unlock(&fh->subscribe_lock);
kvfree(sev);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe);
int v4l2_event_subdev_unsubscribe(struct v4l2_subdev *sd, struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
return v4l2_event_unsubscribe(fh, sub);
}
EXPORT_SYMBOL_GPL(v4l2_event_subdev_unsubscribe);
static void v4l2_event_src_replace(struct v4l2_event *old,
const struct v4l2_event *new)
{
u32 old_changes = old->u.src_change.changes;
old->u.src_change = new->u.src_change;
old->u.src_change.changes |= old_changes;
}
static void v4l2_event_src_merge(const struct v4l2_event *old,
struct v4l2_event *new)
{
new->u.src_change.changes |= old->u.src_change.changes;
}
static const struct v4l2_subscribed_event_ops v4l2_event_src_ch_ops = {
.replace = v4l2_event_src_replace,
.merge = v4l2_event_src_merge,
};
int v4l2_src_change_event_subscribe(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
if (sub->type == V4L2_EVENT_SOURCE_CHANGE)
return v4l2_event_subscribe(fh, sub, 0, &v4l2_event_src_ch_ops);
return -EINVAL;
}
EXPORT_SYMBOL_GPL(v4l2_src_change_event_subscribe);
int v4l2_src_change_event_subdev_subscribe(struct v4l2_subdev *sd,
struct v4l2_fh *fh, struct v4l2_event_subscription *sub)
{
return v4l2_src_change_event_subscribe(fh, sub);
}
EXPORT_SYMBOL_GPL(v4l2_src_change_event_subdev_subscribe);
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* v4l2-fh.c
*
* V4L2 file handles.
*
* Copyright (C) 2009--2010 Nokia Corporation.
*
* Contact: Sakari Ailus <sakari.ailus@iki.fi>
*/
#include <linux/bitops.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)
{
fh->vdev = vdev;
/* Inherit from video_device. May be overridden by the driver. */
fh->ctrl_handler = vdev->ctrl_handler;
INIT_LIST_HEAD(&fh->list);
set_bit(V4L2_FL_USES_V4L2_FH, &fh->vdev->flags);
/*
* determine_valid_ioctls() does not know if struct v4l2_fh
* is used by this driver, but here we do. So enable the
* prio ioctls here.
*/
set_bit(_IOC_NR(VIDIOC_G_PRIORITY), vdev->valid_ioctls);
set_bit(_IOC_NR(VIDIOC_S_PRIORITY), vdev->valid_ioctls);
fh->prio = V4L2_PRIORITY_UNSET;
init_waitqueue_head(&fh->wait);
INIT_LIST_HEAD(&fh->available);
INIT_LIST_HEAD(&fh->subscribed);
fh->sequence = -1;
mutex_init(&fh->subscribe_lock);
}
EXPORT_SYMBOL_GPL(v4l2_fh_init);
void v4l2_fh_add(struct v4l2_fh *fh, struct file *filp)
{
unsigned long flags;
filp->private_data = fh;
v4l2_prio_open(fh->vdev->prio, &fh->prio);
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
list_add(&fh->list, &fh->vdev->fh_list);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
}
EXPORT_SYMBOL_GPL(v4l2_fh_add);
int v4l2_fh_open(struct file *filp)
{
struct video_device *vdev = video_devdata(filp);
struct v4l2_fh *fh = kzalloc_obj(*fh);
if (fh == NULL)
return -ENOMEM;
v4l2_fh_init(fh, vdev);
v4l2_fh_add(fh, filp);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fh_open);
void v4l2_fh_del(struct v4l2_fh *fh, struct file *filp)
{
unsigned long flags;
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
list_del_init(&fh->list);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
v4l2_prio_close(fh->vdev->prio, fh->prio);
filp->private_data = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_fh_del);
void v4l2_fh_exit(struct v4l2_fh *fh)
{
if (fh->vdev == NULL)
return;
v4l_disable_media_source(fh->vdev);
v4l2_event_unsubscribe_all(fh);
mutex_destroy(&fh->subscribe_lock);
fh->vdev = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_fh_exit);
int v4l2_fh_release(struct file *filp)
{
struct v4l2_fh *fh = file_to_v4l2_fh(filp);
if (fh) {
v4l2_fh_del(fh, filp);
v4l2_fh_exit(fh);
kfree(fh);
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fh_release);
int v4l2_fh_is_singular(struct v4l2_fh *fh)
{
unsigned long flags;
int is_singular;
if (fh == NULL || fh->vdev == NULL)
return 0;
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
is_singular = list_is_singular(&fh->list);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
return is_singular;
}
EXPORT_SYMBOL_GPL(v4l2_fh_is_singular);
@@ -0,0 +1,745 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* V4L2 flash LED sub-device registration helpers.
*
* Copyright (C) 2015 Samsung Electronics Co., Ltd
* Author: Jacek Anaszewski <j.anaszewski@samsung.com>
*/
#include <linux/led-class-flash.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <media/v4l2-flash-led-class.h>
#define has_flash_op(v4l2_flash, op) \
(v4l2_flash && v4l2_flash->ops && v4l2_flash->ops->op)
#define call_flash_op(v4l2_flash, op, arg) \
(has_flash_op(v4l2_flash, op) ? \
v4l2_flash->ops->op(v4l2_flash, arg) : \
-EINVAL)
enum ctrl_init_data_id {
LED_MODE,
TORCH_INTENSITY,
FLASH_INTENSITY,
INDICATOR_INTENSITY,
FLASH_TIMEOUT,
STROBE_SOURCE,
/*
* Only above values are applicable to
* the 'ctrls' array in the struct v4l2_flash.
*/
FLASH_STROBE,
STROBE_STOP,
STROBE_STATUS,
FLASH_FAULT,
NUM_FLASH_CTRLS,
};
static enum led_brightness __intensity_to_led_brightness(
struct v4l2_ctrl *ctrl, s32 intensity)
{
intensity -= ctrl->minimum;
intensity /= (u32) ctrl->step;
/*
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
* Therefore it must be possible to set it to 0 level which in
* the LED subsystem reflects LED_OFF state.
*/
if (ctrl->minimum)
++intensity;
return intensity;
}
static s32 __led_brightness_to_intensity(struct v4l2_ctrl *ctrl,
enum led_brightness brightness)
{
/*
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
* Do not decrement brightness read from the LED subsystem for
* indicator LED as it may equal 0. For torch LEDs this function
* is called only when V4L2_FLASH_LED_MODE_TORCH is set and the
* brightness read is guaranteed to be greater than 0. In the mode
* V4L2_FLASH_LED_MODE_NONE the cached torch intensity value is used.
*/
if (ctrl->id != V4L2_CID_FLASH_INDICATOR_INTENSITY)
--brightness;
return (brightness * ctrl->step) + ctrl->minimum;
}
static int v4l2_flash_set_led_brightness(struct v4l2_flash *v4l2_flash,
struct v4l2_ctrl *ctrl)
{
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
struct led_classdev *led_cdev;
enum led_brightness brightness;
if (has_flash_op(v4l2_flash, intensity_to_led_brightness))
brightness = call_flash_op(v4l2_flash,
intensity_to_led_brightness,
ctrl->val);
else
brightness = __intensity_to_led_brightness(ctrl, ctrl->val);
/*
* In case a LED Flash class driver provides ops for custom
* brightness <-> intensity conversion, it also must have defined
* related v4l2 control step == 1. In such a case a backward conversion
* from led brightness to v4l2 intensity is required to find out the
* aligned intensity value.
*/
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
ctrl->val = call_flash_op(v4l2_flash,
led_brightness_to_intensity,
brightness);
if (ctrl == ctrls[TORCH_INTENSITY]) {
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
return 0;
if (WARN_ON_ONCE(!v4l2_flash->fled_cdev))
return -EINVAL;
led_cdev = &v4l2_flash->fled_cdev->led_cdev;
} else {
if (WARN_ON_ONCE(!v4l2_flash->iled_cdev))
return -EINVAL;
led_cdev = v4l2_flash->iled_cdev;
}
return led_set_brightness_sync(led_cdev, brightness);
}
static int v4l2_flash_update_led_brightness(struct v4l2_flash *v4l2_flash,
struct v4l2_ctrl *ctrl)
{
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
struct led_classdev *led_cdev;
int ret;
if (ctrl == ctrls[TORCH_INTENSITY]) {
/*
* Update torch brightness only if in TORCH_MODE. In other modes
* torch led is turned off, which would spuriously inform the
* user space that V4L2_CID_FLASH_TORCH_INTENSITY control value
* has changed to 0.
*/
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
return 0;
if (WARN_ON_ONCE(!v4l2_flash->fled_cdev))
return -EINVAL;
led_cdev = &v4l2_flash->fled_cdev->led_cdev;
} else {
if (WARN_ON_ONCE(!v4l2_flash->iled_cdev))
return -EINVAL;
led_cdev = v4l2_flash->iled_cdev;
}
ret = led_update_brightness(led_cdev);
if (ret < 0)
return ret;
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
ctrl->val = call_flash_op(v4l2_flash,
led_brightness_to_intensity,
led_cdev->brightness);
else
ctrl->val = __led_brightness_to_intensity(ctrl,
led_cdev->brightness);
return 0;
}
static int v4l2_flash_g_volatile_ctrl(struct v4l2_ctrl *c)
{
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
bool is_strobing;
int ret;
switch (c->id) {
case V4L2_CID_FLASH_TORCH_INTENSITY:
case V4L2_CID_FLASH_INDICATOR_INTENSITY:
return v4l2_flash_update_led_brightness(v4l2_flash, c);
}
if (!fled_cdev)
return -EINVAL;
switch (c->id) {
case V4L2_CID_FLASH_INTENSITY:
ret = led_update_flash_brightness(fled_cdev);
if (ret < 0)
return ret;
/*
* No conversion is needed as LED Flash class also uses
* microamperes for flash intensity units.
*/
c->val = fled_cdev->brightness.val;
return 0;
case V4L2_CID_FLASH_STROBE_STATUS:
ret = led_get_flash_strobe(fled_cdev, &is_strobing);
if (ret < 0)
return ret;
c->val = is_strobing;
return 0;
case V4L2_CID_FLASH_FAULT:
/* LED faults map directly to V4L2 flash faults */
return led_get_flash_fault(fled_cdev, &c->val);
default:
return -EINVAL;
}
}
static bool __software_strobe_mode_inactive(struct v4l2_ctrl **ctrls)
{
return ((ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) ||
(ctrls[STROBE_SOURCE] && (ctrls[STROBE_SOURCE]->val !=
V4L2_FLASH_STROBE_SOURCE_SOFTWARE)));
}
static int v4l2_flash_s_ctrl(struct v4l2_ctrl *c)
{
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
struct led_classdev *led_cdev;
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
bool external_strobe;
int ret = 0;
switch (c->id) {
case V4L2_CID_FLASH_TORCH_INTENSITY:
case V4L2_CID_FLASH_INDICATOR_INTENSITY:
return v4l2_flash_set_led_brightness(v4l2_flash, c);
}
if (!fled_cdev)
return -EINVAL;
led_cdev = &fled_cdev->led_cdev;
switch (c->id) {
case V4L2_CID_FLASH_LED_MODE:
switch (c->val) {
case V4L2_FLASH_LED_MODE_NONE:
led_set_brightness_sync(led_cdev, LED_OFF);
return led_set_flash_strobe(fled_cdev, false);
case V4L2_FLASH_LED_MODE_FLASH:
/* Turn the torch LED off */
led_set_brightness_sync(led_cdev, LED_OFF);
if (ctrls[STROBE_SOURCE]) {
external_strobe = (ctrls[STROBE_SOURCE]->val ==
V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
ret = call_flash_op(v4l2_flash,
external_strobe_set,
external_strobe);
}
return ret;
case V4L2_FLASH_LED_MODE_TORCH:
if (ctrls[STROBE_SOURCE]) {
ret = call_flash_op(v4l2_flash,
external_strobe_set,
false);
if (ret < 0)
return ret;
}
/* Stop flash strobing */
ret = led_set_flash_strobe(fled_cdev, false);
if (ret < 0)
return ret;
return v4l2_flash_set_led_brightness(v4l2_flash,
ctrls[TORCH_INTENSITY]);
}
break;
case V4L2_CID_FLASH_STROBE_SOURCE:
external_strobe = (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
/*
* For some hardware arrangements setting strobe source may
* affect torch mode. Therefore, if not in the flash mode,
* cache only this setting. It will be applied upon switching
* to flash mode.
*/
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH)
return 0;
return call_flash_op(v4l2_flash, external_strobe_set,
external_strobe);
case V4L2_CID_FLASH_STROBE:
if (__software_strobe_mode_inactive(ctrls))
return -EBUSY;
return led_set_flash_strobe(fled_cdev, true);
case V4L2_CID_FLASH_STROBE_STOP:
if (__software_strobe_mode_inactive(ctrls))
return -EBUSY;
return led_set_flash_strobe(fled_cdev, false);
case V4L2_CID_FLASH_TIMEOUT:
/*
* No conversion is needed as LED Flash class also uses
* microseconds for flash timeout units.
*/
return led_set_flash_timeout(fled_cdev, c->val);
case V4L2_CID_FLASH_INTENSITY:
/*
* No conversion is needed as LED Flash class also uses
* microamperes for flash intensity units.
*/
return led_set_flash_brightness(fled_cdev, c->val);
}
return -EINVAL;
}
static const struct v4l2_ctrl_ops v4l2_flash_ctrl_ops = {
.g_volatile_ctrl = v4l2_flash_g_volatile_ctrl,
.s_ctrl = v4l2_flash_s_ctrl,
};
static void __lfs_to_v4l2_ctrl_config(struct led_flash_setting *s,
struct v4l2_ctrl_config *c)
{
c->min = s->min;
c->max = s->max;
c->step = s->step;
c->def = s->val;
}
static void __fill_ctrl_init_data(struct v4l2_flash *v4l2_flash,
struct v4l2_flash_config *flash_cfg,
struct v4l2_flash_ctrl_data *ctrl_init_data)
{
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
struct v4l2_ctrl_config *ctrl_cfg;
u32 mask;
/* Init INDICATOR_INTENSITY ctrl data */
if (v4l2_flash->iled_cdev) {
ctrl_init_data[INDICATOR_INTENSITY].cid =
V4L2_CID_FLASH_INDICATOR_INTENSITY;
ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config;
__lfs_to_v4l2_ctrl_config(&flash_cfg->intensity,
ctrl_cfg);
ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY;
ctrl_cfg->min = 0;
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
}
if (!led_cdev || WARN_ON(!(led_cdev->flags & LED_DEV_CAP_FLASH)))
return;
/* Init FLASH_FAULT ctrl data */
if (flash_cfg->flash_faults) {
ctrl_init_data[FLASH_FAULT].cid = V4L2_CID_FLASH_FAULT;
ctrl_cfg = &ctrl_init_data[FLASH_FAULT].config;
ctrl_cfg->id = V4L2_CID_FLASH_FAULT;
ctrl_cfg->max = flash_cfg->flash_faults;
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
V4L2_CTRL_FLAG_READ_ONLY;
}
/* Init FLASH_LED_MODE ctrl data */
mask = 1 << V4L2_FLASH_LED_MODE_NONE |
1 << V4L2_FLASH_LED_MODE_TORCH;
if (led_cdev->flags & LED_DEV_CAP_FLASH)
mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
ctrl_init_data[LED_MODE].cid = V4L2_CID_FLASH_LED_MODE;
ctrl_cfg = &ctrl_init_data[LED_MODE].config;
ctrl_cfg->id = V4L2_CID_FLASH_LED_MODE;
ctrl_cfg->max = V4L2_FLASH_LED_MODE_TORCH;
ctrl_cfg->menu_skip_mask = ~mask;
ctrl_cfg->def = V4L2_FLASH_LED_MODE_NONE;
ctrl_cfg->flags = 0;
/* Init TORCH_INTENSITY ctrl data */
ctrl_init_data[TORCH_INTENSITY].cid = V4L2_CID_FLASH_TORCH_INTENSITY;
ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config;
__lfs_to_v4l2_ctrl_config(&flash_cfg->intensity, ctrl_cfg);
ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY;
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
/* Init FLASH_STROBE ctrl data */
ctrl_init_data[FLASH_STROBE].cid = V4L2_CID_FLASH_STROBE;
ctrl_cfg = &ctrl_init_data[FLASH_STROBE].config;
ctrl_cfg->id = V4L2_CID_FLASH_STROBE;
/* Init STROBE_STOP ctrl data */
ctrl_init_data[STROBE_STOP].cid = V4L2_CID_FLASH_STROBE_STOP;
ctrl_cfg = &ctrl_init_data[STROBE_STOP].config;
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP;
/* Init FLASH_STROBE_SOURCE ctrl data */
if (flash_cfg->has_external_strobe) {
mask = (1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE) |
(1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
ctrl_init_data[STROBE_SOURCE].cid =
V4L2_CID_FLASH_STROBE_SOURCE;
ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config;
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE;
ctrl_cfg->max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
ctrl_cfg->menu_skip_mask = ~mask;
ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
}
/* Init STROBE_STATUS ctrl data */
if (has_flash_op(fled_cdev, strobe_get)) {
ctrl_init_data[STROBE_STATUS].cid =
V4L2_CID_FLASH_STROBE_STATUS;
ctrl_cfg = &ctrl_init_data[STROBE_STATUS].config;
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STATUS;
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
V4L2_CTRL_FLAG_READ_ONLY;
}
/* Init FLASH_TIMEOUT ctrl data */
if (has_flash_op(fled_cdev, timeout_set)) {
ctrl_init_data[FLASH_TIMEOUT].cid = V4L2_CID_FLASH_TIMEOUT;
ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config;
__lfs_to_v4l2_ctrl_config(&fled_cdev->timeout, ctrl_cfg);
ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT;
}
/* Init FLASH_INTENSITY ctrl data */
if (has_flash_op(fled_cdev, flash_brightness_set)) {
ctrl_init_data[FLASH_INTENSITY].cid = V4L2_CID_FLASH_INTENSITY;
ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config;
__lfs_to_v4l2_ctrl_config(&fled_cdev->brightness, ctrl_cfg);
ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY;
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
}
}
static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash,
struct v4l2_flash_config *flash_cfg)
{
struct v4l2_flash_ctrl_data *ctrl_init_data;
struct v4l2_ctrl *ctrl;
struct v4l2_ctrl_config *ctrl_cfg;
int i, ret, num_ctrls = 0;
v4l2_flash->ctrls = devm_kcalloc(v4l2_flash->sd.dev,
STROBE_SOURCE + 1,
sizeof(*v4l2_flash->ctrls),
GFP_KERNEL);
if (!v4l2_flash->ctrls)
return -ENOMEM;
/* allocate memory dynamically so as not to exceed stack frame size */
ctrl_init_data = kzalloc_objs(*ctrl_init_data, NUM_FLASH_CTRLS);
if (!ctrl_init_data)
return -ENOMEM;
__fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data);
for (i = 0; i < NUM_FLASH_CTRLS; ++i)
if (ctrl_init_data[i].cid)
++num_ctrls;
v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
for (i = 0; i < NUM_FLASH_CTRLS; ++i) {
ctrl_cfg = &ctrl_init_data[i].config;
if (!ctrl_init_data[i].cid)
continue;
if (ctrl_cfg->id == V4L2_CID_FLASH_LED_MODE ||
ctrl_cfg->id == V4L2_CID_FLASH_STROBE_SOURCE)
ctrl = v4l2_ctrl_new_std_menu(&v4l2_flash->hdl,
&v4l2_flash_ctrl_ops,
ctrl_cfg->id,
ctrl_cfg->max,
ctrl_cfg->menu_skip_mask,
ctrl_cfg->def);
else
ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
&v4l2_flash_ctrl_ops,
ctrl_cfg->id,
ctrl_cfg->min,
ctrl_cfg->max,
ctrl_cfg->step,
ctrl_cfg->def);
if (ctrl)
ctrl->flags |= ctrl_cfg->flags;
if (i <= STROBE_SOURCE)
v4l2_flash->ctrls[i] = ctrl;
}
kfree(ctrl_init_data);
if (v4l2_flash->hdl.error) {
ret = v4l2_flash->hdl.error;
goto error_free_handler;
}
v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
v4l2_flash->sd.ctrl_handler = &v4l2_flash->hdl;
return 0;
error_free_handler:
v4l2_ctrl_handler_free(&v4l2_flash->hdl);
return ret;
}
static int __sync_device_with_v4l2_controls(struct v4l2_flash *v4l2_flash)
{
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
int ret = 0;
if (ctrls[TORCH_INTENSITY]) {
ret = v4l2_flash_set_led_brightness(v4l2_flash,
ctrls[TORCH_INTENSITY]);
if (ret < 0)
return ret;
}
if (ctrls[INDICATOR_INTENSITY]) {
ret = v4l2_flash_set_led_brightness(v4l2_flash,
ctrls[INDICATOR_INTENSITY]);
if (ret < 0)
return ret;
}
if (ctrls[FLASH_TIMEOUT]) {
if (WARN_ON_ONCE(!fled_cdev))
return -EINVAL;
ret = led_set_flash_timeout(fled_cdev,
ctrls[FLASH_TIMEOUT]->val);
if (ret < 0)
return ret;
}
if (ctrls[FLASH_INTENSITY]) {
if (WARN_ON_ONCE(!fled_cdev))
return -EINVAL;
ret = led_set_flash_brightness(fled_cdev,
ctrls[FLASH_INTENSITY]->val);
if (ret < 0)
return ret;
}
/*
* For some hardware arrangements setting strobe source may affect
* torch mode. Synchronize strobe source setting only if not in torch
* mode. For torch mode case it will get synchronized upon switching
* to flash mode.
*/
if (ctrls[STROBE_SOURCE] &&
ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
ret = call_flash_op(v4l2_flash, external_strobe_set,
ctrls[STROBE_SOURCE]->val);
return ret;
}
/*
* V4L2 subdev internal operations
*/
static int v4l2_flash_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev;
int ret = 0;
if (!v4l2_fh_is_singular(&fh->vfh))
return 0;
if (led_cdev) {
mutex_lock(&led_cdev->led_access);
led_sysfs_disable(led_cdev);
led_trigger_remove(led_cdev);
mutex_unlock(&led_cdev->led_access);
}
if (led_cdev_ind) {
mutex_lock(&led_cdev_ind->led_access);
led_sysfs_disable(led_cdev_ind);
led_trigger_remove(led_cdev_ind);
mutex_unlock(&led_cdev_ind->led_access);
}
ret = __sync_device_with_v4l2_controls(v4l2_flash);
if (ret < 0)
goto out_sync_device;
return 0;
out_sync_device:
if (led_cdev) {
mutex_lock(&led_cdev->led_access);
led_sysfs_enable(led_cdev);
mutex_unlock(&led_cdev->led_access);
}
if (led_cdev_ind) {
mutex_lock(&led_cdev_ind->led_access);
led_sysfs_enable(led_cdev_ind);
mutex_unlock(&led_cdev_ind->led_access);
}
return ret;
}
static int v4l2_flash_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev;
int ret = 0;
if (!v4l2_fh_is_singular(&fh->vfh))
return 0;
if (led_cdev) {
mutex_lock(&led_cdev->led_access);
if (v4l2_flash->ctrls[STROBE_SOURCE])
ret = v4l2_ctrl_s_ctrl(
v4l2_flash->ctrls[STROBE_SOURCE],
V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
led_sysfs_enable(led_cdev);
mutex_unlock(&led_cdev->led_access);
}
if (led_cdev_ind) {
mutex_lock(&led_cdev_ind->led_access);
led_sysfs_enable(led_cdev_ind);
mutex_unlock(&led_cdev_ind->led_access);
}
return ret;
}
static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
.open = v4l2_flash_open,
.close = v4l2_flash_close,
};
static const struct v4l2_subdev_ops v4l2_flash_subdev_ops;
static struct v4l2_flash *__v4l2_flash_init(
struct device *dev, struct fwnode_handle *fwn,
struct led_classdev_flash *fled_cdev, struct led_classdev *iled_cdev,
const struct v4l2_flash_ops *ops, struct v4l2_flash_config *config)
{
struct v4l2_flash *v4l2_flash;
struct v4l2_subdev *sd;
int ret;
if (!config)
return ERR_PTR(-EINVAL);
v4l2_flash = devm_kzalloc(dev, sizeof(*v4l2_flash), GFP_KERNEL);
if (!v4l2_flash)
return ERR_PTR(-ENOMEM);
sd = &v4l2_flash->sd;
v4l2_flash->fled_cdev = fled_cdev;
v4l2_flash->iled_cdev = iled_cdev;
v4l2_flash->ops = ops;
sd->dev = dev;
sd->fwnode = fwn ? fwn : dev_fwnode(dev);
v4l2_subdev_init(sd, &v4l2_flash_subdev_ops);
sd->internal_ops = &v4l2_flash_subdev_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
strscpy(sd->name, config->dev_name, sizeof(sd->name));
ret = media_entity_pads_init(&sd->entity, 0, NULL);
if (ret < 0)
return ERR_PTR(ret);
sd->entity.function = MEDIA_ENT_F_FLASH;
ret = v4l2_flash_init_controls(v4l2_flash, config);
if (ret < 0)
goto err_init_controls;
fwnode_handle_get(sd->fwnode);
ret = v4l2_async_register_subdev(sd);
if (ret < 0)
goto err_async_register_sd;
return v4l2_flash;
err_async_register_sd:
fwnode_handle_put(sd->fwnode);
v4l2_ctrl_handler_free(sd->ctrl_handler);
err_init_controls:
media_entity_cleanup(&sd->entity);
return ERR_PTR(ret);
}
struct v4l2_flash *v4l2_flash_init(
struct device *dev, struct fwnode_handle *fwn,
struct led_classdev_flash *fled_cdev,
const struct v4l2_flash_ops *ops,
struct v4l2_flash_config *config)
{
return __v4l2_flash_init(dev, fwn, fled_cdev, NULL, ops, config);
}
EXPORT_SYMBOL_GPL(v4l2_flash_init);
struct v4l2_flash *v4l2_flash_indicator_init(
struct device *dev, struct fwnode_handle *fwn,
struct led_classdev *iled_cdev,
struct v4l2_flash_config *config)
{
return __v4l2_flash_init(dev, fwn, NULL, iled_cdev, NULL, config);
}
EXPORT_SYMBOL_GPL(v4l2_flash_indicator_init);
void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
{
struct v4l2_subdev *sd;
if (IS_ERR_OR_NULL(v4l2_flash))
return;
sd = &v4l2_flash->sd;
v4l2_async_unregister_subdev(sd);
fwnode_handle_put(sd->fwnode);
v4l2_ctrl_handler_free(sd->ctrl_handler);
media_entity_cleanup(&sd->entity);
}
EXPORT_SYMBOL_GPL(v4l2_flash_release);
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
MODULE_DESCRIPTION("V4L2 Flash sub-device helpers");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,453 @@
// SPDX-License-Identifier: GPL-2.0
/*
* V4L2 H264 helpers.
*
* Copyright (C) 2019 Collabora, Ltd.
*
* Author: Boris Brezillon <boris.brezillon@collabora.com>
*/
#include <linux/module.h>
#include <linux/sort.h>
#include <media/v4l2-h264.h>
/*
* Size of the tempory buffer allocated when printing reference lists. The
* output will be truncated if the size is too small.
*/
static const int tmp_str_size = 1024;
/**
* v4l2_h264_init_reflist_builder() - Initialize a P/B0/B1 reference list
* builder
*
* @b: the builder context to initialize
* @dec_params: decode parameters control
* @sps: SPS control
* @dpb: DPB to use when creating the reference list
*/
void
v4l2_h264_init_reflist_builder(struct v4l2_h264_reflist_builder *b,
const struct v4l2_ctrl_h264_decode_params *dec_params,
const struct v4l2_ctrl_h264_sps *sps,
const struct v4l2_h264_dpb_entry dpb[V4L2_H264_NUM_DPB_ENTRIES])
{
int cur_frame_num, max_frame_num;
unsigned int i;
max_frame_num = 1 << (sps->log2_max_frame_num_minus4 + 4);
cur_frame_num = dec_params->frame_num;
memset(b, 0, sizeof(*b));
if (!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC)) {
b->cur_pic_order_count = min(dec_params->bottom_field_order_cnt,
dec_params->top_field_order_cnt);
b->cur_pic_fields = V4L2_H264_FRAME_REF;
} else if (dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD) {
b->cur_pic_order_count = dec_params->bottom_field_order_cnt;
b->cur_pic_fields = V4L2_H264_BOTTOM_FIELD_REF;
} else {
b->cur_pic_order_count = dec_params->top_field_order_cnt;
b->cur_pic_fields = V4L2_H264_TOP_FIELD_REF;
}
for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE))
continue;
if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
b->refs[i].longterm = true;
/*
* Handle frame_num wraparound as described in section
* '8.2.4.1 Decoding process for picture numbers' of the spec.
* For long term references, frame_num is set to
* long_term_frame_idx which requires no wrapping.
*/
if (!b->refs[i].longterm && dpb[i].frame_num > cur_frame_num)
b->refs[i].frame_num = (int)dpb[i].frame_num -
max_frame_num;
else
b->refs[i].frame_num = dpb[i].frame_num;
b->refs[i].top_field_order_cnt = dpb[i].top_field_order_cnt;
b->refs[i].bottom_field_order_cnt = dpb[i].bottom_field_order_cnt;
if (b->cur_pic_fields == V4L2_H264_FRAME_REF) {
u8 fields = V4L2_H264_FRAME_REF;
b->unordered_reflist[b->num_valid].index = i;
b->unordered_reflist[b->num_valid].fields = fields;
b->num_valid++;
continue;
}
if (dpb[i].fields & V4L2_H264_TOP_FIELD_REF) {
u8 fields = V4L2_H264_TOP_FIELD_REF;
b->unordered_reflist[b->num_valid].index = i;
b->unordered_reflist[b->num_valid].fields = fields;
b->num_valid++;
}
if (dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF) {
u8 fields = V4L2_H264_BOTTOM_FIELD_REF;
b->unordered_reflist[b->num_valid].index = i;
b->unordered_reflist[b->num_valid].fields = fields;
b->num_valid++;
}
}
for (i = b->num_valid; i < ARRAY_SIZE(b->unordered_reflist); i++)
b->unordered_reflist[i].index = i;
}
EXPORT_SYMBOL_GPL(v4l2_h264_init_reflist_builder);
static s32 v4l2_h264_get_poc(const struct v4l2_h264_reflist_builder *b,
const struct v4l2_h264_reference *ref)
{
switch (ref->fields) {
case V4L2_H264_FRAME_REF:
return min(b->refs[ref->index].top_field_order_cnt,
b->refs[ref->index].bottom_field_order_cnt);
case V4L2_H264_TOP_FIELD_REF:
return b->refs[ref->index].top_field_order_cnt;
case V4L2_H264_BOTTOM_FIELD_REF:
return b->refs[ref->index].bottom_field_order_cnt;
}
/* not reached */
return 0;
}
static int v4l2_h264_p_ref_list_cmp(const void *ptra, const void *ptrb,
const void *data)
{
const struct v4l2_h264_reflist_builder *builder = data;
u8 idxa, idxb;
idxa = ((struct v4l2_h264_reference *)ptra)->index;
idxb = ((struct v4l2_h264_reference *)ptrb)->index;
if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES ||
idxb >= V4L2_H264_NUM_DPB_ENTRIES))
return 1;
if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) {
/* Short term pics first. */
if (!builder->refs[idxa].longterm)
return -1;
else
return 1;
}
/*
* For frames, short term pics are in descending pic num order and long
* term ones in ascending order. For fields, the same direction is used
* but with frame_num (wrapped). For frames, the value of pic_num and
* frame_num are the same (see formula (8-28) and (8-29)). For this
* reason we can use frame_num only and share this function between
* frames and fields reflist.
*/
if (!builder->refs[idxa].longterm)
return builder->refs[idxb].frame_num <
builder->refs[idxa].frame_num ?
-1 : 1;
return builder->refs[idxa].frame_num < builder->refs[idxb].frame_num ?
-1 : 1;
}
static int v4l2_h264_b0_ref_list_cmp(const void *ptra, const void *ptrb,
const void *data)
{
const struct v4l2_h264_reflist_builder *builder = data;
s32 poca, pocb;
u8 idxa, idxb;
idxa = ((struct v4l2_h264_reference *)ptra)->index;
idxb = ((struct v4l2_h264_reference *)ptrb)->index;
if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES ||
idxb >= V4L2_H264_NUM_DPB_ENTRIES))
return 1;
if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) {
/* Short term pics first. */
if (!builder->refs[idxa].longterm)
return -1;
else
return 1;
}
/* Long term pics in ascending frame num order. */
if (builder->refs[idxa].longterm)
return builder->refs[idxa].frame_num <
builder->refs[idxb].frame_num ?
-1 : 1;
poca = v4l2_h264_get_poc(builder, ptra);
pocb = v4l2_h264_get_poc(builder, ptrb);
/*
* Short term pics with POC < cur POC first in POC descending order
* followed by short term pics with POC > cur POC in POC ascending
* order.
*/
if ((poca < builder->cur_pic_order_count) !=
(pocb < builder->cur_pic_order_count))
return poca < pocb ? -1 : 1;
else if (poca < builder->cur_pic_order_count)
return pocb < poca ? -1 : 1;
return poca < pocb ? -1 : 1;
}
static int v4l2_h264_b1_ref_list_cmp(const void *ptra, const void *ptrb,
const void *data)
{
const struct v4l2_h264_reflist_builder *builder = data;
s32 poca, pocb;
u8 idxa, idxb;
idxa = ((struct v4l2_h264_reference *)ptra)->index;
idxb = ((struct v4l2_h264_reference *)ptrb)->index;
if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES ||
idxb >= V4L2_H264_NUM_DPB_ENTRIES))
return 1;
if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) {
/* Short term pics first. */
if (!builder->refs[idxa].longterm)
return -1;
else
return 1;
}
/* Long term pics in ascending frame num order. */
if (builder->refs[idxa].longterm)
return builder->refs[idxa].frame_num <
builder->refs[idxb].frame_num ?
-1 : 1;
poca = v4l2_h264_get_poc(builder, ptra);
pocb = v4l2_h264_get_poc(builder, ptrb);
/*
* Short term pics with POC > cur POC first in POC ascending order
* followed by short term pics with POC < cur POC in POC descending
* order.
*/
if ((poca < builder->cur_pic_order_count) !=
(pocb < builder->cur_pic_order_count))
return pocb < poca ? -1 : 1;
else if (poca < builder->cur_pic_order_count)
return pocb < poca ? -1 : 1;
return poca < pocb ? -1 : 1;
}
/*
* The references need to be reordered so that references are alternating
* between top and bottom field references starting with the current picture
* parity. This has to be done for short term and long term references
* separately.
*/
static void reorder_field_reflist(const struct v4l2_h264_reflist_builder *b,
struct v4l2_h264_reference *reflist)
{
struct v4l2_h264_reference tmplist[V4L2_H264_REF_LIST_LEN];
u8 lt, i = 0, j = 0, k = 0;
memcpy(tmplist, reflist, sizeof(tmplist[0]) * b->num_valid);
for (lt = 0; lt <= 1; lt++) {
do {
for (; i < b->num_valid && b->refs[tmplist[i].index].longterm == lt; i++) {
if (tmplist[i].fields == b->cur_pic_fields) {
reflist[k++] = tmplist[i++];
break;
}
}
for (; j < b->num_valid && b->refs[tmplist[j].index].longterm == lt; j++) {
if (tmplist[j].fields != b->cur_pic_fields) {
reflist[k++] = tmplist[j++];
break;
}
}
} while ((i < b->num_valid && b->refs[tmplist[i].index].longterm == lt) ||
(j < b->num_valid && b->refs[tmplist[j].index].longterm == lt));
}
}
static char ref_type_to_char(u8 ref_type)
{
switch (ref_type) {
case V4L2_H264_FRAME_REF:
return 'f';
case V4L2_H264_TOP_FIELD_REF:
return 't';
case V4L2_H264_BOTTOM_FIELD_REF:
return 'b';
}
return '?';
}
static const char *format_ref_list_p(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *reflist,
char **out_str)
{
int n = 0, i;
*out_str = kmalloc(tmp_str_size, GFP_KERNEL);
if (!(*out_str))
return NULL;
n += snprintf(*out_str + n, tmp_str_size - n, "|");
for (i = 0; i < builder->num_valid; i++) {
/* this is pic_num for frame and frame_num (wrapped) for field,
* but for frame pic_num is equal to frame_num (wrapped).
*/
int frame_num = builder->refs[reflist[i].index].frame_num;
bool longterm = builder->refs[reflist[i].index].longterm;
n += scnprintf(*out_str + n, tmp_str_size - n, "%i%c%c|",
frame_num, longterm ? 'l' : 's',
ref_type_to_char(reflist[i].fields));
}
return *out_str;
}
static void print_ref_list_p(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *reflist)
{
char *buf = NULL;
pr_debug("ref_pic_list_p (cur_poc %u%c) %s\n",
builder->cur_pic_order_count,
ref_type_to_char(builder->cur_pic_fields),
format_ref_list_p(builder, reflist, &buf));
kfree(buf);
}
static const char *format_ref_list_b(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *reflist,
char **out_str)
{
int n = 0, i;
*out_str = kmalloc(tmp_str_size, GFP_KERNEL);
if (!(*out_str))
return NULL;
n += snprintf(*out_str + n, tmp_str_size - n, "|");
for (i = 0; i < builder->num_valid; i++) {
int frame_num = builder->refs[reflist[i].index].frame_num;
u32 poc = v4l2_h264_get_poc(builder, reflist + i);
bool longterm = builder->refs[reflist[i].index].longterm;
n += scnprintf(*out_str + n, tmp_str_size - n, "%i%c%c|",
longterm ? frame_num : poc,
longterm ? 'l' : 's',
ref_type_to_char(reflist[i].fields));
}
return *out_str;
}
static void print_ref_list_b(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *reflist, u8 list_num)
{
char *buf = NULL;
pr_debug("ref_pic_list_b%u (cur_poc %u%c) %s",
list_num, builder->cur_pic_order_count,
ref_type_to_char(builder->cur_pic_fields),
format_ref_list_b(builder, reflist, &buf));
kfree(buf);
}
/**
* v4l2_h264_build_p_ref_list() - Build the P reference list
*
* @builder: reference list builder context
* @reflist: 32 sized array used to store the P reference list. Each entry
* is a v4l2_h264_reference structure
*
* This functions builds the P reference lists. This procedure is describe in
* section '8.2.4 Decoding process for reference picture lists construction'
* of the H264 spec. This function can be used by H264 decoder drivers that
* need to pass a P reference list to the hardware.
*/
void
v4l2_h264_build_p_ref_list(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *reflist)
{
memcpy(reflist, builder->unordered_reflist,
sizeof(builder->unordered_reflist[0]) * builder->num_valid);
sort_r(reflist, builder->num_valid, sizeof(*reflist),
v4l2_h264_p_ref_list_cmp, NULL, builder);
if (builder->cur_pic_fields != V4L2_H264_FRAME_REF)
reorder_field_reflist(builder, reflist);
print_ref_list_p(builder, reflist);
}
EXPORT_SYMBOL_GPL(v4l2_h264_build_p_ref_list);
/**
* v4l2_h264_build_b_ref_lists() - Build the B0/B1 reference lists
*
* @builder: reference list builder context
* @b0_reflist: 32 sized array used to store the B0 reference list. Each entry
* is a v4l2_h264_reference structure
* @b1_reflist: 32 sized array used to store the B1 reference list. Each entry
* is a v4l2_h264_reference structure
*
* This functions builds the B0/B1 reference lists. This procedure is described
* in section '8.2.4 Decoding process for reference picture lists construction'
* of the H264 spec. This function can be used by H264 decoder drivers that
* need to pass B0/B1 reference lists to the hardware.
*/
void
v4l2_h264_build_b_ref_lists(const struct v4l2_h264_reflist_builder *builder,
struct v4l2_h264_reference *b0_reflist,
struct v4l2_h264_reference *b1_reflist)
{
memcpy(b0_reflist, builder->unordered_reflist,
sizeof(builder->unordered_reflist[0]) * builder->num_valid);
sort_r(b0_reflist, builder->num_valid, sizeof(*b0_reflist),
v4l2_h264_b0_ref_list_cmp, NULL, builder);
memcpy(b1_reflist, builder->unordered_reflist,
sizeof(builder->unordered_reflist[0]) * builder->num_valid);
sort_r(b1_reflist, builder->num_valid, sizeof(*b1_reflist),
v4l2_h264_b1_ref_list_cmp, NULL, builder);
if (builder->cur_pic_fields != V4L2_H264_FRAME_REF) {
reorder_field_reflist(builder, b0_reflist);
reorder_field_reflist(builder, b1_reflist);
}
if (builder->num_valid > 1 &&
!memcmp(b1_reflist, b0_reflist, builder->num_valid))
swap(b1_reflist[0], b1_reflist[1]);
print_ref_list_b(builder, b0_reflist, 0);
print_ref_list_b(builder, b1_reflist, 1);
}
EXPORT_SYMBOL_GPL(v4l2_h264_build_b_ref_lists);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("V4L2 H264 Helpers");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@collabora.com>");
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* v4l2-i2c - I2C helpers for Video4Linux2
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/property.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
void v4l2_i2c_subdev_unregister(struct v4l2_subdev *sd)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
/*
* We need to unregister the i2c client
* explicitly. We cannot rely on
* i2c_del_adapter to always unregister
* clients for us, since if the i2c bus is a
* platform bus, then it is never deleted.
*
* Device tree or ACPI based devices must not
* be unregistered as they have not been
* registered by us, and would not be
* re-created by just probing the V4L2 driver.
*/
if (client && !dev_fwnode(&client->dev))
i2c_unregister_device(client);
}
void v4l2_i2c_subdev_set_name(struct v4l2_subdev *sd,
struct i2c_client *client,
const char *devname, const char *postfix)
{
if (!devname)
devname = client->dev.driver->name;
if (!postfix)
postfix = "";
snprintf(sd->name, sizeof(sd->name), "%s%s %d-%04x", devname, postfix,
i2c_adapter_id(client->adapter), client->addr);
}
EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_set_name);
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
const struct v4l2_subdev_ops *ops)
{
v4l2_subdev_init(sd, ops);
sd->flags |= V4L2_SUBDEV_FL_IS_I2C;
/* the owner is the same as the i2c_client's driver owner */
sd->owner = client->dev.driver->owner;
sd->dev = &client->dev;
/* i2c_client and v4l2_subdev point to one another */
v4l2_set_subdevdata(sd, client);
i2c_set_clientdata(client, sd);
v4l2_i2c_subdev_set_name(sd, client, NULL, NULL);
}
EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_init);
/* Load an i2c sub-device. */
struct v4l2_subdev
*v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
struct i2c_adapter *adapter,
struct i2c_board_info *info,
const unsigned short *probe_addrs)
{
struct v4l2_subdev *sd = NULL;
struct i2c_client *client;
if (!v4l2_dev)
return NULL;
request_module(I2C_MODULE_PREFIX "%s", info->type);
/* Create the i2c client */
if (info->addr == 0 && probe_addrs)
client = i2c_new_scanned_device(adapter, info, probe_addrs,
NULL);
else
client = i2c_new_client_device(adapter, info);
/*
* Note: by loading the module first we are certain that c->driver
* will be set if the driver was found. If the module was not loaded
* first, then the i2c core tries to delay-load the module for us,
* and then c->driver is still NULL until the module is finally
* loaded. This delay-load mechanism doesn't work if other drivers
* want to use the i2c device, so explicitly loading the module
* is the best alternative.
*/
if (!i2c_client_has_driver(client))
goto error;
/* Lock the module so we can safely get the v4l2_subdev pointer */
if (!try_module_get(client->dev.driver->owner))
goto error;
sd = i2c_get_clientdata(client);
/*
* Register with the v4l2_device which increases the module's
* use count as well.
*/
if (__v4l2_device_register_subdev(v4l2_dev, sd, sd->owner))
sd = NULL;
/* Decrease the module use count to match the first try_module_get. */
module_put(client->dev.driver->owner);
error:
/*
* If we have a client but no subdev, then something went wrong and
* we must unregister the client.
*/
if (!IS_ERR(client) && !sd)
i2c_unregister_device(client);
return sd;
}
EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev_board);
struct v4l2_subdev *v4l2_i2c_new_subdev(struct v4l2_device *v4l2_dev,
struct i2c_adapter *adapter,
const char *client_type,
u8 addr,
const unsigned short *probe_addrs)
{
struct i2c_board_info info;
/*
* Setup the i2c board info with the device type and
* the device address.
*/
memset(&info, 0, sizeof(info));
strscpy(info.type, client_type, sizeof(info.type));
info.addr = addr;
return v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info,
probe_addrs);
}
EXPORT_SYMBOL_GPL(v4l2_i2c_new_subdev);
/* Return i2c client address of v4l2_subdev. */
unsigned short v4l2_i2c_subdev_addr(struct v4l2_subdev *sd)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
return client ? client->addr : I2C_CLIENT_END;
}
EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_addr);
/*
* Return a list of I2C tuner addresses to probe. Use only if the tuner
* addresses are unknown.
*/
const unsigned short *v4l2_i2c_tuner_addrs(enum v4l2_i2c_tuner_type type)
{
static const unsigned short radio_addrs[] = {
#if IS_ENABLED(CONFIG_MEDIA_TUNER_TEA5761)
0x10,
#endif
0x60,
I2C_CLIENT_END
};
static const unsigned short demod_addrs[] = {
0x42, 0x43, 0x4a, 0x4b,
I2C_CLIENT_END
};
static const unsigned short tv_addrs[] = {
0x42, 0x43, 0x4a, 0x4b, /* tda8290 */
0x60, 0x61, 0x62, 0x63, 0x64,
I2C_CLIENT_END
};
switch (type) {
case ADDRS_RADIO:
return radio_addrs;
case ADDRS_DEMOD:
return demod_addrs;
case ADDRS_TV:
return tv_addrs;
case ADDRS_TV_WITH_DEMOD:
return tv_addrs + 4;
}
return NULL;
}
EXPORT_SYMBOL_GPL(v4l2_i2c_tuner_addrs);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Video4Linux2 generic ISP parameters and statistics support
*
* Copyright (C) 2025 Ideas On Board Oy
* Author: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
*/
#include <media/v4l2-isp.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <media/videobuf2-core.h>
int v4l2_isp_params_validate_buffer_size(struct device *dev,
struct vb2_buffer *vb,
size_t max_size)
{
size_t header_size = offsetof(struct v4l2_isp_params_buffer, data);
size_t payload_size = vb2_get_plane_payload(vb, 0);
/* Payload size can't be greater than the destination buffer size */
if (payload_size > max_size) {
dev_dbg(dev, "Payload size is too large: %zu\n", payload_size);
return -EINVAL;
}
/* Payload size can't be smaller than the header size */
if (payload_size < header_size) {
dev_dbg(dev, "Payload size is too small: %zu\n", payload_size);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_isp_params_validate_buffer_size);
int v4l2_isp_params_validate_buffer(struct device *dev, struct vb2_buffer *vb,
const struct v4l2_isp_params_buffer *buffer,
const struct v4l2_isp_params_block_type_info *type_info,
size_t num_block_types)
{
size_t header_size = offsetof(struct v4l2_isp_params_buffer, data);
size_t payload_size = vb2_get_plane_payload(vb, 0);
size_t block_offset = 0;
size_t buffer_size;
/*
* Currently only the first version of the V4L2 ISP parameters format is
* supported. We accept both V0 and V1 to support existing drivers
* compatible with V4L2 ISP that use either 0 or 1 as their "first
* version" identifiers.
*/
if (buffer->version != V4L2_ISP_PARAMS_VERSION_V0 &&
buffer->version != V4L2_ISP_PARAMS_VERSION_V1) {
dev_dbg(dev,
"Unsupported V4L2 ISP parameters format version: %u\n",
buffer->version);
return -EINVAL;
}
/* Validate the size reported in the header */
buffer_size = header_size + buffer->data_size;
if (buffer_size != payload_size) {
dev_dbg(dev, "Data size %zu and payload size %zu are different\n",
buffer_size, payload_size);
return -EINVAL;
}
/* Walk the list of ISP configuration blocks and validate them. */
buffer_size = buffer->data_size;
while (buffer_size >= sizeof(struct v4l2_isp_params_block_header)) {
const struct v4l2_isp_params_block_type_info *info;
const struct v4l2_isp_params_block_header *block;
block = (const struct v4l2_isp_params_block_header *)
(buffer->data + block_offset);
if (block->type >= num_block_types) {
dev_dbg(dev,
"Invalid block type %u at offset %zu\n",
block->type, block_offset);
return -EINVAL;
}
if (block->size > buffer_size) {
dev_dbg(dev, "Premature end of parameters data\n");
return -EINVAL;
}
/* It's invalid to specify both ENABLE and DISABLE. */
if ((block->flags & (V4L2_ISP_PARAMS_FL_BLOCK_ENABLE |
V4L2_ISP_PARAMS_FL_BLOCK_DISABLE)) ==
(V4L2_ISP_PARAMS_FL_BLOCK_ENABLE |
V4L2_ISP_PARAMS_FL_BLOCK_DISABLE)) {
dev_dbg(dev, "Invalid block flags %x at offset %zu\n",
block->flags, block_offset);
return -EINVAL;
}
/*
* Match the block reported size against the type info provided
* one, but allow the block to only contain the header in
* case it is going to be disabled.
*/
info = &type_info[block->type];
if (block->size != info->size &&
(!(block->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) ||
block->size != sizeof(*block))) {
dev_dbg(dev,
"Invalid block size %u (expected %zu) at offset %zu\n",
block->size, info->size, block_offset);
return -EINVAL;
}
block_offset += block->size;
buffer_size -= block->size;
}
if (buffer_size) {
dev_dbg(dev, "Unexpected data after the parameters buffer end\n");
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_isp_params_validate_buffer);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jacopo Mondi <jacopo.mondi@ideasonboard.com");
MODULE_DESCRIPTION("V4L2 generic ISP parameters and statistics helpers");
@@ -0,0 +1,713 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* V4L2 JPEG header parser helpers.
*
* Copyright (C) 2019 Pengutronix, Philipp Zabel <kernel@pengutronix.de>
*
* For reference, see JPEG ITU-T.81 (ISO/IEC 10918-1) [1]
*
* [1] https://www.w3.org/Graphics/JPEG/itu-t81.pdf
*/
#include <linux/unaligned.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <media/v4l2-jpeg.h>
MODULE_DESCRIPTION("V4L2 JPEG header parser helpers");
MODULE_AUTHOR("Philipp Zabel <kernel@pengutronix.de>");
MODULE_LICENSE("GPL");
/* Table B.1 - Marker code assignments */
#define SOF0 0xffc0 /* start of frame */
#define SOF1 0xffc1
#define SOF2 0xffc2
#define SOF3 0xffc3
#define SOF5 0xffc5
#define SOF7 0xffc7
#define JPG 0xffc8 /* extensions */
#define SOF9 0xffc9
#define SOF11 0xffcb
#define SOF13 0xffcd
#define SOF15 0xffcf
#define DHT 0xffc4 /* huffman table */
#define DAC 0xffcc /* arithmetic coding conditioning */
#define RST0 0xffd0 /* restart */
#define RST7 0xffd7
#define SOI 0xffd8 /* start of image */
#define EOI 0xffd9 /* end of image */
#define SOS 0xffda /* start of stream */
#define DQT 0xffdb /* quantization table */
#define DNL 0xffdc /* number of lines */
#define DRI 0xffdd /* restart interval */
#define DHP 0xffde /* hierarchical progression */
#define EXP 0xffdf /* expand reference */
#define APP0 0xffe0 /* application data */
#define APP14 0xffee /* application data for colour encoding */
#define APP15 0xffef
#define JPG0 0xfff0 /* extensions */
#define JPG13 0xfffd
#define COM 0xfffe /* comment */
#define TEM 0xff01 /* temporary */
/* Luma and chroma qp tables to achieve 50% compression quality
* This is as per example in Annex K.1 of ITU-T.81
*/
const u8 v4l2_jpeg_ref_table_luma_qt[V4L2_JPEG_PIXELS_IN_BLOCK] = {
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_qt);
const u8 v4l2_jpeg_ref_table_chroma_qt[V4L2_JPEG_PIXELS_IN_BLOCK] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_qt);
/* Zigzag scan pattern indexes */
const u8 v4l2_jpeg_zigzag_scan_index[V4L2_JPEG_PIXELS_IN_BLOCK] = {
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_zigzag_scan_index);
/*
* Contains the data that needs to be sent in the marker segment of an
* interchange format JPEG stream or an abbreviated format table specification
* data stream. Specifies the huffman table used for encoding the luminance DC
* coefficient differences. The table represents Table K.3 of ITU-T.81
*/
const u8 v4l2_jpeg_ref_table_luma_dc_ht[V4L2_JPEG_REF_HT_DC_LEN] = {
0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_dc_ht);
/*
* Contains the data that needs to be sent in the marker segment of an
* interchange format JPEG stream or an abbreviated format table specification
* data stream. Specifies the huffman table used for encoding the luminance AC
* coefficients. The table represents Table K.5 of ITU-T.81
*/
const u8 v4l2_jpeg_ref_table_luma_ac_ht[V4L2_JPEG_REF_HT_AC_LEN] = {
0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04,
0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A,
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85,
0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8,
0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_luma_ac_ht);
/*
* Contains the data that needs to be sent in the marker segment of an interchange format JPEG
* stream or an abbreviated format table specification data stream.
* Specifies the huffman table used for encoding the chrominance DC coefficient differences.
* The table represents Table K.4 of ITU-T.81
*/
const u8 v4l2_jpeg_ref_table_chroma_dc_ht[V4L2_JPEG_REF_HT_DC_LEN] = {
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_dc_ht);
/*
* Contains the data that needs to be sent in the marker segment of an
* interchange format JPEG stream or an abbreviated format table specification
* data stream. Specifies the huffman table used for encoding the chrominance
* AC coefficients. The table represents Table K.6 of ITU-T.81
*/
const u8 v4l2_jpeg_ref_table_chroma_ac_ht[V4L2_JPEG_REF_HT_AC_LEN] = {
0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04,
0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81,
0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17,
0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54,
0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9,
0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6,
0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9,
0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA
};
EXPORT_SYMBOL_GPL(v4l2_jpeg_ref_table_chroma_ac_ht);
/**
* struct jpeg_stream - JPEG byte stream
* @curr: current position in stream
* @end: end position, after last byte
*/
struct jpeg_stream {
u8 *curr;
u8 *end;
};
/* returns a value that fits into u8, or negative error */
static int jpeg_get_byte(struct jpeg_stream *stream)
{
if (stream->curr >= stream->end)
return -EINVAL;
return *stream->curr++;
}
/* returns a value that fits into u16, or negative error */
static int jpeg_get_word_be(struct jpeg_stream *stream)
{
u16 word;
if (stream->curr + sizeof(__be16) > stream->end)
return -EINVAL;
word = get_unaligned_be16(stream->curr);
stream->curr += sizeof(__be16);
return word;
}
static int jpeg_skip(struct jpeg_stream *stream, size_t len)
{
if (stream->curr + len > stream->end)
return -EINVAL;
stream->curr += len;
return 0;
}
static int jpeg_next_marker(struct jpeg_stream *stream)
{
int byte;
u16 marker = 0;
while ((byte = jpeg_get_byte(stream)) >= 0) {
marker = (marker << 8) | byte;
/* skip stuffing bytes and REServed markers */
if (marker == TEM || (marker > 0xffbf && marker < 0xffff))
return marker;
}
return byte;
}
/* this does not advance the current position in the stream */
static int jpeg_reference_segment(struct jpeg_stream *stream,
struct v4l2_jpeg_reference *segment)
{
u16 len;
if (stream->curr + sizeof(__be16) > stream->end)
return -EINVAL;
len = get_unaligned_be16(stream->curr);
if (stream->curr + len > stream->end)
return -EINVAL;
segment->start = stream->curr;
segment->length = len;
return 0;
}
static int v4l2_jpeg_decode_subsampling(u8 nf, u8 h_v)
{
if (nf == 1)
return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY;
/* no chroma subsampling for 4-component images */
if (nf == 4 && h_v != 0x11)
return -EINVAL;
switch (h_v) {
case 0x11:
return V4L2_JPEG_CHROMA_SUBSAMPLING_444;
case 0x21:
return V4L2_JPEG_CHROMA_SUBSAMPLING_422;
case 0x22:
return V4L2_JPEG_CHROMA_SUBSAMPLING_420;
case 0x41:
return V4L2_JPEG_CHROMA_SUBSAMPLING_411;
default:
return -EINVAL;
}
}
static int jpeg_parse_frame_header(struct jpeg_stream *stream, u16 sof_marker,
struct v4l2_jpeg_frame_header *frame_header)
{
int len = jpeg_get_word_be(stream);
if (len < 0)
return len;
/* Lf = 8 + 3 * Nf, Nf >= 1 */
if (len < 8 + 3)
return -EINVAL;
if (frame_header) {
/* Table B.2 - Frame header parameter sizes and values */
int p, y, x, nf;
int i;
p = jpeg_get_byte(stream);
if (p < 0)
return p;
/*
* Baseline DCT only supports 8-bit precision.
* Extended sequential DCT also supports 12-bit precision.
*/
if (p != 8 && (p != 12 || sof_marker != SOF1))
return -EINVAL;
y = jpeg_get_word_be(stream);
if (y < 0)
return y;
if (y == 0)
return -EINVAL;
x = jpeg_get_word_be(stream);
if (x < 0)
return x;
if (x == 0)
return -EINVAL;
nf = jpeg_get_byte(stream);
if (nf < 0)
return nf;
/*
* The spec allows 1 <= Nf <= 255, but we only support up to 4
* components.
*/
if (nf < 1 || nf > V4L2_JPEG_MAX_COMPONENTS)
return -EINVAL;
if (len != 8 + 3 * nf)
return -EINVAL;
frame_header->precision = p;
frame_header->height = y;
frame_header->width = x;
frame_header->num_components = nf;
for (i = 0; i < nf; i++) {
struct v4l2_jpeg_frame_component_spec *component;
int c, h_v, tq;
c = jpeg_get_byte(stream);
if (c < 0)
return c;
h_v = jpeg_get_byte(stream);
if (h_v < 0)
return h_v;
if (i == 0) {
int subs;
subs = v4l2_jpeg_decode_subsampling(nf, h_v);
if (subs < 0)
return subs;
frame_header->subsampling = subs;
} else if (h_v != 0x11) {
/* all chroma sampling factors must be 1 */
return -EINVAL;
}
tq = jpeg_get_byte(stream);
if (tq < 0)
return tq;
component = &frame_header->component[i];
component->component_identifier = c;
component->horizontal_sampling_factor =
(h_v >> 4) & 0xf;
component->vertical_sampling_factor = h_v & 0xf;
component->quantization_table_selector = tq;
}
} else {
return jpeg_skip(stream, len - 2);
}
return 0;
}
static int jpeg_parse_scan_header(struct jpeg_stream *stream,
struct v4l2_jpeg_scan_header *scan_header)
{
size_t skip;
int len = jpeg_get_word_be(stream);
if (len < 0)
return len;
/* Ls = 8 + 3 * Ns, Ns >= 1 */
if (len < 6 + 2)
return -EINVAL;
if (scan_header) {
int ns;
int i;
ns = jpeg_get_byte(stream);
if (ns < 0)
return ns;
if (ns < 1 || ns > 4 || len != 6 + 2 * ns)
return -EINVAL;
scan_header->num_components = ns;
for (i = 0; i < ns; i++) {
struct v4l2_jpeg_scan_component_spec *component;
int cs, td_ta;
cs = jpeg_get_byte(stream);
if (cs < 0)
return cs;
td_ta = jpeg_get_byte(stream);
if (td_ta < 0)
return td_ta;
component = &scan_header->component[i];
component->component_selector = cs;
component->dc_entropy_coding_table_selector =
(td_ta >> 4) & 0xf;
component->ac_entropy_coding_table_selector =
td_ta & 0xf;
}
skip = 3; /* skip Ss, Se, Ah, and Al */
} else {
skip = len - 2;
}
return jpeg_skip(stream, skip);
}
/* B.2.4.1 Quantization table-specification syntax */
static int jpeg_parse_quantization_tables(struct jpeg_stream *stream,
u8 precision,
struct v4l2_jpeg_reference *tables)
{
int len = jpeg_get_word_be(stream);
if (len < 0)
return len;
/* Lq = 2 + n * 65 (for baseline DCT), n >= 1 */
if (len < 2 + 65)
return -EINVAL;
len -= 2;
while (len >= 65) {
u8 pq, tq, *qk;
int ret;
int pq_tq = jpeg_get_byte(stream);
if (pq_tq < 0)
return pq_tq;
/* quantization table element precision */
pq = (pq_tq >> 4) & 0xf;
/*
* Only 8-bit Qk values for 8-bit sample precision. Extended
* sequential DCT with 12-bit sample precision also supports
* 16-bit Qk values.
*/
if (pq != 0 && (pq != 1 || precision != 12))
return -EINVAL;
/* quantization table destination identifier */
tq = pq_tq & 0xf;
if (tq > 3)
return -EINVAL;
/* quantization table element */
qk = stream->curr;
ret = jpeg_skip(stream, pq ? 128 : 64);
if (ret < 0)
return -EINVAL;
if (tables) {
tables[tq].start = qk;
tables[tq].length = pq ? 128 : 64;
}
len -= pq ? 129 : 65;
}
return 0;
}
/* B.2.4.2 Huffman table-specification syntax */
static int jpeg_parse_huffman_tables(struct jpeg_stream *stream,
struct v4l2_jpeg_reference *tables)
{
int mt;
int len = jpeg_get_word_be(stream);
if (len < 0)
return len;
/* Table B.5 - Huffman table specification parameter sizes and values */
if (len < 2 + 17)
return -EINVAL;
for (len -= 2; len >= 17; len -= 17 + mt) {
u8 tc, th, *table;
int tc_th = jpeg_get_byte(stream);
int i, ret;
if (tc_th < 0)
return tc_th;
/* table class - 0 = DC, 1 = AC */
tc = (tc_th >> 4) & 0xf;
if (tc > 1)
return -EINVAL;
/* huffman table destination identifier */
th = tc_th & 0xf;
/* only two Huffman tables for baseline DCT */
if (th > 1)
return -EINVAL;
/* BITS - number of Huffman codes with length i */
table = stream->curr;
mt = 0;
for (i = 0; i < 16; i++) {
int li;
li = jpeg_get_byte(stream);
if (li < 0)
return li;
mt += li;
}
/* HUFFVAL - values associated with each Huffman code */
ret = jpeg_skip(stream, mt);
if (ret < 0)
return ret;
if (tables) {
tables[(tc << 1) | th].start = table;
tables[(tc << 1) | th].length = stream->curr - table;
}
}
return jpeg_skip(stream, len - 2);
}
/* B.2.4.4 Restart interval definition syntax */
static int jpeg_parse_restart_interval(struct jpeg_stream *stream,
u16 *restart_interval)
{
int len = jpeg_get_word_be(stream);
int ri;
if (len < 0)
return len;
if (len != 4)
return -EINVAL;
ri = jpeg_get_word_be(stream);
if (ri < 0)
return ri;
*restart_interval = ri;
return 0;
}
static int jpeg_skip_segment(struct jpeg_stream *stream)
{
int len = jpeg_get_word_be(stream);
if (len < 0)
return len;
if (len < 2)
return -EINVAL;
return jpeg_skip(stream, len - 2);
}
/* Rec. ITU-T T.872 (06/2012) 6.5.3 */
static int jpeg_parse_app14_data(struct jpeg_stream *stream,
enum v4l2_jpeg_app14_tf *tf)
{
int ret;
int lp;
int skip;
lp = jpeg_get_word_be(stream);
if (lp < 0)
return lp;
/* Check for "Adobe\0" in Ap1..6 */
if (stream->curr + 6 > stream->end ||
strncmp(stream->curr, "Adobe\0", 6))
return jpeg_skip(stream, lp - 2);
/* get to Ap12 */
ret = jpeg_skip(stream, 11);
if (ret < 0)
return ret;
ret = jpeg_get_byte(stream);
if (ret < 0)
return ret;
*tf = ret;
/* skip the rest of the segment, this ensures at least it is complete */
skip = lp - 2 - 11 - 1;
return jpeg_skip(stream, skip);
}
/**
* v4l2_jpeg_parse_header - locate marker segments and optionally parse headers
* @buf: address of the JPEG buffer, should start with a SOI marker
* @len: length of the JPEG buffer
* @out: returns marker segment positions and optionally parsed headers
*
* The out->scan_header pointer must be initialized to NULL or point to a valid
* v4l2_jpeg_scan_header structure. The out->huffman_tables and
* out->quantization_tables pointers must be initialized to NULL or point to a
* valid array of 4 v4l2_jpeg_reference structures each.
*
* Returns 0 or negative error if parsing failed.
*/
int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out)
{
struct jpeg_stream stream;
int marker;
int ret = 0;
stream.curr = buf;
stream.end = stream.curr + len;
out->num_dht = 0;
out->num_dqt = 0;
/* the first bytes must be SOI, B.2.1 High-level syntax */
if (jpeg_get_word_be(&stream) != SOI)
return -EINVAL;
/* init value to signal if this marker is not present */
out->app14_tf = V4L2_JPEG_APP14_TF_UNKNOWN;
/* loop through marker segments */
while ((marker = jpeg_next_marker(&stream)) >= 0) {
switch (marker) {
/* baseline DCT, extended sequential DCT */
case SOF0 ... SOF1:
ret = jpeg_reference_segment(&stream, &out->sof);
if (ret < 0)
return ret;
ret = jpeg_parse_frame_header(&stream, marker,
&out->frame);
break;
/* progressive, lossless */
case SOF2 ... SOF3:
/* differential coding */
case SOF5 ... SOF7:
/* arithmetic coding */
case SOF9 ... SOF11:
case SOF13 ... SOF15:
case DAC:
case TEM:
return -EINVAL;
case DHT:
ret = jpeg_reference_segment(&stream,
&out->dht[out->num_dht++ % 4]);
if (ret < 0)
return ret;
if (!out->huffman_tables) {
ret = jpeg_skip_segment(&stream);
break;
}
ret = jpeg_parse_huffman_tables(&stream,
out->huffman_tables);
break;
case DQT:
ret = jpeg_reference_segment(&stream,
&out->dqt[out->num_dqt++ % 4]);
if (ret < 0)
return ret;
if (!out->quantization_tables) {
ret = jpeg_skip_segment(&stream);
break;
}
ret = jpeg_parse_quantization_tables(&stream,
out->frame.precision,
out->quantization_tables);
break;
case DRI:
ret = jpeg_parse_restart_interval(&stream,
&out->restart_interval);
break;
case APP14:
ret = jpeg_parse_app14_data(&stream,
&out->app14_tf);
break;
case SOS:
ret = jpeg_reference_segment(&stream, &out->sos);
if (ret < 0)
return ret;
ret = jpeg_parse_scan_header(&stream, out->scan);
/*
* stop parsing, the scan header marks the beginning of
* the entropy coded segment
*/
out->ecs_offset = stream.curr - (u8 *)buf;
return ret;
/* markers without parameters */
case RST0 ... RST7: /* restart */
case SOI: /* start of image */
case EOI: /* end of image */
break;
/* skip unknown or unsupported marker segments */
default:
ret = jpeg_skip_segment(&stream);
break;
}
if (ret < 0)
return ret;
}
return marker;
}
EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_header);
@@ -0,0 +1,614 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Media Controller ancillary functions
*
* Copyright (c) 2016 Mauro Carvalho Chehab <mchehab@kernel.org>
* Copyright (C) 2016 Shuah Khan <shuahkh@osg.samsung.com>
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (c) 2016 Intel Corporation.
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/usb.h>
#include <media/media-device.h>
#include <media/media-entity.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-core.h>
int v4l2_mc_create_media_graph(struct media_device *mdev)
{
struct media_entity *entity;
struct media_entity *if_vid = NULL, *if_aud = NULL;
struct media_entity *tuner = NULL, *decoder = NULL;
struct media_entity *io_v4l = NULL, *io_vbi = NULL, *io_swradio = NULL;
bool is_webcam = false;
u32 flags;
int ret, pad_sink, pad_source;
if (!mdev)
return 0;
media_device_for_each_entity(entity, mdev) {
switch (entity->function) {
case MEDIA_ENT_F_IF_VID_DECODER:
if_vid = entity;
break;
case MEDIA_ENT_F_IF_AUD_DECODER:
if_aud = entity;
break;
case MEDIA_ENT_F_TUNER:
tuner = entity;
break;
case MEDIA_ENT_F_ATV_DECODER:
decoder = entity;
break;
case MEDIA_ENT_F_IO_V4L:
io_v4l = entity;
break;
case MEDIA_ENT_F_IO_VBI:
io_vbi = entity;
break;
case MEDIA_ENT_F_IO_SWRADIO:
io_swradio = entity;
break;
case MEDIA_ENT_F_CAM_SENSOR:
is_webcam = true;
break;
}
}
/* It should have at least one I/O entity */
if (!io_v4l && !io_vbi && !io_swradio) {
dev_warn(mdev->dev, "Didn't find any I/O entity\n");
return -EINVAL;
}
/*
* Here, webcams are modelled on a very simple way: the sensor is
* connected directly to the I/O entity. All dirty details, like
* scaler and crop HW are hidden. While such mapping is not enough
* for mc-centric hardware, it is enough for v4l2 interface centric
* PC-consumer's hardware.
*/
if (is_webcam) {
if (!io_v4l) {
dev_warn(mdev->dev, "Didn't find a MEDIA_ENT_F_IO_V4L\n");
return -EINVAL;
}
media_device_for_each_entity(entity, mdev) {
if (entity->function != MEDIA_ENT_F_CAM_SENSOR)
continue;
ret = media_create_pad_link(entity, 0,
io_v4l, 0,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "Failed to create a sensor link\n");
return ret;
}
}
if (!decoder)
return 0;
}
/* The device isn't a webcam. So, it should have a decoder */
if (!decoder) {
dev_warn(mdev->dev, "Decoder not found\n");
return -EINVAL;
}
/* Link the tuner and IF video output pads */
if (tuner) {
if (if_vid) {
pad_source = media_get_pad_index(tuner,
MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_ANALOG);
pad_sink = media_get_pad_index(if_vid,
MEDIA_PAD_FL_SINK,
PAD_SIGNAL_ANALOG);
if (pad_source < 0 || pad_sink < 0) {
dev_warn(mdev->dev, "Couldn't get tuner and/or PLL pad(s): (%d, %d)\n",
pad_source, pad_sink);
return -EINVAL;
}
ret = media_create_pad_link(tuner, pad_source,
if_vid, pad_sink,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "Couldn't create tuner->PLL link)\n");
return ret;
}
pad_source = media_get_pad_index(if_vid,
MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_ANALOG);
pad_sink = media_get_pad_index(decoder,
MEDIA_PAD_FL_SINK,
PAD_SIGNAL_ANALOG);
if (pad_source < 0 || pad_sink < 0) {
dev_warn(mdev->dev, "get decoder and/or PLL pad(s): (%d, %d)\n",
pad_source, pad_sink);
return -EINVAL;
}
ret = media_create_pad_link(if_vid, pad_source,
decoder, pad_sink,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "couldn't link PLL to decoder\n");
return ret;
}
} else {
pad_source = media_get_pad_index(tuner,
MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_ANALOG);
pad_sink = media_get_pad_index(decoder,
MEDIA_PAD_FL_SINK,
PAD_SIGNAL_ANALOG);
if (pad_source < 0 || pad_sink < 0) {
dev_warn(mdev->dev, "couldn't get tuner and/or decoder pad(s): (%d, %d)\n",
pad_source, pad_sink);
return -EINVAL;
}
ret = media_create_pad_link(tuner, pad_source,
decoder, pad_sink,
MEDIA_LNK_FL_ENABLED);
if (ret)
return ret;
}
if (if_aud) {
pad_source = media_get_pad_index(tuner,
MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_AUDIO);
pad_sink = media_get_pad_index(if_aud,
MEDIA_PAD_FL_SINK,
PAD_SIGNAL_AUDIO);
if (pad_source < 0 || pad_sink < 0) {
dev_warn(mdev->dev, "couldn't get tuner and/or decoder pad(s) for audio: (%d, %d)\n",
pad_source, pad_sink);
return -EINVAL;
}
ret = media_create_pad_link(tuner, pad_source,
if_aud, pad_sink,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "couldn't link tuner->audio PLL\n");
return ret;
}
} else {
if_aud = tuner;
}
}
/* Create demod to V4L, VBI and SDR radio links */
if (io_v4l) {
pad_source = media_get_pad_index(decoder, MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_DV);
if (pad_source < 0) {
dev_warn(mdev->dev, "couldn't get decoder output pad for V4L I/O\n");
return -EINVAL;
}
ret = media_create_pad_link(decoder, pad_source,
io_v4l, 0,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "couldn't link decoder output to V4L I/O\n");
return ret;
}
}
if (io_swradio) {
pad_source = media_get_pad_index(decoder, MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_DV);
if (pad_source < 0) {
dev_warn(mdev->dev, "couldn't get decoder output pad for SDR\n");
return -EINVAL;
}
ret = media_create_pad_link(decoder, pad_source,
io_swradio, 0,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "couldn't link decoder output to SDR\n");
return ret;
}
}
if (io_vbi) {
pad_source = media_get_pad_index(decoder, MEDIA_PAD_FL_SOURCE,
PAD_SIGNAL_DV);
if (pad_source < 0) {
dev_warn(mdev->dev, "couldn't get decoder output pad for VBI\n");
return -EINVAL;
}
ret = media_create_pad_link(decoder, pad_source,
io_vbi, 0,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_warn(mdev->dev, "couldn't link decoder output to VBI\n");
return ret;
}
}
/* Create links for the media connectors */
flags = MEDIA_LNK_FL_ENABLED;
media_device_for_each_entity(entity, mdev) {
switch (entity->function) {
case MEDIA_ENT_F_CONN_RF:
if (!tuner)
continue;
pad_sink = media_get_pad_index(tuner, MEDIA_PAD_FL_SINK,
PAD_SIGNAL_ANALOG);
if (pad_sink < 0) {
dev_warn(mdev->dev, "couldn't get tuner analog pad sink\n");
return -EINVAL;
}
ret = media_create_pad_link(entity, 0, tuner,
pad_sink,
flags);
break;
case MEDIA_ENT_F_CONN_SVIDEO:
case MEDIA_ENT_F_CONN_COMPOSITE:
pad_sink = media_get_pad_index(decoder,
MEDIA_PAD_FL_SINK,
PAD_SIGNAL_ANALOG);
if (pad_sink < 0) {
dev_warn(mdev->dev, "couldn't get decoder analog pad sink\n");
return -EINVAL;
}
ret = media_create_pad_link(entity, 0, decoder,
pad_sink,
flags);
break;
default:
continue;
}
if (ret)
return ret;
flags = 0;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_mc_create_media_graph);
int v4l_enable_media_source(struct video_device *vdev)
{
struct media_device *mdev = vdev->entity.graph_obj.mdev;
int ret = 0, err;
if (!mdev)
return 0;
mutex_lock(&mdev->graph_mutex);
if (!mdev->enable_source)
goto end;
err = mdev->enable_source(&vdev->entity, &vdev->pipe);
if (err)
ret = -EBUSY;
end:
mutex_unlock(&mdev->graph_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(v4l_enable_media_source);
void v4l_disable_media_source(struct video_device *vdev)
{
struct media_device *mdev = vdev->entity.graph_obj.mdev;
if (mdev) {
mutex_lock(&mdev->graph_mutex);
if (mdev->disable_source)
mdev->disable_source(&vdev->entity);
mutex_unlock(&mdev->graph_mutex);
}
}
EXPORT_SYMBOL_GPL(v4l_disable_media_source);
int v4l_vb2q_enable_media_source(struct vb2_queue *q)
{
struct v4l2_fh *fh = q->owner;
if (fh && fh->vdev)
return v4l_enable_media_source(fh->vdev);
return 0;
}
EXPORT_SYMBOL_GPL(v4l_vb2q_enable_media_source);
int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd,
struct media_pad *sink, u32 flags)
{
struct fwnode_handle *endpoint;
if (!(sink->flags & MEDIA_PAD_FL_SINK))
return -EINVAL;
fwnode_graph_for_each_endpoint(src_sd->fwnode, endpoint) {
struct fwnode_handle *remote_ep;
int src_idx, sink_idx, ret;
struct media_pad *src;
src_idx = media_entity_get_fwnode_pad(&src_sd->entity,
endpoint,
MEDIA_PAD_FL_SOURCE);
if (src_idx < 0) {
dev_dbg(src_sd->dev, "no source pad found for %pfw\n",
endpoint);
continue;
}
remote_ep = fwnode_graph_get_remote_endpoint(endpoint);
if (!remote_ep) {
dev_dbg(src_sd->dev, "no remote ep found for %pfw\n",
endpoint);
continue;
}
/*
* ask the sink to verify it owns the remote endpoint,
* and translate to a sink pad.
*/
sink_idx = media_entity_get_fwnode_pad(sink->entity,
remote_ep,
MEDIA_PAD_FL_SINK);
fwnode_handle_put(remote_ep);
if (sink_idx < 0 || sink_idx != sink->index) {
dev_dbg(src_sd->dev,
"sink pad index mismatch or error (is %d, expected %u)\n",
sink_idx, sink->index);
continue;
}
/*
* the source endpoint corresponds to one of its source pads,
* the source endpoint connects to an endpoint at the sink
* entity, and the sink endpoint corresponds to the sink
* pad requested, so we have found an endpoint connection
* that works, create the media link for it.
*/
src = &src_sd->entity.pads[src_idx];
/* skip if link already exists */
if (media_entity_find_link(src, sink)) {
dev_dbg(src_sd->dev,
"link %s:%d -> %s:%d already exists\n",
src_sd->entity.name, src_idx,
sink->entity->name, sink_idx);
continue;
}
dev_dbg(src_sd->dev, "creating link %s:%d -> %s:%d\n",
src_sd->entity.name, src_idx,
sink->entity->name, sink_idx);
ret = media_create_pad_link(&src_sd->entity, src_idx,
sink->entity, sink_idx, flags);
if (ret) {
dev_err(src_sd->dev,
"link %s:%d -> %s:%d failed with %d\n",
src_sd->entity.name, src_idx,
sink->entity->name, sink_idx, ret);
fwnode_handle_put(endpoint);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_create_fwnode_links_to_pad);
int v4l2_create_fwnode_links(struct v4l2_subdev *src_sd,
struct v4l2_subdev *sink_sd)
{
unsigned int i;
for (i = 0; i < sink_sd->entity.num_pads; i++) {
struct media_pad *pad = &sink_sd->entity.pads[i];
int ret;
if (!(pad->flags & MEDIA_PAD_FL_SINK))
continue;
ret = v4l2_create_fwnode_links_to_pad(src_sd, pad, 0);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_create_fwnode_links);
/* -----------------------------------------------------------------------------
* Pipeline power management
*
* Entities must be powered up when part of a pipeline that contains at least
* one open video device node.
*
* To achieve this use the entity use_count field to track the number of users.
* For entities corresponding to video device nodes the use_count field stores
* the users count of the node. For entities corresponding to subdevs the
* use_count field stores the total number of users of all video device nodes
* in the pipeline.
*
* The v4l2_pipeline_pm_{get, put}() functions must be called in the open() and
* close() handlers of video device nodes. It increments or decrements the use
* count of all subdev entities in the pipeline.
*
* To react to link management on powered pipelines, the link setup notification
* callback updates the use count of all entities in the source and sink sides
* of the link.
*/
/*
* pipeline_pm_use_count - Count the number of users of a pipeline
* @entity: The entity
*
* Return the total number of users of all video device nodes in the pipeline.
*/
static int pipeline_pm_use_count(struct media_entity *entity,
struct media_graph *graph)
{
int use = 0;
media_graph_walk_start(graph, entity);
while ((entity = media_graph_walk_next(graph))) {
if (is_media_entity_v4l2_video_device(entity))
use += entity->use_count;
}
return use;
}
/*
* pipeline_pm_power_one - Apply power change to an entity
* @entity: The entity
* @change: Use count change
*
* Change the entity use count by @change. If the entity is a subdev update its
* power state by calling the core::s_power operation when the use count goes
* from 0 to != 0 or from != 0 to 0.
*
* Return 0 on success or a negative error code on failure.
*/
static int pipeline_pm_power_one(struct media_entity *entity, int change)
{
struct v4l2_subdev *subdev;
int ret;
subdev = is_media_entity_v4l2_subdev(entity)
? media_entity_to_v4l2_subdev(entity) : NULL;
if (entity->use_count == 0 && change > 0 && subdev != NULL) {
ret = v4l2_subdev_call(subdev, core, s_power, 1);
if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
}
entity->use_count += change;
WARN_ON(entity->use_count < 0);
if (entity->use_count == 0 && change < 0 && subdev != NULL)
v4l2_subdev_call(subdev, core, s_power, 0);
return 0;
}
/*
* pipeline_pm_power - Apply power change to all entities in a pipeline
* @entity: The entity
* @change: Use count change
*
* Walk the pipeline to update the use count and the power state of all non-node
* entities.
*
* Return 0 on success or a negative error code on failure.
*/
static int pipeline_pm_power(struct media_entity *entity, int change,
struct media_graph *graph)
{
struct media_entity *first = entity;
int ret = 0;
if (!change)
return 0;
media_graph_walk_start(graph, entity);
while (!ret && (entity = media_graph_walk_next(graph)))
if (is_media_entity_v4l2_subdev(entity))
ret = pipeline_pm_power_one(entity, change);
if (!ret)
return ret;
media_graph_walk_start(graph, first);
while ((first = media_graph_walk_next(graph))
&& first != entity)
if (is_media_entity_v4l2_subdev(first))
pipeline_pm_power_one(first, -change);
return ret;
}
static int v4l2_pipeline_pm_use(struct media_entity *entity, unsigned int use)
{
struct media_device *mdev = entity->graph_obj.mdev;
int change = use ? 1 : -1;
int ret;
mutex_lock(&mdev->graph_mutex);
/* Apply use count to node. */
entity->use_count += change;
WARN_ON(entity->use_count < 0);
/* Apply power change to connected non-nodes. */
ret = pipeline_pm_power(entity, change, &mdev->pm_count_walk);
if (ret < 0)
entity->use_count -= change;
mutex_unlock(&mdev->graph_mutex);
return ret;
}
int v4l2_pipeline_pm_get(struct media_entity *entity)
{
return v4l2_pipeline_pm_use(entity, 1);
}
EXPORT_SYMBOL_GPL(v4l2_pipeline_pm_get);
void v4l2_pipeline_pm_put(struct media_entity *entity)
{
/* Powering off entities shouldn't fail. */
WARN_ON(v4l2_pipeline_pm_use(entity, 0));
}
EXPORT_SYMBOL_GPL(v4l2_pipeline_pm_put);
int v4l2_pipeline_link_notify(struct media_link *link, u32 flags,
unsigned int notification)
{
struct media_graph *graph = &link->graph_obj.mdev->pm_count_walk;
struct media_entity *source = link->source->entity;
struct media_entity *sink = link->sink->entity;
int source_use;
int sink_use;
int ret = 0;
source_use = pipeline_pm_use_count(source, graph);
sink_use = pipeline_pm_use_count(sink, graph);
if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
!(flags & MEDIA_LNK_FL_ENABLED)) {
/* Powering off entities is assumed to never fail. */
pipeline_pm_power(source, -sink_use, graph);
pipeline_pm_power(sink, -source_use, graph);
return 0;
}
if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH &&
(flags & MEDIA_LNK_FL_ENABLED)) {
ret = pipeline_pm_power(source, sink_use, graph);
if (ret < 0)
return ret;
ret = pipeline_pm_power(sink, source_use, graph);
if (ret < 0)
pipeline_pm_power(source, -sink_use, graph);
}
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_pipeline_link_notify);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* v4l2-spi - SPI helpers for Video4Linux2
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
void v4l2_spi_subdev_unregister(struct v4l2_subdev *sd)
{
struct spi_device *spi = v4l2_get_subdevdata(sd);
if (spi && !spi->dev.of_node && !spi->dev.fwnode)
spi_unregister_device(spi);
}
void v4l2_spi_subdev_init(struct v4l2_subdev *sd, struct spi_device *spi,
const struct v4l2_subdev_ops *ops)
{
v4l2_subdev_init(sd, ops);
sd->flags |= V4L2_SUBDEV_FL_IS_SPI;
/* the owner is the same as the spi_device's driver owner */
sd->owner = spi->dev.driver->owner;
sd->dev = &spi->dev;
/* spi_device and v4l2_subdev point to one another */
v4l2_set_subdevdata(sd, spi);
spi_set_drvdata(spi, sd);
/* initialize name */
snprintf(sd->name, sizeof(sd->name), "%s %s",
spi->dev.driver->name, dev_name(&spi->dev));
}
EXPORT_SYMBOL_GPL(v4l2_spi_subdev_init);
struct v4l2_subdev *v4l2_spi_new_subdev(struct v4l2_device *v4l2_dev,
struct spi_controller *ctlr,
struct spi_board_info *info)
{
struct v4l2_subdev *sd = NULL;
struct spi_device *spi = NULL;
if (!v4l2_dev)
return NULL;
if (info->modalias[0])
request_module(info->modalias);
spi = spi_new_device(ctlr, info);
if (!spi || !spi->dev.driver)
goto error;
if (!try_module_get(spi->dev.driver->owner))
goto error;
sd = spi_get_drvdata(spi);
/*
* Register with the v4l2_device which increases the module's
* use count as well.
*/
if (__v4l2_device_register_subdev(v4l2_dev, sd, sd->owner))
sd = NULL;
/* Decrease the module use count to match the first try_module_get. */
module_put(spi->dev.driver->owner);
error:
/*
* If we have a client but no subdev, then something went wrong and
* we must unregister the client.
*/
if (!sd)
spi_unregister_device(spi);
return sd;
}
EXPORT_SYMBOL_GPL(v4l2_spi_new_subdev);
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* V4L2 sub-device pivate header.
*
* Copyright (C) 2023 Hans de Goede <hdegoede@redhat.com>
*/
#ifndef _V4L2_SUBDEV_PRIV_H_
#define _V4L2_SUBDEV_PRIV_H_
int v4l2_subdev_get_privacy_led(struct v4l2_subdev *sd);
void v4l2_subdev_put_privacy_led(struct v4l2_subdev *sd);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
#include <media/v4l2-common.h>
#include <media/v4l2-fh.h>
#include <media/videobuf2-v4l2.h>
#define CREATE_TRACE_POINTS
#include <trace/events/v4l2.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_v4l2_buf_done);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_v4l2_buf_queue);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_v4l2_dqbuf);
EXPORT_TRACEPOINT_SYMBOL_GPL(vb2_v4l2_qbuf);
File diff suppressed because it is too large Load Diff