diff options
Diffstat (limited to 'drivers/video/av8100')
-rw-r--r-- | drivers/video/av8100/Kconfig | 48 | ||||
-rw-r--r-- | drivers/video/av8100/Makefile | 10 | ||||
-rw-r--r-- | drivers/video/av8100/av8100.c | 4166 | ||||
-rw-r--r-- | drivers/video/av8100/av8100_regs.h | 346 | ||||
-rw-r--r-- | drivers/video/av8100/hdmi.c | 2479 | ||||
-rw-r--r-- | drivers/video/av8100/hdmi_loc.h | 75 |
6 files changed, 7124 insertions, 0 deletions
diff --git a/drivers/video/av8100/Kconfig b/drivers/video/av8100/Kconfig new file mode 100644 index 00000000000..40b9943aaa9 --- /dev/null +++ b/drivers/video/av8100/Kconfig @@ -0,0 +1,48 @@ +config AV8100 + tristate "AV8100 driver support(HDMI/CVBS)" + default n + help + Please enable this feature if hdmi/tvout driver support is required. + +config HDMI_AV8100_DEBUG + bool "HDMI and AV8100 debug messages" + default n + depends on AV8100 + ---help--- + Say Y here if you want the HDMI and AV8100 driver to + output debug messages. + +choice + prompt "AV8100 HW trig method" + default AV8100_HWTRIG_DSI_TE + +config AV8100_HWTRIG_INT + bool "AV8100 HW trig on INT" + depends on AV8100 + ---help--- + If you say Y here AV8100 will use HW triggering + from AV8100 INT to MCDE sync0. + +config AV8100_HWTRIG_I2SDAT3 + bool "AV8100 HW trig on I2SDAT3" + depends on AV8100 + ---help--- + If you say Y here AV8100 will use HW triggering + from AV8100 I2SDAT3 to MCDE sync1. + +config AV8100_HWTRIG_DSI_TE + bool "AV8100 HW trig on DSI" + depends on AV8100 + ---help--- + If you say Y here AV8100 will use HW triggering + using DSI TE polling between AV8100 and MCDE. + +config AV8100_HWTRIG_NONE + bool "AV8100 SW trig" + depends on AV8100 + ---help--- + If you say Y here AV8100 will use SW triggering + between AV8100 and MCDE. + +endchoice + diff --git a/drivers/video/av8100/Makefile b/drivers/video/av8100/Makefile new file mode 100644 index 00000000000..2d3028b18ca --- /dev/null +++ b/drivers/video/av8100/Makefile @@ -0,0 +1,10 @@ +# Make file for compiling and loadable module HDMI + +obj-$(CONFIG_AV8100) += av8100.o hdmi.o + +ifdef CONFIG_HDMI_AV8100_DEBUG +EXTRA_CFLAGS += -DDEBUG +endif + +clean-files := av8100.o hdmi.o built-in.o modules.order + diff --git a/drivers/video/av8100/av8100.c b/drivers/video/av8100/av8100.c new file mode 100644 index 00000000000..d5d159079c3 --- /dev/null +++ b/drivers/video/av8100/av8100.c @@ -0,0 +1,4166 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * AV8100 driver + * + * Author: Per Persson <per.xb.persson@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/fs.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/timer.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include "av8100_regs.h" +#include <video/av8100.h> +#include <video/hdmi.h> +#include <linux/firmware.h> + +#define AV8100_FW_FILENAME "av8100.fw" +#define CUT_STR_0 "2.1" +#define CUT_STR_1 "2.2" +#define CUT_STR_3 "2.3" +#define CUT_STR_30 "3.0" +#define CUT_STR_UNKNOWN "" +#define AV8100_DEVNR_DEFAULT 0 + +/* Interrupts */ +#define AV8100_INT_EVENT 0x1 +#define AV8100_PLUGSTARTUP_EVENT 0x4 + +#define AV8100_PLUGSTARTUP_TIME 100 + +/* Standby search time */ +#define AV8100_ON_TIME 1 /* 9 ms step */ +#define AV8100_DENC_OFF_TIME 3 /* 275 ms step if > V1. Not used if V1 */ +#define AV8100_HDMI_OFF_TIME 2 /* 140 ms step if V2. 80 ms step if V1 */ + +/* Command offsets */ +#define AV8100_COMMAND_OFFSET 0x10 +#define AV8100_CUTVER_OFFSET 0x11 +#define AV8100_COMMAND_MAX_LENGTH 0x81 +#define AV8100_CMD_BUF_OFFSET (AV8100_COMMAND_OFFSET + 1) +#define AV8100_2ND_RET_BYTE_OFFSET (AV8100_COMMAND_OFFSET + 1) +#define AV8100_CEC_RET_BUF_OFFSET (AV8100_COMMAND_OFFSET + 4) +#define AV8100_HDCP_RET_BUF_OFFSET (AV8100_COMMAND_OFFSET + 2) +#define AV8100_EDID_RET_BUF_OFFSET (AV8100_COMMAND_OFFSET + 1) +#define AV8100_FUSE_CRC_OFFSET (AV8100_COMMAND_OFFSET + 2) +#define AV8100_FUSE_PRGD_OFFSET (AV8100_COMMAND_OFFSET + 3) +#define AV8100_CRC32_OFFSET (AV8100_COMMAND_OFFSET + 2) +#define AV8100_CEC_ADDR_OFFSET (AV8100_COMMAND_OFFSET + 3) + +/* Tearing effect line numbers */ +#define AV8100_TE_LINE_NB_14 14 +#define AV8100_TE_LINE_NB_17 17 +#define AV8100_TE_LINE_NB_18 18 +#define AV8100_TE_LINE_NB_21 21 +#define AV8100_TE_LINE_NB_22 22 +#define AV8100_TE_LINE_NB_24 24 +#define AV8100_TE_LINE_NB_25 25 +#define AV8100_TE_LINE_NB_26 26 +#define AV8100_TE_LINE_NB_29 29 +#define AV8100_TE_LINE_NB_30 30 +#define AV8100_TE_LINE_NB_32 32 +#define AV8100_TE_LINE_NB_38 38 +#define AV8100_TE_LINE_NB_40 40 +#define AV8100_UI_X4_DEFAULT 6 + +#define HDMI_REQUEST_FOR_REVOCATION_LIST_INPUT 2 +#define HDMI_CEC_MESSAGE_WRITE_BUFFER_SIZE 16 +#define HDMI_HDCP_SEND_KEY_SIZE 7 +#define HDMI_INFOFRAME_DATA_SIZE 28 +#define HDMI_FUSE_AES_KEY_SIZE 16 +#define HDMI_FUSE_AES_KEY_RET_SIZE 2 +#define HDMI_LOADAES_END_BLK_NR 145 +#define HDMI_CRC32_SIZE 4 +#define HDMI_HDCP_MGMT_BKSV_SIZE 5 +#define HDMI_HDCP_MGMT_SHA_SIZE 20 +#define HDMI_HDCP_MGMT_MAX_DEVICES_SIZE 20 +#define HDMI_HDCP_MGMT_DEVICE_MASK 0x7F +#define HDMI_EDIDREAD_SIZE 0x7F + +#define HPDS_INVALID 0xF +#define CPDS_INVALID 0xF +#define CECRX_INVALID 0xF + +#define REG_16_8_LSB(p) ((u8)(p & 0xFF)) +#define REG_16_8_MSB(p) ((u8)((p & 0xFF00)>>8)) +#define REG_32_8_MSB(p) ((u8)((p & 0xFF000000)>>24)) +#define REG_32_8_MMSB(p) ((u8)((p & 0x00FF0000)>>16)) +#define REG_32_8_MLSB(p) ((u8)((p & 0x0000FF00)>>8)) +#define REG_32_8_LSB(p) ((u8)(p & 0x000000FF)) +#define REG_10_8_MSB(p) ((u8)((p & 0x300)>>8)) +#define REG_12_8_MSB(p) ((u8)((p & 0xf00)>>8)) + +#define AV8100_WAITTIME_1MS 1 +#define AV8100_WAITTIME_5MS 5 +#define AV8100_WAITTIME_10MS 10 +#define AV8100_WAITTIME_50MS 50 +#define AV8100_WATTIME_100US 100 + +static DEFINE_MUTEX(av8100_hw_mutex); +#define LOCK_AV8100_HW mutex_lock(&av8100_hw_mutex) +#define UNLOCK_AV8100_HW mutex_unlock(&av8100_hw_mutex) +static DEFINE_MUTEX(av8100_fwdl_mutex); +#define LOCK_AV8100_FWDL mutex_lock(&av8100_fwdl_mutex) +#define UNLOCK_AV8100_FWDL mutex_unlock(&av8100_fwdl_mutex) + +struct color_conversion_cmd { + unsigned short c0; + unsigned short c1; + unsigned short c2; + unsigned short c3; + unsigned short c4; + unsigned short c5; + unsigned short c6; + unsigned short c7; + unsigned short c8; + unsigned short aoffset; + unsigned short boffset; + unsigned short coffset; + unsigned char lmax; + unsigned char lmin; + unsigned char cmax; + unsigned char cmin; +}; + +struct av8100_config { + struct i2c_client *client; + struct i2c_device_id *id; + struct av8100_video_input_format_cmd hdmi_video_input_cmd; + struct av8100_audio_input_format_cmd hdmi_audio_input_cmd; + struct av8100_video_output_format_cmd hdmi_video_output_cmd; + struct av8100_video_scaling_format_cmd hdmi_video_scaling_cmd; + enum av8100_color_transform color_transform; + struct av8100_cec_message_write_format_cmd + hdmi_cec_message_write_cmd; + struct av8100_cec_message_read_back_format_cmd + hdmi_cec_message_read_back_cmd; + struct av8100_denc_format_cmd hdmi_denc_cmd; + struct av8100_hdmi_cmd hdmi_cmd; + struct av8100_hdcp_send_key_format_cmd hdmi_hdcp_send_key_cmd; + struct av8100_hdcp_management_format_cmd + hdmi_hdcp_management_format_cmd; + struct av8100_infoframes_format_cmd hdmi_infoframes_cmd; + struct av8100_edid_section_readback_format_cmd + hdmi_edid_section_readback_cmd; + struct av8100_pattern_generator_format_cmd hdmi_pattern_generator_cmd; + struct av8100_fuse_aes_key_format_cmd hdmi_fuse_aes_key_cmd; +}; + +enum av8100_plug_state { + AV8100_UNPLUGGED, + AV8100_PLUGGED_STARTUP, + AV8100_PLUGGED +}; + +struct av8100_params { + int denc_off_time;/* 5 volt time */ + int hdmi_off_time;/* 5 volt time */ + int on_time;/* 5 volt time */ + u8 hpdm;/*stby_int_mask*/ + u8 cpdm;/*stby_int_mask*/ + u8 cecm;/*gen_int_mask*/ + u8 hdcpm;/*gen_int_mask*/ + u8 uovbm;/*gen_int_mask*/ + void (*hdmi_ev_cb)(enum av8100_hdmi_event); + enum av8100_plug_state plug_state; + struct clk *inputclk; + bool inputclk_requested; + bool opp_requested; + struct regulator *regulator_pwr; + bool regulator_requested; + bool pre_suspend_power; + bool ints_enabled; + bool irq_requested; +}; + +/** + * struct av8100_cea - CEA(consumer electronic access) standard structure + * @cea_id: + * @cea_nb: + * @vtotale: + **/ + +struct av8100_cea { + char cea_id[40]; + int cea_nb; + int vtotale; + int vactive; + int vsbp; + int vslen; + int vsfp; + char vpol[5]; + int htotale; + int hactive; + int hbp; + int hslen; + int hfp; + int frequence; + char hpol[5]; + int reg_line_duration; + int blkoel_duration; + int uix4; + int pll_mult; + int pll_div; +}; + +enum av8100_command_size { + AV8100_COMMAND_VIDEO_INPUT_FORMAT_SIZE = 0x17, + AV8100_COMMAND_AUDIO_INPUT_FORMAT_SIZE = 0x8, + AV8100_COMMAND_VIDEO_OUTPUT_FORMAT_SIZE = 0x1E, + AV8100_COMMAND_VIDEO_SCALING_FORMAT_SIZE = 0x11, + AV8100_COMMAND_COLORSPACECONVERSION_SIZE = 0x1D, + AV8100_COMMAND_CEC_MESSAGE_WRITE_SIZE = 0x12, + AV8100_COMMAND_CEC_MESSAGE_READ_BACK_SIZE = 0x1, + AV8100_COMMAND_DENC_SIZE = 0x6, + AV8100_COMMAND_HDMI_SIZE = 0x4, + AV8100_COMMAND_HDCP_SENDKEY_SIZE = 0xA, + AV8100_COMMAND_HDCP_MANAGEMENT_SIZE = 0x3, + AV8100_COMMAND_INFOFRAMES_SIZE = 0x21, + AV8100_COMMAND_EDID_SECTION_READBACK_SIZE = 0x3, + AV8100_COMMAND_PATTERNGENERATOR_SIZE = 0x4, + AV8100_COMMAND_FUSE_AES_KEY_SIZE = 0x12, + AV8100_COMMAND_FUSE_AES_CHK_SIZE = 0x2, +}; + +struct av8100_device { + struct list_head list; + struct miscdevice miscdev; + struct device *dev; + struct av8100_config config; + struct av8100_status status; + struct timer_list timer; + wait_queue_head_t event; + int flag; + struct av8100_params params; + u8 chip_version; +}; + +static const unsigned int waittime_retry[10] = { + 1, 2, 4, 6, 8, 10, 10, 10, 10, 10}; + +static int av8100_5V_w(u8 denc_off, u8 hdmi_off, u8 on); +static void clr_plug_status(struct av8100_device *adev, + enum av8100_plugin_status status); +static void set_plug_status(struct av8100_device *adev, + enum av8100_plugin_status status); +static void cec_rx(struct av8100_device *adev); +static void cec_tx(struct av8100_device *adev); +static void cec_txerr(struct av8100_device *adev); +static void hdcp_changed(struct av8100_device *adev); +static const struct color_conversion_cmd *get_color_transform_cmd( + struct av8100_device *adev, + enum av8100_color_transform transform); +static int av8100_open(struct inode *inode, struct file *filp); +static int av8100_release(struct inode *inode, struct file *filp); +static long av8100_ioctl(struct file *file, + unsigned int cmd, unsigned long arg); +static int __devinit av8100_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id); +static int __devexit av8100_remove(struct i2c_client *i2c_client); + +static const struct file_operations av8100_fops = { + .owner = THIS_MODULE, + .open = av8100_open, + .release = av8100_release, + .unlocked_ioctl = av8100_ioctl +}; + +/* List of devices */ +static LIST_HEAD(av8100_device_list); + +static const struct av8100_cea av8100_all_cea[29] = { +/* cea id + * cea_nr vtot vact vsbpp vslen + * vsfp vpol htot hact hbp hslen hfp freq + * hpol rld bd uix4 pm pd */ +{ "0 CUSTOM ", + 0, 0, 0, 0, 0, + 0, "-", 800, 640, 16, 96, 10, 25200000, + "-", 0, 0, 0, 0, 0},/*Settings to be defined*/ +{ "1 CEA 1 VESA 4 640x480p @ 60 Hz ", + 1, 525, 480, 33, 2, + 10, "-", 800, 640, 49, 290, 146, 25200000, + "-", 2438, 1270, 6, 32, 1},/*RGB888*/ +{ "2 CEA 2 - 3 720x480p @ 60 Hz 4:3 ", + 2, 525, 480, 30, 6, + 9, "-", 858, 720, 34, 130, 128, 27027000, + "-", 1828, 0x3C0, 8, 24, 1},/*RGB565*/ +{ "3 CEA 4 1280x720p @ 60 Hz ", + 4, 750, 720, 20, 5, + 5, "+", 1650, 1280, 114, 39, 228, 74250000, + "+", 1706, 164, 6, 32, 1},/*RGB565*/ +{ "4 CEA 5 1920x1080i @ 60 Hz ", + 5, 1125, 540, 20, 5, + 0, "+", 2200, 1920, 88, 44, 10, 74250000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "5 CEA 6-7 480i (NTSC) ", + 6, 525, 240, 44, 5, + 0, "-", 858, 720, 12, 64, 10, 13513513, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "6 CEA 14-15 480p @ 60 Hz ", + 14, 525, 480, 44, 5, + 0, "-", 858, 720, 12, 64, 10, 27027000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "7 CEA 16 1920x1080p @ 60 Hz ", + 16, 1125, 1080, 36, 5, + 0, "+", 1980, 1280, 440, 40, 10, 133650000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "8 CEA 17-18 720x576p @ 50 Hz ", + 17, 625, 576, 44, 5, + 0, "-", 864, 720, 12, 64, 10, 27000000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "9 CEA 19 1280x720p @ 50 Hz ", + 19, 750, 720, 25, 5, + 0, "+", 1980, 1280, 440, 40, 10, 74250000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "10 CEA 20 1920 x 1080i @ 50 Hz ", + 20, 1125, 540, 20, 5, + 0, "+", 2640, 1920, 528, 44, 10, 74250000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "11 CEA 21-22 576i (PAL) ", + 21, 625, 288, 44, 5, + 0, "-", 1728, 1440, 12, 64, 10, 27000000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "12 CEA 29/30 576p ", + 29, 625, 576, 44, 5, + 0, "-", 864, 720, 12, 64, 10, 27000000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "13 CEA 31 1080p 50Hz ", + 31, 1125, 1080, 44, 5, + 0, "-", 2640, 1920, 12, 64, 10, 148500000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "14 CEA 32 1920x1080p @ 24 Hz ", + 32, 1125, 1080, 36, 5, + 4, "+", 2750, 1920, 660, 44, 153, 74250000, + "+", 2844, 0x530, 6, 32, 1},/*RGB565*/ +{ "15 CEA 33 1920x1080p @ 25 Hz ", + 33, 1125, 1080, 36, 5, + 4, "+", 2640, 1920, 528, 44, 10, 74250000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "16 CEA 34 1920x1080p @ 30Hz ", + 34, 1125, 1080, 36, 5, + 4, "+", 2200, 1920, 91, 44, 153, 74250000, + "+", 2275, 0xAB, 6, 32, 1},/*RGB565*/ +{ "17 CEA 60 1280x720p @ 24 Hz ", + 60, 750, 720, 20, 5, + 5, "+", 3300, 1280, 284, 50, 2276, 59400000, + "+", 4266, 0xAD0, 5, 32, 1},/*RGB565*/ +{ "18 CEA 61 1280x720p @ 25 Hz ", + 61, 750, 720, 20, 5, + 5, "+", 3960, 1280, 228, 39, 2503, 74250000, + "+", 4096, 0x500, 5, 32, 1},/*RGB565*/ +{ "19 CEA 62 1280x720p @ 30 Hz ", + 62, 750, 720, 20, 5, + 5, "+", 3300, 1280, 228, 39, 1820, 74250000, + "+", 3413, 0x770, 5, 32, 1},/*RGB565*/ +{ "20 VESA 9 800x600 @ 60 Hz ", + 109, 628, 600, 28, 4, + 0, "+", 1056, 800, 40, 128, 10, 40000000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "21 VESA 14 848x480 @ 60 Hz ", + 114, 517, 480, 20, 5, + 0, "+", 1088, 848, 24, 80, 10, 33750000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "22 VESA 16 1024x768 @ 60 Hz ", + 116, 806, 768, 38, 6, + 0, "-", 1344, 1024, 24, 135, 10, 65000000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "23 VESA 22 1280x768 @ 60 Hz ", + 122, 790, 768, 34, 4, + 0, "+", 1440, 1280, 48, 160, 10, 68250000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "24 VESA 23 1280x768 @ 60 Hz ", + 123, 798, 768, 30, 7, + 0, "+", 1664, 1280, 64, 128, 10, 79500000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "25 VESA 27 1280x800 @ 60 Hz ", + 127, 823, 800, 23, 6, + 0, "+", 1440, 1280, 48, 32, 10, 71000000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "26 VESA 28 1280x800 @ 60 Hz ", + 128, 831, 800, 31, 6, + 0, "+", 1680, 1280, 72, 128, 10, 83500000, + "-", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "27 VESA 39 1360x768 @ 60 Hz ", + 139, 795, 768, 22, 5, + 0, "-", 1792, 1360, 48, 32, 10, 85500000, + "+", 0, 0, 0, 0, 0},/*Settings to be define*/ +{ "28 VESA 81 1366x768 @ 60 Hz ", + 181, 798, 768, 30, 5, + 0, "+", 1792, 1366, 72, 136, 10, 85750000, + "-", 0, 0, 0, 0, 0} /*Settings to be define*/ +}; + +const struct color_conversion_cmd col_trans_identity = { + .c0 = 0x0100, .c1 = 0x0000, .c2 = 0x0000, + .c3 = 0x0000, .c4 = 0x0100, .c5 = 0x0000, + .c6 = 0x0000, .c7 = 0x0000, .c8 = 0x0100, + .aoffset = 0x0000, .boffset = 0x0000, .coffset = 0x0000, + .lmax = 0xff, + .lmin = 0x00, + .cmax = 0xff, + .cmin = 0x00, +}; + +const struct color_conversion_cmd col_trans_identity_clamp_yuv = { + .c0 = 0x0100, .c1 = 0x0000, .c2 = 0x0000, + .c3 = 0x0000, .c4 = 0x0100, .c5 = 0x0000, + .c6 = 0x0000, .c7 = 0x0000, .c8 = 0x0100, + .aoffset = 0x0000, .boffset = 0x0000, .coffset = 0x0000, + .lmax = 0xeb, + .lmin = 0x10, + .cmax = 0xf0, + .cmin = 0x10, +}; + +const struct color_conversion_cmd col_trans_yuv_to_rgb_v1 = { + .c0 = 0x0087, .c1 = 0x0000, .c2 = 0x00ba, + .c3 = 0x0087, .c4 = 0xffd3, .c5 = 0xffa1, + .c6 = 0x0087, .c7 = 0x00eb, .c8 = 0x0000, + .aoffset = 0xffab, .boffset = 0x004e, .coffset = 0xff92, + .lmax = 0xff, + .lmin = 0x00, + .cmax = 0xff, + .cmin = 0x00, +}; + +const struct color_conversion_cmd col_trans_yuv_to_rgb_v2 = { + .c0 = 0x0198, .c1 = 0x012a, .c2 = 0x0000, + .c3 = 0xff30, .c4 = 0x012a, .c5 = 0xff9c, + .c6 = 0x0000, .c7 = 0x012a, .c8 = 0x0204, + .aoffset = 0xff21, .boffset = 0x0088, .coffset = 0xfeeb, + .lmax = 0xff, + .lmin = 0x00, + .cmax = 0xff, + .cmin = 0x00, +}; + +const struct color_conversion_cmd col_trans_yuv_to_denc = { + .c0 = 0x0100, .c1 = 0x0000, .c2 = 0x0000, + .c3 = 0x0000, .c4 = 0x0100, .c5 = 0x0000, + .c6 = 0x0000, .c7 = 0x0000, .c8 = 0x0100, + .aoffset = 0x0000, .boffset = 0x0000, .coffset = 0x0000, + .lmax = 0xeb, + .lmin = 0x10, + .cmax = 0xf0, + .cmin = 0x10, +}; + +const struct color_conversion_cmd col_trans_rgb_to_denc = { + .c0 = 0x0070, .c1 = 0xffb6, .c2 = 0xffda, + .c3 = 0x0042, .c4 = 0x0081, .c5 = 0x0019, + .c6 = 0xffee, .c7 = 0xffa2, .c8 = 0x0070, + .aoffset = 0x007f, .boffset = 0x0010, .coffset = 0x007f, + .lmax = 0xff, + .lmin = 0x00, + .cmax = 0xff, + .cmin = 0x00, +}; + +static const struct i2c_device_id av8100_id[] = { + { "av8100", 0 }, + { } +}; + +static struct av8100_device *devnr_to_adev(int devnr) +{ + /* Get device from list of devices */ + struct list_head *element; + struct av8100_device *av8100_dev; + int cnt = 0; + + list_for_each(element, &av8100_device_list) { + av8100_dev = list_entry(element, struct av8100_device, list); + if (cnt == devnr) + return av8100_dev; + cnt++; + } + + return NULL; +} + +static struct av8100_device *dev_to_adev(struct device *dev) +{ + /* Get device from list of devices */ + struct list_head *element; + struct av8100_device *av8100_dev; + int cnt = 0; + + list_for_each(element, &av8100_device_list) { + av8100_dev = list_entry(element, struct av8100_device, list); + if (av8100_dev->dev == dev) + return av8100_dev; + cnt++; + } + + return NULL; +} + +static int adev_to_devnr(struct av8100_device *adev) +{ + /* Get devnr from list of devices */ + struct list_head *element; + struct av8100_device *av8100_dev; + int cnt = 0; + + list_for_each(element, &av8100_device_list) { + av8100_dev = list_entry(element, struct av8100_device, list); + if (av8100_dev == adev) + return cnt; + cnt++; + } + + return -EINVAL; +} + +#ifdef CONFIG_PM +static int av8100_suspend(struct device *dev) +{ + int ret = 0; + struct av8100_device *adev; + + adev = dev_to_adev(dev); + if (!adev) + return -EFAULT; + + dev_dbg(dev, "%s\n", __func__); + + adev->params.pre_suspend_power = + (av8100_status_get().av8100_state > AV8100_OPMODE_SHUTDOWN); + + if (adev->params.pre_suspend_power) { + ret = av8100_powerdown(); + if (ret) + dev_err(dev, "av8100_powerdown failed\n"); + } + + return ret; +} + +static int av8100_resume(struct device *dev) +{ + int ret; + u8 hpds = 0; + struct av8100_device *adev; + + adev = dev_to_adev(dev); + if (!adev) + return -EFAULT; + + dev_dbg(dev, "%s\n", __func__); + + if (adev->params.pre_suspend_power) { + ret = av8100_powerup(); + if (ret) { + dev_err(dev, "av8100_powerup failed\n"); + return ret; + } + + /* Check HDMI plug status */ + if (av8100_reg_stby_r(NULL, NULL, &hpds, NULL, NULL)) { + dev_warn(dev, "av8100_reg_stby_r failed\n"); + goto av8100_resume_end; + } + + if (hpds) + set_plug_status(adev, AV8100_HDMI_PLUGIN); /* Plugged*/ + else + clr_plug_status(adev, + AV8100_HDMI_PLUGIN); /* Unplugged*/ + + adev->params.hpdm = AV8100_STANDBY_INTERRUPT_MASK_HPDM_HIGH; + av8100_enable_interrupt(); + } + +av8100_resume_end: + return 0; +} + +static const struct dev_pm_ops av8100_dev_pm_ops = { + .suspend = av8100_suspend, + .resume = av8100_resume, +}; +#endif + +static struct i2c_driver av8100_driver = { + .probe = av8100_probe, + .remove = av8100_remove, + .driver = { + .name = "av8100", +#ifdef CONFIG_PM + .pm = &av8100_dev_pm_ops, +#endif + }, + .id_table = av8100_id, +}; + +static void av8100_plugtimer_int(unsigned long value) +{ + struct av8100_device *adev; + + adev = devnr_to_adev((int)value); + adev->flag |= AV8100_PLUGSTARTUP_EVENT; + wake_up_interruptible(&adev->event); + del_timer(&adev->timer); +} + +static int av8100_int_event_handle(struct av8100_device *adev) +{ + u8 hpdi = 0; + u8 cpdi = 0; + u8 uovbi = 0; + u8 hdcpi = 0; + u8 ceci = 0; + u8 hpds = 0; + u8 cpds = 0; + u8 hdcps = 0; + u8 onuvb = 0; + u8 cectxerr = 0; + u8 cecrx = 0; + u8 cectx = 0; + + /* STANDBY_PENDING_INTERRUPT reg */ + if (av8100_reg_stby_pend_int_r(&hpdi, &cpdi, NULL, NULL)) { + dev_dbg(adev->dev, "av8100_reg_stby_pend_int_r failed\n"); + goto av8100_int_event_handle_1; + } + + /* Plug event */ + if (hpdi | cpdi) { + /* Clear pending interrupts */ + (void)av8100_reg_stby_pend_int_w(1, 1, 1, 0); + + /* STANDBY reg */ + if (av8100_reg_stby_r(NULL, NULL, &hpds, &cpds, NULL)) { + dev_dbg(adev->dev, "av8100_reg_stby_r failed\n"); + goto av8100_int_event_handle_1; + } + } + + if (cpdi & adev->params.cpdm) { + /* TVout plugin change */ + if (cpds) { + dev_dbg(adev->dev, "cpds 1\n"); + set_plug_status(adev, AV8100_CVBS_PLUGIN); + } else { + dev_dbg(adev->dev, "cpds 0\n"); + clr_plug_status(adev, AV8100_CVBS_PLUGIN); + } + } + + if (hpdi & adev->params.hpdm) { + /* HDMI plugin change */ + if (hpds) { + /* Plugged */ + /* Set 5V always on */ + av8100_5V_w(adev->params.denc_off_time, + 0, + adev->params.on_time); + dev_dbg(adev->dev, "hpds 1\n"); + set_plug_status(adev, AV8100_HDMI_PLUGIN); + } else { + /* Unplugged */ + av8100_5V_w(adev->params.denc_off_time, + adev->params.hdmi_off_time, + adev->params.on_time); + dev_dbg(adev->dev, "hpds 0\n"); + clr_plug_status(adev, AV8100_HDMI_PLUGIN); + } + } + +av8100_int_event_handle_1: + /* GENERAL_INTERRUPT reg */ + if (av8100_reg_gen_int_r(NULL, NULL, NULL, &ceci, + &hdcpi, &uovbi, NULL)) { + dev_dbg(adev->dev, "av8100_reg_gen_int_r failed\n"); + return -EINVAL; + } + + /* CEC or HDCP event */ + if (ceci | hdcpi | uovbi) { + /* Clear pending interrupts */ + av8100_reg_gen_int_w(1, 1, 1, 1, 1, 1); + + /* GENERAL_STATUS reg */ + if (av8100_reg_gen_status_r(&cectxerr, &cecrx, &cectx, NULL, + &onuvb, &hdcps) != 0) { + dev_dbg(adev->dev, "av8100_reg_gen_status_r fail\n"); + return -EINVAL; + } + } + + /* Underflow or overflow */ + if (uovbi) + dev_dbg(adev->dev, "uovbi %d\n", onuvb); + + /* CEC received */ + if (ceci && cecrx) { + u8 val; + + dev_dbg(adev->dev, "cecrx\n"); + + /* Clear cecrx in status reg*/ + if (av8100_reg_r(AV8100_GENERAL_STATUS, &val) == 0) { + if (av8100_reg_w(AV8100_GENERAL_STATUS, + val & ~AV8100_GENERAL_STATUS_CECREC_MASK)) + dev_info(adev->dev, "gen_stat write error\n"); + } else { + dev_info(adev->dev, "gen_stat read error\n"); + } + + /* Report CEC event */ + cec_rx(adev); + } + + /* CEC tx error */ + if (ceci && cectx && cectxerr) { + dev_dbg(adev->dev, "cectxerr\n"); + /* Report CEC tx error event */ + cec_txerr(adev); + } else if (ceci && cectx) { + dev_dbg(adev->dev, "cectx\n"); + /* Report CEC tx event */ + cec_tx(adev); + } + + /* HDCP event */ + if (hdcpi) { + dev_dbg(adev->dev, "hdcpch:%0x\n", hdcps); + /* Report HDCP status change event */ + hdcp_changed(adev); + } + + return 0; +} + +static int av8100_plugstartup_event_handle(struct av8100_device *adev) +{ + u8 hpds = 0; + u8 cpds = 0; + + switch (adev->params.plug_state) { + case AV8100_UNPLUGGED: + case AV8100_PLUGGED: + default: + break; + + case AV8100_PLUGGED_STARTUP: + /* Unmask interrupt */ + adev->params.hpdm = AV8100_STANDBY_INTERRUPT_MASK_HPDM_HIGH; + if (av8100_reg_stby_int_mask_w(adev->params.hpdm, + adev->params.cpdm, + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_INPUT, + AV8100_STANDBY_INTERRUPT_MASK_IPOL_LOW)) { + dev_dbg(adev->dev, + "av8100_reg_stby_int_mask_w fail\n"); + } + + mdelay(AV8100_WAITTIME_1MS); + + /* Get actual plug status */ + if (av8100_reg_stby_r(NULL, NULL, &hpds, &cpds, NULL)) + dev_dbg(adev->dev, "av8100_reg_stby_r fail\n"); + + /* Set plugstate */ + if (hpds) { + adev->params.plug_state = AV8100_PLUGGED; + dev_dbg(adev->dev, "plug_state:2\n"); + } else { + adev->params.plug_state = AV8100_UNPLUGGED; + dev_dbg(adev->dev, "plug_state:0\n"); + + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb( + AV8100_HDMI_EVENT_HDMI_PLUGOUT); + } + break; + } + + return 0; +} + +static int av8100_thread(void *p) +{ + u8 flags; + struct av8100_device *adev = p; + + while (1) { + wait_event_interruptible(adev->event, (adev->flag != 0)); + flags = adev->flag; + adev->flag = 0; + + if (adev->status.av8100_state < AV8100_OPMODE_STANDBY) + continue; + + if (flags & AV8100_INT_EVENT) + (void)av8100_int_event_handle(adev); + + if (flags & AV8100_PLUGSTARTUP_EVENT) + (void)av8100_plugstartup_event_handle(adev); + } + + return 0; +} + +static irqreturn_t av8100_intr_handler(int irq, void *p) +{ + struct av8100_device *adev; + + adev = (struct av8100_device *) p; + adev->flag |= AV8100_INT_EVENT; + wake_up_interruptible(&adev->event); + + return IRQ_HANDLED; +} + +static u16 av8100_get_te_line_nb( + enum av8100_output_CEA_VESA output_video_format) +{ + u16 retval; + + switch (output_video_format) { + case AV8100_CEA1_640X480P_59_94HZ: + case AV8100_CEA2_3_720X480P_59_94HZ: + case AV8100_VESA16_1024X768P_60HZ: + retval = AV8100_TE_LINE_NB_30; + break; + + case AV8100_CEA4_1280X720P_60HZ: + case AV8100_CEA60_1280X720P_24HZ: + case AV8100_CEA61_1280X720P_25HZ: + case AV8100_CEA62_1280X720P_30HZ: + retval = AV8100_TE_LINE_NB_21; + break; + + case AV8100_CEA5_1920X1080I_60HZ: + case AV8100_CEA6_7_NTSC_60HZ: + case AV8100_CEA20_1920X1080I_50HZ: + case AV8100_CEA21_22_576I_PAL_50HZ: + case AV8100_VESA27_1280X800P_59_91HZ: + retval = AV8100_TE_LINE_NB_18; + break; + + case AV8100_CEA14_15_480p_60HZ: + retval = AV8100_TE_LINE_NB_32; + break; + + case AV8100_CEA17_18_720X576P_50HZ: + case AV8100_CEA29_30_576P_50HZ: + retval = AV8100_TE_LINE_NB_40; + break; + + case AV8100_CEA19_1280X720P_50HZ: + case AV8100_VESA39_1360X768P_60_02HZ: + retval = AV8100_TE_LINE_NB_22; + break; + + case AV8100_CEA32_1920X1080P_24HZ: + case AV8100_CEA33_1920X1080P_25HZ: + case AV8100_CEA34_1920X1080P_30HZ: + retval = AV8100_TE_LINE_NB_38; + break; + + case AV8100_VESA9_800X600P_60_32HZ: + retval = AV8100_TE_LINE_NB_24; + break; + + case AV8100_VESA14_848X480P_60HZ: + retval = AV8100_TE_LINE_NB_29; + break; + + case AV8100_VESA22_1280X768P_59_99HZ: + retval = AV8100_TE_LINE_NB_17; + break; + + case AV8100_VESA23_1280X768P_59_87HZ: + case AV8100_VESA81_1366X768P_59_79HZ: + retval = AV8100_TE_LINE_NB_25; + break; + + case AV8100_VESA28_1280X800P_59_81HZ: + retval = AV8100_TE_LINE_NB_26; + break; + + case AV8100_CEA16_1920X1080P_60HZ: + case AV8100_CEA31_1920x1080P_50Hz: + default: + /* TODO */ + retval = AV8100_TE_LINE_NB_38; + break; + } + + return retval; +} + +static u16 av8100_get_ui_x4( + enum av8100_output_CEA_VESA output_video_format) +{ + return AV8100_UI_X4_DEFAULT; +} + +static int av8100_config_video_output_dep( + enum av8100_output_CEA_VESA output_format) +{ + int retval; + union av8100_configuration config; + + /* video input */ + config.video_input_format.dsi_input_mode = + AV8100_HDMI_DSI_COMMAND_MODE; + config.video_input_format.input_pixel_format = AV8100_INPUT_PIX_RGB565; + config.video_input_format.total_horizontal_pixel = + av8100_all_cea[output_format].htotale; + config.video_input_format.total_horizontal_active_pixel = + av8100_all_cea[output_format].hactive; + config.video_input_format.total_vertical_lines = + av8100_all_cea[output_format].vtotale; + config.video_input_format.total_vertical_active_lines = + av8100_all_cea[output_format].vactive; + + switch (output_format) { + case AV8100_CEA5_1920X1080I_60HZ: + case AV8100_CEA20_1920X1080I_50HZ: + case AV8100_CEA21_22_576I_PAL_50HZ: + case AV8100_CEA6_7_NTSC_60HZ: + config.video_input_format.video_mode = + AV8100_VIDEO_INTERLACE; + break; + + default: + config.video_input_format.video_mode = + AV8100_VIDEO_PROGRESSIVE; + break; + } + + config.video_input_format.nb_data_lane = + AV8100_DATA_LANES_USED_2; + config.video_input_format.nb_virtual_ch_command_mode = 0; + config.video_input_format.nb_virtual_ch_video_mode = 0; + config.video_input_format.ui_x4 = av8100_get_ui_x4(output_format); + config.video_input_format.TE_line_nb = av8100_get_te_line_nb( + output_format); + config.video_input_format.TE_config = AV8100_TE_OFF; + config.video_input_format.master_clock_freq = 0; + + retval = av8100_conf_prep( + AV8100_COMMAND_VIDEO_INPUT_FORMAT, &config); + if (retval) + return -EFAULT; + + /* DENC */ + switch (output_format) { + case AV8100_CEA21_22_576I_PAL_50HZ: + config.denc_format.cvbs_video_format = AV8100_CVBS_625; + config.denc_format.standard_selection = AV8100_PAL_BDGHI; + break; + + case AV8100_CEA6_7_NTSC_60HZ: + config.denc_format.cvbs_video_format = AV8100_CVBS_525; + config.denc_format.standard_selection = AV8100_NTSC_M; + break; + + default: + /* Not supported */ + break; + } + + return 0; +} + +static int av8100_config_init(struct av8100_device *adev) +{ + int retval; + union av8100_configuration config; + + dev_dbg(adev->dev, "%s\n", __func__); + + memset(&config, 0, sizeof(union av8100_configuration)); + memset(&adev->config, 0, sizeof(struct av8100_config)); + + /* Color conversion */ + config.color_transform = AV8100_COLOR_TRANSFORM_INDENTITY; + retval = av8100_conf_prep( + AV8100_COMMAND_COLORSPACECONVERSION, &config); + if (retval) + return -EFAULT; + + /* DENC */ + config.denc_format.cvbs_video_format = AV8100_CVBS_625; + config.denc_format.standard_selection = AV8100_PAL_BDGHI; + config.denc_format.enable = 0; + config.denc_format.macrovision_enable = 0; + config.denc_format.internal_generator = 0; + retval = av8100_conf_prep(AV8100_COMMAND_DENC, &config); + if (retval) + return -EFAULT; + + /* Video output */ + config.video_output_format.video_output_cea_vesa = + AV8100_CEA4_1280X720P_60HZ; + + retval = av8100_conf_prep( + AV8100_COMMAND_VIDEO_OUTPUT_FORMAT, &config); + if (retval) + return -EFAULT; + + /* Video input */ + av8100_config_video_output_dep( + config.video_output_format.video_output_cea_vesa); + + /* Pattern generator */ + config.pattern_generator_format.pattern_audio_mode = + AV8100_PATTERN_AUDIO_OFF; + config.pattern_generator_format.pattern_type = + AV8100_PATTERN_GENERATOR; + config.pattern_generator_format.pattern_video_format = + AV8100_PATTERN_720P; + retval = av8100_conf_prep(AV8100_COMMAND_PATTERNGENERATOR, + &config); + if (retval) + return -EFAULT; + + /* Audio input */ + config.audio_input_format.audio_input_if_format = + AV8100_AUDIO_I2SDELAYED_MODE; + config.audio_input_format.i2s_input_nb = 1; + config.audio_input_format.sample_audio_freq = AV8100_AUDIO_FREQ_48KHZ; + config.audio_input_format.audio_word_lg = AV8100_AUDIO_16BITS; + config.audio_input_format.audio_format = AV8100_AUDIO_LPCM_MODE; + config.audio_input_format.audio_if_mode = AV8100_AUDIO_MASTER; + config.audio_input_format.audio_mute = AV8100_AUDIO_MUTE_DISABLE; + retval = av8100_conf_prep( + AV8100_COMMAND_AUDIO_INPUT_FORMAT, &config); + if (retval) + return -EFAULT; + + /* HDMI mode */ + config.hdmi_format.hdmi_mode = AV8100_HDMI_ON; + config.hdmi_format.hdmi_format = AV8100_HDMI; + config.hdmi_format.dvi_format = AV8100_DVI_CTRL_CTL0; + retval = av8100_conf_prep(AV8100_COMMAND_HDMI, &config); + if (retval) + return -EFAULT; + + /* EDID section readback */ + config.edid_section_readback_format.address = 0xA0; + config.edid_section_readback_format.block_number = 0; + retval = av8100_conf_prep( + AV8100_COMMAND_EDID_SECTION_READBACK, &config); + if (retval) + return -EFAULT; + + return 0; +} + +static int av8100_params_init(struct av8100_device *adev) +{ + dev_dbg(adev->dev, "%s\n", __func__); + + memset(&adev->params, 0, sizeof(struct av8100_params)); + + adev->params.denc_off_time = AV8100_DENC_OFF_TIME; + adev->params.hdmi_off_time = AV8100_HDMI_OFF_TIME; + adev->params.on_time = AV8100_ON_TIME; + + adev->params.hpdm = AV8100_STANDBY_INTERRUPT_MASK_HPDM_HIGH; + adev->params.cpdm = AV8100_STANDBY_INTERRUPT_MASK_CPDM_HIGH; + adev->params.hdcpm = AV8100_GENERAL_INTERRUPT_MASK_HDCPM_HIGH; + adev->params.cecm = AV8100_GENERAL_INTERRUPT_MASK_CECM_HIGH; + adev->params.uovbm = AV8100_GENERAL_INTERRUPT_MASK_UOVBM_HIGH; + + return 0; +} + +static void clr_plug_status(struct av8100_device *adev, + enum av8100_plugin_status status) +{ + adev->status.av8100_plugin_status &= ~status; + + switch (status) { + case AV8100_HDMI_PLUGIN: + switch (adev->params.plug_state) { + case AV8100_UNPLUGGED: + case AV8100_PLUGGED_STARTUP: + default: + break; + + case AV8100_PLUGGED: + adev->params.plug_state = + AV8100_UNPLUGGED; + dev_dbg(adev->dev, "plug_state:0\n"); + + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb( + AV8100_HDMI_EVENT_HDMI_PLUGOUT); + break; + } + break; + + case AV8100_CVBS_PLUGIN: + /* TODO */ + break; + + default: + break; + } +} + +static void set_plug_status(struct av8100_device *adev, + enum av8100_plugin_status status) +{ + adev->status.av8100_plugin_status |= status; + + switch (status) { + case AV8100_HDMI_PLUGIN: + switch (adev->params.plug_state) { + case AV8100_UNPLUGGED: + adev->params.plug_state = + AV8100_PLUGGED_STARTUP; + + dev_dbg(adev->dev, "plug_state:1\n"); + + /* + * Mask interrupts to avoid plug detect during + * startup + * */ + adev->params.hpdm = + AV8100_STANDBY_INTERRUPT_MASK_HPDM_LOW; + if (av8100_reg_stby_int_mask_w( + adev->params.hpdm, + adev->params.cpdm, + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_INPUT, + AV8100_STANDBY_INTERRUPT_MASK_IPOL_LOW)) { + dev_dbg(adev->dev, + "av8100_reg_stby_int_mask_w fail\n"); + } + + /* Set plug startup timer */ + init_timer(&adev->timer); + adev->timer.expires = jiffies + + AV8100_PLUGSTARTUP_TIME; + adev->timer.function = + av8100_plugtimer_int; + adev->timer.data = 0; + adev->timer.data = adev_to_devnr(adev); + mod_timer(&adev->timer, adev->timer.expires); + + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb( + AV8100_HDMI_EVENT_HDMI_PLUGIN); + break; + + case AV8100_PLUGGED_STARTUP: + case AV8100_PLUGGED: + default: + break; + } + break; + + case AV8100_CVBS_PLUGIN: + /* TODO */ + break; + + default: + break; + } +} + +static void cec_rx(struct av8100_device *adev) +{ + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb(AV8100_HDMI_EVENT_CEC); +} + +static void cec_tx(struct av8100_device *adev) +{ + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb(AV8100_HDMI_EVENT_CECTX); +} + +static void cec_txerr(struct av8100_device *adev) +{ + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb(AV8100_HDMI_EVENT_CECTXERR); +} + +static void hdcp_changed(struct av8100_device *adev) +{ + if (adev->params.hdmi_ev_cb) + adev->params.hdmi_ev_cb(AV8100_HDMI_EVENT_HDCP); +} + +static void av8100_set_state(struct av8100_device *adev, + enum av8100_operating_mode state) +{ + adev->status.av8100_state = state; + + if (state <= AV8100_OPMODE_STANDBY) { + clr_plug_status(adev, AV8100_HDMI_PLUGIN); + clr_plug_status(adev, AV8100_CVBS_PLUGIN); + adev->status.hdmi_on = false; + } +} + +/** + * write_single_byte() - Write a single byte to av8100 + * through i2c interface. + * @client: i2c client structure + * @reg: register offset + * @data: data byte to be written + * + * This funtion uses smbus byte write API to write a single byte to av8100 + **/ +static int write_single_byte(struct i2c_client *client, u8 reg, + u8 data) +{ + int ret; + struct device *dev = &client->dev; + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_dbg(dev, "i2c smbus write byte failed\n"); + + return ret; +} + +/** + * read_single_byte() - read single byte from av8100 + * through i2c interface + * @client: i2c client structure + * @reg: register offset + * @val: register value + * + * This funtion uses smbus read block API to read single byte from the reg + * offset. + **/ +static int read_single_byte(struct i2c_client *client, u8 reg, u8 *val) +{ + int value; + struct device *dev = &client->dev; + + value = i2c_smbus_read_byte_data(client, reg); + if (value < 0) { + dev_dbg(dev, "i2c smbus read byte failed,read data = %x " + "from offset:%x\n" , value, reg); + return -EFAULT; + } + + *val = (u8) value; + return 0; +} + +/** + * write_multi_byte() - Write a multiple bytes to av8100 through + * i2c interface. + * @client: i2c client structure + * @buf: buffer to be written + * @nbytes: nunmber of bytes to be written + * + * This funtion uses smbus block write API's to write n number of bytes to the + * av8100 + **/ +static int write_multi_byte(struct i2c_client *client, u8 reg, + u8 *buf, u8 nbytes) +{ + int ret; + struct device *dev = &client->dev; + + ret = i2c_smbus_write_i2c_block_data(client, reg, nbytes, buf); + if (ret < 0) + dev_dbg(dev, "i2c smbus write multi byte error\n"); + + return ret; +} + +static int configuration_video_input_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_video_input_cmd.dsi_input_mode; + buffer[1] = adev->config.hdmi_video_input_cmd.input_pixel_format; + buffer[2] = REG_16_8_MSB(adev->config.hdmi_video_input_cmd. + total_horizontal_pixel); + buffer[3] = REG_16_8_LSB(adev->config.hdmi_video_input_cmd. + total_horizontal_pixel); + buffer[4] = REG_16_8_MSB(adev->config.hdmi_video_input_cmd. + total_horizontal_active_pixel); + buffer[5] = REG_16_8_LSB(adev->config.hdmi_video_input_cmd. + total_horizontal_active_pixel); + buffer[6] = REG_16_8_MSB(adev->config.hdmi_video_input_cmd. + total_vertical_lines); + buffer[7] = REG_16_8_LSB(adev->config.hdmi_video_input_cmd. + total_vertical_lines); + buffer[8] = REG_16_8_MSB(adev->config.hdmi_video_input_cmd. + total_vertical_active_lines); + buffer[9] = REG_16_8_LSB(adev->config.hdmi_video_input_cmd. + total_vertical_active_lines); + buffer[10] = adev->config.hdmi_video_input_cmd.video_mode; + buffer[11] = adev->config.hdmi_video_input_cmd.nb_data_lane; + buffer[12] = adev->config.hdmi_video_input_cmd. + nb_virtual_ch_command_mode; + buffer[13] = adev->config.hdmi_video_input_cmd. + nb_virtual_ch_video_mode; + buffer[14] = REG_16_8_MSB(adev->config.hdmi_video_input_cmd. + TE_line_nb); + buffer[15] = REG_16_8_LSB(adev->config.hdmi_video_input_cmd. + TE_line_nb); + buffer[16] = adev->config.hdmi_video_input_cmd.TE_config; + buffer[17] = REG_32_8_MSB(adev->config.hdmi_video_input_cmd. + master_clock_freq); + buffer[18] = REG_32_8_MMSB(adev->config.hdmi_video_input_cmd. + master_clock_freq); + buffer[19] = REG_32_8_MLSB(adev->config.hdmi_video_input_cmd. + master_clock_freq); + buffer[20] = REG_32_8_LSB(adev->config.hdmi_video_input_cmd. + master_clock_freq); + buffer[21] = adev->config.hdmi_video_input_cmd.ui_x4; + + *length = AV8100_COMMAND_VIDEO_INPUT_FORMAT_SIZE - 1; + return 0; + +} + +static int configuration_audio_input_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_audio_input_cmd.audio_input_if_format; + buffer[1] = adev->config.hdmi_audio_input_cmd.i2s_input_nb; + buffer[2] = adev->config.hdmi_audio_input_cmd.sample_audio_freq; + buffer[3] = adev->config.hdmi_audio_input_cmd.audio_word_lg; + buffer[4] = adev->config.hdmi_audio_input_cmd.audio_format; + buffer[5] = adev->config.hdmi_audio_input_cmd.audio_if_mode; + buffer[6] = adev->config.hdmi_audio_input_cmd.audio_mute; + + *length = AV8100_COMMAND_AUDIO_INPUT_FORMAT_SIZE - 1; + return 0; +} + +static int configuration_video_output_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_video_output_cmd. + video_output_cea_vesa; + + if (buffer[0] == AV8100_CUSTOM) { + buffer[1] = adev->config.hdmi_video_output_cmd. + vsync_polarity; + buffer[2] = adev->config.hdmi_video_output_cmd. + hsync_polarity; + buffer[3] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.total_horizontal_pixel); + buffer[4] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.total_horizontal_pixel); + buffer[5] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.total_horizontal_active_pixel); + buffer[6] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.total_horizontal_active_pixel); + buffer[7] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.total_vertical_in_half_lines); + buffer[8] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.total_vertical_in_half_lines); + buffer[9] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd. + total_vertical_active_in_half_lines); + buffer[10] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd. + total_vertical_active_in_half_lines); + buffer[11] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.hsync_start_in_pixel); + buffer[12] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.hsync_start_in_pixel); + buffer[13] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.hsync_length_in_pixel); + buffer[14] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.hsync_length_in_pixel); + buffer[15] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.vsync_start_in_half_line); + buffer[16] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.vsync_start_in_half_line); + buffer[17] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.vsync_length_in_half_line); + buffer[18] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.vsync_length_in_half_line); + buffer[19] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.hor_video_start_pixel); + buffer[20] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.hor_video_start_pixel); + buffer[21] = REG_16_8_MSB(adev->config. + hdmi_video_output_cmd.vert_video_start_pixel); + buffer[22] = REG_16_8_LSB(adev->config. + hdmi_video_output_cmd.vert_video_start_pixel); + buffer[23] = adev->config.hdmi_video_output_cmd.video_type; + buffer[24] = adev->config.hdmi_video_output_cmd.pixel_repeat; + buffer[25] = REG_32_8_MSB(adev->config. + hdmi_video_output_cmd.pixel_clock_freq_Hz); + buffer[26] = REG_32_8_MMSB(adev->config. + hdmi_video_output_cmd.pixel_clock_freq_Hz); + buffer[27] = REG_32_8_MLSB(adev->config. + hdmi_video_output_cmd.pixel_clock_freq_Hz); + buffer[28] = REG_32_8_LSB(adev->config. + hdmi_video_output_cmd.pixel_clock_freq_Hz); + + *length = AV8100_COMMAND_VIDEO_OUTPUT_FORMAT_SIZE - 1; + } else { + *length = 1; + } + + return 0; +} + +static int configuration_video_scaling_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + h_start_in_pixel); + buffer[1] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + h_start_in_pixel); + buffer[2] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + h_stop_in_pixel); + buffer[3] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + h_stop_in_pixel); + buffer[4] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + v_start_in_line); + buffer[5] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + v_start_in_line); + buffer[6] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + v_stop_in_line); + buffer[7] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + v_stop_in_line); + buffer[8] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + h_start_out_pixel); + buffer[9] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd + .h_start_out_pixel); + buffer[10] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + h_stop_out_pixel); + buffer[11] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + h_stop_out_pixel); + buffer[12] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + v_start_out_line); + buffer[13] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + v_start_out_line); + buffer[14] = REG_16_8_MSB(adev->config.hdmi_video_scaling_cmd. + v_stop_out_line); + buffer[15] = REG_16_8_LSB(adev->config.hdmi_video_scaling_cmd. + v_stop_out_line); + + *length = AV8100_COMMAND_VIDEO_SCALING_FORMAT_SIZE - 1; + return 0; +} + +static int configuration_colorspace_conversion_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + const struct color_conversion_cmd *hdmi_color_space_conversion_cmd; + + hdmi_color_space_conversion_cmd = + get_color_transform_cmd(adev, adev->config.color_transform); + + buffer[0] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c0); + buffer[1] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c0); + buffer[2] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c1); + buffer[3] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c1); + buffer[4] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c2); + buffer[5] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c2); + buffer[6] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c3); + buffer[7] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c3); + buffer[8] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c4); + buffer[9] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c4); + buffer[10] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c5); + buffer[11] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c5); + buffer[12] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c6); + buffer[13] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c6); + buffer[14] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c7); + buffer[15] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c7); + buffer[16] = REG_12_8_MSB(hdmi_color_space_conversion_cmd->c8); + buffer[17] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->c8); + buffer[18] = REG_10_8_MSB(hdmi_color_space_conversion_cmd->aoffset); + buffer[19] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->aoffset); + buffer[20] = REG_10_8_MSB(hdmi_color_space_conversion_cmd->boffset); + buffer[21] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->boffset); + buffer[22] = REG_10_8_MSB(hdmi_color_space_conversion_cmd->coffset); + buffer[23] = REG_16_8_LSB(hdmi_color_space_conversion_cmd->coffset); + buffer[24] = hdmi_color_space_conversion_cmd->lmax; + buffer[25] = hdmi_color_space_conversion_cmd->lmin; + buffer[26] = hdmi_color_space_conversion_cmd->cmax; + buffer[27] = hdmi_color_space_conversion_cmd->cmin; + + *length = AV8100_COMMAND_COLORSPACECONVERSION_SIZE - 1; + return 0; +} + +static int configuration_cec_message_write_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_cec_message_write_cmd.buffer_length; + memcpy(&buffer[1], adev->config.hdmi_cec_message_write_cmd.buffer, + adev->config.hdmi_cec_message_write_cmd.buffer_length); + + *length = adev->config.hdmi_cec_message_write_cmd.buffer_length + 1; + + return 0; +} + +static int configuration_cec_message_read_get(char *buffer, + unsigned int *length) +{ + /* No buffer data */ + *length = AV8100_COMMAND_CEC_MESSAGE_READ_BACK_SIZE - 1; + return 0; +} + +static int configuration_denc_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_denc_cmd.cvbs_video_format; + buffer[1] = adev->config.hdmi_denc_cmd.standard_selection; + buffer[2] = adev->config.hdmi_denc_cmd.enable; + buffer[3] = adev->config.hdmi_denc_cmd.macrovision_enable; + buffer[4] = adev->config.hdmi_denc_cmd.internal_generator; + + *length = AV8100_COMMAND_DENC_SIZE - 1; + return 0; +} + +static int configuration_hdmi_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_cmd.hdmi_mode; + buffer[1] = adev->config.hdmi_cmd.hdmi_format; + buffer[2] = adev->config.hdmi_cmd.dvi_format; + + *length = AV8100_COMMAND_HDMI_SIZE - 1; + return 0; +} + +static int configuration_hdcp_sendkey_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_hdcp_send_key_cmd.key_number; + memcpy(&buffer[1], adev->config.hdmi_hdcp_send_key_cmd.data, + adev->config.hdmi_hdcp_send_key_cmd.data_len); + + *length = adev->config.hdmi_hdcp_send_key_cmd.data_len + 1; + return 0; +} + +static int configuration_hdcp_management_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_hdcp_management_format_cmd.req_type; + buffer[1] = adev->config.hdmi_hdcp_management_format_cmd.encr_use; + + *length = AV8100_COMMAND_HDCP_MANAGEMENT_SIZE - 1; + return 0; +} + +static int configuration_infoframe_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_infoframes_cmd.type; + buffer[1] = adev->config.hdmi_infoframes_cmd.version; + buffer[2] = adev->config.hdmi_infoframes_cmd.length; + buffer[3] = adev->config.hdmi_infoframes_cmd.crc; + memcpy(&buffer[4], adev->config.hdmi_infoframes_cmd.data, + HDMI_INFOFRAME_DATA_SIZE); + + *length = adev->config.hdmi_infoframes_cmd.length + 4; + return 0; +} + +static int av8100_edid_section_readback_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_edid_section_readback_cmd.address; + buffer[1] = adev->config.hdmi_edid_section_readback_cmd. + block_number; + + *length = AV8100_COMMAND_EDID_SECTION_READBACK_SIZE - 1; + return 0; +} + +static int configuration_pattern_generator_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_pattern_generator_cmd.pattern_type; + buffer[1] = adev->config.hdmi_pattern_generator_cmd. + pattern_video_format; + buffer[2] = adev->config.hdmi_pattern_generator_cmd. + pattern_audio_mode; + + *length = AV8100_COMMAND_PATTERNGENERATOR_SIZE - 1; + return 0; +} + +static int configuration_fuse_aes_key_get(struct av8100_device *adev, + char *buffer, unsigned int *length) +{ + buffer[0] = adev->config.hdmi_fuse_aes_key_cmd.fuse_operation; + if (adev->config.hdmi_fuse_aes_key_cmd.fuse_operation) { + /* Write key command */ + memcpy(&buffer[1], adev->config.hdmi_fuse_aes_key_cmd.key, + HDMI_FUSE_AES_KEY_SIZE); + + *length = AV8100_COMMAND_FUSE_AES_KEY_SIZE - 1; + } else { + /* Check key command */ + *length = AV8100_COMMAND_FUSE_AES_CHK_SIZE - 1; + } + return 0; +} + +static int get_command_return_first(struct i2c_client *i2c, + enum av8100_command_type command_type) { + int retval = 0; + char val; + struct device *dev = &i2c->dev; + + retval = read_single_byte(i2c, AV8100_COMMAND_OFFSET, &val); + if (retval) { + dev_dbg(dev, "%s 1st ret failed\n", __func__); + return retval; + } + + if (val != (0x80 | command_type)) { + dev_dbg(dev, "%s 1st ret wrong:%x\n", __func__, val); + return -EFAULT; + } + + return 0; +} + +static int get_command_return_data(struct i2c_client *i2c, + enum av8100_command_type command_type, + u8 *command_buffer, + u8 *buffer_length, + u8 *buffer) +{ + int retval = 0; + char val; + int index = 0; + struct device *dev = &i2c->dev; + + if (buffer_length) + *buffer_length = 0; + + switch (command_type) { + case AV8100_COMMAND_VIDEO_INPUT_FORMAT: + case AV8100_COMMAND_AUDIO_INPUT_FORMAT: + case AV8100_COMMAND_VIDEO_OUTPUT_FORMAT: + case AV8100_COMMAND_VIDEO_SCALING_FORMAT: + case AV8100_COMMAND_COLORSPACECONVERSION: + case AV8100_COMMAND_CEC_MESSAGE_WRITE: + case AV8100_COMMAND_DENC: + case AV8100_COMMAND_HDMI: + case AV8100_COMMAND_INFOFRAMES: + case AV8100_COMMAND_PATTERNGENERATOR: + /* Get the second return byte */ + retval = read_single_byte(i2c, + AV8100_2ND_RET_BYTE_OFFSET, &val); + if (retval) + goto get_command_return_data_fail2r; + + if (val) { + retval = -EFAULT; + goto get_command_return_data_fail2v; + } + break; + + case AV8100_COMMAND_CEC_MESSAGE_READ_BACK: + if ((buffer == NULL) || (buffer_length == NULL)) { + retval = -EINVAL; + goto get_command_return_data_fail; + } + + /* Get the return buffer length */ + retval = read_single_byte(i2c, AV8100_CEC_ADDR_OFFSET, &val); + if (retval) + goto get_command_return_data_fail; + + dev_dbg(dev, "cec buflen:%d\n", val); + *buffer_length = val; + + if (*buffer_length > + HDMI_CEC_READ_MAXSIZE) { + dev_dbg(dev, "CEC size too large %d\n", + *buffer_length); + *buffer_length = HDMI_CEC_READ_MAXSIZE; + } + + dev_dbg(dev, "return data: "); + + /* Get the return buffer */ + for (index = 0; index < *buffer_length; ++index) { + retval = read_single_byte(i2c, + AV8100_CEC_RET_BUF_OFFSET + index, &val); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = val; + dev_dbg(dev, "%02x ", *(buffer + index)); + } + } + + dev_dbg(dev, "\n"); + break; + + case AV8100_COMMAND_HDCP_MANAGEMENT: + { + u8 nrdev; + u8 devcnt; + int cnt; + + /* Get the second return byte */ + retval = read_single_byte(i2c, + AV8100_2ND_RET_BYTE_OFFSET, &val); + if (retval) { + goto get_command_return_data_fail2r; + } else { + /* Check the second return byte */ + if (val) + goto get_command_return_data_fail2v; + } + + if ((buffer == NULL) || (buffer_length == NULL)) + /* Ignore return data */ + break; + + dev_dbg(dev, "req_type:%02x ", command_buffer[0]); + + /* Check if revoc list data is requested */ + if (command_buffer[0] != + HDMI_REQUEST_FOR_REVOCATION_LIST_INPUT) { + *buffer_length = 0; + break; + } + + dev_dbg(dev, "return data: "); + + /* Get the return buffer */ + for (cnt = 0; cnt < HDMI_HDCP_MGMT_BKSV_SIZE; cnt++) { + retval = read_single_byte(i2c, + AV8100_HDCP_RET_BUF_OFFSET + index, &val); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = val; + dev_dbg(dev, "%02x ", *(buffer + index)); + } + index++; + } + + /* Get Device count */ + retval = read_single_byte(i2c, + AV8100_HDCP_RET_BUF_OFFSET + index, &nrdev); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = nrdev; + dev_dbg(dev, "%02x ", *(buffer + index)); + } + index++; + + /* Determine number of devices */ + nrdev &= HDMI_HDCP_MGMT_DEVICE_MASK; + if (nrdev > HDMI_HDCP_MGMT_MAX_DEVICES_SIZE) + nrdev = HDMI_HDCP_MGMT_MAX_DEVICES_SIZE; + + /* Get Bksv for each connected equipment */ + for (devcnt = 0; devcnt < nrdev; devcnt++) + for (cnt = 0; cnt < HDMI_HDCP_MGMT_BKSV_SIZE; cnt++) { + retval = read_single_byte(i2c, + AV8100_HDCP_RET_BUF_OFFSET + index, + &val); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = val; + dev_dbg(dev, "%02x ", + *(buffer + index)); + } + index++; + } + + if (nrdev == 0) + goto hdcp_management_end; + + /* Get SHA signature */ + for (cnt = 0; cnt < HDMI_HDCP_MGMT_SHA_SIZE - 1; cnt++) { + retval = read_single_byte(i2c, + AV8100_HDCP_RET_BUF_OFFSET + index, &val); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = val; + dev_dbg(dev, "%02x ", *(buffer + index)); + } + index++; + } + +hdcp_management_end: + *buffer_length = index; + + dev_dbg(dev, "\n"); + } + break; + + case AV8100_COMMAND_EDID_SECTION_READBACK: + if ((buffer == NULL) || (buffer_length == NULL)) { + retval = -EINVAL; + goto get_command_return_data_fail; + } + + /* Return buffer length is fixed */ + *buffer_length = HDMI_EDIDREAD_SIZE; + + dev_dbg(dev, "return data: "); + + /* Get the return buffer */ + for (index = 0; index < *buffer_length; ++index) { + retval = read_single_byte(i2c, + AV8100_EDID_RET_BUF_OFFSET + index, &val); + if (retval) { + *buffer_length = 0; + goto get_command_return_data_fail; + } else { + *(buffer + index) = val; + dev_dbg(dev, "%02x ", *(buffer + index)); + } + } + + dev_dbg(dev, "\n"); + break; + + case AV8100_COMMAND_FUSE_AES_KEY: + if ((buffer == NULL) || (buffer_length == NULL)) { + retval = -EINVAL; + goto get_command_return_data_fail; + } + + /* Get the second return byte */ + retval = read_single_byte(i2c, + AV8100_2ND_RET_BYTE_OFFSET, &val); + + if (retval) + goto get_command_return_data_fail2r; + + /* Check the second return byte */ + if (val) { + retval = -EFAULT; + goto get_command_return_data_fail2v; + } + + /* Return buffer length is fixed */ + *buffer_length = HDMI_FUSE_AES_KEY_RET_SIZE; + + /* Get CRC */ + retval = read_single_byte(i2c, + AV8100_FUSE_CRC_OFFSET, &val); + if (retval) + goto get_command_return_data_fail; + + *buffer = val; + dev_dbg(dev, "CRC:%02x ", val); + + /* Get programmed status */ + retval = read_single_byte(i2c, + AV8100_FUSE_PRGD_OFFSET, &val); + if (retval) + goto get_command_return_data_fail; + + *(buffer + 1) = val; + + dev_dbg(dev, "programmed:%02x ", val); + break; + + case AV8100_COMMAND_HDCP_SENDKEY: + if ((command_buffer[0] == HDMI_LOADAES_END_BLK_NR) && + ((buffer == NULL) || (buffer_length == NULL))) { + retval = -EINVAL; + goto get_command_return_data_fail; + } + + /* Get the second return byte */ + retval = read_single_byte(i2c, + AV8100_2ND_RET_BYTE_OFFSET, &val); + if (retval) + goto get_command_return_data_fail2r; + + if (val) { + retval = -EFAULT; + goto get_command_return_data_fail2v; + } + + if (command_buffer[0] == HDMI_LOADAES_END_BLK_NR) { + /* Return CRC32 if last AES block */ + int cnt; + + dev_dbg(dev, "CRC32:"); + for (cnt = 0; cnt < HDMI_CRC32_SIZE; cnt++) { + if (read_single_byte(i2c, + AV8100_CRC32_OFFSET + cnt, &val)) + goto get_command_return_data_fail; + *(buffer + cnt) = val; + dev_dbg(dev, "%02x", val); + } + + *buffer_length = HDMI_CRC32_SIZE; + } + break; + + default: + retval = -EFAULT; + break; + } + + return retval; +get_command_return_data_fail2r: + dev_dbg(dev, "%s Reading 2nd return byte failed\n", __func__); + return retval; +get_command_return_data_fail2v: + dev_dbg(dev, "%s 2nd return byte is wrong:%x\n", __func__, val); + return retval; +get_command_return_data_fail: + dev_dbg(dev, "%s FAIL\n", __func__); + return retval; +} + +static int av8100_powerup1(struct av8100_device *adev) +{ + int retval; + struct av8100_platform_data *pdata = adev->dev->platform_data; + + /* Regulator enable */ + if ((adev->params.regulator_pwr) && + (adev->params.regulator_requested == false)) { + retval = regulator_enable(adev->params.regulator_pwr); + if (retval < 0) { + dev_warn(adev->dev, "%s: regulator_enable failed\n", + __func__); + return retval; + } + dev_dbg(adev->dev, "regulator_enable ok\n"); + adev->params.regulator_requested = true; + } + + /* Reset av8100 */ + gpio_set_value_cansleep(pdata->reset, 1); + + /* Need to wait before proceeding */ + mdelay(AV8100_WAITTIME_1MS); + + av8100_set_state(adev, AV8100_OPMODE_STANDBY); + + if (pdata->alt_powerupseq) { + dev_dbg(adev->dev, "powerup seq alt\n"); + retval = av8100_5V_w(0, 0, AV8100_ON_TIME); + if (retval) { + dev_err(adev->dev, "%s reg_wr err 1\n", __func__); + goto av8100_powerup1_err; + } + + udelay(AV8100_WATTIME_100US); + + retval = av8100_reg_stby_pend_int_w( + AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_HIGH); + if (retval) { + dev_err(adev->dev, "%s reg_wr err 2\n", __func__); + goto av8100_powerup1_err; + } + + udelay(AV8100_WATTIME_100US); + + retval = av8100_reg_stby_w(AV8100_STANDBY_CPD_LOW, + AV8100_STANDBY_STBY_HIGH, pdata->mclk_freq); + if (retval) { + dev_err(adev->dev, "%s reg_wr err 3\n", __func__); + goto av8100_powerup1_err; + } + + mdelay(AV8100_WAITTIME_1MS); + + retval = av8100_reg_stby_w(AV8100_STANDBY_CPD_LOW, + AV8100_STANDBY_STBY_LOW, pdata->mclk_freq); + if (retval) { + dev_err(adev->dev, "%s reg_wr err 4\n", __func__); + goto av8100_powerup1_err; + } + + mdelay(AV8100_WAITTIME_1MS); + + retval = av8100_reg_stby_pend_int_w( + AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_LOW); + if (retval) { + dev_err(adev->dev, "%s reg_wr err 5\n", __func__); + goto av8100_powerup1_err; + } + + mdelay(AV8100_WAITTIME_1MS); + } + + retval = request_irq(pdata->irq, av8100_intr_handler, + IRQF_TRIGGER_RISING, "av8100", adev); + if (retval == 0) + adev->params.irq_requested = true; + else + dev_err(adev->dev, "request_irq %d failed %d\n", + pdata->irq, retval); + + return retval; + +av8100_powerup1_err: + av8100_powerdown(); + return -EFAULT; +} + +static int av8100_powerup2(struct av8100_device *adev) +{ + int retval; + + /* ON time & OFF time on 5v HDMI plug detect */ + retval = av8100_5V_w(adev->params.denc_off_time, + adev->params.hdmi_off_time, + adev->params.on_time); + if (retval) { + dev_err(adev->dev, + "Failed to write the value to av8100 register\n"); + return retval; + } + + mdelay(AV8100_WAITTIME_1MS); + + av8100_set_state(adev, AV8100_OPMODE_SCAN); + + return 0; +} + +static int register_read_internal(u8 offset, u8 *value) +{ + int retval = 0; + struct i2c_client *i2c; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EFAULT; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + i2c = adev->config.client; + + /* Read from register */ + retval = read_single_byte(i2c, offset, value); + if (retval) { + dev_dbg(adev->dev, + "Failed to read the value from av8100 register\n"); + return -EFAULT; + } + + return retval; +} + +static int register_write_internal(u8 offset, u8 value) +{ + int retval; + struct i2c_client *i2c; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EFAULT; + + i2c = adev->config.client; + + /* Write to register */ + retval = write_single_byte(i2c, offset, value); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + return 0; +} + +int av8100_powerscan(void) +{ + int retval; + struct av8100_device *adev; + struct av8100_platform_data *pdata; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EFAULT; + + pdata = adev->dev->platform_data; + + dev_dbg(adev->dev, "%s\n", __func__); + + if (av8100_status_get().av8100_state > AV8100_OPMODE_SCAN) { + dev_dbg(adev->dev, "set to scan mode\n"); + + av8100_disable_interrupt(); + + /* Stby mode */ + retval = av8100_reg_stby_w(AV8100_STANDBY_CPD_LOW, + AV8100_STANDBY_STBY_LOW, pdata->mclk_freq); + if (retval) { + dev_err(adev->dev, + "Failed to write to av8100 register\n"); + return retval; + } + + /* Remove APE OPP requirement */ + if (adev->params.opp_requested) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)adev->miscdev.name); + prcmu_qos_remove_requirement(PRCMU_QOS_DDR_OPP, + (char *)adev->miscdev.name); + adev->params.opp_requested = false; + } + + /* Clock disable */ + if (adev->params.inputclk && + adev->params.inputclk_requested) { + clk_disable(adev->params.inputclk); + adev->params.inputclk_requested = false; + } + + mdelay(AV8100_WAITTIME_1MS); + + av8100_enable_interrupt(); + + av8100_set_state(adev, AV8100_OPMODE_SCAN); + } + + return 0; +} +EXPORT_SYMBOL(av8100_powerscan); + +int av8100_powerup(void) +{ + int ret = 0; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EFAULT; + + if (av8100_status_get().av8100_state == AV8100_OPMODE_UNDEFINED) + return -EINVAL; + + if (av8100_status_get().av8100_state < AV8100_OPMODE_STANDBY) { + ret = av8100_powerup1(adev); + if (ret) { + dev_err(adev->dev, "av8100_powerup1 fail\n"); + return -EFAULT; + } + } + + if (av8100_status_get().av8100_state < AV8100_OPMODE_SCAN) + ret = av8100_powerup2(adev); + + av8100_enable_interrupt(); + + return ret; +} +EXPORT_SYMBOL(av8100_powerup); + +int av8100_powerdown(void) +{ + int retval = 0; + struct av8100_device *adev; + struct av8100_platform_data *pdata; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EFAULT; + + pdata = adev->dev->platform_data; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + goto av8100_powerdown_end; + + av8100_disable_interrupt(); + + if (adev->params.irq_requested) + free_irq(pdata->irq, adev); + adev->params.irq_requested = false; + + if (pdata->alt_powerupseq) { + retval = av8100_reg_stby_pend_int_w( + AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_HIGH); + + if (retval) + dev_err(adev->dev, "%s reg_wr err\n", __func__); + msleep(AV8100_WAITTIME_50MS); + } + + /* Remove APE OPP requirement */ + if (adev->params.opp_requested) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)adev->miscdev.name); + prcmu_qos_remove_requirement(PRCMU_QOS_DDR_OPP, + (char *)adev->miscdev.name); + adev->params.opp_requested = false; + } + + /* Clock disable */ + if (adev->params.inputclk && adev->params.inputclk_requested) { + clk_disable(adev->params.inputclk); + adev->params.inputclk_requested = false; + } + + av8100_set_state(adev, AV8100_OPMODE_SHUTDOWN); + + gpio_set_value_cansleep(pdata->reset, 0); + + /* Regulator disable */ + if ((adev->params.regulator_pwr) && + (adev->params.regulator_requested)) { + dev_dbg(adev->dev, "regulator_disable\n"); + regulator_disable(adev->params.regulator_pwr); + adev->params.regulator_requested = false; + } + + if (pdata->alt_powerupseq) + mdelay(AV8100_WAITTIME_5MS); + +av8100_powerdown_end: + return retval; +} +EXPORT_SYMBOL(av8100_powerdown); + +int av8100_download_firmware(enum interface_type if_type) +{ + int retval; + int temp = 0x0; + int increment = 15; + int index = 0; + int size = 0x0; + char val = 0x0; + char checksum = 0; + int cnt; + int cnt_max; + struct i2c_client *i2c; + u8 uc; + u8 fdl; + u8 hld; + u8 wa; + u8 ra; + struct av8100_platform_data *pdata; + const struct firmware *fw_file; + u8 *fw_buff; + int fw_bytes; + struct av8100_device *adev; + struct av8100_status status; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + LOCK_AV8100_FWDL; + + status = av8100_status_get(); + if (status.av8100_state <= AV8100_OPMODE_SHUTDOWN) { + retval = -EINVAL; + goto av8100_download_firmware_err2; + } + + if (status.av8100_state >= AV8100_OPMODE_INIT) { + dev_dbg(adev->dev, "FW already ok\n"); + retval = 0; + goto av8100_download_firmware_err2; + } + + av8100_set_state(adev, AV8100_OPMODE_INIT); + + pdata = adev->dev->platform_data; + + /* Request firmware */ + if (request_firmware(&fw_file, + AV8100_FW_FILENAME, + adev->dev)) { + dev_err(adev->dev, "fw request failed\n"); + retval = -EFAULT; + goto av8100_download_firmware_err2; + } + + /* Master clock timing, running */ + retval = av8100_reg_stby_w(AV8100_STANDBY_CPD_LOW, + AV8100_STANDBY_STBY_HIGH, pdata->mclk_freq); + if (retval) { + dev_err(adev->dev, + "Failed to write the value to av8100 register\n"); + goto av8100_download_firmware_err; + } + + mdelay(AV8100_WAITTIME_1MS); + + /* Clock enable */ + if (adev->params.inputclk && + adev->params.inputclk_requested == false) { + if (clk_enable(adev->params.inputclk)) { + dev_err(adev->dev, "inputclk en failed\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + adev->params.inputclk_requested = true; + } + + /* Request 100% APE OPP */ + if (adev->params.opp_requested == false) { + if (prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char *)adev->miscdev.name, 100)) { + dev_err(adev->dev, "APE OPP 100 failed\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + if (prcmu_qos_add_requirement(PRCMU_QOS_DDR_OPP, + (char *)adev->miscdev.name, 100)) { + dev_err(adev->dev, "DDR OPP 100 failed\n"); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)adev->miscdev.name); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + adev->params.opp_requested = true; + } + + msleep(AV8100_WAITTIME_10MS); + + /* Prepare firmware data */ + fw_bytes = fw_file->size; + fw_buff = (u8 *)fw_file->data; + dev_dbg(adev->dev, "fw size:%d\n", fw_bytes); + + i2c = adev->config.client; + + /* Enable firmware download */ + retval = av8100_reg_gen_ctrl_w( + AV8100_GENERAL_CONTROL_FDL_HIGH, + AV8100_GENERAL_CONTROL_HLD_HIGH, + AV8100_GENERAL_CONTROL_WA_LOW, + AV8100_GENERAL_CONTROL_RA_LOW); + if (retval) { + dev_err(adev->dev, + "Failed to write the value to av8100 register\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + retval = av8100_reg_gen_ctrl_r(&fdl, &hld, &wa, &ra); + if (retval) { + dev_err(adev->dev, + "Failed to read the value from av8100 register\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } else { + dev_dbg(adev->dev, "GENERAL_CONTROL_REG register fdl:%d " + "hld:%d wa:%d ra:%d\n", fdl, hld, wa, ra); + } + + LOCK_AV8100_HW; + + temp = fw_bytes % increment; + for (size = 0; size < (fw_bytes-temp); size = size + increment, + index += increment) { + if (if_type == I2C_INTERFACE) { + retval = write_multi_byte(i2c, + AV8100_FIRMWARE_DOWNLOAD_ENTRY, fw_buff + size, + increment); + if (retval) { + dev_dbg(adev->dev, "Failed to download the " + "av8100 firmware\n"); + UNLOCK_AV8100_HW; + retval = -EFAULT; + goto av8100_download_firmware_err; + } + } else if (if_type == DSI_INTERFACE) { + dev_dbg(adev->dev, + "DSI_INTERFACE is currently not supported\n"); + UNLOCK_AV8100_HW; + retval = -EINVAL; + goto av8100_download_firmware_err; + } else { + UNLOCK_AV8100_HW; + retval = -EINVAL; + goto av8100_download_firmware_err; + } + } + + /* Transfer last firmware bytes */ + if (if_type == I2C_INTERFACE) { + retval = write_multi_byte(i2c, + AV8100_FIRMWARE_DOWNLOAD_ENTRY, fw_buff + size, temp); + if (retval) { + dev_dbg(adev->dev, + "Failed to download the av8100 firmware\n"); + UNLOCK_AV8100_HW; + retval = -EFAULT; + goto av8100_download_firmware_err; + } + } else if (if_type == DSI_INTERFACE) { + /* TODO: Add support for DSI firmware download */ + UNLOCK_AV8100_HW; + retval = -EINVAL; + goto av8100_download_firmware_err; + } else { + UNLOCK_AV8100_HW; + retval = -EINVAL; + goto av8100_download_firmware_err; + } + + /* check transfer*/ + for (size = 0; size < fw_bytes; size++) + checksum = checksum ^ fw_buff[size]; + + UNLOCK_AV8100_HW; + + retval = av8100_reg_fw_dl_entry_r(&val); + if (retval) { + dev_dbg(adev->dev, + "Failed to read the value from the av8100 register\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + dev_dbg(adev->dev, "checksum:%x,val:%x\n", checksum, val); + + if (checksum != val) { + dev_dbg(adev->dev, + ">Fw downloading.... FAIL checksum issue\n"); + dev_dbg(adev->dev, "checksum = %d\n", checksum); + dev_dbg(adev->dev, "checksum read: %d\n", val); + retval = -EFAULT; + goto av8100_download_firmware_err; + } else { + dev_dbg(adev->dev, ">Fw downloading.... success\n"); + } + + /* Set to idle mode */ + av8100_reg_gen_ctrl_w(AV8100_GENERAL_CONTROL_FDL_LOW, + AV8100_GENERAL_CONTROL_HLD_LOW, AV8100_GENERAL_CONTROL_WA_LOW, + AV8100_GENERAL_CONTROL_RA_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to the av8100 register\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + /* Wait Internal Micro controler ready */ + cnt = 0; + cnt_max = sizeof(waittime_retry) / sizeof(waittime_retry[0]); + retval = av8100_reg_gen_status_r(NULL, NULL, NULL, &uc, + NULL, NULL); + while ((retval == 0) && (uc != 0x1) && (cnt < cnt_max)) { + mdelay(waittime_retry[cnt]); + retval = av8100_reg_gen_status_r(NULL, NULL, NULL, + &uc, NULL, NULL); + cnt++; + } + dev_dbg(adev->dev, "av8100 fwdl cnt:%d\n", cnt); + + if (retval) { + dev_dbg(adev->dev, + "Failed to read the value from the av8100 register\n"); + retval = -EFAULT; + goto av8100_download_firmware_err; + } + + if (uc != 0x1) + dev_dbg(adev->dev, "UC is not ready\n"); + + release_firmware(fw_file); + + if (adev->chip_version != 1) { + char *cut_str; + + /* Get cut version */ + retval = read_single_byte(i2c, AV8100_CUTVER_OFFSET, &val); + if (retval) { + dev_err(adev->dev, "Read cut ver failed\n"); + return retval; + } + + switch (val) { + case 0x00: + cut_str = CUT_STR_0; + break; + case 0x01: + cut_str = CUT_STR_1; + break; + case 0x03: + cut_str = CUT_STR_3; + break; + case 0x30: + cut_str = CUT_STR_30; + break; + default: + cut_str = CUT_STR_UNKNOWN; + break; + } + dev_dbg(adev->dev, "Cut ver %d %s\n", val, cut_str); + } + + av8100_set_state(adev, AV8100_OPMODE_IDLE); + + UNLOCK_AV8100_FWDL; + return 0; + +av8100_download_firmware_err: + release_firmware(fw_file); + + /* Remove APE OPP requirement */ + if (adev->params.opp_requested) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, + (char *)adev->miscdev.name); + prcmu_qos_remove_requirement(PRCMU_QOS_DDR_OPP, + (char *)adev->miscdev.name); + adev->params.opp_requested = false; + } + + /* Clock disable */ + if (adev->params.inputclk && adev->params.inputclk_requested) { + clk_disable(adev->params.inputclk); + adev->params.inputclk_requested = false; + } + +av8100_download_firmware_err2: + UNLOCK_AV8100_FWDL; + return retval; +} +EXPORT_SYMBOL(av8100_download_firmware); + +int av8100_disable_interrupt(void) +{ + int retval; + u8 hpdm = 0; + u8 cpdm = 0; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + if (!adev->params.ints_enabled) + return 0; + + retval = av8100_reg_stby_pend_int_w( + AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + retval = av8100_reg_gen_int_mask_w( + AV8100_GENERAL_INTERRUPT_MASK_EOCM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_VSIM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_VSOM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_CECM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_HDCPM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_UOVBM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_TEM_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + hpdm = adev->params.hpdm; + cpdm = adev->params.cpdm; + + retval = av8100_reg_stby_int_mask_w( + AV8100_STANDBY_INTERRUPT_MASK_HPDM_LOW, + AV8100_STANDBY_INTERRUPT_MASK_CPDM_LOW, + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_INPUT, + AV8100_STANDBY_INTERRUPT_MASK_IPOL_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + adev->params.hpdm = hpdm; + adev->params.cpdm = cpdm; + adev->params.ints_enabled = false; + + return 0; +} +EXPORT_SYMBOL(av8100_disable_interrupt); + +int av8100_enable_interrupt(void) +{ + int retval; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + if (adev->params.ints_enabled) + return 0; + + retval = av8100_reg_stby_pend_int_w( + AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW, + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + retval = av8100_reg_gen_int_mask_w( + AV8100_GENERAL_INTERRUPT_MASK_EOCM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_VSIM_LOW, + AV8100_GENERAL_INTERRUPT_MASK_VSOM_LOW, + adev->params.cecm, + adev->params.hdcpm, + adev->params.uovbm, + AV8100_GENERAL_INTERRUPT_MASK_TEM_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + retval = av8100_reg_stby_int_mask_w( + adev->params.hpdm, + adev->params.cpdm, + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_INPUT, + AV8100_STANDBY_INTERRUPT_MASK_IPOL_LOW); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + return -EFAULT; + } + + adev->params.ints_enabled = true; + + return 0; +} +EXPORT_SYMBOL(av8100_enable_interrupt); + +int av8100_reg_stby_w( + u8 cpd, u8 stby, u8 mclkrng) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_STANDBY_CPD(cpd) | AV8100_STANDBY_STBY(stby) | + AV8100_STANDBY_MCLKRNG(mclkrng); + + /* Write to register */ + retval = register_write_internal(AV8100_STANDBY, val); + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_w); + +static int av8100_5V_w(u8 denc_off, u8 hdmi_off, u8 on) +{ + u8 val; + int retval; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value. + * chip_version == 1 have one common off time + * chip_version > 1 support different off time for hdmi and tvout. */ + if (adev->chip_version == 1) + val = AV8100_HDMI_5_VOLT_TIME_OFF_TIME(hdmi_off) | + AV8100_HDMI_5_VOLT_TIME_ON_TIME(on); + else + val = AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME(denc_off) | + AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME(hdmi_off) | + AV8100_HDMI_5_VOLT_TIME_ON_TIME(on); + + /* Write to register */ + retval = register_write_internal(AV8100_HDMI_5_VOLT_TIME, val); + + UNLOCK_AV8100_HW; + + return retval; +} + +int av8100_reg_hdmi_5_volt_time_w(u8 denc_off, u8 hdmi_off, u8 on) +{ + int retval; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + retval = av8100_5V_w(denc_off, hdmi_off, on); + + /* Set vars */ + if (adev->chip_version > 1) + adev->params.denc_off_time = denc_off; + + adev->params.hdmi_off_time = hdmi_off; + if (on) + adev->params.on_time = on; + + return retval; +} +EXPORT_SYMBOL(av8100_reg_hdmi_5_volt_time_w); + +int av8100_reg_stby_int_mask_w( + u8 hpdm, u8 cpdm, u8 stbygpiocfg, u8 ipol) +{ + int retval; + u8 val; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_STANDBY_INTERRUPT_MASK_HPDM(hpdm) | + AV8100_STANDBY_INTERRUPT_MASK_CPDM(cpdm) | + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG(stbygpiocfg) | + AV8100_STANDBY_INTERRUPT_MASK_IPOL(ipol); + + /* Write to register */ + retval = register_write_internal(AV8100_STANDBY_INTERRUPT_MASK, val); + + adev->params.hpdm = hpdm; + adev->params.cpdm = cpdm; + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_int_mask_w); + +int av8100_reg_stby_pend_int_w( + u8 hpdi, u8 cpdi, u8 oni, u8 bpdig) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_STANDBY_PENDING_INTERRUPT_HPDI(hpdi) | + AV8100_STANDBY_PENDING_INTERRUPT_CPDI(cpdi) | + AV8100_STANDBY_PENDING_INTERRUPT_ONI(oni) | + AV8100_STANDBY_PENDING_INTERRUPT_BPDIG(bpdig); + + /* Write to register */ + retval = register_write_internal(AV8100_STANDBY_PENDING_INTERRUPT, val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_pend_int_w); + +int av8100_reg_gen_int_mask_w( + u8 eocm, u8 vsim, u8 vsom, u8 cecm, u8 hdcpm, u8 uovbm, u8 tem) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_GENERAL_INTERRUPT_MASK_EOCM(eocm) | + AV8100_GENERAL_INTERRUPT_MASK_VSIM(vsim) | + AV8100_GENERAL_INTERRUPT_MASK_VSOM(vsom) | + AV8100_GENERAL_INTERRUPT_MASK_CECM(cecm) | + AV8100_GENERAL_INTERRUPT_MASK_HDCPM(hdcpm) | + AV8100_GENERAL_INTERRUPT_MASK_UOVBM(uovbm) | + AV8100_GENERAL_INTERRUPT_MASK_TEM(tem); + + /* Write to register */ + retval = register_write_internal(AV8100_GENERAL_INTERRUPT_MASK, val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_int_mask_w); + +int av8100_reg_gen_int_w( + u8 eoci, u8 vsii, u8 vsoi, u8 ceci, u8 hdcpi, u8 uovbi) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_GENERAL_INTERRUPT_EOCI(eoci) | + AV8100_GENERAL_INTERRUPT_VSII(vsii) | + AV8100_GENERAL_INTERRUPT_VSOI(vsoi) | + AV8100_GENERAL_INTERRUPT_CECI(ceci) | + AV8100_GENERAL_INTERRUPT_HDCPI(hdcpi) | + AV8100_GENERAL_INTERRUPT_UOVBI(uovbi); + + /* Write to register */ + retval = register_write_internal(AV8100_GENERAL_INTERRUPT, val); + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_int_w); + +int av8100_reg_gpio_conf_w( + u8 dat3dir, u8 dat3val, u8 dat2dir, u8 dat2val, u8 dat1dir, + u8 dat1val, u8 ucdbg) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_GPIO_CONFIGURATION_DAT3DIR(dat3dir) | + AV8100_GPIO_CONFIGURATION_DAT3VAL(dat3val) | + AV8100_GPIO_CONFIGURATION_DAT2DIR(dat2dir) | + AV8100_GPIO_CONFIGURATION_DAT2VAL(dat2val) | + AV8100_GPIO_CONFIGURATION_DAT1DIR(dat1dir) | + AV8100_GPIO_CONFIGURATION_DAT1VAL(dat1val) | + AV8100_GPIO_CONFIGURATION_UCDBG(ucdbg); + + /* Write to register */ + retval = register_write_internal(AV8100_GPIO_CONFIGURATION, val); + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gpio_conf_w); + +int av8100_reg_gen_ctrl_w( + u8 fdl, u8 hld, u8 wa, u8 ra) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_GENERAL_CONTROL_FDL(fdl) | + AV8100_GENERAL_CONTROL_HLD(hld) | + AV8100_GENERAL_CONTROL_WA(wa) | + AV8100_GENERAL_CONTROL_RA(ra); + + /* Write to register */ + retval = register_write_internal(AV8100_GENERAL_CONTROL, val); + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_ctrl_w); + +int av8100_reg_fw_dl_entry_w( + u8 mbyte_code_entry) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Set register value */ + val = AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY( + mbyte_code_entry); + + /* Write to register */ + retval = register_write_internal(AV8100_FIRMWARE_DOWNLOAD_ENTRY, val); + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_fw_dl_entry_w); + +int av8100_reg_w( + u8 offset, u8 value) +{ + int retval = 0; + struct i2c_client *i2c; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + i2c = adev->config.client; + + /* Write to register */ + retval = write_single_byte(i2c, offset, value); + if (retval) { + dev_dbg(adev->dev, + "Failed to write the value to av8100 register\n"); + UNLOCK_AV8100_HW; + return -EFAULT; + } + + UNLOCK_AV8100_HW; + return 0; +} +EXPORT_SYMBOL(av8100_reg_w); + +int av8100_reg_stby_r( + u8 *cpd, u8 *stby, u8 *hpds, u8 *cpds, u8 *mclkrng) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_STANDBY, &val); + + /* Set return params */ + if (cpd) + *cpd = AV8100_STANDBY_CPD_GET(val); + if (stby) + *stby = AV8100_STANDBY_STBY_GET(val); + if (hpds) + *hpds = AV8100_STANDBY_HPDS_GET(val); + if (cpds) + *cpds = AV8100_STANDBY_CPDS_GET(val); + if (mclkrng) + *mclkrng = AV8100_STANDBY_MCLKRNG_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_r); + +int av8100_reg_hdmi_5_volt_time_r( + u8 *denc_off_time, u8 *hdmi_off_time, u8 *on_time) +{ + int retval; + u8 val; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_HDMI_5_VOLT_TIME, &val); + + /* Set return params */ + if (adev->chip_version == 1) { + if (denc_off_time) + *denc_off_time = 0; + if (hdmi_off_time) + *hdmi_off_time = + AV8100_HDMI_5_VOLT_TIME_OFF_TIME_GET(val); + } else { + if (denc_off_time) + *denc_off_time = + AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME_GET(val); + if (hdmi_off_time) + *hdmi_off_time = + AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME_GET(val); + } + + if (on_time) + *on_time = AV8100_HDMI_5_VOLT_TIME_ON_TIME_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_hdmi_5_volt_time_r); + +int av8100_reg_stby_int_mask_r( + u8 *hpdm, u8 *cpdm, u8 *stbygpiocfg, u8 *ipol) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_STANDBY_INTERRUPT_MASK, &val); + + /* Set return params */ + if (hpdm) + *hpdm = AV8100_STANDBY_INTERRUPT_MASK_HPDM_GET(val); + if (cpdm) + *cpdm = AV8100_STANDBY_INTERRUPT_MASK_CPDM_GET(val); + if (stbygpiocfg) + *stbygpiocfg = + AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_GET(val); + if (ipol) + *ipol = AV8100_STANDBY_INTERRUPT_MASK_IPOL_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_int_mask_r); + +int av8100_reg_stby_pend_int_r( + u8 *hpdi, u8 *cpdi, u8 *oni, u8 *sid) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_STANDBY_PENDING_INTERRUPT, + &val); + + /* Set return params */ + if (hpdi) + *hpdi = AV8100_STANDBY_PENDING_INTERRUPT_HPDI_GET(val); + if (cpdi) + *cpdi = AV8100_STANDBY_PENDING_INTERRUPT_CPDI_GET(val); + if (oni) + *oni = AV8100_STANDBY_PENDING_INTERRUPT_ONI_GET(val); + if (sid) + *sid = AV8100_STANDBY_PENDING_INTERRUPT_SID_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_stby_pend_int_r); + +int av8100_reg_gen_int_mask_r( + u8 *eocm, + u8 *vsim, + u8 *vsom, + u8 *cecm, + u8 *hdcpm, + u8 *uovbm, + u8 *tem) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_GENERAL_INTERRUPT_MASK, &val); + + /* Set return params */ + if (eocm) + *eocm = AV8100_GENERAL_INTERRUPT_MASK_EOCM_GET(val); + if (vsim) + *vsim = AV8100_GENERAL_INTERRUPT_MASK_VSIM_GET(val); + if (vsom) + *vsom = AV8100_GENERAL_INTERRUPT_MASK_VSOM_GET(val); + if (cecm) + *cecm = AV8100_GENERAL_INTERRUPT_MASK_CECM_GET(val); + if (hdcpm) + *hdcpm = AV8100_GENERAL_INTERRUPT_MASK_HDCPM_GET(val); + if (uovbm) + *uovbm = AV8100_GENERAL_INTERRUPT_MASK_UOVBM_GET(val); + if (tem) + *tem = AV8100_GENERAL_INTERRUPT_MASK_TEM_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_int_mask_r); + +int av8100_reg_gen_int_r( + u8 *eoci, + u8 *vsii, + u8 *vsoi, + u8 *ceci, + u8 *hdcpi, + u8 *uovbi, + u8 *tei) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_GENERAL_INTERRUPT, &val); + + /* Set return params */ + if (eoci) + *eoci = AV8100_GENERAL_INTERRUPT_EOCI_GET(val); + if (vsii) + *vsii = AV8100_GENERAL_INTERRUPT_VSII_GET(val); + if (vsoi) + *vsoi = AV8100_GENERAL_INTERRUPT_VSOI_GET(val); + if (ceci) + *ceci = AV8100_GENERAL_INTERRUPT_CECI_GET(val); + if (hdcpi) + *hdcpi = AV8100_GENERAL_INTERRUPT_HDCPI_GET(val); + if (uovbi) + *uovbi = AV8100_GENERAL_INTERRUPT_UOVBI_GET(val); + if (tei) + *tei = AV8100_GENERAL_INTERRUPT_TEI_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_int_r); + +int av8100_reg_gen_status_r( + u8 *cectxerr, + u8 *cecrec, + u8 *cectrx, + u8 *uc, + u8 *onuvb, + u8 *hdcps) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_GENERAL_STATUS, &val); + + /* Set return params */ + if (cectxerr) + *cectxerr = AV8100_GENERAL_STATUS_CECTXERR_GET(val); + if (cecrec) + *cecrec = AV8100_GENERAL_STATUS_CECREC_GET(val); + if (cectrx) + *cectrx = AV8100_GENERAL_STATUS_CECTRX_GET(val); + if (uc) + *uc = AV8100_GENERAL_STATUS_UC_GET(val); + if (onuvb) + *onuvb = AV8100_GENERAL_STATUS_ONUVB_GET(val); + if (hdcps) + *hdcps = AV8100_GENERAL_STATUS_HDCPS_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_status_r); + +int av8100_reg_gpio_conf_r( + u8 *dat3dir, + u8 *dat3val, + u8 *dat2dir, + u8 *dat2val, + u8 *dat1dir, + u8 *dat1val, + u8 *ucdbg) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_GPIO_CONFIGURATION, &val); + + /* Set return params */ + if (dat3dir) + *dat3dir = AV8100_GPIO_CONFIGURATION_DAT3DIR_GET(val); + if (dat3val) + *dat3val = AV8100_GPIO_CONFIGURATION_DAT3VAL_GET(val); + if (dat2dir) + *dat2dir = AV8100_GPIO_CONFIGURATION_DAT2DIR_GET(val); + if (dat2val) + *dat2val = AV8100_GPIO_CONFIGURATION_DAT2VAL_GET(val); + if (dat1dir) + *dat1dir = AV8100_GPIO_CONFIGURATION_DAT1DIR_GET(val); + if (dat1val) + *dat1val = AV8100_GPIO_CONFIGURATION_DAT1VAL_GET(val); + if (ucdbg) + *ucdbg = AV8100_GPIO_CONFIGURATION_UCDBG_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gpio_conf_r); + +int av8100_reg_gen_ctrl_r( + u8 *fdl, + u8 *hld, + u8 *wa, + u8 *ra) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_GENERAL_CONTROL, &val); + /* Set return params */ + if (fdl) + *fdl = AV8100_GENERAL_CONTROL_FDL_GET(val); + if (hld) + *hld = AV8100_GENERAL_CONTROL_HLD_GET(val); + if (wa) + *wa = AV8100_GENERAL_CONTROL_WA_GET(val); + if (ra) + *ra = AV8100_GENERAL_CONTROL_RA_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_gen_ctrl_r); + +int av8100_reg_fw_dl_entry_r( + u8 *mbyte_code_entry) +{ + int retval; + u8 val; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + /* Read from register */ + retval = register_read_internal(AV8100_FIRMWARE_DOWNLOAD_ENTRY, &val); + + /* Set return params */ + if (mbyte_code_entry) + *mbyte_code_entry = + AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY_GET(val); + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_fw_dl_entry_r); + +int av8100_reg_r( + u8 offset, + u8 *value) +{ + int retval = 0; + struct i2c_client *i2c; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + i2c = adev->config.client; + + /* Read from register */ + retval = read_single_byte(i2c, offset, value); + if (retval) { + dev_dbg(adev->dev, + "Failed to read the value from av8100 register\n"); + retval = -EFAULT; + goto av8100_register_read_out; + } + +av8100_register_read_out: + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_reg_r); + +int av8100_conf_get(enum av8100_command_type command_type, + union av8100_configuration *config) +{ + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state == AV8100_OPMODE_UNDEFINED) + return -EINVAL; + + /* Put configuration data to the corresponding data struct depending + * on command type */ + switch (command_type) { + case AV8100_COMMAND_VIDEO_INPUT_FORMAT: + memcpy(&config->video_input_format, + &adev->config.hdmi_video_input_cmd, + sizeof(struct av8100_video_input_format_cmd)); + break; + + case AV8100_COMMAND_AUDIO_INPUT_FORMAT: + memcpy(&config->audio_input_format, + &adev->config.hdmi_audio_input_cmd, + sizeof(struct av8100_audio_input_format_cmd)); + break; + + case AV8100_COMMAND_VIDEO_OUTPUT_FORMAT: + memcpy(&config->video_output_format, + &adev->config.hdmi_video_output_cmd, + sizeof(struct av8100_video_output_format_cmd)); + break; + + case AV8100_COMMAND_VIDEO_SCALING_FORMAT: + memcpy(&config->video_scaling_format, + &adev->config.hdmi_video_scaling_cmd, + sizeof(struct av8100_video_scaling_format_cmd)); + break; + + case AV8100_COMMAND_COLORSPACECONVERSION: + config->color_transform = adev->config.color_transform; + break; + + case AV8100_COMMAND_CEC_MESSAGE_WRITE: + memcpy(&config->cec_message_write_format, + &adev->config.hdmi_cec_message_write_cmd, + sizeof(struct av8100_cec_message_write_format_cmd)); + break; + + case AV8100_COMMAND_CEC_MESSAGE_READ_BACK: + memcpy(&config->cec_message_read_back_format, + &adev->config.hdmi_cec_message_read_back_cmd, + sizeof(struct av8100_cec_message_read_back_format_cmd)); + break; + + case AV8100_COMMAND_DENC: + memcpy(&config->denc_format, &adev->config.hdmi_denc_cmd, + sizeof(struct av8100_denc_format_cmd)); + break; + + case AV8100_COMMAND_HDMI: + memcpy(&config->hdmi_format, &adev->config.hdmi_cmd, + sizeof(struct av8100_hdmi_cmd)); + break; + + case AV8100_COMMAND_HDCP_SENDKEY: + memcpy(&config->hdcp_send_key_format, + &adev->config.hdmi_hdcp_send_key_cmd, + sizeof(struct av8100_hdcp_send_key_format_cmd)); + break; + + case AV8100_COMMAND_HDCP_MANAGEMENT: + memcpy(&config->hdcp_management_format, + &adev->config.hdmi_hdcp_management_format_cmd, + sizeof(struct av8100_hdcp_management_format_cmd)); + break; + + case AV8100_COMMAND_INFOFRAMES: + memcpy(&config->infoframes_format, + &adev->config.hdmi_infoframes_cmd, + sizeof(struct av8100_infoframes_format_cmd)); + break; + + case AV8100_COMMAND_EDID_SECTION_READBACK: + memcpy(&config->edid_section_readback_format, + &adev->config.hdmi_edid_section_readback_cmd, + sizeof(struct + av8100_edid_section_readback_format_cmd)); + break; + + case AV8100_COMMAND_PATTERNGENERATOR: + memcpy(&config->pattern_generator_format, + &adev->config.hdmi_pattern_generator_cmd, + sizeof(struct av8100_pattern_generator_format_cmd)); + break; + + case AV8100_COMMAND_FUSE_AES_KEY: + memcpy(&config->fuse_aes_key_format, + &adev->config.hdmi_fuse_aes_key_cmd, + sizeof(struct av8100_fuse_aes_key_format_cmd)); + break; + + default: + return -EINVAL; + break; + } + + return 0; +} +EXPORT_SYMBOL(av8100_conf_get); + +int av8100_conf_prep(enum av8100_command_type command_type, + union av8100_configuration *config) +{ + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!config || !adev) + return -EINVAL; + + /* Put configuration data to the corresponding data struct depending + * on command type */ + switch (command_type) { + case AV8100_COMMAND_VIDEO_INPUT_FORMAT: + memcpy(&adev->config.hdmi_video_input_cmd, + &config->video_input_format, + sizeof(struct av8100_video_input_format_cmd)); + break; + + case AV8100_COMMAND_AUDIO_INPUT_FORMAT: + memcpy(&adev->config.hdmi_audio_input_cmd, + &config->audio_input_format, + sizeof(struct av8100_audio_input_format_cmd)); + break; + + case AV8100_COMMAND_VIDEO_OUTPUT_FORMAT: + memcpy(&adev->config.hdmi_video_output_cmd, + &config->video_output_format, + sizeof(struct av8100_video_output_format_cmd)); + + /* Set params that depend on video output */ + av8100_config_video_output_dep(adev->config. + hdmi_video_output_cmd.video_output_cea_vesa); + break; + + case AV8100_COMMAND_VIDEO_SCALING_FORMAT: + memcpy(&adev->config.hdmi_video_scaling_cmd, + &config->video_scaling_format, + sizeof(struct av8100_video_scaling_format_cmd)); + break; + + case AV8100_COMMAND_COLORSPACECONVERSION: + adev->config.color_transform = config->color_transform; + break; + + case AV8100_COMMAND_CEC_MESSAGE_WRITE: + memcpy(&adev->config.hdmi_cec_message_write_cmd, + &config->cec_message_write_format, + sizeof(struct av8100_cec_message_write_format_cmd)); + break; + + case AV8100_COMMAND_CEC_MESSAGE_READ_BACK: + memcpy(&adev->config.hdmi_cec_message_read_back_cmd, + &config->cec_message_read_back_format, + sizeof(struct av8100_cec_message_read_back_format_cmd)); + break; + + case AV8100_COMMAND_DENC: + memcpy(&adev->config.hdmi_denc_cmd, &config->denc_format, + sizeof(struct av8100_denc_format_cmd)); + break; + + case AV8100_COMMAND_HDMI: + memcpy(&adev->config.hdmi_cmd, &config->hdmi_format, + sizeof(struct av8100_hdmi_cmd)); + break; + + case AV8100_COMMAND_HDCP_SENDKEY: + memcpy(&adev->config.hdmi_hdcp_send_key_cmd, + &config->hdcp_send_key_format, + sizeof(struct av8100_hdcp_send_key_format_cmd)); + break; + + case AV8100_COMMAND_HDCP_MANAGEMENT: + memcpy(&adev->config.hdmi_hdcp_management_format_cmd, + &config->hdcp_management_format, + sizeof(struct av8100_hdcp_management_format_cmd)); + break; + + case AV8100_COMMAND_INFOFRAMES: + memcpy(&adev->config.hdmi_infoframes_cmd, + &config->infoframes_format, + sizeof(struct av8100_infoframes_format_cmd)); + break; + + case AV8100_COMMAND_EDID_SECTION_READBACK: + memcpy(&adev->config.hdmi_edid_section_readback_cmd, + &config->edid_section_readback_format, + sizeof(struct + av8100_edid_section_readback_format_cmd)); + break; + + case AV8100_COMMAND_PATTERNGENERATOR: + memcpy(&adev->config.hdmi_pattern_generator_cmd, + &config->pattern_generator_format, + sizeof(struct av8100_pattern_generator_format_cmd)); + break; + + case AV8100_COMMAND_FUSE_AES_KEY: + memcpy(&adev->config.hdmi_fuse_aes_key_cmd, + &config->fuse_aes_key_format, + sizeof(struct av8100_fuse_aes_key_format_cmd)); + break; + + default: + return -EINVAL; + break; + } + + return 0; +} +EXPORT_SYMBOL(av8100_conf_prep); + +int av8100_conf_w(enum av8100_command_type command_type, + u8 *return_buffer_length, + u8 *return_buffer, enum interface_type if_type) +{ + int retval = 0; + u8 cmd_buffer[AV8100_COMMAND_MAX_LENGTH]; + u32 cmd_length = 0; + struct i2c_client *i2c; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + if (return_buffer_length) + *return_buffer_length = 0; + + i2c = adev->config.client; + + memset(&cmd_buffer, 0x00, AV8100_COMMAND_MAX_LENGTH); + +#define PRNK_MODE(_m) dev_dbg(adev->dev, "cmd: " #_m "\n"); + + /* Fill the command buffer with configuration data */ + switch (command_type) { + case AV8100_COMMAND_VIDEO_INPUT_FORMAT: + PRNK_MODE(AV8100_COMMAND_VIDEO_INPUT_FORMAT); + configuration_video_input_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_AUDIO_INPUT_FORMAT: + PRNK_MODE(AV8100_COMMAND_AUDIO_INPUT_FORMAT); + configuration_audio_input_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_VIDEO_OUTPUT_FORMAT: + PRNK_MODE(AV8100_COMMAND_VIDEO_OUTPUT_FORMAT); + configuration_video_output_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_VIDEO_SCALING_FORMAT: + PRNK_MODE(AV8100_COMMAND_VIDEO_SCALING_FORMAT); + configuration_video_scaling_get(adev, cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_COLORSPACECONVERSION: + PRNK_MODE(AV8100_COMMAND_COLORSPACECONVERSION); + configuration_colorspace_conversion_get(adev, cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_CEC_MESSAGE_WRITE: + PRNK_MODE(AV8100_COMMAND_CEC_MESSAGE_WRITE); + configuration_cec_message_write_get(adev, cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_CEC_MESSAGE_READ_BACK: + PRNK_MODE(AV8100_COMMAND_CEC_MESSAGE_READ_BACK); + configuration_cec_message_read_get(cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_DENC: + PRNK_MODE(AV8100_COMMAND_DENC); + configuration_denc_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_HDMI: + PRNK_MODE(AV8100_COMMAND_HDMI); + configuration_hdmi_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_HDCP_SENDKEY: + PRNK_MODE(AV8100_COMMAND_HDCP_SENDKEY); + configuration_hdcp_sendkey_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_HDCP_MANAGEMENT: + PRNK_MODE(AV8100_COMMAND_HDCP_MANAGEMENT); + configuration_hdcp_management_get(adev, cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_INFOFRAMES: + PRNK_MODE(AV8100_COMMAND_INFOFRAMES); + configuration_infoframe_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_EDID_SECTION_READBACK: + PRNK_MODE(AV8100_COMMAND_EDID_SECTION_READBACK); + av8100_edid_section_readback_get(adev, cmd_buffer, &cmd_length); + break; + + case AV8100_COMMAND_PATTERNGENERATOR: + PRNK_MODE(AV8100_COMMAND_PATTERNGENERATOR); + configuration_pattern_generator_get(adev, cmd_buffer, + &cmd_length); + break; + + case AV8100_COMMAND_FUSE_AES_KEY: + PRNK_MODE(AV8100_COMMAND_FUSE_AES_KEY); + configuration_fuse_aes_key_get(adev, cmd_buffer, &cmd_length); + break; + + default: + dev_dbg(adev->dev, "Invalid command type\n"); + retval = -EFAULT; + break; + } + + LOCK_AV8100_HW; + + if (if_type == I2C_INTERFACE) { + int cnt = 0; + int cnt_max; + + dev_dbg(adev->dev, "av8100_conf_w cmd_type:%02x length:%02x ", + command_type, cmd_length); + dev_dbg(adev->dev, "buffer: "); + while (cnt < cmd_length) { + dev_dbg(adev->dev, "%02x ", cmd_buffer[cnt]); + cnt++; + } + + /* Write the command buffer */ + retval = write_multi_byte(i2c, + AV8100_CMD_BUF_OFFSET, cmd_buffer, cmd_length); + if (retval) { + UNLOCK_AV8100_HW; + return retval; + } + + /* Write the command */ + retval = write_single_byte(i2c, AV8100_COMMAND_OFFSET, + command_type); + if (retval) { + UNLOCK_AV8100_HW; + return retval; + } + + + /* Get the first return byte */ + mdelay(AV8100_WAITTIME_1MS); + cnt = 0; + cnt_max = sizeof(waittime_retry) / sizeof(waittime_retry[0]); + retval = get_command_return_first(i2c, command_type); + while (retval && (cnt < cnt_max)) { + mdelay(waittime_retry[cnt]); + retval = get_command_return_first(i2c, command_type); + cnt++; + } + dev_dbg(adev->dev, "first return cnt:%d\n", cnt); + + if (retval) { + UNLOCK_AV8100_HW; + return retval; + } + + retval = get_command_return_data(i2c, command_type, cmd_buffer, + return_buffer_length, return_buffer); + } else if (if_type == DSI_INTERFACE) { + /* TODO */ + } else { + retval = -EINVAL; + dev_dbg(adev->dev, "Invalid command type\n"); + } + + if (command_type == AV8100_COMMAND_HDMI) { + adev->status.hdmi_on = ((adev->config.hdmi_cmd. + hdmi_mode == AV8100_HDMI_ON) && + (adev->config.hdmi_cmd.hdmi_format == AV8100_HDMI)); + } + + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_conf_w); + +int av8100_conf_w_raw(enum av8100_command_type command_type, + u8 buffer_length, + u8 *buffer, + u8 *return_buffer_length, + u8 *return_buffer) +{ + int retval = 0; + struct i2c_client *i2c; + int cnt; + int cnt_max; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + if (av8100_status_get().av8100_state <= AV8100_OPMODE_SHUTDOWN) + return -EINVAL; + + LOCK_AV8100_HW; + + if (return_buffer_length) + *return_buffer_length = 0; + + i2c = adev->config.client; + + /* Write the command buffer */ + retval = write_multi_byte(i2c, + AV8100_CMD_BUF_OFFSET, buffer, buffer_length); + if (retval) + goto av8100_conf_w_raw_out; + + /* Write the command */ + retval = write_single_byte(i2c, AV8100_COMMAND_OFFSET, + command_type); + if (retval) + goto av8100_conf_w_raw_out; + + + /* Get the first return byte */ + mdelay(AV8100_WAITTIME_1MS); + cnt = 0; + cnt_max = sizeof(waittime_retry) / sizeof(waittime_retry[0]); + retval = get_command_return_first(i2c, command_type); + while (retval && (cnt < cnt_max)) { + mdelay(waittime_retry[cnt]); + retval = get_command_return_first(i2c, command_type); + cnt++; + } + dev_dbg(adev->dev, "first return cnt:%d\n", cnt); + if (retval) + goto av8100_conf_w_raw_out; + + retval = get_command_return_data(i2c, command_type, buffer, + return_buffer_length, return_buffer); + +av8100_conf_w_raw_out: + UNLOCK_AV8100_HW; + return retval; +} +EXPORT_SYMBOL(av8100_conf_w_raw); + +struct av8100_status av8100_status_get(void) +{ + struct av8100_status status = {0}; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (adev) + return adev->status; + else + return status; +} +EXPORT_SYMBOL(av8100_status_get); + +enum av8100_output_CEA_VESA av8100_video_output_format_get(int xres, + int yres, + int htot, + int vtot, + int pixelclk, + bool interlaced) +{ + enum av8100_output_CEA_VESA index = 1; + int yres_div = !interlaced ? 1 : 2; + int hres_div = 1; + long freq1; + long freq2; + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + /* + * 720_576_I need a divider for hact and htot since + * these params need to be twice as large as expected in av8100_all_cea, + * which is used as input parameter to video input config. + */ + if ((xres == 720) && (yres == 576) && (interlaced == true)) + hres_div = 2; + + freq1 = 1000000 / htot * 1000000 / vtot / pixelclk + 1; + while (index < sizeof(av8100_all_cea)/sizeof(struct av8100_cea)) { + freq2 = av8100_all_cea[index].frequence / + av8100_all_cea[index].htotale / + av8100_all_cea[index].vtotale; + + dev_dbg(adev->dev, "freq1:%ld freq2:%ld\n", freq1, freq2); + if ((xres == av8100_all_cea[index].hactive / hres_div) && + (yres == av8100_all_cea[index].vactive * yres_div) && + (htot == av8100_all_cea[index].htotale / hres_div) && + (vtot == av8100_all_cea[index].vtotale) && + (abs(freq1 - freq2) < 2)) { + goto av8100_video_output_format_get_out; + } + index++; + } + +av8100_video_output_format_get_out: + dev_dbg(adev->dev, "av8100_video_output_format_get %d %d %d %d %d\n", + xres, yres, htot, vtot, index); + return index; +} +EXPORT_SYMBOL(av8100_video_output_format_get); + +void av8100_hdmi_event_cb_set(void (*hdmi_ev_cb)(enum av8100_hdmi_event)) +{ + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (adev) + adev->params.hdmi_ev_cb = hdmi_ev_cb; +} +EXPORT_SYMBOL(av8100_hdmi_event_cb_set); + +u8 av8100_ver_get(void) +{ + struct av8100_device *adev; + + adev = devnr_to_adev(AV8100_DEVNR_DEFAULT); + if (!adev) + return -EINVAL; + + return adev->chip_version; +} +EXPORT_SYMBOL(av8100_ver_get); + +static const struct color_conversion_cmd *get_color_transform_cmd( + struct av8100_device *adev, + enum av8100_color_transform transform) +{ + const struct color_conversion_cmd *result; + + switch (transform) { + case AV8100_COLOR_TRANSFORM_INDENTITY: + result = &col_trans_identity; + break; + case AV8100_COLOR_TRANSFORM_INDENTITY_CLAMP_YUV: + result = &col_trans_identity_clamp_yuv; + break; + case AV8100_COLOR_TRANSFORM_YUV_TO_RGB: + if (adev->chip_version == AV8100_CHIPVER_1) + result = &col_trans_yuv_to_rgb_v1; + else + result = &col_trans_yuv_to_rgb_v2; + break; + case AV8100_COLOR_TRANSFORM_YUV_TO_DENC: + result = &col_trans_yuv_to_denc; + break; + case AV8100_COLOR_TRANSFORM_RGB_TO_DENC: + result = &col_trans_rgb_to_denc; + break; + default: + dev_warn(adev->dev, "Unknown color space transform\n"); + result = &col_trans_identity; + break; + } + return result; +} + +static int av8100_open(struct inode *inode, struct file *filp) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int av8100_release(struct inode *inode, struct file *filp) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static long av8100_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +int av8100_device_register(struct av8100_device *adev) +{ + adev->miscdev.minor = MISC_DYNAMIC_MINOR; + adev->miscdev.name = "av8100"; + adev->miscdev.fops = &av8100_fops; + + if (misc_register(&adev->miscdev)) { + pr_err("av8100 misc_register failed\n"); + return -EFAULT; + } + return 0; +} + +int av8100_init_device(struct av8100_device *adev, struct device *dev) +{ + adev->dev = dev; + + if (av8100_config_init(adev)) { + dev_info(dev, "av8100_config_init failed\n"); + return -EFAULT; + } + + if (av8100_params_init(adev)) { + dev_info(dev, "av8100_params_init failed\n"); + return -EFAULT; + } + return 0; +} + +static int __devinit av8100_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct av8100_platform_data *pdata = i2c_client->dev.platform_data; + struct device *dev; + struct av8100_device *adev; + + dev = &i2c_client->dev; + + dev_dbg(dev, "%s\n", __func__); + + /* Allocate device data */ + adev = kzalloc(sizeof(struct av8100_device), GFP_KERNEL); + if (!adev) { + dev_info(dev, "%s: Alloc failure\n", __func__); + return -ENOMEM; + } + + /* Add to list */ + list_add_tail(&adev->list, &av8100_device_list); + + av8100_device_register(adev); + + av8100_init_device(adev, dev); + + av8100_set_state(adev, AV8100_OPMODE_UNDEFINED); + + if (!i2c_check_functionality(i2c_client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + ret = -ENODEV; + dev_info(dev, "av8100 i2c_check_functionality failed\n"); + goto err1; + } + + init_waitqueue_head(&adev->event); + + adev->config.client = i2c_client; + adev->config.id = (struct i2c_device_id *) id; + i2c_set_clientdata(i2c_client, &adev->config); + + kthread_run(av8100_thread, adev, "av8100_thread"); + + /* Get regulator resource */ + if (pdata->regulator_pwr_id) { + adev->params.regulator_pwr = regulator_get(dev, + pdata->regulator_pwr_id); + if (IS_ERR(adev->params.regulator_pwr)) { + ret = PTR_ERR(adev->params.regulator_pwr); + dev_warn(dev, + "%s: Failed to get regulator '%s'\n", + __func__, pdata->regulator_pwr_id); + adev->params.regulator_pwr = NULL; + goto err1; + } + } + + /* Get clock resource */ + if (pdata->inputclk_id) { + adev->params.inputclk = clk_get(NULL, pdata->inputclk_id); + if (IS_ERR(adev->params.inputclk)) { + adev->params.inputclk = NULL; + dev_warn(dev, "%s: Failed to get clock '%s'\n", + __func__, pdata->inputclk_id); + } + } + + av8100_set_state(adev, AV8100_OPMODE_SHUTDOWN); + + + if (av8100_powerup1(adev)) { + dev_err(adev->dev, "av8100_powerup1 fail\n"); + ret = -EFAULT; + goto err1; + } + + /* Obtain the chip version */ + ret = av8100_reg_stby_pend_int_r(NULL, NULL, NULL, + &adev->chip_version); + if (ret) { + dev_err(adev->dev, "Failed to read chip version\n"); + goto err2; + } + + dev_info(adev->dev, "chip version:%d\n", adev->chip_version); + + switch (adev->chip_version) { + case AV8100_CHIPVER_1: + case AV8100_CHIPVER_2: + break; + + default: + dev_err(adev->dev, "Unsupported chip version:%d\n", + adev->chip_version); + ret = -EINVAL; + goto err2; + break; + } +err2: + (void) av8100_powerdown(); +err1: + return ret; +} + +static int __devexit av8100_remove(struct i2c_client *i2c_client) +{ + struct av8100_device *adev; + + adev = dev_to_adev(&i2c_client->dev); + if (!adev) + return -EFAULT; + + dev_dbg(adev->dev, "%s\n", __func__); + + if (adev->params.inputclk) + clk_put(adev->params.inputclk); + + /* Release regulator resource */ + if (adev->params.regulator_pwr) + regulator_put(adev->params.regulator_pwr); + + misc_deregister(&adev->miscdev); + + /* Remove from list */ + list_del(&adev->list); + + /* Free device data */ + kfree(adev); + + return 0; +} + +int av8100_init(void) +{ + pr_debug("%s\n", __func__); + + if (i2c_add_driver(&av8100_driver)) { + pr_err("av8100 i2c_add_driver failed\n"); + return -EFAULT; + } + + return 0; +} +module_init(av8100_init); + +void av8100_exit(void) +{ + pr_debug("%s\n", __func__); + + i2c_del_driver(&av8100_driver); +} +module_exit(av8100_exit); + +MODULE_AUTHOR("Per Persson <per.xb.persson@stericsson.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ST-Ericsson hdmi display driver"); diff --git a/drivers/video/av8100/av8100_regs.h b/drivers/video/av8100/av8100_regs.h new file mode 100644 index 00000000000..6ed9000987a --- /dev/null +++ b/drivers/video/av8100/av8100_regs.h @@ -0,0 +1,346 @@ + +#define AV8100_VAL2REG(__reg, __fld, __val) \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK) +#define AV8100_REG2VAL(__reg, __fld, __val) \ + (((__val) & __reg##_##__fld##_MASK) >> __reg##_##__fld##_SHIFT) + +#define AV8100_STANDBY 0x00000000 +#define AV8100_STANDBY_CPD_SHIFT 0 +#define AV8100_STANDBY_CPD_MASK 0x00000001 +#define AV8100_STANDBY_CPD_HIGH 1 +#define AV8100_STANDBY_CPD_LOW 0 +#define AV8100_STANDBY_CPD(__x) \ + AV8100_VAL2REG(AV8100_STANDBY, CPD, __x) +#define AV8100_STANDBY_CPD_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY, CPD, __x) +#define AV8100_STANDBY_STBY_SHIFT 1 +#define AV8100_STANDBY_STBY_MASK 0x00000002 +#define AV8100_STANDBY_STBY_HIGH 1 +#define AV8100_STANDBY_STBY_LOW 0 +#define AV8100_STANDBY_STBY(__x) \ + AV8100_VAL2REG(AV8100_STANDBY, STBY, __x) +#define AV8100_STANDBY_STBY_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY, STBY, __x) +#define AV8100_STANDBY_HPDS_SHIFT 2 +#define AV8100_STANDBY_HPDS_MASK 0x00000004 +#define AV8100_STANDBY_HPDS(__x) \ + AV8100_VAL2REG(AV8100_STANDBY, HPDS, __x) +#define AV8100_STANDBY_HPDS_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY, HPDS, __x) +#define AV8100_STANDBY_CPDS_SHIFT 3 +#define AV8100_STANDBY_CPDS_MASK 0x00000008 +#define AV8100_STANDBY_CPDS(__x) \ + AV8100_VAL2REG(AV8100_STANDBY, CPDS, __x) +#define AV8100_STANDBY_CPDS_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY, CPDS, __x) +#define AV8100_STANDBY_MCLKRNG_SHIFT 4 +#define AV8100_STANDBY_MCLKRNG_MASK 0x000000F0 +#define AV8100_STANDBY_MCLKRNG(__x) \ + AV8100_VAL2REG(AV8100_STANDBY, MCLKRNG, __x) +#define AV8100_STANDBY_MCLKRNG_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY, MCLKRNG, __x) +#define AV8100_HDMI_5_VOLT_TIME 0x00000001 +#define AV8100_HDMI_5_VOLT_TIME_OFF_TIME_SHIFT 0 +#define AV8100_HDMI_5_VOLT_TIME_OFF_TIME_MASK 0x0000001F +#define AV8100_HDMI_5_VOLT_TIME_OFF_TIME(__x) \ + AV8100_VAL2REG(AV8100_HDMI_5_VOLT_TIME, OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_OFF_TIME_GET(__x) \ + AV8100_REG2VAL(AV8100_HDMI_5_VOLT_TIME, OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME_SHIFT 0 +#define AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME_MASK 0x00000003 +#define AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME(__x) \ + AV8100_VAL2REG(AV8100_HDMI_5_VOLT_TIME, DAC_OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_DAC_OFF_TIME_GET(__x) \ + AV8100_REG2VAL(AV8100_HDMI_5_VOLT_TIME, DAC_OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME_SHIFT 2 +#define AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME_MASK 0x0000001C +#define AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME(__x) \ + AV8100_VAL2REG(AV8100_HDMI_5_VOLT_TIME, SU_OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_SU_OFF_TIME_GET(__x) \ + AV8100_REG2VAL(AV8100_HDMI_5_VOLT_TIME, SU_OFF_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_ON_TIME_SHIFT 5 +#define AV8100_HDMI_5_VOLT_TIME_ON_TIME_MASK 0x000000E0 +#define AV8100_HDMI_5_VOLT_TIME_ON_TIME(__x) \ + AV8100_VAL2REG(AV8100_HDMI_5_VOLT_TIME, ON_TIME, __x) +#define AV8100_HDMI_5_VOLT_TIME_ON_TIME_GET(__x) \ + AV8100_REG2VAL(AV8100_HDMI_5_VOLT_TIME, ON_TIME, __x) +#define AV8100_STANDBY_INTERRUPT_MASK 0x00000002 +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM_SHIFT 0 +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM_MASK 0x00000001 +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM_HIGH 1 +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM_LOW 0 +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_INTERRUPT_MASK, HPDM, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_HPDM_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_INTERRUPT_MASK, HPDM, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM_SHIFT 1 +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM_MASK 0x00000002 +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM_HIGH 1 +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM_LOW 0 +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_INTERRUPT_MASK, CPDM, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_CPDM_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_INTERRUPT_MASK, CPDM, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_SHIFT 2 +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_MASK 0x0000000C +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_INPUT 0x00 +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_ALT 0x01 +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_OUTPUT0 0x02 +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_OUTPUT1 0x03 +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_INTERRUPT_MASK, STBYGPIOCFG, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_STBYGPIOCFG_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_INTERRUPT_MASK, STBYGPIOCFG, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL_SHIFT 7 +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL_MASK 0x00000080 +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL_HIGH 1 +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL_LOW 0 +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_INTERRUPT_MASK, IPOL, __x) +#define AV8100_STANDBY_INTERRUPT_MASK_IPOL_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_INTERRUPT_MASK, IPOL, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT 0x00000003 +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI_SHIFT 0 +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI_MASK 0x00000001 +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI_HIGH 1 +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI_LOW 0 +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_PENDING_INTERRUPT, HPDI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_HPDI_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_PENDING_INTERRUPT, HPDI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI_SHIFT 1 +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI_MASK 0x00000002 +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI_HIGH 1 +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI_LOW 0 +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_PENDING_INTERRUPT, CPDI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_CPDI_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_PENDING_INTERRUPT, CPDI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI_SHIFT 2 +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI_MASK 0x00000004 +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI_HIGH 1 +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI_LOW 0 +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_PENDING_INTERRUPT, ONI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_ONI_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_PENDING_INTERRUPT, ONI, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_SID_SHIFT 4 +#define AV8100_STANDBY_PENDING_INTERRUPT_SID_MASK 0x000000F0 +#define AV8100_STANDBY_PENDING_INTERRUPT_SID(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_PENDING_INTERRUPT, SID, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_SID_GET(__x) \ + AV8100_REG2VAL(AV8100_STANDBY_PENDING_INTERRUPT, SID, __x) +#define AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_SHIFT 6 +#define AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_MASK 0x00000040 +#define AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_HIGH 1 +#define AV8100_STANDBY_PENDING_INTERRUPT_BPDIG_LOW 0 +#define AV8100_STANDBY_PENDING_INTERRUPT_BPDIG(__x) \ + AV8100_VAL2REG(AV8100_STANDBY_PENDING_INTERRUPT, BPDIG, __x) +#define AV8100_GENERAL_INTERRUPT_MASK 0x00000004 +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM_SHIFT 0 +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM_MASK 0x00000001 +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, EOCM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_EOCM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, EOCM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM_SHIFT 1 +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM_MASK 0x00000002 +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, VSIM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_VSIM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, VSIM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM_SHIFT 2 +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM_MASK 0x00000004 +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, VSOM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_VSOM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, VSOM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_CECM_SHIFT 3 +#define AV8100_GENERAL_INTERRUPT_MASK_CECM_MASK 0x00000008 +#define AV8100_GENERAL_INTERRUPT_MASK_CECM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_CECM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_CECM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, CECM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_CECM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, CECM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM_SHIFT 4 +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM_MASK 0x00000010 +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, HDCPM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_HDCPM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, HDCPM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM_SHIFT 5 +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM_MASK 0x00000020 +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, UOVBM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_UOVBM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, UOVBM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_TEM_SHIFT 6 +#define AV8100_GENERAL_INTERRUPT_MASK_TEM_MASK 0x00000040 +#define AV8100_GENERAL_INTERRUPT_MASK_TEM_HIGH 1 +#define AV8100_GENERAL_INTERRUPT_MASK_TEM_LOW 0 +#define AV8100_GENERAL_INTERRUPT_MASK_TEM(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT_MASK, TEM, __x) +#define AV8100_GENERAL_INTERRUPT_MASK_TEM_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT_MASK, TEM, __x) +#define AV8100_GENERAL_INTERRUPT 0x00000005 +#define AV8100_GENERAL_INTERRUPT_EOCI_SHIFT 0 +#define AV8100_GENERAL_INTERRUPT_EOCI_MASK 0x00000001 +#define AV8100_GENERAL_INTERRUPT_EOCI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, EOCI, __x) +#define AV8100_GENERAL_INTERRUPT_EOCI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, EOCI, __x) +#define AV8100_GENERAL_INTERRUPT_VSII_SHIFT 1 +#define AV8100_GENERAL_INTERRUPT_VSII_MASK 0x00000002 +#define AV8100_GENERAL_INTERRUPT_VSII(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, VSII, __x) +#define AV8100_GENERAL_INTERRUPT_VSII_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, VSII, __x) +#define AV8100_GENERAL_INTERRUPT_VSOI_SHIFT 2 +#define AV8100_GENERAL_INTERRUPT_VSOI_MASK 0x00000004 +#define AV8100_GENERAL_INTERRUPT_VSOI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, VSOI, __x) +#define AV8100_GENERAL_INTERRUPT_VSOI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, VSOI, __x) +#define AV8100_GENERAL_INTERRUPT_CECI_SHIFT 3 +#define AV8100_GENERAL_INTERRUPT_CECI_MASK 0x00000008 +#define AV8100_GENERAL_INTERRUPT_CECI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, CECI, __x) +#define AV8100_GENERAL_INTERRUPT_CECI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, CECI, __x) +#define AV8100_GENERAL_INTERRUPT_HDCPI_SHIFT 4 +#define AV8100_GENERAL_INTERRUPT_HDCPI_MASK 0x00000010 +#define AV8100_GENERAL_INTERRUPT_HDCPI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, HDCPI, __x) +#define AV8100_GENERAL_INTERRUPT_HDCPI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, HDCPI, __x) +#define AV8100_GENERAL_INTERRUPT_UOVBI_SHIFT 5 +#define AV8100_GENERAL_INTERRUPT_UOVBI_MASK 0x00000020 +#define AV8100_GENERAL_INTERRUPT_UOVBI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, UOVBI, __x) +#define AV8100_GENERAL_INTERRUPT_UOVBI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, UOVBI, __x) +#define AV8100_GENERAL_INTERRUPT_TEI_SHIFT 6 +#define AV8100_GENERAL_INTERRUPT_TEI_MASK 0x00000040 +#define AV8100_GENERAL_INTERRUPT_TEI(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_INTERRUPT, TEI, __x) +#define AV8100_GENERAL_INTERRUPT_TEI_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_INTERRUPT, TEI, __x) +#define AV8100_GENERAL_STATUS 0x00000006 +#define AV8100_GENERAL_STATUS_CECTXERR_SHIFT 0 +#define AV8100_GENERAL_STATUS_CECTXERR_MASK 0x00000001 +#define AV8100_GENERAL_STATUS_CECTXERR_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, CECTXERR, __x) +#define AV8100_GENERAL_STATUS_CECREC_SHIFT 1 +#define AV8100_GENERAL_STATUS_CECREC_MASK 0x00000002 +#define AV8100_GENERAL_STATUS_CECREC_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, CECREC, __x) +#define AV8100_GENERAL_STATUS_CECTRX_SHIFT 2 +#define AV8100_GENERAL_STATUS_CECTRX_MASK 0x00000004 +#define AV8100_GENERAL_STATUS_CECTRX_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, CECTRX, __x) +#define AV8100_GENERAL_STATUS_UC_SHIFT 3 +#define AV8100_GENERAL_STATUS_UC_MASK 0x00000008 +#define AV8100_GENERAL_STATUS_UC_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, UC, __x) +#define AV8100_GENERAL_STATUS_ONUVB_SHIFT 4 +#define AV8100_GENERAL_STATUS_ONUVB_MASK 0x00000010 +#define AV8100_GENERAL_STATUS_ONUVB_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, ONUVB, __x) +#define AV8100_GENERAL_STATUS_HDCPS_SHIFT 5 +#define AV8100_GENERAL_STATUS_HDCPS_MASK 0x000000E0 +#define AV8100_GENERAL_STATUS_HDCPS_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_STATUS, HDCPS, __x) +#define AV8100_GPIO_CONFIGURATION 0x00000007 +#define AV8100_GPIO_CONFIGURATION_DAT3DIR_SHIFT 0 +#define AV8100_GPIO_CONFIGURATION_DAT3DIR_MASK 0x00000001 +#define AV8100_GPIO_CONFIGURATION_DAT3DIR(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT3DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT3DIR_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT3DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT3VAL_SHIFT 1 +#define AV8100_GPIO_CONFIGURATION_DAT3VAL_MASK 0x00000002 +#define AV8100_GPIO_CONFIGURATION_DAT3VAL(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT3VAL, __x) +#define AV8100_GPIO_CONFIGURATION_DAT3VAL_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT3VAL, __x) +#define AV8100_GPIO_CONFIGURATION_DAT2DIR_SHIFT 2 +#define AV8100_GPIO_CONFIGURATION_DAT2DIR_MASK 0x00000004 +#define AV8100_GPIO_CONFIGURATION_DAT2DIR(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT2DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT2DIR_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT2DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT2VAL_SHIFT 3 +#define AV8100_GPIO_CONFIGURATION_DAT2VAL_MASK 0x00000008 +#define AV8100_GPIO_CONFIGURATION_DAT2VAL(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT2VAL, __x) +#define AV8100_GPIO_CONFIGURATION_DAT2VAL_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT2VAL, __x) +#define AV8100_GPIO_CONFIGURATION_DAT1DIR_SHIFT 4 +#define AV8100_GPIO_CONFIGURATION_DAT1DIR_MASK 0x00000010 +#define AV8100_GPIO_CONFIGURATION_DAT1DIR(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT1DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT1DIR_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT1DIR, __x) +#define AV8100_GPIO_CONFIGURATION_DAT1VAL_SHIFT 5 +#define AV8100_GPIO_CONFIGURATION_DAT1VAL_MASK 0x00000020 +#define AV8100_GPIO_CONFIGURATION_DAT1VAL(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, DAT1VAL, __x) +#define AV8100_GPIO_CONFIGURATION_DAT1VAL_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, DAT1VAL, __x) +#define AV8100_GPIO_CONFIGURATION_UCDBG_SHIFT 6 +#define AV8100_GPIO_CONFIGURATION_UCDBG_MASK 0x00000040 +#define AV8100_GPIO_CONFIGURATION_UCDBG(__x) \ + AV8100_VAL2REG(AV8100_GPIO_CONFIGURATION, UCDBG, __x) +#define AV8100_GPIO_CONFIGURATION_UCDBG_GET(__x) \ + AV8100_REG2VAL(AV8100_GPIO_CONFIGURATION, UCDBG, __x) +#define AV8100_GENERAL_CONTROL 0x00000008 +#define AV8100_GENERAL_CONTROL_FDL_SHIFT 4 +#define AV8100_GENERAL_CONTROL_FDL_MASK 0x00000010 +#define AV8100_GENERAL_CONTROL_FDL_HIGH 1 +#define AV8100_GENERAL_CONTROL_FDL_LOW 0 +#define AV8100_GENERAL_CONTROL_FDL(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_CONTROL, FDL, __x) +#define AV8100_GENERAL_CONTROL_FDL_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_CONTROL, FDL, __x) +#define AV8100_GENERAL_CONTROL_HLD_SHIFT 5 +#define AV8100_GENERAL_CONTROL_HLD_MASK 0x00000020 +#define AV8100_GENERAL_CONTROL_HLD_HIGH 1 +#define AV8100_GENERAL_CONTROL_HLD_LOW 0 +#define AV8100_GENERAL_CONTROL_HLD(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_CONTROL, HLD, __x) +#define AV8100_GENERAL_CONTROL_HLD_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_CONTROL, HLD, __x) +#define AV8100_GENERAL_CONTROL_WA_SHIFT 6 +#define AV8100_GENERAL_CONTROL_WA_MASK 0x00000040 +#define AV8100_GENERAL_CONTROL_WA_HIGH 1 +#define AV8100_GENERAL_CONTROL_WA_LOW 0 +#define AV8100_GENERAL_CONTROL_WA(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_CONTROL, WA, __x) +#define AV8100_GENERAL_CONTROL_WA_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_CONTROL, WA, __x) +#define AV8100_GENERAL_CONTROL_RA_SHIFT 7 +#define AV8100_GENERAL_CONTROL_RA_MASK 0x00000080 +#define AV8100_GENERAL_CONTROL_RA_HIGH 1 +#define AV8100_GENERAL_CONTROL_RA_LOW 0 +#define AV8100_GENERAL_CONTROL_RA(__x) \ + AV8100_VAL2REG(AV8100_GENERAL_CONTROL, RA, __x) +#define AV8100_GENERAL_CONTROL_RA_GET(__x) \ + AV8100_REG2VAL(AV8100_GENERAL_CONTROL, RA, __x) +#define AV8100_FIRMWARE_DOWNLOAD_ENTRY 0x0000000F +#define AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY_SHIFT 0 +#define AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY_MASK 0x000000FF +#define AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY(__x) \ + AV8100_VAL2REG(AV8100_FIRMWARE_DOWNLOAD_ENTRY, MBYTE_CODE_ENTRY, __x) +#define AV8100_FIRMWARE_DOWNLOAD_ENTRY_MBYTE_CODE_ENTRY_GET(__x) \ + AV8100_REG2VAL(AV8100_FIRMWARE_DOWNLOAD_ENTRY, MBYTE_CODE_ENTRY, __x) diff --git a/drivers/video/av8100/hdmi.c b/drivers/video/av8100/hdmi.c new file mode 100644 index 00000000000..3159c4446f1 --- /dev/null +++ b/drivers/video/av8100/hdmi.c @@ -0,0 +1,2479 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson HDMI driver + * + * Author: Per Persson <per.xb.persson@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/ioctl.h> +#include <linux/uaccess.h> +#include <video/av8100.h> +#include <video/hdmi.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/ctype.h> +#include "hdmi_loc.h" +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/list.h> + +#define SYSFS_EVENT_FILENAME "evread" +#define HDMI_DEVNR_DEFAULT 0 + +DEFINE_MUTEX(hdmi_events_mutex); +#define LOCK_HDMI_EVENTS mutex_lock(&hdmi_events_mutex) +#define UNLOCK_HDMI_EVENTS mutex_unlock(&hdmi_events_mutex) +#define EVENTS_MASK 0xFF + +struct hdmi_device { + struct list_head list; + struct miscdevice miscdev; + struct device *dev; + struct hdmi_sysfs_data sysfs_data; + int events; + int events_mask; + wait_queue_head_t event_wq; + bool events_received; + int devnr; +}; + +/* List of devices */ +static LIST_HEAD(hdmi_device_list); + +static ssize_t store_storeastext(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_plugdeten(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_edidread(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_edidread(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t store_ceceven(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_cecread(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t store_cecsend(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_infofrsend(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_hdcpeven(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_hdcpchkaesotp(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_hdcpfuseaes(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_hdcpfuseaes(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_hdcploadaes(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_hdcploadaes(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_hdcpauthencr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_hdcpauthencr(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t show_hdcpstateget(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t show_evread(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t store_evclr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_audiocfg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_plugstatus(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_poweronoff(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t show_poweronoff(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_evwakeup(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static const struct device_attribute hdmi_sysfs_attrs[] = { + __ATTR(storeastext, S_IWUSR, NULL, store_storeastext), + __ATTR(plugdeten, S_IWUSR, NULL, store_plugdeten), + __ATTR(edidread, S_IRUGO | S_IWUSR, show_edidread, store_edidread), + __ATTR(ceceven, S_IWUSR, NULL, store_ceceven), + __ATTR(cecread, S_IRUGO, show_cecread, NULL), + __ATTR(cecsend, S_IWUSR, NULL, store_cecsend), + __ATTR(infofrsend, S_IWUSR, NULL, store_infofrsend), + __ATTR(hdcpeven, S_IWUSR, NULL, store_hdcpeven), + __ATTR(hdcpchkaesotp, S_IRUGO, show_hdcpchkaesotp, NULL), + __ATTR(hdcpfuseaes, S_IRUGO | S_IWUSR, show_hdcpfuseaes, + store_hdcpfuseaes), + __ATTR(hdcploadaes, S_IRUGO | S_IWUSR, show_hdcploadaes, + store_hdcploadaes), + __ATTR(hdcpauthencr, S_IRUGO | S_IWUSR, show_hdcpauthencr, + store_hdcpauthencr), + __ATTR(hdcpstateget, S_IRUGO, show_hdcpstateget, NULL), + __ATTR(evread, S_IRUGO, show_evread, NULL), + __ATTR(evclr, S_IWUSR, NULL, store_evclr), + __ATTR(audiocfg, S_IWUSR, NULL, store_audiocfg), + __ATTR(plugstatus, S_IRUGO, show_plugstatus, NULL), + __ATTR(poweronoff, S_IRUGO | S_IWUSR, show_poweronoff, + store_poweronoff), + __ATTR(evwakeup, S_IWUSR, NULL, store_evwakeup), + __ATTR_NULL +}; + +/* Hex to int conversion */ +static unsigned int htoi(const char *ptr) +{ + unsigned int value = 0; + char ch; + + if (!ptr) + return 0; + + ch = *ptr; + if (isdigit(ch)) + value = ch - '0'; + else + value = toupper(ch) - 'A' + 10; + + value <<= 4; + ch = *(++ptr); + + if (isdigit(ch)) + value += ch - '0'; + else + value += toupper(ch) - 'A' + 10; + + return value; +} + +static struct hdmi_device *dev_to_hdev(struct device *dev) +{ + /* Get device from list of devices */ + struct list_head *element; + struct hdmi_device *hdmi_dev; + int cnt = 0; + + list_for_each(element, &hdmi_device_list) { + hdmi_dev = list_entry(element, struct hdmi_device, list); + if (hdmi_dev->dev == dev) + return hdmi_dev; + cnt++; + } + + return NULL; +} + +static struct hdmi_device *devnr_to_hdev(int devnr) +{ + /* Get device from list of devices */ + struct list_head *element; + struct hdmi_device *hdmi_dev; + int cnt = 0; + + list_for_each(element, &hdmi_device_list) { + hdmi_dev = list_entry(element, struct hdmi_device, list); + if (cnt == devnr) + return hdmi_dev; + cnt++; + } + + return NULL; +} + +static int event_enable(struct hdmi_device *hdev, bool enable, + enum hdmi_event ev) +{ + struct kobject *kobj = &hdev->dev->kobj; + + dev_dbg(hdev->dev, "enable_event %d %02x\n", enable, ev); + if (enable) + hdev->events_mask |= ev; + else + hdev->events_mask &= ~ev; + + if (hdev->events & ev) { + /* Report pending event */ + /* Wake up application waiting for event via call to poll() */ + sysfs_notify(kobj, NULL, SYSFS_EVENT_FILENAME); + + LOCK_HDMI_EVENTS; + hdev->events_received = true; + UNLOCK_HDMI_EVENTS; + + wake_up_interruptible(&hdev->event_wq); + } + + return 0; +} + +static int plugdeten(struct hdmi_device *hdev, struct plug_detect *pldet) +{ + struct av8100_status status; + u8 denc_off_time = 0; + int retval; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + event_enable(hdev, pldet->hdmi_detect_enable != 0, + HDMI_EVENT_HDMI_PLUGIN); + event_enable(hdev, pldet->hdmi_detect_enable != 0, + HDMI_EVENT_HDMI_PLUGOUT); + + av8100_reg_hdmi_5_volt_time_r(&denc_off_time, NULL, NULL); + + retval = av8100_reg_hdmi_5_volt_time_w( + denc_off_time, + pldet->hdmi_off_time, + pldet->on_time); + + if (retval) { + dev_err(hdev->dev, "Failed to write the value to av8100 " + "register\n"); + return -EFAULT; + } + + return retval; +} + +static int edidread(struct hdmi_device *hdev, struct edid_read *edidread, + u8 *len, u8 *data) +{ + union av8100_configuration config; + struct av8100_status status; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.edid_section_readback_format.address = edidread->address; + config.edid_section_readback_format.block_number = edidread->block_nr; + + dev_dbg(hdev->dev, "addr:%0x blnr:%0x", + config.edid_section_readback_format.address, + config.edid_section_readback_format.block_number); + + if (av8100_conf_prep(AV8100_COMMAND_EDID_SECTION_READBACK, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_EDID_SECTION_READBACK, + len, data, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + dev_dbg(hdev->dev, "len:%0x\n", *len); + + return 0; +} + +static int cecread(struct hdmi_device *hdev, u8 *src, u8 *dest, u8 *data_len, + u8 *data) +{ + union av8100_configuration config; + struct av8100_status status; + u8 buf_len; + u8 buff[HDMI_CEC_READ_MAXSIZE]; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + if (av8100_conf_prep(AV8100_COMMAND_CEC_MESSAGE_READ_BACK, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_CEC_MESSAGE_READ_BACK, + &buf_len, buff, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + if (buf_len > 0) { + *src = (buff[0] & 0xF0) >> 4; + *dest = buff[0] & 0x0F; + *data_len = buf_len - 1; + memcpy(data, &buff[1], buf_len - 1); + } else + *data_len = 0; + + return 0; +} + +/* CEC tx status can be set or read */ +static bool cec_tx_status(struct hdmi_device *hdev, + enum cec_tx_status_action action) +{ + static bool cec_tx_busy; + + switch (action) { + case CEC_TX_SET_FREE: + cec_tx_busy = false; + dev_dbg(hdev->dev, "cec_tx_busy set:%d\n", cec_tx_busy); + break; + + case CEC_TX_SET_BUSY: + cec_tx_busy = true; + dev_dbg(hdev->dev, "cec_tx_busy set:%d\n", cec_tx_busy); + break; + + case CEC_TX_CHECK: + default: + dev_dbg(hdev->dev, "cec_tx_busy chk:%d\n", cec_tx_busy); + break; + } + + return cec_tx_busy; +} + +static int cecsend(struct hdmi_device *hdev, u8 src, u8 dest, u8 data_len, + u8 *data) +{ + union av8100_configuration config; + struct av8100_status status; + int cnt; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.cec_message_write_format.buffer[0] = ((src & 0x0F) << 4) + + (dest & 0x0F); + config.cec_message_write_format.buffer_length = data_len + 1; + memcpy(&config.cec_message_write_format.buffer[1], data, data_len); + + if (av8100_conf_prep(AV8100_COMMAND_CEC_MESSAGE_WRITE, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_enable_interrupt() != 0) { + dev_err(hdev->dev, "av8100_ei FAIL\n"); + return -EINVAL; + } + + cnt = 0; + while ((cnt < CECTX_TRY) && cec_tx_status(hdev, CEC_TX_CHECK)) { + /* Wait for pending CEC to be finished */ + msleep(CECTX_WAITTIME); + cnt++; + } + dev_dbg(hdev->dev, "cectxcnt:%d\n", cnt); + + if (av8100_conf_w(AV8100_COMMAND_CEC_MESSAGE_WRITE, + NULL, NULL, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + cec_tx_status(hdev, CEC_TX_SET_BUSY); + + return 0; +} + +static int infofrsend(struct hdmi_device *hdev, u8 type, u8 version, u8 crc, + u8 data_len, u8 *data) +{ + union av8100_configuration config; + struct av8100_status status; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + if ((data_len < 1) || (data_len > HDMI_INFOFRAME_MAX_SIZE)) + return -EINVAL; + + config.infoframes_format.type = type; + config.infoframes_format.version = version; + config.infoframes_format.crc = crc; + config.infoframes_format.length = data_len; + memcpy(&config.infoframes_format.data, data, data_len); + if (av8100_conf_prep(AV8100_COMMAND_INFOFRAMES, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_INFOFRAMES, + NULL, NULL, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + return 0; +} + +static int hdcpchkaesotp(struct hdmi_device *hdev, u8 *crc, u8 *progged) +{ + union av8100_configuration config; + struct av8100_status status; + u8 buf_len; + u8 buf[2]; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.fuse_aes_key_format.fuse_operation = AV8100_FUSE_READ; + memset(config.fuse_aes_key_format.key, 0, AV8100_FUSE_KEY_SIZE); + if (av8100_conf_prep(AV8100_COMMAND_FUSE_AES_KEY, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_FUSE_AES_KEY, + &buf_len, buf, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + if (buf_len == 2) { + *crc = buf[0]; + *progged = buf[1]; + } + + return 0; +} + +static int hdcpfuseaes(struct hdmi_device *hdev, u8 *key, u8 crc, u8 *result) +{ + union av8100_configuration config; + struct av8100_status status; + u8 buf_len; + u8 buf[2]; + + /* Default not OK */ + *result = HDMI_RESULT_NOT_OK; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.fuse_aes_key_format.fuse_operation = AV8100_FUSE_WRITE; + memcpy(config.fuse_aes_key_format.key, key, AV8100_FUSE_KEY_SIZE); + if (av8100_conf_prep(AV8100_COMMAND_FUSE_AES_KEY, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_FUSE_AES_KEY, + &buf_len, buf, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + if (buf_len == 2) { + dev_dbg(hdev->dev, "buf[0]:%02x buf[1]:%02x\n", buf[0], buf[1]); + if ((crc == buf[0]) && (buf[1] == 1)) + /* OK */ + *result = HDMI_RESULT_OK; + else + *result = HDMI_RESULT_CRC_MISMATCH; + } + + return 0; +} + +static int hdcploadaes(struct hdmi_device *hdev, u8 block, u8 key_len, u8 *key, + u8 *result, u8 *crc32) +{ + union av8100_configuration config; + struct av8100_status status; + u8 buf_len; + u8 buf[CRC32_SIZE]; + + /* Default not OK */ + *result = HDMI_RESULT_NOT_OK; + + dev_dbg(hdev->dev, "%s block:%d\n", __func__, block); + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.hdcp_send_key_format.key_number = block; + config.hdcp_send_key_format.data_len = key_len; + memcpy(config.hdcp_send_key_format.data, key, key_len); + if (av8100_conf_prep(AV8100_COMMAND_HDCP_SENDKEY, &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_HDCP_SENDKEY, + &buf_len, buf, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + if ((buf_len == CRC32_SIZE) && (crc32)) { + memcpy(crc32, buf, CRC32_SIZE); + dev_dbg(hdev->dev, "crc32:%02x%02x%02x%02x\n", + crc32[0], crc32[1], crc32[2], crc32[3]); + } + + *result = HDMI_RESULT_OK; + + return 0; +} + +static int hdcpauthencr(struct hdmi_device *hdev, u8 auth_type, u8 encr_type, + u8 *len, u8 *data) +{ + union av8100_configuration config; + struct av8100_status status; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + switch (auth_type) { + case HDMI_HDCP_AUTH_OFF: + default: + config.hdcp_management_format.req_type = + AV8100_HDCP_AUTH_REQ_OFF; + break; + + case HDMI_HDCP_AUTH_START: + config.hdcp_management_format.req_type = + AV8100_HDCP_AUTH_REQ_ON; + break; + + case HDMI_HDCP_AUTH_REV_LIST_REQ: + config.hdcp_management_format.req_type = + AV8100_HDCP_REV_LIST_REQ; + break; + case HDMI_HDCP_AUTH_CONT: + config.hdcp_management_format.req_type = + AV8100_HDCP_AUTH_CONT; + break; + } + + switch (encr_type) { + case HDMI_HDCP_ENCR_OESS: + default: + config.hdcp_management_format.encr_use = + AV8100_HDCP_ENCR_USE_OESS; + break; + + case HDMI_HDCP_ENCR_EESS: + config.hdcp_management_format.encr_use = + AV8100_HDCP_ENCR_USE_EESS; + break; + } + + if (av8100_conf_prep(AV8100_COMMAND_HDCP_MANAGEMENT, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_HDCP_MANAGEMENT, + len, data, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + return 0; +} + +static u8 events_read(struct hdmi_device *hdev) +{ + int ret; + + LOCK_HDMI_EVENTS; + ret = hdev->events; + dev_dbg(hdev->dev, "%s %02x\n", __func__, hdev->events); + UNLOCK_HDMI_EVENTS; + + return ret; +} + +static int events_clear(struct hdmi_device *hdev, u8 ev) +{ + dev_dbg(hdev->dev, "%s %02x\n", __func__, ev); + + LOCK_HDMI_EVENTS; + hdev->events &= ~ev & EVENTS_MASK; + UNLOCK_HDMI_EVENTS; + + return 0; +} + +static int event_wakeup(struct hdmi_device *hdev) +{ + struct kobject *kobj = &hdev->dev->kobj; + + dev_dbg(hdev->dev, "%s", __func__); + + LOCK_HDMI_EVENTS; + hdev->events |= HDMI_EVENT_WAKEUP; + hdev->events_received = true; + UNLOCK_HDMI_EVENTS; + + /* Wake up application waiting for event via call to poll() */ + sysfs_notify(kobj, NULL, SYSFS_EVENT_FILENAME); + wake_up_interruptible(&hdev->event_wq); + + return 0; +} + +static int audiocfg(struct hdmi_device *hdev, struct audio_cfg *cfg) +{ + union av8100_configuration config; + struct av8100_status status; + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_STANDBY) { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup failed\n"); + return -EINVAL; + } + } + + if (status.av8100_state <= AV8100_OPMODE_INIT) { + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + } + + config.audio_input_format.audio_input_if_format = cfg->if_format; + config.audio_input_format.i2s_input_nb = cfg->i2s_entries; + config.audio_input_format.sample_audio_freq = cfg->freq; + config.audio_input_format.audio_word_lg = cfg->word_length; + config.audio_input_format.audio_format = cfg->format; + config.audio_input_format.audio_if_mode = cfg->if_mode; + config.audio_input_format.audio_mute = cfg->mute; + + if (av8100_conf_prep(AV8100_COMMAND_AUDIO_INPUT_FORMAT, + &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + + if (av8100_conf_w(AV8100_COMMAND_AUDIO_INPUT_FORMAT, + NULL, NULL, I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + + return 0; +} + +/* sysfs */ +static ssize_t store_storeastext(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if ((count != HDMI_STOREASTEXT_BIN_SIZE) && + (count != HDMI_STOREASTEXT_TEXT_SIZE) && + (count != HDMI_STOREASTEXT_TEXT_SIZE + 1)) + return -EINVAL; + + if ((count == HDMI_STOREASTEXT_BIN_SIZE) && (*buf == 0x1)) + hdev->sysfs_data.store_as_hextext = true; + else if (((count == HDMI_STOREASTEXT_TEXT_SIZE) || + (count == HDMI_STOREASTEXT_TEXT_SIZE + 1)) && (*buf == '0') && + (*(buf + 1) == '1')) { + hdev->sysfs_data.store_as_hextext = true; + } else { + hdev->sysfs_data.store_as_hextext = false; + } + + dev_dbg(hdev->dev, "store_as_hextext:%0d\n", + hdev->sysfs_data.store_as_hextext); + + return count; +} + +static ssize_t store_plugdeten(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct plug_detect plug_detect; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_PLUGDETEN_TEXT_SIZE) && + (count != HDMI_PLUGDETEN_TEXT_SIZE + 1)) + return -EINVAL; + plug_detect.hdmi_detect_enable = htoi(buf + index); + index += 2; + plug_detect.on_time = htoi(buf + index); + index += 2; + plug_detect.hdmi_off_time = htoi(buf + index); + index += 2; + } else { + if (count != HDMI_PLUGDETEN_BIN_SIZE) + return -EINVAL; + plug_detect.hdmi_detect_enable = *(buf + index++); + plug_detect.on_time = *(buf + index++); + plug_detect.hdmi_off_time = *(buf + index++); + } + + if (plugdeten(hdev, &plug_detect)) + return -EINVAL; + + return count; +} + +static ssize_t store_edidread(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct edid_read edid_read; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + dev_dbg(hdev->dev, "count:%d\n", count); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_EDIDREAD_TEXT_SIZE) && + (count != HDMI_EDIDREAD_TEXT_SIZE + 1)) + return -EINVAL; + edid_read.address = htoi(buf + index); + index += 2; + edid_read.block_nr = htoi(buf + index); + index += 2; + } else { + if (count != HDMI_EDIDREAD_BIN_SIZE) + return -EINVAL; + edid_read.address = *(buf + index++); + edid_read.block_nr = *(buf + index++); + } + + if (edidread(hdev, &edid_read, &hdev->sysfs_data.edid_data.buf_len, + hdev->sysfs_data.edid_data.buf)) + return -EINVAL; + + return count; +} + +static ssize_t show_edidread(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int len; + int index = 0; + int cnt; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + len = hdev->sysfs_data.edid_data.buf_len; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", len); + index += 2; + } else + *(buf + index++) = len; + + dev_dbg(hdev->dev, "len:%02x\n", len); + + cnt = 0; + while (cnt < len) { + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", + hdev->sysfs_data.edid_data.buf[cnt]); + index += 2; + } else + *(buf + index++) = + hdev->sysfs_data.edid_data.buf[cnt]; + + dev_dbg(hdev->dev, "%02x ", + hdev->sysfs_data.edid_data.buf[cnt]); + + cnt++; + } + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_ceceven(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + bool enable = false; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_CECEVEN_TEXT_SIZE) && + (count != HDMI_CECEVEN_TEXT_SIZE + 1)) + return -EINVAL; + if ((*buf == '0') && (*(buf + 1) == '1')) + enable = true; + } else { + if (count != HDMI_CECEVEN_BIN_SIZE) + return -EINVAL; + if (*buf == 0x01) + enable = true; + } + + event_enable(hdev, enable, HDMI_EVENT_CEC | HDMI_EVENT_CECTXERR | + HDMI_EVENT_CECTX); + + return count; +} + +static ssize_t show_cecread(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct cec_rw cec_read; + int index = 0; + int cnt; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (cecread(hdev, &cec_read.src, &cec_read.dest, &cec_read.length, + cec_read.data)) + return -EINVAL; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", cec_read.src); + index += 2; + snprintf(buf + index, 3, "%02x", cec_read.dest); + index += 2; + snprintf(buf + index, 3, "%02x", cec_read.length); + index += 2; + } else { + *(buf + index++) = cec_read.src; + *(buf + index++) = cec_read.dest; + *(buf + index++) = cec_read.length; + } + + dev_dbg(hdev->dev, "len:%02x\n", cec_read.length); + + cnt = 0; + while (cnt < cec_read.length) { + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", cec_read.data[cnt]); + index += 2; + } else + *(buf + index++) = cec_read.data[cnt]; + + dev_dbg(hdev->dev, "%02x ", cec_read.data[cnt]); + + cnt++; + } + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_cecsend(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct cec_rw cec_w; + int index = 0; + int cnt; + int store_as_text; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if ((*buf == 'F') || (*buf == 'f')) + /* To be able to override bin format for test purpose */ + store_as_text = 1; + else + store_as_text = hdev->sysfs_data.store_as_hextext; + + if (store_as_text) { + if ((count < HDMI_CECSEND_TEXT_SIZE_MIN) || + (count > HDMI_CECSEND_TEXT_SIZE_MAX)) + return -EINVAL; + + cec_w.src = htoi(buf + index) & 0x0F; + index += 2; + cec_w.dest = htoi(buf + index); + index += 2; + cec_w.length = htoi(buf + index); + index += 2; + if (cec_w.length > HDMI_CEC_WRITE_MAXSIZE) + return -EINVAL; + cnt = 0; + while (cnt < cec_w.length) { + cec_w.data[cnt] = htoi(buf + index); + index += 2; + dev_dbg(hdev->dev, "%02x ", cec_w.data[cnt]); + cnt++; + } + } else { + if ((count < HDMI_CECSEND_BIN_SIZE_MIN) || + (count > HDMI_CECSEND_BIN_SIZE_MAX)) + return -EINVAL; + + cec_w.src = *(buf + index++); + cec_w.dest = *(buf + index++); + cec_w.length = *(buf + index++); + if (cec_w.length > HDMI_CEC_WRITE_MAXSIZE) + return -EINVAL; + memcpy(cec_w.data, buf + index, cec_w.length); + } + + if (cecsend(hdev, cec_w.src, cec_w.dest, cec_w.length, cec_w.data)) + return -EINVAL; + + return count; +} + +static ssize_t store_infofrsend(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct info_fr info_fr; + int index = 0; + int cnt; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count < HDMI_INFOFRSEND_TEXT_SIZE_MIN) || + (count > HDMI_INFOFRSEND_TEXT_SIZE_MAX)) + return -EINVAL; + + info_fr.type = htoi(&buf[index]); + index += 2; + info_fr.ver = htoi(&buf[index]); + index += 2; + info_fr.crc = htoi(&buf[index]); + index += 2; + info_fr.length = htoi(&buf[index]); + index += 2; + + if (info_fr.length > HDMI_INFOFRAME_MAX_SIZE) + return -EINVAL; + cnt = 0; + while (cnt < info_fr.length) { + info_fr.data[cnt] = htoi(buf + index); + index += 2; + dev_dbg(hdev->dev, "%02x ", info_fr.data[cnt]); + cnt++; + } + } else { + if ((count < HDMI_INFOFRSEND_BIN_SIZE_MIN) || + (count > HDMI_INFOFRSEND_BIN_SIZE_MAX)) + return -EINVAL; + + info_fr.type = *(buf + index++); + info_fr.ver = *(buf + index++); + info_fr.crc = *(buf + index++); + info_fr.length = *(buf + index++); + + if (info_fr.length > HDMI_INFOFRAME_MAX_SIZE) + return -EINVAL; + memcpy(info_fr.data, buf + index, info_fr.length); + } + + if (infofrsend(hdev, info_fr.type, info_fr.ver, info_fr.crc, + info_fr.length, info_fr.data)) + return -EINVAL; + + return count; +} + +static ssize_t store_hdcpeven(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + bool enable = false; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_HDCPEVEN_TEXT_SIZE) && + (count != HDMI_HDCPEVEN_TEXT_SIZE + 1)) + return -EINVAL; + if ((*buf == '0') && (*(buf + 1) == '1')) + enable = true; + } else { + if (count != HDMI_HDCPEVEN_BIN_SIZE) + return -EINVAL; + if (*buf == 0x01) + enable = true; + } + + event_enable(hdev, enable, HDMI_EVENT_HDCP); + + return count; +} + +static ssize_t show_hdcpchkaesotp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + u8 crc; + u8 progged; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdcpchkaesotp(hdev, &crc, &progged)) + return -EINVAL; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", progged); + index += 2; + } else { + *(buf + index++) = progged; + } + + dev_dbg(hdev->dev, "progged:%02x\n", progged); + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_hdcpfuseaes(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct hdcp_fuseaes hdcp_fuseaes; + int index = 0; + int cnt; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + /* Default not OK */ + hdev->sysfs_data.fuse_result = HDMI_RESULT_NOT_OK; + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_HDCP_FUSEAES_TEXT_SIZE) && + (count != HDMI_HDCP_FUSEAES_TEXT_SIZE + 1)) + return -EINVAL; + + cnt = 0; + while (cnt < HDMI_HDCP_FUSEAES_KEYSIZE) { + hdcp_fuseaes.key[cnt] = htoi(buf + index); + index += 2; + dev_dbg(hdev->dev, "%02x ", hdcp_fuseaes.key[cnt]); + cnt++; + } + hdcp_fuseaes.crc = htoi(&buf[index]); + index += 2; + dev_dbg(hdev->dev, "%02x ", hdcp_fuseaes.crc); + } else { + if (count != HDMI_HDCP_FUSEAES_BIN_SIZE) + return -EINVAL; + + memcpy(hdcp_fuseaes.key, buf + index, + HDMI_HDCP_FUSEAES_KEYSIZE); + index += HDMI_HDCP_FUSEAES_KEYSIZE; + hdcp_fuseaes.crc = *(buf + index++); + } + + if (hdcpfuseaes(hdev, hdcp_fuseaes.key, hdcp_fuseaes.crc, + &hdcp_fuseaes.result)) + return -EINVAL; + + dev_dbg(hdev->dev, "fuseresult:%02x ", hdcp_fuseaes.result); + + hdev->sysfs_data.fuse_result = hdcp_fuseaes.result; + + return count; +} + +static ssize_t show_hdcpfuseaes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", hdev->sysfs_data.fuse_result); + index += 2; + } else + *(buf + index++) = hdev->sysfs_data.fuse_result; + + dev_dbg(hdev->dev, "status:%02x\n", hdev->sysfs_data.fuse_result); + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_hdcploadaes(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct hdcp_loadaesone hdcp_loadaes; + int index = 0; + int block_cnt; + int cnt; + u8 crc32_rcvd[CRC32_SIZE]; + u8 crc; + u8 progged; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + /* Default not OK */ + hdev->sysfs_data.loadaes_result = HDMI_RESULT_NOT_OK; + + if (hdcpchkaesotp(hdev, &crc, &progged)) + return -EINVAL; + + if (!progged) { + /* AES is not fused */ + hdcp_loadaes.result = HDMI_AES_NOT_FUSED; + goto store_hdcploadaes_err; + } + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_HDCP_LOADAES_TEXT_SIZE) && + (count != HDMI_HDCP_LOADAES_TEXT_SIZE + 1)) { + dev_err(hdev->dev, "%s", "count mismatch\n"); + return -EINVAL; + } + + /* AES */ + block_cnt = 0; + while (block_cnt < HDMI_HDCP_AES_NR_OF_BLOCKS) { + cnt = 0; + while (cnt < HDMI_HDCP_AES_KEYSIZE) { + hdcp_loadaes.key[cnt] = htoi(buf + index); + index += 2; + dev_dbg(hdev->dev, "%02x ", + hdcp_loadaes.key[cnt]); + cnt++; + } + + if (hdcploadaes(hdev, + block_cnt + HDMI_HDCP_AES_BLOCK_START, + HDMI_HDCP_AES_KEYSIZE, + hdcp_loadaes.key, + &hdcp_loadaes.result, + crc32_rcvd)) { + dev_err(hdev->dev, "%s %d\n", + "hdcploadaes err aes block", + block_cnt + HDMI_HDCP_AES_BLOCK_START); + return -EINVAL; + } + + if (hdcp_loadaes.result) + goto store_hdcploadaes_err; + + block_cnt++; + } + + /* KSV */ + memset(hdcp_loadaes.key, 0, HDMI_HDCP_AES_KSVZEROESSIZE); + cnt = HDMI_HDCP_AES_KSVZEROESSIZE; + while (cnt < HDMI_HDCP_AES_KSVSIZE + + HDMI_HDCP_AES_KSVZEROESSIZE) { + hdcp_loadaes.key[cnt] = + htoi(&buf[index]); + index += 2; + dev_dbg(hdev->dev, "%02x ", hdcp_loadaes.key[cnt]); + cnt++; + } + + if (hdcploadaes(hdev, HDMI_HDCP_KSV_BLOCK, + HDMI_HDCP_AES_KSVSIZE + + HDMI_HDCP_AES_KSVZEROESSIZE, + hdcp_loadaes.key, + &hdcp_loadaes.result, + NULL)) { + dev_err(hdev->dev, + "%s %d\n", "hdcploadaes err in ksv\n", + block_cnt + HDMI_HDCP_AES_BLOCK_START); + return -EINVAL; + } + + if (hdcp_loadaes.result) + goto store_hdcploadaes_err; + + /* CRC32 */ + for (cnt = 0; cnt < CRC32_SIZE; cnt++) { + hdcp_loadaes.crc32[cnt] = htoi(buf + index); + index += 2; + } + + if (memcmp(hdcp_loadaes.crc32, crc32_rcvd, CRC32_SIZE)) { + dev_dbg(hdev->dev, "crc32exp:%02x%02x%02x%02x\n", + hdcp_loadaes.crc32[0], + hdcp_loadaes.crc32[1], + hdcp_loadaes.crc32[2], + hdcp_loadaes.crc32[3]); + hdcp_loadaes.result = HDMI_RESULT_CRC_MISMATCH; + goto store_hdcploadaes_err; + } + } else { + if (count != HDMI_HDCP_LOADAES_BIN_SIZE) { + dev_err(hdev->dev, "%s", "count mismatch\n"); + return -EINVAL; + } + + /* AES */ + block_cnt = 0; + while (block_cnt < HDMI_HDCP_AES_NR_OF_BLOCKS) { + memcpy(hdcp_loadaes.key, buf + index, + HDMI_HDCP_AES_KEYSIZE); + index += HDMI_HDCP_AES_KEYSIZE; + + if (hdcploadaes(hdev, + block_cnt + HDMI_HDCP_AES_BLOCK_START, + HDMI_HDCP_AES_KEYSIZE, + hdcp_loadaes.key, + &hdcp_loadaes.result, + crc32_rcvd)) { + dev_err(hdev->dev, "%s %d\n", + "hdcploadaes err aes block", + block_cnt + HDMI_HDCP_AES_BLOCK_START); + return -EINVAL; + } + + if (hdcp_loadaes.result) + goto store_hdcploadaes_err; + + block_cnt++; + } + + /* KSV */ + memset(hdcp_loadaes.key, 0, HDMI_HDCP_AES_KSVZEROESSIZE); + memcpy(hdcp_loadaes.key + HDMI_HDCP_AES_KSVZEROESSIZE, + buf + index, + HDMI_HDCP_AES_KSVSIZE); + index += HDMI_HDCP_AES_KSVSIZE; + + if (hdcploadaes(hdev, HDMI_HDCP_KSV_BLOCK, + HDMI_HDCP_AES_KSVSIZE + + HDMI_HDCP_AES_KSVZEROESSIZE, + hdcp_loadaes.key, + &hdcp_loadaes.result, + NULL)) { + dev_err(hdev->dev, "%s %d\n", + "hdcploadaes err in ksv\n", + block_cnt + HDMI_HDCP_AES_BLOCK_START); + return -EINVAL; + } + + memcpy(hdcp_loadaes.crc32, buf + index, CRC32_SIZE); + index += CRC32_SIZE; + + /* CRC32 */ + if (memcmp(hdcp_loadaes.crc32, crc32_rcvd, CRC32_SIZE)) { + dev_dbg(hdev->dev, "crc32exp:%02x%02x%02x%02x\n", + hdcp_loadaes.crc32[0], + hdcp_loadaes.crc32[1], + hdcp_loadaes.crc32[2], + hdcp_loadaes.crc32[3]); + hdcp_loadaes.result = HDMI_RESULT_CRC_MISMATCH; + } + } + +store_hdcploadaes_err: + hdev->sysfs_data.loadaes_result = hdcp_loadaes.result; + return count; +} + +static ssize_t show_hdcploadaes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", + hdev->sysfs_data.loadaes_result); + index += 2; + } else + *(buf + index++) = hdev->sysfs_data.loadaes_result; + + dev_dbg(hdev->dev, "result:%02x\n", hdev->sysfs_data.loadaes_result); + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_hdcpauthencr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct hdcp_authencr hdcp_authencr; + int index = 0; + u8 crc; + u8 progged; + int result = HDMI_RESULT_NOT_OK; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + /* Default */ + hdev->sysfs_data.authencr.buf_len = 0; + + if (hdcpchkaesotp(hdev, &crc, &progged)) { + result = HDMI_AES_NOT_FUSED; + goto store_hdcpauthencr_end; + } + + if (!progged) { + /* AES is not fused */ + result = HDMI_AES_NOT_FUSED; + goto store_hdcpauthencr_end; + } + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_HDCPAUTHENCR_TEXT_SIZE) && + (count != HDMI_HDCPAUTHENCR_TEXT_SIZE + 1)) + goto store_hdcpauthencr_end; + + hdcp_authencr.auth_type = htoi(buf + index); + index += 2; + hdcp_authencr.encr_type = htoi(buf + index); + index += 2; + } else { + if (count != HDMI_HDCPAUTHENCR_BIN_SIZE) + goto store_hdcpauthencr_end; + + hdcp_authencr.auth_type = *(buf + index++); + hdcp_authencr.encr_type = *(buf + index++); + } + + if (hdcpauthencr(hdev, hdcp_authencr.auth_type, hdcp_authencr.encr_type, + &hdev->sysfs_data.authencr.buf_len, + hdev->sysfs_data.authencr.buf)) + goto store_hdcpauthencr_end; + + result = HDMI_RESULT_OK; + +store_hdcpauthencr_end: + hdev->sysfs_data.authencr.result = result; + return count; +} + +static ssize_t show_hdcpauthencr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int len; + int index = 0; + int cnt; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + /* result */ + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", + hdev->sysfs_data.authencr.result); + index += 2; + } else + *(buf + index++) = hdev->sysfs_data.authencr.result; + + dev_dbg(hdev->dev, "result:%02x\n", hdev->sysfs_data.authencr.result); + + /* resp_size */ + len = hdev->sysfs_data.authencr.buf_len; + if (len > AUTH_BUF_LEN) + len = AUTH_BUF_LEN; + dev_dbg(hdev->dev, "resp_size:%d\n", len); + + /* resp */ + cnt = 0; + while (cnt < len) { + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", + hdev->sysfs_data.authencr.buf[cnt]); + index += 2; + + dev_dbg(hdev->dev, "%02x ", + hdev->sysfs_data.authencr.buf[cnt]); + + } else + *(buf + index++) = hdev->sysfs_data.authencr.buf[cnt]; + + cnt++; + } + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t show_hdcpstateget(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + u8 hdcp_state; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (av8100_reg_gen_status_r(NULL, NULL, NULL, NULL, NULL, &hdcp_state)) + return -EINVAL; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", hdcp_state); + index += 2; + } else + *(buf + index++) = hdcp_state; + + dev_dbg(hdev->dev, "status:%02x\n", hdcp_state); + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t show_evread(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int index = 0; + u8 ev; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + ev = events_read(hdev); + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", ev); + index += 2; + } else + *(buf + index++) = ev; + + if (hdev->sysfs_data.store_as_hextext) + index++; + + /* Events are read: clear events */ + events_clear(hdev, EVENTS_MASK); + + return index; +} + +static ssize_t store_evclr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + u8 ev; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_EVCLR_TEXT_SIZE) && + (count != HDMI_EVCLR_TEXT_SIZE + 1)) + return -EINVAL; + + ev = htoi(&buf[index]); + index += 2; + } else { + if (count != HDMI_EVCLR_BIN_SIZE) + return -EINVAL; + + ev = *(buf + index++); + } + + events_clear(hdev, ev); + + return count; +} + +static ssize_t store_audiocfg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + struct audio_cfg audio_cfg; + int index = 0; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_AUDIOCFG_TEXT_SIZE) && + (count != HDMI_AUDIOCFG_TEXT_SIZE + 1)) + return -EINVAL; + + audio_cfg.if_format = htoi(&buf[index]); + index += 2; + audio_cfg.i2s_entries = htoi(&buf[index]); + index += 2; + audio_cfg.freq = htoi(&buf[index]); + index += 2; + audio_cfg.word_length = htoi(&buf[index]); + index += 2; + audio_cfg.format = htoi(&buf[index]); + index += 2; + audio_cfg.if_mode = htoi(&buf[index]); + index += 2; + audio_cfg.mute = htoi(&buf[index]); + index += 2; + } else { + if (count != HDMI_AUDIOCFG_BIN_SIZE) + return -EINVAL; + + audio_cfg.if_format = *(buf + index++); + audio_cfg.i2s_entries = *(buf + index++); + audio_cfg.freq = *(buf + index++); + audio_cfg.word_length = *(buf + index++); + audio_cfg.format = *(buf + index++); + audio_cfg.if_mode = *(buf + index++); + audio_cfg.mute = *(buf + index++); + } + + audiocfg(hdev, &audio_cfg); + + return count; +} + +static ssize_t show_plugstatus(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int index = 0; + struct av8100_status av8100_status; + u8 plstat; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + av8100_status = av8100_status_get(); + plstat = av8100_status.av8100_plugin_status == AV8100_HDMI_PLUGIN; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", plstat); + index += 2; + } else + *(buf + index++) = plstat; + + if (hdev->sysfs_data.store_as_hextext) + index++; + + return index; +} + +static ssize_t store_poweronoff(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + bool enable = false; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + if (hdev->sysfs_data.store_as_hextext) { + if ((count != HDMI_POWERONOFF_TEXT_SIZE) && + (count != HDMI_POWERONOFF_TEXT_SIZE + 1)) + return -EINVAL; + if ((*buf == '0') && (*(buf + 1) == '1')) + enable = true; + } else { + if (count != HDMI_POWERONOFF_BIN_SIZE) + return -EINVAL; + if (*buf == 0x01) + enable = true; + } + + if (enable == 0) { + if (av8100_powerdown() != 0) { + dev_err(hdev->dev, "av8100_powerdown FAIL\n"); + return -EINVAL; + } + } else { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup FAIL\n"); + return -EINVAL; + } + } + + return count; +} + +static ssize_t show_poweronoff(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + int index = 0; + struct av8100_status status; + u8 power_state; + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + status = av8100_status_get(); + if (status.av8100_state < AV8100_OPMODE_SCAN) + power_state = 0; + else + power_state = 1; + + if (hdev->sysfs_data.store_as_hextext) { + snprintf(buf + index, 3, "%02x", power_state); + index += 3; + } else { + *(buf + index++) = power_state; + } + + return index; +} + +static ssize_t store_evwakeup(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hdmi_device *hdev = dev_to_hdev(dev); + + if (!hdev) + return -EFAULT; + + dev_dbg(hdev->dev, "%s\n", __func__); + + event_wakeup(hdev); + + return count; +} + +static int hdmi_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static int hdmi_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/* ioctl */ +static long hdmi_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + u8 value = 0; + struct hdmi_register reg; + struct av8100_status status; + u8 aes_status; + struct hdmi_device *hdev = devnr_to_hdev(HDMI_DEVNR_DEFAULT); + + switch (cmd) { + case IOC_PLUG_DETECT_ENABLE: + { + struct plug_detect plug_detect; + + if (copy_from_user(&plug_detect, (void *)arg, + sizeof(struct plug_detect))) + return -EINVAL; + + if (plugdeten(hdev, &plug_detect)) + return -EINVAL; + } + break; + + case IOC_EDID_READ: + { + struct edid_read edid_read; + + if (copy_from_user(&edid_read, (void *)arg, + sizeof(struct edid_read))) + return -EINVAL; + + if (edidread(hdev, &edid_read, &edid_read.data_length, + edid_read.data)) + return -EINVAL; + + if (copy_to_user((void *)arg, (void *)&edid_read, + sizeof(struct edid_read))) { + return -EINVAL; + } + } + break; + + case IOC_CEC_EVENT_ENABLE: + if (copy_from_user(&value, (void *)arg, sizeof(u8))) + return -EINVAL; + + event_enable(hdev, value != 0, + HDMI_EVENT_CEC | HDMI_EVENT_CECTXERR | + HDMI_EVENT_CECTX); + break; + + case IOC_CEC_READ: + { + struct cec_rw cec_read; + + if (cecread(hdev, &cec_read.src, &cec_read.dest, + &cec_read.length, cec_read.data)) + return -EINVAL; + + if (copy_to_user((void *)arg, (void *)&cec_read, + sizeof(struct cec_rw))) { + return -EINVAL; + } + } + break; + + case IOC_CEC_SEND: + { + struct cec_rw cec_send; + + if (copy_from_user(&cec_send, (void *)arg, + sizeof(struct cec_rw))) + return -EINVAL; + + if (cecsend(hdev, cec_send.src, cec_send.dest, cec_send.length, + cec_send.data)) + return -EINVAL; + } + break; + + case IOC_INFOFRAME_SEND: + { + struct info_fr info_fr; + + if (copy_from_user(&info_fr, (void *)arg, + sizeof(struct info_fr))) + return -EINVAL; + + if (infofrsend(hdev, info_fr.type, info_fr.ver, info_fr.crc, + info_fr.length, info_fr.data)) + return -EINVAL; + } + break; + + case IOC_HDCP_EVENT_ENABLE: + if (copy_from_user(&value, (void *)arg, sizeof(u8))) + return -EINVAL; + + event_enable(hdev, value != 0, HDMI_EVENT_HDCP); + break; + + case IOC_HDCP_CHKAESOTP: + if (hdcpchkaesotp(hdev, &value, &aes_status)) + return -EINVAL; + + if (copy_to_user((void *)arg, (void *)&aes_status, + sizeof(u8))) { + return -EINVAL; + } + break; + + case IOC_HDCP_FUSEAES: + { + struct hdcp_fuseaes hdcp_fuseaes; + + if (copy_from_user(&hdcp_fuseaes, (void *)arg, + sizeof(struct hdcp_fuseaes))) + return -EINVAL; + + if (hdcpfuseaes(hdev, hdcp_fuseaes.key, hdcp_fuseaes.crc, + &hdcp_fuseaes.result)) + return -EINVAL; + + if (copy_to_user((void *)arg, (void *)&hdcp_fuseaes, + sizeof(struct hdcp_fuseaes))) { + return -EINVAL; + } + } + break; + + case IOC_HDCP_LOADAES: + { + int block_cnt; + struct hdcp_loadaesone hdcp_loadaesone; + struct hdcp_loadaesall hdcp_loadaesall; + + if (copy_from_user(&hdcp_loadaesall, (void *)arg, + sizeof(struct hdcp_loadaesall))) + return -EINVAL; + + if (hdcpchkaesotp(hdev, &value, &aes_status)) + return -EINVAL; + + if (!aes_status) { + /* AES is not fused */ + hdcp_loadaesone.result = HDMI_AES_NOT_FUSED; + goto ioc_hdcploadaes_err; + } + + /* AES */ + block_cnt = 0; + while (block_cnt < HDMI_HDCP_AES_NR_OF_BLOCKS) { + memcpy(hdcp_loadaesone.key, hdcp_loadaesall.key + + block_cnt * HDMI_HDCP_AES_KEYSIZE, + HDMI_HDCP_AES_KEYSIZE); + + if (hdcploadaes(hdev, + block_cnt + HDMI_HDCP_AES_BLOCK_START, + HDMI_HDCP_AES_KEYSIZE, + hdcp_loadaesone.key, + &hdcp_loadaesone.result, + hdcp_loadaesone.crc32)) + return -EINVAL; + + if (hdcp_loadaesone.result) + return -EINVAL; + + block_cnt++; + } + + /* KSV */ + memset(hdcp_loadaesone.key, 0, HDMI_HDCP_AES_KSVZEROESSIZE); + memcpy(hdcp_loadaesone.key + HDMI_HDCP_AES_KSVZEROESSIZE, + hdcp_loadaesall.ksv, HDMI_HDCP_AES_KSVSIZE); + + if (hdcploadaes(hdev, HDMI_HDCP_KSV_BLOCK, + HDMI_HDCP_AES_KSVSIZE + + HDMI_HDCP_AES_KSVZEROESSIZE, + hdcp_loadaesone.key, + &hdcp_loadaesone.result, + NULL)) + return -EINVAL; + + if (hdcp_loadaesone.result) + return -EINVAL; + + /* CRC32 */ + if (memcmp(hdcp_loadaesall.crc32, hdcp_loadaesone.crc32, + CRC32_SIZE)) { + dev_dbg(hdev->dev, "crc32exp:%02x%02x%02x%02x\n", + hdcp_loadaesall.crc32[0], + hdcp_loadaesall.crc32[1], + hdcp_loadaesall.crc32[2], + hdcp_loadaesall.crc32[3]); + hdcp_loadaesone.result = HDMI_RESULT_CRC_MISMATCH; + goto ioc_hdcploadaes_err; + } + +ioc_hdcploadaes_err: + hdcp_loadaesall.result = hdcp_loadaesone.result; + + if (copy_to_user((void *)arg, (void *)&hdcp_loadaesall, + sizeof(struct hdcp_loadaesall))) { + return -EINVAL; + } + } + break; + + case IOC_HDCP_AUTHENCR_REQ: + { + struct hdcp_authencr hdcp_authencr; + int result = HDMI_RESULT_NOT_OK; + + u8 buf[AUTH_BUF_LEN]; + + if (copy_from_user(&hdcp_authencr, (void *)arg, + sizeof(struct hdcp_authencr))) + return -EINVAL; + + /* Default not OK */ + hdcp_authencr.resp_size = 0; + + if (hdcpchkaesotp(hdev, &value, &aes_status)) { + result = HDMI_AES_NOT_FUSED; + goto hdcp_authencr_end; + } + + if (!aes_status) { + /* AES is not fused */ + result = HDMI_AES_NOT_FUSED; + goto hdcp_authencr_end; + } + + if (hdcpauthencr(hdev, hdcp_authencr.auth_type, + hdcp_authencr.encr_type, + &value, + buf)) { + result = HDMI_RESULT_NOT_OK; + goto hdcp_authencr_end; + } + + if (value > AUTH_BUF_LEN) + value = AUTH_BUF_LEN; + + result = HDMI_RESULT_OK; + hdcp_authencr.resp_size = value; + memcpy(hdcp_authencr.resp, buf, value); + +hdcp_authencr_end: + hdcp_authencr.result = result; + if (copy_to_user((void *)arg, (void *)&hdcp_authencr, + sizeof(struct hdcp_authencr))) + return -EINVAL; + } + break; + + case IOC_HDCP_STATE_GET: + if (av8100_reg_gen_status_r(NULL, NULL, NULL, NULL, NULL, + &value)) + return -EINVAL; + + if (copy_to_user((void *)arg, (void *)&value, + sizeof(u8))) { + return -EINVAL; + } + break; + + case IOC_EVENTS_READ: + value = events_read(hdev); + + if (copy_to_user((void *)arg, (void *)&value, + sizeof(u8))) { + return -EINVAL; + } + + /* Events are read: clear events */ + events_clear(hdev, EVENTS_MASK); + break; + + case IOC_EVENTS_CLEAR: + if (copy_from_user(&value, (void *)arg, sizeof(u8))) + return -EINVAL; + + events_clear(hdev, value); + break; + + case IOC_AUDIO_CFG: + { + struct audio_cfg audio_cfg; + + if (copy_from_user(&audio_cfg, (void *)arg, + sizeof(struct audio_cfg))) + return -EINVAL; + + audiocfg(hdev, &audio_cfg); + } + break; + + case IOC_PLUG_STATUS: + status = av8100_status_get(); + value = status.av8100_plugin_status == AV8100_HDMI_PLUGIN; + + if (copy_to_user((void *)arg, (void *)&value, + sizeof(u8))) { + return -EINVAL; + } + break; + + case IOC_POWERONOFF: + /* Get desired power state on or off */ + if (copy_from_user(&value, (void *)arg, sizeof(u8))) + return -EINVAL; + + if (value == 0) { + if (av8100_powerdown() != 0) { + dev_err(hdev->dev, "av8100_powerdown FAIL\n"); + return -EINVAL; + } + } else { + if (av8100_powerup() != 0) { + dev_err(hdev->dev, "av8100_powerup FAIL\n"); + return -EINVAL; + } + } + break; + + case IOC_EVENT_WAKEUP: + /* Trigger event */ + event_wakeup(hdev); + break; + + case IOC_POWERSTATE: + status = av8100_status_get(); + value = status.av8100_state >= AV8100_OPMODE_SCAN; + + if (copy_to_user((void *)arg, (void *)&value, + sizeof(u8))) { + return -EINVAL; + } + break; + + /* Internal */ + case IOC_HDMI_ENABLE_INTERRUPTS: + av8100_disable_interrupt(); + if (av8100_enable_interrupt() != 0) { + dev_err(hdev->dev, "av8100_ei FAIL\n"); + return -EINVAL; + } + break; + + case IOC_HDMI_DOWNLOAD_FW: + if (av8100_download_firmware(I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100 dl fw FAIL\n"); + return -EINVAL; + } + break; + + case IOC_HDMI_ONOFF: + { + union av8100_configuration config; + + /* Get desired HDMI mode on or off */ + if (copy_from_user(&value, (void *)arg, sizeof(u8))) + return -EFAULT; + + if (av8100_conf_get(AV8100_COMMAND_HDMI, &config) != 0) { + dev_err(hdev->dev, "av8100_conf_get FAIL\n"); + return -EINVAL; + } + if (value == 0) + config.hdmi_format.hdmi_mode = AV8100_HDMI_OFF; + else + config.hdmi_format.hdmi_mode = AV8100_HDMI_ON; + + if (av8100_conf_prep(AV8100_COMMAND_HDMI, &config) != 0) { + dev_err(hdev->dev, "av8100_conf_prep FAIL\n"); + return -EINVAL; + } + if (av8100_conf_w(AV8100_COMMAND_HDMI, NULL, NULL, + I2C_INTERFACE) != 0) { + dev_err(hdev->dev, "av8100_conf_w FAIL\n"); + return -EINVAL; + } + } + break; + + case IOC_HDMI_REGISTER_WRITE: + if (copy_from_user(®, (void *)arg, + sizeof(struct hdmi_register))) { + return -EINVAL; + } + + if (av8100_reg_w(reg.offset, reg.value) != 0) { + dev_err(hdev->dev, "hdmi_register_write FAIL\n"); + return -EINVAL; + } + break; + + case IOC_HDMI_REGISTER_READ: + if (copy_from_user(®, (void *)arg, + sizeof(struct hdmi_register))) { + return -EINVAL; + } + + if (av8100_reg_r(reg.offset, ®.value) != 0) { + dev_err(hdev->dev, "hdmi_register_write FAIL\n"); + return -EINVAL; + } + + if (copy_to_user((void *)arg, (void *)®, + sizeof(struct hdmi_register))) { + return -EINVAL; + } + break; + + case IOC_HDMI_STATUS_GET: + status = av8100_status_get(); + + if (copy_to_user((void *)arg, (void *)&status, + sizeof(struct av8100_status))) { + return -EINVAL; + } + break; + + case IOC_HDMI_CONFIGURATION_WRITE: + { + struct hdmi_command_register command_reg; + + if (copy_from_user(&command_reg, (void *)arg, + sizeof(struct hdmi_command_register)) != 0) { + dev_err(hdev->dev, "IOC_HDMI_CONFIGURATION_WRITE " + "fail 1\n"); + command_reg.return_status = EINVAL; + } else { + command_reg.return_status = 0; + if (av8100_conf_w_raw(command_reg.cmd_id, + command_reg.buf_len, + command_reg.buf, + &(command_reg.buf_len), + command_reg.buf) != 0) { + dev_err(hdev->dev, + "IOC_HDMI_CONFIGURATION_WRITE " + "fail 2\n"); + command_reg.return_status = EINVAL; + } + } + + if (copy_to_user((void *)arg, (void *)&command_reg, + sizeof(struct hdmi_command_register)) != 0) { + return -EINVAL; + } + } + break; + + default: + break; + } + + return 0; +} + +static unsigned int +hdmi_poll(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + struct hdmi_device *hdev; + + hdev = devnr_to_hdev(HDMI_DEVNR_DEFAULT); + if (!hdev) + return 0; + + dev_dbg(hdev->dev, "%s\n", __func__); + + poll_wait(filp, &hdev->event_wq , wait); + + LOCK_HDMI_EVENTS; + if (hdev->events_received == true) { + hdev->events_received = false; + mask = POLLIN | POLLRDNORM; + } + UNLOCK_HDMI_EVENTS; + + return mask; +} + +static const struct file_operations hdmi_fops = { + .owner = THIS_MODULE, + .open = hdmi_open, + .release = hdmi_release, + .unlocked_ioctl = hdmi_ioctl, + .poll = hdmi_poll +}; + +/* Event callback function called by hw driver */ +void hdmi_event(enum av8100_hdmi_event ev) +{ + int events_old; + int events_new; + struct hdmi_device *hdev; + struct kobject *kobj; + + hdev = devnr_to_hdev(HDMI_DEVNR_DEFAULT); + if (!hdev) + return; + + dev_dbg(hdev->dev, "hdmi_event %02x\n", ev); + + kobj = &(hdev->dev->kobj); + + LOCK_HDMI_EVENTS; + + events_old = hdev->events; + + /* Set event */ + switch (ev) { + case AV8100_HDMI_EVENT_HDMI_PLUGIN: + hdev->events &= ~HDMI_EVENT_HDMI_PLUGOUT; + hdev->events |= HDMI_EVENT_HDMI_PLUGIN; + break; + + case AV8100_HDMI_EVENT_HDMI_PLUGOUT: + hdev->events &= ~HDMI_EVENT_HDMI_PLUGIN; + hdev->events |= HDMI_EVENT_HDMI_PLUGOUT; + cec_tx_status(hdev, CEC_TX_SET_FREE); + break; + + case AV8100_HDMI_EVENT_CEC: + hdev->events |= HDMI_EVENT_CEC; + break; + + case AV8100_HDMI_EVENT_HDCP: + hdev->events |= HDMI_EVENT_HDCP; + break; + + case AV8100_HDMI_EVENT_CECTXERR: + hdev->events |= HDMI_EVENT_CECTXERR; + cec_tx_status(hdev, CEC_TX_SET_FREE); + break; + + case AV8100_HDMI_EVENT_CECTX: + hdev->events |= HDMI_EVENT_CECTX; + cec_tx_status(hdev, CEC_TX_SET_FREE); + break; + + default: + break; + } + + events_new = hdev->events_mask & hdev->events; + + UNLOCK_HDMI_EVENTS; + + dev_dbg(hdev->dev, "hdmi events:%02x, events_old:%02x mask:%02x\n", + events_new, events_old, hdev->events_mask); + + if (events_new != events_old) { + /* Wake up application waiting for event via call to poll() */ + sysfs_notify(kobj, NULL, SYSFS_EVENT_FILENAME); + + LOCK_HDMI_EVENTS; + hdev->events_received = true; + UNLOCK_HDMI_EVENTS; + + wake_up_interruptible(&hdev->event_wq); + } +} +EXPORT_SYMBOL(hdmi_event); + +int hdmi_device_register(struct hdmi_device *hdev) +{ + hdev->miscdev.minor = MISC_DYNAMIC_MINOR; + hdev->miscdev.name = "hdmi"; + hdev->miscdev.fops = &hdmi_fops; + + if (misc_register(&hdev->miscdev)) { + pr_err("hdmi misc_register failed\n"); + return -EFAULT; + } + + hdev->dev = hdev->miscdev.this_device; + + return 0; +} + +int __init hdmi_init(void) +{ + struct hdmi_device *hdev; + int i; + int ret; + + /* Allocate device data */ + hdev = kzalloc(sizeof(struct hdmi_device), GFP_KERNEL); + if (!hdev) { + pr_err("%s: Alloc failure\n", __func__); + return -ENOMEM; + } + + /* Add to list */ + list_add_tail(&hdev->list, &hdmi_device_list); + + if (hdmi_device_register(hdev)) { + pr_err("%s: Alloc failure\n", __func__); + return -EFAULT; + } + + hdev->devnr = HDMI_DEVNR_DEFAULT; + + /* Default sysfs file format is hextext */ + hdev->sysfs_data.store_as_hextext = true; + + init_waitqueue_head(&hdev->event_wq); + + /* Create sysfs attrs */ + for (i = 0; attr_name(hdmi_sysfs_attrs[i]); i++) { + ret = device_create_file(hdev->dev, &hdmi_sysfs_attrs[i]); + if (ret) + dev_err(hdev->dev, + "Unable to create sysfs attr %s (%d)\n", + hdmi_sysfs_attrs[i].attr.name, ret); + } + + /* Register event callback */ + av8100_hdmi_event_cb_set(hdmi_event); + + return 0; +} +late_initcall(hdmi_init); + +void hdmi_exit(void) +{ + struct hdmi_device *hdev = NULL; + int i; + + if (list_empty(&hdmi_device_list)) + return; + else + hdev = list_entry(hdmi_device_list.next, + struct hdmi_device, list); + + /* Deregister event callback */ + av8100_hdmi_event_cb_set(NULL); + + /* Remove sysfs attrs */ + for (i = 0; attr_name(hdmi_sysfs_attrs[i]); i++) + device_remove_file(hdev->dev, &hdmi_sysfs_attrs[i]); + + misc_deregister(&hdev->miscdev); + + /* Remove from list */ + list_del(&hdev->list); + + /* Free device data */ + kfree(hdev); +} diff --git a/drivers/video/av8100/hdmi_loc.h b/drivers/video/av8100/hdmi_loc.h new file mode 100644 index 00000000000..20314910db9 --- /dev/null +++ b/drivers/video/av8100/hdmi_loc.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Author: Per Persson <per.xb.persson@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ +#ifndef __HDMI_LOC__H__ +#define __HDMI_LOC__H__ + +#define EDID_BUF_LEN 128 +#define COMMAND_BUF_LEN 128 +#define AES_KEY_SIZE 16 +#define CRC32_SIZE 4 +#define AUTH_BUF_LEN 126 +#define CECTX_TRY 20 +#define CECTX_WAITTIME 25 + +struct edid_data { + u8 buf_len; + u8 buf[EDID_BUF_LEN]; +}; + +struct authencr { + int result; + u8 buf_len; + u8 buf[AUTH_BUF_LEN]; +}; + +struct hdmi_register { + unsigned char value; + unsigned char offset; +}; + +struct hdcp_loadaesone { + u8 key[AES_KEY_SIZE]; + u8 result; + u8 crc32[CRC32_SIZE]; +}; + +struct hdmi_sysfs_data { + bool store_as_hextext; + struct plug_detect plug_detect; + bool enable_cec_event; + struct edid_data edid_data; + struct cec_rw cec_read; + bool fuse_result; + int loadaes_result; + struct authencr authencr; +}; + +struct hdmi_command_register { + unsigned char cmd_id; /* input */ + unsigned char buf_len; /* input, output */ + unsigned char buf[COMMAND_BUF_LEN]; /* input, output */ + unsigned char return_status; /* output */ +}; + +enum cec_tx_status_action { + CEC_TX_SET_FREE, + CEC_TX_SET_BUSY, + CEC_TX_CHECK +}; + +/* Internal */ +#define IOC_HDMI_ENABLE_INTERRUPTS _IOWR(HDMI_IOC_MAGIC, 32, int) +#define IOC_HDMI_DOWNLOAD_FW _IOWR(HDMI_IOC_MAGIC, 33, int) +#define IOC_HDMI_ONOFF _IOWR(HDMI_IOC_MAGIC, 34, int) +#define IOC_HDMI_REGISTER_WRITE _IOWR(HDMI_IOC_MAGIC, 35, int) +#define IOC_HDMI_REGISTER_READ _IOWR(HDMI_IOC_MAGIC, 36, int) +#define IOC_HDMI_STATUS_GET _IOWR(HDMI_IOC_MAGIC, 37, int) +#define IOC_HDMI_CONFIGURATION_WRITE _IOWR(HDMI_IOC_MAGIC, 38, int) + +#endif /* __HDMI_LOC__H__ */ |