]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
UBUNTU: SAUCE: import Huawei ES3000_V2 (2.1.0.23)
authorHuawei SSD DEV Team <>
Thu, 14 Jul 2016 16:13:36 +0000 (09:13 -0700)
committerSeth Forshee <seth.forshee@canonical.com>
Mon, 29 Jan 2018 13:44:54 +0000 (07:44 -0600)
BugLink: http://bugs.launchpad.net/bugs/1635594
Source: http://support.huawei.com/enterprisesearch/ebgSearch#sp.keyword=HUAWEI%20ES3000%20V2%20Driver%20SRC

  Huawei SSD device driver
  Copyright (c) 2016, Huawei Technologies Co., Ltd.

  This program is free software; you can redistribute it and/or modify it
  under the terms and conditions of the GNU General Public License,
  version 2, as published by the Free Software Foundation.

  This program is distributed in the hope 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.

Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Acked-by: Brad Figg <brad.figg@canonical.com>
Acked-by: Tim Gardner <tim.gardner@canonical.com>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
BugLink: http://bugs.launchpad.net/bugs/1635594
Signed-off-by: Andy Whitcroft <apw@canonical.com>
Acked-by: Leann Ogasawara <leann.ogasawara@canonical.com>
Acked-by: Stefan Bader <stefan.bader@canonical.com>
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
ubuntu/hio/Makefile [new file with mode: 0644]
ubuntu/hio/hio.c [new file with mode: 0644]
ubuntu/hio/hio.h [new file with mode: 0644]

diff --git a/ubuntu/hio/Makefile b/ubuntu/hio/Makefile
new file mode 100644 (file)
index 0000000..b03e627
--- /dev/null
@@ -0,0 +1,69 @@
+ifneq ($(KERNELRELEASE),)
+#      hio-y := hio_main.o
+#      obj-$(CONFIG_HIO_DRIVER) += hio.o
+       obj-m += hio.o
+else
+
+KVER=
+ifeq ($(KVER),)
+  KVER=$(shell uname -r)
+endif
+
+PDIR=
+ifeq ($(PDIR),)
+  PDIR=extra
+endif
+
+PREFIX=
+
+MODDIR=/lib/modules/$(KVER)/$(PDIR)/hio/
+MOD=hio.ko
+
+ifeq ($(KVER),2.6.32-300.3.1.el6uek.x86_64)
+  FLAGS += -DSSD_QUEUE_PBIO
+endif
+ifeq ($(KVER),2.6.32-220.el6.x86_64)
+  FLAGS += -DSSD_QUEUE_PBIO
+endif
+ifeq ($(KVER),2.6.32-358.el6.x86_64)
+  FLAGS += -DSSD_QUEUE_PBIO
+endif
+ifeq ($(KVER),2.6.32-358.23.2.el6.x86_64)
+  FLAGS += -DSSD_QUEUE_PBIO
+endif
+ifeq ($(KVER),3.0.58-0.6.6-xen)
+  FLAGS += -DSSD_QUEUE_PBIO
+endif
+ifeq ($(KVER),3.2.0-4-amd64)
+  FLAGS += -DSSD_BIOVEC_PHYS_MERGEABLE_FIXED
+endif
+ifeq ($(KVER),2.6.39-400.209.1.el5uek) #Oracle Linux Server release 5.10
+  FLAGS += -DSSD_BIOVEC_PHYS_MERGEABLE_FIXED
+endif
+ifeq ($(KVER),2.6.39-400.215.10.el5uek) #Oracle Linux Server release 5.11
+  FLAGS += -DSSD_BIOVEC_PHYS_MERGEABLE_FIXED
+endif
+ifeq ($(KVER),2.6.39-200.24.1.el6uek.x86_64) #Oracle Linux Server release 6.3
+  FLAGS += -DSSD_BIOVEC_PHYS_MERGEABLE_FIXED
+endif
+ifeq ($(KVER),2.6.39-400.17.1.el6uek.x86_64) #Oracle Linux Server release 6.4
+  FLAGS += -DSSD_BIOVEC_PHYS_MERGEABLE_FIXED
+endif
+
+
+KERNELDIR ?= /lib/modules/$(KVER)/build
+PWD       := $(shell pwd)
+
+default:
+       $(MAKE) -C $(KERNELDIR) M=$(PWD) EXTRA_CFLAGS="$(FLAGS)" modules
+clean:
+       rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.*
+
+install: default
+       mkdir -p $(PREFIX)/$(MODDIR)
+       install -m 444 $(MOD) $(PREFIX)/$(MODDIR)
+       @if [ "$(PREFIX)" = "" ]; then /sbin/depmod -a ;\
+       else echo " *** Run 'depmod -a' to update the module database.";\
+       fi
+endif
+
diff --git a/ubuntu/hio/hio.c b/ubuntu/hio/hio.c
new file mode 100644 (file)
index 0000000..4ea98d8
--- /dev/null
@@ -0,0 +1,12527 @@
+/*
+* Huawei SSD device driver
+* Copyright (c) 2016, Huawei Technologies Co., Ltd.
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope 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.
+*/
+
+#ifndef LINUX_VERSION_CODE
+#include <linux/version.h>
+#endif
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16))
+#include <linux/config.h>
+#endif
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bio.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/blkdev.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/compiler.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/fs.h>
+#include <linux/dma-mapping.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mm.h>
+#include <linux/ioctl.h>
+#include <linux/hdreg.h>       /* HDIO_GETGEO */
+#include <linux/list.h>
+#include <linux/reboot.h>
+#include <linux/kthread.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0))
+#include <linux/seq_file.h>
+#endif
+#include <asm/uaccess.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0))
+#include <linux/scatterlist.h>
+#include <linux/vmalloc.h>
+#else
+#include <asm/scatterlist.h>
+#endif
+#include <asm/io.h>
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17))
+#include <linux/devfs_fs_kernel.h>
+#endif
+
+/* driver */
+#define MODULE_NAME            "hio"
+#define DRIVER_VERSION "2.1.0.23"
+#define DRIVER_VERSION_LEN     16
+
+#define SSD_FW_MIN             0x1
+
+#define SSD_DEV_NAME   MODULE_NAME
+#define SSD_DEV_NAME_LEN       16
+#define SSD_CDEV_NAME  "c"SSD_DEV_NAME
+#define SSD_SDEV_NAME  "s"SSD_DEV_NAME
+
+
+#define SSD_CMAJOR             0
+#define SSD_MAJOR              0
+#define SSD_MAJOR_SL   0
+#define SSD_MINORS             16
+
+#define SSD_MAX_DEV            702
+#define SSD_ALPHABET_NUM       26
+
+#define hio_info(f, arg...) printk(KERN_INFO MODULE_NAME"info: " f , ## arg)
+#define hio_note(f, arg...) printk(KERN_NOTICE MODULE_NAME"note: " f , ## arg)
+#define hio_warn(f, arg...) printk(KERN_WARNING MODULE_NAME"warn: " f , ## arg)
+#define hio_err(f, arg...)  printk(KERN_ERR MODULE_NAME"err: " f , ## arg)
+
+/* slave port */
+#define SSD_SLAVE_PORT_DEVID   0x000a
+
+/* int mode */
+
+/* 2.6.9 msi affinity bug, should turn msi & msi-x off */
+//#define SSD_MSI
+#define SSD_ESCAPE_IRQ
+
+//#define SSD_MSIX
+#ifndef MODULE
+#define SSD_MSIX
+#endif
+#define SSD_MSIX_VEC   8
+#ifdef SSD_MSIX
+#undef SSD_MSI
+//#undef SSD_ESCAPE_IRQ
+#define SSD_MSIX_AFFINITY_FORCE
+#endif
+
+#define SSD_TRIM
+
+/* Over temperature protect */
+#define SSD_OT_PROTECT
+
+#ifdef SSD_QUEUE_PBIO
+#define BIO_SSD_PBIO           20
+#endif
+
+/* debug */
+//#define SSD_DEBUG_ERR
+
+/* cmd timer */
+#define SSD_CMD_TIMEOUT                (60*HZ)
+
+/* i2c & smbus */
+#define SSD_SPI_TIMEOUT                (5*HZ)
+#define SSD_I2C_TIMEOUT                (5*HZ)
+
+#define SSD_I2C_MAX_DATA       (127)
+#define SSD_SMBUS_BLOCK_MAX    (32)
+#define SSD_SMBUS_DATA_MAX     (SSD_SMBUS_BLOCK_MAX + 2)
+
+/* wait for init */
+#define SSD_INIT_WAIT          (1000) //1s
+#define SSD_CONTROLLER_WAIT    (20*1000/SSD_INIT_WAIT) //20s
+#define SSD_INIT_MAX_WAIT      (500*1000/SSD_INIT_WAIT) //500s
+#define SSD_INIT_MAX_WAIT_V3_2 (1400*1000/SSD_INIT_WAIT) //1400s
+#define SSD_RAM_INIT_MAX_WAIT  (10*1000/SSD_INIT_WAIT) //10s
+#define SSD_CH_INFO_MAX_WAIT   (10*1000/SSD_INIT_WAIT) //10s
+
+/* blkdev busy wait */
+#define SSD_DEV_BUSY_WAIT      1000 //ms
+#define SSD_DEV_BUSY_MAX_WAIT  (8*1000/SSD_DEV_BUSY_WAIT) //8s
+
+/* smbus retry */
+#define SSD_SMBUS_RETRY_INTERVAL       (5) //ms
+#define SSD_SMBUS_RETRY_MAX                    (1000/SSD_SMBUS_RETRY_INTERVAL)
+
+#define SSD_BM_RETRY_MAX                       7
+
+/* bm routine interval */
+#define SSD_BM_CAP_LEARNING_DELAY      (10*60*1000)
+
+/* routine interval */
+#define SSD_ROUTINE_INTERVAL           (10*1000)       //10s
+#define SSD_HWMON_ROUTINE_TICK         (60*1000/SSD_ROUTINE_INTERVAL)
+#define SSD_CAPMON_ROUTINE_TICK                ((3600*1000/SSD_ROUTINE_INTERVAL)*24*30)
+#define SSD_CAPMON2_ROUTINE_TICK       (10*60*1000/SSD_ROUTINE_INTERVAL)       //fault recover
+
+/* dma align */
+#define SSD_DMA_ALIGN          (16)
+
+/* some hw defalut */
+#define SSD_LOG_MAX_SZ         4096
+
+#define SSD_NAND_OOB_SZ                1024
+#define SSD_NAND_ID_SZ         8
+#define SSD_NAND_ID_BUFF_SZ    1024
+#define SSD_NAND_MAX_CE                2
+
+#define SSD_BBT_RESERVED       8
+
+#define SSD_ECC_MAX_FLIP       (64+1)
+
+#define SSD_RAM_ALIGN          16
+
+
+#define SSD_RELOAD_FLAG                0x3333CCCC
+#define SSD_RELOAD_FW          0xAA5555AA
+#define SSD_RESET_NOINIT       0xAA5555AA
+#define SSD_RESET                      0x55AAAA55
+#define SSD_RESET_FULL         0x5A
+//#define SSD_RESET_WAIT               1000    //1s
+//#define SSD_RESET_MAX_WAIT   (200*1000/SSD_RESET_WAIT) //200s
+
+
+/* reverion 1 */
+#define SSD_PROTOCOL_V1                        0x0
+
+#define SSD_ROM_SIZE                   (16*1024*1024)
+#define SSD_ROM_BLK_SIZE               (256*1024)
+#define SSD_ROM_PAGE_SIZE              (256)
+#define SSD_ROM_NR_BRIDGE_FW   2
+#define SSD_ROM_NR_CTRL_FW             2
+#define SSD_ROM_BRIDGE_FW_BASE 0
+#define SSD_ROM_BRIDGE_FW_SIZE (2*1024*1024)
+#define SSD_ROM_CTRL_FW_BASE   (SSD_ROM_NR_BRIDGE_FW*SSD_ROM_BRIDGE_FW_SIZE)
+#define SSD_ROM_CTRL_FW_SIZE   (5*1024*1024)
+#define SSD_ROM_LABEL_BASE             (SSD_ROM_CTRL_FW_BASE+SSD_ROM_CTRL_FW_SIZE*SSD_ROM_NR_CTRL_FW)
+#define SSD_ROM_VP_BASE                        (SSD_ROM_LABEL_BASE+SSD_ROM_BLK_SIZE)
+
+/* reverion 3 */
+#define SSD_PROTOCOL_V3                        0x3000000
+#define SSD_PROTOCOL_V3_1_1            0x3010001
+#define SSD_PROTOCOL_V3_1_3            0x3010003
+#define SSD_PROTOCOL_V3_2              0x3020000
+#define SSD_PROTOCOL_V3_2_1            0x3020001       /* <4KB improved */
+#define SSD_PROTOCOL_V3_2_2            0x3020002       /* ot protect */
+#define SSD_PROTOCOL_V3_2_4            0x3020004
+
+
+#define SSD_PV3_ROM_NR_BM_FW   1
+#define SSD_PV3_ROM_BM_FW_SZ   (64*1024*8)
+
+#define SSD_ROM_LOG_SZ                 (64*1024*4)
+
+#define SSD_ROM_NR_SMART_MAX   2
+#define SSD_PV3_ROM_NR_SMART   SSD_ROM_NR_SMART_MAX
+#define SSD_PV3_ROM_SMART_SZ   (64*1024)
+
+/* reverion 3.2 */
+#define SSD_PV3_2_ROM_LOG_SZ   (64*1024*80) /* 5MB */
+#define SSD_PV3_2_ROM_SEC_SZ   (256*1024) /* 256KB */
+
+
+/* register */
+#define SSD_REQ_FIFO_REG               0x0000
+#define SSD_RESP_FIFO_REG              0x0008  //0x0010
+#define SSD_RESP_PTR_REG               0x0010  //0x0018
+#define SSD_INTR_INTERVAL_REG  0x0018
+#define SSD_READY_REG                  0x001C
+#define SSD_BRIDGE_TEST_REG            0x0020
+#define SSD_STRIPE_SIZE_REG            0x0028
+#define SSD_CTRL_VER_REG               0x0030  //controller
+#define SSD_BRIDGE_VER_REG             0x0034  //bridge
+#define SSD_PCB_VER_REG                        0x0038
+#define SSD_BURN_FLAG_REG              0x0040
+#define SSD_BRIDGE_INFO_REG            0x0044
+
+#define SSD_WL_VAL_REG                 0x0048  //32-bit
+
+#define SSD_BB_INFO_REG                        0x004C
+
+#define SSD_ECC_TEST_REG               0x0050 //test only
+#define SSD_ERASE_TEST_REG             0x0058 //test only
+#define SSD_WRITE_TEST_REG             0x0060 //test only
+
+#define SSD_RESET_REG                  0x0068
+#define SSD_RELOAD_FW_REG              0x0070
+
+#define SSD_RESERVED_BLKS_REG  0x0074
+#define SSD_VALID_PAGES_REG            0x0078
+#define SSD_CH_INFO_REG                        0x007C
+
+#define SSD_CTRL_TEST_REG_SZ   0x8
+#define SSD_CTRL_TEST_REG0             0x0080
+#define SSD_CTRL_TEST_REG1             0x0088
+#define SSD_CTRL_TEST_REG2             0x0090
+#define SSD_CTRL_TEST_REG3             0x0098
+#define SSD_CTRL_TEST_REG4             0x00A0
+#define SSD_CTRL_TEST_REG5             0x00A8
+#define SSD_CTRL_TEST_REG6             0x00B0
+#define SSD_CTRL_TEST_REG7             0x00B8
+
+#define SSD_FLASH_INFO_REG0            0x00C0
+#define SSD_FLASH_INFO_REG1            0x00C8
+#define SSD_FLASH_INFO_REG2            0x00D0
+#define SSD_FLASH_INFO_REG3            0x00D8
+#define SSD_FLASH_INFO_REG4            0x00E0
+#define SSD_FLASH_INFO_REG5            0x00E8
+#define SSD_FLASH_INFO_REG6            0x00F0
+#define SSD_FLASH_INFO_REG7            0x00F8
+
+#define SSD_RESP_INFO_REG              0x01B8
+#define SSD_NAND_BUFF_BASE             0x01BC //for nand write
+
+#define SSD_CHIP_INFO_REG_SZ   0x10
+#define SSD_CHIP_INFO_REG0             0x0100  //128 bit
+#define SSD_CHIP_INFO_REG1             0x0110
+#define SSD_CHIP_INFO_REG2             0x0120
+#define SSD_CHIP_INFO_REG3             0x0130
+#define SSD_CHIP_INFO_REG4             0x0140
+#define SSD_CHIP_INFO_REG5             0x0150
+#define SSD_CHIP_INFO_REG6             0x0160
+#define SSD_CHIP_INFO_REG7             0x0170
+
+#define SSD_RAM_INFO_REG               0x01C4
+
+#define SSD_BBT_BASE_REG               0x01C8
+#define SSD_ECT_BASE_REG               0x01CC
+
+#define SSD_CLEAR_INTR_REG             0x01F0
+
+#define SSD_INIT_STATE_REG_SZ  0x8
+#define SSD_INIT_STATE_REG0            0x0200
+#define SSD_INIT_STATE_REG1            0x0208
+#define SSD_INIT_STATE_REG2            0x0210
+#define SSD_INIT_STATE_REG3            0x0218
+#define SSD_INIT_STATE_REG4            0x0220
+#define SSD_INIT_STATE_REG5            0x0228
+#define SSD_INIT_STATE_REG6            0x0230
+#define SSD_INIT_STATE_REG7            0x0238
+
+#define SSD_ROM_INFO_REG               0x0600
+#define SSD_ROM_BRIDGE_FW_INFO_REG     0x0604
+#define SSD_ROM_CTRL_FW_INFO_REG       0x0608
+#define SSD_ROM_VP_INFO_REG            0x060C
+
+#define SSD_LOG_INFO_REG               0x0610
+#define SSD_LED_REG                            0x0614
+#define SSD_MSG_BASE_REG               0x06F8
+
+/*spi reg */
+#define SSD_SPI_REG_CMD                        0x0180
+#define SSD_SPI_REG_CMD_HI             0x0184
+#define SSD_SPI_REG_WDATA              0x0188
+#define SSD_SPI_REG_ID                 0x0190
+#define SSD_SPI_REG_STATUS             0x0198
+#define SSD_SPI_REG_RDATA              0x01A0
+#define SSD_SPI_REG_READY              0x01A8
+
+/* i2c register */
+#define SSD_I2C_CTRL_REG               0x06F0
+#define SSD_I2C_RDATA_REG              0x06F4
+
+/* temperature reg */
+#define SSD_BRIGE_TEMP_REG             0x0618
+
+#define SSD_CTRL_TEMP_REG0             0x0700
+#define SSD_CTRL_TEMP_REG1             0x0708
+#define SSD_CTRL_TEMP_REG2             0x0710
+#define SSD_CTRL_TEMP_REG3             0x0718
+#define SSD_CTRL_TEMP_REG4             0x0720
+#define SSD_CTRL_TEMP_REG5             0x0728
+#define SSD_CTRL_TEMP_REG6             0x0730
+#define SSD_CTRL_TEMP_REG7             0x0738
+
+/* reversion 3 reg */
+#define SSD_PROTOCOL_VER_REG   0x01B4
+
+#define SSD_FLUSH_TIMEOUT_REG  0x02A4
+#define SSD_BM_FAULT_REG               0x0660
+
+#define SSD_PV3_RAM_STATUS_REG_SZ      0x4
+#define SSD_PV3_RAM_STATUS_REG0        0x0260
+#define SSD_PV3_RAM_STATUS_REG1        0x0264
+#define SSD_PV3_RAM_STATUS_REG2        0x0268
+#define SSD_PV3_RAM_STATUS_REG3        0x026C
+#define SSD_PV3_RAM_STATUS_REG4        0x0270
+#define SSD_PV3_RAM_STATUS_REG5        0x0274
+#define SSD_PV3_RAM_STATUS_REG6        0x0278
+#define SSD_PV3_RAM_STATUS_REG7        0x027C
+
+#define SSD_PV3_CHIP_INFO_REG_SZ       0x40
+#define SSD_PV3_CHIP_INFO_REG0 0x0300
+#define SSD_PV3_CHIP_INFO_REG1 0x0340
+#define SSD_PV3_CHIP_INFO_REG2 0x0380
+#define SSD_PV3_CHIP_INFO_REG3 0x03B0
+#define SSD_PV3_CHIP_INFO_REG4 0x0400
+#define SSD_PV3_CHIP_INFO_REG5 0x0440
+#define SSD_PV3_CHIP_INFO_REG6 0x0480
+#define SSD_PV3_CHIP_INFO_REG7 0x04B0
+
+#define SSD_PV3_INIT_STATE_REG_SZ 0x20
+#define SSD_PV3_INIT_STATE_REG0        0x0500
+#define SSD_PV3_INIT_STATE_REG1        0x0520
+#define SSD_PV3_INIT_STATE_REG2        0x0540
+#define SSD_PV3_INIT_STATE_REG3        0x0560
+#define SSD_PV3_INIT_STATE_REG4        0x0580
+#define SSD_PV3_INIT_STATE_REG5        0x05A0
+#define SSD_PV3_INIT_STATE_REG6        0x05C0
+#define SSD_PV3_INIT_STATE_REG7        0x05E0
+
+/* reversion 3.1.1 reg */
+#define SSD_FULL_RESET_REG             0x01B0
+
+#define SSD_CTRL_REG_ZONE_SZ   0x800
+
+#define SSD_BB_THRESHOLD_L1_REG        0x2C0
+#define SSD_BB_THRESHOLD_L2_REG        0x2C4
+
+#define SSD_BB_ACC_REG_SZ              0x4
+#define SSD_BB_ACC_REG0                        0x21C0
+#define SSD_BB_ACC_REG1                        0x29C0
+#define SSD_BB_ACC_REG2                        0x31C0
+
+#define SSD_EC_THRESHOLD_L1_REG        0x2C8
+#define SSD_EC_THRESHOLD_L2_REG        0x2CC
+
+#define SSD_EC_ACC_REG_SZ              0x4
+#define SSD_EC_ACC_REG0                        0x21E0
+#define SSD_EC_ACC_REG1                        0x29E0
+#define SSD_EC_ACC_REG2                        0x31E0
+
+/* reversion 3.1.2 & 3.1.3 reg */
+#define SSD_HW_STATUS_REG              0x02AC
+
+#define SSD_PLP_INFO_REG               0x0664
+
+/*reversion 3.2 reg*/
+#define SSD_POWER_ON_REG               0x01EC
+#define SSD_PCIE_LINKSTATUS_REG        0x01F8
+#define SSD_PL_CAP_LEARN_REG   0x01FC
+
+#define SSD_FPGA_1V0_REG0              0x2070
+#define SSD_FPGA_1V8_REG0              0x2078
+#define SSD_FPGA_1V0_REG1              0x2870
+#define SSD_FPGA_1V8_REG1              0x2878
+
+/*reversion 3.2 reg*/
+#define SSD_READ_OT_REG0               0x2260
+#define SSD_WRITE_OT_REG0              0x2264
+#define SSD_READ_OT_REG1               0x2A60
+#define SSD_WRITE_OT_REG1              0x2A64
+
+
+/* function */
+#define SSD_FUNC_READ                  0x01
+#define SSD_FUNC_WRITE                 0x02
+#define SSD_FUNC_NAND_READ_WOOB        0x03
+#define SSD_FUNC_NAND_READ             0x04
+#define SSD_FUNC_NAND_WRITE            0x05
+#define SSD_FUNC_NAND_ERASE            0x06
+#define SSD_FUNC_NAND_READ_ID  0x07
+#define SSD_FUNC_READ_LOG              0x08
+#define SSD_FUNC_TRIM                  0x09
+#define SSD_FUNC_RAM_READ              0x10
+#define SSD_FUNC_RAM_WRITE             0x11
+#define SSD_FUNC_FLUSH                 0x12    //cache / bbt
+
+/* spi function */
+#define SSD_SPI_CMD_PROGRAM            0x02
+#define SSD_SPI_CMD_READ               0x03
+#define SSD_SPI_CMD_W_DISABLE  0x04
+#define SSD_SPI_CMD_READ_STATUS        0x05
+#define SSD_SPI_CMD_W_ENABLE   0x06
+#define SSD_SPI_CMD_ERASE              0xd8
+#define SSD_SPI_CMD_CLSR               0x30
+#define SSD_SPI_CMD_READ_ID            0x9f
+
+/* i2c */
+#define SSD_I2C_CTRL_READ              0x00
+#define SSD_I2C_CTRL_WRITE             0x01
+
+/* i2c internal register */
+#define SSD_I2C_CFG_REG                        0x00
+#define SSD_I2C_DATA_REG               0x01
+#define SSD_I2C_CMD_REG                        0x02
+#define SSD_I2C_STATUS_REG             0x03
+#define SSD_I2C_SADDR_REG              0x04
+#define SSD_I2C_LEN_REG                        0x05
+#define SSD_I2C_RLEN_REG               0x06
+#define SSD_I2C_WLEN_REG               0x07
+#define SSD_I2C_RESET_REG              0x08    //write for reset
+#define SSD_I2C_PRER_REG               0x09
+
+
+/* hw mon */
+/* FPGA volt = ADC_value / 4096 * 3v */
+#define SSD_FPGA_1V0_ADC_MIN   1228 // 0.9v
+#define SSD_FPGA_1V0_ADC_MAX   1502 // 1.1v
+#define SSD_FPGA_1V8_ADC_MIN   2211 // 1.62v
+#define SSD_FPGA_1V8_ADC_MAX   2703 // 1.98
+
+/* ADC value */
+#define SSD_FPGA_VOLT_MAX(val) (((val) & 0xffff) >> 4)
+#define SSD_FPGA_VOLT_MIN(val) (((val >> 16) & 0xffff) >> 4)
+#define SSD_FPGA_VOLT_CUR(val) (((val >> 32) & 0xffff) >> 4)
+#define SSD_FPGA_VOLT(val)             ((val * 3000) >> 12)
+
+#define SSD_VOLT_LOG_DATA(idx, ctrl, volt)     (((uint32_t)idx << 24) | ((uint32_t)ctrl << 16) | ((uint32_t)volt))
+
+enum ssd_fpga_volt
+{
+       SSD_FPGA_1V0 = 0, 
+       SSD_FPGA_1V8, 
+       SSD_FPGA_VOLT_NR 
+};
+
+enum ssd_clock
+{
+       SSD_CLOCK_166M_LOST = 0, 
+       SSD_CLOCK_166M_SKEW, 
+       SSD_CLOCK_156M_LOST, 
+       SSD_CLOCK_156M_SKEW, 
+       SSD_CLOCK_NR
+};
+
+/* sensor */
+#define SSD_SENSOR_LM75_SADDRESS       (0x49 << 1)
+#define SSD_SENSOR_LM80_SADDRESS       (0x28 << 1)
+
+#define SSD_SENSOR_CONVERT_TEMP(val)   ((int)(val >> 8))
+
+#define SSD_INLET_OT_TEMP                      (55)    //55 DegC
+#define SSD_INLET_OT_HYST                      (50)    //50 DegC
+#define SSD_FLASH_OT_TEMP                      (70)    //70 DegC
+#define SSD_FLASH_OT_HYST                      (65)    //65 DegC
+
+enum ssd_sensor
+{
+       SSD_SENSOR_LM80 = 0,
+       SSD_SENSOR_LM75,
+       SSD_SENSOR_NR
+};
+
+
+/* lm75 */
+enum ssd_lm75_reg
+{
+       SSD_LM75_REG_TEMP = 0, 
+       SSD_LM75_REG_CONF, 
+       SSD_LM75_REG_THYST, 
+       SSD_LM75_REG_TOS
+};
+
+/* lm96080 */
+#define SSD_LM80_REG_IN_MAX(nr)                (0x2a + (nr) * 2)
+#define SSD_LM80_REG_IN_MIN(nr)                (0x2b + (nr) * 2)
+#define SSD_LM80_REG_IN(nr)                    (0x20 + (nr))
+
+#define SSD_LM80_REG_FAN1                      0x28
+#define SSD_LM80_REG_FAN2                      0x29
+#define SSD_LM80_REG_FAN_MIN(nr)       (0x3b + (nr))
+
+#define SSD_LM80_REG_TEMP                      0x27
+#define SSD_LM80_REG_TEMP_HOT_MAX      0x38
+#define SSD_LM80_REG_TEMP_HOT_HYST     0x39
+#define SSD_LM80_REG_TEMP_OS_MAX       0x3a
+#define SSD_LM80_REG_TEMP_OS_HYST      0x3b
+
+#define SSD_LM80_REG_CONFIG                    0x00
+#define SSD_LM80_REG_ALARM1                    0x01
+#define SSD_LM80_REG_ALARM2                    0x02
+#define SSD_LM80_REG_MASK1                     0x03
+#define SSD_LM80_REG_MASK2                     0x04
+#define SSD_LM80_REG_FANDIV                    0x05
+#define SSD_LM80_REG_RES                       0x06
+
+#define SSD_LM80_CONVERT_VOLT(val)     ((val * 10) >> 8)
+
+#define SSD_LM80_3V3_VOLT(val)         ((val)*33/19)
+
+#define SSD_LM80_CONV_INTERVAL         (1000)
+
+enum ssd_lm80_in
+{
+       SSD_LM80_IN_CAP = 0, 
+       SSD_LM80_IN_1V2, 
+       SSD_LM80_IN_1V2a, 
+       SSD_LM80_IN_1V5, 
+       SSD_LM80_IN_1V8, 
+       SSD_LM80_IN_FPGA_3V3, 
+       SSD_LM80_IN_3V3, 
+       SSD_LM80_IN_NR 
+};
+
+struct ssd_lm80_limit
+{
+       uint8_t low;
+       uint8_t high;
+};
+
+/* +/- 5% except cap in*/
+static struct ssd_lm80_limit ssd_lm80_limit[SSD_LM80_IN_NR] = {
+       {171, 217}, /* CAP in: 1710 ~ 2170 */
+       {114, 126}, 
+       {114, 126}, 
+       {142, 158}, 
+       {171, 189}, 
+       {180, 200}, 
+       {180, 200}, 
+};
+
+/* temperature sensors */
+enum ssd_temp_sensor
+{
+       SSD_TEMP_INLET = 0, 
+       SSD_TEMP_FLASH, 
+       SSD_TEMP_CTRL, 
+       SSD_TEMP_NR 
+};
+
+
+#ifdef SSD_OT_PROTECT
+#define SSD_OT_DELAY           (60) //ms
+
+#define SSD_OT_TEMP                    (90) //90 DegC
+
+#define SSD_OT_TEMP_HYST       (85) //85 DegC
+#endif
+
+/* fpga temperature */
+//#define CONVERT_TEMP(val)    ((float)(val)*503.975f/4096.0f-273.15f)
+#define CONVERT_TEMP(val)      ((val)*504/4096-273)
+
+#define MAX_TEMP(val)          CONVERT_TEMP(((val & 0xffff) >> 4))
+#define MIN_TEMP(val)          CONVERT_TEMP((((val>>16) & 0xffff) >> 4))
+#define CUR_TEMP(val)          CONVERT_TEMP((((val>>32) & 0xffff) >> 4))
+
+
+/* CAP monitor */
+#define SSD_PL_CAP_U1                          SSD_LM80_REG_IN(SSD_LM80_IN_CAP)
+#define SSD_PL_CAP_U2                          SSD_LM80_REG_IN(SSD_LM80_IN_1V8)
+#define SSD_PL_CAP_LEARN(u1, u2, t)    ((t*(u1+u2))/(2*162*(u1-u2)))
+#define SSD_PL_CAP_LEARN_WAIT          (20)    //20ms
+#define SSD_PL_CAP_LEARN_MAX_WAIT      (1000/SSD_PL_CAP_LEARN_WAIT)    //1s
+
+#define SSD_PL_CAP_CHARGE_WAIT         (1000)
+#define SSD_PL_CAP_CHARGE_MAX_WAIT     ((120*1000)/SSD_PL_CAP_CHARGE_WAIT)     //120s
+
+#define SSD_PL_CAP_VOLT(val)           (val*7)
+
+#define SSD_PL_CAP_VOLT_FULL           (13700)
+#define SSD_PL_CAP_VOLT_READY          (12880)
+
+#define SSD_PL_CAP_THRESHOLD           (8900)
+#define SSD_PL_CAP_CP_THRESHOLD                (5800)
+#define SSD_PL_CAP_THRESHOLD_HYST      (100)
+
+enum ssd_pl_cap_status
+{
+       SSD_PL_CAP = 0, 
+       SSD_PL_CAP_NR
+};
+
+enum ssd_pl_cap_type
+{
+       SSD_PL_CAP_DEFAULT = 0, /* 4 cap */
+       SSD_PL_CAP_CP   /* 3 cap */
+};
+
+
+/* hwmon offset */
+#define SSD_HWMON_OFFS_TEMP                    (0)
+#define SSD_HWMON_OFFS_SENSOR          (SSD_HWMON_OFFS_TEMP + SSD_TEMP_NR)
+#define SSD_HWMON_OFFS_PL_CAP          (SSD_HWMON_OFFS_SENSOR + SSD_SENSOR_NR)
+#define SSD_HWMON_OFFS_LM80                    (SSD_HWMON_OFFS_PL_CAP + SSD_PL_CAP_NR)
+#define SSD_HWMON_OFFS_CLOCK           (SSD_HWMON_OFFS_LM80 + SSD_LM80_IN_NR)
+#define SSD_HWMON_OFFS_FPGA            (SSD_HWMON_OFFS_CLOCK + SSD_CLOCK_NR) 
+
+#define SSD_HWMON_TEMP(idx)            (SSD_HWMON_OFFS_TEMP + idx)
+#define SSD_HWMON_SENSOR(idx)          (SSD_HWMON_OFFS_SENSOR + idx)
+#define SSD_HWMON_PL_CAP(idx)          (SSD_HWMON_OFFS_PL_CAP + idx)
+#define SSD_HWMON_LM80(idx)                    (SSD_HWMON_OFFS_LM80 + idx)
+#define SSD_HWMON_CLOCK(idx)           (SSD_HWMON_OFFS_CLOCK + idx)
+#define SSD_HWMON_FPGA(ctrl, idx)      (SSD_HWMON_OFFS_FPGA + (ctrl * SSD_FPGA_VOLT_NR) + idx)
+
+
+
+/* fifo */
+typedef struct sfifo
+{
+       uint32_t in;
+       uint32_t out;
+       uint32_t size;
+       uint32_t esize;
+       uint32_t mask;
+       spinlock_t lock;
+       void *data;
+} sfifo_t;
+
+static int sfifo_alloc(struct sfifo *fifo, uint32_t size, uint32_t esize)
+{
+       uint32_t __size = 1;
+
+       if (!fifo || size > INT_MAX || esize == 0) {
+               return -EINVAL;
+       }
+
+       while (__size < size) __size <<= 1;
+
+       if (__size < 2) {
+               return -EINVAL;
+       }
+
+       fifo->data = vmalloc(esize * __size);
+       if (!fifo->data) {
+               return -ENOMEM;
+       }
+
+       fifo->in = 0;
+       fifo->out = 0;
+       fifo->mask = __size - 1;
+       fifo->size = __size;
+       fifo->esize = esize;
+       spin_lock_init(&fifo->lock);
+
+       return 0;
+}
+
+static void sfifo_free(struct sfifo *fifo)
+{
+       if (!fifo) {
+               return;
+       }
+
+       vfree(fifo->data);
+       fifo->data = NULL;
+       fifo->in = 0;
+       fifo->out = 0;
+       fifo->mask = 0;
+       fifo->size = 0;
+       fifo->esize = 0;
+}
+
+static int __sfifo_put(struct sfifo *fifo, void *val)
+{
+       if (((fifo->in + 1) & fifo->mask) == fifo->out) {
+               return -1;
+       }
+
+       memcpy((fifo->data + (fifo->in * fifo->esize)), val, fifo->esize);
+       fifo->in = (fifo->in + 1) & fifo->mask;
+
+       return 0;
+}
+
+static int sfifo_put(struct sfifo *fifo, void *val)
+{
+       int ret = 0;
+
+       if (!fifo || !val) {
+               return -EINVAL;
+       }
+       
+       if (!in_interrupt()) {
+               spin_lock_irq(&fifo->lock);
+               ret = __sfifo_put(fifo, val);
+               spin_unlock_irq(&fifo->lock);
+       } else {
+               spin_lock(&fifo->lock);
+               ret = __sfifo_put(fifo, val);
+               spin_unlock(&fifo->lock);
+       }
+
+       return ret;
+}
+
+static int __sfifo_get(struct sfifo *fifo, void *val)
+{
+       if (fifo->out == fifo->in) {
+               return -1;
+       }
+
+       memcpy(val, (fifo->data + (fifo->out * fifo->esize)), fifo->esize);
+       fifo->out = (fifo->out + 1) & fifo->mask;
+
+       return 0;
+}
+
+static int sfifo_get(struct sfifo *fifo, void *val)
+{
+       int ret = 0;
+
+       if (!fifo || !val) {
+               return -EINVAL;
+       }
+
+       if (!in_interrupt()) {
+               spin_lock_irq(&fifo->lock);
+               ret = __sfifo_get(fifo, val);
+               spin_unlock_irq(&fifo->lock);
+       } else {
+               spin_lock(&fifo->lock);
+               ret = __sfifo_get(fifo, val);
+               spin_unlock(&fifo->lock);
+       }
+
+       return ret;
+}
+
+/* bio list */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30))
+struct ssd_blist {
+       struct bio *prev;
+       struct bio *next;
+};
+
+static inline void ssd_blist_init(struct ssd_blist *ssd_bl)
+{
+       ssd_bl->prev = NULL;
+       ssd_bl->next = NULL;
+}
+
+static inline struct bio *ssd_blist_get(struct ssd_blist *ssd_bl)
+{
+       struct bio *bio = ssd_bl->prev;
+
+       ssd_bl->prev = NULL;
+       ssd_bl->next = NULL;
+
+       return bio;
+}
+
+static inline void ssd_blist_add(struct ssd_blist *ssd_bl, struct bio *bio)
+{
+       bio->bi_next = NULL;
+
+       if (ssd_bl->next) {
+               ssd_bl->next->bi_next = bio;
+       } else {
+               ssd_bl->prev = bio;
+       }
+
+       ssd_bl->next = bio;
+}
+
+#else
+#define ssd_blist              bio_list
+#define ssd_blist_init bio_list_init
+#define ssd_blist_get  bio_list_get
+#define ssd_blist_add  bio_list_add
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
+#define bio_start(bio) (bio->bi_sector)
+#else
+#define bio_start(bio) (bio->bi_iter.bi_sector)
+#endif
+
+/* mutex */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16))
+#define mutex_lock down
+#define mutex_unlock up
+#define mutex semaphore
+#define mutex_init init_MUTEX
+#endif
+
+/* i2c */
+typedef union ssd_i2c_ctrl {
+       uint32_t val;
+       struct {
+               uint8_t wdata;
+               uint8_t addr;
+               uint16_t rw:1;
+               uint16_t pad:15;
+       } bits;
+}__attribute__((packed)) ssd_i2c_ctrl_t;
+
+typedef union ssd_i2c_data {
+       uint32_t val;
+       struct {
+               uint32_t rdata:8;
+               uint32_t valid:1;
+               uint32_t pad:23;
+       } bits;
+}__attribute__((packed)) ssd_i2c_data_t;
+
+/* write mode */
+enum ssd_write_mode
+{
+       SSD_WMODE_BUFFER = 0,
+       SSD_WMODE_BUFFER_EX,
+       SSD_WMODE_FUA,
+       /* dummy */
+       SSD_WMODE_AUTO, 
+       SSD_WMODE_DEFAULT
+};
+
+/* reset type */
+enum ssd_reset_type
+{
+       SSD_RST_NOINIT = 0,
+       SSD_RST_NORMAL,
+       SSD_RST_FULL
+};
+
+/* ssd msg */
+typedef struct ssd_sg_entry
+{
+       uint64_t block:48;
+       uint64_t length:16;
+       uint64_t buf;
+}__attribute__((packed))ssd_sg_entry_t;
+
+typedef struct ssd_rw_msg
+{
+       uint8_t tag;
+       uint8_t flag;
+       uint8_t nsegs;
+       uint8_t fun;
+       uint32_t reserved;      //for 64-bit align
+       struct ssd_sg_entry sge[1]; //base
+}__attribute__((packed))ssd_rw_msg_t;
+
+typedef struct ssd_resp_msg
+{
+       uint8_t tag;
+       uint8_t status:2;
+       uint8_t bitflip:6;
+       uint8_t log;
+       uint8_t fun;
+       uint32_t reserved;
+}__attribute__((packed))ssd_resp_msg_t;
+
+typedef struct ssd_flush_msg
+{
+       uint8_t tag;
+       uint8_t flag:2; //flash cache 0 or bbt 1
+       uint8_t flash:6;
+       uint8_t ctrl_idx;
+       uint8_t fun;
+       uint32_t reserved;      //align
+}__attribute__((packed))ssd_flush_msg_t;
+
+typedef struct ssd_nand_op_msg
+{
+       uint8_t tag;
+       uint8_t flag;
+       uint8_t ctrl_idx;
+       uint8_t fun;
+       uint32_t reserved;      //align
+       uint16_t page_count;
+       uint8_t chip_ce;
+       uint8_t chip_no;
+       uint32_t page_no;
+       uint64_t buf;
+}__attribute__((packed))ssd_nand_op_msg_t;
+
+typedef struct ssd_ram_op_msg
+{
+       uint8_t tag;
+       uint8_t flag;
+       uint8_t ctrl_idx;
+       uint8_t fun;
+       uint32_t reserved;      //align
+       uint32_t start;
+       uint32_t length;
+       uint64_t buf;
+}__attribute__((packed))ssd_ram_op_msg_t;
+
+
+/* log msg */
+typedef struct ssd_log_msg
+{
+       uint8_t tag;
+       uint8_t flag;
+       uint8_t ctrl_idx;
+       uint8_t fun;
+       uint32_t reserved;      //align
+       uint64_t buf;
+}__attribute__((packed))ssd_log_msg_t;
+
+typedef struct ssd_log_op_msg
+{
+       uint8_t tag;
+       uint8_t flag;
+       uint8_t ctrl_idx;
+       uint8_t fun;
+       uint32_t reserved;      //align
+       uint64_t reserved1;     //align
+       uint64_t buf;
+}__attribute__((packed))ssd_log_op_msg_t;
+
+typedef struct ssd_log_resp_msg
+{
+       uint8_t tag;
+       uint16_t status :2;
+       uint16_t reserved1 :2;  //align with the normal resp msg
+       uint16_t nr_log :12;
+       uint8_t fun;
+       uint32_t reserved;
+}__attribute__((packed))ssd_log_resp_msg_t;
+
+
+/* resp msg */
+typedef union ssd_response_msq
+{
+       ssd_resp_msg_t resp_msg;
+       ssd_log_resp_msg_t log_resp_msg;
+       uint64_t u64_msg;
+       uint32_t u32_msg[2];
+} ssd_response_msq_t;
+
+
+/* custom struct */
+typedef struct ssd_protocol_info
+{
+       uint32_t ver;
+       uint32_t init_state_reg;
+       uint32_t init_state_reg_sz;
+       uint32_t chip_info_reg;
+       uint32_t chip_info_reg_sz;
+} ssd_protocol_info_t;
+
+typedef struct ssd_hw_info
+{
+       uint32_t bridge_ver;
+       uint32_t ctrl_ver;
+
+       uint32_t cmd_fifo_sz;
+       uint32_t cmd_fifo_sz_mask;
+       uint32_t cmd_max_sg;
+       uint32_t sg_max_sec;
+       uint32_t resp_ptr_sz;
+       uint32_t resp_msg_sz;
+
+       uint16_t nr_ctrl;
+
+       uint16_t nr_data_ch;
+       uint16_t nr_ch;
+       uint16_t max_ch;
+       uint16_t nr_chip;
+
+       uint8_t  pcb_ver;
+       uint8_t  upper_pcb_ver;
+
+       uint8_t  nand_vendor_id;
+       uint8_t  nand_dev_id;
+
+       uint8_t  max_ce;
+       uint8_t  id_size;
+       uint16_t oob_size;
+
+       uint16_t bbf_pages;
+       uint16_t bbf_seek;      //
+
+       uint16_t page_count;    //per block
+       uint32_t page_size;
+       uint32_t block_count;   //per flash
+
+       uint64_t ram_size;
+       uint32_t ram_align;
+       uint32_t ram_max_len;
+
+       uint64_t bbt_base;
+       uint32_t bbt_size;
+       uint64_t md_base; //metadata
+       uint32_t md_size;
+       uint32_t md_entry_sz;
+
+       uint32_t log_sz;
+
+       uint64_t nand_wbuff_base;
+
+       uint32_t md_reserved_blks;
+       uint32_t reserved_blks;
+       uint32_t valid_pages;
+       uint32_t max_valid_pages;
+       uint64_t size;
+} ssd_hw_info_t;
+
+typedef struct ssd_hw_info_extend
+{
+       uint8_t board_type;
+       uint8_t cap_type;
+       uint8_t plp_type;
+       uint8_t work_mode;
+       uint8_t form_factor;
+
+       uint8_t pad[59];
+}ssd_hw_info_extend_t;
+
+typedef struct ssd_rom_info
+{
+       uint32_t size;
+       uint32_t block_size;
+       uint16_t page_size;
+       uint8_t  nr_bridge_fw;
+       uint8_t  nr_ctrl_fw;
+       uint8_t  nr_bm_fw;
+       uint8_t  nr_smart;
+       uint32_t bridge_fw_base;
+       uint32_t bridge_fw_sz;
+       uint32_t ctrl_fw_base;
+       uint32_t ctrl_fw_sz;
+       uint32_t bm_fw_base;
+       uint32_t bm_fw_sz;
+       uint32_t log_base;
+       uint32_t log_sz;
+       uint32_t smart_base;
+       uint32_t smart_sz;
+       uint32_t vp_base;
+       uint32_t label_base;
+} ssd_rom_info_t;
+
+/* debug info */
+enum ssd_debug_type
+{
+       SSD_DEBUG_NONE = 0, 
+       SSD_DEBUG_READ_ERR, 
+       SSD_DEBUG_WRITE_ERR, 
+       SSD_DEBUG_RW_ERR, 
+       SSD_DEBUG_READ_TO, 
+       SSD_DEBUG_WRITE_TO, 
+       SSD_DEBUG_RW_TO, 
+       SSD_DEBUG_LOG, 
+       SSD_DEBUG_OFFLINE, 
+       SSD_DEBUG_NR
+};
+
+typedef struct ssd_debug_info
+{
+       int type;
+       union {
+               struct {
+                       uint64_t off;
+                       uint32_t len;
+               } loc;
+               struct {
+                       int event;
+                       uint32_t extra;
+               } log;
+       } data;
+}ssd_debug_info_t;
+
+/* label */
+#define SSD_LABEL_FIELD_SZ     32
+#define SSD_SN_SZ                      16
+
+typedef struct ssd_label
+{
+       char date[SSD_LABEL_FIELD_SZ];
+       char sn[SSD_LABEL_FIELD_SZ];
+       char part[SSD_LABEL_FIELD_SZ];
+       char desc[SSD_LABEL_FIELD_SZ];
+       char other[SSD_LABEL_FIELD_SZ];
+       char maf[SSD_LABEL_FIELD_SZ];
+} ssd_label_t;
+
+#define SSD_LABEL_DESC_SZ      256
+
+typedef struct ssd_labelv3
+{
+       char boardtype[SSD_LABEL_FIELD_SZ];
+       char barcode[SSD_LABEL_FIELD_SZ];
+       char item[SSD_LABEL_FIELD_SZ];
+       char description[SSD_LABEL_DESC_SZ];
+       char manufactured[SSD_LABEL_FIELD_SZ];
+       char vendorname[SSD_LABEL_FIELD_SZ];
+       char issuenumber[SSD_LABEL_FIELD_SZ];
+       char cleicode[SSD_LABEL_FIELD_SZ];
+       char bom[SSD_LABEL_FIELD_SZ];
+} ssd_labelv3_t;
+
+/* battery */
+typedef struct ssd_battery_info
+{
+       uint32_t fw_ver;
+} ssd_battery_info_t;
+
+/* ssd power stat */
+typedef struct ssd_power_stat
+{
+       uint64_t nr_poweron;
+       uint64_t nr_powerloss;
+       uint64_t init_failed;
+} ssd_power_stat_t;
+
+/* io stat */
+typedef struct ssd_io_stat
+{
+       uint64_t run_time;
+       uint64_t nr_to;
+       uint64_t nr_ioerr;
+       uint64_t nr_rwerr;
+       uint64_t nr_read;
+       uint64_t nr_write;
+       uint64_t rsectors;
+       uint64_t wsectors;
+} ssd_io_stat_t;
+
+/* ecc */
+typedef struct ssd_ecc_info
+{
+       uint64_t bitflip[SSD_ECC_MAX_FLIP];
+} ssd_ecc_info_t;
+
+/* log */
+enum ssd_log_level
+{
+       SSD_LOG_LEVEL_INFO = 0,
+       SSD_LOG_LEVEL_NOTICE,
+       SSD_LOG_LEVEL_WARNING,
+       SSD_LOG_LEVEL_ERR,
+       SSD_LOG_NR_LEVEL
+};
+
+typedef struct ssd_log_info
+{
+       uint64_t nr_log;
+       uint64_t stat[SSD_LOG_NR_LEVEL];
+} ssd_log_info_t;
+
+/* S.M.A.R.T. */
+#define SSD_SMART_MAGIC        (0x5452414D53445353ull)
+
+typedef struct ssd_smart
+{
+       struct ssd_power_stat pstat;
+       struct ssd_io_stat io_stat;
+       struct ssd_ecc_info ecc_info;
+       struct ssd_log_info log_info;
+       uint64_t version;
+       uint64_t magic;
+} ssd_smart_t;
+
+/* internal log */
+typedef struct ssd_internal_log
+{
+       uint32_t nr_log;
+       void *log;
+} ssd_internal_log_t;
+
+/* ssd cmd */
+typedef struct ssd_cmd
+{
+       struct bio *bio;
+       struct scatterlist *sgl;
+       struct list_head list;
+       void *dev;
+       int nsegs;
+       int flag;               /*pbio(1) or bio(0)*/
+
+       int tag;
+       void *msg;
+       dma_addr_t msg_dma;
+
+       unsigned long start_time;
+
+       int errors;
+       unsigned int nr_log;
+
+       struct timer_list cmd_timer;
+       struct completion *waiting;
+} ssd_cmd_t;
+
+typedef void (*send_cmd_func)(struct ssd_cmd *);
+typedef int (*ssd_event_call)(struct gendisk *, int, int);     /* gendisk, event id, event level */
+
+/* dcmd sz */
+#define SSD_DCMD_MAX_SZ 32
+
+typedef struct ssd_dcmd
+{
+       struct list_head list;
+       void *dev;
+       uint8_t msg[SSD_DCMD_MAX_SZ];
+} ssd_dcmd_t;
+
+
+enum ssd_state {
+       SSD_INIT_WORKQ, 
+       SSD_INIT_BD, 
+       SSD_ONLINE, 
+       /* full reset */
+       SSD_RESETING, 
+       /* hw log */
+       SSD_LOG_HW, 
+       /* log err */
+       SSD_LOG_ERR
+};
+
+#define SSD_QUEUE_NAME_LEN     16
+typedef struct ssd_queue {
+       char name[SSD_QUEUE_NAME_LEN];
+       void *dev;
+
+       int idx;
+
+       uint32_t resp_idx;
+       uint32_t resp_idx_mask;
+       uint32_t resp_msg_sz;
+
+       void *resp_msg;
+       void *resp_ptr;
+
+       struct ssd_cmd *cmd;
+
+       struct ssd_io_stat io_stat;
+       struct ssd_ecc_info ecc_info;
+} ssd_queue_t;
+
+typedef struct ssd_device {
+       char name[SSD_DEV_NAME_LEN];
+
+       int idx;
+       int major;
+       int readonly;
+
+       int int_mode;
+#ifdef SSD_ESCAPE_IRQ
+       int irq_cpu;
+#endif
+
+       int reload_fw;
+
+       int ot_delay; //in ms
+
+       atomic_t refcnt;
+       atomic_t tocnt;
+       atomic_t in_flight[2]; //r&w
+
+       uint64_t uptime;
+
+       struct list_head list;
+       struct pci_dev *pdev;
+
+       unsigned long mmio_base;
+       unsigned long mmio_len;
+       void __iomem *ctrlp;
+
+       struct mutex spi_mutex;
+       struct mutex i2c_mutex;
+
+       struct ssd_protocol_info protocol_info;
+       struct ssd_hw_info hw_info;
+       struct ssd_rom_info rom_info;
+       struct ssd_label label;
+
+       struct ssd_smart smart;
+
+       atomic_t in_sendq;
+       spinlock_t sendq_lock;
+       struct ssd_blist sendq;
+       struct task_struct *send_thread;
+       wait_queue_head_t send_waitq;
+
+       atomic_t in_doneq;
+       spinlock_t doneq_lock;
+       struct ssd_blist doneq;
+       struct task_struct *done_thread;
+       wait_queue_head_t done_waitq;
+
+       struct ssd_dcmd *dcmd;
+       spinlock_t dcmd_lock;
+       struct list_head dcmd_list; /* direct cmd list */
+       wait_queue_head_t dcmd_wq;
+
+       unsigned long *tag_map;
+       wait_queue_head_t tag_wq;
+
+       spinlock_t cmd_lock;
+       struct ssd_cmd *cmd;
+       send_cmd_func scmd;
+
+       ssd_event_call event_call;
+       void *msg_base;
+       dma_addr_t msg_base_dma;
+
+       uint32_t resp_idx;
+       void *resp_msg_base;
+       void *resp_ptr_base;
+       dma_addr_t resp_msg_base_dma;
+       dma_addr_t resp_ptr_base_dma;
+
+       int nr_queue;
+       struct msix_entry entry[SSD_MSIX_VEC];
+       struct ssd_queue queue[SSD_MSIX_VEC];
+
+       struct request_queue *rq; /* The device request queue */
+       struct gendisk *gd; /* The gendisk structure */
+
+       struct mutex internal_log_mutex;
+       struct ssd_internal_log internal_log;
+       struct workqueue_struct *workq;
+       struct work_struct log_work; /* get log */
+       void *log_buf;
+
+       unsigned long state; /* device state, for example, block device inited */
+
+       struct module *owner;
+
+       /* extend */
+
+       int slave;
+       int cmajor;
+       int save_md;
+       int ot_protect;
+
+       struct kref kref;
+
+       struct mutex gd_mutex;
+       struct ssd_log_info log_info; /* volatile */
+
+       atomic_t queue_depth;
+       struct mutex barrier_mutex;
+       struct mutex fw_mutex;
+
+       struct ssd_hw_info_extend hw_info_ext;
+       struct ssd_labelv3 labelv3;
+
+       int wmode;
+       int user_wmode;
+       struct mutex bm_mutex;
+       struct work_struct bm_work; /* check bm */
+       struct timer_list bm_timer;
+       struct sfifo log_fifo;
+
+       struct timer_list routine_timer;
+       unsigned long routine_tick;
+       unsigned long hwmon;
+
+       struct work_struct hwmon_work; /* check hw */
+       struct work_struct capmon_work; /* check battery */
+       struct work_struct tempmon_work; /* check temp */
+
+       /* debug info */
+       struct ssd_debug_info db_info;
+} ssd_device_t;
+
+
+/* Ioctl struct */
+typedef struct ssd_acc_info {
+       uint32_t threshold_l1;
+       uint32_t threshold_l2;
+       uint32_t val;
+} ssd_acc_info_t;
+
+typedef struct ssd_reg_op_info
+{
+       uint32_t offset;
+       uint32_t value;
+} ssd_reg_op_info_t;
+
+typedef struct ssd_spi_op_info
+{
+       void __user *buf;
+       uint32_t off;
+       uint32_t len;
+} ssd_spi_op_info_t;
+
+typedef struct ssd_i2c_op_info
+{
+       uint8_t saddr;
+       uint8_t wsize;
+       uint8_t rsize;
+       void __user *wbuf;
+       void __user *rbuf;
+} ssd_i2c_op_info_t;
+
+typedef struct ssd_smbus_op_info
+{
+       uint8_t saddr;
+       uint8_t cmd;
+       uint8_t size;
+       void __user *buf;
+} ssd_smbus_op_info_t;
+
+typedef struct ssd_ram_op_info {
+       uint8_t ctrl_idx;
+       uint32_t length;
+       uint64_t start;
+       uint8_t __user *buf;
+} ssd_ram_op_info_t;
+
+typedef struct ssd_flash_op_info {
+       uint32_t page;
+       uint16_t flash;
+       uint8_t chip;
+       uint8_t ctrl_idx;
+       uint8_t __user *buf;
+} ssd_flash_op_info_t;
+
+typedef struct ssd_sw_log_info {
+       uint16_t event;
+       uint16_t pad;
+       uint32_t data;
+} ssd_sw_log_info_t;
+
+typedef struct ssd_version_info
+{
+       uint32_t bridge_ver;    /* bridge fw version */
+       uint32_t ctrl_ver;              /* controller fw version */
+       uint32_t bm_ver;                /* battery manager fw version */
+       uint8_t  pcb_ver;               /* main pcb version */
+       uint8_t  upper_pcb_ver;
+       uint8_t  pad0;
+       uint8_t  pad1;
+} ssd_version_info_t;
+
+typedef struct pci_addr
+{
+       uint16_t domain;
+       uint8_t bus;
+       uint8_t slot;
+       uint8_t func;
+} pci_addr_t;
+
+typedef struct ssd_drv_param_info {
+       int mode;
+       int status_mask;
+       int int_mode;
+       int threaded_irq;
+       int log_level;
+       int wmode;
+       int ot_protect;
+       int finject;
+       int pad[8];
+} ssd_drv_param_info_t;
+
+
+/* form factor */
+enum ssd_form_factor
+{
+       SSD_FORM_FACTOR_HHHL = 0, 
+       SSD_FORM_FACTOR_FHHL
+};
+
+
+/* ssd power loss protect */
+enum ssd_plp_type
+{
+       SSD_PLP_SCAP = 0,
+       SSD_PLP_CAP,
+       SSD_PLP_NONE
+};
+
+/* ssd bm */
+#define SSD_BM_SLAVE_ADDRESS   0x16
+#define SSD_BM_CAP     5
+
+/* SBS cmd */
+#define SSD_BM_SAFETYSTATUS                    0x51
+#define SSD_BM_OPERATIONSTATUS         0x54
+
+/* ManufacturerAccess */
+#define SSD_BM_MANUFACTURERACCESS      0x00            
+#define SSD_BM_ENTER_CAP_LEARNING      0x0023          /* cap learning */
+
+/* Data flash access */
+#define SSD_BM_DATA_FLASH_SUBCLASS_ID 0x77
+#define SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1 0x78
+#define SSD_BM_SYSTEM_DATA_SUBCLASS_ID 56
+#define SSD_BM_CONFIGURATION_REGISTERS_ID      64
+
+/* min cap voltage */
+#define SSD_BM_CAP_VOLT_MIN                    500
+
+/*
+enum ssd_bm_cap
+{
+       SSD_BM_CAP_VINA = 1, 
+       SSD_BM_CAP_JH = 3
+};*/
+
+enum ssd_bmstatus
+{
+       SSD_BMSTATUS_OK = 0,
+       SSD_BMSTATUS_CHARGING,          /* not fully charged */
+       SSD_BMSTATUS_WARNING
+};
+
+enum sbs_unit {
+       SBS_UNIT_VALUE = 0,
+       SBS_UNIT_TEMPERATURE,
+       SBS_UNIT_VOLTAGE,
+       SBS_UNIT_CURRENT,
+       SBS_UNIT_ESR,
+       SBS_UNIT_PERCENT,
+       SBS_UNIT_CAPACITANCE
+};
+
+enum sbs_size {
+       SBS_SIZE_BYTE = 1,
+       SBS_SIZE_WORD,
+       SBS_SIZE_BLK,
+};
+
+struct sbs_cmd {
+       uint8_t cmd;
+       uint8_t size;
+       uint8_t unit;
+       uint8_t off;
+       uint16_t mask;
+       char *desc;
+};
+
+struct ssd_bm {
+       uint16_t temp;
+       uint16_t volt;
+       uint16_t curr;
+       uint16_t esr;
+       uint16_t rsoc;
+       uint16_t health;
+       uint16_t cap;
+       uint16_t chg_curr;
+       uint16_t chg_volt;
+       uint16_t cap_volt[SSD_BM_CAP];
+       uint16_t sf_alert;
+       uint16_t sf_status;
+       uint16_t op_status;
+       uint16_t sys_volt;
+};
+
+struct ssd_bm_manufacturer_data
+{
+       uint16_t pack_lot_code;
+       uint16_t pcb_lot_code;
+       uint16_t firmware_ver;
+       uint16_t hardware_ver;
+};
+
+struct ssd_bm_configuration_registers
+{
+       struct {
+               uint16_t cc:3;
+               uint16_t rsvd:5;
+               uint16_t stack:1;
+               uint16_t rsvd1:2;
+               uint16_t temp:2;
+               uint16_t rsvd2:1;
+               uint16_t lt_en:1;
+               uint16_t rsvd3:1;
+       } operation_cfg;
+       uint16_t pad;
+       uint16_t fet_action;
+       uint16_t pad1;
+       uint16_t fault;
+};
+
+#define SBS_VALUE_MASK 0xffff
+
+#define bm_var_offset(var)     ((size_t) &((struct ssd_bm *)0)->var)
+#define bm_var(start, offset)  ((void *) start + (offset))
+
+static struct sbs_cmd ssd_bm_sbs[] = {
+       {0x08, SBS_SIZE_WORD, SBS_UNIT_TEMPERATURE, bm_var_offset(temp), SBS_VALUE_MASK, "Temperature"}, 
+       {0x09, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(volt), SBS_VALUE_MASK, "Voltage"}, 
+       {0x0a, SBS_SIZE_WORD, SBS_UNIT_CURRENT, bm_var_offset(curr), SBS_VALUE_MASK, "Current"}, 
+       {0x0b, SBS_SIZE_WORD, SBS_UNIT_ESR, bm_var_offset(esr), SBS_VALUE_MASK, "ESR"}, 
+       {0x0d, SBS_SIZE_BYTE, SBS_UNIT_PERCENT, bm_var_offset(rsoc), SBS_VALUE_MASK, "RelativeStateOfCharge"}, 
+       {0x0e, SBS_SIZE_BYTE, SBS_UNIT_PERCENT, bm_var_offset(health), SBS_VALUE_MASK, "Health"}, 
+       {0x10, SBS_SIZE_WORD, SBS_UNIT_CAPACITANCE, bm_var_offset(cap), SBS_VALUE_MASK, "Capacitance"}, 
+       {0x14, SBS_SIZE_WORD, SBS_UNIT_CURRENT, bm_var_offset(chg_curr), SBS_VALUE_MASK, "ChargingCurrent"}, 
+       {0x15, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(chg_volt), SBS_VALUE_MASK, "ChargingVoltage"}, 
+       {0x3b, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[4]), SBS_VALUE_MASK, "CapacitorVoltage5"}, 
+       {0x3c, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[3]), SBS_VALUE_MASK, "CapacitorVoltage4"}, 
+       {0x3d, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[2]), SBS_VALUE_MASK, "CapacitorVoltage3"}, 
+       {0x3e, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[1]), SBS_VALUE_MASK, "CapacitorVoltage2"}, 
+       {0x3f, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[0]), SBS_VALUE_MASK, "CapacitorVoltage1"}, 
+       {0x50, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(sf_alert), 0x870F, "SafetyAlert"}, 
+       {0x51, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(sf_status), 0xE7BF, "SafetyStatus"}, 
+       {0x54, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(op_status), 0x79F4, "OperationStatus"}, 
+       {0x5a, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(sys_volt), SBS_VALUE_MASK, "SystemVoltage"}, 
+       {0, 0, 0, 0, 0, NULL}, 
+};
+
+/* ssd ioctl  */
+#define SSD_CMD_GET_PROTOCOL_INFO      _IOR('H', 100, struct ssd_protocol_info)
+#define SSD_CMD_GET_HW_INFO                    _IOR('H', 101, struct ssd_hw_info)
+#define SSD_CMD_GET_ROM_INFO           _IOR('H', 102, struct ssd_rom_info)
+#define SSD_CMD_GET_SMART                      _IOR('H', 103, struct ssd_smart)
+#define SSD_CMD_GET_IDX                                _IOR('H', 105, int)
+#define SSD_CMD_GET_AMOUNT                     _IOR('H', 106, int)
+#define SSD_CMD_GET_TO_INFO                    _IOR('H', 107, int)
+#define SSD_CMD_GET_DRV_VER                    _IOR('H', 108, char[DRIVER_VERSION_LEN])
+
+#define SSD_CMD_GET_BBACC_INFO         _IOR('H', 109, struct ssd_acc_info)
+#define SSD_CMD_GET_ECACC_INFO         _IOR('H', 110, struct ssd_acc_info)
+
+#define SSD_CMD_GET_HW_INFO_EXT                _IOR('H', 111, struct ssd_hw_info_extend)
+
+#define SSD_CMD_REG_READ                       _IOWR('H', 120, struct ssd_reg_op_info)
+#define SSD_CMD_REG_WRITE                      _IOWR('H', 121, struct ssd_reg_op_info)
+
+#define SSD_CMD_SPI_READ                       _IOWR('H', 125, struct ssd_spi_op_info)
+#define SSD_CMD_SPI_WRITE                      _IOWR('H', 126, struct ssd_spi_op_info)
+#define SSD_CMD_SPI_ERASE                      _IOWR('H', 127, struct ssd_spi_op_info)
+
+#define SSD_CMD_I2C_READ                       _IOWR('H', 128, struct ssd_i2c_op_info)
+#define SSD_CMD_I2C_WRITE                      _IOWR('H', 129, struct ssd_i2c_op_info)
+#define SSD_CMD_I2C_WRITE_READ         _IOWR('H', 130, struct ssd_i2c_op_info)
+
+#define SSD_CMD_SMBUS_SEND_BYTE                _IOWR('H', 131, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_RECEIVE_BYTE     _IOWR('H', 132, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_WRITE_BYTE       _IOWR('H', 133, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_READ_BYTE                _IOWR('H', 135, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_WRITE_WORD       _IOWR('H', 136, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_READ_WORD                _IOWR('H', 137, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_WRITE_BLOCK      _IOWR('H', 138, struct ssd_smbus_op_info)
+#define SSD_CMD_SMBUS_READ_BLOCK       _IOWR('H', 139, struct ssd_smbus_op_info)
+
+#define SSD_CMD_BM_GET_VER                     _IOR('H', 140, uint16_t)
+#define SSD_CMD_BM_GET_NR_CAP          _IOR('H', 141, int)
+#define SSD_CMD_BM_CAP_LEARNING                _IOW('H', 142, int)
+#define SSD_CMD_CAP_LEARN                      _IOR('H', 143, uint32_t)
+#define SSD_CMD_GET_CAP_STATUS         _IOR('H', 144, int)
+
+#define SSD_CMD_RAM_READ                       _IOWR('H', 150, struct ssd_ram_op_info)
+#define SSD_CMD_RAM_WRITE                      _IOWR('H', 151, struct ssd_ram_op_info)
+
+#define SSD_CMD_NAND_READ_ID           _IOR('H', 160, struct ssd_flash_op_info)
+#define SSD_CMD_NAND_READ                      _IOWR('H', 161, struct ssd_flash_op_info) //with oob
+#define SSD_CMD_NAND_WRITE                     _IOWR('H', 162, struct ssd_flash_op_info)
+#define SSD_CMD_NAND_ERASE                     _IOWR('H', 163, struct ssd_flash_op_info)
+#define SSD_CMD_NAND_READ_EXT          _IOWR('H', 164, struct ssd_flash_op_info) //ingore EIO
+
+#define SSD_CMD_UPDATE_BBT                     _IOW('H', 180, struct ssd_flash_op_info)
+
+#define SSD_CMD_CLEAR_ALARM                    _IOW('H', 190, int)
+#define SSD_CMD_SET_ALARM                      _IOW('H', 191, int)
+
+#define SSD_CMD_RESET                          _IOW('H', 200, int)
+#define SSD_CMD_RELOAD_FW                      _IOW('H', 201, int)
+#define SSD_CMD_UNLOAD_DEV                     _IOW('H', 202, int)
+#define SSD_CMD_LOAD_DEV                       _IOW('H', 203, int)
+#define SSD_CMD_UPDATE_VP                      _IOWR('H', 205, uint32_t)
+#define SSD_CMD_FULL_RESET                     _IOW('H', 206, int)
+
+#define SSD_CMD_GET_NR_LOG                     _IOR('H', 220, uint32_t)
+#define SSD_CMD_GET_LOG                                _IOR('H', 221, void *)
+#define SSD_CMD_LOG_LEVEL                      _IOW('H', 222, int)
+
+#define SSD_CMD_OT_PROTECT                     _IOW('H', 223, int)
+#define SSD_CMD_GET_OT_STATUS          _IOR('H', 224, int)
+
+#define SSD_CMD_CLEAR_LOG                      _IOW('H', 230, int)
+#define SSD_CMD_CLEAR_SMART                    _IOW('H', 231, int)
+
+#define SSD_CMD_SW_LOG                         _IOW('H', 232, struct ssd_sw_log_info)
+
+#define SSD_CMD_GET_LABEL                      _IOR('H', 235, struct ssd_label)
+#define SSD_CMD_GET_VERSION                    _IOR('H', 236, struct ssd_version_info)
+#define SSD_CMD_GET_TEMPERATURE                _IOR('H', 237, int)
+#define SSD_CMD_GET_BMSTATUS           _IOR('H', 238, int)
+#define SSD_CMD_GET_LABEL2                     _IOR('H', 239, void *)
+
+
+#define SSD_CMD_FLUSH                          _IOW('H', 240, int)
+#define SSD_CMD_SAVE_MD                                _IOW('H', 241, int)
+
+#define SSD_CMD_SET_WMODE                      _IOW('H', 242, int)
+#define SSD_CMD_GET_WMODE                      _IOR('H', 243, int)
+#define SSD_CMD_GET_USER_WMODE         _IOR('H', 244, int)
+
+#define SSD_CMD_DEBUG                          _IOW('H', 250, struct ssd_debug_info)
+#define SSD_CMD_DRV_PARAM_INFO         _IOR('H', 251, struct ssd_drv_param_info)
+
+
+/* log */
+#define SSD_LOG_MAX_SZ                         4096
+#define SSD_LOG_LEVEL                          SSD_LOG_LEVEL_NOTICE
+
+enum ssd_log_data
+{
+       SSD_LOG_DATA_NONE = 0,
+       SSD_LOG_DATA_LOC, 
+       SSD_LOG_DATA_HEX
+};
+
+typedef struct ssd_log_entry
+{
+       union {
+               struct {
+                       uint32_t page:10;
+                       uint32_t block:14;
+                       uint32_t flash:8;
+               } loc;
+               struct {
+                       uint32_t page:12;
+                       uint32_t block:12;
+                       uint32_t flash:8;
+               } loc1;
+               uint32_t val;
+       } data;
+       uint16_t event:10;
+       uint16_t mod:6;
+       uint16_t idx;
+}__attribute__((packed))ssd_log_entry_t;
+
+typedef struct ssd_log
+{
+       uint64_t time:56;
+       uint64_t ctrl_idx:8;
+       ssd_log_entry_t le;
+} __attribute__((packed)) ssd_log_t;
+
+typedef struct ssd_log_desc
+{
+       uint16_t event;
+       uint8_t level;
+       uint8_t data;
+       uint8_t sblock;
+       uint8_t spage;
+       char *desc;
+} __attribute__((packed)) ssd_log_desc_t;
+
+#define SSD_LOG_SW_IDX         0xF
+#define SSD_UNKNOWN_EVENT      ((uint16_t)-1)
+static struct ssd_log_desc ssd_log_desc[] = {
+       /* event, level, show flash, show block, show page, desc */
+       {0x0,  SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC,  0, 0, "Create BBT failure"}, //g3
+       {0x1,  SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC,  0, 0, "Read BBT failure"}, //g3
+       {0x2,  SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Mark bad block"}, 
+       {0x3,  SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Flush BBT failure"}, 
+       {0x4,  SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0x7,  SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "No available blocks"}, 
+       {0x8,  SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Bad EC header"}, 
+       {0x9,  SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC,  1, 0, "Bad VID header"}, //g3
+       {0xa,  SSD_LOG_LEVEL_INFO,    SSD_LOG_DATA_LOC,  1, 0, "Wear leveling"}, 
+       {0xb,  SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "WL read back failure"}, 
+       {0x11, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Data recovery failure"}, // err
+       {0x20, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Init: scan mapping table failure"}, // err g3
+       {0x21, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0x22, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0x23, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0x24, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Merge: read mapping page failure"}, 
+       {0x25, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Merge: read back failure"}, 
+       {0x26, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0x27, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC,  1, 1, "Data corrupted for abnormal power down"}, //g3
+       {0x28, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Merge: mapping page corrupted"}, 
+       {0x29, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Init: no mapping page"}, 
+       {0x2a, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: mapping pages incomplete"}, 
+       {0x2b, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Read back failure after programming failure"}, // err
+       {0xf1, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Read failure without recovery"}, // err
+       {0xf2, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  0, 0, "No available blocks"}, // maybe err g3
+       {0xf3, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 0, "Init: RAID incomplete"}, // err g3
+       {0xf4, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0xf5, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read failure in moving data"}, 
+       {0xf6, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Program failure"}, 
+       {0xf7, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC,  1, 1, "Init: RAID not complete"}, 
+       {0xf8, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Init: data moving interrupted"}, 
+       {0xfe, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Data inspection failure"}, 
+       {0xff, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "IO: ECC failed"}, 
+
+       /* new */
+       {0x2e, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  0, 0, "No available reserved blocks" }, // err
+       {0x30, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Init: PMT membership not found"}, 
+       {0x31, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "Init: PMT corrupted"}, 
+       {0x32, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Init: PBT membership not found"}, 
+       {0x33, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Init: PBT not found"}, 
+       {0x34, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Init: PBT corrupted"}, 
+       {0x35, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PMT page read failure"}, 
+       {0x36, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PBT page read failure"}, 
+       {0x37, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PBT backup page read failure"}, 
+       {0x38, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PBMT read failure"}, 
+       {0x39, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Init: PBMT scan failure"}, // err
+       {0x3a, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: first page read failure"}, 
+       {0x3b, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Init: first page scan failure"}, // err
+       {0x3c, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Init: scan unclosed block failure"}, // err
+       {0x3d, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: write pointer mismatch"}, 
+       {0x3e, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PMT recovery: PBMT read failure"}, 
+       {0x3f, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Init: PMT recovery: PBMT scan failure"}, 
+       {0x40, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "Init: PMT recovery: data page read failure"}, //err
+       {0x41, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PBT write pointer mismatch"}, 
+       {0x42, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: PBT latest version corrupted"}, 
+       {0x43, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 0, "Init: too many unclosed blocks"}, 
+       {0x44, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "Init: PDW block found"}, 
+       {0x45, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_HEX,  0, 0, "Init: more than one PDW block found"}, //err
+       {0x46, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Init: first page is blank or read failure"}, 
+       {0x47, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Init: PDW block not found"}, 
+
+       {0x50, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 0, "Cache: hit error data"}, // err
+       {0x51, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 0, "Cache: read back failure"}, // err
+       {0x52, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Cache: unknown command"}, //?
+       {0x53, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_LOC,  1, 1, "GC/WL read back failure"}, // err
+
+       {0x60, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "Erase failure"}, 
+
+       {0x70, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "LPA not matched"}, 
+       {0x71, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "PBN not matched"}, 
+       {0x72, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read retry failure"}, 
+       {0x73, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Need raid recovery"}, 
+       {0x74, SSD_LOG_LEVEL_INFO,    SSD_LOG_DATA_LOC,  1, 1, "Need read retry"}, 
+       {0x75, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read invalid data page"}, 
+       {0x76, SSD_LOG_LEVEL_INFO,    SSD_LOG_DATA_LOC,  1, 1, "ECC error, data in cache, PBN matched"}, 
+       {0x77, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "ECC error, data in cache, PBN not matched"}, 
+       {0x78, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "ECC error, data in flash, PBN not matched"}, 
+       {0x79, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "ECC ok, data in cache, LPA not matched"},
+       {0x7a, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "ECC ok, data in flash, LPA not matched"},
+       {0x7b, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID data in cache, LPA not matched"}, 
+       {0x7c, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID data in flash, LPA not matched"}, 
+       {0x7d, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read data page status error"}, 
+       {0x7e, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read blank page"}, 
+       {0x7f, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Access flash timeout"}, 
+
+       {0x80, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "EC overflow"}, 
+       {0x81, SSD_LOG_LEVEL_INFO,    SSD_LOG_DATA_NONE, 0, 0, "Scrubbing completed"}, 
+       {0x82, SSD_LOG_LEVEL_INFO,    SSD_LOG_DATA_LOC,  1, 0, "Unstable block(too much bit flip)"}, 
+       {0x83, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "GC: ram error"}, //?
+       {0x84, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "GC: one PBMT read failure"}, 
+
+       {0x88, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "GC: mark bad block"}, 
+       {0x89, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 0, "GC: invalid page count error"}, // maybe err
+       {0x8a, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Warning: Bad Block close to limit"}, 
+       {0x8b, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "Error: Bad Block over limit"}, 
+       {0x8c, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Warning: P/E cycles close to limit"}, 
+       {0x8d, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "Error: P/E cycles over limit"}, 
+
+       {0x90, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Over temperature"}, //xx
+       {0x91, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Temperature is OK"}, //xx
+       {0x92, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Battery fault"}, 
+       {0x93, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "SEU fault"}, //err
+       {0x94, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "DDR error"}, //err
+       {0x95, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "Controller serdes error"}, //err
+       {0x96, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "Bridge serdes 1 error"}, //err
+       {0x97, SSD_LOG_LEVEL_ERR,     SSD_LOG_DATA_NONE, 0, 0, "Bridge serdes 2 error"}, //err
+       {0x98, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "SEU fault (corrected)"}, //err
+       {0x99, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Battery is OK"}, 
+       {0x9a, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Temperature close to limit"}, //xx
+       
+       {0x9b, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "SEU fault address (low)"}, 
+       {0x9c, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "SEU fault address (high)"}, 
+       {0x9d, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "I2C fault" }, 
+       {0x9e, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "DDR single bit error" }, 
+       {0x9f, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Board voltage fault" },
+
+       {0xa0, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "LPA not matched"}, 
+       {0xa1, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Re-read data in cache"}, 
+       {0xa2, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read blank page"}, 
+       {0xa3, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: Read blank page"}, 
+       {0xa4, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: new data in cache"}, 
+       {0xa5, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: PBN not matched"}, 
+       {0xa6, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Read data with error flag"}, 
+       {0xa7, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: recoverd data with error flag"}, 
+       {0xa8, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Blank page in cache, PBN matched"}, 
+       {0xa9, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: Blank page in cache, PBN matched"}, 
+       {0xaa, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  0, 0, "Flash init failure"}, 
+       {0xab, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "Mapping table recovery failure"}, 
+       {0xac, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_LOC,  1, 1, "RAID recovery: ECC failed"}, 
+       {0xb0, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Temperature is up to degree 95"},
+       {0xb1, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_NONE, 0, 0, "Temperature is up to degree 100"},
+
+       {0x300, SSD_LOG_LEVEL_ERR,    SSD_LOG_DATA_HEX,  0, 0, "CMD timeout"}, 
+       {0x301, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Power on"}, 
+       {0x302, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Power off"}, 
+       {0x303, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear log"}, 
+       {0x304, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Set capacity"}, 
+       {0x305, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear data"}, 
+       {0x306, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "BM safety status"}, 
+       {0x307, SSD_LOG_LEVEL_ERR,    SSD_LOG_DATA_HEX,  0, 0, "I/O error"}, 
+       {0x308, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "CMD error"}, 
+       {0x309, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Set wmode"}, 
+       {0x30a, SSD_LOG_LEVEL_ERR,    SSD_LOG_DATA_HEX,  0, 0, "DDR init failed" }, 
+       {0x30b, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "PCIe link status" }, 
+       {0x30c, SSD_LOG_LEVEL_ERR,    SSD_LOG_DATA_HEX,  0, 0, "Controller reset sync error" }, 
+       {0x30d, SSD_LOG_LEVEL_ERR,    SSD_LOG_DATA_HEX,  0, 0, "Clock fault" }, 
+       {0x30e, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "FPGA voltage fault status" }, 
+       {0x30f, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Set capacity finished"}, 
+       {0x310, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear data finished"}, 
+       {0x311, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Reset"}, 
+       {0x312, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_HEX,  0, 0, "CAP: voltage fault"}, 
+       {0x313, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_NONE, 0, 0, "CAP: learn fault"}, 
+       {0x314, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "CAP status"}, 
+       {0x315, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX,  0, 0, "Board voltage fault status"}, 
+       {0x316, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Inlet over temperature"}, 
+       {0x317, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Inlet temperature is OK"}, 
+       {0x318, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Flash over temperature"}, 
+       {0x319, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Flash temperature is OK"}, 
+       {0x31a, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_NONE, 0, 0, "CAP: short circuit"}, 
+       {0x31b, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_HEX,  0, 0, "Sensor fault"}, 
+       {0x31c, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Erase all data"}, 
+       {0x31d, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Erase all data finished"},
+
+       {SSD_UNKNOWN_EVENT, SSD_LOG_LEVEL_NOTICE,  SSD_LOG_DATA_HEX,  0, 0, "unknown event"}, 
+};
+/* */
+#define SSD_LOG_OVER_TEMP              0x90
+#define SSD_LOG_NORMAL_TEMP            0x91
+#define SSD_LOG_WARN_TEMP              0x9a
+#define SSD_LOG_SEU_FAULT              0x93
+#define SSD_LOG_SEU_FAULT1             0x98
+#define SSD_LOG_BATTERY_FAULT  0x92
+#define SSD_LOG_BATTERY_OK             0x99
+#define SSD_LOG_BOARD_VOLT_FAULT       0x9f
+
+/* software log */
+#define SSD_LOG_TIMEOUT                        0x300
+#define SSD_LOG_POWER_ON               0x301
+#define SSD_LOG_POWER_OFF              0x302
+#define SSD_LOG_CLEAR_LOG              0x303
+#define SSD_LOG_SET_CAPACITY   0x304
+#define SSD_LOG_CLEAR_DATA             0x305
+#define SSD_LOG_BM_SFSTATUS            0x306
+#define SSD_LOG_EIO                            0x307
+#define SSD_LOG_ECMD                   0x308
+#define SSD_LOG_SET_WMODE              0x309
+#define SSD_LOG_DDR_INIT_ERR   0x30a
+#define SSD_LOG_PCIE_LINK_STATUS       0x30b
+#define SSD_LOG_CTRL_RST_SYNC  0x30c
+#define SSD_LOG_CLK_FAULT              0x30d
+#define SSD_LOG_VOLT_FAULT             0x30e
+#define SSD_LOG_SET_CAPACITY_END       0x30F
+#define SSD_LOG_CLEAR_DATA_END 0x310
+#define SSD_LOG_RESET                  0x311
+#define SSD_LOG_CAP_VOLT_FAULT 0x312
+#define SSD_LOG_CAP_LEARN_FAULT        0x313
+#define SSD_LOG_CAP_STATUS             0x314
+#define SSD_LOG_VOLT_STATUS            0x315
+#define SSD_LOG_INLET_OVER_TEMP        0x316
+#define SSD_LOG_INLET_NORMAL_TEMP      0x317
+#define SSD_LOG_FLASH_OVER_TEMP        0x318
+#define SSD_LOG_FLASH_NORMAL_TEMP      0x319
+#define SSD_LOG_CAP_SHORT_CIRCUIT      0x31a
+#define SSD_LOG_SENSOR_FAULT   0x31b
+#define SSD_LOG_ERASE_ALL              0x31c
+#define SSD_LOG_ERASE_ALL_END  0x31d
+
+
+/* sw log fifo depth */
+#define SSD_LOG_FIFO_SZ                1024
+
+
+/* done queue */
+static DEFINE_PER_CPU(struct list_head, ssd_doneq);
+static DEFINE_PER_CPU(struct tasklet_struct, ssd_tasklet);
+
+
+/* unloading driver */
+static volatile int ssd_exiting = 0;
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+static struct class_simple *ssd_class;
+#else
+static struct class *ssd_class;
+#endif
+
+static int ssd_cmajor = SSD_CMAJOR;
+
+/* ssd block device major, minors */
+static int ssd_major = SSD_MAJOR;
+static int ssd_major_sl = SSD_MAJOR_SL;
+static int ssd_minors = SSD_MINORS;
+
+/* ssd device list */
+static struct list_head        ssd_list;
+static unsigned long ssd_index_bits[SSD_MAX_DEV / BITS_PER_LONG + 1];
+static unsigned long ssd_index_bits_sl[SSD_MAX_DEV / BITS_PER_LONG + 1];
+static atomic_t ssd_nr;
+
+/* module param */
+enum ssd_drv_mode
+{
+       SSD_DRV_MODE_STANDARD = 0,      /* full */
+       SSD_DRV_MODE_DEBUG = 2, /* debug */
+       SSD_DRV_MODE_BASE       /* base only */
+};
+
+enum ssd_int_mode
+{
+       SSD_INT_LEGACY = 0, 
+       SSD_INT_MSI, 
+       SSD_INT_MSIX
+};
+
+#if (defined SSD_MSIX)
+#define SSD_INT_MODE_DEFAULT SSD_INT_MSIX
+#elif (defined SSD_MSI)
+#define SSD_INT_MODE_DEFAULT SSD_INT_MSI
+#else
+/* auto select the defaut int mode according to the kernel version*/
+/* suse 11 sp1 irqbalance bug: use msi instead*/
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR >= 6) || (defined RHEL_MAJOR && RHEL_MAJOR == 5 && RHEL_MINOR >= 5))
+#define SSD_INT_MODE_DEFAULT SSD_INT_MSIX
+#else
+#define SSD_INT_MODE_DEFAULT SSD_INT_MSI
+#endif
+#endif
+
+static int mode = SSD_DRV_MODE_STANDARD;
+static int status_mask = 0xFF;
+static int int_mode = SSD_INT_MODE_DEFAULT;
+static int threaded_irq = 0;
+static int log_level = SSD_LOG_LEVEL_WARNING;
+static int ot_protect = 1;
+static int wmode = SSD_WMODE_DEFAULT;
+static int finject = 0;
+
+module_param(mode, int, 0);
+module_param(status_mask, int, 0);
+module_param(int_mode, int, 0);
+module_param(threaded_irq, int, 0);
+module_param(log_level, int, 0);
+module_param(ot_protect, int, 0);
+module_param(wmode, int, 0);
+module_param(finject, int, 0);
+
+
+MODULE_PARM_DESC(mode, "driver mode, 0 - standard, 1 - debug, 2 - debug without IO, 3 - basic debug mode");
+MODULE_PARM_DESC(status_mask, "command status mask, 0 - without command error, 0xff - with command error");
+MODULE_PARM_DESC(int_mode, "preferred interrupt mode, 0 - legacy, 1 - msi, 2 - msix");
+MODULE_PARM_DESC(threaded_irq, "threaded irq, 0 - normal irq, 1 - threaded irq");
+MODULE_PARM_DESC(log_level, "log level to display, 0 - info and above, 1 - notice and above, 2 - warning and above, 3 - error only");
+MODULE_PARM_DESC(ot_protect, "over temperature protect, 0 - disable, 1 - enable");
+MODULE_PARM_DESC(wmode, "write mode, 0 - write buffer (with risk for the 6xx firmware), 1 - write buffer ex, 2 - write through, 3 - auto, 4 - default");
+MODULE_PARM_DESC(finject, "enable fault simulation, 0 - off, 1 - on, for debug purpose only");
+
+
+#ifndef MODULE
+static int __init ssd_drv_mode(char *str)
+{
+       mode = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_status_mask(char *str)
+{
+       status_mask = (int)simple_strtoul(str, NULL, 16);
+
+       return 1;
+}
+
+static int __init ssd_int_mode(char *str)
+{
+       int_mode = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_threaded_irq(char *str)
+{
+       threaded_irq = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_log_level(char *str)
+{
+       log_level = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_ot_protect(char *str)
+{
+       ot_protect = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_wmode(char *str)
+{
+       wmode = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+static int __init ssd_finject(char *str)
+{
+       finject = (int)simple_strtoul(str, NULL, 0);
+
+       return 1;
+}
+
+__setup(MODULE_NAME"_mode=", ssd_drv_mode);
+__setup(MODULE_NAME"_status_mask=", ssd_status_mask);
+__setup(MODULE_NAME"_int_mode=", ssd_int_mode);
+__setup(MODULE_NAME"_threaded_irq=", ssd_threaded_irq);
+__setup(MODULE_NAME"_log_level=", ssd_log_level);
+__setup(MODULE_NAME"_ot_protect=", ssd_ot_protect);
+__setup(MODULE_NAME"_wmode=", ssd_wmode);
+__setup(MODULE_NAME"_finject=", ssd_finject);
+#endif
+
+
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#include <asm/uaccess.h>
+
+#define SSD_PROC_DIR   MODULE_NAME
+#define SSD_PROC_INFO  "info"
+
+static struct proc_dir_entry *ssd_proc_dir = NULL;
+static struct proc_dir_entry *ssd_proc_info = NULL;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0))
+static int ssd_proc_read(char *page, char **start, 
+       off_t off, int count, int *eof, void *data)
+{
+       struct ssd_device *dev = NULL;
+       struct ssd_device *n = NULL;
+       uint64_t size;
+       int idx;
+       int len = 0;
+       //char type; //xx
+
+       if (ssd_exiting) {
+               return 0;
+       }
+
+       len += snprintf((page + len), (count - len), "Driver          Version:\t%s\n", DRIVER_VERSION);
+
+       list_for_each_entry_safe(dev, n, &ssd_list, list) {
+               idx = dev->idx + 1;
+               size = dev->hw_info.size ;
+               do_div(size, 1000000000);
+
+               len += snprintf((page + len), (count - len), "\n");
+
+               len += snprintf((page + len), (count - len), "HIO %d              Size:\t%uGB\n", idx, (uint32_t)size);
+
+               len += snprintf((page + len), (count - len), "HIO %d     Bridge FW VER:\t%03X\n", idx, dev->hw_info.bridge_ver);
+               if (dev->hw_info.ctrl_ver != 0) {
+                       len += snprintf((page + len), (count - len), "HIO %d Controller FW VER:\t%03X\n", idx, dev->hw_info.ctrl_ver);
+               }
+
+               len += snprintf((page + len), (count - len), "HIO %d           PCB VER:\t.%c\n", idx, dev->hw_info.pcb_ver);
+
+               if (dev->hw_info.upper_pcb_ver >= 'A') {
+                       len += snprintf((page + len), (count - len), "HIO %d     Upper PCB VER:\t.%c\n", idx, dev->hw_info.upper_pcb_ver);
+               }
+
+               len += snprintf((page + len), (count - len), "HIO %d            Device:\t%s\n", idx, dev->name);
+       }
+
+       return len;
+}
+
+#else
+
+static int ssd_proc_show(struct seq_file *m, void *v)
+{
+       struct ssd_device *dev = NULL;
+       struct ssd_device *n = NULL;
+       uint64_t size;
+       int idx;
+
+       if (ssd_exiting) {
+               return 0;
+       }
+
+       seq_printf(m, "Driver          Version:\t%s\n", DRIVER_VERSION);
+
+       list_for_each_entry_safe(dev, n, &ssd_list, list) {
+               idx = dev->idx + 1;
+               size = dev->hw_info.size ;
+               do_div(size, 1000000000);
+
+               seq_printf(m, "\n");
+
+               seq_printf(m, "HIO %d              Size:\t%uGB\n", idx, (uint32_t)size);
+
+               seq_printf(m, "HIO %d     Bridge FW VER:\t%03X\n", idx, dev->hw_info.bridge_ver);
+               if (dev->hw_info.ctrl_ver != 0) {
+                       seq_printf(m, "HIO %d Controller FW VER:\t%03X\n", idx, dev->hw_info.ctrl_ver);
+               }
+
+               seq_printf(m, "HIO %d           PCB VER:\t.%c\n", idx, dev->hw_info.pcb_ver);
+
+               if (dev->hw_info.upper_pcb_ver >= 'A') {
+                       seq_printf(m, "HIO %d     Upper PCB VER:\t.%c\n", idx, dev->hw_info.upper_pcb_ver);
+               }
+
+               seq_printf(m, "HIO %d            Device:\t%s\n", idx, dev->name);
+       }
+
+       return 0;
+}
+
+static int ssd_proc_open(struct inode *inode, struct file *file)
+{
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0))
+       return single_open(file, ssd_proc_show, PDE(inode)->data);
+#else
+       return single_open(file, ssd_proc_show, PDE_DATA(inode));
+#endif
+}
+
+static const struct file_operations ssd_proc_fops = {
+       .open           = ssd_proc_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+#endif
+
+
+static void ssd_cleanup_proc(void)
+{
+       if (ssd_proc_info) {
+               remove_proc_entry(SSD_PROC_INFO, ssd_proc_dir);
+               ssd_proc_info = NULL;
+       }
+       if (ssd_proc_dir) {
+               remove_proc_entry(SSD_PROC_DIR, NULL);
+               ssd_proc_dir = NULL;
+       }
+}
+static int ssd_init_proc(void)
+{
+       ssd_proc_dir = proc_mkdir(SSD_PROC_DIR, NULL);
+       if (!ssd_proc_dir)
+               goto out_proc_mkdir;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0))
+       ssd_proc_info = create_proc_entry(SSD_PROC_INFO, S_IFREG | S_IRUGO | S_IWUSR, ssd_proc_dir);
+       if (!ssd_proc_info)
+               goto out_create_proc_entry;
+
+       ssd_proc_info->read_proc = ssd_proc_read;
+
+/* kernel bug */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30))
+       ssd_proc_info->owner = THIS_MODULE;
+#endif
+#else
+       ssd_proc_info = proc_create(SSD_PROC_INFO, 0600, ssd_proc_dir, &ssd_proc_fops);
+       if (!ssd_proc_info)
+               goto out_create_proc_entry;
+#endif
+
+       return 0;
+
+out_create_proc_entry:
+       remove_proc_entry(SSD_PROC_DIR, NULL);
+out_proc_mkdir:
+       return -ENOMEM;
+}
+
+#else
+static void ssd_cleanup_proc(void)
+{
+       return;
+}
+static int ssd_init_proc(void)
+{
+       return 0;
+}
+#endif /* CONFIG_PROC_FS */
+
+/* sysfs */
+static void ssd_unregister_sysfs(struct ssd_device *dev)
+{
+       return;
+}
+
+static int ssd_register_sysfs(struct ssd_device *dev)
+{
+       return 0;
+}
+
+static void ssd_cleanup_sysfs(void)
+{
+       return;
+}
+
+static int ssd_init_sysfs(void)
+{
+       return 0;
+}
+
+static inline void ssd_put_index(int slave, int index)
+{
+       unsigned long *index_bits = ssd_index_bits;
+
+       if (slave) {
+               index_bits = ssd_index_bits_sl;
+       }
+
+       if (test_and_clear_bit(index,  index_bits)) {
+               atomic_dec(&ssd_nr);
+       }
+}
+
+static inline int ssd_get_index(int slave)
+{
+       unsigned long *index_bits = ssd_index_bits;
+       int index;
+
+       if (slave) {
+               index_bits = ssd_index_bits_sl;
+       }
+
+find_index:
+       if ((index = find_first_zero_bit(index_bits, SSD_MAX_DEV)) >= SSD_MAX_DEV) {
+                       return -1;
+       }
+
+       if (test_and_set_bit(index, index_bits)) {
+               goto find_index;
+       }
+
+       atomic_inc(&ssd_nr);
+
+       return index;
+}
+
+static void ssd_cleanup_index(void)
+{
+       return;
+}
+
+static int ssd_init_index(void)
+{
+       INIT_LIST_HEAD(&ssd_list);
+       atomic_set(&ssd_nr, 0);
+       memset(ssd_index_bits, 0, (SSD_MAX_DEV / BITS_PER_LONG + 1));
+       memset(ssd_index_bits_sl, 0, (SSD_MAX_DEV / BITS_PER_LONG + 1));
+
+       return 0;
+}
+
+static void ssd_set_dev_name(char *name, size_t size, int idx)
+{
+       if(idx < SSD_ALPHABET_NUM) {
+               snprintf(name, size, "%c", 'a'+idx);
+       } else {
+               idx -= SSD_ALPHABET_NUM;
+               snprintf(name, size, "%c%c", 'a'+(idx/SSD_ALPHABET_NUM), 'a'+(idx%SSD_ALPHABET_NUM));
+       }
+}
+
+/* pci register r&w */
+static inline void ssd_reg_write(void *addr, uint64_t val)
+{
+       iowrite32((uint32_t)val, addr);
+       iowrite32((uint32_t)(val >> 32), addr + 4);
+       wmb();
+}
+
+static inline uint64_t ssd_reg_read(void *addr)
+{
+       uint64_t val;
+       uint32_t val_lo, val_hi;
+
+       val_lo = ioread32(addr);
+       val_hi = ioread32(addr + 4);
+
+       rmb();
+       val = val_lo | ((uint64_t)val_hi << 32);
+
+       return val;
+}
+
+
+#define ssd_reg32_write(addr, val)     writel(val, addr)
+#define ssd_reg32_read(addr)           readl(addr)
+
+/* alarm led */
+static void ssd_clear_alarm(struct ssd_device *dev)
+{
+       uint32_t val;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_LED_REG);
+
+       /* firmware control */
+       val &= ~0x2;
+
+       ssd_reg32_write(dev->ctrlp + SSD_LED_REG, val);
+}
+
+static void ssd_set_alarm(struct ssd_device *dev)
+{
+       uint32_t val;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_LED_REG);
+
+       /* light up */
+       val &= ~0x1;
+       /* software control */
+       val |= 0x2;
+
+       ssd_reg32_write(dev->ctrlp + SSD_LED_REG, val);
+}
+
+#define u32_swap(x) \
+       ((uint32_t)( \
+       (((uint32_t)(x) & (uint32_t)0x000000ffUL) << 24) | \
+       (((uint32_t)(x) & (uint32_t)0x0000ff00UL) <<  8) | \
+       (((uint32_t)(x) & (uint32_t)0x00ff0000UL) >>  8) | \
+       (((uint32_t)(x) & (uint32_t)0xff000000UL) >> 24)))
+
+#define u16_swap(x) \
+       ((uint16_t)( \
+       (((uint16_t)(x) & (uint16_t)0x00ff) <<  8) | \
+       (((uint16_t)(x) & (uint16_t)0xff00) >>  8) ))
+
+
+#if 0
+/* No lock, for init only*/
+static int ssd_spi_read_id(struct ssd_device *dev, uint32_t *id)
+{
+       uint32_t val;
+       unsigned long st;
+       int ret = 0;
+
+       if (!dev || !id) {
+               return -EINVAL;
+       }
+
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_ID);
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+
+       st = jiffies;
+       for (;;) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+               if (val == 0x1000000) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               cond_resched();
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_ID);
+       *id = val;
+
+out:
+       return ret;
+}
+#endif
+
+/* spi access */
+static int ssd_init_spi(struct ssd_device *dev)
+{
+       uint32_t val;
+       unsigned long st;
+       int ret = 0;
+
+       mutex_lock(&dev->spi_mutex);
+       st = jiffies;
+       for(;;) {
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS);
+
+               do {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+
+                       if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out;
+                       }
+                       cond_resched();
+               } while (val != 0x1000000);
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS);
+               if (!(val & 0x1)) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               cond_resched();
+       }
+
+out:
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if (val & 0x1) {
+                       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR);
+               }
+       }
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE);
+       mutex_unlock(&dev->spi_mutex);
+
+       ret = 0;
+
+       return ret;
+}
+
+static int ssd_spi_page_read(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size)
+{
+       uint32_t val;
+       uint32_t rlen = 0;
+       unsigned long st;
+       int ret = 0;
+
+       if (!dev || !buf) {
+               return -EINVAL;
+       }
+
+       if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || 
+               ((uint64_t)off + (uint64_t)size) > dev->rom_info.size || size > dev->rom_info.page_size) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->spi_mutex);
+       while (rlen < size) {
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, ((off + rlen) >> 24));
+               wmb();
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, (((off + rlen) << 8) | SSD_SPI_CMD_READ));
+
+               (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+               (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+               (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+               (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+
+               st = jiffies;
+               for (;;) {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+                       if (val == 0x1000000) {
+                               break;
+                       }
+
+                       if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out;
+                       }
+                       cond_resched();
+               }
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_RDATA);
+               *(uint32_t *)(buf + rlen)= u32_swap(val);
+
+               rlen += sizeof(uint32_t);
+       }
+
+out:
+       mutex_unlock(&dev->spi_mutex);
+       return ret;
+}
+
+static int ssd_spi_page_write(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size)
+{
+       uint32_t val;
+       uint32_t wlen;
+       unsigned long st;
+       int i;
+       int ret = 0;
+
+       if (!dev || !buf) {
+               return -EINVAL;
+       }
+
+       if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || 
+               ((uint64_t)off + (uint64_t)size) > dev->rom_info.size || size > dev->rom_info.page_size || 
+               (off / dev->rom_info.page_size) !=  ((off + size - 1) / dev->rom_info.page_size)) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->spi_mutex);
+
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE);
+
+       wlen = size / sizeof(uint32_t);
+       for (i=0; i<(int)wlen; i++) {
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_WDATA, u32_swap(*((uint32_t *)buf + i)));
+       }
+
+       wmb();
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, (off >> 24));
+       wmb();
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, ((off << 8) | SSD_SPI_CMD_PROGRAM));
+
+       udelay(1);
+
+       st = jiffies;
+       for (;;) {
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS);
+               do {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+
+                       if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out;
+                       }
+                       cond_resched();
+               } while (val != 0x1000000);
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS);
+               if (!(val & 0x1)) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               cond_resched();
+       }
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if ((val >> 6) & 0x1) {
+                       ret = -EIO;
+                       goto out;
+               }
+       }
+
+out:
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if (val & 0x1) {
+                       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR);
+               }
+       }
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE);
+
+       mutex_unlock(&dev->spi_mutex);
+
+       return ret;
+}
+
+static int ssd_spi_block_erase(struct ssd_device *dev, uint32_t off)
+{
+       uint32_t val;
+       unsigned long st;
+       int ret = 0;
+
+       if (!dev) {
+               return -EINVAL;
+       }
+
+       if ((off % dev->rom_info.block_size) != 0 || off >= dev->rom_info.size) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->spi_mutex);
+
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE);
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE);
+
+       wmb();
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, (off >> 24));
+       wmb();
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, ((off << 8) | SSD_SPI_CMD_ERASE));
+
+       st = jiffies;
+       for (;;) {
+               ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS);
+
+               do {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY);
+
+                       if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out;
+                       }
+                       cond_resched();
+               } while (val != 0x1000000);
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS);
+               if (!(val & 0x1)) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               cond_resched();
+       }
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if ((val >> 5) & 0x1) {
+                       ret = -EIO;
+                       goto out;
+               }
+       }
+
+out:
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if (val & 0x1) {
+                       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR);
+               }
+       }
+       ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE);
+
+       mutex_unlock(&dev->spi_mutex);
+
+       return ret;
+}
+
+static int ssd_spi_read(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size)
+{
+       uint32_t len = 0;
+       uint32_t roff;
+       uint32_t rsize;
+       int ret = 0;
+
+       if (!dev || !buf) {
+               return -EINVAL;
+       }
+
+       if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || 
+               ((uint64_t)off + (uint64_t)size) > dev->rom_info.size) {
+               return -EINVAL;
+       }
+
+       while (len < size) {
+               roff = (off + len) % dev->rom_info.page_size;
+               rsize = dev->rom_info.page_size - roff;
+               if ((size - len) < rsize) {
+                       rsize = (size - len);
+               }
+               roff = off + len;
+
+               ret = ssd_spi_page_read(dev, (buf + len), roff, rsize);
+               if (ret) {
+                       goto out;
+               }
+
+               len += rsize;
+
+               cond_resched();
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_spi_write(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size)
+{
+       uint32_t len = 0;
+       uint32_t woff;
+       uint32_t wsize;
+       int ret = 0;
+
+       if (!dev || !buf) {
+               return -EINVAL;
+       }
+
+       if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || 
+               ((uint64_t)off + (uint64_t)size) > dev->rom_info.size) {
+               return -EINVAL;
+       }
+
+       while (len < size) {
+               woff = (off + len) % dev->rom_info.page_size;
+               wsize = dev->rom_info.page_size - woff;
+               if ((size - len) < wsize) {
+                       wsize = (size - len);
+               }
+               woff = off + len;
+
+               ret = ssd_spi_page_write(dev, (buf + len), woff, wsize);
+               if (ret) {
+                       goto out;
+               }
+
+               len += wsize;
+
+               cond_resched();
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_spi_erase(struct ssd_device *dev, uint32_t off, uint32_t size)
+{
+       uint32_t len = 0;
+       uint32_t eoff;
+       int ret = 0;
+
+       if (!dev) {
+               return -EINVAL;
+       }
+
+       if (size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size ||
+               (off % dev->rom_info.block_size) != 0 || (size % dev->rom_info.block_size) != 0) {
+               return -EINVAL;
+       }
+
+       while (len < size) {
+               eoff = (off + len);
+
+               ret = ssd_spi_block_erase(dev, eoff);
+               if (ret) {
+                       goto out;
+               }
+
+               len += dev->rom_info.block_size;
+
+               cond_resched();
+       }
+
+out:
+       return ret;
+}
+
+/* i2c access */
+static uint32_t __ssd_i2c_reg32_read(void *addr)
+{
+       return ssd_reg32_read(addr);
+}
+
+static void __ssd_i2c_reg32_write(void *addr, uint32_t val)
+{
+       ssd_reg32_write(addr, val);
+       ssd_reg32_read(addr);
+}
+
+static int __ssd_i2c_clear(struct ssd_device *dev, uint8_t saddr)
+{
+       ssd_i2c_ctrl_t ctrl;
+       ssd_i2c_data_t data;
+       uint8_t status = 0;
+       int nr_data = 0;
+       unsigned long st;
+       int ret = 0;
+
+check_status:
+       ctrl.bits.wdata = 0;
+       ctrl.bits.addr  = SSD_I2C_STATUS_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       st = jiffies;
+       for (;;) {
+               data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+               if (data.bits.valid == 0) {
+                       break;
+               }
+
+               /* retry */
+               if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               cond_resched();
+       }
+       status = data.bits.rdata;
+
+       if (!(status & 0x4)) {
+               /* clear read fifo data */
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_DATA_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               st = jiffies;
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out;
+                       }
+                       cond_resched();
+               }
+
+               nr_data++;
+               if (nr_data <= SSD_I2C_MAX_DATA) {
+                       goto check_status;
+               } else {
+                       goto out_reset;
+               }
+       }
+
+       if (status & 0x3) {
+               /* clear int */
+               ctrl.bits.wdata = 0x04;
+               ctrl.bits.addr  = SSD_I2C_CMD_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+       }
+
+       if (!(status & 0x8)) {
+out_reset:
+               /* reset i2c controller */
+               ctrl.bits.wdata = 0x0;
+               ctrl.bits.addr  = SSD_I2C_RESET_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_i2c_write(struct ssd_device *dev, uint8_t saddr, uint8_t size, uint8_t *buf)
+{
+       ssd_i2c_ctrl_t ctrl;
+       ssd_i2c_data_t data;
+       uint8_t off = 0;
+       uint8_t status = 0;
+       unsigned long st;
+       int ret = 0;
+
+       mutex_lock(&dev->i2c_mutex);
+
+       ctrl.val = 0;
+
+       /* slave addr */
+       ctrl.bits.wdata = saddr;
+       ctrl.bits.addr  = SSD_I2C_SADDR_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* data */
+       while (off < size) {
+               ctrl.bits.wdata = buf[off];
+               ctrl.bits.addr  = SSD_I2C_DATA_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               off++;
+       }
+
+       /* write */
+       ctrl.bits.wdata = 0x01;
+       ctrl.bits.addr  = SSD_I2C_CMD_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* wait */
+       st = jiffies;
+       for (;;) {
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_STATUS_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out_clear;
+                       }
+                       cond_resched();
+               }
+
+               status = data.bits.rdata;
+               if (status & 0x1) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out_clear;
+               }
+               cond_resched();
+       }
+
+       if (!(status & 0x1)) {
+               ret =  -1;
+               goto out_clear;
+       }
+
+       /* busy ? */
+       if (status & 0x20) {
+               ret =  -2;
+               goto out_clear;
+       }
+
+       /* ack ? */
+       if (status & 0x10) {
+               ret =  -3;
+               goto out_clear;
+       }
+
+       /* clear */
+out_clear:
+       if (__ssd_i2c_clear(dev, saddr)) {
+               if (!ret) ret = -4;
+       }
+
+       mutex_unlock(&dev->i2c_mutex);
+
+       return ret;
+}
+
+static int ssd_i2c_read(struct ssd_device *dev, uint8_t saddr, uint8_t size, uint8_t *buf)
+{
+       ssd_i2c_ctrl_t ctrl;
+       ssd_i2c_data_t data;
+       uint8_t off = 0;
+       uint8_t status = 0;
+       unsigned long st;
+       int ret = 0;
+
+       mutex_lock(&dev->i2c_mutex);
+
+       ctrl.val = 0;
+
+       /* slave addr */
+       ctrl.bits.wdata = saddr;
+       ctrl.bits.addr  = SSD_I2C_SADDR_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* read len */
+       ctrl.bits.wdata = size;
+       ctrl.bits.addr  = SSD_I2C_LEN_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* read */
+       ctrl.bits.wdata = 0x02;
+       ctrl.bits.addr  = SSD_I2C_CMD_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* wait */
+       st = jiffies;
+       for (;;) {
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_STATUS_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out_clear;
+                       }
+                       cond_resched();
+               }
+
+               status = data.bits.rdata;
+               if (status & 0x2) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out_clear;
+               }
+               cond_resched();
+       }
+
+       if (!(status & 0x2)) {
+               ret =  -1;
+               goto out_clear;
+       }
+
+       /* busy ? */
+       if (status & 0x20) {
+               ret =  -2;
+               goto out_clear;
+       }
+
+       /* ack ? */
+       if (status & 0x10) {
+               ret =  -3;
+               goto out_clear;
+       }
+
+       /* data */
+       while (off < size) {
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_DATA_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               st = jiffies;
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out_clear;
+                       }
+                       cond_resched();
+               }
+
+               buf[off] = data.bits.rdata;
+
+               off++;
+       }
+
+       /* clear */
+out_clear:
+       if (__ssd_i2c_clear(dev, saddr)) {
+               if (!ret) ret = -4;
+       }
+
+       mutex_unlock(&dev->i2c_mutex);
+
+       return ret;
+}
+
+static int ssd_i2c_write_read(struct ssd_device *dev, uint8_t saddr, uint8_t wsize, uint8_t *wbuf, uint8_t rsize, uint8_t *rbuf)
+{
+       ssd_i2c_ctrl_t ctrl;
+       ssd_i2c_data_t data;
+       uint8_t off = 0;
+       uint8_t status = 0;
+       unsigned long st;
+       int ret = 0;
+
+       mutex_lock(&dev->i2c_mutex);
+
+       ctrl.val = 0;
+
+       /* slave addr */
+       ctrl.bits.wdata = saddr;
+       ctrl.bits.addr  = SSD_I2C_SADDR_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* data */
+       off = 0;
+       while (off < wsize) {
+               ctrl.bits.wdata = wbuf[off];
+               ctrl.bits.addr  = SSD_I2C_DATA_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               off++;
+       }
+
+       /* read len */
+       ctrl.bits.wdata = rsize;
+       ctrl.bits.addr  = SSD_I2C_LEN_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* write -> read */
+       ctrl.bits.wdata = 0x03;
+       ctrl.bits.addr  = SSD_I2C_CMD_REG;
+       ctrl.bits.rw    = SSD_I2C_CTRL_WRITE;
+       __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+       /* wait */
+       st = jiffies;
+       for (;;) {
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_STATUS_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out_clear;
+                       }
+                       cond_resched();
+               }
+
+               status = data.bits.rdata;
+               if (status & 0x2) {
+                       break;
+               }
+
+               if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                       ret = -ETIMEDOUT;
+                       goto out_clear;
+               }
+               cond_resched();
+       }
+
+       if (!(status & 0x2)) {
+               ret =  -1;
+               goto out_clear;
+       }
+
+       /* busy ? */
+       if (status & 0x20) {
+               ret =  -2;
+               goto out_clear;
+       }
+
+       /* ack ? */
+       if (status & 0x10) {
+               ret =  -3;
+               goto out_clear;
+       }
+
+       /* data */
+       off = 0;
+       while (off < rsize) {
+               ctrl.bits.wdata = 0;
+               ctrl.bits.addr  = SSD_I2C_DATA_REG;
+               ctrl.bits.rw    = SSD_I2C_CTRL_READ;
+               __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val);
+
+               st = jiffies;
+               for (;;) {
+                       data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG);
+                       if (data.bits.valid == 0) {
+                               break;
+                       }
+
+                       /* retry */
+                       if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) {
+                               ret = -ETIMEDOUT;
+                               goto out_clear;
+                       }
+                       cond_resched();
+               }
+
+               rbuf[off] = data.bits.rdata;
+
+               off++;
+       }
+
+       /* clear */
+out_clear:
+       if (__ssd_i2c_clear(dev, saddr)) {
+               if (!ret) ret = -4;
+       }
+       mutex_unlock(&dev->i2c_mutex);
+
+       return ret;
+}
+
+static int ssd_smbus_send_byte(struct ssd_device *dev, uint8_t saddr, uint8_t *buf)
+{
+       int i = 0;
+       int ret = 0;
+
+       for (;;) {
+               ret = ssd_i2c_write(dev, saddr, 1, buf);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_receive_byte(struct ssd_device *dev, uint8_t saddr, uint8_t *buf)
+{
+       int i = 0;
+       int ret = 0;
+
+       for (;;) {
+               ret = ssd_i2c_read(dev, saddr, 1, buf);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_write_byte(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+       memcpy((smb_data + 1), buf, 1);
+
+       for (;;) {
+               ret = ssd_i2c_write(dev, saddr, 2, smb_data);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_read_byte(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+
+       for (;;) {
+               ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, 1, buf);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_write_word(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+       memcpy((smb_data + 1), buf, 2);
+
+       for (;;) {
+               ret = ssd_i2c_write(dev, saddr, 3, smb_data);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_read_word(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+
+       for (;;) {
+               ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, 2, buf);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_write_block(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t size, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+       smb_data[1] = size;
+       memcpy((smb_data + 2), buf, size);
+
+       for (;;) {
+               ret = ssd_i2c_write(dev, saddr, (2 + size), smb_data);
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+
+       return ret;
+}
+
+static int ssd_smbus_read_block(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t size, uint8_t *buf)
+{
+       uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0};
+       uint8_t rsize;
+       int i = 0;
+       int ret = 0;
+
+       smb_data[0] = cmd;
+
+       for (;;) {
+               ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, (SSD_SMBUS_BLOCK_MAX + 1), (smb_data + 1));
+               if (!ret || -ETIMEDOUT == ret) {
+                       break;
+               }
+
+               i++;
+               if (i >= SSD_SMBUS_RETRY_MAX) {
+                       break;
+               }
+               msleep(SSD_SMBUS_RETRY_INTERVAL);
+       }
+       if (ret) {
+               return ret;
+       }
+
+       rsize = smb_data[1];
+
+       if (rsize > size ) {
+               rsize = size;
+       }
+
+       memcpy(buf, (smb_data + 2), rsize);
+
+       return 0;
+}
+
+
+static int ssd_gen_swlog(struct ssd_device *dev, uint16_t event, uint32_t data);
+
+/* sensor */
+static int ssd_init_lm75(struct ssd_device *dev, uint8_t saddr)
+{
+       uint8_t conf = 0;
+       int ret = 0;
+
+       ret = ssd_smbus_read_byte(dev, saddr, SSD_LM75_REG_CONF, &conf);
+       if (ret) {
+               goto out;
+       }
+
+       conf &= (uint8_t)(~1u);
+
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM75_REG_CONF, &conf);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_lm75_read(struct ssd_device *dev, uint8_t saddr, uint16_t *data)
+{
+       uint16_t val = 0;
+       int ret;
+
+       ret = ssd_smbus_read_word(dev, saddr, SSD_LM75_REG_TEMP, (uint8_t *)&val);
+       if (ret) {
+               return ret;
+       }
+
+       *data = u16_swap(val);
+
+       return 0;
+}
+
+static int ssd_init_lm80(struct ssd_device *dev, uint8_t saddr)
+{
+       uint8_t val;
+       uint8_t low, high;
+       int i;
+       int ret = 0;
+
+       /* init */
+       val = 0x80;
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_CONFIG, &val);
+       if (ret) {
+               goto out;
+       }
+
+       /* 11-bit temp */
+       val = 0x08;
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_RES, &val);
+       if (ret) {
+               goto out;
+       }
+
+       /* set volt limit */
+       for (i=0; i<SSD_LM80_IN_NR; i++) {
+               high = ssd_lm80_limit[i].high;
+               low = ssd_lm80_limit[i].low;
+
+               if (SSD_LM80_IN_CAP == i) {
+                       low = 0;
+               }
+
+               if (dev->hw_info.nr_ctrl <= 1 && SSD_LM80_IN_1V2 == i) {
+                       high = 0xFF;
+                       low = 0;
+               }
+
+               /* high limit */
+               ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_IN_MAX(i), &high);
+               if (ret) {
+                       goto out;
+               }
+
+               /* low limit*/
+               ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_IN_MIN(i), &low);
+               if (ret) {
+                       goto out;
+               }
+       }
+
+       /* set interrupt mask: allow volt in interrupt except cap in*/
+       val = 0x81;
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val);
+       if (ret) {
+               goto out;
+       }
+
+       /* set interrupt mask: disable others */
+       val = 0xFF;
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK2, &val);
+       if (ret) {
+               goto out;
+       }
+
+       /* start */
+       val = 0x03;
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_CONFIG, &val);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_lm80_enable_in(struct ssd_device *dev, uint8_t saddr, int idx)
+{
+       uint8_t val = 0;
+       int ret = 0;
+
+       if (idx >= SSD_LM80_IN_NR || idx < 0) {
+               return -EINVAL;
+       }
+
+       ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_MASK1, &val);
+       if (ret) {
+               goto out;
+       }
+
+       val &= ~(1UL << (uint32_t)idx);
+
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_lm80_disable_in(struct ssd_device *dev, uint8_t saddr, int idx)
+{
+       uint8_t val = 0;
+       int ret = 0;
+
+       if (idx >= SSD_LM80_IN_NR || idx < 0) {
+               return -EINVAL;
+       }
+
+       ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_MASK1, &val);
+       if (ret) {
+               goto out;
+       }
+
+       val |= (1UL << (uint32_t)idx);
+
+       ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_lm80_read_temp(struct ssd_device *dev, uint8_t saddr, uint16_t *data)
+{
+       uint16_t val = 0;
+       int ret;
+
+       ret = ssd_smbus_read_word(dev, saddr, SSD_LM80_REG_TEMP, (uint8_t *)&val);
+       if (ret) {
+               return ret;
+       }
+
+       *data = u16_swap(val);
+
+       return 0;
+}
+
+static int ssd_lm80_check_event(struct ssd_device *dev, uint8_t saddr)
+{
+       uint32_t volt;
+       uint16_t val = 0, status;
+       uint8_t alarm1 = 0, alarm2 = 0;
+       int i;
+       int ret = 0;
+
+       /* read interrupt status to clear interrupt */
+       ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_ALARM1, &alarm1);
+       if (ret) {
+               goto out;
+       }
+
+       ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_ALARM2, &alarm2);
+       if (ret) {
+               goto out;
+       }
+
+       status = (uint16_t)alarm1 | ((uint16_t)alarm2 << 8);
+
+       /* parse inetrrupt status */
+       for (i=0; i<SSD_LM80_IN_NR; i++) {
+               if (!((status >> (uint32_t)i) & 0x1)) {
+                       if (test_and_clear_bit(SSD_HWMON_LM80(i), &dev->hwmon)) {
+                               /* enable INx irq */
+                               ret = ssd_lm80_enable_in(dev, saddr, i);
+                               if (ret) {
+                                       goto out;
+                               }
+                       }
+
+                       continue;
+               }
+
+               /* disable INx irq */
+               ret = ssd_lm80_disable_in(dev, saddr, i);
+               if (ret) {
+                       goto out;
+               }
+
+               if (test_and_set_bit(SSD_HWMON_LM80(i), &dev->hwmon)) {
+                       continue;
+               }
+
+               ret = ssd_smbus_read_word(dev, saddr, SSD_LM80_REG_IN(i), (uint8_t *)&val);
+               if (ret) {
+                       goto out;
+               }
+
+               volt = SSD_LM80_CONVERT_VOLT(u16_swap(val));
+
+               switch (i) {
+                       case SSD_LM80_IN_CAP: {
+                               if (0 == volt) {
+                                       ssd_gen_swlog(dev, SSD_LOG_CAP_SHORT_CIRCUIT, 0);
+                               } else {
+                                       ssd_gen_swlog(dev, SSD_LOG_CAP_VOLT_FAULT, SSD_PL_CAP_VOLT(volt));
+                               }
+                               break;
+                       }
+
+                       case SSD_LM80_IN_1V2:
+                       case SSD_LM80_IN_1V2a:
+                       case SSD_LM80_IN_1V5:
+                       case SSD_LM80_IN_1V8: {
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_STATUS, SSD_VOLT_LOG_DATA(i, 0, volt));
+                               break;
+                       }
+                       case SSD_LM80_IN_FPGA_3V3:
+                       case SSD_LM80_IN_3V3: {
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_STATUS, SSD_VOLT_LOG_DATA(i, 0, SSD_LM80_3V3_VOLT(volt)));
+                               break;
+                       }
+                       default:
+                               break;
+               }
+       }
+
+out:
+       if (ret) {
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, (uint32_t)saddr);
+               }
+       } else {
+               test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon);
+       }
+       return ret;
+}
+
+static int ssd_init_sensor(struct ssd_device *dev)
+{
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               goto out;
+       }
+
+       ret = ssd_init_lm75(dev, SSD_SENSOR_LM75_SADDRESS);
+       if (ret) {
+               hio_warn("%s: init lm75 failed\n", dev->name);
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM75_SADDRESS);
+               }
+               goto out;
+       }
+
+       if (dev->hw_info.pcb_ver >= 'B' || dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_HHHL) {
+               ret = ssd_init_lm80(dev, SSD_SENSOR_LM80_SADDRESS);
+               if (ret) {
+                       hio_warn("%s: init lm80 failed\n", dev->name);
+                       if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                               ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+                       }
+                       goto out;
+               }
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+/* board volt */
+static int ssd_mon_boardvolt(struct ssd_device *dev)
+{
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               return 0;
+       }
+
+       return ssd_lm80_check_event(dev, SSD_SENSOR_LM80_SADDRESS);
+}
+
+/* temperature */
+static int ssd_mon_temp(struct ssd_device *dev)
+{
+       int cur;
+       uint16_t val = 0;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               return 0;
+       }
+
+       /* inlet */
+       ret = ssd_lm80_read_temp(dev, SSD_SENSOR_LM80_SADDRESS, &val);
+       if (ret) {
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+               }
+               goto out;
+       }
+       test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon);
+
+       cur = SSD_SENSOR_CONVERT_TEMP(val);
+       if (cur >= SSD_INLET_OT_TEMP) {
+               if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_INLET), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_INLET_OVER_TEMP, (uint32_t)cur);
+               }
+       } else if(cur < SSD_INLET_OT_HYST) {
+               if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_INLET), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_INLET_NORMAL_TEMP, (uint32_t)cur);
+               }
+       }
+
+       /* flash */
+       ret = ssd_lm75_read(dev, SSD_SENSOR_LM75_SADDRESS, &val);
+       if (ret) {
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM75_SADDRESS);
+               }
+               goto out;
+       }
+       test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon);
+
+       cur = SSD_SENSOR_CONVERT_TEMP(val);
+       if (cur >= SSD_FLASH_OT_TEMP) {
+               if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_FLASH), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_FLASH_OVER_TEMP, (uint32_t)cur);
+               }
+       } else if(cur < SSD_FLASH_OT_HYST) {
+               if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_FLASH), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_FLASH_NORMAL_TEMP, (uint32_t)cur);
+               }
+       }
+
+out:
+       return ret;
+}
+
+/* cmd tag */
+static inline void ssd_put_tag(struct ssd_device *dev, int tag)
+{
+       test_and_clear_bit(tag,  dev->tag_map);
+       wake_up(&dev->tag_wq);
+}
+
+static inline int ssd_get_tag(struct ssd_device *dev, int wait)
+{
+       int tag;
+
+find_tag:
+       while ((tag = find_first_zero_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz)) >= atomic_read(&dev->queue_depth)) {
+               DEFINE_WAIT(__wait);
+
+               if (!wait) {
+                       return -1;
+               }
+
+               prepare_to_wait_exclusive(&dev->tag_wq, &__wait, TASK_UNINTERRUPTIBLE);
+               schedule();
+
+               finish_wait(&dev->tag_wq, &__wait);
+       }
+
+       if (test_and_set_bit(tag, dev->tag_map)) {
+               goto find_tag;
+       }
+
+       return tag;
+}
+
+static void ssd_barrier_put_tag(struct ssd_device *dev, int tag)
+{
+       test_and_clear_bit(tag,  dev->tag_map);
+}
+
+static int ssd_barrier_get_tag(struct ssd_device *dev)
+{
+       int tag = 0;
+
+       if (test_and_set_bit(tag, dev->tag_map)) {
+               return -1;
+       }
+
+       return tag;
+}
+
+static void ssd_barrier_end(struct ssd_device *dev)
+{
+       atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz);
+       wake_up_all(&dev->tag_wq);
+
+       mutex_unlock(&dev->barrier_mutex);
+}
+
+static int ssd_barrier_start(struct ssd_device *dev)
+{
+       int i;
+
+       mutex_lock(&dev->barrier_mutex);
+
+       atomic_set(&dev->queue_depth, 0);
+
+       for (i=0; i<SSD_CMD_TIMEOUT; i++) {
+               if (find_first_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) {
+                       return 0;
+               }
+
+               __set_current_state(TASK_INTERRUPTIBLE);
+               schedule_timeout(1);
+       }
+
+       atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz);
+       wake_up_all(&dev->tag_wq);
+
+       mutex_unlock(&dev->barrier_mutex);
+
+       return -EBUSY;
+}
+
+static int ssd_busy(struct ssd_device *dev)
+{
+       if (find_first_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) {
+               return 0;
+       }
+
+       return 1;
+}
+
+static int ssd_wait_io(struct ssd_device *dev)
+{
+       int i;
+
+       for (i=0; i<SSD_CMD_TIMEOUT; i++) {
+               if (find_first_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) {
+                       return 0;
+               }
+
+               __set_current_state(TASK_INTERRUPTIBLE);
+               schedule_timeout(1);
+       }
+
+       return -EBUSY;
+}
+
+#if 0
+static int ssd_in_barrier(struct ssd_device *dev)
+{
+       return (0 == atomic_read(&dev->queue_depth));
+}
+#endif
+
+static void ssd_cleanup_tag(struct ssd_device *dev)
+{
+       kfree(dev->tag_map);
+}
+
+static int ssd_init_tag(struct ssd_device *dev)
+{
+       int nr_ulongs = ALIGN(dev->hw_info.cmd_fifo_sz, BITS_PER_LONG) / BITS_PER_LONG;
+
+       mutex_init(&dev->barrier_mutex);
+
+       atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz);
+
+       dev->tag_map = kmalloc(nr_ulongs * sizeof(unsigned long), GFP_ATOMIC);
+       if (!dev->tag_map) {
+               return -ENOMEM;
+       }
+
+       memset(dev->tag_map, 0, nr_ulongs * sizeof(unsigned long));
+
+       init_waitqueue_head(&dev->tag_wq);
+
+       return 0;
+}
+
+/* io stat */
+static void ssd_end_io_acct(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = cmd->dev;
+       struct bio *bio = cmd->bio;
+       unsigned long dur = jiffies - cmd->start_time;
+       int rw = bio_data_dir(bio);
+
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6 && RHEL_MINOR >= 7))
+       int cpu = part_stat_lock();
+       struct hd_struct *part = disk_map_sector_rcu(dev->gd, bio_start(bio));
+       part_round_stats(cpu, part);
+       part_stat_add(cpu, part, ticks[rw], dur);
+       part_dec_in_flight(part, rw);
+       part_stat_unlock();
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27))
+       int cpu = part_stat_lock();
+       struct hd_struct *part = &dev->gd->part0;
+       part_round_stats(cpu, part);
+       part_stat_add(cpu, part, ticks[rw], dur);
+       part_stat_unlock();
+       part->in_flight[rw] = atomic_dec_return(&dev->in_flight[rw]);
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14))
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+       disk_stat_add(dev->gd, ticks[rw], dur);
+       dev->gd->in_flight = atomic_dec_return(&dev->in_flight[0]);
+#else
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+       if (rw == WRITE) {
+               disk_stat_add(dev->gd, write_ticks, dur);
+       } else {
+               disk_stat_add(dev->gd, read_ticks, dur);
+       }
+       dev->gd->in_flight = atomic_dec_return(&dev->in_flight[0]);
+#endif
+}
+
+static void ssd_start_io_acct(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = cmd->dev;
+       struct bio *bio = cmd->bio;
+       int rw = bio_data_dir(bio);
+
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6 && RHEL_MINOR >= 7))
+       int cpu = part_stat_lock();
+       struct hd_struct *part = disk_map_sector_rcu(dev->gd, bio_start(bio));
+       part_round_stats(cpu, part);
+       part_stat_inc(cpu, part, ios[rw]);
+       part_stat_add(cpu, part, sectors[rw], bio_sectors(bio));
+       part_inc_in_flight(part, rw);
+       part_stat_unlock();
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27))
+       int cpu = part_stat_lock();
+       struct hd_struct *part = &dev->gd->part0;
+       part_round_stats(cpu, part);
+       part_stat_inc(cpu, part, ios[rw]);
+       part_stat_add(cpu, part, sectors[rw], bio_sectors(bio));
+       part_stat_unlock();
+       part->in_flight[rw] = atomic_inc_return(&dev->in_flight[rw]);
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14))
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+       disk_stat_inc(dev->gd, ios[rw]);
+       disk_stat_add(dev->gd, sectors[rw], bio_sectors(bio));
+       dev->gd->in_flight = atomic_inc_return(&dev->in_flight[0]);
+#else
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+       if (rw == WRITE) {
+               disk_stat_inc(dev->gd, writes);
+               disk_stat_add(dev->gd, write_sectors, bio_sectors(bio));
+       } else {
+               disk_stat_inc(dev->gd, reads);
+               disk_stat_add(dev->gd, read_sectors, bio_sectors(bio));
+       }
+       dev->gd->in_flight = atomic_inc_return(&dev->in_flight[0]);
+#endif
+
+       cmd->start_time = jiffies;
+}
+
+/* io */
+static void ssd_queue_bio(struct ssd_device *dev, struct bio *bio)
+{
+       spin_lock(&dev->sendq_lock);
+       ssd_blist_add(&dev->sendq, bio);
+       spin_unlock(&dev->sendq_lock);
+
+       atomic_inc(&dev->in_sendq);
+       wake_up(&dev->send_waitq);
+}
+
+static inline void ssd_end_request(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = cmd->dev;
+       struct bio *bio = cmd->bio;
+       int errors = cmd->errors;
+       int tag = cmd->tag;
+
+       if (bio) {
+#if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)))
+               if (!(bio->bi_rw & REQ_DISCARD)) {
+                       ssd_end_io_acct(cmd);
+                       if (!cmd->flag) {
+                               pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, 
+                                       bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+                       }
+               }
+#elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)))
+               if (!bio_rw_flagged(bio, BIO_RW_DISCARD)) {
+                       ssd_end_io_acct(cmd);
+                       if (!cmd->flag) {
+                               pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, 
+                                       bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+                       }
+               }
+#else
+               ssd_end_io_acct(cmd);
+
+               if (!cmd->flag) {
+                       pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, 
+                               bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+               }
+#endif
+
+               cmd->bio = NULL;
+               ssd_put_tag(dev, tag);
+
+               if (SSD_INT_MSIX == dev->int_mode || tag < 16 || errors) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+                       bio_endio(bio, errors);
+#else
+                       bio_endio(bio, bio->bi_size, errors);
+#endif
+               } else /* if (bio->bi_idx >= bio->bi_vcnt)*/ {
+                       spin_lock(&dev->doneq_lock);
+                       ssd_blist_add(&dev->doneq, bio);
+                       spin_unlock(&dev->doneq_lock);
+
+                       atomic_inc(&dev->in_doneq);
+                       wake_up(&dev->done_waitq);
+               }
+       } else {
+               if (cmd->waiting) {
+                       complete(cmd->waiting);
+               }
+       }
+}
+
+static void ssd_end_timeout_request(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = cmd->dev;
+       struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg;
+       int i;
+
+       for (i=0; i<dev->nr_queue; i++) {
+               disable_irq(dev->entry[i].vector);
+       }
+
+       atomic_inc(&dev->tocnt);
+       //if (cmd->bio) {
+               hio_err("%s: cmd timeout: tag %d fun %#x\n", dev->name, msg->tag, msg->fun);
+               cmd->errors = -ETIMEDOUT;
+               ssd_end_request(cmd);
+       //}
+
+       for (i=0; i<dev->nr_queue; i++) {
+               enable_irq(dev->entry[i].vector);
+       }
+
+       /* alarm led */
+       ssd_set_alarm(dev);
+}
+
+/* cmd timer */
+static void ssd_cmd_add_timer(struct ssd_cmd *cmd, int timeout, void (*complt)(struct ssd_cmd *))
+{
+       init_timer(&cmd->cmd_timer);
+
+       cmd->cmd_timer.data = (unsigned long)cmd;
+       cmd->cmd_timer.expires = jiffies + timeout;
+       cmd->cmd_timer.function = (void (*)(unsigned long)) complt;
+
+       add_timer(&cmd->cmd_timer);
+}
+
+static int ssd_cmd_del_timer(struct ssd_cmd *cmd)
+{
+       return del_timer(&cmd->cmd_timer);
+}
+
+static void ssd_add_timer(struct timer_list *timer, int timeout, void (*complt)(void *), void *data)
+{
+       init_timer(timer);
+
+       timer->data = (unsigned long)data;
+       timer->expires = jiffies + timeout;
+       timer->function = (void (*)(unsigned long)) complt;
+
+       add_timer(timer);
+}
+
+static int ssd_del_timer(struct timer_list *timer)
+{
+       return del_timer(timer);
+}
+
+static void ssd_cmd_timeout(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = cmd->dev;
+       uint32_t msg = *(uint32_t *)cmd->msg;
+
+       ssd_end_timeout_request(cmd);
+
+       ssd_gen_swlog(dev, SSD_LOG_TIMEOUT, msg);
+}
+
+
+static void __ssd_done(unsigned long data)
+{
+       struct ssd_cmd *cmd;
+       LIST_HEAD(localq);
+
+       local_irq_disable();
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
+       list_splice_init(&__get_cpu_var(ssd_doneq), &localq);
+#else
+       list_splice_init(this_cpu_ptr(&ssd_doneq), &localq);
+#endif
+       local_irq_enable();
+
+       while (!list_empty(&localq)) {
+               cmd = list_entry(localq.next, struct ssd_cmd, list);
+               list_del_init(&cmd->list);
+
+               ssd_end_request(cmd);
+       }
+}
+
+static void __ssd_done_db(unsigned long data)
+{
+       struct ssd_cmd *cmd;
+       struct ssd_device *dev;
+       struct bio *bio;
+       LIST_HEAD(localq);
+
+       local_irq_disable();
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
+       list_splice_init(&__get_cpu_var(ssd_doneq), &localq);
+#else
+       list_splice_init(this_cpu_ptr(&ssd_doneq), &localq);
+#endif
+       local_irq_enable();
+
+       while (!list_empty(&localq)) {
+               cmd = list_entry(localq.next, struct ssd_cmd, list);
+               list_del_init(&cmd->list);
+
+               dev = (struct ssd_device *)cmd->dev;
+               bio = cmd->bio;
+
+               if (bio) {
+                       sector_t off = dev->db_info.data.loc.off;
+                       uint32_t len = dev->db_info.data.loc.len;
+
+                       switch (dev->db_info.type) {
+                               case SSD_DEBUG_READ_ERR:
+                                       if (bio_data_dir(bio) == READ && 
+                                               !((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) {
+                                               cmd->errors = -EIO;
+                                       }
+                                       break;
+                               case SSD_DEBUG_WRITE_ERR:
+                                       if (bio_data_dir(bio) == WRITE && 
+                                               !((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) {
+                                               cmd->errors = -EROFS;
+                                       }
+                                       break;
+                               case SSD_DEBUG_RW_ERR:
+                                       if (!((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) {
+                                               if (bio_data_dir(bio) == READ) {
+                                                       cmd->errors = -EIO;
+                                               } else {
+                                                       cmd->errors = -EROFS;
+                                               }
+                                       }
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+
+               ssd_end_request(cmd);
+       }
+}
+
+static inline void ssd_done_bh(struct ssd_cmd *cmd)
+{
+       unsigned long flags = 0;
+
+       if (unlikely(!ssd_cmd_del_timer(cmd))) {
+               struct ssd_device *dev = cmd->dev;
+               struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg;
+               hio_err("%s: unknown cmd: tag %d fun %#x\n", dev->name, msg->tag, msg->fun);
+
+               /* alarm led */
+               ssd_set_alarm(dev);
+               return;
+       }
+
+       local_irq_save(flags);
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
+       list_add_tail(&cmd->list, &__get_cpu_var(ssd_doneq));
+       tasklet_hi_schedule(&__get_cpu_var(ssd_tasklet));
+#else
+       list_add_tail(&cmd->list, this_cpu_ptr(&ssd_doneq));
+       tasklet_hi_schedule(this_cpu_ptr(&ssd_tasklet));
+#endif
+       local_irq_restore(flags);
+
+       return;
+}
+
+static inline void ssd_done(struct ssd_cmd *cmd)
+{
+       if (unlikely(!ssd_cmd_del_timer(cmd))) {
+               struct ssd_device *dev = cmd->dev;
+               struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg;
+               hio_err("%s: unknown cmd: tag %d fun %#x\n", dev->name, msg->tag, msg->fun);
+
+               /* alarm led */
+               ssd_set_alarm(dev);
+               return;
+       }
+
+       ssd_end_request(cmd);
+
+       return;
+}
+
+static inline void ssd_dispatch_cmd(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = (struct ssd_device *)cmd->dev;
+
+       ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout);
+
+       spin_lock(&dev->cmd_lock);
+       ssd_reg_write(dev->ctrlp + SSD_REQ_FIFO_REG, cmd->msg_dma);
+       spin_unlock(&dev->cmd_lock);
+}
+
+static inline void ssd_send_cmd(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = (struct ssd_device *)cmd->dev;
+
+       ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout);
+
+       ssd_reg32_write(dev->ctrlp + SSD_REQ_FIFO_REG, ((uint32_t)cmd->tag | ((uint32_t)cmd->nsegs << 16)));
+}
+
+static inline void ssd_send_cmd_db(struct ssd_cmd *cmd)
+{
+       struct ssd_device *dev = (struct ssd_device *)cmd->dev;
+       struct bio *bio = cmd->bio;
+
+       ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout);
+
+       if (bio) {
+               switch (dev->db_info.type) {
+                       case SSD_DEBUG_READ_TO:
+                               if (bio_data_dir(bio) == READ) {
+                                       return;
+                               }
+                               break;
+                       case SSD_DEBUG_WRITE_TO:
+                               if (bio_data_dir(bio) == WRITE) {
+                                       return;
+                               }
+                               break;
+                       case SSD_DEBUG_RW_TO:
+                               return;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       ssd_reg32_write(dev->ctrlp + SSD_REQ_FIFO_REG, ((uint32_t)cmd->tag | ((uint32_t)cmd->nsegs << 16)));
+}
+
+
+/* fixed for BIOVEC_PHYS_MERGEABLE */
+#ifdef SSD_BIOVEC_PHYS_MERGEABLE_FIXED
+#include <linux/bio.h>
+#include <linux/io.h>
+#include <xen/page.h>
+
+static bool xen_biovec_phys_mergeable_fixed(const struct bio_vec *vec1,
+                              const struct bio_vec *vec2)
+{
+       unsigned long mfn1 = pfn_to_mfn(page_to_pfn(vec1->bv_page));
+       unsigned long mfn2 = pfn_to_mfn(page_to_pfn(vec2->bv_page));
+
+       return __BIOVEC_PHYS_MERGEABLE(vec1, vec2) &&
+               ((mfn1 == mfn2) || ((mfn1+1) == mfn2));
+}
+
+#ifdef BIOVEC_PHYS_MERGEABLE
+#undef BIOVEC_PHYS_MERGEABLE
+#endif
+#define BIOVEC_PHYS_MERGEABLE(vec1, vec2)                              \
+       (__BIOVEC_PHYS_MERGEABLE(vec1, vec2) &&                         \
+        (!xen_domain() || xen_biovec_phys_mergeable_fixed(vec1, vec2)))
+
+#endif
+
+static inline int ssd_bio_map_sg(struct ssd_device *dev, struct bio *bio, struct scatterlist *sgl)
+{
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
+       struct bio_vec *bvec, *bvprv = NULL;
+       struct scatterlist *sg = NULL;
+       int i = 0, nsegs = 0;
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23))
+       sg_init_table(sgl, dev->hw_info.cmd_max_sg);
+#endif
+
+       /*
+       * for each segment in bio
+       */
+       bio_for_each_segment(bvec, bio, i) {
+               if (bvprv && BIOVEC_PHYS_MERGEABLE(bvprv, bvec)) {
+                       sg->length += bvec->bv_len;
+               } else {
+                       if (unlikely(nsegs >= (int)dev->hw_info.cmd_max_sg)) {
+                               break;
+                       }
+
+                       sg = sg ? (sg + 1) : sgl;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+                       sg_set_page(sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset);
+#else
+                       sg->page = bvec->bv_page;
+                       sg->length = bvec->bv_len;
+                       sg->offset = bvec->bv_offset;
+#endif
+                       nsegs++;
+               }
+               bvprv = bvec;
+       }
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+       if (sg) {
+               sg_mark_end(sg);
+       }
+#endif
+
+       bio->bi_idx = i;
+
+       return nsegs;
+#else
+       struct bio_vec bvec, bvprv;
+       struct bvec_iter iter;
+       struct scatterlist *sg = NULL;
+       int nsegs = 0;
+       int first = 1;
+
+       sg_init_table(sgl, dev->hw_info.cmd_max_sg);
+
+       /*
+       * for each segment in bio
+       */
+       bio_for_each_segment(bvec, bio, iter) {
+               if (!first && BIOVEC_PHYS_MERGEABLE(&bvprv, &bvec)) {
+                       sg->length += bvec.bv_len;
+               } else {
+                       if (unlikely(nsegs >= (int)dev->hw_info.cmd_max_sg)) {
+                               break;
+                       }
+
+                       sg = sg ? (sg + 1) : sgl;
+
+                       sg_set_page(sg, bvec.bv_page, bvec.bv_len, bvec.bv_offset);
+
+                       nsegs++;
+                       first = 0;
+               }
+               bvprv = bvec;
+       }
+
+       if (sg) {
+               sg_mark_end(sg);
+       }
+
+       return nsegs;
+#endif
+}
+
+
+static int __ssd_submit_pbio(struct ssd_device *dev, struct bio *bio, int wait)
+{
+       struct ssd_cmd *cmd;
+       struct ssd_rw_msg *msg;
+       struct ssd_sg_entry *sge;
+       sector_t block = bio_start(bio);
+       int tag;
+       int i;
+
+       tag = ssd_get_tag(dev, wait);
+       if (tag < 0) {
+               return -EBUSY;
+       }
+
+       cmd = &dev->cmd[tag];
+       cmd->bio = bio;
+       cmd->flag = 1;
+
+       msg = (struct ssd_rw_msg *)cmd->msg;
+
+#if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)))
+       if (bio->bi_rw & REQ_DISCARD) {
+               unsigned int length = bio_sectors(bio);
+
+               //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block);
+               msg->tag = tag;
+               msg->fun = SSD_FUNC_TRIM;
+
+               sge = msg->sge;
+               for (i=0; i<(dev->hw_info.cmd_max_sg); i++) {
+                       sge->block = block;
+                       sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length;
+                       sge->buf = 0;
+
+                       block += sge->length;
+                       length -= sge->length;
+                       sge++;
+
+                       if (length <= 0) {
+                               break;
+                       }
+               }
+               msg->nsegs = cmd->nsegs = (i + 1);
+
+               dev->scmd(cmd);
+               return 0;
+       }
+#elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)))
+       if (bio_rw_flagged(bio, BIO_RW_DISCARD)) {
+               unsigned int length = bio_sectors(bio);
+
+               //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block);
+               msg->tag = tag;
+               msg->fun = SSD_FUNC_TRIM;
+
+               sge = msg->sge;
+               for (i=0; i<(dev->hw_info.cmd_max_sg); i++) {
+                       sge->block = block;
+                       sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length;
+                       sge->buf = 0;
+
+                       block += sge->length;
+                       length -= sge->length;
+                       sge++;
+
+                       if (length <= 0) {
+                               break;
+                       }
+               }
+               msg->nsegs = cmd->nsegs = (i + 1);
+
+               dev->scmd(cmd);
+               return 0;
+       }
+#endif
+
+       //msg->nsegs = cmd->nsegs = ssd_bio_map_sg(dev, bio, sgl);
+       msg->nsegs = cmd->nsegs = bio->bi_vcnt;
+
+       //xx
+       if (bio_data_dir(bio) == READ) {
+               msg->fun = SSD_FUNC_READ;
+               msg->flag = 0;
+       } else {
+               msg->fun = SSD_FUNC_WRITE;
+               msg->flag = dev->wmode;
+       }
+
+       sge = msg->sge;
+       for (i=0; i<bio->bi_vcnt; i++) {
+               sge->block = block;
+               sge->length = bio->bi_io_vec[i].bv_len >> 9;
+               sge->buf = (uint64_t)((void *)bio->bi_io_vec[i].bv_page + bio->bi_io_vec[i].bv_offset);
+
+               block += sge->length;
+               sge++;
+       }
+
+       msg->tag = tag;
+
+#ifdef SSD_OT_PROTECT
+       if (unlikely(dev->ot_delay > 0 && dev->ot_protect != 0)) {
+               msleep_interruptible(dev->ot_delay);
+       }
+#endif
+
+       ssd_start_io_acct(cmd);
+       dev->scmd(cmd);
+
+       return 0;
+}
+
+static inline int ssd_submit_bio(struct ssd_device *dev, struct bio *bio, int wait)
+{
+       struct ssd_cmd *cmd;
+       struct ssd_rw_msg *msg;
+       struct ssd_sg_entry *sge;
+       struct scatterlist *sgl;
+       sector_t block = bio_start(bio);
+       int tag;
+       int i;
+
+       tag = ssd_get_tag(dev, wait);
+       if (tag < 0) {
+               return -EBUSY;
+       }
+
+       cmd = &dev->cmd[tag];
+       cmd->bio = bio;
+       cmd->flag = 0;
+
+       msg = (struct ssd_rw_msg *)cmd->msg;
+
+       sgl = cmd->sgl;
+
+#if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)))
+       if (bio->bi_rw & REQ_DISCARD) {
+               unsigned int length = bio_sectors(bio);
+
+               //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block);
+               msg->tag = tag;
+               msg->fun = SSD_FUNC_TRIM;
+
+               sge = msg->sge;
+               for (i=0; i<(dev->hw_info.cmd_max_sg); i++) {
+                       sge->block = block;
+                       sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length;
+                       sge->buf = 0;
+
+                       block += sge->length;
+                       length -= sge->length;
+                       sge++;
+
+                       if (length <= 0) {
+                               break;
+                       }
+               }
+               msg->nsegs = cmd->nsegs = (i + 1);
+
+               dev->scmd(cmd);
+               return 0;
+       }
+#elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)))
+       if (bio_rw_flagged(bio, BIO_RW_DISCARD)) {
+               unsigned int length = bio_sectors(bio);
+
+               //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block);
+               msg->tag = tag;
+               msg->fun = SSD_FUNC_TRIM;
+
+               sge = msg->sge;
+               for (i=0; i<(dev->hw_info.cmd_max_sg); i++) {
+                       sge->block = block;
+                       sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length;
+                       sge->buf = 0;
+
+                       block += sge->length;
+                       length -= sge->length;
+                       sge++;
+
+                       if (length <= 0) {
+                               break;
+                       }
+               }
+               msg->nsegs = cmd->nsegs = (i + 1);
+
+               dev->scmd(cmd);
+               return 0;
+       }
+#endif
+
+       msg->nsegs = cmd->nsegs = ssd_bio_map_sg(dev, bio, sgl);
+
+       //xx
+       if (bio_data_dir(bio) == READ) {
+               msg->fun = SSD_FUNC_READ;
+               msg->flag = 0;
+               pci_map_sg(dev->pdev, sgl, cmd->nsegs, PCI_DMA_FROMDEVICE);
+       } else {
+               msg->fun = SSD_FUNC_WRITE;
+               msg->flag = dev->wmode;
+               pci_map_sg(dev->pdev, sgl, cmd->nsegs, PCI_DMA_TODEVICE);
+       }
+
+       sge = msg->sge;
+       for (i=0; i<cmd->nsegs; i++) {
+               sge->block = block;
+               sge->length = sg_dma_len(sgl) >> 9;
+               sge->buf = sg_dma_address(sgl);
+
+               block += sge->length;
+               sgl++;
+               sge++;
+       }
+
+       msg->tag = tag;
+
+#ifdef SSD_OT_PROTECT
+       if (unlikely(dev->ot_delay > 0 && dev->ot_protect != 0)) {
+               msleep_interruptible(dev->ot_delay);
+       }
+#endif
+
+       ssd_start_io_acct(cmd);
+       dev->scmd(cmd);
+
+       return 0;
+}
+
+/* threads */
+static int ssd_done_thread(void *data)
+{
+       struct ssd_device *dev;
+       struct bio *bio;
+       struct bio *next;
+#ifdef SSD_ESCAPE_IRQ
+       cpumask_t new_mask;
+#endif
+
+       if (!data) {
+               return -EINVAL;
+       }
+       dev = data;
+
+       //set_user_nice(current, -5);
+
+       while (!kthread_should_stop()) {
+               wait_event_interruptible(dev->done_waitq, (atomic_read(&dev->in_doneq) || kthread_should_stop()));
+
+               while (atomic_read(&dev->in_doneq)) {
+                       if (threaded_irq) {
+                               spin_lock(&dev->doneq_lock);
+                               bio = ssd_blist_get(&dev->doneq);
+                               spin_unlock(&dev->doneq_lock);
+                       } else {
+                               spin_lock_irq(&dev->doneq_lock);
+                               bio = ssd_blist_get(&dev->doneq);
+                               spin_unlock_irq(&dev->doneq_lock);
+                       }
+
+                       while (bio) {
+                               next = bio->bi_next;
+                               bio->bi_next = NULL;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+                               bio_endio(bio, 0);
+#else
+                               bio_endio(bio, bio->bi_size, 0);
+#endif
+                               atomic_dec(&dev->in_doneq);
+                               bio = next;
+                       }
+
+                       cond_resched();
+
+#ifdef SSD_ESCAPE_IRQ
+                       if (unlikely(smp_processor_id() == dev->irq_cpu)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28))
+                               cpumask_setall(&new_mask);
+                               cpumask_clear_cpu(dev->irq_cpu, &new_mask);
+                               set_cpus_allowed_ptr(current, &new_mask);
+#else
+                               cpus_setall(new_mask);
+                               cpu_clear(dev->irq_cpu, new_mask);
+                               set_cpus_allowed(current, new_mask);
+#endif
+                       }
+#endif
+               }
+       }
+       return 0;
+}
+
+static int ssd_send_thread(void *data)
+{
+       struct ssd_device *dev;
+       struct bio *bio;
+       struct bio *next;
+#ifdef SSD_ESCAPE_IRQ
+       cpumask_t new_mask;
+#endif
+
+       if (!data) {
+               return -EINVAL;
+       }
+       dev = data;
+
+       //set_user_nice(current, -5);
+
+       while (!kthread_should_stop()) {
+               wait_event_interruptible(dev->send_waitq, (atomic_read(&dev->in_sendq) || kthread_should_stop()));
+
+               while (atomic_read(&dev->in_sendq)) {
+                       spin_lock(&dev->sendq_lock);
+                       bio = ssd_blist_get(&dev->sendq);
+                       spin_unlock(&dev->sendq_lock);
+
+                       while (bio) {
+                               next = bio->bi_next;
+                               bio->bi_next = NULL;
+#ifdef SSD_QUEUE_PBIO
+                               if (test_and_clear_bit(BIO_SSD_PBIO, &bio->bi_flags)) {
+                                       __ssd_submit_pbio(dev, bio, 1);
+                               } else {
+                                       ssd_submit_bio(dev, bio, 1);
+                               }
+#else
+                               ssd_submit_bio(dev, bio, 1);
+#endif
+                               atomic_dec(&dev->in_sendq);
+                               bio = next;
+                       }
+
+                       cond_resched();
+
+#ifdef SSD_ESCAPE_IRQ
+                       if (unlikely(smp_processor_id() == dev->irq_cpu)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28))
+                               cpumask_setall(&new_mask);
+                               cpumask_clear_cpu(dev->irq_cpu, &new_mask);
+                               set_cpus_allowed_ptr(current, &new_mask);
+#else
+                               cpus_setall(new_mask);
+                               cpu_clear(dev->irq_cpu, new_mask);
+                               set_cpus_allowed(current, new_mask);
+#endif
+                       }
+#endif
+               }
+       }
+
+       return 0;
+}
+
+static void ssd_cleanup_thread(struct ssd_device *dev)
+{
+       kthread_stop(dev->send_thread);
+       kthread_stop(dev->done_thread);
+}
+
+static int ssd_init_thread(struct ssd_device *dev)
+{
+       int ret;
+
+       atomic_set(&dev->in_doneq, 0);
+       atomic_set(&dev->in_sendq, 0);
+
+       spin_lock_init(&dev->doneq_lock);
+       spin_lock_init(&dev->sendq_lock);
+
+       ssd_blist_init(&dev->doneq);
+       ssd_blist_init(&dev->sendq);
+
+       init_waitqueue_head(&dev->done_waitq);
+       init_waitqueue_head(&dev->send_waitq);
+
+       dev->done_thread = kthread_run(ssd_done_thread, dev, "%s/d", dev->name);
+       if (IS_ERR(dev->done_thread)) {
+               ret = PTR_ERR(dev->done_thread);
+               goto out_done_thread;
+       }
+
+       dev->send_thread = kthread_run(ssd_send_thread, dev, "%s/s", dev->name);
+       if (IS_ERR(dev->send_thread)) {
+               ret = PTR_ERR(dev->send_thread);
+               goto out_send_thread;
+       }
+
+       return 0;
+
+out_send_thread:
+       kthread_stop(dev->done_thread);
+out_done_thread:
+       return ret;
+}
+
+/* dcmd pool */
+static void ssd_put_dcmd(struct ssd_dcmd *dcmd)
+{
+       struct ssd_device *dev = (struct ssd_device *)dcmd->dev;
+
+       spin_lock(&dev->dcmd_lock);
+       list_add_tail(&dcmd->list, &dev->dcmd_list);
+       spin_unlock(&dev->dcmd_lock);
+}
+
+static struct ssd_dcmd *ssd_get_dcmd(struct ssd_device *dev)
+{
+       struct ssd_dcmd *dcmd = NULL;
+
+       spin_lock(&dev->dcmd_lock);
+       if (!list_empty(&dev->dcmd_list)) {
+               dcmd = list_entry(dev->dcmd_list.next, 
+                                struct ssd_dcmd, list);
+               list_del_init(&dcmd->list);
+       }
+       spin_unlock(&dev->dcmd_lock);
+
+       return dcmd;
+}
+
+static void ssd_cleanup_dcmd(struct ssd_device *dev)
+{
+       kfree(dev->dcmd);
+}
+
+static int ssd_init_dcmd(struct ssd_device *dev)
+{
+       struct ssd_dcmd *dcmd;
+       int dcmd_sz = sizeof(struct ssd_dcmd)*dev->hw_info.cmd_fifo_sz;
+       int i;
+
+       spin_lock_init(&dev->dcmd_lock);
+       INIT_LIST_HEAD(&dev->dcmd_list);
+       init_waitqueue_head(&dev->dcmd_wq);
+
+       dev->dcmd = kmalloc(dcmd_sz, GFP_KERNEL);
+       if (!dev->dcmd) {
+               hio_warn("%s: can not alloc dcmd\n", dev->name);
+               goto out_alloc_dcmd;
+       }
+       memset(dev->dcmd, 0, dcmd_sz);
+
+       for (i=0, dcmd=dev->dcmd; i<(int)dev->hw_info.cmd_fifo_sz; i++, dcmd++) {
+               dcmd->dev = dev;
+               INIT_LIST_HEAD(&dcmd->list);
+               list_add_tail(&dcmd->list, &dev->dcmd_list);
+       }
+
+       return 0;
+
+out_alloc_dcmd:
+       return -ENOMEM;
+}
+
+static void ssd_put_dmsg(void *msg)
+{
+       struct ssd_dcmd *dcmd = container_of(msg, struct ssd_dcmd, msg);
+       struct ssd_device *dev = (struct ssd_device *)dcmd->dev;
+
+       memset(dcmd->msg, 0, SSD_DCMD_MAX_SZ);
+       ssd_put_dcmd(dcmd);
+       wake_up(&dev->dcmd_wq);
+}
+
+static void *ssd_get_dmsg(struct ssd_device *dev)
+{
+       struct ssd_dcmd *dcmd = ssd_get_dcmd(dev);
+
+       while (!dcmd) {
+               DEFINE_WAIT(wait);
+               prepare_to_wait_exclusive(&dev->dcmd_wq, &wait, TASK_UNINTERRUPTIBLE);
+               schedule();
+
+               dcmd = ssd_get_dcmd(dev);
+
+               finish_wait(&dev->dcmd_wq, &wait);
+       }
+       return dcmd->msg;
+}
+
+/* do direct cmd */
+static int ssd_do_request(struct ssd_device *dev, int rw, void *msg, int *done)
+{
+       DECLARE_COMPLETION(wait);
+       struct ssd_cmd *cmd;
+       int tag;
+       int ret = 0;
+
+       tag = ssd_get_tag(dev, 1);
+       if (tag < 0) {
+               return -EBUSY;
+       }
+
+       cmd = &dev->cmd[tag];
+       cmd->nsegs = 1;
+       memcpy(cmd->msg, msg, SSD_DCMD_MAX_SZ);
+       ((struct ssd_rw_msg *)cmd->msg)->tag = tag;
+
+       cmd->waiting = &wait;
+
+       dev->scmd(cmd);
+
+       wait_for_completion(cmd->waiting);
+       cmd->waiting = NULL;
+
+       if (cmd->errors == -ETIMEDOUT) {
+               ret = cmd->errors;
+       } else if (cmd->errors) {
+               ret = -EIO;
+       }
+
+       if (done != NULL) {
+               *done = cmd->nr_log;
+       }
+       ssd_put_tag(dev, cmd->tag);
+
+       return ret;
+}
+
+static int ssd_do_barrier_request(struct ssd_device *dev, int rw, void *msg, int *done)
+{
+       DECLARE_COMPLETION(wait);
+       struct ssd_cmd *cmd;
+       int tag;
+       int ret = 0;
+
+       tag = ssd_barrier_get_tag(dev);
+       if (tag < 0) {
+               return -EBUSY;
+       }
+
+       cmd = &dev->cmd[tag];
+       cmd->nsegs = 1;
+       memcpy(cmd->msg, msg, SSD_DCMD_MAX_SZ);
+       ((struct ssd_rw_msg *)cmd->msg)->tag = tag;
+
+       cmd->waiting = &wait;
+
+       dev->scmd(cmd);
+
+       wait_for_completion(cmd->waiting);
+       cmd->waiting = NULL;
+
+       if (cmd->errors == -ETIMEDOUT) {
+               ret = cmd->errors;
+       } else if (cmd->errors) {
+               ret = -EIO;
+       }
+
+       if (done != NULL) {
+               *done = cmd->nr_log;
+       }
+       ssd_barrier_put_tag(dev, cmd->tag);
+
+       return ret;
+}
+
+#ifdef SSD_OT_PROTECT
+static void ssd_check_temperature(struct ssd_device *dev, int temp)
+{
+       uint64_t val;
+       uint32_t off;
+       int cur;
+       int i;
+
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               return;
+       }
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+       }
+
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               off = SSD_CTRL_TEMP_REG0 + i * sizeof(uint64_t);
+
+               val = ssd_reg_read(dev->ctrlp + off);
+               if (val == 0xffffffffffffffffull) {
+                       continue;
+               }
+
+               cur = (int)CUR_TEMP(val);
+               if (cur >= temp) {
+                       if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) {
+                               if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) {
+                                       hio_warn("%s: Over temperature, please check the fans.\n", dev->name);
+                                       dev->ot_delay = SSD_OT_DELAY;
+                               }
+                       }
+                       return;
+               }
+       }
+
+       if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) {
+               if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) {
+                       hio_warn("%s: Temperature is OK.\n", dev->name);
+                       dev->ot_delay = 0;
+               }
+       }
+}
+#endif
+
+static int ssd_get_ot_status(struct ssd_device *dev, int *status)
+{
+       uint32_t off;
+       uint32_t val;
+       int i;
+
+       if (!dev || !status) {
+               return -EINVAL;
+       }
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_2) {
+               for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+                       off = SSD_READ_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ);
+                       val = ssd_reg32_read(dev->ctrlp + off);
+                       if ((val >> 22) & 0x1) {
+                               *status = 1;
+                               goto out;
+                       }
+
+                       
+                       off = SSD_WRITE_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ);
+                       val = ssd_reg32_read(dev->ctrlp + off);
+                       if ((val >> 22) & 0x1) {
+                               *status = 1;
+                               goto out;
+                       }
+               }
+       } else {
+               *status = !!dev->ot_delay;
+       }
+
+out:
+       return 0;
+}
+
+static void ssd_set_ot_protect(struct ssd_device *dev, int protect)
+{
+       uint32_t off;
+       uint32_t val;
+       int i;
+       
+       mutex_lock(&dev->fw_mutex);
+
+       dev->ot_protect = !!protect;
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_2) {
+               for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+                       off = SSD_READ_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ);
+                       val = ssd_reg32_read(dev->ctrlp + off);
+                       if (dev->ot_protect) {
+                               val |= (1U << 21);
+                       } else {
+                               val &= ~(1U << 21);
+                       }
+                       ssd_reg32_write(dev->ctrlp + off, val);
+
+                       
+                       off = SSD_WRITE_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ);
+                       val = ssd_reg32_read(dev->ctrlp + off);
+                       if (dev->ot_protect) {
+                               val |= (1U << 21);
+                       } else {
+                               val &= ~(1U << 21);
+                       }
+                       ssd_reg32_write(dev->ctrlp + off, val);
+               }
+       }
+
+       mutex_unlock(&dev->fw_mutex);
+}
+
+static int ssd_init_ot_protect(struct ssd_device *dev)
+{
+       ssd_set_ot_protect(dev, ot_protect);
+
+#ifdef SSD_OT_PROTECT
+       ssd_check_temperature(dev, SSD_OT_TEMP);
+#endif
+
+       return 0;
+}
+
+/* log */
+static int ssd_read_log(struct ssd_device *dev, int ctrl_idx, void *buf, int *nr_log)
+{
+       struct ssd_log_op_msg *msg;
+       struct ssd_log_msg *lmsg;
+       dma_addr_t buf_dma;
+       size_t length = dev->hw_info.log_sz;
+       int ret = 0;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl) {
+               return -EINVAL;
+       }
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map read DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       msg = (struct ssd_log_op_msg *)ssd_get_dmsg(dev);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               lmsg = (struct ssd_log_msg *)msg;
+               lmsg->fun = SSD_FUNC_READ_LOG;
+               lmsg->ctrl_idx = ctrl_idx;
+               lmsg->buf = buf_dma;
+       } else {
+               msg->fun = SSD_FUNC_READ_LOG;
+               msg->ctrl_idx = ctrl_idx;
+               msg->buf = buf_dma;
+       }
+
+       ret = ssd_do_request(dev, READ, msg, nr_log);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE);
+
+out_dma_mapping:
+        return ret;
+}
+
+#define SSD_LOG_PRINT_BUF_SZ   256
+static int ssd_parse_log(struct ssd_device *dev, struct ssd_log *log, int print)
+{
+       struct ssd_log_desc *log_desc = ssd_log_desc;
+       struct ssd_log_entry *le;
+       char *sn = NULL;
+       char print_buf[SSD_LOG_PRINT_BUF_SZ];
+       int print_len;
+
+       le = &log->le;
+
+       /* find desc */
+       while (log_desc->event != SSD_UNKNOWN_EVENT) {
+               if (log_desc->event == le->event) {
+                       break;
+               }
+               log_desc++;
+       }
+
+       if (!print) {
+               goto out;
+       }
+
+       if (log_desc->level < log_level) {
+               goto out;
+       }
+
+       /* parse */
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               sn = dev->label.sn;
+       } else {
+               sn = dev->labelv3.barcode;
+       }
+
+       print_len = snprintf(print_buf, SSD_LOG_PRINT_BUF_SZ, "%s (%s): <%#x>", dev->name, sn, le->event);
+
+       if (log->ctrl_idx != SSD_LOG_SW_IDX) {
+               print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " controller %d", log->ctrl_idx);
+       }
+
+       switch (log_desc->data) {
+               case SSD_LOG_DATA_NONE:
+                       break;
+               case SSD_LOG_DATA_LOC:
+                       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                               print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " flash %d", le->data.loc.flash);
+                               if (log_desc->sblock) {
+                                       print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " block %d", le->data.loc.block);
+                               }
+                               if (log_desc->spage) {
+                                       print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " page %d", le->data.loc.page);
+                               }
+                       } else {
+                               print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " flash %d", le->data.loc1.flash);
+                               if (log_desc->sblock) {
+                                       print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " block %d", le->data.loc1.block);
+                               }
+                               if (log_desc->spage) {
+                                       print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " page %d", le->data.loc1.page);
+                               }
+                       }
+                       break;
+               case SSD_LOG_DATA_HEX: 
+                       print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " info %#x", le->data.val);
+                       break;
+               default:
+                       break;
+       }
+       /*print_len += */snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), ": %s", log_desc->desc);
+
+       switch (log_desc->level) {
+               case SSD_LOG_LEVEL_INFO:
+                       hio_info("%s\n", print_buf);
+                       break;
+               case SSD_LOG_LEVEL_NOTICE:
+                       hio_note("%s\n", print_buf);
+                       break;
+               case SSD_LOG_LEVEL_WARNING:
+                       hio_warn("%s\n", print_buf);
+                       break;
+               case SSD_LOG_LEVEL_ERR:
+                       hio_err("%s\n", print_buf);
+                       //printk(KERN_ERR MODULE_NAME": some exception occurred, please check the data or refer to FAQ.");
+                       break;
+               default:
+                       hio_warn("%s\n", print_buf);
+                       break;
+       }
+
+out:
+       return log_desc->level;
+}
+
+static int ssd_bm_get_sfstatus(struct ssd_device *dev, uint16_t *status);
+static int ssd_switch_wmode(struct ssd_device *dev, int wmode);
+
+
+static int ssd_handle_event(struct ssd_device *dev, uint16_t event, int level)
+{
+       int ret = 0;
+
+       switch (event) {
+               case SSD_LOG_OVER_TEMP: {
+#ifdef SSD_OT_PROTECT
+                       if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) {
+                               if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) {
+                                       hio_warn("%s: Over temperature, please check the fans.\n", dev->name);
+                                       dev->ot_delay = SSD_OT_DELAY;
+                               }
+                       }
+#endif
+                       break;
+               }
+
+               case SSD_LOG_NORMAL_TEMP: {
+#ifdef SSD_OT_PROTECT
+                       /* need to check all controller's temperature */
+                       ssd_check_temperature(dev, SSD_OT_TEMP_HYST);
+#endif
+                       break;
+               }
+
+               case SSD_LOG_BATTERY_FAULT: {
+                       uint16_t sfstatus;
+
+                       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                               if (!ssd_bm_get_sfstatus(dev, &sfstatus)) {
+                                       ssd_gen_swlog(dev, SSD_LOG_BM_SFSTATUS, sfstatus);
+                               }
+                       }
+
+                       if (!test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                               ssd_switch_wmode(dev, dev->user_wmode); 
+                       }
+                       break;
+               }
+
+               case SSD_LOG_BATTERY_OK: {
+                       if (test_and_clear_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                               ssd_switch_wmode(dev, dev->user_wmode); 
+                       }
+                       break;
+               }
+
+               case SSD_LOG_BOARD_VOLT_FAULT: {
+                       ssd_mon_boardvolt(dev);
+                       break;
+               }
+
+               case SSD_LOG_CLEAR_LOG: {
+                       /* update smart */
+                       memset(&dev->smart.log_info, 0, sizeof(struct ssd_log_info));
+                       break;
+               }
+
+               case SSD_LOG_CAP_VOLT_FAULT: 
+               case SSD_LOG_CAP_LEARN_FAULT: 
+               case SSD_LOG_CAP_SHORT_CIRCUIT: {
+                       if (!test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                               ssd_switch_wmode(dev, dev->user_wmode); 
+                       }
+                       break;
+               }
+
+               default:
+                       break;
+       }
+
+       /* ssd event call */
+       if (dev->event_call) {
+               dev->event_call(dev->gd, event, level);
+
+               /* FIXME */
+               if (SSD_LOG_CAP_VOLT_FAULT == event || SSD_LOG_CAP_LEARN_FAULT == event || SSD_LOG_CAP_SHORT_CIRCUIT == event) {
+                       dev->event_call(dev->gd, SSD_LOG_BATTERY_FAULT, level);
+               }
+       }
+
+       return ret;
+}
+
+static int ssd_save_log(struct ssd_device *dev, struct ssd_log *log)
+{
+       uint32_t off, size;
+       void *internal_log;
+       int ret = 0;
+
+       mutex_lock(&dev->internal_log_mutex);
+
+       size = sizeof(struct ssd_log);
+       off = dev->internal_log.nr_log * size;
+
+       if (off == dev->rom_info.log_sz) {
+               if (dev->internal_log.nr_log == dev->smart.log_info.nr_log) {
+                       hio_warn("%s: internal log is full\n", dev->name);
+               }
+               goto out;
+       }
+
+       internal_log = dev->internal_log.log + off;
+       memcpy(internal_log, log, size);
+
+       if (dev->protocol_info.ver > SSD_PROTOCOL_V3) {
+               off += dev->rom_info.log_base;
+
+               ret = ssd_spi_write(dev, log, off, size);
+               if (ret) {
+                       goto out;
+               }
+       }
+
+       dev->internal_log.nr_log++;
+
+out:
+       mutex_unlock(&dev->internal_log_mutex);
+       return ret;
+}
+
+static int ssd_save_swlog(struct ssd_device *dev, uint16_t event, uint32_t data)
+{
+       struct ssd_log log;
+       struct timeval tv;
+       int level;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       memset(&log, 0, sizeof(struct ssd_log));
+
+       do_gettimeofday(&tv);
+       log.ctrl_idx = SSD_LOG_SW_IDX;
+       log.time = tv.tv_sec;
+       log.le.event = event;
+       log.le.data.val = data;
+
+       level = ssd_parse_log(dev, &log, 0);
+       if (level >= SSD_LOG_LEVEL) {
+               ret = ssd_save_log(dev, &log);
+       }
+
+       /* set alarm */
+       if (SSD_LOG_LEVEL_ERR == level) {
+               ssd_set_alarm(dev);
+       }
+
+       /* update smart */
+       dev->smart.log_info.nr_log++;
+       dev->smart.log_info.stat[level]++;
+
+       /* handle event */
+       ssd_handle_event(dev, event, level);
+
+       return ret;
+}
+
+static int ssd_gen_swlog(struct ssd_device *dev, uint16_t event, uint32_t data)
+{
+       struct ssd_log_entry le;
+       int ret;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       /* slave port ? */
+       if (dev->slave) {
+               return 0;
+       }
+
+       memset(&le, 0, sizeof(struct ssd_log_entry));
+       le.event = event;
+       le.data.val = data;
+
+       ret = sfifo_put(&dev->log_fifo, &le);
+       if (ret) {
+               return ret;
+       }
+
+       if (test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               queue_work(dev->workq, &dev->log_work);
+       }
+
+       return 0;
+}
+
+static int ssd_do_swlog(struct ssd_device *dev)
+{
+       struct ssd_log_entry le;
+       int ret = 0;
+
+       memset(&le, 0, sizeof(struct ssd_log_entry));
+       while (!sfifo_get(&dev->log_fifo, &le)) {
+               ret = ssd_save_swlog(dev, le.event, le.data.val);
+               if (ret) {
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static int __ssd_clear_log(struct ssd_device *dev)
+{
+       uint32_t off, length;
+       int ret;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       if (dev->internal_log.nr_log == 0) {
+               return 0;
+       }
+
+       mutex_lock(&dev->internal_log_mutex);
+
+       off = dev->rom_info.log_base;
+       length = dev->rom_info.log_sz;
+
+       ret = ssd_spi_erase(dev, off, length);
+       if (ret) {
+               hio_warn("%s: log erase: failed\n", dev->name);
+               goto out;
+       }
+
+       dev->internal_log.nr_log = 0;
+
+out:
+       mutex_unlock(&dev->internal_log_mutex);
+       return ret;
+}
+
+static int ssd_clear_log(struct ssd_device *dev)
+{
+       int ret;
+
+       ret = __ssd_clear_log(dev);
+       if(!ret) {
+               ssd_gen_swlog(dev, SSD_LOG_CLEAR_LOG, 0);
+       }
+
+       return ret;
+}
+
+static int ssd_do_log(struct ssd_device *dev, int ctrl_idx, void *buf)
+{
+       struct ssd_log_entry *le;
+       struct ssd_log log;
+       struct timeval tv;
+       int nr_log = 0;
+       int level;
+       int ret = 0;
+
+       ret = ssd_read_log(dev, ctrl_idx, buf, &nr_log);
+       if (ret) {
+               return ret;
+       }
+
+       do_gettimeofday(&tv);
+
+       log.time = tv.tv_sec;
+       log.ctrl_idx = ctrl_idx;
+
+       le = (ssd_log_entry_t *)buf;
+       while (nr_log > 0) {
+               memcpy(&log.le, le, sizeof(struct ssd_log_entry));
+
+               level = ssd_parse_log(dev, &log, 1);
+               if (level >= SSD_LOG_LEVEL) {
+                       ssd_save_log(dev, &log);
+               }
+
+               /* set alarm */
+               if (SSD_LOG_LEVEL_ERR == level) {
+                       ssd_set_alarm(dev);
+               }
+               
+               dev->smart.log_info.nr_log++;
+               if (SSD_LOG_SEU_FAULT != le->event && SSD_LOG_SEU_FAULT1 != le->event) {
+                       dev->smart.log_info.stat[level]++;
+               } else {
+                       /* SEU fault */
+
+                       /* log to the volatile log info */
+                       dev->log_info.nr_log++;
+                       dev->log_info.stat[level]++;
+
+                       /* do something */
+                       dev->reload_fw = 1;
+                       ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FLAG);
+
+                       /*dev->readonly = 1;
+                       set_disk_ro(dev->gd, 1);
+                       hio_warn("%s: switched to read-only mode.\n", dev->name);*/
+               }
+
+               /* handle event */
+               ssd_handle_event(dev, le->event, level);
+
+               le++;
+               nr_log--;
+       }
+
+       return 0;
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static void ssd_log_worker(void *data)
+{
+       struct ssd_device *dev = (struct ssd_device *)data;
+#else
+static void ssd_log_worker(struct work_struct *work)
+{
+       struct ssd_device *dev = container_of(work, struct ssd_device, log_work);
+#endif
+       int i;
+       int ret;
+
+       if (!test_bit(SSD_LOG_ERR, &dev->state) && test_bit(SSD_ONLINE, &dev->state)) {
+               /* alloc log buf */
+               if (!dev->log_buf) {
+                       dev->log_buf = kmalloc(dev->hw_info.log_sz, GFP_KERNEL);
+                       if (!dev->log_buf) {
+                               hio_warn("%s: ssd_log_worker: no mem\n", dev->name);
+                               return;
+                       }
+               }
+
+               /* get log */
+               if (test_and_clear_bit(SSD_LOG_HW, &dev->state)) {
+                       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+                               ret = ssd_do_log(dev, i, dev->log_buf);
+                               if (ret) {
+                                       (void)test_and_set_bit(SSD_LOG_ERR, &dev->state);
+                                       hio_warn("%s: do log fail\n", dev->name);
+                               }
+                       }
+               }
+       }
+
+       ret = ssd_do_swlog(dev);
+       if (ret) {
+               hio_warn("%s: do swlog fail\n", dev->name);
+       }
+}
+
+static void ssd_cleanup_log(struct ssd_device *dev)
+{
+       if (dev->log_buf) {
+               kfree(dev->log_buf);
+               dev->log_buf = NULL;
+       }
+
+       sfifo_free(&dev->log_fifo);
+
+       if (dev->internal_log.log) {
+               vfree(dev->internal_log.log);
+               dev->internal_log.log = NULL;
+       }
+}
+
+static int ssd_init_log(struct ssd_device *dev)
+{
+       struct ssd_log *log;
+       uint32_t off, size;
+       uint32_t len = 0;
+       int ret = 0;
+
+       mutex_init(&dev->internal_log_mutex);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+       INIT_WORK(&dev->log_work, ssd_log_worker, dev);
+#else
+       INIT_WORK(&dev->log_work, ssd_log_worker);
+#endif
+
+       off = dev->rom_info.log_base;
+       size = dev->rom_info.log_sz;
+
+       dev->internal_log.log = vmalloc(size);
+       if (!dev->internal_log.log) {
+               ret = -ENOMEM;
+               goto out_alloc_log;
+       }
+
+       ret = sfifo_alloc(&dev->log_fifo, SSD_LOG_FIFO_SZ, sizeof(struct ssd_log_entry));
+       if (ret < 0) {
+               goto out_alloc_log_fifo;
+       }
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       log = (struct ssd_log *)dev->internal_log.log;
+       while (len < size) {
+               ret = ssd_spi_read(dev, log, off, sizeof(struct ssd_log));
+               if (ret) {
+                       goto out_read_log;
+               }
+
+               if (log->ctrl_idx == 0xff) {
+                       break;
+               }
+
+               dev->internal_log.nr_log++;
+               log++;
+               len += sizeof(struct ssd_log);
+               off += sizeof(struct ssd_log);
+       }
+
+       return 0;
+
+out_read_log:
+       sfifo_free(&dev->log_fifo);
+out_alloc_log_fifo:
+       vfree(dev->internal_log.log);
+       dev->internal_log.log = NULL;
+       dev->internal_log.nr_log = 0;
+out_alloc_log:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+/* work queue */
+static void ssd_stop_workq(struct ssd_device *dev)
+{
+       test_and_clear_bit(SSD_INIT_WORKQ, &dev->state);
+       flush_workqueue(dev->workq);
+}
+
+static void ssd_start_workq(struct ssd_device *dev)
+{
+       (void)test_and_set_bit(SSD_INIT_WORKQ, &dev->state);
+
+       /* log ? */
+       queue_work(dev->workq, &dev->log_work);
+}
+
+static void ssd_cleanup_workq(struct ssd_device *dev)
+{
+       flush_workqueue(dev->workq);
+       destroy_workqueue(dev->workq);
+       dev->workq = NULL;
+}
+
+static int ssd_init_workq(struct ssd_device *dev)
+{
+       int ret = 0;
+       
+       dev->workq = create_singlethread_workqueue(dev->name);
+       if (!dev->workq) {
+               ret = -ESRCH;
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+/* rom */
+static int ssd_init_rom_info(struct ssd_device *dev)
+{
+       uint32_t val;
+
+       mutex_init(&dev->spi_mutex);
+       mutex_init(&dev->i2c_mutex);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               /* fix bug: read data to clear status */
+               (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_RDATA);
+
+               dev->rom_info.size = SSD_ROM_SIZE;
+               dev->rom_info.block_size = SSD_ROM_BLK_SIZE;
+               dev->rom_info.page_size = SSD_ROM_PAGE_SIZE;
+
+               dev->rom_info.bridge_fw_base = SSD_ROM_BRIDGE_FW_BASE;
+               dev->rom_info.bridge_fw_sz = SSD_ROM_BRIDGE_FW_SIZE;
+               dev->rom_info.nr_bridge_fw = SSD_ROM_NR_BRIDGE_FW;
+
+               dev->rom_info.ctrl_fw_base = SSD_ROM_CTRL_FW_BASE;
+               dev->rom_info.ctrl_fw_sz = SSD_ROM_CTRL_FW_SIZE;
+               dev->rom_info.nr_ctrl_fw = SSD_ROM_NR_CTRL_FW;
+
+               dev->rom_info.log_sz = SSD_ROM_LOG_SZ;
+
+               dev->rom_info.vp_base = SSD_ROM_VP_BASE;
+               dev->rom_info.label_base = SSD_ROM_LABEL_BASE;
+       } else if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_INFO_REG);
+               dev->rom_info.size = 0x100000 * (1U << (val & 0xFF));
+               dev->rom_info.block_size = 0x10000 * (1U << ((val>>8) & 0xFF));
+               dev->rom_info.page_size = (val>>16) & 0xFFFF;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_BRIDGE_FW_INFO_REG);
+               dev->rom_info.bridge_fw_base = dev->rom_info.block_size * (val & 0xFFFF);
+               dev->rom_info.bridge_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF);
+               dev->rom_info.nr_bridge_fw = ((val >> 30) & 0x3) + 1;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_CTRL_FW_INFO_REG);
+               dev->rom_info.ctrl_fw_base = dev->rom_info.block_size * (val & 0xFFFF);
+               dev->rom_info.ctrl_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF);
+               dev->rom_info.nr_ctrl_fw = ((val >> 30) & 0x3) + 1;
+
+               dev->rom_info.bm_fw_base = dev->rom_info.ctrl_fw_base + (dev->rom_info.nr_ctrl_fw * dev->rom_info.ctrl_fw_sz);
+               dev->rom_info.bm_fw_sz = SSD_PV3_ROM_BM_FW_SZ;
+               dev->rom_info.nr_bm_fw = SSD_PV3_ROM_NR_BM_FW;
+
+               dev->rom_info.log_base = dev->rom_info.bm_fw_base + (dev->rom_info.nr_bm_fw * dev->rom_info.bm_fw_sz);
+               dev->rom_info.log_sz = SSD_ROM_LOG_SZ;
+
+               dev->rom_info.smart_base = dev->rom_info.log_base + dev->rom_info.log_sz;
+               dev->rom_info.smart_sz = SSD_PV3_ROM_SMART_SZ;
+               dev->rom_info.nr_smart = SSD_PV3_ROM_NR_SMART;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_VP_INFO_REG);
+               dev->rom_info.vp_base = dev->rom_info.block_size * val;
+               dev->rom_info.label_base = dev->rom_info.vp_base + dev->rom_info.block_size;
+               if (dev->rom_info.label_base >= dev->rom_info.size) {
+                       dev->rom_info.label_base = dev->rom_info.vp_base - dev->rom_info.block_size;
+               }
+       } else {
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_INFO_REG);
+               dev->rom_info.size = 0x100000 * (1U << (val & 0xFF));
+               dev->rom_info.block_size = 0x10000 * (1U << ((val>>8) & 0xFF));
+               dev->rom_info.page_size = (val>>16) & 0xFFFF;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_BRIDGE_FW_INFO_REG);
+               dev->rom_info.bridge_fw_base = dev->rom_info.block_size * (val & 0xFFFF);
+               dev->rom_info.bridge_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF);
+               dev->rom_info.nr_bridge_fw = ((val >> 30) & 0x3) + 1;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_CTRL_FW_INFO_REG);
+               dev->rom_info.ctrl_fw_base = dev->rom_info.block_size * (val & 0xFFFF);
+               dev->rom_info.ctrl_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF);
+               dev->rom_info.nr_ctrl_fw = ((val >> 30) & 0x3) + 1;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ROM_VP_INFO_REG);
+               dev->rom_info.vp_base = dev->rom_info.block_size * val;
+               dev->rom_info.label_base = dev->rom_info.vp_base - SSD_PV3_2_ROM_SEC_SZ;
+
+               dev->rom_info.nr_smart = SSD_PV3_ROM_NR_SMART;
+               dev->rom_info.smart_sz = SSD_PV3_2_ROM_SEC_SZ;
+               dev->rom_info.smart_base = dev->rom_info.label_base - (dev->rom_info.smart_sz * dev->rom_info.nr_smart);
+               if (dev->rom_info.smart_sz > dev->rom_info.block_size) {
+                       dev->rom_info.smart_sz = dev->rom_info.block_size;
+               }
+
+               dev->rom_info.log_sz = SSD_PV3_2_ROM_LOG_SZ;
+               dev->rom_info.log_base = dev->rom_info.smart_base - dev->rom_info.log_sz;
+       }
+
+       return ssd_init_spi(dev);
+}
+
+/* smart */
+static int ssd_update_smart(struct ssd_device *dev, struct ssd_smart *smart)
+{
+       struct timeval tv;
+       uint64_t run_time;
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27))
+       struct hd_struct *part;
+       int cpu;
+#endif
+       int i, j;
+       int ret = 0;
+
+       if (!test_bit(SSD_INIT_BD, &dev->state)) {
+               return 0;
+       }
+
+       do_gettimeofday(&tv);
+       if ((uint64_t)tv.tv_sec < dev->uptime) {
+               run_time = 0;
+       } else {
+               run_time = tv.tv_sec - dev->uptime;
+       }
+
+       /* avoid frequently update */
+       if (run_time >= 60) {
+               ret = 1;
+       }
+
+       /* io stat */
+       smart->io_stat.run_time += run_time;
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27))
+       cpu = part_stat_lock();
+       part = &dev->gd->part0;
+       part_round_stats(cpu, part);
+       part_stat_unlock();
+
+       smart->io_stat.nr_read += part_stat_read(part, ios[READ]);
+       smart->io_stat.nr_write += part_stat_read(part, ios[WRITE]);
+       smart->io_stat.rsectors += part_stat_read(part, sectors[READ]);
+       smart->io_stat.wsectors += part_stat_read(part, sectors[WRITE]);
+#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14))
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+
+       smart->io_stat.nr_read += disk_stat_read(dev->gd, ios[READ]);
+       smart->io_stat.nr_write += disk_stat_read(dev->gd, ios[WRITE]);
+       smart->io_stat.rsectors += disk_stat_read(dev->gd, sectors[READ]);
+       smart->io_stat.wsectors += disk_stat_read(dev->gd, sectors[WRITE]);
+#else
+       preempt_disable();
+       disk_round_stats(dev->gd);
+       preempt_enable();
+
+       smart->io_stat.nr_read += disk_stat_read(dev->gd, reads);
+       smart->io_stat.nr_write += disk_stat_read(dev->gd, writes);
+       smart->io_stat.rsectors += disk_stat_read(dev->gd, read_sectors);
+       smart->io_stat.wsectors += disk_stat_read(dev->gd, write_sectors);
+#endif
+
+       smart->io_stat.nr_to += atomic_read(&dev->tocnt);
+
+       for (i=0; i<dev->nr_queue; i++) {
+               smart->io_stat.nr_rwerr += dev->queue[i].io_stat.nr_rwerr;
+               smart->io_stat.nr_ioerr += dev->queue[i].io_stat.nr_ioerr;
+       }
+
+       for (i=0; i<dev->nr_queue; i++) {
+               for (j=0; j<SSD_ECC_MAX_FLIP; j++) {
+                       smart->ecc_info.bitflip[j] += dev->queue[i].ecc_info.bitflip[j];
+               }
+       }
+
+       //dev->uptime = tv.tv_sec;
+
+       return ret;
+}
+
+static int ssd_clear_smart(struct ssd_device *dev)
+{
+       struct timeval tv;
+       uint64_t sversion;
+       uint32_t off, length;
+       int i;
+       int ret;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       /* clear smart */
+       off = dev->rom_info.smart_base;
+       length = dev->rom_info.smart_sz * dev->rom_info.nr_smart;
+
+       ret = ssd_spi_erase(dev, off, length);
+       if (ret) {
+               hio_warn("%s: info erase: failed\n", dev->name);
+               goto out;
+       }
+
+       sversion = dev->smart.version;
+
+       memset(&dev->smart, 0, sizeof(struct ssd_smart));
+       dev->smart.version = sversion + 1;
+       dev->smart.magic = SSD_SMART_MAGIC;
+
+       /* clear all tmp acc */
+       for (i=0; i<dev->nr_queue; i++) {
+               memset(&(dev->queue[i].io_stat), 0, sizeof(struct ssd_io_stat));
+               memset(&(dev->queue[i].ecc_info), 0, sizeof(struct ssd_ecc_info));
+       }
+
+       atomic_set(&dev->tocnt, 0);
+
+       /* clear tmp log info */
+       memset(&dev->log_info, 0, sizeof(struct ssd_log_info));
+
+       do_gettimeofday(&tv);
+       dev->uptime = tv.tv_sec;
+
+       /* clear alarm ? */
+       //ssd_clear_alarm(dev);
+out:
+       return ret;
+}
+
+static int ssd_save_smart(struct ssd_device *dev)
+{
+       uint32_t off, size;
+       int i;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       if (!ssd_update_smart(dev, &dev->smart)) {
+               return 0;
+       }
+
+       dev->smart.version++;
+
+       for (i=0; i<dev->rom_info.nr_smart; i++) {
+               off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i);
+               size = dev->rom_info.smart_sz;
+
+               ret = ssd_spi_erase(dev, off, size);
+               if (ret) {
+                       hio_warn("%s: info erase failed\n", dev->name);
+                       goto out;
+               }
+
+               size = sizeof(struct ssd_smart);
+
+               ret = ssd_spi_write(dev, &dev->smart, off, size);
+               if (ret) {
+                       hio_warn("%s: info write failed\n", dev->name);
+                       goto out;
+               }
+
+               //xx
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_init_smart(struct ssd_device *dev)
+{
+       struct ssd_smart *smart;
+       struct timeval tv;
+       uint32_t off, size;
+       int i;
+       int ret = 0;
+
+       do_gettimeofday(&tv);
+       dev->uptime = tv.tv_sec;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       smart = kmalloc(sizeof(struct ssd_smart) * SSD_ROM_NR_SMART_MAX, GFP_KERNEL);
+       if (!smart) {
+               ret = -ENOMEM;
+               goto out_nomem;
+       }
+
+       memset(&dev->smart, 0, sizeof(struct ssd_smart));
+
+       /* read smart */
+       for (i=0; i<dev->rom_info.nr_smart; i++) {
+               memset(&smart[i], 0, sizeof(struct ssd_smart));
+
+               off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i);
+               size = sizeof(struct ssd_smart);
+
+               ret = ssd_spi_read(dev, &smart[i], off, size);
+               if (ret) {
+                       hio_warn("%s: info read failed\n", dev->name);
+                       goto out;
+               }
+
+               if (smart[i].magic != SSD_SMART_MAGIC) {
+                       smart[i].magic = 0;
+                       smart[i].version = 0;
+                       continue;
+               }
+
+               if (smart[i].version > dev->smart.version) {
+                       memcpy(&dev->smart, &smart[i], sizeof(struct ssd_smart));
+               }
+       }
+
+       if (dev->smart.magic != SSD_SMART_MAGIC) {
+               /* first time power up */
+               dev->smart.magic = SSD_SMART_MAGIC;
+               dev->smart.version = 1;
+       }
+
+       /* check log info */
+       {
+               struct ssd_log_info log_info;
+               struct ssd_log *log = (struct ssd_log *)dev->internal_log.log;
+
+               memset(&log_info, 0, sizeof(struct ssd_log_info));
+
+               while (log_info.nr_log < dev->internal_log.nr_log) {
+                       /* skip the volatile log info */
+                       if (SSD_LOG_SEU_FAULT != log->le.event && SSD_LOG_SEU_FAULT1 != log->le.event) {
+                               log_info.stat[ssd_parse_log(dev, log, 0)]++;
+                       }
+
+                       log_info.nr_log++;
+                       log++;
+               }
+
+               /* check */
+               for (i=(SSD_LOG_NR_LEVEL-1); i>=0; i--) {
+                       if (log_info.stat[i] > dev->smart.log_info.stat[i]) {
+                               /* unclean */
+                               memcpy(&dev->smart.log_info, &log_info, sizeof(struct ssd_log_info));
+                               dev->smart.version++;
+                               break;
+                       }
+               }
+       }
+
+       for (i=0; i<dev->rom_info.nr_smart; i++) {
+               if (smart[i].magic == SSD_SMART_MAGIC && smart[i].version == dev->smart.version) {
+                       continue;
+               }
+
+               off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i);
+               size = dev->rom_info.smart_sz;
+
+               ret = ssd_spi_erase(dev, off, size);
+               if (ret) {
+                       hio_warn("%s: info erase failed\n", dev->name);
+                       goto out;
+               }
+
+               size = sizeof(struct ssd_smart);
+               ret = ssd_spi_write(dev, &dev->smart, off, size);
+               if (ret) {
+                       hio_warn("%s: info write failed\n", dev->name);
+                       goto out;
+               }
+
+               //xx
+       }
+
+       /* sync smart with alarm led */
+       if (dev->smart.io_stat.nr_to || dev->smart.io_stat.nr_rwerr || dev->smart.log_info.stat[SSD_LOG_LEVEL_ERR]) {
+               hio_warn("%s: some fault found in the history info\n", dev->name);
+               ssd_set_alarm(dev);
+       }
+
+out:
+       kfree(smart);
+out_nomem:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+/* bm */
+static int __ssd_bm_get_version(struct ssd_device *dev, uint16_t *ver)
+{
+       struct ssd_bm_manufacturer_data bm_md = {0};
+       uint16_t sc_id = SSD_BM_SYSTEM_DATA_SUBCLASS_ID;
+       uint8_t cmd;
+       int ret = 0;
+
+       if (!dev || !ver) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->bm_mutex);
+
+       cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID;
+       ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&sc_id);
+       if (ret) {
+               goto out;
+       }
+
+       cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1;
+       ret = ssd_smbus_read_block(dev, SSD_BM_SLAVE_ADDRESS, cmd, sizeof(struct ssd_bm_manufacturer_data), (uint8_t *)&bm_md);
+       if (ret) {
+               goto out;
+       }
+
+       if (bm_md.firmware_ver & 0xF000) {
+               ret = -EIO;
+               goto out;
+       }
+
+       *ver = bm_md.firmware_ver;
+
+out:
+       mutex_unlock(&dev->bm_mutex);
+       return ret;
+}
+
+static int ssd_bm_get_version(struct ssd_device *dev, uint16_t *ver)
+{
+       uint16_t tmp = 0;
+       int i = SSD_BM_RETRY_MAX;
+       int ret = 0;
+
+       while (i-- > 0) {
+               ret = __ssd_bm_get_version(dev, &tmp);
+               if (!ret) {
+                       break;
+               }
+       }
+       if (ret) {
+               return ret;
+       }
+
+       *ver = tmp;
+
+       return 0;
+}
+
+static int __ssd_bm_nr_cap(struct ssd_device *dev, int *nr_cap)
+{
+       struct ssd_bm_configuration_registers bm_cr;
+       uint16_t sc_id = SSD_BM_CONFIGURATION_REGISTERS_ID;
+       uint8_t cmd;
+       int ret;
+
+       mutex_lock(&dev->bm_mutex);
+
+       cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID;
+       ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&sc_id);
+       if (ret) {
+               goto out;
+       }
+
+       cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1;
+       ret = ssd_smbus_read_block(dev, SSD_BM_SLAVE_ADDRESS, cmd, sizeof(struct ssd_bm_configuration_registers), (uint8_t *)&bm_cr);
+       if (ret) {
+               goto out;
+       }
+
+       if (bm_cr.operation_cfg.cc == 0 || bm_cr.operation_cfg.cc > 4) {
+               ret = -EIO;
+               goto out;
+       }
+
+       *nr_cap = bm_cr.operation_cfg.cc + 1;
+
+out:
+       mutex_unlock(&dev->bm_mutex);
+       return ret;
+}
+
+static int ssd_bm_nr_cap(struct ssd_device *dev, int *nr_cap)
+{
+       int tmp = 0;
+       int i = SSD_BM_RETRY_MAX;
+       int ret = 0;
+
+       while (i-- > 0) {
+               ret = __ssd_bm_nr_cap(dev, &tmp);
+               if (!ret) {
+                       break;
+               }
+       }
+       if (ret) {
+               return ret;
+       }
+
+       *nr_cap = tmp;
+
+       return 0;
+}
+
+static int ssd_bm_enter_cap_learning(struct ssd_device *dev)
+{
+       uint16_t buf = SSD_BM_ENTER_CAP_LEARNING;
+       uint8_t cmd = SSD_BM_MANUFACTURERACCESS;
+       int ret;
+
+       ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&buf);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_bm_get_sfstatus(struct ssd_device *dev, uint16_t *status)
+{
+       uint16_t val = 0;
+       uint8_t cmd = SSD_BM_SAFETYSTATUS;
+       int ret;
+
+       ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&val);
+       if (ret) {
+               goto out;
+       }
+
+       *status = val;
+out:
+       return ret;
+}
+
+static int ssd_bm_get_opstatus(struct ssd_device *dev, uint16_t *status)
+{
+       uint16_t val = 0;
+       uint8_t cmd = SSD_BM_OPERATIONSTATUS;
+       int ret;
+
+       ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&val);
+       if (ret) {
+               goto out;
+       }
+
+       *status = val;
+out:
+       return ret;
+}
+static int ssd_get_bmstruct(struct ssd_device *dev, struct ssd_bm *bm_status_out)
+{
+       struct sbs_cmd *bm_sbs = ssd_bm_sbs;
+       struct ssd_bm bm_status;
+       uint8_t buf[2] = {0, };
+       uint16_t val = 0;
+       uint16_t cval;
+       int ret = 0;
+
+       memset(&bm_status, 0, sizeof(struct ssd_bm));
+
+       while (bm_sbs->desc != NULL) {
+               switch (bm_sbs->size) {
+                       case SBS_SIZE_BYTE:
+                               ret = ssd_smbus_read_byte(dev, SSD_BM_SLAVE_ADDRESS, bm_sbs->cmd, buf);
+                               if (ret) {
+                                       //printf("Error: smbus read byte %#x\n", bm_sbs->cmd);
+                                       goto out;
+                               }
+                               val = buf[0];
+                               break;
+                       case SBS_SIZE_WORD:
+                               ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, bm_sbs->cmd, (uint8_t *)&val);
+                               if (ret) {
+                                       //printf("Error: smbus read word %#x\n", bm_sbs->cmd);
+                                       goto out;
+                               }
+                               //val = *(uint16_t *)buf;
+                               break;
+                       default:
+                               ret = -1;
+                               goto out;
+                               break;
+               }
+
+               switch (bm_sbs->unit) {
+                       case SBS_UNIT_VALUE:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val & bm_sbs->mask;
+                               break;
+                       case SBS_UNIT_TEMPERATURE:
+                               cval = (uint16_t)(val - 2731) / 10;
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = cval;
+                               break;
+                       case SBS_UNIT_VOLTAGE:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val;
+                               break;
+                       case SBS_UNIT_CURRENT:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val;
+                               break;
+                       case SBS_UNIT_ESR:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val;
+                               break;
+                       case SBS_UNIT_PERCENT:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val;
+                               break;
+                       case SBS_UNIT_CAPACITANCE:
+                               *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val;
+                               break;
+                       default:
+                               ret = -1;
+                               goto out;
+                               break;
+               }
+
+               bm_sbs++;
+       }
+
+       memcpy(bm_status_out, &bm_status, sizeof(struct ssd_bm));
+
+out:
+       return ret;
+}
+
+static int __ssd_bm_status(struct ssd_device *dev, int *status)
+{
+       struct ssd_bm bm_status = {0};
+       int nr_cap = 0;
+       int i;
+       int ret = 0;
+
+       ret = ssd_get_bmstruct(dev, &bm_status);
+       if (ret) {
+               goto out;
+       }
+
+       /* capacitor voltage */
+       ret = ssd_bm_nr_cap(dev, &nr_cap);
+       if (ret) {
+               goto out;
+       }
+
+       for (i=0; i<nr_cap; i++) {
+               if (bm_status.cap_volt[i] < SSD_BM_CAP_VOLT_MIN) {
+                       *status = SSD_BMSTATUS_WARNING;
+                       goto out;
+               }
+       }
+
+       /* Safety Status */
+       if (bm_status.sf_status) {
+               *status = SSD_BMSTATUS_WARNING;
+               goto out;
+       }
+
+       /* charge status */
+       if (!((bm_status.op_status >> 12) & 0x1)) {
+               *status = SSD_BMSTATUS_CHARGING;
+       }else{
+               *status = SSD_BMSTATUS_OK;
+       }
+
+out:
+       return ret;
+}
+
+static void ssd_set_flush_timeout(struct ssd_device *dev, int mode);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static void ssd_bm_worker(void *data)
+{
+       struct ssd_device *dev = (struct ssd_device *)data;
+#else
+static void ssd_bm_worker(struct work_struct *work)
+{
+       struct ssd_device *dev = container_of(work, struct ssd_device, bm_work);
+#endif
+
+       uint16_t opstatus;
+       int ret = 0;
+
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               return;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) {
+               return;
+       }
+
+       if (dev->hw_info_ext.plp_type != SSD_PLP_SCAP) {
+               return;
+       }
+
+       ret = ssd_bm_get_opstatus(dev, &opstatus);
+       if (ret) {
+               hio_warn("%s: get bm operationstatus failed\n", dev->name);
+               return;
+       }
+
+       /* need cap learning ? */
+       if (!(opstatus & 0xF0)) {
+               ret = ssd_bm_enter_cap_learning(dev);
+               if (ret) {
+                       hio_warn("%s: enter capacitance learning failed\n", dev->name);
+                       return;
+               }
+       }
+}
+
+static void ssd_bm_routine_start(void *data)
+{
+       struct ssd_device *dev;
+
+       if (!data) {
+               return;
+       }
+       dev = data;
+
+       if (test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                       queue_work(dev->workq, &dev->bm_work);
+               } else {
+                       queue_work(dev->workq, &dev->capmon_work);
+               }
+       }
+}
+
+/* CAP */
+static int ssd_do_cap_learn(struct ssd_device *dev, uint32_t *cap)
+{
+       uint32_t u1, u2, t;
+       uint16_t val = 0;
+       int wait = 0;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               *cap = 0;
+               return 0;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               *cap = 0;
+               return 0;
+       }
+
+       /* make sure the lm80 voltage value is updated */
+       msleep(SSD_LM80_CONV_INTERVAL);
+
+       /* check if full charged */
+       wait = 0;
+       for (;;) {
+               ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val);
+               if (ret) {
+                       if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                               ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+                       }
+                       goto out;
+               }
+               u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val));
+               if (SSD_PL_CAP_VOLT(u1) >= SSD_PL_CAP_VOLT_FULL) {
+                       break;
+               }
+
+               wait++;
+               if (wait > SSD_PL_CAP_CHARGE_MAX_WAIT) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+               msleep(SSD_PL_CAP_CHARGE_WAIT);
+       }
+
+       ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U2, (uint8_t *)&val);
+       if (ret) {
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+               }
+               goto out;
+       }
+       u2 = SSD_LM80_CONVERT_VOLT(u16_swap(val));
+
+       if (u1 == u2) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* enter cap learn */
+       ssd_reg32_write(dev->ctrlp + SSD_PL_CAP_LEARN_REG, 0x1);
+       
+       wait = 0;
+       for (;;) {
+               msleep(SSD_PL_CAP_LEARN_WAIT);
+
+               t = ssd_reg32_read(dev->ctrlp + SSD_PL_CAP_LEARN_REG);
+               if (!((t >> 1) & 0x1)) {
+                       break;
+               }
+
+               wait++;
+               if (wait > SSD_PL_CAP_LEARN_MAX_WAIT) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
+       }
+
+       if ((t >> 4) & 0x1) {
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       t = (t >> 8);
+       if (0 == t) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       *cap = SSD_PL_CAP_LEARN(u1, u2, t);
+
+out:
+       return ret;
+}
+
+static int ssd_cap_learn(struct ssd_device *dev, uint32_t *cap)
+{
+       int ret = 0;
+
+       if (!dev || !cap) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->bm_mutex);
+
+       ssd_stop_workq(dev);
+
+       ret = ssd_do_cap_learn(dev, cap);
+       if (ret) {
+               ssd_gen_swlog(dev, SSD_LOG_CAP_LEARN_FAULT, 0);
+               goto out;
+       }
+
+       ssd_gen_swlog(dev, SSD_LOG_CAP_STATUS, *cap);
+
+out:
+       ssd_start_workq(dev);
+       mutex_unlock(&dev->bm_mutex);
+
+       return ret;
+}
+
+static int ssd_check_pl_cap(struct ssd_device *dev)
+{
+       uint32_t u1;
+       uint16_t val = 0;
+       uint8_t low = 0;
+       int wait = 0;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               return 0;
+       }
+
+       /* cap ready ? */
+       wait = 0;
+       for (;;) {
+               ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val);
+               if (ret) {
+                       if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                               ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+                       }
+                       goto out;
+               }
+               u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val));
+               if (SSD_PL_CAP_VOLT(u1) >= SSD_PL_CAP_VOLT_READY) {
+                       break;
+               }
+
+               wait++;
+               if (wait > SSD_PL_CAP_CHARGE_MAX_WAIT) {
+                       ret = -ETIMEDOUT;
+                       ssd_gen_swlog(dev, SSD_LOG_CAP_VOLT_FAULT, SSD_PL_CAP_VOLT(u1));
+                       goto out;
+               }
+               msleep(SSD_PL_CAP_CHARGE_WAIT);
+       }
+
+       low = ssd_lm80_limit[SSD_LM80_IN_CAP].low;
+       ret = ssd_smbus_write_byte(dev, SSD_SENSOR_LM80_SADDRESS, SSD_LM80_REG_IN_MIN(SSD_LM80_IN_CAP), &low);
+       if (ret) {
+               goto out;
+       }
+
+       /* enable cap INx */
+       ret = ssd_lm80_enable_in(dev, SSD_SENSOR_LM80_SADDRESS, SSD_LM80_IN_CAP);
+       if (ret) {
+               if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS);
+               }
+               goto out;
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+static int ssd_check_pl_cap_fast(struct ssd_device *dev)
+{
+       uint32_t u1;
+       uint16_t val = 0;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               return 0;
+       }
+
+       /* cap ready ? */
+       ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val);
+       if (ret) {
+               goto out;
+       }
+       u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val));
+       if (SSD_PL_CAP_VOLT(u1) < SSD_PL_CAP_VOLT_READY) {
+               ret = 1;
+       }
+
+out:
+       return ret;
+}
+
+static int ssd_init_pl_cap(struct ssd_device *dev)
+{
+       int ret = 0;
+
+       /* set here: user write mode */
+       dev->user_wmode = wmode;
+       
+       mutex_init(&dev->bm_mutex);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               uint32_t val;
+               val = ssd_reg32_read(dev->ctrlp + SSD_BM_FAULT_REG);
+               if ((val >> 1) & 0x1) {
+                       (void)test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon);
+               }
+       } else {
+               ret = ssd_check_pl_cap(dev);
+               if (ret) {
+                       (void)test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon);
+               }
+       }
+
+       return 0;
+}
+
+/* label */
+static void __end_str(char *str, int len)
+{
+       int i;
+
+       for(i=0; i<len; i++) {
+               if (*(str+i) == '\0')
+                       return;
+       }
+       *str = '\0';
+}
+
+static int ssd_init_label(struct ssd_device *dev)
+{
+       uint32_t off;
+       uint32_t size;
+       int ret;
+
+       /* label location */
+       off = dev->rom_info.label_base;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               size = sizeof(struct ssd_label);
+
+               /* read label */
+               ret = ssd_spi_read(dev, &dev->label, off, size);
+               if (ret) {
+                       memset(&dev->label, 0, size);
+                       goto out;
+               }
+
+               __end_str(dev->label.date, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->label.sn, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->label.part, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->label.desc, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->label.other, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->label.maf, SSD_LABEL_FIELD_SZ);
+       } else {
+               size = sizeof(struct ssd_labelv3);
+
+               /* read label */
+               ret = ssd_spi_read(dev, &dev->labelv3, off, size);
+               if (ret) {
+                       memset(&dev->labelv3, 0, size);
+                       goto out;
+               }
+
+               __end_str(dev->labelv3.boardtype, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.barcode, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.item, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.description, SSD_LABEL_DESC_SZ);
+               __end_str(dev->labelv3.manufactured, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.vendorname, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.issuenumber, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.cleicode, SSD_LABEL_FIELD_SZ);
+               __end_str(dev->labelv3.bom, SSD_LABEL_FIELD_SZ);
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+int ssd_get_label(struct block_device *bdev, struct ssd_label *label)
+{
+       struct ssd_device *dev;
+
+       if (!bdev || !label || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               memset(label, 0, sizeof(struct ssd_label));
+               memcpy(label->date, dev->labelv3.manufactured, SSD_LABEL_FIELD_SZ);
+               memcpy(label->sn, dev->labelv3.barcode, SSD_LABEL_FIELD_SZ);
+               memcpy(label->desc, dev->labelv3.boardtype, SSD_LABEL_FIELD_SZ);
+               memcpy(label->maf, dev->labelv3.vendorname, SSD_LABEL_FIELD_SZ);
+       } else {
+               memcpy(label, &dev->label, sizeof(struct ssd_label));
+       }
+
+       return 0;
+}
+
+static int __ssd_get_version(struct ssd_device *dev, struct ssd_version_info *ver)
+{
+       uint16_t bm_ver = 0;
+       int ret = 0;
+
+       if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               ret = ssd_bm_get_version(dev, &bm_ver);
+               if(ret){
+                       goto out;
+               }
+       }
+
+       ver->bridge_ver = dev->hw_info.bridge_ver;
+       ver->ctrl_ver = dev->hw_info.ctrl_ver;
+       ver->bm_ver = bm_ver;
+       ver->pcb_ver = dev->hw_info.pcb_ver;
+       ver->upper_pcb_ver = dev->hw_info.upper_pcb_ver;
+
+out:
+       return ret;
+
+}
+
+int ssd_get_version(struct block_device *bdev, struct ssd_version_info *ver)
+{
+       struct ssd_device *dev;
+       int ret;
+
+       if (!bdev || !ver || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       mutex_lock(&dev->fw_mutex);
+       ret = __ssd_get_version(dev, ver);
+       mutex_unlock(&dev->fw_mutex);
+
+       return ret;
+}
+
+static int __ssd_get_temperature(struct ssd_device *dev, int *temp)
+{
+       uint64_t val;
+       uint32_t off;
+       int max = -300;
+       int cur;
+       int i;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               *temp = 0;
+               return 0;
+       }
+
+       if (finject) {
+               if (dev->db_info.type == SSD_DEBUG_LOG && 
+                       (dev->db_info.data.log.event == SSD_LOG_OVER_TEMP || 
+                       dev->db_info.data.log.event == SSD_LOG_NORMAL_TEMP || 
+                       dev->db_info.data.log.event == SSD_LOG_WARN_TEMP)) {
+                       *temp = (int)dev->db_info.data.log.extra;
+                       return 0;
+               }
+       }
+
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               off = SSD_CTRL_TEMP_REG0 + i * sizeof(uint64_t);
+
+               val = ssd_reg_read(dev->ctrlp + off);
+               if (val == 0xffffffffffffffffull) {
+                       continue;
+               }
+
+               cur = (int)CUR_TEMP(val);
+               if (cur >= max) {
+                       max = cur;
+               }
+       }
+
+       *temp = max;
+
+       return 0;
+}
+
+int ssd_get_temperature(struct block_device *bdev, int *temp)
+{
+       struct ssd_device *dev;
+       int ret;
+
+       if (!bdev || !temp || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+
+       mutex_lock(&dev->fw_mutex);
+       ret = __ssd_get_temperature(dev, temp);
+       mutex_unlock(&dev->fw_mutex);
+
+       return ret;
+}
+
+int ssd_set_otprotect(struct block_device *bdev, int otprotect)
+ {
+       struct ssd_device *dev;
+
+       if (!bdev || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data; 
+       ssd_set_ot_protect(dev, !!otprotect);
+
+       return 0;
+ }
+
+int ssd_bm_status(struct block_device *bdev, int *status)
+{
+       struct ssd_device *dev;
+       int ret = 0;
+
+       if (!bdev || !status || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       mutex_lock(&dev->fw_mutex);
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                       *status = SSD_BMSTATUS_WARNING;
+               } else {
+                       *status = SSD_BMSTATUS_OK;
+               }
+       } else if(dev->protocol_info.ver > SSD_PROTOCOL_V3) {
+               ret = __ssd_bm_status(dev, status);
+       } else {
+               *status = SSD_BMSTATUS_OK;
+       }
+       mutex_unlock(&dev->fw_mutex);
+
+       return ret;
+}
+
+int ssd_get_pciaddr(struct block_device *bdev, struct pci_addr *paddr)
+{
+       struct ssd_device *dev;
+
+       if (!bdev || !paddr || !bdev->bd_disk) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       paddr->domain = pci_domain_nr(dev->pdev->bus);
+       paddr->bus = dev->pdev->bus->number;
+       paddr->slot = PCI_SLOT(dev->pdev->devfn);
+       paddr->func= PCI_FUNC(dev->pdev->devfn);
+
+       return 0;
+}
+
+/* acc */
+static int ssd_bb_acc(struct ssd_device *dev, struct ssd_acc_info *acc)
+{
+       uint32_t val;
+       int ctrl, chip;
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) {
+               return -EOPNOTSUPP;
+       }
+
+       acc->threshold_l1 = ssd_reg32_read(dev->ctrlp + SSD_BB_THRESHOLD_L1_REG);
+       if (0xffffffffull == acc->threshold_l1) {
+               return -EIO;
+       }
+       acc->threshold_l2 = ssd_reg32_read(dev->ctrlp + SSD_BB_THRESHOLD_L2_REG);
+       if (0xffffffffull == acc->threshold_l2) {
+               return -EIO;
+       }
+       acc->val = 0;
+
+       for (ctrl=0; ctrl<dev->hw_info.nr_ctrl; ctrl++) {
+               for (chip=0; chip<dev->hw_info.nr_chip; chip++) {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_BB_ACC_REG0 + (SSD_CTRL_REG_ZONE_SZ * ctrl) + (SSD_BB_ACC_REG_SZ * chip));
+                       if (0xffffffffull == acc->val) {
+                               return -EIO;
+                       }
+                       if (val > acc->val) {
+                               acc->val = val;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static int ssd_ec_acc(struct ssd_device *dev, struct ssd_acc_info *acc)
+{
+       uint32_t val;
+       int ctrl, chip;
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) {
+               return -EOPNOTSUPP;
+       }
+
+       acc->threshold_l1 = ssd_reg32_read(dev->ctrlp + SSD_EC_THRESHOLD_L1_REG);
+       if (0xffffffffull == acc->threshold_l1) {
+               return -EIO;
+       }
+       acc->threshold_l2 = ssd_reg32_read(dev->ctrlp + SSD_EC_THRESHOLD_L2_REG);
+       if (0xffffffffull == acc->threshold_l2) {
+               return -EIO;
+       }
+       acc->val = 0;
+
+       for (ctrl=0; ctrl<dev->hw_info.nr_ctrl; ctrl++) {
+               for (chip=0; chip<dev->hw_info.nr_chip; chip++) {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_EC_ACC_REG0 + (SSD_CTRL_REG_ZONE_SZ * ctrl) + (SSD_EC_ACC_REG_SZ * chip));
+                       if (0xffffffffull == acc->val) {
+                               return -EIO;
+                       }
+
+                       if (val > acc->val) {
+                               acc->val = val;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+
+/* ram r&w */
+static int ssd_ram_read_4k(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx)
+{
+       struct ssd_ram_op_msg *msg;
+       dma_addr_t buf_dma;
+       size_t len = length;
+       loff_t ofs_w = ofs;
+       int ret = 0;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size 
+               || !length || length > dev->hw_info.ram_max_len 
+               || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) {
+               return -EINVAL;
+       }
+
+       len /= dev->hw_info.ram_align;
+       do_div(ofs_w, dev->hw_info.ram_align);
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map read DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       msg = (struct ssd_ram_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_RAM_READ;
+       msg->ctrl_idx = ctrl_idx;
+       msg->start = (uint32_t)ofs_w;
+       msg->length = len;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, READ, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE);
+
+out_dma_mapping:
+        return ret;
+}
+
+static int ssd_ram_write_4k(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx)
+{
+       struct ssd_ram_op_msg *msg;
+       dma_addr_t buf_dma;
+       size_t len = length;
+       loff_t ofs_w = ofs;
+       int ret = 0;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size 
+               || !length || length > dev->hw_info.ram_max_len 
+               || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) {
+               return -EINVAL;
+       }
+
+       len /= dev->hw_info.ram_align;
+       do_div(ofs_w, dev->hw_info.ram_align);
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_TODEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map write DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       msg = (struct ssd_ram_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_RAM_WRITE;
+       msg->ctrl_idx = ctrl_idx;
+       msg->start = (uint32_t)ofs_w;
+       msg->length = len;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_TODEVICE);
+
+out_dma_mapping:
+        return ret;
+
+}
+
+static int ssd_ram_read(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx)
+{
+       int left = length;
+       size_t len;
+       loff_t off = ofs;
+       int ret = 0;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length 
+               || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) {
+               return -EINVAL;
+       }
+
+       while (left > 0) {
+               len = dev->hw_info.ram_max_len;
+               if (left < (int)dev->hw_info.ram_max_len) {
+                       len = left;
+               }
+
+               ret = ssd_ram_read_4k(dev, buf, len, off, ctrl_idx);
+               if (ret) {
+                       break;
+               }
+
+               left -= len;
+               off += len;
+               buf += len;
+       }
+
+       return ret;
+}
+
+static int ssd_ram_write(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx)
+{
+       int left = length;
+       size_t len;
+       loff_t off = ofs;
+       int ret = 0;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length 
+               || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) {
+               return -EINVAL;
+       }
+
+       while (left > 0) {
+               len = dev->hw_info.ram_max_len;
+               if (left < (int)dev->hw_info.ram_max_len) {
+                       len = left;
+               }
+
+               ret = ssd_ram_write_4k(dev, buf, len, off, ctrl_idx);
+               if (ret) {
+                       break;
+               }
+
+               left -= len;
+               off += len;
+               buf += len;
+       }
+
+       return ret;
+}
+
+
+/* flash op */
+static int ssd_check_flash(struct ssd_device *dev, int flash, int page, int ctrl_idx)
+{
+       int cur_ch = flash % dev->hw_info.max_ch;
+       int cur_chip = flash /dev->hw_info.max_ch;
+
+       if (ctrl_idx >= dev->hw_info.nr_ctrl) {
+               return -EINVAL;
+       }
+
+       if (cur_ch >= dev->hw_info.nr_ch || cur_chip >= dev->hw_info.nr_chip) {
+               return -EINVAL;
+       }
+
+       if (page >= (int)(dev->hw_info.block_count * dev->hw_info.page_count)) {
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int ssd_nand_read_id(struct ssd_device *dev, void *id, int flash, int chip, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       dma_addr_t buf_dma;
+       int ret = 0;
+
+       if (unlikely(!id))
+               return -EINVAL;
+
+       buf_dma = pci_map_single(dev->pdev, id, SSD_NAND_ID_BUFF_SZ, PCI_DMA_FROMDEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map read DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               flash = ((uint32_t)flash << 1) | (uint32_t)chip;
+               chip = 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_NAND_READ_ID;
+       msg->chip_no = flash;
+       msg->chip_ce = chip;
+       msg->ctrl_idx = ctrl_idx;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, READ, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, SSD_NAND_ID_BUFF_SZ, PCI_DMA_FROMDEVICE);
+
+out_dma_mapping:
+       return ret;
+}
+
+#if 0
+static int ssd_nand_read(struct ssd_device *dev, void *buf,
+       int flash, int chip, int page, int page_count, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       dma_addr_t buf_dma;
+       int length;
+       int ret = 0;
+
+       if (!buf) {
+               return -EINVAL;
+       }
+
+       if ((page + page_count) > dev->hw_info.block_count*dev->hw_info.page_count) {
+               return -EINVAL;
+       }
+
+       ret = ssd_check_flash(dev, flash, page, ctrl_idx);
+       if (ret) {
+               return ret;
+       }
+
+       length = page_count * dev->hw_info.page_size;
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map read DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               flash = (flash << 1) | chip;
+               chip = 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_NAND_READ;
+       msg->ctrl_idx = ctrl_idx;
+       msg->chip_no = flash;
+       msg->chip_ce = chip;
+       msg->page_no = page;
+       msg->page_count = page_count;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, READ, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE);
+
+out_dma_mapping:
+       return ret;
+}
+#endif
+
+static int ssd_nand_read_w_oob(struct ssd_device *dev, void *buf, 
+       int flash, int chip, int page, int count, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       dma_addr_t buf_dma;
+       int length;
+       int ret = 0;
+
+       if (!buf) {
+               return -EINVAL;
+       }
+
+       if ((page + count) > (int)(dev->hw_info.block_count * dev->hw_info.page_count)) {
+               return -EINVAL;
+       }
+
+       ret = ssd_check_flash(dev, flash, page, ctrl_idx);
+       if (ret) {
+               return ret;
+       }
+
+       length = count * (dev->hw_info.page_size + dev->hw_info.oob_size);
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map read DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               flash = ((uint32_t)flash << 1) | (uint32_t)chip;
+               chip = 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_NAND_READ_WOOB;
+       msg->ctrl_idx = ctrl_idx;
+       msg->chip_no = flash;
+       msg->chip_ce = chip;
+       msg->page_no = page;
+       msg->page_count = count;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, READ, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE);
+
+out_dma_mapping:
+       return ret;
+}
+
+/* write 1 page */
+static int ssd_nand_write(struct ssd_device *dev, void *buf, 
+       int flash, int chip, int page, int count, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       dma_addr_t buf_dma;
+       int length;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               return -EINVAL;
+       }
+
+       if (!buf) {
+               return -EINVAL;
+       }
+
+       if (count != 1) {
+               return -EINVAL;
+       }
+
+       ret = ssd_check_flash(dev, flash, page, ctrl_idx);
+       if (ret) {
+               return ret;
+       }
+
+       length = count * (dev->hw_info.page_size + dev->hw_info.oob_size);
+
+       /* write data to ram */
+       /*ret = ssd_ram_write(dev, buf, length, dev->hw_info.nand_wbuff_base, ctrl_idx);
+       if (ret) {
+               return ret;
+       }*/
+
+       buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_TODEVICE);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       ret = dma_mapping_error(buf_dma);
+#else
+       ret = dma_mapping_error(&(dev->pdev->dev), buf_dma);
+#endif
+       if (ret) {
+               hio_warn("%s: unable to map write DMA buffer\n", dev->name);
+               goto out_dma_mapping;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               flash = ((uint32_t)flash << 1) | (uint32_t)chip;
+               chip = 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_NAND_WRITE;
+       msg->ctrl_idx = ctrl_idx;
+       msg->chip_no = flash;
+       msg->chip_ce = chip;
+
+       msg->page_no = page;
+       msg->page_count = count;
+       msg->buf = buf_dma;
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_TODEVICE);
+
+out_dma_mapping:
+       return ret;
+}
+
+static int ssd_nand_erase(struct ssd_device *dev, int flash, int chip, int page, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       int ret = 0;
+
+       ret = ssd_check_flash(dev, flash, page, ctrl_idx);
+       if (ret) {
+               return ret;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               flash = ((uint32_t)flash << 1) | (uint32_t)chip;
+               chip = 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_NAND_ERASE;
+       msg->ctrl_idx = ctrl_idx;
+       msg->chip_no = flash;
+       msg->chip_ce = chip;
+       msg->page_no = page;
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+static int ssd_update_bbt(struct ssd_device *dev, int flash, int ctrl_idx)
+{
+       struct ssd_nand_op_msg *msg;
+       struct ssd_flush_msg *fmsg;
+       int ret = 0;
+
+       ret = ssd_check_flash(dev, flash, 0, ctrl_idx);
+       if (ret) {
+               return ret;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               fmsg = (struct ssd_flush_msg *)msg;
+
+               fmsg->fun = SSD_FUNC_FLUSH;
+               fmsg->flag = 0x1;
+               fmsg->flash = flash;
+               fmsg->ctrl_idx = ctrl_idx;
+       } else {
+               msg->fun = SSD_FUNC_FLUSH;
+               msg->flag = 0x1;
+               msg->chip_no = flash;
+               msg->ctrl_idx = ctrl_idx;
+       }
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+/* flash controller init state */
+static int __ssd_check_init_state(struct ssd_device *dev)
+{
+       uint32_t *init_state = NULL;
+       int reg_base, reg_sz;
+       int max_wait = SSD_INIT_MAX_WAIT;
+       int init_wait = 0;
+       int i, j, k;
+       int ch_start = 0;
+
+/*
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               ssd_reg32_write(dev->ctrlp + SSD_CTRL_TEST_REG0 + i * 8, test_data);
+               read_data = ssd_reg32_read(dev->ctrlp + SSD_CTRL_TEST_REG0 + i * 8);
+               if (read_data == ~test_data) {
+                       //dev->hw_info.nr_ctrl++;
+                       dev->hw_info.nr_ctrl_map |= 1<<i;
+               }
+       }
+*/
+
+/*
+       read_data = ssd_reg32_read(dev->ctrlp + SSD_READY_REG);
+       j=0;
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               if (((read_data>>i) & 0x1) == 0) {
+                       j++;
+               }
+       }
+
+       if (dev->hw_info.nr_ctrl != j) {
+               printk(KERN_WARNING "%s: nr_ctrl mismatch: %d %d\n", dev->name, dev->hw_info.nr_ctrl, j);
+               return -1;
+       }
+*/
+
+/*
+       init_state = ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0);
+       for (j=1; j<dev->hw_info.nr_ctrl;j++) {
+               if (init_state != ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0 + j*8)) {
+                       printk(KERN_WARNING "SSD_FLASH_INFO_REG[%d], not match\n", j);
+                       return -1;
+                       }
+               }
+*/
+
+/*     init_state = ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0);
+       for (j=1; j<dev->hw_info.nr_ctrl; j++) {
+               if (init_state != ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + j*16)) {
+                       printk(KERN_WARNING "SSD_CHIP_INFO_REG Lo [%d], not match\n", j);
+                       return -1;
+                       }
+               }
+
+       init_state = ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + 8);
+       for (j=1; j<dev->hw_info.nr_ctrl; j++) {
+               if (init_state != ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + 8 + j*16)) {
+                       printk(KERN_WARNING "SSD_CHIP_INFO_REG Hi [%d], not match\n", j);
+                       return -1;
+                       }
+               }
+*/
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               max_wait = SSD_INIT_MAX_WAIT_V3_2;
+       }
+
+       reg_base = dev->protocol_info.init_state_reg;
+       reg_sz = dev->protocol_info.init_state_reg_sz;
+
+       init_state = (uint32_t *)kmalloc(reg_sz, GFP_KERNEL);
+       if (!init_state) {
+               return -ENOMEM;
+       }
+
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+check_init:
+               for (j=0, k=0; j<reg_sz; j+=sizeof(uint32_t), k++) {
+                       init_state[k] = ssd_reg32_read(dev->ctrlp + reg_base + j);
+               }
+
+               if (dev->protocol_info.ver > SSD_PROTOCOL_V3) {
+                       /* just check the last bit, no need to check all channel */
+                       ch_start = dev->hw_info.max_ch - 1;
+               } else {
+                       ch_start = 0;
+               }
+
+               for (j=0; j<dev->hw_info.nr_chip; j++) {
+                       for (k=ch_start; k<dev->hw_info.max_ch; k++) {
+                               if (test_bit((j*dev->hw_info.max_ch + k), (void *)init_state)) {
+                                       continue;
+                               }
+
+                               init_wait++;
+                               if (init_wait <= max_wait) {
+                                       msleep(SSD_INIT_WAIT);
+                                       goto check_init;
+                               } else {
+                                       if (k < dev->hw_info.nr_ch) {
+                                               hio_warn("%s: controller %d chip %d ch %d init failed\n", 
+                                                       dev->name, i, j, k);
+                                       } else {
+                                               hio_warn("%s: controller %d chip %d init failed\n", 
+                                                       dev->name, i, j);
+                                       }
+
+                                       kfree(init_state);
+                                       return -1;
+                               }
+                       }
+               }
+               reg_base += reg_sz;
+       }
+       //printk(KERN_WARNING "%s: init wait %d\n", dev->name, init_wait);
+
+       kfree(init_state);
+       return 0;
+}
+
+static int ssd_check_init_state(struct ssd_device *dev)
+{
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               return 0;
+       }
+
+       return __ssd_check_init_state(dev);
+}
+
+static void ssd_reset_resp_ptr(struct ssd_device *dev);
+
+/* reset flash controller etc */
+static int __ssd_reset(struct ssd_device *dev, int type)
+{
+       if (type < SSD_RST_NOINIT || type > SSD_RST_FULL) {
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->fw_mutex);
+
+       if (type == SSD_RST_NOINIT) {   //no init
+               ssd_reg32_write(dev->ctrlp + SSD_RESET_REG, SSD_RESET_NOINIT);
+       } else if (type == SSD_RST_NORMAL) {    //reset & init
+               ssd_reg32_write(dev->ctrlp + SSD_RESET_REG, SSD_RESET);
+       } else {        // full reset
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                       mutex_unlock(&dev->fw_mutex);
+                       return -EINVAL;
+               }
+
+               ssd_reg32_write(dev->ctrlp + SSD_FULL_RESET_REG, SSD_RESET_FULL);
+
+               /* ?? */
+               ssd_reset_resp_ptr(dev);
+       }
+
+#ifdef SSD_OT_PROTECT
+       dev->ot_delay = 0;
+#endif
+
+       msleep(1000);
+
+       /* xx */
+       ssd_set_flush_timeout(dev, dev->wmode);
+
+       mutex_unlock(&dev->fw_mutex);
+       ssd_gen_swlog(dev, SSD_LOG_RESET, (uint32_t)type);
+
+       return __ssd_check_init_state(dev);
+}
+
+static int ssd_save_md(struct ssd_device *dev)
+{
+       struct ssd_nand_op_msg *msg;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       if (!dev->save_md) {
+               return 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_FLUSH;
+       msg->flag = 0x2;
+       msg->ctrl_idx = 0;
+       msg->chip_no = 0;
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+static int ssd_barrier_save_md(struct ssd_device *dev)
+{
+       struct ssd_nand_op_msg *msg;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return 0;
+       }
+
+       if (!dev->save_md) {
+               return 0;
+       }
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       msg->fun = SSD_FUNC_FLUSH;
+       msg->flag = 0x2;
+       msg->ctrl_idx = 0;
+       msg->chip_no = 0;
+
+       ret = ssd_do_barrier_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+static int ssd_flush(struct ssd_device *dev)
+{
+       struct ssd_nand_op_msg *msg;
+       struct ssd_flush_msg *fmsg;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               fmsg = (struct ssd_flush_msg *)msg;
+
+               fmsg->fun = SSD_FUNC_FLUSH;
+               fmsg->flag = 0;
+               fmsg->ctrl_idx = 0;
+               fmsg->flash = 0;
+       } else {
+               msg->fun = SSD_FUNC_FLUSH;
+               msg->flag = 0;
+               msg->ctrl_idx = 0;
+               msg->chip_no = 0;
+       }
+
+       ret = ssd_do_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+static int ssd_barrier_flush(struct ssd_device *dev)
+{
+       struct ssd_nand_op_msg *msg;
+       struct ssd_flush_msg *fmsg;
+       int ret = 0;
+
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+       msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev);
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               fmsg = (struct ssd_flush_msg *)msg;
+
+               fmsg->fun = SSD_FUNC_FLUSH;
+               fmsg->flag = 0;
+               fmsg->ctrl_idx = 0;
+               fmsg->flash = 0;
+       } else {
+               msg->fun = SSD_FUNC_FLUSH;
+               msg->flag = 0;
+               msg->ctrl_idx = 0;
+               msg->chip_no = 0;
+       }
+
+       ret = ssd_do_barrier_request(dev, WRITE, msg, NULL);
+       ssd_put_dmsg(msg);
+
+       return ret;
+}
+
+#define SSD_WMODE_BUFFER_TIMEOUT       0x00c82710
+#define SSD_WMODE_BUFFER_EX_TIMEOUT    0x000500c8
+#define SSD_WMODE_FUA_TIMEOUT          0x000503E8
+static void ssd_set_flush_timeout(struct ssd_device *dev, int m)
+{
+       uint32_t to;
+       uint32_t val = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) {
+               return;
+       }
+
+       switch(m) {
+               case SSD_WMODE_BUFFER:
+                       to = SSD_WMODE_BUFFER_TIMEOUT;
+                       break;
+               case SSD_WMODE_BUFFER_EX:
+                       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2_1) {
+                               to = SSD_WMODE_BUFFER_EX_TIMEOUT;
+                       } else {
+                               to = SSD_WMODE_BUFFER_TIMEOUT;
+                       }
+                       break;
+               case SSD_WMODE_FUA:
+                       to = SSD_WMODE_FUA_TIMEOUT;
+                       break;
+               default:
+                       return;
+       }
+
+       val = (((uint32_t)((uint32_t)m & 0x3) << 28) | to);
+
+       ssd_reg32_write(dev->ctrlp + SSD_FLUSH_TIMEOUT_REG, val);
+}
+
+static int ssd_do_switch_wmode(struct ssd_device *dev, int m)
+{
+       int ret = 0;
+
+       ret = ssd_barrier_start(dev);
+       if (ret) {
+               goto out;
+       }
+
+       ret = ssd_barrier_flush(dev);
+       if (ret) {
+               goto out_barrier_end;
+       }
+
+       /* set contoller flush timeout */
+       ssd_set_flush_timeout(dev, m);
+
+       dev->wmode = m;
+       mb();
+
+out_barrier_end:
+       ssd_barrier_end(dev);
+out:
+       return ret;
+}
+
+static int ssd_switch_wmode(struct ssd_device *dev, int m)
+{
+       int default_wmode;
+       int next_wmode;
+       int ret = 0;
+
+       if (!test_bit(SSD_ONLINE, &dev->state)) {
+               return -ENODEV;
+       }
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               default_wmode = SSD_WMODE_BUFFER;
+       } else {
+               default_wmode = SSD_WMODE_BUFFER_EX;
+       }
+
+       if (SSD_WMODE_AUTO == m) {
+               /* battery fault ? */
+               if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                       next_wmode = SSD_WMODE_FUA;
+               } else {
+                       next_wmode = default_wmode;
+               }
+       } else if (SSD_WMODE_DEFAULT == m) {
+               next_wmode = default_wmode;
+       } else {
+               next_wmode = m;
+       }
+
+       if (next_wmode != dev->wmode) {
+               hio_warn("%s: switch write mode (%d -> %d)\n", dev->name, dev->wmode, next_wmode);
+               ret = ssd_do_switch_wmode(dev, next_wmode);
+               if (ret) {
+                       hio_err("%s: can not switch write mode (%d -> %d)\n", dev->name, dev->wmode, next_wmode);
+               }
+       }
+
+       return ret;
+}
+
+static int ssd_init_wmode(struct ssd_device *dev)
+{
+       int default_wmode;
+       int ret = 0;
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               default_wmode = SSD_WMODE_BUFFER;
+       } else {
+               default_wmode = SSD_WMODE_BUFFER_EX;
+       }
+
+       /* dummy mode */
+       if (SSD_WMODE_AUTO == dev->user_wmode) {
+               /* battery fault ? */
+               if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                       dev->wmode = SSD_WMODE_FUA;
+               } else {
+                       dev->wmode = default_wmode;
+               }
+       } else if (SSD_WMODE_DEFAULT == dev->user_wmode) {
+               dev->wmode = default_wmode;
+       } else {
+               dev->wmode = dev->user_wmode;
+       }
+       ssd_set_flush_timeout(dev, dev->wmode);
+
+       return ret;
+}
+
+static int __ssd_set_wmode(struct ssd_device *dev, int m)
+{
+       int ret = 0;
+
+       /* not support old fw*/
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) {
+               ret = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (m < SSD_WMODE_BUFFER || m > SSD_WMODE_DEFAULT) {
+               ret = -EINVAL;
+               goto out;
+       }
+       
+       ssd_gen_swlog(dev, SSD_LOG_SET_WMODE, m);
+
+       dev->user_wmode = m;
+
+       ret = ssd_switch_wmode(dev, dev->user_wmode);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       return ret;
+}
+
+int ssd_set_wmode(struct block_device *bdev, int m)
+{
+       struct ssd_device *dev;
+
+       if (!bdev || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       return __ssd_set_wmode(dev, m);
+}
+
+static int ssd_do_reset(struct ssd_device *dev)
+{
+       int ret = 0;
+
+       if (test_and_set_bit(SSD_RESETING, &dev->state)) {
+               return 0;
+       }
+
+       ssd_stop_workq(dev);
+
+       ret = ssd_barrier_start(dev);
+       if (ret) {
+               goto out;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               /* old reset */
+               ret = __ssd_reset(dev, SSD_RST_NORMAL);
+       } else {
+               /* full reset */
+               //ret = __ssd_reset(dev, SSD_RST_FULL);
+               ret = __ssd_reset(dev, SSD_RST_NORMAL);
+       }
+       if (ret) {
+               goto out_barrier_end;
+       }
+
+out_barrier_end:
+       ssd_barrier_end(dev);
+out:
+       ssd_start_workq(dev);
+       test_and_clear_bit(SSD_RESETING, &dev->state);
+       return ret;
+}
+
+static int ssd_full_reset(struct ssd_device *dev)
+{
+       int ret = 0;
+
+       if (test_and_set_bit(SSD_RESETING, &dev->state)) {
+               return 0;
+       }
+
+       ssd_stop_workq(dev);
+
+       ret = ssd_barrier_start(dev);
+       if (ret) {
+               goto out;
+       }
+
+       ret = ssd_barrier_flush(dev);
+       if (ret) {
+               goto out_barrier_end;
+       }
+
+       ret = ssd_barrier_save_md(dev);
+       if (ret) {
+               goto out_barrier_end;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               /* old reset */
+               ret = __ssd_reset(dev, SSD_RST_NORMAL);
+       } else {
+               /* full reset */
+               //ret = __ssd_reset(dev, SSD_RST_FULL);
+               ret = __ssd_reset(dev, SSD_RST_NORMAL);
+       }
+       if (ret) {
+               goto out_barrier_end;
+       }
+
+out_barrier_end:
+       ssd_barrier_end(dev);
+out:
+       ssd_start_workq(dev);
+       test_and_clear_bit(SSD_RESETING, &dev->state);
+       return ret;
+}
+
+int ssd_reset(struct block_device *bdev)
+{
+       struct ssd_device *dev;
+
+       if (!bdev || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+
+       return ssd_full_reset(dev);
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static int ssd_issue_flush_fn(struct request_queue *q, struct gendisk *disk, 
+               sector_t *error_sector)
+{
+       struct ssd_device *dev = q->queuedata;
+
+       return ssd_flush(dev);
+}
+#endif
+
+void ssd_submit_pbio(struct request_queue *q, struct bio *bio)
+{
+       struct ssd_device *dev = q->queuedata;
+#ifdef SSD_QUEUE_PBIO
+       int ret = -EBUSY;
+#endif
+
+       if (!test_bit(SSD_ONLINE, &dev->state)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -ENODEV);
+#else
+               bio_endio(bio, bio->bi_size, -ENODEV);
+#endif
+               goto out;
+       }
+
+#ifdef SSD_DEBUG_ERR
+       if (atomic_read(&dev->tocnt)) {
+               hio_warn("%s: IO rejected because of IO timeout!\n", dev->name);
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EIO);
+#else
+               bio_endio(bio, bio->bi_size, -EIO);
+#endif
+               goto out;
+       }
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32))
+       if (unlikely(bio_barrier(bio))) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36))
+       if (unlikely(bio_rw_flagged(bio, BIO_RW_BARRIER))) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37))
+       if (unlikely(bio->bi_rw & REQ_HARDBARRIER)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#else
+       //xx
+       if (unlikely(bio->bi_rw & REQ_FUA)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#endif
+
+        if (unlikely(dev->readonly && bio_data_dir(bio) == WRITE)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EROFS);
+#else
+               bio_endio(bio, bio->bi_size, -EROFS);
+#endif
+               goto out;
+       }
+
+#ifdef SSD_QUEUE_PBIO
+       if (0 == atomic_read(&dev->in_sendq)) {
+               ret = __ssd_submit_pbio(dev, bio, 0);
+       }
+
+       if (ret) {
+               (void)test_and_set_bit(BIO_SSD_PBIO, &bio->bi_flags);
+               ssd_queue_bio(dev, bio);
+       }
+#else
+       __ssd_submit_pbio(dev, bio, 1);
+#endif
+
+out:
+       return;
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0))
+static int ssd_make_request(struct request_queue *q, struct bio *bio)
+#else
+static void ssd_make_request(struct request_queue *q, struct bio *bio)
+#endif
+{
+       struct ssd_device *dev = q->queuedata;
+       int ret = -EBUSY;
+
+       if (!test_bit(SSD_ONLINE, &dev->state)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -ENODEV);
+#else
+               bio_endio(bio, bio->bi_size, -ENODEV);
+#endif
+               goto out;
+       }
+
+#ifdef SSD_DEBUG_ERR
+       if (atomic_read(&dev->tocnt)) {
+               hio_warn("%s: IO rejected because of IO timeout!\n", dev->name);
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EIO);
+#else
+               bio_endio(bio, bio->bi_size, -EIO);
+#endif
+               goto out;
+       }
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32))
+       if (unlikely(bio_barrier(bio))) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36))
+       if (unlikely(bio_rw_flagged(bio, BIO_RW_BARRIER))) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37))
+       if (unlikely(bio->bi_rw & REQ_HARDBARRIER)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+#else
+       //xx
+       if (unlikely(bio->bi_rw & REQ_FUA)) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
+               bio_endio(bio, -EOPNOTSUPP);
+#else
+               bio_endio(bio, bio->bi_size, -EOPNOTSUPP);
+#endif
+               goto out;
+       }
+
+       /* writeback_cache_control.txt: REQ_FLUSH requests without data can be completed successfully without doing any work */
+       if (unlikely((bio->bi_rw & REQ_FLUSH) && !bio_sectors(bio))) {
+               bio_endio(bio, 0);
+               goto out;
+       }
+
+#endif
+
+       if (0 == atomic_read(&dev->in_sendq)) {
+               ret = ssd_submit_bio(dev, bio, 0);
+       }
+
+       if (ret) {
+               ssd_queue_bio(dev, bio);
+       }
+
+out:
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0))
+       return 0;
+#else
+       return;
+#endif
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16))
+static int ssd_block_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+       struct ssd_device *dev;
+
+       if (!bdev) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+
+       geo->heads = 4;
+       geo->sectors = 16;
+       geo->cylinders = (dev->hw_info.size & ~0x3f) >> 6;
+       return 0;
+}
+#endif
+
+static void ssd_cleanup_blkdev(struct ssd_device *dev);
+static int ssd_init_blkdev(struct ssd_device *dev);
+static int ssd_ioctl_common(struct ssd_device *dev, unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       void __user *buf = NULL;
+       void *kbuf = NULL;
+       int ret = 0;
+
+       switch (cmd) {
+               case SSD_CMD_GET_PROTOCOL_INFO:
+                       if (copy_to_user(argp, &dev->protocol_info, sizeof(struct ssd_protocol_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_GET_HW_INFO:
+                       if (copy_to_user(argp, &dev->hw_info, sizeof(struct ssd_hw_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_GET_ROM_INFO:
+                       if (copy_to_user(argp, &dev->rom_info, sizeof(struct ssd_rom_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_GET_SMART: {
+                       struct ssd_smart smart;
+                       int i;
+
+                       memcpy(&smart, &dev->smart, sizeof(struct ssd_smart));
+
+                       mutex_lock(&dev->gd_mutex);
+                       ssd_update_smart(dev, &smart);
+                       mutex_unlock(&dev->gd_mutex);
+
+                       /* combine the volatile log info */
+                       if (dev->log_info.nr_log) {
+                               for (i=0; i<SSD_LOG_NR_LEVEL; i++) {
+                                       smart.log_info.stat[i] += dev->log_info.stat[i];
+                               }
+                       }
+
+                       if (copy_to_user(argp, &smart, sizeof(struct ssd_smart))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_GET_IDX:
+                       if (copy_to_user(argp, &dev->idx, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_GET_AMOUNT: {
+                       int nr_ssd = atomic_read(&ssd_nr);
+                       if (copy_to_user(argp, &nr_ssd, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_TO_INFO: {
+                       int tocnt = atomic_read(&dev->tocnt);
+
+                       if (copy_to_user(argp, &tocnt, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_DRV_VER: {
+                       char ver[] = DRIVER_VERSION;
+                       int len = sizeof(ver);
+
+                       if (len > (DRIVER_VERSION_LEN - 1)) {
+                               len = (DRIVER_VERSION_LEN - 1);
+                       }
+                       if (copy_to_user(argp, ver, len)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_BBACC_INFO: {
+                       struct ssd_acc_info acc;
+
+                       mutex_lock(&dev->fw_mutex);
+                       ret = ssd_bb_acc(dev, &acc);
+                       mutex_unlock(&dev->fw_mutex);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &acc, sizeof(struct ssd_acc_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_ECACC_INFO: {
+                       struct ssd_acc_info acc;
+
+                       mutex_lock(&dev->fw_mutex);
+                       ret = ssd_ec_acc(dev, &acc);
+                       mutex_unlock(&dev->fw_mutex);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &acc, sizeof(struct ssd_acc_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_HW_INFO_EXT:
+                       if (copy_to_user(argp, &dev->hw_info_ext, sizeof(struct ssd_hw_info_extend))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_REG_READ: {
+                       struct ssd_reg_op_info reg_info;
+
+                       if (copy_from_user(&reg_info, argp, sizeof(struct ssd_reg_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       if (reg_info.offset > dev->mmio_len-sizeof(uint32_t)) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       reg_info.value = ssd_reg32_read(dev->ctrlp + reg_info.offset);
+                       if (copy_to_user(argp, &reg_info, sizeof(struct ssd_reg_op_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_REG_WRITE: {
+                       struct ssd_reg_op_info reg_info;
+
+                       if (copy_from_user(&reg_info, argp, sizeof(struct ssd_reg_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       if (reg_info.offset > dev->mmio_len-sizeof(uint32_t)) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       ssd_reg32_write(dev->ctrlp + reg_info.offset, reg_info.value);
+
+                       break;
+               }
+
+               case SSD_CMD_SPI_READ: {
+                       struct ssd_spi_op_info spi_info;
+                       uint32_t off, size;
+
+                       if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       off = spi_info.off;
+                       size = spi_info.len;
+                       buf = spi_info.buf;
+
+                       if (size > dev->rom_info.size || 0 == size || (off + size) > dev->rom_info.size) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(size, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       ret = ssd_spi_page_read(dev, kbuf, off, size);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, size)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_SPI_WRITE: {
+                       struct ssd_spi_op_info spi_info;
+                       uint32_t off, size;
+
+                       if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       off = spi_info.off;
+                       size = spi_info.len;
+                       buf = spi_info.buf;
+
+                       if (size > dev->rom_info.size || 0 == size || (off + size) > dev->rom_info.size) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(size, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       if (copy_from_user(kbuf, buf, size)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_spi_page_write(dev, kbuf, off, size);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_SPI_ERASE: {
+                       struct ssd_spi_op_info spi_info;
+                       uint32_t off;
+
+                       if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       off = spi_info.off;
+
+                       if ((off + dev->rom_info.block_size) > dev->rom_info.size) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       ret = ssd_spi_block_erase(dev, off);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_I2C_READ: {
+                       struct ssd_i2c_op_info i2c_info;
+                       uint8_t saddr;
+                       uint8_t rsize;
+
+                       if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = i2c_info.saddr;
+                       rsize = i2c_info.rsize;
+                       buf = i2c_info.rbuf;
+
+                       if (rsize <= 0 || rsize > SSD_I2C_MAX_DATA) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(rsize, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       ret = ssd_i2c_read(dev, saddr, rsize, kbuf);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, rsize)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_I2C_WRITE: {
+                       struct ssd_i2c_op_info i2c_info;
+                       uint8_t saddr;
+                       uint8_t wsize;
+
+                       if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = i2c_info.saddr;
+                       wsize = i2c_info.wsize;
+                       buf = i2c_info.wbuf;
+
+                       if (wsize <= 0 || wsize > SSD_I2C_MAX_DATA) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(wsize, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       if (copy_from_user(kbuf, buf, wsize)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_i2c_write(dev, saddr, wsize, kbuf);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_I2C_WRITE_READ: {
+                       struct ssd_i2c_op_info i2c_info;
+                       uint8_t saddr;
+                       uint8_t wsize;
+                       uint8_t rsize;
+                       uint8_t size;
+
+                       if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = i2c_info.saddr;
+                       wsize = i2c_info.wsize;
+                       rsize = i2c_info.rsize;
+                       buf = i2c_info.wbuf;
+
+                       if (wsize <= 0 || wsize > SSD_I2C_MAX_DATA) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       if (rsize <= 0 || rsize > SSD_I2C_MAX_DATA) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       size = wsize + rsize;
+
+                       kbuf = kmalloc(size, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       if (copy_from_user((kbuf + rsize), buf, wsize)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       buf = i2c_info.rbuf;
+
+                       ret = ssd_i2c_write_read(dev, saddr, wsize, (kbuf + rsize), rsize, kbuf);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, rsize)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_SEND_BYTE: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       buf = smbus_info.buf;
+                       size = 1;
+
+                       if (copy_from_user(smb_data, buf, size)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_smbus_send_byte(dev, saddr, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_RECEIVE_BYTE: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       buf = smbus_info.buf;
+                       size = 1;
+
+                       ret = ssd_smbus_receive_byte(dev, saddr, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(buf, smb_data, size)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_WRITE_BYTE: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = 1;
+
+                       if (copy_from_user(smb_data, buf, size)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_smbus_write_byte(dev, saddr, command, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_READ_BYTE: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = 1;
+
+                       ret = ssd_smbus_read_byte(dev, saddr, command, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(buf, smb_data, size)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_WRITE_WORD: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = 2;
+
+                       if (copy_from_user(smb_data, buf, size)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_smbus_write_word(dev, saddr, command, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_READ_WORD: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = 2;
+
+                       ret = ssd_smbus_read_word(dev, saddr, command, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(buf, smb_data, size)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_WRITE_BLOCK: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = smbus_info.size;
+
+                       if (size > SSD_SMBUS_BLOCK_MAX) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       if (copy_from_user(smb_data, buf, size)) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_smbus_write_block(dev, saddr, command, size, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_SMBUS_READ_BLOCK: {
+                       struct ssd_smbus_op_info smbus_info;
+                       uint8_t smb_data[SSD_SMBUS_BLOCK_MAX];
+                       uint8_t saddr;
+                       uint8_t command;
+                       uint8_t size;
+
+                       if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       saddr = smbus_info.saddr;
+                       command = smbus_info.cmd;
+                       buf = smbus_info.buf;
+                       size = smbus_info.size;
+
+                       if (size > SSD_SMBUS_BLOCK_MAX) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       ret = ssd_smbus_read_block(dev, saddr, command, size, smb_data);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(buf, smb_data, size)) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_BM_GET_VER: {
+                       uint16_t ver;
+
+                       ret = ssd_bm_get_version(dev, &ver);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &ver, sizeof(uint16_t))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_BM_GET_NR_CAP: {
+                       int nr_cap;
+
+                       ret = ssd_bm_nr_cap(dev, &nr_cap);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &nr_cap, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_BM_CAP_LEARNING: {
+                       ret = ssd_bm_enter_cap_learning(dev);
+
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_CAP_LEARN: {
+                       uint32_t cap = 0;
+
+                       ret = ssd_cap_learn(dev, &cap);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &cap, sizeof(uint32_t))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_GET_CAP_STATUS: {
+                       int cap_status = 0;
+
+                       if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                               cap_status = 1;
+                       }
+
+                       if (copy_to_user(argp, &cap_status, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_RAM_READ: {
+                       struct ssd_ram_op_info ram_info;
+                       uint64_t ofs;
+                       uint32_t length;
+                       size_t rlen, len = dev->hw_info.ram_max_len;
+                       int ctrl_idx;
+
+                       if (copy_from_user(&ram_info, argp, sizeof(struct ssd_ram_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ofs = ram_info.start;
+                       length = ram_info.length;
+                       buf = ram_info.buf;
+                       ctrl_idx = ram_info.ctrl_idx;
+
+                       if (ofs >= dev->hw_info.ram_size || length > dev->hw_info.ram_size || 0 == length || (ofs + length) > dev->hw_info.ram_size) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(len, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       for (rlen=0; rlen<length; rlen+=len, buf+=len, ofs+=len) {
+                               if ((length - rlen) < len) {
+                                       len = length - rlen;
+                               }
+
+                               ret = ssd_ram_read(dev, kbuf, len, ofs, ctrl_idx);
+                               if (ret) {
+                                       break;
+                               }
+
+                               if (copy_to_user(buf, kbuf, len)) {
+                                       ret = -EFAULT;
+                                       break;
+                               }
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_RAM_WRITE: {
+                       struct ssd_ram_op_info ram_info;
+                       uint64_t ofs;
+                       uint32_t length;
+                       size_t wlen, len = dev->hw_info.ram_max_len;
+                       int ctrl_idx;
+
+                       if (copy_from_user(&ram_info, argp, sizeof(struct ssd_ram_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       ofs = ram_info.start;
+                       length = ram_info.length;
+                       buf = ram_info.buf;
+                       ctrl_idx = ram_info.ctrl_idx;
+
+                       if (ofs >= dev->hw_info.ram_size || length > dev->hw_info.ram_size || 0 == length || (ofs + length) > dev->hw_info.ram_size) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       kbuf = kmalloc(len, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       for (wlen=0; wlen<length; wlen+=len, buf+=len, ofs+=len) {
+                               if ((length - wlen) < len) {
+                                       len = length - wlen;
+                               }
+
+                               if (copy_from_user(kbuf, buf, len)) {
+                                       ret = -EFAULT;
+                                       break;
+                               }
+
+                               ret = ssd_ram_write(dev, kbuf, len, ofs, ctrl_idx);
+                               if (ret) {
+                                       break;
+                               }
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_NAND_READ_ID: {
+                       struct ssd_flash_op_info flash_info;
+                       int chip_no, chip_ce, length, ctrl_idx;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       chip_no = flash_info.flash;
+                       chip_ce = flash_info.chip;
+                       ctrl_idx = flash_info.ctrl_idx;
+                       buf = flash_info.buf;
+                       length = dev->hw_info.id_size;
+
+                       //kbuf = kmalloc(length, GFP_KERNEL);
+                       kbuf = kmalloc(SSD_NAND_ID_BUFF_SZ, GFP_KERNEL); //xx
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+                       memset(kbuf, 0, length);
+
+                       ret = ssd_nand_read_id(dev, kbuf, chip_no, chip_ce, ctrl_idx);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, length)) {
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       kfree(kbuf);
+
+                       break;
+               }
+
+               case SSD_CMD_NAND_READ: {       //with oob
+                       struct ssd_flash_op_info flash_info;
+                       uint32_t length;
+                       int flash, chip, page, ctrl_idx;
+                       int err = 0;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       flash = flash_info.flash;
+                       chip = flash_info.chip;
+                       page = flash_info.page;
+                       buf = flash_info.buf;
+                       ctrl_idx = flash_info.ctrl_idx;
+
+                       length = dev->hw_info.page_size + dev->hw_info.oob_size;
+
+                       kbuf = kmalloc(length, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       err = ret = ssd_nand_read_w_oob(dev, kbuf, flash, chip, page, 1, ctrl_idx);
+                       if (ret && (-EIO != ret)) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, length)) {
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = err;
+
+                       kfree(kbuf);
+                       break;
+               }
+
+               case SSD_CMD_NAND_WRITE: {
+                       struct ssd_flash_op_info flash_info;
+                       int flash, chip, page, ctrl_idx;
+                       uint32_t length;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       flash = flash_info.flash;
+                       chip = flash_info.chip;
+                       page = flash_info.page;
+                       buf = flash_info.buf;
+                       ctrl_idx = flash_info.ctrl_idx;
+
+                       length = dev->hw_info.page_size + dev->hw_info.oob_size;
+
+                       kbuf = kmalloc(length, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       if (copy_from_user(kbuf, buf, length)) {
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_nand_write(dev, kbuf, flash, chip, page, 1, ctrl_idx);
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       kfree(kbuf);
+                       break;
+               }
+
+               case SSD_CMD_NAND_ERASE: {
+                       struct ssd_flash_op_info flash_info;
+                       int flash, chip, page, ctrl_idx;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       flash = flash_info.flash;
+                       chip = flash_info.chip;
+                       page = flash_info.page;
+                       ctrl_idx = flash_info.ctrl_idx;
+
+                       if ((page % dev->hw_info.page_count) != 0) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       //hio_warn("erase fs = %llx\n", ofs);
+                       ret = ssd_nand_erase(dev, flash, chip, page, ctrl_idx);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_NAND_READ_EXT: {   //ingore EIO
+                       struct ssd_flash_op_info flash_info;
+                       uint32_t length;
+                       int flash, chip, page, ctrl_idx;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       flash = flash_info.flash;
+                       chip = flash_info.chip;
+                       page = flash_info.page;
+                       buf = flash_info.buf;
+                       ctrl_idx = flash_info.ctrl_idx;
+
+                       length = dev->hw_info.page_size + dev->hw_info.oob_size;
+
+                       kbuf = kmalloc(length, GFP_KERNEL);
+                       if (!kbuf) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       ret = ssd_nand_read_w_oob(dev, kbuf, flash, chip, page, 1, ctrl_idx);
+                       if (-EIO == ret) {      //ingore EIO
+                               ret = 0;
+                       }
+                       if (ret) {
+                               kfree(kbuf);
+                               break;
+                       }
+
+                       if (copy_to_user(buf, kbuf, length)) {
+                               kfree(kbuf);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       kfree(kbuf);
+                       break;
+               }
+
+               case SSD_CMD_UPDATE_BBT: {
+                       struct ssd_flash_op_info flash_info;
+                       int ctrl_idx, flash;
+
+                       if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ctrl_idx = flash_info.ctrl_idx;
+                       flash = flash_info.flash;
+                       ret = ssd_update_bbt(dev, flash, ctrl_idx);
+                       if (ret) {
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_CLEAR_ALARM:
+                       ssd_clear_alarm(dev);
+                       break;
+
+               case SSD_CMD_SET_ALARM:
+                       ssd_set_alarm(dev);
+                       break;
+
+               case SSD_CMD_RESET:
+                       ret = ssd_do_reset(dev);
+                       break;
+
+               case SSD_CMD_RELOAD_FW:
+                       dev->reload_fw = 1;
+                       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+                               ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FLAG);
+                       } else if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_1_1) {
+                               ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW);
+                               
+                       }
+                       break;
+
+               case SSD_CMD_UNLOAD_DEV: {
+                       if (atomic_read(&dev->refcnt)) {
+                               ret = -EBUSY;
+                               break;
+                       }
+
+                       /* save smart */
+                       ssd_save_smart(dev);
+
+                       ret = ssd_flush(dev);
+                       if (ret) {
+                               break;
+                       }
+
+                       /* cleanup the block device */
+                       if (test_and_clear_bit(SSD_INIT_BD, &dev->state)) {
+                               mutex_lock(&dev->gd_mutex);
+                               ssd_cleanup_blkdev(dev);
+                               mutex_unlock(&dev->gd_mutex);
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_LOAD_DEV: {
+
+                       if (test_bit(SSD_INIT_BD, &dev->state)) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       ret = ssd_init_smart(dev);
+                       if (ret) {
+                               hio_warn("%s: init info: failed\n", dev->name);
+                               break;
+                       }
+
+                       ret = ssd_init_blkdev(dev);
+                       if (ret) {
+                               hio_warn("%s: register block device: failed\n", dev->name);
+                               break;
+                       }
+                       (void)test_and_set_bit(SSD_INIT_BD, &dev->state);
+
+                       break;
+               }
+
+               case SSD_CMD_UPDATE_VP: {
+                       uint32_t val;
+                       uint32_t new_vp, new_vp1 = 0;
+
+                       if (test_bit(SSD_INIT_BD, &dev->state)) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       if (copy_from_user(&new_vp, argp, sizeof(uint32_t))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       if (new_vp > dev->hw_info.max_valid_pages || new_vp <= 0) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       while (new_vp <= dev->hw_info.max_valid_pages) {
+                               ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, new_vp);
+                               msleep(10);
+                               val = ssd_reg32_read(dev->ctrlp + SSD_VALID_PAGES_REG);
+                               if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                                       new_vp1 = val & 0x3FF;
+                               } else {
+                                       new_vp1 = val & 0x7FFF;
+                               }
+
+                               if (new_vp1 == new_vp) {
+                                       break;
+                               }
+
+                               new_vp++;
+                               /*if (new_vp == dev->hw_info.valid_pages) {
+                                       new_vp++;
+                               }*/
+                       }
+
+                       if (new_vp1 != new_vp || new_vp > dev->hw_info.max_valid_pages) {
+                               /* restore */
+                               ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, dev->hw_info.valid_pages);
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &new_vp, sizeof(uint32_t))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, dev->hw_info.valid_pages);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       /* new */
+                       dev->hw_info.valid_pages = new_vp;
+                       dev->hw_info.size = (uint64_t)dev->hw_info.valid_pages * dev->hw_info.page_size;
+                       dev->hw_info.size *= (dev->hw_info.block_count - dev->hw_info.reserved_blks);
+                       dev->hw_info.size *= ((uint64_t)dev->hw_info.nr_data_ch * (uint64_t)dev->hw_info.nr_chip * (uint64_t)dev->hw_info.nr_ctrl);
+
+                       break;
+               }
+
+               case SSD_CMD_FULL_RESET: {
+                       ret = ssd_full_reset(dev);
+                       break;
+               }
+
+               case SSD_CMD_GET_NR_LOG: {
+                       if (copy_to_user(argp, &dev->internal_log.nr_log, sizeof(dev->internal_log.nr_log))) {
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_LOG: {
+                       uint32_t length = dev->rom_info.log_sz;
+
+                       buf = argp;
+
+                       if (copy_to_user(buf, dev->internal_log.log, length)) {
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_LOG_LEVEL: {
+                       int level = 0;
+                       if (copy_from_user(&level, argp, sizeof(int))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       if (level >= SSD_LOG_NR_LEVEL || level < SSD_LOG_LEVEL_INFO) {
+                               level = SSD_LOG_LEVEL_ERR;
+                       }
+
+                       //just for showing log, no need to protect
+                       log_level = level;
+                       break;
+               }
+
+               case SSD_CMD_OT_PROTECT: {
+                       int protect = 0;
+
+                       if (copy_from_user(&protect, argp, sizeof(int))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ssd_set_ot_protect(dev, !!protect);
+                       break;
+               }
+
+               case SSD_CMD_GET_OT_STATUS: {
+                       int status = ssd_get_ot_status(dev, &status);
+
+                       if (copy_to_user(argp, &status, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_CLEAR_LOG: {
+                       ret = ssd_clear_log(dev);
+                       break;
+               }
+
+               case SSD_CMD_CLEAR_SMART: {
+                       ret = ssd_clear_smart(dev);
+                       break;
+               }
+
+               case SSD_CMD_SW_LOG: {
+                       struct ssd_sw_log_info sw_log;
+
+                       if (copy_from_user(&sw_log, argp, sizeof(struct ssd_sw_log_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = ssd_gen_swlog(dev, sw_log.event, sw_log.data);
+                       break;
+               }
+
+               case SSD_CMD_GET_LABEL: {
+
+                       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+                               ret = -EINVAL;
+                               break;
+                       }
+                       
+                       if (copy_to_user(argp, &dev->label, sizeof(struct ssd_label))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_VERSION: {
+                       struct ssd_version_info ver;
+
+                       mutex_lock(&dev->fw_mutex);
+                       ret = __ssd_get_version(dev, &ver);
+                       mutex_unlock(&dev->fw_mutex);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &ver, sizeof(struct ssd_version_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_TEMPERATURE: {
+                       int temp;
+
+                       mutex_lock(&dev->fw_mutex);
+                       ret = __ssd_get_temperature(dev, &temp);
+                       mutex_unlock(&dev->fw_mutex);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &temp, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_BMSTATUS: {
+                       int status;
+
+                       mutex_lock(&dev->fw_mutex);
+                       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+                               if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                                       status = SSD_BMSTATUS_WARNING;
+                               } else {
+                                       status = SSD_BMSTATUS_OK;
+                               }
+                       } else if(dev->protocol_info.ver > SSD_PROTOCOL_V3) {
+                               ret = __ssd_bm_status(dev, &status);
+                       } else {
+                               status = SSD_BMSTATUS_OK;
+                       }
+                       mutex_unlock(&dev->fw_mutex);
+                       if (ret) {
+                               break;
+                       }
+
+                       if (copy_to_user(argp, &status, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_GET_LABEL2: {
+                       void *label;
+                       int length;
+
+                       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                               label = &dev->label;
+                               length = sizeof(struct ssd_label);
+                       } else {
+                               label = &dev->labelv3;
+                               length = sizeof(struct ssd_labelv3);
+                       }
+
+                       if (copy_to_user(argp, label, length)) {
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               case SSD_CMD_FLUSH:
+                       ret = ssd_flush(dev);
+                       if (ret) {
+                               hio_warn("%s: ssd_flush: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               case SSD_CMD_SAVE_MD: {
+                       int save_md = 0;
+
+                       if (copy_from_user(&save_md, argp, sizeof(int))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       dev->save_md = !!save_md;
+                       break;
+               }
+
+               case SSD_CMD_SET_WMODE: {
+                       int new_wmode = 0;
+                       
+                       if (copy_from_user(&new_wmode, argp, sizeof(int))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       ret = __ssd_set_wmode(dev, new_wmode);
+                       if (ret) {
+                               break;
+                       }
+                       
+                       break;
+               }
+
+               case SSD_CMD_GET_WMODE: {
+                       if (copy_to_user(argp, &dev->wmode, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       
+                       break;
+               }
+
+               case SSD_CMD_GET_USER_WMODE: {
+                       if (copy_to_user(argp, &dev->user_wmode, sizeof(int))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       
+                       break;
+               }
+
+               case SSD_CMD_DEBUG: {
+                       struct ssd_debug_info db_info;
+
+                       if (!finject) {
+                               ret = -EOPNOTSUPP;
+                               break;
+                       }
+
+                       if (copy_from_user(&db_info, argp, sizeof(struct ssd_debug_info))) {
+                               hio_warn("%s: copy_from_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       if (db_info.type < SSD_DEBUG_NONE || db_info.type >= SSD_DEBUG_NR) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       /* IO */
+                       if (db_info.type >= SSD_DEBUG_READ_ERR && db_info.type <= SSD_DEBUG_RW_ERR && 
+                               (db_info.data.loc.off + db_info.data.loc.len) > (dev->hw_info.size >> 9)) {
+                               ret = -EINVAL;
+                               break;
+                       }
+
+                       memcpy(&dev->db_info, &db_info, sizeof(struct ssd_debug_info));
+
+#ifdef SSD_OT_PROTECT
+                       /* temperature */
+                       if (db_info.type == SSD_DEBUG_NONE) {
+                               ssd_check_temperature(dev, SSD_OT_TEMP);
+                       } else if (db_info.type == SSD_DEBUG_LOG) {
+                               if (db_info.data.log.event == SSD_LOG_OVER_TEMP) {
+                                       dev->ot_delay = SSD_OT_DELAY;
+                               } else if (db_info.data.log.event == SSD_LOG_NORMAL_TEMP) {
+                                       dev->ot_delay = 0;
+                               }
+                       }
+#endif
+
+                       /* offline */
+                       if (db_info.type == SSD_DEBUG_OFFLINE) {
+                               test_and_clear_bit(SSD_ONLINE, &dev->state);
+                       } else if (db_info.type == SSD_DEBUG_NONE) {
+                               (void)test_and_set_bit(SSD_ONLINE, &dev->state);
+                       }
+
+                       /* log */
+                       if (db_info.type == SSD_DEBUG_LOG && dev->event_call && dev->gd) {
+                               dev->event_call(dev->gd, db_info.data.log.event, 0);
+                       }
+
+                       break;
+               }
+
+               case SSD_CMD_DRV_PARAM_INFO: {
+                       struct ssd_drv_param_info drv_param;
+
+                       memset(&drv_param, 0, sizeof(struct ssd_drv_param_info));
+
+                       drv_param.mode = mode;
+                       drv_param.status_mask = status_mask;
+                       drv_param.int_mode = int_mode;
+                       drv_param.threaded_irq = threaded_irq;
+                       drv_param.log_level = log_level;
+                       drv_param.wmode = wmode;
+                       drv_param.ot_protect = ot_protect;
+                       drv_param.finject = finject;
+
+                       if (copy_to_user(argp, &drv_param, sizeof(struct ssd_drv_param_info))) {
+                               hio_warn("%s: copy_to_user: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+               }
+
+               default:
+                       ret = -EINVAL;
+                       break;
+       }
+
+       return ret;
+}
+
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27))
+static int ssd_block_ioctl(struct inode *inode, struct file *file, 
+               unsigned int cmd, unsigned long arg)
+{
+       struct ssd_device *dev;
+       void __user *argp = (void __user *)arg;
+       int ret = 0;
+
+       if (!inode) {
+               return -EINVAL;
+       }
+       dev = inode->i_bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#else
+static int ssd_block_ioctl(struct block_device *bdev, fmode_t mode, 
+               unsigned int cmd, unsigned long arg)
+{
+       struct ssd_device *dev;
+       void __user *argp = (void __user *)arg;
+       int ret = 0;
+
+       if (!bdev) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#endif
+
+       switch (cmd) {
+               case HDIO_GETGEO: {
+                       struct hd_geometry geo;
+                       geo.cylinders = (dev->hw_info.size & ~0x3f) >> 6;
+                       geo.heads = 4;
+                       geo.sectors = 16;
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27))
+                       geo.start = get_start_sect(inode->i_bdev);
+#else
+                       geo.start = get_start_sect(bdev);
+#endif
+                       if (copy_to_user(argp, &geo, sizeof(geo))) {
+                               ret = -EFAULT;
+                               break;
+                       }
+
+                       break;
+               }
+
+               case BLKFLSBUF:
+                       ret = ssd_flush(dev);
+                       if (ret) {
+                               hio_warn("%s: ssd_flush: failed\n", dev->name);
+                               ret = -EFAULT;
+                               break;
+                       }
+                       break;
+
+               default:
+                       if (!dev->slave) {
+                               ret = ssd_ioctl_common(dev, cmd, arg);
+                       } else {
+                               ret = -EFAULT;
+                       }
+                       break;
+       }
+
+       return ret;
+}
+
+
+static void ssd_free_dev(struct kref *kref)
+{
+       struct ssd_device *dev;
+
+       if (!kref) {
+               return;
+       }
+
+       dev = container_of(kref, struct ssd_device, kref);
+
+       put_disk(dev->gd);
+
+       ssd_put_index(dev->slave, dev->idx);
+
+       kfree(dev);
+}
+
+static void ssd_put(struct ssd_device *dev)
+{
+       kref_put(&dev->kref, ssd_free_dev);
+}
+
+static int ssd_get(struct ssd_device *dev)
+{
+       kref_get(&dev->kref);
+       return 0;
+}
+
+/* block device */
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27))
+static int ssd_block_open(struct inode *inode, struct file *filp)
+{
+       struct ssd_device *dev;
+
+       if (!inode) {
+               return -EINVAL;
+       }
+
+       dev = inode->i_bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#else
+static int ssd_block_open(struct block_device *bdev, fmode_t mode)
+{
+       struct ssd_device *dev;
+
+       if (!bdev) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#endif
+
+       /*if (!try_module_get(dev->owner))
+               return -ENODEV;
+       */
+
+       ssd_get(dev);
+
+       atomic_inc(&dev->refcnt);
+
+       return 0;
+}
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27))
+static int ssd_block_release(struct inode *inode, struct file *filp)
+{
+       struct ssd_device *dev;
+
+       if (!inode) {
+               return -EINVAL;
+       }
+
+       dev = inode->i_bdev->bd_disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0))
+static int ssd_block_release(struct gendisk *disk, fmode_t mode)
+{
+       struct ssd_device *dev;
+
+       if (!disk) {
+               return -EINVAL;
+       }
+
+       dev = disk->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+#else
+static void ssd_block_release(struct gendisk *disk, fmode_t mode)
+{
+       struct ssd_device *dev;
+
+       if (!disk) {
+               return;
+       }
+
+       dev = disk->private_data;
+       if (!dev) {
+               return;
+       }
+#endif
+
+       atomic_dec(&dev->refcnt);
+
+       ssd_put(dev);
+
+       //module_put(dev->owner);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0))
+       return 0;
+#endif
+}
+
+static struct block_device_operations ssd_fops = {
+       .owner          = THIS_MODULE,
+       .open           = ssd_block_open,
+       .release        = ssd_block_release,
+       .ioctl          = ssd_block_ioctl,
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16))
+       .getgeo         = ssd_block_getgeo,
+#endif
+};
+
+static void ssd_init_trim(ssd_device_t *dev)
+{
+#if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)))
+       if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+               return;
+       }
+       queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, dev->rq);
+
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)) || (defined RHEL_MAJOR && RHEL_MAJOR >= 6))
+       dev->rq->limits.discard_zeroes_data = 1;
+       dev->rq->limits.discard_alignment = 4096;
+       dev->rq->limits.discard_granularity = 4096;
+#endif
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2_4) {
+               dev->rq->limits.max_discard_sectors = dev->hw_info.sg_max_sec;
+       } else {
+               dev->rq->limits.max_discard_sectors = (dev->hw_info.sg_max_sec) * (dev->hw_info.cmd_max_sg);
+       }
+#endif
+}
+
+static void ssd_cleanup_queue(struct ssd_device *dev)
+{
+       ssd_wait_io(dev);
+
+       blk_cleanup_queue(dev->rq);
+       dev->rq = NULL;
+}
+
+static int ssd_init_queue(struct ssd_device *dev)
+{
+       dev->rq = blk_alloc_queue(GFP_KERNEL);
+       if (dev->rq == NULL) {
+               hio_warn("%s: alloc queue: failed\n ", dev->name);
+               goto out_init_queue;
+       }
+
+       /* must be first */
+       blk_queue_make_request(dev->rq, ssd_make_request);
+
+#if ((LINUX_VERSION_CODE < KERNEL_VERSION(2,6,34)) && !(defined RHEL_MAJOR && RHEL_MAJOR == 6))
+       blk_queue_max_hw_segments(dev->rq, dev->hw_info.cmd_max_sg);
+       blk_queue_max_phys_segments(dev->rq, dev->hw_info.cmd_max_sg);
+       blk_queue_max_sectors(dev->rq, dev->hw_info.sg_max_sec);
+#else
+       blk_queue_max_segments(dev->rq, dev->hw_info.cmd_max_sg);
+       blk_queue_max_hw_sectors(dev->rq, dev->hw_info.sg_max_sec);
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31))
+       blk_queue_hardsect_size(dev->rq, 512);
+#else
+       blk_queue_logical_block_size(dev->rq, 512);
+#endif
+       /* not work for make_request based drivers(bio) */
+       blk_queue_max_segment_size(dev->rq, dev->hw_info.sg_max_sec << 9);
+
+       blk_queue_bounce_limit(dev->rq, BLK_BOUNCE_HIGH);
+
+       dev->rq->queuedata = dev;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+       blk_queue_issue_flush_fn(dev->rq, ssd_issue_flush_fn);
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28))
+       queue_flag_set_unlocked(QUEUE_FLAG_NONROT, dev->rq);
+#endif
+
+       ssd_init_trim(dev);
+
+       return 0;
+
+out_init_queue:
+       return -ENOMEM;
+}
+
+static void ssd_cleanup_blkdev(struct ssd_device *dev)
+{
+       del_gendisk(dev->gd);
+}
+
+static int ssd_init_blkdev(struct ssd_device *dev)
+{
+       if (dev->gd) {
+               put_disk(dev->gd);
+       }
+
+       dev->gd = alloc_disk(ssd_minors);
+       if (!dev->gd) {
+               hio_warn("%s: alloc_disk fail\n", dev->name);
+               goto out_alloc_gd;
+       }
+       dev->gd->major = dev->major;
+       dev->gd->first_minor = dev->idx * ssd_minors;
+       dev->gd->fops = &ssd_fops;
+       dev->gd->queue = dev->rq;
+       dev->gd->private_data = dev;
+       dev->gd->driverfs_dev = &dev->pdev->dev;
+       snprintf (dev->gd->disk_name, sizeof(dev->gd->disk_name), "%s", dev->name);
+
+       set_capacity(dev->gd, dev->hw_info.size >> 9);
+
+       add_disk(dev->gd);
+
+       return 0;
+
+out_alloc_gd:
+       return -ENOMEM;
+}
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,10))
+static int ssd_ioctl(struct inode *inode, struct file *file, 
+               unsigned int cmd, unsigned long arg)
+#else
+static long ssd_ioctl(struct file *file, 
+               unsigned int cmd, unsigned long arg)
+#endif
+{
+       struct ssd_device *dev;
+
+       if (!file) {
+               return -EINVAL;
+       }
+
+       dev = file->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+
+       return (long)ssd_ioctl_common(dev, cmd, arg);
+}
+
+static int ssd_open(struct inode *inode, struct file *file)
+{
+       struct ssd_device *dev = NULL;
+       struct ssd_device *n = NULL;
+       int idx;
+       int ret = -ENODEV;
+
+       if (!inode || !file) {
+               return -EINVAL;
+       }
+
+       idx = iminor(inode);
+
+       list_for_each_entry_safe(dev, n, &ssd_list, list) {
+               if (dev->idx == idx) {
+                       ret = 0;
+                       break;
+               }
+       }
+
+       if (ret) {
+               return ret;
+       }
+
+       file->private_data = dev;
+
+       ssd_get(dev);
+
+       return 0;
+}
+
+static int ssd_release(struct inode *inode, struct file *file)
+{
+       struct ssd_device *dev;
+
+       if (!file) {
+               return -EINVAL;
+       }
+
+       dev = file->private_data;
+       if (!dev) {
+               return -EINVAL;
+       }
+
+       ssd_put(dev);
+
+       file->private_data = NULL;
+
+       return 0;
+}
+
+static struct file_operations ssd_cfops = {
+       .owner          = THIS_MODULE, 
+       .open           = ssd_open, 
+       .release        = ssd_release, 
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,10))
+       .ioctl          = ssd_ioctl,
+#else
+       .unlocked_ioctl = ssd_ioctl, 
+#endif
+};
+
+static void ssd_cleanup_chardev(struct ssd_device *dev)
+{
+       if (dev->slave) {
+               return;
+       }
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+       class_simple_device_remove(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx));
+       devfs_remove("c%s", dev->name);
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,14))
+       class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx));
+       devfs_remove("c%s", dev->name);
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17))
+       class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx));
+       devfs_remove("c%s", dev->name);
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24))
+       class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx));
+#else
+       device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx));
+#endif
+}
+
+static int ssd_init_chardev(struct ssd_device *dev)
+{
+       int ret = 0;
+
+       if (dev->slave) {
+               return 0;
+       }
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+       ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name);
+       if (ret) {
+               goto out;
+       }
+       class_simple_device_add(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+out:
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,14))
+       ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name);
+       if (ret) {
+               goto out;
+       }
+       class_device_create(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+out:
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17))
+       ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name);
+       if (ret) {
+               goto out;
+       }
+       class_device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+out:
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24))
+       class_device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26))
+       device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), "c%s", dev->name);
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27))
+       device_create_drvdata(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+#else
+       device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name);
+#endif
+
+       return ret;
+}
+
+static int ssd_check_hw(struct ssd_device *dev)
+{
+       uint32_t test_data = 0x55AA5AA5;
+       uint32_t read_data;
+
+       ssd_reg32_write(dev->ctrlp + SSD_BRIDGE_TEST_REG, test_data);
+       read_data = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_TEST_REG);
+       if (read_data != ~(test_data)) {
+               //hio_warn("%s: check bridge error: %#x\n", dev->name, read_data);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int ssd_check_fw(struct ssd_device *dev)
+{
+       uint32_t val = 0;
+       int i;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) {
+               return 0;
+       }
+
+       for (i=0; i<SSD_CONTROLLER_WAIT; i++) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_HW_STATUS_REG);
+               if ((val & 0x1) && ((val >> 8) & 0x1)) {
+                       break;
+               }
+
+               msleep(SSD_INIT_WAIT);
+       }
+
+       if (!(val & 0x1)) {
+               /* controller fw status */
+               hio_warn("%s: controller firmware load failed: %#x\n", dev->name, val);
+               return -1;
+       } else if (!((val >> 8) & 0x1)) {
+               /* controller state */
+               hio_warn("%s: controller state error: %#x\n", dev->name, val);
+               return -1;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_RELOAD_FW_REG);
+       if (val) {
+               dev->reload_fw = 1;
+       }
+
+       return 0;
+}
+
+static int ssd_init_fw_info(struct ssd_device *dev)
+{
+       uint32_t val;
+       int ret = 0;
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_VER_REG);
+       dev->hw_info.bridge_ver = val & 0xFFF;
+       if (dev->hw_info.bridge_ver < SSD_FW_MIN) {
+               hio_warn("%s: bridge firmware version %03X is not supported\n", dev->name, dev->hw_info.bridge_ver);
+               return -EINVAL;
+       }
+       hio_info("%s: bridge firmware version: %03X\n", dev->name, dev->hw_info.bridge_ver);
+
+       ret = ssd_check_fw(dev);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+static int ssd_check_clock(struct ssd_device *dev)
+{
+       uint32_t val;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) {
+               return 0;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_HW_STATUS_REG);
+
+       /* clock status */
+       if (!((val >> 4 ) & 0x1)) {
+               if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_166M_LOST), &dev->hwmon)) {
+                       hio_warn("%s: 166MHz clock losed: %#x\n", dev->name, val);
+                       ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val);
+               }
+               ret = -1;
+       }
+
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               if (!((val >> 5 ) & 0x1)) {
+                       if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_166M_SKEW), &dev->hwmon)) {
+                               hio_warn("%s: 166MHz clock is skew: %#x\n", dev->name, val);
+                               ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val);
+                       }
+                       ret = -1;
+               }
+               if (!((val >> 6 ) & 0x1)) {
+                       if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_156M_LOST), &dev->hwmon)) {
+                               hio_warn("%s: 156.25MHz clock lost: %#x\n", dev->name, val);
+                               ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val);
+                       }
+                       ret = -1;
+               }
+               if (!((val >> 7 ) & 0x1)) {
+                       if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_156M_SKEW), &dev->hwmon)) {
+                               hio_warn("%s: 156.25MHz clock is skew: %#x\n", dev->name, val);
+                               ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val);
+                       }
+                       ret = -1;
+               }
+       }
+
+       return ret;
+}
+
+static int ssd_check_volt(struct ssd_device *dev)
+{
+       int i = 0;
+       uint64_t val;
+       uint32_t adc_val;
+       int ret =0;
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               /* 1.0v */
+               if (!test_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon)) {
+                       val = ssd_reg_read(dev->ctrlp + SSD_FPGA_1V0_REG0 + i * SSD_CTRL_REG_ZONE_SZ);
+                       adc_val = SSD_FPGA_VOLT_MAX(val);
+                       if (adc_val < SSD_FPGA_1V0_ADC_MIN || adc_val > SSD_FPGA_1V0_ADC_MAX) {
+                               (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon);
+                               hio_warn("%s: controller %d 1.0V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val));
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V0, i, adc_val));
+                               ret = -1;
+                       }
+
+                       adc_val = SSD_FPGA_VOLT_MIN(val);
+                       if (adc_val < SSD_FPGA_1V0_ADC_MIN || adc_val > SSD_FPGA_1V0_ADC_MAX) {
+                               (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon);
+                               hio_warn("%s: controller %d 1.0V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val));
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V0, i, adc_val));
+                               ret = -2;
+                       }
+               }
+
+               /* 1.8v */
+               if (!test_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon)) {
+                       val = ssd_reg_read(dev->ctrlp + SSD_FPGA_1V8_REG0 + i * SSD_CTRL_REG_ZONE_SZ);
+                       adc_val = SSD_FPGA_VOLT_MAX(val);
+                       if (adc_val < SSD_FPGA_1V8_ADC_MIN || adc_val > SSD_FPGA_1V8_ADC_MAX) {
+                               (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon);
+                               hio_warn("%s: controller %d 1.8V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val));
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V8, i, adc_val));
+                               ret = -3;
+                       }
+
+                       adc_val = SSD_FPGA_VOLT_MIN(val);
+                       if (adc_val < SSD_FPGA_1V8_ADC_MIN || adc_val > SSD_FPGA_1V8_ADC_MAX) {
+                               (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon);
+                               hio_warn("%s: controller %d 1.8V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val));
+                               ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V8, i, adc_val));
+                               ret = -4;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static int ssd_check_reset_sync(struct ssd_device *dev)
+{
+       uint32_t val;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) {
+               return 0;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_HW_STATUS_REG);
+       if (!((val >> 8) & 0x1)) {
+               /* controller state */
+               hio_warn("%s: controller state error: %#x\n", dev->name, val);
+               return -1;
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return 0;
+       }
+
+       if (((val >> 9 ) & 0x1)) {
+               hio_warn("%s: controller reset asynchronously: %#x\n", dev->name, val);
+               ssd_gen_swlog(dev, SSD_LOG_CTRL_RST_SYNC, val);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int ssd_check_hw_bh(struct ssd_device *dev)
+{
+       int ret;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) {
+               return 0;
+       }
+
+       /* clock status */
+       ret = ssd_check_clock(dev);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+static int ssd_check_controller(struct ssd_device *dev)
+{
+       int ret;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) {
+               return 0;
+       }
+
+       /* sync reset */
+       ret = ssd_check_reset_sync(dev);
+       if (ret) {
+               goto out;
+       }
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+static int ssd_check_controller_bh(struct ssd_device *dev)
+{
+       uint32_t test_data = 0x55AA5AA5;
+       uint32_t val;
+       int reg_base, reg_sz;
+       int init_wait = 0;
+       int i;
+       int ret = 0;
+
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               return 0;
+       }
+
+       /* controller */
+       val = ssd_reg32_read(dev->ctrlp + SSD_READY_REG);
+       if (val & 0x1) {
+               hio_warn("%s: controller 0 not ready\n", dev->name);
+               return -1;
+       }
+
+       for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+               reg_base = SSD_CTRL_TEST_REG0 + i * SSD_CTRL_TEST_REG_SZ;
+               ssd_reg32_write(dev->ctrlp + reg_base, test_data);
+               val = ssd_reg32_read(dev->ctrlp + reg_base);
+               if (val != ~(test_data)) {
+                       hio_warn("%s: check controller %d error: %#x\n", dev->name, i, val);
+                       return -1;
+               }
+       }
+
+       /* clock */
+       ret = ssd_check_volt(dev);
+       if (ret) {
+               return ret;
+       }
+
+       /* ddr */
+       if (dev->protocol_info.ver > SSD_PROTOCOL_V3) {
+               reg_base = SSD_PV3_RAM_STATUS_REG0;
+               reg_sz = SSD_PV3_RAM_STATUS_REG_SZ;
+
+               for (i=0; i<dev->hw_info.nr_ctrl; i++) {
+check_ram_status:
+                       val = ssd_reg32_read(dev->ctrlp + reg_base);
+
+                       if (!((val >> 1) & 0x1)) {
+                               init_wait++;
+                               if (init_wait <= SSD_RAM_INIT_MAX_WAIT) {
+                                       msleep(SSD_INIT_WAIT);
+                                       goto check_ram_status;
+                               } else {
+                                       hio_warn("%s: controller %d ram init failed: %#x\n", dev->name, i, val);
+                                       ssd_gen_swlog(dev, SSD_LOG_DDR_INIT_ERR, i);
+                                       return -1;
+                               }
+                       }
+
+                       reg_base += reg_sz;
+               }
+       }
+
+       /* ch info */
+       for (i=0; i<SSD_CH_INFO_MAX_WAIT; i++) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_CH_INFO_REG);
+               if (!((val >> 31) & 0x1)) {
+                       break;
+               }
+
+               msleep(SSD_INIT_WAIT);
+       }
+       if ((val >> 31) & 0x1) {
+               hio_warn("%s: channel info init failed: %#x\n", dev->name, val);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int ssd_init_protocol_info(struct ssd_device *dev)
+{
+       uint32_t val;
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_PROTOCOL_VER_REG);
+       if (val == (uint32_t)-1) {
+               hio_warn("%s: protocol version error: %#x\n", dev->name, val);
+               return -EINVAL;
+       }
+       dev->protocol_info.ver = val;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               dev->protocol_info.init_state_reg = SSD_INIT_STATE_REG0;
+               dev->protocol_info.init_state_reg_sz = SSD_INIT_STATE_REG_SZ;
+
+               dev->protocol_info.chip_info_reg = SSD_CHIP_INFO_REG0;
+               dev->protocol_info.chip_info_reg_sz = SSD_CHIP_INFO_REG_SZ;
+       } else {
+               dev->protocol_info.init_state_reg = SSD_PV3_INIT_STATE_REG0;
+               dev->protocol_info.init_state_reg_sz = SSD_PV3_INIT_STATE_REG_SZ;
+
+               dev->protocol_info.chip_info_reg = SSD_PV3_CHIP_INFO_REG0;
+               dev->protocol_info.chip_info_reg_sz = SSD_PV3_CHIP_INFO_REG_SZ;
+       }
+
+       return 0;
+}
+
+static int ssd_init_hw_info(struct ssd_device *dev)
+{
+       uint64_t val64;
+       uint32_t val;
+       uint32_t nr_ctrl;
+       int ret = 0;
+
+       /* base info */
+       val = ssd_reg32_read(dev->ctrlp + SSD_RESP_INFO_REG);
+       dev->hw_info.resp_ptr_sz = 16 * (1U << (val & 0xFF));
+       dev->hw_info.resp_msg_sz = 16 * (1U << ((val >> 8) & 0xFF));
+
+       if (0 == dev->hw_info.resp_ptr_sz || 0 == dev->hw_info.resp_msg_sz) {
+               hio_warn("%s: response info error\n", dev->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_INFO_REG);
+       dev->hw_info.cmd_fifo_sz = 1U << ((val >> 4) & 0xF);
+       dev->hw_info.cmd_max_sg = 1U << ((val >> 8) & 0xF);
+       dev->hw_info.sg_max_sec = 1U << ((val >> 12) & 0xF);
+       dev->hw_info.cmd_fifo_sz_mask = dev->hw_info.cmd_fifo_sz - 1;
+
+       if (0 == dev->hw_info.cmd_fifo_sz || 0 == dev->hw_info.cmd_max_sg || 0 == dev->hw_info.sg_max_sec) {
+               hio_warn("%s: cmd info error\n", dev->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* check hw */
+       if (ssd_check_hw_bh(dev)) {
+               hio_warn("%s: check hardware status failed\n", dev->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (ssd_check_controller(dev)) {
+               hio_warn("%s: check controller state failed\n", dev->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* nr controller : read again*/
+       val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_INFO_REG);
+       dev->hw_info.nr_ctrl = (val >> 16) & 0xF;
+
+       /* nr ctrl configured */
+       nr_ctrl = (val >> 20) & 0xF;
+       if (0 == dev->hw_info.nr_ctrl) {
+               hio_warn("%s: nr controller error: %u\n", dev->name, dev->hw_info.nr_ctrl);
+               ret = -EINVAL;
+               goto out;
+       } else if (0 != nr_ctrl && nr_ctrl != dev->hw_info.nr_ctrl) {
+               hio_warn("%s: nr controller error: configured %u but found %u\n", dev->name, nr_ctrl, dev->hw_info.nr_ctrl);
+               if (mode <= SSD_DRV_MODE_STANDARD) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
+       if (ssd_check_controller_bh(dev)) {
+               hio_warn("%s: check controller failed\n", dev->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_PCB_VER_REG);
+       dev->hw_info.pcb_ver = (uint8_t) ((val >> 4) & 0xF) + 'A' -1;
+       if ((val & 0xF) != 0xF) {
+               dev->hw_info.upper_pcb_ver = (uint8_t) (val & 0xF) + 'A' -1;
+       }
+
+       if (dev->hw_info.pcb_ver < 'A' || (0 != dev->hw_info.upper_pcb_ver && dev->hw_info.upper_pcb_ver < 'A')) {
+               hio_warn("%s: PCB version error: %#x %#x\n", dev->name, dev->hw_info.pcb_ver, dev->hw_info.upper_pcb_ver);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* channel info */
+       if (mode <= SSD_DRV_MODE_DEBUG) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_CH_INFO_REG);
+               dev->hw_info.nr_data_ch = val & 0xFF;
+               dev->hw_info.nr_ch = dev->hw_info.nr_data_ch + ((val >> 8) & 0xFF);
+               dev->hw_info.nr_chip = (val >> 16) & 0xFF;
+
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                       dev->hw_info.max_ch = 1;
+                       while (dev->hw_info.max_ch < dev->hw_info.nr_ch) dev->hw_info.max_ch <<= 1;
+               } else {
+                       /* set max channel 32  */
+                       dev->hw_info.max_ch = 32;
+               }
+
+               if (0 == dev->hw_info.nr_chip) {
+                       //for debug mode
+                       dev->hw_info.nr_chip = 1;
+               }
+
+               //xx
+               dev->hw_info.id_size = SSD_NAND_ID_SZ;
+               dev->hw_info.max_ce = SSD_NAND_MAX_CE;
+
+               if (0 == dev->hw_info.nr_data_ch || 0 == dev->hw_info.nr_ch || 0 == dev->hw_info.nr_chip) {
+                       hio_warn("%s: channel info error: data_ch %u ch %u chip %u\n", dev->name, dev->hw_info.nr_data_ch, dev->hw_info.nr_ch, dev->hw_info.nr_chip);
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
+       /* ram info */
+       if (mode <= SSD_DRV_MODE_DEBUG) {
+               val = ssd_reg32_read(dev->ctrlp + SSD_RAM_INFO_REG);
+               dev->hw_info.ram_size = 0x4000000ull * (1ULL << (val & 0xF));
+               dev->hw_info.ram_align = 1U << ((val >> 12) & 0xF);
+               if (dev->hw_info.ram_align < SSD_RAM_ALIGN) {
+                       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+                               dev->hw_info.ram_align = SSD_RAM_ALIGN;
+                       } else {
+                               hio_warn("%s: ram align error: %u\n", dev->name, dev->hw_info.ram_align);
+                               ret = -EINVAL;
+                               goto out;
+                       }
+               }
+               dev->hw_info.ram_max_len = 0x1000 * (1U << ((val >> 16) & 0xF));
+
+               if (0 == dev->hw_info.ram_size || 0 == dev->hw_info.ram_align || 0 == dev->hw_info.ram_max_len || dev->hw_info.ram_align > dev->hw_info.ram_max_len) {
+                       hio_warn("%s: ram info error\n", dev->name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+                       dev->hw_info.log_sz = SSD_LOG_MAX_SZ;
+               } else {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_LOG_INFO_REG);
+                       dev->hw_info.log_sz = 0x1000 * (1U << (val & 0xFF));
+               }
+               if (0 == dev->hw_info.log_sz) {
+                       hio_warn("%s: log size error\n", dev->name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_BBT_BASE_REG);
+               dev->hw_info.bbt_base = 0x40000ull * (val & 0xFFFF);
+               dev->hw_info.bbt_size = 0x40000 * (((val >> 16) & 0xFFFF) + 1) / (dev->hw_info.max_ch * dev->hw_info.nr_chip);
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+                       if (dev->hw_info.bbt_base > dev->hw_info.ram_size || 0 == dev->hw_info.bbt_size) {
+                               hio_warn("%s: bbt info error\n", dev->name);
+                               ret = -EINVAL;
+                               goto out;
+                       }
+               }
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_ECT_BASE_REG);
+               dev->hw_info.md_base = 0x40000ull * (val & 0xFFFF);
+               if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+                       dev->hw_info.md_size = 0x40000 * (((val >> 16) & 0xFFF) + 1) / (dev->hw_info.max_ch * dev->hw_info.nr_chip);
+               } else {
+                       dev->hw_info.md_size = 0x40000 * (((val >> 16) & 0xFFF) + 1) / (dev->hw_info.nr_chip);
+               }
+               dev->hw_info.md_entry_sz = 8 * (1U << ((val >> 28) & 0xF));
+               if (dev->protocol_info.ver >= SSD_PROTOCOL_V3) {
+                       if (dev->hw_info.md_base > dev->hw_info.ram_size || 0 == dev->hw_info.md_size || 
+                               0 == dev->hw_info.md_entry_sz || dev->hw_info.md_entry_sz > dev->hw_info.md_size) {
+                               hio_warn("%s: md info error\n", dev->name);
+                               ret = -EINVAL;
+                               goto out;
+                       }
+               }
+
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+                       dev->hw_info.nand_wbuff_base = dev->hw_info.ram_size + 1;
+               } else {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_NAND_BUFF_BASE);
+                       dev->hw_info.nand_wbuff_base = 0x8000ull * val;
+               }
+       }
+
+       /* flash info */
+       if (mode <= SSD_DRV_MODE_DEBUG) {
+               if (dev->hw_info.nr_ctrl > 1) {
+                       val = ssd_reg32_read(dev->ctrlp + SSD_CTRL_VER_REG);
+                       dev->hw_info.ctrl_ver = val & 0xFFF;
+                       hio_info("%s: controller firmware version: %03X\n", dev->name, dev->hw_info.ctrl_ver);
+               }
+
+               val64 = ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0);
+               dev->hw_info.nand_vendor_id = ((val64 >> 56) & 0xFF);
+               dev->hw_info.nand_dev_id = ((val64 >> 48) & 0xFF);
+
+               dev->hw_info.block_count = (((val64 >> 32) & 0xFFFF) + 1);
+               dev->hw_info.page_count = ((val64>>16) & 0xFFFF);
+               dev->hw_info.page_size = (val64 & 0xFFFF);
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_BB_INFO_REG);
+               dev->hw_info.bbf_pages = val & 0xFF;
+               dev->hw_info.bbf_seek = (val >> 8) & 0x1;
+
+               if (0 == dev->hw_info.block_count || 0 == dev->hw_info.page_count || 0 == dev->hw_info.page_size || dev->hw_info.block_count > INT_MAX) {
+                       hio_warn("%s: flash info error\n", dev->name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               //xx
+               dev->hw_info.oob_size = SSD_NAND_OOB_SZ;        //(dev->hw_info.page_size) >> 5;
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_VALID_PAGES_REG);
+               if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+                       dev->hw_info.valid_pages = val & 0x3FF;
+                       dev->hw_info.max_valid_pages = (val>>20) & 0x3FF;
+               } else {
+                       dev->hw_info.valid_pages = val & 0x7FFF;
+                       dev->hw_info.max_valid_pages = (val>>15) & 0x7FFF;
+               }
+               if (0 == dev->hw_info.valid_pages || 0 == dev->hw_info.max_valid_pages || 
+                       dev->hw_info.valid_pages > dev->hw_info.max_valid_pages || dev->hw_info.max_valid_pages > dev->hw_info.page_count) {
+                       hio_warn("%s: valid page info error: valid_pages %d, max_valid_pages %d\n", dev->name, dev->hw_info.valid_pages, dev->hw_info.max_valid_pages);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               val = ssd_reg32_read(dev->ctrlp + SSD_RESERVED_BLKS_REG);
+               dev->hw_info.reserved_blks = val & 0xFFFF;
+               dev->hw_info.md_reserved_blks = (val >> 16) & 0xFF;
+               if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) {
+                       dev->hw_info.md_reserved_blks = SSD_BBT_RESERVED;
+               }
+               if (dev->hw_info.reserved_blks > dev->hw_info.block_count || dev->hw_info.md_reserved_blks > dev->hw_info.block_count) {
+                       hio_warn("%s: reserved blocks info error: reserved_blks %d, md_reserved_blks %d\n", dev->name, dev->hw_info.reserved_blks, dev->hw_info.md_reserved_blks);
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
+       /* size */
+       if (mode < SSD_DRV_MODE_DEBUG) {
+               dev->hw_info.size = (uint64_t)dev->hw_info.valid_pages * dev->hw_info.page_size;
+               dev->hw_info.size *= (dev->hw_info.block_count - dev->hw_info.reserved_blks);
+               dev->hw_info.size *= ((uint64_t)dev->hw_info.nr_data_ch * (uint64_t)dev->hw_info.nr_chip * (uint64_t)dev->hw_info.nr_ctrl);
+       }
+
+       /* extend hardware info */
+       val = ssd_reg32_read(dev->ctrlp + SSD_PCB_VER_REG);
+       dev->hw_info_ext.board_type = (val >> 24) & 0xF;
+
+       dev->hw_info_ext.form_factor = SSD_FORM_FACTOR_FHHL;
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_1) {
+               dev->hw_info_ext.form_factor = (val >> 31) & 0x1;
+       }
+       /*
+       dev->hw_info_ext.cap_type = (val >> 28) & 0x3;
+       if (SSD_BM_CAP_VINA != dev->hw_info_ext.cap_type && SSD_BM_CAP_JH != dev->hw_info_ext.cap_type) {
+               dev->hw_info_ext.cap_type = SSD_BM_CAP_VINA;
+       }*/
+
+       /* power loss protect */
+       val = ssd_reg32_read(dev->ctrlp + SSD_PLP_INFO_REG);
+       dev->hw_info_ext.plp_type = (val & 0x3);
+       if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) {
+               /* 3 or 4 cap */
+               dev->hw_info_ext.cap_type = ((val >> 2)& 0x1);
+       }
+
+       /* work mode */
+       val = ssd_reg32_read(dev->ctrlp + SSD_CH_INFO_REG);
+       dev->hw_info_ext.work_mode = (val >> 25) & 0x1;
+
+out:
+       /* skip error if not in standard mode */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ret = 0;
+       }
+       return ret;
+}
+
+static void ssd_cleanup_response(struct ssd_device *dev)
+{
+       int resp_msg_sz = dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * SSD_MSIX_VEC;
+       int resp_ptr_sz = dev->hw_info.resp_ptr_sz * SSD_MSIX_VEC;
+
+       pci_free_consistent(dev->pdev, resp_ptr_sz, dev->resp_ptr_base, dev->resp_ptr_base_dma);
+       pci_free_consistent(dev->pdev, resp_msg_sz, dev->resp_msg_base, dev->resp_msg_base_dma);
+}
+
+static int ssd_init_response(struct ssd_device *dev)
+{
+       int resp_msg_sz = dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * SSD_MSIX_VEC;
+       int resp_ptr_sz = dev->hw_info.resp_ptr_sz * SSD_MSIX_VEC;
+
+       dev->resp_msg_base = pci_alloc_consistent(dev->pdev, resp_msg_sz, &(dev->resp_msg_base_dma));
+       if (!dev->resp_msg_base) {
+               hio_warn("%s: unable to allocate resp msg DMA buffer\n", dev->name);
+               goto out_alloc_resp_msg;
+       }
+       memset(dev->resp_msg_base, 0xFF, resp_msg_sz);
+
+       dev->resp_ptr_base = pci_alloc_consistent(dev->pdev, resp_ptr_sz, &(dev->resp_ptr_base_dma));
+       if (!dev->resp_ptr_base){
+               hio_warn("%s: unable to allocate resp ptr DMA buffer\n", dev->name);
+               goto out_alloc_resp_ptr;
+       }
+       memset(dev->resp_ptr_base, 0, resp_ptr_sz);
+       dev->resp_idx = *(uint32_t *)(dev->resp_ptr_base) = dev->hw_info.cmd_fifo_sz * 2 - 1;
+
+       ssd_reg_write(dev->ctrlp + SSD_RESP_FIFO_REG, dev->resp_msg_base_dma);
+       ssd_reg_write(dev->ctrlp + SSD_RESP_PTR_REG, dev->resp_ptr_base_dma);
+
+       return 0;
+
+out_alloc_resp_ptr:
+       pci_free_consistent(dev->pdev, resp_msg_sz, dev->resp_msg_base, dev->resp_msg_base_dma);
+out_alloc_resp_msg:
+       return -ENOMEM;
+}
+
+static int ssd_cleanup_cmd(struct ssd_device *dev)
+{
+       int msg_sz = ALIGN(sizeof(struct ssd_rw_msg) + (dev->hw_info.cmd_max_sg - 1) * sizeof(struct ssd_sg_entry), SSD_DMA_ALIGN);
+       int i;
+
+       for (i=0; i<(int)dev->hw_info.cmd_fifo_sz; i++) {
+               kfree(dev->cmd[i].sgl);
+       }
+       kfree(dev->cmd);
+       pci_free_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), dev->msg_base, dev->msg_base_dma);
+       return 0;
+}
+
+static int ssd_init_cmd(struct ssd_device *dev)
+{
+       int sgl_sz = sizeof(struct scatterlist) * dev->hw_info.cmd_max_sg;
+       int cmd_sz = sizeof(struct ssd_cmd) * dev->hw_info.cmd_fifo_sz;
+       int msg_sz = ALIGN(sizeof(struct ssd_rw_msg) + (dev->hw_info.cmd_max_sg - 1) * sizeof(struct ssd_sg_entry), SSD_DMA_ALIGN);
+       int i;
+
+       spin_lock_init(&dev->cmd_lock);
+
+       dev->msg_base = pci_alloc_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), &dev->msg_base_dma);
+       if (!dev->msg_base) {
+               hio_warn("%s: can not alloc cmd msg\n", dev->name);
+               goto out_alloc_msg;
+       }
+
+       dev->cmd = kmalloc(cmd_sz, GFP_KERNEL);
+       if (!dev->cmd) {
+               hio_warn("%s: can not alloc cmd\n", dev->name);
+               goto out_alloc_cmd;
+       }
+       memset(dev->cmd, 0, cmd_sz);
+
+       for (i=0; i<(int)dev->hw_info.cmd_fifo_sz; i++) {
+               dev->cmd[i].sgl = kmalloc(sgl_sz, GFP_KERNEL);
+               if (!dev->cmd[i].sgl) {
+                       hio_warn("%s: can not alloc cmd sgl %d\n", dev->name, i);
+                       goto out_alloc_sgl;
+               }
+
+               dev->cmd[i].msg = dev->msg_base + (msg_sz * i);
+               dev->cmd[i].msg_dma = dev->msg_base_dma + ((dma_addr_t)msg_sz * i);
+
+               dev->cmd[i].dev = dev;
+               dev->cmd[i].tag = i;
+               dev->cmd[i].flag = 0;
+
+               INIT_LIST_HEAD(&dev->cmd[i].list);
+       }
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3) {
+               dev->scmd = ssd_dispatch_cmd;
+       } else {
+               ssd_reg_write(dev->ctrlp + SSD_MSG_BASE_REG, dev->msg_base_dma);
+               if (finject) {
+                       dev->scmd = ssd_send_cmd_db;
+               } else {
+                       dev->scmd = ssd_send_cmd;
+               }
+       }
+
+       return 0;
+
+out_alloc_sgl:
+       for (i--; i>=0; i--) {
+               kfree(dev->cmd[i].sgl);
+       }
+       kfree(dev->cmd);
+out_alloc_cmd:
+       pci_free_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), dev->msg_base, dev->msg_base_dma);
+out_alloc_msg:
+       return -ENOMEM;
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30))
+static irqreturn_t ssd_interrupt_check(int irq, void *dev_id)
+{
+       struct ssd_queue *queue = (struct ssd_queue *)dev_id;
+
+       if (*(uint32_t *)queue->resp_ptr == queue->resp_idx) {
+               return IRQ_NONE;
+       }
+
+       return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t ssd_interrupt_threaded(int irq, void *dev_id)
+{
+       struct ssd_queue *queue = (struct ssd_queue *)dev_id;
+       struct ssd_device *dev = (struct ssd_device *)queue->dev;
+       struct ssd_cmd *cmd;
+       union ssd_response_msq __msg;
+       union ssd_response_msq *msg = &__msg;
+       uint64_t *u64_msg;
+       uint32_t resp_idx = queue->resp_idx;
+       uint32_t new_resp_idx = *(uint32_t *)queue->resp_ptr;
+       uint32_t end_resp_idx;
+
+       if (unlikely(resp_idx == new_resp_idx)) {
+               return IRQ_NONE;
+       }
+
+       end_resp_idx = new_resp_idx & queue->resp_idx_mask;
+
+       do {
+               resp_idx = (resp_idx + 1) & queue->resp_idx_mask;
+
+               /* the resp msg */
+               u64_msg = (uint64_t *)(queue->resp_msg + queue->resp_msg_sz * resp_idx);
+               msg->u64_msg = *u64_msg;
+
+               if (unlikely(msg->u64_msg == (uint64_t)(-1))) {
+                       hio_err("%s: empty resp msg: queue %d idx %u\n", dev->name, queue->idx, resp_idx);
+                       continue;
+               }
+               /* clear the resp msg */
+               *u64_msg = (uint64_t)(-1);
+
+               cmd = &queue->cmd[msg->resp_msg.tag];
+               /*if (unlikely(!cmd->bio)) {
+                       printk(KERN_WARNING "%s: unknown tag %d fun %#x\n", 
+                               dev->name, msg->resp_msg.tag, msg->resp_msg.fun);
+                       continue;
+               }*/
+
+               if(unlikely(msg->resp_msg.status & (uint32_t)status_mask)) {
+                       cmd->errors = -EIO;
+               } else {
+                       cmd->errors = 0;
+               }
+               cmd->nr_log = msg->log_resp_msg.nr_log;
+
+               ssd_done(cmd);
+
+               if (unlikely(msg->resp_msg.fun != SSD_FUNC_READ_LOG && msg->resp_msg.log > 0)) {
+                       (void)test_and_set_bit(SSD_LOG_HW, &dev->state);
+                       if (test_bit(SSD_INIT_WORKQ, &dev->state)) {
+                               queue_work(dev->workq, &dev->log_work);
+                       }
+               }
+
+               if (unlikely(msg->resp_msg.status)) {
+                       if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_WRITE) {
+                               hio_err("%s: I/O error %d: tag %d fun %#x\n", 
+                                       dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun);
+
+                               /* alarm led */
+                               ssd_set_alarm(dev);
+                               queue->io_stat.nr_rwerr++;
+                               ssd_gen_swlog(dev, SSD_LOG_EIO, msg->u32_msg[0]);
+                       } else {
+                               hio_info("%s: CMD error %d: tag %d fun %#x\n", 
+                                       dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun);
+
+                               ssd_gen_swlog(dev, SSD_LOG_ECMD, msg->u32_msg[0]);
+                       }
+                       queue->io_stat.nr_ioerr++;
+               }
+
+               if (msg->resp_msg.fun == SSD_FUNC_READ || 
+                       msg->resp_msg.fun == SSD_FUNC_NAND_READ_WOOB ||
+                       msg->resp_msg.fun == SSD_FUNC_NAND_READ) {
+
+                       queue->ecc_info.bitflip[msg->resp_msg.bitflip]++;
+               }
+       }while (resp_idx != end_resp_idx);
+
+       queue->resp_idx = new_resp_idx;
+
+       return IRQ_HANDLED;
+}
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19))
+static irqreturn_t ssd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+#else
+static irqreturn_t ssd_interrupt(int irq, void *dev_id)
+#endif
+{
+       struct ssd_queue *queue = (struct ssd_queue *)dev_id;
+       struct ssd_device *dev = (struct ssd_device *)queue->dev;
+       struct ssd_cmd *cmd;
+       union ssd_response_msq __msg;
+       union ssd_response_msq *msg = &__msg;
+       uint64_t *u64_msg;
+       uint32_t resp_idx = queue->resp_idx;
+       uint32_t new_resp_idx = *(uint32_t *)queue->resp_ptr;
+       uint32_t end_resp_idx;
+
+       if (unlikely(resp_idx == new_resp_idx)) {
+               return IRQ_NONE;
+       }
+
+#if (defined SSD_ESCAPE_IRQ)
+       if (SSD_INT_MSIX != dev->int_mode) {
+               dev->irq_cpu = smp_processor_id();
+       }
+#endif
+
+       end_resp_idx = new_resp_idx & queue->resp_idx_mask;
+
+       do {
+               resp_idx = (resp_idx + 1) & queue->resp_idx_mask;
+
+               /* the resp msg */
+               u64_msg = (uint64_t *)(queue->resp_msg + queue->resp_msg_sz * resp_idx);
+               msg->u64_msg = *u64_msg;
+
+               if (unlikely(msg->u64_msg == (uint64_t)(-1))) {
+                       hio_err("%s: empty resp msg: queue %d idx %u\n", dev->name, queue->idx, resp_idx);
+                       continue;
+               }
+               /* clear the resp msg */
+               *u64_msg = (uint64_t)(-1);
+
+               cmd = &queue->cmd[msg->resp_msg.tag];
+               /*if (unlikely(!cmd->bio)) {
+                       printk(KERN_WARNING "%s: unknown tag %d fun %#x\n", 
+                               dev->name, msg->resp_msg.tag, msg->resp_msg.fun);
+                       continue;
+               }*/
+
+               if(unlikely(msg->resp_msg.status & (uint32_t)status_mask)) {
+                       cmd->errors = -EIO;
+               } else {
+                       cmd->errors = 0;
+               }
+               cmd->nr_log = msg->log_resp_msg.nr_log;
+
+               ssd_done_bh(cmd);
+
+               if (unlikely(msg->resp_msg.fun != SSD_FUNC_READ_LOG && msg->resp_msg.log > 0)) {
+                       (void)test_and_set_bit(SSD_LOG_HW, &dev->state);
+                       if (test_bit(SSD_INIT_WORKQ, &dev->state)) {
+                               queue_work(dev->workq, &dev->log_work);
+                       }
+               }
+
+               if (unlikely(msg->resp_msg.status)) {
+                       if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_WRITE) {                                
+                               hio_err("%s: I/O error %d: tag %d fun %#x\n", 
+                                       dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun);
+
+                               /* alarm led */
+                               ssd_set_alarm(dev);
+                               queue->io_stat.nr_rwerr++;
+                               ssd_gen_swlog(dev, SSD_LOG_EIO, msg->u32_msg[0]);
+                       } else {
+                               hio_info("%s: CMD error %d: tag %d fun %#x\n", 
+                                       dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun);
+
+                               ssd_gen_swlog(dev, SSD_LOG_ECMD, msg->u32_msg[0]);
+                       }
+                       queue->io_stat.nr_ioerr++;
+               }
+
+               if (msg->resp_msg.fun == SSD_FUNC_READ || 
+                       msg->resp_msg.fun == SSD_FUNC_NAND_READ_WOOB ||
+                       msg->resp_msg.fun == SSD_FUNC_NAND_READ) {
+
+                       queue->ecc_info.bitflip[msg->resp_msg.bitflip]++;
+               }
+       }while (resp_idx != end_resp_idx);
+
+       queue->resp_idx = new_resp_idx;
+
+       return IRQ_HANDLED;
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19))
+static irqreturn_t ssd_interrupt_legacy(int irq, void *dev_id, struct pt_regs *regs)
+#else
+static irqreturn_t ssd_interrupt_legacy(int irq, void *dev_id)
+#endif
+{
+       irqreturn_t ret;
+       struct ssd_queue *queue = (struct ssd_queue *)dev_id;
+       struct ssd_device *dev = (struct ssd_device *)queue->dev;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19))
+       ret = ssd_interrupt(irq, dev_id, regs);
+#else
+       ret = ssd_interrupt(irq, dev_id);
+#endif
+
+       /* clear intr */ 
+       if (IRQ_HANDLED == ret) {
+               ssd_reg32_write(dev->ctrlp + SSD_CLEAR_INTR_REG, 1);
+       }
+
+       return ret;
+}
+
+static void ssd_reset_resp_ptr(struct ssd_device *dev)
+{
+       int i;
+
+       for (i=0; i<dev->nr_queue; i++) {
+               *(uint32_t *)dev->queue[i].resp_ptr = dev->queue[i].resp_idx = (dev->hw_info.cmd_fifo_sz * 2) - 1;
+       }
+}
+
+static void ssd_free_irq(struct ssd_device *dev)
+{
+       int i;
+
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6))
+       if (SSD_INT_MSIX == dev->int_mode) {
+               for (i=0; i<dev->nr_queue; i++) {
+                       irq_set_affinity_hint(dev->entry[i].vector, NULL);
+               }
+       }
+#endif
+
+       for (i=0; i<dev->nr_queue; i++) {
+               free_irq(dev->entry[i].vector, &dev->queue[i]);
+       }
+
+       if (SSD_INT_MSIX == dev->int_mode) {
+               pci_disable_msix(dev->pdev);
+       } else if (SSD_INT_MSI == dev->int_mode) {
+               pci_disable_msi(dev->pdev);
+       }
+
+}
+
+static int ssd_init_irq(struct ssd_device *dev)
+{
+#if (!defined MODULE) && (defined SSD_MSIX_AFFINITY_FORCE)
+       const struct cpumask *cpu_mask;
+       static int cpu_affinity = 0;
+#endif
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6))
+       const struct cpumask *mask;
+       static int cpu = 0;
+       int j;
+#endif
+       int i;
+       unsigned long flags = 0;
+       int ret = 0;
+
+       ssd_reg32_write(dev->ctrlp + SSD_INTR_INTERVAL_REG, 0x800);
+
+#ifdef SSD_ESCAPE_IRQ
+       dev->irq_cpu = -1;
+#endif
+
+       if (int_mode >= SSD_INT_MSIX && pci_find_capability(dev->pdev, PCI_CAP_ID_MSIX)) {
+               dev->nr_queue = SSD_MSIX_VEC;
+               for (i=0; i<dev->nr_queue; i++) {
+                       dev->entry[i].entry = i;
+               }
+               for (;;) {
+                       ret = pci_enable_msix(dev->pdev, dev->entry, dev->nr_queue);
+                       if (ret == 0) {
+                               break;
+                       } else if (ret > 0) {
+                               dev->nr_queue = ret;
+                       } else {
+                               hio_warn("%s: can not enable msix\n", dev->name);
+                               /* alarm led */
+                               ssd_set_alarm(dev);
+                               goto out;
+                       }
+               }
+
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6))
+               mask = (dev_to_node(&dev->pdev->dev) == -1) ? cpu_online_mask : cpumask_of_node(dev_to_node(&dev->pdev->dev));
+               if ((0 == cpu) || (!cpumask_intersects(mask, cpumask_of(cpu)))) {
+                       cpu = cpumask_first(mask);
+               }
+               for (i=0; i<dev->nr_queue; i++) {
+                       irq_set_affinity_hint(dev->entry[i].vector, cpumask_of(cpu));
+                       cpu = cpumask_next(cpu, mask);
+                       if (cpu >= nr_cpu_ids) {
+                               cpu = cpumask_first(mask);
+                       }
+               }
+#endif
+
+               dev->int_mode = SSD_INT_MSIX;
+       } else if (int_mode >= SSD_INT_MSI && pci_find_capability(dev->pdev, PCI_CAP_ID_MSI)) {
+               ret = pci_enable_msi(dev->pdev);
+               if (ret) {
+                       hio_warn("%s: can not enable msi\n", dev->name);
+                       /* alarm led */
+                       ssd_set_alarm(dev);
+                       goto out;
+               }
+
+               dev->nr_queue = 1;
+               dev->entry[0].vector = dev->pdev->irq;
+
+               dev->int_mode = SSD_INT_MSI;
+       } else {
+               dev->nr_queue = 1;
+               dev->entry[0].vector = dev->pdev->irq;
+
+               dev->int_mode = SSD_INT_LEGACY;
+       }
+
+       for (i=0; i<dev->nr_queue; i++) {
+               if (dev->nr_queue > 1) {
+                       snprintf(dev->queue[i].name, SSD_QUEUE_NAME_LEN, "%s_e100-%d", dev->name, i);
+               } else {
+                       snprintf(dev->queue[i].name, SSD_QUEUE_NAME_LEN, "%s_e100", dev->name);
+               }
+
+               dev->queue[i].dev = dev;
+               dev->queue[i].idx = i;
+
+               dev->queue[i].resp_idx = (dev->hw_info.cmd_fifo_sz * 2) - 1;
+               dev->queue[i].resp_idx_mask = dev->hw_info.cmd_fifo_sz - 1;
+
+               dev->queue[i].resp_msg_sz = dev->hw_info.resp_msg_sz;
+               dev->queue[i].resp_msg = dev->resp_msg_base + dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * i;
+               dev->queue[i].resp_ptr = dev->resp_ptr_base + dev->hw_info.resp_ptr_sz * i;
+               *(uint32_t *)dev->queue[i].resp_ptr = dev->queue[i].resp_idx;
+
+               dev->queue[i].cmd = dev->cmd;
+       }
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
+       flags = IRQF_SHARED;
+#else
+       flags = SA_SHIRQ;
+#endif
+
+       for (i=0; i<dev->nr_queue; i++) {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30))
+               if (threaded_irq) {
+                       ret = request_threaded_irq(dev->entry[i].vector, ssd_interrupt_check, ssd_interrupt_threaded, flags, dev->queue[i].name, &dev->queue[i]);
+               } else if (dev->int_mode == SSD_INT_LEGACY) {
+                       ret = request_irq(dev->entry[i].vector, &ssd_interrupt_legacy, flags, dev->queue[i].name, &dev->queue[i]);
+               } else {
+                       ret = request_irq(dev->entry[i].vector, &ssd_interrupt, flags, dev->queue[i].name, &dev->queue[i]);
+               }
+#else
+               if (dev->int_mode == SSD_INT_LEGACY) {
+                       ret = request_irq(dev->entry[i].vector, &ssd_interrupt_legacy, flags, dev->queue[i].name, &dev->queue[i]);
+               } else {
+                       ret = request_irq(dev->entry[i].vector, &ssd_interrupt, flags, dev->queue[i].name, &dev->queue[i]);
+               }
+#endif
+               if (ret) {
+                       hio_warn("%s: request irq failed\n", dev->name);
+                       /* alarm led */
+                       ssd_set_alarm(dev);
+                       goto out_request_irq;
+               }
+
+#if (!defined MODULE) && (defined SSD_MSIX_AFFINITY_FORCE)
+               cpu_mask = (dev_to_node(&dev->pdev->dev) == -1) ? cpu_online_mask : cpumask_of_node(dev_to_node(&dev->pdev->dev));
+               if (SSD_INT_MSIX == dev->int_mode) {
+                       if ((0 == cpu_affinity) || (!cpumask_intersects(mask, cpumask_of(cpu_affinity)))) {
+                               cpu_affinity = cpumask_first(cpu_mask);
+                       }
+
+                       irq_set_affinity(dev->entry[i].vector, cpumask_of(cpu_affinity));
+                       cpu_affinity = cpumask_next(cpu_affinity, cpu_mask);
+                       if (cpu_affinity >= nr_cpu_ids) {
+                               cpu_affinity = cpumask_first(cpu_mask);
+                       }
+               }
+#endif
+       }
+
+       return ret;
+
+out_request_irq:
+#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6))
+       if (SSD_INT_MSIX == dev->int_mode) {
+               for (j=0; j<dev->nr_queue; j++) {
+                       irq_set_affinity_hint(dev->entry[j].vector, NULL);
+               }
+       }
+#endif
+
+       for (i--; i>=0; i--) {
+               free_irq(dev->entry[i].vector, &dev->queue[i]);
+       }
+
+       if (SSD_INT_MSIX == dev->int_mode) {
+               pci_disable_msix(dev->pdev);
+       } else if (SSD_INT_MSI == dev->int_mode) {
+               pci_disable_msi(dev->pdev);
+       }
+
+out:
+       return ret;
+}
+
+static void ssd_initial_log(struct ssd_device *dev)
+{
+       uint32_t val;
+       uint32_t speed, width;
+       
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return;
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_POWER_ON_REG);
+       if (val) {
+               ssd_gen_swlog(dev, SSD_LOG_POWER_ON, dev->hw_info.bridge_ver);
+       }
+
+       val = ssd_reg32_read(dev->ctrlp + SSD_PCIE_LINKSTATUS_REG);
+       speed = val & 0xF;
+       width = (val >> 4)& 0x3F;
+       if (0x1 == speed) {
+               hio_info("%s: PCIe: 2.5GT/s, x%u\n", dev->name, width);
+       } else if (0x2 == speed) {
+               hio_info("%s: PCIe: 5GT/s, x%u\n", dev->name, width);
+       } else {
+               hio_info("%s: PCIe: unknown GT/s, x%u\n", dev->name, width);
+       }
+       ssd_gen_swlog(dev, SSD_LOG_PCIE_LINK_STATUS, val);
+
+       return;
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static void ssd_hwmon_worker(void *data)
+{
+       struct ssd_device *dev = (struct ssd_device *)data;
+#else
+static void ssd_hwmon_worker(struct work_struct *work)
+{
+       struct ssd_device *dev = container_of(work, struct ssd_device, hwmon_work);
+#endif
+
+       if (ssd_check_hw(dev)) {
+               //hio_err("%s: check hardware failed\n", dev->name);
+               return;
+       }
+
+       ssd_check_clock(dev);
+       ssd_check_volt(dev);
+
+       ssd_mon_boardvolt(dev);
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static void ssd_tempmon_worker(void *data)
+{
+       struct ssd_device *dev = (struct ssd_device *)data;
+#else
+static void ssd_tempmon_worker(struct work_struct *work)
+{
+       struct ssd_device *dev = container_of(work, struct ssd_device, tempmon_work);
+#endif
+
+       if (ssd_check_hw(dev)) {
+               //hio_err("%s: check hardware failed\n", dev->name);
+               return;
+       }
+
+       ssd_mon_temp(dev);
+}
+
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+static void ssd_capmon_worker(void *data)
+{
+       struct ssd_device *dev = (struct ssd_device *)data;
+#else
+static void ssd_capmon_worker(struct work_struct *work)
+{
+       struct ssd_device *dev = container_of(work, struct ssd_device, capmon_work);
+#endif
+       uint32_t cap = 0;
+       uint32_t cap_threshold = SSD_PL_CAP_THRESHOLD;
+       int ret = 0;
+
+       if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) {
+               return;
+       }
+
+       if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') {
+               return;
+       }
+
+       /* fault before? */
+       if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+               ret = ssd_check_pl_cap_fast(dev);
+               if (ret) {
+                       return;
+               }
+       }
+
+       /* learn */
+       ret = ssd_do_cap_learn(dev, &cap);
+       if (ret) {
+               hio_err("%s: cap learn failed\n", dev->name);
+               ssd_gen_swlog(dev, SSD_LOG_CAP_LEARN_FAULT, 0);
+               return;
+       }
+
+       ssd_gen_swlog(dev, SSD_LOG_CAP_STATUS, cap);
+
+       if (SSD_PL_CAP_CP == dev->hw_info_ext.cap_type) {
+               cap_threshold = SSD_PL_CAP_CP_THRESHOLD;
+       }
+
+       //use the fw event id?
+       if (cap < cap_threshold) {
+               if (!test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_BATTERY_FAULT, 0);
+               }
+       } else if (cap >= (cap_threshold + SSD_PL_CAP_THRESHOLD_HYST)) {
+               if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) {
+                       ssd_gen_swlog(dev, SSD_LOG_BATTERY_OK, 0);
+               }
+       }
+}
+
+static void ssd_routine_start(void *data)
+{
+       struct ssd_device *dev;
+
+       if (!data) {
+               return;
+       }
+       dev = data;
+
+       dev->routine_tick++;
+
+       if (test_bit(SSD_INIT_WORKQ, &dev->state) && !ssd_busy(dev)) {
+               (void)test_and_set_bit(SSD_LOG_HW, &dev->state);
+               queue_work(dev->workq, &dev->log_work);
+       }
+
+       if ((dev->routine_tick % SSD_HWMON_ROUTINE_TICK) == 0 && test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               queue_work(dev->workq, &dev->hwmon_work);
+       }
+
+       if ((dev->routine_tick % SSD_CAPMON_ROUTINE_TICK) == 0 && test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               queue_work(dev->workq, &dev->capmon_work);
+       }
+
+       if ((dev->routine_tick % SSD_CAPMON2_ROUTINE_TICK) == 0 && test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon) && test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               /* CAP fault? check again */
+               queue_work(dev->workq, &dev->capmon_work);
+       }
+
+       if (test_bit(SSD_INIT_WORKQ, &dev->state)) {
+               queue_work(dev->workq, &dev->tempmon_work);
+       }
+
+       /* schedule routine */
+       mod_timer(&dev->routine_timer, jiffies + msecs_to_jiffies(SSD_ROUTINE_INTERVAL));
+}
+
+static void ssd_cleanup_routine(struct ssd_device *dev)
+{
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return;
+
+       (void)ssd_del_timer(&dev->routine_timer);
+
+       (void)ssd_del_timer(&dev->bm_timer);
+}
+
+static int ssd_init_routine(struct ssd_device *dev)
+{
+       if (unlikely(mode != SSD_DRV_MODE_STANDARD))
+               return 0;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+       INIT_WORK(&dev->bm_work, ssd_bm_worker, dev);
+       INIT_WORK(&dev->hwmon_work, ssd_hwmon_worker, dev);
+       INIT_WORK(&dev->capmon_work, ssd_capmon_worker, dev);
+       INIT_WORK(&dev->tempmon_work, ssd_tempmon_worker, dev);
+#else
+       INIT_WORK(&dev->bm_work, ssd_bm_worker);
+       INIT_WORK(&dev->hwmon_work, ssd_hwmon_worker);
+       INIT_WORK(&dev->capmon_work, ssd_capmon_worker);
+       INIT_WORK(&dev->tempmon_work, ssd_tempmon_worker);
+#endif
+
+       /* initial log */
+       ssd_initial_log(dev);
+
+       /* schedule bm routine */
+       ssd_add_timer(&dev->bm_timer, msecs_to_jiffies(SSD_BM_CAP_LEARNING_DELAY), ssd_bm_routine_start, dev);
+
+       /* schedule routine */
+       ssd_add_timer(&dev->routine_timer, msecs_to_jiffies(SSD_ROUTINE_INTERVAL), ssd_routine_start, dev);
+
+       return 0;
+}
+
+static void 
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38))
+__devexit 
+#endif
+ssd_remove_one (struct pci_dev *pdev)
+{
+       struct ssd_device *dev;
+
+       if (!pdev) {
+               return;
+       }
+
+       dev = pci_get_drvdata(pdev);
+       if (!dev) {
+               return;
+       }
+
+       list_del_init(&dev->list);
+
+       ssd_unregister_sysfs(dev);
+
+       /* offline firstly */
+       test_and_clear_bit(SSD_ONLINE, &dev->state);
+
+       /* clean work queue first */
+       if (!dev->slave) {
+               test_and_clear_bit(SSD_INIT_WORKQ, &dev->state);
+               ssd_cleanup_workq(dev);
+       }
+
+       /* flush cache */
+       (void)ssd_flush(dev);
+       (void)ssd_save_md(dev);
+
+       /* save smart */
+       if (!dev->slave) {
+               ssd_save_smart(dev);
+       }
+
+       if (test_and_clear_bit(SSD_INIT_BD, &dev->state)) {
+               ssd_cleanup_blkdev(dev);
+       }
+
+       if (!dev->slave) {
+               ssd_cleanup_chardev(dev);
+       }
+
+       /* clean routine */
+       if (!dev->slave) {
+               ssd_cleanup_routine(dev);
+       }
+
+       ssd_cleanup_queue(dev);
+
+       ssd_cleanup_tag(dev);
+       ssd_cleanup_thread(dev);
+
+       ssd_free_irq(dev);
+
+       ssd_cleanup_dcmd(dev);
+       ssd_cleanup_cmd(dev);
+       ssd_cleanup_response(dev);
+
+       if (!dev->slave) {
+               ssd_cleanup_log(dev);
+       }
+
+       if (dev->reload_fw) { //reload fw
+               ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW);
+       }
+
+       /* unmap physical adress */
+#ifdef LINUX_SUSE_OS
+       iounmap(dev->ctrlp);
+#else
+       pci_iounmap(pdev, dev->ctrlp);
+#endif
+
+       release_mem_region(dev->mmio_base, dev->mmio_len);
+
+       pci_disable_device(pdev);
+
+       pci_set_drvdata(pdev, NULL);
+
+       ssd_put(dev);
+}
+
+static int 
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38))
+__devinit 
+#endif
+ssd_init_one(struct pci_dev *pdev, 
+       const struct pci_device_id *ent)
+{
+       struct ssd_device *dev;
+       int ret = 0;
+
+       if (!pdev || !ent) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       dev = kmalloc(sizeof(struct ssd_device), GFP_KERNEL);
+       if (!dev) {
+               ret = -ENOMEM;
+               goto out_alloc_dev;
+       }
+       memset(dev, 0, sizeof(struct ssd_device));
+
+       dev->owner = THIS_MODULE;
+
+       if (SSD_SLAVE_PORT_DEVID == ent->device) {
+               dev->slave = 1;
+       }
+
+       dev->idx = ssd_get_index(dev->slave);
+       if (dev->idx < 0) {
+               ret = -ENOMEM;
+               goto out_get_index;
+       }
+
+       if (!dev->slave) {
+               snprintf(dev->name, SSD_DEV_NAME_LEN, SSD_DEV_NAME);
+               ssd_set_dev_name(&dev->name[strlen(SSD_DEV_NAME)], SSD_DEV_NAME_LEN-strlen(SSD_DEV_NAME), dev->idx);
+               
+               dev->major = ssd_major;
+               dev->cmajor = ssd_cmajor;
+       } else {
+               snprintf(dev->name, SSD_DEV_NAME_LEN, SSD_SDEV_NAME);
+               ssd_set_dev_name(&dev->name[strlen(SSD_SDEV_NAME)], SSD_DEV_NAME_LEN-strlen(SSD_SDEV_NAME), dev->idx);
+               dev->major = ssd_major_sl;
+               dev->cmajor = 0;
+       }
+
+       atomic_set(&(dev->refcnt), 0);
+       atomic_set(&(dev->tocnt), 0);
+
+       mutex_init(&dev->fw_mutex);
+
+       //xx
+       mutex_init(&dev->gd_mutex);
+
+       dev->pdev = pdev;
+       pci_set_drvdata(pdev, dev);
+
+       kref_init(&dev->kref);
+
+       ret = pci_enable_device(pdev);
+       if (ret) {
+               hio_warn("%s: can not enable device\n", dev->name);
+               goto out_enable_device;
+       }
+
+       pci_set_master(pdev);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31))
+       ret = pci_set_dma_mask(pdev, DMA_64BIT_MASK);
+#else
+       ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+#endif
+       if (ret) {
+               hio_warn("%s: set dma mask: failed\n", dev->name);
+               goto out_set_dma_mask;
+       }
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31))
+       ret = pci_set_consistent_dma_mask(pdev, DMA_64BIT_MASK);
+#else
+       ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+#endif
+       if (ret) {
+               hio_warn("%s: set consistent dma mask: failed\n", dev->name);
+               goto out_set_dma_mask;
+       }
+
+       dev->mmio_base = pci_resource_start(pdev, 0);
+       dev->mmio_len = pci_resource_len(pdev, 0);
+
+       if (!request_mem_region(dev->mmio_base, dev->mmio_len, SSD_DEV_NAME)) {
+               hio_warn("%s: can not reserve MMIO region 0\n", dev->name);
+               ret = -EBUSY;
+               goto out_request_mem_region;
+       }
+
+       /* 2.6.9 kernel bug */
+       dev->ctrlp = pci_iomap(pdev, 0, 0);
+       if (!dev->ctrlp) {
+               hio_warn("%s: can not remap IO region 0\n", dev->name);
+               ret = -ENOMEM;
+               goto out_pci_iomap;
+       }
+
+       ret = ssd_check_hw(dev);
+       if (ret) {
+               hio_err("%s: check hardware failed\n", dev->name);
+               goto out_check_hw;
+       }
+
+       ret = ssd_init_protocol_info(dev);
+       if (ret) {
+               hio_err("%s: init protocol info failed\n", dev->name);
+               goto out_init_protocol_info;
+       }
+
+       /* alarm led ? */
+       ssd_clear_alarm(dev);
+
+       ret = ssd_init_fw_info(dev);
+       if (ret) {
+               hio_err("%s: init firmware info failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_fw_info;
+       }
+       
+       /* slave port ? */
+       if (dev->slave) {
+               goto init_next1;
+       }
+
+       ret = ssd_init_rom_info(dev);
+       if (ret) {
+               hio_err("%s: init rom info failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_rom_info;
+       }
+
+       ret = ssd_init_label(dev);
+       if (ret) {
+               hio_err("%s: init label failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_label;
+       }
+
+       ret = ssd_init_workq(dev);
+       if (ret) {
+               hio_warn("%s: init workq failed\n", dev->name);
+               goto out_init_workq;
+       }
+       (void)test_and_set_bit(SSD_INIT_WORKQ, &dev->state);
+
+       ret = ssd_init_log(dev);
+       if (ret) {
+               hio_err("%s: init log failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_log;
+       }
+
+       ret = ssd_init_smart(dev);
+       if (ret) {
+               hio_err("%s: init info failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_smart;
+       }
+
+init_next1:
+       ret = ssd_init_hw_info(dev);
+       if (ret) {
+               hio_err("%s: init hardware info failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_hw_info;
+       }
+
+       /* slave port ? */
+       if (dev->slave) {
+               goto init_next2;
+       }
+
+       ret = ssd_init_sensor(dev);
+       if (ret) {
+               hio_err("%s: init sensor failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_sensor;
+       }
+
+       ret = ssd_init_pl_cap(dev);
+       if (ret) {
+               hio_err("%s: int pl_cap failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_init_pl_cap;
+       }
+
+init_next2:
+       ret = ssd_check_init_state(dev);
+       if (ret) {
+               hio_err("%s: check init state failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_check_init_state;
+       }
+
+       ret = ssd_init_response(dev);
+       if (ret) {
+               hio_warn("%s: init resp_msg failed\n", dev->name);
+               goto out_init_response;
+       }
+
+       ret = ssd_init_cmd(dev);
+       if (ret) {
+               hio_warn("%s: init msg failed\n", dev->name);
+               goto out_init_cmd;
+       }
+
+       ret = ssd_init_dcmd(dev);
+       if (ret) {
+               hio_warn("%s: init cmd failed\n", dev->name);
+               goto out_init_dcmd;
+       }
+
+       ret = ssd_init_irq(dev);
+       if (ret) {
+               hio_warn("%s: init irq failed\n", dev->name);
+               goto out_init_irq;
+       }
+
+       ret = ssd_init_thread(dev);
+       if (ret) {
+               hio_warn("%s: init thread failed\n", dev->name);
+               goto out_init_thread;
+       }
+
+       ret = ssd_init_tag(dev);
+       if(ret) {
+               hio_warn("%s: init tags failed\n", dev->name);
+               goto out_init_tags;
+       }
+
+       /*  */
+       (void)test_and_set_bit(SSD_ONLINE, &dev->state);
+
+       ret = ssd_init_queue(dev);
+       if (ret) {
+               hio_warn("%s: init queue failed\n", dev->name);
+               goto out_init_queue;
+       }
+
+       /* slave port ? */
+       if (dev->slave) {
+               goto init_next3;
+       }
+
+       ret = ssd_init_ot_protect(dev);
+       if (ret) {
+               hio_err("%s: int ot_protect failed\n", dev->name);
+               /* alarm led */
+               ssd_set_alarm(dev);
+               goto out_int_ot_protect;
+       }
+
+       ret = ssd_init_wmode(dev);
+       if (ret) {
+               hio_warn("%s: init write mode\n", dev->name);
+               goto out_init_wmode;
+       }
+
+       /* init routine after hw is ready */
+       ret = ssd_init_routine(dev);
+       if (ret) {
+               hio_warn("%s: init routine\n", dev->name);
+               goto out_init_routine;
+       }
+
+       ret = ssd_init_chardev(dev);
+       if (ret) {
+               hio_warn("%s: register char device failed\n", dev->name);
+               goto out_init_chardev;
+       }
+
+init_next3:
+       ret = ssd_init_blkdev(dev);
+       if (ret) {
+               hio_warn("%s: register block device failed\n", dev->name);
+               goto out_init_blkdev;
+       }
+       (void)test_and_set_bit(SSD_INIT_BD, &dev->state);
+
+       ret = ssd_register_sysfs(dev);
+       if (ret) {
+               hio_warn("%s: register sysfs failed\n", dev->name);
+               goto out_register_sysfs;
+       }
+
+       dev->save_md = 1;
+
+       list_add_tail(&dev->list, &ssd_list);
+
+       return 0;
+
+out_register_sysfs:
+       test_and_clear_bit(SSD_INIT_BD, &dev->state);
+       ssd_cleanup_blkdev(dev);
+out_init_blkdev:
+       /* slave port ? */
+       if (!dev->slave) {
+               ssd_cleanup_chardev(dev);
+       }
+out_init_chardev:
+       /* slave port ? */
+       if (!dev->slave) {
+               ssd_cleanup_routine(dev);
+       }
+out_init_routine:
+out_init_wmode:
+out_int_ot_protect:
+       ssd_cleanup_queue(dev);
+out_init_queue:
+       test_and_clear_bit(SSD_ONLINE, &dev->state);
+       ssd_cleanup_tag(dev);
+out_init_tags:
+       ssd_cleanup_thread(dev);
+out_init_thread:
+       ssd_free_irq(dev);
+out_init_irq:
+       ssd_cleanup_dcmd(dev);
+out_init_dcmd:
+       ssd_cleanup_cmd(dev);
+out_init_cmd:
+       ssd_cleanup_response(dev);
+out_init_response:
+out_check_init_state:
+out_init_pl_cap:
+out_init_sensor:
+out_init_hw_info:
+out_init_smart:
+       /* slave port ? */
+       if (!dev->slave) {
+               ssd_cleanup_log(dev);
+       }
+out_init_log:
+       /* slave port ? */
+       if (!dev->slave) {
+               test_and_clear_bit(SSD_INIT_WORKQ, &dev->state);
+               ssd_cleanup_workq(dev);
+       }
+out_init_workq:
+out_init_label:
+out_init_rom_info:
+out_init_fw_info:
+out_init_protocol_info:
+out_check_hw:
+#ifdef LINUX_SUSE_OS
+       iounmap(dev->ctrlp);
+#else
+       pci_iounmap(pdev, dev->ctrlp);
+#endif
+out_pci_iomap:
+       release_mem_region(dev->mmio_base, dev->mmio_len);
+out_request_mem_region:
+out_set_dma_mask:
+       pci_disable_device(pdev);
+out_enable_device:
+       pci_set_drvdata(pdev, NULL);
+out_get_index:
+       kfree(dev);
+out_alloc_dev:
+out:
+       return ret;
+}
+
+static void ssd_cleanup_tasklet(void)
+{
+       int i;
+       for_each_online_cpu(i) {
+               tasklet_kill(&per_cpu(ssd_tasklet, i));
+       }
+}
+
+static int ssd_init_tasklet(void)
+{
+       int i;
+
+       for_each_online_cpu(i) {
+               INIT_LIST_HEAD(&per_cpu(ssd_doneq, i));
+
+               if (finject) {
+                       tasklet_init(&per_cpu(ssd_tasklet, i), __ssd_done_db, 0);
+               } else {
+                       tasklet_init(&per_cpu(ssd_tasklet, i), __ssd_done, 0);
+               }
+       }
+
+       return 0;
+}
+
+static struct pci_device_id ssd_pci_tbl[] = {
+       { 0x10ee, 0x0007, PCI_ANY_ID, PCI_ANY_ID, }, /* g3 */
+       { 0x19e5, 0x0007, PCI_ANY_ID, PCI_ANY_ID, }, /* v1 */
+       //{ 0x19e5, 0x0008, PCI_ANY_ID, PCI_ANY_ID, }, /* v1 sp*/
+       { 0x19e5, 0x0009, PCI_ANY_ID, PCI_ANY_ID, }, /* v2 */
+       { 0x19e5, 0x000a, PCI_ANY_ID, PCI_ANY_ID, }, /* v2 dp slave*/
+       { 0, }
+};
+MODULE_DEVICE_TABLE(pci, ssd_pci_tbl);
+
+static struct pci_driver ssd_driver = {
+       .name           = MODULE_NAME, 
+       .id_table       = ssd_pci_tbl, 
+       .probe          = ssd_init_one, 
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38))      
+       .remove         = __devexit_p(ssd_remove_one), 
+#else
+       .remove         = ssd_remove_one, 
+#endif
+};
+
+/* notifier block to get a notify on system shutdown/halt/reboot */
+static int ssd_notify_reboot(struct notifier_block *nb, unsigned long event, void *buf)
+{
+       struct ssd_device *dev = NULL;
+       struct ssd_device *n = NULL;
+
+       list_for_each_entry_safe(dev, n, &ssd_list, list) {
+               ssd_gen_swlog(dev, SSD_LOG_POWER_OFF, 0);
+       
+               (void)ssd_flush(dev);
+               (void)ssd_save_md(dev);
+
+               /* slave port ? */
+               if (!dev->slave) {
+                       ssd_save_smart(dev);
+
+                       ssd_stop_workq(dev);
+
+                       if (dev->reload_fw) {
+                               ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW);
+                       }
+               }
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block ssd_notifier = {
+       ssd_notify_reboot, NULL, 0
+};
+
+static int __init ssd_init_module(void)
+{
+       int ret = 0;
+
+       hio_info("driver version: %s\n", DRIVER_VERSION);
+
+       ret = ssd_init_index();
+       if (ret) {
+               hio_warn("init index failed\n");
+               goto out_init_index;
+       }
+
+       ret = ssd_init_proc();
+       if (ret) {
+               hio_warn("init proc failed\n");
+               goto out_init_proc;
+       }
+
+       ret = ssd_init_sysfs();
+       if (ret) {
+               hio_warn("init sysfs failed\n");
+               goto out_init_sysfs;
+       }
+
+       ret = ssd_init_tasklet();
+       if (ret) {
+               hio_warn("init tasklet failed\n");
+               goto out_init_tasklet;
+       }
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+       ssd_class = class_simple_create(THIS_MODULE, SSD_DEV_NAME);
+#else
+       ssd_class = class_create(THIS_MODULE, SSD_DEV_NAME);
+#endif
+       if (IS_ERR(ssd_class)) {
+               ret = PTR_ERR(ssd_class);
+               goto out_class_create;
+       }
+
+       if (ssd_cmajor > 0) {
+               ret = register_chrdev(ssd_cmajor, SSD_CDEV_NAME, &ssd_cfops);
+       } else {
+               ret = ssd_cmajor = register_chrdev(ssd_cmajor, SSD_CDEV_NAME, &ssd_cfops);
+       }
+       if (ret < 0) {
+               hio_warn("unable to register chardev major number\n");
+               goto out_register_chardev;
+       }
+
+       if (ssd_major > 0) {
+               ret = register_blkdev(ssd_major, SSD_DEV_NAME);
+       } else {
+               ret = ssd_major = register_blkdev(ssd_major, SSD_DEV_NAME);
+       }
+       if (ret < 0) {
+               hio_warn("unable to register major number\n");
+               goto out_register_blkdev;
+       }
+
+       if (ssd_major_sl > 0) {
+               ret = register_blkdev(ssd_major_sl, SSD_SDEV_NAME);
+       } else {
+               ret = ssd_major_sl = register_blkdev(ssd_major_sl, SSD_SDEV_NAME);
+       }
+       if (ret < 0) {
+               hio_warn("unable to register slave major number\n");
+               goto out_register_blkdev_sl;
+       }
+
+       if (mode < SSD_DRV_MODE_STANDARD || mode > SSD_DRV_MODE_BASE) {
+               mode = SSD_DRV_MODE_STANDARD;
+       }
+
+       /* for debug */
+       if (mode != SSD_DRV_MODE_STANDARD) {
+               ssd_minors = 1;
+       }
+
+       if (int_mode < SSD_INT_LEGACY || int_mode > SSD_INT_MSIX) {
+               int_mode = SSD_INT_MODE_DEFAULT;
+       }
+
+       if (threaded_irq) {
+               int_mode = SSD_INT_MSI;
+       }
+
+       if (log_level >= SSD_LOG_NR_LEVEL || log_level < SSD_LOG_LEVEL_INFO) {
+               log_level = SSD_LOG_LEVEL_ERR;
+       }
+
+       if (wmode < SSD_WMODE_BUFFER || wmode > SSD_WMODE_DEFAULT) {
+               wmode = SSD_WMODE_DEFAULT;
+       }
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+       ret = pci_module_init(&ssd_driver);
+#else
+       ret = pci_register_driver(&ssd_driver);
+#endif
+       if (ret) {
+               hio_warn("pci init failed\n");
+               goto out_pci_init;
+       }
+
+       ret = register_reboot_notifier(&ssd_notifier);
+       if (ret) {
+               hio_warn("register reboot notifier failed\n");
+               goto out_register_reboot_notifier;
+       }
+
+       return 0;
+
+out_register_reboot_notifier:
+out_pci_init:
+       pci_unregister_driver(&ssd_driver);
+       unregister_blkdev(ssd_major_sl, SSD_SDEV_NAME);
+out_register_blkdev_sl:
+       unregister_blkdev(ssd_major, SSD_DEV_NAME);
+out_register_blkdev:
+       unregister_chrdev(ssd_cmajor, SSD_CDEV_NAME);
+out_register_chardev:
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+       class_simple_destroy(ssd_class);
+#else
+       class_destroy(ssd_class);
+#endif
+out_class_create:
+       ssd_cleanup_tasklet();
+out_init_tasklet:
+       ssd_cleanup_sysfs();
+out_init_sysfs:
+       ssd_cleanup_proc();
+out_init_proc:
+       ssd_cleanup_index();
+out_init_index:
+       return ret;
+
+}
+
+static void __exit ssd_cleanup_module(void)
+{
+
+       hio_info("unload driver: %s\n", DRIVER_VERSION);
+       /* exiting */
+       ssd_exiting = 1;
+
+       unregister_reboot_notifier(&ssd_notifier);
+
+       pci_unregister_driver(&ssd_driver);
+
+       unregister_blkdev(ssd_major_sl, SSD_SDEV_NAME);
+       unregister_blkdev(ssd_major, SSD_DEV_NAME);
+       unregister_chrdev(ssd_cmajor, SSD_CDEV_NAME);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12))
+       class_simple_destroy(ssd_class);
+#else
+       class_destroy(ssd_class);
+#endif
+
+       ssd_cleanup_tasklet();
+       ssd_cleanup_sysfs();
+       ssd_cleanup_proc();
+       ssd_cleanup_index();
+}
+
+int ssd_register_event_notifier(struct block_device *bdev, ssd_event_call event_call)
+{
+       struct ssd_device *dev;
+       struct timeval tv;
+       struct ssd_log *le;
+       uint64_t cur;
+       int log_nr;
+
+       if (!bdev || !event_call || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+       dev->event_call = event_call;
+
+       do_gettimeofday(&tv);
+       cur = tv.tv_sec;
+
+       le = (struct ssd_log *)(dev->internal_log.log);
+       log_nr = dev->internal_log.nr_log;
+
+       while (log_nr--) {
+               if (le->time <= cur && le->time >= dev->uptime) {
+                       (void)dev->event_call(dev->gd, le->le.event, ssd_parse_log(dev, le, 0));
+               }
+               le++;
+       }
+
+       return 0;
+}
+
+int ssd_unregister_event_notifier(struct block_device *bdev)
+{
+       struct ssd_device *dev;
+
+       if (!bdev || !(bdev->bd_disk)) {
+               return -EINVAL;
+       }
+
+       dev = bdev->bd_disk->private_data;
+       dev->event_call = NULL;
+
+       return 0;
+}
+
+EXPORT_SYMBOL(ssd_get_label);
+EXPORT_SYMBOL(ssd_get_version);
+EXPORT_SYMBOL(ssd_set_otprotect);
+EXPORT_SYMBOL(ssd_bm_status);
+EXPORT_SYMBOL(ssd_submit_pbio);
+EXPORT_SYMBOL(ssd_get_pciaddr);
+EXPORT_SYMBOL(ssd_get_temperature);
+EXPORT_SYMBOL(ssd_register_event_notifier);
+EXPORT_SYMBOL(ssd_unregister_event_notifier);
+EXPORT_SYMBOL(ssd_reset);
+EXPORT_SYMBOL(ssd_set_wmode);
+
+
+
+module_init(ssd_init_module);
+module_exit(ssd_cleanup_module);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huawei SSD DEV Team");
+MODULE_DESCRIPTION("Huawei SSD driver");
diff --git a/ubuntu/hio/hio.h b/ubuntu/hio/hio.h
new file mode 100644 (file)
index 0000000..49fbb45
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+* Huawei SSD device driver
+* Copyright (c) 2016, Huawei Technologies Co., Ltd.
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope 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.
+*/
+
+#ifndef _HIO_H
+#define _HIO_H
+
+#include <linux/types.h>
+#include <linux/genhd.h>
+#include <linux/blkdev.h>
+#include <linux/genhd.h>
+
+
+
+typedef int (*ssd_event_call)(struct gendisk *, int, int);     /* gendisk, event id, event level */
+extern int ssd_register_event_notifier(struct block_device *bdev, ssd_event_call event_call);
+/* unregister event notifier before module exit */
+extern int ssd_unregister_event_notifier(struct block_device *bdev);
+
+
+/* label */
+#define SSD_LABEL_FIELD_SZ     32
+#define SSD_SN_SZ                      16
+
+typedef struct ssd_label
+{
+       char date[SSD_LABEL_FIELD_SZ];
+       char sn[SSD_LABEL_FIELD_SZ];
+       char part[SSD_LABEL_FIELD_SZ];
+       char desc[SSD_LABEL_FIELD_SZ];
+       char other[SSD_LABEL_FIELD_SZ];
+       char maf[SSD_LABEL_FIELD_SZ];
+} ssd_label_t;
+
+
+/* version */
+typedef struct ssd_version_info
+{
+       uint32_t bridge_ver;    /* bridge fw version: hex */
+       uint32_t ctrl_ver;              /* controller fw version: hex */
+       uint32_t bm_ver;                /* battery manager fw version: hex */
+       uint8_t  pcb_ver;               /* main pcb version: char */
+       uint8_t  upper_pcb_ver;
+       uint8_t  pad0;
+       uint8_t  pad1;
+} ssd_version_info_t;
+
+extern int ssd_get_label(struct block_device *bdev, struct ssd_label *label);
+extern int ssd_get_version(struct block_device *bdev, struct ssd_version_info *ver);
+extern int ssd_get_temperature(struct block_device *bdev, int *temp);
+
+
+enum ssd_bmstatus
+{
+       SSD_BMSTATUS_OK = 0,
+       SSD_BMSTATUS_CHARGING, 
+       SSD_BMSTATUS_WARNING
+};
+extern int ssd_bm_status(struct block_device *bdev, int *status);
+
+enum ssd_otprotect
+{
+       SSD_OTPROTECT_OFF = 0,
+       SSD_OTPROTECT_ON
+};
+extern int ssd_set_otprotect(struct block_device *bdev, int otprotect);
+
+typedef struct pci_addr
+{
+       uint16_t domain;
+       uint8_t bus;
+       uint8_t slot;
+       uint8_t func;
+} pci_addr_t;
+extern int ssd_get_pciaddr(struct block_device *bdev, struct pci_addr *paddr);
+
+/* submit phys bio: phys addr in iovec */
+extern void ssd_submit_pbio(struct request_queue *q, struct bio *bio);
+
+extern int ssd_reset(struct block_device *bdev);
+
+enum ssd_write_mode
+{
+       SSD_WMODE_BUFFER = 0,
+       SSD_WMODE_BUFFER_EX,
+       SSD_WMODE_FUA,
+       /* dummy */
+       SSD_WMODE_AUTO, 
+       SSD_WMODE_DEFAULT
+};
+extern int ssd_set_wmode(struct block_device *bdev, int wmode);
+
+#endif
+