503 lines
11 KiB
C
503 lines
11 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Samsung Exynos SoC series Pablo driver
|
||
|
*
|
||
|
* Copyright (c) 2021 Samsung Electronics Co., Ltd
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 as
|
||
|
* published by the Free Software Foundation.
|
||
|
*/
|
||
|
|
||
|
#include <linux/of_platform.h>
|
||
|
|
||
|
#include "is-hw.h"
|
||
|
#include "is-device-camif-dma.h"
|
||
|
|
||
|
#define CAMIF_WDMA 0x01
|
||
|
#define CAMIF_WDMA_MODULE 0x02
|
||
|
|
||
|
static struct is_camif_wdma **wdma_chs;
|
||
|
static int max_num_of_wdma;
|
||
|
static struct is_camif_dma_data is_camif_wdma;
|
||
|
|
||
|
struct is_camif_wdma *is_camif_wdma_get(unsigned int wdma_ch_hint)
|
||
|
{
|
||
|
int i = 0;
|
||
|
struct is_camif_wdma *wdma;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&is_camif_wdma.slock, flags);
|
||
|
|
||
|
if (wdma_ch_hint >= max_num_of_wdma) {
|
||
|
for (i = 0; i < max_num_of_wdma; i++) {
|
||
|
wdma = wdma_chs[i];
|
||
|
if (!wdma->active_cnt) {
|
||
|
wdma->active_cnt++;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
wdma = wdma_chs[wdma_ch_hint];
|
||
|
if (!wdma->active_cnt)
|
||
|
wdma->active_cnt++;
|
||
|
else
|
||
|
i = max_num_of_wdma; /* error detect */
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&is_camif_wdma.slock, flags);
|
||
|
|
||
|
if (i >= max_num_of_wdma) {
|
||
|
err("all wdmas are already used (hint %d)", wdma_ch_hint);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
info("%s: ch(mod%d, dma%d) hint %d\n", __func__, wdma->mod->index,
|
||
|
wdma->index, wdma_ch_hint);
|
||
|
|
||
|
return wdma;
|
||
|
}
|
||
|
KUNIT_EXPORT_SYMBOL(is_camif_wdma_get);
|
||
|
|
||
|
int is_camif_wdma_put(struct is_camif_wdma *wdma)
|
||
|
{
|
||
|
int active_cnt;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (!wdma)
|
||
|
return -EINVAL;
|
||
|
|
||
|
spin_lock_irqsave(&is_camif_wdma.slock, flags);
|
||
|
|
||
|
active_cnt = wdma->active_cnt;
|
||
|
wdma->active_cnt = 0;
|
||
|
|
||
|
spin_unlock_irqrestore(&is_camif_wdma.slock, flags);
|
||
|
|
||
|
if (active_cnt != 1) {
|
||
|
warn("invalid active_cnt(%d) ch(mod%d, dma%d)",
|
||
|
active_cnt, wdma->mod->index, wdma->index);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
info("%s: ch(mod%d, dma%d)\n", __func__, wdma->mod->index, wdma->index);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
KUNIT_EXPORT_SYMBOL(is_camif_wdma_put);
|
||
|
|
||
|
struct is_camif_wdma_module *is_camif_wdma_module_get(unsigned int wdma_ch)
|
||
|
{
|
||
|
struct is_camif_wdma_module *wdma_mod;
|
||
|
|
||
|
if (wdma_ch >= max_num_of_wdma) {
|
||
|
err(" invalid wdma_ch(%d)", wdma_ch);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
wdma_mod = wdma_chs[wdma_ch]->mod;
|
||
|
if (!wdma_mod)
|
||
|
warn(" wdma_module is null");
|
||
|
|
||
|
return wdma_mod;
|
||
|
}
|
||
|
|
||
|
void is_camif_wdma_init(void)
|
||
|
{
|
||
|
int i;
|
||
|
struct is_camif_wdma *wdma;
|
||
|
|
||
|
for (i = 0; i < max_num_of_wdma; i++) {
|
||
|
wdma = wdma_chs[i];
|
||
|
if (!wdma)
|
||
|
continue;
|
||
|
|
||
|
writel(0xFFFFFFFF, wdma->regs_mux);
|
||
|
wdma->active_cnt = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void __iomem *is_camif_ioremap(struct platform_device *pdev,
|
||
|
const char *name)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct resource *res;
|
||
|
|
||
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
||
|
if (!res) {
|
||
|
dev_err(dev, "failed to get memory resource for %s", name);
|
||
|
return ERR_PTR(-ENODEV);
|
||
|
}
|
||
|
|
||
|
return devm_ioremap(dev, res->start, resource_size(res));
|
||
|
}
|
||
|
|
||
|
static int is_camif_wdma_probe(struct platform_device *pdev,
|
||
|
struct is_camif_dma_data *data)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct device_node *node = dev->of_node;
|
||
|
struct is_camif_wdma *wdma;
|
||
|
int ret, vc;
|
||
|
char *name;
|
||
|
size_t name_len;
|
||
|
struct device_node *module_node;
|
||
|
struct platform_device *module_pdev;
|
||
|
struct is_camif_wdma_module *wdma_module;
|
||
|
|
||
|
if (data->type != CAMIF_WDMA) {
|
||
|
dev_err(dev, "invalid probe function\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
wdma = devm_kzalloc(dev, sizeof(*wdma), GFP_KERNEL);
|
||
|
if (!wdma) {
|
||
|
dev_err(dev, "failed to allocate driver data\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
wdma->dev = &pdev->dev;
|
||
|
wdma->data = data;
|
||
|
wdma->index = of_alias_get_id(node, data->alias_stem);
|
||
|
if ((wdma->index < 0) || (wdma->index >= (max_num_of_wdma))) {
|
||
|
dev_err(dev, "invalid global index for wdma: %d\n", wdma->index);
|
||
|
ret = wdma->index;
|
||
|
goto err_get_global_index;
|
||
|
}
|
||
|
|
||
|
/* DMA input mux register */
|
||
|
wdma->regs_mux = is_camif_ioremap(pdev, "mux");
|
||
|
if (IS_ERR_OR_NULL(wdma->regs_mux)) {
|
||
|
dev_err(dev, "couldn't ioremap for wdma%d mux\n", wdma->index);
|
||
|
ret = -ENOMEM;
|
||
|
goto err_ioremap_mux;
|
||
|
}
|
||
|
|
||
|
/* control register */
|
||
|
wdma->regs_ctl = is_camif_ioremap(pdev, "ctl");
|
||
|
if (IS_ERR_OR_NULL(wdma->regs_ctl)) {
|
||
|
dev_err(dev, "couldn't ioremap for wdma%d control\n", wdma->index);
|
||
|
ret = -ENOMEM;
|
||
|
goto err_ioremap_ctl;
|
||
|
}
|
||
|
|
||
|
/* each VC register */
|
||
|
name = __getname();
|
||
|
if (!name) {
|
||
|
ret = -ENOMEM;
|
||
|
goto err_alloc_name;
|
||
|
}
|
||
|
|
||
|
for (vc = 0; vc < DMA_VIRTUAL_CH_MAX; vc++) {
|
||
|
snprintf(name, PATH_MAX, "vc%d", vc);
|
||
|
wdma->regs_vc[vc] = is_camif_ioremap(pdev, name);
|
||
|
if (IS_ERR_OR_NULL(wdma->regs_vc[vc])) {
|
||
|
dev_err(dev, "couldn't ioremap for wdma%d vc%d\n", wdma->index, vc);
|
||
|
ret = -ENOMEM;
|
||
|
goto err_ioremap_vc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
wdma->irq = platform_get_irq_byname(pdev, "dma");
|
||
|
if (wdma->irq < 0) {
|
||
|
dev_err(dev, "couldn't get IRQ for wdma%d\n", wdma->index);
|
||
|
ret = wdma->irq;
|
||
|
goto err_get_irq;
|
||
|
}
|
||
|
|
||
|
name_len = sizeof(wdma->irq_name);
|
||
|
snprintf(wdma->irq_name, name_len, "%s-%d", "CAMIF.DMA", wdma->index);
|
||
|
|
||
|
wdma_chs[wdma->index] = wdma;
|
||
|
|
||
|
/* link wdma module and each channel */
|
||
|
module_node = of_parse_phandle(node, "module", 0);
|
||
|
if (!module_node) {
|
||
|
dev_err(dev, "failed to find module_node\n");
|
||
|
ret = -ENODEV;
|
||
|
goto err_find_module_node;
|
||
|
}
|
||
|
|
||
|
module_pdev = of_find_device_by_node(module_node);
|
||
|
if (!module_pdev) {
|
||
|
dev_err(dev, "failed to find module_pdev\n");
|
||
|
ret = -ENODEV;
|
||
|
goto err_find_module_pdev;
|
||
|
}
|
||
|
|
||
|
wdma_module = (struct is_camif_wdma_module *)platform_get_drvdata(module_pdev);
|
||
|
if (!wdma_module) {
|
||
|
dev_err(dev, "failed to get wdma_module\n");
|
||
|
ret = -EINVAL;
|
||
|
goto err_wdma_module;
|
||
|
}
|
||
|
wdma_module->chs[wdma->index] = wdma;
|
||
|
wdma->mod = wdma_module;
|
||
|
|
||
|
of_node_put(module_node);
|
||
|
|
||
|
__putname(name);
|
||
|
|
||
|
dev_info(dev, "WDMA%d was added successfully\n", wdma->index);
|
||
|
|
||
|
platform_set_drvdata(pdev, wdma);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_wdma_module:
|
||
|
err_find_module_pdev:
|
||
|
of_node_put(module_node);
|
||
|
|
||
|
err_find_module_node:
|
||
|
err_get_irq:
|
||
|
err_ioremap_vc:
|
||
|
__putname(name);
|
||
|
|
||
|
while (vc-- > 0)
|
||
|
devm_iounmap(dev, wdma->regs_vc[vc]);
|
||
|
|
||
|
err_alloc_name:
|
||
|
devm_iounmap(dev, wdma->regs_ctl);
|
||
|
|
||
|
err_ioremap_ctl:
|
||
|
devm_iounmap(dev, wdma->regs_mux);
|
||
|
|
||
|
err_ioremap_mux:
|
||
|
err_get_global_index:
|
||
|
devm_kfree(dev, wdma);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct is_camif_dma_data is_camif_wdma = {
|
||
|
.type = CAMIF_WDMA,
|
||
|
.slock = __SPIN_LOCK_UNLOCKED(is_camif_wdma.slock),
|
||
|
.alias_stem = "wdma",
|
||
|
|
||
|
.probe = is_camif_wdma_probe,
|
||
|
};
|
||
|
|
||
|
/* This quirks code is derived from sound/soc/samsung/abox/abox.c */
|
||
|
static void is_camif_wdma_module_probe_quirks(struct device_node *node,
|
||
|
struct is_camif_wdma_module *wm)
|
||
|
{
|
||
|
#define QUIRKS "samsung,quirks"
|
||
|
#define DEC_MAP(id) {IS_CAMIF_WDMA_MODULE_QUIRK_STR_##id, \
|
||
|
IS_CAMIF_WDMA_MODULE_QUIRK_BIT_##id}
|
||
|
|
||
|
static const struct {
|
||
|
const char *str;
|
||
|
unsigned long bit;
|
||
|
} map[] = {
|
||
|
DEC_MAP(HAS_TEST_PATTERN_GEN),
|
||
|
};
|
||
|
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(map); i++) {
|
||
|
if (of_property_match_string(node, QUIRKS, map[i].str) >= 0) {
|
||
|
wm->quirks |= map[i].bit;
|
||
|
dev_info(wm->dev, " %s\n", map[i].str);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int is_camif_wdma_module_probe(struct platform_device *pdev,
|
||
|
struct is_camif_dma_data *data)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct device_node *node = dev->of_node;
|
||
|
struct is_camif_wdma_module *wdma_module;
|
||
|
int ret;
|
||
|
int num_of_chs;;
|
||
|
|
||
|
if (data->type != CAMIF_WDMA_MODULE) {
|
||
|
dev_err(dev, "invalid probe function\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
wdma_module = devm_kzalloc(dev, sizeof(*wdma_module), GFP_KERNEL);
|
||
|
if (!wdma_module) {
|
||
|
dev_err(dev, "failed to allocate driver data\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
atomic_set(&wdma_module->active_cnt, 0);
|
||
|
spin_lock_init(&wdma_module->slock);
|
||
|
|
||
|
wdma_module->dev = &pdev->dev;
|
||
|
wdma_module->data = data;
|
||
|
wdma_module->index = of_alias_get_id(node, data->alias_stem);
|
||
|
if (wdma_module->index < 0) {
|
||
|
dev_err(dev, "invalid global index for wdma-module\n");
|
||
|
ret = wdma_module->index;
|
||
|
goto err_get_global_index;
|
||
|
}
|
||
|
|
||
|
wdma_module->regs = is_camif_ioremap(pdev, "ctl");
|
||
|
if (IS_ERR_OR_NULL(wdma_module->regs)) {
|
||
|
dev_err(dev, "couldn't ioremap for wdma-module%d\n", wdma_module->index);
|
||
|
ret = -ENOMEM;
|
||
|
goto err_ioremap;
|
||
|
}
|
||
|
|
||
|
if (!of_find_property(node, "channels", NULL)) {
|
||
|
dev_err(dev, "a wdma-module has no channel\n");
|
||
|
ret = -EINVAL;
|
||
|
goto err_no_chs;
|
||
|
}
|
||
|
|
||
|
num_of_chs = of_count_phandle_with_args(node, "channels", NULL);
|
||
|
wdma_module->num_of_chs = num_of_chs;
|
||
|
|
||
|
wdma_module->chs = devm_kzalloc(dev, sizeof(*(wdma_module->chs)) * num_of_chs,
|
||
|
GFP_KERNEL);
|
||
|
if (!wdma_module->chs) {
|
||
|
dev_err(dev, "failed to allocate wdma_module->chs\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err_alloc_chs;
|
||
|
}
|
||
|
|
||
|
max_num_of_wdma = of_alias_get_highest_id(is_camif_wdma.alias_stem) + 1;
|
||
|
if (!wdma_chs) {
|
||
|
wdma_chs = devm_kzalloc(dev, sizeof(*wdma_chs) * max_num_of_wdma,
|
||
|
GFP_KERNEL);
|
||
|
if (!wdma_chs) {
|
||
|
dev_err(dev, "failed to allocate wdma_chs\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err_alloc_wdma_chs;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dev_info(dev, "WDMA-MODULE%d was added successfully with\n", wdma_module->index);
|
||
|
|
||
|
is_camif_wdma_module_probe_quirks(node, wdma_module);
|
||
|
|
||
|
platform_set_drvdata(pdev, wdma_module);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_alloc_wdma_chs:
|
||
|
devm_kfree(dev, wdma_module->chs);
|
||
|
|
||
|
err_alloc_chs:
|
||
|
err_no_chs:
|
||
|
err_ioremap:
|
||
|
err_get_global_index:
|
||
|
devm_kfree(dev, wdma_module);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct is_camif_dma_data is_camif_wdma_module = {
|
||
|
.type = CAMIF_WDMA_MODULE,
|
||
|
.slock = __SPIN_LOCK_UNLOCKED(is_camif_wdma_module.slock),
|
||
|
.alias_stem = "wdma-module",
|
||
|
|
||
|
.probe = is_camif_wdma_module_probe,
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id is_camif_dma_of_table[] = {
|
||
|
{
|
||
|
.name = "camif-wdma",
|
||
|
.compatible = "samsung,camif-wdma",
|
||
|
.data = &is_camif_wdma,
|
||
|
},
|
||
|
{
|
||
|
.name = "camif-wdma-module",
|
||
|
.compatible = "samsung,camif-wdma-module",
|
||
|
.data = &is_camif_wdma_module,
|
||
|
},
|
||
|
{},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, is_camif_dma_of_table);
|
||
|
|
||
|
static int is_camif_dma_suspend(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int is_camif_dma_resume(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int is_camif_dma_runtime_suspend(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int is_camif_dma_runtime_resume(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops is_camif_dma_pm_ops = {
|
||
|
.suspend = is_camif_dma_suspend,
|
||
|
.resume = is_camif_dma_resume,
|
||
|
.runtime_suspend = is_camif_dma_runtime_suspend,
|
||
|
.runtime_resume = is_camif_dma_runtime_resume,
|
||
|
};
|
||
|
|
||
|
static int is_camif_dma_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
const struct of_device_id *of_id;
|
||
|
struct is_camif_dma_data *data;
|
||
|
|
||
|
of_id = of_match_device(of_match_ptr(is_camif_dma_of_table), dev);
|
||
|
if (!of_id)
|
||
|
return -EINVAL;
|
||
|
|
||
|
data = (struct is_camif_dma_data *)of_id->data;
|
||
|
|
||
|
return data->probe(pdev, data);
|
||
|
}
|
||
|
|
||
|
static int is_camif_dma_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
const struct of_device_id *of_id;
|
||
|
struct is_camif_dma_data *data;
|
||
|
|
||
|
of_id = of_match_device(of_match_ptr(is_camif_dma_of_table), dev);
|
||
|
if (!of_id)
|
||
|
return -EINVAL;
|
||
|
|
||
|
data = (struct is_camif_dma_data *)of_id->data;
|
||
|
|
||
|
return data->remove(pdev, data);
|
||
|
}
|
||
|
|
||
|
struct platform_driver is_camif_dma_driver = {
|
||
|
.probe = is_camif_dma_probe,
|
||
|
.remove = is_camif_dma_remove,
|
||
|
.driver = {
|
||
|
.name = "is-camif-dma",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = is_camif_dma_of_table,
|
||
|
.pm = &is_camif_dma_pm_ops,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#ifndef MODULE
|
||
|
static int __init is_camif_dma_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = platform_driver_probe(&is_camif_dma_driver,
|
||
|
is_camif_dma_probe);
|
||
|
if (ret)
|
||
|
pr_err("failed to probe %s driver: %d\n",
|
||
|
"is-camif-dma", ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
device_initcall_sync(is_camif_dma_init);
|
||
|
#endif
|
||
|
|
||
|
MODULE_DESCRIPTION("Samsung EXYNOS SoC Pablo CAMIF DMA driver");
|
||
|
MODULE_ALIAS("platform:samsung-is-camif-dma");
|
||
|
MODULE_LICENSE("GPL v2");
|