/* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* vi: set ts=8 sw=8 sts=8: */ /*************************************************************************/ /*! @File apollo.c @Codingstyle LinuxKernel @Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved @License Dual MIT/GPLv2 The contents of this file are subject to the MIT license as set out below. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 2 ("GPL") in which case the provisions of GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of GPL, and not to allow others to use your version of this file under the terms of the MIT license, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by GPL as set out in the file called "GPL-COPYING" included in this distribution. If you do not delete the provisions above, a recipient may use your version of this file under the terms of either the MIT license or GPL. This License is also included in this distribution in the file called "MIT-COPYING". EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ /* * This is a device driver for the apollo testchip framework. It creates * platform devices for the pdp and ext sub-devices, and exports functions * to manage the shared interrupt handling */ #include #include #include #include #include #include #include #include #if defined(APOLLO_FAKE_INTERRUPTS) #define FAKE_INTERRUPT_TIME_MS 16 #include #include #endif #if defined(CONFIG_MTRR) #include #endif #include "apollo_drv.h" #include "apollo_regs.h" #include "tcf_clk_ctrl.h" #include "tcf_pll.h" /* Odin (3rd gen TCF FPGA) */ #include "odin_defs.h" #include "odin_regs.h" #include "bonnie_tcf.h" #include "pvrmodule.h" #if defined(SUPPORT_ION) #if defined(SUPPORT_RGX) #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) #define APOLLO_ION_HEAP_COUNT 4 #else #define APOLLO_ION_HEAP_COUNT 3 #endif #else #define APOLLO_ION_HEAP_COUNT 2 #endif #include "ion_lma_heap.h" #endif #if defined(SUPPORT_APOLLO_FPGA) || defined(SUPPORT_RGX) #include #endif #define DRV_NAME "apollo" /* Convert a byte offset to a 32 bit dword offset */ #define DWORD_OFFSET(byte_offset) ((byte_offset)>>2) /* How much memory to give to the PDP heap (used for pdp buffers). */ #define APOLLO_PDP_MEM_SIZE ((TC_DISPLAY_MEM_SIZE)*1024*1024) #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) /* How much memory to give to the secure heap. */ #define APOLLO_SECURE_MEM_SIZE ((TC_SECURE_MEM_SIZE)*1024*1024) #endif /* This is a guess of what's a minimum sensible size for the ext heap * It is only used for a warning if the ext heap is smaller, and does * not affect the functional logic in any way */ #define APOLLO_EXT_MINIMUM_MEM_SIZE (10*1024*1024) #define PCI_VENDOR_ID_POWERVR 0x1010 #define DEVICE_ID_PCI_APOLLO_FPGA 0x1CF1 #define DEVICE_ID_PCIE_APOLLO_FPGA 0x1CF2 #define APOLLO_MEM_PCI_BASENUM (2) #define APOLLO_INTERRUPT_FLAG_PDP (1 << PDP1_INT_SHIFT) #define APOLLO_INTERRUPT_FLAG_EXT (1 << EXT_INT_SHIFT) MODULE_DESCRIPTION("APOLLO testchip framework driver"); static int apollo_core_clock = RGX_TC_CORE_CLOCK_SPEED; static int apollo_mem_clock = RGX_TC_MEM_CLOCK_SPEED; module_param(apollo_core_clock, int, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(apollo_core_clock, "Apollo core clock speed"); module_param(apollo_mem_clock, int, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(apollo_mem_clock, "Apollo memory clock speed"); static unsigned long apollo_pdp_mem_size = APOLLO_PDP_MEM_SIZE; module_param(apollo_pdp_mem_size, ulong, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(apollo_pdp_mem_size, "Apollo PDP reserved memory size in bytes"); #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) static unsigned long apollo_secure_mem_size = APOLLO_PDP_MEM_SIZE; module_param(apollo_secure_mem_size, ulong, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(apollo_secure_mem_size, "Apollo secure reserved memory size in bytes"); #endif static int apollo_sys_clock = RGX_TC_SYS_CLOCK_SPEED; module_param(apollo_sys_clock, int, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(apollo_sys_clock, "Apollo system clock speed (TCF5 only)"); enum apollo_version_t { APOLLO_VERSION_TCF_2 = 0, APOLLO_VERSION_TCF_5, APOLLO_VERSION_TCF_BONNIE, ODIN_VERSION_TCF_BONNIE }; #if defined(SUPPORT_RGX) static struct debugfs_blob_wrapper apollo_debugfs_rogue_name_blobs[] = { [APOLLO_VERSION_TCF_2] = { .data = "hood", /* probably */ .size = sizeof("hood") - 1, }, [APOLLO_VERSION_TCF_5] = { .data = "fpga (unknown)", .size = sizeof("fpga (unknown)") - 1, }, [APOLLO_VERSION_TCF_BONNIE] = { .data = "bonnie", .size = sizeof("bonnie") - 1, }, [ODIN_VERSION_TCF_BONNIE] = { .data = "bonnie", .size = sizeof("bonnie") - 1, }, }; #endif /* defined(SUPPORT_RGX) */ struct apollo_interrupt_handler { bool enabled; void (*handler_function)(void *); void *handler_data; }; struct apollo_region { resource_size_t base; resource_size_t size; }; struct apollo_io_region { struct apollo_region region; void __iomem *registers; }; struct apollo_device { struct pci_dev *pdev; struct apollo_io_region tcf; struct apollo_io_region tcf_pll; spinlock_t interrupt_handler_lock; spinlock_t interrupt_enable_lock; struct apollo_interrupt_handler interrupt_handlers[APOLLO_INTERRUPT_COUNT]; struct apollo_region apollo_mem; struct platform_device *pdp_dev; resource_size_t pdp_heap_mem_base; resource_size_t pdp_heap_mem_size; struct platform_device *ext_dev; resource_size_t ext_heap_mem_base; resource_size_t ext_heap_mem_size; #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) resource_size_t secure_heap_mem_base; resource_size_t secure_heap_mem_size; #endif enum apollo_version_t version; struct thermal_zone_device *thermal_zone; #if defined(APOLLO_FAKE_INTERRUPTS) struct timer_list timer; #endif #if defined(SUPPORT_ION) struct ion_device *ion_device; struct ion_heap *ion_heaps[APOLLO_ION_HEAP_COUNT]; int ion_heap_count; #endif #if defined(CONFIG_MTRR) || (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) int mtrr; #endif #if defined(SUPPORT_APOLLO_FPGA) || defined(SUPPORT_RGX) struct dentry *debugfs_apollo_dir; #endif #if defined(SUPPORT_APOLLO_FPGA) struct apollo_io_region fpga; struct dentry *debugfs_apollo_regs; struct dentry *debugfs_apollo_pll_regs; struct dentry *debugfs_fpga_regs; struct dentry *debugfs_apollo_mem; #endif #if defined(SUPPORT_RGX) struct dentry *debugfs_rogue_name; #endif bool odin; }; #if defined(SUPPORT_APOLLO_FPGA) #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) static struct dentry *debugfs_create_file_size(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops, loff_t file_size) { struct dentry *de = debugfs_create_file(name, mode, parent, data, fops); if (de) de->d_inode->i_size = file_size; return de; } #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0)) */ static ssize_t apollo_debugfs_sanity_check(size_t *count, loff_t *ppos, resource_size_t region_size) { if (*ppos < 0) return -EFAULT; if (*ppos + *count > region_size) *count = region_size - *ppos; if ((*ppos) % sizeof(u32)) return -EINVAL; if ((*count) % sizeof(u32)) return -EINVAL; return 0; } static ssize_t apollo_debugfs_read_io(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct apollo_io_region *io = file->private_data; ssize_t err; loff_t i; err = apollo_debugfs_sanity_check(&count, ppos, io->region.size); if (err) return err; if (!access_ok(VERIFY_READ, buf, count)) return -EFAULT; for (i = 0; i < count; i += sizeof(u32), (*ppos) += sizeof(u32)) *(u32 *)(buf + i) = ioread32(io->registers + *ppos); return count; } static ssize_t apollo_debugfs_write_io(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct apollo_io_region *io = file->private_data; ssize_t err; loff_t i; err = apollo_debugfs_sanity_check(&count, ppos, io->region.size); if (err) return err; if (!access_ok(VERIFY_WRITE, buf, count)) return -EFAULT; for (i = 0; i < count; i += sizeof(u32), (*ppos) += sizeof(u32)) iowrite32(*(u32 *)(buf + i), io->registers + *ppos); return count; } static ssize_t apollo_debugfs_read_mem(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct apollo_region *region = file->private_data; void *memory; ssize_t err; err = apollo_debugfs_sanity_check(&count, ppos, region->size); if (err) return err; if (!access_ok(VERIFY_READ, buf, count)) return -EFAULT; memory = ioremap_wc(region->base + *ppos, count); if (!memory) return -EFAULT; memcpy(buf, memory, count); iounmap(memory); (*ppos) += count; return count; } static ssize_t apollo_debugfs_write_mem(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct apollo_region *region = file->private_data; void *memory; ssize_t err; err = apollo_debugfs_sanity_check(&count, ppos, region->size); if (err) return err; if (!access_ok(VERIFY_WRITE, buf, count)) return -EFAULT; memory = ioremap_wc(region->base + *ppos, count); if (!memory) return -EFAULT; memcpy(memory, buf, count); /* Flush the write combiner? */ ioread32(memory + count - sizeof(u32)); iounmap(memory); (*ppos) += count; return count; } static const struct file_operations apollo_io_debugfs_fops = { .open = simple_open, .read = apollo_debugfs_read_io, .write = apollo_debugfs_write_io, .llseek = default_llseek, }; static const struct file_operations apollo_mem_debugfs_fops = { .open = simple_open, .read = apollo_debugfs_read_mem, .write = apollo_debugfs_write_mem, .llseek = default_llseek, }; #endif /* defined(SUPPORT_APOLLO_FPGA) */ static int request_pci_io_addr(struct pci_dev *pdev, u32 index, resource_size_t offset, resource_size_t length) { resource_size_t start, end; start = pci_resource_start(pdev, index); end = pci_resource_end(pdev, index); if ((start + offset + length - 1) > end) return -EIO; if (pci_resource_flags(pdev, index) & IORESOURCE_IO) { if (request_region(start + offset, length, DRV_NAME) == NULL) return -EIO; } else { if (request_mem_region(start + offset, length, DRV_NAME) == NULL) return -EIO; } return 0; } static void release_pci_io_addr(struct pci_dev *pdev, u32 index, resource_size_t start, resource_size_t length) { if (pci_resource_flags(pdev, index) & IORESOURCE_IO) release_region(start, length); else release_mem_region(start, length); } static void pll_write_reg(struct apollo_device *apollo, resource_size_t reg_offset, u32 reg_value) { BUG_ON(reg_offset < TCF_PLL_PLL_CORE_CLK0); BUG_ON(reg_offset > apollo->tcf_pll.region.size + TCF_PLL_PLL_CORE_CLK0 - 4); /* Tweak the offset because we haven't mapped the full pll region */ iowrite32(reg_value, apollo->tcf_pll.registers + reg_offset - TCF_PLL_PLL_CORE_CLK0); } static void apollo_set_clocks(struct apollo_device *apollo) { u32 val; /* This is disabled for TCF2 since the current FPGA builds do not * like their core clocks being set (it takes apollo down). */ if (apollo->version != APOLLO_VERSION_TCF_2) { val = apollo_core_clock / 1000000; pll_write_reg(apollo, TCF_PLL_PLL_CORE_CLK0, val); val = 0x1 << PLL_CORE_DRP_GO_SHIFT; pll_write_reg(apollo, TCF_PLL_PLL_CORE_DRP_GO, val); } val = apollo_mem_clock / 1000000; pll_write_reg(apollo, TCF_PLL_PLL_MEMIF_CLK0, val); val = 0x1 << PLL_MEM_DRP_GO_SHIFT; pll_write_reg(apollo, TCF_PLL_PLL_MEM_DRP_GO, val); if (apollo->version == APOLLO_VERSION_TCF_5) { val = apollo_sys_clock / 1000000; pll_write_reg(apollo, TCF_PLL_PLL_SYSIF_CLK0, val); val = 0x1 << PLL_MEM_DRP_GO_SHIFT; pll_write_reg(apollo, TCF_PLL_PLL_SYS_DRP_GO, val); } dev_dbg(&apollo->pdev->dev, "Setting clocks to %uMHz/%uMHz\n", apollo_core_clock / 1000000, apollo_mem_clock / 1000000); udelay(400); } #if defined(CONFIG_MTRR) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0)) /* * A return value of: * 0 or more means success * -1 means we were unable to add an mtrr but we should continue * -2 means we were unable to add an mtrr but we shouldn't continue */ static int mtrr_setup(struct pci_dev *pdev, resource_size_t mem_start, resource_size_t mem_size) { int err; int mtrr; /* Reset MTRR */ mtrr = mtrr_add(mem_start, mem_size, MTRR_TYPE_UNCACHABLE, 0); if (mtrr < 0) { dev_err(&pdev->dev, "%d - %s: mtrr_add failed (%d)\n", __LINE__, __func__, mtrr); mtrr = -2; goto err_out; } err = mtrr_del(mtrr, mem_start, mem_size); if (err < 0) { dev_err(&pdev->dev, "%d - %s: mtrr_del failed (%d)\n", __LINE__, __func__, err); mtrr = -2; goto err_out; } mtrr = mtrr_add(mem_start, mem_size, MTRR_TYPE_WRBACK, 0); if (mtrr < 0) { /* Stop, but not an error as this may be already be setup */ dev_dbg(&pdev->dev, "%d - %s: mtrr_add failed (%d) - probably means the mtrr is already setup\n", __LINE__, __func__, mtrr); mtrr = -1; goto err_out; } err = mtrr_del(mtrr, mem_start, mem_size); if (err < 0) { dev_err(&pdev->dev, "%d - %s: mtrr_del failed (%d)\n", __LINE__, __func__, err); mtrr = -2; goto err_out; } if (mtrr == 0) { /* Replace 0 with a non-overlapping WRBACK mtrr */ err = mtrr_add(0, mem_start, MTRR_TYPE_WRBACK, 0); if (err < 0) { dev_err(&pdev->dev, "%d - %s: mtrr_add failed (%d)\n", __LINE__, __func__, err); mtrr = -2; goto err_out; } } mtrr = mtrr_add(mem_start, mem_size, MTRR_TYPE_WRCOMB, 0); if (mtrr < 0) { dev_err(&pdev->dev, "%d - %s: mtrr_add failed (%d)\n", __LINE__, __func__, mtrr); mtrr = -1; } err_out: return mtrr; } #endif /* defined(CONFIG_MTRR) && (LINUX_VERSION_CODEtcf.registers + TCF_CLK_CTRL_TEST_CTRL); val &= ~(ADDRESS_FORCE_MASK | PCI_TEST_MODE_MASK | HOST_ONLY_MODE_MASK | HOST_PHY_MODE_MASK); val |= (0x1 << ADDRESS_FORCE_SHIFT); iowrite32(val, apollo->tcf.registers + TCF_CLK_CTRL_TEST_CTRL); } static void apollo_devres_release(struct device *dev, void *res) { /* No extra cleanup needed */ } static void spi_write(struct apollo_device *apollo, u32 off, u32 val) { if (apollo->odin) { iowrite32(off, apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_ADDR_RDNWR); iowrite32(val, apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_WDATA); iowrite32(TCF_SPI_MST_GO_MASK, apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_GO); } else { iowrite32(off, apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_ADDR_RDNWR); iowrite32(val, apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_WDATA); iowrite32(TCF_SPI_MST_GO_MASK, apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_GO); } udelay(1000); } static int spi_read(struct apollo_device *apollo, u32 off, u32 *val) { int cnt = 0; u32 spi_mst_status; if (apollo->odin) { iowrite32(0x40000 | off, apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_ADDR_RDNWR); iowrite32(TCF_SPI_MST_GO_MASK, apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_GO); } else { iowrite32(0x40000 | off, apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_ADDR_RDNWR); iowrite32(TCF_SPI_MST_GO_MASK, apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_GO); } udelay(100); do { if (apollo->odin) { spi_mst_status = ioread32(apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_STATUS); } else { spi_mst_status = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_STATUS); } if (cnt++ > 10000) { dev_err(&apollo->pdev->dev, "spi_read: Time out reading SPI reg (0x%x)\n", off); return -1; } } while (spi_mst_status != 0x08); if (apollo->odin) { *val = ioread32(apollo->tcf.registers + ODN_REG_BANK_TCF_SPI_MASTER + ODN_SPI_MST_RDATA); } else { *val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_TCF_SPI_MST_RDATA); } return 0; } static int is_interface_aligned_es2(u32 eyes, u32 clk_taps, u32 train_ack) { u32 max_eye_start = eyes >> 16; u32 min_eye_end = eyes & 0xffff; /* If either the training or training ack failed, we haven't aligned */ if (!(clk_taps & 0x10000) || !(train_ack & 0x100)) return 0; /* If the max eye >= min eye it means the readings are nonsense */ if (max_eye_start >= min_eye_end) return 0; /* If we failed the ack pattern more than 4 times */ if (((train_ack & 0xf0) >> 4) > 4) return 0; /* If there is less than 7 taps (240ps @40ps/tap, this number should be * lower for the fpga, since its taps are bigger We should really * calculate the "7" based on the interface clock speed. */ if ((min_eye_end - max_eye_start) < 7) return 0; return 1; } static u32 sai_read_es2(struct apollo_device *apollo, u32 addr) { iowrite32(0x200 | addr, apollo->tcf.registers + 0x300); iowrite32(0x1 | addr, apollo->tcf.registers + 0x318); return ioread32(apollo->tcf.registers + 0x310); } static int iopol32_nonzero(u32 mask, void __iomem *addr) { int polnum; u32 read_value; for (polnum = 0; polnum < 50; polnum++) { read_value = ioread32(addr) & mask; if (read_value != 0) break; msleep(20); } if (polnum == 50) { pr_err(DRV_NAME " iopol32_nonzero timeout\n"); return -ETIME; } return 0; } static int apollo_align_interface_es2(struct apollo_device *apollo) { int reg = 0; u32 reg_reset_n; int reset_cnt = 0; int err = -EFAULT; bool aligned = false; /* Try to enable the core clock PLL */ spi_write(apollo, 0x1, 0x0); reg = ioread32(apollo->tcf.registers + 0x320); reg |= 0x1; iowrite32(reg, apollo->tcf.registers + 0x320); reg &= 0xfffffffe; iowrite32(reg, apollo->tcf.registers + 0x320); msleep(1000); if (spi_read(apollo, 0x2, ®)) { dev_err(&apollo->pdev->dev, "Unable to read PLL status\n"); goto err_out; } if (reg == 0x1) { /* Select DUT PLL as core clock */ reg = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); reg &= 0xfffffff7; iowrite32(reg, apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); } else { dev_err(&apollo->pdev->dev, "PLL has failed to lock, status = %x\n", reg); goto err_out; } reg_reset_n = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); while (!aligned && reset_cnt < 10 && apollo->version != APOLLO_VERSION_TCF_5) { int bank; u32 eyes; u32 clk_taps; u32 train_ack; ++reset_cnt; /* Reset the DUT to allow the SAI to retrain */ reg_reset_n &= ~(0x1 << DUT_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); udelay(100); reg_reset_n |= (0x1 << DUT_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); udelay(100); /* Assume alignment passed, if any bank fails on either DUT or * FPGA we will set this to false and try again for a max of 10 * times. */ aligned = true; /* For each of the banks */ for (bank = 0; bank < 10; bank++) { int bank_aligned = 0; /* Check alignment on the DUT */ u32 bank_base = 0x7000 + (0x1000 * bank); spi_read(apollo, bank_base + 0x4, &eyes); spi_read(apollo, bank_base + 0x3, &clk_taps); spi_read(apollo, bank_base + 0x6, &train_ack); bank_aligned = is_interface_aligned_es2( eyes, clk_taps, train_ack); if (!bank_aligned) { dev_warn(&apollo->pdev->dev, "Alignment check failed, retrying\n"); aligned = false; break; } /* Check alignment on the FPGA */ bank_base = 0xb0 + (0x10 * bank); eyes = sai_read_es2(apollo, bank_base + 0x4); clk_taps = sai_read_es2(apollo, bank_base + 0x3); train_ack = sai_read_es2(apollo, bank_base + 0x6); bank_aligned = is_interface_aligned_es2( eyes, clk_taps, train_ack); if (!bank_aligned) { dev_warn(&apollo->pdev->dev, "Alignment check failed, retrying\n"); aligned = false; break; } } } if (!aligned) { dev_err(&apollo->pdev->dev, "Unable to initialise the testchip (interface alignment failure), please restart the system.\n"); goto err_out; } if (reset_cnt > 1) { dev_dbg(&apollo->pdev->dev, "Note: The testchip required more than one reset to find a good interface alignment!\n"); dev_dbg(&apollo->pdev->dev, " This should be harmless, but if you do suspect foul play, please reset the machine.\n"); dev_dbg(&apollo->pdev->dev, " If you continue to see this message you may want to report it to IMGWORKS.\n"); } err = 0; err_out: return err; } #define SAI_STATUS_UNALIGNED 0 #define SAI_STATUS_ALIGNED 1 #define SAI_STATUS_ERROR 2 /* returns 1 for aligned, 0 for unaligned */ static int get_odin_sai_status(struct apollo_device *apollo, int bank) { void __iomem *bank_addr = apollo->tcf.registers + ODN_REG_BANK_SAI_RX_DDR(bank); void __iomem *reg_addr; u32 eyes; u32 clk_taps; u32 train_ack; int bank_aligned; reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_EYES; eyes = ioread32(reg_addr); reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_CLK_TAPS; clk_taps = ioread32(reg_addr); reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_TRAIN_ACK; train_ack = ioread32(reg_addr); #if 0 /* enable this to get debug info if the board is not aligning */ dev_info(&apollo->pdev->dev, "odin bank %d align: eyes=%08x clk_taps=%08x train_ack=%08x\n", bank, eyes, clk_taps, train_ack); #endif bank_aligned = is_interface_aligned_es2(eyes, clk_taps, train_ack); if (bank_aligned) return SAI_STATUS_ALIGNED; dev_warn(&apollo->pdev->dev, "odin bank %d is unaligned\n", bank); return SAI_STATUS_UNALIGNED; } /* Read the odin multi clocked bank align status. * Returns 1 for aligned, 0 for unaligned */ static int read_odin_mca_status(struct apollo_device *apollo) { void __iomem *bank_addr = apollo->tcf.registers + ODN_REG_BANK_MULTI_CLK_ALIGN; void __iomem *reg_addr = bank_addr + ODN_MCA_DEBUG_MCA_STATUS; u32 mca_status; mca_status = ioread32(reg_addr); #if 0 /* Enable this if there are alignment issues */ dev_info(&apollo->pdev->dev, "Odin MCA_STATUS = %08x\n", mca_status); #endif return mca_status & ODN_ALIGNMENT_FOUND_MASK; } /* Read the DUT multi clocked bank align status. * Returns 1 for aligned, 0 for unaligned */ static int read_dut_mca_status(struct apollo_device *apollo) { int mca_status; const int mca_status_register_offset = 1; /* not in bonnie_tcf.h */ int spi_address = DWORD_OFFSET(BONNIE_TCF_OFFSET_MULTI_CLK_ALIGN); spi_address = DWORD_OFFSET(BONNIE_TCF_OFFSET_MULTI_CLK_ALIGN) + mca_status_register_offset; spi_read(apollo, spi_address, &mca_status); #if 0 /* Enable this if there are alignment issues */ dev_info(&apollo->pdev->dev, "DUT MCA_STATUS = %08x\n", mca_status); #endif return mca_status & 1; /* 'alignment found' status is in bit 1 */ } /* returns 1 for aligned, 0 for unaligned */ static int get_dut_sai_status(struct apollo_device *apollo, int bank) { u32 eyes; u32 clk_taps; u32 train_ack; int bank_aligned; const u32 bank_base = DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_RX_1 + (BONNIE_TCF_OFFSET_SAI_RX_DELTA * bank)); int spi_timeout; spi_timeout = spi_read(apollo, bank_base + DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_EYES), &eyes); if (spi_timeout) return SAI_STATUS_ERROR; spi_read(apollo, bank_base + DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_CLK_TAPS), &clk_taps); spi_read(apollo, bank_base + DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_TRAIN_ACK), &train_ack); #if 0 /* enable this to get debug info if the board is not aligning */ dev_info(&apollo->pdev->dev, "dut bank %d align: eyes=%08x clk_taps=%08x train_ack=%08x\n", bank, eyes, clk_taps, train_ack); #endif bank_aligned = is_interface_aligned_es2(eyes, clk_taps, train_ack); if (bank_aligned) return SAI_STATUS_ALIGNED; dev_warn(&apollo->pdev->dev, "dut bank %d is unaligned\n", bank); return SAI_STATUS_UNALIGNED; } /* Do a hard reset on the DUT */ static int odin_hard_reset(struct apollo_device *apollo) { int reset_cnt = 0; bool aligned = false; int alignment_found; msleep(100); /* It is essential to do an SPI reset once on power-up before * doing any DUT reads via the SPI interface. */ iowrite32(1, apollo->tcf.registers /* set bit 1 low */ + ODN_CORE_EXTERNAL_RESETN); msleep(20); iowrite32(3, apollo->tcf.registers /* set bit 1 high */ + ODN_CORE_EXTERNAL_RESETN); msleep(20); while (!aligned && (reset_cnt < 20)) { int bank; /* Reset the DUT to allow the SAI to retrain */ iowrite32(2, /* set bit 0 low */ apollo->tcf.registers + ODN_CORE_EXTERNAL_RESETN); /* Hold the DUT in reset for 50mS */ msleep(50); /* Take the DUT out of reset */ iowrite32(3, /* set bit 0 hi */ apollo->tcf.registers + ODN_CORE_EXTERNAL_RESETN); reset_cnt++; /* Wait 200mS for the DUT to stabilise */ msleep(200); /* Check the odin Multi Clocked bank Align status */ alignment_found = read_odin_mca_status(apollo); dev_info(&apollo->pdev->dev, "Odin mca_status indicates %s\n", (alignment_found)?"aligned":"UNALIGNED"); /* Check the DUT MCA status */ alignment_found = read_dut_mca_status(apollo); dev_info(&apollo->pdev->dev, "DUT mca_status indicates %s\n", (alignment_found)?"aligned":"UNALIGNED"); /* If all banks have aligned then the reset was successful */ for (bank = 0; bank < 10; bank++) { int dut_aligned = 0; int odin_aligned = 0; odin_aligned = get_odin_sai_status(apollo, bank); dut_aligned = get_dut_sai_status(apollo, bank); if (dut_aligned == SAI_STATUS_ERROR) return SAI_STATUS_ERROR; if (!dut_aligned || !odin_aligned) { aligned = false; break; } aligned = true; } if (aligned) { dev_info(&apollo->pdev->dev, "all banks have aligned\n"); break; } dev_warn(&apollo->pdev->dev, "Warning- not all banks have aligned. Trying again.\n"); } if (!aligned) dev_warn(&apollo->pdev->dev, "odin_hard_reset failed\n"); return (aligned) ? 0 : 1; /* return 0 for success */ } #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0)) static int apollo_thermal_get_temp(struct thermal_zone_device *thermal, unsigned long *t) #else static int apollo_thermal_get_temp(struct thermal_zone_device *thermal, int *t) #endif { struct apollo_device *apollo; int err = -ENODEV; u32 tmp; if (!thermal) goto err_out; apollo = (struct apollo_device *)thermal->devdata; if (!apollo) goto err_out; if (spi_read(apollo, TCF_TEMP_SENSOR_SPI_OFFSET, &tmp)) { dev_err(&apollo->pdev->dev, "Failed to read apollo temperature sensor\n"); goto err_out; } /* Report this in millidegree Celsius */ *t = TCF_TEMP_SENSOR_TO_C(tmp) * 1000; err = 0; err_out: return err; } static struct thermal_zone_device_ops apollo_thermal_dev_ops = { .get_temp = apollo_thermal_get_temp, }; static int apollo_hard_reset(struct apollo_device *apollo) { u32 reg; u32 reg_reset_n = 0; /* For displaying some build info */ u32 build_inc; u32 build_owner; int err = 0; /* This is required for SPI reset which is not yet implemented. */ /*u32 aux_reset_n;*/ if (apollo->version == APOLLO_VERSION_TCF_2) { /* Power down */ reg = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); reg &= ~DUT_CTRL_VCC_0V9EN; reg &= ~DUT_CTRL_VCC_1V8EN; reg |= DUT_CTRL_VCC_IO_INH; reg |= DUT_CTRL_VCC_CORE_INH; iowrite32(reg, apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); msleep(500); } /* Put everything into reset */ iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); /* Set clock speed here, before reset. */ apollo_set_clocks(apollo); /* Put take GLB_CLKG and SCB out of reset */ reg_reset_n |= (0x1 << GLB_CLKG_EN_SHIFT); reg_reset_n |= (0x1 << SCB_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); msleep(100); if (apollo->version == APOLLO_VERSION_TCF_2) { /* Enable the voltage control regulators on DUT */ reg = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); reg |= DUT_CTRL_VCC_0V9EN; reg |= DUT_CTRL_VCC_1V8EN; reg &= ~DUT_CTRL_VCC_IO_INH; reg &= ~DUT_CTRL_VCC_CORE_INH; iowrite32(reg, apollo->tcf.registers + TCF_CLK_CTRL_DUT_CONTROL_1); msleep(300); } /* Take PDP1 and PDP2 out of reset */ reg_reset_n |= (0x1 << PDP1_RESETN_SHIFT); reg_reset_n |= (0x1 << PDP2_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); msleep(100); /* Take DDR out of reset */ reg_reset_n |= (0x1 << DDR_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); /* Take DUT_DCM out of reset */ reg_reset_n |= (0x1 << DUT_DCM_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); msleep(100); err = iopol32_nonzero(DCM_LOCK_STATUS_MASK, apollo->tcf.registers + TCF_CLK_CTRL_DCM_LOCK_STATUS); if (err != 0) goto err_out; if (apollo->version == APOLLO_VERSION_TCF_2) { /* Set ODT to a specific value that seems to provide the most * stable signals. */ spi_write(apollo, 0x11, 0x413130); } /* Take DUT out of reset */ reg_reset_n |= (0x1 << DUT_RESETN_SHIFT); iowrite32(reg_reset_n, apollo->tcf.registers + TCF_CLK_CTRL_CLK_AND_RST_CTRL); msleep(100); if (apollo->version != APOLLO_VERSION_TCF_5) { err = apollo_align_interface_es2(apollo); if (err) goto err_out; } if (apollo->version == APOLLO_VERSION_TCF_2) { /* Enable the temperature sensor */ spi_write(apollo, 0xc, 0); /* power up */ spi_write(apollo, 0xc, 2); /* reset */ spi_write(apollo, 0xc, 6); /* init & run */ /* Register a new thermal zone */ apollo->thermal_zone = thermal_zone_device_register("apollo", 0, 0, apollo, &apollo_thermal_dev_ops, NULL, 0, 0); if (IS_ERR(apollo->thermal_zone)) { dev_warn(&apollo->pdev->dev, "Couldn't register thermal zone"); apollo->thermal_zone = NULL; } } /* Check the build */ reg = ioread32(apollo->tcf.registers + 0x10); build_inc = (reg >> 12) & 0xff; build_owner = (reg >> 20) & 0xf; if (build_inc) { dev_alert(&apollo->pdev->dev, "BE WARNED: You are not running a tagged release of the FPGA!\n"); dev_alert(&apollo->pdev->dev, "Owner: 0x%01x, Inc: 0x%02x\n", build_owner, build_inc); } dev_dbg(&apollo->pdev->dev, "FPGA Release: %u.%02u\n", reg >> 8 & 0xf, reg & 0xff); err_out: return err; } static int apollo_hw_init(struct apollo_device *apollo) { u32 reg; apollo_hard_reset(apollo); apollo_set_mem_mode(apollo); if (apollo->version == APOLLO_VERSION_TCF_BONNIE) { /* Enable ASTC via SPI */ if (spi_read(apollo, 0xf, ®)) { dev_err(&apollo->pdev->dev, "Failed to read apollo ASTC register\n"); goto err_out; } reg |= 0x1 << 4; spi_write(apollo, 0xf, reg); } err_out: return 0; } static int odin_hw_init(struct apollo_device *apollo) { int err; err = odin_hard_reset(apollo); dev_info(&apollo->pdev->dev, "odin_hw_init %s\n", (err) ? "failed" : "succeeded"); return err; } /* Reads PLL status and temp sensor if there is one */ int apollo_sys_info(struct device *dev, u32 *tmp, u32 *pll) { int err = -ENODEV; struct apollo_device *apollo = devres_find(dev, apollo_devres_release, NULL, NULL); if (!apollo) { dev_err(dev, "No apollo device resources found\n"); goto err_out; } if (apollo->version == APOLLO_VERSION_TCF_5) { /* Not implemented on TCF5 */ err = 0; goto err_out; } if (apollo->version == APOLLO_VERSION_TCF_2) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0)) unsigned long t; #else int t; #endif err = apollo_thermal_get_temp(apollo->thermal_zone, &t); if (err) goto err_out; *tmp = t / 1000; } if (apollo->odin) { *pll = 0; } else { if (spi_read(apollo, 0x2, pll)) { dev_err(dev, "Failed to read PLL status\n"); goto err_out; } } err = 0; err_out: return err; } EXPORT_SYMBOL(apollo_sys_info); int apollo_core_clock_speed(struct device *dev) { return apollo_core_clock; } EXPORT_SYMBOL(apollo_core_clock_speed); #define HEX2DEC(v) ((((v) >> 4) * 10) + ((v) & 0x0F)) /* Read revision ID registers */ int apollo_sys_strings(struct device *dev, char *str_fpga_rev, size_t size_fpga_rev, char *str_tcf_core_rev, size_t size_tcf_core_rev, char *str_tcf_core_target_build_id, size_t size_tcf_core_target_build_id, char *str_pci_ver, size_t size_pci_ver, char *str_macro_ver, size_t size_macro_ver) { int err = 0; u32 val; resource_size_t host_fpga_base; void __iomem *host_fpga_registers; struct apollo_device *apollo = devres_find(dev, apollo_devres_release, NULL, NULL); if (!str_fpga_rev || !size_fpga_rev || !str_tcf_core_rev || !size_tcf_core_rev || !str_tcf_core_target_build_id || !size_tcf_core_target_build_id || !str_pci_ver || !size_pci_ver || !str_macro_ver || !size_macro_ver) { err = -EINVAL; goto err_out; } if (!apollo) { dev_err(dev, "No apollo device resources found\n"); err = -ENODEV; goto err_out; } if (apollo->odin) { char temp_str[12]; /* Read the Odin major and minor revision ID register Rx-xx */ val = ioread32(apollo->tcf.registers + ODN_CORE_REVISION); snprintf(str_tcf_core_rev, size_tcf_core_rev, "%d.%d", HEX2DEC((val & ODN_REVISION_MAJOR_MASK) >> ODN_REVISION_MAJOR_SHIFT), HEX2DEC((val & ODN_REVISION_MINOR_MASK) >> ODN_REVISION_MINOR_SHIFT)); dev_info(&apollo->pdev->dev, "Odin core revision %s\n", str_tcf_core_rev); /* Read the Odin register containing the Perforce changelist * value that the FPGA build was generated from */ val = ioread32(apollo->tcf.registers + ODN_CORE_CHANGE_SET); snprintf(str_tcf_core_target_build_id, size_tcf_core_target_build_id, "%d", (val & ODN_CHANGE_SET_SET_MASK) >> ODN_CHANGE_SET_SET_SHIFT); /* Read the Odin User_ID register containing the User ID for * identification of a modified build */ val = ioread32(apollo->tcf.registers + ODN_CORE_USER_ID); snprintf(temp_str, sizeof(temp_str), "%d", HEX2DEC((val & ODN_USER_ID_ID_MASK) >> ODN_USER_ID_ID_SHIFT)); /* Read the Odin User_Build register containing the User build * number for identification of modified builds */ val = ioread32(apollo->tcf.registers + ODN_CORE_USER_BUILD); snprintf(temp_str, sizeof(temp_str), "%d", HEX2DEC((val & ODN_USER_BUILD_BUILD_MASK) >> ODN_USER_BUILD_BUILD_SHIFT)); return 0; } /* To get some of the version information we need to read from a * register that we don't normally have mapped. Map it temporarily * (without trying to reserve it) to get the information we need. */ host_fpga_base = pci_resource_start(apollo->pdev, SYS_APOLLO_REG_PCI_BASENUM) + 0x40F0; host_fpga_registers = ioremap(host_fpga_base, 0x04); if (!host_fpga_registers) { dev_err(&apollo->pdev->dev, "Failed to map host fpga registers\n"); err = -EIO; goto err_out; } /* Create the components of the PCI and macro versions */ val = ioread32(host_fpga_registers); snprintf(str_pci_ver, size_pci_ver, "%d", HEX2DEC((val & 0x00FF0000) >> 16)); snprintf(str_macro_ver, size_macro_ver, "%d.%d", (val & 0x00000F00) >> 8, HEX2DEC((val & 0x000000FF) >> 0)); /* Unmap the register now that we no longer need it */ iounmap(host_fpga_registers); /* Create the components of the FPGA revision number */ val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_FPGA_REV_REG); snprintf(str_fpga_rev, size_fpga_rev, "%d.%d.%d", HEX2DEC((val & FPGA_REV_REG_MAJOR_MASK) >> FPGA_REV_REG_MAJOR_SHIFT), HEX2DEC((val & FPGA_REV_REG_MINOR_MASK) >> FPGA_REV_REG_MINOR_SHIFT), HEX2DEC((val & FPGA_REV_REG_MAINT_MASK) >> FPGA_REV_REG_MAINT_SHIFT)); /* Create the components of the TCF core revision number */ val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_TCF_CORE_REV_REG); snprintf(str_tcf_core_rev, size_tcf_core_rev, "%d.%d.%d", HEX2DEC((val & TCF_CORE_REV_REG_MAJOR_MASK) >> TCF_CORE_REV_REG_MAJOR_SHIFT), HEX2DEC((val & TCF_CORE_REV_REG_MINOR_MASK) >> TCF_CORE_REV_REG_MINOR_SHIFT), HEX2DEC((val & TCF_CORE_REV_REG_MAINT_MASK) >> TCF_CORE_REV_REG_MAINT_SHIFT)); /* Create the component of the TCF core target build ID */ val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_TCF_CORE_TARGET_BUILD_CFG); snprintf(str_tcf_core_target_build_id, size_tcf_core_target_build_id, "%d", (val & TCF_CORE_TARGET_BUILD_ID_MASK) >> TCF_CORE_TARGET_BUILD_ID_SHIFT); err_out: return err; } EXPORT_SYMBOL(apollo_sys_strings); static irqreturn_t apollo_irq_handler(int irq, void *data) { u32 interrupt_status; u32 interrupt_clear = 0; unsigned long flags; irqreturn_t ret = IRQ_NONE; struct apollo_device *apollo = (struct apollo_device *)data; spin_lock_irqsave(&apollo->interrupt_handler_lock, flags); #if defined(APOLLO_FAKE_INTERRUPTS) /* If we're faking interrupts pretend we got both ext and PDP ints */ interrupt_status = APOLLO_INTERRUPT_FLAG_EXT | APOLLO_INTERRUPT_FLAG_PDP; #else interrupt_status = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_STATUS); #endif if (interrupt_status & APOLLO_INTERRUPT_FLAG_EXT) { struct apollo_interrupt_handler *ext_int = &apollo->interrupt_handlers[APOLLO_INTERRUPT_EXT]; if (ext_int->enabled && ext_int->handler_function) { ext_int->handler_function(ext_int->handler_data); interrupt_clear |= APOLLO_INTERRUPT_FLAG_EXT; } ret = IRQ_HANDLED; } if (interrupt_status & APOLLO_INTERRUPT_FLAG_PDP) { struct apollo_interrupt_handler *pdp_int = &apollo->interrupt_handlers[APOLLO_INTERRUPT_PDP]; if (pdp_int->enabled && pdp_int->handler_function) { pdp_int->handler_function(pdp_int->handler_data); interrupt_clear |= APOLLO_INTERRUPT_FLAG_PDP; } ret = IRQ_HANDLED; } if (apollo->version == APOLLO_VERSION_TCF_5) { /* On TC5 the interrupt is not by the TC framework, but * by the PDP itself. So we always have to callback to the tc5 * pdp code regardless of the interrupt status of the TCF. */ struct apollo_interrupt_handler *pdp_int = &apollo->interrupt_handlers[APOLLO_INTERRUPT_TC5_PDP]; if (pdp_int->enabled && pdp_int->handler_function) { pdp_int->handler_function(pdp_int->handler_data); ret = IRQ_HANDLED; } } if (interrupt_clear) iowrite32(0xffffffff, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_CLEAR); spin_unlock_irqrestore(&apollo->interrupt_handler_lock, flags); return ret; } static irqreturn_t odin_irq_handler(int irq, void *data) { u32 interrupt_status; u32 interrupt_clear = 0; unsigned long flags; irqreturn_t ret = IRQ_NONE; struct apollo_device *apollo = (struct apollo_device *)data; spin_lock_irqsave(&apollo->interrupt_handler_lock, flags); interrupt_status = ioread32(apollo->tcf.registers +ODN_CORE_INTERRUPT_STATUS); if (interrupt_status & ODN_INTERRUPT_STATUS_DUT) { struct apollo_interrupt_handler *ext_int = &apollo->interrupt_handlers[APOLLO_INTERRUPT_EXT]; if (ext_int->enabled && ext_int->handler_function) { ext_int->handler_function(ext_int->handler_data); interrupt_clear |= ODN_INTERRUPT_CLEAR_DUT; } ret = IRQ_HANDLED; } if (interrupt_status & ODN_INTERRUPT_STATUS_PDP1) { struct apollo_interrupt_handler *pdp_int = &apollo->interrupt_handlers[APOLLO_INTERRUPT_PDP]; if (pdp_int->enabled && pdp_int->handler_function) { pdp_int->handler_function(pdp_int->handler_data); interrupt_clear |= ODN_INTERRUPT_CLEAR_PDP1; } ret = IRQ_HANDLED; } if (interrupt_clear) iowrite32(interrupt_clear, apollo->tcf.registers + ODN_CORE_INTERRUPT_CLR); spin_unlock_irqrestore(&apollo->interrupt_handler_lock, flags); return ret; } #if defined(APOLLO_FAKE_INTERRUPTS) static void apollo_irq_fake_wrapper(unsigned long data) { struct apollo_device *apollo = (struct apollo_device *)data; apollo_irq_handler(0, apollo); mod_timer(&apollo->timer, jiffies + msecs_to_jiffies(FAKE_INTERRUPT_TIME_MS)); } #endif static int apollo_enable_irq(struct apollo_device *apollo) { int err = 0; #if defined(APOLLO_FAKE_INTERRUPTS) setup_timer(&apollo->timer, apollo_irq_fake_wrapper, (unsigned long)apollo); mod_timer(&apollo->timer, jiffies + msecs_to_jiffies(FAKE_INTERRUPT_TIME_MS)); #else { u32 val; iowrite32(0, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); iowrite32(0xffffffff, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_CLEAR); /* Set sense to active high */ val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_OP_CFG) & ~(INT_SENSE_MASK); iowrite32(val, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_OP_CFG); err = request_irq(apollo->pdev->irq, apollo_irq_handler, IRQF_SHARED, DRV_NAME, apollo); } #endif return err; } static void apollo_disable_irq(struct apollo_device *apollo) { #if defined(APOLLO_FAKE_INTERRUPTS) del_timer_sync(&apollo->timer); #else iowrite32(0, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); iowrite32(0xffffffff, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_CLEAR); free_irq(apollo->pdev->irq, apollo); #endif } static int odin_enable_irq(struct apollo_device *apollo) { int err = 0; #if defined(APOLLO_FAKE_INTERRUPTS) setup_timer(&apollo->timer, apollo_irq_fake_wrapper, (unsigned long)apollo); mod_timer(&apollo->timer, jiffies + msecs_to_jiffies(FAKE_INTERRUPT_TIME_MS)); #else iowrite32(0, apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); iowrite32(0xffffffff, apollo->tcf.registers + ODN_CORE_INTERRUPT_CLR); dev_info(&apollo->pdev->dev, "Registering IRQ %d for use by Odin\n", apollo->pdev->irq); err = request_irq(apollo->pdev->irq, odin_irq_handler, IRQF_SHARED, DRV_NAME, apollo); if (err) { dev_err(&apollo->pdev->dev, "Error - IRQ %d failed to register\n", apollo->pdev->irq); } else { dev_info(&apollo->pdev->dev, "IRQ %d was successfully registered for use by Odin\n", apollo->pdev->irq); } #endif return err; } static void odin_disable_irq(struct apollo_device *apollo) { dev_info(&apollo->pdev->dev, "odin_disable_irq\n"); #if defined(APOLLO_FAKE_INTERRUPTS) del_timer_sync(&apollo->timer); #else iowrite32(0, apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); iowrite32(0xffffffff, apollo->tcf.registers + ODN_CORE_INTERRUPT_CLR); free_irq(apollo->pdev->irq, apollo); #endif } static int register_pdp_device(struct apollo_device *apollo) { int err = 0; resource_size_t reg_start = (apollo->odin) ? pci_resource_start(apollo->pdev, ODN_SYS_BAR) : pci_resource_start(apollo->pdev, SYS_APOLLO_REG_PCI_BASENUM); struct resource pdp_resources_es2[] = { DEFINE_RES_MEM_NAMED(reg_start + SYS_APOLLO_REG_PDP1_OFFSET, SYS_APOLLO_REG_PDP1_SIZE, "pdp-regs"), DEFINE_RES_MEM_NAMED(reg_start + SYS_APOLLO_REG_PLL_OFFSET + TCF_PLL_PLL_PDP_CLK0, TCF_PLL_PLL_PDP2_DRP_GO - TCF_PLL_PLL_PDP_CLK0 + 4, "pll-regs"), }; struct resource pdp_resources_tcf5[] = { DEFINE_RES_MEM_NAMED(reg_start + SYS_APOLLO_REG_PDP1_OFFSET, SYS_APOLLO_REG_PDP1_SIZE, "pdp-regs"), DEFINE_RES_MEM_NAMED(reg_start + SYS_APOLLO_REG_PLL_OFFSET + TCF_PLL_PLL_PDP_CLK0, TCF_PLL_PLL_PDP2_DRP_GO - TCF_PLL_PLL_PDP_CLK0 + 4, "pll-regs"), DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, TC5_SYS_APOLLO_REG_PCI_BASENUM) + TC5_SYS_APOLLO_REG_PDP2_OFFSET, TC5_SYS_APOLLO_REG_PDP2_SIZE, "tc5-pdp2-regs"), DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, TC5_SYS_APOLLO_REG_PCI_BASENUM) + TC5_SYS_APOLLO_REG_PDP2_FBDC_OFFSET, TC5_SYS_APOLLO_REG_PDP2_FBDC_SIZE, "tc5-pdp2-fbdc-regs"), DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, TC5_SYS_APOLLO_REG_PCI_BASENUM) + TC5_SYS_APOLLO_REG_HDMI_OFFSET, TC5_SYS_APOLLO_REG_HDMI_SIZE, "tc5-adv5711-regs"), }; struct resource pdp_resources_odin[] = { DEFINE_RES_MEM_NAMED(reg_start + ODN_PDP_REGS_OFFSET, /* start */ ODN_PDP_REGS_SIZE, /* size */ "pdp-regs"), DEFINE_RES_MEM_NAMED(reg_start + ODN_SYS_REGS_OFFSET + ODN_REG_BANK_ODN_CLK_BLK + ODN_PDP_P_CLK_OUT_DIVIDER_REG1, /* start */ ODN_PDP_P_CLK_IN_DIVIDER_REG - ODN_PDP_P_CLK_OUT_DIVIDER_REG1 + 4, /* size */ "pll-regs"), DEFINE_RES_MEM_NAMED(reg_start + ODN_SYS_REGS_OFFSET + ODN_REG_BANK_CORE, /* start */ ODN_CORE_MMCM_LOCK_STATUS + 4, /* size */ "odn-core"), }; struct apollo_pdp_platform_data pdata = { #if defined(SUPPORT_ION) .ion_device = apollo->ion_device, .ion_heap_id = ION_HEAP_APOLLO_PDP, #endif .memory_base = apollo->apollo_mem.base, .pdp_heap_memory_base = apollo->pdp_heap_mem_base, .pdp_heap_memory_size = apollo->pdp_heap_mem_size, }; struct platform_device_info pdp_device_info = { .parent = &apollo->pdev->dev, .name = (apollo->odin) ? ODN_DEVICE_NAME_PDP : APOLLO_DEVICE_NAME_PDP, .id = -2, .data = &pdata, .size_data = sizeof(pdata), .dma_mask = DMA_BIT_MASK(32), }; if (apollo->version == APOLLO_VERSION_TCF_5) { pdp_device_info.res = pdp_resources_tcf5; pdp_device_info.num_res = ARRAY_SIZE(pdp_resources_tcf5); } else if (apollo->version == APOLLO_VERSION_TCF_2 || apollo->version == APOLLO_VERSION_TCF_BONNIE) { pdp_device_info.res = pdp_resources_es2; pdp_device_info.num_res = ARRAY_SIZE(pdp_resources_es2); } else if (apollo->odin) { pdp_device_info.res = pdp_resources_odin; pdp_device_info.num_res = ARRAY_SIZE(pdp_resources_odin); } else { dev_err(&apollo->pdev->dev, "Unable to set PDP resource info for unknown apollo device\n"); } apollo->pdp_dev = platform_device_register_full(&pdp_device_info); if (IS_ERR(apollo->pdp_dev)) { err = PTR_ERR(apollo->pdp_dev); dev_err(&apollo->pdev->dev, "Failed to register PDP device (%d)\n", err); apollo->pdp_dev = NULL; goto err; } err: return err; } #if defined(SUPPORT_RGX) static int register_ext_device(struct apollo_device *apollo) { int err = 0; struct resource rogue_resources[] = { DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, SYS_RGX_REG_PCI_BASENUM), SYS_RGX_REG_REGION_SIZE, "rogue-regs"), }; struct resource odin_rogue_resources[] = { DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, ODN_DUT_SOCIF_BAR), ODN_DUT_SOCIF_SIZE, "rogue-regs"), }; struct apollo_rogue_platform_data pdata = { #if defined(SUPPORT_ION) .ion_device = apollo->ion_device, .ion_heap_id = ION_HEAP_APOLLO_ROGUE, #endif .apollo_memory_base = apollo->apollo_mem.base, .pdp_heap_memory_base = apollo->pdp_heap_mem_base, .pdp_heap_memory_size = apollo->pdp_heap_mem_size, .rogue_heap_memory_base = apollo->ext_heap_mem_base, .rogue_heap_memory_size = apollo->ext_heap_mem_size, #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) .secure_heap_memory_base = apollo->secure_heap_mem_base, .secure_heap_memory_size = apollo->secure_heap_mem_size, #endif }; struct platform_device_info rogue_device_info = { .parent = &apollo->pdev->dev, .name = APOLLO_DEVICE_NAME_ROGUE, .id = -2, .res = rogue_resources, .num_res = ARRAY_SIZE(rogue_resources), .data = &pdata, .size_data = sizeof(pdata), .dma_mask = DMA_BIT_MASK(32), }; struct platform_device_info odin_rogue_dev_info = { .parent = &apollo->pdev->dev, .name = APOLLO_DEVICE_NAME_ROGUE, .id = -2, .res = odin_rogue_resources, .num_res = ARRAY_SIZE(odin_rogue_resources), .data = &pdata, .size_data = sizeof(pdata), .dma_mask = DMA_BIT_MASK(32), }; if (apollo->odin) { apollo->ext_dev = platform_device_register_full(&odin_rogue_dev_info); } else { apollo->ext_dev = platform_device_register_full(&rogue_device_info); } if (IS_ERR(apollo->ext_dev)) { err = PTR_ERR(apollo->ext_dev); dev_err(&apollo->pdev->dev, "Failed to register rogue device (%d)\n", err); apollo->ext_dev = NULL; } return err; } #elif defined(SUPPORT_APOLLO_FPGA) static int register_ext_device(struct apollo_device *apollo) { int err = 0; struct resource fpga_resources[] = { /* FIXME: Don't overload SYS_RGX_REG_xxx for FPGA */ DEFINE_RES_MEM_NAMED(pci_resource_start(apollo->pdev, SYS_RGX_REG_PCI_BASENUM), SYS_RGX_REG_REGION_SIZE, "fpga-regs"), }; struct apollo_fpga_platform_data pdata = { .apollo_memory_base = apollo->apollo_mem.base, .pdp_heap_memory_base = apollo->pdp_heap_mem_base, .pdp_heap_memory_size = apollo->pdp_heap_mem_size, }; struct platform_device_info fpga_device_info = { .parent = &apollo->pdev->dev, .name = APOLLO_DEVICE_NAME_FPGA, .id = -1, .res = fpga_resources, .num_res = ARRAY_SIZE(fpga_resources), .data = &pdata, .size_data = sizeof(pdata), .dma_mask = DMA_BIT_MASK(32), }; apollo->ext_dev = platform_device_register_full(&fpga_device_info); if (IS_ERR(apollo->ext_dev)) { err = PTR_ERR(apollo->ext_dev); dev_err(&apollo->pdev->dev, "Failed to register fpga device (%d)\n", err); apollo->ext_dev = NULL; /* Fall through */ } return err; } #else /* defined(SUPPORT_APOLLO_FPGA) */ static inline int register_ext_device(struct apollo_device *apollo) { return 0; } #endif /* defined(SUPPORT_RGX) */ #if defined(SUPPORT_ION) static int apollo_ion_init(struct apollo_device *apollo, int mem_bar) { int i, err = 0; struct ion_platform_heap ion_heap_data[APOLLO_ION_HEAP_COUNT] = { { .type = ION_HEAP_TYPE_SYSTEM, .id = ION_HEAP_TYPE_SYSTEM, .name = "system", }, { .type = ION_HEAP_TYPE_CUSTOM, .id = ION_HEAP_APOLLO_PDP, .size = apollo->pdp_heap_mem_size, .base = apollo->pdp_heap_mem_base, .name = "apollo-pdp", }, #if defined(SUPPORT_RGX) { .type = ION_HEAP_TYPE_CUSTOM, .id = ION_HEAP_APOLLO_ROGUE, .size = apollo->ext_heap_mem_size, .base = apollo->ext_heap_mem_base, .name = "apollo-rogue", }, #endif /* defined(SUPPORT_RGX) */ #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) { .type = ION_HEAP_TYPE_CUSTOM, .id = ION_HEAP_APOLLO_SECURE, .size = apollo->secure_heap_mem_size, .base = apollo->secure_heap_mem_base, .name = "apollo-secure", }, #endif /* defined(SUPPORT_FAKE_SECURE_ION_HEAP) */ }; apollo->ion_device = ion_device_create(NULL); if (IS_ERR_OR_NULL(apollo->ion_device)) { err = PTR_ERR(apollo->ion_device); goto err_out; } err = request_pci_io_addr(apollo->pdev, mem_bar, 0, apollo->apollo_mem.size); if (err) { dev_err(&apollo->pdev->dev, "Failed to request APOLLO memory (%d)\n", err); goto err_free_device; } apollo->ion_heaps[0] = ion_heap_create(&ion_heap_data[0]); if (IS_ERR_OR_NULL(apollo->ion_heaps[0])) { err = PTR_ERR(apollo->ion_heaps[0]); apollo->ion_heaps[0] = NULL; goto err_free_device; } ion_device_add_heap(apollo->ion_device, apollo->ion_heaps[0]); for (i = 1; i < APOLLO_ION_HEAP_COUNT; i++) { bool allow_cpu_map = true; #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) if (ion_heap_data[i].id == ION_HEAP_APOLLO_SECURE) allow_cpu_map = false; #endif apollo->ion_heaps[i] = ion_lma_heap_create(&ion_heap_data[i], allow_cpu_map); if (IS_ERR_OR_NULL(apollo->ion_heaps[i])) { err = PTR_ERR(apollo->ion_heaps[i]); apollo->ion_heaps[i] = NULL; goto err_free_heaps; } ion_device_add_heap(apollo->ion_device, apollo->ion_heaps[i]); } return 0; err_free_heaps: ion_heap_destroy(apollo->ion_heaps[0]); for (i = 1; i < APOLLO_ION_HEAP_COUNT; i++) { if (!apollo->ion_heaps[i]) break; ion_lma_heap_destroy(apollo->ion_heaps[i]); } release_pci_io_addr(apollo->pdev, mem_bar, apollo->apollo_mem.base, apollo->apollo_mem.size); err_free_device: ion_device_destroy(apollo->ion_device); err_out: /* If the ptr was NULL, it is possible that err is 0 in the err path */ if (err == 0) err = -ENOMEM; return err; } static void apollo_ion_deinit(struct apollo_device *apollo, int mem_bar) { int i = 0; ion_device_destroy(apollo->ion_device); ion_heap_destroy(apollo->ion_heaps[0]); for (i = 1; i < APOLLO_ION_HEAP_COUNT; i++) ion_lma_heap_destroy(apollo->ion_heaps[i]); release_pci_io_addr(apollo->pdev, mem_bar, apollo->apollo_mem.base, apollo->apollo_mem.size); } #endif /* defined(SUPPORT_ION) */ static enum apollo_version_t apollo_detect_tc_version(struct apollo_device *apollo) { u32 val = ioread32(apollo->tcf.registers + TCF_CLK_CTRL_TCF_CORE_TARGET_BUILD_CFG); switch (val) { default: dev_err(&apollo->pdev->dev, "Unknown TCF core target build ID (0x%x) - assuming Hood ES2 - PLEASE REPORT TO ANDROID TEAM\n", val); /* Fall-through */ case 5: dev_err(&apollo->pdev->dev, "Looks like a Hood ES2 TC\n"); return APOLLO_VERSION_TCF_2; case 1: dev_err(&apollo->pdev->dev, "Looks like a TCF5\n"); return APOLLO_VERSION_TCF_5; case 6: dev_err(&apollo->pdev->dev, "Looks like a Bonnie TC\n"); return APOLLO_VERSION_TCF_BONNIE; } } static int setup_io_region(struct pci_dev *pdev, struct apollo_io_region *region, u32 index, resource_size_t offset, resource_size_t size) { int err; resource_size_t pci_phys_addr; err = request_pci_io_addr(pdev, index, offset, size); if (err) { dev_err(&pdev->dev, "Failed to request apollo registers (err=%d)\n", err); return -EIO; } pci_phys_addr = pci_resource_start(pdev, index); region->region.base = pci_phys_addr + offset; region->region.size = size; region->registers = ioremap(region->region.base, region->region.size); if (!region->registers) { dev_err(&pdev->dev, "Failed to map apollo registers\n"); release_pci_io_addr(pdev, index, region->region.base, region->region.size); return -EIO; } return 0; } static int odin_dev_init(struct apollo_device *apollo, struct pci_dev *pdev) { int err; u32 val; apollo->version = ODIN_VERSION_TCF_BONNIE; apollo->pdev = pdev; spin_lock_init(&apollo->interrupt_handler_lock); spin_lock_init(&apollo->interrupt_enable_lock); /* Reserve and map the tcf system registers */ err = setup_io_region(pdev, &apollo->tcf, ODN_SYS_BAR, ODN_SYS_REGS_OFFSET, ODN_SYS_REGS_SIZE); if (err) goto err_out; /* Setup card memory */ apollo->apollo_mem.base = pci_resource_start(pdev, ODN_DDR_BAR); apollo->apollo_mem.size = pci_resource_len(pdev, ODN_DDR_BAR); if (apollo->apollo_mem.size < apollo_pdp_mem_size) { dev_err(&pdev->dev, "Apollo MEM region (bar 4) has size of %lu which is smaller than the requested PDP heap of %lu", (unsigned long)apollo->apollo_mem.size, (unsigned long)apollo_pdp_mem_size); err = -EIO; goto err_odin_unmap_sys_registers; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) /* enable write combining */ apollo->mtrr = arch_phys_wc_add(apollo->apollo_mem.base, apollo->apollo_mem.size); if (apollo->mtrr < 0) goto err_odin_unmap_sys_registers; #elif defined(CONFIG_MTRR) /* enable mtrr region caching */ apollo->mtrr = mtrr_setup(pdev, apollo->apollo_mem.base, apollo->apollo_mem.size); if (apollo->mtrr == -2) goto err_odin_unmap_sys_registers; #endif /* Setup ranges for the device heaps */ apollo->pdp_heap_mem_size = apollo_pdp_mem_size; /* We know ext_heap_mem_size won't underflow as we've compared * apollo_mem.size against the apollo_pdp_mem_size value earlier */ apollo->ext_heap_mem_size = apollo->apollo_mem.size - apollo->pdp_heap_mem_size; if (apollo->ext_heap_mem_size < APOLLO_EXT_MINIMUM_MEM_SIZE) { dev_warn(&pdev->dev, "Apollo MEM region (bar 4) has size of %lu, with %lu apollo_pdp_mem_size only %lu bytes are left for ext device, which looks too small", (unsigned long)apollo->apollo_mem.size, (unsigned long)apollo_pdp_mem_size, (unsigned long)apollo->ext_heap_mem_size); /* Continue as this is only a 'helpful warning' not a hard * requirement */ } apollo->ext_heap_mem_base = apollo->apollo_mem.base; apollo->pdp_heap_mem_base = apollo->apollo_mem.base + apollo->ext_heap_mem_size; #if defined(SUPPORT_ION) err = apollo_ion_init(apollo, ODN_DDR_BAR); if (err) { dev_err(&pdev->dev, "Failed to initialise ION\n"); goto err_odin_unmap_sys_registers; } dev_info(&pdev->dev, "apollo_ion_init succeeded\n"); #endif val = ioread32(apollo->tcf.registers + ODN_CORE_REVISION); dev_info(&pdev->dev, "ODN_CORE_REVISION = %08x\n", val); val = ioread32(apollo->tcf.registers + ODN_CORE_CHANGE_SET); dev_info(&pdev->dev, "ODN_CORE_CHANGE_SET = %08x\n", val); val = ioread32(apollo->tcf.registers + ODN_CORE_USER_ID); dev_info(&pdev->dev, "ODN_CORE_USER_ID = %08x\n", val); val = ioread32(apollo->tcf.registers + ODN_CORE_USER_BUILD); dev_info(&pdev->dev, "ODN_CORE_USER_BUILD = %08x\n", val); err_out: return err; err_odin_unmap_sys_registers: dev_info(&pdev->dev, "odin_dev_init failed. unmapping the io regions.\n"); iounmap(apollo->tcf.registers); release_pci_io_addr(pdev, ODN_SYS_BAR, apollo->tcf.region.base, apollo->tcf.region.size); goto err_out; } static int apollo_dev_init(struct apollo_device *apollo, struct pci_dev *pdev) { int err; apollo->pdev = pdev; spin_lock_init(&apollo->interrupt_handler_lock); spin_lock_init(&apollo->interrupt_enable_lock); /* Reserve and map the tcf_clk / "sys" registers */ err = setup_io_region(pdev, &apollo->tcf, SYS_APOLLO_REG_PCI_BASENUM, SYS_APOLLO_REG_SYS_OFFSET, SYS_APOLLO_REG_SYS_SIZE); if (err) goto err_out; /* Reserve and map the tcf_pll registers */ err = setup_io_region(pdev, &apollo->tcf_pll, SYS_APOLLO_REG_PCI_BASENUM, SYS_APOLLO_REG_PLL_OFFSET + TCF_PLL_PLL_CORE_CLK0, TCF_PLL_PLL_DRP_STATUS - TCF_PLL_PLL_CORE_CLK0 + 4); if (err) goto err_unmap_sys_registers; #if defined(SUPPORT_APOLLO_FPGA) #define FPGA_REGISTERS_SIZE 4 /* If this is a special 'fgpa' build, have the apollo driver manage * the second register bar. */ err = setup_io_region(pdev, &apollo->fpga, SYS_RGX_REG_PCI_BASENUM, 0, FPGA_REGISTERS_SIZE); if (err) goto err_unmap_pll_registers; #endif /* Detect testchip version */ apollo->version = apollo_detect_tc_version(apollo); /* Setup card memory */ apollo->apollo_mem.base = pci_resource_start(pdev, APOLLO_MEM_PCI_BASENUM); apollo->apollo_mem.size = pci_resource_len(pdev, APOLLO_MEM_PCI_BASENUM); if (apollo->apollo_mem.size < apollo_pdp_mem_size) { dev_err(&pdev->dev, "Apollo MEM region (bar %d) has size of %lu which is smaller than the requested PDP heap of %lu", APOLLO_MEM_PCI_BASENUM, (unsigned long)apollo->apollo_mem.size, (unsigned long)apollo_pdp_mem_size); err = -EIO; goto err_unmap_fpga_registers; } #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) if (apollo->apollo_mem.size < (apollo_pdp_mem_size + apollo_secure_mem_size)) { dev_err(&pdev->dev, "Apollo MEM region (bar %d) has size of %lu which is smaller than the requested PDP heap of %lu plus the requested secure heap size %lu", APOLLO_MEM_PCI_BASENUM, (unsigned long)apollo->apollo_mem.size, (unsigned long)apollo_pdp_mem_size, (unsigned long)apollo_secure_mem_size); err = -EIO; goto err_unmap_fpga_registers; } #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) apollo->mtrr = arch_phys_wc_add(apollo->apollo_mem.base, apollo->apollo_mem.size); if (apollo->mtrr < 0) goto err_unmap_fpga_registers; #elif defined(CONFIG_MTRR) apollo->mtrr = mtrr_setup(pdev, apollo->apollo_mem.base, apollo->apollo_mem.size); if (apollo->mtrr == -2) goto err_unmap_fpga_registers; #endif /* Setup ranges for the device heaps */ apollo->pdp_heap_mem_size = apollo_pdp_mem_size; /* We know ext_heap_mem_size won't underflow as we've compared * apollo_mem.size against the apollo_pdp_mem_size value earlier */ apollo->ext_heap_mem_size = apollo->apollo_mem.size - apollo->pdp_heap_mem_size; #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) apollo->ext_heap_mem_size -= apollo_secure_mem_size; #endif if (apollo->ext_heap_mem_size < APOLLO_EXT_MINIMUM_MEM_SIZE) { dev_warn(&pdev->dev, "Apollo MEM region (bar %d) has size of %lu, with %lu apollo_pdp_mem_size only %lu bytes are left for ext device, which looks too small", APOLLO_MEM_PCI_BASENUM, (unsigned long)apollo->apollo_mem.size, (unsigned long)apollo_pdp_mem_size, (unsigned long)apollo->ext_heap_mem_size); /* Continue as this is only a 'helpful warning' not a hard * requirement */ } apollo->ext_heap_mem_base = apollo->apollo_mem.base; apollo->pdp_heap_mem_base = apollo->apollo_mem.base + apollo->ext_heap_mem_size; #if defined(SUPPORT_FAKE_SECURE_ION_HEAP) apollo->secure_heap_mem_base = apollo->pdp_heap_mem_base + apollo->pdp_heap_mem_size; apollo->secure_heap_mem_size = apollo_secure_mem_size; #endif #if defined(SUPPORT_ION) err = apollo_ion_init(apollo, APOLLO_MEM_PCI_BASENUM); if (err) { dev_err(&pdev->dev, "Failed to initialise ION\n"); goto err_unmap_fpga_registers; } #endif #if defined(SUPPORT_APOLLO_FPGA) || defined(SUPPORT_RGX) apollo->debugfs_apollo_dir = debugfs_create_dir("apollo", NULL); #endif #if defined(SUPPORT_APOLLO_FPGA) apollo->debugfs_apollo_regs = debugfs_create_file_size("apollo-regs", S_IRUGO, apollo->debugfs_apollo_dir, &apollo->tcf, &apollo_io_debugfs_fops, apollo->tcf.region.size); apollo->debugfs_apollo_pll_regs = debugfs_create_file_size("apollo-pll-regs", S_IRUGO, apollo->debugfs_apollo_dir, &apollo->tcf_pll, &apollo_io_debugfs_fops, apollo->tcf_pll.region.size); apollo->debugfs_fpga_regs = debugfs_create_file_size("fpga-regs", S_IRUGO, apollo->debugfs_apollo_dir, &apollo->fpga, &apollo_io_debugfs_fops, apollo->fpga.region.size); apollo->debugfs_apollo_mem = debugfs_create_file_size("apollo-mem", S_IRUGO, apollo->debugfs_apollo_dir, &apollo->apollo_mem, &apollo_mem_debugfs_fops, apollo->apollo_mem.size); #endif /* defined(SUPPORT_APOLLO_FPGA) */ #if defined(SUPPORT_RGX) apollo->debugfs_rogue_name = debugfs_create_blob("rogue-name", S_IRUGO, apollo->debugfs_apollo_dir, &apollo_debugfs_rogue_name_blobs[apollo->version]); #endif /* defined(SUPPORT_RGX) */ err_out: return err; err_unmap_fpga_registers: #if defined(SUPPORT_APOLLO_FPGA) iounmap(apollo->fpga.registers); release_pci_io_addr(pdev, SYS_RGX_REG_PCI_BASENUM, apollo->fpga.region.base, apollo->fpga.region.size); err_unmap_pll_registers: #endif /* defined(SUPPORT_APOLLO_FPGA) */ iounmap(apollo->tcf_pll.registers); release_pci_io_addr(pdev, SYS_APOLLO_REG_PCI_BASENUM, apollo->tcf_pll.region.base, apollo->tcf_pll.region.size); err_unmap_sys_registers: iounmap(apollo->tcf.registers); release_pci_io_addr(pdev, SYS_APOLLO_REG_PCI_BASENUM, apollo->tcf.region.base, apollo->tcf.region.size); goto err_out; } static void apollo_dev_cleanup(struct apollo_device *apollo) { #if defined(SUPPORT_RGX) debugfs_remove(apollo->debugfs_rogue_name); #endif #if defined(SUPPORT_APOLLO_FPGA) debugfs_remove(apollo->debugfs_apollo_mem); debugfs_remove(apollo->debugfs_fpga_regs); debugfs_remove(apollo->debugfs_apollo_pll_regs); debugfs_remove(apollo->debugfs_apollo_regs); #endif #if defined(SUPPORT_APOLLO_FPGA) || defined(SUPPORT_RGX) debugfs_remove(apollo->debugfs_apollo_dir); #endif #if defined(SUPPORT_ION) apollo_ion_deinit(apollo, APOLLO_MEM_PCI_BASENUM); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) if (apollo->mtrr >= 0) arch_phys_wc_del(apollo->mtrr); #elif defined(CONFIG_MTRR) if (apollo->mtrr >= 0) { int err; err = mtrr_del(apollo->mtrr, apollo->apollo_mem.base, apollo->apollo_mem.size); if (err < 0) dev_err(&apollo->pdev->dev, "%d - %s: mtrr_del failed (%d)\n", __LINE__, __func__, err); } #endif #if defined(SUPPORT_APOLLO_FPGA) iounmap(apollo->fpga.registers); release_pci_io_addr(apollo->pdev, SYS_RGX_REG_PCI_BASENUM, apollo->fpga.region.base, apollo->fpga.region.size); #endif iounmap(apollo->tcf_pll.registers); release_pci_io_addr(apollo->pdev, SYS_APOLLO_REG_PCI_BASENUM, apollo->tcf_pll.region.base, apollo->tcf_pll.region.size); iounmap(apollo->tcf.registers); release_pci_io_addr(apollo->pdev, SYS_APOLLO_REG_PCI_BASENUM, apollo->tcf.region.base, apollo->tcf.region.size); } static void odin_dev_cleanup(struct apollo_device *apollo) { #if defined(SUPPORT_ION) apollo_ion_deinit(apollo, ODN_DDR_BAR); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) if (apollo->mtrr >= 0) arch_phys_wc_del(apollo->mtrr); #elif defined(CONFIG_MTRR) if (apollo->mtrr >= 0) { int err; err = mtrr_del(apollo->mtrr, apollo->apollo_mem.base, apollo->apollo_mem.size); if (err < 0) dev_err(&apollo->pdev->dev, "%d - %s: mtrr_del failed (%d)\n", __LINE__, __func__, err); } #endif dev_info(&apollo->pdev->dev, "odin_dev_cleanup - unmapping the odin system io region\n"); iounmap(apollo->tcf.registers); release_pci_io_addr(apollo->pdev, ODN_SYS_BAR, apollo->tcf.region.base, apollo->tcf.region.size); } static int apollo_init(struct pci_dev *pdev, const struct pci_device_id *id) { struct apollo_device *apollo; int err = 0; if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) return -ENOMEM; apollo = devres_alloc(apollo_devres_release, sizeof(*apollo), GFP_KERNEL); if (!apollo) { err = -ENOMEM; goto err_out; } devres_add(&pdev->dev, apollo); err = pci_enable_device(pdev); if (err) { dev_err(&pdev->dev, "error - pci_enable_device returned %d\n", err); goto err_out; } if (pdev->vendor == PCI_VENDOR_ID_ODIN && pdev->device == DEVICE_ID_ODIN) { dev_info(&pdev->dev, "PCI_VENDOR_ID_ODIN DEVICE_ID_ODIN\n"); /* The device is an Odin */ apollo->odin = true; err = odin_dev_init(apollo, pdev); if (err) { dev_err(&pdev->dev, "odin_dev_init failed\n"); goto err_disable_device; } err = odin_hw_init(apollo); if (!err) { err = odin_enable_irq(apollo); if (err) { dev_err(&pdev->dev, "Failed to initialise IRQ\n"); } else { dev_info(&pdev->dev, "odin_enable_irq succeeded\n"); } } if (err) { odin_dev_cleanup(apollo); goto err_disable_device; } } else { apollo->odin = false; err = apollo_dev_init(apollo, pdev); if (err) goto err_disable_device; err = apollo_hw_init(apollo); if (err) goto err_dev_cleanup; err = apollo_enable_irq(apollo); if (err) { dev_err(&pdev->dev, "Failed to initialise IRQ\n"); goto err_dev_cleanup; } } #if defined(APOLLO_FAKE_INTERRUPTS) dev_warn(&pdev->dev, "WARNING: Faking interrupts every %d ms", FAKE_INTERRUPT_TIME_MS); #endif /* Register ext and pdp platform devices * Failures here aren't critical? */ register_pdp_device(apollo); register_ext_device(apollo); devres_remove_group(&pdev->dev, NULL); goto apollo_init_return; err_dev_cleanup: apollo_dev_cleanup(apollo); err_disable_device: pci_disable_device(pdev); err_out: devres_release_group(&pdev->dev, NULL); apollo_init_return: if (err) dev_err(&pdev->dev, "apollo_init failed\n"); return err; } static void apollo_exit(struct pci_dev *pdev) { int i; struct apollo_device *apollo; dev_err(&pdev->dev, "apollo_exit\n"); apollo = devres_find(&pdev->dev, apollo_devres_release, NULL, NULL); if (!apollo) goto apollo_exit_end; if (apollo->thermal_zone) thermal_zone_device_unregister(apollo->thermal_zone); if (apollo->pdp_dev) { dev_info(&pdev->dev, "platform_device_unregister pdp_dev\n"); platform_device_unregister(apollo->pdp_dev); } if (apollo->ext_dev) { dev_info(&pdev->dev, "platform_device_unregister ext_dev\n"); platform_device_unregister(apollo->ext_dev); } if (apollo->odin) { odin_disable_irq(apollo); odin_dev_cleanup(apollo); } else { for (i = 0; i < APOLLO_INTERRUPT_COUNT; i++) apollo_disable_interrupt(&pdev->dev, i); apollo_disable_irq(apollo); apollo_dev_cleanup(apollo); } dev_info(&pdev->dev, "pci_disable_device\n"); pci_disable_device(pdev); apollo_exit_end: dev_info(&pdev->dev, "end of apollo_exit\n"); } struct pci_device_id apollo_pci_tbl[] = { { PCI_VDEVICE(POWERVR, DEVICE_ID_PCI_APOLLO_FPGA) }, { PCI_VDEVICE(POWERVR, DEVICE_ID_PCIE_APOLLO_FPGA) }, { PCI_VDEVICE(ODIN, DEVICE_ID_ODIN) }, { }, }; static struct pci_driver apollo_pci_driver = { .name = DRV_NAME, .id_table = apollo_pci_tbl, .probe = apollo_init, .remove = apollo_exit, }; module_pci_driver(apollo_pci_driver); MODULE_DEVICE_TABLE(pci, apollo_pci_tbl); static u32 apollo_interrupt_id_to_flag(int interrupt_id) { switch (interrupt_id) { case APOLLO_INTERRUPT_PDP: return APOLLO_INTERRUPT_FLAG_PDP; case APOLLO_INTERRUPT_EXT: return APOLLO_INTERRUPT_FLAG_EXT; default: BUG(); } } static void apollo_enable_interrupt_register(struct apollo_device *apollo, int interrupt_id) { u32 val; if (interrupt_id == APOLLO_INTERRUPT_PDP || interrupt_id == APOLLO_INTERRUPT_EXT) { val = ioread32( apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); val |= apollo_interrupt_id_to_flag(interrupt_id); iowrite32(val, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); } } static void apollo_disable_interrupt_register(struct apollo_device *apollo, int interrupt_id) { u32 val; if (interrupt_id == APOLLO_INTERRUPT_PDP || interrupt_id == APOLLO_INTERRUPT_EXT) { val = ioread32( apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); val &= ~(apollo_interrupt_id_to_flag(interrupt_id)); iowrite32(val, apollo->tcf.registers + TCF_CLK_CTRL_INTERRUPT_ENABLE); } } static u32 odin_interrupt_id_to_flag(int interrupt_id) { switch (interrupt_id) { case APOLLO_INTERRUPT_PDP: return ODN_INTERRUPT_ENABLE_PDP1; case APOLLO_INTERRUPT_EXT: return ODN_INTERRUPT_ENABLE_DUT; default: BUG(); } } static void odin_enable_interrupt_register(struct apollo_device *apollo, int interrupt_id) { u32 val; u32 flag; dev_info(&apollo->pdev->dev, "odin_enable_interrupt_register\n"); switch (interrupt_id) { case APOLLO_INTERRUPT_PDP: dev_info(&apollo->pdev->dev, "Enabling Odin PDP interrupts\n"); break; case APOLLO_INTERRUPT_EXT: dev_info(&apollo->pdev->dev, "Enabling Odin DUT interrupts\n"); break; default: dev_err(&apollo->pdev->dev, "Error - illegal interrupt id\n"); return; } val = ioread32(apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); flag = odin_interrupt_id_to_flag(interrupt_id); val |= flag; iowrite32(val, apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); } static void odin_disable_interrupt_register(struct apollo_device *apollo, int interrupt_id) { u32 val; dev_info(&apollo->pdev->dev, "odin_disable_interrupt_register\n"); switch (interrupt_id) { case APOLLO_INTERRUPT_PDP: dev_info(&apollo->pdev->dev, "Disabling Odin PDP interrupts\n"); break; case APOLLO_INTERRUPT_EXT: dev_info(&apollo->pdev->dev, "Disabling Odin DUT interrupts\n"); break; default: dev_err(&apollo->pdev->dev, "Error - illegal interrupt id\n"); return; } val = ioread32(apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); val &= ~(odin_interrupt_id_to_flag(interrupt_id)); iowrite32(val, apollo->tcf.registers + ODN_CORE_INTERRUPT_ENABLE); } int apollo_enable(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); return pci_enable_device(pdev); } EXPORT_SYMBOL(apollo_enable); void apollo_disable(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); pci_disable_device(pdev); } EXPORT_SYMBOL(apollo_disable); int apollo_set_interrupt_handler(struct device *dev, int interrupt_id, void (*handler_function)(void *), void *data) { struct apollo_device *apollo = devres_find(dev, apollo_devres_release, NULL, NULL); int err = 0; unsigned long flags; if (!apollo) { dev_err(dev, "No apollo device resources found\n"); err = -ENODEV; goto err_out; } if (interrupt_id < 0 || interrupt_id >= APOLLO_INTERRUPT_COUNT) { dev_err(dev, "Invalid interrupt ID (%d)\n", interrupt_id); err = -EINVAL; goto err_out; } spin_lock_irqsave(&apollo->interrupt_handler_lock, flags); apollo->interrupt_handlers[interrupt_id].handler_function = handler_function; apollo->interrupt_handlers[interrupt_id].handler_data = data; spin_unlock_irqrestore(&apollo->interrupt_handler_lock, flags); err_out: return err; } EXPORT_SYMBOL(apollo_set_interrupt_handler); int apollo_enable_interrupt(struct device *dev, int interrupt_id) { struct apollo_device *apollo = devres_find(dev, apollo_devres_release, NULL, NULL); int err = 0; unsigned long flags; if (!apollo) { dev_err(dev, "No apollo device resources found\n"); err = -ENODEV; goto err_out; } if (interrupt_id < 0 || interrupt_id >= APOLLO_INTERRUPT_COUNT) { dev_err(dev, "Invalid interrupt ID (%d)\n", interrupt_id); err = -EINVAL; goto err_out; } spin_lock_irqsave(&apollo->interrupt_enable_lock, flags); if (apollo->interrupt_handlers[interrupt_id].enabled) { dev_warn(dev, "Interrupt ID %d already enabled\n", interrupt_id); err = -EEXIST; goto err_unlock; } apollo->interrupt_handlers[interrupt_id].enabled = true; if (apollo->odin) odin_enable_interrupt_register(apollo, interrupt_id); else apollo_enable_interrupt_register(apollo, interrupt_id); err_unlock: spin_unlock_irqrestore(&apollo->interrupt_enable_lock, flags); err_out: return err; } EXPORT_SYMBOL(apollo_enable_interrupt); int apollo_disable_interrupt(struct device *dev, int interrupt_id) { struct apollo_device *apollo = devres_find(dev, apollo_devres_release, NULL, NULL); int err = 0; unsigned long flags; if (!apollo) { dev_err(dev, "No apollo device resources found\n"); err = -ENODEV; goto err_out; } if (interrupt_id < 0 || interrupt_id >= APOLLO_INTERRUPT_COUNT) { dev_err(dev, "Invalid interrupt ID (%d)\n", interrupt_id); err = -EINVAL; goto err_out; } spin_lock_irqsave(&apollo->interrupt_enable_lock, flags); if (!apollo->interrupt_handlers[interrupt_id].enabled) { dev_warn(dev, "Interrupt ID %d already disabled\n", interrupt_id); } apollo->interrupt_handlers[interrupt_id].enabled = false; if (apollo->odin) odin_disable_interrupt_register(apollo, interrupt_id); else apollo_disable_interrupt_register(apollo, interrupt_id); spin_unlock_irqrestore(&apollo->interrupt_enable_lock, flags); err_out: return err; } EXPORT_SYMBOL(apollo_disable_interrupt);