drm/ast: initial DP501 support (v0.2)
authorDave Airlie <airlied@redhat.com>
Fri, 28 Mar 2014 01:05:12 +0000 (11:05 +1000)
committerDave Airlie <airlied@redhat.com>
Mon, 19 May 2014 01:13:57 +0000 (11:13 +1000)
This is the initial attempt at porting the DP501 code from the userspace
driver,

the firmware file is in
http://people.freedesktop.org/~airlied/ast_dp501_fw.bin

this should really be exposed as another encoder/connector that is cloneable

v0.2:
init 3rd tx properly,
add scratch reduction of VRAM size
backup firmware properly.

Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/ast/Makefile
drivers/gpu/drm/ast/ast_dp501.c [new file with mode: 0644]
drivers/gpu/drm/ast/ast_drv.h
drivers/gpu/drm/ast/ast_main.c
drivers/gpu/drm/ast/ast_mode.c
drivers/gpu/drm/ast/ast_post.c

index 8df4f28..171aa06 100644 (file)
@@ -4,6 +4,6 @@
 
 ccflags-y := -Iinclude/drm
 
-ast-y := ast_drv.o ast_main.o ast_mode.o ast_fb.o ast_ttm.o ast_post.o
+ast-y := ast_drv.o ast_main.o ast_mode.o ast_fb.o ast_ttm.o ast_post.o ast_dp501.o
 
