diff options
Diffstat (limited to 'arch/arm/plat-mxc/ahci_sata.c')
-rw-r--r-- | arch/arm/plat-mxc/ahci_sata.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/arch/arm/plat-mxc/ahci_sata.c b/arch/arm/plat-mxc/ahci_sata.c new file mode 100644 index 000000000000..23aa0ec13160 --- /dev/null +++ b/arch/arm/plat-mxc/ahci_sata.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/ahci_platform.h> +#include <linux/regulator/consumer.h> +#include <asm/mach-types.h> +#include <mach/common.h> +#include <mach/hardware.h> + +/*****************************************************************************\ + * * + * FSL SATA AHCI low level functions * + * return value 1 failure, 0 success * + * * +\*****************************************************************************/ +enum { + HOST_CAP = 0x00, + HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */ + HOST_PORTS_IMPL = 0x0c, + HOST_TIMER1MS = 0xe0, /* Timer 1-ms */ + /* Offest used to control the MPLL input clk */ + PHY_CR_CLOCK_FREQ_OVRD = 0x12, + /* Port0 PHY Control */ + PORT_PHY_CTL = 0x178, + /* PORT_PHY_CTL bits */ + PORT_PHY_CTL_CAP_ADR_LOC = 0x10000, + PORT_PHY_CTL_CAP_DAT_LOC = 0x20000, + PORT_PHY_CTL_WRITE_LOC = 0x40000, + PORT_PHY_CTL_READ_LOC = 0x80000, + /* Port0 PHY Status */ + PORT_PHY_SR = 0x17c, + /* PORT_PHY_SR */ + PORT_PHY_STAT_DATA_LOC = 0, + PORT_PHY_STAT_ACK_LOC = 18, + /* SATA PHY Register */ + SATA_PHY_CR_CLOCK_CRCMP_LT_LIMIT = 0x0001, + SATA_PHY_CR_CLOCK_DAC_CTL = 0x0008, + SATA_PHY_CR_CLOCK_RTUNE_CTL = 0x0009, + SATA_PHY_CR_CLOCK_ADC_OUT = 0x000A, + SATA_PHY_CR_CLOCK_MPLL_TST = 0x0017, +}; + +static int write_phy_ctl_ack_polling(u32 data, void __iomem *mmio, + int max_iterations, u32 exp_val) +{ + u32 i, val; + + writel(data, mmio + PORT_PHY_CTL); + + for (i = 0; i < max_iterations + 1; i++) { + val = readl(mmio + PORT_PHY_SR); + val = (val >> PORT_PHY_STAT_ACK_LOC) & 0x1; + if (val == exp_val) + return 0; + if (i == max_iterations) { + printk(KERN_ERR "Wait for CR ACK error!\n"); + return 1; + } + msleep(1); + } + return 0; +} + +static int sata_phy_cr_addr(u32 addr, void __iomem *mmio) +{ + u32 temp_wr_data; + + /* write addr */ + temp_wr_data = addr; + writel(temp_wr_data, mmio + PORT_PHY_CTL); + + /* capture addr */ + temp_wr_data |= PORT_PHY_CTL_CAP_ADR_LOC; + + /* wait for ack */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 1)) + return 1; + + /* deassert cap addr */ + temp_wr_data &= 0xffff; + + /* wait for ack de-assetion */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 0)) + return 1; + + return 0; +} + +static int sata_phy_cr_write(u32 data, void __iomem *mmio) +{ + u32 temp_wr_data; + + /* write data */ + temp_wr_data = data; + + /* capture data */ + temp_wr_data |= PORT_PHY_CTL_CAP_DAT_LOC; + + /* wait for ack */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 1)) + return 1; + + /* deassert cap data */ + temp_wr_data &= 0xffff; + + /* wait for ack de-assetion */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 0)) + return 1; + + /* assert wr signal */ + temp_wr_data |= PORT_PHY_CTL_WRITE_LOC; + + /* wait for ack */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 1)) + return 1; + + /* deassert wr _signal */ + temp_wr_data = 0x0; + + /* wait for ack de-assetion */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 0)) + return 1; + + return 0; +} + +static int sata_phy_cr_read(u32 *data, void __iomem *mmio) +{ + u32 temp_rd_data, temp_wr_data; + + /* assert rd signal */ + temp_wr_data = PORT_PHY_CTL_READ_LOC; + + /* wait for ack */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 1)) + return 1; + + /* after got ack return data */ + temp_rd_data = readl(mmio + PORT_PHY_SR); + *data = (temp_rd_data & 0xffff); + + /* deassert rd _signal */ + temp_wr_data = 0x0 ; + + /* wait for ack de-assetion */ + if (write_phy_ctl_ack_polling(temp_wr_data, mmio, 100, 0)) + return 1; + + return 0; +} + +/* SATA AHCI temperature monitor */ +static ssize_t sata_ahci_current_tmp(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + void __iomem *mmio; + u32 mpll_test_reg, rtune_ctl_reg, dac_ctl_reg, adc_out_reg; + u32 str1, str2, str3, str4, read_sum, index; + int m1, m2, a, temp; + + /* map the IO addr */ + mmio = ioremap(MX53_SATA_BASE_ADDR, SZ_2K); + if (mmio == NULL) { + printk(KERN_ERR "Failed to map SATA REGS\n"); + return 1; + } + + /* check rd-wr to reg */ + read_sum = 0; + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_CRCMP_LT_LIMIT, mmio); + sata_phy_cr_write(read_sum, mmio); + sata_phy_cr_read(&read_sum, mmio); + if ((read_sum & 0xffff) != 0) + printk(KERN_ERR "Read/Write REG error, 0x%x!\n", read_sum); + + sata_phy_cr_write(0x5A5A, mmio); + sata_phy_cr_read(&read_sum, mmio); + if ((read_sum & 0xffff) != 0x5A5A) + printk(KERN_ERR "Read/Write REG error, 0x%x!\n", read_sum); + + sata_phy_cr_write(0x1234, mmio); + sata_phy_cr_read(&read_sum, mmio); + if ((read_sum & 0xffff) != 0x1234) + printk(KERN_ERR "Read/Write REG error, 0x%x!\n", read_sum); + + /* stat temperature test */ + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio); + sata_phy_cr_read(&mpll_test_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio); + sata_phy_cr_read(&rtune_ctl_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio); + sata_phy_cr_read(&dac_ctl_reg, mmio); + + /* mpll_tst.meas_iv ([12:2]) */ + str1 = (mpll_test_reg >> 2) & 0x7FF; + /* rtune_ctl.mode ([1:0]) */ + str2 = (rtune_ctl_reg) & 0x3; + /* dac_ctl.dac_mode ([14:12]) */ + str3 = (dac_ctl_reg >> 12) & 0x7; + /* rtune_ctl.sel_atbp ([4]) */ + str4 = (rtune_ctl_reg >> 4); + + /* Caculate the m1 */ + /* mpll_tst.meas_iv */ + mpll_test_reg = (mpll_test_reg & 0xE03) | (512) << 2; + /* rtune_ctl.mode */ + rtune_ctl_reg = (rtune_ctl_reg & 0xFFC) | (1); + /* dac_ctl.dac_mode */ + dac_ctl_reg = (dac_ctl_reg & 0x8FF) | (4) << 12; + /* rtune_ctl.sel_atbp */ + rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (0) << 4; + + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio); + sata_phy_cr_write(mpll_test_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio); + sata_phy_cr_write(dac_ctl_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio); + sata_phy_cr_write(rtune_ctl_reg, mmio); + + /* two dummy read */ + index = 0; + read_sum = 0; + adc_out_reg = 0; + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_ADC_OUT, mmio); + while (index < 2) { + sata_phy_cr_read(&adc_out_reg, mmio); + /* check if valid */ + if (adc_out_reg & 0x400) + index = index + 1; + read_sum++; + if (read_sum > 100000) { + printk(KERN_ERR "Read REG more than 100000 times!\n"); + break; + } + } + + index = 0; + read_sum = 0; + while (index < 80) { + sata_phy_cr_read(&adc_out_reg, mmio); + if (adc_out_reg & 0x400) { + read_sum = read_sum + (adc_out_reg & 0x3FF); + index = index + 1; + } + } + /* Use the U32 to make 1000 precision */ + m1 = (read_sum * 1000) / 80; + + /* Caculate the m2 */ + /* rtune_ctl.sel_atbp */ + rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (1) << 4; + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio); + sata_phy_cr_write(rtune_ctl_reg, mmio); + + /* two dummy read */ + index = 0; + read_sum = 0; + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_ADC_OUT, mmio); + while (index < 2) { + sata_phy_cr_read(&adc_out_reg, mmio); + /* check if valid */ + if (adc_out_reg & 0x400) + index = index + 1; + read_sum++; + if (read_sum > 100000) { + printk(KERN_ERR "Read REG more than 100000 times!\n"); + break; + } + } + + index = 0; + read_sum = 0; + while (index < 80) { + /* FIX ME dead loop protection??? */ + sata_phy_cr_read(&adc_out_reg, mmio); + if (adc_out_reg & 0x400) { + read_sum = read_sum + (adc_out_reg & 0x3FF); + index = index + 1; + } + } + /* Use the U32 to make 1000 precision */ + m2 = (read_sum * 1000) / 80; + + /* restore the status */ + /* mpll_tst.meas_iv */ + mpll_test_reg = (mpll_test_reg & 0xE03) | (str1) << 2; + /* rtune_ctl.mode */ + rtune_ctl_reg = (rtune_ctl_reg & 0xFFC) | (str2); + /* dac_ctl.dac_mode */ + dac_ctl_reg = (dac_ctl_reg & 0x8FF) | (str3) << 12; + /* rtune_ctl.sel_atbp */ + rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (str4) << 4; + + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio); + sata_phy_cr_write(mpll_test_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio); + sata_phy_cr_write(dac_ctl_reg, mmio); + sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio); + sata_phy_cr_write(rtune_ctl_reg, mmio); + + /* Compute temperature */ + if (!(m2 / 1000)) + m2 = 1000; + a = (m2 - m1) / (m2 / 1000); + temp = ((((-559) * a) / 1000) * a) / 1000 + (1379) * a / 1000 + (-458); + + iounmap(mmio); + return sprintf(buf, "SATA AHCI current temperature:%d\n", temp); +} + +static DEVICE_ATTR(temperature, S_IRUGO, sata_ahci_current_tmp, NULL); + +static struct attribute *fsl_sata_ahci_attr[] = { + &dev_attr_temperature.attr, + NULL +}; + +static const struct attribute_group fsl_sata_ahci_group = { + .attrs = fsl_sata_ahci_attr, +}; + +/* HW Initialization, if return 1, initialization is failed. */ +static int sata_init(struct device *dev) +{ + void __iomem *mmio; + struct clk *clk; + int ret = 0; + u32 tmpdata; + struct regulator *reg_2v5, *reg_1v3; + + /* AHCI SATA PWR EN */ + if (machine_is_mx53_smd() || machine_is_mx53_loco()) { + /* PWR(VBUCKPERI and VLDO5) on SATA AHCI */ + reg_2v5 = regulator_get(dev, "DA9052_BUCK_PERI"); + ret = IS_ERR(reg_2v5); + if (ret) { + printk(KERN_ERR "AHCI can't get 2v5 PWR.\n"); + goto err0; + } + ret = regulator_enable(reg_2v5); + if (ret) { + printk(KERN_ERR "AHCI: enable 2v5 regulator error.\n"); + goto err0; + } + msleep(25); + + reg_1v3 = regulator_get(dev, "DA9052_LDO5"); + ret = IS_ERR(reg_1v3); + if (ret) { + printk(KERN_ERR "AHCI can't get 1v3 PWR.\n"); + goto err0; + } + ret = regulator_enable(reg_1v3); + if (ret) { + printk(KERN_ERR "AHCI: enable 1v3 regulator error.\n"); + goto err0; + } + msleep(25); + } + + clk = clk_get(dev, "imx_sata_clk"); + ret = IS_ERR(clk); + if (ret) { + printk(KERN_ERR "AHCI can't get clock.\n"); + goto err0; + } + ret = clk_enable(clk); + if (ret) { + printk(KERN_ERR "AHCI can't enable clock.\n"); + goto err0; + } + + /* Get the AHB clock rate, and configure the TIMER1MS reg later */ + clk = clk_get(NULL, "ahb_clk"); + ret = IS_ERR(clk); + if (ret) { + printk(KERN_ERR "AHCI can't get AHB clock.\n"); + goto err0; + } + + mmio = ioremap(MX53_SATA_BASE_ADDR, SZ_2K); + if (mmio == NULL) { + printk(KERN_ERR "Failed to map SATA REGS\n"); + goto err0; + } + + tmpdata = readl(mmio + HOST_CAP); + if (!(tmpdata & HOST_CAP_SSS)) { + tmpdata |= HOST_CAP_SSS; + writel(tmpdata, mmio + HOST_CAP); + } + + if (!(readl(mmio + HOST_PORTS_IMPL) & 0x1)) + writel((readl(mmio + HOST_PORTS_IMPL) | 0x1), + mmio + HOST_PORTS_IMPL); + + tmpdata = clk_get_rate(clk) / 1000; + writel(tmpdata, mmio + HOST_TIMER1MS); + + if (machine_is_mx53_smd() || machine_is_mx53_loco()) { + /* Internal CLK input is used for AHCI */ + iounmap(mmio); + /* Eanble the IIM CLK */ + clk = clk_get(dev, "iim_clk"); + ret = IS_ERR(clk); + if (ret) { + printk(KERN_ERR "AHCI can't get IIM CLK.\n"); + goto err0; + } + ret = clk_enable(clk); + if (ret) { + printk(KERN_ERR "AHCI can't enable IIM clock.\n"); + goto err0; + } + /* SMD or loco boards use the IC internal clk */ + mmio = ioremap(0x63F98000 + 0x180C, SZ_16); + if (mmio == NULL) { + printk(KERN_ERR "Failed to map IIM interface.\n"); + goto err0; + } + /* USB_PHY1 clk, fuse bank4 row3 bit2 */ + writel((readl(mmio) & (~0x7)) | 0x4, mmio); + iounmap(mmio); + /* release IIM clk */ + clk_disable(clk); + clk_put(clk); + clk = clk_get(dev, "usb_phy1_clk"); + ret = IS_ERR(clk); + if (ret) { + printk(KERN_ERR "AHCI can't get USB PHY1 CLK.\n"); + goto err0; + } + ret = clk_enable(clk); + if (ret) { + printk(KERN_ERR "AHCI Can't enable USB PHY1 clock.\n"); + goto err0; + } + } else { + /* External CLK input is used for AHCI */ + /* write addr */ + tmpdata = PHY_CR_CLOCK_FREQ_OVRD; + writel(tmpdata, mmio + PORT_PHY_CTL); + /* capture addr */ + tmpdata |= PORT_PHY_CTL_CAP_ADR_LOC; + /* Wait for ack */ + if (write_phy_ctl_ack_polling(tmpdata, mmio, 100, 1)) { + ret = -EIO; + goto err0; + } + + /* deassert cap data */ + tmpdata &= 0xFFFF; + /* wait for ack de-assertion */ + if (write_phy_ctl_ack_polling(tmpdata, mmio, 100, 0)) { + ret = -EIO; + goto err0; + } + + /* write data */ + /* Configure the PHY CLK input refer to different OSC + * For 25MHz, pre[13,14]:01, ncy[12,8]:06, + * ncy5[7,6]:02, int_ctl[5,3]:0, prop_ctl[2,0]:7. + * For 50MHz, pre:00, ncy:06, ncy5:02, int_ctl:0, prop_ctl:7. + */ + /* EVK revA */ + if (board_is_mx53_evk_a()) + tmpdata = (0x1 << 15) | (0x1 << 13) | (0x6 << 8) + | (0x2 << 6) | 0x7; + /* Others are 50MHz */ + else + tmpdata = (0x1 << 15) | (0x0 << 13) | (0x6 << 8) + | (0x2 << 6) | 0x7; + + writel(tmpdata, mmio + PORT_PHY_CTL); + /* capture data */ + tmpdata |= PORT_PHY_CTL_CAP_DAT_LOC; + /* wait for ack */ + if (write_phy_ctl_ack_polling(tmpdata, mmio, 100, 1)) { + ret = -EIO; + goto err0; + } + + /* deassert cap data */ + tmpdata &= 0xFFFF; + /* wait for ack de-assertion */ + if (write_phy_ctl_ack_polling(tmpdata, mmio, 100, 0)) { + ret = -EIO; + goto err0; + } + + /* assert wr signal and wait for ack */ + if (write_phy_ctl_ack_polling(PORT_PHY_CTL_WRITE_LOC, mmio, + 100, 1)) { + ret = -EIO; + goto err0; + } + /* deassert rd _signal and wait for ack de-assertion */ + if (write_phy_ctl_ack_polling(0, mmio, 100, 0)) { + ret = -EIO; + goto err0; + } + iounmap(mmio); + } + msleep(10); + + /* Add the temperature monitor */ + ret = sysfs_create_group(&dev->kobj, &fsl_sata_ahci_group); + if (ret) + sysfs_remove_group(&dev->kobj, &fsl_sata_ahci_group); + +err0: + reg_1v3 = NULL; + reg_2v5 = NULL; + clk = NULL; + return ret; +} + +static void sata_exit(struct device *dev) +{ + struct clk *clk; + struct regulator *reg_2v5, *reg_1v3; + + sysfs_remove_group(&dev->kobj, &fsl_sata_ahci_group); + clk = clk_get(dev, "usb_phy1_clk"); + if (IS_ERR(clk)) { + clk = NULL; + printk(KERN_ERR "AHCI can't get USB PHY1 CLK.\n"); + } else { + clk_disable(clk); + clk_put(clk); + } + + clk = clk_get(dev, "imx_sata_clk"); + if (IS_ERR(clk)) { + clk = NULL; + printk(KERN_ERR "IMX SATA can't get clock.\n"); + } else { + clk_disable(clk); + clk_put(clk); + } + + /* AHCI SATA PWR disable */ + if (machine_is_mx53_smd() || machine_is_mx53_loco()) { + reg_2v5 = regulator_get(dev, "DA9052_BUCK_PERI"); + if (IS_ERR(reg_2v5)) { + printk(KERN_ERR "AHCI: get 2v5 regulator error.\n"); + reg_2v5 = NULL; + } else { + regulator_disable(reg_2v5); + regulator_put(reg_2v5); + } + + reg_1v3 = regulator_get(dev, "DA9052_LDO5"); + if (IS_ERR(reg_1v3)) { + printk(KERN_ERR "AHCI: get 2v5 regulator error.\n"); + reg_1v3 = NULL; + } else { + regulator_disable(reg_1v3); + regulator_put(reg_1v3); + } + } +} + +struct ahci_platform_data sata_data = { + .init = sata_init, + .exit = sata_exit, +}; + |