blob: 4d76bfb5e182f8d7d1a0403924f8c477fd040767 [file] [log] [blame]
/**
********************************************************************************
* @file smsc-82514-usb-hub.c
* @brief SMSC USB82514 Automotive Grade USB 2.0 Hi-Speed 4-Port Hub
*
* Copyright (C) 2012 Parrot S.A.
*
* @author Christian ROSALIE <christian.rosalie@parrot.com>
* @date 2012-08-13
********************************************************************************
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include "smsc-82514-usb-hub.h"
#define SMSC_USB82514_DRV_NAME "smsc82514"
#define SMSC_USB82514_DRV_VERSION 0x1
/* Registers */
#define SMSC82514_VENDOR_ID_LSB 0x0 /* Vendor ID (LSB) */
#define SMSC82514_VENDOR_ID_MSB 0x1 /* Vendor ID (MSB) */
#define SMSC82514_PRODUCT_ID_LSB 0x2 /* Product ID (LSB) */
#define SMSC82514_PRODUCT_ID_MSB 0x3 /* Product ID (MSB) */
#define SMSC82514_DEVICE_ID_LSB 0x4 /* Device ID (LSB) */
#define SMSC82514_DEVICE_ID_MSB 0x5 /* Device ID (MSB) */
#define SMSC82514_CFG_DATA_BYTE1 0x6 /* Configuration Data Byte 1 */
#define SMSC82514_CFG_DATA_BYTE2 0x7 /* Configuration Data Byte 2 */
#define SMSC82514_CFG_DATA_BYTE3 0x8 /* Configuration Data Byte 3 */
#define SMSC82514_N_REMOV_DEV 0x9 /* Non-Removable Device */
#define SMSC82514_PORT_DIS_SELF_PWDED 0xa /* Port Disable for Self-Powered Operation */
#define SMSC82514_PORT_DIS_BUS_PWDED 0xb /* Port Disable for Bus-Powered Operation */
#define SMSC82514_MAX_PWR_SELF_PWDED 0x0c /* Max Power for Self-Powered Operation */
#define SMSC82514_MAX_PWR_BUS_PWDED 0x0d /* Max Power for Bus-Powered Operation */
#define SMSC82514_HUB_CTRL_MAX_CUR_SELF_PWDED 0x0E /* Hub Controller Max Current for Self-Powered Operation */
#define SMSC82514_HUB_CTRL_MAX_CUR_BUS_PWDED 0x0f /* Hub Controller Max Current for Bus-Powered Operation */
#define SMSC82514_POWER_ON_TIME 0x10 /* Power-on Time 32h */
#define SMSC82514_LANGUAGE_ID_HIGH 0x11 /* Language ID High */
#define SMSC82514_LANGUAGE_ID_LOW 0x12 /* Language ID Low */
#define SMSC82514_MANUFACTURER_STR_LENGTH 0x13
#define SMSC82514_PRODUCT_STR_LENGTH 0x14
#define SMSC82514_SERIAL_STR_LENGTH 0x15
#define SMSC82514_MANUFACTURER_STR 0x16 /* 16h-53h Manufacturer String 00h */
#define SMSC82514_PRODUCT_STR 0x54 /* 54h-91h Product String 00h */
#define SMSC82514_SERIAL_STR 0x92 /* 92h-Cfh Serial String 00h */
#define SMSC82514_BOOST_UP 0xf6
#define SMSC82514_BOOST_4_0 0xf8
/* Reserved addresses
0xf7 Reserved
0xd0-0xf5 Reserved
0xf9 Reserved
0xfd Reserved
*/
#define SMSC82514_RESERVED1 0xf9
#define SMSC82514_RESERVED2 0xfd
#define SMSC82514_PORTSWAP 0xfa
#define SMSC82514_PORTMAP_12 0xfb
#define SMSC82514_PORTMAP_34 0xfc
#define SMSC82514_STATUS_COMMAND 0xff
struct smsc82514_data {
struct smsc82514_pdata ds_data;
};
/* rom default */
static u8 smsc82514_init_seq[][2] = {
{SMSC82514_VENDOR_ID_LSB, 0x24},
{SMSC82514_VENDOR_ID_MSB, 0x04},
{SMSC82514_PRODUCT_ID_LSB, 0x14},/* depends of chip ... */
{SMSC82514_PRODUCT_ID_MSB, 0x25},
{SMSC82514_DEVICE_ID_LSB, 0xA0},
{SMSC82514_DEVICE_ID_MSB, 0x80},
{SMSC82514_CFG_DATA_BYTE1, 0x9B},
{SMSC82514_CFG_DATA_BYTE2, 0x20},
{SMSC82514_CFG_DATA_BYTE3, 0x02},
{SMSC82514_N_REMOV_DEV, 0x00},
{SMSC82514_PORT_DIS_SELF_PWDED, 0x00},
{SMSC82514_PORT_DIS_BUS_PWDED, 0x00},
{SMSC82514_MAX_PWR_SELF_PWDED, 0x01},
{SMSC82514_MAX_PWR_BUS_PWDED, 0x32},
{SMSC82514_HUB_CTRL_MAX_CUR_SELF_PWDED, 0x01},
{SMSC82514_HUB_CTRL_MAX_CUR_BUS_PWDED, 0x32},
{SMSC82514_POWER_ON_TIME, 0x32},
};
#define SMSC82514_INIT_SEQ_SIZE ARRAY_SIZE(smsc82514_init_seq)
static int smsc82514_init_client(struct i2c_client *client)
{
struct smsc82514_data *data = i2c_get_clientdata(client);
u8 value;
int ret;
int i;
//reset hub
if( data->ds_data.reset_pin ){
gpio_set_value(data->ds_data.reset_pin, 0);
/* check if hub reply to i2c */
ret = i2c_smbus_write_block_data(client, smsc82514_init_seq[0][0],
1, &smsc82514_init_seq[0][1]);
if (ret == 0) {
dev_err(&client->dev, " Reset failed: "
"the reset i/o is not correctly driven\n");
return -EIO;
}
mdelay(20);
gpio_set_value(data->ds_data.reset_pin, 1);
}
else {
value = 0x02; /* hub reset */
ret = i2c_smbus_write_block_data(client, SMSC82514_STATUS_COMMAND,
1, &value);
if (ret < 0) {
dev_err(&client->dev, " i2c transfer failed\n\
Unable to reset usb hub\n");
return -EIO;
}
}
mdelay(10);
/* set rom default value (otherwise everything is 0 ...) */
for( i = 0 ; i < SMSC82514_INIT_SEQ_SIZE ; i++ ){
ret = i2c_smbus_write_block_data(client, smsc82514_init_seq[i][0],
1, &smsc82514_init_seq[i][1]);
if (ret < 0) {
dev_err(&client->dev, "%s: i2c transfer failed\n\
Unable to write (0x%x) register\n",
__func__, smsc82514_init_seq[i][0] );
return -EIO;
}
}
// Apply USB husb boost
// Boost upstream usb hub port if necessary
value = data->ds_data.us_port & 0x3;
if (value) {
ret = i2c_smbus_write_block_data(client, SMSC82514_BOOST_UP,
1, &value);
if (ret < 0) {
dev_err(&client->dev, " i2c transfer failed\n\
Unable apply hub boost (value == 0x%x)\n",
value);
return -EIO;
}
}
//Boost downstream usb hub port if necessary
value = data->ds_data.ds_port_1
| (data->ds_data.ds_port_2 << 2)
| (data->ds_data.ds_port_3 << 4)
| (data->ds_data.ds_port_4 << 6);
if (value) {
ret = i2c_smbus_write_block_data(client, SMSC82514_BOOST_4_0,
1, &value);
if (ret < 0) {
dev_err(&client->dev, " i2c transfer failed\n\
Unable apply hub boost (value == 0x%x)\n",
value);
return -EIO;
}
}
//Start hub
//Once started, registers are protected
//The hub must be reset to update configuration
value = 0x01;
ret = i2c_smbus_write_block_data(client, SMSC82514_STATUS_COMMAND,
1, &value);
if (ret < 0) {
dev_err(&client->dev, " i2c transfer failed\n\
Unable to start usb hub\n");
return -EIO;
}
return 0;
}
static int smsc82514_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct smsc82514_data *data = NULL;
struct smsc82514_pdata *pds_data;
int ret = 0;
pr_info(SMSC_USB82514_DRV_NAME ": version %d\n", SMSC_USB82514_DRV_VERSION );
data = kzalloc(sizeof(struct smsc82514_data), GFP_KERNEL);
if (!data) {
pr_err(SMSC_USB82514_DRV_NAME ": Memory allocation failed\n" );
ret = -ENOMEM;
goto exit;
}
if( client->dev.platform_data ){
pds_data = (struct smsc82514_pdata *) client->dev.platform_data;
if( pds_data ) /* copy drive stregth settings */
memcpy(&data->ds_data, pds_data, sizeof(struct smsc82514_pdata));
else /* init with the default value */
memset(&data->ds_data, 0x0, sizeof(struct smsc82514_pdata));
}
i2c_set_clientdata(client, data);
/* Initialize chip */
ret = smsc82514_init_client(client);
exit:
return ret;
}
static int __devexit smsc82514_remove(struct i2c_client *client)
{
struct smsc82514_data *data = i2c_get_clientdata(client);
kfree(data);
return 0;
}
static int smsc82514_suspend(struct i2c_client *client, pm_message_t mesg)
{
return 0;
}
static int smsc82514_resume(struct i2c_client *client)
{
int ret;
ret = smsc82514_init_client(client);
if (ret < 0)
pr_err(SMSC_USB82514_DRV_NAME ": resume failure %d\n", ret);
return ret;
}
static const struct i2c_device_id smsc82514_i2c_table[] = {
{"smsc82514", 0}, /* 4 ports */
{"smsc82512", 0}, /* 2 ports */
{}
};
MODULE_DEVICE_TABLE(i2c, smsc82514_i2c_table);
static struct i2c_driver smsc82514_i2c_driver = {
.driver = {
.name = SMSC_USB82514_DRV_NAME,
.owner = THIS_MODULE,
},
.probe = smsc82514_probe,
.remove = __devexit_p(smsc82514_remove),
.suspend = smsc82514_suspend,
.resume = smsc82514_resume,
.id_table = smsc82514_i2c_table,
};
static int __init smsc82514_modinit(void)
{
int ret = 0;
ret = i2c_add_driver(&smsc82514_i2c_driver);
if (ret != 0) {
pr_err("Failed to register SMSC USB82514 driver: %d\n", ret);
}
return ret;
}
static void __exit smsc82514_modexit(void)
{
i2c_del_driver(&smsc82514_i2c_driver);
}
subsys_initcall(smsc82514_modinit);
module_exit(smsc82514_modexit);
MODULE_AUTHOR("PARROT SA by Christian ROSALIE <christian.rosalie@parrot.com>");
MODULE_DESCRIPTION("SMSC USB82514 USB 2.0 Hub");
MODULE_LICENSE("GPL");