lkml.org 
[lkml]   [2016]   [Oct]   [7]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 01/10] mfd: Add Juniper SAM FPGA MFD driver
    Date
    From: Maryam Seraj <mseraj@juniper.net>

    Add Juniper's SAM FPGA multi-function driver.

    The SAM FPGAs are present on different FPC/SIB cards from the Juniper's
    PTX series of routers. Depending on the card type and FPGA revision,
    they include the following functional blocks:

    * I2C SAM accelerator - multiple I2C masters and multiplexers
    * GPIO
    * Flash - hardware wrapper interface for the Altera's EPCS flashes
    (used for configuration flash updates)
    * MDIO - multiple MDIO masters

    Signed-off-by: Maryam Seraj <mseraj@juniper.net>
    Signed-off-by: Debjit Ghosh <dghosh@juniper.net>
    Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
    Signed-off-by: Guenter Roeck <groeck@juniper.net>
    Signed-off-by: Rajat Jain <rajatjain@juniper.net>
    [Ported from Juniper kernel]
    Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
    ---
    drivers/mfd/Kconfig | 16 +
    drivers/mfd/Makefile | 1 +
    drivers/mfd/sam-core.c | 997 ++++++++++++++++++++++++++++++++++++++++++++++++
    include/linux/mfd/sam.h | 30 ++
    4 files changed, 1044 insertions(+)
    create mode 100644 drivers/mfd/sam-core.c
    create mode 100644 include/linux/mfd/sam.h

    diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
    index 438666a..75b46a1 100644
    --- a/drivers/mfd/Kconfig
    +++ b/drivers/mfd/Kconfig
    @@ -1355,6 +1355,22 @@ config MFD_JUNIPER_CPLD
    This driver can be built as a module. If built as a module it will be
    called "ptxpmb-cpld"

    +config MFD_JUNIPER_SAM
    + tristate "Juniper SAM FPGA"
    + depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
    + default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
    + select MFD_CORE
    + select I2C_SAM
    + select GPIO_SAM
    + select MTD_SAM_FLASH
    + select MDIO_SAM
    + help
    + Select this to enable the SAM FPGA multi-function kernel driver.
    + This FPGA is used on the PTX FPC board.
    +
    + This driver can be built as a module. If built as a module it will be
    + called "sam-core"
    +
    config MFD_TWL4030_AUDIO
    bool "TI TWL4030 Audio"
    depends on TWL4030_CORE
    diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
    index 62decc9..71a8ba6 100644
    --- a/drivers/mfd/Makefile
    +++ b/drivers/mfd/Makefile
    @@ -149,6 +149,7 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o
    obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o
    obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
    obj-$(CONFIG_MFD_JUNIPER_CPLD) += ptxpmb-cpld-core.o
    +obj-$(CONFIG_MFD_JUNIPER_SAM) += sam-core.o
    obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
    # ab8500-core need to come after db8500-prcmu (which provides the channel)
    obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
    diff --git a/drivers/mfd/sam-core.c b/drivers/mfd/sam-core.c
    new file mode 100644
    index 0000000..2ea2b1b
    --- /dev/null
    +++ b/drivers/mfd/sam-core.c
    @@ -0,0 +1,997 @@
    +#include <linux/device.h>
    +#include <linux/module.h>
    +#include <linux/pci.h>
    +#include <linux/delay.h>
    +#include <linux/io.h>
    +#include <linux/interrupt.h>
    +#include <linux/mfd/core.h>
    +#include <linux/mfd/sam.h>
    +#include <linux/jnx/pci_ids.h>
    +#include <linux/of.h>
    +
    +#define DRIVER_DESC "SAM FPGA MFD core driver"
    +#define DRIVER_VERSION "0.02.2"
    +#define DRIVER_AUTHOR "Maryam Seraj <mseraj@juniper.net>"
    +
    +#define SAM_FPGA_MODULE_NAME "sam-mfd-core"
    +#define FPGA_MEM_SIZE 0x20000
    +
    +#define SAM_NUM_IRQ 2
    +#define SAM_NUM_MFD_CELLS 3
    +#define SAM_NUM_RESOURCES 2
    +#define SAM_NUM_RESOURCES_NOIRQ 1
    +
    +/* Minimum SAM revisions needed for i2c irq and PMA support */
    +#define SAM_REVISION_I2C_IRQ_MIN 0x0065
    +#define SAM_REVISION_PMA_MIN 0x004d
    +#define SAM_REVISION_MASK 0x0000ffff
    +#define SAM_REVISION(s) ((s)->fpga_rev & SAM_REVISION_MASK)
    +
    +#define SAM_BOARD_ID_MASK 0x000000ff
    +#define SAM_BOARD_ID(s) ((s)->board_id & SAM_BOARD_ID_MASK)
    +#define SAM_BOARD_ID_HENDRICKS_FPC 0x00
    +#define SAM_BOARD_ID_CFP4 0x00
    +#define SAM_BOARD_ID_QSFPP 0x00
    +#define SAM_BOARD_ID_GPCAM 0x01
    +#define SAM_BOARD_ID_SANGRIA_FPC 0x03
    +#define SAM_BOARD_ID_QSFPP_OLD 0x03
    +#define SAM_BOARD_ID_24x10GE_PIC 0x0B
    +#define SAM_BOARD_ID_GLADIATOR_3T 0x11
    +#define SAM_BOARD_ID_MLC 0x21
    +
    +#define SAM_IMG_ID_MASK 0x000000ff
    +#define SAM_IMG_ID_SHIFT 16
    +#define SAM_IMG_ID(s) (((s)->board_id >> SAM_IMG_ID_SHIFT) & \
    + SAM_IMG_ID_MASK)
    +
    +#define SAM_IMG_ID_QSFPP 0x00
    +#define SAM_IMG_ID_GPCAM 0x01
    +#define SAM_IMG_ID_SANGRIA_FPC 0x03
    +#define SAM_IMG_ID_QSFPP_OLD 0x03
    +#define SAM_IMG_ID_GLADIATOR_3T 0x03
    +#define SAM_IMG_ID_HENDRICKS_FPC 0x05
    +#define SAM_IMG_ID_24x10GE_PIC 0x0B
    +#define SAM_IMG_ID_MLC 0x21
    +#define SAM_IMG_ID_CFP4 0x22
    +
    +#define SANGRIA_FPC_PCIE_BUS 0x20
    +
    +struct sam_fpga_data {
    + void __iomem *membase;
    + struct pci_dev *pdev;
    + u32 fpga_rev;
    + u32 board_id;
    + u32 pma_lanes;
    + u32 pma_coefficients;
    + int irq_base;
    + u32 i2c_irq_mask;
    + u32 gpio_irq_mask;
    + int gpio_irq_shift;
    + spinlock_t irq_lock;
    + struct mfd_cell mfd_cells[SAM_NUM_MFD_CELLS];
    + struct resource mfd_i2c_resources[SAM_NUM_RESOURCES];
    + struct resource mfd_gpio_resources[SAM_NUM_RESOURCES];
    + struct resource mfd_mtd_resources[SAM_NUM_RESOURCES_NOIRQ];
    +};
    +
    +#define VERSION_ADDR(s) ((s)->membase + 0x000)
    +#define BOARD_ID_ADDR(s) ((s)->membase + 0x004)
    +#define ICTRL_ADDR(s) ((s)->membase + 0x104)
    +#define ISTAT_ADDR(s) ((s)->membase + 0x108)
    +
    +/* PMA */
    +#define SAM_PMA_CONTROL_REG(s) ((s)->membase + 0x40)
    +#define SAM_PMA_STATUS_REG(s) ((s)->membase + 0x44)
    +
    +#define SAM_PMA_CONTROL_WRITE (1 << 31)
    +#define SAM_PMA_CONTROL_READ (1 << 30)
    +#define SAM_PMA_LANE(lane) (((lane) & 0x07) << 25)
    +#define SAM_PMA_COEFF_MASK ((1 << 25) - 1)
    +
    +#define SAM_PMA_STATUS_BUSY (1 << 31)
    +#define SAM_PMA_STATUS_VALID (1 << 30)
    +
    +#define SAM_PMA_RETRIES 40 /* observed to take 20 - 40 uS typ. */
    +#define SAM_PMA_WAIT_TIME 10 /* uS */
    +
    +/* Constants used for FPGA upgrades */
    +
    +#define SAM_FPGA_FLASH_VALID_BIT 0xA5A5A5A5
    +#define SAM_FPGA_FLASH_VALID_BIT_ADDR 0x7F0000
    +
    +/* FPGA remote upgrade registers */
    +#define SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT 0x08000000
    +#define SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY 0x01000000
    +#define SAM_FPGA_REMOTE_UPGRADE_READ_PARAM 0x80000000
    +#define SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM 0x40000000
    +#define SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET 0x10000000
    +
    +#define SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL (0x04 << 24)
    +#define SAM_FPGA_REMOTE_UPGRADE_ANF (0x05 << 24)
    +#define SAM_FPGA_USER_IMAGE_BASE 0x400000
    +
    +#define SAM_FLASH_BASE 0x0300
    +
    +#define FLASH_ADDR_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x000)
    +#define FLASH_COUNTER_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x004)
    +#define FLASH_CONTROL_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x008)
    +#define FLASH_STATUS_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x00c)
    +#define FLASH_WRITE_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x100)
    +#define FLASH_READ_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x200)
    +
    +#define FLASH_STATUS_BUSY 0x01
    +
    +#define SAM_FLASH_IF_CONTROL_READ 0x00000001
    +
    +/* Upgrade control and management */
    +#define SAM_UPGRADE_BASE 0x0200
    +#define UPGRADE_CONTROL_REG(x) ((x)->membase + SAM_UPGRADE_BASE)
    +#define UPGRADE_STATUS_REG(x) ((x)->membase + SAM_UPGRADE_BASE + 0x0004)
    +
    +/* List of discovered SAM devices */
    +struct sam_core_list {
    + struct list_head node;
    + unsigned long insertion_time;
    + int bus;
    + int devfn;
    + u32 rev;
    + u32 id;
    +};
    +
    +LIST_HEAD(sam_core_list);
    +DEFINE_MUTEX(sam_core_list_mutex);
    +
    +static int sam_core_update_entry(struct sam_fpga_data *sam)
    +{
    + struct sam_core_list *entry = NULL, *e;
    + int ret = 0;
    +
    + mutex_lock(&sam_core_list_mutex);
    + list_for_each_entry(e, &sam_core_list, node) {
    + if (e->devfn == sam->pdev->devfn &&
    + e->bus == sam->pdev->bus->number) {
    + entry = e;
    + break;
    + }
    + }
    +
    + if (!entry) {
    + entry = kzalloc(sizeof(*entry), GFP_KERNEL);
    + if (!entry) {
    + ret = -ENOMEM;
    + goto abort;
    + }
    + list_add(&entry->node, &sam_core_list);
    + }
    +
    + entry->bus = sam->pdev->bus->number;
    + entry->devfn = sam->pdev->devfn;
    + entry->rev = sam->fpga_rev;
    + entry->id = sam->board_id;
    + entry->insertion_time = jiffies;
    +abort:
    + mutex_unlock(&sam_core_list_mutex);
    + return ret;
    +}
    +
    +static bool sam_supports_i2c_irq(struct sam_fpga_data *sam)
    +{
    + switch (sam->pdev->device) {
    + case PCI_DEVICE_ID_JNX_SAM_OMEGA:
    + /* Sochu SHAM, Gladiator SIB, Omega SIB */
    + return true;
    + case PCI_DEVICE_ID_JNX_SAM_X:
    + /* Gladiator 3T FPC */
    + return true;
    + case PCI_DEVICE_ID_JNX_PAM:
    + if (SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
    + return true;
    + return false;
    + case PCI_DEVICE_ID_JNX_SAM:
    + default:
    + /* others depend on image/board ID and FPGA version */
    + break;
    + }
    +
    + switch (SAM_IMG_ID(sam)) {
    + case SAM_IMG_ID_QSFPP:
    + /* QSFPP, GPQAM */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
    + return true;
    + break;
    + case SAM_IMG_ID_GPCAM:
    + /* GPCAM, GPQ28 */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_GPCAM)
    + return true;
    + break;
    + case SAM_IMG_ID_HENDRICKS_FPC:
    + /* Hendricks SAM FPGA version 15 still fails */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
    + return false;
    + break;
    + case SAM_IMG_ID_24x10GE_PIC:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
    + SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
    + return true;
    + break;
    + case SAM_IMG_ID_SANGRIA_FPC:
    + /*
    + * Image and board IDs for Sangria FPC and QSFPP are the same.
    + * Use PCIe bus number for disambiguation.
    + * Bus number for QSFPP would be 0x10 or 0x30 (PIC bus numbers).
    + */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
    + SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN &&
    + sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
    + return true;
    + break;
    + default:
    + /*
    + * For all others, play safe and assume that i2c interrupts
    + * don't work.
    + */
    + break;
    + }
    + return false;
    +}
    +
    +static bool sam_supports_msi(struct sam_fpga_data *sam)
    +{
    + switch (sam->pdev->device) {
    + case PCI_DEVICE_ID_JNX_SAM_OMEGA:
    + /* Sochu SHAM, Gladiator SIB, Omega SIB */
    + return true;
    + case PCI_DEVICE_ID_JNX_SAM_X:
    + /* Gladiator 3T FPC */
    + return true;
    + case PCI_DEVICE_ID_JNX_PAM:
    + /* unknown */
    + return false;
    + case PCI_DEVICE_ID_JNX_SAM:
    + default:
    + break;
    + }
    +
    + switch (SAM_IMG_ID(sam)) {
    + case SAM_IMG_ID_HENDRICKS_FPC:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
    + return false;
    + break;
    + case SAM_IMG_ID_24x10GE_PIC:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC)
    + return false;
    + break;
    + case SAM_IMG_ID_SANGRIA_FPC:
    + /*
    + * Image and board IDs for Sangria FPC and QSFPP are the same.
    + * Use PCIe bus number for disambiguation.
    + */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC
    + && sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
    + return false;
    + break;
    + default:
    + break;
    + }
    + return true;
    +}
    +
    +static bool sam_supports_pma(struct sam_fpga_data *sam)
    +{
    + switch (sam->pdev->device) {
    + case PCI_DEVICE_ID_JNX_SAM_OMEGA:
    + /* Sochu SHAM, Gladiator SIB, Omega SIB */
    + /* Note: marked HW use only on SHAM */
    + return true;
    + case PCI_DEVICE_ID_JNX_SAM_X:
    + /* Gladiator 3T FPC */
    + return true;
    + case PCI_DEVICE_ID_JNX_PAM:
    + return false;
    + case PCI_DEVICE_ID_JNX_SAM:
    + break;
    + default:
    + /* play safe */
    + return false;
    + }
    +
    + switch (SAM_IMG_ID(sam)) {
    + case SAM_IMG_ID_QSFPP:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
    + return true;
    + break;
    + case SAM_IMG_ID_CFP4:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_CFP4)
    + return true;
    + break;
    + case SAM_IMG_ID_HENDRICKS_FPC:
    + break;
    + case SAM_IMG_ID_24x10GE_PIC:
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
    + SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN)
    + return true;
    + break;
    + case SAM_IMG_ID_SANGRIA_FPC:
    + /*
    + * Image and board IDs for Sangria FPC and QSFPP (old)
    + * are the same. Use PCIe bus number for disambiguation.
    + */
    + if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
    + SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN &&
    + sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
    + return true;
    + break;
    + default:
    + /* unknown, play safe */
    + break;
    + }
    +
    + return false;
    +}
    +
    +/*
    + * Flash access commands (simplified)
    + */
    +static bool sam_flash_busy(struct sam_fpga_data *sam)
    +{
    + return ioread32(FLASH_STATUS_REG(sam)) & FLASH_STATUS_BUSY;
    +}
    +
    +static int
    +sam_flash_read(struct sam_fpga_data *sam, u_int32_t offset, u32 *data)
    +{
    + if (sam_flash_busy(sam))
    + return -ETIMEDOUT;
    +
    + iowrite32(sizeof(u32) - 1, FLASH_COUNTER_REG(sam));
    + iowrite32(offset, FLASH_ADDR_REG(sam));
    +
    + /* trigger the read */
    + iowrite32(SAM_FLASH_IF_CONTROL_READ, FLASH_CONTROL_REG(sam));
    + ioread32(FLASH_CONTROL_REG(sam));
    +
    + udelay(50);
    +
    + if (sam_flash_busy(sam))
    + return -ETIMEDOUT;
    +
    + *data = ioread32(FLASH_READ_DATA_REG(sam));
    + return 0;
    +}
    +
    +static u32 sam_irq_mask(struct sam_fpga_data *sam, enum sam_irq_type type,
    + u32 mask)
    +{
    + switch (type) {
    + case SAM_IRQ_I2C:
    + mask &= sam->i2c_irq_mask;
    + break;
    + case SAM_IRQ_GPIO:
    + mask <<= sam->gpio_irq_shift;
    + mask &= sam->gpio_irq_mask;
    + break;
    + }
    + return mask;
    +}
    +
    +static void sam_enable_irq(struct device *dev, enum sam_irq_type type, int irq,
    + u32 mask)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    + unsigned long flags;
    + u32 s;
    +
    + /* irq is one of the virqs passed to the driver */
    +
    + mask = sam_irq_mask(sam, type, mask);
    +
    + spin_lock_irqsave(&sam->irq_lock, flags);
    + s = ioread32(ICTRL_ADDR(sam));
    + s |= mask;
    + iowrite32(s, ICTRL_ADDR(sam));
    + ioread32(ICTRL_ADDR(sam));
    + spin_unlock_irqrestore(&sam->irq_lock, flags);
    +}
    +
    +static void sam_disable_irq(struct device *dev, enum sam_irq_type type, int irq,
    + u32 mask)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    + unsigned long flags;
    + u32 s;
    +
    + mask = sam_irq_mask(sam, type, mask);
    +
    + spin_lock_irqsave(&sam->irq_lock, flags);
    + s = ioread32(ICTRL_ADDR(sam));
    + s &= ~mask;
    + iowrite32(s, ICTRL_ADDR(sam));
    + ioread32(ICTRL_ADDR(sam));
    + spin_unlock_irqrestore(&sam->irq_lock, flags);
    +}
    +
    +static u32 sam_irq_status(struct device *dev, enum sam_irq_type type, int irq)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    + u32 status;
    +
    + status = ioread32(ISTAT_ADDR(sam));
    +
    + switch (type) {
    + case SAM_IRQ_I2C:
    + status &= sam->i2c_irq_mask;
    + break;
    + case SAM_IRQ_GPIO:
    + status &= sam->gpio_irq_mask;
    + status >>= sam->gpio_irq_shift;
    + break;
    + }
    + return status;
    +}
    +
    +static void sam_irq_status_clear(struct device *dev, enum sam_irq_type type,
    + int irq, u32 mask)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    +
    + mask = sam_irq_mask(sam, type, mask);
    +
    + iowrite32(mask, ISTAT_ADDR(sam));
    + ioread32(ISTAT_ADDR(sam));
    +}
    +
    +static irqreturn_t sam_irq_handler(int irq, void *data)
    +{
    + struct sam_fpga_data *sam = data;
    + u32 status, mask;
    + int ret = IRQ_NONE;
    +
    + /*
    + * Shared interrupt handlers are called from the interrupt release
    + * function, so be careful not to access already released resources.
    + */
    + if (sam->irq_base) {
    + mask = ioread32(ICTRL_ADDR(sam));
    + status = ioread32(ISTAT_ADDR(sam));
    + status &= mask;
    + if (status & sam->i2c_irq_mask) { /* i2c interrupt */
    + handle_nested_irq(sam->irq_base);
    + ret = IRQ_HANDLED;
    + }
    + if (status & sam->gpio_irq_mask) { /* gpio interrupt */
    + handle_nested_irq(sam->irq_base + 1);
    + ret = IRQ_HANDLED;
    + }
    + }
    + return ret;
    +}
    +
    +static int sam_irq_set_affinity(struct irq_data *d,
    + const struct cpumask *affinity, bool force)
    +{
    + return 0;
    +}
    +
    +static void noop(struct irq_data *data) { }
    +
    +static struct irq_chip sam_irq_chip = {
    + .name = "sam-core",
    + .irq_mask = noop,
    + .irq_unmask = noop,
    + .irq_set_affinity = sam_irq_set_affinity,
    +};
    +
    +static int sam_attach_irq(struct sam_fpga_data *sam)
    +{
    + int irq_base = irq_alloc_descs(-1, 0, SAM_NUM_IRQ, 0);
    + int irq;
    +
    + if (irq_base < 0)
    + return irq_base;
    +
    + for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
    + irq_set_noprobe(irq);
    + irq_set_chip_and_handler(irq, &sam_irq_chip, handle_level_irq);
    + irq_set_chip_data(irq, sam);
    + irq_set_status_flags(irq, IRQ_LEVEL);
    + irq_clear_status_flags(irq, IRQ_NOREQUEST);
    + irq_set_nested_thread(irq, true);
    + }
    + if (sam_supports_i2c_irq(sam)) {
    + sam->mfd_i2c_resources[1].start
    + = sam->mfd_i2c_resources[1].end = irq_base;
    + sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES;
    + }
    + sam->mfd_gpio_resources[1].start = sam->mfd_gpio_resources[1].end
    + = irq_base + 1;
    + sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES;
    + sam->irq_base = irq_base;
    +
    + return 0;
    +}
    +
    +static void sam_detach_irq(struct sam_fpga_data *sam)
    +{
    + int irq, irq_base = sam->irq_base;
    +
    + if (irq_base) {
    + sam->irq_base = 0;
    + for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
    + irq_set_handler_data(irq, NULL);
    + irq_set_chip(irq, NULL);
    + irq_set_chip_data(irq, NULL);
    + }
    + irq_free_descs(irq_base, SAM_NUM_IRQ);
    + }
    +}
    +
    +static int sam_fpga_load_wait(struct sam_fpga_data *sam)
    +{
    + unsigned long start = jiffies;
    + unsigned long timeout = start + msecs_to_jiffies(2000);
    +
    + /* wait for up to two seconds for the command to complete */
    + do {
    + u32 status = ioread32(UPGRADE_STATUS_REG(sam));
    +
    + if (!(status & SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY))
    + return 0;
    +
    + usleep_range(500, 1000);
    + } while (time_before(jiffies, timeout));
    +
    + return -ETIMEDOUT;
    +}
    +
    +/*
    + * FPGA image download
    + */
    +static int sam_fpga_load_image(struct device *dev, struct sam_fpga_data *sam)
    +{
    + int ret;
    + u32 valid;
    + struct sam_core_list *entry;
    +
    + /*
    + * If the node exists, we have seen this device before.
    + * Don't try to re-load it again unless the FPGA version changed,
    + * a different board was inserted, or the board was inserted
    + * more than a minute ago.
    + * This check is necessary to ensure that we don't end up
    + * in endless attempts to re-load SAM.
    + */
    + mutex_lock(&sam_core_list_mutex);
    + list_for_each_entry(entry, &sam_core_list, node) {
    + if (entry->devfn == sam->pdev->devfn &&
    + entry->bus == sam->pdev->bus->number &&
    + entry->id == sam->board_id &&
    + entry->rev == sam->fpga_rev &&
    + time_before(entry->insertion_time, jiffies + HZ * 60)) {
    + mutex_unlock(&sam_core_list_mutex);
    + return 0;
    + }
    + }
    + mutex_unlock(&sam_core_list_mutex);
    +
    + ret = sam_flash_read(sam, SAM_FPGA_FLASH_VALID_BIT_ADDR, &valid);
    + if (ret < 0 || valid != SAM_FPGA_FLASH_VALID_BIT)
    + return 0;
    +
    + /* reset state machine and request upgrade */
    + iowrite32(SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET,
    + UPGRADE_CONTROL_REG(sam));
    + usleep_range(10000, 20000);
    +
    + iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
    + SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL |
    + SAM_FPGA_USER_IMAGE_BASE,
    + UPGRADE_CONTROL_REG(sam));
    + ioread32(UPGRADE_CONTROL_REG(sam));
    +
    + ret = sam_fpga_load_wait(sam);
    + if (ret)
    + return 0;
    +
    +#if 0
    + /*
    + * Request fallback to golden image if upgrade fails
    + * Commented out in Sangria code, kept for reference
    + */
    + iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
    + SAM_FPGA_REMOTE_UPGRADE_ANF | 1,
    + UPGRADE_CONTROL_REG(sam));
    +
    + ret = sam_fpga_load_wait(sam);
    + if (ret)
    + return ret;
    +#endif
    +
    + /* Trigger reconfiguration */
    + iowrite32(SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT, UPGRADE_CONTROL_REG(sam));
    +
    + sam_core_update_entry(sam);
    +
    + /*
    + * With a clean infrastructure, we could return -EPROBEDEFER here and
    + * leave it up to the PCIe hotplug driver to detect that the device has
    + * been removed and re-inserted. Without such a driver, user space
    + * will have to take care of it.
    + */
    + return -ENODEV;
    +}
    +
    +static ssize_t version_show(struct device *dev, struct device_attribute *attr,
    + char *buf)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    +
    + return sprintf(buf, "0x%x\n", sam->fpga_rev);
    +}
    +
    +static ssize_t board_id_show(struct device *dev, struct device_attribute *attr,
    + char *buf)
    +{
    + struct sam_fpga_data *sam = dev_get_drvdata(dev);
    +
    + return sprintf(buf, "0x%x\n", sam->board_id);
    +}
    +
    +static DEVICE_ATTR(version, S_IRUGO, version_show, NULL);
    +static DEVICE_ATTR(board_id, S_IRUGO, board_id_show, NULL);
    +
    +/* Initialize PMA coefficients */
    +
    +static int sam_pma_wait(struct sam_fpga_data *sam)
    +{
    + int i;
    + u32 status;
    +
    + for (i = 0; i < SAM_PMA_RETRIES; i++) {
    + udelay(SAM_PMA_WAIT_TIME);
    + status = ioread32(SAM_PMA_STATUS_REG(sam));
    + if (!(status & SAM_PMA_STATUS_BUSY))
    + return (status & SAM_PMA_STATUS_VALID) ? 0 : -EIO;
    + }
    + return -ETIMEDOUT;
    +}
    +
    +static int sam_pma_write(struct sam_fpga_data *sam, u32 data, void *addr)
    +{
    + iowrite32(data, addr);
    + ioread32(addr);
    + return sam_pma_wait(sam);
    +}
    +
    +static int sam_pma_init(struct sam_fpga_data *sam)
    +{
    + int lane;
    + int err;
    +
    + for (lane = 0; lane < sam->pma_lanes; lane++) {
    + err = sam_pma_write(sam,
    + SAM_PMA_CONTROL_READ | SAM_PMA_LANE(lane),
    + SAM_PMA_CONTROL_REG(sam));
    + if (err)
    + return err;
    + err = sam_pma_write(sam,
    + SAM_PMA_CONTROL_WRITE | SAM_PMA_LANE(lane) |
    + sam->pma_coefficients,
    + SAM_PMA_CONTROL_REG(sam));
    + if (err)
    + return err;
    + }
    + return 0;
    +}
    +
    +static int sam_fpga_of_init(struct device *dev, struct sam_fpga_data *sam)
    +{
    + int len;
    + const __be32 *pma_coefficients;
    +
    + if (!dev->of_node) {
    + dev_warn(dev, "No SAM FDT node\n");
    + return of_have_populated_dt() ? -ENODEV : 0;
    + }
    +
    + pma_coefficients = of_get_property(of_get_parent(dev->of_node),
    + "pma-coefficients", &len);
    + if (pma_coefficients) {
    + if (len != 2 * sizeof(u32))
    + return -EINVAL;
    + sam->pma_lanes = be32_to_cpu(pma_coefficients[0]);
    + sam->pma_coefficients = be32_to_cpu(pma_coefficients[1]);
    +
    + if (sam->pma_lanes > 7 ||
    + (sam->pma_coefficients & ~SAM_PMA_COEFF_MASK))
    + return -EINVAL;
    + }
    + return 0;
    +}
    +
    +/* SAM drivers interrupt handling */
    +static struct sam_platform_data sam_plat_data = {
    + .enable_irq = sam_enable_irq,
    + .disable_irq = sam_disable_irq,
    + .irq_status = sam_irq_status,
    + .irq_status_clear = sam_irq_status_clear,
    + .i2c_mux_channels = 2, /* MLC default */
    +};
    +
    +/* Add a single cell from OF */
    +static int sam_mfd_of_add_cell(struct device *dev, const char *compatible,
    + int id, struct resource *base)
    +{
    + struct device_node *np;
    + struct mfd_cell cell = {0};
    + struct resource res = {0};
    + u32 reg;
    + int ret;
    +
    + /* Note:
    + * Due to limitations in the MFD core, we can't have more
    + * than one compatible node - we can't match properly and bind
    + * the of_nodes to mfd cells.
    + */
    + np = of_find_compatible_node(dev->of_node, NULL, compatible);
    + if (!np)
    + return -ENODEV;
    +
    + ret = of_property_read_u32(np, "reg", &reg);
    + if (ret)
    + return ret;
    +
    + res.start = reg;
    + res.end = resource_size(base) - 1 - reg;
    + res.flags = IORESOURCE_MEM;
    + cell.name = np->name;
    + cell.of_compatible = compatible;
    + cell.resources = &res;
    + cell.num_resources = 1;
    +
    + return mfd_add_devices(dev, id, &cell, 1, base, 0, NULL);
    +}
    +
    +static void sam_init_irq_masks(struct sam_fpga_data *sam)
    +{
    + if ((SAM_IMG_ID(sam) == SAM_IMG_ID_HENDRICKS_FPC &&
    + SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC) ||
    + (SAM_IMG_ID(sam) == SAM_IMG_ID_24x10GE_PIC &&
    + SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC) ||
    + (SAM_IMG_ID(sam) == SAM_IMG_ID_SANGRIA_FPC &&
    + SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
    + sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)) {
    + sam->i2c_irq_mask = 0x000000ff;
    + sam->gpio_irq_mask = 0x1ffff000;
    + sam->gpio_irq_shift = 12;
    + } else {
    + sam->i2c_irq_mask = 0x0000ffff;
    + sam->gpio_irq_mask = 0xffff0000;
    + sam->gpio_irq_shift = 16;
    + }
    +}
    +
    +static int sam_fpga_probe(struct pci_dev *pdev, const struct pci_device_id *id)
    +{
    + int err;
    + struct sam_fpga_data *sam;
    + struct device *dev = &pdev->dev;
    +
    + sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
    + if (sam == NULL)
    + return -ENOMEM;
    +
    + err = sam_fpga_of_init(dev, sam);
    + if (err < 0)
    + return err;
    +
    + err = pci_enable_device(pdev);
    + if (err < 0) {
    + dev_err(dev, "pci_enable_device() failed: %d\n", err);
    + return err;
    + }
    +
    + err = pci_request_regions(pdev, SAM_FPGA_MODULE_NAME);
    + if (err < 0) {
    + dev_err(dev, "pci_request_regions() failed: %d\n",
    + err);
    + goto err_disable;
    + }
    +
    + sam->membase = pci_ioremap_bar(pdev, 0);
    + if (!sam->membase) {
    + dev_err(dev, "pci_ioremap_bar() failed\n");
    + err = -ENOMEM;
    + goto err_release;
    + }
    +
    + sam->pdev = pdev;
    + pci_set_drvdata(pdev, sam);
    +
    + /*
    + * Try to upgrade FPGA image to user image if revision is too old
    + * to support I2C interrupts
    + */
    + sam->fpga_rev = ioread32(VERSION_ADDR(sam));
    + sam->board_id = ioread32(BOARD_ID_ADDR(sam));
    +
    + if (!sam_supports_i2c_irq(sam)) {
    + dev_info(dev,
    + "FPGA revision %u.%u doesn't support I2C interrupts, attempting firmware download\n",
    + (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff);
    + err = sam_fpga_load_image(dev, sam);
    + if (err)
    + goto err_unmap;
    + }
    +
    + if (sam_supports_pma(sam) && sam->pma_lanes) {
    + err = sam_pma_init(sam);
    + if (err < 0)
    + goto err_unmap;
    + }
    +
    + sam_init_irq_masks(sam);
    +
    + spin_lock_init(&sam->irq_lock);
    +
    + sam->mfd_cells[0].name = "i2c-sam";
    + sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES_NOIRQ;
    + sam->mfd_cells[0].resources = sam->mfd_i2c_resources;
    + sam->mfd_cells[0].of_compatible = "jnx,i2c-sam";
    + sam->mfd_cells[0].platform_data = &sam_plat_data;
    + sam->mfd_cells[0].pdata_size = sizeof(sam_plat_data);
    +
    + sam->mfd_i2c_resources[0].end = FPGA_MEM_SIZE - 1;
    + sam->mfd_i2c_resources[0].flags = IORESOURCE_MEM;
    + sam->mfd_i2c_resources[1].flags = IORESOURCE_IRQ;
    +
    + sam->mfd_cells[1].name = "gpio-sam";
    + sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES_NOIRQ;
    + sam->mfd_cells[1].resources = sam->mfd_gpio_resources;
    + sam->mfd_cells[1].of_compatible = "jnx,gpio-sam";
    + sam->mfd_cells[1].platform_data = &sam_plat_data;
    + sam->mfd_cells[1].pdata_size = sizeof(sam_plat_data);
    +
    + sam->mfd_gpio_resources[0].end = FPGA_MEM_SIZE - 1;
    + sam->mfd_gpio_resources[0].flags = IORESOURCE_MEM;
    + sam->mfd_gpio_resources[1].flags = IORESOURCE_IRQ;
    +
    + sam->mfd_cells[2].name = "flash-sam";
    + sam->mfd_cells[2].num_resources = SAM_NUM_RESOURCES_NOIRQ;
    + sam->mfd_cells[2].resources = sam->mfd_mtd_resources;
    + sam->mfd_cells[2].of_compatible = "jnx,flash-sam";
    +
    + sam->mfd_mtd_resources[0].end = FPGA_MEM_SIZE - 1;
    + sam->mfd_mtd_resources[0].flags = IORESOURCE_MEM;
    +
    + /* Enable MSI, if it is supported by this version of SAM */
    + if (sam_supports_msi(sam) && !pci_enable_msi(pdev))
    + pci_set_master(pdev);
    +
    + if (pdev->irq) {
    + err = devm_request_threaded_irq(dev, pdev->irq, NULL,
    + sam_irq_handler,
    + IRQF_ONESHOT,
    + dev_driver_string(dev), sam);
    + if (err) {
    + dev_err(dev, "failed to request irq %d\n", pdev->irq);
    + goto err_unmap;
    + }
    + err = sam_attach_irq(sam);
    + if (err) {
    + dev_err(dev, "failed to attach irq %d\n", pdev->irq);
    + goto err_irq;
    + }
    + }
    +
    + err = mfd_add_devices(dev, pdev->bus->number, sam->mfd_cells,
    + ARRAY_SIZE(sam->mfd_cells), &pdev->resource[0],
    + 0, NULL);
    + if (err < 0)
    + goto err_irq_attach;
    +
    + /*
    + * We don't know if this SAM supports MDIO.
    + * Add client only if compatible node exists.
    + */
    + sam_mfd_of_add_cell(dev, "jnx,mdio-sam", pdev->bus->number,
    + &pdev->resource[0]);
    +
    + err = device_create_file(&pdev->dev, &dev_attr_version);
    + if (err < 0)
    + goto err_remove;
    +
    + err = device_create_file(&pdev->dev, &dev_attr_board_id);
    + if (err < 0)
    + goto err_remove_files;
    +
    + dev_info(dev,
    + "SAM Jspec version %u.%u FPGA version %u.%u image 0x%x board 0x%x inserted\n",
    + (sam->fpga_rev >> 24) & 0xff, (sam->fpga_rev >> 16) & 0xff,
    + (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff,
    + (sam->board_id >> 16) & 0xff,
    + sam->board_id & 0xff);
    +
    + return 0;
    +
    +err_remove_files:
    + device_remove_file(&pdev->dev, &dev_attr_version);
    + device_remove_file(&pdev->dev, &dev_attr_board_id);
    +err_remove:
    + mfd_remove_devices(dev);
    +err_irq_attach:
    + if (pdev->irq)
    + sam_detach_irq(sam);
    +err_irq:
    + /* Call free_irq() before pci_disable_msi() */
    + if (pdev->irq)
    + devm_free_irq(&pdev->dev, pdev->irq, sam);
    +err_unmap:
    + pci_disable_msi(pdev);
    + pci_iounmap(pdev, sam->membase);
    +err_release:
    + pci_release_regions(pdev);
    +err_disable:
    + pci_disable_device(pdev);
    + return err;
    +}
    +
    +static void sam_fpga_remove(struct pci_dev *pdev)
    +{
    + struct sam_fpga_data *sam = pci_get_drvdata(pdev);
    +
    + mfd_remove_devices(&pdev->dev);
    + device_remove_file(&pdev->dev, &dev_attr_version);
    + device_remove_file(&pdev->dev, &dev_attr_board_id);
    + sam_disable_irq(&pdev->dev, SAM_IRQ_I2C, sam->irq_base, 0xffffffff);
    + sam_disable_irq(&pdev->dev, SAM_IRQ_GPIO, sam->irq_base, 0xffffffff);
    + if (pdev->irq) {
    + sam_detach_irq(sam);
    + devm_free_irq(&pdev->dev, pdev->irq, sam);
    + }
    + pci_disable_msi(pdev);
    + pci_iounmap(pdev, sam->membase);
    + pci_release_regions(pdev);
    + pci_disable_device(pdev);
    +}
    +
    +static struct pci_device_id sam_fpga_ids[] = {
    + { PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM) },
    + { PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_X) },
    + { PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_OMEGA) },
    + { PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_PAM) },
    + { }
    +};
    +MODULE_DEVICE_TABLE(pci, sam_fpga_ids);
    +
    +static struct pci_driver sam_fpga_driver = {
    + .name = SAM_FPGA_MODULE_NAME,
    + .id_table = sam_fpga_ids,
    + .probe = sam_fpga_probe,
    + .remove = sam_fpga_remove,
    +};
    +
    +static int __init sam_fpga_init(void)
    +{
    + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
    +
    + return pci_register_driver(&sam_fpga_driver);
    +}
    +
    +static void __exit sam_fpga_exit(void)
    +{
    + struct sam_core_list *entry, *t;
    +
    + list_for_each_entry_safe(entry, t, &sam_core_list, node) {
    + list_del(&entry->node);
    + kfree(entry);
    + }
    + pci_unregister_driver(&sam_fpga_driver);
    +}
    +
    +module_init(sam_fpga_init);
    +module_exit(sam_fpga_exit);
    +
    +MODULE_DESCRIPTION(DRIVER_DESC);
    +MODULE_VERSION(DRIVER_VERSION);
    +MODULE_LICENSE("GPL v2");
    +MODULE_AUTHOR(DRIVER_AUTHOR);
    diff --git a/include/linux/mfd/sam.h b/include/linux/mfd/sam.h
    new file mode 100644
    index 0000000..d41b9fb
    --- /dev/null
    +++ b/include/linux/mfd/sam.h
    @@ -0,0 +1,30 @@
    +/*
    + * Functions exported from SAM mfd driver
    + * Copyright (c) 2013 Juniper Networks <groeck@juniper.net>
    + *
    + * 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.
    + */
    +
    +#ifndef SAM_H
    +#define SAM_H
    +
    +struct device;
    +
    +enum sam_irq_type {
    + SAM_IRQ_I2C = 0,
    + SAM_IRQ_GPIO,
    +};
    +
    +struct sam_platform_data {
    + void (*enable_irq)(struct device *dev, enum sam_irq_type, int irq,
    + u32 mask);
    + void (*disable_irq)(struct device *dev, enum sam_irq_type, int irq,
    + u32 mask);
    + u32 (*irq_status)(struct device *dev, enum sam_irq_type, int irq);
    + void (*irq_status_clear)(struct device *dev, enum sam_irq_type, int irq,
    + u32 mask);
    + int i2c_mux_channels;
    +};
    +#endif /* SAM_H */
    --
    1.9.1
    \
     
     \ /
      Last update: 2016-10-07 17:42    [W:5.734 / U:0.016 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site