drm/tegra: Changes for v5.1-rc1

This set of changes starts of with some refactoring of the CEC support
 to make it reusable on Tegra210 and later. Following are a couple of
 fixes for HDMI audio support (via HDA).
 
 The bulk here is a set of preparatory patches working towards enabling
 Tegra186 support for host1x and VIC. Additional patches will be needed
 to fully enable this, but they're not quite ready yet.
 
 To round things off, this also adds support for configuring the SOR
 crossbar using device tree, and fixes a couple of job-related issues in
 the host1x code.
 -----BEGIN PGP SIGNATURE-----
 
 iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlxdlV4THHRyZWRpbmdA
 bnZpZGlhLmNvbQAKCRDdI6zXfz6zofqZD/9yO8guDD9ifIUztA2tG8NXOBZ1loKe
 FHwYNTmEX85UZ0pll9ljhw9tctaEqDiswp6g2GEu+UEiNueqKJahOctYOmPZRpWS
 tG2oFtd8zQNulpZU249On5rZCUal3zM/gl0okNkMsgg5xx0YkR+9dagrF9+mQO4A
 MzivhrhfLLFQMzVToB5zOKBrPOqc4FqQj92c7wyX9rOp5kM4xSJWqc0wu2t7XvKx
 tben0lmAl3Q5rJF/h3MQKt3NcUZ6HXLH6Weo4hE6EgMSSB1wwCDAsTROOv7BlnW6
 OBJkd/3+yFYO/VRIO0OdS4PAPGfTuqOAz/uAxX9nteE405lV37/ILqMyDalePnB/
 p8i1iy1/cEwsSlqGwLwwJEPJPQ56L7GdVwtv9hJbb3hOdq1/rIuXiggSJ5F6eG8n
 PDKtUVyu1SDaz2trKdI9zHB2I48Z/gvNBHpGrHUC2IGH1SS8cLd0Q3Cx9lD9CgI3
 FaI0GmfWFKHVphSn8rRM37gsGoT0Nt2KWke55Xad8VRl7SdlSSH/v5LB8FHfPckb
 x3ahIjxfi6hwYUSNKHNYkFCBjtF9m/YLv2T3kgtUEX/lWrFvt1uLhcaJNiRZrBkn
 iZwAAmqv09aa9v3nNko3pmSiI9KiK/Tml9ptbA5P2t8nYXju9kF/fz3sNaECgUsS
 0rDGnGL5GqwN7Q==
 =jS/6
 -----END PGP SIGNATURE-----

Merge tag 'drm/tegra/for-5.1-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next

drm/tegra: Changes for v5.1-rc1

This set of changes starts of with some refactoring of the CEC support
to make it reusable on Tegra210 and later. Following are a couple of
fixes for HDMI audio support (via HDA).

The bulk here is a set of preparatory patches working towards enabling
Tegra186 support for host1x and VIC. Additional patches will be needed
to fully enable this, but they're not quite ready yet.

To round things off, this also adds support for configuring the SOR
crossbar using device tree, and fixes a couple of job-related issues in
the host1x code.

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Thierry Reding <thierry.reding@gmail.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190208144721.25830-1-thierry.reding@gmail.com
This commit is contained in:
Dave Airlie 2019-02-11 13:32:38 +10:00
commit 38f070eb12
23 changed files with 644 additions and 305 deletions

View File

@ -238,6 +238,9 @@ of the following host1x client modules:
- nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
- nvidia,edid: supplies a binary EDID blob
- nvidia,panel: phandle of a display panel
- nvidia,xbar-cfg: 5 cells containing the crossbar configuration. Each lane
of the SOR, identified by the cell's index, is mapped via the crossbar to
the pad specified by the cell's value.
Optional properties when driving an eDP output:
- nvidia,dpaux: phandle to a DispayPort AUX interface

View File

@ -10,6 +10,7 @@ tegra-drm-y := \
dc.o \
output.o \
rgb.o \
hda.o \
hdmi.o \
mipi-phy.o \
dsi.o \

View File

