From 8fe9b368ea84b412966e44002bf9f6eafedf2cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacques=20von=20H=C3=A4msterviel?= <59958130+JacquesVonHamsterviel@users.noreply.github.com> Date: Thu, 10 Nov 2022 07:30:49 +0000 Subject: [PATCH] Update Linux Driver --- drivers/CH341SER_LINUX.zip | Bin 10216 -> 0 bytes drivers/CH341SER_LINUX/Makefile | 15 - drivers/CH341SER_LINUX/README.md | 22 + drivers/CH341SER_LINUX/ch34x.c | 1338 ------------------ drivers/CH341SER_LINUX/driver/Makefile | 21 + drivers/CH341SER_LINUX/driver/ch341.c | 1710 ++++++++++++++++++++++++ drivers/CH341SER_LINUX/driver/ch341.h | 161 +++ drivers/CH341SER_LINUX/readme.txt | 19 - 8 files changed, 1914 insertions(+), 1372 deletions(-) delete mode 100644 drivers/CH341SER_LINUX.zip delete mode 100755 drivers/CH341SER_LINUX/Makefile create mode 100644 drivers/CH341SER_LINUX/README.md delete mode 100755 drivers/CH341SER_LINUX/ch34x.c create mode 100644 drivers/CH341SER_LINUX/driver/Makefile create mode 100644 drivers/CH341SER_LINUX/driver/ch341.c create mode 100644 drivers/CH341SER_LINUX/driver/ch341.h delete mode 100755 drivers/CH341SER_LINUX/readme.txt diff --git a/drivers/CH341SER_LINUX.zip b/drivers/CH341SER_LINUX.zip deleted file mode 100644 index 1edb402045f537d14b171270518af3c521857a60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10216 zcmd6tRa9MDwyv>-ySqbhcZY?0aCevBPH=bk;2Io)ySuxS;K4(1yGiZ4tEk$i_IbGv zch0tKwl%)iM;~1t{;ntk4gm}D`@)tBl=#P&|NDjnf(!zbWMyMk7E{ufm6BK0U{uwB z2Z3ei^+7MT@0t=HnU?2z5juxwh-< z*;0#4N#g(%bO155AMt`sU8UON{r<64eM@IQ5r%mMp6t2E`2(d{#bPDvjngdwxL;)@ zLeS{80_{t^S3}>j9KM2E#>Cp%+JyEL(Wd(x-jXXvXpbmuCmTy>-t7#+3KjIq-T-JM zIA%+pd2IUW@*K8O+j(}^ofzh8?V4%iYl;NqXno|0Gj0$wp4ZC>+T(UeC$E{G&MrV; zZPmM>zR;>Ih&io3+R%Of3ZIyU=r;;L{OXK`8>B-d)>Lz#l^#0 z>4RjMeXjO%7gxomW}1LA_ttljmR;7sZnNg5oab@eDlSlA?WZ5b_l0xatxIuDbgor= zaQ-0#py2S>g*W4*Ozhx|s-7qQ76!RCVzk-&Gm8^BC%k-=sj+m3Nt+YQ1KHE*?A?GY z&lfIipXsX;i`lG|zC1j!v>BU>g`{y=wImsMNwo`w!Bm2qSOc5NzD=tbRIG*YX+T zInN=`bA{5#y*|1gt31&1(Vp@RzZVDJ*r|^!m?Nsp;gtxKKJ|#Ktas~Lm{5XBwX2LQ z*z!!4Zfm&i9mdbfDgjUtg}oALVDl;fOaU{WavmiDv)S*i+e0@zXIQQtJ0|nS&l*2_ z+$Y1Dyf=^F3~W(qBr=F zST^_tkCAw!d>hUTCjArYi}sw8vkss$6<)!;_G>y<{nGe8jr9o_-89Y-fb5^9PBrP- zHZ)@Vzy%=1Zss`qlp)(tb9h3f3Tmm;JUmCG5YZ!GQOM4ZhPO^GG_be(C*f8Npz(fS z;%(ntUpKjy-qT+gW&8Pb2aH~1jRdJ_csGftjW)2d+V#``SzobJFqdu0zI?j{{1^ha zz~wl`u>;|&o*-y3kGG(8{gYYPh#UWVo8oQu#-_V*Dkz`n5nUl{x(0Z8e6&DpCoND4@q!R}cqhITAy7%-q_`S#ZXs|_nQwPiuF+o%*@<`d z7{f*DTZ!+M@ql?{{C4dhz6cZbSneR;5D$>l@GovA*NB}NR(q`<-C9u8z8u%Nt<-P& zXH{*yXl|*Jtgq_9uLeXbYPD(bXCWAif2pf7BfD{HV7VeYUv(q+3}UIa`@DCiM%#zS zYjT_h^4f4<&AFIe!s_11*#mAU*b6a0iAvEML3`H#hZ=bd1y4k>B~)HFnF$d^)0BtR zwn*1r$G4Rs7M9+B2V3ZeAwm`>sr&4-KdA$UVP!GuFjQ+Rmp|RhSJ9*ks@vBP;!hOX zqah`nFhQCP!gJIQM+(=#Jx%Y2EkDbfUoM3C6JyLl&=a`yBP#+5VO5=p(dQLh5<&L` zj~zWt`#iR_BO_0rjRjWh(+210yyd}si}{4vlpC_x_R82!wx0n457bZxF6pWPs^E64 zsrwI8W7<8bhJ*FhZ8A#2(yh!+eg|5NG*>U1=(;74u?b6T9f}c+oMT%|3ggZlF(#T$ z-Y8bnSvtBj=O8SZY^HY^V#;Y0=?|m?kwG8WGIw)(=4yk8G$sk0d+>Tozs>K&jbw+@ zd0|3<;ZL<~shW4}>W>kE8yhP@`EJwItW3j&)-#XeP%(iy<^dq~(g?(QrUO|JGn*5M z+gXP{$uGJTfMRRAoPBE#cKLRG;%#(1M~m&2ax4Izl4GDb zPV&ZnmK*Z4dj~`qtZ8p-S^gzWA$1|Doa~xI6@!ULCQT|$1aRm)F_WcPLurZtpGid_ z>a^s0@No)Sux;BDGTDIcoLhH143%z8t#O_ANvO(W=@L(`)07_QQzN^@0)4OvFiahot$(mNqjOzY?f zP1E7mv=c^Hs2B|Dl9~h(&QaJb*2@W zd>zz1*h-_mr{9x<@h^DgTnU}lfky}fK>EN;=qkz8Yak_v=<`G5X>M@hu~IhST$HUD=m#{9nU2y@IF9!ed}%(bXar;YTngZ zc93lHjzl}!B)m-F;Irg)#m@+5C}wslbBTNhp=Oii-$-h;$ih@)ax@O7e1Oi!%~VLR zl^<>SIE9BfN3dzYF12|Gc{160D5c63pER}kL`-YhF0!a0Wtp2I#&mK&rThz{r6wuuiSZ%nnN zb0dbYZ;cjmHd+nC+0Np5ePML zoQVQ&Hx4tND*l{ zhDb-U={jS$+-cMAp7a1!n)lMl*w+GKV85>isanYSquEBJ?USerROgm%wp;Aw{ar=m ztbF-*G36yOL0UETA7HH2*ruxj!U&mROjl?uLAm@8h9nf9llMIvdKXtf_yLk1_r<#T zC5`-Wp}L21)tsz}D35fRfRI_u0CQJ!x zoN7qrLOoqg`=Qk&Z}TnW%RmB+6NdfTkAYaU-(Fb!k~DKgb`tYeOaudJ zft%XcypG7=cI%#(7sZ1zebTD~zZxV`9q%l*lHQqp2Wvc0&aLFR$XU6L|MQh0C;cN$ zlMQdv$?GwhC3Ty9z{090d++Tu1>tQAny*zL zp-)MH=jPF^Jd#BK_K10CXg-uDmK8P^SU$XFfb7qTx(pF>XOkMlZql?Sw&}!Bs<#InW8&jiF;1xU zH@}9ewANG4=Pv5u2V>C0J-hQlnxYNwyu9 zHY2y&>I4=^#qFmt1%Xk3jMr)^nSfM#NB7DrrK>ZO+BZIrIx|Qri zSHhs=rnk^fG$P}h8d`_U1?SmN7y!QK#D{M*ZqiqltJ?|+yaU>`1;U-}(C@$d@iiA{ ze0;rV?P&8w`xKsWB>3PkHE|P>7b63ehs1CYNswuMnyBEWXp~-5!VNd~r{lBXc5_6j zaDgfY=jT)d?y-9h@E3onlR z(!zq`q9hX|zlzM!2WaaM?Sr=0*+>^yey`ZXhIRlfI(>ru*eKDFYvls+cV%{~rpcE; zgqX?JcjQTKe&Z*sRv_thR7Iwm?!&a15F{mlHYZp@z0ykk-9`rR83n#-9FNcMgz_J5uXzC(!$2os#b=n^Q3U@?q61xbP#jx-y+m(R*z~DkCA7$K9`0 zTNy#N9IDU+LwQuFNMkY&*Dp=v@GHb=xPiF2pNV?dHsT^bCo80r!gLv8h12~x)*|g84?daW>vyR zMTo50+H}Avqm%pc;&e-S4t1U&_J=?eWU`QA5M#VX5j{Q}f?mV-VS8}(98Af&%J3yp zgxQ37M;7erFNxN>j4xqzu?b)h6lT0L(~RaTvSVv^xYS9ZNU%a3X@$_S8lJw?6l!?c zJED#v#DLq7x|pHor7B%dk^r^sWP_aP3LG#6YY$s(!HuTks!IsxOCl4WPEi4i>k+me zcT$;QPq&NETZRG55B`C zvy87ppDeC*V~4a3EP>4jE6dN+Jb`EuP^2}fzo7X%>vIQX$2UlIhifhX6O{iA2wO5j z9fCQNWrL$V6)L4kU!_ar13%!{tB#;UQK&0X7%0{90I5o`L7;e>%Awp;Pi*DGy&y=x zNG5Pp*}uEn2G^?zmE_WEFsir)^o6r0vpmNM62-|@##EW8ir7vpV(ET^M#xW0UpT+OzGtLLu8inY0; z1n(VaVevUUsbD;xCvzwUmkW?mi<8t8QbLIvJ99yXa)wOIG;vxMtCE16ny%Sr)GWV@ zf!UjJmptxyWt~kmBfqSvk23x|jiSG89<$%pdd=#J9ai~BHT>m%==mCsCw>MwN{`R8 z3%287Mq2NgV){8@lX`AOf47|SybE{rMy{nWqWwjG@<;(nc>_DDaehj#2J6v7!DZrL zUJTC8L6<5SA3!omJ;QPaW}dlNK4qy>IjOB*lh@Yk3WeA$GN-h%zEIFM^2iEiM1mMXR-u7S1c=ndUo*GS$lF0KQ+IzTEe_WN}_wN zS7y0*KL)mzNo&>eX%50(Nci~;YDuueF$A|hDb^G*bEZQVjT0YsrpBF3gmfDUMC52K!j0wFbvGCPyVe#ilE4!(oMF>!b5v|BOagks0?8Y)QX5qKF_bU~Q@XS{}rtCU{BX~LL4RfJ0 zHeosOwL4fm&OnnRZ)Fu?8|ocealH6t!+6jJJ5pv6Df@Eu0t&a^X{D%WrEHdzf7X_F zMoZt8TL2czvYoVuFj9=h$sLV8A1#{x%8-8+^9<{-Y*Le;nG~J z2f8M#sKyA1S1rSeh?La5Hb$j}cGymSggd`#!}5LZ!(lYmX93eS;{byk(z)x z%|;I^ifKFuTad+V4Hj7WR7T1mwJT&<+H@qp8OkJL}Rr;~DN zk=O$pXz=5%_FM2%brYvu=(xy+F^c2fFji;Xd)?#3OP8~}3bjk{X zRSuN@B|WCQ58I2_NEnpk0L!CGT65_PtfhYC@d3k@;8@hGqy78lSc&QzBx z;Hl79a=MMu(!%6y@C9AqSD=V_136`2#(&a&6wny)6>uyIxkpz7c1&p8pBJX%@n^9&#LkOez*SqjpYmb)w8*u z!B8KwF==@rSsgd!4pey9m2lEOG09x;xm$dK!iJH+vD*rkcebmT*9(L=yO!-**a=YmY{(e*%? z4UL`V<7x-Gdtv@OzePU~{AqOb!k?_HXL30-iyPBuxmtXH#1Pfqa-h|Wy#G*59fV4( z0o16!G;to~`ih}F6w4CfR!P+*AH!T2_((1Okt*~w&czY`!#-ulBuIWt3YBOuK0XZz zCovKCnR|SIF1Cw~beCFzbz+;jXji|c@A{SH>9iq?r*eR6RhT2E=SKb9oX>ea=d#qC z{FGg zN4+0f8(4i`&A=In?#B?oHxa7|TY*?MeC7}Xbu7T%N=rc{C#oJI)i#6UAhY* z5~O0Qr%ta!y5KJ7&`mxsH|;@3hd*kra?G&W>C^gH=OW6p)=K2nx-ojhr1mEF<{3z`sRcR@4!M;>i z9)I7GM1&SX2E3zJC>#EcMKHSPK<4{o(_$9PDI1ZA^hwp1Dl%IMX`ohit$~$Idud7+ zbrA!`E~ro>1+V85CPmx>XX80^s}df97F6z~T%yYN58nm9h)m1Au+WOYYE^)Ae{zp^ zV+p=Dw3{4Th>+bd4(zF3Jkop((r41v11`Gkv@5n&AV$Bh4rYwWi8l3vJ&We`UQGtvPaNa0j;QDNlx;p}KG z%cJ&Z*)GwY$vdqu4G@Qx=4HqNTc|4F9@}kHL-Iwbhfu`l@Q! zuf6ja;@M*baJl*CL7KTg(UekcB{ZWdD3Jw}6xSb$3n$}brE1UfjjY({m3|IK%AnV8 zptmy_(Y`aX<1=%AaOajF$ywT!Ebxd#?hg*Rb`E+!qER?NSJIz$GfavgPQ^5Bzzq;r z8B;Y-g0l8c@4cOWeLh8Z!K)7fT*_fXzxtJkVZ-W=to5oM-uvqUI>z&c{dApuy%;vI) zJ1ao1g|4V!jvlG{r{r-SrK*}J`XsbP;07-!TH`{|knDw-+>IRcGr|DF&`(Up~d+r``lfCE)B^)CnGlZh-Ffo z5F(bK%`~?v6)_&o(tZovW{z%`V2VYrsTD$aJ z8BfFAVBW(wP4>Ehe4&`21*T%~1!P!z8bKF`$q3xIVR&5;%Ja#+CXJ=E5OW{*dcN%( zaKFNoO;Jpal5wMD-08ihR@$ehW5ZYIx{6;am-+%((l$ta2f^7lhjs2!te?I^7l;#N z6%IJPEar2C7U)clOeoExkGW|6Qr1x~p3ct`vtwWVy*Um(M;hhnfjQ~U9zRP;%uX-T z!x4lV-E2=Kn=8ODQ1LI*B79)D2yl3gAmb#eoDJlFITYt7{E`C8dlELBKcg(3z~_7P zIK$ZbTyO=NUo3ms>h14&;COLXk+XYQ!ghOCy2jgaxUlHMC#V;=`wCE$fr9=Dv4~Gc z00JT&0rC(1^$+nD#obNnt#}LbOMlhZmlFmmDE~{g{Vm=8DckKYjuNonc{>8YcNrFI4ehBmfVhBj7NR@oK=S!pR6 zNtU)J-;K5{OtveIznvI;w*IzjQ)+!=WRh=fuzhG@@!@3myV3smZn&M2R$#7vygg9Jj_g*4u|4Ag#F) zPmN=by=*FRw6L5TlUD1bq3ST})njVHU3g85+4;IYtaPXyFzN;tOPP9U0pF`_n7y;M zA-v#;uD9-w;cZUTXVOy2-ZgBEQfV8L=UYVu!KUkPIh6S~%B7ZZ5G=;-{4KTH`w3V$B0 z?XyP*MO-}r(e>MbG|jD|C%>F01NwIkB>mek`ES1b|2|H1{$msZ=dW?%XliI;Ys%p4 z?)+z@*hl{{4dD_$CjTo^#Fj4Ab;1$oBqfzv4;!KVRY=JDp|~cv7HZSb0@9SrV=JI#f|E4(LkTI!|$?cqAekCRg9myKzQd=`dRfAjsFPQ(dh3 z!Zv+bxhaQ4N*lqN$$alWd=}L{?cJaK~) z|1<9XoaFg`zB>@WVo$hbxLEq)U=;&oj5y?EE5js1aVw?#WJqfYgE(a?BxFoX$o`;w z6@%F%{6Fe$H}X7;|3B+4C>RF#f16B#|MjnbTtcV9zd!$dbHC=4p#M3iq$u+n^sh$s zwtr%RyaD<1{}uGlUiAm>_sr5Cy#Kj9Y%_@+1i=YIb{|89YAYdh8(^r=5F(O(YYUq$>6;O~a` z_CS6E-`Yz023Y8CNc?xU{%`ExiT}1F;Jjg<`b(1h#n`vG|A)E1tMXfOe -#ifndef KERNEL_VERSION -#define KERNEL_VERSION(ver, rel, seq) ((ver << 16) | (rel << 8) | (seq)) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -//#include -#include -#include - -#define DRIVER_DESC "WCH CH34x USB to serial adaptor driver" -#define DRIVER_AUTHOR "" - -#define CH34x_VENDOR_ID 0x1A86 -#define CH340_PRODUCT_ID 0x7523 -#define CH341_PRODUCT_ID 0x5523 - -#define CH34x_CLOSING_WAIT (30 * HZ) - -#define CH34x_BUF_SIZE 1024 -#define CH34x_TMP_BUF_SIZE 1024 - -//Vendor define -#define VENDOR_WRITE_TYPE 0x40 -#define VENDOR_READ_TYPE 0xC0 - -#define VENDOR_READ 0x95 -#define VENDOR_WRITE 0x9A -#define VENDOR_SERIAL_INIT 0xA1 -#define VENDOR_MODEM_OUT 0xA4 -#define VENDOR_VERSION 0x5F - -//For CMD 0xA4 -#define UART_CTS 0x01 -#define UART_DSR 0x02 -#define UART_RING 0x04 -#define UART_DCD 0x08 -#define CONTROL_OUT 0x10 -#define CONTROL_DTR 0x20 -#define CONTROL_RTS 0x40 - -//Uart state -#define UART_STATE 0x00 -#define UART_OVERRUN_ERROR 0x01 -#define UART_BREAK_ERROR //no define -#define UART_PARITY_ERROR 0x02 -#define UART_FRAME_ERROR 0x06 -#define UART_RECV_ERROR 0x02 -#define UART_STATE_TRANSIENT_MASK 0x07 - -//Port state -#define PORTA_STATE 0x01 -#define PORTB_STATE 0x02 -#define PORTC_STATE 0x03 - -//CH34x Baud Rate -#define CH34x_BAUDRATE_FACTOR 1532620800 -#define CH34x_BAUDRATE_DIVMAX 3 - -//#define DEBUG_CH34x -#undef DEBUG_CH34x - -#ifdef DEBUG_CH34x -#define dbg_ch34x( format, arg... ) \ - printk( KERN_DEBUG "%d: " format "\n", __LINE__, ##arg ) -#else -#define dbg_ch34x( format, arg... ) \ -do{ \ - if(0) \ - printk(KERN_DEBUG "%d: " format "\n", __LINE__, ##arg); \ -} while (0) -#endif - -#ifdef DEBUG_CH34x -#define err_ch34x( format, arg... ) \ - printk(KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg) -#else -#define err_ch34x( format, arg... ) \ -do{ \ - if(0) \ - printk( KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg)\ -}while(0) -#endif - -// For debug -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,7,1)) -static int debug = 1; -#endif - -static DECLARE_WAIT_QUEUE_HEAD(wq); -static int wait_flag = 0; - -struct ch34x_buf { - unsigned int buf_size; - char *buf_buf; - char *buf_get; - char *buf_put; -}; - -struct ch34x_private { - spinlock_t lock; //access lock - struct ch34x_buf *buf; - int write_urb_in_use; - unsigned baud_rate; - wait_queue_head_t delta_msr_wait; - u8 line_control; - u8 line_status; - u8 termios_initialized; -}; - -static struct usb_device_id id_table [] = { - { USB_DEVICE(CH34x_VENDOR_ID, CH340_PRODUCT_ID) }, - { USB_DEVICE(CH34x_VENDOR_ID, CH341_PRODUCT_ID) }, - { } //End -}; -MODULE_DEVICE_TABLE( usb, id_table ); - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,2)) -static struct usb_driver ch34x_driver = { - .name = "ch34x", - .probe = usb_serial_probe, - .disconnect = usb_serial_disconnect, - .id_table = id_table, - .suspend = usb_serial_suspend, - .resume = usb_serial_resume, - .no_dynamic_id = 1, - .supports_autosuspend = 1, -}; -#endif - -// ch34x_buf_alloc -// Allocate a circular buffer and all associated memory -static struct ch34x_buf *ch34x_buf_alloc( unsigned int size ) -{ - struct ch34x_buf *pb; - - if( size == 0 ) - return NULL; - - pb = kmalloc( sizeof(struct ch34x_buf), GFP_KERNEL ); - if( pb == NULL ) - return NULL; - - pb->buf_buf = kmalloc( size, GFP_KERNEL ); - if( pb->buf_buf == NULL ) { - kfree(pb); - return NULL; - } - - pb->buf_size = size; - pb->buf_get = pb->buf_put = pb->buf_buf; - - return pb; -} - -// ch34x_buf_free -// Free the buffer and all associated memory -static void ch34x_buf_free( struct ch34x_buf *pb ) -{ - if( pb ) { - kfree( pb->buf_buf ); - kfree( pb ); - } -} - -// ch34x_buf_clear -// Clear out all data in the circular buffer -static void ch34x_buf_clear( struct ch34x_buf *pb ) -{ - if( pb != NULL ) - pb->buf_get = pb->buf_put; - // equivalent to a get of all data available -} - -// ch34x_buf_data_avail -// Return the number of bytes of data available in he circular buffer -static unsigned int ch34x_buf_data_avail( struct ch34x_buf *pb ) -{ - if( pb == NULL ) - return 0; - - return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size ); -} - -// ch34x_buf_space_avail -// Return the number of bytes of space available in the circular -static unsigned int ch34x_buf_space_avail( struct ch34x_buf *pb ) -{ - if( pb == NULL ) - return 0; - - return ((pb->buf_size + pb->buf_get - pb->buf_put - 1) % pb->buf_size ); -} - -// ch34x_buf_put -// Copy data from a user buffer and put it into the circular buffer. -// Restrict to the amount of space available -// Return the number of bytes copied -static unsigned int ch34x_buf_put( struct ch34x_buf *pb, - const char *buf, unsigned int count ) -{ - unsigned int len; - - if( pb == NULL ) - return 0; - - len = ch34x_buf_space_avail(pb); - if( count > len ) - count = len; - else if( count == 0 ) - return 0; - - len = pb->buf_buf + pb->buf_size - pb->buf_put; - if( count > len ) { - memcpy( pb->buf_put, buf, len ); - memcpy( pb->buf_buf, buf+len, count - len ); - pb->buf_put = pb->buf_buf + count - len; - } - else { - memcpy( pb->buf_put, buf, count ); - if( count < len ) - pb->buf_put += count; - else if( count == len ) - pb->buf_put = pb->buf_buf; - } - - return count; -} - -static unsigned int ch34x_buf_get( struct ch34x_buf *pb, - char *buf, unsigned int count ) -{ - unsigned int len; - - if( pb == NULL ) - return 0; - - len = ch34x_buf_data_avail(pb); - if( count > len ) - count = len; - else if( count == 0 ) - return 0; - - len = pb->buf_buf + pb->buf_size - pb->buf_get; - if( count > len ) { - memcpy( buf, pb->buf_get, len ); - memcpy( buf+len, pb->buf_buf, count - len ); - pb->buf_get = pb->buf_buf + count - len; - } - else { - memcpy( buf, pb->buf_get, count ); - if( count < len ) - pb->buf_get += count; - else if( count == len ) - pb->buf_get = pb->buf_buf; - } - - return count; -} - -static int ch34x_vendor_read( __u8 request, - __u16 value, - __u16 index, - struct usb_serial *serial, - unsigned char *buf, - __u16 len ) -{ - int retval; - - retval = usb_control_msg( serial->dev, usb_rcvctrlpipe(serial->dev, 0), - request, VENDOR_READ_TYPE, value, index, buf, len, 1000 ); - dbg_ch34x("0x%x:0x%x:0x%x:0x%x %d - %d", - VENDOR_READ_TYPE, request, value, index, retval, len ); - - return retval; -} - -static int ch34x_vendor_write( __u8 request, - __u16 value, - __u16 index, - struct usb_serial *serial, - unsigned char *buf, - __u16 len ) -{ - int retval; - - retval = usb_control_msg( serial->dev, - usb_sndctrlpipe(serial->dev, 0), - request, - VENDOR_WRITE_TYPE, - value, index, buf, len, 1000 ); - - return retval; -} - -static int set_control_lines( struct usb_serial *serial, - u8 value ) -{ - int retval; - - retval = ch34x_vendor_write( VENDOR_MODEM_OUT, (unsigned short)value, - 0x0000, serial, NULL, 0x00 ); - dbg_ch34x("%s - value=%d, retval=%d", __func__, value, retval ); - - return retval; -} - -static int ch34x_get_baud_rate( unsigned int baud_rate, - unsigned char *a, unsigned char *b ) -{ - unsigned long factor = 0; - short divisor = 0; - - if( !baud_rate ) - return -EINVAL; - - factor = (CH34x_BAUDRATE_FACTOR / baud_rate); - divisor = CH34x_BAUDRATE_DIVMAX; - - while( (factor > 0xfff0) && divisor ) { - factor >>= 3; - divisor --; - } - - if( factor > 0xfff0 ) - return -EINVAL; - - factor = 0x10000 - factor; - *a = (factor & 0xff00) >> 8; - *b = divisor; - - return 0; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static void ch34x_set_termios( struct tty_struct *tty, - struct usb_serial_port *port, struct ktermios *old_termios ) -{ -#else -static void ch34x_set_termios( struct usb_serial_port *port, - struct ktermios *old_termios ) -{ - struct tty_struct *tty = port->tty; -#endif - struct usb_serial *serial = port->serial; - struct ch34x_private *priv = usb_get_serial_port_data(port); -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure - struct ktermios *termios = &tty->termios; -#else - struct ktermios *termios = tty->termios; -#endif - unsigned int baud_rate; - unsigned int cflag; - unsigned long flags; - u8 control; - - unsigned char divisor = 0; - unsigned char reg_count = 0; - unsigned char factor = 0; - unsigned char reg_value = 0; - unsigned short value = 0; - unsigned short index = 0; -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - if( !priv->termios_initialized ) { - *(termios) = tty_std_termios; - termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; - termios->c_ispeed = 9600; - termios->c_ospeed = 9600; - priv->termios_initialized = 1; - } - spin_unlock_irqrestore( &priv->lock, flags ); - - /* - * The ch34x is reported to lose bytes if you change serial setting - * even to the same vaules as before. Thus we actually need to filter - * in this specific case. - */ - if( !tty_termios_hw_change(termios, old_termios) ) - return; - - cflag = termios->c_cflag; -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->number, cflag); -#else - dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->port_number, cflag); -#endif - // Get the byte size - switch( cflag & CSIZE ) - { - case CS5: - reg_value |= 0x00; - break; - case CS6: - reg_value |= 0x01; - break; - case CS7: - reg_value |= 0x02; - break; - case CS8: - reg_value |= 0x03; - break; - default: - reg_value |= 0x03; - break; - } - dbg_ch34x("%s - data bits = %d", __func__, reg_value + 0x05 ); - - // Figure out the stop bits - if( cflag & CSTOPB ) { - reg_value |= 0x04; - dbg_ch34x("%s - stop bits = 2", __func__); - } - else - dbg_ch34x("%s - stop bits = 1", __func__); - - // Determine the parity - if( cflag & PARENB ) - if( cflag & PARODD ) { - reg_value |= (0x08 | 0x00); - dbg_ch34x("%s - parity = odd", __func__); - } - else { - reg_value |= (0x08 | 0x10); - dbg_ch34x("%s - parity = even", __func__); - } - else - dbg_ch34x("%s - parity = none", __func__); - - // Determine the baud rate - baud_rate = tty_get_baud_rate( tty ); - dbg_ch34x("%s = baud_rate = %d", __func__, baud_rate); - ch34x_get_baud_rate( baud_rate, &factor, &divisor ); - dbg_ch34x("----->>>> baud_rate = %d, factor:0x%x, divisor:0x%x", - baud_rate, factor, divisor ); - - //enable SFR_UART RX and TX - reg_value |= 0xc0; - //enable SFR_UART Control register and timer - reg_count |= 0x9c; - - value |= reg_count; - value |= (unsigned short)reg_value << 8; - index |= 0x80 | divisor; - index |= (unsigned short)factor << 8; - ch34x_vendor_write( VENDOR_SERIAL_INIT, value, index, serial, NULL, 0 ); - - // change control lines if we are switching to or from B0 - spin_lock_irqsave( &priv->lock, flags ); - control = priv->line_control; - if( (cflag & CBAUD) == B0 ) - priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); - else - priv->line_control |= (CONTROL_DTR | CONTROL_RTS); - - if( control != priv->line_control ) { - control = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - set_control_lines( serial, control ); - } - else - spin_unlock_irqrestore( &priv->lock, flags ); - - if( cflag & CRTSCTS ) - ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0101, serial, NULL, 0); - - // FIXME: Need to read back resulting baud rate - if( baud_rate ) - tty_encode_baud_rate(tty, baud_rate, baud_rate); - -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_tiocmget( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_tiocmget( struct tty_struct *tty, - struct file *filp ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_tiocmget( struct usb_serial_port *port, - struct file *filp ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int mcr; - /*unsigned int msr;*/ - unsigned int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - if( !usb_get_intfdata( port->serial->interface) ) - return -ENODEV; - - spin_lock_irqsave( &priv->lock, flags ); - mcr = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - - retval = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) | - ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) | - ((mcr & UART_CTS) ? TIOCM_CTS : 0) | - ((mcr & UART_DSR) ? TIOCM_DSR : 0) | - ((mcr & UART_RING) ? TIOCM_RI : 0) | - ((mcr & UART_DCD) ? TIOCM_CD : 0); - - dbg_ch34x("%s - retval=0x%x", __func__, retval); - - return retval; -} - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) -static void ch34x_close( struct tty_struct *tty, - struct usb_serial_port *port, struct file *filp ) -{ -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) ) -static void ch34x_close( struct usb_serial_port *port ) -{ - struct tty_struct *tty = port->port.tty; -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) -static void ch34x_close( struct usb_serial_port *port, - struct file *filp ) -{ - - struct tty_struct *tty = port->tty; -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int c_cflag; - int bps; - long timeout; - wait_queue_t wait; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif -// dbg_ch34x("Addr of priv: 0x%x", priv); -// dbg_ch34x("Addr of tty: 0x%x", tty); - // wait for data do drain from the buffer -/* - spin_lock_irqsave( &priv->lock, flags ); - timeout = CH34x_CLOSING_WAIT; - init_waitqueue_entry( &wait, current ); - add_wait_queue( &tty->write_wait, &wait ); - for(;;) { - set_current_state( TASK_INTERRUPTIBLE ); - if( ch34x_buf_data_avail(priv->buf) == 0 || timeout == 0 || - signal_pending(current) || port->serial->disconnected ) - break; - spin_unlock_irqrestore( &priv->lock, flags ); - timeout = schedule_timeout( timeout ); - spin_lock_irqsave( &priv->lock, flags ); - } - set_current_state( TASK_RUNNING ); - remove_wait_queue( &tty->write_wait, &wait ); -*/ - spin_lock_irqsave( &priv->lock, flags ); - // clear out any remaining data in the buffer - ch34x_buf_clear( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - -/* - bps = tty_get_baud_rate( tty ); - if( bps > 1200 ) - timeout = max( (HZ * 2560) / bps, HZ / 10 ); - else - timeout = 2 * HZ; - schedule_timeout_interruptible(timeout); -*/ - // shutdown our urbs - usb_kill_urb(port->interrupt_in_urb); - usb_kill_urb(port->read_urb); - usb_kill_urb(port->write_urb); - //usb_serial_generic_close(port, filp); - - if( tty ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure - c_cflag = tty->termios.c_cflag; -#else - c_cflag = tty->termios->c_cflag; -#endif - if( c_cflag & HUPCL ) { - // drop DTR and RTS - spin_lock_irqsave( &priv->lock, flags ); - priv->line_control = 0; - spin_unlock_irqrestore( &priv->lock, flags ); - set_control_lines( port->serial, 0 ); - } - } - -// usb_serial_generic_close(port); -// usb_kill_urb(port->interrupt_in_urb); - -} - -// kernel version -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) \ - && LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_open( struct tty_struct *tty, - struct usb_serial_port *port, struct file *filp ) -{ -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) -static int ch34x_open( struct tty_struct *tty, - struct usb_serial_port *port ) -{ -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) -static int ch34x_open( struct usb_serial_port *port, - struct file *filp ) -{ - struct tty_struct *tty = port->tty; -#endif - struct ktermios tmp_termios; - struct usb_serial *serial = port->serial; - int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number ); -#endif - usb_clear_halt( serial->dev, port->write_urb->pipe ); - usb_clear_halt( serial->dev, port->read_urb->pipe ); - - if( tty ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) - ch34x_set_termios( tty, port, &tmp_termios ); -#else - ch34x_set_termios( port, &tmp_termios ); -#endif - } - - dbg_ch34x("%s - submit read urb", __func__); - port->read_urb->dev = serial->dev; - retval = usb_submit_urb( port->read_urb, GFP_KERNEL ); - if(retval) { - dev_err( &port->dev, "%s - failed submit read urb,error %d\n", - __func__, retval ); -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) - ch34x_close(tty, port, NULL); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) - ch34x_close(port); -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - ch34x_close(port, filp); -#endif - goto err_out; - } - - dbg_ch34x("%s - submit interrupt urb", __func__ ); - port->interrupt_in_urb->dev = serial->dev; - retval = usb_submit_urb( port->interrupt_in_urb, GFP_KERNEL ); - if(retval) { - dev_err( &port->dev, "%s - failed submit interrupt urb,error %d\n", - __func__, retval ); -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) - ch34x_close(tty, port, NULL); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) - ch34x_close(port); -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - ch34x_close(port, filp); -#endif - goto err_out; - } - -err_out: - return retval; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_tiocmset( struct tty_struct *tty, - unsigned int set, unsigned int clear ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_tiocmset( struct tty_struct *tty, - struct file *filp, unsigned int set, unsigned int clear ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_tiocmset( struct usb_serial_port *port, - struct file *filp, unsigned int set, unsigned int clear ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - /*unsigned int mcr = priv->line_control;*/ - u8 control; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - if( !usb_get_intfdata(port->serial->interface) ) - return -ENODEV; - - spin_lock_irqsave( &priv->lock, flags ); - if( set & TIOCM_RTS ) - priv->line_control |= CONTROL_RTS; - if( set & TIOCM_DTR ) - priv->line_control |= CONTROL_DTR; - if( clear & TIOCM_RTS ) - priv->line_control &= ~CONTROL_RTS; - if( clear & TIOCM_DTR ) - priv->line_control &= ~CONTROL_DTR; - control = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - - return set_control_lines( port->serial, control ); -} - -static int wait_modem_info( struct usb_serial_port *port, - unsigned int arg ) -{ - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int prevstatus; - unsigned int status; - unsigned int changed; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s -port:%d", __func__, port->number); -#else - dbg_ch34x("%s -port:%d", __func__, port->port_number); -#endif - spin_lock_irqsave( &priv->lock, flags ); - prevstatus = priv->line_status; - spin_unlock_irqrestore( &priv->lock, flags ); - - while(1) { - wait_event_interruptible( wq, wait_flag != 0 ); - wait_flag = 0; - // see if a signal did it - if( signal_pending(current) ) - return -ERESTARTSYS; - - spin_lock_irqsave( &priv->lock, flags ); - status = priv->line_status; - spin_unlock_irqrestore( &priv->lock, flags ); - - changed = prevstatus ^ status; - - if( ((arg & TIOCM_RNG) && (changed & UART_RING)) || - ((arg & TIOCM_DSR) && (changed & UART_DSR)) || - ((arg & TIOCM_CD) && (changed & UART_DCD)) || - ((arg & TIOCM_CTS) && (changed & UART_CTS)) ) - return 0; - - prevstatus = status; - } - - // Not reatched - return 0; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_ioctl( struct tty_struct *tty, - unsigned int cmd, unsigned long arg ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_ioctl( struct tty_struct *tty, - struct file *filp, unsigned int cmd, unsigned long arg ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_ioctl( struct usb_serial_port *port, - struct file *filp, unsigned int cmd, unsigned long arg ) -{ - //struct usb_serial_port *port = tty->driver_data; -#endif -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->number, cmd); -#else - dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->port_number, cmd); -#endif - switch(cmd) - { - // Note here - case TIOCMIWAIT: -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->number); -#else - dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->port_number); -#endif - - return wait_modem_info(port, arg); - default: - dbg_ch34x("%s not supported=0x%04x", __func__, cmd); - break; - } - - return -ENOIOCTLCMD; -} - -static void ch34x_send( struct usb_serial_port *port ) -{ - int count; - int retval; - struct ch34x_private *priv = usb_get_serial_port_data( port ); - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - spin_lock_irqsave( &priv->lock, flags ); - if( priv->write_urb_in_use ) { - spin_unlock_irqrestore( &priv->lock, flags ); - return; - } - - count = ch34x_buf_get( priv->buf, port->write_urb->transfer_buffer, - port->bulk_out_size ); - if( count == 0 ) { - spin_unlock_irqrestore( &priv->lock, flags ); - return; - } - - priv->write_urb_in_use = 1; - spin_unlock_irqrestore( &priv->lock, flags ); - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, count, - port->write_urb->transfer_buffer ); -#else - usb_serial_debug_data( debug, &port->dev, __func__, count, - port->write_urb->transfer_buffer ); -#endif - - port->write_urb->transfer_buffer_length = count; - port->write_urb->dev = port->serial->dev; - retval = usb_submit_urb( port->write_urb, GFP_ATOMIC ); - if( retval ) { - dev_err( &port->dev, "%s - failed submitting write urb,error %d\n" - , __func__, retval ); - priv->write_urb_in_use = 0; - // reschedule ch34x_send - } - - usb_serial_port_softint( port ); -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_write( struct tty_struct *tty, - struct usb_serial_port *port, const unsigned char *buf, int count ) -#else -static int ch34x_write( struct usb_serial_port *port, - const unsigned char *buf, int count ) -#endif -{ - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d, %d bytes", __func__, port->number, count); -#else - dbg_ch34x("%s - port:%d, %d bytes", __func__, port->port_number, count); -#endif - - if( !count ) - return count; - - spin_lock_irqsave( &priv->lock, flags ); - count = ch34x_buf_put( priv->buf, buf, count ); - spin_unlock_irqrestore( &priv->lock, flags ); - - ch34x_send(port); - - return count; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_write_room( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_write_room( struct usb_serial_port *port ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data( port ); - int room = 0; - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - room = ch34x_buf_space_avail( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - - dbg_ch34x("%s - room:%d", __func__, room ); - return room; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_chars_in_buffer( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_chars_in_buffer( struct usb_serial_port *port ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - int chars = 0; - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - chars = ch34x_buf_data_avail( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - - dbg_ch34x("%s - chars:%d", __func__, chars ); - - return chars; -} - -static int ch34x_attach( struct usb_serial *serial ) -{ - struct ch34x_private *priv; - int i; - char buf[8]; - - dbg_ch34x("%s", __func__); - - for( i = 0; i < serial->num_ports; ++i ) { - priv = kzalloc( sizeof(struct ch34x_private), GFP_KERNEL ); - if( !priv ) - goto cleanup; - spin_lock_init( &priv->lock ); - priv->buf = ch34x_buf_alloc( CH34x_BUF_SIZE ); - if( priv->buf == NULL ) { - kfree( priv ); - goto cleanup; - } - init_waitqueue_head( &priv->delta_msr_wait ); - usb_set_serial_port_data( serial->port[i], priv ); - } - - ch34x_vendor_read( VENDOR_VERSION, 0x0000, 0x0000, - serial, buf, 0x02 ); - ch34x_vendor_write( VENDOR_SERIAL_INIT, 0x0000, 0x0000, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_WRITE, 0x1312, 0xD982, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_WRITE, 0x0F2C, 0x0004, - serial, NULL, 0x00 ); - ch34x_vendor_read( VENDOR_READ, 0x2518, 0x0000, - serial, buf, 0x02 ); - ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0000, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_MODEM_OUT, 0x009F, 0x0000, - serial, NULL, 0x00 ); - - return 0; - -cleanup: - for( --i; i >= 0; --i ) { - priv = usb_get_serial_port_data( serial->port[i] ); - ch34x_buf_free( priv->buf ); - kfree( priv ); - usb_set_serial_port_data( serial->port[i], NULL ); - } - - return -ENOMEM; -} - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) -static void ch34x_shutdown( struct usb_serial *serial ) -{ - struct ch34x_private *priv; - int i; - - dbg_ch34x("%s", __func__); - - for( i = 0; i < serial->num_ports; ++i ) { - priv = usb_get_serial_port_data( serial->port[i] ); - if( priv ) { - ch34x_buf_free( priv->buf ); - kfree( priv ); - usb_set_serial_port_data( serial->port[i], NULL ); - } - } -} -#endif - -static void ch34x_update_line_status( struct usb_serial_port *port, - unsigned char *data, unsigned int actual_length ) -{ - struct ch34x_private *priv = usb_get_serial_port_data( port ); - unsigned long flags; - u8 length = UART_STATE + 0x04; - - if( actual_length < length ) - return; - - // Save off the uart status for others to look at - spin_lock_irqsave( &priv->lock, flags ); - priv->line_status = data[UART_STATE]; - priv->line_control = data[PORTB_STATE]; - spin_unlock_irqrestore( &priv->lock, flags ); - wait_flag = 1; - wake_up_interruptible( &priv->delta_msr_wait ); -} - -static void ch34x_read_int_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - unsigned char *data = urb->transfer_buffer; - unsigned int actual_length = urb->actual_length; - int status = urb->status; - int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s port:%d", __func__, port->number ); -#else - dbg_ch34x("%s port:%d", __func__, port->port_number ); -#endif - switch( status ) { - case 0: //success - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: //this urb is terminated, clean up - dbg_ch34x("%s - urb shutting down with status:%d", __func__, status ); - return; - default: - dbg_ch34x("%s - nonzero urb status received:%d", __func__, status ); - goto exit; - } - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, - urb->actual_length, urb->transfer_buffer ); -#else - usb_serial_debug_data( debug, &port->dev, - __func__, urb->actual_length, urb->transfer_buffer ); -#endif - - ch34x_update_line_status( port, data, actual_length ); - -exit: - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) - dev_err( &urb->dev->dev, "%s - usb_submit_urb failed with result %d\n", - __func__, retval ); -} - -static void ch34x_read_bulk_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - struct ch34x_private *priv = usb_get_serial_port_data( port ); - struct tty_struct *tty; - unsigned char *data = urb->transfer_buffer; - unsigned long flags; - int i; - int retval; - int status = urb->status; - u8 line_status; - char tty_flag; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - if( status ) { - dbg_ch34x("%s - urb status=%d", __func__, status ); - if( status == -EPROTO ) { - // CH34x mysteriously fails with -EPROTO reschedule the read - dbg_ch34x("%s - caught -EPROTO, resubmitting the urb", __func__); - urb->dev = port->serial->dev; - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) { - dev_err( &urb->dev->dev, - "%s - failed resubmitting read urb, error %d\n", - __func__, retval ); - return; - } - } - - dbg_ch34x("%s - unable to handle the error, exiting.", __func__); - return; - } - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, - urb->actual_length, data ); -#else - usb_serial_debug_data( debug, &port->dev, - __func__, urb->actual_length, data ); -#endif - - // get tty_flag from status - tty_flag = TTY_NORMAL; - - spin_lock_irqsave( &priv->lock, flags ); - line_status = priv->line_status; - priv->line_status &= ~UART_STATE_TRANSIENT_MASK; - spin_unlock_irqrestore( &priv->lock, flags ); - wait_flag = 1; - wake_up_interruptible( &priv->delta_msr_wait ); - - // break takes precedence over parity, - // which takes precedence over framing errors - if( line_status & UART_PARITY_ERROR ) - tty_flag = TTY_PARITY; - else if( line_status & UART_OVERRUN_ERROR ) - tty_flag = TTY_OVERRUN; - else if( line_status & UART_FRAME_ERROR ) - tty_flag = TTY_FRAME; - dbg_ch34x("%s - tty_flag=%d", __func__, tty_flag); - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) - tty = port->port.tty; -#else - tty = port->tty; -#endif - if( tty && urb->actual_length ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_buffer_request_room( tty->port, urb->actual_length + 1); -#else - tty_buffer_request_room( tty, urb->actual_length + 1 ); -#endif - // overrun is special, not associated with a char - if( line_status & UART_OVERRUN_ERROR ) -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_insert_flip_char( tty->port, 0, TTY_OVERRUN ); -#else - tty_insert_flip_char( tty, 0, TTY_OVERRUN ); -#endif - for( i = 0; i < urb->actual_length; ++i ) -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_insert_flip_char( tty->port, data[i], tty_flag ); -#else - tty_insert_flip_char( tty, data[i], tty_flag ); -#endif - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_flip_buffer_push( tty->port ); -#else - tty_flip_buffer_push( tty ); -#endif - } - - //Schedule the next read _if_ we are still open -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - if( port->open_count ) -#endif - { - urb->dev = port->serial->dev; - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) - dev_err( &urb->dev->dev, - "%s - fialed resubmitting read urb, error %d\n", - __func__, retval ); - } - - return; -} - -static void ch34x_write_bulk_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - struct ch34x_private *priv = usb_get_serial_port_data(port); - int retval; - int status = urb->status; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number ); -#endif - switch( status ) { - case 0: //success - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - // this urb is terminated, clean up - dbg_ch34x("%s - urb shutting down with status:%d", __func__, status); - priv->write_urb_in_use = 0; - return; - default: - // error in the urb, so we have to resubmit it - dbg_ch34x("%s - Overflow in write", __func__); - dbg_ch34x("%s - nonzero write bulk status received:%d", __func__, status); - port->write_urb->transfer_buffer_length = 1; - port->write_urb->dev = port->serial->dev; - retval = usb_submit_urb(port->write_urb, GFP_ATOMIC); - if( retval ) - dev_err( &urb->dev->dev, - "%s - failed resubmitting write urv, error:%d\n", - __func__, retval ); - else - return; - } - - priv->write_urb_in_use = 0; - - // send any buffered data - ch34x_send(port); -} - -static struct usb_serial_driver ch34x_device = { - .driver = { - .owner = THIS_MODULE, - .name = "ch34x", - }, - .id_table = id_table, -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - .usb_driver = &ch34x_driver, -#endif - .num_ports = 1, - .open = ch34x_open, - .close = ch34x_close, - .write = ch34x_write, - .ioctl = ch34x_ioctl, - .set_termios = ch34x_set_termios, - .tiocmget = ch34x_tiocmget, - .tiocmset = ch34x_tiocmset, - .read_bulk_callback = ch34x_read_bulk_callback, - .read_int_callback = ch34x_read_int_callback, - .write_bulk_callback = ch34x_write_bulk_callback, - .write_room = ch34x_write_room, - .chars_in_buffer = ch34x_chars_in_buffer, - .attach = ch34x_attach, -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) ) - .shutdown = ch34x_shutdown, -#endif -}; - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5)) -static struct usb_serial_driver *const serial_driver [] = { - &ch34x_device, NULL -}; -#endif - - -static int __init ch34x_init(void) -{ -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5)) - int retval = 0; - - retval = usb_serial_register( &ch34x_device ); - if( retval ) { - goto err_usb_serial_register; - } - retval = usb_register( &ch34x_driver ); - if( retval ) { - goto err_usb_register; - } -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) - info( DRIVER_DESC ); -#endif - return 0; - -err_usb_register: - usb_deregister( &ch34x_driver ); -err_usb_serial_register: - usb_serial_deregister( &ch34x_device ); - return retval; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1)) - return usb_serial_register_drivers( serial_driver, - KBUILD_MODNAME, id_table ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \ - LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - return usb_serial_register_drivers(&ch34x_driver, serial_driver); -#endif -} - -static void __exit ch34x_exit(void) -{ -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5)) - usb_deregister( &ch34x_driver ); - usb_serial_deregister( &ch34x_device ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1)) - usb_serial_deregister_drivers( serial_driver ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \ - LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - usb_serial_deregister_drivers(&ch34x_driver, serial_driver); -#endif -} - -module_init( ch34x_init ); -module_exit( ch34x_exit ); - - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_LICENSE("GPL"); diff --git a/drivers/CH341SER_LINUX/driver/Makefile b/drivers/CH341SER_LINUX/driver/Makefile new file mode 100644 index 0000000..ff44f60 --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/Makefile @@ -0,0 +1,21 @@ +ifeq ($(KERNELRELEASE), ) +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD :=$(shell pwd) +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) +clean: + rm -rf *.mk .tmp_versions Module.symvers *.mod.c *.o *.ko .*.cmd Module.markers modules.order +load: + insmod ch341.ko +unload: + rmmod ch341 +install: default + mkdir -p /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ + cp -f ./ch341.ko /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ + depmod -a +uninstall: + rm -rf /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ch341.ko + depmod -a +else + obj-m := ch341.o +endif diff --git a/drivers/CH341SER_LINUX/driver/ch341.c b/drivers/CH341SER_LINUX/driver/ch341.c new file mode 100644 index 0000000..755e02e --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/ch341.c @@ -0,0 +1,1710 @@ +/* + * USB serial driver for USB to UART chip ch340, ch341, etc. + * + * Copyright (C) 2021 WCH. + * Author: TECH39 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * System required: + * Kernel version beyond 3.4.x + * Update Log: + * V1.0 - initial version + * V1.1 - added supports for high baudrates, modem signal, flow control, etc. + * V1.2 - fixed baud rate 0 bugs and add some debug messages + * V1.3 - special setting on usb packet upload timeout + * V1.4 - changed read urb length to ep size + * V1.5 - fixed hardware flowcontrol bugs + * V1.6 - added supports for application to get uart state + * - removed tty throttle methods + * - submits urbs when uart open while not probe + * - fixed tty kref errors in dtr_rts + * V1.7 - added supports for kernel version beyond 5.14.x + * - fixed data analysis in status ep callback + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif + +#include "ch341.h" + +#define DRIVER_AUTHOR "TECH39 " +#define DRIVER_DESC "USB serial driver for ch340, ch341, etc." +#define VERSION_DESC "V1.7 On 2022.08.23" + +static struct usb_driver ch341_driver; +static struct tty_driver *ch341_tty_driver; + +static DEFINE_IDR(ch341_minors); +static DEFINE_MUTEX(ch341_minors_lock); + +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old); + +/* + * ch341_minors accessors + */ + +/* + * Look up an ch341 structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct ch341 *ch341_get_by_minor(unsigned int minor) +{ + struct ch341 *ch341; + + mutex_lock(&ch341_minors_lock); + ch341 = idr_find(&ch341_minors, minor); + if (ch341) { + mutex_lock(&ch341->mutex); + if (ch341->disconnected) { + mutex_unlock(&ch341->mutex); + ch341 = NULL; + } else { + tty_port_get(&ch341->port); + mutex_unlock(&ch341->mutex); + } + } + mutex_unlock(&ch341_minors_lock); + return ch341; +} + +/* + * Try to find an available minor number and if found, associate it with 'ch341'. + */ +static int ch341_alloc_minor(struct ch341 *ch341) +{ + int minor; + + mutex_lock(&ch341_minors_lock); + minor = idr_alloc(&ch341_minors, ch341, 0, CH341_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&ch341_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch341'. */ +static void ch341_release_minor(struct ch341 *ch341) +{ + mutex_lock(&ch341_minors_lock); + idr_remove(&ch341_minors, ch341->minor); + mutex_unlock(&ch341_minors_lock); +} + +/* + * Functions for CH341 control messages. + */ + +static int ch341_control_out(struct ch341 *ch341, u8 request, + u16 value, u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg(ch341->dev, usb_sndctrlpipe(ch341->dev, 0), + request, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); + + dev_vdbg(&ch341->data->dev, + "ch341_control_out(%02x,%02x,%04x,%04x)\n", + USB_DIR_OUT | 0x40, request, value, index); + + usb_autopm_put_interface(ch341->data); + + return retval < 0 ? retval : 0; +} + +static int ch341_control_in(struct ch341 *ch341, + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) +{ + int retval; + int i; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg(ch341->dev, usb_rcvctrlpipe(ch341->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + + dev_vdbg(&ch341->data->dev, + "ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)\n", + USB_DIR_IN | 0x40, (u8)request, (u16)value, (u16)index, buf, + (int)bufsize); + + dev_vdbg(&ch341->data->dev, + "ch341_control_in result:"); + for (i = 0; i < retval; i++) { + dev_vdbg(&ch341->data->dev, + "0x%.2x ", (u8)buf[i]); + } + + usb_autopm_put_interface(ch341->data); + + return retval; +} + +static inline int ch341_set_control(struct ch341 *ch341, int control) +{ + u16 value = 0; + + value |= (u8)~control; + + return ch341_control_out(ch341, CMD_C2, + value, 0x0000); +} + +static inline int ch341_set_line(struct ch341 *ch341, struct usb_ch341_line_coding *line) +{ + return 0; +} + +static int ch341_get_status(struct ch341 *ch341) +{ + char *buffer; + int retval; + const unsigned size = 2; + unsigned long flags; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = ch341_control_in(ch341, CMD_R, 0x0706, 0, buffer, size); + if (retval < 0) + goto out; + + /* setup the private status if available */ + if (retval > 0) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->ctrlin = (~(*buffer)) & CH341_CTI_ST; + spin_unlock_irqrestore(&ch341->read_lock, flags); + } else + retval = -EPROTO; + +out: + kfree(buffer); + return retval; +} + +/* -------------------------------------------------------------------------- */ + +static int ch341_configure(struct ch341 *ch341) +{ + char *buffer; + int r; + const unsigned size = 2; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + r = ch341_control_in(ch341, CMD_C3, 0, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(ch341, CMD_C1, 0, 0); + if (r < 0) + goto out; + + r = ch341_control_out(ch341, CMD_W, 0x1312, 0xd982); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x0f2c, 0x0007); + if (r < 0) + goto out; + r = ch341_control_in(ch341, CMD_R, 0x2518, 0, buffer, size); + if (r < 0) + goto out; + r = ch341_get_status(ch341); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x2727, 0); + if (r < 0) + goto out; + +out: + kfree(buffer); + return r; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ + +static int ch341_wb_alloc(struct ch341 *ch341) +{ + int i, wbn; + struct ch341_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &ch341->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % CH341_NW; + if (++i >= CH341_NW) + return -1; + } +} + +static int ch341_wb_is_avail(struct ch341 *ch341) +{ + int i, n; + unsigned long flags; + + n = CH341_NW; + spin_lock_irqsave(&ch341->write_lock, flags); + for (i = 0; i < CH341_NW; i++) + n -= ch341->wb[i].use; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold ch341->write_lock + */ +static void ch341_write_done(struct ch341 *ch341, struct ch341_wb *wb) +{ + wb->use = 0; + ch341->transmitting--; + usb_autopm_put_interface_async(ch341->data); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ + +static int ch341_start_wb(struct ch341 *ch341, struct ch341_wb *wb) +{ + int rc; + + ch341->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch341->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + ch341_write_done(ch341, wb); + } + return rc; +} + +static void ch341_update_status(struct ch341 *ch341, + unsigned char *data, size_t len) +{ + unsigned long flags; + u8 status; + u8 difference; + u8 type = data[0]; + u8 handled = 0; + + if (len < 4) + return; + + if (type & CH341_CTT_M) { + status = ~data[2] & CH341_CTI_ST; + + if (!ch341->clocal && (ch341->ctrlin & status & CH341_CTI_DC)) { + tty_port_tty_hangup(&ch341->port, false); + } + + spin_lock_irqsave(&ch341->read_lock, flags); + difference = status ^ ch341->ctrlin; + ch341->ctrlin = status; + ch341->oldcount = ch341->iocount; + if (difference) { + if (difference & CH341_CTI_C) { + ch341->iocount.cts++; + } + if (difference & CH341_CTI_DS) { + ch341->iocount.dsr++; + } + if (difference & CH341_CTRL_RI) { + ch341->iocount.rng++; + } + if (difference & CH341_CTI_DC) { + ch341->iocount.dcd++; + } + spin_unlock_irqrestore(&ch341->read_lock, flags); + wake_up_interruptible(&ch341->wioctl); + } else + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (type & CH341_CTT_O) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.overrun++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if ((type & CH341_CTT_F) == CH341_CTT_F) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.frame++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } else if (type & CH341_CTT_P) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.parity++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (!handled) + dev_err(&ch341->data->dev, + "%s - unknown status received:" + "len:%d, data0:0x%x, data1:0x%x\n", + __func__, + (int)len, data[0], data[1]); +} + +/* + * Interrupt handlers for various CH341 device responses + */ + +/* control interface reports status changes with "interrupt" transfers */ +static void ch341_ctrl_irq(struct urb *urb) +{ + struct ch341 *ch341 = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&ch341->data->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&ch341->data->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(ch341->dev); + ch341_update_status(ch341, data, len); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch341->data->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); +} + +static int ch341_submit_read_urb(struct ch341 *ch341, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &ch341->read_urbs_free)) + return 0; + + dev_vdbg(&ch341->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(ch341->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &ch341->read_urbs_free); + return res; + } + + return 0; +} + +static int ch341_submit_read_urbs(struct ch341 *ch341, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch341->rx_buflimit; ++i) { + res = ch341_submit_read_urb(ch341, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void ch341_process_read_urb(struct ch341 *ch341, struct urb *urb) +{ + if (!urb->actual_length) + return; + + tty_insert_flip_string(&ch341->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&ch341->port); +} + +static void ch341_read_bulk_callback(struct urb *urb) +{ + struct ch341_rb *rb = urb->context; + struct ch341 *ch341 = rb->instance; + int status = urb->status; + + dev_vdbg(&ch341->data->dev, "%s - urb %d, len %d\n", __func__, + rb->index, urb->actual_length); + + if (!ch341->dev) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, "%s - disconnected\n", __func__); + return; + } + if (status) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, "%s - non-zero urb status: %d\n", + __func__, status); + return; + } + usb_mark_last_busy(ch341->dev); + ch341_process_read_urb(ch341, urb); + set_bit(rb->index, &ch341->read_urbs_free); + ch341_submit_read_urb(ch341, rb->index, GFP_ATOMIC); +} + +/* data interface wrote those outgoing bytes */ +static void ch341_write_bulk(struct urb *urb) +{ + struct ch341_wb *wb = urb->context; + struct ch341 *ch341 = wb->instance; + unsigned long flags; + int status = urb->status; + + dev_vdbg(&ch341->data->dev, "%s, len %d\n", __func__, urb->actual_length); + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&ch341->data->dev, "%s - len %d/%d, status %d\n", + __func__, + urb->actual_length, + urb->transfer_buffer_length, + status); + + spin_lock_irqsave(&ch341->write_lock, flags); + ch341_write_done(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + schedule_work(&ch341->work); +} + +static void ch341_softint(struct work_struct *work) +{ + struct ch341 *ch341 = container_of(work, struct ch341, work); + + dev_vdbg(&ch341->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&ch341->port); +} + +static int ch341_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct ch341 *ch341; + int retval; + + dev_dbg(tty->dev, "%s\n", __func__); + + ch341 = ch341_get_by_minor(tty->index); + if (!ch341) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch341; + + return 0; + +error_init_termios: + tty_port_put(&ch341->port); + return retval; +} + +static int ch341_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&ch341->port, tty, filp); +} + +static void ch341_port_dtr_rts(struct tty_port *port, int raise) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int res; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + if (raise) + ch341->ctrlout |= CH341_CTO_D | CH341_CTO_R; + else + ch341->ctrlout &= ~(CH341_CTO_D | CH341_CTO_R); + if (ch341->hardflow) + ch341->ctrlout |= CH341_CTO_R; + res = ch341_set_control(ch341, ch341->ctrlout); + if (res) + dev_err(&ch341->data->dev, "failed to set dtr/rts\n"); +} + +static int ch341_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int retval = -ENODEV; + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + mutex_lock(&ch341->mutex); + if (ch341->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + goto error_get_interface; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch341->data->needs_remote_wakeup = 1; + retval = ch341_configure(ch341); + if (retval) + goto error_configure; + + ch341_tty_set_termios(tty, NULL); + + retval = usb_submit_urb(ch341->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(ctrl cmd) failed\n", __func__); + goto error_submit_urb; + } + + retval = ch341_submit_read_urbs(ch341, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; + usb_autopm_put_interface(ch341->data); + + mutex_unlock(&ch341->mutex); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch341->ctrlurb); +error_configure: + usb_autopm_put_interface(ch341->data); +error_get_interface: +disconnected: + mutex_unlock(&ch341->mutex); + + return usb_translate_errors(retval); +} + +static void ch341_port_destruct(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + ch341_release_minor(ch341); + usb_put_intf(ch341->data); + kfree(ch341); +} + +static void ch341_port_shutdown(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + struct urb *urb; + struct ch341_wb *wb; + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + usb_autopm_get_interface_no_resume(ch341->data); + ch341->data->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch341->data); + + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch341->data); + } + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +} + +static void ch341_tty_cleanup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_put(&ch341->port); +} + +static void ch341_tty_hangup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_hangup(&ch341->port); +} + +static void ch341_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_close(&ch341->port, tty, filp); +} + +static int ch341_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ch341 *ch341 = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch341_wb *wb; + + if (!count) + return 0; + + dev_vdbg(&ch341->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&ch341->write_lock, flags); + wbn = ch341_wb_alloc(ch341); + if (wbn < 0) { + spin_unlock_irqrestore(&ch341->write_lock, flags); + return 0; + } + wb = &ch341->wb[wbn]; + + if (!ch341->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return -ENODEV; + } + + count = (count > ch341->writesize) ? ch341->writesize : count; + + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(ch341->data); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return stat; + } + + if (ch341->susp_count) { + usb_anchor_urb(wb->urb, &ch341->delayed); + spin_unlock_irqrestore(&ch341->write_lock, flags); + return count; + } + + stat = ch341_start_wb(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch341_tty_write_room(struct tty_struct *tty) +#else +static int ch341_tty_write_room(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return ch341_wb_is_avail(ch341) ? ch341->writesize : 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#else +static int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector; + */ + if (ch341->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (CH341_NW - ch341_wb_is_avail(ch341)) * ch341->writesize; +} + +static int ch341_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch341 *ch341 = tty->driver_data; + int retval; + uint16_t reg_contents; + uint8_t *regbuf; + const u16 regval = + ((u16)CH341_RL << 8) | CH341_RB; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + regbuf = kmalloc(2, GFP_KERNEL); + if (!regbuf) + return -1; + + retval = ch341_control_in(ch341, CMD_R, regval, 0, regbuf, 2); + if (retval < 0) { + dev_err(&ch341->data->dev, "%s - ch341_control_in error (%d)\n", + __func__, retval); + goto out; + } + if (state != 0) { + regbuf[0] &= ~CH341_NB; + regbuf[1] &= ~CH341_L_ET; + } else { + regbuf[0] |= CH341_NB; + regbuf[1] |= CH341_L_ET; + } + reg_contents = get_unaligned_le16(regbuf); + + retval = ch341_control_out(ch341, CMD_W, regval, + reg_contents); + if (retval < 0) + dev_err(&ch341->data->dev, "%s - USB control write error (%d)\n", + __func__, retval); +out: + kfree(regbuf); + + return retval; +} + +static int ch341_tty_tiocmget(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + unsigned long flags; + unsigned int result; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + spin_lock_irqsave(&ch341->read_lock, flags); + result = (ch341->ctrlout & CH341_CTO_D ? TIOCM_DTR : 0) | + (ch341->ctrlout & CH341_CTO_R ? TIOCM_RTS : 0) | + (ch341->ctrlin & CH341_CTI_C ? TIOCM_CTS : 0) | + (ch341->ctrlin & CH341_CTI_DS ? TIOCM_DSR : 0) | + (ch341->ctrlin & CH341_CTRL_RI ? TIOCM_RI : 0) | + (ch341->ctrlin & CH341_CTI_DC ? TIOCM_CD : 0); + spin_unlock_irqrestore(&ch341->read_lock, flags); + dev_dbg(&ch341->data->dev, "%s - result = %x\n", __func__, result); + + return result; +} + +static int ch341_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct ch341 *ch341 = tty->driver_data; + unsigned int newctrl; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + newctrl = ch341->ctrlout; + set = (set & TIOCM_DTR ? CH341_CTO_D : 0) | + (set & TIOCM_RTS ? CH341_CTO_R : 0); + clear = (clear & TIOCM_DTR ? CH341_CTO_D : 0) | + (clear & TIOCM_RTS ? CH341_CTO_R : 0); + newctrl = (newctrl & ~clear) | set; + if (C_CRTSCTS(tty)) + newctrl |= CH341_CTO_R; + + if (ch341->ctrlout == newctrl) + return 0; + return ch341_set_control(ch341, ch341->ctrlout = newctrl); +} + +static int get_serial_info(struct ch341 *ch341, struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = ch341->writesize; + tmp.baud_base = le32_to_cpu(ch341->line.dwDTERate); + tmp.close_delay = ch341->port.close_delay / 10; + tmp.closing_wait = ch341->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : ch341->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int set_serial_info(struct ch341 *ch341, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&ch341->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != ch341->port.close_delay) || + (closing_wait != ch341->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + ch341->port.close_delay = close_delay; + ch341->port.closing_wait = closing_wait; + } + + mutex_unlock(&ch341->port.mutex); + return retval; +} + +static int wait_serial_change(struct ch341 *ch341, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + do { + spin_lock_irq(&ch341->read_lock); + old = ch341->oldcount; + new = ch341->iocount; + ch341->oldcount = new; + spin_unlock_irq(&ch341->read_lock); + + if ((arg & TIOCM_CTS) && + old.cts != new.cts) + break; + if ((arg & TIOCM_DSR) && + old.dsr != new.dsr) + break; + if ((arg & TIOCM_RI) && + old.rng != new.rng) + break; + if ((arg & TIOCM_CD) && + old.dcd != new.dcd) + break; + + add_wait_queue(&ch341->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&ch341->wioctl, &wait); + if (ch341->disconnected) { + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + return rv; +} + +static int get_serial_usage(struct ch341 *ch341, + struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int rv = 0; + + memset(&icount, 0, sizeof(icount)); + icount.cts = ch341->iocount.cts; + icount.dsr = ch341->iocount.dsr; + icount.rng = ch341->iocount.rng; + icount.dcd = ch341->iocount.dcd; + icount.frame = ch341->iocount.frame; + icount.overrun = ch341->iocount.overrun; + icount.parity = ch341->iocount.parity; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + rv = -EFAULT; + + return rv; +} + +static int ch341_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct ch341 *ch341 = tty->driver_data; + int rv = -ENOIOCTLCMD; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(ch341, (struct serial_struct __user *)arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(ch341, (struct serial_struct __user *)arg); + break; + case TIOCMIWAIT: + rv = usb_autopm_get_interface(ch341->data); + if (rv < 0) { + rv = -EIO; + break; + } + rv = wait_serial_change(ch341, arg); + usb_autopm_put_interface(ch341->data); + break; + case TIOCGICOUNT: + rv = get_serial_usage(ch341, (struct serial_icounter_struct __user *)arg); + break; + } + + return rv; +} + +static int ch341_get(unsigned int baval, + unsigned char *factor, unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + switch (baval) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baval > 750000 / 255) { + b = 2; + c = 750000; + } else if (baval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + a = (unsigned char)(c / baval); + if (a == 0 || a == 0xFF) + return -EINVAL; + if ((c / a - baval) > (baval - c / (a + 1))) + a++; + a = 256 - a; + break; + } + *factor = a; + *divisor = b; + + return 0; +} + +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct ch341 *ch341 = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_ch341_line_coding newline; + int newctrl = ch341->ctrlout; + + unsigned char divisor = 0; + unsigned char reg_count = 0; + unsigned char factor = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (termios_old && + !tty_termios_hw_change(&tty->termios, termios_old)) { + dev_dbg(tty->dev, "%s - nothing to change\n", __func__); + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + ch341_get(newline.dwDTERate, &factor, &divisor); + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + reg_value |= CH341_L_SB; + + newline.bParityType = termios->c_cflag & PARENB ? (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) + : 0; + + switch (newline.bParityType) { + case 0x01: + reg_value |= CH341_L_PO; + break; + case 0x02: + reg_value |= CH341_L_PE; + break; + case 0x03: + reg_value |= CH341_L_PM; + break; + case 0x04: + reg_value |= CH341_L_PS; + break; + default: + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + reg_value |= CH341_L_D5; + break; + case CS6: + newline.bDataBits = 6; + reg_value |= CH341_L_D6; + break; + case CS7: + newline.bDataBits = 7; + reg_value |= CH341_L_D7; + break; + case CS8: + default: + newline.bDataBits = 8; + reg_value |= CH341_L_D8; + break; + } + + /* FIXME: Needs to clear unsupported bits in the termios */ + ch341->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch341->line.dwDTERate; + newctrl &= ~CH341_CTO_D; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH341_CTO_D; + } + + reg_value |= 0xc0; + reg_count |= 0x9c; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + index |= 0x80 | divisor; + index |= (unsigned short)factor << 8; + ch341_control_out(ch341, CMD_C1, value, index); + + if (newctrl != ch341->ctrlout) + ch341_set_control(ch341, ch341->ctrlout = newctrl); + + if (memcmp(&ch341->line, &newline, sizeof newline)) { + memcpy(&ch341->line, &newline, sizeof newline); + dev_dbg(&ch341->data->dev, "%s - set line: %d %d %d %d\n", + __func__, + newline.dwDTERate, + newline.bDataBits, newline.bCharFormat, + newline.bParityType); + } + if (C_CRTSCTS(tty)) { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0101); + newctrl |= CH341_CTO_R; + ch341_set_control(ch341, ch341->ctrlout = newctrl); + ch341->hardflow = true; + } else { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0000); + ch341->hardflow = false; + } +} + +static const struct tty_port_operations ch341_port_ops = { + .dtr_rts = ch341_port_dtr_rts, + .shutdown = ch341_port_shutdown, + .activate = ch341_port_activate, + .destruct = ch341_port_destruct, +}; + +/* + * USB probe and disconnect routines. + */ + +/* Little helpers: write/read buffers free */ +static void ch341_write_buffers_free(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) + usb_free_coherent(usb_dev, ch341->writesize, wb->buf, wb->dmah); +} + +static void ch341_read_buffers_free(struct ch341 *ch341) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + int i; + + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch341->readsize, + ch341->read_buffers[i].base, ch341->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int ch341_write_buffers_alloc(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch341->dev, ch341->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch341->dev, ch341->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int ch341_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch341 *ch341; + int minor; + int ctrlsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH341_NR; + int i; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + /* handle quirks deadly to normal probing*/ + + data_interface = usb_ifnum_to_if(usb_dev, 0); + + if (intf != data_interface) + return -ENODEV; + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + data_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + epctrl = &data_interface->cur_altsetting->endpoint[2].desc; + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + swap(epread, epwrite); + } + + dev_dbg(&intf->dev, "interface is valid\n"); + + ch341 = kzalloc(sizeof(struct ch341), GFP_KERNEL); + if (ch341 == NULL) + goto alloc_fail; + + minor = ch341_alloc_minor(ch341); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch341 devices\n"); + kfree(ch341); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 1); + ch341->writesize = usb_endpoint_maxp(epwrite) * 20; + ch341->data = data_interface; + ch341->minor = minor; + ch341->dev = usb_dev; + ch341->ctrlsize = ctrlsize; + ch341->readsize = readsize; + ch341->rx_buflimit = num_rx_buf; + + dev_dbg(&intf->dev, "epctrl: %d, epread: %d, epwrite: %d\n", + usb_endpoint_maxp(epctrl), usb_endpoint_maxp(epread), + usb_endpoint_maxp(epwrite)); + + INIT_WORK(&ch341->work, ch341_softint); + init_waitqueue_head(&ch341->wioctl); + spin_lock_init(&ch341->write_lock); + spin_lock_init(&ch341->read_lock); + mutex_init(&ch341->mutex); + ch341->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + tty_port_init(&ch341->port); + ch341->port.ops = &ch341_port_ops; + init_usb_anchor(&ch341->delayed); + ch341->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &ch341->ctrl_dma); + if (!buf) + goto alloc_fail2; + ch341->ctrl_buffer = buf; + + if (ch341_write_buffers_alloc(ch341) < 0) + goto alloc_fail4; + + ch341->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch341->ctrlurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch341_rb *rb = &(ch341->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch341->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch341; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + usb_fill_bulk_urb(urb, ch341->dev, + ch341->rx_endpoint, + rb->base, + ch341->readsize, + ch341_read_bulk_callback, rb); + + ch341->read_urbs[i] = urb; + __set_bit(i, &ch341->read_urbs_free); + } + for (i = 0; i < CH341_NW; i++) { + struct ch341_wb *snd = &(ch341->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, ch341->writesize, ch341_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch341; + } + + usb_set_intfdata(intf, ch341); + + usb_fill_int_urb(ch341->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + ch341->ctrl_buffer, ctrlsize, ch341_ctrl_irq, ch341, + epctrl->bInterval ? epctrl->bInterval : 16); + ch341->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch341->ctrlurb->transfer_dma = ch341->ctrl_dma; + + dev_info(&intf->dev, "ttyCH341USB%d: ch341 USB device\n", minor); + + usb_driver_claim_interface(&ch341_driver, data_interface, ch341); + usb_set_intfdata(data_interface, ch341); + + usb_get_intf(data_interface); + tty_dev = tty_port_register_device(&ch341->port, ch341_tty_driver, minor, + &data_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } + + dev_dbg(&intf->dev, "ch341_probe finished!\n"); + + return 0; + +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_read_buffers_free(ch341); + usb_free_urb(ch341->ctrlurb); +alloc_fail5: + ch341_write_buffers_free(ch341); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, ch341->ctrl_buffer, ch341->ctrl_dma); +alloc_fail2: + ch341_release_minor(ch341); + kfree(ch341); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch341 *ch341) +{ + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); + + cancel_work_sync(&ch341->work); +} + +static void ch341_disconnect(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* sibling interface is already cleaning up */ + if (!ch341) + return; + + mutex_lock(&ch341->mutex); + ch341->disconnected = true; + wake_up_all(&ch341->wioctl); + usb_set_intfdata(ch341->data, NULL); + mutex_unlock(&ch341->mutex); + + tty = tty_port_tty_get(&ch341->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(ch341); + + tty_unregister_device(ch341_tty_driver, ch341->minor); + + usb_free_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_write_buffers_free(ch341); + usb_free_coherent(usb_dev, ch341->ctrlsize, ch341->ctrl_buffer, ch341->ctrl_dma); + ch341_read_buffers_free(ch341); + + usb_driver_release_interface(&ch341_driver, ch341->data); + + tty_port_put(&ch341->port); + dev_info(&intf->dev, "%s\n", "ch341 usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch341_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + int cnt; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch341->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch341->transmitting) { + spin_unlock_irq(&ch341->write_lock); + return -EBUSY; + } + } + cnt = ch341->susp_count++; + spin_unlock_irq(&ch341->write_lock); + + if (cnt) + return 0; + + stop_data_traffic(ch341); + + return 0; +} + +static int ch341_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch341->write_lock); + + if (--ch341->susp_count) + goto out; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) { +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) { +#endif + rv = usb_submit_urb(ch341->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + + ch341_start_wb(ch341, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = ch341_submit_read_urbs(ch341, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch341->write_lock); + + return rv; +} + +static int ch341_reset_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) +#endif + tty_port_tty_hangup(&ch341->port, false); + + return ch341_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ + +static const struct usb_device_id ch341_ids[] = { + { + USB_DEVICE(0x1a86, 0x7523), + }, /* ch340 chip */ + { + USB_DEVICE(0x1a86, 0x7522), + }, /* ch340K chip */ + { + USB_DEVICE(0x1a86, 0x5523), + }, /* ch341 chip */ + { + USB_DEVICE(0x1a86, 0xE523), + }, /* ch330 chip */ + { + USB_DEVICE(0x4348, 0x5523), + }, /* ch340 custom chip */ + {}}; + +MODULE_DEVICE_TABLE(usb, ch341_ids); + +static struct usb_driver ch341_driver = { + .name = "usb_ch341", + .probe = ch341_probe, + .disconnect = ch341_disconnect, +#ifdef CONFIG_PM + .suspend = ch341_suspend, + .resume = ch341_resume, + .reset_resume = ch341_reset_resume, +#endif + .id_table = ch341_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ + +static const struct tty_operations ch341_ops = { + .install = ch341_tty_install, + .open = ch341_tty_open, + .close = ch341_tty_close, + .cleanup = ch341_tty_cleanup, + .hangup = ch341_tty_hangup, + .write = ch341_tty_write, + .write_room = ch341_tty_write_room, + .ioctl = ch341_tty_ioctl, + .chars_in_buffer = ch341_tty_chars_in_buffer, + .break_ctl = ch341_tty_break_ctl, + .set_termios = ch341_tty_set_termios, + .tiocmget = ch341_tty_tiocmget, + .tiocmset = ch341_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init ch341_init(void) +{ + int retval; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver = tty_alloc_driver(CH341_TTY_MINORS, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(ch341_tty_driver)) + return PTR_ERR(ch341_tty_driver); +#else + ch341_tty_driver = alloc_tty_driver(CH341_TTY_MINORS); + if (!ch341_tty_driver) + return -ENOMEM; +#endif + ch341_tty_driver->driver_name = "ch341_uart", + ch341_tty_driver->name = "ttyCH341USB", + ch341_tty_driver->major = CH341_TTY_MAJOR, + ch341_tty_driver->minor_start = 0, + ch341_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + ch341_tty_driver->subtype = SERIAL_TYPE_NORMAL, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; +#endif + ch341_tty_driver->init_termios = tty_std_termios; + ch341_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; + tty_set_operations(ch341_tty_driver, &ch341_ops); + + retval = tty_register_driver(ch341_tty_driver); + if (retval) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + retval = usb_register(&ch341_driver); + if (retval) { + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch341_exit(void) +{ + usb_deregister(&ch341_driver); + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + idr_destroy(&ch341_minors); + printk(KERN_INFO KBUILD_MODNAME ": " + "ch341 driver exit.\n"); +} + +module_init(ch341_init); +module_exit(ch341_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH341_TTY_MAJOR); diff --git a/drivers/CH341SER_LINUX/driver/ch341.h b/drivers/CH341SER_LINUX/driver/ch341.h new file mode 100644 index 0000000..5adaff4 --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/ch341.h @@ -0,0 +1,161 @@ +#ifndef _CH341_H +#define _CH341_H + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define CH341_TTY_MAJOR 169 +#define CH341_TTY_MINORS 256 + +/* + * Requests. + */ + +#define USB_RT_CH341 (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +#define CMD_R 0x95 +#define CMD_W 0x9A +#define CMD_C1 0xA1 +#define CMD_C2 0xA4 +#define CMD_C3 0x5F + +#define CH341_CTO_O 0x10 +#define CH341_CTO_D 0x20 +#define CH341_CTO_R 0x40 +#define CH341_CTI_C 0x01 +#define CH341_CTI_DS 0x02 +#define CH341_CTRL_RI 0x04 +#define CH341_CTI_DC 0x08 +#define CH341_CTI_ST 0x0f + +#define CH341_CTT_M BIT(3) +#define CH341_CTT_F (BIT(2) | BIT(6)) +#define CH341_CTT_P BIT(2) +#define CH341_CTT_O BIT(1) + +#define CH341_L_ER 0x80 +#define CH341_L_ET 0x40 +#define CH341_L_PS 0x38 +#define CH341_L_PM 0x28 +#define CH341_L_PE 0x18 +#define CH341_L_PO 0x08 +#define CH341_L_SB 0x04 +#define CH341_L_D8 0x03 +#define CH341_L_D7 0x02 +#define CH341_L_D6 0x01 +#define CH341_L_D5 0x00 + +#define CH341_RB 0x05 +#define CH341_RL 0x18 +#define CH341_NB 0x01 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ +#define CH341_NW 16 +#define CH341_NR 16 + +struct ch341_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch341 *instance; +}; + +struct ch341_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch341 *instance; +}; + +struct usb_ch341_line_coding { + __le32 dwDTERate; + __u8 bCharFormat; +#define USB_CH341_1_STOP_BITS 0 +#define USB_CH341_1_5_STOP_BITS 1 +#define USB_CH341_2_STOP_BITS 2 + + __u8 bParityType; +#define USB_CH341_NO_PARITY 0 +#define USB_CH341_ODD_PARITY 1 +#define USB_CH341_EVEN_PARITY 2 +#define USB_CH341_MARK_PARITY 3 +#define USB_CH341_SPACE_PARITY 4 + + __u8 bDataBits; +} __attribute__((packed)); + +struct ch341 { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct ch341_wb wb[CH341_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH341_NR]; + struct ch341_rb read_buffers[CH341_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_ch341_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize, ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* ch341 minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int susp_count; /* number of suspended interfaces */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; + bool hardflow; +}; + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) +#endif diff --git a/drivers/CH341SER_LINUX/readme.txt b/drivers/CH341SER_LINUX/readme.txt deleted file mode 100755 index c86009a..0000000 --- a/drivers/CH341SER_LINUX/readme.txt +++ /dev/null @@ -1,19 +0,0 @@ -// ChangeLog -// 1.0 - 1.1 modified to solve transmition between ch341 and ch341 -// 1.1 - 1.2 Support high Linux kernel -Instructions - -Note: 1.Please run followed executable programs as root privilege - 2.Current Driver support versions of linux kernel range from 2.6.25 to 3.13.x - 3.Current Driver support 32bits and 64bits linux systems - -Usage: - (load or unload linux driver of CH34x) - //compile - #make - //load ch34x chips driver - #make load - //unload ch34x chips driver - #make unload -// 1.2 - 1.3 Fix some bugs -