-obj-$(CONFIG_DRM_AST) := ast.o
\ No newline at end of file
+obj-$(CONFIG_DRM_AST) := ast.o
diff --git a/drivers/gpu/drm/ast/ast_dp501.c b/drivers/gpu/drm/ast/ast_dp501.c
new file mode 100644 (file)
index 0000000..5da4b62
--- /dev/null
@@ -0,0 +1,410 @@
+
+#include <linux/firmware.h>
+#include <drm/drmP.h>
+#include "ast_drv.h"
+MODULE_FIRMWARE("ast_dp501_fw.bin");
+
+int ast_load_dp501_microcode(struct drm_device *dev)
+{
+       struct ast_private *ast = dev->dev_private;
+       static char *fw_name = "ast_dp501_fw.bin";
+       int err;
+       err = request_firmware(&ast->dp501_fw, fw_name, dev->dev);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static void send_ack(struct ast_private *ast)
+{
+       u8 sendack;
+       sendack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0xff);
+       sendack |= 0x80;
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0x00, sendack);
+}
+
+static void send_nack(struct ast_private *ast)
+{
+       u8 sendack;
+       sendack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0xff);
+       sendack &= ~0x80;
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, 0x00, sendack);
+}
+
+static bool wait_ack(struct ast_private *ast)
+{
+       u8 waitack;
+       u32 retry = 0;
+       do {
+               waitack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd2, 0xff);
+               waitack &= 0x80;
+               udelay(100);
+       } while ((!waitack) && (retry++ < 1000));
+
+       if (retry < 1000)
+               return true;
+       else
+               return false;
+}
+
+static bool wait_nack(struct ast_private *ast)
+{
+       u8 waitack;
+       u32 retry = 0;
+       do {
+               waitack = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd2, 0xff);
+               waitack &= 0x80;
+               udelay(100);
+       } while ((waitack) && (retry++ < 1000));
+
+       if (retry < 1000)
+               return true;
+       else
+               return false;
+}
+
+static void set_cmd_trigger(struct ast_private *ast)
+{
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, ~0x40, 0x40);
+}
+
+static void clear_cmd_trigger(struct ast_private *ast)
+{
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9b, ~0x40, 0x00);
+}
+
+#if 0
+static bool wait_fw_ready(struct ast_private *ast)
+{
+       u8 waitready;
+       u32 retry = 0;
+       do {
+               waitready = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd2, 0xff);
+               waitready &= 0x40;
+               udelay(100);
+       } while ((!waitready) && (retry++ < 1000));
+
+       if (retry < 1000)
+               return true;
+       else
+               return false;
+}
+#endif
+
+static bool ast_write_cmd(struct drm_device *dev, u8 data)
+{
+       struct ast_private *ast = dev->dev_private;
+       int retry = 0;
+       if (wait_nack(ast)) {
+               send_nack(ast);
+               ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9a, 0x00, data);
+               send_ack(ast);
+               set_cmd_trigger(ast);
+               do {
+                       if (wait_ack(ast)) {
+                               clear_cmd_trigger(ast);
+                               send_nack(ast);
+                               return true;
+                       }
+               } while (retry++ < 100);
+       }
+       clear_cmd_trigger(ast);
+       send_nack(ast);
+       return false;
+}
+
+static bool ast_write_data(struct drm_device *dev, u8 data)
+{
+       struct ast_private *ast = dev->dev_private;
+
+       if (wait_nack(ast)) {
+               send_nack(ast);
+               ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9a, 0x00, data);
+               send_ack(ast);
+               if (wait_ack(ast)) {
+                       send_nack(ast);
+                       return true;
+               }
+       }
+       send_nack(ast);
+       return false;
+}
+
+#if 0
+static bool ast_read_data(struct drm_device *dev, u8 *data)
+{
+       struct ast_private *ast = dev->dev_private;
+       u8 tmp;
+
+       *data = 0;
+
+       if (wait_ack(ast) == false)
+               return false;
+       tmp = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd3, 0xff);
+       *data = tmp;
+       if (wait_nack(ast) == false) {
+               send_nack(ast);
+               return false;
+       }
+       send_nack(ast);
+       return true;
+}
+
+static void clear_cmd(struct ast_private *ast)
+{
+       send_nack(ast);
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x9a, 0x00, 0x00);
+}
+#endif
+
+void ast_set_dp501_video_output(struct drm_device *dev, u8 mode)
+{
+       ast_write_cmd(dev, 0x40);
+       ast_write_data(dev, mode);
+
+       msleep(10);
+}
+
+static u32 get_fw_base(struct ast_private *ast)
+{
+       return ast_mindwm(ast, 0x1e6e2104) & 0x7fffffff;
+}
+
+bool ast_backup_fw(struct drm_device *dev, u8 *addr, u32 size)
+{
+       struct ast_private *ast = dev->dev_private;
+       u32 i, data;
+       u32 boot_address;
+
+       data = ast_mindwm(ast, 0x1e6e2100) & 0x01;
+       if (data) {
+               boot_address = get_fw_base(ast);
+               for (i = 0; i < size; i += 4)
+                       *(u32 *)(addr + i) = ast_mindwm(ast, boot_address + i);
+               return true;
+       }
+       return false;
+}
+
+bool ast_launch_m68k(struct drm_device *dev)
+{
+       struct ast_private *ast = dev->dev_private;
+       u32 i, data, len = 0;
+       u32 boot_address;
+       u8 *fw_addr = NULL;
+       u8 jreg;
+
+       data = ast_mindwm(ast, 0x1e6e2100) & 0x01;
+       if (!data) {
+
+               if (ast->dp501_fw_addr) {
+                       fw_addr = ast->dp501_fw_addr;
+                       len = 32*1024;
+               } else if (ast->dp501_fw) {
+                       fw_addr = (u8 *)ast->dp501_fw->data;
+                       len = ast->dp501_fw->size;
+               }
+               /* Get BootAddress */
+               ast_moutdwm(ast, 0x1e6e2000, 0x1688a8a8);
+               data = ast_mindwm(ast, 0x1e6e0004);
+               switch (data & 0x03) {
+               case 0:
+                       boot_address = 0x44000000;
+                       break;
+               default:
+               case 1:
+                       boot_address = 0x48000000;
+                       break;
+               case 2:
+                       boot_address = 0x50000000;
+                       break;
+               case 3:
+                       boot_address = 0x60000000;
+                       break;
+               }
+               boot_address -= 0x200000; /* -2MB */
+
+               /* copy image to buffer */
+               for (i = 0; i < len; i += 4) {
+                       data = *(u32 *)(fw_addr + i);
+                       ast_moutdwm(ast, boot_address + i, data);
+               }
+
+               /* Init SCU */
+               ast_moutdwm(ast, 0x1e6e2000, 0x1688a8a8);
+
+               /* Launch FW */
+               ast_moutdwm(ast, 0x1e6e2104, 0x80000000 + boot_address);
+               ast_moutdwm(ast, 0x1e6e2100, 1);
+
+               /* Update Scratch */
+               data = ast_mindwm(ast, 0x1e6e2040) & 0xfffff1ff;                /* D[11:9] = 100b: UEFI handling */
+               data |= 0x800;
+               ast_moutdwm(ast, 0x1e6e2040, data);
+
+               jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x99, 0xfc); /* D[1:0]: Reserved Video Buffer */
+               jreg |= 0x02;
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x99, jreg);
+       }
+       return true;
+}
+
+u8 ast_get_dp501_max_clk(struct drm_device *dev)
+{
+       struct ast_private *ast = dev->dev_private;
+       u32 boot_address, offset, data;
+       u8 linkcap[4], linkrate, linklanes, maxclk = 0xff;
+
+       boot_address = get_fw_base(ast);
+
+       /* validate FW version */
+       offset = 0xf000;
+       data = ast_mindwm(ast, boot_address + offset);
+       if ((data & 0xf0) != 0x10) /* version: 1x */
+               return maxclk;
+
+       /* Read Link Capability */
+       offset  = 0xf014;
+       *(u32 *)linkcap = ast_mindwm(ast, boot_address + offset);
+       if (linkcap[2] == 0) {
+               linkrate = linkcap[0];
+               linklanes = linkcap[1];
+               data = (linkrate == 0x0a) ? (90 * linklanes) : (54 * linklanes);
+               if (data > 0xff)
+                       data = 0xff;
+               maxclk = (u8)data;
+       }
+       return maxclk;
+}
+
+bool ast_dp501_read_edid(struct drm_device *dev, u8 *ediddata)
+{
+       struct ast_private *ast = dev->dev_private;
+       u32 i, boot_address, offset, data;
+
+       boot_address = get_fw_base(ast);
+
+       /* validate FW version */
+       offset = 0xf000;
+       data = ast_mindwm(ast, boot_address + offset);
+       if ((data & 0xf0) != 0x10)
+               return false;
+
+       /* validate PnP Monitor */
+       offset = 0xf010;
+       data = ast_mindwm(ast, boot_address + offset);
+       if (!(data & 0x01))
+               return false;
+
+       /* Read EDID */
+       offset = 0xf020;
+       for (i = 0; i < 128; i += 4) {
+               data = ast_mindwm(ast, boot_address + offset + i);
+               *(u32 *)(ediddata + i) = data;
+       }
+
+       return true;
+}
+
+static bool ast_init_dvo(struct drm_device *dev)
+{
+       struct ast_private *ast = dev->dev_private;
+       u8 jreg;
+       u32 data;
+       ast_write32(ast, 0xf004, 0x1e6e0000);
+       ast_write32(ast, 0xf000, 0x1);
+       ast_write32(ast, 0x12000, 0x1688a8a8);
+
+       jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd0, 0xff);
+       if (!(jreg & 0x80)) {
+               /* Init SCU DVO Settings */
+               data = ast_read32(ast, 0x12008);
+               /* delay phase */
+               data &= 0xfffff8ff;
+               data |= 0x00000500;
+               ast_write32(ast, 0x12008, data);
+
+               if (ast->chip == AST2300) {
+                       data = ast_read32(ast, 0x12084);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0xfffe0000;
+                       ast_write32(ast, 0x12084, data);
+
+                       data = ast_read32(ast, 0x12088);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0x000fffff;
+                       ast_write32(ast, 0x12088, data);
+
+                       data = ast_read32(ast, 0x12090);
+                       /* multi-pins for DVO single-edge */
+                       data &= 0xffffffcf;
+                       data |= 0x00000020;
+                       ast_write32(ast, 0x12090, data);
+               } else { /* AST2400 */
+                       data = ast_read32(ast, 0x12088);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0x30000000;
+                       ast_write32(ast, 0x12088, data);
+
+                       data = ast_read32(ast, 0x1208c);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0x000000cf;
+                       ast_write32(ast, 0x1208c, data);
+
+                       data = ast_read32(ast, 0x120a4);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0xffff0000;
+                       ast_write32(ast, 0x120a4, data);
+
+                       data = ast_read32(ast, 0x120a8);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0x0000000f;
+                       ast_write32(ast, 0x120a8, data);
+
+                       data = ast_read32(ast, 0x12094);
+                       /* multi-pins for DVO single-edge */
+                       data |= 0x00000002;
+                       ast_write32(ast, 0x12094, data);
+               }
+       }
+
+       /* Force to DVO */
+       data = ast_read32(ast, 0x1202c);
+       data &= 0xfffbffff;
+       ast_write32(ast, 0x1202c, data);
+
+       /* Init VGA DVO Settings */
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xcf, 0x80);
+       return true;
+}
+
+void ast_init_3rdtx(struct drm_device *dev)
+{
+       struct ast_private *ast = dev->dev_private;
+       u8 jreg;
+       u32 data;
+       if (ast->chip == AST2300 || ast->chip == AST2400) {
+               jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd1, 0xff);
+               switch (jreg & 0x0e) {
+               case 0x04:
+                       ast_init_dvo(dev);
+                       break;
+               case 0x08:
+                       ast_launch_m68k(dev);
+                       break;
+               case 0x0c:
+                       ast_init_dvo(dev);
+                       break;
+               default:
+                       if (ast->tx_chip_type == AST_TX_SIL164)
+                               ast_init_dvo(dev);
+                       else {
+                               ast_write32(ast, 0x12000, 0x1688a8a8);
+                               data = ast_read32(ast, 0x1202c);
+                               data &= 0xfffcffff;
+                               ast_write32(ast, 0, data);
+                       }
+               }
+       }
+}
index 5617427..5d6a875 100644 (file)
@@ -65,6 +65,13 @@ enum ast_chip {
        AST1180,
 };
 
