mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-10 21:07:02 +09:00
camera: add mipi csi driver for sm1 [1/1]
PD#SWPL-5388 Problem: sm1 board camera need add mipi csi module Solution: add mipi csi module Verify: verified on SM1 AC200 Change-Id: I819f2f74aa8da7d725cb59e5636e790185964f79 Signed-off-by: Guosong Zhou <guosong.zhou@amlogic.com>
This commit is contained in:
20
MAINTAINERS
20
MAINTAINERS
@@ -15048,3 +15048,23 @@ F: drivers/amlogic/drm/meson_vpu_pipeline.c
|
||||
F: drivers/amlogic/drm/meson_vpu_pipeline_traverse.c
|
||||
F: include/dt-bindings/display/meson-drm-ids.h
|
||||
|
||||
AMLOGIC SM1 MIPI CSI DRIVER
|
||||
M: Guosong Zhou <guosong.zhou@amlogic.com>
|
||||
F: arch/arm/boot/dts/amlogic/sm1_s905d3_ac200.dts
|
||||
F: arch/arm/boot/dts/amlogic/mesonsm1.dtsi
|
||||
F: arch/arm/configs/meson64_a32_defconfig
|
||||
F: arch/arm64/boot/dts/amlogic/sm1_s905d3_ac200.dts
|
||||
F: arch/arm64/boot/dts/amlogic/mesonsm1.dtsi
|
||||
F: arch/arm64/configs/meson64_defconfig
|
||||
F: drivers/amlogic/clk/g12a/g12a.h
|
||||
F: include/dt-bindings/clock/amlogic,g12a-clkc.h
|
||||
F: drivers/amlogic/clk/sm1/sm1.c
|
||||
F: drivers/amlogic/media/camera/Makefile
|
||||
F: drivers/amlogic/media/camera/common/cam_prober.c
|
||||
F: drivers/amlogic/media/vin/tvin/Kconfig
|
||||
F: drivers/amlogic/media/vin/tvin/Makefile
|
||||
F: include/linux/amlogic/media/camera/aml_cam_info.h
|
||||
F: drivers/amlogic/media/camera/ov5640.c
|
||||
F: drivers/amlogic/media/camera/ov5640_firmware.h
|
||||
F: drivers/amlogic/media/vin/tvin/csi/*
|
||||
F: include/linux/amlogic/media/mipi/*
|
||||
|
||||
@@ -2307,6 +2307,15 @@
|
||||
};
|
||||
};
|
||||
|
||||
i2c2_master_pins3:i2c2_pins3 {
|
||||
mux {
|
||||
groups = "i2c2_sda_z10",
|
||||
"i2c2_sck_z11";
|
||||
function = "i2c2";
|
||||
drive-strength = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c3_master_pins1:i2c3_pins1 {
|
||||
mux {
|
||||
groups = "i2c3_sda_h",
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
compatible = "amlogic, vm";
|
||||
memory-region = <&vm0_cma_reserved>;
|
||||
dev_name = "vm0";
|
||||
status = "disabled";
|
||||
status = "okay";
|
||||
vm_id = <0>;
|
||||
};
|
||||
|
||||
@@ -333,28 +333,45 @@
|
||||
|
||||
aml_cams {
|
||||
compatible = "amlogic, cams_prober";
|
||||
status = "disabled";
|
||||
status = "okay";
|
||||
pinctrl-names="default";
|
||||
pinctrl-0=<&cam_dvp_pins &gen_clk_ee_z>;
|
||||
pinctrl-0=<&gen_clk_ee_z>;
|
||||
clocks = <&clkc CLKID_GEN_CLK>;
|
||||
clock-names = "g12a_24m";
|
||||
cam_0{
|
||||
cam_name = "gc2145";
|
||||
cam_name = "ov5640";
|
||||
front_back = <0>;
|
||||
/*u200 i2c2 gpio conflict with ethmac*/
|
||||
camera-i2c-bus = <&i2c2>;
|
||||
gpio_pwdn-gpios = <&gpio GPIOZ_2 GPIO_ACTIVE_HIGH>;
|
||||
camvdd-gpios = <&gpio GPIOZ_5 GPIO_ACTIVE_HIGH>;
|
||||
gpio_pwdn-gpios = <&gpio GPIOZ_6 GPIO_ACTIVE_HIGH>;
|
||||
gpio_rst-gpios = <&gpio GPIOZ_12 GPIO_ACTIVE_HIGH>;
|
||||
mirror_flip = <1>;
|
||||
vertical_flip = <1>;
|
||||
spread_spectrum = <0>;
|
||||
bt_path = "gpio";
|
||||
bt_path = "csi";
|
||||
interface = "mipi";
|
||||
clk_channel = "a";
|
||||
bt_path_count = <1>;
|
||||
vdin_path = <0>;
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
amvdec_csi {
|
||||
compatible = "amlogic, amvdec_csi";
|
||||
status = "okay";
|
||||
csi_id = <0>;
|
||||
reg = <0xff650000 0x00000100>,
|
||||
<0xffe0c000 0x00000100>,
|
||||
<0xffe0d000 0x00000100>;
|
||||
reg-names = "csi_phy", "csi_host", "csi_adapt";
|
||||
clocks = <&clkc CLKID_CSI_ADAPT_CLK_COMP>,
|
||||
<&clkc CLKID_MIPI_CSI_PHY_CLK_COMP>;
|
||||
clock-names = "cts_csi_adapt_clk_composite",
|
||||
"cts_csi_phy_clk_composite";
|
||||
interrupts = <0 1 0>;
|
||||
interrupt-names = "csi_phy";
|
||||
};
|
||||
|
||||
gpio_keypad{
|
||||
compatible = "amlogic, gpio_keypad";
|
||||
status = "okay";
|
||||
@@ -1049,9 +1066,9 @@
|
||||
};
|
||||
|
||||
&i2c2 {
|
||||
status = "disabled";
|
||||
status = "okay";
|
||||
pinctrl-names="default";
|
||||
pinctrl-0=<&i2c2_master_pins2>;
|
||||
pinctrl-0=<&i2c2_master_pins3>;
|
||||
clock-frequency = <100000>;
|
||||
};
|
||||
|
||||
|
||||
@@ -316,6 +316,7 @@ CONFIG_AMLOGIC_MEDIA_TVIN_HDMI=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_AFE=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_VBI=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_AVDETECT=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_CSI=y
|
||||
CONFIG_AMLOGIC_MEDIA_VIDEO_PROCESSOR=y
|
||||
CONFIG_AMLOGIC_V4L_VIDEO=y
|
||||
CONFIG_AMLOGIC_V4L_VIDEO2=y
|
||||
@@ -333,6 +334,7 @@ CONFIG_AMLOGIC_MEDIA_GDC=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE=y
|
||||
CONFIG_AMLOGIC_VM_DISABLE_VIDEOLAYER=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE_GC2145=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE_OV5640=y
|
||||
CONFIG_AMLOGIC_DTV_DEMOD=y
|
||||
CONFIG_AMLOGIC_MMC=y
|
||||
CONFIG_AMLOGIC_NAND=y
|
||||
|
||||
@@ -2267,6 +2267,15 @@
|
||||
};
|
||||
};
|
||||
|
||||
i2c2_master_pins3:i2c2_pins3 {
|
||||
mux {
|
||||
groups = "i2c2_sda_z10",
|
||||
"i2c2_sck_z11";
|
||||
function = "i2c2";
|
||||
drive-strength = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c3_master_pins1:i2c3_pins1 {
|
||||
mux {
|
||||
groups = "i2c3_sda_h",
|
||||
|
||||
@@ -333,26 +333,43 @@
|
||||
compatible = "amlogic, cams_prober";
|
||||
status = "disabled";
|
||||
pinctrl-names="default";
|
||||
pinctrl-0=<&cam_dvp_pins &gen_clk_ee_z>;
|
||||
pinctrl-0=<&gen_clk_ee_z>;
|
||||
clocks = <&clkc CLKID_GEN_CLK>;
|
||||
clock-names = "g12a_24m";
|
||||
cam_0{
|
||||
cam_name = "gc2145";
|
||||
cam_name = "ov5640";
|
||||
front_back = <0>;
|
||||
/*u200 i2c2 gpio conflict with ethmac*/
|
||||
camera-i2c-bus = <&i2c2>;
|
||||
gpio_pwdn-gpios = <&gpio GPIOZ_2 GPIO_ACTIVE_HIGH>;
|
||||
camvdd-gpios = <&gpio GPIOZ_5 GPIO_ACTIVE_HIGH>;
|
||||
gpio_pwdn-gpios = <&gpio GPIOZ_6 GPIO_ACTIVE_HIGH>;
|
||||
gpio_rst-gpios = <&gpio GPIOZ_12 GPIO_ACTIVE_HIGH>;
|
||||
mirror_flip = <1>;
|
||||
vertical_flip = <1>;
|
||||
spread_spectrum = <0>;
|
||||
bt_path = "gpio";
|
||||
bt_path = "csi";
|
||||
interface = "mipi";
|
||||
clk_channel = "a";
|
||||
bt_path_count = <1>;
|
||||
vdin_path = <0>;
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
amvdec_csi {
|
||||
compatible = "amlogic, amvdec_csi";
|
||||
status = "disabled";
|
||||
csi_id = <0>;
|
||||
reg = <0x0 0xff650000 0x0 0x00000100>,
|
||||
<0x0 0xffe0c000 0x0 0x00000100>,
|
||||
<0x0 0xffe0d000 0x0 0x00000100>;
|
||||
reg-names = "csi_phy", "csi_host", "csi_adapt";
|
||||
clocks = <&clkc CLKID_CSI_ADAPT_CLK_COMP>,
|
||||
<&clkc CLKID_MIPI_CSI_PHY_CLK_COMP>;
|
||||
clock-names = "cts_csi_adapt_clk_composite",
|
||||
"cts_csi_phy_clk_composite";
|
||||
interrupts = <0 1 0>;
|
||||
interrupt-names = "csi_phy";
|
||||
};
|
||||
|
||||
gpio_keypad{
|
||||
compatible = "amlogic, gpio_keypad";
|
||||
status = "okay";
|
||||
@@ -1053,7 +1070,7 @@
|
||||
&i2c2 {
|
||||
status = "disabled";
|
||||
pinctrl-names="default";
|
||||
pinctrl-0=<&i2c2_master_pins2>;
|
||||
pinctrl-0=<&i2c2_master_pins3>;
|
||||
clock-frequency = <100000>;
|
||||
};
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ CONFIG_AMLOGIC_MEDIA_TVIN_HDMI=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_AFE=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_VBI=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_AVDETECT=y
|
||||
CONFIG_AMLOGIC_MEDIA_TVIN_CSI=y
|
||||
CONFIG_AMLOGIC_MEDIA_VIDEO_PROCESSOR=y
|
||||
CONFIG_AMLOGIC_V4L_VIDEO=y
|
||||
CONFIG_AMLOGIC_V4L_VIDEO2=y
|
||||
@@ -327,6 +328,7 @@ CONFIG_AMLOGIC_MEDIA_GDC=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE=y
|
||||
CONFIG_AMLOGIC_VM_DISABLE_VIDEOLAYER=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE_GC2145=y
|
||||
CONFIG_AMLOGIC_VIDEO_CAPTURE_OV5640=y
|
||||
CONFIG_AMLOGIC_DTV_DEMOD=y
|
||||
CONFIG_AMLOGIC_MMC=y
|
||||
CONFIG_AMLOGIC_NAND=y
|
||||
|
||||
@@ -7,9 +7,11 @@ amlcamera-objs := common/plat_ctrl.o
|
||||
amlvm-objs := common/vm.o
|
||||
cam_prober-objs := common/cam_prober.o common/config_parser.o
|
||||
gc2145dri-objs := gc2145.o
|
||||
ov5640dri-objs := ov5640.o
|
||||
|
||||
obj-y += amlflash.o
|
||||
obj-y += amlvm.o
|
||||
obj-y += amlcamera.o
|
||||
obj-y += cam_prober.o
|
||||
obj-$(CONFIG_AMLOGIC_VIDEO_CAPTURE_GC2145) += gc2145dri.o
|
||||
obj-$(CONFIG_AMLOGIC_VIDEO_CAPTURE_OV5640) += ov5640dri.o
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <linux/amlogic/cpu_version.h>
|
||||
#include <linux/amlogic/media/old_cpu_version.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#define CONFIG_ARCH_MESON8
|
||||
|
||||
@@ -1049,7 +1050,8 @@ void aml_cam_init(struct aml_cam_info_s *cam_dev)
|
||||
if (get_cpu_type() == MESON_CPU_MAJOR_ID_GXBB)
|
||||
GXBB_cam_enable_clk();
|
||||
else if ((get_cpu_type() == MESON_CPU_MAJOR_ID_G12A) ||
|
||||
(get_cpu_type() == MESON_CPU_MAJOR_ID_G12B))
|
||||
(get_cpu_type() == MESON_CPU_MAJOR_ID_G12B) ||
|
||||
(is_meson_sm1_cpu()))
|
||||
GX12_cam_enable_clk();
|
||||
else
|
||||
cam_enable_clk(cam_dev->mclk, cam_dev->spread_spectrum);
|
||||
@@ -1109,7 +1111,8 @@ void aml_cam_uninit(struct aml_cam_info_s *cam_dev)
|
||||
if (get_cpu_type() == MESON_CPU_MAJOR_ID_GXBB)
|
||||
GXBB_cam_disable_clk(cam_dev->spread_spectrum);
|
||||
else if ((get_cpu_type() == MESON_CPU_MAJOR_ID_G12A) ||
|
||||
(get_cpu_type() == MESON_CPU_MAJOR_ID_G12B))
|
||||
(get_cpu_type() == MESON_CPU_MAJOR_ID_G12B) ||
|
||||
(is_meson_sm1_cpu()))
|
||||
GX12_cam_disable_clk(cam_dev->spread_spectrum);
|
||||
else
|
||||
cam_disable_clk(cam_dev->spread_spectrum);
|
||||
@@ -1196,6 +1199,15 @@ static int fill_cam_dev(struct device_node *p_node,
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
cam_dev->cam_vdd = of_get_named_gpio(p_node, "camvdd-gpios", 0);
|
||||
pr_info("cam_dev->cam_vdd = %d\n", cam_dev->cam_vdd);
|
||||
if (cam_dev->cam_vdd == 0)
|
||||
pr_info("%s: failed to map gpio_cam_vdd !\n", cam_dev->name);
|
||||
ret = gpio_request(cam_dev->cam_vdd, "camera");
|
||||
if (ret < 0)
|
||||
pr_info("aml_cam_init cam_vdd request failed\n");
|
||||
gpio_direction_output(cam_dev->cam_vdd, 0);
|
||||
|
||||
cam_dev->pwdn_pin = of_get_named_gpio(p_node, "gpio_pwdn-gpios", 0);
|
||||
if (cam_dev->pwdn_pin == 0) {
|
||||
pr_info("%s: failed to map gpio_pwdn !\n", cam_dev->name);
|
||||
|
||||
3346
drivers/amlogic/media/camera/ov5640.c
Normal file
3346
drivers/amlogic/media/camera/ov5640.c
Normal file
File diff suppressed because it is too large
Load Diff
4121
drivers/amlogic/media/camera/ov5640_firmware.h
Normal file
4121
drivers/amlogic/media/camera/ov5640_firmware.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,4 +21,5 @@ source "drivers/amlogic/media/vin/tvin/bt656/Kconfig"
|
||||
source "drivers/amlogic/media/vin/tvin/hdmirx_ext/Kconfig"
|
||||
source "drivers/amlogic/media/vin/tvin/hdmirx/Kconfig"
|
||||
source "drivers/amlogic/media/vin/tvin/tvafe/Kconfig"
|
||||
source "drivers/amlogic/media/vin/tvin/csi/Kconfig"
|
||||
endif
|
||||
|
||||
@@ -13,4 +13,5 @@ obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_BT656) += bt656/
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_EXT) += hdmirx_ext/
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_HDMI) += hdmirx/
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_AFE) += tvafe/
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_CSI) += csi/
|
||||
|
||||
|
||||
11
drivers/amlogic/media/vin/tvin/csi/Kconfig
Normal file
11
drivers/amlogic/media/vin/tvin/csi/Kconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# CSI Device Driver Configuration
|
||||
#
|
||||
|
||||
|
||||
config AMLOGIC_MEDIA_TVIN_CSI
|
||||
tristate "Amlogic CSI device driver"
|
||||
default n
|
||||
help
|
||||
CSI device driver.
|
||||
|
||||
2
drivers/amlogic/media/vin/tvin/csi/Makefile
Normal file
2
drivers/amlogic/media/vin/tvin/csi/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
obj-$(CONFIG_AMLOGIC_MEDIA_TVIN_CSI) += tvin_csi.o
|
||||
tvin_csi-objs := csi.o mipi_hw.o
|
||||
558
drivers/amlogic/media/vin/tvin/csi/csi.c
Normal file
558
drivers/amlogic/media/vin/tvin/csi/csi.c
Normal file
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
* drivers/amlogic/media/vin/tvin/csi/csi.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/major.h>
|
||||
|
||||
#include <linux/amlogic/media/mipi/am_mipi_csi2.h>
|
||||
|
||||
#include "../tvin_global.h"
|
||||
#include "../vdin/vdin_regs.h"
|
||||
#include "../vdin/vdin_drv.h"
|
||||
#include "../vdin/vdin_ctl.h"
|
||||
#include "../tvin_format_table.h"
|
||||
#include "../tvin_frontend.h"
|
||||
#include "csi.h"
|
||||
|
||||
#define DEV_NAME "amvdec_csi"
|
||||
#define DRV_NAME "amvdec_csi"
|
||||
#define CLS_NAME "amvdec_csi"
|
||||
#define MOD_NAME "amvdec_csi"
|
||||
|
||||
#define CSI_MAX_DEVS 1
|
||||
#define WDG_STEP_JIFFIES 10
|
||||
|
||||
static dev_t amcsi_devno;
|
||||
static struct class *amcsi_clsp;
|
||||
|
||||
static void init_csi_dec_parameter(struct amcsi_dev_s *devp)
|
||||
{
|
||||
enum tvin_sig_fmt_e fmt;
|
||||
const struct tvin_format_s *fmt_info_p;
|
||||
|
||||
pr_info("init_csi_dec_parameter.\n");
|
||||
fmt = devp->para.fmt;
|
||||
fmt_info_p =
|
||||
(struct tvin_format_s *)tvin_get_fmt_info(fmt);
|
||||
devp->para.v_active = 1080;
|
||||
devp->para.h_active = 1920;
|
||||
devp->para.hsync_phase = 0;
|
||||
devp->para.vsync_phase = 0;
|
||||
devp->para.hs_bp = 0;
|
||||
devp->para.vs_bp = 0;
|
||||
devp->para.csi_hw_info.lanes = 2;
|
||||
}
|
||||
|
||||
static void reset_btcsi_module(void)
|
||||
{
|
||||
DPRINT("%s, %d\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
static void reinit_csi_dec(struct amcsi_dev_s *devp)
|
||||
{
|
||||
DPRINT("%s, %d\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
static void start_amvdec_csi(struct amcsi_dev_s *devp)
|
||||
{
|
||||
enum tvin_port_e port = devp->para.port;
|
||||
|
||||
pr_info("start_amvdec_csi.\n");
|
||||
|
||||
if (devp->dec_status & TVIN_AMCSI_RUNNING) {
|
||||
pr_info("%s csi have started alreadly.\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
devp->dec_status = TVIN_AMCSI_RUNNING;
|
||||
pr_info("start_amvdec_csi port = %x\n", port);
|
||||
if (port == TVIN_PORT_MIPI) {
|
||||
init_csi_dec_parameter(devp);
|
||||
reinit_csi_dec(devp);
|
||||
} else {
|
||||
devp->para.fmt = TVIN_SIG_FMT_NULL;
|
||||
devp->para.port = TVIN_PORT_NULL;
|
||||
DPRINT("%s: input is not selected, please try again\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
devp->dec_status = TVIN_AMCSI_RUNNING;
|
||||
}
|
||||
|
||||
static void stop_amvdec_csi(struct amcsi_dev_s *devp)
|
||||
{
|
||||
if (devp->dec_status & TVIN_AMCSI_RUNNING) {
|
||||
reset_btcsi_module();
|
||||
devp->dec_status = TVIN_AMCSI_STOP;
|
||||
} else
|
||||
DPRINT("%s device is not started yet\n", __func__);
|
||||
}
|
||||
|
||||
static bool amcsi_check_skip_frame(struct tvin_frontend_s *fe)
|
||||
{
|
||||
struct amcsi_dev_s *devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
|
||||
if (devp->csi_parm.skip_frames > 0) {
|
||||
devp->csi_parm.skip_frames--;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
int amcsi_support(struct tvin_frontend_s *fe, enum tvin_port_e port)
|
||||
{
|
||||
if (port != TVIN_PORT_MIPI) {
|
||||
DPRINT("this is not MIPI port\n");
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int amcsi_open(struct inode *node, struct file *file)
|
||||
{
|
||||
struct amcsi_dev_s *csi_devp;
|
||||
|
||||
csi_devp = container_of(node->i_cdev, struct amcsi_dev_s, cdev);
|
||||
file->private_data = csi_devp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amcsi_release(struct inode *node, struct file *file)
|
||||
{
|
||||
file->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations amcsi_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amcsi_open,
|
||||
.release = amcsi_release,
|
||||
};
|
||||
|
||||
void amcsi_start(struct tvin_frontend_s *fe, enum tvin_sig_fmt_e fmt)
|
||||
{
|
||||
struct amcsi_dev_s *csi_devp;
|
||||
|
||||
csi_devp = container_of(fe, struct amcsi_dev_s, frontend);
|
||||
start_amvdec_csi(csi_devp);
|
||||
}
|
||||
|
||||
static void amcsi_stop(struct tvin_frontend_s *fe, enum tvin_port_e port)
|
||||
{
|
||||
struct amcsi_dev_s *devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
|
||||
if (port != TVIN_PORT_MIPI) {
|
||||
DPRINT("%s:invaild port %d.\n", __func__, port);
|
||||
return;
|
||||
}
|
||||
stop_amvdec_csi(devp);
|
||||
}
|
||||
|
||||
void amcsi_get_sig_property(struct tvin_frontend_s *fe,
|
||||
struct tvin_sig_property_s *prop)
|
||||
{
|
||||
struct amcsi_dev_s *devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
|
||||
prop->color_format = devp->para.cfmt;
|
||||
prop->dest_cfmt = devp->para.dfmt;
|
||||
pr_info("devp->para.cfmt=%d, devp->para.dfmt=%d\n",
|
||||
devp->para.cfmt, devp->para.dfmt);
|
||||
prop->decimation_ratio = 0;
|
||||
}
|
||||
|
||||
int amcsi_isr(struct tvin_frontend_s *fe, unsigned int hcnt)
|
||||
{
|
||||
struct amcsi_dev_s *devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
unsigned int data1 = 0;
|
||||
struct am_csi2_frame_s frame;
|
||||
|
||||
frame.w = READ_CSI_ADPT_REG_BIT(CSI2_PIC_SIZE_STAT, 0, 16);
|
||||
frame.h = READ_CSI_ADPT_REG_BIT(CSI2_PIC_SIZE_STAT, 16, 16);
|
||||
frame.err = READ_CSI_ADPT_REG(CSI2_ERR_STAT0);
|
||||
data1 = READ_CSI_ADPT_REG(CSI2_GEN_STAT0);
|
||||
|
||||
if (frame.err) {
|
||||
DPRINT("%s,error---pixel cnt:%d, line cnt:%d\n",
|
||||
__func__, frame.w, frame.h);
|
||||
DPRINT("error state:0x%x.,status:0x%x\n",
|
||||
frame.err, data1);
|
||||
devp->overflow_cnt++;
|
||||
WRITE_CSI_ADPT_REG(CSI2_ERR_STAT0, 0);
|
||||
}
|
||||
if (devp->overflow_cnt > 4) {
|
||||
DPRINT("should reset mipi\n");
|
||||
devp->overflow_cnt = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
devp->reset = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t csi_attr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
ssize_t len = 0;
|
||||
struct amcsi_dev_s *csi_devp;
|
||||
int i;
|
||||
|
||||
csi_devp = dev_get_drvdata(dev);
|
||||
if (csi_devp->dec_status != TVIN_AMCSI_RUNNING) {
|
||||
len += sprintf(buf+len, "csi does not start\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
len += sprintf(buf+len, "csi parameters below\n");
|
||||
len +=
|
||||
sprintf(buf+len, "\tlanes=%d, channel=%d\n"
|
||||
"\tclk_channel=%d\n"
|
||||
"\tmode=%d, clock_lane_mode=%d, active_pixel=%d\n"
|
||||
"\tactive_line=%d, frame_size=%d, ui_val=%dns\n"
|
||||
"\ths_freq=%dhz, urgent=%d\n",
|
||||
csi_devp->csi_parm.lanes,
|
||||
csi_devp->csi_parm.channel,
|
||||
csi_devp->csi_parm.clk_channel,
|
||||
csi_devp->csi_parm.mode,
|
||||
csi_devp->csi_parm.clock_lane_mode,
|
||||
csi_devp->csi_parm.active_pixel,
|
||||
csi_devp->csi_parm.active_line,
|
||||
csi_devp->csi_parm.frame_size,
|
||||
csi_devp->csi_parm.ui_val,
|
||||
csi_devp->csi_parm.hs_freq,
|
||||
csi_devp->csi_parm.urgent);
|
||||
|
||||
len += sprintf(buf+len, "csi adapter register below\n");
|
||||
for (i = CSI_ADPT_START_REG; i <= CSI_ADPT_END_REG; i++) {
|
||||
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
|
||||
i - CSI_ADPT_START_REG, READ_CSI_ADPT_REG(i));
|
||||
}
|
||||
|
||||
len += sprintf(buf+len, "csi phy register below\n");
|
||||
for (i = CSI_PHY_START_REG; i <= CSI_PHY_END_REG; i++) {
|
||||
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
|
||||
i, READ_CSI_PHY_REG(i));
|
||||
}
|
||||
|
||||
len += sprintf(buf+len, "csi host register below\n");
|
||||
for (i = CSI_HST_START_REG; i <= CSI_HST_END_REG; i++) {
|
||||
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
|
||||
i << 2, READ_CSI_HST_REG(i));
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t csi_attr_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct amcsi_dev_s *csi_devp;
|
||||
|
||||
unsigned int n = 0;
|
||||
|
||||
char *buf_orig, *ps, *token;
|
||||
char *parm[6] = {NULL};
|
||||
|
||||
if (!buf)
|
||||
return len;
|
||||
buf_orig = kstrdup(buf, GFP_KERNEL);
|
||||
csi_devp = dev_get_drvdata(dev);
|
||||
|
||||
ps = buf_orig;
|
||||
while (1) {
|
||||
if (n >= ARRAY_SIZE(parm)) {
|
||||
pr_info("parm array overflow, n=%d\n", n);
|
||||
kfree(buf_orig);
|
||||
return len;
|
||||
}
|
||||
token = strsep(&ps, "\n");
|
||||
if (token == NULL)
|
||||
break;
|
||||
if (*token == '\0')
|
||||
continue;
|
||||
parm[n++] = token;
|
||||
}
|
||||
|
||||
if (strcmp(parm[0], "reset") == 0) {
|
||||
pr_info("reset\n");
|
||||
am_mipi_csi2_init(&csi_devp->csi_parm);
|
||||
} else if (strcmp(parm[0], "init") == 0) {
|
||||
pr_info("init mipi measure clock\n");
|
||||
init_am_mipi_csi2_clock();
|
||||
} else if (strcmp(parm[0], "min") == 0) {
|
||||
csi_devp->min_frmrate =
|
||||
kstrtol(parm[1], 16, NULL);
|
||||
if ((csi_devp->min_frmrate * WDG_STEP_JIFFIES) < HZ)
|
||||
csi_devp->min_frmrate = HZ/WDG_STEP_JIFFIES;
|
||||
pr_info("min_frmrate=%d\n", csi_devp->min_frmrate);
|
||||
}
|
||||
|
||||
kfree(buf_orig);
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(hw_info, 0664, csi_attr_show, csi_attr_store);
|
||||
|
||||
static int amcsi_feopen(struct tvin_frontend_s *fe, enum tvin_port_e port)
|
||||
{
|
||||
struct amcsi_dev_s *csi_devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
struct vdin_parm_s *parm = fe->private_data;
|
||||
|
||||
if (port != TVIN_PORT_MIPI) {
|
||||
DPRINT("[mipi..]%s:invaild port %d.\n", __func__, port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!memcpy(&csi_devp->para, parm,
|
||||
sizeof(struct vdin_parm_s))) {
|
||||
DPRINT("[mipi..]%s memcpy error.\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
init_am_mipi_csi2_clock();
|
||||
|
||||
csi_devp->para.port = port;
|
||||
|
||||
memcpy(&csi_devp->csi_parm,
|
||||
&parm->csi_hw_info, sizeof(struct csi_parm_s));
|
||||
csi_devp->csi_parm.skip_frames = parm->skip_count;
|
||||
|
||||
csi_devp->reset = 0;
|
||||
csi_devp->reset_count = 0;
|
||||
|
||||
cal_csi_para(&csi_devp->csi_parm);
|
||||
am_mipi_csi2_init(&csi_devp->csi_parm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amcsi_feclose(struct tvin_frontend_s *fe)
|
||||
{
|
||||
struct amcsi_dev_s *devp =
|
||||
container_of(fe, struct amcsi_dev_s, frontend);
|
||||
enum tvin_port_e port = devp->para.port;
|
||||
|
||||
if (port != TVIN_PORT_MIPI) {
|
||||
DPRINT("[mipi..]%s:invaild port %d.\n", __func__, port);
|
||||
return;
|
||||
}
|
||||
|
||||
devp->reset = 0;
|
||||
devp->reset_count = 0;
|
||||
|
||||
am_mipi_csi2_uninit();
|
||||
|
||||
memset(&devp->para, 0, sizeof(struct vdin_parm_s));
|
||||
}
|
||||
|
||||
static struct tvin_state_machine_ops_s amcsi_machine_ops = {
|
||||
.nosig = NULL,
|
||||
.fmt_changed = NULL,
|
||||
.get_fmt = NULL,
|
||||
.fmt_config = NULL,
|
||||
.adc_cal = NULL,
|
||||
.pll_lock = NULL,
|
||||
.get_sig_property = amcsi_get_sig_property,
|
||||
.vga_set_param = NULL,
|
||||
.vga_get_param = NULL,
|
||||
.check_frame_skip = amcsi_check_skip_frame,
|
||||
};
|
||||
|
||||
static struct tvin_decoder_ops_s amcsi_decoder_ops_s = {
|
||||
.support = amcsi_support,
|
||||
.open = amcsi_feopen,
|
||||
.start = amcsi_start,
|
||||
.stop = amcsi_stop,
|
||||
.close = amcsi_feclose,
|
||||
.decode_isr = amcsi_isr,
|
||||
};
|
||||
|
||||
static int csi_add_cdev(struct cdev *cdevp,
|
||||
const struct file_operations *fops, int minor)
|
||||
{
|
||||
int ret;
|
||||
dev_t devno = MKDEV(MAJOR(amcsi_devno), minor);
|
||||
|
||||
cdev_init(cdevp, fops);
|
||||
cdevp->owner = THIS_MODULE;
|
||||
ret = cdev_add(cdevp, devno, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void csi_delete_device(int minor)
|
||||
{
|
||||
dev_t devno = MKDEV(MAJOR(amcsi_devno), minor);
|
||||
|
||||
device_destroy(amcsi_clsp, devno);
|
||||
}
|
||||
|
||||
static int amvdec_csi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
int id = 0;
|
||||
struct amcsi_dev_s *devp = NULL;
|
||||
|
||||
devp = kmalloc(sizeof(struct amcsi_dev_s), GFP_KERNEL);
|
||||
if (!devp) {
|
||||
ret = -1;
|
||||
goto fail_kmalloc_dev;
|
||||
}
|
||||
memset(devp, 0, sizeof(struct amcsi_dev_s));
|
||||
|
||||
ret = csi_add_cdev(&devp->cdev, &amcsi_fops, 0);
|
||||
if (ret != 0) {
|
||||
pr_err("%s: failed to add cdev\n", __func__);
|
||||
goto fail_add_cdev;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "csi_id", &id);
|
||||
if (ret != 0) {
|
||||
pr_err("%s: don't find csi_id.\n", __func__);
|
||||
goto fail_add_cdev;
|
||||
}
|
||||
pdev->id = id;
|
||||
|
||||
sprintf(devp->frontend.name, "%s", DEV_NAME);
|
||||
tvin_frontend_init(&devp->frontend, &amcsi_decoder_ops_s,
|
||||
&amcsi_machine_ops, pdev->id);
|
||||
tvin_reg_frontend(&devp->frontend);
|
||||
devp->pdev = pdev;
|
||||
|
||||
platform_set_drvdata(pdev, devp);
|
||||
|
||||
am_mipi_csi2_para_init(pdev);
|
||||
|
||||
pr_info("amvdec_csi probe ok.\n");
|
||||
return ret;
|
||||
|
||||
fail_add_cdev:
|
||||
kfree(devp);
|
||||
fail_kmalloc_dev:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amvdec_csi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct amcsi_dev_s *devp;
|
||||
|
||||
devp = (struct amcsi_dev_s *)platform_get_drvdata(pdev);
|
||||
|
||||
tvin_unreg_frontend(&devp->frontend);
|
||||
device_remove_file(devp->dev, &dev_attr_hw_info);
|
||||
deinit_am_mipi_csi2_clock();
|
||||
csi_delete_device(pdev->id);
|
||||
cdev_del(&devp->cdev);
|
||||
dev_set_drvdata(devp->dev, NULL);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(devp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id csi_dt_match[] = {
|
||||
{
|
||||
.compatible = "amlogic, amvdec_csi",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver amvdec_csi_driver = {
|
||||
.probe = amvdec_csi_probe,
|
||||
.remove = amvdec_csi_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = csi_dt_match,
|
||||
}
|
||||
};
|
||||
|
||||
static int __init amvdec_csi_init_module(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pr_info("amvdec_csi module: init.\n");
|
||||
ret = alloc_chrdev_region(&amcsi_devno, 0,
|
||||
CSI_MAX_DEVS, DEV_NAME);
|
||||
if (ret != 0) {
|
||||
pr_err("%s:failed to alloc major number\n",
|
||||
__func__);
|
||||
goto fail_alloc_cdev_region;
|
||||
}
|
||||
|
||||
pr_info("%s:major %d\n", __func__, MAJOR(amcsi_devno));
|
||||
|
||||
amcsi_clsp = class_create(THIS_MODULE, CLS_NAME);
|
||||
if (IS_ERR(amcsi_clsp)) {
|
||||
ret = PTR_ERR(amcsi_clsp);
|
||||
pr_err("%s:failed to create class\n", __func__);
|
||||
goto fail_class_create;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&amvdec_csi_driver);
|
||||
if (ret) {
|
||||
pr_err("failed to register amvdec_csi driver\n");
|
||||
goto fail_pdrv_register;
|
||||
}
|
||||
|
||||
pr_info("amvdec_csi module: init. ok\n");
|
||||
return 0;
|
||||
|
||||
fail_pdrv_register:
|
||||
class_destroy(amcsi_clsp);
|
||||
fail_class_create:
|
||||
unregister_chrdev_region(amcsi_devno, CSI_MAX_DEVS);
|
||||
fail_alloc_cdev_region:
|
||||
pr_err("amvdec_csi module: init failed, ret=%d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit amvdec_csi_exit_module(void)
|
||||
{
|
||||
pr_info("amvdec_csi module remove.\n");
|
||||
DPRINT("%s, %d\n", __func__, __LINE__);
|
||||
class_destroy(amcsi_clsp);
|
||||
unregister_chrdev_region(amcsi_devno, CSI_MAX_DEVS);
|
||||
platform_driver_unregister(&amvdec_csi_driver);
|
||||
}
|
||||
|
||||
module_init(amvdec_csi_init_module);
|
||||
module_exit(amvdec_csi_exit_module);
|
||||
|
||||
MODULE_DESCRIPTION("AMLOGIC CSI input driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("1.0.0");
|
||||
134
drivers/amlogic/media/vin/tvin/csi/csi.h
Normal file
134
drivers/amlogic/media/vin/tvin/csi/csi.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* drivers/amlogic/media/vin/tvin/csi/csi.h
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __CSI_INPUT_H
|
||||
#define __CSI_INPUT_H
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/amlogic/media/frame_provider/tvin/tvin_v4l2.h>
|
||||
#include <linux/amlogic/media/mipi/am_mipi_csi2.h>
|
||||
#include "../tvin_frontend.h"
|
||||
#include "../tvin_global.h"
|
||||
#ifdef PRINT_DEBUG_INFO
|
||||
#define DPRINT(...) printk(__VA_ARGS__)
|
||||
#else
|
||||
#define DPRINT(...)
|
||||
#endif
|
||||
|
||||
#define CSI2_CFG_USE_NULL_PACKET 0
|
||||
#define CSI2_CFG_BUFFER_PIC_SIZE 1
|
||||
#define CSI2_CFG_422TO444_MODE 1
|
||||
#define CSI2_CFG_INV_FIELD 0
|
||||
#define CSI2_CFG_INTERLACE_EN 0
|
||||
#define CSI2_CFG_COLOR_EXPAND 0
|
||||
#define CSI2_CFG_ALL_TO_MEM 0
|
||||
|
||||
|
||||
/*mipi-csi2 phy registers*/
|
||||
#define MIPI_PHY_CTRL 0x00
|
||||
#define MIPI_PHY_CLK_LANE_CTRL 0x04
|
||||
#define MIPI_PHY_DATA_LANE_CTRL 0x08
|
||||
#define MIPI_PHY_DATA_LANE_CTRL1 0x0C
|
||||
#define MIPI_PHY_TCLK_MISS 0x10
|
||||
#define MIPI_PHY_TCLK_SETTLE 0x14
|
||||
#define MIPI_PHY_THS_EXIT 0x18
|
||||
#define MIPI_PHY_THS_SKIP 0x1C
|
||||
#define MIPI_PHY_THS_SETTLE 0x20
|
||||
#define MIPI_PHY_TINIT 0x24
|
||||
#define MIPI_PHY_TULPS_C 0x28
|
||||
#define MIPI_PHY_TULPS_S 0x2C
|
||||
#define MIPI_PHY_TMBIAS 0x30
|
||||
#define MIPI_PHY_TLP_EN_W 0x34
|
||||
#define MIPI_PHY_TLPOK 0x38
|
||||
#define MIPI_PHY_TWD_INIT 0x3C
|
||||
#define MIPI_PHY_TWD_HS 0x40
|
||||
#define MIPI_PHY_AN_CTRL0 0x44
|
||||
#define MIPI_PHY_AN_CTRL1 0x48
|
||||
#define MIPI_PHY_AN_CTRL2 0x4C
|
||||
#define MIPI_PHY_CLK_LANE_STS 0x50
|
||||
#define MIPI_PHY_DATA_LANE0_STS 0x54
|
||||
#define MIPI_PHY_DATA_LANE1_STS 0x58
|
||||
#define MIPI_PHY_DATA_LANE2_STS 0x5C
|
||||
#define MIPI_PHY_DATA_LANE3_STS 0x60
|
||||
#define MIPI_PHY_INT_STS 0x6C
|
||||
#define MIPI_PHY_MUX_CTRL0 (0x61 << 2)
|
||||
#define MIPI_PHY_MUX_CTRL1 (0x62 << 2)
|
||||
|
||||
/*mipi-csi2 host registers*/
|
||||
#define CSI2_HOST_CSI2_RESETN 0x010
|
||||
#define CSI2_HOST_N_LANES 0x004
|
||||
#define CSI2_HOST_MASK1 0x028
|
||||
#define CSI2_HOST_MASK2 0x02c
|
||||
|
||||
/*adapt frontend registers*/
|
||||
#define CSI2_CLK_RESET (0x00 << 2)
|
||||
#define CSI2_GEN_CTRL0 (0x01 << 2)
|
||||
#define CSI2_GEN_CTRL1 (0x02 << 2)
|
||||
#define CSI2_X_START_END_ISP (0x03 << 2)
|
||||
#define CSI2_Y_START_END_ISP (0x04 << 2)
|
||||
#define CSI2_INTERRUPT_CTRL_STAT (0x14 << 2)
|
||||
#define CSI2_GEN_STAT0 (0x20 << 2)
|
||||
#define CSI2_ERR_STAT0 (0x21 << 2)
|
||||
#define CSI2_PIC_SIZE_STAT (0x22 << 2)
|
||||
|
||||
enum amcsi_status_e {
|
||||
TVIN_AMCSI_STOP,
|
||||
TVIN_AMCSI_RUNNING,
|
||||
TVIN_AMCSI_START,
|
||||
};
|
||||
|
||||
struct amcsi_dev_s {
|
||||
int index;
|
||||
dev_t devt;
|
||||
struct cdev cdev;
|
||||
struct device *dev;
|
||||
struct platform_device *pdev;
|
||||
unsigned int overflow_cnt;
|
||||
enum amcsi_status_e dec_status;
|
||||
struct vdin_parm_s para;
|
||||
struct csi_parm_s csi_parm;
|
||||
unsigned char reset;
|
||||
unsigned int reset_count;
|
||||
unsigned int irq_num;
|
||||
struct tvin_frontend_s frontend;
|
||||
unsigned int period;
|
||||
unsigned int min_frmrate;
|
||||
struct timer_list t;
|
||||
void __iomem *csi_adapt_addr;
|
||||
};
|
||||
|
||||
struct csi_adapt {
|
||||
struct platform_device *p_dev;
|
||||
struct resource csi_phy_reg;
|
||||
struct resource csi_host_reg;
|
||||
struct resource csi_adapt_reg;
|
||||
void __iomem *csi_phy;
|
||||
void __iomem *csi_host;
|
||||
void __iomem *csi_adapt;
|
||||
struct clk *csi_clk;
|
||||
struct clk *adapt_clk;
|
||||
};
|
||||
|
||||
void deinit_am_mipi_csi2_clock(void);
|
||||
void am_mipi_csi2_para_init(struct platform_device *pdev);
|
||||
void WRITE_CSI_ADPT_REG(int addr, uint32_t val);
|
||||
uint32_t READ_CSI_PHY_REG(int addr);
|
||||
uint32_t READ_CSI_HST_REG(int addr);
|
||||
uint32_t READ_CSI_ADPT_REG(int addr);
|
||||
uint32_t READ_CSI_ADPT_REG_BIT(int addr,
|
||||
const uint32_t _start, const uint32_t _len);
|
||||
|
||||
#endif
|
||||
385
drivers/amlogic/media/vin/tvin/csi/mipi_hw.c
Normal file
385
drivers/amlogic/media/vin/tvin/csi/mipi_hw.c
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* drivers/amlogic/media/vin/tvin/csi/mipi_hw.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/amlogic/media/mipi/am_mipi_csi2.h>
|
||||
#include <linux/amlogic/media/frame_provider/tvin/tvin_v4l2.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/amlogic/power_ctrl.h>
|
||||
#include "csi.h"
|
||||
|
||||
#define HHI_CSI_PHY 0xff63c000
|
||||
#define HHI_CSI_PHY_CNTL0 (0x0d3 << 2)
|
||||
#define HHI_CSI_PHY_CNTL1 (0x0d4 << 2)
|
||||
#define HHI_CSI_PHY_CNTL2 (0x0d5 << 2)
|
||||
|
||||
static struct csi_adapt g_csi;
|
||||
|
||||
static inline uint32_t WRITE_CBUS_REG(void __iomem *base, uint32_t addr,
|
||||
unsigned long val)
|
||||
{
|
||||
void __iomem *io_addr = base + addr;
|
||||
|
||||
__raw_writel(val, io_addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void phy_update_wr_reg_bits(unsigned int reg,
|
||||
unsigned int mask, unsigned int val)
|
||||
{
|
||||
unsigned int tmp, orig;
|
||||
void __iomem *base = g_csi.csi_phy;
|
||||
|
||||
if (base != NULL) {
|
||||
orig = __raw_readl(base + reg);
|
||||
tmp = orig & ~mask;
|
||||
tmp |= val & mask;
|
||||
__raw_writel(tmp, base + reg);
|
||||
}
|
||||
}
|
||||
|
||||
inline void WRITE_CSI_PHY_REG_BITS(unsigned int adr, unsigned int val,
|
||||
unsigned int start, unsigned int len)
|
||||
{
|
||||
phy_update_wr_reg_bits(adr,
|
||||
((1 << len) - 1) << start, val << start);
|
||||
}
|
||||
|
||||
inline void WRITE_CSI_PHY_REG(int addr, uint32_t val)
|
||||
{
|
||||
void __iomem *reg_addr = g_csi.csi_phy + addr;
|
||||
|
||||
__raw_writel(val, reg_addr);
|
||||
}
|
||||
|
||||
inline uint32_t READ_CSI_PHY_REG(int addr)
|
||||
{
|
||||
uint32_t val;
|
||||
void __iomem *reg_addr = g_csi.csi_phy + addr;
|
||||
|
||||
val = __raw_readl(reg_addr);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline void WRITE_CSI_HST_REG(int addr, uint32_t val)
|
||||
{
|
||||
void __iomem *reg_addr = g_csi.csi_host + addr;
|
||||
|
||||
__raw_writel(val, reg_addr);
|
||||
}
|
||||
|
||||
inline uint32_t READ_CSI_HST_REG(int addr)
|
||||
{
|
||||
uint32_t val;
|
||||
void __iomem *reg_addr = g_csi.csi_host + addr;
|
||||
|
||||
val = __raw_readl(reg_addr);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline void WRITE_CSI_ADPT_REG(int addr, uint32_t val)
|
||||
{
|
||||
void __iomem *reg_addr = g_csi.csi_adapt + addr;
|
||||
|
||||
__raw_writel(val, reg_addr);
|
||||
}
|
||||
|
||||
inline uint32_t READ_CSI_ADPT_REG(int addr)
|
||||
{
|
||||
uint32_t val;
|
||||
void __iomem *reg_addr = g_csi.csi_adapt + addr;
|
||||
|
||||
val = __raw_readl(reg_addr);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline uint32_t READ_CSI_ADPT_REG_BIT(int addr,
|
||||
const uint32_t _start, const uint32_t _len)
|
||||
{
|
||||
void __iomem *reg_addr = g_csi.csi_adapt + addr;
|
||||
|
||||
return ((__raw_readl(reg_addr) >> (_start)) & ((1L << (_len)) - 1));
|
||||
}
|
||||
|
||||
void mipi_phy_reg_wr_and_check(int32_t addr, int32_t data)
|
||||
{
|
||||
|
||||
#ifdef MIPI_WR_AND_CHECK
|
||||
uint32_t tmp;
|
||||
#endif
|
||||
WRITE_CSI_PHY_REG(addr, data);
|
||||
#ifdef MIPI_WR_AND_CHECK
|
||||
tmp = READ_CSI_PHY_REG(addr);
|
||||
if (tmp != data) {
|
||||
pr_info("%s, addr = %x, data = %x, read write unmatch\n",
|
||||
__func__, addr, tmp);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void init_am_mipi_csi2_clock(void)
|
||||
{
|
||||
int32_t rc = 0;
|
||||
uint32_t csi_rate = 200000000;
|
||||
uint32_t adapt_rate = 666666667;
|
||||
|
||||
g_csi.csi_clk = devm_clk_get(&(g_csi.p_dev->dev),
|
||||
"cts_csi_phy_clk_composite");
|
||||
if (IS_ERR(g_csi.csi_clk)) {
|
||||
pr_err("%s: cannot get clk_gate_csi !!!\n",
|
||||
__func__);
|
||||
g_csi.csi_clk = NULL;
|
||||
return;
|
||||
}
|
||||
clk_set_rate(g_csi.csi_clk, csi_rate);
|
||||
rc = clk_prepare_enable(g_csi.csi_clk);
|
||||
if (rc != 0) {
|
||||
pr_err("Failed to enable csi clk\n");
|
||||
return;
|
||||
}
|
||||
|
||||
g_csi.adapt_clk = devm_clk_get(&(g_csi.p_dev->dev),
|
||||
"cts_csi_adapt_clk_composite");
|
||||
if (IS_ERR(g_csi.adapt_clk)) {
|
||||
pr_err("%s: cannot get clk_gate_adapt !!!\n",
|
||||
__func__);
|
||||
g_csi.adapt_clk = NULL;
|
||||
return;
|
||||
}
|
||||
clk_set_rate(g_csi.adapt_clk, adapt_rate);
|
||||
rc = clk_prepare_enable(g_csi.adapt_clk);
|
||||
if (rc != 0) {
|
||||
pr_err("Failed to enable adapt clk\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void disable_am_mipi_csi2_clk(void)
|
||||
{
|
||||
clk_disable_unprepare(g_csi.csi_clk);
|
||||
clk_disable_unprepare(g_csi.adapt_clk);
|
||||
}
|
||||
|
||||
void deinit_am_mipi_csi2_clock(void)
|
||||
{
|
||||
if (g_csi.csi_clk != NULL) {
|
||||
devm_clk_put(&g_csi.p_dev->dev, g_csi.csi_clk);
|
||||
g_csi.csi_clk = NULL;
|
||||
}
|
||||
|
||||
if (g_csi.adapt_clk != NULL) {
|
||||
devm_clk_put(&g_csi.p_dev->dev, g_csi.adapt_clk);
|
||||
g_csi.adapt_clk = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void init_am_mipi_csi2_host(struct csi_parm_s *info)
|
||||
{
|
||||
pr_info("info->lanes = %d\n", info->lanes);
|
||||
WRITE_CSI_HST_REG(CSI2_HOST_CSI2_RESETN, 1);
|
||||
WRITE_CSI_HST_REG(CSI2_HOST_N_LANES, (info->lanes-1)&3);
|
||||
WRITE_CSI_HST_REG(CSI2_HOST_MASK1, 0x0);
|
||||
WRITE_CSI_HST_REG(CSI2_HOST_MASK2, 0x0);
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
static int init_am_mipi_csi2_adapter(struct csi_parm_s *info)
|
||||
{
|
||||
unsigned int data32;
|
||||
struct amcsi_dev_s *csi_devp;
|
||||
|
||||
csi_devp = container_of(info, struct amcsi_dev_s, csi_parm);
|
||||
WRITE_CSI_ADPT_REG(CSI2_CLK_RESET, 0);
|
||||
data32 = 0;
|
||||
data32 |= CSI2_CFG_422TO444_MODE << 21;
|
||||
data32 |= 0x1f << 16;
|
||||
data32 |= CSI2_CFG_COLOR_EXPAND << 15;
|
||||
data32 |= CSI2_CFG_BUFFER_PIC_SIZE << 11;
|
||||
data32 |= CSI2_CFG_USE_NULL_PACKET << 10;
|
||||
data32 |= CSI2_CFG_INV_FIELD << 9;
|
||||
data32 |= CSI2_CFG_INTERLACE_EN << 8;
|
||||
data32 |= CSI2_CFG_ALL_TO_MEM << 4;
|
||||
data32 |= 0xf;
|
||||
WRITE_CSI_ADPT_REG(CSI2_GEN_CTRL0, data32);
|
||||
|
||||
data32 = 0;
|
||||
data32 |= (csi_devp->para.h_active - 1) << 16;
|
||||
data32 |= 0 << 0;
|
||||
WRITE_CSI_ADPT_REG(CSI2_X_START_END_ISP, data32);
|
||||
|
||||
data32 = 0;
|
||||
data32 |= (csi_devp->para.v_active - 1) << 16;
|
||||
data32 |= 0 << 0;
|
||||
WRITE_CSI_ADPT_REG(CSI2_Y_START_END_ISP, data32);
|
||||
|
||||
WRITE_CSI_ADPT_REG(CSI2_INTERRUPT_CTRL_STAT, (1 << 1));
|
||||
|
||||
/*Enable clock*/
|
||||
data32 = 0;
|
||||
data32 |= 0 << 2;
|
||||
data32 |= 1 << 1;
|
||||
data32 |= 0 << 0;
|
||||
WRITE_CSI_ADPT_REG(CSI2_CLK_RESET, data32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void powerup_csi_analog(struct csi_parm_s *info)
|
||||
{
|
||||
void __iomem *base_addr;
|
||||
|
||||
base_addr = ioremap_nocache(HHI_CSI_PHY, 0x400);
|
||||
if (base_addr == NULL) {
|
||||
pr_err("%s: Failed to ioremap addr\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
WRITE_CBUS_REG(base_addr, HHI_CSI_PHY_CNTL0, 0x0b440585);
|
||||
WRITE_CBUS_REG(base_addr, HHI_CSI_PHY_CNTL1, 0x803f0000);
|
||||
WRITE_CBUS_REG(base_addr, HHI_CSI_PHY_CNTL2, 0xf002);
|
||||
|
||||
iounmap(base_addr);
|
||||
|
||||
power_ctrl_mempd0(1, 3, 6);
|
||||
}
|
||||
|
||||
static void powerdown_csi_analog(void)
|
||||
{
|
||||
power_ctrl_mempd0(0, 3, 6);
|
||||
}
|
||||
|
||||
static void init_am_mipi_phy(struct csi_parm_s *info)
|
||||
{
|
||||
uint32_t settle = 0;
|
||||
uint32_t ui_val = 0;
|
||||
uint32_t cycle_time = 5;
|
||||
|
||||
ui_val = 1000 / info->settle;
|
||||
if ((1000 % info->settle) != 0)
|
||||
ui_val += 1;
|
||||
settle = (85 + 145 + (16 * ui_val)) / 2;
|
||||
settle = settle / cycle_time;
|
||||
pr_info("settle = 0x%04x\n", settle);
|
||||
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_CLK_LANE_CTRL, 0x3d8);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TCLK_MISS, 0x9);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TCLK_SETTLE, 0x1f);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_THS_EXIT, 0x1f);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_THS_SKIP, 0xa);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_THS_SETTLE, settle);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TINIT, 0x4e20);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TMBIAS, 0x100);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TULPS_C, 0x1000);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TULPS_S, 0x100);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TLP_EN_W, 0x0c);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TLPOK, 0x100);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TWD_INIT, 0x400000);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_TWD_HS, 0x400000);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_DATA_LANE_CTRL, 0x0);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_DATA_LANE_CTRL1,
|
||||
0x3 | (0x1f << 2) | (0x3 << 7));
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_MUX_CTRL0, 0x00000123);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_MUX_CTRL1, 0x00000123);
|
||||
mipi_phy_reg_wr_and_check(MIPI_PHY_CTRL, 0);
|
||||
}
|
||||
|
||||
static void reset_am_mipi_csi2_host(void)
|
||||
{
|
||||
WRITE_CSI_HST_REG(CSI2_HOST_CSI2_RESETN, 0);
|
||||
}
|
||||
|
||||
static void reset_am_mipi_csi2_adapter(void)
|
||||
{
|
||||
WRITE_CSI_ADPT_REG(CSI2_CLK_RESET, 1);
|
||||
}
|
||||
|
||||
static void reset_am_mipi_phy(void)
|
||||
{
|
||||
WRITE_CSI_PHY_REG_BITS(MIPI_PHY_CTRL, 0x1, 31, 1);
|
||||
}
|
||||
|
||||
void am_mipi_csi2_para_init(struct platform_device *pdev)
|
||||
{
|
||||
struct resource rs;
|
||||
int i = 0;
|
||||
int rtn = -1;
|
||||
|
||||
memset(&g_csi, 0, sizeof(struct csi_adapt));
|
||||
g_csi.p_dev = pdev;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
rtn = of_address_to_resource(pdev->dev.of_node, i, &rs);
|
||||
if (rtn != 0) {
|
||||
pr_err("%s:Error idx %d get mipi csi reg resource\n",
|
||||
__func__, i);
|
||||
continue;
|
||||
} else {
|
||||
pr_info("%s: rs idx %d info: name: %s\n", __func__,
|
||||
i, rs.name);
|
||||
|
||||
if (strcmp(rs.name, "csi_phy") == 0) {
|
||||
g_csi.csi_phy_reg = rs;
|
||||
g_csi.csi_phy =
|
||||
ioremap_nocache(g_csi.csi_phy_reg.start,
|
||||
resource_size(&g_csi.csi_phy_reg));
|
||||
} else if (strcmp(rs.name, "csi_host") == 0) {
|
||||
g_csi.csi_host_reg = rs;
|
||||
g_csi.csi_host =
|
||||
ioremap_nocache(g_csi.csi_host_reg.start,
|
||||
resource_size(&g_csi.csi_host_reg));
|
||||
} else if (strcmp(rs.name, "csi_adapt") == 0) {
|
||||
g_csi.csi_adapt_reg = rs;
|
||||
g_csi.csi_adapt =
|
||||
ioremap_nocache(g_csi.csi_adapt_reg.start,
|
||||
resource_size(&g_csi.csi_adapt_reg));
|
||||
} else {
|
||||
pr_err("%s:Error match address\n", __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void am_mipi_csi2_init(struct csi_parm_s *info)
|
||||
{
|
||||
powerup_csi_analog(info);
|
||||
init_am_mipi_phy(info);
|
||||
init_am_mipi_csi2_host(info);
|
||||
init_am_mipi_csi2_adapter(info);
|
||||
}
|
||||
|
||||
void am_mipi_csi2_uninit(void)
|
||||
{
|
||||
reset_am_mipi_phy();
|
||||
reset_am_mipi_csi2_host();
|
||||
reset_am_mipi_csi2_adapter();
|
||||
disable_am_mipi_csi2_clk();
|
||||
powerdown_csi_analog();
|
||||
}
|
||||
|
||||
void cal_csi_para(struct csi_parm_s *info)
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user