[PATCH 1/1] Add driver for smsc usb251x i2c configuration
From: Fabien Lahoudere
Date: Tue Aug 02 2016 - 10:33:11 EST
This driver copy the configuration of the controller EEPROM via i2c.
Configuration information is available in Documentation/usb/usb251x.txt
Signed-off-by: Fabien Lahoudere <fabien.lahoudere@xxxxxxxxxxxxxxx>
---
Documentation/devicetree/bindings/usb/usb251x.txt | 27 +++
drivers/usb/misc/Kconfig | 9 +
drivers/usb/misc/Makefile | 1 +
drivers/usb/misc/usb251x.c | 265 ++++++++++++++++++++++
4 files changed, 302 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/usb251x.txt
create mode 100644 drivers/usb/misc/usb251x.c
diff --git a/Documentation/devicetree/bindings/usb/usb251x.txt b/Documentation/devicetree/bindings/usb/usb251x.txt
new file mode 100644
index 0000000..2b0863a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb251x.txt
@@ -0,0 +1,27 @@
+SMSC USB 2.0 Hi-Speed Hub Controller
+
+Required properties:
+- compatible = "smsc,usb251x";
+- reg = <0x2c>;
+
+Optional properties:
+- smsc,usb251x-cfg-data1 : u8, set configuration data 1 (byte 0x06)
+- smsc,usb251x-cfg-data2 : u8, set configuration data 2 (byte 0x07)
+- smsc,usb251x-cfg-data3 : u8, set configuration data 3 (byte 0x08)
+- smsc,usb251x-portmap12 : u8, set port mapping for ports 1 and 2 (byte 0xfb)
+- smsc,usb251x-portmap34 : u8, set port mapping for ports 3 and 4 (byte 0xfc)
+- smsc,usb251x-portmap56 : u8, set port mapping for ports 5 and 6 (byte 0xfd)
+- smsc,usb251x-portmap7 : u8, set port mapping for port 7 (byte 0xfe)
+- smsc,usb251x-status-command : u8, configure smbus behaviour (byte 0xff)
+
+Example:
+
+ usb251x: usb251x@2c {
+ compatible = "smsc,usb251x";
+ reg = <0x2c>;
+ smsc,usb251x-cfg-data3 = <0x09>;
+ smsc,usb251x-portmap12 = <0x21>;
+ smsc,usb251x-portmap12 = <0x43>;
+ smsc,usb251x-status-command = <0x1>;
+ };
+
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index eb8f8d3..89c532f 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -240,6 +240,15 @@ config USB_HSIC_USB3503
help
This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver.
+config USB_USB251X
+ tristate "USB251X device configurable via I2C"
+ depends on I2C
+ help
+ This option enables support for configuring SMSC USB251X USB hub.
+ This driver write the hub configuration in EEPROM via i2C. Fields can be
+ configured through device tree.
+ See Documentation/devicetree/bindings/usb/usb251x.txt
+
config USB_LINK_LAYER_TEST
tristate "USB Link Layer Test driver"
help
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 3d79faa..dac251a 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -27,5 +27,6 @@ obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o
obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o
obj-$(CONFIG_UCSI) += ucsi.o
+obj-$(CONFIG_USB_USB251X) += usb251x.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o
diff --git a/drivers/usb/misc/usb251x.c b/drivers/usb/misc/usb251x.c
new file mode 100644
index 0000000..b3750fc
--- /dev/null
+++ b/drivers/usb/misc/usb251x.c
@@ -0,0 +1,265 @@
+/*
+ * drivers/usb/misc/usb251x.c
+ *
+ * driver for SMSC USB251X USB Hub
+ *
+ * Authors: Rick Bronson <rick@xxxxxxx>
+ * Fabien Lahoudere <fabien.lahoudere@xxxxxxxxxxxxxxx>
+ *
+ * Register initialization is based on code examples provided by Philips
+ * Copyright (c) 2005 Koninklijke Philips Electronics N.V.
+ *
+ * This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/of_device.h>
+#include <linux/types.h>
+
+/* registers */
+#define USB251X_VENDOR_ID_LSB 0x00
+#define USB251X_VENDOR_ID_MSB 0x01
+#define USB251X_PRODUCT_ID_LSB 0x02
+#define USB251X_PRODUCT_ID_MSB 0x03
+#define USB251X_DEVICE_ID_LSB 0x04
+#define USB251X_DEVICE_ID_MSB 0x05
+#define USB251X_CONFIGURATION_DATA_BYTE_1 0x06
+#define USB251X_CONFIGURATION_DATA_BYTE_2 0x07
+#define USB251X_CONFIGURATION_DATA_BYTE_3 0x08
+#define USB251X_NON_REMOVABLE_DEVICES 0x09
+#define USB251X_PORT_DISABLE_SELF 0x0A
+#define USB251X_PORT_DISABLE_BUS 0x0B
+#define USB251X_MAX_POWER_SELF 0x0C
+#define USB251X_MAX_POWER_BUS 0x0D
+#define USB251X_HUB_CONTROLLER_MAX_CURRENT_SELF 0x0E
+#define USB251X_HUB_CONTROLLER_MAX_CURRENT_BUS 0x0F
+#define USB251X_POWER_ON_TIME 0x10
+#define USB251X_LANGUAGE_ID_HIGH 0x11
+#define USB251X_LANGUAGE_ID_LOW 0x12
+#define USB251X_MANUFACTURER_STRING_LENGTH 0x13
+#define USB251X_PRODUCT_STRING_LENGTH 0x14
+#define USB251X_SERIAL_STRING_LENGTH 0x15
+#define USB251X_MANUFACTURER_STRING 0x16
+#define USB251X_PRODUCT_STRING 0x54
+#define USB251X_SERIAL_STRING 0x92
+#define USB251X_BATTERY_CHARGING_ENABLE 0xD0
+#define USB251X_BOOST_UP 0xF6
+#define USB251X_BOOST_X 0xF8
+#define USB251X_PORT_SWAP 0xFA
+#define USB251X_PORT_MAP_12 0xFB
+#define USB251X_PORT_MAP_34 0xFC
+#define USB251X_PORT_MAP_56 0xFD
+#define USB251X_PORT_MAP_7 0xFE
+#define USB251X_STATUS_COMMAND 0xFF
+
+#define USB251X_ADDR_SZ 256 /* address space of device */
+#define USB251X_I2C_WRITE_SIZE 16
+#define USB251X_I2C_NAME "usb251x"
+
+/* The platform data */
+struct usb251x_platform_data {
+ unsigned char *init_table; /* table to init part */
+};
+
+#define DRIVER_DESC "SMSC USB 2.0 Hi-Speed Hub Controller"
+
+static unsigned char default_init_table[USB251X_ADDR_SZ] = {
+ 0x24, 0x04, 0x14, 0x25, 0xa0, 0x0b, 0x9b, 0x20, /* 00 - 07 */
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x32, 0x01, 0x32, /* 08 - 0F */
+ 0x32, 0x00, 0x00, 4, 30, 0x00, 'S', 0x00, /* 10 - 17 */
+ 'M', 0x00, 'S', 0x00, 'C', 0x00, 0x00, 0x00, /* 18 - 1F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 20 - 27 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28 - 2F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 - 37 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38 - 3F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 40 - 47 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 48 - 4F */
+ 0x00, 0x00, 0x00, 0x00, 'U', 0x00, 'S', 0x00, /* 50 - 57 */
+ 'B', 0x00, ' ', 0x00, '2', 0x00, '.', 0x00, /* 58 - 5F */
+ '0', 0x00, ' ', 0x00, 'H', 0x00, 'i', 0x00, /* 60 - 67 */
+ '-', 0x00, 'S', 0x00, 'p', 0x00, 'e', 0x00, /* 68 - 6F */
+ 'e', 0x00, 'd', 0x00, ' ', 0x00, 'H', 0x00, /* 70 - 77 */
+ 'u', 0x00, 'b', 0x00, ' ', 0x00, 'C', 0x00, /* 78 - 7F */
+ 'o', 0x00, 'n', 0x00, 't', 0x00, 'r', 0x00, /* 80 - 87 */
+ 'o', 0x00, 'l', 0x00, 'l', 0x00, 'e', 0x00, /* 88 - 8F */
+ 'r', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 90 - 97 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 98 - 9F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* A0 - A7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* A8 - AF */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* B0 - B7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* B8 - BF */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* C0 - C7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* C8 - CF */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* D0 - D7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* D8 - DF */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* E0 - E7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* E8 - EF */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* F0 - F7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* F8 - FF */
+};
+
+/* USB251X only supports i2c block writes 16+1 bytes at a time */
+static int usb251x_configure(struct i2c_client *client,
+ unsigned char *init_table)
+{
+ int cntr, ret = 0;
+ unsigned char i2cwritebuffer[USB251X_I2C_WRITE_SIZE + 1];
+
+ for (cntr = 0; cntr < USB251X_ADDR_SZ / USB251X_I2C_WRITE_SIZE; cntr++) {
+ i2cwritebuffer[0] = USB251X_I2C_WRITE_SIZE;
+ memcpy(&i2cwritebuffer[1],
+ &init_table[cntr * USB251X_I2C_WRITE_SIZE],
+ USB251X_I2C_WRITE_SIZE);
+ ret =
+ i2c_smbus_write_i2c_block_data(client,
+ cntr *
+ USB251X_I2C_WRITE_SIZE,
+ USB251X_I2C_WRITE_SIZE + 1,
+ &i2cwritebuffer[0]);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writings to 0x%02x\n",
+ cntr * USB251X_I2C_WRITE_SIZE);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static void usb251x_set_config_from_of(const struct device_node *node,
+ unsigned char *table,
+ const char *pname, u8 offset)
+{
+ int ret;
+ unsigned char value;
+
+ ret = of_property_read_u8(node, pname, &value);
+ if (ret == 0)
+ table[offset] = value;
+}
+
+static int usb251x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct usb251x_platform_data *pdata = client->dev.platform_data;
+ struct device *dev = &client->dev;
+
+ dev_info(dev, DRIVER_DESC " " USB251X_I2C_NAME "\n");
+ if (usb_disabled()) {
+ dev_err(dev, "USB is required to be enabled\n.");
+ return -ENODEV;
+ }
+
+ if (dev->of_node) {
+ if (!pdata) {
+ pdata =
+ kmalloc(sizeof(struct usb251x_platform_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ }
+ if (!pdata->init_table) {
+ pdata->init_table =
+ kmalloc(sizeof(unsigned char) * USB251X_ADDR_SZ,
+ GFP_KERNEL);
+ if (!pdata->init_table) {
+ kfree(pdata);
+ return -ENOMEM;
+ }
+ }
+ memcpy(pdata->init_table, default_init_table, USB251X_ADDR_SZ);
+
+ /* set configuration data 1 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-cfg-data1",
+ USB251X_CONFIGURATION_DATA_BYTE_1);
+ /* set configuration data 2 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-cfg-data2",
+ USB251X_CONFIGURATION_DATA_BYTE_2);
+ /* set configuration data 3 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-cfg-data3",
+ USB251X_CONFIGURATION_DATA_BYTE_3);
+ /* set port mapping for ports 1 and 2 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-portmap12",
+ USB251X_PORT_MAP_12);
+ /* set port mapping for ports 3 and 4 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-portmap34",
+ USB251X_PORT_MAP_34);
+ /* set port mapping for ports 5 and 6 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-portmap56",
+ USB251X_PORT_MAP_56);
+ /* set port mapping for port 7 */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-portmap7",
+ USB251X_PORT_MAP_7);
+ /* set SMBus configuration */
+ usb251x_set_config_from_of(dev->of_node,
+ pdata->init_table,
+ "smsc,usb251x-status-command",
+ USB251X_STATUS_COMMAND);
+ } else {
+ dev_err(dev, "initialization data required.\n");
+ return -EINVAL;
+ }
+
+ return usb251x_configure(client, pdata->init_table);
+}
+
+static int usb251x_resume(struct device *dev)
+{
+ const struct usb251x_platform_data *pdata = dev->platform_data;
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return usb251x_configure(client, pdata->init_table);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id usb251x_dt_ids[] = {
+ {.compatible = "smsc,usb251x",},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, usb251x_dt_ids);
+#endif
+
+static const struct i2c_device_id usb251x_id[] = {
+ {USB251X_I2C_NAME, 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, usb251x_id);
+
+static const struct dev_pm_ops usb251x_pm_ops = {
+ .resume = usb251x_resume,
+};
+
+static struct i2c_driver usb251x_driver = {
+ .driver = {
+ .name = USB251X_I2C_NAME,
+ .pm = &usb251x_pm_ops,
+ },
+ .probe = usb251x_probe,
+ .id_table = usb251x_id,
+};
+
+module_i2c_driver(usb251x_driver);
+MODULE_LICENSE("GPL");
--
2.7.4