+enum ast_tx_chip {
+       AST_TX_NONE,
+       AST_TX_SIL164,
+       AST_TX_ITE66121,
+       AST_TX_DP501,
+};
+
 #define AST_DRAM_512Mx16 0
 #define AST_DRAM_1Gx16   1
 #define AST_DRAM_512Mx32 2
@@ -104,6 +111,11 @@ struct ast_private {
        struct ttm_bo_kmap_obj cache_kmap;
        int next_cursor;
        bool support_wide_screen;
+
+       enum ast_tx_chip tx_chip_type;
+       u8 dp501_maxclk;
+       u8 *dp501_fw_addr;
+       const struct firmware *dp501_fw;        /* dp501 fw */
 };
 
 int ast_driver_load(struct drm_device *dev, unsigned long flags);
@@ -370,4 +382,14 @@ int ast_mmap(struct file *filp, struct vm_area_struct *vma);
 
 /* ast post */
 void ast_post_gpu(struct drm_device *dev);
+u32 ast_mindwm(struct ast_private *ast, u32 r);
+void ast_moutdwm(struct ast_private *ast, u32 r, u32 v);
+/* ast dp501 */
+int ast_load_dp501_microcode(struct drm_device *dev);
+void ast_set_dp501_video_output(struct drm_device *dev, u8 mode);
+bool ast_launch_m68k(struct drm_device *dev);
+bool ast_backup_fw(struct drm_device *dev, u8 *addr, u32 size);
+bool ast_dp501_read_edid(struct drm_device *dev, u8 *ediddata);
+u8 ast_get_dp501_max_clk(struct drm_device *dev);
+void ast_init_3rdtx(struct drm_device *dev);
 #endif
