Commit a5517d53 authored by Eugen Hristev's avatar Eugen Hristev Committed by Hans Verkuil

media: microchip: add ISC driver as Microchip ISC

The Atmel ISC driver will be moved to staging to support old users that
are not using the media controller paradigm.
The ISC driver was converted to media controller in the public patch
series:

https://lore.kernel.org/lkml/20220503095127.48710-1-eugen.hristev@microchip.com/T/#m2c320fa8153c01379a1c35b1d90a00903949513a

However the conversion cannot be done directly as it would affect existing
users by breaking the old way of configuration for sama5d2 platforms.

After discussions with the media maintainers, the decision is to move
Atmel ISC to staging as-is, to keep the Kconfig symbols and the users
to the driver in staging. Thus, all the existing users of the non
media-controller paradigm will continue to be happy and use the old config
way.

The converted driver would support both sama5d2 and sama7g5 platforms with
media controller paradigm, but it requires userspace configuration of the
pipeline for all the pipeline modules.

In a simple configuration sensor ==> isc , the old isc driver used to call
subdev s_fmt and control the sensor directly.
This was achieved by having a lot of code inside the driver that was
querying the subdev at probe time and made a list of formats which are
usable.
Basically the user had nothing to configure, as the isc would handle
everything at the top level. This was an easy way to capture, but also came
with the drawback of lack of flexibility.
In a more complicated pipeline
 sensor ==> controller 1 ==> controller 2 ==> isc
this would not be achievable, as controller 1 and controller 2 might be
media-controller configurable, and will not propagate the formats down to
the sensor.
The new driver Microchip ISC would solve all these problems and exposes pads
entities and links to userspace.
For the ease of tracking, the patches that convert to media controller come
on top of this patch that simply readds the driver to the new location under
the new Kconfig symbols.

