mirror of
https://github.com/torvalds/linux.git
synced 2024-12-25 04:11:49 +00:00
e6b5be2be4
Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlSOD20ACgkQMUfUDdst+ylLPACg2QrW1oHhdTMT9WI8jihlHVRM 53kAoLeteByQ3iVwWurwwseRPiWa8+MI =OVRS -----END PGP SIGNATURE----- Merge tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core update from Greg KH: "Here's the set of driver core patches for 3.19-rc1. They are dominated by the removal of the .owner field in platform drivers. They touch a lot of files, but they are "simple" changes, just removing a line in a structure. Other than that, a few minor driver core and debugfs changes. There are some ath9k patches coming in through this tree that have been acked by the wireless maintainers as they relied on the debugfs changes. Everything has been in linux-next for a while" * tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (324 commits) Revert "ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries" fs: debugfs: add forward declaration for struct device type firmware class: Deletion of an unnecessary check before the function call "vunmap" firmware loader: fix hung task warning dump devcoredump: provide a one-way disable function device: Add dev_<level>_once variants ath: ath9k: use debugfs_create_devm_seqfile() helper for seq_file entries ath: use seq_file api for ath9k debugfs files debugfs: add helper function to create device related seq_file drivers/base: cacheinfo: remove noisy error boot message Revert "core: platform: add warning if driver has no owner" drivers: base: support cpu cache information interface to userspace via sysfs drivers: base: add cpu_device_create to support per-cpu devices topology: replace custom attribute macros with standard DEVICE_ATTR* cpumask: factor out show_cpumap into separate helper function driver core: Fix unbalanced device reference in drivers_probe driver core: fix race with userland in device_add() sysfs/kernfs: make read requests on pre-alloc files use the buffer. sysfs/kernfs: allow attributes to request write buffer be pre-allocated. fs: sysfs: return EGBIG on write if offset is larger than file size ...
996 lines
23 KiB
C
996 lines
23 KiB
C
/*
|
|
* Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
|
|
*
|
|
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
|
*
|
|
* Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
|
|
* Younghwan Joo <yhwan.joo@samsung.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-contiguous.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/videodev2.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
|
|
#include "media-dev.h"
|
|
#include "fimc-is.h"
|
|
#include "fimc-is-command.h"
|
|
#include "fimc-is-errno.h"
|
|
#include "fimc-is-i2c.h"
|
|
#include "fimc-is-param.h"
|
|
#include "fimc-is-regs.h"
|
|
|
|
|
|
static char *fimc_is_clocks[ISS_CLKS_MAX] = {
|
|
[ISS_CLK_PPMUISPX] = "ppmuispx",
|
|
[ISS_CLK_PPMUISPMX] = "ppmuispmx",
|
|
[ISS_CLK_LITE0] = "lite0",
|
|
[ISS_CLK_LITE1] = "lite1",
|
|
[ISS_CLK_MPLL] = "mpll",
|
|
[ISS_CLK_ISP] = "isp",
|
|
[ISS_CLK_DRC] = "drc",
|
|
[ISS_CLK_FD] = "fd",
|
|
[ISS_CLK_MCUISP] = "mcuisp",
|
|
[ISS_CLK_UART] = "uart",
|
|
[ISS_CLK_ISP_DIV0] = "ispdiv0",
|
|
[ISS_CLK_ISP_DIV1] = "ispdiv1",
|
|
[ISS_CLK_MCUISP_DIV0] = "mcuispdiv0",
|
|
[ISS_CLK_MCUISP_DIV1] = "mcuispdiv1",
|
|
[ISS_CLK_ACLK200] = "aclk200",
|
|
[ISS_CLK_ACLK200_DIV] = "div_aclk200",
|
|
[ISS_CLK_ACLK400MCUISP] = "aclk400mcuisp",
|
|
[ISS_CLK_ACLK400MCUISP_DIV] = "div_aclk400mcuisp",
|
|
};
|
|
|
|
static void fimc_is_put_clocks(struct fimc_is *is)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ISS_CLKS_MAX; i++) {
|
|
if (IS_ERR(is->clocks[i]))
|
|
continue;
|
|
clk_put(is->clocks[i]);
|
|
is->clocks[i] = ERR_PTR(-EINVAL);
|
|
}
|
|
}
|
|
|
|
static int fimc_is_get_clocks(struct fimc_is *is)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ISS_CLKS_MAX; i++)
|
|
is->clocks[i] = ERR_PTR(-EINVAL);
|
|
|
|
for (i = 0; i < ISS_CLKS_MAX; i++) {
|
|
is->clocks[i] = clk_get(&is->pdev->dev, fimc_is_clocks[i]);
|
|
if (IS_ERR(is->clocks[i])) {
|
|
ret = PTR_ERR(is->clocks[i]);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
fimc_is_put_clocks(is);
|
|
dev_err(&is->pdev->dev, "failed to get clock: %s\n",
|
|
fimc_is_clocks[i]);
|
|
return ret;
|
|
}
|
|
|
|
static int fimc_is_setup_clocks(struct fimc_is *is)
|
|
{
|
|
int ret;
|
|
|
|
ret = clk_set_parent(is->clocks[ISS_CLK_ACLK200],
|
|
is->clocks[ISS_CLK_ACLK200_DIV]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_set_parent(is->clocks[ISS_CLK_ACLK400MCUISP],
|
|
is->clocks[ISS_CLK_ACLK400MCUISP_DIV]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV0], ACLK_AXI_FREQUENCY);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV1], ACLK_AXI_FREQUENCY);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV0],
|
|
ATCLK_MCUISP_FREQUENCY);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV1],
|
|
ATCLK_MCUISP_FREQUENCY);
|
|
}
|
|
|
|
static int fimc_is_enable_clocks(struct fimc_is *is)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ISS_GATE_CLKS_MAX; i++) {
|
|
if (IS_ERR(is->clocks[i]))
|
|
continue;
|
|
ret = clk_prepare_enable(is->clocks[i]);
|
|
if (ret < 0) {
|
|
dev_err(&is->pdev->dev, "clock %s enable failed\n",
|
|
fimc_is_clocks[i]);
|
|
for (--i; i >= 0; i--)
|
|
clk_disable(is->clocks[i]);
|
|
return ret;
|
|
}
|
|
pr_debug("enabled clock: %s\n", fimc_is_clocks[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void fimc_is_disable_clocks(struct fimc_is *is)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ISS_GATE_CLKS_MAX; i++) {
|
|
if (!IS_ERR(is->clocks[i])) {
|
|
clk_disable_unprepare(is->clocks[i]);
|
|
pr_debug("disabled clock: %s\n", fimc_is_clocks[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int fimc_is_parse_sensor_config(struct fimc_is *is, unsigned int index,
|
|
struct device_node *node)
|
|
{
|
|
struct fimc_is_sensor *sensor = &is->sensor[index];
|
|
u32 tmp = 0;
|
|
int ret;
|
|
|
|
sensor->drvdata = fimc_is_sensor_get_drvdata(node);
|
|
if (!sensor->drvdata) {
|
|
dev_err(&is->pdev->dev, "no driver data found for: %s\n",
|
|
node->full_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
node = of_graph_get_next_endpoint(node, NULL);
|
|
if (!node)
|
|
return -ENXIO;
|
|
|
|
node = of_graph_get_remote_port(node);
|
|
if (!node)
|
|
return -ENXIO;
|
|
|
|
/* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */
|
|
ret = of_property_read_u32(node, "reg", &tmp);
|
|
if (ret < 0) {
|
|
dev_err(&is->pdev->dev, "reg property not found at: %s\n",
|
|
node->full_name);
|
|
return ret;
|
|
}
|
|
|
|
sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0;
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_register_subdevs(struct fimc_is *is)
|
|
{
|
|
struct device_node *i2c_bus, *child;
|
|
int ret, index = 0;
|
|
|
|
ret = fimc_isp_subdev_create(&is->isp);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Initialize memory allocator context for the ISP DMA. */
|
|
is->isp.alloc_ctx = is->alloc_ctx;
|
|
|
|
for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) {
|
|
for_each_available_child_of_node(i2c_bus, child) {
|
|
ret = fimc_is_parse_sensor_config(is, index, child);
|
|
|
|
if (ret < 0 || index >= FIMC_IS_SENSORS_NUM) {
|
|
of_node_put(child);
|
|
return ret;
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_unregister_subdevs(struct fimc_is *is)
|
|
{
|
|
fimc_isp_subdev_destroy(&is->isp);
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_load_setfile(struct fimc_is *is, char *file_name)
|
|
{
|
|
const struct firmware *fw;
|
|
void *buf;
|
|
int ret;
|
|
|
|
ret = request_firmware(&fw, file_name, &is->pdev->dev);
|
|
if (ret < 0) {
|
|
dev_err(&is->pdev->dev, "firmware request failed (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
buf = is->memory.vaddr + is->setfile.base;
|
|
memcpy(buf, fw->data, fw->size);
|
|
fimc_is_mem_barrier();
|
|
is->setfile.size = fw->size;
|
|
|
|
pr_debug("mem vaddr: %p, setfile buf: %p\n", is->memory.vaddr, buf);
|
|
|
|
memcpy(is->fw.setfile_info,
|
|
fw->data + fw->size - FIMC_IS_SETFILE_INFO_LEN,
|
|
FIMC_IS_SETFILE_INFO_LEN - 1);
|
|
|
|
is->fw.setfile_info[FIMC_IS_SETFILE_INFO_LEN - 1] = '\0';
|
|
is->setfile.state = 1;
|
|
|
|
pr_debug("FIMC-IS setfile loaded: base: %#x, size: %zu B\n",
|
|
is->setfile.base, fw->size);
|
|
|
|
release_firmware(fw);
|
|
return ret;
|
|
}
|
|
|
|
int fimc_is_cpu_set_power(struct fimc_is *is, int on)
|
|
{
|
|
unsigned int timeout = FIMC_IS_POWER_ON_TIMEOUT;
|
|
|
|
if (on) {
|
|
/* Disable watchdog */
|
|
mcuctl_write(0, is, REG_WDT_ISP);
|
|
|
|
/* Cortex-A5 start address setting */
|
|
mcuctl_write(is->memory.paddr, is, MCUCTL_REG_BBOAR);
|
|
|
|
/* Enable and start Cortex-A5 */
|
|
pmuisp_write(0x18000, is, REG_PMU_ISP_ARM_OPTION);
|
|
pmuisp_write(0x1, is, REG_PMU_ISP_ARM_CONFIGURATION);
|
|
} else {
|
|
/* A5 power off */
|
|
pmuisp_write(0x10000, is, REG_PMU_ISP_ARM_OPTION);
|
|
pmuisp_write(0x0, is, REG_PMU_ISP_ARM_CONFIGURATION);
|
|
|
|
while (pmuisp_read(is, REG_PMU_ISP_ARM_STATUS) & 1) {
|
|
if (timeout == 0)
|
|
return -ETIME;
|
|
timeout--;
|
|
udelay(1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Wait until @bit of @is->state is set to @state in the interrupt handler. */
|
|
int fimc_is_wait_event(struct fimc_is *is, unsigned long bit,
|
|
unsigned int state, unsigned int timeout)
|
|
{
|
|
|
|
int ret = wait_event_timeout(is->irq_queue,
|
|
!state ^ test_bit(bit, &is->state),
|
|
timeout);
|
|
if (ret == 0) {
|
|
dev_WARN(&is->pdev->dev, "%s() timed out\n", __func__);
|
|
return -ETIME;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int fimc_is_start_firmware(struct fimc_is *is)
|
|
{
|
|
struct device *dev = &is->pdev->dev;
|
|
int ret;
|
|
|
|
if (is->fw.f_w == NULL) {
|
|
dev_err(dev, "firmware is not loaded\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(is->memory.vaddr, is->fw.f_w->data, is->fw.f_w->size);
|
|
wmb();
|
|
|
|
ret = fimc_is_cpu_set_power(is, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = fimc_is_wait_event(is, IS_ST_A5_PWR_ON, 1,
|
|
msecs_to_jiffies(FIMC_IS_FW_LOAD_TIMEOUT));
|
|
if (ret < 0)
|
|
dev_err(dev, "FIMC-IS CPU power on failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Allocate working memory for the FIMC-IS CPU. */
|
|
static int fimc_is_alloc_cpu_memory(struct fimc_is *is)
|
|
{
|
|
struct device *dev = &is->pdev->dev;
|
|
|
|
is->memory.vaddr = dma_alloc_coherent(dev, FIMC_IS_CPU_MEM_SIZE,
|
|
&is->memory.paddr, GFP_KERNEL);
|
|
if (is->memory.vaddr == NULL)
|
|
return -ENOMEM;
|
|
|
|
is->memory.size = FIMC_IS_CPU_MEM_SIZE;
|
|
memset(is->memory.vaddr, 0, is->memory.size);
|
|
|
|
dev_info(dev, "FIMC-IS CPU memory base: %#x\n", (u32)is->memory.paddr);
|
|
|
|
if (((u32)is->memory.paddr) & FIMC_IS_FW_ADDR_MASK) {
|
|
dev_err(dev, "invalid firmware memory alignment: %#x\n",
|
|
(u32)is->memory.paddr);
|
|
dma_free_coherent(dev, is->memory.size, is->memory.vaddr,
|
|
is->memory.paddr);
|
|
return -EIO;
|
|
}
|
|
|
|
is->is_p_region = (struct is_region *)(is->memory.vaddr +
|
|
FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE);
|
|
|
|
is->is_dma_p_region = is->memory.paddr +
|
|
FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE;
|
|
|
|
is->is_shared_region = (struct is_share_region *)(is->memory.vaddr +
|
|
FIMC_IS_SHARED_REGION_OFFSET);
|
|
return 0;
|
|
}
|
|
|
|
static void fimc_is_free_cpu_memory(struct fimc_is *is)
|
|
{
|
|
struct device *dev = &is->pdev->dev;
|
|
|
|
if (is->memory.vaddr == NULL)
|
|
return;
|
|
|
|
dma_free_coherent(dev, is->memory.size, is->memory.vaddr,
|
|
is->memory.paddr);
|
|
}
|
|
|
|
static void fimc_is_load_firmware(const struct firmware *fw, void *context)
|
|
{
|
|
struct fimc_is *is = context;
|
|
struct device *dev = &is->pdev->dev;
|
|
void *buf;
|
|
int ret;
|
|
|
|
if (fw == NULL) {
|
|
dev_err(dev, "firmware request failed\n");
|
|
return;
|
|
}
|
|
mutex_lock(&is->lock);
|
|
|
|
if (fw->size < FIMC_IS_FW_SIZE_MIN || fw->size > FIMC_IS_FW_SIZE_MAX) {
|
|
dev_err(dev, "wrong firmware size: %zu\n", fw->size);
|
|
goto done;
|
|
}
|
|
|
|
is->fw.size = fw->size;
|
|
|
|
ret = fimc_is_alloc_cpu_memory(is);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to allocate FIMC-IS CPU memory\n");
|
|
goto done;
|
|
}
|
|
|
|
memcpy(is->memory.vaddr, fw->data, fw->size);
|
|
wmb();
|
|
|
|
/* Read firmware description. */
|
|
buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_DESC_LEN);
|
|
memcpy(&is->fw.info, buf, FIMC_IS_FW_INFO_LEN);
|
|
is->fw.info[FIMC_IS_FW_INFO_LEN] = 0;
|
|
|
|
buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_VER_LEN);
|
|
memcpy(&is->fw.version, buf, FIMC_IS_FW_VER_LEN);
|
|
is->fw.version[FIMC_IS_FW_VER_LEN - 1] = 0;
|
|
|
|
is->fw.state = 1;
|
|
|
|
dev_info(dev, "loaded firmware: %s, rev. %s\n",
|
|
is->fw.info, is->fw.version);
|
|
dev_dbg(dev, "FW size: %zu, paddr: %pad\n", fw->size, &is->memory.paddr);
|
|
|
|
is->is_shared_region->chip_id = 0xe4412;
|
|
is->is_shared_region->chip_rev_no = 1;
|
|
|
|
fimc_is_mem_barrier();
|
|
|
|
/*
|
|
* FIXME: The firmware is not being released for now, as it is
|
|
* needed around for copying to the IS working memory every
|
|
* time before the Cortex-A5 is restarted.
|
|
*/
|
|
release_firmware(is->fw.f_w);
|
|
is->fw.f_w = fw;
|
|
done:
|
|
mutex_unlock(&is->lock);
|
|
}
|
|
|
|
static int fimc_is_request_firmware(struct fimc_is *is, const char *fw_name)
|
|
{
|
|
return request_firmware_nowait(THIS_MODULE,
|
|
FW_ACTION_HOTPLUG, fw_name, &is->pdev->dev,
|
|
GFP_KERNEL, is, fimc_is_load_firmware);
|
|
}
|
|
|
|
/* General IS interrupt handler */
|
|
static void fimc_is_general_irq_handler(struct fimc_is *is)
|
|
{
|
|
is->i2h_cmd.cmd = mcuctl_read(is, MCUCTL_REG_ISSR(10));
|
|
|
|
switch (is->i2h_cmd.cmd) {
|
|
case IHC_GET_SENSOR_NUM:
|
|
fimc_is_hw_get_params(is, 1);
|
|
fimc_is_hw_wait_intmsr0_intmsd0(is);
|
|
fimc_is_hw_set_sensor_num(is);
|
|
pr_debug("ISP FW version: %#x\n", is->i2h_cmd.args[0]);
|
|
break;
|
|
case IHC_SET_FACE_MARK:
|
|
case IHC_FRAME_DONE:
|
|
fimc_is_hw_get_params(is, 2);
|
|
break;
|
|
case IHC_SET_SHOT_MARK:
|
|
case IHC_AA_DONE:
|
|
case IH_REPLY_DONE:
|
|
fimc_is_hw_get_params(is, 3);
|
|
break;
|
|
case IH_REPLY_NOT_DONE:
|
|
fimc_is_hw_get_params(is, 4);
|
|
break;
|
|
case IHC_NOT_READY:
|
|
break;
|
|
default:
|
|
pr_info("unknown command: %#x\n", is->i2h_cmd.cmd);
|
|
}
|
|
|
|
fimc_is_fw_clear_irq1(is, FIMC_IS_INT_GENERAL);
|
|
|
|
switch (is->i2h_cmd.cmd) {
|
|
case IHC_GET_SENSOR_NUM:
|
|
fimc_is_hw_set_intgr0_gd0(is);
|
|
set_bit(IS_ST_A5_PWR_ON, &is->state);
|
|
break;
|
|
|
|
case IHC_SET_SHOT_MARK:
|
|
break;
|
|
|
|
case IHC_SET_FACE_MARK:
|
|
is->fd_header.count = is->i2h_cmd.args[0];
|
|
is->fd_header.index = is->i2h_cmd.args[1];
|
|
is->fd_header.offset = 0;
|
|
break;
|
|
|
|
case IHC_FRAME_DONE:
|
|
break;
|
|
|
|
case IHC_AA_DONE:
|
|
pr_debug("AA_DONE - %d, %d, %d\n", is->i2h_cmd.args[0],
|
|
is->i2h_cmd.args[1], is->i2h_cmd.args[2]);
|
|
break;
|
|
|
|
case IH_REPLY_DONE:
|
|
pr_debug("ISR_DONE: args[0]: %#x\n", is->i2h_cmd.args[0]);
|
|
|
|
switch (is->i2h_cmd.args[0]) {
|
|
case HIC_PREVIEW_STILL...HIC_CAPTURE_VIDEO:
|
|
/* Get CAC margin */
|
|
set_bit(IS_ST_CHANGE_MODE, &is->state);
|
|
is->isp.cac_margin_x = is->i2h_cmd.args[1];
|
|
is->isp.cac_margin_y = is->i2h_cmd.args[2];
|
|
pr_debug("CAC margin (x,y): (%d,%d)\n",
|
|
is->isp.cac_margin_x, is->isp.cac_margin_y);
|
|
break;
|
|
|
|
case HIC_STREAM_ON:
|
|
clear_bit(IS_ST_STREAM_OFF, &is->state);
|
|
set_bit(IS_ST_STREAM_ON, &is->state);
|
|
break;
|
|
|
|
case HIC_STREAM_OFF:
|
|
clear_bit(IS_ST_STREAM_ON, &is->state);
|
|
set_bit(IS_ST_STREAM_OFF, &is->state);
|
|
break;
|
|
|
|
case HIC_SET_PARAMETER:
|
|
is->config[is->config_index].p_region_index[0] = 0;
|
|
is->config[is->config_index].p_region_index[1] = 0;
|
|
set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state);
|
|
pr_debug("HIC_SET_PARAMETER\n");
|
|
break;
|
|
|
|
case HIC_GET_PARAMETER:
|
|
break;
|
|
|
|
case HIC_SET_TUNE:
|
|
break;
|
|
|
|
case HIC_GET_STATUS:
|
|
break;
|
|
|
|
case HIC_OPEN_SENSOR:
|
|
set_bit(IS_ST_OPEN_SENSOR, &is->state);
|
|
pr_debug("data lanes: %d, settle line: %d\n",
|
|
is->i2h_cmd.args[2], is->i2h_cmd.args[1]);
|
|
break;
|
|
|
|
case HIC_CLOSE_SENSOR:
|
|
clear_bit(IS_ST_OPEN_SENSOR, &is->state);
|
|
is->sensor_index = 0;
|
|
break;
|
|
|
|
case HIC_MSG_TEST:
|
|
pr_debug("config MSG level completed\n");
|
|
break;
|
|
|
|
case HIC_POWER_DOWN:
|
|
clear_bit(IS_ST_PWR_SUBIP_ON, &is->state);
|
|
break;
|
|
|
|
case HIC_GET_SET_FILE_ADDR:
|
|
is->setfile.base = is->i2h_cmd.args[1];
|
|
set_bit(IS_ST_SETFILE_LOADED, &is->state);
|
|
break;
|
|
|
|
case HIC_LOAD_SET_FILE:
|
|
set_bit(IS_ST_SETFILE_LOADED, &is->state);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case IH_REPLY_NOT_DONE:
|
|
pr_err("ISR_NDONE: %d: %#x, %s\n", is->i2h_cmd.args[0],
|
|
is->i2h_cmd.args[1],
|
|
fimc_is_strerr(is->i2h_cmd.args[1]));
|
|
|
|
if (is->i2h_cmd.args[1] & IS_ERROR_TIME_OUT_FLAG)
|
|
pr_err("IS_ERROR_TIME_OUT\n");
|
|
|
|
switch (is->i2h_cmd.args[1]) {
|
|
case IS_ERROR_SET_PARAMETER:
|
|
fimc_is_mem_barrier();
|
|
}
|
|
|
|
switch (is->i2h_cmd.args[0]) {
|
|
case HIC_SET_PARAMETER:
|
|
is->config[is->config_index].p_region_index[0] = 0;
|
|
is->config[is->config_index].p_region_index[1] = 0;
|
|
set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case IHC_NOT_READY:
|
|
pr_err("IS control sequence error: Not Ready\n");
|
|
break;
|
|
}
|
|
|
|
wake_up(&is->irq_queue);
|
|
}
|
|
|
|
static irqreturn_t fimc_is_irq_handler(int irq, void *priv)
|
|
{
|
|
struct fimc_is *is = priv;
|
|
unsigned long flags;
|
|
u32 status;
|
|
|
|
spin_lock_irqsave(&is->slock, flags);
|
|
status = mcuctl_read(is, MCUCTL_REG_INTSR1);
|
|
|
|
if (status & (1UL << FIMC_IS_INT_GENERAL))
|
|
fimc_is_general_irq_handler(is);
|
|
|
|
if (status & (1UL << FIMC_IS_INT_FRAME_DONE_ISP))
|
|
fimc_isp_irq_handler(is);
|
|
|
|
spin_unlock_irqrestore(&is->slock, flags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int fimc_is_hw_open_sensor(struct fimc_is *is,
|
|
struct fimc_is_sensor *sensor)
|
|
{
|
|
struct sensor_open_extended *soe = (void *)&is->is_p_region->shared;
|
|
|
|
fimc_is_hw_wait_intmsr0_intmsd0(is);
|
|
|
|
soe->self_calibration_mode = 1;
|
|
soe->actuator_type = 0;
|
|
soe->mipi_lane_num = 0;
|
|
soe->mclk = 0;
|
|
soe->mipi_speed = 0;
|
|
soe->fast_open_sensor = 0;
|
|
soe->i2c_sclk = 88000000;
|
|
|
|
fimc_is_mem_barrier();
|
|
|
|
mcuctl_write(HIC_OPEN_SENSOR, is, MCUCTL_REG_ISSR(0));
|
|
mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
|
|
mcuctl_write(sensor->drvdata->id, is, MCUCTL_REG_ISSR(2));
|
|
mcuctl_write(sensor->i2c_bus, is, MCUCTL_REG_ISSR(3));
|
|
mcuctl_write(is->is_dma_p_region, is, MCUCTL_REG_ISSR(4));
|
|
|
|
fimc_is_hw_set_intgr0_gd0(is);
|
|
|
|
return fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 1,
|
|
sensor->drvdata->open_timeout);
|
|
}
|
|
|
|
|
|
int fimc_is_hw_initialize(struct fimc_is *is)
|
|
{
|
|
const int config_ids[] = {
|
|
IS_SC_PREVIEW_STILL, IS_SC_PREVIEW_VIDEO,
|
|
IS_SC_CAPTURE_STILL, IS_SC_CAPTURE_VIDEO
|
|
};
|
|
struct device *dev = &is->pdev->dev;
|
|
u32 prev_id;
|
|
int i, ret;
|
|
|
|
/* Sensor initialization. Only one sensor is currently supported. */
|
|
ret = fimc_is_hw_open_sensor(is, &is->sensor[0]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Get the setfile address. */
|
|
fimc_is_hw_get_setfile_addr(is);
|
|
|
|
ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1,
|
|
FIMC_IS_CONFIG_TIMEOUT);
|
|
if (ret < 0) {
|
|
dev_err(dev, "get setfile address timed out\n");
|
|
return ret;
|
|
}
|
|
pr_debug("setfile.base: %#x\n", is->setfile.base);
|
|
|
|
/* Load the setfile. */
|
|
fimc_is_load_setfile(is, FIMC_IS_SETFILE_6A3);
|
|
clear_bit(IS_ST_SETFILE_LOADED, &is->state);
|
|
fimc_is_hw_load_setfile(is);
|
|
ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1,
|
|
FIMC_IS_CONFIG_TIMEOUT);
|
|
if (ret < 0) {
|
|
dev_err(dev, "loading setfile timed out\n");
|
|
return ret;
|
|
}
|
|
|
|
pr_debug("setfile: base: %#x, size: %d\n",
|
|
is->setfile.base, is->setfile.size);
|
|
pr_info("FIMC-IS Setfile info: %s\n", is->fw.setfile_info);
|
|
|
|
/* Check magic number. */
|
|
if (is->is_p_region->shared[MAX_SHARED_COUNT - 1] !=
|
|
FIMC_IS_MAGIC_NUMBER) {
|
|
dev_err(dev, "magic number error!\n");
|
|
return -EIO;
|
|
}
|
|
|
|
pr_debug("shared region: %pad, parameter region: %pad\n",
|
|
&is->memory.paddr + FIMC_IS_SHARED_REGION_OFFSET,
|
|
&is->is_dma_p_region);
|
|
|
|
is->setfile.sub_index = 0;
|
|
|
|
/* Stream off. */
|
|
fimc_is_hw_stream_off(is);
|
|
ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1,
|
|
FIMC_IS_CONFIG_TIMEOUT);
|
|
if (ret < 0) {
|
|
dev_err(dev, "stream off timeout\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Preserve previous mode. */
|
|
prev_id = is->config_index;
|
|
|
|
/* Set initial parameter values. */
|
|
for (i = 0; i < ARRAY_SIZE(config_ids); i++) {
|
|
is->config_index = config_ids[i];
|
|
fimc_is_set_initial_params(is);
|
|
ret = fimc_is_itf_s_param(is, true);
|
|
if (ret < 0) {
|
|
is->config_index = prev_id;
|
|
return ret;
|
|
}
|
|
}
|
|
is->config_index = prev_id;
|
|
|
|
set_bit(IS_ST_INIT_DONE, &is->state);
|
|
dev_info(dev, "initialization sequence completed (%d)\n",
|
|
is->config_index);
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_log_show(struct seq_file *s, void *data)
|
|
{
|
|
struct fimc_is *is = s->private;
|
|
const u8 *buf = is->memory.vaddr + FIMC_IS_DEBUG_REGION_OFFSET;
|
|
|
|
if (is->memory.vaddr == NULL) {
|
|
dev_err(&is->pdev->dev, "firmware memory is not initialized\n");
|
|
return -EIO;
|
|
}
|
|
|
|
seq_printf(s, "%s\n", buf);
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, fimc_is_log_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations fimc_is_debugfs_fops = {
|
|
.open = fimc_is_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void fimc_is_debugfs_remove(struct fimc_is *is)
|
|
{
|
|
debugfs_remove_recursive(is->debugfs_entry);
|
|
is->debugfs_entry = NULL;
|
|
}
|
|
|
|
static int fimc_is_debugfs_create(struct fimc_is *is)
|
|
{
|
|
struct dentry *dentry;
|
|
|
|
is->debugfs_entry = debugfs_create_dir("fimc_is", NULL);
|
|
|
|
dentry = debugfs_create_file("fw_log", S_IRUGO, is->debugfs_entry,
|
|
is, &fimc_is_debugfs_fops);
|
|
if (!dentry)
|
|
fimc_is_debugfs_remove(is);
|
|
|
|
return is->debugfs_entry == NULL ? -EIO : 0;
|
|
}
|
|
|
|
static int fimc_is_runtime_resume(struct device *dev);
|
|
static int fimc_is_runtime_suspend(struct device *dev);
|
|
|
|
static int fimc_is_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct fimc_is *is;
|
|
struct resource res;
|
|
struct device_node *node;
|
|
int ret;
|
|
|
|
is = devm_kzalloc(&pdev->dev, sizeof(*is), GFP_KERNEL);
|
|
if (!is)
|
|
return -ENOMEM;
|
|
|
|
is->pdev = pdev;
|
|
is->isp.pdev = pdev;
|
|
|
|
init_waitqueue_head(&is->irq_queue);
|
|
spin_lock_init(&is->slock);
|
|
mutex_init(&is->lock);
|
|
|
|
ret = of_address_to_resource(dev->of_node, 0, &res);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
is->regs = devm_ioremap_resource(dev, &res);
|
|
if (IS_ERR(is->regs))
|
|
return PTR_ERR(is->regs);
|
|
|
|
node = of_get_child_by_name(dev->of_node, "pmu");
|
|
if (!node)
|
|
return -ENODEV;
|
|
|
|
is->pmu_regs = of_iomap(node, 0);
|
|
if (!is->pmu_regs)
|
|
return -ENOMEM;
|
|
|
|
is->irq = irq_of_parse_and_map(dev->of_node, 0);
|
|
if (!is->irq) {
|
|
dev_err(dev, "no irq found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = fimc_is_get_clocks(is);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
platform_set_drvdata(pdev, is);
|
|
|
|
ret = request_irq(is->irq, fimc_is_irq_handler, 0, dev_name(dev), is);
|
|
if (ret < 0) {
|
|
dev_err(dev, "irq request failed\n");
|
|
goto err_clk;
|
|
}
|
|
pm_runtime_enable(dev);
|
|
|
|
if (!pm_runtime_enabled(dev)) {
|
|
ret = fimc_is_runtime_resume(dev);
|
|
if (ret < 0)
|
|
goto err_irq;
|
|
}
|
|
|
|
ret = pm_runtime_get_sync(dev);
|
|
if (ret < 0)
|
|
goto err_pm;
|
|
|
|
is->alloc_ctx = vb2_dma_contig_init_ctx(dev);
|
|
if (IS_ERR(is->alloc_ctx)) {
|
|
ret = PTR_ERR(is->alloc_ctx);
|
|
goto err_pm;
|
|
}
|
|
/*
|
|
* Register FIMC-IS V4L2 subdevs to this driver. The video nodes
|
|
* will be created within the subdev's registered() callback.
|
|
*/
|
|
ret = fimc_is_register_subdevs(is);
|
|
if (ret < 0)
|
|
goto err_vb;
|
|
|
|
ret = fimc_is_debugfs_create(is);
|
|
if (ret < 0)
|
|
goto err_sd;
|
|
|
|
ret = fimc_is_request_firmware(is, FIMC_IS_FW_FILENAME);
|
|
if (ret < 0)
|
|
goto err_dfs;
|
|
|
|
pm_runtime_put_sync(dev);
|
|
|
|
dev_dbg(dev, "FIMC-IS registered successfully\n");
|
|
return 0;
|
|
|
|
err_dfs:
|
|
fimc_is_debugfs_remove(is);
|
|
err_sd:
|
|
fimc_is_unregister_subdevs(is);
|
|
err_vb:
|
|
vb2_dma_contig_cleanup_ctx(is->alloc_ctx);
|
|
err_pm:
|
|
if (!pm_runtime_enabled(dev))
|
|
fimc_is_runtime_suspend(dev);
|
|
err_irq:
|
|
free_irq(is->irq, is);
|
|
err_clk:
|
|
fimc_is_put_clocks(is);
|
|
return ret;
|
|
}
|
|
|
|
static int fimc_is_runtime_resume(struct device *dev)
|
|
{
|
|
struct fimc_is *is = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = fimc_is_setup_clocks(is);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return fimc_is_enable_clocks(is);
|
|
}
|
|
|
|
static int fimc_is_runtime_suspend(struct device *dev)
|
|
{
|
|
struct fimc_is *is = dev_get_drvdata(dev);
|
|
|
|
fimc_is_disable_clocks(is);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int fimc_is_resume(struct device *dev)
|
|
{
|
|
/* TODO: */
|
|
return 0;
|
|
}
|
|
|
|
static int fimc_is_suspend(struct device *dev)
|
|
{
|
|
struct fimc_is *is = dev_get_drvdata(dev);
|
|
|
|
/* TODO: */
|
|
if (test_bit(IS_ST_A5_PWR_ON, &is->state))
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static int fimc_is_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct fimc_is *is = dev_get_drvdata(dev);
|
|
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
if (!pm_runtime_status_suspended(dev))
|
|
fimc_is_runtime_suspend(dev);
|
|
free_irq(is->irq, is);
|
|
fimc_is_unregister_subdevs(is);
|
|
vb2_dma_contig_cleanup_ctx(is->alloc_ctx);
|
|
fimc_is_put_clocks(is);
|
|
fimc_is_debugfs_remove(is);
|
|
release_firmware(is->fw.f_w);
|
|
fimc_is_free_cpu_memory(is);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id fimc_is_of_match[] = {
|
|
{ .compatible = "samsung,exynos4212-fimc-is" },
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, fimc_is_of_match);
|
|
|
|
static const struct dev_pm_ops fimc_is_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(fimc_is_suspend, fimc_is_resume)
|
|
SET_RUNTIME_PM_OPS(fimc_is_runtime_suspend, fimc_is_runtime_resume,
|
|
NULL)
|
|
};
|
|
|
|
static struct platform_driver fimc_is_driver = {
|
|
.probe = fimc_is_probe,
|
|
.remove = fimc_is_remove,
|
|
.driver = {
|
|
.of_match_table = fimc_is_of_match,
|
|
.name = FIMC_IS_DRV_NAME,
|
|
.pm = &fimc_is_pm_ops,
|
|
}
|
|
};
|
|
|
|
static int fimc_is_module_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = fimc_is_register_i2c_driver();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = platform_driver_register(&fimc_is_driver);
|
|
|
|
if (ret < 0)
|
|
fimc_is_unregister_i2c_driver();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fimc_is_module_exit(void)
|
|
{
|
|
fimc_is_unregister_i2c_driver();
|
|
platform_driver_unregister(&fimc_is_driver);
|
|
}
|
|
|
|
module_init(fimc_is_module_init);
|
|
module_exit(fimc_is_module_exit);
|
|
|
|
MODULE_ALIAS("platform:" FIMC_IS_DRV_NAME);
|
|
MODULE_AUTHOR("Younghwan Joo <yhwan.joo@samsung.com>");
|
|
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
|
|
MODULE_LICENSE("GPL v2");
|