index 01ea4b6..1124fb4 100644 (file)
@@ -136,6 +136,31 @@ static int ast_detect_chip(struct drm_device *dev)
                break;
        }
 
+       ast->tx_chip_type = AST_TX_NONE;
+       jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xff);
+       if (jreg & 0x80)
+               ast->tx_chip_type = AST_TX_SIL164;
+       if ((ast->chip == AST2300) || (ast->chip == AST2400)) {
+               jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xd1, 0xff);
+               switch (jreg) {
+               case 0x04:
+                       ast->tx_chip_type = AST_TX_SIL164;
+                       break;
+               case 0x08:
+                       ast->dp501_fw_addr = kzalloc(32*1024, GFP_KERNEL);
+                       if (ast->dp501_fw_addr) {
+                               /* backup firmware */
+                               if (ast_backup_fw(dev, ast->dp501_fw_addr, 32*1024)) {
+                                       kfree(ast->dp501_fw_addr);
+                                       ast->dp501_fw_addr = NULL;
+                               }
+                       }
+                       /* fallthrough */
+               case 0x0c:
+                       ast->tx_chip_type = AST_TX_DP501;
+               }
+       }
+
        return 0;
 }
 
@@ -289,17 +314,32 @@ static u32 ast_get_vram_info(struct drm_device *dev)
 {
        struct ast_private *ast = dev->dev_private;
        u8 jreg;
-
+       u32 vram_size;
        ast_open_key(ast);
 
+       vram_size = AST_VIDMEM_DEFAULT_SIZE;
        jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xaa, 0xff);
        switch (jreg & 3) {
-       case 0: return AST_VIDMEM_SIZE_8M;
-       case 1: return AST_VIDMEM_SIZE_16M;
-       case 2: return AST_VIDMEM_SIZE_32M;
-       case 3: return AST_VIDMEM_SIZE_64M;
+       case 0: vram_size = AST_VIDMEM_SIZE_8M; break;
+       case 1: vram_size = AST_VIDMEM_SIZE_16M; break;
+       case 2: vram_size = AST_VIDMEM_SIZE_32M; break;
+       case 3: vram_size = AST_VIDMEM_SIZE_64M; break;
        }
