Skip to content

Commit

Permalink
Merge branch 'drm/next/adv7511' of git://linuxtv.org/pinchartl/fbdev …
Browse files Browse the repository at this point in the history
…into drm-next

adv7511 fixes.

* 'drm/next/adv7511' of git://linuxtv.org/pinchartl/fbdev:
  drm: adv7511: Refactor power management
  drm: adv7511: Fix nested sleep when reading EDID
  drm: adv7511: Fix DDC error interrupt handling
  • Loading branch information
Dave Airlie committed Mar 22, 2015
2 parents b3ede17 + c6169e4 commit e8b6fe6
Showing 1 changed file with 102 additions and 95 deletions.
197 changes: 102 additions & 95 deletions drivers/gpu/drm/i2c/adv7511.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ struct adv7511 {
struct regmap *regmap;
struct regmap *packet_memory_regmap;
enum drm_connector_status status;
int dpms_mode;
bool powered;

unsigned int f_tmds;

unsigned int current_edid_segment;
uint8_t edid_buf[256];
bool edid_read;

wait_queue_head_t wq;
struct drm_encoder *encoder;
Expand Down Expand Up @@ -357,6 +358,48 @@ static void adv7511_set_link_config(struct adv7511 *adv7511,
adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB;
}

static void adv7511_power_on(struct adv7511 *adv7511)
{
adv7511->current_edid_segment = -1;

regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
ADV7511_INT0_EDID_READY);
regmap_write(adv7511->regmap, ADV7511_REG_INT(1),
ADV7511_INT1_DDC_ERROR);
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, 0);

/*
* Per spec it is allowed to pulse the HDP signal to indicate that the
* EDID information has changed. Some monitors do this when they wakeup
* from standby or are enabled. When the HDP goes low the adv7511 is
* reset and the outputs are disabled which might cause the monitor to
* go to standby again. To avoid this we ignore the HDP pin for the
* first few seconds after enabling the output.
*/
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
ADV7511_REG_POWER2_HDP_SRC_MASK,
ADV7511_REG_POWER2_HDP_SRC_NONE);

/*
* Most of the registers are reset during power down or when HPD is low.
*/
regcache_sync(adv7511->regmap);

adv7511->powered = true;
}

static void adv7511_power_off(struct adv7511 *adv7511)
{
/* TODO: setup additional power down modes */
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN,
ADV7511_POWER_POWER_DOWN);
regcache_mark_dirty(adv7511->regmap);

adv7511->powered = false;
}

/* -----------------------------------------------------------------------------
* Interrupt and hotplug detection
*/
Expand All @@ -379,69 +422,71 @@ static bool adv7511_hpd(struct adv7511 *adv7511)
return false;
}

static irqreturn_t adv7511_irq_handler(int irq, void *devid)
{
struct adv7511 *adv7511 = devid;

if (adv7511_hpd(adv7511))
drm_helper_hpd_irq_event(adv7511->encoder->dev);

wake_up_all(&adv7511->wq);

return IRQ_HANDLED;
}

static unsigned int adv7511_is_interrupt_pending(struct adv7511 *adv7511,
unsigned int irq)
static int adv7511_irq_process(struct adv7511 *adv7511)
{
unsigned int irq0, irq1;
unsigned int pending;
int ret;

ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
if (ret < 0)
return 0;
return ret;

ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1);
if (ret < 0)
return 0;
return ret;

pending = (irq1 << 8) | irq0;
regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0);
regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1);

return pending & irq;
if (irq0 & ADV7511_INT0_HDP)
drm_helper_hpd_irq_event(adv7511->encoder->dev);

if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) {
adv7511->edid_read = true;

if (adv7511->i2c_main->irq)
wake_up_all(&adv7511->wq);
}

return 0;
}

static int adv7511_wait_for_interrupt(struct adv7511 *adv7511, int irq,
int timeout)
static irqreturn_t adv7511_irq_handler(int irq, void *devid)
{
struct adv7511 *adv7511 = devid;
int ret;

ret = adv7511_irq_process(adv7511);
return ret < 0 ? IRQ_NONE : IRQ_HANDLED;
}

/* -----------------------------------------------------------------------------
* EDID retrieval
*/

static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout)
{
unsigned int pending;
int ret;

if (adv7511->i2c_main->irq) {
ret = wait_event_interruptible_timeout(adv7511->wq,
adv7511_is_interrupt_pending(adv7511, irq),
msecs_to_jiffies(timeout));
if (ret <= 0)
return 0;
pending = adv7511_is_interrupt_pending(adv7511, irq);
adv7511->edid_read, msecs_to_jiffies(timeout));
} else {
if (timeout < 25)
timeout = 25;
do {
pending = adv7511_is_interrupt_pending(adv7511, irq);
if (pending)
for (; timeout > 0; timeout -= 25) {
ret = adv7511_irq_process(adv7511);
if (ret < 0)
break;

if (adv7511->edid_read)
break;

msleep(25);
timeout -= 25;
} while (timeout >= 25);
}
}

