| /* |
| * drivers/amlogic/pci/pcie-amlogic.c |
| * |
| * Copyright (C) 2017 Amlogic, 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. |
| * |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pci.h> |
| #include <linux/platform_device.h> |
| #include <linux/resource.h> |
| #include <linux/signal.h> |
| #include <linux/types.h> |
| #include <linux/module.h> |
| #include "../drivers/pci/host/pcie-designware.h" |
| #include "pcie-amlogic.h" |
| |
| #include <dt-bindings/clock/amlogic,axg-clkc.h> |
| #include <linux/clk-provider.h> |
| #include "../clk/clkc.h" |
| |
| |
| struct amlogic_pcie { |
| struct pcie_port pp; |
| struct pcie_phy *phy; |
| void __iomem *elbi_base; /* DT 0th resource */ |
| void __iomem *cfg_base; /* DT 2nd resource */ |
| int reset_gpio; |
| struct clk *clk; |
| struct clk *bus_clk; |
| struct clk *mipi_gate; |
| struct clk *mipi_bandgap_gate; |
| struct clk *port_clk; |
| struct clk *general_clk; |
| int pcie_num; |
| int gpio_type; |
| u32 port_num; |
| u32 pm_enable; |
| u32 device_attch; |
| }; |
| |
| #define to_amlogic_pcie(x) container_of(x, struct amlogic_pcie, pp) |
| struct pcie_phy_aml_regs pcie_aml_regs; |
| struct pcie_phy *g_pcie_phy; |
| |
| static void amlogic_elb_writel(struct amlogic_pcie *amlogic_pcie, u32 val, |
| u32 reg) |
| { |
| writel(val, amlogic_pcie->elbi_base + reg); |
| } |
| |
| static u32 amlogic_elb_readl(struct amlogic_pcie *amlogic_pcie, u32 reg) |
| { |
| return readl(amlogic_pcie->elbi_base + reg); |
| } |
| |
| static void amlogic_cfg_writel(struct amlogic_pcie *amlogic_pcie, u32 val, |
| u32 reg) |
| { |
| writel(val, amlogic_pcie->cfg_base + reg); |
| } |
| |
| static u32 amlogic_cfg_readl(struct amlogic_pcie *amlogic_pcie, u32 reg) |
| { |
| return readl(amlogic_pcie->cfg_base + reg); |
| } |
| |
| static void cr_bus_addr(unsigned int addr) |
| { |
| union phy_r4 phy_r4 = {.d32 = 0}; |
| union phy_r5 phy_r5 = {.d32 = 0}; |
| |
| phy_r4.b.phy_cr_data_in = addr; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| phy_r4.b.phy_cr_cap_addr = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| phy_r4.b.phy_cr_cap_addr = 1; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 0); |
| |
| phy_r4.b.phy_cr_cap_addr = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 1); |
| } |
| |
| static int cr_bus_read(unsigned int addr) |
| { |
| int data; |
| union phy_r4 phy_r4 = {.d32 = 0}; |
| union phy_r5 phy_r5 = {.d32 = 0}; |
| |
| cr_bus_addr(addr); |
| |
| phy_r4.b.phy_cr_read = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| phy_r4.b.phy_cr_read = 1; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 0); |
| |
| data = phy_r5.b.phy_cr_data_out; |
| |
| phy_r4.b.phy_cr_read = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 1); |
| |
| return data; |
| } |
| |
| static void cr_bus_write(unsigned int addr, unsigned int data) |
| { |
| union phy_r4 phy_r4 = {.d32 = 0}; |
| union phy_r5 phy_r5 = {.d32 = 0}; |
| |
| cr_bus_addr(addr); |
| |
| phy_r4.b.phy_cr_data_in = data; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| phy_r4.b.phy_cr_cap_data = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| phy_r4.b.phy_cr_cap_data = 1; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 0); |
| |
| phy_r4.b.phy_cr_cap_data = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 1); |
| |
| phy_r4.b.phy_cr_write = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| phy_r4.b.phy_cr_write = 1; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 0); |
| |
| phy_r4.b.phy_cr_write = 0; |
| writel(phy_r4.d32, pcie_aml_regs.pcie_phy_r[4]); |
| |
| do { |
| phy_r5.d32 = readl(pcie_aml_regs.pcie_phy_r[5]); |
| } while (phy_r5.b.phy_cr_ack == 1); |
| } |
| |
| |
| static void amlogic_phy_cr_writel(u32 val, u32 reg) |
| { |
| cr_bus_write(reg, val); |
| } |
| |
| static u32 amlogic_phy_cr_readl(u32 reg) |
| { |
| return cr_bus_read(reg); |
| } |
| |
| static ssize_t show_pcie_cr_read(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| u32 status = 0; |
| |
| return sprintf(buf, "%d\n", status); |
| } |
| |
| |
| static ssize_t store_pcie_cr_read(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| u32 reg; |
| u32 val; |
| |
| if (sscanf(buf, "%x", ®) != 1) |
| return -EINVAL; |
| |
| val = amlogic_phy_cr_readl(reg); |
| dev_info(dev, "reg 0x%x value is 0x%x\n", reg, val); |
| |
| return count; |
| } |
| DEVICE_ATTR(phyread, 0644, show_pcie_cr_read, store_pcie_cr_read); |
| |
| static ssize_t show_pcie_cr_write(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| u32 status = 0; |
| |
| return sprintf(buf, "%d\n", status); |
| } |
| |
| static ssize_t store_pcie_cr_write(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf_p, size_t count) |
| { |
| unsigned reg, val; |
| char buf[80]; |
| int ret; |
| |
| count = min_t(size_t, count, (sizeof(buf)-1)); |
| strncpy(buf, buf_p, (u32)count); |
| |
| buf[count] = 0; |
| |
| ret = sscanf(buf, "%x %x", ®, &val); |
| |
| switch (ret) { |
| case 2: |
| amlogic_phy_cr_writel(val, reg); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return count; |
| } |
| DEVICE_ATTR(phywrite, 0644, show_pcie_cr_write, store_pcie_cr_write); |
| |
| static void amlogic_pcie_assert_reset(struct amlogic_pcie *amlogic_pcie) |
| { |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| struct device *dev = pp->dev; |
| |
| if (amlogic_pcie->gpio_type == 0) { |
| dev_info(amlogic_pcie->pp.dev, |
| "gpio multiplex, don't reset!\n"); |
| } else if (amlogic_pcie->gpio_type == 1) { |
| dev_info(amlogic_pcie->pp.dev, "pad gpio\n"); |
| if (amlogic_pcie->reset_gpio >= 0) |
| devm_gpio_request(dev, |
| amlogic_pcie->reset_gpio, "RESET"); |
| |
| if (gpio_is_valid(amlogic_pcie->reset_gpio)) { |
| dev_info(amlogic_pcie->pp.dev, |
| "GPIO pad: amlogic_pcie_assert_reset\n"); |
| gpio_direction_output( |
| amlogic_pcie->reset_gpio, 0); |
| msleep(5); |
| gpio_direction_input( |
| amlogic_pcie->reset_gpio); |
| } |
| } else { |
| dev_info(amlogic_pcie->pp.dev, "normal gpio\n"); |
| if (amlogic_pcie->reset_gpio >= 0) |
| devm_gpio_request(dev, |
| amlogic_pcie->reset_gpio, "RESET"); |
| |
| if (gpio_is_valid(amlogic_pcie->reset_gpio)) { |
| dev_info(amlogic_pcie->pp.dev, |
| "GPIO normal: amlogic_pcie_assert_reset\n"); |
| gpio_set_value_cansleep( |
| amlogic_pcie->reset_gpio, 0); |
| msleep(5); |
| gpio_set_value_cansleep( |
| amlogic_pcie->reset_gpio, 1); |
| } |
| } |
| |
| } |
| |
| void amlogic_set_max_payload(struct amlogic_pcie *amlogic_pcie, int size) |
| { |
| int max_payload_size = 1; |
| u32 val = 0; |
| |
| switch (size) { |
| case 128: |
| max_payload_size = 0; |
| break; |
| case 256: |
| max_payload_size = 1; |
| break; |
| case 512: |
| max_payload_size = 2; |
| break; |
| case 1024: |
| max_payload_size = 3; |
| break; |
| case 2048: |
| max_payload_size = 4; |
| break; |
| case 4096: |
| max_payload_size = 5; |
| break; |
| default: |
| max_payload_size = 1; |
| break; |
| } |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_DEV_CTRL_DEV_STUS); |
| val &= (~(0x7<<5)); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_DEV_CTRL_DEV_STUS); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_DEV_CTRL_DEV_STUS); |
| val |= (max_payload_size<<5); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_DEV_CTRL_DEV_STUS); |
| } |
| |
| void amlogic_set_max_rd_req_size(struct amlogic_pcie *amlogic_pcie, int size) |
| { |
| int max_rd_req_size = 1; |
| u32 val = 0; |
| |
| switch (size) { |
| case 128: |
| max_rd_req_size = 0; |
| break; |
| case 256: |
| max_rd_req_size = 1; |
| break; |
| case 512: |
| max_rd_req_size = 2; |
| break; |
| case 1024: |
| max_rd_req_size = 3; |
| break; |
| case 2048: |
| max_rd_req_size = 4; |
| break; |
| case 4096: |
| max_rd_req_size = 5; |
| break; |
| default: |
| max_rd_req_size = 1; |
| break; |
| } |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_DEV_CTRL_DEV_STUS); |
| val &= (~(0x7<<12)); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_DEV_CTRL_DEV_STUS); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_DEV_CTRL_DEV_STUS); |
| val |= (max_rd_req_size<<12); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_DEV_CTRL_DEV_STUS); |
| } |
| |
| static void amlogic_pcie_init_dw(struct amlogic_pcie *amlogic_pcie) |
| { |
| u32 val = 0; |
| |
| val = amlogic_cfg_readl(amlogic_pcie, PCIE_CFG0); |
| val |= APP_LTSSM_ENABLE; |
| amlogic_cfg_writel(amlogic_pcie, val, PCIE_CFG0); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_PORT_LINK_CTRL_OFF); |
| val |= (1<<7); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_PORT_LINK_CTRL_OFF); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_PORT_LINK_CTRL_OFF); |
| val &= (~(0x3f<<16)); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_PORT_LINK_CTRL_OFF); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_PORT_LINK_CTRL_OFF); |
| val |= (0x1<<16); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_PORT_LINK_CTRL_OFF); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_GEN2_CTRL_OFF); |
| val &= (~(0x1f<<8)); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_GEN2_CTRL_OFF); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_GEN2_CTRL_OFF); |
| val |= (0x1<<8); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_GEN2_CTRL_OFF); |
| |
| val = amlogic_elb_readl(amlogic_pcie, PCIE_GEN2_CTRL_OFF); |
| val |= (1<<17); |
| amlogic_elb_writel(amlogic_pcie, val, PCIE_GEN2_CTRL_OFF); |
| |
| amlogic_elb_writel(amlogic_pcie, 0x0, PCIE_BASE_ADDR0); |
| amlogic_elb_writel(amlogic_pcie, 0x0, PCIE_BASE_ADDR1); |
| } |
| |
| void amlogic_enable_memory_space(struct amlogic_pcie *amlogic_pcie) |
| { |
| u32 val = 0; |
| |
| dev_info(amlogic_pcie->pp.dev, "Set the RC Bus Master, Memory Space and I/O Space enables.\n"); |
| val = amlogic_elb_readl(amlogic_pcie, 0x04); |
| val = 7; |
| amlogic_elb_writel(amlogic_pcie, val, 0x04); |
| } |
| |
| |
| static int amlogic_pcie_establish_link(struct amlogic_pcie *amlogic_pcie) |
| { |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| |
| amlogic_pcie_init_dw(amlogic_pcie); |
| amlogic_set_max_payload(amlogic_pcie, 256); |
| amlogic_set_max_rd_req_size(amlogic_pcie, 256); |
| |
| dw_pcie_setup_rc(pp); |
| amlogic_enable_memory_space(amlogic_pcie); |
| |
| amlogic_pcie_assert_reset(amlogic_pcie); |
| |
| /* check if the link is up or not */ |
| if (!dw_pcie_wait_for_link(pp)) |
| return 0; |
| |
| return -ETIMEDOUT; |
| } |
| |
| static irqreturn_t amlogic_pcie_msi_irq_handler(int irq, void *arg) |
| { |
| struct amlogic_pcie *amlogic_pcie = arg; |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| |
| return dw_handle_msi_irq(pp); |
| } |
| |
| static void amlogic_pcie_msi_init(struct amlogic_pcie *amlogic_pcie) |
| { |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| |
| dw_pcie_msi_init(pp); |
| } |
| |
| static void amlogic_pcie_enable_interrupts(struct amlogic_pcie *amlogic_pcie) |
| { |
| if (IS_ENABLED(CONFIG_PCI_MSI)) |
| amlogic_pcie_msi_init(amlogic_pcie); |
| } |
| |
| static u32 amlogic_pcie_readl_rc(struct pcie_port *pp, u32 reg) |
| { |
| u32 val; |
| |
| val = readl(pp->dbi_base + reg); |
| return val; |
| } |
| |
| static void amlogic_pcie_writel_rc(struct pcie_port *pp, u32 reg, u32 val) |
| { |
| writel(val, pp->dbi_base + reg); |
| } |
| |
| static int amlogic_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, |
| u32 *val) |
| { |
| int ret; |
| struct amlogic_pcie *amlogic_pcie = to_amlogic_pcie(pp); |
| |
| if (amlogic_pcie->device_attch == 0) |
| return 0; |
| |
| /* the device class is not reported correctly from the register */ |
| if (where == PCI_CLASS_REVISION) { |
| dev_info(pp->dev, "the device class is not reported correctly from the register\n"); |
| *val = readl(pp->dbi_base + PCI_CLASS_REVISION); |
| *val &= 0xff; /* keep revision id */ |
| *val |= PCI_CLASS_BRIDGE_PCI << 16; |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| ret = dw_pcie_cfg_read(pp->dbi_base + where, size, val); |
| return ret; |
| } |
| |
| static int amlogic_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, |
| u32 val) |
| { |
| int ret; |
| struct amlogic_pcie *amlogic_pcie = to_amlogic_pcie(pp); |
| |
| if (amlogic_pcie->device_attch == 0) |
| return 0; |
| |
| ret = dw_pcie_cfg_write(pp->dbi_base + where, size, val); |
| return ret; |
| } |
| |
| int amlogic_pcie_link_up(struct pcie_port *pp) |
| { |
| u32 smlh_up = 0; |
| u32 rdlh_up = 0; |
| u32 ltssm_up = 0; |
| u32 speed_okay = 0; |
| u32 current_data_rate; |
| int cnt = 0; |
| u32 val = 0; |
| u32 linkup = 0; |
| struct amlogic_pcie *amlogic_pcie = to_amlogic_pcie(pp); |
| |
| val = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); |
| linkup = ((val & PCIE_PHY_DEBUG_R1_LINK_UP) && |
| (!(val & PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING))); |
| if (linkup) |
| return linkup; |
| |
| while (smlh_up == 0 || rdlh_up == 0 |
| || ltssm_up == 0 || speed_okay == 0) { |
| udelay(20); |
| smlh_up = amlogic_cfg_readl(amlogic_pcie, PCIE_CFG_STATUS12); |
| smlh_up = (smlh_up >> 6) & 0x1; |
| |
| rdlh_up = amlogic_cfg_readl(amlogic_pcie, PCIE_CFG_STATUS12); |
| rdlh_up = (rdlh_up >> 16) & 0x1; |
| ltssm_up = amlogic_cfg_readl(amlogic_pcie, PCIE_CFG_STATUS12); |
| ltssm_up = ((ltssm_up >> 10) & 0x1f) == 0x11 ? 1 : 0; |
| current_data_rate = amlogic_cfg_readl( |
| amlogic_pcie, PCIE_CFG_STATUS17); |
| current_data_rate = (current_data_rate >> 7) & 0x1; |
| |
| if ((current_data_rate == PCIE_GEN2) || |
| (current_data_rate == PCIE_GEN1)) |
| speed_okay = 1; |
| |
| if (smlh_up) |
| dev_dbg(pp->dev, "smlh_link_up is on\n"); |
| if (rdlh_up) |
| dev_dbg(pp->dev, "rdlh_link_up is on\n"); |
| if (ltssm_up) |
| dev_dbg(pp->dev, "ltssm_up is on\n"); |
| if (speed_okay) |
| dev_dbg(pp->dev, "speed_okay\n"); |
| |
| cnt++; |
| |
| if (cnt >= WAIT_LINKUP_TIMEOUT) { |
| dev_err(pp->dev, "Error: Wait linkup timeout.\n"); |
| return 0; |
| } |
| |
| udelay(20); |
| } |
| |
| if (current_data_rate == PCIE_GEN2) |
| dev_info(pp->dev, "PCIE SPEED IS GEN2\n"); |
| |
| return 1; |
| } |
| |
| static void amlogic_pcie_host_init(struct pcie_port *pp) |
| { |
| struct amlogic_pcie *amlogic_pcie = to_amlogic_pcie(pp); |
| int ret; |
| |
| ret = amlogic_pcie_establish_link(amlogic_pcie); |
| if (ret) |
| if (amlogic_pcie->phy->device_attch == 0) |
| return; |
| |
| amlogic_pcie->phy->device_attch = 1; |
| if (!ret) |
| amlogic_pcie->device_attch = 1; |
| |
| amlogic_pcie_enable_interrupts(amlogic_pcie); |
| } |
| |
| static struct pcie_host_ops amlogic_pcie_host_ops = { |
| .readl_rc = amlogic_pcie_readl_rc, |
| .writel_rc = amlogic_pcie_writel_rc, |
| .rd_own_conf = amlogic_pcie_rd_own_conf, |
| .wr_own_conf = amlogic_pcie_wr_own_conf, |
| .link_up = amlogic_pcie_link_up, |
| .host_init = amlogic_pcie_host_init, |
| }; |
| |
| static int __init amlogic_add_pcie_port(struct amlogic_pcie *amlogic_pcie, |
| struct platform_device *pdev) |
| { |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| struct device *dev = pp->dev; |
| int ret; |
| |
| if (IS_ENABLED(CONFIG_PCI_MSI)) { |
| pp->msi_irq = platform_get_irq(pdev, 0); |
| if (!pp->msi_irq) { |
| dev_err(dev, "failed to get msi irq\n"); |
| return -ENODEV; |
| } |
| |
| ret = devm_request_irq(dev, pp->msi_irq, |
| amlogic_pcie_msi_irq_handler, |
| IRQF_SHARED | IRQF_NO_THREAD, |
| "amlogic-pcie", amlogic_pcie); |
| if (ret) { |
| dev_err(dev, "failed to request msi irq\n"); |
| return ret; |
| } |
| } |
| |
| pp->root_bus_nr = -1; |
| pp->ops = &amlogic_pcie_host_ops; |
| pp->dbi_base = amlogic_pcie->elbi_base; |
| |
| ret = dw_pcie_host_init(pp); |
| if (ret) { |
| dev_err(dev, "failed to initialize host\n"); |
| return ret; |
| } |
| |
| if (amlogic_pcie->device_attch == 0) { |
| dev_err(pp->dev, "link timeout, disable PCIE PLL\n"); |
| clk_disable_unprepare(amlogic_pcie->port_clk); |
| clk_disable_unprepare(amlogic_pcie->general_clk); |
| clk_disable_unprepare(amlogic_pcie->mipi_bandgap_gate); |
| clk_disable_unprepare(amlogic_pcie->mipi_gate); |
| clk_disable_unprepare(amlogic_pcie->bus_clk); |
| clk_disable_unprepare(amlogic_pcie->clk); |
| if (amlogic_pcie->pcie_num == 2) { |
| if (amlogic_pcie->phy->device_attch == 0) { |
| dev_err(pp->dev, "power down pcie phy\n"); |
| writel(0x1d, pcie_aml_regs.pcie_phy_r[0]); |
| amlogic_pcie->phy->power_state = 0; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int __init amlogic_pcie_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct amlogic_pcie *amlogic_pcie; |
| struct pcie_port *pp; |
| struct device_node *np = dev->of_node; |
| struct pcie_phy *phy; |
| struct resource *elbi_base; |
| struct resource *phy_base; |
| struct resource *cfg_base; |
| struct resource *reset_base; |
| int ret; |
| int pcie_num = 0; |
| int num_lanes = 0; |
| int gpio_type = 0; |
| unsigned long rate = 100000000; |
| int err; |
| int j = 0; |
| u32 val = 0; |
| static u32 port_num; |
| u32 pm_enable = 1; |
| |
| dev_info(&pdev->dev, "amlogic_pcie_probe!\n"); |
| |
| amlogic_pcie = devm_kzalloc(dev, sizeof(*amlogic_pcie), GFP_KERNEL); |
| if (!amlogic_pcie) |
| return -ENOMEM; |
| |
| amlogic_pcie->device_attch = 0; |
| pp = &amlogic_pcie->pp; |
| pp->dev = dev; |
| port_num++; |
| amlogic_pcie->port_num = port_num; |
| if (amlogic_pcie->port_num == 1) { |
| phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); |
| if (!phy) { |
| port_num--; |
| return -ENOMEM; |
| } |
| g_pcie_phy = phy; |
| } |
| |
| amlogic_pcie->phy = g_pcie_phy; |
| |
| ret = of_property_read_u32(np, "pcie-num", &pcie_num); |
| if (ret) |
| amlogic_pcie->pcie_num = 0; |
| |
| amlogic_pcie->pcie_num = pcie_num; |
| |
| ret = of_property_read_u32(np, "pm-enable", &pm_enable); |
| if (ret) |
| amlogic_pcie->pm_enable = 1; |
| else |
| amlogic_pcie->pm_enable = pm_enable; |
| |
| ret = of_property_read_u32(np, "num-lanes", &num_lanes); |
| if (ret) |
| pp->lanes = 0; |
| pp->lanes = num_lanes; |
| |
| if (!amlogic_pcie->phy->phy_base) { |
| phy_base = platform_get_resource_byname( |
| pdev, IORESOURCE_MEM, "phy"); |
| amlogic_pcie->phy->phy_base = |
| devm_ioremap_resource(dev, phy_base); |
| if (IS_ERR(amlogic_pcie->phy->phy_base)) { |
| ret = PTR_ERR(amlogic_pcie->phy->phy_base); |
| port_num--; |
| return ret; |
| } |
| } |
| |
| if (!amlogic_pcie->phy->power_state) { |
| for (j = 0; j < 7; j++) |
| pcie_aml_regs.pcie_phy_r[j] = (void __iomem *) |
| ((unsigned long)amlogic_pcie->phy->phy_base |
| + 4*j); |
| writel(0x1c, pcie_aml_regs.pcie_phy_r[0]); |
| amlogic_pcie->phy->power_state = 1; |
| } |
| |
| ret = of_property_read_u32(np, "gpio-type", &gpio_type); |
| amlogic_pcie->gpio_type = gpio_type; |
| |
| amlogic_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); |
| |
| if (!amlogic_pcie->phy->reset_base) { |
| reset_base = platform_get_resource_byname( |
| pdev, IORESOURCE_MEM, "reset"); |
| amlogic_pcie->phy->reset_base = devm_ioremap_resource( |
| dev, reset_base); |
| if (IS_ERR(amlogic_pcie->phy->reset_base)) { |
| ret = PTR_ERR(amlogic_pcie->phy->reset_base); |
| goto fail_pcie; |
| } |
| } |
| |
| /* RESET0[1,2,6,7] = 0*/ |
| if (!amlogic_pcie->phy->reset_state) { |
| val = readl(amlogic_pcie->phy->reset_base); |
| val &= ~((0x3<<6) | (0x3<<1)); |
| writel(val, amlogic_pcie->phy->reset_base); |
| } |
| |
| amlogic_pcie->bus_clk = devm_clk_get(dev, "pcie_refpll"); |
| if (IS_ERR(amlogic_pcie->bus_clk)) { |
| dev_err(dev, "Failed to get pcie bus clock\n"); |
| ret = PTR_ERR(amlogic_pcie->bus_clk); |
| goto fail_pcie; |
| } |
| |
| if (!amlogic_pcie->phy->reset_state) { |
| err = clk_set_rate(amlogic_pcie->bus_clk, rate); |
| if (err) { |
| ret = err; |
| goto fail_pcie; |
| } |
| |
| if (clk_get_rate(amlogic_pcie->bus_clk) != rate) { |
| ret = -ENODEV; |
| goto fail_pcie; |
| } |
| } |
| |
| ret = clk_prepare_enable(amlogic_pcie->bus_clk); |
| if (ret) |
| goto fail_pcie; |
| |
| amlogic_pcie->mipi_gate = devm_clk_get(dev, "pcie_mipi_enable_gate"); |
| if (IS_ERR(amlogic_pcie->mipi_gate)) { |
| dev_err(dev, "Failed to get pcie mipi gate clock\n"); |
| ret = PTR_ERR(amlogic_pcie->mipi_gate); |
| goto fail_bus_clk; |
| } |
| /*pcie pll: mipi enable and bandgap share with mipi clk */ |
| ret = clk_prepare_enable(amlogic_pcie->mipi_gate); |
| if (ret) |
| goto fail_bus_clk; |
| |
| amlogic_pcie->mipi_bandgap_gate = devm_clk_get(dev, |
| "pcie_mipi_bandgap_gate"); |
| if (IS_ERR(amlogic_pcie->mipi_bandgap_gate)) { |
| dev_err(dev, "Failed to get pcie mipi bandgap gate clock\n"); |
| ret = PTR_ERR(amlogic_pcie->mipi_bandgap_gate); |
| goto fail_mipi_gate; |
| } |
| /*pcie pll: mipi enable and bandgap share with mipi clk */ |
| ret = clk_prepare_enable(amlogic_pcie->mipi_bandgap_gate); |
| if (ret) |
| goto fail_mipi_gate; |
| |
| /*RESET0[6,7] = 1*/ |
| if (!amlogic_pcie->phy->reset_state) { |
| val = readl(amlogic_pcie->phy->reset_base); |
| val |= (0x3<<6); |
| writel(val, amlogic_pcie->phy->reset_base); |
| mdelay(10); |
| } |
| |
| amlogic_pcie->general_clk = devm_clk_get(dev, "pcie_general"); |
| if (IS_ERR(amlogic_pcie->general_clk)) { |
| dev_err(dev, "Failed to get pcie general clock\n"); |
| ret = PTR_ERR(amlogic_pcie->general_clk); |
| goto fail_mipi_bandgap_gate; |
| } |
| |
| ret = clk_prepare_enable(amlogic_pcie->general_clk); |
| if (ret) |
| goto fail_mipi_bandgap_gate; |
| |
| amlogic_pcie->clk = devm_clk_get(dev, "pcie"); |
| if (IS_ERR(amlogic_pcie->clk)) { |
| dev_err(dev, "Failed to get pcie rc clock\n"); |
| ret = PTR_ERR(amlogic_pcie->clk); |
| goto fail_general_clk; |
| } |
| |
| ret = clk_prepare_enable(amlogic_pcie->clk); |
| if (ret) |
| goto fail_general_clk; |
| |
| /*RESET0[1,2] = 1*/ |
| if (amlogic_pcie->pcie_num == 1) { |
| val = readl(amlogic_pcie->phy->reset_base); |
| val |= (0x1<<1); |
| writel(val, amlogic_pcie->phy->reset_base); |
| mdelay(10); |
| } else { |
| val = readl(amlogic_pcie->phy->reset_base); |
| val |= (0x1<<2); |
| writel(val, amlogic_pcie->phy->reset_base); |
| mdelay(10); |
| } |
| |
| amlogic_pcie->phy->reset_state = 1; |
| amlogic_pcie->port_clk = devm_clk_get(dev, "port"); |
| |
| if (IS_ERR(amlogic_pcie->port_clk)) { |
| dev_err(dev, "Failed to get pcie rc clock\n"); |
| ret = PTR_ERR(amlogic_pcie->port_clk); |
| goto fail_clk; |
| } |
| |
| ret = clk_prepare_enable(amlogic_pcie->port_clk); |
| if (ret) |
| goto fail_clk; |
| |
| elbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); |
| amlogic_pcie->elbi_base = devm_ioremap_resource(dev, elbi_base); |
| if (IS_ERR(amlogic_pcie->elbi_base)) { |
| ret = PTR_ERR(amlogic_pcie->elbi_base); |
| goto fail_port_clk; |
| } |
| |
| cfg_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); |
| amlogic_pcie->cfg_base = devm_ioremap_resource(dev, cfg_base); |
| if (IS_ERR(amlogic_pcie->cfg_base)) { |
| ret = PTR_ERR(amlogic_pcie->cfg_base); |
| goto fail_port_clk; |
| } |
| |
| ret = amlogic_add_pcie_port(amlogic_pcie, pdev); |
| if (ret < 0) |
| goto fail_port_clk; |
| |
| platform_set_drvdata(pdev, amlogic_pcie); |
| device_create_file(&pdev->dev, &dev_attr_phyread); |
| device_create_file(&pdev->dev, &dev_attr_phywrite); |
| return 0; |
| |
| fail_port_clk: |
| clk_disable_unprepare(amlogic_pcie->port_clk); |
| fail_clk: |
| clk_disable_unprepare(amlogic_pcie->clk); |
| fail_general_clk: |
| clk_disable_unprepare(amlogic_pcie->general_clk); |
| fail_mipi_bandgap_gate: |
| clk_disable_unprepare(amlogic_pcie->mipi_bandgap_gate); |
| fail_mipi_gate: |
| clk_disable_unprepare(amlogic_pcie->mipi_gate); |
| fail_bus_clk: |
| clk_disable_unprepare(amlogic_pcie->bus_clk); |
| fail_pcie: |
| port_num--; |
| |
| return ret; |
| } |
| |
| static int __exit amlogic_pcie_remove(struct platform_device *pdev) |
| { |
| struct amlogic_pcie *amlogic_pcie = platform_get_drvdata(pdev); |
| |
| if (amlogic_pcie->phy->power_state == 0) { |
| dev_info(&pdev->dev, "PCIE phy power off, no remove\n"); |
| return 0; |
| } |
| |
| device_remove_file(&pdev->dev, &dev_attr_phywrite); |
| device_remove_file(&pdev->dev, &dev_attr_phyread); |
| |
| clk_disable_unprepare(amlogic_pcie->port_clk); |
| clk_disable_unprepare(amlogic_pcie->clk); |
| clk_disable_unprepare(amlogic_pcie->general_clk); |
| clk_disable_unprepare(amlogic_pcie->mipi_bandgap_gate); |
| clk_disable_unprepare(amlogic_pcie->mipi_gate); |
| clk_disable_unprepare(amlogic_pcie->bus_clk); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int amlogic_pcie_suspend(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct amlogic_pcie *amlogic_pcie = platform_get_drvdata(pdev); |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| u32 val; |
| |
| if (!amlogic_pcie->pm_enable) { |
| dev_info(dev, "don't suspend amlogic pcie\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->device_attch == 0) { |
| dev_info(dev, "controller power off, no suspend\n"); |
| return 0; |
| } |
| |
| dev_info(dev, "amlogic_pcie_suspend\n"); |
| |
| /* clear MSE */ |
| val = dw_pcie_readl_rc(pp, PCI_COMMAND); |
| val &= ~PCI_COMMAND_MEMORY; |
| dw_pcie_writel_rc(pp, PCI_COMMAND, val); |
| |
| return 0; |
| } |
| |
| static int amlogic_pcie_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct amlogic_pcie *amlogic_pcie = platform_get_drvdata(pdev); |
| struct pcie_port *pp = &amlogic_pcie->pp; |
| u32 val; |
| |
| if (!amlogic_pcie->pm_enable) { |
| dev_info(dev, "don't resume amlogic pcie\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->device_attch == 0) { |
| dev_info(dev, "controller power off, no resume\n"); |
| return 0; |
| } |
| |
| dev_info(dev, "amlogic_pcie_resume\n"); |
| |
| /* set MSE */ |
| val = dw_pcie_readl_rc(pp, PCI_COMMAND); |
| val |= PCI_COMMAND_MEMORY; |
| dw_pcie_writel_rc(pp, PCI_COMMAND, val); |
| |
| return 0; |
| } |
| |
| static int amlogic_pcie_suspend_noirq(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct amlogic_pcie *amlogic_pcie = platform_get_drvdata(pdev); |
| |
| if (!amlogic_pcie->pm_enable) { |
| dev_info(dev, "don't noirq suspend amlogic pcie\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->phy->device_attch == 0) { |
| dev_info(dev, "PCIE phy power off, no suspend noirq\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->device_attch == 0) { |
| dev_info(dev, "controller power off, no suspend noirq\n"); |
| if (amlogic_pcie->pcie_num == 1) { |
| writel(0x1d, pcie_aml_regs.pcie_phy_r[0]); |
| amlogic_pcie->phy->power_state = 0; |
| } |
| return 0; |
| } |
| |
| dev_info(dev, "amlogic_pcie_suspend_noirq\n"); |
| |
| clk_disable_unprepare(amlogic_pcie->port_clk); |
| clk_disable_unprepare(amlogic_pcie->clk); |
| clk_disable_unprepare(amlogic_pcie->general_clk); |
| clk_disable_unprepare(amlogic_pcie->mipi_bandgap_gate); |
| clk_disable_unprepare(amlogic_pcie->mipi_gate); |
| clk_disable_unprepare(amlogic_pcie->bus_clk); |
| amlogic_pcie->phy->reset_state = 0; |
| |
| if (amlogic_pcie->pcie_num == 1) { |
| writel(0x1d, pcie_aml_regs.pcie_phy_r[0]); |
| amlogic_pcie->phy->power_state = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int amlogic_pcie_resume_noirq(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct amlogic_pcie *amlogic_pcie = platform_get_drvdata(pdev); |
| unsigned long rate = 100000000; |
| |
| if (!amlogic_pcie->pm_enable) { |
| dev_info(dev, "don't noirq resume amlogic pcie\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->phy->device_attch == 0) { |
| dev_info(dev, "PCIE phy power off, no resume noirq\n"); |
| return 0; |
| } |
| |
| if (amlogic_pcie->pcie_num == 1) { |
| writel(0x1c, pcie_aml_regs.pcie_phy_r[0]); |
| amlogic_pcie->phy->power_state = 1; |
| udelay(500); |
| } |
| |
| if (amlogic_pcie->device_attch == 0) { |
| dev_info(dev, "controller power off, no resume noirq\n"); |
| return 0; |
| } |
| |
| dev_info(dev, "amlogic_pcie_resume_noirq\n"); |
| if (!amlogic_pcie->phy->reset_state) |
| clk_set_rate(amlogic_pcie->bus_clk, rate); |
| |
| amlogic_pcie->phy->reset_state = 1; |
| |
| clk_prepare_enable(amlogic_pcie->bus_clk); |
| clk_prepare_enable(amlogic_pcie->mipi_gate); |
| clk_prepare_enable(amlogic_pcie->mipi_bandgap_gate); |
| clk_prepare_enable(amlogic_pcie->general_clk); |
| clk_prepare_enable(amlogic_pcie->clk); |
| clk_prepare_enable(amlogic_pcie->port_clk); |
| udelay(500); |
| |
| return 0; |
| } |
| |
| #else |
| #define amlogic_pcie_suspend NULL |
| #define amlogic_pcie_resume NULL |
| #define amlogic_pcie_suspend_noirq NULL |
| #define amlogic_pcie_resume_noirq NULL |
| #endif |
| |
| static const struct dev_pm_ops amlogic_pcie_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(amlogic_pcie_suspend, amlogic_pcie_resume) |
| SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(amlogic_pcie_suspend_noirq, |
| amlogic_pcie_resume_noirq) |
| }; |
| |
| |
| |
| static const struct of_device_id amlogic_pcie_of_match[] = { |
| { .compatible = "amlogic, amlogic-pcie", }, |
| {}, |
| }; |
| |
| static struct platform_driver amlogic_pcie_driver = { |
| .remove = __exit_p(amlogic_pcie_remove), |
| .driver = { |
| .name = "amlogic-pcie", |
| .of_match_table = amlogic_pcie_of_match, |
| .pm = &amlogic_pcie_pm_ops, |
| }, |
| }; |
| |
| /* AMLOGIC PCIe driver does not allow module unload */ |
| static int __init amlogic_pcie_init(void) |
| { |
| return platform_driver_probe(&amlogic_pcie_driver, amlogic_pcie_probe); |
| } |
| subsys_initcall(amlogic_pcie_init); |