From b7c61d511df4404c9bbc8f4b987059c55abfe2b4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 19 Nov 2018 11:27:39 +0100 Subject: gpu: host1x: Resize channel register region on Tegra186 and later The register region allocated per channel was decreased from 16384 bytes to 256 bytes on Tegra186 and later. Resize the region to make sure every channel (instead of only the first) is properly programmed. Suggested-by: Mikko Perttunen Reviewed-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/channel_hw.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index d188f9068b91..95ea81172a83 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -26,7 +26,6 @@ #include "../intr.h" #include "../job.h" -#define HOST1X_CHANNEL_SIZE 16384 #define TRACE_MAX_LENGTH 128U static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, @@ -203,7 +202,11 @@ static void enable_gather_filter(struct host1x *host, static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev, unsigned int index) { - ch->regs = dev->regs + index * HOST1X_CHANNEL_SIZE; +#if HOST1X_HW < 6 + ch->regs = dev->regs + index * 0x4000; +#else + ch->regs = dev->regs + index * 0x100; +#endif enable_gather_filter(dev, ch); return 0; } -- cgit v1.2.3-59-g8ed1b From 2fc777ba8422e4a38cae61537ad6a26435a86fb8 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 19 Nov 2018 11:31:30 +0100 Subject: gpu: host1x: Fix syncpoint ID field size on Tegra186 The number of syncpoints on Tegra186 is 576 and therefore no longer fits into 8 bits. Increase the size of the syncpoint ID field to 10 in order to accomodate all syncpoints. Reviewed-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/hw_host1x06_uclass.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/hw/hw_host1x06_uclass.h b/drivers/gpu/host1x/hw/hw_host1x06_uclass.h index 4457486c72b0..e599e15bf999 100644 --- a/drivers/gpu/host1x/hw/hw_host1x06_uclass.h +++ b/drivers/gpu/host1x/hw/hw_host1x06_uclass.h @@ -59,7 +59,7 @@ static inline u32 host1x_uclass_incr_syncpt_r(void) host1x_uclass_incr_syncpt_r() static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v) { - return (v & 0xff) << 8; + return (v & 0xff) << 10; } #define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \ host1x_uclass_incr_syncpt_cond_f(v) -- cgit v1.2.3-59-g8ed1b From 0dc34e19f66eb84744af2fea4235302da39fd7e7 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 23 Nov 2018 13:06:37 +0100 Subject: drm/tegra: vic: Implement explicit reset support Tegra supports generic PM domains on 64-bit ARM, and if that is enabled, the power domain code will make sure that resets are asserted and deasserted at appropriate points in time. If generic PM domains are not implemented, such as on 32-bit Tegra, the resets need to be asserted and deasserted explicitly by the driver. Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/vic.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c index 9f657a63b0bb..396f29f289f5 100644 --- a/drivers/gpu/drm/tegra/vic.c +++ b/drivers/gpu/drm/tegra/vic.c @@ -38,6 +38,7 @@ struct vic { struct iommu_domain *domain; struct device *dev; struct clk *clk; + struct reset_control *rst; /* Platform configuration */ const struct vic_config *config; @@ -56,13 +57,37 @@ static void vic_writel(struct vic *vic, u32 value, unsigned int offset) static int vic_runtime_resume(struct device *dev) { struct vic *vic = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(vic->clk); + if (err < 0) + return err; + + usleep_range(10, 20); + + err = reset_control_deassert(vic->rst); + if (err < 0) + goto disable; + + usleep_range(10, 20); + + return 0; - return clk_prepare_enable(vic->clk); +disable: + clk_disable_unprepare(vic->clk); + return err; } static int vic_runtime_suspend(struct device *dev) { struct vic *vic = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(vic->rst); + if (err < 0) + return err; + + usleep_range(2000, 4000); clk_disable_unprepare(vic->clk); @@ -323,6 +348,14 @@ static int vic_probe(struct platform_device *pdev) return PTR_ERR(vic->clk); } + if (!dev->pm_domain) { + vic->rst = devm_reset_control_get(dev, "vic"); + if (IS_ERR(vic->rst)) { + dev_err(&pdev->dev, "failed to get reset\n"); + return PTR_ERR(vic->rst); + } + } + vic->falcon.dev = dev; vic->falcon.regs = vic->regs; vic->falcon.ops = &vic_falcon_ops; -- cgit v1.2.3-59-g8ed1b From 4eab8c0ebd384f080cce2ee466356b446acb283f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 23 Nov 2018 13:06:38 +0100 Subject: drm/tegra: falcon: Fix error handling The ->alloc() callback in struct falcon_ops returns an ERR_PTR()-encoded error code on failure, so it needs to be properly checked for, otherwise subsequent code may dereference an invalid pointer. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/falcon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tegra/falcon.c b/drivers/gpu/drm/tegra/falcon.c index f685e72949d1..78c7a0156601 100644 --- a/drivers/gpu/drm/tegra/falcon.c +++ b/drivers/gpu/drm/tegra/falcon.c @@ -141,9 +141,9 @@ int falcon_load_firmware(struct falcon *falcon) /* allocate iova space for the firmware */ falcon->firmware.vaddr = falcon->ops->alloc(falcon, firmware->size, &falcon->firmware.paddr); - if (!falcon->firmware.vaddr) { - dev_err(falcon->dev, "dma memory mapping failed\n"); - return -ENOMEM; + if (IS_ERR(falcon->firmware.vaddr)) { + dev_err(falcon->dev, "DMA memory mapping failed\n"); + return PTR_ERR(falcon->firmware.vaddr); } /* copy firmware image into local area. this also ensures endianness */ -- cgit v1.2.3-59-g8ed1b From b91bf997ea6efe9c48540572d693415fddaf0c3a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 23 Nov 2018 13:06:39 +0100 Subject: drm/tegra: falcon: Wait for memory scrubbing to complete Before booting the Falcon processor, make sure to wait for memory scrubbing to complete. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/falcon.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/tegra/falcon.c b/drivers/gpu/drm/tegra/falcon.c index 78c7a0156601..352d05feabb0 100644 --- a/drivers/gpu/drm/tegra/falcon.c +++ b/drivers/gpu/drm/tegra/falcon.c @@ -197,11 +197,19 @@ void falcon_exit(struct falcon *falcon) int falcon_boot(struct falcon *falcon) { unsigned long offset; + u32 value; int err; if (!falcon->firmware.vaddr) return -EINVAL; + err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value, + (value & (FALCON_DMACTL_IMEM_SCRUBBING | + FALCON_DMACTL_DMEM_SCRUBBING)) == 0, + 10, 10000); + if (err < 0) + return err; + falcon_writel(falcon, 0, FALCON_DMACTL); /* setup the address of the binary data so Falcon can access it later */ -- cgit v1.2.3-59-g8ed1b From ac1bdbf22bebabfee150bb5c33a02c4d0119d185 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 25 Jan 2018 14:10:44 +0200 Subject: gpu: host1x: Add Tegra194 support The host1x hardware found on Tegra194 is mostly backwards compatible with the version found on Tegra186, with the notable exceptions of the increased number of syncpoints and mlocks. In addition, some rarely used features such as syncpoint wait bases were dropped and some registers had to move around to accomodate the increased number of syncpoints. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/Makefile | 3 +- drivers/gpu/host1x/dev.c | 13 ++ drivers/gpu/host1x/hw/debug_hw_1x06.c | 7 +- drivers/gpu/host1x/hw/host1x07.c | 44 ++++++ drivers/gpu/host1x/hw/host1x07.h | 26 ++++ drivers/gpu/host1x/hw/host1x07_hardware.h | 142 +++++++++++++++++++ drivers/gpu/host1x/hw/hw_host1x07_hypervisor.h | 32 +++++ drivers/gpu/host1x/hw/hw_host1x07_uclass.h | 181 +++++++++++++++++++++++++ drivers/gpu/host1x/hw/hw_host1x07_vm.h | 46 +++++++ drivers/gpu/host1x/hw/syncpt_hw.c | 4 + 10 files changed, 496 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/host1x/hw/host1x07.c create mode 100644 drivers/gpu/host1x/hw/host1x07.h create mode 100644 drivers/gpu/host1x/hw/host1x07_hardware.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x07_hypervisor.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x07_uclass.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x07_vm.h diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile index b92016ce09b7..096017b8789d 100644 --- a/drivers/gpu/host1x/Makefile +++ b/drivers/gpu/host1x/Makefile @@ -13,6 +13,7 @@ host1x-y = \ hw/host1x02.o \ hw/host1x04.o \ hw/host1x05.o \ - hw/host1x06.o + hw/host1x06.o \ + hw/host1x07.o obj-$(CONFIG_TEGRA_HOST1X) += host1x.o diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index de6bc4e7fa23..419d8929a98f 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -44,6 +44,7 @@ #include "hw/host1x04.h" #include "hw/host1x05.h" #include "hw/host1x06.h" +#include "hw/host1x07.h" void host1x_hypervisor_writel(struct host1x *host1x, u32 v, u32 r) { @@ -130,7 +131,19 @@ static const struct host1x_info host1x06_info = { .has_hypervisor = true, }; +static const struct host1x_info host1x07_info = { + .nb_channels = 63, + .nb_pts = 704, + .nb_mlocks = 32, + .nb_bases = 0, + .init = host1x07_init, + .sync_offset = 0x0, + .dma_mask = DMA_BIT_MASK(40), + .has_hypervisor = true, +}; + static const struct of_device_id host1x_of_match[] = { + { .compatible = "nvidia,tegra194-host1x", .data = &host1x07_info, }, { .compatible = "nvidia,tegra186-host1x", .data = &host1x06_info, }, { .compatible = "nvidia,tegra210-host1x", .data = &host1x05_info, }, { .compatible = "nvidia,tegra124-host1x", .data = &host1x04_info, }, diff --git a/drivers/gpu/host1x/hw/debug_hw_1x06.c b/drivers/gpu/host1x/hw/debug_hw_1x06.c index b503c740c022..8b749516c051 100644 --- a/drivers/gpu/host1x/hw/debug_hw_1x06.c +++ b/drivers/gpu/host1x/hw/debug_hw_1x06.c @@ -62,9 +62,12 @@ static void host1x_debug_show_channel_fifo(struct host1x *host, struct host1x_channel *ch, struct output *o) { - u32 val, rd_ptr, wr_ptr, start, end; +#if HOST1X_HW <= 6 + u32 rd_ptr, wr_ptr, start, end; u32 payload = INVALID_PAYLOAD; unsigned int data_count = 0; +#endif + u32 val; host1x_debug_output(o, "%u: fifo:\n", ch->id); @@ -78,6 +81,7 @@ static void host1x_debug_show_channel_fifo(struct host1x *host, val = host1x_ch_readl(ch, HOST1X_CHANNEL_CMDFIFO_RDATA); host1x_debug_output(o, "CMDFIFO_RDATA %08x\n", val); +#if HOST1X_HW <= 6 /* Peek pointer values are invalid during SLCG, so disable it */ host1x_hypervisor_writel(host, 0x1, HOST1X_HV_ICG_EN_OVERRIDE); @@ -127,6 +131,7 @@ static void host1x_debug_show_channel_fifo(struct host1x *host, host1x_hypervisor_writel(host, 0x0, HOST1X_HV_CMDFIFO_PEEK_CTRL); host1x_hypervisor_writel(host, 0x0, HOST1X_HV_ICG_EN_OVERRIDE); +#endif } static void host1x_debug_show_mlocks(struct host1x *host, struct output *o) diff --git a/drivers/gpu/host1x/hw/host1x07.c b/drivers/gpu/host1x/hw/host1x07.c new file mode 100644 index 000000000000..04b779a53f08 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x07.c @@ -0,0 +1,44 @@ +/* + * Host1x init for Tegra194 SoCs + * + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 hw specification */ +#include "host1x07.h" +#include "host1x07_hardware.h" + +/* include code */ +#define HOST1X_HW 7 + +#include "cdma_hw.c" +#include "channel_hw.c" +#include "debug_hw.c" +#include "intr_hw.c" +#include "syncpt_hw.c" + +#include "../dev.h" + +int host1x07_init(struct host1x *host) +{ + host->channel_op = &host1x_channel_ops; + host->cdma_op = &host1x_cdma_ops; + host->cdma_pb_op = &host1x_pushbuffer_ops; + host->syncpt_op = &host1x_syncpt_ops; + host->intr_op = &host1x_intr_ops; + host->debug_op = &host1x_debug_ops; + + return 0; +} diff --git a/drivers/gpu/host1x/hw/host1x07.h b/drivers/gpu/host1x/hw/host1x07.h new file mode 100644 index 000000000000..57b19f354274 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x07.h @@ -0,0 +1,26 @@ +/* + * Host1x init for Tegra194 SoCs + * + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#ifndef HOST1X_HOST1X07_H +#define HOST1X_HOST1X07_H + +struct host1x; + +int host1x07_init(struct host1x *host); + +#endif diff --git a/drivers/gpu/host1x/hw/host1x07_hardware.h b/drivers/gpu/host1x/hw/host1x07_hardware.h new file mode 100644 index 000000000000..1353e7ab71dd --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x07_hardware.h @@ -0,0 +1,142 @@ +/* + * Tegra host1x Register Offsets for Tegra194 + * + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#ifndef __HOST1X_HOST1X07_HARDWARE_H +#define __HOST1X_HOST1X07_HARDWARE_H + +#include +#include + +#include "hw_host1x07_uclass.h" +#include "hw_host1x07_vm.h" +#include "hw_host1x07_hypervisor.h" + +static inline u32 host1x_class_host_wait_syncpt( + unsigned indx, unsigned threshold) +{ + return host1x_uclass_wait_syncpt_indx_f(indx) + | host1x_uclass_wait_syncpt_thresh_f(threshold); +} + +static inline u32 host1x_class_host_load_syncpt_base( + unsigned indx, unsigned threshold) +{ + return host1x_uclass_load_syncpt_base_base_indx_f(indx) + | host1x_uclass_load_syncpt_base_value_f(threshold); +} + +static inline u32 host1x_class_host_wait_syncpt_base( + unsigned indx, unsigned base_indx, unsigned offset) +{ + return host1x_uclass_wait_syncpt_base_indx_f(indx) + | host1x_uclass_wait_syncpt_base_base_indx_f(base_indx) + | host1x_uclass_wait_syncpt_base_offset_f(offset); +} + +static inline u32 host1x_class_host_incr_syncpt_base( + unsigned base_indx, unsigned offset) +{ + return host1x_uclass_incr_syncpt_base_base_indx_f(base_indx) + | host1x_uclass_incr_syncpt_base_offset_f(offset); +} + +static inline u32 host1x_class_host_incr_syncpt( + unsigned cond, unsigned indx) +{ + return host1x_uclass_incr_syncpt_cond_f(cond) + | host1x_uclass_incr_syncpt_indx_f(indx); +} + +static inline u32 host1x_class_host_indoff_reg_write( + unsigned mod_id, unsigned offset, bool auto_inc) +{ + u32 v = host1x_uclass_indoff_indbe_f(0xf) + | host1x_uclass_indoff_indmodid_f(mod_id) + | host1x_uclass_indoff_indroffset_f(offset); + if (auto_inc) + v |= host1x_uclass_indoff_autoinc_f(1); + return v; +} + +static inline u32 host1x_class_host_indoff_reg_read( + unsigned mod_id, unsigned offset, bool auto_inc) +{ + u32 v = host1x_uclass_indoff_indmodid_f(mod_id) + | host1x_uclass_indoff_indroffset_f(offset) + | host1x_uclass_indoff_rwn_read_v(); + if (auto_inc) + v |= host1x_uclass_indoff_autoinc_f(1); + return v; +} + +/* cdma opcodes */ +static inline u32 host1x_opcode_setclass( + unsigned class_id, unsigned offset, unsigned mask) +{ + return (0 << 28) | (offset << 16) | (class_id << 6) | mask; +} + +static inline u32 host1x_opcode_incr(unsigned offset, unsigned count) +{ + return (1 << 28) | (offset << 16) | count; +} + +static inline u32 host1x_opcode_nonincr(unsigned offset, unsigned count) +{ + return (2 << 28) | (offset << 16) | count; +} + +static inline u32 host1x_opcode_mask(unsigned offset, unsigned mask) +{ + return (3 << 28) | (offset << 16) | mask; +} + +static inline u32 host1x_opcode_imm(unsigned offset, unsigned value) +{ + return (4 << 28) | (offset << 16) | value; +} + +static inline u32 host1x_opcode_imm_incr_syncpt(unsigned cond, unsigned indx) +{ + return host1x_opcode_imm(host1x_uclass_incr_syncpt_r(), + host1x_class_host_incr_syncpt(cond, indx)); +} + +static inline u32 host1x_opcode_restart(unsigned address) +{ + return (5 << 28) | (address >> 4); +} + +static inline u32 host1x_opcode_gather(unsigned count) +{ + return (6 << 28) | count; +} + +static inline u32 host1x_opcode_gather_nonincr(unsigned offset, unsigned count) +{ + return (6 << 28) | (offset << 16) | BIT(15) | count; +} + +static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count) +{ + return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count; +} + +#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0) + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x07_hypervisor.h b/drivers/gpu/host1x/hw/hw_host1x07_hypervisor.h new file mode 100644 index 000000000000..2b99d68d3040 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x07_hypervisor.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + * + */ + +#define HOST1X_HV_SYNCPT_PROT_EN 0x1ac4 +#define HOST1X_HV_SYNCPT_PROT_EN_CH_EN BIT(1) +#define HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(x) (0x2020 + (x * 4)) +#define HOST1X_HV_CMDFIFO_PEEK_CTRL 0x233c +#define HOST1X_HV_CMDFIFO_PEEK_CTRL_ADDR(x) (x) +#define HOST1X_HV_CMDFIFO_PEEK_CTRL_CHANNEL(x) ((x) << 16) +#define HOST1X_HV_CMDFIFO_PEEK_CTRL_ENABLE BIT(31) +#define HOST1X_HV_CMDFIFO_PEEK_READ 0x2340 +#define HOST1X_HV_CMDFIFO_PEEK_PTRS 0x2344 +#define HOST1X_HV_CMDFIFO_PEEK_PTRS_WR_PTR_V(x) (((x) >> 16) & 0xfff) +#define HOST1X_HV_CMDFIFO_PEEK_PTRS_RD_PTR_V(x) ((x) & 0xfff) +#define HOST1X_HV_CMDFIFO_SETUP(x) (0x2588 + (x * 4)) +#define HOST1X_HV_CMDFIFO_SETUP_LIMIT_V(x) (((x) >> 16) & 0xfff) +#define HOST1X_HV_CMDFIFO_SETUP_BASE_V(x) ((x) & 0xfff) +#define HOST1X_HV_ICG_EN_OVERRIDE 0x2aa8 diff --git a/drivers/gpu/host1x/hw/hw_host1x07_uclass.h b/drivers/gpu/host1x/hw/hw_host1x07_uclass.h new file mode 100644 index 000000000000..7e4e3b377f91 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x07_uclass.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + * + */ + + /* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ + +#ifndef HOST1X_HW_HOST1X07_UCLASS_H +#define HOST1X_HW_HOST1X07_UCLASS_H + +static inline u32 host1x_uclass_incr_syncpt_r(void) +{ + return 0x0; +} +#define HOST1X_UCLASS_INCR_SYNCPT \ + host1x_uclass_incr_syncpt_r() +static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v) +{ + return (v & 0xff) << 10; +} +#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \ + host1x_uclass_incr_syncpt_cond_f(v) +static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v) +{ + return (v & 0xff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \ + host1x_uclass_incr_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_r(void) +{ + return 0x8; +} +#define HOST1X_UCLASS_WAIT_SYNCPT \ + host1x_uclass_wait_syncpt_r() +static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \ + host1x_uclass_wait_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \ + host1x_uclass_wait_syncpt_thresh_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_r(void) +{ + return 0x9; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE \ + host1x_uclass_wait_syncpt_base_r() +static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 16; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_wait_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_load_syncpt_base_r(void) +{ + return 0xb; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE \ + host1x_uclass_load_syncpt_base_r() +static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_load_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \ + host1x_uclass_load_syncpt_base_value_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_incr_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_incr_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_indoff_r(void) +{ + return 0x2d; +} +#define HOST1X_UCLASS_INDOFF \ + host1x_uclass_indoff_r() +static inline u32 host1x_uclass_indoff_indbe_f(u32 v) +{ + return (v & 0xf) << 28; +} +#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \ + host1x_uclass_indoff_indbe_f(v) +static inline u32 host1x_uclass_indoff_autoinc_f(u32 v) +{ + return (v & 0x1) << 27; +} +#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \ + host1x_uclass_indoff_autoinc_f(v) +static inline u32 host1x_uclass_indoff_indmodid_f(u32 v) +{ + return (v & 0xff) << 18; +} +#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \ + host1x_uclass_indoff_indmodid_f(v) +static inline u32 host1x_uclass_indoff_indroffset_f(u32 v) +{ + return (v & 0xffff) << 2; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_indoff_rwn_read_v(void) +{ + return 1; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x07_vm.h b/drivers/gpu/host1x/hw/hw_host1x07_vm.h new file mode 100644 index 000000000000..7e4629e77a2a --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x07_vm.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + * + */ + +#define HOST1X_CHANNEL_DMASTART 0x0000 +#define HOST1X_CHANNEL_DMASTART_HI 0x0004 +#define HOST1X_CHANNEL_DMAPUT 0x0008 +#define HOST1X_CHANNEL_DMAPUT_HI 0x000c +#define HOST1X_CHANNEL_DMAGET 0x0010 +#define HOST1X_CHANNEL_DMAGET_HI 0x0014 +#define HOST1X_CHANNEL_DMAEND 0x0018 +#define HOST1X_CHANNEL_DMAEND_HI 0x001c +#define HOST1X_CHANNEL_DMACTRL 0x0020 +#define HOST1X_CHANNEL_DMACTRL_DMASTOP BIT(0) +#define HOST1X_CHANNEL_DMACTRL_DMAGETRST BIT(1) +#define HOST1X_CHANNEL_DMACTRL_DMAINITGET BIT(2) +#define HOST1X_CHANNEL_CMDFIFO_STAT 0x0024 +#define HOST1X_CHANNEL_CMDFIFO_STAT_EMPTY BIT(13) +#define HOST1X_CHANNEL_CMDFIFO_RDATA 0x0028 +#define HOST1X_CHANNEL_CMDP_OFFSET 0x0030 +#define HOST1X_CHANNEL_CMDP_CLASS 0x0034 +#define HOST1X_CHANNEL_CHANNELSTAT 0x0038 +#define HOST1X_CHANNEL_CMDPROC_STOP 0x0048 +#define HOST1X_CHANNEL_TEARDOWN 0x004c + +#define HOST1X_SYNC_SYNCPT_CPU_INCR(x) (0x6400 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(x) (0x6464 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(x) (0x652c + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(x) (0x6590 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT(x) (0x8080 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_INT_THRESH(x) (0x8d00 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP(x) (0xa604 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP_CH(v) (((v) & 0x3f) << 8) diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c index a23bb3352d02..d946660d47f8 100644 --- a/drivers/gpu/host1x/hw/syncpt_hw.c +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -37,10 +37,12 @@ static void syncpt_restore(struct host1x_syncpt *sp) */ static void syncpt_restore_wait_base(struct host1x_syncpt *sp) { +#if HOST1X_HW < 7 struct host1x *host = sp->host; host1x_sync_writel(host, sp->base_val, HOST1X_SYNC_SYNCPT_BASE(sp->id)); +#endif } /* @@ -48,10 +50,12 @@ static void syncpt_restore_wait_base(struct host1x_syncpt *sp) */ static void syncpt_read_wait_base(struct host1x_syncpt *sp) { +#if HOST1X_HW < 7 struct host1x *host = sp->host; sp->base_val = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id)); +#endif } /* -- cgit v1.2.3-59-g8ed1b From d6b9bc025859521dae719d8fb8278b0c39b34861 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 26 Oct 2018 10:59:38 +0200 Subject: drm/tegra: vic: Add Tegra194 support The Video Image Composer (VIC) generation found on Tegra194 is backwards compatible with its predecessor found on Tegra186. Reviewed-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 1 + drivers/gpu/drm/tegra/vic.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 65ea4988b332..4b70ce664c41 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1274,6 +1274,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra194-display", }, { .compatible = "nvidia,tegra194-dc", }, { .compatible = "nvidia,tegra194-sor", }, + { .compatible = "nvidia,tegra194-vic", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c index 396f29f289f5..d47983deb1cf 100644 --- a/drivers/gpu/drm/tegra/vic.c +++ b/drivers/gpu/drm/tegra/vic.c @@ -307,10 +307,18 @@ static const struct vic_config vic_t186_config = { .version = 0x18, }; +#define NVIDIA_TEGRA_194_VIC_FIRMWARE "nvidia/tegra194/vic.bin" + +static const struct vic_config vic_t194_config = { + .firmware = NVIDIA_TEGRA_194_VIC_FIRMWARE, + .version = 0x19, +}; + static const struct of_device_id vic_match[] = { { .compatible = "nvidia,tegra124-vic", .data = &vic_t124_config }, { .compatible = "nvidia,tegra210-vic", .data = &vic_t210_config }, { .compatible = "nvidia,tegra186-vic", .data = &vic_t186_config }, + { .compatible = "nvidia,tegra194-vic", .data = &vic_t194_config }, { }, }; @@ -451,3 +459,6 @@ MODULE_FIRMWARE(NVIDIA_TEGRA_210_VIC_FIRMWARE); #if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) MODULE_FIRMWARE(NVIDIA_TEGRA_186_VIC_FIRMWARE); #endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) +MODULE_FIRMWARE(NVIDIA_TEGRA_194_VIC_FIRMWARE); +#endif -- cgit v1.2.3-59-g8ed1b From 0cffbde2e318cc1214486a84be0f31045b1fa493 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 29 Nov 2018 17:24:34 +0100 Subject: drm/tegra: hub: Enable all required clocks The display architecture on Tegra186 and Tegra194 requires that there be some valid clock on all domains before accessing any display register. A further requirement is that in addition to the host1x, hub, disp and dsc clocks, all the head clocks (pclk0-2 on Tegra186 or pclk0-3 on Tegra194) must also be enabled. Implement this logic within the display hub driver to ensure the clocks are always enabled at the right time. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/hub.c | 48 +++++++++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/tegra/hub.h | 3 +++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c index 6112d9042979..922a48d5a483 100644 --- a/drivers/gpu/drm/tegra/hub.c +++ b/drivers/gpu/drm/tegra/hub.c @@ -742,7 +742,9 @@ static const struct host1x_client_ops tegra_display_hub_ops = { static int tegra_display_hub_probe(struct platform_device *pdev) { + struct device_node *child = NULL; struct tegra_display_hub *hub; + struct clk *clk; unsigned int i; int err; @@ -801,6 +803,34 @@ static int tegra_display_hub_probe(struct platform_device *pdev) return err; } + hub->num_heads = of_get_child_count(pdev->dev.of_node); + + hub->clk_heads = devm_kcalloc(&pdev->dev, hub->num_heads, sizeof(clk), + GFP_KERNEL); + if (!hub->clk_heads) + return -ENOMEM; + + for (i = 0; i < hub->num_heads; i++) { + child = of_get_next_child(pdev->dev.of_node, child); + if (!child) { + dev_err(&pdev->dev, "failed to find node for head %u\n", + i); + return -ENODEV; + } + + clk = devm_get_clk_from_child(&pdev->dev, child, "dc"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock for head %u\n", + i); + of_node_put(child); + return PTR_ERR(clk); + } + + hub->clk_heads[i] = clk; + } + + of_node_put(child); + /* XXX: enable clock across reset? */ err = reset_control_assert(hub->rst); if (err < 0) @@ -840,12 +870,16 @@ static int tegra_display_hub_remove(struct platform_device *pdev) static int __maybe_unused tegra_display_hub_suspend(struct device *dev) { struct tegra_display_hub *hub = dev_get_drvdata(dev); + unsigned int i = hub->num_heads; int err; err = reset_control_assert(hub->rst); if (err < 0) return err; + while (i--) + clk_disable_unprepare(hub->clk_heads[i]); + clk_disable_unprepare(hub->clk_hub); clk_disable_unprepare(hub->clk_dsc); clk_disable_unprepare(hub->clk_disp); @@ -856,6 +890,7 @@ static int __maybe_unused tegra_display_hub_suspend(struct device *dev) static int __maybe_unused tegra_display_hub_resume(struct device *dev) { struct tegra_display_hub *hub = dev_get_drvdata(dev); + unsigned int i; int err; err = clk_prepare_enable(hub->clk_disp); @@ -870,13 +905,22 @@ static int __maybe_unused tegra_display_hub_resume(struct device *dev) if (err < 0) goto disable_dsc; + for (i = 0; i < hub->num_heads; i++) { + err = clk_prepare_enable(hub->clk_heads[i]); + if (err < 0) + goto disable_heads; + } + err = reset_control_deassert(hub->rst); if (err < 0) - goto disable_hub; + goto disable_heads; return 0; -disable_hub: +disable_heads: + while (i--) + clk_disable_unprepare(hub->clk_heads[i]); + clk_disable_unprepare(hub->clk_hub); disable_dsc: clk_disable_unprepare(hub->clk_dsc); diff --git a/drivers/gpu/drm/tegra/hub.h b/drivers/gpu/drm/tegra/hub.h index 6696a85fc1f2..479087c0705a 100644 --- a/drivers/gpu/drm/tegra/hub.h +++ b/drivers/gpu/drm/tegra/hub.h @@ -49,6 +49,9 @@ struct tegra_display_hub { struct clk *clk_hub; struct reset_control *rst; + unsigned int num_heads; + struct clk **clk_heads; + const struct tegra_display_hub_soc *soc; struct tegra_windowgroup *wgrps; }; -- cgit v1.2.3-59-g8ed1b From e75d04771ad60cb65005f35a7eb65caa6e7c266c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 29 Nov 2018 17:24:35 +0100 Subject: drm/tegra: dc: Don't clean up unused display controller Display controllers that don't own any windows and which therefore aren't currently being assigned a primary plane are not registered as a CRTC with the DRM framework. They request neither a syncpoint nor an interrupt because they don't use them, so avoid cleaning up those resources. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index f80e82e16475..607a6ea17ecc 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1978,6 +1978,23 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) return IRQ_HANDLED; } +static bool tegra_dc_has_window_groups(struct tegra_dc *dc) +{ + unsigned int i; + + if (!dc->soc->wgrps) + return true; + + for (i = 0; i < dc->soc->num_wgrps; i++) { + const struct tegra_windowgroup_soc *wgrp = &dc->soc->wgrps[i]; + + if (wgrp->dc == dc->pipe && wgrp->num_windows > 0) + return true; + } + + return false; +} + static int tegra_dc_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); @@ -1993,22 +2010,8 @@ static int tegra_dc_init(struct host1x_client *client) * assign a primary plane to them, which in turn will cause KMS to * crash. */ - if (dc->soc->wgrps) { - bool has_wgrps = false; - unsigned int i; - - for (i = 0; i < dc->soc->num_wgrps; i++) { - const struct tegra_windowgroup_soc *wgrp = &dc->soc->wgrps[i]; - - if (wgrp->dc == dc->pipe && wgrp->num_windows > 0) { - has_wgrps = true; - break; - } - } - - if (!has_wgrps) - return 0; - } + if (!tegra_dc_has_window_groups(dc)) + return 0; dc->syncpt = host1x_syncpt_request(client, flags); if (!dc->syncpt) @@ -2094,6 +2097,9 @@ static int tegra_dc_exit(struct host1x_client *client) struct tegra_dc *dc = host1x_client_to_dc(client); int err; + if (!tegra_dc_has_window_groups(dc)) + return 0; + devm_free_irq(dc->dev, dc->irq, dc); err = tegra_dc_rgb_exit(dc); -- cgit v1.2.3-59-g8ed1b From 8e2988a76c26f25178b1aa3f345c92dbef40cac4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Dec 2018 15:46:03 +0100 Subject: drm/tegra: sor: Support for audio over HDMI This code is very similar to the audio over HDMI support on older chips. Interoperation with the audio codec is done via a pair of codec scratch registers and an interrupt that is raised at the SOR when the codec has written those registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/sor.h | 68 +++++++++++++ 2 files changed, 297 insertions(+) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index b129da2e5afd..22a54434a757 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -407,6 +409,7 @@ struct tegra_sor { const struct tegra_sor_soc *soc; void __iomem *regs; unsigned int index; + unsigned int irq; struct reset_control *rst; struct clk *clk_parent; @@ -433,6 +436,11 @@ struct tegra_sor { struct delayed_work scdc; bool scdc_enabled; + + struct { + unsigned int sample_rate; + unsigned int channels; + } audio; }; struct tegra_sor_state { @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor, return 0; } +static void tegra_sor_write_eld(struct tegra_sor *sor) +{ + size_t length = drm_eld_size(sor->output.connector.eld), i; + + for (i = 0; i < length; i++) + tegra_sor_writel(sor, i << 8 | sor->output.connector.eld[i], + SOR_AUDIO_HDA_ELD_BUFWR); + + /* + * The HDA codec will always report an ELD buffer size of 96 bytes and + * the HDA codec driver will check that each byte read from the buffer + * is valid. Therefore every byte must be written, even if no 96 bytes + * were parsed from EDID. + */ + for (i = length; i < 96; i++) + tegra_sor_writel(sor, i << 8 | 0, SOR_AUDIO_HDA_ELD_BUFWR); +} + +static void tegra_sor_audio_prepare(struct tegra_sor *sor) +{ + u32 value; + + tegra_sor_write_eld(sor); + + value = SOR_AUDIO_HDA_PRESENSE_ELDV | SOR_AUDIO_HDA_PRESENSE_PD; + tegra_sor_writel(sor, value, SOR_AUDIO_HDA_PRESENSE); +} + +static void tegra_sor_audio_unprepare(struct tegra_sor *sor) +{ + tegra_sor_writel(sor, 0, SOR_AUDIO_HDA_PRESENSE); +} + +static int tegra_sor_hdmi_enable_audio_infoframe(struct tegra_sor *sor) +{ + u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)]; + struct hdmi_audio_infoframe frame; + u32 value; + int err; + + err = hdmi_audio_infoframe_init(&frame); + if (err < 0) { + dev_err(sor->dev, "failed to setup audio infoframe: %d\n", err); + return err; + } + + frame.channels = sor->audio.channels; + + err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(sor->dev, "failed to pack audio infoframe: %d\n", err); + return err; + } + + tegra_sor_hdmi_write_infopack(sor, buffer, err); + + value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL); + value |= INFOFRAME_CTRL_CHECKSUM_ENABLE; + value |= INFOFRAME_CTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL); + + return 0; +} + +static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_AUDIO_CNTRL); + + /* select HDA audio input */ + value &= ~SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_MASK); + value |= SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_HDA); + + /* inject null samples */ + if (sor->audio.channels != 2) + value &= ~SOR_AUDIO_CNTRL_INJECT_NULLSMPL; + else + value |= SOR_AUDIO_CNTRL_INJECT_NULLSMPL; + + value |= SOR_AUDIO_CNTRL_AFIFO_FLUSH; + + tegra_sor_writel(sor, value, SOR_AUDIO_CNTRL); + + /* enable advertising HBR capability */ + tegra_sor_writel(sor, SOR_AUDIO_SPARE_HBR_ENABLE, SOR_AUDIO_SPARE); + + tegra_sor_writel(sor, 0, SOR_HDMI_ACR_CTRL); + + value = SOR_HDMI_SPARE_ACR_PRIORITY_HIGH | + SOR_HDMI_SPARE_CTS_RESET(1) | + SOR_HDMI_SPARE_HW_CTS_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_SPARE); + + /* enable HW CTS */ + value = SOR_HDMI_ACR_SUBPACK_LOW_SB1(0); + tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_LOW); + + /* allow packet to be sent */ + value = SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_HIGH); + + /* reset N counter and enable lookup */ + 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); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0320); + tegra_sor_writel(sor, 4096, SOR_AUDIO_NVAL_0320); + + tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0441); + tegra_sor_writel(sor, 4704, SOR_AUDIO_NVAL_0441); + + tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0882); + tegra_sor_writel(sor, 9408, SOR_AUDIO_NVAL_0882); + + 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); + 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); + 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); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_1920); + tegra_sor_writel(sor, 24576, SOR_AUDIO_NVAL_1920); + + value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_N); + value &= ~SOR_HDMI_AUDIO_N_RESET; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N); + + tegra_sor_hdmi_enable_audio_infoframe(sor); +} + static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor) { u32 value; @@ -2148,6 +2294,11 @@ static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor) tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL); } +static void tegra_sor_hdmi_audio_disable(struct tegra_sor *sor) +{ + tegra_sor_hdmi_disable_audio_infoframe(sor); +} + static struct tegra_sor_hdmi_settings * tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency) { @@ -2243,6 +2394,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) u32 value; int err; + tegra_sor_audio_unprepare(sor); tegra_sor_hdmi_scdc_stop(sor); err = tegra_sor_detach(sor); @@ -2651,6 +2803,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) dev_err(sor->dev, "failed to wakeup SOR: %d\n", err); tegra_sor_hdmi_scdc_start(sor); + tegra_sor_audio_prepare(sor); } static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = { @@ -2666,6 +2819,7 @@ static int tegra_sor_init(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int connector = DRM_MODE_CONNECTOR_Unknown; int encoder = DRM_MODE_ENCODER_NONE; + u32 value; int err; if (!sor->aux) { @@ -2759,6 +2913,15 @@ static int tegra_sor_init(struct host1x_client *client) if (err < 0) return err; + /* + * Enable and unmask the HDA codec SCRATCH0 register interrupt. This + * is used for interoperability between the HDA codec driver and the + * HDMI/DP driver. + */ + value = SOR_INT_CODEC_SCRATCH1 | SOR_INT_CODEC_SCRATCH0; + tegra_sor_writel(sor, value, SOR_INT_ENABLE); + tegra_sor_writel(sor, value, SOR_INT_MASK); + return 0; } @@ -2767,6 +2930,9 @@ static int tegra_sor_exit(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int err; + tegra_sor_writel(sor, 0, SOR_INT_MASK); + tegra_sor_writel(sor, 0, SOR_INT_ENABLE); + tegra_output_exit(&sor->output); if (sor->aux) { @@ -3037,6 +3203,54 @@ static int tegra_sor_parse_dt(struct tegra_sor *sor) 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; + u32 value; + + value = tegra_sor_readl(sor, SOR_INT_STATUS); + tegra_sor_writel(sor, value, SOR_INT_STATUS); + + if (value & SOR_INT_CODEC_SCRATCH0) { + value = tegra_sor_readl(sor, SOR_AUDIO_HDA_CODEC_SCRATCH0); + + if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) { + unsigned int format, sample_rate, channels; + + 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_sor_hdmi_audio_enable(sor); + } else { + tegra_sor_hdmi_audio_disable(sor); + } + } + + return IRQ_HANDLED; +} + static int tegra_sor_probe(struct platform_device *pdev) { struct device_node *np; @@ -3119,6 +3333,21 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } + err = platform_get_irq(pdev, 0); + if (err < 0) { + dev_err(&pdev->dev, "failed to get IRQ: %d\n", err); + goto remove; + } + + sor->irq = err; + + err = devm_request_irq(sor->dev, sor->irq, tegra_sor_irq, 0, + dev_name(sor->dev), sor); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto remove; + } + if (!pdev->dev.pm_domain) { sor->rst = devm_reset_control_get(&pdev->dev, "sor"); if (IS_ERR(sor->rst)) { diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index fb0854d92a27..13f7e68bec42 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -364,12 +364,28 @@ #define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) #define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) +#define SOR_HDMI_ACR_CTRL 0xb1 + +#define SOR_HDMI_ACR_0320_SUBPACK_LOW 0xb2 +#define SOR_HDMI_ACR_SUBPACK_LOW_SB1(x) (((x) & 0xff) << 24) + +#define SOR_HDMI_ACR_0320_SUBPACK_HIGH 0xb3 +#define SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE (1 << 31) + +#define SOR_HDMI_ACR_0441_SUBPACK_LOW 0xb4 +#define SOR_HDMI_ACR_0441_SUBPACK_HIGH 0xb5 + #define SOR_HDMI_CTRL 0xc0 #define SOR_HDMI_CTRL_ENABLE (1 << 30) #define SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) #define SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10) #define SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0) +#define SOR_HDMI_SPARE 0xcb +#define SOR_HDMI_SPARE_ACR_PRIORITY_HIGH (1 << 31) +#define SOR_HDMI_SPARE_CTS_RESET(x) (((x) & 0x7) << 16) +#define SOR_HDMI_SPARE_HW_CTS_ENABLE (1 << 0) + #define SOR_REFCLK 0xe6 #define SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8) #define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6) @@ -378,10 +394,62 @@ #define SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1) #define SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0) +#define SOR_AUDIO_CNTRL 0xfc +#define SOR_AUDIO_CNTRL_INJECT_NULLSMPL (1 << 29) +#define SOR_AUDIO_CNTRL_SOURCE_SELECT(x) (((x) & 0x3) << 20) +#define SOURCE_SELECT_MASK 0x3 +#define SOURCE_SELECT_HDA 0x2 +#define SOURCE_SELECT_SPDIF 0x1 +#define SOURCE_SELECT_AUTO 0x0 +#define SOR_AUDIO_CNTRL_AFIFO_FLUSH (1 << 12) + +#define SOR_AUDIO_SPARE 0xfe +#define SOR_AUDIO_SPARE_HBR_ENABLE (1 << 27) + +#define SOR_AUDIO_NVAL_0320 0xff +#define SOR_AUDIO_NVAL_0441 0x100 +#define SOR_AUDIO_NVAL_0882 0x101 +#define SOR_AUDIO_NVAL_1764 0x102 +#define SOR_AUDIO_NVAL_0480 0x103 +#define SOR_AUDIO_NVAL_0960 0x104 +#define SOR_AUDIO_NVAL_1920 0x105 + +#define SOR_AUDIO_HDA_CODEC_SCRATCH0 0x10a +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30) +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff + +#define SOR_AUDIO_HDA_ELD_BUFWR 0x10c +#define SOR_AUDIO_HDA_ELD_BUFWR_INDEX(x) (((x) & 0xff) << 8) +#define SOR_AUDIO_HDA_ELD_BUFWR_DATA(x) (((x) & 0xff) << 0) + +#define SOR_AUDIO_HDA_PRESENSE 0x10d +#define SOR_AUDIO_HDA_PRESENSE_ELDV (1 << 1) +#define SOR_AUDIO_HDA_PRESENSE_PD (1 << 0) + +#define SOR_AUDIO_AVAL_0320 0x10f +#define SOR_AUDIO_AVAL_0441 0x110 +#define SOR_AUDIO_AVAL_0882 0x111 +#define SOR_AUDIO_AVAL_1764 0x112 +#define SOR_AUDIO_AVAL_0480 0x113 +#define SOR_AUDIO_AVAL_0960 0x114 +#define SOR_AUDIO_AVAL_1920 0x115 + +#define SOR_INT_STATUS 0x11c +#define SOR_INT_CODEC_CP_REQUEST (1 << 2) +#define SOR_INT_CODEC_SCRATCH1 (1 << 1) +#define SOR_INT_CODEC_SCRATCH0 (1 << 0) + +#define SOR_INT_MASK 0x11d +#define SOR_INT_ENABLE 0x11e + #define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123 #define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124 #define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125 +#define SOR_HDMI_AUDIO_N 0x13c +#define SOR_HDMI_AUDIO_N_LOOKUP (1 << 28) +#define SOR_HDMI_AUDIO_N_RESET (1 << 20) + #define SOR_HDMI2_CTRL 0x13e #define SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1) #define SOR_HDMI2_CTRL_SCRAMBLE (1 << 0) -- cgit v1.2.3-59-g8ed1b From 016a48b3d66e94c99253b79ca16ac656a9de5740 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Dec 2018 18:57:41 +0100 Subject: drm/tegra: sor: Remove temporary workaround Remove the temporary workaround of storing the Tegra186 HDMI/DP I/O pad ID in the SOR driver. The definition has long been available in the soc/tegra/pmc.h header file. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 22a54434a757..07a077bd73e4 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -31,14 +31,6 @@ #include "sor.h" #include "trace.h" -/* - * XXX Remove this after the commit adding it to soc/tegra/pmc.h has been - * merged. Having this around after the commit is merged should be safe since - * the preprocessor will effectively replace all occurrences and therefore no - * duplicate will be defined. - */ -#define TEGRA_IO_PAD_HDMI_DP0 26 - #define SOR_REKEY 0x38 struct tegra_sor_hdmi_settings { -- cgit v1.2.3-59-g8ed1b From 180b46ecdc9f7762aba65c0b029f59c7a6b314fd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Dec 2018 18:56:47 +0100 Subject: drm/tegra: sor: Reset the SOR if possible If the SOR is already up and running when the kernel driver is probed, setting a mode will typically fail. This can be seen for example on Jetson TX2. Under certain circumstances the generic power domain code will cause the SOR to be reset. However, if the power domain is never powered off (this can happen if the HDA controller is enabled, which is part of the same power domain as the SOR), then the SOR will end up not getting reset and fail to properly set a mode. To work around this, try to get the reset control and assert/deassert it, irrespective of whether or not a generic power domain is attached to the SOR. On platforms where the kernel implements generic power domains (up to Tegra210) this will fail, because the power domain will already have acquired an exclusive reference to the reset control. But on recent platforms there the BPMP provides an ABI to control power domains, it's possible to acquire the reset control from SOR and use it to put the SOR into a known good state at probe time. The proper solution for this is to make the SOR driver capable of dealing with hardware that's already up and running (by first grace- fully shutting it down, or perhaps by seamlessly transitioning to the kernel driver and taking over the running display configuration). That is fairly involved, though, so we'll go with this quickfix for now. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 07a077bd73e4..ef8692b7075a 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -3340,14 +3340,23 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - if (!pdev->dev.pm_domain) { - sor->rst = devm_reset_control_get(&pdev->dev, "sor"); - if (IS_ERR(sor->rst)) { - err = PTR_ERR(sor->rst); + sor->rst = devm_reset_control_get(&pdev->dev, "sor"); + if (IS_ERR(sor->rst)) { + err = PTR_ERR(sor->rst); + + if (err != -EBUSY || WARN_ON(!pdev->dev.pm_domain)) { dev_err(&pdev->dev, "failed to get reset control: %d\n", err); goto remove; } + + /* + * At this point, the reset control is most likely being used + * by the generic power domain implementation. With any luck + * the power domain will have taken care of resetting the SOR + * and we don't have to do anything. + */ + sor->rst = NULL; } sor->clk = devm_clk_get(&pdev->dev, NULL); -- cgit v1.2.3-59-g8ed1b