diff options
Diffstat (limited to 'linux-core')
30 files changed, 5038 insertions, 14 deletions
diff --git a/linux-core/Makefile b/linux-core/Makefile index b9405bbb..9b288851 100644 --- a/linux-core/Makefile +++ b/linux-core/Makefile @@ -90,7 +90,7 @@ VIAHEADERS = via_drm.h via_drv.h via_3d_reg.h via_verifier.h $(DRMHEADERS) MACH64HEADERS = mach64_drv.h mach64_drm.h $(DRMHEADERS) NVHEADERS = nv_drv.h $(DRMHEADERS) FFBHEADERS = ffb_drv.h $(DRMHEADERS) -NOUVEAUHEADERS = nouveau_drv.h nouveau_drm.h nouveau_reg.h $(DRMHEADERS) +NOUVEAUHEADERS = nouveau_drv.h nouveau_drm.h nouveau_reg.h nouveau_display.h $(DRMHEADERS) XGIHEADERS = xgi_cmdlist.h xgi_drv.h xgi_misc.h xgi_regs.h $(DRMHEADERS) RADEONMSHEADERS = radeon_ms_driver.h $(DRMHEADERS) diff --git a/linux-core/Makefile.kernel b/linux-core/Makefile.kernel index ac9baf02..23430140 100644 --- a/linux-core/Makefile.kernel +++ b/linux-core/Makefile.kernel @@ -35,7 +35,10 @@ nouveau-objs := nouveau_drv.o nouveau_state.o nouveau_fifo.o nouveau_mem.o \ nv04_fifo.o nv10_fifo.o nv40_fifo.o nv50_fifo.o \ nv04_graph.o nv10_graph.o nv20_graph.o \ nv40_graph.o nv50_graph.o \ - nv04_instmem.o nv50_instmem.o + nv04_instmem.o nv50_instmem.o \ + nouveau_bios.o \ + nv50_crtc.o nv50_cursor.o nv50_lut.o nv50_fb.o nv50_output.o nv50_sor.o nv50_dac.o nv50_connector.o nv50_i2c.o nv50_display.o \ + nv50_kms_wrapper.o radeon-objs := radeon_drv.o radeon_cp.o radeon_state.o radeon_mem.o radeon_irq.o r300_cmdbuf.o radeon_ms-objs := radeon_ms_drv.o radeon_ms_drm.o radeon_ms_family.o \ radeon_ms_state.o radeon_ms_bo.o radeon_ms_irq.o \ diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c index e1b371cc..c8cfaef4 100644 --- a/linux-core/drm_crtc.c +++ b/linux-core/drm_crtc.c @@ -100,6 +100,7 @@ char *drm_get_connector_name(struct drm_connector *connector) connector->connector_type_id); return buf; } +EXPORT_SYMBOL(drm_get_connector_name); char *drm_get_connector_status_name(enum drm_connector_status status) { diff --git a/linux-core/drm_crtc.h b/linux-core/drm_crtc.h index 2b577b93..d6fa4cca 100644 --- a/linux-core/drm_crtc.h +++ b/linux-core/drm_crtc.h @@ -619,6 +619,7 @@ extern void drm_fb_release(struct file *filp); extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group); extern struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter); +extern unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter); extern int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid); extern void drm_mode_probed_add(struct drm_connector *connector, struct drm_display_mode *mode); extern void drm_mode_remove(struct drm_connector *connector, struct drm_display_mode *mode); diff --git a/linux-core/drm_edid.c b/linux-core/drm_edid.c index 0d600396..812e64a6 100644 --- a/linux-core/drm_edid.c +++ b/linux-core/drm_edid.c @@ -122,19 +122,30 @@ static bool edid_is_valid(struct edid *edid) if (memcmp(edid->header, edid_header, sizeof(edid_header))) goto bad; - if (edid->version != 1) + if (edid->version != 1) { + DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version); goto bad; - if (edid->revision <= 0 || edid->revision > 3) + } + if (edid->revision <= 0 || edid->revision > 3) { + DRM_ERROR("EDID has minor version %d, which is not between 0-3\n", edid->revision); goto bad; + } for (i = 0; i < EDID_LENGTH; i++) csum += raw_edid[i]; - if (csum) + if (csum) { + DRM_ERROR("EDID checksum is invalid, remainder is %d\n", csum); goto bad; + } return 1; bad: + if (raw_edid) { + DRM_ERROR("Raw EDID:\n"); + print_hex_dump_bytes(KERN_ERR, DUMP_PREFIX_NONE, raw_edid, EDID_LENGTH); + printk("\n"); + } return 0; } @@ -545,7 +556,7 @@ static int add_detailed_info(struct drm_connector *connector, #define DDC_ADDR 0x50 -static unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) +unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) { unsigned char start = 0x0; unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL); @@ -576,6 +587,7 @@ static unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter) kfree(buf); return NULL; } +EXPORT_SYMBOL(drm_do_probe_ddc_edid); static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) { @@ -589,15 +601,15 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) * Then set clock & data low */ algo_data->setscl(algo_data->data, 1); - udelay(550); /* startup delay */ - algo_data->setscl(algo_data->data, 0); - algo_data->setsda(algo_data->data, 0); + //udelay(550); /* startup delay */ + //algo_data->setscl(algo_data->data, 0); + //algo_data->setsda(algo_data->data, 0); for (i = 0; i < 3; i++) { /* For some old monitors we need the * following process to initialize/stop DDC */ - algo_data->setsda(algo_data->data, 0); + algo_data->setsda(algo_data->data, 1); msleep(13); algo_data->setscl(algo_data->data, 1); @@ -632,16 +644,16 @@ static unsigned char *drm_ddc_read(struct i2c_adapter *adapter) algo_data->setsda(algo_data->data, 1); msleep(15); algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 0); if (edid) break; } /* Release the DDC lines when done or the Apple Cinema HD display * will switch off */ - algo_data->setsda(algo_data->data, 0); - algo_data->setscl(algo_data->data, 0); + algo_data->setsda(algo_data->data, 1); algo_data->setscl(algo_data->data, 1); - + return edid; } diff --git a/linux-core/drm_modes.c b/linux-core/drm_modes.c index 3ed427fb..df670c74 100644 --- a/linux-core/drm_modes.c +++ b/linux-core/drm_modes.c @@ -89,6 +89,7 @@ void drm_mode_list_concat(struct list_head *head, struct list_head *new) list_move_tail(entry, new); } } +EXPORT_SYMBOL(drm_mode_list_concat); /** * drm_mode_width - get the width of a mode @@ -401,6 +402,7 @@ void drm_mode_prune_invalid(struct drm_device *dev, } } } +EXPORT_SYMBOL(drm_mode_prune_invalid); /** * drm_mode_compare - compare modes for favorability @@ -525,7 +527,7 @@ void drm_mode_sort(struct list_head *mode_list) { list_sort(mode_list, drm_mode_compare); } - +EXPORT_SYMBOL(drm_mode_sort); /** * drm_mode_connector_list_update - update the mode list for the connector @@ -564,3 +566,4 @@ void drm_mode_connector_list_update(struct drm_connector *connector) } } } +EXPORT_SYMBOL(drm_mode_connector_list_update); diff --git a/linux-core/nouveau_bios.c b/linux-core/nouveau_bios.c new file mode 100644 index 00000000..83647418 --- /dev/null +++ b/linux-core/nouveau_bios.c @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2005-2006 Erik Waling + * Copyright (C) 2006 Stephane Marchesin + * Copyright (C) 2007-2008 Stuart Bennett + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <asm/byteorder.h> +#include "nouveau_bios.h" +#include "nouveau_drv.h" + +/* returns true if it mismatches */ +static bool nv_checksum(const uint8_t *data, unsigned int length) +{ + /* there's a few checksums in the BIOS, so here's a generic checking function */ + int i; + uint8_t sum = 0; + + for (i = 0; i < length; i++) + sum += data[i]; + + if (sum) + return true; + + return false; +} + +static int nv_valid_bios(struct drm_device *dev, uint8_t *data) +{ + /* check for BIOS signature */ + if (!(data[0] == 0x55 && data[1] == 0xAA)) { + DRM_ERROR("BIOS signature not found.\n"); + return 0; + } + + if (nv_checksum(data, data[2] * 512)) { + DRM_ERROR("BIOS checksum invalid.\n"); + return 1; + } + + return 2; +} + +static void nv_shadow_bios_rom(struct drm_device *dev, uint8_t *data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + /* enable access to rom */ + NV_WRITE(NV04_PBUS_PCI_NV_20, NV04_PBUS_PCI_NV_20_ROM_SHADOW_DISABLED); + + /* This is also valid for pre-NV50, it just happened to be the only define already present. */ + for (i=0; i < NV50_PROM__ESIZE; i++) { + /* Appearantly needed for a 6600GT/6800LE bug. */ + data[i] = DRM_READ8(dev_priv->mmio, NV50_PROM + i); + data[i] = DRM_READ8(dev_priv->mmio, NV50_PROM + i); + data[i] = DRM_READ8(dev_priv->mmio, NV50_PROM + i); + data[i] = DRM_READ8(dev_priv->mmio, NV50_PROM + i); + data[i] = DRM_READ8(dev_priv->mmio, NV50_PROM + i); + } + + /* disable access to rom */ + NV_WRITE(NV04_PBUS_PCI_NV_20, NV04_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED); +} + +static void nv_shadow_bios_ramin(struct drm_device *dev, uint8_t *data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t old_bar0_pramin = 0; + int i; + + /* Move the bios copy to the start of ramin? */ + if (dev_priv->card_type >= NV_50) { + uint32_t vbios_vram = (NV_READ(0x619f04) & ~0xff) << 8; + + if (!vbios_vram) + vbios_vram = (NV_READ(0x1700) << 16) + 0xf0000; + + old_bar0_pramin = NV_READ(0x1700); + NV_WRITE(0x1700, vbios_vram >> 16); + } + + for (i=0; i < NV50_PROM__ESIZE; i++) + data[i] = DRM_READ8(dev_priv->mmio, NV04_PRAMIN + i); + + if (dev_priv->card_type >= NV_50) + NV_WRITE(0x1700, old_bar0_pramin); +} + +static bool nv_shadow_bios(struct drm_device *dev, uint8_t *data) +{ + nv_shadow_bios_rom(dev, data); + if (nv_valid_bios(dev, data) == 2) + return true; + + nv_shadow_bios_ramin(dev, data); + if (nv_valid_bios(dev, data)) + return true; + + return false; +} + +struct bit_entry { + uint8_t id[2]; + uint16_t length; + uint16_t offset; +}; + +static int parse_bit_C_tbl_entry(struct drm_device *dev, struct bios *bios, struct bit_entry *bitentry) +{ + /* offset + 8 (16 bits): PLL limits table pointer + * + * There's more in here, but that's unknown. + */ + + if (bitentry->length < 10) { + DRM_ERROR( "Do not understand BIT C table\n"); + return 0; + } + + bios->pll_limit_tbl_ptr = le16_to_cpu(*((uint16_t *)(&bios->data[bitentry->offset + 8]))); + + return 1; +} + +static void parse_bit_structure(struct drm_device *dev, struct bios *bios, const uint16_t bitoffset) +{ + int entries = bios->data[bitoffset + 4]; + /* parse i first, I next (which needs C & M before it), and L before D */ + char parseorder[] = "iCMILDT"; + struct bit_entry bitentry; + int i, j, offset; + + for (i = 0; i < sizeof(parseorder); i++) { + for (j = 0, offset = bitoffset + 6; j < entries; j++, offset += 6) { + bitentry.id[0] = bios->data[offset]; + bitentry.id[1] = bios->data[offset + 1]; + bitentry.length = le16_to_cpu(*((uint16_t *)&bios->data[offset + 2])); + bitentry.offset = le16_to_cpu(*((uint16_t *)&bios->data[offset + 4])); + + if (bitentry.id[0] != parseorder[i]) + continue; + + switch (bitentry.id[0]) { + case 'C': + parse_bit_C_tbl_entry(dev, bios, &bitentry); + break; + } + } + } +} + +static uint16_t findstr(uint8_t *data, int n, const uint8_t *str, int len) +{ + int i, j; + + for (i = 0; i <= (n - len); i++) { + for (j = 0; j < len; j++) + if (data[i + j] != str[j]) + break; + if (j == len) + return i; + } + + return 0; +} + +static void +read_dcb_i2c_entry(struct drm_device *dev, uint8_t dcb_version, uint16_t i2ctabptr, int index) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct bios *bios = &dev_priv->bios; + uint8_t *i2ctable = &bios->data[i2ctabptr]; + uint8_t headerlen = 0; + int i2c_entries = MAX_NUM_DCB_ENTRIES; + int recordoffset = 0, rdofs = 1, wrofs = 0; + + if (!i2ctabptr) + return; + + if (dcb_version >= 0x30) { + if (i2ctable[0] != dcb_version) /* necessary? */ + DRM_ERROR( + "DCB I2C table version mismatch (%02X vs %02X)\n", + i2ctable[0], dcb_version); + headerlen = i2ctable[1]; + i2c_entries = i2ctable[2]; + + /* same address offset used for read and write for C51 and G80 */ + if (bios->chip_version == 0x51) + rdofs = wrofs = 1; + if (i2ctable[0] >= 0x40) + rdofs = wrofs = 0; + } + /* it's your own fault if you call this function on a DCB 1.1 BIOS -- + * the test below is for DCB 1.2 + */ + if (dcb_version < 0x14) { + recordoffset = 2; + rdofs = 0; + wrofs = 1; + } + + if (index == 0xf) + return; + if (index > i2c_entries) { + DRM_ERROR( + "DCB I2C index too big (%d > %d)\n", + index, i2ctable[2]); + return; + } + if (i2ctable[headerlen + 4 * index + 3] == 0xff) { + DRM_ERROR( + "DCB I2C entry invalid\n"); + return; + } + + if (bios->chip_version == 0x51) { + int port_type = i2ctable[headerlen + 4 * index + 3]; + + if (port_type != 4) + DRM_ERROR( + "DCB I2C table has port type %d\n", port_type); + } + if (i2ctable[0] >= 0x40) { + int port_type = i2ctable[headerlen + 4 * index + 3]; + + if (port_type != 5) + DRM_ERROR( + "DCB I2C table has port type %d\n", port_type); + } + + dev_priv->dcb_table.i2c_read[index] = i2ctable[headerlen + recordoffset + rdofs + 4 * index]; + dev_priv->dcb_table.i2c_write[index] = i2ctable[headerlen + recordoffset + wrofs + 4 * index]; +} + +static bool +parse_dcb_entry(struct drm_device *dev, int index, uint8_t dcb_version, uint16_t i2ctabptr, uint32_t conn, uint32_t conf) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_entry *entry = &dev_priv->dcb_table.entry[index]; + + memset(entry, 0, sizeof (struct dcb_entry)); + + entry->index = index; + /* safe defaults for a crt */ + entry->type = 0; + entry->i2c_index = 0; + entry->heads = 1; + entry->bus = 0; + entry->location = LOC_ON_CHIP; + entry->or = 1; + entry->duallink_possible = false; + + if (dcb_version >= 0x20) { + entry->type = conn & 0xf; + entry->i2c_index = (conn >> 4) & 0xf; + entry->heads = (conn >> 8) & 0xf; + entry->bus = (conn >> 16) & 0xf; + entry->location = (conn >> 20) & 0xf; + entry->or = (conn >> 24) & 0xf; + /* Normal entries consist of a single bit, but dual link has the + * adjacent more significant bit set too + */ + if ((1 << (ffs(entry->or) - 1)) * 3 == entry->or) + entry->duallink_possible = true; + + switch (entry->type) { + case DCB_OUTPUT_LVDS: + { + uint32_t mask; + if (conf & 0x1) + entry->lvdsconf.use_straps_for_mode = true; + if (dcb_version < 0x22) { + mask = ~0xd; + /* both 0x4 and 0x8 show up in v2.0 tables; assume they mean + * the same thing, which is probably wrong, but might work */ + if (conf & 0x4 || conf & 0x8) + entry->lvdsconf.use_power_scripts = true; + } else { + mask = ~0x5; + if (conf & 0x4) + entry->lvdsconf.use_power_scripts = true; + } + if (conf & mask) { + DRM_ERROR( + "Unknown LVDS configuration bits, please report\n"); + /* cause output setting to fail, so message is seen */ + dev_priv->dcb_table.entries = 0; + return false; + } + break; + } + case 0xe: + /* weird type that appears on g80 mobile bios; nv driver treats it as a terminator */ + return false; + } + read_dcb_i2c_entry(dev, dcb_version, i2ctabptr, entry->i2c_index); + } else if (dcb_version >= 0x14 ) { + if (conn != 0xf0003f00 && conn != 0xf2247f10 && conn != 0xf2204001 && conn != 0xf2204301 && conn != 0xf2244311 && conn != 0xf2045f14 && conn != 0xf2205004 && conn != 0xf2208001 && conn != 0xf4204011 && conn != 0xf4208011 && conn != 0xf4248011) { + DRM_ERROR( + "Unknown DCB 1.4 / 1.5 entry, please report\n"); + /* cause output setting to fail, so message is seen */ + dev_priv->dcb_table.entries = 0; + return false; + } + /* most of the below is a "best guess" atm */ + entry->type = conn & 0xf; + if (entry->type == 4) { /* digital */ + if (conn & 0x10) + entry->type = DCB_OUTPUT_LVDS; + else + entry->type = DCB_OUTPUT_TMDS; + } + /* what's in bits 5-13? could be some brooktree/chrontel/philips thing, in tv case */ + entry->i2c_index = (conn >> 14) & 0xf; + /* raw heads field is in range 0-1, so move to 1-2 */ + entry->heads = ((conn >> 18) & 0x7) + 1; + entry->location = (conn >> 21) & 0xf; + entry->bus = (conn >> 25) & 0x7; + /* set or to be same as heads -- hopefully safe enough */ + entry->or = entry->heads; + + switch (entry->type) { + case DCB_OUTPUT_LVDS: + /* this is probably buried in conn's unknown bits */ + entry->lvdsconf.use_power_scripts = true; + break; + case DCB_OUTPUT_TMDS: + /* invent a DVI-A output, by copying the fields of the DVI-D output + * reported to work by math_b on an NV20(!) */ + memcpy(&entry[1], &entry[0], sizeof(struct dcb_entry)); + entry[1].type = DCB_OUTPUT_ANALOG; + dev_priv->dcb_table.entries++; + } + read_dcb_i2c_entry(dev, dcb_version, i2ctabptr, entry->i2c_index); + } else if (dcb_version >= 0x12) { + /* v1.2 tables normally have the same 5 entries, which are not + * specific to the card, so use the defaults for a crt */ + /* DCB v1.2 does have an I2C table that read_dcb_i2c_table can handle, but cards + * exist (seen on nv11) where the pointer to the table points to the wrong + * place, so for now, we rely on the indices parsed in parse_bmp_structure + */ + entry->i2c_index = dev_priv->bios.legacy.i2c_indices.crt; + } else { /* pre DCB / v1.1 - use the safe defaults for a crt */ + DRM_ERROR( + "No information in BIOS output table; assuming a CRT output exists\n"); + entry->i2c_index = dev_priv->bios.legacy.i2c_indices.crt; + } + + if (entry->type == DCB_OUTPUT_LVDS && dev_priv->bios.fp.strapping != 0xff) + entry->lvdsconf.use_straps_for_mode = true; + + dev_priv->dcb_table.entries++; + + return true; +} + +static void merge_like_dcb_entries(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* DCB v2.0 lists each output combination separately. + * Here we merge compatible entries to have fewer outputs, with more options + */ + int i, newentries = 0; + + for (i = 0; i < dev_priv->dcb_table.entries; i++) { + struct dcb_entry *ient = &dev_priv->dcb_table.entry[i]; + int j; + + for (j = i + 1; j < dev_priv->dcb_table.entries; j++) { + struct dcb_entry *jent = &dev_priv->dcb_table.entry[j]; + + if (jent->type == 100) /* already merged entry */ + continue; + + /* merge heads field when all other fields the same */ + if (jent->i2c_index == ient->i2c_index && jent->type == ient->type && jent->location == ient->location && jent->or == ient->or) { + DRM_INFO( + "Merging DCB entries %d and %d\n", i, j); + ient->heads |= jent->heads; + jent->type = 100; /* dummy value */ + } + } + } + + /* Compact entries merged into others out of dcb_table */ + for (i = 0; i < dev_priv->dcb_table.entries; i++) { + if ( dev_priv->dcb_table.entry[i].type == 100 ) + continue; + + if (newentries != i) + memcpy(&dev_priv->dcb_table.entry[newentries], &dev_priv->dcb_table.entry[i], sizeof(struct dcb_entry)); + newentries++; + } + + dev_priv->dcb_table.entries = newentries; +} + +static unsigned int parse_dcb_table(struct drm_device *dev, struct bios *bios) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint16_t dcbptr, i2ctabptr = 0; + uint8_t *dcbtable; + uint8_t dcb_version, headerlen = 0x4, entries = MAX_NUM_DCB_ENTRIES; + bool configblock = true; + int recordlength = 8, confofs = 4; + int i; + + dev_priv->dcb_table.entries = 0; + + /* get the offset from 0x36 */ + dcbptr = le16_to_cpu(*(uint16_t *)&bios->data[0x36]); + + if (dcbptr == 0x0) { + DRM_ERROR( + "No Display Configuration Block pointer found\n"); + /* this situation likely means a really old card, pre DCB, so we'll add the safe CRT entry */ + parse_dcb_entry(dev, 0, 0, 0, 0, 0); + return 1; + } + + dcbtable = &bios->data[dcbptr]; + + /* get DCB version */ + dcb_version = dcbtable[0]; + DRM_INFO( + "Display Configuration Block version %d.%d found\n", + dcb_version >> 4, dcb_version & 0xf); + + if (dcb_version >= 0x20) { /* NV17+ */ + uint32_t sig; + + if (dcb_version >= 0x30) { /* NV40+ */ + headerlen = dcbtable[1]; + entries = dcbtable[2]; + recordlength = dcbtable[3]; + i2ctabptr = le16_to_cpu(*(uint16_t *)&dcbtable[4]); + sig = le32_to_cpu(*(uint32_t *)&dcbtable[6]); + + DRM_INFO( + "DCB header length %d, with %d possible entries\n", + headerlen, entries); + } else { + i2ctabptr = le16_to_cpu(*(uint16_t *)&dcbtable[2]); + sig = le32_to_cpu(*(uint32_t *)&dcbtable[4]); + headerlen = 8; + } + + if (sig != 0x4edcbdcb) { + DRM_ERROR( + "Bad Display Configuration Block signature (%08X)\n", sig); + return 0; + } + } else if (dcb_version >= 0x14) { /* some NV15/16, and NV11+ */ + char sig[8]; + + memset(sig, 0, 8); + strncpy(sig, (char *)&dcbtable[-7], 7); + i2ctabptr = le16_to_cpu(*(uint16_t *)&dcbtable[2]); + recordlength = 10; + confofs = 6; + + if (strcmp(sig, "DEV_REC")) { + DRM_ERROR( + "Bad Display Configuration Block signature (%s)\n", sig); + return 0; + } + } else if (dcb_version >= 0x12) { /* some NV6/10, and NV15+ */ + i2ctabptr = le16_to_cpu(*(uint16_t *)&dcbtable[2]); + configblock = false; + } else { /* NV5+, maybe NV4 */ + /* DCB 1.1 seems to be quite unhelpful - we'll just add the safe CRT entry */ + parse_dcb_entry(dev, 0, dcb_version, 0, 0, 0); + return 1; + } + + if (entries >= MAX_NUM_DCB_ENTRIES) + entries = MAX_NUM_DCB_ENTRIES; + + for (i = 0; i < entries; i++) { + uint32_t connection, config = 0; + + connection = le32_to_cpu(*(uint32_t *)&dcbtable[headerlen + recordlength * i]); + if (configblock) + config = le32_to_cpu(*(uint32_t *)&dcbtable[headerlen + confofs + recordlength * i]); + + /* Should we allow discontinuous DCBs? Certainly DCB I2C tables can be discontinuous */ + if ((connection & 0x0000000f) == 0x0000000f) /* end of records */ + break; + if (connection == 0x00000000) /* seen on an NV11 with DCB v1.5 */ + break; + + DRM_INFO("Raw DCB entry %d: %08x %08x\n", i, connection, config); + if (!parse_dcb_entry(dev, dev_priv->dcb_table.entries, dcb_version, i2ctabptr, connection, config)) + break; + } + + merge_like_dcb_entries(dev); + + return dev_priv->dcb_table.entries; +} + +int nouveau_parse_bios(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + const uint8_t bit_signature[] = { 'B', 'I', 'T' }; + int offset; + + dev_priv->bios.data = kzalloc(NV50_PROM__ESIZE, GFP_KERNEL); + + if (!nv_shadow_bios(dev, dev_priv->bios.data)) + return -EINVAL; + + dev_priv->bios.length = dev_priv->bios.data[2] * 512; + if (dev_priv->bios.length > NV50_PROM__ESIZE) + dev_priv->bios.length = NV50_PROM__ESIZE; + + if ((offset = findstr(dev_priv->bios.data, dev_priv->bios.length, bit_signature, sizeof(bit_signature)))) { + DRM_INFO("BIT BIOS found\n"); + parse_bit_structure(dev, &dev_priv->bios, offset + 4); + } else { + DRM_ERROR("BIT BIOS not found\n"); + return -EINVAL; + } + + if (parse_dcb_table(dev, &dev_priv->bios)) + DRM_INFO("Found %d entries in DCB\n", dev_priv->dcb_table.entries); + + return 0; +} + +/* temporary */ +#define NV_RAMDAC_NVPLL 0x00680500 +#define NV_RAMDAC_MPLL 0x00680504 +#define NV_RAMDAC_VPLL 0x00680508 +# define NV_RAMDAC_PLL_COEFF_MDIV 0x000000FF +# define NV_RAMDAC_PLL_COEFF_NDIV 0x0000FF00 +# define NV_RAMDAC_PLL_COEFF_PDIV 0x00070000 +# define NV30_RAMDAC_ENABLE_VCO2 (1 << 7) +#define NV_RAMDAC_VPLL2 0x00680520 + +bool get_pll_limits(struct drm_device *dev, uint32_t limit_match, struct pll_lims *pll_lim) +{ + /* PLL limits table + * + * Version 0x10: NV31 + * One byte header (version), one record of 24 bytes + * Version 0x11: NV36 - Not implemented + * Seems to have same record style as 0x10, but 3 records rather than 1 + * Version 0x20: Found on Geforce 6 cards + * Trivial 4 byte BIT header. 31 (0x1f) byte record length + * Version 0x21: Found on Geforce 7, 8 and some Geforce 6 cards + * 5 byte header, fifth byte of unknown purpose. 35 (0x23) byte record + * length in general, some (integrated) have an extra configuration byte + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct bios *bios = &dev_priv->bios; + uint8_t pll_lim_ver = 0, headerlen = 0, recordlen = 0, entries = 0; + int pllindex = 0; + uint32_t crystal_strap_mask, crystal_straps; + + if (!bios->pll_limit_tbl_ptr) { + if (bios->chip_version >= 0x40 || bios->chip_version == 0x31 || bios->chip_version == 0x36) { + DRM_ERROR("Pointer to PLL limits table invalid\n"); + return false; + } + } else { + pll_lim_ver = bios->data[bios->pll_limit_tbl_ptr]; + + DRM_INFO("Found PLL limits table version 0x%X\n", pll_lim_ver); + } + + crystal_strap_mask = 1 << 6; + /* open coded pNv->twoHeads test */ + if (bios->chip_version > 0x10 && bios->chip_version != 0x15 && + bios->chip_version != 0x1a && bios->chip_version != 0x20) + crystal_strap_mask |= 1 << 22; + crystal_straps = NV_READ(NV50_PEXTDEV + 0x0) & crystal_strap_mask; + + switch (pll_lim_ver) { + /* we use version 0 to indicate a pre limit table bios (single stage pll) + * and load the hard coded limits instead */ + case 0: + break; + case 0x10: + case 0x11: /* strictly v0x11 has 3 entries, but the last two don't seem to get used */ + headerlen = 1; + recordlen = 0x18; + entries = 1; + pllindex = 0; + break; + case 0x20: + case 0x21: + headerlen = bios->data[bios->pll_limit_tbl_ptr + 1]; + recordlen = bios->data[bios->pll_limit_tbl_ptr + 2]; + entries = bios->data[bios->pll_limit_tbl_ptr + 3]; + break; + default: + DRM_ERROR("PLL limits table revision not currently supported\n"); + return false; + } + + /* initialize all members to zero */ + memset(pll_lim, 0, sizeof(struct pll_lims)); + + if (pll_lim_ver == 0x10 || pll_lim_ver == 0x11) { + uint16_t plloffs = bios->pll_limit_tbl_ptr + headerlen + recordlen * pllindex; + + pll_lim->vco1.minfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs]))); + pll_lim->vco1.maxfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + 4]))); + pll_lim->vco2.minfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + 8]))); + pll_lim->vco2.maxfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + 12]))); + pll_lim->vco1.min_inputfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + 16]))); + pll_lim->vco2.min_inputfreq = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + 20]))); + pll_lim->vco1.max_inputfreq = pll_lim->vco2.max_inputfreq = INT_MAX; + + /* these values taken from nv30/31/36 */ + pll_lim->vco1.min_n = 0x1; + if (bios->chip_version == 0x36) + pll_lim->vco1.min_n = 0x5; + pll_lim->vco1.max_n = 0xff; + pll_lim->vco1.min_m = 0x1; + pll_lim->vco1.max_m = 0xd; + pll_lim->vco2.min_n = 0x4; + /* on nv30, 31, 36 (i.e. all cards with two stage PLLs with this + * table version (apart from nv35)), N2 is compared to + * maxN2 (0x46) and 10 * maxM2 (0x4), so set maxN2 to 0x28 and + * save a comparison + */ + pll_lim->vco2.max_n = 0x28; + if (bios->chip_version == 0x30 || bios->chip_version == 0x35) + /* only 5 bits available for N2 on nv30/35 */ + pll_lim->vco2.max_n = 0x1f; + pll_lim->vco2.min_m = 0x1; + pll_lim->vco2.max_m = 0x4; + } else if (pll_lim_ver) { /* ver 0x20, 0x21 */ + uint16_t plloffs = bios->pll_limit_tbl_ptr + headerlen; + uint32_t reg = 0; /* default match */ + int i; + + /* first entry is default match, if nothing better. warn if reg field nonzero */ + if (le32_to_cpu(*((uint32_t *)&bios->data[plloffs]))) + DRM_ERROR("Default PLL limit entry has non-zero register field\n"); + + if (limit_match > MAX_PLL_TYPES) + /* we've been passed a reg as the match */ + reg = limit_match; + else /* limit match is a pll type */ + for (i = 1; i < entries && !reg; i++) { + uint32_t cmpreg = le32_to_cpu(*((uint32_t *)(&bios->data[plloffs + recordlen * i]))); + + if (limit_match == NVPLL && (cmpreg == NV_RAMDAC_NVPLL || cmpreg == 0x4000)) + reg = cmpreg; + if (limit_match == MPLL && (cmpreg == NV_RAMDAC_MPLL || cmpreg == 0x4020)) + reg = cmpreg; + if (limit_match == VPLL1 && (cmpreg == NV_RAMDAC_VPLL || cmpreg == 0x4010)) + reg = cmpreg; + if (limit_match == VPLL2 && (cmpreg == NV_RAMDAC_VPLL2 || cmpreg == 0x4018)) + reg = cmpreg; + } + + for (i = 1; i < entries; i++) + if (le32_to_cpu(*((uint32_t *)&bios->data[plloffs + recordlen * i])) == reg) { + pllindex = i; + break; + } + + plloffs += recordlen * pllindex; + + DRM_INFO("Loading PLL limits for reg 0x%08x\n", pllindex ? reg : 0); + + /* frequencies are stored in tables in MHz, kHz are more useful, so we convert */ + + /* What output frequencies can each VCO generate? */ + pll_lim->vco1.minfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 4]))) * 1000; + pll_lim->vco1.maxfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 6]))) * 1000; + pll_lim->vco2.minfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 8]))) * 1000; + pll_lim->vco2.maxfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 10]))) * 1000; + + /* What input frequencies do they accept (past the m-divider)? */ + pll_lim->vco1.min_inputfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 12]))) * 1000; + pll_lim->vco2.min_inputfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 14]))) * 1000; + pll_lim->vco1.max_inputfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 16]))) * 1000; + pll_lim->vco2.max_inputfreq = le16_to_cpu(*((uint16_t *)(&bios->data[plloffs + 18]))) * 1000; + + /* What values are accepted as multiplier and divider? */ + pll_lim->vco1.min_n = bios->data[plloffs + 20]; + pll_lim->vco1.max_n = bios->data[plloffs + 21]; + pll_lim->vco1.min_m = bios->data[plloffs + 22]; + pll_lim->vco1.max_m = bios->data[plloffs + 23]; + pll_lim->vco2.min_n = bios->data[plloffs + 24]; + pll_lim->vco2.max_n = bios->data[plloffs + 25]; + pll_lim->vco2.min_m = bios->data[plloffs + 26]; + pll_lim->vco2.max_m = bios->data[plloffs + 27]; + + pll_lim->unk1c = bios->data[plloffs + 28]; + pll_lim->max_log2p_bias = bios->data[plloffs + 29]; + pll_lim->log2p_bias = bios->data[plloffs + 30]; + + if (recordlen > 0x22) + pll_lim->refclk = le32_to_cpu(*((uint32_t *)&bios->data[plloffs + 31])); + + if (recordlen > 0x23) + if (bios->data[plloffs + 35]) + DRM_ERROR("Bits set in PLL configuration byte (%x)\n", bios->data[plloffs + 35]); + + /* C51 special not seen elsewhere */ + /*if (bios->chip_version == 0x51 && !pll_lim->refclk) { + uint32_t sel_clk = nv32_rd(pScrn, NV_RAMDAC_SEL_CLK); + + if (((limit_match == NV_RAMDAC_VPLL || limit_match == VPLL1) && sel_clk & 0x20) || + ((limit_match == NV_RAMDAC_VPLL2 || limit_match == VPLL2) && sel_clk & 0x80)) { + if (nv_idx_port_rd(pScrn, CRTC_INDEX_COLOR, NV_VGA_CRTCX_REVISION) < 0xa3) + pll_lim->refclk = 200000; + else + pll_lim->refclk = 25000; + } + }*/ + } + + /* By now any valid limit table ought to have set a max frequency for + * vco1, so if it's zero it's either a pre limit table bios, or one + * with an empty limit table (seen on nv18) + */ + if (!pll_lim->vco1.maxfreq) { + pll_lim->vco1.minfreq = bios->fminvco; + pll_lim->vco1.maxfreq = bios->fmaxvco; + pll_lim->vco1.min_inputfreq = 0; + pll_lim->vco1.max_inputfreq = INT_MAX; + pll_lim->vco1.min_n = 0x1; + pll_lim->vco1.max_n = 0xff; + pll_lim->vco1.min_m = 0x1; + if (crystal_straps == 0) { + /* nv05 does this, nv11 doesn't, nv10 unknown */ + if (bios->chip_version < 0x11) + pll_lim->vco1.min_m = 0x7; + pll_lim->vco1.max_m = 0xd; + } else { + if (bios->chip_version < 0x11) + pll_lim->vco1.min_m = 0x8; + pll_lim->vco1.max_m = 0xe; + } + } + + if (!pll_lim->refclk) + switch (crystal_straps) { + case 0: + pll_lim->refclk = 13500; + break; + case (1 << 6): + pll_lim->refclk = 14318; + break; + case (1 << 22): + pll_lim->refclk = 27000; + break; + case (1 << 22 | 1 << 6): + pll_lim->refclk = 25000; + break; + } + +#if 1 /* for easy debugging */ + DRM_INFO("pll.vco1.minfreq: %d\n", pll_lim->vco1.minfreq); + DRM_INFO("pll.vco1.maxfreq: %d\n", pll_lim->vco1.maxfreq); + DRM_INFO("pll.vco2.minfreq: %d\n", pll_lim->vco2.minfreq); + DRM_INFO("pll.vco2.maxfreq: %d\n", pll_lim->vco2.maxfreq); + + DRM_INFO("pll.vco1.min_inputfreq: %d\n", pll_lim->vco1.min_inputfreq); + DRM_INFO("pll.vco1.max_inputfreq: %d\n", pll_lim->vco1.max_inputfreq); + DRM_INFO("pll.vco2.min_inputfreq: %d\n", pll_lim->vco2.min_inputfreq); + DRM_INFO("pll.vco2.max_inputfreq: %d\n", pll_lim->vco2.max_inputfreq); + + DRM_INFO("pll.vco1.min_n: %d\n", pll_lim->vco1.min_n); + DRM_INFO("pll.vco1.max_n: %d\n", pll_lim->vco1.max_n); + DRM_INFO("pll.vco1.min_m: %d\n", pll_lim->vco1.min_m); + DRM_INFO("pll.vco1.max_m: %d\n", pll_lim->vco1.max_m); + DRM_INFO("pll.vco2.min_n: %d\n", pll_lim->vco2.min_n); + DRM_INFO("pll.vco2.max_n: %d\n", pll_lim->vco2.max_n); + DRM_INFO("pll.vco2.min_m: %d\n", pll_lim->vco2.min_m); + DRM_INFO("pll.vco2.max_m: %d\n", pll_lim->vco2.max_m); + + DRM_INFO("pll.unk1c: %d\n", pll_lim->unk1c); + DRM_INFO("pll.max_log2p_bias: %d\n", pll_lim->max_log2p_bias); + DRM_INFO("pll.log2p_bias: %d\n", pll_lim->log2p_bias); + + DRM_INFO("pll.refclk: %d\n", pll_lim->refclk); +#endif + + return true; +} diff --git a/linux-core/nouveau_bios.h b/linux-core/nouveau_bios.h new file mode 100644 index 00000000..e33ecd0f --- /dev/null +++ b/linux-core/nouveau_bios.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2006 Erik Waling + * Copyright (C) 2006 Stephane Marchesin + * Copyright (C) 2007-2008 Stuart Bennett + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_BIOS_H__ +#define __NOUVEAU_BIOS_H__ + +#include "drmP.h" +#include "drm.h" + +#define LOC_ON_CHIP 0 + +enum dcb_output_type {/* matches DCB types */ + DCB_OUTPUT_NONE = 4, + DCB_OUTPUT_ANALOG = 0, + DCB_OUTPUT_TMDS = 2, + DCB_OUTPUT_LVDS = 3, + DCB_OUTPUT_TV = 1, +}; + +struct bios { + uint8_t *data; + unsigned int length; + bool execute; + + uint8_t major_version, chip_version; + uint8_t feature_byte; + + uint32_t fmaxvco, fminvco; + + uint32_t dactestval; + + uint16_t init_script_tbls_ptr; + uint16_t extra_init_script_tbl_ptr; + uint16_t macro_index_tbl_ptr; + uint16_t macro_tbl_ptr; + uint16_t condition_tbl_ptr; + uint16_t io_condition_tbl_ptr; + uint16_t io_flag_condition_tbl_ptr; + uint16_t init_function_tbl_ptr; + + uint16_t pll_limit_tbl_ptr; + uint16_t ram_restrict_tbl_ptr; + + struct { + struct nouveau_hw_mode *native_mode; + uint8_t *edid; + uint16_t lvdsmanufacturerpointer; + uint16_t xlated_entry; + bool power_off_for_reset; + bool reset_after_pclk_change; + bool dual_link; + bool link_c_increment; + bool if_is_24bit; + bool BITbit1; + int duallink_transition_clk; + /* lower nibble stores PEXTDEV_BOOT_0 strap + * upper nibble stores xlated display strap */ + uint8_t strapping; + } fp; + + struct { + uint16_t output0_script_ptr; + uint16_t output1_script_ptr; + } tmds; + + struct { + uint16_t mem_init_tbl_ptr; + uint16_t sdr_seq_tbl_ptr; + uint16_t ddr_seq_tbl_ptr; + + struct { + uint8_t crt, tv, panel; + } i2c_indices; + } legacy; +}; + +struct dcb_entry { + int index; + uint8_t type; + uint8_t i2c_index; + uint8_t heads; + uint8_t bus; + uint8_t location; + uint8_t or; + bool duallink_possible; + union { + struct { + bool use_straps_for_mode; + bool use_power_scripts; + } lvdsconf; + }; +}; + +/* changing these requires matching changes to reg tables in nv_get_clock */ +#define MAX_PLL_TYPES 4 +enum pll_types { + NVPLL, + MPLL, + VPLL1, + VPLL2 +}; + +struct pll_lims { + struct { + int minfreq; + int maxfreq; + int min_inputfreq; + int max_inputfreq; + + uint8_t min_m; + uint8_t max_m; + uint8_t min_n; + uint8_t max_n; + } vco1, vco2; + + uint8_t unk1c; + uint8_t max_log2p_bias; + uint8_t log2p_bias; + int refclk; +}; + +bool get_pll_limits(struct drm_device *dev, uint32_t limit_match, struct pll_lims *pll_lim); +int nouveau_parse_bios(struct drm_device *dev); + +#endif /* __NOUVEAU_BIOS_H__ */ diff --git a/linux-core/nouveau_drv.c b/linux-core/nouveau_drv.c index c8f57dff..04f002f2 100644 --- a/linux-core/nouveau_drv.c +++ b/linux-core/nouveau_drv.c @@ -28,6 +28,9 @@ #include "drm_pciids.h" +unsigned int nouveau_modeset = 0; /* kms */ +module_param_named(modeset, nouveau_modeset, int, 0400); + static struct pci_device_id pciidlist[] = { { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID), @@ -104,6 +107,10 @@ static int probe(struct pci_dev *pdev, const struct pci_device_id *ent) static int __init nouveau_init(void) { driver.num_ioctls = nouveau_max_ioctl; + + if (nouveau_modeset == 1) + driver.driver_features |= DRIVER_MODESET; + return drm_init(&driver, pciidlist); } diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c new file mode 100644 index 00000000..d13622b7 --- /dev/null +++ b/linux-core/nv50_connector.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_connector.h" + +static struct nv50_output *nv50_connector_to_output(struct nv50_connector *connector, bool digital) +{ + struct nv50_display *display = nv50_get_display(connector->dev); + struct nv50_output *output = NULL; + bool digital_possible = false; + bool analog_possible = false; + + switch (connector->type) { + case CONNECTOR_VGA: + case CONNECTOR_TV: + analog_possible = true; + break; + case CONNECTOR_DVI_I: + analog_possible = true; + digital_possible = true; + break; + case CONNECTOR_DVI_D: + case CONNECTOR_LVDS: + digital_possible = true; + break; + default: + break; + } + + /* Return early on bad situations. */ + if (!analog_possible && !digital_possible) + return NULL; + + if (!analog_possible && !digital) + return NULL; + + if (!digital_possible && digital) + return NULL; + + list_for_each_entry(output, &display->outputs, head) { + if (connector->bus != output->bus) + continue; + if (digital && output->type == OUTPUT_TMDS) + return output; + if (digital && output->type == OUTPUT_LVDS) + return output; + if (!digital && output->type == OUTPUT_DAC) + return output; + if (!digital && output->type == OUTPUT_TV) + return output; + } + + return NULL; +} + +static bool nv50_connector_detect(struct nv50_connector *connector) +{ + /* kindly borrrowed from the intel driver, hope it works. */ + uint8_t out_buf[] = { 0x0, 0x0}; + uint8_t buf[2]; + int ret; + struct i2c_msg msgs[] = { + { + .addr = 0x50, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = 0x50, + .flags = I2C_M_RD, + .len = 1, + .buf = buf, + } + }; + + NV50_DEBUG("\n"); + + if (!connector->i2c_chan) + return false; + + ret = i2c_transfer(&connector->i2c_chan->adapter, msgs, 2); + DRM_INFO("I2C detect returned %d\n", ret); + + if (ret == 2) + return true; + + return false; +} + +static int nv50_connector_destroy(struct nv50_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = nv50_get_display(dev); + + NV50_DEBUG("\n"); + + if (!display || !connector) + return -EINVAL; + + list_del(&connector->head); + + if (connector->i2c_chan) + nv50_i2c_channel_destroy(connector->i2c_chan); + + if (dev_priv->free_connector) + dev_priv->free_connector(connector); + + return 0; +} + +int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int type) +{ + struct nv50_connector *connector = NULL; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = NULL; + + NV50_DEBUG("\n"); + + /* This allows the public layer to do it's thing. */ + if (dev_priv->alloc_connector) + connector = dev_priv->alloc_connector(dev); + + if (!connector) + return -ENOMEM; + + connector->dev = dev; + + display = nv50_get_display(dev); + if (!display) + goto out; + + if (type == CONNECTOR_UNKNOWN) + goto out; + + list_add_tail(&connector->head, &display->connectors); + + connector->bus = bus; + connector->type = type; + + switch (type) { + case CONNECTOR_VGA: + DRM_INFO("Detected a VGA connector\n"); + break; + case CONNECTOR_DVI_D: + DRM_INFO("Detected a DVI-D connector\n"); + break; + case CONNECTOR_DVI_I: + DRM_INFO("Detected a DVI-I connector\n"); + break; + case CONNECTOR_LVDS: + DRM_INFO("Detected a LVDS connector\n"); + break; + case CONNECTOR_TV: + DRM_INFO("Detected a TV connector\n"); + break; + default: + DRM_ERROR("Unknown connector, this is not good.\n"); + break; + } + + /* some reasonable defaults */ + if (type == CONNECTOR_DVI_D || type == CONNECTOR_LVDS) + connector->scaling_mode = SCALE_FULLSCREEN; + else + connector->scaling_mode = SCALE_PANEL; + + if (i2c_index < 0xf) + connector->i2c_chan = nv50_i2c_channel_create(dev, i2c_index); + + /* set function pointers */ + connector->detect = nv50_connector_detect; + connector->destroy = nv50_connector_destroy; + connector->to_output = nv50_connector_to_output; + + return 0; + +out: + if (dev_priv->free_connector) + dev_priv->free_connector(connector); + + return -EINVAL; +} diff --git a/linux-core/nv50_connector.h b/linux-core/nv50_connector.h new file mode 100644 index 00000000..c70d6ef4 --- /dev/null +++ b/linux-core/nv50_connector.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_CONNECTOR_H__ +#define __NV50_CONNECTOR_H__ + +#include "nv50_output.h" +#include "nv50_i2c.h" + +#define CONNECTOR_UNKNOWN 0 +#define CONNECTOR_VGA 1 +#define CONNECTOR_DVI_D 2 +#define CONNECTOR_DVI_I 3 +#define CONNECTOR_LVDS 4 +#define CONNECTOR_TV 5 + +struct nv50_connector { + struct list_head head; + + struct drm_device *dev; + int type; + + int bus; + struct nv50_i2c_channel *i2c_chan; + struct nv50_output *output; + + int scaling_mode; + bool digital; /* last connected output, this has to be set from the outside*/ + + bool (*detect) (struct nv50_connector *connector); + int (*destroy) (struct nv50_connector *connector); + struct nv50_output *(*to_output) (struct nv50_connector *connector, bool digital); +}; + +int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int type); + +#endif /* __NV50_CONNECTOR_H__ */ diff --git a/linux-core/nv50_crtc.c b/linux-core/nv50_crtc.c new file mode 100644 index 00000000..974f5202 --- /dev/null +++ b/linux-core/nv50_crtc.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_crtc.h" +#include "nv50_cursor.h" +#include "nv50_lut.h" +#include "nv50_fb.h" + +static int nv50_crtc_validate_mode(struct nv50_crtc *crtc, struct nouveau_hw_mode *mode) +{ + NV50_DEBUG("\n"); + + if (mode->clock > 400000) + return MODE_CLOCK_HIGH; + + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static int nv50_crtc_set_mode(struct nv50_crtc *crtc, struct nouveau_hw_mode *mode) +{ + struct nouveau_hw_mode *hw_mode = crtc->mode; + uint8_t rval; + + NV50_DEBUG("index %d\n", crtc->index); + + if (!mode) { + DRM_ERROR("No mode\n"); + return MODE_NOMODE; + } + + if ((rval = crtc->validate_mode(crtc, mode))) { + DRM_ERROR("Mode invalid\n"); + return rval; + } + + /* copy values to mode */ + *hw_mode = *mode; + + return 0; +} + +static int nv50_crtc_execute_mode(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nouveau_hw_mode *hw_mode; + uint32_t hsync_dur, vsync_dur, hsync_start_to_end, vsync_start_to_end; + uint32_t hunk1, vunk1, vunk2a, vunk2b; + uint32_t offset = crtc->index * 0x400; + uint32_t pitch; + + NV50_DEBUG("index %d\n", crtc->index); + NV50_DEBUG("%s native mode\n", crtc->use_native_mode ? "using" : "not using"); + + if (crtc->use_native_mode) + hw_mode = crtc->native_mode; + else + hw_mode = crtc->mode; + + hsync_dur = hw_mode->hsync_end - hw_mode->hsync_start; + vsync_dur = hw_mode->vsync_end - hw_mode->vsync_start; + hsync_start_to_end = hw_mode->hblank_end - hw_mode->hsync_start; + vsync_start_to_end = hw_mode->vblank_end - hw_mode->vsync_start; + /* I can't give this a proper name, anyone else can? */ + hunk1 = hw_mode->htotal - hw_mode->hsync_start + hw_mode->hblank_start; + vunk1 = hw_mode->vtotal - hw_mode->vsync_start + hw_mode->vblank_start; + /* Another strange value, this time only for interlaced modes. */ + vunk2a = 2*hw_mode->vtotal - hw_mode->vsync_start + hw_mode->vblank_start; + vunk2b = hw_mode->vtotal - hw_mode->vsync_start + hw_mode->vblank_end; + + if (hw_mode->flags & V_INTERLACE) { + vsync_dur /= 2; + vsync_start_to_end /= 2; + vunk1 /= 2; + vunk2a /= 2; + vunk2b /= 2; + /* magic */ + if (hw_mode->flags & V_DBLSCAN) { + vsync_start_to_end -= 1; + vunk1 -= 1; + vunk2a -= 1; + vunk2b -= 1; + } + } + + OUT_MODE(NV50_CRTC0_CLOCK + offset, hw_mode->clock | 0x800000); + OUT_MODE(NV50_CRTC0_INTERLACE + offset, (hw_mode->flags & V_INTERLACE) ? 2 : 0); + OUT_MODE(NV50_CRTC0_DISPLAY_START + offset, 0); + OUT_MODE(NV50_CRTC0_UNK82C + offset, 0); + OUT_MODE(NV50_CRTC0_DISPLAY_TOTAL + offset, hw_mode->vtotal << 16 | hw_mode->htotal); + OUT_MODE(NV50_CRTC0_SYNC_DURATION + offset, (vsync_dur - 1) << 16 | (hsync_dur - 1)); + OUT_MODE(NV50_CRTC0_SYNC_START_TO_BLANK_END + offset, (vsync_start_to_end - 1) << 16 | (hsync_start_to_end - 1)); + OUT_MODE(NV50_CRTC0_MODE_UNK1 + offset, (vunk1 - 1) << 16 | (hunk1 - 1)); + if (hw_mode->flags & V_INTERLACE) { + OUT_MODE(NV50_CRTC0_MODE_UNK2 + offset, (vunk2b - 1) << 16 | (vunk2a - 1)); + } + OUT_MODE(NV50_CRTC0_FB_SIZE + offset, crtc->fb->height << 16 | crtc->fb->width); + + /* I suspect this flag indicates a linear fb. */ + pitch = ((crtc->fb->width + 63) & ~63) * (crtc->fb->bpp)/8; + NV50_DEBUG("fb_pitch %d\n", pitch); + OUT_MODE(NV50_CRTC0_FB_PITCH + offset, pitch | 0x100000); + + switch (crtc->fb->depth) { + case 8: + OUT_MODE(NV50_CRTC0_DEPTH + offset, NV50_CRTC0_DEPTH_8BPP); + break; + case 15: + OUT_MODE(NV50_CRTC0_DEPTH + offset, NV50_CRTC0_DEPTH_15BPP); + break; + case 16: + OUT_MODE(NV50_CRTC0_DEPTH + offset, NV50_CRTC0_DEPTH_16BPP); + break; + case 24: + OUT_MODE(NV50_CRTC0_DEPTH + offset, NV50_CRTC0_DEPTH_24BPP); + break; + } + crtc->set_dither(crtc); + OUT_MODE(NV50_CRTC0_COLOR_CTRL + offset, NV50_CRTC_COLOR_CTRL_MODE_COLOR); + OUT_MODE(NV50_CRTC0_FB_POS + offset, (crtc->fb->y << 16) | (crtc->fb->x)); + /* This is the actual resolution of the mode. */ + OUT_MODE(NV50_CRTC0_REAL_RES + offset, (crtc->mode->vdisplay << 16) | crtc->mode->hdisplay); + OUT_MODE(NV50_CRTC0_SCALE_CENTER_OFFSET + offset, NV50_CRTC_SCALE_CENTER_OFFSET_VAL(0,0)); + + /* Maybe move this as well? */ + crtc->blank(crtc, FALSE); + + return 0; +} + +static int nv50_crtc_blank(struct nv50_crtc *crtc, bool blanked) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + uint32_t offset = crtc->index * 0x400; + + NV50_DEBUG("index %d\n", crtc->index); + NV50_DEBUG("%s\n", blanked ? "blanked" : "unblanked"); + + /* We really need a framebuffer. */ + if (!crtc->fb->block && !blanked) { + DRM_ERROR("No framebuffer available on crtc %d\n", crtc->index); + return -EINVAL; + } + + if (blanked) { + crtc->cursor->hide(crtc); + + OUT_MODE(NV50_CRTC0_CLUT_MODE + offset, NV50_CRTC0_CLUT_MODE_BLANK); + OUT_MODE(NV50_CRTC0_CLUT_OFFSET + offset, 0); + if (dev_priv->chipset != 0x50) + OUT_MODE(NV84_CRTC0_BLANK_UNK1 + offset, NV84_CRTC0_BLANK_UNK1_BLANK); + OUT_MODE(NV50_CRTC0_BLANK_CTRL + offset, NV50_CRTC0_BLANK_CTRL_BLANK); + if (dev_priv->chipset != 0x50) + OUT_MODE(NV84_CRTC0_BLANK_UNK2 + offset, NV84_CRTC0_BLANK_UNK2_BLANK); + } else { + uint32_t ram_amount; + + OUT_MODE(NV50_CRTC0_FB_OFFSET + offset, crtc->fb->block->start >> 8); + OUT_MODE(0x864 + offset, 0); + /* maybe this needs to be moved. */ + NV_WRITE(NV50_PDISPLAY_UNK_380, 0); + /* RAM is clamped to 256 MiB. */ + ram_amount = nouveau_mem_fb_amount(crtc->dev); + NV50_DEBUG("ram_amount %d\n", ram_amount); + if (ram_amount > 256*1024*1024) + ram_amount = 256*1024*1024; + NV_WRITE(NV50_PDISPLAY_RAM_AMOUNT, ram_amount - 1); + NV_WRITE(NV50_PDISPLAY_UNK_388, 0x150000); + NV_WRITE(NV50_PDISPLAY_UNK_38C, 0); + if (crtc->cursor->block) + OUT_MODE(NV50_CRTC0_CURSOR_OFFSET + offset, crtc->cursor->block->start >> 8); + else + OUT_MODE(NV50_CRTC0_CURSOR_OFFSET + offset, 0); + if (dev_priv->chipset != 0x50) + OUT_MODE(NV84_CRTC0_BLANK_UNK2 + offset, NV84_CRTC0_BLANK_UNK2_UNBLANK); + + if (crtc->cursor->visible) + crtc->cursor->show(crtc); + else + crtc->cursor->hide(crtc); + + OUT_MODE(NV50_CRTC0_CLUT_MODE + offset, + crtc->fb->depth == 8 ? NV50_CRTC0_CLUT_MODE_OFF : NV50_CRTC0_CLUT_MODE_ON); + OUT_MODE(NV50_CRTC0_CLUT_OFFSET + offset, crtc->lut->block->start >> 8); + if (dev_priv->chipset != 0x50) + OUT_MODE(NV84_CRTC0_BLANK_UNK1 + offset, NV84_CRTC0_BLANK_UNK1_UNBLANK); + OUT_MODE(NV50_CRTC0_BLANK_CTRL + offset, NV50_CRTC0_BLANK_CTRL_UNBLANK); + } + + return 0; +} + +static int nv50_crtc_set_dither(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + uint32_t offset = crtc->index * 0x400; + + NV50_DEBUG("\n"); + + OUT_MODE(NV50_CRTC0_DITHERING_CTRL + offset, crtc->use_dithering ? + NV50_CRTC0_DITHERING_CTRL_ON : NV50_CRTC0_DITHERING_CTRL_OFF); + + return 0; +} + +static void nv50_crtc_calc_scale(struct nv50_crtc *crtc, uint32_t *outX, uint32_t *outY) +{ + float hor_scale, ver_scale; + + hor_scale = (float)crtc->native_mode->hdisplay/(float)crtc->mode->hdisplay; + ver_scale = (float)crtc->native_mode->vdisplay/(float)crtc->mode->vdisplay; + + if (ver_scale > hor_scale) { + *outX = crtc->mode->hdisplay * hor_scale; + *outY = crtc->mode->vdisplay * hor_scale; + } else { + *outX = crtc->mode->hdisplay * ver_scale; + *outY = crtc->mode->vdisplay * ver_scale; + } +} + +static int nv50_crtc_set_scale(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + uint32_t offset = crtc->index * 0x400; + uint32_t outX, outY; + + NV50_DEBUG("\n"); + + switch (crtc->scaling_mode) { + case SCALE_ASPECT: + nv50_crtc_calc_scale(crtc, &outX, &outY); + break; + case SCALE_FULLSCREEN: + outX = crtc->native_mode->hdisplay; + outY = crtc->native_mode->vdisplay; + break; + case SCALE_NOSCALE: + case SCALE_PANEL: + default: + outX = crtc->mode->hdisplay; + outY = crtc->mode->vdisplay; + break; + } + + /* Got a better name for SCALER_ACTIVE? */ + /* One day i've got to really figure out why this is needed. */ + if ((crtc->mode->flags & V_DBLSCAN) || (crtc->mode->flags & V_INTERLACE) || + crtc->mode->hdisplay != outX || crtc->mode->vdisplay != outY) { + OUT_MODE(NV50_CRTC0_SCALE_CTRL + offset, NV50_CRTC0_SCALE_CTRL_SCALER_ACTIVE); + } else { + OUT_MODE(NV50_CRTC0_SCALE_CTRL + offset, NV50_CRTC0_SCALE_CTRL_SCALER_INACTIVE); + } + + OUT_MODE(NV50_CRTC0_SCALE_RES1 + offset, outY << 16 | outX); + OUT_MODE(NV50_CRTC0_SCALE_RES2 + offset, outY << 16 | outX); + + return 0; +} + +static int nv50_crtc_calc_clock(struct nv50_crtc *crtc, + uint32_t *bestN1, uint32_t *bestN2, uint32_t *bestM1, uint32_t *bestM2, uint32_t *bestlog2P) +{ + struct nouveau_hw_mode *hw_mode; + struct pll_lims limits; + int clk, vco2, crystal; + int minvco1, minvco2, minU1, maxU1, minU2, maxU2, minM1, maxM1; + int maxvco1, maxvco2, minN1, maxN1, minM2, maxM2, minN2, maxN2; + bool fixedgain2; + int M1, N1, M2, N2, log2P; + int clkP, calcclk1, calcclk2, calcclkout; + int delta, bestdelta = INT_MAX; + int bestclk = 0; + + NV50_DEBUG("\n"); + + if (crtc->use_native_mode) + hw_mode = crtc->native_mode; + else + hw_mode = crtc->mode; + + clk = hw_mode->clock; + + /* These are in the g80 bios tables, at least in mine. */ + if (!get_pll_limits(crtc->dev, NV50_PDISPLAY_CRTC_CLK_CLK_CTRL1(crtc->index), &limits)) + return -EINVAL; + + minvco1 = limits.vco1.minfreq, maxvco1 = limits.vco1.maxfreq; + minvco2 = limits.vco2.minfreq, maxvco2 = limits.vco2.maxfreq; + minU1 = limits.vco1.min_inputfreq, minU2 = limits.vco2.min_inputfreq; + maxU1 = limits.vco1.max_inputfreq, maxU2 = limits.vco2.max_inputfreq; + minM1 = limits.vco1.min_m, maxM1 = limits.vco1.max_m; + minN1 = limits.vco1.min_n, maxN1 = limits.vco1.max_n; + minM2 = limits.vco2.min_m, maxM2 = limits.vco2.max_m; + minN2 = limits.vco2.min_n, maxN2 = limits.vco2.max_n; + crystal = limits.refclk; + fixedgain2 = (minM2 == maxM2 && minN2 == maxN2); + + vco2 = (maxvco2 - maxvco2/200) / 2; + for (log2P = 0; clk && log2P < 6 && clk <= (vco2 >> log2P); log2P++) /* log2P is maximum of 6 */ + ; + clkP = clk << log2P; + + if (maxvco2 < clk + clk/200) /* +0.5% */ + maxvco2 = clk + clk/200; + + for (M1 = minM1; M1 <= maxM1; M1++) { + if (crystal/M1 < minU1) + return bestclk; + if (crystal/M1 > maxU1) + continue; + + for (N1 = minN1; N1 <= maxN1; N1++) { + calcclk1 = crystal * N1 / M1; + if (calcclk1 < minvco1) + continue; + if (calcclk1 > maxvco1) + break; + + for (M2 = minM2; M2 <= maxM2; M2++) { + if (calcclk1/M2 < minU2) + break; + if (calcclk1/M2 > maxU2) + continue; + + /* add calcclk1/2 to round better */ + N2 = (clkP * M2 + calcclk1/2) / calcclk1; + if (N2 < minN2) + continue; + if (N2 > maxN2) + break; + + if (!fixedgain2) { + calcclk2 = calcclk1 * N2 / M2; + if (calcclk2 < minvco2) + break; + if (calcclk2 > maxvco2) + continue; + } else + calcclk2 = calcclk1; + + calcclkout = calcclk2 >> log2P; + delta = abs(calcclkout - clk); + /* we do an exhaustive search rather than terminating + * on an optimality condition... + */ + if (delta < bestdelta) { + bestdelta = delta; + bestclk = calcclkout; + *bestN1 = N1; + *bestN2 = N2; + *bestM1 = M1; + *bestM2 = M2; + *bestlog2P = log2P; + if (delta == 0) /* except this one */ + return bestclk; + } + } + } + } + + return bestclk; +} + +static int nv50_crtc_set_clock(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + uint32_t pll_reg = NV50_PDISPLAY_CRTC_CLK_CLK_CTRL1(crtc->index); + + uint32_t N1 = 0, N2 = 0, M1 = 0, M2 = 0, log2P = 0; + + uint32_t reg1 = NV_READ(pll_reg + 4); + uint32_t reg2 = NV_READ(pll_reg + 8); + + NV50_DEBUG("\n"); + + NV_WRITE(pll_reg, NV50_PDISPLAY_CRTC_CLK_CLK_CTRL1_CONNECTED | 0x10000011); + + /* The other bits are typically empty, but let's be on the safe side. */ + reg1 &= 0xff00ff00; + reg2 &= 0x8000ff00; + + if (!nv50_crtc_calc_clock(crtc, &N1, &N2, &M1, &M2, &log2P)) + return -EINVAL; + + NV50_DEBUG("N1 %d N2 %d M1 %d M2 %d log2P %d\n", N1, N2, M1, M2, log2P); + + reg1 |= (M1 << 16) | N1; + reg2 |= (log2P << 28) | (M2 << 16) | N2; + + NV_WRITE(pll_reg + 4, reg1); + NV_WRITE(pll_reg + 8, reg2); + + return 0; +} + +static int nv50_crtc_set_clock_mode(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + NV50_DEBUG("\n"); + + /* This acknowledges a clock request. */ + NV_WRITE(NV50_PDISPLAY_CRTC_CLK_CLK_CTRL2(crtc->index), 0); + + return 0; +} + +static int nv50_crtc_destroy(struct nv50_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = nv50_get_display(dev); + + NV50_DEBUG("\n"); + + if (!display || !crtc) + return -EINVAL; + + list_del(&crtc->head); + + nv50_fb_destroy(crtc); + nv50_lut_destroy(crtc); + nv50_cursor_destroy(crtc); + + kfree(crtc->mode); + kfree(crtc->native_mode); + + if (dev_priv->free_crtc) + dev_priv->free_crtc(crtc); + + return 0; +} + +int nv50_crtc_create(struct drm_device *dev, int index) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_crtc *crtc = NULL; + struct nv50_display *display = NULL; + + NV50_DEBUG("\n"); + + /* This allows the public layer to do it's thing. */ + if (dev_priv->alloc_crtc) + crtc = dev_priv->alloc_crtc(dev); + + if (!crtc) + return -ENOMEM; + + crtc->dev = dev; + + display = nv50_get_display(dev); + if (!display) + goto out; + + list_add_tail(&crtc->head, &display->crtcs); + + crtc->index = index; + + crtc->mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + crtc->native_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + + nv50_fb_create(crtc); + nv50_lut_create(crtc); + nv50_cursor_create(crtc); + + /* set function pointers */ + crtc->validate_mode = nv50_crtc_validate_mode; + crtc->set_mode = nv50_crtc_set_mode; + crtc->execute_mode = nv50_crtc_execute_mode; + crtc->blank = nv50_crtc_blank; + crtc->set_dither = nv50_crtc_set_dither; + crtc->set_scale = nv50_crtc_set_scale; + crtc->set_clock = nv50_crtc_set_clock; + crtc->set_clock_mode = nv50_crtc_set_clock_mode; + crtc->destroy = nv50_crtc_destroy; + + return 0; + +out: + if (crtc->mode) + kfree(crtc->mode); + if (crtc->native_mode) + kfree(crtc->native_mode); + if (dev_priv->free_crtc) + dev_priv->free_crtc(crtc); + + return -EINVAL; +} diff --git a/linux-core/nv50_crtc.h b/linux-core/nv50_crtc.h new file mode 100644 index 00000000..5eb815a5 --- /dev/null +++ b/linux-core/nv50_crtc.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_CRTC_H__ +#define __NV50_CRTC_H__ + +#include "nv50_display.h" + +struct nv50_cursor; +struct nv50_lut; +struct nv50_fb; + +struct nv50_crtc { + struct list_head head; + + struct drm_device *dev; + int index; + bool active; + + struct nouveau_hw_mode *mode; + struct nouveau_hw_mode *native_mode; + + bool use_native_mode; + bool use_dithering; + int scaling_mode; + + struct nv50_cursor *cursor; + struct nv50_lut *lut; + struct nv50_fb *fb; + + int (*validate_mode) (struct nv50_crtc *crtc, struct nouveau_hw_mode *mode); + int (*set_mode) (struct nv50_crtc *crtc, struct nouveau_hw_mode *mode); + int (*execute_mode) (struct nv50_crtc *crtc); + int (*blank) (struct nv50_crtc *crtc, bool blanked); + int (*set_dither) (struct nv50_crtc *crtc); + int (*set_scale) (struct nv50_crtc *crtc); + int (*set_clock) (struct nv50_crtc *crtc); + int (*set_clock_mode) (struct nv50_crtc *crtc); + int (*destroy) (struct nv50_crtc *crtc); +}; + +int nv50_crtc_create(struct drm_device *dev, int index); + +#endif /* __NV50_CRTC_H__ */ diff --git a/linux-core/nv50_cursor.c b/linux-core/nv50_cursor.c new file mode 100644 index 00000000..3d35b936 --- /dev/null +++ b/linux-core/nv50_cursor.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_cursor.h" +#include "nv50_crtc.h" +#include "nv50_display.h" + +static int nv50_cursor_enable(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + NV50_DEBUG("\n"); + + NV_WRITE(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index), 0x2000); + while(NV_READ(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index)) & NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_MASK); + + NV_WRITE(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index), NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON); + while((NV_READ(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index)) & NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_MASK) + != NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE); + + crtc->cursor->enabled = true; + + return 0; +} + +static int nv50_cursor_disable(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + NV50_DEBUG("\n"); + + NV_WRITE(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index), 0); + while(NV_READ(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(crtc->index)) & NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_MASK); + + crtc->cursor->enabled = false; + + return 0; +} + +/* Calling update or changing the stored cursor state is left to the higher level ioctl's. */ +static int nv50_cursor_show(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + uint32_t offset = crtc->index * 0x400; + + NV50_DEBUG("\n"); + + /* Better not show the cursor when we have none. */ + /* TODO: is cursor offset actually set? */ + if (!crtc->cursor->block) { + DRM_ERROR("No cursor available on crtc %d\n", crtc->index); + return -EINVAL; + } + + OUT_MODE(NV50_CRTC0_CURSOR_CTRL + offset, NV50_CRTC0_CURSOR_CTRL_SHOW); + + return 0; +} + +static int nv50_cursor_hide(struct nv50_crtc *crtc) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + uint32_t offset = crtc->index * 0x400; + + NV50_DEBUG("\n"); + + OUT_MODE(NV50_CRTC0_CURSOR_CTRL + offset, NV50_CRTC0_CURSOR_CTRL_HIDE); + + return 0; +} + +static int nv50_cursor_set_pos(struct nv50_crtc *crtc, int x, int y) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + NV_WRITE(NV50_HW_CURSOR_POS(crtc->index), ((y & 0xFFFF) << 16) | (x & 0xFFFF)); + /* Needed to make the cursor move. */ + NV_WRITE(NV50_HW_CURSOR_POS_CTRL(crtc->index), 0); + + return 0; +} + +static int nv50_cursor_set_bo(struct nv50_crtc *crtc, drm_handle_t handle) +{ + struct mem_block *block = NULL; + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + + NV50_DEBUG("\n"); + + block = find_block_by_handle(dev_priv->fb_heap, handle); + + if (block) { + bool first_time = false; + if (!crtc->cursor->block) + first_time = true; + + crtc->cursor->block = block; + + /* set the cursor offset cursor */ + if (first_time) { + OUT_MODE(NV50_CRTC0_CURSOR_OFFSET + crtc->index * 0x400, crtc->cursor->block->start >> 8); + if (crtc->cursor->visible) + crtc->cursor->show(crtc); + } + } else { + return -EINVAL; + } + + return 0; +} + +int nv50_cursor_create(struct nv50_crtc *crtc) +{ + NV50_DEBUG("\n"); + + if (!crtc) + return -EINVAL; + + crtc->cursor = kzalloc(sizeof(struct nv50_cursor), GFP_KERNEL); + + /* function pointers */ + crtc->cursor->show = nv50_cursor_show; + crtc->cursor->hide = nv50_cursor_hide; + crtc->cursor->set_pos = nv50_cursor_set_pos; + crtc->cursor->set_bo = nv50_cursor_set_bo; + crtc->cursor->enable = nv50_cursor_enable; + crtc->cursor->disable = nv50_cursor_disable; + + /* defaults */ + crtc->cursor->visible = true; /* won't happen until there is a cursor bo */ + + return 0; +} + +int nv50_cursor_destroy(struct nv50_crtc *crtc) +{ + int rval = 0; + + NV50_DEBUG("\n"); + + if (!crtc) + return -EINVAL; + + if (crtc->cursor->enabled) { + rval = crtc->cursor->disable(crtc); + if (rval != 0) + return rval; + } + + kfree(crtc->cursor); + crtc->cursor = NULL; + + return 0; +} diff --git a/linux-core/nv50_cursor.h b/linux-core/nv50_cursor.h new file mode 100644 index 00000000..a2e4632c --- /dev/null +++ b/linux-core/nv50_cursor.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_CURSOR_H__ +#define __NV50_CURSOR_H__ + +#include "nv50_display.h" + +struct nv50_crtc; + +struct nv50_cursor { + struct mem_block *block; + int x, y; + bool visible; + bool enabled; + + int (*show) (struct nv50_crtc *crtc); + int (*hide) (struct nv50_crtc *crtc); + int (*set_pos) (struct nv50_crtc *crtc, int x, int y); + int (*set_bo) (struct nv50_crtc *crtc, drm_handle_t handle); + int (*enable) (struct nv50_crtc *crtc); + int (*disable) (struct nv50_crtc *crtc); +}; + +int nv50_cursor_create(struct nv50_crtc *crtc); +int nv50_cursor_destroy(struct nv50_crtc *crtc); + +#endif /* __NV50_CURSOR_H__ */ diff --git a/linux-core/nv50_dac.c b/linux-core/nv50_dac.c new file mode 100644 index 00000000..f827fed4 --- /dev/null +++ b/linux-core/nv50_dac.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_output.h" + +static int nv50_dac_validate_mode(struct nv50_output *output, struct nouveau_hw_mode *mode) +{ + NV50_DEBUG("\n"); + + if (mode->clock > 400000) + return MODE_CLOCK_HIGH; + + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static int nv50_dac_execute_mode(struct nv50_output *output, bool disconnect) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + struct nv50_crtc *crtc = output->crtc; + struct nouveau_hw_mode *desired_mode = NULL; + + uint32_t offset = nv50_output_or_offset(output) * 0x80; + + uint32_t mode_ctl = NV50_DAC_MODE_CTRL_OFF; + uint32_t mode_ctl2 = 0; + + NV50_DEBUG("or %d\n", nv50_output_or_offset(output)); + + if (disconnect) { + NV50_DEBUG("Disconnecting DAC\n"); + OUT_MODE(NV50_DAC0_MODE_CTRL + offset, mode_ctl); + return 0; + } + + desired_mode = (crtc->use_native_mode ? crtc->native_mode : + crtc->mode); + + if (crtc->index == 1) + mode_ctl |= NV50_DAC_MODE_CTRL_CRTC1; + else + mode_ctl |= NV50_DAC_MODE_CTRL_CRTC0; + + /* Lacking a working tv-out, this is not a 100% sure. */ + if (output->type == OUTPUT_DAC) { + mode_ctl |= 0x40; + } else if (output->type == OUTPUT_TV) { + mode_ctl |= 0x100; + } + + if (desired_mode->flags & V_NHSYNC) + mode_ctl2 |= NV50_DAC_MODE_CTRL2_NHSYNC; + + if (desired_mode->flags & V_NVSYNC) + mode_ctl2 |= NV50_DAC_MODE_CTRL2_NVSYNC; + + OUT_MODE(NV50_DAC0_MODE_CTRL + offset, mode_ctl); + OUT_MODE(NV50_DAC0_MODE_CTRL2 + offset, mode_ctl2); + + return 0; +} + +static int nv50_dac_set_clock_mode(struct nv50_output *output) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + + NV50_DEBUG("or %d\n", nv50_output_or_offset(output)); + + NV_WRITE(NV50_PDISPLAY_DAC_CLK_CLK_CTRL2(nv50_output_or_offset(output)), 0); + + return 0; +} + +static int nv50_dac_destroy(struct nv50_output *output) +{ + struct drm_device *dev = output->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = nv50_get_display(dev); + + NV50_DEBUG("\n"); + + if (!display || !output) + return -EINVAL; + + list_del(&output->head); + + kfree(output->native_mode); + if (dev_priv->free_output) + dev_priv->free_output(output); + + return 0; +} + +int nv50_dac_create(struct drm_device *dev, int dcb_entry) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_output *output = NULL; + struct nv50_display *display = NULL; + struct dcb_entry *entry = NULL; + + NV50_DEBUG("\n"); + + /* This allows the public layer to do it's thing. */ + if (dev_priv->alloc_output) + output = dev_priv->alloc_output(dev); + + if (!output) + return -ENOMEM; + + output->dev = dev; + + display = nv50_get_display(dev); + if (!display) + goto out; + + entry = &dev_priv->dcb_table.entry[dcb_entry]; + if (!entry) + goto out; + + switch (entry->type) { + case DCB_OUTPUT_ANALOG: + output->type = OUTPUT_DAC; + DRM_INFO("Detected a DAC output\n"); + break; + default: + goto out; + } + + output->dcb_entry = dcb_entry; + output->bus = entry->bus; + + list_add_tail(&output->head, &display->outputs); + + output->native_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + + /* Set function pointers. */ + output->validate_mode = nv50_dac_validate_mode; + output->execute_mode = nv50_dac_execute_mode; + output->set_clock_mode = nv50_dac_set_clock_mode; + output->detect = NULL; /* TODO */ + output->destroy = nv50_dac_destroy; + + return 0; + +out: + if (output->native_mode) + kfree(output->native_mode); + if (dev_priv->free_output) + dev_priv->free_output(output); + return -EINVAL; +} + diff --git a/linux-core/nv50_display.c b/linux-core/nv50_display.c new file mode 100644 index 00000000..56ddeb97 --- /dev/null +++ b/linux-core/nv50_display.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_display.h" +#include "nv50_crtc.h" +#include "nv50_output.h" +#include "nv50_connector.h" + +static int nv50_display_pre_init(struct nv50_display *display) +{ + struct drm_device *dev = display->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + NV50_DEBUG("\n"); + + NV_WRITE(0x00610184, NV_READ(0x00614004)); + /* + * I think the 0x006101XX range is some kind of main control area that enables things. + */ + /* CRTC? */ + NV_WRITE(0x00610190 + 0 * 0x10, NV_READ(0x00616100 + 0 * 0x800)); + NV_WRITE(0x00610190 + 1 * 0x10, NV_READ(0x00616100 + 1 * 0x800)); + NV_WRITE(0x00610194 + 0 * 0x10, NV_READ(0x00616104 + 0 * 0x800)); + NV_WRITE(0x00610194 + 1 * 0x10, NV_READ(0x00616104 + 1 * 0x800)); + NV_WRITE(0x00610198 + 0 * 0x10, NV_READ(0x00616108 + 0 * 0x800)); + NV_WRITE(0x00610198 + 1 * 0x10, NV_READ(0x00616108 + 1 * 0x800)); + NV_WRITE(0x0061019c + 0 * 0x10, NV_READ(0x0061610c + 0 * 0x800)); + NV_WRITE(0x0061019c + 1 * 0x10, NV_READ(0x0061610c + 1 * 0x800)); + /* DAC */ + NV_WRITE(0x006101d0 + 0 * 0x4, NV_READ(0x0061a000 + 0 * 0x800)); + NV_WRITE(0x006101d0 + 1 * 0x4, NV_READ(0x0061a000 + 1 * 0x800)); + NV_WRITE(0x006101d0 + 2 * 0x4, NV_READ(0x0061a000 + 2 * 0x800)); + /* SOR */ + NV_WRITE(0x006101e0 + 0 * 0x4, NV_READ(0x0061c000 + 0 * 0x800)); + NV_WRITE(0x006101e0 + 1 * 0x4, NV_READ(0x0061c000 + 1 * 0x800)); + /* Something not yet in use, tv-out maybe. */ + NV_WRITE(0x006101f0 + 0 * 0x4, NV_READ(0x0061e000 + 0 * 0x800)); + NV_WRITE(0x006101f0 + 1 * 0x4, NV_READ(0x0061e000 + 1 * 0x800)); + NV_WRITE(0x006101f0 + 2 * 0x4, NV_READ(0x0061e000 + 2 * 0x800)); + + for (i = 0; i < 3; i++) { + NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(i), 0x00550000 | NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING); + NV_WRITE(NV50_PDISPLAY_DAC_REGS_CLK_CTRL1(i), 0x00000001); + } + + display->preinit_done = TRUE; + + return 0; +} + +static int nv50_display_init(struct nv50_display *display) +{ + struct drm_device *dev = display->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t val; + + NV50_DEBUG("\n"); + + /* The precise purpose is unknown, i suspect it has something to do with text mode. */ + if (NV_READ(NV50_PDISPLAY_SUPERVISOR) & 0x100) { + NV_WRITE(NV50_PDISPLAY_SUPERVISOR, 0x100); + NV_WRITE(0x006194e8, NV_READ(0x006194e8) & ~1); + while (NV_READ(0x006194e8) & 2); + } + + /* taken from nv bug #12637 */ + NV_WRITE(NV50_PDISPLAY_UNK200_CTRL, 0x2b00); + do { + val = NV_READ(NV50_PDISPLAY_UNK200_CTRL); + if ((val & 0x9f0000) == 0x20000) + NV_WRITE(NV50_PDISPLAY_UNK200_CTRL, val | 0x800000); + + if ((val & 0x3f0000) == 0x30000) + NV_WRITE(NV50_PDISPLAY_UNK200_CTRL, val | 0x200000); + } while (val & 0x1e0000); + + NV_WRITE(NV50_PDISPLAY_CTRL_STATE, NV50_PDISPLAY_CTRL_STATE_ENABLE); + NV_WRITE(NV50_PDISPLAY_UNK200_CTRL, 0x1000b03); + while (!(NV_READ(NV50_PDISPLAY_UNK200_CTRL) & 0x40000000)); + + /* For the moment this is just a wrapper, which should be replaced with a real fifo at some point. */ + OUT_MODE(NV50_UNK84, 0); + OUT_MODE(NV50_UNK88, 0); + OUT_MODE(NV50_CRTC0_BLANK_CTRL, NV50_CRTC0_BLANK_CTRL_BLANK); + OUT_MODE(NV50_CRTC0_UNK800, 0); + OUT_MODE(NV50_CRTC0_DISPLAY_START, 0); + OUT_MODE(NV50_CRTC0_UNK82C, 0); + + /* enable clock change interrupts. */ + NV_WRITE(NV50_PDISPLAY_SUPERVISOR_INTR, NV_READ(NV50_PDISPLAY_SUPERVISOR_INTR) | 0x70); + + display->init_done = TRUE; + + return 0; +} + +static int nv50_display_disable(struct nv50_display *display) +{ + struct drm_device *dev = display->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_crtc *crtc = NULL; + int i; + + NV50_DEBUG("\n"); + + list_for_each_entry(crtc, &display->crtcs, head) { + crtc->blank(crtc, TRUE); + } + + display->update(display); + + /* Almost like ack'ing a vblank interrupt, maybe in the spirit of cleaning up? */ + list_for_each_entry(crtc, &display->crtcs, head) { + if (crtc->active) { + uint32_t mask; + + if (crtc->index == 1) + mask = NV50_PDISPLAY_SUPERVISOR_CRTC1; + else + mask = NV50_PDISPLAY_SUPERVISOR_CRTC0; + + NV_WRITE(NV50_PDISPLAY_SUPERVISOR, mask); + while (!(NV_READ(NV50_PDISPLAY_SUPERVISOR) & mask)); + } + } + + NV_WRITE(NV50_PDISPLAY_UNK200_CTRL, 0); + NV_WRITE(NV50_PDISPLAY_CTRL_STATE, 0); + while ((NV_READ(NV50_PDISPLAY_UNK200_CTRL) & 0x1e0000) != 0); + + for (i = 0; i < 2; i++) { + while (NV_READ(NV50_PDISPLAY_SOR_REGS_DPMS_STATE(i)) & NV50_PDISPLAY_SOR_REGS_DPMS_STATE_WAIT); + } + + /* disable clock change interrupts. */ + NV_WRITE(NV50_PDISPLAY_SUPERVISOR_INTR, NV_READ(NV50_PDISPLAY_SUPERVISOR_INTR) & ~0x70); + + display->init_done = FALSE; + + return 0; +} + +static int nv50_display_update(struct nv50_display *display) +{ + struct drm_device *dev = display->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + + NV50_DEBUG("\n"); + + OUT_MODE(NV50_UPDATE_DISPLAY, 0); + + return 0; +} + +int nv50_display_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = kzalloc(sizeof(struct nv50_display), GFP_KERNEL); + int i, type, output_index, bus; + /* DAC0, DAC1, DAC2, SOR0, SOR1*/ + int or_counter[5] = {0, 0, 0, 0, 0}; + int i2c_index[5] = {0, 0, 0, 0, 0}; + uint32_t bus_mask = 0; + uint32_t bus_digital = 0, bus_analog = 0; + + NV50_DEBUG("\n"); + + INIT_LIST_HEAD(&display->crtcs); + INIT_LIST_HEAD(&display->outputs); + INIT_LIST_HEAD(&display->connectors); + + dev_priv->display_priv = display; + + for (i = 0; i < 2; i++) { + nv50_crtc_create(dev, i); + } + + /* we setup the outputs up from the BIOS table */ + for (i = 0 ; i < dev_priv->dcb_table.entries; i++) { + type = dev_priv->dcb_table.entry[i].type; + output_index = ffs(dev_priv->dcb_table.entry[i].or) - 1; + bus = dev_priv->dcb_table.entry[i].bus; + + switch (type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_LVDS: + or_counter[output_index + 3] += 1; + i2c_index[output_index + 3] = dev_priv->dcb_table.entry[i].i2c_index; + bus_digital |= (1 << bus); + nv50_sor_create(dev, i); + break; + case DCB_OUTPUT_ANALOG: + or_counter[output_index] += 1; + i2c_index[output_index] = dev_priv->dcb_table.entry[i].i2c_index; + bus_analog |= (1 << bus); + nv50_dac_create(dev, i); + break; + default: + break; + } + + } + + /* setup the connectors based on the output tables. */ + for (i = 0 ; i < dev_priv->dcb_table.entries; i++) { + int connector_type = 0; + type = dev_priv->dcb_table.entry[i].type; + bus = dev_priv->dcb_table.entry[i].bus; + + /* already done? */ + if (bus_mask & (1 << bus)) + continue; + + /* only do it for supported outputs */ + if (type != DCB_OUTPUT_ANALOG && type != DCB_OUTPUT_TMDS + && type != DCB_OUTPUT_LVDS) + continue; + + switch (type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_ANALOG: + if ((bus_digital & (1 << bus)) && (bus_analog & (1 << bus))) + connector_type = CONNECTOR_DVI_I; + else if (bus_digital & (1 << bus)) + connector_type = CONNECTOR_DVI_D; + else if (bus_analog & (1 << bus)) + connector_type = CONNECTOR_VGA; + break; + case DCB_OUTPUT_LVDS: + connector_type = CONNECTOR_LVDS; + break; + default: + connector_type = CONNECTOR_UNKNOWN; + break; + } + + if (connector_type == CONNECTOR_UNKNOWN) + continue; + + nv50_connector_create(dev, bus, dev_priv->dcb_table.entry[i].i2c_index, connector_type); + + bus_mask |= (1 << bus); + } + + display->dev = dev; + + /* function pointers */ + display->init = nv50_display_init; + display->pre_init = nv50_display_pre_init; + display->disable = nv50_display_disable; + display->update = nv50_display_update; + + return 0; +} + +int nv50_display_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = nv50_get_display(dev); + struct nv50_crtc *crtc = NULL; + struct nv50_output *output = NULL; + struct nv50_connector *connector = NULL; + + NV50_DEBUG("\n"); + + if (display->init_done) + display->disable(display); + + list_for_each_entry(connector, &display->connectors, head) { + connector->destroy(connector); + } + + list_for_each_entry(output, &display->outputs, head) { + output->destroy(output); + } + + list_for_each_entry(crtc, &display->crtcs, head) { + crtc->destroy(crtc); + } + + kfree(display); + dev_priv->display_priv = NULL; + + return 0; +} + +/* This can be replaced with a real fifo in the future. */ +void nv50_display_command(struct drm_nouveau_private *dev_priv, uint32_t mthd, uint32_t val) +{ + uint32_t counter = 0; + +#if 1 + DRM_INFO("mthd 0x%03X val 0x%08X\n", mthd, val); +#endif + + NV_WRITE(NV50_PDISPLAY_CTRL_VAL, val); + NV_WRITE(NV50_PDISPLAY_CTRL_STATE, NV50_PDISPLAY_CTRL_STATE_PENDING | 0x10000 | mthd | NV50_PDISPLAY_CTRL_STATE_ENABLE); + + while (NV_READ(NV50_PDISPLAY_CTRL_STATE) & NV50_PDISPLAY_CTRL_STATE_PENDING) { + counter++; + if (counter > 25000) { + DRM_ERROR("You probably need a reboot now\n"); + break; + } + } +} + +struct nv50_display *nv50_get_display(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + return (struct nv50_display *) dev_priv->display_priv; +} diff --git a/linux-core/nv50_display.h b/linux-core/nv50_display.h new file mode 100644 index 00000000..f20e67da --- /dev/null +++ b/linux-core/nv50_display.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_DISPLAY_H__ +#define __NV50_DISPLAY_H__ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_dma.h" +#include "nouveau_drv.h" +#include "nouveau_reg.h" +#include "nv50_display_commands.h" + +/* for convience, so you can see through the trees. */ +#define NV50_DEBUG DRM_ERROR + +struct nouveau_hw_mode { + unsigned int clock; + unsigned short hdisplay, hblank_start, hsync_start, hsync_end, hblank_end, htotal; + unsigned short vdisplay, vblank_start, vsync_start, vsync_end, vblank_end, vtotal; + + unsigned int flags; +}; + +struct nv50_crtc; +struct nv50_output; +struct nv50_connector; + +struct nv50_display { + struct drm_device *dev; + + bool preinit_done; + bool init_done; + + int last_crtc; /* crtc used for last mode set */ + + int (*pre_init) (struct nv50_display *display); + int (*init) (struct nv50_display *display); + int (*disable) (struct nv50_display *display); + int (*update) (struct nv50_display *display); + + struct list_head crtcs; + struct list_head outputs; + struct list_head connectors; +}; + +enum scaling_modes { + SCALE_PANEL, + SCALE_FULLSCREEN, + SCALE_ASPECT, + SCALE_NOSCALE, + SCALE_INVALID +}; + +void nv50_display_command(struct drm_nouveau_private *dev_priv, uint32_t mthd, uint32_t val); +struct nv50_display *nv50_get_display(struct drm_device *dev); +int nv50_display_create(struct drm_device *dev); +int nv50_display_destroy(struct drm_device *dev); + +#endif /* __NV50_DISPLAY_H__ */ diff --git a/linux-core/nv50_display_commands.h b/linux-core/nv50_display_commands.h new file mode 100644 index 00000000..97b3d3c1 --- /dev/null +++ b/linux-core/nv50_display_commands.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* copied from ddx definitions, until rules-ng can handle this */ + +#define NV50_UPDATE_DISPLAY 0x80 +#define NV50_UNK84 0x84 +#define NV50_UNK88 0x88 + +#define NV50_DAC0_MODE_CTRL 0x400 + #define NV50_DAC_MODE_CTRL_OFF (0 << 0) + #define NV50_DAC_MODE_CTRL_CRTC0 (1 << 0) + #define NV50_DAC_MODE_CTRL_CRTC1 (1 << 1) +#define NV50_DAC1_MODE_CTRL 0x480 +#define NV50_DAC2_MODE_CTRL 0x500 + +#define NV50_DAC0_MODE_CTRL2 0x404 + #define NV50_DAC_MODE_CTRL2_NHSYNC (1 << 0) + #define NV50_DAC_MODE_CTRL2_NVSYNC (2 << 0) +#define NV50_DAC1_MODE_CTRL2 0x484 +#define NV50_DAC2_MODE_CTRL2 0x504 + +#define NV50_SOR0_MODE_CTRL 0x600 + #define NV50_SOR_MODE_CTRL_OFF (0 << 0) + #define NV50_SOR_MODE_CTRL_CRTC0 (1 << 0) + #define NV50_SOR_MODE_CTRL_CRTC1 (1 << 1) + #define NV50_SOR_MODE_CTRL_LVDS (0 << 8) + #define NV50_SOR_MODE_CTRL_TMDS (1 << 8) + #define NV50_SOR_MODE_CTRL_TMDS_DUAL_LINK (4 << 8) + #define NV50_SOR_MODE_CTRL_NHSYNC (1 << 12) + #define NV50_SOR_MODE_CTRL_NVSYNC (2 << 12) +#define NV50_SOR1_MODE_CTRL 0x640 + +#define NV50_CRTC0_UNK800 0x800 +#define NV50_CRTC0_CLOCK 0x804 +#define NV50_CRTC0_INTERLACE 0x808 + +/* 0x810 is a reasonable guess, nothing more. */ +#define NV50_CRTC0_DISPLAY_START 0x810 +#define NV50_CRTC0_DISPLAY_TOTAL 0x814 +#define NV50_CRTC0_SYNC_DURATION 0x818 +#define NV50_CRTC0_SYNC_START_TO_BLANK_END 0x81C +#define NV50_CRTC0_MODE_UNK1 0x820 +#define NV50_CRTC0_MODE_UNK2 0x824 + +#define NV50_CRTC0_UNK82C 0x82C + +/* You can't have a palette in 8 bit mode (=OFF) */ +#define NV50_CRTC0_CLUT_MODE 0x840 + #define NV50_CRTC0_CLUT_MODE_BLANK 0x00000000 + #define NV50_CRTC0_CLUT_MODE_OFF 0x80000000 + #define NV50_CRTC0_CLUT_MODE_ON 0xC0000000 +#define NV50_CRTC0_CLUT_OFFSET 0x844 + +/* Anyone know what part of the chip is triggered here precisely? */ +#define NV84_CRTC0_BLANK_UNK1 0x85C + #define NV84_CRTC0_BLANK_UNK1_BLANK 0x0 + #define NV84_CRTC0_BLANK_UNK1_UNBLANK 0x1 + +#define NV50_CRTC0_FB_OFFSET 0x860 + +#define NV50_CRTC0_FB_SIZE 0x868 +#define NV50_CRTC0_FB_PITCH 0x86C + +#define NV50_CRTC0_DEPTH 0x870 + #define NV50_CRTC0_DEPTH_8BPP 0x1E00 + #define NV50_CRTC0_DEPTH_15BPP 0xE900 + #define NV50_CRTC0_DEPTH_16BPP 0xE800 + #define NV50_CRTC0_DEPTH_24BPP 0xCF00 + +/* I'm openminded to better interpretations. */ +/* This is an educated guess. */ +/* NV50 has RAMDAC and TMDS offchip, so it's unlikely to be that. */ +#define NV50_CRTC0_BLANK_CTRL 0x874 + #define NV50_CRTC0_BLANK_CTRL_BLANK 0x0 + #define NV50_CRTC0_BLANK_CTRL_UNBLANK 0x1 + +#define NV50_CRTC0_CURSOR_CTRL 0x880 + #define NV50_CRTC0_CURSOR_CTRL_SHOW 0x85000000 + #define NV50_CRTC0_CURSOR_CTRL_HIDE 0x05000000 + +#define NV50_CRTC0_CURSOR_OFFSET 0x884 + +/* Anyone know what part of the chip is triggered here precisely? */ +#define NV84_CRTC0_BLANK_UNK2 0x89C + #define NV84_CRTC0_BLANK_UNK2_BLANK 0x0 + #define NV84_CRTC0_BLANK_UNK2_UNBLANK 0x1 + +#define NV50_CRTC0_DITHERING_CTRL 0x8A0 + #define NV50_CRTC0_DITHERING_CTRL_ON 0x11 + #define NV50_CRTC0_DITHERING_CTRL_OFF 0x0 + +#define NV50_CRTC0_SCALE_CTRL 0x8A4 + #define NV50_CRTC0_SCALE_CTRL_SCALER_INACTIVE (0 << 0) + /* It doesn't seem to be needed, hence i wonder what it does precisely. */ + #define NV50_CRTC0_SCALE_CTRL_SCALER_ACTIVE (9 << 0) +#define NV50_CRTC0_COLOR_CTRL 0x8A8 + #define NV50_CRTC_COLOR_CTRL_MODE_COLOR (4 << 16) + +#define NV50_CRTC0_FB_POS 0x8C0 +#define NV50_CRTC0_REAL_RES 0x8C8 + +/* Added a macro, because the signed stuff can cause you problems very quickly. */ +#define NV50_CRTC0_SCALE_CENTER_OFFSET 0x8D4 + #define NV50_CRTC_SCALE_CENTER_OFFSET_VAL(x, y) ((((unsigned)y << 16) & 0xFFFF0000) | (((unsigned)x) & 0x0000FFFF)) +/* Both of these are needed, otherwise nothing happens. */ +#define NV50_CRTC0_SCALE_RES1 0x8D8 +#define NV50_CRTC0_SCALE_RES2 0x8DC + +#define NV50_CRTC1_UNK800 0xC00 +#define NV50_CRTC1_CLOCK 0xC04 +#define NV50_CRTC1_INTERLACE 0xC08 + +/* 0xC10 is a reasonable guess, nothing more. */ +#define NV50_CRTC1_DISPLAY_START 0xC10 +#define NV50_CRTC1_DISPLAY_TOTAL 0xC14 +#define NV50_CRTC1_SYNC_DURATION 0xC18 +#define NV50_CRTC1_SYNC_START_TO_BLANK_END 0xC1C +#define NV50_CRTC1_MODE_UNK1 0xC20 +#define NV50_CRTC1_MODE_UNK2 0xC24 + +#define NV50_CRTC1_CLUT_MODE 0xC40 + #define NV50_CRTC1_CLUT_MODE_BLANK 0x00000000 + #define NV50_CRTC1_CLUT_MODE_OFF 0x80000000 + #define NV50_CRTC1_CLUT_MODE_ON 0xC0000000 +#define NV50_CRTC1_CLUT_OFFSET 0xC44 + +/* Anyone know what part of the chip is triggered here precisely? */ +#define NV84_CRTC1_BLANK_UNK1 0xC5C + #define NV84_CRTC1_BLANK_UNK1_BLANK 0x0 + #define NV84_CRTC1_BLANK_UNK1_UNBLANK 0x1 + +#define NV50_CRTC1_FB_OFFSET 0xC60 + +#define NV50_CRTC1_FB_SIZE 0xC68 +#define NV50_CRTC1_FB_PITCH 0xC6C + +#define NV50_CRTC1_DEPTH 0xC70 + #define NV50_CRTC1_DEPTH_8BPP 0x1E00 + #define NV50_CRTC1_DEPTH_15BPP 0xE900 + #define NV50_CRTC1_DEPTH_16BPP 0xE800 + #define NV50_CRTC1_DEPTH_24BPP 0xCF00 + +/* I'm openminded to better interpretations. */ +#define NV50_CRTC1_BLANK_CTRL 0xC74 + #define NV50_CRTC1_BLANK_CTRL_BLANK 0x0 + #define NV50_CRTC1_BLANK_CTRL_UNBLANK 0x1 + +#define NV50_CRTC1_CURSOR_CTRL 0xC80 + #define NV50_CRTC1_CURSOR_CTRL_SHOW 0x85000000 + #define NV50_CRTC1_CURSOR_CTRL_HIDE 0x05000000 + +#define NV50_CRTC1_CURSOR_OFFSET 0xC84 + +/* Anyone know what part of the chip is triggered here precisely? */ +#define NV84_CRTC1_BLANK_UNK2 0xC9C + #define NV84_CRTC1_BLANK_UNK2_BLANK 0x0 + #define NV84_CRTC1_BLANK_UNK2_UNBLANK 0x1 + +#define NV50_CRTC1_DITHERING_CTRL 0xCA0 + #define NV50_CRTC1_DITHERING_CTRL_ON 0x11 + #define NV50_CRTC1_DITHERING_CTRL_OFF 0x0 + +#define NV50_CRTC1_SCALE_CTRL 0xCA4 +#define NV50_CRTC1_COLOR_CTRL 0xCA8 + +#define NV50_CRTC1_FB_POS 0xCC0 +#define NV50_CRTC1_REAL_RES 0xCC8 + +#define NV50_CRTC1_SCALE_CENTER_OFFSET 0xCD4 +/* Both of these are needed, otherwise nothing happens. */ +#define NV50_CRTC1_SCALE_RES1 0xCD8 +#define NV50_CRTC1_SCALE_RES2 0xCDC diff --git a/linux-core/nv50_fb.c b/linux-core/nv50_fb.c new file mode 100644 index 00000000..b44b48ab --- /dev/null +++ b/linux-core/nv50_fb.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_fb.h" +#include "nv50_lut.h" +#include "nv50_crtc.h" +#include "nv50_display.h" + +static int nv50_fb_bind(struct nv50_crtc *crtc, struct nv50_fb_info *info) +{ + int rval = 0; + + NV50_DEBUG("\n"); + + if (!crtc || !info) { + DRM_ERROR("crtc %p info %p\n",crtc, info); + return -EINVAL; + } + + if (!info->block || !info->width || !info->height || !info->depth || !info->bpp) { + DRM_ERROR("block %p width %d height %d depth %d bpp %d\n", info->block, info->width, info->height, info->depth, info->bpp); + return -EINVAL; + } + + crtc->fb->block = info->block; + crtc->fb->width = info->width; + crtc->fb->height = info->height; + + crtc->fb->y = info->x; + crtc->fb->x = info->y; + + crtc->fb->depth = info->depth; + crtc->fb->bpp = info->bpp; + + /* update lut if needed */ + if (crtc->fb->depth != crtc->lut->depth) { + int r_size = 0, g_size = 0, b_size = 0; + uint16_t *r_val, *g_val, *b_val; + int i; + + switch (crtc->fb->depth) { + case 15: + r_size = 32; + g_size = 32; + b_size = 32; + break; + case 16: + r_size = 32; + g_size = 64; + b_size = 32; + break; + case 24: + default: + r_size = 256; + g_size = 256; + b_size = 256; + break; + } + + r_val = kmalloc(r_size * sizeof(uint16_t), GFP_KERNEL); + g_val = kmalloc(g_size * sizeof(uint16_t), GFP_KERNEL); + b_val = kmalloc(b_size * sizeof(uint16_t), GFP_KERNEL); + + if (!r_val || !g_val || !b_val) + return -ENOMEM; + + /* Set the color indices. */ + for (i = 0; i < r_size; i++) { + r_val[i] = i << 8; + } + for (i = 0; i < g_size; i++) { + g_val[i] = i << 8; + } + for (i = 0; i < b_size; i++) { + b_val[i] = i << 8; + } + + rval = crtc->lut->set(crtc, r_val, g_val, b_val); + + /* free before returning */ + kfree(r_val); + kfree(g_val); + kfree(b_val); + + if (rval != 0) + return rval; + } + + return 0; +} + +int nv50_fb_create(struct nv50_crtc *crtc) +{ + if (!crtc) + return -EINVAL; + + crtc->fb = kzalloc(sizeof(struct nv50_fb), GFP_KERNEL); + + crtc->fb->bind = nv50_fb_bind; + + return 0; +} + +int nv50_fb_destroy(struct nv50_crtc *crtc) +{ + if (!crtc) + return -EINVAL; + + kfree(crtc->fb); + crtc->fb = NULL; + + return 0; +} diff --git a/linux-core/nv50_fb.h b/linux-core/nv50_fb.h new file mode 100644 index 00000000..6b286315 --- /dev/null +++ b/linux-core/nv50_fb.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_FB_H__ +#define __NV50_FB_H__ + +#include "nv50_display.h" + +struct nv50_crtc; + +struct nv50_fb_info { + struct mem_block *block; + int width, height; + int bpp, depth; + int x,y; +}; + +struct nv50_fb { + struct mem_block *block; + int width, height; + int bpp, depth; + + int x,y; + + /* function points */ + int (*bind) (struct nv50_crtc *crtc, struct nv50_fb_info *info); +}; + +int nv50_fb_create(struct nv50_crtc *crtc); +int nv50_fb_destroy(struct nv50_crtc *crtc); + +#endif /* __NV50_FB_H__ */ diff --git a/linux-core/nv50_i2c.c b/linux-core/nv50_i2c.c new file mode 100644 index 00000000..cf55645b --- /dev/null +++ b/linux-core/nv50_i2c.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* This is largely a clone from xorg i2c functions, as i had serious trouble getting an i2c_bit_algo adaptor running. */ + +#include "nv50_i2c.h" + +static void nv50_i2c_set_bits(struct nv50_i2c_channel *chan, int clock_high, int data_high) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + + NV_WRITE(NV50_PCONNECTOR_I2C_PORT(chan->index), 4 | (data_high << 1) | clock_high); +} + +static void nv50_i2c_get_bits(struct nv50_i2c_channel *chan, int *clock_high, int *data_high) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + + uint32_t val = NV_READ(NV50_PCONNECTOR_I2C_PORT(chan->index)); + + if (val & 1) + *clock_high = 1; + else + *clock_high = 0; + + if (val & 2) + *data_high = 1; + else + *data_high = 0; +} + +static bool nv50_i2c_raise_clock(struct nv50_i2c_channel *chan, int data) +{ + int i, clock; + + nv50_i2c_set_bits(chan, 1, data); + udelay(2); + + for (i = 2200; i > 0; i -= 2) { + nv50_i2c_get_bits(chan, &clock, &data); + if (clock) + return TRUE; + udelay(2); + } + + printk("a timeout occured in nv50_i2c_raise_clock\n"); + + return FALSE; +} + +static bool nv50_i2c_start(struct nv50_i2c_channel *chan) +{ + if (!nv50_i2c_raise_clock(chan, 1)) + return FALSE; + + nv50_i2c_set_bits(chan, 1, 0); + udelay(5); + + nv50_i2c_set_bits(chan, 0, 0); + udelay(5); + + return TRUE; +} + +static void nv50_i2c_stop(struct nv50_i2c_channel *chan) +{ + nv50_i2c_set_bits(chan, 0, 0); + udelay(2); + + nv50_i2c_set_bits(chan, 1, 0); + udelay(5); + + nv50_i2c_set_bits(chan, 1, 1); + udelay(5); +} + +static bool nv50_i2c_write_bit(struct nv50_i2c_channel *chan, int data) +{ + bool rval; + + nv50_i2c_set_bits(chan, 0, data); + udelay(2); + + rval = nv50_i2c_raise_clock(chan, data); + udelay(5); + + nv50_i2c_set_bits(chan, 0, data); + udelay(5); + + return rval; +} + +static bool nv50_i2c_read_bit(struct nv50_i2c_channel *chan, int *data) +{ + bool rval; + int clock; + + rval = nv50_i2c_raise_clock(chan, 1); + udelay(5); + + nv50_i2c_get_bits(chan, &clock, data); + udelay(5); + + nv50_i2c_set_bits(chan, 0, 1); + udelay(5); + + return rval; +} + +static bool nv50_i2c_write_byte(struct nv50_i2c_channel *chan, uint8_t byte) +{ + bool rval; + int i, clock, data; + + for (i = 7; i >= 0; i--) + if (!nv50_i2c_write_bit(chan, (byte >> i) & 1)) + return FALSE; + + nv50_i2c_set_bits(chan, 0, 1); + udelay(5); + + rval = nv50_i2c_raise_clock(chan, 1); + + if (rval) { + for (i = 40; i > 0; i -= 2) { + udelay(2); + nv50_i2c_get_bits(chan, &clock, &data); + if (data == 0) + break; + } + + if (i <= 0) { + printk("a timeout occured in nv50_i2c_write_byte\n"); + rval = FALSE; + } + } + + nv50_i2c_set_bits(chan, 0, 1); + udelay(5); + + return rval; +} + +static bool nv50_i2c_read_byte(struct nv50_i2c_channel *chan, uint8_t *byte, bool last) +{ + int i, bit; + + nv50_i2c_set_bits(chan, 0, 1); + udelay(5); + + *byte = 0; + + for (i = 7; i >= 0; i--) { + if (nv50_i2c_read_bit(chan, &bit)) { + if (bit) + *byte |= (1 << i); + } else { + return FALSE; + } + } + + if (!nv50_i2c_write_bit(chan, last ? 1 : 0)) + return FALSE; + + return TRUE; +} + +/* only 7 bits addresses. */ +static bool nv50_i2c_address(struct nv50_i2c_channel *chan, uint8_t address, bool write) +{ + if (nv50_i2c_start(chan)) { + uint8_t real_addr = (address << 1); + if (!write) + real_addr |= 1; + + if (nv50_i2c_write_byte(chan, real_addr)) + return TRUE; + + /* failure, so issue stop */ + nv50_i2c_stop(chan); + } + + return FALSE; +} + +static bool nv50_i2c_read(struct nv50_i2c_channel *chan, uint8_t address, uint8_t *buffer, uint32_t length) +{ + int i, j; + bool rval, last; + + /* retries */ + for (i = 0; i < 4; i++) { + rval = nv50_i2c_address(chan, address, FALSE); + if (!rval) + return FALSE; + + for (j = 0; j < length; j++) { + last = false; + if (j == (length - 1)) + last = true; + rval = nv50_i2c_read_byte(chan, &buffer[j], last); + if (!rval) { + nv50_i2c_stop(chan); + break; + } + } + + nv50_i2c_stop(chan); + + /* done */ + if (rval) + break; + } + + if (!rval) + printk("nv50_i2c_read failed\n"); + + return rval; +} + +static bool nv50_i2c_write(struct nv50_i2c_channel *chan, uint8_t address, uint8_t *buffer, uint32_t length) +{ + int i, j; + bool rval; + + /* retries */ + for (i = 0; i < 4; i++) { + rval = nv50_i2c_address(chan, address, TRUE); + if (!rval) + return FALSE; + + for (j = 0; j < length; j++) { + rval = nv50_i2c_write_byte(chan, buffer[j]); + if (!rval) { + break; + } + } + + nv50_i2c_stop(chan); + + /* done */ + if (rval) + break; + } + + if (!rval) + printk("nv50_i2c_write failed\n"); + + return rval; +} + +static int nv50_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct nv50_i2c_channel *chan = i2c_get_adapdata(i2c_adap); + bool rval; + int i; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { /* read */ + rval = nv50_i2c_read(chan, msgs[i].addr, msgs[i].buf, msgs[i].len); + } else { /* write */ + rval = nv50_i2c_write(chan, msgs[i].addr, msgs[i].buf, msgs[i].len); + } + + if (!rval) + break; + } + + if (rval) + return i; + else + return -EINVAL; +} + +static u32 nv50_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm nv50_i2c_algo = { + .master_xfer = nv50_i2c_xfer, + .functionality = nv50_i2c_functionality, +}; + +static int nv50_i2c_register_bus(struct i2c_adapter *adap) +{ + adap->algo = &nv50_i2c_algo; + + adap->timeout = 40; + adap->retries = 4; + + return i2c_add_adapter(adap); +} + +#define I2C_HW_B_NOUVEAU 0x010030 +struct nv50_i2c_channel *nv50_i2c_channel_create(struct drm_device *dev, uint32_t index) +{ + struct nv50_i2c_channel *chan; + + chan = kzalloc(sizeof(struct nv50_i2c_channel), GFP_KERNEL); + + if (!chan) + goto out; + + DRM_INFO("Creating i2c bus with index %d\n", index); + + chan->dev = dev; + chan->index = index; + snprintf(chan->adapter.name, I2C_NAME_SIZE, "nv50 i2c %d", index); + chan->adapter.owner = THIS_MODULE; + chan->adapter.id = I2C_HW_B_NOUVEAU; + chan->adapter.dev.parent = &dev->pdev->dev; + + i2c_set_adapdata(&chan->adapter, chan); + + if (nv50_i2c_register_bus(&chan->adapter)) + goto out; + + return chan; + +out: + kfree(chan); + return NULL; +} + +void nv50_i2c_channel_destroy(struct nv50_i2c_channel *chan) +{ + if (!chan) + return; + + i2c_del_adapter(&chan->adapter); + kfree(chan); +} diff --git a/linux-core/nv50_i2c.h b/linux-core/nv50_i2c.h new file mode 100644 index 00000000..1740f8ec --- /dev/null +++ b/linux-core/nv50_i2c.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_I2C_H__ +#define __NV50_I2C_H__ + +#include <linux/i2c.h> +#include <linux/i2c-id.h> +#include <linux/i2c-algo-bit.h> +#include "drmP.h" +#include "drm.h" +#include "nv50_display.h" + +struct nv50_i2c_channel { + struct drm_device *dev; + + uint32_t index; + struct i2c_adapter adapter; +}; + +struct nv50_i2c_channel *nv50_i2c_channel_create(struct drm_device *dev, uint32_t index); +void nv50_i2c_channel_destroy(struct nv50_i2c_channel *chan); + +#endif /* __NV50_I2C_H__ */ diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c new file mode 100644 index 00000000..01d4bc9a --- /dev/null +++ b/linux-core/nv50_kms_wrapper.c @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_kms_wrapper.h" +#include "drm_crtc_helper.h" /* be careful what you use from this */ + +/* This file serves as the interface between the common kernel modesetting code and the device dependent implementation. */ + +/* + * Get private functions. + */ + +struct nv50_kms_priv *nv50_get_kms_priv(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return dev_priv->kms_priv; +} + +/* + * Allocation functions. + */ + +static void *nv50_kms_alloc_crtc(struct drm_device *dev) +{ + struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev); + struct nv50_kms_crtc *crtc = kzalloc(sizeof(struct nv50_kms_crtc), GFP_KERNEL); + + list_add_tail(&crtc->head, &kms_priv->crtcs); + + return &(crtc->priv); +} + +static void *nv50_kms_alloc_output(struct drm_device *dev) +{ + struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev); + struct nv50_kms_encoder *encoder = kzalloc(sizeof(struct nv50_kms_encoder), GFP_KERNEL); + + list_add_tail(&encoder->head, &kms_priv->encoders); + + return &(encoder->priv); +} + +static void *nv50_kms_alloc_connector(struct drm_device *dev) +{ + struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev); + struct nv50_kms_connector *connector = kzalloc(sizeof(struct nv50_kms_connector), GFP_KERNEL); + + list_add_tail(&connector->head, &kms_priv->connectors); + + return &(connector->priv); +} + +static void nv50_kms_free_crtc(void *crtc) +{ + struct nv50_kms_crtc *kms_crtc = from_nv50_crtc(crtc); + + list_del(&kms_crtc->head); + + kfree(kms_crtc); +} + +static void nv50_kms_free_output(void *output) +{ + struct nv50_kms_encoder *kms_encoder = from_nv50_output(output); + + list_del(&kms_encoder->head); + + kfree(kms_encoder); +} + +static void nv50_kms_free_connector(void *connector) +{ + struct nv50_kms_connector *kms_connector = from_nv50_connector(connector); + + list_del(&kms_connector->head); + + kfree(kms_connector); +} + +/* + * Mode conversion functions. + */ + +static struct nouveau_hw_mode *nv50_kms_to_hw_mode(struct drm_display_mode *mode) +{ + struct nouveau_hw_mode *hw_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + + /* create hw values. */ + hw_mode->clock = mode->clock; + hw_mode->flags = hw_mode->flags; + + hw_mode->hdisplay = mode->hdisplay; + hw_mode->hsync_start = mode->hsync_start; + hw_mode->hsync_end = mode->hsync_end; + hw_mode->htotal = mode->htotal; + + hw_mode->hblank_start = mode->hdisplay + 1; + hw_mode->hblank_end = mode->htotal; + + hw_mode->vdisplay = mode->vdisplay; + hw_mode->vsync_start = mode->vsync_start; + hw_mode->vsync_end = mode->vsync_end; + hw_mode->vtotal = mode->vtotal; + + hw_mode->vblank_start = mode->vdisplay + 1; + hw_mode->vblank_end = mode->vtotal; + + return hw_mode; +} + +/* + * State mirroring functions. + */ + +static void nv50_kms_mirror_routing(struct drm_device *dev) +{ + struct nv50_display *display = nv50_get_display(dev); + struct nv50_crtc *crtc = NULL; + struct nv50_output *output = NULL; + struct nv50_connector *connector = NULL; + struct drm_connector *drm_connector = NULL; + + /* Wipe all previous connections. */ + list_for_each_entry(connector, &display->connectors, head) { + connector->output = NULL; + } + + list_for_each_entry(output, &display->outputs, head) { + output->crtc = NULL; + } + + list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) { + if (drm_connector->encoder) { + output = to_nv50_output(drm_connector->encoder); + connector = to_nv50_connector(drm_connector); + + /* hook up output to connector. */ + connector->output = output; + + if (drm_connector->encoder->crtc) { + crtc = to_nv50_crtc(drm_connector->encoder->crtc); + + /* hook up output to crtc. */ + output->crtc = crtc; + } + } + } +} + +/* + * FB functions. + */ + +static void nv50_kms_framebuffer_destroy(struct drm_framebuffer *drm_framebuffer) +{ + drm_framebuffer_cleanup(drm_framebuffer); + + kfree(drm_framebuffer); +} + +static const struct drm_framebuffer_funcs nv50_kms_fb_funcs = { + .destroy = nv50_kms_framebuffer_destroy, +}; + +/* + * Mode config functions. + */ + +static struct drm_framebuffer *nv50_kms_framebuffer_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd *mode_cmd) +{ + struct drm_framebuffer *drm_framebuffer = kzalloc(sizeof(struct drm_framebuffer), GFP_KERNEL); + if (!drm_framebuffer) + return NULL; + + drm_framebuffer_init(dev, drm_framebuffer, &nv50_kms_fb_funcs); + drm_helper_mode_fill_fb_struct(drm_framebuffer, mode_cmd); + + return drm_framebuffer; +} + +static int nv50_kms_fb_changed(struct drm_device *dev) +{ + return 0; /* not needed until nouveaufb? */ +} + +static const struct drm_mode_config_funcs nv50_kms_mode_funcs = { + .resize_fb = NULL, + .fb_create = nv50_kms_framebuffer_create, + .fb_changed = nv50_kms_fb_changed, +}; + +/* + * CRTC functions. + */ + +static int nv50_kms_crtc_cursor_set(struct drm_crtc *drm_crtc, uint32_t buffer_handle, + uint32_t width, uint32_t height) +{ + struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc); + struct nv50_display *display = nv50_get_display(crtc->dev); + int rval; + + if (width != 64 || height != 64) + return -EINVAL; + + rval = crtc->cursor->set_bo(crtc, (drm_handle_t) buffer_handle); + + if (rval != 0) + return rval; + + /* in case this triggers any other cursor changes */ + display->update(display); + + return 0; +} + +static int nv50_kms_crtc_cursor_move(struct drm_crtc *drm_crtc, int x, int y) +{ + struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc); + + return crtc->cursor->set_pos(crtc, x, y); +} + +void nv50_kms_crtc_gamma_set(struct drm_crtc *drm_crtc, u16 *r, u16 *g, u16 *b, + uint32_t size) +{ + struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc); + + if (size != 256) + return; + + crtc->lut->set(crtc, (uint16_t *)r, (uint16_t *)g, (uint16_t *)b); +} + +int nv50_kms_crtc_set_config(struct drm_mode_set *set) +{ + int rval = 0, i; + uint32_t crtc_mask = 0; + struct drm_device *dev = NULL; + struct drm_nouveau_private *dev_priv = NULL; + struct nv50_display *display = NULL; + struct drm_connector *drm_connector = NULL; + struct drm_encoder *drm_encoder = NULL; + struct drm_crtc *drm_crtc = NULL; + + struct nv50_crtc *crtc = NULL; + struct nv50_output *output = NULL; + struct nv50_connector *connector = NULL; + struct nouveau_hw_mode *hw_mode = NULL; + struct nv50_fb_info fb_info; + + NV50_DEBUG("\n"); + + /* + * Initial approach is very simple, always set a mode. + * Always bail out completely if something is wrong. + * Later this could be extended to be more smart. + */ + + /* Sanity checking */ + if (!set) { + NV50_DEBUG("Sanity check failed\n"); + goto out; + } + + if (!set->crtc || !set->fb || !set->mode || !set->connectors) { + NV50_DEBUG("Sanity check failed\n"); + goto out; + } + + /* Basic variable setting */ + dev = set->crtc->dev; + dev_priv = dev->dev_private; + display = nv50_get_display(dev); + crtc = to_nv50_crtc(set->crtc); + + /* Mode validation */ + hw_mode = nv50_kms_to_hw_mode(set->mode); + + rval = crtc->validate_mode(crtc, hw_mode); + + if (rval != MODE_OK) { + NV50_DEBUG("Mode not ok\n"); + goto out; + } + + for (i = 0; i < set->num_connectors; i++) { + drm_connector = set->connectors[i]; + if (!drm_connector) { + NV50_DEBUG("No connector\n"); + goto out; + } + connector = to_nv50_connector(drm_connector); + + output = connector->to_output(connector, connector->digital); + if (!output) { + NV50_DEBUG("No output\n"); + goto out; + } + + rval = output->validate_mode(output, hw_mode); + if (rval != MODE_OK) { + NV50_DEBUG("Mode not ok\n"); + goto out; + } + } + + /* Validation done, move on to cleaning of existing structures. */ + + /* find encoders that use this crtc. */ + list_for_each_entry(drm_encoder, &dev->mode_config.encoder_list, head) { + if (drm_encoder->crtc == set->crtc) { + /* find the connector that goes with it */ + list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) { + if (drm_connector->encoder == drm_encoder) { + drm_connector->encoder = NULL; + break; + } + } + drm_encoder->crtc = NULL; + } + } + + /* now find if our desired encoders or connectors are in use already. */ + for (i = 0; i < set->num_connectors; i++) { + drm_connector = set->connectors[i]; + if (!drm_connector) { + NV50_DEBUG("No connector\n"); + goto out; + } + + if (!drm_connector->encoder) + continue; + + drm_encoder = drm_connector->encoder; + drm_connector->encoder = NULL; + + if (!drm_encoder->crtc) + continue; + + drm_crtc = drm_encoder->crtc; + drm_encoder->crtc = NULL; + + crtc = to_nv50_crtc(drm_crtc); + crtc->active = false; + drm_crtc->enabled = false; + } + + /* set framebuffer */ + set->crtc->fb = set->fb; + + /* Time to wire up the public encoder, the private one will be handled later. */ + for (i = 0; i < set->num_connectors; i++) { + drm_connector = set->connectors[i]; + if (!drm_connector) { + NV50_DEBUG("No connector\n"); + goto out; + } + + output = connector->to_output(connector, connector->digital); + if (!output) { + NV50_DEBUG("No output\n"); + goto out; + } + + /* find the encoder public structure that matches out output structure. */ + drm_encoder = to_nv50_kms_encoder(output); + + if (!drm_encoder) { + NV50_DEBUG("No encoder\n"); + goto out; + } + + + drm_encoder->crtc = set->crtc; + drm_connector->encoder = drm_encoder; + } + + /* mirror everything to the private structs */ + nv50_kms_mirror_routing(dev); + + /* set private framebuffer */ + crtc = to_nv50_crtc(set->crtc); + fb_info.block = find_block_by_handle(dev_priv->fb_heap, set->fb->mm_handle); + fb_info.width = set->fb->width; + fb_info.height = set->fb->height; + fb_info.depth = set->fb->depth; + fb_info.bpp = set->fb->bits_per_pixel; + fb_info.x = set->x; + fb_info.y = set->y; + + rval = crtc->fb->bind(crtc, &fb_info); + if (rval != 0) { + NV50_DEBUG("fb_bind failed\n"); + goto out; + } + + if (!crtc->cursor->enabled) { + rval = crtc->cursor->enable(crtc); + if (rval != 0) { + NV50_DEBUG("cursor_enable failed\n"); + goto out; + } + } + + /* modeset time, finally */ + + /* disconnect unused outputs */ + list_for_each_entry(output, &display->outputs, head) { + if (output->crtc) + crtc_mask |= 1 << output->crtc->index; + else + output->execute_mode(output, TRUE); + } + + rval = crtc->set_mode(crtc, hw_mode); + if (rval != 0) { + NV50_DEBUG("crtc mode set failed\n"); + goto out; + } + + /* find native mode. */ + list_for_each_entry(output, &display->outputs, head) { + if (output->crtc != crtc) + continue; + + *crtc->native_mode = *output->native_mode; + list_for_each_entry(connector, &display->connectors, head) { + if (connector->output != output) + continue; + + crtc->scaling_mode = connector->scaling_mode; + break; + } + + if (crtc->scaling_mode == SCALE_PANEL) + crtc->use_native_mode = false; + else + crtc->use_native_mode = true; + + break; /* no use in finding more than one mode */ + } + + rval = crtc->execute_mode(crtc); + if (rval != 0) { + NV50_DEBUG("crtc execute mode failed\n"); + goto out; + } + + list_for_each_entry(output, &display->outputs, head) { + if (output->crtc != crtc) + continue; + + rval = output->execute_mode(output, FALSE); + if (rval != 0) { + NV50_DEBUG("output execute mode failed\n"); + goto out; + } + } + + rval = crtc->set_scale(crtc); + if (rval != 0) { + NV50_DEBUG("crtc set scale failed\n"); + goto out; + } + + /* next line changes crtc, so putting it here is important */ + display->last_crtc = crtc->index; + + /* blank any unused crtcs */ + list_for_each_entry(crtc, &display->crtcs, head) { + if (!(crtc_mask & (1 << crtc->index))) + crtc->blank(crtc, TRUE); + } + + display->update(display); + + kfree(hw_mode); + + return 0; + +out: + display->update(display); + + kfree(hw_mode); + + if (rval != 0) + return rval; + else + return -EINVAL; +} + +static void nv50_kms_crtc_destroy(struct drm_crtc *drm_crtc) +{ + struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc); + + drm_crtc_cleanup(drm_crtc); + + /* this will even destroy the public structure. */ + crtc->destroy(crtc); +} + +static const struct drm_crtc_funcs nv50_kms_crtc_funcs = { + .save = NULL, + .restore = NULL, + .cursor_set = nv50_kms_crtc_cursor_set, + .cursor_move = nv50_kms_crtc_cursor_move, + .gamma_set = nv50_kms_crtc_gamma_set, + .set_config = nv50_kms_crtc_set_config, + .destroy = nv50_kms_crtc_destroy, +}; + +static int nv50_kms_crtcs_init(struct drm_device *dev) +{ + struct nv50_display *display = nv50_get_display(dev); + struct nv50_crtc *crtc = NULL; + + /* + * This may look a bit confusing, but: + * The internal structure is already allocated and so is the public one. + * Just a matter of getting to the memory and register it. + */ + list_for_each_entry(crtc, &display->crtcs, head) { + struct drm_crtc *drm_crtc = to_nv50_kms_crtc(crtc); + + drm_crtc_init(dev, drm_crtc, &nv50_kms_crtc_funcs); + } + + return 0; +} + +/* + * Encoder functions + */ + +static void nv50_kms_encoder_destroy(struct drm_encoder *drm_encoder) +{ + struct nv50_output *output = to_nv50_output(drm_encoder); + + drm_encoder_cleanup(drm_encoder); + + /* this will even destroy the public structure. */ + output->destroy(output); +} + +static const struct drm_encoder_funcs nv50_kms_encoder_funcs = { + .destroy = nv50_kms_encoder_destroy, +}; + +static int nv50_kms_encoders_init(struct drm_device *dev) +{ + struct nv50_display *display = nv50_get_display(dev); + struct nv50_output *output = NULL; + + list_for_each_entry(output, &display->outputs, head) { + struct drm_encoder *drm_encoder = to_nv50_kms_encoder(output); + uint32_t type = DRM_MODE_ENCODER_NONE; + + switch (output->type) { + case OUTPUT_DAC: + type = DRM_MODE_ENCODER_DAC; + break; + case OUTPUT_TMDS: + type = DRM_MODE_ENCODER_TMDS; + break; + case OUTPUT_LVDS: + type = DRM_MODE_ENCODER_LVDS; + break; + case OUTPUT_TV: + type = DRM_MODE_ENCODER_TVDAC; + break; + default: + type = DRM_MODE_ENCODER_NONE; + break; + } + + if (type == DRM_MODE_ENCODER_NONE) { + DRM_ERROR("DRM_MODE_ENCODER_NONE encountered\n"); + continue; + } + + drm_encoder_init(dev, drm_encoder, &nv50_kms_encoder_funcs, type); + + /* I've never seen possible crtc's restricted. */ + drm_encoder->possible_crtcs = 3; + drm_encoder->possible_clones = 0; + } + + return 0; +} + +/* + * Connector functions + */ + +void nv50_kms_connector_detect_all(struct drm_device *dev) +{ + struct drm_connector *drm_connector = NULL; + enum drm_connector_status old, new; + bool notify = false; + + list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) { + old = drm_connector->status; + new = drm_connector->funcs->detect(drm_connector); + + if (new != old) { + notify = true; + drm_connector->funcs->fill_modes(drm_connector, 0, 0); + } + } + + /* I think this is the hook that notifies of changes. */ + if (notify) + dev->mode_config.funcs->fb_changed(dev); +} + +static enum drm_connector_status nv50_kms_connector_detect(struct drm_connector *drm_connector) +{ + struct nv50_connector *connector = to_nv50_connector(drm_connector); + bool connected; + + connected = connector->detect(connector); + + if (connected) + drm_connector->status = connector_status_connected; + else + drm_connector->status = connector_status_disconnected; + + return drm_connector->status; +} + +static void nv50_kms_connector_destroy(struct drm_connector *drm_connector) +{ + struct nv50_connector *connector = to_nv50_connector(drm_connector); + + drm_sysfs_connector_remove(drm_connector); + drm_connector_cleanup(drm_connector); + + /* this will even destroy the public structure. */ + connector->destroy(connector); +} + +/* + * Detailed mode info for a standard 640x480@60Hz monitor + */ +static struct drm_display_mode std_mode[] = { + /*{ DRM_MODE("640x480", DRM_MODE_TYPE_DEFAULT, 25200, 640, 656, + 752, 800, 0, 480, 490, 492, 525, 0, + V_NHSYNC | V_NVSYNC) },*/ /* 640x480@60Hz */ + { DRM_MODE("1280x1024", DRM_MODE_TYPE_DEFAULT, 135000, 1280, 1296, + 1440, 1688, 0, 1024, 1025, 1028, 1066, 0, + V_PHSYNC | V_PVSYNC) }, /* 1280x1024@75Hz */ +}; + +static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, uint32_t maxX, uint32_t maxY) +{ + struct nv50_connector *connector = to_nv50_connector(drm_connector); + int ret = 0; + bool connected; + struct drm_display_mode *mode, *t; + struct edid *edid = NULL; + + DRM_DEBUG("%s\n", drm_get_connector_name(drm_connector)); + /* set all modes to the unverified state */ + list_for_each_entry_safe(mode, t, &drm_connector->modes, head) + mode->status = MODE_UNVERIFIED; + + connected = connector->detect(connector); + + if (connected) + drm_connector->status = connector_status_connected; + else + drm_connector->status = connector_status_disconnected; + + if (!connected) { + DRM_DEBUG("%s is disconnected\n", drm_get_connector_name(drm_connector)); + /* TODO set EDID to NULL */ + return; + } + + /* Not all connnectors have an i2c channel. */ + if (connector->i2c_chan) + edid = (struct edid *) drm_do_probe_ddc_edid(&connector->i2c_chan->adapter); + + if (edid) { + drm_mode_connector_update_edid_property(drm_connector, edid); + ret = drm_add_edid_modes(drm_connector, edid); + connector->digital = edid->digital; /* cache */ + } + + if (ret) /* number of modes > 1 */ + drm_mode_connector_list_update(drm_connector); + + if (maxX && maxY) + drm_mode_validate_size(drm_connector->dev, &drm_connector->modes, maxX, maxY, 0); + + list_for_each_entry_safe(mode, t, &drm_connector->modes, head) { + if (mode->status == MODE_OK) { + struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode); + struct nv50_output *output = connector->to_output(connector, connector->digital); + + mode->status = output->validate_mode(output, hw_mode); + /* find native mode, TODO: also check if we actually found one */ + if (mode->status == MODE_OK) { + if (mode->type & DRM_MODE_TYPE_PREFERRED) + *output->native_mode = *hw_mode; + } + kfree(hw_mode); + } + } + + /* revalidate now that we have native mode */ + list_for_each_entry_safe(mode, t, &drm_connector->modes, head) { + if (mode->status == MODE_OK) { + struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode); + struct nv50_output *output = connector->to_output(connector, connector->digital); + + mode->status = output->validate_mode(output, hw_mode); + kfree(hw_mode); + } + } + + drm_mode_prune_invalid(drm_connector->dev, &drm_connector->modes, TRUE); + + if (list_empty(&drm_connector->modes)) { + struct drm_display_mode *stdmode; + struct nouveau_hw_mode *hw_mode; + struct nv50_output *output; + + DRM_DEBUG("No valid modes on %s\n", drm_get_connector_name(drm_connector)); + + /* Should we do this here ??? + * When no valid EDID modes are available we end up + * here and bailed in the past, now we add a standard + * 640x480@60Hz mode and carry on. + */ + stdmode = drm_mode_duplicate(drm_connector->dev, &std_mode[0]); + drm_mode_probed_add(drm_connector, stdmode); + drm_mode_list_concat(&drm_connector->probed_modes, + &drm_connector->modes); + + /* also add it as native mode */ + hw_mode = nv50_kms_to_hw_mode(mode); + output = connector->to_output(connector, connector->digital); + + if (hw_mode) + *output->native_mode = *hw_mode; + + DRM_DEBUG("Adding standard 640x480 @ 60Hz to %s\n", + drm_get_connector_name(drm_connector)); + } + + drm_mode_sort(&drm_connector->modes); + + DRM_DEBUG("Probed modes for %s\n", drm_get_connector_name(drm_connector)); + + list_for_each_entry_safe(mode, t, &drm_connector->modes, head) { + mode->vrefresh = drm_mode_vrefresh(mode); + + /* is this needed, as it's unused by the driver? */ + drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); + drm_mode_debug_printmodeline(mode); + } +} + +static const struct drm_connector_funcs nv50_kms_connector_funcs = { + .save = NULL, + .restore = NULL, + .detect = nv50_kms_connector_detect, + .destroy = nv50_kms_connector_destroy, + .fill_modes = nv50_kms_connector_fill_modes, +}; + +static int nv50_kms_connectors_init(struct drm_device *dev) +{ + struct nv50_display *display = nv50_get_display(dev); + struct nv50_connector *connector = NULL; + int i; + + list_for_each_entry(connector, &display->connectors, head) { + struct drm_connector *drm_connector = to_nv50_kms_connector(connector); + uint32_t type = DRM_MODE_CONNECTOR_Unknown; + + switch (connector->type) { + case CONNECTOR_VGA: + type = DRM_MODE_CONNECTOR_VGA; + break; + case CONNECTOR_DVI_D: + type = DRM_MODE_CONNECTOR_DVID; + break; + case CONNECTOR_DVI_I: + type = DRM_MODE_CONNECTOR_DVII; + break; + case CONNECTOR_LVDS: + type = DRM_MODE_CONNECTOR_LVDS; + break; + case CONNECTOR_TV: + type = DRM_MODE_CONNECTOR_SVIDEO; + break; + default: + type = DRM_MODE_CONNECTOR_Unknown; + break; + } + + if (type == DRM_MODE_CONNECTOR_Unknown) { + DRM_ERROR("DRM_MODE_CONNECTOR_Unknown encountered\n"); + continue; + } + + /* It should be allowed sometimes, but let's be safe for the moment. */ + drm_connector->interlace_allowed = false; + drm_connector->doublescan_allowed = false; + + drm_connector_init(dev, drm_connector, &nv50_kms_connector_funcs, type); + + /* attach encoders, possibilities are analog + digital */ + for (i = 0; i < 2; i++) { + struct drm_encoder *drm_encoder = NULL; + struct nv50_output *output = connector->to_output(connector, i); + if (!output) + continue; + + drm_encoder = to_nv50_kms_encoder(output); + if (!drm_encoder) { + DRM_ERROR("No struct drm_connector to match struct nv50_output\n"); + continue; + } + + drm_mode_connector_attach_encoder(drm_connector, drm_encoder); + } + + drm_sysfs_connector_add(drm_connector); + } + + return 0; +} + +/* + * Main functions + */ + +int nv50_kms_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_kms_priv *kms_priv = kzalloc(sizeof(struct nv50_kms_priv), GFP_KERNEL); + struct nv50_display *display = NULL; + int rval = 0; + + dev_priv->kms_priv = kms_priv; + + /* function pointers */ + /* an allocation interface that deals with the outside world, without polluting the core. */ + dev_priv->alloc_crtc = nv50_kms_alloc_crtc; + dev_priv->alloc_output = nv50_kms_alloc_output; + dev_priv->alloc_connector = nv50_kms_alloc_connector; + + dev_priv->free_crtc = nv50_kms_free_crtc; + dev_priv->free_output = nv50_kms_free_output; + dev_priv->free_connector = nv50_kms_free_connector; + + /* bios is needed for tables. */ + rval = nouveau_parse_bios(dev); + if (rval != 0) + goto out; + + /* init basic kernel modesetting */ + drm_mode_config_init(dev); + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + + dev->mode_config.funcs = (void *)&nv50_kms_mode_funcs; + + dev->mode_config.max_width = 8192; + dev->mode_config.max_height = 8192; + + dev->mode_config.fb_base = dev_priv->fb_phys; + + /* init kms lists */ + INIT_LIST_HEAD(&kms_priv->crtcs); + INIT_LIST_HEAD(&kms_priv->encoders); + INIT_LIST_HEAD(&kms_priv->connectors); + + /* init the internal core, must be done first. */ + rval = nv50_display_create(dev); + if (rval != 0) + goto out; + + display = nv50_get_display(dev); + if (!display) { + rval = -EINVAL; + goto out; + } + + /* pre-init now */ + rval = display->pre_init(display); + if (rval != 0) + goto out; + + /* init external layer */ + rval = nv50_kms_crtcs_init(dev); + if (rval != 0) + goto out; + + rval = nv50_kms_encoders_init(dev); + if (rval != 0) + goto out; + + rval = nv50_kms_connectors_init(dev); + if (rval != 0) + goto out; + + /* init now, this'll kill the textmode */ + rval = display->init(display); + if (rval != 0) + goto out; + + /* process cmdbuffer */ + display->update(display); + + return 0; + +out: + kfree(kms_priv); + dev_priv->kms_priv = NULL; + + return rval; +} + +int nv50_kms_destroy(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); + + return 0; +} + diff --git a/linux-core/nv50_kms_wrapper.h b/linux-core/nv50_kms_wrapper.h new file mode 100644 index 00000000..3847d510 --- /dev/null +++ b/linux-core/nv50_kms_wrapper.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_KMS_WRAPPER_H__ +#define __NV50_KMS_WRAPPER_H__ + +#include "drmP.h" +#include "drm.h" + +#include "nv50_display.h" +#include "nv50_crtc.h" +#include "nv50_cursor.h" +#include "nv50_lut.h" +#include "nv50_fb.h" +#include "nv50_output.h" +#include "nv50_connector.h" + +#include "drm_crtc.h" +#include "drm_edid.h" + +/* Link internal modesetting structure to interface. */ + +struct nv50_kms_crtc { + struct list_head head; + + struct nv50_crtc priv; + struct drm_crtc pub; +}; + +struct nv50_kms_encoder { + struct list_head head; + + struct nv50_output priv; + struct drm_encoder pub; +}; + +struct nv50_kms_connector { + struct list_head head; + + struct nv50_connector priv; + struct drm_connector pub; +}; + +struct nv50_kms_priv { + struct list_head crtcs; + struct list_head encoders; + struct list_head connectors; +}; + +/* Get private functions. */ +#define from_nv50_kms_crtc(x) container_of(x, struct nv50_kms_crtc, pub) +#define from_nv50_crtc(x) container_of(x, struct nv50_kms_crtc, priv) +#define from_nv50_kms_encoder(x) container_of(x, struct nv50_kms_encoder, pub) +#define from_nv50_output(x) container_of(x, struct nv50_kms_encoder, priv) +#define from_nv50_kms_connector(x) container_of(x, struct nv50_kms_connector, pub) +#define from_nv50_connector(x) container_of(x, struct nv50_kms_connector, priv) + +#define to_nv50_crtc(x) (&(from_nv50_kms_crtc(x)->priv)) +#define to_nv50_kms_crtc(x) (&(from_nv50_crtc(x)->pub)) +#define to_nv50_output(x) (&(from_nv50_kms_encoder(x)->priv)) +#define to_nv50_kms_encoder(x) (&(from_nv50_output(x)->pub)) +#define to_nv50_connector(x) (&(from_nv50_kms_connector(x)->priv)) +#define to_nv50_kms_connector(x) (&(from_nv50_connector(x)->pub)) + +struct nv50_kms_priv *nv50_get_kms_priv(struct drm_device *dev); +void nv50_kms_connector_detect_all(struct drm_device *dev); + +int nv50_kms_init(struct drm_device *dev); +int nv50_kms_destroy(struct drm_device *dev); + +#endif /* __NV50_KMS_WRAPPER_H__ */ diff --git a/linux-core/nv50_lut.c b/linux-core/nv50_lut.c new file mode 100644 index 00000000..570900ba --- /dev/null +++ b/linux-core/nv50_lut.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_lut.h" +#include "nv50_fb.h" +#include "nv50_crtc.h" +#include "nv50_display.h" + +static int nv50_lut_alloc(struct nv50_crtc *crtc) +{ + struct mem_block *block; + struct drm_file *file_priv = kzalloc(sizeof(struct drm_file), GFP_KERNEL); + uint32_t flags = NOUVEAU_MEM_INTERNAL | NOUVEAU_MEM_FB | NOUVEAU_MEM_MAPPED; + + NV50_DEBUG("\n"); + + /* Any file_priv should do as it's pointer is used as identification. */ + block = nouveau_mem_alloc(crtc->dev, 0, 4096, flags, file_priv); + + if (!block) + return -ENOMEM; + + crtc->lut->block = block; + + return 0; +} + +static int nv50_lut_free(struct nv50_crtc *crtc) +{ + struct drm_file *file_priv = crtc->lut->block->file_priv; + + NV50_DEBUG("\n"); + + nouveau_mem_free(crtc->dev, crtc->lut->block); + + kfree(file_priv); + + return 0; +} + +#define NV50_LUT_INDEX(val, w) ((val << (8 - w)) | (val >> ((w << 1) - 8))) +static int nv50_lut_set(struct nv50_crtc *crtc, uint16_t *red, uint16_t *green, uint16_t *blue) +{ + uint32_t index = 0, i; + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + void __iomem *lut = NULL; + + NV50_DEBUG("\n"); + + if (!crtc->lut || !crtc->lut->block) { + DRM_ERROR("Something wrong with the LUT\n"); + return -EINVAL; + } + + /* 16 bits, red, green, blue, unused, total of 64 bits per index */ + /* maybe switch to ioremap_wc once it becomes available. */ + lut = ioremap(dev_priv->fb_phys + crtc->lut->block->start, crtc->lut->block->size); + if (!lut) { + DRM_ERROR("ioremap failed on LUT\n"); + return -EINVAL; + } + + /* 10 bits lut, with 14 bits values. */ + switch (crtc->fb->depth) { + case 15: + /* R5G5B5 */ + for (i = 0; i < 32; i++) { + index = NV50_LUT_INDEX(i, 5); + writew(red[i] >> 2, lut + 8*index + 0); + writew(green[i] >> 2, lut + 8*index + 2); + writew(blue[i] >> 2, lut + 8*index + 4); + } + break; + case 16: + /* R5G6B5 */ + for (i = 0; i < 32; i++) { + index = NV50_LUT_INDEX(i, 5); + writew(red[i] >> 2, lut + 8*index + 0); + writew(blue[i] >> 2, lut + 8*index + 4); + } + + /* Green has an extra bit. */ + for (i = 0; i < 64; i++) { + index = NV50_LUT_INDEX(i, 6); + writew(green[i] >> 2, lut + 8*index + 2); + } + break; + default: + /* R8G8B8 */ + for (i = 0; i < 256; i++) { + writew(red[i] >> 2, lut + 8*i + 0); + writew(green[i] >> 2, lut + 8*i + 2); + writew(blue[i] >> 2, lut + 8*i + 4); + } + break; + } + + crtc->lut->depth = crtc->fb->depth; + + /* Cleaning time. */ + iounmap(lut); + + return 0; +} + +int nv50_lut_create(struct nv50_crtc *crtc) +{ + int rval = 0; + + NV50_DEBUG("\n"); + + if (!crtc) + return -EINVAL; + + crtc->lut = kzalloc(sizeof(struct nv50_lut), GFP_KERNEL); + + rval = nv50_lut_alloc(crtc); + if (rval != 0) + return rval; + + /* lut will be inited when fb is bound */ + crtc->lut->depth = 0; + + /* function pointers */ + crtc->lut->set = nv50_lut_set; + + return 0; +} + +int nv50_lut_destroy(struct nv50_crtc *crtc) +{ + int rval = 0; + + NV50_DEBUG("\n"); + + if (!crtc) + return -EINVAL; + + rval = nv50_lut_free(crtc); + + kfree(crtc->lut); + crtc->lut = NULL; + + if (rval != 0) + return rval; + + return 0; +} diff --git a/linux-core/nv50_lut.h b/linux-core/nv50_lut.h new file mode 100644 index 00000000..06704830 --- /dev/null +++ b/linux-core/nv50_lut.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_LUT_H__ +#define __NV50_LUT_H__ + +#include "nv50_display.h" + +struct nv50_crtc; + +struct nv50_lut { + struct mem_block *block; + + int depth; /* check against fb to see if it needs to be reuploaded */ + + int (*set) (struct nv50_crtc *crtc, uint16_t *red, uint16_t *green, uint16_t *blue); +}; + +int nv50_lut_create(struct nv50_crtc *crtc); +int nv50_lut_destroy(struct nv50_crtc *crtc); + +#endif /* __NV50_LUT_H__ */ diff --git a/linux-core/nv50_output.c b/linux-core/nv50_output.c new file mode 100644 index 00000000..de0017b9 --- /dev/null +++ b/linux-core/nv50_output.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_output.h" + +int nv50_output_or_offset(struct nv50_output *output) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + return ffs(dev_priv->dcb_table.entry[output->dcb_entry].or) - 1; +} diff --git a/linux-core/nv50_output.h b/linux-core/nv50_output.h new file mode 100644 index 00000000..bdee2826 --- /dev/null +++ b/linux-core/nv50_output.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_OUTPUT_H__ +#define __NV50_OUTPUT_H__ + +#include "nv50_crtc.h" + +#define OUTPUT_UNKNOWN 0 +#define OUTPUT_DAC 1 +#define OUTPUT_TMDS 2 +#define OUTPUT_LVDS 3 +#define OUTPUT_TV 4 + +struct nv50_output { + struct list_head head; + + struct drm_device *dev; + int bus; + int dcb_entry; + int type; + + struct nv50_crtc *crtc; + struct nouveau_hw_mode *native_mode; + + int (*validate_mode) (struct nv50_output *output, struct nouveau_hw_mode *mode); + int (*execute_mode) (struct nv50_output *output, bool disconnect); + int (*set_clock_mode) (struct nv50_output *output); + bool (*detect) (struct nv50_output *output); + int (*destroy) (struct nv50_output *output); +}; + +int nv50_output_or_offset(struct nv50_output *output); +int nv50_sor_create(struct drm_device *dev, int dcb_entry); +int nv50_dac_create(struct drm_device *dev, int dcb_entry); + +#endif /* __NV50_OUTPUT_H__ */ diff --git a/linux-core/nv50_sor.c b/linux-core/nv50_sor.c new file mode 100644 index 00000000..430c1e83 --- /dev/null +++ b/linux-core/nv50_sor.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "nv50_output.h" + +static int nv50_sor_validate_mode(struct nv50_output *output, struct nouveau_hw_mode *mode) +{ + NV50_DEBUG("\n"); + + if (mode->clock > 165000) /* no dual link until we figure it out completely */ + return MODE_CLOCK_HIGH; + + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + if (output->native_mode->hdisplay > 0 && output->native_mode->vdisplay > 0) { + if (mode->hdisplay > output->native_mode->hdisplay || mode->vdisplay > output->native_mode->vdisplay) + return MODE_PANEL; + } + + return MODE_OK; +} + +static int nv50_sor_execute_mode(struct nv50_output *output, bool disconnect) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + struct nv50_crtc *crtc = output->crtc; + struct nouveau_hw_mode *desired_mode = NULL; + + uint32_t offset = nv50_output_or_offset(output) * 0x40; + + uint32_t mode_ctl = NV50_SOR_MODE_CTRL_OFF; + + NV50_DEBUG("or %d\n", nv50_output_or_offset(output)); + + if (disconnect) { + NV50_DEBUG("Disconnecting SOR\n"); + OUT_MODE(NV50_SOR0_MODE_CTRL + offset, mode_ctl); + return 0; + } + + desired_mode = (crtc->use_native_mode ? crtc->native_mode : crtc->mode); + + if (output->type == OUTPUT_LVDS) { + mode_ctl |= NV50_SOR_MODE_CTRL_LVDS; + } else { + mode_ctl |= NV50_SOR_MODE_CTRL_TMDS; + if (desired_mode->clock > 165000) + mode_ctl |= NV50_SOR_MODE_CTRL_TMDS_DUAL_LINK; + } + + if (crtc->index == 1) + mode_ctl |= NV50_SOR_MODE_CTRL_CRTC1; + else + mode_ctl |= NV50_SOR_MODE_CTRL_CRTC0; + + if (desired_mode->flags & V_NHSYNC) + mode_ctl |= NV50_SOR_MODE_CTRL_NHSYNC; + + if (desired_mode->flags & V_NVSYNC) + mode_ctl |= NV50_SOR_MODE_CTRL_NVSYNC; + + /* TODO: DPMS ?????? */ + + OUT_MODE(NV50_SOR0_MODE_CTRL + offset, mode_ctl); + + return 0; +} + +static int nv50_sor_set_clock_mode(struct nv50_output *output) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + struct nv50_crtc *crtc = output->crtc; + + uint32_t limit = 165000; + struct nouveau_hw_mode *hw_mode; + + NV50_DEBUG("or %d\n", nv50_output_or_offset(output)); + + if (crtc->use_native_mode) + hw_mode = crtc->native_mode; + else + hw_mode = crtc->mode; + + /* 0x70000 was a late addition to nv, mentioned as fixing tmds initialisation on certain gpu's. */ + /* I presume it's some kind of clock setting, but what precisely i do not know. */ + NV_WRITE(NV50_PDISPLAY_SOR_CLK_CLK_CTRL2(nv50_output_or_offset(output)), 0x70000 | ((hw_mode->clock > limit) ? 0x101 : 0)); + + return 0; +} + +static int nv50_sor_destroy(struct nv50_output *output) +{ + struct drm_device *dev = output->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *display = nv50_get_display(dev); + + NV50_DEBUG("\n"); + + if (!display || !output) + return -EINVAL; + + list_del(&output->head); + + kfree(output->native_mode); + if (dev_priv->free_output) + dev_priv->free_output(output); + + return 0; +} + +int nv50_sor_create(struct drm_device *dev, int dcb_entry) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_output *output = NULL; + struct nv50_display *display = NULL; + struct dcb_entry *entry = NULL; + + NV50_DEBUG("\n"); + + /* This allows the public layer to do it's thing. */ + if (dev_priv->alloc_output) + output = dev_priv->alloc_output(dev); + + if (!output) + return -ENOMEM; + + output->dev = dev; + + display = nv50_get_display(dev); + if (!display) + goto out; + + entry = &dev_priv->dcb_table.entry[dcb_entry]; + if (!entry) + goto out; + + switch (entry->type) { + case DCB_OUTPUT_TMDS: + output->type = OUTPUT_TMDS; + DRM_INFO("Detected a TMDS output\n"); + break; + case DCB_OUTPUT_LVDS: + output->type = OUTPUT_LVDS; + DRM_INFO("Detected a LVDS output\n"); + break; + default: + goto out; + } + + output->dcb_entry = dcb_entry; + output->bus = entry->bus; + + list_add_tail(&output->head, &display->outputs); + + output->native_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + + /* Set function pointers. */ + output->validate_mode = nv50_sor_validate_mode; + output->execute_mode = nv50_sor_execute_mode; + output->set_clock_mode = nv50_sor_set_clock_mode; + output->detect = NULL; + output->destroy = nv50_sor_destroy; + + /* Some default state, unknown what it precisely means. */ + if (output->type == OUTPUT_TMDS) { + NV_WRITE(NV50_PDISPLAY_SOR_REGS_UNK_00C(nv50_output_or_offset(output)), 0x03010700); + NV_WRITE(NV50_PDISPLAY_SOR_REGS_UNK_010(nv50_output_or_offset(output)), 0x0000152f); + NV_WRITE(NV50_PDISPLAY_SOR_REGS_UNK_014(nv50_output_or_offset(output)), 0x00000000); + NV_WRITE(NV50_PDISPLAY_SOR_REGS_UNK_018(nv50_output_or_offset(output)), 0x00245af8); + } + + return 0; + +out: + if (output->native_mode) + kfree(output->native_mode); + if (dev_priv->free_output) + dev_priv->free_output(output); + return -EINVAL; +} |