Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1497289 > unrolled thread
| Started by | Pantelis Antoniou <pantelis.antoniou@konsulko.com> |
|---|---|
| First post | 2016-10-07 17:50 +0200 |
| Last post | 2016-10-07 17:50 +0200 |
| Articles | 3 — 1 participant |
Back to article view | Back to linux.kernel
[PATCH 00/10] Introduce Juniper SAM FPGA driver Pantelis Antoniou <pantelis.antoniou@konsulko.com> - 2016-10-07 17:50 +0200
[PATCH 07/10] mtd: Add SAM Flash driver Pantelis Antoniou <pantelis.antoniou@konsulko.com> - 2016-10-07 17:50 +0200
[PATCH 01/10] mfd: Add Juniper SAM FPGA MFD driver Pantelis Antoniou <pantelis.antoniou@konsulko.com> - 2016-10-07 17:50 +0200
| From | Pantelis Antoniou <pantelis.antoniou@konsulko.com> |
|---|---|
| Date | 2016-10-07 17:50 +0200 |
| Subject | [PATCH 00/10] Introduce Juniper SAM FPGA driver |
| Message-ID | <spFfX-2lv-13@gated-at.bofh.it> |
Add Juniper's SAM FPGA driver. Those FPGAs are present in Juniper's PTX series of routers. The MFD driver provices i2c/gpio/mtd/mdio devices. There are full device tree binding documents for the master mfd driver and for all slave drivers. This patchset is against mainline as of today: v4.8-9431-g3477d16 and is dependent on the "Juniper prerequisites" and "Juniper infrastructure" patchsets sent earlier. Georgi Vlaev (6): mfd: sam: Add documentation for the SAM FPGA i2c: i2c-sam: Add device tree bindings gpio: sam: Document bindings of SAM FPGA GPIO block mtd: flash-sam: Bindings for Juniper's SAM FPGA flash net: phy: Add MDIO driver for Juniper's SAM FPGA net: mdio-sam: Add device tree documentation for SAM MDIO Guenter Roeck (2): gpio: Introduce SAM gpio driver mtd: Add SAM Flash driver Maryam Seraj (2): mfd: Add Juniper SAM FPGA MFD driver i2c: Juniper SAM I2C driver .../devicetree/bindings/gpio/jnx,gpio-sam.txt | 110 +++ .../devicetree/bindings/i2c/i2c-sam-mux.txt | 20 + Documentation/devicetree/bindings/i2c/i2c-sam.txt | 44 + Documentation/devicetree/bindings/mfd/jnx-sam.txt | 94 ++ .../devicetree/bindings/mtd/flash-sam.txt | 31 + Documentation/devicetree/bindings/net/mdio-sam.txt | 48 + drivers/gpio/Kconfig | 11 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-sam.c | 707 +++++++++++++++ drivers/i2c/busses/Kconfig | 11 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-sam.c | 942 +++++++++++++++++++ drivers/mfd/Kconfig | 16 + drivers/mfd/Makefile | 1 + drivers/mfd/sam-core.c | 997 +++++++++++++++++++++ drivers/mtd/devices/Kconfig | 11 + drivers/mtd/devices/Makefile | 1 + drivers/mtd/devices/sam-flash.c | 642 +++++++++++++ drivers/net/phy/Kconfig | 8 + drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-sam.c | 564 ++++++++++++ include/linux/mfd/sam.h | 30 + 22 files changed, 4291 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam.txt create mode 100644 Documentation/devicetree/bindings/mfd/jnx-sam.txt create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt create mode 100644 drivers/gpio/gpio-sam.c create mode 100644 drivers/i2c/busses/i2c-sam.c create mode 100644 drivers/mfd/sam-core.c create mode 100644 drivers/mtd/devices/sam-flash.c create mode 100644 drivers/net/phy/mdio-sam.c create mode 100644 include/linux/mfd/sam.h -- 1.9.1
[toc] | [next] | [standalone]
| From | Pantelis Antoniou <pantelis.antoniou@konsulko.com> |
|---|---|
| Date | 2016-10-07 17:50 +0200 |
| Subject | [PATCH 07/10] mtd: Add SAM Flash driver |
| Message-ID | <spFzk-2tG-23@gated-at.bofh.it> |
| In reply to | #1497289 |
From: Guenter Roeck <groeck@juniper.net>
Add driver for the flash block in Juniper's SAM FPGA.
This driver is used for updating the Altera's EPCS(64,256)
configuration flash devices via a Juniper defined hardware
interface.
Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
drivers/mtd/devices/Kconfig | 11 +
drivers/mtd/devices/Makefile | 1 +
drivers/mtd/devices/sam-flash.c | 642 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 654 insertions(+)
create mode 100644 drivers/mtd/devices/sam-flash.c
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index d4255fb..f5a9032 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -144,6 +144,17 @@ config MTD_LART
not need any mapping/chip driver for LART. This one does it all
for you, so go disable all of those if you enabled some of them (:
+config MTD_SAM_FLASH
+ tristate "Juniper SAM Flash driver"
+ depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+ default y if MFD_JUNIPER_SAM
+ help
+ This enables the flash driver for the SAM FPGA which is present
+ on relevant Juniper platforms.
+
+ This driver can also be built as a module. When it is so the name of
+ the module is flash-sam.
+
config JNX_PMB_NVRAM
tristate "Juniper FPC PMB NVRAM Driver"
depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index b407c5fc..7556311 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o
+obj-$(CONFIG_MTD_SAM_FLASH) += sam-flash.o
obj-$(CONFIG_JNX_PMB_NVRAM) += jnx_pmb_nvram.o
CFLAGS_docg3.o += -I$(src)
diff --git a/drivers/mtd/devices/sam-flash.c b/drivers/mtd/devices/sam-flash.c
new file mode 100644
index 0000000..5f071e6
--- /dev/null
+++ b/drivers/mtd/devices/sam-flash.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#define SAM_FLASH_DEBUG_ENABLED
+#undef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+#define SAM_FLASH_IF_READ_MAX_SIZE 32 /* 256?! */
+
+#define SAM_FLASH_BASE 0x300
+
+#define ADDR_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x000)
+#define COUNTER_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x004)
+#define CONTROL_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x008)
+#define STATUS_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define WRITE_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x100)
+#define READ_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x200)
+
+static int sam_flash_if_read_max_size = SAM_FLASH_IF_READ_MAX_SIZE;
+module_param(sam_flash_if_read_max_size, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_if_read_max_size,
+ "maximum read size done by SAM flash IF");
+
+#ifdef SAM_FLASH_DEBUG_ENABLED
+
+static int sam_flash_debug;
+module_param(sam_flash_debug, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_debug, "enable debugging information");
+
+#define SAM_FLASH_DEBUG(dev, fmt, args...) \
+ do { \
+ if (sam_flash_debug) { \
+ dev_info(dev, fmt, ## args); \
+ } \
+ } while (0)
+#else /* SAM_FLASH_DEBUG_ENABLED */
+#define SAM_FLASH_DEBUG(dev, fmt, args...) {}
+#endif /* SAM_FLASH_DEBUG_ENABLED */
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS_EXT_STS_ID(sts) ((u8)((sts >> 8) & 0xff))
+#define EPCS_EXT_STS_RDSTS(sts) ((u8)((sts >> 16) & 0xff))
+#define EPCS_EXT_STS_SID(sts) ((u8)((sts >> 24) & 0xff))
+/* EPCS Device "read status" bits */
+#define EPCS_STS_WIP_BIT 0x01
+#define EPCS_STS_WLE_BIT 0x02
+#define EPCS_STS_BP_BITS(status) ((status >> 2) & 0x7)
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS64_S_ID 0x16
+#define EPCS64_NAME "Altera EPCS64"
+#define EPCS64_SECT_SZ_SHIFT 16
+#define EPCS64_SECTOR_SIZE (1 << EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_SECTORS 128
+#define EPCS64_ADDR_TO_SECTOR(_addr) ((_addr) >> EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_PAGE_SIZE 256
+#define EPCS64_PAGES 32768
+#define EPCS64_SIZE (EPCS64_PAGE_SIZE * EPCS64_PAGES)
+#define EPCS64_MIN_SECT(bp_bits) (EPCS64_SECTORS - (1 << bp_bits))
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ * timeout for busy: t5e-pic/t5e_flash.c
+ */
+#define EPCS_TIMEOUT_BUSY 1
+#define EPCS_TIMEOUT_SINGLE_BYTE_READ 3
+#define EPCS_TIMEOUT_READ_ID 3
+#define EPCS_TIMEOUT_READ_STATUS 3
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ * timeout for waiting completion: t5e-pic/t5e_flash.c
+ */
+#define EPCS_RD_TIMEO 20
+#define EPCS_WR_TIMEO 25
+#define EPCS_BLK_WR_TIMEO 25
+#define EPCS_SC_ER_TIMEO (10 * 1000)
+#define EPCS_SC_PRT_TIMEO 35
+#define EPCS_CH_ER_TIMEO (200 * 1000)
+/* */
+#define EPCS_STS_BSY_BIT 0x01
+#define EPCS_ILLEGAL_WR_BIT 0x02
+#define EPCS_ILLEGAL_RD_BIT 0x04
+#define EPCS_ILLEGAL (EPCS_ILLEGAL_WR_BIT | EPCS_ILLEGAL_RD_BIT)
+#define EPCS_STATUS_BUSY(s) ((s) & EPCS_STS_BSY_BIT)
+
+/*
+ * Ref t5e-pic/t5e_flash.c
+ */
+#define EPCS_BUSY_POLLING_START_DELAY 100 /* us */
+#define EPCS_BUSY_POLLING_START_DELAY_CNT 10
+#define EPCS_BUSY_POLLING_DELAY (EPCS_BUSY_POLLING_START_DELAY_CNT * \
+ EPCS_BUSY_POLLING_START_DELAY)
+
+/*
+ * FPGA flash control register: t5e-pic/t5e_fpga.h
+ */
+#define SAM_FLASH_IF_CONTROL_READ_SID 0x00000080
+#define SAM_FLASH_IF_CONTROL_CHIP_ERASE 0x00000040
+#define SAM_FLASH_IF_CONTROL_SECTOR_ERASE 0x00000020
+#define SAM_FLASH_IF_CONTROL_SECTOR_PROTECT 0x00000010
+#define SAM_FLASH_IF_CONTROL_READ_STATUS 0x00000008
+#define SAM_FLASH_IF_CONTROL_READ_ID 0x00000004
+#define SAM_FLASH_IF_CONTROL_WRITE 0x00000002
+#define SAM_FLASH_IF_CONTROL_READ 0x00000001
+#define SAM_FLASH_IF_WRITE_REG_SIZE sizeof(u32)
+#define SAM_FLASH_IF_READ_REG_SIZE sizeof(u32)
+
+struct sam_flash_info {
+ const char *name;
+ u8 device_id;
+ size_t flash_size;
+ size_t page_size;
+ size_t nr_pages;
+ size_t nr_sectors;
+ size_t erasesize;
+ size_t writesize;
+ size_t writebufsize;
+};
+
+static struct sam_flash_info sam_flash_info_db[] = {
+ {
+ .name = EPCS64_NAME,
+ .device_id = EPCS64_S_ID,
+ .flash_size = EPCS64_SIZE,
+ .page_size = EPCS64_PAGE_SIZE,
+ .nr_pages = EPCS64_PAGES,
+ .nr_sectors = EPCS64_SECTORS,
+ .erasesize = EPCS64_SECTOR_SIZE,
+ .writesize = 4,
+ .writebufsize = 4,
+ },
+};
+
+#define SAM_FLASH_INFO_DB_SIZE ARRAY_SIZE(sam_flash_info_db)
+
+/**
+ * struct sam_flash - SAM FLASH private data structure.
+ * @membase: PCI base address of Memory mapped I/O register.
+ * @reg: Memory mapped PCH GPIO register list.
+ * @dev: Pointer to device structure.
+ */
+struct sam_flash {
+ void __iomem *membase;
+ struct mutex lock;
+ struct device *dev;
+ struct sam_flash_info *info;
+ struct mtd_info mtd_info;
+};
+
+#define mtd_to_sam_flash(mtd) container_of(mtd, struct sam_flash, mtd_info)
+
+static bool sam_flash_if_busy(struct sam_flash *sam_flash, int retry)
+{
+ u32 status;
+
+ do {
+ status = ioread32(STATUS_REG(sam_flash));
+ if (!EPCS_STATUS_BUSY(status))
+ return false;
+ if (retry <= 1)
+ return true;
+ usleep_range(50, 100);
+ } while (--retry >= 0);
+
+ return true;
+}
+
+static int
+sam_flash_if_busy_wait(struct sam_flash *sam_flash, unsigned int max_wait_msec)
+{
+ unsigned long timeout;
+ u32 status;
+
+ timeout = jiffies + msecs_to_jiffies(max_wait_msec);
+ udelay(50);
+
+ do {
+ status = ioread32(STATUS_REG(sam_flash));
+ if (!EPCS_STATUS_BUSY(status))
+ return 0;
+
+ if (status & EPCS_ILLEGAL)
+ return -EACCES;
+
+ usleep_range(50, 100);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static int
+sam_flash_mem_read(struct sam_flash *sam_flash, u32 offset,
+ u8 *data, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ void __iomem *io_addr;
+ u32 io_data;
+ int i, cnt;
+
+ if (offset >= info->flash_size || offset + len > info->flash_size)
+ return -EINVAL;
+
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY))
+ return -ETIMEDOUT;
+
+ iowrite32(len - 1, COUNTER_REG(sam_flash));
+ iowrite32(offset, ADDR_REG(sam_flash));
+
+ /* trigger the read */
+ iowrite32(SAM_FLASH_IF_CONTROL_READ, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ /*
+ * Before we start polling the busy bit, wait for some time,
+ * so that, the busy bit will go high
+ */
+#ifdef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+ udelay(50);
+#else /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+ udelay(50 * ((len >> 2) + 1)); /* 50 usec every 4 bytes */
+#endif /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_SINGLE_BYTE_READ))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(COUNTER_REG(sam_flash));
+ if (io_data != len - 1)
+ return -EIO;
+
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s BYTE_CNT: len: %u, io_data: %u.\n",
+ __func__, (unsigned int)len, io_data);
+
+ io_addr = READ_DATA_REG(sam_flash);
+ for (cnt = 0; cnt < len; io_addr += sizeof(u32)) {
+ io_data = ioread32(io_addr);
+ for (i = 0; i < sizeof(u32) && cnt < len; i++, cnt++)
+ *(data++) = (io_data >> (i << 3)) & 0xff;
+ }
+
+ return 0;
+}
+
+static int sam_flash_read_sid(struct sam_flash *sam_flash)
+{
+ u32 io_data;
+
+ iowrite32(SAM_FLASH_IF_CONTROL_READ_SID, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ /*
+ * Before we start polling the busy bit, wait for some time
+ * to ensure that busy bit is high.
+ */
+ udelay(EPCS_BUSY_POLLING_DELAY);
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(STATUS_REG(sam_flash));
+
+ return EPCS_EXT_STS_SID(io_data);
+}
+
+static struct sam_flash_info *sam_flash_get_info(struct sam_flash *sam_flash)
+{
+ struct sam_flash_info *info;
+ u8 sid;
+ int idx;
+
+ sid = sam_flash_read_sid(sam_flash);
+ if (sid < 0)
+ return ERR_PTR(sid);
+
+ info = ERR_PTR(-EINVAL);
+ for (idx = 0; idx < SAM_FLASH_INFO_DB_SIZE; idx++) {
+ if (sam_flash_info_db[idx].device_id == sid) {
+ info = &sam_flash_info_db[idx];
+ break;
+ }
+ }
+ return info;
+}
+
+static inline int sam_flash_get_page_num(struct sam_flash *sam_flash,
+ u32 offset)
+{
+ return offset / sam_flash->info->page_size;
+}
+
+static int sam_flash_mem_write(struct sam_flash *sam_flash, u32 offset,
+ const u8 *data, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ int status, bytes_in_reg, cnt, cnt2;
+ int start_page, end_page;
+ void __iomem *io_addr;
+ u32 io_data;
+ const u8 *buf;
+
+ start_page = sam_flash_get_page_num(sam_flash, offset);
+ end_page = sam_flash_get_page_num(sam_flash, offset + len - 1);
+
+ /*
+ * Based on Altera EPCS Device Datasheet,
+ * Writing with multiple byte must be in the __SAME__ page.
+ * Not sure if SAM FPGA takes that so ...
+ */
+ if (len > info->page_size ||
+ start_page != end_page ||
+ start_page >= info->nr_pages) {
+ dev_err(sam_flash->dev, "Bad write length / offset\n");
+ return -EINVAL;
+ }
+
+ /* check if FPGA is ready to accept new command */
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY)) {
+ dev_err(sam_flash->dev, "chip is busy\n");
+ return -ETIMEDOUT;
+ }
+
+ iowrite32(len - 1, COUNTER_REG(sam_flash));
+
+ /* copy the data to WRITE_DATA register */
+ io_addr = WRITE_DATA_REG(sam_flash);
+ for (buf = data, cnt = 0; cnt < len;) {
+ bytes_in_reg = len - cnt;
+ if (bytes_in_reg > SAM_FLASH_IF_WRITE_REG_SIZE)
+ bytes_in_reg = SAM_FLASH_IF_WRITE_REG_SIZE;
+ io_data = 0;
+ for (cnt2 = 0; cnt2 < bytes_in_reg; cnt2++, buf++)
+ io_data |= *buf << (cnt2 << 3);
+
+ iowrite32(io_data, io_addr);
+ cnt += bytes_in_reg;
+ io_addr += bytes_in_reg;
+ }
+
+ iowrite32(offset, ADDR_REG(sam_flash));
+ /* trigger the write */
+ iowrite32(SAM_FLASH_IF_CONTROL_WRITE, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ status = sam_flash_if_busy_wait(sam_flash, EPCS_WR_TIMEO);
+ return status;
+}
+
+static int sam_flash_mem_is_protected(struct sam_flash *sam_flash,
+ u32 offset, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ u32 flash_status;
+ u32 sector;
+ u32 io_data;
+ int status = 0;
+
+ iowrite32(SAM_FLASH_IF_CONTROL_READ_STATUS, CONTROL_REG(sam_flash));
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(STATUS_REG(sam_flash));
+ flash_status = EPCS_EXT_STS_RDSTS(io_data);
+ sector = EPCS64_ADDR_TO_SECTOR(offset);
+ if (EPCS_STS_BP_BITS(flash_status) &&
+ sector < info->erasesize &&
+ sector >= EPCS64_MIN_SECT(EPCS_STS_BP_BITS(flash_status))) {
+ status = -EACCES;
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s offset: 0x%x, len: %u: PROTECTED(0x%x): %d.\n",
+ __func__, offset, (unsigned int)len,
+ flash_status, status);
+ }
+
+ return status;
+}
+
+static int sam_flash_erase_sector(struct sam_flash *sam_flash, u32 offset)
+{
+ iowrite32(offset, ADDR_REG(sam_flash));
+ iowrite32(SAM_FLASH_IF_CONTROL_SECTOR_ERASE, CONTROL_REG(sam_flash));
+
+ return sam_flash_if_busy_wait(sam_flash, EPCS_SC_ER_TIMEO);
+}
+
+static int sam_flash_erase(struct mtd_info *mtd_info,
+ struct erase_info *erase_info)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ u32 len, start, end, offset;
+ int status = 0;
+
+ len = (u32) erase_info->len;
+ start = (u32) erase_info->addr;
+ end = start + len - 1;
+
+ offset = start;
+ mutex_lock(&sam_flash->lock);
+ erase_info->state = MTD_ERASE_DONE;
+ while (offset < end) {
+ status = sam_flash_erase_sector(sam_flash, offset);
+ if (status) {
+ erase_info->state = MTD_ERASE_FAILED;
+ break;
+ }
+ offset += mtd_info->erasesize;
+ }
+ mutex_unlock(&sam_flash->lock);
+
+ mtd_erase_callback(erase_info);
+
+ return status;
+}
+
+static int sam_flash_read(struct mtd_info *mtd_info, loff_t from, size_t len,
+ size_t *retlen, unsigned char *buf)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ int cnt, max_cnt;
+ int status = 0;
+
+ *retlen = 0;
+
+ max_cnt = len / sam_flash_if_read_max_size;
+ if (len % sam_flash_if_read_max_size != 0)
+ max_cnt++;
+ mutex_lock(&sam_flash->lock);
+ for (cnt = 0; cnt < max_cnt; cnt++) {
+ u32 from2;
+ size_t len2;
+ u8 *buf2;
+
+ from2 = from + *retlen;
+ buf2 = buf + *retlen;
+ len2 = len - *retlen;
+ if (len2 > sam_flash_if_read_max_size)
+ len2 = sam_flash_if_read_max_size;
+
+ status = sam_flash_mem_read(sam_flash, from2, buf2, len2);
+ if (status != 0) {
+ dev_err(sam_flash->dev,
+ "RD: cnt: %04d(%04d): from: %u(%u), len: %u(%u) failed: %d.\n",
+ cnt, max_cnt, from2, (u32) from,
+ (unsigned int)len2, (unsigned int)len, status);
+ break;
+ }
+ *retlen += len2;
+ }
+ mutex_unlock(&sam_flash->lock);
+
+ return status;
+}
+
+static int sam_flash_write(struct mtd_info *mtd_info, loff_t to,
+ size_t len, size_t *retlen, const unsigned char *buf)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ int status, done, to_be_done;
+
+ mutex_lock(&sam_flash->lock);
+ status = sam_flash_mem_is_protected(sam_flash, to, len);
+ if (status)
+ goto abort;
+
+ for (done = 0; done < len; done += to_be_done) {
+ to_be_done = to & (sam_flash->info->page_size - 1);
+ if (to_be_done == 0) {
+ /* 'to' is page aligned */
+ to_be_done = len - done;
+ if (to_be_done > sam_flash->info->page_size)
+ to_be_done = sam_flash->info->page_size;
+ } else {
+ to_be_done = sam_flash->info->page_size - to_be_done;
+ }
+
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s to: 0x%x, buf: 0x%p, to_be_done: %d, done: %d, len: %d.\n",
+ __func__,
+ (u32) to, buf, to_be_done,
+ done, (unsigned int)len);
+ status = sam_flash_mem_write(sam_flash, to, buf, to_be_done);
+ if (status) {
+ dev_err(sam_flash->dev,
+ "WR: failed to 0x%x, buf: 0x%p, done: %d(%d), to_be_done: %d: %d.\n",
+ (u32)to, buf, done, (unsigned int)len,
+ to_be_done, status);
+ break;
+ }
+
+ to += to_be_done;
+ buf += to_be_done;
+ }
+
+ if (!status)
+ *retlen = len;
+
+abort:
+ mutex_unlock(&sam_flash->lock);
+ return status;
+}
+
+static int sam_flash_mtd_attach(struct platform_device *pdev,
+ struct sam_flash *sam_flash)
+{
+ struct mtd_part_parser_data ppdata = {};
+ struct sam_flash_info *info;
+ struct device *dev = sam_flash->dev;
+ struct mtd_info *mtd_info;
+ int ret;
+
+ info = sam_flash_get_info(sam_flash);
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
+ sam_flash->info = info;
+
+ mtd_info = &sam_flash->mtd_info;
+ mtd_info->name = dev_name(dev);
+ mtd_info->type = MTD_NORFLASH;
+ mtd_info->flags = MTD_CAP_NORFLASH;
+ mtd_info->erasesize = info->erasesize;
+ mtd_info->writesize = info->writesize;
+ mtd_info->writebufsize = info->writebufsize;
+ mtd_info->size = info->flash_size;
+ mtd_info->_erase = sam_flash_erase;
+ mtd_info->_read = sam_flash_read;
+ mtd_info->_write = sam_flash_write;
+
+ ret = mtd_device_parse_register(mtd_info, NULL, &ppdata, NULL, 0);
+ if (ret) {
+ dev_err(dev, "mtd_device_parse_register returned %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev,
+ "ATTACH: name: \"%s\" type: %d, flags: 0x%x.\n",
+ mtd_info->name, mtd_info->type, mtd_info->flags);
+ dev_info(dev,
+ "ATTACH: erasesize: %d, writesize: %d, writebufsize: %d\n",
+ mtd_info->erasesize, mtd_info->writesize,
+ mtd_info->writebufsize);
+ dev_info(dev,
+ "ATTACH: size: %llu.%u(%llu KB).\n",
+ mtd_info->size,
+ (unsigned int)info->flash_size,
+ (long long)mtd_info->size >> 10);
+
+ return 0;
+}
+
+static int sam_flash_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sam_flash *sam_flash;
+ struct resource *res;
+
+ sam_flash = devm_kzalloc(dev, sizeof(*sam_flash), GFP_KERNEL);
+ if (sam_flash == NULL)
+ return -ENOMEM;
+
+ sam_flash->dev = dev;
+ platform_set_drvdata(pdev, sam_flash);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOMEM;
+
+ sam_flash->membase = devm_ioremap(dev, res->start, resource_size(res));
+ if (!sam_flash->membase)
+ return -ENOMEM;
+
+ mutex_init(&sam_flash->lock);
+
+ return sam_flash_mtd_attach(pdev, sam_flash);
+}
+
+static int sam_flash_remove(struct platform_device *pdev)
+{
+ struct sam_flash *sam_flash = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(&sam_flash->mtd_info);
+
+ return 0;
+}
+
+static const struct of_device_id sam_flash_ids[] = {
+ { .compatible = "jnx,flash-sam", },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, sam_flash_ids);
+
+static struct platform_driver sam_flash_driver = {
+ .driver = {
+ .name = "flash-sam",
+ .owner = THIS_MODULE,
+ .of_match_table = sam_flash_ids,
+ },
+ .probe = sam_flash_probe,
+ .remove = sam_flash_remove,
+};
+
+static int __init sam_flash_init(void)
+{
+ return platform_driver_register(&sam_flash_driver);
+}
+
+static void __exit sam_flash_exit(void)
+{
+ platform_driver_unregister(&sam_flash_driver);
+}
+
+module_init(sam_flash_init);
+module_exit(sam_flash_exit);
+
+MODULE_DESCRIPTION("SAM-FPGA FLASH Driver");
+MODULE_LICENSE("GPL");
--
1.9.1
[toc] | [prev] | [next] | [standalone]
| From | Pantelis Antoniou <pantelis.antoniou@konsulko.com> |
|---|---|
| Date | 2016-10-07 17:50 +0200 |
| Subject | [PATCH 01/10] mfd: Add Juniper SAM FPGA MFD driver |
| Message-ID | <spFzk-2tG-35@gated-at.bofh.it> |
| In reply to | #1497289 |
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", ®);
+ 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
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web