summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch645
1 files changed, 645 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch
new file mode 100644
index 000000000..08e350c15
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0002-Add-Aspeed-fmc-spi-driver.patch
@@ -0,0 +1,645 @@
+From 1365492683b47f63d470d0666fee258a5c7ca3c3 Mon Sep 17 00:00:00 2001
+From: Vernon Mauery <vernon.mauery@intel.com>
+Date: Thu, 12 Sep 2019 15:26:08 -0700
+Subject: [PATCH 04/52] Add Aspeed fmc-spi driver
+
+Add the Aspeed fmc-spi driver from the Apeed SDK v5.02
+
+Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
+---
+ arch/arm/boot/dts/aspeed-g6.dtsi | 43 ++-
+ drivers/spi/Kconfig | 6 +
+ drivers/spi/Makefile | 1 +
+ drivers/spi/fmc_spi.c | 530 +++++++++++++++++++++++++++++++
+ 4 files changed, 579 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/spi/fmc_spi.c
+
+diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi
+index 42f644ac8111..1aab48fbf49e 100644
+--- a/arch/arm/boot/dts/aspeed-g6.dtsi
++++ b/arch/arm/boot/dts/aspeed-g6.dtsi
+@@ -90,7 +90,7 @@
+ <0x40464000 0x2000>,
+ <0x40466000 0x2000>;
+ };
+-
++#if 1
+ fmc: spi@1e620000 {
+ reg = < 0x1e620000 0xc4
+ 0x20000000 0x10000000 >;
+@@ -169,6 +169,47 @@
+ status = "disabled";
+ };
+ };
++#else
++ spi0: spi@1e620000 {
++ /* reg : cs0 : cs1 : cs2 */
++ reg = <0x1e620000 0x100
++ 0x20000000 0x40
++ 0x28000000 0x40>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "aspeed,fmc-spi";
++ clocks = <&syscon ASPEED_CLK_AHB>;
++ status = "disable";
++ number_of_chip_select = /bits/ 16 <2>;
++ interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
++ };
++
++ spi1: spi1@1e630000 {
++ /* reg : cs0 : cs1 */
++ reg = <0x1e630000 0x100
++ 0x30000000 0x20
++ 0x32000000 0x20>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "aspeed,fmc-spi";
++ clocks = <&syscon ASPEED_CLK_AHB>;
++ status = "disable";
++ number_of_chip_select = /bits/ 16 <2>;
++ };
++
++ spi2: spi2@1e631000 {
++ /* reg : cs0 : cs1 */
++ reg = <0x1e631000 0x100
++ 0x38000000 0x20
++ 0x3A000000 0x20>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "aspeed,fmc-spi";
++ clocks = <&syscon ASPEED_CLK_AHB>;
++ status = "disable";
++ number_of_chip_select = /bits/ 16 <2>;
++ };
++#endif
+
+ mdio0: mdio@1e650000 {
+ compatible = "aspeed,ast2600-mdio";
+diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
+index 6ee514fd0920..9f32c31ffa3c 100644
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -57,6 +57,12 @@ config SPI_MEM
+
+ comment "SPI Master Controller Drivers"
+
++config SPI_FMC
++ tristate "Aspeed FMC SPI Controller"
++ depends on ARCH_ASPEED
++ help
++ This selects a driver for the AST FMC SPI Controller
++
+ config SPI_ALTERA
+ tristate "Altera SPI Controller"
+ help
+diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
+index adbebee93a75..224b9b71e29c 100644
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_SPIDEV) += spidev.o
+ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
+
+ # SPI master controller drivers (bus)
++obj-$(CONFIG_SPI_FMC) += fmc_spi.o
+ obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
+ obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
+ obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
+diff --git a/drivers/spi/fmc_spi.c b/drivers/spi/fmc_spi.c
+new file mode 100644
+index 000000000000..f21f7a00496e
+--- /dev/null
++++ b/drivers/spi/fmc_spi.c
+@@ -0,0 +1,530 @@
++/*
++ * fmc_spi.c - FMC SPI driver for the Aspeed SoC
++ *
++ * Copyright (C) ASPEED Technology Inc.
++ * Ryan Chen <ryan_chen@aspeedtech.com>
++ *
++ * 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.
++ *
++ */
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/clk.h>
++#include <linux/sched.h>
++#include <linux/kernel.h>
++#include <linux/delay.h>
++#include <linux/interrupt.h>
++#include <linux/ioport.h>
++#include <linux/platform_device.h>
++#include <linux/err.h>
++#include <linux/errno.h>
++#include <linux/wait.h>
++#include <linux/delay.h>
++#include <linux/spi/spi.h>
++#include <linux/spi/flash.h>
++
++#include <linux/of_address.h>
++#include <linux/of_irq.h>
++#include <linux/of_platform.h>
++
++/******************************************************************************/
++/* AST_SPI_CONFIG 0x00 : SPI00 CE Type Setting Register */
++#define AST_G5_SPI_CONF_CE1_WEN (0x1 << 17)
++#define AST_G5_SPI_CONF_CE0_WEN (0x1 << 16)
++
++#define SPI_CONF_CE0_WEN (0x1)
++
++/* Register offsets */
++#define FMC_SPI_CONFIG 0x00
++#define FMC_SPI_CTRL 0x04
++#define FMC_SPI_DMA_STS 0x08
++
++#define FMC_SPI_CE0_CTRL 0x10
++#define FMC_SPI_CE1_CTRL 0x14
++
++#define AST_SPI_DMA_CTRL 0x80
++#define AST_SPI_DMA_FLASH_BASE 0x84
++#define AST_SPI_DMA_DRAM_BASE 0x88
++#define AST_SPI_DMA_LENGTH 0x8c
++
++/* AST_FMC_CONFIG 0x00 : FMC00 CE Type Setting Register */
++#define FMC_CONF_LAGACY_DIS (0x1 << 31)
++#define FMC_CONF_CE1_WEN (0x1 << 17)
++#define FMC_CONF_CE0_WEN (0x1 << 16)
++#define FMC_CONF_CE1_SPI (0x2 << 2)
++#define FMC_CONF_CE0_SPI (0x2)
++
++/* FMC_SPI_CTRL : 0x04 : FMC04 CE Control Register */
++#define FMC_CTRL_CE1_4BYTE_MODE (0x1 << 1)
++#define FMC_CTRL_CE0_4BYTE_MODE (0x1)
++
++/* FMC_SPI_DMA_STS : 0x08 : FMC08 Interrupt Control and Status Register */
++#define FMC_STS_DMA_READY 0x0800
++#define FMC_STS_DMA_CLEAR 0x0800
++
++/* FMC_CE0_CTRL for SPI 0x10, 0x14, 0x18, 0x1c, 0x20 */
++#define SPI_IO_MODE_MASK (3 << 28)
++#define SPI_SINGLE_BIT (0 << 28)
++#define SPI_DUAL_MODE (0x2 << 28)
++#define SPI_DUAL_IO_MODE (0x3 << 28)
++#define SPI_QUAD_MODE (0x4 << 28)
++#define SPI_QUAD_IO_MODE (0x5 << 28)
++
++#define SPI_CE_WIDTH(x) (x << 24)
++#define SPI_CMD_DATA_MASK (0xff << 16)
++#define SPI_CMD_DATA(x) (x << 16)
++#define SPI_DUMMY_CMD (1 << 15)
++#define SPI_DUMMY_HIGH (1 << 14)
++//#define SPI_CLK_DIV (1 << 13) ?? TODO ask....
++//#define SPI_ADDR_CYCLE (1 << 13) ?? TODO ask....
++#define SPI_CMD_MERGE_DIS (1 << 12)
++#define SPI_CLK_DIV(x) (x << 8)
++#define SPI_CLK_DIV_MASK (0xf << 8)
++
++#define SPI_DUMMY_LOW_MASK (0x3 << 6)
++#define SPI_DUMMY_LOW(x) ((x) << 6)
++#define SPI_LSB_FIRST_CTRL (1 << 5)
++#define SPI_CPOL_1 (1 << 4)
++#define SPI_DUAL_DATA (1 << 3)
++#define SPI_CE_INACTIVE (1 << 2)
++#define SPI_CMD_MODE_MASK (0x3)
++#define SPI_CMD_NORMAL_READ_MODE 0
++#define SPI_CMD_READ_CMD_MODE 1
++#define SPI_CMD_WRITE_CMD_MODE 2
++#define SPI_CMD_USER_MODE 3
++
++/* AST_SPI_DMA_CTRL 0x80 */
++#define FMC_DMA_ENABLE (0x1)
++
++/******************************************************************************/
++struct fmc_spi_host {
++ void __iomem *base;
++ void __iomem *ctrl_reg;
++ u32 buff[5];
++ struct spi_master *master;
++ struct spi_device *spi_dev;
++ struct device *dev;
++ u32 ahb_clk;
++ spinlock_t lock;
++};
++
++static u32 ast_spi_calculate_divisor(struct fmc_spi_host *host,
++ u32 max_speed_hz)
++{
++ // [0] ->15 : HCLK , HCLK/16
++ u8 SPI_DIV[16] = {
++ 16, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
++ };
++ u32 i, spi_cdvr = 0;
++
++ for (i = 1; i < 17; i++) {
++ if (max_speed_hz >= (host->ahb_clk / i)) {
++ spi_cdvr = SPI_DIV[i - 1];
++ break;
++ }
++ }
++
++ // printk("hclk is %d, divisor is %d, target :%d , cal speed %d\n", host->ahb_clk, spi_cdvr, spi->max_speed_hz, hclk/i);
++ return spi_cdvr;
++}
++
++/* the spi->mode bits understood by this driver: */
++#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
++
++static int fmc_spi_setup(struct spi_device *spi)
++{
++ struct fmc_spi_host *host =
++ (struct fmc_spi_host *)spi_master_get_devdata(spi->master);
++ unsigned int bits = spi->bits_per_word;
++ u32 fmc_config = 0;
++ u32 spi_ctrl = 0;
++ u32 divisor;
++
++ // dev_dbg(host->dev, "fmc_spi_setup() cs: %d, spi->mode %d \n", spi->chip_select, spi->mode);
++ // printk("fmc_spi_setup() cs: %d, spi->mode %d spi->max_speed_hz %d , spi->bits_per_word %d \n", spi->chip_select, spi->mode, spi->max_speed_hz, spi->bits_per_word);
++
++ switch (spi->chip_select) {
++ case 0:
++ fmc_config |= FMC_CONF_CE0_WEN | FMC_CONF_CE0_SPI;
++ host->ctrl_reg = host->base + FMC_SPI_CE0_CTRL;
++ break;
++ case 1:
++ fmc_config |= FMC_CONF_CE1_WEN | FMC_CONF_CE1_SPI;
++ host->ctrl_reg = host->base + FMC_SPI_CE0_CTRL;
++ break;
++ default:
++ dev_dbg(&spi->dev,
++ "setup: invalid chipselect %u (%u defined)\n",
++ spi->chip_select, spi->master->num_chipselect);
++ return -EINVAL;
++ break;
++ }
++ writel(fmc_config, host->base);
++
++ if (bits == 0)
++ bits = 8;
++
++ if (bits < 8 || bits > 16) {
++ dev_dbg(&spi->dev,
++ "setup: invalid bits_per_word %u (8 to 16)\n", bits);
++ return -EINVAL;
++ }
++
++ if (spi->mode & ~MODEBITS) {
++ dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
++ spi->mode & ~MODEBITS);
++ return -EINVAL;
++ }
++
++ /* see notes above re chipselect */
++ if ((spi->chip_select == 0) && (spi->mode & SPI_CS_HIGH)) {
++ dev_dbg(&spi->dev, "setup: can't be active-high\n");
++ return -EINVAL;
++ }
++
++ /*
++ * Pre-new_1 chips start out at half the peripheral
++ * bus speed.
++ */
++
++ if (spi->max_speed_hz) {
++ /* Set the SPI slaves select and characteristic control register */
++ divisor = ast_spi_calculate_divisor(host, spi->max_speed_hz);
++ } else {
++ /* speed zero means "as slow as possible" */
++ divisor = 15;
++ }
++
++ spi_ctrl &= ~SPI_CLK_DIV_MASK;
++ // printk("set div %x \n",divisor);
++ //TODO MASK first
++ spi_ctrl |= SPI_CLK_DIV(divisor);
++
++ /* only support mode 0 (CPOL=0, CPHA=0) and cannot support mode 1 ~ mode 3 */
++
++#if 0
++ if (SPI_CPHA & spi->mode)
++ cpha = SPI_CPHA_1;
++ else
++ cpha = SPI_CPHA_0;
++#endif
++
++ // if (SPI_CPOL & spi->mode)
++ // spi_ctrl |= SPI_CPOL_1;
++ // else
++ // spi_ctrl &= ~SPI_CPOL_1;
++
++ //ISSUE : ast spi ctrl couldn't use mode 3, so fix mode 0
++ spi_ctrl &= ~SPI_CPOL_1;
++
++ if (SPI_LSB_FIRST & spi->mode)
++ spi_ctrl |= SPI_LSB_FIRST_CTRL;
++ else
++ spi_ctrl &= ~SPI_LSB_FIRST_CTRL;
++
++ /* Configure SPI controller */
++ writel(spi_ctrl, host->ctrl_reg);
++
++ // printk("ctrl %x, ", spi_ctrl);
++ return 0;
++}
++
++static int fmc_spi_transfer(struct spi_device *spi, struct spi_message *msg)
++{
++ struct fmc_spi_host *host =
++ (struct fmc_spi_host *)spi_master_get_devdata(spi->master);
++ struct spi_transfer *xfer;
++ const u8 *tx_buf;
++ u8 *rx_buf;
++ unsigned long flags;
++
++ int i = 0, j = 0;
++
++ // dev_dbg(host->dev, "xfer %s \n", dev_name(&spi->dev));
++ // printk("xfer spi->chip_select %d \n", spi->chip_select);
++
++ host->spi_dev = spi;
++ spin_lock_irqsave(&host->lock, flags);
++
++ writel(readl(host->ctrl_reg) | SPI_CMD_USER_MODE, host->ctrl_reg);
++ msg->actual_length = 0;
++ msg->status = 0;
++
++ list_for_each_entry (xfer, &msg->transfers, transfer_list) {
++ dev_dbg(host->dev,
++ "xfer[%d] %p: width %d, len %u, tx %p/%08x, rx %p/%08x\n",
++ j, xfer, xfer->bits_per_word, xfer->len, xfer->tx_buf,
++ xfer->tx_dma, xfer->rx_buf, xfer->rx_dma);
++
++ tx_buf = xfer->tx_buf;
++ rx_buf = xfer->rx_buf;
++
++ if (tx_buf != 0) {
++#if 0
++ printk("tx : ");
++ if(xfer->len > 10) {
++ for(i=0;i<10;i++)
++ printk("%x ",tx_buf[i]);
++ } else {
++ for(i=0;i<xfer->len;i++)
++ printk("%x ",tx_buf[i]);
++ }
++ printk("\n");
++#endif
++ for (i = 0; i < xfer->len; i++) {
++ writeb(tx_buf[i],
++ (void *)host->buff
++ [host->spi_dev->chip_select]);
++ }
++ }
++ //Issue need clarify
++ udelay(1);
++ if (rx_buf != 0) {
++ for (i = 0; i < xfer->len; i++) {
++ rx_buf[i] = readb(
++ (void *)host->buff
++ [host->spi_dev->chip_select]);
++ }
++#if 0
++ printk("rx : ");
++ if(xfer->len > 10) {
++ for(i=0;i<10;i++)
++ printk(" %x",rx_buf[i]);
++ } else {
++ for(i=0;i<xfer->len;i++)
++ printk(" %x",rx_buf[i]);
++ }
++ printk("\n");
++#endif
++ }
++ dev_dbg(host->dev, "old msg->actual_length %d , +len %d \n",
++ msg->actual_length, xfer->len);
++ msg->actual_length += xfer->len;
++ dev_dbg(host->dev, "new msg->actual_length %d \n",
++ msg->actual_length);
++ // j++;
++ }
++
++ // writel( SPI_CE_INACTIVE | readl(host->spi_data->ctrl_reg),host->spi_data->ctrl_reg);
++ writel(readl(host->ctrl_reg) & ~SPI_CMD_USER_MODE, host->ctrl_reg);
++ msg->status = 0;
++
++ msg->complete(msg->context);
++
++ // spin_unlock(&host->lock);
++ spin_unlock_irqrestore(&host->lock, flags);
++
++ return 0;
++}
++
++static void fmc_spi_cleanup(struct spi_device *spi)
++{
++ struct fmc_spi_host *host = spi_master_get_devdata(spi->master);
++ unsigned long flags;
++ dev_dbg(host->dev, "fmc_spi_cleanup() \n");
++
++ spin_lock_irqsave(&host->lock, flags);
++ // if (host->stay == spi) {
++ // host->stay = NULL;
++ // cs_deactivate(host, spi);
++ // }
++ spin_unlock_irqrestore(&host->lock, flags);
++}
++
++#if 0
++static int fmc_spi_flash_read(struct spi_device *spi,
++ struct spi_flash_read_message *msg)
++{
++// struct fmc_spi_host *host = spi_master_get_devdata(spi->master);
++ int ret = 0;
++
++// printk("read msg->from %x, msg->len %x , msg->buf %x , msg->addr_width %d , msg->dummy_bytes %x , msg->read_opcode %x \n", msg->from, msg->len, msg->buf, msg->addr_width, msg->dummy_bytes, msg->read_opcode);
++
++// memcpy_fromio(msg->buf, b53spi->mmio_base + msg->from, msg->len);
++ msg->retlen = msg->len;
++
++ return ret;
++}
++#endif
++
++static int fmc_spi_probe(struct platform_device *pdev)
++{
++ struct resource *res;
++ struct fmc_spi_host *host;
++ struct spi_master *master;
++ struct clk *clk;
++ int cs_num = 0;
++ int err = 0;
++
++ dev_dbg(&pdev->dev, "fmc_spi_probe() \n");
++
++ master = spi_alloc_master(&pdev->dev, sizeof(struct fmc_spi_host));
++ if (NULL == master) {
++ dev_err(&pdev->dev, "No memory for spi_master\n");
++ err = -ENOMEM;
++ goto err_nomem;
++ }
++
++ /* the spi->mode bits understood by this driver: */
++ master->mode_bits =
++ SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_RX_DUAL | SPI_TX_DUAL;
++ master->bits_per_word_mask = SPI_BPW_MASK(8);
++
++ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_RX_DUAL;
++ // master->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 16);
++ master->dev.of_node = pdev->dev.of_node;
++ master->bus_num = pdev->id;
++ // master->num_chipselect = master->dev.of_node ? 0 : 4;
++ platform_set_drvdata(pdev, master);
++
++ host = spi_master_get_devdata(master);
++ memset(host, 0, sizeof(struct fmc_spi_host));
++
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if (!res) {
++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM 0\n");
++ err = -ENXIO;
++ goto err_no_io_res;
++ }
++
++ host->base = devm_ioremap_resource(&pdev->dev, res);
++ if (!host->base) {
++ dev_err(&pdev->dev, "cannot remap register\n");
++ err = -EIO;
++ goto err_no_io_res;
++ }
++
++ clk = devm_clk_get(&pdev->dev, NULL);
++ if (IS_ERR(clk)) {
++ dev_err(&pdev->dev, "no clock defined\n");
++ return -ENODEV;
++ }
++ host->ahb_clk = clk_get_rate(clk);
++
++ dev_dbg(&pdev->dev, "remap phy %x, virt %x \n", (u32)res->start,
++ (u32)host->base);
++
++ host->master = spi_master_get(master);
++
++ if (of_property_read_u16(pdev->dev.of_node, "number_of_chip_select",
++ &host->master->num_chipselect))
++ goto err_register;
++
++ for (cs_num = 0; cs_num < host->master->num_chipselect; cs_num++) {
++ res = platform_get_resource(pdev, IORESOURCE_MEM, cs_num + 1);
++ if (!res) {
++ dev_err(&pdev->dev, "cannot get IORESOURCE_IO 0\n");
++ return -ENXIO;
++ }
++
++ host->buff[cs_num] =
++ (u32)devm_ioremap_resource(&pdev->dev, res);
++ if (!host->buff[cs_num]) {
++ dev_err(&pdev->dev, "cannot remap buffer \n");
++ err = -EIO;
++ goto err_no_io_res;
++ }
++
++ dev_dbg(&pdev->dev, "remap io phy %x, virt %x \n",
++ (u32)res->start, (u32)host->buff[cs_num]);
++ }
++
++ host->master->bus_num = pdev->id;
++ host->dev = &pdev->dev;
++
++ /* Setup the state for bitbang driver */
++ host->master->setup = fmc_spi_setup;
++ host->master->transfer = fmc_spi_transfer;
++ host->master->cleanup = fmc_spi_cleanup;
++ // host->master->spi_flash_read = fmc_spi_flash_read;
++
++ platform_set_drvdata(pdev, host);
++
++ /* Register our spi controller */
++ err = devm_spi_register_master(&pdev->dev, host->master);
++ if (err) {
++ dev_err(&pdev->dev, "failed to register SPI master\n");
++ goto err_register;
++ }
++
++ dev_dbg(&pdev->dev, "fmc_spi : driver load \n");
++
++ return 0;
++
++err_register:
++ spi_master_put(host->master);
++ iounmap(host->base);
++ for (cs_num = 0; cs_num < host->master->num_chipselect; cs_num++) {
++ iounmap((void *)host->buff[cs_num]);
++ }
++
++err_no_io_res:
++ kfree(master);
++ kfree(host);
++
++err_nomem:
++ return err;
++}
++
++static int fmc_spi_remove(struct platform_device *pdev)
++{
++ struct resource *res0;
++ struct fmc_spi_host *host = platform_get_drvdata(pdev);
++
++ dev_dbg(host->dev, "fmc_spi_remove()\n");
++
++ if (!host)
++ return -1;
++
++ res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ release_mem_region(res0->start, res0->end - res0->start + 1);
++ iounmap(host->base);
++ iounmap(host->buff);
++
++ platform_set_drvdata(pdev, NULL);
++ spi_unregister_master(host->master);
++ spi_master_put(host->master);
++ return 0;
++}
++
++#ifdef CONFIG_PM
++static int fmc_spi_suspend(struct platform_device *pdev, pm_message_t msg)
++{
++ return 0;
++}
++
++static int fmc_spi_resume(struct platform_device *pdev)
++{
++ return 0;
++}
++#else
++#define fmc_spi_suspend NULL
++#define fmc_spi_resume NULL
++#endif
++
++static const struct of_device_id fmc_spi_of_match[] = {
++ { .compatible = "aspeed,fmc-spi" },
++ {},
++};
++
++static struct platform_driver fmc_spi_driver = {
++ .probe = fmc_spi_probe,
++ .remove = fmc_spi_remove,
++#ifdef CONFIG_PM
++ .suspend = fmc_spi_suspend,
++ .resume = fmc_spi_resume,
++#endif
++ .driver = {
++ .name = KBUILD_MODNAME,
++ .of_match_table = fmc_spi_of_match,
++ },
++};
++
++module_platform_driver(fmc_spi_driver);
++
++MODULE_DESCRIPTION("FMC SPI Driver");
++MODULE_AUTHOR("Ryan Chen");
++MODULE_LICENSE("GPL");
+--
+2.17.1
+