Commit bfdc56da authored by Federico Vaga's avatar Federico Vaga

drv: gpiochip implementation

Implement the GPIO chip interface and use it for selecting the
FPGA programming mode
Signed-off-by: Federico Vaga's avatarFederico Vaga <federico.vaga@cern.ch>
parent 0d2e6849
......@@ -35,3 +35,5 @@ spec-objs += spec-irq.o
spec-objs += spec-dbg.o
spec-objs += spec-fmc.o
spec-objs += spec-compat.o
spec-objs += gn412x-gpio.o
spec-objs += spec-gpio.o
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/module.h>
#include <linux/gpio/driver.h>
#include "spec.h"
enum gn412x_gpio_versions {
GN412X_VER = 0,
};
enum htvic_mem_resources {
GN412X_MEM_BASE = 0,
};
static int gn412x_gpio_reg_read(struct gpio_chip *chip,
int reg, unsigned offset)
{
struct gn412x_dev *gn412x = to_gn412x_dev_gpio(chip);
return readl(gn412x->mem + reg) & BIT(offset);
}
static void gn412x_gpio_reg_write(struct gpio_chip *chip,
int reg, unsigned offset, int value)
{
struct gn412x_dev *gn412x = to_gn412x_dev_gpio(chip);
uint32_t regval;
regval = readl(gn412x->mem + reg);
if (value)
regval |= BIT(offset);
else
regval &= ~BIT(offset);
writel(regval, gn412x->mem + reg);
}
static int gn412x_gpio_get_direction(struct gpio_chip *chip,
unsigned offset)
{
return !gn412x_gpio_reg_read(chip, GNGPIO_OUTPUT_ENABLE, offset);
}
static int gn412x_gpio_direction_input(struct gpio_chip *chip,
unsigned offset)
{
gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_ENABLE, offset, 0);
return 0;
}
static int gn412x_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_ENABLE, offset, 1);
gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_VALUE, offset, value);
return 0;
}
static int gn412x_gpio_get(struct gpio_chip *chip,
unsigned offset)
{
return gn412x_gpio_reg_read(chip, GNGPIO_INPUT_VALUE, offset);
}
static void gn412x_gpio_set(struct gpio_chip *chip,
unsigned offset, int value)
{
gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_VALUE, offset, value);
}
int gn412x_gpio_init(struct gn412x_dev *gn412x)
{
int err;
memset(&gn412x->gpiochip, 0, sizeof(gn412x->gpiochip));
gn412x->gpiochip.label = "gn412x-gpio";
gn412x->gpiochip.owner = THIS_MODULE;
gn412x->gpiochip.get_direction = gn412x_gpio_get_direction,
gn412x->gpiochip.direction_input = gn412x_gpio_direction_input,
gn412x->gpiochip.direction_output = gn412x_gpio_direction_output,
gn412x->gpiochip.get = gn412x_gpio_get,
gn412x->gpiochip.set = gn412x_gpio_set,
gn412x->gpiochip.base = -1,
gn412x->gpiochip.ngpio = GN4124_GPIO_MAX,
err = gpiochip_add(&gn412x->gpiochip);
if (err)
return err;
return 0;
}
void gn412x_gpio_exit(struct gn412x_dev *gn412x)
{
gpiochip_remove(&gn412x->gpiochip);
}
......@@ -189,3 +189,31 @@ int compat_spec_fw_load(struct spec_dev *spec, const char *name)
return err;
}
int compat_gpiod_add_lookup_table(struct gpiod_lookup_table *table)
{
void (*gpiod_add_lookup_table_p)(struct gpiod_lookup_table *table);
gpiod_add_lookup_table_p = (void *) kallsyms_lookup_name("gpiod_add_lookup_table");
if (gpiod_add_lookup_table_p)
gpiod_add_lookup_table_p(table);
else
return WARN(1, "Cannot find 'gpiod_add_lookup_table'");
return 0;
}
#if KERNEL_VERSION(4, 3, 0) > LINUX_VERSION_CODE
void gpiod_remove_lookup_table(struct gpiod_lookup_table *table)
{
struct mutex *gpio_lookup_lock_p = (void *) kallsyms_lookup_name("gpio_lookup_lock");
mutex_lock(gpio_lookup_lock_p);
list_del(&table->list);
mutex_unlock(gpio_lookup_lock_p);
}
#endif
......@@ -71,3 +71,14 @@ int compat_spec_fw_load(struct spec_dev *spec, const char *name);
#define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
#endif
#if KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE
#define GPIO_PERSISTENT (0 << 3)
#endif
extern int compat_gpiod_add_lookup_table(struct gpiod_lookup_table *table);
#if KERNEL_VERSION(4, 3, 0) > LINUX_VERSION_CODE
extern void gpiod_remove_lookup_table(struct gpiod_lookup_table *table);
#endif
......@@ -144,6 +144,15 @@ static int spec_probe(struct pci_dev *pdev,
/* This virtual device is assciated with this driver */
spec->dev.driver = pdev->dev.driver;
spec->gn412x.mem = spec->remap[2];
err = gn412x_gpio_init(&spec->gn412x);
if (err)
goto err_ggpio;
err = spec_gpio_init(spec);
if (err)
goto err_sgpio;
err = spec_fpga_init(spec);
if (err)
goto err_fpga;
......@@ -173,6 +182,10 @@ err_fw:
err_irq:
spec_fpga_exit(spec);
err_fpga:
spec_gpio_exit(spec);
err_sgpio:
gn412x_gpio_exit(&spec->gn412x);
err_ggpio:
device_unregister(&spec->dev);
err_dev:
err_name:
......@@ -197,6 +210,8 @@ static void spec_remove(struct pci_dev *pdev)
spec_fmc_exit(spec);
spec_irq_exit(spec);
spec_fpga_exit(spec);
spec_gpio_exit(spec);
gn412x_gpio_exit(&spec->gn412x);
for (i = 0; i < 3; i++)
if (spec->remap[i])
......
......@@ -5,6 +5,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <linux/fpga/fpga-mgr.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include "spec.h"
......@@ -37,22 +38,6 @@ static uint32_t unaligned_bitswap_le32(const uint32_t *ptr32)
}
static inline void gpio_out(struct spec_dev *spec, const uint32_t addr,
const int bit, const int value)
{
uint32_t reg;
reg = gennum_readl(spec, addr);
if(value)
reg |= (1<<bit);
else
reg &= ~(1<<bit);
gennum_writel(spec, reg, addr);
}
/**
* it resets the FPGA
*/
......@@ -77,12 +62,7 @@ static void gn4124_fpga_reset(struct spec_dev *spec)
*/
static void gn4124_fpga_gpio_config(struct spec_dev *spec)
{
gpio_out(spec, GNGPIO_DIRECTION_MODE, GPIO_BOOTSEL0, 0);
gpio_out(spec, GNGPIO_DIRECTION_MODE, GPIO_BOOTSEL1, 0);
gpio_out(spec, GNGPIO_OUTPUT_ENABLE, GPIO_BOOTSEL0, 1);
gpio_out(spec, GNGPIO_OUTPUT_ENABLE, GPIO_BOOTSEL1, 1);
gpio_out(spec, GNGPIO_OUTPUT_VALUE, GPIO_BOOTSEL0, 1);
gpio_out(spec, GNGPIO_OUTPUT_VALUE, GPIO_BOOTSEL1, 0);
spec_gpio_fpga_select(spec, SPEC_FPGA_SELECT_GN4124);
}
......@@ -92,10 +72,7 @@ static void gn4124_fpga_gpio_config(struct spec_dev *spec)
*/
static void gn4124_fpga_gpio_restore(struct spec_dev *spec)
{
gpio_out(spec, GNGPIO_OUTPUT_ENABLE, GPIO_BOOTSEL0, 1);
gpio_out(spec, GNGPIO_OUTPUT_ENABLE, GPIO_BOOTSEL1, 1);
gpio_out(spec, GNGPIO_OUTPUT_VALUE, GPIO_BOOTSEL0, 0);
gpio_out(spec, GNGPIO_OUTPUT_VALUE, GPIO_BOOTSEL1, 0);
spec_gpio_fpga_select(spec, SPEC_FPGA_SELECT_FLASH);
}
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2019 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include "spec.h"
#include "spec-compat.h"
void spec_gpio_fpga_select(struct spec_dev *spec, enum spec_fpga_select sel)
{
switch (sel) {
case SPEC_FPGA_SELECT_FLASH:
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL0], 1);
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL1], 1);
break;
case SPEC_FPGA_SELECT_GN4124:
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL0], 1);
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL1], 0);
break;
case SPEC_FPGA_SELECT_SPI:
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL0], 0);
gpiod_set_value(spec->gpiod[GN4124_GPIO_BOOTSEL1], 0);
break;
default:
break;
}
}
static const struct gpiod_lookup_table spec_gpiod_table = {
.table = {
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_BOOTSEL0,
"bootsel", 0,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_BOOTSEL1,
"bootsel", 1,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_SPRI_DIN,
"spi", 0,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_SPRI_FLASH_CS,
"spi", 1,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_IRQ0,
"irq", 0,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_IRQ1,
"irq", 1,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_SCL,
"i2c", 0,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
GPIO_LOOKUP_IDX("gn412x-gpio", GN4124_GPIO_SDA,
"i2c", 1,
GPIO_ACTIVE_HIGH | GPIO_PERSISTENT),
{},
}
};
static inline size_t spec_gpiod_table_size(void)
{
return sizeof(struct gpiod_lookup_table) + (sizeof(struct gpiod_lookup) * 9);
}
int spec_gpio_init(struct spec_dev *spec)
{
struct gpiod_lookup_table *lookup;
int err;
lookup = devm_kzalloc(&spec->dev,
spec_gpiod_table_size(),
GFP_KERNEL);
if (!lookup)
return -ENOMEM;
memcpy(lookup, &spec_gpiod_table, spec_gpiod_table_size());
lookup->dev_id = kstrdup(dev_name(&spec->dev), GFP_KERNEL);
if (!lookup->dev_id)
goto err_dup;
spec->gpiod_table = lookup;
err = compat_gpiod_add_lookup_table(spec->gpiod_table);
if (err)
goto err_lookup;
spec->gpiod[GN4124_GPIO_BOOTSEL0] = gpiod_get_index(&spec->dev,
"bootsel", 0,
GPIOD_OUT_HIGH);
if (IS_ERR(spec->gpiod[GN4124_GPIO_BOOTSEL0]))
goto err_sel0;
spec->gpiod[GN4124_GPIO_BOOTSEL1] = gpiod_get_index(&spec->dev,
"bootsel", 1,
GPIOD_OUT_HIGH);
if (IS_ERR(spec->gpiod[GN4124_GPIO_BOOTSEL1]))
goto err_sel1;
/* Because of a BUG in RedHat kernel 3.10 we re-set direction */
err = gpiod_direction_output(spec->gpiod[GN4124_GPIO_BOOTSEL0], 1);
if (err)
goto err_out0;
err = gpiod_direction_output(spec->gpiod[GN4124_GPIO_BOOTSEL1], 1);
if (err)
goto err_out1;
return 0;
err_out1:
err_out0:
gpiod_put(spec->gpiod[GN4124_GPIO_BOOTSEL1]);
err_sel1:
gpiod_put(spec->gpiod[GN4124_GPIO_BOOTSEL0]);
err_sel0:
gpiod_remove_lookup_table(spec->gpiod_table);
err_lookup:
kfree(lookup->dev_id);
err_dup:
devm_kfree(&spec->dev, lookup);
spec->gpiod_table = NULL;
return -ENODEV;
}
void spec_gpio_exit(struct spec_dev *spec)
{
gpiod_put(spec->gpiod[GN4124_GPIO_BOOTSEL0]);
gpiod_put(spec->gpiod[GN4124_GPIO_BOOTSEL1]);
gpiod_remove_lookup_table(spec->gpiod_table);
kfree(spec->gpiod_table->dev_id);
}
......@@ -15,6 +15,7 @@
#include <linux/irqdomain.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/gpio/driver.h>
#include <linux/spinlock.h>
#include <linux/fmc.h>
......@@ -24,10 +25,6 @@
#define SPEC_I2C_MASTER_ADDR 0x0
#define SPEC_I2C_MASTER_SIZE 8
/* These must be set to choose the FPGA configuration mode */
#define GPIO_BOOTSEL0 15
#define GPIO_BOOTSEL1 14
#define PCI_VENDOR_ID_CERN (0x10DC)
#define PCI_DEVICE_ID_SPEC_45T (0x018D)
#define PCI_DEVICE_ID_SPEC_100T (0x01A2)
......@@ -39,6 +36,28 @@
#define SPEC_FLAG_BITS (8)
#define SPEC_FLAG_UNLOCK BIT(0)
#define GN4124_GPIO_MAX 16
#define GN4124_GPIO_BOOTSEL0 15
#define GN4124_GPIO_BOOTSEL1 14
#define GN4124_GPIO_SPRI_DIN 13
#define GN4124_GPIO_SPRI_FLASH_CS 12
#define GN4124_GPIO_IRQ0 9
#define GN4124_GPIO_IRQ1 8
#define GN4124_GPIO_SCL 5
#define GN4124_GPIO_SDA 4
/**
* @SPEC_FPGA_SELECT_FLASH: (default) the FPGA takes its configuration from
* flash
* @SPEC_FPGA_SELECT_GN4124: the FPGA takes its configuration from GN4124
* @SPEC_FPGA_SELECT_SPI: the SPI flash is accessible from GN4124
*/
enum spec_fpga_select {
SPEC_FPGA_SELECT_FLASH=0,
SPEC_FPGA_SELECT_GN4124,
SPEC_FPGA_SELECT_SPI,
};
#define GN4124_GPIO_IRQ_MAX 16
/* Registers for GN4124 access */
......@@ -89,7 +108,7 @@ enum {
FCL_TIMER_0 = FCL_BASE + 0x14,
FCL_TIMER_1 = FCL_BASE + 0x18,
FCL_CLK_DIV = FCL_BASE + 0x1C,
FCL_IRQ = FCL_BASE + 0x20,
FCL_IRQ = FCL_BASE + 0x20,
FCL_TIMER_CTRL = FCL_BASE + 0x24,
FCL_IM = FCL_BASE + 0x28,
FCL_TIMER2_0 = FCL_BASE + 0x2C,
......@@ -107,6 +126,17 @@ enum {
#define GNINT_STAT_SW1 BIT(3)
#define GNINT_STAT_SW_ALL (GNINT_STAT_SW0 | GNINT_STAT_SW1)
struct gn412x_dev {
void __iomem *mem;
struct gpio_chip gpiochip;
};
static inline struct gn412x_dev *to_gn412x_dev_gpio(struct gpio_chip *chip)
{
return container_of(chip, struct gn412x_dev, gpiochip);
}
/**
* struct spec_dev - SPEC instance
* It describes a SPEC device instance.
......@@ -140,6 +170,11 @@ struct spec_dev {
struct dentry *dbg_fw;
struct completion compl;
struct gn412x_dev gn412x;
struct gpiod_lookup_table *gpiod_table;
struct gpio_desc *gpiod[GN4124_GPIO_MAX];
};
......@@ -188,6 +223,8 @@ static inline void gennum_mask_val(struct spec_dev *spec,
gennum_writel(spec, v, reg);
}
extern int gn412x_gpio_init(struct gn412x_dev *spec);
extern void gn412x_gpio_exit(struct gn412x_dev *spec);
extern int spec_fpga_init(struct spec_dev *spec);
extern void spec_fpga_exit(struct spec_dev *spec);
......@@ -201,4 +238,9 @@ extern void spec_dbg_exit(struct spec_dev *spec);
extern int spec_fmc_init(struct spec_dev *spec);
extern void spec_fmc_exit(struct spec_dev *spec);
extern void spec_gpio_fpga_select(struct spec_dev *spec,
enum spec_fpga_select sel);
extern int spec_gpio_init(struct spec_dev *spec);
extern void spec_gpio_exit(struct spec_dev *spec);
#endif /* __SPEC_H__ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment