mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-01 11:24:25 +08:00
media: staging: media: starfive: camss: Add core driver
Add core driver for StarFive Camera Subsystem. The code parses the device platform resources and registers related devices. Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
This commit is contained in:
parent
f72f80550d
commit
bba185d141
@ -20645,6 +20645,7 @@ L: linux-media@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/admin-guide/media/starfive_camss.rst
|
||||
F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml
|
||||
F: drivers/staging/media/starfive/camss
|
||||
|
||||
STARFIVE CRYPTO DRIVER
|
||||
M: Jia Jie Ho <jiajie.ho@starfivetech.com>
|
||||
|
@ -36,6 +36,8 @@ source "drivers/staging/media/omap4iss/Kconfig"
|
||||
|
||||
source "drivers/staging/media/rkvdec/Kconfig"
|
||||
|
||||
source "drivers/staging/media/starfive/Kconfig"
|
||||
|
||||
source "drivers/staging/media/sunxi/Kconfig"
|
||||
|
||||
source "drivers/staging/media/tegra-video/Kconfig"
|
||||
|
@ -6,6 +6,7 @@ obj-$(CONFIG_VIDEO_MAX96712) += max96712/
|
||||
obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/
|
||||
obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rkvdec/
|
||||
obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive/
|
||||
obj-$(CONFIG_VIDEO_SUNXI) += sunxi/
|
||||
obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
|
||||
obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/
|
||||
|
5
drivers/staging/media/starfive/Kconfig
Normal file
5
drivers/staging/media/starfive/Kconfig
Normal file
@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
comment "StarFive media platform drivers"
|
||||
|
||||
source "drivers/staging/media/starfive/camss/Kconfig"
|
2
drivers/staging/media/starfive/Makefile
Normal file
2
drivers/staging/media/starfive/Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-y += camss/
|
17
drivers/staging/media/starfive/camss/Kconfig
Normal file
17
drivers/staging/media/starfive/camss/Kconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config VIDEO_STARFIVE_CAMSS
|
||||
tristate "Starfive Camera Subsystem driver"
|
||||
depends on V4L_PLATFORM_DRIVERS
|
||||
depends on VIDEO_DEV && OF
|
||||
depends on HAS_DMA
|
||||
depends on PM
|
||||
select MEDIA_CONTROLLER
|
||||
select VIDEO_V4L2_SUBDEV_API
|
||||
select VIDEOBUF2_DMA_CONTIG
|
||||
select V4L2_FWNODE
|
||||
help
|
||||
Enable this to support for the Starfive Camera subsystem
|
||||
found on Starfive JH7110 SoC.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called starfive-camss.
|
9
drivers/staging/media/starfive/camss/Makefile
Normal file
9
drivers/staging/media/starfive/camss/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for StarFive Camera Subsystem driver
|
||||
#
|
||||
|
||||
starfive-camss-objs += \
|
||||
stf-camss.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive-camss.o
|
320
drivers/staging/media/starfive/camss/stf-camss.c
Normal file
320
drivers/staging/media/starfive/camss/stf-camss.c
Normal file
@ -0,0 +1,320 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* stf_camss.c
|
||||
*
|
||||
* Starfive Camera Subsystem driver
|
||||
*
|
||||
* Copyright (C) 2021-2023 StarFive Technology Co., Ltd.
|
||||
*
|
||||
* Author: Jack Zhu <jack.zhu@starfivetech.com>
|
||||
* Author: Changhuang Liang <changhuang.liang@starfivetech.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <media/v4l2-fwnode.h>
|
||||
#include <media/v4l2-mc.h>
|
||||
|
||||
#include "stf-camss.h"
|
||||
|
||||
static const char * const stfcamss_clocks[] = {
|
||||
"wrapper_clk_c",
|
||||
"ispcore_2x",
|
||||
"isp_axi",
|
||||
};
|
||||
|
||||
static const char * const stfcamss_resets[] = {
|
||||
"wrapper_p",
|
||||
"wrapper_c",
|
||||
"axiwr",
|
||||
"isp_top_n",
|
||||
"isp_top_axi",
|
||||
};
|
||||
|
||||
static int stfcamss_get_mem_res(struct stfcamss *stfcamss)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(stfcamss->dev);
|
||||
|
||||
stfcamss->syscon_base =
|
||||
devm_platform_ioremap_resource_byname(pdev, "syscon");
|
||||
if (IS_ERR(stfcamss->syscon_base))
|
||||
return PTR_ERR(stfcamss->syscon_base);
|
||||
|
||||
stfcamss->isp_base = devm_platform_ioremap_resource_byname(pdev, "isp");
|
||||
if (IS_ERR(stfcamss->isp_base))
|
||||
return PTR_ERR(stfcamss->isp_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* stfcamss_of_parse_endpoint_node - Parse port endpoint node
|
||||
* @dev: Device
|
||||
* @node: Device node to be parsed
|
||||
* @csd: Parsed data from port endpoint node
|
||||
*
|
||||
* Return 0 on success or a negative error code on failure
|
||||
*/
|
||||
static int stfcamss_of_parse_endpoint_node(struct stfcamss *stfcamss,
|
||||
struct device_node *node,
|
||||
struct stfcamss_async_subdev *csd)
|
||||
{
|
||||
struct v4l2_fwnode_endpoint vep = { { 0 } };
|
||||
int ret;
|
||||
|
||||
ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep);
|
||||
if (ret) {
|
||||
dev_err(stfcamss->dev, "endpoint not defined at %pOF\n", node);
|
||||
return ret;
|
||||
}
|
||||
|
||||
csd->port = vep.base.port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* stfcamss_of_parse_ports - Parse ports node
|
||||
* @stfcamss: STFCAMSS device
|
||||
*
|
||||
* Return number of "port" nodes found in "ports" node
|
||||
*/
|
||||
static int stfcamss_of_parse_ports(struct stfcamss *stfcamss)
|
||||
{
|
||||
struct device_node *node = NULL;
|
||||
int ret, num_subdevs = 0;
|
||||
|
||||
for_each_endpoint_of_node(stfcamss->dev->of_node, node) {
|
||||
struct stfcamss_async_subdev *csd;
|
||||
|
||||
if (!of_device_is_available(node))
|
||||
continue;
|
||||
|
||||
csd = v4l2_async_nf_add_fwnode_remote(&stfcamss->notifier,
|
||||
of_fwnode_handle(node),
|
||||
struct stfcamss_async_subdev);
|
||||
if (IS_ERR(csd)) {
|
||||
ret = PTR_ERR(csd);
|
||||
dev_err(stfcamss->dev, "failed to add async notifier\n");
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
ret = stfcamss_of_parse_endpoint_node(stfcamss, node, csd);
|
||||
if (ret)
|
||||
goto err_cleanup;
|
||||
|
||||
num_subdevs++;
|
||||
}
|
||||
|
||||
return num_subdevs;
|
||||
|
||||
err_cleanup:
|
||||
of_node_put(node);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stfcamss_subdev_notifier_complete(struct v4l2_async_notifier *ntf)
|
||||
{
|
||||
struct stfcamss *stfcamss =
|
||||
container_of(ntf, struct stfcamss, notifier);
|
||||
|
||||
return v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev);
|
||||
}
|
||||
|
||||
static const struct v4l2_async_notifier_operations
|
||||
stfcamss_subdev_notifier_ops = {
|
||||
.complete = stfcamss_subdev_notifier_complete,
|
||||
};
|
||||
|
||||
static void stfcamss_mc_init(struct platform_device *pdev,
|
||||
struct stfcamss *stfcamss)
|
||||
{
|
||||
stfcamss->media_dev.dev = stfcamss->dev;
|
||||
strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem",
|
||||
sizeof(stfcamss->media_dev.model));
|
||||
media_device_init(&stfcamss->media_dev);
|
||||
|
||||
stfcamss->v4l2_dev.mdev = &stfcamss->media_dev;
|
||||
}
|
||||
|
||||
/*
|
||||
* stfcamss_probe - Probe STFCAMSS platform device
|
||||
* @pdev: Pointer to STFCAMSS platform device
|
||||
*
|
||||
* Return 0 on success or a negative error code on failure
|
||||
*/
|
||||
static int stfcamss_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct stfcamss *stfcamss;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret, num_subdevs;
|
||||
unsigned int i;
|
||||
|
||||
stfcamss = devm_kzalloc(dev, sizeof(*stfcamss), GFP_KERNEL);
|
||||
if (!stfcamss)
|
||||
return -ENOMEM;
|
||||
|
||||
stfcamss->dev = dev;
|
||||
|
||||
stfcamss->nclks = ARRAY_SIZE(stfcamss->sys_clk);
|
||||
for (i = 0; i < stfcamss->nclks; ++i)
|
||||
stfcamss->sys_clk[i].id = stfcamss_clocks[i];
|
||||
ret = devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get clk controls\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
stfcamss->nrsts = ARRAY_SIZE(stfcamss->sys_rst);
|
||||
for (i = 0; i < stfcamss->nrsts; ++i)
|
||||
stfcamss->sys_rst[i].id = stfcamss_resets[i];
|
||||
ret = devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts,
|
||||
stfcamss->sys_rst);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get reset controls\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stfcamss_get_mem_res(stfcamss);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not map registers\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, stfcamss);
|
||||
|
||||
v4l2_async_nf_init(&stfcamss->notifier, &stfcamss->v4l2_dev);
|
||||
|
||||
num_subdevs = stfcamss_of_parse_ports(stfcamss);
|
||||
if (num_subdevs < 0) {
|
||||
ret = -ENODEV;
|
||||
dev_err(dev, "Failed to get sub devices: %d\n", ret);
|
||||
goto err_cleanup_notifier;
|
||||
}
|
||||
|
||||
stfcamss_mc_init(pdev, stfcamss);
|
||||
|
||||
ret = v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register V4L2 device: %d\n", ret);
|
||||
goto err_cleanup_media_device;
|
||||
}
|
||||
|
||||
ret = media_device_register(&stfcamss->media_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register media device: %d\n", ret);
|
||||
goto err_unregister_device;
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
stfcamss->notifier.ops = &stfcamss_subdev_notifier_ops;
|
||||
ret = v4l2_async_nf_register(&stfcamss->notifier);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register async subdev nodes: %d\n",
|
||||
ret);
|
||||
pm_runtime_disable(dev);
|
||||
goto err_unregister_media_dev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_media_dev:
|
||||
media_device_unregister(&stfcamss->media_dev);
|
||||
err_unregister_device:
|
||||
v4l2_device_unregister(&stfcamss->v4l2_dev);
|
||||
err_cleanup_media_device:
|
||||
media_device_cleanup(&stfcamss->media_dev);
|
||||
err_cleanup_notifier:
|
||||
v4l2_async_nf_cleanup(&stfcamss->notifier);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* stfcamss_remove - Remove STFCAMSS platform device
|
||||
* @pdev: Pointer to STFCAMSS platform device
|
||||
*
|
||||
* Always returns 0.
|
||||
*/
|
||||
static int stfcamss_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct stfcamss *stfcamss = platform_get_drvdata(pdev);
|
||||
|
||||
v4l2_device_unregister(&stfcamss->v4l2_dev);
|
||||
media_device_cleanup(&stfcamss->media_dev);
|
||||
v4l2_async_nf_cleanup(&stfcamss->notifier);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stfcamss_of_match[] = {
|
||||
{ .compatible = "starfive,jh7110-camss" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, stfcamss_of_match);
|
||||
|
||||
static int __maybe_unused stfcamss_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct stfcamss *stfcamss = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = reset_control_bulk_assert(stfcamss->nrsts, stfcamss->sys_rst);
|
||||
if (ret) {
|
||||
dev_err(dev, "reset assert failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
clk_bulk_disable_unprepare(stfcamss->nclks, stfcamss->sys_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused stfcamss_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct stfcamss *stfcamss = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_bulk_prepare_enable(stfcamss->nclks, stfcamss->sys_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "clock prepare enable failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_bulk_deassert(stfcamss->nrsts, stfcamss->sys_rst);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "cannot deassert resets\n");
|
||||
clk_bulk_disable_unprepare(stfcamss->nclks, stfcamss->sys_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops stfcamss_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend,
|
||||
stfcamss_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver stfcamss_driver = {
|
||||
.probe = stfcamss_probe,
|
||||
.remove = stfcamss_remove,
|
||||
.driver = {
|
||||
.name = "starfive-camss",
|
||||
.pm = &stfcamss_pm_ops,
|
||||
.of_match_table = stfcamss_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(stfcamss_driver);
|
||||
|
||||
MODULE_AUTHOR("Jack Zhu <jack.zhu@starfivetech.com>");
|
||||
MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>");
|
||||
MODULE_DESCRIPTION("StarFive Camera Subsystem driver");
|
||||
MODULE_LICENSE("GPL");
|
129
drivers/staging/media/starfive/camss/stf-camss.h
Normal file
129
drivers/staging/media/starfive/camss/stf-camss.h
Normal file
@ -0,0 +1,129 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* stf_camss.h
|
||||
*
|
||||
* Starfive Camera Subsystem driver
|
||||
*
|
||||
* Copyright (C) 2021-2023 StarFive Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
#ifndef STF_CAMSS_H
|
||||
#define STF_CAMSS_H
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/reset.h>
|
||||
#include <media/media-device.h>
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-async.h>
|
||||
#include <media/v4l2-device.h>
|
||||
|
||||
enum stf_port_num {
|
||||
STF_PORT_DVP = 0,
|
||||
STF_PORT_CSI2RX
|
||||
};
|
||||
|
||||
enum stf_clk {
|
||||
STF_CLK_WRAPPER_CLK_C = 0,
|
||||
STF_CLK_ISPCORE_2X,
|
||||
STF_CLK_ISP_AXI,
|
||||
STF_CLK_NUM
|
||||
};
|
||||
|
||||
enum stf_rst {
|
||||
STF_RST_WRAPPER_P = 0,
|
||||
STF_RST_WRAPPER_C,
|
||||
STF_RST_AXIWR,
|
||||
STF_RST_ISP_TOP_N,
|
||||
STF_RST_ISP_TOP_AXI,
|
||||
STF_RST_NUM
|
||||
};
|
||||
|
||||
struct stf_isr_data {
|
||||
const char *name;
|
||||
irqreturn_t (*isr)(int irq, void *priv);
|
||||
};
|
||||
|
||||
struct stfcamss {
|
||||
struct v4l2_device v4l2_dev;
|
||||
struct media_device media_dev;
|
||||
struct media_pipeline pipe;
|
||||
struct device *dev;
|
||||
struct v4l2_async_notifier notifier;
|
||||
void __iomem *syscon_base;
|
||||
void __iomem *isp_base;
|
||||
struct clk_bulk_data sys_clk[STF_CLK_NUM];
|
||||
int nclks;
|
||||
struct reset_control_bulk_data sys_rst[STF_RST_NUM];
|
||||
int nrsts;
|
||||
};
|
||||
|
||||
struct stfcamss_async_subdev {
|
||||
struct v4l2_async_connection asd; /* must be first */
|
||||
enum stf_port_num port;
|
||||
};
|
||||
|
||||
static inline u32 stf_isp_reg_read(struct stfcamss *stfcamss, u32 reg)
|
||||
{
|
||||
return ioread32(stfcamss->isp_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_isp_reg_write(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 val)
|
||||
{
|
||||
iowrite32(val, stfcamss->isp_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_isp_reg_write_delay(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 val, u32 delay)
|
||||
{
|
||||
iowrite32(val, stfcamss->isp_base + reg);
|
||||
usleep_range(1000 * delay, 1000 * delay + 100);
|
||||
}
|
||||
|
||||
static inline void stf_isp_reg_set_bit(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 mask, u32 val)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = ioread32(stfcamss->isp_base + reg) & ~mask;
|
||||
val &= mask;
|
||||
val |= value;
|
||||
iowrite32(val, stfcamss->isp_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_isp_reg_set(struct stfcamss *stfcamss, u32 reg, u32 mask)
|
||||
{
|
||||
iowrite32(ioread32(stfcamss->isp_base + reg) | mask,
|
||||
stfcamss->isp_base + reg);
|
||||
}
|
||||
|
||||
static inline u32 stf_syscon_reg_read(struct stfcamss *stfcamss, u32 reg)
|
||||
{
|
||||
return ioread32(stfcamss->syscon_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_syscon_reg_write(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 val)
|
||||
{
|
||||
iowrite32(val, stfcamss->syscon_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_syscon_reg_set_bit(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 bit_mask)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = ioread32(stfcamss->syscon_base + reg);
|
||||
iowrite32(value | bit_mask, stfcamss->syscon_base + reg);
|
||||
}
|
||||
|
||||
static inline void stf_syscon_reg_clear_bit(struct stfcamss *stfcamss,
|
||||
u32 reg, u32 bit_mask)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = ioread32(stfcamss->syscon_base + reg);
|
||||
iowrite32(value & ~bit_mask, stfcamss->syscon_base + reg);
|
||||
}
|
||||
#endif /* STF_CAMSS_H */
|
Loading…
Reference in New Issue
Block a user