tcx.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /*
  2. * QEMU TCX Frame buffer
  3. *
  4. * Copyright (c) 2003-2005 Fabrice Bellard
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. #include "console.h"
  25. #include "pixel_ops.h"
  26. #include "sysbus.h"
  27. #include "qdev-addr.h"
  28. #define MAXX 1024
  29. #define MAXY 768
  30. #define TCX_DAC_NREGS 16
  31. #define TCX_THC_NREGS_8 0x081c
  32. #define TCX_THC_NREGS_24 0x1000
  33. #define TCX_TEC_NREGS 0x1000
  34. typedef struct TCXState {
  35. SysBusDevice busdev;
  36. target_phys_addr_t addr;
  37. DisplayState *ds;
  38. uint8_t *vram;
  39. uint32_t *vram24, *cplane;
  40. ram_addr_t vram_offset, vram24_offset, cplane_offset;
  41. uint32_t vram_size;
  42. uint16_t width, height, depth;
  43. uint8_t r[256], g[256], b[256];
  44. uint32_t palette[256];
  45. uint8_t dac_index, dac_state;
  46. } TCXState;
  47. static void tcx_screen_dump(void *opaque, const char *filename);
  48. static void tcx24_screen_dump(void *opaque, const char *filename);
  49. static void tcx_set_dirty(TCXState *s)
  50. {
  51. unsigned int i;
  52. for (i = 0; i < MAXX * MAXY; i += TARGET_PAGE_SIZE) {
  53. cpu_physical_memory_set_dirty(s->vram_offset + i);
  54. }
  55. }
  56. static void tcx24_set_dirty(TCXState *s)
  57. {
  58. unsigned int i;
  59. for (i = 0; i < MAXX * MAXY * 4; i += TARGET_PAGE_SIZE) {
  60. cpu_physical_memory_set_dirty(s->vram24_offset + i);
  61. cpu_physical_memory_set_dirty(s->cplane_offset + i);
  62. }
  63. }
  64. static void update_palette_entries(TCXState *s, int start, int end)
  65. {
  66. int i;
  67. for(i = start; i < end; i++) {
  68. switch(ds_get_bits_per_pixel(s->ds)) {
  69. default:
  70. case 8:
  71. s->palette[i] = rgb_to_pixel8(s->r[i], s->g[i], s->b[i]);
  72. break;
  73. case 15:
  74. s->palette[i] = rgb_to_pixel15(s->r[i], s->g[i], s->b[i]);
  75. break;
  76. case 16:
  77. s->palette[i] = rgb_to_pixel16(s->r[i], s->g[i], s->b[i]);
  78. break;
  79. case 32:
  80. if (is_surface_bgr(s->ds->surface))
  81. s->palette[i] = rgb_to_pixel32bgr(s->r[i], s->g[i], s->b[i]);
  82. else
  83. s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
  84. break;
  85. }
  86. }
  87. if (s->depth == 24) {
  88. tcx24_set_dirty(s);
  89. } else {
  90. tcx_set_dirty(s);
  91. }
  92. }
  93. static void tcx_draw_line32(TCXState *s1, uint8_t *d,
  94. const uint8_t *s, int width)
  95. {
  96. int x;
  97. uint8_t val;
  98. uint32_t *p = (uint32_t *)d;
  99. for(x = 0; x < width; x++) {
  100. val = *s++;
  101. *p++ = s1->palette[val];
  102. }
  103. }
  104. static void tcx_draw_line16(TCXState *s1, uint8_t *d,
  105. const uint8_t *s, int width)
  106. {
  107. int x;
  108. uint8_t val;
  109. uint16_t *p = (uint16_t *)d;
  110. for(x = 0; x < width; x++) {
  111. val = *s++;
  112. *p++ = s1->palette[val];
  113. }
  114. }
  115. static void tcx_draw_line8(TCXState *s1, uint8_t *d,
  116. const uint8_t *s, int width)
  117. {
  118. int x;
  119. uint8_t val;
  120. for(x = 0; x < width; x++) {
  121. val = *s++;
  122. *d++ = s1->palette[val];
  123. }
  124. }
  125. /*
  126. XXX Could be much more optimal:
  127. * detect if line/page/whole screen is in 24 bit mode
  128. * if destination is also BGR, use memcpy
  129. */
  130. static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d,
  131. const uint8_t *s, int width,
  132. const uint32_t *cplane,
  133. const uint32_t *s24)
  134. {
  135. int x, bgr, r, g, b;
  136. uint8_t val, *p8;
  137. uint32_t *p = (uint32_t *)d;
  138. uint32_t dval;
  139. bgr = is_surface_bgr(s1->ds->surface);
  140. for(x = 0; x < width; x++, s++, s24++) {
  141. if ((be32_to_cpu(*cplane++) & 0xff000000) == 0x03000000) {
  142. // 24-bit direct, BGR order
  143. p8 = (uint8_t *)s24;
  144. p8++;
  145. b = *p8++;
  146. g = *p8++;
  147. r = *p8;
  148. if (bgr)
  149. dval = rgb_to_pixel32bgr(r, g, b);
  150. else
  151. dval = rgb_to_pixel32(r, g, b);
  152. } else {
  153. val = *s;
  154. dval = s1->palette[val];
  155. }
  156. *p++ = dval;
  157. }
  158. }
  159. static inline int check_dirty(ram_addr_t page, ram_addr_t page24,
  160. ram_addr_t cpage)
  161. {
  162. int ret;
  163. unsigned int off;
  164. ret = cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG);
  165. for (off = 0; off < TARGET_PAGE_SIZE * 4; off += TARGET_PAGE_SIZE) {
  166. ret |= cpu_physical_memory_get_dirty(page24 + off, VGA_DIRTY_FLAG);
  167. ret |= cpu_physical_memory_get_dirty(cpage + off, VGA_DIRTY_FLAG);
  168. }
  169. return ret;
  170. }
  171. static inline void reset_dirty(TCXState *ts, ram_addr_t page_min,
  172. ram_addr_t page_max, ram_addr_t page24,
  173. ram_addr_t cpage)
  174. {
  175. cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE,
  176. VGA_DIRTY_FLAG);
  177. page_min -= ts->vram_offset;
  178. page_max -= ts->vram_offset;
  179. cpu_physical_memory_reset_dirty(page24 + page_min * 4,
  180. page24 + page_max * 4 + TARGET_PAGE_SIZE,
  181. VGA_DIRTY_FLAG);
  182. cpu_physical_memory_reset_dirty(cpage + page_min * 4,
  183. cpage + page_max * 4 + TARGET_PAGE_SIZE,
  184. VGA_DIRTY_FLAG);
  185. }
  186. /* Fixed line length 1024 allows us to do nice tricks not possible on
  187. VGA... */
  188. static void tcx_update_display(void *opaque)
  189. {
  190. TCXState *ts = opaque;
  191. ram_addr_t page, page_min, page_max;
  192. int y, y_start, dd, ds;
  193. uint8_t *d, *s;
  194. void (*f)(TCXState *s1, uint8_t *dst, const uint8_t *src, int width);
  195. if (ds_get_bits_per_pixel(ts->ds) == 0)
  196. return;
  197. page = ts->vram_offset;
  198. y_start = -1;
  199. page_min = -1;
  200. page_max = 0;
  201. d = ds_get_data(ts->ds);
  202. s = ts->vram;
  203. dd = ds_get_linesize(ts->ds);
  204. ds = 1024;
  205. switch (ds_get_bits_per_pixel(ts->ds)) {
  206. case 32:
  207. f = tcx_draw_line32;
  208. break;
  209. case 15:
  210. case 16:
  211. f = tcx_draw_line16;
  212. break;
  213. default:
  214. case 8:
  215. f = tcx_draw_line8;
  216. break;
  217. case 0:
  218. return;
  219. }
  220. for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE) {
  221. if (cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG)) {
  222. if (y_start < 0)
  223. y_start = y;
  224. if (page < page_min)
  225. page_min = page;
  226. if (page > page_max)
  227. page_max = page;
  228. f(ts, d, s, ts->width);
  229. d += dd;
  230. s += ds;
  231. f(ts, d, s, ts->width);
  232. d += dd;
  233. s += ds;
  234. f(ts, d, s, ts->width);
  235. d += dd;
  236. s += ds;
  237. f(ts, d, s, ts->width);
  238. d += dd;
  239. s += ds;
  240. } else {
  241. if (y_start >= 0) {
  242. /* flush to display */
  243. dpy_update(ts->ds, 0, y_start,
  244. ts->width, y - y_start);
  245. y_start = -1;
  246. }
  247. d += dd * 4;
  248. s += ds * 4;
  249. }
  250. }
  251. if (y_start >= 0) {
  252. /* flush to display */
  253. dpy_update(ts->ds, 0, y_start,
  254. ts->width, y - y_start);
  255. }
  256. /* reset modified pages */
  257. if (page_max >= page_min) {
  258. cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE,
  259. VGA_DIRTY_FLAG);
  260. }
  261. }
  262. static void tcx24_update_display(void *opaque)
  263. {
  264. TCXState *ts = opaque;
  265. ram_addr_t page, page_min, page_max, cpage, page24;
  266. int y, y_start, dd, ds;
  267. uint8_t *d, *s;
  268. uint32_t *cptr, *s24;
  269. if (ds_get_bits_per_pixel(ts->ds) != 32)
  270. return;
  271. page = ts->vram_offset;
  272. page24 = ts->vram24_offset;
  273. cpage = ts->cplane_offset;
  274. y_start = -1;
  275. page_min = -1;
  276. page_max = 0;
  277. d = ds_get_data(ts->ds);
  278. s = ts->vram;
  279. s24 = ts->vram24;
  280. cptr = ts->cplane;
  281. dd = ds_get_linesize(ts->ds);
  282. ds = 1024;
  283. for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE,
  284. page24 += TARGET_PAGE_SIZE, cpage += TARGET_PAGE_SIZE) {
  285. if (check_dirty(page, page24, cpage)) {
  286. if (y_start < 0)
  287. y_start = y;
  288. if (page < page_min)
  289. page_min = page;
  290. if (page > page_max)
  291. page_max = page;
  292. tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
  293. d += dd;
  294. s += ds;
  295. cptr += ds;
  296. s24 += ds;
  297. tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
  298. d += dd;
  299. s += ds;
  300. cptr += ds;
  301. s24 += ds;
  302. tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
  303. d += dd;
  304. s += ds;
  305. cptr += ds;
  306. s24 += ds;
  307. tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
  308. d += dd;
  309. s += ds;
  310. cptr += ds;
  311. s24 += ds;
  312. } else {
  313. if (y_start >= 0) {
  314. /* flush to display */
  315. dpy_update(ts->ds, 0, y_start,
  316. ts->width, y - y_start);
  317. y_start = -1;
  318. }
  319. d += dd * 4;
  320. s += ds * 4;
  321. cptr += ds * 4;
  322. s24 += ds * 4;
  323. }
  324. }
  325. if (y_start >= 0) {
  326. /* flush to display */
  327. dpy_update(ts->ds, 0, y_start,
  328. ts->width, y - y_start);
  329. }
  330. /* reset modified pages */
  331. if (page_max >= page_min) {
  332. reset_dirty(ts, page_min, page_max, page24, cpage);
  333. }
  334. }
  335. static void tcx_invalidate_display(void *opaque)
  336. {
  337. TCXState *s = opaque;
  338. tcx_set_dirty(s);
  339. qemu_console_resize(s->ds, s->width, s->height);
  340. }
  341. static void tcx24_invalidate_display(void *opaque)
  342. {
  343. TCXState *s = opaque;
  344. tcx_set_dirty(s);
  345. tcx24_set_dirty(s);
  346. qemu_console_resize(s->ds, s->width, s->height);
  347. }
  348. static int vmstate_tcx_post_load(void *opaque, int version_id)
  349. {
  350. TCXState *s = opaque;
  351. update_palette_entries(s, 0, 256);
  352. if (s->depth == 24) {
  353. tcx24_set_dirty(s);
  354. } else {
  355. tcx_set_dirty(s);
  356. }
  357. return 0;
  358. }
  359. static const VMStateDescription vmstate_tcx = {
  360. .name ="tcx",
  361. .version_id = 4,
  362. .minimum_version_id = 4,
  363. .minimum_version_id_old = 4,
  364. .post_load = vmstate_tcx_post_load,
  365. .fields = (VMStateField []) {
  366. VMSTATE_UINT16(height, TCXState),
  367. VMSTATE_UINT16(width, TCXState),
  368. VMSTATE_UINT16(depth, TCXState),
  369. VMSTATE_BUFFER(r, TCXState),
  370. VMSTATE_BUFFER(g, TCXState),
  371. VMSTATE_BUFFER(b, TCXState),
  372. VMSTATE_UINT8(dac_index, TCXState),
  373. VMSTATE_UINT8(dac_state, TCXState),
  374. VMSTATE_END_OF_LIST()
  375. }
  376. };
  377. static void tcx_reset(DeviceState *d)
  378. {
  379. TCXState *s = container_of(d, TCXState, busdev.qdev);
  380. /* Initialize palette */
  381. memset(s->r, 0, 256);
  382. memset(s->g, 0, 256);
  383. memset(s->b, 0, 256);
  384. s->r[255] = s->g[255] = s->b[255] = 255;
  385. update_palette_entries(s, 0, 256);
  386. memset(s->vram, 0, MAXX*MAXY);
  387. cpu_physical_memory_reset_dirty(s->vram_offset, s->vram_offset +
  388. MAXX * MAXY * (1 + 4 + 4), VGA_DIRTY_FLAG);
  389. s->dac_index = 0;
  390. s->dac_state = 0;
  391. }
  392. static uint32_t tcx_dac_readl(void *opaque, target_phys_addr_t addr)
  393. {
  394. return 0;
  395. }
  396. static void tcx_dac_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
  397. {
  398. TCXState *s = opaque;
  399. switch (addr) {
  400. case 0:
  401. s->dac_index = val >> 24;
  402. s->dac_state = 0;
  403. break;
  404. case 4:
  405. switch (s->dac_state) {
  406. case 0:
  407. s->r[s->dac_index] = val >> 24;
  408. update_palette_entries(s, s->dac_index, s->dac_index + 1);
  409. s->dac_state++;
  410. break;
  411. case 1:
  412. s->g[s->dac_index] = val >> 24;
  413. update_palette_entries(s, s->dac_index, s->dac_index + 1);
  414. s->dac_state++;
  415. break;
  416. case 2:
  417. s->b[s->dac_index] = val >> 24;
  418. update_palette_entries(s, s->dac_index, s->dac_index + 1);
  419. s->dac_index = (s->dac_index + 1) & 255; // Index autoincrement
  420. default:
  421. s->dac_state = 0;
  422. break;
  423. }
  424. break;
  425. default:
  426. break;
  427. }
  428. return;
  429. }
  430. static CPUReadMemoryFunc * const tcx_dac_read[3] = {
  431. NULL,
  432. NULL,
  433. tcx_dac_readl,
  434. };
  435. static CPUWriteMemoryFunc * const tcx_dac_write[3] = {
  436. NULL,
  437. NULL,
  438. tcx_dac_writel,
  439. };
  440. static uint32_t tcx_dummy_readl(void *opaque, target_phys_addr_t addr)
  441. {
  442. return 0;
  443. }
  444. static void tcx_dummy_writel(void *opaque, target_phys_addr_t addr,
  445. uint32_t val)
  446. {
  447. }
  448. static CPUReadMemoryFunc * const tcx_dummy_read[3] = {
  449. NULL,
  450. NULL,
  451. tcx_dummy_readl,
  452. };
  453. static CPUWriteMemoryFunc * const tcx_dummy_write[3] = {
  454. NULL,
  455. NULL,
  456. tcx_dummy_writel,
  457. };
  458. static int tcx_init1(SysBusDevice *dev)
  459. {
  460. TCXState *s = FROM_SYSBUS(TCXState, dev);
  461. int io_memory, dummy_memory;
  462. ram_addr_t vram_offset;
  463. int size;
  464. uint8_t *vram_base;
  465. vram_offset = qemu_ram_alloc(NULL, "tcx.vram", s->vram_size * (1 + 4 + 4));
  466. vram_base = qemu_get_ram_ptr(vram_offset);
  467. s->vram_offset = vram_offset;
  468. /* 8-bit plane */
  469. s->vram = vram_base;
  470. size = s->vram_size;
  471. sysbus_init_mmio(dev, size, s->vram_offset);
  472. vram_offset += size;
  473. vram_base += size;
  474. /* DAC */
  475. io_memory = cpu_register_io_memory(tcx_dac_read, tcx_dac_write, s,
  476. DEVICE_NATIVE_ENDIAN);
  477. sysbus_init_mmio(dev, TCX_DAC_NREGS, io_memory);
  478. /* TEC (dummy) */
  479. dummy_memory = cpu_register_io_memory(tcx_dummy_read, tcx_dummy_write,
  480. s, DEVICE_NATIVE_ENDIAN);
  481. sysbus_init_mmio(dev, TCX_TEC_NREGS, dummy_memory);
  482. /* THC: NetBSD writes here even with 8-bit display: dummy */
  483. sysbus_init_mmio(dev, TCX_THC_NREGS_24, dummy_memory);
  484. if (s->depth == 24) {
  485. /* 24-bit plane */
  486. size = s->vram_size * 4;
  487. s->vram24 = (uint32_t *)vram_base;
  488. s->vram24_offset = vram_offset;
  489. sysbus_init_mmio(dev, size, vram_offset);
  490. vram_offset += size;
  491. vram_base += size;
  492. /* Control plane */
  493. size = s->vram_size * 4;
  494. s->cplane = (uint32_t *)vram_base;
  495. s->cplane_offset = vram_offset;
  496. sysbus_init_mmio(dev, size, vram_offset);
  497. s->ds = graphic_console_init(tcx24_update_display,
  498. tcx24_invalidate_display,
  499. tcx24_screen_dump, NULL, s);
  500. } else {
  501. /* THC 8 bit (dummy) */
  502. sysbus_init_mmio(dev, TCX_THC_NREGS_8, dummy_memory);
  503. s->ds = graphic_console_init(tcx_update_display,
  504. tcx_invalidate_display,
  505. tcx_screen_dump, NULL, s);
  506. }
  507. qemu_console_resize(s->ds, s->width, s->height);
  508. return 0;
  509. }
  510. static void tcx_screen_dump(void *opaque, const char *filename)
  511. {
  512. TCXState *s = opaque;
  513. FILE *f;
  514. uint8_t *d, *d1, v;
  515. int y, x;
  516. f = fopen(filename, "wb");
  517. if (!f)
  518. return;
  519. fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255);
  520. d1 = s->vram;
  521. for(y = 0; y < s->height; y++) {
  522. d = d1;
  523. for(x = 0; x < s->width; x++) {
  524. v = *d;
  525. fputc(s->r[v], f);
  526. fputc(s->g[v], f);
  527. fputc(s->b[v], f);
  528. d++;
  529. }
  530. d1 += MAXX;
  531. }
  532. fclose(f);
  533. return;
  534. }
  535. static void tcx24_screen_dump(void *opaque, const char *filename)
  536. {
  537. TCXState *s = opaque;
  538. FILE *f;
  539. uint8_t *d, *d1, v;
  540. uint32_t *s24, *cptr, dval;
  541. int y, x;
  542. f = fopen(filename, "wb");
  543. if (!f)
  544. return;
  545. fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255);
  546. d1 = s->vram;
  547. s24 = s->vram24;
  548. cptr = s->cplane;
  549. for(y = 0; y < s->height; y++) {
  550. d = d1;
  551. for(x = 0; x < s->width; x++, d++, s24++) {
  552. if ((*cptr++ & 0xff000000) == 0x03000000) { // 24-bit direct
  553. dval = *s24 & 0x00ffffff;
  554. fputc((dval >> 16) & 0xff, f);
  555. fputc((dval >> 8) & 0xff, f);
  556. fputc(dval & 0xff, f);
  557. } else {
  558. v = *d;
  559. fputc(s->r[v], f);
  560. fputc(s->g[v], f);
  561. fputc(s->b[v], f);
  562. }
  563. }
  564. d1 += MAXX;
  565. }
  566. fclose(f);
  567. return;
  568. }
  569. static SysBusDeviceInfo tcx_info = {
  570. .init = tcx_init1,
  571. .qdev.name = "SUNW,tcx",
  572. .qdev.size = sizeof(TCXState),
  573. .qdev.reset = tcx_reset,
  574. .qdev.vmsd = &vmstate_tcx,
  575. .qdev.props = (Property[]) {
  576. DEFINE_PROP_TADDR("addr", TCXState, addr, -1),
  577. DEFINE_PROP_HEX32("vram_size", TCXState, vram_size, -1),
  578. DEFINE_PROP_UINT16("width", TCXState, width, -1),
  579. DEFINE_PROP_UINT16("height", TCXState, height, -1),
  580. DEFINE_PROP_UINT16("depth", TCXState, depth, -1),
  581. DEFINE_PROP_END_OF_LIST(),
  582. }
  583. };
  584. static void tcx_register_devices(void)
  585. {
  586. sysbus_register_withprop(&tcx_info);
  587. }
  588. device_init(tcx_register_devices)