diff options
Diffstat (limited to 'drivers')
49 files changed, 4150 insertions, 284 deletions
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 10e1b9eee10e..a230ea797b92 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -128,4 +128,13 @@ config IMG_ASCII_LCD development boards such as the MIPS Boston, MIPS Malta & MIPS SEAD3 from Imagination Technologies. +config HT16K33 + tristate "Holtek Ht16K33 LED controller with keyscan" + depends on FB && OF && I2C && INPUT + select INPUT_MATRIXKMAP + select FB_BACKLIGHT + help + Say yes here to add support for Holtek HT16K33, RAM mapping 16*8 + LED controller driver with keyscan. + endif # AUXDISPLAY diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index 3127175c89df..cb3dd847713b 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_KS0108) += ks0108.o obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o +obj-$(CONFIG_HT16K33) += ht16k33.o diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c new file mode 100644 index 000000000000..eeb323f56c07 --- /dev/null +++ b/drivers/auxdisplay/ht16k33.c @@ -0,0 +1,563 @@ +/* + * HT16K33 driver + * + * Author: Robin van der Gracht <robin@protonic.nl> + * + * Copyright: (C) 2016 Protonic Holland. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/fb.h> +#include <linux/slab.h> +#include <linux/backlight.h> +#include <linux/input.h> +#include <linux/input/matrix_keypad.h> +#include <linux/workqueue.h> +#include <linux/mm.h> + +/* Registers */ +#define REG_SYSTEM_SETUP 0x20 +#define REG_SYSTEM_SETUP_OSC_ON BIT(0) + +#define REG_DISPLAY_SETUP 0x80 +#define REG_DISPLAY_SETUP_ON BIT(0) + +#define REG_ROWINT_SET 0xA0 +#define REG_ROWINT_SET_INT_EN BIT(0) +#define REG_ROWINT_SET_INT_ACT_HIGH BIT(1) + +#define REG_BRIGHTNESS 0xE0 + +/* Defines */ +#define DRIVER_NAME "ht16k33" + +#define MIN_BRIGHTNESS 0x1 +#define MAX_BRIGHTNESS 0x10 + +#define HT16K33_MATRIX_LED_MAX_COLS 8 +#define HT16K33_MATRIX_LED_MAX_ROWS 16 +#define HT16K33_MATRIX_KEYPAD_MAX_COLS 3 +#define HT16K33_MATRIX_KEYPAD_MAX_ROWS 12 + +#define BYTES_PER_ROW (HT16K33_MATRIX_LED_MAX_ROWS / 8) +#define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW) + +struct ht16k33_keypad { + struct input_dev *dev; + spinlock_t lock; + struct delayed_work work; + uint32_t cols; + uint32_t rows; + uint32_t row_shift; + uint32_t debounce_ms; + uint16_t last_key_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; +}; + +struct ht16k33_fbdev { + struct fb_info *info; + uint32_t refresh_rate; + uint8_t *buffer; + uint8_t *cache; + struct delayed_work work; +}; + +struct ht16k33_priv { + struct i2c_client *client; + struct ht16k33_keypad keypad; + struct ht16k33_fbdev fbdev; + struct workqueue_struct *workqueue; +}; + +static struct fb_fix_screeninfo ht16k33_fb_fix = { + .id = DRIVER_NAME, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO10, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = HT16K33_MATRIX_LED_MAX_ROWS, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo ht16k33_fb_var = { + .xres = HT16K33_MATRIX_LED_MAX_ROWS, + .yres = HT16K33_MATRIX_LED_MAX_COLS, + .xres_virtual = HT16K33_MATRIX_LED_MAX_ROWS, + .yres_virtual = HT16K33_MATRIX_LED_MAX_COLS, + .bits_per_pixel = 1, + .red = { 0, 1, 0 }, + .green = { 0, 1, 0 }, + .blue = { 0, 1, 0 }, + .left_margin = 0, + .right_margin = 0, + .upper_margin = 0, + .lower_margin = 0, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static int ht16k33_display_on(struct ht16k33_priv *priv) +{ + uint8_t data = REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON; + + return i2c_smbus_write_byte(priv->client, data); +} + +static int ht16k33_display_off(struct ht16k33_priv *priv) +{ + return i2c_smbus_write_byte(priv->client, REG_DISPLAY_SETUP); +} + +static void ht16k33_fb_queue(struct ht16k33_priv *priv) +{ + struct ht16k33_fbdev *fbdev = &priv->fbdev; + + queue_delayed_work(priv->workqueue, &fbdev->work, + msecs_to_jiffies(HZ / fbdev->refresh_rate)); +} + +static void ht16k33_keypad_queue(struct ht16k33_priv *priv) +{ + struct ht16k33_keypad *keypad = &priv->keypad; + + queue_delayed_work(priv->workqueue, &keypad->work, + msecs_to_jiffies(keypad->debounce_ms)); +} + +/* + * This gets the fb data from cache and copies it to ht16k33 display RAM + */ +static void ht16k33_fb_update(struct work_struct *work) +{ + struct ht16k33_fbdev *fbdev = + container_of(work, struct ht16k33_fbdev, work.work); + struct ht16k33_priv *priv = + container_of(fbdev, struct ht16k33_priv, fbdev); + + uint8_t *p1, *p2; + int len, pos = 0, first = -1; + + p1 = fbdev->cache; + p2 = fbdev->buffer; + + /* Search for the first byte with changes */ + while (pos < HT16K33_FB_SIZE && first < 0) { + if (*(p1++) - *(p2++)) + first = pos; + pos++; + } + + /* No changes found */ + if (first < 0) + goto requeue; + + len = HT16K33_FB_SIZE - first; + p1 = fbdev->cache + HT16K33_FB_SIZE - 1; + p2 = fbdev->buffer + HT16K33_FB_SIZE - 1; + + /* Determine i2c transfer length */ + while (len > 1) { + if (*(p1--) - *(p2--)) + break; + len--; + } + + p1 = fbdev->cache + first; + p2 = fbdev->buffer + first; + if (!i2c_smbus_write_i2c_block_data(priv->client, first, len, p2)) + memcpy(p1, p2, len); +requeue: + ht16k33_fb_queue(priv); +} + +static int ht16k33_keypad_start(struct input_dev *dev) +{ + struct ht16k33_priv *priv = input_get_drvdata(dev); + struct ht16k33_keypad *keypad = &priv->keypad; + + /* + * Schedule an immediate key scan to capture current key state; + * columns will be activated and IRQs be enabled after the scan. + */ + queue_delayed_work(priv->workqueue, &keypad->work, 0); + return 0; +} + +static void ht16k33_keypad_stop(struct input_dev *dev) +{ + struct ht16k33_priv *priv = input_get_drvdata(dev); + struct ht16k33_keypad *keypad = &priv->keypad; + + cancel_delayed_work(&keypad->work); + /* + * ht16k33_keypad_scan() will leave IRQs enabled; + * we should disable them now. + */ + disable_irq_nosync(priv->client->irq); +} + +static int ht16k33_initialize(struct ht16k33_priv *priv) +{ + uint8_t byte; + int err; + uint8_t data[HT16K33_MATRIX_LED_MAX_COLS * 2]; + + /* Clear RAM (8 * 16 bits) */ + memset(data, 0, sizeof(data)); + err = i2c_smbus_write_block_data(priv->client, 0, sizeof(data), data); + if (err) + return err; + + /* Turn on internal oscillator */ + byte = REG_SYSTEM_SETUP_OSC_ON | REG_SYSTEM_SETUP; + err = i2c_smbus_write_byte(priv->client, byte); + if (err) + return err; + + /* Configure INT pin */ + byte = REG_ROWINT_SET | REG_ROWINT_SET_INT_ACT_HIGH; + if (priv->client->irq > 0) + byte |= REG_ROWINT_SET_INT_EN; + return i2c_smbus_write_byte(priv->client, byte); +} + +/* + * This gets the keys from keypad and reports it to input subsystem + */ +static void ht16k33_keypad_scan(struct work_struct *work) +{ + struct ht16k33_keypad *keypad = + container_of(work, struct ht16k33_keypad, work.work); + struct ht16k33_priv *priv = + container_of(keypad, struct ht16k33_priv, keypad); + const unsigned short *keycodes = keypad->dev->keycode; + uint16_t bits_changed, new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; + uint8_t data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2]; + int row, col, code; + bool reschedule = false; + + if (i2c_smbus_read_i2c_block_data(priv->client, 0x40, 6, data) != 6) { + dev_err(&priv->client->dev, "Failed to read key data\n"); + goto end; + } + + for (col = 0; col < keypad->cols; col++) { + new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2]; + if (new_state[col]) + reschedule = true; + bits_changed = keypad->last_key_state[col] ^ new_state[col]; + + while (bits_changed) { + row = ffs(bits_changed) - 1; + code = MATRIX_SCAN_CODE(row, col, keypad->row_shift); + input_event(keypad->dev, EV_MSC, MSC_SCAN, code); + input_report_key(keypad->dev, keycodes[code], + new_state[col] & BIT(row)); + bits_changed &= ~BIT(row); + } + } + input_sync(keypad->dev); + memcpy(keypad->last_key_state, new_state, sizeof(new_state)); + +end: + if (reschedule) + ht16k33_keypad_queue(priv); + else + enable_irq(priv->client->irq); +} + +static irqreturn_t ht16k33_irq_thread(int irq, void *dev) +{ + struct ht16k33_priv *priv = dev; + + disable_irq_nosync(priv->client->irq); + ht16k33_keypad_queue(priv); + + return IRQ_HANDLED; +} + +static int ht16k33_bl_update_status(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + struct ht16k33_priv *priv = bl_get_data(bl); + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.fb_blank != FB_BLANK_UNBLANK || + bl->props.state & BL_CORE_FBBLANK || brightness == 0) { + return ht16k33_display_off(priv); + } + + ht16k33_display_on(priv); + return i2c_smbus_write_byte(priv->client, + REG_BRIGHTNESS | (brightness - 1)); +} + +static int ht16k33_bl_check_fb(struct backlight_device *bl, struct fb_info *fi) +{ + struct ht16k33_priv *priv = bl_get_data(bl); + + return (fi == NULL) || (fi->par == priv); +} + +static const struct backlight_ops ht16k33_bl_ops = { + .update_status = ht16k33_bl_update_status, + .check_fb = ht16k33_bl_check_fb, +}; + +static int ht16k33_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct ht16k33_priv *priv = info->par; + + return vm_insert_page(vma, vma->vm_start, + virt_to_page(priv->fbdev.buffer)); +} + +static struct fb_ops ht16k33_fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = ht16k33_mmap, +}; + +static int ht16k33_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + uint32_t rows, cols, dft_brightness; + struct backlight_device *bl; + struct backlight_properties bl_props; + struct ht16k33_priv *priv; + struct ht16k33_keypad *keypad; + struct ht16k33_fbdev *fbdev; + struct device_node *node = client->dev.of_node; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c_check_functionality error\n"); + return -EIO; + } + + if (client->irq <= 0) { + dev_err(&client->dev, "No IRQ specified\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + fbdev = &priv->fbdev; + keypad = &priv->keypad; + + priv->workqueue = create_singlethread_workqueue(DRIVER_NAME "-wq"); + if (priv->workqueue == NULL) + return -ENOMEM; + + err = ht16k33_initialize(priv); + if (err) + goto err_destroy_wq; + + /* Framebuffer (2 bytes per column) */ + BUILD_BUG_ON(PAGE_SIZE < HT16K33_FB_SIZE); + fbdev->buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL); + if (!fbdev->buffer) { + err = -ENOMEM; + goto err_free_fbdev; + } + + fbdev->cache = devm_kmalloc(&client->dev, HT16K33_FB_SIZE, GFP_KERNEL); + if (!fbdev->cache) { + err = -ENOMEM; + goto err_fbdev_buffer; + } + + fbdev->info = framebuffer_alloc(0, &client->dev); + if (!fbdev->info) { + err = -ENOMEM; + goto err_fbdev_buffer; + } + + err = of_property_read_u32(node, "refresh-rate-hz", + &fbdev->refresh_rate); + if (err) { + dev_err(&client->dev, "refresh rate not specified\n"); + goto err_fbdev_info; + } + fb_bl_default_curve(fbdev->info, 0, MIN_BRIGHTNESS, MAX_BRIGHTNESS); + + INIT_DELAYED_WORK(&fbdev->work, ht16k33_fb_update); + fbdev->info->fbops = &ht16k33_fb_ops; + fbdev->info->screen_base = (char __iomem *) fbdev->buffer; + fbdev->info->screen_size = HT16K33_FB_SIZE; + fbdev->info->fix = ht16k33_fb_fix; + fbdev->info->var = ht16k33_fb_var; + fbdev->info->pseudo_palette = NULL; + fbdev->info->flags = FBINFO_FLAG_DEFAULT; + fbdev->info->par = priv; + + err = register_framebuffer(fbdev->info); + if (err) + goto err_fbdev_info; + + /* Keypad */ + keypad->dev = devm_input_allocate_device(&client->dev); + if (!keypad->dev) { + err = -ENOMEM; + goto err_fbdev_unregister; + } + + keypad->dev->name = DRIVER_NAME"-keypad"; + keypad->dev->id.bustype = BUS_I2C; + keypad->dev->open = ht16k33_keypad_start; + keypad->dev->close = ht16k33_keypad_stop; + + if (!of_get_property(node, "linux,no-autorepeat", NULL)) + __set_bit(EV_REP, keypad->dev->evbit); + + err = of_property_read_u32(node, "debounce-delay-ms", + &keypad->debounce_ms); + if (err) { + dev_err(&client->dev, "key debounce delay not specified\n"); + goto err_fbdev_unregister; + } + + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + ht16k33_irq_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + DRIVER_NAME, priv); + if (err) { + dev_err(&client->dev, "irq request failed %d, error %d\n", + client->irq, err); + goto err_fbdev_unregister; + } + + disable_irq_nosync(client->irq); + rows = HT16K33_MATRIX_KEYPAD_MAX_ROWS; + cols = HT16K33_MATRIX_KEYPAD_MAX_COLS; + err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols); + if (err) + goto err_fbdev_unregister; + + err = matrix_keypad_build_keymap(NULL, NULL, rows, cols, NULL, + keypad->dev); + if (err) { + dev_err(&client->dev, "failed to build keymap\n"); + goto err_fbdev_unregister; + } + + input_set_drvdata(keypad->dev, priv); + keypad->rows = rows; + keypad->cols = cols; + keypad->row_shift = get_count_order(cols); + INIT_DELAYED_WORK(&keypad->work, ht16k33_keypad_scan); + + err = input_register_device(keypad->dev); + if (err) + goto err_fbdev_unregister; + + /* Backlight */ + memset(&bl_props, 0, sizeof(struct backlight_properties)); + bl_props.type = BACKLIGHT_RAW; + bl_props.max_brightness = MAX_BRIGHTNESS; + + bl = devm_backlight_device_register(&client->dev, DRIVER_NAME"-bl", + &client->dev, priv, + &ht16k33_bl_ops, &bl_props); + if (IS_ERR(bl)) { + dev_err(&client->dev, "failed to register backlight\n"); + err = PTR_ERR(bl); + goto err_keypad_unregister; + } + + err = of_property_read_u32(node, "default-brightness-level", + &dft_brightness); + if (err) { + dft_brightness = MAX_BRIGHTNESS; + } else if (dft_brightness > MAX_BRIGHTNESS) { + dev_warn(&client->dev, + "invalid default brightness level: %u, using %u\n", + dft_brightness, MAX_BRIGHTNESS); + dft_brightness = MAX_BRIGHTNESS; + } + + bl->props.brightness = dft_brightness; + ht16k33_bl_update_status(bl); + + ht16k33_fb_queue(priv); + return 0; + +err_keypad_unregister: + input_unregister_device(keypad->dev); +err_fbdev_unregister: + unregister_framebuffer(fbdev->info); +err_fbdev_info: + framebuffer_release(fbdev->info); +err_fbdev_buffer: + free_page((unsigned long) fbdev->buffer); +err_free_fbdev: + kfree(fbdev); +err_destroy_wq: + destroy_workqueue(priv->workqueue); + + return err; +} + +static int ht16k33_remove(struct i2c_client *client) +{ + struct ht16k33_priv *priv = i2c_get_clientdata(client); + struct ht16k33_keypad *keypad = &priv->keypad; + struct ht16k33_fbdev *fbdev = &priv->fbdev; + + ht16k33_keypad_stop(keypad->dev); + + cancel_delayed_work(&fbdev->work); + unregister_framebuffer(fbdev->info); + framebuffer_release(fbdev->info); + free_page((unsigned long) fbdev->buffer); + + destroy_workqueue(priv->workqueue); + return 0; +} + +static const struct i2c_device_id ht16k33_i2c_match[] = { + { "ht16k33", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ht16k33_i2c_match); + +static const struct of_device_id ht16k33_of_match[] = { + { .compatible = "holtek,ht16k33", }, + { } +}; +MODULE_DEVICE_TABLE(of, ht16k33_of_match); + +static struct i2c_driver ht16k33_driver = { + .probe = ht16k33_probe, + .remove = ht16k33_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(ht16k33_of_match), + }, + .id_table = ht16k33_i2c_match, +}; +module_i2c_driver(ht16k33_driver); + +MODULE_DESCRIPTION("Holtek HT16K33 driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robin van der Gracht <robin@protonic.nl>"); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index dcc09739a54e..c17604f8c7c1 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -17,7 +17,6 @@ config DEVMEM config DEVKMEM bool "/dev/kmem virtual device support" - default y help Say Y here if you want to support the /dev/kmem device. The /dev/kmem device is rarely used, but can be used for certain @@ -578,7 +577,7 @@ config DEVPORT source "drivers/s390/char/Kconfig" config TILE_SROM - bool "Character-device access via hypervisor to the Tilera SPI ROM" + tristate "Character-device access via hypervisor to the Tilera SPI ROM" depends on TILE default y ---help--- diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig index 8d3dfb0c8a26..1d1e7da8ad27 100644 --- a/drivers/char/pcmcia/Kconfig +++ b/drivers/char/pcmcia/Kconfig @@ -43,6 +43,17 @@ config CARDMAN_4040 (http://www.omnikey.com/), or a current development version of OpenCT (http://www.opensc-project.org/opensc). +config SCR24X + tristate "SCR24x Chip Card Interface support" + depends on PCMCIA + help + Enable support for the SCR24x PCMCIA Chip Card Interface. + + To compile this driver as a module, choose M here. + The module will be called scr24x_cs.. + + If unsure say N. + config IPWIRELESS tristate "IPWireless 3G UMTS PCMCIA card support" depends on PCMCIA && NETDEVICES && TTY diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile index 0aae20985d57..5b836bc21406 100644 --- a/drivers/char/pcmcia/Makefile +++ b/drivers/char/pcmcia/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o obj-$(CONFIG_CARDMAN_4000) += cm4000_cs.o obj-$(CONFIG_CARDMAN_4040) += cm4040_cs.o +obj-$(CONFIG_SCR24X) += scr24x_cs.o diff --git a/drivers/char/pcmcia/scr24x_cs.c b/drivers/char/pcmcia/scr24x_cs.c new file mode 100644 index 000000000000..4f215ce1a3be --- /dev/null +++ b/drivers/char/pcmcia/scr24x_cs.c @@ -0,0 +1,372 @@ +/* + * SCR24x PCMCIA Smart Card Reader Driver + * + * Copyright (C) 2005-2006 TL Sudheendran + * Copyright (C) 2016 Lubomir Rintel + * + * Derived from "scr24x_v4.2.6_Release.tar.gz" driver by TL Sudheendran. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/cdev.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#define CCID_HEADER_SIZE 10 +#define CCID_LENGTH_OFFSET 1 +#define CCID_MAX_LEN 271 + +#define SCR24X_DATA(n) (1 + n) +#define SCR24X_CMD_STATUS 7 +#define CMD_START 0x40 +#define CMD_WRITE_BYTE 0x41 +#define CMD_READ_BYTE 0x42 +#define STATUS_BUSY 0x80 + +struct scr24x_dev { + struct device *dev; + struct cdev c_dev; + unsigned char buf[CCID_MAX_LEN]; + int devno; + struct mutex lock; + struct kref refcnt; + u8 __iomem *regs; +}; + +#define SCR24X_DEVS 8 +static DECLARE_BITMAP(scr24x_minors, SCR24X_DEVS); + +static struct class *scr24x_class; +static dev_t scr24x_devt; + +static void scr24x_delete(struct kref *kref) +{ + struct scr24x_dev *dev = container_of(kref, struct scr24x_dev, + refcnt); + + kfree(dev); +} + +static int scr24x_wait_ready(struct scr24x_dev *dev) +{ + u_char status; + int timeout = 100; + + do { + status = ioread8(dev->regs + SCR24X_CMD_STATUS); + if (!(status & STATUS_BUSY)) + return 0; + + msleep(20); + } while (--timeout); + + return -EIO; +} + +static int scr24x_open(struct inode *inode, struct file *filp) +{ + struct scr24x_dev *dev = container_of(inode->i_cdev, + struct scr24x_dev, c_dev); + + kref_get(&dev->refcnt); + filp->private_data = dev; + + return nonseekable_open(inode, filp); +} + +static int scr24x_release(struct inode *inode, struct file *filp) +{ + struct scr24x_dev *dev = filp->private_data; + + /* We must not take the dev->lock here as scr24x_delete() + * might be called to remove the dev structure altogether. + * We don't need the lock anyway, since after the reference + * acquired in probe() is released in remove() the chrdev + * is already unregistered and noone can possibly acquire + * a reference via open() anymore. */ + kref_put(&dev->refcnt, scr24x_delete); + return 0; +} + +static int read_chunk(struct scr24x_dev *dev, size_t offset, size_t limit) +{ + size_t i, y; + int ret; + + for (i = offset; i < limit; i += 5) { + iowrite8(CMD_READ_BYTE, dev->regs + SCR24X_CMD_STATUS); + ret = scr24x_wait_ready(dev); + if (ret < 0) + return ret; + + for (y = 0; y < 5 && i + y < limit; y++) + dev->buf[i + y] = ioread8(dev->regs + SCR24X_DATA(y)); + } + + return 0; +} + +static ssize_t scr24x_read(struct file *filp, char __user *buf, size_t count, + loff_t *ppos) +{ + struct scr24x_dev *dev = filp->private_data; + int ret; + int len; + + if (count < CCID_HEADER_SIZE) + return -EINVAL; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + if (!dev->dev) { + ret = -ENODEV; + goto out; + } + + ret = scr24x_wait_ready(dev); + if (ret < 0) + goto out; + len = CCID_HEADER_SIZE; + ret = read_chunk(dev, 0, len); + if (ret < 0) + goto out; + + len += le32_to_cpu(*(__le32 *)(&dev->buf[CCID_LENGTH_OFFSET])); + if (len > sizeof(dev->buf)) { + ret = -EIO; + goto out; + } + read_chunk(dev, CCID_HEADER_SIZE, len); + if (ret < 0) + goto out; + + if (len < count) + count = len; + + if (copy_to_user(buf, dev->buf, count)) { + ret = -EFAULT; + goto out; + } + + ret = count; +out: + mutex_unlock(&dev->lock); + return ret; +} + +static ssize_t scr24x_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scr24x_dev *dev = filp->private_data; + size_t i, y; + int ret; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + if (!dev->dev) { + ret = -ENODEV; + goto out; + } + + if (count > sizeof(dev->buf)) { + ret = -EINVAL; + goto out; + } + + if (copy_from_user(dev->buf, buf, count)) { + ret = -EFAULT; + goto out; + } + + ret = scr24x_wait_ready(dev); + if (ret < 0) + goto out; + + iowrite8(CMD_START, dev->regs + SCR24X_CMD_STATUS); + ret = scr24x_wait_ready(dev); + if (ret < 0) + goto out; + + for (i = 0; i < count; i += 5) { + for (y = 0; y < 5 && i + y < count; y++) + iowrite8(dev->buf[i + y], dev->regs + SCR24X_DATA(y)); + + iowrite8(CMD_WRITE_BYTE, dev->regs + SCR24X_CMD_STATUS); + ret = scr24x_wait_ready(dev); + if (ret < 0) + goto out; + } + + ret = count; +out: + mutex_unlock(&dev->lock); + return ret; +} + +static const struct file_operations scr24x_fops = { + .owner = THIS_MODULE, + .read = scr24x_read, + .write = scr24x_write, + .open = scr24x_open, + .release = scr24x_release, + .llseek = no_llseek, +}; + +static int scr24x_config_check(struct pcmcia_device *link, void *priv_data) +{ + if (resource_size(link->resource[PCMCIA_IOPORT_0]) != 0x11) + return -ENODEV; + return pcmcia_request_io(link); +} + +static int scr24x_probe(struct pcmcia_device *link) +{ + struct scr24x_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->devno = find_first_zero_bit(scr24x_minors, SCR24X_DEVS); + if (dev->devno >= SCR24X_DEVS) { + ret = -EBUSY; + goto err; + } + + mutex_init(&dev->lock); + kref_init(&dev->refcnt); + + link->priv = dev; + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, scr24x_config_check, NULL); + if (ret < 0) + goto err; + + dev->dev = &link->dev; + dev->regs = devm_ioport_map(&link->dev, + link->resource[PCMCIA_IOPORT_0]->start, + resource_size(link->resource[PCMCIA_IOPORT_0])); + if (!dev->regs) { + ret = -EIO; + goto err; + } + + cdev_init(&dev->c_dev, &scr24x_fops); + dev->c_dev.owner = THIS_MODULE; + dev->c_dev.ops = &scr24x_fops; + ret = cdev_add(&dev->c_dev, MKDEV(MAJOR(scr24x_devt), dev->devno), 1); + if (ret < 0) + goto err; + + ret = pcmcia_enable_device(link); + if (ret < 0) { + pcmcia_disable_device(link); + goto err; + } + + device_create(scr24x_class, NULL, MKDEV(MAJOR(scr24x_devt), dev->devno), + NULL, "scr24x%d", dev->devno); + + dev_info(&link->dev, "SCR24x Chip Card Interface\n"); + return 0; + +err: + if (dev->devno < SCR24X_DEVS) + clear_bit(dev->devno, scr24x_minors); + kfree (dev); + return ret; +} + +static void scr24x_remove(struct pcmcia_device *link) +{ + struct scr24x_dev *dev = (struct scr24x_dev *)link->priv; + + device_destroy(scr24x_class, MKDEV(MAJOR(scr24x_devt), dev->devno)); + mutex_lock(&dev->lock); + pcmcia_disable_device(link); + cdev_del(&dev->c_dev); + clear_bit(dev->devno, scr24x_minors); + dev->dev = NULL; + mutex_unlock(&dev->lock); + + kref_put(&dev->refcnt, scr24x_delete); +} + +static const struct pcmcia_device_id scr24x_ids[] = { + PCMCIA_DEVICE_PROD_ID12("HP", "PC Card Smart Card Reader", + 0x53cb94f9, 0xbfdf89a5), + PCMCIA_DEVICE_PROD_ID1("SCR241 PCMCIA", 0x6271efa3), + PCMCIA_DEVICE_PROD_ID1("SCR243 PCMCIA", 0x2054e8de), + PCMCIA_DEVICE_PROD_ID1("SCR24x PCMCIA", 0x54a33665), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, scr24x_ids); + +static struct pcmcia_driver scr24x_driver = { + .owner = THIS_MODULE, + .name = "scr24x_cs", + .probe = scr24x_probe, + .remove = scr24x_remove, + .id_table = scr24x_ids, +}; + +static int __init scr24x_init(void) +{ + int ret; + + scr24x_class = class_create(THIS_MODULE, "scr24x"); + if (IS_ERR(scr24x_class)) + return PTR_ERR(scr24x_class); + + ret = alloc_chrdev_region(&scr24x_devt, 0, SCR24X_DEVS, "scr24x"); + if (ret < 0) { + class_destroy(scr24x_class); + return ret; + } + + ret = pcmcia_register_driver(&scr24x_driver); + if (ret < 0) { + unregister_chrdev_region(scr24x_devt, SCR24X_DEVS); + class_destroy(scr24x_class); + } + + return ret; +} + +static void __exit scr24x_exit(void) +{ + pcmcia_unregister_driver(&scr24x_driver); + unregister_chrdev_region(scr24x_devt, SCR24X_DEVS); + class_destroy(scr24x_class); +} + +module_init(scr24x_init); +module_exit(scr24x_exit); + +MODULE_AUTHOR("Lubomir Rintel"); +MODULE_DESCRIPTION("SCR24x PCMCIA Smart Card Reader Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/tile-srom.c b/drivers/char/tile-srom.c index 398800edb2cc..3d4cca64b2d4 100644 --- a/drivers/char/tile-srom.c +++ b/drivers/char/tile-srom.c @@ -312,7 +312,8 @@ ATTRIBUTE_GROUPS(srom_dev); static char *srom_devnode(struct device *dev, umode_t *mode) { - *mode = S_IRUGO | S_IWUSR; + if (mode) + *mode = 0644; return kasprintf(GFP_KERNEL, "srom/%s", dev_name(dev)); } diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index cd84934774cc..889e4c398304 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -13,12 +13,25 @@ config FPGA if FPGA +config FPGA_REGION + tristate "FPGA Region" + depends on OF && FPGA_BRIDGE + help + FPGA Regions allow loading FPGA images under control of + the Device Tree. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" depends on ARCH_SOCFPGA help FPGA manager driver support for Altera SOCFPGA. +config FPGA_MGR_SOCFPGA_A10 + tristate "Altera SoCFPGA Arria10" + depends on ARCH_SOCFPGA + help + FPGA manager driver support for Altera Arria10 SoCFPGA. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST @@ -26,6 +39,29 @@ config FPGA_MGR_ZYNQ_FPGA help FPGA manager driver support for Xilinx Zynq FPGAs. +config FPGA_BRIDGE + tristate "FPGA Bridge Framework" + depends on OF + help + Say Y here if you want to support bridges connected between host + processors and FPGAs or between FPGAs. + +config SOCFPGA_FPGA_BRIDGE + tristate "Altera SoCFPGA FPGA Bridges" + depends on ARCH_SOCFPGA && FPGA_BRIDGE + help + Say Y to enable drivers for FPGA bridges for Altera SOCFPGA + devices. + +config ALTERA_FREEZE_BRIDGE + tristate "Altera FPGA Freeze Bridge" + depends on ARCH_SOCFPGA && FPGA_BRIDGE + help + Say Y to enable drivers for Altera FPGA Freeze bridges. A + freeze bridge is a bridge that exists in the FPGA fabric to + isolate one region of the FPGA from the busses while that + region is being reprogrammed. + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 8d83fc6b1613..8df07bcf42a6 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -7,4 +7,13 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o +obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o + +# FPGA Bridge Drivers +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o +obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o +obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o + +# High Level Interfaces +obj-$(CONFIG_FPGA_REGION) += fpga-region.o diff --git a/drivers/fpga/altera-fpga2sdram.c b/drivers/fpga/altera-fpga2sdram.c new file mode 100644 index 000000000000..7ab358ed6c76 --- /dev/null +++ b/drivers/fpga/altera-fpga2sdram.c @@ -0,0 +1,180 @@ +/* + * FPGA to SDRAM Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages a bridge between an FPGA and the SDRAM used by the ARM + * host processor system (HPS). + * + * The bridge contains 4 read ports, 4 write ports, and 6 command ports. + * Reconfiguring these ports requires that no SDRAM transactions occur during + * reconfiguration. The code reconfiguring the ports cannot run out of SDRAM + * nor can the FPGA access the SDRAM during reconfiguration. This driver does + * not support reconfiguring the ports. The ports are configured by code + * running out of on chip ram before Linux is started and the configuration + * is passed in a handoff register in the system manager. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <linux/fpga/fpga-bridge.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> + +#define ALT_SDR_CTL_FPGAPORTRST_OFST 0x80 +#define ALT_SDR_CTL_FPGAPORTRST_PORTRSTN_MSK 0x00003fff +#define ALT_SDR_CTL_FPGAPORTRST_RD_SHIFT 0 +#define ALT_SDR_CTL_FPGAPORTRST_WR_SHIFT 4 +#define ALT_SDR_CTL_FPGAPORTRST_CTRL_SHIFT 8 + +/* + * From the Cyclone V HPS Memory Map document: + * These registers are used to store handoff information between the + * preloader and the OS. These 8 registers can be used to store any + * information. The contents of these registers have no impact on + * the state of the HPS hardware. + */ +#define SYSMGR_ISWGRP_HANDOFF3 (0x8C) + +#define F2S_BRIDGE_NAME "fpga2sdram" + +struct alt_fpga2sdram_data { + struct device *dev; + struct regmap *sdrctl; + int mask; +}; + +static int alt_fpga2sdram_enable_show(struct fpga_bridge *bridge) +{ + struct alt_fpga2sdram_data *priv = bridge->priv; + int value; + + regmap_read(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, &value); + + return (value & priv->mask) == priv->mask; +} + +static inline int _alt_fpga2sdram_enable_set(struct alt_fpga2sdram_data *priv, + bool enable) +{ + return regmap_update_bits(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, + priv->mask, enable ? priv->mask : 0); +} + +static int alt_fpga2sdram_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_fpga2sdram_enable_set(bridge->priv, enable); +} + +struct prop_map { + char *prop_name; + u32 *prop_value; + u32 prop_max; +}; + +static const struct fpga_bridge_ops altera_fpga2sdram_br_ops = { + .enable_set = alt_fpga2sdram_enable_set, + .enable_show = alt_fpga2sdram_enable_show, +}; + +static const struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-fpga2sdram-bridge" }, + {}, +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct alt_fpga2sdram_data *priv; + u32 enable; + struct regmap *sysmgr; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->sdrctl = syscon_regmap_lookup_by_compatible("altr,sdr-ctl"); + if (IS_ERR(priv->sdrctl)) { + dev_err(dev, "regmap for altr,sdr-ctl lookup failed.\n"); + return PTR_ERR(priv->sdrctl); + } + + sysmgr = syscon_regmap_lookup_by_compatible("altr,sys-mgr"); + if (IS_ERR(priv->sdrctl)) { + dev_err(dev, "regmap for altr,sys-mgr lookup failed.\n"); + return PTR_ERR(sysmgr); + } + + /* Get f2s bridge configuration saved in handoff register */ + regmap_read(sysmgr, SYSMGR_ISWGRP_HANDOFF3, &priv->mask); + + ret = fpga_bridge_register(dev, F2S_BRIDGE_NAME, + &altera_fpga2sdram_br_ops, priv); + if (ret) + return ret; + + dev_info(dev, "driver initialized with handoff %08x\n", priv->mask); + + if (!of_property_read_u32(dev->of_node, "bridge-enable", &enable)) { + if (enable > 1) { + dev_warn(dev, "invalid bridge-enable %u > 1\n", enable); + } else { + dev_info(dev, "%s bridge\n", + (enable ? "enabling" : "disabling")); + ret = _alt_fpga2sdram_enable_set(priv, enable); + if (ret) { + fpga_bridge_unregister(&pdev->dev); + return ret; + } + } + } + + return ret; +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + fpga_bridge_unregister(&pdev->dev); + + return 0; +} + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver altera_fpga_driver = { + .probe = alt_fpga_bridge_probe, + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_fpga2sdram_bridge", + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +module_platform_driver(altera_fpga_driver); + +MODULE_DESCRIPTION("Altera SoCFPGA FPGA to SDRAM Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c new file mode 100644 index 000000000000..8dcd9fb22cb9 --- /dev/null +++ b/drivers/fpga/altera-freeze-bridge.c @@ -0,0 +1,273 @@ +/* + * FPGA Freeze Bridge Controller + * + * Copyright (C) 2016 Altera Corporation. All rights reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/fpga/fpga-bridge.h> + +#define FREEZE_CSR_STATUS_OFFSET 0 +#define FREEZE_CSR_CTRL_OFFSET 4 +#define FREEZE_CSR_ILLEGAL_REQ_OFFSET 8 +#define FREEZE_CSR_REG_VERSION 12 + +#define FREEZE_CSR_SUPPORTED_VERSION 2 + +#define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0) +#define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1) + +#define FREEZE_CSR_CTRL_FREEZE_REQ BIT(0) +#define FREEZE_CSR_CTRL_RESET_REQ BIT(1) +#define FREEZE_CSR_CTRL_UNFREEZE_REQ BIT(2) + +#define FREEZE_BRIDGE_NAME "freeze" + +struct altera_freeze_br_data { + struct device *dev; + void __iomem *base_addr; + bool enable; +}; + +/* + * Poll status until status bit is set or we have a timeout. + */ +static int altera_freeze_br_req_ack(struct altera_freeze_br_data *priv, + u32 timeout, u32 req_ack) +{ + struct device *dev = priv->dev; + void __iomem *csr_illegal_req_addr = priv->base_addr + + FREEZE_CSR_ILLEGAL_REQ_OFFSET; + u32 status, illegal, ctrl; + int ret = -ETIMEDOUT; + + do { + illegal = readl(csr_illegal_req_addr); + if (illegal) { + dev_err(dev, "illegal request detected 0x%x", illegal); + + writel(1, csr_illegal_req_addr); + + illegal = readl(csr_illegal_req_addr); + if (illegal) + dev_err(dev, "illegal request not cleared 0x%x", + illegal); + + ret = -EINVAL; + break; + } + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + dev_dbg(dev, "%s %x %x\n", __func__, status, req_ack); + status &= req_ack; + if (status) { + ctrl = readl(priv->base_addr + FREEZE_CSR_CTRL_OFFSET); + dev_dbg(dev, "%s request %x acknowledged %x %x\n", + __func__, req_ack, status, ctrl); + ret = 0; + break; + } + + udelay(1); + } while (timeout--); + + if (ret == -ETIMEDOUT) + dev_err(dev, "%s timeout waiting for 0x%x\n", + __func__, req_ack); + + return ret; +} + +static int altera_freeze_br_do_freeze(struct altera_freeze_br_data *priv, + u32 timeout) +{ + struct device *dev = priv->dev; + void __iomem *csr_ctrl_addr = priv->base_addr + + FREEZE_CSR_CTRL_OFFSET; + u32 status; + int ret; + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + if (status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE) { + dev_dbg(dev, "%s bridge already disabled %d\n", + __func__, status); + return 0; + } else if (!(status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE)) { + dev_err(dev, "%s bridge not enabled %d\n", __func__, status); + return -EINVAL; + } + + writel(FREEZE_CSR_CTRL_FREEZE_REQ, csr_ctrl_addr); + + ret = altera_freeze_br_req_ack(priv, timeout, + FREEZE_CSR_STATUS_FREEZE_REQ_DONE); + + if (ret) + writel(0, csr_ctrl_addr); + else + writel(FREEZE_CSR_CTRL_RESET_REQ, csr_ctrl_addr); + + return ret; +} + +static int altera_freeze_br_do_unfreeze(struct altera_freeze_br_data *priv, + u32 timeout) +{ + struct device *dev = priv->dev; + void __iomem *csr_ctrl_addr = priv->base_addr + + FREEZE_CSR_CTRL_OFFSET; + u32 status; + int ret; + + writel(0, csr_ctrl_addr); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) { + dev_dbg(dev, "%s bridge already enabled %d\n", + __func__, status); + return 0; + } else if (!(status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE)) { + dev_err(dev, "%s bridge not frozen %d\n", __func__, status); + return -EINVAL; + } + + writel(FREEZE_CSR_CTRL_UNFREEZE_REQ, csr_ctrl_addr); + + ret = altera_freeze_br_req_ack(priv, timeout, + FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + writel(0, csr_ctrl_addr); + + return ret; +} + +/* + * enable = 1 : allow traffic through the bridge + * enable = 0 : disable traffic through the bridge + */ +static int altera_freeze_br_enable_set(struct fpga_bridge *bridge, + bool enable) +{ + struct altera_freeze_br_data *priv = bridge->priv; + struct fpga_image_info *info = bridge->info; + u32 timeout = 0; + int ret; + + if (enable) { + if (info) + timeout = info->enable_timeout_us; + + ret = altera_freeze_br_do_unfreeze(bridge->priv, timeout); + } else { + if (info) + timeout = info->disable_timeout_us; + + ret = altera_freeze_br_do_freeze(bridge->priv, timeout); + } + + if (!ret) + priv->enable = enable; + + return ret; +} + +static int altera_freeze_br_enable_show(struct fpga_bridge *bridge) +{ + struct altera_freeze_br_data *priv = bridge->priv; + + return priv->enable; +} + +static struct fpga_bridge_ops altera_freeze_br_br_ops = { + .enable_set = altera_freeze_br_enable_set, + .enable_show = altera_freeze_br_enable_show, +}; + +static const struct of_device_id altera_freeze_br_of_match[] = { + { .compatible = "altr,freeze-bridge-controller", }, + {}, +}; +MODULE_DEVICE_TABLE(of, altera_freeze_br_of_match); + +static int altera_freeze_br_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct altera_freeze_br_data *priv; + struct resource *res; + u32 status, revision; + + if (!np) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base_addr)) + return PTR_ERR(priv->base_addr); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) + priv->enable = 1; + + revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION); + if (revision != FREEZE_CSR_SUPPORTED_VERSION) + dev_warn(dev, + "%s Freeze Controller unexpected revision %d != %d\n", + __func__, revision, FREEZE_CSR_SUPPORTED_VERSION); + + return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME, + &altera_freeze_br_br_ops, priv); +} + +static int altera_freeze_br_remove(struct platform_device *pdev) +{ + fpga_bridge_unregister(&pdev->dev); + + return 0; +} + +static struct platform_driver altera_freeze_br_driver = { + .probe = altera_freeze_br_probe, + .remove = altera_freeze_br_remove, + .driver = { + .name = "altera_freeze_br", + .of_match_table = of_match_ptr(altera_freeze_br_of_match), + }, +}; + +module_platform_driver(altera_freeze_br_driver); + +MODULE_DESCRIPTION("Altera Freeze Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c new file mode 100644 index 000000000000..4b354c79be31 --- /dev/null +++ b/drivers/fpga/altera-hps2fpga.c @@ -0,0 +1,222 @@ +/* + * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * Includes this patch from the mailing list: + * fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters + * Signed-off-by: Anatolij Gustschin <agust@denx.de> + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages bridges on a Altera SOCFPGA between the ARM host + * processor system (HPS) and the embedded FPGA. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <linux/clk.h> +#include <linux/fpga/fpga-bridge.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#define ALT_L3_REMAP_OFST 0x0 +#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001 +#define ALT_L3_REMAP_H2F_MSK 0x00000008 +#define ALT_L3_REMAP_LWH2F_MSK 0x00000010 + +#define HPS2FPGA_BRIDGE_NAME "hps2fpga" +#define LWHPS2FPGA_BRIDGE_NAME "lwhps2fpga" +#define FPGA2HPS_BRIDGE_NAME "fpga2hps" + +struct altera_hps2fpga_data { + const char *name; + struct reset_control *bridge_reset; + struct regmap *l3reg; + unsigned int remap_mask; + struct clk *clk; +}; + +static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge) +{ + struct altera_hps2fpga_data *priv = bridge->priv; + + return reset_control_status(priv->bridge_reset); +} + +/* The L3 REMAP register is write only, so keep a cached value. */ +static unsigned int l3_remap_shadow; +static spinlock_t l3_remap_lock; + +static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, + bool enable) +{ + unsigned long flags; + int ret; + + /* bring bridge out of reset */ + if (enable) + ret = reset_control_deassert(priv->bridge_reset); + else + ret = reset_control_assert(priv->bridge_reset); + if (ret) + return ret; + + /* Allow bridge to be visible to L3 masters or not */ + if (priv->remap_mask) { + spin_lock_irqsave(&l3_remap_lock, flags); + l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK; + + if (enable) + l3_remap_shadow |= priv->remap_mask; + else + l3_remap_shadow &= ~priv->remap_mask; + + ret = regmap_write(priv->l3reg, ALT_L3_REMAP_OFST, + l3_remap_shadow); + spin_unlock_irqrestore(&l3_remap_lock, flags); + } + + return ret; +} + +static int alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_hps2fpga_enable_set(bridge->priv, enable); +} + +static const struct fpga_bridge_ops altera_hps2fpga_br_ops = { + .enable_set = alt_hps2fpga_enable_set, + .enable_show = alt_hps2fpga_enable_show, +}; + +static struct altera_hps2fpga_data hps2fpga_data = { + .name = HPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_H2F_MSK, +}; + +static struct altera_hps2fpga_data lwhps2fpga_data = { + .name = LWHPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_LWH2F_MSK, +}; + +static struct altera_hps2fpga_data fpga2hps_data = { + .name = FPGA2HPS_BRIDGE_NAME, +}; + +static const struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-hps2fpga-bridge", + .data = &hps2fpga_data }, + { .compatible = "altr,socfpga-lwhps2fpga-bridge", + .data = &lwhps2fpga_data }, + { .compatible = "altr,socfpga-fpga2hps-bridge", + .data = &fpga2hps_data }, + {}, +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct altera_hps2fpga_data *priv; + const struct of_device_id *of_id; + u32 enable; + int ret; + + of_id = of_match_device(altera_fpga_of_match, dev); + priv = (struct altera_hps2fpga_data *)of_id->data; + + priv->bridge_reset = of_reset_control_get_by_index(dev->of_node, 0); + if (IS_ERR(priv->bridge_reset)) { + dev_err(dev, "Could not get %s reset control\n", priv->name); + return PTR_ERR(priv->bridge_reset); + } + + if (priv->remap_mask) { + priv->l3reg = syscon_regmap_lookup_by_compatible("altr,l3regs"); + if (IS_ERR(priv->l3reg)) { + dev_err(dev, "regmap for altr,l3regs lookup failed\n"); + return PTR_ERR(priv->l3reg); + } + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock specified\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return -EBUSY; + } + + spin_lock_init(&l3_remap_lock); + + if (!of_property_read_u32(dev->of_node, "bridge-enable", &enable)) { + if (enable > 1) { + dev_warn(dev, "invalid bridge-enable %u > 1\n", enable); + } else { + dev_info(dev, "%s bridge\n", + (enable ? "enabling" : "disabling")); + + ret = _alt_hps2fpga_enable_set(priv, enable); + if (ret) { + fpga_bridge_unregister(&pdev->dev); + return ret; + } + } + } + + return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, + priv); +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + struct altera_hps2fpga_data *priv = bridge->priv; + + fpga_bridge_unregister(&pdev->dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver alt_fpga_bridge_driver = { + .probe = alt_fpga_bridge_probe, + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_hps2fpga_bridge", + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +module_platform_driver(alt_fpga_bridge_driver); + +MODULE_DESCRIPTION("Altera SoCFPGA HPS to FPGA Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c new file mode 100644 index 000000000000..33ee83e6373c --- /dev/null +++ b/drivers/fpga/fpga-bridge.c @@ -0,0 +1,395 @@ +/* + * FPGA Bridge Framework Driver + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/fpga/fpga-bridge.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +static DEFINE_IDA(fpga_bridge_ida); +static struct class *fpga_bridge_class; + +/* Lock for adding/removing bridges to linked lists*/ +spinlock_t bridge_list_lock; + +static int fpga_bridge_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_bridge_enable - Enable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_enable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "enable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_enable); + +/** + * fpga_bridge_disable - Disable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_disable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "disable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_disable); + +/** + * of_fpga_bridge_get - get an exclusive reference to a fpga bridge + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * + * Return fpga_bridge struct if successful. + * Return -EBUSY if someone already has a reference to the bridge. + * Return -ENODEV if @np is not a FPGA Bridge. + */ +struct fpga_bridge *of_fpga_bridge_get(struct device_node *np, + struct fpga_image_info *info) + +{ + struct device *dev; + struct fpga_bridge *bridge; + int ret = -ENODEV; + + dev = class_find_device(fpga_bridge_class, NULL, np, + fpga_bridge_of_node_match); + if (!dev) + goto err_dev; + + bridge = to_fpga_bridge(dev); + if (!bridge) + goto err_dev; + + bridge->info = info; + + if (!mutex_trylock(&bridge->mutex)) { + ret = -EBUSY; + goto err_dev; + } + + if (!try_module_get(dev->parent->driver->owner)) + goto err_ll_mod; + + dev_dbg(&bridge->dev, "get\n"); + + return bridge; + +err_ll_mod: + mutex_unlock(&bridge->mutex); +err_dev: + put_device(dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(of_fpga_bridge_get); + +/** + * fpga_bridge_put - release a reference to a bridge + * + * @bridge: FPGA bridge + */ +void fpga_bridge_put(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "put\n"); + + bridge->info = NULL; + module_put(bridge->dev.parent->driver->owner); + mutex_unlock(&bridge->mutex); + put_device(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_put); + +/** + * fpga_bridges_enable - enable bridges in a list + * @bridge_list: list of FPGA bridges + * + * Enable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_enable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_enable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_enable); + +/** + * fpga_bridges_disable - disable bridges in a list + * + * @bridge_list: list of FPGA bridges + * + * Disable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_disable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_disable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_disable); + +/** + * fpga_bridges_put - put bridges + * + * @bridge_list: list of FPGA bridges + * + * For each bridge in the list, put the bridge and remove it from the list. + * If list is empty, do nothing. + */ +void fpga_bridges_put(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node, *next; + unsigned long flags; + + list_for_each_safe(node, next, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + + fpga_bridge_put(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_del(&bridge->node); + spin_unlock_irqrestore(&bridge_list_lock, flags); + } +} +EXPORT_SYMBOL_GPL(fpga_bridges_put); + +/** + * fpga_bridges_get_to_list - get a bridge, add it to a list + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * @bridge_list: list of FPGA bridges + * + * Get an exclusive reference to the bridge and and it to the list. + * + * Return 0 for success, error code from of_fpga_bridge_get() othewise. + */ +int fpga_bridge_get_to_list(struct device_node *np, + struct fpga_image_info *info, + struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + unsigned long flags; + + bridge = of_fpga_bridge_get(np, info); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_add(&bridge->node, bridge_list); + spin_unlock_irqrestore(&bridge_list_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_get_to_list); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + return sprintf(buf, "%s\n", bridge->name); +} + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + int enable = 1; + + if (bridge->br_ops && bridge->br_ops->enable_show) + enable = bridge->br_ops->enable_show(bridge); + + return sprintf(buf, "%s\n", enable ? "enabled" : "disabled"); +} + +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(state); + +static struct attribute *fpga_bridge_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_bridge); + +/** + * fpga_bridge_register - register a fpga bridge driver + * @dev: FPGA bridge device from pdev + * @name: FPGA bridge name + * @br_ops: pointer to structure of fpga bridge ops + * @priv: FPGA bridge private data + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_register(struct device *dev, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv) +{ + struct fpga_bridge *bridge; + int id, ret = 0; + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + id = ida_simple_get(&fpga_bridge_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto error_kfree; + } + + mutex_init(&bridge->mutex); + INIT_LIST_HEAD(&bridge->node); + + bridge->name = name; + bridge->br_ops = br_ops; + bridge->priv = priv; + + device_initialize(&bridge->dev); + bridge->dev.class = fpga_bridge_class; + bridge->dev.parent = dev; + bridge->dev.of_node = dev->of_node; + bridge->dev.id = id; + dev_set_drvdata(dev, bridge); + + ret = dev_set_name(&bridge->dev, "br%d", id); + if (ret) + goto error_device; + + ret = device_add(&bridge->dev); + if (ret) + goto error_device; + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + dev_info(bridge->dev.parent, "fpga bridge [%s] registered\n", + bridge->name); + + return 0; + +error_device: + ida_simple_remove(&fpga_bridge_ida, id); +error_kfree: + kfree(bridge); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_bridge_register); + +/** + * fpga_bridge_unregister - unregister a fpga bridge driver + * @dev: FPGA bridge device from pdev + */ +void fpga_bridge_unregister(struct device *dev) +{ + struct fpga_bridge *bridge = dev_get_drvdata(dev); + + /* + * If the low level driver provides a method for putting bridge into + * a desired state upon unregister, do it. + */ + if (bridge->br_ops && bridge->br_ops->fpga_bridge_remove) + bridge->br_ops->fpga_bridge_remove(bridge); + + device_unregister(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_unregister); + +static void fpga_bridge_dev_release(struct device *dev) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + ida_simple_remove(&fpga_bridge_ida, bridge->dev.id); + kfree(bridge); +} + +static int __init fpga_bridge_dev_init(void) +{ + spin_lock_init(&bridge_list_lock); + + fpga_bridge_class = class_create(THIS_MODULE, "fpga_bridge"); + if (IS_ERR(fpga_bridge_class)) + return PTR_ERR(fpga_bridge_class); + + fpga_bridge_class->dev_groups = fpga_bridge_groups; + fpga_bridge_class->dev_release = fpga_bridge_dev_release; + + return 0; +} + +static void __exit fpga_bridge_dev_exit(void) +{ + class_destroy(fpga_bridge_class); + ida_destroy(&fpga_bridge_ida); +} + +MODULE_DESCRIPTION("FPGA Bridge Driver"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_bridge_dev_init); +module_exit(fpga_bridge_dev_exit); diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 953dc9195937..79ce2eea44db 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -32,19 +32,20 @@ static struct class *fpga_mgr_class; /** * fpga_mgr_buf_load - load fpga from image in buffer * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @buf: buffer contain fpga image * @count: byte count of buf * * Step the low level fpga manager through the device-specific steps of getting * an FPGA ready to be configured, writing the image to it, then doing whatever * post-configuration steps necessary. This code assumes the caller got the - * mgr pointer from of_fpga_mgr_get() and checked that it is not an error code. + * mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is + * not an error code. * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, - size_t count) +int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, + const char *buf, size_t count) { struct device *dev = &mgr->dev; int ret; @@ -55,7 +56,7 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, * ready to receive an FPGA image. */ mgr->state = FPGA_MGR_STATE_WRITE_INIT; - ret = mgr->mops->write_init(mgr, flags, buf, count); + ret = mgr->mops->write_init(mgr, info, buf, count); if (ret) { dev_err(dev, "Error preparing FPGA for writing\n"); mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; @@ -78,7 +79,7 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, * steps to finish and set the FPGA into operating mode. */ mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE; - ret = mgr->mops->write_complete(mgr, flags); + ret = mgr->mops->write_complete(mgr, info); if (ret) { dev_err(dev, "Error after writing image data to FPGA\n"); mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; @@ -93,17 +94,19 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); /** * fpga_mgr_firmware_load - request firmware and load to fpga * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @image_name: name of image file on the firmware search path * * Request an FPGA image using the firmware class, then write out to the FPGA. * Update the state before each step to provide info on what step failed if * there is a failure. This code assumes the caller got the mgr pointer - * from of_fpga_mgr_get() and checked that it is not an error code. + * from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is not an error + * code. * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, +int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *image_name) { struct device *dev = &mgr->dev; @@ -121,7 +124,7 @@ int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, return ret; } - ret = fpga_mgr_buf_load(mgr, flags, fw->data, fw->size); + ret = fpga_mgr_buf_load(mgr, info, fw->data, fw->size); release_firmware(fw); @@ -181,30 +184,11 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -static int fpga_mgr_of_node_match(struct device *dev, const void *data) -{ - return dev->of_node == data; -} - -/** - * of_fpga_mgr_get - get an exclusive reference to a fpga mgr - * @node: device node - * - * Given a device node, get an exclusive reference to a fpga mgr. - * - * Return: fpga manager struct or IS_ERR() condition containing error code. - */ -struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; - struct device *dev; int ret = -ENODEV; - dev = class_find_device(fpga_mgr_class, NULL, node, - fpga_mgr_of_node_match); - if (!dev) - return ERR_PTR(-ENODEV); - mgr = to_fpga_manager(dev); if (!mgr) goto err_dev; @@ -226,6 +210,55 @@ err_dev: put_device(dev); return ERR_PTR(ret); } + +static int fpga_mgr_dev_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +/** + * fpga_mgr_get - get an exclusive reference to a fpga mgr + * @dev: parent device that fpga mgr was registered with + * + * Given a device, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *fpga_mgr_get(struct device *dev) +{ + struct device *mgr_dev = class_find_device(fpga_mgr_class, NULL, dev, + fpga_mgr_dev_match); + if (!mgr_dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(mgr_dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_get); + +static int fpga_mgr_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * of_fpga_mgr_get - get an exclusive reference to a fpga mgr + * @node: device node + * + * Given a device node, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +{ + struct device *dev; + + dev = class_find_device(fpga_mgr_class, NULL, node, + fpga_mgr_of_node_match); + if (!dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(dev); +} EXPORT_SYMBOL_GPL(of_fpga_mgr_get); /** diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c new file mode 100644 index 000000000000..3222fdbad75a --- /dev/null +++ b/drivers/fpga/fpga-region.c @@ -0,0 +1,603 @@ +/* + * FPGA Region - Device Tree support for FPGA programming under Linux + * + * Copyright (C) 2013-2016 Altera Corporation + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +/** + * struct fpga_region - FPGA Region structure + * @dev: FPGA Region device + * @mutex: enforces exclusive reference to region + * @bridge_list: list of FPGA bridges specified in region + * @info: fpga image specific information + */ +struct fpga_region { + struct device dev; + struct mutex mutex; /* for exclusive reference to region */ + struct list_head bridge_list; + struct fpga_image_info *info; +}; + +#define to_fpga_region(d) container_of(d, struct fpga_region, dev) + +static DEFINE_IDA(fpga_region_ida); +static struct class *fpga_region_class; + +static const struct of_device_id fpga_region_of_match[] = { + { .compatible = "fpga-region", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fpga_region_of_match); + +static int fpga_region_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_region_find - find FPGA region + * @np: device node of FPGA Region + * Caller will need to put_device(®ion->dev) when done. + * Returns FPGA Region struct or NULL + */ +static struct fpga_region *fpga_region_find(struct device_node *np) +{ + struct device *dev; + + dev = class_find_device(fpga_region_class, NULL, np, + fpga_region_of_node_match); + if (!dev) + return NULL; + + return to_fpga_region(dev); +} + +/** + * fpga_region_get - get an exclusive reference to a fpga region + * @region: FPGA Region struct + * + * Caller should call fpga_region_put() when done with region. + * + * Return fpga_region struct if successful. + * Return -EBUSY if someone already has a reference to the region. + * Return -ENODEV if @np is not a FPGA Region. + */ +static struct fpga_region *fpga_region_get(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + if (!mutex_trylock(®ion->mutex)) { + dev_dbg(dev, "%s: FPGA Region already in use\n", __func__); + return ERR_PTR(-EBUSY); + } + + get_device(dev); + of_node_get(dev->of_node); + if (!try_module_get(dev->parent->driver->owner)) { + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); + return ERR_PTR(-ENODEV); + } + + dev_dbg(®ion->dev, "get\n"); + + return region; +} + +/** + * fpga_region_put - release a reference to a region + * + * @region: FPGA region + */ +static void fpga_region_put(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + dev_dbg(®ion->dev, "put\n"); + + module_put(dev->parent->driver->owner); + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); +} + +/** + * fpga_region_get_manager - get exclusive reference for FPGA manager + * @region: FPGA region + * + * Get FPGA Manager from "fpga-mgr" property or from ancestor region. + * + * Caller should call fpga_mgr_put() when done with manager. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +static struct fpga_manager *fpga_region_get_manager(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + struct device_node *np = dev->of_node; + struct device_node *mgr_node; + struct fpga_manager *mgr; + + of_node_get(np); + while (np) { + if (of_device_is_compatible(np, "fpga-region")) { + mgr_node = of_parse_phandle(np, "fpga-mgr", 0); + if (mgr_node) { + mgr = of_fpga_mgr_get(mgr_node); + of_node_put(np); + return mgr; + } + } + np = of_get_next_parent(np); + } + of_node_put(np); + + return ERR_PTR(-EINVAL); +} + +/** + * fpga_region_get_bridges - create a list of bridges + * @region: FPGA region + * @overlay: device node of the overlay + * + * Create a list of bridges including the parent bridge and the bridges + * specified by "fpga-bridges" property. Note that the + * fpga_bridges_enable/disable/put functions are all fine with an empty list + * if that happens. + * + * Caller should call fpga_bridges_put(®ion->bridge_list) when + * done with the bridges. + * + * Return 0 for success (even if there are no bridges specified) + * or -EBUSY if any of the bridges are in use. + */ +static int fpga_region_get_bridges(struct fpga_region *region, + struct device_node *overlay) +{ + struct device *dev = ®ion->dev; + struct device_node *region_np = dev->of_node; + struct device_node *br, *np, *parent_br = NULL; + int i, ret; + + /* If parent is a bridge, add to list */ + ret = fpga_bridge_get_to_list(region_np->parent, region->info, + ®ion->bridge_list); + if (ret == -EBUSY) + return ret; + + if (!ret) + parent_br = region_np->parent; + + /* If overlay has a list of bridges, use it. */ + if (of_parse_phandle(overlay, "fpga-bridges", 0)) + np = overlay; + else + np = region_np; + + for (i = 0; ; i++) { + br = of_parse_phandle(np, "fpga-bridges", i); + if (!br) + break; + + /* If parent bridge is in list, skip it. */ + if (br == parent_br) + continue; + + /* If node is a bridge, get it and add to list */ + ret = fpga_bridge_get_to_list(br, region->info, + ®ion->bridge_list); + + /* If any of the bridges are in use, give up */ + if (ret == -EBUSY) { + fpga_bridges_put(®ion->bridge_list); + return -EBUSY; + } + } + + return 0; +} + +/** + * fpga_region_program_fpga - program FPGA + * @region: FPGA region + * @firmware_name: name of FPGA image firmware file + * @overlay: device node of the overlay + * Program an FPGA using information in the device tree. + * Function assumes that there is a firmware-name property. + * Return 0 for success or negative error code. + */ +static int fpga_region_program_fpga(struct fpga_region *region, + const char *firmware_name, + struct device_node *overlay) +{ + struct fpga_manager *mgr; + int ret; + + region = fpga_region_get(region); + if (IS_ERR(region)) { + pr_err("failed to get fpga region\n"); + return PTR_ERR(region); + } + + mgr = fpga_region_get_manager(region); + if (IS_ERR(mgr)) { + pr_err("failed to get fpga region manager\n"); + return PTR_ERR(mgr); + } + + ret = fpga_region_get_bridges(region, overlay); + if (ret) { + pr_err("failed to get fpga region bridges\n"); + goto err_put_mgr; + } + + ret = fpga_bridges_disable(®ion->bridge_list); + if (ret) { + pr_err("failed to disable region bridges\n"); + goto err_put_br; + } + + ret = fpga_mgr_firmware_load(mgr, region->info, firmware_name); + if (ret) { + pr_err("failed to load fpga image\n"); + goto err_put_br; + } + + ret = fpga_bridges_enable(®ion->bridge_list); + if (ret) { + pr_err("failed to enable region bridges\n"); + goto err_put_br; + } + + fpga_mgr_put(mgr); + fpga_region_put(region); + + return 0; + +err_put_br: + fpga_bridges_put(®ion->bridge_list); +err_put_mgr: + fpga_mgr_put(mgr); + fpga_region_put(region); + + return ret; +} + +/** + * child_regions_with_firmware + * @overlay: device node of the overlay + * + * If the overlay adds child FPGA regions, they are not allowed to have + * firmware-name property. + * + * Return 0 for OK or -EINVAL if child FPGA region adds firmware-name. + */ +static int child_regions_with_firmware(struct device_node *overlay) +{ + struct device_node *child_region; + const char *child_firmware_name; + int ret = 0; + + of_node_get(overlay); + + child_region = of_find_matching_node(overlay, fpga_region_of_match); + while (child_region) { + if (!of_property_read_string(child_region, "firmware-name", + &child_firmware_name)) { + ret = -EINVAL; + break; + } + child_region = of_find_matching_node(child_region, + fpga_region_of_match); + } + + of_node_put(child_region); + + if (ret) + pr_err("firmware-name not allowed in child FPGA region: %s", + child_region->full_name); + + return ret; +} + +/** + * fpga_region_notify_pre_apply - pre-apply overlay notification + * + * @region: FPGA region that the overlay was applied to + * @nd: overlay notification data + * + * Called after when an overlay targeted to a FPGA Region is about to be + * applied. Function will check the properties that will be added to the FPGA + * region. If the checks pass, it will program the FPGA. + * + * The checks are: + * The overlay must add either firmware-name or external-fpga-config property + * to the FPGA Region. + * + * firmware-name : program the FPGA + * external-fpga-config : FPGA is already programmed + * + * The overlay can add other FPGA regions, but child FPGA regions cannot have a + * firmware-name property since those regions don't exist yet. + * + * If the overlay that breaks the rules, notifier returns an error and the + * overlay is rejected before it goes into the main tree. + * + * Returns 0 for success or negative error code for failure. + */ +static int fpga_region_notify_pre_apply(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + const char *firmware_name = NULL; + struct fpga_image_info *info; + int ret; + + info = devm_kzalloc(®ion->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + region->info = info; + + /* Reject overlay if child FPGA Regions have firmware-name property */ + ret = child_regions_with_firmware(nd->overlay); + if (ret) + return ret; + + /* Read FPGA region properties from the overlay */ + if (of_property_read_bool(nd->overlay, "partial-fpga-config")) + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + if (of_property_read_bool(nd->overlay, "external-fpga-config")) + info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + + of_property_read_string(nd->overlay, "firmware-name", &firmware_name); + + of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", + &info->enable_timeout_us); + + of_property_read_u32(nd->overlay, "region-freeze-timeout-us", + &info->disable_timeout_us); + + /* If FPGA was externally programmed, don't specify firmware */ + if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { + pr_err("error: specified firmware and external-fpga-config"); + return -EINVAL; + } + + /* FPGA is already configured externally. We're done. */ + if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) + return 0; + + /* If we got this far, we should be programming the FPGA */ + if (!firmware_name) { + pr_err("should specify firmware-name or external-fpga-config\n"); + return -EINVAL; + } + + return fpga_region_program_fpga(region, firmware_name, nd->overlay); +} + +/** + * fpga_region_notify_post_remove - post-remove overlay notification + * + * @region: FPGA region that was targeted by the overlay that was removed + * @nd: overlay notification data + * + * Called after an overlay has been removed if the overlay's target was a + * FPGA region. + */ +static void fpga_region_notify_post_remove(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + fpga_bridges_disable(®ion->bridge_list); + fpga_bridges_put(®ion->bridge_list); + devm_kfree(®ion->dev, region->info); + region->info = NULL; +} + +/** + * of_fpga_region_notify - reconfig notifier for dynamic DT changes + * @nb: notifier block + * @action: notifier action + * @arg: reconfig data + * + * This notifier handles programming a FPGA when a "firmware-name" property is + * added to a fpga-region. + * + * Returns NOTIFY_OK or error if FPGA programming fails. + */ +static int of_fpga_region_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + struct of_overlay_notify_data *nd = arg; + struct fpga_region *region; + int ret; + + switch (action) { + case OF_OVERLAY_PRE_APPLY: + pr_debug("%s OF_OVERLAY_PRE_APPLY\n", __func__); + break; + case OF_OVERLAY_POST_APPLY: + pr_debug("%s OF_OVERLAY_POST_APPLY\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_PRE_REMOVE: + pr_debug("%s OF_OVERLAY_PRE_REMOVE\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_POST_REMOVE: + pr_debug("%s OF_OVERLAY_POST_REMOVE\n", __func__); + break; + default: /* should not happen */ + return NOTIFY_OK; + } + + region = fpga_region_find(nd->target); + if (!region) + return NOTIFY_OK; + + ret = 0; + switch (action) { + case OF_OVERLAY_PRE_APPLY: + ret = fpga_region_notify_pre_apply(region, nd); + break; + + case OF_OVERLAY_POST_REMOVE: + fpga_region_notify_post_remove(region, nd); + break; + } + + put_device(®ion->dev); + + if (ret) + return notifier_from_errno(ret); + + return NOTIFY_OK; +} + +static struct notifier_block fpga_region_of_nb = { + .notifier_call = of_fpga_region_notify, +}; + +static int fpga_region_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct fpga_region *region; + int id, ret = 0; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto err_kfree; + } + + mutex_init(®ion->mutex); + INIT_LIST_HEAD(®ion->bridge_list); + + device_initialize(®ion->dev); + region->dev.class = fpga_region_class; + region->dev.parent = dev; + region->dev.of_node = np; + region->dev.id = id; + dev_set_drvdata(dev, region); + + ret = dev_set_name(®ion->dev, "region%d", id); + if (ret) + goto err_remove; + + ret = device_add(®ion->dev); + if (ret) + goto err_remove; + + of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev); + + dev_info(dev, "FPGA Region probed\n"); + + return 0; + +err_remove: + ida_simple_remove(&fpga_region_ida, id); +err_kfree: + kfree(region); + + return ret; +} + +static int fpga_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = platform_get_drvdata(pdev); + + device_unregister(®ion->dev); + + return 0; +} + +static struct platform_driver fpga_region_driver = { + .probe = fpga_region_probe, + .remove = fpga_region_remove, + .driver = { + .name = "fpga-region", + .of_match_table = of_match_ptr(fpga_region_of_match), + }, +}; + +static void fpga_region_dev_release(struct device *dev) +{ + struct fpga_region *region = to_fpga_region(dev); + + ida_simple_remove(&fpga_region_ida, region->dev.id); + kfree(region); +} + +/** + * fpga_region_init - init function for fpga_region class + * Creates the fpga_region class and registers a reconfig notifier. + */ +static int __init fpga_region_init(void) +{ + int ret; + + fpga_region_class = class_create(THIS_MODULE, "fpga_region"); + if (IS_ERR(fpga_region_class)) + return PTR_ERR(fpga_region_class); + + fpga_region_class->dev_release = fpga_region_dev_release; + + ret = of_overlay_notifier_register(&fpga_region_of_nb); + if (ret) + goto err_class; + + ret = platform_driver_register(&fpga_region_driver); + if (ret) + goto err_plat; + + return 0; + +err_plat: + of_overlay_notifier_unregister(&fpga_region_of_nb); +err_class: + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); + return ret; +} + +static void __exit fpga_region_exit(void) +{ + platform_driver_unregister(&fpga_region_driver); + of_overlay_notifier_unregister(&fpga_region_of_nb); + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); +} + +subsys_initcall(fpga_region_init); +module_exit(fpga_region_exit); + +MODULE_DESCRIPTION("FPGA Region"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/socfpga-a10.c b/drivers/fpga/socfpga-a10.c new file mode 100644 index 000000000000..ccd9fb23bd52 --- /dev/null +++ b/drivers/fpga/socfpga-a10.c @@ -0,0 +1,556 @@ +/* + * FPGA Manager Driver for Altera Arria10 SoCFPGA + * + * Copyright (C) 2015-2016 Altera Corporation + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/regmap.h> + +#define A10_FPGAMGR_DCLKCNT_OFST 0x08 +#define A10_FPGAMGR_DCLKSTAT_OFST 0x0c +#define A10_FPGAMGR_IMGCFG_CTL_00_OFST 0x70 +#define A10_FPGAMGR_IMGCFG_CTL_01_OFST 0x74 +#define A10_FPGAMGR_IMGCFG_CTL_02_OFST 0x78 +#define A10_FPGAMGR_IMGCFG_STAT_OFST 0x80 + +#define A10_FPGAMGR_DCLKSTAT_DCLKDONE BIT(0) + +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NCONFIG BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NSTATUS BIT(1) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_CONDONE BIT(2) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NCONFIG BIT(8) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NSTATUS_OE BIT(16) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_CONDONE_OE BIT(24) + +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST BIT(16) +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE BIT(24) + +#define A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_MASK (BIT(16) | BIT(17)) +#define A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_SHIFT 16 +#define A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH BIT(24) +#define A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH_SHIFT 24 + +#define A10_FPGAMGR_IMGCFG_STAT_F2S_CRC_ERROR BIT(0) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_EARLY_USERMODE BIT(1) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE BIT(2) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN BIT(4) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_CONDONE_PIN BIT(6) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY BIT(9) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_DONE BIT(10) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR BIT(11) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_NCONFIG_PIN BIT(12) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_MASK (BIT(16) | BIT(17) | BIT(18)) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_SHIFT 16 + +/* FPGA CD Ratio Value */ +#define CDRATIO_x1 0x0 +#define CDRATIO_x2 0x1 +#define CDRATIO_x4 0x2 +#define CDRATIO_x8 0x3 + +/* Configuration width 16/32 bit */ +#define CFGWDTH_32 1 +#define CFGWDTH_16 0 + +/* + * struct a10_fpga_priv - private data for fpga manager + * @regmap: regmap for register access + * @fpga_data_addr: iomap for single address data register to FPGA + * @clk: clock + */ +struct a10_fpga_priv { + struct regmap *regmap; + void __iomem *fpga_data_addr; + struct clk *clk; +}; + +static bool socfpga_a10_fpga_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case A10_FPGAMGR_DCLKCNT_OFST: + case A10_FPGAMGR_DCLKSTAT_OFST: + case A10_FPGAMGR_IMGCFG_CTL_00_OFST: + case A10_FPGAMGR_IMGCFG_CTL_01_OFST: + case A10_FPGAMGR_IMGCFG_CTL_02_OFST: + return true; + } + return false; +} + +static bool socfpga_a10_fpga_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case A10_FPGAMGR_DCLKCNT_OFST: + case A10_FPGAMGR_DCLKSTAT_OFST: + case A10_FPGAMGR_IMGCFG_CTL_00_OFST: + case A10_FPGAMGR_IMGCFG_CTL_01_OFST: + case A10_FPGAMGR_IMGCFG_CTL_02_OFST: + case A10_FPGAMGR_IMGCFG_STAT_OFST: + return true; + } + return false; +} + +static const struct regmap_config socfpga_a10_fpga_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .writeable_reg = socfpga_a10_fpga_writeable_reg, + .readable_reg = socfpga_a10_fpga_readable_reg, + .max_register = A10_FPGAMGR_IMGCFG_STAT_OFST, + .cache_type = REGCACHE_NONE, +}; + +/* + * from the register map description of cdratio in imgcfg_ctrl_02: + * Normal Configuration : 32bit Passive Parallel + * Partial Reconfiguration : 16bit Passive Parallel + */ +static void socfpga_a10_fpga_set_cfg_width(struct a10_fpga_priv *priv, + int width) +{ + width <<= A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH_SHIFT; + + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH, width); +} + +static void socfpga_a10_fpga_generate_dclks(struct a10_fpga_priv *priv, + u32 count) +{ + u32 val; + + /* Clear any existing DONE status. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, + A10_FPGAMGR_DCLKSTAT_DCLKDONE); + + /* Issue the DCLK regmap. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKCNT_OFST, count); + + /* wait till the dclkcnt done */ + regmap_read_poll_timeout(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, val, + val, 1, 100); + + /* Clear DONE status. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, + A10_FPGAMGR_DCLKSTAT_DCLKDONE); +} + +#define RBF_ENCRYPTION_MODE_OFFSET 69 +#define RBF_DECOMPRESS_OFFSET 229 + +static int socfpga_a10_fpga_encrypted(u32 *buf32, size_t buf32_size) +{ + if (buf32_size < RBF_ENCRYPTION_MODE_OFFSET + 1) + return -EINVAL; + + /* Is the bitstream encrypted? */ + return ((buf32[RBF_ENCRYPTION_MODE_OFFSET] >> 2) & 3) != 0; +} + +static int socfpga_a10_fpga_compressed(u32 *buf32, size_t buf32_size) +{ + if (buf32_size < RBF_DECOMPRESS_OFFSET + 1) + return -EINVAL; + + /* Is the bitstream compressed? */ + return !((buf32[RBF_DECOMPRESS_OFFSET] >> 1) & 1); +} + +static unsigned int socfpga_a10_fpga_get_cd_ratio(unsigned int cfg_width, + bool encrypt, bool compress) +{ + unsigned int cd_ratio; + + /* + * cd ratio is dependent on cfg width and whether the bitstream + * is encrypted and/or compressed. + * + * | width | encr. | compr. | cd ratio | + * | 16 | 0 | 0 | 1 | + * | 16 | 0 | 1 | 4 | + * | 16 | 1 | 0 | 2 | + * | 16 | 1 | 1 | 4 | + * | 32 | 0 | 0 | 1 | + * | 32 | 0 | 1 | 8 | + * | 32 | 1 | 0 | 4 | + * | 32 | 1 | 1 | 8 | + */ + if (!compress && !encrypt) + return CDRATIO_x1; + + if (compress) + cd_ratio = CDRATIO_x4; + else + cd_ratio = CDRATIO_x2; + + /* If 32 bit, double the cd ratio by incrementing the field */ + if (cfg_width == CFGWDTH_32) + cd_ratio += 1; + + return cd_ratio; +} + +static int socfpga_a10_fpga_set_cdratio(struct fpga_manager *mgr, + unsigned int cfg_width, + const char *buf, size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + unsigned int cd_ratio; + int encrypt, compress; + + encrypt = socfpga_a10_fpga_encrypted((u32 *)buf, count / 4); + if (encrypt < 0) + return -EINVAL; + + compress = socfpga_a10_fpga_compressed((u32 *)buf, count / 4); + if (compress < 0) + return -EINVAL; + + cd_ratio = socfpga_a10_fpga_get_cd_ratio(cfg_width, encrypt, compress); + + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_MASK, + cd_ratio << A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_SHIFT); + + return 0; +} + +static u32 socfpga_a10_fpga_read_stat(struct a10_fpga_priv *priv) +{ + u32 val; + + regmap_read(priv->regmap, A10_FPGAMGR_IMGCFG_STAT_OFST, &val); + + return val; +} + +static int socfpga_a10_fpga_wait_for_pr_ready(struct a10_fpga_priv *priv) +{ + u32 reg, i; + + for (i = 0; i < 10 ; i++) { + reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR) + return -EINVAL; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY) + return 0; + } + + return -ETIMEDOUT; +} + +static int socfpga_a10_fpga_wait_for_pr_done(struct a10_fpga_priv *priv) +{ + u32 reg, i; + + for (i = 0; i < 10 ; i++) { + reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR) + return -EINVAL; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_DONE) + return 0; + } + + return -ETIMEDOUT; +} + +/* Start the FPGA programming by initialize the FPGA Manager */ +static int socfpga_a10_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + unsigned int cfg_width; + u32 msel, stat, mask; + int ret; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) + cfg_width = CFGWDTH_16; + else + return -EINVAL; + + /* Check for passive parallel (msel == 000 or 001) */ + msel = socfpga_a10_fpga_read_stat(priv); + msel &= A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_MASK; + msel >>= A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_SHIFT; + if ((msel != 0) && (msel != 1)) { + dev_dbg(&mgr->dev, "Fail: invalid msel=%d\n", msel); + return -EINVAL; + } + + /* Make sure no external devices are interfering */ + stat = socfpga_a10_fpga_read_stat(priv); + mask = A10_FPGAMGR_IMGCFG_STAT_F2S_NCONFIG_PIN | + A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN; + if ((stat & mask) != mask) + return -EINVAL; + + /* Set cfg width */ + socfpga_a10_fpga_set_cfg_width(priv, cfg_width); + + /* Determine cd ratio from bitstream header and set cd ratio */ + ret = socfpga_a10_fpga_set_cdratio(mgr, cfg_width, buf, count); + if (ret) + return ret; + + /* + * Clear s2f_nce to enable chip select. Leave pr_request + * unasserted and override disabled. + */ + regmap_write(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG); + + /* Set cfg_ctrl to enable s2f dclk and data */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL); + + /* + * Disable overrides not needed for pr. + * s2f_config==1 leaves reset deasseted. + */ + regmap_write(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_00_OFST, + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NCONFIG | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NSTATUS | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_CONDONE | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NCONFIG); + + /* Enable override for data, dclk, nce, and pr_request to CSS */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG, 0); + + /* Send some clocks to clear out any errors */ + socfpga_a10_fpga_generate_dclks(priv, 256); + + /* Assert pr_request */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST); + + /* Provide 2048 DCLKs before starting the config data streaming. */ + socfpga_a10_fpga_generate_dclks(priv, 0x7ff); + + /* Wait for pr_ready */ + return socfpga_a10_fpga_wait_for_pr_ready(priv); +} + +/* + * write data to the FPGA data register + */ +static int socfpga_a10_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 *buffer_32 = (u32 *)buf; + size_t i = 0; + + if (count <= 0) + return -EINVAL; + + /* Write out the complete 32-bit chunks */ + while (count >= sizeof(u32)) { + writel(buffer_32[i++], priv->fpga_data_addr); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks */ + switch (count) { + case 3: + writel(buffer_32[i++] & 0x00ffffff, priv->fpga_data_addr); + break; + case 2: + writel(buffer_32[i++] & 0x0000ffff, priv->fpga_data_addr); + break; + case 1: + writel(buffer_32[i++] & 0x000000ff, priv->fpga_data_addr); + break; + case 0: + break; + default: + /* This will never happen */ + return -EFAULT; + } + + return 0; +} + +static int socfpga_a10_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 reg; + int ret; + + /* Wait for pr_done */ + ret = socfpga_a10_fpga_wait_for_pr_done(priv); + + /* Clear pr_request */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST, 0); + + /* Send some clocks to clear out any errors */ + socfpga_a10_fpga_generate_dclks(priv, 256); + + /* Disable s2f dclk and data */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL, 0); + + /* Deassert chip select */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE); + + /* Disable data, dclk, nce, and pr_request override to CSS */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG); + + /* Return any errors regarding pr_done or pr_error */ + if (ret) + return ret; + + /* Final check */ + reg = socfpga_a10_fpga_read_stat(priv); + + if (((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE) == 0) || + ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_CONDONE_PIN) == 0) || + ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN) == 0)) { + dev_dbg(&mgr->dev, + "Timeout in final check. Status=%08xf\n", reg); + return -ETIMEDOUT; + } + + return 0; +} + +static enum fpga_mgr_states socfpga_a10_fpga_state(struct fpga_manager *mgr) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE) + return FPGA_MGR_STATE_OPERATING; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY) + return FPGA_MGR_STATE_WRITE; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_CRC_ERROR) + return FPGA_MGR_STATE_WRITE_COMPLETE_ERR; + + if ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN) == 0) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static const struct fpga_manager_ops socfpga_a10_fpga_mgr_ops = { + .state = socfpga_a10_fpga_state, + .write_init = socfpga_a10_fpga_write_init, + .write = socfpga_a10_fpga_write, + .write_complete = socfpga_a10_fpga_write_complete, +}; + +static int socfpga_a10_fpga_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct a10_fpga_priv *priv; + void __iomem *reg_base; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* First mmio base is for register access */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + + /* Second mmio base is for writing FPGA image data */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->fpga_data_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->fpga_data_addr)) + return PTR_ERR(priv->fpga_data_addr); + + /* regmap for register access */ + priv->regmap = devm_regmap_init_mmio(dev, reg_base, + &socfpga_a10_fpga_regmap_config); + if (IS_ERR(priv->regmap)) + return -ENODEV; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock specified\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return -EBUSY; + } + + return fpga_mgr_register(dev, "SoCFPGA Arria10 FPGA Manager", + &socfpga_a10_fpga_mgr_ops, priv); +} + +static int socfpga_a10_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct a10_fpga_priv *priv = mgr->priv; + + fpga_mgr_unregister(&pdev->dev); + clk_disable_unprepare(priv->clk); + + return 0; +} + +static const struct of_device_id socfpga_a10_fpga_of_match[] = { + { .compatible = "altr,socfpga-a10-fpga-mgr", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socfpga_a10_fpga_of_match); + +static struct platform_driver socfpga_a10_fpga_driver = { + .probe = socfpga_a10_fpga_probe, + .remove = socfpga_a10_fpga_remove, + .driver = { + .name = "socfpga_a10_fpga_manager", + .of_match_table = socfpga_a10_fpga_of_match, + }, +}; + +module_platform_driver(socfpga_a10_fpga_driver); + +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_DESCRIPTION("SoCFPGA Arria10 FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/socfpga.c b/drivers/fpga/socfpga.c index 27d2ff28132c..b6672e66cda6 100644 --- a/drivers/fpga/socfpga.c +++ b/drivers/fpga/socfpga.c @@ -407,13 +407,14 @@ static int socfpga_fpga_reset(struct fpga_manager *mgr) /* * Prepare the FPGA to receive the configuration data. */ -static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, u32 flags, +static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct socfpga_fpga_priv *priv = mgr->priv; int ret; - if (flags & FPGA_MGR_PARTIAL_RECONFIG) { + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); return -EINVAL; } @@ -478,7 +479,7 @@ static int socfpga_fpga_ops_configure_write(struct fpga_manager *mgr, } static int socfpga_fpga_ops_configure_complete(struct fpga_manager *mgr, - u32 flags) + struct fpga_image_info *info) { struct socfpga_fpga_priv *priv = mgr->priv; u32 status; diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index c2fb4120bd62..249682e92502 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -175,7 +175,8 @@ static irqreturn_t zynq_fpga_isr(int irq, void *data) return IRQ_HANDLED; } -static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, +static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct zynq_fpga_priv *priv; @@ -189,7 +190,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, return err; /* don't globally reset PL if we're doing partial reconfig */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* assert AXI interface resets */ regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET, FPGA_RST_ALL_MASK); @@ -343,7 +344,8 @@ out_free: return err; } -static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) +static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) { struct zynq_fpga_priv *priv = mgr->priv; int err; @@ -364,7 +366,7 @@ static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) return err; /* for the partial reconfig case we didn't touch the level shifters */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* enable level shifters from PL to PS */ regmap_write(priv->slcr, SLCR_LVL_SHFTR_EN_OFFSET, LVL_SHFTR_ENABLE_PL_TO_PS); diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 16f91c8490fe..5fb4c6d9209b 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -39,7 +39,7 @@ * vmbus_setevent- Trigger an event notification on the specified * channel. */ -static void vmbus_setevent(struct vmbus_channel *channel) +void vmbus_setevent(struct vmbus_channel *channel) { struct hv_monitor_page *monitorpage; @@ -65,6 +65,7 @@ static void vmbus_setevent(struct vmbus_channel *channel) vmbus_set_event(channel); } } +EXPORT_SYMBOL_GPL(vmbus_setevent); /* * vmbus_open - Open the specified channel. @@ -635,8 +636,6 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64)); struct kvec bufferlist[3]; u64 aligned_data = 0; - int ret; - bool signal = false; bool lock = channel->acquire_ring_lock; int num_vecs = ((bufferlen != 0) ? 3 : 1); @@ -656,33 +655,9 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, num_vecs, - &signal, lock, channel->signal_policy); - - /* - * Signalling the host is conditional on many factors: - * 1. The ring state changed from being empty to non-empty. - * This is tracked by the variable "signal". - * 2. The variable kick_q tracks if more data will be placed - * on the ring. We will not signal if more data is - * to be placed. - * - * Based on the channel signal state, we will decide - * which signaling policy will be applied. - * - * If we cannot write to the ring-buffer; signal the host - * even if we may not have written anything. This is a rare - * enough condition that it should not matter. - * NOTE: in this case, the hvsock channel is an exception, because - * it looks the host side's hvsock implementation has a throttling - * mechanism which can hurt the performance otherwise. - */ - - if (((ret == 0) && kick_q && signal) || - (ret && !is_hvsock_channel(channel))) - vmbus_setevent(channel); + return hv_ringbuffer_write(channel, bufferlist, num_vecs, + lock, kick_q); - return ret; } EXPORT_SYMBOL(vmbus_sendpacket_ctl); @@ -723,7 +698,6 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, u32 flags, bool kick_q) { - int ret; int i; struct vmbus_channel_packet_page_buffer desc; u32 descsize; @@ -731,7 +705,6 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; if (pagecount > MAX_PAGE_BUFFER_COUNT) @@ -769,29 +742,8 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - /* - * Signalling the host is conditional on many factors: - * 1. The ring state changed from being empty to non-empty. - * This is tracked by the variable "signal". - * 2. The variable kick_q tracks if more data will be placed - * on the ring. We will not signal if more data is - * to be placed. - * - * Based on the channel signal state, we will decide - * which signaling policy will be applied. - * - * If we cannot write to the ring-buffer; signal the host - * even if we may not have written anything. This is a rare - * enough condition that it should not matter. - */ - - if (((ret == 0) && kick_q && signal) || (ret)) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, kick_q); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer_ctl); @@ -822,12 +774,10 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, u32 desc_size, void *buffer, u32 bufferlen, u64 requestid) { - int ret; u32 packetlen; u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; packetlen = desc_size + bufferlen; @@ -848,13 +798,8 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - if (ret == 0 && signal) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, true); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_mpb_desc); @@ -866,14 +811,12 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, struct hv_multipage_buffer *multi_pagebuffer, void *buffer, u32 bufferlen, u64 requestid) { - int ret; struct vmbus_channel_packet_multipage_buffer desc; u32 descsize; u32 packetlen; u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); @@ -913,13 +856,8 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - if (ret == 0 && signal) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, true); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_multipagebuffer); @@ -941,16 +879,9 @@ __vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, u32 bufferlen, u32 *buffer_actual_len, u64 *requestid, bool raw) { - int ret; - bool signal = false; + return hv_ringbuffer_read(channel, buffer, bufferlen, + buffer_actual_len, requestid, raw); - ret = hv_ringbuffer_read(&channel->inbound, buffer, bufferlen, - buffer_actual_len, requestid, &signal, raw); - - if (signal) - vmbus_setevent(channel); - - return ret; } int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 96a85cd39580..cbb96f2f0d1a 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -447,8 +447,6 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) } dev_type = hv_get_dev_type(newchannel); - if (dev_type == HV_NIC) - set_channel_signal_state(newchannel, HV_SIGNAL_POLICY_EXPLICIT); init_vp_index(newchannel, dev_type); diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index fdf8da929cbe..14c3dc4bd23c 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -564,6 +564,11 @@ struct hv_dynmem_device { * next version to try. */ __u32 next_version; + + /* + * The negotiated version agreed by host. + */ + __u32 version; }; static struct hv_dynmem_device dm_device; @@ -645,6 +650,7 @@ static void hv_bring_pgs_online(struct hv_hotadd_state *has, { int i; + pr_debug("Online %lu pages starting at pfn 0x%lx\n", size, start_pfn); for (i = 0; i < size; i++) hv_page_online_one(has, pfn_to_page(start_pfn + i)); } @@ -685,7 +691,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, (HA_CHUNK << PAGE_SHIFT)); if (ret) { - pr_info("hot_add memory failed error is %d\n", ret); + pr_warn("hot_add memory failed error is %d\n", ret); if (ret == -EEXIST) { /* * This error indicates that the error @@ -814,6 +820,9 @@ static unsigned long handle_pg_range(unsigned long pg_start, unsigned long old_covered_state; unsigned long res = 0, flags; + pr_debug("Hot adding %lu pages starting at pfn 0x%lx.\n", pg_count, + pg_start); + spin_lock_irqsave(&dm_device.ha_lock, flags); list_for_each_entry(has, &dm_device.ha_region_list, list) { /* @@ -1025,8 +1034,13 @@ static void process_info(struct hv_dynmem_device *dm, struct dm_info_msg *msg) switch (info_hdr->type) { case INFO_TYPE_MAX_PAGE_CNT: - pr_info("Received INFO_TYPE_MAX_PAGE_CNT\n"); - pr_info("Data Size is %d\n", info_hdr->data_size); + if (info_hdr->data_size == sizeof(__u64)) { + __u64 *max_page_count = (__u64 *)&info_hdr[1]; + + pr_info("INFO_TYPE_MAX_PAGE_CNT = %llu\n", + *max_page_count); + } + break; default: pr_info("Received Unknown type: %d\n", info_hdr->type); @@ -1196,8 +1210,6 @@ static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm, return num_pages; } - - static void balloon_up(struct work_struct *dummy) { unsigned int num_pages = dm_device.balloon_wrk.num_pages; @@ -1224,6 +1236,10 @@ static void balloon_up(struct work_struct *dummy) /* Refuse to balloon below the floor, keep the 2M granularity. */ if (avail_pages < num_pages || avail_pages - num_pages < floor) { + pr_warn("Balloon request will be partially fulfilled. %s\n", + avail_pages < num_pages ? "Not enough memory." : + "Balloon floor reached."); + num_pages = avail_pages > floor ? (avail_pages - floor) : 0; num_pages -= num_pages % PAGES_IN_2M; } @@ -1245,6 +1261,9 @@ static void balloon_up(struct work_struct *dummy) } if (num_ballooned == 0 || num_ballooned == num_pages) { + pr_debug("Ballooned %u out of %u requested pages.\n", + num_pages, dm_device.balloon_wrk.num_pages); + bl_resp->more_pages = 0; done = true; dm_device.state = DM_INITIALIZED; @@ -1292,12 +1311,16 @@ static void balloon_down(struct hv_dynmem_device *dm, int range_count = req->range_count; struct dm_unballoon_response resp; int i; + unsigned int prev_pages_ballooned = dm->num_pages_ballooned; for (i = 0; i < range_count; i++) { free_balloon_pages(dm, &range_array[i]); complete(&dm_device.config_event); } + pr_debug("Freed %u ballooned pages.\n", + prev_pages_ballooned - dm->num_pages_ballooned); + if (req->more_pages == 1) return; @@ -1365,6 +1388,7 @@ static void version_resp(struct hv_dynmem_device *dm, version_req.hdr.size = sizeof(struct dm_version_request); version_req.hdr.trans_id = atomic_inc_return(&trans_id); version_req.version.version = dm->next_version; + dm->version = version_req.version.version; /* * Set the next version to try in case current version fails. @@ -1501,7 +1525,11 @@ static int balloon_probe(struct hv_device *dev, struct dm_version_request version_req; struct dm_capabilities cap_msg; +#ifdef CONFIG_MEMORY_HOTPLUG do_hot_add = hot_add; +#else + do_hot_add = false; +#endif /* * First allocate a send buffer. @@ -1553,6 +1581,7 @@ static int balloon_probe(struct hv_device *dev, version_req.hdr.trans_id = atomic_inc_return(&trans_id); version_req.version.version = DYNMEM_PROTOCOL_VERSION_WIN10; version_req.is_last_attempt = 0; + dm_device.version = version_req.version.version; ret = vmbus_sendpacket(dev->channel, &version_req, sizeof(struct dm_version_request), @@ -1575,6 +1604,11 @@ static int balloon_probe(struct hv_device *dev, ret = -ETIMEDOUT; goto probe_error2; } + + pr_info("Using Dynamic Memory protocol version %u.%u\n", + DYNMEM_MAJOR_VERSION(dm_device.version), + DYNMEM_MINOR_VERSION(dm_device.version)); + /* * Now submit our capabilities to the host. */ diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c index a6707133c297..eee238cc60bd 100644 --- a/drivers/hv/hv_snapshot.c +++ b/drivers/hv/hv_snapshot.c @@ -31,7 +31,10 @@ #define VSS_MINOR 0 #define VSS_VERSION (VSS_MAJOR << 16 | VSS_MINOR) -#define VSS_USERSPACE_TIMEOUT (msecs_to_jiffies(10 * 1000)) +/* + * Timeout values are based on expecations from host + */ +#define VSS_FREEZE_TIMEOUT (15 * 60) /* * Global state maintained for transaction that is being processed. For a class @@ -120,7 +123,7 @@ static int vss_handle_handshake(struct hv_vss_msg *vss_msg) default: return -EINVAL; } - pr_debug("VSS: userspace daemon ver. %d connected\n", dm_reg_value); + pr_info("VSS: userspace daemon ver. %d connected\n", dm_reg_value); return 0; } @@ -128,8 +131,10 @@ static int vss_on_msg(void *msg, int len) { struct hv_vss_msg *vss_msg = (struct hv_vss_msg *)msg; - if (len != sizeof(*vss_msg)) + if (len != sizeof(*vss_msg)) { + pr_debug("VSS: Message size does not match length\n"); return -EINVAL; + } if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER || vss_msg->vss_hdr.operation == VSS_OP_REGISTER1) { @@ -137,8 +142,11 @@ static int vss_on_msg(void *msg, int len) * Don't process registration messages if we're in the middle * of a transaction processing. */ - if (vss_transaction.state > HVUTIL_READY) + if (vss_transaction.state > HVUTIL_READY) { + pr_debug("VSS: Got unexpected registration request\n"); return -EINVAL; + } + return vss_handle_handshake(vss_msg); } else if (vss_transaction.state == HVUTIL_USERSPACE_REQ) { vss_transaction.state = HVUTIL_USERSPACE_RECV; @@ -155,7 +163,7 @@ static int vss_on_msg(void *msg, int len) } } else { /* This is a spurious call! */ - pr_warn("VSS: Transaction not active\n"); + pr_debug("VSS: Transaction not active\n"); return -EINVAL; } return 0; @@ -168,8 +176,10 @@ static void vss_send_op(void) struct hv_vss_msg *vss_msg; /* The transaction state is wrong. */ - if (vss_transaction.state != HVUTIL_HOSTMSG_RECEIVED) + if (vss_transaction.state != HVUTIL_HOSTMSG_RECEIVED) { + pr_debug("VSS: Unexpected attempt to send to daemon\n"); return; + } vss_msg = kzalloc(sizeof(*vss_msg), GFP_KERNEL); if (!vss_msg) @@ -179,7 +189,8 @@ static void vss_send_op(void) vss_transaction.state = HVUTIL_USERSPACE_REQ; - schedule_delayed_work(&vss_timeout_work, VSS_USERSPACE_TIMEOUT); + schedule_delayed_work(&vss_timeout_work, op == VSS_OP_FREEZE ? + VSS_FREEZE_TIMEOUT * HZ : HV_UTIL_TIMEOUT * HZ); rc = hvutil_transport_send(hvt, vss_msg, sizeof(*vss_msg), NULL); if (rc) { @@ -210,9 +221,13 @@ static void vss_handle_request(struct work_struct *dummy) case VSS_OP_HOT_BACKUP: if (vss_transaction.state < HVUTIL_READY) { /* Userspace is not registered yet */ + pr_debug("VSS: Not ready for request.\n"); vss_respond_to_host(HV_E_FAIL); return; } + + pr_debug("VSS: Received request for op code: %d\n", + vss_transaction.msg->vss_hdr.operation); vss_transaction.state = HVUTIL_HOSTMSG_RECEIVED; vss_send_op(); return; @@ -353,8 +368,10 @@ hv_vss_init(struct hv_util_service *srv) hvt = hvutil_transport_init(vss_devname, CN_VSS_IDX, CN_VSS_VAL, vss_on_msg, vss_on_reset); - if (!hvt) + if (!hvt) { + pr_warn("VSS: Failed to initialize transport\n"); return -EFAULT; + } return 0; } diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index bcd06306f3e8..e7707747f56d 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -389,16 +389,19 @@ static int util_probe(struct hv_device *dev, ts_srv_version = TS_VERSION_1; hb_srv_version = HB_VERSION_1; break; - case(VERSION_WIN10): + case VERSION_WIN7: + case VERSION_WIN8: + case VERSION_WIN8_1: util_fw_version = UTIL_FW_VERSION; sd_srv_version = SD_VERSION; - ts_srv_version = TS_VERSION; + ts_srv_version = TS_VERSION_3; hb_srv_version = HB_VERSION; break; + case VERSION_WIN10: default: util_fw_version = UTIL_FW_VERSION; sd_srv_version = SD_VERSION; - ts_srv_version = TS_VERSION_3; + ts_srv_version = TS_VERSION; hb_srv_version = HB_VERSION; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index a5b4442433c8..0675b395ce5c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -38,7 +38,7 @@ /* * Timeout for guest-host handshake for services. */ -#define HV_UTIL_NEGO_TIMEOUT 60 +#define HV_UTIL_NEGO_TIMEOUT 55 /* * The below CPUID leaves are present if VersionAndFeatures.HypervisorPresent @@ -527,14 +527,14 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info, void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info); -int hv_ringbuffer_write(struct hv_ring_buffer_info *ring_info, +int hv_ringbuffer_write(struct vmbus_channel *channel, struct kvec *kv_list, - u32 kv_count, bool *signal, bool lock, - enum hv_signal_policy policy); + u32 kv_count, bool lock, + bool kick_q); -int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, +int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, - u64 *requestid, bool *signal, bool raw); + u64 *requestid, bool raw); void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, struct hv_ring_buffer_debug_info *debug_info); diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 08043da1a61c..cd49cb17eb7f 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -66,21 +66,25 @@ u32 hv_end_read(struct hv_ring_buffer_info *rbi) * once the ring buffer is empty, it will clear the * interrupt_mask and re-check to see if new data has * arrived. + * + * KYS: Oct. 30, 2016: + * It looks like Windows hosts have logic to deal with DOS attacks that + * can be triggered if it receives interrupts when it is not expecting + * the interrupt. The host expects interrupts only when the ring + * transitions from empty to non-empty (or full to non full on the guest + * to host ring). + * So, base the signaling decision solely on the ring state until the + * host logic is fixed. */ -static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi, - enum hv_signal_policy policy) +static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel, + bool kick_q) { + struct hv_ring_buffer_info *rbi = &channel->outbound; + virt_mb(); if (READ_ONCE(rbi->ring_buffer->interrupt_mask)) - return false; - - /* - * When the client wants to control signaling, - * we only honour the host interrupt mask. - */ - if (policy == HV_SIGNAL_POLICY_EXPLICIT) - return true; + return; /* check interrupt_mask before read_index */ virt_rmb(); @@ -89,9 +93,9 @@ static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi, * ring transitions from being empty to non-empty. */ if (old_write == READ_ONCE(rbi->ring_buffer->read_index)) - return true; + vmbus_setevent(channel); - return false; + return; } /* Get the next write location for the specified ring buffer. */ @@ -280,9 +284,9 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info) } /* Write to the ring buffer. */ -int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, - struct kvec *kv_list, u32 kv_count, bool *signal, bool lock, - enum hv_signal_policy policy) +int hv_ringbuffer_write(struct vmbus_channel *channel, + struct kvec *kv_list, u32 kv_count, bool lock, + bool kick_q) { int i = 0; u32 bytes_avail_towrite; @@ -292,6 +296,7 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, u32 old_write; u64 prev_indices = 0; unsigned long flags = 0; + struct hv_ring_buffer_info *outring_info = &channel->outbound; for (i = 0; i < kv_count; i++) totalbytes_towrite += kv_list[i].iov_len; @@ -344,13 +349,13 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, if (lock) spin_unlock_irqrestore(&outring_info->ring_lock, flags); - *signal = hv_need_to_signal(old_write, outring_info, policy); + hv_signal_on_write(old_write, channel, kick_q); return 0; } -int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, +int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, - u64 *requestid, bool *signal, bool raw) + u64 *requestid, bool raw) { u32 bytes_avail_toread; u32 next_read_location = 0; @@ -359,6 +364,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, u32 offset; u32 packetlen; int ret = 0; + struct hv_ring_buffer_info *inring_info = &channel->inbound; if (buflen <= 0) return -EINVAL; @@ -416,7 +422,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, /* Update the read index */ hv_set_next_read_location(inring_info, next_read_location); - *signal = hv_need_to_signal_on_read(inring_info); + hv_signal_on_read(channel); return ret; } diff --git a/drivers/lightnvm/core.c b/drivers/lightnvm/core.c index 1cac0f8bc0dc..2329f050ff82 100644 --- a/drivers/lightnvm/core.c +++ b/drivers/lightnvm/core.c @@ -22,7 +22,7 @@ #include <linux/types.h> #include <linux/sem.h> #include <linux/bitmap.h> -#include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/miscdevice.h> #include <linux/lightnvm.h> #include <linux/sched/sysctl.h> @@ -889,6 +889,10 @@ static const struct kernel_param_ops nvm_configure_by_str_event_param_ops = { .get = nvm_configure_get, }; +/* + * Not available as modular, but easiest way to remain compatible with + * existing boot arg behaviour is to continue using module param here. + */ #undef MODULE_PARAM_PREFIX #define MODULE_PARAM_PREFIX "lnvm." @@ -1162,10 +1166,4 @@ static struct miscdevice _nvm_misc = { .nodename = "lightnvm/control", .fops = &_ctl_fops, }; -module_misc_device(_nvm_misc); - -MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); - -MODULE_AUTHOR("Matias Bjorling <m@bjorling.me>"); -MODULE_LICENSE("GPL v2"); -MODULE_VERSION("0.1"); +builtin_misc_device(_nvm_misc); diff --git a/drivers/misc/genwqe/card_base.h b/drivers/misc/genwqe/card_base.h index cb851c14ca4b..5813b5f25006 100644 --- a/drivers/misc/genwqe/card_base.h +++ b/drivers/misc/genwqe/card_base.h @@ -41,7 +41,6 @@ #include "genwqe_driver.h" #define GENWQE_MSI_IRQS 4 /* Just one supported, no MSIx */ -#define GENWQE_FLAG_MSI_ENABLED (1 << 0) #define GENWQE_MAX_VFS 15 /* maximum 15 VFs are possible */ #define GENWQE_MAX_FUNCS 16 /* 1 PF and 15 VFs */ diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index fc2794b513fa..147b83011b58 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -740,13 +740,10 @@ int genwqe_read_softreset(struct genwqe_dev *cd) int genwqe_set_interrupt_capability(struct genwqe_dev *cd, int count) { int rc; - struct pci_dev *pci_dev = cd->pci_dev; - rc = pci_enable_msi_range(pci_dev, 1, count); + rc = pci_alloc_irq_vectors(cd->pci_dev, 1, count, PCI_IRQ_MSI); if (rc < 0) return rc; - - cd->flags |= GENWQE_FLAG_MSI_ENABLED; return 0; } @@ -756,12 +753,7 @@ int genwqe_set_interrupt_capability(struct genwqe_dev *cd, int count) */ void genwqe_reset_interrupt_capability(struct genwqe_dev *cd) { - struct pci_dev *pci_dev = cd->pci_dev; - - if (cd->flags & GENWQE_FLAG_MSI_ENABLED) { - pci_disable_msi(pci_dev); - cd->flags &= ~GENWQE_FLAG_MSI_ENABLED; - } + pci_free_irq_vectors(cd->pci_dev); } /** diff --git a/drivers/misc/lkdtm_perms.c b/drivers/misc/lkdtm_perms.c index 45f1c0f96612..c7635a79341f 100644 --- a/drivers/misc/lkdtm_perms.c +++ b/drivers/misc/lkdtm_perms.c @@ -60,15 +60,18 @@ static noinline void execute_location(void *dst, bool write) static void execute_user_location(void *dst) { + int copied; + /* Intentionally crossing kernel/user memory boundary. */ void (*func)(void) = dst; pr_info("attempting ok execution at %p\n", do_nothing); do_nothing(); - if (copy_to_user((void __user *)dst, do_nothing, EXEC_SIZE)) + copied = access_process_vm(current, (unsigned long)dst, do_nothing, + EXEC_SIZE, FOLL_WRITE); + if (copied < EXEC_SIZE) return; - flush_icache_range((unsigned long)dst, (unsigned long)dst + EXEC_SIZE); pr_info("attempting bad execution at %p\n", func); func(); } diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 7ae89b4a21d5..466afb2611c6 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -144,7 +144,7 @@ int mei_amthif_run_next_cmd(struct mei_device *dev) dev->iamthif_state = MEI_IAMTHIF_WRITING; cl->fp = cb->fp; - ret = mei_cl_write(cl, cb, false); + ret = mei_cl_write(cl, cb); if (ret < 0) return ret; diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 75b9d4ac8b1e..7f2cef9011ae 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -38,6 +38,9 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO; #define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) +#define MEI_UUID_MKHIF_FIX UUID_LE(0x55213584, 0x9a29, 0x4916, \ + 0xba, 0xdf, 0xf, 0xb7, 0xed, 0x68, 0x2a, 0xeb) + #define MEI_UUID_ANY NULL_UUID_LE /** @@ -69,6 +72,97 @@ static void blacklist(struct mei_cl_device *cldev) cldev->do_match = 0; } +#define OSTYPE_LINUX 2 +struct mei_os_ver { + __le16 build; + __le16 reserved1; + u8 os_type; + u8 major; + u8 minor; + u8 reserved2; +} __packed; + +#define MKHI_FEATURE_PTT 0x10 + +struct mkhi_rule_id { + __le16 rule_type; + u8 feature_id; + u8 reserved; +} __packed; + +struct mkhi_fwcaps { + struct mkhi_rule_id id; + u8 len; + u8 data[0]; +} __packed; + +#define MKHI_FWCAPS_GROUP_ID 0x3 +#define MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD 6 +struct mkhi_msg_hdr { + u8 group_id; + u8 command; + u8 reserved; + u8 result; +} __packed; + +struct mkhi_msg { + struct mkhi_msg_hdr hdr; + u8 data[0]; +} __packed; + +static int mei_osver(struct mei_cl_device *cldev) +{ + int ret; + const size_t size = sizeof(struct mkhi_msg_hdr) + + sizeof(struct mkhi_fwcaps) + + sizeof(struct mei_os_ver); + size_t length = 8; + char buf[size]; + struct mkhi_msg *req; + struct mkhi_fwcaps *fwcaps; + struct mei_os_ver *os_ver; + unsigned int mode = MEI_CL_IO_TX_BLOCKING | MEI_CL_IO_TX_INTERNAL; + + memset(buf, 0, size); + + req = (struct mkhi_msg *)buf; + req->hdr.group_id = MKHI_FWCAPS_GROUP_ID; + req->hdr.command = MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD; + + fwcaps = (struct mkhi_fwcaps *)req->data; + + fwcaps->id.rule_type = 0x0; + fwcaps->id.feature_id = MKHI_FEATURE_PTT; + fwcaps->len = sizeof(*os_ver); + os_ver = (struct mei_os_ver *)fwcaps->data; + os_ver->os_type = OSTYPE_LINUX; + + ret = __mei_cl_send(cldev->cl, buf, size, mode); + if (ret < 0) + return ret; + + ret = __mei_cl_recv(cldev->cl, buf, length); + if (ret < 0) + return ret; + + return 0; +} + +static void mei_mkhi_fix(struct mei_cl_device *cldev) +{ + int ret; + + ret = mei_cldev_enable(cldev); + if (ret) + return; + + ret = mei_osver(cldev); + if (ret) + dev_err(&cldev->dev, "OS version command failed %d\n", ret); + + mei_cldev_disable(cldev); +} + /** * mei_wd - wd client on the bus, change protocol version * as the API has changed. @@ -162,7 +256,8 @@ static int mei_nfc_if_version(struct mei_cl *cl, WARN_ON(mutex_is_locked(&bus->device_lock)); - ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd), 1); + ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd), + MEI_CL_IO_TX_BLOCKING); if (ret < 0) { dev_err(bus->dev, "Could not send IF version cmd\n"); return ret; @@ -309,6 +404,7 @@ static struct mei_fixup { MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist), MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc), MEI_FIXUP(MEI_UUID_WD, mei_wd), + MEI_FIXUP(MEI_UUID_MKHIF_FIX, mei_mkhi_fix), }; /** diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 8cac7ef9ad0d..7c075e9b25e5 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -36,12 +36,12 @@ * @cl: host client * @buf: buffer to send * @length: buffer length - * @blocking: wait for write completion + * @mode: sending mode * * Return: written size bytes or < 0 on error */ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, - bool blocking) + unsigned int mode) { struct mei_device *bus; struct mei_cl_cb *cb; @@ -80,9 +80,11 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, goto out; } + cb->internal = !!(mode & MEI_CL_IO_TX_INTERNAL); + cb->blocking = !!(mode & MEI_CL_IO_TX_BLOCKING); memcpy(cb->buf.data, buf, length); - rets = mei_cl_write(cl, cb, blocking); + rets = mei_cl_write(cl, cb); out: mutex_unlock(&bus->device_lock); @@ -188,7 +190,7 @@ ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length) if (cl == NULL) return -ENODEV; - return __mei_cl_send(cl, buf, length, 1); + return __mei_cl_send(cl, buf, length, MEI_CL_IO_TX_BLOCKING); } EXPORT_SYMBOL_GPL(mei_cldev_send); @@ -228,7 +230,7 @@ static void mei_cl_bus_event_work(struct work_struct *work) bus = cldev->bus; if (cldev->event_cb) - cldev->event_cb(cldev, cldev->events, cldev->event_context); + cldev->event_cb(cldev, cldev->events); cldev->events = 0; @@ -301,7 +303,6 @@ bool mei_cl_bus_rx_event(struct mei_cl *cl) * @cldev: me client devices * @event_cb: callback function * @events_mask: requested events bitmask - * @context: driver context data * * Return: 0 on success * -EALREADY if an callback is already registered @@ -309,7 +310,7 @@ bool mei_cl_bus_rx_event(struct mei_cl *cl) */ int mei_cldev_register_event_cb(struct mei_cl_device *cldev, unsigned long events_mask, - mei_cldev_event_cb_t event_cb, void *context) + mei_cldev_event_cb_t event_cb) { struct mei_device *bus = cldev->bus; int ret; @@ -320,7 +321,6 @@ int mei_cldev_register_event_cb(struct mei_cl_device *cldev, cldev->events = 0; cldev->events_mask = events_mask; cldev->event_cb = event_cb; - cldev->event_context = context; INIT_WORK(&cldev->event_work, mei_cl_bus_event_work); if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) { @@ -483,7 +483,7 @@ int mei_cldev_disable(struct mei_cl_device *cldev) mutex_lock(&bus->device_lock); if (!mei_cl_is_connected(cl)) { - dev_err(bus->dev, "Already disconnected"); + dev_dbg(bus->dev, "Already disconnected"); err = 0; goto out; } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 6fe02350578d..beb942e6c248 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1598,18 +1598,17 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, * * @cl: host client * @cb: write callback with filled data - * @blocking: block until completed * * Return: number of bytes sent on success, <0 on failure. */ -int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) +int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) { struct mei_device *dev; struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; int size; int rets; - + bool blocking; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -1621,6 +1620,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) buf = &cb->buf; size = buf->size; + blocking = cb->blocking; cl_dbg(dev, cl, "size=%d\n", size); diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index d2bfabecd882..f2545af9be7b 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -219,7 +219,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp); int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr, struct mei_cl_cb *cmpl_list); -int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking); +int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index fa50635512e8..e1bf54481fd6 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -322,7 +322,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; } - rets = mei_cl_write(cl, cb, false); + rets = mei_cl_write(cl, cb); out: mutex_unlock(&dev->device_lock); return rets; @@ -653,7 +653,7 @@ static int mei_fasync(int fd, struct file *file, int band) } /** - * fw_status_show - mei device attribute show method + * fw_status_show - mei device fw_status attribute show method * * @device: device pointer * @attr: attribute pointer @@ -684,8 +684,49 @@ static ssize_t fw_status_show(struct device *device, } static DEVICE_ATTR_RO(fw_status); +/** + * hbm_ver_show - display HBM protocol version negotiated with FW + * + * @device: device pointer + * @attr: attribute pointer + * @buf: char out buffer + * + * Return: number of the bytes printed into buf or error + */ +static ssize_t hbm_ver_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct mei_device *dev = dev_get_drvdata(device); + struct hbm_version ver; + + mutex_lock(&dev->device_lock); + ver = dev->version; + mutex_unlock(&dev->device_lock); + + return sprintf(buf, "%u.%u\n", ver.major_version, ver.minor_version); +} +static DEVICE_ATTR_RO(hbm_ver); + +/** + * hbm_ver_drv_show - display HBM protocol version advertised by driver + * + * @device: device pointer + * @attr: attribute pointer + * @buf: char out buffer + * + * Return: number of the bytes printed into buf or error + */ +static ssize_t hbm_ver_drv_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u.%u\n", HBM_MAJOR_VERSION, HBM_MINOR_VERSION); +} +static DEVICE_ATTR_RO(hbm_ver_drv); + static struct attribute *mei_attrs[] = { &dev_attr_fw_status.attr, + &dev_attr_hbm_ver.attr, + &dev_attr_hbm_ver_drv.attr, NULL }; ATTRIBUTE_GROUPS(mei); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 1169fd9e7d02..d50f70b4a05e 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -109,6 +109,17 @@ enum mei_cb_file_ops { MEI_FOP_NOTIFY_STOP, }; +/** + * enum mei_cl_io_mode - io mode between driver and fw + * + * @MEI_CL_IO_TX_BLOCKING: send is blocking + * @MEI_CL_IO_TX_INTERNAL: internal communication between driver and FW + */ +enum mei_cl_io_mode { + MEI_CL_IO_TX_BLOCKING = BIT(0), + MEI_CL_IO_TX_INTERNAL = BIT(1), +}; + /* * Intel MEI message data struct */ @@ -169,6 +180,7 @@ struct mei_cl; * @fp: pointer to file structure * @status: io status of the cb * @internal: communication between driver and FW flag + * @blocking: transmission blocking mode * @completed: the transfer or reception has completed */ struct mei_cl_cb { @@ -180,6 +192,7 @@ struct mei_cl_cb { const struct file *fp; int status; u32 internal:1; + u32 blocking:1; u32 completed:1; }; @@ -304,7 +317,7 @@ void mei_cl_bus_rescan(struct mei_device *bus); void mei_cl_bus_rescan_work(struct work_struct *work); void mei_cl_bus_dev_fixup(struct mei_cl_device *dev); ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, - bool blocking); + unsigned int mode); ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); bool mei_cl_bus_rx_event(struct mei_cl *cl); bool mei_cl_bus_notify_event(struct mei_cl *cl); diff --git a/drivers/nfc/mei_phy.c b/drivers/nfc/mei_phy.c index 6f9563a96488..03139c5a05e4 100644 --- a/drivers/nfc/mei_phy.c +++ b/drivers/nfc/mei_phy.c @@ -297,10 +297,12 @@ static int mei_nfc_recv(struct nfc_mei_phy *phy, u8 *buf, size_t length) } -static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events, - void *context) +static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events) { - struct nfc_mei_phy *phy = context; + struct nfc_mei_phy *phy = mei_cldev_get_drvdata(cldev); + + if (!phy) + return; if (phy->hard_fault != 0) return; @@ -357,7 +359,7 @@ static int nfc_mei_phy_enable(void *phy_id) } r = mei_cldev_register_event_cb(phy->cldev, BIT(MEI_CL_EVENT_RX), - nfc_mei_event_cb, phy); + nfc_mei_event_cb); if (r) { pr_err("Event cb registration failed %d\n", r); goto err; diff --git a/drivers/nfc/microread/mei.c b/drivers/nfc/microread/mei.c index 3092501f26c4..eb5eddf1794e 100644 --- a/drivers/nfc/microread/mei.c +++ b/drivers/nfc/microread/mei.c @@ -82,28 +82,7 @@ static struct mei_cl_driver microread_driver = { .remove = microread_mei_remove, }; -static int microread_mei_init(void) -{ - int r; - - pr_debug(DRIVER_DESC ": %s\n", __func__); - - r = mei_cldev_driver_register(µread_driver); - if (r) { - pr_err(MICROREAD_DRIVER_NAME ": driver registration failed\n"); - return r; - } - - return 0; -} - -static void microread_mei_exit(void) -{ - mei_cldev_driver_unregister(µread_driver); -} - -module_init(microread_mei_init); -module_exit(microread_mei_exit); +module_mei_cl_driver(microread_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/nfc/pn544/mei.c b/drivers/nfc/pn544/mei.c index 46d0eb24eef9..ad57a8ec00d6 100644 --- a/drivers/nfc/pn544/mei.c +++ b/drivers/nfc/pn544/mei.c @@ -82,28 +82,7 @@ static struct mei_cl_driver pn544_driver = { .remove = pn544_mei_remove, }; -static int pn544_mei_init(void) -{ - int r; - - pr_debug(DRIVER_DESC ": %s\n", __func__); - - r = mei_cldev_driver_register(&pn544_driver); - if (r) { - pr_err(PN544_DRIVER_NAME ": driver registration failed\n"); - return r; - } - - return 0; -} - -static void pn544_mei_exit(void) -{ - mei_cldev_driver_unregister(&pn544_driver); -} - -module_init(pn544_mei_init); -module_exit(pn544_mei_exit); +module_mei_cl_driver(pn544_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index ba140eaee5c8..650f1b1797ad 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -35,6 +35,16 @@ config NVMEM_LPC18XX_EEPROM To compile this driver as a module, choose M here: the module will be called nvmem_lpc18xx_eeprom. +config NVMEM_LPC18XX_OTP + tristate "NXP LPC18XX OTP Memory Support" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say Y here to include support for NXP LPC18xx OTP memory found on + all LPC18xx and LPC43xx devices. + To compile this driver as a module, choose M here: the module + will be called nvmem_lpc18xx_otp. + config NVMEM_MXS_OCOTP tristate "Freescale MXS On-Chip OTP Memory Support" depends on ARCH_MXS || COMPILE_TEST @@ -80,6 +90,18 @@ config ROCKCHIP_EFUSE This driver can also be built as a module. If so, the module will be called nvmem_rockchip_efuse. +config NVMEM_BCM_OCOTP + tristate "Broadcom On-Chip OTP Controller support" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM + default ARCH_BCM_IPROC + help + Say y here to enable read/write access to the Broadcom OTP + controller. + + This driver can also be built as a module. If so, the module + will be called nvmem-bcm-ocotp. + config NVMEM_SUNXI_SID tristate "Allwinner SoCs SID support" depends on ARCH_SUNXI diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 8f942a0cdaec..86e45995fdad 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -6,10 +6,14 @@ obj-$(CONFIG_NVMEM) += nvmem_core.o nvmem_core-y := core.o # Devices +obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o +nvmem-bcm-ocotp-y := bcm-ocotp.o obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o nvmem-imx-ocotp-y := imx-ocotp.o obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o +obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o +nvmem_lpc18xx_otp-y := lpc18xx_otp.o obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o nvmem-mxs-ocotp-y := mxs-ocotp.o obj-$(CONFIG_MTK_EFUSE) += nvmem_mtk-efuse.o diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c new file mode 100644 index 000000000000..646cadbf1f93 --- /dev/null +++ b/drivers/nvmem/bcm-ocotp.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2016 Broadcom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> + +/* + * # of tries for OTP Status. The time to execute a command varies. The slowest + * commands are writes which also vary based on the # of bits turned on. Writing + * 0xffffffff takes ~3800 us. + */ +#define OTPC_RETRIES 5000 + +/* Sequence to enable OTP program */ +#define OTPC_PROG_EN_SEQ { 0xf, 0x4, 0x8, 0xd } + +/* OTPC Commands */ +#define OTPC_CMD_READ 0x0 +#define OTPC_CMD_OTP_PROG_ENABLE 0x2 +#define OTPC_CMD_OTP_PROG_DISABLE 0x3 +#define OTPC_CMD_PROGRAM 0xA + +/* OTPC Status Bits */ +#define OTPC_STAT_CMD_DONE BIT(1) +#define OTPC_STAT_PROG_OK BIT(2) + +/* OTPC register definition */ +#define OTPC_MODE_REG_OFFSET 0x0 +#define OTPC_MODE_REG_OTPC_MODE 0 +#define OTPC_COMMAND_OFFSET 0x4 +#define OTPC_COMMAND_COMMAND_WIDTH 6 +#define OTPC_CMD_START_OFFSET 0x8 +#define OTPC_CMD_START_START 0 +#define OTPC_CPU_STATUS_OFFSET 0xc +#define OTPC_CPUADDR_REG_OFFSET 0x28 +#define OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH 16 +#define OTPC_CPU_WRITE_REG_OFFSET 0x2c + +#define OTPC_CMD_MASK (BIT(OTPC_COMMAND_COMMAND_WIDTH) - 1) +#define OTPC_ADDR_MASK (BIT(OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH) - 1) + + +struct otpc_map { + /* in words. */ + u32 otpc_row_size; + /* 128 bit row / 4 words support. */ + u16 data_r_offset[4]; + /* 128 bit row / 4 words support. */ + u16 data_w_offset[4]; +}; + +static struct otpc_map otp_map = { + .otpc_row_size = 1, + .data_r_offset = {0x10}, + .data_w_offset = {0x2c}, +}; + +static struct otpc_map otp_map_v2 = { + .otpc_row_size = 2, + .data_r_offset = {0x10, 0x5c}, + .data_w_offset = {0x2c, 0x64}, +}; + +struct otpc_priv { + struct device *dev; + void __iomem *base; + struct otpc_map *map; + struct nvmem_config *config; +}; + +static inline void set_command(void __iomem *base, u32 command) +{ + writel(command & OTPC_CMD_MASK, base + OTPC_COMMAND_OFFSET); +} + +static inline void set_cpu_address(void __iomem *base, u32 addr) +{ + writel(addr & OTPC_ADDR_MASK, base + OTPC_CPUADDR_REG_OFFSET); +} + +static inline void set_start_bit(void __iomem *base) +{ + writel(1 << OTPC_CMD_START_START, base + OTPC_CMD_START_OFFSET); +} + +static inline void reset_start_bit(void __iomem *base) +{ + writel(0, base + OTPC_CMD_START_OFFSET); +} + +static inline void write_cpu_data(void __iomem *base, u32 value) +{ + writel(value, base + OTPC_CPU_WRITE_REG_OFFSET); +} + +static int poll_cpu_status(void __iomem *base, u32 value) +{ + u32 status; + u32 retries; + + for (retries = 0; retries < OTPC_RETRIES; retries++) { + status = readl(base + OTPC_CPU_STATUS_OFFSET); + if (status & value) + break; + udelay(1); + } + if (retries == OTPC_RETRIES) + return -EAGAIN; + + return 0; +} + +static int enable_ocotp_program(void __iomem *base) +{ + static const u32 vals[] = OTPC_PROG_EN_SEQ; + int i; + int ret; + + /* Write the magic sequence to enable programming */ + set_command(base, OTPC_CMD_OTP_PROG_ENABLE); + for (i = 0; i < ARRAY_SIZE(vals); i++) { + write_cpu_data(base, vals[i]); + set_start_bit(base); + ret = poll_cpu_status(base, OTPC_STAT_CMD_DONE); + reset_start_bit(base); + if (ret) + return ret; + } + + return poll_cpu_status(base, OTPC_STAT_PROG_OK); +} + +static int disable_ocotp_program(void __iomem *base) +{ + int ret; + + set_command(base, OTPC_CMD_OTP_PROG_DISABLE); + set_start_bit(base); + ret = poll_cpu_status(base, OTPC_STAT_PROG_OK); + reset_start_bit(base); + + return ret; +} + +static int bcm_otpc_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct otpc_priv *priv = context; + u32 *buf = val; + u32 bytes_read; + u32 address = offset / priv->config->word_size; + int i, ret; + + for (bytes_read = 0; bytes_read < bytes;) { + set_command(priv->base, OTPC_CMD_READ); + set_cpu_address(priv->base, address++); + set_start_bit(priv->base); + ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); + if (ret) { + dev_err(priv->dev, "otp read error: 0x%x", ret); + return -EIO; + } + + for (i = 0; i < priv->map->otpc_row_size; i++) { + *buf++ = readl(priv->base + + priv->map->data_r_offset[i]); + bytes_read += sizeof(*buf); + } + + reset_start_bit(priv->base); + } + + return 0; +} + +static int bcm_otpc_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct otpc_priv *priv = context; + u32 *buf = val; + u32 bytes_written; + u32 address = offset / priv->config->word_size; + int i, ret; + + if (offset % priv->config->word_size) + return -EINVAL; + + ret = enable_ocotp_program(priv->base); + if (ret) + return -EIO; + + for (bytes_written = 0; bytes_written < bytes;) { + set_command(priv->base, OTPC_CMD_PROGRAM); + set_cpu_address(priv->base, address++); + for (i = 0; i < priv->map->otpc_row_size; i++) { + writel(*buf, priv->base + priv->map->data_r_offset[i]); + buf++; + bytes_written += sizeof(*buf); + } + set_start_bit(priv->base); + ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); + reset_start_bit(priv->base); + if (ret) { + dev_err(priv->dev, "otp write error: 0x%x", ret); + return -EIO; + } + } + + disable_ocotp_program(priv->base); + + return 0; +} + +static struct nvmem_config bcm_otpc_nvmem_config = { + .name = "bcm-ocotp", + .read_only = false, + .word_size = 4, + .stride = 4, + .owner = THIS_MODULE, + .reg_read = bcm_otpc_read, + .reg_write = bcm_otpc_write, +}; + +static const struct of_device_id bcm_otpc_dt_ids[] = { + { .compatible = "brcm,ocotp" }, + { .compatible = "brcm,ocotp-v2" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_otpc_dt_ids); + +static int bcm_otpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node; + struct resource *res; + struct otpc_priv *priv; + struct nvmem_device *nvmem; + int err; + u32 num_words; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (of_device_is_compatible(dev->of_node, "brcm,ocotp")) + priv->map = &otp_map; + else if (of_device_is_compatible(dev->of_node, "brcm,ocotp-v2")) + priv->map = &otp_map_v2; + else { + dev_err(&pdev->dev, + "%s otpc config map not defined\n", __func__); + return -EINVAL; + } + + /* Get OTP base address register. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(priv->base); + } + + /* Enable CPU access to OTPC. */ + writel(readl(priv->base + OTPC_MODE_REG_OFFSET) | + BIT(OTPC_MODE_REG_OTPC_MODE), + priv->base + OTPC_MODE_REG_OFFSET); + reset_start_bit(priv->base); + + /* Read size of memory in words. */ + err = of_property_read_u32(dn, "brcm,ocotp-size", &num_words); + if (err) { + dev_err(dev, "size parameter not specified\n"); + return -EINVAL; + } else if (num_words == 0) { + dev_err(dev, "size must be > 0\n"); + return -EINVAL; + } + + bcm_otpc_nvmem_config.size = 4 * num_words; + bcm_otpc_nvmem_config.dev = dev; + bcm_otpc_nvmem_config.priv = priv; + + if (of_device_is_compatible(dev->of_node, "brcm,ocotp-v2")) { + bcm_otpc_nvmem_config.word_size = 8; + bcm_otpc_nvmem_config.stride = 8; + } + + priv->config = &bcm_otpc_nvmem_config; + + nvmem = nvmem_register(&bcm_otpc_nvmem_config); + if (IS_ERR(nvmem)) { + dev_err(dev, "error registering nvmem config\n"); + return PTR_ERR(nvmem); + } + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int bcm_otpc_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static struct platform_driver bcm_otpc_driver = { + .probe = bcm_otpc_probe, + .remove = bcm_otpc_remove, + .driver = { + .name = "brcm-otpc", + .of_match_table = bcm_otpc_dt_ids, + }, +}; +module_platform_driver(bcm_otpc_driver); + +MODULE_DESCRIPTION("Broadcom OTPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/lpc18xx_otp.c b/drivers/nvmem/lpc18xx_otp.c new file mode 100644 index 000000000000..be8d07403ffc --- /dev/null +++ b/drivers/nvmem/lpc18xx_otp.c @@ -0,0 +1,124 @@ +/* + * NXP LPC18xx/43xx OTP memory NVMEM driver + * + * Copyright (c) 2016 Joachim Eastwood <manabian@gmail.com> + * + * Based on the imx ocotp driver, + * Copyright (c) 2015 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * TODO: add support for writing OTP register via API in boot ROM. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* + * LPC18xx OTP memory contains 4 banks with 4 32-bit words. Bank 0 starts + * at offset 0 from the base. + * + * Bank 0 contains the part ID for Flashless devices and is reseverd for + * devices with Flash. + * Bank 1/2 is generale purpose or AES key storage for secure devices. + * Bank 3 contains control data, USB ID and generale purpose words. + */ +#define LPC18XX_OTP_NUM_BANKS 4 +#define LPC18XX_OTP_WORDS_PER_BANK 4 +#define LPC18XX_OTP_WORD_SIZE sizeof(u32) +#define LPC18XX_OTP_SIZE (LPC18XX_OTP_NUM_BANKS * \ + LPC18XX_OTP_WORDS_PER_BANK * \ + LPC18XX_OTP_WORD_SIZE) + +struct lpc18xx_otp { + void __iomem *base; +}; + +static int lpc18xx_otp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct lpc18xx_otp *otp = context; + unsigned int count = bytes >> 2; + u32 index = offset >> 2; + u32 *buf = val; + int i; + + if (count > (LPC18XX_OTP_SIZE - index)) + count = LPC18XX_OTP_SIZE - index; + + for (i = index; i < (index + count); i++) + *buf++ = readl(otp->base + i * LPC18XX_OTP_WORD_SIZE); + + return 0; +} + +static struct nvmem_config lpc18xx_otp_nvmem_config = { + .name = "lpc18xx-otp", + .read_only = true, + .word_size = LPC18XX_OTP_WORD_SIZE, + .stride = LPC18XX_OTP_WORD_SIZE, + .owner = THIS_MODULE, + .reg_read = lpc18xx_otp_read, +}; + +static int lpc18xx_otp_probe(struct platform_device *pdev) +{ + struct nvmem_device *nvmem; + struct lpc18xx_otp *otp; + struct resource *res; + + otp = devm_kzalloc(&pdev->dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + otp->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(otp->base)) + return PTR_ERR(otp->base); + + lpc18xx_otp_nvmem_config.size = LPC18XX_OTP_SIZE; + lpc18xx_otp_nvmem_config.dev = &pdev->dev; + lpc18xx_otp_nvmem_config.priv = otp; + + nvmem = nvmem_register(&lpc18xx_otp_nvmem_config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int lpc18xx_otp_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static const struct of_device_id lpc18xx_otp_dt_ids[] = { + { .compatible = "nxp,lpc1850-otp" }, + { }, +}; +MODULE_DEVICE_TABLE(of, lpc18xx_otp_dt_ids); + +static struct platform_driver lpc18xx_otp_driver = { + .probe = lpc18xx_otp_probe, + .remove = lpc18xx_otp_remove, + .driver = { + .name = "lpc18xx_otp", + .of_match_table = lpc18xx_otp_dt_ids, + }, +}; +module_platform_driver(lpc18xx_otp_driver); + +MODULE_AUTHOR("Joachim Eastwoood <manabian@gmail.com>"); +MODULE_DESCRIPTION("NXP LPC18xx OTP NVMEM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 318dbb51e7a2..0d4cda7050e0 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -58,6 +58,41 @@ struct of_overlay { static int of_overlay_apply_one(struct of_overlay *ov, struct device_node *target, const struct device_node *overlay); +static BLOCKING_NOTIFIER_HEAD(of_overlay_chain); + +int of_overlay_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&of_overlay_chain, nb); +} +EXPORT_SYMBOL_GPL(of_overlay_notifier_register); + +int of_overlay_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&of_overlay_chain, nb); +} +EXPORT_SYMBOL_GPL(of_overlay_notifier_unregister); + +static int of_overlay_notify(struct of_overlay *ov, + enum of_overlay_notify_action action) +{ + struct of_overlay_notify_data nd; + int i, ret; + + for (i = 0; i < ov->count; i++) { + struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i]; + + nd.target = ovinfo->target; + nd.overlay = ovinfo->overlay; + + ret = blocking_notifier_call_chain(&of_overlay_chain, + action, &nd); + if (ret) + return notifier_to_errno(ret); + } + + return 0; +} + static int of_overlay_apply_single_property(struct of_overlay *ov, struct device_node *target, struct property *prop) { @@ -368,6 +403,13 @@ int of_overlay_create(struct device_node *tree) goto err_free_idr; } + err = of_overlay_notify(ov, OF_OVERLAY_PRE_APPLY); + if (err < 0) { + pr_err("%s: Pre-apply notifier failed (err=%d)\n", + __func__, err); + goto err_free_idr; + } + /* apply the overlay */ err = of_overlay_apply(ov); if (err) @@ -382,6 +424,8 @@ int of_overlay_create(struct device_node *tree) /* add to the tail of the overlay list */ list_add_tail(&ov->node, &ov_list); + of_overlay_notify(ov, OF_OVERLAY_POST_APPLY); + mutex_unlock(&of_mutex); return id; @@ -498,9 +542,10 @@ int of_overlay_destroy(int id) goto out; } - + of_overlay_notify(ov, OF_OVERLAY_PRE_REMOVE); list_del(&ov->node); __of_changeset_revert(&ov->cset); + of_overlay_notify(ov, OF_OVERLAY_POST_REMOVE); of_free_overlay_info(ov); idr_remove(&ov_idr, id); of_changeset_destroy(&ov->cset); diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 1aba2c74160e..2b21033f11f0 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -308,10 +308,8 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, * returns a small amount, then there's no need to pin that * much memory to the process. */ - down_read(¤t->mm->mmap_sem); - ret = get_user_pages(address, 1, is_write ? 0 : FOLL_WRITE, - &page, NULL); - up_read(¤t->mm->mmap_sem); + ret = get_user_pages_unlocked(address, 1, &page, + is_write ? 0 : FOLL_WRITE); if (ret < 0) break; diff --git a/drivers/s390/char/sclp_ctl.c b/drivers/s390/char/sclp_ctl.c index 554eaa1e347d..78a7e4f94721 100644 --- a/drivers/s390/char/sclp_ctl.c +++ b/drivers/s390/char/sclp_ctl.c @@ -10,7 +10,7 @@ #include <linux/uaccess.h> #include <linux/miscdevice.h> #include <linux/gfp.h> -#include <linux/module.h> +#include <linux/init.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <asm/compat.h> @@ -126,4 +126,4 @@ static struct miscdevice sclp_ctl_device = { .name = "sclp", .fops = &sclp_ctl_fops, }; -module_misc_device(sclp_ctl_device); +builtin_misc_device(sclp_ctl_device); diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 86b996c702a0..75cf0691e6c5 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -1,11 +1,11 @@ /* - * Thunderbolt Cactus Ridge driver - NHI registers + * Thunderbolt driver - NHI registers * * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> */ -#ifndef DSL3510_REGS_H_ -#define DSL3510_REGS_H_ +#ifndef NHI_REGS_H_ +#define NHI_REGS_H_ #include <linux/types.h> diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c index 630bd189f167..e0af52265511 100644 --- a/drivers/watchdog/mei_wdt.c +++ b/drivers/watchdog/mei_wdt.c @@ -501,10 +501,8 @@ static void mei_wdt_notify_event(struct mei_cl_device *cldev) * * @cldev: bus device * @events: event mask - * @context: callback context */ -static void mei_wdt_event(struct mei_cl_device *cldev, - u32 events, void *context) +static void mei_wdt_event(struct mei_cl_device *cldev, u32 events) { if (events & BIT(MEI_CL_EVENT_RX)) mei_wdt_event_rx(cldev); @@ -626,7 +624,7 @@ static int mei_wdt_probe(struct mei_cl_device *cldev, ret = mei_cldev_register_event_cb(wdt->cldev, BIT(MEI_CL_EVENT_RX) | BIT(MEI_CL_EVENT_NOTIF), - mei_wdt_event, NULL); + mei_wdt_event); /* on legacy devices notification is not supported * this doesn't fail the registration for RX event @@ -699,25 +697,7 @@ static struct mei_cl_driver mei_wdt_driver = { .remove = mei_wdt_remove, }; -static int __init mei_wdt_init(void) -{ - int ret; - - ret = mei_cldev_driver_register(&mei_wdt_driver); - if (ret) { - pr_err(KBUILD_MODNAME ": module registration failed\n"); - return ret; - } - return 0; -} - -static void __exit mei_wdt_exit(void) -{ - mei_cldev_driver_unregister(&mei_wdt_driver); -} - -module_init(mei_wdt_init); -module_exit(mei_wdt_exit); +module_mei_cl_driver(mei_wdt_driver); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL"); |