Add a default MTU size definition that new WWAN drivers can refer to.
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
include/linux/wwan.h | 5 +++++
1 file changed, 5 insertions(+)
From: Haijun Lio <haijun.liu@mediatek.com>
Cross Layer DMA (CLDMA) Hardware interface (HIF) enables the control
path of Host-Modem data transfers. CLDMA HIF layer provides a common
interface to the Port Layer.
CLDMA manages 8 independent RX/TX physical channels with data flow
control in HW queues. CLDMA uses ring buffers of General Packet
Descriptors (GPD) for TX/RX. GPDs can represent multiple or single
data buffers (DB).
CLDMA HIF initializes GPD rings, registers ISR handlers for CLDMA
interrupts, and initializes CLDMA HW registers.
CLDMA TX flow:
1. Port Layer write
2. Get DB address
3. Configure GPD
4. Triggering processing via HW register write
CLDMA RX flow:
1. CLDMA HW sends a RX "done" to host
2. Driver starts thread to safely read GPD
3. DB is sent to Port layer
4. Create a new buffer for GPD ring
Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
drivers/net/wwan/t7xx/t7xx_cldma.c | 277 +++++
drivers/net/wwan/t7xx/t7xx_cldma.h | 168 +++
drivers/net/wwan/t7xx/t7xx_hif_cldma.c | 1519 ++++++++++++++++++++++++
drivers/net/wwan/t7xx/t7xx_hif_cldma.h | 155 +++
4 files changed, 2119 insertions(+)
create mode 100644 drivers/net/wwan/t7xx/t7xx_cldma.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_cldma.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_cldma.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_cldma.h
@@ -0,0 +1,277 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/delay.h>+#include<linux/io.h>+#include<linux/io-64-nonatomic-lo-hi.h>++#include"t7xx_cldma.h"++voidcldma_clear_ip_busy(structcldma_hw_info*hw_info)+{+/* write 1 to clear IP busy register wake up CPU case */+iowrite32(ioread32(hw_info->ap_pdn_base+REG_CLDMA_IP_BUSY)|IP_BUSY_WAKEUP,+hw_info->ap_pdn_base+REG_CLDMA_IP_BUSY);+}++/**+*cldma_hw_restore()-RestoreCLDMAHWregisters+*@hw_info:Pointertostructcldma_hw_info+*+*RestoreHWafterresume.WritesuplinkconfigurationforCLDMAHW.+*+*/+voidcldma_hw_restore(structcldma_hw_info*hw_info)+{+u32ul_cfg;++ul_cfg=ioread32(hw_info->ap_pdn_base+REG_CLDMA_UL_CFG);+ul_cfg&=~UL_CFG_BIT_MODE_MASK;+if(hw_info->hw_mode==MODE_BIT_64)+ul_cfg|=UL_CFG_BIT_MODE_64;+elseif(hw_info->hw_mode==MODE_BIT_40)+ul_cfg|=UL_CFG_BIT_MODE_40;+elseif(hw_info->hw_mode==MODE_BIT_36)+ul_cfg|=UL_CFG_BIT_MODE_36;++iowrite32(ul_cfg,hw_info->ap_pdn_base+REG_CLDMA_UL_CFG);+/* disable TX and RX invalid address check */+iowrite32(UL_MEM_CHECK_DIS,hw_info->ap_pdn_base+REG_CLDMA_UL_MEM);+iowrite32(DL_MEM_CHECK_DIS,hw_info->ap_pdn_base+REG_CLDMA_DL_MEM);+}++voidcldma_hw_start_queue(structcldma_hw_info*hw_info,u8qno,boolis_rx)+{+void__iomem*reg_start_cmd;+u32val;++mb();/* prevents outstanding GPD updates */+reg_start_cmd=is_rx?hw_info->ap_pdn_base+REG_CLDMA_DL_START_CMD:+hw_info->ap_pdn_base+REG_CLDMA_UL_START_CMD;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val,reg_start_cmd);+}++voidcldma_hw_start(structcldma_hw_info*hw_info)+{+/* unmask txrx interrupt */+iowrite32(TXRX_STATUS_BITMASK,hw_info->ap_pdn_base+REG_CLDMA_L2TIMCR0);+iowrite32(TXRX_STATUS_BITMASK,hw_info->ap_ao_base+REG_CLDMA_L2RIMCR0);+/* unmask empty queue interrupt */+iowrite32(EMPTY_STATUS_BITMASK,hw_info->ap_pdn_base+REG_CLDMA_L2TIMCR0);+iowrite32(EMPTY_STATUS_BITMASK,hw_info->ap_ao_base+REG_CLDMA_L2RIMCR0);+}++voidcldma_hw_reset(void__iomem*ao_base)+{+iowrite32(ioread32(ao_base+REG_INFRA_RST4_SET)|RST4_CLDMA1_SW_RST_SET,+ao_base+REG_INFRA_RST4_SET);+iowrite32(ioread32(ao_base+REG_INFRA_RST2_SET)|RST2_CLDMA1_AO_SW_RST_SET,+ao_base+REG_INFRA_RST2_SET);+udelay(1);+iowrite32(ioread32(ao_base+REG_INFRA_RST4_CLR)|RST4_CLDMA1_SW_RST_CLR,+ao_base+REG_INFRA_RST4_CLR);+iowrite32(ioread32(ao_base+REG_INFRA_RST2_CLR)|RST2_CLDMA1_AO_SW_RST_CLR,+ao_base+REG_INFRA_RST2_CLR);+}++boolcldma_tx_addr_is_set(structcldma_hw_info*hw_info,unsignedcharqno)+{+return!!ioread64(hw_info->ap_pdn_base+REG_CLDMA_UL_START_ADDRL_0+qno*8);+}++voidcldma_hw_set_start_address(structcldma_hw_info*hw_info,unsignedcharqno,u64address,+boolis_rx)+{+void__iomem*base;++if(is_rx){+base=hw_info->ap_ao_base;+iowrite64(address,base+REG_CLDMA_DL_START_ADDRL_0+qno*8);+}else{+base=hw_info->ap_pdn_base;+iowrite64(address,base+REG_CLDMA_UL_START_ADDRL_0+qno*8);+}+}++voidcldma_hw_resume_queue(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+void__iomem*base;++base=hw_info->ap_pdn_base;+mb();/* prevents outstanding GPD updates */++if(is_rx)+iowrite32(BIT(qno),base+REG_CLDMA_DL_RESUME_CMD);+else+iowrite32(BIT(qno),base+REG_CLDMA_UL_RESUME_CMD);+}++unsignedintcldma_hw_queue_status(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+u32mask;++mask=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+if(is_rx)+returnioread32(hw_info->ap_ao_base+REG_CLDMA_DL_STATUS)&mask;+else+returnioread32(hw_info->ap_pdn_base+REG_CLDMA_UL_STATUS)&mask;+}++voidcldma_hw_tx_done(structcldma_hw_info*hw_info,unsignedintbitmask)+{+unsignedintch_id;++ch_id=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2TISAR0)&bitmask;+/* ack interrupt */+iowrite32(ch_id,hw_info->ap_pdn_base+REG_CLDMA_L2TISAR0);+ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2TISAR0);+}++voidcldma_hw_rx_done(structcldma_hw_info*hw_info,unsignedintbitmask)+{+unsignedintch_id;++ch_id=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2RISAR0)&bitmask;+/* ack interrupt */+iowrite32(ch_id,hw_info->ap_pdn_base+REG_CLDMA_L2RISAR0);+ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2RISAR0);+}++unsignedintcldma_hw_int_status(structcldma_hw_info*hw_info,unsignedintbitmask,boolis_rx)+{+void__iomem*reg_int_sta;++reg_int_sta=is_rx?hw_info->ap_pdn_base+REG_CLDMA_L2RISAR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TISAR0;++returnioread32(reg_int_sta)&bitmask;+}++voidcldma_hw_mask_txrxirq(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+void__iomem*reg_ims;+u32val;++/* select the right interrupt mask set register */+reg_ims=is_rx?hw_info->ap_ao_base+REG_CLDMA_L2RIMSR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TIMSR0;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val,reg_ims);+}++voidcldma_hw_mask_eqirq(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+void__iomem*reg_ims;+u32val;++/* select the right interrupt mask set register */+reg_ims=is_rx?hw_info->ap_ao_base+REG_CLDMA_L2RIMSR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TIMSR0;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val<<EQ_STA_BIT_OFFSET,reg_ims);+}++voidcldma_hw_dismask_txrxirq(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+void__iomem*reg_imc;+u32val;++/* select the right interrupt mask clear register */+reg_imc=is_rx?hw_info->ap_ao_base+REG_CLDMA_L2RIMCR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TIMCR0;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val,reg_imc);+}++voidcldma_hw_dismask_eqirq(structcldma_hw_info*hw_info,unsignedcharqno,boolis_rx)+{+void__iomem*reg_imc;+u32val;++/* select the right interrupt mask clear register */+reg_imc=is_rx?hw_info->ap_ao_base+REG_CLDMA_L2RIMCR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TIMCR0;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val<<EQ_STA_BIT_OFFSET,reg_imc);+}++/**+*cldma_hw_init()-InitializeCLDMAHW+*@hw_info:Pointertostructcldma_hw_info+*+*WriteuplinkanddownlinkconfigurationtoCLDMAHW.+*+*/+voidcldma_hw_init(structcldma_hw_info*hw_info)+{+u32ul_cfg;+u32dl_cfg;++ul_cfg=ioread32(hw_info->ap_pdn_base+REG_CLDMA_UL_CFG);+dl_cfg=ioread32(hw_info->ap_ao_base+REG_CLDMA_DL_CFG);++/* configure the DRAM address mode */+ul_cfg&=~UL_CFG_BIT_MODE_MASK;+dl_cfg&=~DL_CFG_BIT_MODE_MASK;+if(hw_info->hw_mode==MODE_BIT_64){+ul_cfg|=UL_CFG_BIT_MODE_64;+dl_cfg|=DL_CFG_BIT_MODE_64;+}elseif(hw_info->hw_mode==MODE_BIT_40){+ul_cfg|=UL_CFG_BIT_MODE_40;+dl_cfg|=DL_CFG_BIT_MODE_40;+}elseif(hw_info->hw_mode==MODE_BIT_36){+ul_cfg|=UL_CFG_BIT_MODE_36;+dl_cfg|=DL_CFG_BIT_MODE_36;+}++iowrite32(ul_cfg,hw_info->ap_pdn_base+REG_CLDMA_UL_CFG);+dl_cfg|=DL_CFG_UP_HW_LAST;+iowrite32(dl_cfg,hw_info->ap_ao_base+REG_CLDMA_DL_CFG);+/* enable interrupt */+iowrite32(0,hw_info->ap_ao_base+REG_CLDMA_INT_MASK);+/* mask wakeup signal */+iowrite32(BUSY_MASK_MD,hw_info->ap_ao_base+REG_CLDMA_BUSY_MASK);+/* disable TX and RX invalid address check */+iowrite32(UL_MEM_CHECK_DIS,hw_info->ap_pdn_base+REG_CLDMA_UL_MEM);+iowrite32(DL_MEM_CHECK_DIS,hw_info->ap_pdn_base+REG_CLDMA_DL_MEM);+}++voidcldma_hw_stop_queue(structcldma_hw_info*hw_info,u8qno,boolis_rx)+{+void__iomem*reg_stop_cmd;+u32val;++reg_stop_cmd=is_rx?hw_info->ap_pdn_base+REG_CLDMA_DL_STOP_CMD:+hw_info->ap_pdn_base+REG_CLDMA_UL_STOP_CMD;++val=(qno==CLDMA_ALL_Q)?CLDMA_ALL_Q:BIT(qno);+iowrite32(val,reg_stop_cmd);+}++voidcldma_hw_stop(structcldma_hw_info*hw_info,boolis_rx)+{+void__iomem*reg_ims;++/* select the right L2 interrupt mask set register */+reg_ims=is_rx?hw_info->ap_ao_base+REG_CLDMA_L2RIMSR0:+hw_info->ap_pdn_base+REG_CLDMA_L2TIMSR0;++iowrite32(TXRX_STATUS_BITMASK,reg_ims);+iowrite32(EMPTY_STATUS_BITMASK,reg_ims);+}
@@ -0,0 +1,1519 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/delay.h>+#include<linux/device.h>+#include<linux/dmapool.h>+#include<linux/dma-mapping.h>+#include<linux/io-64-nonatomic-lo-hi.h>+#include<linux/iopoll.h>+#include<linux/kernel.h>+#include<linux/list.h>+#include<linux/mutex.h>+#include<linux/skbuff.h>++#include"t7xx_cldma.h"+#include"t7xx_common.h"+#include"t7xx_hif_cldma.h"+#include"t7xx_mhccif.h"+#include"t7xx_modem_ops.h"+#include"t7xx_monitor.h"+#include"t7xx_pcie_mac.h"+#include"t7xx_skb_util.h"++#define MAX_TX_BUDGET 16+#define MAX_RX_BUDGET 16++#define CHECK_Q_STOP_TIMEOUT_US 1000000+#define CHECK_Q_STOP_STEP_US 10000++staticstructcldma_ctrl*cldma_md_ctrl[CLDMA_NUM];++staticDEFINE_MUTEX(ctl_cfg_mutex);/* protects CLDMA late init config */++staticenumcldma_queue_typerxq_type[CLDMA_RXQ_NUM];+staticenumcldma_queue_typetxq_type[CLDMA_TXQ_NUM];+staticintrxq_buff_size[CLDMA_RXQ_NUM];+staticintrxq_buff_num[CLDMA_RXQ_NUM];+staticinttxq_buff_num[CLDMA_TXQ_NUM];++staticstructcldma_ctrl*md_cd_get(enumcldma_idhif_id)+{+returncldma_md_ctrl[hif_id];+}++staticinlinevoidmd_cd_set(enumcldma_idhif_id,structcldma_ctrl*md_ctrl)+{+cldma_md_ctrl[hif_id]=md_ctrl;+}++staticinlinevoidmd_cd_queue_struct_reset(structcldma_queue*queue,enumcldma_idhif_id,+enumdirectiondir,unsignedcharindex)+{+queue->dir=dir;+queue->index=index;+queue->hif_id=hif_id;+queue->tr_ring=NULL;+queue->tr_done=NULL;+queue->tx_xmit=NULL;+}++staticinlinevoidmd_cd_queue_struct_init(structcldma_queue*queue,enumcldma_idhif_id,+enumdirectiondir,unsignedcharindex)+{+md_cd_queue_struct_reset(queue,hif_id,dir,index);+init_waitqueue_head(&queue->req_wq);+spin_lock_init(&queue->ring_lock);+}++staticinlinevoidcldma_tgpd_set_data_ptr(structcldma_tgpd*tgpd,dma_addr_tdata_ptr)+{+tgpd->data_buff_bd_ptr_h=upper_32_bits(data_ptr);+tgpd->data_buff_bd_ptr_l=lower_32_bits(data_ptr);+}++staticinlinevoidcldma_tgpd_set_next_ptr(structcldma_tgpd*tgpd,dma_addr_tnext_ptr)+{+tgpd->next_gpd_ptr_h=upper_32_bits(next_ptr);+tgpd->next_gpd_ptr_l=lower_32_bits(next_ptr);+}++staticinlinevoidcldma_rgpd_set_data_ptr(structcldma_rgpd*rgpd,dma_addr_tdata_ptr)+{+rgpd->data_buff_bd_ptr_h=upper_32_bits(data_ptr);+rgpd->data_buff_bd_ptr_l=lower_32_bits(data_ptr);+}++staticinlinevoidcldma_rgpd_set_next_ptr(structcldma_rgpd*rgpd,dma_addr_tnext_ptr)+{+rgpd->next_gpd_ptr_h=upper_32_bits(next_ptr);+rgpd->next_gpd_ptr_l=lower_32_bits(next_ptr);+}++staticstructcldma_request*cldma_ring_step_forward(structcldma_ring*ring,+structcldma_request*req)+{+structcldma_request*next_req;++if(req->entry.next==&ring->gpd_ring)+next_req=list_first_entry(&ring->gpd_ring,structcldma_request,entry);+else+next_req=list_entry(req->entry.next,structcldma_request,entry);++returnnext_req;+}++staticstructcldma_request*cldma_ring_step_backward(structcldma_ring*ring,+structcldma_request*req)+{+structcldma_request*prev_req;++if(req->entry.prev==&ring->gpd_ring)+prev_req=list_last_entry(&ring->gpd_ring,structcldma_request,entry);+else+prev_req=list_entry(req->entry.prev,structcldma_request,entry);++returnprev_req;+}++staticintcldma_gpd_rx_from_queue(structcldma_queue*queue,intbudget,bool*over_budget)+{+unsignedcharhwo_polling_count=0;+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+structcldma_request*req;+structcldma_rgpd*rgpd;+structsk_buff*new_skb;+boolrx_done=false;+structsk_buff*skb;+intcount=0;+intret=0;++md_ctrl=md_cd_get(queue->hif_id);+hw_info=&md_ctrl->hw_info;++while(!rx_done){+req=queue->tr_done;+if(!req){+dev_err(md_ctrl->dev,"RXQ was released\n");+return-ENODATA;+}++rgpd=req->gpd;+if((rgpd->gpd_flags&GPD_FLAGS_HWO)||!req->skb){+u64gpd_addr;++/* current 64 bit address is in a table by Q index */+gpd_addr=ioread64(hw_info->ap_pdn_base++REG_CLDMA_DL_CURRENT_ADDRL_0++queue->index*sizeof(u64));+if(gpd_addr==GENMASK_ULL(63,0)){+dev_err(md_ctrl->dev,"PCIe Link disconnected\n");+return-ENODATA;+}++if((u64)queue->tr_done->gpd_addr!=gpd_addr&&+hwo_polling_count++<100){+udelay(1);+continue;+}++break;+}++hwo_polling_count=0;+skb=req->skb;++if(req->mapped_buff){+dma_unmap_single(md_ctrl->dev,req->mapped_buff,+skb_data_size(skb),DMA_FROM_DEVICE);+req->mapped_buff=0;+}++/* init skb struct */+skb->len=0;+skb_reset_tail_pointer(skb);+skb_put(skb,rgpd->data_buff_len);++/* consume skb */+if(md_ctrl->recv_skb){+ret=md_ctrl->recv_skb(queue,skb);+}else{+ccci_free_skb(&md_ctrl->mtk_dev->pools,skb);+ret=-ENETDOWN;+}++new_skb=NULL;+if(ret>=0||ret==-ENETDOWN)+new_skb=ccci_alloc_skb_from_pool(&md_ctrl->mtk_dev->pools,+queue->tr_ring->pkt_size,+GFS_BLOCKING);++if(!new_skb){+/* either the port was busy or the skb pool was empty */+usleep_range(5000,10000);+return-EAGAIN;+}++/* mark cldma_request as available */+req->skb=NULL;+cldma_rgpd_set_data_ptr(rgpd,0);+queue->tr_done=cldma_ring_step_forward(queue->tr_ring,req);++req=queue->rx_refill;+rgpd=req->gpd;+req->mapped_buff=dma_map_single(md_ctrl->dev,new_skb->data,+skb_data_size(new_skb),DMA_FROM_DEVICE);+if(dma_mapping_error(md_ctrl->dev,req->mapped_buff)){+dev_err(md_ctrl->dev,"DMA mapping failed\n");+req->mapped_buff=0;+ccci_free_skb(&md_ctrl->mtk_dev->pools,new_skb);+return-ENOMEM;+}++cldma_rgpd_set_data_ptr(rgpd,req->mapped_buff);+rgpd->data_buff_len=0;+/* set HWO, no need to hold ring_lock */+rgpd->gpd_flags=GPD_FLAGS_IOC|GPD_FLAGS_HWO;+/* mark cldma_request as available */+req->skb=new_skb;+queue->rx_refill=cldma_ring_step_forward(queue->tr_ring,req);++if(++count>=budget&&need_resched()){+*over_budget=true;+rx_done=true;+}+}++return0;+}++staticintcldma_gpd_rx_collect(structcldma_queue*queue,intbudget)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+boolover_budget=false;+boolrx_not_done=true;+unsignedintl2_rx_int;+unsignedlongflags;+intret;++md_ctrl=md_cd_get(queue->hif_id);+hw_info=&md_ctrl->hw_info;++while(rx_not_done){+rx_not_done=false;+ret=cldma_gpd_rx_from_queue(queue,budget,&over_budget);+if(ret==-ENODATA)+return0;++if(ret)+returnret;++spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(md_ctrl->rxq_active&BIT(queue->index)){+/* resume Rx queue */+if(!cldma_hw_queue_status(hw_info,queue->index,true))+cldma_hw_resume_queue(hw_info,queue->index,true);++/* greedy mode */+l2_rx_int=cldma_hw_int_status(hw_info,BIT(queue->index),true);++if(l2_rx_int){+/* need be scheduled again, avoid the soft lockup */+cldma_hw_rx_done(hw_info,l2_rx_int);+if(over_budget){+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+return-EAGAIN;+}++/* clear IP busy register wake up CPU case */+rx_not_done=true;+}+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++return0;+}++staticvoidcldma_rx_done(structwork_struct*work)+{+structcldma_ctrl*md_ctrl;+structcldma_queue*queue;+intvalue;++queue=container_of(work,structcldma_queue,cldma_rx_work);+md_ctrl=md_cd_get(queue->hif_id);+value=queue->tr_ring->handle_rx_done(queue,queue->budget);++if(value&&md_ctrl->rxq_active&BIT(queue->index)){+queue_work(queue->worker,&queue->cldma_rx_work);+return;+}++/* clear IP busy register wake up CPU case */+cldma_clear_ip_busy(&md_ctrl->hw_info);+/* enable RX_DONE && EMPTY interrupt */+cldma_hw_dismask_txrxirq(&md_ctrl->hw_info,queue->index,true);+cldma_hw_dismask_eqirq(&md_ctrl->hw_info,queue->index,true);+}++staticintcldma_gpd_tx_collect(structcldma_queue*queue)+{+structcldma_ctrl*md_ctrl;+structcldma_request*req;+structsk_buff*skb_free;+structcldma_tgpd*tgpd;+unsignedintdma_len;+unsignedlongflags;+dma_addr_tdma_free;+intcount=0;++md_ctrl=md_cd_get(queue->hif_id);++while(!kthread_should_stop()){+spin_lock_irqsave(&queue->ring_lock,flags);+req=queue->tr_done;+if(!req){+dev_err(md_ctrl->dev,"TXQ was released\n");+spin_unlock_irqrestore(&queue->ring_lock,flags);+break;+}++tgpd=req->gpd;+if((tgpd->gpd_flags&GPD_FLAGS_HWO)||!req->skb){+spin_unlock_irqrestore(&queue->ring_lock,flags);+break;+}++/* restore IOC setting */+if(req->ioc_override&GPD_FLAGS_IOC){+if(req->ioc_override&GPD_FLAGS_HWO)+tgpd->gpd_flags|=GPD_FLAGS_IOC;+else+tgpd->gpd_flags&=~GPD_FLAGS_IOC;+dev_notice(md_ctrl->dev,+"qno%u, req->ioc_override=0x%x,tgpd->gpd_flags=0x%x\n",+queue->index,req->ioc_override,tgpd->gpd_flags);+}++queue->budget++;+/* save skb reference */+dma_free=req->mapped_buff;+dma_len=tgpd->data_buff_len;+skb_free=req->skb;+/* mark cldma_request as available */+req->skb=NULL;+queue->tr_done=cldma_ring_step_forward(queue->tr_ring,req);+spin_unlock_irqrestore(&queue->ring_lock,flags);+count++;+dma_unmap_single(md_ctrl->dev,dma_free,dma_len,DMA_TO_DEVICE);++ccci_free_skb(&md_ctrl->mtk_dev->pools,skb_free);+}++if(count)+wake_up_nr(&queue->req_wq,count);++returncount;+}++staticvoidcldma_tx_queue_empty_handler(structcldma_queue*queue)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+structcldma_request*req;+structcldma_tgpd*tgpd;+dma_addr_tul_curr_addr;+unsignedlongflags;+boolpending_gpd;++md_ctrl=md_cd_get(queue->hif_id);+hw_info=&md_ctrl->hw_info;+if(!(md_ctrl->txq_active&BIT(queue->index)))+return;++/* check if there is any pending TGPD with HWO=1 */+spin_lock_irqsave(&queue->ring_lock,flags);+req=cldma_ring_step_backward(queue->tr_ring,queue->tx_xmit);+tgpd=req->gpd;+pending_gpd=(tgpd->gpd_flags&GPD_FLAGS_HWO)&&req->skb;++spin_unlock_irqrestore(&queue->ring_lock,flags);++spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(pending_gpd){+/* Check current processing TGPD+*current64-bitaddressisinatablebyQindex.+*/+ul_curr_addr=ioread64(hw_info->ap_pdn_base++REG_CLDMA_UL_CURRENT_ADDRL_0++queue->index*sizeof(u64));+if(req->gpd_addr!=ul_curr_addr)+dev_err(md_ctrl->dev,+"CLDMA%d Q%d TGPD addr, SW:%pad, HW:%pad\n",md_ctrl->hif_id,+queue->index,&req->gpd_addr,&ul_curr_addr);+else+/* retry */+cldma_hw_resume_queue(&md_ctrl->hw_info,queue->index,false);+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++staticvoidcldma_tx_done(structwork_struct*work)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+structcldma_queue*queue;+unsignedintl2_tx_int;+unsignedlongflags;++queue=container_of(work,structcldma_queue,cldma_tx_work);+md_ctrl=md_cd_get(queue->hif_id);+hw_info=&md_ctrl->hw_info;+queue->tr_ring->handle_tx_done(queue);++/* greedy mode */+l2_tx_int=cldma_hw_int_status(hw_info,BIT(queue->index)|EQ_STA_BIT(queue->index),+false);+if(l2_tx_int&EQ_STA_BIT(queue->index)){+cldma_hw_tx_done(hw_info,EQ_STA_BIT(queue->index));+cldma_tx_queue_empty_handler(queue);+}++if(l2_tx_int&BIT(queue->index)){+cldma_hw_tx_done(hw_info,BIT(queue->index));+queue_work(queue->worker,&queue->cldma_tx_work);+return;+}++/* enable TX_DONE interrupt */+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(md_ctrl->txq_active&BIT(queue->index)){+cldma_clear_ip_busy(hw_info);+cldma_hw_dismask_eqirq(hw_info,queue->index,false);+cldma_hw_dismask_txrxirq(hw_info,queue->index,false);+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++staticvoidcldma_ring_free(structcldma_ctrl*md_ctrl,+structcldma_ring*ring,enumdma_data_directiondir)+{+structcldma_request*req_cur,*req_next;++list_for_each_entry_safe(req_cur,req_next,&ring->gpd_ring,entry){+if(req_cur->mapped_buff&&req_cur->skb){+dma_unmap_single(md_ctrl->dev,req_cur->mapped_buff,+skb_data_size(req_cur->skb),dir);+req_cur->mapped_buff=0;+}++if(req_cur->skb)+ccci_free_skb(&md_ctrl->mtk_dev->pools,req_cur->skb);++if(req_cur->gpd)+dma_pool_free(md_ctrl->gpd_dmapool,req_cur->gpd,+req_cur->gpd_addr);++list_del(&req_cur->entry);+kfree_sensitive(req_cur);+}+}++staticstructcldma_request*alloc_rx_request(structcldma_ctrl*md_ctrl,size_tpkt_size)+{+structcldma_request*item;+unsignedlongflags;++item=kzalloc(sizeof(*item),GFP_KERNEL);+if(!item)+returnNULL;++item->skb=ccci_alloc_skb_from_pool(&md_ctrl->mtk_dev->pools,pkt_size,GFS_BLOCKING);+if(!item->skb)+gotoerr_skb_alloc;++item->gpd=dma_pool_alloc(md_ctrl->gpd_dmapool,GFP_KERNEL|__GFP_ZERO,+&item->gpd_addr);+if(!item->gpd)+gotoerr_gpd_alloc;++spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+item->mapped_buff=dma_map_single(md_ctrl->dev,item->skb->data,+skb_data_size(item->skb),DMA_FROM_DEVICE);+if(dma_mapping_error(md_ctrl->dev,item->mapped_buff)){+dev_err(md_ctrl->dev,"DMA mapping failed\n");+item->mapped_buff=0;+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+gotoerr_dma_map;+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+returnitem;++err_dma_map:+dma_pool_free(md_ctrl->gpd_dmapool,item->gpd,item->gpd_addr);+err_gpd_alloc:+ccci_free_skb(&md_ctrl->mtk_dev->pools,item->skb);+err_skb_alloc:+kfree(item);+returnNULL;+}++staticintcldma_rx_ring_init(structcldma_ctrl*md_ctrl,structcldma_ring*ring)+{+structcldma_request*item,*first_item=NULL;+structcldma_rgpd*prev_gpd,*gpd=NULL;+inti;++for(i=0;i<ring->length;i++){+item=alloc_rx_request(md_ctrl,ring->pkt_size);+if(!item){+cldma_ring_free(md_ctrl,ring,DMA_FROM_DEVICE);+return-ENOMEM;+}++gpd=(structcldma_rgpd*)item->gpd;+cldma_rgpd_set_data_ptr(gpd,item->mapped_buff);+gpd->data_allow_len=ring->pkt_size;+gpd->gpd_flags=GPD_FLAGS_IOC|GPD_FLAGS_HWO;+if(!i)+first_item=item;+else+cldma_rgpd_set_next_ptr(prev_gpd,item->gpd_addr);++INIT_LIST_HEAD(&item->entry);+list_add_tail(&item->entry,&ring->gpd_ring);+prev_gpd=gpd;+}++if(first_item)+cldma_rgpd_set_next_ptr(gpd,first_item->gpd_addr);++return0;+}++staticstructcldma_request*alloc_tx_request(structcldma_ctrl*md_ctrl)+{+structcldma_request*item;++item=kzalloc(sizeof(*item),GFP_KERNEL);+if(!item)+returnNULL;++item->gpd=dma_pool_alloc(md_ctrl->gpd_dmapool,GFP_KERNEL|__GFP_ZERO,+&item->gpd_addr);+if(!item->gpd){+kfree(item);+returnNULL;+}++returnitem;+}++staticintcldma_tx_ring_init(structcldma_ctrl*md_ctrl,structcldma_ring*ring)+{+structcldma_request*item,*first_item=NULL;+structcldma_tgpd*tgpd,*prev_gpd;+inti;++for(i=0;i<ring->length;i++){+item=alloc_tx_request(md_ctrl);+if(!item){+cldma_ring_free(md_ctrl,ring,DMA_TO_DEVICE);+return-ENOMEM;+}++tgpd=item->gpd;+tgpd->gpd_flags=GPD_FLAGS_IOC;+if(!first_item)+first_item=item;+else+cldma_tgpd_set_next_ptr(prev_gpd,item->gpd_addr);+INIT_LIST_HEAD(&item->bd);+INIT_LIST_HEAD(&item->entry);+list_add_tail(&item->entry,&ring->gpd_ring);+prev_gpd=tgpd;+}++if(first_item)+cldma_tgpd_set_next_ptr(tgpd,first_item->gpd_addr);++return0;+}++staticvoidcldma_queue_switch_ring(structcldma_queue*queue)+{+structcldma_ctrl*md_ctrl;+structcldma_request*req;++md_ctrl=md_cd_get(queue->hif_id);++if(queue->dir==MTK_OUT){+queue->tr_ring=&md_ctrl->tx_ring[queue->index];+req=list_first_entry(&queue->tr_ring->gpd_ring,structcldma_request,entry);+queue->tr_done=req;+queue->tx_xmit=req;+queue->budget=queue->tr_ring->length;+}elseif(queue->dir==MTK_IN){+queue->tr_ring=&md_ctrl->rx_ring[queue->index];+req=list_first_entry(&queue->tr_ring->gpd_ring,structcldma_request,entry);+queue->tr_done=req;+queue->rx_refill=req;+queue->budget=queue->tr_ring->length;+}+}++staticvoidcldma_rx_queue_init(structcldma_queue*queue)+{+queue->dir=MTK_IN;+cldma_queue_switch_ring(queue);+queue->q_type=rxq_type[queue->index];+}++staticvoidcldma_tx_queue_init(structcldma_queue*queue)+{+queue->dir=MTK_OUT;+cldma_queue_switch_ring(queue);+queue->q_type=txq_type[queue->index];+}++staticinlinevoidcldma_enable_irq(structcldma_ctrl*md_ctrl)+{+mtk_pcie_mac_set_int(md_ctrl->mtk_dev,md_ctrl->hw_info.phy_interrupt_id);+}++staticinlinevoidcldma_disable_irq(structcldma_ctrl*md_ctrl)+{+mtk_pcie_mac_clear_int(md_ctrl->mtk_dev,md_ctrl->hw_info.phy_interrupt_id);+}++staticvoidcldma_irq_work_cb(structcldma_ctrl*md_ctrl)+{+u32l2_tx_int_msk,l2_rx_int_msk,l2_tx_int,l2_rx_int,val;+structcldma_hw_info*hw_info=&md_ctrl->hw_info;+inti;++/* L2 raw interrupt status */+l2_tx_int=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2TISAR0);+l2_rx_int=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2RISAR0);+l2_tx_int_msk=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L2TIMR0);+l2_rx_int_msk=ioread32(hw_info->ap_ao_base+REG_CLDMA_L2RIMR0);++l2_tx_int&=~l2_tx_int_msk;+l2_rx_int&=~l2_rx_int_msk;++if(l2_tx_int){+if(l2_tx_int&(TQ_ERR_INT_BITMASK|TQ_ACTIVE_START_ERR_INT_BITMASK)){+/* read and clear L3 TX interrupt status */+val=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L3TISAR0);+iowrite32(val,hw_info->ap_pdn_base+REG_CLDMA_L3TISAR0);+val=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L3TISAR1);+iowrite32(val,hw_info->ap_pdn_base+REG_CLDMA_L3TISAR1);+}++cldma_hw_tx_done(hw_info,l2_tx_int);++if(l2_tx_int&(TXRX_STATUS_BITMASK|EMPTY_STATUS_BITMASK)){+for(i=0;i<CLDMA_TXQ_NUM;i++){+if(l2_tx_int&BIT(i)){+/* disable TX_DONE interrupt */+cldma_hw_mask_eqirq(hw_info,i,false);+cldma_hw_mask_txrxirq(hw_info,i,false);+queue_work(md_ctrl->txq[i].worker,+&md_ctrl->txq[i].cldma_tx_work);+}++if(l2_tx_int&EQ_STA_BIT(i))+cldma_tx_queue_empty_handler(&md_ctrl->txq[i]);+}+}+}++if(l2_rx_int){+/* clear IP busy register wake up CPU case */+if(l2_rx_int&(RQ_ERR_INT_BITMASK|RQ_ACTIVE_START_ERR_INT_BITMASK)){+/* read and clear L3 RX interrupt status */+val=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L3RISAR0);+iowrite32(val,hw_info->ap_pdn_base+REG_CLDMA_L3RISAR0);+val=ioread32(hw_info->ap_pdn_base+REG_CLDMA_L3RISAR1);+iowrite32(val,hw_info->ap_pdn_base+REG_CLDMA_L3RISAR1);+}++cldma_hw_rx_done(hw_info,l2_rx_int);++if(l2_rx_int&(TXRX_STATUS_BITMASK|EMPTY_STATUS_BITMASK)){+for(i=0;i<CLDMA_RXQ_NUM;i++){+if(l2_rx_int&(BIT(i)|EQ_STA_BIT(i))){+/* disable RX_DONE and QUEUE_EMPTY interrupt */+cldma_hw_mask_eqirq(hw_info,i,true);+cldma_hw_mask_txrxirq(hw_info,i,true);+queue_work(md_ctrl->rxq[i].worker,+&md_ctrl->rxq[i].cldma_rx_work);+}+}+}+}+}++staticboolqueues_active(structcldma_hw_info*hw_info)+{+unsignedinttx_active;+unsignedintrx_active;++tx_active=cldma_hw_queue_status(hw_info,CLDMA_ALL_Q,false);+rx_active=cldma_hw_queue_status(hw_info,CLDMA_ALL_Q,true);++return((tx_active||rx_active)&&tx_active!=CLDMA_ALL_Q&&rx_active!=CLDMA_ALL_Q);+}++/**+*cldma_stop()-StopCLDMA+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*+*stopTXRXqueue+*disableL1andL2interrupt+*clearTX/RXemptyinterruptstatus+*+*Return:0onsuccess,anegativeerrorcodeonfailure+*/+intcldma_stop(enumcldma_idhif_id)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+boolactive;+inti;++md_ctrl=md_cd_get(hif_id);+if(!md_ctrl)+return-EINVAL;++hw_info=&md_ctrl->hw_info;++/* stop TX/RX queue */+md_ctrl->rxq_active=0;+cldma_hw_stop_queue(hw_info,CLDMA_ALL_Q,true);+md_ctrl->txq_active=0;+cldma_hw_stop_queue(hw_info,CLDMA_ALL_Q,false);+md_ctrl->txq_started=0;++/* disable L1 and L2 interrupt */+cldma_disable_irq(md_ctrl);+cldma_hw_stop(hw_info,true);+cldma_hw_stop(hw_info,false);++/* clear TX/RX empty interrupt status */+cldma_hw_tx_done(hw_info,CLDMA_L2TISAR0_ALL_INT_MASK);+cldma_hw_rx_done(hw_info,CLDMA_L2RISAR0_ALL_INT_MASK);++if(md_ctrl->is_late_init){+for(i=0;i<CLDMA_TXQ_NUM;i++)+flush_work(&md_ctrl->txq[i].cldma_tx_work);++for(i=0;i<CLDMA_RXQ_NUM;i++)+flush_work(&md_ctrl->rxq[i].cldma_rx_work);+}++if(read_poll_timeout(queues_active,active,!active,CHECK_Q_STOP_STEP_US,+CHECK_Q_STOP_TIMEOUT_US,true,hw_info)){+dev_err(md_ctrl->dev,"Could not stop CLDMA%d queues",hif_id);+return-EAGAIN;+}++return0;+}++staticvoidcldma_late_release(structcldma_ctrl*md_ctrl)+{+inti;++if(md_ctrl->is_late_init){+/* free all TX/RX CLDMA request/GBD/skb buffer */+for(i=0;i<CLDMA_TXQ_NUM;i++)+cldma_ring_free(md_ctrl,&md_ctrl->tx_ring[i],DMA_TO_DEVICE);++for(i=0;i<CLDMA_RXQ_NUM;i++)+cldma_ring_free(md_ctrl,&md_ctrl->rx_ring[i],DMA_FROM_DEVICE);++dma_pool_destroy(md_ctrl->gpd_dmapool);+md_ctrl->gpd_dmapool=NULL;+md_ctrl->is_late_init=false;+}+}++voidcldma_reset(enumcldma_idhif_id)+{+structcldma_ctrl*md_ctrl;+structmtk_modem*md;+unsignedlongflags;+inti;++md_ctrl=md_cd_get(hif_id);+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+md_ctrl->hif_id=hif_id;+md_ctrl->txq_active=0;+md_ctrl->rxq_active=0;+md=md_ctrl->mtk_dev->md;++cldma_disable_irq(md_ctrl);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);++for(i=0;i<CLDMA_TXQ_NUM;i++){+md_ctrl->txq[i].md=md;+cancel_work_sync(&md_ctrl->txq[i].cldma_tx_work);+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+md_cd_queue_struct_reset(&md_ctrl->txq[i],md_ctrl->hif_id,MTK_OUT,i);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++for(i=0;i<CLDMA_RXQ_NUM;i++){+md_ctrl->rxq[i].md=md;+cancel_work_sync(&md_ctrl->rxq[i].cldma_rx_work);+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+md_cd_queue_struct_reset(&md_ctrl->rxq[i],md_ctrl->hif_id,MTK_IN,i);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++cldma_late_release(md_ctrl);+}++/**+*cldma_start()-StartCLDMA+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*+*setTX/RXstartaddress+*startallRXqueuesandenableL2interrupt+*+*/+voidcldma_start(enumcldma_idhif_id)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+unsignedlongflags;++md_ctrl=md_cd_get(hif_id);+hw_info=&md_ctrl->hw_info;++spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(md_ctrl->is_late_init){+inti;++cldma_enable_irq(md_ctrl);+/* set start address */+for(i=0;i<CLDMA_TXQ_NUM;i++){+if(md_ctrl->txq[i].tr_done)+cldma_hw_set_start_address(hw_info,i,+md_ctrl->txq[i].tr_done->gpd_addr,0);+}++for(i=0;i<CLDMA_RXQ_NUM;i++){+if(md_ctrl->rxq[i].tr_done)+cldma_hw_set_start_address(hw_info,i,+md_ctrl->rxq[i].tr_done->gpd_addr,1);+}++/* start all RX queues and enable L2 interrupt */+cldma_hw_start_queue(hw_info,0xff,1);+cldma_hw_start(hw_info);+/* wait write done */+wmb();+md_ctrl->txq_started=0;+md_ctrl->txq_active|=TXRX_STATUS_BITMASK;+md_ctrl->rxq_active|=TXRX_STATUS_BITMASK;+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++staticvoidclear_txq(structcldma_ctrl*md_ctrl,intqnum)+{+structcldma_request*req;+structcldma_queue*txq;+structcldma_tgpd*tgpd;+unsignedlongflags;++txq=&md_ctrl->txq[qnum];+spin_lock_irqsave(&txq->ring_lock,flags);+req=list_first_entry(&txq->tr_ring->gpd_ring,structcldma_request,entry);+txq->tr_done=req;+txq->tx_xmit=req;+txq->budget=txq->tr_ring->length;+list_for_each_entry(req,&txq->tr_ring->gpd_ring,entry){+tgpd=req->gpd;+tgpd->gpd_flags&=~GPD_FLAGS_HWO;+cldma_tgpd_set_data_ptr(tgpd,0);+tgpd->data_buff_len=0;+if(req->skb){+ccci_free_skb(&md_ctrl->mtk_dev->pools,req->skb);+req->skb=NULL;+}+}++spin_unlock_irqrestore(&txq->ring_lock,flags);+}++staticintclear_rxq(structcldma_ctrl*md_ctrl,intqnum)+{+structcldma_request*req;+structcldma_queue*rxq;+structcldma_rgpd*rgpd;+unsignedlongflags;++rxq=&md_ctrl->rxq[qnum];+spin_lock_irqsave(&rxq->ring_lock,flags);+req=list_first_entry(&rxq->tr_ring->gpd_ring,structcldma_request,entry);+rxq->tr_done=req;+rxq->rx_refill=req;+list_for_each_entry(req,&rxq->tr_ring->gpd_ring,entry){+rgpd=req->gpd;+rgpd->gpd_flags=GPD_FLAGS_IOC|GPD_FLAGS_HWO;+rgpd->data_buff_len=0;+if(req->skb){+req->skb->len=0;+skb_reset_tail_pointer(req->skb);+}+}++spin_unlock_irqrestore(&rxq->ring_lock,flags);+list_for_each_entry(req,&rxq->tr_ring->gpd_ring,entry){+rgpd=req->gpd;+if(req->skb)+continue;++req->skb=ccci_alloc_skb_from_pool(&md_ctrl->mtk_dev->pools,+rxq->tr_ring->pkt_size,GFS_BLOCKING);+if(!req->skb){+dev_err(md_ctrl->dev,"skb not allocated\n");+return-ENOMEM;+}++req->mapped_buff=dma_map_single(md_ctrl->dev,req->skb->data,+skb_data_size(req->skb),DMA_FROM_DEVICE);+if(dma_mapping_error(md_ctrl->dev,req->mapped_buff)){+dev_err(md_ctrl->dev,"DMA mapping failed\n");+return-ENOMEM;+}++cldma_rgpd_set_data_ptr(rgpd,req->mapped_buff);+}++return0;+}++/* only allowed when CLDMA is stopped */+staticvoidcldma_clear_all_queue(structcldma_ctrl*md_ctrl,enumdirectiondir)+{+inti;++if(dir==MTK_OUT||dir==MTK_INOUT){+for(i=0;i<CLDMA_TXQ_NUM;i++)+clear_txq(md_ctrl,i);+}++if(dir==MTK_IN||dir==MTK_INOUT){+for(i=0;i<CLDMA_RXQ_NUM;i++)+clear_rxq(md_ctrl,i);+}+}++staticvoidcldma_stop_queue(structcldma_ctrl*md_ctrl,unsignedcharqno,enumdirectiondir)+{+structcldma_hw_info*hw_info;+unsignedlongflags;++hw_info=&md_ctrl->hw_info;+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(dir==MTK_IN){+/* disable RX_DONE and QUEUE_EMPTY interrupt */+cldma_hw_mask_eqirq(hw_info,qno,true);+cldma_hw_mask_txrxirq(hw_info,qno,true);+if(qno==CLDMA_ALL_Q)+md_ctrl->rxq_active&=~TXRX_STATUS_BITMASK;+else+md_ctrl->rxq_active&=~(TXRX_STATUS_BITMASK&BIT(qno));++cldma_hw_stop_queue(hw_info,qno,true);+}elseif(dir==MTK_OUT){+cldma_hw_mask_eqirq(hw_info,qno,false);+cldma_hw_mask_txrxirq(hw_info,qno,false);+if(qno==CLDMA_ALL_Q)+md_ctrl->txq_active&=~TXRX_STATUS_BITMASK;+else+md_ctrl->txq_active&=~(TXRX_STATUS_BITMASK&BIT(qno));++cldma_hw_stop_queue(hw_info,qno,false);+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++/* this is called inside queue->ring_lock */+staticintcldma_gpd_handle_tx_request(structcldma_queue*queue,structcldma_request*tx_req,+structsk_buff*skb,unsignedintioc_override)+{+structcldma_ctrl*md_ctrl;+structcldma_tgpd*tgpd;+unsignedlongflags;++md_ctrl=md_cd_get(queue->hif_id);+tgpd=tx_req->gpd;+/* override current IOC setting */+if(ioc_override&GPD_FLAGS_IOC){+/* backup current IOC setting */+if(tgpd->gpd_flags&GPD_FLAGS_IOC)+tx_req->ioc_override=GPD_FLAGS_IOC|GPD_FLAGS_HWO;+else+tx_req->ioc_override=GPD_FLAGS_IOC;+if(ioc_override&GPD_FLAGS_HWO)+tgpd->gpd_flags|=GPD_FLAGS_IOC;+else+tgpd->gpd_flags&=~GPD_FLAGS_IOC;+}++/* update GPD */+tx_req->mapped_buff=dma_map_single(md_ctrl->dev,skb->data,+skb->len,DMA_TO_DEVICE);+if(dma_mapping_error(md_ctrl->dev,tx_req->mapped_buff)){+dev_err(md_ctrl->dev,"DMA mapping failed\n");+return-ENOMEM;+}++cldma_tgpd_set_data_ptr(tgpd,tx_req->mapped_buff);+tgpd->data_buff_len=skb->len;++/* Set HWO. Use cldma_lock to avoid race condition+*withcldma_stop.ThislockmustcoverTGPDsetting,+*asevenwithoutaresumeoperation.+*CLDMAstillcanstartsendingnextHWO=1,+*iflastTGPDwasjustfinished.+*/+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(md_ctrl->txq_active&BIT(queue->index))+tgpd->gpd_flags|=GPD_FLAGS_HWO;++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+/* mark cldma_request as available */+tx_req->skb=skb;++return0;+}++staticvoidcldma_hw_start_send(structcldma_ctrl*md_ctrl,u8qno)+{+structcldma_hw_info*hw_info;+structcldma_request*req;++hw_info=&md_ctrl->hw_info;++/* check whether the device was powered off (CLDMA start address is not set) */+if(!cldma_tx_addr_is_set(hw_info,qno)){+cldma_hw_init(hw_info);+req=cldma_ring_step_backward(md_ctrl->txq[qno].tr_ring,+md_ctrl->txq[qno].tx_xmit);+cldma_hw_set_start_address(hw_info,qno,req->gpd_addr,false);+md_ctrl->txq_started&=~BIT(qno);+}++/* resume or start queue */+if(!cldma_hw_queue_status(hw_info,qno,false)){+if(md_ctrl->txq_started&BIT(qno))+cldma_hw_resume_queue(hw_info,qno,false);+else+cldma_hw_start_queue(hw_info,qno,false);+md_ctrl->txq_started|=BIT(qno);+}+}++intcldma_write_room(enumcldma_idhif_id,unsignedcharqno)+{+structcldma_ctrl*md_ctrl;+structcldma_queue*queue;++md_ctrl=md_cd_get(hif_id);+queue=&md_ctrl->txq[qno];+if(!queue)+return-EINVAL;++if(queue->budget>(MAX_TX_BUDGET-1))+returnqueue->budget;++return0;+}++/**+*cldma_set_recv_skb()-SetthecallbacktohandleRXpackets+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*@recv_skb:processingcallback+*/+voidcldma_set_recv_skb(enumcldma_idhif_id,+int(*recv_skb)(structcldma_queue*queue,structsk_buff*skb))+{+structcldma_ctrl*md_ctrl=md_cd_get(hif_id);++md_ctrl->recv_skb=recv_skb;+}++/**+*cldma_send_skb()-Sendcontroldatatomodem+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*@qno:queuenumber+*@skb:socketbuffer+*@skb_from_pool:settotruetoreuseskbfromthepool+*@blocking:trueforblockingoperation+*+*Sendcontrolpackettomodemusingaringbuffer.+*Ifblockingisset,itwillwaitforcompletion.+*+*Return:0onsuccess,-ENOMEMonallocationfailure,-EINVALoninvalidqueuerequest,or+*-EBUSYonresourcelockfailure.+*/+intcldma_send_skb(enumcldma_idhif_id,intqno,structsk_buff*skb,boolskb_from_pool,+boolblocking)+{+unsignedintioc_override=0;+structcldma_request*tx_req;+structcldma_ctrl*md_ctrl;+structcldma_queue*queue;+unsignedlongflags;+intret=0;++md_ctrl=md_cd_get(hif_id);++if(qno>=CLDMA_TXQ_NUM){+ret=-EINVAL;+gotoexit;+}++if(skb_from_pool&&skb_headroom(skb)==NET_SKB_PAD){+structccci_buffer_ctrl*buf_ctrl;++buf_ctrl=skb_push(skb,sizeof(*buf_ctrl));+if(buf_ctrl->head_magic==CCCI_BUF_MAGIC)+ioc_override=buf_ctrl->ioc_override;+skb_pull(skb,sizeof(*buf_ctrl));+}++queue=&md_ctrl->txq[qno];++/* check if queue is active */+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+if(!(md_ctrl->txq_active&BIT(qno))){+ret=-EBUSY;+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+gotoexit;+}++spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);++do{+spin_lock_irqsave(&queue->ring_lock,flags);+tx_req=queue->tx_xmit;+if(queue->budget>0&&!tx_req->skb){+queue->budget--;+queue->tr_ring->handle_tx_request(queue,tx_req,skb,ioc_override);+queue->tx_xmit=cldma_ring_step_forward(queue->tr_ring,tx_req);+spin_unlock_irqrestore(&queue->ring_lock,flags);++spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+cldma_hw_start_send(md_ctrl,qno);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+break;+}++spin_unlock_irqrestore(&queue->ring_lock,flags);++/* check CLDMA status */+if(!cldma_hw_queue_status(&md_ctrl->hw_info,qno,false)){+/* resume channel */+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+cldma_hw_resume_queue(&md_ctrl->hw_info,qno,false);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++if(!blocking){+ret=-EBUSY;+break;+}++ret=wait_event_interruptible_exclusive(queue->req_wq,queue->budget>0);+if(ret==-ERESTARTSYS)+ret=-EINTR;++}while(!ret);++exit:+returnret;+}++staticvoidccci_cldma_adjust_config(void)+{+intqno;++/* set default config */+for(qno=0;qno<CLDMA_RXQ_NUM;qno++){+rxq_buff_size[qno]=MTK_SKB_4K;+rxq_type[qno]=CLDMA_SHARED_Q;+rxq_buff_num[qno]=MAX_RX_BUDGET;+}++rxq_buff_size[CLDMA_RXQ_NUM-1]=MTK_SKB_64K;++for(qno=0;qno<CLDMA_TXQ_NUM;qno++){+txq_type[qno]=CLDMA_SHARED_Q;+txq_buff_num[qno]=MAX_TX_BUDGET;+}+}++/* this function contains longer duration initializations */+staticintcldma_late_init(structcldma_ctrl*md_ctrl)+{+chardma_pool_name[32];+inti,ret;++if(md_ctrl->is_late_init){+dev_err(md_ctrl->dev,"CLDMA late init was already done\n");+return-EALREADY;+}++mutex_lock(&ctl_cfg_mutex);+ccci_cldma_adjust_config();+/* init ring buffers */+snprintf(dma_pool_name,32,"cldma_request_dma_hif%d",md_ctrl->hif_id);+md_ctrl->gpd_dmapool=dma_pool_create(dma_pool_name,md_ctrl->dev,+sizeof(structcldma_tgpd),16,0);+if(!md_ctrl->gpd_dmapool){+mutex_unlock(&ctl_cfg_mutex);+dev_err(md_ctrl->dev,"DMA pool alloc fail\n");+return-ENOMEM;+}++for(i=0;i<CLDMA_TXQ_NUM;i++){+INIT_LIST_HEAD(&md_ctrl->tx_ring[i].gpd_ring);+md_ctrl->tx_ring[i].length=txq_buff_num[i];+md_ctrl->tx_ring[i].handle_tx_request=&cldma_gpd_handle_tx_request;+md_ctrl->tx_ring[i].handle_tx_done=&cldma_gpd_tx_collect;+ret=cldma_tx_ring_init(md_ctrl,&md_ctrl->tx_ring[i]);+if(ret){+dev_err(md_ctrl->dev,"control TX ring init fail\n");+gotoerr_tx_ring;+}+}++for(i=0;i<CLDMA_RXQ_NUM;i++){+INIT_LIST_HEAD(&md_ctrl->rx_ring[i].gpd_ring);+md_ctrl->rx_ring[i].length=rxq_buff_num[i];+md_ctrl->rx_ring[i].pkt_size=rxq_buff_size[i];+md_ctrl->rx_ring[i].handle_rx_done=&cldma_gpd_rx_collect;+ret=cldma_rx_ring_init(md_ctrl,&md_ctrl->rx_ring[i]);+if(ret){+dev_err(md_ctrl->dev,"control RX ring init fail\n");+gotoerr_rx_ring;+}+}++/* init queue */+for(i=0;i<CLDMA_TXQ_NUM;i++)+cldma_tx_queue_init(&md_ctrl->txq[i]);++for(i=0;i<CLDMA_RXQ_NUM;i++)+cldma_rx_queue_init(&md_ctrl->rxq[i]);+mutex_unlock(&ctl_cfg_mutex);++md_ctrl->is_late_init=true;+return0;++err_rx_ring:+while(i--)+cldma_ring_free(md_ctrl,&md_ctrl->rx_ring[i],DMA_FROM_DEVICE);++i=CLDMA_TXQ_NUM;+err_tx_ring:+while(i--)+cldma_ring_free(md_ctrl,&md_ctrl->tx_ring[i],DMA_TO_DEVICE);++mutex_unlock(&ctl_cfg_mutex);+returnret;+}++staticinlinevoid__iomem*pcie_addr_transfer(void__iomem*addr,u32addr_trs1,u32phy_addr)+{+returnaddr+phy_addr-addr_trs1;+}++staticvoidhw_info_init(structcldma_ctrl*md_ctrl)+{+structcldma_hw_info*hw_info;+u32phy_ao_base,phy_pd_base;+structmtk_addr_base*pbase;++pbase=&md_ctrl->mtk_dev->base_addr;+hw_info=&md_ctrl->hw_info;+if(md_ctrl->hif_id!=ID_CLDMA1)+return;++phy_ao_base=CLDMA1_AO_BASE;+phy_pd_base=CLDMA1_PD_BASE;+hw_info->phy_interrupt_id=CLDMA1_INT;++hw_info->hw_mode=MODE_BIT_64;+hw_info->ap_ao_base=pcie_addr_transfer(pbase->pcie_ext_reg_base,+pbase->pcie_dev_reg_trsl_addr,+phy_ao_base);+hw_info->ap_pdn_base=pcie_addr_transfer(pbase->pcie_ext_reg_base,+pbase->pcie_dev_reg_trsl_addr,+phy_pd_base);+}++intcldma_alloc(enumcldma_idhif_id,structmtk_pci_dev*mtk_dev)+{+structcldma_ctrl*md_ctrl;++md_ctrl=devm_kzalloc(&mtk_dev->pdev->dev,sizeof(*md_ctrl),GFP_KERNEL);+if(!md_ctrl)+return-ENOMEM;++md_ctrl->mtk_dev=mtk_dev;+md_ctrl->dev=&mtk_dev->pdev->dev;+md_ctrl->hif_id=hif_id;+hw_info_init(md_ctrl);+md_cd_set(hif_id,md_ctrl);+return0;+}++/**+*cldma_exception()-CLDMAexceptionhandler+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*@stage:exceptionstage+*+*disable/flush/stop/startCLDMA/queuesbasedonexceptionstage.+*+*/+voidcldma_exception(enumcldma_idhif_id,enumhif_ex_stagestage)+{+structcldma_ctrl*md_ctrl;++md_ctrl=md_cd_get(hif_id);+switch(stage){+caseHIF_EX_INIT:+/* disable CLDMA TX queues */+cldma_stop_queue(md_ctrl,CLDMA_ALL_Q,MTK_OUT);+/* Clear TX queue */+cldma_clear_all_queue(md_ctrl,MTK_OUT);+break;++caseHIF_EX_CLEARQ_DONE:+/* Stop CLDMA, we do not want to get CLDMA IRQ when MD is+*resettingCLDMAafteritgotclearq_ack.+*/+cldma_stop_queue(md_ctrl,CLDMA_ALL_Q,MTK_IN);+/* flush all TX&RX workqueues */+cldma_stop(hif_id);+if(md_ctrl->hif_id==ID_CLDMA1)+cldma_hw_reset(md_ctrl->mtk_dev->base_addr.infracfg_ao_base);++/* clear the RX queue */+cldma_clear_all_queue(md_ctrl,MTK_IN);+break;++caseHIF_EX_ALLQ_RESET:+cldma_hw_init(&md_ctrl->hw_info);+cldma_start(hif_id);+break;++default:+break;+}+}++voidcldma_hif_hw_init(enumcldma_idhif_id)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+unsignedlongflags;++md_ctrl=md_cd_get(hif_id);+spin_lock_irqsave(&md_ctrl->cldma_lock,flags);+hw_info=&md_ctrl->hw_info;++/* mask CLDMA interrupt */+cldma_hw_stop(hw_info,false);+cldma_hw_stop(hw_info,true);++/* clear CLDMA interrupt */+cldma_hw_rx_done(hw_info,EMPTY_STATUS_BITMASK|TXRX_STATUS_BITMASK);+cldma_hw_tx_done(hw_info,EMPTY_STATUS_BITMASK|TXRX_STATUS_BITMASK);++/* initialize CLDMA hardware */+cldma_hw_init(hw_info);+spin_unlock_irqrestore(&md_ctrl->cldma_lock,flags);+}++staticirqreturn_tcldma_isr_handler(intirq,void*data)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;++md_ctrl=data;+hw_info=&md_ctrl->hw_info;+mtk_pcie_mac_clear_int(md_ctrl->mtk_dev,hw_info->phy_interrupt_id);+cldma_irq_work_cb(md_ctrl);+mtk_pcie_mac_clear_int_status(md_ctrl->mtk_dev,hw_info->phy_interrupt_id);+mtk_pcie_mac_set_int(md_ctrl->mtk_dev,hw_info->phy_interrupt_id);+returnIRQ_HANDLED;+}++/**+*cldma_init()-InitializeCLDMA+*@hif_id:CLDMAID(ID_CLDMA0orID_CLDMA1)+*+*initializeHIFTX/RXqueuestructure+*registerCLDMAcallbackisrwithPCIedriver+*+*Return:0onsuccess,anegativeerrorcodeonfailure+*/+intcldma_init(enumcldma_idhif_id)+{+structcldma_hw_info*hw_info;+structcldma_ctrl*md_ctrl;+structmtk_modem*md;+inti;++md_ctrl=md_cd_get(hif_id);+md=md_ctrl->mtk_dev->md;+md_ctrl->hif_id=hif_id;+md_ctrl->txq_active=0;+md_ctrl->rxq_active=0;+md_ctrl->is_late_init=false;+hw_info=&md_ctrl->hw_info;++spin_lock_init(&md_ctrl->cldma_lock);+/* initialize HIF queue structure */+for(i=0;i<CLDMA_TXQ_NUM;i++){+md_cd_queue_struct_init(&md_ctrl->txq[i],md_ctrl->hif_id,MTK_OUT,i);+md_ctrl->txq[i].md=md;+md_ctrl->txq[i].worker=+alloc_workqueue("md_hif%d_tx%d_worker",+WQ_UNBOUND|WQ_MEM_RECLAIM|(!i?WQ_HIGHPRI:0),+1,hif_id,i);+if(!md_ctrl->txq[i].worker)+return-ENOMEM;++INIT_WORK(&md_ctrl->txq[i].cldma_tx_work,cldma_tx_done);+}++for(i=0;i<CLDMA_RXQ_NUM;i++){+md_cd_queue_struct_init(&md_ctrl->rxq[i],md_ctrl->hif_id,MTK_IN,i);+md_ctrl->rxq[i].md=md;+INIT_WORK(&md_ctrl->rxq[i].cldma_rx_work,cldma_rx_done);+md_ctrl->rxq[i].worker=alloc_workqueue("md_hif%d_rx%d_worker",+WQ_UNBOUND|WQ_MEM_RECLAIM,+1,hif_id,i);+if(!md_ctrl->rxq[i].worker)+return-ENOMEM;+}++mtk_pcie_mac_clear_int(md_ctrl->mtk_dev,hw_info->phy_interrupt_id);++/* registers CLDMA callback ISR with PCIe driver */+md_ctrl->mtk_dev->intr_handler[hw_info->phy_interrupt_id]=cldma_isr_handler;+md_ctrl->mtk_dev->intr_thread[hw_info->phy_interrupt_id]=NULL;+md_ctrl->mtk_dev->callback_param[hw_info->phy_interrupt_id]=md_ctrl;+mtk_pcie_mac_clear_int_status(md_ctrl->mtk_dev,hw_info->phy_interrupt_id);+return0;+}++voidcldma_switch_cfg(enumcldma_idhif_id)+{+structcldma_ctrl*md_ctrl;++md_ctrl=md_cd_get(hif_id);+if(md_ctrl){+cldma_late_release(md_ctrl);+cldma_late_init(md_ctrl);+}+}++voidcldma_exit(enumcldma_idhif_id)+{+structcldma_ctrl*md_ctrl;+inti;++md_ctrl=md_cd_get(hif_id);+if(!md_ctrl)+return;++/* stop CLDMA work */+cldma_stop(hif_id);+cldma_late_release(md_ctrl);++for(i=0;i<CLDMA_TXQ_NUM;i++){+if(md_ctrl->txq[i].worker){+destroy_workqueue(md_ctrl->txq[i].worker);+md_ctrl->txq[i].worker=NULL;+}+}++for(i=0;i<CLDMA_RXQ_NUM;i++){+if(md_ctrl->rxq[i].worker){+destroy_workqueue(md_ctrl->rxq[i].worker);+md_ctrl->rxq[i].worker=NULL;+}+}+}
@@ -0,0 +1,155 @@+/* SPDX-License-Identifier: GPL-2.0-only+*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#ifndef __T7XX_HIF_CLDMA_H__+#define __T7XX_HIF_CLDMA_H__++#include<linux/pci.h>+#include<linux/sched.h>+#include<linux/spinlock.h>+#include<linux/wait.h>+#include<linux/workqueue.h>+#include<linux/types.h>++#include"t7xx_cldma.h"+#include"t7xx_common.h"+#include"t7xx_modem_ops.h"+#include"t7xx_pci.h"++#define CLDMA_NUM 2++enumcldma_id{+ID_CLDMA0,+ID_CLDMA1,+};++enumcldma_queue_type{+CLDMA_SHARED_Q,+CLDMA_DEDICATED_Q,+};++structcldma_request{+void*gpd;/* virtual address for CPU */+dma_addr_tgpd_addr;/* physical address for DMA */+structsk_buff*skb;+dma_addr_tmapped_buff;+structlist_headentry;+structlist_headbd;+/* inherit from skb */+/* bit7: override or not; bit0: IOC setting */+unsignedcharioc_override;+};++structcldma_queue;++/* cldma_ring is quite light, most of ring buffer operations require queue struct. */+structcldma_ring{+structlist_headgpd_ring;/* ring of struct cldma_request */+intlength;/* number of struct cldma_request */+intpkt_size;/* size of each packet in ring */++int(*handle_tx_request)(structcldma_queue*queue,structcldma_request*req,+structsk_buff*skb,unsignedintioc_override);+int(*handle_rx_done)(structcldma_queue*queue,intbudget);+int(*handle_tx_done)(structcldma_queue*queue);+};++structcldma_queue{+unsignedcharindex;+structmtk_modem*md;+structcldma_ring*tr_ring;+structcldma_request*tr_done;+intbudget;/* same as ring buffer size by default */+structcldma_request*rx_refill;+structcldma_request*tx_xmit;+enumcldma_queue_typeq_type;+wait_queue_head_treq_wq;/* only for Tx */+spinlock_tring_lock;++structworkqueue_struct*worker;+structwork_structcldma_rx_work;+structwork_structcldma_tx_work;++wait_queue_head_trx_wq;+structtask_struct*rx_thread;++enumcldma_idhif_id;+enumdirectiondir;+};++structcldma_ctrl{+enumcldma_idhif_id;+structdevice*dev;+structmtk_pci_dev*mtk_dev;+structcldma_queuetxq[CLDMA_TXQ_NUM];+structcldma_queuerxq[CLDMA_RXQ_NUM];+unsignedshorttxq_active;+unsignedshortrxq_active;+unsignedshorttxq_started;+spinlock_tcldma_lock;/* protects CLDMA structure */+/* assuming T/R GPD/BD/SPD have the same size */+structdma_pool*gpd_dmapool;+structcldma_ringtx_ring[CLDMA_TXQ_NUM];+structcldma_ringrx_ring[CLDMA_RXQ_NUM];+structcldma_hw_infohw_info;+boolis_late_init;+int(*recv_skb)(structcldma_queue*queue,structsk_buff*skb);+};++#define GPD_FLAGS_HWO BIT(0)+#define GPD_FLAGS_BDP BIT(1)+#define GPD_FLAGS_BPS BIT(2)+#define GPD_FLAGS_IOC BIT(7)++structcldma_tgpd{+u8gpd_flags;+u16non_used;+u8debug_id;+u32next_gpd_ptr_h;+u32next_gpd_ptr_l;+u32data_buff_bd_ptr_h;+u32data_buff_bd_ptr_l;+u16data_buff_len;+u16non_used1;+}__packed;++structcldma_rgpd{+u8gpd_flags;+u8non_used;+u16data_allow_len;+u32next_gpd_ptr_h;+u32next_gpd_ptr_l;+u32data_buff_bd_ptr_h;+u32data_buff_bd_ptr_l;+u16data_buff_len;+u8non_used1;+u8debug_id;+}__packed;++intcldma_alloc(enumcldma_idhif_id,structmtk_pci_dev*mtk_dev);+voidcldma_hif_hw_init(enumcldma_idhif_id);+intcldma_init(enumcldma_idhif_id);+voidcldma_exception(enumcldma_idhif_id,enumhif_ex_stagestage);+voidcldma_exit(enumcldma_idhif_id);+voidcldma_switch_cfg(enumcldma_idhif_id);+intcldma_write_room(enumcldma_idhif_id,unsignedcharqno);+voidcldma_start(enumcldma_idhif_id);+intcldma_stop(enumcldma_idhif_id);+voidcldma_reset(enumcldma_idhif_id);+voidcldma_set_recv_skb(enumcldma_idhif_id,+int(*recv_skb)(structcldma_queue*queue,structsk_buff*skb));+intcldma_send_skb(enumcldma_idhif_id,intqno,structsk_buff*skb,+boolskb_from_pool,boolblocking);++#endif /* __T7XX_HIF_CLDMA_H__ */
@@ -0,0 +1,161 @@+/* SPDX-License-Identifier: GPL-2.0-only+*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChandrashekarDevegowda<chandrashekar.devegowda@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*/++#ifndef __T7XX_PORT_H__+#define __T7XX_PORT_H__++#include<linux/skbuff.h>+#include<linux/spinlock.h>+#include<linux/wait.h>+#include<linux/wwan.h>+#include<linux/types.h>++#include"t7xx_hif_cldma.h"++#define PORT_F_RX_ALLOW_DROP BIT(0) /* packet will be dropped if port's RX buffer full */+#define PORT_F_RX_FULLED BIT(1) /* RX buffer has been full once */+#define PORT_F_USER_HEADER BIT(2) /* CCCI header will be provided by user, but not by CCCI */+#define PORT_F_RX_EXCLUSIVE BIT(3) /* RX queue only has this one port */+#define PORT_F_RX_ADJUST_HEADER BIT(4) /* check whether need remove CCCI header while recv skb */+#define PORT_F_RX_CH_TRAFFIC BIT(5) /* enable port channel traffic */+#define PORT_F_RX_CHAR_NODE BIT(7) /* need export char dev node for userspace */+#define PORT_F_CHAR_NODE_SHOW BIT(10) /* char dev node is shown to userspace at default */++/* reused for net TX, Data queue, same bit as RX_FULLED */+#define PORT_F_TX_DATA_FULLED BIT(1)+#define PORT_F_TX_ACK_FULLED BIT(8)+#define PORT_F_RAW_DATA BIT(9)++#define CCCI_MAX_CH_ID 0xff /* RX channel ID should NOT be >= this!! */+#define CCCI_CH_ID_MASK 0xff++/* Channel ID and Message ID definitions.+*Thechannelnumberconsistsofpeer_id(15:12),channel_id(11:0)+*peer_id:+*0:reserved,1:tosAP,2:toMD+*/+enumccci_ch{+/* to MD */+CCCI_CONTROL_RX=0x2000,+CCCI_CONTROL_TX=0x2001,+CCCI_SYSTEM_RX=0x2002,+CCCI_SYSTEM_TX=0x2003,+CCCI_UART1_RX=0x2006,/* META */+CCCI_UART1_RX_ACK=0x2007,+CCCI_UART1_TX=0x2008,+CCCI_UART1_TX_ACK=0x2009,+CCCI_UART2_RX=0x200a,/* AT */+CCCI_UART2_RX_ACK=0x200b,+CCCI_UART2_TX=0x200c,+CCCI_UART2_TX_ACK=0x200d,+CCCI_MD_LOG_RX=0x202a,/* MD logging */+CCCI_MD_LOG_TX=0x202b,+CCCI_LB_IT_RX=0x203e,/* loop back test */+CCCI_LB_IT_TX=0x203f,+CCCI_STATUS_RX=0x2043,/* status polling */+CCCI_STATUS_TX=0x2044,+CCCI_MIPC_RX=0x20ce,/* MIPC */+CCCI_MIPC_TX=0x20cf,+CCCI_MBIM_RX=0x20d0,+CCCI_MBIM_TX=0x20d1,+CCCI_DSS0_RX=0x20d2,+CCCI_DSS0_TX=0x20d3,+CCCI_DSS1_RX=0x20d4,+CCCI_DSS1_TX=0x20d5,+CCCI_DSS2_RX=0x20d6,+CCCI_DSS2_TX=0x20d7,+CCCI_DSS3_RX=0x20d8,+CCCI_DSS3_TX=0x20d9,+CCCI_DSS4_RX=0x20da,+CCCI_DSS4_TX=0x20db,+CCCI_DSS5_RX=0x20dc,+CCCI_DSS5_TX=0x20dd,+CCCI_DSS6_RX=0x20de,+CCCI_DSS6_TX=0x20df,+CCCI_DSS7_RX=0x20e0,+CCCI_DSS7_TX=0x20e1,+CCCI_MAX_CH_NUM,+CCCI_MONITOR_CH_ID=GENMASK(31,28),/* for MD init */+CCCI_INVALID_CH_ID=GENMASK(15,0),+};++structt7xx_port;+structport_ops{+int(*init)(structt7xx_port*port);+int(*recv_skb)(structt7xx_port*port,structsk_buff*skb);+void(*md_state_notify)(structt7xx_port*port,unsignedintmd_state);+void(*uninit)(structt7xx_port*port);+int(*enable_chl)(structt7xx_port*port);+int(*disable_chl)(structt7xx_port*port);+};++typedefvoid(*port_skb_handler)(structt7xx_port*port,structsk_buff*skb);++structt7xx_port{+/* members used for initialization, do not change the order */+enumccci_chtx_ch;+enumccci_chrx_ch;+unsignedchartxq_index;+unsignedcharrxq_index;+unsignedchartxq_exp_index;+unsignedcharrxq_exp_index;+enumcldma_idpath_id;+unsignedintflags;+structport_ops*ops;+unsignedintminor;+char*name;+enumwwan_port_typemtk_port_type;++/* members un-initialized in definition */+structwwan_port*mtk_wwan_port;+structmtk_pci_dev*mtk_dev;+structdevice*dev;+shortseq_nums[2];+structport_proxy*port_proxy;+atomic_tusage_cnt;+structlist_headentry;+structlist_headqueue_entry;+unsignedintmajor;+unsignedintminor_base;+/* TX and RX flows are asymmetric since ports are multiplexed on+*queues.+*+*TX:datablocksaresentdirectlytoaqueue.Eachport+*doesnotmaintainaTXlist;instead,theyonlyprovide+*await_queue_headforblockingwrites.+*+*RX:EachportusesaRXlisttoholdpackets,+*allowingthemodemtodispatchRXpacketasquicklyaspossible.+*/+structsk_buff_headrx_skb_list;+boolskb_from_pool;+spinlock_tport_update_lock;/* protects port configuration */+wait_queue_head_trx_wq;+intrx_length_th;+port_skb_handlerskb_handler;+unsignedcharchan_enable;+unsignedcharchn_crt_stat;+structcdev*cdev;+structtask_struct*thread;+structmutextx_mutex_lock;/* protects the seq number operation */+};++intport_kthread_handler(void*arg);+intport_recv_skb(structt7xx_port*port,structsk_buff*skb);+intport_write_room_to_md(structt7xx_port*port);+structt7xx_port*port_get_by_minor(intminor);+structt7xx_port*port_get_by_name(char*port_name);+intport_send_skb_to_md(structt7xx_port*port,structsk_buff*skb,boolblocking);++#endif /* __T7XX_PORT_H__ */
@@ -0,0 +1,775 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChandrashekarDevegowda<chandrashekar.devegowda@intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/bitfield.h>+#include<linux/kernel.h>+#include<linux/list.h>+#include<linux/netlink.h>+#include<linux/skbuff.h>+#include<linux/spinlock.h>+#include<linux/string.h>++#include"t7xx_hif_cldma.h"+#include"t7xx_modem_ops.h"+#include"t7xx_monitor.h"+#include"t7xx_port_proxy.h"+#include"t7xx_skb_util.h"++#define MTK_DEV_NAME "MTK_WWAN_M80"++#define PORT_NETLINK_MSG_MAX_PAYLOAD 32+#define PORT_NOTIFY_PROTOCOL NETLINK_USERSOCK+#define PORT_STATE_BROADCAST_GROUP 21+#define CHECK_RX_SEQ_MASK 0x7fff+#define DATA_AT_CMD_Q 5++/* port->minor is configured in-sequence, but when we use it in code+*itshouldbeuniqueamongallportsforaddressing.+*/+#define TTY_IPC_MINOR_BASE 100+#define TTY_PORT_MINOR_BASE 250+#define TTY_PORT_MINOR_INVALID -1++staticstructport_proxy*port_prox;++#define for_each_proxy_port(i, p, proxy) \+for(i=0,(p)=&(proxy)->ports[i];\+i<(proxy)->port_number;\+i++,(p)=&(proxy)->ports[i])++staticstructport_opsdummy_port_ops;++staticstructt7xx_portmd_ccci_ports[]={+{0,0,0,0,0,0,ID_CLDMA1,0,&dummy_port_ops,0xff,"dummy_port",},+};++staticintport_netlink_send_msg(structt7xx_port*port,intgrp,+constchar*buf,size_tlen)+{+structport_proxy*pprox;+structsk_buff*nl_skb;+structnlmsghdr*nlh;++nl_skb=nlmsg_new(len,GFP_KERNEL);+if(!nl_skb)+return-ENOMEM;++nlh=nlmsg_put(nl_skb,0,1,NLMSG_DONE,len,0);+if(!nlh){+dev_err(port->dev,"could not release netlink\n");+nlmsg_free(nl_skb);+return-EFAULT;+}++/* Add new netlink message to the skb+*aftercheckingifheader+payload+*canbehandled.+*/+memcpy(nlmsg_data(nlh),buf,len);++pprox=port->port_proxy;+returnnetlink_broadcast(pprox->netlink_sock,nl_skb,+0,grp,GFP_KERNEL);+}++staticintport_netlink_init(void)+{+port_prox->netlink_sock=netlink_kernel_create(&init_net,PORT_NOTIFY_PROTOCOL,NULL);++if(!port_prox->netlink_sock){+dev_err(port_prox->dev,"failed to create netlink socket\n");+return-ENOMEM;+}++return0;+}++staticvoidport_netlink_uninit(void)+{+if(port_prox->netlink_sock){+netlink_kernel_release(port_prox->netlink_sock);+port_prox->netlink_sock=NULL;+}+}++staticstructt7xx_port*proxy_get_port(intminor,enumccci_chch)+{+structt7xx_port*port;+inti;++if(!port_prox)+returnNULL;++for_each_proxy_port(i,port,port_prox){+if(minor>=0&&port->minor==minor)+returnport;++if(ch!=CCCI_INVALID_CH_ID&&(port->rx_ch==ch||port->tx_ch==ch))+returnport;+}++returnNULL;+}++structt7xx_port*port_proxy_get_port(intmajor,intminor)+{+if(port_prox&&port_prox->major==major)+returnproxy_get_port(minor,CCCI_INVALID_CH_ID);++returnNULL;+}++staticinlinestructt7xx_port*port_get_by_ch(enumccci_chch)+{+returnproxy_get_port(TTY_PORT_MINOR_INVALID,ch);+}++/* Sequence numbering to track for lost packets */+voidport_proxy_set_seq_num(structt7xx_port*port,structccci_header*ccci_h)+{+if(ccci_h&&port){+ccci_h->status&=~HDR_FLD_SEQ;+ccci_h->status|=FIELD_PREP(HDR_FLD_SEQ,port->seq_nums[MTK_OUT]);+ccci_h->status&=~HDR_FLD_AST;+ccci_h->status|=FIELD_PREP(HDR_FLD_AST,1);+}+}++staticu16port_check_rx_seq_num(structt7xx_port*port,structccci_header*ccci_h)+{+u16channel,seq_num,assert_bit;++channel=FIELD_GET(HDR_FLD_CHN,ccci_h->status);+seq_num=FIELD_GET(HDR_FLD_SEQ,ccci_h->status);+assert_bit=FIELD_GET(HDR_FLD_AST,ccci_h->status);+if(assert_bit&&port->seq_nums[MTK_IN]&&+((seq_num-port->seq_nums[MTK_IN])&CHECK_RX_SEQ_MASK)!=1){+dev_err(port->dev,"channel %d seq number out-of-order %d->%d (data: %X, %X)\n",+channel,seq_num,port->seq_nums[MTK_IN],+ccci_h->data[0],ccci_h->data[1]);+}++returnseq_num;+}++voidport_proxy_reset(structdevice*dev)+{+structt7xx_port*port;+inti;++if(!port_prox){+dev_err(dev,"invalid port proxy\n");+return;+}++for_each_proxy_port(i,port,port_prox){+port->seq_nums[MTK_IN]=-1;+port->seq_nums[MTK_OUT]=0;+}+}++staticinlineintport_get_queue_no(structt7xx_port*port)+{+returnccci_fsm_get_md_state()==MD_STATE_EXCEPTION?+port->txq_exp_index:port->txq_index;+}++staticinlinevoidport_struct_init(structt7xx_port*port)+{+INIT_LIST_HEAD(&port->entry);+INIT_LIST_HEAD(&port->queue_entry);+skb_queue_head_init(&port->rx_skb_list);+init_waitqueue_head(&port->rx_wq);+port->seq_nums[MTK_IN]=-1;+port->seq_nums[MTK_OUT]=0;+atomic_set(&port->usage_cnt,0);+port->port_proxy=port_prox;+}++staticvoidport_adjust_skb(structt7xx_port*port,structsk_buff*skb)+{+structccci_header*ccci_h;++ccci_h=(structccci_header*)skb->data;+if(port->flags&PORT_F_USER_HEADER){/* header provide by user */+/* CCCI_MON_CH should fall in here, as header must be+*sendtomd_init.+*/+if(ccci_h->data[0]==CCCI_HEADER_NO_DATA){+if(skb->len>sizeof(structccci_header)){+dev_err_ratelimited(port->dev,+"recv unexpected data for %s, skb->len=%d\n",+port->name,skb->len);+skb_trim(skb,sizeof(structccci_header));+}+}+}else{+/* remove CCCI header */+skb_pull(skb,sizeof(structccci_header));+}+}++/**+*port_recv_skb()-receiveskbfrommodemorHIF+*@port:porttouse+*@skb:skbtouse+*+*UsedtoreceivenativeHIFRXdata,+*whichhavesameRXreceiveflow.+*+*Return:0forsuccessorerrorcode+*/+intport_recv_skb(structt7xx_port*port,structsk_buff*skb)+{+unsignedlongflags;++spin_lock_irqsave(&port->rx_wq.lock,flags);+if(port->rx_skb_list.qlen<port->rx_length_th){+structccci_header*ccci_h=(structccci_header*)skb->data;++port->flags&=~PORT_F_RX_FULLED;+if(port->flags&PORT_F_RX_ADJUST_HEADER)+port_adjust_skb(port,skb);++if(!(port->flags&PORT_F_RAW_DATA)&&+FIELD_GET(HDR_FLD_CHN,ccci_h->status)==CCCI_STATUS_RX){+port->skb_handler(port,skb);+}else{+if(port->mtk_wwan_port)+wwan_port_rx(port->mtk_wwan_port,skb);+else+__skb_queue_tail(&port->rx_skb_list,skb);+}++spin_unlock_irqrestore(&port->rx_wq.lock,flags);+wake_up_all(&port->rx_wq);+return0;+}++port->flags|=PORT_F_RX_FULLED;+spin_unlock_irqrestore(&port->rx_wq.lock,flags);+if(port->flags&PORT_F_RX_ALLOW_DROP){+dev_err(port->dev,"port %s RX full, drop packet\n",port->name);+return-ENETDOWN;+}++return-ENOBUFS;+}++/**+*port_kthread_handler()-kthreadhandlerforspecificport+*@arg:portpointer+*+*ReceivenativeHIFRXdata,+*whichhavesameRXreceiveflow.+*+*Return:Always0tokthread_run+*/+intport_kthread_handler(void*arg)+{+while(!kthread_should_stop()){+structt7xx_port*port=arg;+structsk_buff*skb;+unsignedlongflags;++spin_lock_irqsave(&port->rx_wq.lock,flags);+if(skb_queue_empty(&port->rx_skb_list)&&+wait_event_interruptible_locked_irq(port->rx_wq,+!skb_queue_empty(&port->rx_skb_list)||+kthread_should_stop())){+spin_unlock_irqrestore(&port->rx_wq.lock,flags);+continue;+}++if(kthread_should_stop()){+spin_unlock_irqrestore(&port->rx_wq.lock,flags);+break;+}++skb=__skb_dequeue(&port->rx_skb_list);+spin_unlock_irqrestore(&port->rx_wq.lock,flags);++if(port->skb_handler)+port->skb_handler(port,skb);+}++return0;+}++intport_write_room_to_md(structt7xx_port*port)+{+returncldma_write_room(port->path_id,port_get_queue_no(port));+}++intport_proxy_send_skb(structt7xx_port*port,structsk_buff*skb,boolfrom_pool)+{+structccci_header*ccci_h;+unsignedchartx_qno;+intret;++ccci_h=(structccci_header*)(skb->data);+tx_qno=port_get_queue_no(port);+port_proxy_set_seq_num(port,(structccci_header*)ccci_h);+ret=cldma_send_skb(port->path_id,tx_qno,skb,from_pool,true);+if(ret){+dev_err(port->dev,"failed to send skb, error: %d\n",ret);+}else{+/* Record the port seq_num after the data is sent to HIF.+*Onlybits0-14areused,thusnegatingoverflow.+*/+port->seq_nums[MTK_OUT]++;+}++returnret;+}++intport_send_skb_to_md(structt7xx_port*port,structsk_buff*skb,boolblocking)+{+enummd_statemd_state;+unsignedintfsm_state;++md_state=ccci_fsm_get_md_state();+fsm_state=ccci_fsm_get_current_state();+if(fsm_state!=CCCI_FSM_PRE_START){+if(md_state==MD_STATE_WAITING_FOR_HS1||+md_state==MD_STATE_WAITING_FOR_HS2){+return-ENODEV;+}++if(md_state==MD_STATE_EXCEPTION&&+port->tx_ch!=CCCI_MD_LOG_TX&&+port->tx_ch!=CCCI_UART1_TX){+return-ETXTBSY;+}++if(md_state==MD_STATE_STOPPED||+md_state==MD_STATE_WAITING_TO_STOP||+md_state==MD_STATE_INVALID){+return-ENODEV;+}+}++returncldma_send_skb(port->path_id,port_get_queue_no(port),+skb,port->skb_from_pool,blocking);+}++staticvoidproxy_setup_channel_mapping(void)+{+structt7xx_port*port;+inti,j;++/* init RX_CH=>port list mapping */+for(i=0;i<ARRAY_SIZE(port_prox->rx_ch_ports);i++)+INIT_LIST_HEAD(&port_prox->rx_ch_ports[i]);+/* init queue_id=>port list mapping per HIF */+for(j=0;j<ARRAY_SIZE(port_prox->queue_ports);j++){+for(i=0;i<ARRAY_SIZE(port_prox->queue_ports[j]);i++)+INIT_LIST_HEAD(&port_prox->queue_ports[j][i]);+}++/* setup port mapping */+for_each_proxy_port(i,port,port_prox){+/* setup RX_CH=>port list mapping */+list_add_tail(&port->entry,+&port_prox->rx_ch_ports[port->rx_ch&CCCI_CH_ID_MASK]);+/* setup QUEUE_ID=>port list mapping */+list_add_tail(&port->queue_entry,+&port_prox->queue_ports[port->path_id][port->rxq_index]);+}+}++/* inject CCCI message to modem */+voidport_proxy_send_msg_to_md(intch,unsignedintmsg,unsignedintresv)+{+structctrl_msg_header*ctrl_msg_h;+structccci_header*ccci_h;+structt7xx_port*port;+structsk_buff*skb;+intret;++port=port_get_by_ch(ch);+if(!port)+return;++skb=ccci_alloc_skb_from_pool(&port->mtk_dev->pools,sizeof(structccci_header),+GFS_BLOCKING);+if(!skb)+return;++if(ch==CCCI_CONTROL_TX){+ccci_h=(structccci_header*)(skb->data);+ccci_h->data[0]=CCCI_HEADER_NO_DATA;+ccci_h->data[1]=sizeof(structctrl_msg_header)+CCCI_H_LEN;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,ch);+ccci_h->reserved=0;+ctrl_msg_h=(structctrl_msg_header*)(skb->data+CCCI_H_LEN);+ctrl_msg_h->data_length=0;+ctrl_msg_h->reserved=resv;+ctrl_msg_h->ctrl_msg_id=msg;+skb_put(skb,CCCI_H_LEN+sizeof(structctrl_msg_header));+}else{+ccci_h=skb_put(skb,sizeof(structccci_header));+ccci_h->data[0]=CCCI_HEADER_NO_DATA;+ccci_h->data[1]=msg;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,ch);+ccci_h->reserved=resv;+}++ret=port_proxy_send_skb(port,skb,port->skb_from_pool);+if(ret){+dev_err(port->dev,"port%s send to MD fail\n",port->name);+ccci_free_skb(&port->mtk_dev->pools,skb);+}+}++/**+*port_proxy_recv_skb_from_q()-receiverawdatafromdedicatedqueue+*@queue:CLDMAqueue+*@skb:socketbuffer+*+*Return:0forsuccessorerrorcodefordrops+*/+staticintport_proxy_recv_skb_from_q(structcldma_queue*queue,structsk_buff*skb)+{+structt7xx_port*port;+intret=0;++port=port_prox->dedicated_ports[queue->hif_id][queue->index];++if(skb&&port->ops->recv_skb)+ret=port->ops->recv_skb(port,skb);++if(ret<0&&ret!=-ENOBUFS){+dev_err(port->dev,"drop on RX ch %d, ret %d\n",port->rx_ch,ret);+ccci_free_skb(&port->mtk_dev->pools,skb);+return-ENETDOWN;+}++returnret;+}++/**+*port_proxy_dispatch_recv_skb()-dispatchreceivedskb+*@queue:CLDMAqueue+*@skb:socketbuffer+*+*Ifrecv_requestreturns0or-ENETDOWN,thenit'stheport'sduty+*tofreetherequestandthecallershouldnolongerreferencetherequest.+*Ifrecv_requestreturnsanyothererror,callershouldfreetherequest.+*+*Return:0orgreaterforsuccess,ornegativeerrorcode+*/+staticintport_proxy_dispatch_recv_skb(structcldma_queue*queue,structsk_buff*skb)+{+structlist_head*port_list;+structccci_header*ccci_h;+structt7xx_port*port;+u16seq_num,channel;+intret=-ENETDOWN;++if(!skb)+return-EINVAL;++ccci_h=(structccci_header*)skb->data;+channel=FIELD_GET(HDR_FLD_CHN,ccci_h->status);++if(channel>=CCCI_MAX_CH_NUM||+ccci_fsm_get_md_state()==MD_STATE_INVALID)+gotoerr_exit;++port_list=&port_prox->rx_ch_ports[channel&CCCI_CH_ID_MASK];+list_for_each_entry(port,port_list,entry){+if(queue->hif_id!=port->path_id||channel!=port->rx_ch)+continue;++/* Multi-cast is not supported, because one port may be freed+*andcanmodifythisrequestbeforeanotherportcanprocessit.+*Howeverwestillcanusereq->statetoachievesomekindof+*multi-castifneeded.+*/+if(port->ops->recv_skb){+seq_num=port_check_rx_seq_num(port,ccci_h);+ret=port->ops->recv_skb(port,skb);+/* If the packet is stored to RX buffer+*successfullyordrop,thesequence+*numwillbeupdated.+*/+if(ret==-ENOBUFS)+returnret;++port->seq_nums[MTK_IN]=seq_num;+}++break;+}++err_exit:+if(ret<0){+structskb_pools*pools;++pools=&queue->md->mtk_dev->pools;+ccci_free_skb(pools,skb);+return-ENETDOWN;+}++return0;+}++staticintport_proxy_recv_skb(structcldma_queue*queue,structsk_buff*skb)+{+if(queue->q_type==CLDMA_SHARED_Q)+returnport_proxy_dispatch_recv_skb(queue,skb);++returnport_proxy_recv_skb_from_q(queue,skb);+}++/**+*port_proxy_md_status_notify()-notifyallportsofstate+*@state:state+*+*Calledbyccci_fsm,+*Usedtodispatchmodemstatusforallports,+*whichwanttoknowMDstatetransition.+*/+voidport_proxy_md_status_notify(unsignedintstate)+{+structt7xx_port*port;+inti;++if(!port_prox)+return;++for_each_proxy_port(i,port,port_prox)+if(port->ops->md_state_notify)+port->ops->md_state_notify(port,state);+}++staticintproxy_register_char_dev(void)+{+dev_tdev=0;+intret;++if(port_prox->major){+dev=MKDEV(port_prox->major,port_prox->minor_base);+ret=register_chrdev_region(dev,TTY_IPC_MINOR_BASE,MTK_DEV_NAME);+}else{+ret=alloc_chrdev_region(&dev,port_prox->minor_base,+TTY_IPC_MINOR_BASE,MTK_DEV_NAME);+if(ret)+dev_err(port_prox->dev,"failed to alloc chrdev region, ret=%d\n",ret);++port_prox->major=MAJOR(dev);+}++returnret;+}++staticvoidproxy_init_all_ports(structmtk_modem*md)+{+structt7xx_port*port;+inti;++for_each_proxy_port(i,port,port_prox){+port_struct_init(port);++port->major=port_prox->major;+port->minor_base=port_prox->minor_base;+port->mtk_dev=md->mtk_dev;+port->dev=&md->mtk_dev->pdev->dev;+spin_lock_init(&port->port_update_lock);+spin_lock(&port->port_update_lock);+mutex_init(&port->tx_mutex_lock);+if(port->flags&PORT_F_CHAR_NODE_SHOW)+port->chan_enable=CCCI_CHAN_ENABLE;+else+port->chan_enable=CCCI_CHAN_DISABLE;++port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+if(port->ops->init)+port->ops->init(port);++if(port->flags&PORT_F_RAW_DATA)+port_prox->dedicated_ports[port->path_id][port->rxq_index]=port;+}++proxy_setup_channel_mapping();+}++staticintproxy_alloc(structmtk_modem*md)+{+intret;++port_prox=devm_kzalloc(&md->mtk_dev->pdev->dev,sizeof(*port_prox),GFP_KERNEL);+if(!port_prox)+return-ENOMEM;++ret=proxy_register_char_dev();+if(ret)+returnret;++port_prox->dev=&md->mtk_dev->pdev->dev;+port_prox->ports=md_ccci_ports;+port_prox->port_number=ARRAY_SIZE(md_ccci_ports);+proxy_init_all_ports(md);++return0;+};++structt7xx_port*port_get_by_minor(intminor)+{+returnproxy_get_port(minor,CCCI_INVALID_CH_ID);+}++structt7xx_port*port_get_by_name(char*port_name)+{+structt7xx_port*port;+inti;++if(!port_prox)+returnNULL;++for_each_proxy_port(i,port,port_prox)+if(!strncmp(port->name,port_name,strlen(port->name)))+returnport;++returnNULL;+}++intport_proxy_broadcast_state(structt7xx_port*port,intstate)+{+charmsg[PORT_NETLINK_MSG_MAX_PAYLOAD];++if(state>=MTK_PORT_STATE_INVALID)+return-EINVAL;++switch(state){+caseMTK_PORT_STATE_ENABLE:+snprintf(msg,PORT_NETLINK_MSG_MAX_PAYLOAD,"enable %s",port->name);+break;++caseMTK_PORT_STATE_DISABLE:+snprintf(msg,PORT_NETLINK_MSG_MAX_PAYLOAD,"disable %s",port->name);+break;++default:+snprintf(msg,PORT_NETLINK_MSG_MAX_PAYLOAD,"invalid operation");+break;+}++returnport_netlink_send_msg(port,PORT_STATE_BROADCAST_GROUP,msg,strlen(msg)+1);+}++/**+*port_proxy_init()-initports+*@md:modem+*+*CalledbyCCCImodem,+*usedtocreateallCCCIportinstances.+*+*Return:0forsuccessorerror+*/+intport_proxy_init(structmtk_modem*md)+{+intret;++ret=proxy_alloc(md);+if(ret)+returnret;++ret=port_netlink_init();+if(ret)+gotoerr_netlink;++cldma_set_recv_skb(ID_CLDMA1,port_proxy_recv_skb);++return0;++err_netlink:+port_proxy_uninit();++returnret;+}++voidport_proxy_uninit(void)+{+structt7xx_port*port;+inti;++for_each_proxy_port(i,port,port_prox)+if(port->ops->uninit)+port->ops->uninit(port);++unregister_chrdev_region(MKDEV(port_prox->major,port_prox->minor_base),+TTY_IPC_MINOR_BASE);+port_netlink_uninit();+}++/**+*port_proxy_node_control()-create/removenode+*@dev:device+*@port_msg:message+*+*Usedtocontrolcreate/removedevicenode.+*+*Return:0forsuccessorerror+*/+intport_proxy_node_control(structdevice*dev,structport_msg*port_msg)+{+structt7xx_port*port;+unsignedintports,i;+unsignedintversion;++version=FIELD_GET(PORT_MSG_VERSION,port_msg->info);+if(version!=PORT_ENUM_VER||+port_msg->head_pattern!=PORT_ENUM_HEAD_PATTERN||+port_msg->tail_pattern!=PORT_ENUM_TAIL_PATTERN){+dev_err(dev,"port message enumeration invalid %x:%x:%x\n",+version,port_msg->head_pattern,port_msg->tail_pattern);+return-EFAULT;+}++ports=FIELD_GET(PORT_MSG_PRT_CNT,port_msg->info);++for(i=0;i<ports;i++){+u32*port_info=(u32*)(port_msg->data+sizeof(*port_info)*i);+unsignedinten_flag=FIELD_GET(PORT_INFO_ENFLG,*port_info);+unsignedintch_id=FIELD_GET(PORT_INFO_CH_ID,*port_info);++port=port_get_by_ch(ch_id);++if(!port){+dev_warn(dev,"Port:%x not found\n",ch_id);+continue;+}++if(ccci_fsm_get_md_state()==MD_STATE_READY){+if(en_flag==CCCI_CHAN_ENABLE){+if(port->ops->enable_chl)+port->ops->enable_chl(port);+}else{+if(port->ops->disable_chl)+port->ops->disable_chl(port);+}+}else{+port->chan_enable=en_flag;+}+}++return0;+}
@@ -202,11 +208,29 @@ static void fsm_routine_exception(struct ccci_fsm_ctl *ctl, struct ccci_fsm_commif(!list_empty(&ctl->event_queue)){event=list_first_entry(&ctl->event_queue,structccci_fsm_event,entry);-if(event->event_id==CCCI_EVENT_MD_EX_PASS)+if(event->event_id==CCCI_EVENT_MD_EX_PASS){+pass=true;fsm_finish_event(ctl,event);+}}spin_unlock_irqrestore(&ctl->event_lock,flags);+if(pass){+log_port=port_get_by_name("ttyCMdLog");+if(log_port)+log_port->ops->enable_chl(log_port);+else+dev_err(dev,"ttyCMdLog port not found\n");++meta_port=port_get_by_name("ttyCMdMeta");+if(meta_port)+meta_port->ops->enable_chl(meta_port);+else+dev_err(dev,"ttyCMdMeta port not found\n");++break;+}+cnt++;msleep(EVENT_POLL_INTERVAL_MS);}
@@ -80,6 +80,20 @@ config IOSM If unsure, say N.+config MTK_T7XX+ tristate "MediaTek PCIe 5G WWAN modem T7XX device"+ depends on PCI+ help+ Enables MediaTek PCIe based 5G WWAN modem (T700 series) device.+ Adapts WWAN framework and provides network interface like wwan0+ and tty interfaces like wwan0at0 (AT protocol), wwan0mbim0+ (MBIM protocol), etc.++ To compile this driver as a module, choose M here: the module will be+ called mtk_t7xx.++ If unsure, say N.+ endif # WWAN endmenu
@@ -0,0 +1,1524 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/bitfield.h>+#include<linux/bitops.h>+#include<linux/delay.h>+#include<linux/io.h>+#include<linux/iopoll.h>+#include<linux/kernel.h>++#include"t7xx_dpmaif.h"+#include"t7xx_reg.h"++staticintdpmaif_init_intr(structdpmaif_hw_info*hw_info)+{+structdpmaif_isr_en_mask*isr_en_msk;+u32value,l2intr_en,l2intr_err_en;+intret;++isr_en_msk=&hw_info->isr_en_mask;++/* set UL interrupt */+l2intr_en=DPMAIF_UL_INT_ERR_MSK|DPMAIF_UL_INT_QDONE_MSK;+isr_en_msk->ap_ul_l2intr_en_msk=l2intr_en;++iowrite32(DPMAIF_AP_ALL_L2TISAR0_MASK,hw_info->pcie_base+DPMAIF_AP_L2TISAR0);++/* set interrupt enable mask */+iowrite32(l2intr_en,hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMCR0);+iowrite32(~l2intr_en,hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMSR0);++/* check mask status */+ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMR0,+value,(value&l2intr_en)!=l2intr_en,+0,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(ret)+returnret;++/* set DL interrupt */+l2intr_err_en=DPMAIF_DL_INT_PITCNT_LEN_ERR|DPMAIF_DL_INT_BATCNT_LEN_ERR;+isr_en_msk->ap_dl_l2intr_err_en_msk=l2intr_err_en;++l2intr_en=DPMAIF_DL_INT_DLQ0_QDONE|DPMAIF_DL_INT_DLQ0_PITCNT_LEN|+DPMAIF_DL_INT_DLQ1_QDONE|DPMAIF_DL_INT_DLQ1_PITCNT_LEN;+isr_en_msk->ap_ul_l2intr_en_msk=l2intr_en;++iowrite32(DPMAIF_AP_APDL_ALL_L2TISAR0_MASK,hw_info->pcie_base+DPMAIF_AP_APDL_L2TISAR0);++/* set DL ISR PD enable mask */+iowrite32(~l2intr_en,hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);++/* check mask status */+ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMR0,+value,(value&l2intr_en)!=l2intr_en,+0,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(ret)+returnret;++isr_en_msk->ap_udl_ip_busy_en_msk=DPMAIF_UDL_IP_BUSY;++iowrite32(DPMAIF_AP_IP_BUSY_MASK,hw_info->pcie_base+DPMAIF_AP_IP_BUSY);++iowrite32(isr_en_msk->ap_udl_ip_busy_en_msk,+hw_info->pcie_base+DPMAIF_AO_AP_DLUL_IP_BUSY_MASK);++value=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_AP_L1TIMR0);+value|=DPMAIF_DL_INT_Q2APTOP|DPMAIF_DL_INT_Q2TOQ1;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_UL_AP_L1TIMR0);+iowrite32(DPMA_HPC_ALL_INT_MASK,hw_info->pcie_base+DPMAIF_HPC_INTR_MASK);++return0;+}++staticvoiddpmaif_mask_ulq_interrupt(structdpmaif_ctrl*dpmaif_ctrl,unsignedintq_num)+{+structdpmaif_isr_en_mask*isr_en_msk;+structdpmaif_hw_info*hw_info;+u32value,ul_int_que_done;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+isr_en_msk=&hw_info->isr_en_mask;+ul_int_que_done=BIT(q_num+_UL_INT_DONE_OFFSET)&DPMAIF_UL_INT_QDONE_MSK;+isr_en_msk->ap_ul_l2intr_en_msk&=~ul_int_que_done;+iowrite32(ul_int_que_done,hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMSR0);++/* check mask status */+ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMR0,+value,(value&ul_int_que_done)==ul_int_que_done,+0,DPMAIF_CHECK_TIMEOUT_US);+if(ret)+dev_err(dpmaif_ctrl->dev,+"Could not mask the UL interrupt. DPMAIF_AO_UL_AP_L2TIMR0 is 0x%x\n",+value);+}++voiddpmaif_unmask_ulq_interrupt(structdpmaif_ctrl*dpmaif_ctrl,unsignedintq_num)+{+structdpmaif_isr_en_mask*isr_en_msk;+structdpmaif_hw_info*hw_info;+u32value,ul_int_que_done;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+isr_en_msk=&hw_info->isr_en_mask;+ul_int_que_done=BIT(q_num+_UL_INT_DONE_OFFSET)&DPMAIF_UL_INT_QDONE_MSK;+isr_en_msk->ap_ul_l2intr_en_msk|=ul_int_que_done;+iowrite32(ul_int_que_done,hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMCR0);++/* check mask status */+ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMR0,+value,(value&ul_int_que_done)!=ul_int_que_done,+0,DPMAIF_CHECK_TIMEOUT_US);+if(ret)+dev_err(dpmaif_ctrl->dev,+"Could not unmask the UL interrupt. DPMAIF_AO_UL_AP_L2TIMR0 is 0x%x\n",+value);+}++staticvoiddpmaif_mask_dl_batcnt_len_err_interrupt(structdpmaif_hw_info*hw_info)+{+hw_info->isr_en_mask.ap_dl_l2intr_en_msk&=~DPMAIF_DL_INT_BATCNT_LEN_ERR;+iowrite32(DPMAIF_DL_INT_BATCNT_LEN_ERR,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);+}++voiddpmaif_unmask_dl_batcnt_len_err_interrupt(structdpmaif_hw_info*hw_info)+{+hw_info->isr_en_mask.ap_dl_l2intr_en_msk|=DPMAIF_DL_INT_BATCNT_LEN_ERR;+iowrite32(DPMAIF_DL_INT_BATCNT_LEN_ERR,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMCR0);+}++staticvoiddpmaif_mask_dl_pitcnt_len_err_interrupt(structdpmaif_hw_info*hw_info)+{+hw_info->isr_en_mask.ap_dl_l2intr_en_msk&=~DPMAIF_DL_INT_PITCNT_LEN_ERR;+iowrite32(DPMAIF_DL_INT_PITCNT_LEN_ERR,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);+}++voiddpmaif_unmask_dl_pitcnt_len_err_interrupt(structdpmaif_hw_info*hw_info)+{+hw_info->isr_en_mask.ap_dl_l2intr_en_msk|=DPMAIF_DL_INT_PITCNT_LEN_ERR;+iowrite32(DPMAIF_DL_INT_PITCNT_LEN_ERR,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMCR0);+}++staticu32update_dlq_interrupt(structdpmaif_hw_info*hw_info,u32q_done)+{+u32value;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMR0);+iowrite32(q_done,hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);++returnvalue;+}++staticintmask_dlq_interrupt(structdpmaif_ctrl*dpmaif_ctrl,unsignedcharqno)+{+structdpmaif_hw_info*hw_info;+u32value,q_done;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+q_done=(qno==DPF_RX_QNO0)?DPMAIF_DL_INT_DLQ0_QDONE:DPMAIF_DL_INT_DLQ1_QDONE;+iowrite32(q_done,hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);++/* check mask status */+ret=read_poll_timeout_atomic(update_dlq_interrupt,value,value&q_done,+0,DPMAIF_CHECK_TIMEOUT_US,false,hw_info,q_done);+if(ret){+dev_err(dpmaif_ctrl->dev,"mask_dl_dlq0 check timeout\n");+return-ETIMEDOUT;+}++hw_info->isr_en_mask.ap_dl_l2intr_en_msk&=~q_done;++return0;+}++voiddpmaif_hw_dlq_unmask_rx_done(structdpmaif_hw_info*hw_info,unsignedcharqno)+{+u32mask;++mask=(qno==DPF_RX_QNO0)?DPMAIF_DL_INT_DLQ0_QDONE:DPMAIF_DL_INT_DLQ1_QDONE;+iowrite32(mask,hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMCR0);+hw_info->isr_en_mask.ap_dl_l2intr_en_msk|=mask;+}++voiddpmaif_clr_ip_busy_sts(structdpmaif_hw_info*hw_info)+{+u32ip_busy_sts=ioread32(hw_info->pcie_base+DPMAIF_AP_IP_BUSY);++iowrite32(ip_busy_sts,hw_info->pcie_base+DPMAIF_AP_IP_BUSY);+}++staticvoiddpmaif_dlq_mask_rx_pitcnt_len_err_intr(structdpmaif_hw_info*hw_info,+unsignedcharqno)+{+if(qno==DPF_RX_QNO0)+iowrite32(DPMAIF_DL_INT_DLQ0_PITCNT_LEN,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);+else+iowrite32(DPMAIF_DL_INT_DLQ1_PITCNT_LEN,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMSR0);+}++voiddpmaif_dlq_unmask_rx_pitcnt_len_err_intr(structdpmaif_hw_info*hw_info,unsignedcharqno)+{+if(qno==DPF_RX_QNO0)+iowrite32(DPMAIF_DL_INT_DLQ0_PITCNT_LEN,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMCR0);+else+iowrite32(DPMAIF_DL_INT_DLQ1_PITCNT_LEN,+hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMCR0);+}++voiddpmaif_clr_ul_all_interrupt(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_AP_ALL_L2TISAR0_MASK,hw_info->pcie_base+DPMAIF_AP_L2TISAR0);+}++voiddpmaif_clr_dl_all_interrupt(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_AP_APDL_ALL_L2TISAR0_MASK,hw_info->pcie_base+DPMAIF_AP_APDL_L2TISAR0);+}++/* para->intr_cnt counter is set to zero before this function is called.+*itisnotcheckforoverflowasthereisnoriskofoverflowingintr_typesorintr_queues.+*/+staticvoiddpmaif_hw_check_tx_interrupt(structdpmaif_ctrl*dpmaif_ctrl,+unsignedintl2_txisar0,+structdpmaif_hw_intr_st_para*para)+{+unsignedintindex;+unsignedintvalue;++value=l2_txisar0&DP_UL_INT_QDONE_MSK;+if(value){+unsignedlongpending=(value>>DP_UL_INT_DONE_OFFSET)&DP_UL_QNUM_MSK;++para->intr_types[para->intr_cnt]=DPF_INTR_UL_DONE;+para->intr_queues[para->intr_cnt]=pending;++/* mask TX queue interrupt immediately after it occurs */+for_each_set_bit(index,&pending,DPMAIF_TXQ_NUM)+dpmaif_mask_ulq_interrupt(dpmaif_ctrl,index);++para->intr_cnt++;+}++value=l2_txisar0&DP_UL_INT_EMPTY_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_UL_DRB_EMPTY;+para->intr_queues[para->intr_cnt]=+(value>>DP_UL_INT_EMPTY_OFFSET)&DP_UL_QNUM_MSK;+para->intr_cnt++;+}++value=l2_txisar0&DP_UL_INT_MD_NOTREADY_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_UL_MD_NOTREADY;+para->intr_queues[para->intr_cnt]=+(value>>DP_UL_INT_MD_NOTRDY_OFFSET)&DP_UL_QNUM_MSK;+para->intr_cnt++;+}++value=l2_txisar0&DP_UL_INT_MD_PWR_NOTREADY_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_UL_MD_PWR_NOTREADY;+para->intr_queues[para->intr_cnt]=+(value>>DP_UL_INT_PWR_NOTRDY_OFFSET)&DP_UL_QNUM_MSK;+para->intr_cnt++;+}++value=l2_txisar0&DP_UL_INT_ERR_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_UL_LEN_ERR;+para->intr_queues[para->intr_cnt]=+(value>>DP_UL_INT_LEN_ERR_OFFSET)&DP_UL_QNUM_MSK;+para->intr_cnt++;+}+}++/* para->intr_cnt counter is set to zero before this function is called.+*itisnotcheckforoverflowasthereisnoriskofoverflowingintr_typesorintr_queues.+*/+staticvoiddpmaif_hw_check_rx_interrupt(structdpmaif_ctrl*dpmaif_ctrl,+unsignedint*pl2_rxisar0,+structdpmaif_hw_intr_st_para*para,+intqno)+{+unsignedintl2_rxisar0=*pl2_rxisar0;+structdpmaif_hw_info*hw_info;+unsignedintvalue;++hw_info=&dpmaif_ctrl->hif_hw_info;++if(qno==DPF_RX_QNO_DFT){+value=l2_rxisar0&DP_DL_INT_SKB_LEN_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_SKB_LEN_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_BATCNT_LEN_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_BATCNT_LEN_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;++dpmaif_mask_dl_batcnt_len_err_interrupt(hw_info);+}++value=l2_rxisar0&DP_DL_INT_PITCNT_LEN_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_PITCNT_LEN_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;++dpmaif_mask_dl_pitcnt_len_err_interrupt(hw_info);+}++value=l2_rxisar0&DP_DL_INT_PKT_EMPTY_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_PKT_EMPTY_SET;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_FRG_EMPTY_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_FRG_EMPTY_SET;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_MTU_ERR_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_MTU_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_FRG_LENERR_MSK;+if(value){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_FRGCNT_LEN_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_DLQ0_PITCNT_LEN_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_DL_INT_DLQ0_PITCNT_LEN_ERR;+para->intr_queues[para->intr_cnt]=BIT(qno);+para->intr_cnt++;++dpmaif_dlq_mask_rx_pitcnt_len_err_intr(hw_info,qno);+}++value=l2_rxisar0&DP_DL_INT_HPC_ENT_TYPE_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_DL_INT_HPC_ENT_TYPE_ERR;+para->intr_queues[para->intr_cnt]=DPF_RX_QNO_DFT;+para->intr_cnt++;+}++value=l2_rxisar0&DP_DL_INT_DLQ0_QDONE_SET;+if(value){+/* mask RX done interrupt immediately after it occurs */+if(!mask_dlq_interrupt(dpmaif_ctrl,qno)){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_DLQ0_DONE;+para->intr_queues[para->intr_cnt]=BIT(qno);+para->intr_cnt++;+}else{+/* Unable to clear the interrupt, try again on the next one+*deviceenteredlowpowermodeorsufferexception+*/+*pl2_rxisar0=l2_rxisar0&~DP_DL_INT_DLQ0_QDONE_SET;+}+}+}else{+value=l2_rxisar0&DP_DL_INT_DLQ1_PITCNT_LEN_ERR;+if(value){+para->intr_types[para->intr_cnt]=DPF_DL_INT_DLQ1_PITCNT_LEN_ERR;+para->intr_queues[para->intr_cnt]=BIT(qno);+para->intr_cnt++;++dpmaif_dlq_mask_rx_pitcnt_len_err_intr(hw_info,qno);+}++value=l2_rxisar0&DP_DL_INT_DLQ1_QDONE_SET;+if(value){+/* mask RX done interrupt immediately after it occurs */+if(!mask_dlq_interrupt(dpmaif_ctrl,qno)){+para->intr_types[para->intr_cnt]=DPF_INTR_DL_DLQ1_DONE;+para->intr_queues[para->intr_cnt]=BIT(qno);+para->intr_cnt++;+}else{+/* Unable to clear the interrupt, try again on the next one+*deviceenteredlowpowermodeorsufferexception+*/+*pl2_rxisar0=l2_rxisar0&~DP_DL_INT_DLQ1_QDONE_SET;+}+}+}+}++/**+*dpmaif_hw_get_interrupt_status()-GetsinterruptstatusfromHW+*@dpmaif_ctrl:Pointertostructdpmaif_ctrl+*@para:Pointertostructdpmaif_hw_intr_st_para+*@qno:Queuenumber+*+*GetsRX/TXinterruptandchecksDL/ULinterruptstatus.+*Clearsinterruptstatusesifneeded.+*+*Return:InterruptCount+*/+intdpmaif_hw_get_interrupt_status(structdpmaif_ctrl*dpmaif_ctrl,+structdpmaif_hw_intr_st_para*para,intqno)+{+structdpmaif_hw_info*hw_info;+u32l2_ri_sar0,l2_ti_sar0=0;+u32l2_rim_r0,l2_tim_r0=0;++hw_info=&dpmaif_ctrl->hif_hw_info;++/* get RX interrupt status from HW */+l2_ri_sar0=ioread32(hw_info->pcie_base+DPMAIF_AP_APDL_L2TISAR0);+l2_rim_r0=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_APDL_L2TIMR0);++/* TX interrupt status */+if(qno==DPF_RX_QNO_DFT){+/* All ULQ and DLQ0 interrupts use the same source+*noneedtocheckULQinterruptswhenaDLQ1+*interrupthasoccurred.+*/+l2_ti_sar0=ioread32(hw_info->pcie_base+DPMAIF_AP_L2TISAR0);+l2_tim_r0=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_AP_L2TIMR0);+}++/* clear IP busy register wake up CPU case */+dpmaif_clr_ip_busy_sts(hw_info);++/* check UL interrupt status */+if(qno==DPF_RX_QNO_DFT){+/* Do not schedule bottom half again or clear UL+*interruptstatuswhenwehavealreadymaskedit.+*/+l2_ti_sar0&=~l2_tim_r0;+if(l2_ti_sar0){+dpmaif_hw_check_tx_interrupt(dpmaif_ctrl,l2_ti_sar0,para);++/* clear interrupt status */+iowrite32(l2_ti_sar0,hw_info->pcie_base+DPMAIF_AP_L2TISAR0);+}+}++/* check DL interrupt status */+if(l2_ri_sar0){+if(qno==DPF_RX_QNO0){+l2_ri_sar0&=DP_DL_DLQ0_STATUS_MASK;++if(l2_rim_r0&DPMAIF_DL_INT_DLQ0_QDONE)+/* Do not schedule bottom half again or clear DL+*queuedoneinterruptstatuswhenwehavealreadymaskedit.+*/+l2_ri_sar0&=~DP_DL_INT_DLQ0_QDONE_SET;+}else{+l2_ri_sar0&=DP_DL_DLQ1_STATUS_MASK;++if(l2_rim_r0&DPMAIF_DL_INT_DLQ1_QDONE)+/* Do not schedule bottom half again or clear DL+*queuedoneinterruptstatuswhenwehavealreadymaskedit.+*/+l2_ri_sar0&=~DP_DL_INT_DLQ1_QDONE_SET;+}++/* have interrupt event */+if(l2_ri_sar0){+dpmaif_hw_check_rx_interrupt(dpmaif_ctrl,&l2_ri_sar0,para,qno);+/* always clear BATCNT_ERR */+l2_ri_sar0|=DP_DL_INT_BATCNT_LEN_ERR;++/* clear interrupt status */+iowrite32(l2_ri_sar0,hw_info->pcie_base+DPMAIF_AP_APDL_L2TISAR0);+}+}++returnpara->intr_cnt;+}++staticintdpmaif_sram_init(structdpmaif_hw_info*hw_info)+{+u32value;++value=ioread32(hw_info->pcie_base+DPMAIF_AP_MEM_CLR);+value|=DPMAIF_MEM_CLR;+iowrite32(value,hw_info->pcie_base+DPMAIF_AP_MEM_CLR);++returnreadx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_AP_MEM_CLR,+value,!(value&DPMAIF_MEM_CLR),+0,DPMAIF_CHECK_INIT_TIMEOUT_US);+}++staticvoiddpmaif_hw_reset(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_AP_AO_RST_BIT,hw_info->pcie_base+DPMAIF_AP_AO_RGU_ASSERT);+udelay(2);+iowrite32(DPMAIF_AP_RST_BIT,hw_info->pcie_base+DPMAIF_AP_RGU_ASSERT);+udelay(2);+iowrite32(DPMAIF_AP_AO_RST_BIT,hw_info->pcie_base+DPMAIF_AP_AO_RGU_DEASSERT);+udelay(2);+iowrite32(DPMAIF_AP_RST_BIT,hw_info->pcie_base+DPMAIF_AP_RGU_DEASSERT);+udelay(2);+}++staticintdpmaif_hw_config(structdpmaif_hw_info*hw_info)+{+unsignedintvalue;+intret;++dpmaif_hw_reset(hw_info);+ret=dpmaif_sram_init(hw_info);+if(ret)+returnret;++/* Set DPMAIF AP port mode */+value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+value|=DPMAIF_PORT_MODE_PCIE;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);++iowrite32(DPMAIF_CG_EN,hw_info->pcie_base+DPMAIF_AP_CG_EN);++return0;+}++staticinlinevoiddpmaif_pcie_dpmaif_sign(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_PCIE_MODE_SET_VALUE,hw_info->pcie_base+DPMAIF_UL_RESERVE_AO_RW);+}++staticvoiddpmaif_dl_performance(structdpmaif_hw_info*hw_info)+{+unsignedintvalue;++/* BAT cache enable */+value=ioread32(hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+value|=DPMAIF_DL_BAT_CACHE_PRI;+iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);++/* PIT burst enable */+value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+value|=DPMAIF_DL_BURST_PIT_EN;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+}++staticvoiddpmaif_common_hw_init(structdpmaif_hw_info*hw_info)+{+dpmaif_pcie_dpmaif_sign(hw_info);+dpmaif_dl_performance(hw_info);+}++/* DPMAIF DL DLQ part HW setting */++staticinlinevoiddpmaif_hw_hpc_cntl_set(structdpmaif_hw_info*hw_info)+{+unsignedintvalue;++value=DPMAIF_HPC_DLQ_PATH_MODE|DPMAIF_HPC_ADD_MODE_DF<<2;+value|=DPMAIF_HASH_PRIME_DF<<4;+value|=DPMAIF_HPC_TOTAL_NUM<<8;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_HPC_CNTL);+}++staticinlinevoiddpmaif_hw_agg_cfg_set(structdpmaif_hw_info*hw_info)+{+unsignedintvalue;++value=DPMAIF_AGG_MAX_LEN_DF|DPMAIF_AGG_TBL_ENT_NUM_DF<<16;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_DLQ_AGG_CFG);+}++staticinlinevoiddpmaif_hw_hash_bit_choose_set(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_DLQ_HASH_BIT_CHOOSE_DF,+hw_info->pcie_base+DPMAIF_AO_DL_DLQPIT_INIT_CON5);+}++staticinlinevoiddpmaif_hw_mid_pit_timeout_thres_set(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_MID_TIMEOUT_THRES_DF,hw_info->pcie_base+DPMAIF_AO_DL_DLQPIT_TIMEOUT0);+}++staticvoiddpmaif_hw_dlq_timeout_thres_set(structdpmaif_hw_info*hw_info)+{+unsignedintvalue,i;++/* each registers holds two DLQ threshold timeout values */+for(i=0;i<DPMAIF_HPC_MAX_TOTAL_NUM/2;i++){+value=FIELD_PREP(DPMAIF_DLQ_LOW_TIMEOUT_THRES_MKS,DPMAIF_DLQ_TIMEOUT_THRES_DF);+value|=FIELD_PREP(DPMAIF_DLQ_HIGH_TIMEOUT_THRES_MSK,+DPMAIF_DLQ_TIMEOUT_THRES_DF);+iowrite32(value,+hw_info->pcie_base+DPMAIF_AO_DL_DLQPIT_TIMEOUT1+sizeof(u32)*i);+}+}++staticinlinevoiddpmaif_hw_dlq_start_prs_thres_set(structdpmaif_hw_info*hw_info)+{+iowrite32(DPMAIF_DLQ_PRS_THRES_DF,hw_info->pcie_base+DPMAIF_AO_DL_DLQPIT_TRIG_THRES);+}++staticvoiddpmaif_dl_dlq_hpc_hw_init(structdpmaif_hw_info*hw_info)+{+dpmaif_hw_hpc_cntl_set(hw_info);+dpmaif_hw_agg_cfg_set(hw_info);+dpmaif_hw_hash_bit_choose_set(hw_info);+dpmaif_hw_mid_pit_timeout_thres_set(hw_info);+dpmaif_hw_dlq_timeout_thres_set(hw_info);+dpmaif_hw_dlq_start_prs_thres_set(hw_info);+}++staticintdpmaif_dl_bat_init_done(structdpmaif_ctrl*dpmaif_ctrl,+unsignedcharq_num,boolfrg_en)+{+structdpmaif_hw_info*hw_info;+u32value,dl_bat_init=0;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;++if(frg_en)+dl_bat_init=DPMAIF_DL_BAT_FRG_INIT;++dl_bat_init|=DPMAIF_DL_BAT_INIT_ALLSET;+dl_bat_init|=DPMAIF_DL_BAT_INIT_EN;++ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_DL_BAT_INIT,+value,!(value&DPMAIF_DL_BAT_INIT_NOT_READY),+0,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(ret){+dev_err(dpmaif_ctrl->dev,"data plane modem DL BAT is not ready\n");+returnret;+}++iowrite32(dl_bat_init,hw_info->pcie_base+DPMAIF_DL_BAT_INIT);++ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_DL_BAT_INIT,+value,!(value&DPMAIF_DL_BAT_INIT_NOT_READY),+0,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(ret)+dev_err(dpmaif_ctrl->dev,"data plane modem DL BAT initialization failed\n");++returnret;+}++staticvoiddpmaif_dl_set_bat_base_addr(structdpmaif_hw_info*hw_info,+unsignedcharq_num,dma_addr_taddr)+{+iowrite32(lower_32_bits(addr),hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON0);+iowrite32(upper_32_bits(addr),hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON3);+}++staticvoiddpmaif_dl_set_bat_size(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintsize)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+value&=~DPMAIF_BAT_SIZE_MSK;+value|=size&DPMAIF_BAT_SIZE_MSK;+iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+}++staticvoiddpmaif_dl_bat_en(structdpmaif_hw_info*hw_info,+unsignedcharq_num,boolenable)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+if(enable)+value|=DPMAIF_BAT_EN_MSK;+else+value&=~DPMAIF_BAT_EN_MSK;++iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+}++staticvoiddpmaif_dl_set_ao_bid_maxcnt(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintcnt)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON0);+value&=~DPMAIF_BAT_BID_MAXCNT_MSK;+value|=FIELD_PREP(DPMAIF_BAT_BID_MAXCNT_MSK,cnt);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON0);+}++staticinlinevoiddpmaif_dl_set_ao_mtu(structdpmaif_hw_info*hw_info,unsignedintmtu_sz)+{+iowrite32(mtu_sz,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON1);+}++staticvoiddpmaif_dl_set_ao_pit_chknum(structdpmaif_hw_info*hw_info,unsignedcharq_num,+unsignedintnumber)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+value&=~DPMAIF_PIT_CHK_NUM_MSK;+value|=FIELD_PREP(DPMAIF_PIT_CHK_NUM_MSK,number);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+}++staticvoiddpmaif_dl_set_ao_remain_minsz(structdpmaif_hw_info*hw_info,unsignedcharq_num,+size_tmin_sz)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON0);+value&=~DPMAIF_BAT_REMAIN_MINSZ_MSK;+value|=FIELD_PREP(DPMAIF_BAT_REMAIN_MINSZ_MSK,min_sz/DPMAIF_BAT_REMAIN_SZ_BASE);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON0);+}++staticvoiddpmaif_dl_set_ao_bat_bufsz(structdpmaif_hw_info*hw_info,+unsignedcharq_num,size_tbuf_sz)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+value&=~DPMAIF_BAT_BUF_SZ_MSK;+value|=FIELD_PREP(DPMAIF_BAT_BUF_SZ_MSK,buf_sz/DPMAIF_BAT_BUFFER_SZ_BASE);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+}++staticvoiddpmaif_dl_set_ao_bat_rsv_length(structdpmaif_hw_info*hw_info,unsignedcharq_num,+unsignedintlength)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+value&=~DPMAIF_BAT_RSV_LEN_MSK;+value|=length&DPMAIF_BAT_RSV_LEN_MSK;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PKTINFO_CON2);+}++staticvoiddpmaif_dl_set_pkt_alignment(structdpmaif_hw_info*hw_info,unsignedcharq_num,+boolenable,unsignedintmode)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+value&=~DPMAIF_PKT_ALIGN_MSK;+if(enable){+value|=DPMAIF_PKT_ALIGN_EN;+value|=FIELD_PREP(DPMAIF_PKT_ALIGN_MSK,mode);+}++iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+}++staticvoiddpmaif_dl_set_pkt_checksum(structdpmaif_hw_info*hw_info)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+value|=DPMAIF_DL_PKT_CHECKSUM_EN;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+}++staticvoiddpmaif_dl_set_ao_frg_check_thres(structdpmaif_hw_info*hw_info,unsignedcharq_num,+unsignedintsize)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+value&=~DPMAIF_FRG_CHECK_THRES_MSK;+value|=(size&DPMAIF_FRG_CHECK_THRES_MSK);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+}++staticvoiddpmaif_dl_set_ao_frg_bufsz(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintbuf_sz)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+value&=~DPMAIF_FRG_BUF_SZ_MSK;+value|=FIELD_PREP(DPMAIF_FRG_BUF_SZ_MSK,buf_sz/DPMAIF_FRG_BUFFER_SZ_BASE);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+}++staticvoiddpmaif_dl_frg_ao_en(structdpmaif_hw_info*hw_info,+unsignedcharq_num,boolenable)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+if(enable)+value|=DPMAIF_FRG_EN_MSK;+else+value&=~DPMAIF_FRG_EN_MSK;++iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_FRG_THRES);+}++staticvoiddpmaif_dl_set_ao_bat_check_thres(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintsize)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+value&=~DPMAIF_BAT_CHECK_THRES_MSK;+value|=FIELD_PREP(DPMAIF_BAT_CHECK_THRES_MSK,size);+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_RDY_CHK_THRES);+}++staticvoiddpmaif_dl_set_pit_seqnum(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintseq)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PIT_SEQ_END);+value&=~DPMAIF_DL_PIT_SEQ_MSK;+value|=seq&DPMAIF_DL_PIT_SEQ_MSK;+iowrite32(value,hw_info->pcie_base+DPMAIF_AO_DL_PIT_SEQ_END);+}++staticvoiddpmaif_dl_set_dlq_pit_base_addr(structdpmaif_hw_info*hw_info,+unsignedcharq_num,dma_addr_taddr)+{+iowrite32(lower_32_bits(addr),hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON0);+iowrite32(upper_32_bits(addr),hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON4);+}++staticvoiddpmaif_dl_set_dlq_pit_size(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintsize)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON1);+value&=~DPMAIF_PIT_SIZE_MSK;+value|=size&DPMAIF_PIT_SIZE_MSK;+iowrite32(value,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON1);++iowrite32(0,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON2);+iowrite32(0,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON3);+iowrite32(0,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON5);+iowrite32(0,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON6);+}++staticvoiddpmaif_dl_dlq_pit_en(structdpmaif_hw_info*hw_info,+unsignedcharq_num,boolenable)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON3);+if(enable)+value|=DPMAIF_DLQPIT_EN_MSK;+else+value&=~DPMAIF_DLQPIT_EN_MSK;++iowrite32(value,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT_CON3);+}++staticvoiddpmaif_dl_dlq_pit_init_done(structdpmaif_ctrl*dpmaif_ctrl,+unsignedcharq_num,unsignedintpit_idx)+{+structdpmaif_hw_info*hw_info;+unsignedintdl_pit_init;+u32value;+inttimeout;++hw_info=&dpmaif_ctrl->hif_hw_info;+dl_pit_init=DPMAIF_DL_PIT_INIT_ALLSET;+dl_pit_init|=(pit_idx<<DPMAIF_DLQPIT_CHAN_OFS);+dl_pit_init|=DPMAIF_DL_PIT_INIT_EN;++timeout=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT,+value,!(value&DPMAIF_DL_PIT_INIT_NOT_READY),+DPMAIF_CHECK_DELAY_US,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(timeout){+dev_err(dpmaif_ctrl->dev,"data plane modem DL PIT is not ready\n");+return;+}++iowrite32(dl_pit_init,hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT);++timeout=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_DL_DLQPIT_INIT,+value,!(value&DPMAIF_DL_PIT_INIT_NOT_READY),+DPMAIF_CHECK_DELAY_US,DPMAIF_CHECK_INIT_TIMEOUT_US);+if(timeout)+dev_err(dpmaif_ctrl->dev,"data plane modem DL PIT initialization failed\n");+}++staticvoiddpmaif_config_dlq_pit_hw(structdpmaif_ctrl*dpmaif_ctrl,unsignedcharq_num,+structdpmaif_dl*dl_que)+{+structdpmaif_hw_info*hw_info;+unsignedintpit_idx=q_num;++hw_info=&dpmaif_ctrl->hif_hw_info;+dpmaif_dl_set_dlq_pit_base_addr(hw_info,q_num,dl_que->pit_base);+dpmaif_dl_set_dlq_pit_size(hw_info,q_num,dl_que->pit_size_cnt);+dpmaif_dl_dlq_pit_en(hw_info,q_num,true);+dpmaif_dl_dlq_pit_init_done(dpmaif_ctrl,q_num,pit_idx);+}++staticvoiddpmaif_config_all_dlq_hw(structdpmaif_ctrl*dpmaif_ctrl)+{+structdpmaif_hw_info*hw_info;+inti;++hw_info=&dpmaif_ctrl->hif_hw_info;++for(i=0;i<DPMAIF_RXQ_NUM;i++)+dpmaif_config_dlq_pit_hw(dpmaif_ctrl,i,&hw_info->dl_que[i]);+}++staticvoiddpmaif_dl_all_queue_en(structdpmaif_ctrl*dpmaif_ctrl,boolenable)+{+structdpmaif_hw_info*hw_info;+u32dl_bat_init,value;+inttimeout;++hw_info=&dpmaif_ctrl->hif_hw_info;+value=ioread32(hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);++if(enable)+value|=DPMAIF_BAT_EN_MSK;+else+value&=~DPMAIF_BAT_EN_MSK;++iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_INIT_CON1);+dl_bat_init=DPMAIF_DL_BAT_INIT_ONLY_ENABLE_BIT;+dl_bat_init|=DPMAIF_DL_BAT_INIT_EN;++/* update DL BAT setting to HW */+timeout=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_DL_BAT_INIT,+value,!(value&DPMAIF_DL_BAT_INIT_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(timeout)+dev_err(dpmaif_ctrl->dev,"timeout updating BAT setting to HW\n");++iowrite32(dl_bat_init,hw_info->pcie_base+DPMAIF_DL_BAT_INIT);++/* wait for HW update */+timeout=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_DL_BAT_INIT,+value,!(value&DPMAIF_DL_BAT_INIT_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(timeout)+dev_err(dpmaif_ctrl->dev,"data plane modem DL BAT is not ready\n");+}++staticintdpmaif_config_dlq_hw(structdpmaif_ctrl*dpmaif_ctrl)+{+structdpmaif_hw_info*hw_info;+structdpmaif_dl_hwq*dl_hw;+structdpmaif_dl*dl_que;+unsignedintqueue=0;/* all queues share one BAT/frag BAT table */+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+dpmaif_dl_dlq_hpc_hw_init(hw_info);++dl_que=&hw_info->dl_que[queue];+dl_hw=&hw_info->dl_que_hw[queue];+if(!dl_que->que_started)+return-EBUSY;++dpmaif_dl_set_ao_remain_minsz(hw_info,queue,dl_hw->bat_remain_size);+dpmaif_dl_set_ao_bat_bufsz(hw_info,queue,dl_hw->bat_pkt_bufsz);+dpmaif_dl_set_ao_frg_bufsz(hw_info,queue,dl_hw->frg_pkt_bufsz);+dpmaif_dl_set_ao_bat_rsv_length(hw_info,queue,dl_hw->bat_rsv_length);+dpmaif_dl_set_ao_bid_maxcnt(hw_info,queue,dl_hw->pkt_bid_max_cnt);++if(dl_hw->pkt_alignment==64)+dpmaif_dl_set_pkt_alignment(hw_info,queue,true,DPMAIF_PKT_ALIGN64_MODE);+elseif(dl_hw->pkt_alignment==128)+dpmaif_dl_set_pkt_alignment(hw_info,queue,true,DPMAIF_PKT_ALIGN128_MODE);+else+dpmaif_dl_set_pkt_alignment(hw_info,queue,false,0);++dpmaif_dl_set_pit_seqnum(hw_info,queue,DPMAIF_DL_PIT_SEQ_VALUE);+dpmaif_dl_set_ao_mtu(hw_info,dl_hw->mtu_size);+dpmaif_dl_set_ao_pit_chknum(hw_info,queue,dl_hw->chk_pit_num);+dpmaif_dl_set_ao_bat_check_thres(hw_info,queue,dl_hw->chk_bat_num);+dpmaif_dl_set_ao_frg_check_thres(hw_info,queue,dl_hw->chk_frg_num);+/* enable frag feature */+dpmaif_dl_frg_ao_en(hw_info,queue,true);+/* init frag use same BAT register */+dpmaif_dl_set_bat_base_addr(hw_info,queue,dl_que->frg_base);+dpmaif_dl_set_bat_size(hw_info,queue,dl_que->frg_size_cnt);+dpmaif_dl_bat_en(hw_info,queue,true);+ret=dpmaif_dl_bat_init_done(dpmaif_ctrl,queue,true);+if(ret)+returnret;++/* normal BAT init */+dpmaif_dl_set_bat_base_addr(hw_info,queue,dl_que->bat_base);+dpmaif_dl_set_bat_size(hw_info,queue,dl_que->bat_size_cnt);+/* enable BAT */+dpmaif_dl_bat_en(hw_info,queue,false);+/* notify HW init/setting done */+ret=dpmaif_dl_bat_init_done(dpmaif_ctrl,queue,false);+if(ret)+returnret;++/* init PIT (two PIT table) */+dpmaif_config_all_dlq_hw(dpmaif_ctrl);+dpmaif_dl_all_queue_en(dpmaif_ctrl,true);++dpmaif_dl_set_pkt_checksum(hw_info);++returnret;+}++staticvoiddpmaif_ul_update_drb_size(structdpmaif_hw_info*hw_info,+unsignedcharq_num,unsignedintsize)+{+unsignedintvalue;++value=ioread32(hw_info->pcie_base+DPMAIF_UL_DRBSIZE_ADDRH_n(q_num));+value&=~DPMAIF_DRB_SIZE_MSK;+value|=size&DPMAIF_DRB_SIZE_MSK;+iowrite32(value,hw_info->pcie_base+DPMAIF_UL_DRBSIZE_ADDRH_n(q_num));+}++staticvoiddpmaif_ul_update_drb_base_addr(structdpmaif_hw_info*hw_info,+unsignedcharq_num,dma_addr_taddr)+{+iowrite32(lower_32_bits(addr),hw_info->pcie_base+DPMAIF_ULQSAR_n(q_num));+iowrite32(upper_32_bits(addr),hw_info->pcie_base+DPMAIF_UL_DRB_ADDRH_n(q_num));+}++staticvoiddpmaif_ul_rdy_en(structdpmaif_hw_info*hw_info,+unsignedcharq_num,boolready)+{+u32value;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);+if(ready)+value|=BIT(q_num);+else+value&=~BIT(q_num);++iowrite32(value,hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);+}++staticvoiddpmaif_ul_arb_en(structdpmaif_hw_info*hw_info,+unsignedcharq_num,boolenable)+{+u32value;++value=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);++if(enable)+value|=BIT(q_num+8);+else+value&=~BIT(q_num+8);++iowrite32(value,hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);+}++staticvoiddpmaif_config_ulq_hw(structdpmaif_hw_info*hw_info)+{+structdpmaif_ul*ul_que;+inti;++for(i=0;i<DPMAIF_TXQ_NUM;i++){+ul_que=&hw_info->ul_que[i];+if(ul_que->que_started){+dpmaif_ul_update_drb_size(hw_info,i,ul_que->drb_size_cnt*+DPMAIF_UL_DRB_ENTRY_WORD);+dpmaif_ul_update_drb_base_addr(hw_info,i,ul_que->drb_base);+dpmaif_ul_rdy_en(hw_info,i,true);+dpmaif_ul_arb_en(hw_info,i,true);+}else{+dpmaif_ul_arb_en(hw_info,i,false);+}+}+}++staticintdpmaif_hw_init_done(structdpmaif_hw_info*hw_info)+{+u32value;+intret;++/* sync default value to SRAM */+value=ioread32(hw_info->pcie_base+DPMAIF_AP_OVERWRITE_CFG);+value|=DPMAIF_SRAM_SYNC;+iowrite32(value,hw_info->pcie_base+DPMAIF_AP_OVERWRITE_CFG);++ret=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_AP_OVERWRITE_CFG,+value,!(value&DPMAIF_SRAM_SYNC),+0,DPMAIF_CHECK_TIMEOUT_US);+if(ret)+returnret;++iowrite32(DPMAIF_UL_INIT_DONE,hw_info->pcie_base+DPMAIF_AO_UL_INIT_SET);+iowrite32(DPMAIF_DL_INIT_DONE,hw_info->pcie_base+DPMAIF_AO_DL_INIT_SET);+return0;+}++staticintdpmaif_config_que_hw(structdpmaif_ctrl*dpmaif_ctrl)+{+structdpmaif_hw_info*hw_info;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+dpmaif_common_hw_init(hw_info);+ret=dpmaif_config_dlq_hw(dpmaif_ctrl);+if(ret)+returnret;++dpmaif_config_ulq_hw(hw_info);+returndpmaif_hw_init_done(hw_info);+}++staticinlinebooldpmaif_dl_idle_check(structdpmaif_hw_info*hw_info)+{+u32value=ioread32(hw_info->pcie_base+DPMAIF_DL_CHK_BUSY);++/* If total queue is idle, then all DL is idle.+*0:idle,1:busy+*/+return!(value&DPMAIF_DL_IDLE_STS);+}++staticvoiddpmaif_ul_all_queue_en(structdpmaif_hw_info*hw_info,boolenable)+{+u32ul_arb_en=ioread32(hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);++if(enable)+ul_arb_en|=DPMAIF_UL_ALL_QUE_ARB_EN;+else+ul_arb_en&=~DPMAIF_UL_ALL_QUE_ARB_EN;++iowrite32(ul_arb_en,hw_info->pcie_base+DPMAIF_AO_UL_CHNL_ARB0);+}++staticinlineintdpmaif_ul_idle_check(structdpmaif_hw_info*hw_info)+{+u32value=ioread32(hw_info->pcie_base+DPMAIF_UL_CHK_BUSY);++/* If total queue is idle, then all UL is idle.+*0:idle,1:busy+*/+return!(value&DPMAIF_UL_IDLE_STS);+}++/* DPMAIF UL Part HW setting */++intdpmaif_ul_add_wcnt(structdpmaif_ctrl*dpmaif_ctrl,+unsignedcharq_num,unsignedintdrb_entry_cnt)+{+structdpmaif_hw_info*hw_info;+u32ul_update,value;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+ul_update=drb_entry_cnt&DPMAIF_UL_ADD_COUNT_MASK;+ul_update|=DPMAIF_UL_ADD_UPDATE;++ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_ULQ_ADD_DESC_CH_n(q_num),+value,!(value&DPMAIF_UL_ADD_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(ret){+dev_err(dpmaif_ctrl->dev,"UL add is not ready\n");+returnret;+}++iowrite32(ul_update,hw_info->pcie_base+DPMAIF_ULQ_ADD_DESC_CH_n(q_num));++ret=readx_poll_timeout_atomic(ioread32,+hw_info->pcie_base+DPMAIF_ULQ_ADD_DESC_CH_n(q_num),+value,!(value&DPMAIF_UL_ADD_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(ret){+dev_err(dpmaif_ctrl->dev,"timeout updating UL add\n");+returnret;+}++return0;+}++unsignedintdpmaif_ul_get_ridx(structdpmaif_hw_info*hw_info,unsignedcharq_num)+{+unsignedintvalue=ioread32(hw_info->pcie_base+DPMAIF_ULQ_STA0_n(q_num));++returnvalue>>DPMAIF_UL_DRB_RIDX_OFFSET;+}++intdpmaif_dl_add_dlq_pit_remain_cnt(structdpmaif_ctrl*dpmaif_ctrl,unsignedintdlq_pit_idx,+unsignedintpit_remain_cnt)+{+structdpmaif_hw_info*hw_info;+u32dl_update,value;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;+dl_update=pit_remain_cnt&DPMAIF_PIT_REM_CNT_MSK;+dl_update|=DPMAIF_DL_ADD_UPDATE|(dlq_pit_idx<<DPMAIF_ADD_DLQ_PIT_CHAN_OFS);++ret=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_DL_DLQPIT_ADD,+value,!(value&DPMAIF_DL_ADD_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(ret){+dev_err(dpmaif_ctrl->dev,"data plane modem is not ready to add dlq\n");+returnret;+}++iowrite32(dl_update,hw_info->pcie_base+DPMAIF_DL_DLQPIT_ADD);++ret=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_DL_DLQPIT_ADD,+value,!(value&DPMAIF_DL_ADD_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(ret){+dev_err(dpmaif_ctrl->dev,"data plane modem add dlq failed\n");+returnret;+}++return0;+}++unsignedintdpmaif_dl_dlq_pit_get_wridx(structdpmaif_hw_info*hw_info,unsignedintdlq_pit_idx)+{+returnioread32(hw_info->pcie_base+DPMAIF_AO_DL_DLQ_WRIDX++dlq_pit_idx*DLQ_PIT_IDX_SIZE)&DPMAIF_DL_PIT_WRIDX_MSK;+}++staticbooldl_add_timedout(structdpmaif_hw_info*hw_info)+{+u32value;+intret;++ret=readx_poll_timeout_atomic(ioread32,hw_info->pcie_base+DPMAIF_DL_BAT_ADD,+value,!(value&DPMAIF_DL_ADD_NOT_READY),+0,DPMAIF_CHECK_TIMEOUT_US);++if(ret)+returntrue;++returnfalse;+}++intdpmaif_dl_add_bat_cnt(structdpmaif_ctrl*dpmaif_ctrl,+unsignedcharq_num,unsignedintbat_entry_cnt)+{+structdpmaif_hw_info*hw_info;+unsignedintvalue;++hw_info=&dpmaif_ctrl->hif_hw_info;+value=bat_entry_cnt&DPMAIF_DL_ADD_COUNT_MASK;+value|=DPMAIF_DL_ADD_UPDATE;++if(dl_add_timedout(hw_info)){+dev_err(dpmaif_ctrl->dev,"DL add BAT not ready. count: %u queue: %dn",+bat_entry_cnt,q_num);+return-EBUSY;+}++iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_ADD);++if(dl_add_timedout(hw_info)){+dev_err(dpmaif_ctrl->dev,"DL add BAT timeout. count: %u queue: %dn",+bat_entry_cnt,q_num);+return-EBUSY;+}++return0;+}++unsignedintdpmaif_dl_get_bat_ridx(structdpmaif_hw_info*hw_info,unsignedcharq_num)+{+returnioread32(hw_info->pcie_base+DPMAIF_AO_DL_BAT_RIDX)&DPMAIF_DL_BAT_WRIDX_MSK;+}++unsignedintdpmaif_dl_get_bat_wridx(structdpmaif_hw_info*hw_info,unsignedcharq_num)+{+returnioread32(hw_info->pcie_base+DPMAIF_AO_DL_BAT_WRIDX)&DPMAIF_DL_BAT_WRIDX_MSK;+}++intdpmaif_dl_add_frg_cnt(structdpmaif_ctrl*dpmaif_ctrl,+unsignedcharq_num,unsignedintfrg_entry_cnt)+{+structdpmaif_hw_info*hw_info;+unsignedintvalue;++hw_info=&dpmaif_ctrl->hif_hw_info;+value=frg_entry_cnt&DPMAIF_DL_ADD_COUNT_MASK;+value|=DPMAIF_DL_FRG_ADD_UPDATE;+value|=DPMAIF_DL_ADD_UPDATE;++if(dl_add_timedout(hw_info)){+dev_err(dpmaif_ctrl->dev,"Data plane modem is not ready to add frag DLQ\n");+return-EBUSY;+}++iowrite32(value,hw_info->pcie_base+DPMAIF_DL_BAT_ADD);++if(dl_add_timedout(hw_info)){+dev_err(dpmaif_ctrl->dev,"Data plane modem add frag DLQ failed");+return-EBUSY;+}++return0;+}++unsignedintdpmaif_dl_get_frg_ridx(structdpmaif_hw_info*hw_info,unsignedcharq_num)+{+returnioread32(hw_info->pcie_base+DPMAIF_AO_DL_FRGBAT_WRIDX)&DPMAIF_DL_FRG_WRIDX_MSK;+}++staticvoiddpmaif_set_queue_property(structdpmaif_hw_info*hw_info,+structdpmaif_hw_params*init_para)+{+structdpmaif_dl_hwq*dl_hwq;+structdpmaif_dl*dl_que;+structdpmaif_ul*ul_que;+inti;++for(i=0;i<DPMAIF_RXQ_NUM;i++){+dl_hwq=&hw_info->dl_que_hw[i];+dl_hwq->bat_remain_size=DPMAIF_HW_BAT_REMAIN;+dl_hwq->bat_pkt_bufsz=DPMAIF_HW_BAT_PKTBUF;+dl_hwq->frg_pkt_bufsz=DPMAIF_HW_FRG_PKTBUF;+dl_hwq->bat_rsv_length=DPMAIF_HW_BAT_RSVLEN;+dl_hwq->pkt_bid_max_cnt=DPMAIF_HW_PKT_BIDCNT;+dl_hwq->pkt_alignment=DPMAIF_HW_PKT_ALIGN;+dl_hwq->mtu_size=DPMAIF_HW_MTU_SIZE;+dl_hwq->chk_bat_num=DPMAIF_HW_CHK_BAT_NUM;+dl_hwq->chk_frg_num=DPMAIF_HW_CHK_FRG_NUM;+dl_hwq->chk_pit_num=DPMAIF_HW_CHK_PIT_NUM;++dl_que=&hw_info->dl_que[i];+dl_que->bat_base=init_para->pkt_bat_base_addr[i];+dl_que->bat_size_cnt=init_para->pkt_bat_size_cnt[i];+dl_que->pit_base=init_para->pit_base_addr[i];+dl_que->pit_size_cnt=init_para->pit_size_cnt[i];+dl_que->frg_base=init_para->frg_bat_base_addr[i];+dl_que->frg_size_cnt=init_para->frg_bat_size_cnt[i];+dl_que->que_started=true;+}++for(i=0;i<DPMAIF_TXQ_NUM;i++){+ul_que=&hw_info->ul_que[i];+ul_que->drb_base=init_para->drb_base_addr[i];+ul_que->drb_size_cnt=init_para->drb_size_cnt[i];+ul_que->que_started=true;+}+}++/**+*dpmaif_hw_stop_tx_queue()-StopallTXqueues+*@dpmaif_ctrl:Pointertostructdpmaif_ctrl+*+*DisableHWULqueue.ChecksbusyULqueuestogotoidle+*withanattemptcountof1000000.+*+*Return:0onsuccessand-ETIMEDOUTuponatimeout+*/+intdpmaif_hw_stop_tx_queue(structdpmaif_ctrl*dpmaif_ctrl)+{+structdpmaif_hw_info*hw_info;+intcount=0;++hw_info=&dpmaif_ctrl->hif_hw_info;++/* disables HW UL arb queue and check for idle */+dpmaif_ul_all_queue_en(hw_info,false);+while(dpmaif_ul_idle_check(hw_info)){+if(++count>=DPMAIF_MAX_CHECK_COUNT){+dev_err(dpmaif_ctrl->dev,"Stop TX failed, 0x%x\n",+ioread32(hw_info->pcie_base+DPMAIF_UL_CHK_BUSY));+return-ETIMEDOUT;+}+}++return0;+}++/**+*dpmaif_hw_stop_rx_queue()-StopallRXqueues+*@dpmaif_ctrl:Pointertostructdpmaif_ctrl+*+*DisableHWDLqueue.ChecksbusyULqueuestogotoidle+*withanattemptcountof1000000.+*CheckthatHWPITwriteindexequalsreadindexwiththesame+*attemptcount.+*+*Return:0onsuccessand-ETIMEDOUTuponatimeout+*/+intdpmaif_hw_stop_rx_queue(structdpmaif_ctrl*dpmaif_ctrl)+{+structdpmaif_hw_info*hw_info;+unsignedintwridx;+unsignedintridx;+intcount=0;++hw_info=&dpmaif_ctrl->hif_hw_info;++/* disables HW DL queue and check idle */+dpmaif_dl_all_queue_en(dpmaif_ctrl,false);+while(dpmaif_dl_idle_check(hw_info)){+if(++count>=DPMAIF_MAX_CHECK_COUNT){+dev_err(dpmaif_ctrl->dev,"stop RX failed, 0x%x\n",+ioread32(hw_info->pcie_base+DPMAIF_DL_CHK_BUSY));+return-ETIMEDOUT;+}+}++/* check middle PIT sync done */+count=0;+do{+wridx=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PIT_WRIDX)&+DPMAIF_DL_PIT_WRIDX_MSK;+ridx=ioread32(hw_info->pcie_base+DPMAIF_AO_DL_PIT_RIDX)&+DPMAIF_DL_PIT_WRIDX_MSK;+if(wridx==ridx)+return0;++if(++count>=DPMAIF_MAX_CHECK_COUNT){+dev_err(dpmaif_ctrl->dev,"check middle PIT sync fail\n");+break;+}+}while(1);++return-ETIMEDOUT;+}++voiddpmaif_start_hw(structdpmaif_ctrl*dpmaif_ctrl)+{+dpmaif_ul_all_queue_en(&dpmaif_ctrl->hif_hw_info,true);+dpmaif_dl_all_queue_en(dpmaif_ctrl,true);+}++/**+*dpmaif_hw_init()-InitializeHWdatapathAPI+*@dpmaif_ctrl:Pointertostructdpmaif_ctrl+*@init_param:Pointertostructdpmaif_hw_params+*+*Configuresportmode,clockconfig,HWinterruptinit,+*andHWqueueconfig.+*+*Return:0onsuccessand-errorcodeuponfailure+*/+intdpmaif_hw_init(structdpmaif_ctrl*dpmaif_ctrl,structdpmaif_hw_params*init_param)+{+structdpmaif_hw_info*hw_info;+intret;++hw_info=&dpmaif_ctrl->hif_hw_info;++/* port mode & clock config */+ret=dpmaif_hw_config(hw_info);++if(ret){+dev_err(dpmaif_ctrl->dev,"sram_init check fail\n");+returnret;+}++/* HW interrupt init */+ret=dpmaif_init_intr(hw_info);+if(ret){+dev_err(dpmaif_ctrl->dev,"init_intr check fail\n");+returnret;+}++/* HW queue config */+dpmaif_set_queue_property(hw_info,init_param);+ret=dpmaif_config_que_hw(dpmaif_ctrl);+if(ret)+dev_err(dpmaif_ctrl->dev,"DPMAIF hw_init_done check fail\n");++returnret;+}++booldpmaif_hw_check_clr_ul_done_status(structdpmaif_hw_info*hw_info,unsignedcharqno)+{+u32value=ioread32(hw_info->pcie_base+DPMAIF_AP_L2TISAR0);++value&=BIT(DP_UL_INT_DONE_OFFSET+qno);+if(value){+/* clear interrupt status */+iowrite32(value,hw_info->pcie_base+DPMAIF_AP_L2TISAR0);+returntrue;+}++returnfalse;+}
@@ -1417,9 +1425,14 @@ static void dpmaif_bat_release_work(struct work_struct *work){structdpmaif_ctrl*dpmaif_ctrl;structdpmaif_rx_queue*rxq;+intret;dpmaif_ctrl=container_of(work,structdpmaif_ctrl,bat_release_work);+ret=pm_runtime_resume_and_get(dpmaif_ctrl->dev);+if(ret<0&&ret!=-EACCES)+return;+/* ALL RXQ use one BAT table, so choose DPF_RX_QNO_DFT */rxq=&dpmaif_ctrl->rxq[DPF_RX_QNO_DFT];
@@ -1427,6 +1440,9 @@ static void dpmaif_bat_release_work(struct work_struct *work)dpmaif_dl_pkt_bat_release_and_add(rxq);/* frag BAT release and add */dpmaif_dl_frag_bat_release_and_add(rxq);++pm_runtime_mark_last_busy(dpmaif_ctrl->dev);+pm_runtime_put_autosuspend(dpmaif_ctrl->dev);}intdpmaif_bat_release_work_alloc(structdpmaif_ctrl*dpmaif_ctrl)
@@ -0,0 +1,424 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/bitfield.h>+#include<linux/cdev.h>+#include<linux/mutex.h>+#include<linux/poll.h>+#include<linux/skbuff.h>+#include<linux/spinlock.h>++#include"t7xx_common.h"+#include"t7xx_monitor.h"+#include"t7xx_port.h"+#include"t7xx_port_proxy.h"+#include"t7xx_skb_util.h"++static__poll_tport_char_poll(structfile*fp,structpoll_table_struct*poll)+{+structt7xx_port*port;+enummd_statemd_state;+__poll_tmask=0;++port=fp->private_data;+md_state=ccci_fsm_get_md_state();+poll_wait(fp,&port->rx_wq,poll);++spin_lock_irq(&port->rx_wq.lock);+if(!skb_queue_empty(&port->rx_skb_list))+mask|=EPOLLIN|EPOLLRDNORM;++spin_unlock_irq(&port->rx_wq.lock);+if(port_write_room_to_md(port)>0)+mask|=EPOLLOUT|EPOLLWRNORM;++if(port->rx_ch==CCCI_UART1_RX&&+md_state!=MD_STATE_READY&&+md_state!=MD_STATE_EXCEPTION){+/* notify MD logger to save its log before md_init kills it */+mask|=EPOLLERR;+dev_err(port->dev,"poll error for MD logger at state: %d, mask: %u\n",+md_state,mask);+}++returnmask;+}++/**+*port_char_open()-opencharport+*@inode:pointertoinodestructure+*@file:pointertofilestructure+*+*Openacharportusingpre-definedmd_ccci_portsstructureinport_proxy+*+*Return:0forsuccess,-EINVALforfailure+*/+staticintport_char_open(structinode*inode,structfile*file)+{+intmajor=imajor(inode);+intminor=iminor(inode);+structt7xx_port*port;++port=port_proxy_get_port(major,minor);+if(!port)+return-EINVAL;++atomic_inc(&port->usage_cnt);+file->private_data=port;+returnnonseekable_open(inode,file);+}++staticintport_char_close(structinode*inode,structfile*file)+{+structt7xx_port*port;+structsk_buff*skb;+intclear_cnt=0;++port=file->private_data;+/* decrease usage count, so when we ask again,+*thepacketcanbedroppedinrecv_request.+*/+atomic_dec(&port->usage_cnt);++/* purge RX request list */+spin_lock_irq(&port->rx_wq.lock);+while((skb=__skb_dequeue(&port->rx_skb_list))!=NULL){+ccci_free_skb(&port->mtk_dev->pools,skb);+clear_cnt++;+}++spin_unlock_irq(&port->rx_wq.lock);+return0;+}++staticssize_tport_char_read(structfile*file,char__user*buf,size_tcount,loff_t*ppos)+{+boolfull_req_done=false;+structt7xx_port*port;+intret=0,read_len;+structsk_buff*skb;++port=file->private_data;+spin_lock_irq(&port->rx_wq.lock);+if(skb_queue_empty(&port->rx_skb_list)){+if(file->f_flags&O_NONBLOCK){+spin_unlock_irq(&port->rx_wq.lock);+return-EAGAIN;+}++ret=wait_event_interruptible_locked_irq(port->rx_wq,+!skb_queue_empty(&port->rx_skb_list));+if(ret==-ERESTARTSYS){+spin_unlock_irq(&port->rx_wq.lock);+return-EINTR;+}+}++skb=skb_peek(&port->rx_skb_list);++if(count>=skb->len){+read_len=skb->len;+full_req_done=true;+__skb_unlink(skb,&port->rx_skb_list);+}else{+read_len=count;+}++spin_unlock_irq(&port->rx_wq.lock);+if(copy_to_user(buf,skb->data,read_len)){+dev_err(port->dev,"read on %s, copy to user failed, %d/%zu\n",+port->name,read_len,count);+ret=-EFAULT;+}++skb_pull(skb,read_len);+if(full_req_done)+ccci_free_skb(&port->mtk_dev->pools,skb);++returnret?ret:read_len;+}++staticssize_tport_char_write(structfile*file,constchar__user*buf,+size_tcount,loff_t*ppos)+{+size_tactual_count,alloc_size,txq_mtu;+inti,multi_packet=1;+structt7xx_port*port;+enummd_statemd_state;+structsk_buff*skb;+boolblocking;+intret;++blocking=!(file->f_flags&O_NONBLOCK);+port=file->private_data;+md_state=ccci_fsm_get_md_state();+if(md_state==MD_STATE_WAITING_FOR_HS1||md_state==MD_STATE_WAITING_FOR_HS2){+dev_warn(port->dev,"port: %s ch: %d, write fail when md_state: %d\n",+port->name,port->tx_ch,md_state);+return-ENODEV;+}++if(port_write_room_to_md(port)<=0&&!blocking)+return-EAGAIN;++txq_mtu=CLDMA_TXQ_MTU;+if(port->flags&PORT_F_RAW_DATA||port->flags&PORT_F_USER_HEADER){+if(port->flags&PORT_F_USER_HEADER&&count>txq_mtu){+dev_err(port->dev,"packet size: %zu larger than MTU on %s\n",+count,port->name);+return-ENOMEM;+}++actual_count=count>txq_mtu?txq_mtu:count;+alloc_size=actual_count;+}else{+actual_count=(count+CCCI_H_ELEN)>txq_mtu?(txq_mtu-CCCI_H_ELEN):count;+alloc_size=actual_count+CCCI_H_ELEN;+if(count+CCCI_H_ELEN>txq_mtu&&(port->tx_ch==CCCI_MBIM_TX||+(port->tx_ch>=CCCI_DSS0_TX&&+port->tx_ch<=CCCI_DSS7_TX))){+multi_packet=(count+txq_mtu-CCCI_H_ELEN-1)/+(txq_mtu-CCCI_H_ELEN);+}+}+mutex_lock(&port->tx_mutex_lock);+for(i=0;i<multi_packet;i++){+structccci_header*ccci_h=NULL;++if(multi_packet>1&&multi_packet==i+1){+actual_count=count%(txq_mtu-CCCI_H_ELEN);+alloc_size=actual_count+CCCI_H_ELEN;+}++skb=ccci_alloc_skb_from_pool(&port->mtk_dev->pools,alloc_size,blocking);+if(!skb){+ret=-ENOMEM;+gotoerr_out;+}++/* Get the user data, no need to validate the data since the driver is just+*passingittothedevice.+*/+if(port->flags&PORT_F_RAW_DATA){+ret=copy_from_user(skb_put(skb,actual_count),buf,actual_count);+if(port->flags&PORT_F_USER_HEADER){+/* The ccci_header is provided by user.+*+*Foronlysendingccci_headerwithoutadditionaldata+*case,data[0]=CCCI_HEADER_NO_DATA,data[1]=user_data,+*ch=tx_channel,reserved=no_use.+*+*Forsendccci_headerwithadditionaldatacase,+*data[0]=0,data[1]=data_size,ch=tx_channel,+*reserved=user_data.+*/+ccci_h=(structccci_header*)skb->data;+if(actual_count==CCCI_H_LEN)+ccci_h->data[0]=CCCI_HEADER_NO_DATA;+else+ccci_h->data[1]=actual_count;++ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,port->tx_ch);+}+}else{+/* ccci_header is provided by driver */+ccci_h=skb_put(skb,CCCI_H_LEN);+ccci_h->data[0]=0;+ccci_h->data[1]=actual_count+CCCI_H_LEN;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,port->tx_ch);+ccci_h->reserved=0;++ret=copy_from_user(skb_put(skb,actual_count),+buf+i*(txq_mtu-CCCI_H_ELEN),actual_count);+}++if(ret){+ret=-EFAULT;+gotoerr_out;+}++/* send out */+port_proxy_set_seq_num(port,ccci_h);+ret=port_send_skb_to_md(port,skb,blocking);+if(ret){+if(ret==-EBUSY&&!blocking)+ret=-EAGAIN;++gotoerr_out;+}else{+/* Record the port seq_num after the data is sent to HIF.+*Onlybits0-14areused,thusnegatingoverflow.+*/+port->seq_nums[MTK_OUT]++;+}++if(multi_packet==1){+mutex_unlock(&port->tx_mutex_lock);+returnactual_count;+}elseif(multi_packet==i+1){+mutex_unlock(&port->tx_mutex_lock);+returncount;+}+}++err_out:+mutex_unlock(&port->tx_mutex_lock);+dev_err(port->dev,"write error done on %s, size: %zu, ret: %d\n",+port->name,actual_count,ret);+ccci_free_skb(&port->mtk_dev->pools,skb);+returnret;+}++staticconststructfile_operationschar_fops={+.owner=THIS_MODULE,+.open=&port_char_open,+.read=&port_char_read,+.write=&port_char_write,+.release=&port_char_close,+.poll=&port_char_poll,+};++staticintport_char_init(structt7xx_port*port)+{+structcdev*dev;++port->rx_length_th=MAX_RX_QUEUE_LENGTH;+port->skb_from_pool=true;+if(port->flags&PORT_F_RX_CHAR_NODE){+dev=cdev_alloc();+if(!dev)+return-ENOMEM;++dev->ops=&char_fops;+dev->owner=THIS_MODULE;+if(cdev_add(dev,MKDEV(port->major,port->minor_base+port->minor),1)){+kobject_put(&dev->kobj);+return-ENOMEM;+}++if(!(port->flags&PORT_F_RAW_DATA))+port->flags|=PORT_F_RX_ADJUST_HEADER;++port->cdev=dev;+}++if(port->rx_ch==CCCI_UART2_RX)+port->flags|=PORT_F_RX_CH_TRAFFIC;++return0;+}++staticvoidport_char_uninit(structt7xx_port*port)+{+unsignedlongflags;+structsk_buff*skb;++if(port->flags&PORT_F_RX_CHAR_NODE&&port->cdev){+if(port->chn_crt_stat==CCCI_CHAN_ENABLE){+port_unregister_device(port->major,port->minor_base+port->minor);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+}++cdev_del(port->cdev);+port->cdev=NULL;+}++/* interrupts need to be disabled */+spin_lock_irqsave(&port->rx_wq.lock,flags);+while((skb=__skb_dequeue(&port->rx_skb_list))!=NULL)+ccci_free_skb(&port->mtk_dev->pools,skb);+spin_unlock_irqrestore(&port->rx_wq.lock,flags);+}++staticintport_char_recv_skb(structt7xx_port*port,structsk_buff*skb)+{+if((port->flags&PORT_F_RX_CHAR_NODE)&&!atomic_read(&port->usage_cnt)){+dev_err_ratelimited(port->dev,+"port %s is not opened, dropping packets\n",port->name);+return-ENETDOWN;+}++returnport_recv_skb(port,skb);+}++staticintport_status_update(structt7xx_port*port)+{+if(!(port->flags&PORT_F_RX_CHAR_NODE))+return0;++if(port->chan_enable==CCCI_CHAN_ENABLE){+intret;++port->flags&=~PORT_F_RX_ALLOW_DROP;+ret=port_register_device(port->name,port->major,+port->minor_base+port->minor);+if(ret)+returnret;++port_proxy_broadcast_state(port,MTK_PORT_STATE_ENABLE);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);++return0;+}++port->flags|=PORT_F_RX_ALLOW_DROP;+port_unregister_device(port->major,port->minor_base+port->minor);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+returnport_proxy_broadcast_state(port,MTK_PORT_STATE_DISABLE);+}++staticintport_char_enable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);+if(port->chn_crt_stat!=port->chan_enable)+returnport_status_update(port);++return0;+}++staticintport_char_disable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+if(port->chn_crt_stat!=port->chan_enable)+returnport_status_update(port);++return0;+}++staticvoidport_char_md_state_notify(structt7xx_port*port,unsignedintstate)+{+if(state==MD_STATE_READY)+port_status_update(port);+}++structport_opschar_port_ops={+.init=&port_char_init,+.recv_skb=&port_char_recv_skb,+.uninit=&port_char_uninit,+.enable_chl=&port_char_enable_chl,+.disable_chl=&port_char_disable_chl,+.md_state_notify=&port_char_md_state_notify,+};
@@ -0,0 +1,191 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/atomic.h>+#include<linux/bitfield.h>+#include<linux/skbuff.h>+#include<linux/spinlock.h>+#include<linux/types.h>++#include"t7xx_common.h"+#include"t7xx_port_proxy.h"+#include"t7xx_skb_util.h"+#include"t7xx_tty_ops.h"++#define TTY_PORT_NAME_BASE "ttyC"+#define TTY_PORT_NR 32++staticintccci_tty_send_pkt(inttty_port_idx,constvoid*data,intlen)+{+structccci_header*ccci_h=NULL;+intactual_count,alloc_size;+intret,header_len=0;+structt7xx_port*port;+structsk_buff*skb;++port=port_get_by_minor(tty_port_idx+TTY_PORT_MINOR_BASE);+if(!port)+return-ENXIO;++if(port->flags&PORT_F_RAW_DATA){+actual_count=len>CLDMA_TXQ_MTU?CLDMA_TXQ_MTU:len;+alloc_size=actual_count;+}else{+/* get skb info */+header_len=sizeof(structccci_header);+actual_count=len>CCCI_MTU?CCCI_MTU:len;+alloc_size=actual_count+header_len;+}++skb=ccci_alloc_skb_from_pool(&port->mtk_dev->pools,alloc_size,GFS_BLOCKING);+if(skb){+if(!(port->flags&PORT_F_RAW_DATA)){+ccci_h=skb_put(skb,sizeof(structccci_header));+ccci_h->data[0]=0;+ccci_h->data[1]=actual_count+header_len;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,port->tx_ch);+ccci_h->reserved=0;+}+}else{+return-ENOMEM;+}++memcpy(skb_put(skb,actual_count),data,actual_count);++/* send data */+port_proxy_set_seq_num(port,ccci_h);+ret=port_send_skb_to_md(port,skb,true);+if(ret){+dev_err(port->dev,"failed to send skb to md, ret = %d\n",ret);+returnret;+}++/* Record the port seq_num after the data is sent to HIF.+*Onlybits0-14areused,thusnegatingoverflow.+*/+port->seq_nums[MTK_OUT]++;++returnactual_count;+}++staticstructtty_ccci_opsmtk_tty_ops={+.tty_num=TTY_PORT_NR,+.name=TTY_PORT_NAME_BASE,+.md_ability=0,+.send_pkt=ccci_tty_send_pkt,+};++staticintport_tty_init(structt7xx_port*port)+{+/* mapping the minor number to tty dev idx */+port->minor+=TTY_PORT_MINOR_BASE;++/* init the tty driver */+if(!tty_ops.tty_driver_status){+tty_ops.tty_driver_status=true;+atomic_set(&tty_ops.port_installed_num,0);+tty_ops.init(&mtk_tty_ops,port);+}++return0;+}++staticintport_tty_recv_skb(structt7xx_port*port,structsk_buff*skb)+{+intactual_recv_len;++/* get skb data */+if(!(port->flags&PORT_F_RAW_DATA))+skb_pull(skb,sizeof(structccci_header));++/* send data to tty driver. */+actual_recv_len=tty_ops.rx_callback(port,skb->data,skb->len);++if(actual_recv_len!=skb->len){+dev_err(port->dev,"ccci port[%s] recv skb fail\n",port->name);+skb_push(skb,sizeof(structccci_header));+return-ENOBUFS;+}++ccci_free_skb(&port->mtk_dev->pools,skb);+return0;+}++staticvoidport_tty_md_state_notify(structt7xx_port*port,unsignedintstate)+{+if(state!=MD_STATE_READY||port->chan_enable!=CCCI_CHAN_ENABLE)+return;++port->flags&=~PORT_F_RX_ALLOW_DROP;+/* create a tty port */+tty_ops.tty_port_create(port,port->name);+atomic_inc(&tty_ops.port_installed_num);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);+}++staticvoidport_tty_uninit(structt7xx_port*port)+{+port->minor-=TTY_PORT_MINOR_BASE;++if(port->chn_crt_stat!=CCCI_CHAN_ENABLE)+return;++/* destroy tty port */+tty_ops.tty_port_destroy(port);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);++/* CCCI tty driver exit */+if(atomic_dec_and_test(&tty_ops.port_installed_num)&&tty_ops.exit){+tty_ops.exit();+tty_ops.tty_driver_status=false;+}+}++staticintport_tty_enable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);+if(port->chn_crt_stat!=port->chan_enable){+port->flags&=~PORT_F_RX_ALLOW_DROP;+/* create a tty port */+tty_ops.tty_port_create(port,port->name);+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);+atomic_inc(&tty_ops.port_installed_num);+}++return0;+}++staticintport_tty_disable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+return0;+}++structport_opstty_port_ops={+.init=&port_tty_init,+.recv_skb=&port_tty_recv_skb,+.md_state_notify=&port_tty_md_state_notify,+.uninit=&port_tty_uninit,+.enable_chl=&port_tty_enable_chl,+.disable_chl=&port_tty_disable_chl,+};
@@ -0,0 +1,205 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/tty.h>+#include<linux/tty_driver.h>+#include<linux/tty_flip.h>++#include"t7xx_port_proxy.h"+#include"t7xx_tty_ops.h"++#define GET_TTY_IDX(p) ((p)->minor - TTY_PORT_MINOR_BASE)++staticstructtty_ctl_block*tty_ctlb;+staticconststructtty_port_operationsnull_ops={};++staticintccci_tty_open(structtty_struct*tty,structfile*filp)+{+structtty_port*pport;+intret=0;++pport=tty->driver->ports[tty->index];+if(pport)+ret=tty_port_open(pport,tty,filp);++returnret;+}++staticvoidccci_tty_close(structtty_struct*tty,structfile*filp)+{+structtty_port*pport;++pport=tty->driver->ports[tty->index];+if(pport)+tty_port_close(pport,tty,filp);+}++staticintccci_tty_write(structtty_struct*tty,constunsignedchar*buf,intcount)+{+if(!(tty_ctlb&&tty_ctlb->driver==tty->driver))+return-EFAULT;++returntty_ctlb->ccci_ops->send_pkt(tty->index,buf,count);+}++staticunsignedintccci_tty_write_room(structtty_struct*tty)+{+returnCCCI_MTU;+}++staticconststructtty_operationsccci_serial_ops={+.open=ccci_tty_open,+.close=ccci_tty_close,+.write=ccci_tty_write,+.write_room=ccci_tty_write_room,+};++staticintccci_tty_port_create(structt7xx_port*port,char*port_name)+{+structtty_driver*tty_drv;+structtty_port*pport;+intminor=GET_TTY_IDX(port);++tty_drv=tty_ctlb->driver;+tty_drv->name=port_name;++pport=devm_kzalloc(port->dev,sizeof(*pport),GFP_KERNEL);+if(!pport)+return-ENOMEM;++tty_port_init(pport);+pport->ops=&null_ops;+tty_port_link_device(pport,tty_drv,minor);+tty_register_device(tty_drv,minor,NULL);+return0;+}++staticintccci_tty_port_destroy(structt7xx_port*port)+{+structtty_driver*tty_drv;+structtty_port*pport;+structtty_struct*tty;+intminor=port->minor;++tty_drv=tty_ctlb->driver;++pport=tty_drv->ports[minor];+if(!pport){+dev_err(port->dev,"Invalid tty minor:%d\n",minor);+return-EINVAL;+}++tty=tty_port_tty_get(pport);+if(tty){+tty_vhangup(tty);+tty_kref_put(tty);+}++tty_unregister_device(tty_drv,minor);+tty_port_destroy(pport);+tty_drv->ports[minor]=NULL;+return0;+}++staticinttty_ccci_init(structtty_ccci_ops*ccci_info,structt7xx_port*port)+{+structport_proxy*port_proxy_ptr;+structtty_driver*tty_drv;+structtty_ctl_block*ctlb;+intret,port_nr;++ctlb=devm_kzalloc(port->dev,sizeof(*ctlb),GFP_KERNEL);+if(!ctlb)+return-ENOMEM;++ctlb->ccci_ops=devm_kzalloc(port->dev,sizeof(*ctlb->ccci_ops),GFP_KERNEL);+if(!ctlb->ccci_ops)+return-ENOMEM;++tty_ctlb=ctlb;+memcpy(ctlb->ccci_ops,ccci_info,sizeof(structtty_ccci_ops));+port_nr=ctlb->ccci_ops->tty_num;++tty_drv=tty_alloc_driver(port_nr,0);+if(IS_ERR(tty_drv))+return-ENOMEM;++/* init tty driver */+port_proxy_ptr=port->port_proxy;+ctlb->driver=tty_drv;+tty_drv->driver_name=ctlb->ccci_ops->name;+tty_drv->name=ctlb->ccci_ops->name;+tty_drv->major=port_proxy_ptr->major;+tty_drv->minor_start=TTY_PORT_MINOR_BASE;+tty_drv->type=TTY_DRIVER_TYPE_SERIAL;+tty_drv->subtype=SERIAL_TYPE_NORMAL;+tty_drv->flags=TTY_DRIVER_RESET_TERMIOS|TTY_DRIVER_REAL_RAW|+TTY_DRIVER_DYNAMIC_DEV|TTY_DRIVER_UNNUMBERED_NODE;+tty_drv->init_termios=tty_std_termios;+tty_drv->init_termios.c_iflag=0;+tty_drv->init_termios.c_oflag=0;+tty_drv->init_termios.c_cflag|=CLOCAL;+tty_drv->init_termios.c_lflag=0;+tty_set_operations(tty_drv,&ccci_serial_ops);++ret=tty_register_driver(tty_drv);+if(ret<0){+dev_err(port->dev,"Could not register tty driver\n");+tty_driver_kref_put(tty_drv);+}++returnret;+}++staticvoidtty_ccci_uninit(void)+{+structtty_driver*tty_drv;+structtty_ctl_block*ctlb;++ctlb=tty_ctlb;+if(ctlb){+tty_drv=ctlb->driver;+tty_unregister_driver(tty_drv);+tty_driver_kref_put(tty_drv);+tty_ctlb=NULL;+}+}++staticinttty_rx_callback(structt7xx_port*port,void*data,intlen)+{+structtty_port*pport;+structtty_driver*drv;+inttty_id=GET_TTY_IDX(port);+intcopied=0;++drv=tty_ctlb->driver;+pport=drv->ports[tty_id];++if(!pport){+dev_err(port->dev,"tty port isn't created, the packet is dropped\n");+returnlen;+}++/* push data to tty port buffer */+copied=tty_insert_flip_string(pport,data,len);++/* trigger port buffer -> line discipline buffer */+tty_flip_buffer_push(pport);+returncopied;+}++structtty_dev_opstty_ops={+.init=&tty_ccci_init,+.tty_port_create=&ccci_tty_port_create,+.tty_port_destroy=&ccci_tty_port_destroy,+.rx_callback=&tty_rx_callback,+.exit=&tty_ccci_uninit,+};
@@ -0,0 +1,120 @@+.. SPDX-License-Identifier: GPL-2.0-only++.. Copyright (C) 2020-21 Intel Corporation++.._t7xx_driver_doc:++============================================+t7xx driver for MTK PCIe based T700 5G modem+============================================+The t7xx driver is a WWAN PCIe host driver developed for linux or Chrome OS platforms+for data exchange over PCIe interface between Host platform & MediaTek's T700 5G modem.+The driver exposes an interface conforming to the MBIM protocol [1]. Any front end+application (e.g. Modem Manager) could easily manage the MBIM interface to enable+data communication towards WWAN. The driver also provides an interface to interact+with the MediaTek's modem via AT commands.++Basic usage+===========+MBIM & AT functions are inactive when unmanaged. The t7xx driver provides+WWAN port userspace interfaces representing MBIM & AT control channels and does+not play any role in managing their functionality. It is the job of a userspace+application to detect port enumeration and enable MBIM & AT functionalities.++Examples of few such userspace applications are:++- mbimcli (included with the libmbim [2] library), and+- Modem Manager [3]++Management Applications to carry out below required actions for establishing+MBIM IP session:++- open the MBIM control channel+- configure network connection settings+- connect to network+- configure IP network interface++Management Applications to carry out below required actions for send an AT+command and receive response:++- open the AT control channel using a UART tool or a special user tool++Management application development+==================================+The driver and userspace interfaces are described below. The MBIM protocol is+described in [1] Mobile Broadband Interface Model v1.0 Errata-1.++MBIM control channel userspace ABI+----------------------------------++/dev/wwan0mbim0 character device+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+The driver exposes an MBIM interface to the MBIM function by implementing+MBIM WWAN Port. The userspace end of the control channel pipe is a+/dev/wwan0mbim0 character device. Application shall use this interface for+MBIM protocol communication.++Fragmentation+~~~~~~~~~~~~~+The userspace application is responsible for all control message fragmentation+and defragmentation as per MBIM specification.++/dev/wwan0mbim0 write()+~~~~~~~~~~~~~~~~~~~~~~~+The MBIM control messages from the management application must not exceed the+negotiated control message size.++/dev/wwan0mbim0 read()+~~~~~~~~~~~~~~~~~~~~~~+The management application must accept control messages of up the negotiated+control message size.++MBIM data channel userspace ABI+-------------------------------++wwan0-X network device+~~~~~~~~~~~~~~~~~~~~~~+The t7xx driver exposes IP link interface "wwan0-X" of type "wwan" for IP+traffic. Iproute network utility is used for creating "wwan0-X" network+interface and for associating it with MBIM IP session.++The userspace management application is responsible for creating new IP link+prior to establishing MBIM IP session where the SessionId is greater than 0.++For example, creating new IP link for a MBIM IP session with SessionId 1:++ ip link add dev wwan0-1 parentdev wwan0 type wwan linkid 1++The driver will automatically map the "wwan0-1" network device to MBIM IP+session 1.++AT port userspace ABI+----------------------------------++/dev/wwan0at0 character device+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+The driver exposes an AT port by implementing AT WWAN Port.+The userspace end of the control port is a /dev/wwan0at0 character+device. Application shall use this interface to issue AT commands.++The MediaTek's T700 modem supports the 3GPP TS 27.007 [4] specification.++References+==========+[1] *MBIM (Mobile Broadband Interface Model) Errata-1*++- https://www.usb.org/document-library/++[2] *libmbim "a glib-based library for talking to WWAN modems and devices which+speak the Mobile Interface Broadband Model (MBIM) protocol"*++- http://www.freedesktop.org/wiki/Software/libmbim/++[3] *Modem Manager "a DBus-activated daemon which controls mobile broadband+(2G/3G/4G/5G) devices and connections"*++- http://www.freedesktop.org/wiki/Software/ModemManager/++[4] *Specification # 27.007 - 3GPP*++- https://www.3gpp.org/DynaReport/27007.htm
From: Haijun Lio <haijun.liu@mediatek.com>
Implements suspend, resumes, freeze, thaw, poweroff, and restore
`dev_pm_ops` callbacks.
From the host point of view, the t7xx driver is one entity. But, the
device has several modules that need to be addressed in different ways
during power management (PM) flows.
The driver uses the term 'PM entities' to refer to the 2 DPMA and
2 CLDMA HW blocks that need to be managed during PM flows.
When a dev_pm_ops function is called, the PM entities list is iterated
and the matching function is called for each entry in the list.
Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
drivers/net/wwan/t7xx/t7xx_hif_cldma.c | 122 +++++-
drivers/net/wwan/t7xx/t7xx_hif_cldma.h | 1 +
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.c | 98 +++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.h | 1 +
drivers/net/wwan/t7xx/t7xx_mhccif.c | 16 +
drivers/net/wwan/t7xx/t7xx_pci.c | 435 +++++++++++++++++++++
drivers/net/wwan/t7xx/t7xx_pci.h | 45 +++
drivers/net/wwan/t7xx/t7xx_state_monitor.c | 2 +
8 files changed, 719 insertions(+), 1 deletion(-)
@@ -28,6 +33,430 @@#define PCI_IREG_BASE 0#define PCI_EREG_BASE 2+#define PM_ACK_TIMEOUT_MS 1500+#define PM_RESOURCE_POLL_TIMEOUT_US 10000+#define PM_RESOURCE_POLL_STEP_US 100++enummtk_pm_state{+MTK_PM_EXCEPTION,/* Exception flow */+MTK_PM_INIT,/* Device initialized, but handshake not completed */+MTK_PM_SUSPENDED,/* Device in suspend state */+MTK_PM_RESUMED,/* Device in resume state */+};++staticintmtk_wait_pm_config(structmtk_pci_dev*mtk_dev)+{+intret,val;++ret=read_poll_timeout(ioread32,val,+(val&PCIE_RESOURCE_STATUS_MSK)==PCIE_RESOURCE_STATUS_MSK,+PM_RESOURCE_POLL_STEP_US,PM_RESOURCE_POLL_TIMEOUT_US,true,+IREG_BASE(mtk_dev)+PCIE_RESOURCE_STATUS);+if(ret==-ETIMEDOUT)+dev_err(&mtk_dev->pdev->dev,"PM configuration timed out\n");++returnret;+}++staticintmtk_pci_pm_init(structmtk_pci_dev*mtk_dev)+{+structpci_dev*pdev;++pdev=mtk_dev->pdev;++INIT_LIST_HEAD(&mtk_dev->md_pm_entities);++mutex_init(&mtk_dev->md_pm_entity_mtx);++init_completion(&mtk_dev->pm_sr_ack);++device_init_wakeup(&pdev->dev,true);++dev_pm_set_driver_flags(&pdev->dev,pdev->dev.power.driver_flags|+DPM_FLAG_NO_DIRECT_COMPLETE);++atomic_set(&mtk_dev->md_pm_state,MTK_PM_INIT);++iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_SET_0);++returnmtk_wait_pm_config(mtk_dev);+}++voidmtk_pci_pm_init_late(structmtk_pci_dev*mtk_dev)+{+/* enable the PCIe Resource Lock only after MD deep sleep is done */+mhccif_mask_clr(mtk_dev,+D2H_INT_SUSPEND_ACK|+D2H_INT_RESUME_ACK|+D2H_INT_SUSPEND_ACK_AP|+D2H_INT_RESUME_ACK_AP);+iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_CLR_0);+atomic_set(&mtk_dev->md_pm_state,MTK_PM_RESUMED);+}++staticintmtk_pci_pm_reinit(structmtk_pci_dev*mtk_dev)+{+/* The device is kept in FSM re-init flow+*sojustrollbackPMsettingtotheinitsetting.+*/+atomic_set(&mtk_dev->md_pm_state,MTK_PM_INIT);++iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_SET_0);+returnmtk_wait_pm_config(mtk_dev);+}++voidmtk_pci_pm_exp_detected(structmtk_pci_dev*mtk_dev)+{+iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_SET_0);+mtk_wait_pm_config(mtk_dev);+atomic_set(&mtk_dev->md_pm_state,MTK_PM_EXCEPTION);+}++intmtk_pci_pm_entity_register(structmtk_pci_dev*mtk_dev,structmd_pm_entity*pm_entity)+{+structmd_pm_entity*entity;++mutex_lock(&mtk_dev->md_pm_entity_mtx);+list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(entity->id==pm_entity->id){+mutex_unlock(&mtk_dev->md_pm_entity_mtx);+return-EEXIST;+}+}++list_add_tail(&pm_entity->entity,&mtk_dev->md_pm_entities);+mutex_unlock(&mtk_dev->md_pm_entity_mtx);+return0;+}++intmtk_pci_pm_entity_unregister(structmtk_pci_dev*mtk_dev,structmd_pm_entity*pm_entity)+{+structmd_pm_entity*entity,*tmp_entity;++mutex_lock(&mtk_dev->md_pm_entity_mtx);++list_for_each_entry_safe(entity,tmp_entity,&mtk_dev->md_pm_entities,entity){+if(entity->id==pm_entity->id){+list_del(&pm_entity->entity);+mutex_unlock(&mtk_dev->md_pm_entity_mtx);+return0;+}+}++mutex_unlock(&mtk_dev->md_pm_entity_mtx);++return-ENXIO;+}++staticint__mtk_pci_pm_suspend(structpci_dev*pdev)+{+structmtk_pci_dev*mtk_dev;+structmd_pm_entity*entity;+unsignedlongwait_ret;+enummtk_pm_idid;+intret=0;++mtk_dev=pci_get_drvdata(pdev);++if(atomic_read(&mtk_dev->md_pm_state)<=MTK_PM_INIT){+dev_err(&pdev->dev,+"[PM] Exiting suspend, because handshake failure or in an exception\n");+return-EFAULT;+}++iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_SET_0);+ret=mtk_wait_pm_config(mtk_dev);+if(ret)+returnret;++atomic_set(&mtk_dev->md_pm_state,MTK_PM_SUSPENDED);++mtk_pcie_mac_clear_int(mtk_dev,SAP_RGU_INT);+mtk_dev->rgu_pci_irq_en=false;++list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(entity->suspend){+ret=entity->suspend(mtk_dev,entity->entity_param);+if(ret){+id=entity->id;+break;+}+}+}++if(ret){+dev_err(&pdev->dev,"[PM] Suspend error: %d, id: %d\n",ret,id);++list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(id==entity->id)+break;++if(entity->resume)+entity->resume(mtk_dev,entity->entity_param);+}++gotosuspend_complete;+}++reinit_completion(&mtk_dev->pm_sr_ack);+/* send D3 enter request to MD */+mhccif_h2d_swint_trigger(mtk_dev,H2D_CH_SUSPEND_REQ);+wait_ret=wait_for_completion_timeout(&mtk_dev->pm_sr_ack,+msecs_to_jiffies(PM_ACK_TIMEOUT_MS));+if(!wait_ret)+dev_err(&pdev->dev,"[PM] Wait for device suspend ACK timeout-MD\n");++reinit_completion(&mtk_dev->pm_sr_ack);+/* send D3 enter request to sAP */+mhccif_h2d_swint_trigger(mtk_dev,H2D_CH_SUSPEND_REQ_AP);+wait_ret=wait_for_completion_timeout(&mtk_dev->pm_sr_ack,+msecs_to_jiffies(PM_ACK_TIMEOUT_MS));+if(!wait_ret)+dev_err(&pdev->dev,"[PM] Wait for device suspend ACK timeout-SAP\n");++/* Each HW's final work */+list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(entity->suspend_late)+entity->suspend_late(mtk_dev,entity->entity_param);+}++suspend_complete:+iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_CLR_0);+if(ret){+atomic_set(&mtk_dev->md_pm_state,MTK_PM_RESUMED);+mtk_pcie_mac_set_int(mtk_dev,SAP_RGU_INT);+}++returnret;+}++staticvoidmtk_pcie_interrupt_reinit(structmtk_pci_dev*mtk_dev)+{+mtk_pcie_mac_msix_cfg(mtk_dev,EXT_INT_NUM);++/* Disable interrupt first and let the IPs enable them */+iowrite32(MSIX_MSK_SET_ALL,IREG_BASE(mtk_dev)+IMASK_HOST_MSIX_CLR_GRP0_0);++/* Device disables PCIe interrupts during resume and+*followingfunctionwillre-enablePCIeinterrupts.+*/+mtk_pcie_mac_interrupts_en(mtk_dev);+mtk_pcie_mac_set_int(mtk_dev,MHCCIF_INT);+}++staticintmtk_pcie_reinit(structmtk_pci_dev*mtk_dev,boolis_d3)+{+intret;++ret=pcim_enable_device(mtk_dev->pdev);+if(ret)+returnret;++mtk_pcie_mac_atr_init(mtk_dev);+mtk_pcie_interrupt_reinit(mtk_dev);++if(is_d3){+mhccif_init(mtk_dev);+returnmtk_pci_pm_reinit(mtk_dev);+}++return0;+}++staticintmtk_send_fsm_command(structmtk_pci_dev*mtk_dev,u32event)+{+structccci_fsm_ctl*fsm_ctl;+intret=-EINVAL;++fsm_ctl=fsm_get_entry();++switch(event){+caseCCCI_COMMAND_STOP:+ret=fsm_append_command(fsm_ctl,CCCI_COMMAND_STOP,1);+break;++caseCCCI_COMMAND_START:+mtk_pcie_mac_clear_int(mtk_dev,SAP_RGU_INT);+mtk_pcie_mac_clear_int_status(mtk_dev,SAP_RGU_INT);+mtk_dev->rgu_pci_irq_en=true;+mtk_pcie_mac_set_int(mtk_dev,SAP_RGU_INT);+ret=fsm_append_command(fsm_ctl,CCCI_COMMAND_START,0);+break;++default:+break;+}+if(ret)+dev_err(&mtk_dev->pdev->dev,"handling FSM CMD event: %u error: %d\n",event,ret);++returnret;+}++staticint__mtk_pci_pm_resume(structpci_dev*pdev,boolstate_check)+{+structmtk_pci_dev*mtk_dev;+structmd_pm_entity*entity;+unsignedlongwait_ret;+u32resume_reg_state;+intret=0;++mtk_dev=pci_get_drvdata(pdev);++if(atomic_read(&mtk_dev->md_pm_state)<=MTK_PM_INIT){+iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_CLR_0);+return0;+}++/* Get the previous state */+resume_reg_state=ioread32(IREG_BASE(mtk_dev)+PCIE_PM_RESUME_STATE);++if(state_check){+/* For D3/L3 resume, the device could boot so quickly that the+*initialvalueofthedummyregistermightbeoverwritten.+*IdentifynewbootsiftheATRsourceaddressregisterisnotinitialized.+*/+u32atr_reg_val=ioread32(IREG_BASE(mtk_dev)++ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR);++if(resume_reg_state==PM_RESUME_REG_STATE_L3||+(resume_reg_state==PM_RESUME_REG_STATE_INIT&&+atr_reg_val==ATR_SRC_ADDR_INVALID)){+ret=mtk_send_fsm_command(mtk_dev,CCCI_COMMAND_STOP);+if(ret)+returnret;++ret=mtk_pcie_reinit(mtk_dev,true);+if(ret)+returnret;++mtk_clear_rgu_irq(mtk_dev);+returnmtk_send_fsm_command(mtk_dev,CCCI_COMMAND_START);+}elseif(resume_reg_state==PM_RESUME_REG_STATE_EXP||+resume_reg_state==PM_RESUME_REG_STATE_L2_EXP){+if(resume_reg_state==PM_RESUME_REG_STATE_L2_EXP){+ret=mtk_pcie_reinit(mtk_dev,false);+if(ret)+returnret;+}++atomic_set(&mtk_dev->md_pm_state,MTK_PM_SUSPENDED);+mtk_dev->rgu_pci_irq_en=true;+mtk_pcie_mac_set_int(mtk_dev,SAP_RGU_INT);++mhccif_mask_clr(mtk_dev,+D2H_INT_EXCEPTION_INIT|+D2H_INT_EXCEPTION_INIT_DONE|+D2H_INT_EXCEPTION_CLEARQ_DONE|+D2H_INT_EXCEPTION_ALLQ_RESET|+D2H_INT_PORT_ENUM);++returnret;+}elseif(resume_reg_state==PM_RESUME_REG_STATE_L2){+ret=mtk_pcie_reinit(mtk_dev,false);+if(ret)+returnret;++}elseif(resume_reg_state!=PM_RESUME_REG_STATE_L1&&+resume_reg_state!=PM_RESUME_REG_STATE_INIT){+ret=mtk_send_fsm_command(mtk_dev,CCCI_COMMAND_STOP);+if(ret)+returnret;++mtk_clear_rgu_irq(mtk_dev);+atomic_set(&mtk_dev->md_pm_state,MTK_PM_SUSPENDED);+return0;+}+}++iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_SET_0);+mtk_wait_pm_config(mtk_dev);++list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(entity->resume_early)+entity->resume_early(mtk_dev,entity->entity_param);+}++reinit_completion(&mtk_dev->pm_sr_ack);+/* send D3 exit request to MD */+mhccif_h2d_swint_trigger(mtk_dev,H2D_CH_RESUME_REQ);+wait_ret=wait_for_completion_timeout(&mtk_dev->pm_sr_ack,+msecs_to_jiffies(PM_ACK_TIMEOUT_MS));+if(!wait_ret)+dev_err(&pdev->dev,"[PM] Timed out waiting for device MD resume ACK\n");++reinit_completion(&mtk_dev->pm_sr_ack);+/* send D3 exit request to sAP */+mhccif_h2d_swint_trigger(mtk_dev,H2D_CH_RESUME_REQ_AP);+wait_ret=wait_for_completion_timeout(&mtk_dev->pm_sr_ack,+msecs_to_jiffies(PM_ACK_TIMEOUT_MS));+if(!wait_ret)+dev_err(&pdev->dev,"[PM] Timed out waiting for device SAP resume ACK\n");++/* Each HW final restore works */+list_for_each_entry(entity,&mtk_dev->md_pm_entities,entity){+if(entity->resume){+ret=entity->resume(mtk_dev,entity->entity_param);+if(ret)+dev_err(&pdev->dev,"[PM] Resume entry ID: %d err: %d\n",+entity->id,ret);+}+}++mtk_dev->rgu_pci_irq_en=true;+mtk_pcie_mac_set_int(mtk_dev,SAP_RGU_INT);+iowrite32(L1_DISABLE_BIT(0),IREG_BASE(mtk_dev)+DIS_ASPM_LOWPWR_CLR_0);+atomic_set(&mtk_dev->md_pm_state,MTK_PM_RESUMED);++returnret;+}++staticintmtk_pci_pm_resume_noirq(structdevice*dev)+{+structmtk_pci_dev*mtk_dev;+structpci_dev*pdev;+void__iomem*pbase;++pdev=to_pci_dev(dev);+mtk_dev=pci_get_drvdata(pdev);+pbase=IREG_BASE(mtk_dev);++/* disable interrupt first and let the IPs enable them */+iowrite32(MSIX_MSK_SET_ALL,pbase+IMASK_HOST_MSIX_CLR_GRP0_0);++return0;+}++staticvoidmtk_pci_shutdown(structpci_dev*pdev)+{+__mtk_pci_pm_suspend(pdev);+}++staticintmtk_pci_pm_suspend(structdevice*dev)+{+return__mtk_pci_pm_suspend(to_pci_dev(dev));+}++staticintmtk_pci_pm_resume(structdevice*dev)+{+return__mtk_pci_pm_resume(to_pci_dev(dev),true);+}++staticintmtk_pci_pm_thaw(structdevice*dev)+{+return__mtk_pci_pm_resume(to_pci_dev(dev),false);+}++staticconststructdev_pm_opsmtk_pci_pm_ops={+.suspend=mtk_pci_pm_suspend,+.resume=mtk_pci_pm_resume,+.resume_noirq=mtk_pci_pm_resume_noirq,+.freeze=mtk_pci_pm_suspend,+.thaw=mtk_pci_pm_thaw,+.poweroff=mtk_pci_pm_suspend,+.restore=mtk_pci_pm_resume,+.restore_noirq=mtk_pci_pm_resume_noirq,+};+staticintmtk_request_irq(structpci_dev*pdev){structmtk_pci_dev*mtk_dev;
From: Haijun Lio <haijun.liu@mediatek.com>
Control Port implements driver control messages such as modem-host
handshaking, controls port enumeration, and handles exception messages.
The handshaking process between the driver and the modem happens during
the init sequence. The process involves the exchange of a list of
supported runtime features to make sure that modem and host are ready
to provide proper feature lists including port enumeration. Further
features can be enabled and controlled in this handshaking process.
Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
drivers/net/wwan/t7xx/Makefile | 1 +
drivers/net/wwan/t7xx/t7xx_modem_ops.c | 244 ++++++++++++++++++++-
drivers/net/wwan/t7xx/t7xx_modem_ops.h | 2 +
drivers/net/wwan/t7xx/t7xx_monitor.h | 3 +
drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c | 150 +++++++++++++
drivers/net/wwan/t7xx/t7xx_port_proxy.c | 9 +-
drivers/net/wwan/t7xx/t7xx_port_proxy.h | 4 +
drivers/net/wwan/t7xx/t7xx_state_monitor.c | 3 +
8 files changed, 411 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c
@@ -270,6 +270,242 @@ static void md_sys_sw_init(struct mtk_pci_dev *mtk_dev)mtk_pcie_register_rgu_isr(mtk_dev);}+structfeature_query{+u32head_pattern;+u8feature_set[FEATURE_COUNT];+u32tail_pattern;+};++staticvoidprepare_host_rt_data_query(structcore_sys_info*core)+{+structctrl_msg_header*ctrl_msg_h;+structfeature_query*ft_query;+structccci_header*ccci_h;+structsk_buff*skb;+size_tpacket_size;++packet_size=sizeof(structccci_header)++sizeof(structctrl_msg_header)++sizeof(structfeature_query);+skb=ccci_alloc_skb(packet_size,GFS_BLOCKING);+if(!skb)+return;++skb_put(skb,packet_size);+/* fill CCCI header */+ccci_h=(structccci_header*)skb->data;+ccci_h->data[0]=0;+ccci_h->data[1]=packet_size;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,core->ctl_port->tx_ch);+ccci_h->status&=~HDR_FLD_SEQ;+ccci_h->reserved=0;+/* fill control message */+ctrl_msg_h=(structctrl_msg_header*)(skb->data++sizeof(structccci_header));+ctrl_msg_h->ctrl_msg_id=CTL_ID_HS1_MSG;+ctrl_msg_h->reserved=0;+ctrl_msg_h->data_length=sizeof(structfeature_query);+/* fill feature query */+ft_query=(structfeature_query*)(skb->data++sizeof(structccci_header)++sizeof(structctrl_msg_header));+ft_query->head_pattern=MD_FEATURE_QUERY_ID;+memcpy(ft_query->feature_set,core->feature_set,FEATURE_COUNT);+ft_query->tail_pattern=MD_FEATURE_QUERY_ID;+/* send HS1 message to device */+port_proxy_send_skb(core->ctl_port,skb,0);+}++staticintprepare_device_rt_data(structcore_sys_info*core,structdevice*dev,+void*data,intdata_length)+{+structmtk_runtime_featurert_feature;+structctrl_msg_header*ctrl_msg_h;+structfeature_query*md_feature;+structccci_header*ccci_h;+structsk_buff*skb;+intpacket_size=0;+char*rt_data;+inti;++skb=ccci_alloc_skb(MTK_SKB_4K,GFS_BLOCKING);+if(!skb)+return-EFAULT;++/* fill CCCI header */+ccci_h=(structccci_header*)skb->data;+ccci_h->data[0]=0;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,core->ctl_port->tx_ch);+ccci_h->status&=~HDR_FLD_SEQ;+ccci_h->reserved=0;+/* fill control message header */+ctrl_msg_h=(structctrl_msg_header*)(skb->data+sizeof(structccci_header));+ctrl_msg_h->ctrl_msg_id=CTL_ID_HS3_MSG;+ctrl_msg_h->reserved=0;+rt_data=(skb->data+sizeof(structccci_header)+sizeof(structctrl_msg_header));++/* parse MD runtime data query */+md_feature=data;+if(md_feature->head_pattern!=MD_FEATURE_QUERY_ID||+md_feature->tail_pattern!=MD_FEATURE_QUERY_ID){+dev_err(dev,"md_feature pattern is wrong: head 0x%x, tail 0x%x\n",+md_feature->head_pattern,md_feature->tail_pattern);+return-EINVAL;+}++/* fill runtime feature */+for(i=0;i<FEATURE_COUNT;i++){+u8md_feature_mask=FIELD_GET(FEATURE_MSK,md_feature->feature_set[i]);++memset(&rt_feature,0,sizeof(rt_feature));+rt_feature.feature_id=i;+switch(md_feature_mask){+caseMTK_FEATURE_DOES_NOT_EXIST:+caseMTK_FEATURE_MUST_BE_SUPPORTED:+rt_feature.support_info=md_feature->feature_set[i];+break;++default:+break;+}++if(FIELD_GET(FEATURE_MSK,rt_feature.support_info)!=+MTK_FEATURE_MUST_BE_SUPPORTED){+rt_feature.data_len=0;+memcpy(rt_data,&rt_feature,sizeof(structmtk_runtime_feature));+rt_data+=sizeof(structmtk_runtime_feature);+}++packet_size+=(sizeof(structmtk_runtime_feature)+rt_feature.data_len);+}++ctrl_msg_h->data_length=packet_size;+ccci_h->data[1]=packet_size+sizeof(structctrl_msg_header)++sizeof(structccci_header);+skb_put(skb,ccci_h->data[1]);+/* send HS3 message to device */+port_proxy_send_skb(core->ctl_port,skb,0);+return0;+}++staticintparse_host_rt_data(structcore_sys_info*core,structdevice*dev,+void*data,intdata_length)+{+enummtk_feature_support_typeft_spt_st,ft_spt_cfg;+structmtk_runtime_feature*rt_feature;+inti,offset;++offset=sizeof(structfeature_query);+for(i=0;i<FEATURE_COUNT&&offset<data_length;i++){+rt_feature=(structmtk_runtime_feature*)(data+offset);+ft_spt_st=FIELD_GET(FEATURE_MSK,rt_feature->support_info);+ft_spt_cfg=FIELD_GET(FEATURE_MSK,core->feature_set[i]);+offset+=sizeof(structmtk_runtime_feature)+rt_feature->data_len;++if(ft_spt_cfg==MTK_FEATURE_MUST_BE_SUPPORTED){+if(ft_spt_st!=MTK_FEATURE_MUST_BE_SUPPORTED){+dev_err(dev,"mismatch: runtime feature%d (%d,%d)\n",+i,ft_spt_cfg,ft_spt_st);+return-EINVAL;+}++if(i==RT_ID_MD_PORT_ENUM)+port_proxy_node_control(dev,(structport_msg*)rt_feature->data);+}+}++return0;+}++staticvoidcore_reset(structmtk_modem*md)+{+structccci_fsm_ctl*fsm_ctl;++atomic_set(&md->core_md.ready,0);+fsm_ctl=fsm_get_entry();++if(!fsm_ctl){+dev_err(&md->mtk_dev->pdev->dev,"fsm ctl is not initialized\n");+return;+}++/* append HS2_EXIT event to cancel the ongoing handshake in core_hk_handler() */+if(atomic_read(&md->core_md.handshake_ongoing))+fsm_append_event(fsm_ctl,CCCI_EVENT_MD_HS2_EXIT,NULL,0);++atomic_set(&md->core_md.handshake_ongoing,0);+}++staticvoidcore_hk_handler(structmtk_modem*md,structccci_fsm_ctl*ctl,+enumccci_fsm_event_stateevent_id,+enumccci_fsm_event_stateerr_detect)+{+structcore_sys_info*core_info;+structccci_fsm_event*event,*event_next;+unsignedlongflags;+structdevice*dev;+intret;++core_info=&md->core_md;+dev=&md->mtk_dev->pdev->dev;+prepare_host_rt_data_query(core_info);+while(!kthread_should_stop()){+boolevent_received=false;++spin_lock_irqsave(&ctl->event_lock,flags);+list_for_each_entry_safe(event,event_next,&ctl->event_queue,entry){+if(event->event_id==err_detect){+list_del(&event->entry);+spin_unlock_irqrestore(&ctl->event_lock,flags);+dev_err(dev,"core handshake error event received\n");+gotoexit;+}++if(event->event_id==event_id){+list_del(&event->entry);+event_received=true;+break;+}+}++spin_unlock_irqrestore(&ctl->event_lock,flags);++if(event_received)+break;++wait_event_interruptible(ctl->event_wq,!list_empty(&ctl->event_queue)||+kthread_should_stop());+if(kthread_should_stop())+gotoexit;+}++if(atomic_read(&ctl->exp_flg))+gotoexit;++ret=parse_host_rt_data(core_info,dev,event->data,event->length);+if(ret){+dev_err(dev,"host runtime data parsing fail:%d\n",ret);+gotoexit;+}++if(atomic_read(&ctl->exp_flg))+gotoexit;++ret=prepare_device_rt_data(core_info,dev,event->data,event->length);+if(ret){+dev_err(dev,"device runtime data parsing fail:%d",ret);+gotoexit;+}++atomic_set(&core_info->ready,1);+atomic_set(&core_info->handshake_ongoing,0);+wake_up(&ctl->async_hk_wq);+exit:+kfree(event);+}+staticvoidmd_hk_wq(structwork_struct*work){structccci_fsm_ctl*ctl;
@@ -277,12 +513,14 @@ static void md_hk_wq(struct work_struct *work)ctl=fsm_get_entry();+/* clear the HS2 EXIT event appended in core_reset() */+fsm_clear_event(ctl,CCCI_EVENT_MD_HS2_EXIT);cldma_switch_cfg(ID_CLDMA1);cldma_start(ID_CLDMA1);fsm_broadcast_state(ctl,MD_STATE_WAITING_FOR_HS2);md=container_of(work,structmtk_modem,handshake_work);-atomic_set(&md->core_md.ready,1);-wake_up(&ctl->async_hk_wq);+atomic_set(&md->core_md.handshake_ongoing,1);+core_hk_handler(md,ctl,CCCI_EVENT_MD_HS2,CCCI_EVENT_MD_HS2_EXIT);}voidmtk_md_event_notify(structmtk_modem*md,enummd_event_idevt_id)
@@ -0,0 +1,150 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/kthread.h>+#include<linux/skbuff.h>+#include<linux/spinlock.h>++#include"t7xx_common.h"+#include"t7xx_monitor.h"+#include"t7xx_port_proxy.h"++staticvoidfsm_ee_message_handler(structsk_buff*skb)+{+structctrl_msg_header*ctrl_msg_h;+structccci_fsm_ctl*ctl;+enummd_statemd_state;+structdevice*dev;++ctrl_msg_h=(structctrl_msg_header*)skb->data;+md_state=ccci_fsm_get_md_state();+ctl=fsm_get_entry();+dev=&ctl->md->mtk_dev->pdev->dev;+if(md_state!=MD_STATE_EXCEPTION){+dev_err(dev,"receive invalid MD_EX %x when MD state is %d\n",+ctrl_msg_h->reserved,md_state);+return;+}++switch(ctrl_msg_h->ctrl_msg_id){+caseCTL_ID_MD_EX:+if(ctrl_msg_h->reserved!=MD_EX_CHK_ID){+dev_err(dev,"receive invalid MD_EX %x\n",ctrl_msg_h->reserved);+}else{+port_proxy_send_msg_to_md(CCCI_CONTROL_TX,CTL_ID_MD_EX,MD_EX_CHK_ID);+fsm_append_event(ctl,CCCI_EVENT_MD_EX,NULL,0);+}++break;++caseCTL_ID_MD_EX_ACK:+if(ctrl_msg_h->reserved!=MD_EX_CHK_ACK_ID)+dev_err(dev,"receive invalid MD_EX_ACK %x\n",ctrl_msg_h->reserved);+else+fsm_append_event(ctl,CCCI_EVENT_MD_EX_REC_OK,NULL,0);++break;++caseCTL_ID_MD_EX_PASS:+fsm_append_event(ctl,CCCI_EVENT_MD_EX_PASS,NULL,0);+break;++caseCTL_ID_DRV_VER_ERROR:+dev_err(dev,"AP/MD driver version mismatch\n");+}+}++staticvoidcontrol_msg_handler(structt7xx_port*port,structsk_buff*skb)+{+structctrl_msg_header*ctrl_msg_h;+structccci_fsm_ctl*ctl;+intret=0;++skb_pull(skb,sizeof(structccci_header));+ctrl_msg_h=(structctrl_msg_header*)skb->data;+ctl=fsm_get_entry();++switch(ctrl_msg_h->ctrl_msg_id){+caseCTL_ID_HS2_MSG:+skb_pull(skb,sizeof(structctrl_msg_header));+if(port->rx_ch==CCCI_CONTROL_RX)+fsm_append_event(ctl,CCCI_EVENT_MD_HS2,+skb->data,ctrl_msg_h->data_length);++ccci_free_skb(&port->mtk_dev->pools,skb);+break;++caseCTL_ID_MD_EX:+caseCTL_ID_MD_EX_ACK:+caseCTL_ID_MD_EX_PASS:+caseCTL_ID_DRV_VER_ERROR:+fsm_ee_message_handler(skb);+ccci_free_skb(&port->mtk_dev->pools,skb);+break;++caseCTL_ID_PORT_ENUM:+skb_pull(skb,sizeof(structctrl_msg_header));+ret=port_proxy_node_control(port->dev,(structport_msg*)skb->data);+if(!ret)+port_proxy_send_msg_to_md(CCCI_CONTROL_TX,CTL_ID_PORT_ENUM,0);+else+port_proxy_send_msg_to_md(CCCI_CONTROL_TX,+CTL_ID_PORT_ENUM,PORT_ENUM_VER_MISMATCH);++break;++default:+dev_err(port->dev,"unknown control message ID to FSM %x\n",+ctrl_msg_h->ctrl_msg_id);+break;+}++if(ret)+dev_err(port->dev,"%s control message handle error: %d\n",port->name,ret);+}++staticintport_ctl_init(structt7xx_port*port)+{+port->skb_handler=&control_msg_handler;+port->thread=kthread_run(port_kthread_handler,port,"%s",port->name);+if(IS_ERR(port->thread)){+dev_err(port->dev,"failed to start port control thread\n");+returnPTR_ERR(port->thread);+}++port->rx_length_th=MAX_CTRL_QUEUE_LENGTH;+port->skb_from_pool=true;+return0;+}++staticvoidport_ctl_uninit(structt7xx_port*port)+{+unsignedlongflags;+structsk_buff*skb;++if(port->thread)+kthread_stop(port->thread);++spin_lock_irqsave(&port->rx_wq.lock,flags);+while((skb=__skb_dequeue(&port->rx_skb_list))!=NULL)+ccci_free_skb(&port->mtk_dev->pools,skb);++spin_unlock_irqrestore(&port->rx_wq.lock,flags);+}++structport_opsctl_port_ops={+.init=&port_ctl_init,+.recv_skb=&port_recv_skb,+.uninit=&port_ctl_uninit,+};
@@ -45,6 +45,7 @@ struct port_proxy {unsignedintmajor;unsignedintminor_base;structt7xx_port*ports;+structt7xx_port*ctl_port;structt7xx_port*dedicated_ports[CLDMA_NUM][MTK_MAX_QUEUE_NUM];/* port list of each RX channel, for RX dispatching */structlist_headrx_ch_ports[CCCI_MAX_CH_ID];
@@ -0,0 +1,281 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:ChandrashekarDevegowda<chandrashekar.devegowda@intel.com>+*HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/device.h>+#include<linux/fs.h>+#include<linux/minmax.h>+#include<linux/module.h>+#include<linux/poll.h>+#include<linux/uaccess.h>+#include<linux/wait.h>+#include<linux/wwan.h>+#include<linux/bitfield.h>++#include"t7xx_common.h"+#include"t7xx_hif_cldma.h"+#include"t7xx_monitor.h"+#include"t7xx_port_proxy.h"+#include"t7xx_skb_util.h"++staticintmtk_port_ctrl_start(structwwan_port*port)+{+structt7xx_port*port_mtk;++port_mtk=wwan_port_get_drvdata(port);++if(atomic_read(&port_mtk->usage_cnt))+return-EBUSY;++atomic_inc(&port_mtk->usage_cnt);+return0;+}++staticvoidmtk_port_ctrl_stop(structwwan_port*port)+{+structt7xx_port*port_mtk;++port_mtk=wwan_port_get_drvdata(port);++atomic_dec(&port_mtk->usage_cnt);+}++staticintmtk_port_ctrl_tx(structwwan_port*port,structsk_buff*skb)+{+size_tactual_count=0,alloc_size=0,txq_mtu=0;+structsk_buff*skb_ccci=NULL;+structt7xx_port*port_ccci;+inti,multi_packet=1;+enummd_statemd_state;+unsignedintcount;+intret=0;++count=skb->len;+if(!count)+return-EINVAL;++port_ccci=wwan_port_get_drvdata(port);+md_state=ccci_fsm_get_md_state();+if(md_state==MD_STATE_WAITING_FOR_HS1||md_state==MD_STATE_WAITING_FOR_HS2){+dev_warn(port_ccci->dev,"port %s ch%d write fail when md_state=%d!\n",+port_ccci->name,port_ccci->tx_ch,md_state);+return-ENODEV;+}++txq_mtu=CLDMA_TXQ_MTU;++if(port_ccci->flags&PORT_F_RAW_DATA||port_ccci->flags&PORT_F_USER_HEADER){+if(port_ccci->flags&PORT_F_USER_HEADER&&count>txq_mtu){+dev_err(port_ccci->dev,"reject packet(size=%u), larger than MTU on %s\n",+count,port_ccci->name);+return-ENOMEM;+}++alloc_size=min_t(size_t,txq_mtu,count);+actual_count=alloc_size;+}else{+alloc_size=min_t(size_t,txq_mtu,count+CCCI_H_ELEN);+actual_count=alloc_size-CCCI_H_ELEN;+if(count+CCCI_H_ELEN>txq_mtu&&+(port_ccci->tx_ch==CCCI_MBIM_TX||+(port_ccci->tx_ch>=CCCI_DSS0_TX&&port_ccci->tx_ch<=CCCI_DSS7_TX)))+multi_packet=DIV_ROUND_UP(count,txq_mtu-CCCI_H_ELEN);+}++for(i=0;i<multi_packet;i++){+structccci_header*ccci_h=NULL;++if(multi_packet>1&&multi_packet==i+1){+actual_count=count%(txq_mtu-CCCI_H_ELEN);+alloc_size=actual_count+CCCI_H_ELEN;+}++skb_ccci=ccci_alloc_skb_from_pool(&port_ccci->mtk_dev->pools,alloc_size,+GFS_BLOCKING);+if(!skb_ccci)+return-ENOMEM;++if(port_ccci->flags&PORT_F_RAW_DATA){+memcpy(skb_put(skb_ccci,actual_count),skb->data,actual_count);++if(port_ccci->flags&PORT_F_USER_HEADER){+/* The ccci_header is provided by user.+*+*Foronlysendccci_headerwithoutadditionaldata+*case,data[0]=CCCI_HEADER_NO_DATA,data[1]=user_data,+*ch=tx_channel,reserved=no_use.+*+*Forsendccci_header+*withadditionaldatacase,data[0]=0,+*data[1]=data_size,ch=tx_channel,+*reserved=user_data.+*/+ccci_h=(structccci_header*)skb->data;+if(actual_count==CCCI_H_LEN)+ccci_h->data[0]=CCCI_HEADER_NO_DATA;+else+ccci_h->data[1]=actual_count;++ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,port_ccci->tx_ch);+}+}else{+/* ccci_header is provided by driver */+ccci_h=skb_put(skb_ccci,CCCI_H_LEN);+ccci_h->data[0]=0;+ccci_h->data[1]=actual_count+CCCI_H_LEN;+ccci_h->status&=~HDR_FLD_CHN;+ccci_h->status|=FIELD_PREP(HDR_FLD_CHN,port_ccci->tx_ch);+ccci_h->reserved=0;++memcpy(skb_put(skb_ccci,actual_count),+skb->data+i*(txq_mtu-CCCI_H_ELEN),actual_count);+}++port_proxy_set_seq_num(port_ccci,ccci_h);+ret=port_send_skb_to_md(port_ccci,skb_ccci,true);+if(ret)+gotoerr_out;++/* record the port seq_num after the data is sent to HIF */+port_ccci->seq_nums[MTK_OUT]++;++if(multi_packet==1)+returnactual_count;+elseif(multi_packet==i+1)+returncount;+}++err_out:+if(ret!=-ENOMEM){+dev_err(port_ccci->dev,"write error done on %s, size=%zu, ret=%d\n",+port_ccci->name,actual_count,ret);+ccci_free_skb(&port_ccci->mtk_dev->pools,skb_ccci);+}++returnret;+}++staticconststructwwan_port_opsmtk_wwan_port_ops={+.start=mtk_port_ctrl_start,+.stop=mtk_port_ctrl_stop,+.tx=mtk_port_ctrl_tx,+};++staticintport_wwan_init(structt7xx_port*port)+{+port->rx_length_th=MAX_RX_QUEUE_LENGTH;+port->skb_from_pool=true;++if(!(port->flags&PORT_F_RAW_DATA))+port->flags|=PORT_F_RX_ADJUST_HEADER;++if(port->rx_ch==CCCI_UART2_RX)+port->flags|=PORT_F_RX_CH_TRAFFIC;++if(port->mtk_port_type!=WWAN_PORT_UNKNOWN){+port->mtk_wwan_port=+wwan_create_port(port->dev,port->mtk_port_type,&mtk_wwan_port_ops,port);+if(IS_ERR(port->mtk_wwan_port))+returnPTR_ERR(port->mtk_wwan_port);+}else{+port->mtk_wwan_port=NULL;+}++return0;+}++staticvoidport_wwan_uninit(structt7xx_port*port)+{+if(port->mtk_wwan_port){+if(port->chn_crt_stat==CCCI_CHAN_ENABLE){+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+}++wwan_remove_port(port->mtk_wwan_port);+port->mtk_wwan_port=NULL;+}+}++staticintport_wwan_recv_skb(structt7xx_port*port,structsk_buff*skb)+{+if(port->flags&PORT_F_RX_CHAR_NODE){+if(!atomic_read(&port->usage_cnt)){+dev_err_ratelimited(port->dev,"port %s is not opened, drop packets\n",+port->name);+return-ENETDOWN;+}+}++returnport_recv_skb(port,skb);+}++staticintport_status_update(structt7xx_port*port)+{+if(port->flags&PORT_F_RX_CHAR_NODE){+if(port->chan_enable==CCCI_CHAN_ENABLE){+port->flags&=~PORT_F_RX_ALLOW_DROP;+}else{+port->flags|=PORT_F_RX_ALLOW_DROP;+spin_lock(&port->port_update_lock);+port->chn_crt_stat=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);+returnport_proxy_broadcast_state(port,MTK_PORT_STATE_DISABLE);+}+}++return0;+}++staticintport_wwan_enable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_ENABLE;+spin_unlock(&port->port_update_lock);++if(port->chn_crt_stat!=port->chan_enable)+returnport_status_update(port);++return0;+}++staticintport_wwan_disable_chl(structt7xx_port*port)+{+spin_lock(&port->port_update_lock);+port->chan_enable=CCCI_CHAN_DISABLE;+spin_unlock(&port->port_update_lock);++if(port->chn_crt_stat!=port->chan_enable)+returnport_status_update(port);++return0;+}++staticvoidport_wwan_md_state_notify(structt7xx_port*port,unsignedintstate)+{+if(state==MD_STATE_READY)+port_status_update(port);+}++structport_opswwan_sub_port_ops={+.init=&port_wwan_init,+.recv_skb=&port_wwan_recv_skb,+.uninit=&port_wwan_uninit,+.enable_chl=&port_wwan_enable_chl,+.disable_chl=&port_wwan_disable_chl,+.md_state_notify=&port_wwan_md_state_notify,+};
From: Haijun Lio <haijun.liu@mediatek.com>
Introduce the mechanism to lock/unlock the device 'deep sleep' mode.
When the PCIe link state is L1.2 or L2, the host side still can keep
the device is in D0 state from the host side point of view. At the same
time, if the device's 'deep sleep' mode is unlocked, the device will
go to 'deep sleep' while it is still in D0 state on the host side.
Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
drivers/net/wwan/t7xx/t7xx_hif_cldma.c | 11 +++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c | 24 ++++--
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c | 39 ++++++---
drivers/net/wwan/t7xx/t7xx_mhccif.c | 3 +
drivers/net/wwan/t7xx/t7xx_pci.c | 95 ++++++++++++++++++++++
drivers/net/wwan/t7xx/t7xx_pci.h | 10 +++
6 files changed, 166 insertions(+), 16 deletions(-)
@@ -1020,6 +1022,7 @@ static int dpmaif_rx_data_collect(struct dpmaif_ctrl *dpmaif_ctrl,return0;}+/* call after mtk_pci_disable_sleep */staticvoiddpmaif_do_rx(structdpmaif_ctrl*dpmaif_ctrl,structdpmaif_rx_queue*rxq){intret;
@@ -1059,7 +1062,13 @@ static void dpmaif_rxq_work(struct work_struct *work)if(ret<0&&ret!=-EACCES)return;-dpmaif_do_rx(dpmaif_ctrl,rxq);+mtk_pci_disable_sleep(dpmaif_ctrl->mtk_dev);++/* we can try blocking wait for lock resource here in process context */+if(mtk_pci_sleep_disable_complete(dpmaif_ctrl->mtk_dev))+dpmaif_do_rx(dpmaif_ctrl,rxq);++mtk_pci_enable_sleep(dpmaif_ctrl->mtk_dev);pm_runtime_mark_last_busy(dpmaif_ctrl->dev);pm_runtime_put_autosuspend(dpmaif_ctrl->dev);
@@ -1433,14 +1442,19 @@ static void dpmaif_bat_release_work(struct work_struct *work)if(ret<0&&ret!=-EACCES)return;+mtk_pci_disable_sleep(dpmaif_ctrl->mtk_dev);+/* ALL RXQ use one BAT table, so choose DPF_RX_QNO_DFT */rxq=&dpmaif_ctrl->rxq[DPF_RX_QNO_DFT];-/* normal BAT release and add */-dpmaif_dl_pkt_bat_release_and_add(rxq);-/* frag BAT release and add */-dpmaif_dl_frag_bat_release_and_add(rxq);+if(mtk_pci_sleep_disable_complete(dpmaif_ctrl->mtk_dev)){+/* normal BAT release and add */+dpmaif_dl_pkt_bat_release_and_add(rxq);+/* frag BAT release and add */+dpmaif_dl_frag_bat_release_and_add(rxq);+}+mtk_pci_enable_sleep(dpmaif_ctrl->mtk_dev);pm_runtime_mark_last_busy(dpmaif_ctrl->dev);pm_runtime_put_autosuspend(dpmaif_ctrl->dev);}
@@ -172,19 +172,26 @@ static void dpmaif_tx_done(struct work_struct *work)if(ret<0&&ret!=-EACCES)return;-ret=dpmaif_tx_release(dpmaif_ctrl,txq->index,txq->drb_size_cnt);-if(ret==-EAGAIN||-(dpmaif_hw_check_clr_ul_done_status(&dpmaif_ctrl->hif_hw_info,txq->index)&&-dpmaif_no_remain_spurious_tx_done_intr(txq))){-queue_work(dpmaif_ctrl->txq[txq->index].worker,-&dpmaif_ctrl->txq[txq->index].dpmaif_tx_work);-/* clear IP busy to give the device time to enter the low power state */-dpmaif_clr_ip_busy_sts(&dpmaif_ctrl->hif_hw_info);-}else{-dpmaif_clr_ip_busy_sts(&dpmaif_ctrl->hif_hw_info);-dpmaif_unmask_ulq_interrupt(dpmaif_ctrl,txq->index);+/* The device may be in low power state. disable sleep if needed */+mtk_pci_disable_sleep(dpmaif_ctrl->mtk_dev);++/* ensure that we are not in deep sleep */+if(mtk_pci_sleep_disable_complete(dpmaif_ctrl->mtk_dev)){+ret=dpmaif_tx_release(dpmaif_ctrl,txq->index,txq->drb_size_cnt);+if(ret==-EAGAIN||+(dpmaif_hw_check_clr_ul_done_status(&dpmaif_ctrl->hif_hw_info,txq->index)&&+dpmaif_no_remain_spurious_tx_done_intr(txq))){+queue_work(dpmaif_ctrl->txq[txq->index].worker,+&dpmaif_ctrl->txq[txq->index].dpmaif_tx_work);+/* clear IP busy to give the device time to enter the low power state */+dpmaif_clr_ip_busy_sts(&dpmaif_ctrl->hif_hw_info);+}else{+dpmaif_clr_ip_busy_sts(&dpmaif_ctrl->hif_hw_info);+dpmaif_unmask_ulq_interrupt(dpmaif_ctrl,txq->index);+}}+mtk_pci_enable_sleep(dpmaif_ctrl->mtk_dev);pm_runtime_mark_last_busy(dpmaif_ctrl->dev);pm_runtime_put_autosuspend(dpmaif_ctrl->dev);}
@@ -46,6 +47,22 @@ enum mtk_pm_state {MTK_PM_RESUMED,/* Device in resume state */};+staticvoidmtk_dev_set_sleep_capability(structmtk_pci_dev*mtk_dev,boolenable)+{+void__iomem*ctrl_reg;+u32value;++ctrl_reg=IREG_BASE(mtk_dev)+PCIE_MISC_CTRL;+value=ioread32(ctrl_reg);++if(enable)+value&=~PCIE_MISC_MAC_SLEEP_DIS;+else+value|=PCIE_MISC_MAC_SLEEP_DIS;++iowrite32(value,ctrl_reg);+}+staticintmtk_wait_pm_config(structmtk_pci_dev*mtk_dev){intret,val;
@@ -68,10 +85,14 @@ static int mtk_pci_pm_init(struct mtk_pci_dev *mtk_dev)INIT_LIST_HEAD(&mtk_dev->md_pm_entities);+spin_lock_init(&mtk_dev->md_pm_lock);+mutex_init(&mtk_dev->md_pm_entity_mtx);+init_completion(&mtk_dev->sleep_lock_acquire);init_completion(&mtk_dev->pm_sr_ack);+atomic_set(&mtk_dev->sleep_disable_count,0);device_init_wakeup(&pdev->dev,true);dev_pm_set_driver_flags(&pdev->dev,pdev->dev.power.driver_flags|
@@ -90,6 +111,7 @@ void mtk_pci_pm_init_late(struct mtk_pci_dev *mtk_dev){/* enable the PCIe Resource Lock only after MD deep sleep is done */mhccif_mask_clr(mtk_dev,+D2H_INT_DS_LOCK_ACK|D2H_INT_SUSPEND_ACK|D2H_INT_RESUME_ACK|D2H_INT_SUSPEND_ACK_AP|
@@ -156,6 +178,79 @@ int mtk_pci_pm_entity_unregister(struct mtk_pci_dev *mtk_dev, struct md_pm_entitreturn-ENXIO;}+intmtk_pci_sleep_disable_complete(structmtk_pci_dev*mtk_dev)+{+intret;++ret=wait_for_completion_timeout(&mtk_dev->sleep_lock_acquire,+msecs_to_jiffies(MTK_WAIT_TIMEOUT_MS));+if(!ret)+dev_err_ratelimited(&mtk_dev->pdev->dev,"Resource wait complete timed out\n");++returnret;+}++/**+*mtk_pci_disable_sleep()-disabledeepsleepcapability+*@mtk_dev:MTKdevice+*+*Lockthedeepsleepcapabitily,notethatthedevicecangointodeepsleep+*statewhileitisstillinD0statefromthehostpointofview.+*+*Ifdeviceisindeepsleepstatethenwakeupthedeviceanddisabledeepsleepcapability.+*/+voidmtk_pci_disable_sleep(structmtk_pci_dev*mtk_dev)+{+unsignedlongflags;++if(atomic_read(&mtk_dev->md_pm_state)<MTK_PM_RESUMED){+atomic_inc(&mtk_dev->sleep_disable_count);+complete_all(&mtk_dev->sleep_lock_acquire);+return;+}++spin_lock_irqsave(&mtk_dev->md_pm_lock,flags);+if(atomic_inc_return(&mtk_dev->sleep_disable_count)==1){+reinit_completion(&mtk_dev->sleep_lock_acquire);+mtk_dev_set_sleep_capability(mtk_dev,false);+/* read register status to check whether the device's+*deepsleepisdisabledornot.+*/+if((ioread32(IREG_BASE(mtk_dev)+PCIE_RESOURCE_STATUS)&+PCIE_RESOURCE_STATUS_MSK)==PCIE_RESOURCE_STATUS_MSK){+spin_unlock_irqrestore(&mtk_dev->md_pm_lock,flags);+complete_all(&mtk_dev->sleep_lock_acquire);+return;+}++mhccif_h2d_swint_trigger(mtk_dev,H2D_CH_DS_LOCK);+}++spin_unlock_irqrestore(&mtk_dev->md_pm_lock,flags);+}++/**+*mtk_pci_enable_sleep()-enabledeepsleepcapability+*@mtk_dev:MTKdevice+*+*Afterenablingdeepsleep,devicecanenterintodeepsleepstate.+*/+voidmtk_pci_enable_sleep(structmtk_pci_dev*mtk_dev)+{+unsignedlongflags;++if(atomic_read(&mtk_dev->md_pm_state)<MTK_PM_RESUMED){+atomic_dec(&mtk_dev->sleep_disable_count);+return;+}++if(atomic_dec_and_test(&mtk_dev->sleep_disable_count)){+spin_lock_irqsave(&mtk_dev->md_pm_lock,flags);+mtk_dev_set_sleep_capability(mtk_dev,true);+spin_unlock_irqrestore(&mtk_dev->md_pm_lock,flags);+}+}+staticint__mtk_pci_pm_suspend(structpci_dev*pdev){structmtk_pci_dev*mtk_dev;
From: Haijun Lio <haijun.liu@mediatek.com>
Data Path Modem AP Interface (DPMAIF) HIF layer provides methods
for initialization, ISR, control and event handling of TX/RX flows.
DPMAIF TX
Exposes the `dmpaif_tx_send_skb` function which can be used by the
network device to transmit packets.
The uplink data management uses a Descriptor Ring Buffer (DRB).
First DRB entry is a message type that will be followed by 1 or more
normal DRB entries. Message type DRB will hold the skb information
and each normal DRB entry holds a pointer to the skb payload.
DPMAIF RX
The downlink buffer management uses Buffer Address Table (BAT) and
Packet Information Table (PIT) rings.
The BAT ring holds the address of skb data buffer for the HW to use,
while the PIT contains metadata about a whole network packet including
a reference to the BAT entry holding the data buffer address.
The driver reads the PIT and BAT entries written by the modem, when
reaching a threshold, the driver will reload the PIT and BAT rings.
Signed-off-by: Haijun Lio <haijun.liu@mediatek.com>
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Co-developed-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
Signed-off-by: Ricardo Martinez <ricardo.martinez@linux.intel.com>
---
drivers/net/wwan/t7xx/Makefile | 4 +
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.c | 540 +++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.h | 278 ++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c | 1532 ++++++++++++++++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.h | 117 ++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c | 810 +++++++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.h | 82 ++
7 files changed, 3363 insertions(+)
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.h
@@ -691,10 +692,15 @@ int mtk_md_init(struct mtk_pci_dev *mtk_dev)if(ret)gotoerr_alloc;-ret=cldma_init(ID_CLDMA1);+/* init the data path */+ret=ccmni_init(mtk_dev);if(ret)gotoerr_fsm_init;+ret=cldma_init(ID_CLDMA1);+if(ret)+gotoerr_ccmni_init;+ret=port_proxy_init(mtk_dev->md);if(ret)gotoerr_cldma_init;
@@ -709,6 +715,8 @@ int mtk_md_init(struct mtk_pci_dev *mtk_dev)err_cldma_init:cldma_exit(ID_CLDMA1);+err_ccmni_init:+ccmni_exit(mtk_dev);err_fsm_init:ccci_fsm_uninit();err_alloc:
@@ -0,0 +1,545 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(c)2021,MediaTekInc.+*Copyright(c)2021,IntelCorporation.+*+*Authors:HaijunLio<haijun.liu@mediatek.com>+*Contributors:AmirHanania<amir.hanania@intel.com>+*AndyShevchenko<andriy.shevchenko@linux.intel.com>+*ChandrashekarDevegowda<chandrashekar.devegowda@intel.com>+*ChiranjeeviRapolu<chiranjeevi.rapolu@intel.com>+*EliotLee<eliot.lee@intel.com>+*MoisesVeleta<moises.veleta@intel.com>+*RicardoMartinez<ricardo.martinez@linux.intel.com>+*SreehariKancharla<sreehari.kancharla@intel.com>+*/++#include<linux/bitfield.h>+#include<linux/ip.h>+#include<linux/netdevice.h>+#include<linux/wwan.h>++#include<net/ipv6.h>++#include"t7xx_hif_dpmaif_rx.h"+#include"t7xx_hif_dpmaif_tx.h"+#include"t7xx_netdev.h"++#define IP_MUX_SESSION_DEFAULT 0+#define SBD_PACKET_TYPE_MASK GENMASK(7, 4)++staticvoidccmni_make_etherframe(structnet_device*dev,void*skb_eth_hdr,+u8*mac_addr,unsignedintpacket_type)+{+structethhdr*eth_hdr;++eth_hdr=skb_eth_hdr;+memcpy(eth_hdr->h_dest,mac_addr,sizeof(eth_hdr->h_dest));+memset(eth_hdr->h_source,0,sizeof(eth_hdr->h_source));++if(packet_type==IPV6_VERSION)+eth_hdr->h_proto=cpu_to_be16(ETH_P_IPV6);+else+eth_hdr->h_proto=cpu_to_be16(ETH_P_IP);+}++staticenumtxq_typeget_txq_type(structsk_buff*skb)+{+u32total_len,payload_len,l4_off;+booltcp_syn_fin_rst,is_tcp;+structipv6hdr*ip6h;+structtcphdr*tcph;+structiphdr*ip4h;+u32packet_type;+__be16frag_off;++packet_type=skb->data[0]&SBD_PACKET_TYPE_MASK;+if(packet_type==IPV6_VERSION){+ip6h=(structipv6hdr*)skb->data;+total_len=sizeof(structipv6hdr)+ntohs(ip6h->payload_len);+l4_off=ipv6_skip_exthdr(skb,sizeof(structipv6hdr),&ip6h->nexthdr,&frag_off);+tcph=(structtcphdr*)(skb->data+l4_off);+is_tcp=ip6h->nexthdr==IPPROTO_TCP;+payload_len=total_len-l4_off-(tcph->doff<<2);+}elseif(packet_type==IPV4_VERSION){+ip4h=(structiphdr*)skb->data;+tcph=(structtcphdr*)(skb->data+(ip4h->ihl<<2));+is_tcp=ip4h->protocol==IPPROTO_TCP;+payload_len=ntohs(ip4h->tot_len)-(ip4h->ihl<<2)-(tcph->doff<<2);+}else{+returnTXQ_NORMAL;+}++tcp_syn_fin_rst=tcph->syn||tcph->fin||tcph->rst;+if(is_tcp&&!payload_len&&!tcp_syn_fin_rst)+returnTXQ_FAST;++returnTXQ_NORMAL;+}++staticu16ccmni_select_queue(structnet_device*dev,structsk_buff*skb,+structnet_device*sb_dev)+{+structccmni_instance*ccmni;++ccmni=netdev_priv(dev);++if(ccmni->ctlb->capability&NIC_CAP_DATA_ACK_DVD)+returnget_txq_type(skb);++returnTXQ_NORMAL;+}++staticintccmni_open(structnet_device*dev)+{+structccmni_instance*ccmni;++ccmni=wwan_netdev_drvpriv(dev);+netif_carrier_on(dev);+netif_tx_start_all_queues(dev);+atomic_inc(&ccmni->usage);+return0;+}++staticintccmni_close(structnet_device*dev)+{+structccmni_instance*ccmni;++ccmni=wwan_netdev_drvpriv(dev);++if(atomic_dec_return(&ccmni->usage)<0)+return-EINVAL;++netif_carrier_off(dev);+netif_tx_disable(dev);+return0;+}++staticintccmni_send_packet(structccmni_instance*ccmni,structsk_buff*skb,enumtxq_typetxqt)+{+structccmni_ctl_block*ctlb;+structccci_header*ccci_h;+unsignedintccmni_idx;++skb_push(skb,sizeof(structccci_header));+ccci_h=(structccci_header*)skb->data;+ccci_h->status&=~HDR_FLD_CHN;++ccmni_idx=ccmni->index;+ccci_h->data[0]=ccmni_idx;+ccci_h->data[1]=skb->len;+ccci_h->reserved=0;++ctlb=ccmni->ctlb;+if(dpmaif_tx_send_skb(ctlb->hif_ctrl,txqt,skb)){+skb_pull(skb,sizeof(structccci_header));+/* we will reserve header again in the next retry */+returnNETDEV_TX_BUSY;+}++return0;+}++staticintccmni_start_xmit(structsk_buff*skb,structnet_device*dev)+{+structccmni_instance*ccmni;+structccmni_ctl_block*ctlb;+enumtxq_typetxqt;+intskb_len;++ccmni=wwan_netdev_drvpriv(dev);+ctlb=ccmni->ctlb;+txqt=TXQ_NORMAL;+skb_len=skb->len;++/* If MTU changed or there is no headroom, drop the packet */+if(skb->len>dev->mtu||skb_headroom(skb)<sizeof(structccci_header)){+dev_kfree_skb(skb);+dev->stats.tx_dropped++;+returnNETDEV_TX_OK;+}++if(ctlb->capability&NIC_CAP_DATA_ACK_DVD)+txqt=get_txq_type(skb);++if(ccmni_send_packet(ccmni,skb,txqt)){+if(!(ctlb->capability&NIC_CAP_TXBUSY_STOP)){+if((ccmni->tx_busy_cnt[txqt]++)%100==0)+netdev_notice(dev,"[TX]CCMNI:%d busy:pkt=%ld(ack=%d) cnt=%ld\n",+ccmni->index,dev->stats.tx_packets,+txqt,ccmni->tx_busy_cnt[txqt]);+}else{+ccmni->tx_busy_cnt[txqt]++;+}++returnNETDEV_TX_BUSY;+}++dev->stats.tx_packets++;+dev->stats.tx_bytes+=skb_len;+if(ccmni->tx_busy_cnt[txqt]>10){+netdev_notice(dev,"[TX]CCMNI:%d TX busy:tx_pkt=%ld(ack=%d) retries=%ld\n",+ccmni->index,dev->stats.tx_packets,+txqt,ccmni->tx_busy_cnt[txqt]);+}+ccmni->tx_busy_cnt[txqt]=0;++returnNETDEV_TX_OK;+}++staticintccmni_change_mtu(structnet_device*dev,intnew_mtu)+{+if(new_mtu>CCMNI_MTU_MAX)+return-EINVAL;++dev->mtu=new_mtu;+return0;+}++staticvoidccmni_tx_timeout(structnet_device*dev,unsignedint__always_unusedtxqueue)+{+structccmni_instance*ccmni;++ccmni=(structccmni_instance*)netdev_priv(dev);+dev->stats.tx_errors++;+if(atomic_read(&ccmni->usage)>0)+netif_tx_wake_all_queues(dev);+}++staticconststructnet_device_opsccmni_netdev_ops={+.ndo_open=ccmni_open,+.ndo_stop=ccmni_close,+.ndo_start_xmit=ccmni_start_xmit,+.ndo_tx_timeout=ccmni_tx_timeout,+.ndo_change_mtu=ccmni_change_mtu,+.ndo_select_queue=ccmni_select_queue,+};++staticvoidccmni_start(structccmni_ctl_block*ctlb)+{+structccmni_instance*ccmni;+inti;++/* carry on the net link */+for(i=0;i<ctlb->nic_dev_num;i++){+ccmni=ctlb->ccmni_inst[i];+if(!ccmni)+continue;++if(atomic_read(&ccmni->usage)>0){+netif_tx_start_all_queues(ccmni->dev);+netif_carrier_on(ccmni->dev);+}+}+}++staticvoidccmni_pre_stop(structccmni_ctl_block*ctlb)+{+structccmni_instance*ccmni;+inti;++/* stop tx */+for(i=0;i<ctlb->nic_dev_num;i++){+ccmni=ctlb->ccmni_inst[i];+if(!ccmni)+continue;++if(atomic_read(&ccmni->usage)>0)+netif_tx_disable(ccmni->dev);+}+}++staticvoidccmni_pos_stop(structccmni_ctl_block*ctlb)+{+structccmni_instance*ccmni;+inti;++/* carry off the net link */+for(i=0;i<ctlb->nic_dev_num;i++){+ccmni=ctlb->ccmni_inst[i];+if(!ccmni)+continue;++if(atomic_read(&ccmni->usage)>0)+netif_carrier_off(ccmni->dev);+}+}++staticvoidccmni_wwan_setup(structnet_device*dev)+{+dev->header_ops=NULL;+dev->hard_header_len+=sizeof(structccci_header);++dev->mtu=WWAN_DEFAULT_MTU;+dev->max_mtu=CCMNI_MTU_MAX;+dev->tx_queue_len=CCMNI_TX_QUEUE;+dev->watchdog_timeo=CCMNI_NETDEV_WDT_TO;+/* ccmni is a pure IP device */+dev->flags=(IFF_POINTOPOINT|IFF_NOARP)+&~(IFF_BROADCAST|IFF_MULTICAST);++/* not supporting VLAN */+dev->features=NETIF_F_VLAN_CHALLENGED;++dev->features|=NETIF_F_SG;+dev->hw_features|=NETIF_F_SG;++/* uplink checksum offload */+dev->features|=NETIF_F_HW_CSUM;+dev->hw_features|=NETIF_F_HW_CSUM;++/* downlink checksum offload */+dev->features|=NETIF_F_RXCSUM;+dev->hw_features|=NETIF_F_RXCSUM;++dev->addr_len=ETH_ALEN;++/* use kernel default free_netdev() function */+dev->needs_free_netdev=true;++/* no need to free again because of free_netdev() */+dev->priv_destructor=NULL;+dev->type=ARPHRD_PPP;++dev->netdev_ops=&ccmni_netdev_ops;+eth_random_addr(dev->dev_addr);+}++staticintccmni_wwan_newlink(void*ctxt,structnet_device*dev,u32if_id,+structnetlink_ext_ack*extack)+{+structccmni_ctl_block*ctlb;+structccmni_instance*ccmni;+intret;++ctlb=ctxt;++if(if_id>=ARRAY_SIZE(ctlb->ccmni_inst))+return-EINVAL;++/* initialize private structure of netdev */+ccmni=wwan_netdev_drvpriv(dev);+ccmni->index=if_id;+ccmni->ctlb=ctlb;+ccmni->dev=dev;+atomic_set(&ccmni->usage,0);+ctlb->ccmni_inst[if_id]=ccmni;++ret=register_netdevice(dev);+if(ret)+returnret;++netif_device_attach(dev);+return0;+}++staticvoidccmni_wwan_dellink(void*ctxt,structnet_device*dev,structlist_head*head)+{+structccmni_instance*ccmni;+structccmni_ctl_block*ctlb;+intif_id;++ccmni=wwan_netdev_drvpriv(dev);+ctlb=ctxt;+if_id=ccmni->index;++if(if_id>=ARRAY_SIZE(ctlb->ccmni_inst))+return;++if(WARN_ON(ctlb->ccmni_inst[if_id]!=ccmni))+return;++unregister_netdevice(dev);+}++staticconststructwwan_opsccmni_wwan_ops={+.priv_size=sizeof(structccmni_instance),+.setup=ccmni_wwan_setup,+.newlink=ccmni_wwan_newlink,+.dellink=ccmni_wwan_dellink,+};++staticintccmni_md_state_callback(enummd_statestate,void*para)+{+structccmni_ctl_block*ctlb;+intret=0;++ctlb=para;+ctlb->md_sta=state;++switch(state){+caseMD_STATE_READY:+ccmni_start(ctlb);+break;++caseMD_STATE_EXCEPTION:+caseMD_STATE_STOPPED:+ccmni_pre_stop(ctlb);+ret=dpmaif_md_state_callback(ctlb->hif_ctrl,state);+if(ret<0)+dev_err(ctlb->hif_ctrl->dev,+"dpmaif md state callback err, md_sta=%d\n",state);++ccmni_pos_stop(ctlb);+break;++caseMD_STATE_WAITING_FOR_HS1:+caseMD_STATE_WAITING_TO_STOP:+ret=dpmaif_md_state_callback(ctlb->hif_ctrl,state);+if(ret<0)+dev_err(ctlb->hif_ctrl->dev,+"dpmaif md state callback err, md_sta=%d\n",state);+break;++default:+break;+}++returnret;+}++staticvoidinit_md_status_notifier(structccmni_ctl_block*ctlb)+{+structfsm_notifier_block*md_status_notifier;++md_status_notifier=&ctlb->md_status_notify;+INIT_LIST_HEAD(&md_status_notifier->entry);+md_status_notifier->notifier_fn=ccmni_md_state_callback;+md_status_notifier->data=ctlb;++fsm_notifier_register(md_status_notifier);+}++staticvoidccmni_recv_skb(structmtk_pci_dev*mtk_dev,intnetif_id,structsk_buff*skb)+{+structccmni_instance*ccmni;+structnet_device*dev;+intpkt_type,skb_len;++ccmni=mtk_dev->ccmni_ctlb->ccmni_inst[netif_id];+if(!ccmni){+dev_kfree_skb(skb);+return;+}++dev=ccmni->dev;++pkt_type=skb->data[0]&SBD_PACKET_TYPE_MASK;+ccmni_make_etherframe(dev,skb->data-ETH_HLEN,dev->dev_addr,pkt_type);+skb_set_mac_header(skb,-ETH_HLEN);+skb_reset_network_header(skb);+skb->dev=dev;+if(pkt_type==IPV6_VERSION)+skb->protocol=htons(ETH_P_IPV6);+else+skb->protocol=htons(ETH_P_IP);++skb_len=skb->len;++netif_rx_any_context(skb);+dev->stats.rx_packets++;+dev->stats.rx_bytes+=skb_len;+}++staticvoidccmni_queue_tx_irq_notify(structccmni_ctl_block*ctlb,intqno)+{+structnetdev_queue*net_queue;+structccmni_instance*ccmni;++ccmni=ctlb->ccmni_inst[0];++if(netif_running(ccmni->dev)&&atomic_read(&ccmni->usage)>0){+if(ctlb->capability&NIC_CAP_CCMNI_MQ){+net_queue=netdev_get_tx_queue(ccmni->dev,qno);+if(netif_tx_queue_stopped(net_queue))+netif_tx_wake_queue(net_queue);+}elseif(netif_queue_stopped(ccmni->dev)){+netif_wake_queue(ccmni->dev);+}+}+}++staticvoidccmni_queue_tx_full_notify(structccmni_ctl_block*ctlb,intqno)+{+structnetdev_queue*net_queue;+structccmni_instance*ccmni;++ccmni=ctlb->ccmni_inst[0];++if(atomic_read(&ccmni->usage)>0){+dev_err(&ctlb->mtk_dev->pdev->dev,"TX queue %d is full\n",qno);+if(ctlb->capability&NIC_CAP_CCMNI_MQ){+net_queue=netdev_get_tx_queue(ccmni->dev,qno);+netif_tx_stop_queue(net_queue);+}else{+netif_stop_queue(ccmni->dev);+}+}+}++staticvoidccmni_queue_state_notify(structmtk_pci_dev*mtk_dev,+enumdpmaif_txq_statestate,intqno)+{+if(!(mtk_dev->ccmni_ctlb->capability&NIC_CAP_TXBUSY_STOP)||+mtk_dev->ccmni_ctlb->md_sta!=MD_STATE_READY||+qno>=TXQ_TYPE_CNT)+return;++if(!mtk_dev->ccmni_ctlb->ccmni_inst[0]){+dev_warn(&mtk_dev->pdev->dev,"No netdev registered yet\n");+return;+}++if(state==DMPAIF_TXQ_STATE_IRQ)+ccmni_queue_tx_irq_notify(mtk_dev->ccmni_ctlb,qno);+elseif(state==DMPAIF_TXQ_STATE_FULL)+ccmni_queue_tx_full_notify(mtk_dev->ccmni_ctlb,qno);+}++intccmni_init(structmtk_pci_dev*mtk_dev)+{+structccmni_ctl_block*ctlb;+intret;++ctlb=devm_kzalloc(&mtk_dev->pdev->dev,sizeof(*ctlb),GFP_KERNEL);+if(!ctlb)+return-ENOMEM;++mtk_dev->ccmni_ctlb=ctlb;+ctlb->mtk_dev=mtk_dev;+ctlb->callbacks.state_notify=ccmni_queue_state_notify;+ctlb->callbacks.recv_skb=ccmni_recv_skb;+ctlb->nic_dev_num=NIC_DEV_DEFAULT;+ctlb->capability=NIC_CAP_TXBUSY_STOP|NIC_CAP_SGIO|+NIC_CAP_DATA_ACK_DVD|NIC_CAP_CCMNI_MQ;++ctlb->hif_ctrl=dpmaif_hif_init(mtk_dev,&ctlb->callbacks);+if(!ctlb->hif_ctrl)+return-ENOMEM;++/* WWAN core will create a netdev for the default IP MUX channel */+ret=wwan_register_ops(&ctlb->mtk_dev->pdev->dev,&ccmni_wwan_ops,ctlb,+IP_MUX_SESSION_DEFAULT);+if(ret)+gotoerror_md;++init_md_status_notifier(ctlb);++return0;++error_md:+wwan_unregister_ops(&ctlb->mtk_dev->pdev->dev);++returnret;+}++voidccmni_exit(structmtk_pci_dev*mtk_dev)+{+structccmni_ctl_block*ctlb;++ctlb=mtk_dev->ccmni_ctlb;+/* unregister FSM notifier */+fsm_notifier_unregister(&ctlb->md_status_notify);+wwan_unregister_ops(&ctlb->mtk_dev->pdev->dev);+dpmaif_hif_exit(ctlb->hif_ctrl);+}
t7xx is the PCIe host device driver for Intel 5G 5000 M.2 solution which
is based on MediaTek's T700 modem to provide WWAN connectivity.
The driver uses the WWAN framework infrastructure to create the following
control ports and network interfaces:
* /dev/wwan0mbim0 - Interface conforming to the MBIM protocol.
Applications like libmbim [1] or Modem Manager [2] from v1.16 onwards
with [3][4] can use it to enable data communication towards WWAN.
* /dev/wwan0at0 - Interface that supports AT commands.
* wwan0 - Primary network interface for IP traffic.
That should be prefixed with net-next
The main blocks in t7xx driver are:
* PCIe layer - Implements probe, removal, and power management callbacks.
* Port-proxy - Provides a common interface to interact with different types
of ports such as WWAN ports.
* Modem control & status monitor - Implements the entry point for modem
initialization, reset and exit, as well as exception handling.
* CLDMA (Control Layer DMA) - Manages the HW used by the port layer to send
control messages to the modem using MediaTek's CCCI (Cross-Core
Communication Interface) protocol.
* DPMAIF (Data Plane Modem AP Interface) - Controls the HW that provides
uplink and downlink queues for the data path. The data exchange takes
place using circular buffers to share data buffer addresses and metadata
to describe the packets.
* MHCCIF (Modem Host Cross-Core Interface) - Provides interrupt channels
for bidirectional event notification such as handshake, exception, PM and
port enumeration.
The compilation of the t7xx driver is enabled by the CONFIG_MTK_T7XX config
option which depends on CONFIG_WWAN.
This driver was originally developed by MediaTek. Intel adapted t7xx to
the WWAN framework, optimized and refactored the driver source in close
collaboration with MediaTek. This will enable getting the t7xx driver on
Approved Vendor List for interested OEM's and ODM's productization plans
with Intel 5G 5000 M.2 solution.
List of contributors:
Amir Hanania [off-list ref]
Andriy Shevchenko [off-list ref]
Chandrashekar Devegowda [off-list ref]
Dinesh Sharma [off-list ref]
Eliot Lee [off-list ref]
Haijun Liu [off-list ref]
M Chetan Kumar [off-list ref]
Mika Westerberg [off-list ref]
Moises Veleta [off-list ref]
Pierre-louis Bossart [off-list ref]
Chiranjeevi Rapolu [off-list ref]
Ricardo Martinez [off-list ref]
Muralidharan Sethuraman [off-list ref]
Soumya Prakash Mishra [off-list ref]
Sreehari Kancharla [off-list ref]
Suresh Nagaraj [off-list ref]
[1] https://www.freedesktop.org/software/libmbim/
[2] https://www.freedesktop.org/software/ModemManager/
[3] https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/582
[4] https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/523
v2:
- Replace pdev->driver->name with dev_driver_string(&pdev->dev).
- Replace random_ether_addr() with eth_random_addr().
- Update kernel-doc comment for enum data_policy.
- Indicate the driver is 'Supported' instead of 'Maintained'.
- Fix the Signed-of-by and Co-developed-by tags in the patches.
- Added authors and contributors in the top comment of the src files.
Chandrashekar Devegowda (1):
net: wwan: t7xx: Add AT and MBIM WWAN ports
Haijun Lio (11):
net: wwan: t7xx: Add control DMA interface
net: wwan: t7xx: Add core components
net: wwan: t7xx: Add port proxy infrastructure
net: wwan: t7xx: Add control port
net: wwan: t7xx: Data path HW layer
net: wwan: t7xx: Add data path interface
net: wwan: t7xx: Add WWAN network interface
net: wwan: t7xx: Introduce power management support
net: wwan: t7xx: Runtime PM
net: wwan: t7xx: Device deep sleep lock/unlock
net: wwan: t7xx: Add debug and test ports
Ricardo Martinez (2):
net: wwan: Add default MTU size
net: wwan: t7xx: Add maintainers and documentation
.../networking/device_drivers/wwan/index.rst | 1 +
.../networking/device_drivers/wwan/t7xx.rst | 120 ++
MAINTAINERS | 11 +
drivers/net/wwan/Kconfig | 14 +
drivers/net/wwan/Makefile | 1 +
drivers/net/wwan/t7xx/Makefile | 24 +
drivers/net/wwan/t7xx/t7xx_cldma.c | 277 +++
drivers/net/wwan/t7xx/t7xx_cldma.h | 168 ++
drivers/net/wwan/t7xx/t7xx_common.h | 76 +
drivers/net/wwan/t7xx/t7xx_dpmaif.c | 1524 +++++++++++++++
drivers/net/wwan/t7xx/t7xx_dpmaif.h | 168 ++
drivers/net/wwan/t7xx/t7xx_hif_cldma.c | 1663 +++++++++++++++++
drivers/net/wwan/t7xx/t7xx_hif_cldma.h | 156 ++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.c | 638 +++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif.h | 279 +++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c | 1562 ++++++++++++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.h | 117 ++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c | 842 +++++++++
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.h | 82 +
drivers/net/wwan/t7xx/t7xx_mhccif.c | 124 ++
drivers/net/wwan/t7xx/t7xx_mhccif.h | 35 +
drivers/net/wwan/t7xx/t7xx_modem_ops.c | 747 ++++++++
drivers/net/wwan/t7xx/t7xx_modem_ops.h | 92 +
drivers/net/wwan/t7xx/t7xx_monitor.h | 147 ++
drivers/net/wwan/t7xx/t7xx_netdev.c | 545 ++++++
drivers/net/wwan/t7xx/t7xx_netdev.h | 63 +
drivers/net/wwan/t7xx/t7xx_pci.c | 789 ++++++++
drivers/net/wwan/t7xx/t7xx_pci.h | 121 ++
drivers/net/wwan/t7xx/t7xx_pcie_mac.c | 277 +++
drivers/net/wwan/t7xx/t7xx_pcie_mac.h | 36 +
drivers/net/wwan/t7xx/t7xx_port.h | 163 ++
drivers/net/wwan/t7xx/t7xx_port_char.c | 424 +++++
drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c | 150 ++
drivers/net/wwan/t7xx/t7xx_port_proxy.c | 829 ++++++++
drivers/net/wwan/t7xx/t7xx_port_proxy.h | 102 +
drivers/net/wwan/t7xx/t7xx_port_tty.c | 191 ++
drivers/net/wwan/t7xx/t7xx_port_wwan.c | 281 +++
drivers/net/wwan/t7xx/t7xx_reg.h | 398 ++++
drivers/net/wwan/t7xx/t7xx_skb_util.c | 362 ++++
drivers/net/wwan/t7xx/t7xx_skb_util.h | 110 ++
drivers/net/wwan/t7xx/t7xx_state_monitor.c | 627 +++++++
drivers/net/wwan/t7xx/t7xx_tty_ops.c | 205 ++
drivers/net/wwan/t7xx/t7xx_tty_ops.h | 44 +
include/linux/wwan.h | 5 +
44 files changed, 14590 insertions(+)
create mode 100644 Documentation/networking/device_drivers/wwan/t7xx.rst
create mode 100644 drivers/net/wwan/t7xx/Makefile
create mode 100644 drivers/net/wwan/t7xx/t7xx_cldma.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_cldma.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_common.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_dpmaif.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_dpmaif.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_cldma.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_cldma.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_mhccif.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_mhccif.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_modem_ops.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_modem_ops.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_monitor.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_netdev.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_netdev.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_pci.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_pci.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_pcie_mac.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_pcie_mac.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_port.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_char.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_proxy.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_proxy.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_tty.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_port_wwan.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_reg.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_skb_util.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_skb_util.h
create mode 100644 drivers/net/wwan/t7xx/t7xx_state_monitor.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_tty_ops.c
create mode 100644 drivers/net/wwan/t7xx/t7xx_tty_ops.h
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Date: 2021-11-01 14:04:15
On Sun, Oct 31, 2021 at 08:56:23PM -0700, Ricardo Martinez wrote:
From: Haijun Lio <haijun.liu@mediatek.com>
Cross Layer DMA (CLDMA) Hardware interface (HIF) enables the control
path of Host-Modem data transfers. CLDMA HIF layer provides a common
interface to the Port Layer.
CLDMA manages 8 independent RX/TX physical channels with data flow
control in HW queues. CLDMA uses ring buffers of General Packet
Descriptors (GPD) for TX/RX. GPDs can represent multiple or single
data buffers (DB).
CLDMA HIF initializes GPD rings, registers ISR handlers for CLDMA
interrupts, and initializes CLDMA HW registers.
CLDMA TX flow:
1. Port Layer write
2. Get DB address
3. Configure GPD
4. Triggering processing via HW register write
CLDMA RX flow:
1. CLDMA HW sends a RX "done" to host
2. Driver starts thread to safely read GPD
3. DB is sent to Port layer
4. Create a new buffer for GPD ring
...doesn't correlate with the above.
At least Ricardo seems to be the (co-)author of the code according to
the tag block above.
Also consider to start list(s) from the new line:
Autors:
A B <>
X Y <>
etc.
...
+#include "t7xx_cldma.h"
+
+void cldma_clear_ip_busy(struct cldma_hw_info *hw_info)
+{
+ /* write 1 to clear IP busy register wake up CPU case */
+ iowrite32(ioread32(hw_info->ap_pdn_base + REG_CLDMA_IP_BUSY) | IP_BUSY_WAKEUP,
+ hw_info->ap_pdn_base + REG_CLDMA_IP_BUSY);
Much easier to read in the standard pattern, i.e.
u32 val;
val = ioread32(hw_info->ap_pdn_base + REG_CLDMA_IP_BUSY);
val |= IP_BUSY_WAKEUP;
iowrite32(val, hw_info->ap_pdn_base + REG_CLDMA_IP_BUSY);
In one case you have a long line, in many others you have doing this.
A bit of consistency is required. If you use 100, use it everywhere,
or otherwise go for 80 everywhere (with possible exceptions as written
in the documentation).
...
+ if (queue->budget > (MAX_TX_BUDGET - 1))
if (queue->budget >= MAX_TX_BUDGET)
+ return queue->budget;
...
+ * Return: 0 on success, -ENOMEM on allocation failure, -EINVAL on invalid queue request, or
+ * -EBUSY on resource lock failure.
* Return: 0 on success,
-ENOMEM on allocation failure,
-EINVAL on invalid queue request, or
-EBUSY on resource lock failure.
?
...
+ ret = wait_event_interruptible_exclusive(queue->req_wq, queue->budget > 0);
+ if (ret == -ERESTARTSYS)
+ ret = -EINTR;
Why overriding?
...
+exit:
Seems useless.
+ return ret;
...
+/* this function contains longer duration initializations */
Useless.
You must really revisit all the comments over the driver and change them to
explain why rather than what.
Above, of course, maybe converted to kernel doc that actually describes what
the API is doing in more verbose way.
...
All of these headers are needed and used here?
Really?!
But bits.h is missed...
...
+/* cldma_ring is quite light, most of ring buffer operations require queue struct. */
+struct cldma_ring {
+ struct list_head gpd_ring; /* ring of struct cldma_request */
+ int length; /* number of struct cldma_request */
+ int pkt_size; /* size of each packet in ring */
+
+ int (*handle_tx_request)(struct cldma_queue *queue, struct cldma_request *req,
+ struct sk_buff *skb, unsigned int ioc_override);
+ int (*handle_rx_done)(struct cldma_queue *queue, int budget);
+ int (*handle_tx_done)(struct cldma_queue *queue);
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Date: 2021-11-02 16:40:26
On Sun, Oct 31, 2021 at 08:56:24PM -0700, Ricardo Martinez wrote:
From: Haijun Lio <haijun.liu@mediatek.com>
Registers the t7xx device driver with the kernel. Setup all the core
components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
modem control operations, modem state machine, and build
infrastructure.
* PCIe layer code implements driver probe and removal.
* MHCCIF provides interrupt channels to communicate events
such as handshake, PM and port enumeration.
* Modem control implements the entry point for modem init,
reset and exit.
* The modem status monitor is a state machine used by modem control
to complete initialization and stop. It is used also to propagate
exception events reported by other components.
I will assume that the comments given against previous patch will be applied
to this and the rest of the patches where it makes sense or appropriate.
Below only new comments.
...
DELAY in microseconds while msleep() takes milliseconds.
Something is wrong here.
Also, delays such as 10ms+ should be explained. Esp. when they are
in the threaded IRQ handler.
...
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Date: 2021-11-03 15:40:48
On Sun, Oct 31, 2021 at 08:56:25PM -0700, Ricardo Martinez wrote:
From: Haijun Lio <haijun.liu@mediatek.com>
Port-proxy provides a common interface to interact with different types
of ports. Ports export their configuration via `struct t7xx_port` and
operate as defined by `struct port_ops`.
Same here, assuming that the comments from the previous patches are applied
here as well, only unique are given.
...
- if (stage == HIF_EX_CLEARQ_DONE)
+ if (stage == HIF_EX_CLEARQ_DONE) {
/* give DHL time to flush data.
* this is an empirical value that assure
* that DHL have enough time to flush all the date.
*/
msleep(PORT_RESET_DELAY_US);
+ }
These curly brackets should be part of previous patch. Try to minimize
(ideally avoid) ping-pong style of changes in the same series.
...
+#define CCCI_MAX_CH_ID 0xff /* RX channel ID should NOT be >= this!! */
I haven't got the details behind the comment. Is the Rx channel ID predefined
somewhere? If so, use static_assert() instead of this comment.
...
+/* port->minor is configured in-sequence, but when we use it in code
+ * it should be unique among all ports for addressing.
+ */
+#define TTY_IPC_MINOR_BASE 100
+#define TTY_PORT_MINOR_BASE 250
+#define TTY_PORT_MINOR_INVALID -1
+ if (port->flags & PORT_F_USER_HEADER) { /* header provide by user */
Is it proper English in the comment?
+ /* CCCI_MON_CH should fall in here, as header must be
+ * send to md_init.
+ */
+ if (ccci_h->data[0] == CCCI_HEADER_NO_DATA) {
+ if (skb->len > sizeof(struct ccci_header)) {
+ dev_err_ratelimited(port->dev,
+ "recv unexpected data for %s, skb->len=%d\n",
+ port->name, skb->len);
+ skb_trim(skb, sizeof(struct ccci_header));
+ }
+ }
+ } else {
+ /* remove CCCI header */
+ skb_pull(skb, sizeof(struct ccci_header));
+ }
...
+int port_proxy_send_skb(struct t7xx_port *port, struct sk_buff *skb, bool from_pool)
+{
+ struct ccci_header *ccci_h;
+ unsigned char tx_qno;
+ int ret;
+
+ ccci_h = (struct ccci_header *)(skb->data);
+ tx_qno = port_get_queue_no(port);
+ port_proxy_set_seq_num(port, (struct ccci_header *)ccci_h);
+ ret = cldma_send_skb(port->path_id, tx_qno, skb, from_pool, true);
+ if (ret) {
+ dev_err(port->dev, "failed to send skb, error: %d\n", ret);
+ } else {
+ /* Record the port seq_num after the data is sent to HIF.
+ * Only bits 0-14 are used, thus negating overflow.
+ */
+ port->seq_nums[MTK_OUT]++;
+ }
+
+ return ret;
The density of the characters is a bit high. Why not refactor this?
...blank line here...
ret = cldma_send_skb(port->path_id, tx_qno, skb, from_pool, true);
if (ret) {
dev_err(port->dev, "failed to send skb, error: %d\n", ret);
return ret;
}
...
+}
...
+ port_list = &port_prox->rx_ch_ports[channel & CCCI_CH_ID_MASK];
+ list_for_each_entry(port, port_list, entry) {
+ if (queue->hif_id != port->path_id || channel != port->rx_ch)
+ continue;
+
+ /* Multi-cast is not supported, because one port may be freed
+ * and can modify this request before another port can process it.
+ * However we still can use req->state to achieve some kind of
+ * multi-cast if needed.
+ */
+ if (port->ops->recv_skb) {
+ seq_num = port_check_rx_seq_num(port, ccci_h);
+ ret = port->ops->recv_skb(port, skb);
+ /* If the packet is stored to RX buffer
+ * successfully or drop, the sequence
+ * num will be updated.
+ */
+ if (ret == -ENOBUFS)
Why not simply split this to
return 0;
// pay attention to the label naming
err_free_skb:
ccci_free_skb(&queue->md->mtk_dev->pools, skb);
return -ENETDOWN;
I suspect that this may be part of something bigger which is comming,
so try to minimize both weirdness here and additional shuffling
somewhere else in that case.
...
+ for_each_proxy_port(i, port, port_prox)
+ if (port->ops->md_state_notify)
+ port->ops->md_state_notify(port, state);
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Cross Layer DMA (CLDMA) Hardware interface (HIF) enables the control
path of Host-Modem data transfers. CLDMA HIF layer provides a common
interface to the Port Layer.
CLDMA manages 8 independent RX/TX physical channels with data flow
control in HW queues. CLDMA uses ring buffers of General Packet
Descriptors (GPD) for TX/RX. GPDs can represent multiple or single
data buffers (DB).
CLDMA HIF initializes GPD rings, registers ISR handlers for CLDMA
interrupts, and initializes CLDMA HW registers.
CLDMA TX flow:
1. Port Layer write
2. Get DB address
3. Configure GPD
4. Triggering processing via HW register write
CLDMA RX flow:
1. CLDMA HW sends a RX "done" to host
2. Driver starts thread to safely read GPD
3. DB is sent to Port layer
4. Create a new buffer for GPD ring
Place this pointers array to a private _device_ state structure.
Otherwise, with these global pointers, the driver will break as soon
as a second modem is connected to the host.
Also all corresponding functions should be reworked to be able to
accept a modem state container pointer.
+static DEFINE_MUTEX(ctl_cfg_mutex); /* protects CLDMA late init config */
Place this mutex to a private device state structure as well to avoid
useless inter-device locking and possible deadlocks.
+static enum cldma_queue_type rxq_type[CLDMA_RXQ_NUM];
+static enum cldma_queue_type txq_type[CLDMA_TXQ_NUM];
+static int rxq_buff_size[CLDMA_RXQ_NUM];
+static int rxq_buff_num[CLDMA_RXQ_NUM];
+static int txq_buff_num[CLDMA_TXQ_NUM];
If these arrays could be shared between modem instances (i.e. if this
is a some kind of static configuration) then initialize them
statically or in the module_init(). Otherwise, if these arrays are
part of the runtime state, then place them in a device state
container.
--
Sergey
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Registers the t7xx device driver with the kernel. Setup all the core
components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
modem control operations, modem state machine, and build
infrastructure.
* PCIe layer code implements driver probe and removal.
* MHCCIF provides interrupt channels to communicate events
such as handshake, PM and port enumeration.
* Modem control implements the entry point for modem init,
reset and exit.
* The modem status monitor is a state machine used by modem control
to complete initialization and stop. It is used also to propagate
exception events reported by other components.
Out of curiosity, why is this file called t7xx_state_monitor.c, while
the corresponding header file is called simply t7xx_monitor.h? Are any
other monitors planed?
[skipped]
...
+struct ccci_header {
+ /* do not assume data[1] is data length in rx */
+ u32 data[2];
If these fields have different meaning on Tx and Rx you could define
them using a union. E.g.
union {
struct {
__le32 idx;
__le32 type;
} rx;
struct {
__le32 idx;
__le32 len;
} tx;
};
__le32 status;
or even like this:
__le32 idx;
union {
__le32 rx_type;
__le32 tx_len;
};
__le32 status;
Such definition better documents code then the comment above the field.
+ u32 status;
+ u32 reserved;
+};
All these fields should have a type of __le32 since the structure
passed to the modem as is.
[skipped]
I am wondering what performance gains have you achieved with these skb
pools? Can we see any numbers?
I do not think the control path performance is worth the complexity of
the multilayer skb allocation. In the data packet Rx path, you need to
allocate skb anyway as soon as the driver passes them to the stack. So
what is the gain?
[skipped]
Place this pointer at least to the mtk_modem structure. Otherwise,
with this global pointer, the driver will break as soon as a second
modem will be connected to the host.
Also all functions in this file also should be reworked to be able to
accept modem state container pointer, e.g. mtk_modem or some other
related structure.
--
Sergey
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Port-proxy provides a common interface to interact with different types
of ports. Ports export their configuration via `struct t7xx_port` and
operate as defined by `struct port_ops`.
...
+struct t7xx_port {
+ /* members used for initialization, do not change the order */
As already suggested by Andy, use C99 initializers to initialize the
md_ccci_ports array and drop the above comment about the strict order
requirements.
Why did you need these two fields with the port name and minor number?
The WWAN subsystem will care about these data for you. It is its
purpose.
+ enum wwan_port_type mtk_port_type;
+
+ /* members un-initialized in definition */
+ struct wwan_port *mtk_wwan_port;
+ struct mtk_pci_dev *mtk_dev;
+ struct device *dev;
+ short seq_nums[2];
+ struct port_proxy *port_proxy;
+ atomic_t usage_cnt;
+ struct list_head entry;
+ struct list_head queue_entry;
+ unsigned int major;
+ unsigned int minor_base;
+ /* TX and RX flows are asymmetric since ports are multiplexed on
+ * queues.
+ *
+ * TX: data blocks are sent directly to a queue. Each port
+ * does not maintain a TX list; instead, they only provide
+ * a wait_queue_head for blocking writes.
+ *
+ * RX: Each port uses a RX list to hold packets,
+ * allowing the modem to dispatch RX packet as quickly as possible.
+ */
+ struct sk_buff_head rx_skb_list;
+ bool skb_from_pool;
+ spinlock_t port_update_lock; /* protects port configuration */
+ wait_queue_head_t rx_wq;
+ int rx_length_th;
+ port_skb_handler skb_handler;
+ unsigned char chan_enable;
+ unsigned char chn_crt_stat;
+ struct cdev *cdev;
+ struct task_struct *thread;
+ struct mutex tx_mutex_lock; /* protects the seq number operation */
+};
You should split the t7xx_port structure at least for two parts. A
first part with static configuration can remain in the structure and
statically initialized in the md_ccci_ports array. All non-shareable
runtime state fields (e.g. SKB lists, pointers to dynamically
allocated device instance structures) should be moved to a device
state container.
[skipped]
There is a clear statement in the include/uapi/linux/netlink.h file
that NETLINK_USERSOCK is reserved for user mode socket protocols.
Please do not abuse netlink protocol numbers.
If you really need a special Netlink interface to communicate with
userspace, consider creating a new generic netlink family. But it
looks like all the Netlink stuff here is a leftover of a debug
interface that was used at an earlier driver development stage. So I
suggest to just remove all Netlink usage here and consider using
dynamic debug logging, or switch to the kernel tracing.
...
+static struct port_proxy *port_prox;
This is another one pointer that should be placed into a device
runtime state structure to avoid driver crash with multiple modems.
...
+static struct port_ops dummy_port_ops;
Why do you need this dummy ops structure? You anyway remove it in the
next patch. If you need it as a placeholder for the md_ccci_ports
array below, then it is safe to define an empty md_ccci_ports array
and then just fill it. Please consider removing this structure to
avoid ping-pong changes.
Field endians handling is missed here. Probably you should first
convert status field data to CPU endians and only then parse it. E.g.
u32 status = le32_to_cpu(ccci_h->status);
channel = FIELD_GET(HDR_FLD_CHN, status);
seq_num = FIELD_GET(HDR_FLD_SEQ, status);
assert_bit = FIELD_GET(HDR_FLD_AST, status);
cchi_h is already of type ccci_header, no casting is required here.
...
+
+/* inject CCCI message to modem */
+void port_proxy_send_msg_to_md(int ch, unsigned int msg, unsigned int resv)
This function is not called by any code in this patch. Should the
function be moved to the "net: wwan: t7xx: Add control port" patch
along with the ctrl_msg_header structure definition?
Netlink by nature is a binary protocol. You need to emit messages of
different types per port state with a port name attribute inside. Or
emit a single type message with two separate attributes: one to carry
a port state and a separate attribute to carry a port name.
Or, as I suggested above, just drop this Netlink abuse and switch to
dynamic debug logging. Or even better, consider switching to the
kernel tracing API.
[skipped]
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Control Port implements driver control messages such as modem-host
handshaking, controls port enumeration, and handles exception messages.
The handshaking process between the driver and the modem happens during
the init sequence. The process involves the exchange of a list of
supported runtime features to make sure that modem and host are ready
to provide proper feature lists including port enumeration. Further
features can be enabled and controlled in this handshaking process.
This should be a
ft_query->head_pattern = cpu_to_le32(MD_FEATURE_QUERY_ID);
to run on the big-endians CPU. strace will notify you about each
endians mismatch as soon as you change head_pattern field type to
__le32.
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Data Path Modem AP Interface (DPMAIF) HIF layer provides methods
for initialization, ISR, control and event handling of TX/RX flows.
DPMAIF TX
Exposes the `dmpaif_tx_send_skb` function which can be used by the
network device to transmit packets.
The uplink data management uses a Descriptor Ring Buffer (DRB).
First DRB entry is a message type that will be followed by 1 or more
normal DRB entries. Message type DRB will hold the skb information
and each normal DRB entry holds a pointer to the skb payload.
DPMAIF RX
The downlink buffer management uses Buffer Address Table (BAT) and
Packet Information Table (PIT) rings.
The BAT ring holds the address of skb data buffer for the HW to use,
while the PIT contains metadata about a whole network packet including
a reference to the BAT entry holding the data buffer address.
The driver reads the PIT and BAT entries written by the modem, when
reaching a threshold, the driver will reload the PIT and BAT rings.
...
+static int dpmaif_net_rx_push_thread(void *arg)
+{
...
+ while (!kthread_should_stop()) {
+ if (skb_queue_empty(&q->skb_queue.skb_list)) {
+ if (wait_event_interruptible(q->rx_wq,
+ !skb_queue_empty(&q->skb_queue.skb_list) ||
+ kthread_should_stop()))
+ continue;
+ }
+
+ if (kthread_should_stop())
+ break;
Looks like the above check is used to recheck thread state after the
wait_event_interruptible() call, so the check could be moved to the
skb_queue_empty() code block to avoid odd thread state checks.
...
+static void dpmaif_rx_skb(struct dpmaif_rx_queue *rxq, struct dpmaif_cur_rx_skb_info *rx_skb_info)
+{
+ struct sk_buff *new_skb;
+ u32 *lhif_header;
+
+ new_skb = rx_skb_info->cur_skb;
...
+ /* MD put the ccmni_index to the msg pkt,
+ * so we need push it alone. Maybe not needed.
+ */
+ lhif_header = skb_push(new_skb, sizeof(*lhif_header));
+ *lhif_header &= ~LHIF_HEADER_NETIF;
+ *lhif_header |= FIELD_PREP(LHIF_HEADER_NETIF, rx_skb_info->cur_chn_idx);
Why is the skb data field used to carry packet control data? Consider
using the skb control buffer (i.e skb->cb) to carry control data
between the driver layers to make metadata handling less expensive and
increase driver performance.
+ /* add data to rx thread skb list */
+ ccci_skb_enqueue(&rxq->skb_queue, new_skb);
+}
...
+void dpmaif_rxq_free(struct dpmaif_rx_queue *queue)
+{
...
+ while ((skb = skb_dequeue(&queue->skb_queue.skb_list)))
+ kfree_skb(skb);
Just place control data to the skb control buffer (i.e. skb->cb) and
define this control data as a structure:
struct rx_pkt_cb {
u8 nw_type;
u8 netif;
u8 flow;
};
Place this metadata to the skb control buffer (i.e. skb->cb) to avoid
odd skb_push()/skb_pull() calls.
Also this looks like an abuse of ccci_header structure. In fact it
never passed to the modem along with a data packet, but searching
through the code show this as a structure usage place.
Please, place the packet metadata (dpmaif_tx_event data) in the skb
control buffer (i.e. skb->cb) and use skb queue API as in Rx path.
This will allow you to avoid the per-packet metadata memory allocation
and make code simple.
It is better to invert the above condition, handle TXQ full situation
as a corner case and packet queuing as a normal case. I.e. instead of:
if (have_queue_space) {
/* Enqueue packet */
return 0;
}
/* Queue full notification emitting */
return -EBUSY;
handle queuing like this:
if (unlikely(!have_queue_space)) {
/* Queue full notification emitting */
return -EBUSY;
}
/* Enqueue packet */
return 0;
This is a matter of taste, but makes code more readable.
--
Sergey
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Creates the Cross Core Modem Network Interface (CCMNI) which implements
the wwan_ops for registration with the WWAN framework, CCMNI also
implements the net_device_ops functions used by the network device.
Network device operations include open, close, start transmission, TX
timeout, change MTU, and select queue.
If the modem is a pure IP device, you do not need to forge an Ethernet
header. Moreover this does not make any sense, only odd CPU time
spending. Just set netdev->type to ARPHRD_NONE and send a pure
IPv4/IPv6 packet up to the stack.
I am wondering how much modem performance has improved with this
optimization compared to the performance loss on each packet due to
the cache miss? Do you have any measurement results?
Please do not push control data to the skb data. You anyway will
remove them during the enqueuing to HW. This approach will cause a
performance penalty. Also this looks like a ccci_header structure
abuse.
Use a dedicated structure and the skb control buffer (e.g. skb->cb) to
preserve control data while the packet stays in an intermediate queue.
+ ccmni_idx = ccmni->index;
+ ccci_h->data[0] = ccmni_idx;
+ ccci_h->data[1] = skb->len;
+ ccci_h->reserved = 0;
+
+ ctlb = ccmni->ctlb;
+ if (dpmaif_tx_send_skb(ctlb->hif_ctrl, txqt, skb)) {
+ skb_pull(skb, sizeof(struct ccci_header));
+ /* we will reserve header again in the next retry */
+ return NETDEV_TX_BUSY;
+ }
+
+ return 0;
+}
+
+static int ccmni_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ccmni_instance *ccmni;
+ struct ccmni_ctl_block *ctlb;
+ enum txq_type txqt;
+ int skb_len;
+
+ ccmni = wwan_netdev_drvpriv(dev);
Move assignment to the variable definition.
+ ctlb = ccmni->ctlb;
+ txqt = TXQ_NORMAL;
+ skb_len = skb->len;
+
+ /* If MTU changed or there is no headroom, drop the packet */
+ if (skb->len > dev->mtu || skb_headroom(skb) < sizeof(struct ccci_header)) {
+ dev_kfree_skb(skb);
+ dev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
+ if (ctlb->capability & NIC_CAP_DATA_ACK_DVD)
+ txqt = get_txq_type(skb);
+
+ if (ccmni_send_packet(ccmni, skb, txqt)) {
+ if (!(ctlb->capability & NIC_CAP_TXBUSY_STOP)) {
+ if ((ccmni->tx_busy_cnt[txqt]++) % 100 == 0)
+ netdev_notice(dev, "[TX]CCMNI:%d busy:pkt=%ld(ack=%d) cnt=%ld\n",
+ ccmni->index, dev->stats.tx_packets,
+ txqt, ccmni->tx_busy_cnt[txqt]);
You do not need this function at all. You already specify the max_mtu
value in the ccmni_wwan_setup(), so the network core code will be
happy to check a user requested MTU against max_mtu for you.
You do not need to configure HW address length as the modem is a pure
IP device. Just drop the above line or explicitly set address length
to zero.
+ /* use kernel default free_netdev() function */
+ dev->needs_free_netdev = true;
+
+ /* no need to free again because of free_netdev() */
+ dev->priv_destructor = NULL;
+ dev->type = ARPHRD_PPP;
Use ARPHRD_NONE here since the modem is a pure IP device. Or you could
use ARPHRD_RAWIP depending on how you would like to allocate the link
IPv6 address. If in doubt then ARPHRD_NONE is a good starting point.
Is this a really carefully selected queue depth limit, or just an
arbitrary value? If the last one, then feel free to use the
DEFAULT_TX_QUEUE_LEN macro.
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
Creates char and tty port infrastructure for debug and testing.
Those ports support use cases such as:
* Modem log collection
* Memory dump
* Loop-back test
* Factory tests
* Device Service Streams
A-ha. The purpose of chardev stuff in previous patches becomes much more clear.
Please do not do this in such a way. This is not an everyday usage
operation to be supported via the chardev. Also this will require an
end user to study another one bunch of custom vendor tools for quite
common development tasks.
The kernel already has a rich set of frameworks for device debugging.
If you need to update firmware use the devlink firmware updating
facility, see e.g. Intel iosm driver for reference. For memory dumping
you could utilize devlink or device coredump facilities (see ath10k
driver for reference).
For other debugging tasks I recommend you to use debugfs. The WWAN
subsystem could be extended to provide a driver with a common debugfs
infrastructure (e.g. create common directory for WWAN devices, etc.).
As for modem logs, you could pipe them to the common kernel log via
the dynamic debug logging or export them via the debugfs as well.
Loic, Johannes, do you have some other advice on how to facilitate the
modem debugging/development tasks?
--
Sergey
Hello Ricardo,
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
t7xx is the PCIe host device driver for Intel 5G 5000 M.2 solution which
is based on MediaTek's T700 modem to provide WWAN connectivity.
The driver uses the WWAN framework infrastructure to create the following
control ports and network interfaces:
* /dev/wwan0mbim0 - Interface conforming to the MBIM protocol.
Applications like libmbim [1] or Modem Manager [2] from v1.16 onwards
with [3][4] can use it to enable data communication towards WWAN.
* /dev/wwan0at0 - Interface that supports AT commands.
* wwan0 - Primary network interface for IP traffic.
The main blocks in t7xx driver are:
* PCIe layer - Implements probe, removal, and power management callbacks.
* Port-proxy - Provides a common interface to interact with different types
of ports such as WWAN ports.
* Modem control & status monitor - Implements the entry point for modem
initialization, reset and exit, as well as exception handling.
* CLDMA (Control Layer DMA) - Manages the HW used by the port layer to send
control messages to the modem using MediaTek's CCCI (Cross-Core
Communication Interface) protocol.
* DPMAIF (Data Plane Modem AP Interface) - Controls the HW that provides
uplink and downlink queues for the data path. The data exchange takes
place using circular buffers to share data buffer addresses and metadata
to describe the packets.
* MHCCIF (Modem Host Cross-Core Interface) - Provides interrupt channels
for bidirectional event notification such as handshake, exception, PM and
port enumeration.
The compilation of the t7xx driver is enabled by the CONFIG_MTK_T7XX config
option which depends on CONFIG_WWAN.
This driver was originally developed by MediaTek. Intel adapted t7xx to
the WWAN framework, optimized and refactored the driver source in close
collaboration with MediaTek. This will enable getting the t7xx driver on
Approved Vendor List for interested OEM's and ODM's productization plans
with Intel 5G 5000 M.2 solution.
Nice work! The driver generally looks good for me. But at the same
time the driver looks a bit raw, needs some style and functionality
improvements, and a lot of cleanup. Please find general thoughts below
and per-patch comments.
A one nitpick that is common for the entire series. Please consider
using a common prefix for all driver function names (e.g. t7xx_) to
make them more specific. This should improve the code readability.
Thus, any reader will know for sure that the called functions belong
to the driver, and not to a generic kernel API. E.g. use the
t7xx_cldma_hw_init() name for the CLDMA initialization function
instead of the too generic cldma_hw_init() name, etc.
Interestingly, that you are using the common 't7xx_' prefix for all
driver file names. This is Ok, but it does not add to the specifics as
all driver files are already located in a common directory with the
specific name. But function names at the same time lack a common
prefix.
Another common drawback is that the driver should break as soon as two
modems are connected simultaneously. This should happen due to the use
of multiple _global_ variables that keeps pointers to a modem runtime
state. Out of curiosity, did you test the driver with two or more
modems connected simultaneously?
Next, the driver entirely lacks the multibyte field endians handling.
Looks like it will be unable to run on a big-endians CPU. To fix this,
it is needed to find all the structures that are passed to the modem
and replace the multibyte fields of types u16/u32 with __le16/__le32.
Then examine all the field accesses and use
cpu_to_le{16,32}()/le{16,32}_to_cpu() to update/read field contents.
As soon as you change the types to __le16/__le32, sparse (a static
analyzing utility) will warn you about every unsafe field access. Just
build your kernel with make C=1.
Ricardo, please consider submitting at the next iteration a patch
series with the driver that will be cleaned from debug stuff and
questionable optimizations. Just a bare minimum functional: AT/MBIM
control ports and network interface for data communications. This will
cut the code in half. What will greatly facilitate the reviewing
process. And then extend the driver functionality with follow up
patches.
--
Sergey
Hello Ricardo,
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
...
Nice work! The driver generally looks good for me. But at the same
time the driver looks a bit raw, needs some style and functionality
improvements, and a lot of cleanup. Please find general thoughts below
and per-patch comments.
Thanks for the feedback Sergey, we will work on it.
A one nitpick that is common for the entire series. Please consider
using a common prefix for all driver function names (e.g. t7xx_) to
make them more specific. This should improve the code readability.
Thus, any reader will know for sure that the called functions belong
to the driver, and not to a generic kernel API. E.g. use the
t7xx_cldma_hw_init() name for the CLDMA initialization function
instead of the too generic cldma_hw_init() name, etc.
Does this apply to static functions as well?
Interestingly, that you are using the common 't7xx_' prefix for all
driver file names. This is Ok, but it does not add to the specifics as
all driver files are already located in a common directory with the
specific name. But function names at the same time lack a common
prefix.
Another common drawback is that the driver should break as soon as two
modems are connected simultaneously. This should happen due to the use
of multiple _global_ variables that keeps pointers to a modem runtime
state. Out of curiosity, did you test the driver with two or more
modems connected simultaneously?
We haven't tested such configurations, we are focusing on platforms with one single modem.
Next, the driver entirely lacks the multibyte field endians handling.
Looks like it will be unable to run on a big-endians CPU. To fix this,
it is needed to find all the structures that are passed to the modem
and replace the multibyte fields of types u16/u32 with __le16/__le32.
Then examine all the field accesses and use
cpu_to_le{16,32}()/le{16,32}_to_cpu() to update/read field contents.
As soon as you change the types to __le16/__le32, sparse (a static
analyzing utility) will warn you about every unsafe field access. Just
build your kernel with make C=1.
Ricardo, please consider submitting at the next iteration a patch
series with the driver that will be cleaned from debug stuff and
questionable optimizations. Just a bare minimum functional: AT/MBIM
control ports and network interface for data communications. This will
cut the code in half. What will greatly facilitate the reviewing
process. And then extend the driver functionality with follow up
patches.
--
Sergey
On Tue, Nov 9, 2021 at 8:26 AM Martinez, Ricardo wrote:
On 11/6/2021 11:10 AM, Sergey Ryazanov wrote:
quoted
A one nitpick that is common for the entire series. Please consider
using a common prefix for all driver function names (e.g. t7xx_) to
make them more specific. This should improve the code readability.
Thus, any reader will know for sure that the called functions belong
to the driver, and not to a generic kernel API. E.g. use the
t7xx_cldma_hw_init() name for the CLDMA initialization function
instead of the too generic cldma_hw_init() name, etc.
Does this apply to static functions as well?
As I wrote, this is a nitpick. As you can see in
Documentation/process/coding-style.rst, there are no general rules for
functions naming. My personal rule of thumb is that if a function
performs a very general operation (like averaging, interpolation,
etc.), then a prefix can be omitted. If a function operation is
specific for a module, then add a common module prefix to the function
name. But again, this is my personal rule.
As for the driver, it was quite difficult to read the code that calls
functions such as cldma_alloc(), cldma_init(). It was hard to figure
out whether these functions are new kernel API or they are specific to
the driver. A common way to solve such ambiguity issues is to prefix
the driver function names. But again, this was just an attempt to draw
your attention to the function naming. Feel free to name functions as
you would like, just make the code readable for developers who are not
familiar with the specific HW chip.
quoted
Another common drawback is that the driver should break as soon as two
modems are connected simultaneously. This should happen due to the use
of multiple _global_ variables that keeps pointers to a modem runtime
state. Out of curiosity, did you test the driver with two or more
modems connected simultaneously?
We haven't tested such configurations, we are focusing on platforms with one single modem.
Now you are aware of the potential kernel crash due to the global
variables misuse. Please fix it.
--
Sergey
I am just wondering, the chip does support MBIM message fragmentation,
but does not support AT commands stream (CCCI_UART2_TX) fragmentation.
Is that the correct conclusion from the code above?
BTW, you could factor out data fragmentation support to a dedicated
function to improve code readability. Something like this:
static inline bool port_is_multipacket_capable(... *port)
{
return port->tx_ch == CCCI_MBIM_TX ||
(port->tx_ch >= CCCI_DSS0_TX && port->tx_ch <= CCCI_DSS7_TX);
}
So condition become something like that:
if (count + CCCI_H_ELEN > txq_mtu &&
port_is_multipacket_capable(port))
multi_packet = DIV_ROUND_UP(count, txq_mtu - CCCI_H_ELEN);
--
Sergey
On Sun, Oct 31, 2021 at 08:56:23PM -0700, Ricardo Martinez wrote:
quoted
From: Haijun Lio <haijun.liu@mediatek.com>
Cross Layer DMA (CLDMA) Hardware interface (HIF) enables the control
path of Host-Modem data transfers. CLDMA HIF layer provides a common
interface to the Port Layer.
CLDMA manages 8 independent RX/TX physical channels with data flow
control in HW queues. CLDMA uses ring buffers of General Packet
Descriptors (GPD) for TX/RX. GPDs can represent multiple or single
data buffers (DB).
CLDMA HIF initializes GPD rings, registers ISR handlers for CLDMA
interrupts, and initializes CLDMA HW registers.
CLDMA TX flow:
1. Port Layer write
2. Get DB address
3. Configure GPD
4. Triggering processing via HW register write
CLDMA RX flow:
1. CLDMA HW sends a RX "done" to host
2. Driver starts thread to safely read GPD
3. DB is sent to Port layer
4. Create a new buffer for GPD ring
On Sun, Oct 31, 2021 at 08:56:25PM -0700, Ricardo Martinez wrote:
quoted
From: Haijun Lio <haijun.liu@mediatek.com>
Port-proxy provides a common interface to interact with different types
of ports. Ports export their configuration via `struct t7xx_port` and
operate as defined by `struct port_ops`.
Same here, assuming that the comments from the previous patches are applied
here as well, only unique are given.
Thanks for the feedback.
...
quoted
+ nlh = nlmsg_put(nl_skb, 0, 1, NLMSG_DONE, len, 0);
+ if (!nlh) {
+ dev_err(port->dev, "could not release netlink\n");
I'm wondering why you are not using net_err() / netdev_err() / netif_err()
where it's appropriate.
The original idea was to avoid mixing different types of APIs, but yes,
it makes sense to use
those APIs where it's appropriate, I'm thinking on using them at
t7xx_netdev.c where the
required net_device is available.
...
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Date: 2021-11-19 08:29:09
On Thu, Nov 18, 2021 at 10:36:32PM -0800, Martinez, Ricardo wrote:
On 11/1/2021 7:03 AM, Andy Shevchenko wrote:
quoted
On Sun, Oct 31, 2021 at 08:56:23PM -0700, Ricardo Martinez wrote:
...
quoted
quoted
+ ret = cldma_gpd_rx_from_queue(queue, budget, &over_budget);
+ if (ret == -ENODATA)
+ return 0;
+
+ if (ret)
+ return ret;
Drop redundant blank line
The style followed is to keep a blank line after 'if' blocks.
Is that acceptable as long as it is consistent across the driver?
The idea behind suggestion is that you check for value returned from the call.
So, both if:s are tighten to that call and can be considered as a whole.
It doesn't mean you should blindly remove blank lines everywhere.
...
quoted
quoted
+exit:
Seems useless.
This tag is used when the PM patch is introduced later in the same series.
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
quoted
Port-proxy provides a common interface to interact with different types
of ports. Ports export their configuration via `struct t7xx_port` and
operate as defined by `struct port_ops`.
Why did you need these two fields with the port name and minor number?
The WWAN subsystem will care about these data for you. It is its
purpose.
This port proxy structure can abstract different types of ports. 'minor'
field is used for
tty and char ports but it will be removed since the next iteration will
contain only
WWAN ports and one control port.
'name' is currently used when printing error logs, in the future it can
be used
to define the name in the file system for test and debug ports.
[skipped]
quoted
+static int proxy_register_char_dev(void)
+{
+ dev_t dev = 0;
+ int ret;
+
+ if (port_prox->major) {
+ dev = MKDEV(port_prox->major, port_prox->minor_base);
+ ret = register_chrdev_region(dev, TTY_IPC_MINOR_BASE, MTK_DEV_NAME);
+ } else {
+ ret = alloc_chrdev_region(&dev, port_prox->minor_base,
+ TTY_IPC_MINOR_BASE, MTK_DEV_NAME);
+ if (ret)
+ dev_err(port_prox->dev, "failed to alloc chrdev region, ret=%d\n", ret);
+
+ port_prox->major = MAJOR(dev);
+ }
For what do you need these character devices? The WWAN subsystem
already handle all these tasks.
This infrastructure is not going to be included in the next iteration,
it is used for debug and test ports.
quoted
+ return ret;
+}
...
+static int proxy_alloc(struct mtk_modem *md)
+{
+ int ret;
+
+ port_prox = devm_kzalloc(&md->mtk_dev->pdev->dev, sizeof(*port_prox), GFP_KERNEL);
+ if (!port_prox)
+ return -ENOMEM;
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
quoted
Creates the Cross Core Modem Network Interface (CCMNI) which implements
the wwan_ops for registration with the WWAN framework, CCMNI also
implements the net_device_ops functions used by the network device.
Network device operations include open, close, start transmission, TX
timeout, change MTU, and select queue.
I am wondering how much modem performance has improved with this
optimization compared to the performance loss on each packet due to
the cache miss? Do you have any measurement results?
No performance gains observed in the latest tests, this is going to be
removed for the
next iteration.
Is this a really carefully selected queue depth limit, or just an
arbitrary value? If the last one, then feel free to use the
DEFAULT_TX_QUEUE_LEN macro.
Changing this to DEFAULT_TX_QUEUE_LEN for the next iteration
I am just wondering, the chip does support MBIM message fragmentation,
but does not support AT commands stream (CCCI_UART2_TX) fragmentation.
Is that the correct conclusion from the code above?
Yes, that is correct.
BTW, you could factor out data fragmentation support to a dedicated
function to improve code readability. Something like this:
static inline bool port_is_multipacket_capable(... *port)
{
return port->tx_ch == CCCI_MBIM_TX ||
(port->tx_ch >= CCCI_DSS0_TX && port->tx_ch <= CCCI_DSS7_TX);
}
So condition become something like that:
if (count + CCCI_H_ELEN > txq_mtu &&
port_is_multipacket_capable(port))
multi_packet = DIV_ROUND_UP(count, txq_mtu - CCCI_H_ELEN);
I am just wondering, the chip does support MBIM message fragmentation,
but does not support AT commands stream (CCCI_UART2_TX) fragmentation.
Is that the correct conclusion from the code above?
Yes, that is correct.
Are you sure that the modem does not support AT command fragmentation?
The AT commands interface is a stream of chars by its nature. It is
designed to work over serial lines. Some modem configuration software
even writes commands to a port in a char-by-char manner, i.e. it
writes no more than one char at a time to the port.
The mechanism that is implemented in the driver to split user input
into individual messages is not a true fragmentation mechanism since
it does not preserve the original user input length. It just cuts the
user input into individual messages and sends them to the modem
independently. So, the modem firmware has no way to distinguish
whether the user input has been "fragmented" by the user or the
driver. How, then, does the modem firmware deal with an AT command
"fragmented" by a user? Will the modem firmware ignore the AT command
that is received in the char-by-char manner?
--
Sergey
Just curious why the _VERSION suffix? Why not, for example, PKT_TYPE_ prefix?
Nothing special about _VERSION, but it does look a bit weird, will use
PKT_TYPE_ as suggested
I checked the driver code again and found that these constants are
really used to distinguish between IPv4 and IPv6 packets by checking
the first byte of the data packet (IP header version field).
Now I am wondering, does the modem firmware report a packet type in
one of the BAT or PIT headers? If the modem is already reporting a
packet type, then it is better to use the provided information instead
of touching the packet data. Otherwise, if the modem does not
explicitly report a packet type, and you have to check the version
field of the IP header, then it seems Ok to keep the names of these
constants as they are (with the _VERSION suffix).
--
Sergey
Just curious why the _VERSION suffix? Why not, for example, PKT_TYPE_ prefix?
Nothing special about _VERSION, but it does look a bit weird, will use
PKT_TYPE_ as suggested
I checked the driver code again and found that these constants are
really used to distinguish between IPv4 and IPv6 packets by checking
the first byte of the data packet (IP header version field).
Now I am wondering, does the modem firmware report a packet type in
one of the BAT or PIT headers? If the modem is already reporting a
packet type, then it is better to use the provided information instead
of touching the packet data. Otherwise, if the modem does not
explicitly report a packet type, and you have to check the version
field of the IP header, then it seems Ok to keep the names of these
constants as they are (with the _VERSION suffix).
Actually, there is a bit in the PIT header for the packet type, I'll
make the driver
use it instead of looking at the data packet.
On Mon, Nov 1, 2021 at 6:57 AM Ricardo Martinez
[off-list ref] wrote:
quoted
Registers the t7xx device driver with the kernel. Setup all the core
components: PCIe layer, Modem Host Cross Core Interface (MHCCIF),
modem control operations, modem state machine, and build
infrastructure.
* PCIe layer code implements driver probe and removal.
* MHCCIF provides interrupt channels to communicate events
such as handshake, PM and port enumeration.
* Modem control implements the entry point for modem init,
reset and exit.
* The modem status monitor is a state machine used by modem control
to complete initialization and stop. It is used also to propagate
exception events reported by other components.
Out of curiosity, why is this file called t7xx_state_monitor.c, while
the corresponding header file is called simply t7xx_monitor.h? Are any
other monitors planed?
[skipped]
No other monitors, I'll rename it to make it consistent.
[skipped]
I am wondering what performance gains have you achieved with these skb
pools? Can we see any numbers?
I do not think the control path performance is worth the complexity of
the multilayer skb allocation. In the data packet Rx path, you need to
allocate skb anyway as soon as the driver passes them to the stack. So
what is the gain?
[skipped]
Agree, we are removing the skb pools for the control path.
Regarding Rx data path, we'll get some numbers to see if the pool is
worth it,
otherwise remove it too.
[skipped]
I am just wondering, the chip does support MBIM message fragmentation,
but does not support AT commands stream (CCCI_UART2_TX) fragmentation.
Is that the correct conclusion from the code above?
Yes, that is correct.
Are you sure that the modem does not support AT command fragmentation?
The AT commands interface is a stream of chars by its nature. It is
designed to work over serial lines. Some modem configuration software
even writes commands to a port in a char-by-char manner, i.e. it
writes no more than one char at a time to the port.
The mechanism that is implemented in the driver to split user input
into individual messages is not a true fragmentation mechanism since
it does not preserve the original user input length. It just cuts the
user input into individual messages and sends them to the modem
independently. So, the modem firmware has no way to distinguish
whether the user input has been "fragmented" by the user or the
driver. How, then, does the modem firmware deal with an AT command
"fragmented" by a user? Will the modem firmware ignore the AT command
that is received in the char-by-char manner?
The modem supports char-by-char AT command over serial lines, I'll get
more information
about why splitting long commands is supported only for MBIM and not for
AT commands.
I am just wondering, the chip does support MBIM message fragmentation,
but does not support AT commands stream (CCCI_UART2_TX) fragmentation.
Is that the correct conclusion from the code above?
Yes, that is correct.
Are you sure that the modem does not support AT command fragmentation?
The AT commands interface is a stream of chars by its nature. It is
designed to work over serial lines. Some modem configuration software
even writes commands to a port in a char-by-char manner, i.e. it
writes no more than one char at a time to the port.
The mechanism that is implemented in the driver to split user input
into individual messages is not a true fragmentation mechanism since
it does not preserve the original user input length. It just cuts the
user input into individual messages and sends them to the modem
independently. So, the modem firmware has no way to distinguish
whether the user input has been "fragmented" by the user or the
driver. How, then, does the modem firmware deal with an AT command
"fragmented" by a user? Will the modem firmware ignore the AT command
that is received in the char-by-char manner?
The modem supports char-by-char AT command over serial lines, I'll get
more information
about why splitting long commands is supported only for MBIM and not
for AT commands.
The next version will allow AT command fragmentation as there is modem
is able to handle
fragments as you pointed out. This will make the WWAN Tx callback a bit
simpler.