2016-08-07 02:25:35 -07:00
/*
* H / W layer of ISHTP provider device ( ISH )
*
* Copyright ( c ) 2014 - 2016 , Intel Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*/
# include <linux/sched.h>
# include <linux/spinlock.h>
# include <linux/delay.h>
# include <linux/jiffies.h>
# include "client.h"
# include "hw-ish.h"
# include "hbm.h"
/* For FW reset flow */
static struct work_struct fw_reset_work ;
static struct ishtp_device * ishtp_dev ;
/**
* ish_reg_read ( ) - Read register
* @ dev : ISHTP device pointer
* @ offset : Register offset
*
* Read 32 bit register at a given offset
*
* Return : Read register value
*/
static inline uint32_t ish_reg_read ( const struct ishtp_device * dev ,
unsigned long offset )
{
struct ish_hw * hw = to_ish_hw ( dev ) ;
return readl ( hw - > mem_addr + offset ) ;
}
/**
* ish_reg_write ( ) - Write register
* @ dev : ISHTP device pointer
* @ offset : Register offset
* @ value : Value to write
*
* Writes 32 bit register at a give offset
*/
static inline void ish_reg_write ( struct ishtp_device * dev ,
unsigned long offset ,
uint32_t value )
{
struct ish_hw * hw = to_ish_hw ( dev ) ;
writel ( value , hw - > mem_addr + offset ) ;
}
/**
* _ish_read_fw_sts_reg ( ) - Read FW status register
* @ dev : ISHTP device pointer
*
* Read FW status register
*
* Return : Read register value
*/
static inline uint32_t _ish_read_fw_sts_reg ( struct ishtp_device * dev )
{
return ish_reg_read ( dev , IPC_REG_ISH_HOST_FWSTS ) ;
}
/**
* check_generated_interrupt ( ) - Check if ISH interrupt
* @ dev : ISHTP device pointer
*
* Check if an interrupt was generated for ISH
*
* Return : Read true or false
*/
static bool check_generated_interrupt ( struct ishtp_device * dev )
{
bool interrupt_generated = true ;
uint32_t pisr_val = 0 ;
if ( dev - > pdev - > device = = CHV_DEVICE_ID ) {
pisr_val = ish_reg_read ( dev , IPC_REG_PISR_CHV_AB ) ;
interrupt_generated =
IPC_INT_FROM_ISH_TO_HOST_CHV_AB ( pisr_val ) ;
} else {
pisr_val = ish_reg_read ( dev , IPC_REG_PISR_BXT ) ;
interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT ( pisr_val ) ;
}
return interrupt_generated ;
}
/**
* ish_is_input_ready ( ) - Check if FW ready for RX
* @ dev : ISHTP device pointer
*
* Check if ISH FW is ready for receiving data
*
* Return : Read true or false
*/
static bool ish_is_input_ready ( struct ishtp_device * dev )
{
uint32_t doorbell_val ;
doorbell_val = ish_reg_read ( dev , IPC_REG_HOST2ISH_DRBL ) ;
return ! IPC_IS_BUSY ( doorbell_val ) ;
}
/**
* set_host_ready ( ) - Indicate host ready
* @ dev : ISHTP device pointer
*
* Set host ready indication to FW
*/
static void set_host_ready ( struct ishtp_device * dev )
{
if ( dev - > pdev - > device = = CHV_DEVICE_ID ) {
if ( dev - > pdev - > revision = = REVISION_ID_CHT_A0 | |
( dev - > pdev - > revision & REVISION_ID_SI_MASK ) = =
REVISION_ID_CHT_Ax_SI )
ish_reg_write ( dev , IPC_REG_HOST_COMM , 0x81 ) ;
else if ( dev - > pdev - > revision = = REVISION_ID_CHT_B0 | |
( dev - > pdev - > revision & REVISION_ID_SI_MASK ) = =
REVISION_ID_CHT_Bx_SI | |
( dev - > pdev - > revision & REVISION_ID_SI_MASK ) = =
REVISION_ID_CHT_Kx_SI | |
( dev - > pdev - > revision & REVISION_ID_SI_MASK ) = =
REVISION_ID_CHT_Dx_SI ) {
uint32_t host_comm_val ;
host_comm_val = ish_reg_read ( dev , IPC_REG_HOST_COMM ) ;
host_comm_val | = IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81 ;
ish_reg_write ( dev , IPC_REG_HOST_COMM , host_comm_val ) ;
}
} else {
uint32_t host_pimr_val ;
host_pimr_val = ish_reg_read ( dev , IPC_REG_PIMR_BXT ) ;
host_pimr_val | = IPC_PIMR_INT_EN_BIT_BXT ;
/*
* disable interrupt generated instead of
* RX_complete_msg
*/
host_pimr_val & = ~ IPC_HOST2ISH_BUSYCLEAR_MASK_BIT ;
ish_reg_write ( dev , IPC_REG_PIMR_BXT , host_pimr_val ) ;
}
}
/**
* ishtp_fw_is_ready ( ) - Check if FW ready
* @ dev : ISHTP device pointer
*
* Check if ISH FW is ready
*
* Return : Read true or false
*/
static bool ishtp_fw_is_ready ( struct ishtp_device * dev )
{
uint32_t ish_status = _ish_read_fw_sts_reg ( dev ) ;
return IPC_IS_ISH_ILUP ( ish_status ) & &
IPC_IS_ISH_ISHTP_READY ( ish_status ) ;
}
/**
* ish_set_host_rdy ( ) - Indicate host ready
* @ dev : ISHTP device pointer
*
* Set host ready indication to FW
*/
static void ish_set_host_rdy ( struct ishtp_device * dev )
{
uint32_t host_status = ish_reg_read ( dev , IPC_REG_HOST_COMM ) ;
IPC_SET_HOST_READY ( host_status ) ;
ish_reg_write ( dev , IPC_REG_HOST_COMM , host_status ) ;
}
/**
* ish_clr_host_rdy ( ) - Indicate host not ready
* @ dev : ISHTP device pointer
*
* Send host not ready indication to FW
*/
static void ish_clr_host_rdy ( struct ishtp_device * dev )
{
uint32_t host_status = ish_reg_read ( dev , IPC_REG_HOST_COMM ) ;
IPC_CLEAR_HOST_READY ( host_status ) ;
ish_reg_write ( dev , IPC_REG_HOST_COMM , host_status ) ;
}
/**
* _ishtp_read_hdr ( ) - Read message header
* @ dev : ISHTP device pointer
*
* Read header of 32 bit length
*
* Return : Read register value
*/
static uint32_t _ishtp_read_hdr ( const struct ishtp_device * dev )
{
return ish_reg_read ( dev , IPC_REG_ISH2HOST_MSG ) ;
}
/**
* _ishtp_read - Read message
* @ dev : ISHTP device pointer
* @ buffer : message buffer
* @ buffer_length : length of message buffer
*
* Read message from FW
*
* Return : Always 0
*/
static int _ishtp_read ( struct ishtp_device * dev , unsigned char * buffer ,
unsigned long buffer_length )
{
uint32_t i ;
uint32_t * r_buf = ( uint32_t * ) buffer ;
uint32_t msg_offs ;
msg_offs = IPC_REG_ISH2HOST_MSG + sizeof ( struct ishtp_msg_hdr ) ;
for ( i = 0 ; i < buffer_length ; i + = sizeof ( uint32_t ) )
* r_buf + + = ish_reg_read ( dev , msg_offs + i ) ;
return 0 ;
}
/**
* write_ipc_from_queue ( ) - try to write ipc msg from Tx queue to device
* @ dev : ishtp device pointer
*
* Check if DRBL is cleared . if it is - write the first IPC msg , then call
* the callback function ( unless it ' s NULL )
*
* Return : 0 for success else failure code
*/
static int write_ipc_from_queue ( struct ishtp_device * dev )
{
struct wr_msg_ctl_info * ipc_link ;
unsigned long length ;
unsigned long rem ;
unsigned long flags ;
uint32_t doorbell_val ;
uint32_t * r_buf ;
uint32_t reg_addr ;
int i ;
void ( * ipc_send_compl ) ( void * ) ;
void * ipc_send_compl_prm ;
static int out_ipc_locked ;
unsigned long out_ipc_flags ;
if ( dev - > dev_state = = ISHTP_DEV_DISABLED )
return - EINVAL ;
spin_lock_irqsave ( & dev - > out_ipc_spinlock , out_ipc_flags ) ;
if ( out_ipc_locked ) {
spin_unlock_irqrestore ( & dev - > out_ipc_spinlock , out_ipc_flags ) ;
return - EBUSY ;
}
out_ipc_locked = 1 ;
if ( ! ish_is_input_ready ( dev ) ) {
out_ipc_locked = 0 ;
spin_unlock_irqrestore ( & dev - > out_ipc_spinlock , out_ipc_flags ) ;
return - EBUSY ;
}
spin_unlock_irqrestore ( & dev - > out_ipc_spinlock , out_ipc_flags ) ;
spin_lock_irqsave ( & dev - > wr_processing_spinlock , flags ) ;
/*
* if tx send list is empty - return 0 ;
* may happen , as RX_COMPLETE handler doesn ' t check list emptiness .
*/
if ( list_empty ( & dev - > wr_processing_list_head . link ) ) {
spin_unlock_irqrestore ( & dev - > wr_processing_spinlock , flags ) ;
out_ipc_locked = 0 ;
return 0 ;
}
ipc_link = list_entry ( dev - > wr_processing_list_head . link . next ,
struct wr_msg_ctl_info , link ) ;
/* first 4 bytes of the data is the doorbell value (IPC header) */
length = ipc_link - > length - sizeof ( uint32_t ) ;
doorbell_val = * ( uint32_t * ) ipc_link - > inline_data ;
r_buf = ( uint32_t * ) ( ipc_link - > inline_data + sizeof ( uint32_t ) ) ;
/* If sending MNG_SYNC_FW_CLOCK, update clock again */
if ( IPC_HEADER_GET_PROTOCOL ( doorbell_val ) = = IPC_PROTOCOL_MNG & &
IPC_HEADER_GET_MNG_CMD ( doorbell_val ) = = MNG_SYNC_FW_CLOCK ) {
2017-05-18 22:21:42 +02:00
uint64_t usec_system , usec_utc ;
2016-08-07 02:25:35 -07:00
struct ipc_time_update_msg time_update ;
struct time_sync_format ts_format ;
2017-05-18 22:21:42 +02:00
usec_system = ktime_to_us ( ktime_get_boottime ( ) ) ;
usec_utc = ktime_to_us ( ktime_get_real ( ) ) ;
2016-08-07 02:25:35 -07:00
ts_format . ts1_source = HOST_SYSTEM_TIME_USEC ;
ts_format . ts2_source = HOST_UTC_TIME_USEC ;
2016-11-05 16:15:03 +01:00
ts_format . reserved = 0 ;
2016-08-07 02:25:35 -07:00
time_update . primary_host_time = usec_system ;
time_update . secondary_host_time = usec_utc ;
time_update . sync_info = ts_format ;
memcpy ( r_buf , & time_update ,
sizeof ( struct ipc_time_update_msg ) ) ;
}
for ( i = 0 , reg_addr = IPC_REG_HOST2ISH_MSG ; i < length > > 2 ; i + + ,
reg_addr + = 4 )
ish_reg_write ( dev , reg_addr , r_buf [ i ] ) ;
rem = length & 0x3 ;
if ( rem > 0 ) {
uint32_t reg = 0 ;
memcpy ( & reg , & r_buf [ length > > 2 ] , rem ) ;
ish_reg_write ( dev , reg_addr , reg ) ;
}
/* Flush writes to msg registers and doorbell */
ish_reg_read ( dev , IPC_REG_ISH_HOST_FWSTS ) ;
/* Update IPC counters */
+ + dev - > ipc_tx_cnt ;
dev - > ipc_tx_bytes_cnt + = IPC_HEADER_GET_LENGTH ( doorbell_val ) ;
ish_reg_write ( dev , IPC_REG_HOST2ISH_DRBL , doorbell_val ) ;
out_ipc_locked = 0 ;
ipc_send_compl = ipc_link - > ipc_send_compl ;
ipc_send_compl_prm = ipc_link - > ipc_send_compl_prm ;
list_del_init ( & ipc_link - > link ) ;
list_add_tail ( & ipc_link - > link , & dev - > wr_free_list_head . link ) ;
spin_unlock_irqrestore ( & dev - > wr_processing_spinlock , flags ) ;
/*
* callback will be called out of spinlock ,
* after ipc_link returned to free list
*/
if ( ipc_send_compl )
ipc_send_compl ( ipc_send_compl_prm ) ;
return 0 ;
}
/**
* write_ipc_to_queue ( ) - write ipc msg to Tx queue
* @ dev : ishtp device instance
* @ ipc_send_compl : Send complete callback
* @ ipc_send_compl_prm : Parameter to send in complete callback
* @ msg : Pointer to message
* @ length : Length of message
*
* Recived msg with IPC ( and upper protocol ) header and add it to the device
* Tx - to - write list then try to send the first IPC waiting msg
* ( if DRBL is cleared )
* This function returns negative value for failure ( means free list
* is empty , or msg too long ) and 0 for success .
*
* Return : 0 for success else failure code
*/
static int write_ipc_to_queue ( struct ishtp_device * dev ,
void ( * ipc_send_compl ) ( void * ) , void * ipc_send_compl_prm ,
unsigned char * msg , int length )
{
struct wr_msg_ctl_info * ipc_link ;
unsigned long flags ;
if ( length > IPC_FULL_MSG_SIZE )
return - EMSGSIZE ;
spin_lock_irqsave ( & dev - > wr_processing_spinlock , flags ) ;
if ( list_empty ( & dev - > wr_free_list_head . link ) ) {
spin_unlock_irqrestore ( & dev - > wr_processing_spinlock , flags ) ;
return - ENOMEM ;
}
ipc_link = list_entry ( dev - > wr_free_list_head . link . next ,
struct wr_msg_ctl_info , link ) ;
list_del_init ( & ipc_link - > link ) ;
ipc_link - > ipc_send_compl = ipc_send_compl ;
ipc_link - > ipc_send_compl_prm = ipc_send_compl_prm ;
ipc_link - > length = length ;
memcpy ( ipc_link - > inline_data , msg , length ) ;
list_add_tail ( & ipc_link - > link , & dev - > wr_processing_list_head . link ) ;
spin_unlock_irqrestore ( & dev - > wr_processing_spinlock , flags ) ;
write_ipc_from_queue ( dev ) ;
return 0 ;
}
/**
* ipc_send_mng_msg ( ) - Send management message
* @ dev : ishtp device instance
* @ msg_code : Message code
* @ msg : Pointer to message
* @ size : Length of message
*
* Send management message to FW
*
* Return : 0 for success else failure code
*/
static int ipc_send_mng_msg ( struct ishtp_device * dev , uint32_t msg_code ,
void * msg , size_t size )
{
unsigned char ipc_msg [ IPC_FULL_MSG_SIZE ] ;
uint32_t drbl_val = IPC_BUILD_MNG_MSG ( msg_code , size ) ;
memcpy ( ipc_msg , & drbl_val , sizeof ( uint32_t ) ) ;
memcpy ( ipc_msg + sizeof ( uint32_t ) , msg , size ) ;
return write_ipc_to_queue ( dev , NULL , NULL , ipc_msg ,
sizeof ( uint32_t ) + size ) ;
}
2016-11-11 09:40:23 +08:00
# define WAIT_FOR_FW_RDY 0x1
# define WAIT_FOR_INPUT_RDY 0x2
/**
* timed_wait_for_timeout ( ) - wait special event with timeout
* @ dev : ISHTP device pointer
* @ condition : indicate the condition for waiting
* @ timeinc : time slice for every wait cycle , in ms
* @ timeout : time in ms for timeout
*
* This function will check special event to be ready in a loop , the loop
* period is specificd in timeinc . Wait timeout will causes failure .
*
* Return : 0 for success else failure code
*/
static int timed_wait_for_timeout ( struct ishtp_device * dev , int condition ,
unsigned int timeinc , unsigned int timeout )
{
bool complete = false ;
int ret ;
do {
if ( condition = = WAIT_FOR_FW_RDY ) {
complete = ishtp_fw_is_ready ( dev ) ;
} else if ( condition = = WAIT_FOR_INPUT_RDY ) {
complete = ish_is_input_ready ( dev ) ;
} else {
ret = - EINVAL ;
goto out ;
}
if ( ! complete ) {
unsigned long left_time ;
left_time = msleep_interruptible ( timeinc ) ;
timeout - = ( timeinc - left_time ) ;
}
} while ( ! complete & & timeout > 0 ) ;
if ( complete )
ret = 0 ;
else
ret = - EBUSY ;
out :
return ret ;
}
# define TIME_SLICE_FOR_FW_RDY_MS 100
# define TIME_SLICE_FOR_INPUT_RDY_MS 100
# define TIMEOUT_FOR_FW_RDY_MS 2000
# define TIMEOUT_FOR_INPUT_RDY_MS 2000
2016-08-07 02:25:35 -07:00
/**
* ish_fw_reset_handler ( ) - FW reset handler
* @ dev : ishtp device pointer
*
* Handle FW reset
*
* Return : 0 for success else failure code
*/
static int ish_fw_reset_handler ( struct ishtp_device * dev )
{
uint32_t reset_id ;
unsigned long flags ;
struct wr_msg_ctl_info * processing , * next ;
/* Read reset ID */
reset_id = ish_reg_read ( dev , IPC_REG_ISH2HOST_MSG ) & 0xFFFF ;
/* Clear IPC output queue */
spin_lock_irqsave ( & dev - > wr_processing_spinlock , flags ) ;
list_for_each_entry_safe ( processing , next ,
& dev - > wr_processing_list_head . link , link ) {
2016-08-21 15:27:29 +00:00
list_move_tail ( & processing - > link , & dev - > wr_free_list_head . link ) ;
2016-08-07 02:25:35 -07:00
}
spin_unlock_irqrestore ( & dev - > wr_processing_spinlock , flags ) ;
/* ISHTP notification in IPC_RESET */
ishtp_reset_handler ( dev ) ;
if ( ! ish_is_input_ready ( dev ) )
2016-11-11 09:40:23 +08:00
timed_wait_for_timeout ( dev , WAIT_FOR_INPUT_RDY ,
TIME_SLICE_FOR_INPUT_RDY_MS , TIMEOUT_FOR_INPUT_RDY_MS ) ;
2016-08-07 02:25:35 -07:00
/* ISH FW is dead */
if ( ! ish_is_input_ready ( dev ) )
return - EPIPE ;
/*
* Set HOST2ISH . ILUP . Apparently we need this BEFORE sending
* RESET_NOTIFY_ACK - FW will be checking for it
*/
ish_set_host_rdy ( dev ) ;
/* Send RESET_NOTIFY_ACK (with reset_id) */
ipc_send_mng_msg ( dev , MNG_RESET_NOTIFY_ACK , & reset_id ,
sizeof ( uint32_t ) ) ;
/* Wait for ISH FW'es ILUP and ISHTP_READY */
2016-11-11 09:40:23 +08:00
timed_wait_for_timeout ( dev , WAIT_FOR_FW_RDY ,
TIME_SLICE_FOR_FW_RDY_MS , TIMEOUT_FOR_FW_RDY_MS ) ;
2016-08-07 02:25:35 -07:00
if ( ! ishtp_fw_is_ready ( dev ) ) {
/* ISH FW is dead */
uint32_t ish_status ;
ish_status = _ish_read_fw_sts_reg ( dev ) ;
dev_err ( dev - > devc ,
" [ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X) \n " ,
ish_status ) ;
return - ENODEV ;
}
return 0 ;
}
2016-11-11 09:40:33 +08:00
# define TIMEOUT_FOR_HW_RDY_MS 300
2016-08-07 02:25:35 -07:00
/**
* ish_fw_reset_work_fn ( ) - FW reset worker function
* @ unused : not used
*
* Call ish_fw_reset_handler to complete FW reset
*/
static void fw_reset_work_fn ( struct work_struct * unused )
{
int rv ;
rv = ish_fw_reset_handler ( ishtp_dev ) ;
if ( ! rv ) {
/* ISH is ILUP & ISHTP-ready. Restart ISHTP */
2016-11-11 09:40:33 +08:00
msleep_interruptible ( TIMEOUT_FOR_HW_RDY_MS ) ;
2016-08-07 02:25:35 -07:00
ishtp_dev - > recvd_hw_ready = 1 ;
wake_up_interruptible ( & ishtp_dev - > wait_hw_ready ) ;
/* ISHTP notification in IPC_RESET sequence completion */
ishtp_reset_compl_handler ( ishtp_dev ) ;
} else
dev_err ( ishtp_dev - > devc , " [ishtp-ish]: FW reset failed (%d) \n " ,
rv ) ;
}
/**
* _ish_sync_fw_clock ( ) - Sync FW clock with the OS clock
* @ dev : ishtp device pointer
*
* Sync FW and OS time
*/
static void _ish_sync_fw_clock ( struct ishtp_device * dev )
{
static unsigned long prev_sync ;
uint64_t usec ;
if ( prev_sync & & jiffies - prev_sync < 20 * HZ )
return ;
prev_sync = jiffies ;
2017-05-18 22:21:42 +02:00
usec = ktime_to_us ( ktime_get_boottime ( ) ) ;
2016-08-07 02:25:35 -07:00
ipc_send_mng_msg ( dev , MNG_SYNC_FW_CLOCK , & usec , sizeof ( uint64_t ) ) ;
}
/**
* recv_ipc ( ) - Receive and process IPC management messages
* @ dev : ishtp device instance
* @ doorbell_val : doorbell value
*
* This function runs in ISR context .
* NOTE : Any other mng command than reset_notify and reset_notify_ack
* won ' t wake BH handler
*/
static void recv_ipc ( struct ishtp_device * dev , uint32_t doorbell_val )
{
uint32_t mng_cmd ;
mng_cmd = IPC_HEADER_GET_MNG_CMD ( doorbell_val ) ;
switch ( mng_cmd ) {
default :
break ;
case MNG_RX_CMPL_INDICATION :
if ( dev - > suspend_flag ) {
dev - > suspend_flag = 0 ;
wake_up_interruptible ( & dev - > suspend_wait ) ;
}
if ( dev - > resume_flag ) {
dev - > resume_flag = 0 ;
wake_up_interruptible ( & dev - > resume_wait ) ;
}
write_ipc_from_queue ( dev ) ;
break ;
case MNG_RESET_NOTIFY :
if ( ! ishtp_dev ) {
ishtp_dev = dev ;
INIT_WORK ( & fw_reset_work , fw_reset_work_fn ) ;
}
schedule_work ( & fw_reset_work ) ;
break ;
case MNG_RESET_NOTIFY_ACK :
dev - > recvd_hw_ready = 1 ;
wake_up_interruptible ( & dev - > wait_hw_ready ) ;
break ;
}
}
/**
* ish_irq_handler ( ) - ISH IRQ handler
* @ irq : irq number
* @ dev_id : ishtp device pointer
*
* ISH IRQ handler . If interrupt is generated and is for ISH it will process
* the interrupt .
*/
irqreturn_t ish_irq_handler ( int irq , void * dev_id )
{
struct ishtp_device * dev = dev_id ;
uint32_t doorbell_val ;
bool interrupt_generated ;
/* Check that it's interrupt from ISH (may be shared) */
interrupt_generated = check_generated_interrupt ( dev ) ;
if ( ! interrupt_generated )
return IRQ_NONE ;
doorbell_val = ish_reg_read ( dev , IPC_REG_ISH2HOST_DRBL ) ;
if ( ! IPC_IS_BUSY ( doorbell_val ) )
return IRQ_HANDLED ;
if ( dev - > dev_state = = ISHTP_DEV_DISABLED )
return IRQ_HANDLED ;
/* Sanity check: IPC dgram length in header */
if ( IPC_HEADER_GET_LENGTH ( doorbell_val ) > IPC_PAYLOAD_SIZE ) {
dev_err ( dev - > devc ,
" IPC hdr - bad length: %u; dropped \n " ,
( unsigned int ) IPC_HEADER_GET_LENGTH ( doorbell_val ) ) ;
goto eoi ;
}
switch ( IPC_HEADER_GET_PROTOCOL ( doorbell_val ) ) {
default :
break ;
case IPC_PROTOCOL_MNG :
recv_ipc ( dev , doorbell_val ) ;
break ;
case IPC_PROTOCOL_ISHTP :
ishtp_recv ( dev ) ;
break ;
}
eoi :
/* Update IPC counters */
+ + dev - > ipc_rx_cnt ;
dev - > ipc_rx_bytes_cnt + = IPC_HEADER_GET_LENGTH ( doorbell_val ) ;
ish_reg_write ( dev , IPC_REG_ISH2HOST_DRBL , 0 ) ;
/* Flush write to doorbell */
ish_reg_read ( dev , IPC_REG_ISH_HOST_FWSTS ) ;
return IRQ_HANDLED ;
}
2016-10-21 15:48:39 -07:00
/**
* ish_disable_dma ( ) - disable dma communication between host and ISHFW
* @ dev : ishtp device pointer
*
* Clear the dma enable bit and wait for dma inactive .
*
* Return : 0 for success else error code .
*/
static int ish_disable_dma ( struct ishtp_device * dev )
{
unsigned int dma_delay ;
/* Clear the dma enable bit */
ish_reg_write ( dev , IPC_REG_ISH_RMP2 , 0 ) ;
/* wait for dma inactive */
for ( dma_delay = 0 ; dma_delay < MAX_DMA_DELAY & &
_ish_read_fw_sts_reg ( dev ) & ( IPC_ISH_IN_DMA ) ;
dma_delay + = 5 )
mdelay ( 5 ) ;
if ( dma_delay > = MAX_DMA_DELAY ) {
dev_err ( dev - > devc ,
" Wait for DMA inactive timeout \n " ) ;
return - EBUSY ;
}
return 0 ;
}
2016-10-21 15:48:38 -07:00
/**
* ish_wakeup ( ) - wakeup ishfw from waiting - for - host state
* @ dev : ishtp device pointer
*
* Set the dma enable bit and send a void message to FW ,
* it wil wakeup FW from waiting - for - host state .
*/
static void ish_wakeup ( struct ishtp_device * dev )
{
/* Set dma enable bit */
ish_reg_write ( dev , IPC_REG_ISH_RMP2 , IPC_RMP2_DMA_ENABLED ) ;
/*
* Send 0 IPC message so that ISH FW wakes up if it was already
* asleep .
*/
ish_reg_write ( dev , IPC_REG_HOST2ISH_DRBL , IPC_DRBL_BUSY_BIT ) ;
/* Flush writes to doorbell and REMAP2 */
ish_reg_read ( dev , IPC_REG_ISH_HOST_FWSTS ) ;
}
2016-08-07 02:25:35 -07:00
/**
* _ish_hw_reset ( ) - HW reset
* @ dev : ishtp device pointer
*
* Reset ISH HW to recover if any error
*
* Return : 0 for success else error fault code
*/
static int _ish_hw_reset ( struct ishtp_device * dev )
{
struct pci_dev * pdev = dev - > pdev ;
int rv ;
uint16_t csr ;
if ( ! pdev )
return - ENODEV ;
rv = pci_reset_function ( pdev ) ;
if ( ! rv )
dev - > dev_state = ISHTP_DEV_RESETTING ;
if ( ! pdev - > pm_cap ) {
dev_err ( & pdev - > dev , " Can't reset - no PM caps \n " ) ;
return - EINVAL ;
}
2016-10-21 15:48:39 -07:00
/* Disable dma communication between FW and host */
if ( ish_disable_dma ( dev ) ) {
2016-08-07 02:25:35 -07:00
dev_err ( & pdev - > dev ,
" Can't reset - stuck with DMA in-progress \n " ) ;
return - EBUSY ;
}
pci_read_config_word ( pdev , pdev - > pm_cap + PCI_PM_CTRL , & csr ) ;
csr & = ~ PCI_PM_CTRL_STATE_MASK ;
csr | = PCI_D3hot ;
pci_write_config_word ( pdev , pdev - > pm_cap + PCI_PM_CTRL , csr ) ;
mdelay ( pdev - > d3_delay ) ;
csr & = ~ PCI_PM_CTRL_STATE_MASK ;
csr | = PCI_D0 ;
pci_write_config_word ( pdev , pdev - > pm_cap + PCI_PM_CTRL , csr ) ;
2016-10-21 15:48:38 -07:00
/* Now we can enable ISH DMA operation and wakeup ISHFW */
ish_wakeup ( dev ) ;
2016-08-07 02:25:35 -07:00
return 0 ;
}
/**
* _ish_ipc_reset ( ) - IPC reset
* @ dev : ishtp device pointer
*
* Resets host and fw IPC and upper layers
*
* Return : 0 for success else error fault code
*/
static int _ish_ipc_reset ( struct ishtp_device * dev )
{
struct ipc_rst_payload_type ipc_mng_msg ;
int rv = 0 ;
ipc_mng_msg . reset_id = 1 ;
ipc_mng_msg . reserved = 0 ;
set_host_ready ( dev ) ;
/* Clear the incoming doorbell */
ish_reg_write ( dev , IPC_REG_ISH2HOST_DRBL , 0 ) ;
/* Flush write to doorbell */
ish_reg_read ( dev , IPC_REG_ISH_HOST_FWSTS ) ;
dev - > recvd_hw_ready = 0 ;
/* send message */
rv = ipc_send_mng_msg ( dev , MNG_RESET_NOTIFY , & ipc_mng_msg ,
sizeof ( struct ipc_rst_payload_type ) ) ;
if ( rv ) {
dev_err ( dev - > devc , " Failed to send IPC MNG_RESET_NOTIFY \n " ) ;
return rv ;
}
wait_event_interruptible_timeout ( dev - > wait_hw_ready ,
dev - > recvd_hw_ready , 2 * HZ ) ;
if ( ! dev - > recvd_hw_ready ) {
dev_err ( dev - > devc , " Timed out waiting for HW ready \n " ) ;
rv = - ENODEV ;
}
return rv ;
}
/**
* ish_hw_start ( ) - Start ISH HW
* @ dev : ishtp device pointer
*
* Set host to ready state and wait for FW reset
*
* Return : 0 for success else error fault code
*/
int ish_hw_start ( struct ishtp_device * dev )
{
ish_set_host_rdy ( dev ) ;
2016-10-21 15:48:38 -07:00
/* After that we can enable ISH DMA operation and wakeup ISHFW */
ish_wakeup ( dev ) ;
2016-08-07 02:25:35 -07:00
set_host_ready ( dev ) ;
/* wait for FW-initiated reset flow */
if ( ! dev - > recvd_hw_ready )
wait_event_interruptible_timeout ( dev - > wait_hw_ready ,
dev - > recvd_hw_ready ,
10 * HZ ) ;
if ( ! dev - > recvd_hw_ready ) {
dev_err ( dev - > devc ,
" [ishtp-ish]: Timed out waiting for FW-initiated reset \n " ) ;
return - ENODEV ;
}
return 0 ;
}
/**
* ish_ipc_get_header ( ) - Get doorbell value
* @ dev : ishtp device pointer
* @ length : length of message
* @ busy : busy status
*
* Get door bell value from message header
*
* Return : door bell value
*/
static uint32_t ish_ipc_get_header ( struct ishtp_device * dev , int length ,
int busy )
{
uint32_t drbl_val ;
drbl_val = IPC_BUILD_HEADER ( length , IPC_PROTOCOL_ISHTP , busy ) ;
return drbl_val ;
}
static const struct ishtp_hw_ops ish_hw_ops = {
. hw_reset = _ish_hw_reset ,
. ipc_reset = _ish_ipc_reset ,
. ipc_get_header = ish_ipc_get_header ,
. ishtp_read = _ishtp_read ,
. write = write_ipc_to_queue ,
. get_fw_status = _ish_read_fw_sts_reg ,
. sync_fw_clock = _ish_sync_fw_clock ,
. ishtp_read_hdr = _ishtp_read_hdr
} ;
/**
* ish_dev_init ( ) - Initialize ISH devoce
* @ pdev : PCI device
*
* Allocate ISHTP device and initialize IPC processing
*
* Return : ISHTP device instance on success else NULL
*/
struct ishtp_device * ish_dev_init ( struct pci_dev * pdev )
{
struct ishtp_device * dev ;
int i ;
dev = kzalloc ( sizeof ( struct ishtp_device ) + sizeof ( struct ish_hw ) ,
GFP_KERNEL ) ;
if ( ! dev )
return NULL ;
ishtp_device_init ( dev ) ;
init_waitqueue_head ( & dev - > wait_hw_ready ) ;
spin_lock_init ( & dev - > wr_processing_spinlock ) ;
spin_lock_init ( & dev - > out_ipc_spinlock ) ;
/* Init IPC processing and free lists */
INIT_LIST_HEAD ( & dev - > wr_processing_list_head . link ) ;
INIT_LIST_HEAD ( & dev - > wr_free_list_head . link ) ;
for ( i = 0 ; i < IPC_TX_FIFO_SIZE ; + + i ) {
struct wr_msg_ctl_info * tx_buf ;
tx_buf = kzalloc ( sizeof ( struct wr_msg_ctl_info ) , GFP_KERNEL ) ;
if ( ! tx_buf ) {
/*
* IPC buffers may be limited or not available
* at all - although this shouldn ' t happen
*/
dev_err ( dev - > devc ,
" [ishtp-ish]: failure in Tx FIFO allocations (%d) \n " ,
i ) ;
break ;
}
list_add_tail ( & tx_buf - > link , & dev - > wr_free_list_head . link ) ;
}
dev - > ops = & ish_hw_ops ;
dev - > devc = & pdev - > dev ;
dev - > mtu = IPC_PAYLOAD_SIZE - sizeof ( struct ishtp_msg_hdr ) ;
return dev ;
}
/**
* ish_device_disable ( ) - Disable ISH device
* @ dev : ISHTP device pointer
*
* Disable ISH by clearing host ready to inform firmware .
*/
void ish_device_disable ( struct ishtp_device * dev )
{
2016-10-21 15:48:40 -07:00
struct pci_dev * pdev = dev - > pdev ;
if ( ! pdev )
return ;
/* Disable dma communication between FW and host */
if ( ish_disable_dma ( dev ) ) {
dev_err ( & pdev - > dev ,
" Can't reset - stuck with DMA in-progress \n " ) ;
return ;
}
/* Put ISH to D3hot state for power saving */
pci_set_power_state ( pdev , PCI_D3hot ) ;
2016-08-07 02:25:35 -07:00
dev - > dev_state = ISHTP_DEV_DISABLED ;
ish_clr_host_rdy ( dev ) ;
}