| /* Copyright (c) 2010-2013, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/iopoll.h> |
| #include <linux/of_address.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_platform.h> |
| #include <linux/types.h> |
| #include <mach/msm_hdmi_audio_codec.h> |
| |
| #define REG_DUMP 0 |
| |
| #include "mdss_debug.h" |
| #include "mdss_fb.h" |
| #include "mdss_hdmi_tx.h" |
| #include "mdss_hdmi_edid.h" |
| #include "mdss_hdmi_hdcp.h" |
| #include "mdss.h" |
| #include "mdss_panel.h" |
| #include "mdss_hdmi_mhl.h" |
| |
| #define DRV_NAME "hdmi-tx" |
| #define COMPATIBLE_NAME "qcom,hdmi-tx" |
| |
| #define DEFAULT_VIDEO_RESOLUTION HDMI_VFRMT_640x480p60_4_3 |
| |
| /* HDMI PHY/PLL bit field macros */ |
| #define SW_RESET BIT(2) |
| #define SW_RESET_PLL BIT(0) |
| |
| #define HPD_DISCONNECT_POLARITY 0 |
| #define HPD_CONNECT_POLARITY 1 |
| |
| #define IFRAME_CHECKSUM_32(d) \ |
| ((d & 0xff) + ((d >> 8) & 0xff) + \ |
| ((d >> 16) & 0xff) + ((d >> 24) & 0xff)) |
| |
| /* Enable HDCP by default */ |
| static bool hdcp_feature_on = true; |
| |
| /* Supported HDMI Audio channels */ |
| #define MSM_HDMI_AUDIO_CHANNEL_2 2 |
| #define MSM_HDMI_AUDIO_CHANNEL_4 4 |
| #define MSM_HDMI_AUDIO_CHANNEL_6 6 |
| #define MSM_HDMI_AUDIO_CHANNEL_8 8 |
| |
| enum msm_hdmi_supported_audio_sample_rates { |
| AUDIO_SAMPLE_RATE_32KHZ, |
| AUDIO_SAMPLE_RATE_44_1KHZ, |
| AUDIO_SAMPLE_RATE_48KHZ, |
| AUDIO_SAMPLE_RATE_88_2KHZ, |
| AUDIO_SAMPLE_RATE_96KHZ, |
| AUDIO_SAMPLE_RATE_176_4KHZ, |
| AUDIO_SAMPLE_RATE_192KHZ, |
| AUDIO_SAMPLE_RATE_MAX |
| }; |
| |
| /* parameters for clock regeneration */ |
| struct hdmi_tx_audio_acr { |
| u32 n; |
| u32 cts; |
| }; |
| |
| struct hdmi_tx_audio_acr_arry { |
| u32 pclk; |
| struct hdmi_tx_audio_acr lut[AUDIO_SAMPLE_RATE_MAX]; |
| }; |
| |
| static int hdmi_tx_sysfs_enable_hpd(struct hdmi_tx_ctrl *hdmi_ctrl, int on); |
| static irqreturn_t hdmi_tx_isr(int irq, void *data); |
| static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl); |
| |
| struct mdss_hw hdmi_tx_hw = { |
| .hw_ndx = MDSS_HW_HDMI, |
| .ptr = NULL, |
| .irq_handler = hdmi_tx_isr, |
| }; |
| |
| struct dss_gpio hpd_gpio_config[] = { |
| {0, 1, COMPATIBLE_NAME "-hpd"}, |
| {0, 1, COMPATIBLE_NAME "-ddc-clk"}, |
| {0, 1, COMPATIBLE_NAME "-ddc-data"}, |
| {0, 1, COMPATIBLE_NAME "-mux-en"}, |
| {0, 0, COMPATIBLE_NAME "-mux-sel"} |
| }; |
| |
| struct dss_gpio core_gpio_config[] = { |
| }; |
| |
| struct dss_gpio cec_gpio_config[] = { |
| {0, 1, COMPATIBLE_NAME "-cec"} |
| }; |
| |
| const char *hdmi_pm_name(enum hdmi_tx_power_module_type module) |
| { |
| switch (module) { |
| case HDMI_TX_HPD_PM: return "HDMI_TX_HPD_PM"; |
| case HDMI_TX_CORE_PM: return "HDMI_TX_CORE_PM"; |
| case HDMI_TX_CEC_PM: return "HDMI_TX_CEC_PM"; |
| default: return "???"; |
| } |
| } /* hdmi_pm_name */ |
| |
| static u8 hdmi_tx_avi_iframe_lut[][20] = { |
| {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, |
| 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, |
| 0x10, 0x10}, /*00*/ |
| {0x18, 0x18, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, |
| 0x28, 0x28, 0x28, 0x28, 0x18, 0x28, 0x18, 0x28, 0x28, |
| 0x28, 0x28}, /*01*/ |
| {0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, |
| 0x04, 0x04, 0x04, 0x04, 0x88, 0x00, 0x04, 0x04, 0x04, |
| 0x04, 0x04}, /*02*/ |
| {0x02, 0x06, 0x11, 0x15, 0x04, 0x13, 0x10, 0x05, 0x1F, |
| 0x14, 0x20, 0x22, 0x21, 0x01, 0x03, 0x11, 0x00, 0x00, |
| 0x00, 0x00}, /*03*/ |
| {0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00}, /*04*/ |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00}, /*05*/ |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00}, /*06*/ |
| {0xE1, 0xE1, 0x41, 0x41, 0xD1, 0xd1, 0x39, 0x39, 0x39, |
| 0x39, 0x39, 0x39, 0x39, 0xe1, 0xE1, 0x41, 0x71, 0x71, |
| 0x71, 0x71}, /*07*/ |
| {0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, |
| 0x04, 0x04, 0x04, 0x04, 0x01, 0x01, 0x02, 0x08, 0x08, |
| 0x08, 0x08}, /*08*/ |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00}, /*09*/ |
| {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00}, /*10*/ |
| {0xD1, 0xD1, 0xD1, 0xD1, 0x01, 0x01, 0x81, 0x81, 0x81, |
| 0x81, 0x81, 0x81, 0x81, 0x81, 0xD1, 0xD1, 0x01, 0x01, |
| 0x01, 0x01}, /*11*/ |
| {0x02, 0x02, 0x02, 0x02, 0x05, 0x05, 0x07, 0x07, 0x07, |
| 0x07, 0x07, 0x07, 0x07, 0x02, 0x02, 0x02, 0x0F, 0x0F, |
| 0x0F, 0x10} /*12*/ |
| }; |
| |
| /* Audio constants lookup table for hdmi_tx_audio_acr_setup */ |
| /* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */ |
| static const struct hdmi_tx_audio_acr_arry hdmi_tx_audio_acr_lut[] = { |
| /* 25.200MHz */ |
| {25200, {{4096, 25200}, {6272, 28000}, {6144, 25200}, {12544, 28000}, |
| {12288, 25200}, {25088, 28000}, {24576, 25200} } }, |
| /* 27.000MHz */ |
| {27000, {{4096, 27000}, {6272, 30000}, {6144, 27000}, {12544, 30000}, |
| {12288, 27000}, {25088, 30000}, {24576, 27000} } }, |
| /* 27.027MHz */ |
| {27030, {{4096, 27027}, {6272, 30030}, {6144, 27027}, {12544, 30030}, |
| {12288, 27027}, {25088, 30030}, {24576, 27027} } }, |
| /* 74.250MHz */ |
| {74250, {{4096, 74250}, {6272, 82500}, {6144, 74250}, {12544, 82500}, |
| {12288, 74250}, {25088, 82500}, {24576, 74250} } }, |
| /* 148.500MHz */ |
| {148500, {{4096, 148500}, {6272, 165000}, {6144, 148500}, |
| {12544, 165000}, {12288, 148500}, {25088, 165000}, |
| {24576, 148500} } }, |
| /* 297.000MHz */ |
| {297000, {{3072, 222750}, {4704, 247500}, {5120, 247500}, |
| {9408, 247500}, {10240, 247500}, {18816, 247500}, |
| {20480, 247500} } }, |
| }; |
| |
| const char *hdmi_tx_pm_name(enum hdmi_tx_power_module_type module) |
| { |
| switch (module) { |
| case HDMI_TX_HPD_PM: return "HDMI_TX_HPD_PM"; |
| case HDMI_TX_CORE_PM: return "HDMI_TX_CORE_PM"; |
| case HDMI_TX_CEC_PM: return "HDMI_TX_CEC_PM"; |
| default: return "???"; |
| } |
| } /* hdmi_tx_pm_name */ |
| |
| static const char *hdmi_tx_io_name(u32 type) |
| { |
| switch (type) { |
| case HDMI_TX_CORE_IO: return "core_physical"; |
| case HDMI_TX_PHY_IO: return "phy_physical"; |
| case HDMI_TX_QFPROM_IO: return "qfprom_physical"; |
| default: return NULL; |
| } |
| } /* hdmi_tx_io_name */ |
| |
| static int hdmi_tx_get_vic_from_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl, |
| struct mdss_panel_info *pinfo) |
| { |
| int new_vic = -1; |
| u32 h_total, v_total; |
| struct msm_hdmi_mode_timing_info timing; |
| |
| if (!hdmi_ctrl || !pinfo) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (pinfo->vic) { |
| if (hdmi_get_supported_mode(pinfo->vic)) { |
| new_vic = pinfo->vic; |
| DEV_DBG("%s: %s is supported\n", __func__, |
| msm_hdmi_mode_2string(new_vic)); |
| } else { |
| DEV_ERR("%s: invalid or not supported vic %d\n", |
| __func__, pinfo->vic); |
| return -EPERM; |
| } |
| } else { |
| timing.active_h = pinfo->xres; |
| timing.back_porch_h = pinfo->lcdc.h_back_porch; |
| timing.front_porch_h = pinfo->lcdc.h_front_porch; |
| timing.pulse_width_h = pinfo->lcdc.h_pulse_width; |
| h_total = timing.active_h + timing.back_porch_h + |
| timing.front_porch_h + timing.pulse_width_h; |
| DEV_DBG("%s: ah=%d bph=%d fph=%d pwh=%d ht=%d\n", __func__, |
| timing.active_h, timing.back_porch_h, |
| timing.front_porch_h, timing.pulse_width_h, h_total); |
| |
| timing.active_v = pinfo->yres; |
| timing.back_porch_v = pinfo->lcdc.v_back_porch; |
| timing.front_porch_v = pinfo->lcdc.v_front_porch; |
| timing.pulse_width_v = pinfo->lcdc.v_pulse_width; |
| v_total = timing.active_v + timing.back_porch_v + |
| timing.front_porch_v + timing.pulse_width_v; |
| DEV_DBG("%s: av=%d bpv=%d fpv=%d pwv=%d vt=%d\n", __func__, |
| timing.active_v, timing.back_porch_v, |
| timing.front_porch_v, timing.pulse_width_v, v_total); |
| |
| timing.pixel_freq = pinfo->clk_rate / 1000; |
| if (h_total && v_total) { |
| timing.refresh_rate = ((timing.pixel_freq * 1000) / |
| (h_total * v_total)) * 1000; |
| } else { |
| DEV_ERR("%s: cannot cal refresh rate\n", __func__); |
| return -EPERM; |
| } |
| DEV_DBG("%s: pixel_freq=%d refresh_rate=%d\n", __func__, |
| timing.pixel_freq, timing.refresh_rate); |
| |
| new_vic = hdmi_get_video_id_code(&timing); |
| } |
| |
| return new_vic; |
| } /* hdmi_tx_get_vic_from_panel_info */ |
| |
| static struct hdmi_tx_ctrl *hdmi_tx_get_drvdata_from_panel_data( |
| struct mdss_panel_data *mpd) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| if (mpd) { |
| hdmi_ctrl = container_of(mpd, struct hdmi_tx_ctrl, panel_data); |
| if (!hdmi_ctrl) |
| DEV_ERR("%s: hdmi_ctrl = NULL\n", __func__); |
| } else { |
| DEV_ERR("%s: mdss_panel_data = NULL\n", __func__); |
| } |
| return hdmi_ctrl; |
| } /* hdmi_tx_get_drvdata_from_panel_data */ |
| |
| static struct hdmi_tx_ctrl *hdmi_tx_get_drvdata_from_sysfs_dev( |
| struct device *device) |
| { |
| struct msm_fb_data_type *mfd = NULL; |
| struct mdss_panel_data *panel_data = NULL; |
| struct fb_info *fbi = dev_get_drvdata(device); |
| |
| if (fbi) { |
| mfd = (struct msm_fb_data_type *)fbi->par; |
| panel_data = dev_get_platdata(&mfd->pdev->dev); |
| |
| return hdmi_tx_get_drvdata_from_panel_data(panel_data); |
| } else { |
| DEV_ERR("%s: fbi = NULL\n", __func__); |
| return NULL; |
| } |
| } /* hdmi_tx_get_drvdata_from_sysfs_dev */ |
| |
| /* todo: Fix this. Right now this is declared in hdmi_util.h */ |
| void *hdmi_get_featuredata_from_sysfs_dev(struct device *device, |
| u32 feature_type) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| if (!device || feature_type > HDMI_TX_FEAT_MAX) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return NULL; |
| } |
| |
| hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(device); |
| if (hdmi_ctrl) |
| return hdmi_ctrl->feature_data[feature_type]; |
| else |
| return NULL; |
| |
| } /* hdmi_tx_get_featuredata_from_sysfs_dev */ |
| EXPORT_SYMBOL(hdmi_get_featuredata_from_sysfs_dev); |
| |
| static ssize_t hdmi_tx_sysfs_rda_connected(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t ret; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&hdmi_ctrl->mutex); |
| ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->hpd_state); |
| DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_state); |
| mutex_unlock(&hdmi_ctrl->mutex); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_rda_connected */ |
| |
| static ssize_t hdmi_tx_sysfs_rda_hpd(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t ret; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->hpd_feature_on); |
| DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_feature_on); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_rda_hpd */ |
| |
| static ssize_t hdmi_tx_sysfs_wta_hpd(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int hpd, rc = 0; |
| ssize_t ret = strnlen(buf, PAGE_SIZE); |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| DEV_DBG("%s:\n", __func__); |
| hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| rc = kstrtoint(buf, 10, &hpd); |
| if (rc) { |
| DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc); |
| return rc; |
| } |
| |
| if (0 == hpd && hdmi_ctrl->hpd_feature_on) { |
| rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, false); |
| } else if (1 == hpd && !hdmi_ctrl->hpd_feature_on) { |
| rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true); |
| } else { |
| DEV_DBG("%s: hpd is already '%s'. return\n", __func__, |
| hdmi_ctrl->hpd_feature_on ? "enabled" : "disabled"); |
| return ret; |
| } |
| |
| if (!rc) { |
| hdmi_ctrl->hpd_feature_on = |
| (~hdmi_ctrl->hpd_feature_on) & BIT(0); |
| DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_feature_on); |
| } else { |
| DEV_ERR("%s: failed to '%s' hpd. rc = %d\n", __func__, |
| hpd ? "enable" : "disable", rc); |
| ret = rc; |
| } |
| |
| return ret; |
| } /* hdmi_tx_sysfs_wta_hpd */ |
| |
| static ssize_t hdmi_tx_sysfs_wta_vendor_name(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| ssize_t ret; |
| u8 *s = (u8 *) buf; |
| u8 *d = NULL; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| d = hdmi_ctrl->spd_vendor_name; |
| ret = strnlen(buf, PAGE_SIZE); |
| ret = (ret > 8) ? 8 : ret; |
| |
| memset(hdmi_ctrl->spd_vendor_name, 0, 8); |
| while (*s) { |
| if (*s & 0x60 && *s ^ 0x7f) { |
| *d = *s; |
| } else { |
| /* stop copying if control character found */ |
| break; |
| } |
| |
| if (++s > (u8 *) (buf + ret)) |
| break; |
| |
| d++; |
| } |
| |
| DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_vendor_name); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_wta_vendor_name */ |
| |
| static ssize_t hdmi_tx_sysfs_rda_vendor_name(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t ret; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = snprintf(buf, PAGE_SIZE, "%s\n", hdmi_ctrl->spd_vendor_name); |
| DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_vendor_name); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_rda_vendor_name */ |
| |
| static ssize_t hdmi_tx_sysfs_wta_product_description(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| ssize_t ret; |
| u8 *s = (u8 *) buf; |
| u8 *d = NULL; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| d = hdmi_ctrl->spd_product_description; |
| ret = strnlen(buf, PAGE_SIZE); |
| ret = (ret > 16) ? 16 : ret; |
| |
| memset(hdmi_ctrl->spd_product_description, 0, 16); |
| while (*s) { |
| if (*s & 0x60 && *s ^ 0x7f) { |
| *d = *s; |
| } else { |
| /* stop copying if control character found */ |
| break; |
| } |
| |
| if (++s > (u8 *) (buf + ret)) |
| break; |
| |
| d++; |
| } |
| |
| DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_product_description); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_wta_product_description */ |
| |
| static ssize_t hdmi_tx_sysfs_rda_product_description(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t ret; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_sysfs_dev(dev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = snprintf(buf, PAGE_SIZE, "%s\n", |
| hdmi_ctrl->spd_product_description); |
| DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_product_description); |
| |
| return ret; |
| } /* hdmi_tx_sysfs_rda_product_description */ |
| |
| static DEVICE_ATTR(connected, S_IRUGO, hdmi_tx_sysfs_rda_connected, NULL); |
| static DEVICE_ATTR(hpd, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_hpd, |
| hdmi_tx_sysfs_wta_hpd); |
| static DEVICE_ATTR(vendor_name, S_IRUGO | S_IWUSR, |
| hdmi_tx_sysfs_rda_vendor_name, hdmi_tx_sysfs_wta_vendor_name); |
| static DEVICE_ATTR(product_description, S_IRUGO | S_IWUSR, |
| hdmi_tx_sysfs_rda_product_description, |
| hdmi_tx_sysfs_wta_product_description); |
| |
| static struct attribute *hdmi_tx_fs_attrs[] = { |
| &dev_attr_connected.attr, |
| &dev_attr_hpd.attr, |
| &dev_attr_vendor_name.attr, |
| &dev_attr_product_description.attr, |
| NULL, |
| }; |
| static struct attribute_group hdmi_tx_fs_attrs_group = { |
| .attrs = hdmi_tx_fs_attrs, |
| }; |
| |
| static int hdmi_tx_sysfs_create(struct hdmi_tx_ctrl *hdmi_ctrl, |
| struct fb_info *fbi) |
| { |
| int rc; |
| |
| if (!hdmi_ctrl || !fbi) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| |
| rc = sysfs_create_group(&fbi->dev->kobj, |
| &hdmi_tx_fs_attrs_group); |
| if (rc) { |
| DEV_ERR("%s: failed, rc=%d\n", __func__, rc); |
| return rc; |
| } |
| hdmi_ctrl->kobj = &fbi->dev->kobj; |
| DEV_DBG("%s: sysfs group %p\n", __func__, hdmi_ctrl->kobj); |
| |
| return 0; |
| } /* hdmi_tx_sysfs_create */ |
| |
| static void hdmi_tx_sysfs_remove(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| if (hdmi_ctrl->kobj) |
| sysfs_remove_group(hdmi_ctrl->kobj, &hdmi_tx_fs_attrs_group); |
| hdmi_ctrl->kobj = NULL; |
| } /* hdmi_tx_sysfs_remove */ |
| |
| static inline u32 hdmi_tx_is_dvi_mode(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| return hdmi_edid_get_sink_mode( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) ? 0 : 1; |
| } /* hdmi_tx_is_dvi_mode */ |
| |
| static inline void hdmi_tx_send_cable_notification( |
| struct hdmi_tx_ctrl *hdmi_ctrl, int val) |
| { |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (!hdmi_ctrl->pdata.primary && (hdmi_ctrl->sdev.state != val)) |
| switch_set_state(&hdmi_ctrl->sdev, val); |
| } /* hdmi_tx_send_cable_notification */ |
| |
| static inline void hdmi_tx_set_audio_switch_node(struct hdmi_tx_ctrl *hdmi_ctrl, |
| int val, bool force) |
| { |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (!hdmi_tx_is_dvi_mode(hdmi_ctrl) && |
| (force || (hdmi_ctrl->audio_sdev.state != val))) { |
| switch_set_state(&hdmi_ctrl->audio_sdev, val); |
| DEV_INFO("%s: hdmi_audio state switched to %d\n", __func__, |
| hdmi_ctrl->audio_sdev.state); |
| } |
| } /* hdmi_tx_set_audio_switch_node */ |
| |
| void hdmi_tx_hdcp_cb(void *ptr, enum hdmi_hdcp_state status) |
| { |
| int rc = 0; |
| struct hdmi_tx_ctrl *hdmi_ctrl = (struct hdmi_tx_ctrl *)ptr; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| DEV_DBG("%s: HDCP status=%s hpd_state=%d\n", __func__, |
| hdcp_state_name(status), hdmi_ctrl->hpd_state); |
| |
| switch (status) { |
| case HDCP_STATE_AUTHENTICATED: |
| if (hdmi_ctrl->hpd_state) |
| hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1, false); |
| break; |
| case HDCP_STATE_AUTH_FAIL: |
| hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0, false); |
| |
| if (hdmi_ctrl->hpd_state) { |
| DEV_DBG("%s: Reauthenticating\n", __func__); |
| rc = hdmi_hdcp_reauthenticate( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]); |
| if (rc) |
| DEV_ERR("%s: HDCP reauth failed. rc=%d\n", |
| __func__, rc); |
| } else { |
| DEV_DBG("%s: Not reauthenticating. Cable not conn\n", |
| __func__); |
| } |
| |
| break; |
| case HDCP_STATE_AUTHENTICATING: |
| case HDCP_STATE_INACTIVE: |
| default: |
| break; |
| /* do nothing */ |
| } |
| } |
| |
| /* Enable HDMI features */ |
| static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| struct hdmi_edid_init_data edid_init_data; |
| struct hdmi_hdcp_init_data hdcp_init_data; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| edid_init_data.io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| edid_init_data.mutex = &hdmi_ctrl->mutex; |
| edid_init_data.sysfs_kobj = hdmi_ctrl->kobj; |
| edid_init_data.ddc_ctrl = &hdmi_ctrl->ddc_ctrl; |
| |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = |
| hdmi_edid_init(&edid_init_data); |
| if (!hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) { |
| DEV_ERR("%s: hdmi_edid_init failed\n", __func__); |
| return -EPERM; |
| } |
| hdmi_edid_set_video_resolution( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], |
| hdmi_ctrl->video_resolution); |
| |
| /* Initialize HDCP feature */ |
| if (hdmi_ctrl->present_hdcp) { |
| hdcp_init_data.core_io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| hdcp_init_data.qfprom_io = |
| &hdmi_ctrl->pdata.io[HDMI_TX_QFPROM_IO]; |
| hdcp_init_data.mutex = &hdmi_ctrl->mutex; |
| hdcp_init_data.ddc_ctrl = &hdmi_ctrl->ddc_ctrl; |
| hdcp_init_data.workq = hdmi_ctrl->workq; |
| hdcp_init_data.notify_status = hdmi_tx_hdcp_cb; |
| hdcp_init_data.cb_data = (void *)hdmi_ctrl; |
| |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP] = |
| hdmi_hdcp_init(&hdcp_init_data); |
| if (!hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) { |
| DEV_ERR("%s: hdmi_hdcp_init failed\n", __func__); |
| hdmi_edid_deinit(hdmi_ctrl->feature_data[ |
| HDMI_TX_FEAT_EDID]); |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = NULL; |
| return -EPERM; |
| } |
| |
| DEV_DBG("%s: HDCP feature initialized\n", __func__); |
| } |
| return 0; |
| } /* hdmi_tx_init_features */ |
| |
| static inline u32 hdmi_tx_is_controller_on(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| struct dss_io_data *io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| return DSS_REG_R_ND(io, HDMI_CTRL) & BIT(0); |
| } /* hdmi_tx_is_controller_on */ |
| |
| static int hdmi_tx_init_panel_info(uint32_t resolution, |
| struct mdss_panel_info *pinfo) |
| { |
| const struct msm_hdmi_mode_timing_info *timing = |
| hdmi_get_supported_mode(resolution); |
| |
| if (!timing || !pinfo) { |
| DEV_ERR("%s: invalid input.\n", __func__); |
| return -EINVAL; |
| } |
| |
| pinfo->xres = timing->active_h; |
| pinfo->yres = timing->active_v; |
| pinfo->clk_rate = timing->pixel_freq*1000; |
| |
| pinfo->lcdc.h_back_porch = timing->back_porch_h; |
| pinfo->lcdc.h_front_porch = timing->front_porch_h; |
| pinfo->lcdc.h_pulse_width = timing->pulse_width_h; |
| pinfo->lcdc.v_back_porch = timing->back_porch_v; |
| pinfo->lcdc.v_front_porch = timing->front_porch_v; |
| pinfo->lcdc.v_pulse_width = timing->pulse_width_v; |
| |
| pinfo->type = DTV_PANEL; |
| pinfo->pdest = DISPLAY_2; |
| pinfo->wait_cycle = 0; |
| pinfo->bpp = 24; |
| pinfo->fb_num = 1; |
| |
| pinfo->lcdc.border_clr = 0; /* blk */ |
| pinfo->lcdc.underflow_clr = 0xff; /* blue */ |
| pinfo->lcdc.hsync_skew = 0; |
| |
| return 0; |
| } /* hdmi_tx_init_panel_info */ |
| |
| /* Table tuned to indicate video formats supported by the MHL Tx */ |
| /* Valid pclk rates (Mhz): 25.2, 27, 27.03, 74.25 */ |
| static void hdmi_tx_setup_mhl_video_mode_lut(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| u32 i; |
| struct msm_hdmi_mode_timing_info *temp_timing; |
| |
| if (!hdmi_ctrl->mhl_max_pclk) { |
| DEV_WARN("%s: mhl max pclk not set!\n", __func__); |
| return; |
| } |
| DEV_DBG("%s: max mode set to [%u]\n", |
| __func__, hdmi_ctrl->mhl_max_pclk); |
| for (i = 0; i < HDMI_VFRMT_MAX; i++) { |
| temp_timing = |
| (struct msm_hdmi_mode_timing_info *)hdmi_get_supported_mode(i); |
| if (!temp_timing) |
| continue; |
| /* formats that exceed max mhl line clk bw */ |
| if (temp_timing->pixel_freq > hdmi_ctrl->mhl_max_pclk) |
| hdmi_del_supported_mode(i); |
| } |
| } /* hdmi_tx_setup_mhl_video_mode_lut */ |
| |
| static int hdmi_tx_read_sink_info(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int status; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!hdmi_tx_is_controller_on(hdmi_ctrl)) { |
| DEV_ERR("%s: failed: HDMI controller is off", __func__); |
| status = -ENXIO; |
| goto error; |
| } |
| |
| hdmi_ddc_config(&hdmi_ctrl->ddc_ctrl); |
| |
| status = hdmi_edid_read(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]); |
| if (!status) |
| DEV_DBG("%s: hdmi_edid_read success\n", __func__); |
| else |
| DEV_ERR("%s: hdmi_edid_read failed\n", __func__); |
| |
| error: |
| return status; |
| } /* hdmi_tx_read_sink_info */ |
| |
| static void hdmi_tx_hpd_int_work(struct work_struct *work) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, hpd_int_work); |
| if (!hdmi_ctrl || !hdmi_ctrl->hpd_initialized) { |
| DEV_DBG("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| DEV_DBG("%s: Got HPD interrupt\n", __func__); |
| |
| if (hdmi_ctrl->hpd_state) { |
| hdmi_tx_read_sink_info(hdmi_ctrl); |
| hdmi_tx_send_cable_notification(hdmi_ctrl, 1); |
| DEV_INFO("%s: sense cable CONNECTED: state switch to %d\n", |
| __func__, hdmi_ctrl->sdev.state); |
| } else { |
| hdmi_tx_send_cable_notification(hdmi_ctrl, 0); |
| DEV_INFO("%s: sense cable DISCONNECTED: state switch to %d\n", |
| __func__, hdmi_ctrl->sdev.state); |
| } |
| |
| if (!completion_done(&hdmi_ctrl->hpd_done)) |
| complete_all(&hdmi_ctrl->hpd_done); |
| } /* hdmi_tx_hpd_int_work */ |
| |
| static int hdmi_tx_check_capability(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| u32 hdmi_disabled, hdcp_disabled; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_QFPROM_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: QFPROM io is not initialized\n", __func__); |
| return -EINVAL; |
| } |
| |
| hdcp_disabled = DSS_REG_R_ND(io, |
| QFPROM_RAW_FEAT_CONFIG_ROW0_LSB) & BIT(31); |
| |
| hdmi_disabled = DSS_REG_R_ND(io, |
| QFPROM_RAW_FEAT_CONFIG_ROW0_MSB) & BIT(0); |
| |
| DEV_DBG("%s: Features <HDMI:%s, HDCP:%s>\n", __func__, |
| hdmi_disabled ? "OFF" : "ON", hdcp_disabled ? "OFF" : "ON"); |
| |
| if (hdmi_disabled) { |
| DEV_ERR("%s: HDMI disabled\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (hdcp_disabled) { |
| hdmi_ctrl->present_hdcp = 0; |
| DEV_WARN("%s: HDCP disabled\n", __func__); |
| } else { |
| hdmi_ctrl->present_hdcp = 1; |
| DEV_DBG("%s: Device is HDCP enabled\n", __func__); |
| } |
| |
| return 0; |
| } /* hdmi_tx_check_capability */ |
| |
| static int hdmi_tx_set_video_fmt(struct hdmi_tx_ctrl *hdmi_ctrl, |
| struct mdss_panel_info *pinfo) |
| { |
| int new_vic = -1; |
| const struct msm_hdmi_mode_timing_info *timing = NULL; |
| |
| if (!hdmi_ctrl || !pinfo) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| new_vic = hdmi_tx_get_vic_from_panel_info(hdmi_ctrl, pinfo); |
| if ((new_vic < 0) || (new_vic > HDMI_VFRMT_MAX)) { |
| DEV_ERR("%s: invalid or not supported vic\n", __func__); |
| return -EPERM; |
| } |
| |
| DEV_DBG("%s: switching from %s => %s", __func__, |
| msm_hdmi_mode_2string(hdmi_ctrl->video_resolution), |
| msm_hdmi_mode_2string(new_vic)); |
| |
| hdmi_ctrl->video_resolution = (u32)new_vic; |
| |
| timing = hdmi_get_supported_mode(hdmi_ctrl->video_resolution); |
| |
| /* todo: find a better way */ |
| hdmi_ctrl->pdata.power_data[HDMI_TX_CORE_PM].clk_config[0].rate = |
| timing->pixel_freq * 1000; |
| |
| hdmi_edid_set_video_resolution( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], |
| hdmi_ctrl->video_resolution); |
| |
| return 0; |
| } /* hdmi_tx_set_video_fmt */ |
| |
| static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl, |
| int video_format) |
| { |
| u32 total_v = 0; |
| u32 total_h = 0; |
| u32 start_h = 0; |
| u32 end_h = 0; |
| u32 start_v = 0; |
| u32 end_v = 0; |
| struct dss_io_data *io = NULL; |
| |
| const struct msm_hdmi_mode_timing_info *timing = |
| hdmi_get_supported_mode(video_format); |
| if (timing == NULL) { |
| DEV_ERR("%s: video format not supported: %d\n", __func__, |
| video_format); |
| return -EPERM; |
| } |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return -EPERM; |
| } |
| |
| total_h = timing->active_h + timing->front_porch_h + |
| timing->back_porch_h + timing->pulse_width_h - 1; |
| total_v = timing->active_v + timing->front_porch_v + |
| timing->back_porch_v + timing->pulse_width_v - 1; |
| if (((total_v << 16) & 0xE0000000) || (total_h & 0xFFFFE000)) { |
| DEV_ERR("%s: total v=%d or h=%d is larger than supported\n", |
| __func__, total_v, total_h); |
| return -EPERM; |
| } |
| DSS_REG_W(io, HDMI_TOTAL, (total_v << 16) | (total_h << 0)); |
| |
| start_h = timing->back_porch_h + timing->pulse_width_h; |
| end_h = (total_h + 1) - timing->front_porch_h; |
| if (((end_h << 16) & 0xE0000000) || (start_h & 0xFFFFE000)) { |
| DEV_ERR("%s: end_h=%d or start_h=%d is larger than supported\n", |
| __func__, end_h, start_h); |
| return -EPERM; |
| } |
| DSS_REG_W(io, HDMI_ACTIVE_H, (end_h << 16) | (start_h << 0)); |
| |
| start_v = timing->back_porch_v + timing->pulse_width_v - 1; |
| end_v = total_v - timing->front_porch_v; |
| if (((end_v << 16) & 0xE0000000) || (start_v & 0xFFFFE000)) { |
| DEV_ERR("%s: end_v=%d or start_v=%d is larger than supported\n", |
| __func__, end_v, start_v); |
| return -EPERM; |
| } |
| DSS_REG_W(io, HDMI_ACTIVE_V, (end_v << 16) | (start_v << 0)); |
| |
| if (timing->interlaced) { |
| DSS_REG_W(io, HDMI_V_TOTAL_F2, (total_v + 1) << 0); |
| DSS_REG_W(io, HDMI_ACTIVE_V_F2, |
| ((end_v + 1) << 16) | ((start_v + 1) << 0)); |
| } else { |
| DSS_REG_W(io, HDMI_V_TOTAL_F2, 0); |
| DSS_REG_W(io, HDMI_ACTIVE_V_F2, 0); |
| } |
| |
| DSS_REG_W(io, HDMI_FRAME_CTRL, |
| ((timing->interlaced << 31) & 0x80000000) | |
| ((timing->active_low_h << 29) & 0x20000000) | |
| ((timing->active_low_v << 28) & 0x10000000)); |
| |
| return 0; |
| } /* hdmi_tx_video_setup */ |
| |
| static void hdmi_tx_set_avi_infoframe(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int i, mode = 0; |
| u8 avi_iframe[16]; /* two header + length + 13 data */ |
| u8 checksum; |
| u32 sum, regVal; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return; |
| } |
| |
| switch (hdmi_ctrl->video_resolution) { |
| case HDMI_VFRMT_720x480p60_4_3: |
| mode = 0; |
| break; |
| case HDMI_VFRMT_720x480i60_16_9: |
| mode = 1; |
| break; |
| case HDMI_VFRMT_720x576p50_16_9: |
| mode = 2; |
| break; |
| case HDMI_VFRMT_720x576i50_16_9: |
| mode = 3; |
| break; |
| case HDMI_VFRMT_1280x720p60_16_9: |
| mode = 4; |
| break; |
| case HDMI_VFRMT_1280x720p50_16_9: |
| mode = 5; |
| break; |
| case HDMI_VFRMT_1920x1080p60_16_9: |
| mode = 6; |
| break; |
| case HDMI_VFRMT_1920x1080i60_16_9: |
| mode = 7; |
| break; |
| case HDMI_VFRMT_1920x1080p50_16_9: |
| mode = 8; |
| break; |
| case HDMI_VFRMT_1920x1080i50_16_9: |
| mode = 9; |
| break; |
| case HDMI_VFRMT_1920x1080p24_16_9: |
| mode = 10; |
| break; |
| case HDMI_VFRMT_1920x1080p30_16_9: |
| mode = 11; |
| break; |
| case HDMI_VFRMT_1920x1080p25_16_9: |
| mode = 12; |
| break; |
| case HDMI_VFRMT_640x480p60_4_3: |
| mode = 13; |
| break; |
| case HDMI_VFRMT_720x480p60_16_9: |
| mode = 14; |
| break; |
| case HDMI_VFRMT_720x576p50_4_3: |
| mode = 15; |
| break; |
| case HDMI_VFRMT_3840x2160p30_16_9: |
| mode = 16; |
| break; |
| case HDMI_VFRMT_3840x2160p25_16_9: |
| mode = 17; |
| break; |
| case HDMI_VFRMT_3840x2160p24_16_9: |
| mode = 18; |
| break; |
| case HDMI_VFRMT_4096x2160p24_16_9: |
| mode = 19; |
| break; |
| default: |
| DEV_INFO("%s: mode %d not supported\n", __func__, |
| hdmi_ctrl->video_resolution); |
| return; |
| } |
| |
| /* InfoFrame Type = 82 */ |
| avi_iframe[0] = 0x82; |
| /* Version = 2 */ |
| avi_iframe[1] = 2; |
| /* Length of AVI InfoFrame = 13 */ |
| avi_iframe[2] = 13; |
| |
| /* Data Byte 01: 0 Y1 Y0 A0 B1 B0 S1 S0 */ |
| avi_iframe[3] = hdmi_tx_avi_iframe_lut[0][mode]; |
| avi_iframe[3] |= hdmi_edid_get_sink_scaninfo( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], |
| hdmi_ctrl->video_resolution); |
| |
| /* Data Byte 02: C1 C0 M1 M0 R3 R2 R1 R0 */ |
| avi_iframe[4] = hdmi_tx_avi_iframe_lut[1][mode]; |
| /* Data Byte 03: ITC EC2 EC1 EC0 Q1 Q0 SC1 SC0 */ |
| avi_iframe[5] = hdmi_tx_avi_iframe_lut[2][mode]; |
| /* Data Byte 04: 0 VIC6 VIC5 VIC4 VIC3 VIC2 VIC1 VIC0 */ |
| avi_iframe[6] = hdmi_tx_avi_iframe_lut[3][mode]; |
| /* Data Byte 05: 0 0 0 0 PR3 PR2 PR1 PR0 */ |
| avi_iframe[7] = hdmi_tx_avi_iframe_lut[4][mode]; |
| /* Data Byte 06: LSB Line No of End of Top Bar */ |
| avi_iframe[8] = hdmi_tx_avi_iframe_lut[5][mode]; |
| /* Data Byte 07: MSB Line No of End of Top Bar */ |
| avi_iframe[9] = hdmi_tx_avi_iframe_lut[6][mode]; |
| /* Data Byte 08: LSB Line No of Start of Bottom Bar */ |
| avi_iframe[10] = hdmi_tx_avi_iframe_lut[7][mode]; |
| /* Data Byte 09: MSB Line No of Start of Bottom Bar */ |
| avi_iframe[11] = hdmi_tx_avi_iframe_lut[8][mode]; |
| /* Data Byte 10: LSB Pixel Number of End of Left Bar */ |
| avi_iframe[12] = hdmi_tx_avi_iframe_lut[9][mode]; |
| /* Data Byte 11: MSB Pixel Number of End of Left Bar */ |
| avi_iframe[13] = hdmi_tx_avi_iframe_lut[10][mode]; |
| /* Data Byte 12: LSB Pixel Number of Start of Right Bar */ |
| avi_iframe[14] = hdmi_tx_avi_iframe_lut[11][mode]; |
| /* Data Byte 13: MSB Pixel Number of Start of Right Bar */ |
| avi_iframe[15] = hdmi_tx_avi_iframe_lut[12][mode]; |
| |
| sum = 0; |
| for (i = 0; i < 16; i++) |
| sum += avi_iframe[i]; |
| sum &= 0xFF; |
| sum = 256 - sum; |
| checksum = (u8) sum; |
| |
| regVal = avi_iframe[5]; |
| regVal = regVal << 8 | avi_iframe[4]; |
| regVal = regVal << 8 | avi_iframe[3]; |
| regVal = regVal << 8 | checksum; |
| DSS_REG_W(io, HDMI_AVI_INFO0, regVal); |
| |
| regVal = avi_iframe[9]; |
| regVal = regVal << 8 | avi_iframe[8]; |
| regVal = regVal << 8 | avi_iframe[7]; |
| regVal = regVal << 8 | avi_iframe[6]; |
| DSS_REG_W(io, HDMI_AVI_INFO1, regVal); |
| |
| regVal = avi_iframe[13]; |
| regVal = regVal << 8 | avi_iframe[12]; |
| regVal = regVal << 8 | avi_iframe[11]; |
| regVal = regVal << 8 | avi_iframe[10]; |
| DSS_REG_W(io, HDMI_AVI_INFO2, regVal); |
| |
| regVal = avi_iframe[1]; |
| regVal = regVal << 16 | avi_iframe[15]; |
| regVal = regVal << 8 | avi_iframe[14]; |
| DSS_REG_W(io, HDMI_AVI_INFO3, regVal); |
| |
| /* AVI InfFrame enable (every frame) */ |
| DSS_REG_W(io, HDMI_INFOFRAME_CTRL0, |
| DSS_REG_R(io, HDMI_INFOFRAME_CTRL0) | BIT(1) | BIT(0)); |
| } /* hdmi_tx_set_avi_infoframe */ |
| |
| /* todo: add 3D support */ |
| static void hdmi_tx_set_vendor_specific_infoframe( |
| struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int i; |
| u8 vs_iframe[9]; /* two header + length + 6 data */ |
| u32 sum, reg_val; |
| u32 hdmi_vic, hdmi_video_format; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return; |
| } |
| |
| /* HDMI Spec 1.4a Table 8-10 */ |
| vs_iframe[0] = 0x81; /* type */ |
| vs_iframe[1] = 0x1; /* version */ |
| vs_iframe[2] = 0x8; /* length */ |
| |
| vs_iframe[3] = 0x0; /* PB0: checksum */ |
| |
| /* PB1..PB3: 24 Bit IEEE Registration Code 00_0C_03 */ |
| vs_iframe[4] = 0x03; |
| vs_iframe[5] = 0x0C; |
| vs_iframe[6] = 0x00; |
| |
| hdmi_video_format = 0x1; |
| switch (hdmi_ctrl->video_resolution) { |
| case HDMI_VFRMT_3840x2160p30_16_9: |
| hdmi_vic = 0x1; |
| break; |
| case HDMI_VFRMT_3840x2160p25_16_9: |
| hdmi_vic = 0x2; |
| break; |
| case HDMI_VFRMT_3840x2160p24_16_9: |
| hdmi_vic = 0x3; |
| break; |
| case HDMI_VFRMT_4096x2160p24_16_9: |
| hdmi_vic = 0x4; |
| break; |
| default: |
| hdmi_video_format = 0x0; |
| hdmi_vic = 0x0; |
| } |
| |
| /* PB4: HDMI Video Format[7:5], Reserved[4:0] */ |
| vs_iframe[7] = (hdmi_video_format << 5) & 0xE0; |
| |
| /* PB5: HDMI_VIC or 3D_Structure[7:4], Reserved[3:0] */ |
| vs_iframe[8] = hdmi_vic; |
| |
| /* compute checksum */ |
| sum = 0; |
| for (i = 0; i < 9; i++) |
| sum += vs_iframe[i]; |
| |
| sum &= 0xFF; |
| sum = 256 - sum; |
| vs_iframe[3] = (u8)sum; |
| |
| reg_val = (hdmi_vic << 16) | (vs_iframe[3] << 8) | |
| (hdmi_video_format << 5) | vs_iframe[2]; |
| DSS_REG_W(io, HDMI_VENSPEC_INFO0, reg_val); |
| |
| /* vendor specific info-frame enable (every frame) */ |
| DSS_REG_W(io, HDMI_INFOFRAME_CTRL0, |
| DSS_REG_R(io, HDMI_INFOFRAME_CTRL0) | BIT(13) | BIT(12)); |
| } /* hdmi_tx_set_vendor_specific_infoframe */ |
| |
| static void hdmi_tx_set_spd_infoframe(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| u32 packet_header = 0; |
| u32 check_sum = 0; |
| u32 packet_payload = 0; |
| u32 packet_control = 0; |
| |
| u8 *vendor_name = NULL; |
| u8 *product_description = NULL; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return; |
| } |
| |
| vendor_name = hdmi_ctrl->spd_vendor_name; |
| product_description = hdmi_ctrl->spd_product_description; |
| |
| /* Setup Packet header and payload */ |
| /* |
| * 0x83 InfoFrame Type Code |
| * 0x01 InfoFrame Version Number |
| * 0x19 Length of Source Product Description InfoFrame |
| */ |
| packet_header = 0x83 | (0x01 << 8) | (0x19 << 16); |
| DSS_REG_W(io, HDMI_GENERIC1_HDR, packet_header); |
| check_sum += IFRAME_CHECKSUM_32(packet_header); |
| |
| packet_payload = (vendor_name[3] & 0x7f) |
| | ((vendor_name[4] & 0x7f) << 8) |
| | ((vendor_name[5] & 0x7f) << 16) |
| | ((vendor_name[6] & 0x7f) << 24); |
| DSS_REG_W(io, HDMI_GENERIC1_1, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| /* Product Description (7-bit ASCII code) */ |
| packet_payload = (vendor_name[7] & 0x7f) |
| | ((product_description[0] & 0x7f) << 8) |
| | ((product_description[1] & 0x7f) << 16) |
| | ((product_description[2] & 0x7f) << 24); |
| DSS_REG_W(io, HDMI_GENERIC1_2, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| packet_payload = (product_description[3] & 0x7f) |
| | ((product_description[4] & 0x7f) << 8) |
| | ((product_description[5] & 0x7f) << 16) |
| | ((product_description[6] & 0x7f) << 24); |
| DSS_REG_W(io, HDMI_GENERIC1_3, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| packet_payload = (product_description[7] & 0x7f) |
| | ((product_description[8] & 0x7f) << 8) |
| | ((product_description[9] & 0x7f) << 16) |
| | ((product_description[10] & 0x7f) << 24); |
| DSS_REG_W(io, HDMI_GENERIC1_4, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| packet_payload = (product_description[11] & 0x7f) |
| | ((product_description[12] & 0x7f) << 8) |
| | ((product_description[13] & 0x7f) << 16) |
| | ((product_description[14] & 0x7f) << 24); |
| DSS_REG_W(io, HDMI_GENERIC1_5, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| /* |
| * Source Device Information |
| * 00h unknown |
| * 01h Digital STB |
| * 02h DVD |
| * 03h D-VHS |
| * 04h HDD Video |
| * 05h DVC |
| * 06h DSC |
| * 07h Video CD |
| * 08h Game |
| * 09h PC general |
| */ |
| packet_payload = (product_description[15] & 0x7f) | 0x00 << 8; |
| DSS_REG_W(io, HDMI_GENERIC1_6, packet_payload); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| |
| /* Vendor Name (7bit ASCII code) */ |
| packet_payload = ((vendor_name[0] & 0x7f) << 8) |
| | ((vendor_name[1] & 0x7f) << 16) |
| | ((vendor_name[2] & 0x7f) << 24); |
| check_sum += IFRAME_CHECKSUM_32(packet_payload); |
| packet_payload |= ((0x100 - (0xff & check_sum)) & 0xff); |
| DSS_REG_W(io, HDMI_GENERIC1_0, packet_payload); |
| |
| /* |
| * GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND |
| * Setup HDMI TX generic packet control |
| * Enable this packet to transmit every frame |
| * Enable HDMI TX engine to transmit Generic packet 1 |
| */ |
| packet_control = DSS_REG_R_ND(io, HDMI_GEN_PKT_CTRL); |
| packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4)); |
| DSS_REG_W(io, HDMI_GEN_PKT_CTRL, packet_control); |
| } /* hdmi_tx_set_spd_infoframe */ |
| |
| static void hdmi_tx_set_mode(struct hdmi_tx_ctrl *hdmi_ctrl, u32 power_on) |
| { |
| struct dss_io_data *io = NULL; |
| /* Defaults: Disable block, HDMI mode */ |
| u32 reg_val = BIT(1); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return; |
| } |
| |
| if (power_on) { |
| /* Enable the block */ |
| reg_val |= BIT(0); |
| |
| /* HDMI Encryption, if HDCP is enabled */ |
| if (hdmi_ctrl->hdcp_feature_on && hdmi_ctrl->present_hdcp) |
| reg_val |= BIT(2); |
| |
| /* Set transmission mode to DVI based in EDID info */ |
| if (hdmi_edid_get_sink_mode( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) == 0) |
| reg_val &= ~BIT(1); /* DVI mode */ |
| } |
| |
| DSS_REG_W(io, HDMI_CTRL, reg_val); |
| |
| DEV_DBG("HDMI Core: %s, HDMI_CTRL=0x%08x\n", |
| power_on ? "Enable" : "Disable", reg_val); |
| } /* hdmi_tx_set_mode */ |
| |
| static int hdmi_tx_config_power(struct hdmi_tx_ctrl *hdmi_ctrl, |
| enum hdmi_tx_power_module_type module, int config) |
| { |
| int rc = 0; |
| struct dss_module_power *power_data = NULL; |
| |
| if (!hdmi_ctrl || module >= HDMI_TX_MAX_PM) { |
| DEV_ERR("%s: Error: invalid input\n", __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| power_data = &hdmi_ctrl->pdata.power_data[module]; |
| if (!power_data) { |
| DEV_ERR("%s: Error: invalid power data\n", __func__); |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (config) { |
| rc = msm_dss_config_vreg(&hdmi_ctrl->pdev->dev, |
| power_data->vreg_config, power_data->num_vreg, 1); |
| if (rc) { |
| DEV_ERR("%s: Failed to config %s vreg. Err=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| goto exit; |
| } |
| |
| rc = msm_dss_get_clk(&hdmi_ctrl->pdev->dev, |
| power_data->clk_config, power_data->num_clk); |
| if (rc) { |
| DEV_ERR("%s: Failed to get %s clk. Err=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| |
| msm_dss_config_vreg(&hdmi_ctrl->pdev->dev, |
| power_data->vreg_config, power_data->num_vreg, 0); |
| } |
| } else { |
| msm_dss_put_clk(power_data->clk_config, power_data->num_clk); |
| |
| rc = msm_dss_config_vreg(&hdmi_ctrl->pdev->dev, |
| power_data->vreg_config, power_data->num_vreg, 0); |
| if (rc) |
| DEV_ERR("%s: Fail to deconfig %s vreg. Err=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| } |
| |
| exit: |
| return rc; |
| } /* hdmi_tx_config_power */ |
| |
| static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl, |
| enum hdmi_tx_power_module_type module, int enable) |
| { |
| int rc = 0; |
| struct dss_module_power *power_data = NULL; |
| |
| if (!hdmi_ctrl || module >= HDMI_TX_MAX_PM) { |
| DEV_ERR("%s: Error: invalid input\n", __func__); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| power_data = &hdmi_ctrl->pdata.power_data[module]; |
| if (!power_data) { |
| DEV_ERR("%s: Error: invalid power data\n", __func__); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| if (enable) { |
| rc = msm_dss_enable_vreg(power_data->vreg_config, |
| power_data->num_vreg, 1); |
| if (rc) { |
| DEV_ERR("%s: Failed to enable %s vreg. Error=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| goto error; |
| } |
| |
| rc = msm_dss_enable_gpio(power_data->gpio_config, |
| power_data->num_gpio, 1); |
| if (rc) { |
| DEV_ERR("%s: Failed to enable %s gpio. Error=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| goto disable_vreg; |
| } |
| |
| rc = msm_dss_clk_set_rate(power_data->clk_config, |
| power_data->num_clk); |
| if (rc) { |
| DEV_ERR("%s: failed to set clks rate for %s. err=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| goto disable_gpio; |
| } |
| |
| rc = msm_dss_enable_clk(power_data->clk_config, |
| power_data->num_clk, 1); |
| if (rc) { |
| DEV_ERR("%s: Failed to enable clks for %s. Error=%d\n", |
| __func__, hdmi_tx_pm_name(module), rc); |
| goto disable_gpio; |
| } |
| } else { |
| msm_dss_enable_clk(power_data->clk_config, |
| power_data->num_clk, 0); |
| msm_dss_clk_set_rate(power_data->clk_config, |
| power_data->num_clk); |
| msm_dss_enable_gpio(power_data->gpio_config, |
| power_data->num_gpio, 0); |
| msm_dss_enable_vreg(power_data->vreg_config, |
| power_data->num_vreg, 0); |
| } |
| |
| return rc; |
| |
| disable_gpio: |
| msm_dss_enable_gpio(power_data->gpio_config, power_data->num_gpio, 0); |
| disable_vreg: |
| msm_dss_enable_vreg(power_data->vreg_config, power_data->num_vreg, 0); |
| error: |
| return rc; |
| } /* hdmi_tx_enable_power */ |
| |
| static void hdmi_tx_core_off(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CEC_PM, 0); |
| hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 0); |
| } /* hdmi_tx_core_off */ |
| |
| static int hdmi_tx_core_on(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 1); |
| if (rc) { |
| DEV_ERR("%s: core hdmi_msm_enable_power failed rc = %d\n", |
| __func__, rc); |
| return rc; |
| } |
| rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CEC_PM, 1); |
| if (rc) { |
| DEV_ERR("%s: cec hdmi_msm_enable_power failed rc = %d\n", |
| __func__, rc); |
| goto disable_core_power; |
| } |
| |
| return rc; |
| disable_core_power: |
| hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 0); |
| return rc; |
| } /* hdmi_tx_core_on */ |
| |
| static void hdmi_tx_phy_reset(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| unsigned int phy_reset_polarity = 0x0; |
| unsigned int pll_reset_polarity = 0x0; |
| unsigned int val; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return; |
| } |
| |
| val = DSS_REG_R_ND(io, HDMI_PHY_CTRL); |
| |
| phy_reset_polarity = val >> 3 & 0x1; |
| pll_reset_polarity = val >> 1 & 0x1; |
| |
| if (phy_reset_polarity == 0) |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET); |
| else |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET)); |
| |
| if (pll_reset_polarity == 0) |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET_PLL); |
| else |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET_PLL)); |
| |
| if (phy_reset_polarity == 0) |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET)); |
| else |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET); |
| |
| if (pll_reset_polarity == 0) |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET_PLL)); |
| else |
| DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET_PLL); |
| } /* hdmi_tx_phy_reset */ |
| |
| static void hdmi_tx_init_phy(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_PHY_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: phy io is not initialized\n", __func__); |
| return; |
| } |
| |
| DSS_REG_W_ND(io, HDMI_PHY_ANA_CFG0, 0x1B); |
| DSS_REG_W_ND(io, HDMI_PHY_ANA_CFG1, 0xF2); |
| DSS_REG_W_ND(io, HDMI_PHY_BIST_CFG0, 0x0); |
| DSS_REG_W_ND(io, HDMI_PHY_BIST_PATN0, 0x0); |
| DSS_REG_W_ND(io, HDMI_PHY_BIST_PATN1, 0x0); |
| DSS_REG_W_ND(io, HDMI_PHY_BIST_PATN2, 0x0); |
| DSS_REG_W_ND(io, HDMI_PHY_BIST_PATN3, 0x0); |
| |
| DSS_REG_W_ND(io, HDMI_PHY_PD_CTRL1, 0x20); |
| } /* hdmi_tx_init_phy */ |
| |
| static void hdmi_tx_powerdown_phy(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_PHY_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: phy io is not initialized\n", __func__); |
| return; |
| } |
| |
| DSS_REG_W_ND(io, HDMI_PHY_PD_CTRL0, 0x7F); |
| } /* hdmi_tx_powerdown_phy */ |
| |
| static int hdmi_tx_audio_acr_setup(struct hdmi_tx_ctrl *hdmi_ctrl, |
| bool enabled, int num_of_channels) |
| { |
| /* Read first before writing */ |
| u32 acr_pck_ctrl_reg; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: Invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return -EINVAL; |
| } |
| |
| acr_pck_ctrl_reg = DSS_REG_R(io, HDMI_ACR_PKT_CTRL); |
| |
| if (enabled) { |
| const struct msm_hdmi_mode_timing_info *timing = |
| hdmi_get_supported_mode(hdmi_ctrl->video_resolution); |
| const struct hdmi_tx_audio_acr_arry *audio_acr = |
| &hdmi_tx_audio_acr_lut[0]; |
| const int lut_size = sizeof(hdmi_tx_audio_acr_lut) |
| / sizeof(*hdmi_tx_audio_acr_lut); |
| u32 i, n, cts, layout, multiplier, aud_pck_ctrl_2_reg; |
| |
| if (timing == NULL) { |
| DEV_WARN("%s: video format %d not supported\n", |
| __func__, hdmi_ctrl->video_resolution); |
| return -EPERM; |
| } |
| |
| for (i = 0; i < lut_size; |
| audio_acr = &hdmi_tx_audio_acr_lut[++i]) { |
| if (audio_acr->pclk == timing->pixel_freq) |
| break; |
| } |
| if (i >= lut_size) { |
| DEV_WARN("%s: pixel clk %d not supported\n", __func__, |
| timing->pixel_freq); |
| return -EPERM; |
| } |
| |
| n = audio_acr->lut[hdmi_ctrl->audio_sample_rate].n; |
| cts = audio_acr->lut[hdmi_ctrl->audio_sample_rate].cts; |
| layout = (MSM_HDMI_AUDIO_CHANNEL_2 == num_of_channels) ? 0 : 1; |
| |
| if ( |
| (AUDIO_SAMPLE_RATE_192KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_176_4KHZ == hdmi_ctrl->audio_sample_rate)) { |
| multiplier = 4; |
| n >>= 2; /* divide N by 4 and use multiplier */ |
| } else if ( |
| (AUDIO_SAMPLE_RATE_96KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_88_2KHZ == hdmi_ctrl->audio_sample_rate)) { |
| multiplier = 2; |
| n >>= 1; /* divide N by 2 and use multiplier */ |
| } else { |
| multiplier = 1; |
| } |
| DEV_DBG("%s: n=%u, cts=%u, layout=%u\n", __func__, n, cts, |
| layout); |
| |
| /* AUDIO_PRIORITY | SOURCE */ |
| acr_pck_ctrl_reg |= 0x80000100; |
| /* N_MULTIPLE(multiplier) */ |
| acr_pck_ctrl_reg |= (multiplier & 7) << 16; |
| |
| if ((AUDIO_SAMPLE_RATE_48KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_96KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_192KHZ == hdmi_ctrl->audio_sample_rate)) { |
| /* SELECT(3) */ |
| acr_pck_ctrl_reg |= 3 << 4; |
| /* CTS_48 */ |
| cts <<= 12; |
| |
| /* CTS: need to determine how many fractional bits */ |
| DSS_REG_W(io, HDMI_ACR_48_0, cts); |
| /* N */ |
| DSS_REG_W(io, HDMI_ACR_48_1, n); |
| } else if ( |
| (AUDIO_SAMPLE_RATE_44_1KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_88_2KHZ == hdmi_ctrl->audio_sample_rate) || |
| (AUDIO_SAMPLE_RATE_176_4KHZ == hdmi_ctrl->audio_sample_rate)) { |
| /* SELECT(2) */ |
| acr_pck_ctrl_reg |= 2 << 4; |
| /* CTS_44 */ |
| cts <<= 12; |
| |
| /* CTS: need to determine how many fractional bits */ |
| DSS_REG_W(io, HDMI_ACR_44_0, cts); |
| /* N */ |
| DSS_REG_W(io, HDMI_ACR_44_1, n); |
| } else { /* default to 32k */ |
| /* SELECT(1) */ |
| acr_pck_ctrl_reg |= 1 << 4; |
| /* CTS_32 */ |
| cts <<= 12; |
| |
| /* CTS: need to determine how many fractional bits */ |
| DSS_REG_W(io, HDMI_ACR_32_0, cts); |
| /* N */ |
| DSS_REG_W(io, HDMI_ACR_32_1, n); |
| } |
| /* Payload layout depends on number of audio channels */ |
| /* LAYOUT_SEL(layout) */ |
| aud_pck_ctrl_2_reg = 1 | (layout << 1); |
| /* override | layout */ |
| DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg); |
| |
| /* SEND | CONT */ |
| acr_pck_ctrl_reg |= 0x00000003; |
| } else { |
| /* ~(SEND | CONT) */ |
| acr_pck_ctrl_reg &= ~0x00000003; |
| } |
| DSS_REG_W(io, HDMI_ACR_PKT_CTRL, acr_pck_ctrl_reg); |
| |
| return 0; |
| } /* hdmi_tx_audio_acr_setup */ |
| |
| static int hdmi_tx_audio_iframe_setup(struct hdmi_tx_ctrl *hdmi_ctrl, |
| bool enabled, u32 num_of_channels, u32 channel_allocation, |
| u32 level_shift, bool down_mix) |
| { |
| struct dss_io_data *io = NULL; |
| |
| u32 channel_count = 1; /* Def to 2 channels -> Table 17 in CEA-D */ |
| u32 check_sum, audio_info_0_reg, audio_info_1_reg; |
| u32 audio_info_ctrl_reg; |
| u32 aud_pck_ctrl_2_reg; |
| u32 layout; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return -EINVAL; |
| } |
| |
| layout = (MSM_HDMI_AUDIO_CHANNEL_2 == num_of_channels) ? 0 : 1; |
| aud_pck_ctrl_2_reg = 1 | (layout << 1); |
| DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg); |
| |
| /* |
| * Please see table 20 Audio InfoFrame in HDMI spec |
| * FL = front left |
| * FC = front Center |
| * FR = front right |
| * FLC = front left center |
| * FRC = front right center |
| * RL = rear left |
| * RC = rear center |
| * RR = rear right |
| * RLC = rear left center |
| * RRC = rear right center |
| * LFE = low frequency effect |
| */ |
| |
| /* Read first then write because it is bundled with other controls */ |
| audio_info_ctrl_reg = DSS_REG_R(io, HDMI_INFOFRAME_CTRL0); |
| |
| if (enabled) { |
| switch (num_of_channels) { |
| case MSM_HDMI_AUDIO_CHANNEL_2: |
| break; |
| case MSM_HDMI_AUDIO_CHANNEL_4: |
| channel_count = 3; |
| break; |
| case MSM_HDMI_AUDIO_CHANNEL_6: |
| channel_count = 5; |
| break; |
| case MSM_HDMI_AUDIO_CHANNEL_8: |
| channel_count = 7; |
| break; |
| default: |
| DEV_ERR("%s: Unsupported num_of_channels = %u\n", |
| __func__, num_of_channels); |
| return -EINVAL; |
| } |
| |
| /* Program the Channel-Speaker allocation */ |
| audio_info_1_reg = 0; |
| /* CA(channel_allocation) */ |
| audio_info_1_reg |= channel_allocation & 0xff; |
| /* Program the Level shifter */ |
| audio_info_1_reg |= (level_shift << 11) & 0x00007800; |
| /* Program the Down-mix Inhibit Flag */ |
| audio_info_1_reg |= (down_mix << 15) & 0x00008000; |
| |
| DSS_REG_W(io, HDMI_AUDIO_INFO1, audio_info_1_reg); |
| |
| /* |
| * Calculate CheckSum: Sum of all the bytes in the |
| * Audio Info Packet (See table 8.4 in HDMI spec) |
| */ |
| check_sum = 0; |
| /* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_TYPE[0x84] */ |
| check_sum += 0x84; |
| /* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_VERSION[0x01] */ |
| check_sum += 1; |
| /* HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH[0x0A] */ |
| check_sum += 0x0A; |
| check_sum += channel_count; |
| check_sum += channel_allocation; |
| /* See Table 8.5 in HDMI spec */ |
| check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7; |
| check_sum &= 0xFF; |
| check_sum = (u8) (256 - check_sum); |
| |
| audio_info_0_reg = 0; |
| /* CHECKSUM(check_sum) */ |
| audio_info_0_reg |= check_sum & 0xff; |
| /* CC(channel_count) */ |
| audio_info_0_reg |= (channel_count << 8) & 0x00000700; |
| |
| DSS_REG_W(io, HDMI_AUDIO_INFO0, audio_info_0_reg); |
| |
| /* |
| * Set these flags |
| * AUDIO_INFO_UPDATE | |
| * AUDIO_INFO_SOURCE | |
| * AUDIO_INFO_CONT | |
| * AUDIO_INFO_SEND |
| */ |
| audio_info_ctrl_reg |= 0x000000F0; |
| } else { |
| /*Clear these flags |
| * ~(AUDIO_INFO_UPDATE | |
| * AUDIO_INFO_SOURCE | |
| * AUDIO_INFO_CONT | |
| * AUDIO_INFO_SEND) |
| */ |
| audio_info_ctrl_reg &= ~0x000000F0; |
| } |
| DSS_REG_W(io, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg); |
| |
| dss_reg_dump(io->base, io->len, |
| enabled ? "HDMI-AUDIO-ON: " : "HDMI-AUDIO-OFF: ", REG_DUMP); |
| |
| return 0; |
| } /* hdmi_tx_audio_iframe_setup */ |
| |
| static int hdmi_tx_audio_info_setup(struct platform_device *pdev, |
| u32 num_of_channels, u32 channel_allocation, u32 level_shift, |
| bool down_mix) |
| { |
| int rc = 0; |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (hdmi_ctrl->panel_power_on) { |
| rc = hdmi_tx_audio_iframe_setup(hdmi_ctrl, true, |
| num_of_channels, channel_allocation, level_shift, |
| down_mix); |
| if (rc) |
| DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed.rc=%d\n", |
| __func__, rc); |
| } else { |
| DEV_ERR("%s: Error. panel is not on.\n", __func__); |
| rc = -EPERM; |
| } |
| |
| return rc; |
| } /* hdmi_tx_audio_info_setup */ |
| |
| static int hdmi_tx_get_audio_edid_blk(struct platform_device *pdev, |
| struct msm_hdmi_audio_edid_blk *blk) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!hdmi_ctrl->audio_sdev.state) { |
| DEV_ERR("%s: failed. HDMI is not connected/ready for audio\n", |
| __func__); |
| return -EPERM; |
| } |
| |
| return hdmi_edid_get_audio_blk( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], blk); |
| } /* hdmi_tx_get_audio_edid_blk */ |
| |
| static u8 hdmi_tx_tmds_enabled(struct platform_device *pdev) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| |
| /* status of tmds */ |
| return (hdmi_ctrl->timing_gen_on == true); |
| } |
| |
| static int hdmi_tx_set_mhl_max_pclk(struct platform_device *pdev, u32 max_val) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| if (max_val) { |
| hdmi_ctrl->mhl_max_pclk = max_val; |
| hdmi_tx_setup_mhl_video_mode_lut(hdmi_ctrl); |
| } else { |
| DEV_ERR("%s: invalid max pclk val\n", __func__); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int msm_hdmi_register_mhl(struct platform_device *pdev, |
| struct msm_hdmi_mhl_ops *ops) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid pdev\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!ops) { |
| DEV_ERR("%s: invalid ops\n", __func__); |
| return -EINVAL; |
| } |
| |
| ops->tmds_enabled = hdmi_tx_tmds_enabled; |
| ops->set_mhl_max_pclk = hdmi_tx_set_mhl_max_pclk; |
| return 0; |
| } |
| |
| int msm_hdmi_register_audio_codec(struct platform_device *pdev, |
| struct msm_hdmi_audio_codec_ops *ops) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| |
| if (!hdmi_ctrl || !ops) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -ENODEV; |
| } |
| |
| ops->audio_info_setup = hdmi_tx_audio_info_setup; |
| ops->get_audio_edid_blk = hdmi_tx_get_audio_edid_blk; |
| |
| return 0; |
| } /* hdmi_tx_audio_register */ |
| EXPORT_SYMBOL(msm_hdmi_register_audio_codec); |
| |
| static int hdmi_tx_audio_setup(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| const int channels = MSM_HDMI_AUDIO_CHANNEL_2; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return -EINVAL; |
| } |
| |
| rc = hdmi_tx_audio_acr_setup(hdmi_ctrl, true, channels); |
| if (rc) { |
| DEV_ERR("%s: hdmi_tx_audio_acr_setup failed. rc=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| rc = hdmi_tx_audio_iframe_setup(hdmi_ctrl, true, channels, 0, 0, false); |
| if (rc) { |
| DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed. rc=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| DEV_INFO("HDMI Audio: Enabled\n"); |
| |
| return 0; |
| } /* hdmi_tx_audio_setup */ |
| |
| static void hdmi_tx_audio_off(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| u32 i, status, sleep_us, timeout_us, timeout_sec = 15; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return; |
| } |
| |
| /* Check if audio engine is turned off by QDSP or not */ |
| /* send off notification after every 1 sec for 15 seconds */ |
| for (i = 0; i < timeout_sec; i++) { |
| sleep_us = 5000; /* Maximum time to sleep between two reads */ |
| timeout_us = 1000 * 1000; /* Total time for condition to meet */ |
| |
| if (readl_poll_timeout((io->base + HDMI_AUDIO_CFG), |
| status, ((status & BIT(0)) == 0), |
| sleep_us, timeout_us)) { |
| |
| DEV_ERR("%s: audio still on after %d sec. try again\n", |
| __func__, i+1); |
| |
| hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0, true); |
| continue; |
| } |
| break; |
| } |
| if (i == timeout_sec) |
| DEV_ERR("%s: Error: cannot turn off audio engine\n", __func__); |
| |
| if (hdmi_tx_audio_iframe_setup(hdmi_ctrl, false, 0, 0, 0, false)) |
| DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed.\n", __func__); |
| |
| if (hdmi_tx_audio_acr_setup(hdmi_ctrl, false, 0)) |
| DEV_ERR("%s: hdmi_tx_audio_acr_setup failed.\n", __func__); |
| |
| DEV_INFO("HDMI Audio: Disabled\n"); |
| } /* hdmi_tx_audio_off */ |
| |
| static int hdmi_tx_start(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io is not initialized\n", __func__); |
| return -EINVAL; |
| } |
| |
| hdmi_tx_set_mode(hdmi_ctrl, false); |
| hdmi_tx_init_phy(hdmi_ctrl); |
| DSS_REG_W(io, HDMI_USEC_REFTIMER, 0x0001001B); |
| |
| hdmi_tx_set_mode(hdmi_ctrl, true); |
| |
| rc = hdmi_tx_video_setup(hdmi_ctrl, hdmi_ctrl->video_resolution); |
| if (rc) { |
| DEV_ERR("%s: hdmi_tx_video_setup failed. rc=%d\n", |
| __func__, rc); |
| hdmi_tx_set_mode(hdmi_ctrl, false); |
| return rc; |
| } |
| |
| if (!hdmi_tx_is_dvi_mode(hdmi_ctrl)) { |
| rc = hdmi_tx_audio_setup(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: hdmi_msm_audio_setup failed. rc=%d\n", |
| __func__, rc); |
| hdmi_tx_set_mode(hdmi_ctrl, false); |
| return rc; |
| } |
| |
| if (!hdmi_ctrl->hdcp_feature_on || !hdmi_ctrl->present_hdcp) |
| hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1, false); |
| |
| hdmi_tx_set_avi_infoframe(hdmi_ctrl); |
| hdmi_tx_set_vendor_specific_infoframe(hdmi_ctrl); |
| hdmi_tx_set_spd_infoframe(hdmi_ctrl); |
| } |
| |
| /* todo: CEC */ |
| |
| DEV_INFO("%s: HDMI Core: Initialized\n", __func__); |
| |
| return rc; |
| } /* hdmi_tx_start */ |
| |
| static void hdmi_tx_hpd_polarity_setup(struct hdmi_tx_ctrl *hdmi_ctrl, |
| bool polarity) |
| { |
| struct dss_io_data *io = NULL; |
| u32 cable_sense; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io is not initialized\n", __func__); |
| return; |
| } |
| |
| if (polarity) |
| DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(2) | BIT(1)); |
| else |
| DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(2)); |
| |
| cable_sense = (DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(1)) >> 1; |
| DEV_DBG("%s: listen = %s, sense = %s\n", __func__, |
| polarity ? "connect" : "disconnect", |
| cable_sense ? "connect" : "disconnect"); |
| |
| if (cable_sense == polarity) { |
| u32 reg_val = DSS_REG_R(io, HDMI_HPD_CTRL); |
| |
| /* Toggle HPD circuit to trigger HPD sense */ |
| DSS_REG_W(io, HDMI_HPD_CTRL, reg_val & ~BIT(28)); |
| DSS_REG_W(io, HDMI_HPD_CTRL, reg_val | BIT(28)); |
| } |
| } /* hdmi_tx_hpd_polarity_setup */ |
| |
| static void hdmi_tx_power_off_work(struct work_struct *work) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| struct dss_io_data *io = NULL; |
| |
| hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, power_off_work); |
| if (!hdmi_ctrl) { |
| DEV_DBG("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: Core io is not initialized\n", __func__); |
| return; |
| } |
| |
| if (hdmi_ctrl->hdcp_feature_on && hdmi_ctrl->present_hdcp) { |
| DEV_DBG("%s: Turning off HDCP\n", __func__); |
| hdmi_hdcp_off(hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]); |
| } |
| |
| if (!hdmi_tx_is_dvi_mode(hdmi_ctrl)) { |
| hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0, false); |
| hdmi_tx_audio_off(hdmi_ctrl); |
| } |
| |
| hdmi_tx_powerdown_phy(hdmi_ctrl); |
| |
| /* |
| * this is needed to avoid pll lock failure due to |
| * clk framework's rate caching. |
| */ |
| hdmi_ctrl->pdata.power_data[HDMI_TX_CORE_PM].clk_config[0].rate = 0; |
| |
| hdmi_tx_core_off(hdmi_ctrl); |
| |
| if (hdmi_ctrl->hpd_off_pending) { |
| hdmi_tx_hpd_off(hdmi_ctrl); |
| hdmi_ctrl->hpd_off_pending = false; |
| } |
| |
| mutex_lock(&hdmi_ctrl->mutex); |
| hdmi_ctrl->panel_power_on = false; |
| mutex_unlock(&hdmi_ctrl->mutex); |
| |
| DEV_INFO("%s: HDMI Core: OFF\n", __func__); |
| } /* hdmi_tx_power_off_work */ |
| |
| static int hdmi_tx_power_off(struct mdss_panel_data *panel_data) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_panel_data(panel_data); |
| |
| if (!hdmi_ctrl || !hdmi_ctrl->panel_power_on) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* |
| * Queue work item to handle power down sequence. |
| * This is needed since we need to wait for the audio engine |
| * to shutdown first before we shutdown the HDMI core. |
| */ |
| DEV_DBG("%s: Queuing work to power off HDMI core\n", __func__); |
| queue_work(hdmi_ctrl->workq, &hdmi_ctrl->power_off_work); |
| |
| return 0; |
| } /* hdmi_tx_power_off */ |
| |
| static int hdmi_tx_power_on(struct mdss_panel_data *panel_data) |
| { |
| u32 timeout; |
| int rc = 0; |
| struct dss_io_data *io = NULL; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_panel_data(panel_data); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io is not initialized\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!hdmi_ctrl->hpd_initialized) { |
| DEV_ERR("%s: HDMI on is not possible w/o cable detection.\n", |
| __func__); |
| return -EPERM; |
| } |
| |
| /* If a power down is already underway, wait for it to finish */ |
| flush_work_sync(&hdmi_ctrl->power_off_work); |
| |
| if (hdmi_ctrl->pdata.primary) { |
| timeout = wait_for_completion_interruptible_timeout( |
| &hdmi_ctrl->hpd_done, HZ); |
| if (!timeout) { |
| DEV_ERR("%s: cable connection hasn't happened yet\n", |
| __func__); |
| return -ETIMEDOUT; |
| } |
| } |
| |
| rc = hdmi_tx_set_video_fmt(hdmi_ctrl, &panel_data->panel_info); |
| if (rc) { |
| DEV_ERR("%s: cannot set video_fmt.rc=%d\n", __func__, rc); |
| return rc; |
| } |
| |
| hdmi_ctrl->hdcp_feature_on = hdcp_feature_on; |
| |
| DEV_INFO("power: ON (%s)\n", msm_hdmi_mode_2string( |
| hdmi_ctrl->video_resolution)); |
| |
| rc = hdmi_tx_core_on(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: hdmi_msm_core_on failed\n", __func__); |
| return rc; |
| } |
| |
| mutex_lock(&hdmi_ctrl->mutex); |
| hdmi_ctrl->panel_power_on = true; |
| |
| if (hdmi_ctrl->hpd_state) { |
| DEV_DBG("%s: Turning HDMI on\n", __func__); |
| mutex_unlock(&hdmi_ctrl->mutex); |
| rc = hdmi_tx_start(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: hdmi_tx_start failed. rc=%d\n", |
| __func__, rc); |
| hdmi_tx_power_off(panel_data); |
| return rc; |
| } |
| } else { |
| mutex_unlock(&hdmi_ctrl->mutex); |
| } |
| |
| dss_reg_dump(io->base, io->len, "HDMI-ON: ", REG_DUMP); |
| |
| DEV_INFO("%s: HDMI=%s DVI= %s\n", __func__, |
| hdmi_tx_is_controller_on(hdmi_ctrl) ? "ON" : "OFF" , |
| hdmi_tx_is_dvi_mode(hdmi_ctrl) ? "ON" : "OFF"); |
| |
| hdmi_tx_hpd_polarity_setup(hdmi_ctrl, HPD_DISCONNECT_POLARITY); |
| |
| return 0; |
| } /* hdmi_tx_power_on */ |
| |
| static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (!hdmi_ctrl->hpd_initialized) { |
| DEV_DBG("%s: HPD is already OFF, returning\n", __func__); |
| return; |
| } |
| |
| mdss_disable_irq(&hdmi_tx_hw); |
| |
| hdmi_tx_set_mode(hdmi_ctrl, false); |
| |
| rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, 0); |
| if (rc) |
| DEV_INFO("%s: Failed to disable hpd power. Error=%d\n", |
| __func__, rc); |
| |
| hdmi_ctrl->hpd_state = false; |
| hdmi_ctrl->hpd_initialized = false; |
| } /* hdmi_tx_hpd_off */ |
| |
| static int hdmi_tx_hpd_on(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| u32 reg_val; |
| int rc = 0; |
| struct dss_io_data *io = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_ERR("%s: core io not inititalized\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (hdmi_ctrl->hpd_initialized) { |
| DEV_DBG("%s: HPD is already ON\n", __func__); |
| } else { |
| rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, true); |
| if (rc) { |
| DEV_ERR("%s: Failed to enable hpd power. rc=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| dss_reg_dump(io->base, io->len, "HDMI-INIT: ", REG_DUMP); |
| |
| hdmi_tx_set_mode(hdmi_ctrl, false); |
| hdmi_tx_phy_reset(hdmi_ctrl); |
| hdmi_tx_set_mode(hdmi_ctrl, true); |
| |
| DSS_REG_W(io, HDMI_USEC_REFTIMER, 0x0001001B); |
| |
| mdss_enable_irq(&hdmi_tx_hw); |
| |
| hdmi_ctrl->hpd_initialized = true; |
| |
| /* set timeout to 4.1ms (max) for hardware debounce */ |
| reg_val = DSS_REG_R(io, HDMI_HPD_CTRL) | 0x1FFF; |
| |
| /* Turn on HPD HW circuit */ |
| DSS_REG_W(io, HDMI_HPD_CTRL, reg_val | BIT(28)); |
| |
| hdmi_tx_hpd_polarity_setup(hdmi_ctrl, HPD_CONNECT_POLARITY); |
| } |
| |
| return rc; |
| } /* hdmi_tx_hpd_on */ |
| |
| static int hdmi_tx_sysfs_enable_hpd(struct hdmi_tx_ctrl *hdmi_ctrl, int on) |
| { |
| int rc = 0; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| DEV_INFO("%s: %d\n", __func__, on); |
| if (on) { |
| rc = hdmi_tx_hpd_on(hdmi_ctrl); |
| } else { |
| /* If power down is already underway, wait for it to finish */ |
| flush_work_sync(&hdmi_ctrl->power_off_work); |
| |
| if (!hdmi_ctrl->panel_power_on) |
| hdmi_tx_hpd_off(hdmi_ctrl); |
| else |
| hdmi_ctrl->hpd_off_pending = true; |
| |
| hdmi_tx_send_cable_notification(hdmi_ctrl, 0); |
| DEV_DBG("%s: Hdmi state switch to %d\n", __func__, |
| hdmi_ctrl->sdev.state); |
| } |
| |
| return rc; |
| } /* hdmi_tx_sysfs_enable_hpd */ |
| |
| static irqreturn_t hdmi_tx_isr(int irq, void *data) |
| { |
| struct dss_io_data *io = NULL; |
| struct hdmi_tx_ctrl *hdmi_ctrl = (struct hdmi_tx_ctrl *)data; |
| |
| if (!hdmi_ctrl || !hdmi_ctrl->hpd_initialized) { |
| DEV_WARN("%s: invalid input data, ISR ignored\n", __func__); |
| return IRQ_HANDLED; |
| } |
| |
| io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO]; |
| if (!io->base) { |
| DEV_WARN("%s: core io not initialized, ISR ignored\n", |
| __func__); |
| return IRQ_HANDLED; |
| } |
| |
| if (DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(0)) { |
| hdmi_ctrl->hpd_state = |
| (DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(1)) >> 1; |
| |
| /* |
| * Ack the current hpd interrupt and stop listening to |
| * new hpd interrupt. |
| */ |
| DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(0)); |
| queue_work(hdmi_ctrl->workq, &hdmi_ctrl->hpd_int_work); |
| } |
| |
| if (hdmi_ddc_isr(&hdmi_ctrl->ddc_ctrl) < 0) |
| DEV_ERR("%s: hdmi_ddc_isr failed\n", __func__); |
| |
| if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) |
| if (hdmi_hdcp_isr( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) < 0) |
| DEV_ERR("%s: hdmi_hdcp_isr failed\n", __func__); |
| |
| return IRQ_HANDLED; |
| } /* hdmi_tx_isr */ |
| |
| static void hdmi_tx_dev_deinit(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) { |
| hdmi_hdcp_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]); |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP] = NULL; |
| } |
| |
| if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) |
| hdmi_edid_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]); |
| |
| switch_dev_unregister(&hdmi_ctrl->audio_sdev); |
| switch_dev_unregister(&hdmi_ctrl->sdev); |
| if (hdmi_ctrl->workq) |
| destroy_workqueue(hdmi_ctrl->workq); |
| mutex_destroy(&hdmi_ctrl->mutex); |
| |
| hdmi_tx_hw.ptr = NULL; |
| } /* hdmi_tx_dev_deinit */ |
| |
| static int hdmi_tx_dev_init(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| struct hdmi_tx_platform_data *pdata = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| pdata = &hdmi_ctrl->pdata; |
| |
| rc = hdmi_tx_check_capability(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: no HDMI device\n", __func__); |
| goto fail_no_hdmi; |
| } |
| |
| /* irq enable/disable will be handled in hpd on/off */ |
| hdmi_tx_hw.ptr = (void *)hdmi_ctrl; |
| |
| hdmi_setup_video_mode_lut(); |
| mutex_init(&hdmi_ctrl->mutex); |
| hdmi_ctrl->workq = create_workqueue("hdmi_tx_workq"); |
| if (!hdmi_ctrl->workq) { |
| DEV_ERR("%s: hdmi_tx_workq creation failed.\n", __func__); |
| rc = -EPERM; |
| goto fail_create_workq; |
| } |
| |
| hdmi_ctrl->ddc_ctrl.io = &pdata->io[HDMI_TX_CORE_IO]; |
| init_completion(&hdmi_ctrl->ddc_ctrl.ddc_sw_done); |
| |
| hdmi_ctrl->panel_power_on = false; |
| hdmi_ctrl->panel_suspend = false; |
| |
| hdmi_ctrl->hpd_state = false; |
| hdmi_ctrl->hpd_initialized = false; |
| hdmi_ctrl->hpd_off_pending = false; |
| init_completion(&hdmi_ctrl->hpd_done); |
| INIT_WORK(&hdmi_ctrl->hpd_int_work, hdmi_tx_hpd_int_work); |
| |
| INIT_WORK(&hdmi_ctrl->power_off_work, hdmi_tx_power_off_work); |
| |
| hdmi_ctrl->audio_sample_rate = AUDIO_SAMPLE_RATE_48KHZ; |
| |
| hdmi_ctrl->sdev.name = "hdmi"; |
| if (switch_dev_register(&hdmi_ctrl->sdev) < 0) { |
| DEV_ERR("%s: Hdmi switch registration failed\n", __func__); |
| rc = -ENODEV; |
| goto fail_create_workq; |
| } |
| |
| hdmi_ctrl->audio_sdev.name = "hdmi_audio"; |
| if (switch_dev_register(&hdmi_ctrl->audio_sdev) < 0) { |
| DEV_ERR("%s: hdmi_audio switch registration failed\n", |
| __func__); |
| rc = -ENODEV; |
| goto fail_audio_switch_dev; |
| } |
| |
| return 0; |
| |
| fail_audio_switch_dev: |
| switch_dev_unregister(&hdmi_ctrl->sdev); |
| fail_create_workq: |
| if (hdmi_ctrl->workq) |
| destroy_workqueue(hdmi_ctrl->workq); |
| mutex_destroy(&hdmi_ctrl->mutex); |
| fail_no_hdmi: |
| return rc; |
| } /* hdmi_tx_dev_init */ |
| |
| static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data, |
| int event, void *arg) |
| { |
| int rc = 0, new_vic = -1; |
| struct hdmi_tx_ctrl *hdmi_ctrl = |
| hdmi_tx_get_drvdata_from_panel_data(panel_data); |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| DEV_DBG("%s: event = %d suspend=%d, hpd_feature=%d\n", __func__, |
| event, hdmi_ctrl->panel_suspend, hdmi_ctrl->hpd_feature_on); |
| |
| switch (event) { |
| case MDSS_EVENT_FB_REGISTERED: |
| rc = hdmi_tx_sysfs_create(hdmi_ctrl, arg); |
| if (rc) { |
| DEV_ERR("%s: hdmi_tx_sysfs_create failed.rc=%d\n", |
| __func__, rc); |
| return rc; |
| } |
| rc = hdmi_tx_init_features(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: init_features failed.rc=%d\n", |
| __func__, rc); |
| hdmi_tx_sysfs_remove(hdmi_ctrl); |
| return rc; |
| } |
| |
| if (hdmi_ctrl->pdata.primary) { |
| INIT_COMPLETION(hdmi_ctrl->hpd_done); |
| rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true); |
| if (rc) { |
| DEV_ERR("%s: hpd_enable failed. rc=%d\n", |
| __func__, rc); |
| hdmi_tx_sysfs_remove(hdmi_ctrl); |
| return rc; |
| } else { |
| hdmi_ctrl->hpd_feature_on = true; |
| } |
| } |
| |
| break; |
| |
| case MDSS_EVENT_CHECK_PARAMS: |
| new_vic = hdmi_tx_get_vic_from_panel_info(hdmi_ctrl, |
| (struct mdss_panel_info *)arg); |
| if ((new_vic < 0) || (new_vic > HDMI_VFRMT_MAX)) { |
| DEV_ERR("%s: invalid or not supported vic\n", __func__); |
| return -EPERM; |
| } |
| |
| /* |
| * return value of 1 lets mdss know that panel |
| * needs a reconfig due to new resolution and |
| * it will issue close and open subsequently. |
| */ |
| if (new_vic != hdmi_ctrl->video_resolution) |
| rc = 1; |
| else |
| DEV_DBG("%s: no res change.\n", __func__); |
| break; |
| |
| case MDSS_EVENT_RESUME: |
| /* If a suspend is already underway, wait for it to finish */ |
| if (hdmi_ctrl->panel_suspend && hdmi_ctrl->panel_power_on) |
| flush_work(&hdmi_ctrl->power_off_work); |
| |
| if (hdmi_ctrl->hpd_feature_on) { |
| INIT_COMPLETION(hdmi_ctrl->hpd_done); |
| |
| rc = hdmi_tx_hpd_on(hdmi_ctrl); |
| if (rc) |
| DEV_ERR("%s: hdmi_tx_hpd_on failed. rc=%d\n", |
| __func__, rc); |
| } |
| break; |
| |
| case MDSS_EVENT_RESET: |
| if (hdmi_ctrl->panel_suspend) { |
| u32 timeout; |
| hdmi_ctrl->panel_suspend = false; |
| |
| timeout = wait_for_completion_interruptible_timeout( |
| &hdmi_ctrl->hpd_done, HZ/10); |
| if (!timeout & !hdmi_ctrl->hpd_state) { |
| DEV_INFO("%s: cable removed during suspend\n", |
| __func__); |
| hdmi_tx_send_cable_notification(hdmi_ctrl, 0); |
| rc = -EPERM; |
| } else { |
| DEV_DBG("%s: cable present after resume\n", |
| __func__); |
| } |
| } |
| break; |
| |
| case MDSS_EVENT_UNBLANK: |
| rc = hdmi_tx_power_on(panel_data); |
| if (rc) |
| DEV_ERR("%s: hdmi_tx_power_on failed. rc=%d\n", |
| __func__, rc); |
| break; |
| |
| case MDSS_EVENT_PANEL_ON: |
| if (hdmi_ctrl->hdcp_feature_on && hdmi_ctrl->present_hdcp) { |
| DEV_DBG("%s: Starting HDCP authentication\n", __func__); |
| rc = hdmi_hdcp_authenticate( |
| hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]); |
| if (rc) |
| DEV_ERR("%s: hdcp auth failed. rc=%d\n", |
| __func__, rc); |
| } |
| hdmi_ctrl->timing_gen_on = true; |
| break; |
| |
| case MDSS_EVENT_SUSPEND: |
| if (!hdmi_ctrl->panel_power_on) { |
| if (hdmi_ctrl->hpd_feature_on) |
| hdmi_tx_hpd_off(hdmi_ctrl); |
| |
| hdmi_ctrl->panel_suspend = false; |
| } else { |
| hdmi_ctrl->hpd_off_pending = true; |
| hdmi_ctrl->panel_suspend = true; |
| } |
| break; |
| |
| case MDSS_EVENT_BLANK: |
| if (hdmi_ctrl->panel_power_on) { |
| rc = hdmi_tx_power_off(panel_data); |
| if (rc) |
| DEV_ERR("%s: hdmi_tx_power_off failed.rc=%d\n", |
| __func__, rc); |
| |
| } else { |
| DEV_DBG("%s: hdmi is already powered off\n", __func__); |
| } |
| break; |
| |
| case MDSS_EVENT_PANEL_OFF: |
| hdmi_ctrl->timing_gen_on = false; |
| break; |
| |
| case MDSS_EVENT_CLOSE: |
| if (hdmi_ctrl->hpd_feature_on) |
| hdmi_tx_hpd_polarity_setup(hdmi_ctrl, |
| HPD_CONNECT_POLARITY); |
| break; |
| } |
| |
| return rc; |
| } /* hdmi_tx_panel_event_handler */ |
| |
| static int hdmi_tx_register_panel(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int rc = 0; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| hdmi_ctrl->panel_data.event_handler = hdmi_tx_panel_event_handler; |
| |
| hdmi_ctrl->video_resolution = DEFAULT_VIDEO_RESOLUTION; |
| rc = hdmi_tx_init_panel_info(hdmi_ctrl->video_resolution, |
| &hdmi_ctrl->panel_data.panel_info); |
| if (rc) { |
| DEV_ERR("%s: hdmi_init_panel_info failed\n", __func__); |
| return rc; |
| } |
| |
| rc = mdss_register_panel(hdmi_ctrl->pdev, &hdmi_ctrl->panel_data); |
| if (rc) { |
| DEV_ERR("%s: FAILED: to register HDMI panel\n", __func__); |
| return rc; |
| } |
| |
| return rc; |
| } /* hdmi_tx_register_panel */ |
| |
| static void hdmi_tx_deinit_resource(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int i; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| /* VREG & CLK */ |
| for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--) { |
| if (hdmi_tx_config_power(hdmi_ctrl, i, 0)) |
| DEV_ERR("%s: '%s' power deconfig fail\n", |
| __func__, hdmi_tx_pm_name(i)); |
| } |
| |
| /* IO */ |
| for (i = HDMI_TX_MAX_IO - 1; i >= 0; i--) |
| msm_dss_iounmap(&hdmi_ctrl->pdata.io[i]); |
| } /* hdmi_tx_deinit_resource */ |
| |
| static int hdmi_tx_init_resource(struct hdmi_tx_ctrl *hdmi_ctrl) |
| { |
| int i, rc = 0; |
| struct hdmi_tx_platform_data *pdata = NULL; |
| |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| pdata = &hdmi_ctrl->pdata; |
| |
| /* IO */ |
| for (i = 0; i < HDMI_TX_MAX_IO; i++) { |
| rc = msm_dss_ioremap_byname(hdmi_ctrl->pdev, &pdata->io[i], |
| hdmi_tx_io_name(i)); |
| if (rc) { |
| DEV_ERR("%s: '%s' remap failed\n", __func__, |
| hdmi_tx_io_name(i)); |
| goto error; |
| } |
| DEV_INFO("%s: '%s': start = 0x%x, len=0x%x\n", __func__, |
| hdmi_tx_io_name(i), (u32)pdata->io[i].base, |
| pdata->io[i].len); |
| } |
| |
| /* VREG & CLK */ |
| for (i = 0; i < HDMI_TX_MAX_PM; i++) { |
| rc = hdmi_tx_config_power(hdmi_ctrl, i, 1); |
| if (rc) { |
| DEV_ERR("%s: '%s' power config failed.rc=%d\n", |
| __func__, hdmi_tx_pm_name(i), rc); |
| goto error; |
| } |
| } |
| |
| return rc; |
| |
| error: |
| hdmi_tx_deinit_resource(hdmi_ctrl); |
| return rc; |
| } /* hdmi_tx_init_resource */ |
| |
| static void hdmi_tx_put_dt_clk_data(struct device *dev, |
| struct dss_module_power *module_power) |
| { |
| if (!module_power) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (module_power->clk_config) { |
| devm_kfree(dev, module_power->clk_config); |
| module_power->clk_config = NULL; |
| } |
| module_power->num_clk = 0; |
| } /* hdmi_tx_put_dt_clk_data */ |
| |
| /* todo: once clk are moved to device tree then change this implementation */ |
| static int hdmi_tx_get_dt_clk_data(struct device *dev, |
| struct dss_module_power *mp, u32 module_type) |
| { |
| int rc = 0; |
| |
| if (!dev || !mp) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type)); |
| |
| switch (module_type) { |
| case HDMI_TX_HPD_PM: |
| mp->num_clk = 2; |
| mp->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) * |
| mp->num_clk, GFP_KERNEL); |
| if (!mp->clk_config) { |
| DEV_ERR("%s: can't alloc '%s' clk mem\n", __func__, |
| hdmi_tx_pm_name(module_type)); |
| goto error; |
| } |
| |
| snprintf(mp->clk_config[0].clk_name, 32, "%s", "iface_clk"); |
| mp->clk_config[0].type = DSS_CLK_AHB; |
| mp->clk_config[0].rate = 0; |
| |
| snprintf(mp->clk_config[1].clk_name, 32, "%s", "core_clk"); |
| mp->clk_config[1].type = DSS_CLK_OTHER; |
| mp->clk_config[1].rate = 19200000; |
| break; |
| |
| case HDMI_TX_CORE_PM: |
| mp->num_clk = 2; |
| mp->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) * |
| mp->num_clk, GFP_KERNEL); |
| if (!mp->clk_config) { |
| DEV_ERR("%s: can't alloc '%s' clk mem\n", __func__, |
| hdmi_tx_pm_name(module_type)); |
| goto error; |
| } |
| |
| snprintf(mp->clk_config[0].clk_name, 32, "%s", "extp_clk"); |
| mp->clk_config[0].type = DSS_CLK_PCLK; |
| /* This rate will be overwritten when core is powered on */ |
| mp->clk_config[0].rate = 148500000; |
| |
| snprintf(mp->clk_config[1].clk_name, 32, "%s", "alt_iface_clk"); |
| mp->clk_config[1].type = DSS_CLK_AHB; |
| mp->clk_config[1].rate = 0; |
| break; |
| |
| case HDMI_TX_CEC_PM: |
| mp->num_clk = 0; |
| DEV_DBG("%s: no clk\n", __func__); |
| break; |
| |
| default: |
| DEV_ERR("%s: invalid module type=%d\n", __func__, |
| module_type); |
| return -EINVAL; |
| } |
| |
| return rc; |
| |
| error: |
| if (mp->clk_config) { |
| devm_kfree(dev, mp->clk_config); |
| mp->clk_config = NULL; |
| } |
| mp->num_clk = 0; |
| |
| return rc; |
| } /* hdmi_tx_get_dt_clk_data */ |
| |
| static void hdmi_tx_put_dt_vreg_data(struct device *dev, |
| struct dss_module_power *module_power) |
| { |
| if (!module_power) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (module_power->vreg_config) { |
| devm_kfree(dev, module_power->vreg_config); |
| module_power->vreg_config = NULL; |
| } |
| module_power->num_vreg = 0; |
| } /* hdmi_tx_put_dt_vreg_data */ |
| |
| static int hdmi_tx_get_dt_vreg_data(struct device *dev, |
| struct dss_module_power *mp, u32 module_type) |
| { |
| int i, j, rc = 0; |
| int dt_vreg_total = 0, mod_vreg_total = 0; |
| u32 ndx_mask = 0; |
| u32 *val_array = NULL; |
| const char *mod_name = NULL; |
| struct device_node *of_node = NULL; |
| char prop_name[32]; |
| |
| if (!dev || !mp) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| switch (module_type) { |
| case HDMI_TX_HPD_PM: |
| mod_name = "hpd"; |
| break; |
| case HDMI_TX_CORE_PM: |
| mod_name = "core"; |
| break; |
| case HDMI_TX_CEC_PM: |
| mod_name = "cec"; |
| break; |
| default: |
| DEV_ERR("%s: invalid module type=%d\n", __func__, |
| module_type); |
| return -EINVAL; |
| } |
| |
| DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type)); |
| |
| of_node = dev->of_node; |
| |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, "supply-names"); |
| dt_vreg_total = of_property_count_strings(of_node, prop_name); |
| if (dt_vreg_total < 0) { |
| DEV_ERR("%s: vreg not found. rc=%d\n", __func__, |
| dt_vreg_total); |
| rc = dt_vreg_total; |
| goto error; |
| } |
| |
| /* count how many vreg for particular hdmi module */ |
| for (i = 0; i < dt_vreg_total; i++) { |
| const char *st = NULL; |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "supply-names"); |
| rc = of_property_read_string_index(of_node, |
| prop_name, i, &st); |
| if (rc) { |
| DEV_ERR("%s: error reading name. i=%d, rc=%d\n", |
| __func__, i, rc); |
| goto error; |
| } |
| |
| if (strnstr(st, mod_name, strlen(st))) { |
| ndx_mask |= BIT(i); |
| mod_vreg_total++; |
| } |
| } |
| |
| if (mod_vreg_total > 0) { |
| mp->num_vreg = mod_vreg_total; |
| mp->vreg_config = devm_kzalloc(dev, sizeof(struct dss_vreg) * |
| mod_vreg_total, GFP_KERNEL); |
| if (!mp->vreg_config) { |
| DEV_ERR("%s: can't alloc '%s' vreg mem\n", __func__, |
| hdmi_tx_pm_name(module_type)); |
| goto error; |
| } |
| } else { |
| DEV_DBG("%s: no vreg\n", __func__); |
| return 0; |
| } |
| |
| val_array = devm_kzalloc(dev, sizeof(u32) * dt_vreg_total, GFP_KERNEL); |
| if (!val_array) { |
| DEV_ERR("%s: can't allocate vreg scratch mem\n", __func__); |
| rc = -ENOMEM; |
| goto error; |
| } |
| |
| for (i = 0, j = 0; (i < dt_vreg_total) && (j < mod_vreg_total); i++) { |
| const char *st = NULL; |
| |
| if (!(ndx_mask & BIT(0))) { |
| ndx_mask >>= 1; |
| continue; |
| } |
| |
| /* vreg-name */ |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "supply-names"); |
| rc = of_property_read_string_index(of_node, |
| prop_name, i, &st); |
| if (rc) { |
| DEV_ERR("%s: error reading name. i=%d, rc=%d\n", |
| __func__, i, rc); |
| goto error; |
| } |
| snprintf(mp->vreg_config[j].vreg_name, 32, "%s", st); |
| |
| /* vreg-type */ |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "supply-type"); |
| memset(val_array, 0, sizeof(u32) * dt_vreg_total); |
| rc = of_property_read_u32_array(of_node, |
| prop_name, val_array, dt_vreg_total); |
| if (rc) { |
| DEV_ERR("%s: error read '%s' vreg type. rc=%d\n", |
| __func__, hdmi_tx_pm_name(module_type), rc); |
| goto error; |
| } |
| mp->vreg_config[j].type = val_array[i]; |
| |
| /* vreg-min-voltage */ |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "min-voltage-level"); |
| memset(val_array, 0, sizeof(u32) * dt_vreg_total); |
| rc = of_property_read_u32_array(of_node, |
| prop_name, val_array, |
| dt_vreg_total); |
| if (rc) { |
| DEV_ERR("%s: error read '%s' min volt. rc=%d\n", |
| __func__, hdmi_tx_pm_name(module_type), rc); |
| goto error; |
| } |
| mp->vreg_config[j].min_voltage = val_array[i]; |
| |
| /* vreg-max-voltage */ |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "max-voltage-level"); |
| memset(val_array, 0, sizeof(u32) * dt_vreg_total); |
| rc = of_property_read_u32_array(of_node, |
| prop_name, val_array, |
| dt_vreg_total); |
| if (rc) { |
| DEV_ERR("%s: error read '%s' max volt. rc=%d\n", |
| __func__, hdmi_tx_pm_name(module_type), rc); |
| goto error; |
| } |
| mp->vreg_config[j].max_voltage = val_array[i]; |
| |
| /* vreg-op-mode */ |
| memset(prop_name, 0, sizeof(prop_name)); |
| snprintf(prop_name, 32, "%s-%s", COMPATIBLE_NAME, |
| "op-mode"); |
| memset(val_array, 0, sizeof(u32) * dt_vreg_total); |
| rc = of_property_read_u32_array(of_node, |
| prop_name, val_array, |
| dt_vreg_total); |
| if (rc) { |
| DEV_ERR("%s: error read '%s' min volt. rc=%d\n", |
| __func__, hdmi_tx_pm_name(module_type), rc); |
| goto error; |
| } |
| mp->vreg_config[j].optimum_voltage = val_array[i]; |
| |
| DEV_DBG("%s: %s type=%d, min=%d, max=%d, op=%d\n", |
| __func__, mp->vreg_config[j].vreg_name, |
| mp->vreg_config[j].type, |
| mp->vreg_config[j].min_voltage, |
| mp->vreg_config[j].max_voltage, |
| mp->vreg_config[j].optimum_voltage); |
| |
| ndx_mask >>= 1; |
| j++; |
| } |
| |
| devm_kfree(dev, val_array); |
| |
| return rc; |
| |
| error: |
| if (mp->vreg_config) { |
| devm_kfree(dev, mp->vreg_config); |
| mp->vreg_config = NULL; |
| } |
| mp->num_vreg = 0; |
| |
| if (val_array) |
| devm_kfree(dev, val_array); |
| return rc; |
| } /* hdmi_tx_get_dt_vreg_data */ |
| |
| static void hdmi_tx_put_dt_gpio_data(struct device *dev, |
| struct dss_module_power *module_power) |
| { |
| if (!module_power) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| if (module_power->gpio_config) { |
| devm_kfree(dev, module_power->gpio_config); |
| module_power->gpio_config = NULL; |
| } |
| module_power->num_gpio = 0; |
| } /* hdmi_tx_put_dt_gpio_data */ |
| |
| static int hdmi_tx_get_dt_gpio_data(struct device *dev, |
| struct dss_module_power *mp, u32 module_type) |
| { |
| int i, j; |
| int mp_gpio_cnt = 0, gpio_list_size = 0; |
| struct dss_gpio *gpio_list = NULL; |
| struct device_node *of_node = NULL; |
| |
| DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type)); |
| |
| if (!dev || !mp) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| of_node = dev->of_node; |
| |
| switch (module_type) { |
| case HDMI_TX_HPD_PM: |
| gpio_list_size = ARRAY_SIZE(hpd_gpio_config); |
| gpio_list = hpd_gpio_config; |
| break; |
| case HDMI_TX_CORE_PM: |
| gpio_list_size = ARRAY_SIZE(core_gpio_config); |
| gpio_list = core_gpio_config; |
| break; |
| case HDMI_TX_CEC_PM: |
| gpio_list_size = ARRAY_SIZE(cec_gpio_config); |
| gpio_list = cec_gpio_config; |
| break; |
| default: |
| DEV_ERR("%s: invalid module type=%d\n", __func__, |
| module_type); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < gpio_list_size; i++) |
| if (of_find_property(of_node, gpio_list[i].gpio_name, NULL)) |
| mp_gpio_cnt++; |
| |
| if (!mp_gpio_cnt) { |
| DEV_DBG("%s: no gpio\n", __func__); |
| return 0; |
| } |
| |
| DEV_DBG("%s: mp_gpio_cnt = %d\n", __func__, mp_gpio_cnt); |
| mp->num_gpio = mp_gpio_cnt; |
| |
| mp->gpio_config = devm_kzalloc(dev, sizeof(struct dss_gpio) * |
| mp_gpio_cnt, GFP_KERNEL); |
| if (!mp->gpio_config) { |
| DEV_ERR("%s: can't alloc '%s' gpio mem\n", __func__, |
| hdmi_tx_pm_name(module_type)); |
| |
| mp->num_gpio = 0; |
| return -ENOMEM; |
| } |
| |
| for (i = 0, j = 0; i < gpio_list_size; i++) { |
| int gpio = of_get_named_gpio(of_node, |
| gpio_list[i].gpio_name, 0); |
| if (gpio < 0) { |
| DEV_DBG("%s: no gpio named %s\n", __func__, |
| gpio_list[i].gpio_name); |
| continue; |
| } |
| memcpy(&mp->gpio_config[j], &gpio_list[i], |
| sizeof(struct dss_gpio)); |
| |
| mp->gpio_config[j].gpio = (unsigned)gpio; |
| |
| DEV_DBG("%s: gpio num=%d, name=%s, value=%d\n", |
| __func__, mp->gpio_config[j].gpio, |
| mp->gpio_config[j].gpio_name, |
| mp->gpio_config[j].value); |
| j++; |
| } |
| |
| return 0; |
| } /* hdmi_tx_get_dt_gpio_data */ |
| |
| static void hdmi_tx_put_dt_data(struct device *dev, |
| struct hdmi_tx_platform_data *pdata) |
| { |
| int i; |
| if (!dev || !pdata) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--) |
| hdmi_tx_put_dt_clk_data(dev, &pdata->power_data[i]); |
| |
| for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--) |
| hdmi_tx_put_dt_vreg_data(dev, &pdata->power_data[i]); |
| |
| for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--) |
| hdmi_tx_put_dt_gpio_data(dev, &pdata->power_data[i]); |
| } /* hdmi_tx_put_dt_data */ |
| |
| static int hdmi_tx_get_dt_data(struct platform_device *pdev, |
| struct hdmi_tx_platform_data *pdata) |
| { |
| int i, rc = 0; |
| struct device_node *of_node = NULL; |
| |
| if (!pdev || !pdata) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| of_node = pdev->dev.of_node; |
| |
| rc = of_property_read_u32(of_node, "cell-index", &pdev->id); |
| if (rc) { |
| DEV_ERR("%s: dev id from dt not found.rc=%d\n", |
| __func__, rc); |
| goto error; |
| } |
| DEV_DBG("%s: id=%d\n", __func__, pdev->id); |
| |
| /* GPIO */ |
| for (i = 0; i < HDMI_TX_MAX_PM; i++) { |
| rc = hdmi_tx_get_dt_gpio_data(&pdev->dev, |
| &pdata->power_data[i], i); |
| if (rc) { |
| DEV_ERR("%s: '%s' get_dt_gpio_data failed.rc=%d\n", |
| __func__, hdmi_tx_pm_name(i), rc); |
| goto error; |
| } |
| } |
| |
| /* VREG */ |
| for (i = 0; i < HDMI_TX_MAX_PM; i++) { |
| rc = hdmi_tx_get_dt_vreg_data(&pdev->dev, |
| &pdata->power_data[i], i); |
| if (rc) { |
| DEV_ERR("%s: '%s' get_dt_vreg_data failed.rc=%d\n", |
| __func__, hdmi_tx_pm_name(i), rc); |
| goto error; |
| } |
| } |
| |
| /* CLK */ |
| for (i = 0; i < HDMI_TX_MAX_PM; i++) { |
| rc = hdmi_tx_get_dt_clk_data(&pdev->dev, |
| &pdata->power_data[i], i); |
| if (rc) { |
| DEV_ERR("%s: '%s' get_dt_clk_data failed.rc=%d\n", |
| __func__, hdmi_tx_pm_name(i), rc); |
| goto error; |
| } |
| } |
| |
| if (of_find_property(pdev->dev.of_node, "qcom,primary_panel", NULL)) { |
| u32 tmp; |
| of_property_read_u32(pdev->dev.of_node, "qcom,primary_panel", |
| &tmp); |
| pdata->primary = tmp ? true : false; |
| } |
| |
| return rc; |
| |
| error: |
| hdmi_tx_put_dt_data(&pdev->dev, pdata); |
| return rc; |
| } /* hdmi_tx_get_dt_data */ |
| |
| static int __devinit hdmi_tx_probe(struct platform_device *pdev) |
| { |
| int rc = 0; |
| struct device_node *of_node = pdev->dev.of_node; |
| struct hdmi_tx_ctrl *hdmi_ctrl = NULL; |
| |
| if (!of_node) { |
| DEV_ERR("%s: FAILED: of_node not found\n", __func__); |
| rc = -ENODEV; |
| return rc; |
| } |
| |
| hdmi_ctrl = devm_kzalloc(&pdev->dev, sizeof(*hdmi_ctrl), GFP_KERNEL); |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: FAILED: cannot alloc hdmi tx ctrl\n", __func__); |
| rc = -ENOMEM; |
| goto failed_no_mem; |
| } |
| |
| platform_set_drvdata(pdev, hdmi_ctrl); |
| hdmi_ctrl->pdev = pdev; |
| |
| rc = hdmi_tx_get_dt_data(pdev, &hdmi_ctrl->pdata); |
| if (rc) { |
| DEV_ERR("%s: FAILED: parsing device tree data. rc=%d\n", |
| __func__, rc); |
| goto failed_dt_data; |
| } |
| |
| rc = hdmi_tx_init_resource(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: FAILED: resource init. rc=%d\n", |
| __func__, rc); |
| goto failed_res_init; |
| } |
| |
| rc = hdmi_tx_dev_init(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: FAILED: hdmi_tx_dev_init. rc=%d\n", __func__, rc); |
| goto failed_dev_init; |
| } |
| |
| rc = hdmi_tx_register_panel(hdmi_ctrl); |
| if (rc) { |
| DEV_ERR("%s: FAILED: register_panel. rc=%d\n", __func__, rc); |
| goto failed_reg_panel; |
| } |
| |
| rc = of_platform_populate(of_node, NULL, NULL, &pdev->dev); |
| if (rc) { |
| DEV_ERR("%s: Failed to add child devices. rc=%d\n", |
| __func__, rc); |
| goto failed_reg_panel; |
| } else { |
| DEV_DBG("%s: Add child devices.\n", __func__); |
| } |
| |
| if (mdss_debug_register_base("hdmi", |
| hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO].base, |
| hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO].len)) |
| DEV_WARN("%s: hdmi_tx debugfs register failed\n", __func__); |
| |
| return rc; |
| |
| failed_reg_panel: |
| hdmi_tx_dev_deinit(hdmi_ctrl); |
| failed_dev_init: |
| hdmi_tx_deinit_resource(hdmi_ctrl); |
| failed_res_init: |
| hdmi_tx_put_dt_data(&pdev->dev, &hdmi_ctrl->pdata); |
| failed_dt_data: |
| devm_kfree(&pdev->dev, hdmi_ctrl); |
| failed_no_mem: |
| return rc; |
| } /* hdmi_tx_probe */ |
| |
| static int __devexit hdmi_tx_remove(struct platform_device *pdev) |
| { |
| struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev); |
| if (!hdmi_ctrl) { |
| DEV_ERR("%s: no driver data\n", __func__); |
| return -ENODEV; |
| } |
| |
| hdmi_tx_sysfs_remove(hdmi_ctrl); |
| hdmi_tx_dev_deinit(hdmi_ctrl); |
| hdmi_tx_deinit_resource(hdmi_ctrl); |
| hdmi_tx_put_dt_data(&pdev->dev, &hdmi_ctrl->pdata); |
| devm_kfree(&hdmi_ctrl->pdev->dev, hdmi_ctrl); |
| |
| return 0; |
| } /* hdmi_tx_remove */ |
| |
| static const struct of_device_id hdmi_tx_dt_match[] = { |
| {.compatible = COMPATIBLE_NAME,}, |
| }; |
| MODULE_DEVICE_TABLE(of, hdmi_tx_dt_match); |
| |
| static struct platform_driver this_driver = { |
| .probe = hdmi_tx_probe, |
| .remove = hdmi_tx_remove, |
| .driver = { |
| .name = DRV_NAME, |
| .of_match_table = hdmi_tx_dt_match, |
| }, |
| }; |
| |
| static int __init hdmi_tx_drv_init(void) |
| { |
| int rc; |
| |
| rc = platform_driver_register(&this_driver); |
| if (rc) |
| DEV_ERR("%s: FAILED: rc=%d\n", __func__, rc); |
| |
| return rc; |
| } /* hdmi_tx_drv_init */ |
| |
| static void __exit hdmi_tx_drv_exit(void) |
| { |
| platform_driver_unregister(&this_driver); |
| } /* hdmi_tx_drv_exit */ |
| |
| static int set_hdcp_feature_on(const char *val, const struct kernel_param *kp) |
| { |
| int rc = 0; |
| |
| rc = param_set_bool(val, kp); |
| if (!rc) |
| pr_debug("%s: HDCP feature = %d\n", __func__, hdcp_feature_on); |
| |
| return rc; |
| } |
| |
| static struct kernel_param_ops hdcp_feature_on_param_ops = { |
| .set = set_hdcp_feature_on, |
| .get = param_get_bool, |
| }; |
| |
| module_param_cb(hdcp, &hdcp_feature_on_param_ops, &hdcp_feature_on, |
| S_IRUGO | S_IWUSR); |
| MODULE_PARM_DESC(hdcp, "Enable or Disable HDCP"); |
| |
| module_init(hdmi_tx_drv_init); |
| module_exit(hdmi_tx_drv_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_VERSION("0.3"); |
| MODULE_DESCRIPTION("HDMI MSM TX driver"); |