[PATCH V2 09/13] pm80xx : IOCTL functionality for GPIO.

Deepak Ukey
2020-01-17T07:10:49Z

From: Deepak Ukey <Deepak.Ukey@microchip.com>

Added IOCTL functionality for GPIO.
The SPCv controller provides 24 GPIO signals. The first 12 signals
[11:0] and the last 4 signals [23:20] are for customer use. Eight
signals [19:12] are reserved for the SPCv controller firmware.
Whenever the host performs GPIO setup or a read/write operation
using the GPIO command the host needs to make sure that it does
not disturb the GPIO configuration for the bits [19:12].
Each signal can be configured either as an input or as an output.
When configured as an output, the host can use the GPIO Command to
set the desired level. GPIO inputs can also be configured so that
the SPCv controller sends the GPIO Event notification when specific
GPIO events occur.
Different GPIO features implemented:
1) GPIO Pin Setup
2) GPIO Event Setup
3) GPIO Read
4) GPIO Write

Signed-off-by: Deepak Ukey <deepak.ukey@microchip.com>
Signed-off-by: Viswas G <Viswas.G@microchip.com>
Signed-off-by: Radha Ramachandran <radha@google.com>
---
drivers/scsi/pm8001/pm8001_ctl.c | 95 ++++++++++++++++++++++++++++++++++
drivers/scsi/pm8001/pm8001_ctl.h | 24 +++++++++
drivers/scsi/pm8001/pm8001_init.c | 6 +++
drivers/scsi/pm8001/pm8001_sas.h | 17 ++++++
drivers/scsi/pm8001/pm80xx_hwi.c | 106 ++++++++++++++++++++++++++++++++++++++
drivers/scsi/pm8001/pm80xx_hwi.h | 49 ++++++++++++++++++
6 files changed, 297 insertions(+)

diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c
index 7f45e114a31a..cadb5be394b6 100644
--- a/drivers/scsi/pm8001/pm8001_ctl.c
+++ b/drivers/scsi/pm8001/pm8001_ctl.c
@@ -39,6 +39,7 @@
*/
#include <linux/firmware.h>
#include <linux/slab.h>
+#include <linux/poll.h>
#include "pm8001_sas.h"
#include "pm8001_ctl.h"
#include "pm80xx_hwi.h"
@@ -941,6 +942,73 @@ static long pm8001_info_ioctl(struct pm8001_hba_info *pm8001_ha,
return ret;
}

