| /* |
| * 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/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" |
| |
| #define SYSFS_EVENT_FILENAME "evread" |
| |
| 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 |
| |
| static int device_open; |
| static int events; |
| static int events_mask; |
| static bool events_received; |
| static wait_queue_head_t hdmi_event_wq; |
| struct device *hdmidev; |
| |
| 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_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 DEVICE_ATTR(storeastext, S_IWUSR, NULL, store_storeastext); |
| static DEVICE_ATTR(plugdeten, S_IWUSR, NULL, store_plugdeten); |
| static DEVICE_ATTR(edidread, S_IRUGO | S_IWUSR, show_edidread, store_edidread); |
| static DEVICE_ATTR(ceceven, S_IWUSR, NULL, store_ceceven); |
| static DEVICE_ATTR(cecread, S_IRUGO, show_cecread, NULL); |
| static DEVICE_ATTR(cecsend, S_IWUSR, NULL, store_cecsend); |
| static DEVICE_ATTR(infofrsend, S_IWUSR, NULL, store_infofrsend); |
| static DEVICE_ATTR(hdcpeven, S_IWUSR, NULL, store_hdcpeven); |
| static DEVICE_ATTR(hdcpchkaesotp, S_IRUGO, show_hdcpchkaesotp, NULL); |
| static DEVICE_ATTR(hdcpfuseaes, S_IRUGO | S_IWUSR, show_hdcpfuseaes, |
| store_hdcpfuseaes); |
| static DEVICE_ATTR(hdcploadaes, S_IRUGO | S_IWUSR, show_hdcploadaes, |
| store_hdcploadaes); |
| static DEVICE_ATTR(hdcpauthencr, S_IWUSR, NULL, store_hdcpauthencr); |
| static DEVICE_ATTR(hdcpstateget, S_IRUGO, show_hdcpstateget, NULL); |
| static DEVICE_ATTR(evread, S_IRUGO, show_evread, NULL); |
| static DEVICE_ATTR(evclr, S_IWUSR, NULL, store_evclr); |
| static DEVICE_ATTR(audiocfg, S_IWUSR, NULL, store_audiocfg); |
| |
| /* Hex to int conversion */ |
| static unsigned int htoi(const char *ptr) |
| { |
| unsigned int value = 0; |
| char ch = *ptr; |
| |
| if (!ptr) |
| return 0; |
| |
| 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 int event_enable(bool enable, enum hdmi_event ev) |
| { |
| dev_dbg(hdmidev, "enable_event %d %02x\n", enable, ev); |
| if (enable) |
| events_mask |= ev; |
| else |
| events_mask &= ~ev; |
| |
| return 0; |
| } |
| |
| static int plugdeten(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| 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(hdmidev, "Failed to write the value to av8100 " |
| "register\n"); |
| return -EFAULT; |
| } |
| |
| status = av8100_status_get(); |
| if (status.av8100_state < AV8100_OPMODE_IDLE) { |
| av8100_disable_interrupt(); |
| av8100_enable_interrupt(); |
| } |
| |
| event_enable(pldet->hdmi_detect_enable != 0, |
| HDMI_EVENT_HDMI_PLUGIN); |
| event_enable(pldet->hdmi_detect_enable != 0, |
| HDMI_EVENT_HDMI_PLUGOUT); |
| |
| return retval; |
| } |
| |
| static int edidread(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_EDID_SECTION_READBACK, |
| len, |
| data, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(hdmidev, "len:%0x\n", *len); |
| |
| return 0; |
| } |
| |
| static int cecread(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100 dl fw FAIL\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (av8100_conf_prep(AV8100_COMMAND_CEC_MESSAGE_READ_BACK, |
| &config) != 0) { |
| dev_err(hdmidev, "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(hdmidev, "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; |
| } |
| |
| static int cecsend(u8 src, u8 dest, 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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100 dl fw FAIL\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (data_len < 1) |
| 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 - 1); |
| |
| if (av8100_conf_prep(AV8100_COMMAND_CEC_MESSAGE_WRITE, |
| &config) != 0) { |
| dev_err(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_CEC_MESSAGE_WRITE, |
| NULL, NULL, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int infofrsend(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_INFOFRAMES, |
| NULL, NULL, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int hdcpchkaesotp(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != |
| 0) { |
| dev_err(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_FUSE_AES_KEY, |
| &buf_len, buf, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (buf_len == 2) { |
| *crc = buf[0]; |
| *progged = buf[1]; |
| } |
| |
| return 0; |
| } |
| |
| static int hdcpfuseaes(u8 *key, u8 crc, u8 *result) |
| { |
| union av8100_configuration config; |
| struct av8100_status status; |
| u8 buf_len; |
| u8 buf[2]; |
| |
| /* Default not OK */ |
| *result = 1; |
| |
| status = av8100_status_get(); |
| if (status.av8100_state < AV8100_OPMODE_STANDBY) { |
| if (av8100_powerup() != 0) { |
| dev_err(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != |
| 0) { |
| dev_err(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_FUSE_AES_KEY, |
| &buf_len, buf, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (buf_len == 2) { |
| if ((crc == buf[0]) && (buf[1] == 1)) |
| /* OK */ |
| *result = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int hdcploadaes(u8 block, u8 key_len, u8 *key, u8 *result) |
| { |
| union av8100_configuration config; |
| struct av8100_status status; |
| |
| /* Default not OK */ |
| *result = 1; |
| |
| status = av8100_status_get(); |
| if (status.av8100_state < AV8100_OPMODE_STANDBY) { |
| if (av8100_powerup() != 0) { |
| dev_err(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != |
| 0) { |
| dev_err(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_HDCP_SENDKEY, |
| NULL, NULL, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| *result = 0; |
| |
| return 0; |
| } |
| |
| static int hdcpauthencr(u8 auth_type, u8 encr_type) |
| { |
| 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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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_CONT: |
| config.hdcp_management_format.req_type = |
| AV8100_HDCP_AUTH_CONT; |
| break; |
| } |
| |
| switch (encr_type) { |
| case HDMI_HDCP_ENCR_OFF: |
| config.hdcp_management_format.req_encr = |
| AV8100_HDCP_ENCR_REQ_OFF; |
| config.hdcp_management_format.encr_use = |
| AV8100_HDCP_ENCR_USE_OESS; |
| break; |
| |
| case HDMI_HDCP_ENCR_OESS: |
| config.hdcp_management_format.req_encr = |
| AV8100_HDCP_ENCR_REQ_ON; |
| config.hdcp_management_format.encr_use = |
| AV8100_HDCP_ENCR_USE_OESS; |
| break; |
| |
| case HDMI_HDCP_ENCR_EESS: |
| config.hdcp_management_format.req_encr = |
| AV8100_HDCP_ENCR_REQ_ON; |
| config.hdcp_management_format.encr_use = |
| AV8100_HDCP_ENCR_USE_EESS; |
| break; |
| } |
| |
| if (av8100_conf_prep(AV8100_COMMAND_HDCP_MANAGEMENT, |
| &config) != 0) { |
| dev_err(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_HDCP_MANAGEMENT, |
| NULL, NULL, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100_conf_w FAIL\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static u8 events_read(void) |
| { |
| int ret; |
| |
| LOCK_HDMI_EVENTS; |
| ret = events; |
| dev_dbg(hdmidev, "%s %02x\n", __func__, events); |
| UNLOCK_HDMI_EVENTS; |
| |
| return ret; |
| } |
| |
| static int events_clear(u8 ev) |
| { |
| dev_dbg(hdmidev, "%s %02x\n", __func__, ev); |
| |
| LOCK_HDMI_EVENTS; |
| events &= ~ev & EVENTS_MASK; |
| UNLOCK_HDMI_EVENTS; |
| |
| return 0; |
| } |
| |
| static int audiocfg(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(hdmidev, "av8100_powerup failed\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (status.av8100_state < AV8100_OPMODE_INIT) { |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| |
| if (av8100_conf_w(AV8100_COMMAND_AUDIO_INPUT_FORMAT, |
| NULL, NULL, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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_driver_data *hdmi_driver_data; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| 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)) |
| hdmi_driver_data->store_as_hextext = true; |
| else if (((count == HDMI_STOREASTEXT_TEXT_SIZE) || |
| (count == HDMI_STOREASTEXT_TEXT_SIZE + 1)) && (*buf == '0') && |
| (*(buf + 1) == '1')) |
| hdmi_driver_data->store_as_hextext = true; |
| |
| dev_dbg(hdmidev, "store_as_hextext:%0d\n", |
| hdmi_driver_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_driver_data *hdmi_driver_data; |
| struct plug_detect plug_detect; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(&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_driver_data *hdmi_driver_data; |
| struct edid_read edid_read; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| dev_dbg(hdmidev, "count:%d\n", count); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(&edid_read, &hdmi_driver_data->edid_data.buf_len, |
| hdmi_driver_data->edid_data.buf)) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static ssize_t show_edidread(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| int len; |
| int index = 0; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| len = hdmi_driver_data->edid_data.buf_len; |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", len); |
| index += 2; |
| } else |
| *(buf + index++) = len; |
| |
| dev_dbg(hdmidev, "len:%02x\n", len); |
| |
| cnt = 0; |
| while (cnt < len) { |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", |
| hdmi_driver_data->edid_data.buf[cnt]); |
| index += 2; |
| } else |
| *(buf + index++) = |
| hdmi_driver_data->edid_data.buf[cnt]; |
| |
| dev_dbg(hdmidev, "%02x ", |
| hdmi_driver_data->edid_data.buf[cnt]); |
| |
| cnt++; |
| } |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t store_ceceven(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| bool enable = false; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(enable, HDMI_EVENT_CEC); |
| |
| return count; |
| } |
| |
| static ssize_t show_cecread(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct cec_rw cec_read; |
| int index = 0; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (cecread(&cec_read.src, &cec_read.dest, &cec_read.length, |
| cec_read.data)) |
| return -EINVAL; |
| |
| if (hdmi_driver_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); |
| } else { |
| *(buf + index++) = cec_read.src; |
| *(buf + index++) = cec_read.dest; |
| *(buf + index++) = cec_read.length; |
| } |
| |
| dev_dbg(hdmidev, "len:%02x\n", cec_read.length); |
| |
| cnt = 0; |
| while (cnt < cec_read.length) { |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", cec_read.data[cnt]); |
| index += 2; |
| } else |
| *(buf + index++) = cec_read.data[cnt]; |
| |
| dev_dbg(hdmidev, "%02x ", cec_read.data[cnt]); |
| |
| cnt++; |
| } |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t store_cecsend(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct cec_rw cec_w; |
| int index = 0; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| if ((count < HDMI_CECSEND_TEXT_SIZE_MIN) || |
| (count > HDMI_CECSEND_TEXT_SIZE_MAX)) |
| return -EINVAL; |
| |
| cec_w.src = htoi(buf + index); |
| 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(hdmidev, "%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(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_driver_data *hdmi_driver_data; |
| struct info_fr info_fr; |
| int index = 0; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(hdmidev, "%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(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_driver_data *hdmi_driver_data; |
| bool enable = false; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(enable, HDMI_EVENT_HDCP); |
| |
| return count; |
| } |
| |
| static ssize_t show_hdcpchkaesotp(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| u8 crc; |
| u8 status; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdcpchkaesotp(&crc, &status)) |
| return -EINVAL; |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", status); |
| index += 2; |
| } else { |
| *(buf + index++) = status; |
| } |
| |
| dev_dbg(hdmidev, "status:%02x\n", status); |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t store_hdcpfuseaes(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct hdcp_fuseaes hdcp_fuseaes; |
| int index = 0; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(hdmidev, "%02x ", hdcp_fuseaes.key[cnt]); |
| cnt++; |
| } |
| hdcp_fuseaes.crc = htoi(&buf[index]); |
| index += 2; |
| } 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(hdcp_fuseaes.key, hdcp_fuseaes.crc, |
| &hdcp_fuseaes.result)) |
| return -EINVAL; |
| |
| hdmi_driver_data->fuse_result = hdcp_fuseaes.result; |
| |
| return count; |
| } |
| |
| static ssize_t show_hdcpfuseaes(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", |
| hdmi_driver_data->fuse_result); |
| index += 2; |
| } else |
| *(buf + index++) = hdmi_driver_data->fuse_result; |
| |
| dev_dbg(hdmidev, "status:%02x\n", hdmi_driver_data->fuse_result); |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t store_hdcploadaes(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct hdcp_loadaesone hdcp_loadaes; |
| int index = 0; |
| int block_cnt; |
| int cnt; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| /* Default not OK */ |
| hdmi_driver_data->loadaes_result = 1; |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| if ((count != HDMI_HDCP_LOADAES_TEXT_SIZE) && |
| (count != HDMI_HDCP_LOADAES_TEXT_SIZE + 1)) |
| 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(hdmidev, "%02x ", |
| hdcp_loadaes.key[cnt]); |
| cnt++; |
| } |
| |
| if (hdcploadaes(block_cnt + HDMI_HDCP_AES_BLOCK_START, |
| HDMI_HDCP_AES_KEYSIZE, |
| hdcp_loadaes.key, |
| &hdcp_loadaes.result)) |
| return -EINVAL; |
| if (hdcp_loadaes.result) |
| return -EINVAL; |
| |
| 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(hdmidev, "%02x ", hdcp_loadaes.key[cnt]); |
| cnt++; |
| } |
| |
| if (hdcploadaes(HDMI_HDCP_KSV_BLOCK, |
| HDMI_HDCP_AES_KSVSIZE + |
| HDMI_HDCP_AES_KSVZEROESSIZE, |
| hdcp_loadaes.key, |
| &hdcp_loadaes.result)) |
| return -EINVAL; |
| |
| if (hdcp_loadaes.result) |
| return -EINVAL; |
| } else { |
| if (count != HDMI_HDCP_LOADAES_BIN_SIZE) |
| 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(block_cnt + HDMI_HDCP_AES_BLOCK_START, |
| HDMI_HDCP_AES_KEYSIZE, |
| hdcp_loadaes.key, |
| &hdcp_loadaes.result)) |
| return -EINVAL; |
| |
| if (hdcp_loadaes.result) |
| return -EINVAL; |
| } |
| |
| /* 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(HDMI_HDCP_KSV_BLOCK, |
| HDMI_HDCP_AES_KSVSIZE + |
| HDMI_HDCP_AES_KSVZEROESSIZE, |
| hdcp_loadaes.key, |
| &hdcp_loadaes.result)) |
| return -EINVAL; |
| |
| if (hdcp_loadaes.result) |
| return -EINVAL; |
| } |
| |
| hdmi_driver_data->loadaes_result = hdcp_loadaes.result; |
| |
| return count; |
| } |
| |
| static ssize_t show_hdcploadaes(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", |
| hdmi_driver_data->loadaes_result); |
| index += 2; |
| } else |
| *(buf + index++) = hdmi_driver_data->loadaes_result; |
| |
| dev_dbg(hdmidev, "status:%02x\n", hdmi_driver_data->loadaes_result); |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t store_hdcpauthencr(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct hdcp_authencr hdcp_authencr; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| if ((count != HDMI_HDCPAUTHENCR_TEXT_SIZE) && |
| (count != HDMI_HDCPAUTHENCR_TEXT_SIZE + 1)) |
| return -EINVAL; |
| |
| 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) |
| return -EINVAL; |
| |
| hdcp_authencr.auth_type = *(buf + index++); |
| hdcp_authencr.encr_type = *(buf + index++); |
| } |
| |
| if (hdcpauthencr(hdcp_authencr.auth_type, hdcp_authencr.encr_type)) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static ssize_t show_hdcpstateget(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| u8 hdcp_state; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (av8100_reg_gen_status_r(NULL, NULL, NULL, NULL, &hdcp_state)) |
| return -EINVAL; |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", hdcp_state); |
| index += 2; |
| } else |
| *(buf + index++) = hdcp_state; |
| |
| dev_dbg(hdmidev, "status:%02x\n", hdcp_state); |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| return index; |
| } |
| |
| static ssize_t show_evread(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| int index = 0; |
| u8 ev; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| ev = events_read(); |
| |
| if (hdmi_driver_data->store_as_hextext) { |
| snprintf(buf + index, 3, "%02x", ev & 0xFF); |
| index += 2; |
| } else |
| *(buf + index++) = ev & 0xFF; |
| |
| if (hdmi_driver_data->store_as_hextext) |
| *(buf + index++) = '\0'; |
| |
| /* Events are read: clear events */ |
| events_clear(EVENTS_MASK); |
| |
| return index; |
| } |
| |
| static ssize_t store_evclr(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| u8 ev; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(ev); |
| |
| return count; |
| } |
| |
| static ssize_t store_audiocfg(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct hdmi_driver_data *hdmi_driver_data; |
| struct audio_cfg audio_cfg; |
| int index = 0; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| hdmi_driver_data = dev_get_drvdata(dev); |
| |
| if (hdmi_driver_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(&audio_cfg); |
| |
| return count; |
| } |
| |
| static int hdmi_open(struct inode *inode, struct file *filp) |
| { |
| if (device_open) |
| return -EBUSY; |
| |
| device_open++; |
| |
| return 0; |
| } |
| |
| static int hdmi_release(struct inode *inode, struct file *filp) |
| { |
| if (device_open) |
| device_open--; |
| |
| return 0; |
| } |
| |
| /* ioctl */ |
| static int hdmi_ioctl(struct inode *inode, struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| u8 value = 0; |
| struct plug_detect plug_detect; |
| struct edid_read edid_read; |
| struct cec_rw cec_read; |
| struct cec_rw cec_send; |
| struct info_fr info_fr; |
| struct hdcp_fuseaes hdcp_fuseaes; |
| struct hdcp_loadaesall hdcp_loadaesall; |
| int block_cnt; |
| struct hdcp_loadaesone hdcp_loadaesone; |
| struct hdcp_authencr hdcp_authencr; |
| struct audio_cfg audio_cfg; |
| union av8100_configuration config; |
| struct hdmi_register reg; |
| struct hdmi_command_register command_reg; |
| struct av8100_status status; |
| u8 aes_status; |
| |
| switch (cmd) { |
| |
| case IOC_PLUG_DETECT_ENABLE: |
| if (copy_from_user(&plug_detect, (void *)arg, |
| sizeof(struct plug_detect))) |
| return -EINVAL; |
| |
| if (plugdeten(&plug_detect)) |
| return -EINVAL; |
| break; |
| |
| case IOC_EDID_READ: |
| if (copy_from_user(&edid_read, (void *)arg, |
| sizeof(struct edid_read))) |
| return -EINVAL; |
| |
| if (edidread(&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(value != 0, HDMI_EVENT_CEC); |
| break; |
| |
| case IOC_CEC_READ: |
| if (cecread(&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: |
| if (copy_from_user(&cec_send, (void *)arg, |
| sizeof(struct cec_rw))) |
| return -EINVAL; |
| |
| if (cecsend(cec_send.src, |
| cec_send.dest, |
| cec_send.length, |
| cec_send.data)) |
| return -EINVAL; |
| break; |
| |
| case IOC_INFOFRAME_SEND: |
| if (copy_from_user(&info_fr, (void *)arg, |
| sizeof(struct info_fr))) |
| return -EINVAL; |
| |
| if (infofrsend(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(value != 0, HDMI_EVENT_HDCP); |
| break; |
| |
| case IOC_HDCP_CHKAESOTP: |
| if (hdcpchkaesotp(&value, &aes_status)) |
| return -EINVAL; |
| |
| if (copy_to_user((void *)arg, (void *)&aes_status, |
| sizeof(u8))) { |
| return -EINVAL; |
| } |
| break; |
| |
| case IOC_HDCP_FUSEAES: |
| if (copy_from_user(&hdcp_fuseaes, (void *)arg, |
| sizeof(struct hdcp_fuseaes))) |
| return -EINVAL; |
| |
| if (hdcpfuseaes(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: |
| if (copy_from_user(&hdcp_loadaesall, (void *)arg, |
| sizeof(struct hdcp_loadaesall))) |
| return -EINVAL; |
| |
| /* 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(block_cnt + HDMI_HDCP_AES_BLOCK_START, |
| HDMI_HDCP_AES_KEYSIZE, |
| hdcp_loadaesone.key, |
| &hdcp_loadaesone.result)) |
| return -EINVAL; |
| |
| if (hdcp_loadaesone.result) |
| return -EINVAL; |
| } |
| |
| /* 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(HDMI_HDCP_KSV_BLOCK, |
| HDMI_HDCP_AES_KSVSIZE + |
| HDMI_HDCP_AES_KSVZEROESSIZE, |
| hdcp_loadaesone.key, |
| &hdcp_loadaesone.result)) |
| return -EINVAL; |
| |
| if (hdcp_loadaesone.result) |
| return -EINVAL; |
| |
| 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: |
| if (copy_from_user(&hdcp_authencr, (void *)arg, |
| sizeof(struct hdcp_authencr))) |
| return -EINVAL; |
| |
| if (hdcpauthencr(hdcp_authencr.auth_type, |
| hdcp_authencr.encr_type)) |
| return -EINVAL; |
| break; |
| |
| case IOC_HDCP_STATE_GET: |
| if (av8100_reg_gen_status_r(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(); |
| |
| if (copy_to_user((void *)arg, (void *)&value, |
| sizeof(u8))) { |
| return -EINVAL; |
| } |
| |
| /* Events are read: clear events */ |
| events_clear(EVENTS_MASK); |
| break; |
| |
| case IOC_EVENTS_CLEAR: |
| if (copy_from_user(&value, (void *)arg, sizeof(u8))) |
| return -EINVAL; |
| |
| events_clear(value); |
| break; |
| |
| case IOC_AUDIO_CFG: |
| if (copy_from_user(&audio_cfg, (void *)arg, |
| sizeof(struct audio_cfg))) |
| return -EINVAL; |
| |
| audiocfg(&audio_cfg); |
| break; |
| |
| /* Internal */ |
| case IOC_HDMI_POWER: |
| /* 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(hdmidev, "av8100_powerdown FAIL\n"); |
| return -EINVAL; |
| } |
| } else { |
| if (av8100_powerup() != 0) { |
| dev_err(hdmidev, "av8100_powerup FAIL\n"); |
| return -EINVAL; |
| } |
| } |
| break; |
| |
| case IOC_HDMI_ENABLE_INTERRUPTS: |
| av8100_disable_interrupt(); |
| if (av8100_enable_interrupt() != 0) { |
| dev_err(hdmidev, "av8100_conf_get FAIL\n"); |
| return -EINVAL; |
| } |
| break; |
| |
| case IOC_HDMI_DOWNLOAD_FW: |
| if (av8100_download_firmware(NULL, 0, I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "av8100 dl fw FAIL\n"); |
| return -EINVAL; |
| } |
| break; |
| |
| case IOC_HDMI_ONOFF: |
| /* 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(hdmidev, "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(hdmidev, "av8100_conf_prep FAIL\n"); |
| return -EINVAL; |
| } |
| if (av8100_conf_w(AV8100_COMMAND_HDMI, NULL, NULL, |
| I2C_INTERFACE) != 0) { |
| dev_err(hdmidev, "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(hdmidev, "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(hdmidev, "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: |
| if (copy_from_user(&command_reg, (void *)arg, |
| sizeof(struct hdmi_command_register)) != 0) { |
| dev_err(hdmidev, "IOC_HDMI_CONFIGURATION_WRITE " |
| "fail 1\n"); |
| command_reg.return_status = EINVAL; |
| } else { |
| 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(hdmidev, "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; |
| |
| dev_dbg(hdmidev, "%s\n", __func__); |
| |
| poll_wait(filp, &hdmi_event_wq , wait); |
| |
| LOCK_HDMI_EVENTS; |
| if (events_received == true) { |
| 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, |
| .ioctl = hdmi_ioctl, |
| .poll = hdmi_poll |
| }; |
| |
| static struct miscdevice hdmi_miscdev = { |
| MISC_DYNAMIC_MINOR, |
| "hdmi", |
| &hdmi_fops |
| }; |
| |
| /* Event callback function called by hw driver */ |
| void hdmi_event(enum av8100_hdmi_event ev) |
| { |
| int events_old; |
| int events_new; |
| struct kobject *kobj = &hdmidev->kobj; |
| |
| dev_dbg(hdmidev, "hdmi_event %02x\n", ev); |
| |
| LOCK_HDMI_EVENTS; |
| |
| events_old = events; |
| |
| /* Set event */ |
| switch (ev) { |
| case AV8100_HDMI_EVENT_HDMI_PLUGIN: |
| events |= events_mask & HDMI_EVENT_HDMI_PLUGIN; |
| break; |
| |
| case AV8100_HDMI_EVENT_HDMI_PLUGOUT: |
| events |= events_mask & HDMI_EVENT_HDMI_PLUGOUT; |
| break; |
| |
| case AV8100_HDMI_EVENT_CEC: |
| events |= events_mask & HDMI_EVENT_CEC; |
| break; |
| |
| case AV8100_HDMI_EVENT_HDCP: |
| events |= events_mask & HDMI_EVENT_HDCP; |
| break; |
| |
| default: |
| break; |
| } |
| |
| events_new = events; |
| |
| UNLOCK_HDMI_EVENTS; |
| |
| dev_dbg(hdmidev, "hdmi events:%02x, events_old:%02x mask:%02x\n", |
| events_new, events_old, 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; |
| events_received = true; |
| UNLOCK_HDMI_EVENTS; |
| |
| wake_up_interruptible(&hdmi_event_wq); |
| } |
| } |
| |
| int __init hdmi_init(void) |
| { |
| int ret; |
| struct hdmi_driver_data *hdmi_driver_data; |
| |
| ret = misc_register(&hdmi_miscdev); |
| if (ret) |
| goto hdmi_init_out; |
| |
| hdmidev = hdmi_miscdev.this_device; |
| |
| hdmidev->driver_data = |
| kzalloc(sizeof(struct hdmi_driver_data), GFP_KERNEL); |
| |
| if (!hdmidev->driver_data) |
| return -ENOMEM; |
| |
| hdmi_driver_data = dev_get_drvdata(hdmidev); |
| |
| /* Default sysfs file format is hextext */ |
| hdmi_driver_data->store_as_hextext = true; |
| |
| init_waitqueue_head(&hdmi_event_wq); |
| |
| if (device_create_file(hdmidev, &dev_attr_storeastext)) |
| dev_info(hdmidev, "Unable to create storeastext attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_plugdeten)) |
| dev_info(hdmidev, "Unable to create plugdeten attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_edidread)) |
| dev_info(hdmidev, "Unable to create edidread attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_ceceven)) |
| dev_info(hdmidev, "Unable to create ceceven attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_cecread)) |
| dev_info(hdmidev, "Unable to create cecread attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_cecsend)) |
| dev_info(hdmidev, "Unable to create cecsend attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_infofrsend)) |
| dev_info(hdmidev, "Unable to create infofrsend attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcpeven)) |
| dev_info(hdmidev, "Unable to create hdcpeven attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcpchkaesotp)) |
| dev_info(hdmidev, "Unable to create hdcpchkaesotp attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcpfuseaes)) |
| dev_info(hdmidev, "Unable to create hdcpfuseaes attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcploadaes)) |
| dev_info(hdmidev, "Unable to create hdcploadaes attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcpauthencr)) |
| dev_info(hdmidev, "Unable to create hdcpauthreq attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_hdcpstateget)) |
| dev_info(hdmidev, "Unable to create hdcpstateget attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_evread)) |
| dev_info(hdmidev, "Unable to create evread attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_evclr)) |
| dev_info(hdmidev, "Unable to create evclr attribute\n"); |
| if (device_create_file(hdmidev, &dev_attr_audiocfg)) |
| dev_info(hdmidev, "Unable to create audiocfg attribute\n"); |
| |
| /* Register event callback */ |
| av8100_hdmi_event_cb_set(hdmi_event); |
| |
| hdmi_init_out: |
| return ret; |
| } |
| |
| void hdmi_exit(void) |
| { |
| /* Deregister event callback */ |
| av8100_hdmi_event_cb_set(NULL); |
| |
| device_remove_file(hdmidev, &dev_attr_storeastext); |
| device_remove_file(hdmidev, &dev_attr_plugdeten); |
| device_remove_file(hdmidev, &dev_attr_edidread); |
| device_remove_file(hdmidev, &dev_attr_ceceven); |
| device_remove_file(hdmidev, &dev_attr_cecread); |
| device_remove_file(hdmidev, &dev_attr_cecsend); |
| device_remove_file(hdmidev, &dev_attr_infofrsend); |
| device_remove_file(hdmidev, &dev_attr_hdcpeven); |
| device_remove_file(hdmidev, &dev_attr_hdcpchkaesotp); |
| device_remove_file(hdmidev, &dev_attr_hdcpfuseaes); |
| device_remove_file(hdmidev, &dev_attr_hdcploadaes); |
| device_remove_file(hdmidev, &dev_attr_hdcpauthencr); |
| device_remove_file(hdmidev, &dev_attr_hdcpstateget); |
| device_remove_file(hdmidev, &dev_attr_evread); |
| device_remove_file(hdmidev, &dev_attr_evclr); |
| device_remove_file(hdmidev, &dev_attr_audiocfg); |
| |
| kfree(hdmidev->driver_data); |
| |
| misc_deregister(&hdmi_miscdev); |
| } |