2322 lines
56 KiB
C
2322 lines
56 KiB
C
|
/*
|
||
|
* SiI8620 Linux Driver
|
||
|
*
|
||
|
* Copyright (C) 2013-2014 Silicon Image, Inc.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License as
|
||
|
* published by the Free Software Foundation version 2.
|
||
|
* This program is distributed AS-IS WITHOUT ANY WARRANTY of any
|
||
|
* kind, whether express or implied; INCLUDING without the implied warranty
|
||
|
* of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE or NON-INFRINGEMENT.
|
||
|
* See the GNU General Public License for more details at
|
||
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/moduleparam.h>
|
||
|
#include <linux/kernel.h>
|
||
|
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/semaphore.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/spi/spi.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
|
||
|
#include "si_fw_macros.h"
|
||
|
#include "si_infoframe.h"
|
||
|
#include "si_edid.h"
|
||
|
#include "si_mhl_defs.h"
|
||
|
#include "si_mhl2_edid_3d_api.h"
|
||
|
#include "si_mhl_tx_hw_drv_api.h"
|
||
|
#ifdef MEDIA_DATA_TUNNEL_SUPPORT
|
||
|
#include <linux/input.h>
|
||
|
#include "si_mdt_inputdev.h"
|
||
|
#endif
|
||
|
#include "mhl_linux_tx.h"
|
||
|
#include "mhl_supp.h"
|
||
|
#include "platform.h"
|
||
|
#include "si_mhl_callback_api.h"
|
||
|
#include "si_8620_drv.h"
|
||
|
#include "si_8620_regs.h"
|
||
|
|
||
|
#ifdef SIMG_USE_DTS
|
||
|
#include <linux/of_gpio.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Platform resources (I2C port, GPIOs, ...) needed to control the
|
||
|
* MHL starter kit. These default values are used to interface with
|
||
|
* PandaBoard which does not (currently) use device tree. Device tree
|
||
|
* capable systems should modify the device tree to declare the platform
|
||
|
* resources assigned to the MHL starter kit. If compiled with device tree
|
||
|
* support (-DSIMG_USE_DTS), this driver will override these default
|
||
|
* values with those from the device tree.
|
||
|
*/
|
||
|
#define GPIO_MHL_INT 138 /* W_INT */
|
||
|
#define GPIO_BB_RESET 140 /* P_BB_RST# */
|
||
|
#define I2C_ADAPTER 4
|
||
|
|
||
|
#define GPIO_EXP_ADDR 0x40
|
||
|
|
||
|
#define RESET_PULSE_WIDTH 1 /* In ms */
|
||
|
|
||
|
/*
|
||
|
* NOTE: The following GPIO expander register type address
|
||
|
* offsets are all defined with the address auto-increment
|
||
|
* bit set (0x80)
|
||
|
*/
|
||
|
#define GPIO_EXP_INPUT_REGS_OFFSET 0x80
|
||
|
#define GPIO_EXP_OUTPUT_REGS_OFFSET 0x88
|
||
|
#define GPIO_EXP_POL_INVERT_REGS_OFFSET 0x90
|
||
|
#define GPIO_EXP_IO_CONFIG_REGS_OFFSET 0x98
|
||
|
#define GPIO_EXP_INTR_MASK_REGS_OFFSET 0xA0
|
||
|
|
||
|
#define GPIO_EXP_BANK_2_OUTPUT_DEFAULT 0xFF
|
||
|
#define GPIO_EXP_BANK_2_3D (0x01 << 0)
|
||
|
#define GPIO_EXP_BANK_2_PKD_PXL (0x01 << 1)
|
||
|
#define GPIO_EXP_BANK_2_HDCP_ON (0x01 << 2)
|
||
|
#define GPIO_EXP_BANK_2_N_TCODE (0x01 << 3)
|
||
|
#define GPIO_EXP_BANK_2_LED_USB_MODE (0x01 << 4)
|
||
|
#define GPIO_EXP_BANK_2_SPR_LED2 (0x01 << 5)
|
||
|
#define GPIO_EXP_BANK_2_SPR_LED3 (0x01 << 6)
|
||
|
#define GPIO_EXP_BANK_2_SPR_LED4 (0x01 << 7)
|
||
|
|
||
|
#define GPIO_EXP_BANK_3_OUTPUT_DEFAULT 0x67
|
||
|
|
||
|
#define GPIO_EXP_BANK_3_MHL_TX_RST_B (0x01 << 0)
|
||
|
#define GPIO_EXP_BANK_3_FW_WAKE_A (0x01 << 1)
|
||
|
#define GPIO_EXP_BANK_3_CHG_DET (0x01 << 2)
|
||
|
#define GPIO_EXP_BANK_3_XO3_SINK_VBUS_SENSE (0x01 << 3)
|
||
|
#define GPIO_EXP_BANK_3_12V_PS_SENSE (0x01 << 4)
|
||
|
#define GPIO_EXP_BANK_3_EEPROM_WR_EN (0x01 << 5)
|
||
|
#define GPIO_EXP_BANK_3_TX2MHLRX_PWR_A (0x01 << 6)
|
||
|
#define GPIO_EXP_BANK_3_M2U_VBUS_CTRL_A (0x01 << 7)
|
||
|
|
||
|
#define GPIO_EXP_BANK_4_OUTPUT_DEFAULT 0xF0
|
||
|
#define GPIO_EXP_BANK_4_DSW9 (0x01 << 0)
|
||
|
#define GPIO_EXP_BANK_4_DSW10 (0x01 << 1)
|
||
|
#define GPIO_EXP_BANK_4_DSW11 (0x01 << 2)
|
||
|
#define GPIO_EXP_BANK_4_DSW12 (0x01 << 3)
|
||
|
#define GPIO_EXP_BANK_4_USB_SW_CTRL0 (0x01 << 4)
|
||
|
#define GPIO_EXP_BANK_4_USB_SW_CTRL1 (0x01 << 5)
|
||
|
#define GPIO_EXP_BANK_4_LED15_AMBER (0x01 << 6)
|
||
|
#define GPIO_EXP_BANK_4_LED15_GREEN (0x01 << 7)
|
||
|
|
||
|
#define GET_FROM_MODULE_PARAM -1
|
||
|
#define GPIO_ON_EXPANDER -2
|
||
|
|
||
|
#define REG_PCA_950x_PORT_0_INPUT 0x00
|
||
|
#define REG_PCA_950x_PORT_1_INPUT 0x01
|
||
|
#define REG_PCA_950x_PORT_2_INPUT 0x02
|
||
|
#define REG_PCA_950x_PORT_3_INPUT 0x03
|
||
|
#define REG_PCA_950x_PORT_4_INPUT 0x04
|
||
|
|
||
|
#define REG_PCA_950x_PORT_0_OUTPUT 0x08
|
||
|
#define REG_PCA_950x_PORT_1_OUTPUT 0x09
|
||
|
#define REG_PCA_950x_PORT_2_OUTPUT 0x0A
|
||
|
#define REG_PCA_950x_PORT_3_OUTPUT 0x0B
|
||
|
#define REG_PCA_950x_PORT_4_OUTPUT 0x0C
|
||
|
|
||
|
u8 gpio_exp_bank2_output;
|
||
|
u8 gpio_exp_bank3_output;
|
||
|
u8 gpio_exp_bank4_output;
|
||
|
|
||
|
static char *buildTime = "Built " __DATE__ "-" __TIME__;
|
||
|
static char *buildVersion = "1.03." BUILD_NUM_STRING;
|
||
|
|
||
|
struct semaphore platform_lock;
|
||
|
static uint32_t platform_flags;
|
||
|
bool probe_fail;
|
||
|
|
||
|
static struct spi_device *spi_dev;
|
||
|
#define SPI_BUS_NUM 1
|
||
|
#define SPI_CHIP_SEL 0
|
||
|
#define SPI_TRANSFER_MODE SPI_MODE_0
|
||
|
#define SPI_BUS_SPEED 1000000
|
||
|
|
||
|
enum si_spi_opcodes {
|
||
|
spi_op_disable = 0x04,
|
||
|
spi_op_enable = 0x06,
|
||
|
spi_op_clear_status = 0x07,
|
||
|
spi_op_reg_read = 0x60,
|
||
|
spi_op_reg_write = 0x61,
|
||
|
spi_op_ddc_reg_read = 0x62,
|
||
|
spi_op_emsc_read = 0x80,
|
||
|
spi_op_emsc_write = 0x81,
|
||
|
spi_op_slow_cbus_read = 0x90,
|
||
|
spi_op_slow_cbus_write = 0x91
|
||
|
};
|
||
|
|
||
|
#define MAX_SPI_PAYLOAD_SIZE LOCAL_BLK_RCV_BUFFER_SIZE
|
||
|
#define MAX_SPI_CMD_SIZE 3
|
||
|
#define EMSC_WRITE_SPI_CMD_SIZE 1
|
||
|
#define EMSC_READ_SPI_CMD_SIZE 1
|
||
|
#define MAX_SPI_DUMMY_XFER_BYTES 20
|
||
|
#define MAX_SPI_XFER_BUFFER_SIZE (MAX_SPI_CMD_SIZE + \
|
||
|
MAX_SPI_DUMMY_XFER_BYTES + MAX_SPI_PAYLOAD_SIZE)
|
||
|
#define MAX_SPI_EMSC_BLOCK_SIZE (MAX_SPI_CMD_SIZE + MAX_SPI_PAYLOAD_SIZE)
|
||
|
|
||
|
#define MAX_I2C_PAYLOAD_SIZE LOCAL_BLK_RCV_BUFFER_SIZE
|
||
|
#define MAX_I2C_CMD_SIZE 0
|
||
|
|
||
|
#define MAX_I2C_EMSC_BLOCK_SIZE (MAX_I2C_CMD_SIZE + MAX_I2C_PAYLOAD_SIZE)
|
||
|
|
||
|
struct spi_xfer_mem {
|
||
|
u8 *tx_buf;
|
||
|
u8 *rx_buf;
|
||
|
/* block commands are asynchronous to normal cbus traffic
|
||
|
and CANNOT share a buffer.
|
||
|
*/
|
||
|
uint8_t *block_tx_buffers;
|
||
|
struct spi_transfer spi_xfer[2];
|
||
|
struct spi_message spi_cmd;
|
||
|
} spi_mem;
|
||
|
|
||
|
struct i2c_xfer_mem {
|
||
|
uint8_t *block_tx_buffers;
|
||
|
} i2c_mem;
|
||
|
|
||
|
#ifdef MHL_GPIO_EXPANDER
|
||
|
static bool expander_enabled = true;
|
||
|
#else
|
||
|
static bool expander_enabled;
|
||
|
#endif
|
||
|
|
||
|
static int gpio_expander_transfer(u8 offset, u16 count, u8 *values, bool write);
|
||
|
|
||
|
#if defined(SIMG_USE_DTS)
|
||
|
static u32 i2c_adapter_num = I2C_ADAPTER;
|
||
|
#endif
|
||
|
static u32 spi_bus_num = SPI_BUS_NUM;
|
||
|
static struct i2c_adapter *i2c_bus_adapter;
|
||
|
|
||
|
struct i2c_dev_info {
|
||
|
uint8_t dev_addr;
|
||
|
struct i2c_client *client;
|
||
|
};
|
||
|
|
||
|
#define I2C_DEV_INFO(addr) \
|
||
|
{.dev_addr = addr >> 1, .client = NULL}
|
||
|
|
||
|
static struct i2c_dev_info device_addresses[] = {
|
||
|
I2C_DEV_INFO(SA_TX_PAGE_0),
|
||
|
I2C_DEV_INFO(SA_TX_PAGE_1),
|
||
|
I2C_DEV_INFO(SA_TX_PAGE_2),
|
||
|
I2C_DEV_INFO(SA_TX_PAGE_3),
|
||
|
I2C_DEV_INFO(SA_TX_PAGE_6),
|
||
|
I2C_DEV_INFO(SA_TX_CBUS),
|
||
|
I2C_DEV_INFO(GPIO_EXP_ADDR),
|
||
|
};
|
||
|
|
||
|
int debug_level;
|
||
|
bool debug_reg_dump;
|
||
|
bool input_dev_rap = 1;
|
||
|
bool input_dev_rcp = 1;
|
||
|
bool input_dev_ucp = 1;
|
||
|
#if (INCLUDE_RBP == 1)
|
||
|
bool input_dev_rbp = 1;
|
||
|
#endif
|
||
|
int hdcp_content_type;
|
||
|
bool use_spi = 0; /* Default to i2c (0). */
|
||
|
int crystal_khz = 19200; /* SiI8620 SK has 19.2MHz crystal */
|
||
|
int use_heartbeat;
|
||
|
|
||
|
bool wait_for_user_intr;
|
||
|
int tmds_link_speed;
|
||
|
#ifdef FORCE_OCBUS_FOR_ECTS
|
||
|
bool force_ocbus_for_ects;
|
||
|
#endif
|
||
|
int gpio_index = 138;
|
||
|
|
||
|
module_param(debug_reg_dump, bool, S_IRUGO);
|
||
|
module_param(debug_level, int, S_IRUGO);
|
||
|
|
||
|
module_param(input_dev_rap, bool, S_IRUGO);
|
||
|
module_param(input_dev_rcp, bool, S_IRUGO);
|
||
|
module_param(input_dev_ucp, bool, S_IRUGO);
|
||
|
#if (INCLUDE_RBP == 1)
|
||
|
module_param(input_dev_rbp, bool, S_IRUGO);
|
||
|
#endif
|
||
|
module_param(hdcp_content_type, int, S_IRUGO);
|
||
|
module_param(use_spi, bool, S_IRUGO);
|
||
|
module_param(crystal_khz, int, S_IRUGO);
|
||
|
module_param(use_heartbeat, int, S_IRUGO);
|
||
|
module_param(wait_for_user_intr, bool, S_IRUGO);
|
||
|
module_param(tmds_link_speed, int, S_IRUGO);
|
||
|
#ifdef FORCE_OCBUS_FOR_ECTS
|
||
|
module_param(force_ocbus_for_ects, bool, S_IRUGO);
|
||
|
#endif
|
||
|
|
||
|
module_param_named(debug_msgs, debug_level, int, S_IRUGO);
|
||
|
|
||
|
struct platform_signals_list platform_signals[] = {
|
||
|
{.name = "TX_HW_RESET",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_MHL_TX_RST_B,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "TX_FW_WAKE",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_FW_WAKE_A,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "CHG_DET",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_CHG_DET,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "XO3_SINK_VBUS_SENSE",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_INPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_XO3_SINK_VBUS_SENSE,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "TWELVE_VOLT_PS_SENSE",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_12V_PS_SENSE,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "EEPROM_WR_EN",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_EEPROM_WR_EN,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "TX2MHLRX_PWR",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_TX2MHLRX_PWR_A,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "M2U_VBUS_CTRL",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_3_M2U_VBUS_CTRL_A,
|
||
|
.gpio_bank_value = &gpio_exp_bank3_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_3D",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_3D,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_PACKED_PIXEL",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_PKD_PXL,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_HDCP",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_HDCP_ON,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_USB_MODE",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_LED_USB_MODE,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_SPARE_2",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED2,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_SPARE_3",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED3,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "LED_SPARE_4",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
|
||
|
.gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED4,
|
||
|
.gpio_bank_value = &gpio_exp_bank2_output,
|
||
|
.param = NULL},
|
||
|
{.name = "X02_USB_SW_CTRL",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
|
||
|
.gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL0 |
|
||
|
GPIO_EXP_BANK_4_USB_SW_CTRL1),
|
||
|
.gpio_bank_value = &gpio_exp_bank4_output,
|
||
|
.param = NULL},
|
||
|
{.name = "X02_USB_SW_CTRL0",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
|
||
|
.gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL0),
|
||
|
.gpio_bank_value = &gpio_exp_bank4_output,
|
||
|
.param = NULL},
|
||
|
{.name = "X02_USB_SW_CTRL1",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
|
||
|
.gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL1),
|
||
|
.gpio_bank_value = &gpio_exp_bank4_output,
|
||
|
.param = NULL},
|
||
|
{.name = "X02_LED15_AMBER",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
|
||
|
.gpio_mask_PCA950x = (GPIO_EXP_BANK_4_LED15_AMBER),
|
||
|
.gpio_bank_value = &gpio_exp_bank4_output,
|
||
|
.param = NULL},
|
||
|
{.name = "X02_LED15_GREEN",
|
||
|
.gpio_number = GPIO_ON_EXPANDER,
|
||
|
.gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
|
||
|
.gpio_mask_PCA950x = (GPIO_EXP_BANK_4_LED15_GREEN),
|
||
|
.gpio_bank_value = &gpio_exp_bank4_output,
|
||
|
.param = NULL}
|
||
|
};
|
||
|
|
||
|
#define MHL_INT_INDEX 0
|
||
|
#define MHL_RESET_INDEX 1
|
||
|
static struct gpio starter_kit_control_gpios[] = {
|
||
|
/*
|
||
|
* GPIO signals needed for the starter kit board.
|
||
|
*/
|
||
|
{GPIO_MHL_INT, GPIOF_IN, "MHL_intr"},
|
||
|
{GPIO_BB_RESET, GPIOF_OUT_INIT_HIGH, "MHL_reset"},
|
||
|
};
|
||
|
|
||
|
static inline int platform_read_i2c_block(struct i2c_adapter *i2c_bus, u8 page,
|
||
|
u8 offset, u16 count, u8 *values)
|
||
|
{
|
||
|
struct i2c_msg msg[2];
|
||
|
|
||
|
msg[0].flags = 0;
|
||
|
msg[0].addr = page >> 1;
|
||
|
msg[0].buf = &offset;
|
||
|
msg[0].len = 1;
|
||
|
|
||
|
msg[1].flags = I2C_M_RD;
|
||
|
msg[1].addr = page >> 1;
|
||
|
msg[1].buf = values;
|
||
|
msg[1].len = count;
|
||
|
|
||
|
return i2c_transfer(i2c_bus, msg, 2);
|
||
|
}
|
||
|
|
||
|
static inline int platform_write_i2c_block(struct i2c_adapter *i2c_bus, u8 page,
|
||
|
u8 offset, u16 count, u8 *values)
|
||
|
{
|
||
|
struct i2c_msg msg;
|
||
|
u8 *buffer;
|
||
|
int ret;
|
||
|
|
||
|
buffer = kmalloc(count + 1, GFP_KERNEL);
|
||
|
if (!buffer) {
|
||
|
printk(KERN_ERR "%s:%d buffer allocation failed\n",
|
||
|
__func__, __LINE__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
buffer[0] = offset;
|
||
|
memmove(&buffer[1], values, count);
|
||
|
|
||
|
msg.flags = 0;
|
||
|
msg.addr = page >> 1;
|
||
|
msg.buf = buffer;
|
||
|
msg.len = count + 1;
|
||
|
|
||
|
ret = i2c_transfer(i2c_bus, &msg, 1);
|
||
|
|
||
|
kfree(buffer);
|
||
|
|
||
|
if (ret != 1) {
|
||
|
printk(KERN_ERR "%s:%d I2c write failed 0x%02x:0x%02x\n",
|
||
|
__func__, __LINE__, page, offset);
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int platform_read_i2c_reg(struct i2c_adapter *bus_adapter_i2c, u8 page,
|
||
|
u8 offset)
|
||
|
{
|
||
|
int ret;
|
||
|
u8 byte_read;
|
||
|
|
||
|
ret = platform_read_i2c_block(bus_adapter_i2c, page, offset, 1,
|
||
|
&byte_read);
|
||
|
MHL_TX_DBG_INFO("\tGI2C_R %2x:%2x = %2x\n", page, offset, ret);
|
||
|
if (ret != 2) {
|
||
|
printk(KERN_ERR "%s:%d I2c read failed, 0x%02x:0x%02x\n",
|
||
|
__func__, __LINE__, page, offset);
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
}
|
||
|
return ret ? ret : byte_read;
|
||
|
}
|
||
|
|
||
|
static int platform_write_i2c_reg(struct i2c_adapter *bus_adapter_i2c, u8 page,
|
||
|
u8 offset, u8 value)
|
||
|
{
|
||
|
MHL_TX_DBG_INFO("\tGI2C_W %2x:%2x <- %2x\n", page, offset, value);
|
||
|
return platform_write_i2c_block(bus_adapter_i2c, page, offset, 1,
|
||
|
&value);
|
||
|
}
|
||
|
|
||
|
uint32_t platform_get_flags(void)
|
||
|
{
|
||
|
return platform_flags;
|
||
|
}
|
||
|
|
||
|
static void toggle_reset_n(void)
|
||
|
{
|
||
|
MHL_TX_DBG_INFO("Toggle MHL_RST_B/RESET_N pin. Resets 8620 only.\n");
|
||
|
gpio_exp_bank3_output &= ~GPIO_EXP_BANK_3_MHL_TX_RST_B;
|
||
|
gpio_expander_transfer(GPIO_EXP_OUTPUT_REGS_OFFSET + 3,
|
||
|
1, &gpio_exp_bank3_output, true);
|
||
|
|
||
|
/* Without this, we see a 500ns reset pulse. Enforce 1ms.*/
|
||
|
msleep(RESET_PULSE_WIDTH);
|
||
|
|
||
|
gpio_exp_bank3_output |= GPIO_EXP_BANK_3_MHL_TX_RST_B;
|
||
|
gpio_expander_transfer(GPIO_EXP_OUTPUT_REGS_OFFSET + 3,
|
||
|
1, &gpio_exp_bank3_output, true);
|
||
|
|
||
|
}
|
||
|
|
||
|
static void toggle_BB_RST(int reset_period)
|
||
|
{
|
||
|
MHL_TX_DBG_INFO("Toggle BB_RST# pin. Resets GPIO expander AND 8620\n");
|
||
|
gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio, 0);
|
||
|
msleep(reset_period);
|
||
|
gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio, 1);
|
||
|
|
||
|
}
|
||
|
|
||
|
int is_interrupt_asserted(void)
|
||
|
{
|
||
|
return gpio_get_value(starter_kit_control_gpios[MHL_INT_INDEX].gpio)
|
||
|
? 0 : 1;
|
||
|
}
|
||
|
|
||
|
int get_config(void *dev_context, int config_idx)
|
||
|
{
|
||
|
int pin_state = 0;
|
||
|
|
||
|
if (config_idx < ARRAY_SIZE(platform_signals)) {
|
||
|
switch (platform_signals[config_idx].gpio_number) {
|
||
|
case GET_FROM_MODULE_PARAM:
|
||
|
pin_state = *(platform_signals[config_idx].param);
|
||
|
break;
|
||
|
case GPIO_ON_EXPANDER:
|
||
|
pin_state =
|
||
|
(platform_read_i2c_reg
|
||
|
(i2c_bus_adapter,
|
||
|
platform_signals[config_idx].gpio_reg_PCA950x.
|
||
|
slave_addr,
|
||
|
platform_signals[config_idx].gpio_reg_PCA950x.
|
||
|
offset)
|
||
|
& platform_signals[config_idx].
|
||
|
gpio_mask_PCA950x) ? 1 : 0;
|
||
|
break;
|
||
|
default:
|
||
|
pin_state =
|
||
|
gpio_get_value(platform_signals[config_idx].
|
||
|
gpio_number);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return pin_state;
|
||
|
}
|
||
|
|
||
|
void set_pin_impl(int pin_idx, int value,
|
||
|
const char *function_name, int line_num)
|
||
|
{
|
||
|
uint8_t bank_value;
|
||
|
|
||
|
if (pin_idx < ARRAY_SIZE(platform_signals)) {
|
||
|
|
||
|
MHL_TX_DBG_INFO("set_pin(%s,%d)\n",
|
||
|
platform_signals[pin_idx].name, value);
|
||
|
switch (platform_signals[pin_idx].gpio_number) {
|
||
|
case GET_FROM_MODULE_PARAM:
|
||
|
break;
|
||
|
case GPIO_ON_EXPANDER:
|
||
|
if (expander_enabled) {
|
||
|
bank_value = *(platform_signals[pin_idx]
|
||
|
.gpio_bank_value);
|
||
|
if (value)
|
||
|
bank_value |= platform_signals[pin_idx].
|
||
|
gpio_mask_PCA950x;
|
||
|
else
|
||
|
bank_value &=
|
||
|
~platform_signals[pin_idx].
|
||
|
gpio_mask_PCA950x;
|
||
|
|
||
|
*(platform_signals[pin_idx].gpio_bank_value) =
|
||
|
bank_value;
|
||
|
platform_write_i2c_reg(i2c_bus_adapter,
|
||
|
platform_signals[pin_idx].
|
||
|
gpio_reg_PCA950x.slave_addr,
|
||
|
platform_signals[pin_idx].
|
||
|
gpio_reg_PCA950x.offset,
|
||
|
bank_value);
|
||
|
} else {
|
||
|
bank_value = 0;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
gpio_set_value(platform_signals[pin_idx].gpio_number,
|
||
|
value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void platform_mhl_tx_hw_reset(uint32_t reset_period, uint32_t reset_delay)
|
||
|
{
|
||
|
/* then reset the chip */
|
||
|
if (expander_enabled)
|
||
|
toggle_reset_n();
|
||
|
else
|
||
|
toggle_BB_RST(100);
|
||
|
|
||
|
if (reset_delay)
|
||
|
msleep(reset_delay);
|
||
|
|
||
|
if (use_spi) {
|
||
|
u8 cmd = spi_op_enable;
|
||
|
spi_write(spi_dev, &cmd, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mhl_tx_vbus_control(enum vbus_power_state power_state)
|
||
|
{
|
||
|
struct device *parent_dev;
|
||
|
struct mhl_dev_context *dev_context;
|
||
|
|
||
|
if (use_spi)
|
||
|
parent_dev = &spi_dev->dev;
|
||
|
else
|
||
|
parent_dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
dev_context = dev_get_drvdata(parent_dev);
|
||
|
|
||
|
switch (power_state) {
|
||
|
case VBUS_OFF:
|
||
|
set_pin(M2U_VBUS_CTRL, 0);
|
||
|
set_pin(TX2MHLRX_PWR, 1);
|
||
|
break;
|
||
|
|
||
|
case VBUS_ON:
|
||
|
set_pin(TX2MHLRX_PWR, 0);
|
||
|
set_pin(M2U_VBUS_CTRL, 1);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
dev_err(dev_context->mhl_dev,
|
||
|
"%s: Invalid power state %d received!\n",
|
||
|
__func__, power_state);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mhl_tx_vbus_current_ctl(uint16_t max_current_in_milliamps)
|
||
|
{
|
||
|
/*
|
||
|
Starter kit does not have a PMIC.
|
||
|
Implement VBUS input current limit control here.
|
||
|
*/
|
||
|
|
||
|
}
|
||
|
|
||
|
int si_device_dbg_i2c_reg_xfer(void *dev_context, u8 page, u8 offset,
|
||
|
u16 count, bool rw_flag, u8 *buffer)
|
||
|
{
|
||
|
u16 address = (page << 8) | offset;
|
||
|
|
||
|
if (rw_flag == DEBUG_I2C_WRITE)
|
||
|
return mhl_tx_write_reg_block(dev_context, address, count,
|
||
|
buffer);
|
||
|
else
|
||
|
return mhl_tx_read_reg_block(dev_context, address, count,
|
||
|
buffer);
|
||
|
}
|
||
|
|
||
|
#define MAX_DEBUG_MSG_SIZE 1024
|
||
|
|
||
|
#if defined(DEBUG)
|
||
|
|
||
|
/*
|
||
|
* Return a pointer to the file name part of the
|
||
|
* passed path spec string.
|
||
|
*/
|
||
|
char *find_file_name(const char *path_spec)
|
||
|
{
|
||
|
char *pc;
|
||
|
|
||
|
for (pc = (char *)&path_spec[strlen(path_spec)];
|
||
|
pc != path_spec; --pc) {
|
||
|
if ('\\' == *pc) {
|
||
|
++pc;
|
||
|
break;
|
||
|
}
|
||
|
if ('/' == *pc) {
|
||
|
++pc;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return pc;
|
||
|
}
|
||
|
|
||
|
void print_formatted_debug_msg(char *file_spec, const char *func_name,
|
||
|
int line_num, char *fmt, ...)
|
||
|
{
|
||
|
uint8_t *msg = NULL;
|
||
|
uint8_t *msg_offset;
|
||
|
char *file_spec_sep = NULL;
|
||
|
int remaining_msg_len = MAX_DEBUG_MSG_SIZE;
|
||
|
int len;
|
||
|
va_list ap;
|
||
|
|
||
|
if (fmt == NULL)
|
||
|
return;
|
||
|
|
||
|
msg = kmalloc(remaining_msg_len, GFP_KERNEL);
|
||
|
if (msg == NULL)
|
||
|
return;
|
||
|
|
||
|
msg_offset = msg;
|
||
|
|
||
|
len = scnprintf(msg_offset, remaining_msg_len, "mhl: ");
|
||
|
msg_offset += len;
|
||
|
remaining_msg_len -= len;
|
||
|
|
||
|
/* Only print the file name, not the path */
|
||
|
if (file_spec != NULL)
|
||
|
file_spec = find_file_name(file_spec);
|
||
|
|
||
|
if (file_spec != NULL) {
|
||
|
if (func_name != NULL)
|
||
|
file_spec_sep = "->";
|
||
|
else if (line_num != -1)
|
||
|
file_spec_sep = ":";
|
||
|
}
|
||
|
|
||
|
if (file_spec) {
|
||
|
len = scnprintf(msg_offset, remaining_msg_len, "%s", file_spec);
|
||
|
msg_offset += len;
|
||
|
remaining_msg_len -= len;
|
||
|
}
|
||
|
|
||
|
if (file_spec_sep) {
|
||
|
len =
|
||
|
scnprintf(msg_offset, remaining_msg_len, "%s",
|
||
|
file_spec_sep);
|
||
|
msg_offset += len;
|
||
|
remaining_msg_len -= len;
|
||
|
}
|
||
|
|
||
|
if (func_name) {
|
||
|
len = scnprintf(msg_offset, remaining_msg_len, "%s", func_name);
|
||
|
msg_offset += len;
|
||
|
remaining_msg_len -= len;
|
||
|
}
|
||
|
|
||
|
if (line_num != -1) {
|
||
|
if ((file_spec != NULL) || (func_name != NULL))
|
||
|
len =
|
||
|
scnprintf(msg_offset, remaining_msg_len, ":%d ",
|
||
|
line_num);
|
||
|
else
|
||
|
len =
|
||
|
scnprintf(msg_offset, remaining_msg_len, "%d ",
|
||
|
line_num);
|
||
|
|
||
|
msg_offset += len;
|
||
|
remaining_msg_len -= len;
|
||
|
}
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
len = vscnprintf(msg_offset, remaining_msg_len, fmt, ap);
|
||
|
va_end(ap);
|
||
|
|
||
|
printk(msg);
|
||
|
|
||
|
kfree(msg);
|
||
|
}
|
||
|
|
||
|
void dump_transfer(enum tx_interface_types if_type,
|
||
|
u8 page, u8 offset, u16 count, u8 *values, bool write)
|
||
|
{
|
||
|
if (debug_reg_dump != 0) {
|
||
|
int buf_size = 64;
|
||
|
u16 idx;
|
||
|
int buf_offset;
|
||
|
char *buf;
|
||
|
char *if_type_msg;
|
||
|
|
||
|
switch (if_type) {
|
||
|
case TX_INTERFACE_TYPE_I2C:
|
||
|
if_type_msg = "I2C";
|
||
|
break;
|
||
|
case TX_INTERFACE_TYPE_SPI:
|
||
|
if_type_msg = "SPI";
|
||
|
break;
|
||
|
default:
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
if (count > 1) {
|
||
|
/* 3 chars per byte displayed */
|
||
|
buf_size += count * 3;
|
||
|
/* plus per display row overhead */
|
||
|
buf_size += ((count / 16) + 1) * 8;
|
||
|
}
|
||
|
|
||
|
buf = kmalloc(buf_size, GFP_KERNEL);
|
||
|
if (!buf)
|
||
|
return;
|
||
|
|
||
|
if (count == 1) {
|
||
|
|
||
|
scnprintf(buf, buf_size, " %s %02X.%02X %s %02X\n",
|
||
|
if_type_msg,
|
||
|
page, offset, write ? "W" : "R", values[0]);
|
||
|
} else {
|
||
|
idx = 0;
|
||
|
buf_offset =
|
||
|
scnprintf(buf, buf_size, "%s %02X.%02X %s(%d)",
|
||
|
if_type_msg, page, offset,
|
||
|
write ? "W" : "R", count);
|
||
|
|
||
|
for (idx = 0; idx < count; idx++) {
|
||
|
if (0 == (idx & 0x0F))
|
||
|
buf_offset +=
|
||
|
scnprintf(&buf[buf_offset],
|
||
|
buf_size - buf_offset,
|
||
|
"\n%04X: ", idx);
|
||
|
|
||
|
buf_offset += scnprintf(&buf[buf_offset],
|
||
|
buf_size - buf_offset,
|
||
|
"%02X ", values[idx]);
|
||
|
}
|
||
|
buf_offset +=
|
||
|
scnprintf(&buf[buf_offset], buf_size - buf_offset,
|
||
|
"\n");
|
||
|
}
|
||
|
|
||
|
print_formatted_debug_msg(NULL, NULL, -1, buf);
|
||
|
kfree(buf);
|
||
|
}
|
||
|
}
|
||
|
#endif /* #if defined(DEBUG) */
|
||
|
|
||
|
static struct mhl_drv_info drv_info = {
|
||
|
.drv_context_size = sizeof(struct drv_hw_context),
|
||
|
.mhl_device_initialize = si_mhl_tx_chip_initialize,
|
||
|
.mhl_device_isr = si_mhl_tx_drv_device_isr,
|
||
|
.mhl_device_dbg_i2c_reg_xfer = si_device_dbg_i2c_reg_xfer,
|
||
|
.mhl_device_get_aksv = si_mhl_tx_drv_get_aksv
|
||
|
};
|
||
|
|
||
|
int mhl_tx_write_reg_block_i2c(void *drv_context, u8 page, u8 offset,
|
||
|
u16 count, u8 *values)
|
||
|
{
|
||
|
DUMP_I2C_TRANSFER(page, offset, count, values, true);
|
||
|
|
||
|
return platform_write_i2c_block(i2c_bus_adapter, page, offset, count,
|
||
|
values);
|
||
|
}
|
||
|
|
||
|
int mhl_tx_write_reg_i2c(void *drv_context, u8 page, u8 offset, u8 value)
|
||
|
{
|
||
|
return mhl_tx_write_reg_block_i2c(drv_context, page, offset, 1, &value);
|
||
|
}
|
||
|
|
||
|
int mhl_tx_read_reg_block_i2c(void *drv_context, u8 page, u8 offset,
|
||
|
u16 count, u8 *values)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
if (count == 0) {
|
||
|
MHL_TX_DBG_ERR("Tried to read 0 bytes\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = platform_read_i2c_block(i2c_bus_adapter, page, offset, count,
|
||
|
values);
|
||
|
if (ret != 2) {
|
||
|
MHL_TX_DBG_ERR("I2c read failed, 0x%02x:0x%02x\n", page,
|
||
|
offset);
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
DUMP_I2C_TRANSFER(page, offset, count, values, false);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int mhl_tx_read_reg_i2c(void *drv_context, u8 page, u8 offset)
|
||
|
{
|
||
|
u8 byte_read;
|
||
|
int status;
|
||
|
|
||
|
status = mhl_tx_read_reg_block_i2c(drv_context, page, offset,
|
||
|
1, &byte_read);
|
||
|
|
||
|
return status ? status : byte_read;
|
||
|
}
|
||
|
|
||
|
static int i2c_addr_to_spi_cmd(void *drv_context, bool write, u8 *page,
|
||
|
u8 *opcode, u8 *dummy_bytes)
|
||
|
{
|
||
|
if (write) {
|
||
|
*opcode = spi_op_reg_write;
|
||
|
*dummy_bytes = 0;
|
||
|
} else {
|
||
|
*opcode = spi_op_reg_read;
|
||
|
*dummy_bytes = 5;
|
||
|
}
|
||
|
|
||
|
switch (*page) {
|
||
|
case SA_TX_PAGE_0:
|
||
|
*page = 0;
|
||
|
break;
|
||
|
case SA_TX_PAGE_1:
|
||
|
*page = 1;
|
||
|
break;
|
||
|
case SA_TX_PAGE_2:
|
||
|
*page = 2;
|
||
|
break;
|
||
|
case SA_TX_PAGE_3:
|
||
|
*page = 3;
|
||
|
break;
|
||
|
case SA_TX_PAGE_4:
|
||
|
*page = 4;
|
||
|
break;
|
||
|
case SA_TX_CBUS:
|
||
|
*page = 5;
|
||
|
break;
|
||
|
case SA_TX_PAGE_6:
|
||
|
*page = 6;
|
||
|
break;
|
||
|
case SA_TX_PAGE_7:
|
||
|
*page = 7;
|
||
|
break;
|
||
|
case SA_TX_PAGE_8:
|
||
|
*page = 8;
|
||
|
break;
|
||
|
default:
|
||
|
MHL_TX_DBG_ERR("Called with unknown page 0x%02x\n", *page);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
inline uint8_t reg_page(uint16_t address)
|
||
|
{
|
||
|
return (uint8_t)((address >> 8) & 0x00FF);
|
||
|
}
|
||
|
|
||
|
inline uint8_t reg_offset(uint16_t address)
|
||
|
{
|
||
|
return (uint8_t)(address & 0x00FF);
|
||
|
}
|
||
|
|
||
|
static int mhl_tx_write_reg_block_spi(void *drv_context, u8 page, u8 offset,
|
||
|
u16 count, u8 *values)
|
||
|
{
|
||
|
u8 opcode;
|
||
|
u8 dummy_bytes;
|
||
|
u16 length = count + 3;
|
||
|
int ret;
|
||
|
|
||
|
DUMP_SPI_TRANSFER(page, offset, count, values, true);
|
||
|
|
||
|
ret = i2c_addr_to_spi_cmd(drv_context, true, &page, &opcode,
|
||
|
&dummy_bytes);
|
||
|
if (ret != 0)
|
||
|
return ret;
|
||
|
|
||
|
length = 3 + count + dummy_bytes;
|
||
|
|
||
|
if (length > MAX_SPI_XFER_BUFFER_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Transfer count (%d) is too large!\n", count);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
spi_mem.tx_buf[0] = opcode;
|
||
|
spi_mem.tx_buf[1] = page;
|
||
|
spi_mem.tx_buf[2] = offset;
|
||
|
if (dummy_bytes)
|
||
|
memset(&spi_mem.tx_buf[3], 0, dummy_bytes);
|
||
|
|
||
|
memmove(&spi_mem.tx_buf[dummy_bytes + 3], values, count);
|
||
|
|
||
|
ret = spi_write(spi_dev, spi_mem.tx_buf, length);
|
||
|
|
||
|
if (ret != 0) {
|
||
|
MHL_TX_DBG_ERR("SPI write block failed, "
|
||
|
"page: 0x%02x, register: 0x%02x\n",
|
||
|
page, offset);
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mhl_tx_write_reg_spi(void *drv_context, u8 page, u8 offset, u8 value)
|
||
|
{
|
||
|
return mhl_tx_write_reg_block_spi(drv_context, page, offset, 1, &value);
|
||
|
}
|
||
|
|
||
|
static int mhl_tx_read_reg_block_spi(void *drv_context, u8 page, u8 offset,
|
||
|
u16 count, u8 *values)
|
||
|
{
|
||
|
u8 page_num = page;
|
||
|
u8 opcode;
|
||
|
u8 dummy_bytes;
|
||
|
u16 length;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (count > MAX_SPI_PAYLOAD_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Requested transfer count (%d) is too large\n",
|
||
|
count);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = i2c_addr_to_spi_cmd(drv_context, false, &page_num, &opcode,
|
||
|
&dummy_bytes);
|
||
|
if (ret != 0)
|
||
|
return ret;
|
||
|
|
||
|
if ((reg_page(REG_DDC_DATA) == page) &&
|
||
|
(reg_offset(REG_DDC_DATA) == offset)) {
|
||
|
dummy_bytes = 11;
|
||
|
opcode = spi_op_ddc_reg_read;
|
||
|
}
|
||
|
|
||
|
length = 3 + count + dummy_bytes;
|
||
|
if (length > MAX_SPI_XFER_BUFFER_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Requested transfer total (%d) is too large\n",
|
||
|
length);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
spi_message_init(&spi_mem.spi_cmd);
|
||
|
memset(&spi_mem.spi_xfer, 0, sizeof(spi_mem.spi_xfer));
|
||
|
spi_mem.tx_buf[0] = opcode;
|
||
|
spi_mem.tx_buf[1] = page_num;
|
||
|
spi_mem.tx_buf[2] = offset;
|
||
|
|
||
|
spi_mem.spi_xfer[0].tx_buf = spi_mem.tx_buf;
|
||
|
spi_mem.spi_xfer[0].len = 3 + dummy_bytes;
|
||
|
#ifdef USE_SPIOPTIMIZE
|
||
|
memset(&spi_mem.tx_buf[3], 0, dummy_bytes + count);
|
||
|
spi_mem.spi_xfer[0].len += count;
|
||
|
|
||
|
spi_mem.spi_xfer[0].rx_buf = spi_mem.rx_buf;
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
|
||
|
ret = spi_sync(spi_dev, &spi_mem.spi_cmd);
|
||
|
#else
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
|
||
|
|
||
|
spi_mem.spi_xfer[1].rx_buf = spi_mem.rx_buf;
|
||
|
spi_mem.spi_xfer[1].len = count;
|
||
|
spi_mem.spi_xfer[1].cs_change = 1;
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[1], &spi_mem.spi_cmd);
|
||
|
|
||
|
ret = spi_sync(spi_dev, &spi_mem.spi_cmd);
|
||
|
#endif
|
||
|
|
||
|
if (ret != 0) {
|
||
|
MHL_TX_DBG_ERR("SPI read block failed, "
|
||
|
"page: 0x%02x, register: 0x%02x\n",
|
||
|
page, offset);
|
||
|
} else {
|
||
|
#ifdef USE_SPIOPTIMIZE
|
||
|
memcpy(values, &spi_mem.rx_buf[3 + dummy_bytes], count);
|
||
|
#else
|
||
|
memcpy(values, spi_mem.rx_buf, count);
|
||
|
#endif
|
||
|
DUMP_SPI_TRANSFER(page, offset, count, values, false);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mhl_tx_read_reg_block_spi_emsc(void *drv_context, u16 count,
|
||
|
u8 *values)
|
||
|
{
|
||
|
u8 dummy_bytes = 1;
|
||
|
u16 length;
|
||
|
int ret;
|
||
|
|
||
|
if (count > MAX_SPI_PAYLOAD_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Requested transfer count (%d) is too large\n",
|
||
|
count);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
length = EMSC_READ_SPI_CMD_SIZE + dummy_bytes + count;
|
||
|
if (length > MAX_SPI_XFER_BUFFER_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Requested transfer total (%d) is too large\n",
|
||
|
length);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
spi_message_init(&spi_mem.spi_cmd);
|
||
|
memset(&spi_mem.spi_xfer, 0, sizeof(spi_mem.spi_xfer));
|
||
|
spi_mem.tx_buf[0] = spi_op_emsc_read;
|
||
|
|
||
|
spi_mem.spi_xfer[0].tx_buf = spi_mem.tx_buf;
|
||
|
spi_mem.spi_xfer[0].len = EMSC_READ_SPI_CMD_SIZE + dummy_bytes;
|
||
|
|
||
|
#ifdef USE_SPIOPTIMIZE
|
||
|
memset(&spi_mem.tx_buf[EMSC_READ_SPI_CMD_SIZE], 0, dummy_bytes + count);
|
||
|
spi_mem.spi_xfer[0].rx_buf = spi_mem.rx_buf;
|
||
|
spi_mem.spi_xfer[0].len += count;
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
|
||
|
#else
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
|
||
|
|
||
|
spi_mem.spi_xfer[1].rx_buf = spi_mem.rx_buf;
|
||
|
spi_mem.spi_xfer[1].len = count;
|
||
|
spi_mem.spi_xfer[1].cs_change = 1;
|
||
|
spi_message_add_tail(&spi_mem.spi_xfer[1], &spi_mem.spi_cmd);
|
||
|
#endif
|
||
|
ret = spi_sync(spi_dev, &spi_mem.spi_cmd);
|
||
|
|
||
|
if (ret != 0) {
|
||
|
MHL_TX_DBG_ERR("SPI eMSC read block failed ");
|
||
|
} else {
|
||
|
#ifdef USE_SPIOPTIMIZE
|
||
|
memcpy(values,
|
||
|
&spi_mem.rx_buf[EMSC_READ_SPI_CMD_SIZE + dummy_bytes],
|
||
|
count);
|
||
|
#else
|
||
|
memcpy(values, spi_mem.rx_buf, count);
|
||
|
#endif
|
||
|
/* DUMP_SPI_TRANSFER(page, offset, count, values, false); */
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int mhl_tx_read_spi_emsc(void *drv_context, u16 count, u8 *values)
|
||
|
{
|
||
|
u8 *dptr;
|
||
|
u16 length, block;
|
||
|
int ret = 0;
|
||
|
|
||
|
/*
|
||
|
* Don't read more than 128 bytes at a time to reduce
|
||
|
* possible problems with SPI hardware.
|
||
|
*/
|
||
|
dptr = values;
|
||
|
length = count;
|
||
|
while (length > 0) {
|
||
|
block = (length < 128) ? length : 128;
|
||
|
ret = mhl_tx_read_reg_block_spi_emsc(drv_context, block, dptr);
|
||
|
if (ret != 0)
|
||
|
break;
|
||
|
length -= block;
|
||
|
dptr += block;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mhl_tx_read_reg_spi(void *drv_context, u8 page, u8 offset)
|
||
|
{
|
||
|
u8 byte_read = 0;
|
||
|
int status;
|
||
|
|
||
|
status = mhl_tx_read_reg_block_spi(drv_context, page, offset, 1,
|
||
|
&byte_read);
|
||
|
|
||
|
return status ? status : byte_read;
|
||
|
}
|
||
|
|
||
|
void mhl_tx_clear_emsc_read_err(void *drv_context)
|
||
|
{
|
||
|
|
||
|
if (use_spi) {
|
||
|
spi_mem.tx_buf[0] = spi_op_clear_status;
|
||
|
spi_mem.tx_buf[1] = 0x02;
|
||
|
spi_write(spi_dev, spi_mem.tx_buf, 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mhl_tx_write_reg_block(void *drv_context, u16 address, u16 count,
|
||
|
u8 *values)
|
||
|
{
|
||
|
u8 page = (u8)(address >> 8);
|
||
|
u8 offset = (u8)address;
|
||
|
|
||
|
if (use_spi)
|
||
|
return mhl_tx_write_reg_block_spi(drv_context, page, offset,
|
||
|
count, values);
|
||
|
else
|
||
|
return mhl_tx_write_reg_block_i2c(drv_context, page, offset,
|
||
|
count, values);
|
||
|
}
|
||
|
|
||
|
void si_mhl_tx_platform_get_block_buffer_info(struct block_buffer_info_t
|
||
|
*block_buffer_info)
|
||
|
{
|
||
|
if (use_spi) {
|
||
|
block_buffer_info->buffer = spi_mem.block_tx_buffers;
|
||
|
block_buffer_info->req_size = MAX_SPI_EMSC_BLOCK_SIZE;
|
||
|
block_buffer_info->payload_offset = EMSC_WRITE_SPI_CMD_SIZE;
|
||
|
} else {
|
||
|
block_buffer_info->buffer = i2c_mem.block_tx_buffers;
|
||
|
block_buffer_info->req_size = MAX_I2C_EMSC_BLOCK_SIZE;
|
||
|
block_buffer_info->payload_offset = MAX_I2C_CMD_SIZE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mhl_tx_write_block_spi_emsc(void *drv_context, struct block_req *req)
|
||
|
{
|
||
|
u16 length;
|
||
|
int ret;
|
||
|
|
||
|
/* DUMP_SPI_TRANSFER(page, offset, req->count, req->payload->as_bytes,
|
||
|
* true);
|
||
|
*/
|
||
|
|
||
|
/* dummy bytes will always be zero */
|
||
|
length = EMSC_WRITE_SPI_CMD_SIZE + req->count;
|
||
|
|
||
|
if (length > MAX_SPI_EMSC_BLOCK_SIZE) {
|
||
|
MHL_TX_DBG_ERR("Transfer count (%d) is too large!\n",
|
||
|
req->count);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
req->platform_header[0] = spi_op_emsc_write;
|
||
|
|
||
|
ret = spi_write(spi_dev, req->platform_header, length);
|
||
|
|
||
|
if (ret != 0) {
|
||
|
MHL_TX_DBG_ERR("SPI write block failed\n");
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int mhl_tx_write_reg(void *drv_context, u16 address, u8 value)
|
||
|
{
|
||
|
u8 page = (u8)(address >> 8);
|
||
|
u8 offset = (u8)address;
|
||
|
|
||
|
if (use_spi)
|
||
|
return mhl_tx_write_reg_spi(drv_context, page, offset, value);
|
||
|
else
|
||
|
return mhl_tx_write_reg_i2c(drv_context, page, offset, value);
|
||
|
}
|
||
|
|
||
|
int mhl_tx_read_reg_block(void *drv_context, u16 address, u16 count, u8 *values)
|
||
|
{
|
||
|
u8 page = (u8)(address >> 8);
|
||
|
u8 offset = (u8)address;
|
||
|
|
||
|
if (use_spi)
|
||
|
return mhl_tx_read_reg_block_spi(drv_context, page, offset,
|
||
|
count, values);
|
||
|
else
|
||
|
return mhl_tx_read_reg_block_i2c(drv_context, page, offset,
|
||
|
count, values);
|
||
|
}
|
||
|
|
||
|
int mhl_tx_read_reg(void *drv_context, u16 address)
|
||
|
{
|
||
|
u8 page = (u8)(address >> 8);
|
||
|
u8 offset = (u8)address;
|
||
|
|
||
|
if (use_spi)
|
||
|
return mhl_tx_read_reg_spi(drv_context, page, offset);
|
||
|
else
|
||
|
return mhl_tx_read_reg_i2c(drv_context, page, offset);
|
||
|
}
|
||
|
|
||
|
int mhl_tx_modify_reg(void *drv_context, u16 address, u8 mask, u8 value)
|
||
|
{
|
||
|
int reg_value;
|
||
|
int write_status;
|
||
|
|
||
|
reg_value = mhl_tx_read_reg(drv_context, address);
|
||
|
if (reg_value < 0)
|
||
|
return reg_value;
|
||
|
|
||
|
reg_value &= ~mask;
|
||
|
reg_value |= mask & value;
|
||
|
|
||
|
write_status = mhl_tx_write_reg(drv_context, address, reg_value);
|
||
|
|
||
|
if (write_status < 0)
|
||
|
return write_status;
|
||
|
else
|
||
|
return reg_value;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Return a value indicating how upstream HPD is
|
||
|
* implemented on this platform.
|
||
|
*/
|
||
|
enum hpd_control_mode platform_get_hpd_control_mode(void)
|
||
|
{
|
||
|
return HPD_CTRL_PUSH_PULL;
|
||
|
}
|
||
|
|
||
|
static int gpio_expander_transfer(u8 offset, u16 count, u8 *values, bool write)
|
||
|
{
|
||
|
struct i2c_msg msg[2];
|
||
|
u8 buf[8];
|
||
|
int msg_count;
|
||
|
int ret;
|
||
|
|
||
|
if ((count + 1) > ARRAY_SIZE(buf))
|
||
|
return -1;
|
||
|
|
||
|
if (write) {
|
||
|
buf[0] = offset;
|
||
|
memmove(&buf[1], values, count);
|
||
|
|
||
|
msg[0].flags = 0;
|
||
|
msg[0].addr = GPIO_EXP_ADDR >> 1;
|
||
|
msg[0].buf = buf;
|
||
|
msg[0].len = count + 1;
|
||
|
|
||
|
msg_count = 1;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
msg[0].flags = 0;
|
||
|
msg[0].addr = GPIO_EXP_ADDR >> 1;
|
||
|
msg[0].buf = &offset;
|
||
|
msg[0].len = 1;
|
||
|
|
||
|
msg[1].flags = I2C_M_RD;
|
||
|
msg[1].addr = GPIO_EXP_ADDR >> 1;
|
||
|
msg[1].buf = values;
|
||
|
msg[1].len = count;
|
||
|
msg_count = 2;
|
||
|
}
|
||
|
|
||
|
ret = i2c_transfer(i2c_bus_adapter, msg, msg_count);
|
||
|
if (ret != msg_count) {
|
||
|
MHL_TX_DBG_ERR("I2c %s failed ret:%d, page: 0x%02x, "
|
||
|
"register: 0x%02x count:0x%x\n",
|
||
|
write ? "write" : "read", ret, GPIO_EXP_ADDR,
|
||
|
offset, count);
|
||
|
ret = -EIO;
|
||
|
} else {
|
||
|
ret = 0;
|
||
|
/*DUMP_I2C_TRANSFER(GPIO_EXP_ADDR, offset, count, values,
|
||
|
* write);
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int gpio_expander_init(void)
|
||
|
{
|
||
|
u8 gpio_exp_mask_init[] = { GPIO_EXP_INTR_MASK_REGS_OFFSET,
|
||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||
|
};
|
||
|
u8 gpio_exp_pol_invert_init[] = { GPIO_EXP_POL_INVERT_REGS_OFFSET,
|
||
|
0x00, 0x00, 0x00, 0x00, 0x00
|
||
|
};
|
||
|
u8 gpio_exp_output_init[] = { GPIO_EXP_OUTPUT_REGS_OFFSET, 0x00, 0x00,
|
||
|
GPIO_EXP_BANK_2_OUTPUT_DEFAULT,
|
||
|
GPIO_EXP_BANK_3_OUTPUT_DEFAULT,
|
||
|
GPIO_EXP_BANK_4_OUTPUT_DEFAULT
|
||
|
};
|
||
|
u8 gpio_exp_io_config_init[] = { GPIO_EXP_IO_CONFIG_REGS_OFFSET,
|
||
|
0xFF, 0xFF, 0x00, 0x18, 0x0F
|
||
|
};
|
||
|
int ret;
|
||
|
|
||
|
/* First reset GPIO Expander (and 8620) */
|
||
|
toggle_BB_RST(10);
|
||
|
|
||
|
ret = gpio_expander_transfer(gpio_exp_mask_init[0],
|
||
|
ARRAY_SIZE(gpio_exp_mask_init) - 1,
|
||
|
&gpio_exp_mask_init[1], true);
|
||
|
if (ret != 0) {
|
||
|
pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
|
||
|
__func__, __LINE__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = gpio_expander_transfer(gpio_exp_pol_invert_init[0],
|
||
|
ARRAY_SIZE(gpio_exp_pol_invert_init) - 1,
|
||
|
&gpio_exp_pol_invert_init[1], true);
|
||
|
if (ret != 0) {
|
||
|
pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
|
||
|
__func__, __LINE__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = gpio_expander_transfer(gpio_exp_output_init[0],
|
||
|
ARRAY_SIZE(gpio_exp_output_init) - 1,
|
||
|
&gpio_exp_output_init[1], true);
|
||
|
gpio_exp_bank2_output = GPIO_EXP_BANK_2_OUTPUT_DEFAULT;
|
||
|
gpio_exp_bank3_output = GPIO_EXP_BANK_3_OUTPUT_DEFAULT;
|
||
|
gpio_exp_bank4_output = GPIO_EXP_BANK_4_OUTPUT_DEFAULT;
|
||
|
|
||
|
if (ret != 0) {
|
||
|
pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
|
||
|
__func__, __LINE__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = gpio_expander_transfer(gpio_exp_io_config_init[0],
|
||
|
ARRAY_SIZE(gpio_exp_io_config_init) - 1,
|
||
|
&gpio_exp_io_config_init[1], true);
|
||
|
if (ret != 0) {
|
||
|
pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
|
||
|
__func__, __LINE__, ret);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
static int starter_kit_init(void)
|
||
|
#else
|
||
|
static int __devinit starter_kit_init(void)
|
||
|
#endif
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
/* Acquire the GPIO pins needed to control the starter kit. */
|
||
|
ret = gpio_request_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
if (ret < 0) {
|
||
|
pr_err("%s(): gpio_request_array failed, error code %d\n",
|
||
|
__func__, ret);
|
||
|
} else {
|
||
|
if (expander_enabled)
|
||
|
ret = gpio_expander_init();
|
||
|
else
|
||
|
toggle_BB_RST(100);
|
||
|
if (ret != 0) {
|
||
|
gpio_free_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if defined(SIMG_USE_DTS)
|
||
|
static int si_8620_parse_dt(struct device *dev)
|
||
|
{
|
||
|
struct device_node *np = dev->of_node;
|
||
|
int value;
|
||
|
int pmi_gpio9;
|
||
|
int pmi_gpio10;
|
||
|
int pm_gpio2;
|
||
|
int fw_wake;
|
||
|
int rc = 0;
|
||
|
struct pinctrl *pinctrl;
|
||
|
struct pinctrl_state *active_state;
|
||
|
struct regulator *ldo;
|
||
|
|
||
|
pinctrl = devm_pinctrl_get(dev);
|
||
|
if (IS_ERR_OR_NULL(pinctrl)) {
|
||
|
MHL_TX_DBG_ERR("%s: failed to get pinctrl\n", __func__);
|
||
|
rc = -ENODEV;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
active_state = pinctrl_lookup_state(pinctrl, "mhl_active");
|
||
|
if (IS_ERR_OR_NULL(active_state)) {
|
||
|
MHL_TX_DBG_ERR("%s: failed to find mhl_active state\n",
|
||
|
__func__);
|
||
|
rc = -ENODEV;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
if (pinctrl_select_state(pinctrl, active_state)) {
|
||
|
MHL_TX_DBG_ERR("%s: failed to select mhl active state\n",
|
||
|
__func__);
|
||
|
}
|
||
|
|
||
|
ldo = devm_regulator_get(dev, "ldo25");
|
||
|
if (IS_ERR_OR_NULL(ldo)) {
|
||
|
MHL_TX_DBG_ERR("%s: Unable to get regulator\n", __func__);
|
||
|
rc = -ENODEV;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
rc = regulator_set_voltage(ldo, 1000000, 1000000);
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s: Unable to set regulator voltage\n",
|
||
|
__func__);
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
rc = regulator_enable(ldo);
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s: Regulator enable failed\n", __func__);
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
pm_gpio2 = of_get_named_gpio(np, "sil,pwr-gpio", 0);
|
||
|
pmi_gpio9 = of_get_named_gpio(np, "sil,pmi9", 0);
|
||
|
pmi_gpio10 = of_get_named_gpio(np, "sil,pmi10", 0);
|
||
|
fw_wake = of_get_named_gpio(np, "sil,fw-wake", 0);
|
||
|
|
||
|
if (gpio_is_valid(pmi_gpio9)) {
|
||
|
rc = gpio_request(pmi_gpio9, "mhl_pmi9");
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
|
||
|
__func__, pmi_gpio9);
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
gpio_direction_output(pmi_gpio9, 0);
|
||
|
gpio_set_value(pmi_gpio9, 1);
|
||
|
} else {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
|
||
|
__func__, pmi_gpio9);
|
||
|
rc = pmi_gpio9;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
if (gpio_is_valid(pmi_gpio10)) {
|
||
|
rc = gpio_request(pmi_gpio10, "mhl_pmi10");
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
|
||
|
__func__, pmi_gpio10);
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
gpio_direction_output(pmi_gpio10, 0);
|
||
|
gpio_set_value(pmi_gpio10, 1);
|
||
|
} else {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
|
||
|
__func__, pmi_gpio10);
|
||
|
rc = pmi_gpio10;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
if (gpio_is_valid(pm_gpio2)) {
|
||
|
rc = gpio_request(pm_gpio2, "mhl_pm2");
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
|
||
|
__func__, pm_gpio2);
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
gpio_direction_output(pm_gpio2, 0);
|
||
|
gpio_set_value(pm_gpio2, 0);
|
||
|
} else {
|
||
|
MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
|
||
|
__func__, pm_gpio2);
|
||
|
rc = pm_gpio2;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
if (gpio_is_valid(fw_wake)) {
|
||
|
rc = gpio_request(fw_wake, "mhl_fw");
|
||
|
if (rc) {
|
||
|
MHL_TX_DBG_ERR("%s:wake_gpio=[%d] req failed:\n",
|
||
|
__func__, fw_wake);
|
||
|
return rc;
|
||
|
}
|
||
|
gpio_direction_output(fw_wake, 0);
|
||
|
gpio_set_value(fw_wake, 1);
|
||
|
platform_signals[TX_FW_WAKE].gpio_number = fw_wake;
|
||
|
} else {
|
||
|
MHL_TX_DBG_ERR("%s:wake_gpio=[%d] invalid\n",
|
||
|
__func__, fw_wake);
|
||
|
rc = fw_wake;
|
||
|
goto dt_exit;
|
||
|
}
|
||
|
|
||
|
value = of_get_named_gpio_flags(np, "sil,reset-gpio", 0, NULL);
|
||
|
if (value >= 0) {
|
||
|
starter_kit_control_gpios[MHL_RESET_INDEX].gpio = value;
|
||
|
gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio,
|
||
|
1);
|
||
|
}
|
||
|
|
||
|
value = of_get_named_gpio_flags(np, "sil,irq-gpio", 0, NULL);
|
||
|
if (value >= 0)
|
||
|
starter_kit_control_gpios[MHL_INT_INDEX].gpio = value;
|
||
|
|
||
|
/*
|
||
|
* Need this for I/O expander in case we're using SPI as
|
||
|
* the register I/O.
|
||
|
*/
|
||
|
if (!of_property_read_u32(np, "sil,i2c_port#", &value))
|
||
|
i2c_adapter_num = value;
|
||
|
|
||
|
MHL_TX_DBG_INFO("Resources assigned to driver...\n");
|
||
|
MHL_TX_DBG_INFO(" Reset GPIO = %d\n",
|
||
|
starter_kit_control_gpios[MHL_RESET_INDEX].gpio);
|
||
|
MHL_TX_DBG_INFO(" Interrupt GPIO = %d\n",
|
||
|
starter_kit_control_gpios[MHL_INT_INDEX].gpio);
|
||
|
MHL_TX_DBG_INFO(" I2C adapter = %d\n", i2c_adapter_num);
|
||
|
if (use_spi)
|
||
|
MHL_TX_DBG_ERR(" SPI adapter = %d\n", spi_bus_num);
|
||
|
|
||
|
dt_exit:
|
||
|
return rc;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
static int si_8620_mhl_tx_i2c_probe(struct i2c_client *client,
|
||
|
#else
|
||
|
static int __devinit si_8620_mhl_tx_i2c_probe(struct i2c_client *client,
|
||
|
#endif
|
||
|
const struct i2c_device_id *id)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
pr_info("%s(), i2c_device_id = %p\n", __func__, id);
|
||
|
|
||
|
#if defined(SIMG_USE_DTS)
|
||
|
/*
|
||
|
* Modify default driver platform parameters with those
|
||
|
* specified in the device tree.
|
||
|
*/
|
||
|
if (client->dev.of_node) {
|
||
|
ret = si_8620_parse_dt(&client->dev);
|
||
|
if (ret)
|
||
|
goto done;
|
||
|
}
|
||
|
#endif
|
||
|
i2c_bus_adapter = to_i2c_adapter(client->dev.parent);
|
||
|
device_addresses[0].client = client;
|
||
|
|
||
|
if (!i2c_bus_adapter ||
|
||
|
!i2c_check_functionality(i2c_bus_adapter,
|
||
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||
|
MHL_TX_DBG_ERR("[ERROR] i2c function check failed\n");
|
||
|
ret = -EIO;
|
||
|
goto done;
|
||
|
}
|
||
|
i2c_mem.block_tx_buffers = kmalloc(MAX_I2C_EMSC_BLOCK_SIZE *
|
||
|
NUM_BLOCK_QUEUE_REQUESTS, GFP_KERNEL);
|
||
|
if (NULL == i2c_mem.block_tx_buffers) {
|
||
|
ret = -ENOMEM;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
ret = starter_kit_init();
|
||
|
if (ret >= 0) {
|
||
|
drv_info.irq = gpio_to_irq(
|
||
|
starter_kit_control_gpios[MHL_INT_INDEX].gpio);
|
||
|
ret = mhl_tx_init(&drv_info, &client->dev);
|
||
|
if (ret) {
|
||
|
MHL_TX_DBG_ERR("mhl_tx_init failed, error code %d\n",
|
||
|
ret);
|
||
|
gpio_free_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
}
|
||
|
}
|
||
|
done:
|
||
|
if (ret != 0) {
|
||
|
kfree(i2c_mem.block_tx_buffers);
|
||
|
probe_fail = true;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
static int si_8620_mhl_tx_remove(struct i2c_client *client)
|
||
|
#else
|
||
|
static int __devexit si_8620_mhl_tx_remove(struct i2c_client *client)
|
||
|
#endif
|
||
|
{
|
||
|
if (!use_spi)
|
||
|
kfree(i2c_mem.block_tx_buffers);
|
||
|
|
||
|
gpio_free_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int si_8620_pm_suspend(struct device *dev)
|
||
|
{
|
||
|
int status = -EINVAL;
|
||
|
|
||
|
if (dev == 0)
|
||
|
goto done;
|
||
|
|
||
|
status = down_interruptible(&platform_lock);
|
||
|
if (status)
|
||
|
goto done;
|
||
|
|
||
|
status = mhl_handle_power_change_request(dev, false);
|
||
|
/*
|
||
|
* Set MHL/USB switch to USB
|
||
|
* NOTE: Switch control is implemented differently on each
|
||
|
* version of the starter kit.
|
||
|
*/
|
||
|
set_pin(X02_USB_SW_CTRL, 0);
|
||
|
|
||
|
up(&platform_lock);
|
||
|
done:
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
int si_8620_pm_resume(struct device *dev)
|
||
|
{
|
||
|
int status = -EINVAL;
|
||
|
|
||
|
if (dev == 0)
|
||
|
goto done;
|
||
|
|
||
|
status = down_interruptible(&platform_lock);
|
||
|
if (status)
|
||
|
goto done;
|
||
|
|
||
|
set_pin(X02_USB_SW_CTRL, 1);
|
||
|
status = mhl_handle_power_change_request(dev, true);
|
||
|
|
||
|
up(&platform_lock);
|
||
|
done:
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
int si_8620_power_control(bool power_up)
|
||
|
{
|
||
|
struct device *dev = NULL;
|
||
|
int status;
|
||
|
|
||
|
if (use_spi)
|
||
|
dev = &spi_dev->dev;
|
||
|
else
|
||
|
dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
if (power_up)
|
||
|
status = si_8620_pm_resume(dev);
|
||
|
else
|
||
|
status = si_8620_pm_suspend(dev);
|
||
|
return status;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(si_8620_power_control);
|
||
|
|
||
|
int si_8620_get_hpd_status(int *hpd_status)
|
||
|
{
|
||
|
struct device *dev = NULL;
|
||
|
int status = 0;
|
||
|
struct mhl_dev_context *dev_context;
|
||
|
|
||
|
if (use_spi)
|
||
|
dev = &spi_dev->dev;
|
||
|
else
|
||
|
dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
dev_context = dev_get_drvdata(dev);
|
||
|
|
||
|
if (down_interruptible(&dev_context->isr_lock)) {
|
||
|
MHL_TX_DBG_ERR("%scouldn't acquire mutex%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
return -ERESTARTSYS;
|
||
|
}
|
||
|
|
||
|
if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
|
||
|
MHL_TX_DBG_ERR("%sshutting down%s\n",
|
||
|
ANSI_ESC_YELLOW_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
status = -ENODEV;
|
||
|
} else {
|
||
|
*hpd_status = si_mhl_tx_drv_get_hpd_status(dev_context);
|
||
|
MHL_TX_DBG_INFO("%HPD status: %s%d%s\n",
|
||
|
ANSI_ESC_YELLOW_TEXT,
|
||
|
*hpd_status,
|
||
|
ANSI_ESC_RESET_TEXT);
|
||
|
}
|
||
|
up(&dev_context->isr_lock);
|
||
|
return status;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(si_8620_get_hpd_status);
|
||
|
|
||
|
int si_8620_get_hdcp2_status(uint32_t *hdcp2_status)
|
||
|
{
|
||
|
struct device *dev = NULL;
|
||
|
int status = 0;
|
||
|
struct mhl_dev_context *dev_context;
|
||
|
|
||
|
if (use_spi)
|
||
|
dev = &spi_dev->dev;
|
||
|
else
|
||
|
dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
dev_context = dev_get_drvdata(dev);
|
||
|
|
||
|
if (down_interruptible(&dev_context->isr_lock)) {
|
||
|
MHL_TX_DBG_ERR("%scouldn't acquire mutex%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
return -ERESTARTSYS;
|
||
|
}
|
||
|
|
||
|
if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
|
||
|
MHL_TX_DBG_ERR("%sshutting down%s\n",
|
||
|
ANSI_ESC_YELLOW_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
status = -ENODEV;
|
||
|
} else {
|
||
|
*hdcp2_status = si_mhl_tx_drv_get_hdcp2_status(dev_context);
|
||
|
MHL_TX_DBG_INFO("%HDCP2 status: %s0x%8x%s\n",
|
||
|
ANSI_ESC_YELLOW_TEXT,
|
||
|
*hdcp2_status,
|
||
|
ANSI_ESC_RESET_TEXT);
|
||
|
}
|
||
|
up(&dev_context->isr_lock);
|
||
|
return status;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(si_8620_get_hdcp2_status);
|
||
|
|
||
|
static const struct i2c_device_id si_8620_mhl_tx_id[] = {
|
||
|
{MHL_DEVICE_NAME, 0},
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(i2c, si_8620_mhl_tx_id);
|
||
|
|
||
|
static const struct dev_pm_ops si_8620_tx_pm_ops = {
|
||
|
.runtime_suspend = si_8620_pm_suspend,
|
||
|
.runtime_resume = si_8620_pm_resume,
|
||
|
};
|
||
|
|
||
|
#ifdef SIMG_USE_DTS
|
||
|
static struct of_device_id si_8620_of_match_table[] = {
|
||
|
{
|
||
|
.compatible = "sil,sii-8620",
|
||
|
},
|
||
|
{}
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
static struct i2c_driver si_8620_mhl_tx_i2c_driver = {
|
||
|
.driver = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.name = MHL_DRIVER_NAME,
|
||
|
#ifdef SIMG_USE_DTS
|
||
|
.of_match_table = si_8620_of_match_table,
|
||
|
#endif
|
||
|
.pm = &si_8620_tx_pm_ops,
|
||
|
},
|
||
|
.id_table = si_8620_mhl_tx_id,
|
||
|
.probe = si_8620_mhl_tx_i2c_probe,
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
.remove = si_8620_mhl_tx_remove,
|
||
|
#else
|
||
|
.remove = __devexit_p(si_8620_mhl_tx_remove),
|
||
|
#endif
|
||
|
.command = NULL,
|
||
|
};
|
||
|
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
static struct i2c_board_info __initdata si_8620_i2c_boardinfo[] = {
|
||
|
{
|
||
|
I2C_BOARD_INFO(MHL_DEVICE_NAME, (SA_TX_PAGE_0 >> 1)),
|
||
|
.flags = I2C_CLIENT_WAKE,
|
||
|
.irq = -1
|
||
|
}
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
static int si_8620_mhl_tx_spi_probe(struct spi_device *spi)
|
||
|
#else
|
||
|
static int __devinit si_8620_mhl_tx_spi_probe(struct spi_device *spi)
|
||
|
#endif
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
pr_info("%s(), spi = %p\n", __func__, spi);
|
||
|
spi->bits_per_word = 8;
|
||
|
spi_dev = spi;
|
||
|
spi_bus_num = spi->master->bus_num;
|
||
|
|
||
|
#if defined(SIMG_USE_DTS)
|
||
|
/*
|
||
|
* Modify default driver platform parameters with those
|
||
|
* specified in the device tree.
|
||
|
*/
|
||
|
if (spi_dev->dev.of_node) {
|
||
|
ret = si_8620_parse_dt(&spi_dev->dev);
|
||
|
if (ret)
|
||
|
goto failed;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Because we're using SPI for register access, we need to
|
||
|
* obtain separate i2c access for the I/O expander, so we
|
||
|
* use the I2C adapter number we got from the device tree.
|
||
|
*/
|
||
|
i2c_bus_adapter = i2c_get_adapter(i2c_adapter_num);
|
||
|
if (i2c_bus_adapter == NULL) {
|
||
|
pr_err("%s() failed to get i2c adapter %d\n", __func__,
|
||
|
i2c_adapter_num);
|
||
|
ret = -EFAULT;
|
||
|
goto failed;
|
||
|
}
|
||
|
|
||
|
if (!i2c_check_functionality(i2c_bus_adapter,
|
||
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||
|
MHL_TX_DBG_ERR("[ERROR] i2c function check failed\n");
|
||
|
ret = -EIO;
|
||
|
goto failed;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
spi_mem.tx_buf = kmalloc(MAX_SPI_XFER_BUFFER_SIZE, GFP_KERNEL);
|
||
|
spi_mem.rx_buf = kmalloc(MAX_SPI_XFER_BUFFER_SIZE, GFP_KERNEL);
|
||
|
spi_mem.block_tx_buffers = kmalloc(MAX_SPI_EMSC_BLOCK_SIZE *
|
||
|
NUM_BLOCK_QUEUE_REQUESTS, GFP_KERNEL);
|
||
|
if (!spi_mem.tx_buf || !spi_mem.rx_buf || !spi_mem.block_tx_buffers) {
|
||
|
ret = -ENOMEM;
|
||
|
goto mem_cleanup;
|
||
|
}
|
||
|
ret = starter_kit_init();
|
||
|
if (ret >= 0) {
|
||
|
drv_info.irq = gpio_to_irq(
|
||
|
starter_kit_control_gpios[MHL_INT_INDEX].gpio);
|
||
|
ret = mhl_tx_init(&drv_info, &spi_dev->dev);
|
||
|
if (ret) {
|
||
|
pr_err("%s(): mhl_tx_init failed, error code %d\n",
|
||
|
__func__, ret);
|
||
|
gpio_free_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
goto mem_cleanup;
|
||
|
}
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
mem_cleanup:
|
||
|
kfree(spi_mem.tx_buf);
|
||
|
spi_mem.tx_buf = NULL;
|
||
|
|
||
|
kfree(spi_mem.rx_buf);
|
||
|
spi_mem.rx_buf = NULL;
|
||
|
|
||
|
kfree(spi_mem.block_tx_buffers);
|
||
|
spi_mem.block_tx_buffers = NULL;
|
||
|
|
||
|
#if defined(SIMG_USE_DTS)
|
||
|
failed:
|
||
|
#endif
|
||
|
probe_fail = true;
|
||
|
|
||
|
done:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
static int si_8620_mhl_spi_remove(struct spi_device *spi_dev)
|
||
|
#else
|
||
|
static int __devexit si_8620_mhl_spi_remove(struct spi_device *spi_dev)
|
||
|
#endif
|
||
|
{
|
||
|
pr_info("%s() called\n", __func__);
|
||
|
|
||
|
gpio_free_array(starter_kit_control_gpios,
|
||
|
ARRAY_SIZE(starter_kit_control_gpios));
|
||
|
kfree(spi_mem.tx_buf);
|
||
|
kfree(spi_mem.rx_buf);
|
||
|
kfree(spi_mem.block_tx_buffers);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct spi_driver si_86x0_mhl_tx_spi_driver = {
|
||
|
.driver = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.name = MHL_DRIVER_NAME,
|
||
|
#ifdef SIMG_USE_DTS
|
||
|
.of_match_table = si_8620_of_match_table,
|
||
|
#endif
|
||
|
.pm = &si_8620_tx_pm_ops,
|
||
|
},
|
||
|
.probe = si_8620_mhl_tx_spi_probe,
|
||
|
#if (LINUX_KERNEL_VER >= 308)
|
||
|
.remove = si_8620_mhl_spi_remove,
|
||
|
#else
|
||
|
.remove = __devexit_p(si_8620_mhl_spi_remove),
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
static struct spi_board_info __initdata si_86x0_spi_board_info = {
|
||
|
.modalias = MHL_DRIVER_NAME,
|
||
|
.max_speed_hz = SPI_BUS_SPEED,
|
||
|
.bus_num = SPI_BUS_NUM,
|
||
|
.chip_select = SPI_CHIP_SEL,
|
||
|
.mode = SPI_TRANSFER_MODE,
|
||
|
.irq = -1
|
||
|
};
|
||
|
|
||
|
static int __init add_spi_device_to_bus(void)
|
||
|
{
|
||
|
struct spi_master *spi_master;
|
||
|
int status = 0;
|
||
|
|
||
|
pr_info("add_spi_device_to_bus called\n");
|
||
|
|
||
|
spi_master = spi_busnum_to_master(SPI_BUS_NUM);
|
||
|
if (spi_master == NULL) {
|
||
|
pr_err("spi_busnum_to_master(%d) returned NULL\n", SPI_BUS_NUM);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
spi_dev = spi_new_device(spi_master, &si_86x0_spi_board_info);
|
||
|
if (spi_dev == NULL || probe_fail) {
|
||
|
pr_err("spi_new_device() failed\n");
|
||
|
if (spi_dev)
|
||
|
spi_unregister_device(spi_dev);
|
||
|
status = -1;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
put_device(&spi_master->dev);
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int __init spi_init(void)
|
||
|
{
|
||
|
int status;
|
||
|
|
||
|
status = spi_register_driver(&si_86x0_mhl_tx_spi_driver);
|
||
|
if (status < 0) {
|
||
|
pr_err("[ERROR] %s():%d failed !\n", __func__, __LINE__);
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
status = add_spi_device_to_bus();
|
||
|
if (status < 0)
|
||
|
MHL_TX_DBG_ERR("Failed to add MHL transmitter as SPI device\n");
|
||
|
#endif
|
||
|
|
||
|
if (probe_fail || status < 0) {
|
||
|
spi_unregister_driver(&si_86x0_mhl_tx_spi_driver);
|
||
|
status = -ENODEV;
|
||
|
}
|
||
|
done:
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
static int __init i2c_init(void)
|
||
|
{
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
struct i2c_client *client;
|
||
|
int idx;
|
||
|
#endif
|
||
|
int ret = -ENODEV;
|
||
|
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
/* "Hotplug" the MHL transmitter device onto the 2nd I2C bus */
|
||
|
i2c_bus_adapter = i2c_get_adapter(I2C_ADAPTER);
|
||
|
if (i2c_bus_adapter == NULL)
|
||
|
goto done;
|
||
|
|
||
|
for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
|
||
|
if (idx == 0) {
|
||
|
client =
|
||
|
i2c_new_device(i2c_bus_adapter,
|
||
|
&si_8620_i2c_boardinfo[idx]);
|
||
|
device_addresses[idx].client = client;
|
||
|
} else {
|
||
|
device_addresses[idx].client =
|
||
|
i2c_new_dummy(i2c_bus_adapter,
|
||
|
device_addresses[idx].dev_addr);
|
||
|
}
|
||
|
if (device_addresses[idx].client == NULL) {
|
||
|
MHL_TX_DBG_INFO("returning %d\n", ret);
|
||
|
goto err_exit;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
ret = i2c_add_driver(&si_8620_mhl_tx_i2c_driver);
|
||
|
if (ret < 0 || probe_fail) {
|
||
|
if (ret == 0)
|
||
|
i2c_del_driver(&si_8620_mhl_tx_i2c_driver);
|
||
|
MHL_TX_DBG_INFO("failed !\n\nCHECK POWER AND CONNECTION "
|
||
|
"TO CP8620 Starter Kit.\n\n");
|
||
|
goto err_exit;
|
||
|
}
|
||
|
|
||
|
goto done;
|
||
|
|
||
|
err_exit:
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
|
||
|
if (device_addresses[idx].client != NULL)
|
||
|
i2c_unregister_device(device_addresses[idx].client);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
done:
|
||
|
if (probe_fail)
|
||
|
ret = -ENODEV;
|
||
|
|
||
|
MHL_TX_DBG_INFO("returning %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __init si_8620_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
pr_info("mhl: Starting SiI%d Driver v%s\n", MHL_PRODUCT_NUM,
|
||
|
buildVersion);
|
||
|
pr_info("mhl: %s\n", buildTime);
|
||
|
#if (INCLUDE_HID == 1)
|
||
|
pr_info("mhl: Supports MHL3 HID\n");
|
||
|
#endif
|
||
|
|
||
|
sema_init(&platform_lock, 1);
|
||
|
|
||
|
platform_flags &= ~PLATFORM_FLAG_HEARTBEAT_MASK;
|
||
|
switch (use_heartbeat) {
|
||
|
case 0:
|
||
|
/* don't do anything with heatbeat */
|
||
|
break;
|
||
|
case 1:
|
||
|
platform_flags |= PLATFORM_VALUE_ISSUE_HEARTBEAT;
|
||
|
break;
|
||
|
case 2:
|
||
|
platform_flags |= PLATFORM_VALUE_DISCONN_HEARTBEAT;
|
||
|
break;
|
||
|
default:
|
||
|
MHL_TX_DBG_ERR("%sinvalid use_heartbeat parameter%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
}
|
||
|
|
||
|
if (tmds_link_speed == 15)
|
||
|
platform_flags |= PLATFORM_FLAG_1_5GBPS;
|
||
|
else if (tmds_link_speed == 3)
|
||
|
platform_flags |= PLATFORM_FLAG_3GBPS;
|
||
|
else if (tmds_link_speed == 6)
|
||
|
platform_flags |= PLATFORM_FLAG_6GBPS;
|
||
|
|
||
|
/*
|
||
|
* NOTE: Even if the user selects to communicate with the MHL
|
||
|
* transmitter via SPI we still need I2C to communicate with
|
||
|
* other devices on the starter kit board.
|
||
|
*/
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
i2c_bus_adapter = i2c_get_adapter(I2C_ADAPTER);
|
||
|
if (i2c_bus_adapter == NULL) {
|
||
|
pr_err("%s() failed to get i2c adapter %d\n", __func__,
|
||
|
(int)I2C_ADAPTER);
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
#endif
|
||
|
if (use_spi)
|
||
|
ret = spi_init();
|
||
|
else
|
||
|
ret = i2c_init();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void __exit si_8620_exit(void)
|
||
|
{
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
int idx;
|
||
|
#endif
|
||
|
|
||
|
pr_info("si_8620_exit called\n");
|
||
|
|
||
|
si_8620_power_control(false);
|
||
|
|
||
|
if (use_spi) {
|
||
|
mhl_tx_remove(&spi_dev->dev);
|
||
|
spi_unregister_driver(&si_86x0_mhl_tx_spi_driver);
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
spi_unregister_device(spi_dev);
|
||
|
#endif
|
||
|
} else {
|
||
|
if (device_addresses[0].client != NULL) {
|
||
|
mhl_tx_remove(&device_addresses[0].client->dev);
|
||
|
MHL_TX_DBG_INFO("client removed\n");
|
||
|
}
|
||
|
i2c_del_driver(&si_8620_mhl_tx_i2c_driver);
|
||
|
MHL_TX_DBG_INFO("i2c driver deleted from context\n");
|
||
|
|
||
|
#ifndef SIMG_USE_DTS
|
||
|
for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
|
||
|
MHL_TX_DBG_INFO("\n");
|
||
|
if (device_addresses[idx].client != NULL) {
|
||
|
MHL_TX_DBG_INFO("unregistering device:%p\n",
|
||
|
device_addresses[idx].client);
|
||
|
i2c_unregister_device(device_addresses[idx].
|
||
|
client);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
MHL_TX_DBG_ERR("driver unloaded.\n");
|
||
|
}
|
||
|
|
||
|
static int debug_level_stack[15];
|
||
|
static unsigned int debug_level_stack_ptr;
|
||
|
|
||
|
void push_debug_level(int new_verbosity)
|
||
|
{
|
||
|
if (debug_level_stack_ptr < ARRAY_SIZE(debug_level_stack)) {
|
||
|
/* stack is initially empty */
|
||
|
debug_level_stack[debug_level_stack_ptr++] = debug_level;
|
||
|
debug_level = new_verbosity;
|
||
|
} else {
|
||
|
MHL_TX_DBG_ERR("%sdebug_level_stack overflowed%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void pop_debug_level(void)
|
||
|
{
|
||
|
if (debug_level_stack_ptr > 0) {
|
||
|
if (debug_level_stack_ptr > ARRAY_SIZE(debug_level_stack)) {
|
||
|
MHL_TX_DBG_ERR("%sdebug_level_stack overflowed%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
} else {
|
||
|
debug_level =
|
||
|
debug_level_stack[--debug_level_stack_ptr];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int si_8620_register_callbacks(struct si_mhl_callback_api_t *p_callbacks)
|
||
|
{
|
||
|
struct device *dev = NULL;
|
||
|
int status = 0;
|
||
|
struct mhl_dev_context *dev_context;
|
||
|
struct drv_hw_context *hw_context;
|
||
|
|
||
|
if (use_spi)
|
||
|
dev = &spi_dev->dev;
|
||
|
else
|
||
|
dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
dev_context = dev_get_drvdata(dev);
|
||
|
hw_context = (struct drv_hw_context *)&dev_context->drv_context;
|
||
|
|
||
|
if (down_interruptible(&dev_context->isr_lock))
|
||
|
return -ERESTARTSYS;
|
||
|
|
||
|
if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
|
||
|
status = -ENODEV;
|
||
|
} else {
|
||
|
if (NULL != p_callbacks) {
|
||
|
if (p_callbacks->context)
|
||
|
hw_context->
|
||
|
callbacks.context =
|
||
|
p_callbacks->context;
|
||
|
if (p_callbacks->display_timing_enum_begin)
|
||
|
hw_context->
|
||
|
callbacks.display_timing_enum_begin =
|
||
|
p_callbacks->display_timing_enum_begin;
|
||
|
if (p_callbacks->display_timing_enum_item)
|
||
|
hw_context->
|
||
|
callbacks.display_timing_enum_item =
|
||
|
p_callbacks->display_timing_enum_item;
|
||
|
if (p_callbacks->display_timing_enum_end)
|
||
|
hw_context->
|
||
|
callbacks.display_timing_enum_end =
|
||
|
p_callbacks->display_timing_enum_end;
|
||
|
if (p_callbacks->hpd_driven_low)
|
||
|
hw_context->
|
||
|
callbacks.hpd_driven_low =
|
||
|
p_callbacks->hpd_driven_low;
|
||
|
if (p_callbacks->hpd_driven_high)
|
||
|
hw_context->
|
||
|
callbacks.hpd_driven_high =
|
||
|
p_callbacks->hpd_driven_high;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
up(&dev_context->isr_lock);
|
||
|
return status;
|
||
|
}
|
||
|
EXPORT_SYMBOL(si_8620_register_callbacks);
|
||
|
|
||
|
int si_8620_info_frame_change(enum hpd_high_callback_status mode_parm,
|
||
|
union avif_or_cea_861_dtd_u *p_avif_or_dtd,
|
||
|
size_t avif_or_dtd_max_length, union vsif_mhl3_or_hdmi_u *p_vsif,
|
||
|
size_t vsif_max_length)
|
||
|
{
|
||
|
struct device *dev = NULL;
|
||
|
int status;
|
||
|
struct mhl_dev_context *dev_context;
|
||
|
struct drv_hw_context *hw_context;
|
||
|
|
||
|
if (use_spi)
|
||
|
dev = &spi_dev->dev;
|
||
|
else
|
||
|
dev = &device_addresses[0].client->dev;
|
||
|
|
||
|
dev_context = dev_get_drvdata(dev);
|
||
|
hw_context = (struct drv_hw_context *)&dev_context->drv_context;
|
||
|
|
||
|
if (down_interruptible(&dev_context->isr_lock))
|
||
|
return -ERESTARTSYS;
|
||
|
|
||
|
if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
|
||
|
status = -ENODEV;
|
||
|
} else {
|
||
|
size_t xfer_size;
|
||
|
|
||
|
memset(&hw_context->vsif_mhl3_or_hdmi_from_callback, 0,
|
||
|
sizeof(hw_context->vsif_mhl3_or_hdmi_from_callback));
|
||
|
memset(&hw_context->avif_or_dtd_from_callback, 0,
|
||
|
sizeof(hw_context->avif_or_dtd_from_callback));
|
||
|
|
||
|
if (sizeof(hw_context->vsif_mhl3_or_hdmi_from_callback) <
|
||
|
vsif_max_length) {
|
||
|
xfer_size = sizeof(
|
||
|
hw_context->vsif_mhl3_or_hdmi_from_callback);
|
||
|
} else {
|
||
|
xfer_size = vsif_max_length;
|
||
|
}
|
||
|
memcpy(&hw_context->vsif_mhl3_or_hdmi_from_callback, p_vsif,
|
||
|
xfer_size);
|
||
|
|
||
|
if (sizeof(hw_context->avif_or_dtd_from_callback) <
|
||
|
avif_or_dtd_max_length) {
|
||
|
xfer_size = sizeof(
|
||
|
hw_context->avif_or_dtd_from_callback);
|
||
|
} else {
|
||
|
xfer_size = avif_or_dtd_max_length;
|
||
|
}
|
||
|
memcpy(&hw_context->avif_or_dtd_from_callback, p_avif_or_dtd,
|
||
|
xfer_size);
|
||
|
|
||
|
status = si_mhl_tx_drv_set_display_mode(dev_context, mode_parm);
|
||
|
}
|
||
|
|
||
|
up(&dev_context->isr_lock);
|
||
|
return status;
|
||
|
}
|
||
|
EXPORT_SYMBOL(si_8620_info_frame_change);
|
||
|
|
||
|
module_init(si_8620_init);
|
||
|
module_exit(si_8620_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("Silicon Image MHL Transmitter driver");
|
||
|
MODULE_AUTHOR("Silicon Image <http://www.siliconimage.com>");
|
||
|
MODULE_LICENSE("GPL");
|