return pending;
return adv7511->edid_read ? 0 : -EIO;
}

/* -----------------------------------------------------------------------------
* EDID retrieval
*/

static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block,
size_t len)
{
Expand All @@ -463,19 +508,14 @@ static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block,
return ret;

if (status != 2) {
adv7511->edid_read = false;
regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT,
block);
ret = adv7511_wait_for_interrupt(adv7511,
ADV7511_INT0_EDID_READY |
ADV7511_INT1_DDC_ERROR, 200);

if (!(ret & ADV7511_INT0_EDID_READY))
return -EIO;
ret = adv7511_wait_for_edid(adv7511, 200);
if (ret < 0)
return ret;
}

regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
ADV7511_INT0_EDID_READY | ADV7511_INT1_DDC_ERROR);

/* Break this apart, hopefully more I2C controllers will
* support 64 byte transfers than 256 byte transfers
*/
Expand Down Expand Up @@ -526,17 +566,19 @@ static int adv7511_get_modes(struct drm_encoder *encoder,
unsigned int count;

/* Reading the EDID only works if the device is powered */
if (adv7511->dpms_mode != DRM_MODE_DPMS_ON) {
if (!adv7511->powered) {
regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
ADV7511_INT0_EDID_READY | ADV7511_INT1_DDC_ERROR);
ADV7511_INT0_EDID_READY);
regmap_write(adv7511->regmap, ADV7511_REG_INT(1),
ADV7511_INT1_DDC_ERROR);
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, 0);
adv7511->current_edid_segment = -1;
}

edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511);

if (adv7511->dpms_mode != DRM_MODE_DPMS_ON)
if (!adv7511->powered)
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN,
ADV7511_POWER_POWER_DOWN);
Expand All @@ -558,41 +600,10 @@ static void adv7511_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct adv7511 *adv7511 = encoder_to_adv7511(encoder);

switch (mode) {
case DRM_MODE_DPMS_ON:
adv7511->current_edid_segment = -1;

regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
ADV7511_INT0_EDID_READY | ADV7511_INT1_DDC_ERROR);
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, 0);
/*
* Per spec it is allowed to pulse the HDP signal to indicate
* that the EDID information has changed. Some monitors do this
* when they wakeup from standby or are enabled. When the HDP
* goes low the adv7511 is reset and the outputs are disabled
* which might cause the monitor to go to standby again. To
* avoid this we ignore the HDP pin for the first few seconds
* after enabling the output.
*/
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
ADV7511_REG_POWER2_HDP_SRC_MASK,
ADV7511_REG_POWER2_HDP_SRC_NONE);
/* Most of the registers are reset during power down or
* when HPD is low
*/
regcache_sync(adv7511->regmap);
break;
default:
/* TODO: setup additional power down modes */
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN,
ADV7511_POWER_POWER_DOWN);
regcache_mark_dirty(adv7511->regmap);
break;
}

adv7511->dpms_mode = mode;
if (mode == DRM_MODE_DPMS_ON)
adv7511_power_on(adv7511);
else
adv7511_power_off(adv7511);
}

static enum drm_connector_status
Expand Down Expand Up @@ -620,10 +631,9 @@ adv7511_encoder_detect(struct drm_encoder *encoder,
* there is a pending HPD interrupt and the cable is connected there was
* at least one transition from disconnected to connected and the chip
* has to be reinitialized. */
if (status == connector_status_connected && hpd &&
adv7511->dpms_mode == DRM_MODE_DPMS_ON) {
if (status == connector_status_connected && hpd && adv7511->powered) {
regcache_mark_dirty(adv7511->regmap);
adv7511_encoder_dpms(encoder, adv7511->dpms_mode);
adv7511_power_on(adv7511);
adv7511_get_modes(encoder, connector);
if (adv7511->status == connector_status_connected)
status = connector_status_disconnected;
Expand Down Expand Up @@ -858,7 +868,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
if (!adv7511)
return -ENOMEM;

adv7511->dpms_mode = DRM_MODE_DPMS_OFF;
adv7511->powered = false;
adv7511->status = connector_status_disconnected;

ret = adv7511_parse_dt(dev->of_node, &link_config);
Expand Down Expand Up @@ -918,10 +928,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
ADV7511_CEC_CTRL_POWER_DOWN);

regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, ADV7511_POWER_POWER_DOWN);

adv7511->current_edid_segment = -1;
adv7511_power_off(adv7511);

i2c_set_clientdata(i2c, adv7511);

Expand Down

0 comments on commit e8b6fe6

Please sign in to comment.