+static long pm8001_gpio_ioctl(struct pm8001_hba_info *pm8001_ha,
+ unsigned long arg)
+{
+ struct gpio_buffer buffer;
+ struct pm8001_gpio *payload;
+ struct gpio_ioctl_resp *gpio_resp;
+ DECLARE_COMPLETION_ONSTACK(completion);
+ unsigned long timeout;
+ u32 ret = 0, operation;
+
+ if (pm8001_ha->pdev->subsystem_vendor == PCI_VENDOR_ID_ADAPTEC2)
+ return ADPT_IOCTL_CALL_INVALID_DEVICE;
+
+ if (copy_from_user(&buffer, (struct gpio_buffer *)arg,
+ sizeof(struct gpio_buffer))) {
+ ret = ADPT_IOCTL_CALL_FAILED;
+ }
+ mutex_lock(&pm8001_ha->ioctl_mutex);
+ pm8001_ha->ioctl_completion = &completion;
+ payload = &buffer.gpio_payload;
+ operation = payload->operation;
+ ret = PM8001_CHIP_DISP->gpio_req(pm8001_ha, payload);
+ if (ret != 0) {
+ ret = ADPT_IOCTL_CALL_FAILED;
+ goto exit;
+ }
+
+ timeout = (unsigned long)buffer.header.timeout * 1000;
+ if (timeout < 2000)
+ timeout = 2000;
+
+ timeout = wait_for_completion_timeout(&completion,
+ msecs_to_jiffies(timeout));
+ if (timeout == 0) {
+ ret = ADPT_IOCTL_CALL_TIMEOUT;
+ goto exit;
+ }
+ gpio_resp = &pm8001_ha->gpio_resp;
+ buffer.header.return_code = ADPT_IOCTL_CALL_SUCCESS;
+
+ if (operation == GPIO_READ) {
+ payload->rd_wr_val = gpio_resp->gpio_rd_val;
+ payload->input_enable = gpio_resp->gpio_in_enabled;
+ payload->pinsetup1 = gpio_resp->gpio_pinsetup1;
+ payload->pinsetup2 = gpio_resp->gpio_pinsetup2;
+ payload->event_level = gpio_resp->gpio_evt_change;
+ payload->event_rising_edge = gpio_resp->gpio_evt_rise;
+ payload->event_falling_edge = gpio_resp->gpio_evt_fall;
+
+ if (copy_to_user((void *)arg, (void *)&buffer,
+ sizeof(struct gpio_buffer))) {
+ ret = ADPT_IOCTL_CALL_FAILED;
+ }
+ } else {
+ if (copy_to_user((void *)arg, (void *)&buffer.header,
+ sizeof(struct ioctl_header))) {
+ ret = ADPT_IOCTL_CALL_FAILED;
+ }
+ }
+exit:
+ spin_lock_irq(&pm8001_ha->ioctl_lock);
+ pm8001_ha->ioctl_completion = NULL;
+ spin_unlock_irq(&pm8001_ha->ioctl_lock);
+ mutex_unlock(&pm8001_ha->ioctl_mutex);
+ return ret;
+}
+
static int pm8001_ioctl_get_phy_profile(struct pm8001_hba_info *pm8001_ha,
unsigned long arg)
{
@@ -1100,6 +1168,9 @@ static long pm8001_ioctl(struct file *file,
case ADPT_IOCTL_INFO:
ret = pm8001_info_ioctl(pm8001_ha, arg);
break;
+ case ADPT_IOCTL_GPIO:
+ ret = pm8001_gpio_ioctl(pm8001_ha, arg);
+ break;
case ADPT_IOCTL_GET_PHY_PROFILE:
ret = pm8001_ioctl_get_phy_profile(pm8001_ha, arg);
return ret;
@@ -1122,11 +1193,35 @@ static long pm8001_ioctl(struct file *file,
return ret;
}

+/**
+ *pm8001_poll - pm8001 poll request function
+ *@file: file handle
+ *@wait: poll table to wait
+ *Handles a poll request.
+ */
+__poll_t pm8001_poll(struct file *file, poll_table *wait)
+{
+ struct pm8001_hba_info *pm8001_ha;
+ __poll_t mask = 0;
+
+ pm8001_ha = file->private_data;
+
+ poll_wait(file, &pm8001_ha->pollq, wait);
+
+ if (pm8001_ha->gpio_event_occurred == 1) {
+ pm8001_ha->gpio_event_occurred = 0;
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ return mask;
+}
+
static const struct file_operations pm8001_fops = {
.owner = THIS_MODULE,
.open = pm8001_open,
.release = pm8001_close,
.unlocked_ioctl = pm8001_ioctl,
+ .poll = pm8001_poll,
};

/**
diff --git a/drivers/scsi/pm8001/pm8001_ctl.h b/drivers/scsi/pm8001/pm8001_ctl.h
index 686ad69f0e0c..5be43b2672d4 100644
--- a/drivers/scsi/pm8001/pm8001_ctl.h
+++ b/drivers/scsi/pm8001/pm8001_ctl.h
@@ -64,6 +64,7 @@
#define ADPT_IOCTL_CALL_FAILED 0x01
#define ADPT_IOCTL_CALL_INVALID_CODE 0x03
#define ADPT_IOCTL_CALL_INVALID_DEVICE 0x04
+#define ADPT_IOCTL_CALL_TIMEOUT 0x08

#define MAX_NUM_PHYS 16

@@ -86,11 +87,33 @@ struct ioctl_drv_info {
u32 reserved[16];
};

+#define GPIO_READ 0
+#define GPIO_WRITE 1
+#define GPIO_PINSETUP 2
+#define GPIO_EVENTSETUP 3
+
+struct pm8001_gpio {
+ u32 operation;
+ u32 mask;
+ u32 rd_wr_val;
+ u32 input_enable;
+ u32 pinsetup1;
+ u32 pinsetup2;
+ u32 event_level;
+ u32 event_rising_edge;
+ u32 event_falling_edge;
+};
+
struct ioctl_info_buffer {
struct ioctl_header header;
struct ioctl_drv_info information;
};

+struct gpio_buffer {
+ struct ioctl_header header;
+ struct pm8001_gpio gpio_payload;
+};
+
struct phy_profile {
char phy_id;
unsigned int phys:4;
@@ -119,6 +142,7 @@ struct phy_prof_resp {
};

#define ADPT_IOCTL_INFO _IOR(ADPT_MAGIC_NUMBER, 0, struct ioctl_info_buffer *)
+#define ADPT_IOCTL_GPIO _IOWR(ADPT_MAGIC_NUMBER, 1, struct gpio_buffer *)
#define ADPT_IOCTL_GET_PHY_PROFILE _IOWR(ADPT_MAGIC_NUMBER, 8, \
struct phy_profile*)
#define ADPT_IOCTL_GET_PHY_ERR_CNT _IOWR(ADPT_MAGIC_NUMBER, 9, \
diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c
index 25e74f1dbd0c..6e2512aa5f6e 100644
--- a/drivers/scsi/pm8001/pm8001_init.c
+++ b/drivers/scsi/pm8001/pm8001_init.c
@@ -498,6 +498,12 @@ static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev,
else
pm8001_ha->iomb_size = IOMB_SIZE_SPC;

+ mutex_init(&pm8001_ha->ioctl_mutex);
+ pm8001_ha->ioctl_completion = NULL;
+ init_waitqueue_head(&pm8001_ha->pollq);
+ pm8001_ha->gpio_event_occurred = 0;
+ spin_lock_init(&pm8001_ha->ioctl_lock);
+
#ifdef PM8001_USE_TASKLET
/* Tasklet for non msi-x interrupt handler */
if ((!pdev->msix_cap || !pci_msi_enabled())
diff --git a/drivers/scsi/pm8001/pm8001_sas.h b/drivers/scsi/pm8001/pm8001_sas.h
index 87d676810a18..7d9920376fb7 100644
--- a/drivers/scsi/pm8001/pm8001_sas.h
+++ b/drivers/scsi/pm8001/pm8001_sas.h
@@ -132,6 +132,18 @@ extern const struct pm8001_dispatch pm8001_80xx_dispatch;
struct pm8001_hba_info;
struct pm8001_ccb_info;
struct pm8001_device;
+
+struct gpio_ioctl_resp {
+ u32 tag;
+ u32 gpio_rd_val;
+ u32 gpio_in_enabled;
+ u32 gpio_pinsetup1;
+ u32 gpio_pinsetup2;
+ u32 gpio_evt_change;
+ u32 gpio_evt_rise;
+ u32 gpio_evt_fall;
+};
+
/* define task management IU */
struct pm8001_tmf_task {
u8 tmf;
@@ -247,6 +259,8 @@ struct pm8001_dispatch {
int (*sas_diag_execute_req)(struct pm8001_hba_info *pm8001_ha,
u32 state);
int (*sas_re_init_req)(struct pm8001_hba_info *pm8001_ha);
+ int (*gpio_req)(struct pm8001_hba_info *pm8001_ha,
+ struct pm8001_gpio *gpio_payload);
int (*get_phy_profile_req)(struct pm8001_hba_info *pm8001_ha,
int phy, int page);
};
@@ -562,6 +576,9 @@ struct pm8001_hba_info {
u32 smp_exp_mode;
bool controller_fatal_error;
const struct firmware *fw_image;
+ struct gpio_ioctl_resp gpio_resp;
+ u32 gpio_event_occurred;
+ wait_queue_head_t pollq;
struct isr_param irq_vector[PM8001_MAX_MSIX_VEC];
spinlock_t ioctl_lock;
struct mutex ioctl_mutex;
diff --git a/drivers/scsi/pm8001/pm80xx_hwi.c b/drivers/scsi/pm8001/pm80xx_hwi.c
index e8af262976d6..2cdcb1c64970 100644
--- a/drivers/scsi/pm8001/pm80xx_hwi.c
+++ b/drivers/scsi/pm8001/pm80xx_hwi.c
@@ -3845,6 +3845,50 @@ static int ssp_coalesced_comp_resp(struct pm8001_hba_info *pm8001_ha,
return 0;
}

+static int mpi_gpio_resp(struct pm8001_hba_info *pm8001_ha, void *piomb)
+{
+ u32 tag;
+ struct gpio_ioctl_resp *pgpio_resp;
+ struct gpio_resp *ppayload = (struct gpio_resp *)(piomb + 4);
+
+ tag = le32_to_cpu(ppayload->tag);
+ spin_lock(&pm8001_ha->ioctl_lock);
+ if (pm8001_ha->ioctl_completion != NULL) {
+ pgpio_resp = &pm8001_ha->gpio_resp;
+ pgpio_resp->gpio_rd_val = le32_to_cpu(ppayload->gpio_rd_val);
+ pgpio_resp->gpio_in_enabled =
+ le32_to_cpu(ppayload->gpio_in_enabled);
+ pgpio_resp->gpio_pinsetup1 =
+ le32_to_cpu(ppayload->gpio_pinsetup1);
+ pgpio_resp->gpio_pinsetup2 =
+ le32_to_cpu(ppayload->gpio_pinsetup2);
+ pgpio_resp->gpio_evt_change =
+ le32_to_cpu(ppayload->gpio_evt_change);
+ pgpio_resp->gpio_evt_rise =
+ le32_to_cpu(ppayload->gpio_evt_rise);
+ pgpio_resp->gpio_evt_fall =
+ le32_to_cpu(ppayload->gpio_evt_fall);
+
+ complete(pm8001_ha->ioctl_completion);
+ }
+ spin_unlock(&pm8001_ha->ioctl_lock);
+ pm8001_tag_free(pm8001_ha, tag);
+ return 0;
+}
+
+static int mpi_gpio_event(struct pm8001_hba_info *pm8001_ha, void *piomb)
+{
+ u32 gpio_event = 0;
+ struct gpio_event *ppayload = (struct gpio_event *)(piomb + 4);
+
+ gpio_event = le32_to_cpu(ppayload->gpio_event);
+ PM8001_MSG_DBG(pm8001_ha,
+ pm8001_printk("GPIO event: 0x%X\n", gpio_event));
+ pm8001_ha->gpio_event_occurred = 1;
+ wake_up_interruptible(&pm8001_ha->pollq);
+ return 0;
+}
+
/**
* process_one_iomb - process one outbound Queue memory block
* @pm8001_ha: our hba card information
@@ -3931,10 +3975,12 @@ static void process_one_iomb(struct pm8001_hba_info *pm8001_ha, void *piomb)
case OPC_OUB_GPIO_RESPONSE:
PM8001_MSG_DBG(pm8001_ha,
pm8001_printk("OPC_OUB_GPIO_RESPONSE\n"));
+ mpi_gpio_resp(pm8001_ha, piomb);
break;
case OPC_OUB_GPIO_EVENT:
PM8001_MSG_DBG(pm8001_ha,
pm8001_printk("OPC_OUB_GPIO_EVENT\n"));
+ mpi_gpio_event(pm8001_ha, piomb);
break;
case OPC_OUB_GENERAL_EVENT:
PM8001_MSG_DBG(pm8001_ha,
@@ -5038,6 +5084,65 @@ void pm8001_set_phy_profile_single(struct pm8001_hba_info *pm8001_ha,
PM8001_INIT_DBG(pm8001_ha,
pm8001_printk("PHY %d settings applied", phy));
}
+
+/**
+ * pm80xx_chip_gpio_req - support for GPIO operation
+ * @pm8001_ha: our hba card information.
+ * @ioctl_payload: the payload for the GPIO operation
+ */
+int pm80xx_chip_gpio_req(struct pm8001_hba_info *pm8001_ha,
+ struct pm8001_gpio *gpio_payload)
+{
+ struct gpio_req payload;
+ struct inbound_queue_table *circularQ;
+ int ret;
+ u32 tag;
+ u32 opc = OPC_INB_GPIO;
+
+ if (pm8001_ha->pdev->subsystem_vendor == PCI_VENDOR_ID_ADAPTEC2)
+ return -1;
+
+ ret = pm8001_tag_alloc(pm8001_ha, &tag);
+ if (ret)
+ return -1;
+
+ memset(&payload, 0, sizeof(payload));
+ circularQ = &pm8001_ha->inbnd_q_tbl[0];
+ payload.tag = cpu_to_le32(tag);
+
+ switch (gpio_payload->operation) {
+ case GPIO_READ:
+ payload.eobid_ge_gs_gr_gw = cpu_to_le32(GPIO_GR_BIT);
+ break;
+ case GPIO_WRITE:
+ payload.eobid_ge_gs_gr_gw = cpu_to_le32(GPIO_GW_BIT);
+ payload.gpio_wr_msk = cpu_to_le32(gpio_payload->mask);
+ payload.gpio_wr_val = cpu_to_le32(gpio_payload->rd_wr_val);
+ break;
+ case GPIO_PINSETUP:
+ payload.eobid_ge_gs_gr_gw = cpu_to_le32(GPIO_GS_BIT);
+ payload.gpio_in_enabled =
+ cpu_to_le32(gpio_payload->input_enable);
+ payload.gpio_pinsetup1 = cpu_to_le32(gpio_payload->pinsetup1);
+ payload.gpio_pinsetup2 = cpu_to_le32(gpio_payload->pinsetup2);
+ break;
+ case GPIO_EVENTSETUP:
+ payload.eobid_ge_gs_gr_gw = cpu_to_le32(GPIO_GE_BIT);
+ payload.gpio_evt_change =
+ cpu_to_le32(gpio_payload->event_level);
+ payload.gpio_evt_rise =
+ cpu_to_le32(gpio_payload->event_rising_edge);
+ payload.gpio_evt_fall =
+ cpu_to_le32(gpio_payload->event_falling_edge);
+ break;
+ }
+ ret = pm8001_mpi_build_cmd(pm8001_ha, circularQ, opc, &payload,
+ sizeof(payload), 0);
+ if (ret != 0)
+ pm8001_tag_free(pm8001_ha, tag);
+ return ret;
+}
+
const struct pm8001_dispatch pm8001_80xx_dispatch = {
.name = "pmc80xx",
.chip_init = pm80xx_chip_init,
@@ -5064,5 +5169,6 @@ const struct pm8001_dispatch pm8001_80xx_dispatch = {
.set_nvmd_req = pm8001_chip_set_nvmd_req,
.fw_flash_update_req = pm8001_chip_fw_flash_update_req,
.set_dev_state_req = pm8001_chip_set_dev_state_req,
+ .gpio_req = pm80xx_chip_gpio_req,
.get_phy_profile_req = pm8001_chip_get_phy_profile,
};
diff --git a/drivers/scsi/pm8001/pm80xx_hwi.h b/drivers/scsi/pm8001/pm80xx_hwi.h
index b5119c5479da..fed193df93d6 100644
--- a/drivers/scsi/pm8001/pm80xx_hwi.h
+++ b/drivers/scsi/pm8001/pm80xx_hwi.h
@@ -529,6 +529,55 @@ struct hw_event_ack_req {
u32 reserved1[27];
} __attribute__((packed, aligned(4)));

+/*
+ * brief the data structure of GPIO Commannd
+ * use to control MPI GPIOs (64 bytes)
+ */
+struct gpio_req {
+ __le32 tag;
+ __le32 eobid_ge_gs_gr_gw;
+ __le32 gpio_wr_msk;
+ __le32 gpio_wr_val;
+ __le32 gpio_in_enabled;
+ __le32 gpio_pinsetup1;
+ __le32 gpio_pinsetup2;
+ __le32 gpio_evt_change;
+ __le32 gpio_evt_rise;
+ __le32 gpio_evt_fall;
+ u32 reserved[5];
+} __packed __aligned(4);
+
+#define GPIO_GW_BIT 0x1
+#define GPIO_GR_BIT 0x2
+#define GPIO_GS_BIT 0x4
+#define GPIO_GE_BIT 0x8
+
+/*
+ * brief the data structure of GPIO Response
+ * indicates the completion of GPIO command (64 bytes)
+ */
+struct gpio_resp {
+ __le32 tag;
+ u32 reserved[2];
+ __le32 gpio_rd_val;
+ __le32 gpio_in_enabled;
+ __le32 gpio_pinsetup1;
+ __le32 gpio_pinsetup2;
+ __le32 gpio_evt_change;
+ __le32 gpio_evt_rise;
+ __le32 gpio_evt_fall;
+ u32 reserved1[5];
+} __packed __aligned(4);
+
+/*
+ * brief the data structure of GPIO Event
+ * indicates the generation of GPIO event (64 bytes)
+ */
+struct gpio_event {
+ __le32 gpio_event;
+ u32 reserved[14];
+} __packed __aligned(4);
+
/*
* brief the data structure of PHY_START Response Command
* indicates the completion of PHY_START command (64 bytes)
--
2.16.3