-       return AST_VIDMEM_DEFAULT_SIZE;
+
+       jreg = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x99, 0xff);
+       switch (jreg & 0x03) {
+       case 1:
+               vram_size -= 0x100000;
+               break;
+       case 2:
+               vram_size -= 0x200000;
+               break;
+       case 3:
+               vram_size -= 0x400000;
+               break;
+       }
+
+       return vram_size;
 }
 
 int ast_driver_load(struct drm_device *dev, unsigned long flags)
@@ -376,6 +416,7 @@ int ast_driver_unload(struct drm_device *dev)
 {
        struct ast_private *ast = dev->dev_private;
 
+       kfree(ast->dp501_fw_addr);
        ast_mode_fini(dev);
        ast_fbdev_fini(dev);
        drm_mode_config_cleanup(dev);
index e9a14a1..208dc45 100644 (file)
@@ -460,9 +460,13 @@ static void ast_crtc_dpms(struct drm_crtc *crtc, int mode)
        case DRM_MODE_DPMS_STANDBY:
        case DRM_MODE_DPMS_SUSPEND:
                ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x1, 0xdf, 0);
+               if (ast->tx_chip_type == AST_TX_DP501)
+                       ast_set_dp501_video_output(crtc->dev, 1);
                ast_crtc_load_lut(crtc);
                break;
        case DRM_MODE_DPMS_OFF:
+               if (ast->tx_chip_type == AST_TX_DP501)
+                       ast_set_dp501_video_output(crtc->dev, 0);
                ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x1, 0xdf, 0x20);
                break;
        }
@@ -738,10 +742,24 @@ static int ast_encoder_init(struct drm_device *dev)
 static int ast_get_modes(struct drm_connector *connector)
 {
        struct ast_connector *ast_connector = to_ast_connector(connector);
+       struct ast_private *ast = connector->dev->dev_private;
        struct edid *edid;
        int ret;
-
-       edid = drm_get_edid(connector, &ast_connector->i2c->adapter);
+       bool flags = false;
+       if (ast->tx_chip_type == AST_TX_DP501) {
+               ast->dp501_maxclk = 0xff;
+               edid = kmalloc(128, GFP_KERNEL);
+               if (!edid)
+                       return -ENOMEM;
+
+               flags = ast_dp501_read_edid(connector->dev, (u8 *)edid);
+               if (flags)
+                       ast->dp501_maxclk = ast_get_dp501_max_clk(connector->dev);
+               else
+                       kfree(edid);
+       }
+       if (!flags)
+               edid = drm_get_edid(connector, &ast_connector->i2c->adapter);
        if (edid) {
                drm_mode_connector_update_edid_property(&ast_connector->base, edid);
                ret = drm_add_edid_modes(connector, edid);
index e8a6438..116c830 100644 (file)
@@ -107,7 +107,7 @@ ast_set_def_ext_reg(struct drm_device *dev)
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xff, reg);
 }
 
-static u32 ast_mindwm(struct ast_private *ast, u32 r)
+u32 ast_mindwm(struct ast_private *ast, u32 r)
 {
        uint32_t data;
 
@@ -120,7 +120,7 @@ static u32 ast_mindwm(struct ast_private *ast, u32 r)
        return ast_read32(ast, 0x10000 + (r & 0x0000ffff));
 }
 
-static void ast_moutdwm(struct ast_private *ast, u32 r, u32 v)
+void ast_moutdwm(struct ast_private *ast, u32 r, u32 v)
 {
        uint32_t data;
        ast_write32(ast, 0xf004, r & 0xffff0000);
@@ -378,6 +378,8 @@ void ast_post_gpu(struct drm_device *dev)
                ast_init_dram_2300(dev);
        else
                ast_init_dram_reg(dev);
+
+       ast_init_3rdtx(dev);
 }
 
 /* AST 2300 DRAM settings */