diff options
Diffstat (limited to 'drivers')
48 files changed, 4582 insertions, 2536 deletions
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index 5987e0ba8c2d..3807e572aef7 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -83,6 +83,23 @@ config SATA_AHCI_PLATFORM If unsure, say N. +config SATA_AHCI_TEGRA + tristate "TEGRA AHCI SATA support" + depends on ARCH_TEGRA_3x_SOC + help + This option enables support for TEGRA AHCI Serial ATA. + + If unsure, say N. + +config TEGRA_SATA_IDLE_POWERGATE + bool "TEGRA SATA idle power-gating" + depends on SATA_AHCI_TEGRA && PM && PM_RUNTIME + help + This option enables power-gating during SATA idling. + This option should not be enabled if sata clocks are in dvfs_table. + + If unsure, say N. + config SATA_FSL tristate "Freescale 3.0Gbps SATA support" depends on FSL_SOC diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 9550d691fd19..4245ecf30f05 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_ATA) += libata.o # non-SFF interface obj-$(CONFIG_SATA_AHCI) += ahci.o libahci.o +obj-$(CONFIG_SATA_AHCI_TEGRA) += ahci-tegra.o libahci.o obj-$(CONFIG_SATA_ACARD_AHCI) += acard-ahci.o libahci.o obj-$(CONFIG_SATA_AHCI_PLATFORM) += ahci_platform.o libahci.o obj-$(CONFIG_SATA_FSL) += sata_fsl.o diff --git a/drivers/ata/ahci-tegra.c b/drivers/ata/ahci-tegra.c new file mode 100644 index 000000000000..5c58da143c5a --- /dev/null +++ b/drivers/ata/ahci-tegra.c @@ -0,0 +1,2154 @@ +/* + * ahci-tegra.c - AHCI SATA support for TEGRA AHCI device + * + * Copyright (c) 2011-2012, NVIDIA Corporation. All rights reserved. + * + * 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 <http://www.gnu.org/licenses/>. + * + * + * libata documentation is available via 'make {ps|pdf}docs', + * as Documentation/DocBook/libata.* + * + * AHCI hardware documentation: + * http://www.intel.com/technology/serialata/pdf/rev1_0.pdf + * http://www.intel.com/technology/serialata/pdf/rev1_1.pdf + * + */ + +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/blkdev.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_cmnd.h> +#include <linux/libata.h> +#include <linux/regulator/machine.h> +#include <linux/pm_runtime.h> +#include "ahci.h" + +#include <linux/clk.h> +#include <mach/clk.h> +#include <mach/iomap.h> +#include <mach/io.h> +#include <mach/powergate.h> + +#define DRV_NAME "tegra-sata" +#define DRV_VERSION "1.0" + +#define ENABLE_AHCI_DBG_PRINT 0 +#if ENABLE_AHCI_DBG_PRINT +#define AHCI_DBG_PRINT(fmt, arg...) printk(KERN_ERR fmt, ## arg) +#else +#define AHCI_DBG_PRINT(fmt, arg...) do {} while (0) +#endif + +/* number of AHCI ports */ +#define TEGRA_AHCI_NUM_PORTS 1 + +/* idle timeout for PM in msec */ +#define TEGRA_AHCI_MIN_IDLE_TIME 1000 +#define TEGRA_AHCI_DEFAULT_IDLE_TIME 2000 + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE +static u32 tegra_ahci_idle_time = TEGRA_AHCI_DEFAULT_IDLE_TIME; +#endif + +/* Bit 0 (EN_FPCI) to allow FPCI accesses to SATA */ +#define SATA_CONFIGURATION_0_OFFSET 0x180 +#define EN_FPCI (1 << 0) + +#define SATA_INTR_MASK_0_OFFSET 0x188 +#define IP_INT_MASK (1 << 16) + +/* Need to write 0x00400200 to 0x70020094 */ +#define SATA_FPCI_BAR5_0_OFFSET 0x094 +#define PRI_ICTLR_CPU_IER_SET_0_OFFSET 0x024 +#define CPU_IER_SATA_CTL (1 << 23) + +#define AHCI_BAR5_CONFIG_LOCATION 0x24 +#define TEGRA_SATA_BAR5_INIT_PROGRAM 0xFFFFFFFF +#define TEGRA_SATA_BAR5_FINAL_PROGRAM 0x40020000 + +#define FUSE_SATA_CALIB_OFFSET 0x224 +#define FUSE_SATA_CALIB_MASK 0x3 + +#define T_SATA0_CFG_PHY_REG 0x120 +#define PHY_USE_7BIT_ALIGN_DET_FOR_SPD_MASK (1 << 11) + +#define T_SATA0_CFG_POWER_GATE 0x4ac +#define POWER_GATE_SSTS_RESTORED_MASK (1 << 23) +#define POWER_GATE_SSTS_RESTORED_YES (1 << 23) +#define POWER_GATE_SSTS_RESTORED_NO (0 << 23) + +#define T_SATA0_DBG0_OFFSET 0x550 + +#define T_SATA0_INDEX_OFFSET 0x680 +#define SATA0_NONE_SELECTED 0 +#define SATA0_CH1_SELECTED (1 << 0) + +#define T_SATA0_CHX_PHY_CTRL1_GEN1_OFFSET 0x690 +#define SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_SHIFT 0 +#define SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_MASK (0xff << 0) +#define SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_SHIFT 8 +#define SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_MASK (0xff << 8) + +#define T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET 0x694 +#define SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_SHIFT 0 +#define SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_MASK (0xff << 0) +#define SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_SHIFT 12 +#define SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_MASK (0xff << 12) +#define SATA0_CHX_PHY_CTRL1_GEN2_RX_EQ_SHIFT 24 +#define SATA0_CHX_PHY_CTRL1_GEN2_RX_EQ_MASK (0xf << 24) + +/* AHCI config space defines */ +#define TEGRA_PRIVATE_AHCI_CC_BKDR 0x4a4 +#define TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE 0x54c +#define TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE_EN (1 << 12) +#define TEGRA_PRIVATE_AHCI_CC_BKDR_PGM 0x01060100 + +/* AHCI HBA_CAP */ +#define TEGRA_PRIVATE_AHCI_CAP_BKDR 0xa0 +#define T_SATA0_AHCI_HBA_CAP_BKDR 0x300 + +#define TEGRA_SATA_IO_SPACE_OFFSET 4 +#define TEGRA_SATA_ENABLE_IO_SPACE (1 << 0) +#define TEGRA_SATA_ENABLE_MEM_SPACE (1 << 1) +#define TEGRA_SATA_ENABLE_BUS_MASTER (1 << 2) +#define TEGRA_SATA_ENABLE_SERR (1 << 8) + +#define TEGRA_SATA_CORE_CLOCK_FREQ_HZ (108*1000*1000) +#define TEGRA_SATA_OOB_CLOCK_FREQ_HZ (216*1000*1000) + +#define APB_PMC_SATA_PWRGT_0_REG 0x1ac +#define CLK_RST_SATA_PLL_CFG0_REG 0x490 +#define CLK_RST_SATA_PLL_CFG1_REG 0x494 +#define SATA_AUX_PAD_PLL_CNTL_1_REG 0x1100 +#define SATA_AUX_MISC_CNTL_1_REG 0x1108 + +/* for APB_PMC_SATA_PWRGT_0_REG */ +#define PG_INFO_MASK (1 << 6) +#define PG_INFO_ON (1 << 6) +#define PG_INFO_OFF (0 << 6) +#define PLLE_IDDQ_SWCTL_MASK (1 << 4) +#define PLLE_IDDQ_SWCTL_ON (1 << 4) +#define PLLE_IDDQ_SWCTL_OFF (0 << 4) +#define PADPHY_IDDQ_OVERRIDE_VALUE_MASK (1 << 3) +#define PADPHY_IDDQ_OVERRIDE_VALUE_ON (1 << 3) +#define PADPHY_IDDQ_OVERRIDE_VALUE_OFF (0 << 3) +#define PADPHY_IDDQ_SWCTL_MASK (1 << 2) +#define PADPHY_IDDQ_SWCTL_ON (1 << 2) +#define PADPHY_IDDQ_SWCTL_OFF (0 << 2) +#define PADPLL_IDDQ_OVERRIDE_VALUE_MASK (1 << 1) +#define PADPLL_IDDQ_OVERRIDE_VALUE_ON (1 << 1) +#define PADPLL_IDDQ_OVERRIDE_VALUE_OFF (0 << 1) +#define PADPLL_IDDQ_SWCTL_MASK (1 << 0) +#define PADPLL_IDDQ_SWCTL_ON (1 << 0) +#define PADPLL_IDDQ_SWCTL_OFF (0 << 0) + +/* for CLK_RST_SATA_PLL_CFG0_REG */ +#define PADPLL_RESET_OVERRIDE_VALUE_MASK (1 << 1) +#define PADPLL_RESET_OVERRIDE_VALUE_ON (1 << 1) +#define PADPLL_RESET_OVERRIDE_VALUE_OFF (0 << 1) +#define PADPLL_RESET_SWCTL_MASK (1 << 0) +#define PADPLL_RESET_SWCTL_ON (1 << 0) +#define PADPLL_RESET_SWCTL_OFF (0 << 0) + +/* for CLK_RST_SATA_PLL_CFG1_REG */ +#define IDDQ2LANE_SLUMBER_DLY_MASK (0xffL << 16) +#define IDDQ2LANE_SLUMBER_DLY_SHIFT 16 +#define IDDQ2LANE_SLUMBER_DLY_3MS (3 << 16) +#define IDDQ2LANE_IDDQ_DLY_SHIFT 0 +#define IDDQ2LANE_IDDQ_DLY_MASK (0xffL << 0) + +/* for SATA_AUX_PAD_PLL_CNTL_1_REG */ +#define REFCLK_SEL_MASK (3 << 11) +#define REFCLK_SEL_INT_CML (0 << 11) +#define LOCKDET_FIELD (1 << 6) + +/* for SATA_AUX_MISC_CNTL_1_REG */ +#define NVA2SATA_OOB_ON_POR_MASK (1 << 7) +#define NVA2SATA_OOB_ON_POR_YES (1 << 7) +#define NVA2SATA_OOB_ON_POR_NO (0 << 7) +#define L0_RX_IDLE_T_SAX_SHIFT 5 +#define L0_RX_IDLE_T_SAX_MASK (3 << 5) +#define L0_RX_IDLE_T_NPG_SHIFT 3 +#define L0_RX_IDLE_T_NPG_MASK (3 << 3) +#define L0_RX_IDLE_T_MUX_MASK (1 << 2) +#define L0_RX_IDLE_T_MUX_FROM_APB_MISC (1 << 2) +#define L0_RX_IDLE_T_MUX_FROM_SATA (0 << 2) + +#define SSTAT_IPM_STATE_MASK 0xF00 +#define SSTAT_IPM_SLUMBER_STATE 0x600 + +enum { + AHCI_PCI_BAR = 5, +}; + +enum port_idle_status { + PORT_IS_NOT_IDLE, + PORT_IS_IDLE, + PORT_IS_IDLE_NOT_SLUMBER, + PORT_IS_SLUMBER, +}; + +enum sata_state { + SATA_ON, + SATA_OFF, + SATA_GOING_ON, + SATA_GOING_OFF, + SATA_ABORT_OFF, +}; + +char *sata_power_rails[] = { + "avdd_sata", + "vdd_sata", + "hvdd_sata", + "avdd_sata_pll" +}; + +#define NUM_SATA_POWER_RAILS ARRAY_SIZE(sata_power_rails) + +struct tegra_qc_list { + struct list_head list; + struct ata_queued_cmd *qc; +}; + +/* + * tegra_ahci_host_priv is the extension of ahci_host_priv + * with extra fields: idle_timer, pg_save, pg_state, etc. + */ +struct tegra_ahci_host_priv { + struct ahci_host_priv ahci_host_priv; + struct regulator *power_rails[NUM_SATA_POWER_RAILS]; + void __iomem *bars_table[6]; + struct ata_host *host; + struct timer_list idle_timer; + struct device *dev; + void *pg_save; + enum sata_state pg_state; + struct list_head qc_list; +}; + +static int tegra_ahci_init_one(struct platform_device *pdev); +static int tegra_ahci_remove_one(struct platform_device *pdev); + +#ifdef CONFIG_PM +static bool tegra_ahci_power_un_gate(struct ata_host *host); +static bool tegra_ahci_power_gate(struct ata_host *host); +static void tegra_ahci_abort_power_gate(struct ata_host *host); +static int tegra_ahci_controller_suspend(struct platform_device *pdev); +static int tegra_ahci_controller_resume(struct platform_device *pdev); +static int tegra_ahci_suspend(struct platform_device *pdev, pm_message_t mesg); +static int tegra_ahci_resume(struct platform_device *pdev); +static enum port_idle_status tegra_ahci_is_port_idle(struct ata_port *ap); +static enum port_idle_status tegra_ahci_is_port_slumber(struct ata_port *ap); +static bool tegra_ahci_are_all_ports_idle(struct ata_host *host); +static bool tegra_ahci_are_all_ports_slumber(struct ata_host *host); +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE +static unsigned int tegra_ahci_qc_issue(struct ata_queued_cmd *qc); +static int tegra_ahci_hardreset(struct ata_link *link, unsigned int *class, + unsigned long deadline); +static int tegra_ahci_runtime_suspend(struct device *dev); +static int tegra_ahci_runtime_resume(struct device *dev); +static void tegra_ahci_idle_timer(unsigned long arg); +static int tegra_ahci_queue_one_qc(struct tegra_ahci_host_priv *tegra_hpriv, + struct ata_queued_cmd *qc); +static void tegra_ahci_dequeue_qcs(struct tegra_ahci_host_priv *tegra_hpriv); +#endif +#else +#define tegra_ahci_controller_suspend NULL +#define tegra_ahci_controller_resume NULL +#define tegra_ahci_suspend NULL +#define tegra_ahci_resume NULL +#endif + +static struct scsi_host_template ahci_sht = { + AHCI_SHT("tegra-sata"), +}; + +static struct ata_port_operations tegra_ahci_ops = { + .inherits = &ahci_ops, +#ifdef CONFIG_PM +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + .qc_issue = tegra_ahci_qc_issue, + .hardreset = tegra_ahci_hardreset, +#endif +#endif +}; + +static const struct ata_port_info ahci_port_info = { + .flags = AHCI_FLAG_COMMON, + .pio_mask = 0x1f, /* pio0-4 */ + .udma_mask = ATA_UDMA6, + .port_ops = &tegra_ahci_ops, +}; + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE +static const struct dev_pm_ops tegra_ahci_dev_rt_ops = { + .runtime_suspend = tegra_ahci_runtime_suspend, + .runtime_resume = tegra_ahci_runtime_resume, +}; +#endif + +static struct platform_driver tegra_platform_ahci_driver = { + .probe = tegra_ahci_init_one, + .remove = __devexit_p(tegra_ahci_remove_one), +#ifdef CONFIG_PM + .suspend = tegra_ahci_suspend, + .resume = tegra_ahci_resume, + .driver = { + .name = DRV_NAME, +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + .pm = &tegra_ahci_dev_rt_ops, +#endif + } +#else + .driver = { + .name = DRV_NAME, + } +#endif +}; + +struct tegra_ahci_host_priv *g_tegra_hpriv; + +static inline u32 pmc_readl(u32 offset) +{ + u32 val; + val = readl(IO_ADDRESS(TEGRA_PMC_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_PMC_BASE+offset, val); + return val; +} + +static inline void pmc_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_PMC_BASE+offset, val); + writel(val, IO_ADDRESS(TEGRA_PMC_BASE + offset)); +} + +static inline u32 clk_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_CLK_RESET_BASE+offset, val); + return val; +} + +static inline void clk_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_CLK_RESET_BASE+offset, val); + writel(val, IO_ADDRESS(TEGRA_CLK_RESET_BASE + offset)); +} + +static inline u32 misc_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_APB_MISC_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_APB_MISC_BASE+offset, val); + return val; +} + +static inline void misc_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_APB_MISC_BASE+offset, val); + writel(val, IO_ADDRESS(TEGRA_APB_MISC_BASE + offset)); +} + +static inline u32 sata_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_SATA_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_SATA_BASE+offset, val); + return val; +} + +static inline void sata_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_SATA_BASE+offset, val); + writel(val, IO_ADDRESS(TEGRA_SATA_BASE + offset)); +} + +static inline u32 scfg_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_SATA_CONFIG_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_SATA_CONFIG_BASE+offset, + val); + return val; +} + +static inline void scfg_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_SATA_CONFIG_BASE+offset, + val); + writel(val, IO_ADDRESS(TEGRA_SATA_CONFIG_BASE + offset)); +} + +static inline u32 pictlr_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_PRIMARY_ICTLR_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_PRIMARY_ICTLR_BASE+offset, + val); + return val; +} + +static inline void pictlr_writel(u32 val, u32 offset) +{ + AHCI_DBG_PRINT("[0x%x] <= 0x%08x\n", TEGRA_PRIMARY_ICTLR_BASE+offset, + val); + writel(val, IO_ADDRESS(TEGRA_PRIMARY_ICTLR_BASE + offset)); +} + +static inline u32 fuse_readl(u32 offset) +{ + u32 val; + + val = readl(IO_ADDRESS(TEGRA_FUSE_BASE + offset)); + AHCI_DBG_PRINT("[0x%x] => 0x%08x\n", TEGRA_FUSE_BASE+offset, val); + + return val; +} + +/* Sata Pad Cntrl Values */ +struct sata_pad_cntrl { + u8 gen1_tx_amp; + u8 gen1_tx_peak; + u8 gen2_tx_amp; + u8 gen2_tx_peak; +}; + +static const struct sata_pad_cntrl sata_calib_pad_val[] = { + { /* SATA_CALIB[1:0] = 00 */ + 0x0c, + 0x04, + 0x0e, + 0x0a + }, + { /* SATA_CALIB[1:0] = 01 */ + 0x0e, + 0x04, + 0x14, + 0x0a + }, + { /* SATA_CALIB[1:0] = 10 */ + 0x0e, + 0x07, + 0x1a, + 0x0e + }, + { /* SATA_CALIB[1:0] = 11 */ + 0x14, + 0x0e, + 0x1a, + 0x0e + } +}; + +static void tegra_ahci_set_pad_cntrl_regs(void) +{ + int calib_val; + int val; + int i; + + calib_val = fuse_readl(FUSE_SATA_CALIB_OFFSET) & FUSE_SATA_CALIB_MASK; + + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + scfg_writel((1 << i), T_SATA0_INDEX_OFFSET); + + val = scfg_readl(T_SATA0_CHX_PHY_CTRL1_GEN1_OFFSET); + val &= ~SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_MASK; + val |= (sata_calib_pad_val[calib_val].gen1_tx_amp << + SATA0_CHX_PHY_CTRL1_GEN1_TX_AMP_SHIFT); + scfg_writel(val, T_SATA0_CHX_PHY_CTRL1_GEN1_OFFSET); + + val = scfg_readl(T_SATA0_CHX_PHY_CTRL1_GEN1_OFFSET); + val &= ~SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_MASK; + val |= (sata_calib_pad_val[calib_val].gen1_tx_peak << + SATA0_CHX_PHY_CTRL1_GEN1_TX_PEAK_SHIFT); + scfg_writel(val, T_SATA0_CHX_PHY_CTRL1_GEN1_OFFSET); + + val = scfg_readl(T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + val &= ~SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_MASK; + val |= (sata_calib_pad_val[calib_val].gen2_tx_amp << + SATA0_CHX_PHY_CTRL1_GEN2_TX_AMP_SHIFT); + scfg_writel(val, T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + + val = scfg_readl(T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + val &= ~SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_MASK; + val |= (sata_calib_pad_val[calib_val].gen2_tx_peak << + SATA0_CHX_PHY_CTRL1_GEN2_TX_PEAK_SHIFT); + scfg_writel(val, T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + + /* set 2 to SATA0_CHX_PHY_CTRL1_GEN2_RX_EQ field */ + val = scfg_readl(T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + val &= ~SATA0_CHX_PHY_CTRL1_GEN2_RX_EQ_MASK; + val |= (2 << SATA0_CHX_PHY_CTRL1_GEN2_RX_EQ_SHIFT); + scfg_writel(val, T_SATA0_CHX_PHY_CTRL1_GEN2_OFFSET); + } + scfg_writel(SATA0_NONE_SELECTED, T_SATA0_INDEX_OFFSET); +} + +int tegra_ahci_get_rails(struct regulator *regulators[]) +{ + struct regulator *reg; + int i; + int ret = 0; + + for (i = 0; i < NUM_SATA_POWER_RAILS; ++i) { + reg = regulator_get(NULL, sata_power_rails[i]); + if (IS_ERR_OR_NULL(reg)) { + pr_err("%s: can't get regulator %s\n", + __func__, sata_power_rails[i]); + WARN_ON(1); + ret = PTR_ERR(reg); + goto exit; + } + regulators[i] = reg; + } +exit: + return ret; +} + +void tegra_ahci_put_rails(struct regulator *regulators[]) +{ + int i; + + for (i = 0; i < NUM_SATA_POWER_RAILS; ++i) + regulator_put(regulators[i]); +} + +int tegra_ahci_power_on_rails(struct regulator *regulators[]) +{ + struct regulator *reg; + int i; + int ret = 0; + + for (i = 0; i < NUM_SATA_POWER_RAILS; ++i) { + reg = regulators[i]; + ret = regulator_enable(reg); + if (ret) { + pr_err("%s: can't enable regulator[%d]\n", + __func__, i); + WARN_ON(1); + goto exit; + } + } + +exit: + return ret; +} + +int tegra_ahci_power_off_rails(struct regulator *regulators[]) +{ + struct regulator *reg; + int i; + int ret = 0; + + for (i = 0; i < NUM_SATA_POWER_RAILS; ++i) { + reg = regulators[i]; + if (!IS_ERR_OR_NULL(reg)) { + ret = regulator_disable(reg); + if (ret) { + pr_err("%s: can't disable regulator[%d]\n", + __func__, i); + WARN_ON(1); + goto exit; + } + } + } + +exit: + return ret; +} +static int tegra_ahci_controller_init(struct tegra_ahci_host_priv *tegra_hpriv) +{ + int err; + struct clk *clk_sata = NULL; + struct clk *clk_sata_oob = NULL; + struct clk *clk_sata_cold = NULL; + struct clk *clk_pllp = NULL; + u32 val; + u32 timeout; + + err = tegra_ahci_get_rails(tegra_hpriv->power_rails); + if (err) { + pr_err("%s: fails to get rails (%d)\n", __func__, err); + goto exit; + } + + err = tegra_ahci_power_on_rails(tegra_hpriv->power_rails); + if (err) { + pr_err("%s: fails to power on rails (%d)\n", __func__, err); + goto exit; + } + + /* pll_p is the parent of tegra_sata and tegra_sata_oob */ + clk_pllp = clk_get_sys(NULL, "pll_p"); + if (IS_ERR_OR_NULL(clk_pllp)) { + pr_err("%s: unable to get PLL_P clock\n", __func__); + err = -ENODEV; + goto exit; + } + + clk_sata = clk_get_sys("tegra_sata", NULL); + if (IS_ERR_OR_NULL(clk_sata)) { + pr_err("%s: unable to get SATA clock\n", __func__); + err = -ENODEV; + goto exit; + } + + clk_sata_oob = clk_get_sys("tegra_sata_oob", NULL); + if (IS_ERR_OR_NULL(clk_sata_oob)) { + pr_err("%s: unable to get SATA OOB clock\n", __func__); + err = -ENODEV; + goto exit; + } + + clk_sata_cold = clk_get_sys("tegra_sata_cold", NULL); + if (IS_ERR_OR_NULL(clk_sata_cold)) { + pr_err("%s: unable to get SATA COLD clock\n", __func__); + err = -ENODEV; + goto exit; + } + + tegra_periph_reset_assert(clk_sata); + tegra_periph_reset_assert(clk_sata_oob); + tegra_periph_reset_assert(clk_sata_cold); + udelay(10); + + /* need to establish both clocks divisors before setting clk sources */ + clk_set_rate(clk_sata, clk_get_rate(clk_sata)/10); + clk_set_rate(clk_sata_oob, clk_get_rate(clk_sata_oob)/10); + + /* set SATA clk and SATA_OOB clk source */ + clk_set_parent(clk_sata, clk_pllp); + clk_set_parent(clk_sata_oob, clk_pllp); + + /* Configure SATA clocks */ + /* Core clock runs at 108MHz */ + if (clk_set_rate(clk_sata, TEGRA_SATA_CORE_CLOCK_FREQ_HZ)) { + err = -ENODEV; + goto exit; + } + /* OOB clock runs at 216MHz */ + if (clk_set_rate(clk_sata_oob, TEGRA_SATA_OOB_CLOCK_FREQ_HZ)) { + err = -ENODEV; + goto exit; + } + + /**** Init the SATA PAD PLL ****/ + /* SATA_PADPLL_IDDQ_SWCTL=1 and SATA_PADPLL_IDDQ_OVERRIDE_VALUE=1 */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~(PADPLL_IDDQ_SWCTL_MASK | PADPLL_IDDQ_OVERRIDE_VALUE_MASK); + val |= (PADPLL_IDDQ_SWCTL_ON | PADPLL_IDDQ_OVERRIDE_VALUE_ON); + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + /* SATA_PADPLL_RESET_OVERRIDE_VALUE=1 and SATA_PADPLL_RESET_SWCTL=1 */ + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~(PADPLL_RESET_OVERRIDE_VALUE_MASK | PADPLL_RESET_SWCTL_MASK); + val |= (PADPLL_RESET_OVERRIDE_VALUE_ON | PADPLL_RESET_SWCTL_ON); + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + + /* SATA_PADPHY_IDDQ_OVERRIDE_VALUE and SATA_PADPHY_IDDQ_SWCTL = 1 */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~(PADPHY_IDDQ_OVERRIDE_VALUE_MASK | PADPHY_IDDQ_SWCTL_MASK); + val |= (PADPHY_IDDQ_OVERRIDE_VALUE_ON | PADPHY_IDDQ_SWCTL_ON); + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + /* Get SATA pad PLL out of IDDQ mode */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PADPLL_IDDQ_OVERRIDE_VALUE_MASK; + val |= PADPLL_IDDQ_OVERRIDE_VALUE_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + udelay(3); + + /* select internal CML ref clk + * select PLLE as input to IO phy */ + val = misc_readl(SATA_AUX_PAD_PLL_CNTL_1_REG); + val &= ~REFCLK_SEL_MASK; + val |= REFCLK_SEL_INT_CML; + misc_writel(val, SATA_AUX_PAD_PLL_CNTL_1_REG); + + /* wait for SATA_PADPLL_IDDQ2LANE_SLUMBER_DLY = 3 microseconds. */ + val = clk_readl(CLK_RST_SATA_PLL_CFG1_REG); + val &= ~IDDQ2LANE_SLUMBER_DLY_MASK; + val |= IDDQ2LANE_SLUMBER_DLY_3MS; + clk_writel(val, CLK_RST_SATA_PLL_CFG1_REG); + udelay(3); + + /* de-assert IDDQ mode signal going to PHY */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PADPHY_IDDQ_OVERRIDE_VALUE_MASK; + val |= PADPHY_IDDQ_OVERRIDE_VALUE_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + err = tegra_unpowergate_partition_with_clk_on(TEGRA_POWERGATE_SATA); + if (err) { + pr_err("%s: ** failed to turn-on SATA (0x%x) **\n", + __func__, err); + goto exit; + } + + /* + * place SATA Pad PLL out of reset by writing + * SATA_PADPLL_RST_OVERRIDE_VALUE = 0 + */ + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~PADPLL_RESET_OVERRIDE_VALUE_MASK; + val |= PADPLL_RESET_OVERRIDE_VALUE_OFF; + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + + /* + * Wait for SATA_AUX_PAD_PLL_CNTL_1_0_LOCKDET to turn 1 with a timeout + * of 15 us. + */ + timeout = 15; + while (timeout--) { + udelay(1); + val = misc_readl(SATA_AUX_PAD_PLL_CNTL_1_REG); + if (val & LOCKDET_FIELD) + break; + } + if (timeout == 0) + pr_err("%s: AUX_PAD_PLL_CNTL_1 (0x%x) is not locked in 15us.\n", + __func__, val); + + /* clear SW control of SATA PADPLL, SATA PHY and PLLE */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~(PADPLL_IDDQ_SWCTL_MASK | PADPHY_IDDQ_SWCTL_MASK | + PLLE_IDDQ_SWCTL_MASK); + val |= (PADPLL_IDDQ_SWCTL_OFF | PADPHY_IDDQ_SWCTL_OFF | + PLLE_IDDQ_SWCTL_OFF); + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~PADPLL_RESET_SWCTL_MASK; + val |= PADPLL_RESET_SWCTL_OFF; + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + + /* clear NVA2SATA_OOB_ON_POR in SATA_AUX_MISC_CNTL_1_REG */ + val = misc_readl(SATA_AUX_MISC_CNTL_1_REG); + val &= ~NVA2SATA_OOB_ON_POR_MASK; + misc_writel(val, SATA_AUX_MISC_CNTL_1_REG); + + val = sata_readl(SATA_CONFIGURATION_0_OFFSET); + val |= EN_FPCI; + sata_writel(val, SATA_CONFIGURATION_0_OFFSET); + + /* program sata pad control based on the fuse */ + tegra_ahci_set_pad_cntrl_regs(); + + /* + * clear bit T_SATA0_CFG_PHY_0_USE_7BIT_ALIGN_DET_FOR_SPD of + * T_SATA0_CFG_PHY_0 + */ + val = scfg_readl(T_SATA0_CFG_PHY_REG); + val &= ~PHY_USE_7BIT_ALIGN_DET_FOR_SPD_MASK; + scfg_writel(val, T_SATA0_CFG_PHY_REG); + + /* + * WAR: Before enabling SATA PLL shutdown, lockdet needs to be ignored. + * To ignore lockdet, T_SATA0_DBG0_OFFSET register bit 10 needs to + * be 1, and bit 8 needs to be 0. + */ + val = scfg_readl(T_SATA0_DBG0_OFFSET); + val |= (1 << 10); + val &= ~(1 << 8); + scfg_writel(val, T_SATA0_DBG0_OFFSET); + + /* program class code and programming interface for AHCI */ + val = scfg_readl(TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); + val |= TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE_EN; + scfg_writel(val, TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); + scfg_writel(TEGRA_PRIVATE_AHCI_CC_BKDR_PGM, TEGRA_PRIVATE_AHCI_CC_BKDR); + val &= ~TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE_EN; + scfg_writel(val, TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); + + /* Program config space registers: */ + + /* Enable BUS_MASTER+MEM+IO space, and SERR */ + val = scfg_readl(TEGRA_SATA_IO_SPACE_OFFSET); + val |= TEGRA_SATA_ENABLE_IO_SPACE | TEGRA_SATA_ENABLE_MEM_SPACE | + TEGRA_SATA_ENABLE_BUS_MASTER | TEGRA_SATA_ENABLE_SERR; + scfg_writel(val, TEGRA_SATA_IO_SPACE_OFFSET); + + /* program bar5 space, by first writing 1's to bar5 register */ + scfg_writel(TEGRA_SATA_BAR5_INIT_PROGRAM, AHCI_BAR5_CONFIG_LOCATION); + /* flush */ + val = scfg_readl(AHCI_BAR5_CONFIG_LOCATION); + + /* then, write the BAR5_FINAL_PROGRAM address */ + scfg_writel(TEGRA_SATA_BAR5_FINAL_PROGRAM, AHCI_BAR5_CONFIG_LOCATION); + /* flush */ + scfg_readl(AHCI_BAR5_CONFIG_LOCATION); + + sata_writel((TEGRA_SATA_BAR5_FINAL_PROGRAM >> 8), + SATA_FPCI_BAR5_0_OFFSET); + + val = scfg_readl(T_SATA0_AHCI_HBA_CAP_BKDR); + val |= (HOST_CAP_ALPM | HOST_CAP_SSC | HOST_CAP_PART); + scfg_writel(val, T_SATA0_AHCI_HBA_CAP_BKDR); + + /* enable Interrupt channel */ + val = pictlr_readl(PRI_ICTLR_CPU_IER_SET_0_OFFSET); + val |= CPU_IER_SATA_CTL; + pictlr_writel(val, PRI_ICTLR_CPU_IER_SET_0_OFFSET); + + /* set IP_INT_MASK */ + val = sata_readl(SATA_INTR_MASK_0_OFFSET); + val |= IP_INT_MASK; + sata_writel(val, SATA_INTR_MASK_0_OFFSET); + +exit: + if (!IS_ERR_OR_NULL(clk_pllp)) + clk_put(clk_pllp); + if (!IS_ERR_OR_NULL(clk_sata)) + clk_put(clk_sata); + if (!IS_ERR_OR_NULL(clk_sata_oob)) + clk_put(clk_sata_oob); + if (!IS_ERR_OR_NULL(clk_sata_cold)) + clk_put(clk_sata_cold); + + if (err) { + /* turn off all SATA power rails; ignore returned status */ + tegra_ahci_power_off_rails(tegra_hpriv->power_rails); + /* return regulators to system */ + tegra_ahci_put_rails(tegra_hpriv->power_rails); + } + + return err; +} + +static void tegra_ahci_save_initial_config(struct platform_device *pdev, + struct ahci_host_priv *hpriv) +{ + ahci_save_initial_config(&pdev->dev, hpriv, 0, 0); +} + +static void tegra_ahci_controller_remove(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct tegra_ahci_host_priv *tegra_hpriv; + int status; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + +#ifdef CONFIG_PM + /* call tegra_ahci_controller_suspend() to power-down the SATA */ + status = tegra_ahci_controller_suspend(pdev); + if (status) + dev_err(host->dev, "remove: error suspend SATA (0x%x)\n", + status); +#else + /* power off the sata */ + status = tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_SATA); + if (status) + dev_err(host->dev, "remove: error turn-off SATA (0x%x)\n", + status); + tegra_ahci_power_off_rails(tegra_hpriv->power_rails); +#endif + + /* return system resources */ + tegra_ahci_put_rails(tegra_hpriv->power_rails); +} + +#ifdef CONFIG_PM +static int tegra_ahci_controller_suspend(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct tegra_ahci_host_priv *tegra_hpriv; + unsigned long flags; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + /* stop the idle timer */ + if (timer_pending(&tegra_hpriv->idle_timer)) + del_timer_sync(&tegra_hpriv->idle_timer); + + spin_lock_irqsave(&host->lock, flags); + if (tegra_hpriv->pg_state == SATA_OFF) + dev_dbg(host->dev, "suspend: SATA already power gated\n"); + else { + bool pg_ok; + + dev_dbg(host->dev, "suspend: power gating SATA...\n"); + pg_ok = tegra_ahci_power_gate(host); + if (pg_ok) { + tegra_hpriv->pg_state = SATA_OFF; + dev_dbg(host->dev, "suspend: SATA is power gated\n"); + } else { + dev_err(host->dev, "suspend: abort power gating\n"); + tegra_ahci_abort_power_gate(host); + spin_unlock_irqrestore(&host->lock, flags); + return -EBUSY; + } + } + spin_unlock_irqrestore(&host->lock, flags); + + return tegra_ahci_power_off_rails(tegra_hpriv->power_rails); +} + +static int tegra_ahci_controller_resume(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct tegra_ahci_host_priv *tegra_hpriv; + unsigned long flags; + int err; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + err = tegra_ahci_power_on_rails(tegra_hpriv->power_rails); + if (err) { + pr_err("%s: fails to power on rails (%d)\n", __func__, err); + return err; + } + + spin_lock_irqsave(&host->lock, flags); + if (!tegra_hpriv->pg_state == SATA_ON) { + dev_dbg(host->dev, "resume: SATA already powered on\n"); + } else { + dev_dbg(host->dev, "resume: powering on SATA...\n"); + tegra_ahci_power_un_gate(host); + tegra_hpriv->pg_state = SATA_ON; + } + spin_unlock_irqrestore(&host->lock, flags); + + return 0; +} + +static int tegra_ahci_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + void __iomem *mmio = host->iomap[AHCI_PCI_BAR]; + u32 ctl; + int rc; + + dev_dbg(host->dev, "** entering %s: **\n", __func__); + if (mesg.event & PM_EVENT_SLEEP) { + /* + * AHCI spec rev1.1 section 8.3.3: + * Software must disable interrupts prior to requesting a + * transition of the HBA to D3 state. + */ + ctl = readl(mmio + HOST_CTL); + ctl &= ~HOST_IRQ_EN; + writel(ctl, mmio + HOST_CTL); + readl(mmio + HOST_CTL); /* flush */ + } + + rc = ata_host_suspend(host, mesg); + if (rc) + return rc; + + return tegra_ahci_controller_suspend(pdev); +} + +static int tegra_ahci_resume(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + int rc; + + dev_dbg(host->dev, "** entering %s: **\n", __func__); + rc = tegra_ahci_controller_resume(pdev); + if (rc) + return rc; + + if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + rc = ahci_reset_controller(host); + if (rc) + return rc; + + ahci_init_controller(host); + } + + ata_host_resume(host); + return 0; +} + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE +static int tegra_ahci_runtime_suspend(struct device *dev) +{ + struct ata_host *host; + struct tegra_ahci_host_priv *tegra_hpriv; + bool pg_ok; + unsigned long flags; + int err = 0; + + host = dev_get_drvdata(dev); + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + spin_lock_irqsave(&host->lock, flags); + + switch (tegra_hpriv->pg_state) { + case SATA_OFF: + dev_dbg(dev, "** rt-suspend: already power gated **\n"); + break; + + case SATA_ABORT_OFF: + dev_dbg(dev, "** rt-suspend: abort suspend **\n"); + pm_runtime_get_noresume(dev); + tegra_hpriv->pg_state = SATA_ON; + tegra_ahci_dequeue_qcs(tegra_hpriv); + err = -EBUSY; + break; + + case SATA_ON: + case SATA_GOING_OFF: + if (tegra_ahci_are_all_ports_idle(host)) { + /* if all ports are in idle, do power-gate */ + dev_dbg(dev, "** rt-suspend: power-down sata (%u) **\n", + tegra_hpriv->pg_state); + pg_ok = tegra_ahci_power_gate(host); + dev_dbg(dev, "** rt-suspend: done **\n"); + if (pg_ok) { + tegra_hpriv->pg_state = SATA_OFF; + } else { + dev_err(dev, "** rt-suspend: abort pg **\n"); + tegra_ahci_abort_power_gate(host); + tegra_hpriv->pg_state = SATA_ON; + err = -EBUSY; + } + } else { + dev_dbg(dev, "** rt-suspend: port not idle (%u) **\n", + tegra_hpriv->pg_state); + err = -EBUSY; + } + break; + + case SATA_GOING_ON: + default: + dev_err(dev, "** rt-suspend: bad state (%u) **\n", + tegra_hpriv->pg_state); + WARN_ON(1); + err = -EBUSY; + break; + + } + + spin_unlock_irqrestore(&host->lock, flags); + + return err; +} + +static int tegra_ahci_runtime_resume(struct device *dev) +{ + struct ata_host *host; + struct tegra_ahci_host_priv *tegra_hpriv; + unsigned long flags; + int err = 0; + + host = dev_get_drvdata(dev); + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + spin_lock_irqsave(&host->lock, flags); + + if (tegra_hpriv->pg_state == SATA_ON) { + dev_dbg(dev, "** rt-resume: already power ungated **\n"); + goto exit; + } + + if ((tegra_hpriv->pg_state == SATA_OFF) || + (tegra_hpriv->pg_state == SATA_GOING_ON)) { + dev_dbg(dev, "** rt-resume: power-up sata (%u) **\n", + tegra_hpriv->pg_state); + tegra_ahci_power_un_gate(host); + dev_dbg(dev, "** rt-resume: done **\n"); + tegra_hpriv->pg_state = SATA_ON; + + /* now qc_issue all qcs in the qc_list */ + tegra_ahci_dequeue_qcs(tegra_hpriv); + } else { + dev_err(dev, "** rt-resume: bad state (%u) **\n", + tegra_hpriv->pg_state); + WARN_ON(1); + err = -EBUSY; + } + +exit: + spin_unlock_irqrestore(&host->lock, flags); + return err; +} +#endif + +static u16 pg_save_bar5_registers[] = { + 0x018, /* T_AHCI_HBA_CCC_PORTS */ + 0x004, /* T_AHCI_HBA_GHC */ + 0x014, /* T_AHCI_HBA_CCC_CTL - OP (optional) */ + 0x01C, /* T_AHCI_HBA_EM_LOC */ + 0x020 /* T_AHCI_HBA_EM_CTL - OP */ +}; + +static u16 pg_save_bar5_port_registers[] = { + 0x100, /* T_AHCI_PORT_PXCLB */ + 0x104, /* T_AHCI_PORT_PXCLBU */ + 0x108, /* T_AHCI_PORT_PXFB */ + 0x10C, /* T_AHCI_PORT_PXFBU */ + 0x114, /* T_AHCI_PORT_PXIE */ + 0x118, /* T_AHCI_PORT_PXCMD */ + 0x12C /* T_AHCI_PORT_PXSCTL */ +}; + +/* + * pg_save_bar5_bkdr_registers: + * These registers in BAR5 are read only. + * To restore back those register values, write the saved value + * to the registers specified in pg_restore_bar5_bkdr_registers[]. + * These pg_restore_bar5_bkdr_registers[] are in SATA_CONFIG space. + */ +static u16 pg_save_bar5_bkdr_registers[] = { + /* Save and restore via bkdr writes */ + 0x000, /* T_AHCI_HBA_CAP */ + 0x00C, /* T_AHCI_HBA_PI */ + 0x024 /* T_AHCI_HBA_CAP2 */ +}; + +static u16 pg_restore_bar5_bkdr_registers[] = { + /* Save and restore via bkdr writes */ + 0x300, /* BKDR of T_AHCI_HBA_CAP */ + 0x33c, /* BKDR of T_AHCI_HBA_PI */ + 0x330 /* BKDR of T_AHCI_HBA_CAP2 */ +}; + +/* These registers are saved for each port */ +static u16 pg_save_bar5_bkdr_port_registers[] = { + 0x120, /* NV_PROJ__SATA0_CHX_AHCI_PORT_PXTFD */ + 0x124, /* NV_PROJ__SATA0_CHX_AHCI_PORT_PXSIG */ + 0x128 /* NV_PROJ__SATA0_CHX_AHCI_PORT_PXSSTS */ +}; + +static u16 pg_restore_bar5_bkdr_port_registers[] = { + /* Save and restore via bkdr writes */ + 0x790, /* BKDR of NV_PROJ__SATA0_CHX_AHCI_PORT_PXTFD */ + 0x794, /* BKDR of NV_PROJ__SATA0_CHX_AHCI_PORT_PXSIG */ + 0x798 /* BKDR of NV_PROJ__SATA0_CHX_AHCI_PORT_PXSSTS */ +}; + +static u16 pg_save_config_registers[] = { + 0x004, /* T_SATA0_CFG_1 */ + 0x00C, /* T_SATA0_CFG_3 */ + 0x024, /* T_SATA0_CFG_9 */ + 0x028, /* T_SATA0_CFG_10 */ + 0x030, /* T_SATA0_CFG_12 */ + 0x034, /* T_SATA0_CFG_13 */ + 0x038, /* T_SATA0_CFG_14 */ + 0x03C, /* T_SATA0_CFG_15 */ + 0x040, /* T_SATA0_CFG_16 */ + 0x044, /* T_SATA0_CFG_17 */ + 0x048, /* T_SATA0_CFG_18 */ + 0x0B0, /* T_SATA0_MSI_CTRL */ + 0x0B4, /* T_SATA0_MSI_ADDR1 */ + 0x0B8, /* T_SATA0_MSI_ADDR2 */ + 0x0BC, /* T_SATA0_MSI_DATA */ + 0x0C0, /* T_SATA0_MSI_QUEUE */ + 0x0EC, /* T_SATA0_MSI_MAP */ + 0x124, /* T_SATA0_CFG_PHY_POWER */ + 0x128, /* T_SATA0_CFG_PHY_POWER_1 */ + 0x12C, /* T_SATA0_CFG_PHY_1 */ + 0x174, /* T_SATA0_CFG_LINK_0 */ + 0x178, /* T_SATA0_CFG_LINK_1 */ + 0x1D0, /* MCP_SATA0_CFG_TRANS_0 */ + 0x238, /* T_SATA0_ALPM_CTRL */ + 0x30C, /* T_SATA0_AHCI_HBA_CYA_0 */ + 0x320, /* T_SATA0_AHCI_HBA_SPARE_1 */ + 0x324, /* T_SATA0_AHCI_HBA_SPARE_2 */ + 0x328, /* T_SATA0_AHCI_HBA_DYN_CLK_CLAMP */ + 0x32C, /* T_SATA0_AHCI_CFG_ERR_CTRL */ + 0x338, /* T_SATA0_AHCI_HBA_CYA_1 */ + 0x340, /* T_SATA0_AHCI_HBA_PRE_STAGING_CONTROL */ + 0x430, /* T_SATA0_CFG_FPCI_0 */ + 0x494, /* T_SATA0_CFG_ESATA_CTRL */ + 0x4A0, /* T_SATA0_CYA1 */ + 0x4B0, /* T_SATA0_CFG_GLUE */ + 0x534, /* T_SATA0_PHY_CTRL */ + 0x540, /* T_SATA0_CTRL */ + 0x550, /* T_SATA0_DBG0 */ + 0x554 /* T_SATA0_LOW_POWER_COUNT */ +}; + +static u16 pg_save_config_port_registers[] = { + /* Save and restore per port */ + /* need to have port selected */ + 0x530, /* T_SATA0_CHXCFG1 */ + 0x684, /* T_SATA0_CHX_MISC */ + 0x700, /* T_SATA0_CHXCFG3 */ + 0x704, /* T_SATA0_CHXCFG4_CHX */ + 0x690, /* T_SATA0_CHX_PHY_CTRL1_GEN1 */ + 0x694, /* T_SATA0_CHX_PHY_CTRL1_GEN2 */ + 0x698, /* T_SATA0_CHX_PHY_CTRL1_GEN3 */ + 0x69C, /* T_SATA0_CHX_PHY_CTRL_2 */ + 0x6B0, /* T_SATA0_CHX_PHY_CTRL_3 */ + 0x6B4, /* T_SATA0_CHX_PHY_CTRL_4 */ + 0x6B8, /* T_SATA0_CHX_PHY_CTRL_5 */ + 0x6BC, /* T_SATA0_CHX_PHY_CTRL_6 */ + 0x714, /* T_SATA0_PRBS_CHX - OP */ + 0x750, /* T_SATA0_CHX_LINK0 */ + 0x7F0 /* T_SATA0_CHX_GLUE */ +}; + +static u16 pg_save_ipfs_registers[] = { + 0x094, /* SATA_FPCI_BAR5_0 */ + 0x0C0, /* SATA_MSI_BAR_SZ_0 */ + 0x0C4, /* SATA_MSI_AXI_BAR_ST_0 */ + 0x0C8, /* SATA_MSI_FPCI_BAR_ST_0 */ + 0x140, /* SATA_MSI_EN_VEC0_0 */ + 0x144, /* SATA_MSI_EN_VEC1_0 */ + 0x148, /* SATA_MSI_EN_VEC2_0 */ + 0x14C, /* SATA_MSI_EN_VEC3_0 */ + 0x150, /* SATA_MSI_EN_VEC4_0 */ + 0x154, /* SATA_MSI_EN_VEC5_0 */ + 0x158, /* SATA_MSI_EN_VEC6_0 */ + 0x15C, /* SATA_MSI_EN_VEC7_0 */ + 0x180, /* SATA_CONFIGURATION_0 */ + 0x184, /* SATA_FPCI_ERROR_MASKS_0 */ + 0x188, /* SATA_INTR_MASK_0 */ + 0x1A0, /* SATA_CFG_REVID_0 */ + 0x198, /* SATA_IPFS_INTR_ENABLE_0 */ + 0x1BC, /* SATA_CLKGATE_HYSTERSIS_0 */ + 0x1DC /* SATA_SATA_MCCIF_FIFOCTRL_0 */ +}; + +static void tegra_ahci_save_regs(u32 **save_addr, + u32 reg_base, + u16 reg_array[], + u32 regs) +{ + u32 i; + u32 *dest = (u32 *)*save_addr; + u32 base = (u32)IO_ADDRESS(reg_base); + + for (i = 0; i < regs; ++i, ++dest) { + *dest = readl(base + (u32)reg_array[i]); + AHCI_DBG_PRINT("save: [0x%x]=0x%08x\n", + (reg_base+(u32)reg_array[i]), *dest); + } + *save_addr = dest; +} + +static void tegra_ahci_restore_regs(void **save_addr, + u32 reg_base, + u16 reg_array[], + u32 regs) +{ + u32 i; + u32 *src = (u32 *)*save_addr; + u32 base = (u32)IO_ADDRESS(reg_base); + + for (i = 0; i < regs; ++i, ++src) { + writel(*src, base + (u32)reg_array[i]); + AHCI_DBG_PRINT("restore: [0x%x]=0x%08x\n", + (reg_base+(u32)reg_array[i]), *src); + } + *save_addr = src; +} + +static void tegra_ahci_pg_save_registers(struct ata_host *host) +{ + struct tegra_ahci_host_priv *tegra_hpriv; + u32 *pg_save; + u32 regs; + int i; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + pg_save = tegra_hpriv->pg_save; + + /* + * Driver should save/restore the registers in the order of + * IPFS, CFG, Ext CFG, BAR5. + */ + + /* save IPFS registers */ + regs = ARRAY_SIZE(pg_save_ipfs_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_BASE, + pg_save_ipfs_registers, regs); + /* after the call, pg_save should point to the next address to save */ + + /* save CONFIG registers */ + regs = ARRAY_SIZE(pg_save_config_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_save_config_registers, regs); + + /* save CONFIG per port registers */ + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + scfg_writel((1 << i), T_SATA0_INDEX_OFFSET); + regs = ARRAY_SIZE(pg_save_config_port_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_save_config_port_registers, regs); + } + scfg_writel(SATA0_NONE_SELECTED, T_SATA0_INDEX_OFFSET); + + /* save BAR5 registers */ + regs = ARRAY_SIZE(pg_save_bar5_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_BAR5_BASE, + pg_save_bar5_registers, regs); + + /* save BAR5 port_registers */ + regs = ARRAY_SIZE(pg_save_bar5_port_registers); + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_BAR5_BASE + (0x80*i), + pg_save_bar5_port_registers, regs); + + /* save bkdr registers */ + regs = ARRAY_SIZE(pg_save_bar5_bkdr_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_BAR5_BASE, + pg_save_bar5_bkdr_registers, regs); + + /* and save bkdr per_port registers */ + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + scfg_writel((1 << i), T_SATA0_INDEX_OFFSET); + regs = ARRAY_SIZE(pg_save_bar5_bkdr_port_registers); + tegra_ahci_save_regs(&pg_save, TEGRA_SATA_BAR5_BASE + (0x80*i), + pg_save_bar5_bkdr_port_registers, + regs); + } + scfg_writel(SATA0_NONE_SELECTED, T_SATA0_INDEX_OFFSET); +} + +static void tegra_ahci_pg_restore_registers(struct ata_host *host) +{ + struct tegra_ahci_host_priv *tegra_hpriv; + void *pg_save; + u32 regs, val; + int i; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + pg_save = tegra_hpriv->pg_save; + + /* + * Driver should restore the registers in the order of + * IPFS, CFG, Ext CFG, BAR5. + */ + + /* restore IPFS registers */ + regs = ARRAY_SIZE(pg_save_ipfs_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_BASE, + pg_save_ipfs_registers, regs); + /* after the call, pg_save should point to the next addr to restore */ + + /* restore CONFIG registers */ + regs = ARRAY_SIZE(pg_save_config_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_save_config_registers, regs); + + /* restore CONFIG per port registers */ + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + scfg_writel((1 << i), T_SATA0_INDEX_OFFSET); + regs = ARRAY_SIZE(pg_save_config_port_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_save_config_port_registers, + regs); + } + scfg_writel(SATA0_NONE_SELECTED, T_SATA0_INDEX_OFFSET); + + /* restore BAR5 registers */ + regs = ARRAY_SIZE(pg_save_bar5_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_BAR5_BASE, + pg_save_bar5_registers, regs); + + /* restore BAR5 port_registers */ + regs = ARRAY_SIZE(pg_save_bar5_port_registers); + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_BAR5_BASE+(0x80*i), + pg_save_bar5_port_registers, regs); + + /* restore bkdr registers */ + regs = ARRAY_SIZE(pg_restore_bar5_bkdr_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_restore_bar5_bkdr_registers, regs); + + /* and restore BAR5 bkdr per_port registers */ + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + scfg_writel((1 << i), T_SATA0_INDEX_OFFSET); + regs = ARRAY_SIZE(pg_restore_bar5_bkdr_port_registers); + tegra_ahci_restore_regs(&pg_save, TEGRA_SATA_CONFIG_BASE, + pg_restore_bar5_bkdr_port_registers, + regs); + } + scfg_writel(SATA0_NONE_SELECTED, T_SATA0_INDEX_OFFSET); + + /* program class code and programming interface for AHCI */ + val = scfg_readl(TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); + val |= TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE_EN; + scfg_writel(val, TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); + scfg_writel(TEGRA_PRIVATE_AHCI_CC_BKDR_PGM, TEGRA_PRIVATE_AHCI_CC_BKDR); + val &= ~TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE_EN; + scfg_writel(val, TEGRA_PRIVATE_AHCI_CC_BKDR_OVERRIDE); +} + +static u32 tegra_ahci_port_error(struct ata_port *ap) +{ + void __iomem *port_mmio = ahci_port_base(ap); + u32 err_status; + + err_status = readl(port_mmio + PORT_IRQ_STAT); + /* excludes PhyRdy and Connect Change status */ + err_status &= (PORT_IRQ_ERROR & (~(PORT_IRQ_PHYRDY|PORT_IRQ_CONNECT))); + return err_status; +} + +static bool tegra_ahci_check_errors(struct ata_host *host) +{ int i; + struct ata_port *ap; + u32 err; + + for (i = 0; i < host->n_ports; i++) { + ap = host->ports[i]; + err = tegra_ahci_port_error(ap); + if (err) { + dev_err(host->dev, + "pg-chk-err = 0x%08x on port %d\n", err, i); + return true; + } + } + return false; +} + +static void tegra_ahci_abort_power_gate(struct ata_host *host) +{ + u32 val; + + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PG_INFO_MASK; + val |= PG_INFO_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); +} + +static bool tegra_ahci_power_gate(struct ata_host *host) +{ + u32 val; + u32 dat; + struct tegra_ahci_host_priv *tegra_hpriv; + int status; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PG_INFO_MASK; + val |= PG_INFO_ON; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + tegra_ahci_pg_save_registers(host); + + /* + * Read SATA_AUX_MISC_CNTL_1_0 register L0_RX_IDLE_T_SAX field and + * write that value into same register L0_RX_IDLE_T_NPG field. + * And write 1 to L0_RX_IDLE_T_MUX field. + */ + val = misc_readl(SATA_AUX_MISC_CNTL_1_REG); + dat = val; + dat &= L0_RX_IDLE_T_SAX_MASK; + dat >>= L0_RX_IDLE_T_SAX_SHIFT; + dat <<= L0_RX_IDLE_T_NPG_SHIFT; + val &= ~L0_RX_IDLE_T_NPG_MASK; + val |= dat; + val &= ~L0_RX_IDLE_T_MUX_MASK; + val |= L0_RX_IDLE_T_MUX_FROM_APB_MISC; + misc_writel(val, SATA_AUX_MISC_CNTL_1_REG); + + /* abort PG if there are errors occurred */ + if (tegra_ahci_check_errors(host)) { + dev_err(host->dev, "** pg: errors; abort power gating **\n"); + return false; + } + /* make sure all ports have no outstanding commands and are idle. */ + if (!tegra_ahci_are_all_ports_idle(host)) { + dev_err(host->dev, "** pg: cmds; abort power gating **\n"); + return false; + } + + /* + * Hw wake up is not needed: + * Driver/RM shall place the SATA PHY and SATA PADPLL in IDDQ. + * SATA_PADPLL_RESET_SWCTL =1 + * SATA_PADPLL_RESET_OVERRIDE_VALUE=1 + * SATA_PADPHY_IDDQ_SWCTL=1 + * SATA_PADPHY_IDDQ_OVERRIDE_VALUE=1 + */ + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~(PADPLL_RESET_SWCTL_MASK | PADPLL_RESET_OVERRIDE_VALUE_MASK); + val |= (PADPLL_RESET_SWCTL_ON | PADPLL_RESET_OVERRIDE_VALUE_ON); + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~(PADPHY_IDDQ_OVERRIDE_VALUE_MASK | PADPHY_IDDQ_SWCTL_MASK); + val |= (PADPHY_IDDQ_SWCTL_ON | PADPHY_IDDQ_OVERRIDE_VALUE_ON); + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + /* Wait for time specified in SATA_LANE_IDDQ2_PADPLL_IDDQ */ + val = clk_readl(CLK_RST_SATA_PLL_CFG1_REG); + dat = (val & IDDQ2LANE_IDDQ_DLY_MASK) >> IDDQ2LANE_IDDQ_DLY_SHIFT; + udelay(dat); + + /* SATA_PADPLL_IDDQ_SWCTL=1 & SATA_PADPLL_IDDQ_OVERRIDE_VALUE=1 */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~(PADPLL_IDDQ_OVERRIDE_VALUE_MASK | PADPLL_IDDQ_SWCTL_MASK); + val |= (PADPLL_IDDQ_SWCTL_ON | PADPLL_IDDQ_OVERRIDE_VALUE_ON); + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + /* power off the sata */ + status = tegra_powergate_partition_with_clk_off(TEGRA_POWERGATE_SATA); + if (status) + dev_err(host->dev, "** failed to turn-off SATA (0x%x) **\n", + status); + + return true; +} + +static bool tegra_ahci_power_un_gate(struct ata_host *host) +{ + u32 val; + u32 dat; + u32 timeout; + struct tegra_ahci_host_priv *tegra_hpriv; + int status; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + /* get sata phy and pll out of iddq: */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PADPLL_IDDQ_OVERRIDE_VALUE_MASK; + val |= PADPLL_IDDQ_OVERRIDE_VALUE_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + /* wait for delay of IDDQ2LAND_SLUMBER_DLY */ + val = clk_readl(CLK_RST_SATA_PLL_CFG1_REG); + dat = (val & IDDQ2LANE_SLUMBER_DLY_MASK) >> IDDQ2LANE_SLUMBER_DLY_SHIFT; + udelay(dat); + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PADPHY_IDDQ_OVERRIDE_VALUE_MASK; + val |= PADPHY_IDDQ_OVERRIDE_VALUE_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + status = tegra_unpowergate_partition_with_clk_on(TEGRA_POWERGATE_SATA); + if (status) + dev_err(host->dev, "** failed to turn-on SATA (0x%x) **\n", + status); + + /* deasset PADPLL and wait until it locks. */ + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~PADPLL_RESET_OVERRIDE_VALUE_MASK; + val |= PADPLL_RESET_OVERRIDE_VALUE_OFF; + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + + /* + * Wait for SATA_AUX_PAD_PLL_CNTL_1_0_LOCKDET to turn 1 with a timeout + * of 15 us. + */ + timeout = 15; + while (timeout--) { + udelay(1); + val = misc_readl(SATA_AUX_PAD_PLL_CNTL_1_REG); + if (val & LOCKDET_FIELD) + break; + } + if (timeout == 0) + pr_err("%s: SATA_PAD_PLL is not locked in 15us.\n", __func__); + + /* restore registers */ + tegra_ahci_pg_restore_registers(host); + + /* + * During the restoration of the registers, the driver would now need to + * restore the register T_SATA0_CFG_POWER_GATE_SSTS_RESTORED after the + * ssts_det, ssts_spd are restored. This register is used to tell the + * controller whether a drive existed earlier or not and move the PHY + * state machines into either HR_slumber or not. + */ + val = scfg_readl(T_SATA0_CFG_POWER_GATE); + val &= ~POWER_GATE_SSTS_RESTORED_MASK; + val |= POWER_GATE_SSTS_RESTORED_YES; + scfg_writel(val, T_SATA0_CFG_POWER_GATE); + + /* + * Driver needs to switch the rx_idle_t driven source back to from + * Sata controller after SAX is power-ungated. + */ + val = misc_readl(SATA_AUX_MISC_CNTL_1_REG); + val &= ~L0_RX_IDLE_T_MUX_MASK; + val |= L0_RX_IDLE_T_MUX_FROM_SATA; + misc_writel(val, SATA_AUX_MISC_CNTL_1_REG); + + /* + * Driver can start to use main SATA interrupt instead of the + * rx_stat_t interrupt. + */ + val = pictlr_readl(PRI_ICTLR_CPU_IER_SET_0_OFFSET); + val |= CPU_IER_SATA_CTL; + pictlr_writel(val, PRI_ICTLR_CPU_IER_SET_0_OFFSET); + + /* Set the bits in the CAR to allow HW based low power sequencing. */ + val = clk_readl(CLK_RST_SATA_PLL_CFG0_REG); + val &= ~PADPLL_RESET_SWCTL_MASK; + val |= PADPLL_RESET_SWCTL_OFF; + clk_writel(val, CLK_RST_SATA_PLL_CFG0_REG); + + /* + * power un-gating process is complete by clearing + * APBDEV_PMC_SATA_PWRGT_0.Pmc2sata_pg_info = 0 + */ + val = pmc_readl(APB_PMC_SATA_PWRGT_0_REG); + val &= ~PG_INFO_MASK; + val |= PG_INFO_OFF; + pmc_writel(val, APB_PMC_SATA_PWRGT_0_REG); + + return true; +} + +static enum port_idle_status tegra_ahci_is_port_idle(struct ata_port *ap) +{ + void __iomem *port_mmio = ahci_port_base(ap); + + if (readl(port_mmio + PORT_CMD_ISSUE) || + readl(port_mmio + PORT_SCR_ACT)) + return PORT_IS_NOT_IDLE; + return PORT_IS_IDLE; +} + +static enum port_idle_status tegra_ahci_is_port_slumber(struct ata_port *ap) +{ + void __iomem *port_mmio = ahci_port_base(ap); + u32 sstat; + + if (tegra_ahci_is_port_idle(ap) == PORT_IS_NOT_IDLE) + return PORT_IS_NOT_IDLE; + + /* return 1 if PORT_SCR_STAT is in IPM_SLUMBER_STATE */ + sstat = readl(port_mmio + PORT_SCR_STAT); + if ((sstat & SSTAT_IPM_STATE_MASK) == SSTAT_IPM_SLUMBER_STATE) + return PORT_IS_SLUMBER; + return PORT_IS_IDLE_NOT_SLUMBER; +} + +/* check if all supported ports are idle (no outstanding commands) */ +static bool tegra_ahci_are_all_ports_idle(struct ata_host *host) +{ int i; + struct ata_port *ap; + + for (i = 0; i < host->n_ports; i++) { + ap = host->ports[i]; + if (ap && (tegra_ahci_is_port_idle(ap) == PORT_IS_NOT_IDLE)) + return false; + } + return true; +} + +/* check if all supported ports are in slumber */ +static bool tegra_ahci_are_all_ports_slumber(struct ata_host *host) +{ int i; + struct ata_port *ap; + + for (i = 0; i < host->n_ports; i++) { + ap = host->ports[i]; + if (ap && (tegra_ahci_is_port_slumber(ap) != PORT_IS_SLUMBER)) + return false; + } + return true; +} + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE +static void tegra_ahci_to_add_idle_timer(struct ata_host *host) +{ + struct tegra_ahci_host_priv *tegra_hpriv; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + /* note: the routine is called from interrupt context */ + spin_lock(&host->lock); + /* start idle-timer if all ports have no outstanding commands */ + if (tegra_ahci_are_all_ports_idle(host)) { + /* adjust tegra_ahci_idle_time to minimum if it is too small */ + tegra_ahci_idle_time = max((u32)TEGRA_AHCI_MIN_IDLE_TIME, + tegra_ahci_idle_time); + tegra_hpriv->idle_timer.expires = + ata_deadline(jiffies, tegra_ahci_idle_time); + mod_timer(&tegra_hpriv->idle_timer, + tegra_hpriv->idle_timer.expires); + } + spin_unlock(&host->lock); +} + +static void tegra_ahci_idle_timer(unsigned long arg) +{ + struct ata_host *host = (void *)arg; + struct tegra_ahci_host_priv *tegra_hpriv; + unsigned long flags; + + tegra_hpriv = (struct tegra_ahci_host_priv *)host->private_data; + + spin_lock_irqsave(&host->lock, flags); + if (tegra_hpriv->pg_state == SATA_ON) + tegra_hpriv->pg_state = SATA_GOING_OFF; + else { + dev_err(host->dev, "idle_timer: bad state (%u)\n", + tegra_hpriv->pg_state); + WARN_ON(1); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + spin_unlock_irqrestore(&host->lock, flags); + + pm_runtime_put(tegra_hpriv->dev); +} + +static int tegra_ahci_queue_one_qc(struct tegra_ahci_host_priv *tegra_hpriv, + struct ata_queued_cmd *qc) +{ + struct tegra_qc_list *qc_list; + + qc_list = kmalloc(sizeof(struct tegra_qc_list), GFP_ATOMIC); + if (!qc_list) { + dev_err(tegra_hpriv->dev, "failed to alloc qc_list\n"); + return AC_ERR_SYSTEM; + } + qc_list->qc = qc; + list_add_tail(&(qc_list->list), &(tegra_hpriv->qc_list)); + dev_dbg(tegra_hpriv->dev, "queuing qc=%x\n", (unsigned int)qc); + return 0; +} + +static void tegra_ahci_dequeue_qcs(struct tegra_ahci_host_priv *tegra_hpriv) +{ + struct list_head *list, *next; + struct tegra_qc_list *qc_list; + struct ata_queued_cmd *qc; + + /* now qc_issue all qcs in the qc_list */ + list_for_each_safe(list, next, &tegra_hpriv->qc_list) { + qc_list = list_entry(list, struct tegra_qc_list, list); + qc = qc_list->qc; + dev_dbg(tegra_hpriv->dev, "dequeue qc=%x\n", (unsigned int)qc); + ahci_ops.qc_issue(qc); + list_del(list); + kfree(qc_list); + } +} + +static unsigned int tegra_ahci_qc_issue(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct ata_host *host = ap->host; + struct tegra_ahci_host_priv *tegra_hpriv = host->private_data; + int rc; + + /* stop the idle timer */ + if (timer_pending(&tegra_hpriv->idle_timer)) + del_timer_sync(&tegra_hpriv->idle_timer); + + /* note: host->lock is locked */ + switch (tegra_hpriv->pg_state) { + case SATA_ON: + /* normal case, issue the qc */ + return ahci_ops.qc_issue(qc); + case SATA_GOING_OFF: + case SATA_ABORT_OFF: + /* SATA is going OFF, let's abort the suspend */ + dev_dbg(host->dev, "** qc_issue: going OFF **\n"); + tegra_hpriv->pg_state = SATA_ABORT_OFF; + return tegra_ahci_queue_one_qc(tegra_hpriv, qc); + case SATA_OFF: + dev_dbg(host->dev, "** qc_issue: request power-up sata **\n"); + rc = pm_runtime_get(tegra_hpriv->dev); + /* rc == 0 means the request has been queued successfully */ + if (rc) { + dev_err(host->dev, "** qc_issue: rt_get()=%d **\n", + rc); + WARN_ON(1); + return AC_ERR_SYSTEM; + } + tegra_hpriv->pg_state = SATA_GOING_ON; + /* continue with the following code to queue the qc */ + case SATA_GOING_ON: + return tegra_ahci_queue_one_qc(tegra_hpriv, qc); + default: + dev_err(host->dev, "** qc_issue: bad state (%u) **\n", + tegra_hpriv->pg_state); + WARN_ON(1); + return AC_ERR_SYSTEM; + } +} + +static int tegra_ahci_hardreset(struct ata_link *link, unsigned int *class, + unsigned long deadline) +{ + struct ata_port *ap = link->ap; + struct ata_host *host = ap->host; + struct tegra_ahci_host_priv *tegra_hpriv = host->private_data; + int rc; + + if (tegra_hpriv->pg_state == SATA_OFF) { + dev_dbg(host->dev, "** hreset: request power-up sata **\n"); + rc = pm_runtime_get_sync(tegra_hpriv->dev); + /* rc == 0 means the request has been run successfully */ + if (rc) { + dev_err(host->dev, "** hreset: rt_get()=%d **\n", rc); + WARN_ON(1); + return AC_ERR_SYSTEM; + } + tegra_hpriv->pg_state = SATA_ON; + } + + return ahci_ops.hardreset(link, class, deadline); +} + +static irqreturn_t tegra_ahci_interrupt(int irq, void *dev_instance) +{ + irqreturn_t irq_retval; + + irq_retval = ahci_interrupt(irq, dev_instance); + if (irq_retval == IRQ_NONE) + return IRQ_NONE; + +#ifdef CONFIG_PM + tegra_ahci_to_add_idle_timer((struct ata_host *)dev_instance); +#endif + + return irq_retval; +} +#endif +#endif + +static int __devexit tegra_ahci_remove_one(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct ahci_host_priv *hpriv; + + BUG_ON(host == NULL); + BUG_ON(host->iomap[AHCI_PCI_BAR] == NULL); + hpriv = host->private_data; + + tegra_ahci_controller_remove(pdev); + + devm_iounmap(&pdev->dev, host->iomap[AHCI_PCI_BAR]); + devres_free(host); + +#ifdef CONFIG_PM + /* Free PG save/restore area */ + devm_kfree(&pdev->dev, ((struct tegra_ahci_host_priv *)hpriv)->pg_save); + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + pm_runtime_disable(&pdev->dev); +#endif +#endif + + devm_kfree(&pdev->dev, hpriv); + + return 0; +} + +static int __devinit tegra_ahci_init_one(struct platform_device *pdev) +{ + struct ata_port_info pi = ahci_port_info; + const struct ata_port_info *ppi[] = { &pi, NULL }; + struct device *dev = &pdev->dev; + struct ahci_host_priv *hpriv = NULL; + struct tegra_ahci_host_priv *tegra_hpriv; + struct ata_host *host = NULL; + int n_ports, i, rc; + struct resource *res, *irq_res; + void __iomem *mmio; + u32 save_size; + irq_handler_t irq_handler = ahci_interrupt; + + VPRINTK("ENTER\n"); + + WARN_ON((int)ATA_MAX_QUEUE > AHCI_MAX_CMDS); + + ata_print_version_once(&pdev->dev, DRV_VERSION); + + /* Simple resource validation */ + if (pdev->num_resources != 3) { + dev_err(dev, "invalid number of resources\n"); + dev_err(dev, "not enough SATA resources\n"); + return -EINVAL; + } + + /* acquire bar resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -EINVAL; + + /* acquire IRQ resource */ + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (irq_res == NULL) + return -EINVAL; + if (irq_res->start <= 0) + return -EINVAL; + + /* allocate sizeof tegra_ahci_host_priv, which contains extra fields */ + hpriv = devm_kzalloc(dev, sizeof(struct tegra_ahci_host_priv), + GFP_KERNEL); + if (!hpriv) { + rc = -ENOMEM; + goto fail; + } + hpriv->flags |= (unsigned long)pi.private_data; + tegra_hpriv = (struct tegra_ahci_host_priv *)hpriv; + g_tegra_hpriv = tegra_hpriv; + + /* Call tegra init routine */ + rc = tegra_ahci_controller_init(tegra_hpriv); + if (rc != 0) { + dev_err(dev, "TEGRA SATA init failed\n"); + goto fail; + } + + /* + * We reserve a table of 6 BARs in tegra_hpriv to store BARs. + * Save the mapped AHCI_PCI_BAR address to the table. + */ + mmio = devm_ioremap(dev, res->start, (res->end-res->start+1)); + tegra_hpriv->bars_table[AHCI_PCI_BAR] = mmio; + hpriv->mmio = mmio; + + /* save initial config */ + tegra_ahci_save_initial_config(pdev, hpriv); + dev_dbg(dev, "past save init config\n"); + + /* prepare host */ + if (hpriv->cap & HOST_CAP_NCQ) { + pi.flags |= ATA_FLAG_NCQ; + pi.flags |= ATA_FLAG_FPDMA_AA; + } + + /* + * CAP.NP sometimes indicate the index of the last enabled + * port, at other times, that of the last possible port, so + * determining the maximum port number requires looking at + * both CAP.NP and port_map. + */ + n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map)); + host = ata_host_alloc_pinfo(dev, ppi, n_ports); + if (!host) { + rc = -ENOMEM; + goto fail; + } + host->private_data = hpriv; + tegra_hpriv->host = host; + tegra_hpriv->dev = dev; + host->iomap = tegra_hpriv->bars_table; + + if (!(hpriv->cap & HOST_CAP_SSS)) + host->flags |= ATA_HOST_PARALLEL_SCAN; + else + printk(KERN_INFO "ahci: SSS flag set, parallel bus scan disabled\n"); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + + /* set initial link pm policy */ + ap->target_lpm_policy = ATA_LPM_UNKNOWN; + + /* disabled/not-implemented port */ + if (!(hpriv->port_map & (1 << i))) + ap->ops = &ata_dummy_port_ops; + else + ap->target_lpm_policy = ATA_LPM_MIN_POWER; + } + + rc = ahci_reset_controller(host); + if (rc) { + dev_err(dev, "Reset controller failed! (rc=%d)\n", rc); + goto fail; + } + + ahci_init_controller(host); + ahci_print_info(host, "TEGRA-SATA"); + dev_dbg(dev, "controller init okay\n"); + +#ifdef CONFIG_PM + /* Setup PG save/restore area: */ + + /* calculate the size */ + save_size = ARRAY_SIZE(pg_save_ipfs_registers) + + ARRAY_SIZE(pg_save_config_registers) + + ARRAY_SIZE(pg_save_bar5_registers) + + ARRAY_SIZE(pg_save_bar5_bkdr_registers); + + /* and add save port_registers for all the ports */ + save_size += TEGRA_AHCI_NUM_PORTS * + (ARRAY_SIZE(pg_save_config_port_registers) + + ARRAY_SIZE(pg_save_bar5_port_registers) + + ARRAY_SIZE(pg_save_bar5_bkdr_port_registers)); + + /* + * save_size is number of registers times number of bytes per + * register to get total save size. + */ + save_size *= sizeof(u32); + tegra_hpriv->pg_save = devm_kzalloc(dev, save_size, GFP_KERNEL); + if (!tegra_hpriv->pg_save) { + rc = -ENOMEM; + goto fail; + } + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + pm_runtime_set_active(dev); + pm_suspend_ignore_children(dev, true); + pm_runtime_enable(dev); + + tegra_hpriv->pg_state = SATA_ON; + + /* setup sata idle timer */ + init_timer_deferrable(&tegra_hpriv->idle_timer); + tegra_hpriv->idle_timer.function = tegra_ahci_idle_timer; + tegra_hpriv->idle_timer.data = (unsigned long)host; + + INIT_LIST_HEAD(&tegra_hpriv->qc_list); + + /* use our own irq handler */ + irq_handler = tegra_ahci_interrupt; +#endif + +#endif + + rc = ata_host_activate(host, irq_res->start, irq_handler, 0, &ahci_sht); + if (rc == 0) + return 0; + + /* Free PG save/restore area */ + devm_kfree(dev, tegra_hpriv->pg_save); + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + pm_runtime_put(dev); + pm_runtime_enable(dev); +#endif + +fail: + if (host) { + if (host->iomap[AHCI_PCI_BAR]) + devm_iounmap(dev, host->iomap[AHCI_PCI_BAR]); + devres_free(host); + } + if (hpriv) + devm_kfree(dev, hpriv); + + return rc; +} + +static int __init ahci_init(void) +{ + return platform_driver_register(&tegra_platform_ahci_driver); +} + +static void __exit ahci_exit(void) +{ + platform_driver_unregister(&tegra_platform_ahci_driver); +} + + +#ifdef CONFIG_DEBUG_FS + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +static void dbg_ahci_dump_regs(struct seq_file *s, u32 *ptr, u32 base, u32 regs) +{ +#define REGS_PER_LINE 4 + + u32 i, j; + u32 lines = regs / REGS_PER_LINE; + + for (i = 0; i < lines; i++) { + seq_printf(s, "0x%08x: ", base+(i*16)); + for (j = 0; j < REGS_PER_LINE; ++j) { + seq_printf(s, "0x%08x ", readl(ptr)); + ++ptr; + } + seq_printf(s, "\n"); + } +#undef REGS_PER_LINE +} + +static int dbg_ahci_dump_show(struct seq_file *s, void *unused) +{ + u32 base; + u32 *ptr; + u32 i; + + base = TEGRA_SATA_CONFIG_BASE; + ptr = (u32 *)IO_TO_VIRT(base); + seq_printf(s, "SATA CONFIG Registers:\n"); + seq_printf(s, "----------------------\n"); + dbg_ahci_dump_regs(s, ptr, base, 0x200); + + base = TEGRA_SATA_BAR5_BASE; + ptr = (u32 *)IO_TO_VIRT(base); + seq_printf(s, "\nAHCI HBA Registers:\n"); + seq_printf(s, "-------------------\n"); + dbg_ahci_dump_regs(s, ptr, base, 64); + + for (i = 0; i < TEGRA_AHCI_NUM_PORTS; ++i) { + base = TEGRA_SATA_BAR5_BASE + 0x100 + (0x80*i); + ptr = (u32 *)IO_TO_VIRT(base); + seq_printf(s, "\nPort %u Registers:\n", i); + seq_printf(s, "---------------\n"); + dbg_ahci_dump_regs(s, ptr, base, 16); + } + +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + /* adjust tegra_ahci_idle_time to minimum if it is too small */ + tegra_ahci_idle_time = max((u32)TEGRA_AHCI_MIN_IDLE_TIME, + tegra_ahci_idle_time); + seq_printf(s, "\nIdle Timeout = %u milli-seconds.\n", + tegra_ahci_idle_time); +#endif + + if (tegra_powergate_is_powered(TEGRA_POWERGATE_SATA)) + seq_printf(s, "\n=== SATA controller is powered on ===\n\n"); + else + seq_printf(s, "\n=== SATA controller is powered off ===\n\n"); + + return 0; +} + +static int dbg_ahci_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_ahci_dump_show, &inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = dbg_ahci_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init tegra_ahci_dump_debuginit(void) +{ + (void) debugfs_create_file("tegra_ahci", S_IRUGO, + NULL, NULL, &debug_fops); +#ifdef CONFIG_TEGRA_SATA_IDLE_POWERGATE + (void) debugfs_create_u32("tegra_ahci_idle_ms", S_IRWXUGO, + NULL, &tegra_ahci_idle_time); +#endif + return 0; +} +late_initcall(tegra_ahci_dump_debuginit); +#endif + +MODULE_AUTHOR("NVIDIA"); +MODULE_DESCRIPTION("Tegra AHCI SATA low-level driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); + +module_init(ahci_init); +module_exit(ahci_exit); diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 96916bed997f..1358dc70b0e4 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -331,12 +331,19 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) { u32 val; int tx_fifo_avail; - u8 *buf = i2c_dev->msg_buf; - size_t buf_remaining = i2c_dev->msg_buf_remaining; + u8 *buf; + size_t buf_remaining; int words_to_transfer; unsigned long flags; spin_lock_irqsave(&i2c_dev->fifo_lock, flags); + if (!i2c_dev->msg_buf_remaining) { + spin_unlock_irqrestore(&i2c_dev->fifo_lock, flags); + return 0; + } + + buf = i2c_dev->msg_buf; + buf_remaining = i2c_dev->msg_buf_remaining; val = i2c_readl(i2c_dev, I2C_FIFO_STATUS); tx_fifo_avail = (val & I2C_FIFO_STATUS_TX_MASK) >> @@ -375,7 +382,12 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) * boundary and fault. */ if (tx_fifo_avail > 0 && buf_remaining > 0) { - BUG_ON(buf_remaining > 3); + if (buf_remaining > 3) { + dev_err(i2c_dev->dev, + "Remaining buffer more than 3 %d\n", + buf_remaining); + BUG(); + } memcpy(&val, buf, buf_remaining); /* Again update before writing to FIFO to make sure isr sees. */ diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig index f5aea996e2d6..5099fe12c3e2 100644 --- a/drivers/media/video/tegra/Kconfig +++ b/drivers/media/video/tegra/Kconfig @@ -95,3 +95,10 @@ config VIDEO_AD5820 ---help--- This is a driver for the AD5820 focuser for use with the tegra isp. + +config VIDEO_AD5816 + tristate "AD5816 focuser support" + depends on I2C && ARCH_TEGRA + ---help--- + This is a driver for the AD5816 focuser + for use with the tegra isp. diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile index 08b81e261611..3c1a0ce4638e 100644 --- a/drivers/media/video/tegra/Makefile +++ b/drivers/media/video/tegra/Makefile @@ -18,4 +18,5 @@ obj-$(CONFIG_TORCH_SSL3250A) += ssl3250a.o obj-$(CONFIG_TORCH_TPS61050) += tps61050.o obj-$(CONFIG_VIDEO_SH532U) += sh532u.o obj-$(CONFIG_VIDEO_AD5820) += ad5820.o +obj-$(CONFIG_VIDEO_AD5816) += ad5816.o diff --git a/drivers/media/video/tegra/ad5816.c b/drivers/media/video/tegra/ad5816.c new file mode 100644 index 000000000000..d95368f43cf6 --- /dev/null +++ b/drivers/media/video/tegra/ad5816.c @@ -0,0 +1,1204 @@ +/* Copyright (C) 2011-2012 NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ +/* This is a NVC kernel driver for a focuser device called + * ad5816. + */ +/* Implementation + * -------------- + * The board level details about the device need to be provided in the board + * file with the <device>_platform_data structure. + * Standard among NVC kernel drivers in this structure is: + * .cfg = Use the NVC_CFG_ defines that are in nvc.h. + * Descriptions of the configuration options are with the defines. + * This value is typically 0. + * .num = The number of the instance of the device. This should start at 1 and + * and increment for each device on the board. This number will be + * appended to the MISC driver name, Example: /dev/focuser.1 + * If not used or 0, then nothing is appended to the name. + * .sync = If there is a need to synchronize two devices, then this value is + * the number of the device instance (.num above) this device is to + * sync to. For example: + * Device 1 platform entries = + * .num = 1, + * .sync = 2, + * Device 2 platfrom entries = + * .num = 2, + * .sync = 1, + * The above example sync's device 1 and 2. + * To disable sync, set .sync = 0. Note that the .num = 0 device is not + * allowed to be synced to. + * This is typically used for stereo applications. + * .dev_name = The MISC driver name the device registers as. If not used, + * then the part number of the device is used for the driver name. + * If using the NVC user driver then use the name found in this + * driver under _default_pdata. + * .gpio_count = The ARRAY_SIZE of the nvc_gpio_pdata table. + * .gpio = A pointer to the nvc_gpio_pdata structure's platform GPIO data. + * The GPIO mechanism works by cross referencing the .gpio_type key + * among the nvc_gpio_pdata GPIO data and the driver's nvc_gpio_init + * GPIO data to build a GPIO table the driver can use. The GPIO's + * defined in the device header file's _gpio_type enum are the + * gpio_type keys for the nvc_gpio_pdata and nvc_gpio_init structures. + * These need to be present in the board file's nvc_gpio_pdata + * structure for the GPIO's that are used. + * The driver's GPIO logic uses assert/deassert throughout until the + * low level _gpio_wr/rd calls where the .assert_high is used to + * convert the value to the correct signal level. + * See the GPIO notes in nvc.h for additional information. + * + * The following is specific to NVC kernel focus drivers: + * .nvc = Pointer to the nvc_focus_nvc structure. This structure needs to + * be defined and populated if overriding the driver defaults. + * .cap = Pointer to the nvc_focus_cap structure. This structure needs to + * be defined and populated if overriding the driver defaults. + * + * The following is specific to this NVC kernel focus driver: + * .info = Pointer to the ad5816_pdata_info structure. This structure does + * not need to be defined and populated unless overriding ROM data. + * + * Power Requirements: + * The device's header file defines the voltage regulators needed with the + * enumeration <device>_vreg. The order these are enumerated is the order + * the regulators will be enabled when powering on the device. When the + * device is powered off the regulators are disabled in descending order. + * The <device>_vregs table in this driver uses the nvc_regulator_init + * structure to define the regulator ID strings that go with the regulators + * defined with <device>_vreg. These regulator ID strings (or supply names) + * will be used in the regulator_get function in the _vreg_init function. + * The board power file and <device>_vregs regulator ID strings must match. + */ + +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <linux/list.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <media/ad5816.h> + +#define AD5816_ID 0x04 +#define AD5816_FOCAL_LENGTH (4.570f) +#define AD5816_FNUMBER (2.8f) +#define AD5816_ACTUATOR_RANGE 680 +#define AD5816_SETTLETIME 110 +#define AD5816_FOCUS_MACRO 810 +#define AD5816_FOCUS_INFINITY 50 /* Exact value needs to be decided */ +#define AD5816_POS_LOW_DEFAULT 220 +#define AD5816_POS_HIGH_DEFAULT 900 +/* Need to decide exact value of VCM_THRESHOLD and its use */ +/* define AD5816_VCM_THRESHOLD 20 */ + +static u8 ad5816_ids[] = { + 0x04, +}; + +static struct nvc_gpio_init ad5816_gpios[] = { + { AD5816_GPIO_RESET, GPIOF_OUT_INIT_LOW, "reset", false, true, }, + { AD5816_GPIO_I2CMUX, 0, "i2c_mux", 0, false}, + { AD5816_GPIO_GP1, 0, "gp1", 0, false}, + { AD5816_GPIO_GP2, 0, "gp2", 0, false}, + { AD5816_GPIO_GP3, 0, "gp3", 0, false}, +}; + +static struct nvc_regulator_init ad5816_vregs[] = { + { AD5816_VREG_VDD, "vdd"}, + { AD5816_VREG_VDD_AF, "vdd_af"}, + { AD5816_VREG_VDD_I2C, "vdd_i2c"}, +}; + +struct ad5816_info { + atomic_t in_use; + struct i2c_client *i2c_client; + struct ad5816_platform_data *pdata; + struct miscdevice miscdev; + struct list_head list; + struct nvc_gpio gpio[ARRAY_SIZE(ad5816_gpios)]; + struct nvc_regulator vreg[ARRAY_SIZE(ad5816_vregs)]; + int pwr_api; + int pwr_dev; + int id_minor; + u32 pos; + u8 s_mode; + bool reset_flag; + struct ad5816_info *s_info; + struct nvc_focus_nvc nvc; + struct nvc_focus_cap cap; + struct ad5816_pdata_info config; +}; + +/** + * The following are default values + */ + +static struct ad5816_pdata_info ad5816_default_info = { + .pos_low = AD5816_POS_LOW_DEFAULT, + .pos_high = AD5816_POS_HIGH_DEFAULT, +}; + +static struct nvc_focus_cap ad5816_default_cap = { + .version = NVC_FOCUS_CAP_VER2, + .actuator_range = AD5816_ACTUATOR_RANGE, + .settle_time = AD5816_SETTLETIME, + .focus_macro = AD5816_FOCUS_MACRO, + .focus_infinity = AD5816_FOCUS_INFINITY, +}; + +static struct nvc_focus_nvc ad5816_default_nvc = { + .focal_length = AD5816_FOCAL_LENGTH, + .fnumber = AD5816_FNUMBER, +}; + +static struct ad5816_platform_data ad5816_default_pdata = { + .cfg = 0, + .num = 0, + .sync = 0, + .dev_name = "focuser", +}; +static LIST_HEAD(ad5816_info_list); +static DEFINE_SPINLOCK(ad5816_spinlock); + +static int ad5816_i2c_rd8(struct ad5816_info *info, u8 addr, u8 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[2]; + buf[0] = reg; + if (addr) { + msg[0].addr = addr; + msg[1].addr = addr; + } else { + msg[0].addr = info->i2c_client->addr; + msg[1].addr = info->i2c_client->addr; + } + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &buf[0]; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &buf[1]; + *val = 0; + if (i2c_transfer(info->i2c_client->adapter, msg, 2) != 2) + return -EIO; + *val = buf[1]; + return 0; +} + +static int ad5816_i2c_wr8(struct ad5816_info *info, u8 reg, u8 val) +{ + struct i2c_msg msg; + u8 buf[2]; + buf[0] = reg; + buf[1] = val; + msg.addr = info->i2c_client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[0]; + if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1) + return -EIO; + return 0; +} + +static int ad5816_i2c_rd16(struct ad5816_info *info, u8 reg, u16 *val) +{ + struct i2c_msg msg[2]; + u8 buf[3]; + buf[0] = reg; + msg[0].addr = info->i2c_client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &buf[0]; + msg[1].addr = info->i2c_client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = &buf[1]; + if (i2c_transfer(info->i2c_client->adapter, msg, 2) != 2) + return -EIO; + *val = (((u16)buf[1] << 8) | (u16)buf[2]); + return 0; +} + +static int ad5816_i2c_wr16(struct ad5816_info *info, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3]; + buf[0] = reg; + buf[1] = (u8)(val >> 8); + buf[2] = (u8)(val & 0xff); + msg.addr = info->i2c_client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = &buf[0]; + if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1) + return -EIO; + return 0; +} + +static int ad5816_gpio_wr(struct ad5816_info *info, + enum ad5816_gpio_types i, + int val) /* val: 0=deassert, 1=assert */ +{ + int err = -EINVAL; + if (info->gpio[i].valid) { + if (val) + val = 1; + if (!info->gpio[i].active_high) + val = !val; + val &= 1; + err = val; + gpio_set_value_cansleep(info->gpio[i].gpio, val); + dev_dbg(&info->i2c_client->dev, "%s %u %d\n", __func__, info->gpio[i].gpio, val); + } + return err; /* return value written or error */ +} + +static int ad5816_gpio_reset(struct ad5816_info *info, int val) +{ + int err = 0; + + if (val) { + if (!info->reset_flag) { + info->reset_flag = true; + err = ad5816_gpio_wr(info, AD5816_GPIO_RESET, 1); + if (err < 0) + return 0; /* flag no reset */ + + mdelay(1); + ad5816_gpio_wr(info, AD5816_GPIO_RESET, 0); + mdelay(10); /* startup delay needs to be modified*/ + err = 1; /* flag that a reset was done */ + } + } else { + info->reset_flag = false; + } + return err; +} + +static void ad5816_gpio_able(struct ad5816_info *info, int val) +{ + /** + * This is a feature that allows driver to control GPIOs + * that may be needed for the board (not the device). + * */ + if (val) { + ad5816_gpio_wr(info, AD5816_GPIO_GP1, val); + ad5816_gpio_wr(info, AD5816_GPIO_GP2, val); + ad5816_gpio_wr(info, AD5816_GPIO_GP3, val); + } else { + ad5816_gpio_wr(info, AD5816_GPIO_GP3, val); + ad5816_gpio_wr(info, AD5816_GPIO_GP2, val); + ad5816_gpio_wr(info, AD5816_GPIO_GP1, val); + } +} +static void ad5816_gpio_exit(struct ad5816_info *info) +{ + unsigned i; + for (i = 0; i <= ARRAY_SIZE(ad5816_gpios); i++) { + if (info->gpio[i].flag && info->gpio[i].own) { + gpio_free(info->gpio[i].gpio); + info->gpio[i].own = false; + } + } +} + +static void ad5816_gpio_init(struct ad5816_info *info) +{ + char label[32]; + unsigned long flags; + unsigned type; + unsigned i; + unsigned j; + int err; + for (i = 0; i < ARRAY_SIZE(ad5816_gpios); i++) + info->gpio[i].flag = false; + + if (!info->pdata->gpio_count || !info->pdata->gpio) + return; + + for (i = 0; i < ARRAY_SIZE(ad5816_gpios); i++) { + type = ad5816_gpios[i].gpio_type; + + for (j = 0; j < info->pdata->gpio_count; j++) { + if (type == info->pdata->gpio[j].gpio_type) + break; + } + + if (j == info->pdata->gpio_count) + continue; + info->gpio[type].gpio = info->pdata->gpio[j].gpio; + info->gpio[type].flag = true; + + if (ad5816_gpios[i].use_flags) { + flags = ad5816_gpios[i].flags; + info->gpio[type].active_high = ad5816_gpios[i].active_high; + } else { + info->gpio[type].active_high = info->pdata->gpio[j].active_high; + if (info->gpio[type].active_high) + flags = GPIOF_OUT_INIT_LOW; + else + flags = GPIOF_OUT_INIT_HIGH; + } + + if (!info->pdata->gpio[j].init_en) + continue; + snprintf(label, sizeof(label), "ad5816_%u_%s", + info->pdata->num, ad5816_gpios[i].label); + err = gpio_request_one(info->gpio[type].gpio, flags, label); + if (err) { + dev_err(&info->i2c_client->dev, "%s ERR %s %u\n", + __func__, label, info->gpio[type].gpio); + } else { + info->gpio[type].own = true; + dev_dbg(&info->i2c_client->dev, "%s %s %u\n", + __func__, label, info->gpio[type].gpio); + } + } +} + +static int ad5816_vreg_dis(struct ad5816_info *info, + enum ad5816_vreg i) +{ + int err = 0; + if (info->vreg[i].vreg_flag && (info->vreg[i].vreg != NULL)) { + err = regulator_disable(info->vreg[i].vreg); + if (!err) + dev_dbg(&info->i2c_client->dev, "%s: %s\n", + __func__, info->vreg[i].vreg_name); + else + dev_err(&info->i2c_client->dev, "%s %s ERR\n", + __func__, info->vreg[i].vreg_name); + } + info->vreg[i].vreg_flag = false; + return err; +} + +static int ad5816_vreg_dis_all(struct ad5816_info *info) +{ + unsigned i; + int err = 0; + for (i = ARRAY_SIZE(ad5816_vregs); i > 0; i--) + err |= ad5816_vreg_dis(info, (i - 1)); + return err; +} + +static int ad5816_vreg_en(struct ad5816_info *info, + enum ad5816_vreg i) +{ + int err = 0; + if (!info->vreg[i].vreg_flag && (info->vreg[i].vreg != NULL)) { + err = regulator_enable(info->vreg[i].vreg); + + if (!err) { + dev_dbg(&info->i2c_client->dev, "%s: %s\n", + __func__, info->vreg[i].vreg_name); + info->vreg[i].vreg_flag = true; + err = 1; /* flag regulator state change */ + } else { + dev_err(&info->i2c_client->dev, "%s %s ERR\n", + __func__, info->vreg[i].vreg_name); + } + + } + return err; +} + +static int ad5816_vreg_en_all(struct ad5816_info *info) +{ + unsigned i; + int err = 0; + for (i = 0; i < ARRAY_SIZE(ad5816_vregs); i++) + err |= ad5816_vreg_en(info, i); + return err; +} + +static void ad5816_vreg_exit(struct ad5816_info *info) +{ + unsigned i; + for (i = 0; i < ARRAY_SIZE(ad5816_vregs); i++) { + regulator_put(info->vreg[i].vreg); + info->vreg[i].vreg = NULL; + } +} + +static int ad5816_vreg_init(struct ad5816_info *info) +{ + unsigned i; + unsigned j; + int err = 0; + for (i = 0; i < ARRAY_SIZE(ad5816_vregs); i++) { + j = ad5816_vregs[i].vreg_num; + info->vreg[j].vreg_name = ad5816_vregs[i].vreg_name; + info->vreg[j].vreg_flag = false; + info->vreg[j].vreg = regulator_get(&info->i2c_client->dev, + info->vreg[j].vreg_name); + if (IS_ERR_OR_NULL(info->vreg[j].vreg)) { + dev_dbg(&info->i2c_client->dev, "%s %s ERR: %d\n", + __func__, info->vreg[j].vreg_name, + (int)info->vreg[j].vreg); + err |= PTR_ERR(info->vreg[j].vreg); + info->vreg[j].vreg = NULL; + } else { + dev_dbg(&info->i2c_client->dev, "%s: %s\n", + __func__, info->vreg[j].vreg_name); + } + } + return err; +} + +void ad5816_set_power_down(struct ad5816_info *info) +{ + int err; + u16 data = 0x0000; + err = ad5816_i2c_wr16(info, VCM_CODE_MSB, data); + if (err) + dev_err(&info->i2c_client->dev, " %s: failed \n", + __func__); +} + +void ad5816_set_arc_mode(struct ad5816_info *info) +{ + int err = 0; + /* set ARC enable */ + err = ad5816_i2c_wr8(info, CONTROL, 0x02); + if (err) + dev_err(&info->i2c_client->dev, + "%s: CONTROL reg write failed \n", __func__); + + /* set the ARC RES2 */ + err = ad5816_i2c_wr8(info, MODE, 0x01); + if (err) + dev_err(&info->i2c_client->dev, + "%s: MODE reg write failed \n", __func__); + + /* set the VCM_FREQ to 12.8mS */ + err = ad5816_i2c_wr8(info, VCM_FREQ, 0x80); + if (err) + dev_err(&info->i2c_client->dev, + "%s: VCM_FREQ reg write failed \n", __func__); +} + +static int ad5816_pm_wr(struct ad5816_info *info, int pwr) +{ + int err = 0; + + if ((info->pdata->cfg & (NVC_CFG_OFF2STDBY | NVC_CFG_BOOT_INIT)) && + (pwr == NVC_PWR_OFF || + pwr == NVC_PWR_STDBY_OFF)) + pwr = NVC_PWR_STDBY; + + if (pwr == info->pwr_dev) + return 0; + + switch (pwr) + { + case NVC_PWR_OFF_FORCE: + case NVC_PWR_OFF: + err = ad5816_vreg_dis_all(info); + ad5816_gpio_able(info, 0); + ad5816_gpio_reset(info, 0); + break; + case NVC_PWR_STDBY_OFF: + case NVC_PWR_STDBY: + err = ad5816_vreg_en_all(info); + ad5816_gpio_able(info, 1); + ad5816_gpio_reset(info, 1); + break; + case NVC_PWR_COMM: + case NVC_PWR_ON: + err = ad5816_vreg_en_all(info); + ad5816_gpio_able(info, 1); + ad5816_gpio_reset(info, 1); + break; + default: + err = -EINVAL; + break; + } + + if (err < 0) { + dev_err(&info->i2c_client->dev, "%s err %d\n", __func__, err); + pwr = NVC_PWR_ERR; + } + + info->pwr_dev = pwr; + dev_dbg(&info->i2c_client->dev, "%s pwr_dev=%d\n", __func__, info->pwr_dev); + + if (err > 0) + return 0; + + return err; +} +static int ad5816_pm_wr_s(struct ad5816_info *info, int pwr) +{ + int err1 = 0; + int err2 = 0; + if ((info->s_mode == NVC_SYNC_OFF) || + (info->s_mode == NVC_SYNC_MASTER) || + (info->s_mode == NVC_SYNC_STEREO)) + err1 = ad5816_pm_wr(info, pwr); + if ((info->s_mode == NVC_SYNC_SLAVE) || + (info->s_mode == NVC_SYNC_STEREO)) + err2 = ad5816_pm_wr(info->s_info, pwr); + return err1 | err2; +} + +static int ad5816_pm_api_wr(struct ad5816_info *info, int pwr) +{ + int err = 0; + if (!pwr || (pwr > NVC_PWR_ON)) + return 0; + if (pwr > info->pwr_dev) { + err = ad5816_pm_wr_s(info, pwr); + } + if (!err) { + info->pwr_api = pwr; + } else + info->pwr_api = NVC_PWR_ERR; + if (info->pdata->cfg & NVC_CFG_NOERR) + return 0; + return err; +} + +static int ad5816_pm_dev_wr(struct ad5816_info *info, int pwr) +{ + if (pwr < info->pwr_api) + pwr = info->pwr_api; + return ad5816_pm_wr(info, pwr); +} + +static void ad5816_pm_exit(struct ad5816_info *info) +{ + ad5816_pm_wr(info, NVC_PWR_OFF_FORCE); + ad5816_vreg_exit(info); + ad5816_gpio_exit(info); +} +static void ad5816_pm_init(struct ad5816_info *info) +{ + ad5816_gpio_init(info); + ad5816_vreg_init(info); +} + +static int ad5816_reset(struct ad5816_info *info, u32 level) +{ + int err; + if (level == NVC_RESET_SOFT) { + err = ad5816_pm_wr(info, NVC_PWR_COMM); + err |= ad5816_i2c_wr8(info, CONTROL, 0x01); /* SW reset */ + } else { + err = ad5816_pm_wr(info, NVC_PWR_OFF_FORCE); + } + err |= ad5816_pm_wr(info, info->pwr_api); + return err; +} + +static int ad5816_dev_id(struct ad5816_info *info) +{ + u16 val = 0; + unsigned i; + int err; + ad5816_pm_dev_wr(info, NVC_PWR_COMM); + err = ad5816_i2c_rd16(info, IC_INFO, &val); + if (!err) { + dev_dbg(&info->i2c_client->dev, "%s found devId: %x\n", __func__, val); + info->id_minor = 0; + val = val & 0xff; + for (i = 0; i < ARRAY_SIZE(ad5816_ids); i++) { + if (val == ad5816_ids[i]) { + info->id_minor = val; + break; + } + } + if (!info->id_minor) { + err = -ENODEV; + dev_dbg(&info->i2c_client->dev, "%s No devId match\n", __func__); + } + } + ad5816_pm_dev_wr(info, NVC_PWR_OFF); + return err; +} + +static void ad5816_sts_rd(struct ad5816_info *info) +{ + /** + * Device specific code for status + * + * TODO: Ad5816 has support to get status for over/under + * voltage conditions but currently this feature is not + * required. + */ +} +/** + * Below are device specific functions. + */ + +static int ad5816_position_rd(struct ad5816_info *info, unsigned *position) +{ + + u16 pos = 0; + u8 t1 = 0; + int err = 0; + + err = ad5816_i2c_rd8(info, 0, VCM_CODE_MSB, &t1); + pos = t1 & 0x03; + err = ad5816_i2c_rd8(info, 0, VCM_CODE_LSB, &t1); + pos = (pos << 8) | t1; + if(pos) + *position = pos - info->config.pos_low; + else + *position = info->config.pos_low; + + return 0; +} + +static int ad5816_position_wr(struct ad5816_info *info, unsigned position) +{ + position = position + info->config.pos_low; + + if(position > info->config.pos_high) + position = info->config.pos_high; + + u16 data = position & 0x03ff; + + return ad5816_i2c_wr16(info, VCM_CODE_MSB, data); +} + +static int ad5816_param_rd(struct ad5816_info *info, unsigned long arg) +{ + struct nvc_param params; + const void *data_ptr; + u32 data_size = 0; + u32 position; + int err; + if (copy_from_user(¶ms, + (const void __user *)arg, + sizeof(struct nvc_param))) { + dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n", __func__, __LINE__); + return -EFAULT; + } + if (info->s_mode == NVC_SYNC_SLAVE) + info = info->s_info; + switch (params.param) { + case NVC_PARAM_LOCUS: + ad5816_pm_dev_wr(info, NVC_PWR_COMM); + err = ad5816_position_rd(info, &position); + if (err && !(info->pdata->cfg & NVC_CFG_NOERR)) + return err; + data_ptr = &position; + data_size = sizeof(position); + ad5816_pm_dev_wr(info, NVC_PWR_STDBY); + dev_dbg(&info->i2c_client->dev, "%s LOCUS: %d\n", + __func__, position); + break; + case NVC_PARAM_FOCAL_LEN: + info->nvc.focal_length = AD5816_FOCAL_LENGTH; + data_ptr = &info->nvc.focal_length; + data_size = sizeof(info->nvc.focal_length); + break; + case NVC_PARAM_MAX_APERTURE: + data_ptr = &info->nvc.max_aperature; + data_size = sizeof(info->nvc.max_aperature); + dev_dbg(&info->i2c_client->dev, "%s MAX_APERTURE: %x\n", + __func__, info->nvc.max_aperature); + break; + case NVC_PARAM_FNUMBER: + data_ptr = &info->nvc.fnumber; + data_size = sizeof(info->nvc.fnumber); + dev_dbg(&info->i2c_client->dev, "%s FNUMBER: %u\n", + __func__, info->nvc.fnumber); + break; + case NVC_PARAM_CAPS: + data_ptr = &info->cap; + /* there are different sizes depending on the version */ + /* send back just what's requested or our max size */ + if (params.sizeofvalue < sizeof(info->cap)) + data_size = params.sizeofvalue; + else + data_size = sizeof(info->cap); + dev_err(&info->i2c_client->dev, "%s CAPS\n", __func__); + break; + case NVC_PARAM_STS: + /*data_ptr = &info->sts; + data_size = sizeof(info->sts);*/ + dev_dbg(&info->i2c_client->dev, "%s \n", __func__); + break; + case NVC_PARAM_STEREO: + data_ptr = &info->s_mode; + data_size = sizeof(info->s_mode); + dev_err(&info->i2c_client->dev, "%s STEREO: %d\n", __func__, info->s_mode); + break; + default: + dev_err(&info->i2c_client->dev, "%s unsupported parameter: %d\n", + __func__, params.param); + return -EINVAL; + } + if (params.sizeofvalue < data_size) { + dev_err(&info->i2c_client->dev, + "%s data size mismatch %d != %d Param: %d\n", + __func__, params.sizeofvalue, data_size, params.param); + return -EINVAL; + } + if (copy_to_user((void __user *)params.p_value, data_ptr, data_size)) { + dev_err(&info->i2c_client->dev, "%s copy_to_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + return 0; +} + +static int ad5816_param_wr_s(struct ad5816_info *info, + struct nvc_param *params, + u32 u32val) +{ + struct nvc_focus_cap cap; + u8 u8val; + int err = 0; + u8val = (u8)u32val; + switch (params->param) { + case NVC_PARAM_LOCUS: + dev_dbg(&info->i2c_client->dev, "%s LOCUS: %u\n", __func__, u32val); + err = ad5816_position_wr(info, u32val); + return err; + case NVC_PARAM_RESET: + err = ad5816_reset(info, u32val); + dev_dbg(&info->i2c_client->dev, "%s RESET: %d\n", __func__, err); + return err; + case NVC_PARAM_SELF_TEST: + err = 0; + dev_dbg(&info->i2c_client->dev, "%s SELF_TEST: %d\n", __func__, err); + return err; + default: + dev_dbg(&info->i2c_client->dev, + "%s unsupported parameter: %d\n", + __func__, params->param); + return -EINVAL; + } +} + +static int ad5816_param_wr(struct ad5816_info *info, unsigned long arg) +{ + struct nvc_param params; + u8 u8val; + u32 u32val; + int err = 0; + if (copy_from_user(¶ms, (const void __user *)arg, + sizeof(struct nvc_param))) { + dev_err(&info->i2c_client->dev, "%s copy_from_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + if (copy_from_user(&u32val, (const void __user *)params.p_value, sizeof(u32val))) { + dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n", __func__, __LINE__); + return -EFAULT; + } + u8val = (u8)u32val; + /* parameters independent of sync mode */ + switch (params.param) { + case NVC_PARAM_STEREO: + dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n", __func__, u8val); + if (u8val == info->s_mode) + return 0; + switch (u8val) { + case NVC_SYNC_OFF: + info->s_mode = u8val; + ad5816_gpio_wr(info, AD5816_GPIO_I2CMUX, 0); + if (info->s_info != NULL) { + info->s_info->s_mode = u8val; + ad5816_pm_wr(info->s_info, NVC_PWR_OFF); + } + break; + case NVC_SYNC_MASTER: + info->s_mode = u8val; + ad5816_gpio_wr(info, AD5816_GPIO_I2CMUX, 0); + if (info->s_info != NULL) + info->s_info->s_mode = u8val; + break; + case NVC_SYNC_SLAVE: + if (info->s_info != NULL) { + /* default slave lens position */ + err = ad5816_position_wr(info->s_info, + info->s_info->cap.focus_infinity); + if (!err) { + info->s_mode = u8val; + info->s_info->s_mode = u8val; + ad5816_gpio_wr(info, + AD5816_GPIO_I2CMUX, 0); + } + else { + if (info->s_mode != NVC_SYNC_STEREO) + ad5816_pm_wr(info->s_info, + NVC_PWR_OFF); + err = -EIO; + } + } else { + err = -EINVAL; + } + break; + case NVC_SYNC_STEREO: + if (info->s_info != NULL) { + /* sync power */ + info->s_info->pwr_api = info->pwr_api; + /* move slave lens to master position */ + err = ad5816_position_wr(info->s_info, info->pos); + if (!err) { + info->s_mode = u8val; + info->s_info->s_mode = u8val; + ad5816_gpio_wr(info, AD5816_GPIO_I2CMUX, 1); + } + else { + if (info->s_mode != NVC_SYNC_SLAVE) + ad5816_pm_wr(info->s_info, NVC_PWR_OFF); + err = -EIO; + } + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + if (info->pdata->cfg & NVC_CFG_NOERR) + return 0; + return err; + default: + /* parameters dependent on sync mode */ + switch (info->s_mode) { + case NVC_SYNC_OFF: + case NVC_SYNC_MASTER: + return ad5816_param_wr_s(info, ¶ms, u32val); + case NVC_SYNC_SLAVE: + return ad5816_param_wr_s(info->s_info, ¶ms, u32val); + case NVC_SYNC_STEREO: + err = ad5816_param_wr_s(info, ¶ms, u32val); + if (!(info->pdata->cfg & NVC_CFG_SYNC_I2C_MUX)) + err |= ad5816_param_wr_s(info->s_info, + ¶ms, + u32val); + return err; + default: + dev_err(&info->i2c_client->dev, "%s %d internal err\n", + __func__, __LINE__); + return -EINVAL; + } + } +} + +static long ad5816_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct ad5816_info *info = file->private_data; + int pwr; + int err = 0; + switch (cmd) { + case NVC_IOCTL_PARAM_WR: + err = ad5816_param_wr(info, arg); + return err; + case NVC_IOCTL_PARAM_RD: + err = ad5816_param_rd(info, arg); + return err; + case NVC_IOCTL_PWR_WR: + /* This is a Guaranteed Level of Service (GLOS) call */ + pwr = (int)arg * 2; + dev_dbg(&info->i2c_client->dev, "%s PWR_WR: %d\n", + __func__, pwr); + err = ad5816_pm_api_wr(info, pwr); + return err; + case NVC_IOCTL_PWR_RD: + if (info->s_mode == NVC_SYNC_SLAVE) + pwr = info->s_info->pwr_api / 2; + else + pwr = info->pwr_api / 2; + dev_dbg(&info->i2c_client->dev, "%s PWR_RD: %d\n", + __func__, pwr); + if (copy_to_user((void __user *)arg, (const void *)&pwr, sizeof(pwr))) { + dev_err(&info->i2c_client->dev, "%s copy_to_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + return 0; + default: + dev_dbg(&info->i2c_client->dev, "%s unsupported ioctl: %x\n", __func__, cmd); + } + return -EINVAL; +} + + +static void ad5816_sdata_init(struct ad5816_info *info) +{ + /* set defaults */ + memcpy(&info->config, &ad5816_default_info, sizeof(info->config)); + memcpy(&info->nvc, &ad5816_default_nvc, sizeof(info->nvc)); + memcpy(&info->cap, &ad5816_default_cap, sizeof(info->cap)); + + info->config.settle_time = AD5816_SETTLETIME; + info->config.focal_length = AD5816_FOCAL_LENGTH; + info->config.fnumber = AD5816_FNUMBER; + info->config.pos_low = AD5816_POS_LOW_DEFAULT; + info->config.pos_high = AD5816_POS_HIGH_DEFAULT; + + /* set to proper value */ + info->cap.actuator_range = info->config.pos_high - info->config.pos_low; + + /* set overrides if any */ + if (info->pdata->nvc) { + if (info->pdata->nvc->fnumber) + info->nvc.fnumber = info->pdata->nvc->fnumber; + if (info->pdata->nvc->focal_length) + info->nvc.focal_length = info->pdata->nvc->focal_length; + if (info->pdata->nvc->max_aperature) + info->nvc.max_aperature = info->pdata->nvc->max_aperature; + } + + if (info->pdata->cap) { + if (info->pdata->cap->actuator_range) + info->cap.actuator_range = info->pdata->cap->actuator_range; + if (info->pdata->cap->settle_time) + info->cap.settle_time = info->pdata->cap->settle_time; + if (info->pdata->cap->focus_macro) + info->cap.focus_macro = info->pdata->cap->focus_macro; + if (info->pdata->cap->focus_hyper) + info->cap.focus_hyper = info->pdata->cap->focus_hyper; + if (info->pdata->cap->focus_infinity) + info->cap.focus_infinity = info->pdata->cap->focus_infinity; + } +} + +static int ad5816_sync_en(unsigned num, unsigned sync) +{ + struct ad5816_info *master = NULL; + struct ad5816_info *slave = NULL; + struct ad5816_info *pos = NULL; + rcu_read_lock(); + list_for_each_entry_rcu(pos, &ad5816_info_list, list) { + if (pos->pdata->num == num) { + master = pos; + break; + } + } + pos = NULL; + list_for_each_entry_rcu(pos, &ad5816_info_list, list) { + if (pos->pdata->num == sync) { + slave = pos; + break; + } + } + rcu_read_unlock(); + if (master != NULL) + master->s_info = NULL; + if (slave != NULL) + slave->s_info = NULL; + if (!sync) + return 0; /* no err if sync disabled */ + if (num == sync) + return -EINVAL; /* err if sync instance is itself */ + if ((master != NULL) && (slave != NULL)) { + master->s_info = slave; + slave->s_info = master; + } + return 0; +} + +static int ad5816_sync_dis(struct ad5816_info *info) +{ + if (info->s_info != NULL) { + info->s_info->s_mode = 0; + info->s_info->s_info = NULL; + info->s_mode = 0; + info->s_info = NULL; + return 0; + } + return -EINVAL; +} + +static int ad5816_open(struct inode *inode, struct file *file) +{ + struct ad5816_info *info = NULL; + struct ad5816_info *pos = NULL; + int err; + rcu_read_lock(); + list_for_each_entry_rcu(pos, &ad5816_info_list, list) { + if (pos->miscdev.minor == iminor(inode)) { + info = pos; + break; + } + } + rcu_read_unlock(); + if (!info) + return -ENODEV; + err = ad5816_sync_en(info->pdata->num, info->pdata->sync); + if (err == -EINVAL) + dev_err(&info->i2c_client->dev, "%s err: invalid num (%u) and sync (%u) instance\n", + __func__, info->pdata->num, info->pdata->sync); + if (atomic_xchg(&info->in_use, 1)) + return -EBUSY; + if (info->s_info != NULL) { + if (atomic_xchg(&info->s_info->in_use, 1)) + return -EBUSY; + } + file->private_data = info; + ad5816_pm_dev_wr(info, NVC_PWR_ON); + /* set ARC Mode to ensure faster focus */ + ad5816_set_arc_mode(info); + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + + return 0; +} + +static int ad5816_release(struct inode *inode, struct file *file) +{ + struct ad5816_info *info = file->private_data; + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + ad5816_pm_wr_s(info, NVC_PWR_OFF); + file->private_data = NULL; + WARN_ON(!atomic_xchg(&info->in_use, 0)); + if (info->s_info != NULL) + WARN_ON(!atomic_xchg(&info->s_info->in_use, 0)); + ad5816_sync_dis(info); + return 0; +} + +static const struct file_operations ad5816_fileops = { + .owner = THIS_MODULE, + .open = ad5816_open, + .unlocked_ioctl = ad5816_ioctl, + .release = ad5816_release, +}; + +static void ad5816_del(struct ad5816_info *info) +{ + ad5816_pm_exit(info); + if ((info->s_mode == NVC_SYNC_SLAVE) || + (info->s_mode == NVC_SYNC_STEREO)) + ad5816_pm_exit(info->s_info); + + ad5816_sync_dis(info); + spin_lock(&ad5816_spinlock); + list_del_rcu(&info->list); + spin_unlock(&ad5816_spinlock); + synchronize_rcu(); +} + +static int ad5816_remove(struct i2c_client *client) +{ + struct ad5816_info *info = i2c_get_clientdata(client); + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + misc_deregister(&info->miscdev); + ad5816_del(info); + return 0; +} + +static int ad5816_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + pr_info("ad5816: probing focuser.\n"); + struct ad5816_info *info; + char dname[16]; + int err; + dev_dbg(&client->dev, "%s\n", __func__); + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + dev_err(&client->dev, "%s: kzalloc error\n", __func__); + return -ENOMEM; + } + info->i2c_client = client; + if (client->dev.platform_data) { + info->pdata = client->dev.platform_data; + } else { + info->pdata = &ad5816_default_pdata; + dev_dbg(&client->dev,"%s No platform data. Using defaults.\n", __func__); + } + + i2c_set_clientdata(client, info); + INIT_LIST_HEAD(&info->list); + spin_lock(&ad5816_spinlock); + list_add_rcu(&info->list, &ad5816_info_list); + spin_unlock(&ad5816_spinlock); + ad5816_pm_init(info); + ad5816_sdata_init(info); + + err = ad5816_dev_id(info); + if (err < 0) { + dev_err(&client->dev, "%s device not found\n", __func__); + ad5816_pm_wr(info, NVC_PWR_OFF); + if (info->pdata->cfg & NVC_CFG_NODEV) { + ad5816_del(info); + return -ENODEV; + } + } else { + dev_dbg(&client->dev, "%s device found\n", __func__); + if (info->pdata->cfg & NVC_CFG_BOOT_INIT) { + /* initial move causes full initialization */ + ad5816_pm_dev_wr(info, NVC_PWR_ON); + ad5816_position_wr(info, info->cap.focus_infinity); + ad5816_pm_dev_wr(info, NVC_PWR_OFF); + } + } + + if (info->pdata->dev_name != 0) + strcpy(dname, info->pdata->dev_name); + else + strcpy(dname, "ad5816"); + + if (info->pdata->num) + snprintf(dname, sizeof(dname), "%s.%u", dname, info->pdata->num); + + info->miscdev.name = dname; + info->miscdev.fops = &ad5816_fileops; + info->miscdev.minor = MISC_DYNAMIC_MINOR; + if (misc_register(&info->miscdev)) { + dev_err(&client->dev, "%s unable to register misc device %s\n", + __func__, dname); + ad5816_del(info); + return -ENODEV; + } + + return 0; +} + + +static const struct i2c_device_id ad5816_id[] = { + { "ad5816", 0 }, + { }, +}; + + +MODULE_DEVICE_TABLE(i2c, ad5816_id); + +static struct i2c_driver ad5816_i2c_driver = { + .driver = { + .name = "ad5816", + .owner = THIS_MODULE, + }, + .id_table = ad5816_id, + .probe = ad5816_probe, + .remove = ad5816_remove, +}; + +static int __init ad5816_init(void) +{ + return i2c_add_driver(&ad5816_i2c_driver); +} + +static void __exit ad5816_exit(void) +{ + i2c_del_driver(&ad5816_i2c_driver); +} + +module_init(ad5816_init); +module_exit(ad5816_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/tps80031.c b/drivers/mfd/tps80031.c index 94ba777bcdb3..f0f2ce1f6840 100644 --- a/drivers/mfd/tps80031.c +++ b/drivers/mfd/tps80031.c @@ -105,6 +105,9 @@ #define TPS80031_CFG_INPUT_PUPD3 0xF2 #define TPS80031_CFG_INPUT_PUPD4 0xF3 +#define TPS80031_BBSPOR_CFG 0xE6 +#define TPS80031_BBSPOR_CHG_EN 0x8 + struct tps80031_pupd_data { u8 reg; u8 pullup_bit; @@ -568,6 +571,17 @@ static void tps80031_pupd_init(struct tps80031 *tps80031, } } +static void tps80031_backup_battery_charger_control(struct tps80031 *tps80031, + int enable) +{ + if (enable) + tps80031_update(tps80031->dev, SLAVE_ID1, TPS80031_BBSPOR_CFG, + TPS80031_BBSPOR_CHG_EN, TPS80031_BBSPOR_CHG_EN); + else + tps80031_update(tps80031->dev, SLAVE_ID1, TPS80031_BBSPOR_CFG, + 0, TPS80031_BBSPOR_CHG_EN); +} + static void tps80031_init_ext_control(struct tps80031 *tps80031, struct tps80031_platform_data *pdata) { int ret; @@ -1287,6 +1301,8 @@ static int __devinit tps80031_i2c_probe(struct i2c_client *client, tps80031_debuginit(tps80031); + tps80031_backup_battery_charger_control(tps80031, 1); + if (pdata->use_power_off && !pm_power_off) pm_power_off = tps80031_power_off; @@ -1302,13 +1318,17 @@ fail: #ifdef CONFIG_PM static int tps80031_i2c_suspend(struct i2c_client *client, pm_message_t state) { + struct tps80031 *tps80031 = i2c_get_clientdata(client); if (client->irq) disable_irq(client->irq); + tps80031_backup_battery_charger_control(tps80031, 0); return 0; } static int tps80031_i2c_resume(struct i2c_client *client) { + struct tps80031 *tps80031 = i2c_get_clientdata(client); + tps80031_backup_battery_charger_control(tps80031, 1); if (client->irq) enable_irq(client->irq); return 0; diff --git a/drivers/misc/tegra-baseband/bb-m7400.c b/drivers/misc/tegra-baseband/bb-m7400.c index 5808a6e321cd..99698a860917 100644 --- a/drivers/misc/tegra-baseband/bb-m7400.c +++ b/drivers/misc/tegra-baseband/bb-m7400.c @@ -28,10 +28,12 @@ #include <linux/device.h> #include <linux/usb.h> #include <linux/wakelock.h> +#include <linux/platform_data/tegra_usb.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> #include <mach/tegra-bb-power.h> #include <mach/usb_phy.h> + #include "bb-power.h" static struct tegra_bb_gpio_data m7400_gpios[] = { @@ -193,20 +195,17 @@ static int m7400_power(int code) static void m7400_ehci_customize(struct platform_device *pdev) { - struct tegra_ehci_platform_data *ehci_pdata; - struct tegra_uhsic_config *hsic_config; + struct tegra_usb_platform_data *ehci_pdata; - ehci_pdata = (struct tegra_ehci_platform_data *) + ehci_pdata = (struct usb_platform_data *) pdev->dev.platform_data; - hsic_config = (struct tegra_uhsic_config *) - ehci_pdata->phy_config; /* Register PHY callbacks */ - hsic_config->postsuspend = m7400_l2_suspend; - hsic_config->preresume = m7400_l2_resume; + ehci_pdata->ops->post_suspend = m7400_l2_suspend; + ehci_pdata->ops->pre_resume = m7400_l2_resume; /* Override required settings */ - ehci_pdata->power_down_on_bus_suspend = 0; + ehci_pdata->u_data.host.power_off_on_suspend = false; } static int m7400_attrib_write(struct device *dev, int value) diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 8fec147471a8..609fd6391d1f 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -545,11 +545,6 @@ static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci, u16 clk, ctrl; unsigned int val; - /* Switch OFF the card clock to prevent glitches on the clock line */ - clk = sdhci_readw(sdhci, SDHCI_CLOCK_CONTROL); - clk &= ~SDHCI_CLOCK_CARD_EN; - sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); - ctrl = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2); if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) { ctrl |= SDHCI_CTRL_VDD_180; @@ -559,6 +554,17 @@ static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci, if (ctrl & SDHCI_CTRL_VDD_180) ctrl &= ~SDHCI_CTRL_VDD_180; } + + /* Check if the slot can support the required voltage */ + if (min_uV > tegra_host->vddio_max_uv) + return 0; + + /* Switch OFF the card clock to prevent glitches on the clock line */ + clk = sdhci_readw(sdhci, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); + + /* Set/clear the 1.8V signalling */ sdhci_writew(sdhci, ctrl, SDHCI_HOST_CONTROL2); /* Switch the I/O rail voltage */ diff --git a/drivers/net/wireless/bcm4329/wl_iw.c b/drivers/net/wireless/bcm4329/wl_iw.c index 434e584f830c..e71ab64c2caa 100644 --- a/drivers/net/wireless/bcm4329/wl_iw.c +++ b/drivers/net/wireless/bcm4329/wl_iw.c @@ -5109,6 +5109,7 @@ wl_iw_set_pmksa( uint i; int ret = 0; char eabuf[ETHER_ADDR_STR_LEN]; + pmkid_t * pmkid_array = pmkid_list.pmkids.pmkid; WL_WSEC(("%s: SIOCSIWPMKSA\n", dev->name)); CHECK_EXTRA_FOR_NULL(extra); @@ -5139,18 +5140,18 @@ wl_iw_set_pmksa( } for (i = 0; i < pmkid_list.pmkids.npmkid; i++) - if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_list.pmkids.pmkid[i].BSSID, + if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_array[i].BSSID, ETHER_ADDR_LEN)) break; if ((pmkid_list.pmkids.npmkid > 0) && (i < pmkid_list.pmkids.npmkid)) { - bzero(&pmkid_list.pmkids.pmkid[i], sizeof(pmkid_t)); + bzero(&pmkid_array[i], sizeof(pmkid_t)); for (; i < (pmkid_list.pmkids.npmkid - 1); i++) { - bcopy(&pmkid_list.pmkids.pmkid[i+1].BSSID, - &pmkid_list.pmkids.pmkid[i].BSSID, + bcopy(&pmkid_array[i+1].BSSID, + &pmkid_array[i].BSSID, ETHER_ADDR_LEN); - bcopy(&pmkid_list.pmkids.pmkid[i+1].PMKID, - &pmkid_list.pmkids.pmkid[i].PMKID, + bcopy(&pmkid_array[i+1].PMKID, + &pmkid_array[i].PMKID, WPA2_PMKID_LEN); } pmkid_list.pmkids.npmkid--; @@ -5161,14 +5162,14 @@ wl_iw_set_pmksa( else if (iwpmksa->cmd == IW_PMKSA_ADD) { for (i = 0; i < pmkid_list.pmkids.npmkid; i++) - if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_list.pmkids.pmkid[i].BSSID, + if (!bcmp(&iwpmksa->bssid.sa_data[0], &pmkid_array[i].BSSID, ETHER_ADDR_LEN)) break; if (i < MAXPMKID) { bcopy(&iwpmksa->bssid.sa_data[0], - &pmkid_list.pmkids.pmkid[i].BSSID, + &pmkid_array[i].BSSID, ETHER_ADDR_LEN); - bcopy(&iwpmksa->pmkid[0], &pmkid_list.pmkids.pmkid[i].PMKID, + bcopy(&iwpmksa->pmkid[0], &pmkid_array[i].PMKID, WPA2_PMKID_LEN); if (i == pmkid_list.pmkids.npmkid) pmkid_list.pmkids.npmkid++; @@ -5181,10 +5182,10 @@ wl_iw_set_pmksa( uint k; k = pmkid_list.pmkids.npmkid; WL_WSEC(("wl_iw_set_pmksa,IW_PMKSA_ADD - PMKID: %s = ", - bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[k].BSSID, + bcm_ether_ntoa(&pmkid_array[k].BSSID, eabuf))); for (j = 0; j < WPA2_PMKID_LEN; j++) - WL_WSEC(("%02x ", pmkid_list.pmkids.pmkid[k].PMKID[j])); + WL_WSEC(("%02x ", pmkid_array[k].PMKID[j])); WL_WSEC(("\n")); } } @@ -5192,10 +5193,10 @@ wl_iw_set_pmksa( for (i = 0; i < pmkid_list.pmkids.npmkid; i++) { uint j; WL_WSEC(("PMKID[%d]: %s = ", i, - bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[i].BSSID, + bcm_ether_ntoa(&pmkid_array[i].BSSID, eabuf))); for (j = 0; j < WPA2_PMKID_LEN; j++) - WL_WSEC(("%02x ", pmkid_list.pmkids.pmkid[i].PMKID[j])); + WL_WSEC(("%02x ", pmkid_array[i].PMKID[j])); WL_WSEC(("\n")); } WL_WSEC(("\n")); diff --git a/drivers/spi/spi-tegra.c b/drivers/spi/spi-tegra.c index 4901114f746c..d4b9c3344eee 100644 --- a/drivers/spi/spi-tegra.c +++ b/drivers/spi/spi-tegra.c @@ -144,7 +144,6 @@ #define DATA_DIR_TX (1 << 0) #define DATA_DIR_RX (1 << 1) -#define SPI_FIFO_DEPTH 32 #define SLINK_DMA_TIMEOUT (msecs_to_jiffies(1000)) @@ -169,7 +168,7 @@ static const unsigned long spi_tegra_req_sels[] = { RX_FIFO_FULL_COUNT_ZERO << 16) #define MAX_CHIP_SELECT 4 -#define SLINK_FIFO_DEPTH 4 +#define SLINK_FIFO_DEPTH 32 struct spi_tegra_data { struct spi_master *master; @@ -247,6 +246,9 @@ struct spi_tegra_data { struct work_struct spi_transfer_work; }; +static int tegra_spi_runtime_idle(struct device *dev); +static int tegra_spi_runtime_resume(struct device *dev); + static inline unsigned long spi_tegra_readl(struct spi_tegra_data *tspi, unsigned long reg) { @@ -261,6 +263,9 @@ static inline void spi_tegra_writel(struct spi_tegra_data *tspi, if (!tspi->clk_state) BUG(); writel(val, tspi->base + reg); + + /* Synchronize write by reading back the register */ + readl(tspi->base + SLINK_MAS_DATA); } static void cancel_dma(struct tegra_dma_channel *dma_chan, @@ -731,12 +736,7 @@ static void spi_tegra_start_transfer(struct spi_device *spi, command2 = tspi->def_command2_reg; if (is_first_of_msg) { - if (!tspi->is_clkon_always) { - if (!tspi->clk_state) { - pm_runtime_get_sync(&tspi->pdev->dev); - tspi->clk_state = 1; - } - } + pm_runtime_get_sync(&tspi->pdev->dev); spi_tegra_clear_status(tspi); @@ -806,7 +806,7 @@ static void spi_tegra_start_transfer(struct spi_device *spi, spi_tegra_writel(tspi, command2, SLINK_COMMAND2); tspi->command2_reg = command2; - if (total_fifo_words > SPI_FIFO_DEPTH) + if (total_fifo_words > SLINK_FIFO_DEPTH) ret = spi_tegra_start_dma_based_transfer(tspi, t); else ret = spi_tegra_start_cpu_based_transfer(tspi, t); @@ -848,6 +848,8 @@ static int spi_tegra_setup(struct spi_device *spi) return -EINVAL; } + pm_runtime_get_sync(&tspi->pdev->dev); + spin_lock_irqsave(&tspi->lock, flags); val = tspi->def_command_reg; if (spi->mode & SPI_CS_HIGH) @@ -855,20 +857,10 @@ static int spi_tegra_setup(struct spi_device *spi) else val &= ~cs_bit; tspi->def_command_reg = val; - - if (!tspi->is_clkon_always && !tspi->clk_state) { - spin_unlock_irqrestore(&tspi->lock, flags); - pm_runtime_get_sync(&tspi->pdev->dev); - spin_lock_irqsave(&tspi->lock, flags); - tspi->clk_state = 1; - } spi_tegra_writel(tspi, tspi->def_command_reg, SLINK_COMMAND); - if (!tspi->is_clkon_always && tspi->clk_state) { - tspi->clk_state = 0; - spin_unlock_irqrestore(&tspi->lock, flags); - pm_runtime_put_sync(&tspi->pdev->dev); - } else - spin_unlock_irqrestore(&tspi->lock, flags); + spin_unlock_irqrestore(&tspi->lock, flags); + + pm_runtime_put_sync(&tspi->pdev->dev); return 0; } @@ -1010,19 +1002,11 @@ static void spi_tegra_curr_transfer_complete(struct spi_tegra_data *tspi, SLINK_COMMAND); spi_tegra_writel(tspi, tspi->def_command2_reg, SLINK_COMMAND2); - if (!tspi->is_clkon_always) { - if (tspi->clk_state) { - /* Provide delay to stablize the signal - state */ - spin_unlock_irqrestore(&tspi->lock, - *irq_flags); - udelay(10); - pm_runtime_put_sync(&tspi->pdev->dev); - spin_lock_irqsave(&tspi->lock, - *irq_flags); - tspi->clk_state = 0; - } - } + /* Provide delay to stablize the signal state */ + spin_unlock_irqrestore(&tspi->lock, *irq_flags); + udelay(10); + pm_runtime_put_sync(&tspi->pdev->dev); + spin_lock_irqsave(&tspi->lock, *irq_flags); tspi->is_transfer_in_progress = false; /* Check if any new request has come between * clock disable */ @@ -1173,7 +1157,7 @@ static irqreturn_t spi_tegra_isr_thread(int irq, void *context_data) /* Continue transfer in current message */ total_fifo_words = spi_tegra_calculate_curr_xfer_param(tspi->cur_spi, tspi, t); - if (total_fifo_words > SPI_FIFO_DEPTH) + if (total_fifo_words > SLINK_FIFO_DEPTH) err = spi_tegra_start_dma_based_transfer(tspi, t); else err = spi_tegra_start_cpu_based_transfer(tspi, t); @@ -1201,6 +1185,87 @@ static irqreturn_t spi_tegra_isr(int irq, void *context_data) return IRQ_WAKE_THREAD; } +static void spi_tegra_deinit_dma_param(struct spi_tegra_data *tspi, + bool dma_to_memory) +{ + struct tegra_dma_channel *tdc; + u32 *dma_buf; + dma_addr_t dma_phys; + + if (dma_to_memory) { + dma_buf = tspi->rx_buf; + tdc = tspi->rx_dma; + dma_phys = tspi->rx_buf_phys; + tspi->rx_dma = NULL; + tspi->rx_buf = NULL; + } else { + dma_buf = tspi->tx_buf; + tdc = tspi->tx_dma; + dma_phys = tspi->tx_buf_phys; + tspi->tx_buf = NULL; + tspi->tx_dma = NULL; + } + + dma_free_coherent(&tspi->pdev->dev, tspi->dma_buf_size, + dma_buf, dma_phys); + tegra_dma_free_channel(tdc); +} + +static int __init spi_tegra_init_dma_param(struct spi_tegra_data *tspi, + bool dma_to_memory) +{ + struct tegra_dma_req *dma_req; + struct tegra_dma_channel *tdc; + u32 *dma_buf; + dma_addr_t dma_phys; + + tdc = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT, "spi_%s_%d", + (dma_to_memory) ? "rx" : "tx", tspi->pdev->id); + if (!tdc) { + dev_err(&tspi->pdev->dev, "can not allocate rx dma channel\n"); + return -ENODEV; + } + + dma_buf = dma_alloc_coherent(&tspi->pdev->dev, tspi->dma_buf_size, + &dma_phys, GFP_KERNEL); + if (!dma_buf) { + dev_err(&tspi->pdev->dev, "can not allocate rx bounce buffer"); + tegra_dma_free_channel(tdc); + return -ENOMEM; + } + + dma_req = (dma_to_memory) ? &tspi->rx_dma_req : &tspi->tx_dma_req; + memset(dma_req, 0, sizeof(*dma_req)); + + dma_req->req_sel = spi_tegra_req_sels[tspi->pdev->id]; + dma_req->dev = tspi; + dma_req->dest_bus_width = 32; + dma_req->source_bus_width = 32; + dma_req->to_memory = (dma_to_memory) ? 1 : 0; + dma_req->virt_addr = dma_buf; + dma_req->dest_wrap = 0; + dma_req->source_wrap = 0; + + if (dma_to_memory) { + dma_req->complete = tegra_spi_rx_dma_complete; + dma_req->dest_addr = dma_phys; + dma_req->source_addr = tspi->phys + SLINK_RX_FIFO; + dma_req->source_wrap = 4; + tspi->rx_buf_phys = dma_phys; + tspi->rx_buf = dma_buf; + tspi->rx_dma = tdc; + } else { + dma_req->complete = tegra_spi_tx_dma_complete; + dma_req->dest_addr = tspi->phys + SLINK_TX_FIFO; + dma_req->source_addr = dma_phys; + dma_req->dest_wrap = 4; + tspi->tx_buf = dma_buf; + tspi->tx_buf_phys = dma_phys; + tspi->tx_dma = tdc; + } + return 0; +} + static int __init spi_tegra_probe(struct platform_device *pdev) { struct spi_master *master; @@ -1236,55 +1301,50 @@ static int __init spi_tegra_probe(struct platform_device *pdev) spin_lock_init(&tspi->lock); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { + if (!r) { + dev_err(&pdev->dev, "No IO memory resource\n"); ret = -ENODEV; - goto fail_no_mem; - } - - if (!request_mem_region(r->start, resource_size(r), - dev_name(&pdev->dev))) { - ret = -EBUSY; - goto fail_no_mem; + goto exit_free_master; } - tspi->phys = r->start; - tspi->base = ioremap(r->start, resource_size(r)); + tspi->base = devm_request_and_ioremap(&pdev->dev, r); if (!tspi->base) { - dev_err(&pdev->dev, "can't ioremap iomem\n"); - ret = -ENOMEM; - goto fail_io_map; + dev_err(&pdev->dev, + "Cannot request memregion/iomap dma address\n"); + ret = -EADDRNOTAVAIL; + goto exit_free_master; } spi_irq = platform_get_irq(pdev, 0); if (unlikely(spi_irq < 0)) { dev_err(&pdev->dev, "can't find irq resource\n"); ret = -ENXIO; - goto fail_irq_req; + goto exit_free_master; } tspi->irq = spi_irq; sprintf(tspi->port_name, "tegra_spi_%d", pdev->id); - ret = request_threaded_irq(tspi->irq, spi_tegra_isr, - spi_tegra_isr_thread, IRQF_ONESHOT, + ret = devm_request_threaded_irq(&pdev->dev, tspi->irq, + spi_tegra_isr, spi_tegra_isr_thread, IRQF_ONESHOT, tspi->port_name, tspi); if (ret < 0) { dev_err(&pdev->dev, "Failed to register ISR for IRQ %d\n", tspi->irq); - goto fail_irq_req; + goto exit_free_master; } - tspi->clk = clk_get(&pdev->dev, "spi"); + tspi->clk = devm_clk_get(&pdev->dev, "spi"); if (IS_ERR(tspi->clk)) { dev_err(&pdev->dev, "can not get clock\n"); ret = PTR_ERR(tspi->clk); - goto fail_clk_get; + goto exit_free_master; } - tspi->sclk = clk_get(&pdev->dev, "sclk"); + tspi->sclk = devm_clk_get(&pdev->dev, "sclk"); if (IS_ERR(tspi->sclk)) { dev_err(&pdev->dev, "can not get sclock\n"); ret = PTR_ERR(tspi->sclk); - goto fail_sclk_get; + goto exit_free_master; } INIT_LIST_HEAD(&tspi->queue); @@ -1327,84 +1387,41 @@ static int __init spi_tegra_probe(struct platform_device *pdev) init_completion(&tspi->tx_dma_complete); init_completion(&tspi->rx_dma_complete); - - tspi->rx_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT, - "spi_rx_%d", pdev->id); - if (!tspi->rx_dma) { - dev_err(&pdev->dev, "can not allocate rx dma channel\n"); - ret = -ENODEV; - goto fail_rx_dma_alloc; - } - - tspi->rx_buf = dma_alloc_coherent(&pdev->dev, tspi->dma_buf_size, - &tspi->rx_buf_phys, GFP_KERNEL); - if (!tspi->rx_buf) { - dev_err(&pdev->dev, "can not allocate rx bounce buffer\n"); - ret = -ENOMEM; - goto fail_rx_buf_alloc; - } - - memset(&tspi->rx_dma_req, 0, sizeof(struct tegra_dma_req)); - tspi->rx_dma_req.complete = tegra_spi_rx_dma_complete; - tspi->rx_dma_req.to_memory = 1; - tspi->rx_dma_req.dest_addr = tspi->rx_buf_phys; - tspi->rx_dma_req.virt_addr = tspi->rx_buf; - tspi->rx_dma_req.dest_bus_width = 32; - tspi->rx_dma_req.source_addr = tspi->phys + SLINK_RX_FIFO; - tspi->rx_dma_req.source_bus_width = 32; - tspi->rx_dma_req.source_wrap = 4; - tspi->rx_dma_req.dest_wrap = 0; - tspi->rx_dma_req.req_sel = spi_tegra_req_sels[pdev->id]; - tspi->rx_dma_req.dev = tspi; - - tspi->tx_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT, - "spi_tx_%d", pdev->id); - if (!tspi->tx_dma) { - dev_err(&pdev->dev, "can not allocate tx dma channel\n"); - ret = -ENODEV; - goto fail_tx_dma_alloc; + ret = spi_tegra_init_dma_param(tspi, true); + if (ret < 0) { + dev_err(&pdev->dev, "Error in rx dma init\n"); + goto exit_free_master; } - tspi->tx_buf = dma_alloc_coherent(&pdev->dev, tspi->dma_buf_size, - &tspi->tx_buf_phys, GFP_KERNEL); - if (!tspi->tx_buf) { - dev_err(&pdev->dev, "can not allocate tx bounce buffer\n"); - ret = -ENOMEM; - goto fail_tx_buf_alloc; + ret = spi_tegra_init_dma_param(tspi, false); + if (ret < 0) { + dev_err(&pdev->dev, "Error in tx dma init\n"); + goto exit_rx_dma_free; } - memset(&tspi->tx_dma_req, 0, sizeof(struct tegra_dma_req)); - tspi->tx_dma_req.complete = tegra_spi_tx_dma_complete; - tspi->tx_dma_req.to_memory = 0; - tspi->tx_dma_req.dest_addr = tspi->phys + SLINK_TX_FIFO; - tspi->tx_dma_req.virt_addr = tspi->tx_buf; - tspi->tx_dma_req.dest_bus_width = 32; - tspi->tx_dma_req.dest_wrap = 4; - tspi->tx_dma_req.source_wrap = 0; - tspi->tx_dma_req.source_addr = tspi->tx_buf_phys; - tspi->tx_dma_req.source_bus_width = 32; - tspi->tx_dma_req.req_sel = spi_tegra_req_sels[pdev->id]; - tspi->tx_dma_req.dev = tspi; tspi->max_buf_size = tspi->dma_buf_size; tspi->def_command_reg = SLINK_CS_SW | SLINK_M_S; tspi->def_command2_reg = SLINK_CS_ACTIVE_BETWEEN; skip_dma_alloc: pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - tspi->clk_state = 1; - master->dev.of_node = pdev->dev.of_node; - ret = spi_register_master(master); - if (!tspi->is_clkon_always) { - if (tspi->clk_state) { - pm_runtime_put_sync(&pdev->dev); - tspi->clk_state = 0; + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra_spi_runtime_resume(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "runtime resume failed %d", ret); + goto exit_pm_disable; } } + /* Enable clock if it is require to be enable always */ + if (tspi->is_clkon_always) + pm_runtime_get_sync(&pdev->dev); + + master->dev.of_node = pdev->dev.of_node; + ret = spi_register_master(master); if (ret < 0) { dev_err(&pdev->dev, "can not register to master err %d\n", ret); - goto fail_master_register; + goto exit_pm_suspend; } /* create the workqueue for the kbc path */ @@ -1413,42 +1430,32 @@ skip_dma_alloc: if (!tspi->spi_workqueue) { dev_err(&pdev->dev, "Failed to create work queue\n"); ret = -ENODEV; - goto fail_workqueue; + goto exit_master_unregister; } INIT_WORK(&tspi->spi_transfer_work, tegra_spi_transfer_work); return ret; -fail_workqueue: +exit_master_unregister: spi_unregister_master(master); -fail_master_register: - if (tspi->tx_buf) - dma_free_coherent(&pdev->dev, tspi->dma_buf_size, - tspi->tx_buf, tspi->tx_buf_phys); -fail_tx_buf_alloc: - if (tspi->tx_dma) - tegra_dma_free_channel(tspi->tx_dma); -fail_tx_dma_alloc: - if (tspi->rx_buf) - dma_free_coherent(&pdev->dev, tspi->dma_buf_size, - tspi->rx_buf, tspi->rx_buf_phys); -fail_rx_buf_alloc: - if (tspi->rx_dma) - tegra_dma_free_channel(tspi->rx_dma); -fail_rx_dma_alloc: + if (tspi->is_clkon_always) + pm_runtime_put_sync(&pdev->dev); + +exit_pm_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra_spi_runtime_idle(&pdev->dev); + +exit_pm_disable: pm_runtime_disable(&pdev->dev); - clk_put(tspi->sclk); -fail_sclk_get: - clk_put(tspi->clk); -fail_clk_get: - free_irq(tspi->irq, tspi); -fail_irq_req: - iounmap(tspi->base); -fail_io_map: - release_mem_region(r->start, resource_size(r)); -fail_no_mem: + + spi_tegra_deinit_dma_param(tspi, false); + +exit_rx_dma_free: + spi_tegra_deinit_dma_param(tspi, true); + +exit_free_master: spi_master_put(master); return ret; } @@ -1457,56 +1464,44 @@ static int __devexit spi_tegra_remove(struct platform_device *pdev) { struct spi_master *master; struct spi_tegra_data *tspi; - struct resource *r; master = dev_get_drvdata(&pdev->dev); tspi = spi_master_get_devdata(master); spi_unregister_master(master); - if (tspi->tx_buf) - dma_free_coherent(&pdev->dev, tspi->dma_buf_size, - tspi->tx_buf, tspi->tx_buf_phys); + if (tspi->tx_dma) - tegra_dma_free_channel(tspi->tx_dma); - if (tspi->rx_buf) - dma_free_coherent(&pdev->dev, tspi->dma_buf_size, - tspi->rx_buf, tspi->rx_buf_phys); + spi_tegra_deinit_dma_param(tspi, false); + if (tspi->rx_dma) - tegra_dma_free_channel(tspi->rx_dma); + spi_tegra_deinit_dma_param(tspi, true); - if (tspi->is_clkon_always) { + /* Disable clock if it is always enabled */ + if (tspi->is_clkon_always) pm_runtime_put_sync(&pdev->dev); - tspi->clk_state = 0; - } pm_runtime_disable(&pdev->dev); - clk_put(tspi->sclk); - clk_put(tspi->clk); - iounmap(tspi->base); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra_spi_runtime_idle(&pdev->dev); destroy_workqueue(tspi->spi_workqueue); - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - return 0; } #ifdef CONFIG_PM -static int spi_tegra_suspend(struct platform_device *pdev, pm_message_t state) +static int spi_tegra_suspend(struct device *dev) { - struct spi_master *master; - struct spi_tegra_data *tspi; - unsigned limit = 50; + struct spi_master *master = dev_get_drvdata(dev); + struct spi_tegra_data *tspi = spi_master_get_devdata(master); + unsigned limit = 50; unsigned long flags; - master = dev_get_drvdata(&pdev->dev); - tspi = spi_master_get_devdata(master); spin_lock_irqsave(&tspi->lock, flags); /* Wait for all transfer completes */ if (!list_empty(&tspi->queue)) - dev_warn(&pdev->dev, "The transfer list is not empty " + dev_warn(dev, "The transfer list is not empty " "Waiting for time %d ms to complete transfer\n", limit * 20); @@ -1520,7 +1515,7 @@ static int spi_tegra_suspend(struct platform_device *pdev, pm_message_t state) tspi->is_suspended = true; if (!list_empty(&tspi->queue)) { limit = 50; - dev_err(&pdev->dev, "All transfer has not completed, " + dev_err(dev, "All transfer has not completed, " "Waiting for %d ms current transfer to complete\n", limit * 20); while (tspi->is_transfer_in_progress && limit--) { @@ -1531,7 +1526,7 @@ static int spi_tegra_suspend(struct platform_device *pdev, pm_message_t state) } if (tspi->is_transfer_in_progress) { - dev_err(&pdev->dev, + dev_err(dev, "Spi transfer is in progress Avoiding suspend\n"); tspi->is_suspended = false; spin_unlock_irqrestore(&tspi->lock, flags); @@ -1539,33 +1534,32 @@ static int spi_tegra_suspend(struct platform_device *pdev, pm_message_t state) } spin_unlock_irqrestore(&tspi->lock, flags); - if (tspi->is_clkon_always) { - pm_runtime_put_sync(&pdev->dev); - tspi->clk_state = 0; - } + + /* Disable clock if it is always enabled */ + if (tspi->is_clkon_always) + pm_runtime_put_sync(dev); + return 0; } -static int spi_tegra_resume(struct platform_device *pdev) +static int spi_tegra_resume(struct device *dev) { - struct spi_master *master; - struct spi_tegra_data *tspi; + struct spi_master *master = dev_get_drvdata(dev); + struct spi_tegra_data *tspi = spi_master_get_devdata(master); struct spi_message *m; struct spi_device *spi; struct spi_transfer *t = NULL; int single_xfer = 0; unsigned long flags; - master = dev_get_drvdata(&pdev->dev); - tspi = spi_master_get_devdata(master); + /* Enable clock if it is always enabled */ + if (tspi->is_clkon_always) + pm_runtime_get_sync(dev); - pm_runtime_get_sync(&pdev->dev); - tspi->clk_state = 1; + pm_runtime_get_sync(dev); spi_tegra_writel(tspi, tspi->command_reg, SLINK_COMMAND); - if (!tspi->is_clkon_always) { - pm_runtime_put_sync(&pdev->dev); - tspi->clk_state = 0; - } + pm_runtime_put_sync(dev); + spin_lock_irqsave(&tspi->lock, flags); tspi->cur_speed = 0; @@ -1587,15 +1581,15 @@ static int spi_tegra_resume(struct platform_device *pdev) } #endif -#if defined(CONFIG_PM_RUNTIME) - static int tegra_spi_runtime_idle(struct device *dev) { - struct spi_master *master; - struct spi_tegra_data *tspi; - master = dev_get_drvdata(dev); - tspi = spi_master_get_devdata(master); + struct spi_master *master = dev_get_drvdata(dev); + struct spi_tegra_data *tspi = spi_master_get_devdata(master); + + /* Flush all write which are in PPSB queue by reading back */ + spi_tegra_readl(tspi, SLINK_MAS_DATA); + tspi->clk_state = 0; clk_disable(tspi->clk); clk_disable(tspi->sclk); return 0; @@ -1603,22 +1597,25 @@ static int tegra_spi_runtime_idle(struct device *dev) static int tegra_spi_runtime_resume(struct device *dev) { - struct spi_master *master; - struct spi_tegra_data *tspi; - master = dev_get_drvdata(dev); - tspi = spi_master_get_devdata(master); + struct spi_master *master = dev_get_drvdata(dev); + struct spi_tegra_data *tspi = spi_master_get_devdata(master); clk_enable(tspi->sclk); clk_enable(tspi->clk); + tspi->clk_state = 1; return 0; } static const struct dev_pm_ops tegra_spi_dev_pm_ops = { +#if defined(CONFIG_PM_RUNTIME) .runtime_idle = tegra_spi_runtime_idle, .runtime_resume = tegra_spi_runtime_resume, -}; - #endif +#ifdef CONFIG_PM + .suspend = spi_tegra_suspend, + .resume = spi_tegra_resume, +#endif +}; MODULE_ALIAS("platform:spi_tegra"); @@ -1636,16 +1633,10 @@ static struct platform_driver spi_tegra_driver = { .driver = { .name = "spi_tegra", .owner = THIS_MODULE, -#if defined(CONFIG_PM_RUNTIME) .pm = &tegra_spi_dev_pm_ops, -#endif .of_match_table = spi_tegra_of_match_table, }, .remove = __devexit_p(spi_tegra_remove), -#ifdef CONFIG_PM - .suspend = spi_tegra_suspend, - .resume = spi_tegra_resume, -#endif }; static int __init spi_tegra_init(void) diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index fedd8eef4ae2..c8c7c687010d 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -1,7 +1,6 @@ # # USB peripheral controller drivers # -GCOV_PROFILE_fsl_tegra_udc.o := y GCOV_PROFILE_tegra_udc.o := y ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index fbafe8a3bca4..4805670cf6e3 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -319,6 +319,13 @@ static int mtp_function_ctrlrequest(struct android_usb_function *f, return mtp_ctrlrequest(cdev, c); } +static int ptp_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return mtp_ctrlrequest(cdev, c); +} + static struct android_usb_function mtp_function = { .name = "mtp", .init = mtp_function_init, @@ -333,6 +340,7 @@ static struct android_usb_function ptp_function = { .init = ptp_function_init, .cleanup = ptp_function_cleanup, .bind_config = ptp_function_bind_config, + .ctrlrequest = ptp_function_ctrlrequest, }; diff --git a/drivers/usb/gadget/fsl_tegra_udc.c b/drivers/usb/gadget/fsl_tegra_udc.c deleted file mode 100644 index 31b476ad2279..000000000000 --- a/drivers/usb/gadget/fsl_tegra_udc.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Description: - * Helper functions to support the tegra USB controller - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ -#include <linux/fsl_devices.h> -#include <linux/platform_device.h> -#include <linux/err.h> -#include <linux/io.h> -#include <mach/usb_phy.h> - -static struct tegra_usb_phy *phy; -static struct clk *udc_clk; -static struct clk *emc_clk; -static struct clk *sclk_clk; -static void *udc_base; - -int fsl_udc_clk_init(struct platform_device *pdev) -{ - struct resource *res; - int err; - int instance; - struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; - - - udc_clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(udc_clk)) { - dev_err(&pdev->dev, "Can't get udc clock\n"); - return PTR_ERR(udc_clk); - } - - clk_enable(udc_clk); - - sclk_clk = clk_get(&pdev->dev, "sclk"); - if (IS_ERR(sclk_clk)) { - dev_err(&pdev->dev, "Can't get sclk clock\n"); - err = PTR_ERR(sclk_clk); - goto err_sclk; - } - - clk_set_rate(sclk_clk, 80000000); - clk_enable(sclk_clk); - - emc_clk = clk_get(&pdev->dev, "emc"); - if (IS_ERR(emc_clk)) { - dev_err(&pdev->dev, "Can't get emc clock\n"); - err = PTR_ERR(emc_clk); - goto err_emc; - } - - clk_enable(emc_clk); -#ifdef CONFIG_ARCH_TEGRA_2x_SOC - /* Set DDR busy hints to 150MHz. For Tegra 2x SOC, DDR rate is half of EMC rate */ - clk_set_rate(emc_clk, 300000000); -#else - /* Set DDR busy hints to 100MHz. For Tegra 3x SOC DDR rate equals to EMC rate */ - clk_set_rate(emc_clk, 100000000); -#endif - - /* we have to remap the registers ourselves as fsl_udc does not - * export them for us. - */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - err = -ENXIO; - goto err0; - } - udc_base = ioremap(res->start, resource_size(res)); - if (!udc_base) { - err = -ENOMEM; - goto err0; - } - - instance = pdev->id; - if (instance == -1) - instance = 0; - - phy = tegra_usb_phy_open(instance, udc_base, pdata->phy_config, - TEGRA_USB_PHY_MODE_DEVICE, pdata->usb_phy_type); - if (IS_ERR(phy)) { - dev_err(&pdev->dev, "Can't open phy\n"); - err = PTR_ERR(phy); - goto err1; - } - tegra_usb_phy_power_on(phy, true); - - return 0; -err1: - iounmap(udc_base); -err0: - clk_disable(emc_clk); - clk_put(emc_clk); -err_emc: - clk_disable(sclk_clk); - clk_put(sclk_clk); -err_sclk: - clk_disable(udc_clk); - clk_put(udc_clk); - return err; -} - -void fsl_udc_clk_finalize(struct platform_device *pdev) -{ -} - -void fsl_udc_clk_release(void) -{ - tegra_usb_phy_close(phy); - - iounmap(udc_base); - - clk_disable(udc_clk); - clk_put(udc_clk); - - clk_disable(sclk_clk); - clk_put(sclk_clk); - - clk_disable(emc_clk); - clk_put(emc_clk); -} - -void fsl_udc_clk_suspend(bool is_dpd) -{ - tegra_usb_phy_power_off(phy, is_dpd); - clk_disable(udc_clk); - clk_disable(sclk_clk); - clk_disable(emc_clk); -} - -void fsl_udc_clk_resume(bool is_dpd) -{ - clk_enable(emc_clk); - clk_enable(sclk_clk); - clk_enable(udc_clk); - tegra_usb_phy_power_on(phy, is_dpd); -} - -void fsl_udc_clk_enable(void) -{ - clk_enable(udc_clk); -} - -void fsl_udc_clk_disable(void) -{ - clk_disable(udc_clk); -} - -bool fsl_udc_charger_detect(void) -{ - return tegra_usb_phy_charger_detect(phy); -} - -void fsl_udc_dtd_prepare(void) -{ - /* When we are programming two DTDs very close to each other, - * the second DTD is being prefetched before it is actually written - * to DDR. To prevent this, we disable prefetcher before programming - * any new DTD and re-enable it before priming endpoint. - */ - tegra_usb_phy_memory_prefetch_off(phy); -} - -void fsl_udc_ep_barrier(void) -{ - tegra_usb_phy_memory_prefetch_on(phy); -} diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index 978db7ece1de..f978f0f2d1e7 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -37,7 +37,6 @@ #include <linux/workqueue.h> #include <linux/err.h> #include <linux/io.h> -#include <linux/fsl_devices.h> /* check this */ #include <asm/byteorder.h> #include <asm/io.h> @@ -50,14 +49,10 @@ #include "tegra_udc.h" -/* #define IS_NEW_PHY_DRIVER 1 */ - -#ifndef IS_NEW_PHY_DRIVER -#include "fsl_tegra_udc.c" -#endif #define DRIVER_DESC "Nvidia Tegra High-Speed USB SOC \ - Device Controller driver" + Device Controller driver" + #define DRIVER_AUTHOR "Venkat Moganty/Rakesh Bodla" #define DRIVER_VERSION "Apr 30, 2012" #define USB1_PREFETCH_ID 6 @@ -73,7 +68,6 @@ & USB_DIR_IN) == USB_DIR_IN) - static const char driver_name[] = "tegra-udc"; static const char driver_desc[] = DRIVER_DESC; @@ -136,7 +130,7 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status) unsigned char stopped = ep->stopped; struct ep_td_struct *curr_td, *next_td; int j; - + BUG_ON(!(in_irq() || irqs_disabled())); udc = (struct tegra_udc *)ep->udc; /* Removed the req from tegra_ep->queue */ list_del_init(&req->queue); @@ -186,19 +180,20 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status) } #endif - spin_unlock(&ep->udc->lock); /* complete() is from gadget layer, * eg fsg->bulk_in_complete() */ - if (req->req.complete) + if (req->req.complete) { + spin_unlock(&ep->udc->lock); req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->udc->lock); + } - spin_lock(&ep->udc->lock); ep->stopped = stopped; } /* * nuke(): delete all requests related to this ep - * called with spinlock held + * Must be called with spinlock held and interrupt disabled */ static void nuke(struct tegra_ep *ep, int status) { @@ -733,11 +728,7 @@ static void tegra_queue_td(struct tegra_ep *ep, struct tegra_req *req) | EP_QUEUE_HEAD_STATUS_HALT)); dQH->size_ioc_int_sts &= temp; -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_memory_prefetch_on(udc->phy); -#else - fsl_udc_ep_barrier(); -#endif /* Ensure that updates to the QH will occur before priming. */ wmb(); @@ -828,11 +819,7 @@ static int tegra_req_to_dtd(struct tegra_req *req, gfp_t gfp_flags) struct ep_td_struct *last_dtd = NULL, *dtd; dma_addr_t dma; -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_memory_prefetch_off(the_udc->phy); -#else - fsl_udc_dtd_prepare(); -#endif do { dtd = tegra_build_dtd(req, &count, &dma, &is_last, gfp_flags); @@ -1229,20 +1216,20 @@ static int tegra_set_selfpowered(struct usb_gadget *gadget, int is_on) } /** - * Notify controller that VBUS is powered, Called by whatever + * Notify controller that VBUS is powered, called by whatever * detects VBUS sessions */ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) { struct tegra_udc *udc = container_of(gadget, struct tegra_udc, gadget); - + unsigned long flags; DBG("%s(%d) turn VBUS state from %s to %s", __func__, __LINE__, udc->vbus_active ? "on" : "off", is_active ? "on" : "off"); if (udc->vbus_active && !is_active) { /* If cable disconnected, cancel any delayed work */ cancel_delayed_work(&udc->work); - spin_lock(&udc->lock); + spin_lock_irqsave(&udc->lock, flags); /* reset all internal Queues and inform client driver */ reset_queues(udc); /* stop the controller and turn off the clocks */ @@ -1250,23 +1237,15 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) dr_controller_reset(udc); udc->vbus_active = 0; udc->usb_state = USB_STATE_DEFAULT; - spin_unlock(&udc->lock); -#ifdef IS_NEW_PHY_DRIVER + spin_unlock_irqrestore(&udc->lock,flags); tegra_usb_phy_power_off(udc->phy); -#else - fsl_udc_clk_suspend(false); -#endif if (udc->vbus_reg) { /* set the current limit to 0mA */ regulator_set_current_limit( udc->vbus_reg, 0, 0); } } else if (!udc->vbus_active && is_active) { -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_power_on(udc->phy); -#else - fsl_udc_clk_resume(false); -#endif /* setup the controller in the device mode */ dr_controller_setup(udc); /* setup EP0 for setup packet */ @@ -1348,12 +1327,7 @@ static void tegra_udc_release(struct device *dev) struct tegra_udc *udc = platform_get_drvdata(pdev); complete(udc->done); -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_close(udc->phy); -#else - fsl_udc_clk_suspend(false); -#endif - kfree(udc); } @@ -2109,15 +2083,18 @@ static void tegra_udc_irq_work(struct work_struct *irq_work) { struct tegra_udc *udc = container_of(irq_work, struct tegra_udc, irq_work); + DBG("%s(%d) BEGIN\n", __func__, __LINE__); /* Check whether cable is connected*/ if (vbus_enabled(udc)) tegra_vbus_session(&udc->gadget, 1); else tegra_vbus_session(&udc->gadget, 0); + + DBG("%s(%d) END\n", __func__, __LINE__); } -/* +/** * If VBUS is detected and setup packet is not received in 100ms then * work thread starts and checks for the USB charger detection. */ @@ -2127,11 +2104,7 @@ static void tegra_udc_charger_detect_work(struct work_struct *work) DBG("%s(%d) BEGIN\n", __func__, __LINE__); /* check for the platform charger detection */ -#ifdef IS_NEW_PHY_DRIVER if (tegra_usb_phy_charger_detected(udc->phy)) { -#else - if (fsl_udc_charger_detect()) { -#endif printk(KERN_INFO "USB compliant charger detected\n"); /* check udc regulator is available for drawing vbus current*/ if (udc->vbus_reg) { @@ -2271,8 +2244,6 @@ static int tegra_udc_start(struct usb_gadget_driver *driver, if (!udc) return -ENODEV; - - if (!driver || (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) || !bind || !driver->disconnect @@ -2404,7 +2375,7 @@ static int tegra_udc_setup_qh(struct tegra_udc *udc) /* FIXME: tegra_alloc_request() ignores ep argument */ udc->status_req = container_of(tegra_alloc_request(NULL, GFP_KERNEL), struct tegra_req, req); - /* allocate a small amount of memory to get valid address */ + /* Allocate a small amount of memory to get valid address */ udc->status_req->req.buf = dma_alloc_coherent(&udc->pdev->dev, STATUS_BUFFER_SIZE, &udc->status_req->req.dma, GFP_KERNEL); @@ -2552,7 +2523,6 @@ static int __init tegra_udc_probe(struct platform_device *pdev) goto err_iounmap; } -#ifdef IS_NEW_PHY_DRIVER udc->phy = tegra_usb_phy_open(pdev); if (IS_ERR(udc->phy)) { dev_err(&pdev->dev, "failed to open USB phy\n"); @@ -2571,23 +2541,10 @@ static int __init tegra_udc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to init the phy\n"); goto err_phy; } -#else - /* Initialize USB clocks */ - fsl_udc_clk_init(pdev); -#endif spin_lock_init(&udc->lock); udc->stopped = 1; udc->pdev = pdev; -#ifdef IS_NEW_PHY_DRIVER udc->has_hostpc = tegra_usb_phy_has_hostpc(udc->phy) ? 1 : 0; -#else - #ifdef CONFIG_ARCH_TEGRA_2x_SOC - udc->has_hostpc = 0; - #else - udc->has_hostpc = 1; - #endif -#endif - platform_set_drvdata(pdev, udc); /* Initialize the udc structure including QH members */ @@ -2597,7 +2554,7 @@ static int __init tegra_udc_probe(struct platform_device *pdev) goto err_phy; } - /* initialize usb hw reg except for regs for EP, + /* Initialize usb hw reg except for regs for EP, * leave usbintr reg untouched */ err = dr_controller_setup(udc); if (err) { @@ -2617,7 +2574,7 @@ static int __init tegra_udc_probe(struct platform_device *pdev) goto err_unregister; } - /* use dma_pool for TD management */ + /* Use dma_pool for TD management */ udc->td_pool = dma_pool_create("udc_td", &pdev->dev, sizeof(struct ep_td_struct), DTD_ALIGNMENT, UDC_DMA_BOUNDARY); @@ -2638,9 +2595,9 @@ static int __init tegra_udc_probe(struct platform_device *pdev) PM_QOS_DEFAULT_VALUE); #endif - /* create a work for controlling clocks to the phy if otg is disabled */ + /* Create work for controlling clocks to the phy if otg is disabled */ INIT_WORK(&udc->irq_work, tegra_udc_irq_work); - /* create a delayed work for detecting the USB charger */ + /* Create a delayed work for detecting the USB charger */ INIT_DELAYED_WORK(&udc->work, tegra_udc_charger_detect_work); INIT_WORK(&udc->charger_work, tegra_udc_set_current_limit_work); @@ -2654,35 +2611,21 @@ static int __init tegra_udc_probe(struct platform_device *pdev) } #ifdef CONFIG_USB_OTG_UTILS - -#ifdef IS_NEW_PHY_DRIVER if (tegra_usb_phy_otg_supported(udc->phy)) udc->transceiver = otg_get_transceiver(); -#else - udc->transceiver = otg_get_transceiver(); -#endif if (udc->transceiver) { dr_controller_stop(udc); dr_controller_reset(udc); -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_power_off(udc->phy); -#else - fsl_udc_clk_suspend(false); -#endif udc->vbus_active = 0; udc->usb_state = USB_STATE_DEFAULT; otg_set_peripheral(udc->transceiver, &udc->gadget); } #else /* Power down the phy if cable is not connected */ - if (!vbus_enabled()) { -#ifdef IS_NEW_PHY_DRIVER + if (!vbus_enabled()) tegra_usb_phy_power_off(udc->phy); -#else - fsl_udc_clk_suspend(false); -#endif - } #endif DBG("%s(%d) END\n", __func__, __LINE__); @@ -2694,15 +2637,11 @@ err_del_udc: err_unregister: device_unregister(&udc->gadget.dev); -#ifdef IS_NEW_PHY_DRIVER err_phy: tegra_usb_phy_close(udc->phy); err_irq: free_irq(udc->irq, udc); -#else -err_phy: -#endif err_iounmap: iounmap(udc->regs); @@ -2743,9 +2682,6 @@ static int __exit tegra_udc_remove(struct platform_device *pdev) if (udc->transceiver) otg_set_peripheral(udc->transceiver, NULL); -#ifndef IS_NEW_PHY_DRIVER - fsl_udc_clk_release(); -#endif /* Free allocated memory */ dma_free_coherent(&pdev->dev, STATUS_BUFFER_SIZE, @@ -2760,7 +2696,7 @@ static int __exit tegra_udc_remove(struct platform_device *pdev) release_mem_region(res->start, res->end - res->start + 1); device_unregister(&udc->gadget.dev); - /* free udc --wait for the release() finished */ + /* Free udc -- wait for the release() finished */ wait_for_completion(&done); return 0; @@ -2769,30 +2705,27 @@ static int __exit tegra_udc_remove(struct platform_device *pdev) static int tegra_udc_suspend(struct platform_device *pdev, pm_message_t state) { struct tegra_udc *udc = platform_get_drvdata(pdev); + unsigned long flags; DBG("%s(%d) BEGIN\n", __func__, __LINE__); - /* if it controller is in otg mode, return */ + /* If the controller is in otg mode, return */ if (udc->transceiver) return 0; if (udc->vbus_active) { - spin_lock(&udc->lock); + spin_lock_irqsave(&udc->lock, flags); /* Reset all internal Queues and inform client driver */ reset_queues(udc); udc->vbus_active = 0; udc->usb_state = USB_STATE_DEFAULT; - spin_unlock(&udc->lock); + spin_unlock_irqrestore(&udc->lock, flags); } - /* stop the controller and turn off the clocks */ + /* Stop the controller and turn off the clocks */ dr_controller_stop(udc); if (udc->transceiver) udc->transceiver->state = OTG_STATE_UNDEFINED; -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_power_off(udc->phy); -#else - fsl_udc_clk_suspend(true); -#endif DBG("%s(%d) END\n", __func__, __LINE__); return 0; @@ -2803,36 +2736,18 @@ static int tegra_udc_resume(struct platform_device *pdev) struct tegra_udc *udc = platform_get_drvdata(pdev); DBG("%s(%d) BEGIN\n", __func__, __LINE__); -#ifndef IS_NEW_PHY_DRIVER -#ifdef CONFIG_ARCH_TEGRA_2x_SOC - /* Work around to get UTMIP_OTGPD, UTMIP_BIASPD values correctly */ - fsl_udc_clk_resume(true); - fsl_udc_clk_suspend(true); -#endif -#endif - if (udc->transceiver) return 0; -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_power_on(udc->phy); -#else - fsl_udc_clk_resume(true); - fsl_udc_clk_resume(true); -#endif tegra_udc_restart(udc); /* Power down the phy if cable is not connected */ if (!vbus_enabled(udc)) { udc->vbus_active = 0; -#ifdef IS_NEW_PHY_DRIVER tegra_usb_phy_power_off(udc->phy); -#else - fsl_udc_clk_suspend(true); -#endif } - DBG("%s(%d) END\n", __func__, __LINE__); return 0; } diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 25ed607aab9a..bf171c180987 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -145,7 +145,7 @@ static __maybe_unused void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, spin_lock_irqsave(&ehci->lock, flags); /* clear phy low-power mode before changing wakeup flags */ - if (ehci->has_hostpc) { + if (ehci->has_hostpc && !ehci->broken_hostpc_phcd) { port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *hostpc_reg; @@ -181,7 +181,7 @@ static __maybe_unused void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, } /* enter phy low-power mode again */ - if (ehci->has_hostpc) { + if (ehci->has_hostpc && !ehci->broken_hostpc_phcd) { port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *hostpc_reg; @@ -285,7 +285,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) } } #ifdef CONFIG_ARCH_TEGRA_2x_SOC - if (changed && ehci->has_hostpc) { + if (changed && ehci->has_hostpc && !ehci->broken_hostpc_phcd) { spin_unlock_irq(&ehci->lock); msleep(5); /* 5 ms for HCD to enter low-power mode */ spin_lock_irq(&ehci->lock); @@ -389,7 +389,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) spin_lock_irq(&ehci->lock); /* clear phy low-power mode before resume */ - if (ehci->bus_suspended && ehci->has_hostpc) { + if (ehci->bus_suspended && ehci->has_hostpc && !ehci->broken_hostpc_phcd) { i = HCS_N_PORTS(ehci->hcs_params); while (i--) { if (test_bit(i, &ehci->bus_suspended)) { @@ -731,7 +731,7 @@ static int ehci_hub_control ( goto error; /* clear phy low-power mode before resume */ - if (hostpc_reg) { + if (hostpc_reg && !ehci->broken_hostpc_phcd) { temp1 = ehci_readl(ehci, hostpc_reg); ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, hostpc_reg); @@ -979,7 +979,7 @@ static int ehci_hub_control ( temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); - if (hostpc_reg) { + if (hostpc_reg && !ehci->broken_hostpc_phcd) { spin_unlock_irqrestore(&ehci->lock, flags); msleep(5);/* 5ms for HCD enter low pwr mode */ spin_lock_irqsave(&ehci->lock, flags); diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 4e7277b2b889..c5fa8160bffe 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -2,7 +2,7 @@ * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs * * Copyright (C) 2010 Google, Inc. - * Copyright (C) 2009 - 2012 NVIDIA Corporation + * Copyright (C) 2009 - 2011 NVIDIA Corporation * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -16,7 +16,6 @@ * */ -#include <linux/clk.h> #include <linux/platform_device.h> #include <linux/platform_data/tegra_usb.h> #include <linux/irq.h> @@ -24,191 +23,157 @@ #include <mach/usb_phy.h> #include <mach/iomap.h> -#define TEGRA_USB_PORTSC_PHCD (1 << 23) - -#define TEGRA_USB_SUSP_CTRL_OFFSET 0x400 -#define TEGRA_USB_SUSP_CLR (1 << 5) -#define TEGRA_USB_PHY_CLK_VALID (1 << 7) -#define TEGRA_USB_SRT (1 << 25) -#define TEGRA_USB_PHY_CLK_VALID_INT_ENB (1 << 9) -#define TEGRA_USB_PHY_CLK_VALID_INT_STS (1 << 8) - -#ifdef CONFIG_ARCH_TEGRA_2x_SOC -#define TEGRA_USB_PORTSC1_OFFSET 0x184 +#if 0 +#define EHCI_DBG(stuff...) pr_info("ehci-tegra: " stuff) #else -#define TEGRA_USB_PORTSC1_OFFSET 0x174 +#define EHCI_DBG(stuff...) do {} while (0) #endif -#define TEGRA_USB_PORTSC1_WKCN (1 << 20) -#define TEGRA_LVL2_CLK_GATE_OVRB 0xfc -#define TEGRA_USB2_CLK_OVR_ON (1 << 10) +static const char driver_name[] = "tegra-ehci"; #define TEGRA_USB_DMA_ALIGN 32 -#define STS_SRI (1<<7) /* SOF Recieved */ - -#define HOSTPC_REG_OFFSET 0x1b4 - -#define HOSTPC1_DEVLC_STS (1 << 28) -#define HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29) - -#define USB1_PREFETCH_ID 6 -#define USB2_PREFETCH_ID 18 -#define USB3_PREFETCH_ID 17 - struct tegra_ehci_hcd { struct ehci_hcd *ehci; struct tegra_usb_phy *phy; - struct clk *clk; - struct clk *emc_clk; - struct clk *sclk_clk; +#ifdef CONFIG_USB_OTG_UTILS struct otg_transceiver *transceiver; - int host_resumed; - int bus_suspended; - int port_resuming; - int power_down_on_bus_suspend; - int default_enable; - enum tegra_usb_phy_port_speed port_speed; - struct work_struct clk_timer_work; - struct timer_list clk_timer; - bool clock_enabled; - bool timer_event; - struct mutex tegra_ehci_hcd_mutex; +#endif + struct mutex sync_lock; + bool port_resuming; unsigned int irq; bool bus_suspended_fail; }; -static void tegra_ehci_power_up(struct usb_hcd *hcd, bool is_dpd) +struct dma_align_buffer { + void *kmalloc_ptr; + void *old_xfer_buffer; + u8 data[0]; +}; + +static void free_align_buffer(struct urb *urb) { - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + struct dma_align_buffer *temp = container_of(urb->transfer_buffer, + struct dma_align_buffer, data); - if (!tegra->default_enable) - clk_enable(tegra->clk); - tegra_usb_phy_power_on(tegra->phy, is_dpd); - tegra->host_resumed = 1; + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + /* In transaction, DMA from Device */ + if (usb_urb_dir_in(urb)) + memcpy(temp->old_xfer_buffer, temp->data, + urb->transfer_buffer_length); + + urb->transfer_buffer = temp->old_xfer_buffer; + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; + kfree(temp->kmalloc_ptr); } -static void tegra_ehci_power_down(struct usb_hcd *hcd, bool is_dpd) +static int alloc_align_buffer(struct urb *urb, gfp_t mem_flags) { - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + struct dma_align_buffer *temp, *kmalloc_ptr; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) + return 0; - tegra->host_resumed = 0; - tegra_usb_phy_power_off(tegra->phy, is_dpd); - if (!tegra->default_enable) - clk_disable(tegra->clk); + /* Allocate a buffer with enough padding for alignment */ + kmalloc_size = urb->transfer_buffer_length + + sizeof(struct dma_align_buffer) + TEGRA_USB_DMA_ALIGN - 1; + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + + if (!kmalloc_ptr) + return -ENOMEM; + + /* Position our struct dma_align_buffer such that data is aligned */ + temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1; + temp->kmalloc_ptr = kmalloc_ptr; + temp->old_xfer_buffer = urb->transfer_buffer; + /* OUT transaction, DMA to Device */ + if (!usb_urb_dir_in(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + + urb->transfer_buffer = temp->data; + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; } -static int tegra_ehci_internal_port_reset( - struct ehci_hcd *ehci, - u32 __iomem *portsc_reg -) +static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) { - u32 temp; - unsigned long flags; - int retval = 0; - int i, tries; - u32 saved_usbintr; - - spin_lock_irqsave(&ehci->lock, flags); - saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); - /* disable USB interrupt */ - ehci_writel(ehci, 0, &ehci->regs->intr_enable); - spin_unlock_irqrestore(&ehci->lock, flags); - - /* - * Here we have to do Port Reset at most twice for - * Port Enable bit to be set. - */ - for (i = 0; i < 2; i++) { - temp = ehci_readl(ehci, portsc_reg); - temp |= PORT_RESET; - ehci_writel(ehci, temp, portsc_reg); - mdelay(10); - temp &= ~PORT_RESET; - ehci_writel(ehci, temp, portsc_reg); - mdelay(1); - tries = 100; - do { - mdelay(1); - /* - * Up to this point, Port Enable bit is - * expected to be set after 2 ms waiting. - * USB1 usually takes extra 45 ms, for safety, - * we take 100 ms as timeout. - */ - temp = ehci_readl(ehci, portsc_reg); - } while (!(temp & PORT_PE) && tries--); - if (temp & PORT_PE) - break; + int ret; + + ret = alloc_align_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + + /* Control packets over dma */ + if (urb->setup_dma) + dma_sync_single_for_device(hcd->self.controller, + urb->setup_dma, sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + + /* urb buffers over dma */ + if (urb->transfer_dma) { + enum dma_data_direction dir; + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + dma_sync_single_for_device(hcd->self.controller, + urb->transfer_dma, urb->transfer_buffer_length, dir); } - if (i == 2) - retval = -ETIMEDOUT; - - /* - * Clear Connect Status Change bit if it's set. - * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. - */ - if (temp & PORT_CSC) - ehci_writel(ehci, PORT_CSC, portsc_reg); - - /* - * Write to clear any interrupt status bits that might be set - * during port reset. - */ - temp = ehci_readl(ehci, &ehci->regs->status); - ehci_writel(ehci, temp, &ehci->regs->status); - - /* restore original interrupt enable bits */ - ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); - return retval; + + if (ret) + free_align_buffer(urb); + + return ret; } -static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd) +static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, + struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + free_align_buffer(urb); + + if (urb->transfer_dma) { + enum dma_data_direction dir; + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + if (dir == DMA_FROM_DEVICE) + dma_sync_single_for_cpu(hcd->self.controller, + urb->transfer_dma, urb->transfer_buffer_length, + DMA_FROM_DEVICE); + } +} + +static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) { - struct ehci_hcd *ehci = hcd_to_ehci (hcd); - struct ehci_regs __iomem *hw = ehci->regs; struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - u32 val; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); irqreturn_t irq_status; bool pmc_remote_wakeup = false; - /* Fence read for coherency of AHB master intiated writes */ - if (tegra->phy->instance == 0) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB1_PREFETCH_ID)); - else if (tegra->phy->instance == 1) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB2_PREFETCH_ID)); - else if (tegra->phy->instance == 2) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB3_PREFETCH_ID)); - - if ((tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) && - (tegra->ehci->has_hostpc)) { - /* check if there is any remote wake event */ - if (tegra_usb_phy_is_remotewake_detected(tegra->phy)) { - pmc_remote_wakeup = true; - spin_lock (&ehci->lock); - usb_hcd_resume_root_hub(hcd); - spin_unlock (&ehci->lock); - } - } - if (tegra->phy->hotplug) { - spin_lock(&ehci->lock); - val = readl(hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET); - if ((val & TEGRA_USB_PHY_CLK_VALID_INT_STS)) { - val &= ~TEGRA_USB_PHY_CLK_VALID_INT_ENB | - TEGRA_USB_PHY_CLK_VALID_INT_STS; - writel(val , (hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET)); - - val = readl(&hw->status); - if (!(val & STS_PCD)) { - spin_unlock(&ehci->lock); - return 0; - } - val = readl(hcd->regs + TEGRA_USB_PORTSC1_OFFSET); - val &= ~(TEGRA_USB_PORTSC1_WKCN | PORT_RWC_BITS); - writel(val , (hcd->regs + TEGRA_USB_PORTSC1_OFFSET)); - } + spin_lock(&ehci->lock); + irq_status = tegra_usb_phy_irq(tegra->phy); + if (irq_status == IRQ_NONE) { spin_unlock(&ehci->lock); + return irq_status; + } + if (tegra_usb_phy_remote_wakeup(tegra->phy)) { + ehci_info(ehci, "remote wakeup detected\n"); + pmc_remote_wakeup = true; + usb_hcd_resume_root_hub(hcd); } + spin_unlock(&ehci->lock); + + EHCI_DBG("%s() cmd = 0x%x, int_sts = 0x%x, portsc = 0x%x\n", __func__, + ehci_readl(ehci, &ehci->regs->command), + ehci_readl(ehci, &ehci->regs->status), + ehci_readl(ehci, &ehci->regs->port_status[0])); irq_status = ehci_irq(hcd); @@ -218,61 +183,37 @@ static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd) if (ehci->controller_remote_wakeup) { ehci->controller_remote_wakeup = false; - /* disable interrupts */ - ehci_writel(ehci, 0, &ehci->regs->intr_enable); - tegra_usb_phy_preresume(tegra->phy, true); + tegra_usb_phy_pre_resume(tegra->phy, true); tegra->port_resuming = 1; } return irq_status; } + static int tegra_ehci_hub_control( struct usb_hcd *hcd, - u16 typeReq, - u16 wValue, - u16 wIndex, - char *buf, - u16 wLength + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength ) { - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - int ports = HCS_N_PORTS(ehci->hcs_params); - u32 temp, status, cmd_run; - u32 __iomem *status_reg; - u32 usbsts_reg; - + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); unsigned long flags; - int retval = 0; - unsigned selector; - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - bool hsic = false; + int retval = 0; + u32 __iomem *status_reg; - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - if (!tegra->host_resumed) { + if (!tegra_usb_phy_hw_accessible(tegra->phy)) { if (buf) - memset (buf, 0, wLength); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); + memset(buf, 0, wLength); return retval; } - hsic = (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_HSIC); - - status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; - - spin_lock_irqsave(&ehci->lock, flags); - - /* - * In ehci_hub_control() for USB_PORT_FEAT_ENABLE clears the other bits - * that are write on clear, by writing back the register read value, so - * USB_PORT_FEAT_ENABLE is handled by masking the set on clear bits - */ - if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_ENABLE) { - temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS; - ehci_writel(ehci, temp & ~PORT_PE, status_reg); - goto done; - } else if (typeReq == GetPortStatus) { - temp = ehci_readl(ehci, status_reg); - /* check port is in resume state */ + /* Do tegra phy specific actions based on the type request */ + switch (typeReq) { + case GetPortStatus: if (tegra->port_resuming) { int delay = ehci->reset_done[wIndex-1] - jiffies; /* Sometimes it seems we get called too soon... In that case, wait.*/ @@ -280,501 +221,68 @@ static int tegra_ehci_hub_control( ehci_dbg(ehci, "GetPortStatus called too soon, waiting %dms...\n", delay); mdelay(jiffies_to_msecs(delay)); } + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; /* Ensure the port PORT_SUSPEND and PORT_RESUME has cleared */ if (handshake(ehci, status_reg, (PORT_SUSPEND | PORT_RESUME), 0, 25000)) { pr_err("%s: timeout waiting for SUSPEND to clear\n", __func__); } + tegra_usb_phy_post_resume(tegra->phy); tegra->port_resuming = 0; - tegra_usb_phy_postresume(tegra->phy, false); - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) { + /* If run bit is not set by now enable it */ + if (ehci->command & CMD_RUN) { ehci->command |= CMD_RUN; - cmd_run = ehci_readl(ehci, &ehci->regs->command); - cmd_run |= CMD_RUN; - /* - * ehci run bit is disabled to avoid SOF. - * 2LS WAR is executed by now enable the run bit. - */ - ehci_writel(ehci, cmd_run, &ehci->regs->command); - /* Now we can safely re-enable irqs */ - ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + ehci_writel(ehci, ehci->command, &ehci->regs->command); } + /* Now we can safely re-enable irqs */ + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); } - - } else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { - temp = ehci_readl(ehci, status_reg); - if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { - retval = -EPIPE; - goto done; - } - - temp &= ~PORT_WKCONN_E; - temp |= PORT_WKDISC_E | PORT_WKOC_E; - ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); - - /* Need a 4ms delay before the controller goes to suspend */ - mdelay(4); - - /* - * If a transaction is in progress, there may be a delay in - * suspending the port. Poll until the port is suspended. - */ - if (handshake(ehci, status_reg, PORT_SUSPEND, - PORT_SUSPEND, 5000)) - pr_err("%s: timeout waiting for SUSPEND\n", __func__); - - set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports); - /* - * If RUN bit is disabled interrupt is not generated after suspend. - * This change on T20 will allow ASE interrupt generated after suspend - * which will unlink the qheads. - */ -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) { - /* Disable RUN bit. */ - ehci->command &= ~CMD_RUN; - cmd_run = ehci_readl(ehci, &ehci->regs->command); - cmd_run &= ~CMD_RUN; - ehci_writel(ehci, cmd_run, &ehci->regs->command); - if (handshake (ehci, &ehci->regs->status, - STS_HALT, STS_HALT, 16 * 125)) - pr_err("%s() timeout waiting for STS_HALT\n", __func__); + break; + case ClearPortFeature: + if (wValue == USB_PORT_FEAT_SUSPEND) { + tegra_usb_phy_pre_resume(tegra->phy, false); + tegra->port_resuming = 1; } -#endif - tegra_usb_phy_postsuspend(tegra->phy, false); - - goto done; - } - - /* For USB1 port we need to issue Port Reset twice internally */ - if (tegra->phy->instance == 0 && - (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) { - spin_unlock_irqrestore(&ehci->lock, flags); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return tegra_ehci_internal_port_reset(ehci, status_reg); + break; } - /* - * Tegra host controller will time the resume operation to clear the bit - * when the port control state switches to HS or FS Idle. This behavior - * is different from EHCI where the host controller driver is required - * to set this bit to a zero after the resume duration is timed in the - * driver. - */ - else if (typeReq == ClearPortFeature && - wValue == USB_PORT_FEAT_SUSPEND) { - temp = ehci_readl(ehci, status_reg); - if ((temp & PORT_RESET) || !(temp & PORT_PE)) { - retval = -EPIPE; - goto done; - } - - if (!(temp & PORT_SUSPEND)) - goto done; - - tegra->port_resuming = 1; - - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) { - /* disable interrupts */ - ehci_writel(ehci, 0, &ehci->regs->intr_enable); - /* Disable RUN bit. */ - ehci->command &= ~CMD_RUN; - cmd_run = ehci_readl(ehci, &ehci->regs->command); - cmd_run &= ~CMD_RUN; - ehci_writel(ehci, cmd_run, &ehci->regs->command); - if (handshake (ehci, &ehci->regs->status, - STS_HALT, STS_HALT, 16 * 125)) - pr_err("%s() timeout waiting for STS_HALT\n", __func__); - } - - /* Disable disconnect detection during port resume */ - tegra_usb_phy_preresume(tegra->phy, false); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - if (tegra->phy->usb_phy_type != TEGRA_USB_PHY_TYPE_UTMIP) { -#endif - ehci_dbg(ehci, "%s:USBSTS = 0x%x", __func__, - ehci_readl(ehci, &ehci->regs->status)); - usbsts_reg = ehci_readl(ehci, &ehci->regs->status); - ehci_writel(ehci, usbsts_reg, &ehci->regs->status); - usbsts_reg = ehci_readl(ehci, &ehci->regs->status); - udelay(20); - - if (handshake(ehci, &ehci->regs->status, STS_SRI, STS_SRI, 2000)) - pr_err("%s: timeout set for STS_SRI\n", __func__); - - usbsts_reg = ehci_readl(ehci, &ehci->regs->status); - ehci_writel(ehci, usbsts_reg, &ehci->regs->status); - - if (handshake(ehci, &ehci->regs->status, STS_SRI, 0, 2000)) - pr_err("%s: timeout clear STS_SRI\n", __func__); - - if (handshake(ehci, &ehci->regs->status, STS_SRI, STS_SRI, 2000)) - pr_err("%s: timeout set STS_SRI\n", __func__); - - udelay(20); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - } -#endif - temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); - /* start resume signaling */ - ehci_writel(ehci, temp | PORT_RESUME, status_reg); - - ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25); - /* whoever resumes must GetPortStatus to complete it!! */ - goto done; - } + /* handle ehci hub control request */ + retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); - /* Handle port reset here */ - if ((hsic) && (typeReq == SetPortFeature) && - ((wValue == USB_PORT_FEAT_RESET) || (wValue == USB_PORT_FEAT_POWER))) { - selector = wIndex >> 8; - wIndex &= 0xff; - if (!wIndex || wIndex > ports) { - retval = -EPIPE; - goto done; - } - wIndex--; - status = 0; - temp = ehci_readl(ehci, status_reg); - if (temp & PORT_OWNER) - goto done; - temp &= ~PORT_RWC_BITS; - - switch (wValue) { - case USB_PORT_FEAT_RESET: - { - if (temp & PORT_RESUME) { - retval = -EPIPE; - goto done; - } - /* line status bits may report this as low speed, - * which can be fine if this root hub has a - * transaction translator built in. - */ - if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT - && !ehci_is_TDI(ehci) && PORT_USB11 (temp)) { - ehci_dbg (ehci, "port %d low speed --> companion\n", wIndex + 1); - temp |= PORT_OWNER; - ehci_writel(ehci, temp, status_reg); - } else { - ehci_vdbg(ehci, "port %d reset\n", wIndex + 1); - temp &= ~PORT_PE; - /* - * caller must wait, then call GetPortStatus - * usb 2.0 spec says 50 ms resets on root - */ - ehci->reset_done[wIndex] = jiffies + msecs_to_jiffies(50); - ehci_writel(ehci, temp, status_reg); - if (hsic && (wIndex == 0)) + /* do tegra phy specific actions based on the type request */ + if (!retval) { + switch (typeReq) { + case SetPortFeature: + if (wValue == USB_PORT_FEAT_SUSPEND) { + /* Need a 4ms delay for controller to suspend */ + mdelay(4); + tegra_usb_phy_post_suspend(tegra->phy); + } else if (wValue == USB_PORT_FEAT_RESET) { + if (ehci->reset_done[0] && wIndex == 0) tegra_usb_phy_bus_reset(tegra->phy); + } else if (wValue == USB_PORT_FEAT_POWER) { + if (wIndex == 1) + tegra_usb_phy_port_power(tegra->phy); } - break; } - case USB_PORT_FEAT_POWER: - { - if (HCS_PPC(ehci->hcs_params)) - ehci_writel(ehci, temp | PORT_POWER, status_reg); - if (hsic && (wIndex == 0)) - tegra_usb_phy_bus_connect(tegra->phy); - break; - } - } - goto done; } - spin_unlock_irqrestore(&ehci->lock, flags); - - /* Handle the hub control events here */ - retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return retval; -done: - spin_unlock_irqrestore(&ehci->lock, flags); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); return retval; } -#ifdef CONFIG_PM -static void tegra_ehci_restart(struct usb_hcd *hcd, bool is_dpd) +static void tegra_ehci_shutdown(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - unsigned int temp; - - ehci->controller_resets_phy = 0; - tegra_ehci_pre_reset(tegra->phy, false); - ehci_reset(ehci); - tegra_ehci_post_reset(tegra->phy, false); - - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_NULL_ULPI) - ehci->controller_resets_phy = 1; - - /* setup the frame list and Async q heads */ - ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); - ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); - /* setup the command register and set the controller in RUN mode */ - ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - /* dont start RS here for HSIC, it will be set by bus_reset */ - if (tegra->phy->usb_phy_type != TEGRA_USB_PHY_TYPE_HSIC) -#endif - ehci->command |= CMD_RUN; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - - /* Enable the root Port Power */ - if (HCS_PPC(ehci->hcs_params)) { - temp = ehci_readl(ehci, &ehci->regs->port_status[0]); - ehci_writel(ehci, temp | PORT_POWER, &ehci->regs->port_status[0]); - } - - down_write(&ehci_cf_port_reset_rwsem); - if(is_dpd) - hcd->state = HC_STATE_SUSPENDED; - else - hcd->state = HC_STATE_RUNNING; - ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); - /* flush posted writes */ - ehci_readl(ehci, &ehci->regs->command); - up_write(&ehci_cf_port_reset_rwsem); - - /* Turn On Interrupts */ - ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); -} - -static int tegra_usb_suspend(struct usb_hcd *hcd, bool is_dpd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - struct ehci_regs __iomem *hw = tegra->ehci->regs; - unsigned long flags; - int hsic = 0; - - hsic = (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_HSIC); - - spin_lock_irqsave(&tegra->ehci->lock, flags); - - if (tegra->ehci->has_hostpc) - tegra->port_speed = (readl(hcd->regs + HOSTPC_REG_OFFSET) >> 25) & 0x3; - else - tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; - ehci_halt(tegra->ehci); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) { - /* - * Ehci run bit is disabled by now read this into command variable - * so that bus resume will not enable run bit immedialty. - * this is required for 2LS WAR on UTMIP interface. - */ - tegra->ehci->command = ehci_readl(tegra->ehci, - &tegra->ehci->regs->command); - } -#endif - - spin_unlock_irqrestore(&tegra->ehci->lock, flags); - - tegra_ehci_power_down(hcd, is_dpd); - return 0; -} - -static int tegra_usb_resume(struct usb_hcd *hcd, bool is_dpd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct ehci_regs __iomem *hw = ehci->regs; - unsigned long val; - bool hsic; - bool null_ulpi; - bool utmip_remote_wakeup = false; - - null_ulpi = (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_NULL_ULPI); - hsic = (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_HSIC); - - tegra_ehci_power_up(hcd, is_dpd); - set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - - if ((tegra->port_speed > TEGRA_USB_PHY_PORT_SPEED_HIGH) || (hsic) || - (null_ulpi)) - goto restart; - - /* Force the phy to keep data lines in suspend state */ - tegra_ehci_phy_restore_start(tegra->phy, tegra->port_speed); - - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) { - ehci_reset(ehci); - } - - /* Enable host mode */ - tdi_reset(ehci); - - if ((tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) && - (tegra->ehci->has_hostpc)) { - val = readl(hcd->regs + HOSTPC_REG_OFFSET); - val &= ~HOSTPC1_DEVLC_PTS(~0); - val |= HOSTPC1_DEVLC_STS; - writel(val, hcd->regs + HOSTPC_REG_OFFSET); - } - - /* Enable Port Power */ - val = readl(&hw->port_status[0]); - val |= PORT_POWER; - writel(val, &hw->port_status[0]); - udelay(10); - - if ((tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_UTMIP) && - (tegra->ehci->has_hostpc) && (tegra->phy->remote_wakeup)) { - utmip_remote_wakeup = true; - } - - /* Check if the phy resume from LP0. When the phy resume from LP0 - * USB register will be reset. */ - if (!readl(&hw->async_next)) { -#ifdef CONFIG_ARCH_TEGRA_2x_SOC - /* Start the controller */ - val = readl(&hw->command); - writel((val | CMD_RUN), &hw->command); -#endif - /* Program the field PTC based on the saved speed mode */ - val = readl(&hw->port_status[0]); - val &= ~PORT_TEST(~0); - if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_HIGH) - val |= PORT_TEST_FORCE; - else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL) - val |= PORT_TEST(6); - else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) - val |= PORT_TEST(7); - writel(val, &hw->port_status[0]); - udelay(10); - - /* Disable test mode by setting PTC field to NORMAL_OP */ - val = readl(&hw->port_status[0]); - val &= ~PORT_TEST(~0); - writel(val, &hw->port_status[0]); - udelay(10); - } - - /* Poll until CCS is enabled */ - if (handshake(ehci, &hw->port_status[0], PORT_CONNECT, - PORT_CONNECT, 2000)) { - pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__); - goto restart; - } - - /* Poll until PE is enabled */ - if (handshake(ehci, &hw->port_status[0], PORT_PE, - PORT_PE, 2000)) { - pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__); - goto restart; - } - - /* Clear the PCI status, to avoid an interrupt taken upon resume */ - val = readl(&hw->status); - val |= STS_PCD; - writel(val, &hw->status); - - /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */ - val = readl(&hw->port_status[0]); - if ((val & PORT_POWER) && (val & PORT_PE)) { - val |= PORT_SUSPEND; - writel(val, &hw->port_status[0]); - - /* Need a 4ms delay before the controller goes to suspend */ - mdelay(4); - - /* Wait until port suspend completes */ - if (handshake(ehci, &hw->port_status[0], PORT_SUSPEND, - PORT_SUSPEND, 1000)) { - pr_err("%s: timeout waiting for PORT_SUSPEND\n", - __func__); - goto restart; - } - } - - tegra_ehci_phy_restore_end(tegra->phy); - if (utmip_remote_wakeup) { - ehci->command |= CMD_RUN; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - } - return 0; - -restart: - if (null_ulpi) { - bool LP0 = !readl(&hw->async_next); - - if (LP0) { - static int cnt = 1; - - pr_info("LP0 restart %d\n", cnt++); - tegra_ehci_phy_restore_start(tegra->phy, - tegra->port_speed); - } - - val = readl(&hw->port_status[0]); - if (!((val & PORT_POWER) && (val & PORT_PE))) { - tegra_ehci_restart(hcd, is_dpd); - } - - if (LP0) - tegra_ehci_phy_restore_end(tegra->phy); - - return 0; - } - - if ((tegra->port_speed <= TEGRA_USB_PHY_PORT_SPEED_HIGH) && (!hsic)) - tegra_ehci_phy_restore_end(tegra->phy); - if (hsic) { - val = readl(&hw->port_status[0]); - if (!((val & PORT_POWER) && (val & PORT_PE))) - tegra_ehci_restart(hcd, false); - - tegra_usb_phy_bus_idle(tegra->phy); - if (!tegra_usb_phy_is_device_connected(tegra->phy)) - pr_err("%s: no hsic device conenction\n", __func__); - } else { - tegra_ehci_restart(hcd, false); - } - - return 0; -} -#endif - -/* - * Disable PHY clock valid interrupts and wait for the interrupt handler to - * finish. - * - * Requires a lock on tegra_ehci_hcd_mutex - * Must not be called with a lock on ehci->lock - */ -static void tegra_ehci_disable_phy_interrupt(struct usb_hcd *hcd) { - struct tegra_ehci_hcd *tegra; - u32 val; - if (hcd->irq >= 0) { - tegra = dev_get_drvdata(hcd->self.controller); - if (tegra->phy->hotplug) { - /* Disable PHY clock valid interrupts */ - val = readl(hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET); - val &= ~TEGRA_USB_PHY_CLK_VALID_INT_ENB; - writel(val , (hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET)); - } - /* Wait for the interrupt handler to finish */ - synchronize_irq(hcd->irq); - } -} - -static void tegra_ehci_shutdown(struct usb_hcd *hcd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - tegra_ehci_disable_phy_interrupt(hcd); - /* ehci_shutdown touches the USB controller registers, make sure - * controller has clocks to it */ - if (!tegra->host_resumed) - tegra_ehci_power_up(hcd, false); - - ehci_shutdown(hcd); - - /* we are ready to shut down, powerdown the phy */ - tegra_ehci_power_down(hcd, false); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); + mutex_lock(&tegra->sync_lock); + del_timer_sync(&ehci->watchdog); + del_timer_sync(&ehci->iaa_watchdog); + if (tegra_usb_phy_hw_accessible(tegra->phy)) { + spin_lock_irq(&ehci->lock); + ehci_silence_controller(ehci); + spin_unlock_irq(&ehci->lock); + } + mutex_unlock(&tegra->sync_lock); } static int tegra_ehci_setup(struct usb_hcd *hcd) @@ -786,24 +294,18 @@ static int tegra_ehci_setup(struct usb_hcd *hcd) /* EHCI registers start at offset 0x100 */ ehci->caps = hcd->regs + 0x100; ehci->regs = hcd->regs + 0x100 + - HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase)); + HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase)); dbg_hcs_params(ehci, "reset"); dbg_hcc_params(ehci, "reset"); /* cache this readonly data; minimize chip reads */ ehci->hcs_params = readl(&ehci->caps->hcs_params); + ehci->has_hostpc = tegra_usb_phy_has_hostpc(tegra->phy) ? 1 : 0; + ehci->broken_hostpc_phcd = true; -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - ehci->has_hostpc = 1; -#endif hcd->has_tt = 1; - if (tegra->phy->usb_phy_type != TEGRA_USB_PHY_TYPE_NULL_ULPI) { - ehci_reset(ehci); - tegra_ehci_post_reset(tegra->phy, false); - } - retval = ehci_halt(ehci); if (retval) return retval; @@ -815,267 +317,49 @@ static int tegra_ehci_setup(struct usb_hcd *hcd) ehci->sbrn = 0x20; ehci->controller_remote_wakeup = false; - - if (tegra->phy->usb_phy_type == TEGRA_USB_PHY_TYPE_NULL_ULPI) { - tegra_ehci_pre_reset(tegra->phy, false); - ehci_reset(ehci); - tegra_ehci_post_reset(tegra->phy, false); - - /* - * Resetting the controller has the side effect of resetting the PHY. - * So, never reset the controller after the calling - * tegra_ehci_reinit API. - */ - ehci->controller_resets_phy = 1; - } + ehci_reset(ehci); + tegra_usb_phy_reset(tegra->phy); ehci_port_power(ehci, 1); return retval; } + #ifdef CONFIG_PM static int tegra_ehci_bus_suspend(struct usb_hcd *hcd) { struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - int error_status = 0; - - mutex_lock(&tegra->tegra_ehci_hcd_mutex); + int err = 0; + EHCI_DBG("%s() BEGIN\n", __func__); + mutex_lock(&tegra->sync_lock); tegra->bus_suspended_fail = false; - tegra_ehci_disable_phy_interrupt(hcd); - /* ehci_shutdown touches the USB controller registers, make sure - * controller has clocks to it */ - if (!tegra->host_resumed) - tegra_ehci_power_up(hcd, false); - error_status = ehci_bus_suspend(hcd); - if (error_status) + err = ehci_bus_suspend(hcd); + if (err) tegra->bus_suspended_fail = true; - if (!error_status && tegra->power_down_on_bus_suspend) { - tegra_usb_suspend(hcd, false); - tegra->bus_suspended = 1; - } - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); + else + tegra_usb_phy_suspend(tegra->phy); + mutex_unlock(&tegra->sync_lock); + EHCI_DBG("%s() END\n", __func__); - return error_status; + return err; } static int tegra_ehci_bus_resume(struct usb_hcd *hcd) { struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - int ehci_bus_resumed; + int err = 0; + EHCI_DBG("%s() BEGIN\n", __func__); - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - if (tegra->bus_suspended && tegra->power_down_on_bus_suspend) { - tegra_usb_resume(hcd, false); - tegra->bus_suspended = 0; - } + mutex_lock(&tegra->sync_lock); + tegra_usb_phy_resume(tegra->phy); + err = ehci_bus_resume(hcd); + mutex_unlock(&tegra->sync_lock); + EHCI_DBG("%s() END\n", __func__); - ehci_bus_resumed = ehci_bus_resume(hcd); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return ehci_bus_resumed; + return err; } #endif -struct dma_aligned_buffer { - void *kmalloc_ptr; - void *old_xfer_buffer; - u8 data[0]; -}; - -static void free_dma_aligned_buffer(struct urb *urb) -{ - struct dma_aligned_buffer *temp = container_of(urb->transfer_buffer, - struct dma_aligned_buffer, data); - - if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) - return; - - if(usb_urb_dir_in(urb)) - memcpy(temp->old_xfer_buffer, temp->data, - urb->transfer_buffer_length); - urb->transfer_buffer = temp->old_xfer_buffer; - kfree(temp->kmalloc_ptr); - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; -} - -static int alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) -{ - struct dma_aligned_buffer *temp, *kmalloc_ptr; - size_t kmalloc_size; - - if (urb->num_sgs || urb->sg || - urb->transfer_buffer_length == 0 || - !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) - return 0; - - /* Allocate a buffer with enough padding for alignment */ - kmalloc_size = urb->transfer_buffer_length + - sizeof(struct dma_aligned_buffer) + TEGRA_USB_DMA_ALIGN - 1; - - kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); - if (!kmalloc_ptr) - return -ENOMEM; - - /* Position our struct dma_aligned_buffer such that data is aligned */ - temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1; - temp->kmalloc_ptr = kmalloc_ptr; - temp->old_xfer_buffer = urb->transfer_buffer; - if (!usb_urb_dir_in(urb)) - memcpy(temp->data, urb->transfer_buffer, - urb->transfer_buffer_length); - urb->transfer_buffer = temp->data; - urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; - - return 0; -} - -static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - int ret; - - ret = alloc_dma_aligned_buffer(urb, mem_flags); - if (ret) - return ret; - - ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); - - /* control packets over dma */ - if (urb->setup_dma) - dma_sync_single_for_device(hcd->self.controller, - urb->setup_dma, sizeof(struct usb_ctrlrequest), - DMA_TO_DEVICE); - - /* urb buffers over dma */ - if (urb->transfer_dma) { - enum dma_data_direction dir; - dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; - - dma_sync_single_for_device(hcd->self.controller, - urb->transfer_dma, urb->transfer_buffer_length, dir); - } - - if (ret) - free_dma_aligned_buffer(urb); - - return ret; -} - -static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - - /* Fence read for coherency of AHB master intiated writes */ - if (tegra->phy->instance == 0) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB1_PREFETCH_ID)); - else if (tegra->phy->instance == 1) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB2_PREFETCH_ID)); - else if (tegra->phy->instance == 2) - readb(IO_ADDRESS(IO_PPCS_PHYS + USB3_PREFETCH_ID)); - - if (urb->transfer_dma) { - enum dma_data_direction dir; - dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; - if (dir == DMA_FROM_DEVICE) - dma_sync_single_for_cpu(hcd->self.controller, - urb->transfer_dma, urb->transfer_buffer_length, - DMA_FROM_DEVICE); - } - - usb_hcd_unmap_urb_for_dma(hcd, urb); - free_dma_aligned_buffer(urb); -} - -void clk_timer_callback(unsigned long data) -{ - struct tegra_ehci_hcd *tegra = (struct tegra_ehci_hcd*) data; - unsigned long flags; - - if (!timer_pending(&tegra->clk_timer)) { - spin_lock_irqsave(&tegra->ehci->lock, flags); - tegra->timer_event = 1; - spin_unlock_irqrestore(&tegra->ehci->lock, flags); - schedule_work(&tegra->clk_timer_work); - } -} - -static void clk_timer_work_handler(struct work_struct* clk_timer_work) { - struct tegra_ehci_hcd *tegra = container_of(clk_timer_work, - struct tegra_ehci_hcd, clk_timer_work); - int ret; - unsigned long flags; - bool clock_enabled, timer_event; - - spin_lock_irqsave(&tegra->ehci->lock, flags); - clock_enabled = tegra->clock_enabled; - timer_event = tegra->timer_event; - spin_unlock_irqrestore(&tegra->ehci->lock, flags); - - if (timer_event) { - spin_lock_irqsave(&tegra->ehci->lock, flags); - tegra->clock_enabled = 0; - tegra->timer_event = 0; - spin_unlock_irqrestore(&tegra->ehci->lock, flags); - clk_disable(tegra->emc_clk); - clk_disable(tegra->sclk_clk); - return; - } - - if ((!clock_enabled)) { - ret = mod_timer(&tegra->clk_timer, jiffies + msecs_to_jiffies(2000)); - if (ret) - pr_err("tegra_ehci_urb_enqueue timer modify failed \n"); - clk_enable(tegra->emc_clk); - clk_enable(tegra->sclk_clk); - spin_lock_irqsave(&tegra->ehci->lock, flags); - tegra->clock_enabled = 1; - spin_unlock_irqrestore(&tegra->ehci->lock, flags); - } else { - if (timer_pending(&tegra->clk_timer)) { - mod_timer_pending (&tegra->clk_timer, jiffies - + msecs_to_jiffies(2000)); - } - } -} - -static int tegra_ehci_urb_enqueue ( - struct usb_hcd *hcd, - struct urb *urb, - gfp_t mem_flags) -{ - struct tegra_ehci_hcd *pdata; - int xfertype; - int transfer_buffer_length; - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - unsigned long flags; - pdata = dev_get_drvdata(hcd->self.controller); - - xfertype = usb_endpoint_type(&urb->ep->desc); - transfer_buffer_length = urb->transfer_buffer_length; - spin_lock_irqsave(&ehci->lock,flags); - /* Turn on the USB busy hints */ - switch (xfertype) { - case USB_ENDPOINT_XFER_INT: - if (transfer_buffer_length < 255) { - /* Do nothing for interrupt buffers < 255 */ - } else { - /* signal to set the busy hints */ - schedule_work(&pdata->clk_timer_work); - } - break; - case USB_ENDPOINT_XFER_ISOC: - case USB_ENDPOINT_XFER_BULK: - /* signal to set the busy hints */ - schedule_work(&pdata->clk_timer_work); - break; - case USB_ENDPOINT_XFER_CONTROL: - default: - /* Do nothing special here */ - break; - } - spin_unlock_irqrestore(&ehci->lock,flags); - return ehci_urb_enqueue(hcd, urb, mem_flags); -} - static const struct hc_driver tegra_ehci_hc_driver = { .description = hcd_name, .product_desc = "Tegra EHCI Host Controller", @@ -1085,9 +369,10 @@ static const struct hc_driver tegra_ehci_hc_driver = { /* standard ehci functions */ .start = ehci_run, .stop = ehci_stop, + .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, - .endpoint_reset = ehci_endpoint_reset, + .endpoint_reset = ehci_endpoint_reset, .get_frame_number = ehci_get_frame, .hub_status_data = ehci_hub_status_data, .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, @@ -1101,10 +386,9 @@ static const struct hc_driver tegra_ehci_hc_driver = { .map_urb_for_dma = tegra_ehci_map_urb_for_dma, .unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma, .hub_control = tegra_ehci_hub_control, - .urb_enqueue = tegra_ehci_urb_enqueue, #ifdef CONFIG_PM - .bus_suspend = tegra_ehci_bus_suspend, - .bus_resume = tegra_ehci_bus_resume, + .bus_suspend = tegra_ehci_bus_suspend, + .bus_resume = tegra_ehci_bus_resume, #endif }; @@ -1113,76 +397,30 @@ static int tegra_ehci_probe(struct platform_device *pdev) struct resource *res; struct usb_hcd *hcd; struct tegra_ehci_hcd *tegra; - struct tegra_ehci_platform_data *pdata; int err = 0; int irq; - int instance = pdev->id; - - pdata = pdev->dev.platform_data; - if (!pdata) { - dev_err(&pdev->dev, "Platform data missing\n"); - return -EINVAL; - } tegra = kzalloc(sizeof(struct tegra_ehci_hcd), GFP_KERNEL); - if (!tegra) + if (!tegra) { + dev_err(&pdev->dev, "memory alloc failed\n"); return -ENOMEM; + } - mutex_init(&tegra->tegra_ehci_hcd_mutex); + mutex_init(&tegra->sync_lock); hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) { - dev_err(&pdev->dev, "Unable to create HCD\n"); + dev_err(&pdev->dev, "unable to create HCD\n"); err = -ENOMEM; goto fail_hcd; } platform_set_drvdata(pdev, tegra); - tegra->default_enable = pdata->default_enable; - - tegra->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(tegra->clk)) { - dev_err(&pdev->dev, "Can't get ehci clock\n"); - err = PTR_ERR(tegra->clk); - goto fail_clk; - } - - err = clk_enable(tegra->clk); - if (err) - goto fail_clken; - - - tegra->sclk_clk = clk_get(&pdev->dev, "sclk"); - if (IS_ERR(tegra->sclk_clk)) { - dev_err(&pdev->dev, "Can't get sclk clock\n"); - err = PTR_ERR(tegra->sclk_clk); - goto fail_sclk_clk; - } - - clk_set_rate(tegra->sclk_clk, 80000000); - - tegra->emc_clk = clk_get(&pdev->dev, "emc"); - if (IS_ERR(tegra->emc_clk)) { - dev_err(&pdev->dev, "Can't get emc clock\n"); - err = PTR_ERR(tegra->emc_clk); - goto fail_emc_clk; - } - init_timer(&tegra->clk_timer); - tegra->clk_timer.function = clk_timer_callback; - tegra->clk_timer.data = (unsigned long) tegra; - -#ifdef CONFIG_ARCH_TEGRA_2x_SOC - /* Set DDR busy hints to 150MHz. For Tegra 2x SOC, DDR rate is half of EMC rate */ - clk_set_rate(tegra->emc_clk, 300000000); -#else - /* Set DDR busy hints to 100MHz. For Tegra 3x SOC DDR rate equals to EMC rate */ - clk_set_rate(tegra->emc_clk, 100000000); -#endif res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { - dev_err(&pdev->dev, "Failed to get I/O memory\n"); + dev_err(&pdev->dev, "failed to get I/O memory\n"); err = -ENXIO; goto fail_io; } @@ -1190,179 +428,103 @@ static int tegra_ehci_probe(struct platform_device *pdev) hcd->rsrc_len = resource_size(res); hcd->regs = ioremap(res->start, resource_size(res)); if (!hcd->regs) { - dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + dev_err(&pdev->dev, "failed to remap I/O memory\n"); err = -ENOMEM; goto fail_io; } - INIT_WORK(&tegra->clk_timer_work, clk_timer_work_handler); + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + err = -ENODEV; + goto fail_irq; + } + set_irq_flags(irq, IRQF_VALID); + tegra->irq = irq; - tegra->phy = tegra_usb_phy_open(instance, hcd->regs, pdata->phy_config, - TEGRA_USB_PHY_MODE_HOST, pdata->phy_type); + tegra->phy = tegra_usb_phy_open(pdev); if (IS_ERR(tegra->phy)) { - dev_err(&pdev->dev, "Failed to open USB phy\n"); + dev_err(&pdev->dev, "failed to open USB phy\n"); err = -ENXIO; - goto fail_phy; + goto fail_irq; } - tegra->phy->hotplug = pdata->hotplug; - err = tegra_usb_phy_power_on(tegra->phy, true); + err = tegra_usb_phy_power_on(tegra->phy); if (err) { - dev_err(&pdev->dev, "Failed to power on the phy\n"); - goto fail; - } - - tegra->host_resumed = 1; - tegra->power_down_on_bus_suspend = pdata->power_down_on_bus_suspend; - tegra->ehci = hcd_to_ehci(hcd); - - irq = platform_get_irq(pdev, 0); - if (!irq) { - dev_err(&pdev->dev, "Failed to get IRQ\n"); - err = -ENODEV; - goto fail; + dev_err(&pdev->dev, "failed to power on the phy\n"); + goto fail_phy; } - set_irq_flags(irq, IRQF_VALID); - tegra->irq = irq; -#ifdef CONFIG_USB_OTG_UTILS - if (pdata->operating_mode == TEGRA_USB_OTG) { - tegra->transceiver = otg_get_transceiver(); - if (tegra->transceiver) - otg_set_host(tegra->transceiver, &hcd->self); + err = tegra_usb_phy_init(tegra->phy); + if (err) { + dev_err(&pdev->dev, "failed to init the phy\n"); + goto fail_phy; } -#endif err = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); if (err) { - dev_err(&pdev->dev, "Failed to add USB HCD error = %d\n", err); - goto fail; + dev_err(&pdev->dev, "Failed to add USB HCD, error=%d\n", err); + goto fail_phy; } err = enable_irq_wake(tegra->irq); if (err < 0) { dev_warn(&pdev->dev, - "Couldn't enable USB host mode wakeup, irq=%d, " - "error=%d\n", tegra->irq, err); + "Couldn't enable USB host mode wakeup, irq=%d, " + "error=%d\n", irq, err); err = 0; tegra->irq = 0; } - return err; + tegra->ehci = hcd_to_ehci(hcd); -fail: #ifdef CONFIG_USB_OTG_UTILS - if (tegra->transceiver) { - otg_set_host(tegra->transceiver, NULL); - otg_put_transceiver(tegra->transceiver); + if (tegra_usb_phy_otg_supported(tegra->phy)) { + tegra->transceiver = otg_get_transceiver(); + if (tegra->transceiver) + otg_set_host(tegra->transceiver, &hcd->self); } #endif - tegra_usb_phy_close(tegra->phy); + return err; + fail_phy: + tegra_usb_phy_close(tegra->phy); +fail_irq: iounmap(hcd->regs); fail_io: - clk_disable(tegra->emc_clk); - clk_put(tegra->emc_clk); -fail_emc_clk: - clk_disable(tegra->sclk_clk); - clk_put(tegra->sclk_clk); -fail_sclk_clk: - clk_disable(tegra->clk); -fail_clken: - clk_put(tegra->clk); -fail_clk: usb_put_hcd(hcd); fail_hcd: kfree(tegra); + return err; } + #ifdef CONFIG_PM static int tegra_ehci_resume_noirq(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) { - if (tegra->default_enable) - clk_enable(tegra->clk); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return 0; - } - - if (tegra->default_enable) - clk_enable(tegra->clk); - - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return 0; + return tegra_usb_phy_power_on(tegra->phy); } -static int tegra_ehci_resume(struct device *dev) +static int tegra_ehci_suspend_noirq(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - int ret; - - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) { - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return 0; - } - ret = tegra_usb_resume(hcd, true); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return ret; -} - -static int tegra_ehci_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - int ret; - u32 val; - - mutex_lock(&tegra->tegra_ehci_hcd_mutex); - /* if bus suspend is failed means there is remote wakeup resume, - then abort the PM suspend */ - if (tegra->bus_suspended_fail) { - tegra->bus_suspended_fail = false; - pr_err("%s: bus suspend failed, aborting driver suspend\n", __func__); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); + /* bus suspend could have failed because of remote wakeup resume */ + if (tegra->bus_suspended_fail) return -EBUSY; - } - if (tegra->phy->hotplug) { - /* Disable PHY clock valid interrupts while going into suspend*/ - val = readl(hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET); - val &= ~TEGRA_USB_PHY_CLK_VALID_INT_ENB; - writel(val , (hcd->regs + TEGRA_USB_SUSP_CTRL_OFFSET)); - } - - if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) { - if (tegra->default_enable) - clk_disable(tegra->clk); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return 0; - } - - if (time_before(jiffies, tegra->ehci->next_statechange)) - msleep(10); - - ret = tegra_usb_suspend(hcd, true); - if (tegra->default_enable) - clk_disable(tegra->clk); - mutex_unlock(&tegra->tegra_ehci_hcd_mutex); - return ret; + else + return tegra_usb_phy_power_off(tegra->phy); } static struct dev_pm_ops tegra_ehci_dev_pm_ops = { - .suspend = tegra_ehci_suspend, - .resume = tegra_ehci_resume, - .resume_noirq = tegra_ehci_resume_noirq, + .suspend_noirq = tegra_ehci_suspend_noirq, + .resume_noirq = tegra_ehci_resume_noirq, }; - #endif static int tegra_ehci_remove(struct platform_device *pdev) @@ -1372,9 +534,6 @@ static int tegra_ehci_remove(struct platform_device *pdev) if (tegra == NULL || hcd == NULL) return -EINVAL; - /* make sure controller is on as we will touch its registers */ - if (!tegra->host_resumed) - tegra_ehci_power_up(hcd, true); #ifdef CONFIG_USB_OTG_UTILS if (tegra->transceiver) { @@ -1383,30 +542,15 @@ static int tegra_ehci_remove(struct platform_device *pdev) } #endif - /* Turn Off Interrupts */ - ehci_writel(tegra->ehci, 0, &tegra->ehci->regs->intr_enable); - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); if (tegra->irq) disable_irq_wake(tegra->irq); usb_remove_hcd(hcd); usb_put_hcd(hcd); - tegra_usb_phy_power_off(tegra->phy, true); + tegra_usb_phy_power_off(tegra->phy); tegra_usb_phy_close(tegra->phy); iounmap(hcd->regs); - - del_timer_sync(&tegra->clk_timer); - - clk_disable(tegra->clk); - clk_put(tegra->clk); - - if (tegra->clock_enabled) { - clk_disable(tegra->sclk_clk); - clk_disable(tegra->emc_clk); - } - clk_put(tegra->sclk_clk); - clk_put(tegra->emc_clk); - kfree(tegra); + return 0; } @@ -1421,12 +565,12 @@ static void tegra_ehci_hcd_shutdown(struct platform_device *pdev) static struct platform_driver tegra_ehci_driver = { .probe = tegra_ehci_probe, - .remove = tegra_ehci_remove, + .remove = tegra_ehci_remove, .shutdown = tegra_ehci_hcd_shutdown, - .driver = { - .name = "tegra-ehci", + .driver = { + .name = driver_name, #ifdef CONFIG_PM - .pm = &tegra_ehci_dev_pm_ops, + .pm = &tegra_ehci_dev_pm_ops, #endif } }; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 509934ceb4a9..cfbdf32ec0b2 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -143,6 +143,7 @@ struct ehci_hcd { /* one per controller */ #ifdef CONFIG_USB_EHCI_TEGRA unsigned controller_resets_phy:1; unsigned controller_remote_wakeup:1; + unsigned broken_hostpc_phcd:1; #endif /* required for usb32 quirk */ diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c index f72e2225e7c7..4c590076f73e 100644 --- a/drivers/usb/otg/tegra-otg.c +++ b/drivers/usb/otg/tegra-otg.c @@ -24,7 +24,6 @@ #include <linux/usb.h> #include <linux/usb/otg.h> #include <linux/usb/gadget.h> -#include <linux/usb/hcd.h> #include <linux/platform_device.h> #include <linux/platform_data/tegra_usb.h> #include <linux/clk.h> @@ -41,7 +40,6 @@ #define USB_VBUS_INT_EN (1 << 8) #define USB_VBUS_INT_STATUS (1 << 9) #define USB_VBUS_STATUS (1 << 10) -#define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS) #define USB_INT_EN (USB_VBUS_INT_EN | USB_ID_INT_EN | \ USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN) @@ -67,9 +65,9 @@ struct tegra_otg_data { bool clk_enabled; callback_t charger_cb; void *charger_cb_data; - bool interrupt_mode; }; + static struct tegra_otg_data *tegra_clone; static inline unsigned long otg_readl(struct tegra_otg_data *tegra, @@ -84,20 +82,6 @@ static inline void otg_writel(struct tegra_otg_data *tegra, unsigned long val, writel(val, tegra->regs + offset); } -static void tegra_otg_enable_clk(void) -{ - if (!tegra_clone->clk_enabled) - clk_enable(tegra_clone->clk); - tegra_clone->clk_enabled = true; -} - -static void tegra_otg_disable_clk(void) -{ - if (tegra_clone->clk_enabled) - clk_disable(tegra_clone->clk); - tegra_clone->clk_enabled = false; -} - static const char *tegra_state_name(enum usb_otg_state state) { switch (state) { @@ -132,78 +116,69 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en) return val; } -static struct platform_device * -tegra_usb_otg_host_register(struct platform_device *ehci_device, - struct tegra_ehci_platform_data *pdata) +static void tegra_start_host(struct tegra_otg_data *tegra) { - struct platform_device *pdev; + struct tegra_usb_otg_data *pdata = tegra->otg.dev->platform_data; + struct platform_device *pdev, *ehci_device = pdata->ehci_device; void *platform_data; int val; + DBG("%s(%d) Begin\n", __func__, __LINE__); + if (tegra->pdev) + return ; + + /* prepare device structure for registering host*/ pdev = platform_device_alloc(ehci_device->name, ehci_device->id); if (!pdev) - return NULL; + return ; val = platform_device_add_resources(pdev, ehci_device->resource, ehci_device->num_resources); if (val) goto error; - pdev->dev.dma_mask = ehci_device->dev.dma_mask; + pdev->dev.dma_mask = ehci_device->dev.dma_mask; pdev->dev.coherent_dma_mask = ehci_device->dev.coherent_dma_mask; - platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data), - GFP_KERNEL); + platform_data = kmalloc(sizeof(struct tegra_usb_platform_data), GFP_KERNEL); if (!platform_data) goto error; - memcpy(platform_data, pdata, sizeof(struct tegra_ehci_platform_data)); + memcpy(platform_data, pdata->ehci_pdata, + sizeof(struct tegra_usb_platform_data)); pdev->dev.platform_data = platform_data; val = platform_device_add(pdev); if (val) goto error_add; - return pdev; + tegra->pdev = pdev; + DBG("%s(%d) End\n", __func__, __LINE__); + return ; error_add: kfree(platform_data); error: pr_err("%s: failed to add the host controller device\n", __func__); platform_device_put(pdev); - return NULL; -} - -static void tegra_usb_otg_host_unregister(struct platform_device *pdev) -{ - kfree(pdev->dev.platform_data); - pdev->dev.platform_data = NULL; - platform_device_unregister(pdev); + tegra->pdev = NULL; } -void tegra_start_host(struct tegra_otg_data *tegra) +static void tegra_stop_host(struct tegra_otg_data *tegra) { - DBG("%s(%d) BEGIN\n", __func__, __LINE__); - - struct tegra_otg_platform_data *pdata = tegra->otg.dev->platform_data; - if (!tegra->pdev) { - tegra->pdev = tegra_usb_otg_host_register(pdata->ehci_device, - pdata->ehci_pdata); - } - - DBG("%s(%d) END\n", __func__, __LINE__); -} + struct platform_device *pdev = tegra->pdev; -void tegra_stop_host(struct tegra_otg_data *tegra) -{ - DBG("%s(%d) BEGIN\n", __func__, __LINE__); + DBG("%s(%d) Begin\n", __func__, __LINE__); - if (tegra->pdev) { - tegra_usb_otg_host_unregister(tegra->pdev); + if (pdev) { + /* unregister host from otg */ + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = NULL; + platform_device_unregister(pdev); tegra->pdev = NULL; } - DBG("%s(%d) END\n", __func__, __LINE__); + DBG("%s(%d) End\n", __func__, __LINE__); } int register_otg_callback(callback_t cb, void *args) @@ -263,10 +238,7 @@ static void irq_work(struct work_struct *work) unsigned long flags; unsigned long status; - clk_enable(tegra->clk); - spin_lock_irqsave(&tegra->lock, flags); - status = tegra->int_status; /* Debug prints */ @@ -299,8 +271,6 @@ static void irq_work(struct work_struct *work) spin_unlock_irqrestore(&tegra->lock, flags); tegra_change_otg_state(tegra, to); - clk_disable(tegra->clk); - tegra_otg_disable_clk(); } static irqreturn_t tegra_otg_irq(int irq, void *data) @@ -310,8 +280,9 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) unsigned long val; spin_lock_irqsave(&tegra->lock, flags); - val = otg_readl(tegra, USB_PHY_WAKEUP); + DBG("%s(%d) interrupt val = 0x%x\n", __func__, __LINE__, val); + if (val & (USB_VBUS_INT_EN | USB_ID_INT_EN)) { DBG("%s(%d) PHY_WAKEUP = 0x%x\n", __func__, __LINE__, val); otg_writel(tegra, val, USB_PHY_WAKEUP); @@ -320,17 +291,11 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) schedule_work(&tegra->work); } } - spin_unlock_irqrestore(&tegra->lock, flags); return IRQ_HANDLED; } -void tegra_otg_check_vbus_detection(void) -{ - tegra_otg_enable_clk(); -} -EXPORT_SYMBOL(tegra_otg_check_vbus_detection); static int tegra_otg_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) @@ -353,7 +318,7 @@ static int tegra_otg_set_peripheral(struct otg_transceiver *otg, if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { tegra->int_status = val; - schedule_work (&tegra->work); + schedule_work(&tegra->work); } DBG("%s(%d) END\n", __func__, __LINE__); @@ -373,7 +338,6 @@ static int tegra_otg_set_host(struct otg_transceiver *otg, clk_enable(tegra->clk); val = otg_readl(tegra, USB_PHY_WAKEUP); val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS); - val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); otg_writel(tegra, val, USB_PHY_WAKEUP); clk_disable(tegra->clk); @@ -435,8 +399,6 @@ static DEVICE_ATTR(enable_host, 0644, show_host_en, store_host_en); static int tegra_otg_probe(struct platform_device *pdev) { struct tegra_otg_data *tegra; - struct tegra_otg_platform_data *otg_pdata; - struct tegra_ehci_platform_data *ehci_pdata; struct resource *res; int err; @@ -445,8 +407,6 @@ static int tegra_otg_probe(struct platform_device *pdev) return -ENOMEM; tegra->otg.dev = &pdev->dev; - otg_pdata = tegra->otg.dev->platform_data; - ehci_pdata = otg_pdata->ehci_pdata; tegra->otg.label = "tegra-otg"; tegra->otg.state = OTG_STATE_UNDEFINED; tegra->otg.set_host = tegra_otg_set_host; @@ -457,7 +417,6 @@ static int tegra_otg_probe(struct platform_device *pdev) platform_set_drvdata(pdev, tegra); tegra_clone = tegra; - tegra->clk_enabled = false; tegra->interrupt_mode = true; tegra->clk = clk_get(&pdev->dev, NULL); @@ -505,10 +464,8 @@ static int tegra_otg_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to register IRQ\n"); goto err_irq; } - INIT_WORK (&tegra->work, irq_work); + INIT_WORK(&tegra->work, irq_work); - if (!ehci_pdata->default_enable) - clk_disable(tegra->clk); dev_info(&pdev->dev, "otg transceiver registered\n"); err = device_create_file(&pdev->dev, &dev_attr_enable_host); @@ -552,59 +509,57 @@ static int __exit tegra_otg_remove(struct platform_device *pdev) static int tegra_otg_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); - struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); - struct otg_transceiver *otg = &tegra_otg->otg; + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + struct otg_transceiver *otg = &tegra->otg; int val; DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__, tegra_state_name(otg->state)); - clk_enable(tegra_otg->clk); - val = readl(tegra_otg->regs + USB_PHY_WAKEUP); + clk_enable(tegra->clk); + val = readl(tegra->regs + USB_PHY_WAKEUP); val &= ~USB_INT_EN; - writel(val, tegra_otg->regs + USB_PHY_WAKEUP); - clk_disable(tegra_otg->clk); + writel(val, tegra->regs + USB_PHY_WAKEUP); + clk_disable(tegra->clk); - /* suspend peripheral mode, host mode is taken care by host driver */ + /* Suspend peripheral mode, host mode is taken care by host driver */ if (otg->state == OTG_STATE_B_PERIPHERAL) - tegra_change_otg_state(tegra_otg, OTG_STATE_A_SUSPEND); + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); DBG("%s(%d) END\n", __func__, __LINE__); - tegra_otg_disable_clk(); return 0; } static void tegra_otg_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); - struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); - struct otg_transceiver *otg = &tegra_otg->otg; - + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + struct otg_transceiver *otg = &tegra->otg; int val; unsigned long flags; DBG("%s(%d) BEGIN\n", __func__, __LINE__); /* Clear pending interrupts */ - clk_enable(tegra_otg->clk); - val = readl(tegra_otg->regs + USB_PHY_WAKEUP); - writel(val, tegra_otg->regs + USB_PHY_WAKEUP); + clk_enable(tegra->clk); + val = readl(tegra->regs + USB_PHY_WAKEUP); + writel(val, tegra->regs + USB_PHY_WAKEUP); DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val); - clk_disable(tegra_otg->clk); + clk_disable(tegra->clk); /* Handle if host cable is replaced with device during suspend state */ if (otg->state == OTG_STATE_A_HOST && (val & USB_ID_STATUS)) - tegra_change_otg_state(tegra_otg, OTG_STATE_A_SUSPEND); + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); /* Enable interrupt and call work to set to appropriate state */ - spin_lock_irqsave(&tegra_otg->lock, flags); - tegra_otg->int_status = (val | USB_INT_EN); - spin_unlock_irqrestore(&tegra_otg->lock, flags); - irq_work(&tegra_otg->work); + spin_lock_irqsave(&tegra->lock, flags); + tegra->int_status = (val | USB_INT_EN); + spin_unlock_irqrestore(&tegra->lock, flags); + irq_work(&tegra->work); - clk_enable(tegra_otg->clk); - val = readl(tegra_otg->regs + USB_PHY_WAKEUP); + clk_enable(tegra->clk); + val = readl(tegra->regs + USB_PHY_WAKEUP); val |= USB_INT_EN; - writel(val, tegra_otg->regs + USB_PHY_WAKEUP); - clk_disable(tegra_otg->clk); + writel(val, tegra->regs + USB_PHY_WAKEUP); + clk_disable(tegra->clk); DBG("%s(%d) END\n", __func__, __LINE__); } diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index 55d3fccf56b2..df54578a5b5a 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -583,6 +583,23 @@ static unsigned int tegra_dc_has_multiple_dc(void) return (cnt > 1); } +/* get the stride size of a window. + * return: stride size in bytes for window win. or 0 if unavailble. */ +int tegra_dc_get_stride(struct tegra_dc *dc, unsigned win) +{ + u32 tmp; + u32 stride; + + if (!dc->enabled) + return 0; + BUG_ON(win > DC_N_WINDOWS); + tegra_dc_writel(dc, WINDOW_A_SELECT << win, + DC_CMD_DISPLAY_WINDOW_HEADER); + tmp = tegra_dc_readl(dc, DC_WIN_LINE_STRIDE); + return GET_LINE_STRIDE(tmp); +} +EXPORT_SYMBOL(tegra_dc_get_stride); + struct tegra_dc *tegra_dc_get_dc(unsigned idx) { if (idx < TEGRA_MAX_DC) diff --git a/drivers/video/tegra/dc/dc_reg.h b/drivers/video/tegra/dc/dc_reg.h index ded64de2decc..0b628fc7a14a 100644 --- a/drivers/video/tegra/dc/dc_reg.h +++ b/drivers/video/tegra/dc/dc_reg.h @@ -431,6 +431,8 @@ #define DC_WIN_LINE_STRIDE 0x70a #define LINE_STRIDE(x) (x) #define UV_LINE_STRIDE(x) (((x) & 0xffff) << 16) +#define GET_LINE_STRIDE(x) ((x) & 0xffff) +#define GET_UV_LINE_STRIDE(x) (((x) >> 16) & 0xffff) #define DC_WIN_BUF_STRIDE 0x70b #define DC_WIN_UV_BUF_STRIDE 0x70c #define DC_WIN_BUFFER_ADDR_MODE 0x70d diff --git a/drivers/video/tegra/dc/dsi.c b/drivers/video/tegra/dc/dsi.c index 69cc60f70f1c..e402c416b779 100644 --- a/drivers/video/tegra/dc/dsi.c +++ b/drivers/video/tegra/dc/dsi.c @@ -1,7 +1,7 @@ /* * drivers/video/tegra/dc/dsi.c * - * Copyright (c) 2011, NVIDIA Corporation. + * Copyright (c) 2011-2012, NVIDIA Corporation. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -31,6 +31,7 @@ #include <mach/dc.h> #include <mach/fb.h> #include <mach/csi.h> +#include <mach/iomap.h> #include <linux/nvhost.h> #include "dc_reg.h" @@ -38,6 +39,9 @@ #include "dsi_regs.h" #include "dsi.h" +#define APB_MISC_GP_MIPI_PAD_CTRL_0 (TEGRA_APB_MISC_BASE + 0x820) +#define DSIB_MODE_ENABLE 0x2 + #define DSI_USE_SYNC_POINTS 1 #define S_TO_MS(x) (1000 * (x)) @@ -1621,6 +1625,15 @@ static void tegra_dsi_pad_calibration(struct tegra_dc_dsi_data *dsi) tegra_vi_csi_writel(val, CSI_CIL_PAD_CONFIG); } +static void tegra_dsi_panelB_enable() +{ + unsigned int val; + + val = readl(IO_ADDRESS(APB_MISC_GP_MIPI_PAD_CTRL_0)); + val |= DSIB_MODE_ENABLE; + writel(val, (IO_ADDRESS(APB_MISC_GP_MIPI_PAD_CTRL_0))); +} + static int tegra_dsi_init_hw(struct tegra_dc *dc, struct tegra_dc_dsi_data *dsi) { @@ -1634,7 +1647,7 @@ static int tegra_dsi_init_hw(struct tegra_dc *dc, tegra_dsi_set_dsi_clk(dc, dsi, dsi->target_lp_clk_khz); if (dsi->info.dsi_instance) { - /* TODO:Set the misc register*/ + tegra_dsi_panelB_enable(); } /* TODO: only need to change the timing for bta */ diff --git a/drivers/video/tegra/fb.c b/drivers/video/tegra/fb.c index 50aa9b383059..1193a2eb8c52 100644 --- a/drivers/video/tegra/fb.c +++ b/drivers/video/tegra/fb.c @@ -44,7 +44,7 @@ #include "dc/dc_priv.h" /* Pad pitch to 16-byte boundary. */ -#define TEGRA_LINEAR_PITCH_ALIGNMENT 16 +#define TEGRA_LINEAR_PITCH_ALIGNMENT 32 struct tegra_fb_info { struct tegra_dc_win *win; @@ -527,6 +527,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, unsigned long fb_size = 0; unsigned long fb_phys = 0; int ret = 0; + unsigned stride; win = tegra_dc_get_window(dc, fb_data->win); if (!win) { @@ -560,6 +561,11 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, tegra_fb->valid = true; } + stride = tegra_dc_get_stride(dc, 0); + if (!stride) /* default to pad the stride to 16-byte boundary. */ + stride = round_up(info->fix.line_length, + TEGRA_LINEAR_PITCH_ALIGNMENT); + info->fbops = &tegra_fb_ops; info->pseudo_palette = pseudo_palette; info->screen_base = fb_base; @@ -574,9 +580,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, info->fix.smem_start = fb_phys; info->fix.smem_len = fb_size; info->fix.line_length = fb_data->xres * fb_data->bits_per_pixel / 8; - /* Pad the stride to 16-byte boundary. */ - info->fix.line_length = round_up(info->fix.line_length, - TEGRA_LINEAR_PITCH_ALIGNMENT); + info->fix.line_length = stride; info->var.xres = fb_data->xres; info->var.yres = fb_data->yres; diff --git a/drivers/video/tegra/host/bus_client.c b/drivers/video/tegra/host/bus_client.c index fd632a6ea9c5..87aa9c64d363 100644 --- a/drivers/video/tegra/host/bus_client.c +++ b/drivers/video/tegra/host/bus_client.c @@ -141,12 +141,6 @@ static int nvhost_channelopen(struct inode *inode, struct file *filp) priv->clientid = atomic_add_return(1, &nvhost_get_host(ch->dev)->clientid); priv->timeout = MAX_STUCK_CHECK_COUNT * SYNCPT_CHECK_PERIOD; - - priv->job = nvhost_job_alloc(ch, priv->hwctx, &priv->hdr, - NULL, priv->priority, priv->clientid); - if (!priv->job) - goto fail; - return 0; fail: nvhost_channelrelease(inode, filp); @@ -166,7 +160,7 @@ static int set_submit(struct nvhost_channel_userctx *ctx) return -EFAULT; } - ctx->job = nvhost_job_realloc(ctx->job, + ctx->job = nvhost_job_alloc(ctx->ch, ctx->hwctx, &ctx->hdr, ctx->nvmap, @@ -238,17 +232,28 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf, cmdbuf.mem, cmdbuf.words, cmdbuf.offset); hdr->num_cmdbufs--; } else if (hdr->num_relocs) { - consumed = sizeof(struct nvhost_reloc); - if (remaining < consumed) + int numrelocs = remaining / sizeof(struct nvhost_reloc); + if (!numrelocs) break; - if (copy_from_user(&job->pinarray[job->num_pins], + numrelocs = min_t(int, numrelocs, priv->hdr.num_relocs); + consumed = numrelocs * sizeof(struct nvhost_reloc); + if (copy_from_user(&job->relocarray[job->num_relocs], buf, consumed)) { err = -EFAULT; break; } - trace_nvhost_channel_write_reloc(chname); - job->num_pins++; - hdr->num_relocs--; + while (numrelocs) { + struct nvhost_reloc *reloc = + &job->relocarray[job->num_relocs]; + trace_nvhost_channel_write_reloc(chname, + reloc->cmdbuf_mem, + reloc->cmdbuf_offset, + reloc->target, + reloc->target_offset); + job->num_relocs++; + hdr->num_relocs--; + numrelocs--; + } } else if (hdr->num_waitchks) { int numwaitchks = (remaining / sizeof(struct nvhost_waitchk)); @@ -269,17 +274,19 @@ static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf, hdr->num_waitchks -= numwaitchks; } else if (priv->num_relocshifts) { int next_shift = - job->num_pins - priv->num_relocshifts; - consumed = sizeof(struct nvhost_reloc_shift); - if (remaining < consumed) + job->num_relocs - priv->num_relocshifts; + int num = + (remaining / sizeof(struct nvhost_reloc_shift)); + if (!num) break; - if (copy_from_user( - &job->pinarray[next_shift].reloc_shift, + num = min_t(int, num, priv->num_relocshifts); + consumed = num * sizeof(struct nvhost_reloc_shift); + if (copy_from_user(&job->relocshiftarray[next_shift], buf, consumed)) { err = -EFAULT; break; } - priv->num_relocshifts--; + priv->num_relocshifts -= num; } else { err = -EFAULT; break; @@ -302,7 +309,7 @@ static int nvhost_ioctl_channel_flush( struct nvhost_get_param_args *args, int null_kickoff) { - struct device *device = &ctx->ch->dev->dev; + struct nvhost_device *ndev = to_nvhost_device(&ctx->ch->dev->dev); int err; trace_nvhost_ioctl_channel_flush(ctx->ch->dev->name); @@ -312,13 +319,13 @@ static int nvhost_ioctl_channel_flush( ctx->hdr.num_cmdbufs || ctx->hdr.num_waitchks) { reset_submit(ctx); - dev_err(device, "channel submit out of sync\n"); + dev_err(&ndev->dev, "channel submit out of sync\n"); return -EFAULT; } - err = nvhost_job_pin(ctx->job); + err = nvhost_job_pin(ctx->job, &nvhost_get_host(ndev)->syncpt); if (err) { - dev_warn(device, "nvhost_job_pin failed: %d\n", err); + dev_warn(&ndev->dev, "nvhost_job_pin failed: %d\n", err); return err; } @@ -337,6 +344,9 @@ static int nvhost_ioctl_channel_flush( if (err) nvhost_job_unpin(ctx->job); + nvhost_job_put(ctx->job); + ctx->job = NULL; + return err; } diff --git a/drivers/video/tegra/host/chip_support.h b/drivers/video/tegra/host/chip_support.h index edc5f6a51574..d69e1c4bccb9 100644 --- a/drivers/video/tegra/host/chip_support.h +++ b/drivers/video/tegra/host/chip_support.h @@ -28,10 +28,9 @@ struct output; struct nvhost_master; struct nvhost_intr; struct nvhost_syncpt; -struct nvhost_waitchk; struct nvhost_userctx_timeout; struct nvhost_channel; -struct nvmap_handle; +struct nvmap_handle_ref; struct nvmap_client; struct nvhost_hwctx; struct nvhost_cdma; @@ -77,7 +76,7 @@ struct nvhost_chip_support { void (*destroy)(struct push_buffer *); void (*push_to)(struct push_buffer *, struct nvmap_client *, - struct nvmap_handle *, + struct nvmap_handle_ref *, u32 op1, u32 op2); void (*pop_from)(struct push_buffer *, unsigned int slots); @@ -106,11 +105,8 @@ struct nvhost_chip_support { void (*read_wait_base)(struct nvhost_syncpt *, u32 id); u32 (*update_min)(struct nvhost_syncpt *, u32 id); void (*cpu_incr)(struct nvhost_syncpt *, u32 id); - int (*wait_check)(struct nvhost_syncpt *sp, - struct nvmap_client *nvmap, - u32 waitchk_mask, - struct nvhost_waitchk *wait, - int num_waitchk); + int (*patch_wait)(struct nvhost_syncpt *sp, + void *patch_addr); void (*debug)(struct nvhost_syncpt *); const char * (*name)(struct nvhost_syncpt *, u32 id); int (*mutex_try_lock)(struct nvhost_syncpt *, diff --git a/drivers/video/tegra/host/debug.c b/drivers/video/tegra/host/debug.c index 8a26f92c79f6..820eac85521d 100644 --- a/drivers/video/tegra/host/debug.c +++ b/drivers/video/tegra/host/debug.c @@ -106,13 +106,53 @@ static void show_all(struct nvhost_master *m, struct output *o) nvhost_get_chip_ops()->debug.show_mlocks(m, o); show_syncpts(m, o); nvhost_debug_output(o, "---- channels ----\n"); - bus_for_each_dev(&(nvhost_bus_get())->nvhost_bus_type, NULL, o, show_channels); + bus_for_each_dev(&(nvhost_bus_get())->nvhost_bus_type, NULL, o, + show_channels); nvhost_module_idle(m->dev); } #ifdef CONFIG_DEBUG_FS -static int nvhost_debug_show(struct seq_file *s, void *unused) +static int show_channels_no_fifo(struct device *dev, void *data) +{ + struct nvhost_channel *ch; + struct nvhost_device *nvdev = to_nvhost_device(dev); + struct output *o = data; + struct nvhost_master *m; + + if (nvdev == NULL) + return 0; + + m = nvhost_get_host(nvdev); + ch = nvdev->channel; + if (ch) { + mutex_lock(&ch->reflock); + if (ch->refcount) { + mutex_lock(&ch->cdma.lock); + nvhost_get_chip_ops()->debug.show_channel_cdma(m, + ch, o, nvdev->index); + mutex_unlock(&ch->cdma.lock); + } + mutex_unlock(&ch->reflock); + } + + return 0; +} + +static void show_all_no_fifo(struct nvhost_master *m, struct output *o) +{ + nvhost_module_busy(m->dev); + + nvhost_get_chip_ops()->debug.show_mlocks(m, o); + show_syncpts(m, o); + nvhost_debug_output(o, "---- channels ----\n"); + bus_for_each_dev(&(nvhost_bus_get())->nvhost_bus_type, NULL, o, + show_channels_no_fifo); + + nvhost_module_idle(m->dev); +} + +static int nvhost_debug_show_all(struct seq_file *s, void *unused) { struct output o = { .fn = write_to_seqfile, @@ -121,6 +161,27 @@ static int nvhost_debug_show(struct seq_file *s, void *unused) show_all(s->private, &o); return 0; } +static int nvhost_debug_show(struct seq_file *s, void *unused) +{ + struct output o = { + .fn = write_to_seqfile, + .ctx = s + }; + show_all_no_fifo(s->private, &o); + return 0; +} + +static int nvhost_debug_open_all(struct inode *inode, struct file *file) +{ + return single_open(file, nvhost_debug_show_all, inode->i_private); +} + +static const struct file_operations nvhost_debug_all_fops = { + .open = nvhost_debug_open_all, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; static int nvhost_debug_open(struct inode *inode, struct file *file) { @@ -140,6 +201,8 @@ void nvhost_debug_init(struct nvhost_master *master) debugfs_create_file("status", S_IRUGO, de, master, &nvhost_debug_fops); + debugfs_create_file("status_all", S_IRUGO, de, + master, &nvhost_debug_all_fops); debugfs_create_u32("null_kickoff_pid", S_IRUGO|S_IWUSR, de, &nvhost_debug_null_kickoff_pid); diff --git a/drivers/video/tegra/host/gr3d/gr3d_t20.c b/drivers/video/tegra/host/gr3d/gr3d_t20.c index c0efac03b882..5645f5b2b0c6 100644 --- a/drivers/video/tegra/host/gr3d/gr3d_t20.c +++ b/drivers/video/tegra/host/gr3d/gr3d_t20.c @@ -138,7 +138,7 @@ static void save_push_v0(struct nvhost_hwctx *nctx, struct nvhost_cdma *cdma) nvhost_cdma_push_gather(cdma, nvhost_get_host(nctx->channel->dev)->nvmap, - p->save_buf->handle, + p->save_buf, 0, nvhost_opcode_gather(p->save_size), p->save_phys); diff --git a/drivers/video/tegra/host/gr3d/gr3d_t30.c b/drivers/video/tegra/host/gr3d/gr3d_t30.c index 93d98dfa645c..57f4c779eff8 100644 --- a/drivers/video/tegra/host/gr3d/gr3d_t30.c +++ b/drivers/video/tegra/host/gr3d/gr3d_t30.c @@ -145,7 +145,7 @@ static void save_push_v1(struct nvhost_hwctx *nctx, struct nvhost_cdma *cdma) /* gather the save buffer */ nvhost_cdma_push_gather(cdma, nvhost_get_host(nctx->channel->dev)->nvmap, - p->save_buf->handle, + p->save_buf, 0, nvhost_opcode_gather(p->save_size), p->save_phys); diff --git a/drivers/video/tegra/host/host1x/host1x_cdma.c b/drivers/video/tegra/host/host1x/host1x_cdma.c index fcb1f05f0025..4569c3d62494 100644 --- a/drivers/video/tegra/host/host1x/host1x_cdma.c +++ b/drivers/video/tegra/host/host1x/host1x_cdma.c @@ -137,7 +137,7 @@ static void push_buffer_destroy(struct push_buffer *pb) */ static void push_buffer_push_to(struct push_buffer *pb, struct nvmap_client *client, - struct nvmap_handle *handle, u32 op1, u32 op2) + struct nvmap_handle_ref *handle, u32 op1, u32 op2) { u32 cur = pb->cur; u32 *p = (u32 *)((u32)pb->mapped + cur); diff --git a/drivers/video/tegra/host/host1x/host1x_channel.c b/drivers/video/tegra/host/host1x/host1x_channel.c index 8c4a7a5c74ad..c72e6478b806 100644 --- a/drivers/video/tegra/host/host1x/host1x_channel.c +++ b/drivers/video/tegra/host/host1x/host1x_channel.c @@ -143,7 +143,7 @@ static void submit_ctxrestore(struct nvhost_job *job) /* Send restore buffer to channel */ nvhost_cdma_push_gather(&ch->cdma, host->nvmap, - nvmap_ref_to_handle(ctx->restore), + ctx->restore, 0, nvhost_opcode_gather(ctx->restore_size), ctx->restore_phys); @@ -188,7 +188,7 @@ void submit_gathers(struct nvhost_job *job) u32 op2 = job->gathers[i].mem; nvhost_cdma_push_gather(&job->ch->cdma, job->nvmap, - nvmap_id_to_handle(job->gathers[i].mem_id), + job->gathers[i].ref, job->gathers[i].offset, op1, op2); } @@ -242,22 +242,6 @@ int host1x_channel_submit(struct nvhost_job *job) goto error; } - /* remove stale waits */ - if (job->num_waitchk) { - err = nvhost_syncpt_wait_check(sp, - job->nvmap, - job->waitchk_mask, - job->waitchk, - job->num_waitchk); - if (err) { - dev_warn(&ch->dev->dev, - "nvhost_syncpt_wait_check failed: %d\n", err); - mutex_unlock(&ch->submitlock); - nvhost_module_idle(ch->dev); - goto error; - } - } - /* begin a CDMA submit */ err = nvhost_cdma_begin(&ch->cdma, job); if (err) { diff --git a/drivers/video/tegra/host/host1x/host1x_debug.c b/drivers/video/tegra/host/host1x/host1x_debug.c index 76483d82528b..7de342298c4d 100644 --- a/drivers/video/tegra/host/host1x/host1x_debug.c +++ b/drivers/video/tegra/host/host1x/host1x_debug.c @@ -28,8 +28,8 @@ #include "host1x_hardware.h" #include "nvhost_cdma.h" #include "nvhost_channel.h" -#include "../../nvmap/nvmap.h" #include "host1x_cdma.h" +#include "nvhost_job.h" #define NVHOST_DEBUG_MAX_PAGE_OFFSET 102400 @@ -160,6 +160,34 @@ static void show_channel_word(struct output *o, int *state, int *count, } } +static void do_show_channel_gather(struct output *o, + phys_addr_t phys_addr, + u32 words, struct nvhost_cdma *cdma, + phys_addr_t pin_addr, u32 *map_addr) +{ + /* Map dmaget cursor to corresponding nvmap_handle */ + u32 offset; + int state, count, i; + + offset = phys_addr - pin_addr; + /* + * Sometimes we're given different hardware address to the same + * page - in these cases the offset will get an invalid number and + * we just have to bail out. + */ + if (offset > NVHOST_DEBUG_MAX_PAGE_OFFSET) { + nvhost_debug_output(o, "[address mismatch]\n"); + } else { + /* GATHER buffer starts always with commands */ + state = NVHOST_DBG_STATE_CMD; + for (i = 0; i < words; i++) + show_channel_word(o, &state, &count, + phys_addr + i * 4, + *(map_addr + offset/4 + i), + cdma); + } +} + static void show_channel_gather(struct output *o, u32 addr, phys_addr_t phys_addr, u32 words, struct nvhost_cdma *cdma) @@ -169,81 +197,36 @@ static void show_channel_gather(struct output *o, u32 addr, struct push_buffer *pb = &cdma->push_buffer; u32 cur = addr - pb->phys; struct nvmap_client_handle *nvmap = &pb->nvmap[cur/8]; - struct nvmap_handle_ref ref; u32 *map_addr, offset; phys_addr_t pin_addr; - int state, count, i; - if (!nvmap->handle || !nvmap->client - || atomic_read(&nvmap->handle->ref) < 1) { + if (!nvmap || !nvmap->handle || !nvmap->client) { nvhost_debug_output(o, "[already deallocated]\n"); return; } - /* Create a fake nvmap_handle_ref - nvmap requires it - * but accesses only the first field - nvmap_handle */ - ref.handle = nvmap->handle; - - map_addr = nvmap_mmap(&ref); + map_addr = nvmap_mmap(nvmap->handle); if (!map_addr) { nvhost_debug_output(o, "[could not mmap]\n"); return; } /* Get base address from nvmap */ - pin_addr = nvmap_pin(nvmap->client, &ref); + pin_addr = nvmap_pin(nvmap->client, nvmap->handle); if (IS_ERR_VALUE(pin_addr)) { nvhost_debug_output(o, "[couldn't pin]\n"); - nvmap_munmap(&ref, map_addr); + nvmap_munmap(nvmap->handle, map_addr); return; } offset = phys_addr - pin_addr; - /* - * Sometimes we're given different hardware address to the same - * page - in these cases the offset will get an invalid number and - * we just have to bail out. - */ - if (offset > NVHOST_DEBUG_MAX_PAGE_OFFSET) { - nvhost_debug_output(o, "[address mismatch]\n"); - } else { - /* GATHER buffer starts always with commands */ - state = NVHOST_DBG_STATE_CMD; - for (i = 0; i < words; i++) - show_channel_word(o, &state, &count, - phys_addr + i * 4, - *(map_addr + offset/4 + i), - cdma); - } - nvmap_unpin(nvmap->client, &ref); - nvmap_munmap(&ref, map_addr); + do_show_channel_gather(o, phys_addr, words, cdma, + pin_addr, map_addr); + nvmap_unpin(nvmap->client, nvmap->handle); + nvmap_munmap(nvmap->handle, map_addr); #endif } -static void show_channel_pair(struct output *o, u32 addr, - u32 w0, u32 w1, struct nvhost_cdma *cdma) -{ - int state = NVHOST_DBG_STATE_CMD; - int count; - - show_channel_word(o, &state, &count, addr, w0, cdma); - show_channel_word(o, &state, &count, addr+4, w1, cdma); -} - -/** - * Retrieve the op pair at a slot offset from a DMA address - */ -static void cdma_peek(struct nvhost_cdma *cdma, - u32 dmaget, int slot, u32 *out) -{ - u32 offset = dmaget - cdma->push_buffer.phys; - u32 *p = cdma->push_buffer.mapped; - - offset = ((offset + slot * 8) & (PUSH_BUFFER_SIZE - 1)) >> 2; - out[0] = p[offset]; - out[1] = p[offset + 1]; -} - u32 previous_oppair(struct nvhost_cdma *cdma, u32 cur) { u32 pb = cdma->push_buffer.phys; @@ -253,6 +236,42 @@ u32 previous_oppair(struct nvhost_cdma *cdma, u32 cur) return prev; } +void show_channel_gathers(struct output *o, struct nvhost_cdma *cdma) +{ + struct nvhost_job *job; + + list_for_each_entry(job, &cdma->sync_queue, list) { + int i; + nvhost_debug_output(o, "\n%p: JOB, syncpt_id=%d, syncpt_val=%d," + " first_get=%08x, timeout=%d, ctx=%p," + " num_slots=%d, num_handles=%d\n", + job, + job->syncpt_id, + job->syncpt_end, + job->first_get, + job->timeout, + job->hwctx, + job->num_slots, + job->num_unpins); + + for (i = 0; i < job->num_gathers; i++) { + struct nvhost_job_gather *g = &job->gathers[i]; + u32 *mapped = nvmap_mmap(g->ref); + if (!mapped) { + nvhost_debug_output(o, "[could not mmap]\n"); + continue; + } + + nvhost_debug_output(o, " GATHER at %08x, %d words\n", + g->mem, g->words); + + do_show_channel_gather(o, g->mem + g->offset, + g->words, cdma, g->mem, mapped); + nvmap_munmap(g->ref, mapped); + } + } +} + static void t20_debug_show_channel_cdma(struct nvhost_master *m, struct nvhost_channel *ch, struct output *o, int chid) { @@ -261,7 +280,6 @@ static void t20_debug_show_channel_cdma(struct nvhost_master *m, u32 dmaput, dmaget, dmactrl; u32 cbstat, cbread; u32 val, base, baseval; - u32 pbw[2]; dmaput = readl(channel->aperture + HOST1X_CHANNEL_DMAPUT); dmaget = readl(channel->aperture + HOST1X_CHANNEL_DMAGET); @@ -310,9 +328,7 @@ static void t20_debug_show_channel_cdma(struct nvhost_master *m, dmaput, dmaget, dmactrl); nvhost_debug_output(o, "CBREAD %08x, CBSTAT %08x\n", cbread, cbstat); - cdma_peek(cdma, dmaget, -1, pbw); - show_channel_pair(o, previous_oppair(cdma, dmaget), - pbw[0], pbw[1], &channel->cdma); + show_channel_gathers(o, cdma); nvhost_debug_output(o, "\n"); } diff --git a/drivers/video/tegra/host/host1x/host1x_syncpt.c b/drivers/video/tegra/host/host1x/host1x_syncpt.c index b7d6587acc61..4cc8e9e212fa 100644 --- a/drivers/video/tegra/host/host1x/host1x_syncpt.c +++ b/drivers/video/tegra/host/host1x/host1x_syncpt.c @@ -103,62 +103,14 @@ static void t20_syncpt_cpu_incr(struct nvhost_syncpt *sp, u32 id) wmb(); } -/* check for old WAITs to be removed (avoiding a wrap) */ -static int t20_syncpt_wait_check(struct nvhost_syncpt *sp, - struct nvmap_client *nvmap, - u32 waitchk_mask, - struct nvhost_waitchk *wait, - int num_waitchk) +/* remove a wait pointed to by patch_addr */ +static int host1x_syncpt_patch_wait(struct nvhost_syncpt *sp, + void *patch_addr) { - u32 idx; - int err = 0; - - /* get current syncpt values */ - for (idx = 0; idx < NV_HOST1X_SYNCPT_NB_PTS; idx++) { - if (BIT(idx) & waitchk_mask) - nvhost_syncpt_update_min(sp, idx); - } - - BUG_ON(!wait && !num_waitchk); - - /* compare syncpt vs wait threshold */ - while (num_waitchk) { - u32 override; - - BUG_ON(wait->syncpt_id >= NV_HOST1X_SYNCPT_NB_PTS); - trace_nvhost_syncpt_wait_check(wait->mem, wait->offset, - wait->syncpt_id, wait->thresh); - if (nvhost_syncpt_is_expired(sp, - wait->syncpt_id, wait->thresh)) { - /* - * NULL an already satisfied WAIT_SYNCPT host method, - * by patching its args in the command stream. The - * method data is changed to reference a reserved - * (never given out or incr) NVSYNCPT_GRAPHICS_HOST - * syncpt with a matching threshold value of 0, so - * is guaranteed to be popped by the host HW. - */ - dev_dbg(&syncpt_to_dev(sp)->dev->dev, - "drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n", - wait->syncpt_id, - syncpt_op().name(sp, wait->syncpt_id), - wait->thresh, - nvhost_syncpt_read_min(sp, wait->syncpt_id)); - - /* patch the wait */ - override = nvhost_class_host_wait_syncpt( - NVSYNCPT_GRAPHICS_HOST, 0); - err = nvmap_patch_word(nvmap, - (struct nvmap_handle *)wait->mem, - wait->offset, override); - if (err) - break; - } - - wait++; - num_waitchk--; - } - return err; + u32 override = nvhost_class_host_wait_syncpt( + NVSYNCPT_GRAPHICS_HOST, 0); + __raw_writel(override, patch_addr); + return 0; } @@ -241,7 +193,7 @@ int host1x_init_syncpt_support(struct nvhost_master *host, op->syncpt.read_wait_base = t20_syncpt_read_wait_base; op->syncpt.update_min = t20_syncpt_update_min; op->syncpt.cpu_incr = t20_syncpt_cpu_incr; - op->syncpt.wait_check = t20_syncpt_wait_check; + op->syncpt.patch_wait = host1x_syncpt_patch_wait; op->syncpt.debug = t20_syncpt_debug; op->syncpt.name = t20_syncpt_name; op->syncpt.mutex_try_lock = syncpt_mutex_try_lock; diff --git a/drivers/video/tegra/host/mpe/mpe.c b/drivers/video/tegra/host/mpe/mpe.c index d8c9da7e9a76..3fe2fcd8bb50 100644 --- a/drivers/video/tegra/host/mpe/mpe.c +++ b/drivers/video/tegra/host/mpe/mpe.c @@ -502,7 +502,7 @@ static void ctxmpe_save_push(struct nvhost_hwctx *nctx, struct host1x_hwctx_handler *h = host1x_hwctx_handler(ctx); nvhost_cdma_push_gather(cdma, nvhost_get_host(nctx->channel->dev)->nvmap, - h->save_buf->handle, + h->save_buf, 0, nvhost_opcode_gather(h->save_size), h->save_phys); diff --git a/drivers/video/tegra/host/nvhost_cdma.c b/drivers/video/tegra/host/nvhost_cdma.c index a72e18f16ac7..c87415bf5ac2 100644 --- a/drivers/video/tegra/host/nvhost_cdma.c +++ b/drivers/video/tegra/host/nvhost_cdma.c @@ -53,6 +53,18 @@ static void add_to_sync_queue(struct nvhost_cdma *cdma, job->num_slots = nr_slots; nvhost_job_get(job); list_add_tail(&job->list, &cdma->sync_queue); + + switch (job->priority) { + case NVHOST_PRIORITY_HIGH: + cdma->high_prio_count++; + break; + case NVHOST_PRIORITY_MEDIUM: + cdma->med_prio_count++; + break; + case NVHOST_PRIORITY_LOW: + cdma->low_prio_count++; + break; + } } /** @@ -200,6 +212,19 @@ static void update_cdma_locked(struct nvhost_cdma *cdma) } list_del(&job->list); + + switch (job->priority) { + case NVHOST_PRIORITY_HIGH: + cdma->high_prio_count--; + break; + case NVHOST_PRIORITY_MEDIUM: + cdma->med_prio_count--; + break; + case NVHOST_PRIORITY_LOW: + cdma->low_prio_count--; + break; + } + nvhost_job_put(job); } @@ -371,15 +396,13 @@ int nvhost_cdma_begin(struct nvhost_cdma *cdma, struct nvhost_job *job) } static void trace_write_gather(struct nvhost_cdma *cdma, - struct nvmap_handle *handle, + struct nvmap_handle_ref *ref, u32 offset, u32 words) { - struct nvmap_handle_ref ref; void *mem = NULL; if (nvhost_debug_trace_cmdbuf) { - ref.handle = handle; - mem = nvmap_mmap(&ref); + mem = nvmap_mmap(ref); if (IS_ERR_OR_NULL(mem)) mem = NULL; }; @@ -393,12 +416,12 @@ static void trace_write_gather(struct nvhost_cdma *cdma, for (i = 0; i < words; i += TRACE_MAX_LENGTH) { trace_nvhost_cdma_push_gather( cdma_to_channel(cdma)->dev->name, - (u32)handle, + (u32)ref->handle, min(words - i, TRACE_MAX_LENGTH), offset + i * sizeof(u32), mem); } - nvmap_munmap(&ref, mem); + nvmap_munmap(ref, mem); } } @@ -421,7 +444,7 @@ void nvhost_cdma_push(struct nvhost_cdma *cdma, u32 op1, u32 op2) */ void nvhost_cdma_push_gather(struct nvhost_cdma *cdma, struct nvmap_client *client, - struct nvmap_handle *handle, + struct nvmap_handle_ref *handle, u32 offset, u32 op1, u32 op2) { u32 slots_free = cdma->slots_free; @@ -468,6 +491,12 @@ void nvhost_cdma_end(struct nvhost_cdma *cdma, if (job->timeout && was_idle) cdma_start_timer_locked(cdma, job); + trace_nvhost_cdma_end(job->ch->dev->name, + job->priority, + job->ch->cdma.high_prio_count, + job->ch->cdma.med_prio_count, + job->ch->cdma.low_prio_count); + mutex_unlock(&cdma->lock); } @@ -492,6 +521,8 @@ int nvhost_cdma_flush(struct nvhost_cdma *cdma, int timeout) unsigned int space, err = 0; unsigned long end_jiffies = jiffies + msecs_to_jiffies(timeout); + trace_nvhost_cdma_flush(cdma_to_channel(cdma)->dev->name, timeout); + /* * Wait for at most timeout ms. Recalculate timeout at each iteration * to better keep within given timeout. diff --git a/drivers/video/tegra/host/nvhost_cdma.h b/drivers/video/tegra/host/nvhost_cdma.h index e6f51179150f..2056774a7bc7 100644 --- a/drivers/video/tegra/host/nvhost_cdma.h +++ b/drivers/video/tegra/host/nvhost_cdma.h @@ -48,7 +48,7 @@ struct nvhost_job; struct nvmap_client_handle { struct nvmap_client *client; - struct nvmap_handle *handle; + struct nvmap_handle_ref *handle; }; struct push_buffer { @@ -99,6 +99,9 @@ struct nvhost_cdma { struct buffer_timeout timeout; /* channel's timeout state/wq */ bool running; bool torndown; + int high_prio_count; + int med_prio_count; + int low_prio_count; }; #define cdma_to_channel(cdma) container_of(cdma, struct nvhost_channel, cdma) @@ -113,7 +116,7 @@ int nvhost_cdma_begin(struct nvhost_cdma *cdma, struct nvhost_job *job); void nvhost_cdma_push(struct nvhost_cdma *cdma, u32 op1, u32 op2); void nvhost_cdma_push_gather(struct nvhost_cdma *cdma, struct nvmap_client *client, - struct nvmap_handle *handle, u32 offset, u32 op1, u32 op2); + struct nvmap_handle_ref *handle, u32 offset, u32 op1, u32 op2); void nvhost_cdma_end(struct nvhost_cdma *cdma, struct nvhost_job *job); void nvhost_cdma_update(struct nvhost_cdma *cdma); diff --git a/drivers/video/tegra/host/nvhost_channel.c b/drivers/video/tegra/host/nvhost_channel.c index ef8886fe4652..ad303cf0a22d 100644 --- a/drivers/video/tegra/host/nvhost_channel.c +++ b/drivers/video/tegra/host/nvhost_channel.c @@ -51,10 +51,26 @@ int nvhost_channel_init(struct nvhost_channel *ch, int nvhost_channel_submit(struct nvhost_job *job) { - /* Low priority submits wait until sync queue is empty. Ignores result - * from nvhost_cdma_flush, as we submit either when push buffer is - * empty or when we reach the timeout. */ - if (job->priority < NVHOST_PRIORITY_MEDIUM) + /* + * Check if queue has higher priority jobs running. If so, wait until + * queue is empty. Ignores result from nvhost_cdma_flush, as we submit + * either when push buffer is empty or when we reach the timeout. + */ + int higher_count = 0; + + switch (job->priority) { + case NVHOST_PRIORITY_HIGH: + higher_count = 0; + break; + case NVHOST_PRIORITY_MEDIUM: + higher_count = job->ch->cdma.high_prio_count; + break; + case NVHOST_PRIORITY_LOW: + higher_count = job->ch->cdma.high_prio_count + + job->ch->cdma.med_prio_count; + break; + } + if (higher_count > 0) (void)nvhost_cdma_flush(&job->ch->cdma, NVHOST_CHANNEL_LOW_PRIO_MAX_WAIT); diff --git a/drivers/video/tegra/host/nvhost_channel.h b/drivers/video/tegra/host/nvhost_channel.h index eac51731547b..b3a904d5a3ee 100644 --- a/drivers/video/tegra/host/nvhost_channel.h +++ b/drivers/video/tegra/host/nvhost_channel.h @@ -31,18 +31,10 @@ #define NVHOST_MAX_POWERGATE_IDS 2 struct nvhost_master; -struct nvhost_waitchk; struct nvhost_device; struct nvhost_channel; struct nvhost_hwctx; -struct nvhost_channel_gather { - u32 words; - phys_addr_t mem; - u32 mem_id; - int offset; -}; - struct nvhost_channel { int refcount; int chid; diff --git a/drivers/video/tegra/host/nvhost_intr.c b/drivers/video/tegra/host/nvhost_intr.c index ba821f694cb4..af2e3ad1bdb5 100644 --- a/drivers/video/tegra/host/nvhost_intr.c +++ b/drivers/video/tegra/host/nvhost_intr.c @@ -128,12 +128,16 @@ static void action_submit_complete(struct nvhost_waitlist *waiter) struct nvhost_channel *channel = waiter->data; int nr_completed = waiter->count; + nvhost_cdma_update(&channel->cdma); + nvhost_module_idle_mult(channel->dev, nr_completed); + /* Add nr_completed to trace */ trace_nvhost_channel_submit_complete(channel->dev->name, - nr_completed, waiter->thresh); + nr_completed, waiter->thresh, + channel->cdma.high_prio_count, + channel->cdma.med_prio_count, + channel->cdma.low_prio_count); - nvhost_cdma_update(&channel->cdma); - nvhost_module_idle_mult(channel->dev, nr_completed); } static void action_ctxsave(struct nvhost_waitlist *waiter) diff --git a/drivers/video/tegra/host/nvhost_job.c b/drivers/video/tegra/host/nvhost_job.c index 71f2ab0e751f..e029449b6184 100644 --- a/drivers/video/tegra/host/nvhost_job.c +++ b/drivers/video/tegra/host/nvhost_job.c @@ -23,9 +23,11 @@ #include <linux/err.h> #include <linux/vmalloc.h> #include <linux/nvmap.h> +#include <trace/events/nvhost.h> #include "nvhost_channel.h" #include "nvhost_job.h" #include "nvhost_hwctx.h" +#include "nvhost_syncpt.h" #include "dev.h" /* Magic to use to fill freed handle slots */ @@ -33,128 +35,44 @@ static int job_size(struct nvhost_submit_hdr_ext *hdr) { - int num_pins = hdr ? (hdr->num_relocs + hdr->num_cmdbufs)*2 : 0; + int num_relocs = hdr ? hdr->num_relocs : 0; int num_waitchks = hdr ? hdr->num_waitchks : 0; + int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0; + int num_unpins = num_cmdbufs + num_relocs; return sizeof(struct nvhost_job) - + num_pins * sizeof(struct nvmap_pinarray_elem) - + num_pins * sizeof(struct nvmap_handle *) - + num_waitchks * sizeof(struct nvhost_waitchk); -} - -static int gather_size(int num_cmdbufs) -{ - return num_cmdbufs * sizeof(struct nvhost_channel_gather); -} - -static void free_gathers(struct nvhost_job *job) -{ - if (job->gathers) { - nvmap_munmap(job->gather_mem, job->gathers); - job->gathers = NULL; - } - if (job->gather_mem) { - nvmap_free(job->nvmap, job->gather_mem); - job->gather_mem = NULL; - } -} - -static int alloc_gathers(struct nvhost_job *job, - int num_cmdbufs) -{ - int err = 0; - - job->gather_mem = NULL; - job->gathers = NULL; - job->gather_mem_size = 0; - - if (num_cmdbufs) { - /* Allocate memory */ - job->gather_mem = nvmap_alloc(job->nvmap, - gather_size(num_cmdbufs), - 32, NVMAP_HANDLE_CACHEABLE, 0); - if (IS_ERR_OR_NULL(job->gather_mem)) { - err = job->gather_mem ? PTR_ERR(job->gather_mem) : -ENOMEM; - job->gather_mem = NULL; - goto error; - } - job->gather_mem_size = gather_size(num_cmdbufs); - - /* Map memory to kernel */ - job->gathers = nvmap_mmap(job->gather_mem); - if (IS_ERR_OR_NULL(job->gathers)) { - err = job->gathers ? PTR_ERR(job->gathers) : -ENOMEM; - job->gathers = NULL; - goto error; - } - } - - return 0; - -error: - free_gathers(job); - return err; -} - -static int realloc_gathers(struct nvhost_job *oldjob, - struct nvhost_job *newjob, - int num_cmdbufs) -{ - int err = 0; - - /* Check if we can reuse gather buffer */ - if (oldjob->gather_mem_size < gather_size(num_cmdbufs) - || oldjob->nvmap != newjob->nvmap) { - free_gathers(oldjob); - err = alloc_gathers(newjob, num_cmdbufs); - } else { - newjob->gather_mem = oldjob->gather_mem; - newjob->gathers = oldjob->gathers; - newjob->gather_mem_size = oldjob->gather_mem_size; - - oldjob->gather_mem = NULL; - oldjob->gathers = NULL; - oldjob->gather_mem_size = 0; - } - return err; + + num_relocs * sizeof(struct nvhost_reloc) + + num_relocs * sizeof(struct nvhost_reloc_shift) + + num_unpins * sizeof(struct nvmap_handle_ref *) + + num_waitchks * sizeof(struct nvhost_waitchk) + + num_cmdbufs * sizeof(struct nvhost_job_gather); } static void init_fields(struct nvhost_job *job, struct nvhost_submit_hdr_ext *hdr, int priority, int clientid) { - int num_pins = hdr ? (hdr->num_relocs + hdr->num_cmdbufs)*2 : 0; + int num_relocs = hdr ? hdr->num_relocs : 0; int num_waitchks = hdr ? hdr->num_waitchks : 0; + int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0; + int num_unpins = num_cmdbufs + num_relocs; void *mem = job; /* First init state to zero */ - job->num_gathers = 0; - job->num_pins = 0; - job->num_unpins = 0; - job->num_waitchk = 0; - job->waitchk_mask = 0; - job->syncpt_id = 0; - job->syncpt_incrs = 0; - job->syncpt_end = 0; job->priority = priority; job->clientid = clientid; - job->null_kickoff = false; - job->first_get = 0; - job->num_slots = 0; /* Redistribute memory to the structs */ mem += sizeof(struct nvhost_job); - if (num_pins) { - job->pinarray = mem; - mem += num_pins * sizeof(struct nvmap_pinarray_elem); - job->unpins = mem; - mem += num_pins * sizeof(struct nvmap_handle *); - } else { - job->pinarray = NULL; - job->unpins = NULL; - } - + job->relocarray = num_relocs ? mem : NULL; + mem += num_relocs * sizeof(struct nvhost_reloc); + job->relocshiftarray = num_relocs ? mem : NULL; + mem += num_relocs * sizeof(struct nvhost_reloc_shift); + job->unpins = num_unpins ? mem : NULL; + mem += num_unpins * sizeof(struct nvmap_handle_ref *); job->waitchk = num_waitchks ? mem : NULL; + mem += num_waitchks * sizeof(struct nvhost_waitchk); + job->gathers = num_cmdbufs ? mem : NULL; /* Copy information from header */ if (hdr) { @@ -172,8 +90,6 @@ struct nvhost_job *nvhost_job_alloc(struct nvhost_channel *ch, int clientid) { struct nvhost_job *job = NULL; - int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0; - int err = 0; job = vzalloc(job_size(hdr)); if (!job) @@ -186,10 +102,6 @@ struct nvhost_job *nvhost_job_alloc(struct nvhost_channel *ch, hwctx->h->get(hwctx); job->nvmap = nvmap ? nvmap_client_get(nvmap) : NULL; - err = alloc_gathers(job, num_cmdbufs); - if (err) - goto error; - init_fields(job, hdr, priority, clientid); return job; @@ -200,46 +112,6 @@ error: return NULL; } -struct nvhost_job *nvhost_job_realloc( - struct nvhost_job *oldjob, - struct nvhost_hwctx *hwctx, - struct nvhost_submit_hdr_ext *hdr, - struct nvmap_client *nvmap, - int priority, int clientid) -{ - struct nvhost_job *newjob = NULL; - int num_cmdbufs = hdr ? hdr->num_cmdbufs : 0; - int err = 0; - - newjob = vzalloc(job_size(hdr)); - if (!newjob) - goto error; - kref_init(&newjob->ref); - newjob->ch = oldjob->ch; - newjob->hwctx = hwctx; - if (hwctx) - newjob->hwctx->h->get(newjob->hwctx); - newjob->timeout = oldjob->timeout; - newjob->nvmap = nvmap ? nvmap_client_get(nvmap) : NULL; - - err = realloc_gathers(oldjob, newjob, num_cmdbufs); - if (err) - goto error; - - nvhost_job_put(oldjob); - - init_fields(newjob, hdr, priority, clientid); - - return newjob; - -error: - if (newjob) - nvhost_job_put(newjob); - if (oldjob) - nvhost_job_put(oldjob); - return NULL; -} - void nvhost_job_get(struct nvhost_job *job) { kref_get(&job->ref); @@ -253,10 +125,6 @@ static void job_free(struct kref *ref) job->hwctxref->h->put(job->hwctxref); if (job->hwctx) job->hwctx->h->put(job->hwctx); - if (job->gathers) - nvmap_munmap(job->gather_mem, job->gathers); - if (job->gather_mem) - nvmap_free(job->nvmap, job->gather_mem); if (job->nvmap) nvmap_client_put(job->nvmap); vfree(job); @@ -280,42 +148,177 @@ void nvhost_job_put(struct nvhost_job *job) void nvhost_job_add_gather(struct nvhost_job *job, u32 mem_id, u32 words, u32 offset) { - struct nvmap_pinarray_elem *pin; - struct nvhost_channel_gather *cur_gather = + struct nvhost_job_gather *cur_gather = &job->gathers[job->num_gathers]; - pin = &job->pinarray[job->num_pins++]; - pin->patch_mem = (u32)nvmap_ref_to_handle(job->gather_mem); - pin->patch_offset = (void *)&(cur_gather->mem) - (void *)job->gathers; - pin->pin_mem = nvmap_convert_handle_u2k(mem_id); - pin->pin_offset = offset; cur_gather->words = words; cur_gather->mem_id = mem_id; cur_gather->offset = offset; job->num_gathers += 1; } -int nvhost_job_pin(struct nvhost_job *job) +static int do_relocs(struct nvhost_job *job, u32 cmdbuf_mem, void *cmdbuf_addr) { - int err = 0; + phys_addr_t target_phys; + int i; + u32 mem_id = 0; + struct nvmap_handle_ref *target_ref = NULL; + + /* pin & patch the relocs for one gather */ + for (i = 0; i < job->num_relocs; i++) { + struct nvhost_reloc *reloc = &job->relocarray[i]; + struct nvhost_reloc_shift *shift = &job->relocshiftarray[i]; + + /* skip all other gathers */ + if (cmdbuf_mem != reloc->cmdbuf_mem) + continue; + + /* check if pin-mem is same as previous */ + if (reloc->target != mem_id) { + target_ref = nvmap_duplicate_handle_id(job->nvmap, + reloc->target); + if (IS_ERR(target_ref)) + return PTR_ERR(target_ref); + + target_phys = nvmap_pin(job->nvmap, target_ref); + if (IS_ERR((void *)target_phys)) { + nvmap_free(job->nvmap, target_ref); + return target_phys; + } + + mem_id = reloc->target; + job->unpins[job->num_unpins++] = target_ref; + } - /* pin mem handles and patch physical addresses */ - job->num_unpins = nvmap_pin_array(job->nvmap, - nvmap_ref_to_handle(job->gather_mem), - job->pinarray, job->num_pins, - job->unpins); - if (job->num_unpins < 0) - err = job->num_unpins; + __raw_writel( + (target_phys + reloc->target_offset) >> shift->shift, + (cmdbuf_addr + reloc->cmdbuf_offset)); + + /* Different gathers might have same mem_id. This ensures we + * perform reloc only once per gather memid. */ + reloc->cmdbuf_mem = 0; + } + + return 0; +} + +/* + * Check driver supplied waitchk structs for syncpt thresholds + * that have already been satisfied and NULL the comparison (to + * avoid a wrap condition in the HW). + */ +static int do_waitchks(struct nvhost_job *job, struct nvhost_syncpt *sp, + u32 patch_mem, void *patch_addr) +{ + int i; + + /* compare syncpt vs wait threshold */ + for (i = 0; i < job->num_waitchk; i++) { + struct nvhost_waitchk *wait = &job->waitchk[i]; + + /* skip all other gathers */ + if (patch_mem != wait->mem) + continue; + + trace_nvhost_syncpt_wait_check(wait->mem, wait->offset, + wait->syncpt_id, wait->thresh, + nvhost_syncpt_read(sp, wait->syncpt_id)); + if (nvhost_syncpt_is_expired(sp, + wait->syncpt_id, wait->thresh)) { + /* + * NULL an already satisfied WAIT_SYNCPT host method, + * by patching its args in the command stream. The + * method data is changed to reference a reserved + * (never given out or incr) NVSYNCPT_GRAPHICS_HOST + * syncpt with a matching threshold value of 0, so + * is guaranteed to be popped by the host HW. + */ + dev_dbg(&syncpt_to_dev(sp)->dev->dev, + "drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n", + wait->syncpt_id, + syncpt_op().name(sp, wait->syncpt_id), + wait->thresh, + nvhost_syncpt_read_min(sp, wait->syncpt_id)); + + /* patch the wait */ + nvhost_syncpt_patch_wait(sp, + (patch_addr + wait->offset)); + } + + wait->mem = 0; + } + return 0; +} + +int nvhost_job_pin(struct nvhost_job *job, struct nvhost_syncpt *sp) +{ + int err = 0, i = 0; + phys_addr_t gather_phys = 0; + void *gather_addr = NULL; + unsigned long waitchk_mask = job->waitchk_mask; + + /* get current syncpt values for waitchk */ + for_each_set_bit(i, &waitchk_mask, sizeof(job->waitchk_mask)) + nvhost_syncpt_update_min(sp, i); + + /* pin gathers */ + for (i = 0; i < job->num_gathers; i++) { + struct nvhost_job_gather *g = &job->gathers[i]; + + /* process each gather mem only once */ + if (!g->ref) { + g->ref = nvmap_duplicate_handle_id(job->nvmap, + job->gathers[i].mem_id); + if (IS_ERR(g->ref)) { + err = PTR_ERR(g->ref); + g->ref = NULL; + break; + } + + gather_phys = nvmap_pin(job->nvmap, g->ref); + if (IS_ERR((void *)gather_phys)) { + nvmap_free(job->nvmap, g->ref); + err = gather_phys; + break; + } + + /* store the gather ref into unpin array */ + job->unpins[job->num_unpins++] = g->ref; + + gather_addr = nvmap_mmap(g->ref); + if (!gather_addr) { + err = -ENOMEM; + break; + } + + err = do_relocs(job, g->mem_id, gather_addr); + if (!err) + err = do_waitchks(job, sp, + g->mem_id, gather_addr); + nvmap_munmap(g->ref, gather_addr); + + if (err) + break; + } + g->mem = gather_phys + g->offset; + } + wmb(); return err; } void nvhost_job_unpin(struct nvhost_job *job) { - nvmap_unpin_handles(job->nvmap, job->unpins, - job->num_unpins); + int i; + + for (i = 0; i < job->num_unpins; i++) { + nvmap_unpin(job->nvmap, job->unpins[i]); + nvmap_free(job->nvmap, job->unpins[i]); + } + memset(job->unpins, BAD_MAGIC, - job->num_unpins * sizeof(struct nvmap_handle *)); + job->num_unpins * sizeof(struct nvmap_handle_ref *)); + job->num_unpins = 0; } /** diff --git a/drivers/video/tegra/host/nvhost_job.h b/drivers/video/tegra/host/nvhost_job.h index ad9d1af60da1..ec1366337279 100644 --- a/drivers/video/tegra/host/nvhost_job.h +++ b/drivers/video/tegra/host/nvhost_job.h @@ -27,7 +27,15 @@ struct nvhost_channel; struct nvhost_hwctx; struct nvmap_client; struct nvhost_waitchk; -struct nvmap_handle; +struct nvhost_syncpt; + +struct nvhost_job_gather { + u32 words; + phys_addr_t mem; + u32 mem_id; + int offset; + struct nvmap_handle_ref *ref; +}; /* * Each submit is tracked as a nvhost_job. @@ -50,10 +58,8 @@ struct nvhost_job { struct nvmap_client *nvmap; /* Gathers and their memory */ - struct nvmap_handle_ref *gather_mem; - struct nvhost_channel_gather *gathers; + struct nvhost_job_gather *gathers; int num_gathers; - int gather_mem_size; /* Wait checks to be processed at submit time */ struct nvhost_waitchk *waitchk; @@ -61,9 +67,10 @@ struct nvhost_job { u32 waitchk_mask; /* Array of handles to be pinned & unpinned */ - struct nvmap_pinarray_elem *pinarray; - int num_pins; - struct nvmap_handle **unpins; + struct nvhost_reloc *relocarray; + struct nvhost_reloc_shift *relocshiftarray; + int num_relocs; + struct nvmap_handle_ref **unpins; int num_unpins; /* Sync point id, number of increments and end related to the submit */ @@ -99,17 +106,6 @@ struct nvhost_job *nvhost_job_alloc(struct nvhost_channel *ch, int priority, int clientid); /* - * Allocate memory for a job. Just enough memory will be allocated to - * accomodate the submit announced in submit header. Gather memory from - * oldjob will be reused, and nvhost_job_put() will be called to it. - */ -struct nvhost_job *nvhost_job_realloc(struct nvhost_job *oldjob, - struct nvhost_hwctx *hwctx, - struct nvhost_submit_hdr_ext *hdr, - struct nvmap_client *nvmap, - int priority, int clientid); - -/* * Add a gather to a job. */ void nvhost_job_add_gather(struct nvhost_job *job, @@ -134,8 +130,11 @@ void nvhost_job_put(struct nvhost_job *job); * Pin memory related to job. This handles relocation of addresses to the * host1x address space. Handles both the gather memory and any other memory * referred to from the gather buffers. + * + * Handles also patching out host waits that would wait for an expired sync + * point value. */ -int nvhost_job_pin(struct nvhost_job *job); +int nvhost_job_pin(struct nvhost_job *job, struct nvhost_syncpt *sp); /* * Unpin memory related to job. diff --git a/drivers/video/tegra/host/nvhost_syncpt.c b/drivers/video/tegra/host/nvhost_syncpt.c index 4835d22881b8..7550512b0214 100644 --- a/drivers/video/tegra/host/nvhost_syncpt.c +++ b/drivers/video/tegra/host/nvhost_syncpt.c @@ -74,7 +74,7 @@ u32 nvhost_syncpt_update_min(struct nvhost_syncpt *sp, u32 id) BUG_ON(!syncpt_op().update_min); - return syncpt_op().update_min(sp, id); + val = syncpt_op().update_min(sp, id); trace_nvhost_syncpt_update_min(id, val); return val; @@ -130,6 +130,19 @@ void nvhost_syncpt_incr(struct nvhost_syncpt *sp, u32 id) } /** + * Updated sync point form hardware, and returns true if syncpoint is expired, + * false if we may need to wait + */ +static bool syncpt_update_min_is_expired( + struct nvhost_syncpt *sp, + u32 id, + u32 thresh) +{ + syncpt_op().update_min(sp, id); + return nvhost_syncpt_is_expired(sp, id, thresh); +} + +/** * Main entrypoint for syncpoint value waits. */ int nvhost_syncpt_wait_timeout(struct nvhost_syncpt *sp, u32 id, @@ -190,9 +203,9 @@ int nvhost_syncpt_wait_timeout(struct nvhost_syncpt *sp, u32 id, while (timeout) { u32 check = min_t(u32, SYNCPT_CHECK_PERIOD, timeout); int remain = wait_event_interruptible_timeout(wq, - nvhost_syncpt_is_expired(sp, id, thresh), + syncpt_update_min_is_expired(sp, id, thresh), check); - if (remain > 0) { + if (remain > 0 || nvhost_syncpt_is_expired(sp, id, thresh)) { if (value) *value = nvhost_syncpt_read_min(sp, id); err = 0; @@ -317,15 +330,10 @@ void nvhost_mutex_unlock(struct nvhost_syncpt *sp, int idx) atomic_dec(&sp->lock_counts[idx]); } -/* check for old WAITs to be removed (avoiding a wrap) */ -int nvhost_syncpt_wait_check(struct nvhost_syncpt *sp, - struct nvmap_client *nvmap, - u32 waitchk_mask, - struct nvhost_waitchk *wait, - int num_waitchk) +/* remove a wait pointed to by patch_addr */ +int nvhost_syncpt_patch_wait(struct nvhost_syncpt *sp, void *patch_addr) { - return syncpt_op().wait_check(sp, nvmap, - waitchk_mask, wait, num_waitchk); + return syncpt_op().patch_wait(sp, patch_addr); } /* Displays the current value of the sync point via sysfs */ diff --git a/drivers/video/tegra/host/nvhost_syncpt.h b/drivers/video/tegra/host/nvhost_syncpt.h index b770ed91c76c..b58921bffa9c 100644 --- a/drivers/video/tegra/host/nvhost_syncpt.h +++ b/drivers/video/tegra/host/nvhost_syncpt.h @@ -136,23 +136,7 @@ static inline int nvhost_syncpt_wait(struct nvhost_syncpt *sp, u32 id, u32 thres MAX_SCHEDULE_TIMEOUT, NULL); } -/* - * Check driver supplied waitchk structs for syncpt thresholds - * that have already been satisfied and NULL the comparison (to - * avoid a wrap condition in the HW). - * - * @param: sp - global shadowed syncpt struct - * @param: nvmap - needed to access command buffer - * @param: mask - bit mask of syncpt IDs referenced in WAITs - * @param: wait - start of filled in array of waitchk structs - * @param: waitend - end ptr (one beyond last valid waitchk) - */ -struct nvhost_waitchk; -int nvhost_syncpt_wait_check(struct nvhost_syncpt *sp, - struct nvmap_client *nvmap, - u32 mask, - struct nvhost_waitchk *wait, - int num_waitchk); +int nvhost_syncpt_patch_wait(struct nvhost_syncpt *sp, void *patch_addr); void nvhost_syncpt_debug(struct nvhost_syncpt *sp); diff --git a/drivers/video/tegra/host/t30/t30.c b/drivers/video/tegra/host/t30/t30.c index 257ba0849277..b2768741546a 100644 --- a/drivers/video/tegra/host/t30/t30.c +++ b/drivers/video/tegra/host/t30/t30.c @@ -95,7 +95,7 @@ struct nvhost_device t30_devices[] = { .waitbases = BIT(NVWAITBASE_2D_0) | BIT(NVWAITBASE_2D_1), .modulemutexes = BIT(NVMODMUTEX_2D_FULL) | BIT(NVMODMUTEX_2D_SIMPLE) | BIT(NVMODMUTEX_2D_SB_A) | BIT(NVMODMUTEX_2D_SB_B), - .clocks = { {"gr2d", UINT_MAX}, + .clocks = { {"gr2d", 0}, {"epp", 0}, {"emc", 300000000} }, NVHOST_MODULE_NO_POWERGATE_IDS, diff --git a/drivers/video/tegra/nvmap/nvmap.c b/drivers/video/tegra/nvmap/nvmap.c index a0c4156668e5..b7fd695d04ee 100644 --- a/drivers/video/tegra/nvmap/nvmap.c +++ b/drivers/video/tegra/nvmap/nvmap.c @@ -352,225 +352,6 @@ static phys_addr_t handle_phys(struct nvmap_handle *h) return addr; } -/* stores the physical address (+offset) of each handle relocation entry - * into its output location. see nvmap_pin_array for more details. - * - * each entry in arr (i.e., each relocation request) specifies two handles: - * the handle to pin (pin), and the handle where the address of pin should be - * written (patch). in pseudocode, this loop basically looks like: - * - * for (i = 0; i < nr; i++) { - * (pin, pin_offset, patch, patch_offset) = arr[i]; - * patch[patch_offset] = address_of(pin) + pin_offset; - * } - */ -static int nvmap_reloc_pin_array(struct nvmap_client *client, - const struct nvmap_pinarray_elem *arr, - int nr, struct nvmap_handle *gather) -{ - struct nvmap_handle *last_patch = NULL; - unsigned int last_pfn = 0; - pte_t **pte; - void *addr; - int i; - - pte = nvmap_alloc_pte(client->dev, &addr); - if (IS_ERR(pte)) - return PTR_ERR(pte); - - for (i = 0; i < nr; i++) { - struct nvmap_handle *patch; - struct nvmap_handle *pin; - phys_addr_t reloc_addr; - phys_addr_t phys; - unsigned int pfn; - - /* all of the handles are validated and get'ted prior to - * calling this function, so casting is safe here */ - pin = (struct nvmap_handle *)arr[i].pin_mem; - - if (arr[i].patch_mem == (unsigned long)last_patch) { - patch = last_patch; - } else if (arr[i].patch_mem == (unsigned long)gather) { - patch = gather; - } else { - if (last_patch) - nvmap_handle_put(last_patch); - - patch = nvmap_get_handle_id(client, arr[i].patch_mem); - if (!patch) { - nvmap_free_pte(client->dev, pte); - return -EPERM; - } - last_patch = patch; - } - - if (patch->heap_pgalloc) { - unsigned int page = arr[i].patch_offset >> PAGE_SHIFT; - phys = page_to_phys(patch->pgalloc.pages[page]); - phys += (arr[i].patch_offset & ~PAGE_MASK); - } else { - phys = patch->carveout->base + arr[i].patch_offset; - } - - pfn = __phys_to_pfn(phys); - if (pfn != last_pfn) { - pgprot_t prot = nvmap_pgprot(patch, pgprot_kernel); - phys_addr_t kaddr = (phys_addr_t)addr; - set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot)); - flush_tlb_kernel_page(kaddr); - last_pfn = pfn; - } - - reloc_addr = handle_phys(pin) + arr[i].pin_offset; - reloc_addr >>= arr[i].reloc_shift; - __raw_writel(reloc_addr, addr + (phys & ~PAGE_MASK)); - } - - nvmap_free_pte(client->dev, pte); - - if (last_patch) - nvmap_handle_put(last_patch); - - wmb(); - - return 0; -} - -static int nvmap_validate_get_pin_array(struct nvmap_client *client, - const struct nvmap_pinarray_elem *arr, - int nr, struct nvmap_handle **h) -{ - int i; - int ret = 0; - int count = 0; - - nvmap_ref_lock(client); - - for (i = 0; i < nr; i++) { - struct nvmap_handle_ref *ref; - - if (need_resched()) { - nvmap_ref_unlock(client); - schedule(); - nvmap_ref_lock(client); - } - - ref = _nvmap_validate_id_locked(client, arr[i].pin_mem); - - if (!ref) - nvmap_warn(client, "falied to validate id\n"); - else if (!ref->handle) - nvmap_warn(client, "id had no associated handle\n"); - else if (!ref->handle->alloc) - nvmap_warn(client, "handle had no allocation\n"); - - if (!ref || !ref->handle || !ref->handle->alloc) { - ret = -EPERM; - break; - } - - /* a handle may be referenced multiple times in arr, but - * it will only be pinned once; this ensures that the - * minimum number of sync-queue slots in the host driver - * are dedicated to storing unpin lists, which allows - * for greater parallelism between the CPU and graphics - * processor */ - if (ref->handle->flags & NVMAP_HANDLE_VISITED) - continue; - - ref->handle->flags |= NVMAP_HANDLE_VISITED; - - h[count] = nvmap_handle_get(ref->handle); - BUG_ON(!h[count]); - count++; - } - - nvmap_ref_unlock(client); - - if (ret) { - for (i = 0; i < count; i++) { - h[i]->flags &= ~NVMAP_HANDLE_VISITED; - nvmap_handle_put(h[i]); - } - } - - return ret ?: count; -} - -/* a typical mechanism host1x clients use for using the Tegra graphics - * processor is to build a command buffer which contains relocatable - * memory handle commands, and rely on the kernel to convert these in-place - * to addresses which are understood by the GPU hardware. - * - * this is implemented by having clients provide a sideband array - * of relocatable handles (+ offsets) and the location in the command - * buffer handle to patch with the GPU address when the client submits - * its command buffer to the host1x driver. - * - * the host driver also uses this relocation mechanism internally to - * relocate the client's (unpinned) command buffers into host-addressable - * memory. - * - * @client: nvmap_client which should be used for validation; should be - * owned by the process which is submitting command buffers - * @gather: special handle for relocated command buffer outputs used - * internally by the host driver. if this handle is encountered - * as an output handle in the relocation array, it is assumed - * to be a known-good output and is not validated. - * @arr: array of ((relocatable handle, offset), (output handle, offset)) - * tuples. - * @nr: number of entries in arr - * @unique_arr: list of nvmap_handle objects which were pinned by - * nvmap_pin_array. must be unpinned by the caller after the - * command buffers referenced in gather have completed. - */ -int nvmap_pin_array(struct nvmap_client *client, struct nvmap_handle *gather, - const struct nvmap_pinarray_elem *arr, int nr, - struct nvmap_handle **unique_arr) -{ - int count = 0; - int ret = 0; - int i; - - if (mutex_lock_interruptible(&client->share->pin_lock)) { - nvmap_warn(client, "%s interrupted when acquiring pin lock\n", - current->group_leader->comm); - return -EINTR; - } - - count = nvmap_validate_get_pin_array(client, arr, nr, unique_arr); - if (count < 0) { - mutex_unlock(&client->share->pin_lock); - nvmap_warn(client, "failed to validate pin array\n"); - return count; - } - - for (i = 0; i < count; i++) - unique_arr[i]->flags &= ~NVMAP_HANDLE_VISITED; - - ret = wait_pin_array_locked(client, unique_arr, count); - - mutex_unlock(&client->share->pin_lock); - - if (!ret) - ret = nvmap_reloc_pin_array(client, arr, nr, gather); - - if (WARN_ON(ret)) { - for (i = 0; i < count; i++) - nvmap_handle_put(unique_arr[i]); - return ret; - } else { - for (i = 0; i < count; i++) { - if (unique_arr[i]->heap_pgalloc && - unique_arr[i]->pgalloc.dirty) - map_iovmm_area(unique_arr[i]); - } - } - - return count; -} - phys_addr_t nvmap_pin(struct nvmap_client *client, struct nvmap_handle_ref *ref) { @@ -820,52 +601,3 @@ void nvmap_free(struct nvmap_client *client, struct nvmap_handle_ref *r) nvmap_free_handle_id(client, nvmap_ref_to_id(r)); } - -/* - * create a mapping to the user's buffer and write it - * (uses similar logic from nvmap_reloc_pin_array to map the cmdbuf) - */ -int nvmap_patch_word(struct nvmap_client *client, - struct nvmap_handle *patch, - u32 patch_offset, u32 patch_value) -{ - phys_addr_t phys; - unsigned long kaddr; - unsigned int pfn; - void *addr; - pte_t **pte; - pgprot_t prot; - - if (patch_offset >= patch->size) { - nvmap_warn(client, "read/write outside of handle\n"); - return -EFAULT; - } - - pte = nvmap_alloc_pte(client->dev, &addr); - if (IS_ERR(pte)) - return PTR_ERR(pte); - - /* derive physaddr of cmdbuf WAIT to patch */ - if (patch->heap_pgalloc) { - unsigned int page = patch_offset >> PAGE_SHIFT; - phys = page_to_phys(patch->pgalloc.pages[page]); - phys += (patch_offset & ~PAGE_MASK); - } else { - phys = patch->carveout->base + patch_offset; - } - - pfn = __phys_to_pfn(phys); - prot = nvmap_pgprot(patch, pgprot_kernel); - kaddr = (unsigned long)addr; - - /* write PTE, so addr points to cmdbuf PFN */ - set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot)); - flush_tlb_kernel_page(kaddr); - - /* write patch_value to addr + page offset */ - __raw_writel(patch_value, addr + (phys & ~PAGE_MASK)); - - nvmap_free_pte(client->dev, pte); - wmb(); - return 0; -} diff --git a/drivers/video/tegra/nvmap/nvmap.h b/drivers/video/tegra/nvmap/nvmap.h index b0fb70f64a5c..25403f5e7098 100644 --- a/drivers/video/tegra/nvmap/nvmap.h +++ b/drivers/video/tegra/nvmap/nvmap.h @@ -37,6 +37,8 @@ struct nvmap_device; struct page; struct tegra_iovmm_area; +void _nvmap_handle_free(struct nvmap_handle *h); + #if defined(CONFIG_TEGRA_NVMAP) #define nvmap_err(_client, _fmt, ...) \ dev_err(nvmap_client_to_device(_client), \ @@ -162,7 +164,46 @@ static inline void nvmap_ref_unlock(struct nvmap_client *priv) { mutex_unlock(&priv->ref_lock); } -#endif /* CONFIG_TEGRA_NVMAP */ + +static inline struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h) +{ + if (unlikely(atomic_inc_return(&h->ref) <= 1)) { + pr_err("%s: %s getting a freed handle\n", + __func__, current->group_leader->comm); + if (atomic_read(&h->ref) <= 0) + return NULL; + } + return h; +} + +static inline void nvmap_handle_put(struct nvmap_handle *h) +{ + int cnt = atomic_dec_return(&h->ref); + + if (WARN_ON(cnt < 0)) { + pr_err("%s: %s put to negative references\n", + __func__, current->comm); + } else if (cnt == 0) + _nvmap_handle_free(h); +} + +static inline pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot) +{ + if (h->flags == NVMAP_HANDLE_UNCACHEABLE) + return pgprot_noncached(prot); + else if (h->flags == NVMAP_HANDLE_WRITE_COMBINE) + return pgprot_writecombine(prot); + else if (h->flags == NVMAP_HANDLE_INNER_CACHEABLE) + return pgprot_inner_writeback(prot); + return prot; +} + +#else /* CONFIG_TEGRA_NVMAP */ +struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h); +void nvmap_handle_put(struct nvmap_handle *h); +pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot); + +#endif /* !CONFIG_TEGRA_NVMAP */ struct device *nvmap_client_to_device(struct nvmap_client *client); @@ -216,51 +257,10 @@ int nvmap_pin_ids(struct nvmap_client *client, void nvmap_unpin_ids(struct nvmap_client *priv, unsigned int nr, const unsigned long *ids); -void _nvmap_handle_free(struct nvmap_handle *h); - int nvmap_handle_remove(struct nvmap_device *dev, struct nvmap_handle *h); void nvmap_handle_add(struct nvmap_device *dev, struct nvmap_handle *h); -#if defined(CONFIG_TEGRA_NVMAP) -static inline struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h) -{ - if (unlikely(atomic_inc_return(&h->ref) <= 1)) { - pr_err("%s: %s getting a freed handle\n", - __func__, current->group_leader->comm); - if (atomic_read(&h->ref) <= 0) - return NULL; - } - return h; -} - -static inline void nvmap_handle_put(struct nvmap_handle *h) -{ - int cnt = atomic_dec_return(&h->ref); - - if (WARN_ON(cnt < 0)) { - pr_err("%s: %s put to negative references\n", - __func__, current->comm); - } else if (cnt == 0) - _nvmap_handle_free(h); -} - -static inline pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot) -{ - if (h->flags == NVMAP_HANDLE_UNCACHEABLE) - return pgprot_noncached(prot); - else if (h->flags == NVMAP_HANDLE_WRITE_COMBINE) - return pgprot_writecombine(prot); - else if (h->flags == NVMAP_HANDLE_INNER_CACHEABLE) - return pgprot_inner_writeback(prot); - return prot; -} -#else /* CONFIG_TEGRA_NVMAP */ -struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h); -void nvmap_handle_put(struct nvmap_handle *h); -pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot); -#endif /* !CONFIG_TEGRA_NVMAP */ - int is_nvmap_vma(struct vm_area_struct *vma); struct nvmap_handle_ref *nvmap_alloc_iovm(struct nvmap_client *client, @@ -268,4 +268,4 @@ struct nvmap_handle_ref *nvmap_alloc_iovm(struct nvmap_client *client, void nvmap_free_iovm(struct nvmap_client *client, struct nvmap_handle_ref *r); -#endif +#endif /* __VIDEO_TEGRA_NVMAP_NVMAP_H */ diff --git a/drivers/video/tegra/nvmap/nvmap_dev.c b/drivers/video/tegra/nvmap/nvmap_dev.c index c78818711f74..9ecce7eeeb17 100644 --- a/drivers/video/tegra/nvmap/nvmap_dev.c +++ b/drivers/video/tegra/nvmap/nvmap_dev.c @@ -973,20 +973,17 @@ static void client_stringify(struct nvmap_client *client, struct seq_file *s) } static void allocations_stringify(struct nvmap_client *client, - struct seq_file *s) + struct seq_file *s, bool iovmm) { - unsigned long base = 0; struct rb_node *n = rb_first(&client->handle_refs); for (; n != NULL; n = rb_next(n)) { struct nvmap_handle_ref *ref = rb_entry(n, struct nvmap_handle_ref, node); struct nvmap_handle *handle = ref->handle; - if (handle->alloc && !handle->heap_pgalloc) { - seq_printf(s, "%-18s %-18s %8lx %10u %8x\n", "", "", - (unsigned long)(handle->carveout->base), - handle->size, handle->userflags); - } else if (handle->alloc && handle->heap_pgalloc) { + if (handle->alloc && handle->heap_pgalloc == iovmm) { + unsigned long base = iovmm ? 0: + (unsigned long)(handle->carveout->base); seq_printf(s, "%-18s %-18s %8lx %10u %8x\n", "", "", base, handle->size, handle->userflags); } @@ -1010,7 +1007,7 @@ static int nvmap_debug_allocations_show(struct seq_file *s, void *unused) get_client_from_carveout_commit(node, commit); client_stringify(client, s); seq_printf(s, " %10u\n", commit->commit); - allocations_stringify(client, s); + allocations_stringify(client, s, false); seq_printf(s, "\n"); total += commit->commit; } @@ -1111,14 +1108,14 @@ static int nvmap_debug_iovmm_allocations_show(struct seq_file *s, void *unused) struct nvmap_device *dev = s->private; spin_lock_irqsave(&dev->clients_lock, flags); - seq_printf(s, "%-18s %18s %8s %10s\n", "CLIENT", "PROCESS", "PID", - "SIZE"); + seq_printf(s, "%-18s %18s %8s %10s %8s\n", "CLIENT", "PROCESS", "PID", + "SIZE", "FLAGS"); seq_printf(s, "%-18s %18s %8s %10s\n", "", "", "BASE", "SIZE"); list_for_each_entry(client, &dev->clients, list) { client_stringify(client, s); seq_printf(s, " %10u\n", atomic_read(&client->iovm_commit)); - allocations_stringify(client, s); + allocations_stringify(client, s, true); seq_printf(s, "\n"); total += atomic_read(&client->iovm_commit); } diff --git a/drivers/video/tegra/nvmap/nvmap_handle.c b/drivers/video/tegra/nvmap/nvmap_handle.c index 2f24ba515862..56e2dab1820c 100644 --- a/drivers/video/tegra/nvmap/nvmap_handle.c +++ b/drivers/video/tegra/nvmap/nvmap_handle.c @@ -38,22 +38,12 @@ #include <mach/iovmm.h> #include <linux/nvmap.h> -#include <linux/vmstat.h> -#include <linux/swap.h> #include <linux/shrinker.h> #include <linux/moduleparam.h> - #include "nvmap.h" #include "nvmap_mru.h" #include "nvmap_common.h" -#define PRINT_CARVEOUT_CONVERSION 0 -#if PRINT_CARVEOUT_CONVERSION -#define PR_INFO pr_info -#else -#define PR_INFO(...) -#endif - #define NVMAP_SECURE_HEAPS (NVMAP_HEAP_CARVEOUT_IRAM | NVMAP_HEAP_IOVMM | \ NVMAP_HEAP_CARVEOUT_VPR) #ifdef CONFIG_NVMAP_HIGHMEM_ONLY @@ -646,36 +636,19 @@ fail: static void alloc_handle(struct nvmap_client *client, struct nvmap_handle *h, unsigned int type) { + unsigned int carveout_mask = NVMAP_HEAP_CARVEOUT_MASK; + unsigned int iovmm_mask = NVMAP_HEAP_IOVMM; + BUG_ON(type & (type - 1)); #ifdef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM -#define __NVMAP_HEAP_CARVEOUT (NVMAP_HEAP_CARVEOUT_IRAM | NVMAP_HEAP_CARVEOUT_VPR) -#define __NVMAP_HEAP_IOVMM (NVMAP_HEAP_IOVMM | NVMAP_HEAP_CARVEOUT_GENERIC) - if (type & NVMAP_HEAP_CARVEOUT_GENERIC) { -#ifdef CONFIG_NVMAP_ALLOW_SYSMEM - if (h->size <= PAGE_SIZE) { - PR_INFO("###CARVEOUT CONVERTED TO SYSMEM " - "0x%x bytes %s(%d)###\n", - h->size, current->comm, current->pid); - goto sysheap; - } -#endif - PR_INFO("###CARVEOUT CONVERTED TO IOVM " - "0x%x bytes %s(%d)###\n", - h->size, current->comm, current->pid); - } -#else -#define __NVMAP_HEAP_CARVEOUT NVMAP_HEAP_CARVEOUT_MASK -#define __NVMAP_HEAP_IOVMM NVMAP_HEAP_IOVMM + /* Convert generic carveout requests to iovmm requests. */ + carveout_mask &= ~NVMAP_HEAP_CARVEOUT_GENERIC; + iovmm_mask |= NVMAP_HEAP_CARVEOUT_GENERIC; #endif - if (type & __NVMAP_HEAP_CARVEOUT) { + if (type & carveout_mask) { struct nvmap_heap_block *b; -#ifdef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM - PR_INFO("###IRAM REQUEST RETAINED " - "0x%x bytes %s(%d)###\n", - h->size, current->comm, current->pid); -#endif /* Protect handle from relocation */ nvmap_usecount_inc(h); @@ -689,7 +662,7 @@ static void alloc_handle(struct nvmap_client *client, } nvmap_usecount_dec(h); - } else if (type & __NVMAP_HEAP_IOVMM) { + } else if (type & iovmm_mask) { size_t reserved = PAGE_ALIGN(h->size); int commit = 0; int ret; @@ -713,10 +686,6 @@ static void alloc_handle(struct nvmap_client *client, } } else if (type & NVMAP_HEAP_SYSMEM) { -#if defined(CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM) && \ - defined(CONFIG_NVMAP_ALLOW_SYSMEM) -sysheap: -#endif if (handle_page_alloc(client, h, true) == 0) { BUG_ON(!h->pgalloc.contig); h->heap_pgalloc = true; @@ -751,10 +720,6 @@ static const unsigned int heap_policy_large[] = { 0, }; -/* Do not override single page policy if there is not much space to -avoid invoking system oom killer. */ -#define NVMAP_SMALL_POLICY_SYSMEM_THRESHOLD 50000000 - int nvmap_alloc_handle_id(struct nvmap_client *client, unsigned long id, unsigned int heap_mask, size_t align, unsigned int flags) @@ -779,32 +744,22 @@ int nvmap_alloc_handle_id(struct nvmap_client *client, h->align = max_t(size_t, align, L1_CACHE_BYTES); #ifndef CONFIG_TEGRA_IOVMM + /* convert iovmm requests to generic carveout. */ if (heap_mask & NVMAP_HEAP_IOVMM) { - heap_mask &= NVMAP_HEAP_IOVMM; - heap_mask |= NVMAP_HEAP_CARVEOUT_GENERIC; + heap_mask = heap_mask & ~NVMAP_HEAP_IOVMM | + NVMAP_HEAP_CARVEOUT_GENERIC; } #endif -#ifndef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM #ifdef CONFIG_NVMAP_ALLOW_SYSMEM /* Allow single pages allocations in system memory to save * carveout space and avoid extra iovm mappings */ if (nr_page == 1) { - if (heap_mask & NVMAP_HEAP_IOVMM) + if (heap_mask & + (NVMAP_HEAP_IOVMM | NVMAP_HEAP_CARVEOUT_GENERIC)) heap_mask |= NVMAP_HEAP_SYSMEM; - else if (heap_mask & NVMAP_HEAP_CARVEOUT_GENERIC) { - /* Calculate size of free physical pages - * managed by kernel */ - unsigned long freeMem = - (global_page_state(NR_FREE_PAGES) + - global_page_state(NR_FILE_PAGES) - - total_swapcache_pages) << PAGE_SHIFT; - - if (freeMem > NVMAP_SMALL_POLICY_SYSMEM_THRESHOLD) - heap_mask |= NVMAP_HEAP_SYSMEM; - } } #endif - +#ifndef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM /* This restriction is deprecated as alignments greater than PAGE_SIZE are now correctly handled, but it is retained for AP20 compatibility. */ |