summaryrefslogtreecommitdiff
path: root/linux-core
diff options
context:
space:
mode:
authorDave Airlie <airlied@linux.ie>2008-07-26 08:38:59 +1000
committerDave Airlie <airlied@linux.ie>2008-07-26 08:38:59 +1000
commitfb05c4d621084d7a3fb3dd52d7d9c888eac852d0 (patch)
treebca43a3c777984e0eb962d55817541801377d3c4 /linux-core
parent2556341f8baf0e0b7b5f7843135e43e662751af0 (diff)
parent53428453758621da70d9608c9baec58b4b9383ec (diff)
Merge remote branch 'origin/modesetting-101' into modesetting-gem
Diffstat (limited to 'linux-core')
-rw-r--r--linux-core/drm_crtc.c75
-rw-r--r--linux-core/drm_crtc.h10
-rw-r--r--linux-core/drm_crtc_helper.c3
-rw-r--r--linux-core/drm_drv.c5
-rw-r--r--linux-core/drm_modes.c4
-rw-r--r--linux-core/intel_crt.c4
-rw-r--r--linux-core/intel_tv.c2
-rw-r--r--linux-core/nouveau_bios.c51
-rw-r--r--linux-core/nv50_connector.c54
-rw-r--r--linux-core/nv50_connector.h7
-rw-r--r--linux-core/nv50_crtc.c10
-rw-r--r--linux-core/nv50_crtc.h4
-rw-r--r--linux-core/nv50_dac.c42
-rw-r--r--linux-core/nv50_display.h2
-rw-r--r--linux-core/nv50_kms_wrapper.c247
-rw-r--r--linux-core/nv50_kms_wrapper.h2
-rw-r--r--linux-core/nv50_output.h2
17 files changed, 450 insertions, 74 deletions
diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c
index fc8d1fe8..91bff1f6 100644
--- a/linux-core/drm_crtc.c
+++ b/linux-core/drm_crtc.c
@@ -60,6 +60,26 @@ char *drm_get_dpms_name(int val)
return "unknown";
}
+/*
+ * Optional properties
+ */
+static struct drm_prop_enum_list drm_scaling_mode_enum_list[] =
+{
+ { DRM_MODE_SCALE_NON_GPU, "Non-GPU" },
+ { DRM_MODE_SCALE_FULLSCREEN, "Fullscreen" },
+ { DRM_MODE_SCALE_NO_SCALE, "No scale" },
+ { DRM_MODE_SCALE_ASPECT, "Aspect" },
+};
+
+static struct drm_prop_enum_list drm_dithering_mode_enum_list[] =
+{
+ { DRM_MODE_DITHERING_OFF, "Off" },
+ { DRM_MODE_DITHERING_ON, "On" },
+};
+
+/*
+ * Non-global properties, but "required" for certain connectors.
+ */
static struct drm_prop_enum_list drm_select_subconnector_enum_list[] =
{
{ DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */
@@ -102,6 +122,9 @@ char *drm_get_subconnector_name(int val)
return "unknown";
}
+/*
+ * Connector and encoder types.
+ */
static struct drm_prop_enum_list drm_connector_enum_list[] =
{ { DRM_MODE_CONNECTOR_Unknown, "Unknown" },
{ DRM_MODE_CONNECTOR_VGA, "VGA" },
@@ -647,6 +670,52 @@ int drm_mode_create_tv_properties(struct drm_device *dev, int num_modes,
EXPORT_SYMBOL(drm_mode_create_tv_properties);
/**
+ * drm_mode_create_scaling_mode_property - create scaling mode property
+ * @dev: DRM device
+ *
+ * Called by a driver the first time it's needed, must be attached to desired connectors.
+ */
+int drm_mode_create_scaling_mode_property(struct drm_device *dev)
+{
+ int i;
+
+ if (dev->mode_config.scaling_mode_property) /* already done */
+ return 0;
+
+ dev->mode_config.scaling_mode_property =
+ drm_property_create(dev, DRM_MODE_PROP_ENUM,
+ "scaling mode", ARRAY_SIZE(drm_scaling_mode_enum_list));
+ for (i = 0; i < ARRAY_SIZE(drm_scaling_mode_enum_list); i++)
+ drm_property_add_enum(dev->mode_config.scaling_mode_property, i, drm_scaling_mode_enum_list[i].type, drm_scaling_mode_enum_list[i].name);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
+
+/**
+ * drm_mode_create_dithering_property - create dithering property
+ * @dev: DRM device
+ *
+ * Called by a driver the first time it's needed, must be attached to desired connectors.
+ */
+int drm_mode_create_dithering_property(struct drm_device *dev)
+{
+ int i;
+
+ if (dev->mode_config.dithering_mode_property) /* already done */
+ return 0;
+
+ dev->mode_config.dithering_mode_property =
+ drm_property_create(dev, DRM_MODE_PROP_ENUM,
+ "dithering", ARRAY_SIZE(drm_dithering_mode_enum_list));
+ for (i = 0; i < ARRAY_SIZE(drm_dithering_mode_enum_list); i++)
+ drm_property_add_enum(dev->mode_config.dithering_mode_property, i, drm_dithering_mode_enum_list[i].type, drm_dithering_mode_enum_list[i].name);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_dithering_property);
+
+/**
* drm_mode_config_init - initialize DRM mode_configuration structure
* @dev: DRM device
*
@@ -2115,12 +2184,12 @@ int drm_mode_connector_property_set_ioctl(struct drm_device *dev,
}
}
- /* store the property value */
- drm_connector_property_set_value(connector, property, out_resp->value);
-
if (connector->funcs->set_property)
ret = connector->funcs->set_property(connector, property, out_resp->value);
+ /* store the property value if succesful */
+ if (!ret)
+ drm_connector_property_set_value(connector, property, out_resp->value);
out:
mutex_unlock(&dev->mode_config.mutex);
return ret;
diff --git a/linux-core/drm_crtc.h b/linux-core/drm_crtc.h
index d4bb8794..117b7213 100644
--- a/linux-core/drm_crtc.h
+++ b/linux-core/drm_crtc.h
@@ -375,7 +375,7 @@ struct drm_connector_funcs {
void (*restore)(struct drm_connector *connector);
enum drm_connector_status (*detect)(struct drm_connector *connector);
void (*fill_modes)(struct drm_connector *connector, uint32_t max_width, uint32_t max_height);
- bool (*set_property)(struct drm_connector *connector, struct drm_property *property,
+ int (*set_property)(struct drm_connector *connector, struct drm_property *property,
uint64_t val);
void (*destroy)(struct drm_connector *connector);
};
@@ -536,7 +536,7 @@ struct drm_mode_config {
struct drm_property *edid_property;
struct drm_property *dpms_property;
- /* optional properties */
+ /* DVI-I properties */
struct drm_property *dvi_i_subconnector_property;
struct drm_property *dvi_i_select_subconnector_property;
@@ -549,6 +549,10 @@ struct drm_mode_config {
struct drm_property *tv_top_margin_property;
struct drm_property *tv_bottom_margin_property;
+ /* Optional properties */
+ struct drm_property *scaling_mode_property;
+ struct drm_property *dithering_mode_property;
+
/* hotplug */
uint32_t hotplug_counter;
};
@@ -650,6 +654,8 @@ extern int drm_property_add_enum(struct drm_property *property, int index,
extern int drm_mode_create_dvi_i_properties(struct drm_device *dev);
extern int drm_mode_create_tv_properties(struct drm_device *dev, int num_formats,
char *formats[]);
+extern int drm_mode_create_scaling_mode_property(struct drm_device *dev);
+extern int drm_mode_create_dithering_property(struct drm_device *dev);
extern char *drm_get_encoder_name(struct drm_encoder *encoder);
extern int drm_mode_connector_attach_encoder(struct drm_connector *connector,
diff --git a/linux-core/drm_crtc_helper.c b/linux-core/drm_crtc_helper.c
index 7e5d8fcc..6f16dad6 100644
--- a/linux-core/drm_crtc_helper.c
+++ b/linux-core/drm_crtc_helper.c
@@ -566,7 +566,8 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
ret = -EINVAL;
goto fail_no_encoder;
}
-
+
+ count = 0;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (!connector->encoder)
continue;
diff --git a/linux-core/drm_drv.c b/linux-core/drm_drv.c
index 24a6df2f..9113fa54 100644
--- a/linux-core/drm_drv.c
+++ b/linux-core/drm_drv.c
@@ -246,7 +246,10 @@ int drm_lastclose(struct drm_device * dev)
dev->agp->acquired = 0;
dev->agp->enabled = 0;
}
- if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg) {
+
+ /* You're supposed to have a real memory manager for modesetting, but this'll suffice as a temporary workaround. */
+ /* This assumes sgdma is inited at load time. */
+ if (drm_core_check_feature(dev, DRIVER_SG) && !drm_core_check_feature(dev, DRIVER_MODESET) && dev->sg) {
drm_sg_cleanup(dev->sg);
dev->sg = NULL;
}
diff --git a/linux-core/drm_modes.c b/linux-core/drm_modes.c
index 4ee00305..d4cda0be 100644
--- a/linux-core/drm_modes.c
+++ b/linux-core/drm_modes.c
@@ -398,7 +398,7 @@ void drm_mode_prune_invalid(struct drm_device *dev,
drm_mode_debug_printmodeline(mode);
DRM_DEBUG("Not using %s mode %d\n", mode->name, mode->status);
}
- kfree(mode);
+ drm_mode_destroy(dev, mode);
}
}
}
@@ -556,7 +556,7 @@ void drm_mode_connector_list_update(struct drm_connector *connector)
/* if equal delete the probed mode */
mode->status = pmode->status;
list_del(&pmode->head);
- kfree(pmode);
+ drm_mode_destroy(connector->dev, pmode);
break;
}
}
diff --git a/linux-core/intel_crt.c b/linux-core/intel_crt.c
index 2ed1cb79..2505d983 100644
--- a/linux-core/intel_crt.c
+++ b/linux-core/intel_crt.c
@@ -210,7 +210,7 @@ static int intel_crt_get_modes(struct drm_connector *connector)
return intel_ddc_get_modes(intel_output);
}
-static bool intel_crt_set_property(struct drm_connector *connector,
+static int intel_crt_set_property(struct drm_connector *connector,
struct drm_property *property,
uint64_t value)
{
@@ -219,7 +219,7 @@ static bool intel_crt_set_property(struct drm_connector *connector,
if (property == dev->mode_config.dpms_property && connector->encoder)
intel_crt_dpms(connector->encoder, (uint32_t)(value & 0xf));
- return true;
+ return 0;
}
/*
diff --git a/linux-core/intel_tv.c b/linux-core/intel_tv.c
index b89ccece..3efa9d2d 100644
--- a/linux-core/intel_tv.c
+++ b/linux-core/intel_tv.c
@@ -1560,7 +1560,7 @@ intel_tv_destroy (struct drm_connector *connector)
}
-static bool
+static int
intel_tv_set_property(struct drm_connector *connector, struct drm_property *property,
uint64_t val)
{
diff --git a/linux-core/nouveau_bios.c b/linux-core/nouveau_bios.c
index 3e7fe23f..faa2b2b0 100644
--- a/linux-core/nouveau_bios.c
+++ b/linux-core/nouveau_bios.c
@@ -128,6 +128,39 @@ struct bit_entry {
uint16_t offset;
};
+static int parse_bit_A_tbl_entry(struct drm_device *dev, struct bios *bios, struct bit_entry *bitentry)
+{
+ /* Parses the load detect value table.
+ *
+ * Starting at bitentry->offset:
+ *
+ * offset + 0 (16 bits): table pointer
+ */
+
+ uint16_t load_table_pointer;
+
+ if (bitentry->length != 3) {
+ DRM_ERROR("Do not understand BIT loadval table\n");
+ return 0;
+ }
+
+ load_table_pointer = le16_to_cpu(*((uint16_t *)(&bios->data[bitentry->offset])));
+
+ if (load_table_pointer == 0x0) {
+ DRM_ERROR("Pointer to loadval table invalid\n");
+ return 0;
+ }
+
+ /* Some kind of signature */
+ if (bios->data[load_table_pointer] != 16 || bios->data[load_table_pointer + 1] != 4 ||
+ bios->data[load_table_pointer + 2] != 4 || bios->data[load_table_pointer + 3] != 2)
+ return 0;
+
+ bios->dactestval = le32_to_cpu(*((uint32_t *)&bios->data[load_table_pointer + 4])) & 0x3FF;
+
+ return 1;
+}
+
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
@@ -136,7 +169,7 @@ static int parse_bit_C_tbl_entry(struct drm_device *dev, struct bios *bios, stru
*/
if (bitentry->length < 10) {
- DRM_ERROR( "Do not understand BIT C table\n");
+ DRM_ERROR("Do not understand BIT C table\n");
return 0;
}
@@ -149,7 +182,7 @@ static void parse_bit_structure(struct drm_device *dev, struct bios *bios, const
{
int entries = bios->data[bitoffset + 4];
/* parse i first, I next (which needs C & M before it), and L before D */
- char parseorder[] = "iCMILDT";
+ char parseorder[] = "iCMILDTA";
struct bit_entry bitentry;
int i, j, offset;
@@ -164,6 +197,9 @@ static void parse_bit_structure(struct drm_device *dev, struct bios *bios, const
continue;
switch (bitentry.id[0]) {
+ case 'A':
+ parse_bit_A_tbl_entry(dev, bios, &bitentry);
+ break;
case 'C':
parse_bit_C_tbl_entry(dev, bios, &bitentry);
break;
@@ -305,11 +341,12 @@ parse_dcb_entry(struct drm_device *dev, int index, uint8_t dcb_version, uint16_t
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;
+ if (dcb_version < 0x40) { /* we know g80 cards have unknown bits */
+ 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;
}
diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c
index ac5194c0..34706bae 100644
--- a/linux-core/nv50_connector.c
+++ b/linux-core/nv50_connector.c
@@ -76,7 +76,44 @@ static struct nv50_output *nv50_connector_to_output(struct nv50_connector *conne
return NULL;
}
-static bool nv50_connector_detect(struct nv50_connector *connector)
+static int nv50_connector_hpd_detect(struct nv50_connector *connector)
+{
+ struct drm_nouveau_private *dev_priv = connector->dev->dev_private;
+ bool present = 0;
+ uint32_t reg = 0;
+
+ /* Assume connected for the moment. */
+ if (connector->type == CONNECTOR_LVDS) {
+ NV50_DEBUG("LVDS is defaulting to connected for the moment.\n");
+ return 1;
+ }
+
+ /* No i2c port, no idea what to do for hotplug. */
+ if (connector->i2c_chan->index == 15) {
+ DRM_ERROR("You have a non-LVDS SOR with no i2c port, please report\n");
+ return -EINVAL;
+ }
+
+ if (connector->i2c_chan->index > 3) {
+ DRM_ERROR("You have an unusual configuration, index is %d\n", connector->i2c_chan->index);
+ DRM_ERROR("Please report.\n");
+ return -EINVAL;
+ }
+
+ /* Check hotplug pins. */
+ reg = NV_READ(NV50_PCONNECTOR_HOTPLUG_STATE);
+ if (reg & (NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C0 << (4 * connector->i2c_chan->index)))
+ present = 1;
+
+ if (present)
+ NV50_DEBUG("Hotplug detect returned positive for bus %d\n", connector->bus);
+ else
+ NV50_DEBUG("Hotplug detect returned negative for bus %d\n", connector->bus);
+
+ return present;
+}
+
+static int nv50_connector_i2c_detect(struct nv50_connector *connector)
{
/* kindly borrrowed from the intel driver, hope it works. */
uint8_t out_buf[] = { 0x0, 0x0};
@@ -97,13 +134,11 @@ static bool nv50_connector_detect(struct nv50_connector *connector)
}
};
- NV50_DEBUG("\n");
-
if (!connector->i2c_chan)
- return false;
+ return -EINVAL;
ret = i2c_transfer(&connector->i2c_chan->adapter, msgs, 2);
- DRM_INFO("I2C detect returned %d\n", ret);
+ NV50_DEBUG("I2C detect returned %d\n", ret);
if (ret == 2)
return true;
@@ -185,15 +220,18 @@ int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int ty
/* some reasonable defaults */
if (type == CONNECTOR_DVI_D || type == CONNECTOR_DVI_I || type == CONNECTOR_LVDS)
- connector->scaling_mode = SCALE_FULLSCREEN;
+ connector->requested_scaling_mode = SCALE_FULLSCREEN;
else
- connector->scaling_mode = SCALE_PANEL;
+ connector->requested_scaling_mode = SCALE_NON_GPU;
+
+ connector->use_dithering = false;
if (i2c_index < 0xf)
connector->i2c_chan = nv50_i2c_channel_create(dev, i2c_index);
/* set function pointers */
- connector->detect = nv50_connector_detect;
+ connector->hpd_detect = nv50_connector_hpd_detect;
+ connector->i2c_detect = nv50_connector_i2c_detect;
connector->destroy = nv50_connector_destroy;
connector->to_output = nv50_connector_to_output;
diff --git a/linux-core/nv50_connector.h b/linux-core/nv50_connector.h
index ebd6eac6..fa7316e2 100644
--- a/linux-core/nv50_connector.h
+++ b/linux-core/nv50_connector.h
@@ -47,9 +47,12 @@ struct nv50_connector {
struct nv50_i2c_channel *i2c_chan;
struct nv50_output *output;
- int scaling_mode;
+ int requested_scaling_mode;
- bool (*detect) (struct nv50_connector *connector);
+ bool use_dithering;
+
+ int (*hpd_detect) (struct nv50_connector *connector);
+ int (*i2c_detect) (struct nv50_connector *connector);
int (*destroy) (struct nv50_connector *connector);
struct nv50_output *(*to_output) (struct nv50_connector *connector, bool digital);
};
diff --git a/linux-core/nv50_crtc.c b/linux-core/nv50_crtc.c
index 6c3d404f..c4ca7e76 100644
--- a/linux-core/nv50_crtc.c
+++ b/linux-core/nv50_crtc.c
@@ -255,7 +255,7 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc)
NV50_DEBUG("\n");
- switch (crtc->scaling_mode) {
+ switch (crtc->requested_scaling_mode) {
case SCALE_ASPECT:
nv50_crtc_calc_scale(crtc, &outX, &outY);
break;
@@ -264,7 +264,7 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc)
outY = crtc->native_mode->vdisplay;
break;
case SCALE_NOSCALE:
- case SCALE_PANEL:
+ case SCALE_NON_GPU:
default:
outX = crtc->mode->hdisplay;
outY = crtc->mode->vdisplay;
@@ -283,6 +283,9 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc)
OUT_MODE(NV50_CRTC0_SCALE_RES1 + offset, outY << 16 | outX);
OUT_MODE(NV50_CRTC0_SCALE_RES2 + offset, outY << 16 | outX);
+ /* processed */
+ crtc->scaling_mode = crtc->requested_scaling_mode;
+
return 0;
}
@@ -492,6 +495,9 @@ int nv50_crtc_create(struct drm_device *dev, int index)
crtc->mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL);
crtc->native_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL);
+ crtc->requested_scaling_mode = SCALE_INVALID;
+ crtc->scaling_mode = SCALE_INVALID;
+
if (!crtc->mode || !crtc->native_mode) {
rval = -ENOMEM;
goto out;
diff --git a/linux-core/nv50_crtc.h b/linux-core/nv50_crtc.h
index 8235c9d6..b4b83584 100644
--- a/linux-core/nv50_crtc.h
+++ b/linux-core/nv50_crtc.h
@@ -46,6 +46,10 @@ struct nv50_crtc {
bool use_native_mode;
bool use_dithering;
+
+ /* Changing scaling modes requires a modeset sometimes. */
+ /* We need to know the currently active hw mode, as well as the requested one by the user. */
+ int requested_scaling_mode;
int scaling_mode;
struct nv50_cursor *cursor;
diff --git a/linux-core/nv50_dac.c b/linux-core/nv50_dac.c
index ca4bb5e1..5dddb469 100644
--- a/linux-core/nv50_dac.c
+++ b/linux-core/nv50_dac.c
@@ -133,6 +133,46 @@ static int nv50_dac_set_power_mode(struct nv50_output *output, int mode)
return 0;
}
+static int nv50_dac_detect(struct nv50_output *output)
+{
+ struct drm_nouveau_private *dev_priv = output->dev->dev_private;
+ int or = nv50_output_or_offset(output);
+ bool present = 0;
+ uint32_t dpms_state, load_pattern, load_state;
+
+ NV_WRITE(NV50_PDISPLAY_DAC_REGS_CLK_CTRL1(or), 0x00000001);
+ dpms_state = NV_READ(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or));
+
+ NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or), 0x00150000 | NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING);
+ while (NV_READ(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or)) & NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING);
+
+ /* Use bios provided value if possible. */
+ if (dev_priv->bios.dactestval) {
+ load_pattern = dev_priv->bios.dactestval;
+ NV50_DEBUG("Using bios provided load_pattern of %d\n", load_pattern);
+ } else {
+ load_pattern = 340;
+ NV50_DEBUG("Using default load_pattern of %d\n", load_pattern);
+ }
+
+ NV_WRITE(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or), NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_ACTIVE | load_pattern);
+ udelay(10000); /* give it some time to process */
+ load_state = NV_READ(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or));
+
+ NV_WRITE(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or), 0);
+ NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or), dpms_state);
+
+ if ((load_state & NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_PRESENT) == NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_PRESENT)
+ present = 1;
+
+ if (present)
+ NV50_DEBUG("Load was detected on output with or %d\n", or);
+ else
+ NV50_DEBUG("Load was not detected on output with or %d\n", or);
+
+ return present;
+}
+
static int nv50_dac_destroy(struct nv50_output *output)
{
struct drm_device *dev = output->dev;
@@ -210,7 +250,7 @@ int nv50_dac_create(struct drm_device *dev, int dcb_entry)
output->execute_mode = nv50_dac_execute_mode;
output->set_clock_mode = nv50_dac_set_clock_mode;
output->set_power_mode = nv50_dac_set_power_mode;
- output->detect = NULL; /* TODO */
+ output->detect = nv50_dac_detect;
output->destroy = nv50_dac_destroy;
return 0;
diff --git a/linux-core/nv50_display.h b/linux-core/nv50_display.h
index f20e67da..3c2ee1c9 100644
--- a/linux-core/nv50_display.h
+++ b/linux-core/nv50_display.h
@@ -68,7 +68,7 @@ struct nv50_display {
};
enum scaling_modes {
- SCALE_PANEL,
+ SCALE_NON_GPU,
SCALE_FULLSCREEN,
SCALE_ASPECT,
SCALE_NOSCALE,
diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c
index a7966e9a..355d25d6 100644
--- a/linux-core/nv50_kms_wrapper.c
+++ b/linux-core/nv50_kms_wrapper.c
@@ -386,7 +386,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
/* This is to ensure it knows the connector subtype. */
drm_connector->funcs->fill_modes(drm_connector, 0, 0);
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (!output) {
DRM_ERROR("No output\n");
goto out;
@@ -458,7 +458,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
goto out;
}
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (!output) {
DRM_ERROR("No output\n");
goto out;
@@ -635,11 +635,12 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
if (connector->output != output)
continue;
- crtc->scaling_mode = connector->scaling_mode;
+ crtc->requested_scaling_mode = connector->requested_scaling_mode;
+ crtc->use_dithering = connector->use_dithering;
break;
}
- if (crtc->scaling_mode == SCALE_PANEL)
+ if (crtc->requested_scaling_mode == SCALE_NON_GPU)
crtc->use_native_mode = false;
else
crtc->use_native_mode = true;
@@ -834,7 +835,9 @@ static int nv50_kms_encoders_init(struct drm_device *dev)
* Connector functions
*/
-bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector)
+
+/* These 2 functions wrap the connector properties that deal with multiple encoders per connector. */
+bool nv50_kms_connector_get_digital(struct drm_connector *drm_connector)
{
struct drm_device *dev = drm_connector->dev;
@@ -891,6 +894,33 @@ bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector)
return false;
}
+static void nv50_kms_connector_set_digital(struct drm_connector *drm_connector, int digital, bool force)
+{
+ struct drm_device *dev = drm_connector->dev;
+
+ if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) {
+ uint64_t cur_value, new_value;
+
+ int rval = drm_connector_property_get_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, &cur_value);
+ if (rval) {
+ DRM_ERROR("Unable to find subconnector property\n");
+ return;
+ }
+
+ /* Only set when unknown or when forced to do so. */
+ if (cur_value != DRM_MODE_SUBCONNECTOR_Unknown && !force)
+ return;
+
+ if (digital == 1)
+ new_value = DRM_MODE_SUBCONNECTOR_DVID;
+ else if (digital == 0)
+ new_value = DRM_MODE_SUBCONNECTOR_DVIA;
+ else
+ new_value = DRM_MODE_SUBCONNECTOR_Unknown;
+ drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, new_value);
+ }
+}
+
void nv50_kms_connector_detect_all(struct drm_device *dev)
{
struct drm_connector *drm_connector = NULL;
@@ -902,16 +932,32 @@ void nv50_kms_connector_detect_all(struct drm_device *dev)
static enum drm_connector_status nv50_kms_connector_detect(struct drm_connector *drm_connector)
{
- struct nv50_connector *connector = to_nv50_connector(drm_connector);
struct drm_device *dev = drm_connector->dev;
- bool connected;
- int old_status;
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ struct nv50_output *output = NULL;
+ int hpd_detect = 0, load_detect = 0, i2c_detect = 0;
+ int old_status = drm_connector->status;
- connected = connector->detect(connector);
+ /* hotplug detect */
+ hpd_detect = connector->hpd_detect(connector);
- old_status = drm_connector->status;
+ /* load detect */
+ output = connector->to_output(connector, FALSE); /* analog */
+ if (output && output->detect)
+ load_detect = output->detect(output);
- if (connected)
+ if (hpd_detect < 0 || load_detect < 0) /* did an error occur? */
+ i2c_detect = connector->i2c_detect(connector);
+
+ if (load_detect == 1) {
+ nv50_kms_connector_set_digital(drm_connector, 0, TRUE); /* analog, forced */
+ } else if (hpd_detect == 1 && load_detect == 0) {
+ nv50_kms_connector_set_digital(drm_connector, 1, TRUE); /* digital, forced */
+ } else {
+ nv50_kms_connector_set_digital(drm_connector, -1, TRUE); /* unknown, forced */
+ }
+
+ if (hpd_detect == 1 || load_detect == 1 || i2c_detect == 1)
drm_connector->status = connector_status_connected;
else
drm_connector->status = connector_status_disconnected;
@@ -955,7 +1001,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
struct nv50_connector *connector = to_nv50_connector(drm_connector);
struct drm_device *dev = drm_connector->dev;
int rval = 0;
- bool connected;
+ bool connected = false;
struct drm_display_mode *mode, *t;
struct edid *edid = NULL;
@@ -964,16 +1010,13 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
list_for_each_entry_safe(mode, t, &drm_connector->modes, head)
mode->status = MODE_UNVERIFIED;
- connected = connector->detect(connector);
+ if (nv50_kms_connector_detect(drm_connector) == connector_status_connected)
+ connected = true;
if (connected)
- drm_connector->status = connector_status_connected;
+ NV50_DEBUG("%s is connected\n", drm_get_connector_name(drm_connector));
else
- drm_connector->status = connector_status_disconnected;
-
- if (!connected) {
NV50_DEBUG("%s is disconnected\n", drm_get_connector_name(drm_connector));
- }
/* Not all connnectors have an i2c channel. */
if (connected && connector->i2c_chan)
@@ -985,16 +1028,8 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
if (edid) {
rval = drm_add_edid_modes(drm_connector, edid);
- /* 2 encoders per connector */
- /* eventually do this based on load detect and hot plug detect */
- if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) {
- uint64_t subtype = 0;
- if (edid->digital)
- subtype = DRM_MODE_SUBCONNECTOR_DVID;
- else
- subtype = DRM_MODE_SUBCONNECTOR_DVIA;
- drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, subtype);
- }
+ /* Only update when relevant and when detect couldn't determine type. */
+ nv50_kms_connector_set_digital(drm_connector, edid->digital ? 1 : 0, FALSE);
kfree(edid);
}
@@ -1008,7 +1043,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
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, nv50_kms_connector_is_digital(drm_connector));
+ struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
mode->status = output->validate_mode(output, hw_mode);
/* find native mode, TODO: also check if we actually found one */
@@ -1024,7 +1059,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
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, nv50_kms_connector_is_digital(drm_connector));
+ struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
mode->status = output->validate_mode(output, hw_mode);
kfree(hw_mode);
@@ -1044,6 +1079,10 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
NV50_DEBUG("No valid modes on %s\n", drm_get_connector_name(drm_connector));
+ /* Making up native modes for LVDS is a bad idea. */
+ if (drm_connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
+ return;
+
/* 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
@@ -1056,7 +1095,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
/* also add it as native mode */
hw_mode = nv50_kms_to_hw_mode(mode);
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (hw_mode)
*output->native_mode = *hw_mode;
@@ -1078,22 +1117,109 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
}
}
-static bool nv50_kms_connector_set_property(struct drm_connector *connector,
+static int nv50_kms_connector_set_property(struct drm_connector *drm_connector,
struct drm_property *property,
uint64_t value)
{
- struct drm_device *dev = connector->dev;
+ struct drm_device *dev = drm_connector->dev;
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ int rval = 0;
+ bool delay_change = false;
- if (property == dev->mode_config.dpms_property && connector->encoder) {
- struct nv50_output *output = to_nv50_output(connector->encoder);
+ /* DPMS */
+ if (property == dev->mode_config.dpms_property && drm_connector->encoder) {
+ struct nv50_output *output = to_nv50_output(drm_connector->encoder);
- if (!output->set_power_mode(output, (int) value))
- return true;
+ rval = output->set_power_mode(output, (int) value);
+
+ return rval;
+ }
+
+ /* Scaling mode */
+ if (property == dev->mode_config.scaling_mode_property) {
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_display *display = nv50_get_display(dev);
+ int internal_value = 0;
+
+ switch (value) {
+ case DRM_MODE_SCALE_NON_GPU:
+ internal_value = SCALE_NON_GPU;
+ break;
+ case DRM_MODE_SCALE_FULLSCREEN:
+ internal_value = SCALE_FULLSCREEN;
+ break;
+ case DRM_MODE_SCALE_NO_SCALE:
+ internal_value = SCALE_NOSCALE;
+ break;
+ case DRM_MODE_SCALE_ASPECT:
+ internal_value = SCALE_ASPECT;
+ break;
+ default:
+ break;
+ }
+
+ /* LVDS always needs gpu scaling */
+ if (connector->type == CONNECTOR_LVDS && internal_value == SCALE_NON_GPU)
+ return -EINVAL;
+
+ connector->requested_scaling_mode = internal_value;
+
+ if (drm_connector->encoder && drm_connector->encoder->crtc)
+ crtc = to_nv50_crtc(drm_connector->encoder->crtc);
+
+ if (!crtc)
+ return 0;
+
+ crtc->requested_scaling_mode = connector->requested_scaling_mode;
+
+ /* going from and to a gpu scaled regime requires a modesetting, so wait until next modeset */
+ if (crtc->scaling_mode == SCALE_NON_GPU || internal_value == SCALE_NON_GPU) {
+ DRM_INFO("Moving from or to a non-gpu scaled mode, this will be processed upon next modeset.");
+ delay_change = true;
+ }
+
+ if (delay_change)
+ return 0;
+
+ rval = crtc->set_scale(crtc);
+ if (rval)
+ return rval;
+
+ /* process command buffer */
+ display->update(display);
+
+ return 0;
+ }
+
+ /* Dithering */
+ if (property == dev->mode_config.dithering_mode_property) {
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_display *display = nv50_get_display(dev);
+
+ if (value == DRM_MODE_DITHERING_ON)
+ connector->use_dithering = true;
else
- return false;
+ connector->use_dithering = false;
+
+ if (drm_connector->encoder && drm_connector->encoder->crtc)
+ crtc = to_nv50_crtc(drm_connector->encoder->crtc);
+
+ if (!crtc)
+ return 0;
+
+ /* update hw state */
+ crtc->use_dithering = connector->use_dithering;
+ rval = crtc->set_dither(crtc);
+ if (rval)
+ return rval;
+
+ /* process command buffer */
+ display->update(display);
+
+ return 0;
}
- return false;
+ return -EINVAL;
}
static const struct drm_connector_funcs nv50_kms_connector_funcs = {
@@ -1105,12 +1231,48 @@ static const struct drm_connector_funcs nv50_kms_connector_funcs = {
.set_property = nv50_kms_connector_set_property
};
+static int nv50_kms_get_scaling_mode(struct drm_connector *drm_connector)
+{
+ struct nv50_connector *connector = NULL;
+ int drm_mode = 0;
+
+ if (!drm_connector) {
+ DRM_ERROR("drm_connector is NULL\n");
+ return 0;
+ }
+
+ connector = to_nv50_connector(drm_connector);
+
+ switch (connector->requested_scaling_mode) {
+ case SCALE_NON_GPU:
+ drm_mode = DRM_MODE_SCALE_NON_GPU;
+ break;
+ case SCALE_FULLSCREEN:
+ drm_mode = DRM_MODE_SCALE_FULLSCREEN;
+ break;
+ case SCALE_NOSCALE:
+ drm_mode = DRM_MODE_SCALE_NO_SCALE;
+ break;
+ case SCALE_ASPECT:
+ drm_mode = DRM_MODE_SCALE_ASPECT;
+ break;
+ default:
+ break;
+ }
+
+ return drm_mode;
+}
+
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;
+ /* Initialise some optional connector properties. */
+ drm_mode_create_scaling_mode_property(dev);
+ drm_mode_create_dithering_property(dev);
+
list_for_each_entry(connector, &display->connectors, item) {
struct drm_connector *drm_connector = to_nv50_kms_connector(connector);
uint32_t type = DRM_MODE_CONNECTOR_Unknown;
@@ -1154,6 +1316,13 @@ static int nv50_kms_connectors_init(struct drm_device *dev)
drm_connector_attach_property(drm_connector, dev->mode_config.dvi_i_select_subconnector_property, 0);
}
+ /* If supported in the future, it will have to use the scalers internally and not expose them. */
+ if (type != DRM_MODE_CONNECTOR_SVIDEO) {
+ drm_connector_attach_property(drm_connector, dev->mode_config.scaling_mode_property, nv50_kms_get_scaling_mode(drm_connector));
+ }
+
+ drm_connector_attach_property(drm_connector, dev->mode_config.dithering_mode_property, connector->use_dithering ? DRM_MODE_DITHERING_ON : DRM_MODE_DITHERING_OFF);
+
/* attach encoders, possibilities are analog + digital */
for (i = 0; i < 2; i++) {
struct drm_encoder *drm_encoder = NULL;
diff --git a/linux-core/nv50_kms_wrapper.h b/linux-core/nv50_kms_wrapper.h
index 5ac66522..60384804 100644
--- a/linux-core/nv50_kms_wrapper.h
+++ b/linux-core/nv50_kms_wrapper.h
@@ -87,7 +87,7 @@ struct nv50_kms_priv {
struct nv50_kms_priv *nv50_get_kms_priv(struct drm_device *dev);
void nv50_kms_connector_detect_all(struct drm_device *dev);
-bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector);
+bool nv50_kms_connector_get_digital(struct drm_connector *drm_connector);
int nv50_kms_init(struct drm_device *dev);
int nv50_kms_destroy(struct drm_device *dev);
diff --git a/linux-core/nv50_output.h b/linux-core/nv50_output.h
index a5faf050..ac6d714f 100644
--- a/linux-core/nv50_output.h
+++ b/linux-core/nv50_output.h
@@ -51,7 +51,7 @@ struct nv50_output {
int (*set_clock_mode) (struct nv50_output *output);
/* this is not a normal modeset call, it is a direct register write, so it's executed immediately */
int (*set_power_mode) (struct nv50_output *output, int mode);
- bool (*detect) (struct nv50_output *output);
+ int (*detect) (struct nv50_output *output);
int (*destroy) (struct nv50_output *output);
};