@ -92,10 +92,6 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
return -ENOMEM;
if (iommu_present(&platform_bus_type)) {
u64 carveout_start, carveout_end, gem_start, gem_end;
struct iommu_domain_geometry *geometry;
unsigned long order;
tegra->domain = iommu_domain_alloc(&platform_bus_type);
if (!tegra->domain) {
err = -ENOMEM;
@ -105,27 +101,6 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
err = iova_cache_get();
if (err < 0)
goto domain;
geometry = &tegra->domain->geometry;
gem_start = geometry->aperture_start;
gem_end = geometry->aperture_end - CARVEOUT_SZ;
carveout_start = gem_end + 1;
carveout_end = geometry->aperture_end;
order = __ffs(tegra->domain->pgsize_bitmap);
init_iova_domain(&tegra->carveout.domain, 1UL << order,
carveout_start >> order);
tegra->carveout.shift = iova_shift(&tegra->carveout.domain);
tegra->carveout.limit = carveout_end >> tegra->carveout.shift;
drm_mm_init(&tegra->mm, gem_start, gem_end - gem_start + 1);
mutex_init(&tegra->mm_lock);
DRM_DEBUG("IOMMU apertures:\n");
DRM_DEBUG(" GEM: %#llx-%#llx\n", gem_start, gem_end);
DRM_DEBUG(" Carveout: %#llx-%#llx\n", carveout_start,
carveout_end);
}
mutex_init(&tegra->clients_lock);
@ -159,6 +134,36 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (err < 0)
goto fbdev;
if (tegra->domain) {
u64 carveout_start, carveout_end, gem_start, gem_end;
u64 dma_mask = dma_get_mask(&device->dev);
dma_addr_t start, end;
unsigned long order;
start = tegra->domain->geometry.aperture_start & dma_mask;
end = tegra->domain->geometry.aperture_end & dma_mask;
gem_start = start;
gem_end = end - CARVEOUT_SZ;
carveout_start = gem_end + 1;
carveout_end = end;
order = __ffs(tegra->domain->pgsize_bitmap);
init_iova_domain(&tegra->carveout.domain, 1UL << order,
carveout_start >> order);
tegra->carveout.shift = iova_shift(&tegra->carveout.domain);
tegra->carveout.limit = carveout_end >> tegra->carveout.shift;
drm_mm_init(&tegra->mm, gem_start, gem_end - gem_start + 1);
mutex_init(&tegra->mm_lock);
DRM_DEBUG("IOMMU apertures:\n");
DRM_DEBUG(" GEM: %#llx-%#llx\n", gem_start, gem_end);
DRM_DEBUG(" Carveout: %#llx-%#llx\n", carveout_start,
carveout_end);
}
if (tegra->hub) {
err = tegra_display_hub_prepare(tegra->hub);
if (err < 0)
@ -1041,6 +1046,7 @@ int tegra_drm_register_client(struct tegra_drm *tegra,
{
mutex_lock(&tegra->clients_lock);
list_add_tail(&client->list, &tegra->clients);
client->drm = tegra;
mutex_unlock(&tegra->clients_lock);
return 0;
@ -1051,6 +1057,7 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra,
{
mutex_lock(&tegra->clients_lock);
list_del_init(&client->list);
client->drm = NULL;
mutex_unlock(&tegra->clients_lock);
return 0;

View File

@ -88,6 +88,7 @@ int tegra_drm_submit(struct tegra_drm_context *context,
struct tegra_drm_client {
struct host1x_client base;
struct list_head list;
struct tegra_drm *drm;
unsigned int version;
const struct tegra_drm_client_ops *ops;
@ -124,7 +125,7 @@ struct tegra_output {
struct drm_panel *panel;
struct i2c_adapter *ddc;
const struct edid *edid;
struct cec_notifier *notifier;
struct cec_notifier *cec;
unsigned int hpd_irq;
int hpd_gpio;
enum of_gpio_flags hpd_gpio_flags;

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2019 NVIDIA Corporation
*/
#include <linux/bug.h>
#include <sound/hda_verbs.h>
#include "hda.h"
void tegra_hda_parse_format(unsigned int format, struct tegra_hda_format *fmt)
{
unsigned int mul, div, bits, channels;
if (format & AC_FMT_TYPE_NON_PCM)
fmt->pcm = false;
else
fmt->pcm = true;
if (format & AC_FMT_BASE_44K)
fmt->sample_rate = 44100;
else
fmt->sample_rate = 48000;
mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
fmt->sample_rate *= (mul + 1) / (div + 1);
switch (format & AC_FMT_BITS_MASK) {
case AC_FMT_BITS_8:
fmt->bits = 8;
break;
case AC_FMT_BITS_16:
fmt->bits = 16;
break;
case AC_FMT_BITS_20:
fmt->bits = 20;
break;
case AC_FMT_BITS_24:
fmt->bits = 24;
break;
case AC_FMT_BITS_32:
fmt->bits = 32;
break;
default:
bits = (format & AC_FMT_BITS_MASK) >> AC_FMT_BITS_SHIFT;
WARN(1, "invalid number of bits: %#x\n", bits);
fmt->bits = 8;
break;
}
channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
/* channels are encoded as n - 1 */
fmt->channels = channels + 1;
}

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2019 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_HDA_H
#define DRM_TEGRA_HDA_H 1
#include <linux/types.h>
struct tegra_hda_format {
unsigned int sample_rate;
unsigned int channels;
unsigned int bits;
bool pcm;
};
void tegra_hda_parse_format(unsigned int format, struct tegra_hda_format *fmt);
#endif

View File

@ -11,6 +11,7 @@
#include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/hdmi.h>
#include <linux/math64.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
@ -20,10 +21,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_probe_helper.h>
#include <sound/hda_verbs.h>
#include <media/cec-notifier.h>
#include "hda.h"
#include "hdmi.h"
#include "drm.h"
#include "dc.h"
@ -71,8 +69,7 @@ struct tegra_hdmi {
const struct tegra_hdmi_config *config;
unsigned int audio_source;
unsigned int audio_sample_rate;
unsigned int audio_channels;
struct tegra_hda_format format;
unsigned int pixel_clock;
bool stereo;
@ -119,68 +116,11 @@ static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, u32 value,
}
struct tegra_hdmi_audio_config {
unsigned int pclk;
unsigned int n;
unsigned int cts;
unsigned int aval;
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_32k[] = {
{ 25200000, 4096, 25200, 24000 },
{ 27000000, 4096, 27000, 24000 },
{ 74250000, 4096, 74250, 24000 },
{ 148500000, 4096, 148500, 24000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_44_1k[] = {
{ 25200000, 5880, 26250, 25000 },
{ 27000000, 5880, 28125, 25000 },
{ 74250000, 4704, 61875, 20000 },
{ 148500000, 4704, 123750, 20000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_48k[] = {
{ 25200000, 6144, 25200, 24000 },
{ 27000000, 6144, 27000, 24000 },
{ 74250000, 6144, 74250, 24000 },
{ 148500000, 6144, 148500, 24000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_88_2k[] = {
{ 25200000, 11760, 26250, 25000 },
{ 27000000, 11760, 28125, 25000 },
{ 74250000, 9408, 61875, 20000 },
{ 148500000, 9408, 123750, 20000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_96k[] = {
{ 25200000, 12288, 25200, 24000 },
{ 27000000, 12288, 27000, 24000 },
{ 74250000, 12288, 74250, 24000 },
{ 148500000, 12288, 148500, 24000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_176_4k[] = {
{ 25200000, 23520, 26250, 25000 },
{ 27000000, 23520, 28125, 25000 },
{ 74250000, 18816, 61875, 20000 },
{ 148500000, 18816, 123750, 20000 },
{ 0, 0, 0, 0 },
};
static const struct tegra_hdmi_audio_config tegra_hdmi_audio_192k[] = {
{ 25200000, 24576, 25200, 24000 },
{ 27000000, 24576, 27000, 24000 },
{ 74250000, 24576, 74250, 24000 },
{ 148500000, 24576, 148500, 24000 },
{ 0, 0, 0, 0 },
};
static const struct tmds_config tegra20_tmds_config[] = {
{ /* slow pixel clock modes */
.pclk = 27000000,
@ -418,52 +358,53 @@ static const struct tmds_config tegra124_tmds_config[] = {
},
};
static const struct tegra_hdmi_audio_config *
tegra_hdmi_get_audio_config(unsigned int sample_rate, unsigned int pclk)
static int
tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pix_clock,
struct tegra_hdmi_audio_config *config)
{
const struct tegra_hdmi_audio_config *table;
const unsigned int afreq = 128 * audio_freq;
const unsigned int min_n = afreq / 1500;
const unsigned int max_n = afreq / 300;
const unsigned int ideal_n = afreq / 1000;
int64_t min_err = (uint64_t)-1 >> 1;
unsigned int min_delta = -1;
int n;
switch (sample_rate) {
case 32000:
table = tegra_hdmi_audio_32k;
break;
memset(config, 0, sizeof(*config));
config->n = -1;
case 44100:
table = tegra_hdmi_audio_44_1k;
break;
for (n = min_n; n <= max_n; n++) {
uint64_t cts_f, aval_f;
unsigned int delta;
int64_t cts, err;
case 48000:
table = tegra_hdmi_audio_48k;
break;
/* compute aval in 48.16 fixed point */
aval_f = ((int64_t)24000000 << 16) * n;
do_div(aval_f, afreq);
/* It should round without any rest */
if (aval_f & 0xFFFF)
continue;
case 88200:
table = tegra_hdmi_audio_88_2k;
break;
/* Compute cts in 48.16 fixed point */
cts_f = ((int64_t)pix_clock << 16) * n;
do_div(cts_f, afreq);
/* Round it to the nearest integer */
cts = (cts_f & ~0xFFFF) + ((cts_f & BIT(15)) << 1);
case 96000:
table = tegra_hdmi_audio_96k;
break;
delta = abs(n - ideal_n);
case 176400:
table = tegra_hdmi_audio_176_4k;
break;
case 192000:
table = tegra_hdmi_audio_192k;
break;
default:
return NULL;
/* Compute the absolute error */
err = abs((int64_t)cts_f - cts);
if (err < min_err || (err == min_err && delta < min_delta)) {
config->n = n;
config->cts = cts >> 16;
config->aval = aval_f >> 16;
min_delta = delta;
min_err = err;
}
}
while (table->pclk) {
if (table->pclk == pclk)
return table;
table++;
}
return NULL;
return config->n != -1 ? 0 : -EINVAL;
}
static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi)
@ -510,7 +451,7 @@ static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value)
unsigned int i;
for (i = 0; i < ARRAY_SIZE(regs); i++) {
if (regs[i].sample_rate == hdmi->audio_sample_rate) {
if (regs[i].sample_rate == hdmi->format.sample_rate) {
tegra_hdmi_writel(hdmi, value, regs[i].offset);
break;
}
@ -519,8 +460,9 @@ static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value)
static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)
{
const struct tegra_hdmi_audio_config *config;
struct tegra_hdmi_audio_config config;
u32 source, value;
int err;
switch (hdmi->audio_source) {
case HDA:
@ -564,7 +506,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)
* play back system startup sounds early. It is possibly not
* needed on Linux at all.
*/
if (hdmi->audio_channels == 2)
if (hdmi->format.channels == 2)
value = SOR_AUDIO_CNTRL0_INJECT_NULLSMPL;
else
value = 0;
@ -595,25 +537,28 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_SPARE0);
}
config = tegra_hdmi_get_audio_config(hdmi->audio_sample_rate,
hdmi->pixel_clock);
if (!config) {
err = tegra_hdmi_get_audio_config(hdmi->format.sample_rate,
hdmi->pixel_clock, &config);
if (err < 0) {
dev_err(hdmi->dev,
"cannot set audio to %u Hz at %u Hz pixel clock\n",
hdmi->audio_sample_rate, hdmi->pixel_clock);
return -EINVAL;
hdmi->format.sample_rate, hdmi->pixel_clock);
return err;
}
dev_dbg(hdmi->dev, "audio: pixclk=%u, n=%u, cts=%u, aval=%u\n",
hdmi->pixel_clock, config.n, config.cts, config.aval);
tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_HDMI_ACR_CTRL);
value = AUDIO_N_RESETF | AUDIO_N_GENERATE_ALTERNATE |
AUDIO_N_VALUE(config->n - 1);
AUDIO_N_VALUE(config.n - 1);
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N);
tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE,
tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config.n) | ACR_ENABLE,
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH);
tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts),
tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config.cts),
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW);
value = SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1);
@ -624,7 +569,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N);
if (hdmi->config->has_hda)
tegra_hdmi_write_aval(hdmi, config->aval);
tegra_hdmi_write_aval(hdmi, config.aval);
tegra_hdmi_setup_audio_fs_tables(hdmi);
@ -788,7 +733,7 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
return;
}
frame.channels = hdmi->audio_channels;
frame.channels = hdmi->format.channels;
err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
if (err < 0) {
@ -1590,24 +1535,6 @@ static const struct of_device_id tegra_hdmi_of_match[] = {
};
MODULE_DEVICE_TABLE(of, tegra_hdmi_of_match);
static void hda_format_parse(unsigned int format, unsigned int *rate,
unsigned int *channels)
{
unsigned int mul, div;
if (format & AC_FMT_BASE_44K)
*rate = 44100;
else
*rate = 48000;
mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
*rate = *rate * (mul + 1) / (div + 1);
*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
}
static irqreturn_t tegra_hdmi_irq(int irq, void *data)
{
struct tegra_hdmi *hdmi = data;
@ -1624,14 +1551,9 @@ static irqreturn_t tegra_hdmi_irq(int irq, void *data)
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0);
if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) {
unsigned int sample_rate, channels;
format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
hda_format_parse(format, &sample_rate, &channels);
hdmi->audio_sample_rate = sample_rate;
hdmi->audio_channels = channels;
tegra_hda_parse_format(format, &hdmi->format);
err = tegra_hdmi_setup_audio(hdmi);
if (err < 0) {
@ -1665,8 +1587,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
hdmi->dev = &pdev->dev;
hdmi->audio_source = AUTO;
hdmi->audio_sample_rate = 48000;
hdmi->audio_channels = 2;
hdmi->stereo = false;
hdmi->dvi = false;
@ -1710,10 +1630,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
return PTR_ERR(hdmi->vdd);
}
hdmi->output.notifier = cec_notifier_get(&pdev->dev);
if (hdmi->output.notifier == NULL)
return -ENOMEM;
hdmi->output.dev = &pdev->dev;
err = tegra_output_probe(&hdmi->output);
@ -1772,9 +1688,6 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
tegra_output_remove(&hdmi->output);
if (hdmi->output.notifier)
cec_notifier_put(hdmi->output.notifier);
return 0;
}

View File

@ -36,7 +36,7 @@ int tegra_output_connector_get_modes(struct drm_connector *connector)
else if (output->ddc)
edid = drm_get_edid(connector, output->ddc);
cec_notifier_set_phys_addr_from_edid(output->notifier, edid);
cec_notifier_set_phys_addr_from_edid(output->cec, edid);
drm_connector_update_edid_property(connector, edid);
if (edid) {
@ -73,7 +73,7 @@ tegra_output_connector_detect(struct drm_connector *connector, bool force)
}
if (status != connector_status_connected)
cec_notifier_phys_addr_invalidate(output->notifier);
cec_notifier_phys_addr_invalidate(output->cec);
return status;
}
@ -174,11 +174,18 @@ int tegra_output_probe(struct tegra_output *output)
disable_irq(output->hpd_irq);
}
output->cec = cec_notifier_get(output->dev);
if (!output->cec)
return -ENOMEM;
return 0;
}
void tegra_output_remove(struct tegra_output *output)
{
if (output->cec)
cec_notifier_put(output->cec);
if (gpio_is_valid(output->hpd_gpio)) {
free_irq(output->hpd_irq, output);
gpio_free(output->hpd_gpio);

View File

@ -19,8 +19,6 @@
#include <soc/tegra/pmc.h>
#include <sound/hda_verbs.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_panel.h>
@ -28,6 +26,7 @@
#include "dc.h"
#include "drm.h"
#include "hda.h"
#include "sor.h"
#include "trace.h"
@ -411,6 +410,8 @@ struct tegra_sor {
struct clk *clk_dp;
struct clk *clk;
u8 xbar_cfg[5];
struct drm_dp_aux *aux;
struct drm_info_list *debugfs_files;
@ -429,10 +430,7 @@ struct tegra_sor {
struct delayed_work scdc;
bool scdc_enabled;
struct {
unsigned int sample_rate;
unsigned int channels;
} audio;
struct tegra_hda_format format;
};
struct tegra_sor_state {
@ -1818,7 +1816,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
/* XXX not in TRM */
for (value = 0, i = 0; i < 5; i++)
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) |
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->xbar_cfg[i]) |
SOR_XBAR_CTRL_LINK1_XSEL(i, i);
tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL);
@ -2186,7 +2184,7 @@ static int tegra_sor_hdmi_enable_audio_infoframe(struct tegra_sor *sor)
return err;
}
frame.channels = sor->audio.channels;
frame.channels = sor->format.channels;
err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
if (err < 0) {
@ -2215,7 +2213,7 @@ static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor)
value |= SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_HDA);
/* inject null samples */
if (sor->audio.channels != 2)
if (sor->format.channels != 2)
value &= ~SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
else
value |= SOR_AUDIO_CNTRL_INJECT_NULLSMPL;
@ -2246,7 +2244,7 @@ static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor)
value = SOR_HDMI_AUDIO_N_RESET | SOR_HDMI_AUDIO_N_LOOKUP;
tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N);
value = (24000 * 4096) / (128 * sor->audio.sample_rate / 1000);
value = (24000 * 4096) / (128 * sor->format.sample_rate / 1000);
tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0320);
tegra_sor_writel(sor, 4096, SOR_AUDIO_NVAL_0320);
@ -2259,15 +2257,15 @@ static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor)
tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_1764);
tegra_sor_writel(sor, 18816, SOR_AUDIO_NVAL_1764);
value = (24000 * 6144) / (128 * sor->audio.sample_rate / 1000);
value = (24000 * 6144) / (128 * sor->format.sample_rate / 1000);
tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0480);
tegra_sor_writel(sor, 6144, SOR_AUDIO_NVAL_0480);
value = (24000 * 12288) / (128 * sor->audio.sample_rate / 1000);
value = (24000 * 12288) / (128 * sor->format.sample_rate / 1000);
tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0960);
tegra_sor_writel(sor, 12288, SOR_AUDIO_NVAL_0960);
value = (24000 * 24576) / (128 * sor->audio.sample_rate / 1000);
value = (24000 * 24576) / (128 * sor->format.sample_rate / 1000);
tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_1920);
tegra_sor_writel(sor, 24576, SOR_AUDIO_NVAL_1920);
@ -2555,7 +2553,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
/* XXX not in TRM */
for (value = 0, i = 0; i < 5; i++)
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) |
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->xbar_cfg[i]) |
SOR_XBAR_CTRL_LINK1_XSEL(i, i);
tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL);
@ -3176,6 +3174,8 @@ MODULE_DEVICE_TABLE(of, tegra_sor_of_match);
static int tegra_sor_parse_dt(struct tegra_sor *sor)
{
struct device_node *np = sor->dev->of_node;
u32 xbar_cfg[5];
unsigned int i;
u32 value;
int err;
@ -3193,27 +3193,20 @@ static int tegra_sor_parse_dt(struct tegra_sor *sor)
sor->pad = TEGRA_IO_PAD_HDMI_DP0 + sor->index;
}
err = of_property_read_u32_array(np, "nvidia,xbar-cfg", xbar_cfg, 5);
if (err < 0) {
/* fall back to default per-SoC XBAR configuration */
for (i = 0; i < 5; i++)
sor->xbar_cfg[i] = sor->soc->xbar_cfg[i];
} else {
/* copy cells to SOR XBAR configuration */
for (i = 0; i < 5; i++)
sor->xbar_cfg[i] = xbar_cfg[i];
}
return 0;
}
static void tegra_hda_parse_format(unsigned int format, unsigned int *rate,
unsigned int *channels)
{
unsigned int mul, div;
if (format & AC_FMT_BASE_44K)
*rate = 44100;
else
*rate = 48000;
mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
*rate = *rate * (mul + 1) / (div + 1);
*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
}
static irqreturn_t tegra_sor_irq(int irq, void *data)
{
struct tegra_sor *sor = data;
@ -3226,14 +3219,11 @@ static irqreturn_t tegra_sor_irq(int irq, void *data)
value = tegra_sor_readl(sor, SOR_AUDIO_HDA_CODEC_SCRATCH0);
if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) {
unsigned int format, sample_rate, channels;
unsigned int format;
format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
tegra_hda_parse_format(format, &sample_rate, &channels);
sor->audio.sample_rate = sample_rate;
sor->audio.channels = channels;
tegra_hda_parse_format(format, &sor->format);
tegra_sor_hdmi_audio_enable(sor);
} else {

View File

@ -26,6 +26,7 @@
struct vic_config {
const char *firmware;
unsigned int version;
bool supports_sid;
};
struct vic {
@ -105,6 +106,22 @@ static int vic_boot(struct vic *vic)
if (vic->booted)
return 0;
if (vic->config->supports_sid) {
struct iommu_fwspec *spec = dev_iommu_fwspec_get(vic->dev);
u32 value;
value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) |
TRANSCFG_ATT(0, TRANSCFG_SID_HW);
vic_writel(vic, value, VIC_TFBIF_TRANSCFG);
if (spec && spec->num_ids > 0) {
value = spec->ids[0] & 0xffff;
vic_writel(vic, value, VIC_THI_STREAMID0);
vic_writel(vic, value, VIC_THI_STREAMID1);
}
}
/* setup clockgating registers */
vic_writel(vic, CG_IDLE_CG_DLY_CNT(4) |
CG_IDLE_CG_EN |
@ -181,13 +198,6 @@ static int vic_init(struct host1x_client *client)
vic->domain = tegra->domain;
}
if (!vic->falcon.data) {
vic->falcon.data = tegra;
err = falcon_load_firmware(&vic->falcon);
if (err < 0)
goto detach;
}
vic->channel = host1x_channel_request(client->dev);
if (!vic->channel) {
err = -ENOMEM;
@ -246,6 +256,30 @@ static const struct host1x_client_ops vic_client_ops = {
.exit = vic_exit,
};
static int vic_load_firmware(struct vic *vic)
{
int err;
if (vic->falcon.data)
return 0;
vic->falcon.data = vic->client.drm;
err = falcon_read_firmware(&vic->falcon, vic->config->firmware);
if (err < 0)
goto cleanup;
err = falcon_load_firmware(&vic->falcon);
if (err < 0)
goto cleanup;
return 0;
cleanup:
vic->falcon.data = NULL;
return err;
}
static int vic_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
@ -256,19 +290,25 @@ static int vic_open_channel(struct tegra_drm_client *client,
if (err < 0)
return err;
err = vic_load_firmware(vic);
if (err < 0)
goto rpm_put;
err = vic_boot(vic);
if (err < 0) {
pm_runtime_put(vic->dev);
return err;
}
if (err < 0)
goto rpm_put;
context->channel = host1x_channel_get(vic->channel);
if (!context->channel) {
pm_runtime_put(vic->dev);
return -ENOMEM;
err = -ENOMEM;
goto rpm_put;
}
return 0;
rpm_put:
pm_runtime_put(vic->dev);
return err;
}
static void vic_close_channel(struct tegra_drm_context *context)
@ -291,6 +331,7 @@ static const struct tegra_drm_client_ops vic_ops = {
static const struct vic_config vic_t124_config = {
.firmware = NVIDIA_TEGRA_124_VIC_FIRMWARE,
.version = 0x40,
.supports_sid = false,
};
#define NVIDIA_TEGRA_210_VIC_FIRMWARE "nvidia/tegra210/vic04_ucode.bin"
@ -298,6 +339,7 @@ static const struct vic_config vic_t124_config = {
static const struct vic_config vic_t210_config = {
.firmware = NVIDIA_TEGRA_210_VIC_FIRMWARE,
.version = 0x21,
.supports_sid = false,
};
#define NVIDIA_TEGRA_186_VIC_FIRMWARE "nvidia/tegra186/vic04_ucode.bin"
@ -305,6 +347,7 @@ static const struct vic_config vic_t210_config = {
static const struct vic_config vic_t186_config = {
.firmware = NVIDIA_TEGRA_186_VIC_FIRMWARE,
.version = 0x18,
.supports_sid = true,
};
#define NVIDIA_TEGRA_194_VIC_FIRMWARE "nvidia/tegra194/vic.bin"
@ -312,6 +355,7 @@ static const struct vic_config vic_t186_config = {
static const struct vic_config vic_t194_config = {
.firmware = NVIDIA_TEGRA_194_VIC_FIRMWARE,
.version = 0x19,
.supports_sid = true,
};
static const struct of_device_id vic_match[] = {
@ -372,10 +416,6 @@ static int vic_probe(struct platform_device *pdev)
if (err < 0)
return err;
err = falcon_read_firmware(&vic->falcon, vic->config->firmware);
if (err < 0)
goto exit_falcon;
platform_set_drvdata(pdev, vic);
INIT_LIST_HEAD(&vic->client.base.list);
@ -393,7 +433,6 @@ static int vic_probe(struct platform_device *pdev)
err = host1x_client_register(&vic->client.base);
if (err < 0) {
dev_err(dev, "failed to register host1x client: %d\n", err);
platform_set_drvdata(pdev, NULL);
goto exit_falcon;
}

View File

@ -17,11 +17,20 @@
/* VIC registers */
#define VIC_THI_STREAMID0 0x00000030
#define VIC_THI_STREAMID1 0x00000034
#define NV_PVIC_MISC_PRI_VIC_CG 0x000016d0
#define CG_IDLE_CG_DLY_CNT(val) ((val & 0x3f) << 0)
#define CG_IDLE_CG_EN (1 << 6)
#define CG_WAKEUP_DLY_CNT(val) ((val & 0xf) << 16)
#define VIC_TFBIF_TRANSCFG 0x00002044
#define TRANSCFG_ATT(i, v) (((v) & 0x3) << (i * 4))
#define TRANSCFG_SID_HW 0
#define TRANSCFG_SID_PHY 1
#define TRANSCFG_SID_FALCON 2
/* Firmware offsets */
#define VIC_UCODE_FCE_HEADER_OFFSET (6*4)

View File

@ -15,8 +15,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/debugfs.h>
#include <linux/host1x.h>
#include <linux/of.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/of_device.h>
@ -500,6 +502,36 @@ static void host1x_detach_driver(struct host1x *host1x,
mutex_unlock(&host1x->devices_lock);
}
static int host1x_devices_show(struct seq_file *s, void *data)
{
struct host1x *host1x = s->private;
struct host1x_device *device;
mutex_lock(&host1x->devices_lock);
list_for_each_entry(device, &host1x->devices, list) {
struct host1x_subdev *subdev;
seq_printf(s, "%s\n", dev_name(&device->dev));
mutex_lock(&device->subdevs_lock);
list_for_each_entry(subdev, &device->active, list)
seq_printf(s, " %pOFf: %s\n", subdev->np,
dev_name(subdev->client->dev));
list_for_each_entry(subdev, &device->subdevs, list)
seq_printf(s, " %pOFf:\n", subdev->np);
mutex_unlock(&device->subdevs_lock);
}
mutex_unlock(&host1x->devices_lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(host1x_devices);
/**
* host1x_register() - register a host1x controller
* @host1x: host1x controller
@ -523,6 +555,9 @@ int host1x_register(struct host1x *host1x)
mutex_unlock(&drivers_lock);
debugfs_create_file("devices", S_IRUGO, host1x->debugfs, host1x,
&host1x_devices_fops);
return 0;
}

View File

@ -41,7 +41,17 @@
* means that the push buffer is full, not empty.
*/
#define HOST1X_PUSHBUFFER_SLOTS 512
/*
* Typically the commands written into the push buffer are a pair of words. We
* use slots to represent each of these pairs and to simplify things. Note the
* strange number of slots allocated here. 512 slots will fit exactly within a
* single memory page. We also need one additional word at the end of the push
* buffer for the RESTART opcode that will instruct the CDMA to jump back to
* the beginning of the push buffer. With 512 slots, this means that we'll use
* 2 memory pages and waste 4092 bytes of the second page that will never be
* used.
*/
#define HOST1X_PUSHBUFFER_SLOTS 511
/*
* Clean up push buffer resources
@ -143,7 +153,10 @@ static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
WARN_ON(pb->pos == pb->fence);
*(p++) = op1;
*(p++) = op2;
pb->pos = (pb->pos + 8) & (pb->size - 1);
pb->pos += 8;
if (pb->pos >= pb->size)
pb->pos -= pb->size;
}
/*
@ -153,7 +166,10 @@ static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
{
/* Advance the next write position */
pb->fence = (pb->fence + slots * 8) & (pb->size - 1);
pb->fence += slots * 8;
if (pb->fence >= pb->size)
pb->fence -= pb->size;
}
/*
@ -161,7 +177,12 @@ static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
*/
static u32 host1x_pushbuffer_space(struct push_buffer *pb)
{
return ((pb->fence - pb->pos) & (pb->size - 1)) / 8;
unsigned int fence = pb->fence;
if (pb->fence < pb->pos)
fence += pb->size;
return (fence - pb->pos) / 8;
}
/*
@ -210,13 +231,52 @@ unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
cdma->event = event;
mutex_unlock(&cdma->lock);
down(&cdma->sem);
wait_for_completion(&cdma->complete);
mutex_lock(&cdma->lock);
}
return 0;
}
/*
* Sleep (if necessary) until the push buffer has enough free space.
*
* Must be called with the cdma lock held.
*/
int host1x_cdma_wait_pushbuffer_space(struct host1x *host1x,
struct host1x_cdma *cdma,
unsigned int needed)
{
while (true) {
struct push_buffer *pb = &cdma->push_buffer;
unsigned int space;
space = host1x_pushbuffer_space(pb);
if (space >= needed)
break;
trace_host1x_wait_cdma(dev_name(cdma_to_channel(cdma)->dev),
CDMA_EVENT_PUSH_BUFFER_SPACE);
host1x_hw_cdma_flush(host1x, cdma);
/* If somebody has managed to already start waiting, yield */
if (cdma->event != CDMA_EVENT_NONE) {
mutex_unlock(&cdma->lock);
schedule();
mutex_lock(&cdma->lock);
continue;
}
cdma->event = CDMA_EVENT_PUSH_BUFFER_SPACE;
mutex_unlock(&cdma->lock);
wait_for_completion(&cdma->complete);
mutex_lock(&cdma->lock);
}
return 0;
}
/*
* Start timer that tracks the time spent by the job.
* Must be called with the cdma lock held.
@ -314,7 +374,7 @@ static void update_cdma_locked(struct host1x_cdma *cdma)
if (signal) {
cdma->event = CDMA_EVENT_NONE;
up(&cdma->sem);
complete(&cdma->complete);
}
}
@ -323,7 +383,7 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
{
struct host1x *host1x = cdma_to_host1x(cdma);
u32 restart_addr, syncpt_incrs, syncpt_val;
struct host1x_job *job = NULL;
struct host1x_job *job, *next_job = NULL;
syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
@ -341,40 +401,37 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
__func__);
list_for_each_entry(job, &cdma->sync_queue, list) {
if (syncpt_val < job->syncpt_end)
break;
if (syncpt_val < job->syncpt_end) {
if (!list_is_last(&job->list, &cdma->sync_queue))
next_job = list_next_entry(job, list);
goto syncpt_incr;
}
host1x_job_dump(dev, job);
}
/* all jobs have been completed */
job = NULL;
syncpt_incr:
/*
* Walk the sync_queue, first incrementing with the CPU syncpts that
* are partially executed (the first buffer) or fully skipped while
* still in the current context (slots are also NOP-ed).
* Increment with CPU the remaining syncpts of a partially executed job.
*
* At the point contexts are interleaved, syncpt increments must be
* done inline with the pushbuffer from a GATHER buffer to maintain
* the order (slots are modified to be a GATHER of syncpt incrs).
*
* Note: save in restart_addr the location where the timed out buffer
* started in the PB, so we can start the refetch from there (with the
* modified NOP-ed PB slots). This lets things appear to have completed
* properly for this buffer and resources are freed.
* CDMA will continue execution starting with the next job or will get
* into idle state.
*/
dev_dbg(dev, "%s: perform CPU incr on pending same ctx buffers\n",
__func__);
if (!list_empty(&cdma->sync_queue))
restart_addr = job->first_get;
if (next_job)
restart_addr = next_job->first_get;
else
restart_addr = cdma->last_pos;
/* do CPU increments as long as this context continues */
list_for_each_entry_from(job, &cdma->sync_queue, list) {
/* different context, gets us out of this loop */
if (job->client != cdma->timeout.client)
break;
/* do CPU increments for the remaining syncpts */
if (job) {
dev_dbg(dev, "%s: perform CPU incr on pending buffers\n",
__func__);
/* won't need a timeout when replayed */
job->timeout = 0;
@ -389,21 +446,10 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
syncpt_incrs, job->syncpt_end,
job->num_slots);
syncpt_val += syncpt_incrs;
dev_dbg(dev, "%s: finished sync_queue modification\n",
__func__);
}
/*
* The following sumbits from the same client may be dependent on the
* failed submit and therefore they may fail. Force a small timeout
* to make the queue cleanup faster.
*/
list_for_each_entry_from(job, &cdma->sync_queue, list)
if (job->client == cdma->timeout.client)
job->timeout = min_t(unsigned int, job->timeout, 500);
dev_dbg(dev, "%s: finished sync_queue modification\n", __func__);
/* roll back DMAGET and start up channel again */
host1x_hw_cdma_resume(host1x, cdma, restart_addr);
}
@ -416,7 +462,7 @@ int host1x_cdma_init(struct host1x_cdma *cdma)
int err;
mutex_init(&cdma->lock);
sema_init(&cdma->sem, 0);
init_completion(&cdma->complete);
INIT_LIST_HEAD(&cdma->sync_queue);
@ -509,6 +555,59 @@ void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2)
host1x_pushbuffer_push(pb, op1, op2);
}
/*
* Push four words into two consecutive push buffer slots. Note that extra
* care needs to be taken not to split the two slots across the end of the
* push buffer. Otherwise the RESTART opcode at the end of the push buffer
* that ensures processing will restart at the beginning will break up the
* four words.
*
* Blocks as necessary if the push buffer is full.
*/
void host1x_cdma_push_wide(struct host1x_cdma *cdma, u32 op1, u32 op2,
u32 op3, u32 op4)
{
struct host1x_channel *channel = cdma_to_channel(cdma);
struct host1x *host1x = cdma_to_host1x(cdma);
struct push_buffer *pb = &cdma->push_buffer;
unsigned int needed = 2, extra = 0, i;
unsigned int space = cdma->slots_free;
if (host1x_debug_trace_cmdbuf)
trace_host1x_cdma_push_wide(dev_name(channel->dev), op1, op2,
op3, op4);
/* compute number of extra slots needed for padding */
if (pb->pos + 16 > pb->size) {
extra = (pb->size - pb->pos) / 8;
needed += extra;
}
host1x_cdma_wait_pushbuffer_space(host1x, cdma, needed);
space = host1x_pushbuffer_space(pb);
cdma->slots_free = space - needed;
cdma->slots_used += needed;
/*
* Note that we rely on the fact that this is only used to submit wide
* gather opcodes, which consist of 3 words, and they are padded with
* a NOP to avoid having to deal with fractional slots (a slot always
* represents 2 words). The fourth opcode passed to this function will
* therefore always be a NOP.
*
* This works around a slight ambiguity when it comes to opcodes. For
* all current host1x incarnations the NOP opcode uses the exact same
* encoding (0x20000000), so we could hard-code the value here, but a
* new incarnation may change it and break that assumption.
*/
for (i = 0; i < extra; i++)
host1x_pushbuffer_push(pb, op4, op4);
host1x_pushbuffer_push(pb, op1, op2);
host1x_pushbuffer_push(pb, op3, op4);
}
/*
* End a cdma submit
* Kick off DMA, add job to the sync queue, and a number of slots to be freed

View File

@ -20,7 +20,7 @@
#define __HOST1X_CDMA_H
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/completion.h>
#include <linux/list.h>
struct host1x_syncpt;
@ -69,8 +69,8 @@ enum cdma_event {
struct host1x_cdma {
struct mutex lock; /* controls access to shared state */
struct semaphore sem; /* signalled when event occurs */
enum cdma_event event; /* event that sem is waiting for */
struct completion complete; /* signalled when event occurs */
enum cdma_event event; /* event that complete is waiting for */
unsigned int slots_used; /* pb slots used in current submit */
unsigned int slots_free; /* pb slots free in current submit */
unsigned int first_get; /* DMAGET value, where submit begins */
@ -90,6 +90,8 @@ int host1x_cdma_init(struct host1x_cdma *cdma);
int host1x_cdma_deinit(struct host1x_cdma *cdma);
int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job);
void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2);
void host1x_cdma_push_wide(struct host1x_cdma *cdma, u32 op1, u32 op2,
u32 op3, u32 op4);
void host1x_cdma_end(struct host1x_cdma *cdma, struct host1x_job *job);
void host1x_cdma_update(struct host1x_cdma *cdma);
void host1x_cdma_peek(struct host1x_cdma *cdma, u32 dmaget, int slot,

View File

@ -120,6 +120,15 @@ static const struct host1x_info host1x05_info = {
.dma_mask = DMA_BIT_MASK(34),
};
static const struct host1x_sid_entry tegra186_sid_table[] = {
{
/* VIC */
.base = 0x1af0,
.offset = 0x30,
.limit = 0x34
},
};
static const struct host1x_info host1x06_info = {
.nb_channels = 63,
.nb_pts = 576,
@ -127,8 +136,19 @@ static const struct host1x_info host1x06_info = {
.nb_bases = 16,
.init = host1x06_init,
.sync_offset = 0x0,
.dma_mask = DMA_BIT_MASK(34),
.dma_mask = DMA_BIT_MASK(40),
.has_hypervisor = true,
.num_sid_entries = ARRAY_SIZE(tegra186_sid_table),
.sid_table = tegra186_sid_table,
};
static const struct host1x_sid_entry tegra194_sid_table[] = {
{
/* VIC */
.base = 0x1af0,
.offset = 0x30,
.limit = 0x34
},
};
static const struct host1x_info host1x07_info = {
@ -140,6 +160,8 @@ static const struct host1x_info host1x07_info = {
.sync_offset = 0x0,
.dma_mask = DMA_BIT_MASK(40),
.has_hypervisor = true,
.num_sid_entries = ARRAY_SIZE(tegra194_sid_table),
.sid_table = tegra194_sid_table,
};
static const struct of_device_id host1x_of_match[] = {
@ -154,6 +176,19 @@ static const struct of_device_id host1x_of_match[] = {
};
MODULE_DEVICE_TABLE(of, host1x_of_match);
static void host1x_setup_sid_table(struct host1x *host)
{
const struct host1x_info *info = host->info;
unsigned int i;
for (i = 0; i < info->num_sid_entries; i++) {
const struct host1x_sid_entry *entry = &info->sid_table[i];
host1x_hypervisor_writel(host, entry->offset, entry->base);
host1x_hypervisor_writel(host, entry->limit, entry->base + 4);
}
}
static int host1x_probe(struct platform_device *pdev)
{
struct host1x *host;
@ -248,6 +283,8 @@ static int host1x_probe(struct platform_device *pdev)
host->group = iommu_group_get(&pdev->dev);
if (host->group) {
struct iommu_domain_geometry *geometry;
u64 mask = dma_get_mask(host->dev);
dma_addr_t start, end;
unsigned long order;
err = iova_cache_get();
@ -275,11 +312,12 @@ static int host1x_probe(struct platform_device *pdev)
}
geometry = &host->domain->geometry;
start = geometry->aperture_start & mask;
end = geometry->aperture_end & mask;
order = __ffs(host->domain->pgsize_bitmap);
init_iova_domain(&host->iova, 1UL << order,
geometry->aperture_start >> order);
host->iova_end = geometry->aperture_end;
init_iova_domain(&host->iova, 1UL << order, start >> order);
host->iova_end = end;
}
skip_iommu:
@ -316,6 +354,9 @@ skip_iommu:
host1x_debug_init(host);
if (host->info->has_hypervisor)
host1x_setup_sid_table(host);
err = host1x_register(host);
if (err < 0)
goto fail_deinit_intr;

View File

@ -94,6 +94,12 @@ struct host1x_intr_ops {
int (*free_syncpt_irq)(struct host1x *host);
};
struct host1x_sid_entry {
unsigned int base;
unsigned int offset;
unsigned int limit;
};
struct host1x_info {
unsigned int nb_channels; /* host1x: number of channels supported */
unsigned int nb_pts; /* host1x: number of syncpoints supported */
@ -103,6 +109,8 @@ struct host1x_info {
unsigned int sync_offset; /* offset of syncpoint registers */
u64 dma_mask; /* mask of addressable memory */
bool has_hypervisor; /* has hypervisor registers */
unsigned int num_sid_entries;
const struct host1x_sid_entry *sid_table;
};
struct host1x {

View File

@ -39,8 +39,6 @@ static void push_buffer_init(struct push_buffer *pb)
static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
u32 syncpt_incrs, u32 syncval, u32 nr_slots)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct push_buffer *pb = &cdma->push_buffer;
unsigned int i;
for (i = 0; i < syncpt_incrs; i++)
@ -48,18 +46,6 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
/* after CPU incr, ensure shadow is up to date */
host1x_syncpt_load(cdma->timeout.syncpt);
/* NOP all the PB slots */
while (nr_slots--) {
u32 *p = (u32 *)(pb->mapped + getptr);
*(p++) = HOST1X_OPCODE_NOP;
*(p++) = HOST1X_OPCODE_NOP;
dev_dbg(host1x->dev, "%s: NOP at %pad+%#x\n", __func__,
&pb->dma, getptr);
getptr = (getptr + 8) & (pb->size - 1);
}
wmb();
}
/*
@ -68,20 +54,31 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
static void cdma_start(struct host1x_cdma *cdma)
{
struct host1x_channel *ch = cdma_to_channel(cdma);
u64 start, end;
if (cdma->running)
return;
cdma->last_pos = cdma->push_buffer.pos;
start = cdma->push_buffer.dma;
end = cdma->push_buffer.size + 4;
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
/* set base, put and end pointer */
host1x_ch_writel(ch, cdma->push_buffer.dma, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, lower_32_bits(start), HOST1X_CHANNEL_DMASTART);
#if HOST1X_HW >= 6
host1x_ch_writel(ch, upper_32_bits(start), HOST1X_CHANNEL_DMASTART_HI);
#endif
host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
host1x_ch_writel(ch, cdma->push_buffer.dma + cdma->push_buffer.size + 4,
HOST1X_CHANNEL_DMAEND);
#if HOST1X_HW >= 6
host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMAPUT_HI);
#endif
host1x_ch_writel(ch, lower_32_bits(end), HOST1X_CHANNEL_DMAEND);
#if HOST1X_HW >= 6
host1x_ch_writel(ch, upper_32_bits(end), HOST1X_CHANNEL_DMAEND_HI);
#endif
/* reset GET */
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP |
@ -104,6 +101,7 @@ static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr)
{
struct host1x *host1x = cdma_to_host1x(cdma);
struct host1x_channel *ch = cdma_to_channel(cdma);
u64 start, end;
if (cdma->running)
return;
@ -113,10 +111,18 @@ static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr)
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
HOST1X_CHANNEL_DMACTRL);
start = cdma->push_buffer.dma;
end = cdma->push_buffer.size + 4;
/* set base, end pointer (all of memory) */
host1x_ch_writel(ch, cdma->push_buffer.dma, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.dma + cdma->push_buffer.size,
HOST1X_CHANNEL_DMAEND);
host1x_ch_writel(ch, lower_32_bits(start), HOST1X_CHANNEL_DMASTART);
#if HOST1X_HW >= 6
host1x_ch_writel(ch, upper_32_bits(start), HOST1X_CHANNEL_DMASTART_HI);
#endif
host1x_ch_writel(ch, lower_32_bits(end), HOST1X_CHANNEL_DMAEND);
#if HOST1X_HW >= 6
host1x_ch_writel(ch, upper_32_bits(end), HOST1X_CHANNEL_DMAEND_HI);
#endif
/* set GET, by loading the value in PUT (then reset GET) */
host1x_ch_writel(ch, getptr, HOST1X_CHANNEL_DMAPUT);

View File

@ -17,6 +17,7 @@
*/
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/slab.h>
#include <trace/events/host1x.h>
@ -60,15 +61,37 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo,
static void submit_gathers(struct host1x_job *job)
{
struct host1x_cdma *cdma = &job->channel->cdma;
#if HOST1X_HW < 6
struct device *dev = job->channel->dev;
#endif
unsigned int i;
for (i = 0; i < job->num_gathers; i++) {
struct host1x_job_gather *g = &job->gathers[i];
u32 op1 = host1x_opcode_gather(g->words);
u32 op2 = g->base + g->offset;
dma_addr_t addr = g->base + g->offset;
u32 op2, op3;
trace_write_gather(cdma, g->bo, g->offset, op1 & 0xffff);
host1x_cdma_push(cdma, op1, op2);
op2 = lower_32_bits(addr);
op3 = upper_32_bits(addr);
trace_write_gather(cdma, g->bo, g->offset, g->words);
if (op3 != 0) {
#if HOST1X_HW >= 6
u32 op1 = host1x_opcode_gather_wide(g->words);
u32 op4 = HOST1X_OPCODE_NOP;
host1x_cdma_push_wide(cdma, op1, op2, op3, op4);
#else
dev_err(dev, "invalid gather for push buffer %pad\n",
&addr);
continue;
#endif
} else {
u32 op1 = host1x_opcode_gather(g->words);
host1x_cdma_push(cdma, op1, op2);
}
}
}
@ -89,6 +112,16 @@ static inline void synchronize_syncpt_base(struct host1x_job *job)
HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(value));
}
static void host1x_channel_set_streamid(struct host1x_channel *channel)
{
#if HOST1X_HW >= 6
struct iommu_fwspec *spec = dev_iommu_fwspec_get(channel->dev->parent);
u32 sid = spec ? spec->ids[0] & 0xffff : 0x7f;
host1x_ch_writel(channel, sid, HOST1X_CHANNEL_SMMU_STREAMID);
#endif
}
static int channel_submit(struct host1x_job *job)
{
struct host1x_channel *ch = job->channel;
@ -120,6 +153,8 @@ static int channel_submit(struct host1x_job *job)
goto error;
}
host1x_channel_set_streamid(ch);
/* begin a CDMA submit */
err = host1x_cdma_begin(&ch->cdma, job);
if (err) {

View File

@ -22,6 +22,7 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include "hw_host1x06_channel.h"
#include "hw_host1x06_uclass.h"
#include "hw_host1x06_vm.h"
#include "hw_host1x06_hypervisor.h"
@ -137,6 +138,11 @@ static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count)
return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count;
}
static inline u32 host1x_opcode_gather_wide(unsigned count)
{
return (12 << 28) | count;
}
#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0)
#endif

View File

@ -22,6 +22,7 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include "hw_host1x07_channel.h"
#include "hw_host1x07_uclass.h"
#include "hw_host1x07_vm.h"
#include "hw_host1x07_hypervisor.h"
@ -137,6 +138,11 @@ static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count)
return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count;
}
static inline u32 host1x_opcode_gather_wide(unsigned count)
{
return (12 << 28) | count;
}
#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0)
#endif

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 NVIDIA Corporation.
*/
#ifndef HOST1X_HW_HOST1X06_CHANNEL_H
#define HOST1X_HW_HOST1X06_CHANNEL_H
#define HOST1X_CHANNEL_SMMU_STREAMID 0x084
#endif

