summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig4
-rw-r--r--drivers/Makefile2
-rw-r--r--drivers/base/regmap/regmap-spmi.c228
-rw-r--r--drivers/char/agp/frontend.c1
-rw-r--r--drivers/char/agp/generic.c1
-rw-r--r--drivers/char/agp/intel-gtt.c1
-rw-r--r--drivers/char/agp/sgi-agp.c1
-rw-r--r--drivers/char/hw_random/bcm2835-rng.c1
-rw-r--r--drivers/char/hw_random/core.c1
-rw-r--r--drivers/char/hw_random/exynos-rng.c1
-rw-r--r--drivers/char/hw_random/n2-drv.c1
-rw-r--r--drivers/char/hw_random/nomadik-rng.c1
-rw-r--r--drivers/char/hw_random/octeon-rng.c1
-rw-r--r--drivers/char/ipmi/ipmi_si_intf.c1
-rw-r--r--drivers/char/mem.c6
-rw-r--r--drivers/char/mwave/3780i.c1
-rw-r--r--drivers/char/tile-srom.c1
-rw-r--r--drivers/char/tpm/tpm_i2c_infineon.c1
-rw-r--r--drivers/char/tpm/tpm_i2c_stm_st33.c1
-rw-r--r--drivers/connector/cn_proc.c18
-rw-r--r--drivers/connector/connector.c20
-rw-r--r--drivers/extcon/Kconfig4
-rw-r--r--drivers/extcon/Makefile2
-rw-r--r--drivers/extcon/extcon-class.c42
-rw-r--r--drivers/extcon/extcon-gpio.c4
-rw-r--r--drivers/extcon/extcon-palmas.c5
-rw-r--r--drivers/extcon/of_extcon.c64
-rw-r--r--drivers/fmc/fmc-core.c22
-rw-r--r--drivers/fmc/fmc-sdb.c41
-rw-r--r--drivers/hv/Makefile2
-rw-r--r--drivers/hv/channel.c42
-rw-r--r--drivers/hv/hv_balloon.c3
-rw-r--r--drivers/hv/hv_fcopy.c414
-rw-r--r--drivers/hv/hv_kvp.c4
-rw-r--r--drivers/hv/hv_snapshot.c2
-rw-r--r--drivers/hv/hv_util.c11
-rw-r--r--drivers/hv/hyperv_vmbus.h8
-rw-r--r--drivers/hv/ring_buffer.c17
-rw-r--r--drivers/hv/vmbus_drv.c51
-rw-r--r--drivers/iio/adc/Kconfig10
-rw-r--r--drivers/iio/adc/Makefile1
-rw-r--r--drivers/iio/adc/men_z188_adc.c172
-rw-r--r--drivers/mcb/Kconfig31
-rw-r--r--drivers/mcb/Makefile7
-rw-r--r--drivers/mcb/mcb-core.c414
-rw-r--r--drivers/mcb/mcb-internal.h118
-rw-r--r--drivers/mcb/mcb-parse.c159
-rw-r--r--drivers/mcb/mcb-pci.c114
-rw-r--r--drivers/md/dm-log-userspace-transfer.c2
-rw-r--r--drivers/memory/Kconfig15
-rw-r--r--drivers/memory/Makefile2
-rw-r--r--drivers/memory/fsl_ifc.c309
-rw-r--r--drivers/memory/ti-aemif.c427
-rw-r--r--drivers/misc/Kconfig2
-rw-r--r--drivers/misc/ad525x_dpot.c1
-rw-r--r--drivers/misc/apds9802als.c1
-rw-r--r--drivers/misc/bmp085.c1
-rw-r--r--drivers/misc/carma/carma-fpga.c1
-rw-r--r--drivers/misc/ds1682.c1
-rw-r--r--drivers/misc/eeprom/at25.c1
-rw-r--r--drivers/misc/eeprom/eeprom.c1
-rw-r--r--drivers/misc/eeprom/eeprom_93xx46.c1
-rw-r--r--drivers/misc/eeprom/max6875.c1
-rw-r--r--drivers/misc/eeprom/sunxi_sid.c3
-rw-r--r--drivers/misc/genwqe/card_debugfs.c1
-rw-r--r--drivers/misc/hmc6352.c1
-rw-r--r--drivers/misc/isl29003.c1
-rw-r--r--drivers/misc/isl29020.c1
-rw-r--r--drivers/misc/lattice-ecp3-config.c1
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d.c1
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d_i2c.c1
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d_spi.c1
-rw-r--r--drivers/misc/lkdtm.c74
-rw-r--r--drivers/misc/mei/Kconfig9
-rw-r--r--drivers/misc/mei/Makefile6
-rw-r--r--drivers/misc/mei/amthif.c72
-rw-r--r--drivers/misc/mei/bus.c21
-rw-r--r--drivers/misc/mei/client.c300
-rw-r--r--drivers/misc/mei/client.h30
-rw-r--r--drivers/misc/mei/debugfs.c54
-rw-r--r--drivers/misc/mei/hbm.c281
-rw-r--r--drivers/misc/mei/hbm.h1
-rw-r--r--drivers/misc/mei/hw-me.c30
-rw-r--r--drivers/misc/mei/hw-txe-regs.h294
-rw-r--r--drivers/misc/mei/hw-txe.c1107
-rw-r--r--drivers/misc/mei/hw-txe.h74
-rw-r--r--drivers/misc/mei/hw.h20
-rw-r--r--drivers/misc/mei/init.c43
-rw-r--r--drivers/misc/mei/interrupt.c159
-rw-r--r--drivers/misc/mei/main.c23
-rw-r--r--drivers/misc/mei/mei_dev.h54
-rw-r--r--drivers/misc/mei/nfc.c14
-rw-r--r--drivers/misc/mei/pci-me.c14
-rw-r--r--drivers/misc/mei/pci-txe.c293
-rw-r--r--drivers/misc/mei/wd.c136
-rw-r--r--drivers/misc/mic/host/mic_intr.c2
-rw-r--r--drivers/misc/pch_phub.c5
-rw-r--r--drivers/misc/sram.c127
-rw-r--r--drivers/misc/ti-st/st_core.c1
-rw-r--r--drivers/misc/ti_dac7512.c1
-rw-r--r--drivers/misc/tsl2550.c1
-rw-r--r--drivers/misc/vmw_vmci/vmci_guest.c7
-rw-r--r--drivers/mtd/nand/Kconfig1
-rw-r--r--drivers/mtd/nand/fsl_ifc_nand.c2
-rw-r--r--drivers/parport/share.c3
-rw-r--r--drivers/spmi/Kconfig27
-rw-r--r--drivers/spmi/Makefile6
-rw-r--r--drivers/spmi/spmi-pmic-arb.c778
-rw-r--r--drivers/spmi/spmi.c574
-rw-r--r--drivers/usb/dwc3/dwc3-omap.c3
-rw-r--r--drivers/video/hyperv_fb.c88
-rw-r--r--drivers/video/uvesafb.c4
-rw-r--r--drivers/vme/bridges/vme_ca91cx42.c29
-rw-r--r--drivers/vme/bridges/vme_tsi148.c18
-rw-r--r--drivers/w1/masters/Kconfig3
-rw-r--r--drivers/w1/masters/ds2490.c155
-rw-r--r--drivers/w1/masters/mxc_w1.c43
-rw-r--r--drivers/w1/masters/w1-gpio.c19
-rw-r--r--drivers/w1/slaves/Kconfig5
-rw-r--r--drivers/w1/slaves/w1_therm.c21
-rw-r--r--drivers/w1/w1.c269
-rw-r--r--drivers/w1/w1.h186
-rw-r--r--drivers/w1/w1_family.c8
-rw-r--r--drivers/w1/w1_family.h13
-rw-r--r--drivers/w1/w1_int.c25
-rw-r--r--drivers/w1/w1_io.c102
-rw-r--r--drivers/w1/w1_netlink.c359
-rw-r--r--drivers/w1/w1_netlink.h33
128 files changed, 7665 insertions, 1196 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index b3138fbb46a4..0a0a90f52d26 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig"
source "drivers/spi/Kconfig"
+source "drivers/spmi/Kconfig"
+
source "drivers/hsi/Kconfig"
source "drivers/pps/Kconfig"
@@ -170,4 +172,6 @@ source "drivers/phy/Kconfig"
source "drivers/powercap/Kconfig"
+source "drivers/mcb/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 8e3b8b06c0b2..e3ced91b1784 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_ATA) += ata/
obj-$(CONFIG_TARGET_CORE) += target/
obj-$(CONFIG_MTD) += mtd/
obj-$(CONFIG_SPI) += spi/
+obj-$(CONFIG_SPMI) += spmi/
obj-y += hsi/
obj-y += net/
obj-$(CONFIG_ATM) += atm/
@@ -155,3 +156,4 @@ obj-$(CONFIG_IPACK_BUS) += ipack/
obj-$(CONFIG_NTB) += ntb/
obj-$(CONFIG_FMC) += fmc/
obj-$(CONFIG_POWERCAP) += powercap/
+obj-$(CONFIG_MCB) += mcb/
diff --git a/drivers/base/regmap/regmap-spmi.c b/drivers/base/regmap/regmap-spmi.c
index ac2391013db1..d7026dc33388 100644
--- a/drivers/base/regmap/regmap-spmi.c
+++ b/drivers/base/regmap/regmap-spmi.c
@@ -22,69 +22,235 @@
#include <linux/module.h>
#include <linux/init.h>
-static int regmap_spmi_read(void *context,
- const void *reg, size_t reg_size,
- void *val, size_t val_size)
+static int regmap_spmi_base_read(void *context,
+ const void *reg, size_t reg_size,
+ void *val, size_t val_size)
{
+ u8 addr = *(u8 *)reg;
+ int err = 0;
+
+ BUG_ON(reg_size != 1);
+
+ while (val_size-- && !err)
+ err = spmi_register_read(context, addr++, val++);
+
+ return err;
+}
+
+static int regmap_spmi_base_gather_write(void *context,
+ const void *reg, size_t reg_size,
+ const void *val, size_t val_size)
+{
+ const u8 *data = val;
+ u8 addr = *(u8 *)reg;
+ int err = 0;
+
+ BUG_ON(reg_size != 1);
+
+ /*
+ * SPMI defines a more bandwidth-efficient 'Register 0 Write' sequence,
+ * use it when possible.
+ */
+ if (addr == 0 && val_size) {
+ err = spmi_register_zero_write(context, *data);
+ if (err)
+ goto err_out;
+
+ data++;
+ addr++;
+ val_size--;
+ }
+
+ while (val_size) {
+ err = spmi_register_write(context, addr, *data);
+ if (err)
+ goto err_out;
+
+ data++;
+ addr++;
+ val_size--;
+ }
+
+err_out:
+ return err;
+}
+
+static int regmap_spmi_base_write(void *context, const void *data,
+ size_t count)
+{
+ BUG_ON(count < 1);
+ return regmap_spmi_base_gather_write(context, data, 1, data + 1,
+ count - 1);
+}
+
+static struct regmap_bus regmap_spmi_base = {
+ .read = regmap_spmi_base_read,
+ .write = regmap_spmi_base_write,
+ .gather_write = regmap_spmi_base_gather_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
+ .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
+};
+
+/**
+ * regmap_init_spmi_base(): Create regmap for the Base register space
+ * @sdev: SPMI device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer to
+ * a struct regmap.
+ */
+struct regmap *regmap_init_spmi_base(struct spmi_device *sdev,
+ const struct regmap_config *config)
+{
+ return regmap_init(&sdev->dev, &regmap_spmi_base, sdev, config);
+}
+EXPORT_SYMBOL_GPL(regmap_init_spmi_base);
+
+/**
+ * devm_regmap_init_spmi_base(): Create managed regmap for Base register space
+ * @sdev: SPMI device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap. The regmap will be automatically freed by the
+ * device management code.
+ */
+struct regmap *devm_regmap_init_spmi_base(struct spmi_device *sdev,
+ const struct regmap_config *config)
+{
+ return devm_regmap_init(&sdev->dev, &regmap_spmi_base, sdev, config);
+}
+EXPORT_SYMBOL_GPL(devm_regmap_init_spmi_base);
+
+static int regmap_spmi_ext_read(void *context,
+ const void *reg, size_t reg_size,
+ void *val, size_t val_size)
+{
+ int err = 0;
+ size_t len;
+ u16 addr;
+
BUG_ON(reg_size != 2);
- return spmi_ext_register_readl(context, *(u16 *)reg,
- val, val_size);
+
+ addr = *(u16 *)reg;
+
+ /*
+ * Split accesses into two to take advantage of the more
+ * bandwidth-efficient 'Extended Register Read' command when possible
+ */
+ while (addr <= 0xFF && val_size) {
+ len = min_t(size_t, val_size, 16);
+
+ err = spmi_ext_register_read(context, addr, val, len);
+ if (err)
+ goto err_out;
+
+ addr += len;
+ val += len;
+ val_size -= len;
+ }
+
+ while (val_size) {
+ len = min_t(size_t, val_size, 8);
+
+ err = spmi_ext_register_readl(context, addr, val, val_size);
+ if (err)
+ goto err_out;
+
+ addr += len;
+ val += len;
+ val_size -= len;
+ }
+
+err_out:
+ return err;
}
-static int regmap_spmi_gather_write(void *context,
- const void *reg, size_t reg_size,
- const void *val, size_t val_size)
+static int regmap_spmi_ext_gather_write(void *context,
+ const void *reg, size_t reg_size,
+ const void *val, size_t val_size)
{
+ int err = 0;
+ size_t len;
+ u16 addr;
+
BUG_ON(reg_size != 2);
- return spmi_ext_register_writel(context, *(u16 *)reg, val, val_size);
+
+ addr = *(u16 *)reg;
+
+ while (addr <= 0xFF && val_size) {
+ len = min_t(size_t, val_size, 16);
+
+ err = spmi_ext_register_write(context, addr, val, len);
+ if (err)
+ goto err_out;
+
+ addr += len;
+ val += len;
+ val_size -= len;
+ }
+
+ while (val_size) {
+ len = min_t(size_t, val_size, 8);
+
+ err = spmi_ext_register_writel(context, addr, val, len);
+ if (err)
+ goto err_out;
+
+ addr += len;
+ val += len;
+ val_size -= len;
+ }
+
+err_out:
+ return err;
}
-static int regmap_spmi_write(void *context, const void *data,
- size_t count)
+static int regmap_spmi_ext_write(void *context, const void *data,
+ size_t count)
{
BUG_ON(count < 2);
- return regmap_spmi_gather_write(context, data, 2, data + 2, count - 2);
+ return regmap_spmi_ext_gather_write(context, data, 2, data + 2,
+ count - 2);
}
-static struct regmap_bus regmap_spmi = {
- .read = regmap_spmi_read,
- .write = regmap_spmi_write,
- .gather_write = regmap_spmi_gather_write,
+static struct regmap_bus regmap_spmi_ext = {
+ .read = regmap_spmi_ext_read,
+ .write = regmap_spmi_ext_write,
+ .gather_write = regmap_spmi_ext_gather_write,
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
};
/**
- * regmap_init_spmi(): Initialize register map
- *
- * @sdev: Device that will be interacted with
- * @config: Configuration for register map
+ * regmap_init_spmi_ext(): Create regmap for Ext register space
+ * @sdev: Device that will be interacted with
+ * @config: Configuration for register map
*
* The return value will be an ERR_PTR() on error or a valid pointer to
* a struct regmap.
*/
-struct regmap *regmap_init_spmi(struct spmi_device *sdev,
- const struct regmap_config *config)
+struct regmap *regmap_init_spmi_ext(struct spmi_device *sdev,
+ const struct regmap_config *config)
{
- return regmap_init(&sdev->dev, &regmap_spmi, sdev, config);
+ return regmap_init(&sdev->dev, &regmap_spmi_ext, sdev, config);
}
-EXPORT_SYMBOL_GPL(regmap_init_spmi);
+EXPORT_SYMBOL_GPL(regmap_init_spmi_ext);
/**
- * devm_regmap_init_spmi(): Initialise managed register map
- *
- * @sdev: Device that will be interacted with
- * @config: Configuration for register map
+ * devm_regmap_init_spmi_ext(): Create managed regmap for Ext register space
+ * @sdev: SPMI device that will be interacted with
+ * @config: Configuration for register map
*
* The return value will be an ERR_PTR() on error or a valid pointer
* to a struct regmap. The regmap will be automatically freed by the
* device management code.
*/
-struct regmap *devm_regmap_init_spmi(struct spmi_device *sdev,
+struct regmap *devm_regmap_init_spmi_ext(struct spmi_device *sdev,
const struct regmap_config *config)
{
- return devm_regmap_init(&sdev->dev, &regmap_spmi, sdev, config);
+ return devm_regmap_init(&sdev->dev, &regmap_spmi_ext, sdev, config);
}
-EXPORT_SYMBOL_GPL(devm_regmap_init_spmi);
+EXPORT_SYMBOL_GPL(devm_regmap_init_spmi_ext);
MODULE_LICENSE("GPL");
diff --git a/drivers/char/agp/frontend.c b/drivers/char/agp/frontend.c
index 1b192395a90c..8121b4c70ede 100644
--- a/drivers/char/agp/frontend.c
+++ b/drivers/char/agp/frontend.c
@@ -31,7 +31,6 @@
#include <linux/module.h>
#include <linux/mman.h>
#include <linux/pci.h>
-#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/agp_backend.h>
#include <linux/agpgart.h>
diff --git a/drivers/char/agp/generic.c b/drivers/char/agp/generic.c
index f39437addb58..0fbccce1cee9 100644
--- a/drivers/char/agp/generic.c
+++ b/drivers/char/agp/generic.c
@@ -29,7 +29,6 @@
*/
#include <linux/module.h>
#include <linux/pci.h>
-#include <linux/init.h>
#include <linux/pagemap.h>
#include <linux/miscdevice.h>
#include <linux/pm.h>
diff --git a/drivers/char/agp/intel-gtt.c b/drivers/char/agp/intel-gtt.c
index 5c85350f4c3d..9a024f899dd4 100644
--- a/drivers/char/agp/intel-gtt.c
+++ b/drivers/char/agp/intel-gtt.c
@@ -17,7 +17,6 @@
#include <linux/module.h>
#include <linux/pci.h>
-#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/pagemap.h>
#include <linux/agp_backend.h>
diff --git a/drivers/char/agp/sgi-agp.c b/drivers/char/agp/sgi-agp.c
index 05b8d0241bde..3051c73bc383 100644
--- a/drivers/char/agp/sgi-agp.c
+++ b/drivers/char/agp/sgi-agp.c
@@ -15,7 +15,6 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
-#include <linux/init.h>
#include <linux/agp_backend.h>
#include <asm/sn/addrs.h>
#include <asm/sn/io.h>
diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c
index 43577ca780e3..8c3b255e629a 100644
--- a/drivers/char/hw_random/bcm2835-rng.c
+++ b/drivers/char/hw_random/bcm2835-rng.c
@@ -8,7 +8,6 @@
*/
#include <linux/hw_random.h>
-#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c
index a0f7724852eb..b9495a8c05c6 100644
--- a/drivers/char/hw_random/core.c
+++ b/drivers/char/hw_random/core.c
@@ -37,7 +37,6 @@
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>
-#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/slab.h>
diff --git a/drivers/char/hw_random/exynos-rng.c b/drivers/char/hw_random/exynos-rng.c
index 402ccfb625c5..9f8277cc44b4 100644
--- a/drivers/char/hw_random/exynos-rng.c
+++ b/drivers/char/hw_random/exynos-rng.c
@@ -22,7 +22,6 @@
#include <linux/hw_random.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
diff --git a/drivers/char/hw_random/n2-drv.c b/drivers/char/hw_random/n2-drv.c
index f9beed54d0c8..432232eefe05 100644
--- a/drivers/char/hw_random/n2-drv.c
+++ b/drivers/char/hw_random/n2-drv.c
@@ -7,7 +7,6 @@
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/preempt.h>
diff --git a/drivers/char/hw_random/nomadik-rng.c b/drivers/char/hw_random/nomadik-rng.c
index 232b87fb5fc9..00e9d2d46634 100644
--- a/drivers/char/hw_random/nomadik-rng.c
+++ b/drivers/char/hw_random/nomadik-rng.c
@@ -10,7 +10,6 @@
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/device.h>
#include <linux/amba/bus.h>
#include <linux/hw_random.h>
diff --git a/drivers/char/hw_random/octeon-rng.c b/drivers/char/hw_random/octeon-rng.c
index f2885dbe1849..b5cc3420c659 100644
--- a/drivers/char/hw_random/octeon-rng.c
+++ b/drivers/char/hw_random/octeon-rng.c
@@ -10,7 +10,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/hw_random.h>
diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c
index 03f41896d090..b7efd3c1a882 100644
--- a/drivers/char/ipmi/ipmi_si_intf.c
+++ b/drivers/char/ipmi/ipmi_si_intf.c
@@ -61,7 +61,6 @@
#include <linux/ipmi_smi.h>
#include <asm/io.h>
#include "ipmi_si_sm.h"
-#include <linux/init.h>
#include <linux/dmi.h>
#include <linux/string.h>
#include <linux/ctype.h>
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
index 92c5937f80c3..917403fe10da 100644
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -99,6 +99,9 @@ static ssize_t read_mem(struct file *file, char __user *buf,
ssize_t read, sz;
char *ptr;
+ if (p != *ppos)
+ return 0;
+
if (!valid_phys_addr_range(p, count))
return -EFAULT;
read = 0;
@@ -157,6 +160,9 @@ static ssize_t write_mem(struct file *file, const char __user *buf,
unsigned long copied;
void *ptr;
+ if (p != *ppos)
+ return -EFBIG;
+
if (!valid_phys_addr_range(p, count))
return -EFAULT;
diff --git a/drivers/char/mwave/3780i.c b/drivers/char/mwave/3780i.c
index 881c9e595939..28740046bc83 100644
--- a/drivers/char/mwave/3780i.c
+++ b/drivers/char/mwave/3780i.c
@@ -50,7 +50,6 @@
#include <linux/unistd.h>
#include <linux/delay.h>
#include <linux/ioport.h>
-#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/sched.h> /* cond_resched() */
diff --git a/drivers/char/tile-srom.c b/drivers/char/tile-srom.c
index 0e506bad1986..bd377472dcfb 100644
--- a/drivers/char/tile-srom.c
+++ b/drivers/char/tile-srom.c
@@ -20,7 +20,6 @@
#include <linux/module.h>
#include <linux/moduleparam.h>
-#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c
index 52b9b2b2f300..472af4bb1b61 100644
--- a/drivers/char/tpm/tpm_i2c_infineon.c
+++ b/drivers/char/tpm/tpm_i2c_infineon.c
@@ -21,7 +21,6 @@
*
*
*/
-#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/wait.h>
diff --git a/drivers/char/tpm/tpm_i2c_stm_st33.c b/drivers/char/tpm/tpm_i2c_stm_st33.c
index 5b0dd8ef74c0..3b7bf2162898 100644
--- a/drivers/char/tpm/tpm_i2c_stm_st33.c
+++ b/drivers/char/tpm/tpm_i2c_stm_st33.c
@@ -38,7 +38,6 @@
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/delay.h>
-#include <linux/init.h>
#include <linux/wait.h>
#include <linux/string.h>
#include <linux/interrupt.h>
diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c
index 18c5b9b16645..148d707a1d43 100644
--- a/drivers/connector/cn_proc.c
+++ b/drivers/connector/cn_proc.c
@@ -95,7 +95,7 @@ void proc_fork_connector(struct task_struct *task)
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
/* If cn_netlink_send() failed, the data is not sent */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_exec_connector(struct task_struct *task)
@@ -122,7 +122,7 @@ void proc_exec_connector(struct task_struct *task)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_id_connector(struct task_struct *task, int which_id)
@@ -163,7 +163,7 @@ void proc_id_connector(struct task_struct *task, int which_id)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_sid_connector(struct task_struct *task)
@@ -190,7 +190,7 @@ void proc_sid_connector(struct task_struct *task)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_ptrace_connector(struct task_struct *task, int ptrace_id)
@@ -225,7 +225,7 @@ void proc_ptrace_connector(struct task_struct *task, int ptrace_id)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_comm_connector(struct task_struct *task)
@@ -253,7 +253,7 @@ void proc_comm_connector(struct task_struct *task)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_coredump_connector(struct task_struct *task)
@@ -280,7 +280,7 @@ void proc_coredump_connector(struct task_struct *task)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
void proc_exit_connector(struct task_struct *task)
@@ -309,7 +309,7 @@ void proc_exit_connector(struct task_struct *task)
msg->ack = 0; /* not used */
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
/*
@@ -343,7 +343,7 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
msg->ack = rcvd_ack + 1;
msg->len = sizeof(*ev);
msg->flags = 0; /* not used */
- cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL);
+ cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL);
}
/**
diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c
index a36749f1e44a..77afe7487d34 100644
--- a/drivers/connector/connector.c
+++ b/drivers/connector/connector.c
@@ -50,7 +50,7 @@ static int cn_already_initialized;
*
* Sequence number is incremented with each message to be sent.
*
- * If we expect reply to our message then the sequence number in
+ * If we expect a reply to our message then the sequence number in
* received message MUST be the same as in original message, and
* acknowledge number MUST be the same + 1.
*
@@ -62,8 +62,11 @@ static int cn_already_initialized;
* the acknowledgement number in the original message + 1, then it is
* a new message.
*
+ * The message is sent to, the portid if given, the group if given, both if
+ * both, or if both are zero then the group is looked up and sent there.
*/
-int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
+int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group,
+ gfp_t gfp_mask)
{
struct cn_callback_entry *__cbq;
unsigned int size;
@@ -74,7 +77,9 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
u32 group = 0;
int found = 0;
- if (!__group) {
+ if (portid || __group) {
+ group = __group;
+ } else {
spin_lock_bh(&dev->cbdev->queue_lock);
list_for_each_entry(__cbq, &dev->cbdev->queue_list,
callback_entry) {
@@ -88,11 +93,9 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
if (!found)
return -ENODEV;
- } else {
- group = __group;
}
- if (!netlink_has_listeners(dev->nls, group))
+ if (!portid && !netlink_has_listeners(dev->nls, group))
return -ESRCH;
size = sizeof(*msg) + msg->len;
@@ -113,7 +116,10 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask)
NETLINK_CB(skb).dst_group = group;
- return netlink_broadcast(dev->nls, skb, 0, group, gfp_mask);
+ if (group)
+ return netlink_broadcast(dev->nls, skb, portid, group,
+ gfp_mask);
+ return netlink_unicast(dev->nls, skb, portid, !(gfp_mask&__GFP_WAIT));
}
EXPORT_SYMBOL_GPL(cn_netlink_send);
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index bdb5a00f1dfa..be56e8ac95e6 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -14,10 +14,6 @@ if EXTCON
comment "Extcon Device Drivers"
-config OF_EXTCON
- def_tristate y
- depends on OF
-
config EXTCON_GPIO
tristate "GPIO extcon support"
depends on GPIOLIB
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 43eccc0e3448..bf7861ec0906 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -2,8 +2,6 @@
# Makefile for external connector class (extcon) devices
#
-obj-$(CONFIG_OF_EXTCON) += of_extcon.o
-
obj-$(CONFIG_EXTCON) += extcon-class.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c
index 76322330cbd7..7ab21aa6eaa1 100644
--- a/drivers/extcon/extcon-class.c
+++ b/drivers/extcon/extcon-class.c
@@ -31,6 +31,7 @@
#include <linux/extcon.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
+#include <linux/of.h>
/*
* extcon_cable_name suggests the standard cable names for commonly used
@@ -818,6 +819,47 @@ void extcon_dev_unregister(struct extcon_dev *edev)
}
EXPORT_SYMBOL_GPL(extcon_dev_unregister);
+#ifdef CONFIG_OF
+/*
+ * extcon_get_edev_by_phandle - Get the extcon device from devicetree
+ * @dev - instance to the given device
+ * @index - index into list of extcon_dev
+ *
+ * return the instance of extcon device
+ */
+struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)
+{
+ struct device_node *node;
+ struct extcon_dev *edev;
+
+ if (!dev->of_node) {
+ dev_err(dev, "device does not have a device node entry\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ node = of_parse_phandle(dev->of_node, "extcon", index);
+ if (!node) {
+ dev_err(dev, "failed to get phandle in %s node\n",
+ dev->of_node->full_name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ edev = extcon_get_extcon_dev(node->name);
+ if (!edev) {
+ dev_err(dev, "unable to get extcon device : %s\n", node->name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return edev;
+}
+#else
+struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)
+{
+ return ERR_PTR(-ENOSYS);
+}
+#endif /* CONFIG_OF */
+EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle);
+
static int __init extcon_class_init(void)
{
return create_extcon_class();
diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c
index a63a6b21c9ad..13d522255d81 100644
--- a/drivers/extcon/extcon-gpio.c
+++ b/drivers/extcon/extcon-gpio.c
@@ -176,9 +176,7 @@ static int gpio_extcon_resume(struct device *dev)
}
#endif
-static const struct dev_pm_ops gpio_extcon_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(NULL, gpio_extcon_resume)
-};
+static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
static struct platform_driver gpio_extcon_driver = {
.probe = gpio_extcon_probe,
diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c
index 2aea4bcdd7f3..ddff2b72f0a8 100644
--- a/drivers/extcon/extcon-palmas.c
+++ b/drivers/extcon/extcon-palmas.c
@@ -271,10 +271,7 @@ static int palmas_usb_resume(struct device *dev)
};
#endif
-static const struct dev_pm_ops palmas_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(palmas_usb_suspend,
- palmas_usb_resume)
-};
+static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume);
static struct of_device_id of_palmas_match_tbl[] = {
{ .compatible = "ti,palmas-usb", },
diff --git a/drivers/extcon/of_extcon.c b/drivers/extcon/of_extcon.c
deleted file mode 100644
index 72173ecbb311..000000000000
--- a/drivers/extcon/of_extcon.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * OF helpers for External connector (extcon) framework
- *
- * Copyright (C) 2013 Texas Instruments, Inc.
- * Kishon Vijay Abraham I <kishon@ti.com>
- *
- * Copyright (C) 2013 Samsung Electronics
- * Chanwoo Choi <cw00.choi@samsung.com>
- *
- * 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/module.h>
-#include <linux/slab.h>
-#include <linux/err.h>
-#include <linux/extcon.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/extcon/of_extcon.h>
-
-/*
- * of_extcon_get_extcon_dev - Get the name of extcon device from devicetree
- * @dev - instance to the given device
- * @index - index into list of extcon_dev
- *
- * return the instance of extcon device
- */
-struct extcon_dev *of_extcon_get_extcon_dev(struct device *dev, int index)
-{
- struct device_node *node;
- struct extcon_dev *edev;
- struct platform_device *extcon_parent_dev;
-
- if (!dev->of_node) {
- dev_dbg(dev, "device does not have a device node entry\n");
- return ERR_PTR(-EINVAL);
- }
-
- node = of_parse_phandle(dev->of_node, "extcon", index);
- if (!node) {
- dev_dbg(dev, "failed to get phandle in %s node\n",
- dev->of_node->full_name);
- return ERR_PTR(-ENODEV);
- }
-
- extcon_parent_dev = of_find_device_by_node(node);
- if (!extcon_parent_dev) {
- dev_dbg(dev, "unable to find device by node\n");
- return ERR_PTR(-EPROBE_DEFER);
- }
-
- edev = extcon_get_extcon_dev(dev_name(&extcon_parent_dev->dev));
- if (!edev) {
- dev_dbg(dev, "unable to get extcon device : %s\n",
- dev_name(&extcon_parent_dev->dev));
- return ERR_PTR(-ENODEV);
- }
-
- return edev;
-}
-EXPORT_SYMBOL_GPL(of_extcon_get_extcon_dev);
diff --git a/drivers/fmc/fmc-core.c b/drivers/fmc/fmc-core.c
index 24d52497524d..353fc546fb08 100644
--- a/drivers/fmc/fmc-core.c
+++ b/drivers/fmc/fmc-core.c
@@ -99,10 +99,23 @@ static ssize_t fmc_read_eeprom(struct file *file, struct kobject *kobj,
return count;
}
+static ssize_t fmc_write_eeprom(struct file *file, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev;
+ struct fmc_device *fmc;
+
+ dev = container_of(kobj, struct device, kobj);
+ fmc = container_of(dev, struct fmc_device, dev);
+ return fmc->op->write_ee(fmc, off, buf, count);
+}
+
static struct bin_attribute fmc_eeprom_attr = {
- .attr = { .name = "eeprom", .mode = S_IRUGO, },
+ .attr = { .name = "eeprom", .mode = S_IRUGO | S_IWUSR, },
.size = 8192, /* more or less standard */
.read = fmc_read_eeprom,
+ .write = fmc_write_eeprom,
};
/*
@@ -154,7 +167,7 @@ int fmc_device_register_n(struct fmc_device **devs, int n)
ret = -EINVAL;
break;
}
- if (fmc->flags == FMC_DEVICE_NO_MEZZANINE) {
+ if (fmc->flags & FMC_DEVICE_NO_MEZZANINE) {
dev_info(fmc->hwdev, "absent mezzanine in slot %d\n",
fmc->slot_id);
continue;
@@ -189,9 +202,6 @@ int fmc_device_register_n(struct fmc_device **devs, int n)
for (i = 0; i < n; i++) {
fmc = devarray[i];
- if (fmc->flags == FMC_DEVICE_NO_MEZZANINE)
- continue; /* dev_info already done above */
-
fmc->nr_slots = n; /* each slot must know how many are there */
fmc->devarray = devarray;
@@ -263,8 +273,6 @@ void fmc_device_unregister_n(struct fmc_device **devs, int n)
kfree(devs[0]->devarray);
for (i = 0; i < n; i++) {
- if (devs[i]->flags == FMC_DEVICE_NO_MEZZANINE)
- continue;
sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr);
device_del(&devs[i]->dev);
fmc_free_id_info(devs[i]);
diff --git a/drivers/fmc/fmc-sdb.c b/drivers/fmc/fmc-sdb.c
index 79adc39221ea..4603fdb74465 100644
--- a/drivers/fmc/fmc-sdb.c
+++ b/drivers/fmc/fmc-sdb.c
@@ -150,23 +150,36 @@ int fmc_reprogram(struct fmc_device *fmc, struct fmc_driver *d, char *gw,
}
EXPORT_SYMBOL(fmc_reprogram);
+static char *__strip_trailing_space(char *buf, char *str, int len)
+{
+ int i = len - 1;
+
+ memcpy(buf, str, len);
+ while(i >= 0 && buf[i] == ' ')
+ buf[i--] = '\0';
+ return buf;
+}
+
+#define __sdb_string(buf, field) ({ \
+ BUILD_BUG_ON(sizeof(buf) < sizeof(field)); \
+ __strip_trailing_space(buf, (void *)(field), sizeof(field)); \
+ })
+
static void __fmc_show_sdb_tree(const struct fmc_device *fmc,
const struct sdb_array *arr)
{
+ unsigned long base = arr->baseaddr;
int i, j, n = arr->len, level = arr->level;
- const struct sdb_array *ap;
+ char buf[64];
for (i = 0; i < n; i++) {
- unsigned long base;
union sdb_record *r;
struct sdb_product *p;
struct sdb_component *c;
r = &arr->record[i];
c = &r->dev.sdb_component;
p = &c->product;
- base = 0;
- for (ap = arr; ap; ap = ap->parent)
- base += ap->baseaddr;
+
dev_info(&fmc->dev, "SDB: ");
for (j = 0; j < level; j++)
@@ -193,8 +206,8 @@ static void __fmc_show_sdb_tree(const struct fmc_device *fmc,
p->name,
__be64_to_cpu(c->addr_first) + base);
if (IS_ERR(arr->subtree[i])) {
- printk(KERN_CONT "(bridge error %li)\n",
- PTR_ERR(arr->subtree[i]));
+ dev_info(&fmc->dev, "SDB: (bridge error %li)\n",
+ PTR_ERR(arr->subtree[i]));
break;
}
__fmc_show_sdb_tree(fmc, arr->subtree[i]);
@@ -203,10 +216,20 @@ static void __fmc_show_sdb_tree(const struct fmc_device *fmc,
printk(KERN_CONT "integration\n");
break;
case sdb_type_repo_url:
- printk(KERN_CONT "repo-url\n");
+ printk(KERN_CONT "Synthesis repository: %s\n",
+ __sdb_string(buf, r->repo_url.repo_url));
break;
case sdb_type_synthesis:
- printk(KERN_CONT "synthesis-info\n");
+ printk(KERN_CONT "Bitstream '%s' ",
+ __sdb_string(buf, r->synthesis.syn_name));
+ printk(KERN_CONT "synthesized %08x by %s ",
+ __be32_to_cpu(r->synthesis.date),
+ __sdb_string(buf, r->synthesis.user_name));
+ printk(KERN_CONT "(%s version %x), ",
+ __sdb_string(buf, r->synthesis.tool_name),
+ __be32_to_cpu(r->synthesis.tool_version));
+ printk(KERN_CONT "commit %pm\n",
+ r->synthesis.commit_id);
break;
case sdb_type_empty:
printk(KERN_CONT "empty\n");
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 0a74b5661186..5e4dfa4cfe22 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -5,4 +5,4 @@ obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o
hv_vmbus-y := vmbus_drv.o \
hv.o connection.o channel.o \
channel_mgmt.o ring_buffer.o
-hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o
+hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 69ea36f07b4d..602ca86a6488 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -27,6 +27,7 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/hyperv.h>
+#include <linux/uio.h>
#include "hyperv_vmbus.h"
@@ -554,14 +555,14 @@ EXPORT_SYMBOL_GPL(vmbus_close);
*
* Mainly used by Hyper-V drivers.
*/
-int vmbus_sendpacket(struct vmbus_channel *channel, const void *buffer,
+int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer,
u32 bufferlen, u64 requestid,
enum vmbus_packet_type type, u32 flags)
{
struct vmpacket_descriptor desc;
u32 packetlen = sizeof(struct vmpacket_descriptor) + bufferlen;
u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64));
- struct scatterlist bufferlist[3];
+ struct kvec bufferlist[3];
u64 aligned_data = 0;
int ret;
bool signal = false;
@@ -575,11 +576,12 @@ int vmbus_sendpacket(struct vmbus_channel *channel, const void *buffer,
desc.len8 = (u16)(packetlen_aligned >> 3);
desc.trans_id = requestid;
- sg_init_table(bufferlist, 3);
- sg_set_buf(&bufferlist[0], &desc, sizeof(struct vmpacket_descriptor));
- sg_set_buf(&bufferlist[1], buffer, bufferlen);
- sg_set_buf(&bufferlist[2], &aligned_data,
- packetlen_aligned - packetlen);
+ bufferlist[0].iov_base = &desc;
+ bufferlist[0].iov_len = sizeof(struct vmpacket_descriptor);
+ bufferlist[1].iov_base = buffer;
+ bufferlist[1].iov_len = bufferlen;
+ bufferlist[2].iov_base = &aligned_data;
+ bufferlist[2].iov_len = (packetlen_aligned - packetlen);
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
@@ -605,7 +607,7 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel,
u32 descsize;
u32 packetlen;
u32 packetlen_aligned;
- struct scatterlist bufferlist[3];
+ struct kvec bufferlist[3];
u64 aligned_data = 0;
bool signal = false;
@@ -637,11 +639,12 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel,
desc.range[i].pfn = pagebuffers[i].pfn;
}
- sg_init_table(bufferlist, 3);
- sg_set_buf(&bufferlist[0], &desc, descsize);
- sg_set_buf(&bufferlist[1], buffer, bufferlen);
- sg_set_buf(&bufferlist[2], &aligned_data,
- packetlen_aligned - packetlen);
+ bufferlist[0].iov_base = &desc;
+ bufferlist[0].iov_len = descsize;
+ bufferlist[1].iov_base = buffer;
+ bufferlist[1].iov_len = bufferlen;
+ bufferlist[2].iov_base = &aligned_data;
+ bufferlist[2].iov_len = (packetlen_aligned - packetlen);
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
@@ -665,7 +668,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
u32 descsize;
u32 packetlen;
u32 packetlen_aligned;
- struct scatterlist bufferlist[3];
+ struct kvec bufferlist[3];
u64 aligned_data = 0;
bool signal = false;
u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset,
@@ -700,11 +703,12 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
memcpy(desc.range.pfn_array, multi_pagebuffer->pfn_array,
pfncount * sizeof(u64));
- sg_init_table(bufferlist, 3);
- sg_set_buf(&bufferlist[0], &desc, descsize);
- sg_set_buf(&bufferlist[1], buffer, bufferlen);
- sg_set_buf(&bufferlist[2], &aligned_data,
- packetlen_aligned - packetlen);
+ bufferlist[0].iov_base = &desc;
+ bufferlist[0].iov_len = descsize;
+ bufferlist[1].iov_base = buffer;
+ bufferlist[1].iov_len = bufferlen;
+ bufferlist[2].iov_base = &aligned_data;
+ bufferlist[2].iov_len = (packetlen_aligned - packetlen);
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c
index 7e17a5495e02..7e6d78dc9437 100644
--- a/drivers/hv/hv_balloon.c
+++ b/drivers/hv/hv_balloon.c
@@ -1171,7 +1171,8 @@ static int dm_thread_func(void *dm_dev)
int t;
while (!kthread_should_stop()) {
- t = wait_for_completion_timeout(&dm_device.config_event, 1*HZ);
+ t = wait_for_completion_interruptible_timeout(
+ &dm_device.config_event, 1*HZ);
/*
* The host expects us to post information on the memory
* pressure every second.
diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c
new file mode 100644
index 000000000000..eaaa3d843b80
--- /dev/null
+++ b/drivers/hv/hv_fcopy.c
@@ -0,0 +1,414 @@
+/*
+ * An implementation of file copy service.
+ *
+ * Copyright (C) 2014, Microsoft, Inc.
+ *
+ * Author : K. Y. Srinivasan <ksrinivasan@novell.com>
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/semaphore.h>
+#include <linux/fs.h>
+#include <linux/nls.h>
+#include <linux/workqueue.h>
+#include <linux/cdev.h>
+#include <linux/hyperv.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+
+#include "hyperv_vmbus.h"
+
+#define WIN8_SRV_MAJOR 1
+#define WIN8_SRV_MINOR 1
+#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
+
+/*
+ * Global state maintained for transaction that is being processed.
+ * For a class of integration services, including the "file copy service",
+ * the specified protocol is a "request/response" protocol which means that
+ * there can only be single outstanding transaction from the host at any
+ * given point in time. We use this to simplify memory management in this
+ * driver - we cache and process only one message at a time.
+ *
+ * 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
+ * handled.
+ *
+ * The transaction "active" state is set when we receive a request from the
+ * host and we cleanup this state when the transaction is completed - when we
+ * respond to the host with our response. When the transaction active state is
+ * set, we defer handling incoming packets.
+ */
+
+static struct {
+ bool active; /* transaction status - active or not */
+ int recv_len; /* number of bytes received. */
+ struct hv_fcopy_hdr *fcopy_msg; /* current message */
+ struct hv_start_fcopy message; /* sent to daemon */
+ struct vmbus_channel *recv_channel; /* chn we got the request */
+ u64 recv_req_id; /* request ID. */
+ void *fcopy_context; /* for the channel callback */
+ struct semaphore read_sema;
+} fcopy_transaction;
+
+static bool opened; /* currently device opened */
+
+/*
+ * Before we can accept copy messages from the host, we need
+ * to handshake with the user level daemon. This state tracks
+ * if we are in the handshake phase.
+ */
+static bool in_hand_shake = true;
+static void fcopy_send_data(void);
+static void fcopy_respond_to_host(int error);
+static void fcopy_work_func(struct work_struct *dummy);
+static DECLARE_DELAYED_WORK(fcopy_work, fcopy_work_func);
+static u8 *recv_buffer;
+
+static void fcopy_work_func(struct work_struct *dummy)
+{
+ /*
+ * If the timer fires, the user-mode component has not responded;
+ * process the pending transaction.
+ */
+ fcopy_respond_to_host(HV_E_FAIL);
+}
+
+static int fcopy_handle_handshake(u32 version)
+{
+ switch (version) {
+ case FCOPY_CURRENT_VERSION:
+ break;
+ default:
+ /*
+ * For now we will fail the registration.
+ * If and when we have multiple versions to
+ * deal with, we will be backward compatible.
+ * We will add this code when needed.
+ */
+ return -EINVAL;
+ }
+ pr_info("FCP: user-mode registering done. Daemon version: %d\n",
+ version);
+ fcopy_transaction.active = false;
+ if (fcopy_transaction.fcopy_context)
+ hv_fcopy_onchannelcallback(fcopy_transaction.fcopy_context);
+ in_hand_shake = false;
+ return 0;
+}
+
+static void fcopy_send_data(void)
+{
+ struct hv_start_fcopy *smsg_out = &fcopy_transaction.message;
+ int operation = fcopy_transaction.fcopy_msg->operation;
+ struct hv_start_fcopy *smsg_in;
+
+ /*
+ * The strings sent from the host are encoded in
+ * in utf16; convert it to utf8 strings.
+ * The host assures us that the utf16 strings will not exceed
+ * 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 W_MAX_PATH -1 to guarantee
+ * that the strings can be properly terminated!
+ */
+
+ switch (operation) {
+ case START_FILE_COPY:
+ memset(smsg_out, 0, sizeof(struct hv_start_fcopy));
+ smsg_out->hdr.operation = operation;
+ smsg_in = (struct hv_start_fcopy *)fcopy_transaction.fcopy_msg;
+
+ utf16s_to_utf8s((wchar_t *)smsg_in->file_name, W_MAX_PATH,
+ UTF16_LITTLE_ENDIAN,
+ (__u8 *)smsg_out->file_name, W_MAX_PATH - 1);
+
+ utf16s_to_utf8s((wchar_t *)smsg_in->path_name, W_MAX_PATH,
+ UTF16_LITTLE_ENDIAN,
+ (__u8 *)smsg_out->path_name, W_MAX_PATH - 1);
+
+ smsg_out->copy_flags = smsg_in->copy_flags;
+ smsg_out->file_size = smsg_in->file_size;
+ break;
+
+ default:
+ break;
+ }
+ up(&fcopy_transaction.read_sema);
+ return;
+}
+
+/*
+ * Send a response back to the host.
+ */
+
+static void
+fcopy_respond_to_host(int error)
+{
+ struct icmsg_hdr *icmsghdr;
+ u32 buf_len;
+ struct vmbus_channel *channel;
+ u64 req_id;
+
+ /*
+ * Copy the global state for completing the transaction. Note that
+ * only one transaction can be active at a time. This is guaranteed
+ * by the file copy protocol implemented by the host. Furthermore,
+ * the "transaction active" state we maintain ensures that there can
+ * only be one active transaction at a time.
+ */
+
+ buf_len = fcopy_transaction.recv_len;
+ channel = fcopy_transaction.recv_channel;
+ req_id = fcopy_transaction.recv_req_id;
+
+ fcopy_transaction.active = false;
+
+ icmsghdr = (struct icmsg_hdr *)
+ &recv_buffer[sizeof(struct vmbuspipe_hdr)];
+
+ if (channel->onchannel_callback == NULL)
+ /*
+ * We have raced with util driver being unloaded;
+ * silently return.
+ */
+ return;
+
+ icmsghdr->status = error;
+ icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
+ vmbus_sendpacket(channel, recv_buffer, buf_len, req_id,
+ VM_PKT_DATA_INBAND, 0);
+}
+
+void hv_fcopy_onchannelcallback(void *context)
+{
+ struct vmbus_channel *channel = context;
+ u32 recvlen;
+ u64 requestid;
+ struct hv_fcopy_hdr *fcopy_msg;
+ struct icmsg_hdr *icmsghdr;
+ struct icmsg_negotiate *negop = NULL;
+ int util_fw_version;
+ int fcopy_srv_version;
+
+ if (fcopy_transaction.active) {
+ /*
+ * We will defer processing this callback once
+ * the current transaction is complete.
+ */
+ fcopy_transaction.fcopy_context = context;
+ return;
+ }
+
+ vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen,
+ &requestid);
+ if (recvlen <= 0)
+ return;
+
+ icmsghdr = (struct icmsg_hdr *)&recv_buffer[
+ sizeof(struct vmbuspipe_hdr)];
+ if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+ util_fw_version = UTIL_FW_VERSION;
+ fcopy_srv_version = WIN8_SRV_VERSION;
+ vmbus_prep_negotiate_resp(icmsghdr, negop, recv_buffer,
+ util_fw_version, fcopy_srv_version);
+ } else {
+ fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[
+ sizeof(struct vmbuspipe_hdr) +
+ sizeof(struct icmsg_hdr)];
+
+ /*
+ * Stash away this global state for completing the
+ * transaction; note transactions are serialized.
+ */
+
+ fcopy_transaction.active = true;
+ fcopy_transaction.recv_len = recvlen;
+ fcopy_transaction.recv_channel = channel;
+ fcopy_transaction.recv_req_id = requestid;
+ fcopy_transaction.fcopy_msg = fcopy_msg;
+
+ /*
+ * Send the information to the user-level daemon.
+ */
+ fcopy_send_data();
+ schedule_delayed_work(&fcopy_work, 5*HZ);
+ return;
+ }
+ icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
+ vmbus_sendpacket(channel, recv_buffer, recvlen, requestid,
+ VM_PKT_DATA_INBAND, 0);
+}
+
+/*
+ * Create a char device that can support read/write for passing
+ * the payload.
+ */
+
+static ssize_t fcopy_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ void *src;
+ size_t copy_size;
+ int operation;
+
+ /*
+ * Wait until there is something to be read.
+ */
+ if (down_interruptible(&fcopy_transaction.read_sema))
+ return -EINTR;
+
+ /*
+ * The channel may be rescinded and in this case, we will wakeup the
+ * the thread blocked on the semaphore and we will use the opened
+ * state to correctly handle this case.
+ */
+ if (!opened)
+ return -ENODEV;
+
+ operation = fcopy_transaction.fcopy_msg->operation;
+
+ if (operation == START_FILE_COPY) {
+ src = &fcopy_transaction.message;
+ copy_size = sizeof(struct hv_start_fcopy);
+ if (count < copy_size)
+ return 0;
+ } else {
+ src = fcopy_transaction.fcopy_msg;
+ copy_size = sizeof(struct hv_do_fcopy);
+ if (count < copy_size)
+ return 0;
+ }
+ if (copy_to_user(buf, src, copy_size))
+ return -EFAULT;
+
+ return copy_size;
+}
+
+static ssize_t fcopy_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int response = 0;
+
+ if (count != sizeof(int))
+ return -EINVAL;
+
+ if (copy_from_user(&response, buf, sizeof(int)))
+ return -EFAULT;
+
+ if (in_hand_shake) {
+ if (fcopy_handle_handshake(response))
+ return -EINVAL;
+ return sizeof(int);
+ }
+
+ /*
+ * Complete the transaction by forwarding the result
+ * to the host. But first, cancel the timeout.
+ */
+ if (cancel_delayed_work_sync(&fcopy_work))
+ fcopy_respond_to_host(response);
+
+ return sizeof(int);
+}
+
+static int fcopy_open(struct inode *inode, struct file *f)
+{
+ /*
+ * The user level daemon that will open this device is
+ * really an extension of this driver. We can have only
+ * active open at a time.
+ */
+ if (opened)
+ return -EBUSY;
+
+ /*
+ * The daemon is alive; setup the state.
+ */
+ opened = true;
+ return 0;
+}
+
+static int fcopy_release(struct inode *inode, struct file *f)
+{
+ /*
+ * The daemon has exited; reset the state.
+ */
+ in_hand_shake = true;
+ opened = false;
+ return 0;
+}
+
+
+static const struct file_operations fcopy_fops = {
+ .read = fcopy_read,
+ .write = fcopy_write,
+ .release = fcopy_release,
+ .open = fcopy_open,
+};
+
+static struct miscdevice fcopy_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "vmbus/hv_fcopy",
+ .fops = &fcopy_fops,
+};
+
+static int fcopy_dev_init(void)
+{
+ return misc_register(&fcopy_misc);
+}
+
+static void fcopy_dev_deinit(void)
+{
+
+ /*
+ * The device is going away - perhaps because the
+ * host has rescinded the channel. Setup state so that
+ * user level daemon can gracefully exit if it is blocked
+ * on the read semaphore.
+ */
+ opened = false;
+ /*
+ * Signal the semaphore as the device is
+ * going away.
+ */
+ up(&fcopy_transaction.read_sema);
+ misc_deregister(&fcopy_misc);
+}
+
+int hv_fcopy_init(struct hv_util_service *srv)
+{
+ recv_buffer = srv->recv_buffer;
+
+ /*
+ * When this driver loads, the user level daemon that
+ * processes the host requests may not yet be running.
+ * Defer processing channel callbacks until the daemon
+ * has registered.
+ */
+ fcopy_transaction.active = true;
+ sema_init(&fcopy_transaction.read_sema, 0);
+
+ return fcopy_dev_init();
+}
+
+void hv_fcopy_deinit(void)
+{
+ cancel_delayed_work_sync(&fcopy_work);
+ fcopy_dev_deinit();
+}
diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c
index 09988b289622..ea852537307e 100644
--- a/drivers/hv/hv_kvp.c
+++ b/drivers/hv/hv_kvp.c
@@ -113,7 +113,7 @@ kvp_register(int reg_value)
kvp_msg->kvp_hdr.operation = reg_value;
strcpy(version, HV_DRV_VERSION);
msg->len = sizeof(struct hv_kvp_msg);
- cn_netlink_send(msg, 0, GFP_ATOMIC);
+ cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
kfree(msg);
}
}
@@ -435,7 +435,7 @@ kvp_send_key(struct work_struct *dummy)
}
msg->len = sizeof(struct hv_kvp_msg);
- cn_netlink_send(msg, 0, GFP_ATOMIC);
+ cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
kfree(msg);
return;
diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c
index 0c3546224376..34f14fddb666 100644
--- a/drivers/hv/hv_snapshot.c
+++ b/drivers/hv/hv_snapshot.c
@@ -98,7 +98,7 @@ static void vss_send_op(struct work_struct *dummy)
vss_msg->vss_hdr.operation = op;
msg->len = sizeof(struct hv_vss_msg);
- cn_netlink_send(msg, 0, GFP_ATOMIC);
+ cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
kfree(msg);
return;
diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c
index 62dfd246b948..dd761806f0e8 100644
--- a/drivers/hv/hv_util.c
+++ b/drivers/hv/hv_util.c
@@ -28,6 +28,7 @@
#include <linux/reboot.h>
#include <linux/hyperv.h>
+#include "hyperv_vmbus.h"
#define SD_MAJOR 3
#define SD_MINOR 0
@@ -82,6 +83,12 @@ static struct hv_util_service util_vss = {
.util_deinit = hv_vss_deinit,
};
+static struct hv_util_service util_fcopy = {
+ .util_cb = hv_fcopy_onchannelcallback,
+ .util_init = hv_fcopy_init,
+ .util_deinit = hv_fcopy_deinit,
+};
+
static void perform_shutdown(struct work_struct *dummy)
{
orderly_poweroff(true);
@@ -401,6 +408,10 @@ static const struct hv_vmbus_device_id id_table[] = {
{ HV_VSS_GUID,
.driver_data = (unsigned long)&util_vss
},
+ /* File copy GUID */
+ { HV_FCOPY_GUID,
+ .driver_data = (unsigned long)&util_fcopy
+ },
{ },
};
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index e05517616a06..860134da8039 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -559,8 +559,8 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info, void *buffer,
void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);
int hv_ringbuffer_write(struct hv_ring_buffer_info *ring_info,
- struct scatterlist *sglist,
- u32 sgcount, bool *signal);
+ struct kvec *kv_list,
+ u32 kv_count, bool *signal);
int hv_ringbuffer_peek(struct hv_ring_buffer_info *ring_info, void *buffer,
u32 buflen);
@@ -669,5 +669,9 @@ int vmbus_set_event(struct vmbus_channel *channel);
void vmbus_on_event(unsigned long data);
+int hv_fcopy_init(struct hv_util_service *);
+void hv_fcopy_deinit(void);
+void hv_fcopy_onchannelcallback(void *);
+
#endif /* _HYPERV_VMBUS_H */
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c
index 26c93cf9f6be..15db66b74141 100644
--- a/drivers/hv/ring_buffer.c
+++ b/drivers/hv/ring_buffer.c
@@ -26,6 +26,7 @@
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/hyperv.h>
+#include <linux/uio.h>
#include "hyperv_vmbus.h"
@@ -387,23 +388,20 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
*
*/
int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
- struct scatterlist *sglist, u32 sgcount, bool *signal)
+ struct kvec *kv_list, u32 kv_count, bool *signal)
{
int i = 0;
u32 bytes_avail_towrite;
u32 bytes_avail_toread;
u32 totalbytes_towrite = 0;
- struct scatterlist *sg;
u32 next_write_location;
u32 old_write;
u64 prev_indices = 0;
unsigned long flags;
- for_each_sg(sglist, sg, sgcount, i)
- {
- totalbytes_towrite += sg->length;
- }
+ for (i = 0; i < kv_count; i++)
+ totalbytes_towrite += kv_list[i].iov_len;
totalbytes_towrite += sizeof(u64);
@@ -427,12 +425,11 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
old_write = next_write_location;
- for_each_sg(sglist, sg, sgcount, i)
- {
+ for (i = 0; i < kv_count; i++) {
next_write_location = hv_copyto_ringbuffer(outring_info,
next_write_location,
- sg_virt(sg),
- sg->length);
+ kv_list[i].iov_base,
+ kv_list[i].iov_len);
}
/* Set previous packet start */
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 3f0a95290e14..8e53a3c2607e 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -43,6 +43,12 @@ static struct tasklet_struct msg_dpc;
static struct completion probe_event;
static int irq;
+struct resource hyperv_mmio = {
+ .name = "hyperv mmio",
+ .flags = IORESOURCE_MEM,
+};
+EXPORT_SYMBOL_GPL(hyperv_mmio);
+
static int vmbus_exists(void)
{
if (hv_acpi_dev == NULL)
@@ -843,18 +849,21 @@ void vmbus_device_unregister(struct hv_device *device_obj)
/*
- * VMBUS is an acpi enumerated device. Get the the IRQ information
- * from DSDT.
+ * VMBUS is an acpi enumerated device. Get the the information we
+ * need from DSDT.
*/
-static acpi_status vmbus_walk_resources(struct acpi_resource *res, void *irq)
+static acpi_status vmbus_walk_resources(struct acpi_resource *res, void *ctx)
{
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IRQ:
+ irq = res->data.irq.interrupts[0];
+ break;
- if (res->type == ACPI_RESOURCE_TYPE_IRQ) {
- struct acpi_resource_irq *irqp;
- irqp = &res->data.irq;
-
- *((unsigned int *)irq) = irqp->interrupts[0];
+ case ACPI_RESOURCE_TYPE_ADDRESS64:
+ hyperv_mmio.start = res->data.address64.minimum;
+ hyperv_mmio.end = res->data.address64.maximum;
+ break;
}
return AE_OK;
@@ -863,18 +872,34 @@ static acpi_status vmbus_walk_resources(struct acpi_resource *res, void *irq)
static int vmbus_acpi_add(struct acpi_device *device)
{
acpi_status result;
+ int ret_val = -ENODEV;
hv_acpi_dev = device;
result = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
- vmbus_walk_resources, &irq);
+ vmbus_walk_resources, NULL);
- if (ACPI_FAILURE(result)) {
- complete(&probe_event);
- return -ENODEV;
+ if (ACPI_FAILURE(result))
+ goto acpi_walk_err;
+ /*
+ * The parent of the vmbus acpi device (Gen2 firmware) is the VMOD that
+ * has the mmio ranges. Get that.
+ */
+ if (device->parent) {
+ result = acpi_walk_resources(device->parent->handle,
+ METHOD_NAME__CRS,
+ vmbus_walk_resources, NULL);
+
+ if (ACPI_FAILURE(result))
+ goto acpi_walk_err;
+ if (hyperv_mmio.start && hyperv_mmio.end)
+ request_resource(&iomem_resource, &hyperv_mmio);
}
+ ret_val = 0;
+
+acpi_walk_err:
complete(&probe_event);
- return 0;
+ return ret_val;
}
static const struct acpi_device_id vmbus_acpi_device_ids[] = {
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 2209f28441e9..5c63f0918d12 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -155,6 +155,16 @@ config MCP3422
This driver can also be built as a module. If so, the module will be
called mcp3422.
+config MEN_Z188_ADC
+ tristate "MEN 16z188 ADC IP Core support"
+ depends on MCB
+ help
+ Say yes here to enable support for the MEN 16z188 ADC IP-Core on a MCB
+ carrier.
+
+ This driver can also be built as a module. If so, the module will be
+ called men_z188_adc.
+
config NAU7802
tristate "Nuvoton NAU7802 ADC driver"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index ba9a10a24cd0..85a4a045f1f0 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_MCP320X) += mcp320x.o
obj-$(CONFIG_MCP3422) += mcp3422.o
+obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
diff --git a/drivers/iio/adc/men_z188_adc.c b/drivers/iio/adc/men_z188_adc.c
new file mode 100644
index 000000000000..6989c16aec2b
--- /dev/null
+++ b/drivers/iio/adc/men_z188_adc.c
@@ -0,0 +1,172 @@
+/*
+ * MEN 16z188 Analog to Digial Converter
+ *
+ * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de)
+ * Author: Johannes Thumshirn <johannes.thumshirn@men.de>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mcb.h>
+#include <linux/io.h>
+#include <linux/iio/iio.h>
+
+#define Z188_ADC_MAX_CHAN 8
+#define Z188_ADC_GAIN 0x0700000
+#define Z188_MODE_VOLTAGE BIT(27)
+#define Z188_CFG_AUTO 0x1
+#define Z188_CTRL_REG 0x40
+
+#define ADC_DATA(x) (((x) >> 2) & 0x7ffffc)
+#define ADC_OVR(x) ((x) & 0x1)
+
+struct z188_adc {
+ struct resource *mem;
+ void __iomem *base;
+};
+
+#define Z188_ADC_CHANNEL(idx) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec z188_adc_iio_channels[] = {
+ Z188_ADC_CHANNEL(0),
+ Z188_ADC_CHANNEL(1),
+ Z188_ADC_CHANNEL(2),
+ Z188_ADC_CHANNEL(3),
+ Z188_ADC_CHANNEL(4),
+ Z188_ADC_CHANNEL(5),
+ Z188_ADC_CHANNEL(6),
+ Z188_ADC_CHANNEL(7),
+};
+
+static int z188_iio_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long info)
+{
+ struct z188_adc *adc = iio_priv(iio_dev);
+ int ret;
+ u16 tmp;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ tmp = readw(adc->base + chan->channel * 4);
+
+ if (ADC_OVR(tmp)) {
+ dev_info(&iio_dev->dev,
+ "Oversampling error on ADC channel %d\n",
+ chan->channel);
+ return -EIO;
+ }
+ *val = ADC_DATA(tmp);
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct iio_info z188_adc_info = {
+ .read_raw = &z188_iio_read_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static void men_z188_config_channels(void __iomem *addr)
+{
+ int i;
+ u32 cfg;
+ u32 ctl;
+
+ ctl = readl(addr + Z188_CTRL_REG);
+ ctl |= Z188_CFG_AUTO;
+ writel(ctl, addr + Z188_CTRL_REG);
+
+ for (i = 0; i < Z188_ADC_MAX_CHAN; i++) {
+ cfg = readl(addr + i);
+ cfg &= ~Z188_ADC_GAIN;
+ cfg |= Z188_MODE_VOLTAGE;
+ writel(cfg, addr + i);
+ }
+}
+
+static int men_z188_probe(struct mcb_device *dev,
+ const struct mcb_device_id *id)
+{
+ struct z188_adc *adc;
+ struct iio_dev *indio_dev;
+ struct resource *mem;
+
+ indio_dev = devm_iio_device_alloc(&dev->dev, sizeof(struct z188_adc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(indio_dev);
+ indio_dev->name = "z188-adc";
+ indio_dev->dev.parent = &dev->dev;
+ indio_dev->info = &z188_adc_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = z188_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(z188_adc_iio_channels);
+
+ mem = mcb_request_mem(dev, "z188-adc");
+ if (!mem)
+ return -ENOMEM;
+
+ adc->base = ioremap(mem->start, resource_size(mem));
+ if (adc->base == NULL)
+ goto err;
+
+ men_z188_config_channels(adc->base);
+
+ adc->mem = mem;
+ mcb_set_drvdata(dev, indio_dev);
+
+ return iio_device_register(indio_dev);
+
+err:
+ mcb_release_mem(mem);
+ return -ENXIO;
+}
+
+static void men_z188_remove(struct mcb_device *dev)
+{
+ struct iio_dev *indio_dev = mcb_get_drvdata(dev);
+ struct z188_adc *adc = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iounmap(adc->base);
+ mcb_release_mem(adc->mem);
+}
+
+static const struct mcb_device_id men_z188_ids[] = {
+ { .device = 0xbc },
+};
+MODULE_DEVICE_TABLE(mcb, men_z188_ids);
+
+static struct mcb_driver men_z188_driver = {
+ .driver = {
+ .name = "z188-adc",
+ .owner = THIS_MODULE,
+ },
+ .probe = men_z188_probe,
+ .remove = men_z188_remove,
+ .id_table = men_z188_ids,
+};
+module_mcb_driver(men_z188_driver);
+
+MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IIO ADC driver for MEN 16z188 ADC Core");
+MODULE_ALIAS("mcb:16z188");
diff --git a/drivers/mcb/Kconfig b/drivers/mcb/Kconfig
new file mode 100644
index 000000000000..e9a6976e1010
--- /dev/null
+++ b/drivers/mcb/Kconfig
@@ -0,0 +1,31 @@
+#
+# MEN Chameleon Bus (MCB) support
+#
+
+menuconfig MCB
+ tristate "MCB support"
+ default n
+ depends on HAS_IOMEM
+ help
+
+ The MCB (MEN Chameleon Bus) is a Bus specific to MEN Mikroelektronik
+ FPGA based devices. It is used to identify MCB based IP-Cores within
+ an FPGA and provide the necessary framework for instantiating drivers
+ for these devices.
+
+ If build as a module, the module is called mcb.ko
+
+if MCB
+config MCB_PCI
+ tristate "PCI based MCB carrier"
+ default n
+ depends on PCI
+ help
+
+ This is a MCB carrier on a PCI device. Both PCI attached on-board
+ FPGAs as well as CompactPCI attached MCB FPGAs are supported with
+ this driver.
+
+ If build as a module, the module is called mcb-pci.ko
+
+endif # MCB
diff --git a/drivers/mcb/Makefile b/drivers/mcb/Makefile
new file mode 100644
index 000000000000..1ae141311def
--- /dev/null
+++ b/drivers/mcb/Makefile
@@ -0,0 +1,7 @@
+
+obj-$(CONFIG_MCB) += mcb.o
+
+mcb-y += mcb-core.o
+mcb-y += mcb-parse.o
+
+obj-$(CONFIG_MCB_PCI) += mcb-pci.o
diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c
new file mode 100644
index 000000000000..bbe12932d404
--- /dev/null
+++ b/drivers/mcb/mcb-core.c
@@ -0,0 +1,414 @@
+/*
+ * MEN Chameleon Bus.
+ *
+ * Copyright (C) 2013 MEN Mikroelektronik GmbH (www.men.de)
+ * Author: Johannes Thumshirn <johannes.thumshirn@men.de>
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/idr.h>
+#include <linux/mcb.h>
+
+static DEFINE_IDA(mcb_ida);
+
+static const struct mcb_device_id *mcb_match_id(const struct mcb_device_id *ids,
+ struct mcb_device *dev)
+{
+ if (ids) {
+ while (ids->device) {
+ if (ids->device == dev->id)
+ return ids;
+ ids++;
+ }
+ }
+
+ return NULL;
+}
+
+static int mcb_match(struct device *dev, struct device_driver *drv)
+{
+ struct mcb_driver *mdrv = to_mcb_driver(drv);
+ struct mcb_device *mdev = to_mcb_device(dev);
+ const struct mcb_device_id *found_id;
+
+ found_id = mcb_match_id(mdrv->id_table, mdev);
+ if (found_id)
+ return 1;
+
+ return 0;
+}
+
+static int mcb_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct mcb_device *mdev = to_mcb_device(dev);
+ int ret;
+
+ ret = add_uevent_var(env, "MODALIAS=mcb:16z%03d", mdev->id);
+ if (ret)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int mcb_probe(struct device *dev)
+{
+ struct mcb_driver *mdrv = to_mcb_driver(dev->driver);
+ struct mcb_device *mdev = to_mcb_device(dev);
+ const struct mcb_device_id *found_id;
+
+ found_id = mcb_match_id(mdrv->id_table, mdev);
+ if (!found_id)
+ return -ENODEV;
+
+ return mdrv->probe(mdev, found_id);
+}
+
+static int mcb_remove(struct device *dev)
+{
+ struct mcb_driver *mdrv = to_mcb_driver(dev->driver);
+ struct mcb_device *mdev = to_mcb_device(dev);
+
+ mdrv->remove(mdev);
+
+ put_device(&mdev->dev);
+
+ return 0;
+}
+
+static void mcb_shutdown(struct device *dev)
+{
+ struct mcb_device *mdev = to_mcb_device(dev);
+ struct mcb_driver *mdrv = mdev->driver;
+
+ if (mdrv && mdrv->shutdown)
+ mdrv->shutdown(mdev);
+}
+
+static struct bus_type mcb_bus_type = {
+ .name = "mcb",
+ .match = mcb_match,
+ .uevent = mcb_uevent,
+ .probe = mcb_probe,
+ .remove = mcb_remove,
+ .shutdown = mcb_shutdown,
+};
+
+/**
+ * __mcb_register_driver() - Register a @mcb_driver at the system
+ * @drv: The @mcb_driver
+ * @owner: The @mcb_driver's module
+ * @mod_name: The name of the @mcb_driver's module
+ *
+ * Register a @mcb_driver at the system. Perform some sanity checks, if
+ * the .probe and .remove methods are provided by the driver.
+ */
+int __mcb_register_driver(struct mcb_driver *drv, struct module *owner,
+ const char *mod_name)
+{
+ if (!drv->probe || !drv->remove)
+ return -EINVAL;
+
+ drv->driver.owner = owner;
+ drv->driver.bus = &mcb_bus_type;
+ drv->driver.mod_name = mod_name;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__mcb_register_driver);
+
+/**
+ * mcb_unregister_driver() - Unregister a @mcb_driver from the system
+ * @drv: The @mcb_driver
+ *
+ * Unregister a @mcb_driver from the system.
+ */
+void mcb_unregister_driver(struct mcb_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(mcb_unregister_driver);
+
+static void mcb_release_dev(struct device *dev)
+{
+ struct mcb_device *mdev = to_mcb_device(dev);
+
+ mcb_bus_put(mdev->bus);
+ kfree(mdev);
+}
+
+/**
+ * mcb_device_register() - Register a mcb_device
+ * @bus: The @mcb_bus of the device
+ * @dev: The @mcb_device
+ *
+ * Register a specific @mcb_device at a @mcb_bus and the system itself.
+ */
+int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev)
+{
+ int ret;
+ int device_id;
+
+ device_initialize(&dev->dev);
+ dev->dev.bus = &mcb_bus_type;
+ dev->dev.parent = bus->dev.parent;
+ dev->dev.release = mcb_release_dev;
+
+ device_id = dev->id;
+ dev_set_name(&dev->dev, "mcb%d-16z%03d-%d:%d:%d",
+ bus->bus_nr, device_id, dev->inst, dev->group, dev->var);
+
+ ret = device_add(&dev->dev);
+ if (ret < 0) {
+ pr_err("Failed registering device 16z%03d on bus mcb%d (%d)\n",
+ device_id, bus->bus_nr, ret);
+ goto out;
+ }
+
+ return 0;
+
+out:
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mcb_device_register);
+
+/**
+ * mcb_alloc_bus() - Allocate a new @mcb_bus
+ *
+ * Allocate a new @mcb_bus.
+ */
+struct mcb_bus *mcb_alloc_bus(void)
+{
+ struct mcb_bus *bus;
+ int bus_nr;
+
+ bus = kzalloc(sizeof(struct mcb_bus), GFP_KERNEL);
+ if (!bus)
+ return NULL;
+
+ bus_nr = ida_simple_get(&mcb_ida, 0, 0, GFP_KERNEL);
+ if (bus_nr < 0) {
+ kfree(bus);
+ return ERR_PTR(bus_nr);
+ }
+
+ INIT_LIST_HEAD(&bus->children);
+ bus->bus_nr = bus_nr;
+
+ return bus;
+}
+EXPORT_SYMBOL_GPL(mcb_alloc_bus);
+
+static int __mcb_devices_unregister(struct device *dev, void *data)
+{
+ device_unregister(dev);
+ return 0;
+}
+
+static void mcb_devices_unregister(struct mcb_bus *bus)
+{
+ bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_devices_unregister);
+}
+/**
+ * mcb_release_bus() - Free a @mcb_bus
+ * @bus: The @mcb_bus to release
+ *
+ * Release an allocated @mcb_bus from the system.
+ */
+void mcb_release_bus(struct mcb_bus *bus)
+{
+ mcb_devices_unregister(bus);
+
+ ida_simple_remove(&mcb_ida, bus->bus_nr);
+
+ kfree(bus);
+}
+EXPORT_SYMBOL_GPL(mcb_release_bus);
+
+/**
+ * mcb_bus_put() - Increment refcnt
+ * @bus: The @mcb_bus
+ *
+ * Get a @mcb_bus' ref
+ */
+struct mcb_bus *mcb_bus_get(struct mcb_bus *bus)
+{
+ if (bus)
+ get_device(&bus->dev);
+
+ return bus;
+}
+EXPORT_SYMBOL_GPL(mcb_bus_get);
+
+/**
+ * mcb_bus_put() - Decrement refcnt
+ * @bus: The @mcb_bus
+ *
+ * Release a @mcb_bus' ref
+ */
+void mcb_bus_put(struct mcb_bus *bus)
+{
+ if (bus)
+ put_device(&bus->dev);
+}
+EXPORT_SYMBOL_GPL(mcb_bus_put);
+
+/**
+ * mcb_alloc_dev() - Allocate a device
+ * @bus: The @mcb_bus the device is part of
+ *
+ * Allocate a @mcb_device and add bus.
+ */
+struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus)
+{
+ struct mcb_device *dev;
+
+ dev = kzalloc(sizeof(struct mcb_device), GFP_KERNEL);
+ if (!dev)
+ return NULL;
+
+ INIT_LIST_HEAD(&dev->bus_list);
+ dev->bus = bus;
+
+ return dev;
+}
+EXPORT_SYMBOL_GPL(mcb_alloc_dev);
+
+/**
+ * mcb_free_dev() - Free @mcb_device
+ * @dev: The device to free
+ *
+ * Free a @mcb_device
+ */
+void mcb_free_dev(struct mcb_device *dev)
+{
+ kfree(dev);
+}
+EXPORT_SYMBOL_GPL(mcb_free_dev);
+
+static int __mcb_bus_add_devices(struct device *dev, void *data)
+{
+ struct mcb_device *mdev = to_mcb_device(dev);
+ int retval;
+
+ if (mdev->is_added)
+ return 0;
+
+ retval = device_attach(dev);
+ if (retval < 0)
+ dev_err(dev, "Error adding device (%d)\n", retval);
+
+ mdev->is_added = true;
+
+ return 0;
+}
+
+static int __mcb_bus_add_child(struct device *dev, void *data)
+{
+ struct mcb_device *mdev = to_mcb_device(dev);
+ struct mcb_bus *child;
+
+ BUG_ON(!mdev->is_added);
+ child = mdev->subordinate;
+
+ if (child)
+ mcb_bus_add_devices(child);
+
+ return 0;
+}
+
+/**
+ * mcb_bus_add_devices() - Add devices in the bus' internal device list
+ * @bus: The @mcb_bus we add the devices
+ *
+ * Add devices in the bus' internal device list to the system.
+ */
+void mcb_bus_add_devices(const struct mcb_bus *bus)
+{
+ bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_bus_add_devices);
+ bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_bus_add_child);
+
+}
+EXPORT_SYMBOL_GPL(mcb_bus_add_devices);
+
+/**
+ * mcb_request_mem() - Request memory
+ * @dev: The @mcb_device the memory is for
+ * @name: The name for the memory reference.
+ *
+ * Request memory for a @mcb_device. If @name is NULL the driver name will
+ * be used.
+ */
+struct resource *mcb_request_mem(struct mcb_device *dev, const char *name)
+{
+ struct resource *mem;
+ u32 size;
+
+ if (!name)
+ name = dev->dev.driver->name;
+
+ size = resource_size(&dev->mem);
+
+ mem = request_mem_region(dev->mem.start, size, name);
+ if (!mem)
+ return ERR_PTR(-EBUSY);
+
+ return mem;
+}
+EXPORT_SYMBOL_GPL(mcb_request_mem);
+
+/**
+ * mcb_release_mem() - Release memory requested by device
+ * @dev: The @mcb_device that requested the memory
+ *
+ * Release memory that was prior requested via @mcb_request_mem().
+ */
+void mcb_release_mem(struct resource *mem)
+{
+ u32 size;
+
+ size = resource_size(mem);
+ release_mem_region(mem->start, size);
+}
+EXPORT_SYMBOL_GPL(mcb_release_mem);
+
+/**
+ * mcb_get_irq() - Get device's IRQ number
+ * @dev: The @mcb_device the IRQ is for
+ *
+ * Get the IRQ number of a given @mcb_device.
+ */
+int mcb_get_irq(struct mcb_device *dev)
+{
+ struct resource *irq = &dev->irq;
+
+ return irq->start;
+}
+EXPORT_SYMBOL_GPL(mcb_get_irq);
+
+static int mcb_init(void)
+{
+ return bus_register(&mcb_bus_type);
+}
+
+static void mcb_exit(void)
+{
+ bus_unregister(&mcb_bus_type);
+}
+
+/* mcb must be initialized after PCI but before the chameleon drivers.
+ * That means we must use some initcall between subsys_initcall and
+ * device_initcall.
+ */
+fs_initcall(mcb_init);
+module_exit(mcb_exit);
+
+MODULE_DESCRIPTION("MEN Chameleon Bus Driver");
+MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mcb/mcb-internal.h b/drivers/mcb/mcb-internal.h
new file mode 100644
index 000000000000..f956ef26c0ce
--- /dev/null
+++ b/drivers/mcb/mcb-internal.h
@@ -0,0 +1,118 @@
+#ifndef __MCB_INTERNAL
+#define __MCB_INTERNAL
+
+#include <linux/types.h>
+
+#define PCI_VENDOR_ID_MEN 0x1a88
+#define PCI_DEVICE_ID_MEN_CHAMELEON 0x4d45
+#define CHAMELEON_FILENAME_LEN 12
+#define CHAMELEONV2_MAGIC 0xabce
+
+enum chameleon_descriptor_type {
+ CHAMELEON_DTYPE_GENERAL = 0x0,
+ CHAMELEON_DTYPE_BRIDGE = 0x1,
+ CHAMELEON_DTYPE_CPU = 0x2,
+ CHAMELEON_DTYPE_BAR = 0x3,
+ CHAMELEON_DTYPE_END = 0xf,
+};
+
+enum chameleon_bus_type {
+ CHAMELEON_BUS_WISHBONE,
+ CHAMELEON_BUS_AVALON,
+ CHAMELEON_BUS_LPC,
+ CHAMELEON_BUS_ISA,
+};
+
+/**
+ * struct chameleon_fpga_header
+ *
+ * @revision: Revison of Chameleon table in FPGA
+ * @model: Chameleon table model ASCII char
+ * @minor: Revision minor
+ * @bus_type: Bus type (usually %CHAMELEON_BUS_WISHBONE)
+ * @magic: Chameleon header magic number (0xabce for version 2)
+ * @reserved: Reserved
+ * @filename: Filename of FPGA bitstream
+ */
+struct chameleon_fpga_header {
+ u8 revision;
+ char model;
+ u8 minor;
+ u8 bus_type;
+ u16 magic;
+ u16 reserved;
+ /* This one has no '\0' at the end!!! */
+ char filename[CHAMELEON_FILENAME_LEN];
+} __packed;
+#define HEADER_MAGIC_OFFSET 0x4
+
+/**
+ * struct chameleon_gdd - Chameleon General Device Descriptor
+ *
+ * @irq: the position in the FPGA's IRQ controller vector
+ * @rev: the revision of the variant's implementation
+ * @var: the variant of the IP core
+ * @dev: the device the IP core is
+ * @dtype: device descriptor type
+ * @bar: BAR offset that must be added to module offset
+ * @inst: the instance number of the device, 0 is first instance
+ * @group: the group the device belongs to (0 = no group)
+ * @reserved: reserved
+ * @offset: beginning of the address window of desired module
+ * @size: size of the module's address window
+ */
+struct chameleon_gdd {
+ __le32 reg1;
+ __le32 reg2;
+ __le32 offset;
+ __le32 size;
+
+} __packed;
+
+/* GDD Register 1 fields */
+#define GDD_IRQ(x) ((x) & 0x1f)
+#define GDD_REV(x) (((x) >> 5) & 0x3f)
+#define GDD_VAR(x) (((x) >> 11) & 0x3f)
+#define GDD_DEV(x) (((x) >> 18) & 0x3ff)
+#define GDD_DTY(x) (((x) >> 28) & 0xf)
+
+/* GDD Register 2 fields */
+#define GDD_BAR(x) ((x) & 0x7)
+#define GDD_INS(x) (((x) >> 3) & 0x3f)
+#define GDD_GRP(x) (((x) >> 9) & 0x3f)
+
+/**
+ * struct chameleon_bdd - Chameleon Bridge Device Descriptor
+ *
+ * @irq: the position in the FPGA's IRQ controller vector
+ * @rev: the revision of the variant's implementation
+ * @var: the variant of the IP core
+ * @dev: the device the IP core is
+ * @dtype: device descriptor type
+ * @bar: BAR offset that must be added to module offset
+ * @inst: the instance number of the device, 0 is first instance
+ * @dbar: destination bar from the bus _behind_ the bridge
+ * @chamoff: offset within the BAR of the source bus
+ * @offset:
+ * @size:
+ */
+struct chameleon_bdd {
+ unsigned int irq:6;
+ unsigned int rev:6;
+ unsigned int var:6;
+ unsigned int dev:10;
+ unsigned int dtype:4;
+ unsigned int bar:3;
+ unsigned int inst:6;
+ unsigned int dbar:3;
+ unsigned int group:6;
+ unsigned int reserved:14;
+ u32 chamoff;
+ u32 offset;
+ u32 size;
+} __packed;
+
+int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase,
+ void __iomem *base);
+
+#endif
diff --git a/drivers/mcb/mcb-parse.c b/drivers/mcb/mcb-parse.c
new file mode 100644
index 000000000000..d1278b5f3028
--- /dev/null
+++ b/drivers/mcb/mcb-parse.c
@@ -0,0 +1,159 @@
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/mcb.h>
+
+#include "mcb-internal.h"
+
+struct mcb_parse_priv {
+ phys_addr_t mapbase;
+ void __iomem *base;
+};
+
+#define for_each_chameleon_cell(dtype, p) \
+ for ((dtype) = get_next_dtype((p)); \
+ (dtype) != CHAMELEON_DTYPE_END; \
+ (dtype) = get_next_dtype((p)))
+
+static inline uint32_t get_next_dtype(void __iomem *p)
+{
+ uint32_t dtype;
+
+ dtype = readl(p);
+ return dtype >> 28;
+}
+
+static int chameleon_parse_bdd(struct mcb_bus *bus,
+ phys_addr_t mapbase,
+ void __iomem *base)
+{
+ return 0;
+}
+
+static int chameleon_parse_gdd(struct mcb_bus *bus,
+ phys_addr_t mapbase,
+ void __iomem *base)
+{
+ struct chameleon_gdd __iomem *gdd =
+ (struct chameleon_gdd __iomem *) base;
+ struct mcb_device *mdev;
+ u32 offset;
+ u32 size;
+ int ret;
+ __le32 reg1;
+ __le32 reg2;
+
+ mdev = mcb_alloc_dev(bus);
+ if (!mdev)
+ return -ENOMEM;
+
+ reg1 = readl(&gdd->reg1);
+ reg2 = readl(&gdd->reg2);
+ offset = readl(&gdd->offset);
+ size = readl(&gdd->size);
+
+ mdev->id = GDD_DEV(reg1);
+ mdev->rev = GDD_REV(reg1);
+ mdev->var = GDD_VAR(reg1);
+ mdev->bar = GDD_BAR(reg1);
+ mdev->group = GDD_GRP(reg2);
+ mdev->inst = GDD_INS(reg2);
+
+ pr_debug("Found a 16z%03d\n", mdev->id);
+
+ mdev->irq.start = GDD_IRQ(reg1);
+ mdev->irq.end = GDD_IRQ(reg1);
+ mdev->irq.flags = IORESOURCE_IRQ;
+
+ mdev->mem.start = mapbase + offset;
+ mdev->mem.end = mdev->mem.start + size - 1;
+ mdev->mem.flags = IORESOURCE_MEM;
+
+ mdev->is_added = false;
+
+ ret = mcb_device_register(bus, mdev);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+
+err:
+ mcb_free_dev(mdev);
+
+ return ret;
+}
+
+int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase,
+ void __iomem *base)
+{
+ char __iomem *p = base;
+ struct chameleon_fpga_header *header;
+ uint32_t dtype;
+ int num_cells = 0;
+ int ret = 0;
+ u32 hsize;
+
+ hsize = sizeof(struct chameleon_fpga_header);
+
+ header = kzalloc(hsize, GFP_KERNEL);
+ if (!header)
+ return -ENOMEM;
+
+ /* Extract header information */
+ memcpy_fromio(header, p, hsize);
+ /* We only support chameleon v2 at the moment */
+ header->magic = le16_to_cpu(header->magic);
+ if (header->magic != CHAMELEONV2_MAGIC) {
+ pr_err("Unsupported chameleon version 0x%x\n",
+ header->magic);
+ kfree(header);
+ return -ENODEV;
+ }
+ p += hsize;
+
+ pr_debug("header->revision = %d\n", header->revision);
+ pr_debug("header->model = 0x%x ('%c')\n", header->model,
+ header->model);
+ pr_debug("header->minor = %d\n", header->minor);
+ pr_debug("header->bus_type = 0x%x\n", header->bus_type);
+
+
+ pr_debug("header->magic = 0x%x\n", header->magic);
+ pr_debug("header->filename = \"%.*s\"\n", CHAMELEON_FILENAME_LEN,
+ header->filename);
+
+ for_each_chameleon_cell(dtype, p) {
+ switch (dtype) {
+ case CHAMELEON_DTYPE_GENERAL:
+ ret = chameleon_parse_gdd(bus, mapbase, p);
+ if (ret < 0)
+ goto out;
+ p += sizeof(struct chameleon_gdd);
+ break;
+ case CHAMELEON_DTYPE_BRIDGE:
+ chameleon_parse_bdd(bus, mapbase, p);
+ p += sizeof(struct chameleon_bdd);
+ break;
+ case CHAMELEON_DTYPE_END:
+ break;
+ default:
+ pr_err("Invalid chameleon descriptor type 0x%x\n",
+ dtype);
+ return -EINVAL;
+ }
+ num_cells++;
+ }
+
+ if (num_cells == 0)
+ num_cells = -EINVAL;
+
+ kfree(header);
+ return num_cells;
+
+out:
+ kfree(header);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(chameleon_parse_cells);
diff --git a/drivers/mcb/mcb-pci.c b/drivers/mcb/mcb-pci.c
new file mode 100644
index 000000000000..99c742cbfb5b
--- /dev/null
+++ b/drivers/mcb/mcb-pci.c
@@ -0,0 +1,114 @@
+/*
+ * MEN Chameleon Bus.
+ *
+ * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de)
+ * Author: Johannes Thumshirn <johannes.thumshirn@men.de>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/mcb.h>
+
+#include "mcb-internal.h"
+
+struct priv {
+ struct mcb_bus *bus;
+ void __iomem *base;
+};
+
+static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct priv *priv;
+ phys_addr_t mapbase;
+ int ret;
+ int num_cells;
+ unsigned long flags;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = pci_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to enable PCI device\n");
+ return -ENODEV;
+ }
+
+ mapbase = pci_resource_start(pdev, 0);
+ if (!mapbase) {
+ dev_err(&pdev->dev, "No PCI resource\n");
+ goto err_start;
+ }
+
+ ret = pci_request_region(pdev, 0, KBUILD_MODNAME);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request PCI BARs\n");
+ goto err_start;
+ }
+
+ priv->base = pci_iomap(pdev, 0, 0);
+ if (!priv->base) {
+ dev_err(&pdev->dev, "Cannot ioremap\n");
+ ret = -ENOMEM;
+ goto err_ioremap;
+ }
+
+ flags = pci_resource_flags(pdev, 0);
+ if (flags & IORESOURCE_IO) {
+ ret = -ENOTSUPP;
+ dev_err(&pdev->dev,
+ "IO mapped PCI devices are not supported\n");
+ goto err_ioremap;
+ }
+
+ pci_set_drvdata(pdev, priv);
+
+ priv->bus = mcb_alloc_bus();
+
+ ret = chameleon_parse_cells(priv->bus, mapbase, priv->base);
+ if (ret < 0)
+ goto err_drvdata;
+ num_cells = ret;
+
+ dev_dbg(&pdev->dev, "Found %d cells\n", num_cells);
+
+ mcb_bus_add_devices(priv->bus);
+
+err_drvdata:
+ pci_iounmap(pdev, priv->base);
+err_ioremap:
+ pci_release_region(pdev, 0);
+err_start:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void mcb_pci_remove(struct pci_dev *pdev)
+{
+ struct priv *priv = pci_get_drvdata(pdev);
+
+ mcb_release_bus(priv->bus);
+}
+
+static const struct pci_device_id mcb_pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_MEN, PCI_DEVICE_ID_MEN_CHAMELEON) },
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, mcb_pci_tbl);
+
+static struct pci_driver mcb_pci_driver = {
+ .name = "mcb-pci",
+ .id_table = mcb_pci_tbl,
+ .probe = mcb_pci_probe,
+ .remove = mcb_pci_remove,
+};
+
+module_pci_driver(mcb_pci_driver);
+
+MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MCB over PCI support");
diff --git a/drivers/md/dm-log-userspace-transfer.c b/drivers/md/dm-log-userspace-transfer.c
index 08d9a207259a..b428c0ae63d5 100644
--- a/drivers/md/dm-log-userspace-transfer.c
+++ b/drivers/md/dm-log-userspace-transfer.c
@@ -66,7 +66,7 @@ static int dm_ulog_sendto_server(struct dm_ulog_request *tfr)
msg->seq = tfr->seq;
msg->len = sizeof(struct dm_ulog_request) + tfr->data_size;
- r = cn_netlink_send(msg, 0, gfp_any());
+ r = cn_netlink_send(msg, 0, 0, gfp_any());
return r;
}
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index 29a11db365bc..c59e9c96e86d 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -7,6 +7,17 @@ menuconfig MEMORY
if MEMORY
+config TI_AEMIF
+ tristate "Texas Instruments AEMIF driver"
+ depends on (ARCH_DAVINCI || ARCH_KEYSTONE) && OF
+ help
+ This driver is for the AEMIF module available in Texas Instruments
+ SoCs. AEMIF stands for Asynchronous External Memory Interface and
+ is intended to provide a glue-less interface to a variety of
+ asynchronuous memory devices like ASRAM, NOR and NAND memory. A total
+ of 256M bytes of any of these memories can be accessed at a given
+ time via four chip selects with 64M byte access per chip select.
+
config TI_EMIF
tristate "Texas Instruments EMIF driver"
depends on ARCH_OMAP2PLUS
@@ -50,4 +61,8 @@ config TEGRA30_MC
analysis, especially for IOMMU/SMMU(System Memory Management
Unit) module.
+config FSL_IFC
+ bool
+ depends on FSL_SOC
+
endif
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index 969d923dad93..71160a2b7313 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -5,7 +5,9 @@
ifeq ($(CONFIG_DDR),y)
obj-$(CONFIG_OF) += of_memory.o
endif
+obj-$(CONFIG_TI_AEMIF) += ti-aemif.o
obj-$(CONFIG_TI_EMIF) += emif.o
+obj-$(CONFIG_FSL_IFC) += fsl_ifc.o
obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o
obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o
obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o
diff --git a/drivers/memory/fsl_ifc.c b/drivers/memory/fsl_ifc.c
new file mode 100644
index 000000000000..3d5d792d5cb2
--- /dev/null
+++ b/drivers/memory/fsl_ifc.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2011 Freescale Semiconductor, Inc
+ *
+ * Freescale Integrated Flash Controller
+ *
+ * Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
+ *
+ * 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.
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_ifc.h>
+#include <asm/prom.h>
+
+struct fsl_ifc_ctrl *fsl_ifc_ctrl_dev;
+EXPORT_SYMBOL(fsl_ifc_ctrl_dev);
+
+/*
+ * convert_ifc_address - convert the base address
+ * @addr_base: base address of the memory bank
+ */
+unsigned int convert_ifc_address(phys_addr_t addr_base)
+{
+ return addr_base & CSPR_BA;
+}
+EXPORT_SYMBOL(convert_ifc_address);
+
+/*
+ * fsl_ifc_find - find IFC bank
+ * @addr_base: base address of the memory bank
+ *
+ * This function walks IFC banks comparing "Base address" field of the CSPR
+ * registers with the supplied addr_base argument. When bases match this
+ * function returns bank number (starting with 0), otherwise it returns
+ * appropriate errno value.
+ */
+int fsl_ifc_find(phys_addr_t addr_base)
+{
+ int i = 0;
+
+ if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->regs)
+ return -ENODEV;
+
+ for (i = 0; i < ARRAY_SIZE(fsl_ifc_ctrl_dev->regs->cspr_cs); i++) {
+ u32 cspr = in_be32(&fsl_ifc_ctrl_dev->regs->cspr_cs[i].cspr);
+ if (cspr & CSPR_V && (cspr & CSPR_BA) ==
+ convert_ifc_address(addr_base))
+ return i;
+ }
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL(fsl_ifc_find);
+
+static int fsl_ifc_ctrl_init(struct fsl_ifc_ctrl *ctrl)
+{
+ struct fsl_ifc_regs __iomem *ifc = ctrl->regs;
+
+ /*
+ * Clear all the common status and event registers
+ */
+ if (in_be32(&ifc->cm_evter_stat) & IFC_CM_EVTER_STAT_CSER)
+ out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER);
+
+ /* enable all error and events */
+ out_be32(&ifc->cm_evter_en, IFC_CM_EVTER_EN_CSEREN);
+
+ /* enable all error and event interrupts */
+ out_be32(&ifc->cm_evter_intr_en, IFC_CM_EVTER_INTR_EN_CSERIREN);
+ out_be32(&ifc->cm_erattr0, 0x0);
+ out_be32(&ifc->cm_erattr1, 0x0);
+
+ return 0;
+}
+
+static int fsl_ifc_ctrl_remove(struct platform_device *dev)
+{
+ struct fsl_ifc_ctrl *ctrl = dev_get_drvdata(&dev->dev);
+
+ free_irq(ctrl->nand_irq, ctrl);
+ free_irq(ctrl->irq, ctrl);
+
+ irq_dispose_mapping(ctrl->nand_irq);
+ irq_dispose_mapping(ctrl->irq);
+
+ iounmap(ctrl->regs);
+
+ dev_set_drvdata(&dev->dev, NULL);
+ kfree(ctrl);
+
+ return 0;
+}
+
+/*
+ * NAND events are split between an operational interrupt which only
+ * receives OPC, and an error interrupt that receives everything else,
+ * including non-NAND errors. Whichever interrupt gets to it first
+ * records the status and wakes the wait queue.
+ */
+static DEFINE_SPINLOCK(nand_irq_lock);
+
+static u32 check_nand_stat(struct fsl_ifc_ctrl *ctrl)
+{
+ struct fsl_ifc_regs __iomem *ifc = ctrl->regs;
+ unsigned long flags;
+ u32 stat;
+
+ spin_lock_irqsave(&nand_irq_lock, flags);
+
+ stat = in_be32(&ifc->ifc_nand.nand_evter_stat);
+ if (stat) {
+ out_be32(&ifc->ifc_nand.nand_evter_stat, stat);
+ ctrl->nand_stat = stat;
+ wake_up(&ctrl->nand_wait);
+ }
+
+ spin_unlock_irqrestore(&nand_irq_lock, flags);
+
+ return stat;
+}
+
+static irqreturn_t fsl_ifc_nand_irq(int irqno, void *data)
+{
+ struct fsl_ifc_ctrl *ctrl = data;
+
+ if (check_nand_stat(ctrl))
+ return IRQ_HANDLED;
+
+ return IRQ_NONE;
+}
+
+/*
+ * NOTE: This interrupt is used to report ifc events of various kinds,
+ * such as transaction errors on the chipselects.
+ */
+static irqreturn_t fsl_ifc_ctrl_irq(int irqno, void *data)
+{
+ struct fsl_ifc_ctrl *ctrl = data;
+ struct fsl_ifc_regs __iomem *ifc = ctrl->regs;
+ u32 err_axiid, err_srcid, status, cs_err, err_addr;
+ irqreturn_t ret = IRQ_NONE;
+
+ /* read for chip select error */
+ cs_err = in_be32(&ifc->cm_evter_stat);
+ if (cs_err) {
+ dev_err(ctrl->dev, "transaction sent to IFC is not mapped to"
+ "any memory bank 0x%08X\n", cs_err);
+ /* clear the chip select error */
+ out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER);
+
+ /* read error attribute registers print the error information */
+ status = in_be32(&ifc->cm_erattr0);
+ err_addr = in_be32(&ifc->cm_erattr1);
+
+ if (status & IFC_CM_ERATTR0_ERTYP_READ)
+ dev_err(ctrl->dev, "Read transaction error"
+ "CM_ERATTR0 0x%08X\n", status);
+ else
+ dev_err(ctrl->dev, "Write transaction error"
+ "CM_ERATTR0 0x%08X\n", status);
+
+ err_axiid = (status & IFC_CM_ERATTR0_ERAID) >>
+ IFC_CM_ERATTR0_ERAID_SHIFT;
+ dev_err(ctrl->dev, "AXI ID of the error"
+ "transaction 0x%08X\n", err_axiid);
+
+ err_srcid = (status & IFC_CM_ERATTR0_ESRCID) >>
+ IFC_CM_ERATTR0_ESRCID_SHIFT;
+ dev_err(ctrl->dev, "SRC ID of the error"
+ "transaction 0x%08X\n", err_srcid);
+
+ dev_err(ctrl->dev, "Transaction Address corresponding to error"
+ "ERADDR 0x%08X\n", err_addr);
+
+ ret = IRQ_HANDLED;
+ }
+
+ if (check_nand_stat(ctrl))
+ ret = IRQ_HANDLED;
+
+ return ret;
+}
+
+/*
+ * fsl_ifc_ctrl_probe
+ *
+ * called by device layer when it finds a device matching
+ * one our driver can handled. This code allocates all of
+ * the resources needed for the controller only. The
+ * resources for the NAND banks themselves are allocated
+ * in the chip probe function.
+*/
+static int fsl_ifc_ctrl_probe(struct platform_device *dev)
+{
+ int ret = 0;
+
+
+ dev_info(&dev->dev, "Freescale Integrated Flash Controller\n");
+
+ fsl_ifc_ctrl_dev = kzalloc(sizeof(*fsl_ifc_ctrl_dev), GFP_KERNEL);
+ if (!fsl_ifc_ctrl_dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(&dev->dev, fsl_ifc_ctrl_dev);
+
+ /* IOMAP the entire IFC region */
+ fsl_ifc_ctrl_dev->regs = of_iomap(dev->dev.of_node, 0);
+ if (!fsl_ifc_ctrl_dev->regs) {
+ dev_err(&dev->dev, "failed to get memory region\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ /* get the Controller level irq */
+ fsl_ifc_ctrl_dev->irq = irq_of_parse_and_map(dev->dev.of_node, 0);
+ if (fsl_ifc_ctrl_dev->irq == NO_IRQ) {
+ dev_err(&dev->dev, "failed to get irq resource "
+ "for IFC\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ /* get the nand machine irq */
+ fsl_ifc_ctrl_dev->nand_irq =
+ irq_of_parse_and_map(dev->dev.of_node, 1);
+
+ fsl_ifc_ctrl_dev->dev = &dev->dev;
+
+ ret = fsl_ifc_ctrl_init(fsl_ifc_ctrl_dev);
+ if (ret < 0)
+ goto err;
+
+ init_waitqueue_head(&fsl_ifc_ctrl_dev->nand_wait);
+
+ ret = request_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_irq, IRQF_SHARED,
+ "fsl-ifc", fsl_ifc_ctrl_dev);
+ if (ret != 0) {
+ dev_err(&dev->dev, "failed to install irq (%d)\n",
+ fsl_ifc_ctrl_dev->irq);
+ goto err_irq;
+ }
+
+ if (fsl_ifc_ctrl_dev->nand_irq) {
+ ret = request_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_nand_irq,
+ 0, "fsl-ifc-nand", fsl_ifc_ctrl_dev);
+ if (ret != 0) {
+ dev_err(&dev->dev, "failed to install irq (%d)\n",
+ fsl_ifc_ctrl_dev->nand_irq);
+ goto err_nandirq;
+ }
+ }
+
+ return 0;
+
+err_nandirq:
+ free_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_ctrl_dev);
+ irq_dispose_mapping(fsl_ifc_ctrl_dev->nand_irq);
+err_irq:
+ free_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_dev);
+ irq_dispose_mapping(fsl_ifc_ctrl_dev->irq);
+err:
+ return ret;
+}
+
+static const struct of_device_id fsl_ifc_match[] = {
+ {
+ .compatible = "fsl,ifc",
+ },
+ {},
+};
+
+static struct platform_driver fsl_ifc_ctrl_driver = {
+ .driver = {
+ .name = "fsl-ifc",
+ .of_match_table = fsl_ifc_match,
+ },
+ .probe = fsl_ifc_ctrl_probe,
+ .remove = fsl_ifc_ctrl_remove,
+};
+
+static int __init fsl_ifc_init(void)
+{
+ return platform_driver_register(&fsl_ifc_ctrl_driver);
+}
+subsys_initcall(fsl_ifc_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_DESCRIPTION("Freescale Integrated Flash Controller driver");
diff --git a/drivers/memory/ti-aemif.c b/drivers/memory/ti-aemif.c
new file mode 100644
index 000000000000..d3df7602f406
--- /dev/null
+++ b/drivers/memory/ti-aemif.c
@@ -0,0 +1,427 @@
+/*
+ * TI AEMIF driver
+ *
+ * Copyright (C) 2010 - 2013 Texas Instruments Incorporated. http://www.ti.com/
+ *
+ * Authors:
+ * Murali Karicheri <m-karicheri2@ti.com>
+ * Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#define TA_SHIFT 2
+#define RHOLD_SHIFT 4
+#define RSTROBE_SHIFT 7
+#define RSETUP_SHIFT 13
+#define WHOLD_SHIFT 17
+#define WSTROBE_SHIFT 20
+#define WSETUP_SHIFT 26
+#define EW_SHIFT 30
+#define SS_SHIFT 31
+
+#define TA(x) ((x) << TA_SHIFT)
+#define RHOLD(x) ((x) << RHOLD_SHIFT)
+#define RSTROBE(x) ((x) << RSTROBE_SHIFT)
+#define RSETUP(x) ((x) << RSETUP_SHIFT)
+#define WHOLD(x) ((x) << WHOLD_SHIFT)
+#define WSTROBE(x) ((x) << WSTROBE_SHIFT)
+#define WSETUP(x) ((x) << WSETUP_SHIFT)
+#define EW(x) ((x) << EW_SHIFT)
+#define SS(x) ((x) << SS_SHIFT)
+
+#define ASIZE_MAX 0x1
+#define TA_MAX 0x3
+#define RHOLD_MAX 0x7
+#define RSTROBE_MAX 0x3f
+#define RSETUP_MAX 0xf
+#define WHOLD_MAX 0x7
+#define WSTROBE_MAX 0x3f
+#define WSETUP_MAX 0xf
+#define EW_MAX 0x1
+#define SS_MAX 0x1
+#define NUM_CS 4
+
+#define TA_VAL(x) (((x) & TA(TA_MAX)) >> TA_SHIFT)
+#define RHOLD_VAL(x) (((x) & RHOLD(RHOLD_MAX)) >> RHOLD_SHIFT)
+#define RSTROBE_VAL(x) (((x) & RSTROBE(RSTROBE_MAX)) >> RSTROBE_SHIFT)
+#define RSETUP_VAL(x) (((x) & RSETUP(RSETUP_MAX)) >> RSETUP_SHIFT)
+#define WHOLD_VAL(x) (((x) & WHOLD(WHOLD_MAX)) >> WHOLD_SHIFT)
+#define WSTROBE_VAL(x) (((x) & WSTROBE(WSTROBE_MAX)) >> WSTROBE_SHIFT)
+#define WSETUP_VAL(x) (((x) & WSETUP(WSETUP_MAX)) >> WSETUP_SHIFT)
+#define EW_VAL(x) (((x) & EW(EW_MAX)) >> EW_SHIFT)
+#define SS_VAL(x) (((x) & SS(SS_MAX)) >> SS_SHIFT)
+
+#define NRCSR_OFFSET 0x00
+#define AWCCR_OFFSET 0x04
+#define A1CR_OFFSET 0x10
+
+#define ACR_ASIZE_MASK 0x3
+#define ACR_EW_MASK BIT(30)
+#define ACR_SS_MASK BIT(31)
+#define ASIZE_16BIT 1
+
+#define CONFIG_MASK (TA(TA_MAX) | \
+ RHOLD(RHOLD_MAX) | \
+ RSTROBE(RSTROBE_MAX) | \
+ RSETUP(RSETUP_MAX) | \
+ WHOLD(WHOLD_MAX) | \
+ WSTROBE(WSTROBE_MAX) | \
+ WSETUP(WSETUP_MAX) | \
+ EW(EW_MAX) | SS(SS_MAX) | \
+ ASIZE_MAX)
+
+/**
+ * struct aemif_cs_data: structure to hold cs parameters
+ * @cs: chip-select number
+ * @wstrobe: write strobe width, ns
+ * @rstrobe: read strobe width, ns
+ * @wsetup: write setup width, ns
+ * @whold: write hold width, ns
+ * @rsetup: read setup width, ns
+ * @rhold: read hold width, ns
+ * @ta: minimum turn around time, ns
+ * @enable_ss: enable/disable select strobe mode
+ * @enable_ew: enable/disable extended wait mode
+ * @asize: width of the asynchronous device's data bus
+ */
+struct aemif_cs_data {
+ u8 cs;
+ u16 wstrobe;
+ u16 rstrobe;
+ u8 wsetup;
+ u8 whold;
+ u8 rsetup;
+ u8 rhold;
+ u8 ta;
+ u8 enable_ss;
+ u8 enable_ew;
+ u8 asize;
+};
+
+/**
+ * struct aemif_device: structure to hold device data
+ * @base: base address of AEMIF registers
+ * @clk: source clock
+ * @clk_rate: clock's rate in kHz
+ * @num_cs: number of assigned chip-selects
+ * @cs_offset: start number of cs nodes
+ * @cs_data: array of chip-select settings
+ */
+struct aemif_device {
+ void __iomem *base;
+ struct clk *clk;
+ unsigned long clk_rate;
+ u8 num_cs;
+ int cs_offset;
+ struct aemif_cs_data cs_data[NUM_CS];
+};
+
+/**
+ * aemif_calc_rate - calculate timing data.
+ * @pdev: platform device to calculate for
+ * @wanted: The cycle time needed in nanoseconds.
+ * @clk: The input clock rate in kHz.
+ * @max: The maximum divider value that can be programmed.
+ *
+ * On success, returns the calculated timing value minus 1 for easy
+ * programming into AEMIF timing registers, else negative errno.
+ */
+static int aemif_calc_rate(struct platform_device *pdev, int wanted,
+ unsigned long clk, int max)
+{
+ int result;
+
+ result = DIV_ROUND_UP((wanted * clk), NSEC_PER_MSEC) - 1;
+
+ dev_dbg(&pdev->dev, "%s: result %d from %ld, %d\n", __func__, result,
+ clk, wanted);
+
+ /* It is generally OK to have a more relaxed timing than requested... */
+ if (result < 0)
+ result = 0;
+
+ /* ... But configuring tighter timings is not an option. */
+ else if (result > max)
+ result = -EINVAL;
+
+ return result;
+}
+
+/**
+ * aemif_config_abus - configure async bus parameters
+ * @pdev: platform device to configure for
+ * @csnum: aemif chip select number
+ *
+ * This function programs the given timing values (in real clock) into the
+ * AEMIF registers taking the AEMIF clock into account.
+ *
+ * This function does not use any locking while programming the AEMIF
+ * because it is expected that there is only one user of a given
+ * chip-select.
+ *
+ * Returns 0 on success, else negative errno.
+ */
+static int aemif_config_abus(struct platform_device *pdev, int csnum)
+{
+ struct aemif_device *aemif = platform_get_drvdata(pdev);
+ struct aemif_cs_data *data = &aemif->cs_data[csnum];
+ int ta, rhold, rstrobe, rsetup, whold, wstrobe, wsetup;
+ unsigned long clk_rate = aemif->clk_rate;
+ unsigned offset;
+ u32 set, val;
+
+ offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4;
+
+ ta = aemif_calc_rate(pdev, data->ta, clk_rate, TA_MAX);
+ rhold = aemif_calc_rate(pdev, data->rhold, clk_rate, RHOLD_MAX);
+ rstrobe = aemif_calc_rate(pdev, data->rstrobe, clk_rate, RSTROBE_MAX);
+ rsetup = aemif_calc_rate(pdev, data->rsetup, clk_rate, RSETUP_MAX);
+ whold = aemif_calc_rate(pdev, data->whold, clk_rate, WHOLD_MAX);
+ wstrobe = aemif_calc_rate(pdev, data->wstrobe, clk_rate, WSTROBE_MAX);
+ wsetup = aemif_calc_rate(pdev, data->wsetup, clk_rate, WSETUP_MAX);
+
+ if (ta < 0 || rhold < 0 || rstrobe < 0 || rsetup < 0 ||
+ whold < 0 || wstrobe < 0 || wsetup < 0) {
+ dev_err(&pdev->dev, "%s: cannot get suitable timings\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ set = TA(ta) | RHOLD(rhold) | RSTROBE(rstrobe) | RSETUP(rsetup) |
+ WHOLD(whold) | WSTROBE(wstrobe) | WSETUP(wsetup);
+
+ set |= (data->asize & ACR_ASIZE_MASK);
+ if (data->enable_ew)
+ set |= ACR_EW_MASK;
+ if (data->enable_ss)
+ set |= ACR_SS_MASK;
+
+ val = readl(aemif->base + offset);
+ val &= ~CONFIG_MASK;
+ val |= set;
+ writel(val, aemif->base + offset);
+
+ return 0;
+}
+
+static inline int aemif_cycles_to_nsec(int val, unsigned long clk_rate)
+{
+ return ((val + 1) * NSEC_PER_MSEC) / clk_rate;
+}
+
+/**
+ * aemif_get_hw_params - function to read hw register values
+ * @pdev: platform device to read for
+ * @csnum: aemif chip select number
+ *
+ * This function reads the defaults from the registers and update
+ * the timing values. Required for get/set commands and also for
+ * the case when driver needs to use defaults in hardware.
+ */
+static void aemif_get_hw_params(struct platform_device *pdev, int csnum)
+{
+ struct aemif_device *aemif = platform_get_drvdata(pdev);
+ struct aemif_cs_data *data = &aemif->cs_data[csnum];
+ unsigned long clk_rate = aemif->clk_rate;
+ u32 val, offset;
+
+ offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4;
+ val = readl(aemif->base + offset);
+
+ data->ta = aemif_cycles_to_nsec(TA_VAL(val), clk_rate);
+ data->rhold = aemif_cycles_to_nsec(RHOLD_VAL(val), clk_rate);
+ data->rstrobe = aemif_cycles_to_nsec(RSTROBE_VAL(val), clk_rate);
+ data->rsetup = aemif_cycles_to_nsec(RSETUP_VAL(val), clk_rate);
+ data->whold = aemif_cycles_to_nsec(WHOLD_VAL(val), clk_rate);
+ data->wstrobe = aemif_cycles_to_nsec(WSTROBE_VAL(val), clk_rate);
+ data->wsetup = aemif_cycles_to_nsec(WSETUP_VAL(val), clk_rate);
+ data->enable_ew = EW_VAL(val);
+ data->enable_ss = SS_VAL(val);
+ data->asize = val & ASIZE_MAX;
+}
+
+/**
+ * of_aemif_parse_abus_config - parse CS configuration from DT
+ * @pdev: platform device to parse for
+ * @np: device node ptr
+ *
+ * This function update the emif async bus configuration based on the values
+ * configured in a cs device binding node.
+ */
+static int of_aemif_parse_abus_config(struct platform_device *pdev,
+ struct device_node *np)
+{
+ struct aemif_device *aemif = platform_get_drvdata(pdev);
+ struct aemif_cs_data *data;
+ u32 cs;
+ u32 val;
+
+ if (of_property_read_u32(np, "ti,cs-chipselect", &cs)) {
+ dev_dbg(&pdev->dev, "cs property is required");
+ return -EINVAL;
+ }
+
+ if (cs - aemif->cs_offset >= NUM_CS || cs < aemif->cs_offset) {
+ dev_dbg(&pdev->dev, "cs number is incorrect %d", cs);
+ return -EINVAL;
+ }
+
+ if (aemif->num_cs >= NUM_CS) {
+ dev_dbg(&pdev->dev, "cs count is more than %d", NUM_CS);
+ return -EINVAL;
+ }
+
+ data = &aemif->cs_data[aemif->num_cs];
+ data->cs = cs;
+
+ /* read the current value in the hw register */
+ aemif_get_hw_params(pdev, aemif->num_cs++);
+
+ /* override the values from device node */
+ if (!of_property_read_u32(np, "ti,cs-min-turnaround-ns", &val))
+ data->ta = val;
+
+ if (!of_property_read_u32(np, "ti,cs-read-hold-ns", &val))
+ data->rhold = val;
+
+ if (!of_property_read_u32(np, "ti,cs-read-strobe-ns", &val))
+ data->rstrobe = val;
+
+ if (!of_property_read_u32(np, "ti,cs-read-setup-ns", &val))
+ data->rsetup = val;
+
+ if (!of_property_read_u32(np, "ti,cs-write-hold-ns", &val))
+ data->whold = val;
+
+ if (!of_property_read_u32(np, "ti,cs-write-strobe-ns", &val))
+ data->wstrobe = val;
+
+ if (!of_property_read_u32(np, "ti,cs-write-setup-ns", &val))
+ data->wsetup = val;
+
+ if (!of_property_read_u32(np, "ti,cs-bus-width", &val))
+ if (val == 16)
+ data->asize = 1;
+ data->enable_ew = of_property_read_bool(np, "ti,cs-extended-wait-mode");
+ data->enable_ss = of_property_read_bool(np, "ti,cs-select-strobe-mode");
+ return 0;
+}
+
+static const struct of_device_id aemif_of_match[] = {
+ { .compatible = "ti,davinci-aemif", },
+ { .compatible = "ti,da850-aemif", },
+ {},
+};
+
+static int aemif_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret = -ENODEV;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child_np;
+ struct aemif_device *aemif;
+
+ if (np == NULL)
+ return 0;
+
+ aemif = devm_kzalloc(dev, sizeof(*aemif), GFP_KERNEL);
+ if (!aemif)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, aemif);
+
+ aemif->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(aemif->clk)) {
+ dev_err(dev, "cannot get clock 'aemif'\n");
+ return PTR_ERR(aemif->clk);
+ }
+
+ clk_prepare_enable(aemif->clk);
+ aemif->clk_rate = clk_get_rate(aemif->clk) / MSEC_PER_SEC;
+
+ if (of_device_is_compatible(np, "ti,da850-aemif"))
+ aemif->cs_offset = 2;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ aemif->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(aemif->base)) {
+ ret = PTR_ERR(aemif->base);
+ goto error;
+ }
+
+ /*
+ * For every controller device node, there is a cs device node that
+ * describe the bus configuration parameters. This functions iterate
+ * over these nodes and update the cs data array.
+ */
+ for_each_available_child_of_node(np, child_np) {
+ ret = of_aemif_parse_abus_config(pdev, child_np);
+ if (ret < 0)
+ goto error;
+ }
+
+ for (i = 0; i < aemif->num_cs; i++) {
+ ret = aemif_config_abus(pdev, i);
+ if (ret < 0) {
+ dev_err(dev, "Error configuring chip select %d\n",
+ aemif->cs_data[i].cs);
+ goto error;
+ }
+ }
+
+ /*
+ * Create a child devices explicitly from here to
+ * guarantee that the child will be probed after the AEMIF timing
+ * parameters are set.
+ */
+ for_each_available_child_of_node(np, child_np) {
+ ret = of_platform_populate(child_np, NULL, NULL, dev);
+ if (ret < 0)
+ goto error;
+ }
+
+ return 0;
+error:
+ clk_disable_unprepare(aemif->clk);
+ return ret;
+}
+
+static int aemif_remove(struct platform_device *pdev)
+{
+ struct aemif_device *aemif = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(aemif->clk);
+ return 0;
+}
+
+static struct platform_driver aemif_driver = {
+ .probe = aemif_probe,
+ .remove = aemif_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(aemif_of_match),
+ },
+};
+
+module_platform_driver(aemif_driver);
+
+MODULE_AUTHOR("Murali Karicheri <m-karicheri2@ti.com>");
+MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>");
+MODULE_DESCRIPTION("Texas Instruments AEMIF driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6cb388e8fb7d..809afebe0dad 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -235,7 +235,7 @@ config SGI_XP
config CS5535_MFGPT
tristate "CS5535/CS5536 Geode Multi-Function General Purpose Timer (MFGPT) support"
- depends on PCI && X86 && MFD_CS5535
+ depends on MFD_CS5535
default n
help
This driver provides access to MFGPT functionality for other
diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c
index d3eee113baeb..a43053daad0e 100644
--- a/drivers/misc/ad525x_dpot.c
+++ b/drivers/misc/ad525x_dpot.c
@@ -72,7 +72,6 @@
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
diff --git a/drivers/misc/apds9802als.c b/drivers/misc/apds9802als.c
index 0c6e037153d2..c6cc3dc8ae1f 100644
--- a/drivers/misc/apds9802als.c
+++ b/drivers/misc/apds9802als.c
@@ -22,7 +22,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/err.h>
diff --git a/drivers/misc/bmp085.c b/drivers/misc/bmp085.c
index 820e53d0048f..9b313f7810f5 100644
--- a/drivers/misc/bmp085.c
+++ b/drivers/misc/bmp085.c
@@ -47,7 +47,6 @@
#include <linux/module.h>
#include <linux/device.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/of.h>
#include "bmp085.h"
diff --git a/drivers/misc/carma/carma-fpga.c b/drivers/misc/carma/carma-fpga.c
index 9e2b985293fc..14d90eae605b 100644
--- a/drivers/misc/carma/carma-fpga.c
+++ b/drivers/misc/carma/carma-fpga.c
@@ -101,7 +101,6 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/poll.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kref.h>
#include <linux/io.h>
diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c
index 154b02e5094f..6a672f9ef522 100644
--- a/drivers/misc/ds1682.c
+++ b/drivers/misc/ds1682.c
@@ -32,7 +32,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/string.h>
#include <linux/list.h>
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 4f3bca1003a1..634f72929e12 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -10,7 +10,6 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
diff --git a/drivers/misc/eeprom/eeprom.c b/drivers/misc/eeprom/eeprom.c
index f0fa4e8ca124..33f8673d23a6 100644
--- a/drivers/misc/eeprom/eeprom.c
+++ b/drivers/misc/eeprom/eeprom.c
@@ -17,7 +17,6 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c
index 78e55b501c94..9ebeacdb8ec4 100644
--- a/drivers/misc/eeprom/eeprom_93xx46.c
+++ b/drivers/misc/eeprom/eeprom_93xx46.c
@@ -11,7 +11,6 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c
index e36157d5d3ab..580ff9df5529 100644
--- a/drivers/misc/eeprom/max6875.c
+++ b/drivers/misc/eeprom/max6875.c
@@ -27,7 +27,6 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c
index 9c34e5704304..3f2b625b2032 100644
--- a/drivers/misc/eeprom/sunxi_sid.c
+++ b/drivers/misc/eeprom/sunxi_sid.c
@@ -21,7 +21,6 @@
#include <linux/err.h>
#include <linux/export.h>
#include <linux/fs.h>
-#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
@@ -96,7 +95,7 @@ static int sunxi_sid_remove(struct platform_device *pdev)
}
static const struct of_device_id sunxi_sid_of_match[] = {
- { .compatible = "allwinner,sun4i-sid", .data = (void *)16},
+ { .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16},
{ .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512},
{/* sentinel */},
};
diff --git a/drivers/misc/genwqe/card_debugfs.c b/drivers/misc/genwqe/card_debugfs.c
index 3bfdc07a7248..50d2096ea1c7 100644
--- a/drivers/misc/genwqe/card_debugfs.c
+++ b/drivers/misc/genwqe/card_debugfs.c
@@ -26,7 +26,6 @@
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
diff --git a/drivers/misc/hmc6352.c b/drivers/misc/hmc6352.c
index 170bd3daf336..90520d76633f 100644
--- a/drivers/misc/hmc6352.c
+++ b/drivers/misc/hmc6352.c
@@ -22,7 +22,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/err.h>
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
index e3183f26216b..12c30b486b27 100644
--- a/drivers/misc/isl29003.c
+++ b/drivers/misc/isl29003.c
@@ -26,7 +26,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
diff --git a/drivers/misc/isl29020.c b/drivers/misc/isl29020.c
index b7f84dacf822..4a9c50a43afb 100644
--- a/drivers/misc/isl29020.c
+++ b/drivers/misc/isl29020.c
@@ -23,7 +23,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/err.h>
diff --git a/drivers/misc/lattice-ecp3-config.c b/drivers/misc/lattice-ecp3-config.c
index 61fbe6acabef..0a1565e63c71 100644
--- a/drivers/misc/lattice-ecp3-config.c
+++ b/drivers/misc/lattice-ecp3-config.c
@@ -12,7 +12,6 @@
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c
index 036effe9a795..3ef4627f9cb1 100644
--- a/drivers/misc/lis3lv02d/lis3lv02d.c
+++ b/drivers/misc/lis3lv02d/lis3lv02d.c
@@ -23,7 +23,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/types.h>
diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c
index 7c97550240f1..d324f8a97b88 100644
--- a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c
+++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c
@@ -26,7 +26,6 @@
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c
index 9aa2bd2a71ae..bd06d0cfac45 100644
--- a/drivers/misc/lis3lv02d/lis3lv02d_spi.c
+++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c
@@ -10,7 +10,6 @@
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/err.h>
#include <linux/input.h>
#include <linux/interrupt.h>
diff --git a/drivers/misc/lkdtm.c b/drivers/misc/lkdtm.c
index 49c7a23f02fc..d66a2f24f6b3 100644
--- a/drivers/misc/lkdtm.c
+++ b/drivers/misc/lkdtm.c
@@ -30,6 +30,7 @@
*
* See Documentation/fault-injection/provoke-crashes.txt for instructions
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/fs.h>
@@ -45,6 +46,7 @@
#include <linux/debugfs.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
+#include <asm/cacheflush.h>
#ifdef CONFIG_IDE
#include <linux/ide.h>
@@ -101,6 +103,7 @@ enum ctype {
CT_EXEC_USERSPACE,
CT_ACCESS_USERSPACE,
CT_WRITE_RO,
+ CT_WRITE_KERN,
};
static char* cp_name[] = {
@@ -137,6 +140,7 @@ static char* cp_type[] = {
"EXEC_USERSPACE",
"ACCESS_USERSPACE",
"WRITE_RO",
+ "WRITE_KERN",
};
static struct jprobe lkdtm;
@@ -316,6 +320,13 @@ static void do_nothing(void)
return;
}
+/* Must immediately follow do_nothing for size calculuations to work out. */
+static void do_overwritten(void)
+{
+ pr_info("do_overwritten wasn't overwritten!\n");
+ return;
+}
+
static noinline void corrupt_stack(void)
{
/* Use default char array length that triggers stack protection. */
@@ -328,7 +339,12 @@ static void execute_location(void *dst)
{
void (*func)(void) = dst;
+ pr_info("attempting ok execution at %p\n", do_nothing);
+ do_nothing();
+
memcpy(dst, do_nothing, EXEC_SIZE);
+ flush_icache_range((unsigned long)dst, (unsigned long)dst + EXEC_SIZE);
+ pr_info("attempting bad execution at %p\n", func);
func();
}
@@ -337,8 +353,13 @@ static void execute_user_location(void *dst)
/* 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))
return;
+ flush_icache_range((unsigned long)dst, (unsigned long)dst + EXEC_SIZE);
+ pr_info("attempting bad execution at %p\n", func);
func();
}
@@ -463,8 +484,12 @@ static void lkdtm_do_action(enum ctype which)
}
ptr = (unsigned long *)user_addr;
+
+ pr_info("attempting bad read at %p\n", ptr);
tmp = *ptr;
tmp += 0xc0dec0de;
+
+ pr_info("attempting bad write at %p\n", ptr);
*ptr = tmp;
vm_munmap(user_addr, PAGE_SIZE);
@@ -475,10 +500,28 @@ static void lkdtm_do_action(enum ctype which)
unsigned long *ptr;
ptr = (unsigned long *)&rodata;
+
+ pr_info("attempting bad write at %p\n", ptr);
*ptr ^= 0xabcd1234;
break;
}
+ case CT_WRITE_KERN: {
+ size_t size;
+ unsigned char *ptr;
+
+ size = (unsigned long)do_overwritten -
+ (unsigned long)do_nothing;
+ ptr = (unsigned char *)do_overwritten;
+
+ pr_info("attempting bad %zu byte write at %p\n", size, ptr);
+ memcpy(ptr, (unsigned char *)do_nothing, size);
+ flush_icache_range((unsigned long)ptr,
+ (unsigned long)(ptr + size));
+
+ do_overwritten();
+ break;
+ }
case CT_NONE:
default:
break;
@@ -493,8 +536,8 @@ static void lkdtm_handler(void)
spin_lock_irqsave(&count_lock, flags);
count--;
- printk(KERN_INFO "lkdtm: Crash point %s of type %s hit, trigger in %d rounds\n",
- cp_name_to_str(cpoint), cp_type_to_str(cptype), count);
+ pr_info("Crash point %s of type %s hit, trigger in %d rounds\n",
+ cp_name_to_str(cpoint), cp_type_to_str(cptype), count);
if (count == 0) {
do_it = true;
@@ -551,18 +594,18 @@ static int lkdtm_register_cpoint(enum cname which)
lkdtm.kp.symbol_name = "generic_ide_ioctl";
lkdtm.entry = (kprobe_opcode_t*) jp_generic_ide_ioctl;
#else
- printk(KERN_INFO "lkdtm: Crash point not available\n");
+ pr_info("Crash point not available\n");
return -EINVAL;
#endif
break;
default:
- printk(KERN_INFO "lkdtm: Invalid Crash Point\n");
+ pr_info("Invalid Crash Point\n");
return -EINVAL;
}
cpoint = which;
if ((ret = register_jprobe(&lkdtm)) < 0) {
- printk(KERN_INFO "lkdtm: Couldn't register jprobe\n");
+ pr_info("Couldn't register jprobe\n");
cpoint = CN_INVALID;
}
@@ -709,8 +752,7 @@ static ssize_t direct_entry(struct file *f, const char __user *user_buf,
if (type == CT_NONE)
return -EINVAL;
- printk(KERN_INFO "lkdtm: Performing direct entry %s\n",
- cp_type_to_str(type));
+ pr_info("Performing direct entry %s\n", cp_type_to_str(type));
lkdtm_do_action(type);
*off += count;
@@ -772,7 +814,7 @@ static int __init lkdtm_module_init(void)
/* Register debugfs interface */
lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL);
if (!lkdtm_debugfs_root) {
- printk(KERN_ERR "lkdtm: creating root dir failed\n");
+ pr_err("creating root dir failed\n");
return -ENODEV;
}
@@ -787,28 +829,26 @@ static int __init lkdtm_module_init(void)
de = debugfs_create_file(cur->name, 0644, lkdtm_debugfs_root,
NULL, &cur->fops);
if (de == NULL) {
- printk(KERN_ERR "lkdtm: could not create %s\n",
- cur->name);
+ pr_err("could not create %s\n", cur->name);
goto out_err;
}
}
if (lkdtm_parse_commandline() == -EINVAL) {
- printk(KERN_INFO "lkdtm: Invalid command\n");
+ pr_info("Invalid command\n");
goto out_err;
}
if (cpoint != CN_INVALID && cptype != CT_NONE) {
ret = lkdtm_register_cpoint(cpoint);
if (ret < 0) {
- printk(KERN_INFO "lkdtm: Invalid crash point %d\n",
- cpoint);
+ pr_info("Invalid crash point %d\n", cpoint);
goto out_err;
}
- printk(KERN_INFO "lkdtm: Crash point %s of type %s registered\n",
- cpoint_name, cpoint_type);
+ pr_info("Crash point %s of type %s registered\n",
+ cpoint_name, cpoint_type);
} else {
- printk(KERN_INFO "lkdtm: No crash points registered, enable through debugfs\n");
+ pr_info("No crash points registered, enable through debugfs\n");
}
return 0;
@@ -823,7 +863,7 @@ static void __exit lkdtm_module_exit(void)
debugfs_remove_recursive(lkdtm_debugfs_root);
unregister_jprobe(&lkdtm);
- printk(KERN_INFO "lkdtm: Crash point unregistered\n");
+ pr_info("Crash point unregistered\n");
}
module_init(lkdtm_module_init);
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig
index c76fa31e9bf6..d23384dde73b 100644
--- a/drivers/misc/mei/Kconfig
+++ b/drivers/misc/mei/Kconfig
@@ -34,3 +34,12 @@ config INTEL_MEI_ME
82Q33 Express
82X38/X48 Express
+config INTEL_MEI_TXE
+ tristate "Intel Trusted Execution Environment with ME Interface"
+ select INTEL_MEI
+ depends on X86 && PCI && WATCHDOG_CORE
+ help
+ MEI Support for Trusted Execution Environment device on Intel SoCs
+
+ Supported SoCs:
+ Intel Bay Trail
diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile
index 08698a466268..8ebc6cda1373 100644
--- a/drivers/misc/mei/Makefile
+++ b/drivers/misc/mei/Makefile
@@ -1,6 +1,6 @@
#
# Makefile - Intel Management Engine Interface (Intel MEI) Linux driver
-# Copyright (c) 2010-2011, Intel Corporation.
+# Copyright (c) 2010-2014, Intel Corporation.
#
obj-$(CONFIG_INTEL_MEI) += mei.o
mei-objs := init.o
@@ -17,3 +17,7 @@ mei-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o
mei-me-objs := pci-me.o
mei-me-objs += hw-me.o
+
+obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
+mei-txe-objs := pci-txe.o
+mei-txe-objs += hw-txe.o
diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c
index 2fad84432829..b8deb3455480 100644
--- a/drivers/misc/mei/amthif.c
+++ b/drivers/misc/mei/amthif.c
@@ -21,7 +21,6 @@
#include <linux/fcntl.h>
#include <linux/aio.h>
#include <linux/pci.h>
-#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/list.h>
@@ -35,7 +34,6 @@
#include "mei_dev.h"
#include "hbm.h"
-#include "hw-me.h"
#include "client.h"
const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d,
@@ -79,10 +77,9 @@ int mei_amthif_host_init(struct mei_device *dev)
i = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
if (i < 0) {
- ret = i;
dev_info(&dev->pdev->dev,
- "amthif: failed to find the client %d\n", ret);
- return ret;
+ "amthif: failed to find the client %d\n", i);
+ return -ENOTTY;
}
cl->me_client_id = dev->me_clients[i].client_id;
@@ -116,14 +113,11 @@ int mei_amthif_host_init(struct mei_device *dev)
cl->state = MEI_FILE_CONNECTING;
- if (mei_hbm_cl_connect_req(dev, cl)) {
- dev_dbg(&dev->pdev->dev, "amthif: Failed to connect to ME client\n");
- cl->state = MEI_FILE_DISCONNECTED;
- cl->host_client_id = 0;
- } else {
- cl->timer_count = MEI_CONNECT_TIMEOUT;
- }
- return 0;
+ ret = mei_cl_connect(cl, NULL);
+
+ dev->iamthif_state = MEI_IAMTHIF_IDLE;
+
+ return ret;
}
/**
@@ -137,14 +131,12 @@ int mei_amthif_host_init(struct mei_device *dev)
struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
struct file *file)
{
- struct mei_cl_cb *pos = NULL;
- struct mei_cl_cb *next = NULL;
+ struct mei_cl_cb *cb;
- list_for_each_entry_safe(pos, next,
- &dev->amthif_rd_complete_list.list, list) {
- if (pos->cl && pos->cl == &dev->iamthif_cl &&
- pos->file_object == file)
- return pos;
+ list_for_each_entry(cb, &dev->amthif_rd_complete_list.list, list) {
+ if (cb->cl && cb->cl == &dev->iamthif_cl &&
+ cb->file_object == file)
+ return cb;
}
return NULL;
}
@@ -180,14 +172,13 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
/* Only possible if we are in timeout */
if (!cl || cl != &dev->iamthif_cl) {
dev_dbg(&dev->pdev->dev, "bad file ext.\n");
- return -ETIMEDOUT;
+ return -ETIME;
}
i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id);
-
if (i < 0) {
dev_dbg(&dev->pdev->dev, "amthif client not found.\n");
- return -ENODEV;
+ return -ENOTTY;
}
dev_dbg(&dev->pdev->dev, "checking amthif data\n");
cb = mei_amthif_find_read_list_entry(dev, file);
@@ -228,7 +219,7 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
dev_dbg(&dev->pdev->dev, "amthif Time out\n");
/* 15 sec for the message has expired */
list_del(&cb->list);
- rets = -ETIMEDOUT;
+ rets = -ETIME;
goto free;
}
}
@@ -253,9 +244,10 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
* the buf_idx may point beyond */
length = min_t(size_t, length, (cb->buf_idx - *offset));
- if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length))
+ if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) {
+ dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n");
rets = -EFAULT;
- else {
+ } else {
rets = length;
if ((*offset + length) < cb->buf_idx) {
*offset += length;
@@ -302,9 +294,8 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb)
if (ret < 0)
return ret;
- if (ret && dev->hbuf_is_ready) {
+ if (ret && mei_hbuf_acquire(dev)) {
ret = 0;
- dev->hbuf_is_ready = false;
if (cb->request_buffer.size > mei_hbuf_max_len(dev)) {
mei_hdr.length = mei_hbuf_max_len(dev);
mei_hdr.msg_complete = 0;
@@ -336,10 +327,6 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb)
list_add_tail(&cb->list, &dev->write_list.list);
}
} else {
- if (!dev->hbuf_is_ready)
- dev_dbg(&dev->pdev->dev, "host buffer is not empty");
-
- dev_dbg(&dev->pdev->dev, "No flow control credentials, so add iamthif cb to write list.\n");
list_add_tail(&cb->list, &dev->write_list.list);
}
return 0;
@@ -365,7 +352,7 @@ int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb)
if (ret)
return ret;
- cb->fop_type = MEI_FOP_IOCTL;
+ cb->fop_type = MEI_FOP_WRITE;
if (!list_empty(&dev->amthif_cmd_list.list) ||
dev->iamthif_state != MEI_IAMTHIF_IDLE) {
@@ -447,23 +434,23 @@ unsigned int mei_amthif_poll(struct mei_device *dev,
/**
- * mei_amthif_irq_write_completed - processes completed iamthif operation.
+ * mei_amthif_irq_write - write iamthif command in irq thread context.
*
* @dev: the device structure.
- * @slots: free slots.
* @cb_pos: callback block.
* @cl: private data of the file object.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise, error.
*/
-int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list)
+int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
struct mei_msg_hdr mei_hdr;
size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index;
u32 msg_slots = mei_data2slots(len);
+ int slots;
int rets;
rets = mei_cl_flow_ctrl_creds(cl);
@@ -480,13 +467,15 @@ int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
mei_hdr.reserved = 0;
mei_hdr.internal = 0;
- if (*slots >= msg_slots) {
+ slots = mei_hbuf_empty_slots(dev);
+
+ if (slots >= msg_slots) {
mei_hdr.length = len;
mei_hdr.msg_complete = 1;
/* Split the message only if we can write the whole host buffer */
- } else if (*slots == dev->hbuf_depth) {
- msg_slots = *slots;
- len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr);
+ } else if (slots == dev->hbuf_depth) {
+ msg_slots = slots;
+ len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr);
mei_hdr.length = len;
mei_hdr.msg_complete = 0;
} else {
@@ -496,7 +485,6 @@ int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr));
- *slots -= msg_slots;
rets = mei_write_message(dev, &mei_hdr,
dev->iamthif_msg_buf + dev->iamthif_msg_buf_index);
if (rets) {
diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index 4bc7d620d695..ddc5ac92a200 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -26,7 +26,6 @@
#include <linux/mei_cl_bus.h>
#include "mei_dev.h"
-#include "hw-me.h"
#include "client.h"
#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver)
@@ -145,9 +144,9 @@ static struct device_type mei_cl_device_type = {
static struct mei_cl *mei_bus_find_mei_cl_by_uuid(struct mei_device *dev,
uuid_le uuid)
{
- struct mei_cl *cl, *next;
+ struct mei_cl *cl;
- list_for_each_entry_safe(cl, next, &dev->device_list, device_link) {
+ list_for_each_entry(cl, &dev->device_list, device_link) {
if (!uuid_le_cmp(uuid, cl->device_uuid))
return cl;
}
@@ -524,6 +523,22 @@ void mei_cl_bus_rx_event(struct mei_cl *cl)
schedule_work(&device->event_work);
}
+void mei_cl_bus_remove_devices(struct mei_device *dev)
+{
+ struct mei_cl *cl, *next;
+
+ mutex_lock(&dev->device_lock);
+ list_for_each_entry_safe(cl, next, &dev->device_list, device_link) {
+ if (cl->device)
+ mei_cl_remove_device(cl->device);
+
+ list_del(&cl->device_link);
+ mei_cl_unlink(cl);
+ kfree(cl);
+ }
+ mutex_unlock(&dev->device_lock);
+}
+
int __init mei_cl_bus_init(void)
{
return bus_register(&mei_cl_bus_type);
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 89a557972d1b..8c078b808cd3 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -29,20 +29,21 @@
* mei_me_cl_by_uuid - locate index of me client
*
* @dev: mei device
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
* returns me client index or -ENOENT if not found
*/
int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid)
{
- int i, res = -ENOENT;
+ int i;
for (i = 0; i < dev->me_clients_num; ++i)
if (uuid_le_cmp(*uuid,
- dev->me_clients[i].props.protocol_name) == 0) {
- res = i;
- break;
- }
+ dev->me_clients[i].props.protocol_name) == 0)
+ return i;
- return res;
+ return -ENOENT;
}
@@ -60,37 +61,79 @@ int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid)
int mei_me_cl_by_id(struct mei_device *dev, u8 client_id)
{
int i;
+
for (i = 0; i < dev->me_clients_num; i++)
if (dev->me_clients[i].client_id == client_id)
- break;
- if (WARN_ON(dev->me_clients[i].client_id != client_id))
- return -ENOENT;
+ return i;
- if (i == dev->me_clients_num)
- return -ENOENT;
-
- return i;
+ return -ENOENT;
}
/**
- * mei_io_list_flush - removes list entry belonging to cl.
+ * mei_cl_cmp_id - tells if the clients are the same
*
- * @list: An instance of our list structure
- * @cl: host client
+ * @cl1: host client 1
+ * @cl2: host client 2
+ *
+ * returns true - if the clients has same host and me ids
+ * false - otherwise
+ */
+static inline bool mei_cl_cmp_id(const struct mei_cl *cl1,
+ const struct mei_cl *cl2)
+{
+ return cl1 && cl2 &&
+ (cl1->host_client_id == cl2->host_client_id) &&
+ (cl1->me_client_id == cl2->me_client_id);
+}
+
+/**
+ * mei_io_list_flush - removes cbs belonging to cl.
+ *
+ * @list: an instance of our list structure
+ * @cl: host client, can be NULL for flushing the whole list
+ * @free: whether to free the cbs
*/
-void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl)
+static void __mei_io_list_flush(struct mei_cl_cb *list,
+ struct mei_cl *cl, bool free)
{
struct mei_cl_cb *cb;
struct mei_cl_cb *next;
+ /* enable removing everything if no cl is specified */
list_for_each_entry_safe(cb, next, &list->list, list) {
- if (cb->cl && mei_cl_cmp_id(cl, cb->cl))
+ if (!cl || (cb->cl && mei_cl_cmp_id(cl, cb->cl))) {
list_del(&cb->list);
+ if (free)
+ mei_io_cb_free(cb);
+ }
}
}
/**
+ * mei_io_list_flush - removes list entry belonging to cl.
+ *
+ * @list: An instance of our list structure
+ * @cl: host client
+ */
+static inline void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl)
+{
+ __mei_io_list_flush(list, cl, false);
+}
+
+
+/**
+ * mei_io_list_free - removes cb belonging to cl and free them
+ *
+ * @list: An instance of our list structure
+ * @cl: host client
+ */
+static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl)
+{
+ __mei_io_list_flush(list, cl, true);
+}
+
+/**
* mei_io_cb_free - free mei_cb_private related memory
*
* @cb: mei callback struct
@@ -196,8 +239,8 @@ int mei_cl_flush_queues(struct mei_cl *cl)
cl_dbg(dev, cl, "remove list entry belonging to cl\n");
mei_io_list_flush(&cl->dev->read_list, cl);
- mei_io_list_flush(&cl->dev->write_list, cl);
- mei_io_list_flush(&cl->dev->write_waiting_list, cl);
+ mei_io_list_free(&cl->dev->write_list, cl);
+ mei_io_list_free(&cl->dev->write_waiting_list, cl);
mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
@@ -254,10 +297,9 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)
struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl)
{
struct mei_device *dev = cl->dev;
- struct mei_cl_cb *cb = NULL;
- struct mei_cl_cb *next = NULL;
+ struct mei_cl_cb *cb;
- list_for_each_entry_safe(cb, next, &dev->read_list.list, list)
+ list_for_each_entry(cb, &dev->read_list.list, list)
if (mei_cl_cmp_id(cl, cb->cl))
return cb;
return NULL;
@@ -375,6 +417,23 @@ void mei_host_client_init(struct work_struct *work)
mutex_unlock(&dev->device_lock);
}
+/**
+ * mei_hbuf_acquire: try to acquire host buffer
+ *
+ * @dev: the device structure
+ * returns true if host buffer was acquired
+ */
+bool mei_hbuf_acquire(struct mei_device *dev)
+{
+ if (!dev->hbuf_is_ready) {
+ dev_dbg(&dev->pdev->dev, "hbuf is not ready\n");
+ return false;
+ }
+
+ dev->hbuf_is_ready = false;
+
+ return true;
+}
/**
* mei_cl_disconnect - disconnect host client from the me one
@@ -406,8 +465,7 @@ int mei_cl_disconnect(struct mei_cl *cl)
return -ENOMEM;
cb->fop_type = MEI_FOP_CLOSE;
- if (dev->hbuf_is_ready) {
- dev->hbuf_is_ready = false;
+ if (mei_hbuf_acquire(dev)) {
if (mei_hbm_cl_disconnect_req(dev, cl)) {
rets = -ENODEV;
cl_err(dev, cl, "failed to disconnect.\n");
@@ -461,17 +519,17 @@ free:
bool mei_cl_is_other_connecting(struct mei_cl *cl)
{
struct mei_device *dev;
- struct mei_cl *pos;
- struct mei_cl *next;
+ struct mei_cl *ocl; /* the other client */
if (WARN_ON(!cl || !cl->dev))
return false;
dev = cl->dev;
- list_for_each_entry_safe(pos, next, &dev->file_list, link) {
- if ((pos->state == MEI_FILE_CONNECTING) &&
- (pos != cl) && cl->me_client_id == pos->me_client_id)
+ list_for_each_entry(ocl, &dev->file_list, link) {
+ if (ocl->state == MEI_FILE_CONNECTING &&
+ ocl != cl &&
+ cl->me_client_id == ocl->me_client_id)
return true;
}
@@ -505,11 +563,10 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file)
goto out;
}
- cb->fop_type = MEI_FOP_IOCTL;
-
- if (dev->hbuf_is_ready && !mei_cl_is_other_connecting(cl)) {
- dev->hbuf_is_ready = false;
+ cb->fop_type = MEI_FOP_CONNECT;
+ /* run hbuf acquire last so we don't have to undo */
+ if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) {
if (mei_hbm_cl_connect_req(dev, cl)) {
rets = -ENODEV;
goto out;
@@ -521,18 +578,19 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file)
}
mutex_unlock(&dev->device_lock);
- rets = wait_event_timeout(dev->wait_recvd_msg,
- (cl->state == MEI_FILE_CONNECTED ||
- cl->state == MEI_FILE_DISCONNECTED),
- mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
+ wait_event_timeout(dev->wait_recvd_msg,
+ (cl->state == MEI_FILE_CONNECTED ||
+ cl->state == MEI_FILE_DISCONNECTED),
+ mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (cl->state != MEI_FILE_CONNECTED) {
- rets = -EFAULT;
+ /* something went really wrong */
+ if (!cl->status)
+ cl->status = -EFAULT;
mei_io_list_flush(&dev->ctrl_rd_list, cl);
mei_io_list_flush(&dev->ctrl_wr_list, cl);
- goto out;
}
rets = cl->status;
@@ -554,7 +612,8 @@ out:
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
{
struct mei_device *dev;
- int i;
+ struct mei_me_client *me_cl;
+ int id;
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
@@ -567,19 +626,19 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
if (cl->mei_flow_ctrl_creds > 0)
return 1;
- for (i = 0; i < dev->me_clients_num; i++) {
- struct mei_me_client *me_cl = &dev->me_clients[i];
- if (me_cl->client_id == cl->me_client_id) {
- if (me_cl->mei_flow_ctrl_creds) {
- if (WARN_ON(me_cl->props.single_recv_buf == 0))
- return -EINVAL;
- return 1;
- } else {
- return 0;
- }
- }
+ id = mei_me_cl_by_id(dev, cl->me_client_id);
+ if (id < 0) {
+ cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
+ return id;
}
- return -ENOENT;
+
+ me_cl = &dev->me_clients[id];
+ if (me_cl->mei_flow_ctrl_creds) {
+ if (WARN_ON(me_cl->props.single_recv_buf == 0))
+ return -EINVAL;
+ return 1;
+ }
+ return 0;
}
/**
@@ -595,32 +654,31 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
{
struct mei_device *dev;
- int i;
+ struct mei_me_client *me_cl;
+ int id;
if (WARN_ON(!cl || !cl->dev))
return -EINVAL;
dev = cl->dev;
- if (!dev->me_clients_num)
- return -ENOENT;
+ id = mei_me_cl_by_id(dev, cl->me_client_id);
+ if (id < 0) {
+ cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
+ return id;
+ }
- for (i = 0; i < dev->me_clients_num; i++) {
- struct mei_me_client *me_cl = &dev->me_clients[i];
- if (me_cl->client_id == cl->me_client_id) {
- if (me_cl->props.single_recv_buf != 0) {
- if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0))
- return -EINVAL;
- dev->me_clients[i].mei_flow_ctrl_creds--;
- } else {
- if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
- return -EINVAL;
- cl->mei_flow_ctrl_creds--;
- }
- return 0;
- }
+ me_cl = &dev->me_clients[id];
+ if (me_cl->props.single_recv_buf != 0) {
+ if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0))
+ return -EINVAL;
+ me_cl->mei_flow_ctrl_creds--;
+ } else {
+ if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
+ return -EINVAL;
+ cl->mei_flow_ctrl_creds--;
}
- return -ENOENT;
+ return 0;
}
/**
@@ -652,7 +710,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length)
i = mei_me_cl_by_id(dev, cl->me_client_id);
if (i < 0) {
cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
- return -ENODEV;
+ return -ENOTTY;
}
cb = mei_io_cb_init(cl, NULL);
@@ -666,8 +724,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length)
goto err;
cb->fop_type = MEI_FOP_READ;
- if (dev->hbuf_is_ready) {
- dev->hbuf_is_ready = false;
+ if (mei_hbuf_acquire(dev)) {
if (mei_hbm_cl_flow_control_req(dev, cl)) {
cl_err(dev, cl, "flow control send failed\n");
rets = -ENODEV;
@@ -687,27 +744,26 @@ err:
}
/**
- * mei_cl_irq_write_complete - write a message to device
+ * mei_cl_irq_write - write a message to device
* from the interrupt thread context
*
* @cl: client
* @cb: callback block.
- * @slots: free slots.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise error.
*/
-int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list)
+int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev;
struct mei_msg_data *buf;
struct mei_msg_hdr mei_hdr;
size_t len;
u32 msg_slots;
+ int slots;
int rets;
-
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
@@ -724,6 +780,7 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
return 0;
}
+ slots = mei_hbuf_empty_slots(dev);
len = buf->size - cb->buf_idx;
msg_slots = mei_data2slots(len);
@@ -732,13 +789,13 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
mei_hdr.reserved = 0;
mei_hdr.internal = cb->internal;
- if (*slots >= msg_slots) {
+ if (slots >= msg_slots) {
mei_hdr.length = len;
mei_hdr.msg_complete = 1;
/* Split the message only if we can write the whole host buffer */
- } else if (*slots == dev->hbuf_depth) {
- msg_slots = *slots;
- len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr);
+ } else if (slots == dev->hbuf_depth) {
+ msg_slots = slots;
+ len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr);
mei_hdr.length = len;
mei_hdr.msg_complete = 0;
} else {
@@ -749,7 +806,6 @@ int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
cl_dbg(dev, cl, "buf: size = %d idx = %lu\n",
cb->request_buffer.size, cb->buf_idx);
- *slots -= msg_slots;
rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx);
if (rets) {
cl->status = rets;
@@ -802,21 +858,29 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
cb->fop_type = MEI_FOP_WRITE;
+ cb->buf_idx = 0;
+ cl->writing_state = MEI_IDLE;
+
+ mei_hdr.host_addr = cl->host_client_id;
+ mei_hdr.me_addr = cl->me_client_id;
+ mei_hdr.reserved = 0;
+ mei_hdr.msg_complete = 0;
+ mei_hdr.internal = cb->internal;
rets = mei_cl_flow_ctrl_creds(cl);
if (rets < 0)
goto err;
- /* Host buffer is not ready, we queue the request */
- if (rets == 0 || !dev->hbuf_is_ready) {
- cb->buf_idx = 0;
- /* unseting complete will enqueue the cb for write */
- mei_hdr.msg_complete = 0;
+ if (rets == 0) {
+ cl_dbg(dev, cl, "No flow control credentials: not sending.\n");
+ rets = buf->size;
+ goto out;
+ }
+ if (!mei_hbuf_acquire(dev)) {
+ cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n");
rets = buf->size;
goto out;
}
-
- dev->hbuf_is_ready = false;
/* Check for a maximum length */
if (buf->size > mei_hbuf_max_len(dev)) {
@@ -827,12 +891,6 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
mei_hdr.msg_complete = 1;
}
- mei_hdr.host_addr = cl->host_client_id;
- mei_hdr.me_addr = cl->me_client_id;
- mei_hdr.reserved = 0;
- mei_hdr.internal = cb->internal;
-
-
rets = mei_write_message(dev, &mei_hdr, buf->data);
if (rets)
goto err;
@@ -840,13 +898,12 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
cl->writing_state = MEI_WRITING;
cb->buf_idx = mei_hdr.length;
- rets = buf->size;
out:
if (mei_hdr.msg_complete) {
- if (mei_cl_flow_ctrl_reduce(cl)) {
- rets = -ENODEV;
+ rets = mei_cl_flow_ctrl_reduce(cl);
+ if (rets < 0)
goto err;
- }
+
list_add_tail(&cb->list, &dev->write_waiting_list.list);
} else {
list_add_tail(&cb->list, &dev->write_list.list);
@@ -856,15 +913,18 @@ out:
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
mutex_unlock(&dev->device_lock);
- if (wait_event_interruptible(cl->tx_wait,
- cl->writing_state == MEI_WRITE_COMPLETE)) {
- if (signal_pending(current))
- rets = -EINTR;
- else
- rets = -ERESTARTSYS;
- }
+ rets = wait_event_interruptible(cl->tx_wait,
+ cl->writing_state == MEI_WRITE_COMPLETE);
mutex_lock(&dev->device_lock);
+ /* wait_event_interruptible returns -ERESTARTSYS */
+ if (rets) {
+ if (signal_pending(current))
+ rets = -EINTR;
+ goto err;
+ }
}
+
+ rets = buf->size;
err:
return rets;
}
@@ -905,9 +965,9 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
void mei_cl_all_disconnect(struct mei_device *dev)
{
- struct mei_cl *cl, *next;
+ struct mei_cl *cl;
- list_for_each_entry_safe(cl, next, &dev->file_list, link) {
+ list_for_each_entry(cl, &dev->file_list, link) {
cl->state = MEI_FILE_DISCONNECTED;
cl->mei_flow_ctrl_creds = 0;
cl->timer_count = 0;
@@ -922,8 +982,8 @@ void mei_cl_all_disconnect(struct mei_device *dev)
*/
void mei_cl_all_wakeup(struct mei_device *dev)
{
- struct mei_cl *cl, *next;
- list_for_each_entry_safe(cl, next, &dev->file_list, link) {
+ struct mei_cl *cl;
+ list_for_each_entry(cl, &dev->file_list, link) {
if (waitqueue_active(&cl->rx_wait)) {
cl_dbg(dev, cl, "Waking up reading client!\n");
wake_up_interruptible(&cl->rx_wait);
@@ -942,20 +1002,8 @@ void mei_cl_all_wakeup(struct mei_device *dev)
*/
void mei_cl_all_write_clear(struct mei_device *dev)
{
- struct mei_cl_cb *cb, *next;
- struct list_head *list;
-
- list = &dev->write_list.list;
- list_for_each_entry_safe(cb, next, list, list) {
- list_del(&cb->list);
- mei_io_cb_free(cb);
- }
-
- list = &dev->write_waiting_list.list;
- list_for_each_entry_safe(cb, next, list, list) {
- list_del(&cb->list);
- mei_io_cb_free(cb);
- }
+ mei_io_list_free(&dev->write_list, NULL);
+ mei_io_list_free(&dev->write_waiting_list, NULL);
}
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index c8396e582f1c..96d5de0389f9 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -45,8 +45,6 @@ static inline void mei_io_list_init(struct mei_cl_cb *list)
{
INIT_LIST_HEAD(&list->list);
}
-void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl);
-
/*
* MEI Host Client Functions
*/
@@ -61,22 +59,6 @@ int mei_cl_unlink(struct mei_cl *cl);
int mei_cl_flush_queues(struct mei_cl *cl);
struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl);
-/**
- * mei_cl_cmp_id - tells if file private data have same id
- *
- * @fe1: private data of 1. file object
- * @fe2: private data of 2. file object
- *
- * returns true - if ids are the same and not NULL
- */
-static inline bool mei_cl_cmp_id(const struct mei_cl *cl1,
- const struct mei_cl *cl2)
-{
- return cl1 && cl2 &&
- (cl1->host_client_id == cl2->host_client_id) &&
- (cl1->me_client_id == cl2->me_client_id);
-}
-
int mei_cl_flow_ctrl_creds(struct mei_cl *cl);
@@ -86,15 +68,15 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);
*/
static inline bool mei_cl_is_connected(struct mei_cl *cl)
{
- return (cl->dev &&
+ return cl->dev &&
cl->dev->dev_state == MEI_DEV_ENABLED &&
- cl->state == MEI_FILE_CONNECTED);
+ cl->state == MEI_FILE_CONNECTED;
}
static inline bool mei_cl_is_transitioning(struct mei_cl *cl)
{
- return (MEI_FILE_INITIALIZING == cl->state ||
+ return MEI_FILE_INITIALIZING == cl->state ||
MEI_FILE_DISCONNECTED == cl->state ||
- MEI_FILE_DISCONNECTING == cl->state);
+ MEI_FILE_DISCONNECTING == cl->state;
}
bool mei_cl_is_other_connecting(struct mei_cl *cl);
@@ -102,8 +84,8 @@ int mei_cl_disconnect(struct mei_cl *cl);
int mei_cl_connect(struct mei_cl *cl, struct file *file);
int mei_cl_read_start(struct mei_cl *cl, size_t length);
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking);
-int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list);
+int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list);
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c
index a3ae154444b2..ced5b777c70f 100644
--- a/drivers/misc/mei/debugfs.c
+++ b/drivers/misc/mei/debugfs.c
@@ -75,6 +75,54 @@ static const struct file_operations mei_dbgfs_fops_meclients = {
.llseek = generic_file_llseek,
};
+static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ struct mei_device *dev = fp->private_data;
+ struct mei_cl *cl;
+ const size_t bufsz = 1024;
+ char *buf;
+ int i = 0;
+ int pos = 0;
+ int ret;
+
+ if (!dev)
+ return -ENODEV;
+
+ buf = kzalloc(bufsz, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ pos += scnprintf(buf + pos, bufsz - pos,
+ " |me|host|state|rd|wr|\n");
+
+ mutex_lock(&dev->device_lock);
+
+ /* if the driver is not enabled the list won't b consitent */
+ if (dev->dev_state != MEI_DEV_ENABLED)
+ goto out;
+
+ list_for_each_entry(cl, &dev->file_list, link) {
+
+ pos += scnprintf(buf + pos, bufsz - pos,
+ "%2d|%2d|%4d|%5d|%2d|%2d|\n",
+ i, cl->me_client_id, cl->host_client_id, cl->state,
+ cl->reading_state, cl->writing_state);
+ i++;
+ }
+out:
+ mutex_unlock(&dev->device_lock);
+ ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations mei_dbgfs_fops_active = {
+ .open = simple_open,
+ .read = mei_dbgfs_read_active,
+ .llseek = generic_file_llseek,
+};
+
static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
@@ -128,6 +176,12 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
dev_err(&dev->pdev->dev, "meclients: registration failed\n");
goto err;
}
+ f = debugfs_create_file("active", S_IRUSR, dir,
+ dev, &mei_dbgfs_fops_active);
+ if (!f) {
+ dev_err(&dev->pdev->dev, "meclients: registration failed\n");
+ goto err;
+ }
f = debugfs_create_file("devstate", S_IRUSR, dir,
dev, &mei_dbgfs_fops_devstate);
if (!f) {
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index 28cd74c073b9..4960288e543a 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -21,7 +21,41 @@
#include "mei_dev.h"
#include "hbm.h"
-#include "hw-me.h"
+#include "client.h"
+
+static const char *mei_cl_conn_status_str(enum mei_cl_connect_status status)
+{
+#define MEI_CL_CS(status) case MEI_CL_CONN_##status: return #status
+ switch (status) {
+ MEI_CL_CS(SUCCESS);
+ MEI_CL_CS(NOT_FOUND);
+ MEI_CL_CS(ALREADY_STARTED);
+ MEI_CL_CS(OUT_OF_RESOURCES);
+ MEI_CL_CS(MESSAGE_SMALL);
+ default: return "unknown";
+ }
+#undef MEI_CL_CCS
+}
+
+/**
+ * mei_cl_conn_status_to_errno - convert client connect response
+ * status to error code
+ *
+ * @status: client connect response status
+ *
+ * returns corresponding error code
+ */
+static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status)
+{
+ switch (status) {
+ case MEI_CL_CONN_SUCCESS: return 0;
+ case MEI_CL_CONN_NOT_FOUND: return -ENOTTY;
+ case MEI_CL_CONN_ALREADY_STARTED: return -EBUSY;
+ case MEI_CL_CONN_OUT_OF_RESOURCES: return -EBUSY;
+ case MEI_CL_CONN_MESSAGE_SMALL: return -EINVAL;
+ default: return -EINVAL;
+ }
+}
/**
* mei_hbm_me_cl_allocate - allocates storage for me clients
@@ -100,33 +134,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf)
/**
- * is_treat_specially_client - checks if the message belongs
- * to the file private data.
- *
- * @cl: private data of the file object
- * @rs: connect response bus message
- *
- */
-static bool is_treat_specially_client(struct mei_cl *cl,
- struct hbm_client_connect_response *rs)
-{
- if (mei_hbm_cl_addr_equal(cl, rs)) {
- if (!rs->status) {
- cl->state = MEI_FILE_CONNECTED;
- cl->status = 0;
-
- } else {
- cl->state = MEI_FILE_DISCONNECTED;
- cl->status = -ENODEV;
- }
- cl->timer_count = 0;
-
- return true;
- }
- return false;
-}
-
-/**
* mei_hbm_idle - set hbm to idle state
*
* @dev: the device structure
@@ -147,13 +154,13 @@ int mei_hbm_start_wait(struct mei_device *dev)
ret = wait_event_interruptible_timeout(dev->wait_recvd_msg,
dev->hbm_state == MEI_HBM_IDLE ||
dev->hbm_state >= MEI_HBM_STARTED,
- mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT));
+ mei_secs_to_jiffies(MEI_HBM_TIMEOUT));
mutex_lock(&dev->device_lock);
if (ret <= 0 && (dev->hbm_state <= MEI_HBM_START)) {
dev->hbm_state = MEI_HBM_IDLE;
dev_err(&dev->pdev->dev, "waiting for mei start failed\n");
- return -ETIMEDOUT;
+ return -ETIME;
}
return 0;
}
@@ -283,17 +290,18 @@ static int mei_hbm_prop_req(struct mei_device *dev)
}
/**
- * mei_hbm_stop_req_prepare - prepare stop request message
+ * mei_hbm_stop_req - send stop request message
*
* @dev - mei device
- * @mei_hdr - mei message header
- * @data - hbm message body buffer
+ * @cl: client info
+ *
+ * This function returns -EIO on write failure
*/
-static void mei_hbm_stop_req_prepare(struct mei_device *dev,
- struct mei_msg_hdr *mei_hdr, unsigned char *data)
+static int mei_hbm_stop_req(struct mei_device *dev)
{
+ struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr;
struct hbm_host_stop_request *req =
- (struct hbm_host_stop_request *)data;
+ (struct hbm_host_stop_request *)dev->wr_msg.data;
const size_t len = sizeof(struct hbm_host_stop_request);
mei_hbm_hdr(mei_hdr, len);
@@ -301,6 +309,8 @@ static void mei_hbm_stop_req_prepare(struct mei_device *dev,
memset(req, 0, len);
req->hbm_cmd = HOST_STOP_REQ_CMD;
req->reason = DRIVER_STOP_REQUEST;
+
+ return mei_write_message(dev, mei_hdr, dev->wr_msg.data);
}
/**
@@ -319,8 +329,7 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl)
mei_hbm_hdr(mei_hdr, len);
mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, dev->wr_msg.data, len);
- dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n",
- cl->host_client_id, cl->me_client_id);
+ cl_dbg(dev, cl, "sending flow control\n");
return mei_write_message(dev, mei_hdr, dev->wr_msg.data);
}
@@ -330,27 +339,34 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl)
*
* @dev: the device structure
* @flow: flow control.
+ *
+ * return 0 on success, < 0 otherwise
*/
-static void mei_hbm_add_single_flow_creds(struct mei_device *dev,
+static int mei_hbm_add_single_flow_creds(struct mei_device *dev,
struct hbm_flow_control *flow)
{
- struct mei_me_client *client;
- int i;
-
- for (i = 0; i < dev->me_clients_num; i++) {
- client = &dev->me_clients[i];
- if (client && flow->me_addr == client->client_id) {
- if (client->props.single_recv_buf) {
- client->mei_flow_ctrl_creds++;
- dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n",
- flow->me_addr);
- dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n",
- client->mei_flow_ctrl_creds);
- } else {
- BUG(); /* error in flow control */
- }
- }
+ struct mei_me_client *me_cl;
+ int id;
+
+ id = mei_me_cl_by_id(dev, flow->me_addr);
+ if (id < 0) {
+ dev_err(&dev->pdev->dev, "no such me client %d\n",
+ flow->me_addr);
+ return id;
}
+
+ me_cl = &dev->me_clients[id];
+ if (me_cl->props.single_recv_buf) {
+ me_cl->mei_flow_ctrl_creds++;
+ dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n",
+ flow->me_addr);
+ dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n",
+ me_cl->mei_flow_ctrl_creds);
+ } else {
+ BUG(); /* error in flow control */
+ }
+
+ return 0;
}
/**
@@ -362,8 +378,7 @@ static void mei_hbm_add_single_flow_creds(struct mei_device *dev,
static void mei_hbm_cl_flow_control_res(struct mei_device *dev,
struct hbm_flow_control *flow_control)
{
- struct mei_cl *cl = NULL;
- struct mei_cl *next = NULL;
+ struct mei_cl *cl;
if (!flow_control->host_addr) {
/* single receive buffer */
@@ -372,7 +387,7 @@ static void mei_hbm_cl_flow_control_res(struct mei_device *dev,
}
/* normal connection */
- list_for_each_entry_safe(cl, next, &dev->file_list, link) {
+ list_for_each_entry(cl, &dev->file_list, link) {
if (mei_hbm_cl_addr_equal(cl, flow_control)) {
cl->mei_flow_ctrl_creds++;
dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n",
@@ -405,6 +420,25 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl)
}
/**
+ * mei_hbm_cl_disconnect_rsp - sends disconnect respose to the FW
+ *
+ * @dev: the device structure
+ * @cl: a client to disconnect from
+ *
+ * This function returns -EIO on write failure
+ */
+int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl)
+{
+ struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr;
+ const size_t len = sizeof(struct hbm_client_connect_response);
+
+ mei_hbm_hdr(mei_hdr, len);
+ mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, dev->wr_msg.data, len);
+
+ return mei_write_message(dev, mei_hdr, dev->wr_msg.data);
+}
+
+/**
* mei_hbm_cl_disconnect_res - disconnect response from ME
*
* @dev: the device structure
@@ -414,29 +448,23 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev,
struct hbm_client_connect_response *rs)
{
struct mei_cl *cl;
- struct mei_cl_cb *pos = NULL, *next = NULL;
-
- dev_dbg(&dev->pdev->dev,
- "disconnect_response:\n"
- "ME Client = %d\n"
- "Host Client = %d\n"
- "Status = %d\n",
- rs->me_addr,
- rs->host_addr,
- rs->status);
-
- list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) {
- cl = pos->cl;
-
- if (!cl) {
- list_del(&pos->list);
+ struct mei_cl_cb *cb, *next;
+
+ dev_dbg(&dev->pdev->dev, "hbm: disconnect response cl:host=%02d me=%02d status=%d\n",
+ rs->me_addr, rs->host_addr, rs->status);
+
+ list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) {
+ cl = cb->cl;
+
+ /* this should not happen */
+ if (WARN_ON(!cl)) {
+ list_del(&cb->list);
return;
}
- dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n");
if (mei_hbm_cl_addr_equal(cl, rs)) {
- list_del(&pos->list);
- if (!rs->status)
+ list_del(&cb->list);
+ if (rs->status == MEI_CL_DISCONN_SUCCESS)
cl->state = MEI_FILE_DISCONNECTED;
cl->status = 0;
@@ -476,46 +504,41 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev,
{
struct mei_cl *cl;
- struct mei_cl_cb *pos = NULL, *next = NULL;
+ struct mei_cl_cb *cb, *next;
- dev_dbg(&dev->pdev->dev,
- "connect_response:\n"
- "ME Client = %d\n"
- "Host Client = %d\n"
- "Status = %d\n",
- rs->me_addr,
- rs->host_addr,
- rs->status);
+ dev_dbg(&dev->pdev->dev, "hbm: connect response cl:host=%02d me=%02d status=%s\n",
+ rs->me_addr, rs->host_addr,
+ mei_cl_conn_status_str(rs->status));
- /* if WD or iamthif client treat specially */
+ cl = NULL;
- if (is_treat_specially_client(&dev->wd_cl, rs)) {
- dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n");
- mei_watchdog_register(dev);
+ list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) {
- return;
- }
+ cl = cb->cl;
+ /* this should not happen */
+ if (WARN_ON(!cl)) {
+ list_del_init(&cb->list);
+ continue;
+ }
- if (is_treat_specially_client(&dev->iamthif_cl, rs)) {
- dev->iamthif_state = MEI_IAMTHIF_IDLE;
- return;
- }
- list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) {
+ if (cb->fop_type != MEI_FOP_CONNECT)
+ continue;
- cl = pos->cl;
- if (!cl) {
- list_del(&pos->list);
- return;
- }
- if (pos->fop_type == MEI_FOP_IOCTL) {
- if (is_treat_specially_client(cl, rs)) {
- list_del(&pos->list);
- cl->status = 0;
- cl->timer_count = 0;
- break;
- }
+ if (mei_hbm_cl_addr_equal(cl, rs)) {
+ list_del(&cb->list);
+ break;
}
}
+
+ if (!cl)
+ return;
+
+ cl->timer_count = 0;
+ if (rs->status == MEI_CL_CONN_SUCCESS)
+ cl->state = MEI_FILE_CONNECTED;
+ else
+ cl->state = MEI_FILE_DISCONNECTED;
+ cl->status = mei_cl_conn_status_to_errno(rs->status);
}
@@ -525,32 +548,34 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev,
*
* @dev: the device structure.
* @disconnect_req: disconnect request bus message from the me
+ *
+ * returns -ENOMEM on allocation failure
*/
-static void mei_hbm_fw_disconnect_req(struct mei_device *dev,
+static int mei_hbm_fw_disconnect_req(struct mei_device *dev,
struct hbm_client_connect_request *disconnect_req)
{
- struct mei_cl *cl, *next;
- const size_t len = sizeof(struct hbm_client_connect_response);
+ struct mei_cl *cl;
+ struct mei_cl_cb *cb;
- list_for_each_entry_safe(cl, next, &dev->file_list, link) {
+ list_for_each_entry(cl, &dev->file_list, link) {
if (mei_hbm_cl_addr_equal(cl, disconnect_req)) {
dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n",
disconnect_req->host_addr,
disconnect_req->me_addr);
cl->state = MEI_FILE_DISCONNECTED;
cl->timer_count = 0;
- if (cl == &dev->wd_cl)
- dev->wd_pending = false;
- else if (cl == &dev->iamthif_cl)
- dev->iamthif_timer = 0;
-
- /* prepare disconnect response */
- mei_hbm_hdr(&dev->wr_ext_msg.hdr, len);
- mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD,
- dev->wr_ext_msg.data, len);
+
+ cb = mei_io_cb_init(cl, NULL);
+ if (!cb)
+ return -ENOMEM;
+ cb->fop_type = MEI_FOP_DISCONNECT_RSP;
+ cl_dbg(dev, cl, "add disconnect response as first\n");
+ list_add(&cb->list, &dev->ctrl_wr_list.list);
+
break;
}
}
+ return 0;
}
@@ -629,10 +654,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev_warn(&dev->pdev->dev, "hbm: start: version mismatch - stopping the driver.\n");
dev->hbm_state = MEI_HBM_STOPPED;
- mei_hbm_stop_req_prepare(dev, &dev->wr_msg.hdr,
- dev->wr_msg.data);
- if (mei_write_message(dev, &dev->wr_msg.hdr,
- dev->wr_msg.data)) {
+ if (mei_hbm_stop_req(dev)) {
dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n");
return -EIO;
}
@@ -778,10 +800,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
case ME_STOP_REQ_CMD:
dev_dbg(&dev->pdev->dev, "hbm: stop request: message received\n");
-
dev->hbm_state = MEI_HBM_STOPPED;
- mei_hbm_stop_req_prepare(dev, &dev->wr_ext_msg.hdr,
- dev->wr_ext_msg.data);
+ if (mei_hbm_stop_req(dev)) {
+ dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n");
+ return -EIO;
+ }
break;
default:
BUG();
diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h
index 5f92188a5cd7..20e8782711c0 100644
--- a/drivers/misc/mei/hbm.h
+++ b/drivers/misc/mei/hbm.h
@@ -54,6 +54,7 @@ int mei_hbm_start_req(struct mei_device *dev);
int mei_hbm_start_wait(struct mei_device *dev);
int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl);
+int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl);
bool mei_hbm_version_is_supported(struct mei_device *dev);
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c
index 6f656c053b14..8dbdaaef1af5 100644
--- a/drivers/misc/mei/hw-me.c
+++ b/drivers/misc/mei/hw-me.c
@@ -20,10 +20,10 @@
#include <linux/interrupt.h>
#include "mei_dev.h"
-#include "hw-me.h"
-
#include "hbm.h"
+#include "hw-me.h"
+#include "hw-me-regs.h"
/**
* mei_me_reg_read - Reads 32bit data from the mei device
@@ -240,11 +240,11 @@ static int mei_me_hw_ready_wait(struct mei_device *dev)
mutex_unlock(&dev->device_lock);
err = wait_event_interruptible_timeout(dev->wait_hw_ready,
dev->recvd_hw_ready,
- mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT));
+ mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT));
mutex_lock(&dev->device_lock);
if (!err && !dev->recvd_hw_ready) {
if (!err)
- err = -ETIMEDOUT;
+ err = -ETIME;
dev_err(&dev->pdev->dev,
"wait hw ready failed. status = %d\n", err);
return err;
@@ -303,7 +303,7 @@ static bool mei_me_hbuf_is_empty(struct mei_device *dev)
*
* @dev: the device structure
*
- * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count
+ * returns -EOVERFLOW if overflow, otherwise empty slots count
*/
static int mei_me_hbuf_empty_slots(struct mei_device *dev)
{
@@ -326,7 +326,7 @@ static size_t mei_me_hbuf_max_len(const struct mei_device *dev)
/**
- * mei_write_message - writes a message to mei device.
+ * mei_me_write_message - writes a message to mei device.
*
* @dev: the device structure
* @header: mei HECI header of message
@@ -354,7 +354,7 @@ static int mei_me_write_message(struct mei_device *dev,
dw_cnt = mei_data2slots(length);
if (empty_slots < 0 || dw_cnt > empty_slots)
- return -EIO;
+ return -EMSGSIZE;
mei_me_reg_write(hw, H_CB_WW, *((u32 *) header));
@@ -381,7 +381,7 @@ static int mei_me_write_message(struct mei_device *dev,
*
* @dev: the device structure
*
- * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count
+ * returns -EOVERFLOW if overflow, otherwise filled slots count
*/
static int mei_me_count_full_read_slots(struct mei_device *dev)
{
@@ -505,17 +505,25 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
/* check slots available for reading */
slots = mei_count_full_read_slots(dev);
while (slots > 0) {
- /* we have urgent data to send so break the read */
- if (dev->wr_ext_msg.hdr.length)
- break;
dev_dbg(&dev->pdev->dev, "slots to read = %08x\n", slots);
rets = mei_irq_read_handler(dev, &complete_list, &slots);
+ /* There is a race between ME write and interrupt delivery:
+ * Not all data is always available immediately after the
+ * interrupt, so try to read again on the next interrupt.
+ */
+ if (rets == -ENODATA)
+ break;
+
if (rets && dev->dev_state != MEI_DEV_RESETTING) {
+ dev_err(&dev->pdev->dev, "mei_irq_read_handler ret = %d.\n",
+ rets);
schedule_work(&dev->reset_work);
goto end;
}
}
+ dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
+
rets = mei_irq_write_handler(dev, &complete_list);
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
diff --git a/drivers/misc/mei/hw-txe-regs.h b/drivers/misc/mei/hw-txe-regs.h
new file mode 100644
index 000000000000..7283c24c1af1
--- /dev/null
+++ b/drivers/misc/mei/hw-txe-regs.h
@@ -0,0 +1,294 @@
+/******************************************************************************
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Intel MEI Interface Header
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2013 - 2014 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.
+ *
+ * The full GNU General Public License is included in this distribution
+ * in the file called COPYING
+ *
+ * Contact Information:
+ * Intel Corporation.
+ * linux-mei@linux.intel.com
+ * http://www.intel.com
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+#ifndef _MEI_HW_TXE_REGS_H_
+#define _MEI_HW_TXE_REGS_H_
+
+#include "hw.h"
+
+#define SEC_ALIVENESS_TIMER_TIMEOUT (5 * MSEC_PER_SEC)
+#define SEC_ALIVENESS_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
+#define SEC_RESET_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
+#define SEC_READY_WAIT_TIMEOUT (5 * MSEC_PER_SEC)
+#define START_MESSAGE_RESPONSE_WAIT_TIMEOUT (5 * MSEC_PER_SEC)
+#define RESET_CANCEL_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
+
+enum {
+ SEC_BAR,
+ BRIDGE_BAR,
+
+ NUM_OF_MEM_BARS
+};
+
+/* SeC FW Status Register
+ *
+ * FW uses this register in order to report its status to host.
+ * This register resides in PCI-E config space.
+ */
+#define PCI_CFG_TXE_FW_STS0 0x40
+# define PCI_CFG_TXE_FW_STS0_WRK_ST_MSK 0x0000000F
+# define PCI_CFG_TXE_FW_STS0_OP_ST_MSK 0x000001C0
+# define PCI_CFG_TXE_FW_STS0_FW_INIT_CMPLT 0x00000200
+# define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK 0x0000F000
+# define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK 0x000F0000
+# define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK 0x00F00000
+
+
+#define IPC_BASE_ADDR 0x80400 /* SeC IPC Base Address */
+
+/* IPC Input Doorbell Register */
+#define SEC_IPC_INPUT_DOORBELL_REG (0x0000 + IPC_BASE_ADDR)
+
+/* IPC Input Status Register
+ * This register indicates whether or not processing of
+ * the most recent command has been completed by the SEC
+ * New commands and payloads should not be written by the Host
+ * until this indicates that the previous command has been processed.
+ */
+#define SEC_IPC_INPUT_STATUS_REG (0x0008 + IPC_BASE_ADDR)
+# define SEC_IPC_INPUT_STATUS_RDY BIT(0)
+
+/* IPC Host Interrupt Status Register */
+#define SEC_IPC_HOST_INT_STATUS_REG (0x0010 + IPC_BASE_ADDR)
+#define SEC_IPC_HOST_INT_STATUS_OUT_DB BIT(0)
+#define SEC_IPC_HOST_INT_STATUS_IN_RDY BIT(1)
+#define SEC_IPC_HOST_INT_STATUS_HDCP_M0_RCVD BIT(5)
+#define SEC_IPC_HOST_INT_STATUS_ILL_MEM_ACCESS BIT(17)
+#define SEC_IPC_HOST_INT_STATUS_AES_HKEY_ERR BIT(18)
+#define SEC_IPC_HOST_INT_STATUS_DES_HKEY_ERR BIT(19)
+#define SEC_IPC_HOST_INT_STATUS_TMRMTB_OVERFLOW BIT(21)
+
+/* Convenient mask for pending interrupts */
+#define SEC_IPC_HOST_INT_STATUS_PENDING \
+ (SEC_IPC_HOST_INT_STATUS_OUT_DB| \
+ SEC_IPC_HOST_INT_STATUS_IN_RDY)
+
+/* IPC Host Interrupt Mask Register */
+#define SEC_IPC_HOST_INT_MASK_REG (0x0014 + IPC_BASE_ADDR)
+
+# define SEC_IPC_HOST_INT_MASK_OUT_DB BIT(0) /* Output Doorbell Int Mask */
+# define SEC_IPC_HOST_INT_MASK_IN_RDY BIT(1) /* Input Ready Int Mask */
+
+/* IPC Input Payload RAM */
+#define SEC_IPC_INPUT_PAYLOAD_REG (0x0100 + IPC_BASE_ADDR)
+/* IPC Shared Payload RAM */
+#define IPC_SHARED_PAYLOAD_REG (0x0200 + IPC_BASE_ADDR)
+
+/* SeC Address Translation Table Entry 2 - Ctrl
+ *
+ * This register resides also in SeC's PCI-E Memory space.
+ */
+#define SATT2_CTRL_REG 0x1040
+# define SATT2_CTRL_VALID_MSK BIT(0)
+# define SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT 8
+# define SATT2_CTRL_BRIDGE_HOST_EN_MSK BIT(12)
+
+/* SATT Table Entry 2 SAP Base Address Register */
+#define SATT2_SAP_BA_REG 0x1044
+/* SATT Table Entry 2 SAP Size Register. */
+#define SATT2_SAP_SIZE_REG 0x1048
+ /* SATT Table Entry 2 SAP Bridge Address - LSB Register */
+#define SATT2_BRG_BA_LSB_REG 0x104C
+
+/* Host High-level Interrupt Status Register */
+#define HHISR_REG 0x2020
+/* Host High-level Interrupt Enable Register
+ *
+ * Resides in PCI memory space. This is the top hierarchy for
+ * interrupts from SeC to host, aggregating both interrupts that
+ * arrive through HICR registers as well as interrupts
+ * that arrive via IPC.
+ */
+#define HHIER_REG 0x2024
+#define IPC_HHIER_SEC BIT(0)
+#define IPC_HHIER_BRIDGE BIT(1)
+#define IPC_HHIER_MSK (IPC_HHIER_SEC | IPC_HHIER_BRIDGE)
+
+/* Host High-level Interrupt Mask Register.
+ *
+ * Resides in PCI memory space.
+ * This is the top hierarchy for masking interrupts from SeC to host.
+ */
+#define HHIMR_REG 0x2028
+#define IPC_HHIMR_SEC BIT(0)
+#define IPC_HHIMR_BRIDGE BIT(1)
+
+/* Host High-level IRQ Status Register */
+#define HHIRQSR_REG 0x202C
+
+/* Host Interrupt Cause Register 0 - SeC IPC Readiness
+ *
+ * This register is both an ICR to Host from PCI Memory Space
+ * and it is also exposed in the SeC memory space.
+ * This register is used by SeC's IPC driver in order
+ * to synchronize with host about IPC interface state.
+ */
+#define HICR_SEC_IPC_READINESS_REG 0x2040
+#define HICR_SEC_IPC_READINESS_HOST_RDY BIT(0)
+#define HICR_SEC_IPC_READINESS_SEC_RDY BIT(1)
+#define HICR_SEC_IPC_READINESS_SYS_RDY \
+ (HICR_SEC_IPC_READINESS_HOST_RDY | \
+ HICR_SEC_IPC_READINESS_SEC_RDY)
+#define HICR_SEC_IPC_READINESS_RDY_CLR BIT(2)
+
+/* Host Interrupt Cause Register 1 - Aliveness Response */
+/* This register is both an ICR to Host from PCI Memory Space
+ * and it is also exposed in the SeC memory space.
+ * The register may be used by SeC to ACK a host request for aliveness.
+ */
+#define HICR_HOST_ALIVENESS_RESP_REG 0x2044
+#define HICR_HOST_ALIVENESS_RESP_ACK BIT(0)
+
+/* Host Interrupt Cause Register 2 - SeC IPC Output Doorbell */
+#define HICR_SEC_IPC_OUTPUT_DOORBELL_REG 0x2048
+
+/* Host Interrupt Status Register.
+ *
+ * Resides in PCI memory space.
+ * This is the main register involved in generating interrupts
+ * from SeC to host via HICRs.
+ * The interrupt generation rules are as follows:
+ * An interrupt will be generated whenever for any i,
+ * there is a transition from a state where at least one of
+ * the following conditions did not hold, to a state where
+ * ALL the following conditions hold:
+ * A) HISR.INT[i]_STS == 1.
+ * B) HIER.INT[i]_EN == 1.
+ */
+#define HISR_REG 0x2060
+#define HISR_INT_0_STS BIT(0)
+#define HISR_INT_1_STS BIT(1)
+#define HISR_INT_2_STS BIT(2)
+#define HISR_INT_3_STS BIT(3)
+#define HISR_INT_4_STS BIT(4)
+#define HISR_INT_5_STS BIT(5)
+#define HISR_INT_6_STS BIT(6)
+#define HISR_INT_7_STS BIT(7)
+#define HISR_INT_STS_MSK \
+ (HISR_INT_0_STS | HISR_INT_1_STS | HISR_INT_2_STS)
+
+/* Host Interrupt Enable Register. Resides in PCI memory space. */
+#define HIER_REG 0x2064
+#define HIER_INT_0_EN BIT(0)
+#define HIER_INT_1_EN BIT(1)
+#define HIER_INT_2_EN BIT(2)
+#define HIER_INT_3_EN BIT(3)
+#define HIER_INT_4_EN BIT(4)
+#define HIER_INT_5_EN BIT(5)
+#define HIER_INT_6_EN BIT(6)
+#define HIER_INT_7_EN BIT(7)
+
+#define HIER_INT_EN_MSK \
+ (HIER_INT_0_EN | HIER_INT_1_EN | HIER_INT_2_EN)
+
+
+/* SEC Memory Space IPC output payload.
+ *
+ * This register is part of the output payload which SEC provides to host.
+ */
+#define BRIDGE_IPC_OUTPUT_PAYLOAD_REG 0x20C0
+
+/* SeC Interrupt Cause Register - Host Aliveness Request
+ * This register is both an ICR to SeC and it is also exposed
+ * in the host-visible PCI memory space.
+ * The register is used by host to request SeC aliveness.
+ */
+#define SICR_HOST_ALIVENESS_REQ_REG 0x214C
+#define SICR_HOST_ALIVENESS_REQ_REQUESTED BIT(0)
+
+
+/* SeC Interrupt Cause Register - Host IPC Readiness
+ *
+ * This register is both an ICR to SeC and it is also exposed
+ * in the host-visible PCI memory space.
+ * This register is used by the host's SeC driver uses in order
+ * to synchronize with SeC about IPC interface state.
+ */
+#define SICR_HOST_IPC_READINESS_REQ_REG 0x2150
+
+
+#define SICR_HOST_IPC_READINESS_HOST_RDY BIT(0)
+#define SICR_HOST_IPC_READINESS_SEC_RDY BIT(1)
+#define SICR_HOST_IPC_READINESS_SYS_RDY \
+ (SICR_HOST_IPC_READINESS_HOST_RDY | \
+ SICR_HOST_IPC_READINESS_SEC_RDY)
+#define SICR_HOST_IPC_READINESS_RDY_CLR BIT(2)
+
+/* SeC Interrupt Cause Register - SeC IPC Output Status
+ *
+ * This register indicates whether or not processing of the most recent
+ * command has been completed by the Host.
+ * New commands and payloads should not be written by SeC until this
+ * register indicates that the previous command has been processed.
+ */
+#define SICR_SEC_IPC_OUTPUT_STATUS_REG 0x2154
+# define SEC_IPC_OUTPUT_STATUS_RDY BIT(0)
+
+
+
+/* MEI IPC Message payload size 64 bytes */
+#define PAYLOAD_SIZE 64
+
+/* MAX size for SATT range 32MB */
+#define SATT_RANGE_MAX (32 << 20)
+
+
+#endif /* _MEI_HW_TXE_REGS_H_ */
+
diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c
new file mode 100644
index 000000000000..f60182a52f96
--- /dev/null
+++ b/drivers/misc/mei/hw-txe.c
@@ -0,0 +1,1107 @@
+/*
+ *
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2013-2014, Intel 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.
+ *
+ */
+
+#include <linux/pci.h>
+#include <linux/jiffies.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/irqreturn.h>
+
+#include <linux/mei.h>
+
+#include "mei_dev.h"
+#include "hw-txe.h"
+#include "client.h"
+#include "hbm.h"
+
+/**
+ * mei_txe_reg_read - Reads 32bit data from the device
+ *
+ * @base_addr: registers base address
+ * @offset: register offset
+ *
+ */
+static inline u32 mei_txe_reg_read(void __iomem *base_addr,
+ unsigned long offset)
+{
+ return ioread32(base_addr + offset);
+}
+
+/**
+ * mei_txe_reg_write - Writes 32bit data to the device
+ *
+ * @base_addr: registers base address
+ * @offset: register offset
+ * @value: the value to write
+ */
+static inline void mei_txe_reg_write(void __iomem *base_addr,
+ unsigned long offset, u32 value)
+{
+ iowrite32(value, base_addr + offset);
+}
+
+/**
+ * mei_txe_sec_reg_read_silent - Reads 32bit data from the SeC BAR
+ *
+ * @dev: the device structure
+ * @offset: register offset
+ *
+ * Doesn't check for aliveness while Reads 32bit data from the SeC BAR
+ */
+static inline u32 mei_txe_sec_reg_read_silent(struct mei_txe_hw *hw,
+ unsigned long offset)
+{
+ return mei_txe_reg_read(hw->mem_addr[SEC_BAR], offset);
+}
+
+/**
+ * mei_txe_sec_reg_read - Reads 32bit data from the SeC BAR
+ *
+ * @dev: the device structure
+ * @offset: register offset
+ *
+ * Reads 32bit data from the SeC BAR and shout loud if aliveness is not set
+ */
+static inline u32 mei_txe_sec_reg_read(struct mei_txe_hw *hw,
+ unsigned long offset)
+{
+ WARN(!hw->aliveness, "sec read: aliveness not asserted\n");
+ return mei_txe_sec_reg_read_silent(hw, offset);
+}
+/**
+ * mei_txe_sec_reg_write_silent - Writes 32bit data to the SeC BAR
+ * doesn't check for aliveness
+ *
+ * @dev: the device structure
+ * @offset: register offset
+ * @value: value to write
+ *
+ * Doesn't check for aliveness while writes 32bit data from to the SeC BAR
+ */
+static inline void mei_txe_sec_reg_write_silent(struct mei_txe_hw *hw,
+ unsigned long offset, u32 value)
+{
+ mei_txe_reg_write(hw->mem_addr[SEC_BAR], offset, value);
+}
+
+/**
+ * mei_txe_sec_reg_write - Writes 32bit data to the SeC BAR
+ *
+ * @dev: the device structure
+ * @offset: register offset
+ * @value: value to write
+ *
+ * Writes 32bit data from the SeC BAR and shout loud if aliveness is not set
+ */
+static inline void mei_txe_sec_reg_write(struct mei_txe_hw *hw,
+ unsigned long offset, u32 value)
+{
+ WARN(!hw->aliveness, "sec write: aliveness not asserted\n");
+ mei_txe_sec_reg_write_silent(hw, offset, value);
+}
+/**
+ * mei_txe_br_reg_read - Reads 32bit data from the Bridge BAR
+ *
+ * @hw: the device structure
+ * @offset: offset from which to read the data
+ *
+ */
+static inline u32 mei_txe_br_reg_read(struct mei_txe_hw *hw,
+ unsigned long offset)
+{
+ return mei_txe_reg_read(hw->mem_addr[BRIDGE_BAR], offset);
+}
+
+/**
+ * mei_txe_br_reg_write - Writes 32bit data to the Bridge BAR
+ *
+ * @hw: the device structure
+ * @offset: offset from which to write the data
+ * @value: the byte to write
+ */
+static inline void mei_txe_br_reg_write(struct mei_txe_hw *hw,
+ unsigned long offset, u32 value)
+{
+ mei_txe_reg_write(hw->mem_addr[BRIDGE_BAR], offset, value);
+}
+
+/**
+ * mei_txe_aliveness_set - request for aliveness change
+ *
+ * @dev: the device structure
+ * @req: requested aliveness value
+ *
+ * Request for aliveness change and returns true if the change is
+ * really needed and false if aliveness is already
+ * in the requested state
+ * Requires device lock to be held
+ */
+static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req)
+{
+
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ bool do_req = hw->aliveness != req;
+
+ dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n",
+ hw->aliveness, req);
+ if (do_req) {
+ hw->recvd_aliveness = false;
+ mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req);
+ }
+ return do_req;
+}
+
+
+/**
+ * mei_txe_aliveness_req_get - get aliveness requested register value
+ *
+ * @dev: the device structure
+ *
+ * Extract HICR_HOST_ALIVENESS_RESP_ACK bit from
+ * from HICR_HOST_ALIVENESS_REQ register value
+ */
+static u32 mei_txe_aliveness_req_get(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 reg;
+ reg = mei_txe_br_reg_read(hw, SICR_HOST_ALIVENESS_REQ_REG);
+ return reg & SICR_HOST_ALIVENESS_REQ_REQUESTED;
+}
+
+/**
+ * mei_txe_aliveness_get - get aliveness response register value
+ * @dev: the device structure
+ *
+ * Extract HICR_HOST_ALIVENESS_RESP_ACK bit
+ * from HICR_HOST_ALIVENESS_RESP register value
+ */
+static u32 mei_txe_aliveness_get(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 reg;
+ reg = mei_txe_br_reg_read(hw, HICR_HOST_ALIVENESS_RESP_REG);
+ return reg & HICR_HOST_ALIVENESS_RESP_ACK;
+}
+
+/**
+ * mei_txe_aliveness_poll - waits for aliveness to settle
+ *
+ * @dev: the device structure
+ * @expected: expected aliveness value
+ *
+ * Polls for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set
+ * returns > 0 if the expected value was received, -ETIME otherwise
+ */
+static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ int t = 0;
+
+ do {
+ hw->aliveness = mei_txe_aliveness_get(dev);
+ if (hw->aliveness == expected) {
+ dev_dbg(&dev->pdev->dev,
+ "aliveness settled after %d msecs\n", t);
+ return t;
+ }
+ mutex_unlock(&dev->device_lock);
+ msleep(MSEC_PER_SEC / 5);
+ mutex_lock(&dev->device_lock);
+ t += MSEC_PER_SEC / 5;
+ } while (t < SEC_ALIVENESS_WAIT_TIMEOUT);
+
+ dev_err(&dev->pdev->dev, "aliveness timed out\n");
+ return -ETIME;
+}
+
+/**
+ * mei_txe_aliveness_wait - waits for aliveness to settle
+ *
+ * @dev: the device structure
+ * @expected: expected aliveness value
+ *
+ * Waits for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set
+ * returns returns 0 on success and < 0 otherwise
+ */
+static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ const unsigned long timeout =
+ msecs_to_jiffies(SEC_ALIVENESS_WAIT_TIMEOUT);
+ long err;
+ int ret;
+
+ hw->aliveness = mei_txe_aliveness_get(dev);
+ if (hw->aliveness == expected)
+ return 0;
+
+ mutex_unlock(&dev->device_lock);
+ err = wait_event_timeout(hw->wait_aliveness,
+ hw->recvd_aliveness, timeout);
+ mutex_lock(&dev->device_lock);
+
+ hw->aliveness = mei_txe_aliveness_get(dev);
+ ret = hw->aliveness == expected ? 0 : -ETIME;
+
+ if (ret)
+ dev_err(&dev->pdev->dev, "aliveness timed out");
+ else
+ dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n",
+ jiffies_to_msecs(timeout - err));
+ hw->recvd_aliveness = false;
+ return ret;
+}
+
+/**
+ * mei_txe_aliveness_set_sync - sets an wait for aliveness to complete
+ *
+ * @dev: the device structure
+ *
+ * returns returns 0 on success and < 0 otherwise
+ */
+int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req)
+{
+ if (mei_txe_aliveness_set(dev, req))
+ return mei_txe_aliveness_wait(dev, req);
+ return 0;
+}
+
+/**
+ * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_input_ready_interrupt_enable(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 hintmsk;
+ /* Enable the SEC_IPC_HOST_INT_MASK_IN_RDY interrupt */
+ hintmsk = mei_txe_sec_reg_read(hw, SEC_IPC_HOST_INT_MASK_REG);
+ hintmsk |= SEC_IPC_HOST_INT_MASK_IN_RDY;
+ mei_txe_sec_reg_write(hw, SEC_IPC_HOST_INT_MASK_REG, hintmsk);
+}
+
+/**
+ * mei_txe_input_doorbell_set
+ * - Sets bit 0 in SEC_IPC_INPUT_DOORBELL.IPC_INPUT_DOORBELL.
+ * @dev: the device structure
+ */
+static void mei_txe_input_doorbell_set(struct mei_txe_hw *hw)
+{
+ /* Clear the interrupt cause */
+ clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause);
+ mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_DOORBELL_REG, 1);
+}
+
+/**
+ * mei_txe_output_ready_set - Sets the SICR_SEC_IPC_OUTPUT_STATUS bit to 1
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_output_ready_set(struct mei_txe_hw *hw)
+{
+ mei_txe_br_reg_write(hw,
+ SICR_SEC_IPC_OUTPUT_STATUS_REG,
+ SEC_IPC_OUTPUT_STATUS_RDY);
+}
+
+/**
+ * mei_txe_is_input_ready - check if TXE is ready for receiving data
+ *
+ * @dev: the device structure
+ */
+static bool mei_txe_is_input_ready(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 status;
+ status = mei_txe_sec_reg_read(hw, SEC_IPC_INPUT_STATUS_REG);
+ return !!(SEC_IPC_INPUT_STATUS_RDY & status);
+}
+
+/**
+ * mei_txe_intr_clear - clear all interrupts
+ *
+ * @dev: the device structure
+ */
+static inline void mei_txe_intr_clear(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_sec_reg_write_silent(hw, SEC_IPC_HOST_INT_STATUS_REG,
+ SEC_IPC_HOST_INT_STATUS_PENDING);
+ mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_STS_MSK);
+ mei_txe_br_reg_write(hw, HHISR_REG, IPC_HHIER_MSK);
+}
+
+/**
+ * mei_txe_intr_disable - disable all interrupts
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_intr_disable(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_br_reg_write(hw, HHIER_REG, 0);
+ mei_txe_br_reg_write(hw, HIER_REG, 0);
+}
+/**
+ * mei_txe_intr_disable - enable all interrupts
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_intr_enable(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_br_reg_write(hw, HHIER_REG, IPC_HHIER_MSK);
+ mei_txe_br_reg_write(hw, HIER_REG, HIER_INT_EN_MSK);
+}
+
+/**
+ * mei_txe_pending_interrupts - check if there are pending interrupts
+ * only Aliveness, Input ready, and output doorbell are of relevance
+ *
+ * @dev: the device structure
+ *
+ * Checks if there are pending interrupts
+ * only Aliveness, Readiness, Input ready, and Output doorbell are relevant
+ */
+static bool mei_txe_pending_interrupts(struct mei_device *dev)
+{
+
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ bool ret = (hw->intr_cause & (TXE_INTR_READINESS |
+ TXE_INTR_ALIVENESS |
+ TXE_INTR_IN_READY |
+ TXE_INTR_OUT_DB));
+
+ if (ret) {
+ dev_dbg(&dev->pdev->dev,
+ "Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n",
+ !!(hw->intr_cause & TXE_INTR_IN_READY),
+ !!(hw->intr_cause & TXE_INTR_READINESS),
+ !!(hw->intr_cause & TXE_INTR_ALIVENESS),
+ !!(hw->intr_cause & TXE_INTR_OUT_DB));
+ }
+ return ret;
+}
+
+/**
+ * mei_txe_input_payload_write - write a dword to the host buffer
+ * at offset idx
+ *
+ * @dev: the device structure
+ * @idx: index in the host buffer
+ * @value: value
+ */
+static void mei_txe_input_payload_write(struct mei_device *dev,
+ unsigned long idx, u32 value)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_sec_reg_write(hw, SEC_IPC_INPUT_PAYLOAD_REG +
+ (idx * sizeof(u32)), value);
+}
+
+/**
+ * mei_txe_out_data_read - read dword from the device buffer
+ * at offset idx
+ *
+ * @dev: the device structure
+ * @idx: index in the device buffer
+ *
+ * returns register value at index
+ */
+static u32 mei_txe_out_data_read(const struct mei_device *dev,
+ unsigned long idx)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ return mei_txe_br_reg_read(hw,
+ BRIDGE_IPC_OUTPUT_PAYLOAD_REG + (idx * sizeof(u32)));
+}
+
+/* Readiness */
+
+/**
+ * mei_txe_readiness_set_host_rdy
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_readiness_set_host_rdy(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_br_reg_write(hw,
+ SICR_HOST_IPC_READINESS_REQ_REG,
+ SICR_HOST_IPC_READINESS_HOST_RDY);
+}
+
+/**
+ * mei_txe_readiness_clear
+ *
+ * @dev: the device structure
+ */
+static void mei_txe_readiness_clear(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ mei_txe_br_reg_write(hw, SICR_HOST_IPC_READINESS_REQ_REG,
+ SICR_HOST_IPC_READINESS_RDY_CLR);
+}
+/**
+ * mei_txe_readiness_get - Reads and returns
+ * the HICR_SEC_IPC_READINESS register value
+ *
+ * @dev: the device structure
+ */
+static u32 mei_txe_readiness_get(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ return mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG);
+}
+
+
+/**
+ * mei_txe_readiness_is_sec_rdy - check readiness
+ * for HICR_SEC_IPC_READINESS_SEC_RDY
+ *
+ * @readiness - cached readiness state
+ */
+static inline bool mei_txe_readiness_is_sec_rdy(u32 readiness)
+{
+ return !!(readiness & HICR_SEC_IPC_READINESS_SEC_RDY);
+}
+
+/**
+ * mei_txe_hw_is_ready - check if the hw is ready
+ *
+ * @dev: the device structure
+ */
+static bool mei_txe_hw_is_ready(struct mei_device *dev)
+{
+ u32 readiness = mei_txe_readiness_get(dev);
+ return mei_txe_readiness_is_sec_rdy(readiness);
+}
+
+/**
+ * mei_txe_host_is_ready - check if the host is ready
+ *
+ * @dev: the device structure
+ */
+static inline bool mei_txe_host_is_ready(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 reg = mei_txe_br_reg_read(hw, HICR_SEC_IPC_READINESS_REG);
+ return !!(reg & HICR_SEC_IPC_READINESS_HOST_RDY);
+}
+
+/**
+ * mei_txe_readiness_wait - wait till readiness settles
+ *
+ * @dev: the device structure
+ *
+ * returns 0 on success and -ETIME on timeout
+ */
+static int mei_txe_readiness_wait(struct mei_device *dev)
+{
+ if (mei_txe_hw_is_ready(dev))
+ return 0;
+
+ mutex_unlock(&dev->device_lock);
+ wait_event_timeout(dev->wait_hw_ready, dev->recvd_hw_ready,
+ msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT));
+ mutex_lock(&dev->device_lock);
+ if (!dev->recvd_hw_ready) {
+ dev_err(&dev->pdev->dev, "wait for readiness failed\n");
+ return -ETIME;
+ }
+
+ dev->recvd_hw_ready = false;
+ return 0;
+}
+
+/**
+ * mei_txe_hw_config - configure hardware at the start of the devices
+ *
+ * @dev: the device structure
+ *
+ * Configure hardware at the start of the device should be done only
+ * once at the device probe time
+ */
+static void mei_txe_hw_config(struct mei_device *dev)
+{
+
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ /* Doesn't change in runtime */
+ dev->hbuf_depth = PAYLOAD_SIZE / 4;
+
+ hw->aliveness = mei_txe_aliveness_get(dev);
+ hw->readiness = mei_txe_readiness_get(dev);
+
+ dev_dbg(&dev->pdev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n",
+ hw->aliveness, hw->readiness);
+}
+
+
+/**
+ * mei_txe_write - writes a message to device.
+ *
+ * @dev: the device structure
+ * @header: header of message
+ * @buf: message buffer will be written
+ * returns 1 if success, 0 - otherwise.
+ */
+
+static int mei_txe_write(struct mei_device *dev,
+ struct mei_msg_hdr *header, unsigned char *buf)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ unsigned long rem;
+ unsigned long length;
+ int slots = dev->hbuf_depth;
+ u32 *reg_buf = (u32 *)buf;
+ u32 dw_cnt;
+ int i;
+
+ if (WARN_ON(!header || !buf))
+ return -EINVAL;
+
+ length = header->length;
+
+ dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header));
+
+ dw_cnt = mei_data2slots(length);
+ if (dw_cnt > slots)
+ return -EMSGSIZE;
+
+ if (WARN(!hw->aliveness, "txe write: aliveness not asserted\n"))
+ return -EAGAIN;
+
+ /* Enable Input Ready Interrupt. */
+ mei_txe_input_ready_interrupt_enable(dev);
+
+ if (!mei_txe_is_input_ready(dev)) {
+ dev_err(&dev->pdev->dev, "Input is not ready");
+ return -EAGAIN;
+ }
+
+ mei_txe_input_payload_write(dev, 0, *((u32 *)header));
+
+ for (i = 0; i < length / 4; i++)
+ mei_txe_input_payload_write(dev, i + 1, reg_buf[i]);
+
+ rem = length & 0x3;
+ if (rem > 0) {
+ u32 reg = 0;
+ memcpy(&reg, &buf[length - rem], rem);
+ mei_txe_input_payload_write(dev, i + 1, reg);
+ }
+
+ /* after each write the whole buffer is consumed */
+ hw->slots = 0;
+
+ /* Set Input-Doorbell */
+ mei_txe_input_doorbell_set(hw);
+
+ return 0;
+}
+
+/**
+ * mei_txe_hbuf_max_len - mimics the me hbuf circular buffer
+ *
+ * @dev: the device structure
+ *
+ * returns the PAYLOAD_SIZE - 4
+ */
+static size_t mei_txe_hbuf_max_len(const struct mei_device *dev)
+{
+ return PAYLOAD_SIZE - sizeof(struct mei_msg_hdr);
+}
+
+/**
+ * mei_txe_hbuf_empty_slots - mimics the me hbuf circular buffer
+ *
+ * @dev: the device structure
+ *
+ * returns always hbuf_depth
+ */
+static int mei_txe_hbuf_empty_slots(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ return hw->slots;
+}
+
+/**
+ * mei_txe_count_full_read_slots - mimics the me device circular buffer
+ *
+ * @dev: the device structure
+ *
+ * returns always buffer size in dwords count
+ */
+static int mei_txe_count_full_read_slots(struct mei_device *dev)
+{
+ /* read buffers has static size */
+ return PAYLOAD_SIZE / 4;
+}
+
+/**
+ * mei_txe_read_hdr - read message header which is always in 4 first bytes
+ *
+ * @dev: the device structure
+ *
+ * returns mei message header
+ */
+
+static u32 mei_txe_read_hdr(const struct mei_device *dev)
+{
+ return mei_txe_out_data_read(dev, 0);
+}
+/**
+ * mei_txe_read - reads a message from the txe device.
+ *
+ * @dev: the device structure
+ * @buf: message buffer will be written
+ * @len: message size will be read
+ *
+ * returns -EINVAL on error wrong argument and 0 on success
+ */
+static int mei_txe_read(struct mei_device *dev,
+ unsigned char *buf, unsigned long len)
+{
+
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 i;
+ u32 *reg_buf = (u32 *)buf;
+ u32 rem = len & 0x3;
+
+ if (WARN_ON(!buf || !len))
+ return -EINVAL;
+
+ dev_dbg(&dev->pdev->dev,
+ "buffer-length = %lu buf[0]0x%08X\n",
+ len, mei_txe_out_data_read(dev, 0));
+
+ for (i = 0; i < len / 4; i++) {
+ /* skip header: index starts from 1 */
+ u32 reg = mei_txe_out_data_read(dev, i + 1);
+ dev_dbg(&dev->pdev->dev, "buf[%d] = 0x%08X\n", i, reg);
+ *reg_buf++ = reg;
+ }
+
+ if (rem) {
+ u32 reg = mei_txe_out_data_read(dev, i + 1);
+ memcpy(reg_buf, &reg, rem);
+ }
+
+ mei_txe_output_ready_set(hw);
+ return 0;
+}
+
+/**
+ * mei_txe_hw_reset - resets host and fw.
+ *
+ * @dev: the device structure
+ * @intr_enable: if interrupt should be enabled after reset.
+ *
+ * returns 0 on success and < 0 in case of error
+ */
+static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+
+ u32 aliveness_req;
+ /*
+ * read input doorbell to ensure consistency between Bridge and SeC
+ * return value might be garbage return
+ */
+ (void)mei_txe_sec_reg_read_silent(hw, SEC_IPC_INPUT_DOORBELL_REG);
+
+ aliveness_req = mei_txe_aliveness_req_get(dev);
+ hw->aliveness = mei_txe_aliveness_get(dev);
+
+ /* Disable interrupts in this stage we will poll */
+ mei_txe_intr_disable(dev);
+
+ /*
+ * If Aliveness Request and Aliveness Response are not equal then
+ * wait for them to be equal
+ * Since we might have interrupts disabled - poll for it
+ */
+ if (aliveness_req != hw->aliveness)
+ if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) {
+ dev_err(&dev->pdev->dev,
+ "wait for aliveness settle failed ... bailing out\n");
+ return -EIO;
+ }
+
+ /*
+ * If Aliveness Request and Aliveness Response are set then clear them
+ */
+ if (aliveness_req) {
+ mei_txe_aliveness_set(dev, 0);
+ if (mei_txe_aliveness_poll(dev, 0) < 0) {
+ dev_err(&dev->pdev->dev,
+ "wait for aliveness failed ... bailing out\n");
+ return -EIO;
+ }
+ }
+
+ /*
+ * Set rediness RDY_CLR bit
+ */
+ mei_txe_readiness_clear(dev);
+
+ return 0;
+}
+
+/**
+ * mei_txe_hw_start - start the hardware after reset
+ *
+ * @dev: the device structure
+ *
+ * returns 0 on success and < 0 in case of error
+ */
+static int mei_txe_hw_start(struct mei_device *dev)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ int ret;
+
+ u32 hisr;
+
+ /* bring back interrupts */
+ mei_txe_intr_enable(dev);
+
+ ret = mei_txe_readiness_wait(dev);
+ if (ret < 0) {
+ dev_err(&dev->pdev->dev, "wating for readiness failed\n");
+ return ret;
+ }
+
+ /*
+ * If HISR.INT2_STS interrupt status bit is set then clear it.
+ */
+ hisr = mei_txe_br_reg_read(hw, HISR_REG);
+ if (hisr & HISR_INT_2_STS)
+ mei_txe_br_reg_write(hw, HISR_REG, HISR_INT_2_STS);
+
+ /* Clear the interrupt cause of OutputDoorbell */
+ clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause);
+
+ ret = mei_txe_aliveness_set_sync(dev, 1);
+ if (ret < 0) {
+ dev_err(&dev->pdev->dev, "wait for aliveness failed ... bailing out\n");
+ return ret;
+ }
+
+ /* enable input ready interrupts:
+ * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK
+ */
+ mei_txe_input_ready_interrupt_enable(dev);
+
+
+ /* Set the SICR_SEC_IPC_OUTPUT_STATUS.IPC_OUTPUT_READY bit */
+ mei_txe_output_ready_set(hw);
+
+ /* Set bit SICR_HOST_IPC_READINESS.HOST_RDY
+ */
+ mei_txe_readiness_set_host_rdy(dev);
+
+ return 0;
+}
+
+/**
+ * mei_txe_check_and_ack_intrs - translate multi BAR interrupt into
+ * single bit mask and acknowledge the interrupts
+ *
+ * @dev: the device structure
+ * @do_ack: acknowledge interrupts
+ */
+static bool mei_txe_check_and_ack_intrs(struct mei_device *dev, bool do_ack)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ u32 hisr;
+ u32 hhisr;
+ u32 ipc_isr;
+ u32 aliveness;
+ bool generated;
+
+ /* read interrupt registers */
+ hhisr = mei_txe_br_reg_read(hw, HHISR_REG);
+ generated = (hhisr & IPC_HHIER_MSK);
+ if (!generated)
+ goto out;
+
+ hisr = mei_txe_br_reg_read(hw, HISR_REG);
+
+ aliveness = mei_txe_aliveness_get(dev);
+ if (hhisr & IPC_HHIER_SEC && aliveness)
+ ipc_isr = mei_txe_sec_reg_read_silent(hw,
+ SEC_IPC_HOST_INT_STATUS_REG);
+ else
+ ipc_isr = 0;
+
+ generated = generated ||
+ (hisr & HISR_INT_STS_MSK) ||
+ (ipc_isr & SEC_IPC_HOST_INT_STATUS_PENDING);
+
+ if (generated && do_ack) {
+ /* Save the interrupt causes */
+ hw->intr_cause |= hisr & HISR_INT_STS_MSK;
+ if (ipc_isr & SEC_IPC_HOST_INT_STATUS_IN_RDY)
+ hw->intr_cause |= TXE_INTR_IN_READY;
+
+
+ mei_txe_intr_disable(dev);
+ /* Clear the interrupts in hierarchy:
+ * IPC and Bridge, than the High Level */
+ mei_txe_sec_reg_write_silent(hw,
+ SEC_IPC_HOST_INT_STATUS_REG, ipc_isr);
+ mei_txe_br_reg_write(hw, HISR_REG, hisr);
+ mei_txe_br_reg_write(hw, HHISR_REG, hhisr);
+ }
+
+out:
+ return generated;
+}
+
+/**
+ * mei_txe_irq_quick_handler - The ISR of the MEI device
+ *
+ * @irq: The irq number
+ * @dev_id: pointer to the device structure
+ *
+ * returns irqreturn_t
+ */
+irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id)
+{
+ struct mei_device *dev = dev_id;
+
+ if (mei_txe_check_and_ack_intrs(dev, true))
+ return IRQ_WAKE_THREAD;
+ return IRQ_NONE;
+}
+
+
+/**
+ * mei_txe_irq_thread_handler - txe interrupt thread
+ *
+ * @irq: The irq number
+ * @dev_id: pointer to the device structure
+ *
+ * returns irqreturn_t
+ *
+ */
+irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
+{
+ struct mei_device *dev = (struct mei_device *) dev_id;
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+ struct mei_cl_cb complete_list;
+ s32 slots;
+ int rets = 0;
+
+ dev_dbg(&dev->pdev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n",
+ mei_txe_br_reg_read(hw, HHISR_REG),
+ mei_txe_br_reg_read(hw, HISR_REG),
+ mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG));
+
+
+ /* initialize our complete list */
+ mutex_lock(&dev->device_lock);
+ mei_io_list_init(&complete_list);
+
+ if (pci_dev_msi_enabled(dev->pdev))
+ mei_txe_check_and_ack_intrs(dev, true);
+
+ /* show irq events */
+ mei_txe_pending_interrupts(dev);
+
+ hw->aliveness = mei_txe_aliveness_get(dev);
+ hw->readiness = mei_txe_readiness_get(dev);
+
+ /* Readiness:
+ * Detection of TXE driver going through reset
+ * or TXE driver resetting the HECI interface.
+ */
+ if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) {
+ dev_dbg(&dev->pdev->dev, "Readiness Interrupt was received...\n");
+
+ /* Check if SeC is going through reset */
+ if (mei_txe_readiness_is_sec_rdy(hw->readiness)) {
+ dev_dbg(&dev->pdev->dev, "we need to start the dev.\n");
+ dev->recvd_hw_ready = true;
+ } else {
+ dev->recvd_hw_ready = false;
+ if (dev->dev_state != MEI_DEV_RESETTING) {
+
+ dev_warn(&dev->pdev->dev, "FW not ready: resetting.\n");
+ schedule_work(&dev->reset_work);
+ goto end;
+
+ }
+ }
+ wake_up(&dev->wait_hw_ready);
+ }
+
+ /************************************************************/
+ /* Check interrupt cause:
+ * Aliveness: Detection of SeC acknowledge of host request that
+ * it remain alive or host cancellation of that request.
+ */
+
+ if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) {
+ /* Clear the interrupt cause */
+ dev_dbg(&dev->pdev->dev,
+ "Aliveness Interrupt: Status: %d\n", hw->aliveness);
+ hw->recvd_aliveness = true;
+ if (waitqueue_active(&hw->wait_aliveness))
+ wake_up(&hw->wait_aliveness);
+ }
+
+
+ /* Output Doorbell:
+ * Detection of SeC having sent output to host
+ */
+ slots = mei_count_full_read_slots(dev);
+ if (test_and_clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause)) {
+ /* Read from TXE */
+ rets = mei_irq_read_handler(dev, &complete_list, &slots);
+ if (rets && dev->dev_state != MEI_DEV_RESETTING) {
+ dev_err(&dev->pdev->dev,
+ "mei_irq_read_handler ret = %d.\n", rets);
+
+ schedule_work(&dev->reset_work);
+ goto end;
+ }
+ }
+ /* Input Ready: Detection if host can write to SeC */
+ if (test_and_clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause)) {
+ dev->hbuf_is_ready = true;
+ hw->slots = dev->hbuf_depth;
+ }
+
+ if (hw->aliveness && dev->hbuf_is_ready) {
+ /* get the real register value */
+ dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
+ rets = mei_irq_write_handler(dev, &complete_list);
+ if (rets && rets != -EMSGSIZE)
+ dev_err(&dev->pdev->dev, "mei_irq_write_handler ret = %d.\n",
+ rets);
+ dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
+ }
+
+ mei_irq_compl_handler(dev, &complete_list);
+
+end:
+ dev_dbg(&dev->pdev->dev, "interrupt thread end ret = %d\n", rets);
+
+ mutex_unlock(&dev->device_lock);
+
+ mei_enable_interrupts(dev);
+ return IRQ_HANDLED;
+}
+
+static const struct mei_hw_ops mei_txe_hw_ops = {
+
+ .host_is_ready = mei_txe_host_is_ready,
+
+ .hw_is_ready = mei_txe_hw_is_ready,
+ .hw_reset = mei_txe_hw_reset,
+ .hw_config = mei_txe_hw_config,
+ .hw_start = mei_txe_hw_start,
+
+ .intr_clear = mei_txe_intr_clear,
+ .intr_enable = mei_txe_intr_enable,
+ .intr_disable = mei_txe_intr_disable,
+
+ .hbuf_free_slots = mei_txe_hbuf_empty_slots,
+ .hbuf_is_ready = mei_txe_is_input_ready,
+ .hbuf_max_len = mei_txe_hbuf_max_len,
+
+ .write = mei_txe_write,
+
+ .rdbuf_full_slots = mei_txe_count_full_read_slots,
+ .read_hdr = mei_txe_read_hdr,
+
+ .read = mei_txe_read,
+
+};
+
+/**
+ * mei_txe_dev_init - allocates and initializes txe hardware specific structure
+ *
+ * @pdev - pci device
+ * returns struct mei_device * on success or NULL;
+ *
+ */
+struct mei_device *mei_txe_dev_init(struct pci_dev *pdev)
+{
+ struct mei_device *dev;
+ struct mei_txe_hw *hw;
+
+ dev = kzalloc(sizeof(struct mei_device) +
+ sizeof(struct mei_txe_hw), GFP_KERNEL);
+ if (!dev)
+ return NULL;
+
+ mei_device_init(dev);
+
+ hw = to_txe_hw(dev);
+
+ init_waitqueue_head(&hw->wait_aliveness);
+
+ dev->ops = &mei_txe_hw_ops;
+
+ dev->pdev = pdev;
+ return dev;
+}
+
+/**
+ * mei_txe_setup_satt2 - SATT2 configuration for DMA support.
+ *
+ * @dev: the device structure
+ * @addr: physical address start of the range
+ * @range: physical range size
+ */
+int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range)
+{
+ struct mei_txe_hw *hw = to_txe_hw(dev);
+
+ u32 lo32 = lower_32_bits(addr);
+ u32 hi32 = upper_32_bits(addr);
+ u32 ctrl;
+
+ /* SATT is limited to 36 Bits */
+ if (hi32 & ~0xF)
+ return -EINVAL;
+
+ /* SATT has to be 16Byte aligned */
+ if (lo32 & 0xF)
+ return -EINVAL;
+
+ /* SATT range has to be 4Bytes aligned */
+ if (range & 0x4)
+ return -EINVAL;
+
+ /* SATT is limited to 32 MB range*/
+ if (range > SATT_RANGE_MAX)
+ return -EINVAL;
+
+ ctrl = SATT2_CTRL_VALID_MSK;
+ ctrl |= hi32 << SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT;
+
+ mei_txe_br_reg_write(hw, SATT2_SAP_SIZE_REG, range);
+ mei_txe_br_reg_write(hw, SATT2_BRG_BA_LSB_REG, lo32);
+ mei_txe_br_reg_write(hw, SATT2_CTRL_REG, ctrl);
+ dev_dbg(&dev->pdev->dev, "SATT2: SAP_SIZE_OFFSET=0x%08X, BRG_BA_LSB_OFFSET=0x%08X, CTRL_OFFSET=0x%08X\n",
+ range, lo32, ctrl);
+
+ return 0;
+}
diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h
new file mode 100644
index 000000000000..0812d98633a4
--- /dev/null
+++ b/drivers/misc/mei/hw-txe.h
@@ -0,0 +1,74 @@
+/*
+ *
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2013-2014, Intel 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.
+ *
+ */
+
+#ifndef _MEI_HW_TXE_H_
+#define _MEI_HW_TXE_H_
+
+#include <linux/irqreturn.h>
+
+#include "hw.h"
+#include "hw-txe-regs.h"
+
+/* Flatten Hierarchy interrupt cause */
+#define TXE_INTR_READINESS_BIT 0 /* HISR_INT_0_STS */
+#define TXE_INTR_READINESS HISR_INT_0_STS
+#define TXE_INTR_ALIVENESS_BIT 1 /* HISR_INT_1_STS */
+#define TXE_INTR_ALIVENESS HISR_INT_1_STS
+#define TXE_INTR_OUT_DB_BIT 2 /* HISR_INT_2_STS */
+#define TXE_INTR_OUT_DB HISR_INT_2_STS
+#define TXE_INTR_IN_READY_BIT 8 /* beyond HISR */
+#define TXE_INTR_IN_READY BIT(8)
+
+/**
+ * struct mei_txe_hw - txe hardware specifics
+ *
+ * @mem_addr: SeC and BRIDGE bars
+ * @aliveness: aliveness (power gating) state of the hardware
+ * @readiness: readiness state of the hardware
+ * @wait_aliveness: aliveness wait queue
+ * @recvd_aliveness: aliveness interrupt was recived
+ * @intr_cause: translated interrupt cause
+ */
+struct mei_txe_hw {
+ void __iomem *mem_addr[NUM_OF_MEM_BARS];
+ u32 aliveness;
+ u32 readiness;
+ u32 slots;
+
+ wait_queue_head_t wait_aliveness;
+ bool recvd_aliveness;
+
+ unsigned long intr_cause;
+};
+
+#define to_txe_hw(dev) (struct mei_txe_hw *)((dev)->hw)
+
+static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw)
+{
+ return container_of((void *)hw, struct mei_device, hw);
+}
+
+struct mei_device *mei_txe_dev_init(struct pci_dev *pdev);
+
+irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id);
+irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id);
+
+int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req);
+
+int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range);
+
+
+#endif /* _MEI_HW_TXE_H_ */
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index dd44e33ad2b6..6b476ab49b2e 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -22,7 +22,7 @@
/*
* Timeouts in Seconds
*/
-#define MEI_INTEROP_TIMEOUT 7 /* Timeout on ready message */
+#define MEI_HW_READY_TIMEOUT 2 /* Timeout on ready message */
#define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */
#define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */
@@ -31,13 +31,13 @@
#define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */
#define MEI_IAMTHIF_READ_TIMER 10 /* HPS */
+#define MEI_HBM_TIMEOUT 1 /* 1 second */
/*
* MEI Version
*/
#define HBM_MINOR_VERSION 0
#define HBM_MAJOR_VERSION 1
-#define HBM_TIMEOUT 1 /* 1 second */
/* Host bus message command opcode */
#define MEI_HBM_CMD_OP_MSK 0x7f
@@ -89,19 +89,19 @@ enum mei_stop_reason_types {
* Client Connect Status
* used by hbm_client_connect_response.status
*/
-enum client_connect_status_types {
- CCS_SUCCESS = 0x00,
- CCS_NOT_FOUND = 0x01,
- CCS_ALREADY_STARTED = 0x02,
- CCS_OUT_OF_RESOURCES = 0x03,
- CCS_MESSAGE_SMALL = 0x04
+enum mei_cl_connect_status {
+ MEI_CL_CONN_SUCCESS = 0x00,
+ MEI_CL_CONN_NOT_FOUND = 0x01,
+ MEI_CL_CONN_ALREADY_STARTED = 0x02,
+ MEI_CL_CONN_OUT_OF_RESOURCES = 0x03,
+ MEI_CL_CONN_MESSAGE_SMALL = 0x04
};
/*
* Client Disconnect Status
*/
-enum client_disconnect_status_types {
- CDS_SUCCESS = 0x00
+enum mei_cl_disconnect_status {
+ MEI_CL_DISCONN_SUCCESS = 0x00
};
/*
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index cdd31c2a2a2b..4460975c0eef 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -116,7 +116,6 @@ int mei_reset(struct mei_device *dev)
mei_cl_unlink(&dev->wd_cl);
mei_cl_unlink(&dev->iamthif_cl);
mei_amthif_reset_params(dev);
- memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg));
}
@@ -126,7 +125,6 @@ int mei_reset(struct mei_device *dev)
if (ret) {
dev_err(&dev->pdev->dev, "hw_reset failed ret = %d\n", ret);
- dev->dev_state = MEI_DEV_DISABLED;
return ret;
}
@@ -139,7 +137,6 @@ int mei_reset(struct mei_device *dev)
ret = mei_hw_start(dev);
if (ret) {
dev_err(&dev->pdev->dev, "hw_start failed ret = %d\n", ret);
- dev->dev_state = MEI_DEV_DISABLED;
return ret;
}
@@ -149,7 +146,7 @@ int mei_reset(struct mei_device *dev)
ret = mei_hbm_start_req(dev);
if (ret) {
dev_err(&dev->pdev->dev, "hbm_start failed ret = %d\n", ret);
- dev->dev_state = MEI_DEV_DISABLED;
+ dev->dev_state = MEI_DEV_RESETTING;
return ret;
}
@@ -166,6 +163,7 @@ EXPORT_SYMBOL_GPL(mei_reset);
*/
int mei_start(struct mei_device *dev)
{
+ int ret;
mutex_lock(&dev->device_lock);
/* acknowledge interrupt and stop interrupts */
@@ -175,10 +173,18 @@ int mei_start(struct mei_device *dev)
dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n");
- dev->dev_state = MEI_DEV_INITIALIZING;
dev->reset_count = 0;
- mei_reset(dev);
+ do {
+ dev->dev_state = MEI_DEV_INITIALIZING;
+ ret = mei_reset(dev);
+
+ if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
+ dev_err(&dev->pdev->dev, "reset failed ret = %d", ret);
+ goto err;
+ }
+ } while (ret);
+ /* we cannot start the device w/o hbm start message completed */
if (dev->dev_state == MEI_DEV_DISABLED) {
dev_err(&dev->pdev->dev, "reset failed");
goto err;
@@ -238,27 +244,40 @@ int mei_restart(struct mei_device *dev)
mutex_unlock(&dev->device_lock);
- if (err || dev->dev_state == MEI_DEV_DISABLED)
+ if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
+ dev_err(&dev->pdev->dev, "device disabled = %d\n", err);
return -ENODEV;
+ }
+
+ /* try to start again */
+ if (err)
+ schedule_work(&dev->reset_work);
+
return 0;
}
EXPORT_SYMBOL_GPL(mei_restart);
-
static void mei_reset_work(struct work_struct *work)
{
struct mei_device *dev =
container_of(work, struct mei_device, reset_work);
+ int ret;
mutex_lock(&dev->device_lock);
- mei_reset(dev);
+ ret = mei_reset(dev);
mutex_unlock(&dev->device_lock);
- if (dev->dev_state == MEI_DEV_DISABLED)
- dev_err(&dev->pdev->dev, "reset failed");
+ if (dev->dev_state == MEI_DEV_DISABLED) {
+ dev_err(&dev->pdev->dev, "device disabled = %d\n", ret);
+ return;
+ }
+
+ /* retry reset in case of failure */
+ if (ret)
+ schedule_work(&dev->reset_work);
}
void mei_stop(struct mei_device *dev)
@@ -269,6 +288,8 @@ void mei_stop(struct mei_device *dev)
mei_nfc_host_exit(dev);
+ mei_cl_bus_remove_devices(dev);
+
mutex_lock(&dev->device_lock);
mei_wd_stop(dev);
diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c
index f0fbb5179f80..29b5af8efb71 100644
--- a/drivers/misc/mei/interrupt.c
+++ b/drivers/misc/mei/interrupt.c
@@ -26,7 +26,6 @@
#include "mei_dev.h"
#include "hbm.h"
-#include "hw-me.h"
#include "client.h"
@@ -161,29 +160,63 @@ static int mei_cl_irq_read_msg(struct mei_device *dev,
}
/**
+ * mei_cl_irq_disconnect_rsp - send disconnection response message
+ *
+ * @cl: client
+ * @cb: callback block.
+ * @cmpl_list: complete list.
+ *
+ * returns 0, OK; otherwise, error.
+ */
+static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
+{
+ struct mei_device *dev = cl->dev;
+ u32 msg_slots;
+ int slots;
+ int ret;
+
+ slots = mei_hbuf_empty_slots(dev);
+ msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_response));
+
+ if (slots < msg_slots)
+ return -EMSGSIZE;
+
+ ret = mei_hbm_cl_disconnect_rsp(dev, cl);
+
+ cl->state = MEI_FILE_DISCONNECTED;
+ cl->status = 0;
+ list_del(&cb->list);
+ mei_io_cb_free(cb);
+
+ return ret;
+}
+
+
+
+/**
* mei_cl_irq_close - processes close related operation from
* interrupt thread context - send disconnect request
*
* @cl: client
* @cb: callback block.
- * @slots: free slots.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise, error.
*/
static int mei_cl_irq_close(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list)
+ struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
+ u32 msg_slots;
+ int slots;
- u32 msg_slots =
- mei_data2slots(sizeof(struct hbm_client_connect_request));
+ msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
+ slots = mei_hbuf_empty_slots(dev);
- if (*slots < msg_slots)
+ if (slots < msg_slots)
return -EMSGSIZE;
- *slots -= msg_slots;
-
if (mei_hbm_cl_disconnect_req(dev, cl)) {
cl->status = 0;
cb->buf_idx = 0;
@@ -207,27 +240,23 @@ static int mei_cl_irq_close(struct mei_cl *cl, struct mei_cl_cb *cb,
*
* @cl: client
* @cb: callback block.
- * @slots: free slots.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise, error.
*/
static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list)
+ struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
- u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control));
-
+ u32 msg_slots;
+ int slots;
int ret;
+ msg_slots = mei_data2slots(sizeof(struct hbm_flow_control));
+ slots = mei_hbuf_empty_slots(dev);
- if (*slots < msg_slots) {
- /* return the cancel routine */
- list_del(&cb->list);
+ if (slots < msg_slots)
return -EMSGSIZE;
- }
-
- *slots -= msg_slots;
ret = mei_hbm_cl_flow_control_req(dev, cl);
if (ret) {
@@ -244,32 +273,30 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
/**
- * mei_cl_irq_ioctl - processes client ioctl related operation from the
- * interrupt thread context - send connection request
+ * mei_cl_irq_connect - send connect request in irq_thread context
*
* @cl: client
* @cb: callback block.
- * @slots: free slots.
* @cmpl_list: complete list.
*
* returns 0, OK; otherwise, error.
*/
-static int mei_cl_irq_ioctl(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list)
+static int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
+ u32 msg_slots;
+ int slots;
int ret;
- u32 msg_slots =
- mei_data2slots(sizeof(struct hbm_client_connect_request));
+ msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
+ slots = mei_hbuf_empty_slots(dev);
- if (*slots < msg_slots) {
- /* return the cancel routine */
- list_del(&cb->list);
- return -EMSGSIZE;
- }
+ if (mei_cl_is_other_connecting(cl))
+ return 0;
- *slots -= msg_slots;
+ if (slots < msg_slots)
+ return -EMSGSIZE;
cl->state = MEI_FILE_CONNECTING;
@@ -323,7 +350,7 @@ int mei_irq_read_handler(struct mei_device *dev,
dev_err(&dev->pdev->dev, "less data available than length=%08x.\n",
*slots);
/* we can't read the message */
- ret = -ERANGE;
+ ret = -ENODATA;
goto end;
}
@@ -409,10 +436,10 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
s32 slots;
int ret;
- if (!mei_hbuf_is_ready(dev)) {
- dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n");
+
+ if (!mei_hbuf_acquire(dev))
return 0;
- }
+
slots = mei_hbuf_empty_slots(dev);
if (slots <= 0)
return -EMSGSIZE;
@@ -447,29 +474,16 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
if (dev->wd_state == MEI_WD_STOPPING) {
dev->wd_state = MEI_WD_IDLE;
- wake_up_interruptible(&dev->wait_stop_wd);
+ wake_up(&dev->wait_stop_wd);
}
- if (dev->wr_ext_msg.hdr.length) {
- mei_write_message(dev, &dev->wr_ext_msg.hdr,
- dev->wr_ext_msg.data);
- slots -= mei_data2slots(dev->wr_ext_msg.hdr.length);
- dev->wr_ext_msg.hdr.length = 0;
- }
- if (dev->dev_state == MEI_DEV_ENABLED) {
+ if (mei_cl_is_connected(&dev->wd_cl)) {
if (dev->wd_pending &&
mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) {
- if (mei_wd_send(dev))
- dev_dbg(&dev->pdev->dev, "wd send failed.\n");
- else if (mei_cl_flow_ctrl_reduce(&dev->wd_cl))
- return -ENODEV;
-
+ ret = mei_wd_send(dev);
+ if (ret)
+ return ret;
dev->wd_pending = false;
-
- if (dev->wd_state == MEI_WD_RUNNING)
- slots -= mei_data2slots(MEI_WD_START_MSG_SIZE);
- else
- slots -= mei_data2slots(MEI_WD_STOP_MSG_SIZE);
}
}
@@ -484,28 +498,31 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
switch (cb->fop_type) {
case MEI_FOP_CLOSE:
/* send disconnect message */
- ret = mei_cl_irq_close(cl, cb, &slots, cmpl_list);
+ ret = mei_cl_irq_close(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_READ:
/* send flow control message */
- ret = mei_cl_irq_read(cl, cb, &slots, cmpl_list);
+ ret = mei_cl_irq_read(cl, cb, cmpl_list);
if (ret)
return ret;
break;
- case MEI_FOP_IOCTL:
+ case MEI_FOP_CONNECT:
/* connect message */
- if (mei_cl_is_other_connecting(cl))
- continue;
- ret = mei_cl_irq_ioctl(cl, cb, &slots, cmpl_list);
+ ret = mei_cl_irq_connect(cl, cb, cmpl_list);
if (ret)
return ret;
break;
-
+ case MEI_FOP_DISCONNECT_RSP:
+ /* send disconnect resp */
+ ret = mei_cl_irq_disconnect_rsp(cl, cb, cmpl_list);
+ if (ret)
+ return ret;
+ break;
default:
BUG();
}
@@ -518,11 +535,9 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
if (cl == NULL)
continue;
if (cl == &dev->iamthif_cl)
- ret = mei_amthif_irq_write_complete(cl, cb,
- &slots, cmpl_list);
+ ret = mei_amthif_irq_write(cl, cb, cmpl_list);
else
- ret = mei_cl_irq_write_complete(cl, cb,
- &slots, cmpl_list);
+ ret = mei_cl_irq_write(cl, cb, cmpl_list);
if (ret)
return ret;
}
@@ -541,8 +556,7 @@ EXPORT_SYMBOL_GPL(mei_irq_write_handler);
void mei_timer(struct work_struct *work)
{
unsigned long timeout;
- struct mei_cl *cl_pos = NULL;
- struct mei_cl *cl_next = NULL;
+ struct mei_cl *cl;
struct mei_cl_cb *cb_pos = NULL;
struct mei_cl_cb *cb_next = NULL;
@@ -570,9 +584,9 @@ void mei_timer(struct work_struct *work)
goto out;
/*** connect/disconnect timeouts ***/
- list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) {
- if (cl_pos->timer_count) {
- if (--cl_pos->timer_count == 0) {
+ list_for_each_entry(cl, &dev->file_list, link) {
+ if (cl->timer_count) {
+ if (--cl->timer_count == 0) {
dev_err(&dev->pdev->dev, "timer: connect/disconnect timeout.\n");
mei_reset(dev);
goto out;
@@ -580,6 +594,9 @@ void mei_timer(struct work_struct *work)
}
}
+ if (!mei_cl_is_connected(&dev->iamthif_cl))
+ goto out;
+
if (dev->iamthif_stall_timer) {
if (--dev->iamthif_stall_timer == 0) {
dev_err(&dev->pdev->dev, "timer: amthif hanged.\n");
@@ -619,10 +636,10 @@ void mei_timer(struct work_struct *work)
list_for_each_entry_safe(cb_pos, cb_next,
&dev->amthif_rd_complete_list.list, list) {
- cl_pos = cb_pos->file_object->private_data;
+ cl = cb_pos->file_object->private_data;
/* Finding the AMTHI entry. */
- if (cl_pos == &dev->iamthif_cl)
+ if (cl == &dev->iamthif_cl)
list_del(&cb_pos->list);
}
mei_io_cb_free(dev->iamthif_current_cb);
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 5424f8ff3f7f..b35594dbf52f 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -13,9 +13,6 @@
* more details.
*
*/
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
@@ -40,7 +37,6 @@
#include <linux/mei.h>
#include "mei_dev.h"
-#include "hw-me.h"
#include "client.h"
/**
@@ -129,17 +125,11 @@ static int mei_release(struct inode *inode, struct file *file)
}
if (cl->state == MEI_FILE_CONNECTED) {
cl->state = MEI_FILE_DISCONNECTING;
- dev_dbg(&dev->pdev->dev,
- "disconnecting client host client = %d, "
- "ME client = %d\n",
- cl->host_client_id,
- cl->me_client_id);
+ cl_dbg(dev, cl, "disconnecting\n");
rets = mei_cl_disconnect(cl);
}
mei_cl_flush_queues(cl);
- dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n",
- cl->host_client_id,
- cl->me_client_id);
+ cl_dbg(dev, cl, "removing\n");
mei_cl_unlink(cl);
@@ -284,6 +274,7 @@ copy_buffer:
length = min_t(size_t, length, cb->buf_idx - *offset);
if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) {
+ dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n");
rets = -EFAULT;
goto free;
}
@@ -340,7 +331,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
id = mei_me_cl_by_id(dev, cl->me_client_id);
if (id < 0) {
- rets = -ENODEV;
+ rets = -ENOTTY;
goto out;
}
@@ -404,7 +395,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
rets = copy_from_user(write_cb->request_buffer.data, ubuf, length);
if (rets) {
- dev_err(&dev->pdev->dev, "failed to copy data from userland\n");
+ dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n");
rets = -EFAULT;
goto out;
}
@@ -471,7 +462,7 @@ static int mei_ioctl_connect_client(struct file *file,
if (i < 0 || dev->me_clients[i].props.fixed_address) {
dev_dbg(&dev->pdev->dev, "Cannot connect to FW Client UUID = %pUl\n",
&data->in_client_uuid);
- rets = -ENODEV;
+ rets = -ENOTTY;
goto end;
}
@@ -569,7 +560,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
dev_dbg(&dev->pdev->dev, "copy connect data from user\n");
if (copy_from_user(connect_data, (char __user *)data,
sizeof(struct mei_connect_client_data))) {
- dev_err(&dev->pdev->dev, "failed to copy data from userland\n");
+ dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n");
rets = -EFAULT;
goto out;
}
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index f7de95b4cdd9..94a516716d22 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -24,7 +24,6 @@
#include <linux/mei_cl_bus.h>
#include "hw.h"
-#include "hw-me-regs.h"
#include "hbm.h"
/*
@@ -130,16 +129,18 @@ enum mei_wd_states {
/**
* enum mei_cb_file_ops - file operation associated with the callback
- * @MEI_FOP_READ - read
- * @MEI_FOP_WRITE - write
- * @MEI_FOP_IOCTL - ioctl
- * @MEI_FOP_OPEN - open
- * @MEI_FOP_CLOSE - close
+ * @MEI_FOP_READ - read
+ * @MEI_FOP_WRITE - write
+ * @MEI_FOP_CONNECT - connect
+ * @MEI_FOP_DISCONNECT_RSP - disconnect response
+ * @MEI_FOP_OPEN - open
+ * @MEI_FOP_CLOSE - close
*/
enum mei_cb_file_ops {
MEI_FOP_READ = 0,
MEI_FOP_WRITE,
- MEI_FOP_IOCTL,
+ MEI_FOP_CONNECT,
+ MEI_FOP_DISCONNECT_RSP,
MEI_FOP_OPEN,
MEI_FOP_CLOSE
};
@@ -236,20 +237,20 @@ struct mei_cl {
*/
struct mei_hw_ops {
- bool (*host_is_ready) (struct mei_device *dev);
+ bool (*host_is_ready)(struct mei_device *dev);
- bool (*hw_is_ready) (struct mei_device *dev);
- int (*hw_reset) (struct mei_device *dev, bool enable);
- int (*hw_start) (struct mei_device *dev);
- void (*hw_config) (struct mei_device *dev);
+ bool (*hw_is_ready)(struct mei_device *dev);
+ int (*hw_reset)(struct mei_device *dev, bool enable);
+ int (*hw_start)(struct mei_device *dev);
+ void (*hw_config)(struct mei_device *dev);
- void (*intr_clear) (struct mei_device *dev);
- void (*intr_enable) (struct mei_device *dev);
- void (*intr_disable) (struct mei_device *dev);
+ void (*intr_clear)(struct mei_device *dev);
+ void (*intr_enable)(struct mei_device *dev);
+ void (*intr_disable)(struct mei_device *dev);
- int (*hbuf_free_slots) (struct mei_device *dev);
- bool (*hbuf_is_ready) (struct mei_device *dev);
- size_t (*hbuf_max_len) (const struct mei_device *dev);
+ int (*hbuf_free_slots)(struct mei_device *dev);
+ bool (*hbuf_is_ready)(struct mei_device *dev);
+ size_t (*hbuf_max_len)(const struct mei_device *dev);
int (*write)(struct mei_device *dev,
struct mei_msg_hdr *hdr,
@@ -258,7 +259,7 @@ struct mei_hw_ops {
int (*rdbuf_full_slots)(struct mei_device *dev);
u32 (*read_hdr)(const struct mei_device *dev);
- int (*read) (struct mei_device *dev,
+ int (*read)(struct mei_device *dev,
unsigned char *buf, unsigned long len);
};
@@ -294,6 +295,7 @@ int __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length);
int __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length);
int __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length);
void mei_cl_bus_rx_event(struct mei_cl *cl);
+void mei_cl_bus_remove_devices(struct mei_device *dev);
int mei_cl_bus_init(void);
void mei_cl_bus_exit(void);
@@ -339,7 +341,6 @@ struct mei_cl_device {
* @hbuf_depth - depth of hardware host/write buffer is slots
* @hbuf_is_ready - query if the host host/write buffer is ready
* @wr_msg - the buffer for hbm control messages
- * @wr_ext_msg - the buffer for hbm control responses (set in read cycle)
*/
struct mei_device {
struct pci_dev *pdev; /* pointer to pci device struct */
@@ -394,11 +395,6 @@ struct mei_device {
unsigned char data[128];
} wr_msg;
- struct {
- struct mei_msg_hdr hdr;
- unsigned char data[4]; /* All HBM messages are 4 bytes */
- } wr_ext_msg; /* for control responses */
-
struct hbm_version version;
struct mei_me_client *me_clients; /* Note: memory has to be allocated */
@@ -518,8 +514,8 @@ struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
void mei_amthif_run_next_cmd(struct mei_device *dev);
-int mei_amthif_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
- s32 *slots, struct mei_cl_cb *cmpl_list);
+int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list);
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb);
int mei_amthif_irq_read_msg(struct mei_device *dev,
@@ -546,7 +542,7 @@ int mei_wd_host_init(struct mei_device *dev);
* once we got connection to the WD Client
* @dev - mei device
*/
-void mei_watchdog_register(struct mei_device *dev);
+int mei_watchdog_register(struct mei_device *dev);
/*
* mei_watchdog_unregister - Unregistering watchdog interface
* @dev - mei device
@@ -633,6 +629,8 @@ static inline int mei_count_full_read_slots(struct mei_device *dev)
return dev->ops->rdbuf_full_slots(dev);
}
+bool mei_hbuf_acquire(struct mei_device *dev);
+
#if IS_ENABLED(CONFIG_DEBUG_FS)
int mei_dbgfs_register(struct mei_device *dev, const char *name);
void mei_dbgfs_deregister(struct mei_device *dev);
diff --git a/drivers/misc/mei/nfc.c b/drivers/misc/mei/nfc.c
index a58320c0c049..3095fc514a65 100644
--- a/drivers/misc/mei/nfc.c
+++ b/drivers/misc/mei/nfc.c
@@ -364,7 +364,7 @@ static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length)
if (!wait_event_interruptible_timeout(ndev->send_wq,
ndev->recv_req_id == ndev->req_id, HZ)) {
dev_err(&dev->pdev->dev, "NFC MEI command timeout\n");
- err = -ETIMEDOUT;
+ err = -ETIME;
} else {
ndev->req_id++;
}
@@ -502,7 +502,7 @@ int mei_nfc_host_init(struct mei_device *dev)
i = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid);
if (i < 0) {
dev_info(&dev->pdev->dev, "nfc: failed to find the client\n");
- ret = -ENOENT;
+ ret = -ENOTTY;
goto err;
}
@@ -520,7 +520,7 @@ int mei_nfc_host_init(struct mei_device *dev)
i = mei_me_cl_by_uuid(dev, &mei_nfc_guid);
if (i < 0) {
dev_info(&dev->pdev->dev, "nfc: failed to find the client\n");
- ret = -ENOENT;
+ ret = -ENOTTY;
goto err;
}
@@ -552,13 +552,7 @@ err:
void mei_nfc_host_exit(struct mei_device *dev)
{
struct mei_nfc_dev *ndev = &nfc_dev;
-
cancel_work_sync(&ndev->init_work);
+}
- mutex_lock(&dev->device_lock);
- if (ndev->cl && ndev->cl->device)
- mei_cl_remove_device(ndev->cl->device);
- mei_nfc_free(ndev);
- mutex_unlock(&dev->device_lock);
-}
diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
index ddadd08956f4..1c8fd3a3e135 100644
--- a/drivers/misc/mei/pci-me.c
+++ b/drivers/misc/mei/pci-me.c
@@ -13,9 +13,6 @@
* more details.
*
*/
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
@@ -27,7 +24,6 @@
#include <linux/aio.h>
#include <linux/pci.h>
#include <linux/poll.h>
-#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/sched.h>
@@ -40,11 +36,12 @@
#include <linux/mei.h>
#include "mei_dev.h"
-#include "hw-me.h"
#include "client.h"
+#include "hw-me-regs.h"
+#include "hw-me.h"
/* mei_pci_tbl - PCI Device ID Table */
-static DEFINE_PCI_DEVICE_TABLE(mei_me_pci_tbl) = {
+static const struct pci_device_id mei_me_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)},
@@ -270,7 +267,7 @@ static void mei_me_remove(struct pci_dev *pdev)
}
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
static int mei_me_pci_suspend(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
@@ -330,11 +327,12 @@ static int mei_me_pci_resume(struct device *device)
return 0;
}
+
static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume);
#define MEI_ME_PM_OPS (&mei_me_pm_ops)
#else
#define MEI_ME_PM_OPS NULL
-#endif /* CONFIG_PM */
+#endif /* CONFIG_PM_SLEEP */
/*
* PCI driver structure
*/
diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c
new file mode 100644
index 000000000000..ad3adb009a1e
--- /dev/null
+++ b/drivers/misc/mei/pci-txe.c
@@ -0,0 +1,293 @@
+/*
+ *
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2013-2014, Intel 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/uuid.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+#include <linux/mei.h>
+
+
+#include "mei_dev.h"
+#include "hw-txe.h"
+
+static const struct pci_device_id mei_txe_pci_tbl[] = {
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F18)}, /* Baytrail */
+ {0, }
+};
+MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl);
+
+
+static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw)
+{
+ int i;
+ for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) {
+ if (hw->mem_addr[i]) {
+ pci_iounmap(pdev, hw->mem_addr[i]);
+ hw->mem_addr[i] = NULL;
+ }
+ }
+}
+/**
+ * mei_probe - Device Initialization Routine
+ *
+ * @pdev: PCI device structure
+ * @ent: entry in mei_txe_pci_tbl
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct mei_device *dev;
+ struct mei_txe_hw *hw;
+ int err;
+ int i;
+
+ /* enable pci dev */
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable pci device.\n");
+ goto end;
+ }
+ /* set PCI host mastering */
+ pci_set_master(pdev);
+ /* pci request regions for mei driver */
+ err = pci_request_regions(pdev, KBUILD_MODNAME);
+ if (err) {
+ dev_err(&pdev->dev, "failed to get pci regions.\n");
+ goto disable_device;
+ }
+
+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36));
+ if (err) {
+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (err) {
+ dev_err(&pdev->dev, "No suitable DMA available.\n");
+ goto release_regions;
+ }
+ }
+
+ /* allocates and initializes the mei dev structure */
+ dev = mei_txe_dev_init(pdev);
+ if (!dev) {
+ err = -ENOMEM;
+ goto release_regions;
+ }
+ hw = to_txe_hw(dev);
+
+ /* mapping IO device memory */
+ for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) {
+ hw->mem_addr[i] = pci_iomap(pdev, i, 0);
+ if (!hw->mem_addr[i]) {
+ dev_err(&pdev->dev, "mapping I/O device memory failure.\n");
+ err = -ENOMEM;
+ goto free_device;
+ }
+ }
+
+
+ pci_enable_msi(pdev);
+
+ /* clear spurious interrupts */
+ mei_clear_interrupts(dev);
+
+ /* request and enable interrupt */
+ if (pci_dev_msi_enabled(pdev))
+ err = request_threaded_irq(pdev->irq,
+ NULL,
+ mei_txe_irq_thread_handler,
+ IRQF_ONESHOT, KBUILD_MODNAME, dev);
+ else
+ err = request_threaded_irq(pdev->irq,
+ mei_txe_irq_quick_handler,
+ mei_txe_irq_thread_handler,
+ IRQF_SHARED, KBUILD_MODNAME, dev);
+ if (err) {
+ dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n",
+ pdev->irq);
+ goto free_device;
+ }
+
+ if (mei_start(dev)) {
+ dev_err(&pdev->dev, "init hw failure.\n");
+ err = -ENODEV;
+ goto release_irq;
+ }
+
+ err = mei_register(dev);
+ if (err)
+ goto release_irq;
+
+ pci_set_drvdata(pdev, dev);
+
+ return 0;
+
+release_irq:
+
+ mei_cancel_work(dev);
+
+ /* disable interrupts */
+ mei_disable_interrupts(dev);
+
+ free_irq(pdev->irq, dev);
+ pci_disable_msi(pdev);
+
+free_device:
+ mei_txe_pci_iounmap(pdev, hw);
+
+ kfree(dev);
+release_regions:
+ pci_release_regions(pdev);
+disable_device:
+ pci_disable_device(pdev);
+end:
+ dev_err(&pdev->dev, "initialization failed.\n");
+ return err;
+}
+
+/**
+ * mei_remove - Device Removal Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * mei_remove is called by the PCI subsystem to alert the driver
+ * that it should release a PCI device.
+ */
+static void mei_txe_remove(struct pci_dev *pdev)
+{
+ struct mei_device *dev;
+ struct mei_txe_hw *hw;
+
+ dev = pci_get_drvdata(pdev);
+ if (!dev) {
+ dev_err(&pdev->dev, "mei: dev =NULL\n");
+ return;
+ }
+
+ hw = to_txe_hw(dev);
+
+ mei_stop(dev);
+
+ /* disable interrupts */
+ mei_disable_interrupts(dev);
+ free_irq(pdev->irq, dev);
+ pci_disable_msi(pdev);
+
+ pci_set_drvdata(pdev, NULL);
+
+ mei_txe_pci_iounmap(pdev, hw);
+
+ mei_deregister(dev);
+
+ kfree(dev);
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+static int mei_txe_pci_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct mei_device *dev = pci_get_drvdata(pdev);
+
+ if (!dev)
+ return -ENODEV;
+
+ dev_dbg(&pdev->dev, "suspend\n");
+
+ mei_stop(dev);
+
+ mei_disable_interrupts(dev);
+
+ free_irq(pdev->irq, dev);
+ pci_disable_msi(pdev);
+
+ return 0;
+}
+
+static int mei_txe_pci_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct mei_device *dev;
+ int err;
+
+ dev = pci_get_drvdata(pdev);
+ if (!dev)
+ return -ENODEV;
+
+ pci_enable_msi(pdev);
+
+ mei_clear_interrupts(dev);
+
+ /* request and enable interrupt */
+ if (pci_dev_msi_enabled(pdev))
+ err = request_threaded_irq(pdev->irq,
+ NULL,
+ mei_txe_irq_thread_handler,
+ IRQF_ONESHOT, KBUILD_MODNAME, dev);
+ else
+ err = request_threaded_irq(pdev->irq,
+ mei_txe_irq_quick_handler,
+ mei_txe_irq_thread_handler,
+ IRQF_SHARED, KBUILD_MODNAME, dev);
+ if (err) {
+ dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n",
+ pdev->irq);
+ return err;
+ }
+
+ err = mei_restart(dev);
+
+ return err;
+}
+
+static SIMPLE_DEV_PM_OPS(mei_txe_pm_ops,
+ mei_txe_pci_suspend,
+ mei_txe_pci_resume);
+
+#define MEI_TXE_PM_OPS (&mei_txe_pm_ops)
+#else
+#define MEI_TXE_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+/*
+ * PCI driver structure
+ */
+static struct pci_driver mei_txe_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = mei_txe_pci_tbl,
+ .probe = mei_txe_probe,
+ .remove = mei_txe_remove,
+ .shutdown = mei_txe_remove,
+ .driver.pm = MEI_TXE_PM_OPS,
+};
+
+module_pci_driver(mei_txe_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel(R) Trusted Execution Environment Interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c
index f70945ed96f6..ebf1cbc198fd 100644
--- a/drivers/misc/mei/wd.c
+++ b/drivers/misc/mei/wd.c
@@ -25,7 +25,6 @@
#include "mei_dev.h"
#include "hbm.h"
-#include "hw-me.h"
#include "client.h"
static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 };
@@ -53,7 +52,7 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout)
*
* @dev: the device structure
*
- * returns -ENENT if wd client cannot be found
+ * returns -ENOTTY if wd client cannot be found
* -EIO if write has failed
* 0 on success
*/
@@ -73,7 +72,7 @@ int mei_wd_host_init(struct mei_device *dev)
id = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (id < 0) {
dev_info(&dev->pdev->dev, "wd: failed to find the client\n");
- return id;
+ return -ENOTTY;
}
cl->me_client_id = dev->me_clients[id].client_id;
@@ -87,15 +86,20 @@ int mei_wd_host_init(struct mei_device *dev)
cl->state = MEI_FILE_CONNECTING;
- if (mei_hbm_cl_connect_req(dev, cl)) {
- dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n");
- cl->state = MEI_FILE_DISCONNECTED;
- cl->host_client_id = 0;
- return -EIO;
+ ret = mei_cl_connect(cl, NULL);
+
+ if (ret) {
+ dev_err(&dev->pdev->dev, "wd: failed to connect = %d\n", ret);
+ mei_cl_unlink(cl);
+ return ret;
}
- cl->timer_count = MEI_CONNECT_TIMEOUT;
- return 0;
+ ret = mei_watchdog_register(dev);
+ if (ret) {
+ mei_cl_disconnect(cl);
+ mei_cl_unlink(cl);
+ }
+ return ret;
}
/**
@@ -106,13 +110,16 @@ int mei_wd_host_init(struct mei_device *dev)
* returns 0 if success,
* -EIO when message send fails
* -EINVAL when invalid message is to be sent
+ * -ENODEV on flow control failure
*/
int mei_wd_send(struct mei_device *dev)
{
+ struct mei_cl *cl = &dev->wd_cl;
struct mei_msg_hdr hdr;
+ int ret;
- hdr.host_addr = dev->wd_cl.host_client_id;
- hdr.me_addr = dev->wd_cl.me_client_id;
+ hdr.host_addr = cl->host_client_id;
+ hdr.me_addr = cl->me_client_id;
hdr.msg_complete = 1;
hdr.reserved = 0;
hdr.internal = 0;
@@ -121,10 +128,24 @@ int mei_wd_send(struct mei_device *dev)
hdr.length = MEI_WD_START_MSG_SIZE;
else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE))
hdr.length = MEI_WD_STOP_MSG_SIZE;
- else
+ else {
+ dev_err(&dev->pdev->dev, "wd: invalid message is to be sent, aborting\n");
return -EINVAL;
+ }
- return mei_write_message(dev, &hdr, dev->wd_data);
+ ret = mei_write_message(dev, &hdr, dev->wd_data);
+ if (ret) {
+ dev_err(&dev->pdev->dev, "wd: write message failed\n");
+ return ret;
+ }
+
+ ret = mei_cl_flow_ctrl_reduce(cl);
+ if (ret) {
+ dev_err(&dev->pdev->dev, "wd: flow_ctrl_reduce failed.\n");
+ return ret;
+ }
+
+ return 0;
}
/**
@@ -133,9 +154,11 @@ int mei_wd_send(struct mei_device *dev)
* @dev: the device structure
* @preserve: indicate if to keep the timeout value
*
- * returns 0 if success,
- * -EIO when message send fails
+ * returns 0 if success
+ * on error:
+ * -EIO when message send fails
* -EINVAL when invalid message is to be sent
+ * -ETIME on message timeout
*/
int mei_wd_stop(struct mei_device *dev)
{
@@ -151,20 +174,12 @@ int mei_wd_stop(struct mei_device *dev)
ret = mei_cl_flow_ctrl_creds(&dev->wd_cl);
if (ret < 0)
- goto out;
-
- if (ret && dev->hbuf_is_ready) {
- ret = 0;
- dev->hbuf_is_ready = false;
-
- if (!mei_wd_send(dev)) {
- ret = mei_cl_flow_ctrl_reduce(&dev->wd_cl);
- if (ret)
- goto out;
- } else {
- dev_err(&dev->pdev->dev, "wd: send stop failed\n");
- }
+ goto err;
+ if (ret && mei_hbuf_acquire(dev)) {
+ ret = mei_wd_send(dev);
+ if (ret)
+ goto err;
dev->wd_pending = false;
} else {
dev->wd_pending = true;
@@ -172,21 +187,21 @@ int mei_wd_stop(struct mei_device *dev)
mutex_unlock(&dev->device_lock);
- ret = wait_event_interruptible_timeout(dev->wait_stop_wd,
- dev->wd_state == MEI_WD_IDLE,
- msecs_to_jiffies(MEI_WD_STOP_TIMEOUT));
+ ret = wait_event_timeout(dev->wait_stop_wd,
+ dev->wd_state == MEI_WD_IDLE,
+ msecs_to_jiffies(MEI_WD_STOP_TIMEOUT));
mutex_lock(&dev->device_lock);
- if (dev->wd_state == MEI_WD_IDLE) {
- dev_dbg(&dev->pdev->dev, "wd: stop completed ret=%d.\n", ret);
- ret = 0;
- } else {
- if (!ret)
- ret = -ETIMEDOUT;
+ if (dev->wd_state != MEI_WD_IDLE) {
+ /* timeout */
+ ret = -ETIME;
dev_warn(&dev->pdev->dev,
"wd: stop failed to complete ret=%d.\n", ret);
+ goto err;
}
-
-out:
+ dev_dbg(&dev->pdev->dev, "wd: stop completed after %u msec\n",
+ MEI_WD_STOP_TIMEOUT - jiffies_to_msecs(ret));
+ return 0;
+err:
return ret;
}
@@ -260,8 +275,8 @@ static int mei_wd_ops_stop(struct watchdog_device *wd_dev)
*/
static int mei_wd_ops_ping(struct watchdog_device *wd_dev)
{
- int ret = 0;
struct mei_device *dev;
+ int ret;
dev = watchdog_get_drvdata(wd_dev);
if (!dev)
@@ -277,25 +292,18 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev)
dev->wd_state = MEI_WD_RUNNING;
+ ret = mei_cl_flow_ctrl_creds(&dev->wd_cl);
+ if (ret < 0)
+ goto end;
/* Check if we can send the ping to HW*/
- if (dev->hbuf_is_ready && mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) {
+ if (ret && mei_hbuf_acquire(dev)) {
- dev->hbuf_is_ready = false;
dev_dbg(&dev->pdev->dev, "wd: sending ping\n");
- if (mei_wd_send(dev)) {
- dev_err(&dev->pdev->dev, "wd: send failed.\n");
- ret = -EIO;
+ ret = mei_wd_send(dev);
+ if (ret)
goto end;
- }
-
- if (mei_cl_flow_ctrl_reduce(&dev->wd_cl)) {
- dev_err(&dev->pdev->dev,
- "wd: mei_cl_flow_ctrl_reduce() failed.\n");
- ret = -EIO;
- goto end;
- }
-
+ dev->wd_pending = false;
} else {
dev->wd_pending = true;
}
@@ -363,17 +371,25 @@ static struct watchdog_device amt_wd_dev = {
};
-void mei_watchdog_register(struct mei_device *dev)
+int mei_watchdog_register(struct mei_device *dev)
{
- if (watchdog_register_device(&amt_wd_dev)) {
- dev_err(&dev->pdev->dev,
- "wd: unable to register watchdog device.\n");
- return;
+
+ int ret;
+
+ /* unlock to perserve correct locking order */
+ mutex_unlock(&dev->device_lock);
+ ret = watchdog_register_device(&amt_wd_dev);
+ mutex_lock(&dev->device_lock);
+ if (ret) {
+ dev_err(&dev->pdev->dev, "wd: unable to register watchdog device = %d.\n",
+ ret);
+ return ret;
}
dev_dbg(&dev->pdev->dev,
"wd: successfully register watchdog interface.\n");
watchdog_set_drvdata(&amt_wd_dev, dev);
+ return 0;
}
void mei_watchdog_unregister(struct mei_device *dev)
diff --git a/drivers/misc/mic/host/mic_intr.c b/drivers/misc/mic/host/mic_intr.c
index f9c29bc918bc..dbc5afde1392 100644
--- a/drivers/misc/mic/host/mic_intr.c
+++ b/drivers/misc/mic/host/mic_intr.c
@@ -194,7 +194,7 @@ static int mic_setup_msix(struct mic_device *mdev, struct pci_dev *pdev)
for (i = 0; i < MIC_MIN_MSIX; i++)
mdev->irq_info.msix_entries[i].entry = i;
- rc = pci_enable_msix(pdev, mdev->irq_info.msix_entries,
+ rc = pci_enable_msix_exact(pdev, mdev->irq_info.msix_entries,
MIC_MIN_MSIX);
if (rc) {
dev_dbg(&pdev->dev, "Error enabling MSIx. rc = %d\n", rc);
diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c
index a5925f7f17f6..956597321d2a 100644
--- a/drivers/misc/pch_phub.c
+++ b/drivers/misc/pch_phub.c
@@ -636,6 +636,7 @@ static ssize_t store_pch_mac(struct device *dev, struct device_attribute *attr,
u8 mac[ETH_ALEN];
ssize_t rom_size;
struct pch_phub_reg *chip = dev_get_drvdata(dev);
+ int ret;
if (!mac_pton(buf, mac))
return -EINVAL;
@@ -644,8 +645,10 @@ static ssize_t store_pch_mac(struct device *dev, struct device_attribute *attr,
if (!chip->pch_phub_extrom_base_address)
return -ENOMEM;
- pch_phub_write_gbe_mac_addr(chip, mac);
+ ret = pch_phub_write_gbe_mac_addr(chip, mac);
pci_unmap_rom(chip->pdev, chip->pch_phub_extrom_base_address);
+ if (ret)
+ return ret;
return count;
}
diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c
index afe66571ce0b..21181fa243df 100644
--- a/drivers/misc/sram.c
+++ b/drivers/misc/sram.c
@@ -24,6 +24,9 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -36,14 +39,35 @@ struct sram_dev {
struct clk *clk;
};
+struct sram_reserve {
+ struct list_head list;
+ u32 start;
+ u32 size;
+};
+
+static int sram_reserve_cmp(void *priv, struct list_head *a,
+ struct list_head *b)
+{
+ struct sram_reserve *ra = list_entry(a, struct sram_reserve, list);
+ struct sram_reserve *rb = list_entry(b, struct sram_reserve, list);
+
+ return ra->start - rb->start;
+}
+
static int sram_probe(struct platform_device *pdev)
{
void __iomem *virt_base;
struct sram_dev *sram;
struct resource *res;
- unsigned long size;
+ struct device_node *np = pdev->dev.of_node, *child;
+ unsigned long size, cur_start, cur_size;
+ struct sram_reserve *rblocks, *block;
+ struct list_head reserve_list;
+ unsigned int nblocks;
int ret;
+ INIT_LIST_HEAD(&reserve_list);
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
virt_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(virt_base))
@@ -65,19 +89,106 @@ static int sram_probe(struct platform_device *pdev)
if (!sram->pool)
return -ENOMEM;
- ret = gen_pool_add_virt(sram->pool, (unsigned long)virt_base,
- res->start, size, -1);
- if (ret < 0) {
- if (sram->clk)
- clk_disable_unprepare(sram->clk);
- return ret;
+ /*
+ * We need an additional block to mark the end of the memory region
+ * after the reserved blocks from the dt are processed.
+ */
+ nblocks = (np) ? of_get_available_child_count(np) + 1 : 1;
+ rblocks = kmalloc((nblocks) * sizeof(*rblocks), GFP_KERNEL);
+ if (!rblocks) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ block = &rblocks[0];
+ for_each_available_child_of_node(np, child) {
+ struct resource child_res;
+
+ ret = of_address_to_resource(child, 0, &child_res);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "could not get address for node %s\n",
+ child->full_name);
+ goto err_chunks;
+ }
+
+ if (child_res.start < res->start || child_res.end > res->end) {
+ dev_err(&pdev->dev,
+ "reserved block %s outside the sram area\n",
+ child->full_name);
+ ret = -EINVAL;
+ goto err_chunks;
+ }
+
+ block->start = child_res.start - res->start;
+ block->size = resource_size(&child_res);
+ list_add_tail(&block->list, &reserve_list);
+
+ dev_dbg(&pdev->dev, "found reserved block 0x%x-0x%x\n",
+ block->start,
+ block->start + block->size);
+
+ block++;
+ }
+
+ /* the last chunk marks the end of the region */
+ rblocks[nblocks - 1].start = size;
+ rblocks[nblocks - 1].size = 0;
+ list_add_tail(&rblocks[nblocks - 1].list, &reserve_list);
+
+ list_sort(NULL, &reserve_list, sram_reserve_cmp);
+
+ cur_start = 0;
+
+ list_for_each_entry(block, &reserve_list, list) {
+ /* can only happen if sections overlap */
+ if (block->start < cur_start) {
+ dev_err(&pdev->dev,
+ "block at 0x%x starts after current offset 0x%lx\n",
+ block->start, cur_start);
+ ret = -EINVAL;
+ goto err_chunks;
+ }
+
+ /* current start is in a reserved block, so continue after it */
+ if (block->start == cur_start) {
+ cur_start = block->start + block->size;
+ continue;
+ }
+
+ /*
+ * allocate the space between the current starting
+ * address and the following reserved block, or the
+ * end of the region.
+ */
+ cur_size = block->start - cur_start;
+
+ dev_dbg(&pdev->dev, "adding chunk 0x%lx-0x%lx\n",
+ cur_start, cur_start + cur_size);
+ ret = gen_pool_add_virt(sram->pool,
+ (unsigned long)virt_base + cur_start,
+ res->start + cur_start, cur_size, -1);
+ if (ret < 0)
+ goto err_chunks;
+
+ /* next allocation after this reserved block */
+ cur_start = block->start + block->size;
}
+ kfree(rblocks);
+
platform_set_drvdata(pdev, sram);
dev_dbg(&pdev->dev, "SRAM pool: %ld KiB @ 0x%p\n", size / 1024, virt_base);
return 0;
+
+err_chunks:
+ kfree(rblocks);
+err_alloc:
+ if (sram->clk)
+ clk_disable_unprepare(sram->clk);
+ return ret;
}
static int sram_remove(struct platform_device *pdev)
@@ -87,8 +198,6 @@ static int sram_remove(struct platform_device *pdev)
if (gen_pool_avail(sram->pool) < gen_pool_size(sram->pool))
dev_dbg(&pdev->dev, "removed while SRAM allocated\n");
- gen_pool_destroy(sram->pool);
-
if (sram->clk)
clk_disable_unprepare(sram->clk);
diff --git a/drivers/misc/ti-st/st_core.c b/drivers/misc/ti-st/st_core.c
index 3aed525e55b4..1972d57aadb3 100644
--- a/drivers/misc/ti-st/st_core.c
+++ b/drivers/misc/ti-st/st_core.c
@@ -22,7 +22,6 @@
#define pr_fmt(fmt) "(stc): " fmt
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/tty.h>
#include <linux/seq_file.h>
diff --git a/drivers/misc/ti_dac7512.c b/drivers/misc/ti_dac7512.c
index 83da711ce9f1..cb0289b44a17 100644
--- a/drivers/misc/ti_dac7512.c
+++ b/drivers/misc/ti_dac7512.c
@@ -20,7 +20,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c
index 5bc10fa193de..b00335652e52 100644
--- a/drivers/misc/tsl2550.c
+++ b/drivers/misc/tsl2550.c
@@ -20,7 +20,6 @@
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c
index d35cda06b5e8..e0d5017785e5 100644
--- a/drivers/misc/vmw_vmci/vmci_guest.c
+++ b/drivers/misc/vmw_vmci/vmci_guest.c
@@ -383,11 +383,12 @@ static int vmci_enable_msix(struct pci_dev *pdev,
vmci_dev->msix_entries[i].vector = i;
}
- result = pci_enable_msix(pdev, vmci_dev->msix_entries, VMCI_MAX_INTRS);
+ result = pci_enable_msix_exact(pdev,
+ vmci_dev->msix_entries, VMCI_MAX_INTRS);
if (result == 0)
vmci_dev->exclusive_vectors = true;
- else if (result > 0)
- result = pci_enable_msix(pdev, vmci_dev->msix_entries, 1);
+ else if (result == -ENOSPC)
+ result = pci_enable_msix_exact(pdev, vmci_dev->msix_entries, 1);
return result;
}
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 90ff447bf043..a4bee41ad5cb 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -428,6 +428,7 @@ config MTD_NAND_FSL_IFC
tristate "NAND support for Freescale IFC controller"
depends on MTD_NAND && FSL_SOC
select FSL_IFC
+ select MEMORY
help
Various Freescale chips e.g P1010, include a NAND Flash machine
with built-in hardware ECC capabilities.
diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c
index 90ca7e75d6f0..50d9161c4faf 100644
--- a/drivers/mtd/nand/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/fsl_ifc_nand.c
@@ -30,7 +30,7 @@
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/nand_ecc.h>
-#include <asm/fsl_ifc.h>
+#include <linux/fsl_ifc.h>
#define FSL_IFC_V1_1_0 0x01010000
#define ERR_BYTE 0xFF /* Value returned for read
diff --git a/drivers/parport/share.c b/drivers/parport/share.c
index 6a83ee1e9178..3fa66244ce32 100644
--- a/drivers/parport/share.c
+++ b/drivers/parport/share.c
@@ -905,7 +905,8 @@ int parport_claim_or_block(struct pardevice *dev)
/* If dev->waiting is clear now, an interrupt
gave us the port and we would deadlock if we slept. */
if (dev->waiting) {
- interruptible_sleep_on (&dev->wait_q);
+ wait_event_interruptible(dev->wait_q,
+ !dev->waiting);
if (signal_pending (current)) {
return -EINTR;
}
diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
new file mode 100644
index 000000000000..bf1295e19f89
--- /dev/null
+++ b/drivers/spmi/Kconfig
@@ -0,0 +1,27 @@
+#
+# SPMI driver configuration
+#
+menuconfig SPMI
+ tristate "SPMI support"
+ help
+ SPMI (System Power Management Interface) is a two-wire
+ serial interface between baseband and application processors
+ and Power Management Integrated Circuits (PMIC).
+
+if SPMI
+
+config SPMI_MSM_PMIC_ARB
+ tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)"
+ depends on ARM
+ depends on IRQ_DOMAIN
+ depends on ARCH_QCOM || COMPILE_TEST
+ default ARCH_QCOM
+ help
+ If you say yes to this option, support will be included for the
+ built-in SPMI PMIC Arbiter interface on Qualcomm MSM family
+ processors.
+
+ This is required for communicating with Qualcomm PMICs and
+ other devices that have the SPMI interface.
+
+endif
diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile
new file mode 100644
index 000000000000..fc75104a5aab
--- /dev/null
+++ b/drivers/spmi/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for kernel SPMI framework.
+#
+obj-$(CONFIG_SPMI) += spmi.o
+
+obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o
diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c
new file mode 100644
index 000000000000..246e03a18c94
--- /dev/null
+++ b/drivers/spmi/spmi-pmic-arb.c
@@ -0,0 +1,778 @@
+/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+
+/* PMIC Arbiter configuration registers */
+#define PMIC_ARB_VERSION 0x0000
+#define PMIC_ARB_INT_EN 0x0004
+
+/* PMIC Arbiter channel registers */
+#define PMIC_ARB_CMD(N) (0x0800 + (0x80 * (N)))
+#define PMIC_ARB_CONFIG(N) (0x0804 + (0x80 * (N)))
+#define PMIC_ARB_STATUS(N) (0x0808 + (0x80 * (N)))
+#define PMIC_ARB_WDATA0(N) (0x0810 + (0x80 * (N)))
+#define PMIC_ARB_WDATA1(N) (0x0814 + (0x80 * (N)))
+#define PMIC_ARB_RDATA0(N) (0x0818 + (0x80 * (N)))
+#define PMIC_ARB_RDATA1(N) (0x081C + (0x80 * (N)))
+
+/* Interrupt Controller */
+#define SPMI_PIC_OWNER_ACC_STATUS(M, N) (0x0000 + ((32 * (M)) + (4 * (N))))
+#define SPMI_PIC_ACC_ENABLE(N) (0x0200 + (4 * (N)))
+#define SPMI_PIC_IRQ_STATUS(N) (0x0600 + (4 * (N)))
+#define SPMI_PIC_IRQ_CLEAR(N) (0x0A00 + (4 * (N)))
+
+/* Mapping Table */
+#define SPMI_MAPPING_TABLE_REG(N) (0x0B00 + (4 * (N)))
+#define SPMI_MAPPING_BIT_INDEX(X) (((X) >> 18) & 0xF)
+#define SPMI_MAPPING_BIT_IS_0_FLAG(X) (((X) >> 17) & 0x1)
+#define SPMI_MAPPING_BIT_IS_0_RESULT(X) (((X) >> 9) & 0xFF)
+#define SPMI_MAPPING_BIT_IS_1_FLAG(X) (((X) >> 8) & 0x1)
+#define SPMI_MAPPING_BIT_IS_1_RESULT(X) (((X) >> 0) & 0xFF)
+
+#define SPMI_MAPPING_TABLE_LEN 255
+#define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */
+
+/* Ownership Table */
+#define SPMI_OWNERSHIP_TABLE_REG(N) (0x0700 + (4 * (N)))
+#define SPMI_OWNERSHIP_PERIPH2OWNER(X) ((X) & 0x7)
+
+/* Channel Status fields */
+enum pmic_arb_chnl_status {
+ PMIC_ARB_STATUS_DONE = (1 << 0),
+ PMIC_ARB_STATUS_FAILURE = (1 << 1),
+ PMIC_ARB_STATUS_DENIED = (1 << 2),
+ PMIC_ARB_STATUS_DROPPED = (1 << 3),
+};
+
+/* Command register fields */
+#define PMIC_ARB_CMD_MAX_BYTE_COUNT 8
+
+/* Command Opcodes */
+enum pmic_arb_cmd_op_code {
+ PMIC_ARB_OP_EXT_WRITEL = 0,
+ PMIC_ARB_OP_EXT_READL = 1,
+ PMIC_ARB_OP_EXT_WRITE = 2,
+ PMIC_ARB_OP_RESET = 3,
+ PMIC_ARB_OP_SLEEP = 4,
+ PMIC_ARB_OP_SHUTDOWN = 5,
+ PMIC_ARB_OP_WAKEUP = 6,
+ PMIC_ARB_OP_AUTHENTICATE = 7,
+ PMIC_ARB_OP_MSTR_READ = 8,
+ PMIC_ARB_OP_MSTR_WRITE = 9,
+ PMIC_ARB_OP_EXT_READ = 13,
+ PMIC_ARB_OP_WRITE = 14,
+ PMIC_ARB_OP_READ = 15,
+ PMIC_ARB_OP_ZERO_WRITE = 16,
+};
+
+/* Maximum number of support PMIC peripherals */
+#define PMIC_ARB_MAX_PERIPHS 256
+#define PMIC_ARB_PERIPH_ID_VALID (1 << 15)
+#define PMIC_ARB_TIMEOUT_US 100
+#define PMIC_ARB_MAX_TRANS_BYTES (8)
+
+#define PMIC_ARB_APID_MASK 0xFF
+#define PMIC_ARB_PPID_MASK 0xFFF
+
+/* interrupt enable bit */
+#define SPMI_PIC_ACC_ENABLE_BIT BIT(0)
+
+/**
+ * spmi_pmic_arb_dev - SPMI PMIC Arbiter object
+ *
+ * @base: address of the PMIC Arbiter core registers.
+ * @intr: address of the SPMI interrupt control registers.
+ * @cnfg: address of the PMIC Arbiter configuration registers.
+ * @lock: lock to synchronize accesses.
+ * @channel: which channel to use for accesses.
+ * @irq: PMIC ARB interrupt.
+ * @ee: the current Execution Environment
+ * @min_apid: minimum APID (used for bounding IRQ search)
+ * @max_apid: maximum APID
+ * @mapping_table: in-memory copy of PPID -> APID mapping table.
+ * @domain: irq domain object for PMIC IRQ domain
+ * @spmic: SPMI controller object
+ * @apid_to_ppid: cached mapping from APID to PPID
+ */
+struct spmi_pmic_arb_dev {
+ void __iomem *base;
+ void __iomem *intr;
+ void __iomem *cnfg;
+ raw_spinlock_t lock;
+ u8 channel;
+ int irq;
+ u8 ee;
+ u8 min_apid;
+ u8 max_apid;
+ u32 mapping_table[SPMI_MAPPING_TABLE_LEN];
+ struct irq_domain *domain;
+ struct spmi_controller *spmic;
+ u16 apid_to_ppid[256];
+};
+
+static inline u32 pmic_arb_base_read(struct spmi_pmic_arb_dev *dev, u32 offset)
+{
+ return readl_relaxed(dev->base + offset);
+}
+
+static inline void pmic_arb_base_write(struct spmi_pmic_arb_dev *dev,
+ u32 offset, u32 val)
+{
+ writel_relaxed(val, dev->base + offset);
+}
+
+/**
+ * pa_read_data: reads pmic-arb's register and copy 1..4 bytes to buf
+ * @bc: byte count -1. range: 0..3
+ * @reg: register's address
+ * @buf: output parameter, length must be bc + 1
+ */
+static void pa_read_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc)
+{
+ u32 data = pmic_arb_base_read(dev, reg);
+ memcpy(buf, &data, (bc & 3) + 1);
+}
+
+/**
+ * pa_write_data: write 1..4 bytes from buf to pmic-arb's register
+ * @bc: byte-count -1. range: 0..3.
+ * @reg: register's address.
+ * @buf: buffer to write. length must be bc + 1.
+ */
+static void
+pa_write_data(struct spmi_pmic_arb_dev *dev, const u8 *buf, u32 reg, u8 bc)
+{
+ u32 data = 0;
+ memcpy(&data, buf, (bc & 3) + 1);
+ pmic_arb_base_write(dev, reg, data);
+}
+
+static int pmic_arb_wait_for_done(struct spmi_controller *ctrl)
+{
+ struct spmi_pmic_arb_dev *dev = spmi_controller_get_drvdata(ctrl);
+ u32 status = 0;
+ u32 timeout = PMIC_ARB_TIMEOUT_US;
+ u32 offset = PMIC_ARB_STATUS(dev->channel);
+
+ while (timeout--) {
+ status = pmic_arb_base_read(dev, offset);
+
+ if (status & PMIC_ARB_STATUS_DONE) {
+ if (status & PMIC_ARB_STATUS_DENIED) {
+ dev_err(&ctrl->dev,
+ "%s: transaction denied (0x%x)\n",
+ __func__, status);
+ return -EPERM;
+ }
+
+ if (status & PMIC_ARB_STATUS_FAILURE) {
+ dev_err(&ctrl->dev,
+ "%s: transaction failed (0x%x)\n",
+ __func__, status);
+ return -EIO;
+ }
+
+ if (status & PMIC_ARB_STATUS_DROPPED) {
+ dev_err(&ctrl->dev,
+ "%s: transaction dropped (0x%x)\n",
+ __func__, status);
+ return -EIO;
+ }
+
+ return 0;
+ }
+ udelay(1);
+ }
+
+ dev_err(&ctrl->dev,
+ "%s: timeout, status 0x%x\n",
+ __func__, status);
+ return -ETIMEDOUT;
+}
+
+/* Non-data command */
+static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid)
+{
+ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl);
+ unsigned long flags;
+ u32 cmd;
+ int rc;
+
+ /* Check for valid non-data command */
+ if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP)
+ return -EINVAL;
+
+ cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20);
+
+ raw_spin_lock_irqsave(&pmic_arb->lock, flags);
+ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
+ rc = pmic_arb_wait_for_done(ctrl);
+ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags);
+
+ return rc;
+}
+
+static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
+ u16 addr, u8 *buf, size_t len)
+{
+ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl);
+ unsigned long flags;
+ u8 bc = len - 1;
+ u32 cmd;
+ int rc;
+
+ if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
+ dev_err(&ctrl->dev,
+ "pmic-arb supports 1..%d bytes per trans, but %d requested",
+ PMIC_ARB_MAX_TRANS_BYTES, len);
+ return -EINVAL;
+ }
+
+ /* Check the opcode */
+ if (opc >= 0x60 && opc <= 0x7F)
+ opc = PMIC_ARB_OP_READ;
+ else if (opc >= 0x20 && opc <= 0x2F)
+ opc = PMIC_ARB_OP_EXT_READ;
+ else if (opc >= 0x38 && opc <= 0x3F)
+ opc = PMIC_ARB_OP_EXT_READL;
+ else
+ return -EINVAL;
+
+ cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
+
+ raw_spin_lock_irqsave(&pmic_arb->lock, flags);
+ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
+ rc = pmic_arb_wait_for_done(ctrl);
+ if (rc)
+ goto done;
+
+ pa_read_data(pmic_arb, buf, PMIC_ARB_RDATA0(pmic_arb->channel),
+ min_t(u8, bc, 3));
+
+ if (bc > 3)
+ pa_read_data(pmic_arb, buf + 4,
+ PMIC_ARB_RDATA1(pmic_arb->channel), bc - 4);
+
+done:
+ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags);
+ return rc;
+}
+
+static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
+ u16 addr, const u8 *buf, size_t len)
+{
+ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl);
+ unsigned long flags;
+ u8 bc = len - 1;
+ u32 cmd;
+ int rc;
+
+ if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
+ dev_err(&ctrl->dev,
+ "pmic-arb supports 1..%d bytes per trans, but:%d requested",
+ PMIC_ARB_MAX_TRANS_BYTES, len);
+ return -EINVAL;
+ }
+
+ /* Check the opcode */
+ if (opc >= 0x40 && opc <= 0x5F)
+ opc = PMIC_ARB_OP_WRITE;
+ else if (opc >= 0x00 && opc <= 0x0F)
+ opc = PMIC_ARB_OP_EXT_WRITE;
+ else if (opc >= 0x30 && opc <= 0x37)
+ opc = PMIC_ARB_OP_EXT_WRITEL;
+ else if (opc >= 0x80 && opc <= 0xFF)
+ opc = PMIC_ARB_OP_ZERO_WRITE;
+ else
+ return -EINVAL;
+
+ cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
+
+ /* Write data to FIFOs */
+ raw_spin_lock_irqsave(&pmic_arb->lock, flags);
+ pa_write_data(pmic_arb, buf, PMIC_ARB_WDATA0(pmic_arb->channel)
+ , min_t(u8, bc, 3));
+ if (bc > 3)
+ pa_write_data(pmic_arb, buf + 4,
+ PMIC_ARB_WDATA1(pmic_arb->channel), bc - 4);
+
+ /* Start the transaction */
+ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
+ rc = pmic_arb_wait_for_done(ctrl);
+ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags);
+
+ return rc;
+}
+
+enum qpnpint_regs {
+ QPNPINT_REG_RT_STS = 0x10,
+ QPNPINT_REG_SET_TYPE = 0x11,
+ QPNPINT_REG_POLARITY_HIGH = 0x12,
+ QPNPINT_REG_POLARITY_LOW = 0x13,
+ QPNPINT_REG_LATCHED_CLR = 0x14,
+ QPNPINT_REG_EN_SET = 0x15,
+ QPNPINT_REG_EN_CLR = 0x16,
+ QPNPINT_REG_LATCHED_STS = 0x18,
+};
+
+struct spmi_pmic_arb_qpnpint_type {
+ u8 type; /* 1 -> edge */
+ u8 polarity_high;
+ u8 polarity_low;
+} __packed;
+
+/* Simplified accessor functions for irqchip callbacks */
+static void qpnpint_spmi_write(struct irq_data *d, u8 reg, void *buf,
+ size_t len)
+{
+ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
+ u8 sid = d->hwirq >> 24;
+ u8 per = d->hwirq >> 16;
+
+ if (pmic_arb_write_cmd(pa->spmic, SPMI_CMD_EXT_WRITEL, sid,
+ (per << 8) + reg, buf, len))
+ dev_err_ratelimited(&pa->spmic->dev,
+ "failed irqchip transaction on %x\n",
+ d->irq);
+}
+
+static void qpnpint_spmi_read(struct irq_data *d, u8 reg, void *buf, size_t len)
+{
+ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
+ u8 sid = d->hwirq >> 24;
+ u8 per = d->hwirq >> 16;
+
+ if (pmic_arb_read_cmd(pa->spmic, SPMI_CMD_EXT_READL, sid,
+ (per << 8) + reg, buf, len))
+ dev_err_ratelimited(&pa->spmic->dev,
+ "failed irqchip transaction on %x\n",
+ d->irq);
+}
+
+static void periph_interrupt(struct spmi_pmic_arb_dev *pa, u8 apid)
+{
+ unsigned int irq;
+ u32 status;
+ int id;
+
+ status = readl_relaxed(pa->intr + SPMI_PIC_IRQ_STATUS(apid));
+ while (status) {
+ id = ffs(status) - 1;
+ status &= ~(1 << id);
+ irq = irq_find_mapping(pa->domain,
+ pa->apid_to_ppid[apid] << 16
+ | id << 8
+ | apid);
+ generic_handle_irq(irq);
+ }
+}
+
+static void pmic_arb_chained_irq(unsigned int irq, struct irq_desc *desc)
+{
+ struct spmi_pmic_arb_dev *pa = irq_get_handler_data(irq);
+ struct irq_chip *chip = irq_get_chip(irq);
+ void __iomem *intr = pa->intr;
+ int first = pa->min_apid >> 5;
+ int last = pa->max_apid >> 5;
+ u32 status;
+ int i, id;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = first; i <= last; ++i) {
+ status = readl_relaxed(intr +
+ SPMI_PIC_OWNER_ACC_STATUS(pa->ee, i));
+ while (status) {
+ id = ffs(status) - 1;
+ status &= ~(1 << id);
+ periph_interrupt(pa, id + i * 32);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void qpnpint_irq_ack(struct irq_data *d)
+{
+ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
+ u8 irq = d->hwirq >> 8;
+ u8 apid = d->hwirq;
+ unsigned long flags;
+ u8 data;
+
+ raw_spin_lock_irqsave(&pa->lock, flags);
+ writel_relaxed(1 << irq, pa->intr + SPMI_PIC_IRQ_CLEAR(apid));
+ raw_spin_unlock_irqrestore(&pa->lock, flags);
+
+ data = 1 << irq;
+ qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1);
+}
+
+static void qpnpint_irq_mask(struct irq_data *d)
+{
+ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
+ u8 irq = d->hwirq >> 8;
+ u8 apid = d->hwirq;
+ unsigned long flags;
+ u32 status;
+ u8 data;
+
+ raw_spin_lock_irqsave(&pa->lock, flags);
+ status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid));
+ if (status & SPMI_PIC_ACC_ENABLE_BIT) {
+ status = status & ~SPMI_PIC_ACC_ENABLE_BIT;
+ writel_relaxed(status, pa->intr + SPMI_PIC_ACC_ENABLE(apid));
+ }
+ raw_spin_unlock_irqrestore(&pa->lock, flags);
+
+ data = 1 << irq;
+ qpnpint_spmi_write(d, QPNPINT_REG_EN_CLR, &data, 1);
+}
+
+static void qpnpint_irq_unmask(struct irq_data *d)
+{
+ struct spmi_pmic_arb_dev *pa = irq_data_get_irq_chip_data(d);
+ u8 irq = d->hwirq >> 8;
+ u8 apid = d->hwirq;
+ unsigned long flags;
+ u32 status;
+ u8 data;
+
+ raw_spin_lock_irqsave(&pa->lock, flags);
+ status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid));
+ if (!(status & SPMI_PIC_ACC_ENABLE_BIT)) {
+ writel_relaxed(status | SPMI_PIC_ACC_ENABLE_BIT,
+ pa->intr + SPMI_PIC_ACC_ENABLE(apid));
+ }
+ raw_spin_unlock_irqrestore(&pa->lock, flags);
+
+ data = 1 << irq;
+ qpnpint_spmi_write(d, QPNPINT_REG_EN_SET, &data, 1);
+}
+
+static void qpnpint_irq_enable(struct irq_data *d)
+{
+ u8 irq = d->hwirq >> 8;
+ u8 data;
+
+ qpnpint_irq_unmask(d);
+
+ data = 1 << irq;
+ qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1);
+}
+
+static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ struct spmi_pmic_arb_qpnpint_type type;
+ u8 irq = d->hwirq >> 8;
+
+ qpnpint_spmi_read(d, QPNPINT_REG_SET_TYPE, &type, sizeof(type));
+
+ if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
+ type.type |= 1 << irq;
+ if (flow_type & IRQF_TRIGGER_RISING)
+ type.polarity_high |= 1 << irq;
+ if (flow_type & IRQF_TRIGGER_FALLING)
+ type.polarity_low |= 1 << irq;
+ } else {
+ if ((flow_type & (IRQF_TRIGGER_HIGH)) &&
+ (flow_type & (IRQF_TRIGGER_LOW)))
+ return -EINVAL;
+
+ type.type &= ~(1 << irq); /* level trig */
+ if (flow_type & IRQF_TRIGGER_HIGH)
+ type.polarity_high |= 1 << irq;
+ else
+ type.polarity_low |= 1 << irq;
+ }
+
+ qpnpint_spmi_write(d, QPNPINT_REG_SET_TYPE, &type, sizeof(type));
+ return 0;
+}
+
+static struct irq_chip pmic_arb_irqchip = {
+ .name = "pmic_arb",
+ .irq_enable = qpnpint_irq_enable,
+ .irq_ack = qpnpint_irq_ack,
+ .irq_mask = qpnpint_irq_mask,
+ .irq_unmask = qpnpint_irq_unmask,
+ .irq_set_type = qpnpint_irq_set_type,
+ .flags = IRQCHIP_MASK_ON_SUSPEND
+ | IRQCHIP_SKIP_SET_WAKE,
+};
+
+struct spmi_pmic_arb_irq_spec {
+ unsigned slave:4;
+ unsigned per:8;
+ unsigned irq:3;
+};
+
+static int search_mapping_table(struct spmi_pmic_arb_dev *pa,
+ struct spmi_pmic_arb_irq_spec *spec,
+ u8 *apid)
+{
+ u16 ppid = spec->slave << 8 | spec->per;
+ u32 *mapping_table = pa->mapping_table;
+ int index = 0, i;
+ u32 data;
+
+ for (i = 0; i < SPMI_MAPPING_TABLE_TREE_DEPTH; ++i) {
+ data = mapping_table[index];
+
+ if (ppid & (1 << SPMI_MAPPING_BIT_INDEX(data))) {
+ if (SPMI_MAPPING_BIT_IS_1_FLAG(data)) {
+ index = SPMI_MAPPING_BIT_IS_1_RESULT(data);
+ } else {
+ *apid = SPMI_MAPPING_BIT_IS_1_RESULT(data);
+ return 0;
+ }
+ } else {
+ if (SPMI_MAPPING_BIT_IS_0_FLAG(data)) {
+ index = SPMI_MAPPING_BIT_IS_0_RESULT(data);
+ } else {
+ *apid = SPMI_MAPPING_BIT_IS_0_RESULT(data);
+ return 0;
+ }
+ }
+ }
+
+ return -ENODEV;
+}
+
+static int qpnpint_irq_domain_dt_translate(struct irq_domain *d,
+ struct device_node *controller,
+ const u32 *intspec,
+ unsigned int intsize,
+ unsigned long *out_hwirq,
+ unsigned int *out_type)
+{
+ struct spmi_pmic_arb_dev *pa = d->host_data;
+ struct spmi_pmic_arb_irq_spec spec;
+ int err;
+ u8 apid;
+
+ dev_dbg(&pa->spmic->dev,
+ "intspec[0] 0x%1x intspec[1] 0x%02x intspec[2] 0x%02x\n",
+ intspec[0], intspec[1], intspec[2]);
+
+ if (d->of_node != controller)
+ return -EINVAL;
+ if (intsize != 4)
+ return -EINVAL;
+ if (intspec[0] > 0xF || intspec[1] > 0xFF || intspec[2] > 0x7)
+ return -EINVAL;
+
+ spec.slave = intspec[0];
+ spec.per = intspec[1];
+ spec.irq = intspec[2];
+
+ err = search_mapping_table(pa, &spec, &apid);
+ if (err)
+ return err;
+
+ pa->apid_to_ppid[apid] = spec.slave << 8 | spec.per;
+
+ /* Keep track of {max,min}_apid for bounding search during interrupt */
+ if (apid > pa->max_apid)
+ pa->max_apid = apid;
+ if (apid < pa->min_apid)
+ pa->min_apid = apid;
+
+ *out_hwirq = spec.slave << 24
+ | spec.per << 16
+ | spec.irq << 8
+ | apid;
+ *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;
+
+ dev_dbg(&pa->spmic->dev, "out_hwirq = %lu\n", *out_hwirq);
+
+ return 0;
+}
+
+static int qpnpint_irq_domain_map(struct irq_domain *d,
+ unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct spmi_pmic_arb_dev *pa = d->host_data;
+
+ dev_dbg(&pa->spmic->dev, "virq = %u, hwirq = %lu\n", virq, hwirq);
+
+ irq_set_chip_and_handler(virq, &pmic_arb_irqchip, handle_level_irq);
+ irq_set_chip_data(virq, d->host_data);
+ irq_set_noprobe(virq);
+ return 0;
+}
+
+static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
+ .map = qpnpint_irq_domain_map,
+ .xlate = qpnpint_irq_domain_dt_translate,
+};
+
+static int spmi_pmic_arb_probe(struct platform_device *pdev)
+{
+ struct spmi_pmic_arb_dev *pa;
+ struct spmi_controller *ctrl;
+ struct resource *res;
+ u32 channel, ee;
+ int err, i;
+
+ ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*pa));
+ if (!ctrl)
+ return -ENOMEM;
+
+ pa = spmi_controller_get_drvdata(ctrl);
+ pa->spmic = ctrl;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
+ pa->base = devm_ioremap_resource(&ctrl->dev, res);
+ if (IS_ERR(pa->base)) {
+ err = PTR_ERR(pa->base);
+ goto err_put_ctrl;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr");
+ pa->intr = devm_ioremap_resource(&ctrl->dev, res);
+ if (IS_ERR(pa->intr)) {
+ err = PTR_ERR(pa->intr);
+ goto err_put_ctrl;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cnfg");
+ pa->cnfg = devm_ioremap_resource(&ctrl->dev, res);
+ if (IS_ERR(pa->cnfg)) {
+ err = PTR_ERR(pa->cnfg);
+ goto err_put_ctrl;
+ }
+
+ pa->irq = platform_get_irq_byname(pdev, "periph_irq");
+ if (pa->irq < 0) {
+ err = pa->irq;
+ goto err_put_ctrl;
+ }
+
+ err = of_property_read_u32(pdev->dev.of_node, "qcom,channel", &channel);
+ if (err) {
+ dev_err(&pdev->dev, "channel unspecified.\n");
+ goto err_put_ctrl;
+ }
+
+ if (channel > 5) {
+ dev_err(&pdev->dev, "invalid channel (%u) specified.\n",
+ channel);
+ goto err_put_ctrl;
+ }
+
+ pa->channel = channel;
+
+ err = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &ee);
+ if (err) {
+ dev_err(&pdev->dev, "EE unspecified.\n");
+ goto err_put_ctrl;
+ }
+
+ if (ee > 5) {
+ dev_err(&pdev->dev, "invalid EE (%u) specified\n", ee);
+ err = -EINVAL;
+ goto err_put_ctrl;
+ }
+
+ pa->ee = ee;
+
+ for (i = 0; i < ARRAY_SIZE(pa->mapping_table); ++i)
+ pa->mapping_table[i] = readl_relaxed(
+ pa->cnfg + SPMI_MAPPING_TABLE_REG(i));
+
+ /* Initialize max_apid/min_apid to the opposite bounds, during
+ * the irq domain translation, we are sure to update these */
+ pa->max_apid = 0;
+ pa->min_apid = PMIC_ARB_MAX_PERIPHS - 1;
+
+ platform_set_drvdata(pdev, ctrl);
+ raw_spin_lock_init(&pa->lock);
+
+ ctrl->cmd = pmic_arb_cmd;
+ ctrl->read_cmd = pmic_arb_read_cmd;
+ ctrl->write_cmd = pmic_arb_write_cmd;
+
+ dev_dbg(&pdev->dev, "adding irq domain\n");
+ pa->domain = irq_domain_add_tree(pdev->dev.of_node,
+ &pmic_arb_irq_domain_ops, pa);
+ if (!pa->domain) {
+ dev_err(&pdev->dev, "unable to create irq_domain\n");
+ err = -ENOMEM;
+ goto err_put_ctrl;
+ }
+
+ irq_set_handler_data(pa->irq, pa);
+ irq_set_chained_handler(pa->irq, pmic_arb_chained_irq);
+
+ err = spmi_controller_add(ctrl);
+ if (err)
+ goto err_domain_remove;
+
+ dev_dbg(&ctrl->dev, "PMIC Arb Version 0x%x\n",
+ pmic_arb_base_read(pa, PMIC_ARB_VERSION));
+
+ return 0;
+
+err_domain_remove:
+ irq_set_chained_handler(pa->irq, NULL);
+ irq_set_handler_data(pa->irq, NULL);
+ irq_domain_remove(pa->domain);
+err_put_ctrl:
+ spmi_controller_put(ctrl);
+ return err;
+}
+
+static int spmi_pmic_arb_remove(struct platform_device *pdev)
+{
+ struct spmi_controller *ctrl = platform_get_drvdata(pdev);
+ struct spmi_pmic_arb_dev *pa = spmi_controller_get_drvdata(ctrl);
+ spmi_controller_remove(ctrl);
+ irq_set_chained_handler(pa->irq, NULL);
+ irq_set_handler_data(pa->irq, NULL);
+ irq_domain_remove(pa->domain);
+ spmi_controller_put(ctrl);
+ return 0;
+}
+
+static const struct of_device_id spmi_pmic_arb_match_table[] = {
+ { .compatible = "qcom,spmi-pmic-arb", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, spmi_pmic_arb_match_table);
+
+static struct platform_driver spmi_pmic_arb_driver = {
+ .probe = spmi_pmic_arb_probe,
+ .remove = spmi_pmic_arb_remove,
+ .driver = {
+ .name = "spmi_pmic_arb",
+ .owner = THIS_MODULE,
+ .of_match_table = spmi_pmic_arb_match_table,
+ },
+};
+module_platform_driver(spmi_pmic_arb_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:spmi_pmic_arb");
diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c
new file mode 100644
index 000000000000..3b5780710d50
--- /dev/null
+++ b/drivers/spmi/spmi.c
@@ -0,0 +1,574 @@
+/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/errno.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/spmi.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+
+#include <dt-bindings/spmi/spmi.h>
+
+static DEFINE_IDA(ctrl_ida);
+
+static void spmi_dev_release(struct device *dev)
+{
+ struct spmi_device *sdev = to_spmi_device(dev);
+ kfree(sdev);
+}
+
+static const struct device_type spmi_dev_type = {
+ .release = spmi_dev_release,
+};
+
+static void spmi_ctrl_release(struct device *dev)
+{
+ struct spmi_controller *ctrl = to_spmi_controller(dev);
+ ida_simple_remove(&ctrl_ida, ctrl->nr);
+ kfree(ctrl);
+}
+
+static const struct device_type spmi_ctrl_type = {
+ .release = spmi_ctrl_release,
+};
+
+static int spmi_device_match(struct device *dev, struct device_driver *drv)
+{
+ if (of_driver_match_device(dev, drv))
+ return 1;
+
+ if (drv->name)
+ return strncmp(dev_name(dev), drv->name,
+ SPMI_NAME_SIZE) == 0;
+
+ return 0;
+}
+
+/**
+ * spmi_device_add() - add a device previously constructed via spmi_device_alloc()
+ * @sdev: spmi_device to be added
+ */
+int spmi_device_add(struct spmi_device *sdev)
+{
+ struct spmi_controller *ctrl = sdev->ctrl;
+ int err;
+
+ dev_set_name(&sdev->dev, "%d-%02x", ctrl->nr, sdev->usid);
+
+ err = device_add(&sdev->dev);
+ if (err < 0) {
+ dev_err(&sdev->dev, "Can't add %s, status %d\n",
+ dev_name(&sdev->dev), err);
+ goto err_device_add;
+ }
+
+ dev_dbg(&sdev->dev, "device %s registered\n", dev_name(&sdev->dev));
+
+err_device_add:
+ return err;
+}
+EXPORT_SYMBOL_GPL(spmi_device_add);
+
+/**
+ * spmi_device_remove(): remove an SPMI device
+ * @sdev: spmi_device to be removed
+ */
+void spmi_device_remove(struct spmi_device *sdev)
+{
+ device_unregister(&sdev->dev);
+}
+EXPORT_SYMBOL_GPL(spmi_device_remove);
+
+static inline int
+spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid)
+{
+ if (!ctrl || !ctrl->cmd || ctrl->dev.type != &spmi_ctrl_type)
+ return -EINVAL;
+
+ return ctrl->cmd(ctrl, opcode, sid);
+}
+
+static inline int spmi_read_cmd(struct spmi_controller *ctrl, u8 opcode,
+ u8 sid, u16 addr, u8 *buf, size_t len)
+{
+ if (!ctrl || !ctrl->read_cmd || ctrl->dev.type != &spmi_ctrl_type)
+ return -EINVAL;
+
+ return ctrl->read_cmd(ctrl, opcode, sid, addr, buf, len);
+}
+
+static inline int spmi_write_cmd(struct spmi_controller *ctrl, u8 opcode,
+ u8 sid, u16 addr, const u8 *buf, size_t len)
+{
+ if (!ctrl || !ctrl->write_cmd || ctrl->dev.type != &spmi_ctrl_type)
+ return -EINVAL;
+
+ return ctrl->write_cmd(ctrl, opcode, sid, addr, buf, len);
+}
+
+/**
+ * spmi_register_read() - register read
+ * @sdev: SPMI device.
+ * @addr: slave register address (5-bit address).
+ * @buf: buffer to be populated with data from the Slave.
+ *
+ * Reads 1 byte of data from a Slave device register.
+ */
+int spmi_register_read(struct spmi_device *sdev, u8 addr, u8 *buf)
+{
+ /* 5-bit register address */
+ if (addr > 0x1F)
+ return -EINVAL;
+
+ return spmi_read_cmd(sdev->ctrl, SPMI_CMD_READ, sdev->usid, addr,
+ buf, 1);
+}
+EXPORT_SYMBOL_GPL(spmi_register_read);
+
+/**
+ * spmi_ext_register_read() - extended register read
+ * @sdev: SPMI device.
+ * @addr: slave register address (8-bit address).
+ * @buf: buffer to be populated with data from the Slave.
+ * @len: the request number of bytes to read (up to 16 bytes).
+ *
+ * Reads up to 16 bytes of data from the extended register space on a
+ * Slave device.
+ */
+int spmi_ext_register_read(struct spmi_device *sdev, u8 addr, u8 *buf,
+ size_t len)
+{
+ /* 8-bit register address, up to 16 bytes */
+ if (len == 0 || len > 16)
+ return -EINVAL;
+
+ return spmi_read_cmd(sdev->ctrl, SPMI_CMD_EXT_READ, sdev->usid, addr,
+ buf, len);
+}
+EXPORT_SYMBOL_GPL(spmi_ext_register_read);
+
+/**
+ * spmi_ext_register_readl() - extended register read long
+ * @sdev: SPMI device.
+ * @addr: slave register address (16-bit address).
+ * @buf: buffer to be populated with data from the Slave.
+ * @len: the request number of bytes to read (up to 8 bytes).
+ *
+ * Reads up to 8 bytes of data from the extended register space on a
+ * Slave device using 16-bit address.
+ */
+int spmi_ext_register_readl(struct spmi_device *sdev, u16 addr, u8 *buf,
+ size_t len)
+{
+ /* 16-bit register address, up to 8 bytes */
+ if (len == 0 || len > 8)
+ return -EINVAL;
+
+ return spmi_read_cmd(sdev->ctrl, SPMI_CMD_EXT_READL, sdev->usid, addr,
+ buf, len);
+}
+EXPORT_SYMBOL_GPL(spmi_ext_register_readl);
+
+/**
+ * spmi_register_write() - register write
+ * @sdev: SPMI device
+ * @addr: slave register address (5-bit address).
+ * @data: buffer containing the data to be transferred to the Slave.
+ *
+ * Writes 1 byte of data to a Slave device register.
+ */
+int spmi_register_write(struct spmi_device *sdev, u8 addr, u8 data)
+{
+ /* 5-bit register address */
+ if (addr > 0x1F)
+ return -EINVAL;
+
+ return spmi_write_cmd(sdev->ctrl, SPMI_CMD_WRITE, sdev->usid, addr,
+ &data, 1);
+}
+EXPORT_SYMBOL_GPL(spmi_register_write);
+
+/**
+ * spmi_register_zero_write() - register zero write
+ * @sdev: SPMI device.
+ * @data: the data to be written to register 0 (7-bits).
+ *
+ * Writes data to register 0 of the Slave device.
+ */
+int spmi_register_zero_write(struct spmi_device *sdev, u8 data)
+{
+ return spmi_write_cmd(sdev->ctrl, SPMI_CMD_ZERO_WRITE, sdev->usid, 0,
+ &data, 1);
+}
+EXPORT_SYMBOL_GPL(spmi_register_zero_write);
+
+/**
+ * spmi_ext_register_write() - extended register write
+ * @sdev: SPMI device.
+ * @addr: slave register address (8-bit address).
+ * @buf: buffer containing the data to be transferred to the Slave.
+ * @len: the request number of bytes to read (up to 16 bytes).
+ *
+ * Writes up to 16 bytes of data to the extended register space of a
+ * Slave device.
+ */
+int spmi_ext_register_write(struct spmi_device *sdev, u8 addr, const u8 *buf,
+ size_t len)
+{
+ /* 8-bit register address, up to 16 bytes */
+ if (len == 0 || len > 16)
+ return -EINVAL;
+
+ return spmi_write_cmd(sdev->ctrl, SPMI_CMD_EXT_WRITE, sdev->usid, addr,
+ buf, len);
+}
+EXPORT_SYMBOL_GPL(spmi_ext_register_write);
+
+/**
+ * spmi_ext_register_writel() - extended register write long
+ * @sdev: SPMI device.
+ * @addr: slave register address (16-bit address).
+ * @buf: buffer containing the data to be transferred to the Slave.
+ * @len: the request number of bytes to read (up to 8 bytes).
+ *
+ * Writes up to 8 bytes of data to the extended register space of a
+ * Slave device using 16-bit address.
+ */
+int spmi_ext_register_writel(struct spmi_device *sdev, u16 addr, const u8 *buf,
+ size_t len)
+{
+ /* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */
+ if (len == 0 || len > 8)
+ return -EINVAL;
+
+ return spmi_write_cmd(sdev->ctrl, SPMI_CMD_EXT_WRITEL, sdev->usid,
+ addr, buf, len);
+}
+EXPORT_SYMBOL_GPL(spmi_ext_register_writel);
+
+/**
+ * spmi_command_reset() - sends RESET command to the specified slave
+ * @sdev: SPMI device.
+ *
+ * The Reset command initializes the Slave and forces all registers to
+ * their reset values. The Slave shall enter the STARTUP state after
+ * receiving a Reset command.
+ */
+int spmi_command_reset(struct spmi_device *sdev)
+{
+ return spmi_cmd(sdev->ctrl, SPMI_CMD_RESET, sdev->usid);
+}
+EXPORT_SYMBOL_GPL(spmi_command_reset);
+
+/**
+ * spmi_command_sleep() - sends SLEEP command to the specified SPMI device
+ * @sdev: SPMI device.
+ *
+ * The Sleep command causes the Slave to enter the user defined SLEEP state.
+ */
+int spmi_command_sleep(struct spmi_device *sdev)
+{
+ return spmi_cmd(sdev->ctrl, SPMI_CMD_SLEEP, sdev->usid);
+}
+EXPORT_SYMBOL_GPL(spmi_command_sleep);
+
+/**
+ * spmi_command_wakeup() - sends WAKEUP command to the specified SPMI device
+ * @sdev: SPMI device.
+ *
+ * The Wakeup command causes the Slave to move from the SLEEP state to
+ * the ACTIVE state.
+ */
+int spmi_command_wakeup(struct spmi_device *sdev)
+{
+ return spmi_cmd(sdev->ctrl, SPMI_CMD_WAKEUP, sdev->usid);
+}
+EXPORT_SYMBOL_GPL(spmi_command_wakeup);
+
+/**
+ * spmi_command_shutdown() - sends SHUTDOWN command to the specified SPMI device
+ * @sdev: SPMI device.
+ *
+ * The Shutdown command causes the Slave to enter the SHUTDOWN state.
+ */
+int spmi_command_shutdown(struct spmi_device *sdev)
+{
+ return spmi_cmd(sdev->ctrl, SPMI_CMD_SHUTDOWN, sdev->usid);
+}
+EXPORT_SYMBOL_GPL(spmi_command_shutdown);
+
+static int spmi_drv_probe(struct device *dev)
+{
+ const struct spmi_driver *sdrv = to_spmi_driver(dev->driver);
+ struct spmi_device *sdev = to_spmi_device(dev);
+ int err;
+
+ /* Ensure the slave is in ACTIVE state */
+ err = spmi_command_wakeup(sdev);
+ if (err)
+ goto fail_wakeup;
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ err = sdrv->probe(sdev);
+ if (err)
+ goto fail_probe;
+
+ return 0;
+
+fail_probe:
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+fail_wakeup:
+ return err;
+}
+
+static int spmi_drv_remove(struct device *dev)
+{
+ const struct spmi_driver *sdrv = to_spmi_driver(dev->driver);
+
+ pm_runtime_get_sync(dev);
+ sdrv->remove(to_spmi_device(dev));
+ pm_runtime_put_noidle(dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ return 0;
+}
+
+static struct bus_type spmi_bus_type = {
+ .name = "spmi",
+ .match = spmi_device_match,
+ .probe = spmi_drv_probe,
+ .remove = spmi_drv_remove,
+};
+
+/**
+ * spmi_controller_alloc() - Allocate a new SPMI device
+ * @ctrl: associated controller
+ *
+ * Caller is responsible for either calling spmi_device_add() to add the
+ * newly allocated controller, or calling spmi_device_put() to discard it.
+ */
+struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl)
+{
+ struct spmi_device *sdev;
+
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return NULL;
+
+ sdev->ctrl = ctrl;
+ device_initialize(&sdev->dev);
+ sdev->dev.parent = &ctrl->dev;
+ sdev->dev.bus = &spmi_bus_type;
+ sdev->dev.type = &spmi_dev_type;
+ return sdev;
+}
+EXPORT_SYMBOL_GPL(spmi_device_alloc);
+
+/**
+ * spmi_controller_alloc() - Allocate a new SPMI controller
+ * @parent: parent device
+ * @size: size of private data
+ *
+ * Caller is responsible for either calling spmi_controller_add() to add the
+ * newly allocated controller, or calling spmi_controller_put() to discard it.
+ * The allocated private data region may be accessed via
+ * spmi_controller_get_drvdata()
+ */
+struct spmi_controller *spmi_controller_alloc(struct device *parent,
+ size_t size)
+{
+ struct spmi_controller *ctrl;
+ int id;
+
+ if (WARN_ON(!parent))
+ return NULL;
+
+ ctrl = kzalloc(sizeof(*ctrl) + size, GFP_KERNEL);
+ if (!ctrl)
+ return NULL;
+
+ device_initialize(&ctrl->dev);
+ ctrl->dev.type = &spmi_ctrl_type;
+ ctrl->dev.bus = &spmi_bus_type;
+ ctrl->dev.parent = parent;
+ ctrl->dev.of_node = parent->of_node;
+ spmi_controller_set_drvdata(ctrl, &ctrl[1]);
+
+ id = ida_simple_get(&ctrl_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ dev_err(parent,
+ "unable to allocate SPMI controller identifier.\n");
+ spmi_controller_put(ctrl);
+ return NULL;
+ }
+
+ ctrl->nr = id;
+ dev_set_name(&ctrl->dev, "spmi-%d", id);
+
+ dev_dbg(&ctrl->dev, "allocated controller 0x%p id %d\n", ctrl, id);
+ return ctrl;
+}
+EXPORT_SYMBOL_GPL(spmi_controller_alloc);
+
+static void of_spmi_register_devices(struct spmi_controller *ctrl)
+{
+ struct device_node *node;
+ int err;
+
+ if (!ctrl->dev.of_node)
+ return;
+
+ for_each_available_child_of_node(ctrl->dev.of_node, node) {
+ struct spmi_device *sdev;
+ u32 reg[2];
+
+ dev_dbg(&ctrl->dev, "adding child %s\n", node->full_name);
+
+ err = of_property_read_u32_array(node, "reg", reg, 2);
+ if (err) {
+ dev_err(&ctrl->dev,
+ "node %s err (%d) does not have 'reg' property\n",
+ node->full_name, err);
+ continue;
+ }
+
+ if (reg[1] != SPMI_USID) {
+ dev_err(&ctrl->dev,
+ "node %s contains unsupported 'reg' entry\n",
+ node->full_name);
+ continue;
+ }
+
+ if (reg[0] >= SPMI_MAX_SLAVE_ID) {
+ dev_err(&ctrl->dev,
+ "invalid usid on node %s\n",
+ node->full_name);
+ continue;
+ }
+
+ dev_dbg(&ctrl->dev, "read usid %02x\n", reg[0]);
+
+ sdev = spmi_device_alloc(ctrl);
+ if (!sdev)
+ continue;
+
+ sdev->dev.of_node = node;
+ sdev->usid = (u8) reg[0];
+
+ err = spmi_device_add(sdev);
+ if (err) {
+ dev_err(&sdev->dev,
+ "failure adding device. status %d\n", err);
+ spmi_device_put(sdev);
+ }
+ }
+}
+
+/**
+ * spmi_controller_add() - Add an SPMI controller
+ * @ctrl: controller to be registered.
+ *
+ * Register a controller previously allocated via spmi_controller_alloc() with
+ * the SPMI core.
+ */
+int spmi_controller_add(struct spmi_controller *ctrl)
+{
+ int ret;
+
+ /* Can't register until after driver model init */
+ if (WARN_ON(!spmi_bus_type.p))
+ return -EAGAIN;
+
+ ret = device_add(&ctrl->dev);
+ if (ret)
+ return ret;
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_spmi_register_devices(ctrl);
+
+ dev_dbg(&ctrl->dev, "spmi-%d registered: dev:%p\n",
+ ctrl->nr, &ctrl->dev);
+
+ return 0;
+};
+EXPORT_SYMBOL_GPL(spmi_controller_add);
+
+/* Remove a device associated with a controller */
+static int spmi_ctrl_remove_device(struct device *dev, void *data)
+{
+ struct spmi_device *spmidev = to_spmi_device(dev);
+ if (dev->type == &spmi_dev_type)
+ spmi_device_remove(spmidev);
+ return 0;
+}
+
+/**
+ * spmi_controller_remove(): remove an SPMI controller
+ * @ctrl: controller to remove
+ *
+ * Remove a SPMI controller. Caller is responsible for calling
+ * spmi_controller_put() to discard the allocated controller.
+ */
+void spmi_controller_remove(struct spmi_controller *ctrl)
+{
+ int dummy;
+
+ if (!ctrl)
+ return;
+
+ dummy = device_for_each_child(&ctrl->dev, NULL,
+ spmi_ctrl_remove_device);
+ device_del(&ctrl->dev);
+}
+EXPORT_SYMBOL_GPL(spmi_controller_remove);
+
+/**
+ * spmi_driver_register() - Register client driver with SPMI core
+ * @sdrv: client driver to be associated with client-device.
+ *
+ * This API will register the client driver with the SPMI framework.
+ * It is typically called from the driver's module-init function.
+ */
+int spmi_driver_register(struct spmi_driver *sdrv)
+{
+ sdrv->driver.bus = &spmi_bus_type;
+ return driver_register(&sdrv->driver);
+}
+EXPORT_SYMBOL_GPL(spmi_driver_register);
+
+static void __exit spmi_exit(void)
+{
+ bus_unregister(&spmi_bus_type);
+}
+module_exit(spmi_exit);
+
+static int __init spmi_init(void)
+{
+ return bus_register(&spmi_bus_type);
+}
+postcore_initcall(spmi_init);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SPMI module");
+MODULE_ALIAS("platform:spmi");
diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c
index b269dbd47fc4..b1d7ee6e40b7 100644
--- a/drivers/usb/dwc3/dwc3-omap.c
+++ b/drivers/usb/dwc3/dwc3-omap.c
@@ -29,7 +29,6 @@
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/extcon.h>
-#include <linux/extcon/of_extcon.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/otg.h>
@@ -522,7 +521,7 @@ static int dwc3_omap_probe(struct platform_device *pdev)
dwc3_omap_enable_irqs(omap);
if (of_property_read_bool(node, "extcon")) {
- edev = of_extcon_get_extcon_dev(dev, 0);
+ edev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(edev)) {
dev_vdbg(dev, "couldn't get extcon device\n");
ret = -EPROBE_DEFER;
diff --git a/drivers/video/hyperv_fb.c b/drivers/video/hyperv_fb.c
index 130708f96430..e23392ec5af3 100644
--- a/drivers/video/hyperv_fb.c
+++ b/drivers/video/hyperv_fb.c
@@ -42,6 +42,7 @@
#include <linux/completion.h>
#include <linux/fb.h>
#include <linux/pci.h>
+#include <linux/efi.h>
#include <linux/hyperv.h>
@@ -212,6 +213,7 @@ struct synthvid_msg {
struct hvfb_par {
struct fb_info *info;
+ struct resource mem;
bool fb_ready; /* fb device is ready */
struct completion wait;
u32 synthvid_version;
@@ -460,13 +462,13 @@ static int synthvid_connect_vsp(struct hv_device *hdev)
goto error;
}
- if (par->synthvid_version == SYNTHVID_VERSION_WIN7) {
+ if (par->synthvid_version == SYNTHVID_VERSION_WIN7)
screen_depth = SYNTHVID_DEPTH_WIN7;
- screen_fb_size = SYNTHVID_FB_SIZE_WIN7;
- } else {
+ else
screen_depth = SYNTHVID_DEPTH_WIN8;
- screen_fb_size = SYNTHVID_FB_SIZE_WIN8;
- }
+
+ screen_fb_size = hdev->channel->offermsg.offer.
+ mmio_megabytes * 1024 * 1024;
return 0;
@@ -627,26 +629,46 @@ static void hvfb_get_option(struct fb_info *info)
/* Get framebuffer memory from Hyper-V video pci space */
static int hvfb_getmem(struct fb_info *info)
{
- struct pci_dev *pdev;
- ulong fb_phys;
+ struct hvfb_par *par = info->par;
+ struct pci_dev *pdev = NULL;
void __iomem *fb_virt;
+ int gen2vm = efi_enabled(EFI_BOOT);
+ int ret;
- pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
+ par->mem.name = KBUILD_MODNAME;
+ par->mem.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (gen2vm) {
+ ret = allocate_resource(&hyperv_mmio, &par->mem,
+ screen_fb_size,
+ 0, -1,
+ screen_fb_size,
+ NULL, NULL);
+ if (ret != 0) {
+ pr_err("Unable to allocate framebuffer memory\n");
+ return -ENODEV;
+ }
+ } else {
+ pdev = pci_get_device(PCI_VENDOR_ID_MICROSOFT,
PCI_DEVICE_ID_HYPERV_VIDEO, NULL);
- if (!pdev) {
- pr_err("Unable to find PCI Hyper-V video\n");
- return -ENODEV;
- }
+ if (!pdev) {
+ pr_err("Unable to find PCI Hyper-V video\n");
+ return -ENODEV;
+ }
- if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
- pci_resource_len(pdev, 0) < screen_fb_size)
- goto err1;
+ if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
+ pci_resource_len(pdev, 0) < screen_fb_size)
+ goto err1;
- fb_phys = pci_resource_end(pdev, 0) - screen_fb_size + 1;
- if (!request_mem_region(fb_phys, screen_fb_size, KBUILD_MODNAME))
- goto err1;
+ par->mem.end = pci_resource_end(pdev, 0);
+ par->mem.start = par->mem.end - screen_fb_size + 1;
+ ret = request_resource(&pdev->resource[0], &par->mem);
+ if (ret != 0) {
+ pr_err("Unable to request framebuffer memory\n");
+ goto err1;
+ }
+ }
- fb_virt = ioremap(fb_phys, screen_fb_size);
+ fb_virt = ioremap(par->mem.start, screen_fb_size);
if (!fb_virt)
goto err2;
@@ -654,30 +676,44 @@ static int hvfb_getmem(struct fb_info *info)
if (!info->apertures)
goto err3;
- info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
- info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
- info->fix.smem_start = fb_phys;
+ if (gen2vm) {
+ info->apertures->ranges[0].base = screen_info.lfb_base;
+ info->apertures->ranges[0].size = screen_info.lfb_size;
+ remove_conflicting_framebuffers(info->apertures,
+ KBUILD_MODNAME, false);
+ } else {
+ info->apertures->ranges[0].base = pci_resource_start(pdev, 0);
+ info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
+ }
+
+ info->fix.smem_start = par->mem.start;
info->fix.smem_len = screen_fb_size;
info->screen_base = fb_virt;
info->screen_size = screen_fb_size;
- pci_dev_put(pdev);
+ if (!gen2vm)
+ pci_dev_put(pdev);
+
return 0;
err3:
iounmap(fb_virt);
err2:
- release_mem_region(fb_phys, screen_fb_size);
+ release_resource(&par->mem);
err1:
- pci_dev_put(pdev);
+ if (!gen2vm)
+ pci_dev_put(pdev);
+
return -ENOMEM;
}
/* Release the framebuffer */
static void hvfb_putmem(struct fb_info *info)
{
+ struct hvfb_par *par = info->par;
+
iounmap(info->screen_base);
- release_mem_region(info->fix.smem_start, screen_fb_size);
+ release_resource(&par->mem);
}
diff --git a/drivers/video/uvesafb.c b/drivers/video/uvesafb.c
index 256fba7f4641..1f38445014c1 100644
--- a/drivers/video/uvesafb.c
+++ b/drivers/video/uvesafb.c
@@ -190,7 +190,7 @@ static int uvesafb_exec(struct uvesafb_ktask *task)
uvfb_tasks[seq] = task;
mutex_unlock(&uvfb_lock);
- err = cn_netlink_send(m, 0, GFP_KERNEL);
+ err = cn_netlink_send(m, 0, 0, GFP_KERNEL);
if (err == -ESRCH) {
/*
* Try to start the userspace helper if sending
@@ -204,7 +204,7 @@ static int uvesafb_exec(struct uvesafb_ktask *task)
"helper is installed and executable\n");
} else {
v86d_started = 1;
- err = cn_netlink_send(m, 0, gfp_any());
+ err = cn_netlink_send(m, 0, 0, gfp_any());
if (err == -ENOBUFS)
err = 0;
}
diff --git a/drivers/vme/bridges/vme_ca91cx42.c b/drivers/vme/bridges/vme_ca91cx42.c
index 1b5d48c578e1..bfb2d3f06738 100644
--- a/drivers/vme/bridges/vme_ca91cx42.c
+++ b/drivers/vme/bridges/vme_ca91cx42.c
@@ -869,14 +869,13 @@ static ssize_t ca91cx42_master_read(struct vme_master_resource *image,
spin_lock(&image->lock);
- /* The following code handles VME address alignment problem
- * in order to assure the maximal data width cycle.
- * We cannot use memcpy_xxx directly here because it
- * may cut data transfer in 8-bits cycles, thus making
- * D16 cycle impossible.
- * From the other hand, the bridge itself assures that
- * maximal configured data cycle is used and splits it
- * automatically for non-aligned addresses.
+ /* The following code handles VME address alignment. We cannot use
+ * memcpy_xxx here because it may cut data transfers in to 8-bit
+ * cycles when D16 or D32 cycles are required on the VME bus.
+ * On the other hand, the bridge itself assures that the maximum data
+ * cycle configured for the transfer is used and splits it
+ * automatically for non-aligned addresses, so we don't want the
+ * overhead of needlessly forcing small transfers for the entire cycle.
*/
if ((uintptr_t)addr & 0x1) {
*(u8 *)buf = ioread8(addr);
@@ -896,9 +895,9 @@ static ssize_t ca91cx42_master_read(struct vme_master_resource *image,
}
count32 = (count - done) & ~0x3;
- if (count32 > 0) {
- memcpy_fromio(buf + done, addr + done, (unsigned int)count);
- done += count32;
+ while (done < count32) {
+ *(u32 *)(buf + done) = ioread32(addr + done);
+ done += 4;
}
if ((count - done) & 0x2) {
@@ -930,7 +929,7 @@ static ssize_t ca91cx42_master_write(struct vme_master_resource *image,
spin_lock(&image->lock);
/* Here we apply for the same strategy we do in master_read
- * function in order to assure D16 cycle when required.
+ * function in order to assure the correct cycles.
*/
if ((uintptr_t)addr & 0x1) {
iowrite8(*(u8 *)buf, addr);
@@ -950,9 +949,9 @@ static ssize_t ca91cx42_master_write(struct vme_master_resource *image,
}
count32 = (count - done) & ~0x3;
- if (count32 > 0) {
- memcpy_toio(addr + done, buf + done, count32);
- done += count32;
+ while (done < count32) {
+ iowrite32(*(u32 *)(buf + done), addr + done);
+ done += 4;
}
if ((count - done) & 0x2) {
diff --git a/drivers/vme/bridges/vme_tsi148.c b/drivers/vme/bridges/vme_tsi148.c
index 9911cd5fddb5..06990c6a1a69 100644
--- a/drivers/vme/bridges/vme_tsi148.c
+++ b/drivers/vme/bridges/vme_tsi148.c
@@ -1276,8 +1276,8 @@ static ssize_t tsi148_master_read(struct vme_master_resource *image, void *buf,
spin_lock(&image->lock);
/* The following code handles VME address alignment. We cannot use
- * memcpy_xxx directly here because it may cut small data transfers in
- * to 8-bit cycles, thus making D16 cycle impossible.
+ * memcpy_xxx here because it may cut data transfers in to 8-bit
+ * cycles when D16 or D32 cycles are required on the VME bus.
* On the other hand, the bridge itself assures that the maximum data
* cycle configured for the transfer is used and splits it
* automatically for non-aligned addresses, so we don't want the
@@ -1301,9 +1301,9 @@ static ssize_t tsi148_master_read(struct vme_master_resource *image, void *buf,
}
count32 = (count - done) & ~0x3;
- if (count32 > 0) {
- memcpy_fromio(buf + done, addr + done, count32);
- done += count32;
+ while (done < count32) {
+ *(u32 *)(buf + done) = ioread32(addr + done);
+ done += 4;
}
if ((count - done) & 0x2) {
@@ -1363,7 +1363,7 @@ static ssize_t tsi148_master_write(struct vme_master_resource *image, void *buf,
spin_lock(&image->lock);
/* Here we apply for the same strategy we do in master_read
- * function in order to assure D16 cycle when required.
+ * function in order to assure the correct cycles.
*/
if ((uintptr_t)addr & 0x1) {
iowrite8(*(u8 *)buf, addr);
@@ -1383,9 +1383,9 @@ static ssize_t tsi148_master_write(struct vme_master_resource *image, void *buf,
}
count32 = (count - done) & ~0x3;
- if (count32 > 0) {
- memcpy_toio(addr + done, buf + done, count32);
- done += count32;
+ while (done < count32) {
+ iowrite32(*(u32 *)(buf + done), addr + done);
+ done += 4;
}
if ((count - done) & 0x2) {
diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
index efc7f075fcbe..1708b2300c7a 100644
--- a/drivers/w1/masters/Kconfig
+++ b/drivers/w1/masters/Kconfig
@@ -36,13 +36,12 @@ config W1_MASTER_DS2482
config W1_MASTER_MXC
tristate "Freescale MXC 1-wire busmaster"
- depends on W1 && ARCH_MXC
+ depends on ARCH_MXC || COMPILE_TEST
help
Say Y here to enable MXC 1-wire host
config W1_MASTER_DS1WM
tristate "Maxim DS1WM 1-wire busmaster"
- depends on W1
help
Say Y here to enable the DS1WM 1-wire driver, such as that
in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c
index 4f7e1d770f81..7404ad3062b7 100644
--- a/drivers/w1/masters/ds2490.c
+++ b/drivers/w1/masters/ds2490.c
@@ -1,5 +1,5 @@
/*
- * dscore.c
+ * ds2490.c USB to one wire bridge
*
* Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
*
@@ -28,6 +28,10 @@
#include "../w1_int.h"
#include "../w1.h"
+/* USB Standard */
+/* USB Control request vendor type */
+#define VENDOR 0x40
+
/* COMMAND TYPE CODES */
#define CONTROL_CMD 0x00
#define COMM_CMD 0x01
@@ -107,6 +111,8 @@
#define ST_HALT 0x10 /* DS2490 is currently halted */
#define ST_IDLE 0x20 /* DS2490 is currently idle */
#define ST_EPOF 0x80
+/* Status transfer size, 16 bytes status, 16 byte result flags */
+#define ST_SIZE 0x20
/* Result Register flags */
#define RR_DETECT 0xA5 /* New device detected */
@@ -198,7 +204,7 @@ static int ds_send_control_cmd(struct ds_device *dev, u16 value, u16 index)
int err;
err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]),
- CONTROL_CMD, 0x40, value, index, NULL, 0, 1000);
+ CONTROL_CMD, VENDOR, value, index, NULL, 0, 1000);
if (err < 0) {
printk(KERN_ERR "Failed to send command control message %x.%x: err=%d.\n",
value, index, err);
@@ -213,7 +219,7 @@ static int ds_send_control_mode(struct ds_device *dev, u16 value, u16 index)
int err;
err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]),
- MODE_CMD, 0x40, value, index, NULL, 0, 1000);
+ MODE_CMD, VENDOR, value, index, NULL, 0, 1000);
if (err < 0) {
printk(KERN_ERR "Failed to send mode control message %x.%x: err=%d.\n",
value, index, err);
@@ -228,7 +234,7 @@ static int ds_send_control(struct ds_device *dev, u16 value, u16 index)
int err;
err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]),
- COMM_CMD, 0x40, value, index, NULL, 0, 1000);
+ COMM_CMD, VENDOR, value, index, NULL, 0, 1000);
if (err < 0) {
printk(KERN_ERR "Failed to send control message %x.%x: err=%d.\n",
value, index, err);
@@ -246,7 +252,8 @@ static int ds_recv_status_nodump(struct ds_device *dev, struct ds_status *st,
memset(st, 0, sizeof(*st));
count = 0;
- err = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_STATUS]), buf, size, &count, 100);
+ err = usb_interrupt_msg(dev->udev, usb_rcvintpipe(dev->udev,
+ dev->ep[EP_STATUS]), buf, size, &count, 100);
if (err < 0) {
printk(KERN_ERR "Failed to read 1-wire data from 0x%x: err=%d.\n", dev->ep[EP_STATUS], err);
return err;
@@ -353,7 +360,7 @@ static int ds_recv_data(struct ds_device *dev, unsigned char *buf, int size)
err = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_DATA_IN]),
buf, size, &count, 1000);
if (err < 0) {
- u8 buf[0x20];
+ u8 buf[ST_SIZE];
int count;
printk(KERN_INFO "Clearing ep0x%x.\n", dev->ep[EP_DATA_IN]);
@@ -398,7 +405,7 @@ int ds_stop_pulse(struct ds_device *dev, int limit)
{
struct ds_status st;
int count = 0, err = 0;
- u8 buf[0x20];
+ u8 buf[ST_SIZE];
do {
err = ds_send_control(dev, CTL_HALT_EXE_IDLE, 0);
@@ -450,10 +457,11 @@ int ds_detect(struct ds_device *dev, struct ds_status *st)
static int ds_wait_status(struct ds_device *dev, struct ds_status *st)
{
- u8 buf[0x20];
+ u8 buf[ST_SIZE];
int err, count = 0;
do {
+ st->status = 0;
err = ds_recv_status_nodump(dev, st, buf, sizeof(buf));
#if 0
if (err >= 0) {
@@ -464,7 +472,7 @@ static int ds_wait_status(struct ds_device *dev, struct ds_status *st)
printk("\n");
}
#endif
- } while (!(buf[0x08] & ST_IDLE) && !(err < 0) && ++count < 100);
+ } while (!(st->status & ST_IDLE) && !(err < 0) && ++count < 100);
if (err >= 16 && st->status & ST_EPOF) {
printk(KERN_INFO "Resetting device after ST_EPOF.\n");
@@ -690,37 +698,106 @@ static int ds_write_block(struct ds_device *dev, u8 *buf, int len)
return !(err == len);
}
-#if 0
-
-static int ds_search(struct ds_device *dev, u64 init, u64 *buf, u8 id_number, int conditional_search)
+static void ds9490r_search(void *data, struct w1_master *master,
+ u8 search_type, w1_slave_found_callback callback)
{
+ /* When starting with an existing id, the first id returned will
+ * be that device (if it is still on the bus most likely).
+ *
+ * If the number of devices found is less than or equal to the
+ * search_limit, that number of IDs will be returned. If there are
+ * more, search_limit IDs will be returned followed by a non-zero
+ * discrepency value.
+ */
+ struct ds_device *dev = data;
int err;
u16 value, index;
struct ds_status st;
+ u8 st_buf[ST_SIZE];
+ int search_limit;
+ int found = 0;
+ int i;
- memset(buf, 0, sizeof(buf));
+ /* DS18b20 spec, 13.16 ms per device, 75 per second, sleep for
+ * discovering 8 devices (1 bulk transfer and 1/2 FIFO size) at a time.
+ */
+ const unsigned long jtime = msecs_to_jiffies(1000*8/75);
+ /* FIFO 128 bytes, bulk packet size 64, read a multiple of the
+ * packet size.
+ */
+ u64 buf[2*64/8];
- err = ds_send_data(ds_dev, (unsigned char *)&init, 8);
- if (err)
- return err;
+ mutex_lock(&master->bus_mutex);
- ds_wait_status(ds_dev, &st);
+ /* address to start searching at */
+ if (ds_send_data(dev, (u8 *)&master->search_id, 8) < 0)
+ goto search_out;
+ master->search_id = 0;
- value = COMM_SEARCH_ACCESS | COMM_IM | COMM_SM | COMM_F | COMM_RTS;
- index = (conditional_search ? 0xEC : 0xF0) | (id_number << 8);
- err = ds_send_control(ds_dev, value, index);
- if (err)
- return err;
+ value = COMM_SEARCH_ACCESS | COMM_IM | COMM_RST | COMM_SM | COMM_F |
+ COMM_RTS;
+ search_limit = master->max_slave_count;
+ if (search_limit > 255)
+ search_limit = 0;
+ index = search_type | (search_limit << 8);
+ if (ds_send_control(dev, value, index) < 0)
+ goto search_out;
- ds_wait_status(ds_dev, &st);
+ do {
+ schedule_timeout(jtime);
- err = ds_recv_data(ds_dev, (unsigned char *)buf, 8*id_number);
- if (err < 0)
- return err;
+ if (ds_recv_status_nodump(dev, &st, st_buf, sizeof(st_buf)) <
+ sizeof(st)) {
+ break;
+ }
- return err/8;
+ if (st.data_in_buffer_status) {
+ /* Bulk in can receive partial ids, but when it does
+ * they fail crc and will be discarded anyway.
+ * That has only been seen when status in buffer
+ * is 0 and bulk is read anyway, so don't read
+ * bulk without first checking if status says there
+ * is data to read.
+ */
+ err = ds_recv_data(dev, (u8 *)buf, sizeof(buf));
+ if (err < 0)
+ break;
+ for (i = 0; i < err/8; ++i) {
+ ++found;
+ if (found <= search_limit)
+ callback(master, buf[i]);
+ /* can't know if there will be a discrepancy
+ * value after until the next id */
+ if (found == search_limit)
+ master->search_id = buf[i];
+ }
+ }
+
+ if (test_bit(W1_ABORT_SEARCH, &master->flags))
+ break;
+ } while (!(st.status & (ST_IDLE | ST_HALT)));
+
+ /* only continue the search if some weren't found */
+ if (found <= search_limit) {
+ master->search_id = 0;
+ } else if (!test_bit(W1_WARN_MAX_COUNT, &master->flags)) {
+ /* Only max_slave_count will be scanned in a search,
+ * but it will start where it left off next search
+ * until all ids are identified and then it will start
+ * over. A continued search will report the previous
+ * last id as the first id (provided it is still on the
+ * bus).
+ */
+ dev_info(&dev->udev->dev, "%s: max_slave_count %d reached, "
+ "will continue next search.\n", __func__,
+ master->max_slave_count);
+ set_bit(W1_WARN_MAX_COUNT, &master->flags);
+ }
+search_out:
+ mutex_unlock(&master->bus_mutex);
}
+#if 0
static int ds_match_access(struct ds_device *dev, u64 init)
{
int err;
@@ -894,6 +971,7 @@ static int ds_w1_init(struct ds_device *dev)
dev->master.write_block = &ds9490r_write_block;
dev->master.reset_bus = &ds9490r_reset;
dev->master.set_pullup = &ds9490r_set_pullup;
+ dev->master.search = &ds9490r_search;
return w1_add_master_device(&dev->master);
}
@@ -910,15 +988,13 @@ static int ds_probe(struct usb_interface *intf,
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *iface_desc;
struct ds_device *dev;
- int i, err;
+ int i, err, alt;
- dev = kmalloc(sizeof(struct ds_device), GFP_KERNEL);
+ dev = kzalloc(sizeof(struct ds_device), GFP_KERNEL);
if (!dev) {
printk(KERN_INFO "Failed to allocate new DS9490R structure.\n");
return -ENOMEM;
}
- dev->spu_sleep = 0;
- dev->spu_bit = 0;
dev->udev = usb_get_dev(udev);
if (!dev->udev) {
err = -ENOMEM;
@@ -928,20 +1004,25 @@ static int ds_probe(struct usb_interface *intf,
usb_set_intfdata(intf, dev);
- err = usb_set_interface(dev->udev, intf->altsetting[0].desc.bInterfaceNumber, 3);
+ err = usb_reset_configuration(dev->udev);
if (err) {
- printk(KERN_ERR "Failed to set alternative setting 3 for %d interface: err=%d.\n",
- intf->altsetting[0].desc.bInterfaceNumber, err);
+ dev_err(&dev->udev->dev,
+ "Failed to reset configuration: err=%d.\n", err);
goto err_out_clear;
}
- err = usb_reset_configuration(dev->udev);
+ /* alternative 3, 1ms interrupt (greatly speeds search), 64 byte bulk */
+ alt = 3;
+ err = usb_set_interface(dev->udev,
+ intf->altsetting[alt].desc.bInterfaceNumber, alt);
if (err) {
- printk(KERN_ERR "Failed to reset configuration: err=%d.\n", err);
+ dev_err(&dev->udev->dev, "Failed to set alternative setting %d "
+ "for %d interface: err=%d.\n", alt,
+ intf->altsetting[alt].desc.bInterfaceNumber, err);
goto err_out_clear;
}
- iface_desc = &intf->altsetting[0];
+ iface_desc = &intf->altsetting[alt];
if (iface_desc->desc.bNumEndpoints != NUM_EP-1) {
printk(KERN_INFO "Num endpoints=%d. It is not DS9490R.\n", iface_desc->desc.bNumEndpoints);
err = -EINVAL;
diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c
index 1e5d94c5afc9..67b067a3e2ab 100644
--- a/drivers/w1/masters/mxc_w1.c
+++ b/drivers/w1/masters/mxc_w1.c
@@ -10,24 +10,16 @@
* 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
*/
-#include <linux/module.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
#include <linux/clk.h>
-#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
#include "../w1.h"
#include "../w1_int.h"
-#include "../w1_log.h"
/* According to the mx27 Datasheet the reset procedure should take up to about
* 1350us. We set the timeout to 500*100us = 50ms for sure */
@@ -36,13 +28,13 @@
/*
* MXC W1 Register offsets
*/
-#define MXC_W1_CONTROL 0x00
-#define MXC_W1_TIME_DIVIDER 0x02
-#define MXC_W1_RESET 0x04
-#define MXC_W1_COMMAND 0x06
-#define MXC_W1_TXRX 0x08
-#define MXC_W1_INTERRUPT 0x0A
-#define MXC_W1_INTERRUPT_EN 0x0C
+#define MXC_W1_CONTROL 0x00
+# define MXC_W1_CONTROL_RDST BIT(3)
+# define MXC_W1_CONTROL_WR(x) BIT(5 - (x))
+# define MXC_W1_CONTROL_PST BIT(6)
+# define MXC_W1_CONTROL_RPP BIT(7)
+#define MXC_W1_TIME_DIVIDER 0x02
+#define MXC_W1_RESET 0x04
struct mxc_w1_device {
void __iomem *regs;
@@ -61,12 +53,12 @@ static u8 mxc_w1_ds2_reset_bus(void *data)
unsigned int timeout_cnt = 0;
struct mxc_w1_device *dev = data;
- __raw_writeb(0x80, (dev->regs + MXC_W1_CONTROL));
+ writeb(MXC_W1_CONTROL_RPP, (dev->regs + MXC_W1_CONTROL));
while (1) {
- reg_val = __raw_readb(dev->regs + MXC_W1_CONTROL);
+ reg_val = readb(dev->regs + MXC_W1_CONTROL);
- if (((reg_val >> 7) & 0x1) == 0 ||
+ if (!(reg_val & MXC_W1_CONTROL_RPP) ||
timeout_cnt > MXC_W1_RESET_TIMEOUT)
break;
else
@@ -74,7 +66,7 @@ static u8 mxc_w1_ds2_reset_bus(void *data)
udelay(100);
}
- return (reg_val >> 7) & 0x1;
+ return !!(reg_val & MXC_W1_CONTROL_PST);
}
/*
@@ -90,16 +82,16 @@ static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit)
* datasheet.
*/
- __raw_writeb((1 << (5 - bit)), ctrl_addr);
+ writeb(MXC_W1_CONTROL_WR(bit), ctrl_addr);
while (timeout_cnt--) {
- if (!((__raw_readb(ctrl_addr) >> (5 - bit)) & 0x1))
+ if (!(readb(ctrl_addr) & MXC_W1_CONTROL_WR(bit)))
break;
udelay(1);
}
- return ((__raw_readb(ctrl_addr)) >> 3) & 0x1;
+ return !!(readb(ctrl_addr) & MXC_W1_CONTROL_RDST);
}
static int mxc_w1_probe(struct platform_device *pdev)
@@ -139,7 +131,7 @@ static int mxc_w1_probe(struct platform_device *pdev)
if (err)
return err;
- __raw_writeb(clkdiv - 1, mdev->regs + MXC_W1_TIME_DIVIDER);
+ writeb(clkdiv - 1, mdev->regs + MXC_W1_TIME_DIVIDER);
mdev->bus_master.data = mdev;
mdev->bus_master.reset_bus = mxc_w1_ds2_reset_bus;
@@ -177,6 +169,7 @@ MODULE_DEVICE_TABLE(of, mxc_w1_dt_ids);
static struct platform_driver mxc_w1_driver = {
.driver = {
.name = "mxc_w1",
+ .owner = THIS_MODULE,
.of_match_table = mxc_w1_dt_ids,
},
.probe = mxc_w1_probe,
diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c
index 9709b8b484ba..1d111e56c8c8 100644
--- a/drivers/w1/masters/w1-gpio.c
+++ b/drivers/w1/masters/w1-gpio.c
@@ -89,11 +89,22 @@ static int w1_gpio_probe_dt(struct platform_device *pdev)
pdata->is_open_drain = 1;
gpio = of_get_gpio(np, 0);
- if (gpio < 0)
+ if (gpio < 0) {
+ if (gpio != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "Failed to parse gpio property for data pin (%d)\n",
+ gpio);
+
return gpio;
+ }
pdata->pin = gpio;
- pdata->ext_pullup_enable_pin = of_get_gpio(np, 1);
+ gpio = of_get_gpio(np, 1);
+ if (gpio == -EPROBE_DEFER)
+ return gpio;
+ /* ignore other errors as the pullup gpio is optional */
+ pdata->ext_pullup_enable_pin = gpio;
+
pdev->dev.platform_data = pdata;
return 0;
@@ -107,10 +118,8 @@ static int w1_gpio_probe(struct platform_device *pdev)
if (of_have_populated_dt()) {
err = w1_gpio_probe_dt(pdev);
- if (err < 0) {
- dev_err(&pdev->dev, "Failed to parse DT\n");
+ if (err < 0)
return err;
- }
}
pdata = dev_get_platdata(&pdev->dev);
diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig
index 5e6a3c9e510b..1cdce80b6abf 100644
--- a/drivers/w1/slaves/Kconfig
+++ b/drivers/w1/slaves/Kconfig
@@ -72,7 +72,6 @@ config W1_SLAVE_DS2433_CRC
config W1_SLAVE_DS2760
tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)"
- depends on W1
help
If you enable this you will have the DS2760 battery monitor
chip support.
@@ -85,7 +84,6 @@ config W1_SLAVE_DS2760
config W1_SLAVE_DS2780
tristate "Dallas 2780 battery monitor chip"
- depends on W1
help
If you enable this you will have the DS2780 battery monitor
chip support.
@@ -98,7 +96,6 @@ config W1_SLAVE_DS2780
config W1_SLAVE_DS2781
tristate "Dallas 2781 battery monitor chip"
- depends on W1
help
If you enable this you will have the DS2781 battery monitor
chip support.
@@ -111,7 +108,6 @@ config W1_SLAVE_DS2781
config W1_SLAVE_DS28E04
tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)"
- depends on W1
select CRC16
help
If you enable this you will have the DS28E04-100
@@ -124,7 +120,6 @@ config W1_SLAVE_DS28E04
config W1_SLAVE_BQ27000
tristate "BQ27000 slave support"
- depends on W1
help
Say Y here if you want to use a hdq
bq27000 slave support.
diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c
index 8b5ff33f72cf..1f11a20a8ab9 100644
--- a/drivers/w1/slaves/w1_therm.c
+++ b/drivers/w1/slaves/w1_therm.c
@@ -27,6 +27,7 @@
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/types.h>
+#include <linux/slab.h>
#include <linux/delay.h>
#include "../w1.h"
@@ -58,6 +59,19 @@ MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS28EA00));
static int w1_strong_pullup = 1;
module_param_named(strong_pullup, w1_strong_pullup, int, 0);
+static int w1_therm_add_slave(struct w1_slave *sl)
+{
+ sl->family_data = kzalloc(9, GFP_KERNEL);
+ if (!sl->family_data)
+ return -ENOMEM;
+ return 0;
+}
+
+static void w1_therm_remove_slave(struct w1_slave *sl)
+{
+ kfree(sl->family_data);
+ sl->family_data = NULL;
+}
static ssize_t w1_slave_show(struct device *device,
struct device_attribute *attr, char *buf);
@@ -71,6 +85,8 @@ static struct attribute *w1_therm_attrs[] = {
ATTRIBUTE_GROUPS(w1_therm);
static struct w1_family_ops w1_therm_fops = {
+ .add_slave = w1_therm_add_slave,
+ .remove_slave = w1_therm_remove_slave,
.groups = w1_therm_groups,
};
@@ -253,12 +269,13 @@ static ssize_t w1_slave_show(struct device *device,
c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n",
crc, (verdict) ? "YES" : "NO");
if (verdict)
- memcpy(sl->rom, rom, sizeof(sl->rom));
+ memcpy(sl->family_data, rom, sizeof(rom));
else
dev_warn(device, "Read failed CRC check\n");
for (i = 0; i < 9; ++i)
- c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", sl->rom[i]);
+ c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ",
+ ((u8 *)sl->family_data)[i]);
c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n",
w1_convert_temp(rom, sl->family->fid));
diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c
index 66efa96c4603..b96f61b15dc6 100644
--- a/drivers/w1/w1.c
+++ b/drivers/w1/w1.c
@@ -46,18 +46,29 @@ MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol.");
static int w1_timeout = 10;
-int w1_max_slave_count = 10;
+int w1_max_slave_count = 64;
int w1_max_slave_ttl = 10;
module_param_named(timeout, w1_timeout, int, 0);
+MODULE_PARM_DESC(timeout, "time in seconds between automatic slave searches");
+/* A search stops when w1_max_slave_count devices have been found in that
+ * search. The next search will start over and detect the same set of devices
+ * on a static 1-wire bus. Memory is not allocated based on this number, just
+ * on the number of devices known to the kernel. Having a high number does not
+ * consume additional resources. As a special case, if there is only one
+ * device on the network and w1_max_slave_count is set to 1, the device id can
+ * be read directly skipping the normal slower search process.
+ */
module_param_named(max_slave_count, w1_max_slave_count, int, 0);
+MODULE_PARM_DESC(max_slave_count,
+ "maximum number of slaves detected in a search");
module_param_named(slave_ttl, w1_max_slave_ttl, int, 0);
+MODULE_PARM_DESC(slave_ttl,
+ "Number of searches not seeing a slave before it will be removed");
DEFINE_MUTEX(w1_mlock);
LIST_HEAD(w1_masters);
-static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn);
-
static int w1_master_match(struct device *dev, struct device_driver *drv)
{
return 1;
@@ -81,19 +92,10 @@ static void w1_slave_release(struct device *dev)
{
struct w1_slave *sl = dev_to_w1_slave(dev);
- dev_dbg(dev, "%s: Releasing %s.\n", __func__, sl->name);
-
- while (atomic_read(&sl->refcnt)) {
- dev_dbg(dev, "Waiting for %s to become free: refcnt=%d.\n",
- sl->name, atomic_read(&sl->refcnt));
- if (msleep_interruptible(1000))
- flush_signals(current);
- }
+ dev_dbg(dev, "%s: Releasing %s [%p]\n", __func__, sl->name, sl);
w1_family_put(sl->family);
sl->master->slave_count--;
-
- complete(&sl->released);
}
static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
@@ -243,7 +245,9 @@ static ssize_t w1_master_attribute_store_search(struct device * dev,
mutex_lock(&md->mutex);
md->search_count = tmp;
mutex_unlock(&md->mutex);
- wake_up_process(md->thread);
+ /* Only wake if it is going to be searching. */
+ if (tmp)
+ wake_up_process(md->thread);
return count;
}
@@ -277,7 +281,6 @@ static ssize_t w1_master_attribute_store_pullup(struct device *dev,
mutex_lock(&md->mutex);
md->enable_pullup = tmp;
mutex_unlock(&md->mutex);
- wake_up_process(md->thread);
return count;
}
@@ -314,6 +317,24 @@ static ssize_t w1_master_attribute_show_timeout(struct device *dev, struct devic
return count;
}
+static ssize_t w1_master_attribute_store_max_slave_count(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int tmp;
+ struct w1_master *md = dev_to_w1_master(dev);
+
+ if (kstrtoint(buf, 0, &tmp) == -EINVAL || tmp < 1)
+ return -EINVAL;
+
+ mutex_lock(&md->mutex);
+ md->max_slave_count = tmp;
+ /* allow each time the max_slave_count is updated */
+ clear_bit(W1_WARN_MAX_COUNT, &md->flags);
+ mutex_unlock(&md->mutex);
+
+ return count;
+}
+
static ssize_t w1_master_attribute_show_max_slave_count(struct device *dev, struct device_attribute *attr, char *buf)
{
struct w1_master *md = dev_to_w1_master(dev);
@@ -352,23 +373,20 @@ static ssize_t w1_master_attribute_show_slaves(struct device *dev,
{
struct w1_master *md = dev_to_w1_master(dev);
int c = PAGE_SIZE;
+ struct list_head *ent, *n;
+ struct w1_slave *sl = NULL;
- mutex_lock(&md->mutex);
-
- if (md->slave_count == 0)
- c -= snprintf(buf + PAGE_SIZE - c, c, "not found.\n");
- else {
- struct list_head *ent, *n;
- struct w1_slave *sl;
+ mutex_lock(&md->list_mutex);
- list_for_each_safe(ent, n, &md->slist) {
- sl = list_entry(ent, struct w1_slave, w1_slave_entry);
+ list_for_each_safe(ent, n, &md->slist) {
+ sl = list_entry(ent, struct w1_slave, w1_slave_entry);
- c -= snprintf(buf + PAGE_SIZE - c, c, "%s\n", sl->name);
- }
+ c -= snprintf(buf + PAGE_SIZE - c, c, "%s\n", sl->name);
}
+ if (!sl)
+ c -= snprintf(buf + PAGE_SIZE - c, c, "not found.\n");
- mutex_unlock(&md->mutex);
+ mutex_unlock(&md->list_mutex);
return PAGE_SIZE - c;
}
@@ -422,19 +440,22 @@ static int w1_atoreg_num(struct device *dev, const char *buf, size_t count,
}
/* Searches the slaves in the w1_master and returns a pointer or NULL.
- * Note: must hold the mutex
+ * Note: must not hold list_mutex
*/
-static struct w1_slave *w1_slave_search_device(struct w1_master *dev,
+struct w1_slave *w1_slave_search_device(struct w1_master *dev,
struct w1_reg_num *rn)
{
struct w1_slave *sl;
+ mutex_lock(&dev->list_mutex);
list_for_each_entry(sl, &dev->slist, w1_slave_entry) {
if (sl->reg_num.family == rn->family &&
sl->reg_num.id == rn->id &&
sl->reg_num.crc == rn->crc) {
+ mutex_unlock(&dev->list_mutex);
return sl;
}
}
+ mutex_unlock(&dev->list_mutex);
return NULL;
}
@@ -491,7 +512,10 @@ static ssize_t w1_master_attribute_store_remove(struct device *dev,
mutex_lock(&md->mutex);
sl = w1_slave_search_device(md, &rn);
if (sl) {
- w1_slave_detach(sl);
+ result = w1_slave_detach(sl);
+ /* refcnt 0 means it was detached in the call */
+ if (result == 0)
+ result = count;
} else {
dev_info(dev, "Device %02x-%012llx doesn't exists\n", rn.family,
(unsigned long long)rn.id);
@@ -516,7 +540,7 @@ static ssize_t w1_master_attribute_store_remove(struct device *dev,
static W1_MASTER_ATTR_RO(name, S_IRUGO);
static W1_MASTER_ATTR_RO(slaves, S_IRUGO);
static W1_MASTER_ATTR_RO(slave_count, S_IRUGO);
-static W1_MASTER_ATTR_RO(max_slave_count, S_IRUGO);
+static W1_MASTER_ATTR_RW(max_slave_count, S_IRUGO | S_IWUSR | S_IWGRP);
static W1_MASTER_ATTR_RO(attempts, S_IRUGO);
static W1_MASTER_ATTR_RO(timeout, S_IRUGO);
static W1_MASTER_ATTR_RO(pointer, S_IRUGO);
@@ -686,12 +710,14 @@ static int __w1_attach_slave_device(struct w1_slave *sl)
dev_set_uevent_suppress(&sl->dev, false);
kobject_uevent(&sl->dev.kobj, KOBJ_ADD);
+ mutex_lock(&sl->master->list_mutex);
list_add_tail(&sl->w1_slave_entry, &sl->master->slist);
+ mutex_unlock(&sl->master->list_mutex);
return 0;
}
-static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
+int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
{
struct w1_slave *sl;
struct w1_family *f;
@@ -713,8 +739,8 @@ static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
memset(&msg, 0, sizeof(msg));
memcpy(&sl->reg_num, rn, sizeof(sl->reg_num));
- atomic_set(&sl->refcnt, 0);
- init_completion(&sl->released);
+ atomic_set(&sl->refcnt, 1);
+ atomic_inc(&sl->master->refcnt);
/* slave modules need to be loaded in a context with unlocked mutex */
mutex_unlock(&dev->mutex);
@@ -754,23 +780,48 @@ static int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
return 0;
}
-void w1_slave_detach(struct w1_slave *sl)
+int w1_unref_slave(struct w1_slave *sl)
{
- struct w1_netlink_msg msg;
-
- dev_dbg(&sl->dev, "%s: detaching %s [%p].\n", __func__, sl->name, sl);
-
- list_del(&sl->w1_slave_entry);
-
- memset(&msg, 0, sizeof(msg));
- memcpy(msg.id.id, &sl->reg_num, sizeof(msg.id));
- msg.type = W1_SLAVE_REMOVE;
- w1_netlink_send(sl->master, &msg);
-
- device_unregister(&sl->dev);
+ struct w1_master *dev = sl->master;
+ int refcnt;
+ mutex_lock(&dev->list_mutex);
+ refcnt = atomic_sub_return(1, &sl->refcnt);
+ if (refcnt == 0) {
+ struct w1_netlink_msg msg;
+
+ dev_dbg(&sl->dev, "%s: detaching %s [%p].\n", __func__,
+ sl->name, sl);
+
+ list_del(&sl->w1_slave_entry);
+
+ memset(&msg, 0, sizeof(msg));
+ memcpy(msg.id.id, &sl->reg_num, sizeof(msg.id));
+ msg.type = W1_SLAVE_REMOVE;
+ w1_netlink_send(sl->master, &msg);
+
+ device_unregister(&sl->dev);
+ #ifdef DEBUG
+ memset(sl, 0, sizeof(*sl));
+ #endif
+ kfree(sl);
+ }
+ atomic_dec(&dev->refcnt);
+ mutex_unlock(&dev->list_mutex);
+ return refcnt;
+}
- wait_for_completion(&sl->released);
- kfree(sl);
+int w1_slave_detach(struct w1_slave *sl)
+{
+ /* Only detach a slave once as it decreases the refcnt each time. */
+ int destroy_now;
+ mutex_lock(&sl->master->list_mutex);
+ destroy_now = !test_bit(W1_SLAVE_DETACH, &sl->flags);
+ set_bit(W1_SLAVE_DETACH, &sl->flags);
+ mutex_unlock(&sl->master->list_mutex);
+
+ if (destroy_now)
+ destroy_now = !w1_unref_slave(sl);
+ return destroy_now ? 0 : -EBUSY;
}
struct w1_master *w1_search_master_id(u32 id)
@@ -799,7 +850,7 @@ struct w1_slave *w1_search_slave(struct w1_reg_num *id)
mutex_lock(&w1_mlock);
list_for_each_entry(dev, &w1_masters, w1_master_entry) {
- mutex_lock(&dev->mutex);
+ mutex_lock(&dev->list_mutex);
list_for_each_entry(sl, &dev->slist, w1_slave_entry) {
if (sl->reg_num.family == id->family &&
sl->reg_num.id == id->id &&
@@ -810,7 +861,7 @@ struct w1_slave *w1_search_slave(struct w1_reg_num *id)
break;
}
}
- mutex_unlock(&dev->mutex);
+ mutex_unlock(&dev->list_mutex);
if (found)
break;
@@ -830,6 +881,7 @@ void w1_reconnect_slaves(struct w1_family *f, int attach)
dev_dbg(&dev->dev, "Reconnecting slaves in device %s "
"for family %02x.\n", dev->name, f->fid);
mutex_lock(&dev->mutex);
+ mutex_lock(&dev->list_mutex);
list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) {
/* If it is a new family, slaves with the default
* family driver and are that family will be
@@ -841,14 +893,19 @@ void w1_reconnect_slaves(struct w1_family *f, int attach)
(!attach && sl->family->fid == f->fid)) {
struct w1_reg_num rn;
+ mutex_unlock(&dev->list_mutex);
memcpy(&rn, &sl->reg_num, sizeof(rn));
- w1_slave_detach(sl);
-
- w1_attach_slave_device(dev, &rn);
+ /* If it was already in use let the automatic
+ * scan pick it up again later.
+ */
+ if (!w1_slave_detach(sl))
+ w1_attach_slave_device(dev, &rn);
+ mutex_lock(&dev->list_mutex);
}
}
dev_dbg(&dev->dev, "Reconnecting slaves in device %s "
"has been finished.\n", dev->name);
+ mutex_unlock(&dev->list_mutex);
mutex_unlock(&dev->mutex);
}
mutex_unlock(&w1_mlock);
@@ -876,7 +933,12 @@ void w1_slave_found(struct w1_master *dev, u64 rn)
}
/**
- * Performs a ROM Search & registers any devices found.
+ * w1_search() - Performs a ROM Search & registers any devices found.
+ * @dev: The master device to search
+ * @search_type: W1_SEARCH to search all devices, or W1_ALARM_SEARCH
+ * to return only devices in the alarmed state
+ * @cb: Function to call when a device is found
+ *
* The 1-wire search is a simple binary tree search.
* For each bit of the address, we read two bits and write one bit.
* The bit written will put to sleep all devies that don't match that bit.
@@ -886,8 +948,6 @@ void w1_slave_found(struct w1_master *dev, u64 rn)
*
* See "Application note 187 1-wire search algorithm" at www.maxim-ic.com
*
- * @dev The master device to search
- * @cb Function to call when a device is found
*/
void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb)
{
@@ -898,7 +958,8 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb
u8 triplet_ret = 0;
search_bit = 0;
- rn = last_rn = 0;
+ rn = dev->search_id;
+ last_rn = 0;
last_device = 0;
last_zero = -1;
@@ -945,7 +1006,7 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb
else
search_bit = ((last_rn >> i) & 0x1);
- /** Read two bits and write one bit */
+ /* Read two bits and write one bit */
triplet_ret = w1_triplet(dev, search_bit);
/* quit if no device responded */
@@ -960,8 +1021,7 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb
tmp64 = (triplet_ret >> 2);
rn |= (tmp64 << i);
- /* ensure we're called from kthread and not by netlink callback */
- if (!dev->priv && kthread_should_stop()) {
+ if (test_bit(W1_ABORT_SEARCH, &dev->flags)) {
mutex_unlock(&dev->bus_mutex);
dev_dbg(&dev->dev, "Abort w1_search\n");
return;
@@ -970,11 +1030,30 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb
mutex_unlock(&dev->bus_mutex);
if ( (triplet_ret & 0x03) != 0x03 ) {
- if ( (desc_bit == last_zero) || (last_zero < 0))
+ if ((desc_bit == last_zero) || (last_zero < 0)) {
last_device = 1;
+ dev->search_id = 0;
+ } else {
+ dev->search_id = rn;
+ }
desc_bit = last_zero;
cb(dev, rn);
}
+
+ if (!last_device && slave_count == dev->max_slave_count &&
+ !test_bit(W1_WARN_MAX_COUNT, &dev->flags)) {
+ /* Only max_slave_count will be scanned in a search,
+ * but it will start where it left off next search
+ * until all ids are identified and then it will start
+ * over. A continued search will report the previous
+ * last id as the first id (provided it is still on the
+ * bus).
+ */
+ dev_info(&dev->dev, "%s: max_slave_count %d reached, "
+ "will continue next search.\n", __func__,
+ dev->max_slave_count);
+ set_bit(W1_WARN_MAX_COUNT, &dev->flags);
+ }
}
}
@@ -983,17 +1062,24 @@ void w1_search_process_cb(struct w1_master *dev, u8 search_type,
{
struct w1_slave *sl, *sln;
+ mutex_lock(&dev->list_mutex);
list_for_each_entry(sl, &dev->slist, w1_slave_entry)
clear_bit(W1_SLAVE_ACTIVE, &sl->flags);
+ mutex_unlock(&dev->list_mutex);
w1_search_devices(dev, search_type, cb);
+ mutex_lock(&dev->list_mutex);
list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) {
- if (!test_bit(W1_SLAVE_ACTIVE, &sl->flags) && !--sl->ttl)
+ if (!test_bit(W1_SLAVE_ACTIVE, &sl->flags) && !--sl->ttl) {
+ mutex_unlock(&dev->list_mutex);
w1_slave_detach(sl);
+ mutex_lock(&dev->list_mutex);
+ }
else if (test_bit(W1_SLAVE_ACTIVE, &sl->flags))
sl->ttl = dev->slave_ttl;
}
+ mutex_unlock(&dev->list_mutex);
if (dev->search_count > 0)
dev->search_count--;
@@ -1004,6 +1090,32 @@ static void w1_search_process(struct w1_master *dev, u8 search_type)
w1_search_process_cb(dev, search_type, w1_slave_found);
}
+/**
+ * w1_process_callbacks() - execute each dev->async_list callback entry
+ * @dev: w1_master device
+ *
+ * Return: 1 if there were commands to executed 0 otherwise
+ */
+int w1_process_callbacks(struct w1_master *dev)
+{
+ int ret = 0;
+ struct w1_async_cmd *async_cmd, *async_n;
+
+ /* The list can be added to in another thread, loop until it is empty */
+ while (!list_empty(&dev->async_list)) {
+ list_for_each_entry_safe(async_cmd, async_n, &dev->async_list,
+ async_entry) {
+ /* drop the lock, if it is a search it can take a long
+ * time */
+ mutex_unlock(&dev->list_mutex);
+ async_cmd->cb(dev, async_cmd);
+ ret = 1;
+ mutex_lock(&dev->list_mutex);
+ }
+ }
+ return ret;
+}
+
int w1_process(void *data)
{
struct w1_master *dev = (struct w1_master *) data;
@@ -1011,23 +1123,46 @@ int w1_process(void *data)
* time can be calculated in jiffies once.
*/
const unsigned long jtime = msecs_to_jiffies(w1_timeout * 1000);
+ /* remainder if it woke up early */
+ unsigned long jremain = 0;
- while (!kthread_should_stop()) {
- if (dev->search_count) {
+ for (;;) {
+
+ if (!jremain && dev->search_count) {
mutex_lock(&dev->mutex);
w1_search_process(dev, W1_SEARCH);
mutex_unlock(&dev->mutex);
}
+ mutex_lock(&dev->list_mutex);
+ /* Note, w1_process_callback drops the lock while processing,
+ * but locks it again before returning.
+ */
+ if (!w1_process_callbacks(dev) && jremain) {
+ /* a wake up is either to stop the thread, process
+ * callbacks, or search, it isn't process callbacks, so
+ * schedule a search.
+ */
+ jremain = 1;
+ }
+
try_to_freeze();
__set_current_state(TASK_INTERRUPTIBLE);
+ /* hold list_mutex until after interruptible to prevent loosing
+ * the wakeup signal when async_cmd is added.
+ */
+ mutex_unlock(&dev->list_mutex);
+
if (kthread_should_stop())
break;
/* Only sleep when the search is active. */
- if (dev->search_count)
- schedule_timeout(jtime);
+ if (dev->search_count) {
+ if (!jremain)
+ jremain = jtime;
+ jremain = schedule_timeout(jremain);
+ }
else
schedule();
}
diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h
index ca8081a101d6..734dab7fc687 100644
--- a/drivers/w1/w1.h
+++ b/drivers/w1/w1.h
@@ -22,6 +22,13 @@
#ifndef __W1_H
#define __W1_H
+/**
+ * struct w1_reg_num - broken out slave device id
+ *
+ * @family: identifies the type of device
+ * @id: along with family is the unique device id
+ * @crc: checksum of the other bytes
+ */
struct w1_reg_num
{
#if defined(__LITTLE_ENDIAN_BITFIELD)
@@ -58,7 +65,24 @@ struct w1_reg_num
#define W1_RESUME_CMD 0xA5
#define W1_SLAVE_ACTIVE 0
+#define W1_SLAVE_DETACH 1
+/**
+ * struct w1_slave - holds a single slave device on the bus
+ *
+ * @owner: Points to the one wire "wire" kernel module.
+ * @name: Device id is ascii.
+ * @w1_slave_entry: data for the linked list
+ * @reg_num: the slave id in binary
+ * @refcnt: reference count, delete when 0
+ * @flags: bit flags for W1_SLAVE_ACTIVE W1_SLAVE_DETACH
+ * @ttl: decrement per search this slave isn't found, deatch at 0
+ * @master: bus which this slave is on
+ * @family: module for device family type
+ * @family_data: pointer for use by the family module
+ * @dev: kernel device identifier
+ *
+ */
struct w1_slave
{
struct module *owner;
@@ -66,7 +90,6 @@ struct w1_slave
struct list_head w1_slave_entry;
struct w1_reg_num reg_num;
atomic_t refcnt;
- u8 rom[9];
int ttl;
unsigned long flags;
@@ -74,99 +97,146 @@ struct w1_slave
struct w1_family *family;
void *family_data;
struct device dev;
- struct completion released;
};
typedef void (*w1_slave_found_callback)(struct w1_master *, u64);
/**
+ * struct w1_bus_master - operations available on a bus master
+ *
+ * @data: the first parameter in all the functions below
+ *
+ * @read_bit: Sample the line level @return the level read (0 or 1)
+ *
+ * @write_bit: Sets the line level
+ *
+ * @touch_bit: the lowest-level function for devices that really support the
+ * 1-wire protocol.
+ * touch_bit(0) = write-0 cycle
+ * touch_bit(1) = write-1 / read cycle
+ * @return the bit read (0 or 1)
+ *
+ * @read_byte: Reads a bytes. Same as 8 touch_bit(1) calls.
+ * @return the byte read
+ *
+ * @write_byte: Writes a byte. Same as 8 touch_bit(x) calls.
+ *
+ * @read_block: Same as a series of read_byte() calls
+ * @return the number of bytes read
+ *
+ * @write_block: Same as a series of write_byte() calls
+ *
+ * @triplet: Combines two reads and a smart write for ROM searches
+ * @return bit0=Id bit1=comp_id bit2=dir_taken
+ *
+ * @reset_bus: long write-0 with a read for the presence pulse detection
+ * @return -1=Error, 0=Device present, 1=No device present
+ *
+ * @set_pullup: Put out a strong pull-up pulse of the specified duration.
+ * @return -1=Error, 0=completed
+ *
+ * @search: Really nice hardware can handles the different types of ROM search
+ * w1_master* is passed to the slave found callback.
+ * u8 is search_type, W1_SEARCH or W1_ALARM_SEARCH
+ *
* Note: read_bit and write_bit are very low level functions and should only
* be used with hardware that doesn't really support 1-wire operations,
* like a parallel/serial port.
* Either define read_bit and write_bit OR define, at minimum, touch_bit and
* reset_bus.
+ *
*/
struct w1_bus_master
{
- /** the first parameter in all the functions below */
void *data;
- /**
- * Sample the line level
- * @return the level read (0 or 1)
- */
u8 (*read_bit)(void *);
- /** Sets the line level */
void (*write_bit)(void *, u8);
- /**
- * touch_bit is the lowest-level function for devices that really
- * support the 1-wire protocol.
- * touch_bit(0) = write-0 cycle
- * touch_bit(1) = write-1 / read cycle
- * @return the bit read (0 or 1)
- */
u8 (*touch_bit)(void *, u8);
- /**
- * Reads a bytes. Same as 8 touch_bit(1) calls.
- * @return the byte read
- */
u8 (*read_byte)(void *);
- /**
- * Writes a byte. Same as 8 touch_bit(x) calls.
- */
void (*write_byte)(void *, u8);
- /**
- * Same as a series of read_byte() calls
- * @return the number of bytes read
- */
u8 (*read_block)(void *, u8 *, int);
- /** Same as a series of write_byte() calls */
void (*write_block)(void *, const u8 *, int);
- /**
- * Combines two reads and a smart write for ROM searches
- * @return bit0=Id bit1=comp_id bit2=dir_taken
- */
u8 (*triplet)(void *, u8);
- /**
- * long write-0 with a read for the presence pulse detection
- * @return -1=Error, 0=Device present, 1=No device present
- */
u8 (*reset_bus)(void *);
- /**
- * Put out a strong pull-up pulse of the specified duration.
- * @return -1=Error, 0=completed
- */
u8 (*set_pullup)(void *, int);
- /** Really nice hardware can handles the different types of ROM search
- * w1_master* is passed to the slave found callback.
- */
void (*search)(void *, struct w1_master *,
u8, w1_slave_found_callback);
};
+/**
+ * enum w1_master_flags - bitfields used in w1_master.flags
+ * @W1_ABORT_SEARCH: abort searching early on shutdown
+ * @W1_WARN_MAX_COUNT: limit warning when the maximum count is reached
+ */
+enum w1_master_flags {
+ W1_ABORT_SEARCH = 0,
+ W1_WARN_MAX_COUNT = 1,
+};
+
+/**
+ * struct w1_master - one per bus master
+ * @w1_master_entry: master linked list
+ * @owner: module owner
+ * @name: dynamically allocate bus name
+ * @list_mutex: protect slist and async_list
+ * @slist: linked list of slaves
+ * @async_list: linked list of netlink commands to execute
+ * @max_slave_count: maximum number of slaves to search for at a time
+ * @slave_count: current number of slaves known
+ * @attempts: number of searches ran
+ * @slave_ttl: number of searches before a slave is timed out
+ * @initialized: prevent init/removal race conditions
+ * @id: w1 bus number
+ * @search_count: number of automatic searches to run, -1 unlimited
+ * @search_id: allows continuing a search
+ * @refcnt: reference count
+ * @priv: private data storage
+ * @priv_size: size allocated
+ * @enable_pullup: allows a strong pullup
+ * @pullup_duration: time for the next strong pullup
+ * @flags: one of w1_master_flags
+ * @thread: thread for bus search and netlink commands
+ * @mutex: protect most of w1_master
+ * @bus_mutex: pretect concurrent bus access
+ * @driver: sysfs driver
+ * @dev: sysfs device
+ * @bus_master: io operations available
+ * @seq: sequence number used for netlink broadcasts
+ * @portid: destination for the current netlink command
+ */
struct w1_master
{
struct list_head w1_master_entry;
struct module *owner;
unsigned char name[W1_MAXNAMELEN];
+ /* list_mutex protects just slist and async_list so slaves can be
+ * searched for and async commands added while the master has
+ * w1_master.mutex locked and is operating on the bus.
+ * lock order w1_mlock, w1_master.mutex, w1_master.list_mutex
+ */
+ struct mutex list_mutex;
struct list_head slist;
+ struct list_head async_list;
int max_slave_count, slave_count;
unsigned long attempts;
int slave_ttl;
int initialized;
u32 id;
int search_count;
+ /* id to start searching on, to continue a search or 0 to restart */
+ u64 search_id;
atomic_t refcnt;
@@ -178,6 +248,8 @@ struct w1_master
/** 5V strong pullup duration in milliseconds, zero disabled. */
int pullup_duration;
+ long flags;
+
struct task_struct *thread;
struct mutex mutex;
struct mutex bus_mutex;
@@ -188,16 +260,41 @@ struct w1_master
struct w1_bus_master *bus_master;
u32 seq;
+ /* port id to send netlink responses to. The value is temporarily
+ * stored here while processing a message, set after locking the
+ * mutex, zero before unlocking the mutex.
+ */
+ u32 portid;
+};
+
+/**
+ * struct w1_async_cmd - execute callback from the w1_process kthread
+ * @async_entry: link entry
+ * @cb: callback function, must list_del and destroy this list before
+ * returning
+ *
+ * When inserted into the w1_master async_list, w1_process will execute
+ * the callback. Embed this into the structure with the command details.
+ */
+struct w1_async_cmd {
+ struct list_head async_entry;
+ void (*cb)(struct w1_master *dev, struct w1_async_cmd *async_cmd);
};
int w1_create_master_attributes(struct w1_master *);
void w1_destroy_master_attributes(struct w1_master *master);
void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb);
void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb);
+/* call w1_unref_slave to release the reference counts w1_search_slave added */
struct w1_slave *w1_search_slave(struct w1_reg_num *id);
+/* decrements the reference on sl->master and sl, and cleans up if zero
+ * returns the reference count after it has been decremented */
+int w1_unref_slave(struct w1_slave *sl);
void w1_slave_found(struct w1_master *dev, u64 rn);
void w1_search_process_cb(struct w1_master *dev, u8 search_type,
w1_slave_found_callback cb);
+struct w1_slave *w1_slave_search_device(struct w1_master *dev,
+ struct w1_reg_num *rn);
struct w1_master *w1_search_master_id(u32 id);
/* Disconnect and reconnect devices in the given family. Used for finding
@@ -206,7 +303,9 @@ struct w1_master *w1_search_master_id(u32 id);
* has just been registered, to 0 when it has been unregistered.
*/
void w1_reconnect_slaves(struct w1_family *f, int attach);
-void w1_slave_detach(struct w1_slave *sl);
+int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn);
+/* 0 success, otherwise EBUSY */
+int w1_slave_detach(struct w1_slave *sl);
u8 w1_triplet(struct w1_master *dev, int bdir);
void w1_write_8(struct w1_master *, u8);
@@ -242,6 +341,7 @@ extern int w1_max_slave_ttl;
extern struct list_head w1_masters;
extern struct mutex w1_mlock;
+extern int w1_process_callbacks(struct w1_master *dev);
extern int w1_process(void *);
#endif /* __KERNEL__ */
diff --git a/drivers/w1/w1_family.c b/drivers/w1/w1_family.c
index e9309778ee72..3bff6b37b472 100644
--- a/drivers/w1/w1_family.c
+++ b/drivers/w1/w1_family.c
@@ -31,6 +31,10 @@
DEFINE_SPINLOCK(w1_flock);
static LIST_HEAD(w1_families);
+/**
+ * w1_register_family() - register a device family driver
+ * @newf: family to register
+ */
int w1_register_family(struct w1_family *newf)
{
struct list_head *ent, *n;
@@ -59,6 +63,10 @@ int w1_register_family(struct w1_family *newf)
return ret;
}
+/**
+ * w1_unregister_family() - unregister a device family driver
+ * @fent: family to unregister
+ */
void w1_unregister_family(struct w1_family *fent)
{
struct list_head *ent, *n;
diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h
index 4ad0e81b6404..26ca1343055b 100644
--- a/drivers/w1/w1_family.h
+++ b/drivers/w1/w1_family.h
@@ -48,6 +48,12 @@
struct w1_slave;
+/**
+ * struct w1_family_ops - operations for a family type
+ * @add_slave: add_slave
+ * @remove_slave: remove_slave
+ * @groups: sysfs group
+ */
struct w1_family_ops
{
int (* add_slave)(struct w1_slave *);
@@ -55,6 +61,13 @@ struct w1_family_ops
const struct attribute_group **groups;
};
+/**
+ * struct w1_family - reference counted family structure.
+ * @family_entry: family linked list
+ * @fid: 8 bit family identifier
+ * @fops: operations for this family
+ * @refcnt: reference counter
+ */
struct w1_family
{
struct list_head family_entry;
diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c
index 590bd8a7cd1b..9b084db739c7 100644
--- a/drivers/w1/w1_int.c
+++ b/drivers/w1/w1_int.c
@@ -75,8 +75,10 @@ static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl,
atomic_set(&dev->refcnt, 2);
INIT_LIST_HEAD(&dev->slist);
+ INIT_LIST_HEAD(&dev->async_list);
mutex_init(&dev->mutex);
mutex_init(&dev->bus_mutex);
+ mutex_init(&dev->list_mutex);
memcpy(&dev->dev, device, sizeof(struct device));
dev_set_name(&dev->dev, "w1_bus_master%u", dev->id);
@@ -103,6 +105,10 @@ static void w1_free_dev(struct w1_master *dev)
device_unregister(&dev->dev);
}
+/**
+ * w1_add_master_device() - registers a new master device
+ * @master: master bus device to register
+ */
int w1_add_master_device(struct w1_bus_master *master)
{
struct w1_master *dev, *entry;
@@ -172,6 +178,7 @@ int w1_add_master_device(struct w1_bus_master *master)
#if 0 /* Thread cleanup code, not required currently. */
err_out_kill_thread:
+ set_bit(W1_ABORT_SEARCH, &dev->flags);
kthread_stop(dev->thread);
#endif
err_out_rm_attr:
@@ -187,16 +194,22 @@ void __w1_remove_master_device(struct w1_master *dev)
struct w1_netlink_msg msg;
struct w1_slave *sl, *sln;
- kthread_stop(dev->thread);
-
mutex_lock(&w1_mlock);
list_del(&dev->w1_master_entry);
mutex_unlock(&w1_mlock);
+ set_bit(W1_ABORT_SEARCH, &dev->flags);
+ kthread_stop(dev->thread);
+
mutex_lock(&dev->mutex);
- list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry)
+ mutex_lock(&dev->list_mutex);
+ list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) {
+ mutex_unlock(&dev->list_mutex);
w1_slave_detach(sl);
+ mutex_lock(&dev->list_mutex);
+ }
w1_destroy_master_attributes(dev);
+ mutex_unlock(&dev->list_mutex);
mutex_unlock(&dev->mutex);
atomic_dec(&dev->refcnt);
@@ -206,7 +219,9 @@ void __w1_remove_master_device(struct w1_master *dev)
if (msleep_interruptible(1000))
flush_signals(current);
+ w1_process_callbacks(dev);
}
+ w1_process_callbacks(dev);
memset(&msg, 0, sizeof(msg));
msg.id.mst.id = dev->id;
@@ -216,6 +231,10 @@ void __w1_remove_master_device(struct w1_master *dev)
w1_free_dev(dev);
}
+/**
+ * w1_remove_master_device() - unregister a master device
+ * @bm: master bus device to remove
+ */
void w1_remove_master_device(struct w1_bus_master *bm)
{
struct w1_master *dev, *found = NULL;
diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c
index e10acc237733..282092421cc9 100644
--- a/drivers/w1/w1_io.c
+++ b/drivers/w1/w1_io.c
@@ -62,7 +62,9 @@ static void w1_write_bit(struct w1_master *dev, int bit);
static u8 w1_read_bit(struct w1_master *dev);
/**
- * Generates a write-0 or write-1 cycle and samples the level.
+ * w1_touch_bit() - Generates a write-0 or write-1 cycle and samples the level.
+ * @dev: the master device
+ * @bit: 0 - write a 0, 1 - write a 0 read the level
*/
static u8 w1_touch_bit(struct w1_master *dev, int bit)
{
@@ -77,7 +79,10 @@ static u8 w1_touch_bit(struct w1_master *dev, int bit)
}
/**
- * Generates a write-0 or write-1 cycle.
+ * w1_write_bit() - Generates a write-0 or write-1 cycle.
+ * @dev: the master device
+ * @bit: bit to write
+ *
* Only call if dev->bus_master->touch_bit is NULL
*/
static void w1_write_bit(struct w1_master *dev, int bit)
@@ -102,11 +107,12 @@ static void w1_write_bit(struct w1_master *dev, int bit)
}
/**
+ * w1_pre_write() - pre-write operations
+ * @dev: the master device
+ *
* Pre-write operation, currently only supporting strong pullups.
* Program the hardware for a strong pullup, if one has been requested and
* the hardware supports it.
- *
- * @param dev the master device
*/
static void w1_pre_write(struct w1_master *dev)
{
@@ -118,11 +124,12 @@ static void w1_pre_write(struct w1_master *dev)
}
/**
+ * w1_post_write() - post-write options
+ * @dev: the master device
+ *
* Post-write operation, currently only supporting strong pullups.
* If a strong pullup was requested, clear it if the hardware supports
* them, or execute the delay otherwise, in either case clear the request.
- *
- * @param dev the master device
*/
static void w1_post_write(struct w1_master *dev)
{
@@ -136,10 +143,9 @@ static void w1_post_write(struct w1_master *dev)
}
/**
- * Writes 8 bits.
- *
- * @param dev the master device
- * @param byte the byte to write
+ * w1_write_8() - Writes 8 bits.
+ * @dev: the master device
+ * @byte: the byte to write
*/
void w1_write_8(struct w1_master *dev, u8 byte)
{
@@ -161,7 +167,9 @@ EXPORT_SYMBOL_GPL(w1_write_8);
/**
- * Generates a write-1 cycle and samples the level.
+ * w1_read_bit() - Generates a write-1 cycle and samples the level.
+ * @dev: the master device
+ *
* Only call if dev->bus_master->touch_bit is NULL
*/
static u8 w1_read_bit(struct w1_master *dev)
@@ -185,16 +193,17 @@ static u8 w1_read_bit(struct w1_master *dev)
}
/**
- * Does a triplet - used for searching ROM addresses.
+ * w1_triplet() - * Does a triplet - used for searching ROM addresses.
+ * @dev: the master device
+ * @bdir: the bit to write if both id_bit and comp_bit are 0
+ *
* Return bits:
* bit 0 = id_bit
* bit 1 = comp_bit
* bit 2 = dir_taken
* If both bits 0 & 1 are set, the search should be restarted.
*
- * @param dev the master device
- * @param bdir the bit to write if both id_bit and comp_bit are 0
- * @return bit fields - see above
+ * Return: bit fields - see above
*/
u8 w1_triplet(struct w1_master *dev, int bdir)
{
@@ -226,10 +235,10 @@ u8 w1_triplet(struct w1_master *dev, int bdir)
}
/**
- * Reads 8 bits.
+ * w1_read_8() - Reads 8 bits.
+ * @dev: the master device
*
- * @param dev the master device
- * @return the byte read
+ * Return: the byte read
*/
u8 w1_read_8(struct w1_master *dev)
{
@@ -247,11 +256,10 @@ u8 w1_read_8(struct w1_master *dev)
EXPORT_SYMBOL_GPL(w1_read_8);
/**
- * Writes a series of bytes.
- *
- * @param dev the master device
- * @param buf pointer to the data to write
- * @param len the number of bytes to write
+ * w1_write_block() - Writes a series of bytes.
+ * @dev: the master device
+ * @buf: pointer to the data to write
+ * @len: the number of bytes to write
*/
void w1_write_block(struct w1_master *dev, const u8 *buf, int len)
{
@@ -269,11 +277,10 @@ void w1_write_block(struct w1_master *dev, const u8 *buf, int len)
EXPORT_SYMBOL_GPL(w1_write_block);
/**
- * Touches a series of bytes.
- *
- * @param dev the master device
- * @param buf pointer to the data to write
- * @param len the number of bytes to write
+ * w1_touch_block() - Touches a series of bytes.
+ * @dev: the master device
+ * @buf: pointer to the data to write
+ * @len: the number of bytes to write
*/
void w1_touch_block(struct w1_master *dev, u8 *buf, int len)
{
@@ -294,12 +301,11 @@ void w1_touch_block(struct w1_master *dev, u8 *buf, int len)
EXPORT_SYMBOL_GPL(w1_touch_block);
/**
- * Reads a series of bytes.
- *
- * @param dev the master device
- * @param buf pointer to the buffer to fill
- * @param len the number of bytes to read
- * @return the number of bytes read
+ * w1_read_block() - Reads a series of bytes.
+ * @dev: the master device
+ * @buf: pointer to the buffer to fill
+ * @len: the number of bytes to read
+ * Return: the number of bytes read
*/
u8 w1_read_block(struct w1_master *dev, u8 *buf, int len)
{
@@ -319,10 +325,9 @@ u8 w1_read_block(struct w1_master *dev, u8 *buf, int len)
EXPORT_SYMBOL_GPL(w1_read_block);
/**
- * Issues a reset bus sequence.
- *
- * @param dev The bus master pointer
- * @return 0=Device present, 1=No device present or error
+ * w1_reset_bus() - Issues a reset bus sequence.
+ * @dev: the master device
+ * Return: 0=Device present, 1=No device present or error
*/
int w1_reset_bus(struct w1_master *dev)
{
@@ -383,12 +388,15 @@ void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_cal
}
/**
+ * w1_reset_select_slave() - reset and select a slave
+ * @sl: the slave to select
+ *
* Resets the bus and then selects the slave by sending either a skip rom
- * or a rom match.
+ * or a rom match. A skip rom is issued if there is only one device
+ * registered on the bus.
* The w1 master lock must be held.
*
- * @param sl the slave to select
- * @return 0=success, anything else=error
+ * Return: 0=success, anything else=error
*/
int w1_reset_select_slave(struct w1_slave *sl)
{
@@ -409,6 +417,9 @@ int w1_reset_select_slave(struct w1_slave *sl)
EXPORT_SYMBOL_GPL(w1_reset_select_slave);
/**
+ * w1_reset_resume_command() - resume instead of another match ROM
+ * @dev: the master device
+ *
* When the workflow with a slave amongst many requires several
* successive commands a reset between each, this function is similar
* to doing a reset then a match ROM for the last matched ROM. The
@@ -420,8 +431,6 @@ EXPORT_SYMBOL_GPL(w1_reset_select_slave);
* doesn't work of course, but the resume command is the next best thing.
*
* The w1 master lock must be held.
- *
- * @param dev the master device
*/
int w1_reset_resume_command(struct w1_master *dev)
{
@@ -435,6 +444,10 @@ int w1_reset_resume_command(struct w1_master *dev)
EXPORT_SYMBOL_GPL(w1_reset_resume_command);
/**
+ * w1_next_pullup() - register for a strong pullup
+ * @dev: the master device
+ * @delay: time in milliseconds
+ *
* Put out a strong pull-up of the specified duration after the next write
* operation. Not all hardware supports strong pullups. Hardware that
* doesn't support strong pullups will sleep for the given time after the
@@ -442,8 +455,7 @@ EXPORT_SYMBOL_GPL(w1_reset_resume_command);
* the next write, specifying zero will clear a previous request.
* The w1 master lock must be held.
*
- * @param delay time in milliseconds
- * @return 0=success, anything else=error
+ * Return: 0=success, anything else=error
*/
void w1_next_pullup(struct w1_master *dev, int delay)
{
diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c
index 40788c925d1c..5234964fe001 100644
--- a/drivers/w1/w1_netlink.c
+++ b/drivers/w1/w1_netlink.c
@@ -45,7 +45,7 @@ void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg)
memcpy(w, msg, sizeof(struct w1_netlink_msg));
- cn_netlink_send(m, 0, GFP_KERNEL);
+ cn_netlink_send(m, dev->portid, 0, GFP_KERNEL);
}
static void w1_send_slave(struct w1_master *dev, u64 rn)
@@ -54,53 +54,95 @@ static void w1_send_slave(struct w1_master *dev, u64 rn)
struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1);
struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1);
int avail;
-
- /* update kernel slave list */
- w1_slave_found(dev, rn);
+ u64 *data;
avail = dev->priv_size - cmd->len;
- if (avail > 8) {
- u64 *data = (void *)(cmd + 1) + cmd->len;
+ if (avail < 8) {
+ msg->ack++;
+ cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL);
- *data = rn;
- cmd->len += 8;
- hdr->len += 8;
- msg->len += 8;
- return;
+ msg->len = sizeof(struct w1_netlink_msg) +
+ sizeof(struct w1_netlink_cmd);
+ hdr->len = sizeof(struct w1_netlink_cmd);
+ cmd->len = 0;
}
- msg->ack++;
- cn_netlink_send(msg, 0, GFP_KERNEL);
+ data = (void *)(cmd + 1) + cmd->len;
- msg->len = sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd);
- hdr->len = sizeof(struct w1_netlink_cmd);
- cmd->len = 0;
+ *data = rn;
+ cmd->len += 8;
+ hdr->len += 8;
+ msg->len += 8;
}
-static int w1_process_search_command(struct w1_master *dev, struct cn_msg *msg,
- unsigned int avail)
+static void w1_found_send_slave(struct w1_master *dev, u64 rn)
{
- struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1);
- struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1);
- int search_type = (cmd->cmd == W1_CMD_ALARM_SEARCH)?W1_ALARM_SEARCH:W1_SEARCH;
+ /* update kernel slave list */
+ w1_slave_found(dev, rn);
- dev->priv = msg;
- dev->priv_size = avail;
+ w1_send_slave(dev, rn);
+}
+
+/* Get the current slave list, or search (with or without alarm) */
+static int w1_get_slaves(struct w1_master *dev,
+ struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr,
+ struct w1_netlink_cmd *req_cmd)
+{
+ struct cn_msg *msg;
+ struct w1_netlink_msg *hdr;
+ struct w1_netlink_cmd *cmd;
+ struct w1_slave *sl;
+
+ msg = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->id = req_msg->id;
+ msg->seq = req_msg->seq;
+ msg->ack = 0;
+ msg->len = sizeof(struct w1_netlink_msg) +
+ sizeof(struct w1_netlink_cmd);
+
+ hdr = (struct w1_netlink_msg *)(msg + 1);
+ cmd = (struct w1_netlink_cmd *)(hdr + 1);
+
+ hdr->type = W1_MASTER_CMD;
+ hdr->id = req_hdr->id;
+ hdr->len = sizeof(struct w1_netlink_cmd);
+
+ cmd->cmd = req_cmd->cmd;
+ cmd->len = 0;
- w1_search_process_cb(dev, search_type, w1_send_slave);
+ dev->priv = msg;
+ dev->priv_size = PAGE_SIZE - msg->len - sizeof(struct cn_msg);
+
+ if (req_cmd->cmd == W1_CMD_LIST_SLAVES) {
+ __u64 rn;
+ mutex_lock(&dev->list_mutex);
+ list_for_each_entry(sl, &dev->slist, w1_slave_entry) {
+ memcpy(&rn, &sl->reg_num, sizeof(rn));
+ w1_send_slave(dev, rn);
+ }
+ mutex_unlock(&dev->list_mutex);
+ } else {
+ w1_search_process_cb(dev, cmd->cmd == W1_CMD_ALARM_SEARCH ?
+ W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave);
+ }
msg->ack = 0;
- cn_netlink_send(msg, 0, GFP_KERNEL);
+ cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL);
dev->priv = NULL;
dev->priv_size = 0;
+ kfree(msg);
+
return 0;
}
static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr,
- struct w1_netlink_cmd *cmd)
+ struct w1_netlink_cmd *cmd, u32 portid)
{
void *data;
struct w1_netlink_msg *h;
@@ -131,7 +173,7 @@ static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr,
memcpy(c->data, cmd->data, c->len);
- err = cn_netlink_send(cm, 0, GFP_KERNEL);
+ err = cn_netlink_send(cm, portid, 0, GFP_KERNEL);
kfree(data);
@@ -146,11 +188,11 @@ static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg,
switch (cmd->cmd) {
case W1_CMD_TOUCH:
w1_touch_block(dev, cmd->data, cmd->len);
- w1_send_read_reply(msg, hdr, cmd);
+ w1_send_read_reply(msg, hdr, cmd, dev->portid);
break;
case W1_CMD_READ:
w1_read_block(dev, cmd->data, cmd->len);
- w1_send_read_reply(msg, hdr, cmd);
+ w1_send_read_reply(msg, hdr, cmd, dev->portid);
break;
case W1_CMD_WRITE:
w1_write_block(dev, cmd->data, cmd->len);
@@ -163,38 +205,57 @@ static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg,
return err;
}
-static int w1_process_command_master(struct w1_master *dev, struct cn_msg *req_msg,
- struct w1_netlink_msg *req_hdr, struct w1_netlink_cmd *req_cmd)
+static int w1_process_command_addremove(struct w1_master *dev,
+ struct cn_msg *msg, struct w1_netlink_msg *hdr,
+ struct w1_netlink_cmd *cmd)
{
- int err = -EINVAL;
- struct cn_msg *msg;
- struct w1_netlink_msg *hdr;
- struct w1_netlink_cmd *cmd;
+ struct w1_slave *sl;
+ int err = 0;
+ struct w1_reg_num *id;
- msg = kzalloc(PAGE_SIZE, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
+ if (cmd->len != 8)
+ return -EINVAL;
- msg->id = req_msg->id;
- msg->seq = req_msg->seq;
- msg->ack = 0;
- msg->len = sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd);
+ id = (struct w1_reg_num *)cmd->data;
- hdr = (struct w1_netlink_msg *)(msg + 1);
- cmd = (struct w1_netlink_cmd *)(hdr + 1);
+ sl = w1_slave_search_device(dev, id);
+ switch (cmd->cmd) {
+ case W1_CMD_SLAVE_ADD:
+ if (sl)
+ err = -EINVAL;
+ else
+ err = w1_attach_slave_device(dev, id);
+ break;
+ case W1_CMD_SLAVE_REMOVE:
+ if (sl)
+ w1_slave_detach(sl);
+ else
+ err = -EINVAL;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
- hdr->type = W1_MASTER_CMD;
- hdr->id = req_hdr->id;
- hdr->len = sizeof(struct w1_netlink_cmd);
+ return err;
+}
- cmd->cmd = req_cmd->cmd;
- cmd->len = 0;
+static int w1_process_command_master(struct w1_master *dev,
+ struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr,
+ struct w1_netlink_cmd *req_cmd)
+{
+ int err = -EINVAL;
- switch (cmd->cmd) {
+ /* drop bus_mutex for search (does it's own locking), and add/remove
+ * which doesn't use the bus
+ */
+ switch (req_cmd->cmd) {
case W1_CMD_SEARCH:
case W1_CMD_ALARM_SEARCH:
- err = w1_process_search_command(dev, msg,
- PAGE_SIZE - msg->len - sizeof(struct cn_msg));
+ case W1_CMD_LIST_SLAVES:
+ mutex_unlock(&dev->bus_mutex);
+ err = w1_get_slaves(dev, req_msg, req_hdr, req_cmd);
+ mutex_lock(&dev->bus_mutex);
break;
case W1_CMD_READ:
case W1_CMD_WRITE:
@@ -204,12 +265,20 @@ static int w1_process_command_master(struct w1_master *dev, struct cn_msg *req_m
case W1_CMD_RESET:
err = w1_reset_bus(dev);
break;
+ case W1_CMD_SLAVE_ADD:
+ case W1_CMD_SLAVE_REMOVE:
+ mutex_unlock(&dev->bus_mutex);
+ mutex_lock(&dev->mutex);
+ err = w1_process_command_addremove(dev, req_msg, req_hdr,
+ req_cmd);
+ mutex_unlock(&dev->mutex);
+ mutex_lock(&dev->bus_mutex);
+ break;
default:
err = -EINVAL;
break;
}
- kfree(msg);
return err;
}
@@ -223,7 +292,8 @@ static int w1_process_command_slave(struct w1_slave *sl, struct cn_msg *msg,
return w1_process_command_io(sl->master, msg, hdr, cmd);
}
-static int w1_process_command_root(struct cn_msg *msg, struct w1_netlink_msg *mcmd)
+static int w1_process_command_root(struct cn_msg *msg,
+ struct w1_netlink_msg *mcmd, u32 portid)
{
struct w1_master *m;
struct cn_msg *cn;
@@ -256,7 +326,7 @@ static int w1_process_command_root(struct cn_msg *msg, struct w1_netlink_msg *mc
mutex_lock(&w1_mlock);
list_for_each_entry(m, &w1_masters, w1_master_entry) {
if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) {
- cn_netlink_send(cn, 0, GFP_KERNEL);
+ cn_netlink_send(cn, portid, 0, GFP_KERNEL);
cn->ack++;
cn->len = sizeof(struct w1_netlink_msg);
w->len = 0;
@@ -269,7 +339,7 @@ static int w1_process_command_root(struct cn_msg *msg, struct w1_netlink_msg *mc
id++;
}
cn->ack = 0;
- cn_netlink_send(cn, 0, GFP_KERNEL);
+ cn_netlink_send(cn, portid, 0, GFP_KERNEL);
mutex_unlock(&w1_mlock);
kfree(cn);
@@ -277,7 +347,7 @@ static int w1_process_command_root(struct cn_msg *msg, struct w1_netlink_msg *mc
}
static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rmsg,
- struct w1_netlink_cmd *rcmd, int error)
+ struct w1_netlink_cmd *rcmd, int portid, int error)
{
struct cn_msg *cmsg;
struct w1_netlink_msg *msg;
@@ -304,35 +374,147 @@ static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rm
cmsg->len += sizeof(*cmd);
}
- error = cn_netlink_send(cmsg, 0, GFP_KERNEL);
+ error = cn_netlink_send(cmsg, portid, 0, GFP_KERNEL);
kfree(cmsg);
return error;
}
+/* Bundle together a reference count, the full message, and broken out
+ * commands to be executed on each w1 master kthread in one memory allocation.
+ */
+struct w1_cb_block {
+ atomic_t refcnt;
+ u32 portid; /* Sending process port ID */
+ struct cn_msg msg;
+ /* cn_msg data */
+ /* one or more variable length struct w1_cb_node */
+};
+struct w1_cb_node {
+ struct w1_async_cmd async;
+ /* pointers within w1_cb_block and msg data */
+ struct w1_cb_block *block;
+ struct w1_netlink_msg *m;
+ struct w1_slave *sl;
+ struct w1_master *dev;
+};
+
+static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd)
+{
+ struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node,
+ async);
+ u16 mlen = node->m->len;
+ u8 *cmd_data = node->m->data;
+ int err = 0;
+ struct w1_slave *sl = node->sl;
+ struct w1_netlink_cmd *cmd = NULL;
+
+ mutex_lock(&dev->bus_mutex);
+ dev->portid = node->block->portid;
+ if (sl && w1_reset_select_slave(sl))
+ err = -ENODEV;
+
+ while (mlen && !err) {
+ cmd = (struct w1_netlink_cmd *)cmd_data;
+
+ if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) {
+ err = -E2BIG;
+ break;
+ }
+
+ if (sl)
+ err = w1_process_command_slave(sl, &node->block->msg,
+ node->m, cmd);
+ else
+ err = w1_process_command_master(dev, &node->block->msg,
+ node->m, cmd);
+
+ w1_netlink_send_error(&node->block->msg, node->m, cmd,
+ node->block->portid, err);
+ err = 0;
+
+ cmd_data += cmd->len + sizeof(struct w1_netlink_cmd);
+ mlen -= cmd->len + sizeof(struct w1_netlink_cmd);
+ }
+
+ if (!cmd || err)
+ w1_netlink_send_error(&node->block->msg, node->m, cmd,
+ node->block->portid, err);
+
+ if (sl)
+ w1_unref_slave(sl);
+ else
+ atomic_dec(&dev->refcnt);
+ dev->portid = 0;
+ mutex_unlock(&dev->bus_mutex);
+
+ mutex_lock(&dev->list_mutex);
+ list_del(&async_cmd->async_entry);
+ mutex_unlock(&dev->list_mutex);
+
+ if (atomic_sub_return(1, &node->block->refcnt) == 0)
+ kfree(node->block);
+}
+
static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
{
struct w1_netlink_msg *m = (struct w1_netlink_msg *)(msg + 1);
- struct w1_netlink_cmd *cmd;
struct w1_slave *sl;
struct w1_master *dev;
+ u16 msg_len;
int err = 0;
+ struct w1_cb_block *block = NULL;
+ struct w1_cb_node *node = NULL;
+ int node_count = 0;
+
+ /* Count the number of master or slave commands there are to allocate
+ * space for one cb_node each.
+ */
+ msg_len = msg->len;
+ while (msg_len && !err) {
+ if (m->len + sizeof(struct w1_netlink_msg) > msg_len) {
+ err = -E2BIG;
+ break;
+ }
+
+ if (m->type == W1_MASTER_CMD || m->type == W1_SLAVE_CMD)
+ ++node_count;
- while (msg->len && !err) {
+ msg_len -= sizeof(struct w1_netlink_msg) + m->len;
+ m = (struct w1_netlink_msg *)(((u8 *)m) +
+ sizeof(struct w1_netlink_msg) + m->len);
+ }
+ m = (struct w1_netlink_msg *)(msg + 1);
+ if (node_count) {
+ /* msg->len doesn't include itself */
+ long size = sizeof(struct w1_cb_block) + msg->len +
+ node_count*sizeof(struct w1_cb_node);
+ block = kmalloc(size, GFP_KERNEL);
+ if (!block) {
+ w1_netlink_send_error(msg, m, NULL, nsp->portid,
+ -ENOMEM);
+ return;
+ }
+ atomic_set(&block->refcnt, 1);
+ block->portid = nsp->portid;
+ memcpy(&block->msg, msg, sizeof(*msg) + msg->len);
+ node = (struct w1_cb_node *)((u8 *)block->msg.data + msg->len);
+ }
+
+ msg_len = msg->len;
+ while (msg_len && !err) {
struct w1_reg_num id;
u16 mlen = m->len;
- u8 *cmd_data = m->data;
dev = NULL;
sl = NULL;
- cmd = NULL;
memcpy(&id, m->id.id, sizeof(id));
#if 0
printk("%s: %02x.%012llx.%02x: type=%02x, len=%u.\n",
__func__, id.family, (unsigned long long)id.id, id.crc, m->type, m->len);
#endif
- if (m->len + sizeof(struct w1_netlink_msg) > msg->len) {
+ if (m->len + sizeof(struct w1_netlink_msg) > msg_len) {
err = -E2BIG;
break;
}
@@ -344,7 +526,7 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
if (sl)
dev = sl->master;
} else {
- err = w1_process_command_root(msg, m);
+ err = w1_process_command_root(msg, m, nsp->portid);
goto out_cont;
}
@@ -357,41 +539,24 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
if (!mlen)
goto out_cont;
- mutex_lock(&dev->mutex);
-
- if (sl && w1_reset_select_slave(sl)) {
- err = -ENODEV;
- goto out_up;
- }
+ atomic_inc(&block->refcnt);
+ node->async.cb = w1_process_cb;
+ node->block = block;
+ node->m = (struct w1_netlink_msg *)((u8 *)&block->msg +
+ (size_t)((u8 *)m - (u8 *)msg));
+ node->sl = sl;
+ node->dev = dev;
- while (mlen) {
- cmd = (struct w1_netlink_cmd *)cmd_data;
+ mutex_lock(&dev->list_mutex);
+ list_add_tail(&node->async.async_entry, &dev->async_list);
+ wake_up_process(dev->thread);
+ mutex_unlock(&dev->list_mutex);
+ ++node;
- if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) {
- err = -E2BIG;
- break;
- }
-
- if (sl)
- err = w1_process_command_slave(sl, msg, m, cmd);
- else
- err = w1_process_command_master(dev, msg, m, cmd);
-
- w1_netlink_send_error(msg, m, cmd, err);
- err = 0;
-
- cmd_data += cmd->len + sizeof(struct w1_netlink_cmd);
- mlen -= cmd->len + sizeof(struct w1_netlink_cmd);
- }
-out_up:
- atomic_dec(&dev->refcnt);
- if (sl)
- atomic_dec(&sl->refcnt);
- mutex_unlock(&dev->mutex);
out_cont:
- if (!cmd || err)
- w1_netlink_send_error(msg, m, cmd, err);
- msg->len -= sizeof(struct w1_netlink_msg) + m->len;
+ if (err)
+ w1_netlink_send_error(msg, m, NULL, nsp->portid, err);
+ msg_len -= sizeof(struct w1_netlink_msg) + m->len;
m = (struct w1_netlink_msg *)(((u8 *)m) + sizeof(struct w1_netlink_msg) + m->len);
/*
@@ -400,6 +565,8 @@ out_cont:
if (err == -ENODEV)
err = 0;
}
+ if (block && atomic_sub_return(1, &block->refcnt) == 0)
+ kfree(block);
}
int w1_init_netlink(void)
diff --git a/drivers/w1/w1_netlink.h b/drivers/w1/w1_netlink.h
index b0922dc29658..1e9504e67650 100644
--- a/drivers/w1/w1_netlink.h
+++ b/drivers/w1/w1_netlink.h
@@ -27,6 +27,18 @@
#include "w1.h"
+/**
+ * enum w1_netlink_message_types - message type
+ *
+ * @W1_SLAVE_ADD: notification that a slave device was added
+ * @W1_SLAVE_REMOVE: notification that a slave device was removed
+ * @W1_MASTER_ADD: notification that a new bus master was added
+ * @W1_MASTER_REMOVE: notification that a bus masterwas removed
+ * @W1_MASTER_CMD: initiate operations on a specific master
+ * @W1_SLAVE_CMD: sends reset, selects the slave, then does a read/write/touch
+ * operation
+ * @W1_LIST_MASTERS: used to determine the bus master identifiers
+ */
enum w1_netlink_message_types {
W1_SLAVE_ADD = 0,
W1_SLAVE_REMOVE,
@@ -52,6 +64,22 @@ struct w1_netlink_msg
__u8 data[0];
};
+/**
+ * enum w1_commands - commands available for master or slave operations
+ * @W1_CMD_READ: read len bytes
+ * @W1_CMD_WRITE: write len bytes
+ * @W1_CMD_SEARCH: initiate a standard search, returns only the slave
+ * devices found during that search
+ * @W1_CMD_ALARM_SEARCH: search for devices that are currently alarming
+ * @W1_CMD_TOUCH: Touches a series of bytes.
+ * @W1_CMD_RESET: sends a bus reset on the given master
+ * @W1_CMD_SLAVE_ADD: adds a slave to the given master,
+ * 8 byte slave id at data[0]
+ * @W1_CMD_SLAVE_REMOVE: removes a slave to the given master,
+ * 8 byte slave id at data[0]
+ * @W1_CMD_LIST_SLAVES: list of slaves registered on this master
+ * @W1_CMD_MAX: number of available commands
+ */
enum w1_commands {
W1_CMD_READ = 0,
W1_CMD_WRITE,
@@ -59,7 +87,10 @@ enum w1_commands {
W1_CMD_ALARM_SEARCH,
W1_CMD_TOUCH,
W1_CMD_RESET,
- W1_CMD_MAX,
+ W1_CMD_SLAVE_ADD,
+ W1_CMD_SLAVE_REMOVE,
+ W1_CMD_LIST_SLAVES,
+ W1_CMD_MAX
};
struct w1_netlink_cmd