| /*************************************************************************** |
| * Copyright (c) 2014 Amlogic, Inc. All rights reserved. |
| * |
| * This source code is subject to the terms and conditions defined in the |
| * file 'LICENSE' which is part of this source code package. |
| * |
| * Description: |
| * |
| ***************************************************************************/ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/interrupt.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/spi/spi.h> |
| #include <linux/amlogic/aml_gpio_consumer.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/amlogic/sd.h> |
| #include <linux/of_irq.h> |
| #include <linux/irq.h> |
| #include <linux/mutex.h> |
| #include <linux/firmware.h> |
| #include <linux/delay.h> |
| |
| #include "aml_cimax.h" |
| |
| #define MOD_NAME "aml_cimax_spi" |
| |
| #define pr_dbg(fmt...)\ |
| do {\ |
| if (cimax_spi_debug)\ |
| pr_info("cimax_spi: "fmt);\ |
| } while (0) |
| #define pr_error(fmt...) pr_err("AML_CIMAX_SPI: " fmt) |
| |
| #define BUFFIN_CFG 0x0000 |
| #define BUFFIN_ADDR_LSB 0x0001 |
| #define BUFFIN_ADDR_MSB 0x0002 |
| #define BUFFIN_DATA 0x0003 |
| #define BUFFOUT_CFG 0x0004 |
| #define BUFFOUT_ADDR_LSB 0x0005 |
| #define BUFFOUT_ADDR_MSB 0x0006 |
| #define BUFFOUT_DATA 0x0007 |
| #define BOOT_Key 0x0008 |
| #define BOOT_Status 0x0009 |
| #define BOOT_Test 0x000A |
| #define usb2_0_irq_mask 0x0010 |
| #define usb2_0_status 0x0011 |
| #define usb2_0_rx 0x0012 |
| #define usb2_0_tx 0x0013 |
| #define SPI_Slave_Ctrl 0x0018 |
| #define SPI_Slave_Status 0x0019 |
| #define SPI_Slave_Rx 0x001A |
| #define SPI_Slave_Tx 0x001B |
| #define SPI_Slave_Mask 0x001C |
| #define UCSG_Ctrl 0x0020 |
| #define UCSG_Status 0x0021 |
| #define UCSG_RxData 0x0022 |
| #define UCSG_TxData 0x0023 |
| #define PCtrl_Ctrl 0x0028 |
| #define PCtrl_Status 0x0029 |
| #define PCtrl_NbByte_LSB 0x002A |
| #define PCtrl_NbByte_MSB 0x002B |
| #define SPI_Master_Ctl 0x0030 |
| #define SPI_Master_NCS 0x0031 |
| #define SPI_Master_Status 0x0032 |
| #define SPI_Master_TxBuf 0x0033 |
| #define SPI_Master_RxBuf 0x0034 |
| #define BISTRAM_Ctl 0x0038 |
| #define BISTRAM_Bank 0x0039 |
| #define BISTRAM_Pat 0x003A |
| #define BISTRAM_SM 0x003B |
| #define BISTRAM_AddrLSB 0x003C |
| #define BISTROM_Config 0x0040 |
| #define BISTROM_SignatureLSB 0x0041 |
| #define BISTROM_SignatureMSB 0x0042 |
| #define BISTROM_StartAddrLSB 0x0043 |
| #define BISTROM_StartAddrMSB 0x0043 |
| #define BISTROM_StopAddrLSB 0x0043 |
| #define BISTROM_StopAddrMSB 0x0043 |
| #define CkMan_Config 0x0048 |
| #define CkMan_Select 0x0049 |
| #define CkMan_Test 0x004A |
| #define Revision_Number 0x004B |
| #define ResMan_Config 0x0050 |
| #define ResMan_Status 0x0051 |
| #define ResMan_WD 0x0052 |
| #define ResMan_WD_MSB 0x0053 |
| #define CPU_Test 0x0060 |
| #define IrqMan_Config0 0x0068 |
| #define IrqMan_Config1 0x0069 |
| #define IrqMan_Irq0 0x006A |
| #define IrqMan_NMI 0x006B |
| #define IrqMan_SleepKey 0x006C |
| #define Tim_Config 0x0070 |
| #define Tim_Value_LSB 0x0071 |
| #define Tim_Value_MSB 0x0072 |
| #define Tim_Comp_LSB 0x0073 |
| #define Tim_Comp_MSB 0x0074 |
| #define TI_Config 0x0076 |
| #define TI_Data 0x0077 |
| #define TI_Reg0 0x0078 |
| #define TI_Reg1 0x0079 |
| #define TI_Reg2 0x007A |
| #define TI_Reg3 0x007B |
| #define TI_Reg4 0x007C |
| #define TI_ROM1 0x007D |
| #define TI_ROM2 0x007E |
| #define TI_ROM3 0x007F |
| #define DVBCI_START_ADDR 0x0100 |
| #define DVBCI_END_ADDR 0x017F |
| #define DATA 0x0180 |
| /*#define CTRL 0x0181*/ |
| #define QB_HOST 0x0182 |
| #define LEN_HOST_LSB 0x0183 |
| #define LEN_HOST_MSB 0x0184 |
| #define FIFO_TX_TH_LSB 0x0185 |
| #define FIFO_TX_TH_MSB 0x0186 |
| #define FIFO_TX_D_NB_LSB 0x0187 |
| #define FIFO_TX_D_NB_MSB 0x0188 |
| #define QB_MOD_CURR 0x0189 |
| #define LEN_MOD_CURR_LSB 0x018A |
| #define LEN_MOD_CURR_MSB 0x018B |
| #define QB_MOD 0x018C |
| #define LEN_MOD_LSB 0x018D |
| #define LEN_MOD_MSB 0x018E |
| #define FIFO_RX_TH_LSB 0x018F |
| #define FIFO_RX_TH_MSB 0x0190 |
| #define FIFO_RX_D_NB_LSB 0x0191 |
| #define FIFO_RX_D_NB_MSB 0x0192 |
| #define IT_STATUS_0 0x0193 |
| #define IT_STATUS_1 0x0194 |
| #define IT_MASK_0 0x0195 |
| #define IT_MASK_1 0x0196 |
| #define IT_HOST_PIN_CFG 0x0200 |
| #define CFG_0 0x0201 |
| #define CFG_1 0x0202 |
| #define CFG_2 0x0203 |
| #define IT_HOST 0x0204 |
| #define MOD_IT_STATUS 0x0205 |
| #define MOD_IT_MASK 0x0206 |
| #define MOD_CTRL_A 0x0207 |
| #define MOD_CTRL_B 0x0208 |
| #define DEST_SEL 0x0209 |
| #define CAM_MSB_ADD 0x020A |
| #define GPIO0_DIR 0x020B |
| #define GPIO0_DATA_IN 0x020C |
| #define GPIO0_DATA_OUT 0x020D |
| #define GPIO0_STATUS 0x020E |
| #define GPIO0_IT_MASK 0x020F |
| #define GPIO0_DFT 0x0210 |
| #define GPIO0_MASK_DATA 0x0211 |
| #define GPIO1_DIR 0x0212 |
| #define GPIO1_DATA_IN 0x0213 |
| #define GPIO1_DATA_OUT 0x0214 |
| #define GPIO1_STATUS 0x0215 |
| #define GPIO1_IT_MASK 0x0216 |
| #define MEM_ACC_TIME_A 0x0217 |
| #define MEM_ACC_TIME_B 0x0218 |
| #define IO_ACC_TIME_A 0x0219 |
| #define IO_ACC_TIME_B 0x021A |
| #define EXT_CH_ACC_TIME_A 0x021B |
| #define EXT_CH_ACC_TIME_B 0x021C |
| #define PAR_IF_0 0x021D |
| #define PAR_IF_1 0x021E |
| #define PAR_IF_CTRL 0x021F |
| #define PCK_LENGTH 0x0220 |
| #define USB2TS_CTRL 0x0221 |
| #define USB2TS0_RDL 0x0222 |
| #define USB2TS1_RDL 0x0223 |
| #define TS2USB_CTRL 0x0224 |
| #define TSOUT_PAR_CTRL 0x0225 |
| #define TSOUT_PAR_CLK_SEL 0x0226 |
| #define S2P_CH0_CTRL 0x0227 |
| #define S2P_CH1_CTRL 0x0228 |
| #define P2S_CH0_CTRL 0x0229 |
| #define P2S_CH1_CTRL 0x022A |
| #define TS_IT_STATUS 0x022B |
| #define TS_IT_MASK 0x022C |
| #define IN_SEL 0x022D |
| #define OUT_SEL 0x022E |
| #define ROUTER_CAM_CH 0x022F |
| #define ROUTER_CAM_MOD 0x0230 |
| #define FIFO_CTRL 0x0231 |
| #define FIFO1_2_STATUS 0x0232 |
| #define FIFO3_4_STATUS 0x0233 |
| #define GAP_REMOVER_CH0_CTRL 0x0234 |
| #define GAP_REMOVER_CH1_CTRL 0x0235 |
| #define SYNC_RTV_CTRL 0x0236 |
| #define SYNC_RTV_CH0_SYNC_NB 0x0237 |
| #define SYNC_RTV_CH0_PATTERN 0x0238 |
| #define SYNC_RTV_CH1_SYNC_NB 0x0239 |
| #define SYNC_RTV_CH1_PATTERN 0x023A |
| #define SYNC_RTV_OFFSET_PATT 0x023B |
| #define CTRL_FILTER 0x023D |
| #define PID_EN_FILTER_CH0 0x023E |
| #define PID_EN_FILTER_CH1 0x023F |
| #define PID_LSB_FILTER_CH0_0 0x0240 |
| #define PID_MSB_FILTER_CH0_0 0x0241 |
| #define PID_LSB_FILTER_CH0_1 0x0242 |
| #define PID_MSB_FILTER_CH0_1 0x0243 |
| #define PID_LSB_FILTER_CH0_2 0x0244 |
| #define PID_MSB_FILTER_CH0_2 0x0245 |
| #define PID_LSB_FILTER_CH0_3 0x0246 |
| #define PID_MSB_FILTER_CH0_3 0x0247 |
| #define PID_LSB_FILTER_CH0_4 0x0248 |
| #define PID_MSB_FILTER_CH0_4 0x0249 |
| #define PID_LSB_FILTER_CH0_5 0x024A |
| #define PID_MSB_FILTER_CH0_5 0x024B |
| #define PID_LSB_FILTER_CH0_6 0x024C |
| #define PID_MSB_FILTER_CH0_6 0x024D |
| #define PID_LSB_FILTER_CH0_7 0x024E |
| #define PID_MSB_FILTER_CH0_7 0x024F |
| #define PID_LSB_FILTER_CH1_0 0x0260 |
| #define PID_MSB_FILTER_CH1_0 0x0261 |
| #define PID_LSB_FILTER_CH1_1 0x0262 |
| #define PID_MSB_FILTER_CH1_1 0x0263 |
| #define PID_LSB_FILTER_CH1_2 0x0264 |
| #define PID_MSB_FILTER_CH1_2 0x0265 |
| #define PID_LSB_FILTER_CH1_3 0x0266 |
| #define PID_MSB_FILTER_CH1_3 0x0267 |
| #define PID_LSB_FILTER_CH1_4 0x0268 |
| #define PID_MSB_FILTER_CH1_4 0x0269 |
| #define PID_LSB_FILTER_CH1_5 0x026A |
| #define PID_MSB_FILTER_CH1_5 0x026B |
| #define PID_LSB_FILTER_CH1_6 0x026C |
| #define PID_MSB_FILTER_CH1_6 0x026D |
| #define PID_LSB_FILTER_CH1_7 0x026E |
| #define PID_MSB_FILTER_CH1_7 0x026F |
| #define PID_OLD_LSB_REMAPPER_0 0x0280 |
| #define PID_OLD_MSB_REMAPPER_0 0x0281 |
| #define PID_OLD_LSB_REMAPPER_1 0x0282 |
| #define PID_OLD_MSB_REMAPPER_1 0x0283 |
| #define PID_OLD_LSB_REMAPPER_2 0x0284 |
| #define PID_OLD_MSB_REMAPPER_2 0x0285 |
| #define PID_OLD_LSB_REMAPPER_3 0x0286 |
| #define PID_OLD_MSB_REMAPPER_3 0x0287 |
| #define PID_OLD_LSB_REMAPPER_4 0x0288 |
| #define PID_OLD_MSB_REMAPPER_4 0x0289 |
| #define PID_OLD_LSB_REMAPPER_5 0x028A |
| #define PID_OLD_MSB_REMAPPER_5 0x028B |
| #define PID_OLD_LSB_REMAPPER_6 0x028C |
| #define PID_OLD_MSB_REMAPPER_6 0x028D |
| #define PID_OLD_LSB_REMAPPER_7 0x028E |
| #define PID_OLD_MSB_REMAPPER_7 0x028F |
| #define PID_NEW_LSB_REMAPPER_0 0x02A0 |
| #define PID_NEW_MSB_REMAPPER_0 0x02A1 |
| #define PID_NEW_LSB_REMAPPER_1 0x02A2 |
| #define PID_NEW_MSB_REMAPPER_1 0x02A3 |
| #define PID_NEW_LSB_REMAPPER_2 0x02A4 |
| #define PID_NEW_MSB_REMAPPER_2 0x02A5 |
| #define PID_NEW_LSB_REMAPPER_3 0x02A6 |
| #define PID_NEW_MSB_REMAPPER_3 0x02A7 |
| #define PID_NEW_LSB_REMAPPER_4 0x02A8 |
| #define PID_NEW_MSB_REMAPPER_4 0x02A9 |
| #define PID_NEW_LSB_REMAPPER_5 0x02AA |
| #define PID_NEW_MSB_REMAPPER_5 0x02AB |
| #define PID_NEW_LSB_REMAPPER_6 0x02AC |
| #define PID_NEW_MSB_REMAPPER_6 0x02AD |
| #define PID_NEW_LSB_REMAPPER_7 0x02AE |
| #define PID_NEW_MSB_REMAPPER_7 0x02AF |
| #define MERGER_DIV_MICLK 0x02C0 |
| #define PID_AND_SYNC_REMAPPER_CTRL 0x02C1 |
| #define PID_EN_REMAPPER 0x02C2 |
| #define SYNC_SYMBOL 0x02C3 |
| #define PID_AND_SYNC_REMAPPER_INV_CTRL 0x02C4 |
| #define BITRATE_CH0_LSB 0x02C5 |
| #define BITRATE_CH0_MSB 0x02C6 |
| #define BITRATE_CH1_LSB 0x02C7 |
| #define BITRATE_CH1_MSB 0x02C8 |
| #define STATUS_CLK_SWITCH_0 0x02C9 |
| #define STATUS_CLK_SWITCH_1 0x02CA |
| #define RESET_CLK_SWITCH_0 0x02CB |
| #define RESET_CLK_SWITCH_1 0x02CC |
| #define PAD_DRVSTR_CTRL 0x02CD |
| #define PAD_PUPD_CTRL 0x02CE |
| #define PRE_HEADER_ADDER_CH0_0 0x02D0 |
| #define PRE_HEADER_ADDER_CH0_1 0x02D1 |
| #define PRE_HEADER_ADDER_CH0_2 0x02D2 |
| #define PRE_HEADER_ADDER_CH0_3 0x02D3 |
| #define PRE_HEADER_ADDER_CH0_4 0x02D4 |
| #define PRE_HEADER_ADDER_CH0_5 0x02D5 |
| #define PRE_HEADER_ADDER_CH0_6 0x02D6 |
| #define PRE_HEADER_ADDER_CH0_7 0x02D7 |
| #define PRE_HEADER_ADDER_CH0_8 0x02D8 |
| #define PRE_HEADER_ADDER_CH0_9 0x02D9 |
| #define PRE_HEADER_ADDER_CH0_10 0x02DA |
| #define PRE_HEADER_ADDER_CH0_11 0x02DB |
| #define PRE_HEADER_ADDER_CH1_0 0x02E0 |
| #define PRE_HEADER_ADDER_CH1_1 0x02E1 |
| #define PRE_HEADER_ADDER_CH1_2 0x02E2 |
| #define PRE_HEADER_ADDER_CH1_3 0x02E3 |
| #define PRE_HEADER_ADDER_CH1_4 0x02E4 |
| #define PRE_HEADER_ADDER_CH1_5 0x02E5 |
| #define PRE_HEADER_ADDER_CH1_6 0x02E6 |
| #define PRE_HEADER_ADDER_CH1_7 0x02E7 |
| #define PRE_HEADER_ADDER_CH1_8 0x02E8 |
| #define PRE_HEADER_ADDER_CH1_9 0x02E9 |
| #define PRE_HEADER_ADDER_CH1_10 0x02EA |
| #define PRE_HEADER_ADDER_CH1_11 0x02EB |
| #define PRE_HEADER_ADDER_CTRL 0x02EC |
| #define PRE_HEADER_ADDER_LEN 0x02ED |
| #define PRE_HEADER_REMOVER_CTRL 0x02EE |
| #define FSM_DVB 0x02F0 |
| #define TS2USB_FSM_DEBUG 0x02F2 |
| #define TSOUT_PAR_FSM_DEBUG 0x02F3 |
| #define GAP_REMOVER_FSM_DEBUG 0x02F4 |
| #define PID_AND_SYNC_REMAPPER_FSM_DEBUG 0x02F5 |
| #define PRE_HEADER_ADDER_FSM_DEBUG 0x02F6 |
| #define SYNC_RTV_FSM_DEBUG 0x02F7 |
| #define CHECK_PHY_CLK 0x0E00 |
| #define USB_CTRL1 0x0E01 |
| #define USB_ISO2_out 0x0800 |
| #define USB_ISO1_out 0x1000 |
| #define USB_Interrupt_out 0x1E00 |
| #define USB_Bulk_in 0x1F00 |
| #define CC2_Buffer_out 0x2000 |
| #define USB_EP0 0x30C0 |
| #define CC2_Buffer_in 0x4000 |
| #define USB_ISO2_in 0x5800 |
| #define USB_ISO1_in 0x6000 |
| #define nmb_vector_address_lsb 0xFFFA |
| #define nmb_vector_address_msb 0xFFFB |
| #define reset_vector_address_lsb 0xFFFC |
| #define reset_vector_address_msb 0xFFFD |
| #define irb_vector_address_lsb 0xFFFE |
| #define irb_vector_address_msb 0xFFFF |
| |
| |
| #define CIMAX_REG_HDR_SIZE 4 |
| #define CIMAX_REG_PLD_SIZE 255 |
| #define CIMAX_CAM_HDR_SIZE 4 |
| #define CIMAX_CAM_PLD_SIZE 65535 |
| |
| #define DEF_LOCK(_l_) struct mutex _l_ |
| |
| struct cimax_spi { |
| struct platform_device *pdev; |
| struct spi_device *dev; |
| |
| struct aml_cimax *cimax; |
| |
| u8 buf[CIMAX_REG_HDR_SIZE + CIMAX_CAM_HDR_SIZE + CIMAX_CAM_PLD_SIZE]; |
| |
| int cam_inserted[2]; |
| int cam_data_ready[2]; |
| |
| int poll_mode; |
| #define STOP_MODE 0 |
| #define POLL_MODE 1 |
| #define INT_MODE 2 |
| |
| int irq; |
| int irq_io; |
| int rst_io; |
| |
| struct workqueue_struct *workq; |
| struct delayed_work work; |
| int work_cnt; |
| |
| DEF_LOCK(lock); |
| #define lock_init(_spi) mutex_init(&(_spi)->lock) |
| #define lock_lock(_spi) do {\ |
| int err = mutex_lock_interruptible(&(_spi)->lock);\ |
| if (err)\ |
| return err;\ |
| } while (0) |
| #define lock_unlock(_spi) mutex_unlock(&(_spi)->lock) |
| |
| u8 *cis; |
| #define CIS_MAX 512 |
| }; |
| |
| static struct cimax_spi *g_spi; |
| |
| MODULE_PARM_DESC(debug, "enable verbose debug messages"); |
| static int cimax_spi_debug = 1; |
| module_param_named(debug, cimax_spi_debug, int, 0644); |
| |
| MODULE_PARM_DESC(poll_interval, "interval for spi poll"); |
| static int spi_poll_interval = 100; |
| module_param_named(poll_interval, spi_poll_interval, int, 0644); |
| |
| MODULE_PARM_DESC(poll_mode, "set cimax poll mode, need reset"); |
| static int cimax_poll_mode = 1; |
| module_param_named(poll_mode, cimax_poll_mode, int, 0644); |
| |
| MODULE_PARM_DESC(cam_irq_mode, "set cam irq mode, need reset"); |
| static int cam_irq_mode; |
| module_param_named(cam_irq_mode, cam_irq_mode, int, 0644); |
| |
| #define CIMAX_REG_READ 0xff |
| #define CIMAX_REG_READ_OK 0x4c |
| #define CIMAX_REG_WRITE 0x7f |
| #define CIMAX_REG_WRITE_OK 0x4d |
| #define CIMAX_REG_INIT 0x00 |
| #define CIMAX_REG_INIT_OK 0x4b |
| #define CIMAX_REG_CMD_ERROR 0x51 |
| |
| #define CIMAX_CAM_RESET 0x01 |
| #define CIMAX_CAM_RESET_OK 0x40 |
| #define CIMAX_CAM_CIS 0x02 |
| #define CIMAX_CAM_CIS_OK 0x41 |
| #define CIMAX_CAM_COR 0x03 |
| #define CIMAX_CAM_COR_OK 0x42 |
| #define CIMAX_CAM_NEG 0x04 |
| #define CIMAX_CAM_NEG_OK 0x43 |
| #define CIMAX_CAM_WLPDU 0x05 |
| #define CIMAX_CAM_WLPDU_OK 0x44 |
| #define CIMAX_CAM_RLPDU 0x06 |
| #define CIMAX_CAM_RLPDU_OK 0x46 |
| #define CIMAX_CAM_EVT 0x0d |
| #define CIMAX_CAM_DET_OK 0x45 |
| #define CIMAX_CAM_NOCAM 0x49 |
| #define CIMAX_CAM_ERROR 0x4a |
| #define CIMAX_CAM_NOEVT 0x55 |
| #define CIMAX_CAM_DATA_READY 0x4e |
| #define CIMAX_CAM_WBUSY 0x54 |
| #define CIMAX_CAM_PENDING 0x56 |
| #define CIMAX_CAM_REGSTAT 0x0e |
| #define CIMAX_CAM_REGSTAT_OK 0x57 |
| |
| |
| #define CIMAX_CAM_PKT_CNT_VAL 1 |
| |
| #define CIMAX_SLOT_A 0 |
| #define CIMAX_SLOT_B 1 |
| |
| #define CIMAX_CMD_RESP_MASK 0x7f |
| |
| #define cimax_to_spi(_c) ((struct cimax_spi *)((_c)->priv)) |
| #define dev_to_spi(_d) ((struct cimax_spi *)spi_get_drvdata(_d)) |
| |
| #define byte_to_u16(_b1, _b2) (((_b1)<<8) | (_b2)) |
| |
| #define hdr_cmd_resp(_s) ((_s)->buf[0] & CIMAX_CMD_RESP_MASK) |
| |
| #define reg_hdr(_s) ((_s)->buf) |
| #define reg_addr(_s) byte_to_u16((_s)->buf[1], (_s)->buf[2]) |
| #define reg_hdr_dat_size(_s) ((_s)->buf[3]) |
| #define reg_dat(_s) (&((_s)->buf[CIMAX_REG_HDR_SIZE])) |
| |
| #define cam_hdr(_s) ((_s)->buf) |
| #define cam_hdr_slot(_s) (((_s)->buf[0] & 0x80) ? 1 : 0) |
| #define cam_hdr_pkt_cnt(_s) ((_s)->buf[1]) |
| #define cam_hdr_dat_size(_s) byte_to_u16((_s)->buf[2], (_s)->buf[3]) |
| #define cam_dat(_s) (&((_s)->buf[CIMAX_CAM_HDR_SIZE])) |
| |
| #define REG_TIMEOUT 500 |
| #define CAM_TIMEOUT 5000 |
| |
| #define USE_INT_PIO |
| |
| static int aml_cimax_spi_mod_init(void); |
| static void aml_cimax_spi_mod_exit(void); |
| |
| static void dump(char *title, u8 *buf, int size) |
| { |
| int i; |
| pr_info("%s\n", title); |
| for (i = 0; i < size; i++) { |
| if (!(i & 0xf)) |
| pr_info("\n\t"); |
| pr_info("%02x ", *(buf+i)); |
| } |
| pr_info("\n"); |
| } |
| |
| static void perr(char *err, struct cimax_spi *spi) |
| { |
| pr_error("error: %s\n", err); |
| dump("dump:", spi->buf, 16); |
| } |
| |
| static inline unsigned long get_jiffies(void) |
| { |
| return (unsigned long)(sched_clock()/10000000); |
| } |
| |
| static inline void set_spi_cam_ready(struct cimax_spi *spi, int slot) |
| { |
| if (spi->cam_inserted[slot] == 1) { |
| spi->cam_inserted[slot] = 2; |
| aml_cimax_slot_state_changed(spi->cimax, |
| slot, spi->cam_inserted[slot]); |
| } |
| } |
| |
| |
| static int cimax_spi_get_resp(struct cimax_spi *spi, int timeout) |
| { |
| struct spi_device *dev = spi->dev; |
| int ret = 0; |
| unsigned long start = get_jiffies(); |
| #ifndef USE_INT_PIO |
| u8 t = 0; |
| |
| while (!t && jiffies_to_msecs(get_jiffies() - start) < timeout) { |
| ret = spi_read(dev, &t, 1); |
| if (ret) |
| return ret; |
| usleep_range(1000, 2000); |
| } |
| if (!t) { |
| pr_error("resp timeout: %dms\n", timeout); |
| return -EIO; |
| } |
| cam_hdr(spi)[0] = t; |
| ret = spi_read(dev, cam_hdr(spi)+1, CIMAX_CAM_HDR_SIZE-1); |
| if (ret) |
| return ret; |
| #else |
| do { |
| if (jiffies_to_msecs(get_jiffies() - start) >= timeout) { |
| pr_error("resp timeout: %dms\n", timeout); |
| return -EIO; |
| } |
| usleep_range(1000, 2000); |
| } while (gpio_get_value(spi->irq_io)); |
| |
| ret = spi_read(dev, cam_hdr(spi), CIMAX_CAM_HDR_SIZE); |
| if (ret) |
| return ret; |
| #endif |
| /*pr_dbg("rp: %02x:%02x:%02x:%02x\n", |
| cam_hdr(spi)[0], cam_hdr(spi)[1], |
| cam_hdr(spi)[2], cam_hdr(spi)[3]);*/ |
| switch (hdr_cmd_resp(spi)) { |
| case CIMAX_REG_READ_OK: |
| case CIMAX_REG_WRITE_OK: |
| case CIMAX_REG_INIT_OK: |
| case CIMAX_REG_CMD_ERROR: { |
| int len = reg_hdr_dat_size(spi); |
| if (len) { |
| ret = spi_read(dev, reg_dat(spi), len); |
| if (ret != 0) |
| return ret; |
| } |
| } break; |
| case CIMAX_CAM_RESET_OK: |
| case CIMAX_CAM_CIS_OK: |
| case CIMAX_CAM_COR_OK: |
| case CIMAX_CAM_NEG_OK: |
| case CIMAX_CAM_WLPDU_OK: |
| case CIMAX_CAM_RLPDU_OK: |
| case CIMAX_CAM_NOCAM: |
| case CIMAX_CAM_ERROR: |
| case CIMAX_CAM_WBUSY: |
| case CIMAX_CAM_DET_OK: |
| case CIMAX_CAM_DATA_READY: |
| case CIMAX_CAM_PENDING: |
| case CIMAX_CAM_REGSTAT_OK: |
| case CIMAX_CAM_NOEVT: { |
| int len = cam_hdr_dat_size(spi); |
| if (len) { |
| ret = spi_read(dev, cam_dat(spi), len); |
| if (ret != 0) |
| return ret; |
| /*if (len) |
| dump("dat:", cam_dat(spi), len);*/ |
| } |
| } |
| break; |
| default: |
| pr_error("unknown resp:0x%02x\n", hdr_cmd_resp(spi)); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int init_reg_hdr(u8 *hdr, u8 tag, int addr, int size) |
| { |
| hdr[0] = tag; |
| hdr[1] = (addr>>8) & 0xff; |
| hdr[2] = addr & 0xff; |
| hdr[3] = size; |
| return 0; |
| } |
| |
| static int check_reg_hdr(u8 *hdr, u8 tag, int addr, int size) |
| { |
| return hdr[0] != tag |
| || hdr[1] != ((addr>>8) & 0xff) |
| || hdr[2] != (addr & 0xff) |
| || hdr[3] != size; |
| } |
| |
| static int aml_cimax_spi_read_reg(struct aml_cimax *cimax, int addr, |
| u8 *buf, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| struct spi_device *dev = spi->dev; |
| int err = 0; |
| |
| init_reg_hdr(spi->buf, CIMAX_REG_READ, addr, size); |
| |
| lock_lock(spi); |
| |
| pr_dbg("rd %02x:%02x:%02x:%02x\n", |
| reg_hdr(spi)[0], reg_hdr(spi)[1], |
| reg_hdr(spi)[2], reg_hdr(spi)[3]); |
| err = spi_write(dev, spi->buf, CIMAX_REG_HDR_SIZE); |
| if (err) |
| goto end; |
| err = cimax_spi_get_resp(spi, REG_TIMEOUT); |
| if (err) |
| goto end; |
| if (check_reg_hdr(reg_hdr(spi), CIMAX_REG_READ_OK, addr, size) != 0) { |
| perr("read reg fail.", spi); |
| err = -EINVAL; |
| goto end; |
| } |
| memcpy(buf, reg_dat(spi), size); |
| end: |
| lock_unlock(spi); |
| return err; |
| } |
| |
| static int aml_cimax_spi_write_reg(struct aml_cimax *cimax, int addr, |
| u8 *buf, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| struct spi_device *dev = spi->dev; |
| int err = 0; |
| |
| init_reg_hdr(spi->buf, CIMAX_REG_WRITE, addr, size); |
| memcpy(&spi->buf[CIMAX_REG_HDR_SIZE], buf, size); |
| |
| lock_lock(spi); |
| |
| pr_dbg("wr %02x:%02x:%02x:%02x\n", |
| reg_hdr(spi)[0], reg_hdr(spi)[1], |
| reg_hdr(spi)[2], reg_hdr(spi)[3]); |
| err = spi_write(dev, spi->buf, CIMAX_REG_HDR_SIZE + size); |
| if (err) |
| goto end; |
| err = cimax_spi_get_resp(spi, REG_TIMEOUT); |
| if (err) |
| goto end; |
| if (check_reg_hdr(reg_hdr(spi), CIMAX_REG_WRITE_OK, addr, 0) != 0) { |
| perr("write reg fail.", spi); |
| err = -EINVAL; |
| goto end; |
| } |
| end: |
| lock_unlock(spi); |
| return err; |
| } |
| |
| static inline int init_cam_hdr(u8 *hdr, int cmd, int size) |
| { |
| hdr[0] = cmd; |
| hdr[1] = CIMAX_CAM_PKT_CNT_VAL; |
| hdr[2] = (size>>8) & 0xff; |
| hdr[3] = size & 0xff; |
| return 0; |
| } |
| |
| static inline int cam_err(struct cimax_spi *spi) |
| { |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_ERROR |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 2) |
| return 0; |
| return byte_to_u16(cam_dat(spi)[0], cam_dat(spi)[1]); |
| } |
| |
| static inline char *cam_err_str(int err) |
| { |
| #define CAMERROR_RESET 0x0101 |
| #define CAMERROR_CIS_BUF 0x0201 |
| #define CAMERROR_CIS_SIZE 0x0202 |
| #define CAMERROR_CAM_NOT_ACT 0x0203 |
| #define CAMERROR_COR_NOT_READY 0x0301 |
| #define CAMERROR_COR_VAL_CHK 0x0302 |
| #define CAMERROR_NEG_NO_RESP 0x0401 |
| #define CAMERROR_NEG_BAD_SIZE 0x0402 |
| #define CAMERROR_NEG_NOT_READY 0x0403 |
| #define CAMERROR_LPDU_NOT_AVAIL 0x0601 |
| struct { int err; char *str; } cam_err_strings[] = { |
| {CAMERROR_RESET, "reset error, not ready."}, |
| {CAMERROR_CIS_BUF, "cis error, buffer not allocated."}, |
| {CAMERROR_CIS_SIZE, "cis error, bad cis size."}, |
| {CAMERROR_CAM_NOT_ACT, "cam not activated."}, |
| {CAMERROR_COR_NOT_READY, "cam not ready during write COR."}, |
| {CAMERROR_COR_VAL_CHK, "COR value check failed."}, |
| {CAMERROR_NEG_NO_RESP, "cam not responding when negotiation."}, |
| {CAMERROR_NEG_BAD_SIZE, "cam buf size length != 2."}, |
| {CAMERROR_NEG_NOT_READY, "cam not ready during negotiation."}, |
| {CAMERROR_LPDU_NOT_AVAIL, "lpdu not available."} |
| }; |
| int i; |
| for (i = 0; |
| i < sizeof(cam_err_strings)/sizeof(cam_err_strings[0]); i++) { |
| if (cam_err_strings[i].err == err) |
| return cam_err_strings[i].str; |
| } |
| return "err unknown."; |
| } |
| |
| static int cimax_spi_access_cam(struct cimax_spi *spi, int slot, |
| int cmd, u8 *buf, int size) |
| { |
| struct spi_device *dev = spi->dev; |
| int err = 0; |
| |
| cmd |= slot ? 0x80 : 0; |
| init_cam_hdr(cam_hdr(spi), cmd, size); |
| memcpy(cam_dat(spi), buf, size); |
| /*dump("access cam:", cam_hdr(spi), CIMAX_CAM_HDR_SIZE+size);*/ |
| err = spi_write(dev, cam_hdr(spi), CIMAX_CAM_HDR_SIZE + size); |
| if (err) |
| return err; |
| err = cimax_spi_get_resp(spi, CAM_TIMEOUT); |
| if (err) |
| return err; |
| if (cam_hdr_slot(spi) != slot) { |
| pr_error("expect slot(%d), but slot(%d)\n", |
| slot, cam_hdr_slot(spi)); |
| return -EINVAL; |
| } |
| switch (hdr_cmd_resp(spi)) { |
| case CIMAX_CAM_NOCAM: |
| pr_dbg("no cam\n"); |
| err = -ENODEV; |
| break; |
| case CIMAX_CAM_ERROR: |
| pr_error("cam error\n"); |
| pr_error("err code: 0x%04x(%s)\n", cam_err(spi), |
| cam_err_str(cam_err(spi))); |
| err = -ENODEV; |
| break; |
| case CIMAX_CAM_WBUSY: |
| pr_dbg("cam busy\n"); |
| err = -EBUSY; |
| break; |
| case CIMAX_CAM_PENDING: |
| pr_dbg("cam pending\n"); |
| err = -EAGAIN; |
| break; |
| } |
| return err; |
| } |
| |
| static int aml_cimax_spi_read_cis(struct aml_cimax *cimax, int slot, |
| u8 *buf, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int err = 0; |
| int len; |
| |
| lock_lock(spi); |
| |
| err = cimax_spi_access_cam(spi, slot, CIMAX_CAM_CIS, NULL, 0); |
| if (err) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_CIS_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL) { |
| perr("read cis fail.", spi); |
| err = -EINVAL; |
| goto end; |
| } |
| len = cam_hdr_dat_size(spi); |
| if (size < len) { |
| pr_error("cis size too large, expect<%d, but:%d\n", size, len); |
| perr("cis fail.", spi); |
| err = -EINVAL; |
| goto end; |
| } |
| memcpy(buf, cam_dat(spi), len); |
| |
| if (!spi->cis) |
| spi->cis = kzalloc((len < 512) ? 512 : len, GFP_KERNEL); |
| if (spi->cis) |
| memcpy(spi->cis, cam_dat(spi), len); |
| |
| end: |
| lock_unlock(spi); |
| return err; |
| } |
| #define CIMAX_CAM_COR_PLD_SIZE 5 |
| static int aml_cimax_spi_write_cor(struct aml_cimax *cimax, int slot, |
| int addr, u8 *buf) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int err = 0; |
| u8 out[CIMAX_CAM_COR_PLD_SIZE + 8]; |
| int sz = CIMAX_CAM_COR_PLD_SIZE; |
| |
| out[0] = addr>>8 & 0xff; |
| out[1] = addr & 0xff; |
| out[2] = buf[0]; |
| out[3] = 0; |
| out[4] = 0; |
| |
| if (!cam_irq_mode) { |
| out[5] = 0x40;/*cam poll mode*/ |
| sz++; |
| } |
| |
| lock_lock(spi); |
| |
| err = cimax_spi_access_cam(spi, slot, CIMAX_CAM_COR, out, sz); |
| if (err) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_COR_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 0) { |
| perr("write cor fail.", spi); |
| err = -EINVAL; |
| goto end; |
| } |
| end: |
| lock_unlock(spi); |
| return err; |
| } |
| #define CIMAX_CAM_NEG_PLD_SIZE 2 |
| static int aml_cimax_spi_negotiate(struct aml_cimax *cimax, int slot, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int ret = 0; |
| u8 out[CIMAX_CAM_NEG_PLD_SIZE]; |
| |
| out[0] = (size>>8) & 0xff; |
| out[1] = size & 0xff; |
| |
| lock_lock(spi); |
| |
| ret = cimax_spi_access_cam(spi, slot, CIMAX_CAM_NEG, |
| out, CIMAX_CAM_NEG_PLD_SIZE); |
| if (ret) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_NEG_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 2) { |
| perr("negotiate fail.", spi); |
| ret = -EINVAL; |
| goto end; |
| } |
| ret = byte_to_u16(cam_dat(spi)[0], cam_dat(spi)[1]); |
| |
| set_spi_cam_ready(spi, slot); |
| end: |
| lock_unlock(spi); |
| return ret; |
| } |
| |
| static int aml_cimax_spi_write_lpdu(struct aml_cimax *cimax, int slot, |
| u8 *buf, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int ret = 0; |
| |
| lock_lock(spi); |
| |
| /*dump("lpdu ->", buf, size);*/ |
| ret = cimax_spi_access_cam(spi, slot, CIMAX_CAM_WLPDU, buf, size); |
| if (ret) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_WLPDU_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 0) { |
| perr("write lpdu fail.", spi); |
| ret = -EINVAL; |
| goto end; |
| } |
| ret = size; |
| end: |
| lock_unlock(spi); |
| return ret; |
| } |
| |
| static int aml_cimax_spi_read_lpdu(struct aml_cimax *cimax, int slot, |
| u8 *buf, int size) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int ret = 0; |
| |
| lock_lock(spi); |
| |
| ret = cimax_spi_access_cam(spi, slot, CIMAX_CAM_RLPDU, NULL, 0); |
| if (ret) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_RLPDU_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL) { |
| perr("read lpdu fail.", spi); |
| ret = -EINVAL; |
| goto end; |
| } |
| ret = cam_hdr_dat_size(spi); |
| memcpy(buf, cam_dat(spi), ret); |
| |
| /*dump("lpdu <-", buf, ret);*/ |
| |
| spi->cam_data_ready[slot] = 0; |
| end: |
| lock_unlock(spi); |
| return ret; |
| } |
| |
| static int aml_cimax_spi_read_cam_status(struct aml_cimax *cimax, int slot) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int ret = 0; |
| |
| if (cam_irq_mode && spi->cam_data_ready[slot]) |
| return 0x80; |
| |
| lock_lock(spi); |
| |
| ret = cimax_spi_access_cam(spi, slot, CIMAX_CAM_REGSTAT, NULL, 0); |
| if (ret) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_REGSTAT_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 1) { |
| perr("read cam status fail.", spi); |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| ret = cam_dat(spi)[0]; |
| end: |
| lock_unlock(spi); |
| return ret; |
| } |
| |
| static int aml_cimax_spi_slot_reset(struct aml_cimax *cimax, int slot) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| int ret = 0; |
| |
| spi->cam_data_ready[slot] = 0; |
| |
| lock_lock(spi); |
| |
| ret = cimax_spi_access_cam(spi, slot, CIMAX_CAM_RESET, NULL, 0); |
| if (ret) |
| goto end; |
| if (hdr_cmd_resp(spi) != CIMAX_CAM_RESET_OK |
| || cam_hdr_pkt_cnt(spi) != CIMAX_CAM_PKT_CNT_VAL |
| || cam_hdr_dat_size(spi) != 0) { |
| perr("slot reset fail.", spi); |
| ret = -EINVAL; |
| goto end; |
| } |
| end: |
| lock_unlock(spi); |
| return ret; |
| } |
| |
| static int aml_cimax_spi_cam_reset(struct aml_cimax *cimax, int slot) |
| { |
| pr_dbg("Slot(%d): camreset\n", slot); |
| return 0; |
| } |
| |
| static int aml_cimax_spi_slot_shutdown(struct aml_cimax *cimax, int slot) |
| { |
| pr_dbg("Slot(%d): shutdown\n", slot); |
| return 0; |
| } |
| static int aml_cimax_spi_slot_ts_enable(struct aml_cimax *cimax, int slot) |
| { |
| pr_dbg("Slot(%d): ts control\n", slot); |
| return 0; |
| } |
| static int aml_cimax_spi_slot_status(struct aml_cimax *cimax, int slot) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| if (spi->cam_inserted[slot]) { |
| /*pr_dbg("CA Module present and ready\n");*/ |
| return DVB_CA_EN50221_POLL_CAM_PRESENT | |
| DVB_CA_EN50221_POLL_CAM_READY; |
| } else { |
| /*pr_error("CA Module not present or not ready\n");*/ |
| } |
| return 0; |
| } |
| |
| static int cimax_spi_cam_plugin(struct cimax_spi *spi, int slot, int plugin) |
| { |
| pr_dbg("cam plug: slot(%d) %s\n", |
| slot, plugin ? "plugged" : "unplugged"); |
| return aml_cimax_camchanged(spi->cimax, slot, plugin); |
| } |
| |
| static int cimax_spi_poll(struct cimax_spi *spi) |
| { |
| struct spi_device *dev = spi->dev; |
| int err = 0; |
| |
| lock_lock(spi); |
| |
| #ifdef USE_INT_PIO |
| if (gpio_get_value(spi->irq_io)) |
| goto end; |
| #endif |
| |
| init_reg_hdr(spi->buf, CIMAX_CAM_EVT, 0x100, 0); |
| err = spi_write(dev, spi->buf, CIMAX_REG_HDR_SIZE); |
| if (err) |
| goto end; |
| err = cimax_spi_get_resp(spi, CAM_TIMEOUT); |
| if (err) |
| goto end; |
| switch (hdr_cmd_resp(spi)) { |
| case CIMAX_CAM_DET_OK: { |
| int slot = cam_hdr_slot(spi); |
| int insert = cam_dat(spi)[0]; |
| if (!!spi->cam_inserted[slot] != insert) { |
| spi->cam_inserted[slot] = insert; |
| cimax_spi_cam_plugin(spi, slot, insert); |
| aml_cimax_slot_state_changed(spi->cimax, slot, |
| spi->cam_inserted[slot]); |
| } |
| } break; |
| case CIMAX_CAM_DATA_READY: { |
| int slot = cam_hdr_slot(spi); |
| spi->cam_data_ready[slot] = 1; |
| } break; |
| case CIMAX_CAM_NOEVT: |
| break; |
| default: |
| pr_error("unknown resp:%02x\n", hdr_cmd_resp(spi)); |
| break; |
| } |
| end: |
| queue_delayed_work(spi->workq, &spi->work, spi_poll_interval); |
| lock_unlock(spi); |
| return 0; |
| } |
| |
| static void cimax_spi_poll_work(struct work_struct *work) |
| { |
| struct cimax_spi *spi = |
| container_of(to_delayed_work(work), struct cimax_spi, work); |
| spi->work_cnt++; |
| cimax_spi_poll(spi); |
| } |
| |
| static irqreturn_t cimax_irq_handler(int irq, void *para) |
| { |
| return IRQ_HANDLED; |
| } |
| |
| #define CTRL_DISABLE -1 |
| #define CTRL_STOP 0 |
| #define CTRL_START 1 |
| |
| static inline int cimax_spi_poll_ctrl(struct cimax_spi *spi, int ctrl) |
| { |
| if (ctrl == CTRL_START) { |
| spi->workq = create_singlethread_workqueue("cimax_spi"); |
| INIT_DELAYED_WORK(&spi->work, &cimax_spi_poll_work); |
| queue_delayed_work(spi->workq, |
| &spi->work, spi_poll_interval); |
| pr_dbg("poll started\n"); |
| } else { |
| if (!spi->workq) |
| return 0; |
| cancel_delayed_work_sync(&spi->work); |
| destroy_workqueue(spi->workq); |
| spi->workq = NULL; |
| pr_dbg("poll stopped\n"); |
| } |
| return 0; |
| } |
| |
| static inline int cimax_spi_intr_ctrl(struct cimax_spi *spi, int ctrl) |
| { |
| if (ctrl == CTRL_START) { |
| int ret; |
| if (spi->irq == -1) { |
| pr_error("incorrect irq"); |
| return -1; |
| } |
| ret = request_irq(spi->irq, cimax_irq_handler, |
| IRQF_SHARED|IRQF_TRIGGER_RISING, |
| "cimax irq", spi); |
| enable_irq(spi->irq); |
| } else { |
| if (spi->irq == -1) |
| return 0; |
| disable_irq(spi->irq); |
| free_irq(spi->irq, spi); |
| } |
| return 0; |
| } |
| |
| static int cimax_spi_setup_poll(struct cimax_spi *spi, int poll_mode) |
| { |
| if (poll_mode == spi->poll_mode) |
| return 0; |
| switch (poll_mode) { |
| case POLL_MODE: |
| if (spi->poll_mode == INT_MODE) |
| cimax_spi_intr_ctrl(spi, CTRL_DISABLE); |
| cimax_spi_poll_ctrl(spi, CTRL_START); |
| spi->poll_mode = POLL_MODE; |
| break; |
| case INT_MODE: |
| if (spi->poll_mode == POLL_MODE) |
| cimax_spi_poll_ctrl(spi, CTRL_DISABLE); |
| cimax_spi_intr_ctrl(spi, CTRL_START); |
| spi->poll_mode = INT_MODE; |
| break; |
| case STOP_MODE: |
| if (spi->poll_mode == POLL_MODE) |
| cimax_spi_poll_ctrl(spi, CTRL_DISABLE); |
| else if (spi->poll_mode == INT_MODE) |
| cimax_spi_intr_ctrl(spi, CTRL_DISABLE); |
| spi->poll_mode = STOP_MODE; |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int cimax_spi_hw_reset(struct cimax_spi *spi, int reset_val) |
| { |
| /*trigger reset io*/ |
| if (spi->rst_io) { |
| gpio_direction_output(spi->rst_io, reset_val ? 1 : 0); |
| msleep(50); |
| gpio_direction_output(spi->rst_io, reset_val ? 0 : 1); |
| } |
| return 0; |
| } |
| |
| |
| enum regOperation_e { |
| /** Read register. */ |
| REG_OP_READ, |
| /** Write register. */ |
| REG_OP_WRITE, |
| /** Read register until some bits are set. */ |
| REG_OP_WAIT_TO_BE_SET, |
| /** Read register until some bits are cleared. */ |
| REG_OP_WAIT_TO_BE_CLEARED, |
| /** Read register until it's value is not equal to defined. */ |
| REG_OP_WAIT_EQUAL, |
| /** Perform logical AND over register. */ |
| REG_OP_LOGICAL_AND, |
| /** Perform logical OR over register. */ |
| REG_OP_LOGICAL_OR, |
| /** Wait timeout in miliseconds. */ |
| REG_OP_WAIT |
| }; |
| |
| struct regSettings_s { |
| /** CIMaX+ register address. */ |
| u16 reg; |
| /** CIMaX+ register value. */ |
| u16 val; |
| /** CIMaX+ register operation. */ |
| enum regOperation_e op; |
| }; |
| |
| static struct regSettings_s spiRegSettings[] = { |
| /** TS interface init. */ |
| {IN_SEL, 0x00, REG_OP_WRITE}, /** Close TS input. */ |
| {OUT_SEL, 0x00, REG_OP_WRITE}, /** Close TS output. */ |
| {FIFO_CTRL, 0x0f, REG_OP_WRITE}, /** Reset TS FIFO. */ |
| {SYNC_RTV_CTRL, 0x0f, REG_OP_WRITE}, |
| |
| /** CAM power. */ |
| {GPIO0_DATA_OUT, 0x00, REG_OP_WRITE}, |
| /** Unlock CFG. */ |
| {CFG_2, 0x00, REG_OP_WRITE}, |
| /** 1) DVB/CI/CI+/SCARD 2slot. */ |
| {CFG_1, 0x00, REG_OP_WRITE}, |
| /** 2) Set the Default "power off" state |
| such as VCC_MODA=VCC_MODB=VPPx_MODA=VPPx_MODB='Z'. */ |
| {GPIO0_DFT, 0x00, REG_OP_WRITE}, |
| /** 3) Set GPIO3 as external power switch driver. */ |
| {GPIO0_MASK_DATA, 0x07, REG_OP_WRITE}, |
| /** 4) Set "power on" state (VCC=VPP1=VPP2= 5V). */ |
| {GPIO0_DATA_OUT, 0x03, REG_OP_WRITE}, |
| /** 5) Lock config. */ |
| {CFG_2, 0x01, REG_OP_WRITE}, |
| /** 6) Write in the GPIO0_DIR_REG: defines the GPIOs, |
| which are used to drive the external power switch, in output mode. */ |
| {GPIO0_DIR, 0x07, REG_OP_WRITE}, |
| /** 7) Check VCCENable. */ |
| {CFG_1, 0x20, REG_OP_WAIT_TO_BE_SET}, |
| /** 8) Set & wait for PcmciaOutputEnable. */ |
| {CFG_1, 0x08, REG_OP_LOGICAL_OR}, |
| {CFG_1, 0x08, REG_OP_WAIT_TO_BE_SET}, |
| |
| /** Set router CAM. */ |
| /** CH0 & CH1 from CAM A & B, CAM A & B from CH0 & CH1. */ |
| {ROUTER_CAM_MOD, 0x21, REG_OP_WRITE}, |
| {ROUTER_CAM_CH, 0x00, REG_OP_WRITE}, |
| /** Wait 200 miliseconds. */ |
| {0x0000, 200, REG_OP_WAIT}, |
| |
| /** Set In/Out. */ |
| /** Route CAM Channel 0 to Channel 0, Channel 1 null. */ |
| {ROUTER_CAM_CH, 0x80, REG_OP_WRITE}, |
| |
| #ifdef PARALLEL_OUT |
| #else |
| #if 1 |
| /*72M internal clock source*/ |
| /*CLK Select SER0->72M*/ |
| {CkMan_Select, 0x20, REG_OP_WRITE}, |
| /*Enable SER0 clk source, Enable 72M clk source*/ |
| {CkMan_Config, 0x44, REG_OP_LOGICAL_OR}, |
| #else |
| /*108M internal clock source*/ |
| /*CLK Select SER0->108M*/ |
| {CkMan_Select, 0x30, REG_OP_WRITE}, |
| /*Enable SER0 clk source, Enable 108M clk source*/ |
| {CkMan_Config, 0x48, REG_OP_LOGICAL_OR}, |
| #endif |
| {P2S_CH0_CTRL, 0x19, REG_OP_WRITE}, /*Enable p2s*/ |
| {OUT_SEL, 0x02, REG_OP_WRITE}, /*Out1=p2s0*/ |
| #endif /*Parallel out*/ |
| |
| /** Input Ch0=Parallel, Ch1=null. */ |
| {IN_SEL, 0x01, REG_OP_WRITE}, |
| }; |
| |
| int downloadCfg(struct cimax_spi *spi) |
| { |
| u32 cnt; |
| u8 buf[CIMAX_REG_PLD_SIZE]; |
| struct aml_cimax *cimax = spi->cimax; |
| |
| pr_info("Download CIMaX+ configuration(register settings):\n"); |
| |
| for (cnt = 0; cnt < sizeof(spiRegSettings)/sizeof(struct regSettings_s); |
| cnt++) { |
| pr_dbg("reg:%04x, val:%02x, op:%d\n", |
| spiRegSettings[cnt].reg, |
| spiRegSettings[cnt].val, |
| spiRegSettings[cnt].op); |
| switch (spiRegSettings[cnt].op) { |
| case REG_OP_READ: |
| /* Read register. */ |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("FAILED at REG_OP_READ operation.\n"); |
| return -1; |
| } |
| break; |
| case REG_OP_WRITE: |
| /* Write register. */ |
| if (aml_cimax_spi_write_reg(cimax, |
| spiRegSettings[cnt].reg, |
| (u8 *)&spiRegSettings[cnt].val, |
| 1) < 0) { |
| /* CIMaX+ write error. */ |
| pr_error("FAILED at REG_OP_WRITE operation.\n"); |
| return -1; |
| } |
| break; |
| case REG_OP_WAIT_TO_BE_SET: |
| do { |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("E REG_OP_WAIT_TO_BE_SET\n"); |
| return -1; |
| } |
| } while ((buf[0] & spiRegSettings[cnt].val) |
| != spiRegSettings[cnt].val); |
| break; |
| case REG_OP_WAIT_TO_BE_CLEARED: |
| do { |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("REG_OP_WAIT_TO_BE_CLEARED\n"); |
| return -1; |
| } |
| } while ((buf[0] & spiRegSettings[cnt].val) != 0); |
| break; |
| case REG_OP_WAIT_EQUAL: |
| do { |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("REG_OP_WAIT_EQUAL.\n"); |
| return -1; |
| } |
| } while (buf[0] != spiRegSettings[cnt].val); |
| break; |
| case REG_OP_LOGICAL_AND: |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("FAILED at REG_OP_LOGICAL_AND(r).\n"); |
| return -1; |
| } |
| buf[0] &= spiRegSettings[cnt].val; |
| if (aml_cimax_spi_write_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ write error. */ |
| pr_error("FAILED at REG_OP_LOGICAL_AND(w).\n"); |
| return -1; |
| } |
| break; |
| case REG_OP_LOGICAL_OR: |
| if (aml_cimax_spi_read_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ read error. */ |
| pr_error("FAILED at REG_OP_LOGICAL_OR(r).\n"); |
| return -1; |
| } |
| buf[0] |= spiRegSettings[cnt].val; |
| if (aml_cimax_spi_write_reg(cimax, |
| spiRegSettings[cnt].reg, buf, 1) < 0) { |
| /* CIMaX+ write error. */ |
| pr_error("FAILED at REG_OP_LOGICAL_AND(w).\n"); |
| return -1; |
| } |
| break; |
| case REG_OP_WAIT: |
| msleep(spiRegSettings[cnt].val); |
| break; |
| default: |
| pr_error("\nInvalid operation 0x%02x!\n", |
| spiRegSettings[cnt].op); |
| } |
| } |
| pr_info("config OK.\n"); |
| return 0; |
| } |
| |
| |
| #define CIMAX_FW_PKT_SIZE 128 |
| #define CIMAX_FW_START_ADDR 0x8000 |
| #define CIMAX_FW_STOP_ADDR 0xcff9 |
| #define CIMAX_FW_VECT_ADDR 0xfffa |
| #define CIMAX_FW_VECT_SIZE 6 |
| |
| static u32 compute_bistrom(const u8 *ptr, int size, u32 sign) |
| { |
| int k, i; |
| u16 s; |
| |
| for (k = 0; k < size; k++) { |
| s = ptr[k]&0x01; |
| for (i = 0; i < 16; i++) |
| if (0x88B7 & (1<<i)) |
| s ^= (sign>>i) & 0x01; |
| s |= ((sign<<1) ^ (ptr[k])) & 0x00FE; |
| s |= (sign<<1) & 0x00FF00; |
| sign = s; |
| } |
| return sign; |
| } |
| |
| static int cimax_spi_upload_firmware(struct cimax_spi *spi, |
| const u8 *fw_data, u32 *sign) |
| { |
| struct aml_cimax *cimax = spi->cimax; |
| int err = 0; |
| int addr; |
| const u8 *ptr; |
| int size; |
| int debug = cimax_spi_debug; |
| u8 *ptmp = kzalloc(CIMAX_FW_PKT_SIZE + CIMAX_REG_HDR_SIZE, GFP_KERNEL); |
| |
| if (!ptmp) |
| return -ENOMEM; |
| |
| cimax_spi_debug = 0; |
| |
| addr = CIMAX_FW_START_ADDR; |
| ptr = fw_data + addr; |
| while (addr < CIMAX_FW_STOP_ADDR) { |
| size = (addr <= (CIMAX_FW_STOP_ADDR+1-CIMAX_FW_PKT_SIZE)) ? |
| CIMAX_FW_PKT_SIZE : (CIMAX_FW_STOP_ADDR+1-addr); |
| |
| *sign = compute_bistrom(ptr, size, *sign); |
| |
| pr_dbg(">>%x@%x\n", size, addr); |
| |
| /*dump("w:", (u8*)ptr, size);*/ |
| err = aml_cimax_spi_write_reg(cimax, addr, (u8 *)ptr, size); |
| if (err) |
| break; |
| err = aml_cimax_spi_read_reg(cimax, addr, ptmp, size); |
| if (err) |
| break; |
| /*dump("r:", ptmp, size);*/ |
| if (memcmp(ptr, ptmp, size)) { |
| pr_error("fw write error.\n"); |
| err = -ENODEV; |
| break; |
| } |
| |
| addr += size; |
| ptr += size; |
| } |
| |
| if (!err) { |
| addr = CIMAX_FW_VECT_ADDR; |
| ptr = fw_data + addr; |
| size = CIMAX_FW_VECT_SIZE; |
| |
| *sign = compute_bistrom(ptr, size, *sign); |
| |
| err = aml_cimax_spi_write_reg(cimax, addr, (u8 *)ptr, size); |
| if (err) |
| goto end; |
| err = aml_cimax_spi_read_reg(cimax, addr, ptmp, size); |
| if (err) |
| goto end; |
| if (memcmp(ptr, ptmp, size)) { |
| pr_error("fw vect write error.\n"); |
| err = -ENODEV; |
| goto end; |
| } |
| } |
| end: |
| kfree(ptmp); |
| |
| cimax_spi_debug = debug; |
| return err; |
| } |
| |
| static int cimax_spi_check_bistrom(struct cimax_spi *spi, |
| int start, int end, u32 sign) |
| { |
| struct aml_cimax *cimax = spi->cimax; |
| int err = 0; |
| u8 buf[2]; |
| |
| buf[0] = (0xd000-start) & 0xff; |
| buf[1] = (0xd000-start) >> 8; |
| err = aml_cimax_spi_write_reg(cimax, 0x8d, buf, 2); |
| if (err) |
| return err; |
| buf[0] = sign & 0xff; |
| buf[1] = sign >> 8; |
| err = aml_cimax_spi_write_reg(cimax, 0x80, buf, 2); |
| if (err) |
| return err; |
| buf[0] = 0xf; |
| err = aml_cimax_spi_write_reg(cimax, 0x82, buf, 1); |
| if (err) |
| return err; |
| err = aml_cimax_spi_read_reg(cimax, 0x41, buf, 2); |
| if (err) |
| return err; |
| pr_dbg("bist checked: 0x%04x\n", byte_to_u16(buf[0], buf[1])); |
| err = aml_cimax_spi_read_reg(cimax, 0x09, buf, 1); |
| if (err) |
| return err; |
| pr_dbg("rom status: 0x%02x\n", buf[0]); |
| return buf[0]; |
| } |
| |
| static int cimax_spi_init_firmware(struct cimax_spi *spi) |
| { |
| struct spi_device *dev = spi->dev; |
| int err = 0; |
| |
| init_reg_hdr(spi->buf, CIMAX_REG_INIT, 0, 0); |
| err = spi_write(dev, spi->buf, CIMAX_REG_HDR_SIZE); |
| if (err) |
| return err; |
| err = cimax_spi_get_resp(spi, REG_TIMEOUT); |
| if (err) |
| return err; |
| if (check_reg_hdr(spi->buf, CIMAX_REG_INIT_OK, 0, 0)) { |
| perr("init fw fail.", spi); |
| return -EINVAL; |
| } |
| return err; |
| } |
| |
| static void request_fw_callback(const struct firmware *fw, void *context) |
| { |
| u32 sign = 0; |
| int err = 0; |
| struct cimax_spi *spi = (struct cimax_spi *)context; |
| |
| if (!fw) |
| return; |
| |
| pr_dbg("got fw: %zd @ %p\n", fw->size, fw->data); |
| |
| /*cimax_spi_hw_reset(spi, 1);*/ |
| |
| err = cimax_spi_upload_firmware(spi, fw->data, &sign); |
| if (err) |
| goto end; |
| pr_dbg("upload fw done.\n"); |
| err = cimax_spi_check_bistrom(spi, |
| CIMAX_FW_START_ADDR, CIMAX_FW_STOP_ADDR, sign); |
| if (err != 0x2) |
| goto end; |
| pr_dbg("check bistrom done.\n"); |
| err = cimax_spi_init_firmware(spi); |
| if (err) |
| goto end; |
| end: |
| if (fw) |
| release_firmware(fw); |
| if (err) |
| return; |
| |
| if (downloadCfg(spi)) { |
| pr_error("download config fail.\n"); |
| return; |
| } |
| |
| cimax_spi_setup_poll(spi, cimax_poll_mode ? POLL_MODE : INT_MODE); |
| |
| return; |
| } |
| |
| static int cimax_spi_load_fw(struct cimax_spi *spi) |
| { |
| char *name = "cimax_spidvb.bin"; |
| return request_firmware_nowait(THIS_MODULE, 1, name, |
| &spi->dev->dev, GFP_KERNEL, spi, request_fw_callback); |
| } |
| |
| static int cimax_spi_dev_probe(struct spi_device *spi) |
| { |
| int ret; |
| struct cimax_spi *cimax_spi; |
| |
| pr_dbg("dev probe\n"); |
| /*setup again?*/ |
| spi->bits_per_word = 8; |
| ret = spi_setup(spi); |
| if (ret) |
| pr_dbg("spi setup failed\n"); |
| |
| cimax_spi = dev_get_platdata(&spi->dev); |
| cimax_spi->dev = spi; |
| |
| spi_set_drvdata(spi, cimax_spi); |
| |
| return cimax_spi_load_fw(cimax_spi); |
| } |
| |
| static int cimax_spi_dev_remove(struct spi_device *spi) |
| { |
| struct cimax_spi *cimax_spi = dev_get_drvdata(&spi->dev); |
| |
| pr_dbg("dev remove\n"); |
| cimax_spi_setup_poll(cimax_spi, STOP_MODE); |
| return 0; |
| } |
| |
| static int cimax_spi_get_config_from_dts(struct cimax_spi *spi, |
| struct spi_board_info *bdinfo) |
| { |
| struct device_node *child = NULL; |
| struct platform_device *pdev = spi->pdev; |
| struct device_node *np = pdev->dev.of_node; |
| unsigned int val; |
| int ret = 0; |
| pr_dbg("fetch cimax spi in dts\n"); |
| |
| child = of_get_child_by_name(np, "cimax"); |
| if (child == NULL) { |
| pr_error("cimax not found in dts\n"); |
| return -1; |
| } |
| child = of_get_child_by_name(child, "spi"); |
| if (!child) { |
| pr_error("spi not found in cimax"); |
| return -1; |
| } |
| |
| /* get spi config */ |
| ret = of_property_read_u32(child, "bus_num", &val); |
| if (ret) |
| pr_error("bus_num not found, use default.\n"); |
| else |
| bdinfo->bus_num = val; |
| pr_dbg("bus_num: %d\n", bdinfo->bus_num); |
| ret = of_property_read_u32(child, "chip_select", &val); |
| if (ret) |
| pr_error("chip_select not found, use default.\n"); |
| else |
| bdinfo->chip_select = val; |
| pr_dbg("chip_select: %d\n", bdinfo->chip_select); |
| ret = of_property_read_u32(child, "max_frequency", &val); |
| if (ret) |
| pr_error("max_frequency not found, use default.\n"); |
| else |
| bdinfo->max_speed_hz = val; |
| pr_dbg("max_speed_hz: %d\n", bdinfo->max_speed_hz); |
| ret = of_property_read_u32(child, "mode", &val); |
| if (ret) |
| pr_error("mode not found, use default.\n"); |
| else |
| bdinfo->mode = val; |
| pr_dbg("mode: %d\n", bdinfo->mode); |
| /* |
| dvbci { |
| compatible = "amlogic, dvbci"; |
| dev_name = "dvbci"; |
| io_type = <2>;//0:iobus,1:spi,2:cimax |
| cimax { |
| io_type = <0> //0:spi 1:usb |
| spi { |
| spi_bus_num = <0>; |
| spi_chip_select = <0>; |
| spi_max_frequency = <3000000>; |
| |
| rst_gpio = <&gpio_ao GPIOAO_2 GPIO_ACTIVE_HIGH>; |
| |
| irq_gpio = <&gpio_ao GPIOAO_2 GPIO_ACTIVE_HIGH>; |
| irq = <2>; |
| interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>; |
| }; |
| }; |
| |
| }; |
| */ |
| #ifdef USE_INT_PIO |
| { |
| int ret = 0; |
| int gpio = -1; |
| gpio = of_get_named_gpio_flags(child, "irq-gpios", 0, NULL); |
| ret = gpio_request(gpio, "cimax-irq"); |
| if (ret < 0) { |
| pr_error("irq-gpios request fail.\n"); |
| return ret; |
| } |
| |
| ret = gpio_direction_input(gpio); |
| |
| spi->irq_io = gpio; |
| pr_dbg("irq_io: %d\n", spi->irq_io); |
| } |
| #ifdef CIMAX_IRQ |
| { |
| int irq; |
| unsigned int irqflag; |
| ret = of_property_read_u32(child, "irq", &gpio_irq); |
| irq = irq_of_parse_and_map(child, 0); |
| |
| gpio_for_irq(gpio, |
| AML_GPIO_IRQ(gpio_irq, FILTER_NUM7, GPIO_IRQ_FALLING)); |
| spi->irq = irq; |
| pr_dbg("irq: %d\n", spi->irq); |
| } |
| #else |
| spi->irq = -1; |
| #endif |
| #endif/*USE_INT_PIO*/ |
| { |
| int ret = 0; |
| int gpio = -1; |
| gpio = of_get_named_gpio_flags(child, "rst-gpios", 0, NULL); |
| if (gpio != -1) { |
| ret = gpio_request(gpio, "cimax"); |
| if (ret < 0) { |
| pr_error("rst-gpios request fail.\n"); |
| return ret; |
| } |
| |
| cimax_spi_hw_reset(spi, 1); |
| |
| spi->rst_io = gpio; |
| pr_dbg("rst: %d\n", spi->rst_io); |
| } else { |
| pr_error("rst io got fail, %d\n", gpio); |
| } |
| } |
| return 0; |
| } |
| |
| static struct spi_board_info cimax_spi_bdinfo = { |
| .modalias = "cimax_spi", |
| .mode = SPI_MODE_3, |
| .max_speed_hz = 1000000, /* 1MHz */ |
| .bus_num = 0, /* SPI bus No. */ |
| .chip_select = 0, /* the device index on the spi bus */ |
| .controller_data = NULL, |
| }; |
| |
| static struct spi_driver cimax_spi_dev_driver = { |
| .probe = cimax_spi_dev_probe, |
| .remove = cimax_spi_dev_remove, |
| .driver = { |
| .name = "cimax_spi", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| int aml_cimax_spi_init(struct platform_device *pdev, struct aml_cimax *cimax) |
| { |
| int ret; |
| struct cimax_spi *cimax_spi; |
| |
| cimax_spi = kzalloc(sizeof(struct cimax_spi), GFP_KERNEL); |
| if (!cimax_spi) |
| return -ENOMEM; |
| |
| cimax_spi->pdev = pdev; |
| cimax_spi->cimax = cimax; |
| cimax_spi_get_config_from_dts(cimax_spi, &cimax_spi_bdinfo); |
| |
| /*init spi_lock*/ |
| lock_init(cimax_spi); |
| |
| /*register device*/ |
| cimax_spi_bdinfo.platform_data = cimax_spi; |
| spi_register_board_info(&cimax_spi_bdinfo, 1); |
| |
| /*register driver*/ |
| ret = spi_register_driver(&cimax_spi_dev_driver); |
| if (ret) { |
| pr_error("register cimax spi driver failed\n"); |
| return ret; |
| } |
| |
| /*init cimax used api.*/ |
| #define WI(_f)\ |
| cimax->ops._f = aml_cimax_spi_##_f |
| WI(read_cis); |
| WI(write_cor); |
| WI(negotiate); |
| WI(read_lpdu); |
| WI(write_lpdu); |
| WI(read_cam_status); |
| WI(cam_reset); |
| WI(slot_reset); |
| WI(slot_shutdown); |
| WI(slot_ts_enable); |
| WI(slot_status); |
| /*WI(start);*/ |
| /*WI(stop);*/ |
| WI(read_reg); |
| WI(write_reg); |
| |
| cimax->priv = cimax_spi; |
| |
| g_spi = cimax_spi; |
| |
| aml_cimax_spi_mod_init(); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_cimax_spi_init); |
| |
| int aml_cimax_spi_exit(struct aml_cimax *cimax) |
| { |
| struct cimax_spi *spi = cimax_to_spi(cimax); |
| |
| if (!spi) |
| return -ENODEV; |
| |
| aml_cimax_spi_mod_exit(); |
| |
| /*unregister driver*/ |
| spi_unregister_driver(&cimax_spi_dev_driver); |
| /*unregister device*/ |
| spi_unregister_device(spi->dev); |
| |
| if (spi->irq_io) |
| gpio_free(spi->irq_io); |
| if (spi->rst_io) |
| gpio_free(spi->rst_io); |
| |
| kfree(spi->cis); |
| |
| kfree(spi); |
| cimax->priv = NULL; |
| |
| g_spi = NULL; |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_cimax_spi_exit); |
| |
| static int cimax_spi_reset(struct cimax_spi *spi, int reset_val) |
| { |
| pr_dbg("reset spi:%p, rst:%d\n", spi, spi ? spi->rst_io : -1); |
| if (!spi) |
| return -ENODEV; |
| |
| pr_dbg("cimax spi reset\n"); |
| |
| cimax_spi_setup_poll(spi, STOP_MODE); |
| |
| cimax_spi_hw_reset(spi, reset_val); |
| |
| /*notify unplugged*/ |
| aml_cimax_camchanged(spi->cimax, 0, 0); |
| aml_cimax_camchanged(spi->cimax, 1, 0); |
| |
| spi->cam_inserted[0] = spi->cam_inserted[1] = 0; |
| spi->cam_data_ready[0] = spi->cam_data_ready[1] = 0; |
| |
| /*async start fw*/ |
| cimax_spi_load_fw(spi); |
| |
| /*cimax_spi_setup_poll(spi, cimax_poll_mode? POLL_MODE : INT_MODE);*/ |
| return 0; |
| } |
| |
| static ssize_t reset_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret; |
| ret = sprintf(buf, "echo 1 > %s\n", attr->attr.name); |
| return ret; |
| } |
| |
| static ssize_t reset_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| int ret; |
| int val = 0; |
| if (!g_spi) |
| return size; |
| ret = sscanf(buf, "%i", &val); |
| if (ret == 1) |
| ret = cimax_spi_reset(g_spi, val); |
| return size; |
| } |
| |
| static ssize_t debug_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| if (!g_spi) |
| return ret; |
| |
| ret = sprintf(buf, "poll mode: %d\n", g_spi->poll_mode); |
| ret += sprintf(buf+ret, "status slot[0]=[%d] slot[1]=[%d]\n", |
| g_spi->cam_inserted[0], g_spi->cam_inserted[1]); |
| ret += sprintf(buf+ret, "data slot[0]=[%d] slot[1]=[%d]\n", |
| g_spi->cam_data_ready[0], g_spi->cam_data_ready[1]); |
| ret += sprintf(buf+ret, "work cnt:%d\n", g_spi->work_cnt); |
| return ret; |
| } |
| |
| static int reg_addr; |
| static ssize_t addr_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| ret = sprintf(buf, "addr = 0x%04x\n", reg_addr); |
| return ret; |
| } |
| |
| static ssize_t addr_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| if (!g_spi) |
| return size; |
| if (sscanf(buf, "%i", ®_addr) == 1) |
| return size; |
| return size; |
| } |
| |
| static ssize_t reg_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| u8 reg_val = 0; |
| struct aml_cimax *cimax = NULL; |
| |
| if (!g_spi) |
| return ret; |
| |
| cimax = g_spi->cimax; |
| ret = aml_cimax_spi_read_reg(cimax, reg_addr, ®_val, 1); |
| if (ret) |
| ret = sprintf(buf, "read fail, err=%d\n", ret); |
| else |
| ret = sprintf(buf, "reg[0x%04x] = 0x%02x\n", reg_addr, reg_val); |
| return ret; |
| } |
| |
| static ssize_t reg_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| int ret = 0; |
| struct aml_cimax *cimax = NULL; |
| int val = 0; |
| u8 reg_val = 0; |
| |
| if (!g_spi) |
| return size; |
| |
| if (sscanf(buf, "%i", &val) != 1) |
| return size; |
| reg_val = val; |
| cimax = g_spi->cimax; |
| ret = aml_cimax_spi_write_reg(cimax, reg_addr, ®_val, 1); |
| if (ret) |
| return ret; |
| return size; |
| } |
| |
| static int cis_mode; /*0:hex 1:binary*/ |
| static ssize_t cis_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| if (!g_spi || !g_spi->cis) |
| return ret; |
| |
| if (cis_mode == 0) { |
| int i; |
| for (i = 0; i < CIS_MAX; i++) { |
| if (i && !(i & 0xf)) |
| ret += sprintf(buf+ret, "\n"); |
| ret += sprintf(buf+ret, "%02X ", g_spi->cis[i]); |
| } |
| ret += sprintf(buf+ret, "\n"); |
| return ret; |
| } else { |
| memcpy(buf, g_spi->cis, CIS_MAX); |
| return CIS_MAX; |
| } |
| return ret; |
| } |
| |
| static ssize_t cis_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| if (size >= 3 |
| && !memcmp(buf, "bin", 3)) |
| cis_mode = 1; |
| else |
| cis_mode = 0; |
| return size; |
| } |
| |
| static ssize_t ts_rate_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| u8 lsb = 0, msb = 0, plen = 0; |
| struct aml_cimax *cimax = NULL; |
| int err = 0; |
| |
| if (!g_spi) |
| return ret; |
| |
| cimax = g_spi->cimax; |
| err = aml_cimax_spi_read_reg(cimax, PCK_LENGTH, &plen, 1); |
| err |= aml_cimax_spi_read_reg(cimax, BITRATE_CH0_LSB, &lsb, 1); |
| err |= aml_cimax_spi_read_reg(cimax, BITRATE_CH0_MSB, &msb, 1); |
| if (err || !byte_to_u16(msb, lsb)) |
| ret += sprintf(buf+ret, "read fail, err=%d\n", err); |
| else |
| ret += sprintf(buf+ret, "rate[0] = %d Kbps\n", |
| 540*plen*8/byte_to_u16(msb, lsb)); |
| if (err) |
| return ret; |
| |
| err = aml_cimax_spi_read_reg(cimax, BITRATE_CH1_LSB, &lsb, 1); |
| err |= aml_cimax_spi_read_reg(cimax, BITRATE_CH1_MSB, &msb, 1); |
| if (err || !byte_to_u16(msb, lsb)) |
| ret += sprintf(buf+ret, "read fail, err=%d\n", err); |
| else |
| ret += sprintf(buf+ret, "rate[1] = %d Kbps\n", |
| 540*plen*8/byte_to_u16(msb, lsb)); |
| return ret; |
| } |
| |
| static ssize_t loop_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| u8 ch = 0, mod = 0; |
| struct aml_cimax *cimax = NULL; |
| int err = 0; |
| |
| if (!g_spi) |
| return ret; |
| |
| cimax = g_spi->cimax; |
| err = aml_cimax_spi_read_reg(cimax, ROUTER_CAM_CH, &ch, 1); |
| err |= aml_cimax_spi_read_reg(cimax, ROUTER_CAM_MOD, &mod, 1); |
| if (err) { |
| ret = sprintf(buf, "read fail, err=%d\n", err); |
| return ret; |
| } |
| ret += sprintf(buf + ret, "OUT-0 <= "); |
| switch (ch & 0x0f) { |
| case 0x0: |
| ret += sprintf(buf + ret, "CAM-A"); break; |
| case 0x1: |
| ret += sprintf(buf + ret, "CH0-IN"); break; |
| case 0x2: |
| ret += sprintf(buf + ret, "CH1-IN"); break; |
| case 0x3: |
| ret += sprintf(buf + ret, "REMAPPER"); break; |
| case 0x4: |
| ret += sprintf(buf + ret, "PREHEADER"); break; |
| case 0x5: |
| ret += sprintf(buf + ret, "CAM-B"); break; |
| case 0x6: |
| ret += sprintf(buf + ret, "GAPREMOVER-0"); break; |
| case 0x7: |
| ret += sprintf(buf + ret, "GAPREMOVER-1"); break; |
| case 0x8: |
| ret += sprintf(buf + ret, "NONE"); break; |
| default: |
| ret += sprintf(buf + ret, "UNKNOWN"); break; |
| } |
| ret += sprintf(buf + ret, "\nCAM-A <= "); |
| switch (mod & 0x07) { |
| case 0x1: |
| ret += sprintf(buf + ret, "CH0-IN"); break; |
| case 0x2: |
| ret += sprintf(buf + ret, "CH1-IN"); break; |
| case 0x3: |
| ret += sprintf(buf + ret, "REMAPPER"); break; |
| case 0x4: |
| ret += sprintf(buf + ret, "PREHEADER"); break; |
| case 0x5: |
| ret += sprintf(buf + ret, "CAM-B"); break; |
| case 0x6: |
| ret += sprintf(buf + ret, "GAPREMOVER-0"); break; |
| case 0x7: |
| ret += sprintf(buf + ret, "GAPREMOVER-1"); break; |
| default: |
| ret += sprintf(buf + ret, "NONE"); break; |
| } |
| ret += sprintf(buf + ret, "\n"); |
| |
| return ret; |
| } |
| |
| |
| static ssize_t loop_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| int loop = 0; |
| int err = 0; |
| struct aml_cimax *cimax = NULL; |
| |
| if (!g_spi) |
| return size; |
| |
| if (sscanf(buf, "%i", &loop) == 1) { |
| int a = g_spi->cam_inserted[0]; |
| int b = g_spi->cam_inserted[1]; |
| u8 cm[2]; |
| cm[0] = loop ? (b ? 0x85 : 0x80) : 0x81;/*CH*/ |
| cm[1] = loop ? (a ? 0x51 : 0x11) : 0x00;/*MOD*/ |
| cimax = g_spi->cimax; |
| err = aml_cimax_spi_write_reg(cimax, ROUTER_CAM_CH, cm, 2); |
| } |
| return size; |
| } |
| |
| static ssize_t slot_reset_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| int err = 0; |
| int slot = 0; |
| struct aml_cimax *cimax = NULL; |
| |
| if (!g_spi) |
| return size; |
| |
| if (sscanf(buf, "%i", &slot) == 1) { |
| if (slot == 0 || slot == 1) { |
| pr_dbg("reset slot %d\n", slot); |
| cimax = g_spi->cimax; |
| err = aml_cimax_spi_slot_reset(cimax, slot); |
| } |
| } |
| return size; |
| } |
| |
| static ssize_t detect_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| int err = 0; |
| int slot = 0; |
| struct aml_cimax *cimax = NULL; |
| |
| if (!g_spi) |
| return size; |
| |
| if (sscanf(buf, "%i", &slot) == 1) { |
| if (slot == 0 || slot == 1) { |
| int addr = (!slot) ? MOD_CTRL_A : MOD_CTRL_B; |
| u8 reg = 0; |
| cimax = g_spi->cimax; |
| err = aml_cimax_spi_read_reg(cimax, addr, ®, 1); |
| g_spi->cam_inserted[slot] = reg & 1; |
| pr_dbg("detect slot(%d): %d\n", slot, reg & 1); |
| } |
| } |
| return size; |
| } |
| |
| static struct class_attribute cimax_spi_class_attrs[] = { |
| __ATTR_RW(reset), |
| __ATTR_RO(debug), |
| __ATTR_RW(addr), |
| __ATTR_RW(reg), |
| __ATTR_RW(cis), |
| __ATTR_RO(ts_rate), |
| __ATTR_RW(loop), |
| __ATTR_WO(slot_reset), |
| __ATTR_WO(detect), |
| __ATTR_NULL |
| }; |
| |
| static struct class cimax_spi_class = { |
| .name = "cimax_spi", |
| .class_attrs = cimax_spi_class_attrs, |
| }; |
| |
| static int aml_cimax_spi_mod_init(void) |
| { |
| int ret; |
| pr_dbg("Amlogic CIMAX SPI Init\n"); |
| ret = class_register(&cimax_spi_class); |
| return 0; |
| } |
| |
| static void aml_cimax_spi_mod_exit(void) |
| { |
| pr_dbg("Amlogic CIMAX SPI Exit\n"); |
| class_unregister(&cimax_spi_class); |
| } |
| |