summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/video/crtc6845.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/video/crtc6845.c')
-rw-r--r--src/emu/video/crtc6845.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/emu/video/crtc6845.c b/src/emu/video/crtc6845.c
new file mode 100644
index 00000000000..69040b0b892
--- /dev/null
+++ b/src/emu/video/crtc6845.c
@@ -0,0 +1,444 @@
+/**********************************************************************
+
+ Motorola 6845 CRT controller emulation
+
+**********************************************************************/
+
+#include "driver.h"
+#include "crtc6845.h"
+
+
+#define LOG (0)
+
+
+typedef struct _crtc6845_state crtc6845_state;
+
+struct _crtc6845_state
+{
+ const crtc6845_interface *intf;
+
+ /* internal registers */
+ UINT8 address_latch;
+ UINT8 horiz_total;
+ UINT8 horiz_disp;
+ UINT8 horiz_sync_pos;
+ UINT8 sync_width;
+ UINT8 vert_total;
+ UINT8 vert_total_adj;
+ UINT8 vert_disp;
+ UINT8 vert_sync_pos;
+ UINT8 intl_skew;
+ UINT8 max_ras_addr;
+ UINT8 cursor_start_ras;
+ UINT8 cursor_end_ras;
+ UINT16 start_addr;
+ UINT16 cursor;
+ UINT16 light_pen;
+
+ emu_timer *display_enable_changed_timer;
+
+ /* saved screen parameters so we don't call
+ video_screen_configure() unneccessarily.
+ Do NOT state save these! */
+ UINT16 last_horiz_total;
+ UINT16 last_vert_total;
+ UINT16 last_max_x;
+ UINT16 last_max_y;
+ UINT16 current_ma; /* the MA address currently drawn */
+ int has_valid_parameters;
+};
+
+static crtc6845_state crtc6845;
+
+
+static void crtc6845_state_save_postload(void *param);
+static void configure_screen(crtc6845_state *chip, int postload);
+static void update_timer(crtc6845_state *chip);
+static TIMER_CALLBACK_PTR( display_enable_changed_timer_cb );
+
+
+void crtc6845_init(void)
+{
+ crtc6845_state *chip = &crtc6845;
+ int which = 0;
+
+ state_save_register_func_postload_ptr(crtc6845_state_save_postload, chip);
+
+ state_save_register_item("crtc6845", which, chip->address_latch);
+ state_save_register_item("crtc6845", which, chip->horiz_total);
+ state_save_register_item("crtc6845", which, chip->horiz_disp);
+ state_save_register_item("crtc6845", which, chip->horiz_sync_pos);
+ state_save_register_item("crtc6845", which, chip->sync_width);
+ state_save_register_item("crtc6845", which, chip->vert_total);
+ state_save_register_item("crtc6845", which, chip->vert_total_adj);
+ state_save_register_item("crtc6845", which, chip->vert_disp);
+ state_save_register_item("crtc6845", which, chip->vert_sync_pos);
+ state_save_register_item("crtc6845", which, chip->intl_skew);
+ state_save_register_item("crtc6845", which, chip->max_ras_addr);
+ state_save_register_item("crtc6845", which, chip->cursor_start_ras);
+ state_save_register_item("crtc6845", which, chip->cursor_end_ras);
+ state_save_register_item("crtc6845", which, chip->start_addr);
+ state_save_register_item("crtc6845", which, chip->cursor);
+ state_save_register_item("crtc6845", which, chip->light_pen);
+
+ /* do not configure the screen */
+ chip->intf = 0;
+ chip->has_valid_parameters = FALSE;
+}
+
+
+void crtc6845_config(int which, const crtc6845_interface *intf)
+{
+ crtc6845_state *chip = &crtc6845;
+
+ crtc6845_init();
+
+ chip->intf = intf;
+
+ /* create the timer if the user is interested in getting display enable
+ notifications */
+ if (intf->display_enable_changed)
+ chip->display_enable_changed_timer = timer_alloc_ptr(display_enable_changed_timer_cb, chip);
+}
+
+
+static void crtc6845_state_save_postload(void *param)
+{
+ crtc6845_state *chip = (crtc6845_state *)param;
+
+ configure_screen(chip, TRUE);
+}
+
+
+READ8_HANDLER( crtc6845_register_r )
+{
+ crtc6845_state *chip = &crtc6845;
+
+ UINT8 ret = 0xff;
+
+ switch (chip->address_latch)
+ {
+ case 14:
+ ret = chip->cursor >> 8;
+ break;
+ case 15:
+ ret = chip->cursor;
+ break;
+ case 16:
+ ret = chip->light_pen >> 8;
+ break;
+ case 17:
+ ret = chip->light_pen;
+ break;
+ default:
+ /* all other registers are write only */
+ break;
+ }
+ return ret;
+}
+
+
+WRITE8_HANDLER( crtc6845_address_w )
+{
+ crtc6845_state *chip = &crtc6845;
+
+ chip->address_latch = data & 0x1f;
+}
+
+
+WRITE8_HANDLER( crtc6845_register_w )
+{
+ crtc6845_state *chip = &crtc6845;
+
+ int call_configure_screen = FALSE;
+ if (LOG) logerror("CRT #0 PC %04x: CRTC6845 reg 0x%02x = 0x%02x\n", activecpu_get_pc(), chip->address_latch, data);
+
+ switch (chip->address_latch)
+ {
+ case 0:
+ chip->horiz_total = data;
+ call_configure_screen = TRUE;
+ break;
+ case 1:
+ chip->horiz_disp = data;
+ call_configure_screen = TRUE;
+ break;
+ case 2:
+ chip->horiz_sync_pos = data;
+ break;
+ case 3:
+ chip->sync_width = data & 0x0f;
+ break;
+ case 4:
+ chip->vert_total = data & 0x7f;
+ call_configure_screen = TRUE;
+ break;
+ case 5:
+ chip->vert_total_adj = data & 0x1f;
+ call_configure_screen = TRUE;
+ break;
+ case 6:
+ chip->vert_disp = data & 0x7f;
+ call_configure_screen = TRUE;
+ break;
+ case 7:
+ chip->vert_sync_pos = data & 0x7f;
+ break;
+ case 8:
+ chip->intl_skew = data & 0x03;
+ break;
+ case 9:
+ chip->max_ras_addr = data & 0x1f;
+ call_configure_screen = TRUE;
+ break;
+ case 10:
+ chip->cursor_start_ras = data & 0x7f;
+ break;
+ case 11:
+ chip->cursor_end_ras = data & 0x1f;
+ break;
+ case 12:
+ chip->start_addr &= 0x00ff;
+ chip->start_addr |= (data & 0x3f) << 8;
+ call_configure_screen = TRUE;
+ break;
+ case 13:
+ chip->start_addr &= 0xff00;
+ chip->start_addr |= data;
+ call_configure_screen = TRUE;
+ break;
+ case 14:
+ chip->cursor &= 0x00ff;
+ chip->cursor |= (data & 0x3f) << 8;
+ break;
+ case 15:
+ chip->cursor &= 0xff00;
+ chip->cursor |= data;
+ break;
+ case 16: /* read-only */
+ break;
+ case 17: /* read-only */
+ break;
+ default:
+ break;
+ }
+
+ if (call_configure_screen)
+ configure_screen(chip, FALSE);
+}
+
+
+static void configure_screen(crtc6845_state *chip, int postload)
+{
+ if (chip->intf)
+ {
+ /* compute the screen sizes */
+ UINT16 horiz_total = (chip->horiz_total + 1) * chip->intf->hpixels_per_column;
+ UINT16 vert_total = (chip->vert_total + 1) * (chip->max_ras_addr + 1) + chip->vert_total_adj;
+
+ /* determine the visible area, avoid division by 0 */
+ UINT16 max_x = chip->horiz_disp * chip->intf->hpixels_per_column - 1;
+ UINT16 max_y = chip->vert_disp * (chip->max_ras_addr + 1) - 1;
+
+ /* update only if screen parameters changed, unless we are coming here after loading the saved state */
+ if (postload ||
+ (horiz_total != chip->last_horiz_total) || (vert_total != chip->last_vert_total) ||
+ (max_x != chip->last_max_x) || (max_y != chip->last_max_y))
+ {
+ /* update the screen only if we have valid data */
+ if ((chip->horiz_total > 0) && (max_x < horiz_total) && (chip->vert_total > 0) && (max_y < vert_total))
+ {
+ rectangle visarea;
+
+ attoseconds_t refresh = HZ_TO_ATTOSECONDS(chip->intf->clock) * (chip->horiz_total + 1) * vert_total;
+
+ visarea.min_x = 0;
+ visarea.min_y = 0;
+ visarea.max_x = max_x;
+ visarea.max_y = max_y;
+
+ if (LOG) logerror("CRTC6845 config screen: HTOTAL: %x VTOTAL: %x MAX_X: %x MAX_Y: %x FPS: %f\n",
+ horiz_total, vert_total, max_x, max_y, 1 / ATTOSECONDS_TO_DOUBLE(refresh));
+
+ video_screen_configure(chip->intf->scrnum, horiz_total, vert_total, &visarea, refresh);
+
+ chip->has_valid_parameters = TRUE;
+ }
+ else
+ chip->has_valid_parameters = FALSE;
+
+ chip->last_horiz_total = horiz_total;
+ chip->last_vert_total = vert_total;
+ chip->last_max_x = max_x;
+ chip->last_max_y = max_y;
+
+ update_timer(chip);
+ }
+ }
+}
+
+
+static int is_display_enabled(crtc6845_state *chip)
+{
+ UINT16 y = video_screen_get_vpos(chip->intf->scrnum);
+ UINT16 x = video_screen_get_hpos(chip->intf->scrnum);
+
+ return (y <= chip->last_max_y) && (x <= chip->last_max_x);
+}
+
+
+static void update_timer(crtc6845_state *chip)
+{
+ INT16 next_y;
+ UINT16 next_x;
+ attotime duration;
+
+ if (chip->has_valid_parameters && (chip->display_enable_changed_timer != 0))
+ {
+ if (is_display_enabled(chip))
+ {
+ /* we are in a display region,
+ get the location of the next blanking start */
+
+ /* normally, it's at end the current raster line */
+ next_y = video_screen_get_vpos(chip->intf->scrnum);
+ next_x = chip->last_max_x + 1;
+
+ /* but if visible width = horiz_total, then we need
+ to go to the beginning of VBLANK */
+ if (next_x == chip->last_horiz_total)
+ {
+ next_y = chip->last_max_y + 1;
+ next_x = 0;
+
+ /* abnormal case, no vertical blanking, either */
+ if (next_y == chip->last_vert_total)
+ next_y = -1;
+ }
+ }
+ else
+ {
+ /* we are in a blanking region,
+ get the location of the next display start */
+ next_x = 0;
+ next_y = (video_screen_get_vpos(chip->intf->scrnum) + 1) % chip->last_vert_total;
+
+ /* if we would now fall in the vertical blanking, we need
+ to go to the top of the screen */
+ if (next_y > chip->last_max_y)
+ next_y = 0;
+ }
+
+ if (next_y != -1)
+ duration = video_screen_get_time_until_pos(chip->intf->scrnum, next_y, next_x);
+ else
+ duration = attotime_never;
+
+ timer_adjust_ptr(chip->display_enable_changed_timer, duration, attotime_never);
+ }
+}
+
+
+static TIMER_CALLBACK_PTR( display_enable_changed_timer_cb )
+{
+ crtc6845_state *chip = param;
+
+ /* call the callback function -- we know it exists */
+ chip->intf->display_enable_changed(is_display_enabled(chip));
+
+ update_timer(chip);
+}
+
+
+UINT16 crtc6845_get_ma(int which)
+{
+ crtc6845_state *chip = &crtc6845;
+
+ UINT16 ret;
+
+ if (chip->has_valid_parameters)
+ {
+ /* get the current raster positions and clamp them to the visible region */
+ int y = video_screen_get_vpos(0);
+ int x = video_screen_get_hpos(0);
+
+ /* since the MA counter stops in the blanking regions, if we are in a
+ VBLANK, both X and Y are at their max */
+ if ((y > chip->last_max_y) || (x > chip->last_max_x))
+ x = chip->last_max_x;
+
+ if (y > chip->last_max_y)
+ y = chip->last_max_y;
+
+ ret = (chip->start_addr +
+ (y / (chip->max_ras_addr + 1)) * chip->horiz_disp +
+ (x / chip->intf->hpixels_per_column)) & 0x3fff;
+ }
+ else
+ ret = 0;
+
+ return ret;
+}
+
+
+UINT8 crtc6845_get_ra(int which)
+{
+ crtc6845_state *chip = &crtc6845;
+
+ UINT8 ret;
+
+ if (chip->has_valid_parameters)
+ {
+ /* get the current vertical raster position and clamp it to the visible region */
+ int y = video_screen_get_vpos(0);
+
+ if (y > chip->last_max_y)
+ y = chip->last_max_y;
+
+ ret = y % (chip->max_ras_addr + 1);
+ }
+ else
+ ret = 0;
+
+ return ret;
+}
+
+
+VIDEO_UPDATE( crtc6845 )
+{
+ crtc6845_state *chip = &crtc6845;
+
+ if (chip->has_valid_parameters)
+ {
+ UINT16 y;
+
+ /* call the set up function if any */
+ void *param = 0;
+
+ if (chip->intf->begin_update)
+ param = chip->intf->begin_update(machine, screen, bitmap, cliprect);
+
+ /* read the start address at the beginning of the frame */
+ if (cliprect->min_y == 0)
+ chip->current_ma = chip->start_addr;
+
+ /* for each row in the visible region */
+ for (y = cliprect->min_y; y <= cliprect->max_y; y++)
+ {
+ UINT8 ra = y % (chip->max_ras_addr + 1);
+
+ /* call the external system to draw it */
+ chip->intf->update_row(bitmap, cliprect, chip->current_ma, ra, y, chip->horiz_disp, param);
+
+ /* update MA if the last raster address */
+ if (ra == chip->max_ras_addr)
+ chip->current_ma = (chip->current_ma + chip->horiz_disp) & 0x3fff;
+ }
+
+ /* call the tear down function if any */
+ if (chip->intf->end_update)
+ chip->intf->end_update(bitmap, cliprect, param);
+ }
+
+ return 0;
+}