View File

@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 NVIDIA Corporation.
*/
#ifndef HOST1X_HW_HOST1X07_CHANNEL_H
#define HOST1X_HW_HOST1X07_CHANNEL_H
#define HOST1X_CHANNEL_SMMU_STREAMID 0x084
#endif

View File

@ -80,6 +80,32 @@ TRACE_EVENT(host1x_cdma_push,
__entry->name, __entry->op1, __entry->op2)
);
TRACE_EVENT(host1x_cdma_push_wide,
TP_PROTO(const char *name, u32 op1, u32 op2, u32 op3, u32 op4),
TP_ARGS(name, op1, op2, op3, op4),
TP_STRUCT__entry(
__field(const char *, name)
__field(u32, op1)
__field(u32, op2)
__field(u32, op3)
__field(u32, op4)
),
TP_fast_assign(
__entry->name = name;
__entry->op1 = op1;
__entry->op2 = op2;
__entry->op3 = op3;
__entry->op4 = op4;
),
TP_printk("name=%s, op1=%08x, op2=%08x, op3=%08x op4=%08x",
__entry->name, __entry->op1, __entry->op2, __entry->op3,
__entry->op4)
);
TRACE_EVENT(host1x_cdma_push_gather,
TP_PROTO(const char *name, struct host1x_bo *bo,
u32 words, u32 offset, void *cmdbuf),