To differentiate between the old driver and the new driver, I have renamed
the new driver to Microchip ISC, renaming the Kconfig symbols as well, and
all the mentions inside the driver.
The only thing that remains common is the file
include/linux/atmel-isc-media.h which is the ABI for the v4l2 custom
controls that the ISC exposes.
This file is used by both driver, so I kept it as-is.
To further avoid confusion all files have been renamed and all functions
named isc_* as well.
The exported symbols have been renamed with added microchip_ prefix, to
avoid symbol duplication with the old driver, and to avoid confusion.
Other than that, I have fixed small checkpatch issues when readding the
driver.
Signed-off-by: default avatarEugen Hristev <eugen.hristev@microchip.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
parent 5c018562
......@@ -13495,6 +13495,8 @@ F: Documentation/devicetree/bindings/media/atmel,isc.yaml
F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
F: drivers/media/platform/atmel/atmel-isc*
F: drivers/media/platform/atmel/atmel-sama*-isc*
F: drivers/media/platform/microchip/microchip-isc*
F: drivers/media/platform/microchip/microchip-sama*-isc*
F: include/linux/atmel-isc-media.h
MICROCHIP ISI DRIVER
......
......@@ -2,6 +2,48 @@
comment "Microchip Technology, Inc. media platform drivers"
config VIDEO_MICROCHIP_ISC
tristate "Microchip Image Sensor Controller (ISC) support"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV && COMMON_CLK
depends on ARCH_AT91 || COMPILE_TEST
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select VIDEOBUF2_DMA_CONTIG
select REGMAP_MMIO
select V4L2_FWNODE
select VIDEO_MICROCHIP_ISC_BASE
help
This module makes the Microchip Image Sensor Controller available
as a v4l2 device.
To compile this driver as a module, choose M here: the
module will be called microchip-isc.
config VIDEO_MICROCHIP_XISC
tristate "Microchip eXtended Image Sensor Controller (XISC) support"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV && COMMON_CLK && VIDEO_V4L2_SUBDEV_API
depends on ARCH_AT91 || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select REGMAP_MMIO
select V4L2_FWNODE
select VIDEO_MICROCHIP_ISC_BASE
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
help
This module makes the Microchip eXtended Image Sensor Controller
available as a v4l2 device.
To compile this driver as a module, choose M here: the
module will be called microchip-xisc.
config VIDEO_MICROCHIP_ISC_BASE
tristate
default n
help
Microchip ISC and XISC common code base.
config VIDEO_MICROCHIP_CSI2DC
tristate "Microchip CSI2 Demux Controller"
depends on V4L_PLATFORM_DRIVERS
......
# SPDX-License-Identifier: GPL-2.0-only
microchip-isc-objs = microchip-sama5d2-isc.o
microchip-xisc-objs = microchip-sama7g5-isc.o
microchip-isc-common-objs = microchip-isc-base.o microchip-isc-clk.o
obj-$(CONFIG_VIDEO_MICROCHIP_ISC_BASE) += microchip-isc-common.o
obj-$(CONFIG_VIDEO_MICROCHIP_ISC) += microchip-isc.o
obj-$(CONFIG_VIDEO_MICROCHIP_XISC) += microchip-xisc.o
obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Microchip Image Sensor Controller (ISC) common clock driver setup
*
* Copyright (C) 2016 Microchip Technology, Inc.
*
* Author: Songjun Wu
* Author: Eugen Hristev <eugen.hristev@microchip.com>
*
*/
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include "microchip-isc-regs.h"
#include "microchip-isc.h"
static int isc_wait_clk_stable(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
struct regmap *regmap = isc_clk->regmap;
unsigned long timeout = jiffies + usecs_to_jiffies(1000);
unsigned int status;
while (time_before(jiffies, timeout)) {
regmap_read(regmap, ISC_CLKSR, &status);
if (!(status & ISC_CLKSR_SIP))
return 0;
usleep_range(10, 250);
}
return -ETIMEDOUT;
}
static int isc_clk_prepare(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
int ret;
ret = pm_runtime_resume_and_get(isc_clk->dev);
if (ret < 0)
return ret;
return isc_wait_clk_stable(hw);
}
static void isc_clk_unprepare(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
isc_wait_clk_stable(hw);
pm_runtime_put_sync(isc_clk->dev);
}
static int isc_clk_enable(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
u32 id = isc_clk->id;
struct regmap *regmap = isc_clk->regmap;
unsigned long flags;
unsigned int status;
dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
__func__, id, isc_clk->div, isc_clk->parent_id);
spin_lock_irqsave(&isc_clk->lock, flags);
regmap_update_bits(regmap, ISC_CLKCFG,
ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
(isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
(isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
spin_unlock_irqrestore(&isc_clk->lock, flags);
regmap_read(regmap, ISC_CLKSR, &status);
if (status & ISC_CLK(id))
return 0;
else
return -EINVAL;
}
static void isc_clk_disable(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
u32 id = isc_clk->id;
unsigned long flags;
spin_lock_irqsave(&isc_clk->lock, flags);
regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
spin_unlock_irqrestore(&isc_clk->lock, flags);
}
static int isc_clk_is_enabled(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
u32 status;
int ret;
ret = pm_runtime_resume_and_get(isc_clk->dev);
if (ret < 0)
return 0;
regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
pm_runtime_put_sync(isc_clk->dev);
return status & ISC_CLK(isc_clk->id) ? 1 : 0;
}
static unsigned long
isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
}
static int isc_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
long best_rate = -EINVAL;
int best_diff = -1;
unsigned int i, div;
for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
struct clk_hw *parent;
unsigned long parent_rate;
parent = clk_hw_get_parent_by_index(hw, i);
if (!parent)
continue;
parent_rate = clk_hw_get_rate(parent);
if (!parent_rate)
continue;
for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
unsigned long rate;
int diff;
rate = DIV_ROUND_CLOSEST(parent_rate, div);
diff = abs(req->rate - rate);
if (best_diff < 0 || best_diff > diff) {
best_rate = rate;
best_diff = diff;
req->best_parent_rate = parent_rate;
req->best_parent_hw = parent;
}
if (!best_diff || rate < req->rate)
break;
}
if (!best_diff)
break;
}
dev_dbg(isc_clk->dev,
"ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
__func__, best_rate,
__clk_get_name((req->best_parent_hw)->clk),
req->best_parent_rate);
if (best_rate < 0)
return best_rate;
req->rate = best_rate;
return 0;
}
static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
if (index >= clk_hw_get_num_parents(hw))
return -EINVAL;
isc_clk->parent_id = index;
return 0;
}
static u8 isc_clk_get_parent(struct clk_hw *hw)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
return isc_clk->parent_id;
}
static int isc_clk_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct isc_clk *isc_clk = to_isc_clk(hw);
u32 div;
if (!rate)
return -EINVAL;
div = DIV_ROUND_CLOSEST(parent_rate, rate);
if (div > (ISC_CLK_MAX_DIV + 1) || !div)
return -EINVAL;
isc_clk->div = div - 1;
return 0;
}
static const struct clk_ops isc_clk_ops = {
.prepare = isc_clk_prepare,
.unprepare = isc_clk_unprepare,
.enable = isc_clk_enable,
.disable = isc_clk_disable,
.is_enabled = isc_clk_is_enabled,
.recalc_rate = isc_clk_recalc_rate,
.determine_rate = isc_clk_determine_rate,
.set_parent = isc_clk_set_parent,
.get_parent = isc_clk_get_parent,
.set_rate = isc_clk_set_rate,
};
static int isc_clk_register(struct isc_device *isc, unsigned int id)
{
struct regmap *regmap = isc->regmap;
struct device_node *np = isc->dev->of_node;
struct isc_clk *isc_clk;
struct clk_init_data init;
const char *clk_name = np->name;
const char *parent_names[3];
int num_parents;
if (id == ISC_ISPCK && !isc->ispck_required)
return 0;
num_parents = of_clk_get_parent_count(np);
if (num_parents < 1 || num_parents > 3)
return -EINVAL;
if (num_parents > 2 && id == ISC_ISPCK)
num_parents = 2;
of_clk_parent_fill(np, parent_names, num_parents);
if (id == ISC_MCK)
of_property_read_string(np, "clock-output-names", &clk_name);
else
clk_name = "isc-ispck";
init.parent_names = parent_names;
init.num_parents = num_parents;
init.name = clk_name;
init.ops = &isc_clk_ops;
init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
isc_clk = &isc->isc_clks[id];
isc_clk->hw.init = &init;
isc_clk->regmap = regmap;
isc_clk->id = id;
isc_clk->dev = isc->dev;
spin_lock_init(&isc_clk->lock);
isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
if (IS_ERR(isc_clk->clk)) {
dev_err(isc->dev, "%s: clock register fail\n", clk_name);
return PTR_ERR(isc_clk->clk);
} else if (id == ISC_MCK) {
of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
}
return 0;
}
int microchip_isc_clk_init(struct isc_device *isc)
{
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
ret = isc_clk_register(isc, i);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(microchip_isc_clk_init);
void microchip_isc_clk_cleanup(struct isc_device *isc)
{
unsigned int i;
of_clk_del_provider(isc->dev->of_node);
for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
struct isc_clk *isc_clk = &isc->isc_clks[i];
if (!IS_ERR(isc_clk->clk))
clk_unregister(isc_clk->clk);
}
}
EXPORT_SYMBOL_GPL(microchip_isc_clk_cleanup);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment