diff options
Diffstat (limited to 'drivers/misc/jesd204b/jesd_phy.c')
-rw-r--r-- | drivers/misc/jesd204b/jesd_phy.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/drivers/misc/jesd204b/jesd_phy.c b/drivers/misc/jesd204b/jesd_phy.c new file mode 100644 index 000000000000..c35d9433d0fc --- /dev/null +++ b/drivers/misc/jesd204b/jesd_phy.c @@ -0,0 +1,384 @@ +/* + * Jesd204b phy support + * + * Copyright (C) 2014 - 2015 Xilinx, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/delay.h> +#include "jesd_phy.h" +#include "gtx7s_cpll_bands.h" +#include "gtx7s_qpll_bands.h" + +#define PLATFORM_JESD204_PHY_ADDR 0x41E10000 +#define JESD_PHY_LOOP_OFF 0 +#define JESD_PHY_LOOP_PCS 1 +#define JESD_PHY_LOOP_PMA 2 +#define JESD_PHY_LOOP_MAX 2 + +static inline void jesd204b_phy_write(struct jesd204b_phy_state *st, + unsigned reg, unsigned val) +{ + iowrite32(val, st->phy + reg); +} + +static inline unsigned int jesd204b_phy_read(struct jesd204b_phy_state *st, + unsigned reg) +{ + return ioread32(st->phy + reg); +} + +#define NUM_GT_CHANNELS 8 + +#define QPLL 0x3 /* QPLL (7 series) QPLL1 (UltraScale) */ +#define QPLL0 0x2 /* (UltraScale Only) */ +#define CPLL 0x0 + +#define DRPREAD BIT(30) +#define DRPWRITE BIT(31) + +#define NR_COMMON_DRP_INTERFACES 0x008 +#define NR_TRANS_DRP_INTERFACES 0x00C + +#define CHANNEL_DRP_BASE 0x200 +#define CHANNEL_DRP_ADDR 0x204 +#define CHANNEL_DRP_DREAD 0x20C +#define CHANNEL_DRP_DWRITE 0x208 +#define CHANNEL_DRP_STAT 0x214 + +#define CHANNEL_XCVR_SEL 0x400 +#define CHANNEL_XCVR_TXPLL 0x40C +#define CHANNEL_XCVR_RXPLL 0x410 +#define CHANNEL_XCVR_LOOPB 0x41C + +static u32 read_channel_drp_reg(struct jesd204b_phy_state *st, u32 addr) +{ + u32 temp; + + jesd204b_phy_write(st, CHANNEL_DRP_ADDR, (DRPREAD | addr)); + temp = jesd204b_phy_read(st, CHANNEL_DRP_DREAD); + return temp; +} + +static void write_channel_drp_reg(struct jesd204b_phy_state *st, u32 addr, + u32 data) +{ + u32 loop = 10; + + jesd204b_phy_write(st, CHANNEL_DRP_DWRITE, data); + jesd204b_phy_write(st, CHANNEL_DRP_ADDR, (DRPWRITE | addr)); + + do { + if (!jesd204b_phy_read(st, CHANNEL_DRP_STAT)) + break; + msleep(1); + } while (loop--); + + if (!loop) + dev_err(st->dev, "DRP wait timeout\n"); +} + +static void read_plls(struct jesd204b_phy_state *st) +{ + int i; + int pll = st->pll; + u32 no_of_common_drp_interfaces = 1; + + if (st->pll == CPLL) + no_of_common_drp_interfaces = jesd204b_phy_read( + st, NR_TRANS_DRP_INTERFACES); + else + no_of_common_drp_interfaces = jesd204b_phy_read( + st, NR_COMMON_DRP_INTERFACES); + + for (i = 0; i < no_of_common_drp_interfaces; i++) { + jesd204b_phy_write(st, CHANNEL_XCVR_SEL, i); + pll = jesd204b_phy_read(st, CHANNEL_XCVR_TXPLL); + pll = jesd204b_phy_read(st, CHANNEL_XCVR_RXPLL); + } +} + +static void configure_plls(struct jesd204b_phy_state *st, u32 pll) +{ + int i; + u32 no_of_common_drp_interfaces; + + if (pll == CPLL) + no_of_common_drp_interfaces = jesd204b_phy_read( + st, NR_TRANS_DRP_INTERFACES); + else + no_of_common_drp_interfaces = jesd204b_phy_read( + st, NR_COMMON_DRP_INTERFACES); + + for (i = 0; i < no_of_common_drp_interfaces; i++) { + jesd204b_phy_write(st, CHANNEL_XCVR_SEL, i); + jesd204b_phy_write(st, CHANNEL_XCVR_TXPLL, pll); + jesd204b_phy_write(st, CHANNEL_XCVR_RXPLL, pll); + } +} + +static void configure_channel_drp(struct jesd204b_phy_state *st, u32 setting) +{ + u32 i, j, addr, temp, no_of_common_drp_interfaces; + u32 no_channel_drp_reg = GTX7S_QPLL_NUM_CHANNEL_DRP_REGS; + + no_of_common_drp_interfaces = jesd204b_phy_read( + st, NR_TRANS_DRP_INTERFACES); + + if (st->pll == CPLL) + no_channel_drp_reg = GTX7S_CPLL_NUM_CHANNEL_DRP_REGS; + for (i = 0; i < no_of_common_drp_interfaces; i++) { + jesd204b_phy_write(st, CHANNEL_DRP_BASE, i); + for (j = 0; j < no_channel_drp_reg; j++) { + /* Get the register address */ + if (st->pll == QPLL) { + addr = get_gtx7s_qpll_address_lut(j); + + /* Read the register */ + temp = read_channel_drp_reg(st, addr); + + temp &= (0xFFFF ^ (get_gtx7s_qpll_mask_lut(j))); + temp |= ((get_gtx7s_qpll_param_lut(j, setting) + << get_gtx7s_qpll_offset_lut(j)) + & get_gtx7s_qpll_mask_lut(j)); + } else { + addr = get_gtx7s_cpll_address_lut(j); + + temp = read_channel_drp_reg(st, addr); + + temp &= (0xFFFF ^ (get_gtx7s_cpll_mask_lut(j))); + temp |= ((get_gtx7s_cpll_param_lut(j, setting) + << get_gtx7s_cpll_offset_lut(j)) + & get_gtx7s_cpll_mask_lut(j)); + } + write_channel_drp_reg(st, addr, temp); + } + } +} + +void jesd204_phy_set_speed(struct jesd204b_phy_state *st, u32 band) +{ + /* make sure we have the correct PLL's selected. */ + configure_channel_drp(st, band); +} + +static void jesd204_phy_init(struct jesd204b_phy_state *st, int line_rate) +{ + jesd204_phy_set_speed(st, line_rate); +} + +int jesd204_phy_set_loop(struct jesd204b_phy_state *st, u32 loopval) +{ + int i; + u32 no_of_channels; + + no_of_channels = jesd204b_phy_read(st, NR_COMMON_DRP_INTERFACES); + + if (loopval > JESD_PHY_LOOP_MAX) + return -EINVAL; + + for (i = 0; i < no_of_channels ; i++) { + jesd204b_phy_write(st, CHANNEL_XCVR_SEL, i); + jesd204b_phy_write(st, CHANNEL_XCVR_LOOPB, loopval); + } + return 0; +} + +static ssize_t jesd204b_pll_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct jesd204b_phy_state *st = dev_get_drvdata(dev); + + read_plls(st); + if (st->pll == CPLL) + return sprintf(buf, "cpll\n"); + return sprintf(buf, "qpll\n"); +} + +static ssize_t jesd204b_configure_pll(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct jesd204b_phy_state *st = dev_get_drvdata(dev); + unsigned val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (!ret) + return 0; + + if (val > QPLL) { + dev_err(dev, "Setting the pll to %d valid values\n" + "00 = CPLL\n" + "10 = QPLL0 (UltraScale Only)\n" + "11 = QPLL (7 series) QPLL1 (UltraScale)\n", val); + return 0; + } + st->pll = val; + configure_plls(st, val); + + return count; +} + +static DEVICE_ATTR(configure_pll, S_IWUSR | S_IRUSR, jesd204b_pll_read, + jesd204b_configure_pll); + +static ssize_t jesd204b_linerate_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct jesd204b_phy_state *st = dev_get_drvdata(dev); + + return sprintf(buf, "0x%X\n", st->band); +} + +static ssize_t jesd204b_linerate_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct jesd204b_phy_state *st = dev_get_drvdata(dev); + int ret; + /* Low frequencies are not supported by qpll */ + + ret = kstrtouint(buf, 0, &st->band); + if (ret) + return ret; + + dev_info(dev, "Setting the line rate to band to %d\n", st->band); + /* QPLL - freq options in phy + * 62.5 + * 78.125 + * 94.697 + * 97.656 + * 125.000 + * 156.25 + * 187.5 + * 189.394 + * 195.313 + * 234.375 + * 250.000 + * 284.091 + * 292.969 + */ + if (st->band == 2) + clk_set_rate(st->clk, 62500000); /* 2.5G */ + else if (st->band == 4) + clk_set_rate(st->clk, 97656000); /* 3.9G */ + else if (st->band == 6) + clk_set_rate(st->clk, 125000000); /* 5G */ + else if (st->band == 7) + clk_set_rate(st->clk, 156250000); /* 6.25G */ + else if (st->band == 8) + clk_set_rate(st->clk, 195313000); /* 7.812G */ + else if (st->band == 9) + clk_set_rate(st->clk, 250000000);/* 10G */ + + jesd204_phy_init(st, st->band); + + return count; +} + +static DEVICE_ATTR(line_rate_band, S_IWUSR | S_IRUSR, jesd204b_linerate_read, + jesd204b_linerate_write); + +/* Match table for of_platform binding */ +static const struct of_device_id jesd204b_phy_of_match[] = { + { .compatible = "xlnx,jesd204-phy-2.0", }, + { /* end of list */ }, +}; + +static int jesd204b_phy_probe(struct platform_device *pdev) +{ + struct jesd204b_phy_state *st; + struct resource *mem; /* IO mem resources */ + int ret; + u32 ref_clk; + + st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(st->clk)) + return -EPROBE_DEFER; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + st->phy = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(st->phy)) { + dev_err(&pdev->dev, "Failed ioremap\n"); + return PTR_ERR(st->phy); + } + st->dev = &pdev->dev; + platform_set_drvdata(pdev, st); + + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,lanes", + &st->lanes); + if (ret) { + dev_err(&pdev->dev, "Failed to read required dt property\n"); + return ret; + } + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,pll-selection", + &st->pll); + if (ret) { + dev_err(&pdev->dev, "Failed to read required dt property\n"); + return ret; + } + ret = of_property_read_u32(pdev->dev.of_node, "xlnx,gt-refclk-freq", + &ref_clk); + if (ret) { + dev_err(&pdev->dev, "Failed to read required dt property\n"); + return ret; + } + + clk_set_rate(st->clk, (unsigned long)ref_clk); + device_create_file(&pdev->dev, &dev_attr_configure_pll); + device_create_file(&pdev->dev, &dev_attr_line_rate_band); + + ret = clk_prepare_enable(st->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock.\n"); + return ret; + } + + return 0; +} + +static int jesd204b_phy_remove(struct platform_device *pdev) +{ + struct jesd204b_phy_state *st = platform_get_drvdata(pdev); + + clk_disable_unprepare(st->clk); + clk_put(st->clk); + device_remove_file(&pdev->dev, &dev_attr_configure_pll); + device_remove_file(&pdev->dev, &dev_attr_line_rate_band); + return 0; +} + +static struct platform_driver jesd204b_driver = { + .driver = { + .name = "jesd204b_phy", + .of_match_table = jesd204b_phy_of_match, + }, + .probe = jesd204b_phy_probe, + .remove = jesd204b_phy_remove, +}; + +module_platform_driver(jesd204b_driver); + +MODULE_AUTHOR("Shubhrajyoti Datta <shubhraj@xilinx.com>"); +MODULE_DESCRIPTION("AXI-JESD204B Phy Interface Module"); +MODULE_LICENSE("GPL"); |