From c0c0d9eeeb8df43964601a2c4666f0c49bedacb4 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 11 Dec 2013 14:44:02 -0500 Subject: drm/msm: hdmi audio support Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/Makefile | 1 + drivers/gpu/drm/msm/hdmi/hdmi.c | 8 +- drivers/gpu/drm/msm/hdmi/hdmi.h | 25 +++ drivers/gpu/drm/msm/hdmi/hdmi_audio.c | 273 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 26 ++-- 5 files changed, 317 insertions(+), 16 deletions(-) create mode 100644 drivers/gpu/drm/msm/hdmi/hdmi_audio.c diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 4f977a593bea..5e1e6b0cd8ac 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -7,6 +7,7 @@ msm-y := \ adreno/adreno_gpu.o \ adreno/a3xx_gpu.o \ hdmi/hdmi.o \ + hdmi/hdmi_audio.o \ hdmi/hdmi_bridge.o \ hdmi/hdmi_connector.o \ hdmi/hdmi_i2c.o \ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index 6f1588aa9071..6048b6b2a8c7 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -67,6 +67,8 @@ void hdmi_destroy(struct kref *kref) if (hdmi->i2c) hdmi_i2c_destroy(hdmi->i2c); + platform_set_drvdata(hdmi->pdev, NULL); + put_device(&hdmi->pdev->dev); } @@ -102,6 +104,8 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) hdmi->config = config; hdmi->encoder = encoder; + hdmi_audio_infoframe_init(&hdmi->audio.infoframe); + /* not sure about which phy maps to which msm.. probably I miss some */ if (config->phy_init) hdmi->phy = config->phy_init(hdmi); @@ -228,6 +232,8 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) priv->bridges[priv->num_bridges++] = hdmi->bridge; priv->connectors[priv->num_connectors++] = hdmi->connector; + platform_set_drvdata(pdev, hdmi); + return hdmi; fail: @@ -305,7 +311,7 @@ static int hdmi_dev_probe(struct platform_device *pdev) config.ddc_data_gpio = 71; config.hpd_gpio = 72; config.mux_en_gpio = -1; - config.mux_sel_gpio = 13 + NR_GPIO_IRQS; + config.mux_sel_gpio = -1; } else if (cpu_is_msm8960() || cpu_is_msm8960ab()) { static const char *hpd_reg_names[] = {"8921_hdmi_mvs"}; config.phy_init = hdmi_phy_8960_init; diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index 41b29add70b1..9fafee6a3e43 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "msm_drv.h" #include "hdmi.xml.h" @@ -30,6 +31,12 @@ struct hdmi_phy; struct hdmi_platform_config; +struct hdmi_audio { + bool enabled; + struct hdmi_audio_infoframe infoframe; + int rate; +}; + struct hdmi { struct kref refcount; @@ -38,6 +45,13 @@ struct hdmi { const struct hdmi_platform_config *config; + /* audio state: */ + struct hdmi_audio audio; + + /* video state: */ + bool power_on; + unsigned long int pixclock; + void __iomem *mmio; struct regulator *hpd_regs[2]; @@ -131,6 +145,17 @@ struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi); struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi); struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi); +/* + * audio: + */ + +int hdmi_audio_update(struct hdmi *hdmi); +int hdmi_audio_info_setup(struct hdmi *hdmi, bool enabled, + uint32_t num_of_channels, uint32_t channel_allocation, + uint32_t level_shift, bool down_mix); +void hdmi_audio_set_sample_rate(struct hdmi *hdmi, int rate); + + /* * hdmi bridge: */ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c new file mode 100644 index 000000000000..872485f60134 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include "hdmi.h" + + +/* Supported HDMI Audio channels */ +#define MSM_HDMI_AUDIO_CHANNEL_2 0 +#define MSM_HDMI_AUDIO_CHANNEL_4 1 +#define MSM_HDMI_AUDIO_CHANNEL_6 2 +#define MSM_HDMI_AUDIO_CHANNEL_8 3 + +/* maps MSM_HDMI_AUDIO_CHANNEL_n consts used by audio driver to # of channels: */ +static int nchannels[] = { 2, 4, 6, 8 }; + +/* Supported HDMI Audio sample rates */ +#define MSM_HDMI_SAMPLE_RATE_32KHZ 0 +#define MSM_HDMI_SAMPLE_RATE_44_1KHZ 1 +#define MSM_HDMI_SAMPLE_RATE_48KHZ 2 +#define MSM_HDMI_SAMPLE_RATE_88_2KHZ 3 +#define MSM_HDMI_SAMPLE_RATE_96KHZ 4 +#define MSM_HDMI_SAMPLE_RATE_176_4KHZ 5 +#define MSM_HDMI_SAMPLE_RATE_192KHZ 6 +#define MSM_HDMI_SAMPLE_RATE_MAX 7 + + +struct hdmi_msm_audio_acr { + uint32_t n; /* N parameter for clock regeneration */ + uint32_t cts; /* CTS parameter for clock regeneration */ +}; + +struct hdmi_msm_audio_arcs { + unsigned long int pixclock; + struct hdmi_msm_audio_acr lut[MSM_HDMI_SAMPLE_RATE_MAX]; +}; + +#define HDMI_MSM_AUDIO_ARCS(pclk, ...) { (1000 * (pclk)), __VA_ARGS__ } + +/* Audio constants lookup table for hdmi_msm_audio_acr_setup */ +/* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */ +static const struct hdmi_msm_audio_arcs acr_lut[] = { + /* 25.200MHz */ + HDMI_MSM_AUDIO_ARCS(25200, { + {4096, 25200}, {6272, 28000}, {6144, 25200}, {12544, 28000}, + {12288, 25200}, {25088, 28000}, {24576, 25200} }), + /* 27.000MHz */ + HDMI_MSM_AUDIO_ARCS(27000, { + {4096, 27000}, {6272, 30000}, {6144, 27000}, {12544, 30000}, + {12288, 27000}, {25088, 30000}, {24576, 27000} }), + /* 27.027MHz */ + HDMI_MSM_AUDIO_ARCS(27030, { + {4096, 27027}, {6272, 30030}, {6144, 27027}, {12544, 30030}, + {12288, 27027}, {25088, 30030}, {24576, 27027} }), + /* 74.250MHz */ + HDMI_MSM_AUDIO_ARCS(74250, { + {4096, 74250}, {6272, 82500}, {6144, 74250}, {12544, 82500}, + {12288, 74250}, {25088, 82500}, {24576, 74250} }), + /* 148.500MHz */ + HDMI_MSM_AUDIO_ARCS(148500, { + {4096, 148500}, {6272, 165000}, {6144, 148500}, {12544, 165000}, + {12288, 148500}, {25088, 165000}, {24576, 148500} }), +}; + +static const struct hdmi_msm_audio_arcs *get_arcs(unsigned long int pixclock) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(acr_lut); i++) { + const struct hdmi_msm_audio_arcs *arcs = &acr_lut[i]; + if (arcs->pixclock == pixclock) + return arcs; + } + + return NULL; +} + +int hdmi_audio_update(struct hdmi *hdmi) +{ + struct hdmi_audio *audio = &hdmi->audio; + struct hdmi_audio_infoframe *info = &audio->infoframe; + const struct hdmi_msm_audio_arcs *arcs = NULL; + bool enabled = audio->enabled; + uint32_t acr_pkt_ctrl, vbi_pkt_ctrl, aud_pkt_ctrl; + uint32_t infofrm_ctrl, audio_config; + + DBG("audio: enabled=%d, channels=%d, channel_allocation=0x%x, " + "level_shift_value=%d, downmix_inhibit=%d, rate=%d", + audio->enabled, info->channels, info->channel_allocation, + info->level_shift_value, info->downmix_inhibit, audio->rate); + DBG("video: power_on=%d, pixclock=%lu", hdmi->power_on, hdmi->pixclock); + + if (enabled && !(hdmi->power_on && hdmi->pixclock)) { + DBG("disabling audio: no video"); + enabled = false; + } + + if (enabled) { + arcs = get_arcs(hdmi->pixclock); + if (!arcs) { + DBG("disabling audio: unsupported pixclock: %lu", + hdmi->pixclock); + enabled = false; + } + } + + /* Read first before writing */ + acr_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_ACR_PKT_CTRL); + vbi_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_VBI_PKT_CTRL); + aud_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_AUDIO_PKT_CTRL1); + infofrm_ctrl = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); + audio_config = hdmi_read(hdmi, REG_HDMI_AUDIO_CFG); + + /* Clear N/CTS selection bits */ + acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SELECT__MASK; + + if (enabled) { + uint32_t n, cts, multiplier; + enum hdmi_acr_cts select; + uint8_t buf[14]; + + n = arcs->lut[audio->rate].n; + cts = arcs->lut[audio->rate].cts; + + if ((MSM_HDMI_SAMPLE_RATE_192KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_176_4KHZ == audio->rate)) { + multiplier = 4; + n >>= 2; /* divide N by 4 and use multiplier */ + } else if ((MSM_HDMI_SAMPLE_RATE_96KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_88_2KHZ == audio->rate)) { + multiplier = 2; + n >>= 1; /* divide N by 2 and use multiplier */ + } else { + multiplier = 1; + } + + DBG("n=%u, cts=%u, multiplier=%u", n, cts, multiplier); + + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SOURCE; + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_AUDIO_PRIORITY; + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_N_MULTIPLIER(multiplier); + + if ((MSM_HDMI_SAMPLE_RATE_48KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_96KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_192KHZ == audio->rate)) + select = ACR_48; + else if ((MSM_HDMI_SAMPLE_RATE_44_1KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_88_2KHZ == audio->rate) || + (MSM_HDMI_SAMPLE_RATE_176_4KHZ == audio->rate)) + select = ACR_44; + else /* default to 32k */ + select = ACR_32; + + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SELECT(select); + + hdmi_write(hdmi, REG_HDMI_ACR_0(select - 1), + HDMI_ACR_0_CTS(cts)); + hdmi_write(hdmi, REG_HDMI_ACR_1(select - 1), + HDMI_ACR_1_N(n)); + + hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL2, + COND(info->channels != 2, HDMI_AUDIO_PKT_CTRL2_LAYOUT) | + HDMI_AUDIO_PKT_CTRL2_OVERRIDE); + + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_CONT; + acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SEND; + + /* configure infoframe: */ + hdmi_audio_infoframe_pack(info, buf, sizeof(buf)); + hdmi_write(hdmi, REG_HDMI_AUDIO_INFO0, + (buf[3] << 0) || (buf[4] << 8) || + (buf[5] << 16) || (buf[6] << 24)); + hdmi_write(hdmi, REG_HDMI_AUDIO_INFO1, + (buf[7] << 0) || (buf[8] << 8)); + + hdmi_write(hdmi, REG_HDMI_GC, 0); + + vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_ENABLE; + vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME; + + aud_pkt_ctrl |= HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND; + + infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND; + infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT; + infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE; + infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; + + audio_config &= ~HDMI_AUDIO_CFG_FIFO_WATERMARK__MASK; + audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4); + audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE; + } else { + hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE); + acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT; + acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND; + vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE; + vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME; + aud_pkt_ctrl &= ~HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND; + infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND; + infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT; + infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE; + infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; + audio_config &= ~HDMI_AUDIO_CFG_ENGINE_ENABLE; + } + + hdmi_write(hdmi, REG_HDMI_ACR_PKT_CTRL, acr_pkt_ctrl); + hdmi_write(hdmi, REG_HDMI_VBI_PKT_CTRL, vbi_pkt_ctrl); + hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL1, aud_pkt_ctrl); + hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, infofrm_ctrl); + + hdmi_write(hdmi, REG_HDMI_AUD_INT, + COND(enabled, HDMI_AUD_INT_AUD_FIFO_URUN_INT) | + COND(enabled, HDMI_AUD_INT_AUD_SAM_DROP_INT)); + + hdmi_write(hdmi, REG_HDMI_AUDIO_CFG, audio_config); + + + DBG("audio %sabled", enabled ? "en" : "dis"); + + return 0; +} + +int hdmi_audio_info_setup(struct hdmi *hdmi, bool enabled, + uint32_t num_of_channels, uint32_t channel_allocation, + uint32_t level_shift, bool down_mix) +{ + struct hdmi_audio *audio; + + if (!hdmi) + return -ENXIO; + + audio = &hdmi->audio; + + if (num_of_channels >= ARRAY_SIZE(nchannels)) + return -EINVAL; + + audio->enabled = enabled; + audio->infoframe.channels = nchannels[num_of_channels]; + audio->infoframe.channel_allocation = channel_allocation; + audio->infoframe.level_shift_value = level_shift; + audio->infoframe.downmix_inhibit = down_mix; + + return hdmi_audio_update(hdmi); +} + +void hdmi_audio_set_sample_rate(struct hdmi *hdmi, int rate) +{ + struct hdmi_audio *audio; + + if (!hdmi) + return; + + audio = &hdmi->audio; + + if ((rate < 0) || (rate >= MSM_HDMI_SAMPLE_RATE_MAX)) + return; + + audio->rate = rate; + hdmi_audio_update(hdmi); +} diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c index 7d10e55403c6..f6cf745c249e 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c @@ -19,11 +19,7 @@ struct hdmi_bridge { struct drm_bridge base; - struct hdmi *hdmi; - bool power_on; - - unsigned long int pixclock; }; #define to_hdmi_bridge(x) container_of(x, struct hdmi_bridge, base) @@ -52,8 +48,8 @@ static void power_on(struct drm_bridge *bridge) } if (config->pwr_clk_cnt > 0) { - DBG("pixclock: %lu", hdmi_bridge->pixclock); - ret = clk_set_rate(hdmi->pwr_clks[0], hdmi_bridge->pixclock); + DBG("pixclock: %lu", hdmi->pixclock); + ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock); if (ret) { dev_err(dev->dev, "failed to set pixel clk: %s (%d)\n", config->pwr_clk_names[0], ret); @@ -102,12 +98,13 @@ static void hdmi_bridge_pre_enable(struct drm_bridge *bridge) DBG("power up"); - if (!hdmi_bridge->power_on) { + if (!hdmi->power_on) { power_on(bridge); - hdmi_bridge->power_on = true; + hdmi->power_on = true; + hdmi_audio_update(hdmi); } - phy->funcs->powerup(phy, hdmi_bridge->pixclock); + phy->funcs->powerup(phy, hdmi->pixclock); hdmi_set_mode(hdmi, true); } @@ -129,9 +126,10 @@ static void hdmi_bridge_post_disable(struct drm_bridge *bridge) hdmi_set_mode(hdmi, false); phy->funcs->powerdown(phy); - if (hdmi_bridge->power_on) { + if (hdmi->power_on) { power_off(bridge); - hdmi_bridge->power_on = false; + hdmi->power_on = false; + hdmi_audio_update(hdmi); } } @@ -146,7 +144,7 @@ static void hdmi_bridge_mode_set(struct drm_bridge *bridge, mode = adjusted_mode; - hdmi_bridge->pixclock = mode->clock * 1000; + hdmi->pixclock = mode->clock * 1000; hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1; @@ -194,9 +192,7 @@ static void hdmi_bridge_mode_set(struct drm_bridge *bridge, DBG("frame_ctrl=%08x", frame_ctrl); hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl); - // TODO until we have audio, this might be safest: - if (hdmi->hdmi_mode) - hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE); + hdmi_audio_update(hdmi); } static const struct drm_bridge_funcs hdmi_bridge_funcs = { -- cgit v1.2.3 From 5b6ef08e4b4e1bcb6b3ac4172c054e4462e2c767 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sun, 22 Dec 2013 10:29:43 -0500 Subject: drm/msm: add hang_debug module param msm.hang_debug=y will dump out current register values if the gpu locks up, for easier debugging. Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 34 ++++++++++++++++++++++++++++++++- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 18 +++++++++++++++++ drivers/gpu/drm/msm/adreno/adreno_gpu.h | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index 461df93e825e..8b6fb847789e 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -35,6 +35,12 @@ A3XX_INT0_CP_AHB_ERROR_HALT | \ A3XX_INT0_UCHE_OOB_ACCESS) + +static bool hang_debug = false; +MODULE_PARM_DESC(hang_debug, "Dump registers when hang is detected (can be slow!)"); +module_param_named(hang_debug, hang_debug, bool, 0600); +static void a3xx_dump(struct msm_gpu *gpu); + static struct platform_device *a3xx_pdev; static void a3xx_me_init(struct msm_gpu *gpu) @@ -291,6 +297,9 @@ static int a3xx_hw_init(struct msm_gpu *gpu) static void a3xx_recover(struct msm_gpu *gpu) { + /* dump registers before resetting gpu, if enabled: */ + if (hang_debug) + a3xx_dump(gpu); gpu_write(gpu, REG_A3XX_RBBM_SW_RESET_CMD, 1); gpu_read(gpu, REG_A3XX_RBBM_SW_RESET_CMD); gpu_write(gpu, REG_A3XX_RBBM_SW_RESET_CMD, 0); @@ -352,7 +361,6 @@ static irqreturn_t a3xx_irq(struct msm_gpu *gpu) return IRQ_HANDLED; } -#ifdef CONFIG_DEBUG_FS static const unsigned int a3xx_registers[] = { 0x0000, 0x0002, 0x0010, 0x0012, 0x0018, 0x0018, 0x0020, 0x0027, 0x0029, 0x002b, 0x002e, 0x0033, 0x0040, 0x0042, 0x0050, 0x005c, @@ -392,6 +400,7 @@ static const unsigned int a3xx_registers[] = { 0x303c, 0x303c, 0x305e, 0x305f, }; +#ifdef CONFIG_DEBUG_FS static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m) { int i; @@ -415,6 +424,29 @@ static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m) } #endif +/* would be nice to not have to duplicate the _show() stuff with printk(): */ +static void a3xx_dump(struct msm_gpu *gpu) +{ + int i; + + adreno_dump(gpu); + printk("status: %08x\n", + gpu_read(gpu, REG_A3XX_RBBM_STATUS)); + + /* dump these out in a form that can be parsed by demsm: */ + printk("IO:region %s 00000000 00020000\n", gpu->name); + for (i = 0; i < ARRAY_SIZE(a3xx_registers); i += 2) { + uint32_t start = a3xx_registers[i]; + uint32_t end = a3xx_registers[i+1]; + uint32_t addr; + + for (addr = start; addr <= end; addr++) { + uint32_t val = gpu_read(gpu, addr); + printk("IO:R %08x %08x\n", addr<<2, val); + } + } +} + static const struct adreno_gpu_funcs funcs = { .base = { .get_param = adreno_get_param, diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index d321099abdd4..cf6eb976dda7 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -260,6 +260,24 @@ void adreno_show(struct msm_gpu *gpu, struct seq_file *m) } #endif +/* would be nice to not have to duplicate the _show() stuff with printk(): */ +void adreno_dump(struct msm_gpu *gpu) +{ + struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); + + printk("revision: %d (%d.%d.%d.%d)\n", + adreno_gpu->info->revn, adreno_gpu->rev.core, + adreno_gpu->rev.major, adreno_gpu->rev.minor, + adreno_gpu->rev.patchid); + + printk("fence: %d/%d\n", adreno_gpu->memptrs->fence, + gpu->submitted_fence); + printk("rptr: %d\n", adreno_gpu->memptrs->rptr); + printk("wptr: %d\n", adreno_gpu->memptrs->wptr); + printk("rb wptr: %d\n", get_wptr(gpu->rb)); + +} + void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.h b/drivers/gpu/drm/msm/adreno/adreno_gpu.h index ca11ea4da165..e16200ddc60b 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.h +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.h @@ -114,6 +114,7 @@ void adreno_idle(struct msm_gpu *gpu); #ifdef CONFIG_DEBUG_FS void adreno_show(struct msm_gpu *gpu, struct seq_file *m); #endif +void adreno_dump(struct msm_gpu *gpu); void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords); int adreno_gpu_init(struct drm_device *drm, struct platform_device *pdev, -- cgit v1.2.3 From 0963756fe51313a1e2d76885cd21624d3b2cfbf2 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 11 Jan 2014 16:11:59 -0500 Subject: drm/msm: spin helper Helper macro to simplify places where we need to poll with timeout waiting for gpu. Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 14 +++-------- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 41 ++++++++++++--------------------- drivers/gpu/drm/msm/adreno/adreno_gpu.h | 15 +++++++++++- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index 8b6fb847789e..59ed7620fc1a 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -326,21 +326,13 @@ static void a3xx_destroy(struct msm_gpu *gpu) static void a3xx_idle(struct msm_gpu *gpu) { - unsigned long t; - /* wait for ringbuffer to drain: */ adreno_idle(gpu); - t = jiffies + ADRENO_IDLE_TIMEOUT; - /* then wait for GPU to finish: */ - do { - uint32_t rbbm_status = gpu_read(gpu, REG_A3XX_RBBM_STATUS); - if (!(rbbm_status & A3XX_RBBM_STATUS_GPU_BUSY)) - return; - } while(time_before(jiffies, t)); - - DRM_ERROR("timeout waiting for %s to idle!\n", gpu->name); + if (spin_until(!(gpu_read(gpu, REG_A3XX_RBBM_STATUS) & + A3XX_RBBM_STATUS_GPU_BUSY))) + DRM_ERROR("%s: timeout waiting for GPU to idle!\n", gpu->name); /* TODO maybe we need to reset GPU here to recover from hang? */ } diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index cf6eb976dda7..7a11563379b8 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -225,19 +225,11 @@ void adreno_flush(struct msm_gpu *gpu) void adreno_idle(struct msm_gpu *gpu) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); - uint32_t rptr, wptr = get_wptr(gpu->rb); - unsigned long t; - - t = jiffies + ADRENO_IDLE_TIMEOUT; - - /* then wait for CP to drain ringbuffer: */ - do { - rptr = adreno_gpu->memptrs->rptr; - if (rptr == wptr) - return; - } while(time_before(jiffies, t)); + uint32_t wptr = get_wptr(gpu->rb); - DRM_ERROR("%s: timeout waiting to drain ringbuffer!\n", gpu->name); + /* wait for CP to drain ringbuffer: */ + if (spin_until(adreno_gpu->memptrs->rptr == wptr)) + DRM_ERROR("%s: timeout waiting to drain ringbuffer!\n", gpu->name); /* TODO maybe we need to reset GPU here to recover from hang? */ } @@ -278,22 +270,19 @@ void adreno_dump(struct msm_gpu *gpu) } -void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords) +static uint32_t ring_freewords(struct msm_gpu *gpu) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); - uint32_t freedwords; - unsigned long t = jiffies + ADRENO_IDLE_TIMEOUT; - do { - uint32_t size = gpu->rb->size / 4; - uint32_t wptr = get_wptr(gpu->rb); - uint32_t rptr = adreno_gpu->memptrs->rptr; - freedwords = (rptr + (size - 1) - wptr) % size; - - if (time_after(jiffies, t)) { - DRM_ERROR("%s: timeout waiting for ringbuffer space\n", gpu->name); - break; - } - } while(freedwords < ndwords); + uint32_t size = gpu->rb->size / 4; + uint32_t wptr = get_wptr(gpu->rb); + uint32_t rptr = adreno_gpu->memptrs->rptr; + return (rptr + (size - 1) - wptr) % size; +} + +void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords) +{ + if (spin_until(ring_freewords(gpu) >= ndwords)) + DRM_ERROR("%s: timeout waiting for ringbuffer space\n", gpu->name); } static const char *iommu_ports[] = { diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.h b/drivers/gpu/drm/msm/adreno/adreno_gpu.h index e16200ddc60b..63c36ce33020 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.h +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.h @@ -76,7 +76,20 @@ struct adreno_platform_config { #endif }; -#define ADRENO_IDLE_TIMEOUT (20 * 1000) +#define ADRENO_IDLE_TIMEOUT msecs_to_jiffies(1000) + +#define spin_until(X) ({ \ + int __ret = -ETIMEDOUT; \ + unsigned long __t = jiffies + ADRENO_IDLE_TIMEOUT; \ + do { \ + if (X) { \ + __ret = 0; \ + break; \ + } \ + } while (time_before(jiffies, __t)); \ + __ret; \ +}) + static inline bool adreno_is_a3xx(struct adreno_gpu *gpu) { -- cgit v1.2.3 From 37d77c3ab589bf8e3160a33b651fe6dd7439ba48 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 11 Jan 2014 16:25:08 -0500 Subject: drm/msm: crank down gpu when inactive Shut down the clks when the gpu has nothing to do. A short inactivity timer is used to provide a low pass filter for power transitions. Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 10 +++++ drivers/gpu/drm/msm/msm_drv.c | 7 ++- drivers/gpu/drm/msm/msm_gpu.c | 85 +++++++++++++++++++++++++++++++++-- drivers/gpu/drm/msm/msm_gpu.h | 16 ++++++- 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index 59ed7620fc1a..e6cb2bc40ebe 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -395,9 +395,15 @@ static const unsigned int a3xx_registers[] = { #ifdef CONFIG_DEBUG_FS static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m) { + struct drm_device *dev = gpu->dev; int i; adreno_show(gpu, m); + + mutex_lock(&dev->struct_mutex); + + gpu->funcs->pm_resume(gpu); + seq_printf(m, "status: %08x\n", gpu_read(gpu, REG_A3XX_RBBM_STATUS)); @@ -413,6 +419,10 @@ static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m) seq_printf(m, "IO:R %08x %08x\n", addr<<2, val); } } + + gpu->funcs->pm_suspend(gpu); + + mutex_unlock(&dev->struct_mutex); } #endif diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index e6adafc7eff3..e913efa4ea0a 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -311,7 +311,6 @@ static void load_gpu(struct drm_device *dev) gpu = NULL; /* not fatal */ } - mutex_unlock(&dev->struct_mutex); if (gpu) { int ret; @@ -321,10 +320,16 @@ static void load_gpu(struct drm_device *dev) dev_err(dev->dev, "gpu hw init failed: %d\n", ret); gpu->funcs->destroy(gpu); gpu = NULL; + } else { + /* give inactive pm a chance to kick in: */ + msm_gpu_retire(gpu); } + } priv->gpu = gpu; + + mutex_unlock(&dev->struct_mutex); } static int msm_open(struct drm_device *dev, struct drm_file *file) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 0cfe3f426ee4..3e667ca1f2b9 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -154,9 +154,18 @@ static int disable_axi(struct msm_gpu *gpu) int msm_gpu_pm_resume(struct msm_gpu *gpu) { + struct drm_device *dev = gpu->dev; int ret; - DBG("%s", gpu->name); + DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt); + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (gpu->active_cnt++ > 0) + return 0; + + if (WARN_ON(gpu->active_cnt <= 0)) + return -EINVAL; ret = enable_pwrrail(gpu); if (ret) @@ -175,9 +184,18 @@ int msm_gpu_pm_resume(struct msm_gpu *gpu) int msm_gpu_pm_suspend(struct msm_gpu *gpu) { + struct drm_device *dev = gpu->dev; int ret; - DBG("%s", gpu->name); + DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt); + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (--gpu->active_cnt > 0) + return 0; + + if (WARN_ON(gpu->active_cnt < 0)) + return -EINVAL; ret = disable_axi(gpu); if (ret) @@ -194,6 +212,55 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu) return 0; } +/* + * Inactivity detection (for suspend): + */ + +static void inactive_worker(struct work_struct *work) +{ + struct msm_gpu *gpu = container_of(work, struct msm_gpu, inactive_work); + struct drm_device *dev = gpu->dev; + + if (gpu->inactive) + return; + + DBG("%s: inactive!\n", gpu->name); + mutex_lock(&dev->struct_mutex); + if (!(msm_gpu_active(gpu) || gpu->inactive)) { + disable_axi(gpu); + disable_clk(gpu); + gpu->inactive = true; + } + mutex_unlock(&dev->struct_mutex); +} + +static void inactive_handler(unsigned long data) +{ + struct msm_gpu *gpu = (struct msm_gpu *)data; + struct msm_drm_private *priv = gpu->dev->dev_private; + + queue_work(priv->wq, &gpu->inactive_work); +} + +/* cancel inactive timer and make sure we are awake: */ +static void inactive_cancel(struct msm_gpu *gpu) +{ + DBG("%s", gpu->name); + del_timer(&gpu->inactive_timer); + if (gpu->inactive) { + enable_clk(gpu); + enable_axi(gpu); + gpu->inactive = false; + } +} + +static void inactive_start(struct msm_gpu *gpu) +{ + DBG("%s", gpu->name); + mod_timer(&gpu->inactive_timer, + round_jiffies_up(jiffies + DRM_MSM_INACTIVE_JIFFIES)); +} + /* * Hangcheck detection for locked gpu: */ @@ -206,7 +273,10 @@ static void recover_worker(struct work_struct *work) dev_err(dev->dev, "%s: hangcheck recover!\n", gpu->name); mutex_lock(&dev->struct_mutex); - gpu->funcs->recover(gpu); + if (msm_gpu_active(gpu)) { + inactive_cancel(gpu); + gpu->funcs->recover(gpu); + } mutex_unlock(&dev->struct_mutex); msm_gpu_retire(gpu); @@ -281,6 +351,9 @@ static void retire_worker(struct work_struct *work) } mutex_unlock(&dev->struct_mutex); + + if (!msm_gpu_active(gpu)) + inactive_start(gpu); } /* call from irq handler to schedule work to retire bo's */ @@ -302,6 +375,8 @@ int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit, gpu->submitted_fence = submit->fence; + inactive_cancel(gpu); + ret = gpu->funcs->submit(gpu, submit, ctx); priv->lastctx = ctx; @@ -357,11 +432,15 @@ int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev, gpu->dev = drm; gpu->funcs = funcs; gpu->name = name; + gpu->inactive = true; INIT_LIST_HEAD(&gpu->active_list); INIT_WORK(&gpu->retire_work, retire_worker); + INIT_WORK(&gpu->inactive_work, inactive_worker); INIT_WORK(&gpu->recover_work, recover_worker); + setup_timer(&gpu->inactive_timer, inactive_handler, + (unsigned long)gpu); setup_timer(&gpu->hangcheck_timer, hangcheck_handler, (unsigned long)gpu); diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h index 458db8c64c28..fad27008922f 100644 --- a/drivers/gpu/drm/msm/msm_gpu.h +++ b/drivers/gpu/drm/msm/msm_gpu.h @@ -72,6 +72,10 @@ struct msm_gpu { uint32_t submitted_fence; + /* is gpu powered/active? */ + int active_cnt; + bool inactive; + /* worker for handling active-list retiring: */ struct work_struct retire_work; @@ -91,7 +95,12 @@ struct msm_gpu { uint32_t bsc; #endif - /* Hang Detction: */ + /* Hang and Inactivity Detection: + */ +#define DRM_MSM_INACTIVE_PERIOD 66 /* in ms (roughly four frames) */ +#define DRM_MSM_INACTIVE_JIFFIES msecs_to_jiffies(DRM_MSM_INACTIVE_PERIOD) + struct timer_list inactive_timer; + struct work_struct inactive_work; #define DRM_MSM_HANGCHECK_PERIOD 500 /* in ms */ #define DRM_MSM_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_MSM_HANGCHECK_PERIOD) struct timer_list hangcheck_timer; @@ -99,6 +108,11 @@ struct msm_gpu { struct work_struct recover_work; }; +static inline bool msm_gpu_active(struct msm_gpu *gpu) +{ + return gpu->submitted_fence > gpu->funcs->last_fence(gpu); +} + static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data) { msm_writel(data, gpu->mmio + (reg << 2)); -- cgit v1.2.3 From 4e1cbaa3eb1c051d330f0586045bb719a8b9a8ae Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Tue, 4 Feb 2014 14:16:04 -0500 Subject: drm/msm: add chip-id param Some of the w/a or different behavior of userspace blob driver seem to be keyed to gpu patch revision, rather than gpu-id. So expose the full chip-id to userspace so it can DTRT. Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 6 ++++++ include/uapi/drm/msm_drm.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 7a11563379b8..28ca8cd8b09e 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -73,6 +73,12 @@ int adreno_get_param(struct msm_gpu *gpu, uint32_t param, uint64_t *value) case MSM_PARAM_GMEM_SIZE: *value = adreno_gpu->gmem; return 0; + case MSM_PARAM_CHIP_ID: + *value = adreno_gpu->rev.patchid | + (adreno_gpu->rev.minor << 8) | + (adreno_gpu->rev.major << 16) | + (adreno_gpu->rev.core << 24); + return 0; default: DBG("%s: invalid param: %u", gpu->name, param); return -EINVAL; diff --git a/include/uapi/drm/msm_drm.h b/include/uapi/drm/msm_drm.h index d3c62074016d..bf91a78a0b0e 100644 --- a/include/uapi/drm/msm_drm.h +++ b/include/uapi/drm/msm_drm.h @@ -50,6 +50,7 @@ struct drm_msm_timespec { #define MSM_PARAM_GPU_ID 0x01 #define MSM_PARAM_GMEM_SIZE 0x02 +#define MSM_PARAM_CHIP_ID 0x03 struct drm_msm_param { uint32_t pipe; /* in, MSM_PIPE_x */ -- cgit v1.2.3 From 060530f1ea6740eb767085008d183f89ccdd289c Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Mon, 3 Mar 2014 14:19:12 -0500 Subject: drm/msm: use componentised device support Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 49 ++++++++++----- drivers/gpu/drm/msm/hdmi/hdmi.c | 44 ++++++++----- drivers/gpu/drm/msm/msm_drv.c | 112 ++++++++++++++++++++++++++++++++-- drivers/gpu/drm/msm/msm_drv.h | 4 ++ 4 files changed, 176 insertions(+), 33 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index e6cb2bc40ebe..f20fbde5dc49 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -41,8 +41,6 @@ MODULE_PARM_DESC(hang_debug, "Dump registers when hang is detected (can be slow! module_param_named(hang_debug, hang_debug, bool, 0600); static void a3xx_dump(struct msm_gpu *gpu); -static struct platform_device *a3xx_pdev; - static void a3xx_me_init(struct msm_gpu *gpu) { struct msm_ringbuffer *ring = gpu->rb; @@ -320,7 +318,6 @@ static void a3xx_destroy(struct msm_gpu *gpu) ocmem_free(OCMEM_GRAPHICS, a3xx_gpu->ocmem_hdl); #endif - put_device(&a3xx_gpu->pdev->dev); kfree(a3xx_gpu); } @@ -473,7 +470,8 @@ struct msm_gpu *a3xx_gpu_init(struct drm_device *dev) struct a3xx_gpu *a3xx_gpu = NULL; struct adreno_gpu *adreno_gpu; struct msm_gpu *gpu; - struct platform_device *pdev = a3xx_pdev; + struct msm_drm_private *priv = dev->dev_private; + struct platform_device *pdev = priv->gpu_pdev; struct adreno_platform_config *config; int ret; @@ -494,7 +492,6 @@ struct msm_gpu *a3xx_gpu_init(struct drm_device *dev) adreno_gpu = &a3xx_gpu->base; gpu = &adreno_gpu->base; - get_device(&pdev->dev); a3xx_gpu->pdev = pdev; gpu->fast_rate = config->fast_rate; @@ -556,17 +553,24 @@ fail: # include #endif -static int a3xx_probe(struct platform_device *pdev) +static void set_gpu_pdev(struct drm_device *dev, + struct platform_device *pdev) +{ + struct msm_drm_private *priv = dev->dev_private; + priv->gpu_pdev = pdev; +} + +static int a3xx_bind(struct device *dev, struct device *master, void *data) { static struct adreno_platform_config config = {}; #ifdef CONFIG_OF - struct device_node *child, *node = pdev->dev.of_node; + struct device_node *child, *node = dev->of_node; u32 val; int ret; ret = of_property_read_u32(node, "qcom,chipid", &val); if (ret) { - dev_err(&pdev->dev, "could not find chipid: %d\n", ret); + dev_err(dev, "could not find chipid: %d\n", ret); return ret; } @@ -582,7 +586,7 @@ static int a3xx_probe(struct platform_device *pdev) for_each_child_of_node(child, pwrlvl) { ret = of_property_read_u32(pwrlvl, "qcom,gpu-freq", &val); if (ret) { - dev_err(&pdev->dev, "could not find gpu-freq: %d\n", ret); + dev_err(dev, "could not find gpu-freq: %d\n", ret); return ret; } config.fast_rate = max(config.fast_rate, val); @@ -592,12 +596,12 @@ static int a3xx_probe(struct platform_device *pdev) } if (!config.fast_rate) { - dev_err(&pdev->dev, "could not find clk rates\n"); + dev_err(dev, "could not find clk rates\n"); return -ENXIO; } #else - struct kgsl_device_platform_data *pdata = pdev->dev.platform_data; + struct kgsl_device_platform_data *pdata = dev->platform_data; uint32_t version = socinfo_get_version(); if (cpu_is_apq8064ab()) { config.fast_rate = 450000000; @@ -643,14 +647,30 @@ static int a3xx_probe(struct platform_device *pdev) config.bus_scale_table = pdata->bus_scale_table; # endif #endif - pdev->dev.platform_data = &config; - a3xx_pdev = pdev; + dev->platform_data = &config; + set_gpu_pdev(dev_get_drvdata(master), to_platform_device(dev)); return 0; } +static void a3xx_unbind(struct device *dev, struct device *master, + void *data) +{ + set_gpu_pdev(dev_get_drvdata(master), NULL); +} + +static const struct component_ops a3xx_ops = { + .bind = a3xx_bind, + .unbind = a3xx_unbind, +}; + +static int a3xx_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &a3xx_ops); +} + static int a3xx_remove(struct platform_device *pdev) { - a3xx_pdev = NULL; + component_del(&pdev->dev, &a3xx_ops); return 0; } @@ -658,7 +678,6 @@ static const struct of_device_id dt_match[] = { { .compatible = "qcom,kgsl-3d0" }, {} }; -MODULE_DEVICE_TABLE(of, dt_match); static struct platform_driver a3xx_driver = { .probe = a3xx_probe, diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index 6048b6b2a8c7..ae750f6928c1 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -17,8 +17,6 @@ #include "hdmi.h" -static struct platform_device *hdmi_pdev; - void hdmi_set_mode(struct hdmi *hdmi, bool power_on) { uint32_t ctrl = 0; @@ -68,8 +66,6 @@ void hdmi_destroy(struct kref *kref) hdmi_i2c_destroy(hdmi->i2c); platform_set_drvdata(hdmi->pdev, NULL); - - put_device(&hdmi->pdev->dev); } /* initialize connector */ @@ -77,7 +73,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) { struct hdmi *hdmi = NULL; struct msm_drm_private *priv = dev->dev_private; - struct platform_device *pdev = hdmi_pdev; + struct platform_device *pdev = priv->hdmi_pdev; struct hdmi_platform_config *config; int i, ret; @@ -97,8 +93,6 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) kref_init(&hdmi->refcount); - get_device(&pdev->dev); - hdmi->dev = dev; hdmi->pdev = pdev; hdmi->config = config; @@ -255,17 +249,24 @@ fail: #include -static int hdmi_dev_probe(struct platform_device *pdev) +static void set_hdmi_pdev(struct drm_device *dev, + struct platform_device *pdev) +{ + struct msm_drm_private *priv = dev->dev_private; + priv->hdmi_pdev = pdev; +} + +static int hdmi_bind(struct device *dev, struct device *master, void *data) { static struct hdmi_platform_config config = {}; #ifdef CONFIG_OF - struct device_node *of_node = pdev->dev.of_node; + struct device_node *of_node = dev->of_node; int get_gpio(const char *name) { int gpio = of_get_named_gpio(of_node, name, 0); if (gpio < 0) { - dev_err(&pdev->dev, "failed to get gpio: %s (%d)\n", + dev_err(dev, "failed to get gpio: %s (%d)\n", name, gpio); gpio = -1; } @@ -342,14 +343,30 @@ static int hdmi_dev_probe(struct platform_device *pdev) config.mux_sel_gpio = -1; } #endif - pdev->dev.platform_data = &config; - hdmi_pdev = pdev; + dev->platform_data = &config; + set_hdmi_pdev(dev_get_drvdata(master), to_platform_device(dev)); return 0; } +static void hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + set_hdmi_pdev(dev_get_drvdata(master), NULL); +} + +static const struct component_ops hdmi_ops = { + .bind = hdmi_bind, + .unbind = hdmi_unbind, +}; + +static int hdmi_dev_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &hdmi_ops); +} + static int hdmi_dev_remove(struct platform_device *pdev) { - hdmi_pdev = NULL; + component_del(&pdev->dev, &hdmi_ops); return 0; } @@ -357,7 +374,6 @@ static const struct of_device_id dt_match[] = { { .compatible = "qcom,hdmi-tx" }, {} }; -MODULE_DEVICE_TABLE(of, dt_match); static struct platform_driver hdmi_driver = { .probe = hdmi_dev_probe, diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index e913efa4ea0a..7a7421fb02b4 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -56,6 +56,10 @@ static char *vram; MODULE_PARM_DESC(vram, "Configure VRAM size (for devices without IOMMU/GPUMMU"); module_param(vram, charp, 0); +/* + * Util/helpers: + */ + void __iomem *msm_ioremap(struct platform_device *pdev, const char *name, const char *dbgname) { @@ -143,6 +147,8 @@ static int msm_unload(struct drm_device *dev) priv->vram.paddr, &attrs); } + component_unbind_all(dev->dev, dev); + dev->dev_private = NULL; kfree(priv); @@ -175,6 +181,7 @@ static int msm_load(struct drm_device *dev, unsigned long flags) struct msm_kms *kms; int ret; + priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { dev_err(dev->dev, "failed to allocate private data\n"); @@ -226,6 +233,13 @@ static int msm_load(struct drm_device *dev, unsigned long flags) (uint32_t)(priv->vram.paddr + size)); } + platform_set_drvdata(pdev, dev); + + /* Bind all our sub-components: */ + ret = component_bind_all(dev->dev, dev); + if (ret) + return ret; + switch (get_mdp_ver(pdev)) { case 4: kms = mdp4_kms_init(dev); @@ -281,8 +295,6 @@ static int msm_load(struct drm_device *dev, unsigned long flags) goto fail; } - platform_set_drvdata(pdev, dev); - #ifdef CONFIG_DRM_MSM_FBDEV priv->fbdev = msm_fbdev_init(dev); #endif @@ -823,6 +835,98 @@ static const struct dev_pm_ops msm_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(msm_pm_suspend, msm_pm_resume) }; +/* + * Componentized driver support: + */ + +#ifdef CONFIG_OF +/* NOTE: the CONFIG_OF case duplicates the same code as exynos or imx + * (or probably any other).. so probably some room for some helpers + */ +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int msm_drm_add_components(struct device *master, struct master *m) +{ + struct device_node *np = master->of_node; + unsigned i; + int ret; + + for (i = 0; ; i++) { + struct device_node *node; + + node = of_parse_phandle(np, "connectors", i); + if (!node) + break; + + ret = component_master_add_child(m, compare_of, node); + of_node_put(node); + + if (ret) + return ret; + } + return 0; +} +#else +static int compare_dev(struct device *dev, void *data) +{ + return dev == data; +} + +static int msm_drm_add_components(struct device *master, struct master *m) +{ + /* For non-DT case, it kinda sucks. We don't actually have a way + * to know whether or not we are waiting for certain devices (or if + * they are simply not present). But for non-DT we only need to + * care about apq8064/apq8060/etc (all mdp4/a3xx): + */ + static const char *devnames[] = { + "hdmi_msm.0", "kgsl-3d0.0", + }; + int i; + + DBG("Adding components.."); + + for (i = 0; i < ARRAY_SIZE(devnames); i++) { + struct device *dev; + int ret; + + dev = bus_find_device_by_name(&platform_bus_type, + NULL, devnames[i]); + if (!dev) { + dev_info(master, "still waiting for %s\n", devnames[i]); + return -EPROBE_DEFER; + } + + ret = component_master_add_child(m, compare_dev, dev); + if (ret) { + DBG("could not add child: %d", ret); + return ret; + } + } + + return 0; +} +#endif + +static int msm_drm_bind(struct device *dev) +{ + return drm_platform_init(&msm_driver, to_platform_device(dev)); +} + +static void msm_drm_unbind(struct device *dev) +{ + drm_put_dev(platform_get_drvdata(to_platform_device(dev))); +} + +static const struct component_master_ops msm_drm_ops = { + .add_components = msm_drm_add_components, + .bind = msm_drm_bind, + .unbind = msm_drm_unbind, +}; + /* * Platform driver: */ @@ -830,12 +934,12 @@ static const struct dev_pm_ops msm_pm_ops = { static int msm_pdev_probe(struct platform_device *pdev) { pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); - return drm_platform_init(&msm_driver, pdev); + return component_master_add(&pdev->dev, &msm_drm_ops); } static int msm_pdev_remove(struct platform_device *pdev) { - drm_put_dev(platform_get_drvdata(pdev)); + component_master_del(&pdev->dev, &msm_drm_ops); return 0; } diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 3d63269c5b29..9d10ee0b5aac 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,9 @@ struct msm_drm_private { struct msm_kms *kms; + /* subordinate devices, if present: */ + struct platform_device *hdmi_pdev, *gpu_pdev; + /* when we have more than one 'msm_gpu' these need to be an array: */ struct msm_gpu *gpu; struct msm_file_private *lastctx; -- cgit v1.2.3 From 93ddb0d3b022dfbd963f243bd01741643cebfb28 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Mon, 3 Mar 2014 09:42:33 -0500 Subject: drm/msm: validate flags, etc After reading a nice article on LWN[1], I went back and double checked my handling of invalid-input checking. Turns out there were a couple places I had missed. Since the driver is fairly young, and the devices it supports are really only just barely usable for basic stuff (serial console) with an upstream kernel, I think we should fix this now and revert specific parts of this patch later in the unlikely event that a regression is reported. [1] https://lwn.net/Articles/588444/ Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_drv.c | 20 +++++++++++++++++++- drivers/gpu/drm/msm/msm_gem_submit.c | 15 +++++++++++++-- include/uapi/drm/msm_drm.h | 11 +++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 7a7421fb02b4..f9de156b9e65 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -664,6 +664,12 @@ static int msm_ioctl_gem_new(struct drm_device *dev, void *data, struct drm_file *file) { struct drm_msm_gem_new *args = data; + + if (args->flags & ~MSM_BO_FLAGS) { + DRM_ERROR("invalid flags: %08x\n", args->flags); + return -EINVAL; + } + return msm_gem_new_handle(dev, file, args->size, args->flags, &args->handle); } @@ -677,6 +683,11 @@ static int msm_ioctl_gem_cpu_prep(struct drm_device *dev, void *data, struct drm_gem_object *obj; int ret; + if (args->op & ~MSM_PREP_FLAGS) { + DRM_ERROR("invalid op: %08x\n", args->op); + return -EINVAL; + } + obj = drm_gem_object_lookup(dev, file, args->handle); if (!obj) return -ENOENT; @@ -731,7 +742,14 @@ static int msm_ioctl_wait_fence(struct drm_device *dev, void *data, struct drm_file *file) { struct drm_msm_wait_fence *args = data; - return msm_wait_fence_interruptable(dev, args->fence, &TS(args->timeout)); + + if (args->pad) { + DRM_ERROR("invalid pad: %08x\n", args->pad); + return -EINVAL; + } + + return msm_wait_fence_interruptable(dev, args->fence, + &TS(args->timeout)); } static const struct drm_ioctl_desc msm_ioctls[] = { diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 5423e914e491..1f1f4cffdaed 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -23,7 +23,6 @@ * Cmdstream submission: */ -#define BO_INVALID_FLAGS ~(MSM_SUBMIT_BO_READ | MSM_SUBMIT_BO_WRITE) /* make sure these don't conflict w/ MSM_SUBMIT_BO_x */ #define BO_VALID 0x8000 #define BO_LOCKED 0x4000 @@ -77,7 +76,7 @@ static int submit_lookup_objects(struct msm_gem_submit *submit, goto out_unlock; } - if (submit_bo.flags & BO_INVALID_FLAGS) { + if (submit_bo.flags & ~MSM_SUBMIT_BO_FLAGS) { DRM_ERROR("invalid flags: %x\n", submit_bo.flags); ret = -EINVAL; goto out_unlock; @@ -369,6 +368,18 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, goto out; } + /* validate input from userspace: */ + switch (submit_cmd.type) { + case MSM_SUBMIT_CMD_BUF: + case MSM_SUBMIT_CMD_IB_TARGET_BUF: + case MSM_SUBMIT_CMD_CTX_RESTORE_BUF: + break; + default: + DRM_ERROR("invalid type: %08x\n", submit_cmd.type); + ret = -EINVAL; + goto out; + } + ret = submit_bo(submit, submit_cmd.submit_idx, &msm_obj, &iova, NULL); if (ret) diff --git a/include/uapi/drm/msm_drm.h b/include/uapi/drm/msm_drm.h index bf91a78a0b0e..0664c31f010c 100644 --- a/include/uapi/drm/msm_drm.h +++ b/include/uapi/drm/msm_drm.h @@ -70,6 +70,12 @@ struct drm_msm_param { #define MSM_BO_WC 0x00020000 #define MSM_BO_UNCACHED 0x00040000 +#define MSM_BO_FLAGS (MSM_BO_SCANOUT | \ + MSM_BO_GPU_READONLY | \ + MSM_BO_CACHED | \ + MSM_BO_WC | \ + MSM_BO_UNCACHED) + struct drm_msm_gem_new { uint64_t size; /* in */ uint32_t flags; /* in, mask of MSM_BO_x */ @@ -86,6 +92,8 @@ struct drm_msm_gem_info { #define MSM_PREP_WRITE 0x02 #define MSM_PREP_NOSYNC 0x04 +#define MSM_PREP_FLAGS (MSM_PREP_READ | MSM_PREP_WRITE | MSM_PREP_NOSYNC) + struct drm_msm_gem_cpu_prep { uint32_t handle; /* in */ uint32_t op; /* in, mask of MSM_PREP_x */ @@ -153,6 +161,9 @@ struct drm_msm_gem_submit_cmd { */ #define MSM_SUBMIT_BO_READ 0x0001 #define MSM_SUBMIT_BO_WRITE 0x0002 + +#define MSM_SUBMIT_BO_FLAGS (MSM_SUBMIT_BO_READ | MSM_SUBMIT_BO_WRITE) + struct drm_msm_gem_submit_bo { uint32_t flags; /* in, mask of MSM_SUBMIT_BO_x */ uint32_t handle; /* in, GEM handle */ -- cgit v1.2.3 From 30b6f8f6792abe582ef25100984a113ac9bc88dd Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Mon, 24 Mar 2014 09:12:24 -0400 Subject: drm/msm/mdp: add timeout for irq wait Make things recover a bit more gracefully if we get stuck with no vblank irq ever coming. Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/mdp/mdp_kms.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/mdp/mdp_kms.c b/drivers/gpu/drm/msm/mdp/mdp_kms.c index 3be48f7c36be..03455b64a245 100644 --- a/drivers/gpu/drm/msm/mdp/mdp_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp_kms.c @@ -101,7 +101,8 @@ void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask) .count = 1, }; mdp_irq_register(mdp_kms, &wait.irq); - wait_event(wait_event, (wait.count <= 0)); + wait_event_timeout(wait_event, (wait.count <= 0), + msecs_to_jiffies(100)); mdp_irq_unregister(mdp_kms, &wait.irq); } -- cgit v1.2.3 From 06fb220b2d1a80be5621fa06ef276a3890e6be78 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 24 Dec 2013 12:58:01 +0100 Subject: drm/omap: Don't dereference list head when the connectors list is empty The connectors list iterator returns the list head when the list is empty. Fix it by returning NULL in that case. Signed-off-by: Laurent Pinchart Reviewed-by: Rob Clark Signed-off-by: Rob Clark --- drivers/gpu/drm/omapdrm/omap_fb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/omapdrm/omap_fb.c b/drivers/gpu/drm/omapdrm/omap_fb.c index f466c4aaee94..19a9b8613620 100644 --- a/drivers/gpu/drm/omapdrm/omap_fb.c +++ b/drivers/gpu/drm/omapdrm/omap_fb.c @@ -306,7 +306,8 @@ struct drm_connector *omap_framebuffer_get_next_connector( struct drm_connector *connector = from; if (!from) - return list_first_entry(connector_list, typeof(*from), head); + return list_first_entry_or_null(connector_list, typeof(*from), + head); list_for_each_entry_from(connector, connector_list, head) { if (connector != from) { -- cgit v1.2.3