diff --git a/drivers/media/platform/tegra/Makefile b/drivers/media/platform/tegra/Makefile index d2394660..3a65d7b2 100644 --- a/drivers/media/platform/tegra/Makefile +++ b/drivers/media/platform/tegra/Makefile @@ -5,3 +5,4 @@ obj-m += cdi/ obj-m += isc/ obj-m += camera/ obj-m += tpg/ +obj-m += cam_fsync/ diff --git a/drivers/media/platform/tegra/cam_fsync/Makefile b/drivers/media/platform/tegra/cam_fsync/Makefile new file mode 100644 index 00000000..8edb1fd2 --- /dev/null +++ b/drivers/media/platform/tegra/cam_fsync/Makefile @@ -0,0 +1,3 @@ +GCOV_PROFILE := y + +obj-m += cam_fsync.o diff --git a/drivers/media/platform/tegra/cam_fsync/cam_fsync.c b/drivers/media/platform/tegra/cam_fsync/cam_fsync.c new file mode 100644 index 00000000..1e0c459a --- /dev/null +++ b/drivers/media/platform/tegra/cam_fsync/cam_fsync.c @@ -0,0 +1,1149 @@ +// SPDX-License-Identifier: LicenseRef-NvidiaProprietary +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define TSC_TICKS_PER_HZ (31250000ULL) +#define TSC_NS_PER_TICK (32) +#define NS_PER_MS (1000000U) + +#define TSC_MTSCCNTCV0 (0x10) +#define TSC_MTSCCNTCV0_CV GENMASK(31, 0) + +#define TSC_MTSCCNTCV1 (0x14) +#define TSC_MTSCCNTCV1_CV GENMASK(31, 0) + +#define TSC_GENX_CTRL (0x00) +#define TSC_GENX_CTRL_RST (0x00) +#define TSC_GENX_CTRL_INITIAL_VAL BIT(1) +#define TSC_GENX_CTRL_ENABLE BIT(0) + +#define TSC_GENX_START0 (0x04) +#define TSC_GENX_START0_LSB_VAL GENMASK(31, 0) + +#define TSC_GENX_START1 (0x08) +#define TSC_GENX_START1_MSB_VAL GENMASK(23, 0) + +#define TSC_GENX_STATUS (0x0C) +#define TSC_GENX_STATUS_INTERRUPT_STATUS BIT(6) +#define TSC_GENX_STATUS_VALUE BIT(5) +#define TSC_GENX_STATUS_EDGE_ID GENMASK(4, 2) +#define TSC_GENX_STATUS_RUNNING BIT(1) +#define TSC_GENX_STATUS_WAITING BIT(0) + +#define TSC_GENX_EDGE0 (0x18) +#define TSC_GENX_EDGE1 (0x1C) +#define TSC_GENX_EDGE2 (0x20) +#define TSC_GENX_EDGE3 (0x24) +#define TSC_GENX_EDGE4 (0x28) +#define TSC_GENX_EDGE5 (0x2C) +#define TSC_GENX_EDGE6 (0x30) +#define TSC_GENX_EDGE7 (0x34) + +#define TSC_GENX_EDGEX_INTERRUPT_EN BIT(31) +#define TSC_GENX_EDGEX_STOP BIT(30) +#define TSC_GENX_EDGEX_TOGGLE BIT(29) +#define TSC_GENX_EDGEX_LOOP BIT(28) +#define TSC_GENX_EDGEX_OFFSET GENMASK(27, 0) + +/* Time (ms) offset for the TSC signal generators */ +#define TSC_GENX_START_OFFSET_MS (100) + +#define TSC_DEFAULT_GROUP_ID (0) +#define TSC_MAX_GENERATORS (4) + +#define CAM_FSYNC_CLASS_NAME "cam-fsync-groups" +#define CAM_FSYNC_GROUPS_NODE "fsync-groups" + +/** + * struct cam_fsync_controller_features: TSC signal controller SW feature support. + * @rational_locking: + * @enforced: Generator periods must be derived from a common base. + * @max_freq_hz_lcm: Maximum frequency (hz) of the common base generator period. + * @offset: + * @enabled: Allow generators to offset their signal from the start of their period. + */ +struct cam_fsync_controller_features { + struct { + bool enforced; + u32 max_freq_hz_lcm; + } rational_locking; + struct { + bool enabled; + } offset; +}; + +/** + * struct cam_fsync_generator : Generator context. + * @base: ioremapped register base. + * @of: Generator device node. + * @config: + * @freq_hz: Frequency (hz) of the generator. + * @duty_cycle: Duty cycle (%) of the generator. + * @offset_ms: Offset (ms) to shift the signal by. + * @debugfs: + * @regset_ro: Debug FS read-only register set. + * @list: List node + */ +struct cam_fsync_generator { + void __iomem *base; + struct device_node *of; + struct { + u32 freq_hz; + u32 duty_cycle; + u32 offset_ms; + } config; + struct { + struct debugfs_regset32 regset_ro; + } debugfs; + struct list_head list; +}; + +/** + * sturct fsync_generator_group : Generator group context + * @id: Group id as specified in DT + * @dev: device. + * @features: Feature support for the group. + * @abs_start_ticks: Start time in TSC ticks to start all generators in group + * @active: Is group active + * @generators: Linked list of child generators + * @list: List node + */ +struct fsync_generator_group { + u32 id; + struct device *dev; + const struct cam_fsync_controller_features *features; + uint64_t abs_start_ticks; + bool active; + struct list_head generators; + struct list_head list; +}; + +/** + * struct cam_fsync_controller : Controller context + * @dev: device. + * @cdev: character device + * @base: ioremapped register base. + * @debugfs: + * @d: dentry to debugfs directory. + * @features: Feature support for the controller. + * @cam_fsync_class: Config class + * @cam_fsync_major: Holds dynamically allocated major number + * @groups: Linked list of child groups. + * @cam_fsync_fops: Holds file ops function pointer + */ +struct cam_fsync_controller { + struct device *dev; + struct cdev cdev; + void __iomem *base; + struct { + struct dentry *d; + } debugfs; + const struct cam_fsync_controller_features *features; + struct class *cam_fsync_class; + int cam_fsync_major; + struct list_head groups; + const struct file_operations cam_fsync_fops; +}; + +static const struct cam_fsync_controller_features tegra234_cam_fsync_features = { + .rational_locking = { + .enforced = true, + .max_freq_hz_lcm = 120, + }, + .offset = { + .enabled = true, + }, +}; + +static const struct debugfs_reg32 cam_fsync_generator_debugfs_regset[] = { + { + .name = "status", + .offset = TSC_GENX_STATUS, + }, +}; + +#define TSC_SIG_GEN_DEBUGFS_REGSET_SIZE ARRAY_SIZE(cam_fsync_generator_debugfs_regset) + + +static inline void +cam_fsync_generator_writel(struct cam_fsync_generator *generator, u32 reg, u32 val) +{ + writel(val, generator->base + reg); +} + +static inline u32 +cam_fsync_generator_readl(struct cam_fsync_generator *generator, u32 reg) +{ + return readl(generator->base + reg); +} + +static inline u32 +cam_fsync_controller_readl(struct cam_fsync_controller *controller, u32 reg) +{ + return readl(controller->base + reg); +} + +static u32 +cam_fsync_find_max_freq_hz_lcm(struct fsync_generator_group *group) +{ + struct cam_fsync_generator *generator; + u32 running_lcm = 0; + + list_for_each_entry(generator, &group->generators, list) { + running_lcm = lcm_not_zero(generator->config.freq_hz, running_lcm); + } + + return running_lcm; +} + +/** + * @brief Check if generator is already part of any group + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] new_generator pointer to struct cam_fsync_generator (non-null) + * + * @returns true (Generator in some group), false (Generator not in group) + */ +static bool +cam_fsync_is_generator_in_group(struct cam_fsync_controller *controller, + struct cam_fsync_generator *new_generator) +{ + struct cam_fsync_generator *generator; + struct fsync_generator_group *group; + + list_for_each_entry(group, &controller->groups, list) { + list_for_each_entry(generator, &group->generators, list) { + if (!strcmp(generator->of->full_name, new_generator->of->full_name)) + return true; + } + } + return false; +} + +/** + * @brief Add generators to fsync generator group struct + * Allocate memory for generator, read and program details from DT + * Add generator to list in group structure + * + * @param[in] group pointer to struct fsync_generator_group (non-null) + * @param[in] np pointer to struct device_node (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_add_generator(struct fsync_generator_group *group, struct device_node *np) +{ + struct cam_fsync_generator *generator; + struct resource res; + int err; + struct cam_fsync_controller *controller = dev_get_drvdata(group->dev); + + generator = devm_kzalloc(group->dev, sizeof(*generator), GFP_KERNEL); + if (!generator) + return -ENOMEM; + + generator->of = np; + INIT_LIST_HEAD(&generator->list); + + if (of_address_to_resource(np, 0, &res)) + return -EINVAL; + + if (cam_fsync_is_generator_in_group(controller, generator)) { + dev_err(group->dev, "Generator is already part of a group\n"); + return -EALREADY; + } + + generator->base = devm_ioremap_resource(group->dev, &res); + if (IS_ERR(generator->base)) + return PTR_ERR(generator->base); + + err = of_property_read_u32(np, "freq_hz", &generator->config.freq_hz); + if (err != 0) { + dev_err(group->dev, "Failed to read generator frequency: %d\n", err); + return err; + } + + if (generator->config.freq_hz == 0) { + dev_err(group->dev, "Frequency must be non-zero\n"); + return -EINVAL; + } + + err = of_property_read_u32(np, "duty_cycle", &generator->config.duty_cycle); + if (err != 0) { + dev_err(group->dev, "Failed to read generator duty cycle: %d\n", err); + return err; + } + if (generator->config.duty_cycle >= 100) { + dev_err(group->dev, "Duty cycle must be < 100%%\n"); + return -EINVAL; + } + + if (group->features->offset.enabled) { + err = of_property_read_u32(np, "offset_ms", &generator->config.offset_ms); + if (err != 0) { + dev_err(group->dev, "Failed to read generator offset: %d\n", err); + return err; + } + } + list_add_tail(&generator->list, &group->generators); + return err; +} + +static int cam_fsync_program_group_generator_edges(struct fsync_generator_group *group) +{ + struct cam_fsync_generator *generator; + u32 max_freq_hz_lcm = 0; + + /* + * If rational locking is enforced (e.g. a 30Hz & 60Hz signal must align every two periods + * w.r.t. the 60Hz signal) edges will be derived from whole-number multiples of the LCM of + * all generator frequencies belonging to this group. + * + * If rational locking is _not_ enforced then generator edges will be independently + * derived based on their configured frequency. + */ + if (group->features->rational_locking.enforced) { + max_freq_hz_lcm = cam_fsync_find_max_freq_hz_lcm(group); + if (max_freq_hz_lcm > group->features->rational_locking.max_freq_hz_lcm) { + dev_err(group->dev, + "Highest common frequency of %u hz exceeds maximum allowed (%u hz)\n", + max_freq_hz_lcm, + group->features->rational_locking.max_freq_hz_lcm); + return -EINVAL; + } + } + + list_for_each_entry(generator, &group->generators, list) { + u32 ticks_in_period = 0; + u32 ticks_active = 0; + u32 ticks_inactive = 0; + + if (group->features->rational_locking.enforced) { + ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, max_freq_hz_lcm); + ticks_in_period *= max_freq_hz_lcm / generator->config.freq_hz; + } else { + ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, + generator->config.freq_hz); + } + + ticks_active = mult_frac(ticks_in_period, generator->config.duty_cycle, 100); + ticks_inactive = ticks_in_period - ticks_active; + + cam_fsync_generator_writel(generator, TSC_GENX_EDGE0, + TSC_GENX_EDGEX_TOGGLE | + FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_active)); + + cam_fsync_generator_writel(generator, TSC_GENX_EDGE1, + TSC_GENX_EDGEX_TOGGLE | + TSC_GENX_EDGEX_LOOP | + FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_inactive)); + } + + return 0; +} + +/** + * @brief Program start value to the generator + * + * @param[in] group pointer to struct fsync_generator_group (non-null) + */ +static void cam_fsync_program_group_generator_start_values(struct fsync_generator_group *group) +{ + struct cam_fsync_generator *generator; + uint64_t abs_start_tsc_ticks = 0; + + list_for_each_entry(generator, &group->generators, list) { + abs_start_tsc_ticks = group->abs_start_ticks; + if (group->features->offset.enabled && (generator->config.offset_ms != 0)) + abs_start_tsc_ticks += mult_frac(generator->config.offset_ms, + NS_PER_MS, TSC_NS_PER_TICK); + + cam_fsync_generator_writel(generator, TSC_GENX_START0, + FIELD_PREP(TSC_GENX_START0_LSB_VAL, lower_32_bits(abs_start_tsc_ticks))); + + cam_fsync_generator_writel(generator, TSC_GENX_START1, + FIELD_PREP(TSC_GENX_START1_MSB_VAL, upper_32_bits(abs_start_tsc_ticks))); + } +} + +/** + * @brief Get current tsc ticks + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * + * @returns Current ticks + */ +static u64 cam_fsync_get_current_tsc_ticks(struct cam_fsync_controller *controller) +{ + const u32 current_ticks_lo = FIELD_GET(TSC_MTSCCNTCV0_CV, + cam_fsync_controller_readl(controller, TSC_MTSCCNTCV0)); + const u32 current_ticks_hi = FIELD_GET(TSC_MTSCCNTCV1_CV, + cam_fsync_controller_readl(controller, TSC_MTSCCNTCV1)); + const u64 current_ticks = ((u64)current_ticks_hi << 32) | current_ticks_lo; + return current_ticks; +} + +/** + * @brief Get default start time + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * + * @returns default start ticks + */ +static u64 cam_fsync_get_default_start_ticks(struct cam_fsync_controller *controller) +{ + u64 default_start_ticks = mult_frac( + TSC_GENX_START_OFFSET_MS, NS_PER_MS, TSC_NS_PER_TICK); + default_start_ticks += cam_fsync_get_current_tsc_ticks(controller); + return default_start_ticks; +} + +/** + * @brief Check if generator is running + * + * @param[in] generator pointer to struct cam_fsync_generator (non-null) + * + * @returns True (running), False (Not running) + */ +static bool cam_fsync_generator_is_running(struct cam_fsync_generator *generator) +{ + const u32 status = cam_fsync_generator_readl(generator, TSC_GENX_STATUS); + + return FIELD_GET(TSC_GENX_STATUS_RUNNING, status) == 1; +} + +/** + * @brief Check if generator is waiting to start + * + * @param[in] generator pointer to struct cam_fsync_generator (non-null) + * + * @returns True (Waiting), False (Not Waiting) + */ +static bool cam_fsync_generator_is_waiting(struct cam_fsync_generator *generator) +{ + const u32 status = cam_fsync_generator_readl(generator, TSC_GENX_STATUS); + + return FIELD_GET(TSC_GENX_STATUS_WAITING, status) == 1; +} + +/** + * @brief Check if generator is idle + * + * @param[in] generator pointer to struct cam_fsync_generator (non-null) + * + * @returns True (Idle), False (Not Idle) + */ +static inline bool cam_fsync_generator_is_idle(struct cam_fsync_generator *generator) +{ + return !cam_fsync_generator_is_running(generator) && + !cam_fsync_generator_is_waiting(generator); +} + +/** + * @brief Start all generators in group + * Program and start generator in group + * + * @param[in] group pointer to struct fsync_generator_group (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_start_group_generators(struct fsync_generator_group *group) +{ + struct cam_fsync_generator *generator; + int err; + + /* A generator must be idle (e.g. neither running nor waiting) before starting */ + list_for_each_entry(generator, &group->generators, list) { + if (!cam_fsync_generator_is_idle(generator)) { + dev_err(group->dev, "Generator %s in group %d is not idle\n", + generator->of->full_name, group->id); + return -EBUSY; + } + } + + err = cam_fsync_program_group_generator_edges(group); + if (err != 0) + return err; + + cam_fsync_program_group_generator_start_values(group); + + /* Start the generators */ + list_for_each_entry(generator, &group->generators, list) { + cam_fsync_generator_writel(generator, TSC_GENX_CTRL, + TSC_GENX_CTRL_INITIAL_VAL | TSC_GENX_CTRL_ENABLE); + + if (cam_fsync_generator_is_idle(generator)) { + dev_err(group->dev, "Generator start failed %s\n", + generator->of->full_name); + return -EFAULT; + } + } + group->active = true; + + return 0; +} + +/** + * @brief Stop all generators in group + * + * @param[in] group pointer to struct fsync_generator_group (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_stop_group_generators(struct fsync_generator_group *group) +{ + struct cam_fsync_generator *generator; + + list_for_each_entry(generator, &group->generators, list) { + cam_fsync_generator_writel(generator, TSC_GENX_CTRL, TSC_GENX_CTRL_RST); + + /* Ensure the generator has stopped */ + if (!cam_fsync_generator_is_idle(generator)) { + dev_err(group->dev, "Generator %s failed to stop\n", + generator->of->full_name); + return -EIO; + } + } + + return 0; +} + +/** + * @brief Init debugfs + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_debugfs_init(struct cam_fsync_controller *controller) +{ + struct cam_fsync_generator *generator; + struct fsync_generator_group *group; + + controller->debugfs.d = + debugfs_create_dir(controller->dev->of_node->full_name, NULL); + if (IS_ERR(controller->debugfs.d)) + return PTR_ERR(controller->debugfs.d); + + list_for_each_entry(group, &controller->groups, list) { + list_for_each_entry(generator, &group->generators, list) { + generator->debugfs.regset_ro.regs = cam_fsync_generator_debugfs_regset; + generator->debugfs.regset_ro.nregs = TSC_SIG_GEN_DEBUGFS_REGSET_SIZE; + generator->debugfs.regset_ro.base = generator->base; + + debugfs_create_regset32( + generator->of->full_name, + 0400, + controller->debugfs.d, + &generator->debugfs.regset_ro); + } + } + + return 0; +} + + +/** + * @brief Remove debugfs + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + */ +static void cam_fsync_debugfs_remove(struct cam_fsync_controller *controller) +{ + debugfs_remove_recursive(controller->debugfs.d); + controller->debugfs.d = NULL; +} + +/** + * @brief Get correspoding group for specified id + * Iterate through list of groups and return pointer to the group for which group_id match + * otherwise return null + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] group_id numerical value start time(0: MAX_GROUP_ID_DEFINED IN_DT) + * + * @returns 0 (success), neg. errno (failure) + */ +static struct fsync_generator_group * +cam_fsync_get_group_by_id(struct cam_fsync_controller *controller, unsigned int group_id) +{ + struct fsync_generator_group *group; + + list_for_each_entry(group, &controller->groups, list) { + if (group->id == group_id) { + dev_info(controller->dev, "Group %u found\n", group_id); + return group; + } + } + + dev_err(controller->dev, "Group %u not found\n", group_id); + return NULL; +} + +/** + * @brief Open group node + * @ref Get reference to corresponding group structure + * + * This is the a open file operation handler for a cam fsync generator group node. + * + * @param[in] inode Generator group character device inode struct (Non-NULL) + * @param[in] file Generator group character device file struct (Non-NULL) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_open(struct inode *inode, struct file *file) +{ + unsigned int group_id = iminor(inode); + struct cam_fsync_controller *controller; + struct fsync_generator_group *group; + + controller = container_of(file->f_op, struct cam_fsync_controller, cam_fsync_fops); + if (IS_ERR(controller)) + return PTR_ERR(controller); + + group = cam_fsync_get_group_by_id(controller, group_id); + if (IS_ERR(group)) + return PTR_ERR(group); + + file->private_data = group; + + return 0; +} + +/** + * @brief Validate start time + * Start time ticks must be greater than current ticks + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] start_time numerical value start time(current_ticks < value > MAX_UINT64) + * + * @returns 0 (success), neg. errno (failure) + */ +static int +cam_fsync_validate_start_time(struct cam_fsync_controller *controller, u64 start_time_ticks) +{ + u64 current_ticks = cam_fsync_get_current_tsc_ticks(controller); + + if (start_time_ticks < current_ticks) { + dev_err(controller->dev, "Start time is in past\n"); + return -EFAULT; + } + + return 0; +} + +/** + * @brief Process an IOCTL call on a cam fsync group character device. + * + * Depending on the specific IOCTL, the argument may be a pointer to a + * defined struct payload that is copied from user-space. This memory + * is allocated and mapped from user-space and must be kept available until + * after the IOCTL call completes. + * + * This is the a ioctl file operation handler for a cam fsync group. + * + * @param[in] file cam fsync group character device file struct (non-null) + * @param[in] cmd cam fsync group IOCTL command (CAM_FSYNC_GRP_ABS_START_VAL) + * @param[in] startTimeInTSCTicks numerical value start time + * (current_ticks < value > MAX_UINT64) + * + * @returns 0 (success), neg. errno (failure) + */ +static long cam_fsync_ioctl(struct file *file, unsigned int cmd, unsigned long startTimeInTSCTicks) +{ + struct fsync_generator_group *group = file->private_data; + struct cam_fsync_controller *controller = dev_get_drvdata(group->dev); + long err = 0; + + switch (cmd) { + case CAM_FSYNC_GRP_ABS_START_VAL: + if (copy_from_user(&group->abs_start_ticks, (u64 *)startTimeInTSCTicks, + sizeof(group->abs_start_ticks))) + dev_err(group->dev, "Unable to read start value\n"); + err = cam_fsync_validate_start_time(controller, group->abs_start_ticks); + if (err != 0) { + dev_err(group->dev, "Invalid start value\n"); + return err; + } + err = cam_fsync_start_group_generators(group); + break; + default: + dev_err(group->dev, "Invalid command\n"); + err = -EINVAL; + break; + } + return err; +} + +static const struct file_operations cam_fsync_fileops = { + .owner = THIS_MODULE, + .open = cam_fsync_open, + .unlocked_ioctl = cam_fsync_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = cam_fsync_ioctl, +#endif +}; + +/** + * @brief Create device node for group + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] pdev pointer to struct platform_device (non-null) + * @param[in] group pointer to struct fsync_generator_group (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_group_node_init(struct cam_fsync_controller *controller, + struct platform_device *pdev, + struct fsync_generator_group *group) +{ + struct device *dev; + dev_t devt; + + devt = MKDEV(controller->cam_fsync_major, group->id); + + dev = device_create(controller->cam_fsync_class, &pdev->dev, devt, NULL, + "fsync-group%u", group->id); + if (IS_ERR(dev)) { + dev_err(controller->dev, "Error creating device for group %d\n", group->id); + return PTR_ERR(dev); + } + + return 0; +} + +/** + * @brief Destroy device node for group + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] group pointer to struct fsync_generator_group (non-null) + * + * @returns 0 (success) + */ +static int cam_fsync_group_node_deinit(struct cam_fsync_controller *controller, + struct fsync_generator_group *group) +{ + dev_t devt; + + devt = MKDEV(controller->cam_fsync_major, group->id); + + device_destroy(controller->cam_fsync_class, devt); + + return 0; +} + +/** + * @brief Create and initialize group structure + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] group_id id corresponding to group read from DT (0: MAX_UINT32_t) + * + * @returns struct fsync_generator_group *(pointer to newly create group struct or null) + */ +static struct fsync_generator_group *cam_fsync_group_init( + struct cam_fsync_controller *controller, + u32 group_id) +{ + struct fsync_generator_group *group = devm_kzalloc(controller->dev, + sizeof(*group), GFP_KERNEL); + + if (IS_ERR(group)) + return group; + + INIT_LIST_HEAD(&group->generators); + INIT_LIST_HEAD(&group->list); + group->id = group_id; + group->dev = controller->dev; + group->features = controller->features; + return group; +} + +/** + * @brief Create default group struct + * If group node is not found in DT, create a group with default group id for all generators + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_create_default_group(struct cam_fsync_controller *controller) +{ + struct device_node *np; + const char *node_status; + int err = 0; + struct fsync_generator_group *group = cam_fsync_group_init(controller, + TSC_DEFAULT_GROUP_ID); + + if (IS_ERR(group)) + return PTR_ERR(group); + + dev_info(controller->dev, "Creating default group with id %d\n", TSC_DEFAULT_GROUP_ID); + + for_each_child_of_node(controller->dev->of_node, np) { + err = of_property_read_string(np, "status", &node_status); + if (err != 0) { + dev_err(controller->dev, "Failed to read generator status: %d\n", err); + return err; + } + if (strcmp("okay", node_status) != 0) { + dev_dbg(controller->dev, "Generator %s disabled - skipping\n", + np->full_name); + continue; + } + err = cam_fsync_add_generator(group, np); + if (err != 0) { + dev_err(controller->dev, "Failed to add generator : %d\n", err); + return err; + } + } + list_add_tail(&group->list, &controller->groups); + + return err; +} + +/** + * @brief Find and create group + * If group node is found in DT, create a group with generators defined in DT config + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * @param[in] pdev pointer to struct platform_device (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_find_and_add_groups(struct cam_fsync_controller *controller, + struct platform_device *pdev) +{ + struct device_node *np; + struct device_node *group_node; + const char *node_status; + int err = 0; + int num_generators, i; + struct device_node *gen; + struct fsync_generator_group *group; + u32 group_id = 0; + + group_node = of_get_child_by_name(controller->dev->of_node, CAM_FSYNC_GROUPS_NODE); + if (group_node == NULL) { + dev_err(controller->dev, "Failed to read generator status: %d\n", err); + return -EOPNOTSUPP; + } + err = of_property_read_string(group_node, "status", &node_status); + if (err != 0) { + dev_err(controller->dev, "Failed to read group status: %d\n", err); + return err; + } + if (strcmp("okay", node_status) != 0) { + dev_err(controller->dev, "Grouping disabled\n"); + return -EOPNOTSUPP; + } + + for_each_child_of_node(group_node, np) { + err = of_property_read_string(np, "status", &node_status); + if (err != 0) { + dev_err(controller->dev, "Failed to read group status: %d\n", err); + return err; + } + if (strcmp("okay", node_status) != 0) { + dev_dbg(controller->dev, "Group %s disabled - skipping\n", np->full_name); + continue; + } + + err = of_property_read_u32(np, "id", &group_id); + if (err != 0) { + dev_err(controller->dev, "Failed to read group id: %d\n", err); + return err; + } + + group = cam_fsync_group_init(controller, group_id); + if (IS_ERR(group)) + return PTR_ERR(group); + + cam_fsync_group_node_init(controller, pdev, group); + + num_generators = of_property_count_elems_of_size(np, "generators", + sizeof(u32)); + for (i = 0; i < num_generators; i++) { + gen = of_parse_phandle(np, "generators", i); + err = cam_fsync_add_generator(group, gen); + if (err != 0) { + dev_err(controller->dev, "Failed to add generator %s : %d\n", + gen->full_name, err); + cam_fsync_group_node_deinit(controller, group); + return err; + } + } + list_add_tail(&group->list, &controller->groups); + } + return err; +} + +/** + * @brief Character device init + * Create class and register char device to get major number allocated dynamically + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_chrdev_init(struct cam_fsync_controller *controller) +{ + controller->cam_fsync_class = class_create(THIS_MODULE, CAM_FSYNC_CLASS_NAME); + if (IS_ERR(controller->cam_fsync_class)) + return PTR_ERR(controller->cam_fsync_class); + + controller->cam_fsync_major = register_chrdev(0, CAM_FSYNC_CLASS_NAME, + &controller->cam_fsync_fops); + if (controller->cam_fsync_major < 0) { + class_destroy(controller->cam_fsync_class); + return controller->cam_fsync_major; + } + + return 0; +} + +/** + * @brief Character device deinit + * Delete class and deregister char device + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + */ +static void cam_fsync_chrdev_deinit(struct cam_fsync_controller *controller) +{ + struct fsync_generator_group *group; + + list_for_each_entry(group, &controller->groups, list) { + cam_fsync_group_node_deinit(controller, group); + } + unregister_chrdev(controller->cam_fsync_major, CAM_FSYNC_CLASS_NAME); + class_destroy(controller->cam_fsync_class); +} + +/** + * @brief Create and start default group + * If group is not specified in DT, a default group will be created and all + * generators will be part of it + * + * @param[in] controller pointer to struct cam_fsync_controller (non-null) + */ +static int cam_fsync_default(struct cam_fsync_controller *controller) +{ + struct fsync_generator_group *group; + int err = cam_fsync_create_default_group(controller); + + if (err != 0) + return err; + + group = cam_fsync_get_group_by_id(controller, TSC_DEFAULT_GROUP_ID); + group->abs_start_ticks = cam_fsync_get_default_start_ticks(controller); + return cam_fsync_start_group_generators(group); +} + +/** + * @brief Driver Probe + * Called when driver is loaded. + * + * @param[in] pdev pointer to struct platform_device (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_probe(struct platform_device *pdev) +{ + struct cam_fsync_controller *controller; + struct resource *res; + int err; + + dev_info(&pdev->dev, "CAM_FSYNC probing...\n"); + + controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + + memcpy((void *)&controller->cam_fsync_fops, &cam_fsync_fileops, + sizeof(cam_fsync_fileops)); + + err = cam_fsync_chrdev_init(controller); + if (err != 0) + return err; + + controller->dev = &pdev->dev; + controller->features = of_device_get_match_data(&pdev->dev); + if (controller->features == NULL) { + dev_err(controller->dev, "No controller feature table found\n"); + return -ENODEV; + } + + INIT_LIST_HEAD(&controller->groups); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + controller->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(controller->base)) + return PTR_ERR(controller->base); + + platform_set_drvdata(pdev, controller); + + /* + * Check if fsync group node defined in DT, if yes read group and expose node for each group + * If not defined read all the generators and start them + */ + err = cam_fsync_find_and_add_groups(controller, pdev); + if (err == -EOPNOTSUPP) { + dev_info(controller->dev, + "Failed to find frame sync group, creating default group\n"); + err = cam_fsync_default(controller); + } + + if (err != 0) { + dev_err(controller->dev, + "Failed to create frame sync group\n"); + cam_fsync_chrdev_deinit(controller); + return err; + } + + if (debugfs_initialized()) { + err = cam_fsync_debugfs_init(controller); + if (err != 0) { + cam_fsync_chrdev_deinit(controller); + return err; + } + } + return 0; +} + +/** + * @brief Driver remove + * Called when driver is removed. + * + * @param[in] pdev pointer to struct platform_device (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int cam_fsync_remove(struct platform_device *pdev) +{ + struct cam_fsync_controller *controller = platform_get_drvdata(pdev); + struct fsync_generator_group *group; + int err = 0; + + cam_fsync_debugfs_remove(controller); + cam_fsync_chrdev_deinit(controller); + + list_for_each_entry(group, &controller->groups, list) { + if (group->active) { + err = cam_fsync_stop_group_generators(group); + if (err != 0) + return err; + group->active = false; + } + } + + return err; +} + +/** + * @brief SC7 Suspend + * Called when driver is removed. + * + * @param[in] dev pointer to struct device (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int __maybe_unused cam_fsync_suspend(struct device *dev) +{ + struct cam_fsync_controller *controller = dev_get_drvdata(dev); + struct fsync_generator_group *group; + int err = 0; + + list_for_each_entry(group, &controller->groups, list) { + if (group->active) { + err = cam_fsync_stop_group_generators(group); + if (err != 0) + return err; + } + } + + return err; +} + +/** + * @brief SC7 Resume + * Called when driver is removed. + * + * @param[in] dev pointer to struct device (non-null) + * + * @returns 0 (success), neg. errno (failure) + */ +static int __maybe_unused cam_fsync_resume(struct device *dev) +{ + struct cam_fsync_controller *controller = dev_get_drvdata(dev); + struct fsync_generator_group *group; + int err = 0; + + list_for_each_entry(group, &controller->groups, list) { + if (group->active) { + err = cam_fsync_start_group_generators(group); + if (err != 0) + return err; + } + } + + return err; +} + +static const struct of_device_id cam_fsync_of_match[] = { + { .compatible = "nvidia,tegra234-cdi-tsc", .data = &tegra234_cam_fsync_features }, + { }, +}; +MODULE_DEVICE_TABLE(of, cam_fsync_of_match); + +static SIMPLE_DEV_PM_OPS(cam_fsync_pm, cam_fsync_suspend, cam_fsync_resume); + +static struct platform_driver cam_fsync_driver = { + .driver = { + .name = "cam_fsync", + .owner = THIS_MODULE, + .of_match_table = cam_fsync_of_match, + .pm = &cam_fsync_pm, + }, + .probe = cam_fsync_probe, + .remove = cam_fsync_remove, +}; +module_platform_driver(cam_fsync_driver); + +MODULE_AUTHOR("Mohit Ingale "); +MODULE_DESCRIPTION("Cam Fsync Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cam_fsync"); diff --git a/drivers/media/platform/tegra/cdi/Makefile b/drivers/media/platform/tegra/cdi/Makefile index 64152fa8..dc48d2e4 100644 --- a/drivers/media/platform/tegra/cdi/Makefile +++ b/drivers/media/platform/tegra/cdi/Makefile @@ -11,4 +11,3 @@ obj-m += cdi_mgr.o endif obj-m += cdi_dev.o obj-m += cdi_pwm.o -obj-m += cdi_tsc.o diff --git a/drivers/media/platform/tegra/cdi/cdi_tsc.c b/drivers/media/platform/tegra/cdi/cdi_tsc.c deleted file mode 100644 index d5c6a36b..00000000 --- a/drivers/media/platform/tegra/cdi/cdi_tsc.c +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TSC_TICKS_PER_HZ (31250000ULL) -#define TSC_NS_PER_TICK (32) -#define NS_PER_MS (1000000U) - -#define TSC_MTSCCNTCV0 (0x10) -#define TSC_MTSCCNTCV0_CV GENMASK(31, 0) - -#define TSC_MTSCCNTCV1 (0x14) -#define TSC_MTSCCNTCV1_CV GENMASK(31, 0) - -#define TSC_GENX_CTRL (0x00) -#define TSC_GENX_CTRL_RST (0x00) -#define TSC_GENX_CTRL_INITIAL_VAL BIT(1) -#define TSC_GENX_CTRL_ENABLE BIT(0) - -#define TSC_GENX_START0 (0x04) -#define TSC_GENX_START0_LSB_VAL GENMASK(31, 0) - -#define TSC_GENX_START1 (0x08) -#define TSC_GENX_START1_MSB_VAL GENMASK(23, 0) - -#define TSC_GENX_STATUS (0x0C) -#define TSC_GENX_STATUS_INTERRUPT_STATUS BIT(6) -#define TSC_GENX_STATUS_VALUE BIT(5) -#define TSC_GENX_STATUS_EDGE_ID GENMASK(4, 2) -#define TSC_GENX_STATUS_RUNNING BIT(1) -#define TSC_GENX_STATUS_WAITING BIT(0) - -#define TSC_GENX_EDGE0 (0x18) -#define TSC_GENX_EDGE1 (0x1C) -#define TSC_GENX_EDGE2 (0x20) -#define TSC_GENX_EDGE3 (0x24) -#define TSC_GENX_EDGE4 (0x28) -#define TSC_GENX_EDGE5 (0x2C) -#define TSC_GENX_EDGE6 (0x30) -#define TSC_GENX_EDGE7 (0x34) - -#define TSC_GENX_EDGEX_INTERRUPT_EN BIT(31) -#define TSC_GENX_EDGEX_STOP BIT(30) -#define TSC_GENX_EDGEX_TOGGLE BIT(29) -#define TSC_GENX_EDGEX_LOOP BIT(28) -#define TSC_GENX_EDGEX_OFFSET GENMASK(27, 0) - -/* Time (ms) offset for the TSC signal generators */ -#define TSC_GENX_START_OFFSET_MS (100) - -/** - * struct tsc_signal_controller_features: TSC signal controller SW feature support. - * @rational_locking: - * @enforced: Generator periods must be derived from a common base. - * @max_freq_hz_lcm: Maximum frequency (hz) of the common base generator period. - * @offset: - * @enabled: Allow generators to offset their signal from the start of their period. - */ -struct tsc_signal_controller_features { - struct { - bool enforced; - u32 max_freq_hz_lcm; - } rational_locking; - struct { - bool enabled; - } offset; -}; - -/** - * struct tsc_signal_generator : Generator context. - * @base: ioremapped register base. - * @of: Generator device node. - * @config: - * @freq_hz: Frequency (hz) of the generator. - * @duty_cycle: Duty cycle (%) of the generator. - * @offset_ms: Offset (ms) to shift the signal by. - * @debugfs: - * @regset_ro: Debug FS read-only register set. - * @list: List node - */ -struct tsc_signal_generator { - void __iomem *base; - struct device_node *of; - struct { - u32 freq_hz; - u32 duty_cycle; - u32 offset_ms; - } config; - struct { - struct debugfs_regset32 regset_ro; - } debugfs; - struct list_head list; -}; - -/** - * struct tsc_signal_controller : Controller context - * @dev: device. - * @base: ioremapped register base. - * @debugfs: - * @d: dentry to debugfs directory. - * @features: Feature support for the controller. - * @generators: Linked list of child generators. - */ -struct tsc_signal_controller { - struct device *dev; - void __iomem *base; - struct { - struct dentry *d; - } debugfs; - const struct tsc_signal_controller_features *features; - struct list_head generators; -}; - -static const struct tsc_signal_controller_features tegra234_tsc_features = { - .rational_locking = { - .enforced = true, - .max_freq_hz_lcm = 120, - }, - .offset = { - .enabled = true, - }, -}; - -static const struct debugfs_reg32 tsc_signal_generator_debugfs_regset[] = { - { - .name = "status", - .offset = TSC_GENX_STATUS, - }, -}; -#define TSC_SIG_GEN_DEBUGFS_REGSET_SIZE ARRAY_SIZE(tsc_signal_generator_debugfs_regset) - -static inline void cdi_tsc_generator_writel(struct tsc_signal_generator *generator, u32 reg, u32 val) -{ - writel(val, generator->base + reg); -} - -static inline u32 cdi_tsc_generator_readl(struct tsc_signal_generator *generator, u32 reg) -{ - return readl(generator->base + reg); -} - -static inline u32 cdi_tsc_controller_readl(struct tsc_signal_controller *controller, u32 reg) -{ - return readl(controller->base + reg); -} - -static u32 cdi_tsc_find_max_freq_hz_lcm(const struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - u32 running_lcm = 0; - - list_for_each_entry(generator, &controller->generators, list) { - running_lcm = lcm_not_zero(generator->config.freq_hz, running_lcm); - } - - return running_lcm; -} - -static int cdi_tsc_find_and_add_generators(struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - struct device_node *np; - struct resource res; - const char *node_status; - int err; - - for_each_child_of_node(controller->dev->of_node, np) { - err = of_property_read_string(np, "status", &node_status); - if (err != 0) { - dev_err(controller->dev, "Failed to read generator status: %d\n", err); - return err; - } - if (strcmp("okay", node_status) != 0) { - dev_dbg(controller->dev, "Generator %s disabled - skipping\n", np->full_name); - continue; - } - - dev_dbg(controller->dev, "Generator found: %s\n", np->full_name); - - generator = devm_kzalloc(controller->dev, sizeof(*generator), GFP_KERNEL); - if (!generator) - return -ENOMEM; - - generator->of = np; - INIT_LIST_HEAD(&generator->list); - - if (of_address_to_resource(np, 0, &res)) - return -EINVAL; - - generator->base = devm_ioremap_resource(controller->dev, &res); - if (IS_ERR(generator->base)) - return PTR_ERR(generator->base); - - err = of_property_read_u32(np, "freq_hz", &generator->config.freq_hz); - if (err != 0) { - dev_err(controller->dev, "Failed to read generator frequency: %d\n", err); - return err; - } - - if (generator->config.freq_hz == 0) { - dev_err(controller->dev, "Frequency must be non-zero\n"); - return -EINVAL; - } - - err = of_property_read_u32(np, "duty_cycle", &generator->config.duty_cycle); - if (err != 0) { - dev_err(controller->dev, "Failed to read generator duty cycle: %d\n", err); - return err; - } - if (generator->config.duty_cycle >= 100) { - dev_err(controller->dev, "Duty cycle must be < 100%%\n"); - return -EINVAL; - } - - if (controller->features->offset.enabled) { - err = of_property_read_u32(np, "offset_ms", &generator->config.offset_ms); - if (err != 0) { - dev_err(controller->dev, "Failed to read generator offset: %d\n", err); - return err; - } - } - - list_add_tail(&generator->list, &controller->generators); - dev_dbg(controller->dev, "Generator %s added to controller\n", np->full_name); - } - - return 0; -} - -static int cdi_tsc_program_generator_edges(struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - u32 max_freq_hz_lcm = 0; - - /* - * If rational locking is enforced (e.g. a 30Hz & 60Hz signal must align every two periods - * w.r.t. the 60Hz signal) edges will be derived from whole-number multiples of the LCM of - * all generator frequencies belonging to this controller. - * - * If rational locking is _not_ enforced then generator edges will be independently - * derived based on their configured frequency. - */ - if (controller->features->rational_locking.enforced) { - max_freq_hz_lcm = cdi_tsc_find_max_freq_hz_lcm(controller); - if (max_freq_hz_lcm > controller->features->rational_locking.max_freq_hz_lcm) { - dev_err(controller->dev, - "Highest common frequency of %u hz exceeds maximum allowed (%u hz)\n", - max_freq_hz_lcm, - controller->features->rational_locking.max_freq_hz_lcm); - return -EINVAL; - } - } - - list_for_each_entry(generator, &controller->generators, list) { - u32 ticks_in_period = 0; - u32 ticks_active = 0; - u32 ticks_inactive = 0; - - if (controller->features->rational_locking.enforced) { - ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, max_freq_hz_lcm); - ticks_in_period *= max_freq_hz_lcm / generator->config.freq_hz; - } else { - ticks_in_period = DIV_ROUND_CLOSEST(TSC_TICKS_PER_HZ, generator->config.freq_hz); - } - - ticks_active = mult_frac(ticks_in_period, generator->config.duty_cycle, 100); - ticks_inactive = ticks_in_period - ticks_active; - - cdi_tsc_generator_writel(generator, TSC_GENX_EDGE0, - TSC_GENX_EDGEX_TOGGLE | - FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_active)); - - cdi_tsc_generator_writel(generator, TSC_GENX_EDGE1, - TSC_GENX_EDGEX_TOGGLE | - TSC_GENX_EDGEX_LOOP | - FIELD_PREP(TSC_GENX_EDGEX_OFFSET, ticks_inactive)); - } - - return 0; -} - -static void cdi_tsc_program_generator_start_values(struct tsc_signal_controller *controller) -{ - const u32 relative_ticks_to_start = mult_frac( - TSC_GENX_START_OFFSET_MS, NS_PER_MS, TSC_NS_PER_TICK); - - const u32 current_ticks_lo = FIELD_GET(TSC_MTSCCNTCV0_CV, - cdi_tsc_controller_readl(controller, TSC_MTSCCNTCV0)); - const u32 current_ticks_hi = FIELD_GET(TSC_MTSCCNTCV1_CV, - cdi_tsc_controller_readl(controller, TSC_MTSCCNTCV1)); - - const u64 current_ticks = ((u64)current_ticks_hi << 32) | current_ticks_lo; - - struct tsc_signal_generator *generator; - - list_for_each_entry(generator, &controller->generators, list) { - u64 absolute_ticks_to_start = current_ticks + relative_ticks_to_start; - - if (controller->features->offset.enabled && (generator->config.offset_ms != 0)) { - absolute_ticks_to_start += mult_frac(generator->config.offset_ms, NS_PER_MS, TSC_NS_PER_TICK); - } - - cdi_tsc_generator_writel(generator, TSC_GENX_START0, - FIELD_PREP(TSC_GENX_START0_LSB_VAL, lower_32_bits(absolute_ticks_to_start))); - - cdi_tsc_generator_writel(generator, TSC_GENX_START1, - FIELD_PREP(TSC_GENX_START1_MSB_VAL, upper_32_bits(absolute_ticks_to_start))); - } -} - -static bool cdi_tsc_generator_is_running(struct tsc_signal_generator *generator) -{ - const u32 status = cdi_tsc_generator_readl(generator, TSC_GENX_STATUS); - - return FIELD_GET(TSC_GENX_STATUS_RUNNING, status) == 1; -} - -static bool cdi_tsc_generator_is_waiting(struct tsc_signal_generator *generator) -{ - const u32 status = cdi_tsc_generator_readl(generator, TSC_GENX_STATUS); - - return FIELD_GET(TSC_GENX_STATUS_WAITING, status) == 1; -} - -static inline bool cdi_tsc_generator_is_idle(struct tsc_signal_generator *generator) -{ - return !cdi_tsc_generator_is_running(generator) && - !cdi_tsc_generator_is_waiting(generator); -} - -static int cdi_tsc_start_generators(struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - int err; - - /* A generator must be idle (e.g. neither running nor waiting) before starting */ - list_for_each_entry(generator, &controller->generators, list) { - if (!cdi_tsc_generator_is_idle(generator)) { - dev_err(controller->dev, "Generator %s is not idle\n", generator->of->full_name); - return -EBUSY; - } - } - - err = cdi_tsc_program_generator_edges(controller); - if (err != 0) - return err; - - cdi_tsc_program_generator_start_values(controller); - - /* Start the generators */ - list_for_each_entry(generator, &controller->generators, list) { - cdi_tsc_generator_writel(generator, TSC_GENX_CTRL, - TSC_GENX_CTRL_INITIAL_VAL | TSC_GENX_CTRL_ENABLE); - } - - return 0; -} - -static int cdi_tsc_stop_generators(struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - - list_for_each_entry(generator, &controller->generators, list) { - cdi_tsc_generator_writel(generator, TSC_GENX_CTRL, TSC_GENX_CTRL_RST); - - /* Ensure the generator has stopped */ - if (!cdi_tsc_generator_is_idle(generator)) { - dev_err(controller->dev, "Generator %s failed to stop\n", - generator->of->full_name); - return -EIO; - } - } - - return 0; -} - -#ifdef CONFIG_DEBUG_FS -static int cdi_tsc_debugfs_init(struct tsc_signal_controller *controller) -{ - struct tsc_signal_generator *generator; - - controller->debugfs.d = - debugfs_create_dir(controller->dev->of_node->full_name, NULL); - if (IS_ERR(controller->debugfs.d)) - return PTR_ERR(controller->debugfs.d); - - list_for_each_entry(generator, &controller->generators, list) { - generator->debugfs.regset_ro.regs = tsc_signal_generator_debugfs_regset; - generator->debugfs.regset_ro.nregs = TSC_SIG_GEN_DEBUGFS_REGSET_SIZE; - generator->debugfs.regset_ro.base = generator->base; - - debugfs_create_regset32( - generator->of->full_name, - 0400, - controller->debugfs.d, - &generator->debugfs.regset_ro); - } - - return 0; -} - -static void cdi_tsc_debugfs_remove(struct tsc_signal_controller *controller) -{ - debugfs_remove_recursive(controller->debugfs.d); - controller->debugfs.d = NULL; -} -#endif - -static int cdi_tsc_probe(struct platform_device *pdev) -{ - struct tsc_signal_controller *controller; - struct resource *res; - int err; - - dev_info(&pdev->dev, "CDI TSC probing...\n"); - - controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL); - if (!controller) - return -ENOMEM; - - controller->dev = &pdev->dev; - controller->features = of_device_get_match_data(&pdev->dev); - if (controller->features == NULL) { - dev_err(controller->dev, "No controller feature table found\n"); - return -ENODEV; - } - - INIT_LIST_HEAD(&controller->generators); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - controller->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(controller->base)) - return PTR_ERR(controller->base); - - platform_set_drvdata(pdev, controller); - - err = cdi_tsc_find_and_add_generators(controller); - if (err != 0) - return err; - -#ifdef CONFIG_DEBUG_FS - if (debugfs_initialized()) { - err = cdi_tsc_debugfs_init(controller); - if (err != 0) - return err; - } -#endif - - return cdi_tsc_start_generators(controller); -} - -static int cdi_tsc_remove(struct platform_device *pdev) -{ - struct tsc_signal_controller *controller = platform_get_drvdata(pdev); - -#ifdef CONFIG_DEBUG_FS - cdi_tsc_debugfs_remove(controller); -#endif - return cdi_tsc_stop_generators(controller); -} - -static int __maybe_unused cdi_tsc_suspend(struct device *dev) -{ - struct tsc_signal_controller *controller = dev_get_drvdata(dev); - - return cdi_tsc_stop_generators(controller); -} - -static int __maybe_unused cdi_tsc_resume(struct device *dev) -{ - struct tsc_signal_controller *controller = dev_get_drvdata(dev); - - return cdi_tsc_start_generators(controller); -} - -static const struct of_device_id cdi_tsc_of_match[] = { - { .compatible = "nvidia,tegra234-cdi-tsc", .data = &tegra234_tsc_features }, - { }, -}; -MODULE_DEVICE_TABLE(of, cdi_tsc_of_match); - -static SIMPLE_DEV_PM_OPS(cdi_tsc_pm, cdi_tsc_suspend, cdi_tsc_resume); - -static struct platform_driver cdi_tsc_driver = { - .driver = { - .name = "cdi_tsc", - .owner = THIS_MODULE, - .of_match_table = cdi_tsc_of_match, - .pm = &cdi_tsc_pm, - }, - .probe = cdi_tsc_probe, - .remove = cdi_tsc_remove, -}; -module_platform_driver(cdi_tsc_driver); - -MODULE_AUTHOR("Ian Kaszubski "); -MODULE_DESCRIPTION("CDI TSC Signal Generation Driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:cdi_tsc"); - diff --git a/include/uapi/media/cam_fsync.h b/include/uapi/media/cam_fsync.h new file mode 100644 index 00000000..39d460f1 --- /dev/null +++ b/include/uapi/media/cam_fsync.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LicenseRef-NvidiaProprietary + * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __CAM_FSYNC_H__ +#define __CAM_FSYNC_H__ + +#define CAM_FSYNC_GRP_ABS_START_VAL \ + _IOW('T', 1, uint64_t) + +#endif