Commit 73616367 authored by Dan Williams's avatar Dan Williams

Merge branch 'for-4.12/dax' into libnvdimm-for-next

parents d5483fed 1ef97fe4
......@@ -128,9 +128,6 @@
</sect1>
<sect1 id="Device_model_support"><title>Device model support</title>
!Idrivers/rapidio/rio-driver.c
</sect1>
<sect1 id="Sysfs_support"><title>Sysfs support</title>
!Idrivers/rapidio/rio-sysfs.c
</sect1>
<sect1 id="PPC32_support"><title>PPC32 support</title>
!Iarch/powerpc/sysdev/fsl_rio.c
......
DT bindings for the Hitachi HD44780 Character LCD Controller
The Hitachi HD44780 Character LCD Controller is commonly used on character LCDs
that can display one or more lines of text. It exposes an M6800 bus interface,
which can be used in either 4-bit or 8-bit mode.
Required properties:
- compatible: Must contain "hit,hd44780",
- data-gpios: Must contain an array of either 4 or 8 GPIO specifiers,
referring to the GPIO pins connected to the data signal lines DB0-DB7
(8-bit mode) or DB4-DB7 (4-bit mode) of the LCD Controller's bus interface,
- enable-gpios: Must contain a GPIO specifier, referring to the GPIO pin
connected to the "E" (Enable) signal line of the LCD Controller's bus
interface,
- rs-gpios: Must contain a GPIO specifier, referring to the GPIO pin
connected to the "RS" (Register Select) signal line of the LCD Controller's
bus interface,
- display-height: Height of the display, in character cells,
- display-width: Width of the display, in character cells.
Optional properties:
- rw-gpios: Must contain a GPIO specifier, referring to the GPIO pin
connected to the "RW" (Read/Write) signal line of the LCD Controller's bus
interface,
- backlight-gpios: Must contain a GPIO specifier, referring to the GPIO pin
used for enabling the LCD's backlight,
- internal-buffer-width: Internal buffer width (default is 40 for displays
with 1 or 2 lines, and display-width for displays with more than 2 lines).
Example:
auxdisplay {
compatible = "hit,hd44780";
data-gpios = <&hc595 0 GPIO_ACTIVE_HIGH>,
<&hc595 1 GPIO_ACTIVE_HIGH>,
<&hc595 2 GPIO_ACTIVE_HIGH>,
<&hc595 3 GPIO_ACTIVE_HIGH>;
enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>;
rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>;
display-height = <2>;
display-width = <16>;
};
......@@ -186,6 +186,7 @@ Optional properties:
otherwise full reconfiguration is done.
- external-fpga-config : boolean, set if the FPGA has already been configured
prior to OS boot up.
- encrypted-fpga-config : boolean, set if the bitstream is encrypted
- region-unfreeze-timeout-us : The maximum time in microseconds to wait for
bridges to successfully become enabled after the region has been
programmed.
......
Lattice iCE40 FPGA Manager
Required properties:
- compatible: Should contain "lattice,ice40-fpga-mgr"
- reg: SPI chip select
- spi-max-frequency: Maximum SPI frequency (>=1000000, <=25000000)
- cdone-gpios: GPIO input connected to CDONE pin
- reset-gpios: Active-low GPIO output connected to CRESET_B pin. Note
that unless the GPIO is held low during startup, the
FPGA will enter Master SPI mode and drive SCK with a
clock signal potentially jamming other devices on the
bus until the firmware is loaded.
Example:
fpga: fpga@0 {
compatible = "lattice,ice40-fpga-mgr";
reg = <0>;
spi-max-frequency = <1000000>;
cdone-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio 22 GPIO_ACTIVE_LOW>;
};
This diff is collapsed.
......@@ -2,7 +2,11 @@
- This file
w1_therm
- The Maxim/Dallas Semiconductor ds18*20 temperature sensor.
w1_ds2413
- The Maxim/Dallas Semiconductor ds2413 dual channel addressable switch.
w1_ds2423
- The Maxim/Dallas Semiconductor ds2423 counter device.
w1_ds2438
- The Maxim/Dallas Semiconductor ds2438 smart battery monitor.
w1_ds28e04
- The Maxim/Dallas Semiconductor ds28e04 eeprom.
Kernel driver w1_ds2413
=======================
Supported chips:
* Maxim DS2413 1-Wire Dual Channel Addressable Switch
supported family codes:
W1_FAMILY_DS2413 0x3A
Author: Mariusz Bialonczyk <manio@skyboo.net>
Description
-----------
The DS2413 chip has two open-drain outputs (PIO A and PIO B).
Support is provided through the sysfs files "output" and "state".
Reading state
-------------
The "state" file provides one-byte value which is in the same format as for
the chip PIO_ACCESS_READ command (refer the datasheet for details):
Bit 0: PIOA Pin State
Bit 1: PIOA Output Latch State
Bit 2: PIOB Pin State
Bit 3: PIOB Output Latch State
Bit 4-7: Complement of Bit 3 to Bit 0 (verified by the kernel module)
This file is readonly.
Writing output
--------------
You can set the PIO pins using the "output" file.
It is writable, you can write one-byte value to this sysfs file.
Similarly the byte format is the same as for the PIO_ACCESS_WRITE command:
Bit 0: PIOA
Bit 1: PIOB
Bit 2-7: No matter (driver will set it to "1"s)
The chip has some kind of basic protection against transmission errors.
When reading the state, there is a four complement bits.
The driver is checking this complement, and when it is wrong then it is
returning I/O error.
When writing output, the master must repeat the PIO Output Data byte in
its inverted form and it is waiting for a confirmation.
If the write is unsuccessful for three times, the write also returns
I/O error.
Kernel driver w1_ds2438
=======================
Supported chips:
* Maxim DS2438 Smart Battery Monitor
supported family codes:
W1_FAMILY_DS2438 0x26
Author: Mariusz Bialonczyk <manio@skyboo.net>
Description
-----------
The DS2438 chip provides several functions that are desirable to carry in
a battery pack. It also has a 40 bytes of nonvolatile EEPROM.
Because the ability of temperature, current and voltage measurement, the chip
is also often used in weather stations and applications such as: rain gauge,
wind speed/direction measuring, humidity sensing, etc.
Current support is provided through the following sysfs files (all files
except "iad" are readonly):
"iad"
-----
This file controls the 'Current A/D Control Bit' (IAD) in the
Status/Configuration Register.
Writing a zero value will clear the IAD bit and disables the current
measurements.
Writing value "1" is setting the IAD bit (enables the measurements).
The IAD bit is enabled by default in the DS2438.
When writing to sysfs file bits 2-7 are ignored, so it's safe to write ASCII.
An I/O error is returned when there is a problem setting the new value.
"page0"
-------
This file provides full 8 bytes of the chip Page 0 (00h).
This page contains the most frequently accessed information of the DS2438.
Internally when this file is read, the additional CRC byte is also obtained
from the slave device. If it is correct, the 8 bytes page data are passed
to userspace, otherwise an I/O error is returned.
"temperature"
-------------
Opening and reading this file initiates the CONVERT_T (temperature conversion)
command of the chip, afterwards the temperature is read from the device
registers and provided as an ASCII decimal value.
Important: The returned value has to be divided by 256 to get a real
temperature in degrees Celsius.
"vad", "vdd"
------------
Opening and reading this file initiates the CONVERT_V (voltage conversion)
command of the chip.
Depending on a sysfs filename a different input for the A/D will be selected:
vad: general purpose A/D input (VAD)
vdd: battery input (VDD)
After the voltage conversion the value is returned as decimal ASCII.
Note: The value is in mV, so to get a volts the value has to be divided by 10.
......@@ -5109,7 +5109,6 @@ F: include/uapi/linux/firewire*.h
F: tools/firewire/
FIRMWARE LOADER (request_firmware)
M: Ming Lei <ming.lei@canonical.com>
M: Luis R. Rodriguez <mcgrof@kernel.org>
L: linux-kernel@vger.kernel.org
S: Maintained
......
......@@ -210,6 +210,28 @@ static struct ep93xx_eth_data __initdata ts72xx_eth_data = {
.phy_id = 1,
};
#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX)
/* Relative to EP93XX_CS1_PHYS_BASE */
#define TS73XX_FPGA_LOADER_BASE 0x03c00000
static struct resource ts73xx_fpga_resources[] = {
{
.start = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE,
.end = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE + 1,
.flags = IORESOURCE_MEM,
},
};
static struct platform_device ts73xx_fpga_device = {
.name = "ts73xx-fpga-mgr",
.id = -1,
.resource = ts73xx_fpga_resources,
.num_resources = ARRAY_SIZE(ts73xx_fpga_resources),
};
#endif
static void __init ts72xx_init_machine(void)
{
ep93xx_init_devices();
......@@ -218,6 +240,10 @@ static void __init ts72xx_init_machine(void)
platform_device_register(&ts72xx_wdt_device);
ep93xx_register_eth(&ts72xx_eth_data, 1);
#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX)
if (board_is_ts7300())
platform_device_register(&ts73xx_fpga_device);
#endif
}
MACHINE_START(TS72XX, "Technologic Systems TS-72xx SBC")
......
......@@ -284,6 +284,7 @@ config CPM2
config AXON_RAM
tristate "Axon DDR2 memory device driver"
depends on PPC_IBM_CELL_BLADE && BLOCK
select DAX
default m
help
It registers one block device per Axon's DDR2 memory bank found
......
......@@ -25,6 +25,7 @@
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/dax.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
......@@ -62,6 +63,7 @@ static int azfs_major, azfs_minor;
struct axon_ram_bank {
struct platform_device *device;
struct gendisk *disk;
struct dax_device *dax_dev;
unsigned int irq_id;
unsigned long ph_addr;
unsigned long io_addr;
......@@ -137,25 +139,32 @@ axon_ram_make_request(struct request_queue *queue, struct bio *bio)
return BLK_QC_T_NONE;
}
/**
* axon_ram_direct_access - direct_access() method for block device
* @device, @sector, @data: see block_device_operations method
*/
static const struct block_device_operations axon_ram_devops = {
.owner = THIS_MODULE,
};
static long
axon_ram_direct_access(struct block_device *device, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
__axon_ram_direct_access(struct axon_ram_bank *bank, pgoff_t pgoff, long nr_pages,
void **kaddr, pfn_t *pfn)
{
struct axon_ram_bank *bank = device->bd_disk->private_data;
loff_t offset = (loff_t)sector << AXON_RAM_SECTOR_SHIFT;
resource_size_t offset = pgoff * PAGE_SIZE;
*kaddr = (void *) bank->io_addr + offset;
*pfn = phys_to_pfn_t(bank->ph_addr + offset, PFN_DEV);
return bank->size - offset;
return (bank->size - offset) / PAGE_SIZE;
}
static const struct block_device_operations axon_ram_devops = {
.owner = THIS_MODULE,
.direct_access = axon_ram_direct_access
static long
axon_ram_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, long nr_pages,
void **kaddr, pfn_t *pfn)
{
struct axon_ram_bank *bank = dax_get_private(dax_dev);
return __axon_ram_direct_access(bank, pgoff, nr_pages, kaddr, pfn);
}
static const struct dax_operations axon_ram_dax_ops = {
.direct_access = axon_ram_dax_direct_access,
};
/**
......@@ -219,6 +228,7 @@ static int axon_ram_probe(struct platform_device *device)
goto failed;
}
bank->disk->major = azfs_major;
bank->disk->first_minor = azfs_minor;
bank->disk->fops = &axon_ram_devops;
......@@ -227,6 +237,13 @@ static int axon_ram_probe(struct platform_device *device)
sprintf(bank->disk->disk_name, "%s%d",
AXON_RAM_DEVICE_NAME, axon_ram_bank_id);
bank->dax_dev = alloc_dax(bank, bank->disk->disk_name,
&axon_ram_dax_ops);
if (!bank->dax_dev) {
rc = -ENOMEM;
goto failed;
}
bank->disk->queue = blk_alloc_queue(GFP_KERNEL);
if (bank->disk->queue == NULL) {
dev_err(&device->dev, "Cannot register disk queue\n");
......@@ -278,6 +295,8 @@ static int axon_ram_probe(struct platform_device *device)
del_gendisk(bank->disk);
put_disk(bank->disk);
}
kill_dax(bank->dax_dev);
put_dax(bank->dax_dev);
device->dev.platform_data = NULL;
if (bank->io_addr != 0)
iounmap((void __iomem *) bank->io_addr);
......@@ -300,6 +319,8 @@ axon_ram_remove(struct platform_device *device)
device_remove_file(&device->dev, &dev_attr_ecc);
free_irq(bank->irq_id, device);
kill_dax(bank->dax_dev);
put_dax(bank->dax_dev);
del_gendisk(bank->disk);
put_disk(bank->disk);
iounmap((void __iomem *) bank->io_addr);
......
......@@ -25,7 +25,7 @@
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/clockchips.h>
#include <linux/hyperv.h>
#ifdef CONFIG_X86_64
......
......@@ -44,11 +44,6 @@ static inline void arch_memcpy_to_pmem(void *dst, const void *src, size_t n)
BUG();
}
static inline int arch_memcpy_from_pmem(void *dst, const void *src, size_t n)
{
return memcpy_mcsafe(dst, src, n);
}
/**
* arch_wb_cache_pmem - write back a cache range with CLWB
* @vaddr: virtual start address
......
......@@ -79,6 +79,7 @@ int strcmp(const char *cs, const char *ct);
#define memset(s, c, n) __memset(s, c, n)
#endif
#define __HAVE_ARCH_MEMCPY_MCSAFE 1
__must_check int memcpy_mcsafe_unrolled(void *dst, const void *src, size_t cnt);
DECLARE_STATIC_KEY_FALSE(mcsafe_key);
......
......@@ -124,7 +124,7 @@
* Recommend using hypercall for address space switches rather
* than MOV to CR3 instruction
*/
#define HV_X64_MWAIT_RECOMMENDED (1 << 0)
#define HV_X64_AS_SWITCH_RECOMMENDED (1 << 0)
/* Recommend using hypercall for local TLB flushes rather
* than INVLPG or MOV to CR3 instructions */
#define HV_X64_LOCAL_TLB_FLUSH_RECOMMENDED (1 << 1)
......@@ -147,6 +147,11 @@
*/
#define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5)
/*
* Virtual APIC support
*/
#define HV_X64_DEPRECATING_AEOI_RECOMMENDED (1 << 9)
/*
* Crash notification flag.
*/
......
......@@ -6,6 +6,7 @@ menuconfig BLOCK
default y
select SBITMAP
select SRCU
select DAX
help
Provide block layer support for the kernel.
......
......@@ -16,7 +16,6 @@
#include <linux/kmod.h>
#include <linux/ctype.h>
#include <linux/genhd.h>
#include <linux/dax.h>
#include <linux/blktrace_api.h>
#include "partitions/check.h"
......@@ -631,24 +630,12 @@ int invalidate_partitions(struct gendisk *disk, struct block_device *bdev)
return 0;
}
static struct page *read_pagecache_sector(struct block_device *bdev, sector_t n)
{
struct address_space *mapping = bdev->bd_inode->i_mapping;
return read_mapping_page(mapping, (pgoff_t)(n >> (PAGE_SHIFT-9)),
NULL);
}
unsigned char *read_dev_sector(struct block_device *bdev, sector_t n, Sector *p)
{
struct address_space *mapping = bdev->bd_inode->i_mapping;
struct page *page;
/* don't populate page cache for dax capable devices */
if (IS_DAX(bdev->bd_inode))
page = read_dax_sector(bdev, n);
else
page = read_pagecache_sector(bdev, n);
page = read_mapping_page(mapping, (pgoff_t)(n >> (PAGE_SHIFT-9)), NULL);
if (!IS_ERR(page)) {
if (PageError(page))
goto fail;
......
......@@ -71,7 +71,7 @@ obj-$(CONFIG_PARPORT) += parport/
obj-$(CONFIG_NVM) += lightnvm/
obj-y += base/ block/ misc/ mfd/ nfc/
obj-$(CONFIG_LIBNVDIMM) += nvdimm/
obj-$(CONFIG_DEV_DAX) += dax/
obj-$(CONFIG_DAX) += dax/
obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf/
obj-$(CONFIG_NUBUS) += nubus/
obj-y += macintosh/
......
......@@ -1849,8 +1849,7 @@ static int acpi_nfit_blk_single_io(struct nfit_blk *nfit_blk,
mmio_flush_range((void __force *)
mmio->addr.aperture + offset, c);
memcpy_from_pmem(iobuf + copied,
mmio->addr.aperture + offset, c);
memcpy(iobuf + copied, mmio->addr.aperture + offset, c);
}
copied += c;
......
......@@ -22,7 +22,7 @@ config ANDROID_BINDER_IPC
config ANDROID_BINDER_DEVICES
string "Android Binder devices"
depends on ANDROID_BINDER_IPC
default "binder"
default "binder,hwbinder"
---help---
Default value for the binder.devices parameter.
......
......@@ -13,8 +13,22 @@ menuconfig AUXDISPLAY
If you say N, all options in this submenu will be skipped and disabled.
config CHARLCD
tristate "Character LCD core support" if COMPILE_TEST
if AUXDISPLAY
config HD44780
tristate "HD44780 Character LCD support"
depends on GPIOLIB || COMPILE_TEST
select CHARLCD
---help---
Enable support for Character LCDs using a HD44780 controller.
The LCD is accessible through the /dev/lcd char device (10, 156).
This code can either be compiled as a module, or linked into the
kernel and started at boot.
If you don't understand what all this is about, say N.
config KS0108
tristate "KS0108 LCD Controller"
depends on PARPORT_PC
......
......@@ -2,7 +2,9 @@
# Makefile for the kernel auxiliary displays device drivers.
#
obj-$(CONFIG_CHARLCD) += charlcd.o
obj-$(CONFIG_KS0108) += ks0108.o
obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
obj-$(CONFIG_HD44780) += hd44780.o
obj-$(CONFIG_HT16K33) += ht16k33.o
This diff is collapsed.
/*
* HD44780 Character LCD driver for Linux
*
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
* Copyright (C) 2016-2017 Glider bvba
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <misc/charlcd.h>
enum hd44780_pin {
/* Order does matter due to writing to GPIO array subsets! */
PIN_DATA0, /* Optional */
PIN_DATA1, /* Optional */
PIN_DATA2, /* Optional */
PIN_DATA3, /* Optional */
PIN_DATA4,
PIN_DATA5,
PIN_DATA6,
PIN_DATA7,
PIN_CTRL_RS,
PIN_CTRL_RW, /* Optional */
PIN_CTRL_E,
PIN_CTRL_BL, /* Optional */
PIN_NUM
};
struct hd44780 {
struct gpio_desc *pins[PIN_NUM];
};
static void hd44780_backlight(struct charlcd *lcd, int on)
{
struct hd44780 *hd = lcd->drvdata;
if (hd->pins[PIN_CTRL_BL])
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
}
static void hd44780_strobe_gpio(struct hd44780 *hd)
{
/* Maintain the data during 20 us before the strobe */
udelay(20);
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1);
/* Maintain the strobe during 40 us */
udelay(40);
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0);
}
/* write to an LCD panel register in 8 bit GPIO mode */
static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs)
{
int values[10]; /* for DATA[0-7], RS, RW */
unsigned int i, n;
for (i = 0; i < 8; i++)
values[PIN_DATA0 + i] = !!(val & BIT(i));
values[PIN_CTRL_RS] = rs;
n = 9;
if (hd->pins[PIN_CTRL_RW]) {
values[PIN_CTRL_RW] = 0;
n++;
}
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values);
hd44780_strobe_gpio(hd);
}
/* write to an LCD panel register in 4 bit GPIO mode */
static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
{
int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
unsigned int i, n;
/* High nibble + RS, RW */
for (i = 4; i < 8; i++)
values[PIN_DATA0 + i] = !!(val & BIT(i));
values[PIN_CTRL_RS] = rs;
n = 5;
if (hd->pins[PIN_CTRL_RW]) {
values[PIN_CTRL_RW] = 0;
n++;
}
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
&values[PIN_DATA4]);
hd44780_strobe_gpio(hd);
/* Low nibble */
for (i = 0; i < 4; i++)
values[PIN_DATA4 + i] = !!(val & BIT(i));
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
&values[PIN_DATA4]);
hd44780_strobe_gpio(hd);
}
/* Send a command to the LCD panel in 8 bit GPIO mode */
static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
{
struct hd44780 *hd = lcd->drvdata;
hd44780_write_gpio8(hd, cmd, 0);
/* The shortest command takes at least 120 us */
udelay(120);
}
/* Send data to the LCD panel in 8 bit GPIO mode */
static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
{
struct hd44780 *hd = lcd->drvdata;
hd44780_write_gpio8(hd, data, 1);
/* The shortest data takes at least 45 us */
udelay(45);
}
static const struct charlcd_ops hd44780_ops_gpio8 = {
.write_cmd = hd44780_write_cmd_gpio8,
.write_data = hd44780_write_data_gpio8,
.backlight = hd44780_backlight,
};
/* Send a command to the LCD panel in 4 bit GPIO mode */
static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
{
struct hd44780 *hd = lcd->drvdata;
hd44780_write_gpio4(hd, cmd, 0);
/* The shortest command takes at least 120 us */
udelay(120);
}
/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */
static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd)
{
int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
struct hd44780 *hd = lcd->drvdata;
unsigned int i, n;
/* Command nibble + RS, RW */
for (i = 0; i < 4; i++)
values[PIN_DATA4 + i] = !!(cmd & BIT(i));
values[PIN_CTRL_RS] = 0;
n = 5;
if (hd->pins[PIN_CTRL_RW]) {
values[PIN_CTRL_RW] = 0;
n++;
}
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
&values[PIN_DATA4]);
hd44780_strobe_gpio(hd);
}
/* Send data to the LCD panel in 4 bit GPIO mode */
static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
{
struct hd44780 *hd = lcd->drvdata;
hd44780_write_gpio4(hd, data, 1);
/* The shortest data takes at least 45 us */
udelay(45);
}
static const struct charlcd_ops hd44780_ops_gpio4 = {
.write_cmd = hd44780_write_cmd_gpio4,
.write_cmd_raw4 = hd44780_write_cmd_raw_gpio4,
.write_data = hd44780_write_data_gpio4,
.backlight = hd44780_backlight,
};
static int hd44780_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
unsigned int i, base;
struct charlcd *lcd;
struct hd44780 *hd;
int ifwidth, ret;
/* Required pins */
ifwidth = gpiod_count(dev, "data");
if (ifwidth < 0)
return ifwidth;
switch (ifwidth) {
case 4:
base = PIN_DATA4;
break;
case 8:
base = PIN_DATA0;
break;
default:
return -EINVAL;
}
lcd = charlcd_alloc(sizeof(struct hd44780));
if (!lcd)
return -ENOMEM;
hd = lcd->drvdata;
for (i = 0; i < ifwidth; i++) {
hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i,
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[base + i])) {
ret = PTR_ERR(hd->pins[base + i]);
goto fail;
}
}
hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_E])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_E]);
goto fail;
}
hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH);
if (IS_ERR(hd->pins[PIN_CTRL_RS])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RS]);
goto fail;
}
/* Optional pins */
hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw",
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
goto fail;
}
hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight",
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_BL])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_BL]);
goto fail;
}
/* Required properties */
ret = device_property_read_u32(dev, "display-height", &lcd->height);
if (ret)
goto fail;
ret = device_property_read_u32(dev, "display-width", &lcd->width);
if (ret)
goto fail;
/*
* On displays with more than two rows, the internal buffer width is
* usually equal to the display width
*/
if (lcd->height > 2)
lcd->bwidth = lcd->width;
/* Optional properties */
device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth);
lcd->ifwidth = ifwidth;
lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
ret = charlcd_register(lcd);
if (ret)
goto fail;
platform_set_drvdata(pdev, lcd);
return 0;
fail:
kfree(lcd);
return ret;
}
static int hd44780_remove(struct platform_device *pdev)
{
struct charlcd *lcd = platform_get_drvdata(pdev);
charlcd_unregister(lcd);
return 0;
}
static const struct of_device_id hd44780_of_match[] = {
{ .compatible = "hit,hd44780" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, hd44780_of_match);
static struct platform_driver hd44780_driver = {
.probe = hd44780_probe,
.remove = hd44780_remove,
.driver = {
.name = "hd44780",
.of_match_table = hd44780_of_match,
},
};
module_platform_driver(hd44780_driver);
MODULE_DESCRIPTION("HD44780 Character LCD driver");
MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
MODULE_LICENSE("GPL");
......@@ -339,6 +339,7 @@ config BLK_DEV_SX8
config BLK_DEV_RAM
tristate "RAM block device support"
select DAX if BLK_DEV_RAM_DAX
---help---
Saying Y here will allow you to use a portion of your RAM memory as
a block device, so that you can make file systems on it, read and
......
......@@ -21,6 +21,7 @@
#include <linux/slab.h>
#ifdef CONFIG_BLK_DEV_RAM_DAX
#include <linux/pfn_t.h>
#include <linux/dax.h>
#endif
#include <linux/uaccess.h>
......@@ -41,6 +42,9 @@ struct brd_device {
struct request_queue *brd_queue;
struct gendisk *brd_disk;
#ifdef CONFIG_BLK_DEV_RAM_DAX
struct dax_device *dax_dev;
#endif
struct list_head brd_list;
/*
......@@ -375,30 +379,38 @@ static int brd_rw_page(struct block_device *bdev, sector_t sector,
}
#ifdef CONFIG_BLK_DEV_RAM_DAX
static long brd_direct_access(struct block_device *bdev, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static long __brd_direct_access(struct brd_device *brd, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
struct brd_device *brd = bdev->bd_disk->private_data;
struct page *page;
if (!brd)
return -ENODEV;
page = brd_insert_page(brd, sector);
page = brd_insert_page(brd, PFN_PHYS(pgoff) / 512);
if (!page)
return -ENOSPC;
*kaddr = page_address(page);
*pfn = page_to_pfn_t(page);
return PAGE_SIZE;
return 1;
}
#else
#define brd_direct_access NULL
static long brd_dax_direct_access(struct dax_device *dax_dev,
pgoff_t pgoff, long nr_pages, void **kaddr, pfn_t *pfn)
{
struct brd_device *brd = dax_get_private(dax_dev);
return __brd_direct_access(brd, pgoff, nr_pages, kaddr, pfn);
}
static const struct dax_operations brd_dax_ops = {
.direct_access = brd_dax_direct_access,
};
#endif
static const struct block_device_operations brd_fops = {
.owner = THIS_MODULE,
.rw_page = brd_rw_page,
.direct_access = brd_direct_access,
};
/*
......@@ -469,9 +481,6 @@ static struct brd_device *brd_alloc(int i)
blk_queue_max_discard_sectors(brd->brd_queue, UINT_MAX);
brd->brd_queue->limits.discard_zeroes_data = 1;
queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, brd->brd_queue);
#ifdef CONFIG_BLK_DEV_RAM_DAX
queue_flag_set_unlocked(QUEUE_FLAG_DAX, brd->brd_queue);
#endif
disk = brd->brd_disk = alloc_disk(max_part);
if (!disk)
goto out_free_queue;
......@@ -484,8 +493,21 @@ static struct brd_device *brd_alloc(int i)
sprintf(disk->disk_name, "ram%d", i);
set_capacity(disk, rd_size * 2);
#ifdef CONFIG_BLK_DEV_RAM_DAX
queue_flag_set_unlocked(QUEUE_FLAG_DAX, brd->brd_queue);
brd->dax_dev = alloc_dax(brd, disk->disk_name, &brd_dax_ops);
if (!brd->dax_dev)
goto out_free_inode;
#endif
return brd;
#ifdef CONFIG_BLK_DEV_RAM_DAX
out_free_inode:
kill_dax(brd->dax_dev);
put_dax(brd->dax_dev);
#endif
out_free_queue:
blk_cleanup_queue(brd->brd_queue);
out_free_dev:
......@@ -525,6 +547,10 @@ static struct brd_device *brd_init_one(int i, bool *new)
static void brd_del_one(struct brd_device *brd)
{
list_del(&brd->brd_list);
#ifdef CONFIG_BLK_DEV_RAM_DAX
kill_dax(brd->dax_dev);
put_dax(brd->dax_dev);
#endif
del_gendisk(brd->brd_disk);
brd_free(brd);
}
......
......@@ -575,7 +575,7 @@ static inline unsigned long hpet_time_div(struct hpets *hpets,
}
static int
hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg,
hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg,
struct hpet_info *info)
{
struct hpet_timer __iomem *timer;
......
......@@ -2302,7 +2302,7 @@ static int __init init(void)
pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
if (!pdrvdata.debugfs_dir)
pr_warning("Error creating debugfs dir for virtio-ports\n");
pr_warn("Error creating debugfs dir for virtio-ports\n");
INIT_LIST_HEAD(&pdrvdata.consoles);
INIT_LIST_HEAD(&pdrvdata.portdevs);
......
menuconfig DEV_DAX
menuconfig DAX
tristate "DAX: direct access to differentiated memory"
select SRCU
default m if NVDIMM_DAX
if DAX
config DEV_DAX
tristate "Device DAX: direct access mapping device"
depends on TRANSPARENT_HUGEPAGE
select SRCU
help
Support raw access to differentiated (persistence, bandwidth,
latency...) memory via an mmap(2) capable character
......@@ -11,7 +16,6 @@ menuconfig DEV_DAX
baseline memory pool. Mappings of a /dev/daxX.Y device impose
restrictions that make the mapping behavior deterministic.
if DEV_DAX
config DEV_DAX_PMEM
tristate "PMEM DAX: direct access to persistent memory"
......
obj-$(CONFIG_DEV_DAX) += dax.o
obj-$(CONFIG_DAX) += dax.o
obj-$(CONFIG_DEV_DAX) += device_dax.o
obj-$(CONFIG_DEV_DAX_PMEM) += dax_pmem.o
dax-y := super.o
dax_pmem-y := pmem.o
device_dax-y := device.o
......@@ -38,22 +38,18 @@ struct dax_region {
};
/**
* struct dax_dev - subdivision of a dax region
* struct dev_dax - instance data for a subdivision of a dax region
* @region - parent region
* @inode - inode
* @dev - device backing the character device
* @cdev - core chardev data
* @alive - !alive + srcu grace period == no new mappings can be established
* @dax_dev - core dax functionality
* @dev - device core
* @id - child id in the region
* @num_resources - number of physical address extents in this device
* @res - array of physical address ranges
*/
struct dax_dev {
struct dev_dax {
struct dax_region *region;
struct inode *inode;
struct dax_device *dax_dev;
struct device dev;
struct cdev cdev;
bool alive;
int id;
int num_resources;
struct resource res[0];
......
/*
* Copyright(c) 2016 Intel Corporation. All rights reserved.
* Copyright(c) 2016 - 2017 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
......@@ -12,14 +12,7 @@
*/
#ifndef __DAX_H__
#define __DAX_H__
struct device;
struct dax_dev;
struct resource;
struct dax_region;
void dax_region_put(struct dax_region *dax_region);
struct dax_region *alloc_dax_region(struct device *parent,
int region_id, struct resource *res, unsigned int align,
void *addr, unsigned long flags);
struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
struct resource *res, int count);
struct dax_device;
struct dax_device *inode_dax(struct inode *inode);
struct inode *dax_inode(struct dax_device *dax_dev);
#endif /* __DAX_H__ */
/*
* Copyright(c) 2016 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License 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.
*/
#ifndef __DEVICE_DAX_H__
#define __DEVICE_DAX_H__
struct device;
struct dev_dax;
struct resource;
struct dax_region;
void dax_region_put(struct dax_region *dax_region);
struct dax_region *alloc_dax_region(struct device *parent,
int region_id, struct resource *res, unsigned int align,
void *addr, unsigned long flags);
struct dev_dax *devm_create_dev_dax(struct dax_region *dax_region,
struct resource *res, int count);
#endif /* __DEVICE_DAX_H__ */
This diff is collapsed.
......@@ -16,7 +16,7 @@
#include <linux/pfn_t.h>
#include "../nvdimm/pfn.h"
#include "../nvdimm/nd.h"
#include "dax.h"
#include "device-dax.h"
struct dax_pmem {
struct device *dev;
......@@ -61,8 +61,8 @@ static int dax_pmem_probe(struct device *dev)
int rc;
void *addr;
struct resource res;
struct dax_dev *dax_dev;
struct nd_pfn_sb *pfn_sb;
struct dev_dax *dev_dax;
struct dax_pmem *dax_pmem;
struct nd_region *nd_region;
struct nd_namespace_io *nsio;
......@@ -130,12 +130,12 @@ static int dax_pmem_probe(struct device *dev)
return -ENOMEM;
/* TODO: support for subdividing a dax region... */
dax_dev = devm_create_dax_dev(dax_region, &res, 1);
dev_dax = devm_create_dev_dax(dax_region, &res, 1);
/* child dax_dev instances now own the lifetime of the dax_region */
/* child dev_dax instances now own the lifetime of the dax_region */
dax_region_put(dax_region);
return PTR_ERR_OR_ZERO(dax_dev);
return PTR_ERR_OR_ZERO(dev_dax);
}
static struct nd_device_driver dax_pmem_driver = {
......
This diff is collapsed.
......@@ -20,6 +20,12 @@ config FPGA_REGION
FPGA Regions allow loading FPGA images under control of
the Device Tree.
config FPGA_MGR_ICE40_SPI
tristate "Lattice iCE40 SPI"
depends on OF && SPI
help
FPGA manager driver support for Lattice iCE40 FPGAs over SPI.
config FPGA_MGR_SOCFPGA
tristate "Altera SOCFPGA FPGA Manager"
depends on ARCH_SOCFPGA || COMPILE_TEST
......@@ -33,6 +39,13 @@ config FPGA_MGR_SOCFPGA_A10
help
FPGA manager driver support for Altera Arria10 SoCFPGA.
config FPGA_MGR_TS73XX
tristate "Technologic Systems TS-73xx SBC FPGA Manager"
depends on ARCH_EP93XX && MACH_TS72XX
help
FPGA manager driver support for the Altera Cyclone II FPGA
present on the TS-73xx SBC boards.
config FPGA_MGR_ZYNQ_FPGA
tristate "Xilinx Zynq FPGA"
depends on ARCH_ZYNQ || COMPILE_TEST
......
......@@ -6,8 +6,10 @@
obj-$(CONFIG_FPGA) += fpga-mgr.o
# FPGA Manager Drivers
obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o
obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o
obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o
obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o
obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o
# FPGA Bridge Drivers
......
......@@ -27,7 +27,7 @@ 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 spinlock_t bridge_list_lock;
static int fpga_bridge_of_node_match(struct device *dev, const void *data)
{
......@@ -146,11 +146,9 @@ EXPORT_SYMBOL_GPL(fpga_bridge_put);
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);
list_for_each_entry(bridge, bridge_list, node) {
ret = fpga_bridge_enable(bridge);
if (ret)
return ret;
......@@ -172,11 +170,9 @@ EXPORT_SYMBOL_GPL(fpga_bridges_enable);
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);
list_for_each_entry(bridge, bridge_list, node) {
ret = fpga_bridge_disable(bridge);
if (ret)
return ret;
......@@ -196,13 +192,10 @@ EXPORT_SYMBOL_GPL(fpga_bridges_disable);
*/
void fpga_bridges_put(struct list_head *bridge_list)
{
struct fpga_bridge *bridge;
struct list_head *node, *next;
struct fpga_bridge *bridge, *next;
unsigned long flags;
list_for_each_safe(node, next, bridge_list) {
bridge = list_entry(node, struct fpga_bridge, node);
list_for_each_entry_safe(bridge, next, bridge_list, node) {
fpga_bridge_put(bridge);
spin_lock_irqsave(&bridge_list_lock, flags);
......
......@@ -361,7 +361,7 @@ static struct attribute *fpga_mgr_attrs[] = {
};
ATTRIBUTE_GROUPS(fpga_mgr);
struct fpga_manager *__fpga_mgr_get(struct device *dev)
static struct fpga_manager *__fpga_mgr_get(struct device *dev)
{
struct fpga_manager *mgr;
int ret = -ENODEV;
......
......@@ -337,8 +337,9 @@ static int child_regions_with_firmware(struct device_node *overlay)
* 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
* firmware-name : program the FPGA
* external-fpga-config : FPGA is already programmed
* encrypted-fpga-config : FPGA bitstream is encrypted
*
* The overlay can add other FPGA regions, but child FPGA regions cannot have a
* firmware-name property since those regions don't exist yet.
......@@ -373,6 +374,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region,
if (of_property_read_bool(nd->overlay, "external-fpga-config"))
info->flags |= FPGA_MGR_EXTERNAL_CONFIG;
if (of_property_read_bool(nd->overlay, "encrypted-fpga-config"))
info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM;
of_property_read_string(nd->overlay, "firmware-name", &firmware_name);
of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us",
......
/*
* FPGA Manager Driver for Lattice iCE40.
*
* Copyright (c) 2016 Joel Holdsworth
*
* 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 of the License.
*
* This driver adds support to the FPGA manager for configuring the SRAM of
* Lattice iCE40 FPGAs through slave SPI.
*/
#include <linux/fpga/fpga-mgr.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/spi/spi.h>
#include <linux/stringify.h>
#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */
#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */
#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */
#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */
#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8)
struct ice40_fpga_priv {
struct spi_device *dev;
struct gpio_desc *reset;
struct gpio_desc *cdone;
};
static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr)
{
struct ice40_fpga_priv *priv = mgr->priv;
return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING :
FPGA_MGR_STATE_UNKNOWN;
}
static int ice40_fpga_ops_write_init(struct fpga_manager *mgr,
struct fpga_image_info *info,
const char *buf, size_t count)
{
struct ice40_fpga_priv *priv = mgr->priv;
struct spi_device *dev = priv->dev;
struct spi_message message;
struct spi_transfer assert_cs_then_reset_delay = {
.cs_change = 1,
.delay_usecs = ICE40_SPI_RESET_DELAY
};
struct spi_transfer housekeeping_delay_then_release_cs = {
.delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY
};
int ret;
if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
dev_err(&dev->dev,
"Partial reconfiguration is not supported\n");
return -ENOTSUPP;
}
/* Lock the bus, assert CRESET_B and SS_B and delay >200ns */
spi_bus_lock(dev->master);
gpiod_set_value(priv->reset, 1);
spi_message_init(&message);
spi_message_add_tail(&assert_cs_then_reset_delay, &message);
ret = spi_sync_locked(dev, &message);
/* Come out of reset */
gpiod_set_value(priv->reset, 0);
/* Abort if the chip-select failed */
if (ret)
goto fail;
/* Check CDONE is de-asserted i.e. the FPGA is reset */
if (gpiod_get_value(priv->cdone)) {
dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n");
ret = -EIO;
goto fail;
}
/* Wait for the housekeeping to complete, and release SS_B */
spi_message_init(&message);
spi_message_add_tail(&housekeeping_delay_then_release_cs, &message);
ret = spi_sync_locked(dev, &message);
fail:
spi_bus_unlock(dev->master);
return ret;
}
static int ice40_fpga_ops_write(struct fpga_manager *mgr,
const char *buf, size_t count)
{
struct ice40_fpga_priv *priv = mgr->priv;
return spi_write(priv->dev, buf, count);
}
static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr,
struct fpga_image_info *info)
{
struct ice40_fpga_priv *priv = mgr->priv;
struct spi_device *dev = priv->dev;
const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0};
/* Check CDONE is asserted */
if (!gpiod_get_value(priv->cdone)) {
dev_err(&dev->dev,
"CDONE was not asserted after firmware transfer\n");
return -EIO;
}
/* Send of zero-padding to activate the firmware */
return spi_write(dev, padding, sizeof(padding));
}
static const struct fpga_manager_ops ice40_fpga_ops = {
.state = ice40_fpga_ops_state,
.write_init = ice40_fpga_ops_write_init,
.write = ice40_fpga_ops_write,
.write_complete = ice40_fpga_ops_write_complete,
};
static int ice40_fpga_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct ice40_fpga_priv *priv;
int ret;
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = spi;
/* Check board setup data. */
if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) {
dev_err(dev, "SPI speed is too high, maximum speed is "
__stringify(ICE40_SPI_MAX_SPEED) "\n");
return -EINVAL;
}
if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) {
dev_err(dev, "SPI speed is too low, minimum speed is "
__stringify(ICE40_SPI_MIN_SPEED) "\n");
return -EINVAL;
}
if (spi->mode & SPI_CPHA) {
dev_err(dev, "Bad SPI mode, CPHA not supported\n");
return -EINVAL;
}
/* Set up the GPIOs */
priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN);
if (IS_ERR(priv->cdone)) {
ret = PTR_ERR(priv->cdone);
dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret);
return ret;
}
priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->reset)) {
ret = PTR_ERR(priv->reset);
dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret);
return ret;
}
/* Register with the FPGA manager */
return fpga_mgr_register(dev, "Lattice iCE40 FPGA Manager",
&ice40_fpga_ops, priv);
}
static int ice40_fpga_remove(struct spi_device *spi)
{
fpga_mgr_unregister(&spi->dev);
return 0;
}
static const struct of_device_id ice40_fpga_of_match[] = {
{ .compatible = "lattice,ice40-fpga-mgr", },
{},
};
MODULE_DEVICE_TABLE(of, ice40_fpga_of_match);
static struct spi_driver ice40_fpga_driver = {
.probe = ice40_fpga_probe,
.remove = ice40_fpga_remove,
.driver = {
.name = "ice40spi",
.of_match_table = of_match_ptr(ice40_fpga_of_match),
},
};
module_spi_driver(ice40_fpga_driver);
MODULE_AUTHOR("Joel Holdsworth <joel@airwebreathe.org.uk>");
MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager");
MODULE_LICENSE("GPL v2");
/*
* Technologic Systems TS-73xx SBC FPGA loader
*
* Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com>
*
* FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on
* TS-7300, heavily based on load_fpga.c in their vendor tree.
*
* 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 of the License.
*
* 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/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/iopoll.h>
#include <linux/fpga/fpga-mgr.h>
#define TS73XX_FPGA_DATA_REG 0
#define TS73XX_FPGA_CONFIG_REG 1
#define TS73XX_FPGA_WRITE_DONE 0x1
#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */
#define TS73XX_FPGA_RESET 0x2
#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */
#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */
#define TS73XX_FPGA_LOAD_OK 0x4
#define TS73XX_FPGA_CONFIG_LOAD 0x8
struct ts73xx_fpga_priv {
void __iomem *io_base;
struct device *dev;
};
static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr)
{
return FPGA_MGR_STATE_UNKNOWN;
}
static int ts73xx_fpga_write_init(struct fpga_manager *mgr,
struct fpga_image_info *info,
const char *buf, size_t count)
{
struct ts73xx_fpga_priv *priv = mgr->priv;
/* Reset the FPGA */
writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG);
udelay(TS73XX_FPGA_RESET_LOW_DELAY);
writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG);
udelay(TS73XX_FPGA_RESET_HIGH_DELAY);
return 0;
}
static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf,
size_t count)
{
struct ts73xx_fpga_priv *priv = mgr->priv;
size_t i = 0;
int ret;
u8 reg;
while (count--) {
ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG,
reg, !(reg & TS73XX_FPGA_WRITE_DONE),
1, TS73XX_FPGA_WRITE_DONE_TIMEOUT);
if (ret < 0)
return ret;
writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG);
i++;
}
return 0;
}
static int ts73xx_fpga_write_complete(struct fpga_manager *mgr,
struct fpga_image_info *info)
{
struct ts73xx_fpga_priv *priv = mgr->priv;
u8 reg;
usleep_range(1000, 2000);
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
reg |= TS73XX_FPGA_CONFIG_LOAD;
writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
usleep_range(1000, 2000);
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
reg &= ~TS73XX_FPGA_CONFIG_LOAD;
writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK)
return -ETIMEDOUT;
return 0;
}
static const struct fpga_manager_ops ts73xx_fpga_ops = {
.state = ts73xx_fpga_state,
.write_init = ts73xx_fpga_write_init,
.write = ts73xx_fpga_write,
.write_complete = ts73xx_fpga_write_complete,
};
static int ts73xx_fpga_probe(struct platform_device *pdev)
{
struct device *kdev = &pdev->dev;
struct ts73xx_fpga_priv *priv;
struct resource *res;
priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = kdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->io_base = devm_ioremap_resource(kdev, res);
if (IS_ERR(priv->io_base)) {
dev_err(kdev, "unable to remap registers\n");
return PTR_ERR(priv->io_base);
}
return fpga_mgr_register(kdev, "TS-73xx FPGA Manager",
&ts73xx_fpga_ops, priv);
}
static int ts73xx_fpga_remove(struct platform_device *pdev)
{
fpga_mgr_unregister(&pdev->dev);
return 0;
}
static struct platform_driver ts73xx_fpga_driver = {
.driver = {
.name = "ts73xx-fpga-mgr",
},
.probe = ts73xx_fpga_probe,
.remove = ts73xx_fpga_remove,
};
module_platform_driver(ts73xx_fpga_driver);
MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
MODULE_DESCRIPTION("TS-73xx FPGA Manager driver");
MODULE_LICENSE("GPL v2");
......@@ -72,6 +72,10 @@
#define CTRL_PCAP_PR_MASK BIT(27)
/* Enable PCAP */
#define CTRL_PCAP_MODE_MASK BIT(26)
/* Lower rate to allow decrypt on the fly */
#define CTRL_PCAP_RATE_EN_MASK BIT(25)
/* System booted in secure mode */
#define CTRL_SEC_EN_MASK BIT(7)
/* Miscellaneous Control Register bit definitions */
/* Internal PCAP loopback */
......@@ -266,6 +270,17 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
if (err)
return err;
/* check if bitstream is encrypted & and system's still secure */
if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) {
ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
if (!(ctrl & CTRL_SEC_EN_MASK)) {
dev_err(&mgr->dev,
"System not secure, can't use crypted bitstreams\n");
err = -EINVAL;
goto out_err;
}
}
/* don't globally reset PL if we're doing partial reconfig */
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
if (!zynq_fpga_has_sync(buf, count)) {
......@@ -337,12 +352,19 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
/* set configuration register with following options:
* - enable PCAP interface
* - set throughput for maximum speed
* - set throughput for maximum speed (if bistream not crypted)
* - set CPU in user mode
*/
ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
zynq_fpga_write(priv, CTRL_OFFSET,
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl));
if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM)
zynq_fpga_write(priv, CTRL_OFFSET,
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
| CTRL_PCAP_RATE_EN_MASK | ctrl));
else
zynq_fpga_write(priv, CTRL_OFFSET,
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
| ctrl));
/* We expect that the command queue is empty right now. */
status = zynq_fpga_read(priv, STATUS_OFFSET);
......
......@@ -333,7 +333,7 @@ static int create_gpadl_header(void *kbuffer, u32 size,
* Gpadl is u32 and we are using a pointer which could
* be 64-bit
* This is governed by the guest/host protocol and
* so the hypervisor gurantees that this is ok.
* so the hypervisor guarantees that this is ok.
*/
for (i = 0; i < pfncurr; i++)
gpadl_body->pfn[i] = slow_virt_to_phys(
......@@ -380,7 +380,7 @@ static int create_gpadl_header(void *kbuffer, u32 size,
}
/*
* vmbus_establish_gpadl - Estabish a GPADL for the specified buffer
* vmbus_establish_gpadl - Establish a GPADL for the specified buffer
*
* @channel: a channel
* @kbuffer: from kmalloc or vmalloc
......@@ -731,7 +731,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
/* Setup the descriptor */
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
desc.flags = flags;
desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
desc.length8 = (u16)(packetlen_aligned >> 3);
desc.transactionid = requestid;
desc.rangecount = pagecount;
......@@ -792,7 +792,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
/* Setup the descriptor */
desc->type = VM_PKT_DATA_USING_GPA_DIRECT;
desc->flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
desc->dataoffset8 = desc_size >> 3; /* in 8-bytes grandularity */
desc->dataoffset8 = desc_size >> 3; /* in 8-bytes granularity */
desc->length8 = (u16)(packetlen_aligned >> 3);
desc->transactionid = requestid;
desc->rangecount = 1;
......@@ -842,7 +842,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
/* Setup the descriptor */
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
desc.length8 = (u16)(packetlen_aligned >> 3);
desc.transactionid = requestid;
desc.rangecount = 1;
......
......@@ -1080,30 +1080,30 @@ static void vmbus_onversion_response(
}
/* Channel message dispatch table */
struct vmbus_channel_message_table_entry
channel_message_table[CHANNELMSG_COUNT] = {
{CHANNELMSG_INVALID, 0, NULL},
{CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer},
{CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind},
{CHANNELMSG_REQUESTOFFERS, 0, NULL},
{CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered},
{CHANNELMSG_OPENCHANNEL, 0, NULL},
{CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result},
{CHANNELMSG_CLOSECHANNEL, 0, NULL},
{CHANNELMSG_GPADL_HEADER, 0, NULL},
{CHANNELMSG_GPADL_BODY, 0, NULL},
{CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created},
{CHANNELMSG_GPADL_TEARDOWN, 0, NULL},
{CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown},
{CHANNELMSG_RELID_RELEASED, 0, NULL},
{CHANNELMSG_INITIATE_CONTACT, 0, NULL},
{CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response},
{CHANNELMSG_UNLOAD, 0, NULL},
{CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response},
{CHANNELMSG_18, 0, NULL},
{CHANNELMSG_19, 0, NULL},
{CHANNELMSG_20, 0, NULL},
{CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL},
const struct vmbus_channel_message_table_entry
channel_message_table[CHANNELMSG_COUNT] = {
{ CHANNELMSG_INVALID, 0, NULL },
{ CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer },
{ CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind },
{ CHANNELMSG_REQUESTOFFERS, 0, NULL },
{ CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered },
{ CHANNELMSG_OPENCHANNEL, 0, NULL },
{ CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result },
{ CHANNELMSG_CLOSECHANNEL, 0, NULL },
{ CHANNELMSG_GPADL_HEADER, 0, NULL },
{ CHANNELMSG_GPADL_BODY, 0, NULL },
{ CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created },
{ CHANNELMSG_GPADL_TEARDOWN, 0, NULL },
{ CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown },
{ CHANNELMSG_RELID_RELEASED, 0, NULL },
{ CHANNELMSG_INITIATE_CONTACT, 0, NULL },
{ CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response },
{ CHANNELMSG_UNLOAD, 0, NULL },
{ CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response },
{ CHANNELMSG_18, 0, NULL },
{ CHANNELMSG_19, 0, NULL },
{ CHANNELMSG_20, 0, NULL },
{ CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL },
};
/*
......
......@@ -296,44 +296,47 @@ struct vmbus_channel *relid2channel(u32 relid)
/*
* vmbus_on_event - Process a channel event notification
*
* For batched channels (default) optimize host to guest signaling
* by ensuring:
* 1. While reading the channel, we disable interrupts from host.
* 2. Ensure that we process all posted messages from the host
* before returning from this callback.
* 3. Once we return, enable signaling from the host. Once this
* state is set we check to see if additional packets are
* available to read. In this case we repeat the process.
* If this tasklet has been running for a long time
* then reschedule ourselves.
*/
void vmbus_on_event(unsigned long data)
{
struct vmbus_channel *channel = (void *) data;
void (*callback_fn)(void *);
unsigned long time_limit = jiffies + 2;
/*
* A channel once created is persistent even when there
* is no driver handling the device. An unloading driver
* sets the onchannel_callback to NULL on the same CPU
* as where this interrupt is handled (in an interrupt context).
* Thus, checking and invoking the driver specific callback takes
* care of orderly unloading of the driver.
*/
callback_fn = READ_ONCE(channel->onchannel_callback);
if (unlikely(callback_fn == NULL))
return;
(*callback_fn)(channel->channel_callback_context);
if (channel->callback_mode == HV_CALL_BATCHED) {
/*
* This callback reads the messages sent by the host.
* We can optimize host to guest signaling by ensuring:
* 1. While reading the channel, we disable interrupts from
* host.
* 2. Ensure that we process all posted messages from the host
* before returning from this callback.
* 3. Once we return, enable signaling from the host. Once this
* state is set we check to see if additional packets are
* available to read. In this case we repeat the process.
do {
void (*callback_fn)(void *);
/* A channel once created is persistent even when
* there is no driver handling the device. An
* unloading driver sets the onchannel_callback to NULL.
*/
if (hv_end_read(&channel->inbound) != 0) {
hv_begin_read(&channel->inbound);
callback_fn = READ_ONCE(channel->onchannel_callback);
if (unlikely(callback_fn == NULL))
return;
tasklet_schedule(&channel->callback_event);
}
}
(*callback_fn)(channel->channel_callback_context);
if (channel->callback_mode != HV_CALL_BATCHED)
return;
if (likely(hv_end_read(&channel->inbound) == 0))
return;
hv_begin_read(&channel->inbound);
} while (likely(time_before(jiffies, time_limit)));
/* The time limit (2 jiffies) has been reached */
tasklet_schedule(&channel->callback_event);
}
/*
......
......@@ -254,7 +254,10 @@ int hv_synic_init(unsigned int cpu)
shared_sint.as_uint64 = 0;
shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
shared_sint.masked = false;
shared_sint.auto_eoi = true;
if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED)
shared_sint.auto_eoi = false;
else
shared_sint.auto_eoi = true;
hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
shared_sint.as_uint64);
......
......@@ -722,8 +722,6 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size,
5*HZ);
post_status(&dm_device);
}
return;
}
static void hv_online_page(struct page *pg)
......
......@@ -186,8 +186,6 @@ static void fcopy_send_data(struct work_struct *dummy)
}
}
kfree(smsg_out);
return;
}
/*
......
......@@ -69,7 +69,7 @@ static const int fw_versions[] = {
*
* While the request/response protocol is guaranteed by the host, we further
* ensure this by serializing packet processing in this driver - we do not
* read additional packets from the VMBUs until the current packet is fully
* read additional packets from the VMBUS until the current packet is fully
* handled.
*/
......@@ -397,7 +397,7 @@ kvp_send_key(struct work_struct *dummy)
* the max lengths specified. We will however, reserve room
* for the string terminating character - in the utf16s_utf8s()
* function we limit the size of the buffer where the converted
* string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee
* string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to guarantee
* that the strings can be properly terminated!
*/
......@@ -483,8 +483,6 @@ kvp_send_key(struct work_struct *dummy)
}
kfree(message);
return;
}
/*
......@@ -533,7 +531,7 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
*/
if (error) {
/*
* Something failed or we have timedout;
* Something failed or we have timed out;
* terminate the current host-side iteration.
*/
goto response_done;
......@@ -607,8 +605,8 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
* This callback is invoked when we get a KVP message from the host.
* The host ensures that only one KVP transaction can be active at a time.
* KVP implementation in Linux needs to forward the key to a user-mde
* component to retrive the corresponding value. Consequently, we cannot
* respond to the host in the conext of this callback. Since the host
* component to retrieve the corresponding value. Consequently, we cannot
* respond to the host in the context of this callback. Since the host
* guarantees that at most only one transaction can be active at a time,
* we stash away the transaction state in a set of global variables.
*/
......
......@@ -212,8 +212,6 @@ static void vss_send_op(void)
}
kfree(vss_msg);
return;
}
static void vss_handle_request(struct work_struct *dummy)
......
......@@ -218,8 +218,8 @@ struct hv_per_cpu_context {
struct hv_context {
/* We only support running on top of Hyper-V
* So at this point this really can only contain the Hyper-V ID
*/
* So at this point this really can only contain the Hyper-V ID
*/
u64 guestid;
void *tsc_page;
......@@ -248,14 +248,6 @@ struct hv_context {
extern struct hv_context hv_context;
struct hv_ring_buffer_debug_info {
u32 current_interrupt_mask;
u32 current_read_index;
u32 current_write_index;
u32 bytes_avail_toread;
u32 bytes_avail_towrite;
};
/* Hv Interface */
extern int hv_init(void);
......@@ -289,9 +281,6 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
void *buffer, u32 buflen, u32 *buffer_actual_len,
u64 *requestid, bool raw);
void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
struct hv_ring_buffer_debug_info *debug_info);
/*
* Maximum channels is determined by the size of the interrupt page
* which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt
......@@ -376,7 +365,7 @@ struct vmbus_channel_message_table_entry {
void (*message_handler)(struct vmbus_channel_message_header *msg);
};
extern struct vmbus_channel_message_table_entry
extern const struct vmbus_channel_message_table_entry
channel_message_table[CHANNELMSG_COUNT];
......@@ -403,17 +392,17 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep);
void vmbus_on_event(unsigned long data);
void vmbus_on_msg_dpc(unsigned long data);
int hv_kvp_init(struct hv_util_service *);
int hv_kvp_init(struct hv_util_service *srv);
void hv_kvp_deinit(void);
void hv_kvp_onchannelcallback(void *);
void hv_kvp_onchannelcallback(void *context);
int hv_vss_init(struct hv_util_service *);
int hv_vss_init(struct hv_util_service *srv);
void hv_vss_deinit(void);
void hv_vss_onchannelcallback(void *);
void hv_vss_onchannelcallback(void *context);
int hv_fcopy_init(struct hv_util_service *);
int hv_fcopy_init(struct hv_util_service *srv);
void hv_fcopy_deinit(void);
void hv_fcopy_onchannelcallback(void *);
void hv_fcopy_onchannelcallback(void *context);
void vmbus_initiate_unload(bool crash);
static inline void hv_poll_channel(struct vmbus_channel *channel,
......
......@@ -73,8 +73,6 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel)
*/
if (old_write == READ_ONCE(rbi->ring_buffer->read_index))
vmbus_setevent(channel);
return;
}
/* Get the next write location for the specified ring buffer. */
......@@ -208,6 +206,7 @@ void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
ring_info->ring_buffer->interrupt_mask;
}
}
EXPORT_SYMBOL_GPL(hv_ringbuffer_get_debuginfo);
/* Initialize the ring buffer. */
int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
......@@ -267,14 +266,13 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
int hv_ringbuffer_write(struct vmbus_channel *channel,
const struct kvec *kv_list, u32 kv_count)
{
int i = 0;
int i;
u32 bytes_avail_towrite;
u32 totalbytes_towrite = 0;
u32 totalbytes_towrite = sizeof(u64);
u32 next_write_location;
u32 old_write;
u64 prev_indices = 0;
unsigned long flags = 0;
u64 prev_indices;
unsigned long flags;
struct hv_ring_buffer_info *outring_info = &channel->outbound;
if (channel->rescind)
......@@ -283,8 +281,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
for (i = 0; i < kv_count; i++)
totalbytes_towrite += kv_list[i].iov_len;
totalbytes_towrite += sizeof(u64);
spin_lock_irqsave(&outring_info->ring_lock, flags);
bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
......@@ -341,18 +337,16 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
u64 *requestid, bool raw)
{
u32 bytes_avail_toread;
u32 next_read_location = 0;
u32 next_read_location;
u64 prev_indices = 0;
struct vmpacket_descriptor desc;
u32 offset;
u32 packetlen;
int ret = 0;
struct hv_ring_buffer_info *inring_info = &channel->inbound;
if (buflen <= 0)
return -EINVAL;
*buffer_actual_len = 0;
*requestid = 0;
......@@ -363,7 +357,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
* No error is set when there is even no header, drivers are
* supposed to analyze buffer_actual_len.
*/
return ret;
return 0;
}
init_cached_read_index(channel);
......@@ -408,5 +402,5 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
hv_signal_on_read(channel);
return ret;
return 0;
}
......@@ -787,8 +787,6 @@ static void vmbus_shutdown(struct device *child_device)
if (drv->shutdown)
drv->shutdown(dev);
return;
}
......@@ -855,7 +853,7 @@ void vmbus_on_msg_dpc(unsigned long data)
struct hv_message *msg = (struct hv_message *)page_addr +
VMBUS_MESSAGE_SINT;
struct vmbus_channel_message_header *hdr;
struct vmbus_channel_message_table_entry *entry;
const struct vmbus_channel_message_table_entry *entry;
struct onmessage_work_context *ctx;
u32 message_type = msg->header.message_type;
......
......@@ -200,6 +200,7 @@ config BLK_DEV_DM_BUILTIN
config BLK_DEV_DM
tristate "Device mapper support"
select BLK_DEV_DM_BUILTIN
select DAX
---help---
Device-mapper is a low level volume manager. It works by allowing
people to specify mappings for ranges of logical sectors. Various
......
......@@ -58,6 +58,7 @@ struct mapped_device {
struct target_type *immutable_target_type;
struct gendisk *disk;
struct dax_device *dax_dev;
char name[16];
void *interface_ptr;
......
......@@ -9,6 +9,7 @@
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/dax.h>
#include <linux/slab.h>
#include <linux/device-mapper.h>
......@@ -141,22 +142,20 @@ static int linear_iterate_devices(struct dm_target *ti,
return fn(ti, lc->dev, lc->start, ti->len, data);
}
static long linear_direct_access(struct dm_target *ti, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static long linear_dax_direct_access(struct dm_target *ti, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
long ret;
struct linear_c *lc = ti->private;
struct block_device *bdev = lc->dev->bdev;
struct blk_dax_ctl dax = {
.sector = linear_map_sector(ti, sector),
.size = size,
};
long ret;
ret = bdev_direct_access(bdev, &dax);
*kaddr = dax.addr;
*pfn = dax.pfn;
return ret;
struct dax_device *dax_dev = lc->dev->dax_dev;
sector_t dev_sector, sector = pgoff * PAGE_SECTORS;
dev_sector = linear_map_sector(ti, sector);
ret = bdev_dax_pgoff(bdev, dev_sector, nr_pages * PAGE_SIZE, &pgoff);
if (ret)
return ret;
return dax_direct_access(dax_dev, pgoff, nr_pages, kaddr, pfn);
}
static struct target_type linear_target = {
......@@ -169,7 +168,7 @@ static struct target_type linear_target = {
.status = linear_status,
.prepare_ioctl = linear_prepare_ioctl,
.iterate_devices = linear_iterate_devices,
.direct_access = linear_direct_access,
.direct_access = linear_dax_direct_access,
};
int __init dm_linear_init(void)
......
......@@ -2302,8 +2302,8 @@ static int origin_map(struct dm_target *ti, struct bio *bio)
return do_origin(o->dev, bio);
}
static long origin_direct_access(struct dm_target *ti, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static long origin_dax_direct_access(struct dm_target *ti, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
DMWARN("device does not support dax.");
return -EIO;
......@@ -2368,7 +2368,7 @@ static struct target_type origin_target = {
.postsuspend = origin_postsuspend,
.status = origin_status,
.iterate_devices = origin_iterate_devices,
.direct_access = origin_direct_access,
.direct_access = origin_dax_direct_access,
};
static struct target_type snapshot_target = {
......
......@@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/dax.h>
#include <linux/slab.h>
#include <linux/log2.h>
......@@ -308,27 +309,25 @@ static int stripe_map(struct dm_target *ti, struct bio *bio)
return DM_MAPIO_REMAPPED;
}
static long stripe_direct_access(struct dm_target *ti, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static long stripe_dax_direct_access(struct dm_target *ti, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
sector_t dev_sector, sector = pgoff * PAGE_SECTORS;
struct stripe_c *sc = ti->private;
uint32_t stripe;
struct dax_device *dax_dev;
struct block_device *bdev;
struct blk_dax_ctl dax = {
.size = size,
};
uint32_t stripe;
long ret;
stripe_map_sector(sc, sector, &stripe, &dax.sector);
dax.sector += sc->stripe[stripe].physical_start;
stripe_map_sector(sc, sector, &stripe, &dev_sector);
dev_sector += sc->stripe[stripe].physical_start;
dax_dev = sc->stripe[stripe].dev->dax_dev;
bdev = sc->stripe[stripe].dev->bdev;
ret = bdev_direct_access(bdev, &dax);
*kaddr = dax.addr;
*pfn = dax.pfn;
return ret;
ret = bdev_dax_pgoff(bdev, dev_sector, nr_pages * PAGE_SIZE, &pgoff);
if (ret)
return ret;
return dax_direct_access(dax_dev, pgoff, nr_pages, kaddr, pfn);
}
/*
......@@ -448,7 +447,7 @@ static struct target_type stripe_target = {
.status = stripe_status,
.iterate_devices = stripe_iterate_devices,
.io_hints = stripe_io_hints,
.direct_access = stripe_direct_access,
.direct_access = stripe_dax_direct_access,
};
int __init dm_stripe_init(void)
......
......@@ -142,8 +142,8 @@ static void io_err_release_clone_rq(struct request *clone)
{
}
static long io_err_direct_access(struct dm_target *ti, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static long io_err_dax_direct_access(struct dm_target *ti, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
return -EIO;
}
......@@ -157,7 +157,7 @@ static struct target_type error_target = {
.map = io_err_map,
.clone_and_map_rq = io_err_clone_and_map_rq,
.release_clone_rq = io_err_release_clone_rq,
.direct_access = io_err_direct_access,
.direct_access = io_err_dax_direct_access,
};
int __init dm_target_init(void)
......
......@@ -16,6 +16,7 @@
#include <linux/blkpg.h>
#include <linux/bio.h>
#include <linux/mempool.h>
#include <linux/dax.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <linux/hdreg.h>
......@@ -629,6 +630,7 @@ static int open_table_device(struct table_device *td, dev_t dev,
}
td->dm_dev.bdev = bdev;
td->dm_dev.dax_dev = dax_get_by_host(bdev->bd_disk->disk_name);
return 0;
}
......@@ -642,7 +644,9 @@ static void close_table_device(struct table_device *td, struct mapped_device *md
bd_unlink_disk_holder(td->dm_dev.bdev, dm_disk(md));
blkdev_put(td->dm_dev.bdev, td->dm_dev.mode | FMODE_EXCL);
put_dax(td->dm_dev.dax_dev);
td->dm_dev.bdev = NULL;
td->dm_dev.dax_dev = NULL;
}
static struct table_device *find_table_device(struct list_head *l, dev_t dev,
......@@ -908,31 +912,49 @@ int dm_set_target_max_io_len(struct dm_target *ti, sector_t len)
}
EXPORT_SYMBOL_GPL(dm_set_target_max_io_len);
static long dm_blk_direct_access(struct block_device *bdev, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
static struct dm_target *dm_dax_get_live_target(struct mapped_device *md,
sector_t sector, int *srcu_idx)
{
struct mapped_device *md = bdev->bd_disk->private_data;
struct dm_table *map;
struct dm_target *ti;
int srcu_idx;
long len, ret = -EIO;
map = dm_get_live_table(md, &srcu_idx);
map = dm_get_live_table(md, srcu_idx);
if (!map)
goto out;
return NULL;
ti = dm_table_find_target(map, sector);
if (!dm_target_is_valid(ti))
goto out;
return NULL;
return ti;
}
static long dm_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
struct mapped_device *md = dax_get_private(dax_dev);
sector_t sector = pgoff * PAGE_SECTORS;
struct dm_target *ti;
long len, ret = -EIO;
int srcu_idx;
len = max_io_len(sector, ti) << SECTOR_SHIFT;
size = min(len, size);
ti = dm_dax_get_live_target(md, sector, &srcu_idx);
if (!ti)
goto out;
if (!ti->type->direct_access)
goto out;
len = max_io_len(sector, ti) / PAGE_SECTORS;
if (len < 1)
goto out;
nr_pages = min(len, nr_pages);
if (ti->type->direct_access)
ret = ti->type->direct_access(ti, sector, kaddr, pfn, size);
out:
ret = ti->type->direct_access(ti, pgoff, nr_pages, kaddr, pfn);
out:
dm_put_live_table(md, srcu_idx);
return min(ret, size);
return ret;
}
/*
......@@ -1437,6 +1459,7 @@ static int next_free_minor(int *minor)
}
static const struct block_device_operations dm_blk_dops;
static const struct dax_operations dm_dax_ops;
static void dm_wq_work(struct work_struct *work);
......@@ -1483,6 +1506,12 @@ static void cleanup_mapped_device(struct mapped_device *md)
if (md->bs)
bioset_free(md->bs);
if (md->dax_dev) {
kill_dax(md->dax_dev);
put_dax(md->dax_dev);
md->dax_dev = NULL;
}
if (md->disk) {
spin_lock(&_minor_lock);
md->disk->private_data = NULL;
......@@ -1510,6 +1539,7 @@ static void cleanup_mapped_device(struct mapped_device *md)
static struct mapped_device *alloc_dev(int minor)
{
int r, numa_node_id = dm_get_numa_node();
struct dax_device *dax_dev;
struct mapped_device *md;
void *old_md;
......@@ -1574,6 +1604,12 @@ static struct mapped_device *alloc_dev(int minor)
md->disk->queue = md->queue;
md->disk->private_data = md;
sprintf(md->disk->disk_name, "dm-%d", minor);
dax_dev = alloc_dax(md, md->disk->disk_name, &dm_dax_ops);
if (!dax_dev)
goto bad;
md->dax_dev = dax_dev;
add_disk(md->disk);
format_dev_t(md->name, MKDEV(_major, minor));
......@@ -2775,12 +2811,15 @@ static const struct block_device_operations dm_blk_dops = {
.open = dm_blk_open,
.release = dm_blk_close,
.ioctl = dm_blk_ioctl,
.direct_access = dm_blk_direct_access,
.getgeo = dm_blk_getgeo,
.pr_ops = &dm_pr_ops,
.owner = THIS_MODULE
};
static const struct dax_operations dm_dax_ops = {
.direct_access = dm_dax_direct_access,
};
/*
* module hooks
*/
......
......@@ -495,6 +495,7 @@ config VEXPRESS_SYSCFG
config PANEL
tristate "Parallel port LCD/Keypad Panel support"
depends on PARPORT
select CHARLCD
---help---
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
parallel port. This driver also features 4 and 6-key keypads. The LCD
......@@ -771,6 +772,14 @@ config PANEL_BOOT_MESSAGE
endif # PANEL
config ASPEED_LPC_CTRL
depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
---help---
Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
ioctl()s, the driver also provides a read/write interface to a BMC ram
region where the host LPC read/write region can be buffered.
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
......
......@@ -54,6 +54,7 @@ obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PANEL) += panel.o
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
......
This diff is collapsed.
This diff is collapsed.
......@@ -20,6 +20,7 @@ if LIBNVDIMM
config BLK_DEV_PMEM
tristate "PMEM: Persistent memory block device support"
default LIBNVDIMM
select DAX
select ND_BTT if BTT
select ND_PFN if NVDIMM_PFN
help
......
......@@ -246,7 +246,7 @@ static int nsio_rw_bytes(struct nd_namespace_common *ndns,
if (rw == READ) {
if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align)))
return -EIO;
return memcpy_from_pmem(buf, nsio->addr + offset, size);
return memcpy_mcsafe(buf, nsio->addr + offset, size);
}
if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align))) {
......
......@@ -28,6 +28,7 @@
#include <linux/pfn_t.h>
#include <linux/slab.h>
#include <linux/pmem.h>
#include <linux/dax.h>
#include <linux/nd.h>
#include "pmem.h"
#include "pfn.h"
......@@ -88,7 +89,7 @@ static int read_pmem(struct page *page, unsigned int off,
int rc;
void *mem = kmap_atomic(page);
rc = memcpy_from_pmem(mem + off, pmem_addr, len);
rc = memcpy_mcsafe(mem + off, pmem_addr, len);
kunmap_atomic(mem);
if (rc)
return -EIO;
......@@ -199,13 +200,13 @@ static int pmem_rw_page(struct block_device *bdev, sector_t sector,
}
/* see "strong" declaration in tools/testing/nvdimm/pmem-dax.c */
__weak long pmem_direct_access(struct block_device *bdev, sector_t sector,
void **kaddr, pfn_t *pfn, long size)
__weak long __pmem_direct_access(struct pmem_device *pmem, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
struct pmem_device *pmem = bdev->bd_queue->queuedata;
resource_size_t offset = sector * 512 + pmem->data_offset;
resource_size_t offset = PFN_PHYS(pgoff) + pmem->data_offset;
if (unlikely(is_bad_pmem(&pmem->bb, sector, size)))
if (unlikely(is_bad_pmem(&pmem->bb, PFN_PHYS(pgoff) / 512,
PFN_PHYS(nr_pages))))
return -EIO;
*kaddr = pmem->virt_addr + offset;
*pfn = phys_to_pfn_t(pmem->phys_addr + offset, pmem->pfn_flags);
......@@ -215,26 +216,41 @@ __weak long pmem_direct_access(struct block_device *bdev, sector_t sector,
* requested range.
*/
if (unlikely(pmem->bb.count))
return size;
return pmem->size - pmem->pfn_pad - offset;
return nr_pages;
return PHYS_PFN(pmem->size - pmem->pfn_pad - offset);
}
static const struct block_device_operations pmem_fops = {
.owner = THIS_MODULE,
.rw_page = pmem_rw_page,
.direct_access = pmem_direct_access,
.revalidate_disk = nvdimm_revalidate_disk,
};
static long pmem_dax_direct_access(struct dax_device *dax_dev,
pgoff_t pgoff, long nr_pages, void **kaddr, pfn_t *pfn)
{
struct pmem_device *pmem = dax_get_private(dax_dev);
return __pmem_direct_access(pmem, pgoff, nr_pages, kaddr, pfn);
}
static const struct dax_operations pmem_dax_ops = {
.direct_access = pmem_dax_direct_access,
};
static void pmem_release_queue(void *q)
{
blk_cleanup_queue(q);
}
static void pmem_release_disk(void *disk)
static void pmem_release_disk(void *__pmem)
{
del_gendisk(disk);
put_disk(disk);
struct pmem_device *pmem = __pmem;
kill_dax(pmem->dax_dev);
put_dax(pmem->dax_dev);
del_gendisk(pmem->disk);
put_disk(pmem->disk);
}
static int pmem_attach_disk(struct device *dev,
......@@ -245,6 +261,7 @@ static int pmem_attach_disk(struct device *dev,
struct vmem_altmap __altmap, *altmap = NULL;
struct resource *res = &nsio->res;
struct nd_pfn *nd_pfn = NULL;
struct dax_device *dax_dev;
int nid = dev_to_node(dev);
struct nd_pfn_sb *pfn_sb;
struct pmem_device *pmem;
......@@ -325,6 +342,7 @@ static int pmem_attach_disk(struct device *dev,
disk = alloc_disk_node(0, nid);
if (!disk)
return -ENOMEM;
pmem->disk = disk;
disk->fops = &pmem_fops;
disk->queue = q;
......@@ -336,9 +354,16 @@ static int pmem_attach_disk(struct device *dev,
return -ENOMEM;
nvdimm_badblocks_populate(nd_region, &pmem->bb, res);
disk->bb = &pmem->bb;
device_add_disk(dev, disk);
if (devm_add_action_or_reset(dev, pmem_release_disk, disk))
dax_dev = alloc_dax(pmem, disk->disk_name, &pmem_dax_ops);
if (!dax_dev) {
put_disk(disk);
return -ENOMEM;
}
pmem->dax_dev = dax_dev;
device_add_disk(dev, disk);
if (devm_add_action_or_reset(dev, pmem_release_disk, pmem))
return -ENOMEM;
revalidate_disk(disk);
......
......@@ -5,8 +5,6 @@
#include <linux/pfn_t.h>
#include <linux/fs.h>
long pmem_direct_access(struct block_device *bdev, sector_t sector,
void **kaddr, pfn_t *pfn, long size);
/* this definition is in it's own header for tools/testing/nvdimm to consume */
struct pmem_device {
/* One contiguous memory region per device */
......@@ -20,5 +18,10 @@ struct pmem_device {
/* trim size when namespace capacity has been section aligned */
u32 pfn_pad;
struct badblocks bb;
struct dax_device *dax_dev;
struct gendisk *disk;
};
long __pmem_direct_access(struct pmem_device *pmem, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn);
#endif /* __NVDIMM_PMEM_H__ */
This diff is collapsed.
This diff is collapsed.
......@@ -192,8 +192,6 @@ int rio_add_device(struct rio_dev *rdev)
}
spin_unlock(&rio_global_list_lock);
rio_create_sysfs_dev_files(rdev);
return 0;
}
EXPORT_SYMBOL_GPL(rio_add_device);
......@@ -220,7 +218,6 @@ void rio_del_device(struct rio_dev *rdev, enum rio_device_state state)
}
}
spin_unlock(&rio_global_list_lock);
rio_remove_sysfs_dev_files(rdev);
device_unregister(&rdev->dev);
}
EXPORT_SYMBOL_GPL(rio_del_device);
......
......@@ -27,8 +27,6 @@ extern u32 rio_mport_get_efb(struct rio_mport *port, int local, u16 destid,
u8 hopcount, u32 from);
extern int rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid,
u8 hopcount);
extern int rio_create_sysfs_dev_files(struct rio_dev *rdev);
extern void rio_remove_sysfs_dev_files(struct rio_dev *rdev);
extern int rio_lock_device(struct rio_mport *port, u16 destid,
u8 hopcount, int wait_ms);
extern int rio_unlock_device(struct rio_mport *port, u16 destid, u8 hopcount);
......
......@@ -14,6 +14,7 @@ config BLK_DEV_XPRAM
config DCSSBLK
def_tristate m
select DAX
prompt "DCSSBLK support"
depends on S390 && BLOCK
help
......
This diff is collapsed.
This diff is collapsed.
......@@ -86,6 +86,12 @@ config W1_SLAVE_DS2433_CRC
Each block has 30 bytes of data and a two byte CRC16.
Full block writes are only allowed if the CRC is valid.
config W1_SLAVE_DS2438
tristate "DS2438 Smart Battery Monitor 0x26 family support"
help
Say Y here if you want to use a 1-wire
DS2438 Smart Battery Monitor device support
config W1_SLAVE_DS2760
tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)"
help
......
......@@ -11,6 +11,7 @@ obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o
obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o
obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o
obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o
obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o
obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o
obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o
obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o
......
This diff is collapsed.
......@@ -24,11 +24,13 @@
#define DS2760_DATA_SIZE 0x40
#define DS2760_PROTECTION_REG 0x00
#define DS2760_STATUS_REG 0x01
#define DS2760_STATUS_IE (1 << 2)
#define DS2760_STATUS_SWEN (1 << 3)
#define DS2760_STATUS_RNAOP (1 << 4)
#define DS2760_STATUS_PMOD (1 << 5)
#define DS2760_STATUS_IE (1 << 2)
#define DS2760_STATUS_SWEN (1 << 3)
#define DS2760_STATUS_RNAOP (1 << 4)
#define DS2760_STATUS_PMOD (1 << 5)
#define DS2760_EEPROM_REG 0x07
#define DS2760_SPECIAL_FEATURE_REG 0x08
#define DS2760_VOLTAGE_MSB 0x0c
......
......@@ -29,6 +29,7 @@
#define W1_COUNTER_DS2423 0x1D
#define W1_THERM_DS1822 0x22
#define W1_EEPROM_DS2433 0x23
#define W1_FAMILY_DS2438 0x26
#define W1_THERM_DS18B20 0x28
#define W1_FAMILY_DS2408 0x29
#define W1_EEPROM_DS2431 0x2D
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -5,5 +5,4 @@ extern void zorro_name_device(struct zorro_dev *z);
static inline void zorro_name_device(struct zorro_dev *dev) { }
#endif
extern int zorro_create_sysfs_dev_files(struct zorro_dev *z);
extern const struct attribute_group *zorro_device_attribute_groups[];
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment