isdbt_port_mtv23x.c 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  1. /*
  2. *
  3. * drivers/media/tdmb/isdbt_port_mtv23x.c
  4. *
  5. * isdbt driver
  6. *
  7. * Copyright (C) (2014, Samsung Electronics)
  8. *
  9. * This program is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation version 2.
  12. *
  13. * This program is distributed "as is" WITHOUT ANY WARRANTY of any
  14. * kind, whether express or implied; without even the implied warranty
  15. * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. */
  19. #include <linux/kernel.h>
  20. #include <linux/errno.h>
  21. #include <linux/device.h>
  22. #include <linux/init.h>
  23. #include <linux/types.h>
  24. #include <linux/fcntl.h>
  25. #include <linux/workqueue.h>
  26. #include <linux/irq.h>
  27. #include <linux/interrupt.h>
  28. #include <linux/io.h>
  29. #include <linux/fs.h>
  30. #include <linux/uaccess.h>
  31. #include <linux/time.h>
  32. #include <linux/timer.h>
  33. #include <linux/vmalloc.h>
  34. #include "isdbt.h"
  35. #include "isdbt_port_mtv23x.h"
  36. #ifndef VM_RESERVED /* for kernel 3.10 */
  37. #define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
  38. #endif
  39. extern void mtv23x_set_port_if(unsigned long interface);
  40. static bool mtv23x_on_air = false;
  41. struct MTV23x_CB *mtv23x_cb_ptr = NULL;
  42. #ifdef _MTV_KERNEL_FILE_DUMP_ENABLE
  43. struct file *mtv_ts_filp = NULL;
  44. #endif
  45. static void tsb_enqueue(unsigned char *ts_chunk)
  46. {
  47. #ifdef DEBUG_TSP_BUF
  48. int readi, writei, num_euqueued_tsp, num_euqueued_seg;
  49. #endif
  50. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  51. if (!tsb_cb->tsbd) {
  52. DMBERR("Not memory mapped\n");
  53. return;
  54. }
  55. if (tsb_cb->tsbd->op_enabled) {
  56. /* Check if the specified tspb is the allocated tspb? */
  57. if (ts_chunk == tsb_cb->seg_buf[tsb_cb->enqueue_seg_idx]) {
  58. /* Update the next index of write-tsp. */
  59. tsb_cb->tsbd->write_idx = tsb_cb->avail_write_tspb_idx;
  60. /* Update the next index of segment. */
  61. tsb_cb->enqueue_seg_idx = tsb_cb->avail_seg_idx;
  62. #ifdef DEBUG_TSP_BUF
  63. readi = tsb_cb->tsbd->read_idx;
  64. writei = tsb_cb->tsbd->write_idx;
  65. if (writei > readi)
  66. num_euqueued_tsp = writei - readi;
  67. else if (writei < readi)
  68. num_euqueued_tsp = tsb_cb->num_total_tsp - (readi - writei);
  69. else
  70. num_euqueued_tsp = 0;
  71. mtv23x_cb_ptr->max_enqueued_tsp_cnt
  72. = MAX(mtv23x_cb_ptr->max_enqueued_tsp_cnt, num_euqueued_tsp);
  73. num_euqueued_seg = num_euqueued_tsp / tsb_cb->num_tsp_per_seg;
  74. mtv23x_cb_ptr->max_enqueued_seg_cnt
  75. = MAX(mtv23x_cb_ptr->max_enqueued_seg_cnt, num_euqueued_seg);
  76. #endif
  77. } else
  78. DMBERR("Invalid the enqueuing chunk address!\n");
  79. }
  80. }
  81. /* Get a TS buffer */
  82. static U8 *tsb_get(void)
  83. {
  84. int readi;
  85. int nwi; /* Next index of tsp buffer to be write. */
  86. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  87. unsigned char *tspb = NULL;
  88. int num_tsp_per_seg = tsb_cb->num_tsp_per_seg;
  89. #ifdef DEBUG_TSP_BUF
  90. int num_used_segment; /* Should NOT zero. */
  91. int write_seg_idx, read_seg_idx;
  92. #endif
  93. if (!tsb_cb->tsbd) {
  94. DMBERR("Not memory mapped\n");
  95. return NULL;
  96. }
  97. if (tsb_cb->tsbd->op_enabled) {
  98. readi = tsb_cb->tsbd->read_idx;
  99. /* Get the next avaliable index of segment to be write in the next time. */
  100. nwi = tsb_cb->avail_write_tspb_idx + num_tsp_per_seg;
  101. if (nwi >= tsb_cb->num_total_tsp)
  102. nwi = 0;
  103. if ((readi < nwi) || (readi >= (nwi + num_tsp_per_seg))) {
  104. tspb = tsb_cb->seg_buf[tsb_cb->avail_seg_idx];
  105. /* Update the writting index of tsp buffer. */
  106. tsb_cb->avail_write_tspb_idx = nwi;
  107. /* Update the avaliable index of segment to be write in the next time. */
  108. if (++tsb_cb->avail_seg_idx >= tsb_cb->num_total_seg)
  109. tsb_cb->avail_seg_idx = 0;
  110. #ifdef DEBUG_TSP_BUF
  111. write_seg_idx = tsb_cb->avail_seg_idx;
  112. read_seg_idx = readi / num_tsp_per_seg;
  113. if (write_seg_idx > read_seg_idx)
  114. num_used_segment = write_seg_idx - read_seg_idx;
  115. else
  116. num_used_segment
  117. = tsb_cb->num_total_seg - (read_seg_idx - write_seg_idx);
  118. DMBMSG("wseg_idx(%d), rseg_idx(%d), num_used_segment(%d)\n",
  119. write_seg_idx, read_seg_idx, num_used_segment);
  120. mtv23x_cb_ptr->max_alloc_seg_cnt
  121. = MAX(mtv23x_cb_ptr->max_alloc_seg_cnt, num_used_segment);
  122. #endif
  123. //DMBMSG("@@ readi(%d), next_writei(%d), avail_seg_idx(%d), tspb(0x%08lX)\n",
  124. // readi, nwi, tsb_cb->avail_seg_idx, (unsigned long)tspb);
  125. } else
  126. DMBERR("Full tsp buffer.\n");
  127. }
  128. return tspb;
  129. }
  130. static inline void tsb_free_mapping_area(void)
  131. {
  132. int i;
  133. unsigned int order;
  134. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  135. order = get_order(tsb_cb->seg_size);
  136. for (i = 0; i < tsb_cb->num_total_seg; i++) {
  137. if (tsb_cb->seg_buf[i]) {
  138. //DMBMSG("SEG[%d]: seg_buf(0x%lX)\n", i, (unsigned long)tsb_cb->seg_buf[i]);
  139. free_pages((unsigned long)tsb_cb->seg_buf[i], order);
  140. tsb_cb->seg_buf[i] = NULL;
  141. }
  142. }
  143. tsb_cb->seg_bufs_allocated = false;
  144. tsb_cb->seg_size = 0;
  145. tsb_cb->num_total_seg = 0;
  146. if (tsb_cb->tsbd) {
  147. order = get_order(tsb_cb->desc_size);
  148. free_pages((unsigned long)tsb_cb->tsbd, order);
  149. tsb_cb->tsbd = NULL;
  150. tsb_cb->desc_size = 0;
  151. }
  152. }
  153. static inline int tsb_alloc_mapping_area(unsigned int desc_size,
  154. unsigned int seg_size, int num_seg)
  155. {
  156. int i, ret;
  157. unsigned int order;
  158. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  159. /* Allocate the TSB descriptor. */
  160. order = get_order(desc_size);
  161. tsb_cb->tsbd
  162. = (struct TSB_DESC_INFO *)__get_dma_pages(GFP_KERNEL, order);
  163. if (!tsb_cb->tsbd) {
  164. DMBMSG("DESC allocation error\n");
  165. return -ENOMEM;
  166. }
  167. /* Allocate the TSB segments. */
  168. order = get_order(seg_size);
  169. DMBMSG("SEG order(%u)\n", order);
  170. if (order > MAX_ORDER) {
  171. DMBMSG("Invalid page order value of segment (%u)\n", order);
  172. ret = -ENOMEM;
  173. goto free_tsb;
  174. }
  175. for (i = 0; i < num_seg; i++) {
  176. tsb_cb->seg_buf[i] = (U8 *)__get_dma_pages(GFP_KERNEL, order);
  177. if (!tsb_cb->seg_buf[i]) {
  178. DMBMSG("SEG[%u] allocation error\n", i);
  179. ret = -ENOMEM;
  180. goto free_tsb;
  181. }
  182. }
  183. tsb_cb->seg_bufs_allocated = true;
  184. DMBMSG("Success\n");
  185. return 0;
  186. free_tsb:
  187. tsb_free_mapping_area();
  188. return ret;
  189. }
  190. static void mtv23x_mmap_close(struct vm_area_struct *vma)
  191. {
  192. DMBMSG("Entered. mmap_completed(%d)\n", mtv23x_cb_ptr->tsb_cb.mmap_completed);
  193. mtv23x_cb_ptr->tsb_cb.mmap_completed = false;
  194. DMBMSG("Leaved...\n");
  195. }
  196. static const struct vm_operations_struct mtv23x_mmap_ops = {
  197. .close = mtv23x_mmap_close,
  198. };
  199. static int mtv23x_mmap(struct file *filp, struct vm_area_struct *vma)
  200. {
  201. int ret, num_total_seg;
  202. unsigned int i, mmap_size, desc_size, seg_size;
  203. unsigned long pfn;
  204. unsigned long start = vma->vm_start;
  205. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  206. vma->vm_flags |= VM_RESERVED;
  207. vma->vm_ops = &mtv23x_mmap_ops;
  208. mmap_size = vma->vm_end - vma->vm_start;
  209. #if 0
  210. DMBMSG("mmap_size(0x%X), vm_start(0x%lX), vm_page_prot(0x%lX)\n",
  211. mmap_size, vma->vm_start, vma->vm_page_prot);
  212. #endif
  213. if (mmap_size & (~PAGE_MASK)) {
  214. DMBERR("Must align with PAGE size\n");
  215. return -EINVAL;
  216. }
  217. if (tsb_cb->mmap_completed == true) {
  218. DMBERR("Already mapped!\n");
  219. return 0;
  220. }
  221. seg_size = vma->vm_pgoff << PAGE_SHIFT;
  222. num_total_seg = (mmap_size - PAGE_SIZE) / seg_size;
  223. desc_size = mmap_size - (num_total_seg * seg_size);
  224. /* Save */
  225. tsb_cb->desc_size = desc_size;
  226. tsb_cb->seg_size = seg_size;
  227. tsb_cb->num_total_seg = num_total_seg;
  228. #if 1
  229. DMBMSG("mmap_size(%u), seg_size(%u) #seg(%d), desc_size(%u)\n",
  230. mmap_size, seg_size, num_total_seg, desc_size);
  231. #endif
  232. if (num_total_seg > MAX_NUM_TSB_SEG) {
  233. DMBERR("Too large request #seg! kernel(%u), req(%d)\n",
  234. MAX_NUM_TSB_SEG, num_total_seg);
  235. return -ENOMEM;
  236. }
  237. if (desc_size > MAX_TSB_DESC_SIZE) {
  238. DMBERR("Too large request desc size! kernel(%u), req(%u)\n",
  239. MAX_TSB_DESC_SIZE, desc_size);
  240. return -ENOMEM;
  241. }
  242. if (seg_size > MAX_TSB_SEG_SIZE) {
  243. DMBERR("Too large request seg size! kernel(%u), req(%u)\n",
  244. MAX_TSB_SEG_SIZE, seg_size);
  245. return -ENOMEM;
  246. }
  247. if (!tsb_cb->tsbd) {
  248. DMBERR("TSB DESC was NOT allocated!\n");
  249. return -ENOMEM;
  250. }
  251. if (tsb_cb->seg_bufs_allocated == false) {
  252. DMBERR("TSB SEG are NOT allocated!\n");
  253. return -ENOMEM;
  254. }
  255. /* Map the shared informations. */
  256. pfn = virt_to_phys(tsb_cb->tsbd) >> PAGE_SHIFT;
  257. if (remap_pfn_range(vma, vma->vm_start, pfn, desc_size, vma->vm_page_prot)) {
  258. DMBERR("HDR remap_pfn_range() error!\n");
  259. ret = -EAGAIN;
  260. goto out;
  261. }
  262. /* Init descriptor except the addres of segments */
  263. tsb_cb->tsbd->op_enabled = 0;
  264. tsb_cb->tsbd->read_idx = 0;
  265. tsb_cb->tsbd->write_idx = 0;
  266. #if 0
  267. DMBMSG("tsbd(0x%lX), pfn(0x%lX), start(0x%lX)\n",
  268. (unsigned long)tsb_cb->tsbd, pfn, start);
  269. #endif
  270. start += desc_size; /* Avdance VMA. */
  271. /* Allocate and map the TSP buffer segments. */
  272. for (i = 0; i < num_total_seg; i++) {
  273. pfn = virt_to_phys(tsb_cb->seg_buf[i]) >> PAGE_SHIFT;
  274. #if 0
  275. DMBMSG("SEG[%d]: seg_buf(0x%lX) pfn(0x%lX) start(0x%lX)\n",
  276. i, (unsigned long)tsb_cb->seg_buf[i], pfn, start);
  277. #endif
  278. if (remap_pfn_range(vma, start, pfn, seg_size, vma->vm_page_prot)) {
  279. DMBERR("SEG[%u] remap_pfn_range() error!\n", i);
  280. ret = -EAGAIN;
  281. goto out;
  282. }
  283. tsb_cb->tsbd->seg_base[i] = start;
  284. start += seg_size;
  285. }
  286. tsb_cb->mmap_completed = true;
  287. return 0;
  288. out:
  289. return ret;
  290. }
  291. static void mtv23x_power_off(void)
  292. {
  293. DMBMSG("mtv23x_power_off\n");
  294. if (mtv23x_cb_ptr->is_power_on) {
  295. mtv23x_on_air = false;
  296. // isdbt_control_irq(false);
  297. isdbt_control_gpio(false);
  298. mtv23x_cb_ptr->is_power_on = false;
  299. mtv23x_cb_ptr->tsout_enabled = false;
  300. RTV_GUARD_DEINIT;
  301. }
  302. }
  303. static INLINE int __mtv23x_power_on(unsigned long arg)
  304. {
  305. int ret;
  306. enum E_RTV_BANDWIDTH_TYPE bandwidth;
  307. IOCTL_ISDBT_POWER_ON_INFO __user *argp
  308. = (IOCTL_ISDBT_POWER_ON_INFO __user *)arg;
  309. #if defined(RTV_IF_SPI) || defined(RTV_IF_CSI656_RAW_8BIT_ENABLE)
  310. int i;
  311. for (i = 0; i < MAX_NUM_RTV_SERVICE; i++) {
  312. if (get_user(mtv23x_cb_ptr->intr_size[i],
  313. &argp->spi_intr_size[i]))
  314. return -EFAULT;
  315. DMBMSG("intr_size[%d]: %u\n", i, mtv23x_cb_ptr->intr_size[i]);
  316. }
  317. mtv23x_cb_ptr->cfged_tsp_chunk_size = 0;
  318. #endif
  319. if (get_user(bandwidth, &argp->bandwidth))
  320. return -EFAULT;
  321. mtv23x_cb_ptr->tsout_enabled = false;
  322. mtv23x_cb_ptr->cfged_svc = RTV_SERVICE_INVALID;
  323. ret = rtvMTV23x_Initialize(bandwidth);
  324. if (ret != RTV_SUCCESS) {
  325. DMBERR("Tuner initialization failed: %d\n", ret);
  326. if (put_user(ret, &argp->tuner_err_code))
  327. return -EFAULT;
  328. return -EIO; /* error occurred during the open() */
  329. }
  330. return ret;
  331. }
  332. static int mtv23x_power_on(unsigned long arg)
  333. {
  334. int ret;
  335. IOCTL_ISDBT_POWER_ON_INFO __user *argp
  336. = (IOCTL_ISDBT_POWER_ON_INFO __user *)arg;
  337. DMBMSG("mtv23x_power_on\n");
  338. if (mtv23x_cb_ptr->is_power_on) {
  339. return 0;
  340. } else {
  341. isdbt_control_gpio(true);
  342. RTV_GUARD_INIT;
  343. mtv23x_set_port_if((unsigned long)isdbt_get_if_handle());
  344. ret = __mtv23x_power_on(arg);
  345. if (ret) {
  346. DMBERR("Tuner initialization failed: %d\n", ret);
  347. isdbt_control_gpio(false);
  348. if (put_user(ret, &argp->tuner_err_code))
  349. return -EFAULT;
  350. return ret;
  351. } else {
  352. // isdbt_control_irq(true);
  353. mtv23x_cb_ptr->is_power_on = true;
  354. return 0;
  355. }
  356. }
  357. }
  358. #if defined(RTV_IF_SPI) || defined(RTV_IF_SPI_TSIFx)
  359. static unsigned long diff_jiffies_1st, diff_jiffies0, hours_cnt;
  360. #endif
  361. #define ISDBT_VALID_SVC_MASK (1<<RTV_SERVICE_UHF_ISDBT_1seg)
  362. /*============================================================================
  363. * Test IO control commands(0 ~ 10)
  364. *==========================================================================*/
  365. static int test_register_io(unsigned long arg, unsigned int cmd)
  366. {
  367. int ret = 0;
  368. unsigned int page, addr, write_data, read_cnt, i;
  369. U8 value;
  370. #if defined(RTV_IF_SPI) || defined(RTV_IF_SPI_TSIFx)
  371. unsigned long diff_jiffies1;
  372. unsigned int elapsed_ms;
  373. unsigned long param1;
  374. #endif
  375. U8 *reg_read_buf;
  376. U8 *src_ptr, *dst_ptr;
  377. IOCTL_REG_ACCESS_INFO __user *argp
  378. = (IOCTL_REG_ACCESS_INFO __user *)arg;
  379. if (mtv23x_cb_ptr->is_power_on == FALSE) {
  380. DMBMSG("[mtv] Power Down state!Must Power ON\n");
  381. return -EFAULT;
  382. }
  383. if (get_user(page, &argp->page))
  384. return -EFAULT;
  385. if (get_user(addr, &argp->addr))
  386. return -EFAULT;
  387. RTV_GUARD_LOCK;
  388. switch (cmd) {
  389. case IOCTL_TEST_REG_SINGLE_READ:
  390. RTV_REG_MAP_SEL(page);
  391. value = RTV_REG_GET(addr);
  392. if (put_user(value, &argp->read_data[0])) {
  393. ret = -EFAULT;
  394. goto regio_exit;
  395. }
  396. break;
  397. case IOCTL_TEST_REG_BURST_READ:
  398. if (get_user(read_cnt, &argp->read_cnt)) {
  399. ret = -EFAULT;
  400. goto regio_exit;
  401. }
  402. reg_read_buf = kmalloc(MAX_NUM_MTV_REG_READ_BUF, GFP_KERNEL);
  403. if (reg_read_buf == NULL) {
  404. DMBERR("Register buffer allocation error\n");
  405. ret = -ENOMEM;
  406. goto regio_exit;
  407. }
  408. RTV_REG_MAP_SEL(page);
  409. RTV_REG_BURST_GET(addr, reg_read_buf, read_cnt);
  410. src_ptr = &reg_read_buf[0];
  411. dst_ptr = argp->read_data;
  412. for (i = 0; i< read_cnt; i++, src_ptr++, dst_ptr++) {
  413. if(put_user(*src_ptr, dst_ptr)) {
  414. ret = -EFAULT;
  415. break;
  416. }
  417. }
  418. kfree(reg_read_buf);
  419. break;
  420. case IOCTL_TEST_REG_WRITE:
  421. if (get_user(write_data, &argp->write_data)) {
  422. ret = -EFAULT;
  423. goto regio_exit;
  424. }
  425. RTV_REG_MAP_SEL(page);
  426. RTV_REG_SET(addr, write_data);
  427. break;
  428. case IOCTL_TEST_REG_SPI_MEM_READ:
  429. #if defined(RTV_IF_SPI)
  430. if (get_user(write_data, &argp->write_data)) {
  431. ret = -EFAULT;
  432. goto regio_exit;
  433. }
  434. if (get_user(read_cnt, &argp->read_cnt)) {
  435. ret = -EFAULT;
  436. goto regio_exit;
  437. }
  438. if (get_user(param1, &argp->param1)) {
  439. ret = -EFAULT;
  440. goto regio_exit;
  441. }
  442. reg_read_buf = kmalloc(MAX_NUM_MTV_REG_READ_BUF, GFP_KERNEL);
  443. if (reg_read_buf == NULL) {
  444. DMBERR("Register buffer allocation error\n");
  445. ret = -ENOMEM;
  446. goto regio_exit;
  447. }
  448. if (param1 == 0) {
  449. diff_jiffies_1st = diff_jiffies0 = get_jiffies_64();
  450. hours_cnt = 0;
  451. DMBMSG("START [AGING SPI Memory Test with Single IO]\n");
  452. }
  453. RTV_REG_MAP_SEL(page);
  454. RTV_REG_SET(addr, write_data);
  455. RTV_REG_MAP_SEL(SPI_MEM_PAGE);
  456. RTV_REG_BURST_GET(0x10, reg_read_buf, read_cnt);
  457. RTV_REG_MAP_SEL(page);
  458. value = RTV_REG_GET(addr);
  459. diff_jiffies1 = get_jiffies_64();
  460. elapsed_ms = jiffies_to_msecs(diff_jiffies1-diff_jiffies0);
  461. if (elapsed_ms >= (1000 * 60 * 60)) {
  462. diff_jiffies0 = get_jiffies_64(); /* Re-start */
  463. hours_cnt++;
  464. DMBMSG("\t %lu hours elaspesed...\n", hours_cnt);
  465. }
  466. if (write_data != value) {
  467. unsigned int min, sec;
  468. elapsed_ms = jiffies_to_msecs(diff_jiffies1-diff_jiffies_1st);
  469. sec = elapsed_ms / 1000;
  470. min = sec / 60;
  471. DMBMSG("END [AGING SPI Memory Test with Single IO]\n");
  472. DMBMSG("Total minutes: %u\n", min);
  473. }
  474. if (put_user(value, &argp->read_data[0]))
  475. ret = -EFAULT;
  476. kfree(reg_read_buf);
  477. #else
  478. DMBERR("Not SPI interface\n");
  479. #endif
  480. break;
  481. case IOCTL_TEST_REG_ONLY_SPI_MEM_READ:
  482. #if defined(RTV_IF_SPI)
  483. if (get_user(read_cnt, &argp->read_cnt)) {
  484. ret = -EFAULT;
  485. goto regio_exit;
  486. }
  487. if (get_user(write_data, &argp->write_data)) {
  488. ret = -EFAULT;
  489. goto regio_exit;
  490. }
  491. reg_read_buf = kmalloc(MAX_NUM_MTV_REG_READ_BUF, GFP_KERNEL);
  492. if (reg_read_buf == NULL) {
  493. DMBERR("Register buffer allocation error\n");
  494. ret = -ENOMEM;
  495. goto regio_exit;
  496. }
  497. if (write_data == 0) /* only one-time page selection */
  498. RTV_REG_MAP_SEL(page);
  499. RTV_REG_BURST_GET(addr, reg_read_buf, read_cnt);
  500. src_ptr = reg_read_buf;
  501. dst_ptr = argp->read_data;
  502. for (i = 0; i< read_cnt; i++, src_ptr++, dst_ptr++) {
  503. if(put_user(*src_ptr, dst_ptr)) {
  504. ret = -EFAULT;
  505. break;
  506. }
  507. }
  508. kfree(reg_read_buf);
  509. #else
  510. DMBERR("Not SPI interface\n");
  511. #endif
  512. break;
  513. default:
  514. break;
  515. }
  516. regio_exit:
  517. RTV_GUARD_FREE;
  518. return 0;
  519. }
  520. static int test_gpio(unsigned long arg, unsigned int cmd)
  521. {
  522. unsigned int pin, value;
  523. IOCTL_GPIO_ACCESS_INFO __user *argp = (IOCTL_GPIO_ACCESS_INFO __user *)arg;
  524. if (get_user(pin, &argp->pin))
  525. return -EFAULT;
  526. switch (cmd) {
  527. case IOCTL_TEST_GPIO_SET:
  528. if(get_user(value, &argp->value))
  529. return -EFAULT;
  530. gpio_set_value(pin, value);
  531. break;
  532. case IOCTL_TEST_GPIO_GET:
  533. value = gpio_get_value(pin);
  534. if(put_user(value, &argp->value))
  535. return -EFAULT;
  536. }
  537. return 0;
  538. }
  539. static void test_power_on_off(unsigned int cmd)
  540. {
  541. switch (cmd) {
  542. case IOCTL_TEST_MTV_POWER_ON:
  543. DMBMSG("IOCTL_TEST_MTV_POWER_ON\n");
  544. if (mtv23x_cb_ptr->is_power_on == FALSE) {
  545. isdbt_control_gpio(true);
  546. rtvMTV23x_Initialize(RTV_BW_MODE_430KHZ);
  547. mtv23x_cb_ptr->is_power_on = TRUE;
  548. }
  549. break;
  550. case IOCTL_TEST_MTV_POWER_OFF:
  551. if(mtv23x_cb_ptr->is_power_on == TRUE) {
  552. isdbt_control_gpio(false);
  553. mtv23x_cb_ptr->is_power_on = FALSE;
  554. }
  555. break;
  556. }
  557. }
  558. /*==============================================================================
  559. * TDMB IO control commands(30 ~ 49)
  560. *============================================================================*/
  561. static INLINE int mtv23x_get_signal_qual_info(unsigned long arg)
  562. {
  563. IOCTL_ISDBT_SIGNAL_QUAL_INFO sig;
  564. void __user *argp = (void __user *)arg;
  565. sig.lock_mask = rtvMTV23x_GetLockStatus();
  566. sig.rssi = rtvMTV23x_GetRSSI();
  567. sig.ber_layer_A = rtvMTV23x_GetBER();
  568. sig.ber_layer_B = rtvMTV23x_GetBER2();
  569. sig.per_layer_A = rtvMTV23x_GetPER();
  570. sig.per_layer_B = rtvMTV23x_GetPER2();
  571. sig.cnr_layer_A = rtvMTV23x_GetCNR_LayerA();
  572. sig.ant_level_layer_A = rtvMTV23x_GetAntennaLevel_1seg(sig.cnr_layer_A);
  573. sig.cnr_layer_B = rtvMTV23x_GetCNR_LayerB();
  574. sig.ant_level_layer_B = rtvMTV23x_GetAntennaLevel(sig.cnr_layer_B);
  575. if (copy_to_user(argp, &sig, sizeof(IOCTL_ISDBT_SIGNAL_QUAL_INFO)))
  576. return -EFAULT;
  577. SHOW_ISDBT_DEBUG_STAT;
  578. return 0;
  579. }
  580. static INLINE int mtv23x_get_cnr(unsigned long arg)
  581. {
  582. int cnr = (int)rtvMTV23x_GetCNR();
  583. if (put_user(cnr, (int *)arg))
  584. return -EFAULT;
  585. SHOW_ISDBT_DEBUG_STAT;
  586. return 0;
  587. }
  588. static INLINE int mtv23x_get_rssi(unsigned long arg)
  589. {
  590. int rssi = rtvMTV23x_GetRSSI();
  591. if (put_user(rssi, (int *)arg))
  592. return -EFAULT;
  593. SHOW_ISDBT_DEBUG_STAT;
  594. return 0;
  595. }
  596. static INLINE int mtv23x_get_ber_per_info(unsigned long arg)
  597. {
  598. IOCTL_ISDBT_BER_PER_INFO info;
  599. void __user *argp = (void __user *)arg;
  600. info.ber_layer_A = rtvMTV23x_GetBER();
  601. info.ber_layer_B = rtvMTV23x_GetBER2();
  602. info.per_layer_A = rtvMTV23x_GetPER();
  603. info.per_layer_B = rtvMTV23x_GetPER2();
  604. if (copy_to_user(argp, &info, sizeof(IOCTL_ISDBT_BER_PER_INFO)))
  605. return -EFAULT;
  606. SHOW_ISDBT_DEBUG_STAT;
  607. return 0;
  608. }
  609. static INLINE void mtv23x_disable_standby_mode(unsigned long arg)
  610. {
  611. rtvMTV23x_StandbyMode(0);
  612. }
  613. static INLINE void mtv23x_enable_standby_mode(unsigned long arg)
  614. {
  615. rtvMTV23x_StandbyMode(1);
  616. }
  617. static INLINE int mtv23x_get_lock_status(unsigned long arg)
  618. {
  619. unsigned int lock_mask = rtvMTV23x_GetLockStatus();
  620. if (put_user(lock_mask, (unsigned int *)arg))
  621. return -EFAULT;
  622. return 0;
  623. }
  624. static INLINE int mtv23x_get_signal_info(unsigned long arg)
  625. {
  626. IOCTL_ISDBT_SIGNAL_INFO sig;
  627. void __user *argp = (void __user *)arg;
  628. sig.lock_mask = rtvMTV23x_GetLockStatus();
  629. sig.ber = rtvMTV23x_GetBER();
  630. sig.cnr = rtvMTV23x_GetCNR();
  631. sig.per = rtvMTV23x_GetPER();
  632. sig.rssi = rtvMTV23x_GetRSSI();
  633. sig.ant_level = rtvMTV23x_GetAntennaLevel(sig.cnr);
  634. if (copy_to_user(argp, &sig, sizeof(IOCTL_ISDBT_SIGNAL_INFO)))
  635. return -EFAULT;
  636. SHOW_ISDBT_DEBUG_STAT;
  637. return 0;
  638. }
  639. static INLINE int mtv23x_set_channel(unsigned long arg)
  640. {
  641. int ret = 0;
  642. unsigned int freq_khz, subch_id, intr_size;
  643. enum E_RTV_SERVICE_TYPE svc_type;
  644. enum E_RTV_BANDWIDTH_TYPE bw;
  645. IOCTL_ISDBT_SET_CH_INFO __user *argp
  646. = (IOCTL_ISDBT_SET_CH_INFO __user *)arg;
  647. if (get_user(freq_khz, &argp->freq_khz))
  648. return -EFAULT;
  649. if (get_user(subch_id, &argp->subch_id))
  650. return -EFAULT;
  651. if (get_user(svc_type, &argp->svc_type))
  652. return -EFAULT;
  653. if (get_user(bw, &argp->bandwidth))
  654. return -EFAULT;
  655. if (!((1<<svc_type) & ISDBT_VALID_SVC_MASK)) {
  656. DMBERR("Invaild service type: %d\n", svc_type);
  657. mtv23x_cb_ptr->cfged_svc = RTV_SERVICE_INVALID;
  658. return -EINVAL;
  659. }
  660. DMBMSG("freq_khz(%u), subch_id(%u), svc_type(%u), bandwidth(%d)\n",
  661. freq_khz, subch_id, svc_type, bw);
  662. #if defined(RTV_IF_SPI)
  663. intr_size = mtv23x_cb_ptr->intr_size[svc_type];
  664. #else
  665. intr_size = 0;
  666. #endif
  667. mtv23x_cb_ptr->cfged_svc = svc_type;
  668. mtv23x_cb_ptr->freq_khz = freq_khz;
  669. ret = rtvMTV23x_SetFrequency(freq_khz, subch_id, svc_type, bw, intr_size);
  670. if (ret != RTV_SUCCESS) {
  671. DMBERR("failed: %d\n", ret);
  672. if (put_user(ret, &argp->tuner_err_code))
  673. return -EFAULT;
  674. return -EIO;
  675. }
  676. DMBMSG("Leave...\n");
  677. return 0;
  678. }
  679. static INLINE int mtv23x_scan_channel(unsigned long arg)
  680. {
  681. int ret;
  682. unsigned int freq_khz, subch_id, intr_size;
  683. enum E_RTV_SERVICE_TYPE svc_type;
  684. enum E_RTV_BANDWIDTH_TYPE bw;
  685. IOCTL_ISDBT_SCAN_INFO __user *argp
  686. = (IOCTL_ISDBT_SCAN_INFO __user *)arg;
  687. if (get_user(freq_khz, &argp->freq_khz))
  688. return -EFAULT;
  689. if (get_user(subch_id, &argp->subch_id))
  690. return -EFAULT;
  691. if (get_user(svc_type, &argp->svc_type))
  692. return -EFAULT;
  693. if (get_user(bw, &argp->bandwidth))
  694. return -EFAULT;
  695. if (!((1<<svc_type) & ISDBT_VALID_SVC_MASK)) {
  696. DMBERR("Invaild service type: %d\n", svc_type);
  697. mtv23x_cb_ptr->cfged_svc = RTV_SERVICE_INVALID;
  698. return -EINVAL;
  699. }
  700. #if defined(RTV_IF_SPI)
  701. intr_size = mtv23x_cb_ptr->intr_size[svc_type];
  702. #else
  703. intr_size = 0;
  704. #endif
  705. mtv23x_cb_ptr->cfged_svc = svc_type;
  706. mtv23x_cb_ptr->freq_khz = freq_khz;
  707. ret = rtvMTV23x_ScanFrequency(freq_khz, subch_id, svc_type, bw, intr_size);
  708. if (ret == RTV_SUCCESS)
  709. return 0;
  710. else {
  711. if(ret != RTV_CHANNEL_NOT_DETECTED)
  712. DMBERR("Device error: %d\n", ret);
  713. /* Copy the tuner error-code to application */
  714. if (put_user(ret, &argp->tuner_err_code))
  715. return -EFAULT;
  716. return -EINVAL;
  717. }
  718. }
  719. static INLINE int mtv23x_disable_ts_out(void)
  720. {
  721. int ret = 0;
  722. DMBMSG("Enter\n");
  723. if (!mtv23x_cb_ptr->tsout_enabled) {
  724. DMBMSG("Already TS out Disabled\n");
  725. return 0;
  726. }
  727. mtv23x_cb_ptr->tsout_enabled = false;
  728. rtvMTV23x_DisableStreamOut();
  729. #ifdef _MTV_KERNEL_FILE_DUMP_ENABLE
  730. mtv_ts_dump_kfile_close();
  731. #endif
  732. DMBMSG("Leave\n");
  733. return ret;
  734. }
  735. static INLINE int mtv23x_enable_ts_out(void)
  736. {
  737. int ret = 0;
  738. #if defined(RTV_IF_SPI) || defined(RTV_IF_CSI656_RAW_8BIT_ENABLE)
  739. struct TSB_CB_INFO *tsb_cb = &mtv23x_cb_ptr->tsb_cb;
  740. #endif
  741. //DMBMSG("ENTER\n");
  742. if (mtv23x_cb_ptr->tsout_enabled) {
  743. DMBMSG("Already TS out Enabled\n");
  744. return 0;
  745. }
  746. RESET_DEBUG_INTR_STAT;
  747. RESET_DEBUG_TSPB_STAT;
  748. if (!((1<<mtv23x_cb_ptr->cfged_svc) & ISDBT_VALID_SVC_MASK)) {
  749. DMBERR("Invaild configured service type: %d\n",
  750. mtv23x_cb_ptr->cfged_svc);
  751. mtv23x_cb_ptr->cfged_svc = RTV_SERVICE_INVALID;
  752. return -EINVAL;
  753. }
  754. #if defined(RTV_IF_SPI) || defined(RTV_IF_CSI656_RAW_8BIT_ENABLE)
  755. /* Setup the tsb_cb stuff to process interrupt. */
  756. tsb_cb->avail_seg_idx = 0;
  757. tsb_cb->avail_write_tspb_idx = 0;
  758. tsb_cb->enqueue_seg_idx = 0;
  759. if (mtv23x_cb_ptr->intr_size[mtv23x_cb_ptr->cfged_svc]
  760. != mtv23x_cb_ptr->cfged_tsp_chunk_size) {
  761. mtv23x_cb_ptr->cfged_tsp_chunk_size
  762. = mtv23x_cb_ptr->intr_size[mtv23x_cb_ptr->cfged_svc];
  763. tsb_cb->num_tsp_per_seg
  764. = mtv23x_cb_ptr->intr_size[mtv23x_cb_ptr->cfged_svc]
  765. / RTV_TSP_XFER_SIZE;
  766. tsb_cb->num_total_tsp
  767. = tsb_cb->num_tsp_per_seg * tsb_cb->num_total_seg;
  768. }
  769. DMBMSG("svc_type(%d), #tsp_per_seg(%u), #seg(%u), #total_tsp(%u)\n",
  770. mtv23x_cb_ptr->cfged_svc, tsb_cb->num_tsp_per_seg,
  771. tsb_cb->num_total_seg, tsb_cb->num_total_tsp);
  772. #endif
  773. #ifdef _MTV_KERNEL_FILE_DUMP_ENABLE
  774. ret = mtv_ts_dump_kfile_open(mtv23x_cb_ptr->freq_khz);
  775. if (ret != 0)
  776. return ret;
  777. #endif
  778. mtv23x_cb_ptr->tsout_enabled = true;
  779. rtvMTV23x_EnableStreamOut();
  780. //DMBMSG("END\n");
  781. return ret;
  782. }
  783. static long mtv23x_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  784. {
  785. int ret = 0;
  786. mutex_lock(&mtv23x_cb_ptr->ioctl_lock);
  787. switch (cmd) {
  788. case IOCTL_ISDBT_POWER_ON:
  789. ret = isdbt_power_on(arg);
  790. if (ret)
  791. isdbt_power_off();
  792. break;
  793. case IOCTL_ISDBT_POWER_OFF:
  794. mtv23x_disable_ts_out();
  795. isdbt_power_off();
  796. break;
  797. case IOCTL_ISDBT_SCAN_CHANNEL:
  798. ret = mtv23x_scan_channel(arg);
  799. break;
  800. case IOCTL_ISDBT_SET_CHANNEL:
  801. ret = mtv23x_set_channel(arg);
  802. break;
  803. case IOCTL_ISDBT_START_TS:
  804. ret = mtv23x_enable_ts_out();
  805. break;
  806. case IOCTL_ISDBT_STOP_TS:
  807. mtv23x_disable_ts_out();
  808. break;
  809. case IOCTL_ISDBT_GET_LOCK_STATUS:
  810. ret = mtv23x_get_lock_status(arg);
  811. break;
  812. case IOCTL_ISDBT_GET_SIGNAL_INFO:
  813. ret = mtv23x_get_signal_info(arg);
  814. break;
  815. case IOCTL_ISDBT_SUSPEND:
  816. mtv23x_enable_standby_mode(arg);
  817. break;
  818. case IOCTL_ISDBT_RESUME:
  819. mtv23x_disable_standby_mode(arg);
  820. break;
  821. case IOCTL_ISDBT_GET_BER_PER_INFO:
  822. ret = mtv23x_get_ber_per_info(arg);
  823. break;
  824. case IOCTL_ISDBT_GET_RSSI:
  825. ret = mtv23x_get_rssi(arg);
  826. break;
  827. case IOCTL_ISDBT_GET_CNR:
  828. ret = mtv23x_get_cnr(arg);
  829. break;
  830. case IOCTL_ISDBT_GET_SIGNAL_QUAL_INFO:
  831. ret = mtv23x_get_signal_qual_info(arg);
  832. break;
  833. /* Test IO command */
  834. case IOCTL_TEST_GPIO_SET:
  835. case IOCTL_TEST_GPIO_GET:
  836. ret = test_gpio(arg, cmd);
  837. break;
  838. case IOCTL_TEST_MTV_POWER_ON:
  839. case IOCTL_TEST_MTV_POWER_OFF:
  840. test_power_on_off(cmd);
  841. break;
  842. case IOCTL_TEST_REG_SINGLE_READ:
  843. case IOCTL_TEST_REG_BURST_READ:
  844. case IOCTL_TEST_REG_WRITE:
  845. case IOCTL_TEST_REG_SPI_MEM_READ:
  846. case IOCTL_TEST_REG_ONLY_SPI_MEM_READ:
  847. ret = test_register_io(arg, cmd);
  848. break;
  849. default:
  850. DMBERR("Invalid ioctl command: 0x%X\n", cmd);
  851. ret = -ENOIOCTLCMD;
  852. break;
  853. }
  854. mutex_unlock(&mtv23x_cb_ptr->ioctl_lock);
  855. return ret;
  856. }
  857. irqreturn_t mtv23x_irq_handler(int irq, void *param)
  858. {
  859. U8 *tspb = NULL; /* reset */
  860. UINT intr_size;
  861. U8 istatus;
  862. if (mtv23x_cb_ptr->tsout_enabled == false) {
  863. return IRQ_HANDLED;
  864. }
  865. /*
  866. ¸¸¾à óÀ½À̸é
  867. #ifdef SCHED_FIFO_USE
  868. struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
  869. sched_setscheduler(current, SCHED_FIFO, &param);
  870. #else
  871. set_user_nice(current, -20);
  872. #endif
  873. */
  874. RTV_GUARD_LOCK;
  875. RTV_REG_MAP_SEL(SPI_CTRL_PAGE);
  876. intr_size = rtvMTV23x_GetInterruptSize();
  877. /* Read the register of interrupt status. */
  878. istatus = RTV_REG_GET(0x10);
  879. //DMBMSG("$$istatus(0x%02X)\n", istatus);
  880. if (istatus & SPI_UNDERFLOW_INTR) {
  881. RTV_REG_SET(0x2A, 1);
  882. RTV_REG_SET(0x2A, 0);
  883. DMBMSG("UDF: 0x%02X\n", istatus);
  884. goto exit_isr;
  885. }
  886. if (istatus & (SPI_THRESHOLD_INTR|SPI_OVERFLOW_INTR)) {
  887. /* Allocate a TS buffer from shared memory. */
  888. tspb = tsb_get();
  889. if (tspb) {
  890. RTV_REG_MAP_SEL(SPI_MEM_PAGE);
  891. RTV_REG_BURST_GET(0x10, tspb, intr_size);
  892. #ifdef _MTV_KERNEL_FILE_DUMP_ENABLE
  893. mtv_ts_dump_kfile_write(tspb, intr_size);
  894. #endif
  895. #if 0
  896. {
  897. UINT i;
  898. const U8 *tspb = (const U8 *)tspb;
  899. for (i = 0; i < size/188; i++, tsp_buf_ptr += 188) {
  900. DMBMSG("[%d] 0x%02X 0x%02X 0x%02X 0x%02X | 0x%02X\n",
  901. i, tsp_buf_ptr[0], tsp_buf_ptr[1],
  902. tsp_buf_ptr[2], tsp_buf_ptr[3],
  903. tsp_buf_ptr[187]);
  904. }
  905. }
  906. #endif
  907. /* Enqueue */
  908. tsb_enqueue(tspb);
  909. DMB_LEVEL_INTR_INC;
  910. if (istatus & SPI_OVERFLOW_INTR) {
  911. DMB_OVF_INTR_INC;
  912. DMBMSG("OVF: 0x%02X\n", istatus);
  913. }
  914. } else {
  915. RTV_REG_SET(0x2A, 1); /* SRAM init */
  916. RTV_REG_SET(0x2A, 0);
  917. #ifdef DEBUG_TSP_BUF
  918. mtv23x_cb_ptr->alloc_tspb_err_cnt++;
  919. #endif
  920. DMBERR("No more TSP buffer from pool.\n");
  921. }
  922. } else
  923. DMBMSG("No data interrupt (0x%02X)\n", istatus);
  924. exit_isr:
  925. RTV_GUARD_FREE;
  926. return IRQ_HANDLED;
  927. }
  928. static int mtv23x_remove(void)
  929. {
  930. mutex_lock(&mtv23x_cb_ptr->ioctl_lock);
  931. mtv23x_power_off();
  932. mutex_unlock(&mtv23x_cb_ptr->ioctl_lock);
  933. mutex_destroy(&mtv23x_cb_ptr->ioctl_lock);
  934. tsb_free_mapping_area();
  935. if (mtv23x_cb_ptr) {
  936. kfree(mtv23x_cb_ptr);
  937. mtv23x_cb_ptr = NULL;
  938. }
  939. return 0;
  940. }
  941. static int mtv23x_probe(void)
  942. {
  943. int ret;
  944. mtv23x_cb_ptr = kzalloc(sizeof(struct MTV23x_CB), GFP_KERNEL);
  945. if (!mtv23x_cb_ptr) {
  946. DMBERR("MTV222 CB allocating error!\n");
  947. return false;
  948. }
  949. ret = tsb_alloc_mapping_area(MAX_TSB_DESC_SIZE, MAX_TSB_SEG_SIZE,
  950. MAX_NUM_TSB_SEG);
  951. if (ret) {
  952. kfree(mtv23x_cb_ptr);
  953. mtv23x_cb_ptr = NULL;
  954. return ret;
  955. }
  956. mutex_init(&mtv23x_cb_ptr->ioctl_lock);
  957. return ret;
  958. }
  959. static struct isdbt_drv_func raontech_mtv23x_drv_func = {
  960. .probe = mtv23x_probe,
  961. .remove = mtv23x_remove,
  962. .power_on = mtv23x_power_on,
  963. .power_off = mtv23x_power_off,
  964. .mmap = mtv23x_mmap,
  965. .ioctl = mtv23x_ioctl,
  966. .irq_handler = mtv23x_irq_handler,
  967. };
  968. struct isdbt_drv_func *mtv23x_drv_func(void)
  969. {
  970. DMBMSG("isdbt_drv_func : mtv23x\n");
  971. return &raontech_mtv23x_drv_func;
  972. }