Files
linux-nv-oot/drivers/platform/tegra/dce/dce-os-utils.c
anupamg c0e60a639a DCE-KMD: Update DCE OS abstraction - Part 6
Module covered: dce-workqueue

This is not a functional change. It does the following:
1) Move include/dce-workqueue.h to
   os/linux/include/dce-os-work.h

2) s/dce_work/dce_os_work/g

3) s/dce_init_work/dce_os_work_init/g

4) s/dce_schedule_work/dce_os_work_schedule/g

5) Remove intermediate os header os/include/os-dce-workqueue.h
   and replace all includes directly with <dce-os-work.h>

JIRA TDS-16126

Change-Id: I4d88cf68a187a061fd0c8c084ea074fb9e74d315
Signed-off-by: anupamg <anupamg@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3228552
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Mahesh Kumar <mahkumar@nvidia.com>
Reviewed-by: Arun Swain <arswain@nvidia.com>
2025-07-24 10:19:12 +00:00

791 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <dce.h>
#include <dce-thread.h>
#include <dce-os-device.h>
#include <dce-os-utils.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/firmware.h>
#include <linux/bitops.h>
#include <linux/bitmap.h>
/**
* Do not add any more util functions to this file.
* We should add OS util functions to respective OS module files.
*/
/**
* dce_os_writel - Dce io function to perform MMIO writes
*
* @d : Pointer to tegra_dce struct.
* @r : register offset from dce_base.
* @v : value to be written
*
* Return : Void
*/
void dce_os_writel(struct tegra_dce *d, u32 r, u32 v)
{
struct dce_os_device *d_dev = dce_os_device_from_dce(d);
if (unlikely(!d_dev->regs))
dce_os_err(d, "DCE Register Space not IOMAPed to CPU");
else
writel(v, d_dev->regs + r);
}
/**
* dce_os_readl - Dce io function to perform MMIO reads
*
* @d : Pointer to tegra_dce struct.
* @r : register offset from dce_base.
*
* Return : the read value
*/
u32 dce_os_readl(struct tegra_dce *d, u32 r)
{
u32 v = 0xffffffff;
struct dce_os_device *d_dev = dce_os_device_from_dce(d);
if (unlikely(!d_dev->regs))
dce_os_err(d, "DCE Register Space not IOMAPed to CPU");
else
v = readl(d_dev->regs + r);
/*TODO : Add error check here */
return v;
}
/**
* dce_os_writel_check - Performs MMIO writes and checks if the writes
* are actaully correct.
*
* @d : Pointer to tegra_dce struct.
* @r : register offset from dce_base.
* @v : value to be written
*
* Return : Void
*/
void dce_os_writel_check(struct tegra_dce *d, u32 r, u32 v)
{
/* TODO : Write and read back to check */
}
/**
* dce_os_io_exists - Dce io function to check if the registers are mapped
* to CPU correctly
*
* @d : Pointer to tegra_dce struct.
*
* Return : True if mapped.
*/
bool dce_os_io_exists(struct tegra_dce *d)
{
struct dce_os_device *d_dev = dce_os_device_from_dce(d);
return d_dev->regs != NULL;
}
/**
* dce_os_io_valid_reg - Dce io function to check if the requested offset is
* within the range of CPU mapped MMIO range.
*
* @d : Pointer to tegra_dce struct.
* @r : register offset from dce_base.
*
* Return : True if offset within range.
*/
bool dce_os_io_valid_reg(struct tegra_dce *d, u32 r)
{
/* TODO : Implement range check here. Returning true for now*/
return true;
}
/**
* dce_os_kzalloc - Function to allocate contiguous kernel memory
*
* @d : Pointer to tegra_dce struct.
* @size_t : Size of the memory to be allocated
* @dma_flag: True if allocated memory should be DMAable
*
* Return : CPU Mapped Address if successful else NULL.
*/
void *dce_os_kzalloc(struct tegra_dce *d, size_t size, bool dma_flag)
{
void *alloc;
gfp_t flags = GFP_KERNEL;
if (dma_flag)
flags |= __GFP_DMA;
alloc = kzalloc(size, flags);
return alloc;
}
/**
* dce_os_kfree - Frees an alloc from dce_os_kzalloc
*
* @d : Pointer to tegra_dce struct.
* @addr : Address of the object to free.
*
* Return : void
*/
void dce_os_kfree(struct tegra_dce *d, void *addr)
{
kfree(addr);
}
/**
* dce_os_request_firmware - Reads the fw into memory.
*
* @d : Pointer to tegra_dce struct.
* @fw_name : Name of the fw.
*
* Return : Pointer to dce_firmware if successful else NULL.
*/
struct dce_firmware *dce_os_request_firmware(struct tegra_dce *d,
const char *fw_name)
{
struct device *dev = dev_from_dce(d);
struct dce_firmware *fw;
const struct firmware *l_fw;
fw = dce_os_kzalloc(d, sizeof(*fw), false);
if (!fw)
return NULL;
if (request_firmware(&l_fw, fw_name, dev) < 0) {
dce_os_err(d, "FW Request Failed");
goto err;
}
if (!l_fw)
goto err;
/* Make sure the address is aligned to 4K */
fw->size = l_fw->size;
fw->size = ALIGN(fw->size + SZ_4K, SZ_4K);
/**
* BUG : Currently overwriting all alignment logic above to blinldy
* allocate 2MB FW virtual space. Ideally it should be as per the
* actual size of the fw.
*/
fw->size = SZ_32M;
fw->data = dma_alloc_coherent(dev, fw->size,
(dma_addr_t *)&fw->dma_handle,
GFP_KERNEL);
if (!fw->data)
goto err_release;
memcpy((u8 *)fw->data, (u8 *)l_fw->data, l_fw->size);
release_firmware(l_fw);
return fw;
err_release:
release_firmware(l_fw);
err:
dce_os_kfree(d, fw);
return NULL;
}
/**
* dce_release_firmware - Reads the fw into memory.
*
* @d : Pointer to tegra_dce struct.
* @fw : Pointer to dce_firmware.
*
* Return : void
*/
void dce_os_release_fw(struct tegra_dce *d, struct dce_firmware *fw)
{
struct device *dev = dev_from_dce(d);
if (!fw)
return;
dma_free_coherent(dev, fw->size,
(void *)fw->data,
(dma_addr_t)fw->dma_handle);
dce_os_kfree(d, fw);
}
/**
* dce_get_phys_stream_id - Gets the physical stream ID to be programmed from
* platform data.
*
* @d : Pointer to tegra_dce struct.
*
* Return : Stream ID Value
*/
u8 dce_get_phys_stream_id(struct tegra_dce *d)
{
return pdata_from_dce(d)->phys_stream_id;
}
/**
* dce_get_dce_stream_id - Gets the dce stream ID to be programmed from
* platform data.
*
* @d : Pointer to tegra_dce struct.
*
* Return : Stream ID Value
*/
u8 dce_get_dce_stream_id(struct tegra_dce *d)
{
return pdata_from_dce(d)->stream_id;
}
/**
* dce_get_fw_vm_index - Gets the VMIndex for the fw region to be
* programmed from platform data.
*
* @d : Pointer to tegra_dce struct.
*
* Return : VMIndex
*/
u8 dce_get_fw_vm_index(struct tegra_dce *d)
{
return pdata_from_dce(d)->fw_vmindex;
}
/**
* dce_get_fw_carveout_id- Gets the carveout ID for the fw region to be
* programmed from platform data.
*
* @d : Pointer to tegra_dce struct.
*
* Return : Carveout Id
*/
u8 dce_get_fw_carveout_id(struct tegra_dce *d)
{
return pdata_from_dce(d)->fw_carveout_id;
}
/**
* dce_is_physical_id_valid - Checks if the DCE can use physical stream ID.
*
* @d : Pointer to tegra_dce struct.
*
* Return : True if SMMU is disabled.
*/
bool dce_is_physical_id_valid(struct tegra_dce *d)
{
return pdata_from_dce(d)->use_physical_id;
}
/**
* dce_get_fw_dce_addr - Gets the 32bit address to be used for
* loading the fw before being converted
* by AST into a 40-bit address.
*
* @d : Pointer to tegra_dce struct.
*
* Return : 32bit address
*/
u32 dce_get_fw_dce_addr(struct tegra_dce *d)
{
return pdata_from_dce(d)->fw_dce_addr;
}
/**
* dce_get_fw_phy_addr - Gets the 40bit address to be used by AST
* for loading the fw after converting
* the 32bit incoming address.
*
* @d : Pointer to tegra_dce struct.
*
* This API is to be used only if the memory is being allocated
* via kzalloc or friends. Do not use this if memory is
* allocated via dma apis.
*
* Return : 64bit address
*/
u64 dce_get_fw_phy_addr(struct tegra_dce *d, struct dce_firmware *fw)
{
/* Caller should make sure that *fw is valid since this func is
* not expected to return any error.
*/
return (u64)virt_to_phys((void *)fw->data);
}
/**
* dce_get_fw_name - Gets the dce fw name from platform data.
*
* @d : Pointer to tegra_dce struct.
*
* Return : fw_name
*/
const char *dce_get_fw_name(struct tegra_dce *d)
{
return pdata_from_dce(d)->fw_name;
}
static void dce_print(const char *func_name, int line,
enum dce_os_log_type type, const char *log)
{
#define DCE_LOG_FMT "dce: %15s:%-4d %s\n"
switch (type) {
case DCE_OS_DEBUG:
pr_debug(DCE_LOG_FMT, func_name, line, log);
break;
case DCE_OS_INFO:
pr_info(DCE_LOG_FMT, func_name, line, log);
break;
case DCE_OS_WARNING:
pr_warn(DCE_LOG_FMT, func_name, line, log);
break;
case DCE_OS_ERROR:
pr_err(DCE_LOG_FMT, func_name, line, log);
break;
}
#undef DCE_LOG_FMT
}
__printf(5, 6)
void dce_os_log_msg(struct tegra_dce *d, const char *func_name, int line,
enum dce_os_log_type type, const char *fmt, ...)
{
#define BUF_LEN 100
char log[BUF_LEN];
va_list args;
va_start(args, fmt);
(void) vsnprintf(log, BUF_LEN, fmt, args);
va_end(args);
dce_print(func_name, line, type, log);
}
/**
* dce_os_cond_init - Initialize a condition variable
*
* @cond - The condition variable to initialize
*
* Initialize a condition variable before using it.
*/
int dce_os_cond_init(struct dce_os_cond *cond)
{
init_waitqueue_head(&cond->wq);
cond->initialized = true;
return 0;
}
/**
* dce_os_cond_destroy - Destroy a condition variable
*
* @cond - The condition variable to destroy
*/
void dce_os_cond_destroy(struct dce_os_cond *cond)
{
cond->initialized = false;
}
/**
* dce_os_cond_signal - Signal a condition variable
*
* @cond - The condition variable to signal
*
* Wake up a waiter for a condition variable to check if its condition has been
* satisfied.
*
* The waiter is using an uninterruptible wait.
*/
void dce_os_cond_signal(struct dce_os_cond *cond)
{
WARN_ON(!cond->initialized);
wake_up(&cond->wq);
}
/**
* dce_os_cond_signal_interruptible - Signal a condition variable
*
* @cond - The condition variable to signal
*
* Wake up a waiter for a condition variable to check if its condition has been
* satisfied.
*
* The waiter is using an interruptible wait.
*/
void dce_os_cond_signal_interruptible(struct dce_os_cond *cond)
{
WARN_ON(!cond->initialized);
wake_up_interruptible(&cond->wq);
}
/**
* dce_os_cond_broadcast - Signal all waiters of a condition variable
*
* @cond - The condition variable to signal
*
* Wake up all waiters for a condition variable to check if their conditions
* have been satisfied.
*
* The waiters are using an uninterruptible wait.
*/
int dce_os_cond_broadcast(struct dce_os_cond *cond)
{
if (!cond->initialized)
return -EINVAL;
wake_up_all(&cond->wq);
return 0;
}
/**
* dce_os_cond_broadcast_interruptible - Signal all waiters of a condition
* variable
*
* @cond - The condition variable to signal
*
* Wake up all waiters for a condition variable to check if their conditions
* have been satisfied.
*
* The waiters are using an interruptible wait.
*/
int dce_os_cond_broadcast_interruptible(struct dce_os_cond *cond)
{
if (!cond->initialized)
return -EINVAL;
wake_up_interruptible_all(&cond->wq);
return 0;
}
/**
* dce_thread_proxy - Function to be passed to kthread.
*
* @thread_data : Pointer to actual dce_thread struct
*
* Return : Ruturns the return value of the function to be run.
*/
static int dce_thread_proxy(void *thread_data)
{
struct dce_thread *thread = thread_data;
int ret = thread->fn(thread->data);
thread->running = false;
return ret;
}
/**
* dce_thread_create - Create and run a new thread.
*
* @thread - thread structure to use
* @data - data to pass to threadfn
* @threadfn - Thread function
* @name - name of the thread
*
* Create a thread and run threadfn in it. The thread stays alive as long as
* threadfn is running. As soon as threadfn returns the thread is destroyed.
*
* threadfn needs to continuously poll dce_thread_should_stop() to determine
* if it should exit.
*/
int dce_thread_create(struct dce_thread *thread,
void *data,
int (*threadfn)(void *data), const char *name)
{
struct task_struct *task = kthread_create(dce_thread_proxy,
thread, name);
if (IS_ERR(task))
return PTR_ERR(task);
thread->task = task;
thread->fn = threadfn;
thread->data = data;
thread->running = true;
wake_up_process(task);
return 0;
};
/**
* dce_thread_stop - Destroy or request to destroy a thread
*
* @thread - thread to stop
*
* Request a thread to stop by setting dce_thread_should_stop() to
* true and wait for thread to exit.
*/
void dce_thread_stop(struct dce_thread *thread)
{
/*
* Threads waiting on wq's should have dce_thread_should_stop()
* as one of its wakeup condition. This allows the thread to be woken
* up when kthread_stop() is invoked and does not require an additional
* callback to wakeup the sleeping thread.
*/
if (thread->task) {
kthread_stop(thread->task);
thread->task = NULL;
}
};
/**
* dce_thread_should_stop - Query if thread should stop
*
* @thread
*
* Return true if thread should exit. Can be run only in the thread's own
* context and with the thread as parameter.
*/
bool dce_thread_should_stop(struct dce_thread *thread)
{
return kthread_should_stop();
};
/**
* dce_thread_is_running - Query if thread is running
*
* @thread
*
* Return true if thread is started.
*/
bool dce_thread_is_running(struct dce_thread *thread)
{
return READ_ONCE(thread->running);
};
/**
* dce_thread_join - join a thread to reclaim resources
* after it has exited
*
* @thread - thread to join
*
*/
void dce_thread_join(struct dce_thread *thread)
{
while (READ_ONCE(thread->running))
usleep_range(10000, 20000);
};
/**
* dce_os_get_nxt_pow_of_2 : get next power of 2 number for a given number
*
* @addr : Address of given number
* @nbits : bits in given number
*
* Return : unsigned long next power of 2 value
*/
unsigned long dce_os_get_nxt_pow_of_2(unsigned long *addr, u8 nbits)
{
u8 l_bit = 0;
u8 bit_index = 0;
unsigned long val;
val = *addr;
if (val == 0)
return 0;
bit_index = find_first_bit(addr, nbits);
while (bit_index && (bit_index < nbits)) {
l_bit = bit_index;
bit_index = find_next_bit(addr, nbits, bit_index + 1);
}
if (BIT(l_bit) < val) {
l_bit += 1UL;
val = BIT(l_bit);
}
return val;
}
/*
* dce_os_usleep_range : sleep between min-max range
*
* @min : minimum sleep time in usec
* @max : maximum sleep time in usec
*
* Return : void
*/
void dce_os_usleep_range(unsigned long min, unsigned long max)
{
usleep_range(min, max);
}
/*
* dce_os_work_schedule : schedule work in global highpri workqueue
*
* @work : dce work to be scheduled
*
* Return : void
*/
void dce_os_work_schedule(struct dce_os_work_struct *work)
{
queue_work(system_highpri_wq, &work->work);
}
/*
* dce_os_work_handle_fn : handler function for scheduled dce-work
*
* @work : Pointer to the scheduled work
*
* Return : void
*/
static void dce_os_work_handle_fn(struct work_struct *work)
{
struct dce_os_work_struct *dce_os_work = container_of(work,
struct dce_os_work_struct,
work);
if (dce_os_work->dce_os_work_fn != NULL)
dce_os_work->dce_os_work_fn(dce_os_work->d);
}
/*
* dce_os_work_init : Init dce work structure
*
* @d : Pointer to tegra_dce struct.
* @work : Pointer to dce work structure
* @work_fn : worker function to be called
*
* Return : 0 if successful
*/
int dce_os_work_init(struct tegra_dce *d,
struct dce_os_work_struct *work,
void (*work_fn)(struct tegra_dce *d))
{
work->d = d;
work->dce_os_work_fn = work_fn;
INIT_WORK(&work->work, dce_os_work_handle_fn);
return 0;
}
/**
* ipc_allocate_region [Private] - Allocates IPC region
*
* @d : Pointer to tegra_dce structure.
*
* Return : 0 if successful
*/
static int ipc_allocate_region(struct tegra_dce *d)
{
unsigned long tot_q_sz;
unsigned long tot_ivc_q_sz;
struct device *dev;
struct dce_ipc_region *region;
dev = dev_from_dce(d);
region = &d->d_ipc.region;
tot_q_sz = ((DCE_ADMIN_CMD_MAX_NFRAMES *
tegra_ivc_align(DCE_ADMIN_CMD_MAX_FSIZE) * 2) +
(DCE_DISPRM_CMD_MAX_NFRAMES *
tegra_ivc_align(DCE_DISPRM_CMD_MAX_FSIZE) * 2) +
(DCE_ADMIN_CMD_MAX_NFRAMES *
tegra_ivc_align(DCE_ADMIN_CMD_CHAN_FSIZE) * 2) +
(DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES *
tegra_ivc_align(DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE) * 2)
);
tot_ivc_q_sz = tegra_ivc_total_queue_size(tot_q_sz);
region->size = dce_os_get_nxt_pow_of_2(&tot_ivc_q_sz, 32);
region->base = dma_alloc_coherent(dev, region->size,
&region->iova, GFP_KERNEL | __GFP_ZERO);
if (!region->base)
return -ENOMEM;
region->s_offset = 0;
return 0;
}
/**
* ipc_free_region [Private] - Frees up the IPC region
*
* @d : Pointer to the tegra_dce struct.
*
* Return : Void
*/
static void ipc_free_region(struct tegra_dce *d)
{
struct device *dev;
struct dce_ipc_region *region;
dev = dev_from_dce(d);
region = &d->d_ipc.region;
dma_free_coherent(dev, region->size,
(void *)region->base, region->iova);
region->s_offset = 0;
}
/**
* dce_ipc_init_region_info - Initialize IPC region information.
*
* @d : Pointer to tegra_dce structure.
*
* Return : 0 if successful
*/
int dce_ipc_init_region_info(struct tegra_dce *d)
{
return ipc_allocate_region(d);
}
/**
* dce_os_ipc_deinit_region_info - De-initialize the IPC region
*
* @d : Pointer to the tegra_dce struct.
*
* Return : Void
*/
void dce_ipc_deinit_region_info(struct tegra_dce *d)
{
return ipc_free_region(d);
}
/**
* dce_os_bitmap_set - Set bits in a bitmap
*
* @map : Pointer to map
* @start : Start bit
* @len : Length indicating number of bits to set.
*
* Return : Void
*/
void dce_os_bitmap_set(unsigned long *map,
unsigned int start, unsigned int len)
{
bitmap_set(map, start, (int)len);
}
/**
* dce_os_bitmap_set - Set bits in a bitmap
*
* @map : Pointer to map
* @start : Start bit
* @len : Length indicating number of bits to clear.
*
* Return : Void
*/
void dce_os_bitmap_clear(unsigned long *map,
unsigned int start, unsigned int len)
{
bitmap_clear(map, start, (int)len);
}