diff options
Diffstat (limited to 'drivers')
78 files changed, 11065 insertions, 2154 deletions
diff --git a/drivers/ata/ahci-tegra.c b/drivers/ata/ahci-tegra.c index c90472827b9b..32bf46940b0f 100644 --- a/drivers/ata/ahci-tegra.c +++ b/drivers/ata/ahci-tegra.c @@ -217,6 +217,7 @@ enum sata_state { }; char *sata_power_rails[] = { + "avdd_plle", "avdd_sata", "vdd_sata", "hvdd_sata", diff --git a/drivers/hwmon/lm95245.c b/drivers/hwmon/lm95245.c index dce9e68241e6..1b60fe6de776 100644 --- a/drivers/hwmon/lm95245.c +++ b/drivers/hwmon/lm95245.c @@ -34,6 +34,8 @@ #include <linux/mutex.h> #include <linux/sysfs.h> +#include <linux/lm95245.h> + #define DEVNAME "lm95245" static const unsigned short normal_i2c[] = { @@ -93,6 +95,7 @@ static const unsigned short normal_i2c[] = { #define RATE_CR1000 0x02 #define RATE_CR2500 0x03 +#define STATUS1_ROS 0x10 #define STATUS1_DIODE_FAULT 0x04 #define STATUS1_RTCRIT 0x02 #define STATUS1_LOC 0x01 @@ -107,12 +110,32 @@ static const u8 lm95245_reg_address[] = { LM95245_REG_R_REMOTE_TEMPL_S, LM95245_REG_R_REMOTE_TEMPH_U, LM95245_REG_R_REMOTE_TEMPL_U, + LM95245_REG_RW_REMOTE_OS_LIMIT, LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT, LM95245_REG_RW_REMOTE_TCRIT_LIMIT, LM95245_REG_RW_COMMON_HYSTERESIS, LM95245_REG_R_STATUS1, }; +/* Indices and offsets into above register array */ + +enum { + INDEX_LOCAL_TEMP = 0, + INDEX_REMOTE_TEMP = 2, + INDEX_REMOTE_OS_LIMIT = 6, + INDEX_LOCAL_OS_TCRIT_LIMIT, + INDEX_REMOTE_TCRIT_LIMIT, + INDEX_COMMON_HYSTERESIS, + INDEX_STATUS1, +}; + +enum { + OFFSET_HIGH_SIGNED = 0, + OFFSET_LOW_SIGNED, + OFFSET_HIGH_UNSIGNED, + OFFSET_LOW_UNSIGNED, +}; + /* Client data (each client gets its own) */ struct lm95245_data { struct device *hwmon_dev; @@ -214,24 +237,42 @@ static unsigned long lm95245_set_conversion_rate(struct i2c_client *client, } /* Sysfs stuff */ -static ssize_t show_input(struct device *dev, struct device_attribute *attr, - char *buf) +void thermal_get_temp(struct device *dev, int *temp, int index) { struct lm95245_data *data = lm95245_update_device(dev); - int temp; - int index = to_sensor_dev_attr(attr)->index; /* - * Index 0 (Local temp) is always signed - * Index 2 (Remote temp) has both signed and unsigned data + * Local temp is always signed + * Remote temp has both signed and unsigned data * use signed calculation for remote if signed bit is set */ - if (index == 0 || data->regs[index] & 0x80) - temp = temp_from_reg_signed(data->regs[index], - data->regs[index + 1]); + if (index == INDEX_LOCAL_TEMP || data->regs[index + OFFSET_HIGH_SIGNED] & 0x80) + *temp = temp_from_reg_signed(data->regs[index + OFFSET_HIGH_SIGNED], + data->regs[index + OFFSET_LOW_SIGNED]); else - temp = temp_from_reg_unsigned(data->regs[index + 2], - data->regs[index + 3]); + *temp = temp_from_reg_unsigned(data->regs[index + OFFSET_HIGH_UNSIGNED], + data->regs[index + OFFSET_LOW_UNSIGNED]); +} + +void lm95245_get_local_temp(struct device *dev, int *temp) +{ + thermal_get_temp(dev, temp, INDEX_LOCAL_TEMP); +} +EXPORT_SYMBOL(lm95245_get_local_temp); + +void lm95245_get_remote_temp(struct device *dev, int *temp) +{ + thermal_get_temp(dev, temp, INDEX_REMOTE_TEMP); +} +EXPORT_SYMBOL(lm95245_get_remote_temp); + +static ssize_t show_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int temp = 0; + int index = to_sensor_dev_attr(attr)->index; + + thermal_get_temp(dev, &temp, index); return snprintf(buf, PAGE_SIZE - 1, "%d\n", temp); } @@ -246,20 +287,14 @@ static ssize_t show_limit(struct device *dev, struct device_attribute *attr, data->regs[index] * 1000); } -static ssize_t set_limit(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +void thermal_set_limit(struct device *dev, int val, int index) { struct i2c_client *client = to_i2c_client(dev); struct lm95245_data *data = i2c_get_clientdata(client); - int index = to_sensor_dev_attr(attr)->index; - unsigned long val; - - if (strict_strtoul(buf, 10, &val) < 0) - return -EINVAL; val /= 1000; - val = SENSORS_LIMIT(val, 0, (index == 6 ? 127 : 255)); + val = SENSORS_LIMIT(val, 0, (index == INDEX_LOCAL_OS_TCRIT_LIMIT ? 127 : 255)); mutex_lock(&data->update_lock); @@ -268,6 +303,36 @@ static ssize_t set_limit(struct device *dev, struct device_attribute *attr, i2c_smbus_write_byte_data(client, lm95245_reg_address[index], val); mutex_unlock(&data->update_lock); +} + +void lm95245_set_remote_os_limit(struct device *dev, int val) +{ + thermal_set_limit(dev, val, INDEX_REMOTE_OS_LIMIT); +} +EXPORT_SYMBOL(lm95245_set_remote_os_limit); + +void lm95245_set_remote_critical_limit(struct device *dev, int val) +{ + thermal_set_limit(dev, val, INDEX_REMOTE_TCRIT_LIMIT); +} +EXPORT_SYMBOL(lm95245_set_remote_critical_limit); + +void lm95245_set_local_shared_os__critical_limit(struct device *dev, int val) +{ + thermal_set_limit(dev, val, INDEX_LOCAL_OS_TCRIT_LIMIT); +} +EXPORT_SYMBOL(lm95245_set_local_shared_os__critical_limit); + +static ssize_t set_limit(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + unsigned long val; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + + thermal_set_limit(dev, (int)val, index); return count; } @@ -345,7 +410,7 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, int index = to_sensor_dev_attr(attr)->index; return snprintf(buf, PAGE_SIZE - 1, "%d\n", - !!(data->regs[9] & index)); + !!(data->regs[INDEX_STATUS1] & index)); } static ssize_t show_interval(struct device *dev, struct device_attribute *attr, @@ -375,19 +440,23 @@ static ssize_t set_interval(struct device *dev, struct device_attribute *attr, return count; } -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, INDEX_LOCAL_TEMP); static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_limit, - set_limit, 6); + set_limit, INDEX_LOCAL_OS_TCRIT_LIMIT); static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_limit, - set_crit_hyst, 8); + set_crit_hyst, INDEX_COMMON_HYSTERESIS); static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, STATUS1_LOC); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, INDEX_REMOTE_TEMP); +static SENSOR_DEVICE_ATTR(temp2_os, S_IWUSR | S_IRUGO, show_limit, + set_limit, INDEX_REMOTE_OS_LIMIT); static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_limit, - set_limit, 7); + set_limit, INDEX_REMOTE_TCRIT_LIMIT); static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IWUSR | S_IRUGO, show_limit, - set_crit_hyst, 8); + set_crit_hyst, INDEX_COMMON_HYSTERESIS); +static SENSOR_DEVICE_ATTR(temp2_os_alarm, S_IRUGO, show_alarm, NULL, + STATUS1_ROS); static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, STATUS1_RTCRIT); static SENSOR_DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type, @@ -404,8 +473,10 @@ static struct attribute *lm95245_attributes[] = { &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_os.dev_attr.attr, &sensor_dev_attr_temp2_crit.dev_attr.attr, &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_os_alarm.dev_attr.attr, &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp2_type.dev_attr.attr, &sensor_dev_attr_temp2_fault.dev_attr.attr, @@ -454,6 +525,13 @@ static void lm95245_init_client(struct i2c_client *client) i2c_smbus_write_byte_data(client, LM95245_REG_RW_CONFIG1, data->config1); } + + /* Configure over-temperature shutdown (OS) output pin */ + if (client->dev.platform_data && ((struct lm95245_platform_data*)(client->dev.platform_data))->enable_os_pin) { + data->config2 |= CFG2_OS_A0; + i2c_smbus_write_byte_data(client, LM95245_REG_RW_CONFIG2, + data->config2); + } } static int lm95245_probe(struct i2c_client *new_client, @@ -485,6 +563,10 @@ static int lm95245_probe(struct i2c_client *new_client, goto exit_remove_files; } + /* Notify callback that probe is done */ + if (new_client->dev.platform_data && ((struct lm95245_platform_data*)(new_client->dev.platform_data))->probe_callback) + ((struct lm95245_platform_data*)(new_client->dev.platform_data))->probe_callback(&new_client->dev); + return 0; exit_remove_files: diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 1def6f105d6b..ec1bd9009288 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -790,7 +790,7 @@ static int gpio_keys_suspend(struct device *dev) for (i = 0; i < ddata->n_buttons; i++) { struct gpio_button_data *bdata = &ddata->data[i]; if (bdata->button->wakeup) - enable_irq_wake(bdata->irq); + WARN_ON(enable_irq_wake(bdata->irq)); } } diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index a8fc74f730dd..936f1e71a21f 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -770,4 +770,11 @@ config TOUCHSCREEN_SYN_RMI4_SPI To compile this driver as a module, choose M here: the module will be called rmi-spi. +config TOUCHSCREEN_FUSION_F0710A + tristate "TouchRevolution Fusion F0710A Touchscreens" + depends on I2C + help + Say Y here if you want to support the multi-touch input driver for + the TouchRevolution Fusion 7 and 10 panels. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 9c42d2100b5c..2fa571b65088 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -64,3 +64,4 @@ obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_RM31080A) += rm31080a_ts.o obj-$(CONFIG_TOUCHSCREEN_SYN_RMI4_SPI) += rmi4/ +obj-$(CONFIG_TOUCHSCREEN_FUSION_F0710A) += fusion_F0710A.o diff --git a/drivers/input/touchscreen/fusion_F0710A.c b/drivers/input/touchscreen/fusion_F0710A.c new file mode 100644 index 000000000000..c5c0f1fc38ea --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.c @@ -0,0 +1,524 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * 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/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/irq.h> +#include <linux/gpio.h> +#include <linux/input/fusion_F0710A.h> + +#include "fusion_F0710A.h" + +#define DRV_NAME "fusion_F0710A" + + +static struct fusion_F0710A_data fusion_F0710A; + +static unsigned short normal_i2c[] = { fusion_F0710A_I2C_SLAVE_ADDR, I2C_CLIENT_END }; + +//I2C_CLIENT_INSMOD; + +static int fusion_F0710A_write_u8(u8 addr, u8 data) +{ + return i2c_smbus_write_byte_data(fusion_F0710A.client, addr, data); +} + +static int fusion_F0710A_read_u8(u8 addr) +{ + return i2c_smbus_read_byte_data(fusion_F0710A.client, addr); +} + +static int fusion_F0710A_read_block(u8 addr, u8 len, u8 *data) +{ +#if 0 + /* When i2c_smbus_read_i2c_block_data() takes a block length parameter, we can do + * this. lm-sensors lists hints this has been fixed, but I can't tell whether it + * was or will be merged upstream. */ + + return i2c_smbus_read_i2c_block_data(&fusion_F0710A.client, addr, data); +#else + u8 msgbuf0[1] = { addr }; + u16 slave = fusion_F0710A.client->addr; + u16 flags = fusion_F0710A.client->flags; + struct i2c_msg msg[2] = { { slave, flags, 1, msgbuf0 }, + { slave, flags | I2C_M_RD, len, data } + }; + + return i2c_transfer(fusion_F0710A.client->adapter, msg, ARRAY_SIZE(msg)); +#endif +} + + +static int fusion_F0710A_register_input(void) +{ + int ret; + struct input_dev *dev; + + dev = fusion_F0710A.input = input_allocate_device(); + if (dev == NULL) + return -ENOMEM; + + dev->name = "fusion_F0710A"; + + set_bit(EV_KEY, dev->evbit); + set_bit(EV_ABS, dev->evbit); + + input_set_abs_params(dev, ABS_MT_POSITION_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, fusion_F0710A.info.yres-1, 0, 0); +#ifdef CONFIG_ANDROID + input_set_abs_params(dev, ABS_MT_TRACKING_ID, 0, 15, 0, 0); +#else + input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0); +#endif + + input_set_abs_params(dev, ABS_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, fusion_F0710A.info.yres-1, 0, 0); + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + ret = input_register_device(dev); + if (ret < 0) + goto bail1; + + return 0; + +bail1: + input_free_device(dev); + return ret; +} + +#define WC_RETRY_COUNT 3 +static int fusion_F0710A_write_complete(void) +{ + int ret, i; + + for(i=0; i<WC_RETRY_COUNT; i++) + { + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if(ret == 0) + break; + else + dev_err(&fusion_F0710A.client->dev, "Write complete failed(%d): %d\n", i, ret); + } + + return ret; +} + +#define DATA_START fusion_F0710A_DATA_INFO +#define DATA_END fusion_F0710A_SEC_TIDTS +#define DATA_LEN (DATA_END - DATA_START + 1) +#define DATA_OFF(x) ((x) - DATA_START) + +static int fusion_F0710A_read_sensor(void) +{ + int ret; + u8 data[DATA_LEN]; + +#define DATA(x) (data[DATA_OFF(x)]) + /* To ensure data coherency, read the sensor with a single transaction. */ + ret = fusion_F0710A_read_block(DATA_START, DATA_LEN, data); + if (ret < 0) { + dev_err(&fusion_F0710A.client->dev, + "Read block failed: %d\n", ret); + + return ret; + } + + fusion_F0710A.f_num = DATA(fusion_F0710A_DATA_INFO)&0x03; + + fusion_F0710A.y1 = DATA(fusion_F0710A_POS_X1_HI) << 8; + fusion_F0710A.y1 |= DATA(fusion_F0710A_POS_X1_LO); + fusion_F0710A.x1 = DATA(fusion_F0710A_POS_Y1_HI) << 8; + fusion_F0710A.x1 |= DATA(fusion_F0710A_POS_Y1_LO); + fusion_F0710A.z1 = DATA(fusion_F0710A_FIR_PRESS); + fusion_F0710A.tip1 = DATA(fusion_F0710A_FIR_TIDTS)&0x0f; + fusion_F0710A.tid1 = (DATA(fusion_F0710A_FIR_TIDTS)&0xf0)>>4; + + + fusion_F0710A.y2 = DATA(fusion_F0710A_POS_X2_HI) << 8; + fusion_F0710A.y2 |= DATA(fusion_F0710A_POS_X2_LO); + fusion_F0710A.x2 = DATA(fusion_F0710A_POS_Y2_HI) << 8; + fusion_F0710A.x2 |= DATA(fusion_F0710A_POS_Y2_LO); + fusion_F0710A.z2 = DATA(fusion_F0710A_SEC_PRESS); + fusion_F0710A.tip2 = DATA(fusion_F0710A_SEC_TIDTS)&0x0f; + fusion_F0710A.tid2 =(DATA(fusion_F0710A_SEC_TIDTS)&0xf0)>>4; +#undef DATA + + return 0; +} + +#define val_cut_max(x, max, reverse) \ +do \ +{ \ + if(x > max) \ + x = max; \ + if(reverse) \ + x = (max) - (x); \ +} \ +while(0) + +static void fusion_F0710A_wq(struct work_struct *work) +{ + struct input_dev *dev = fusion_F0710A.input; + int save_points = 0; + int x1 = 0, y1 = 0, z1 = 0, x2 = 0, y2 = 0, z2 = 0; + + if (fusion_F0710A_read_sensor() < 0) + goto restore_irq; + +#ifdef DEBUG + printk(KERN_DEBUG "tip1, tid1, x1, y1, z1 (%x,%x,%d,%d,%d); tip2, tid2, x2, y2, z2 (%x,%x,%d,%d,%d)\n", + fusion_F0710A.tip1, fusion_F0710A.tid1, fusion_F0710A.x1, fusion_F0710A.y1, fusion_F0710A.z1, + fusion_F0710A.tip2, fusion_F0710A.tid2, fusion_F0710A.x2, fusion_F0710A.y2, fusion_F0710A.z2); +#endif /* DEBUG */ + + val_cut_max(fusion_F0710A.x1, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y1, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.x2, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y2, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + + if(fusion_F0710A.tip1 == 1) + { + if(fusion_F0710A.tid1 == 1) + { + /* first point */ + x1 = fusion_F0710A.x1; + y1 = fusion_F0710A.y1; + z1 = fusion_F0710A.z1; + save_points |= fusion_F0710A_SAVE_PT1; + } + else if(fusion_F0710A.tid1 == 2) + { + /* second point ABS_DISTANCE second point pressure, BTN_2 second point touch */ + x2 = fusion_F0710A.x1; + y2 = fusion_F0710A.y1; + z2 = fusion_F0710A.z1; + save_points |= fusion_F0710A_SAVE_PT2; + } + } + + if(fusion_F0710A.tip2 == 1) + { + if(fusion_F0710A.tid2 == 2) + { + /* second point ABS_DISTANCE second point pressure, BTN_2 second point touch */ + x2 = fusion_F0710A.x2; + y2 = fusion_F0710A.y2; + z2 = fusion_F0710A.z2; + save_points |= fusion_F0710A_SAVE_PT2; + } + else if(fusion_F0710A.tid2 == 1)/* maybe this will never happen */ + { + /* first point */ + x1 = fusion_F0710A.x2; + y1 = fusion_F0710A.y2; + z1 = fusion_F0710A.z2; + save_points |= fusion_F0710A_SAVE_PT1; + } + } + +#ifdef CONFIG_ANDROID + if(z1) + { + input_report_abs(dev, ABS_MT_TRACKING_ID, 1); + input_report_abs(dev, ABS_MT_POSITION_X, x1); + input_report_abs(dev, ABS_MT_POSITION_Y, y1); + } + input_mt_sync(dev); + + if(z2) + { + input_report_abs(dev, ABS_MT_TRACKING_ID, 2); + input_report_abs(dev, ABS_MT_POSITION_X, x2); + input_report_abs(dev, ABS_MT_POSITION_Y, y2); + } + input_mt_sync(dev); +#else /* CONFIG_ANDROID */ + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, z1); + input_report_abs(dev, ABS_MT_WIDTH_MAJOR, 1); + input_report_abs(dev, ABS_MT_POSITION_X, x1); + input_report_abs(dev, ABS_MT_POSITION_Y, y1); + input_mt_sync(dev); + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, z2); + input_report_abs(dev, ABS_MT_WIDTH_MAJOR, 2); + input_report_abs(dev, ABS_MT_POSITION_X, x2); + input_report_abs(dev, ABS_MT_POSITION_Y, y2); + input_mt_sync(dev); +#endif /* CONFIG_ANDROID */ + + input_report_abs(dev, ABS_X, x1); + input_report_abs(dev, ABS_Y, y1); + input_report_abs(dev, ABS_PRESSURE, z1); + input_report_key(dev, BTN_TOUCH, fusion_F0710A.tip1); + + input_sync(dev); + +restore_irq: + enable_irq(fusion_F0710A.client->irq); + + /* Clear fusion_F0710A interrupt */ + fusion_F0710A_write_complete(); +} +static DECLARE_WORK(fusion_F0710A_work, fusion_F0710A_wq); + +static irqreturn_t fusion_F0710A_interrupt(int irq, void *dev_id) +{ + disable_irq_nosync(fusion_F0710A.client->irq); + + queue_work(fusion_F0710A.workq, &fusion_F0710A_work); + + return IRQ_HANDLED; +} + +const static u8* g_ver_product[4] = { + "10Z8", "70Z7", "43Z6", "" +}; + +static int fusion_F0710A_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + int ret; + u8 ver_product, ver_id; + u32 version; + + if (pdata == NULL) + { + dev_err(&i2c->dev, "No platform data for Fusion driver\n"); + return -ENODEV; + } + + /* Request pinmuxing, if necessary */ + if (pdata->pinmux_fusion_pins != NULL) + { + ret = pdata->pinmux_fusion_pins(); + if (ret < 0) { + dev_err(&i2c->dev, "muxing GPIOs failed\n"); + return -ENODEV; + } + } + + if ((gpio_request(pdata->gpio_int, "Fusion pen down interrupt") == 0) && + (gpio_direction_input(pdata->gpio_int) == 0)) { + gpio_export(pdata->gpio_int, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion pen down\n"); + return -ENODEV; + } + + if ((gpio_request(pdata->gpio_reset, "Fusion reset") == 0) && + (gpio_direction_output(pdata->gpio_reset, 1) == 0)) { + + /* Generate a 0 => 1 edge explicitly, and wait for startup... */ + gpio_set_value(pdata->gpio_reset, 0); + msleep(10); + gpio_set_value(pdata->gpio_reset, 1); + /* Wait for startup (up to 125ms according to datasheet) */ + msleep(125); + + gpio_export(pdata->gpio_reset, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion reset\n"); + ret = -ENODEV; + goto bail0; + } + + /* Use Pen Down GPIO as sampling interrupt */ + i2c->irq = gpio_to_irq(pdata->gpio_int); + + if(!i2c->irq) + { + dev_err(&i2c->dev, "fusion_F0710A irq < 0 \n"); + ret = -ENOMEM; + goto bail1; + } + + /* Attach the I2C client */ + fusion_F0710A.client = i2c; + i2c_set_clientdata(i2c, &fusion_F0710A); + + dev_info(&i2c->dev, "Touchscreen registered with bus id (%d) with slave address 0x%x\n", + i2c_adapter_id(fusion_F0710A.client->adapter), fusion_F0710A.client->addr); + + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO_LO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + ver_product = (((u8)ret) & 0xc0) >> 6; + version = (10 + ((((u32)ret)&0x30) >> 4)) * 100000; + version += (((u32)ret)&0xf) * 1000; + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + ver_id = ((u8)(ret) & 0x6) >> 1; + version += ((((u32)ret) & 0xf8) >> 3) * 10; + version += (((u32)ret) & 0x1) + 1; /* 0 is build 1, 1 is build 2 */ + dev_info(&i2c->dev, "version product %s(%d)\n", g_ver_product[ver_product] ,ver_product); + dev_info(&i2c->dev, "version id %s(%d)\n", ver_id ? "1.4" : "1.0", ver_id); + dev_info(&i2c->dev, "version series (%d)\n", version); + + switch(ver_product) + { + case fusion_F0710A_VIESION_07: /* 7 inch */ + fusion_F0710A.info.xres = fusion_F0710A07_XMAX; + fusion_F0710A.info.yres = fusion_F0710A07_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A07_REV; + break; + case fusion_F0710A_VIESION_43: /* 4.3 inch */ + fusion_F0710A.info.xres = fusion_F0710A43_XMAX; + fusion_F0710A.info.yres = fusion_F0710A43_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A43_REV; + break; + default: /* fusion_F0710A_VIESION_10 10 inch */ + fusion_F0710A.info.xres = fusion_F0710A10_XMAX; + fusion_F0710A.info.yres = fusion_F0710A10_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A10_REV; + break; + } + + /* Register the input device. */ + ret = fusion_F0710A_register_input(); + if (ret < 0) { + dev_err(&i2c->dev, "can't register input: %d\n", ret); + goto bail1; + } + + /* Create a worker thread */ + fusion_F0710A.workq = create_singlethread_workqueue(DRV_NAME); + if (fusion_F0710A.workq == NULL) { + dev_err(&i2c->dev, "can't create work queue\n"); + ret = -ENOMEM; + goto bail2; + } + + + /* Register for the interrupt and enable it. Our handler will + * start getting invoked after this call. */ + ret = request_irq(i2c->irq, fusion_F0710A_interrupt, IRQF_TRIGGER_RISING, + i2c->name, &fusion_F0710A); + if (ret < 0) { + dev_err(&i2c->dev, "can't get irq %d: %d\n", i2c->irq, ret); + goto bail3; + } + /* clear the irq first */ + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if (ret < 0) { + dev_err(&i2c->dev, "Clear irq failed: %d\n", ret); + goto bail4; + } + + return 0; + +bail4: + free_irq(i2c->irq, &fusion_F0710A); + +bail3: + destroy_workqueue(fusion_F0710A.workq); + fusion_F0710A.workq = NULL; + +bail2: + input_unregister_device(fusion_F0710A.input); +bail1: + gpio_free(pdata->gpio_reset); +bail0: + gpio_free(pdata->gpio_int); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int fusion_F0710A_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + disable_irq(i2c->irq); + flush_workqueue(fusion_F0710A.workq); + + return 0; +} + +static int fusion_F0710A_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + enable_irq(i2c->irq); + + return 0; +} +#endif + +static int fusion_F0710A_remove(struct i2c_client *i2c) +{ + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + + gpio_free(pdata->gpio_int); + gpio_free(pdata->gpio_reset); + destroy_workqueue(fusion_F0710A.workq); + free_irq(i2c->irq, &fusion_F0710A); + input_unregister_device(fusion_F0710A.input); + i2c_set_clientdata(i2c, NULL); + + dev_info(&i2c->dev, "driver removed\n"); + + return 0; +} + +static struct i2c_device_id fusion_F0710A_id[] = { + {"fusion_F0710A", 0}, + {}, +}; + +static const struct dev_pm_ops fusion_F0710A_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fusion_F0710A_suspend, fusion_F0710A_resume) +}; + +static struct i2c_driver fusion_F0710A_i2c_drv = { + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + .pm = &fusion_F0710A_pm_ops, + }, + .probe = fusion_F0710A_probe, + .remove = fusion_F0710A_remove, + .id_table = fusion_F0710A_id, + .address_list = normal_i2c, +}; + +static int __init fusion_F0710A_init( void ) +{ + int ret; + + memset(&fusion_F0710A, 0, sizeof(fusion_F0710A)); + + /* Probe for fusion_F0710A on I2C. */ + ret = i2c_add_driver(&fusion_F0710A_i2c_drv); + if (ret < 0) { + printk(KERN_WARNING DRV_NAME " can't add i2c driver: %d\n", ret); + } + + return ret; +} + +static void __exit fusion_F0710A_exit( void ) +{ + i2c_del_driver(&fusion_F0710A_i2c_drv); +} +module_init(fusion_F0710A_init); +module_exit(fusion_F0710A_exit); + +MODULE_DESCRIPTION("fusion_F0710A Touchscreen Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/touchscreen/fusion_F0710A.h b/drivers/input/touchscreen/fusion_F0710A.h new file mode 100644 index 000000000000..85f8210345a9 --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.h @@ -0,0 +1,87 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * 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. + */ + +/* I2C slave address */ +#define fusion_F0710A_I2C_SLAVE_ADDR 0x10 + +/* I2C registers */ +#define fusion_F0710A_DATA_INFO 0x00 + +/* First Point*/ +#define fusion_F0710A_POS_X1_HI 0x01 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X1_LO 0x02 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y1_HI 0x03 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y1_LO 0x04 /* 16-bit register, LSB */ +#define fusion_F0710A_FIR_PRESS 0X05 +#define fusion_F0710A_FIR_TIDTS 0X06 + +/* Second Point */ +#define fusion_F0710A_POS_X2_HI 0x07 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X2_LO 0x08 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y2_HI 0x09 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y2_LO 0x0A /* 16-bit register, LSB */ +#define fusion_F0710A_SEC_PRESS 0x0B +#define fusion_F0710A_SEC_TIDTS 0x0C + +#define fusion_F0710A_VIESION_INFO_LO 0X0E +#define fusion_F0710A_VIESION_INFO 0X0F + +#define fusion_F0710A_RESET 0x10 +#define fusion_F0710A_SCAN_COMPLETE 0x11 + + +#define fusion_F0710A_VIESION_10 0 +#define fusion_F0710A_VIESION_07 1 +#define fusion_F0710A_VIESION_43 2 + +/* fusion_F0710A 10 inch panel */ +#define fusion_F0710A10_XMAX 2275 +#define fusion_F0710A10_YMAX 1275 +#define fusion_F0710A10_REV 1 + +/* fusion_F0710A 7 inch panel */ +#define fusion_F0710A07_XMAX 1500 +#define fusion_F0710A07_YMAX 900 +#define fusion_F0710A07_REV 0 + +/* fusion_F0710A 4.3 inch panel */ +#define fusion_F0710A43_XMAX 900 +#define fusion_F0710A43_YMAX 500 +#define fusion_F0710A43_REV 0 + +#define fusion_F0710A_SAVE_PT1 0x1 +#define fusion_F0710A_SAVE_PT2 0x2 + + + +/* fusion_F0710A touch screen information */ +struct fusion_F0710A_info { + int xres; /* x resolution */ + int yres; /* y resolution */ + int xy_reverse; /* if need reverse in the x,y value x=xres-1-x, y=yres-1-y*/ +}; + +struct fusion_F0710A_data { + struct fusion_F0710A_info info; + struct i2c_client *client; + struct workqueue_struct *workq; + struct input_dev *input; + u16 x1; + u16 y1; + u8 z1; + u8 tip1; + u8 tid1; + u16 x2; + u16 y2; + u8 z2; + u8 tip2; + u8 tid2; + u8 f_num; + u8 save_points; +}; + diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c index ae88e13c99ff..dbd94f3c957a 100644 --- a/drivers/input/touchscreen/stmpe-ts.c +++ b/drivers/input/touchscreen/stmpe-ts.c @@ -48,17 +48,6 @@ #define STMPE_IRQ_TOUCH_DET 0 -#define SAMPLE_TIME(x) ((x & 0xf) << 4) -#define MOD_12B(x) ((x & 0x1) << 3) -#define REF_SEL(x) ((x & 0x1) << 1) -#define ADC_FREQ(x) (x & 0x3) -#define AVE_CTRL(x) ((x & 0x3) << 6) -#define DET_DELAY(x) ((x & 0x7) << 3) -#define SETTLING(x) (x & 0x7) -#define FRACTION_Z(x) (x & 0x7) -#define I_DRIVE(x) (x & 0x1) -#define OP_MODE(x) ((x & 0x7) << 1) - #define STMPE_TS_NAME "stmpe-ts" #define XY_MASK 0xfff @@ -118,6 +107,7 @@ static void stmpe_work(struct work_struct *work) __stmpe_reset_fifo(ts->stmpe); input_report_abs(ts->idev, ABS_PRESSURE, 0); + input_report_key(ts->idev, BTN_TOUCH, 0); input_sync(ts->idev); } @@ -148,9 +138,17 @@ static irqreturn_t stmpe_ts_handler(int irq, void *data) y = ((data_set[1] & 0xf) << 8) | data_set[2]; z = data_set[3]; +#ifndef CONFIG_ANDROID input_report_abs(ts->idev, ABS_X, x); input_report_abs(ts->idev, ABS_Y, y); +#else /* !CONFIG_ANDROID */ + /* Hack: rotate touch for now due to missing calibration integration + Note: 12-bit touch resolution */ + input_report_abs(ts->idev, ABS_X, 4096 - x); + input_report_abs(ts->idev, ABS_Y, 4096 - y); +#endif /* !CONFIG_ANDROID */ input_report_abs(ts->idev, ABS_PRESSURE, z); + input_report_key(ts->idev, BTN_TOUCH, (z != 0)); input_sync(ts->idev); /* flush the FIFO after we have read out our values. */ @@ -161,7 +159,7 @@ static irqreturn_t stmpe_ts_handler(int irq, void *data) STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN); /* start polling for touch_det to detect release */ - schedule_delayed_work(&ts->work, HZ / 50); + schedule_delayed_work(&ts->work, HZ / 10); return IRQ_HANDLED; } diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c index 5dbe73af2f8f..72e2abea2c28 100644 --- a/drivers/input/touchscreen/wm97xx-core.c +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -54,6 +54,10 @@ #define WM_CORE_VERSION "1.00" #define DEFAULT_PRESSURE 0xb0c0 +#ifdef CONFIG_MACH_COLIBRI_T20 +extern void *get_colibri_t20_audio_platform_data(void); +#endif + /* * Touchscreen absolute values @@ -442,8 +446,18 @@ static int wm97xx_read_samples(struct wm97xx *wm) "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n", data.x >> 12, data.x & 0xfff, data.y >> 12, data.y & 0xfff, data.p >> 12, data.p & 0xfff); +#ifndef CONFIG_ANDROID input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff); input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff); +#else /* !CONFIG_ANDROID */ + /* Hack: rotate touch for now due to missing calibration + integration + Note: 12-bit touch resolution */ + input_report_abs(wm->input_dev, ABS_X, 4096 - (data.x & 0xfff)); + input_report_abs(wm->input_dev, ABS_Y, 4096 - (data.y & 0xfff)); +#endif /* !CONFIG_ANDROID */ + + input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); input_report_key(wm->input_dev, BTN_TOUCH, 1); input_sync(wm->input_dev); @@ -640,7 +654,12 @@ static int wm97xx_probe(struct device *dev) } /* set up touch configuration */ +#ifdef CONFIG_ANDROID + /* Hack: rename due to idc parser having issues with spaces in names */ + wm->input_dev->name = "wm97xx-ts"; +#else /* CONFIG_ANDROID */ wm->input_dev->name = "wm97xx touchscreen"; +#endif /* CONFIG_ANDROID */ wm->input_dev->phys = "wm97xx"; wm->input_dev->open = wm97xx_ts_input_open; wm->input_dev->close = wm97xx_ts_input_close; @@ -671,7 +690,13 @@ static int wm97xx_probe(struct device *dev) } platform_set_drvdata(wm->battery_dev, wm); wm->battery_dev->dev.parent = dev; + +#if defined(CONFIG_MACH_COLIBRI_T20) && !defined(CONFIG_ANDROID) + wm->battery_dev->dev.platform_data = get_colibri_t20_audio_platform_data(); +#else wm->battery_dev->dev.platform_data = pdata; +#endif + ret = platform_device_add(wm->battery_dev); if (ret < 0) goto batt_reg_err; diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index dfff30255a92..0fe628f5912d 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -60,7 +60,6 @@ config VIDEOBUF2_VMALLOC select VIDEOBUF2_MEMOPS tristate - config VIDEOBUF2_DMA_SG #depends on HAS_DMA select VIDEOBUF2_CORE @@ -797,6 +796,12 @@ config SOC_CAMERA_IMX074 help This driver supports IMX074 cameras from Sony +config SOC_CAMERA_MAX9526 + tristate "max9526 support" + depends on SOC_CAMERA && I2C + help + This driver supports MAX9526 video decoders from Maxim Integrated + config SOC_CAMERA_MT9M001 tristate "mt9m001 support" depends on SOC_CAMERA && I2C diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index ce79916258ef..28362059e105 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o +obj-$(CONFIG_SOC_CAMERA_MAX9526) += max9526.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o diff --git a/drivers/media/video/adv7180.c b/drivers/media/video/adv7180.c index d2138d06bcad..14b48edb39b2 100644 --- a/drivers/media/video/adv7180.c +++ b/drivers/media/video/adv7180.c @@ -27,13 +27,20 @@ #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/soc_camera.h> #include <linux/mutex.h> #define DRIVER_NAME "adv7180" #define ADV7180_INPUT_CONTROL_REG 0x00 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN1 0x00 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN2 0x01 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN3 0x02 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN4 0x03 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN5 0x04 +#define ADV7180_INPUT_CONTROL_COMPOSITE_IN6 0x05 #define ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM 0x00 -#define ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM_PED 0x10 +#define ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_M_SECAM 0x10 #define ADV7180_INPUT_CONTROL_AD_PAL_N_NTSC_J_SECAM 0x20 #define ADV7180_INPUT_CONTROL_AD_PAL_N_NTSC_M_SECAM 0x30 #define ADV7180_INPUT_CONTROL_NTSC_J 0x40 @@ -71,7 +78,8 @@ #define ADV7180_STATUS1_AUTOD_SECAM_525 0x70 #define ADV7180_IDENT_REG 0x11 -#define ADV7180_ID_7180 0x18 +#define ADV7180_ID_7180 0x1C /* 64-lead and 40-lead models only */ +#define ADV7180_ID2_7180 0x1E /* 48-lead and 32-lead devices only */ #define ADV7180_ICONF1_ADI 0x40 #define ADV7180_ICONF1_ACTIVE_LOW 0x01 @@ -97,6 +105,7 @@ struct adv7180_state { int irq; v4l2_std_id curr_norm; bool autodetect; + int active_input; }; static v4l2_std_id adv7180_std_to_v4l2(u8 status1) @@ -224,14 +233,18 @@ static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std) if (std == V4L2_STD_ALL) { ret = i2c_smbus_write_byte_data(client, ADV7180_INPUT_CONTROL_REG, - ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM); + ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM | + (ADV7180_INPUT_CONTROL_COMPOSITE_IN1 + + state->active_input)); if (ret < 0) goto out; __adv7180_status(client, NULL, &state->curr_norm); state->autodetect = true; } else { - ret = v4l2_std_to_adv7180(std); + ret = v4l2_std_to_adv7180(std) | + (ADV7180_INPUT_CONTROL_COMPOSITE_IN1 + + state->active_input); if (ret < 0) goto out; @@ -249,19 +262,99 @@ out: return ret; } +static int adv7180_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + return 0; +} + +/* Request bus settings on camera side */ +static unsigned long adv7180_query_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_link *icl = to_soc_camera_link(icd); + + unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | SOCAM_MASTER | + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8; + + return soc_camera_apply_sensor_flags(icl, flags); +} + +static enum v4l2_mbus_pixelcode adv7180_codes[] = { + V4L2_MBUS_FMT_YUYV8_2X8, +}; + +static int adv7180_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + enum v4l2_colorspace cspace; + enum v4l2_mbus_pixelcode code = mf->code; +// struct i2c_client *client = v4l2_get_subdevdata(sd); +// u8 status1; + +// status1 = i2c_smbus_read_byte_data(client, ADV7180_STATUS1_REG); +// printk(KERN_ERR "*********************************** status1 = 0x%02x\n", status1); + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + cspace = V4L2_COLORSPACE_SRGB; + break; + default: + return -EINVAL; + } + + mf->code = code; + mf->colorspace = cspace; + + return adv7180_s_std(sd, V4L2_STD_ALL); +} + +static int adv7180_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + mf->field = V4L2_FIELD_INTERLACED_TB; + mf->code = V4L2_MBUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SRGB; + + // PAL + mf->width = 720; + mf->height = 576; + + return 0; +} + +static int adv7180_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(adv7180_codes)) + return -EINVAL; + + *code = adv7180_codes[index]; + + return 0; +} + +static struct soc_camera_ops adv7180_ops = { + .set_bus_param = adv7180_set_bus_param, + .query_bus_param = adv7180_query_bus_param, +}; + static const struct v4l2_subdev_video_ops adv7180_video_ops = { - .querystd = adv7180_querystd, - .g_input_status = adv7180_g_input_status, + .s_mbus_fmt = adv7180_s_fmt, + .try_mbus_fmt = adv7180_try_fmt, + .enum_mbus_fmt = adv7180_enum_fmt, + .querystd = adv7180_querystd, + .g_input_status = adv7180_g_input_status, }; static const struct v4l2_subdev_core_ops adv7180_core_ops = { - .g_chip_ident = adv7180_g_chip_ident, - .s_std = adv7180_s_std, + .g_chip_ident = adv7180_g_chip_ident, + .s_std = adv7180_s_std, }; -static const struct v4l2_subdev_ops adv7180_ops = { - .core = &adv7180_core_ops, - .video = &adv7180_video_ops, +static const struct v4l2_subdev_ops adv7180_subdev_ops = { + .core = &adv7180_core_ops, + .video = &adv7180_video_ops, }; static void adv7180_work(struct work_struct *work) @@ -297,6 +390,36 @@ static irqreturn_t adv7180_irq(int irq, void *devid) return IRQ_HANDLED; } +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) { + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct adv7180_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 val; + + if (i < 6) { + state->active_input = i; + val = i2c_smbus_read_byte_data(client, + ADV7180_INPUT_CONTROL_REG); + val &= 0xf0; + val |= (ADV7180_INPUT_CONTROL_COMPOSITE_IN1 + + state->active_input); + return i2c_smbus_write_byte_data(client, + ADV7180_INPUT_CONTROL_REG, val); + } + return -EINVAL; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) { + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct adv7180_state *state = to_state(sd); + + *i = state->active_input; + + return 0; +} + /* * Generic i2c probe * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' @@ -306,8 +429,11 @@ static __devinit int adv7180_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct adv7180_state *state; + struct soc_camera_device *icd = client->dev.platform_data; struct v4l2_subdev *sd; + u8 ident; int ret; + struct v4l2_ioctl_ops *ops; /* Check if the adapter supports the needed features */ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) @@ -322,17 +448,25 @@ static __devinit int adv7180_probe(struct i2c_client *client, goto err; } + ident = i2c_smbus_read_byte_data(client, ADV7180_IDENT_REG); + WARN_ON((ident != ADV7180_ID_7180) && (ident != ADV7180_ID2_7180)); + v4l_info(client, "ident reg is 0x%02x\n", ident); + state->irq = client->irq; INIT_WORK(&state->work, adv7180_work); mutex_init(&state->mutex); state->autodetect = true; + state->active_input = 0; // input 1 sd = &state->sd; - v4l2_i2c_subdev_init(sd, client, &adv7180_ops); + v4l2_i2c_subdev_init(sd, client, &adv7180_subdev_ops); + icd->ops = &adv7180_ops; /* Initialize adv7180 */ /* Enable autodetection */ ret = i2c_smbus_write_byte_data(client, ADV7180_INPUT_CONTROL_REG, - ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM); + ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM | + (ADV7180_INPUT_CONTROL_COMPOSITE_IN1 + + state->active_input)); if (ret < 0) goto err_unreg_subdev; @@ -393,6 +527,15 @@ static __devinit int adv7180_probe(struct i2c_client *client, goto err_unreg_subdev; } + /* + * this is the only way to support more than one input as soc_camera + * assumes in its own vidioc_s(g)_input implementation that only one + * input is present we have to override that with our own handlers. + */ + ops = (struct v4l2_ioctl_ops*)icd->vdev->ioctl_ops; + ops->vidioc_s_input = &vidioc_s_input; + ops->vidioc_g_input = &vidioc_g_input; + return 0; err_unreg_subdev: @@ -432,7 +575,7 @@ static const struct i2c_device_id adv7180_id[] = { {}, }; -MODULE_DEVICE_TABLE(i2c, adv7180_id); +//MODULE_DEVICE_TABLE(i2c, adv7180_id); static struct i2c_driver adv7180_driver = { .driver = { @@ -460,4 +603,3 @@ module_exit(adv7180_exit); MODULE_DESCRIPTION("Analog Devices ADV7180 video decoder driver"); MODULE_AUTHOR("Mocean Laboratories"); MODULE_LICENSE("GPL v2"); - diff --git a/drivers/media/video/max9526.c b/drivers/media/video/max9526.c new file mode 100644 index 000000000000..1f13dcb29c5a --- /dev/null +++ b/drivers/media/video/max9526.c @@ -0,0 +1,1102 @@ +/* + * drivers/media/video/max9526.c + * + * MAXIM MAX9526 decoder driver + * + * Copyright (c) 2011 Ming-Yao Chen <mychen0518@gmail.com> + * (based on tvp514x.c) + * + * Copyright (c) 2013 Ant Micro <www.antmicro.com> + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> + +#include <media/soc_camera.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-ioctl.h> + +/*MODULE NAME*/ +#define MAX9526_MODULE_NAME "max9526" + +/*Private macros for MAX9526*/ + +#define LOCK_RETRY_DELAY (200) +#define LOCK_RETRY_COUNT (5) +#define I2C_RETRY_COUNT (5) + + + /* registers */ +#define REG_STATUS_0 0x00 +#define REG_STATUS_1 0x01 +#define REG_IRQMASK_0 0x02 +#define REG_IRQMASK_1 0x03 +#define REG_STANDARD_SELECT_SHUTDOWN_CONTROL 0x04 +#define REG_CONTRAST 0x05 +#define REG_BRIGHTNESS 0x06 +#define REG_HUE 0x07 +#define REG_SATURATION 0x08 +#define REG_VIDEO_INPUT_SELECT_AND_CLAMP 0x09 +#define REG_GAIN_CONTROL 0x0A +#define REG_COLOR_KILL 0x0B +#define REG_OUTPUT_TEST_SIGNAL 0x0C +#define REG_CLOCK_AND_OUTPUT 0x0D +#define REG_PLL_CONTROL 0x0E +#define REG_MISCELLANEOUS 0x0F + +#define PAL_NUM_ACTIVE_PIXELS (720) +#define PAL_NUM_ACTIVE_LINES (576) + +#define NTSC_NUM_ACTIVE_PIXELS (720) +#define NTSC_NUM_ACTIVE_LINES (480) + +#define REG_VIDEO_INPUT_SELECT_IN1 0x00 +#define REG_VIDEO_INPUT_SELECT_IN2 0x40 +#define REG_VIDEO_INPUT_SELECT_AUTO 0x80 + + +struct max9526_reg { + u8 token; + u8 reg; + u32 val; +}; + +enum max9526_tokens { + TOK_TERM, + TOK_SKIP, + TOK_DELAY, + TOK_WRITE, +}; + +enum { + VIDEO_STDSEL_NTSC_M_BIT, + VIDEO_STDSEL_PAL_BGHID_BIT, +}; + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("MAX9526 linux decoder driver"); +MODULE_LICENSE("GPL"); + + +/* enum max9526_std - enum for supported standards*/ + +enum max9526_std { + STD_NTSC_MJ = 0, + STD_PAL_BDGHIN, + STD_INVALID +}; + + +/** + * struct max9526_std_info - Structure to store standard informations + * @width: Line width in pixels + * @height:Number of active lines + * @video_std: Value to write in REG_VIDEO_STD register + * @standard: v4l2 standard structure information + */ + + +struct max9526_std_info { + unsigned long width; + unsigned long height; + u8 video_std; + struct v4l2_standard standard; +}; + +// TODO: redo this +static const struct max9526_reg max9526_reg_list_default[] = { + {TOK_SKIP , REG_STATUS_0, 0x84}, + {TOK_SKIP , REG_STATUS_1, 0x40}, + {TOK_SKIP , REG_IRQMASK_0, 0x00}, + {TOK_SKIP , REG_IRQMASK_1, 0x00}, + /*Standard Select, Shutdown, and Control Register*/ + {TOK_WRITE, REG_STANDARD_SELECT_SHUTDOWN_CONTROL, 0x10}, // was 0x10 (autodetect), 0x0=PAL, 0x40=NTSC + {TOK_SKIP, REG_CONTRAST, 0x80}, + {TOK_SKIP, REG_BRIGHTNESS, 0x00}, + {TOK_SKIP, REG_HUE, 0x80}, + {TOK_SKIP, REG_SATURATION, 0x88}, + {TOK_WRITE, REG_VIDEO_INPUT_SELECT_AND_CLAMP, 0x80}, // auto-select + // between input 1 and 2 + {TOK_SKIP, REG_GAIN_CONTROL, 0x00}, + {TOK_SKIP, REG_COLOR_KILL, 0x23}, + {TOK_WRITE, REG_OUTPUT_TEST_SIGNAL, 0x03}, // select 100% color bars + {TOK_WRITE, REG_CLOCK_AND_OUTPUT, 0x04}, // select HSVS + {TOK_SKIP, REG_PLL_CONTROL, 0x03}, + {TOK_SKIP, REG_MISCELLANEOUS, 0x18}, + {TOK_TERM, 0, 0}, +}; + + +//static struct max9526_reg max9526_reg_list_default[0x11]; + +/*MAX9526 default register values*/ + +static int max9526_s_stream(struct v4l2_subdev *sd, int enable); + +/** + * struct max9526_decoder - MAX9526 decoder object + * @sd: Subdevice Slave handle + * @max9526_regs: copy of hw's regs with preset values. + * @pdata: Board specific + * @ver: Chip version + * @streaming: MAX9526 decoder streaming - enabled or disabled. + * @pix: Current pixel format + * @num_fmts: Number of formats + * @fmt_list: Format list + * @current_std: Current standard + * @num_stds: Number of standards + * @std_list: Standards list + * @input: Input routing at chip level + * @output: Output routing at chip level + */ +struct max9526_decoder { + struct v4l2_subdev sd; + struct max9526_reg max9526_regs[ARRAY_SIZE(max9526_reg_list_default)]; + const struct max9526_platform_data *pdata; + + int ver; + int streaming; + + struct v4l2_pix_format pix; + int num_fmts; + const struct v4l2_fmtdesc *fmt_list; + + enum max9526_std current_std; + int num_stds; + struct max9526_std_info *std_list; + /* Input and Output Routing parameters */ + u32 input; + u32 output; + + int active_input; +}; + +/** + * List of image formats supported by max9526 decoder + * Currently we are using 8 bit mode only, but can be + * extended to 10/20 bit mode. + */ +static const struct v4l2_fmtdesc max9526_fmt_list[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "8-bit YUYV 4:2:2 Format", + .pixelformat = V4L2_PIX_FMT_YUYV, + }, + { + .index = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "8-bit UYVY 4:2:2 Format", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + +/** + * Supported standards - + * + * Currently supports two standards only, need to add support for rest of the + * modes, like SECAM, etc... + */ +static struct max9526_std_info max9526_std_list[] = { + /* Standard: STD_NTSC_MJ */ + [STD_NTSC_MJ] = { + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .video_std = VIDEO_STDSEL_NTSC_M_BIT, + .standard = { + .index = 0, + .id = V4L2_STD_NTSC, + .name = "NTSC", + .frameperiod = {1001, 30000}, + .framelines = 525 + }, + /* Standard: STD_PAL_BDGHIN */ + }, + [STD_PAL_BDGHIN] = { + .width = PAL_NUM_ACTIVE_PIXELS, + .height = PAL_NUM_ACTIVE_LINES, + .video_std = VIDEO_STDSEL_PAL_BGHID_BIT, + .standard = { + .index = 1, + .id = V4L2_STD_PAL, + .name = "PAL", + .frameperiod = {1, 25}, + .framelines = 625 + }, + }, + /* Standard: need to add for additional standard */ +}; + +static inline struct max9526_decoder *to_decoder(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max9526_decoder, sd); +} + +/** + * max9526_read_reg() - Read a value from a register in an MAX9526. + * @sd: ptr to v4l2_subdev struct + * @reg: max9526 register address + * + * Returns value read if successful, or non-zero (-1) otherwise. + */ +static int max9526_read_reg(struct v4l2_subdev *sd, u8 reg) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + +read_again: + + err = i2c_smbus_read_byte_data(client, reg); + if (err < 0) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Read: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto read_again; + } + } + + return err; +} + +#if 0 +/** + * dump_reg() - dump the register content of MAX9526. + * @sd: ptr to v4l2_subdev struct + * @reg: MAX9526 register address + */ +static void dump_reg(struct v4l2_subdev *sd, u8 reg) +{ + u32 val; + + val = max9526_read_reg(sd, reg); + v4l2_info(sd, "Reg(0x%.2X): 0x%.2X\n", reg, val); +} +#endif + +/** + * max9526_write_reg() - Write a value to a register in MAX9526 + * @sd: ptr to v4l2_subdev struct + * @reg: MAX9526 register address + * @val: value to be written to the register + * + * Write a value to a register in an MAX9526 decoder device. + * Returns zero if successful, or non-zero otherwise. + */ +static int max9526_write_reg(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); +write_again: + + err = i2c_smbus_write_byte_data(client, reg, val); + if (err) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Write: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto write_again; + } + } +// if (!err) { +// v4l2_warn(sd, "max9526: wrote %X to register %X.\n", val, reg); +// } + return err; +} + + +/** + * max9526_write_regs() : Initializes a list of MAX9526 registers + * @sd: ptr to v4l2_subdev struct + * @reglist: list of MAX9526 registers and values + * + * Initializes a list of MAX9526 registers: token is state flag + * if token is TOK_TERM, then entire write operation terminates + * if token is TOK_DELAY, then a delay of 'val' msec is introduced + * if token is TOK_SKIP, then the register write is skipped + * if token is TOK_WRITE, then the register write is performed + * Returns zero if successful, or non-zero otherwise. + */ +static int max9526_write_regs(struct v4l2_subdev *sd, + const struct max9526_reg reglist[]) +{ + int err; + const struct max9526_reg *next = reglist; + for (; next->token != TOK_TERM; next++) { + if (next->token == TOK_DELAY) { + msleep(next->val); + continue; + } + + if (next->token == TOK_SKIP) + continue; + + err = max9526_write_reg(sd, next->reg, (u8) next->val); + if (err) { + v4l2_err(sd, "Write failed. Err[%d]\n", err); + return err; + } + } + return 0; +} + + +/** + * max9526_get_current_std() : Get the current standard detected by max9526 + * @sd: ptr to v4l2_subdev struct + * + * Get current standard detected by MAX9526, STD_INVALID if there is no + * standard detected. + */ +static enum max9526_std max9526_get_current_std(struct v4l2_subdev *sd) +{ + u8 std, std_status; + + std = max9526_read_reg(sd, REG_STANDARD_SELECT_SHUTDOWN_CONTROL); + + std_status = std>>5; + switch (std_status) { + case VIDEO_STDSEL_NTSC_M_BIT: + return STD_NTSC_MJ; + + case VIDEO_STDSEL_PAL_BGHID_BIT: + return STD_PAL_BDGHIN; + + default: + return STD_INVALID; + } + + return STD_INVALID; +} + + +/** + * max9526_configure() - Configure the MAX9526 registers + * @sd: ptr to v4l2_subdev struct + * @decoder: ptr to max9526_decoder structure + * + * Returns zero if successful, or non-zero otherwise. + */ +static int max9526_configure(struct v4l2_subdev *sd, struct max9526_decoder *decoder) +{ + int err; + + /* common register initialization */ + err = + max9526_write_regs(sd, decoder->max9526_regs); + if (err) + return err; + +// if (debug) +// max9526_reg_dump(sd); + + return 0; +} + + +/** + * max9526_querystd() - V4L2 decoder interface handler for querystd + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 std_id ioctl enum + * + * Returns the current standard detected by MAX9526. If no active input is + * detected, returns -EINVAL + */ +static int max9526_querystd(struct v4l2_subdev *sd, v4l2_std_id *std_id) +{ + struct max9526_decoder *decoder = to_decoder(sd); + enum max9526_std current_std; + //enum max9526_input input_sel; + //u8 sync_lock_status, lock_mask; + //int err; + + if (std_id == NULL) + return -EINVAL; + + msleep(LOCK_RETRY_DELAY); + + /* get the current standard */ + current_std = max9526_get_current_std(sd); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + *std_id = decoder->std_list[current_std].standard.id; + +// v4l2_dbg(1, debug, sd, "Current STD: %s", +// decoder->std_list[current_std].standard.name); + return 0; +} + +/** + * max9526_s_std() - V4L2 decoder interface handler for s_std + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 v4l2_std_id ioctl enum + * + * If std_id is supported, sets the requested standard. Otherwise, returns + * -EINVAL + */ +static int max9526_s_std(struct v4l2_subdev *sd, v4l2_std_id std_id) +{ + struct max9526_decoder *decoder = to_decoder(sd); + int err, i; + + for (i = 0; i < decoder->num_stds; i++) + if (std_id & decoder->std_list[i].standard.id) + break; + + if ((i == decoder->num_stds) || (i == STD_INVALID)) + return -EINVAL; + + err = max9526_write_reg(sd, REG_STANDARD_SELECT_SHUTDOWN_CONTROL, + decoder->std_list[i].video_std); + if (err) + return err; + + decoder->current_std = i; + decoder->max9526_regs[REG_STANDARD_SELECT_SHUTDOWN_CONTROL].val = + decoder->std_list[i].video_std; + +// v4l2_dbg(1, debug, sd, "Standard set to: %s", +// decoder->std_list[i].standard.name); + return 0; +} + +/** + * max9526_s_routing() - V4L2 decoder interface handler for s_routing + * @sd: pointer to standard V4L2 sub-device structure + * @input: input selector for routing the signal + * @output: output selector for routing the signal + * @config: config value. Not used + * + * If index is valid, selects the requested input. Otherwise, returns -EINVAL if + * the input is not supported or there is no active signal present in the + * selected input. + */ +static int max9526_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + //struct max9526_decoder *decoder = to_decoder(sd); + + /* + * For the sequence streamon -> streamoff and again s_input, most of + * the time it fails to lock the signal, since streamoff puts MAX9526 + * into power off state which leads to failure in sub-sequent s_input. + */ + max9526_s_stream(sd, 1); + return 0; +} + + +/** + * max9526_queryctrl() - V4L2 decoder interface handler for queryctrl + * @sd: pointer to standard V4L2 sub-device structure + * @qctrl: standard V4L2 v4l2_queryctrl structure + * + * If the requested control is supported, returns the control information. + * Otherwise, returns -EINVAL if the control is not supported. + */ +static int +max9526_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qctrl) +{ + int err = -EINVAL; + + if (qctrl == NULL) + return err; + + switch (qctrl->id) { + case V4L2_CID_BRIGHTNESS: + /* Brightness supported is (0-255), */ + err = v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 128); + break; + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + /** + * Saturation and Contrast supported is - + * Contrast: 0 - 255 (Default - 128) + * Saturation: 0 - 255 (Default - 128) + */ + err = v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 128); + break; + case V4L2_CID_HUE: + /* Hue Supported is - + * Hue - -180 - +180 (Default - 0, Step - +180) + */ + err = v4l2_ctrl_query_fill(qctrl, -180, 180, 180, 0); + break; + case V4L2_CID_AUTOGAIN: + /** + * Auto Gain supported is - + * 0 - 1 (Default - 1) + */ + err = v4l2_ctrl_query_fill(qctrl, 0, 1, 1, 1); + break; + default: + v4l2_err(sd, "invalid control id %d\n", qctrl->id); + return err; + } + +// v4l2_dbg(1, debug, sd, "Query Control:%s: Min - %d, Max - %d, Def - %d", +// qctrl->name, qctrl->minimum, qctrl->maximum, +// qctrl->default_value); + + return err; +} + + +/** + * max9526_g_ctrl() - V4L2 decoder interface handler for g_ctrl + * @sd: pointer to standard V4L2 sub-device structure + * @ctrl: pointer to v4l2_control structure + * + * If the requested control is supported, returns the control's current + * value from the decoder. Otherwise, returns -EINVAL if the control is not + * supported. + */ +static int +max9526_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct max9526_decoder *decoder = to_decoder(sd); + + if (ctrl == NULL) + return -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = decoder->max9526_regs[REG_BRIGHTNESS].val; + break; + case V4L2_CID_CONTRAST: + ctrl->value = decoder->max9526_regs[REG_CONTRAST].val; + break; + case V4L2_CID_SATURATION: + ctrl->value = decoder->max9526_regs[REG_SATURATION].val; + break; + case V4L2_CID_HUE: + ctrl->value = decoder->max9526_regs[REG_HUE].val; + if (ctrl->value == 0x7F) + ctrl->value = 180; + else if (ctrl->value == 0x80) + ctrl->value = -180; + else + ctrl->value = 0; + + break; + default: + v4l2_err(sd, "invalid control id %d\n", ctrl->id); + return -EINVAL; + } + +// v4l2_dbg(1, debug, sd, "Get Control: ID - %d - %d", +// ctrl->id, ctrl->value); + return 0; +} + +/** + * max9526_s_ctrl() - V4L2 decoder interface handler for s_ctrl + * @sd: pointer to standard V4L2 sub-device structure + * @ctrl: pointer to v4l2_control structure + * + * If the requested control is supported, sets the control's current + * value in HW. Otherwise, returns -EINVAL if the control is not supported. + */ +static int +max9526_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct max9526_decoder *decoder = to_decoder(sd); + int err = -EINVAL, value; + + if (ctrl == NULL) + return err; + + value = ctrl->value; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l2_err(sd, "invalid brightness setting %d\n", + ctrl->value); + return -ERANGE; + } + err = max9526_write_reg(sd, REG_BRIGHTNESS, + value); + if (err) + return err; + + decoder->max9526_regs[REG_BRIGHTNESS].val = value; + break; + case V4L2_CID_CONTRAST: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l2_err(sd, "invalid contrast setting %d\n", + ctrl->value); + return -ERANGE; + } + err = max9526_write_reg(sd, REG_CONTRAST, value); + if (err) + return err; + + decoder->max9526_regs[REG_CONTRAST].val = value; + break; + case V4L2_CID_SATURATION: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l2_err(sd, "invalid saturation setting %d\n", + ctrl->value); + return -ERANGE; + } + err = max9526_write_reg(sd, REG_SATURATION, value); + if (err) + return err; + + decoder->max9526_regs[REG_SATURATION].val = value; + break; + case V4L2_CID_HUE: + if (value == 180) + value = 0x7F; + else if (value == -180) + value = 0x80; + else if (value == 0) + value = 0; + else { + v4l2_err(sd, "invalid hue setting %d\n", ctrl->value); + return -ERANGE; + } + err = max9526_write_reg(sd, REG_HUE, value); + if (err) + return err; + + decoder->max9526_regs[REG_HUE].val = value; + break; + default: + v4l2_err(sd, "invalid control id %d\n", ctrl->id); + return err; + } + +// v4l2_dbg(1, debug, sd, "Set Control: ID - %d - %d", +// ctrl->id, ctrl->value); + + return err; +} + + +static int max9526_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + struct max9526_decoder *decoder = to_decoder(sd); + + if (index < 0 || index >= decoder->num_fmts) + return -EINVAL; + switch (index) { + case 0: + *code = V4L2_MBUS_FMT_YUYV8_2X8; + break; + case 1: + *code = V4L2_MBUS_FMT_UYVY8_2X8; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max9526_try_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ +/* pr_info("%s width:%d\n", __func__, mf->width); + pr_info("%s height:%d\n", __func__, mf->height); + pr_info("%s field:0x%X (V4L2_FIELD_NONE==0x%X)\n", __func__, mf->field, V4L2_FIELD_NONE); + pr_info("%s code:0x%X (V4L2_MBUS_FMT_YUYV8_2X8==0x%X)\n", __func__, mf->code, V4L2_MBUS_FMT_YUYV8_2X8); + pr_info("%s colorspace:0x%X (V4L2_COLORSPACE_SRGB==0x%X)\n", __func__, mf->colorspace, V4L2_COLORSPACE_SRGB);*/ + + if (mf->code == V4L2_MBUS_FMT_UYVY8_2X8) { + mf->width = PAL_NUM_ACTIVE_PIXELS; + mf->height = PAL_NUM_ACTIVE_LINES; + } else if (mf->code == V4L2_MBUS_FMT_YUYV8_2X8) { + mf->width = PAL_NUM_ACTIVE_PIXELS; + mf->height = PAL_NUM_ACTIVE_LINES; + } + mf->field = V4L2_FIELD_INTERLACED_TB; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + + +/** + * max9526_g_parm() - V4L2 decoder interface handler for g_parm + * @sd: pointer to standard V4L2 sub-device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the decoder's video CAPTURE parameters. + */ +static int +max9526_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct max9526_decoder *decoder = to_decoder(sd); + struct v4l2_captureparm *cparm; + enum max9526_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + /* only capture is supported */ + return -EINVAL; + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* get the current standard */ + current_std = max9526_get_current_std(sd); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + + cparm = &a->parm.capture; + cparm->capability = V4L2_CAP_TIMEPERFRAME; + cparm->timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + + +/** + * max9526_s_parm() - V4L2 decoder interface handler for s_parm + * @sd: pointer to standard V4L2 sub-device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the decoder to use the input parameters, if possible. If + * not possible, returns the appropriate error code. + */ +static int +max9526_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct max9526_decoder *decoder = to_decoder(sd); + struct v4l2_fract *timeperframe; + enum max9526_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + /* only capture is supported */ + return -EINVAL; + + timeperframe = &a->parm.capture.timeperframe; + + /* get the current standard */ + current_std = max9526_get_current_std(sd); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + + *timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * max9526_s_stream() - V4L2 decoder i/f handler for s_stream + * @sd: pointer to standard V4L2 sub-device structure + * @enable: streaming enable or disable + * + * Sets streaming to enable or disable, if possible. + */ +static int max9526_s_stream(struct v4l2_subdev *sd, int enable) +{ + int err = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct max9526_decoder *decoder = to_decoder(sd); + + if (decoder->streaming == enable) + return 0; + + switch (enable) { + case 0: + { + decoder->streaming = enable; + break; + } + case 1: + { + struct max9526_reg *int_seq = (struct max9526_reg *)client->driver->id_table->driver_data; + + err = max9526_write_regs(sd, int_seq); + if (err) { + v4l2_err(sd, "Unable to turn on decoder\n"); + return err; + } + err = max9526_configure(sd, decoder); + if (err) { + v4l2_err(sd, "Unable to configure decoder\n"); + return err; + } + decoder->streaming = enable; + break; + } + default: + err = -ENODEV; + break; + } + + return err; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) { + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct max9526_decoder *decoder = to_decoder(sd); + u8 val; + + if (i < 3) { + decoder->active_input = i; + switch (decoder->active_input) { + case 0: + val = REG_VIDEO_INPUT_SELECT_IN1; + break; + case 1: + val = REG_VIDEO_INPUT_SELECT_IN2; + break; + default: + val = REG_VIDEO_INPUT_SELECT_AUTO; + } + return max9526_write_reg(sd, REG_VIDEO_INPUT_SELECT_AND_CLAMP, + val); + } + + return -EINVAL; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) { + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct max9526_decoder *decoder = to_decoder(sd); + + *i = decoder->active_input; + + return 0; +} + +static const struct v4l2_subdev_core_ops max9526_core_ops = { + .queryctrl = max9526_queryctrl, + .g_ctrl = max9526_g_ctrl, + .s_ctrl = max9526_s_ctrl, + .s_std = max9526_s_std, +}; + + +static int max9526_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ +#if 0 + pr_info("%s width:%d\n", __func__, mf->width); + pr_info("%s height:%d\n", __func__, mf->height); + pr_info("%s field:0x%X (V4L2_FIELD_NONE==0x%X)\n", __func__, mf->field, V4L2_FIELD_NONE); + pr_info("%s code:0x%X (V4L2_MBUS_FMT_YUYV8_2X8==0x%X)\n", __func__, mf->code, V4L2_MBUS_FMT_YUYV8_2X8); + pr_info("%s colorspace:0x%X (V4L2_COLORSPACE_SRGB==0x%X)\n", __func__, mf->colorspace, V4L2_COLORSPACE_SRGB); + + mf->width = PAL_NUM_ACTIVE_PIXELS; + mf->height = PAL_NUM_ACTIVE_LINES; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; +#endif + return 0; +} + +static const struct v4l2_subdev_video_ops max9526_video_ops = { + .s_routing = max9526_s_routing, + .querystd = max9526_querystd, + .enum_mbus_fmt = max9526_enum_mbus_fmt, + .try_mbus_fmt = max9526_try_mbus_fmt, + .s_mbus_fmt = max9526_s_mbus_fmt, + .g_parm = max9526_g_parm, + .s_parm = max9526_s_parm, + .s_stream = max9526_s_stream, +}; + +/* Alter bus settings on camera side */ +static int max9526_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + return 0; +} + +static unsigned long max9526_query_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_link *icl = to_soc_camera_link(icd); + + unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | SOCAM_MASTER | + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8; + + return soc_camera_apply_sensor_flags(icl, flags); +} + +static struct soc_camera_ops max9526_soc_camera_ops = { + .set_bus_param = max9526_set_bus_param, + .query_bus_param = max9526_query_bus_param, + .num_controls = 0, +}; + +static const struct v4l2_subdev_ops max9526_ops = { + .core = &max9526_core_ops, + .video = &max9526_video_ops, +}; + +static struct max9526_decoder max9526_dev = { + .streaming = 0, + + .fmt_list = max9526_fmt_list, + .num_fmts = ARRAY_SIZE(max9526_fmt_list), + + .pix = { + /* Default to PAL 8-bit YUV 422 */ + .width = PAL_NUM_ACTIVE_PIXELS, + .height = PAL_NUM_ACTIVE_LINES, + .pixelformat = V4L2_PIX_FMT_UYVY, + .field = V4L2_FIELD_INTERLACED_TB, + .bytesperline = PAL_NUM_ACTIVE_PIXELS * 2, + .sizeimage = + PAL_NUM_ACTIVE_PIXELS * 2 * PAL_NUM_ACTIVE_LINES, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + }, + + .current_std = STD_PAL_BDGHIN, + .std_list = max9526_std_list, + .num_stds = ARRAY_SIZE(max9526_std_list), + + .active_input = 2, // auto-select between input 1 and 2 +}; + +/** + * max9526_probe() - decoder driver i2c probe handler + * @client: i2c driver client device structure + * @id: i2c driver id table + * + * Register decoder as an i2c client device and V4L2 + * device. + */ + +static int max9526_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct max9526_decoder *decoder; + struct v4l2_subdev *sd; + struct soc_camera_device *icd = client->dev.platform_data; + struct v4l2_ioctl_ops *ops; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if (!client->dev.platform_data) { + v4l2_err(client, "No platform data!!\n"); + return -ENODEV; + } + + decoder = kzalloc(sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + + /* Initialize the max9526_decoder with default configuration */ + *decoder = max9526_dev; + /* Copy default register configuration */ + memcpy(decoder->max9526_regs, max9526_reg_list_default, + sizeof(max9526_reg_list_default)); + + /* Copy board specific information here */ + decoder->pdata = client->dev.platform_data; + + + /* Register with V4L2 layer as slave device */ + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &max9526_ops); + + icd->ops = &max9526_soc_camera_ops; + + /* + * This is the only way to support more than one input as soc_camera + * assumes in its own vidioc_s(g)_input implementation that only one + * input is present we have to override that with our own handlers. + */ + ops = (struct v4l2_ioctl_ops*)icd->vdev->ioctl_ops; + ops->vidioc_s_input = &vidioc_s_input; + ops->vidioc_g_input = &vidioc_g_input; + + v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); + + return 0; + +} + + +/** + * max9526_remove() - decoder driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister decoder as an i2c client device and V4L2 + * device. Complement of max9526_probe(). + */ +static int max9526_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct max9526_decoder *decoder = to_decoder(sd); + + v4l2_device_unregister_subdev(sd); + kfree(decoder); + return 0; +} + +/** + * I2C Device Table - + * + * name - Name of the actual device/chip. + * driver_data - Driver data + */ +static const struct i2c_device_id max9526_id[] = { + {"max9526", (unsigned long)max9526_reg_list_default}, +}; + + +static struct i2c_driver max9526_driver = { + .driver = { + .owner = THIS_MODULE, + .name = MAX9526_MODULE_NAME, + }, + .probe = max9526_probe, + .remove = max9526_remove, + .id_table = max9526_id, +}; + +static int __init max9526_init(void) +{ + return i2c_add_driver(&max9526_driver); +} + +static void __exit max9526_exit(void) +{ + i2c_del_driver(&max9526_driver); +} + +module_init(max9526_init); +module_exit(max9526_exit); diff --git a/drivers/media/video/tegra_v4l2_camera.c b/drivers/media/video/tegra_v4l2_camera.c index 8f71d1702537..14a74cd6b7cc 100644 --- a/drivers/media/video/tegra_v4l2_camera.c +++ b/drivers/media/video/tegra_v4l2_camera.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -14,7 +14,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -23,13 +22,14 @@ #include <linux/nvhost.h> #include <mach/iomap.h> -#include <mach/powergate.h> #include <media/soc_camera.h> #include <media/soc_mediabus.h> #include <media/videobuf2-dma-nvmap.h> #include <media/tegra_v4l2_camera.h> +#include <mach/powergate.h> + #include "dev.h" #include "bus_client.h" #include "host1x/host1x_syncpt.h" @@ -37,18 +37,22 @@ #define TEGRA_CAM_DRV_NAME "vi" #define TEGRA_CAM_VERSION_CODE KERNEL_VERSION(0, 0, 5) -#define TEGRA_SYNCPT_VI_WAIT_TIMEOUT 200 +static unsigned int internal_sync = 0; +module_param(internal_sync, int, 0644); +MODULE_PARM_DESC(internal_sync, "enable internal vsync and hsync decoded " \ + "from data"); + +#define TEGRA_SYNCPT_VI_WAIT_TIMEOUT 25 #define TEGRA_SYNCPT_CSI_WAIT_TIMEOUT 200 #define TEGRA_SYNCPT_RETRY_COUNT 10 -#define TEGRA_VIP_H_ACTIVE_START 0x98 -#define TEGRA_VIP_V_ACTIVE_START 0x10 +#define TEGRA_VIP_H_ACTIVE_START 0x8F //0x98 +#define TEGRA_VIP_V_ACTIVE_START 0x12 //0x10 /* SYNCPTs 12-17 are reserved for VI. */ #define TEGRA_VI_SYNCPT_VI NVSYNCPT_VI_ISP_2 -#define TEGRA_VI_SYNCPT_CSI_A NVSYNCPT_VI_ISP_3 -#define TEGRA_VI_SYNCPT_CSI_B NVSYNCPT_VI_ISP_4 +#define TEGRA_VI_SYNCPT_CSI NVSYNCPT_VI_ISP_3 /* Tegra CSI-MIPI registers. */ #define TEGRA_VI_OUT_1_INCR_SYNCPT 0x0000 @@ -235,7 +239,10 @@ #define TEGRA_CSI_PIXEL_STREAM_A_EXPECTED_FRAME 0x08c8 #define TEGRA_CSI_PIXEL_STREAM_B_EXPECTED_FRAME 0x08cc #define TEGRA_CSI_DSI_MIPI_CAL_CONFIG 0x08d0 -#define TEGRA_CSI_MIPIBIAS_PAD_CONFIG0 0x08d4 + +#define IS_INTERLACED ((pcdev->field == V4L2_FIELD_INTERLACED)\ + || (pcdev->field == V4L2_FIELD_INTERLACED_BT)\ + || (pcdev->field == V4L2_FIELD_INTERLACED_TB)) #define TC_VI_REG_RD(DEV, REG) readl(DEV->vi_base + REG) #define TC_VI_REG_WT(DEV, REG, VAL) writel(VAL, DEV->vi_base + REG) @@ -256,8 +263,6 @@ struct tegra_buffer { struct vb2_buffer vb; /* v4l buffer must be first */ struct list_head queue; - struct soc_camera_device *icd; - int output_channel; /* * Various buffer addresses shadowed so we don't have to recalculate @@ -269,21 +274,20 @@ struct tegra_buffer { dma_addr_t start_addr; dma_addr_t start_addr_u; dma_addr_t start_addr_v; + void* virtual_addr; }; struct tegra_camera_dev { struct soc_camera_host ici; + struct soc_camera_device *icd; struct nvhost_device *ndev; + struct tegra_camera_platform_data *pdata; struct clk *clk_vi; struct clk *clk_vi_sensor; struct clk *clk_csi; struct clk *clk_isp; struct clk *clk_csus; - struct clk *clk_sclk; - struct clk *clk_emc; - - struct regulator *reg; void __iomem *vi_base; spinlock_t videobuf_queue_lock; @@ -291,22 +295,16 @@ struct tegra_camera_dev { struct vb2_buffer *active; struct vb2_alloc_ctx *alloc_ctx; enum v4l2_field field; - int sequence_a; - int sequence_b; + int sequence; struct work_struct work; struct mutex work_mutex; u32 syncpt_vi; - u32 syncpt_csi_a; - u32 syncpt_csi_b; + u32 syncpt_csi; /* Debug */ int num_frames; - int enable_refcnt; - - /* CSI pad calibration flag */ - int cal_done; }; static const struct soc_mbus_pixelfmt tegra_camera_formats[] = { @@ -352,23 +350,6 @@ static const struct soc_mbus_pixelfmt tegra_camera_formats[] = { .packing = SOC_MBUS_PACKING_NONE, .order = SOC_MBUS_ORDER_LE, }, - - /* For RAW8 and RAW10 output, we always output 16-bit (2 bytes). */ - { - .fourcc = V4L2_PIX_FMT_SBGGR8, - .name = "Bayer 8 BGBG.. GRGR..", - .bits_per_sample = 16, - .packing = SOC_MBUS_PACKING_EXTEND16, - .order = SOC_MBUS_ORDER_LE, - }, - { - .fourcc = V4L2_PIX_FMT_SBGGR10, - .name = "Bayer 10 BGBG.. GRGR..", - .bits_per_sample = 16, - .packing = SOC_MBUS_PACKING_EXTEND16, - .order = SOC_MBUS_ORDER_LE, - }, - }; static struct tegra_buffer *to_tegra_vb(struct vb2_buffer *vb) @@ -378,13 +359,9 @@ static struct tegra_buffer *to_tegra_vb(struct vb2_buffer *vb) static void tegra_camera_save_syncpts(struct tegra_camera_dev *pcdev) { - pcdev->syncpt_csi_a = + pcdev->syncpt_csi = nvhost_syncpt_read_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_A); - - pcdev->syncpt_csi_b = - nvhost_syncpt_read_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_B); + TEGRA_VI_SYNCPT_CSI); pcdev->syncpt_vi = nvhost_syncpt_read_ext(pcdev->ndev, @@ -394,65 +371,60 @@ static void tegra_camera_save_syncpts(struct tegra_camera_dev *pcdev) static void tegra_camera_incr_syncpts(struct tegra_camera_dev *pcdev) { nvhost_syncpt_cpu_incr_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_A); - - nvhost_syncpt_cpu_incr_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_B); + TEGRA_VI_SYNCPT_CSI); nvhost_syncpt_cpu_incr_ext(pcdev->ndev, TEGRA_VI_SYNCPT_VI); } -static void tegra_camera_capture_clean(struct tegra_camera_dev *pcdev) +static void tegra_camera_capture_setup_csi_a(struct tegra_camera_dev *pcdev, + int input_format, + int yuv_input_format) { - TC_VI_REG_WT(pcdev, TEGRA_CSI_VI_INPUT_STREAM_CONTROL, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_HOST_INPUT_STREAM_CONTROL, 0x00000000); + struct soc_camera_device *icd = pcdev->icd; + int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_PIXEL_PARSER_STATUS, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_CIL_STATUS, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_PIXEL_PARSER_INTERRUPT_MASK, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_CIL_INTERRUPT_MASK, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_READONLY_STATUS, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_ESCAPE_MODE_COMMAND, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_ESCAPE_MODE_DATA, 0x0); + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_CORE_CONTROL, 0x02000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_PAD_CONFIG, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_MIPI_CAL_STATUS, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CLKEN_OVERRIDE, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_INPUT_CONTROL, + (yuv_input_format << 8) | + (input_format << 2)); - TC_VI_REG_WT(pcdev, TEGRA_CSI_DEBUG_CONTROL, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_DEBUG_COUNTER_0, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_DEBUG_COUNTER_1, 0x0); - TC_VI_REG_WT(pcdev, TEGRA_CSI_DEBUG_COUNTER_2, 0x0); -} + TC_VI_REG_WT(pcdev, TEGRA_VI_H_DOWNSCALE_CONTROL, 0x00000004); + TC_VI_REG_WT(pcdev, TEGRA_VI_V_DOWNSCALE_CONTROL, 0x00000004); -static void tegra_camera_capture_setup_csi_a(struct tegra_camera_dev *pcdev, - struct soc_camera_device *icd, - u32 hdr) -{ - struct tegra_camera_platform_data *pdata = icd->link->priv; - int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, - icd->current_fmt->host_fmt); + /* CSI-A H_ACTIVE and V_ACTIVE */ + TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPA_H_ACTIVE, + (icd->user_width << 16)); + TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPA_V_ACTIVE, + (icd->user_height << 16)); + /* CSI A */ + TC_VI_REG_WT(pcdev, TEGRA_CSI_VI_INPUT_STREAM_CONTROL, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_HOST_INPUT_STREAM_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_INPUT_STREAM_A_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_CONTROL0, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_CONTROL1, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_WORD_COUNT, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_GAP, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CILA_CONTROL0, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_PAD_CONFIG0, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_PAD_CONFIG1, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_PAD_CONFIG, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_MIPI_CAL_CONFIG, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_EXPECTED_FRAME, 0x0); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_CORE_CONTROL, 0x02000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_MIPI_CAL_STATUS, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CLKEN_OVERRIDE, 0x00000000); - /* CSI-A H_ACTIVE and V_ACTIVE */ - TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPA_H_ACTIVE, - (icd->user_width << 16)); - TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPA_V_ACTIVE, - (icd->user_height << 16)); + /* pad1s enabled, virtual channel ID 00 */ + TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_CONTROL0, + (0x1 << 16) | /* Output 1 pixel per clock */ + (0x1e << 8) | /* If hdr shows wrong fmt, use YUV422 */ + (0x1 << 7) | /* Check header CRC */ + (0x1 << 6) | /* Use word count field in the header */ + (0x1 << 5) | /* Look at data identifier byte in hdr */ + (0x1 << 4)); /* Expect packet header */ TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_CONTROL1, 0x1); /* Frame # for top field detect for interlaced */ @@ -466,27 +438,22 @@ static void tegra_camera_capture_setup_csi_a(struct tegra_camera_dev *pcdev, (0x100 << 4) | /* Wait 0x100 vi clks for timeout */ 0x1); /* Enable line timeout */ - /* pad 0s enabled, virtual channel ID 00 */ - TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_A_CONTROL0, - (0x1 << 16) | /* Output 1 pixel per clock */ - (hdr << 8) | /* If hdr shows wrong fmt, use right value */ - (0x1 << 7) | /* Check header CRC */ - (0x1 << 6) | /* Use word count field in the header */ - (0x1 << 5) | /* Look at data identifier byte in hdr */ - (0x1 << 4)); /* Expect packet header */ - TC_VI_REG_WT(pcdev, TEGRA_CSI_INPUT_STREAM_A_CONTROL, (0x3f << 16) | /* Skip packet threshold */ - (pdata->lanes - 1)); + (pcdev->pdata->lanes - 1)); /* Use 0x00000022 for continuous clock mode. */ TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CILA_CONTROL0, - (pdata->continuous_clk << 5) | + (pcdev->pdata->continuous_clk << 5) | 0x5); /* Clock settle time */ TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_CSI_PPA_FRAME_END, (0x1 << 8) | /* Enable continuous syncpt */ - TEGRA_VI_SYNCPT_CSI_A); + TEGRA_VI_SYNCPT_CSI); + + TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_OUT_1, + (0x1 << 8) | /* Enable continuous syncpt */ + TEGRA_VI_SYNCPT_VI); TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CIL_COMMAND, 0x00020001); @@ -494,37 +461,49 @@ static void tegra_camera_capture_setup_csi_a(struct tegra_camera_dev *pcdev, } static void tegra_camera_capture_setup_csi_b(struct tegra_camera_dev *pcdev, - struct soc_camera_device *icd, - u32 hdr) + int input_format, + int yuv_input_format) { - struct tegra_camera_platform_data *pdata = icd->link->priv; + struct soc_camera_device *icd = pcdev->icd; int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, icd->current_fmt->host_fmt); + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_CORE_CONTROL, 0x04000000); + + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_INPUT_CONTROL, + (yuv_input_format << 8) | + (input_format << 2)); + + TC_VI_REG_WT(pcdev, TEGRA_VI_H_DOWNSCALE_CONTROL, 0x00000008); + TC_VI_REG_WT(pcdev, TEGRA_VI_V_DOWNSCALE_CONTROL, 0x00000008); + + /* CSI-B H_ACTIVE and V_ACTIVE */ + TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPB_H_ACTIVE, + (icd->user_width << 16)); + TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPB_V_ACTIVE, + (icd->user_height << 16)); + + /* CSI B */ + TC_VI_REG_WT(pcdev, TEGRA_CSI_VI_INPUT_STREAM_CONTROL, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_HOST_INPUT_STREAM_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_INPUT_STREAM_B_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_CONTROL0, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_CONTROL1, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_WORD_COUNT, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_GAP, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CILB_CONTROL0, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CILB_PAD_CONFIG0, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_CILB_PAD_CONFIG1, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_PAD_CONFIG, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_CSI_CILB_MIPI_CAL_CONFIG, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_EXPECTED_FRAME, 0x0); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_CORE_CONTROL, 0x04000000); - - /* CSI-B H_ACTIVE and V_ACTIVE */ - TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPB_H_ACTIVE, - (icd->user_width << 16)); - TC_VI_REG_WT(pcdev, TEGRA_VI_CSI_PPB_V_ACTIVE, - (icd->user_height << 16)); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CIL_MIPI_CAL_STATUS, 0x00000000); + TC_VI_REG_WT(pcdev, TEGRA_CSI_CLKEN_OVERRIDE, 0x00000000); - /* pad 0s enabled, virtual channel ID 00 */ + /* pad1s enabled, virtual channel ID 00 */ TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_B_CONTROL0, (0x1 << 16) | /* Output 1 pixel per clock */ - (hdr << 8) | /* If hdr shows wrong fmt, use right value */ + (0x1e << 8) | /* If hdr shows wrong fmt, use YUV422 */ (0x1 << 7) | /* Check header CRC */ (0x1 << 6) | /* Use word count field in the header */ (0x1 << 5) | /* Look at data identifier byte in hdr */ @@ -545,16 +524,20 @@ static void tegra_camera_capture_setup_csi_b(struct tegra_camera_dev *pcdev, TC_VI_REG_WT(pcdev, TEGRA_CSI_INPUT_STREAM_B_CONTROL, (0x3f << 16) | /* Skip packet threshold */ - (pdata->lanes - 1)); + (pcdev->pdata->lanes - 1)); /* Use 0x00000022 for continuous clock mode. */ TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CILB_CONTROL0, - (pdata->continuous_clk << 5) | + (pcdev->pdata->continuous_clk << 5) | 0x5); /* Clock settle time */ TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_CSI_PPB_FRAME_END, (0x1 << 8) | /* Enable continuous syncpt */ - TEGRA_VI_SYNCPT_CSI_B); + TEGRA_VI_SYNCPT_CSI); + + TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_OUT_1, + (0x1 << 8) | /* Enable continuous syncpt */ + TEGRA_VI_SYNCPT_VI); TC_VI_REG_WT(pcdev, TEGRA_CSI_PHY_CIL_COMMAND, 0x00010002); @@ -562,17 +545,22 @@ static void tegra_camera_capture_setup_csi_b(struct tegra_camera_dev *pcdev, } static void tegra_camera_capture_setup_vip(struct tegra_camera_dev *pcdev, - struct soc_camera_device *icd, - u32 input_control) + int input_format, + int yuv_input_format) { + struct soc_camera_device *icd = pcdev->icd; TC_VI_REG_WT(pcdev, TEGRA_VI_VI_CORE_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_VI_VI_INPUT_CONTROL, - (1 << 27) | /* field detect */ - (1 << 25) | /* hsync/vsync decoded from data (BT.656) */ +// (1 << 27) | /* field detect */ + (0 << 28) | /* 1 == top field is even field, 00 == odd */ + ((internal_sync == 1) << 25) | /* 1 == hsync/vsync decoded + internally from data + (BT.656) */ + (yuv_input_format << 8) | (1 << 1) | /* VIP_INPUT_ENABLE */ - input_control); + (input_format << 2)); TC_VI_REG_WT(pcdev, TEGRA_VI_H_DOWNSCALE_CONTROL, 0x00000000); TC_VI_REG_WT(pcdev, TEGRA_VI_V_DOWNSCALE_CONTROL, 0x00000000); @@ -580,180 +568,80 @@ static void tegra_camera_capture_setup_vip(struct tegra_camera_dev *pcdev, /* VIP H_ACTIVE and V_ACTIVE */ TC_VI_REG_WT(pcdev, TEGRA_VI_VIP_H_ACTIVE, (icd->user_width << 16) | - TEGRA_VIP_H_ACTIVE_START); + (TEGRA_VIP_H_ACTIVE_START - ((internal_sync == 1)?1:0))); TC_VI_REG_WT(pcdev, TEGRA_VI_VIP_V_ACTIVE, - (icd->user_height << 16) | + ( ( IS_INTERLACED ? (icd->user_height/2) : (icd->user_height) ) << 16) | TEGRA_VIP_V_ACTIVE_START); /* * For VIP, D9..D2 is mapped to the video decoder's P7..P0. * Disable/mask out the other Dn wires. */ - TC_VI_REG_WT(pcdev, TEGRA_VI_PIN_INPUT_ENABLE, 0x000003fc); + TC_VI_REG_WT(pcdev, TEGRA_VI_PIN_INPUT_ENABLE, 0x000003fc | 0x6000); // D2..D9 + VSYNC + HSYNC TC_VI_REG_WT(pcdev, TEGRA_VI_VI_DATA_INPUT_CONTROL, 0x000003fc); TC_VI_REG_WT(pcdev, TEGRA_VI_PIN_INVERSION, 0x00000000); - TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_VIP_VSYNC, + TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_OUT_1, (0x1 << 8) | /* Enable continuous syncpt */ TEGRA_VI_SYNCPT_VI); - TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, 0x00000004); +// TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, 0x00000004); } -static int tegra_camera_capture_output_channel_setup( - struct tegra_camera_dev *pcdev, - struct soc_camera_device *icd) +static void tegra_camera_capture_setup(struct tegra_camera_dev *pcdev) { - struct tegra_camera_platform_data *pdata = icd->link->priv; - int port = pdata->port; - int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, - icd->current_fmt->host_fmt); + struct soc_camera_device *icd = pcdev->icd; const struct soc_camera_format_xlate *current_fmt = icd->current_fmt; + enum v4l2_mbus_pixelcode input_code = current_fmt->code; u32 output_fourcc = current_fmt->host_fmt->fourcc; - u32 output_format, output_control; - struct tegra_buffer *buf = to_tegra_vb(pcdev->active); + int yuv_input_format = 0x0; + int input_format = 0x0; /* Default to YUV422 */ + int yuv_output_format = 0x0; + int output_format = 0x3; /* Default to YUV422 */ + int port = pcdev->pdata->port; + int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + int frame_count = 1; + + switch (input_code) { + case V4L2_MBUS_FMT_UYVY8_2X8: + yuv_input_format = 0x2; + break; + case V4L2_MBUS_FMT_VYUY8_2X8: + yuv_input_format = 0x3; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + yuv_input_format = 0x0; + break; + case V4L2_MBUS_FMT_YVYU8_2X8: + yuv_input_format = 0x1; + break; + default: + BUG_ON(1); + } switch (output_fourcc) { case V4L2_PIX_FMT_UYVY: - output_format = 0x3; /* Default to YUV422 */ + yuv_output_format = 0x0; break; case V4L2_PIX_FMT_VYUY: - output_format = (0x1 << 17) | 0x3; + yuv_output_format = 0x1; break; case V4L2_PIX_FMT_YUYV: - output_format = (0x2 << 17) | 0x3; + yuv_output_format = 0x2; break; case V4L2_PIX_FMT_YVYU: - output_format = (0x3 << 17) | 0x3; + yuv_output_format = 0x3; break; case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: output_format = 0x6; /* YUV420 planar */ break; - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SBGGR10: - /* Use second output channel for RAW8/RAW10 */ - buf->output_channel = 1; - - if (port == TEGRA_CAMERA_PORT_CSI_A) - output_format = 0x7; - else if (port == TEGRA_CAMERA_PORT_CSI_B) - output_format = 0x8; - else - output_format = 0x9; - break; default: - dev_err(&pcdev->ndev->dev, "Wrong output format %d\n", - output_fourcc); - return -EINVAL; + BUG_ON(1); } - output_control = (pdata->flip_v ? (0x1 << 20) : 0) | - (pdata->flip_h ? (0x1 << 19) : 0) | - output_format; - - if (buf->output_channel == 0) { - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_FIRST_OUTPUT_CONTROL, - output_control); - /* - * Set up frame size. Bits 31:16 are the number of lines, and - * bits 15:0 are the number of pixels per line. - */ - TC_VI_REG_WT(pcdev, TEGRA_VI_FIRST_OUTPUT_FRAME_SIZE, - (icd->user_height << 16) | icd->user_width); - - /* First output memory enabled */ - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_ENABLE, 0x00000000); - - /* Set the number of frames in the buffer. */ - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_COUNT_FIRST, 0x00000001); - - /* Set up buffer frame size. */ - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_SIZE_FIRST, - (icd->user_height << 16) | icd->user_width); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BUFFER_STRIDE_FIRST, - (icd->user_height * bytes_per_line)); - - TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_OUT_1, - (0x1 << 8) | /* Enable continuous syncpt */ - TEGRA_VI_SYNCPT_VI); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_ENABLE, 0x00000000); - } else if (buf->output_channel == 1) { - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_SECOND_OUTPUT_CONTROL, - output_control); - - TC_VI_REG_WT(pcdev, TEGRA_VI_SECOND_OUTPUT_FRAME_SIZE, - (icd->user_height << 16) | icd->user_width); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_ENABLE_2, 0x00000000); - - /* Set the number of frames in the buffer. */ - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_COUNT_SECOND, 0x00000001); - - /* Set up buffer frame size. */ - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_SIZE_SECOND, - (icd->user_height << 16) | icd->user_width); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BUFFER_STRIDE_SECOND, - (icd->user_height * bytes_per_line)); - - TC_VI_REG_WT(pcdev, TEGRA_VI_CONT_SYNCPT_OUT_2, - (0x1 << 8) | /* Enable continuous syncpt */ - TEGRA_VI_SYNCPT_VI); - - TC_VI_REG_WT(pcdev, TEGRA_VI_VI_ENABLE_2, 0x00000000); - } else { - dev_err(&pcdev->ndev->dev, "Wrong output channel %d\n", - buf->output_channel); - return -EINVAL; - } - - return 0; -} - -static int tegra_camera_capture_setup(struct tegra_camera_dev *pcdev) -{ - struct vb2_buffer *vb = pcdev->active; - struct tegra_buffer *buf = to_tegra_vb(vb); - struct soc_camera_device *icd = buf->icd; - struct tegra_camera_platform_data *pdata = icd->link->priv; - int port = pdata->port; - const struct soc_camera_format_xlate *current_fmt = icd->current_fmt; - enum v4l2_mbus_pixelcode input_code = current_fmt->code; - u32 hdr, input_control = 0x0; - - switch (input_code) { - case V4L2_MBUS_FMT_UYVY8_2X8: - input_control |= 0x2 << 8; - hdr = 30; - break; - case V4L2_MBUS_FMT_VYUY8_2X8: - input_control |= 0x3 << 8; - hdr = 30; - break; - case V4L2_MBUS_FMT_YUYV8_2X8: - input_control |= 0x0; - hdr = 30; - break; - case V4L2_MBUS_FMT_YVYU8_2X8: - input_control |= 0x1 << 8; - hdr = 30; - break; - case V4L2_MBUS_FMT_SBGGR8_1X8: - input_control |= 0x2 << 2; /* Input Format = Bayer */ - hdr = 42; - break; - case V4L2_MBUS_FMT_SBGGR10_1X10: - input_control |= 0x2 << 2; /* Input Format = Bayer */ - hdr = 43; - break; - default: - dev_err(&pcdev->ndev->dev, "Input format %d is not supported\n", - input_code); - return -EINVAL; - } + BUG_ON(!tegra_camera_port_is_valid(port)); /* * Set up low pass filter. Use 0x240 for chromaticity and 0x240 @@ -765,25 +653,98 @@ static int tegra_camera_capture_setup(struct tegra_camera_dev *pcdev) /* Set up raise-on-edge, so we get an interrupt on end of frame. */ TC_VI_REG_WT(pcdev, TEGRA_VI_VI_RAISE, 0x00000001); - /* Cleanup registers */ - tegra_camera_capture_clean(pcdev); - - /* Setup registers for CSI-A, CSI-B and VIP inputs */ if (port == TEGRA_CAMERA_PORT_CSI_A) - tegra_camera_capture_setup_csi_a(pcdev, icd, hdr); + tegra_camera_capture_setup_csi_a(pcdev, input_format, + yuv_input_format); else if (port == TEGRA_CAMERA_PORT_CSI_B) - tegra_camera_capture_setup_csi_b(pcdev, icd, hdr); + tegra_camera_capture_setup_csi_b(pcdev, input_format, + yuv_input_format); else - tegra_camera_capture_setup_vip(pcdev, icd, input_control); + tegra_camera_capture_setup_vip(pcdev, input_format, + yuv_input_format); + + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_FIRST_OUTPUT_CONTROL, + (pcdev->pdata->flip_v ? (0x1 << 20) : 0) | + (pcdev->pdata->flip_h ? (0x1 << 19) : 0) | + (yuv_output_format << 17) | + output_format); + + // if the video is interlaced, then take two frames + frame_count = IS_INTERLACED ? 2 : 1; + + /* + * Set up frame size. Bits 31:16 are the number of lines, and + * bits 15:0 are the number of pixels per line. + */ + TC_VI_REG_WT(pcdev, TEGRA_VI_FIRST_OUTPUT_FRAME_SIZE, + ((icd->user_height/frame_count) << 16) | icd->user_width); + + /* Set the number of frames in the buffer. */ + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_COUNT_FIRST, frame_count); + + /* Set up buffer frame size. */ + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_SIZE_FIRST, + ((icd->user_height/frame_count) << 16) | icd->user_width); + if(output_fourcc == V4L2_PIX_FMT_YUV420 || output_fourcc == V4L2_PIX_FMT_YVU420) { + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BUFFER_STRIDE_FIRST, + ((icd->user_height/frame_count) * icd->user_width) | (2<<30)); + } + else { + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BUFFER_STRIDE_FIRST, + ((icd->user_height/frame_count) * bytes_per_line) | (2<<30)); + } + + /* First output memory enabled */ + TC_VI_REG_WT(pcdev, TEGRA_VI_VI_ENABLE, 0x00000000); - /* Setup registers for output channels */ - return tegra_camera_capture_output_channel_setup(pcdev, icd); } -static int tegra_camera_capture_buffer_setup(struct tegra_camera_dev *pcdev, - struct tegra_buffer *buf) +// +// make_interlaced +// +// make one interlaced frame out of two frames in the buffer @ addr +// this function must be issued for Y, U and V buffers. +// +static void make_interlaced(uint8_t * addr, int width, int height) { + uint8_t starting_line_buf[width]; + bool lines[height]; + int i; + int destination_line, starting_line; + for (i = 1; i < height-1; i++) lines[i] = false; + lines[0] = true; + lines[height-1] = true; + + #define real_line(i) ( (i % 2) ? ((i / 2) + (height/2)) : (i / 2) ) + while (1) { + starting_line = -1; + for (i = 1; i < ((height)-1); i++) if (!lines[i]) { + starting_line = i; + break; + } + if (starting_line == -1) break; // all is done + + memcpy(starting_line_buf, (void*) ((unsigned int)(addr) + (width*starting_line)), width); + destination_line = starting_line; + while (1) { + lines[destination_line] = true; + if (real_line(destination_line) == starting_line) { + memcpy(addr + width * destination_line, starting_line_buf, width); + break; + } + memcpy(addr + (width * destination_line), (void*) ((unsigned int)(addr) + (width * real_line(destination_line))), width); + destination_line = real_line(destination_line); + } + } +} + +static int tegra_camera_capture_start(struct tegra_camera_dev *pcdev, + struct tegra_buffer *buf) { - struct soc_camera_device *icd = buf->icd; + struct soc_camera_device *icd = pcdev->icd; + int port = pcdev->pdata->port; + int err; + int bytes_per_line; + uint8_t *src; switch (icd->current_fmt->host_fmt->fourcc) { case V4L2_PIX_FMT_YUV420: @@ -802,90 +763,87 @@ static int tegra_camera_capture_buffer_setup(struct tegra_camera_dev *pcdev, case V4L2_PIX_FMT_VYUY: case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SBGGR10: - /* output 1 */ - if (buf->output_channel == 0) { - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BASE_ADDRESS_FIRST, - buf->buffer_addr); - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_START_ADDRESS_FIRST, - buf->start_addr); - /* output 2 */ - } else if (buf->output_channel == 1) { - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BASE_ADDRESS_SECOND, - buf->buffer_addr); - TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_START_ADDRESS_SECOND, - buf->start_addr); - } else { - dev_err(&pcdev->ndev->dev, "Wrong output channel %d\n", - buf->output_channel); - return -EINVAL; - } - break; + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_BASE_ADDRESS_FIRST, + buf->buffer_addr); + TC_VI_REG_WT(pcdev, TEGRA_VI_VB0_START_ADDRESS_FIRST, + buf->start_addr); + + break; default: - dev_err(&pcdev->ndev->dev, "Wrong host format %d\n", - icd->current_fmt->host_fmt->fourcc); - return -EINVAL; + BUG_ON(1); } - return 0; -} + BUG_ON(!tegra_camera_port_is_valid(port)); -static int tegra_camera_capture_start(struct tegra_camera_dev *pcdev, - struct tegra_buffer *buf) -{ - struct soc_camera_device *icd = buf->icd; - struct tegra_camera_platform_data *pdata = icd->link->priv; - int port = pdata->port; - int err; + if (port == TEGRA_CAMERA_PORT_CSI_A) + TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, + 0x0000f005); + else if (port == TEGRA_CAMERA_PORT_CSI_B) + TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, + 0x0000f005); + else + TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, + 0x00000005); // start & stop - err = tegra_camera_capture_buffer_setup(pcdev, buf); - if (err < 0) - return err; /* * Only wait on CSI frame end syncpt if we're using CSI. Otherwise, * wait on VIP VSYNC syncpt. */ - if (port == TEGRA_CAMERA_PORT_CSI_A) { - pcdev->syncpt_csi_a++; - TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, - 0x0000f005); - err = nvhost_syncpt_wait_timeout_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_A, - pcdev->syncpt_csi_a, - TEGRA_SYNCPT_CSI_WAIT_TIMEOUT, - NULL); - } else if (port == TEGRA_CAMERA_PORT_CSI_B) { - pcdev->syncpt_csi_b++; - TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, - 0x0000f005); + + pcdev->syncpt_vi++; + pcdev->syncpt_csi++; + + if (tegra_camera_port_is_csi(port)) err = nvhost_syncpt_wait_timeout_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_CSI_B, - pcdev->syncpt_csi_b, - TEGRA_SYNCPT_CSI_WAIT_TIMEOUT, - NULL); - } else { - pcdev->syncpt_vi++; - TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, - 0x00000001); + TEGRA_VI_SYNCPT_CSI, + pcdev->syncpt_csi, + TEGRA_SYNCPT_CSI_WAIT_TIMEOUT, + NULL); + else err = nvhost_syncpt_wait_timeout_ext(pcdev->ndev, - TEGRA_VI_SYNCPT_VI, - pcdev->syncpt_csi_a, - TEGRA_SYNCPT_VI_WAIT_TIMEOUT, - NULL); - } + TEGRA_VI_SYNCPT_VI, + pcdev->syncpt_vi, + TEGRA_SYNCPT_VI_WAIT_TIMEOUT, + NULL); - if (!err) - return 0; + if (IS_INTERLACED) { + + TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, + 0x00000005); // start & stop + + pcdev->syncpt_vi++; + pcdev->syncpt_csi++; + + err = nvhost_syncpt_wait_timeout_ext(pcdev->ndev, + TEGRA_VI_SYNCPT_VI, + pcdev->syncpt_vi, + TEGRA_SYNCPT_VI_WAIT_TIMEOUT, + NULL); + + src = (uint8_t*)(buf->virtual_addr); + if(icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_YUV420 || icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_YVU420 ) { + make_interlaced(src, icd->user_width, icd->user_height); // Y + make_interlaced(src + icd->user_width * icd->user_height, icd->user_width, icd->user_height/4); // U + make_interlaced(src + icd->user_width * icd->user_height + (icd->user_width * icd->user_height)/4, icd->user_width, icd->user_height/4); // V + } + else { + bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + make_interlaced(src, bytes_per_line, icd->user_height); // Y + } + } + + if (!err) + return 0; if (tegra_camera_port_is_csi(port)) { u32 ppstatus; u32 cilstatus; u32 rostatus; - dev_warn(&icd->vdev->dev, "Timeout on CSI syncpt\n"); - dev_warn(&icd->vdev->dev, "buffer_addr = 0x%08x\n", + dev_err(&pcdev->ndev->dev, "Timeout on CSI syncpt\n"); + dev_err(&pcdev->ndev->dev, "buffer_addr = 0x%08x\n", buf->buffer_addr); ppstatus = TC_VI_REG_RD(pcdev, @@ -895,7 +853,7 @@ static int tegra_camera_capture_start(struct tegra_camera_dev *pcdev, rostatus = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_READONLY_STATUS); - dev_warn(&icd->vdev->dev, + dev_err(&pcdev->ndev->dev, "PPSTATUS = 0x%08x, " "CILSTATUS = 0x%08x, " "ROSTATUS = 0x%08x\n", @@ -903,14 +861,14 @@ static int tegra_camera_capture_start(struct tegra_camera_dev *pcdev, } else { u32 vip_input_status; - dev_warn(&pcdev->ndev->dev, "Timeout on VI syncpt\n"); - dev_warn(&pcdev->ndev->dev, "buffer_addr = 0x%08x\n", + dev_err(&pcdev->ndev->dev, "Timeout on VI syncpt\n"); + dev_err(&pcdev->ndev->dev, "buffer_addr = 0x%08x\n", buf->buffer_addr); vip_input_status = TC_VI_REG_RD(pcdev, TEGRA_VI_VIP_INPUT_STATUS); - dev_warn(&pcdev->ndev->dev, + dev_err(&pcdev->ndev->dev, "VIP_INPUT_STATUS = 0x%08x\n", vip_input_status); } @@ -918,10 +876,12 @@ static int tegra_camera_capture_start(struct tegra_camera_dev *pcdev, return err; } -static int tegra_camera_capture_stop(struct tegra_camera_dev *pcdev, int port) +static int tegra_camera_capture_stop(struct tegra_camera_dev *pcdev) { + int port = pcdev->pdata->port; int err; - struct tegra_buffer *buf = to_tegra_vb(pcdev->active); + + BUG_ON(!tegra_camera_port_is_valid(port)); if (port == TEGRA_CAMERA_PORT_CSI_A) TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, @@ -929,9 +889,9 @@ static int tegra_camera_capture_stop(struct tegra_camera_dev *pcdev, int port) else if (port == TEGRA_CAMERA_PORT_CSI_B) TC_VI_REG_WT(pcdev, TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, 0x0000f002); - else - TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, - 0x00000005); +// else +// TC_VI_REG_WT(pcdev, TEGRA_VI_CAMERA_CONTROL, +// 0x00000005); if (tegra_camera_port_is_csi(port)) err = nvhost_syncpt_wait_timeout_ext(pcdev->ndev, @@ -947,28 +907,17 @@ static int tegra_camera_capture_stop(struct tegra_camera_dev *pcdev, int port) u32 ppstatus; u32 cilstatus; - dev_warn(&pcdev->ndev->dev, "Timeout on VI syncpt\n"); - - if (buf->output_channel == 0) - buffer_addr = TC_VI_REG_RD(pcdev, + dev_err(&pcdev->ndev->dev, "Timeout on VI syncpt\n"); + buffer_addr = TC_VI_REG_RD(pcdev, TEGRA_VI_VB0_BASE_ADDRESS_FIRST); - else if (buf->output_channel == 1) - buffer_addr = TC_VI_REG_RD(pcdev, - TEGRA_VI_VB0_BASE_ADDRESS_SECOND); - else { - dev_err(&pcdev->ndev->dev, "Wrong output channel %d\n", - buf->output_channel); - return -EINVAL; - } - - dev_warn(&pcdev->ndev->dev, "buffer_addr = 0x%08x\n", + dev_err(&pcdev->ndev->dev, "buffer_addr = 0x%08x\n", buffer_addr); ppstatus = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_PIXEL_PARSER_STATUS); cilstatus = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_CIL_STATUS); - dev_warn(&pcdev->ndev->dev, + dev_err(&pcdev->ndev->dev, "PPSTATUS = 0x%08x, CILSTATUS = 0x%08x\n", ppstatus, cilstatus); } @@ -976,132 +925,81 @@ static int tegra_camera_capture_stop(struct tegra_camera_dev *pcdev, int port) return err; } -static void tegra_camera_activate(struct tegra_camera_dev *pcdev) -{ - nvhost_module_busy_ext(pcdev->ndev); - - /* Enable external power */ - regulator_enable(pcdev->reg); - - /* Unpowergate VE */ - tegra_unpowergate_partition(TEGRA_POWERGATE_VENC); - - /* Turn on relevant clocks. */ - clk_set_rate(pcdev->clk_vi, 150000000); - clk_enable(pcdev->clk_vi); - clk_set_rate(pcdev->clk_vi_sensor, 24000000); - clk_enable(pcdev->clk_vi_sensor); - clk_enable(pcdev->clk_csi); - clk_enable(pcdev->clk_isp); - clk_enable(pcdev->clk_csus); - clk_set_rate(pcdev->clk_sclk, 80000000); - clk_enable(pcdev->clk_sclk); - clk_set_rate(pcdev->clk_sclk, 375000000); - clk_enable(pcdev->clk_emc); - - /* Save current syncpt values. */ - tegra_camera_save_syncpts(pcdev); -} - -static void tegra_camera_deactivate(struct tegra_camera_dev *pcdev) -{ - /* Turn off relevant clocks. */ - clk_disable(pcdev->clk_vi); - clk_disable(pcdev->clk_vi_sensor); - clk_disable(pcdev->clk_csi); - clk_disable(pcdev->clk_isp); - clk_disable(pcdev->clk_csus); - clk_disable(pcdev->clk_sclk); - clk_disable(pcdev->clk_emc); - - /* Powergate VE */ - tegra_powergate_partition(TEGRA_POWERGATE_VENC); - - /* Disable external power */ - regulator_disable(pcdev->reg); - - nvhost_module_idle_ext(pcdev->ndev); - - pcdev->cal_done = 0; -} - static int tegra_camera_capture_frame(struct tegra_camera_dev *pcdev) { - struct vb2_buffer *vb = pcdev->active; - struct tegra_buffer *buf = to_tegra_vb(vb); - struct soc_camera_device *icd = buf->icd; - struct tegra_camera_platform_data *pdata = icd->link->priv; - int port = pdata->port; + struct vb2_buffer *vb; + struct tegra_buffer *buf; int retry = TEGRA_SYNCPT_RETRY_COUNT; - int err; + int port = pcdev->pdata->port; + int err = 0; - while (retry) { - err = tegra_camera_capture_start(pcdev, buf); - /* Capturing succeed, stop capturing */ - if (!err) - err = tegra_camera_capture_stop(pcdev, port); - /* Capturing failed, stop and retry */ - else { - retry--; - - /* Stop streaming. */ - if (port == TEGRA_CAMERA_PORT_CSI_A) { - TC_VI_REG_WT(pcdev, - TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, - 0x0000f002); - /* Clear status registers. */ - TC_VI_REG_WT(pcdev, - TEGRA_CSI_CSI_PIXEL_PARSER_STATUS, - 0xffffffff); - TC_VI_REG_WT(pcdev, - TEGRA_CSI_CSI_CIL_STATUS, - 0xffffffff); - } else if (port == TEGRA_CAMERA_PORT_CSI_B) { - TC_VI_REG_WT(pcdev, - TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, - 0x0000f002); - /* Clear status registers. */ - TC_VI_REG_WT(pcdev, - TEGRA_CSI_CSI_PIXEL_PARSER_STATUS, - 0xffffffff); - TC_VI_REG_WT(pcdev, - TEGRA_CSI_CSI_CIL_STATUS, - 0xffffffff); - } else { - TC_VI_REG_WT(pcdev, - TEGRA_VI_CAMERA_CONTROL, - 0x00000005); - } + if (!pcdev->active) + return 0; - tegra_camera_incr_syncpts(pcdev); - tegra_camera_save_syncpts(pcdev); + vb = pcdev->active; + buf = to_tegra_vb(vb); - continue; + while (retry--) { + err = tegra_camera_capture_start(pcdev, buf); + if (err == 0) { + err = tegra_camera_capture_stop(pcdev); + if (err == 0) break; } - break; - } + /* Stop streaming. */ + if (port == TEGRA_CAMERA_PORT_CSI_A) { + TC_VI_REG_WT(pcdev, + TEGRA_CSI_PIXEL_STREAM_PPA_COMMAND, + 0x0000f002); + /* Clear status registers. */ + TC_VI_REG_WT(pcdev, + TEGRA_CSI_CSI_PIXEL_PARSER_STATUS, + 0xffffffff); + TC_VI_REG_WT(pcdev, + TEGRA_CSI_CSI_CIL_STATUS, + 0xffffffff); + } else if (port == TEGRA_CAMERA_PORT_CSI_B) { + TC_VI_REG_WT(pcdev, + TEGRA_CSI_PIXEL_STREAM_PPB_COMMAND, + 0x0000f002); + /* Clear status registers. */ + TC_VI_REG_WT(pcdev, + TEGRA_CSI_CSI_PIXEL_PARSER_STATUS, + 0xffffffff); + TC_VI_REG_WT(pcdev, + TEGRA_CSI_CSI_CIL_STATUS, + 0xffffffff); + } else { + TC_VI_REG_WT(pcdev, + TEGRA_VI_CAMERA_CONTROL, + 0x00000005); + } - /* Reset hardware for too many errors */ - if (!retry) { - tegra_camera_deactivate(pcdev); - mdelay(5); - tegra_camera_activate(pcdev); - if (pcdev->active) - tegra_camera_capture_setup(pcdev); + tegra_camera_incr_syncpts(pcdev); + tegra_camera_save_syncpts(pcdev); + continue; } spin_lock_irq(&pcdev->videobuf_queue_lock); + /* + * If vb->state is VB2_BUF_STATE_ERROR, then the vb has already been + * removed, so we shouldn't remove it again. + */ + if (vb->state != VB2_BUF_STATE_ERROR) + list_del_init(&buf->queue); + + if (!list_empty(&pcdev->capture)) + pcdev->active = &list_entry(pcdev->capture.next, + struct tegra_buffer, queue)->vb; + else + pcdev->active = NULL; + do_gettimeofday(&vb->v4l2_buf.timestamp); vb->v4l2_buf.field = pcdev->field; - if (port == TEGRA_CAMERA_PORT_CSI_A) - vb->v4l2_buf.sequence = pcdev->sequence_a++; - else if (port == TEGRA_CAMERA_PORT_CSI_B) - vb->v4l2_buf.sequence = pcdev->sequence_b++; + vb->v4l2_buf.sequence = pcdev->sequence++; - vb2_buffer_done(vb, err < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); - list_del_init(&buf->queue); + vb2_buffer_done(vb, (err != 0) ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); pcdev->num_frames++; @@ -1110,145 +1008,101 @@ static int tegra_camera_capture_frame(struct tegra_camera_dev *pcdev) return err; } -static int tegra_camera_csi_pad_calibration(struct tegra_camera_dev *pcdev) +static void tegra_camera_work(struct work_struct *work) { - struct vb2_buffer *vb = pcdev->active; - struct tegra_buffer *buf = to_tegra_vb(vb); - struct soc_camera_device *icd = buf->icd; - struct tegra_camera_platform_data *pdata = icd->link->priv; - int port = pdata->port; - int retry = 500; - u32 data; - int err = -EINVAL; - - TC_VI_REG_WT(pcdev, TEGRA_CSI_DSI_MIPI_CAL_CONFIG, 0x40300); - TC_VI_REG_WT(pcdev, TEGRA_CSI_MIPIBIAS_PAD_CONFIG0, 0x50700); - - if (port == TEGRA_CAMERA_PORT_CSI_B || pdata->lanes == 4) - TC_VI_REG_WT(pcdev, TEGRA_CSI_CILB_MIPI_CAL_CONFIG, 0x200004); - - data = 0x2a000004; - if (port == TEGRA_CAMERA_PORT_CSI_A) - data |= 0x200000; /* MIPI_CAL_SELA */ - TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_MIPI_CAL_CONFIG, data); - - /* Start calibration */ - data |= 0x80000000; - TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_MIPI_CAL_CONFIG, data); + struct tegra_camera_dev *pcdev = + container_of(work, struct tegra_camera_dev, work); - while (retry--) { - data = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_CIL_STATUS); - if ((data & 0x8000) == 0x8000) - break; - udelay(20); - } - if (!retry) { - dev_warn(&pcdev->ndev->dev, "MIPI calibration timeout!\n"); - goto cal_out; - } + mutex_lock(&pcdev->work_mutex); - /* Clear status */ - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_CIL_STATUS, data); - retry = 500; - while (retry--) { - data = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_CIL_STATUS); - if ((data & 0x8000) == 0x0) - break; - udelay(20); - } + while (pcdev->active) + tegra_camera_capture_frame(pcdev); - if (!retry) { - dev_warn(&pcdev->ndev->dev, - "Clear MIPI calibration status timeout!\n"); - goto cal_out; - } + mutex_unlock(&pcdev->work_mutex); +} - data = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_PIXEL_PARSER_STATUS); - err = TC_VI_REG_RD(pcdev, TEGRA_CSI_CSI_CIL_STATUS); - if (data | err) { - dev_warn(&pcdev->ndev->dev, - "Calibration status not be cleared!\n"); - err = -EINVAL; - goto cal_out; - } +static void tegra_camera_activate(struct tegra_camera_dev *pcdev) +{ +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + u32 val; + void __iomem *apb_misc; +#endif - /* Calibration succeed */ - err = 0; + nvhost_module_busy_ext(pcdev->ndev); -cal_out: - TC_VI_REG_WT(pcdev, TEGRA_CSI_CSI_CIL_STATUS, data); +#ifndef CONFIG_ARCH_TEGRA_2x_SOC + if(!tegra_powergate_is_powered(TEGRA_POWERGATE_VENC)) + tegra_unpowergate_partition(TEGRA_POWERGATE_VENC); +#endif - /* un-select to avoid interference with DSI */ - if (port == TEGRA_CAMERA_PORT_CSI_B || pdata->lanes == 4) - TC_VI_REG_WT(pcdev, TEGRA_CSI_CILB_MIPI_CAL_CONFIG, 0x4); + /* Turn on relevant clocks. */ + clk_enable(pcdev->clk_vi); + clk_enable(pcdev->clk_vi_sensor); + clk_enable(pcdev->clk_csi); + clk_enable(pcdev->clk_isp); + clk_enable(pcdev->clk_csus); - TC_VI_REG_WT(pcdev, TEGRA_CSI_CILA_MIPI_CAL_CONFIG, 0x2a000004); +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + apb_misc = IO_ADDRESS(TEGRA_APB_MISC_BASE); + val = readl(apb_misc + 0x42c); + writel(val | 0x1, apb_misc + 0x42c); +#endif - return err; + /* Save current syncpt values. */ + tegra_camera_save_syncpts(pcdev); } -static void tegra_camera_work(struct work_struct *work) +static void tegra_camera_deactivate(struct tegra_camera_dev *pcdev) { - struct tegra_camera_dev *pcdev = - container_of(work, struct tegra_camera_dev, work); - struct tegra_buffer *buf; + mutex_lock(&pcdev->work_mutex); - while (1) { - mutex_lock(&pcdev->work_mutex); - - spin_lock_irq(&pcdev->videobuf_queue_lock); - if (list_empty(&pcdev->capture)) { - pcdev->active = NULL; - spin_unlock_irq(&pcdev->videobuf_queue_lock); - mutex_unlock(&pcdev->work_mutex); - return; - } + /* Cancel active buffer. */ + if (pcdev->active) { + list_del_init(&to_tegra_vb(pcdev->active)->queue); + vb2_buffer_done(pcdev->active, VB2_BUF_STATE_ERROR); + pcdev->active = NULL; + } - buf = list_entry(pcdev->capture.next, struct tegra_buffer, - queue); - pcdev->active = &buf->vb; - spin_unlock_irq(&pcdev->videobuf_queue_lock); + mutex_unlock(&pcdev->work_mutex); - tegra_camera_capture_setup(pcdev); - if (!pcdev->cal_done) { - tegra_camera_csi_pad_calibration(pcdev); - pcdev->cal_done = 1; - } - tegra_camera_capture_frame(pcdev); + /* Turn off relevant clocks. */ + clk_disable(pcdev->clk_vi); + clk_disable(pcdev->clk_vi_sensor); + clk_disable(pcdev->clk_csi); + clk_disable(pcdev->clk_isp); + clk_disable(pcdev->clk_csus); + + nvhost_module_idle_ext(pcdev->ndev); - mutex_unlock(&pcdev->work_mutex); - } } -static int tegra_camera_init_buffer(struct tegra_buffer *buf) +static void tegra_camera_init_buffer(struct tegra_camera_dev *pcdev, + struct tegra_buffer *buf) { - struct soc_camera_device *icd = buf->icd; + struct soc_camera_device *icd = pcdev->icd; int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, icd->current_fmt->host_fmt); - struct tegra_camera_platform_data *pdata = icd->link->priv; + buf->buffer_addr = vb2_dma_nvmap_plane_paddr(&buf->vb, 0); // physical addr + buf->virtual_addr = vb2_plane_vaddr(&buf->vb, 0); // save virtual addr for later interlace handling switch (icd->current_fmt->host_fmt->fourcc) { case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_VYUY: case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SBGGR10: - buf->buffer_addr = vb2_dma_nvmap_plane_paddr(&buf->vb, 0); buf->start_addr = buf->buffer_addr; - if (pdata->flip_v) + if (pcdev->pdata->flip_v) buf->start_addr += bytes_per_line * (icd->user_height-1); - if (pdata->flip_h) + if (pcdev->pdata->flip_h) buf->start_addr += bytes_per_line - 1; break; case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: - buf->buffer_addr = vb2_dma_nvmap_plane_paddr(&buf->vb, 0); buf->buffer_addr_u = buf->buffer_addr + icd->user_width * icd->user_height; buf->buffer_addr_v = buf->buffer_addr_u + @@ -1265,7 +1119,7 @@ static int tegra_camera_init_buffer(struct tegra_buffer *buf) buf->start_addr_u = buf->buffer_addr_u; buf->start_addr_v = buf->buffer_addr_v; - if (pdata->flip_v) { + if (pcdev->pdata->flip_v) { buf->start_addr += icd->user_width * (icd->user_height - 1); @@ -1276,7 +1130,7 @@ static int tegra_camera_init_buffer(struct tegra_buffer *buf) ((icd->user_height/2) - 1)); } - if (pdata->flip_h) { + if (pcdev->pdata->flip_h) { buf->start_addr += icd->user_width - 1; buf->start_addr_u += (icd->user_width/2) - 1; @@ -1287,12 +1141,8 @@ static int tegra_camera_init_buffer(struct tegra_buffer *buf) break; default: - dev_err(icd->parent, "Wrong host format %d\n", - icd->current_fmt->host_fmt->fourcc); - return -EINVAL; + BUG_ON(1); } - - return 0; } /* @@ -1307,7 +1157,6 @@ static int tegra_camera_videobuf_setup(struct vb2_queue *vq, struct soc_camera_device *icd = container_of(vq, struct soc_camera_device, vb2_vidq); - struct tegra_camera_platform_data *pdata = icd->link->priv; struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct tegra_camera_dev *pcdev = ici->priv; int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, @@ -1320,16 +1169,20 @@ static int tegra_camera_videobuf_setup(struct vb2_queue *vq, *num_planes = 1; - if (pdata->port == TEGRA_CAMERA_PORT_CSI_A) - pcdev->sequence_a = 0; - else if (pdata->port == TEGRA_CAMERA_PORT_CSI_B) - pcdev->sequence_b = 0; + pcdev->sequence = 0; sizes[0] = bytes_per_line * icd->user_height; alloc_ctxs[0] = pcdev->alloc_ctx; if (!*num_buffers) *num_buffers = 2; + dev_dbg(icd->parent, "num_buffers=%u, size=%lu\n", + *num_buffers, sizes[0]); + + tegra_camera_capture_setup(pcdev); + + dev_dbg(icd->parent, "Finished tegra_camera_videobuf_setup()\n"); + return 0; } @@ -1338,8 +1191,9 @@ static int tegra_camera_videobuf_prepare(struct vb2_buffer *vb) struct soc_camera_device *icd = container_of(vb->vb2_queue, struct soc_camera_device, vb2_vidq); - struct tegra_buffer *buf = to_tegra_vb(vb); - struct tegra_camera_platform_data *pdata = icd->link->priv; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct tegra_camera_dev *pcdev = ici->priv; + struct tegra_buffer *buf; int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, icd->current_fmt->host_fmt); unsigned long size; @@ -1349,19 +1203,7 @@ static int tegra_camera_videobuf_prepare(struct vb2_buffer *vb) if (bytes_per_line < 0) return bytes_per_line; - buf->icd = icd; - - if (!pdata) { - dev_err(icd->parent, "No platform data for this device!\n"); - return -EINVAL; - } - - if (!tegra_camera_port_is_valid(pdata->port)) { - dev_err(icd->parent, - "Invalid camera port %d in platform data\n", - pdata->port); - return -EINVAL; - } + buf = to_tegra_vb(vb); dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb, vb2_plane_vaddr(vb, 0), vb2_plane_size(vb, 0)); @@ -1375,10 +1217,7 @@ static int tegra_camera_videobuf_prepare(struct vb2_buffer *vb) memset(vb2_plane_vaddr(vb, 0), 0xbd, vb2_plane_size(vb, 0)); #endif - if (!icd->current_fmt) { - dev_err(icd->parent, "%s NULL format point\n", __func__); - return -EINVAL; - } + BUG_ON(NULL == icd->current_fmt); size = icd->user_height * bytes_per_line; @@ -1390,7 +1229,11 @@ static int tegra_camera_videobuf_prepare(struct vb2_buffer *vb) vb2_set_plane_payload(vb, 0, size); - return tegra_camera_init_buffer(buf); + tegra_camera_init_buffer(pcdev, buf); + + dev_dbg(icd->parent, "Finished tegra_camera_videobuf_prepare()\n"); + + return 0; } static void tegra_camera_videobuf_queue(struct vb2_buffer *vb) @@ -1402,12 +1245,18 @@ static void tegra_camera_videobuf_queue(struct vb2_buffer *vb) struct tegra_camera_dev *pcdev = ici->priv; struct tegra_buffer *buf = to_tegra_vb(vb); + dev_dbg(icd->parent, "In tegra_camera_videobuf_queue()\n"); + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); spin_lock_irq(&pcdev->videobuf_queue_lock); list_add_tail(&buf->queue, &pcdev->capture); - schedule_work(&pcdev->work); + + if (!pcdev->active) { + pcdev->active = vb; + schedule_work(&pcdev->work); + } spin_unlock_irq(&pcdev->videobuf_queue_lock); dev_dbg(icd->parent, "Finished tegra_camera_videobuf_queue()\n"); @@ -1462,24 +1311,16 @@ static int tegra_camera_stop_streaming(struct vb2_queue *q) struct tegra_camera_dev *pcdev = ici->priv; struct list_head *buf_head, *tmp; - mutex_lock(&pcdev->work_mutex); spin_lock_irq(&pcdev->videobuf_queue_lock); - list_for_each_safe(buf_head, tmp, &pcdev->capture) { - struct tegra_buffer *buf = container_of(buf_head, - struct tegra_buffer, - queue); - if (buf->icd == icd) - list_del_init(buf_head); - } - spin_unlock_irq(&pcdev->videobuf_queue_lock); - if (pcdev->active) { - struct tegra_buffer *buf = to_tegra_vb(pcdev->active); - if (buf->icd == icd) - pcdev->active = NULL; - } + pcdev->active = NULL; + + list_for_each_safe(buf_head, tmp, &pcdev->capture) + list_del_init(buf_head); + + spin_unlock_irq(&pcdev->videobuf_queue_lock); mutex_unlock(&pcdev->work_mutex); @@ -1524,13 +1365,24 @@ static int tegra_camera_add_device(struct soc_camera_device *icd) { struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct tegra_camera_dev *pcdev = ici->priv; + int err; - if (!pcdev->enable_refcnt) { - pm_runtime_get_sync(ici->v4l2_dev.dev); - tegra_camera_activate(pcdev); - pcdev->num_frames = 0; + if (pcdev->icd) + return -EBUSY; + + pm_runtime_get_sync(ici->v4l2_dev.dev); + + if (pcdev->pdata->enable_camera) { + err = pcdev->pdata->enable_camera(pcdev->ndev); + if (IS_ERR_VALUE(err)) + return err; } - pcdev->enable_refcnt++; + + tegra_camera_activate(pcdev); + + pcdev->icd = icd; + + pcdev->num_frames = 0; dev_dbg(icd->parent, "TEGRA Camera host attached to camera %d\n", icd->devnum); @@ -1544,12 +1396,14 @@ static void tegra_camera_remove_device(struct soc_camera_device *icd) struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct tegra_camera_dev *pcdev = ici->priv; - pcdev->enable_refcnt--; - if (!pcdev->enable_refcnt) { - cancel_work_sync(&pcdev->work); - tegra_camera_deactivate(pcdev); - pm_runtime_put_sync(ici->v4l2_dev.dev); - } + tegra_camera_deactivate(pcdev); + + pcdev->icd = NULL; + + if (pcdev->pdata->disable_camera) + pcdev->pdata->disable_camera(pcdev->ndev); + + pm_runtime_put_sync(ici->v4l2_dev.dev); dev_dbg(icd->parent, "Frames captured: %d\n", pcdev->num_frames); @@ -1591,8 +1445,6 @@ static int tegra_camera_get_formats(struct soc_camera_device *icd, case V4L2_MBUS_FMT_VYUY8_2X8: case V4L2_MBUS_FMT_YUYV8_2X8: case V4L2_MBUS_FMT_YVYU8_2X8: - case V4L2_MBUS_FMT_SBGGR8_1X8: - case V4L2_MBUS_FMT_SBGGR10_1X10: formats += ARRAY_SIZE(tegra_camera_formats); for (k = 0; xlate && (k < ARRAY_SIZE(tegra_camera_formats)); @@ -1721,6 +1573,15 @@ static int tegra_camera_try_fmt(struct soc_camera_device *icd, case V4L2_FIELD_NONE: pix->field = V4L2_FIELD_NONE; break; + case V4L2_FIELD_INTERLACED_BT: + pix->field = V4L2_FIELD_INTERLACED_BT; + break; + case V4L2_FIELD_INTERLACED_TB: + pix->field = V4L2_FIELD_INTERLACED_TB; + break; + case V4L2_FIELD_INTERLACED: + pix->field = V4L2_FIELD_INTERLACED; + break; default: /* TODO: support interlaced at least in pass-through mode */ dev_err(icd->parent, "Field type %d unsupported.\n", @@ -1785,7 +1646,8 @@ static int __devinit tegra_camera_probe(struct nvhost_device *ndev, goto exit; } - pcdev->ndev = ndev; + pcdev->pdata = ndev->dev.platform_data; + pcdev->ndev = ndev; pcdev->ici.priv = pcdev; pcdev->ici.v4l2_dev.dev = &ndev->dev; @@ -1798,6 +1660,14 @@ static int __devinit tegra_camera_probe(struct nvhost_device *ndev, spin_lock_init(&pcdev->videobuf_queue_lock); mutex_init(&pcdev->work_mutex); + nvhost_set_drvdata(ndev, pcdev); + + if (!tegra_camera_port_is_valid(pcdev->pdata->port)) { + dev_err(&ndev->dev, "Invalid camera port %d in platform data\n", + pcdev->pdata->port); + goto exit_free_pcdev; + } + pcdev->clk_vi = clk_get_sys("tegra_camera", "vi"); if (IS_ERR_OR_NULL(pcdev->clk_vi)) { dev_err(&ndev->dev, "Failed to get vi clock.\n"); @@ -1827,48 +1697,16 @@ static int __devinit tegra_camera_probe(struct nvhost_device *ndev, dev_err(&ndev->dev, "Failed to get csus clock.\n"); goto exit_put_clk_isp; } - - pcdev->clk_sclk = clk_get_sys("tegra_camera", "sclk"); - if (IS_ERR_OR_NULL(pcdev->clk_sclk)) { - dev_err(&ndev->dev, "Failed to get sclk clock.\n"); - goto exit_put_clk_csus; - } - - pcdev->clk_emc = clk_get_sys("tegra_camera", "emc"); - if (IS_ERR_OR_NULL(pcdev->clk_emc)) { - dev_err(&ndev->dev, "Failed to get emc clock.\n"); - goto exit_put_clk_sclk; - } - clk_set_rate(pcdev->clk_vi, 150000000); clk_set_rate(pcdev->clk_vi_sensor, 24000000); - /* Get regulator pointer */ -#ifdef CONFIG_ARCH_TEGRA_2x_SOC - pcdev->reg = regulator_get(&ndev->dev, "vcsi"); -#else - pcdev->reg = regulator_get(&ndev->dev, "avdd_dsi_csi"); -#endif - if (IS_ERR_OR_NULL(pcdev->reg)) { - dev_err(&ndev->dev, "%s: couldn't get regulator\n", - __func__); - goto exit_put_clk_emc; - } - - nvhost_set_drvdata(ndev, pcdev); err = nvhost_client_device_get_resources(ndev); - if (err) { - dev_err(&ndev->dev, "%s: nvhost get resources failed %d\n", - __func__, err); - goto exit_put_regulator; - } + if (err) + goto exit_put_clk_csus; - err = nvhost_client_device_init(ndev); - if (err) { - dev_err(&ndev->dev, "%s: nvhost init failed %d\n", - __func__, err); - goto exit_put_regulator; - } + // initialize nvhost client device only the first time + if (ndev->power_attrib == NULL) + nvhost_client_device_init(ndev); pcdev->vi_base = ndev->aperture; @@ -1876,10 +1714,10 @@ static int __devinit tegra_camera_probe(struct nvhost_device *ndev, pm_runtime_enable(&ndev->dev); pm_runtime_resume(&ndev->dev); - pcdev->alloc_ctx = vb2_dma_nvmap_init_ctx(&ndev->dev); + pcdev->alloc_ctx = vb2_dma_nvmap_init_ctx(pcdev->ici.v4l2_dev.dev); if (IS_ERR(pcdev->alloc_ctx)) { err = PTR_ERR(pcdev->alloc_ctx); - goto exit_pm_disable; + goto exit_put_resources; } err = soc_camera_host_register(&pcdev->ici); @@ -1892,15 +1730,9 @@ static int __devinit tegra_camera_probe(struct nvhost_device *ndev, exit_cleanup_alloc_ctx: vb2_dma_nvmap_cleanup_ctx(pcdev->alloc_ctx); -exit_pm_disable: +exit_put_resources: pm_runtime_disable(&ndev->dev); nvhost_client_device_put_resources(ndev); -exit_put_regulator: - regulator_put(pcdev->reg); -exit_put_clk_emc: - clk_put(pcdev->clk_emc); -exit_put_clk_sclk: - clk_put(pcdev->clk_sclk); exit_put_clk_csus: clk_put(pcdev->clk_csus); exit_put_clk_isp: @@ -1963,6 +1795,11 @@ static int tegra_camera_suspend(struct nvhost_device *ndev, pm_message_t state) /* Suspend the camera sensor. */ WARN_ON(!pcdev->icd->ops->suspend); pcdev->icd->ops->suspend(pcdev->icd, state); + + /* Power off the camera subsystem. */ + pcdev->pdata->disable_camera(pcdev->ndev); + + nvhost_module_idle_ext(nvhost_get_parent(ndev)); } return 0; @@ -1976,10 +1813,14 @@ static int tegra_camera_resume(struct nvhost_device *ndev) /* We only need to do something if a camera sensor is attached. */ if (pcdev->icd) { + nvhost_module_busy_ext(nvhost_get_parent(ndev)); + + /* Power on the camera subsystem. */ + pcdev->pdata->enable_camera(pcdev->ndev); + /* Resume the camera host. */ tegra_camera_save_syncpts(pcdev); - if (pcdev->active) - tegra_camera_capture_setup(pcdev); + tegra_camera_capture_setup(pcdev); /* Resume the camera sensor. */ WARN_ON(!pcdev->icd->ops->resume); @@ -2021,6 +1862,5 @@ module_exit(tegra_camera_exit); MODULE_DESCRIPTION("TEGRA SoC Camera Host driver"); MODULE_AUTHOR("Andrew Chew <achew@nvidia.com>"); -MODULE_AUTHOR("Bryan Wu <pengw@nvidia.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("nvhost:" TEGRA_CAM_DRV_NAME); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 10dba0cbda97..1f131d797583 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -297,6 +297,7 @@ config MFD_STMPE GPIO: stmpe-gpio Keypad: stmpe-keypad Touchscreen: stmpe-ts + ADC: stmpe-adc config MFD_TC3589X bool "Support Toshiba TC35892 and variants" diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 2963689cf45c..0c556fa65dd2 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -350,6 +350,25 @@ static struct mfd_cell stmpe_ts_cell = { }; /* + * ADC (STMPE811) + */ + +static struct resource stmpe_adc_resources[] = { + { + .name = "STMPE_ADC", + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell stmpe_adc_cell = { + .name = "stmpe-adc", + .resources = stmpe_adc_resources, + .num_resources = ARRAY_SIZE(stmpe_adc_resources), +}; + +/* * STMPE811 */ @@ -381,6 +400,11 @@ static struct stmpe_variant_block stmpe811_blocks[] = { .irq = STMPE811_IRQ_TOUCH_DET, .block = STMPE_BLOCK_TOUCHSCREEN, }, + { + .cell = &stmpe_adc_cell, + .irq = STMPE811_IRQ_ADC, + .block = STMPE_BLOCK_ADC, + }, }; static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks, diff --git a/drivers/mfd/tps6586x.c b/drivers/mfd/tps6586x.c index 9a146c8bba92..1c140e5ace4f 100644 --- a/drivers/mfd/tps6586x.c +++ b/drivers/mfd/tps6586x.c @@ -96,6 +96,7 @@ struct tps6586x { struct mutex lock; struct device *dev; struct i2c_client *client; + enum tps6586x_type type; struct gpio_chip gpio; struct irq_chip irq_chip; @@ -255,6 +256,14 @@ out: } EXPORT_SYMBOL_GPL(tps6586x_update); +enum tps6586x_type tps6586x_gettype(struct device *dev) +{ + struct tps6586x *tps6586x = dev_get_drvdata(dev); + + return tps6586x->type; +} +EXPORT_SYMBOL_GPL(tps6586x_gettype); + static struct i2c_client *tps6586x_i2c_client = NULL; static void tps6586x_power_off(void) { @@ -517,17 +526,36 @@ static int __devinit tps6586x_i2c_probe(struct i2c_client *client, return -ENOTSUPP; } + tps6586x = kzalloc(sizeof(struct tps6586x), GFP_KERNEL); + if (tps6586x == NULL) + return -ENOMEM; + ret = i2c_smbus_read_byte_data(client, TPS6586X_VERSIONCRC); if (ret < 0) { dev_err(&client->dev, "Chip ID read failed: %d\n", ret); - return -EIO; + ret = -EIO; + goto err_irq_init; + } + tps6586x->type = (enum tps6586x_type)ret; + switch (ret) { + case TPS658621A: + dev_info(&client->dev, "found TPS658621A, "); + break; + case TPS658621D: + dev_info(&client->dev, "found TPS658621D, "); + break; + case TPS658623: + dev_info(&client->dev, "found TPS658623, "); + break; + case TPS658643: + dev_info(&client->dev, "found TPS658643, "); + break; + default: + dev_info(&client->dev, "unknown TPS6586X found, "); + tps6586x->type = TPS6586X_ANY; } - dev_info(&client->dev, "VERSIONCRC is %02x\n", ret); - - tps6586x = kzalloc(sizeof(struct tps6586x), GFP_KERNEL); - if (tps6586x == NULL) - return -ENOMEM; + printk("VERSIONCRC is %02x\n", ret); tps6586x->client = client; tps6586x->dev = &client->dev; diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 8ff35e4cbfe4..73b65ce3089e 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -942,7 +942,13 @@ static struct sdhci_pltfm_data sdhci_tegra_pdata = { SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | SDHCI_QUIRK_NO_CALC_MAX_DISCARD_TO | - SDHCI_QUIRK_BROKEN_CARD_DETECTION, + SDHCI_QUIRK_BROKEN_CARD_DETECTION | +#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_COLIBRI_T30) +/* Hack: SDR12, SDR25, SDR50, SDR104 and DDR50 all require 1.8V signalling which + our current T30 designs can't do. */ + SDHCI_QUIRK2_NO_1_8_V | +#endif + 0, .ops = &tegra_sdhci_ops, }; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 3a440dcf0f24..da1547ffc9da 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -486,7 +486,10 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, if (offset) { if (data->flags & MMC_DATA_WRITE) { buffer = sdhci_kmap_atomic(sg, &flags); +/* Hack to avoid extensive warning messages from Redpine Signals LiteFi driver */ +#ifndef CONFIG_MACH_COLIBRI_T20 WARN_ON(((long)buffer & PAGE_MASK) > (PAGE_SIZE - 3)); +#endif memcpy(align, buffer, offset); sdhci_kunmap_atomic(buffer, &flags); } @@ -2658,9 +2661,13 @@ int sdhci_add_host(struct sdhci_host *host) mmc_card_is_removable(mmc) && !(host->ops->get_cd)) mmc->caps |= MMC_CAP_NEEDS_POLL; - /* UHS-I mode(s) supported by the host controller. */ - if (host->version >= SDHCI_SPEC_300) - mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; + if (host->quirks & SDHCI_QUIRK2_NO_1_8_V) + caps[1] &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | + SDHCI_SUPPORT_DDR50); + else + /* UHS-I mode(s) supported by the host controller. */ + if (host->version >= SDHCI_SPEC_300) + mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; /* SDR104 supports also implies SDR50 support */ if (caps[1] & SDHCI_SUPPORT_SDR104) diff --git a/drivers/mtd/devices/tegra_nand.c b/drivers/mtd/devices/tegra_nand.c index 38baa40746c6..6034f0b2f239 100644 --- a/drivers/mtd/devices/tegra_nand.c +++ b/drivers/mtd/devices/tegra_nand.c @@ -434,6 +434,35 @@ static int nand_cmd_get_status(struct tegra_nand_info *info, uint32_t *status) return 0; } +static int tegra_nand_cmd_reset(struct tegra_nand_info *info, + uint32_t *chip_id) +{ + int err; + uint32_t status = 0; + +#ifdef TEGRA_NAND_DEBUG_PEDANTIC + BUG_ON(info->chip.curr_chip == -1); +#endif + + info->command_reg = (COMMAND_CLE | + (COMMAND_CE(info->chip.curr_chip))); + writel(NAND_CMD_RESET, CMD_REG1); + writel(0, CMD_REG2); + writel(0, ADDR_REG1); + writel(0, ADDR_REG2); + writel(0, CONFIG_REG); + + err = tegra_nand_go(info); + if (err != 0) + return err; + + err = nand_cmd_get_status(info, &status); + if (err != 0) + return err; + + return 0; +} + /* must be called with lock held */ static int check_block_isbad(struct mtd_info *mtd, loff_t offs) { @@ -1400,6 +1429,11 @@ static int tegra_nand_scan(struct mtd_info *mtd, int maxchips) writel(0, CONFIG_REG); select_chip(info, 0); + + err = tegra_nand_cmd_reset(info, &tmp); + if (err != 0) + goto out_error; + err = tegra_nand_cmd_readid(info, &tmp); if (err != 0) goto out_error; @@ -1458,7 +1492,7 @@ static int tegra_nand_scan(struct mtd_info *mtd, int maxchips) /* page_size */ tmp = dev_parms & 0x3; - mtd->writesize = 1024 << tmp; + mtd->writesize = mtd->writebufsize = 1024 << tmp; info->chip.column_mask = mtd->writesize - 1; if (mtd->writesize > 4096) { @@ -1481,6 +1515,11 @@ static int tegra_nand_scan(struct mtd_info *mtd, int maxchips) /* data block size (erase size) (w/o spare) */ tmp = (dev_parms >> 4) & 0x3; + /* work around wrong block size identified for our device + Note: ONFI would really be the way to go but has not been supported + in Linux prior to version 2.6.37. */ + if ((vendor_id == 0x2C) && (dev_id == 0x38)) + tmp += 1; mtd->erasesize = (64 * 1024) << tmp; info->chip.block_shift = ffs(mtd->erasesize) - 1; /* bus width of the nand chip 8/16 */ diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c index 00cf1b0d6053..a56bf52acf5e 100644 --- a/drivers/mtd/nand/nand_ids.c +++ b/drivers/mtd/nand/nand_ids.c @@ -104,6 +104,7 @@ struct nand_flash_dev nand_flash_ids[] = { {"NAND 512MiB 3,3V 16-bit", 0xCC, 0, 512, 0, LP_OPTIONS16}, /* 8 Gigabit */ + {"NAND 1GiB 1,8V 8-bit", 0x38, 0, 1024, 0, LP_OPTIONS}, {"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, LP_OPTIONS}, {"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, LP_OPTIONS}, {"NAND 1GiB 1,8V 16-bit", 0xB3, 0, 1024, 0, LP_OPTIONS16}, diff --git a/drivers/net/can/mcp251x.c b/drivers/net/can/mcp251x.c index 330140ee266d..535e4032da2b 100644 --- a/drivers/net/can/mcp251x.c +++ b/drivers/net/can/mcp251x.c @@ -593,6 +593,18 @@ static int mcp251x_do_set_bittiming(struct net_device *net) return 0; } +static int mcp251x_get_berr_counter(const struct net_device *dev, struct can_berr_counter *bec) +{ + struct mcp251x_priv *priv = netdev_priv(dev); + struct spi_device *spi = priv->spi; + uint8_t tec,rec; + + mcp251x_read_2regs(spi, TEC, &tec, &rec); + bec->txerr = tec; + bec->rxerr = rec; + return 0; +} + static int mcp251x_setup(struct net_device *net, struct mcp251x_priv *priv, struct spi_device *spi) { @@ -997,6 +1009,7 @@ static int __devinit mcp251x_can_probe(struct spi_device *spi) priv = netdev_priv(net); priv->can.bittiming_const = &mcp251x_bittiming_const; priv->can.do_set_mode = mcp251x_do_set_mode; + priv->can.do_get_berr_counter = mcp251x_get_berr_counter; priv->can.clock.freq = pdata->oscillator_frequency / 2; priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY; diff --git a/drivers/net/can/sja1000/sja1000_platform.c b/drivers/net/can/sja1000/sja1000_platform.c index d9fadc489b32..c8428f25d914 100644 --- a/drivers/net/can/sja1000/sja1000_platform.c +++ b/drivers/net/can/sja1000/sja1000_platform.c @@ -38,12 +38,24 @@ MODULE_LICENSE("GPL v2"); static u8 sp_read_reg8(const struct sja1000_priv *priv, int reg) { +#if !defined(CONFIG_MACH_COLIBRI_T20) && !defined(CONFIG_MACH_COLIBRI_T30) return ioread8(priv->reg_base + reg); +#else + u8 value; + iowrite8(reg, priv->reg_base); + value = ioread8(priv->reg_base + 8); + return value; +#endif } static void sp_write_reg8(const struct sja1000_priv *priv, int reg, u8 val) { +#if !defined(CONFIG_MACH_COLIBRI_T20) && !defined(CONFIG_MACH_COLIBRI_T30) iowrite8(val, priv->reg_base + reg); +#else + iowrite8(reg, priv->reg_base); + iowrite8(val, priv->reg_base + 8); +#endif } static u8 sp_read_reg16(const struct sja1000_priv *priv, int reg) diff --git a/drivers/net/igb/Makefile b/drivers/net/igb/Makefile index c6e4621b6262..01193b8460fc 100644 --- a/drivers/net/igb/Makefile +++ b/drivers/net/igb/Makefile @@ -33,5 +33,6 @@ obj-$(CONFIG_IGB) += igb.o igb-objs := igb_main.o igb_ethtool.o e1000_82575.o \ - e1000_mac.o e1000_nvm.o e1000_phy.o e1000_mbx.o + e1000_mac.o e1000_nvm.o e1000_phy.o e1000_mbx.o \ + e1000_i210.o diff --git a/drivers/net/igb/e1000_82575.c b/drivers/net/igb/e1000_82575.c index c0857bdfb03a..ad3bf1792d7c 100644 --- a/drivers/net/igb/e1000_82575.c +++ b/drivers/net/igb/e1000_82575.c @@ -34,6 +34,7 @@ #include "e1000_mac.h" #include "e1000_82575.h" +#include "e1000_i210.h" static s32 igb_get_invariants_82575(struct e1000_hw *); static s32 igb_acquire_phy_82575(struct e1000_hw *); @@ -50,6 +51,8 @@ static s32 igb_write_phy_reg_82580(struct e1000_hw *, u32, u16); static s32 igb_reset_hw_82575(struct e1000_hw *); static s32 igb_reset_hw_82580(struct e1000_hw *); static s32 igb_set_d0_lplu_state_82575(struct e1000_hw *, bool); +static s32 igb_set_d0_lplu_state_82580(struct e1000_hw *, bool); +static s32 igb_set_d3_lplu_state_82580(struct e1000_hw *, bool); static s32 igb_setup_copper_link_82575(struct e1000_hw *); static s32 igb_setup_serdes_link_82575(struct e1000_hw *); static s32 igb_write_phy_reg_sgmii_82575(struct e1000_hw *, u32, u16); @@ -98,6 +101,8 @@ static bool igb_sgmii_uses_mdio_82575(struct e1000_hw *hw) break; case e1000_82580: case e1000_i350: + case e1000_i210: + case e1000_i211: reg = rd32(E1000_MDICNFG); ext_mdio = !!(reg & E1000_MDICNFG_EXT_MDIO); break; @@ -152,6 +157,17 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) case E1000_DEV_ID_I350_SGMII: mac->type = e1000_i350; break; + case E1000_DEV_ID_I210_COPPER: + case E1000_DEV_ID_I210_COPPER_OEM1: + case E1000_DEV_ID_I210_COPPER_IT: + case E1000_DEV_ID_I210_FIBER: + case E1000_DEV_ID_I210_SERDES: + case E1000_DEV_ID_I210_SGMII: + mac->type = e1000_i210; + break; + case E1000_DEV_ID_I211_COPPER: + mac->type = e1000_i211; + break; default: return -E1000_ERR_MAC_INIT; break; @@ -184,26 +200,44 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) /* Set mta register count */ mac->mta_reg_count = 128; /* Set rar entry count */ - mac->rar_entry_count = E1000_RAR_ENTRIES_82575; - if (mac->type == e1000_82576) + switch (mac->type) { + case e1000_82576: mac->rar_entry_count = E1000_RAR_ENTRIES_82576; - if (mac->type == e1000_82580) + break; + case e1000_82580: mac->rar_entry_count = E1000_RAR_ENTRIES_82580; - if (mac->type == e1000_i350) + break; + case e1000_i350: + case e1000_i210: + case e1000_i211: mac->rar_entry_count = E1000_RAR_ENTRIES_I350; + break; + default: + mac->rar_entry_count = E1000_RAR_ENTRIES_82575; + break; + } /* reset */ if (mac->type >= e1000_82580) mac->ops.reset_hw = igb_reset_hw_82580; else mac->ops.reset_hw = igb_reset_hw_82575; + + if (mac->type >= e1000_i210) { + mac->ops.acquire_swfw_sync = igb_acquire_swfw_sync_i210; + mac->ops.release_swfw_sync = igb_release_swfw_sync_i210; + } else { + mac->ops.acquire_swfw_sync = igb_acquire_swfw_sync_82575; + mac->ops.release_swfw_sync = igb_release_swfw_sync_82575; + } + /* Set if part includes ASF firmware */ mac->asf_firmware_present = true; /* Set if manageability features are enabled. */ mac->arc_subsystem_valid = (rd32(E1000_FWSM) & E1000_FWSM_MODE_MASK) ? true : false; - /* enable EEE on i350 parts */ - if (mac->type == e1000_i350) + /* enable EEE on i350 parts and later parts */ + if (mac->type >= e1000_i350) dev_spec->eee_disable = false; else dev_spec->eee_disable = true; @@ -215,26 +249,6 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) /* NVM initialization */ eecd = rd32(E1000_EECD); - - nvm->opcode_bits = 8; - nvm->delay_usec = 1; - switch (nvm->override) { - case e1000_nvm_override_spi_large: - nvm->page_size = 32; - nvm->address_bits = 16; - break; - case e1000_nvm_override_spi_small: - nvm->page_size = 8; - nvm->address_bits = 8; - break; - default: - nvm->page_size = eecd & E1000_EECD_ADDR_BITS ? 32 : 8; - nvm->address_bits = eecd & E1000_EECD_ADDR_BITS ? 16 : 8; - break; - } - - nvm->type = e1000_nvm_eeprom_spi; - size = (u16)((eecd & E1000_EECD_SIZE_EX_MASK) >> E1000_EECD_SIZE_EX_SHIFT); @@ -244,6 +258,33 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) */ size += NVM_WORD_SIZE_BASE_SHIFT; + nvm->word_size = 1 << size; + if (hw->mac.type < e1000_i210) { + nvm->opcode_bits = 8; + nvm->delay_usec = 1; + switch (nvm->override) { + case e1000_nvm_override_spi_large: + nvm->page_size = 32; + nvm->address_bits = 16; + break; + case e1000_nvm_override_spi_small: + nvm->page_size = 8; + nvm->address_bits = 8; + break; + default: + nvm->page_size = eecd + & E1000_EECD_ADDR_BITS ? 32 : 8; + nvm->address_bits = eecd + & E1000_EECD_ADDR_BITS ? 16 : 8; + break; + } + if (nvm->word_size == (1 << 15)) + nvm->page_size = 128; + + nvm->type = e1000_nvm_eeprom_spi; + } else + nvm->type = e1000_nvm_flash_hw; + /* * Check for invalid size */ @@ -252,32 +293,60 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) "defaulting to 32K.\n"); size = 15; } - nvm->word_size = 1 << size; - if (nvm->word_size == (1 << 15)) - nvm->page_size = 128; /* NVM Function Pointers */ - nvm->ops.acquire = igb_acquire_nvm_82575; - if (nvm->word_size < (1 << 15)) - nvm->ops.read = igb_read_nvm_eerd; - else - nvm->ops.read = igb_read_nvm_spi; - - nvm->ops.release = igb_release_nvm_82575; switch (hw->mac.type) { case e1000_82580: nvm->ops.validate = igb_validate_nvm_checksum_82580; nvm->ops.update = igb_update_nvm_checksum_82580; + nvm->ops.acquire = igb_acquire_nvm_82575; + nvm->ops.release = igb_release_nvm_82575; + if (nvm->word_size < (1 << 15)) + nvm->ops.read = igb_read_nvm_eerd; + else + nvm->ops.read = igb_read_nvm_spi; + nvm->ops.write = igb_write_nvm_spi; break; case e1000_i350: nvm->ops.validate = igb_validate_nvm_checksum_i350; nvm->ops.update = igb_update_nvm_checksum_i350; + nvm->ops.acquire = igb_acquire_nvm_82575; + nvm->ops.release = igb_release_nvm_82575; + if (nvm->word_size < (1 << 15)) + nvm->ops.read = igb_read_nvm_eerd; + else + nvm->ops.read = igb_read_nvm_spi; + nvm->ops.write = igb_write_nvm_spi; + break; + case e1000_i210: + nvm->ops.validate = igb_validate_nvm_checksum_i210; + nvm->ops.update = igb_update_nvm_checksum_i210; + nvm->ops.acquire = igb_acquire_nvm_i210; + nvm->ops.release = igb_release_nvm_i210; + nvm->ops.read = igb_read_nvm_srrd_i210; + nvm->ops.valid_led_default = igb_valid_led_default_i210; + break; + case e1000_i211: + nvm->ops.acquire = igb_acquire_nvm_i210; + nvm->ops.release = igb_release_nvm_i210; + nvm->ops.read = igb_read_nvm_i211; + nvm->ops.valid_led_default = igb_valid_led_default_i210; + nvm->ops.validate = NULL; + nvm->ops.update = NULL; + nvm->ops.write = NULL; break; default: nvm->ops.validate = igb_validate_nvm_checksum; nvm->ops.update = igb_update_nvm_checksum; + nvm->ops.acquire = igb_acquire_nvm_82575; + nvm->ops.release = igb_release_nvm_82575; + if (nvm->word_size < (1 << 15)) + nvm->ops.read = igb_read_nvm_eerd; + else + nvm->ops.read = igb_read_nvm_spi; + nvm->ops.write = igb_write_nvm_spi; + break; } - nvm->ops.write = igb_write_nvm_spi; /* if part supports SR-IOV then initialize mailbox parameters */ switch (mac->type) { @@ -315,9 +384,13 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) if (igb_sgmii_active_82575(hw) && !igb_sgmii_uses_mdio_82575(hw)) { phy->ops.read_reg = igb_read_phy_reg_sgmii_82575; phy->ops.write_reg = igb_write_phy_reg_sgmii_82575; - } else if (hw->mac.type >= e1000_82580) { + } else if ((hw->mac.type == e1000_82580) + || (hw->mac.type == e1000_i350)) { phy->ops.read_reg = igb_read_phy_reg_82580; phy->ops.write_reg = igb_write_phy_reg_82580; + } else if (hw->phy.type >= e1000_phy_i210) { + phy->ops.read_reg = igb_read_phy_reg_gs40g; + phy->ops.write_reg = igb_write_phy_reg_gs40g; } else { phy->ops.read_reg = igb_read_phy_reg_igp; phy->ops.write_reg = igb_write_phy_reg_igp; @@ -346,6 +419,14 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) else phy->ops.get_cable_length = igb_get_cable_length_m88; + if (phy->id == I210_I_PHY_ID) { + phy->ops.get_cable_length = + igb_get_cable_length_m88_gen2; + phy->ops.set_d0_lplu_state = + igb_set_d0_lplu_state_82580; + phy->ops.set_d3_lplu_state = + igb_set_d3_lplu_state_82580; + } phy->ops.force_speed_duplex = igb_phy_force_speed_duplex_m88; break; case IGP03E1000_E_PHY_ID: @@ -362,6 +443,17 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw) phy->ops.force_speed_duplex = igb_phy_force_speed_duplex_82580; phy->ops.get_cable_length = igb_get_cable_length_82580; phy->ops.get_phy_info = igb_get_phy_info_82580; + phy->ops.set_d0_lplu_state = igb_set_d0_lplu_state_82580; + phy->ops.set_d3_lplu_state = igb_set_d3_lplu_state_82580; + break; + case I210_I_PHY_ID: + phy->type = e1000_phy_i210; + phy->ops.get_phy_info = igb_get_phy_info_m88; + phy->ops.check_polarity = igb_check_polarity_m88; + phy->ops.get_cable_length = igb_get_cable_length_m88_gen2; + phy->ops.set_d0_lplu_state = igb_set_d0_lplu_state_82580; + phy->ops.set_d3_lplu_state = igb_set_d3_lplu_state_82580; + phy->ops.force_speed_duplex = igb_phy_force_speed_duplex_m88; break; default: return -E1000_ERR_PHY; @@ -388,7 +480,7 @@ static s32 igb_acquire_phy_82575(struct e1000_hw *hw) else if (hw->bus.func == E1000_FUNC_3) mask = E1000_SWFW_PHY3_SM; - return igb_acquire_swfw_sync_82575(hw, mask); + return hw->mac.ops.acquire_swfw_sync(hw, mask); } /** @@ -409,7 +501,7 @@ static void igb_release_phy_82575(struct e1000_hw *hw) else if (hw->bus.func == E1000_FUNC_3) mask = E1000_SWFW_PHY3_SM; - igb_release_swfw_sync_82575(hw, mask); + hw->mac.ops.release_swfw_sync(hw, mask); } /** @@ -513,6 +605,8 @@ static s32 igb_get_phy_id_82575(struct e1000_hw *hw) break; case e1000_82580: case e1000_i350: + case e1000_i210: + case e1000_i211: mdic = rd32(E1000_MDICNFG); mdic &= E1000_MDICNFG_PHY_MASK; phy->addr = mdic >> E1000_MDICNFG_PHY_SHIFT; @@ -677,6 +771,96 @@ out: } /** + * igb_set_d0_lplu_state_82580 - Set Low Power Linkup D0 state + * @hw: pointer to the HW structure + * @active: true to enable LPLU, false to disable + * + * Sets the LPLU D0 state according to the active flag. When + * activating LPLU this function also disables smart speed + * and vice versa. LPLU will not be activated unless the + * device autonegotiation advertisement meets standards of + * either 10 or 10/100 or 10/100/1000 at all duplexes. + * This is a function pointer entry point only called by + * PHY setup routines. + **/ +static s32 igb_set_d0_lplu_state_82580(struct e1000_hw *hw, bool active) +{ + struct e1000_phy_info *phy = &hw->phy; + s32 ret_val = 0; + u16 data; + + data = rd32(E1000_82580_PHY_POWER_MGMT); + + if (active) { + data |= E1000_82580_PM_D0_LPLU; + + /* When LPLU is enabled, we should disable SmartSpeed */ + data &= ~E1000_82580_PM_SPD; + } else { + data &= ~E1000_82580_PM_D0_LPLU; + + /* + * LPLU and SmartSpeed are mutually exclusive. LPLU is used + * during Dx states where the power conservation is most + * important. During driver activity we should enable + * SmartSpeed, so performance is maintained. + */ + if (phy->smart_speed == e1000_smart_speed_on) + data |= E1000_82580_PM_SPD; + else if (phy->smart_speed == e1000_smart_speed_off) + data &= ~E1000_82580_PM_SPD; } + + wr32(E1000_82580_PHY_POWER_MGMT, data); + return ret_val; +} + +/** + * igb_set_d3_lplu_state_82580 - Sets low power link up state for D3 + * @hw: pointer to the HW structure + * @active: boolean used to enable/disable lplu + * + * Success returns 0, Failure returns 1 + * + * The low power link up (lplu) state is set to the power management level D3 + * and SmartSpeed is disabled when active is true, else clear lplu for D3 + * and enable Smartspeed. LPLU and Smartspeed are mutually exclusive. LPLU + * is used during Dx states where the power conservation is most important. + * During driver activity, SmartSpeed should be enabled so performance is + * maintained. + **/ +s32 igb_set_d3_lplu_state_82580(struct e1000_hw *hw, bool active) +{ + struct e1000_phy_info *phy = &hw->phy; + s32 ret_val = 0; + u16 data; + + data = rd32(E1000_82580_PHY_POWER_MGMT); + + if (!active) { + data &= ~E1000_82580_PM_D3_LPLU; + /* + * LPLU and SmartSpeed are mutually exclusive. LPLU is used + * during Dx states where the power conservation is most + * important. During driver activity we should enable + * SmartSpeed, so performance is maintained. + */ + if (phy->smart_speed == e1000_smart_speed_on) + data |= E1000_82580_PM_SPD; + else if (phy->smart_speed == e1000_smart_speed_off) + data &= ~E1000_82580_PM_SPD; + } else if ((phy->autoneg_advertised == E1000_ALL_SPEED_DUPLEX) || + (phy->autoneg_advertised == E1000_ALL_NOT_GIG) || + (phy->autoneg_advertised == E1000_ALL_10_SPEED)) { + data |= E1000_82580_PM_D3_LPLU; + /* When LPLU is enabled, we should disable SmartSpeed */ + data &= ~E1000_82580_PM_SPD; + } + + wr32(E1000_82580_PHY_POWER_MGMT, data); + return ret_val; +} + +/** * igb_acquire_nvm_82575 - Request for access to EEPROM * @hw: pointer to the HW structure * @@ -689,14 +873,14 @@ static s32 igb_acquire_nvm_82575(struct e1000_hw *hw) { s32 ret_val; - ret_val = igb_acquire_swfw_sync_82575(hw, E1000_SWFW_EEP_SM); + ret_val = hw->mac.ops.acquire_swfw_sync(hw, E1000_SWFW_EEP_SM); if (ret_val) goto out; ret_val = igb_acquire_nvm(hw); if (ret_val) - igb_release_swfw_sync_82575(hw, E1000_SWFW_EEP_SM); + hw->mac.ops.release_swfw_sync(hw, E1000_SWFW_EEP_SM); out: return ret_val; @@ -712,7 +896,7 @@ out: static void igb_release_nvm_82575(struct e1000_hw *hw) { igb_release_nvm(hw); - igb_release_swfw_sync_82575(hw, E1000_SWFW_EEP_SM); + hw->mac.ops.release_swfw_sync(hw, E1000_SWFW_EEP_SM); } /** @@ -1080,7 +1264,6 @@ static s32 igb_init_hw_82575(struct e1000_hw *hw) * is no link. */ igb_clear_hw_cntrs_82575(hw); - return ret_val; } @@ -1117,6 +1300,7 @@ static s32 igb_setup_copper_link_82575(struct e1000_hw *hw) } } switch (hw->phy.type) { + case e1000_phy_i210: case e1000_phy_m88: if (hw->phy.id == I347AT4_E_PHY_ID || hw->phy.id == M88E1112_E_PHY_ID) @@ -1740,7 +1924,7 @@ static s32 igb_reset_hw_82580(struct e1000_hw *hw) /* Determine whether or not a global dev reset is requested */ if (global_device_reset && - igb_acquire_swfw_sync_82575(hw, swmbsw_mask)) + hw->mac.ops.acquire_swfw_sync(hw, swmbsw_mask)) global_device_reset = false; if (global_device_reset && @@ -1786,7 +1970,7 @@ static s32 igb_reset_hw_82580(struct e1000_hw *hw) /* Release semaphore */ if (global_device_reset) - igb_release_swfw_sync_82575(hw, swmbsw_mask); + hw->mac.ops.release_swfw_sync(hw, swmbsw_mask); return ret_val; } diff --git a/drivers/net/igb/e1000_82575.h b/drivers/net/igb/e1000_82575.h index 786e110011a3..b477b20a2b05 100644 --- a/drivers/net/igb/e1000_82575.h +++ b/drivers/net/igb/e1000_82575.h @@ -55,10 +55,11 @@ extern void igb_rx_fifo_flush_82575(struct e1000_hw *hw); #define E1000_SRRCTL_DROP_EN 0x80000000 #define E1000_SRRCTL_TIMESTAMP 0x40000000 + #define E1000_MRQC_ENABLE_RSS_4Q 0x00000002 #define E1000_MRQC_ENABLE_VMDQ 0x00000003 -#define E1000_MRQC_ENABLE_VMDQ_RSS_2Q 0x00000005 #define E1000_MRQC_RSS_FIELD_IPV4_UDP 0x00400000 +#define E1000_MRQC_ENABLE_VMDQ_RSS_2Q 0x00000005 #define E1000_MRQC_RSS_FIELD_IPV6_UDP 0x00800000 #define E1000_MRQC_RSS_FIELD_IPV6_UDP_EX 0x01000000 diff --git a/drivers/net/igb/e1000_defines.h b/drivers/net/igb/e1000_defines.h index 7b8ddd830f19..e32e3a71eff1 100644 --- a/drivers/net/igb/e1000_defines.h +++ b/drivers/net/igb/e1000_defines.h @@ -452,6 +452,7 @@ #define E1000_ERR_INVALID_ARGUMENT 16 #define E1000_ERR_NO_SPACE 17 #define E1000_ERR_NVM_PBA_SECTION 18 +#define E1000_ERR_INVM_VALUE_NOT_FOUND 19 /* Loop limit on how long we wait for auto-negotiation to complete */ #define COPPER_LINK_UP_LIMIT 10 @@ -589,6 +590,25 @@ #define E1000_EECD_AUTO_RD 0x00000200 /* NVM Auto Read done */ #define E1000_EECD_SIZE_EX_MASK 0x00007800 /* NVM Size */ #define E1000_EECD_SIZE_EX_SHIFT 11 +#define E1000_EECD_FLUPD_I210 0x00800000 /* Update FLASH */ +#define E1000_EECD_FLUDONE_I210 0x04000000 /* Update FLASH done*/ +#define E1000_FLUDONE_ATTEMPTS 20000 +#define E1000_EERD_EEWR_MAX_COUNT 512 /* buffered EEPROM words rw */ +#define E1000_I210_FIFO_SEL_RX 0x00 +#define E1000_I210_FIFO_SEL_TX_QAV(_i) (0x02 + (_i)) +#define E1000_I210_FIFO_SEL_TX_LEGACY E1000_I210_FIFO_SEL_TX_QAV(0) +#define E1000_I210_FIFO_SEL_BMC2OS_TX 0x06 +#define E1000_I210_FIFO_SEL_BMC2OS_RX 0x01 +#define E1000_EECD_FLUPD_I210 0x00800000 /* Update FLASH */ +#define E1000_EECD_FLUDONE_I210 0x04000000 /* Update FLASH done*/ +#define E1000_FLUDONE_ATTEMPTS 20000 +#define E1000_EERD_EEWR_MAX_COUNT 512 /* buffered EEPROM words rw */ +#define E1000_I210_FIFO_SEL_RX 0x00 +#define E1000_I210_FIFO_SEL_TX_QAV(_i) (0x02 + (_i)) +#define E1000_I210_FIFO_SEL_TX_LEGACY E1000_I210_FIFO_SEL_TX_QAV(0) +#define E1000_I210_FIFO_SEL_BMC2OS_TX 0x06 +#define E1000_I210_FIFO_SEL_BMC2OS_RX 0x01 + /* Offset to data in NVM read/write registers */ #define E1000_NVM_RW_REG_DATA 16 @@ -607,6 +627,16 @@ #define NVM_CHECKSUM_REG 0x003F #define NVM_COMPATIBILITY_REG_3 0x0003 #define NVM_COMPATIBILITY_BIT_MASK 0x8000 +#define NVM_MAC_ADDR 0x0000 +#define NVM_SUB_DEV_ID 0x000B +#define NVM_SUB_VEN_ID 0x000C +#define NVM_DEV_ID 0x000D +#define NVM_VEN_ID 0x000E +#define NVM_INIT_CTRL_2 0x000F +#define NVM_INIT_CTRL_4 0x0013 +#define NVM_LED_1_CFG 0x001C +#define NVM_LED_0_2_CFG 0x001F + #define E1000_NVM_CFG_DONE_PORT_0 0x040000 /* MNG config cycle done */ #define E1000_NVM_CFG_DONE_PORT_1 0x080000 /* ...for second port */ @@ -633,6 +663,7 @@ #define NVM_PBA_OFFSET_0 8 #define NVM_PBA_OFFSET_1 9 +#define NVM_RESERVED_WORD 0xFFFF #define NVM_PBA_PTR_GUARD 0xFAFA #define NVM_WORD_SIZE_BASE_SHIFT 6 @@ -690,6 +721,7 @@ #define I82580_I_PHY_ID 0x015403A0 #define I350_I_PHY_ID 0x015403B0 #define M88_VENDOR 0x0141 +#define I210_I_PHY_ID 0x01410C00 /* M88E1000 Specific Registers */ #define M88E1000_PHY_SPEC_CTRL 0x10 /* PHY Specific Control Register */ @@ -809,6 +841,7 @@ #define E1000_IPCNFG_EEE_100M_AN 0x00000004 /* EEE Enable 100M AN */ #define E1000_EEER_TX_LPI_EN 0x00010000 /* EEE Tx LPI Enable */ #define E1000_EEER_RX_LPI_EN 0x00020000 /* EEE Rx LPI Enable */ +#define E1000_EEER_FRC_AN 0x10000000 /* Enable EEE in loopback */ #define E1000_EEER_LPI_FC 0x00040000 /* EEE Enable on FC */ /* SerDes Control */ diff --git a/drivers/net/igb/e1000_hw.h b/drivers/net/igb/e1000_hw.h index 4519a1367170..6542df695834 100644 --- a/drivers/net/igb/e1000_hw.h +++ b/drivers/net/igb/e1000_hw.h @@ -63,6 +63,23 @@ struct e1000_hw; #define E1000_DEV_ID_I350_FIBER 0x1522 #define E1000_DEV_ID_I350_SERDES 0x1523 #define E1000_DEV_ID_I350_SGMII 0x1524 +#ifdef CONFIG_MACH_APALIS_T30 +/* Hack: Springville with a blank Flash (tools only, not for driver) */ +#define E1000_DEV_ID_I210_COPPER 0x1531 +#else +#define E1000_DEV_ID_I210_COPPER 0x1533 +#endif +#define E1000_DEV_ID_I210_COPPER_OEM1 0x1534 +#define E1000_DEV_ID_I210_COPPER_IT 0x1535 +#define E1000_DEV_ID_I210_FIBER 0x1536 +#define E1000_DEV_ID_I210_SERDES 0x1537 +#define E1000_DEV_ID_I210_SGMII 0x1538 +#ifdef CONFIG_MACH_APALIS_T30 +/* Hack: I211 with a blank iNVM (tools only, not for driver) */ +#define E1000_DEV_ID_I211_COPPER 0x1532 +#else +#define E1000_DEV_ID_I211_COPPER 0x1539 +#endif #define E1000_REVISION_2 2 #define E1000_REVISION_4 4 @@ -83,6 +100,8 @@ enum e1000_mac_type { e1000_82576, e1000_82580, e1000_i350, + e1000_i210, + e1000_i211, e1000_num_macs /* List is 1-based, so subtract 1 for true count. */ }; @@ -117,6 +136,7 @@ enum e1000_phy_type { e1000_phy_igp_3, e1000_phy_ife, e1000_phy_82580, + e1000_phy_i210, }; enum e1000_bus_type { @@ -313,6 +333,9 @@ struct e1000_mac_operations { void (*rar_set)(struct e1000_hw *, u8 *, u32); s32 (*read_mac_addr)(struct e1000_hw *); s32 (*get_speed_and_duplex)(struct e1000_hw *, u16 *, u16 *); + s32 (*acquire_swfw_sync)(struct e1000_hw *, u16); + void (*release_swfw_sync)(struct e1000_hw *, u16); + }; struct e1000_phy_operations { @@ -338,6 +361,7 @@ struct e1000_nvm_operations { s32 (*write)(struct e1000_hw *, u16, u16, u16 *); s32 (*update)(struct e1000_hw *); s32 (*validate)(struct e1000_hw *); + s32 (*valid_led_default)(struct e1000_hw *, u16 *); }; struct e1000_info { diff --git a/drivers/net/igb/e1000_i210.c b/drivers/net/igb/e1000_i210.c new file mode 100644 index 000000000000..77a5f939bc74 --- /dev/null +++ b/drivers/net/igb/e1000_i210.c @@ -0,0 +1,603 @@ +/******************************************************************************* + + Intel(R) Gigabit Ethernet Linux driver + Copyright(c) 2007-2012 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. + + 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 St - Fifth Floor, Boston, MA 02110-1301 USA. + + The full GNU General Public License is included in this distribution in + the file called "COPYING". + + Contact Information: + e1000-devel Mailing List <e1000-devel@lists.sourceforge.net> + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ + +/* e1000_i210 + * e1000_i211 + */ + +#include <linux/types.h> +#include <linux/if_ether.h> + +#include "e1000_hw.h" +#include "e1000_i210.h" + +static s32 igb_get_hw_semaphore_i210(struct e1000_hw *hw); +static void igb_put_hw_semaphore_i210(struct e1000_hw *hw); +static s32 igb_write_nvm_srwr(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data); +static s32 igb_pool_flash_update_done_i210(struct e1000_hw *hw); + +/** + * igb_acquire_nvm_i210 - Request for access to EEPROM + * @hw: pointer to the HW structure + * + * Acquire the necessary semaphores for exclusive access to the EEPROM. + * Set the EEPROM access request bit and wait for EEPROM access grant bit. + * Return successful if access grant bit set, else clear the request for + * EEPROM access and return -E1000_ERR_NVM (-1). + **/ +s32 igb_acquire_nvm_i210(struct e1000_hw *hw) +{ + return igb_acquire_swfw_sync_i210(hw, E1000_SWFW_EEP_SM); +} + +/** + * igb_release_nvm_i210 - Release exclusive access to EEPROM + * @hw: pointer to the HW structure + * + * Stop any current commands to the EEPROM and clear the EEPROM request bit, + * then release the semaphores acquired. + **/ +void igb_release_nvm_i210(struct e1000_hw *hw) +{ + igb_release_swfw_sync_i210(hw, E1000_SWFW_EEP_SM); +} + +/** + * igb_acquire_swfw_sync_i210 - Acquire SW/FW semaphore + * @hw: pointer to the HW structure + * @mask: specifies which semaphore to acquire + * + * Acquire the SW/FW semaphore to access the PHY or NVM. The mask + * will also specify which port we're acquiring the lock for. + **/ +s32 igb_acquire_swfw_sync_i210(struct e1000_hw *hw, u16 mask) +{ + u32 swfw_sync; + u32 swmask = mask; + u32 fwmask = mask << 16; + s32 ret_val = E1000_SUCCESS; + s32 i = 0, timeout = 200; /* FIXME: find real value to use here */ + + while (i < timeout) { + if (igb_get_hw_semaphore_i210(hw)) { + ret_val = -E1000_ERR_SWFW_SYNC; + goto out; + } + + swfw_sync = rd32(E1000_SW_FW_SYNC); + if (!(swfw_sync & fwmask)) + break; + + /* + * Firmware currently using resource (fwmask) + */ + igb_put_hw_semaphore_i210(hw); + mdelay(5); + i++; + } + + if (i == timeout) { + hw_dbg("Driver can't access resource, SW_FW_SYNC timeout.\n"); + ret_val = -E1000_ERR_SWFW_SYNC; + goto out; + } + + swfw_sync |= swmask; + wr32(E1000_SW_FW_SYNC, swfw_sync); + + igb_put_hw_semaphore_i210(hw); +out: + return ret_val; +} + +/** + * igb_release_swfw_sync_i210 - Release SW/FW semaphore + * @hw: pointer to the HW structure + * @mask: specifies which semaphore to acquire + * + * Release the SW/FW semaphore used to access the PHY or NVM. The mask + * will also specify which port we're releasing the lock for. + **/ +void igb_release_swfw_sync_i210(struct e1000_hw *hw, u16 mask) +{ + u32 swfw_sync; + + while (igb_get_hw_semaphore_i210(hw) != E1000_SUCCESS) + ; /* Empty */ + + swfw_sync = rd32(E1000_SW_FW_SYNC); + swfw_sync &= ~mask; + wr32(E1000_SW_FW_SYNC, swfw_sync); + + igb_put_hw_semaphore_i210(hw); +} + +/** + * igb_get_hw_semaphore_i210 - Acquire hardware semaphore + * @hw: pointer to the HW structure + * + * Acquire the HW semaphore to access the PHY or NVM + **/ +static s32 igb_get_hw_semaphore_i210(struct e1000_hw *hw) +{ + u32 swsm; + s32 ret_val = E1000_SUCCESS; + s32 timeout = hw->nvm.word_size + 1; + s32 i = 0; + + /* Get the FW semaphore. */ + for (i = 0; i < timeout; i++) { + swsm = rd32(E1000_SWSM); + wr32(E1000_SWSM, swsm | E1000_SWSM_SWESMBI); + + /* Semaphore acquired if bit latched */ + if (rd32(E1000_SWSM) & E1000_SWSM_SWESMBI) + break; + + udelay(50); + } + + if (i == timeout) { + /* Release semaphores */ + igb_put_hw_semaphore(hw); + hw_dbg("Driver can't access the NVM\n"); + ret_val = -E1000_ERR_NVM; + goto out; + } + +out: + return ret_val; +} + +/** + * igb_put_hw_semaphore_i210 - Release hardware semaphore + * @hw: pointer to the HW structure + * + * Release hardware semaphore used to access the PHY or NVM + **/ +static void igb_put_hw_semaphore_i210(struct e1000_hw *hw) +{ + u32 swsm; + + swsm = rd32(E1000_SWSM); + + swsm &= ~E1000_SWSM_SWESMBI; + + wr32(E1000_SWSM, swsm); +} + +/** + * igb_read_nvm_srrd_i210 - Reads Shadow Ram using EERD register + * @hw: pointer to the HW structure + * @offset: offset of word in the Shadow Ram to read + * @words: number of words to read + * @data: word read from the Shadow Ram + * + * Reads a 16 bit word from the Shadow Ram using the EERD register. + * Uses necessary synchronization semaphores. + **/ +s32 igb_read_nvm_srrd_i210(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data) +{ + s32 status = E1000_SUCCESS; + u16 i, count; + + /* We cannot hold synchronization semaphores for too long, + * because of forceful takeover procedure. However it is more efficient + * to read in bursts than synchronizing access for each word. */ + for (i = 0; i < words; i += E1000_EERD_EEWR_MAX_COUNT) { + count = (words - i) / E1000_EERD_EEWR_MAX_COUNT > 0 ? + E1000_EERD_EEWR_MAX_COUNT : (words - i); + if (hw->nvm.ops.acquire(hw) == E1000_SUCCESS) { + status = igb_read_nvm_eerd(hw, offset, count, + data + i); + hw->nvm.ops.release(hw); + } else { + status = E1000_ERR_SWFW_SYNC; + } + + if (status != E1000_SUCCESS) + break; + } + + return status; +} + +/** + * igb_write_nvm_srwr_i210 - Write to Shadow RAM using EEWR + * @hw: pointer to the HW structure + * @offset: offset within the Shadow RAM to be written to + * @words: number of words to write + * @data: 16 bit word(s) to be written to the Shadow RAM + * + * Writes data to Shadow RAM at offset using EEWR register. + * + * If e1000_update_nvm_checksum is not called after this function , the + * data will not be committed to FLASH and also Shadow RAM will most likely + * contain an invalid checksum. + * + * If error code is returned, data and Shadow RAM may be inconsistent - buffer + * partially written. + **/ +s32 igb_write_nvm_srwr_i210(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data) +{ + s32 status = E1000_SUCCESS; + u16 i, count; + + /* We cannot hold synchronization semaphores for too long, + * because of forceful takeover procedure. However it is more efficient + * to write in bursts than synchronizing access for each word. */ + for (i = 0; i < words; i += E1000_EERD_EEWR_MAX_COUNT) { + count = (words - i) / E1000_EERD_EEWR_MAX_COUNT > 0 ? + E1000_EERD_EEWR_MAX_COUNT : (words - i); + if (hw->nvm.ops.acquire(hw) == E1000_SUCCESS) { + status = igb_write_nvm_srwr(hw, offset, count, + data + i); + hw->nvm.ops.release(hw); + } else { + status = E1000_ERR_SWFW_SYNC; + } + + if (status != E1000_SUCCESS) + break; + } + + return status; +} + +/** + * igb_write_nvm_srwr - Write to Shadow Ram using EEWR + * @hw: pointer to the HW structure + * @offset: offset within the Shadow Ram to be written to + * @words: number of words to write + * @data: 16 bit word(s) to be written to the Shadow Ram + * + * Writes data to Shadow Ram at offset using EEWR register. + * + * If igb_update_nvm_checksum is not called after this function , the + * Shadow Ram will most likely contain an invalid checksum. + **/ +static s32 igb_write_nvm_srwr(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data) +{ + struct e1000_nvm_info *nvm = &hw->nvm; + u32 i, k, eewr = 0; + u32 attempts = 100000; + s32 ret_val = E1000_SUCCESS; + + /* + * A check for invalid values: offset too large, too many words, + * too many words for the offset, and not enough words. + */ + if ((offset >= nvm->word_size) || (words > (nvm->word_size - offset)) || + (words == 0)) { + hw_dbg("nvm parameter(s) out of bounds\n"); + ret_val = -E1000_ERR_NVM; + goto out; + } + + for (i = 0; i < words; i++) { + eewr = ((offset+i) << E1000_NVM_RW_ADDR_SHIFT) | + (data[i] << E1000_NVM_RW_REG_DATA) | + E1000_NVM_RW_REG_START; + + wr32(E1000_SRWR, eewr); + + for (k = 0; k < attempts; k++) { + if (E1000_NVM_RW_REG_DONE & + rd32(E1000_SRWR)) { + ret_val = E1000_SUCCESS; + break; + } + udelay(5); + } + + if (ret_val != E1000_SUCCESS) { + hw_dbg("Shadow RAM write EEWR timed out\n"); + break; + } + } + +out: + return ret_val; +} + +/** + * igb_read_nvm_i211 - Read NVM wrapper function for I211 + * @hw: pointer to the HW structure + * @address: the word address (aka eeprom offset) to read + * @data: pointer to the data read + * + * Wrapper function to return data formerly found in the NVM. + **/ +s32 igb_read_nvm_i211(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data) +{ + s32 ret_val = E1000_SUCCESS; + + /* Only the MAC addr is required to be present in the iNVM */ + switch (offset) { + case NVM_MAC_ADDR: + ret_val = igb_read_invm_i211(hw, offset, &data[0]); + ret_val |= igb_read_invm_i211(hw, offset+1, &data[1]); + ret_val |= igb_read_invm_i211(hw, offset+2, &data[2]); + if (ret_val != E1000_SUCCESS) + hw_dbg("MAC Addr not found in iNVM\n"); + break; + case NVM_ID_LED_SETTINGS: + case NVM_INIT_CTRL_2: + case NVM_INIT_CTRL_4: + case NVM_LED_1_CFG: + case NVM_LED_0_2_CFG: + igb_read_invm_i211(hw, offset, data); + break; + case NVM_COMPAT: + *data = ID_LED_DEFAULT_I210; + break; + case NVM_SUB_DEV_ID: + *data = hw->subsystem_device_id; + break; + case NVM_SUB_VEN_ID: + *data = hw->subsystem_vendor_id; + break; + case NVM_DEV_ID: + *data = hw->device_id; + break; + case NVM_VEN_ID: + *data = hw->vendor_id; + break; + default: + hw_dbg("NVM word 0x%02x is not mapped.\n", offset); + *data = NVM_RESERVED_WORD; + break; + } + return ret_val; +} + +/** + * igb_read_invm_i211 - Reads OTP + * @hw: pointer to the HW structure + * @address: the word address (aka eeprom offset) to read + * @data: pointer to the data read + * + * Reads 16-bit words from the OTP. Return error when the word is not + * stored in OTP. + **/ +s32 igb_read_invm_i211(struct e1000_hw *hw, u16 address, u16 *data) +{ + s32 status = -E1000_ERR_INVM_VALUE_NOT_FOUND; + u32 invm_dword; + u16 i; + u8 record_type, word_address; + + for (i = 0; i < E1000_INVM_SIZE; i++) { + invm_dword = rd32(E1000_INVM_DATA_REG(i)); + /* Get record type */ + record_type = INVM_DWORD_TO_RECORD_TYPE(invm_dword); + if (record_type == E1000_INVM_UNINITIALIZED_STRUCTURE) + break; + if (record_type == E1000_INVM_CSR_AUTOLOAD_STRUCTURE) + i += E1000_INVM_CSR_AUTOLOAD_DATA_SIZE_IN_DWORDS; + if (record_type == E1000_INVM_RSA_KEY_SHA256_STRUCTURE) + i += E1000_INVM_RSA_KEY_SHA256_DATA_SIZE_IN_DWORDS; + if (record_type == E1000_INVM_WORD_AUTOLOAD_STRUCTURE) { + word_address = INVM_DWORD_TO_WORD_ADDRESS(invm_dword); + if (word_address == (u8)address) { + *data = INVM_DWORD_TO_WORD_DATA(invm_dword); + hw_dbg("Read INVM Word 0x%02x = %x", + address, *data); + status = E1000_SUCCESS; + break; + } + } + } + if (status != E1000_SUCCESS) + hw_dbg("Requested word 0x%02x not found in OTP\n", address); + return status; +} + +/** + * igb_validate_nvm_checksum_i210 - Validate EEPROM checksum + * @hw: pointer to the HW structure + * + * Calculates the EEPROM checksum by reading/adding each word of the EEPROM + * and then verifies that the sum of the EEPROM is equal to 0xBABA. + **/ +s32 igb_validate_nvm_checksum_i210(struct e1000_hw *hw) +{ + s32 status = E1000_SUCCESS; + s32 (*read_op_ptr)(struct e1000_hw *, u16, u16, u16 *); + + if (hw->nvm.ops.acquire(hw) == E1000_SUCCESS) { + + /* + * Replace the read function with semaphore grabbing with + * the one that skips this for a while. + * We have semaphore taken already here. + */ + read_op_ptr = hw->nvm.ops.read; + hw->nvm.ops.read = igb_read_nvm_eerd; + + status = igb_validate_nvm_checksum(hw); + + /* Revert original read operation. */ + hw->nvm.ops.read = read_op_ptr; + + hw->nvm.ops.release(hw); + } else { + status = E1000_ERR_SWFW_SYNC; + } + + return status; +} + + +/** + * igb_update_nvm_checksum_i210 - Update EEPROM checksum + * @hw: pointer to the HW structure + * + * Updates the EEPROM checksum by reading/adding each word of the EEPROM + * up to the checksum. Then calculates the EEPROM checksum and writes the + * value to the EEPROM. Next commit EEPROM data onto the Flash. + **/ +s32 igb_update_nvm_checksum_i210(struct e1000_hw *hw) +{ + s32 ret_val = E1000_SUCCESS; + u16 checksum = 0; + u16 i, nvm_data; + + /* + * Read the first word from the EEPROM. If this times out or fails, do + * not continue or we could be in for a very long wait while every + * EEPROM read fails + */ + ret_val = igb_read_nvm_eerd(hw, 0, 1, &nvm_data); + if (ret_val != E1000_SUCCESS) { + hw_dbg("EEPROM read failed\n"); + goto out; + } + + if (hw->nvm.ops.acquire(hw) == E1000_SUCCESS) { + /* + * Do not use hw->nvm.ops.write, hw->nvm.ops.read + * because we do not want to take the synchronization + * semaphores twice here. + */ + + for (i = 0; i < NVM_CHECKSUM_REG; i++) { + ret_val = igb_read_nvm_eerd(hw, i, 1, &nvm_data); + if (ret_val) { + hw->nvm.ops.release(hw); + hw_dbg("NVM Read Error while updating checksum.\n"); + goto out; + } + checksum += nvm_data; + } + checksum = (u16) NVM_SUM - checksum; + ret_val = igb_write_nvm_srwr(hw, NVM_CHECKSUM_REG, 1, + &checksum); + if (ret_val != E1000_SUCCESS) { + hw->nvm.ops.release(hw); + hw_dbg("NVM Write Error while updating checksum.\n"); + goto out; + } + + hw->nvm.ops.release(hw); + + ret_val = igb_update_flash_i210(hw); + } else { + ret_val = -E1000_ERR_SWFW_SYNC; + } +out: + return ret_val; +} + +/** + * igb_update_flash_i210 - Commit EEPROM to the flash + * @hw: pointer to the HW structure + * + **/ +s32 igb_update_flash_i210(struct e1000_hw *hw) +{ + s32 ret_val = E1000_SUCCESS; + u32 flup; + + ret_val = igb_pool_flash_update_done_i210(hw); + if (ret_val == -E1000_ERR_NVM) { + hw_dbg("Flash update time out\n"); + goto out; + } + + flup = rd32(E1000_EECD) | E1000_EECD_FLUPD_I210; + wr32(E1000_EECD, flup); + + ret_val = igb_pool_flash_update_done_i210(hw); + if (ret_val == E1000_SUCCESS) + hw_dbg("Flash update complete\n"); + else + hw_dbg("Flash update time out\n"); + +out: + return ret_val; +} + +/** + * igb_pool_flash_update_done_i210 - Pool FLUDONE status. + * @hw: pointer to the HW structure + * + **/ +s32 igb_pool_flash_update_done_i210(struct e1000_hw *hw) +{ + s32 ret_val = -E1000_ERR_NVM; + u32 i, reg; + + for (i = 0; i < E1000_FLUDONE_ATTEMPTS; i++) { + reg = rd32(E1000_EECD); + if (reg & E1000_EECD_FLUDONE_I210) { + ret_val = E1000_SUCCESS; + break; + } + udelay(5); + } + + return ret_val; +} + +/** + * igb_valid_led_default_i210 - Verify a valid default LED config + * @hw: pointer to the HW structure + * @data: pointer to the NVM (EEPROM) + * + * Read the EEPROM for the current default LED configuration. If the + * LED configuration is not valid, set to a valid LED configuration. + **/ +s32 igb_valid_led_default_i210(struct e1000_hw *hw, u16 *data) +{ + s32 ret_val; + + ret_val = hw->nvm.ops.read(hw, NVM_ID_LED_SETTINGS, 1, data); + if (ret_val) { + hw_dbg("NVM Read Error\n"); + goto out; + } + + if (*data == ID_LED_RESERVED_0000 || *data == ID_LED_RESERVED_FFFF) { + switch (hw->phy.media_type) { + case e1000_media_type_internal_serdes: + *data = ID_LED_DEFAULT_I210_SERDES; + break; + case e1000_media_type_copper: + default: + *data = ID_LED_DEFAULT_I210; + break; + } + } +out: + return ret_val; +} diff --git a/drivers/net/igb/e1000_i210.h b/drivers/net/igb/e1000_i210.h new file mode 100644 index 000000000000..5dc2bd3f50bc --- /dev/null +++ b/drivers/net/igb/e1000_i210.h @@ -0,0 +1,76 @@ +/******************************************************************************* + + Intel(R) Gigabit Ethernet Linux driver + Copyright(c) 2007-2012 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. + + 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 St - Fifth Floor, Boston, MA 02110-1301 USA. + + The full GNU General Public License is included in this distribution in + the file called "COPYING". + + Contact Information: + e1000-devel Mailing List <e1000-devel@lists.sourceforge.net> + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +*******************************************************************************/ + +#ifndef _E1000_I210_H_ +#define _E1000_I210_H_ + +extern s32 igb_update_flash_i210(struct e1000_hw *hw); +extern s32 igb_update_nvm_checksum_i210(struct e1000_hw *hw); +extern s32 igb_validate_nvm_checksum_i210(struct e1000_hw *hw); +extern s32 igb_write_nvm_srwr_i210(struct e1000_hw *hw, u16 offset, + u16 words, u16 *data); +extern s32 igb_read_nvm_srrd_i210(struct e1000_hw *hw, u16 offset, + u16 words, u16 *data); +extern s32 igb_read_invm_i211(struct e1000_hw *hw, u16 address, u16 *data); +extern s32 igb_acquire_swfw_sync_i210(struct e1000_hw *hw, u16 mask); +extern void igb_release_swfw_sync_i210(struct e1000_hw *hw, u16 mask); +extern s32 igb_acquire_nvm_i210(struct e1000_hw *hw); +extern void igb_release_nvm_i210(struct e1000_hw *hw); +extern s32 igb_valid_led_default_i210(struct e1000_hw *hw, u16 *data); +extern s32 igb_read_nvm_i211(struct e1000_hw *hw, u16 offset, u16 words, + u16 *data); + +#define E1000_STM_OPCODE 0xDB00 +#define E1000_EEPROM_FLASH_SIZE_WORD 0x11 + +#define INVM_DWORD_TO_RECORD_TYPE(invm_dword) \ + (u8)((invm_dword) & 0x7) +#define INVM_DWORD_TO_WORD_ADDRESS(invm_dword) \ + (u8)(((invm_dword) & 0x0000FE00) >> 9) +#define INVM_DWORD_TO_WORD_DATA(invm_dword) \ + (u16)(((invm_dword) & 0xFFFF0000) >> 16) + +enum E1000_INVM_STRUCTURE_TYPE { + E1000_INVM_UNINITIALIZED_STRUCTURE = 0x00, + E1000_INVM_WORD_AUTOLOAD_STRUCTURE = 0x01, + E1000_INVM_CSR_AUTOLOAD_STRUCTURE = 0x02, + E1000_INVM_PHY_REGISTER_AUTOLOAD_STRUCTURE = 0x03, + E1000_INVM_RSA_KEY_SHA256_STRUCTURE = 0x04, + E1000_INVM_INVALIDATED_STRUCTURE = 0x0F, +}; + +#define E1000_INVM_RSA_KEY_SHA256_DATA_SIZE_IN_DWORDS 8 +#define E1000_INVM_CSR_AUTOLOAD_DATA_SIZE_IN_DWORDS 1 + +#define ID_LED_DEFAULT_I210 ((ID_LED_OFF1_ON2 << 8) | \ + (ID_LED_OFF1_OFF2 << 4) | \ + (ID_LED_DEF1_DEF2)) +#define ID_LED_DEFAULT_I210_SERDES ((ID_LED_DEF1_DEF2 << 8) | \ + (ID_LED_DEF1_DEF2 << 4) | \ + (ID_LED_DEF1_DEF2)) + +#endif diff --git a/drivers/net/igb/e1000_mac.c b/drivers/net/igb/e1000_mac.c index 2b5ef761d2ab..c3ee7b21ab4e 100644 --- a/drivers/net/igb/e1000_mac.c +++ b/drivers/net/igb/e1000_mac.c @@ -596,6 +596,7 @@ s32 igb_setup_link(struct e1000_hw *hw) ret_val = igb_set_fc_watermarks(hw); out: + return ret_val; } diff --git a/drivers/net/igb/e1000_nvm.c b/drivers/net/igb/e1000_nvm.c index 40407124e722..38047a584605 100644 --- a/drivers/net/igb/e1000_nvm.c +++ b/drivers/net/igb/e1000_nvm.c @@ -710,4 +710,3 @@ s32 igb_update_nvm_checksum(struct e1000_hw *hw) out: return ret_val; } - diff --git a/drivers/net/igb/e1000_phy.c b/drivers/net/igb/e1000_phy.c index e662554c62d6..d9e0ec065cf7 100644 --- a/drivers/net/igb/e1000_phy.c +++ b/drivers/net/igb/e1000_phy.c @@ -35,6 +35,7 @@ static s32 igb_phy_setup_autoneg(struct e1000_hw *hw); static void igb_phy_force_speed_duplex_setup(struct e1000_hw *hw, u16 *phy_ctrl); static s32 igb_wait_autoneg(struct e1000_hw *hw); +static s32 igb_set_master_slave_mode(struct e1000_hw *hw); /* Cable length tables */ static const u16 e1000_m88_cable_length_table[] = @@ -564,6 +565,11 @@ s32 igb_copper_link_setup_m88(struct e1000_hw *hw) hw_dbg("Error committing the PHY changes\n"); goto out; } + if (phy->type == e1000_phy_i210) { + ret_val = igb_set_master_slave_mode(hw); + if (ret_val) + return ret_val; + } out: return ret_val; @@ -1207,12 +1213,22 @@ s32 igb_phy_force_speed_duplex_m88(struct e1000_hw *hw) goto out; if (!link) { - if (hw->phy.type != e1000_phy_m88 || - hw->phy.id == I347AT4_E_PHY_ID || - hw->phy.id == M88E1112_E_PHY_ID) { + bool reset_dsp = true; + + switch (hw->phy.id) { + case I347AT4_E_PHY_ID: + case M88E1112_E_PHY_ID: + case I210_I_PHY_ID: + reset_dsp = false; + break; + default: + if (hw->phy.type != e1000_phy_m88) + reset_dsp = false; + break; + } + if (!reset_dsp) hw_dbg("Link taking longer than expected.\n"); - } else { - + else { /* * We didn't get link. * Reset the DSP and cross our fingers. @@ -1237,7 +1253,8 @@ s32 igb_phy_force_speed_duplex_m88(struct e1000_hw *hw) if (hw->phy.type != e1000_phy_m88 || hw->phy.id == I347AT4_E_PHY_ID || - hw->phy.id == M88E1112_E_PHY_ID) + hw->phy.id == M88E1112_E_PHY_ID || + hw->phy.id == I210_I_PHY_ID) goto out; ret_val = phy->ops.read_reg(hw, M88E1000_EXT_PHY_SPEC_CTRL, &phy_data); @@ -1435,6 +1452,7 @@ s32 igb_check_downshift(struct e1000_hw *hw) u16 phy_data, offset, mask; switch (phy->type) { + case e1000_phy_i210: case e1000_phy_m88: case e1000_phy_gg82563: offset = M88E1000_PHY_SPEC_STATUS; @@ -1470,7 +1488,7 @@ out: * * Polarity is determined based on the PHY specific status register. **/ -static s32 igb_check_polarity_m88(struct e1000_hw *hw) +s32 igb_check_polarity_m88(struct e1000_hw *hw) { struct e1000_phy_info *phy = &hw->phy; s32 ret_val; @@ -1659,6 +1677,7 @@ s32 igb_get_cable_length_m88_gen2(struct e1000_hw *hw) u16 phy_data, phy_data2, index, default_page, is_cm; switch (hw->phy.id) { + case I210_I_PHY_ID: case I347AT4_E_PHY_ID: /* Remember the original page select and set it to 7 */ ret_val = phy->ops.read_reg(hw, I347AT4_PAGE_SELECT, @@ -2123,10 +2142,16 @@ s32 igb_phy_init_script_igp3(struct e1000_hw *hw) void igb_power_up_phy_copper(struct e1000_hw *hw) { u16 mii_reg = 0; + u16 power_reg = 0; /* The PHY will retain its settings across a power down/up cycle */ hw->phy.ops.read_reg(hw, PHY_CONTROL, &mii_reg); mii_reg &= ~MII_CR_POWER_DOWN; + if (hw->phy.type == e1000_phy_i210) { + hw->phy.ops.read_reg(hw, GS40G_COPPER_SPEC, &power_reg); + power_reg &= ~GS40G_CS_POWER_DOWN; + hw->phy.ops.write_reg(hw, GS40G_COPPER_SPEC, power_reg); + } hw->phy.ops.write_reg(hw, PHY_CONTROL, mii_reg); } @@ -2140,10 +2165,18 @@ void igb_power_up_phy_copper(struct e1000_hw *hw) void igb_power_down_phy_copper(struct e1000_hw *hw) { u16 mii_reg = 0; + u16 power_reg = 0; /* The PHY will retain its settings across a power down/up cycle */ hw->phy.ops.read_reg(hw, PHY_CONTROL, &mii_reg); mii_reg |= MII_CR_POWER_DOWN; + + /* i210 Phy requires an additional bit for power up/down */ + if (hw->phy.type == e1000_phy_i210) { + hw->phy.ops.read_reg(hw, GS40G_COPPER_SPEC, &power_reg); + power_reg |= GS40G_CS_POWER_DOWN; + hw->phy.ops.write_reg(hw, GS40G_COPPER_SPEC, power_reg); + } hw->phy.ops.write_reg(hw, PHY_CONTROL, mii_reg); msleep(1); } @@ -2339,3 +2372,103 @@ s32 igb_get_cable_length_82580(struct e1000_hw *hw) out: return ret_val; } + +/** + * igb_write_phy_reg_gs40g - Write GS40G PHY register + * @hw: pointer to the HW structure + * @offset: lower half is register offset to write to + * upper half is page to use. + * @data: data to write at register offset + * + * Acquires semaphore, if necessary, then writes the data to PHY register + * at the offset. Release any acquired semaphores before exiting. + **/ +s32 igb_write_phy_reg_gs40g(struct e1000_hw *hw, u32 offset, u16 data) +{ + s32 ret_val; + u16 page = offset >> GS40G_PAGE_SHIFT; + + offset = offset & GS40G_OFFSET_MASK; + ret_val = hw->phy.ops.acquire(hw); + if (ret_val) + return ret_val; + + ret_val = igb_write_phy_reg_mdic(hw, GS40G_PAGE_SELECT, page); + if (ret_val) + goto release; + ret_val = igb_write_phy_reg_mdic(hw, offset, data); + +release: + hw->phy.ops.release(hw); + return ret_val; +} + +/** + * igb_read_phy_reg_gs40g - Read GS40G PHY register + * @hw: pointer to the HW structure + * @offset: lower half is register offset to read to + * upper half is page to use. + * @data: data to read at register offset + * + * Acquires semaphore, if necessary, then reads the data in the PHY register + * at the offset. Release any acquired semaphores before exiting. + **/ +s32 igb_read_phy_reg_gs40g(struct e1000_hw *hw, u32 offset, u16 *data) +{ + s32 ret_val; + u16 page = offset >> GS40G_PAGE_SHIFT; + + offset = offset & GS40G_OFFSET_MASK; + ret_val = hw->phy.ops.acquire(hw); + if (ret_val) + return ret_val; + + ret_val = igb_write_phy_reg_mdic(hw, GS40G_PAGE_SELECT, page); + if (ret_val) + goto release; + ret_val = igb_read_phy_reg_mdic(hw, offset, data); + +release: + hw->phy.ops.release(hw); + return ret_val; +} + +/** + * igb_set_master_slave_mode - Setup PHY for Master/slave mode + * @hw: pointer to the HW structure + * + * Sets up Master/slave mode + **/ +static s32 igb_set_master_slave_mode(struct e1000_hw *hw) +{ + s32 ret_val; + u16 phy_data; + + /* Resolve Master/Slave mode */ + ret_val = hw->phy.ops.read_reg(hw, PHY_1000T_CTRL, &phy_data); + if (ret_val) + return ret_val; + + /* load defaults for future use */ + hw->phy.original_ms_type = (phy_data & CR_1000T_MS_ENABLE) ? + ((phy_data & CR_1000T_MS_VALUE) ? + e1000_ms_force_master : + e1000_ms_force_slave) : e1000_ms_auto; + + switch (hw->phy.ms_type) { + case e1000_ms_force_master: + phy_data |= (CR_1000T_MS_ENABLE | CR_1000T_MS_VALUE); + break; + case e1000_ms_force_slave: + phy_data |= CR_1000T_MS_ENABLE; + phy_data &= ~(CR_1000T_MS_VALUE); + break; + case e1000_ms_auto: + phy_data &= ~CR_1000T_MS_ENABLE; + /* fall-through */ + default: + break; + } + + return hw->phy.ops.write_reg(hw, PHY_1000T_CTRL, phy_data); +} diff --git a/drivers/net/igb/e1000_phy.h b/drivers/net/igb/e1000_phy.h index 8510797b9d81..3b813a8bdf1a 100644 --- a/drivers/net/igb/e1000_phy.h +++ b/drivers/net/igb/e1000_phy.h @@ -73,6 +73,9 @@ s32 igb_copper_link_setup_82580(struct e1000_hw *hw); s32 igb_get_phy_info_82580(struct e1000_hw *hw); s32 igb_phy_force_speed_duplex_82580(struct e1000_hw *hw); s32 igb_get_cable_length_82580(struct e1000_hw *hw); +s32 igb_read_phy_reg_gs40g(struct e1000_hw *hw, u32 offset, u16 *data); +s32 igb_write_phy_reg_gs40g(struct e1000_hw *hw, u32 offset, u16 data); +s32 igb_check_polarity_m88(struct e1000_hw *hw); /* IGP01E1000 Specific Registers */ #define IGP01E1000_PHY_PORT_CONFIG 0x10 /* Port Config */ @@ -114,6 +117,13 @@ s32 igb_get_cable_length_82580(struct e1000_hw *hw); /* I82580 PHY Diagnostics Status */ #define I82580_DSTATUS_CABLE_LENGTH 0x03FC #define I82580_DSTATUS_CABLE_LENGTH_SHIFT 2 + +/* 82580 PHY Power Management */ +#define E1000_82580_PHY_POWER_MGMT 0xE14 +#define E1000_82580_PM_SPD 0x0001 /* Smart Power Down */ +#define E1000_82580_PM_D0_LPLU 0x0002 /* For D0a states */ +#define E1000_82580_PM_D3_LPLU 0x0004 /* For all other states */ + /* Enable flexible speed on link-up */ #define IGP02E1000_PM_D0_LPLU 0x0002 /* For D0a states */ #define IGP02E1000_PM_D3_LPLU 0x0004 /* For all other states */ @@ -133,4 +143,16 @@ s32 igb_get_cable_length_82580(struct e1000_hw *hw); #define E1000_CABLE_LENGTH_UNDEFINED 0xFF +/* GS40G - I210 PHY defines */ +#define GS40G_PAGE_SELECT 0x16 +#define GS40G_PAGE_SHIFT 16 +#define GS40G_OFFSET_MASK 0xFFFF +#define GS40G_PAGE_2 0x20000 +#define GS40G_MAC_REG2 0x15 +#define GS40G_MAC_LB 0x4140 +#define GS40G_MAC_SPEED_1G 0X0006 +#define GS40G_COPPER_SPEC 0x0010 +#define GS40G_CS_POWER_DOWN 0x0002 +#define GS40G_LINE_LB 0x4000 + #endif diff --git a/drivers/net/igb/e1000_regs.h b/drivers/net/igb/e1000_regs.h index 0990f6d860c7..bb62cf641318 100644 --- a/drivers/net/igb/e1000_regs.h +++ b/drivers/net/igb/e1000_regs.h @@ -351,4 +351,18 @@ #define E1000_O2BGPTC 0x08FE4 /* OS2BMC packets received by BMC */ #define E1000_O2BSPC 0x0415C /* OS2BMC packets transmitted by host */ +#define E1000_SRWR 0x12018 /* Shadow Ram Write Register - RW */ +#define E1000_I210_FLMNGCTL 0x12038 +#define E1000_I210_FLMNGDATA 0x1203C +#define E1000_I210_FLMNGCNT 0x12040 + +#define E1000_I210_FLSWCTL 0x12048 +#define E1000_I210_FLSWDATA 0x1204C +#define E1000_I210_FLSWCNT 0x12050 + +#define E1000_I210_FLA 0x1201C + +#define E1000_INVM_DATA_REG(_n) (0x12120 + 4*(_n)) +#define E1000_INVM_SIZE 64 /* Number of INVM Data Registers */ + #endif diff --git a/drivers/net/igb/igb.h b/drivers/net/igb/igb.h index 265e151b66c4..111236008d16 100644 --- a/drivers/net/igb/igb.h +++ b/drivers/net/igb/igb.h @@ -61,11 +61,14 @@ struct igb_adapter; #define MAX_Q_VECTORS 8 /* Transmit and receive queues */ -#define IGB_MAX_RX_QUEUES (adapter->vfs_allocated_count ? 2 : \ - (hw->mac.type > e1000_82575 ? 8 : 4)) +#define IGB_MAX_RX_QUEUES ((adapter->vfs_allocated_count ? 2 : \ + (hw->mac.type > e1000_82575 ? 8 : 4))) +#define IGB_MAX_RX_QUEUES_I210 4 +#define IGB_MAX_RX_QUEUES_I211 2 #define IGB_ABS_MAX_TX_QUEUES 8 -#define IGB_MAX_TX_QUEUES IGB_MAX_RX_QUEUES - +#define IGB_MAX_TX_QUEUES 16 +#define IGB_MAX_TX_QUEUES_I210 4 +#define IGB_MAX_TX_QUEUES_I211 2 #define IGB_MAX_VF_MC_ENTRIES 30 #define IGB_MAX_VF_FUNCTIONS 8 #define IGB_MAX_VFTA_ENTRIES 128 @@ -227,8 +230,9 @@ struct igb_ring { }; }; -#define IGB_RING_FLAG_RX_CSUM 0x00000001 /* RX CSUM enabled */ -#define IGB_RING_FLAG_RX_SCTP_CSUM 0x00000002 /* SCTP CSUM offload enabled */ +#define IGB_RING_FLAG_RX_CSUM 0x00000001 /* RX CSUM enabled */ +#define IGB_RING_FLAG_RX_SCTP_CSUM 0x00000002 /* SCTP CSUM offload enabled */ +#define IGB_RING_FLAG_RX_LB_VLAN_BSWAP 0x00000003 #define IGB_RING_FLAG_TX_CTX_IDX 0x00000001 /* HW requires context index */ diff --git a/drivers/net/igb/igb_ethtool.c b/drivers/net/igb/igb_ethtool.c index 414b0225be89..c9e3d65bd71d 100644 --- a/drivers/net/igb/igb_ethtool.c +++ b/drivers/net/igb/igb_ethtool.c @@ -549,10 +549,13 @@ static void igb_get_regs(struct net_device *netdev, regs_buff[548] = rd32(E1000_TDFT); regs_buff[549] = rd32(E1000_TDFHS); regs_buff[550] = rd32(E1000_TDFPC); - regs_buff[551] = adapter->stats.o2bgptc; - regs_buff[552] = adapter->stats.b2ospc; - regs_buff[553] = adapter->stats.o2bspc; - regs_buff[554] = adapter->stats.b2ogprc; + + if (hw->mac.type > e1000_82580) { + regs_buff[551] = adapter->stats.o2bgptc; + regs_buff[552] = adapter->stats.b2ospc; + regs_buff[553] = adapter->stats.o2bspc; + regs_buff[554] = adapter->stats.b2ogprc; + } } static int igb_get_eeprom_len(struct net_device *netdev) @@ -621,6 +624,9 @@ static int igb_set_eeprom(struct net_device *netdev, if (eeprom->len == 0) return -EOPNOTSUPP; + if (hw->mac.type == e1000_i211) + return -EOPNOTSUPP; + if (eeprom->magic != (hw->vendor_id | (hw->device_id << 16))) return -EFAULT; @@ -855,6 +861,36 @@ struct igb_reg_test { #define TABLE64_TEST_LO 5 #define TABLE64_TEST_HI 6 +/* i210 reg test */ +static struct igb_reg_test reg_test_i210[] = { + { E1000_FCAL, 0x100, 1, PATTERN_TEST, 0xFFFFFFFF, 0xFFFFFFFF }, + { E1000_FCAH, 0x100, 1, PATTERN_TEST, 0x0000FFFF, 0xFFFFFFFF }, + { E1000_FCT, 0x100, 1, PATTERN_TEST, 0x0000FFFF, 0xFFFFFFFF }, + { E1000_RDBAL(0), 0x100, 4, PATTERN_TEST, 0xFFFFFF80, 0xFFFFFFFF }, + { E1000_RDBAH(0), 0x100, 4, PATTERN_TEST, 0xFFFFFFFF, 0xFFFFFFFF }, + { E1000_RDLEN(0), 0x100, 4, PATTERN_TEST, 0x000FFF80, 0x000FFFFF }, + /* RDH is read-only for i210, only test RDT. */ + { E1000_RDT(0), 0x100, 4, PATTERN_TEST, 0x0000FFFF, 0x0000FFFF }, + { E1000_FCRTH, 0x100, 1, PATTERN_TEST, 0x0000FFF0, 0x0000FFF0 }, + { E1000_FCTTV, 0x100, 1, PATTERN_TEST, 0x0000FFFF, 0x0000FFFF }, + { E1000_TIPG, 0x100, 1, PATTERN_TEST, 0x3FFFFFFF, 0x3FFFFFFF }, + { E1000_TDBAL(0), 0x100, 4, PATTERN_TEST, 0xFFFFFF80, 0xFFFFFFFF }, + { E1000_TDBAH(0), 0x100, 4, PATTERN_TEST, 0xFFFFFFFF, 0xFFFFFFFF }, + { E1000_TDLEN(0), 0x100, 4, PATTERN_TEST, 0x000FFF80, 0x000FFFFF }, + { E1000_TDT(0), 0x100, 4, PATTERN_TEST, 0x0000FFFF, 0x0000FFFF }, + { E1000_RCTL, 0x100, 1, SET_READ_TEST, 0xFFFFFFFF, 0x00000000 }, + { E1000_RCTL, 0x100, 1, SET_READ_TEST, 0x04CFB0FE, 0x003FFFFB }, + { E1000_RCTL, 0x100, 1, SET_READ_TEST, 0x04CFB0FE, 0xFFFFFFFF }, + { E1000_TCTL, 0x100, 1, SET_READ_TEST, 0xFFFFFFFF, 0x00000000 }, + { E1000_RA, 0, 16, TABLE64_TEST_LO, + 0xFFFFFFFF, 0xFFFFFFFF }, + { E1000_RA, 0, 16, TABLE64_TEST_HI, + 0x900FFFFF, 0xFFFFFFFF }, + { E1000_MTA, 0, 128, TABLE32_TEST, + 0xFFFFFFFF, 0xFFFFFFFF }, + { 0, 0, 0, 0, 0 } +}; + /* i350 reg test */ static struct igb_reg_test reg_test_i350[] = { { E1000_FCAL, 0x100, 1, PATTERN_TEST, 0xFFFFFFFF, 0xFFFFFFFF }, @@ -1077,6 +1113,11 @@ static int igb_reg_test(struct igb_adapter *adapter, u64 *data) test = reg_test_i350; toggle = 0x7FEFF3FF; break; + case e1000_i210: + case e1000_i211: + test = reg_test_i210; + toggle = 0x7FEFF3FF; + break; case e1000_82580: test = reg_test_82580; toggle = 0x7FEFF3FF; @@ -1158,23 +1199,13 @@ static int igb_reg_test(struct igb_adapter *adapter, u64 *data) static int igb_eeprom_test(struct igb_adapter *adapter, u64 *data) { - u16 temp; - u16 checksum = 0; - u16 i; - *data = 0; - /* Read and add up the contents of the EEPROM */ - for (i = 0; i < (NVM_CHECKSUM_REG + 1); i++) { - if ((adapter->hw.nvm.ops.read(&adapter->hw, i, 1, &temp)) < 0) { - *data = 1; - break; - } - checksum += temp; - } - /* If Checksum is not Correct return error else test passed */ - if ((checksum != (u16) NVM_SUM) && !(*data)) - *data = 2; + /* Validate eeprom on all parts but i211 */ + if (adapter->hw.mac.type != e1000_i211) { + if (adapter->hw.nvm.ops.validate(&adapter->hw) < 0) + *data = 2; + } return *data; } @@ -1240,6 +1271,8 @@ static int igb_intr_test(struct igb_adapter *adapter, u64 *data) ics_mask = 0x77DCFED5; break; case e1000_i350: + case e1000_i210: + case e1000_i211: ics_mask = 0x77DCFED5; break; default: @@ -1407,23 +1440,35 @@ static int igb_integrated_phy_loopback(struct igb_adapter *adapter) { struct e1000_hw *hw = &adapter->hw; u32 ctrl_reg = 0; + u16 phy_reg = 0; hw->mac.autoneg = false; - if (hw->phy.type == e1000_phy_m88) { + switch (hw->phy.type) { + case e1000_phy_m88: /* Auto-MDI/MDIX Off */ igb_write_phy_reg(hw, M88E1000_PHY_SPEC_CTRL, 0x0808); /* reset to update Auto-MDI/MDIX */ igb_write_phy_reg(hw, PHY_CONTROL, 0x9140); /* autoneg off */ igb_write_phy_reg(hw, PHY_CONTROL, 0x8140); - } else if (hw->phy.type == e1000_phy_82580) { + break; + case e1000_phy_82580: /* enable MII loopback */ igb_write_phy_reg(hw, I82580_PHY_LBK_CTRL, 0x8041); + break; + case e1000_phy_i210: + /* set loopback speed in PHY */ + igb_read_phy_reg(hw, (GS40G_PAGE_SELECT & GS40G_PAGE_2), + &phy_reg); + phy_reg |= GS40G_MAC_SPEED_1G; + igb_write_phy_reg(hw, (GS40G_PAGE_SELECT & GS40G_PAGE_2), + phy_reg); + ctrl_reg = rd32(E1000_CTRL_EXT); + default: + break; } - ctrl_reg = rd32(E1000_CTRL); - /* force 1000, set loopback */ igb_write_phy_reg(hw, PHY_CONTROL, 0x4140); @@ -1436,7 +1481,7 @@ static int igb_integrated_phy_loopback(struct igb_adapter *adapter) E1000_CTRL_FD | /* Force Duplex to FULL */ E1000_CTRL_SLU); /* Set link up enable bit */ - if (hw->phy.type == e1000_phy_m88) + if ((hw->phy.type == e1000_phy_m88) || (hw->phy.type == e1000_phy_i210)) ctrl_reg |= E1000_CTRL_ILOS; /* Invert Loss of Signal */ wr32(E1000_CTRL, ctrl_reg); @@ -1444,7 +1489,7 @@ static int igb_integrated_phy_loopback(struct igb_adapter *adapter) /* Disable the receiver on the PHY so when a cable is plugged in, the * PHY does not begin to autoneg when a cable is reconnected to the NIC. */ - if (hw->phy.type == e1000_phy_m88) + if ((hw->phy.type == e1000_phy_m88) || (hw->phy.type == e1000_phy_i210)) igb_phy_disable_receiver(adapter); udelay(500); @@ -1703,6 +1748,14 @@ static int igb_loopback_test(struct igb_adapter *adapter, u64 *data) *data = 0; goto out; } + if ((adapter->hw.mac.type == e1000_i210) + || (adapter->hw.mac.type == e1000_i210)) { + dev_err(&adapter->pdev->dev, + "Loopback test not supported " + "on this part at this time.\n"); + *data = 0; + goto out; + } *data = igb_setup_desc_rings(adapter); if (*data) goto out; diff --git a/drivers/net/igb/igb_main.c b/drivers/net/igb/igb_main.c index 40d4c405fd7e..3b1aa4e15df8 100644 --- a/drivers/net/igb/igb_main.c +++ b/drivers/net/igb/igb_main.c @@ -50,6 +50,7 @@ #ifdef CONFIG_IGB_DCA #include <linux/dca.h> #endif +#include <linux/ctype.h> #include "igb.h" #define MAJ 3 @@ -67,7 +68,15 @@ static const struct e1000_info *igb_info_tbl[] = { [board_82575] = &e1000_82575_info, }; +static char g_mac_addr[ETH_ALEN]; +static int g_usr_mac = 0; + static DEFINE_PCI_DEVICE_TABLE(igb_pci_tbl) = { + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_COPPER), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES), board_82575 }, + { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SGMII), board_82575 }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_COPPER), board_82575 }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_FIBER), board_82575 }, { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_SERDES), board_82575 }, @@ -255,6 +264,40 @@ static const struct igb_reg_info igb_reg_info_tbl[] = { {} }; +/* Retrieve user set MAC address */ +static int __init setup_igb_mac(char *macstr) +{ + int i, j; + unsigned char result, value; + + for (i = 0; i < ETH_ALEN; i++) { + result = 0; + + if (i != 5 && *(macstr + 2) != ':') + return -1; + + for (j = 0; j < 2; j++) { + if (isxdigit(*macstr) + && (value = + isdigit(*macstr) ? *macstr - + '0' : toupper(*macstr) - 'A' + 10) < 16) { + result = result * 16 + value; + macstr++; + } else + return -1; + } + + macstr++; + g_mac_addr[i] = result; + } + + g_usr_mac = 1; + + return 0; +} + +__setup("igb_mac=", setup_igb_mac); + /* * igb_regdump - register printout routine */ @@ -647,6 +690,8 @@ static void igb_cache_ring_register(struct igb_adapter *adapter) case e1000_82575: case e1000_82580: case e1000_i350: + case e1000_i210: + case e1000_i211: default: for (; i < adapter->num_rx_queues; i++) adapter->rx_ring[i]->reg_idx = rbase_offset + i; @@ -711,6 +756,14 @@ static int igb_alloc_queues(struct igb_adapter *adapter) /* set flag indicating ring supports SCTP checksum offload */ if (adapter->hw.mac.type >= e1000_82576) ring->flags |= IGB_RING_FLAG_RX_SCTP_CSUM; + + /* + * On i350, i210, and i211, loopback VLAN packets + * have the tag byte-swapped. + * */ + if (adapter->hw.mac.type >= e1000_i350) + ring->flags |= IGB_RING_FLAG_RX_LB_VLAN_BSWAP; + adapter->rx_ring[i] = ring; } @@ -791,6 +844,8 @@ static void igb_assign_vector(struct igb_q_vector *q_vector, int msix_vector) break; case e1000_82580: case e1000_i350: + case e1000_i210: + case e1000_i211: /* 82580 uses the same table-based approach as 82576 but has fewer entries as a result we carry over for queues greater than 4. */ if (rx_queue > IGB_N0_QUEUE) { @@ -872,6 +927,8 @@ static void igb_configure_msix(struct igb_adapter *adapter) case e1000_82576: case e1000_82580: case e1000_i350: + case e1000_i210: + case e1000_i211: /* Turn on MSI-X capability first, or our settings * won't stick. And it will take days to debug. */ wr32(E1000_GPIE, E1000_GPIE_MSIX_MODE | @@ -1018,6 +1075,11 @@ static int igb_set_interrupt_capability(struct igb_adapter *adapter) if (!(adapter->flags & IGB_FLAG_QUEUE_PAIRS)) numvecs += adapter->num_tx_queues; + /* i210 and i211 can only have 4 MSIX vectors for rx/tx queues. */ + if ((adapter->hw.mac.type == e1000_i210) + || (adapter->hw.mac.type == e1000_i211)) + numvecs = 4; + /* store the number of vectors reserved for queues */ adapter->num_q_vectors = numvecs; @@ -1025,6 +1087,7 @@ static int igb_set_interrupt_capability(struct igb_adapter *adapter) numvecs++; adapter->msix_entries = kcalloc(numvecs, sizeof(struct msix_entry), GFP_KERNEL); + if (!adapter->msix_entries) goto msi_only; @@ -1599,6 +1662,8 @@ void igb_reset(struct igb_adapter *adapter) pba &= E1000_RXPBS_SIZE_MASK_82576; break; case e1000_82575: + case e1000_i210: + case e1000_i211: default: pba = E1000_PBA_34K; break; @@ -1839,7 +1904,7 @@ static int __devinit igb_probe(struct pci_dev *pdev, */ if (pdev->is_virtfn) { WARN(1, KERN_ERR "%s (%hx:%hx) should not be a VF!\n", - pci_name(pdev), pdev->vendor, pdev->device); + pci_name(pdev), pdev->vendor, pdev->device); return -EINVAL; } @@ -1979,17 +2044,39 @@ static int __devinit igb_probe(struct pci_dev *pdev, * known good starting state */ hw->mac.ops.reset_hw(hw); - /* make sure the NVM is good */ - if (hw->nvm.ops.validate(hw) < 0) { - dev_err(&pdev->dev, "The NVM Checksum Is Not Valid\n"); - err = -EIO; - goto err_eeprom; +#ifndef CONFIG_MACH_APALIS_T30 +/* Hack: skip NVM validation on Apalis T30 for now */ + /* + * make sure the NVM is good , i211 parts have special NVM that + * doesn't contain a checksum + */ + if (hw->mac.type != e1000_i211) { + if (hw->nvm.ops.validate(hw) < 0) { + dev_err(&pdev->dev, "The NVM Checksum Is Not Valid\n"); + err = -EIO; + goto err_eeprom; + } } +#endif /* CONFIG_MACH_APALIS_T30 */ /* copy the MAC address out of the NVM */ if (hw->mac.ops.read_mac_addr(hw)) dev_err(&pdev->dev, "NVM Read Error\n"); +#ifdef CONFIG_MACH_APALIS_T30 + if (g_usr_mac && (g_usr_mac < 3)) { + /* Get user set MAC address */ + if (g_usr_mac == 2) { + /* 0x100000 offset for 2nd Ethernet MAC */ + g_mac_addr[3] += 0x10; + if (g_mac_addr[3] < 0x10) + dev_warn(&pdev->dev, "MAC address byte 3 (0x%02x) wrap around", g_mac_addr[3]); + } + memcpy(hw->mac.addr, g_mac_addr, ETH_ALEN); + g_usr_mac++; + } else dev_warn(&pdev->dev, "using default igb MAC"); +#endif /* CONFIG_MACH_APALIS_T30 */ + memcpy(netdev->dev_addr, hw->mac.addr, netdev->addr_len); memcpy(netdev->perm_addr, hw->mac.addr, netdev->addr_len); @@ -2117,6 +2204,8 @@ static int __devinit igb_probe(struct pci_dev *pdev, adapter->num_rx_queues, adapter->num_tx_queues); switch (hw->mac.type) { case e1000_i350: + case e1000_i210: + case e1000_i211: igb_set_eee_i350(hw); break; default: @@ -2231,11 +2320,17 @@ static void __devinit igb_probe_vfs(struct igb_adapter * adapter) { #ifdef CONFIG_PCI_IOV struct pci_dev *pdev = adapter->pdev; + struct e1000_hw *hw = &adapter->hw; + + /* Virtualization features not supported on i210 family. */ + if ((hw->mac.type == e1000_i210) || (hw->mac.type == e1000_i211)) + return; if (adapter->vfs_allocated_count) { adapter->vf_data = kcalloc(adapter->vfs_allocated_count, sizeof(struct vf_data_storage), GFP_KERNEL); + /* if allocation failed then we do not support SR-IOV */ if (!adapter->vf_data) { adapter->vfs_allocated_count = 0; @@ -2409,11 +2504,28 @@ static int __devinit igb_sw_init(struct igb_adapter *adapter) } else adapter->vfs_allocated_count = max_vfs; break; + case e1000_i210: + case e1000_i211: + adapter->vfs_allocated_count = 0; + break; default: break; } #endif /* CONFIG_PCI_IOV */ - adapter->rss_queues = min_t(u32, IGB_MAX_RX_QUEUES, num_online_cpus()); + switch (hw->mac.type) { + case e1000_i210: + adapter->rss_queues = min_t(u32, IGB_MAX_RX_QUEUES_I210, + num_online_cpus()); + break; + case e1000_i211: + adapter->rss_queues = min_t(u32, IGB_MAX_RX_QUEUES_I211, + num_online_cpus()); + break; + default: + adapter->rss_queues = min_t(u32, IGB_MAX_RX_QUEUES, + num_online_cpus()); + break; + } /* i350 cannot do RSS and SR-IOV at the same time */ if (hw->mac.type == e1000_i350 && adapter->vfs_allocated_count) adapter->rss_queues = 1; @@ -2438,7 +2550,7 @@ static int __devinit igb_sw_init(struct igb_adapter *adapter) /* Explicitly disable IRQ since the NIC can be in any state. */ igb_irq_disable(adapter); - if (hw->mac.type == e1000_i350) + if (hw->mac.type >= e1000_i350) adapter->flags &= ~IGB_FLAG_DMAC; set_bit(__IGB_DOWN, &adapter->state); @@ -2852,6 +2964,17 @@ static void igb_setup_mrqc(struct igb_adapter *adapter) /* Don't need to set TUOFL or IPOFL, they default to 1 */ wr32(E1000_RXCSUM, rxcsum); + /* + * Generate RSS hash based on TCP port numbers and/or + * IPv4/v6 src and dst addresses since UDP cannot be + * hashed reliably due to IP fragmentation + */ + + mrqc = E1000_MRQC_RSS_FIELD_IPV4 | + E1000_MRQC_RSS_FIELD_IPV4_TCP | + E1000_MRQC_RSS_FIELD_IPV6 | + E1000_MRQC_RSS_FIELD_IPV6_TCP | + E1000_MRQC_RSS_FIELD_IPV6_TCP_EX; /* If VMDq is enabled then we set the appropriate mode for that, else * we default to RSS so that an RSS hash is calculated per packet even @@ -2867,25 +2990,15 @@ static void igb_setup_mrqc(struct igb_adapter *adapter) wr32(E1000_VT_CTL, vtctl); } if (adapter->rss_queues > 1) - mrqc = E1000_MRQC_ENABLE_VMDQ_RSS_2Q; + mrqc |= E1000_MRQC_ENABLE_VMDQ_RSS_2Q; else - mrqc = E1000_MRQC_ENABLE_VMDQ; + mrqc |= E1000_MRQC_ENABLE_VMDQ; } else { - mrqc = E1000_MRQC_ENABLE_RSS_4Q; + if (hw->mac.type != e1000_i211) + mrqc |= E1000_MRQC_ENABLE_RSS_4Q; } igb_vmm_control(adapter); - /* - * Generate RSS hash based on TCP port numbers and/or - * IPv4/v6 src and dst addresses since UDP cannot be - * hashed reliably due to IP fragmentation - */ - mrqc |= E1000_MRQC_RSS_FIELD_IPV4 | - E1000_MRQC_RSS_FIELD_IPV4_TCP | - E1000_MRQC_RSS_FIELD_IPV6 | - E1000_MRQC_RSS_FIELD_IPV6_TCP | - E1000_MRQC_RSS_FIELD_IPV6_TCP_EX; - wr32(E1000_MRQC, mrqc); } @@ -3484,7 +3597,7 @@ static void igb_set_rx_mode(struct net_device *netdev) * we will have issues with VLAN tag stripping not being done for frames * that are only arriving because we are the default pool */ - if (hw->mac.type < e1000_82576) + if ((hw->mac.type < e1000_82576) || (hw->mac.type > e1000_i350)) return; vmolr |= rd32(E1000_VMOLR(vfn)) & @@ -3581,7 +3694,7 @@ static bool igb_thermal_sensor_event(struct e1000_hw *hw, u32 event) bool ret = false; u32 ctrl_ext, thstat; - /* check for thermal sensor event on i350, copper only */ + /* check for thermal sensor event on i350 copper only */ if (hw->mac.type == e1000_i350) { thstat = rd32(E1000_THSTAT); ctrl_ext = rd32(E1000_CTRL_EXT); @@ -6858,6 +6971,8 @@ static void igb_vmm_control(struct igb_adapter *adapter) switch (hw->mac.type) { case e1000_82575: + case e1000_i210: + case e1000_i211: default: /* replication is not supported for 82575 */ return; diff --git a/drivers/net/usb/asix.c b/drivers/net/usb/asix.c index 710557700095..f2d5822338d7 100644 --- a/drivers/net/usb/asix.c +++ b/drivers/net/usb/asix.c @@ -1,8 +1,7 @@ /* * ASIX AX8817X based USB 2.0 Ethernet Devices - * Copyright (C) 2003-2006 David Hollis <dhollis@davehollis.com> + * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.com> * Copyright (C) 2005 Phil Chang <pchang23@sbcglobal.net> - * Copyright (C) 2006 James Painter <jamie.painter@iname.com> * Copyright (c) 2002-2003 TiVo Inc. * * This program is free software; you can redistribute it and/or modify @@ -20,11 +19,16 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -// #define DEBUG // error path messages, extra info -// #define VERBOSE // more; success messages +//#define DEBUG // debug messages, extra info +#include <linux/version.h> +//#include <linux/config.h> +#ifdef CONFIG_USB_DEBUG +# define DEBUG +#endif #include <linux/module.h> #include <linux/kmod.h> +#include <linux/sched.h> #include <linux/init.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> @@ -33,690 +37,863 @@ #include <linux/mii.h> #include <linux/usb.h> #include <linux/crc32.h> -#include <linux/usb/usbnet.h> -#include <linux/slab.h> -#define DRIVER_VERSION "14-Jun-2006" -static const char driver_name [] = "asix"; - -/* ASIX AX8817X based USB 2.0 Ethernet Devices */ +#include "axusbnet.c" +#include "asix.h" + +#define DRV_VERSION "4.4.0" + +static char version[] = +KERN_INFO "ASIX USB Ethernet Adapter:v" DRV_VERSION + " " __TIME__ " " __DATE__ "\n" +KERN_INFO " http://www.asix.com.tw\n"; + +static char g_mac_addr[ETH_ALEN]; +static int g_usr_mac = 0; + +/* configuration of maximum bulk in size */ +static int bsize = AX88772B_MAX_BULKIN_16K; +module_param (bsize, int, 0); +MODULE_PARM_DESC (bsize, "Maximum transfer size per bulk"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void ax88772b_link_reset (void *data); +static void ax88772a_link_reset (void *data); +static void ax88772_link_reset (void *data); +#else +static void ax88772b_link_reset (struct work_struct *work); +static void ax88772a_link_reset (struct work_struct *work); +static void ax88772_link_reset (struct work_struct *work); +#endif +static int ax88772a_phy_powerup (struct usbnet *dev); + +/* Retrieve user set MAC address */ +static int __init setup_asix_mac(char *macstr) +{ + int i, j; + unsigned char result, value; + + for (i = 0; i < ETH_ALEN; i++) { + result = 0; + + if (i != 5 && *(macstr + 2) != ':') + return -1; + + for (j = 0; j < 2; j++) { + if (isxdigit(*macstr) + && (value = + isdigit(*macstr) ? *macstr - + '0' : toupper(*macstr) - 'A' + 10) < 16) { + result = result * 16 + value; + macstr++; + } else + return -1; + } -#define AX_CMD_SET_SW_MII 0x06 -#define AX_CMD_READ_MII_REG 0x07 -#define AX_CMD_WRITE_MII_REG 0x08 -#define AX_CMD_SET_HW_MII 0x0a -#define AX_CMD_READ_EEPROM 0x0b -#define AX_CMD_WRITE_EEPROM 0x0c -#define AX_CMD_WRITE_ENABLE 0x0d -#define AX_CMD_WRITE_DISABLE 0x0e -#define AX_CMD_READ_RX_CTL 0x0f -#define AX_CMD_WRITE_RX_CTL 0x10 -#define AX_CMD_READ_IPG012 0x11 -#define AX_CMD_WRITE_IPG0 0x12 -#define AX_CMD_WRITE_IPG1 0x13 -#define AX_CMD_READ_NODE_ID 0x13 -#define AX_CMD_WRITE_NODE_ID 0x14 -#define AX_CMD_WRITE_IPG2 0x14 -#define AX_CMD_WRITE_MULTI_FILTER 0x16 -#define AX88172_CMD_READ_NODE_ID 0x17 -#define AX_CMD_READ_PHY_ID 0x19 -#define AX_CMD_READ_MEDIUM_STATUS 0x1a -#define AX_CMD_WRITE_MEDIUM_MODE 0x1b -#define AX_CMD_READ_MONITOR_MODE 0x1c -#define AX_CMD_WRITE_MONITOR_MODE 0x1d -#define AX_CMD_READ_GPIOS 0x1e -#define AX_CMD_WRITE_GPIOS 0x1f -#define AX_CMD_SW_RESET 0x20 -#define AX_CMD_SW_PHY_STATUS 0x21 -#define AX_CMD_SW_PHY_SELECT 0x22 - -#define AX_MONITOR_MODE 0x01 -#define AX_MONITOR_LINK 0x02 -#define AX_MONITOR_MAGIC 0x04 -#define AX_MONITOR_HSFS 0x10 - -/* AX88172 Medium Status Register values */ -#define AX88172_MEDIUM_FD 0x02 -#define AX88172_MEDIUM_TX 0x04 -#define AX88172_MEDIUM_FC 0x10 -#define AX88172_MEDIUM_DEFAULT \ - ( AX88172_MEDIUM_FD | AX88172_MEDIUM_TX | AX88172_MEDIUM_FC ) - -#define AX_MCAST_FILTER_SIZE 8 -#define AX_MAX_MCAST 64 - -#define AX_SWRESET_CLEAR 0x00 -#define AX_SWRESET_RR 0x01 -#define AX_SWRESET_RT 0x02 -#define AX_SWRESET_PRTE 0x04 -#define AX_SWRESET_PRL 0x08 -#define AX_SWRESET_BZ 0x10 -#define AX_SWRESET_IPRL 0x20 -#define AX_SWRESET_IPPD 0x40 - -#define AX88772_IPG0_DEFAULT 0x15 -#define AX88772_IPG1_DEFAULT 0x0c -#define AX88772_IPG2_DEFAULT 0x12 - -/* AX88772 & AX88178 Medium Mode Register */ -#define AX_MEDIUM_PF 0x0080 -#define AX_MEDIUM_JFE 0x0040 -#define AX_MEDIUM_TFC 0x0020 -#define AX_MEDIUM_RFC 0x0010 -#define AX_MEDIUM_ENCK 0x0008 -#define AX_MEDIUM_AC 0x0004 -#define AX_MEDIUM_FD 0x0002 -#define AX_MEDIUM_GM 0x0001 -#define AX_MEDIUM_SM 0x1000 -#define AX_MEDIUM_SBP 0x0800 -#define AX_MEDIUM_PS 0x0200 -#define AX_MEDIUM_RE 0x0100 - -#define AX88178_MEDIUM_DEFAULT \ - (AX_MEDIUM_PS | AX_MEDIUM_FD | AX_MEDIUM_AC | \ - AX_MEDIUM_RFC | AX_MEDIUM_TFC | AX_MEDIUM_JFE | \ - AX_MEDIUM_RE ) - -#define AX88772_MEDIUM_DEFAULT \ - (AX_MEDIUM_FD | AX_MEDIUM_RFC | \ - AX_MEDIUM_TFC | AX_MEDIUM_PS | \ - AX_MEDIUM_AC | AX_MEDIUM_RE ) - -/* AX88772 & AX88178 RX_CTL values */ -#define AX_RX_CTL_SO 0x0080 -#define AX_RX_CTL_AP 0x0020 -#define AX_RX_CTL_AM 0x0010 -#define AX_RX_CTL_AB 0x0008 -#define AX_RX_CTL_SEP 0x0004 -#define AX_RX_CTL_AMALL 0x0002 -#define AX_RX_CTL_PRO 0x0001 -#define AX_RX_CTL_MFB_2048 0x0000 -#define AX_RX_CTL_MFB_4096 0x0100 -#define AX_RX_CTL_MFB_8192 0x0200 -#define AX_RX_CTL_MFB_16384 0x0300 - -#define AX_DEFAULT_RX_CTL \ - (AX_RX_CTL_SO | AX_RX_CTL_AB ) - -/* GPIO 0 .. 2 toggles */ -#define AX_GPIO_GPO0EN 0x01 /* GPIO0 Output enable */ -#define AX_GPIO_GPO_0 0x02 /* GPIO0 Output value */ -#define AX_GPIO_GPO1EN 0x04 /* GPIO1 Output enable */ -#define AX_GPIO_GPO_1 0x08 /* GPIO1 Output value */ -#define AX_GPIO_GPO2EN 0x10 /* GPIO2 Output enable */ -#define AX_GPIO_GPO_2 0x20 /* GPIO2 Output value */ -#define AX_GPIO_RESERVED 0x40 /* Reserved */ -#define AX_GPIO_RSE 0x80 /* Reload serial EEPROM */ - -#define AX_EEPROM_MAGIC 0xdeadbeef -#define AX88172_EEPROM_LEN 0x40 -#define AX88772_EEPROM_LEN 0xff - -#define PHY_MODE_MARVELL 0x0000 -#define MII_MARVELL_LED_CTRL 0x0018 -#define MII_MARVELL_STATUS 0x001b -#define MII_MARVELL_CTRL 0x0014 - -#define MARVELL_LED_MANUAL 0x0019 - -#define MARVELL_STATUS_HWCFG 0x0004 - -#define MARVELL_CTRL_TXDELAY 0x0002 -#define MARVELL_CTRL_RXDELAY 0x0080 - -/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */ -struct asix_data { - u8 multi_filter[AX_MCAST_FILTER_SIZE]; - u8 mac_addr[ETH_ALEN]; - u8 phymode; - u8 ledmode; - u8 eeprom_len; -}; + macstr++; + g_mac_addr[i] = result; + } -struct ax88172_int_data { - __le16 res1; - u8 link; - __le16 res2; - u8 status; - __le16 res3; -} __packed; + g_usr_mac = 1; -static int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, - u16 size, void *data) -{ - void *buf; - int err = -ENOMEM; + return 0; +} - netdev_dbg(dev->net, "asix_read_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n", - cmd, value, index, size); +__setup("asix_mac=", setup_asix_mac); - buf = kmalloc(size, GFP_KERNEL); - if (!buf) - goto out; +/* ASIX AX8817X based USB 2.0 Ethernet Devices */ - err = usb_control_msg( +static int ax8817x_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + return usb_control_msg( dev->udev, usb_rcvctrlpipe(dev->udev, 0), cmd, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, index, - buf, + data, size, USB_CTRL_GET_TIMEOUT); - if (err == size) - memcpy(data, buf, size); - else if (err >= 0) - err = -EINVAL; - kfree(buf); - -out: - return err; } -static int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, +static int ax8817x_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data) { - void *buf = NULL; - int err = -ENOMEM; - - netdev_dbg(dev->net, "asix_write_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n", - cmd, value, index, size); - - if (data) { - buf = kmemdup(data, size, GFP_KERNEL); - if (!buf) - goto out; - } - - err = usb_control_msg( + return usb_control_msg( dev->udev, usb_sndctrlpipe(dev->udev, 0), cmd, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, index, - buf, + data, size, USB_CTRL_SET_TIMEOUT); - kfree(buf); - -out: - return err; } -static void asix_async_cmd_callback(struct urb *urb) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void ax8817x_async_cmd_callback(struct urb *urb, struct pt_regs *regs) +#else +static void ax8817x_async_cmd_callback(struct urb *urb) +#endif { struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; - int status = urb->status; - if (status < 0) - printk(KERN_DEBUG "asix_async_cmd_callback() failed with %d", - status); + if (urb->status < 0) + printk(KERN_DEBUG "ax8817x_async_cmd_callback() failed with %d", + urb->status); kfree(req); usb_free_urb(urb); } -static void -asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, - u16 size, void *data) +static int ax8817x_set_mac_addr (struct net_device *net, void *p) { - struct usb_ctrlrequest *req; - int status; - struct urb *urb; + struct usbnet *dev = netdev_priv(net); + struct sockaddr *addr = p; - netdev_dbg(dev->net, "asix_write_cmd_async() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n", - cmd, value, index, size); - if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) { - netdev_err(dev->net, "Error allocating URB in write_cmd_async!\n"); + memcpy (net->dev_addr, addr->sa_data, ETH_ALEN); + + /* Set the MAC address */ + return ax8817x_write_cmd (dev, AX88772_CMD_WRITE_NODE_ID, + 0, 0, ETH_ALEN, net->dev_addr); + +} + +static void ax88178_status(struct usbnet *dev, struct urb *urb) +{ + struct ax88172_int_data *event; + struct ax88178_data *ax178dataptr = (struct ax88178_data *)dev->priv; + int link; + + if (urb->actual_length < 8) return; - } - if ((req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC)) == NULL) { - netdev_err(dev->net, "Failed to allocate memory for control request\n"); - usb_free_urb(urb); + if (ax178dataptr->EepromData == PHY_MODE_MAC_TO_MAC_GMII) return; + + event = urb->transfer_buffer; + link = event->link & 0x01; + if (netif_carrier_ok(dev->net) != link) { + if (link) { + netif_carrier_on(dev->net); + axusbnet_defer_kevent (dev, EVENT_LINK_RESET); + } else + netif_carrier_off(dev->net); + devwarn(dev, "ax88178 - Link status is: %d", link); } +} - req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; - req->bRequest = cmd; - req->wValue = cpu_to_le16(value); - req->wIndex = cpu_to_le16(index); - req->wLength = cpu_to_le16(size); +static void ax8817x_status(struct usbnet *dev, struct urb *urb) +{ + struct ax88172_int_data *event; + int link; - usb_fill_control_urb(urb, dev->udev, - usb_sndctrlpipe(dev->udev, 0), - (void *)req, data, size, - asix_async_cmd_callback, req); + if (urb->actual_length < 8) + return; - if((status = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - netdev_err(dev->net, "Error submitting the control message: status=%d\n", - status); - kfree(req); - usb_free_urb(urb); + event = urb->transfer_buffer; + link = event->link & 0x01; + if (netif_carrier_ok(dev->net) != link) { + if (link) { + netif_carrier_on(dev->net); + axusbnet_defer_kevent (dev, EVENT_LINK_RESET ); + } else + netif_carrier_off(dev->net); + devwarn(dev, "ax8817x - Link status is: %d", link); } } -static int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +static void ax88772_status(struct usbnet *dev, struct urb *urb) { - u8 *head; - u32 header; - char *packet; - struct sk_buff *ax_skb; - u16 size; + struct ax88172_int_data *event; + struct ax88772_data *ax772_data = (struct ax88772_data *)dev->priv; + int link; + + if (urb->actual_length < 8) + return; - head = (u8 *) skb->data; - memcpy(&header, head, sizeof(header)); - le32_to_cpus(&header); - packet = head + sizeof(header); + event = urb->transfer_buffer; + link = event->link & 0x01; + + if (netif_carrier_ok(dev->net) != link) { + if (link) { + netif_carrier_on(dev->net); + ax772_data->Event = AX_SET_RX_CFG; + } else { + netif_carrier_off(dev->net); + if (ax772_data->Event == AX_NOP) { + ax772_data->Event = PHY_POWER_DOWN; + ax772_data->TickToExpire = 25; + } + } - skb_pull(skb, 4); + devwarn(dev, "ax88772 - Link status is: %d", link); + } + + if (ax772_data->Event) + queue_work (ax772_data->ax_work, &ax772_data->check_link); +} - while (skb->len > 0) { - if ((header & 0x07ff) != ((~header >> 16) & 0x07ff)) - netdev_err(dev->net, "asix_rx_fixup() Bad Header Length\n"); +static void ax88772a_status(struct usbnet *dev, struct urb *urb) +{ + struct ax88172_int_data *event; + struct ax88772a_data *ax772a_data = (struct ax88772a_data *)dev->priv; + int link; + int PowSave = (ax772a_data->EepromData >> 14); - /* get the packet length */ - size = (u16) (header & 0x000007ff); + if (urb->actual_length < 8) + return; - if ((skb->len) - ((size + 1) & 0xfffe) == 0) { - u8 alignment = (unsigned long)skb->data & 0x3; - if (alignment != 0x2) { - /* - * not 16bit aligned so use the room provided by - * the 32 bit header to align the data - * - * note we want 16bit alignment as MAC header is - * 14bytes thus ip header will be aligned on - * 32bit boundary so accessing ipheader elements - * using a cast to struct ip header wont cause - * an unaligned accesses. - */ - u8 realignment = (alignment + 2) & 0x3; - memmove(skb->data - realignment, - skb->data, - size); - skb->data -= realignment; - skb_set_tail_pointer(skb, size); - } - return 2; - } + event = urb->transfer_buffer; + link = event->link & 0x01; - if (size > dev->net->mtu + ETH_HLEN) { - netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n", - size); - return 0; - } - ax_skb = skb_clone(skb, GFP_ATOMIC); - if (ax_skb) { - u8 alignment = (unsigned long)packet & 0x3; - ax_skb->len = size; + if (netif_carrier_ok(dev->net) != link) { - if (alignment != 0x2) { - /* - * not 16bit aligned use the room provided by - * the 32 bit header to align the data - */ - u8 realignment = (alignment + 2) & 0x3; - memmove(packet - realignment, packet, size); - packet -= realignment; + if (link) { + netif_carrier_on(dev->net); + ax772a_data->Event = AX_SET_RX_CFG; + } else if ((PowSave == 0x3) || (PowSave == 0x1)) { + netif_carrier_off(dev->net); + if (ax772a_data->Event == AX_NOP) { + ax772a_data->Event = CHK_CABLE_EXIST; + ax772a_data->TickToExpire = 14; } - ax_skb->data = packet; - skb_set_tail_pointer(ax_skb, size); - usbnet_skb_return(dev, ax_skb); } else { - return 0; + netif_carrier_off(dev->net); + ax772a_data->Event = AX_NOP; } - skb_pull(skb, (size + 1) & 0xfffe); - - if (skb->len < sizeof(header)) - break; - - head = (u8 *) skb->data; - memcpy(&header, head, sizeof(header)); - le32_to_cpus(&header); - packet = head + sizeof(header); - skb_pull(skb, 4); - } - - if (skb->len < 0) { - netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d\n", - skb->len); - return 0; + devwarn(dev, "ax88772a - Link status is: %d", link); } - return 1; + + if (ax772a_data->Event) + queue_work (ax772a_data->ax_work, &ax772a_data->check_link); } -static struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, - gfp_t flags) +static int ax88772b_stop(struct usbnet *dev) { - int padlen; - int headroom = skb_headroom(skb); - int tailroom = skb_tailroom(skb); - u32 packet_len; - u32 padbytes = 0xffff0000; + u16 *medium; - padlen = ((skb->len + 4) % 512) ? 0 : 4; + medium = kmalloc (2, GFP_ATOMIC); + if (medium) { + ax8817x_read_cmd (dev, AX_CMD_READ_MEDIUM_MODE, 0, 0, 2, medium); + ax8817x_write_cmd (dev, AX_CMD_WRITE_MEDIUM_MODE, + (*medium & ~AX88772_MEDIUM_RX_ENABLE), 0, 0, NULL); - if ((!skb_cloned(skb)) && - ((headroom + tailroom) >= (4 + padlen))) { - if ((headroom < 4) || (tailroom < padlen)) { - skb->data = memmove(skb->head + 4, skb->data, skb->len); - skb_set_tail_pointer(skb, skb->len); - } - } else { - struct sk_buff *skb2; - skb2 = skb_copy_expand(skb, 4, padlen, flags); - dev_kfree_skb_any(skb); - skb = skb2; - if (!skb) - return NULL; + kfree (medium); + return 0; } + return -EINVAL; + +} - skb_push(skb, 4); - packet_len = (((skb->len - 4) ^ 0x0000ffff) << 16) + (skb->len - 4); - cpu_to_le32s(&packet_len); - skb_copy_to_linear_data(skb, &packet_len, sizeof(packet_len)); +static int ax88772b_reset(struct usbnet *dev) +{ + int ret; - if ((skb->len % 512) == 0) { - cpu_to_le32s(&padbytes); - memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes)); - skb_put(skb, sizeof(padbytes)); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + AX88772_MEDIUM_DEFAULT, 0, 0, NULL)) < 0) { + deverr(dev, "Write medium mode register: %d", ret); } - return skb; + return ret; + } -static void asix_status(struct usbnet *dev, struct urb *urb) + +static void ax88772b_status(struct usbnet *dev, struct urb *urb) { + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; struct ax88172_int_data *event; int link; if (urb->actual_length < 8) return; + if (ax772b_data->OperationMode == OPERATION_PHY_MODE) + return; + event = urb->transfer_buffer; - link = event->link & 0x01; + if (ax772b_data->PhySelect == 0 && ax772b_data->OperationMode + == OPERATION_MAC_MODE) + link = event->link & AX_INT_SPLS_LINK; + else + link = event->link & AX_INT_PPLS_LINK; + if (netif_carrier_ok(dev->net) != link) { if (link) { netif_carrier_on(dev->net); - usbnet_defer_kevent (dev, EVENT_LINK_RESET ); - } else + ax772b_data->Event = AX_SET_RX_CFG; + } else { netif_carrier_off(dev->net); - netdev_dbg(dev->net, "Link Status is: %d\n", link); + ax772b_data->time_to_chk = jiffies; + } + devwarn(dev, "ax88772b - Link status is: %d", link); } -} - -static inline int asix_set_sw_mii(struct usbnet *dev) -{ - int ret; - ret = asix_write_cmd(dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to enable software MII access\n"); - return ret; -} -static inline int asix_set_hw_mii(struct usbnet *dev) -{ - int ret; - ret = asix_write_cmd(dev, AX_CMD_SET_HW_MII, 0x0000, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to enable hardware MII access\n"); - return ret; -} + if (!link) { -static inline int asix_get_phy_addr(struct usbnet *dev) -{ - u8 buf[2]; - int ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf); - - netdev_dbg(dev->net, "asix_get_phy_addr()\n"); + int no_cable = (event->link & AX_INT_CABOFF_UNPLUG) ? 1 : 0; - if (ret < 0) { - netdev_err(dev->net, "Error reading PHYID register: %02x\n", ret); - goto out; + if (no_cable) { + if ((ax772b_data->psc & + (AX_SWRESET_IPPSL_0 | AX_SWRESET_IPPSL_1)) && + !ax772b_data->pw_enabled) { + /* + * AX88772B already entered power saving state + */ + ax772b_data->pw_enabled = 1; + } +/* Disable failing auto detach stuff for now */ +#if 0 + ax772b_data->Event = AX_CHK_AUTODETACH; +#endif + } else { + /* AX88772B resumed from power saving state */ + if (ax772b_data->pw_enabled || + (jiffies > (ax772b_data->time_to_chk + + AX88772B_WATCHDOG))) { + if (ax772b_data->pw_enabled) + ax772b_data->pw_enabled = 0; + ax772b_data->Event = PHY_POWER_UP; + ax772b_data->time_to_chk = jiffies; + } + } } - netdev_dbg(dev->net, "asix_get_phy_addr() returning 0x%04x\n", - *((__le16 *)buf)); - ret = buf[1]; -out: - return ret; + if (ax772b_data->Event) + queue_work (ax772b_data->ax_work, &ax772b_data->check_link); } -static int asix_sw_reset(struct usbnet *dev, u8 flags) +void +ax8817x_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) { - int ret; + struct usb_ctrlrequest *req; + int status; + struct urb *urb; - ret = asix_write_cmd(dev, AX_CMD_SW_RESET, flags, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to send software reset: %02x\n", ret); + if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) { + deverr(dev, "Error allocating URB in write_cmd_async!"); + return; + } - return ret; -} + if ((req = kmalloc (sizeof (struct usb_ctrlrequest), + GFP_ATOMIC)) == NULL) { + deverr(dev, "Failed to allocate memory for control request"); + usb_free_urb(urb); + return; + } -static u16 asix_read_rx_ctl(struct usbnet *dev) -{ - __le16 v; - int ret = asix_read_cmd(dev, AX_CMD_READ_RX_CTL, 0, 0, 2, &v); + req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + req->bRequest = cmd; + req->wValue = cpu_to_le16(value); + req->wIndex = cpu_to_le16(index); + req->wLength = cpu_to_le16(size); + + usb_fill_control_urb(urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void *)req, data, size, + ax8817x_async_cmd_callback, req); - if (ret < 0) { - netdev_err(dev->net, "Error reading RX_CTL register: %02x\n", ret); - goto out; + if((status = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + deverr(dev, "Error submitting the control message: status=%d", + status); + kfree(req); + usb_free_urb(urb); } - ret = le16_to_cpu(v); -out: - return ret; } -static int asix_write_rx_ctl(struct usbnet *dev, u16 mode) +static void ax8817x_set_multicast(struct net_device *net) { - int ret; + struct usbnet *dev = netdev_priv(net); + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + u8 rx_ctl = AX_RX_CTL_START | AX_RX_CTL_AB; + int mc_count; - netdev_dbg(dev->net, "asix_write_rx_ctl() - mode = 0x%04x\n", mode); - ret = asix_write_cmd(dev, AX_CMD_WRITE_RX_CTL, mode, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to write RX_CTL mode to 0x%04x: %02x\n", - mode, ret); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + mc_count = net->mc_count; +#else + mc_count = netdev_mc_count (net); +#endif - return ret; -} + if (net->flags & IFF_PROMISC) { + rx_ctl |= AX_RX_CTL_PRO; + } else if (net->flags & IFF_ALLMULTI + || mc_count > AX_MAX_MCAST) { + rx_ctl |= AX_RX_CTL_AMALL; + } else if (mc_count == 0) { + /* just broadcast and directed */ + } else { + /* We use the 20 byte dev->data + * for our 8 byte filter buffer + * to avoid allocating memory that + * is tricky to free later */ + u32 crc_bits; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + struct dev_mc_list *mc_list = net->mc_list; + int i; -static u16 asix_read_medium_status(struct usbnet *dev) -{ - __le16 v; - int ret = asix_read_cmd(dev, AX_CMD_READ_MEDIUM_STATUS, 0, 0, 2, &v); + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + + /* Build the multicast hash filter. */ + for (i = 0; i < net->mc_count; i++) { + crc_bits = + ether_crc(ETH_ALEN, + mc_list->dmi_addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + mc_list = mc_list->next; + } +#else + struct netdev_hw_addr *ha; + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + netdev_for_each_mc_addr (ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + } +#endif + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, + AX_MCAST_FILTER_SIZE, data->multi_filter); - if (ret < 0) { - netdev_err(dev->net, "Error reading Medium Status register: %02x\n", - ret); - goto out; + rx_ctl |= AX_RX_CTL_AM; } - ret = le16_to_cpu(v); -out: - return ret; + + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); } -static int asix_write_medium_mode(struct usbnet *dev, u16 mode) +static void ax88178_set_multicast(struct net_device *net) { - int ret; + struct usbnet *dev = netdev_priv(net); + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + u16 rx_ctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_MFB); + int mc_count; - netdev_dbg(dev->net, "asix_write_medium_mode() - mode = 0x%04x\n", mode); - ret = asix_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, mode, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to write Medium Mode mode to 0x%04x: %02x\n", - mode, ret); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + mc_count = net->mc_count; +#else + mc_count = netdev_mc_count (net); +#endif - return ret; -} + if (net->flags & IFF_PROMISC) { + rx_ctl |= AX_RX_CTL_PRO; + } else if (net->flags & IFF_ALLMULTI + || mc_count > AX_MAX_MCAST) { + rx_ctl |= AX_RX_CTL_AMALL; + } else if (mc_count == 0) { + /* just broadcast and directed */ + } else { + /* We use the 20 byte dev->data + * for our 8 byte filter buffer + * to avoid allocating memory that + * is tricky to free later */ + u32 crc_bits; -static int asix_write_gpio(struct usbnet *dev, u16 value, int sleep) -{ - int ret; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + struct dev_mc_list *mc_list = net->mc_list; + int i; - netdev_dbg(dev->net, "asix_write_gpio() - value = 0x%04x\n", value); - ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, value, 0, 0, NULL); - if (ret < 0) - netdev_err(dev->net, "Failed to write GPIO value 0x%04x: %02x\n", - value, ret); + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + + /* Build the multicast hash filter. */ + for (i = 0; i < net->mc_count; i++) { + crc_bits = + ether_crc(ETH_ALEN, + mc_list->dmi_addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + mc_list = mc_list->next; + } +#else + struct netdev_hw_addr *ha; + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + netdev_for_each_mc_addr (ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + } +#endif + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, + AX_MCAST_FILTER_SIZE, data->multi_filter); - if (sleep) - msleep(sleep); + rx_ctl |= AX_RX_CTL_AM; + } - return ret; + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); } -/* - * AX88772 & AX88178 have a 16-bit RX_CTL value - */ -static void asix_set_multicast(struct net_device *net) +static void ax88772b_set_multicast(struct net_device *net) { struct usbnet *dev = netdev_priv(net); - struct asix_data *data = (struct asix_data *)&dev->data; - u16 rx_ctl = AX_DEFAULT_RX_CTL; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + u16 rx_ctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_HEADER_DEFAULT); + int mc_count; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + mc_count = net->mc_count; +#else + mc_count = netdev_mc_count (net); +#endif if (net->flags & IFF_PROMISC) { rx_ctl |= AX_RX_CTL_PRO; - } else if (net->flags & IFF_ALLMULTI || - netdev_mc_count(net) > AX_MAX_MCAST) { + } else if (net->flags & IFF_ALLMULTI + || mc_count > AX_MAX_MCAST) { rx_ctl |= AX_RX_CTL_AMALL; - } else if (netdev_mc_empty(net)) { + } else if (mc_count == 0) { /* just broadcast and directed */ } else { /* We use the 20 byte dev->data * for our 8 byte filter buffer * to avoid allocating memory that * is tricky to free later */ - struct netdev_hw_addr *ha; u32 crc_bits; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) + struct dev_mc_list *mc_list = net->mc_list; + int i; + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); /* Build the multicast hash filter. */ - netdev_for_each_mc_addr(ha, net) { - crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; + for (i = 0; i < net->mc_count; i++) { + crc_bits = + ether_crc(ETH_ALEN, + mc_list->dmi_addr) >> 26; data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); + mc_list = mc_list->next; } - - asix_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, +#else + struct netdev_hw_addr *ha; + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + netdev_for_each_mc_addr (ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + } +#endif + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, AX_MCAST_FILTER_SIZE, data->multi_filter); rx_ctl |= AX_RX_CTL_AM; } - asix_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); +} + +static int ax8817x_mdio_read(struct net_device *netdev, int phy_id, int loc) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 *res; + u16 ret; + + res = kmalloc (2, GFP_ATOMIC); + if (!res) + return 0; + + ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, NULL); + ax8817x_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, res); + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, NULL); + + ret = *res & 0xffff; + kfree (res); + + return ret; } -static int asix_mdio_read(struct net_device *netdev, int phy_id, int loc) +static int +ax8817x_swmii_mdio_read(struct net_device *netdev, int phy_id, int loc) { struct usbnet *dev = netdev_priv(netdev); - __le16 res; + u16 *res; + u16 ret; + + res = kmalloc (2, GFP_ATOMIC); + if (!res) + return 0; + + ax8817x_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, + (__u16)loc, 2, res); - mutex_lock(&dev->phy_mutex); - asix_set_sw_mii(dev); - asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, - (__u16)loc, 2, &res); - asix_set_hw_mii(dev); - mutex_unlock(&dev->phy_mutex); + ret = *res & 0xffff; + kfree (res); - netdev_dbg(dev->net, "asix_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", - phy_id, loc, le16_to_cpu(res)); + return ret; +} - return le16_to_cpu(res); +/* same as above, but converts resulting value to cpu byte order */ +static int ax8817x_mdio_read_le(struct net_device *netdev, int phy_id, int loc) +{ + return le16_to_cpu(ax8817x_mdio_read(netdev,phy_id, loc)); +} + +static int +ax8817x_swmii_mdio_read_le(struct net_device *netdev, int phy_id, int loc) +{ + return le16_to_cpu(ax8817x_swmii_mdio_read(netdev,phy_id, loc)); } static void -asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val) +ax8817x_mdio_write(struct net_device *netdev, int phy_id, int loc, int val) { struct usbnet *dev = netdev_priv(netdev); - __le16 res = cpu_to_le16(val); + u16 *res; + + res = kmalloc (2, GFP_ATOMIC); + if (!res) + return; + *res = val; + + ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, NULL); + ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, + (__u16)loc, 2, res); + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, NULL); - netdev_dbg(dev->net, "asix_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", - phy_id, loc, val); - mutex_lock(&dev->phy_mutex); - asix_set_sw_mii(dev); - asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, &res); - asix_set_hw_mii(dev); - mutex_unlock(&dev->phy_mutex); + kfree (res); } -/* Get the PHY Identifier from the PHYSID1 & PHYSID2 MII registers */ -static u32 asix_get_phyid(struct usbnet *dev) +static void ax8817x_swmii_mdio_write(struct net_device *netdev, + int phy_id, int loc, int val) { - int phy_reg; - u32 phy_id; + struct usbnet *dev = netdev_priv(netdev); + u16 *res; - phy_reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_PHYSID1); - if (phy_reg < 0) - return 0; + res = kmalloc (2, GFP_ATOMIC); + if (!res) + return; + *res = val; - phy_id = (phy_reg & 0xffff) << 16; + ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, + (__u16)loc, 2, res); - phy_reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_PHYSID2); - if (phy_reg < 0) - return 0; + kfree (res); +} + +static void +ax88772b_mdio_write(struct net_device *netdev, int phy_id, int loc, int val) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 *res; + + res = kmalloc (2, GFP_ATOMIC); + if (!res) + return; + *res = val; + + ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, NULL); + ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, + (__u16)loc, 2, res); + + if (loc == MII_ADVERTISE) { + *res = cpu_to_le16(BMCR_ANENABLE | BMCR_ANRESTART); + ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, + (__u16)MII_BMCR, 2, res); + } + + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, NULL); + + kfree (res); +} + +/* same as above, but converts new value to le16 byte order before writing */ +static void +ax8817x_mdio_write_le(struct net_device *netdev, int phy_id, int loc, int val) +{ + ax8817x_mdio_write( netdev, phy_id, loc, cpu_to_le16(val) ); +} + +static void ax8817x_swmii_mdio_write_le(struct net_device *netdev, + int phy_id, int loc, int val) +{ + ax8817x_swmii_mdio_write( netdev, phy_id, loc, cpu_to_le16(val) ); +} + +static void +ax88772b_mdio_write_le(struct net_device *netdev, int phy_id, int loc, int val) +{ + ax88772b_mdio_write( netdev, phy_id, loc, cpu_to_le16(val) ); +} + +static int ax88772_suspend (struct usb_interface *intf, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,10) + pm_message_t message) +#else + u32 message) +#endif +{ + struct usbnet *dev = usb_get_intfdata(intf); + u16 *medium; + + medium = kmalloc (2, GFP_ATOMIC); + if (!medium) + return axusbnet_suspend (intf, message); + + ax8817x_read_cmd (dev, AX_CMD_READ_MEDIUM_MODE, 0, 0, 2, medium); + ax8817x_write_cmd (dev, AX_CMD_WRITE_MEDIUM_MODE, + (*medium & ~AX88772_MEDIUM_RX_ENABLE), 0, 0, NULL); + + kfree (medium); + return axusbnet_suspend (intf, message); +} + +static int ax88772b_suspend (struct usb_interface *intf, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,10) + pm_message_t message) +#else + u32 message) +#endif +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + u16 *tmp16; + u8 *opt; + + tmp16 = kmalloc (2, GFP_ATOMIC); + if (!tmp16) + return axusbnet_suspend (intf, message); + opt = (u8 *)tmp16; + + ax8817x_read_cmd (dev, AX_CMD_READ_MEDIUM_MODE, 0, 0, 2, tmp16); + ax8817x_write_cmd (dev, AX_CMD_WRITE_MEDIUM_MODE, + (*tmp16 & ~AX88772_MEDIUM_RX_ENABLE), 0, 0, NULL); + + ax8817x_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, opt); + if (!(*opt & AX_MONITOR_LINK) && !(*opt & AX_MONITOR_MAGIC)) { + ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | AX_SWRESET_IPPD, 0, 0, NULL); + } else { + + if (ax772b_data->psc & AX_SWRESET_WOLLP) { + *tmp16 = ax8817x_mdio_read_le (dev->net, + dev->mii.phy_id, MII_BMCR); + ax8817x_mdio_write_le (dev->net, dev->mii.phy_id, + MII_BMCR, *tmp16 | BMCR_ANENABLE); + + ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | ax772b_data->psc, 0, 0, NULL); + } + + if (ax772b_data->psc & + (AX_SWRESET_IPPSL_0 | AX_SWRESET_IPPSL_1)) { + *opt |= AX_MONITOR_LINK; + ax8817x_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, + *opt, 0, 0, NULL); + } + } + + kfree (tmp16); + return axusbnet_suspend (intf, message); +} - phy_id |= (phy_reg & 0xffff); +static int ax88772_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + netif_carrier_off (dev->net); + + return axusbnet_resume (intf); +} + +static int ax88772b_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + + if (ax772b_data->psc & AX_SWRESET_WOLLP) { + ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | (ax772b_data->psc & 0x7FFF), + 0, 0, NULL); + } + + if (ax772b_data->psc & (AX_SWRESET_IPPSL_0 | AX_SWRESET_IPPSL_1)) { + ax88772a_phy_powerup (dev); + } + + netif_carrier_off (dev->net); + + return axusbnet_resume (intf); +} + +static int ax88172_link_reset(struct usbnet *dev) +{ + u16 lpa; + u16 adv; + u16 res; + u8 mode; + + mode = AX_MEDIUM_TX_ABORT_ALLOW | AX_MEDIUM_FLOW_CONTROL_EN; + lpa = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, MII_LPA); + adv = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, MII_ADVERTISE); + res = mii_nway_result(lpa|adv); + if (res & LPA_DUPLEX) + mode |= AX_MEDIUM_FULL_DUPLEX; + ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, mode, 0, 0, NULL); - return phy_id; + return 0; } static void -asix_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +ax8817x_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) { struct usbnet *dev = netdev_priv(net); - u8 opt; + u8 *opt; + + wolinfo->supported = 0; + wolinfo->wolopts = 0; - if (asix_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, &opt) < 0) { - wolinfo->supported = 0; - wolinfo->wolopts = 0; + opt = kmalloc (1, GFP_KERNEL); + if (!opt) return; - } + + if (ax8817x_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, opt) < 0) + return; + wolinfo->supported = WAKE_PHY | WAKE_MAGIC; - wolinfo->wolopts = 0; - if (opt & AX_MONITOR_MODE) { - if (opt & AX_MONITOR_LINK) - wolinfo->wolopts |= WAKE_PHY; - if (opt & AX_MONITOR_MAGIC) - wolinfo->wolopts |= WAKE_MAGIC; - } + + if (*opt & AX_MONITOR_LINK) + wolinfo->wolopts |= WAKE_PHY; + if (*opt & AX_MONITOR_MAGIC) + wolinfo->wolopts |= WAKE_MAGIC; + + kfree (opt); } static int -asix_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +ax8817x_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) { struct usbnet *dev = netdev_priv(net); - u8 opt = 0; + u8 *opt; + + opt = kmalloc (1, GFP_KERNEL); + if (!opt) + return -ENOMEM; + *opt = 0; if (wolinfo->wolopts & WAKE_PHY) - opt |= AX_MONITOR_LINK; + *opt |= AX_MONITOR_LINK; if (wolinfo->wolopts & WAKE_MAGIC) - opt |= AX_MONITOR_MAGIC; - if (opt != 0) - opt |= AX_MONITOR_MODE; + *opt |= AX_MONITOR_MAGIC; - if (asix_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, - opt, 0, 0, NULL) < 0) - return -EINVAL; + ax8817x_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, *opt, 0, 0, NULL); + kfree (opt); return 0; } -static int asix_get_eeprom_len(struct net_device *net) +static int ax8817x_get_eeprom_len(struct net_device *net) { - struct usbnet *dev = netdev_priv(net); - struct asix_data *data = (struct asix_data *)&dev->data; - - return data->eeprom_len; + return AX_EEPROM_LEN; } -static int asix_get_eeprom(struct net_device *net, +static int ax8817x_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data) { struct usbnet *dev = netdev_priv(net); - __le16 *ebuf = (__le16 *)data; + u16 *ebuf = (u16 *)data; int i; /* Crude hack to ensure that we don't overwrite memory @@ -729,722 +906,2765 @@ static int asix_get_eeprom(struct net_device *net, /* ax8817x returns 2 bytes from eeprom on read */ for (i=0; i < eeprom->len / 2; i++) { - if (asix_read_cmd(dev, AX_CMD_READ_EEPROM, + if (ax8817x_read_cmd(dev, AX_CMD_READ_EEPROM, eeprom->offset + i, 0, 2, &ebuf[i]) < 0) return -EINVAL; } return 0; } -static void asix_get_drvinfo (struct net_device *net, +static void ax8817x_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info) { - struct usbnet *dev = netdev_priv(net); - struct asix_data *data = (struct asix_data *)&dev->data; - /* Inherit standard device info */ - usbnet_get_drvinfo(net, info); - strncpy (info->driver, driver_name, sizeof info->driver); - strncpy (info->version, DRIVER_VERSION, sizeof info->version); - info->eedump_len = data->eeprom_len; + axusbnet_get_drvinfo(net, info); + info->eedump_len = 0x3e; } -static u32 asix_get_link(struct net_device *net) +static int ax8817x_get_settings(struct net_device *net, struct ethtool_cmd *cmd) { struct usbnet *dev = netdev_priv(net); + return mii_ethtool_gset(&dev->mii,cmd); +} - return mii_link_ok(&dev->mii); +static int ax8817x_set_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + return mii_ethtool_sset(&dev->mii,cmd); } -static int asix_ioctl (struct net_device *net, struct ifreq *rq, int cmd) +/* We need to override some ethtool_ops so we require our + own structure so we don't interfere with other usbnet + devices that may be connected at the same time. */ +static struct ethtool_ops ax8817x_ethtool_ops = { + .get_drvinfo = ax8817x_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_msglevel = axusbnet_get_msglevel, + .set_msglevel = axusbnet_set_msglevel, + .get_wol = ax8817x_get_wol, + .set_wol = ax8817x_set_wol, + .get_eeprom_len = ax8817x_get_eeprom_len, + .get_eeprom = ax8817x_get_eeprom, + .get_settings = ax8817x_get_settings, + .set_settings = ax8817x_set_settings, +}; + +static int ax8817x_ioctl (struct net_device *net, struct ifreq *rq, int cmd) { struct usbnet *dev = netdev_priv(net); return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL); } -static int asix_set_mac_address(struct net_device *net, void *p) +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,29) +static const struct net_device_ops ax88x72_netdev_ops = { + .ndo_open = axusbnet_open, + .ndo_stop = axusbnet_stop, + .ndo_start_xmit = axusbnet_start_xmit, + .ndo_tx_timeout = axusbnet_tx_timeout, + .ndo_change_mtu = axusbnet_change_mtu, + .ndo_get_stats = axusbnet_get_stats, + .ndo_do_ioctl = ax8817x_ioctl, + .ndo_set_mac_address = ax8817x_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,2,0) + .ndo_set_multicast_list = ax8817x_set_multicast, +#else + .ndo_set_rx_mode = ax8817x_set_multicast, +#endif +}; +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,29) +static const struct net_device_ops ax88178_netdev_ops = { + .ndo_open = axusbnet_open, + .ndo_stop = axusbnet_stop, + .ndo_start_xmit = axusbnet_start_xmit, + .ndo_tx_timeout = axusbnet_tx_timeout, + .ndo_change_mtu = axusbnet_change_mtu, + .ndo_get_stats = axusbnet_get_stats, + .ndo_do_ioctl = ax8817x_ioctl, + .ndo_set_mac_address = ax8817x_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,2,0) + .ndo_set_multicast_list = ax88178_set_multicast, +#else + .ndo_set_rx_mode = ax88178_set_multicast, +#endif +}; +#endif + +static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf) { - struct usbnet *dev = netdev_priv(net); - struct asix_data *data = (struct asix_data *)&dev->data; - struct sockaddr *addr = p; + int ret = 0; + void *buf; + int i; + unsigned long gpio_bits = dev->driver_info->data; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + + axusbnet_get_endpoints(dev,intf); + + buf = kmalloc(ETH_ALEN, GFP_KERNEL); + if(!buf) { + ret = -ENOMEM; + goto out1; + } + + /* Toggle the GPIOs in a manufacturer/model specific way */ + for (i = 2; i >= 0; i--) { + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + (gpio_bits >> (i * 8)) & 0xff, 0, 0, + NULL)) < 0) + goto out2; + msleep(5); + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + 0x80, 0, 0, NULL)) < 0) { + deverr(dev, "send AX_CMD_WRITE_RX_CTL failed: %d", ret); + goto out2; + } + + /* Get the MAC address */ + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_NODE_ID, + 0, 0, 6, buf)) < 0) { + deverr(dev, "read AX_CMD_READ_NODE_ID failed: %d", ret); + goto out2; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, + 0, 0, 2, buf)) < 0) { + deverr(dev, "error on read AX_CMD_READ_PHY_ID: %02x", ret); + goto out2; + } else if (ret < 2) { + /* this should always return 2 bytes */ + deverr(dev, "Read PHYID returned less than 2 bytes: ret=%02x", + ret); + ret = -EIO; + goto out2; + } - if (netif_running(net)) - return -EBUSY; - if (!is_valid_ether_addr(addr->sa_data)) - return -EADDRNOTAVAIL; + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read_le; + dev->mii.mdio_write = ax8817x_mdio_write_le; + dev->mii.phy_id_mask = 0x3f; + dev->mii.reg_num_mask = 0x1f; + dev->mii.phy_id = *((u8 *)buf + 1); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + dev->net->do_ioctl = ax8817x_ioctl; + dev->net->set_multicast_list = ax8817x_set_multicast; + dev->net->set_mac_address = ax8817x_set_mac_addr; +#else + dev->net->netdev_ops = &ax88x72_netdev_ops; +#endif - memcpy(net->dev_addr, addr->sa_data, ETH_ALEN); + dev->net->ethtool_ops = &ax8817x_ethtool_ops; + + /* Register suspend and resume functions */ + data->suspend = axusbnet_suspend; + data->resume = axusbnet_resume; + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); + mii_nway_restart(&dev->mii); - /* We use the 20 byte dev->data - * for our 6 byte mac buffer - * to avoid allocating memory that - * is tricky to free later */ - memcpy(data->mac_addr, addr->sa_data, ETH_ALEN); - asix_write_cmd_async(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, - data->mac_addr); + printk (version); return 0; +out2: + kfree(buf); +out1: + return ret; } -/* We need to override some ethtool_ops so we require our - own structure so we don't interfere with other usbnet - devices that may be connected at the same time. */ -static const struct ethtool_ops ax88172_ethtool_ops = { - .get_drvinfo = asix_get_drvinfo, - .get_link = asix_get_link, - .get_msglevel = usbnet_get_msglevel, - .set_msglevel = usbnet_set_msglevel, - .get_wol = asix_get_wol, - .set_wol = asix_set_wol, - .get_eeprom_len = asix_get_eeprom_len, - .get_eeprom = asix_get_eeprom, - .get_settings = usbnet_get_settings, - .set_settings = usbnet_set_settings, - .nway_reset = usbnet_nway_reset, +static struct ethtool_ops ax88772_ethtool_ops = { + .get_drvinfo = ax8817x_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_msglevel = axusbnet_get_msglevel, + .set_msglevel = axusbnet_set_msglevel, + .get_wol = ax8817x_get_wol, + .set_wol = ax8817x_set_wol, + .get_eeprom_len = ax8817x_get_eeprom_len, + .get_eeprom = ax8817x_get_eeprom, + .get_settings = ax8817x_get_settings, + .set_settings = ax8817x_set_settings, }; -static void ax88172_set_multicast(struct net_device *net) +static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf) { - struct usbnet *dev = netdev_priv(net); - struct asix_data *data = (struct asix_data *)&dev->data; - u8 rx_ctl = 0x8c; + int ret; + void *buf; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + struct ax88772_data *ax772_data = NULL; - if (net->flags & IFF_PROMISC) { - rx_ctl |= 0x01; - } else if (net->flags & IFF_ALLMULTI || - netdev_mc_count(net) > AX_MAX_MCAST) { - rx_ctl |= 0x02; - } else if (netdev_mc_empty(net)) { - /* just broadcast and directed */ - } else { - /* We use the 20 byte dev->data - * for our 8 byte filter buffer - * to avoid allocating memory that - * is tricky to free later */ - struct netdev_hw_addr *ha; - u32 crc_bits; + axusbnet_get_endpoints(dev,intf); - memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + buf = kmalloc(6, GFP_KERNEL); + if(!buf) { + deverr(dev, "Cannot allocate memory for buffer"); + ret = -ENOMEM; + goto out1; + } - /* Build the multicast hash filter. */ - netdev_for_each_mc_addr(ha, net) { - crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; - data->multi_filter[crc_bits >> 3] |= - 1 << (crc_bits & 7); + ax772_data = kmalloc (sizeof(*ax772_data), GFP_KERNEL); + if (!ax772_data) { + deverr(dev, "Cannot allocate memory for AX88772 data"); + kfree (buf); + return -ENOMEM; + } + memset (ax772_data, 0, sizeof(*ax772_data)); + dev->priv = ax772_data; + + ax772_data->ax_work = create_singlethread_workqueue ("ax88772"); + if (!ax772_data->ax_work) { + kfree (ax772_data); + kfree (buf); + return -ENOMEM; + } + + ax772_data->dev = dev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK (&ax772_data->check_link, ax88772_link_reset, dev); +#else + INIT_WORK (&ax772_data->check_link, ax88772_link_reset); +#endif + + /* reload eeprom data */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + 0x00B0, 0, 0, NULL)) < 0) + goto out2; + + msleep(5); + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read_le; + dev->mii.mdio_write = ax8817x_mdio_write_le; + dev->mii.phy_id_mask = 0xff; + dev->mii.reg_num_mask = 0xff; + + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, + 0, 0, 2, buf)) < 0) { + deverr(dev, "Error reading PHY ID: %02x", ret); + goto out2; + } else if (ret < 2) { + /* this should always return 2 bytes */ + deverr(dev, "Read PHYID returned less than 2 bytes: ret=%02x", + ret); + ret = -EIO; + goto out2; + } + dev->mii.phy_id = *((u8 *)buf + 1); + + if (dev->mii.phy_id == 0x10) + { + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + 0x0001, 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY #1 failed: %d", ret); + goto out2; } - asix_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, - AX_MCAST_FILTER_SIZE, data->multi_filter); + if ((ret = ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, + 0, 0, NULL)) < 0) { + deverr(dev, "Failed to power down PHY: %d", ret); + goto out2; + } + + msleep(150); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_CLEAR, + 0, 0, NULL)) < 0) { + deverr(dev, + "Failed to perform software reset: %d", ret); + goto out2; + } + + msleep(150); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | AX_SWRESET_PRL, + 0, 0, NULL)) < 0) { + deverr(dev, + "Failed to set PHY reset control: %d", ret); + goto out2; + } + } + else + { + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + 0x0000, 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY #1 failed: %d", ret); + goto out2; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD | AX_SWRESET_PRL, + 0, 0, NULL)) < 0) { + deverr(dev, + "Failed to power down internal PHY: %d", ret); + goto out2; + } + } + + msleep(150); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + 0x0000, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to reset RX_CTL: %d", ret); + goto out2; + } + + /* Get the MAC address */ + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd(dev, AX88772_CMD_READ_NODE_ID, + 0, 0, ETH_ALEN, buf)) < 0) { + deverr(dev, "Failed to read MAC address: %d", ret); + goto out2; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Enabling software MII failed: %d", ret); + goto out2; + } + + if (dev->mii.phy_id == 0x10) + { + if ((ret = ax8817x_mdio_read_le(dev->net, + dev->mii.phy_id, 2)) != 0x003b) { + deverr(dev, "Read PHY register 2 must be 0x3b00: %d", + ret); + goto out2; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_PRL, + 0, 0, NULL)) < 0) { + deverr(dev, "Set external PHY reset pin level: %d", ret); + goto out2; + } + msleep(150); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | AX_SWRESET_PRL, + 0, 0, NULL)) < 0) { + deverr(dev, + "Set Internal/External PHY reset control: %d", + ret); + goto out2; + } + msleep(150); + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + dev->net->do_ioctl = ax8817x_ioctl; + dev->net->set_multicast_list = ax8817x_set_multicast; + dev->net->set_mac_address = ax8817x_set_mac_addr; +#else + dev->net->netdev_ops = &ax88x72_netdev_ops; +#endif + + dev->net->ethtool_ops = &ax88772_ethtool_ops; + + /* Register suspend and resume functions */ + data->suspend = ax88772_suspend; + data->resume = ax88772_resume; + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA); - rx_ctl |= 0x10; + mii_nway_restart(&dev->mii); + ax772_data->autoneg_start = jiffies; + ax772_data->Event = WAIT_AUTONEG_COMPLETE; + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Write medium mode register: %d", ret); + goto out2; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT << 8, + AX88772_IPG2_DEFAULT, 0, NULL)) < 0) { + deverr(dev, "Write IPG,IPG1,IPG2 failed: %d", ret); + goto out2; + } + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to set hardware MII: %02x", ret); + goto out2; + } + + /* Set RX_CTL to default values with 2k buffer, and enable cactus */ + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, 0x0088, 0, 0, + NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto out2; + } + + /* Asix framing packs multiple eth frames into a 2K usb bulk transfer */ + if (dev->driver_info->flags & FLAG_FRAMING_AX) { + /* hard_mtu is still the default - the device does not support + jumbo eth frames */ + dev->rx_urb_size = 2048; } - asix_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); + kfree (buf); + printk (version); + return 0; + +out2: + destroy_workqueue (ax772_data->ax_work); + kfree (ax772_data); + kfree(buf); +out1: + return ret; } -static int ax88172_link_reset(struct usbnet *dev) +static void ax88772_unbind(struct usbnet *dev, struct usb_interface *intf) { - u8 mode; - struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; + struct ax88772_data *ax772_data = (struct ax88772_data *)dev->priv; - mii_check_media(&dev->mii, 1, 1); - mii_ethtool_gset(&dev->mii, &ecmd); - mode = AX88172_MEDIUM_DEFAULT; + if (ax772_data) { - if (ecmd.duplex != DUPLEX_FULL) - mode |= ~AX88172_MEDIUM_FD; + flush_workqueue (ax772_data->ax_work); + destroy_workqueue (ax772_data->ax_work); - netdev_dbg(dev->net, "ax88172_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n", - ethtool_cmd_speed(&ecmd), ecmd.duplex, mode); + /* stop MAC operation */ + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL); - asix_write_medium_mode(dev, mode); + /* Power down PHY */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, 0, 0, NULL); - return 0; + kfree (ax772_data); + } } -static const struct net_device_ops ax88172_netdev_ops = { - .ndo_open = usbnet_open, - .ndo_stop = usbnet_stop, - .ndo_start_xmit = usbnet_start_xmit, - .ndo_tx_timeout = usbnet_tx_timeout, - .ndo_change_mtu = usbnet_change_mtu, - .ndo_set_mac_address = eth_mac_addr, - .ndo_validate_addr = eth_validate_addr, - .ndo_do_ioctl = asix_ioctl, - .ndo_set_multicast_list = ax88172_set_multicast, -}; +static int ax88772a_phy_powerup (struct usbnet *dev) +{ + int ret; + /* set the embedded Ethernet PHY in power-down state */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD | AX_SWRESET_IPRL, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to power down PHY: %d", ret); + return ret; + } + + msleep(10); + + + /* set the embedded Ethernet PHY in power-up state */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to reset PHY: %d", ret); + return ret; + } + + msleep(600); + + /* set the embedded Ethernet PHY in reset state */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_CLEAR, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to power up PHY: %d", ret); + return ret; + } + + /* set the embedded Ethernet PHY in power-up state */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to reset PHY: %d", ret); + return ret; + } + + return 0; +} -static int ax88172_bind(struct usbnet *dev, struct usb_interface *intf) +static int ax88772a_bind(struct usbnet *dev, struct usb_interface *intf) { - int ret = 0; - u8 buf[ETH_ALEN]; - int i; - unsigned long gpio_bits = dev->driver_info->data; - struct asix_data *data = (struct asix_data *)&dev->data; + int ret = -EIO; + void *buf; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + struct ax88772a_data *ax772a_data = NULL; - data->eeprom_len = AX88172_EEPROM_LEN; + axusbnet_get_endpoints(dev,intf); - usbnet_get_endpoints(dev,intf); + buf = kmalloc(6, GFP_KERNEL); + if(!buf) { + deverr(dev, "Cannot allocate memory for buffer"); + ret = -ENOMEM; + goto out1; + } - /* Toggle the GPIOs in a manufacturer/model specific way */ - for (i = 2; i >= 0; i--) { - if ((ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, - (gpio_bits >> (i * 8)) & 0xff, 0, 0, - NULL)) < 0) - goto out; - msleep(5); + ax772a_data = kmalloc (sizeof(*ax772a_data), GFP_KERNEL); + if (!ax772a_data) { + deverr(dev, "Cannot allocate memory for AX88772A data"); + kfree (buf); + return -ENOMEM; + } + memset (ax772a_data, 0, sizeof(*ax772a_data)); + dev->priv = ax772a_data; + + ax772a_data->ax_work = create_singlethread_workqueue ("ax88772a"); + if (!ax772a_data->ax_work) { + kfree (ax772a_data); + kfree (buf); + return -ENOMEM; + } + + ax772a_data->dev = dev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK (&ax772a_data->check_link, ax88772a_link_reset, dev); +#else + INIT_WORK (&ax772a_data->check_link, ax88772a_link_reset); +#endif + + /* Get the EEPROM data*/ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_EEPROM, + 0x0017, 0, 2, (void *)&ax772a_data->EepromData)) < 0) { + deverr(dev, "read SROM address 17h failed: %d", ret); + goto out2; + } + le16_to_cpus (&ax772a_data->EepromData); + /* End of get EEPROM data */ + + /* reload eeprom data */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_RSE, 0, 0, NULL)) < 0) + goto out2; + + msleep(5); + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read_le; + dev->mii.mdio_write = ax8817x_mdio_write_le; + dev->mii.phy_id_mask = 0xff; + dev->mii.reg_num_mask = 0xff; + + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, + 0, 0, 2, buf)) < 0) { + deverr(dev, "Error reading PHY ID: %02x", ret); + goto out2; + } else if (ret < 2) { + /* this should always return 2 bytes */ + deverr(dev, "Read PHYID returned less than 2 bytes: ret=%02x", + ret); + goto out2; + } + dev->mii.phy_id = *((u8 *)buf + 1); + + if(dev->mii.phy_id != 0x10) { + deverr(dev, "Got wrong PHY ID: %02x", dev->mii.phy_id); + goto out2; + } + + /* select the embedded 10/100 Ethernet PHY */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + AX_PHYSEL_SSEN | AX_PHYSEL_PSEL | AX_PHYSEL_SSMII, + 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY #1 failed: %d", ret); + goto out2; } - if ((ret = asix_write_rx_ctl(dev, 0x80)) < 0) - goto out; + if ((ret = ax88772a_phy_powerup (dev)) < 0) + goto out2; + + /* stop MAC operation */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto out2; + } /* Get the MAC address */ - if ((ret = asix_read_cmd(dev, AX88172_CMD_READ_NODE_ID, + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd(dev, AX88772_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf)) < 0) { - dbg("read AX_CMD_READ_NODE_ID failed: %d", ret); - goto out; + deverr(dev, "Failed to read MAC address: %d", ret); + goto out2; } memcpy(dev->net->dev_addr, buf, ETH_ALEN); - /* Initialize MII structure */ - dev->mii.dev = dev->net; - dev->mii.mdio_read = asix_mdio_read; - dev->mii.mdio_write = asix_mdio_write; - dev->mii.phy_id_mask = 0x3f; - dev->mii.reg_num_mask = 0x1f; - dev->mii.phy_id = asix_get_phy_addr(dev); + /* make sure the driver can enable sw mii operation */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Enabling software MII failed: %d", ret); + goto out2; + } - dev->net->netdev_ops = &ax88172_netdev_ops; - dev->net->ethtool_ops = &ax88172_ethtool_ops; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + dev->net->do_ioctl = ax8817x_ioctl; + dev->net->set_multicast_list = ax8817x_set_multicast; + dev->net->set_mac_address = ax8817x_set_mac_addr; +#else + dev->net->netdev_ops = &ax88x72_netdev_ops; +#endif + + dev->net->ethtool_ops = &ax88772_ethtool_ops; + + /* Register suspend and resume functions */ + data->suspend = ax88772_suspend; + data->resume = ax88772_resume; + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, - ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); mii_nway_restart(&dev->mii); + ax772a_data->autoneg_start = jiffies; + ax772a_data->Event = WAIT_AUTONEG_COMPLETE; - return 0; + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Write medium mode register: %d", ret); + goto out2; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772A_IPG0_DEFAULT | AX88772A_IPG1_DEFAULT << 8, + AX88772A_IPG2_DEFAULT, 0, NULL)) < 0) { + deverr(dev, "Write IPG,IPG1,IPG2 failed: %d", ret); + goto out2; + } + + /* Set RX_CTL to default values with 2k buffer, and enable cactus */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + (AX_RX_CTL_START | AX_RX_CTL_AB), + 0, 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto out2; + } + + /* Asix framing packs multiple eth frames into a 2K usb bulk transfer */ + if (dev->driver_info->flags & FLAG_FRAMING_AX) { + /* hard_mtu is still the default - the device does not support + jumbo eth frames */ + dev->rx_urb_size = 2048; + } + + kfree (buf); -out: + printk (version); + + return ret; +out2: + destroy_workqueue (ax772a_data->ax_work); + kfree (ax772a_data); + kfree (buf); +out1: return ret; } -static const struct ethtool_ops ax88772_ethtool_ops = { - .get_drvinfo = asix_get_drvinfo, - .get_link = asix_get_link, - .get_msglevel = usbnet_get_msglevel, - .set_msglevel = usbnet_set_msglevel, - .get_wol = asix_get_wol, - .set_wol = asix_set_wol, - .get_eeprom_len = asix_get_eeprom_len, - .get_eeprom = asix_get_eeprom, - .get_settings = usbnet_get_settings, - .set_settings = usbnet_set_settings, - .nway_reset = usbnet_nway_reset, -}; - -static int ax88772_link_reset(struct usbnet *dev) +static void ax88772a_unbind(struct usbnet *dev, struct usb_interface *intf) { - u16 mode; - struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; + struct ax88772a_data *ax772a_data = (struct ax88772a_data *)dev->priv; + + if (ax772a_data) { + + flush_workqueue (ax772a_data->ax_work); + destroy_workqueue (ax772a_data->ax_work); + + /* stop MAC operation */ + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL); + + /* Power down PHY */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, 0, 0, NULL); + + kfree (ax772a_data); + } +} - mii_check_media(&dev->mii, 1, 1); - mii_ethtool_gset(&dev->mii, &ecmd); - mode = AX88772_MEDIUM_DEFAULT; +static int ax88772b_set_csums(struct usbnet *dev) +{ + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + u16 checksum; - if (ethtool_cmd_speed(&ecmd) != SPEED_100) - mode &= ~AX_MEDIUM_PS; + if (ax772b_data->checksum & AX_RX_CHECKSUM) + checksum = AX_RXCOE_DEF_CSUM; + else + checksum = 0; - if (ecmd.duplex != DUPLEX_FULL) - mode &= ~AX_MEDIUM_FD; + ax8817x_write_cmd (dev, AX_CMD_WRITE_RXCOE_CTL, + checksum, 0, 0, NULL); - netdev_dbg(dev->net, "ax88772_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n", - ethtool_cmd_speed(&ecmd), ecmd.duplex, mode); + if (ax772b_data->checksum & AX_TX_CHECKSUM) + checksum = AX_TXCOE_DEF_CSUM; + else + checksum = 0; - asix_write_medium_mode(dev, mode); + ax8817x_write_cmd (dev, AX_CMD_WRITE_TXCOE_CTL, + checksum, 0, 0, NULL); return 0; } +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0) +static u32 ax88772b_get_tx_csum(struct net_device *netdev) +{ + struct usbnet *dev = netdev_priv(netdev); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; -static const struct net_device_ops ax88772_netdev_ops = { - .ndo_open = usbnet_open, - .ndo_stop = usbnet_stop, - .ndo_start_xmit = usbnet_start_xmit, - .ndo_tx_timeout = usbnet_tx_timeout, - .ndo_change_mtu = usbnet_change_mtu, - .ndo_set_mac_address = asix_set_mac_address, - .ndo_validate_addr = eth_validate_addr, - .ndo_do_ioctl = asix_ioctl, - .ndo_set_multicast_list = asix_set_multicast, -}; + return (ax772b_data->checksum & AX_TX_CHECKSUM); +} -static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf) +static u32 ax88772b_get_rx_csum(struct net_device *netdev) +{ + struct usbnet *dev = netdev_priv(netdev); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + + return (ax772b_data->checksum & AX_RX_CHECKSUM); +} + +static int ax88772b_set_rx_csum(struct net_device *netdev, u32 val) +{ + struct usbnet *dev = netdev_priv(netdev); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + + if (val) + ax772b_data->checksum |= AX_RX_CHECKSUM; + else + ax772b_data->checksum &= ~AX_RX_CHECKSUM; + + return ax88772b_set_csums(dev); +} + +static int ax88772b_set_tx_csum(struct net_device *netdev, u32 val) { - int ret, embd_phy; - u16 rx_ctl; - struct asix_data *data = (struct asix_data *)&dev->data; - u8 buf[ETH_ALEN]; - u32 phyid; + struct usbnet *dev = netdev_priv(netdev); + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; - data->eeprom_len = AX88772_EEPROM_LEN; + if (val) + ax772b_data->checksum |= AX_TX_CHECKSUM; + else + ax772b_data->checksum &= ~AX_TX_CHECKSUM; + + ethtool_op_set_tx_csum(netdev, val); + + return ax88772b_set_csums(dev); +} +#endif +static struct ethtool_ops ax88772b_ethtool_ops = { + .get_drvinfo = ax8817x_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_msglevel = axusbnet_get_msglevel, + .set_msglevel = axusbnet_set_msglevel, + .get_wol = ax8817x_get_wol, + .set_wol = ax8817x_set_wol, + .get_eeprom_len = ax8817x_get_eeprom_len, + .get_eeprom = ax8817x_get_eeprom, + .get_settings = ax8817x_get_settings, + .set_settings = ax8817x_set_settings, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0) + .set_tx_csum = ax88772b_set_tx_csum, + .get_tx_csum = ax88772b_get_tx_csum, + .get_rx_csum = ax88772b_get_rx_csum, + .set_rx_csum = ax88772b_set_rx_csum, +#endif +}; - usbnet_get_endpoints(dev,intf); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,29) +static const struct net_device_ops ax88772b_netdev_ops = { + .ndo_open = axusbnet_open, + .ndo_stop = axusbnet_stop, + .ndo_start_xmit = axusbnet_start_xmit, + .ndo_tx_timeout = axusbnet_tx_timeout, + .ndo_change_mtu = axusbnet_change_mtu, + .ndo_do_ioctl = ax8817x_ioctl, + .ndo_get_stats = axusbnet_get_stats, + .ndo_set_mac_address = ax8817x_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,2,0) + .ndo_set_multicast_list = ax88772b_set_multicast, +#else + .ndo_set_rx_mode = ax88772b_set_multicast, +#endif +}; +#endif - if ((ret = asix_write_gpio(dev, - AX_GPIO_RSE | AX_GPIO_GPO_2 | AX_GPIO_GPO2EN, 5)) < 0) - goto out; +static int ax88772b_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret; + void *buf; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + struct ax88772b_data *ax772b_data; + u16 *tmp16; + u8 i; + u8 TempPhySelect; + bool InternalPhy; + u8 default_asix_mac[ETH_ALEN] = { 0x00, 0x0e, 0xc6, 0x87, 0x72, 0x01 }; + + axusbnet_get_endpoints(dev,intf); + + buf = kmalloc (6, GFP_KERNEL); + if (!buf) { + deverr(dev, "Cannot allocate memory for buffer"); + return -ENOMEM; + } + tmp16 = (u16 *)buf; - /* 0x10 is the phy id of the embedded 10/100 ethernet phy */ - embd_phy = ((asix_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0); - if ((ret = asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, - embd_phy, 0, 0, NULL)) < 0) { - dbg("Select PHY #1 failed: %d", ret); - goto out; + ax772b_data = kmalloc (sizeof(*ax772b_data), GFP_KERNEL); + if (!ax772b_data) { + deverr(dev, "Cannot allocate memory for AX88772B data"); + kfree (buf); + return -ENOMEM; + } + memset (ax772b_data, 0, sizeof(*ax772b_data)); + dev->priv = ax772b_data; + + ax772b_data->ax_work = create_singlethread_workqueue ("ax88772b"); + if (!ax772b_data->ax_work) { + kfree (buf); + kfree (ax772b_data); + return -ENOMEM; } - if ((ret = asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL)) < 0) - goto out; + ax772b_data->dev = dev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK (&ax772b_data->check_link, ax88772b_link_reset, dev); +#else + INIT_WORK (&ax772b_data->check_link, ax88772b_link_reset); +#endif + + if ((ret = ax8817x_read_cmd (dev, AX_CMD_SW_PHY_STATUS, 0, 0, 1, + &TempPhySelect)) < 0) { + deverr(dev, "read SW interface selection status register" + "failed: %d\n", ret); + goto err_out; + } - msleep(150); - if ((ret = asix_sw_reset(dev, AX_SWRESET_CLEAR)) < 0) - goto out; + TempPhySelect &= 0x0C; - msleep(150); - if (embd_phy) { - if ((ret = asix_sw_reset(dev, AX_SWRESET_IPRL)) < 0) - goto out; + if (TempPhySelect == AX_PHYSEL_SSRMII) { + InternalPhy = false; + ax772b_data->OperationMode = OPERATION_MAC_MODE; + ax772b_data->PhySelect = 0x00; + } + else if (TempPhySelect == AX_PHYSEL_SSRRMII) { + InternalPhy = true; + ax772b_data->OperationMode = OPERATION_PHY_MODE; + ax772b_data->PhySelect = 0x00; + } + else if (TempPhySelect == AX_PHYSEL_SSMII) { + InternalPhy = true; + ax772b_data->OperationMode = OPERATION_MAC_MODE; + ax772b_data->PhySelect = 0x01; } else { - if ((ret = asix_sw_reset(dev, AX_SWRESET_PRTE)) < 0) - goto out; + deverr(dev, "Unknown MII type\n"); + goto err_out; } - msleep(150); - rx_ctl = asix_read_rx_ctl(dev); - dbg("RX_CTL is 0x%04x after software reset", rx_ctl); - if ((ret = asix_write_rx_ctl(dev, 0x0000)) < 0) - goto out; + /* reload eeprom data */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_RSE, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to enable GPIO finction: %d", ret); + goto err_out; + } + msleep(5); - rx_ctl = asix_read_rx_ctl(dev); - dbg("RX_CTL is 0x%04x setting to 0x0000", rx_ctl); + /* Get the EEPROM data*/ + if ((ret = ax8817x_read_cmd (dev, AX_CMD_READ_EEPROM, + 0x18, 0, 2, (void *)tmp16)) < 0) { + deverr(dev, "read SROM address 18h failed: %d", ret); + goto err_out; + } + le16_to_cpus(tmp16); + ax772b_data->psc = *tmp16 & 0xFF00; + /* End of get EEPROM data */ + + /* Get the MAC address from EEPROM */ + memset(buf, 0, ETH_ALEN); + for (i = 0; i < (ETH_ALEN >> 1); i++) { + if ((ret = ax8817x_read_cmd (dev, AX_CMD_READ_EEPROM, + 0x04 + i, 0, 2, (buf + i * 2))) < 0) { + deverr(dev, "read SROM address 04h failed: %d", ret); + goto err_out; + } + } - /* Get the MAC address */ - if ((ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, - 0, 0, ETH_ALEN, buf)) < 0) { - dbg("Failed to read MAC address: %d", ret); - goto out; + /* Check for default ASIX MAC (e.g. 00:0e:c6:87:72:01) in case of no EEPROM being present */ + if (!memcmp(buf, default_asix_mac, ETH_ALEN)) { + if (g_usr_mac && (g_usr_mac < 3)) { + /* Get user set MAC address */ + if (g_usr_mac == 2) { + /* 0x100000 offset for 2nd Ethernet MAC */ + g_mac_addr[3] += 0x10; + if (g_mac_addr[3] < 0x10) + devwarn(dev, "MAC address byte 3 (0x%02x) wrap around", g_mac_addr[3]); + } + memcpy(buf, g_mac_addr, ETH_ALEN); + g_usr_mac++; + } else devwarn(dev, "using default ASIX MAC"); } memcpy(dev->net->dev_addr, buf, ETH_ALEN); + /* Set the MAC address */ + if ((ret = ax8817x_write_cmd (dev, AX88772_CMD_WRITE_NODE_ID, + 0, 0, ETH_ALEN, buf)) < 0) { + deverr(dev, "set MAC address failed: %d", ret); + goto err_out; + } + /* Initialize MII structure */ dev->mii.dev = dev->net; - dev->mii.mdio_read = asix_mdio_read; - dev->mii.mdio_write = asix_mdio_write; - dev->mii.phy_id_mask = 0x1f; - dev->mii.reg_num_mask = 0x1f; - dev->mii.phy_id = asix_get_phy_addr(dev); + dev->mii.mdio_read = ax8817x_mdio_read_le; + dev->mii.mdio_write = ax88772b_mdio_write_le; + dev->mii.phy_id_mask = 0xff; + dev->mii.reg_num_mask = 0xff; - phyid = asix_get_phyid(dev); - dbg("PHYID=0x%08x", phyid); + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, + 0, 0, 2, buf)) < 0) { + deverr(dev, "Error reading PHY ID: %02x", ret); + goto err_out; + } else if (ret < 2) { + /* this should always return 2 bytes */ + deverr(dev, "Read PHYID returned less than 2 bytes: ret=%02x", + ret); + ret = -EIO; + goto err_out; + } - if ((ret = asix_sw_reset(dev, AX_SWRESET_PRL)) < 0) - goto out; + if (InternalPhy) + dev->mii.phy_id = *((u8 *)buf + 1); + else + dev->mii.phy_id = *((u8 *)buf); - msleep(150); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + ax772b_data->PhySelect, 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY #1 failed: %d", ret); + goto err_out; + } - if ((ret = asix_sw_reset(dev, AX_SWRESET_IPRL | AX_SWRESET_PRL)) < 0) - goto out; +#if 0 + /* select the embedded 10/100 Ethernet PHY */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + AX_PHYSEL_SSEN | AX_PHYSEL_PSEL | AX_PHYSEL_SSMII + , 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY #1 failed: %d", ret); + goto err_out; + } - msleep(150); + if(dev->mii.phy_id != 0x10) { + deverr(dev, "Got wrong PHY ID: %02x", dev->mii.phy_id); + ret = -EIO; + goto err_out; + } +#endif + if ((ret = ax88772a_phy_powerup (dev)) < 0) + goto err_out; + + /* stop MAC operation */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto err_out; + } - dev->net->netdev_ops = &ax88772_netdev_ops; - dev->net->ethtool_ops = &ax88772_ethtool_ops; + /* make sure the driver can enable sw mii operation */ + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Enabling software MII failed: %d", ret); + goto err_out; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + dev->net->do_ioctl = ax8817x_ioctl; + dev->net->set_multicast_list = ax88772b_set_multicast; + dev->net->set_mac_address = ax8817x_set_mac_addr; +#else + dev->net->netdev_ops = &ax88772b_netdev_ops; +#endif + + dev->net->ethtool_ops = &ax88772b_ethtool_ops; + + /* Register suspend and resume functions */ + data->suspend = ax88772b_suspend; + data->resume = ax88772b_resume; + + if (ax772b_data->OperationMode == OPERATION_PHY_MODE) + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id + , MII_BMCR, 0x3900); + + if (dev->mii.phy_id != 0x10) + ax8817x_mdio_write_le(dev->net, 0x10, MII_BMCR, 0x3900); + + + if (dev->mii.phy_id == 0x10 && ax772b_data->OperationMode + != OPERATION_PHY_MODE) { + + *tmp16 = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, 0x12); + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, 0x12, + ((*tmp16 & 0xFF9F) | 0x0040)); + } + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, - ADVERTISE_ALL | ADVERTISE_CSMA); mii_nway_restart(&dev->mii); - if ((ret = asix_write_medium_mode(dev, AX88772_MEDIUM_DEFAULT)) < 0) - goto out; + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Failed to write medium mode: %d", ret); + goto err_out; + } - if ((ret = asix_write_cmd(dev, AX_CMD_WRITE_IPG0, - AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT, - AX88772_IPG2_DEFAULT, 0, NULL)) < 0) { - dbg("Write IPG,IPG1,IPG2 failed: %d", ret); - goto out; + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772A_IPG0_DEFAULT | AX88772A_IPG1_DEFAULT << 8, + AX88772A_IPG2_DEFAULT, 0, NULL)) < 0) { + deverr(dev, "Failed to write interframe gap: %d", ret); + goto err_out; } - /* Set RX_CTL to default values with 2k buffer, and enable cactus */ - if ((ret = asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL)) < 0) - goto out; + dev->net->features |= NETIF_F_IP_CSUM; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22) + dev->net->features |= NETIF_F_IPV6_CSUM; +#endif - rx_ctl = asix_read_rx_ctl(dev); - dbg("RX_CTL is 0x%04x after all initializations", rx_ctl); + ax772b_data->checksum = AX_RX_CHECKSUM | AX_TX_CHECKSUM; + if ((ret = ax88772b_set_csums(dev)) < 0) { + deverr(dev, "Write RX_COE/TX_COE failed: %d", ret); + goto err_out; + } - rx_ctl = asix_read_medium_status(dev); - dbg("Medium Status is 0x%04x after all initializations", rx_ctl); + dev->rx_size = bsize & 0x07; + if (dev->udev->speed == USB_SPEED_HIGH) { - /* Asix framing packs multiple eth frames into a 2K usb bulk transfer */ - if (dev->driver_info->flags & FLAG_FRAMING_AX) { - /* hard_mtu is still the default - the device does not support - jumbo eth frames */ + if ((ret = ax8817x_write_cmd (dev, 0x2A, + AX88772B_BULKIN_SIZE[dev->rx_size].byte_cnt, + AX88772B_BULKIN_SIZE[dev->rx_size].threshold, + 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto err_out; + } + + dev->rx_urb_size = AX88772B_BULKIN_SIZE[dev->rx_size].size; + } else { + if ((ret = ax8817x_write_cmd (dev, 0x2A, + 0x8000, 0x8001, 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto err_out; + } dev->rx_urb_size = 2048; } - return 0; -out: + /* Configure RX header type */ + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_RX_CTL, + (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_HEADER_DEFAULT), + 0, 0, NULL)) < 0) { + deverr(dev, "Reset RX_CTL failed: %d", ret); + goto err_out; + } + + /* Overwrite power saving configuration from eeprom */ + if ((ret = ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | (ax772b_data->psc & 0x7FFF), 0, 0, NULL)) < 0) { + deverr(dev, "Failed to configure PHY power saving: %d", ret); + goto err_out; + } + + if (ax772b_data->OperationMode == OPERATION_PHY_MODE) + netif_carrier_on(dev->net); + + kfree (buf); + printk (version); + + return ret; +err_out: + destroy_workqueue (ax772b_data->ax_work); + kfree (buf); + kfree (ax772b_data); return ret; } -static struct ethtool_ops ax88178_ethtool_ops = { - .get_drvinfo = asix_get_drvinfo, - .get_link = asix_get_link, - .get_msglevel = usbnet_get_msglevel, - .set_msglevel = usbnet_set_msglevel, - .get_wol = asix_get_wol, - .set_wol = asix_set_wol, - .get_eeprom_len = asix_get_eeprom_len, - .get_eeprom = asix_get_eeprom, - .get_settings = usbnet_get_settings, - .set_settings = usbnet_set_settings, - .nway_reset = usbnet_nway_reset, -}; +static void ax88772b_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; + + if (ax772b_data) { + /* Check for user set MAC address */ + if (!memcmp(dev->net->dev_addr, g_mac_addr, ETH_ALEN)) { + /* Release user set MAC address */ + g_usr_mac--; + + if (g_usr_mac == 2) { + /* 0x100000 offset for 2nd Ethernet MAC */ + g_mac_addr[3] -= 0x10; + if (g_mac_addr[3] > 0xf0) + devwarn(dev, "MAC address byte 3 (0x%02x) wrap around", g_mac_addr[3]); + } + } + + flush_workqueue (ax772b_data->ax_work); + destroy_workqueue (ax772b_data->ax_work); + + /* stop MAC operation */ + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL); + + /* Power down PHY */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, 0, 0, NULL); + + kfree (ax772b_data); + } +} -static int marvell_phy_init(struct usbnet *dev) +static int +ax88178_media_check (struct usbnet *dev, struct ax88178_data *ax178dataptr) { - struct asix_data *data = (struct asix_data *)&dev->data; - u16 reg; + int fullduplex; + u16 tempshort = 0; + u16 media; + u16 advertise, lpa, result, stat1000; + + advertise = ax8817x_mdio_read_le (dev->net, + dev->mii.phy_id, MII_ADVERTISE); + lpa = ax8817x_mdio_read_le (dev->net, dev->mii.phy_id, MII_LPA); + result = advertise & lpa; + + stat1000 = ax8817x_mdio_read_le (dev->net, + dev->mii.phy_id, MII_STAT1000); + + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) { + tempshort = ax8817x_mdio_read_le (dev->net, + dev->mii.phy_id, MARVELL_MANUAL_LED) & 0xfc0f; + } - netdev_dbg(dev->net, "marvell_phy_init()\n"); + fullduplex=1; + if (stat1000 & LPA_1000FULL) { + media = MEDIUM_GIGA_MODE | MEDIUM_FULL_DUPLEX_MODE | + MEDIUM_ENABLE_125MHZ | MEDIUM_ENABLE_RECEIVE; + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) + tempshort|= 0x3e0; + } else if (result & LPA_100FULL) { + media = MEDIUM_FULL_DUPLEX_MODE | MEDIUM_ENABLE_RECEIVE | + MEDIUM_MII_100M_MODE; + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) + tempshort|= 0x3b0; + } else if (result & LPA_100HALF) { + fullduplex = 0; + media = MEDIUM_ENABLE_RECEIVE | MEDIUM_MII_100M_MODE; + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) + tempshort |= 0x3b0; + } else if (result & LPA_10FULL) { + media = MEDIUM_FULL_DUPLEX_MODE | MEDIUM_ENABLE_RECEIVE; + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) + tempshort |= 0x2f0; + } else { + media = MEDIUM_ENABLE_RECEIVE; + fullduplex=0; + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) + tempshort |= 0x02f0; + } - reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_MARVELL_STATUS); - netdev_dbg(dev->net, "MII_MARVELL_STATUS = 0x%04x\n", reg); + if ((ax178dataptr->PhyMode == PHY_MODE_MARVELL) && + (ax178dataptr->LedMode == 1)) { + ax8817x_mdio_write_le (dev->net, + dev->mii.phy_id, MARVELL_MANUAL_LED, tempshort); + } - asix_mdio_write(dev->net, dev->mii.phy_id, MII_MARVELL_CTRL, - MARVELL_CTRL_RXDELAY | MARVELL_CTRL_TXDELAY); + media |= 0x0004; + if(ax178dataptr->UseRgmii) + media |= 0x0008; + if(fullduplex) { + media |= 0x0020; //ebable tx flow control as default; + media |= 0x0010; //ebable rx flow control as default; + } - if (data->ledmode) { - reg = asix_mdio_read(dev->net, dev->mii.phy_id, - MII_MARVELL_LED_CTRL); - netdev_dbg(dev->net, "MII_MARVELL_LED_CTRL (1) = 0x%04x\n", reg); + return media; +} - reg &= 0xf8ff; - reg |= (1 + 0x0100); - asix_mdio_write(dev->net, dev->mii.phy_id, - MII_MARVELL_LED_CTRL, reg); +static void Vitess_8601_Init (struct usbnet *dev, int State) +{ + u16 reg; - reg = asix_mdio_read(dev->net, dev->mii.phy_id, - MII_MARVELL_LED_CTRL); - netdev_dbg(dev->net, "MII_MARVELL_LED_CTRL (2) = 0x%04x\n", reg); - reg &= 0xfc0f; + switch (State) { + case 0: // tx, rx clock skew + ax8817x_swmii_mdio_write_le (dev->net, dev->mii.phy_id, 31, 1); + ax8817x_swmii_mdio_write_le (dev->net, dev->mii.phy_id, 28, 0); + ax8817x_swmii_mdio_write_le (dev->net, dev->mii.phy_id, 31, 0); + break; + + case 1: + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 31, 0x52B5); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, 0x009E); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xDD39); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87AA); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xA7B4); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, + ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18)); + + reg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & ~0x003f) | 0x003c; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, reg); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87B4); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xa794); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, + ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18)); + + reg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & ~0x003f) | 0x003e; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, reg); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x8794); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, 0x00f7); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xbe36); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x879e); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xa7a0); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, + ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18)); + + reg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & ~0x003f) | 0x0034; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, reg); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87a0); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, 0x003c); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xf3cf); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87a2); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, 0x003c); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xf3cf); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87a4); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, 0x003c); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xd287); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87a6); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xa7a8); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, + ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18)); + + reg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & ~0x0fff) | 0x0125; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, reg); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87a8); + + // Enable Smart Pre-emphasis + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xa7fa); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, + ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18)); + + reg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & ~0x0008) | 0x0008; + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, reg); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87fa); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 31, 0); + + break; } +} - return 0; +static void +marvell_88E1510_magic_init(struct usbnet *dev) +{ + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 22, 0xff); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0x214b); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x2144); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0x0c28); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x2146); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xb233); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x214d); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, 0xcc0c); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x2159); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 22, 0x00fb); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 7, 0xc00d); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 22, 0); } -static int marvell_led_status(struct usbnet *dev, u16 speed) +static int +ax88178_phy_init (struct usbnet *dev, struct ax88178_data *ax178dataptr) { - u16 reg = asix_mdio_read(dev->net, dev->mii.phy_id, MARVELL_LED_MANUAL); + int i; + u16 PhyAnar, PhyAuxCtrl, PhyCtrl, TempShort, PhyID1; + u16 PhyReg = 0; - netdev_dbg(dev->net, "marvell_led_status() read 0x%04x\n", reg); + //Disable MII operation of AX88178 Hardware + ax8817x_write_cmd (dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL); - /* Clear out the center LED bits - 0x03F0 */ - reg &= 0xfc0f; - switch (speed) { - case SPEED_1000: - reg |= 0x03e0; - break; - case SPEED_100: - reg |= 0x03b0; - break; - default: - reg |= 0x02f0; + //Read SROM - MiiPhy Address (ID) + ax8817x_read_cmd (dev, AX_CMD_READ_PHY_ID, 0, 0, 2, &dev->mii.phy_id); + le32_to_cpus (&dev->mii.phy_id); + + /* Initialize MII structure */ + dev->mii.phy_id >>= 8; + dev->mii.phy_id &= PHY_ID_MASK; + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read_le; + dev->mii.mdio_write = ax8817x_mdio_write_le; + dev->mii.phy_id_mask = 0x3f; + dev->mii.reg_num_mask = 0x1f; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11) + dev->mii.supports_gmii = 1; +#endif + + if (ax178dataptr->PhyMode == PHY_MODE_MAC_TO_MAC_GMII) + { + ax178dataptr->UseRgmii = 0; + ax178dataptr->MediaLink = MEDIUM_GIGA_MODE | + MEDIUM_FULL_DUPLEX_MODE | + MEDIUM_ENABLE_125MHZ | + MEDIUM_ENABLE_RECEIVE | + MEDIUM_ENABLE_RX_FLOWCTRL | + MEDIUM_ENABLE_TX_FLOWCTRL; + + goto SkipPhySetting; } - netdev_dbg(dev->net, "marvell_led_status() writing 0x%04x\n", reg); - asix_mdio_write(dev->net, dev->mii.phy_id, MARVELL_LED_MANUAL, reg); + // test read phy register 2 + if (!ax178dataptr->UseGpio0) { + i = 1000; + while (i--) { + PhyID1 = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, GMII_PHY_OUI); + if ((PhyID1 == 0x000f) || (PhyID1 == 0x0141) || + (PhyID1 == 0x0282) || (PhyID1 == 0x004d) || + (PhyID1 == 0x0243) || (PhyID1 == 0x001C) || + (PhyID1 == 0x0007)) + break; + msleep(5); + } + if (i < 0) + return -EIO; + } - return 0; -} + ax178dataptr->UseRgmii = 0; + if (ax178dataptr->PhyMode == PHY_MODE_MARVELL) { + PhyReg = ax8817x_swmii_mdio_read_le(dev->net, + dev->mii.phy_id, 27); + if (!(PhyReg & 4) && !(ax178dataptr->LedMode & 0x10)) { + ax178dataptr->UseRgmii = 1; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 20, 0x82); + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } else if (ax178dataptr->LedMode & 0x10) { + + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + marvell_88E1510_magic_init(dev); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 22, 2); + + PhyReg = ax8817x_swmii_mdio_read_le(dev->net, + dev->mii.phy_id, 21); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 21, PhyReg | 0x30); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 22, 0); + + } + } else if ((ax178dataptr->PhyMode == PHY_MODE_AGERE_V0) || + (ax178dataptr->PhyMode == PHY_MODE_AGERE_V0_GMII)) { + if (ax178dataptr->PhyMode == PHY_MODE_AGERE_V0) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } + } else if (ax178dataptr->PhyMode == PHY_MODE_CICADA_V1) { + // not Cameo + if (!ax178dataptr->UseGpio0 || ax178dataptr->LedMode) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } -static int ax88178_link_reset(struct usbnet *dev) -{ - u16 mode; - struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; - struct asix_data *data = (struct asix_data *)&dev->data; - u32 speed; + for (i = 0; i < (sizeof(CICADA_FAMILY_HWINIT) / + sizeof(CICADA_FAMILY_HWINIT[0])); i++) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, + CICADA_FAMILY_HWINIT[i].offset, + CICADA_FAMILY_HWINIT[i].value); + } - netdev_dbg(dev->net, "ax88178_link_reset()\n"); + } else if (ax178dataptr->PhyMode == PHY_MODE_CICADA_V2) { + // not Cameo + if (!ax178dataptr->UseGpio0 || ax178dataptr->LedMode) + { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } - mii_check_media(&dev->mii, 1, 1); - mii_ethtool_gset(&dev->mii, &ecmd); - mode = AX88178_MEDIUM_DEFAULT; - speed = ethtool_cmd_speed(&ecmd); + for (i = 0; i < (sizeof(CICADA_V2_HWINIT) / + sizeof(CICADA_V2_HWINIT[0])); i++) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, CICADA_V2_HWINIT[i].offset, + CICADA_V2_HWINIT[i].value); + } + } else if (ax178dataptr->PhyMode == PHY_MODE_CICADA_V2_ASIX) { + // not Cameo + if (!ax178dataptr->UseGpio0 || ax178dataptr->LedMode) + { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } - if (speed == SPEED_1000) - mode |= AX_MEDIUM_GM; - else if (speed == SPEED_100) - mode |= AX_MEDIUM_PS; - else - mode &= ~(AX_MEDIUM_PS | AX_MEDIUM_GM); + for (i = 0; i < (sizeof(CICADA_V2_HWINIT) / + sizeof(CICADA_V2_HWINIT[0])); i++) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, CICADA_V2_HWINIT[i].offset, + CICADA_V2_HWINIT[i].value); + } + } else if (ax178dataptr->PhyMode == PHY_MODE_RTL8211CL) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } else if (ax178dataptr->PhyMode == PHY_MODE_RTL8211BN) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } else if (ax178dataptr->PhyMode == PHY_MODE_RTL8251CL) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; + } else if (ax178dataptr->PhyMode == PHY_MODE_VSC8601) { + ax178dataptr->UseRgmii = 1; + ax178dataptr->MediaLink |= MEDIUM_ENABLE_125MHZ; +// Vitess_8601_Init (dev, 0); + } - mode |= AX_MEDIUM_ENCK; + if (ax178dataptr->PhyMode != PHY_MODE_ATTANSIC_V0) { + // software reset + ax8817x_swmii_mdio_write_le ( + dev->net, dev->mii.phy_id, GMII_PHY_CONTROL, + ax8817x_swmii_mdio_read_le ( + dev->net, dev->mii.phy_id, GMII_PHY_CONTROL) + | GMII_CONTROL_RESET); + msleep (1); + } - if (ecmd.duplex == DUPLEX_FULL) - mode |= AX_MEDIUM_FD; - else - mode &= ~AX_MEDIUM_FD; + if ((ax178dataptr->PhyMode == PHY_MODE_AGERE_V0) || + (ax178dataptr->PhyMode == PHY_MODE_AGERE_V0_GMII)) { + if (ax178dataptr->PhyMode == PHY_MODE_AGERE_V0) + { + i = 1000; + while (i--) + { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 21, 0x1001); + + PhyReg = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 21); + if ((PhyReg & 0xf00f) == 0x1001) + break; + } + if (i < 0) + return -EIO; + } + + if (ax178dataptr->LedMode == 4) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 28, 0x7417); + } else if (ax178dataptr->LedMode == 9) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 28, 0x7a10); + } else if (ax178dataptr->LedMode == 10) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 28, 0x7a13); + } + + for (i = 0; i < (sizeof(AGERE_FAMILY_HWINIT) / + sizeof(AGERE_FAMILY_HWINIT[0])); i++) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, AGERE_FAMILY_HWINIT[i].offset, + AGERE_FAMILY_HWINIT[i].value); + } + } else if (ax178dataptr->PhyMode == PHY_MODE_RTL8211CL) { + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x1f, 0x0005); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x0c, 0); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x01, + (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 0x01) | 0x0080)); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x1f, 0); + + if (ax178dataptr->LedMode == 12) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x1f, 0x0002); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x1a, 0x00cb); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 0x1f, 0); + } + } else if (ax178dataptr->PhyMode == PHY_MODE_VSC8601) { + Vitess_8601_Init (dev, 1); + } + + // read phy register 0 + PhyCtrl = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, GMII_PHY_CONTROL); + TempShort = PhyCtrl; + PhyCtrl &= ~(GMII_CONTROL_POWER_DOWN | GMII_CONTROL_ISOLATE); + if (PhyCtrl != TempShort) { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, GMII_PHY_CONTROL, PhyCtrl); + } + + // led + if (ax178dataptr->PhyMode == PHY_MODE_MARVELL) { + if (ax178dataptr->LedMode == 1) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0xf8ff) | (1 + 0x100); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + PhyReg = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 25) & 0xfc0f; + + } else if (ax178dataptr->LedMode == 2) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0xf886) | + (1 + 0x10 + 0x300); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + + } else if (ax178dataptr->LedMode == 5) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0xf8be) | + (1 + 0x40 + 0x300); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + + } else if (ax178dataptr->LedMode == 7) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0xf8ff) | + (1 + 0x100); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + + } else if (ax178dataptr->LedMode == 8) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0xf8be) | + (1 + 0x40 + 0x100); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + + } else if (ax178dataptr->LedMode == 11) { + + PhyReg = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 24) & 0x4106; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 24, PhyReg); + + } else if (ax178dataptr->LedMode == 0x10) { + //MARVEL 88e1510 use default led setting + } + + } else if ((ax178dataptr->PhyMode == PHY_MODE_CICADA_V1) || + (ax178dataptr->PhyMode == PHY_MODE_CICADA_V2) || + (ax178dataptr->PhyMode == PHY_MODE_CICADA_V2_ASIX)) { + + if (ax178dataptr->LedMode == 3) { + + PhyReg = (ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 27) & 0xFCFF) | 0x0100; + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 27, PhyReg); + } + + } + + if (ax178dataptr->PhyMode == PHY_MODE_MARVELL) + { + if (ax178dataptr->LedMode == 1) + PhyReg |= 0x3f0; + } + + PhyAnar = 1 | (GMII_ANAR_PAUSE | GMII_ANAR_100TXFD | GMII_ANAR_100TX | + GMII_ANAR_10TFD | GMII_ANAR_10T | GMII_ANAR_ASYM_PAUSE); + + PhyAuxCtrl = GMII_1000_AUX_CTRL_FD_CAPABLE; + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, GMII_PHY_ANAR, PhyAnar); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, GMII_PHY_1000BT_CONTROL, PhyAuxCtrl); + + if (ax178dataptr->PhyMode == PHY_MODE_VSC8601) + { + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 31, 0x52B5); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0xA7F8); + + TempShort = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 17) & (~0x0018); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 17, TempShort); + + TempShort = ax8817x_swmii_mdio_read_le (dev->net, + dev->mii.phy_id, 18); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 18, TempShort); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 16, 0x87F8); + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 31, 0); + } + + if (ax178dataptr->PhyMode == PHY_MODE_ATTANSIC_V0) { + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, GMII_PHY_CONTROL, 0x9000); + + } else { + PhyCtrl &= ~GMII_CONTROL_LOOPBACK; + PhyCtrl |= (GMII_CONTROL_ENABLE_AUTO | GMII_CONTROL_START_AUTO); + + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, GMII_PHY_CONTROL, PhyCtrl); + } + + if (ax178dataptr->PhyMode == PHY_MODE_MARVELL) { + if (ax178dataptr->LedMode == 1) + ax8817x_swmii_mdio_write_le (dev->net, + dev->mii.phy_id, 25, PhyReg); + } - netdev_dbg(dev->net, "ax88178_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n", - speed, ecmd.duplex, mode); +SkipPhySetting: - asix_write_medium_mode(dev, mode); + ax8817x_write_cmd (dev, AX_CMD_WRITE_MEDIUM_MODE, + ax178dataptr->MediaLink, 0, 0, NULL); - if (data->phymode == PHY_MODE_MARVELL && data->ledmode) - marvell_led_status(dev, speed); + ax8817x_write_cmd (dev, AX_CMD_WRITE_IPG0, + AX88772_IPG0_DEFAULT | (AX88772_IPG1_DEFAULT << 8), + AX88772_IPG2_DEFAULT, 0, NULL); + + msleep (1); + + ax8817x_write_cmd (dev, AX_CMD_SET_HW_MII, 0, 0, 0, NULL); return 0; } -static void ax88178_set_mfb(struct usbnet *dev) +static int ax88178_bind(struct usbnet *dev, struct usb_interface *intf) { - u16 mfb = AX_RX_CTL_MFB_16384; - u16 rxctl; - u16 medium; - int old_rx_urb_size = dev->rx_urb_size; + int ret; + void *buf; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + struct ax88178_data *ax178dataptr = NULL; - if (dev->hard_mtu < 2048) { - dev->rx_urb_size = 2048; - mfb = AX_RX_CTL_MFB_2048; - } else if (dev->hard_mtu < 4096) { - dev->rx_urb_size = 4096; - mfb = AX_RX_CTL_MFB_4096; - } else if (dev->hard_mtu < 8192) { - dev->rx_urb_size = 8192; - mfb = AX_RX_CTL_MFB_8192; - } else if (dev->hard_mtu < 16384) { + axusbnet_get_endpoints(dev,intf); + + buf = kmalloc(6, GFP_KERNEL); + if(!buf) { + deverr(dev, "Cannot allocate memory for buffer"); + return -ENOMEM; + } + + /* allocate 178 data */ + ax178dataptr = kmalloc (sizeof (*ax178dataptr), GFP_KERNEL); + if (!ax178dataptr) { + deverr(dev, "Cannot allocate memory for AX88178 data"); + ret = -ENOMEM; + goto error_out; + } + memset (ax178dataptr, 0, sizeof (struct ax88178_data)); + dev->priv = ax178dataptr; + /* end of allocate 178 data */ + + /* Get the EEPROM data*/ + if ((ret = ax8817x_read_cmd (dev, AX_CMD_READ_EEPROM, 0x0017, 0, 2, + (void *)(&ax178dataptr->EepromData))) < 0) { + deverr(dev, "read SROM address 17h failed: %d", ret); + goto error_out; + } + le16_to_cpus (&ax178dataptr->EepromData); + /* End of get EEPROM data */ + + if (ax178dataptr->EepromData == 0xffff) { + ax178dataptr->PhyMode = PHY_MODE_MARVELL; + ax178dataptr->LedMode = 0; + ax178dataptr->UseGpio0 = 1; //True + } else { + ax178dataptr->PhyMode = (u8)(ax178dataptr->EepromData & + EEPROMMASK); + ax178dataptr->LedMode = (u8)(ax178dataptr->EepromData >> 8); + if (ax178dataptr->LedMode == 6) // for buffalo new (use gpio2) + ax178dataptr->LedMode = 1; + else if (ax178dataptr->LedMode == 1) + ax178dataptr->BuffaloOld = 1; + + + if(ax178dataptr->EepromData & 0x80) { + ax178dataptr->UseGpio0=0; //MARVEL se and other + } else { + ax178dataptr->UseGpio0=1; //cameo + } + } + + if (ax178dataptr->UseGpio0) { + + if (ax178dataptr->PhyMode == PHY_MODE_MARVELL) { + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_GPO0EN | AXGPIOS_RSE, + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (25); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO2 | AXGPIOS_GPO2EN | + AXGPIOS_GPO0EN), 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (15); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_GPO2EN | AXGPIOS_GPO0EN, + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (245); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO2 | AXGPIOS_GPO2EN | + AXGPIOS_GPO0EN), 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + } else { // vitesse + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_RSE | AXGPIOS_GPO0EN | + AXGPIOS_GPO0), 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (25); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO0EN | AXGPIOS_GPO0 | + AXGPIOS_GPO2EN | AXGPIOS_GPO2), + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (25); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO0EN | AXGPIOS_GPO0 | + AXGPIOS_GPO2EN), 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (245); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO0EN | AXGPIOS_GPO0 | + AXGPIOS_GPO2EN | AXGPIOS_GPO2), + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + } + } else { // use gpio1 + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO1 |AXGPIOS_GPO1EN | AXGPIOS_RSE), + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + if (ax178dataptr->BuffaloOld) { + + msleep (350); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_GPO1EN, 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (350); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + AXGPIOS_GPO1EN | AXGPIOS_GPO1, + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + } + else + { + msleep (25); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO1EN | AXGPIOS_GPO1 | + AXGPIOS_GPO2EN | AXGPIOS_GPO2), + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (25); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO1EN | AXGPIOS_GPO1 | + AXGPIOS_GPO2EN), 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + + msleep (245); + + if ((ret = ax8817x_write_cmd (dev, AX_CMD_WRITE_GPIOS, + (AXGPIOS_GPO1EN | AXGPIOS_GPO1 | + AXGPIOS_GPO2EN | AXGPIOS_GPO2), + 0, 0, NULL)) < 0) { + deverr(dev, "write GPIO failed: %d", ret); + goto error_out; + } + } + } + + if ((ret = ax8817x_write_cmd(dev, + AX_CMD_SW_PHY_SELECT, 0, 0, 0, NULL)) < 0) { + deverr(dev, "Select PHY failed: %d", ret); + goto error_out; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD | AX_SWRESET_PRL, 0, 0, NULL)) < 0) { + deverr(dev, "Issue sw reset failed: %d", ret); + goto error_out; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + 0, 0, 0, NULL)) < 0) { + deverr(dev, "Issue rx ctrl failed: %d", ret); + goto error_out; + } + + /* Get the MAC address */ + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd (dev, AX88772_CMD_READ_NODE_ID, + 0, 0, ETH_ALEN, buf)) < 0) { + deverr(dev, "read AX_CMD_READ_NODE_ID failed: %d", ret); + goto error_out; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + /* End of get MAC address */ + + if ((ret = ax88178_phy_init (dev, ax178dataptr)) < 0) + goto error_out; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + dev->net->do_ioctl = ax8817x_ioctl; + dev->net->set_multicast_list = ax88178_set_multicast; + dev->net->set_mac_address = ax88178_set_mac_addr; +#else + dev->net->netdev_ops = &ax88178_netdev_ops; +#endif + dev->net->ethtool_ops = &ax8817x_ethtool_ops; + + /* Register suspend and resume functions */ + data->suspend = ax88772_suspend; + data->resume = ax88772_resume; + + if (dev->driver_info->flags & FLAG_FRAMING_AX) { dev->rx_urb_size = 16384; - mfb = AX_RX_CTL_MFB_16384; } - rxctl = asix_read_rx_ctl(dev); - asix_write_rx_ctl(dev, (rxctl & ~AX_RX_CTL_MFB_16384) | mfb); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + (AX_RX_CTL_MFB | AX_RX_CTL_START | AX_RX_CTL_AB), + 0, 0, NULL)) < 0) { + deverr(dev, "write RX ctrl reg failed: %d", ret); + goto error_out; + } - medium = asix_read_medium_status(dev); - if (dev->net->mtu > 1500) - medium |= AX_MEDIUM_JFE; - else - medium &= ~AX_MEDIUM_JFE; - asix_write_medium_mode(dev, medium); + kfree (buf); + printk (version); + return ret; - if (dev->rx_urb_size > old_rx_urb_size) - usbnet_unlink_rx_urbs(dev); +error_out: + if (ax178dataptr) + kfree (ax178dataptr); + kfree (buf); + return ret; } -static int ax88178_change_mtu(struct net_device *net, int new_mtu) +static void ax88178_unbind(struct usbnet *dev, struct usb_interface *intf) { - struct usbnet *dev = netdev_priv(net); - int ll_mtu = new_mtu + net->hard_header_len + 4; + struct ax88178_data *ax178dataptr = (struct ax88178_data *)dev->priv; - netdev_dbg(dev->net, "ax88178_change_mtu() new_mtu=%d\n", new_mtu); + if (ax178dataptr) { - if (new_mtu <= 0 || ll_mtu > 16384) - return -EINVAL; + /* stop MAC operation */ + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, + AX_RX_CTL_STOP, 0, 0, NULL); + + kfree (ax178dataptr); + } +} + +static int ax88772_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + u8 *head; + u32 header; + char *packet; + struct sk_buff *ax_skb; + u16 size; + + head = (u8 *) skb->data; + memcpy(&header, head, sizeof(header)); + le32_to_cpus(&header); + packet = head + sizeof(header); - if ((ll_mtu % dev->maxpacket) == 0) - return -EDOM; + skb_pull(skb, 4); - net->mtu = new_mtu; - dev->hard_mtu = net->mtu + net->hard_header_len; - ax88178_set_mfb(dev); + while (skb->len > 0) { + if ((short)(header & 0x0000ffff) != + ~((short)((header & 0xffff0000) >> 16))) { + deverr(dev, "header length data is error 0x%08x, %d\n", + header, skb->len); + } + /* get the packet length */ + size = (u16) (header & 0x0000ffff); - return 0; + if ((skb->len) - ((size + 1) & 0xfffe) == 0) { + + /* Make sure ip header is aligned on 32-bit boundary */ + if (!((unsigned long)skb->data & 0x02)) { + memmove (skb->data - 2, skb->data, size); + skb->data -= 2; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + skb->tail = skb->data + size; +#else + skb_set_tail_pointer (skb, size); +#endif + } + skb->truesize = size + sizeof(struct sk_buff); + return 2; + } + + if (size > ETH_FRAME_LEN) { + deverr(dev, "invalid rx length %d", size); + return 0; + } + ax_skb = skb_clone(skb, GFP_ATOMIC); + if (ax_skb) { + + /* Make sure ip header is aligned on 32-bit boundary */ + if (!((unsigned long)packet & 0x02)) { + memmove (packet - 2, packet, size); + packet -= 2; + } + ax_skb->data = packet; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + ax_skb->tail = packet + size; +#else + skb_set_tail_pointer (ax_skb, size); +#endif + ax_skb->truesize = size + sizeof(struct sk_buff); + axusbnet_skb_return(dev, ax_skb); + + } else { + return 0; + } + + skb_pull(skb, (size + 1) & 0xfffe); + + if (skb->len == 0) + break; + + head = (u8 *) skb->data; + memcpy(&header, head, sizeof(header)); + le32_to_cpus(&header); + packet = head + sizeof(header); + skb_pull(skb, 4); + } + + if (skb->len < 0) { + deverr(dev, "invalid rx length %d", skb->len); + return 0; + } + return 1; } -static const struct net_device_ops ax88178_netdev_ops = { - .ndo_open = usbnet_open, - .ndo_stop = usbnet_stop, - .ndo_start_xmit = usbnet_start_xmit, - .ndo_tx_timeout = usbnet_tx_timeout, - .ndo_set_mac_address = asix_set_mac_address, - .ndo_validate_addr = eth_validate_addr, - .ndo_set_multicast_list = asix_set_multicast, - .ndo_do_ioctl = asix_ioctl, - .ndo_change_mtu = ax88178_change_mtu, -}; +static struct sk_buff *ax88772_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + int padlen = ((skb->len + 4) % 512) ? 0 : 4; + u32 packet_len; + u32 padbytes = 0xffff0000; -static int ax88178_bind(struct usbnet *dev, struct usb_interface *intf) +#if (!AX_FORCE_BUFF_ALIGN) + int headroom = skb_headroom(skb); + int tailroom = skb_tailroom(skb); + + if ((!skb_cloned(skb)) + && ((headroom + tailroom) >= (4 + padlen))) { + if ((headroom < 4) || (tailroom < padlen)) { + skb->data = memmove(skb->head + 4, skb->data, skb->len); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + skb->tail = skb->data + skb->len; +#else + skb_set_tail_pointer (skb, skb->len); +#endif + } + } else +#endif + { + struct sk_buff *skb2; + skb2 = skb_copy_expand(skb, 4, padlen, flags); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return NULL; + } + + skb_push(skb, 4); + packet_len = (((skb->len - 4) ^ 0x0000ffff) << 16) + (skb->len - 4); + cpu_to_le32s(&packet_len); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + memcpy(skb->data, &packet_len, sizeof(packet_len)); +#else + skb_copy_to_linear_data(skb, &packet_len, sizeof(packet_len)); +#endif + + if ((skb->len % 512) == 0) { + cpu_to_le32s(&padbytes); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + memcpy(skb->tail, &padbytes, sizeof(padbytes)); +#else + memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes)); +#endif + skb_put(skb, sizeof(padbytes)); + } + return skb; +} + +static void +ax88772b_rx_checksum (struct sk_buff *skb, struct ax88772b_rx_header *rx_hdr) { - struct asix_data *data = (struct asix_data *)&dev->data; - int ret; - u8 buf[ETH_ALEN]; - __le16 eeprom; - u8 status; - int gpio0 = 0; - u32 phyid; + skb->ip_summed = CHECKSUM_NONE; + + /* checksum error bit is set */ + if (rx_hdr->l3_csum_err || rx_hdr->l4_csum_err) { + return; + } + + /* It must be a TCP or UDP packet with a valid checksum */ + if ((rx_hdr->l4_type == AX_RXHDR_L4_TYPE_TCP) || + (rx_hdr->l4_type == AX_RXHDR_L4_TYPE_UDP)) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + } +} + +static int ax88772b_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + struct ax88772b_rx_header rx_hdr; + struct sk_buff *ax_skb; + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; - usbnet_get_endpoints(dev,intf); + while (skb->len > 0) { - asix_read_cmd(dev, AX_CMD_READ_GPIOS, 0, 0, 1, &status); - dbg("GPIO Status: 0x%04x", status); + le16_to_cpus((u16 *)skb->data); + le16_to_cpus(((u16 *)skb->data) + 1); - asix_write_cmd(dev, AX_CMD_WRITE_ENABLE, 0, 0, 0, NULL); - asix_read_cmd(dev, AX_CMD_READ_EEPROM, 0x0017, 0, 2, &eeprom); - asix_write_cmd(dev, AX_CMD_WRITE_DISABLE, 0, 0, 0, NULL); + memcpy (&rx_hdr, skb->data, sizeof (struct ax88772b_rx_header)); - dbg("EEPROM index 0x17 is 0x%04x", eeprom); + if ((short)rx_hdr.len != (~((short)rx_hdr.len_bar) & 0x7FF)) { + return 0; + } - if (eeprom == cpu_to_le16(0xffff)) { - data->phymode = PHY_MODE_MARVELL; - data->ledmode = 0; - gpio0 = 1; - } else { - data->phymode = le16_to_cpu(eeprom) & 7; - data->ledmode = le16_to_cpu(eeprom) >> 8; - gpio0 = (le16_to_cpu(eeprom) & 0x80) ? 0 : 1; + if (rx_hdr.len > (ETH_FRAME_LEN + 4)) { + deverr(dev, "invalid rx length %d", rx_hdr.len); + return 0; + } + + if (skb->len - ((rx_hdr.len + + sizeof (struct ax88772b_rx_header) + 3) & + 0xfffc) == 0) { + skb_pull(skb, sizeof (struct ax88772b_rx_header)); + skb->len = rx_hdr.len; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + skb->tail = skb->data + rx_hdr.len; +#else + skb_set_tail_pointer(skb, rx_hdr.len); +#endif + skb->truesize = rx_hdr.len + sizeof(struct sk_buff); + + if (ax772b_data->checksum & AX_RX_CHECKSUM) + ax88772b_rx_checksum (skb, &rx_hdr); + + return 2; + } + + ax_skb = skb_clone(skb, GFP_ATOMIC); + if (ax_skb) { + ax_skb->len = rx_hdr.len; + ax_skb->data = skb->data + + sizeof (struct ax88772b_rx_header); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + ax_skb->tail = ax_skb->data + rx_hdr.len; +#else + skb_set_tail_pointer(ax_skb, rx_hdr.len); +#endif + ax_skb->truesize = rx_hdr.len + sizeof(struct sk_buff); + + if (ax772b_data->checksum & AX_RX_CHECKSUM) { + ax88772b_rx_checksum (ax_skb, &rx_hdr); + } + + axusbnet_skb_return(dev, ax_skb); + + } else { + return 0; + } + + skb_pull(skb, ((rx_hdr.len + + sizeof (struct ax88772b_rx_header) + 3) + & 0xfffc)); } - dbg("GPIO0: %d, PhyMode: %d", gpio0, data->phymode); - asix_write_gpio(dev, AX_GPIO_RSE | AX_GPIO_GPO_1 | AX_GPIO_GPO1EN, 40); - if ((le16_to_cpu(eeprom) >> 8) != 1) { - asix_write_gpio(dev, 0x003c, 30); - asix_write_gpio(dev, 0x001c, 300); - asix_write_gpio(dev, 0x003c, 30); - } else { - dbg("gpio phymode == 1 path"); - asix_write_gpio(dev, AX_GPIO_GPO1EN, 30); - asix_write_gpio(dev, AX_GPIO_GPO1EN | AX_GPIO_GPO_1, 30); + if (skb->len < 0) { + deverr(dev, "invalid rx length %d", skb->len); + return 0; } + return 1; +} - asix_sw_reset(dev, 0); - msleep(150); +static struct sk_buff * +ax88772b_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + int padlen = ((skb->len + 4) % 512) ? 0 : 4; + u32 packet_len; + u32 padbytes = 0xffff0000; - asix_sw_reset(dev, AX_SWRESET_PRL | AX_SWRESET_IPPD); - msleep(150); +#if (!AX_FORCE_BUFF_ALIGN) + int headroom = skb_headroom(skb); + int tailroom = skb_tailroom(skb); - asix_write_rx_ctl(dev, 0); + if ((!skb_cloned(skb)) + && ((headroom + tailroom) >= (4 + padlen))) { + if ((headroom < 4) || (tailroom < padlen)) { + skb->data = memmove(skb->head + 4, skb->data, skb->len); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + skb->tail = skb->data + skb->len; +#else + skb_set_tail_pointer(skb, skb->len); +#endif + } + } else +#endif + { + struct sk_buff *skb2; + skb2 = skb_copy_expand(skb, 4, padlen, flags); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return NULL; + } - /* Get the MAC address */ - if ((ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, - 0, 0, ETH_ALEN, buf)) < 0) { - dbg("Failed to read MAC address: %d", ret); - goto out; + skb_push(skb, 4); + packet_len = (((skb->len - 4) ^ 0x0000ffff) << 16) + (skb->len - 4); + + cpu_to_le32s (&packet_len); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + memcpy(skb->data, &packet_len, sizeof(packet_len)); +#else + skb_copy_to_linear_data(skb, &packet_len, sizeof(packet_len)); +#endif + + if ((skb->len % 512) == 0) { + cpu_to_le32s (&padbytes); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + memcpy(skb->tail, &padbytes, sizeof(padbytes)); +#else + memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes)); +#endif + skb_put(skb, sizeof(padbytes)); } - memcpy(dev->net->dev_addr, buf, ETH_ALEN); - /* Initialize MII structure */ - dev->mii.dev = dev->net; - dev->mii.mdio_read = asix_mdio_read; - dev->mii.mdio_write = asix_mdio_write; - dev->mii.phy_id_mask = 0x1f; - dev->mii.reg_num_mask = 0xff; - dev->mii.supports_gmii = 1; - dev->mii.phy_id = asix_get_phy_addr(dev); + return skb; +} - dev->net->netdev_ops = &ax88178_netdev_ops; - dev->net->ethtool_ops = &ax88178_ethtool_ops; +static const u8 ChkCntSel [6][3] = +{ + {12, 23, 31}, + {12, 31, 23}, + {23, 31, 12}, + {23, 12, 31}, + {31, 12, 23}, + {31, 23, 12} +}; - phyid = asix_get_phyid(dev); - dbg("PHYID=0x%08x", phyid); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void ax88772_link_reset (void *data) +{ + struct usbnet *dev = (struct usbnet *)data; + struct ax88772_data *ax772_data = (struct ax88772_data *)dev->priv; +#else +static void ax88772_link_reset (struct work_struct *work) +{ + struct ax88772_data *ax772_data = container_of (work, + struct ax88772_data, check_link); + struct usbnet *dev = ax772_data->dev; +#endif + + if (ax772_data->Event == AX_SET_RX_CFG) { + u16 bmcr; + u16 mode; + + ax772_data->Event = AX_NOP; + + mode = AX88772_MEDIUM_DEFAULT; + + bmcr = ax8817x_mdio_read_le(dev->net, + dev->mii.phy_id, MII_BMCR); + if (!(bmcr & BMCR_FULLDPLX)) + mode &= ~AX88772_MEDIUM_FULL_DUPLEX; + if (!(bmcr & BMCR_SPEED100)) + mode &= ~AX88772_MEDIUM_100MB; + + ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + mode, 0, 0, NULL); + return; + } + + switch (ax772_data->Event) { + case WAIT_AUTONEG_COMPLETE: + if (jiffies > (ax772_data->autoneg_start + 5 * HZ)) { + ax772_data->Event = PHY_POWER_DOWN; + ax772_data->TickToExpire = 23; + } + break; + case PHY_POWER_DOWN: + if (ax772_data->TickToExpire == 23) { + /* Set Phy Power Down */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, + 0, 0, NULL); + --ax772_data->TickToExpire; + } else if (--ax772_data->TickToExpire == 0) { + /* Set Phy Power Up */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL, 0, 0, NULL); + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD | AX_SWRESET_IPRL, 0, 0, NULL); + msleep(10); + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL, 0, 0, NULL); + msleep(60); + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_CLEAR, 0, 0, NULL); + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL, 0, 0, NULL); + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, + MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | + ADVERTISE_PAUSE_CAP); + mii_nway_restart(&dev->mii); + + ax772_data->Event = PHY_POWER_UP; + ax772_data->TickToExpire = 47; + } + break; + case PHY_POWER_UP: + if (--ax772_data->TickToExpire == 0) { + ax772_data->Event = PHY_POWER_DOWN; + ax772_data->TickToExpire = 23; + } + break; + default: + break; + } + return; +} - if (data->phymode == PHY_MODE_MARVELL) { - marvell_phy_init(dev); - msleep(60); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void ax88772a_link_reset (void *data) +{ + struct usbnet *dev = (struct usbnet *)data; + struct ax88772a_data *ax772a_data = (struct ax88772a_data *)dev->priv; +#else +static void ax88772a_link_reset (struct work_struct *work) +{ + struct ax88772a_data *ax772a_data = container_of (work, + struct ax88772a_data, check_link); + struct usbnet *dev = ax772a_data->dev; +#endif + int PowSave = (ax772a_data->EepromData >> 14); + u16 phy_reg; + + if (ax772a_data->Event == AX_SET_RX_CFG) { + u16 bmcr; + u16 mode; + + ax772a_data->Event = AX_NOP; + + mode = AX88772_MEDIUM_DEFAULT; + + bmcr = ax8817x_mdio_read_le(dev->net, + dev->mii.phy_id, MII_BMCR); + if (!(bmcr & BMCR_FULLDPLX)) + mode &= ~AX88772_MEDIUM_FULL_DUPLEX; + if (!(bmcr & BMCR_SPEED100)) + mode &= ~AX88772_MEDIUM_100MB; + + ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + mode, 0, 0, NULL); + return; } - asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, - BMCR_RESET | BMCR_ANENABLE); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, + switch (ax772a_data->Event) { + case WAIT_AUTONEG_COMPLETE: + if (jiffies > (ax772a_data->autoneg_start + 5 * HZ)) { + ax772a_data->Event = CHK_CABLE_EXIST; + ax772a_data->TickToExpire = 14; + } + break; + case CHK_CABLE_EXIST: + phy_reg = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, 0x12); + if ((phy_reg != 0x8012) && (phy_reg != 0x8013)) { + ax8817x_mdio_write_le(dev->net, + dev->mii.phy_id, 0x16, 0x4040); + mii_nway_restart(&dev->mii); + ax772a_data->Event = CHK_CABLE_STATUS; + ax772a_data->TickToExpire = 31; + } else if (--ax772a_data->TickToExpire == 0) { + mii_nway_restart(&dev->mii); + ax772a_data->Event = CHK_CABLE_EXIST_AGAIN; + if (PowSave == 0x03){ + ax772a_data->TickToExpire = 47; + } else if (PowSave == 0x01) { + ax772a_data->DlyIndex = (u8)(jiffies % 6); + ax772a_data->DlySel = 0; + ax772a_data->TickToExpire = + ChkCntSel[ax772a_data->DlyIndex][ax772a_data->DlySel]; + } + } + break; + case CHK_CABLE_EXIST_AGAIN: + /* if cable disconnected */ + phy_reg = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, 0x12); + if ((phy_reg != 0x8012) && (phy_reg != 0x8013)) { + mii_nway_restart(&dev->mii); + ax772a_data->Event = CHK_CABLE_STATUS; + ax772a_data->TickToExpire = 31; + } else if (--ax772a_data->TickToExpire == 0) { + /* Power down PHY */ + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, + AX_SWRESET_IPPD, + 0, 0, NULL); + ax772a_data->Event = PHY_POWER_DOWN; + if (PowSave == 0x03){ + ax772a_data->TickToExpire = 23; + } else if (PowSave == 0x01) { + ax772a_data->TickToExpire = 31; + } + } + break; + case PHY_POWER_DOWN: + if (--ax772a_data->TickToExpire == 0) { + ax772a_data->Event = PHY_POWER_UP; + } + break; + case CHK_CABLE_STATUS: + if (--ax772a_data->TickToExpire == 0) { + ax8817x_mdio_write_le(dev->net, + dev->mii.phy_id, 0x16, 0x4040); + mii_nway_restart(&dev->mii); + ax772a_data->Event = CHK_CABLE_EXIST_AGAIN; + if (PowSave == 0x03){ + ax772a_data->TickToExpire = 47; + } else if (PowSave == 0x01) { + ax772a_data->DlyIndex = (u8)(jiffies % 6); + ax772a_data->DlySel = 0; + ax772a_data->TickToExpire = + ChkCntSel[ax772a_data->DlyIndex][ax772a_data->DlySel]; + } + } + break; + case PHY_POWER_UP: + + ax88772a_phy_powerup (dev); + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); - asix_mdio_write(dev->net, dev->mii.phy_id, MII_CTRL1000, - ADVERTISE_1000FULL); - mii_nway_restart(&dev->mii); + mii_nway_restart(&dev->mii); + + ax772a_data->Event = CHK_CABLE_EXIST_AGAIN; + + if (PowSave == 0x03){ + ax772a_data->TickToExpire = 47; + + } else if (PowSave == 0x01) { + + if (++ax772a_data->DlySel >= 3) { + ax772a_data->DlyIndex = (u8)(jiffies % 6); + ax772a_data->DlySel = 0; + } + ax772a_data->TickToExpire = + ChkCntSel[ax772a_data->DlyIndex][ax772a_data->DlySel]; + } + break; + default: + break; + } - if ((ret = asix_write_medium_mode(dev, AX88178_MEDIUM_DEFAULT)) < 0) - goto out; + return; +} - if ((ret = asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL)) < 0) - goto out; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void ax88772b_link_reset (void *data) +{ + struct usbnet *dev = (struct usbnet *)data; + struct ax88772b_data *ax772b_data = (struct ax88772b_data *)dev->priv; +#else +static void ax88772b_link_reset (struct work_struct *work) +{ + struct ax88772b_data *ax772b_data = container_of (work, + struct ax88772b_data, check_link); + struct usbnet *dev = ax772b_data->dev; +#endif + + switch (ax772b_data->Event) { + + case AX_SET_RX_CFG: + { + u16 bmcr = ax8817x_mdio_read_le(dev->net, + dev->mii.phy_id, MII_BMCR); + u16 mode = AX88772_MEDIUM_DEFAULT; + + if (!(bmcr & BMCR_FULLDPLX)) + mode &= ~AX88772_MEDIUM_FULL_DUPLEX; + if (!(bmcr & BMCR_SPEED100)) + mode &= ~AX88772_MEDIUM_100MB; + + ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + mode, 0, 0, NULL); + break; + } + case PHY_POWER_UP: + { + u16 tmp16; - /* Asix framing packs multiple eth frames into a 2K usb bulk transfer */ - if (dev->driver_info->flags & FLAG_FRAMING_AX) { - /* hard_mtu is still the default - the device does not support - jumbo eth frames */ - dev->rx_urb_size = 2048; + ax88772a_phy_powerup (dev); + tmp16 = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, 0x12); + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, 0x12, + ((tmp16 & 0xFF9F) | 0x0040)); + + ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); + break; + } + case AX_CHK_AUTODETACH: + { + u16 tmp16; + int ret; + void *buf; + buf = kmalloc(6, GFP_KERNEL); + devwarn(dev, "EVENT: AX_CHK_AUTODETACH\n"); + /* Get the EEPROM data*/ + if ((ret = ax8817x_read_cmd (dev, AX_CMD_READ_EEPROM, + 0x18, 0, 2, (void *)(&tmp16))) < 0) { + deverr(dev, "read SROM address 18h failed: %d", ret); + } + else { + ax772b_data->psc = le16_to_cpu(tmp16) & 0xFF00; + devwarn(dev, "EEPROM (0x18) = : %04X", ax772b_data->psc); + if ((ret = ax8817x_write_cmd (dev, AX_CMD_SW_RESET, + AX_SWRESET_IPRL | (ax772b_data->psc & 0x7FFF), 0, 0, buf)) < 0) { + deverr(dev, "Failed to configure PHY power saving: %d", ret); + } + } + break; + } + default: + break; } + + ax772b_data->Event = AX_NOP; + + return; +} + +static int ax88178_set_media(struct usbnet *dev) +{ + int ret; + struct ax88178_data *ax178dataptr = (struct ax88178_data *)dev->priv; + int media; + + media = ax88178_media_check (dev, ax178dataptr); + if (media < 0) + return media; + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, + media, 0, 0, NULL)) < 0) { + deverr(dev, "write mode medium reg failed: %d", ret); + return ret; + } + return 0; +} -out: - return ret; +static int ax88178_link_reset(struct usbnet *dev) +{ + return ax88178_set_media (dev); +} + +static int ax_suspend (struct usb_interface *intf, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,10) + pm_message_t message) +#else + u32 message) +#endif +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + + return data->suspend (intf, message); +} + +static int ax_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + + return data->resume (intf); } +static const struct driver_info ax88178_info = { + .description = "ASIX AX88178 USB 2.0 Ethernet", + .bind = ax88178_bind, + .unbind = ax88178_unbind, + .status = ax88178_status, + .link_reset = ax88178_link_reset, + .reset = ax88178_link_reset, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88772_rx_fixup, + .tx_fixup = ax88772_tx_fixup, +}; + +static const struct driver_info belkin178_info = { + .description = "Belkin Gigabit USB 2.0 Network Adapter", + .bind = ax88178_bind, + .unbind = ax88178_unbind, + .status = ax8817x_status, + .link_reset = ax88178_link_reset, + .reset = ax88178_link_reset, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88772_rx_fixup, + .tx_fixup = ax88772_tx_fixup, +}; + static const struct driver_info ax8817x_info = { .description = "ASIX AX8817x USB 2.0 Ethernet", - .bind = ax88172_bind, - .status = asix_status, + .bind = ax8817x_bind, + .status = ax8817x_status, .link_reset = ax88172_link_reset, .reset = ax88172_link_reset, - .flags = FLAG_ETHER | FLAG_LINK_INTR, - .data = 0x00130103, + .flags = FLAG_ETHER, }; static const struct driver_info dlink_dub_e100_info = { .description = "DLink DUB-E100 USB Ethernet", - .bind = ax88172_bind, - .status = asix_status, + .bind = ax8817x_bind, + .status = ax8817x_status, .link_reset = ax88172_link_reset, .reset = ax88172_link_reset, - .flags = FLAG_ETHER | FLAG_LINK_INTR, - .data = 0x009f9d9f, + .flags = FLAG_ETHER, }; static const struct driver_info netgear_fa120_info = { .description = "Netgear FA-120 USB Ethernet", - .bind = ax88172_bind, - .status = asix_status, + .bind = ax8817x_bind, + .status = ax8817x_status, .link_reset = ax88172_link_reset, .reset = ax88172_link_reset, - .flags = FLAG_ETHER | FLAG_LINK_INTR, - .data = 0x00130103, + .flags = FLAG_ETHER, }; static const struct driver_info hawking_uf200_info = { .description = "Hawking UF200 USB Ethernet", - .bind = ax88172_bind, - .status = asix_status, + .bind = ax8817x_bind, + .status = ax8817x_status, .link_reset = ax88172_link_reset, .reset = ax88172_link_reset, - .flags = FLAG_ETHER | FLAG_LINK_INTR, - .data = 0x001f1d1f, + .flags = FLAG_ETHER, }; static const struct driver_info ax88772_info = { .description = "ASIX AX88772 USB 2.0 Ethernet", .bind = ax88772_bind, - .status = asix_status, - .link_reset = ax88772_link_reset, - .reset = ax88772_link_reset, - .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR, - .rx_fixup = asix_rx_fixup, - .tx_fixup = asix_tx_fixup, + .unbind = ax88772_unbind, + .status = ax88772_status, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88772_rx_fixup, + .tx_fixup = ax88772_tx_fixup, + .stop = ax88772b_stop, + .reset = ax88772b_reset, }; -static const struct driver_info ax88178_info = { - .description = "ASIX AX88178 USB 2.0 Ethernet", - .bind = ax88178_bind, - .status = asix_status, - .link_reset = ax88178_link_reset, - .reset = ax88178_link_reset, - .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR, - .rx_fixup = asix_rx_fixup, - .tx_fixup = asix_tx_fixup, +static const struct driver_info dlink_dub_e100b_info = { + .description = "D-Link DUB-E100 USB 2.0 Fast Ethernet Adapter", + .bind = ax88772_bind, + .unbind = ax88772_unbind, + .status = ax88772_status, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88772_rx_fixup, + .tx_fixup = ax88772_tx_fixup, + .stop = ax88772b_stop, + .reset = ax88772b_reset, +}; + +static const struct driver_info ax88772a_info = { + .description = "ASIX AX88772A USB 2.0 Ethernet", + .bind = ax88772a_bind, + .unbind = ax88772a_unbind, + .status = ax88772a_status, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88772_rx_fixup, + .tx_fixup = ax88772_tx_fixup, + .stop = ax88772b_stop, + .reset = ax88772b_reset, +}; + +static const struct driver_info ax88772b_info = { + .description = "ASIX AX88772B USB 2.0 Ethernet", + .bind = ax88772b_bind, + .unbind = ax88772b_unbind, + .status = ax88772b_status, + .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_HW_IP_ALIGNMENT, + .rx_fixup = ax88772b_rx_fixup, + .tx_fixup = ax88772b_tx_fixup, + .stop = ax88772b_stop, + .reset = ax88772b_reset, }; static const struct usb_device_id products [] = { { + // 88178 + USB_DEVICE (0x0b95, 0x1780), + .driver_info = (unsigned long) &ax88178_info, +}, { + // 88178 for billianton linksys + USB_DEVICE (0x077b, 0x2226), + .driver_info = (unsigned long) &ax88178_info, +}, { + // ABOCOM for linksys + USB_DEVICE (0x1737, 0x0039), + .driver_info = (unsigned long) &ax88178_info, +}, { + // ABOCOM for pci + USB_DEVICE (0x14ea, 0xab11), + .driver_info = (unsigned long) &ax88178_info, +}, { + // Belkin + USB_DEVICE (0x050d, 0x5055), + .driver_info = (unsigned long) &belkin178_info, +}, { // Linksys USB200M USB_DEVICE (0x077b, 0x2226), .driver_info = (unsigned long) &ax8817x_info, @@ -1457,6 +3677,14 @@ static const struct usb_device_id products [] = { USB_DEVICE (0x2001, 0x1a00), .driver_info = (unsigned long) &dlink_dub_e100_info, }, { + // DLink DUB-E100B + USB_DEVICE (0x2001, 0x3c05), + .driver_info = (unsigned long) &dlink_dub_e100b_info, +}, { + // DLink DUB-E100B + USB_DEVICE (0x07d1, 0x3c05), + .driver_info = (unsigned long) &dlink_dub_e100b_info, +}, { // Intellinet, ST Lab USB Ethernet USB_DEVICE (0x0b95, 0x1720), .driver_info = (unsigned long) &ax8817x_info, @@ -1465,9 +3693,9 @@ static const struct usb_device_id products [] = { USB_DEVICE (0x07b8, 0x420a), .driver_info = (unsigned long) &hawking_uf200_info, }, { - // Billionton Systems, USB2AR - USB_DEVICE (0x08dd, 0x90ff), - .driver_info = (unsigned long) &ax8817x_info, + // Billionton Systems, USB2AR + USB_DEVICE (0x08dd, 0x90ff), + .driver_info = (unsigned long) &ax8817x_info, }, { // ATEN UC210T USB_DEVICE (0x0557, 0x2009), @@ -1477,10 +3705,6 @@ static const struct usb_device_id products [] = { USB_DEVICE (0x0411, 0x003d), .driver_info = (unsigned long) &ax8817x_info, }, { - // Buffalo LUA-U2-GT 10/100/1000 - USB_DEVICE (0x0411, 0x006e), - .driver_info = (unsigned long) &ax88178_info, -}, { // Sitecom LN-029 "USB 2.0 10/100 Ethernet adapter" USB_DEVICE (0x6189, 0x182d), .driver_info = (unsigned long) &ax8817x_info, @@ -1497,86 +3721,53 @@ static const struct usb_device_id products [] = { USB_DEVICE (0x1631, 0x6200), .driver_info = (unsigned long) &ax8817x_info, }, { - // JVC MP-PRX1 Port Replicator - USB_DEVICE (0x04f1, 0x3008), - .driver_info = (unsigned long) &ax8817x_info, -}, { - // ASIX AX88772B 10/100 - USB_DEVICE (0x0b95, 0x772b), - .driver_info = (unsigned long) &ax88772_info, -}, { // ASIX AX88772 10/100 - USB_DEVICE (0x0b95, 0x7720), - .driver_info = (unsigned long) &ax88772_info, -}, { - // ASIX AX88178 10/100/1000 - USB_DEVICE (0x0b95, 0x1780), - .driver_info = (unsigned long) &ax88178_info, -}, { - // Logitec LAN-GTJ/U2A - USB_DEVICE (0x0789, 0x0160), - .driver_info = (unsigned long) &ax88178_info, -}, { - // Linksys USB200M Rev 2 - USB_DEVICE (0x13b1, 0x0018), - .driver_info = (unsigned long) &ax88772_info, + USB_DEVICE (0x0b95, 0x7720), + .driver_info = (unsigned long) &ax88772_info, }, { - // 0Q0 cable ethernet - USB_DEVICE (0x1557, 0x7720), - .driver_info = (unsigned long) &ax88772_info, -}, { - // DLink DUB-E100 H/W Ver B1 - USB_DEVICE (0x07d1, 0x3c05), - .driver_info = (unsigned long) &ax88772_info, -}, { - // DLink DUB-E100 H/W Ver B1 Alternate - USB_DEVICE (0x2001, 0x3c05), - .driver_info = (unsigned long) &ax88772_info, -}, { - // Linksys USB1000 - USB_DEVICE (0x1737, 0x0039), - .driver_info = (unsigned long) &ax88178_info, + // ASIX AX88772 10/100 + USB_DEVICE (0x125E, 0x180D), + .driver_info = (unsigned long) &ax88772_info, }, { - // IO-DATA ETG-US2 - USB_DEVICE (0x04bb, 0x0930), - .driver_info = (unsigned long) &ax88178_info, + // ASIX AX88772A 10/100 + USB_DEVICE (0x0b95, 0x772A), + .driver_info = (unsigned long) &ax88772a_info, }, { - // Belkin F5D5055 - USB_DEVICE(0x050d, 0x5055), - .driver_info = (unsigned long) &ax88178_info, + // ASIX AX88772A 10/100 + USB_DEVICE (0x0db0, 0xA877), + .driver_info = (unsigned long) &ax88772a_info, }, { - // Apple USB Ethernet Adapter - USB_DEVICE(0x05ac, 0x1402), - .driver_info = (unsigned long) &ax88772_info, + // ASIX AX88772A 10/100 + USB_DEVICE (0x0421, 0x772A), + .driver_info = (unsigned long) &ax88772a_info, }, { - // Cables-to-Go USB Ethernet Adapter - USB_DEVICE(0x0b95, 0x772a), - .driver_info = (unsigned long) &ax88772_info, + // Linksys 200M + USB_DEVICE (0x13B1, 0x0018), + .driver_info = (unsigned long) &ax88772a_info, }, { - // ABOCOM for pci - USB_DEVICE(0x14ea, 0xab11), - .driver_info = (unsigned long) &ax88178_info, + USB_DEVICE (0x05ac, 0x1402), + .driver_info = (unsigned long) &ax88772a_info, }, { - // ASIX 88772a - USB_DEVICE(0x0db0, 0xa877), - .driver_info = (unsigned long) &ax88772_info, + // ASIX AX88772B 10/100 + USB_DEVICE (0x0b95, 0x772B), + .driver_info = (unsigned long) &ax88772b_info, }, { - // Asus USB Ethernet Adapter - USB_DEVICE (0x0b95, 0x7e2b), - .driver_info = (unsigned long) &ax88772_info, + // ASIX AX88772B 10/100 + USB_DEVICE (0x0b95, 0x7E2B), + .driver_info = (unsigned long) &ax88772b_info, }, { }, // END }; MODULE_DEVICE_TABLE(usb, products); static struct usb_driver asix_driver = { +// .owner = THIS_MODULE, .name = "asix", .id_table = products, - .probe = usbnet_probe, - .suspend = usbnet_suspend, - .resume = usbnet_resume, - .disconnect = usbnet_disconnect, - .supports_autosuspend = 1, + .probe = axusbnet_probe, + .suspend = ax_suspend, + .resume = ax_resume, + .disconnect = axusbnet_disconnect, }; static int __init asix_init(void) diff --git a/drivers/net/usb/asix.h b/drivers/net/usb/asix.h new file mode 100644 index 000000000000..ea51eb7001e2 --- /dev/null +++ b/drivers/net/usb/asix.h @@ -0,0 +1,558 @@ +#ifndef __LINUX_USBNET_ASIX_H +#define __LINUX_USBNET_ASIX_H + +/* + * Turn on this flag if the implementation of your USB host controller + * cannot handle non-double word aligned buffer. + * When turn on this flag, driver will fixup egress packet aligned on double + * word boundary before deliver to USB host controller. And will Disable the + * function "skb_reserve (skb, NET_IP_ALIGN)" to retain the buffer aligned on + * double word alignment for ingress packets. + */ +#define AX_FORCE_BUFF_ALIGN 0 + +#define AX_MONITOR_MODE 0x01 +#define AX_MONITOR_LINK 0x02 +#define AX_MONITOR_MAGIC 0x04 +#define AX_MONITOR_HSFS 0x10 + +/* AX88172 Medium Status Register values */ +#define AX_MEDIUM_FULL_DUPLEX 0x02 +#define AX_MEDIUM_TX_ABORT_ALLOW 0x04 +#define AX_MEDIUM_FLOW_CONTROL_EN 0x10 +#define AX_MCAST_FILTER_SIZE 8 +#define AX_MAX_MCAST 64 + +#define AX_EEPROM_LEN 0x40 + +#define AX_SWRESET_CLEAR 0x00 +#define AX_SWRESET_RR 0x01 +#define AX_SWRESET_RT 0x02 +#define AX_SWRESET_PRTE 0x04 +#define AX_SWRESET_PRL 0x08 +#define AX_SWRESET_BZ 0x10 +#define AX_SWRESET_IPRL 0x20 +#define AX_SWRESET_IPPD 0x40 +#define AX_SWRESET_IPOSC 0x0080 +#define AX_SWRESET_IPPSL_0 0x0100 +#define AX_SWRESET_IPPSL_1 0x0200 +#define AX_SWRESET_IPCOPS 0x0400 +#define AX_SWRESET_IPCOPSC 0x0800 +#define AX_SWRESET_AUTODETACH 0x1000 +#define AX_SWRESET_WOLLP 0x8000 + +#define AX88772_IPG0_DEFAULT 0x15 +#define AX88772_IPG1_DEFAULT 0x0c +#define AX88772_IPG2_DEFAULT 0x0E + +#define AX88772A_IPG0_DEFAULT 0x15 +#define AX88772A_IPG1_DEFAULT 0x16 +#define AX88772A_IPG2_DEFAULT 0x1A + +#define AX88772_MEDIUM_FULL_DUPLEX 0x0002 +#define AX88772_MEDIUM_RESERVED 0x0004 +#define AX88772_MEDIUM_RX_FC_ENABLE 0x0010 +#define AX88772_MEDIUM_TX_FC_ENABLE 0x0020 +#define AX88772_MEDIUM_PAUSE_FORMAT 0x0080 +#define AX88772_MEDIUM_RX_ENABLE 0x0100 +#define AX88772_MEDIUM_100MB 0x0200 +#define AX88772_MEDIUM_DEFAULT \ + (AX88772_MEDIUM_FULL_DUPLEX | AX88772_MEDIUM_RX_FC_ENABLE | \ + AX88772_MEDIUM_TX_FC_ENABLE | AX88772_MEDIUM_100MB | \ + AX88772_MEDIUM_RESERVED | AX88772_MEDIUM_RX_ENABLE ) + +#define AX_CMD_SET_SW_MII 0x06 +#define AX_CMD_READ_MII_REG 0x07 +#define AX_CMD_WRITE_MII_REG 0x08 +#define AX_CMD_SET_HW_MII 0x0a +#define AX_CMD_READ_EEPROM 0x0b +#define AX_CMD_WRITE_EEPROM 0x0c +#define AX_CMD_WRITE_EEPROM_EN 0x0d +#define AX_CMD_WRITE_EEPROM_DIS 0x0e +#define AX_CMD_WRITE_RX_CTL 0x10 +#define AX_CMD_READ_IPG012 0x11 +#define AX_CMD_WRITE_IPG0 0x12 +#define AX_CMD_WRITE_IPG1 0x13 +#define AX_CMD_WRITE_IPG2 0x14 +#define AX_CMD_WRITE_MULTI_FILTER 0x16 +#define AX_CMD_READ_NODE_ID 0x17 +#define AX_CMD_READ_PHY_ID 0x19 +#define AX_CMD_READ_MEDIUM_MODE 0x1a +#define AX_CMD_WRITE_MEDIUM_MODE 0x1b +#define AX_CMD_READ_MONITOR_MODE 0x1c +#define AX_CMD_WRITE_MONITOR_MODE 0x1d +#define AX_CMD_WRITE_GPIOS 0x1f +#define AX_CMD_SW_RESET 0x20 +#define AX_CMD_SW_PHY_STATUS 0x21 +#define AX_CMD_SW_PHY_SELECT 0x22 + #define AX_PHYSEL_PSEL (1 << 0) + #define AX_PHYSEL_ASEL (1 << 1) + #define AX_PHYSEL_SSMII (0 << 2) + #define AX_PHYSEL_SSRMII (1 << 2) + #define AX_PHYSEL_SSRRMII (3 << 2) + #define AX_PHYSEL_SSEN (1 << 4) +#define AX88772_CMD_READ_NODE_ID 0x13 +#define AX88772_CMD_WRITE_NODE_ID 0x14 +#define AX_CMD_READ_RXCOE_CTL 0x2b +#define AX_CMD_WRITE_RXCOE_CTL 0x2c +#define AX_CMD_READ_TXCOE_CTL 0x2d +#define AX_CMD_WRITE_TXCOE_CTL 0x2e + +#define REG_LENGTH 2 +#define PHY_ID_MASK 0x1f + +#define AX_RXCOE_IPCE 0x0001 +#define AX_RXCOE_IPVE 0x0002 +#define AX_RXCOE_V6VE 0x0004 +#define AX_RXCOE_TCPE 0x0008 +#define AX_RXCOE_UDPE 0x0010 +#define AX_RXCOE_ICMP 0x0020 +#define AX_RXCOE_IGMP 0x0040 +#define AX_RXCOE_ICV6 0x0080 +#define AX_RXCOE_TCPV6 0x0100 +#define AX_RXCOE_UDPV6 0x0200 +#define AX_RXCOE_ICMV6 0x0400 +#define AX_RXCOE_IGMV6 0x0800 +#define AX_RXCOE_ICV6V6 0x1000 +#define AX_RXCOE_FOPC 0x8000 +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22) +#define AX_RXCOE_DEF_CSUM (AX_RXCOE_IPCE | AX_RXCOE_IPVE | \ + AX_RXCOE_V6VE | AX_RXCOE_TCPE | \ + AX_RXCOE_UDPE | AX_RXCOE_ICV6 | \ + AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6) +#else +#define AX_RXCOE_DEF_CSUM (AX_RXCOE_IPCE | AX_RXCOE_IPVE | \ + AX_RXCOE_TCPE | AX_RXCOE_UDPE) +#endif + +#define AX_RXCOE_64TE 0x0100 +#define AX_RXCOE_PPPOE 0x0200 +#define AX_RXCOE_RPCE 0x8000 + +#define AX_TXCOE_IP 0x0001 +#define AX_TXCOE_TCP 0x0002 +#define AX_TXCOE_UDP 0x0004 +#define AX_TXCOE_ICMP 0x0008 +#define AX_TXCOE_IGMP 0x0010 +#define AX_TXCOE_ICV6 0x0020 + +#define AX_TXCOE_TCPV6 0x0100 +#define AX_TXCOE_UDPV6 0x0200 +#define AX_TXCOE_ICMV6 0x0400 +#define AX_TXCOE_IGMV6 0x0800 +#define AX_TXCOE_ICV6V6 0x1000 +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22) +#define AX_TXCOE_DEF_CSUM (AX_TXCOE_TCP | AX_TXCOE_UDP | \ + AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6) +#else +#define AX_TXCOE_DEF_CSUM (AX_TXCOE_TCP | AX_TXCOE_UDP) +#endif + +#define AX_TXCOE_64TE 0x0001 +#define AX_TXCOE_PPPE 0x0002 + +#define AX88772B_MAX_BULKIN_2K 0 +#define AX88772B_MAX_BULKIN_4K 1 +#define AX88772B_MAX_BULKIN_6K 2 +#define AX88772B_MAX_BULKIN_8K 3 +#define AX88772B_MAX_BULKIN_16K 4 +#define AX88772B_MAX_BULKIN_20K 5 +#define AX88772B_MAX_BULKIN_24K 6 +#define AX88772B_MAX_BULKIN_32K 7 +struct {unsigned short size, byte_cnt,threshold;} AX88772B_BULKIN_SIZE[] = +{ + /* 2k */ + {2048, 0x8000, 0x8001}, + /* 4k */ + {4096, 0x8100, 0x8147}, + /* 6k */ + {6144, 0x8200, 0x81EB}, + /* 8k */ + {8192, 0x8300, 0x83D7}, + /* 16 */ + {16384, 0x8400, 0x851E}, + /* 20k */ + {20480, 0x8500, 0x8666}, + /* 24k */ + {24576, 0x8600, 0x87AE}, + /* 32k */ + {32768, 0x8700, 0x8A3D}, +}; + + +#define AX_RX_CTL_RH1M 0x0100 /* Enable RX-Header mode 0 */ +#define AX_RX_CTL_RH2M 0x0200 /* Enable IP header in receive buffer aligned on 32-bit aligment */ +#define AX_RX_CTL_RH3M 0x0400 /* checksum value in rx header 3 */ +#define AX_RX_HEADER_DEFAULT (AX_RX_CTL_RH1M | AX_RX_CTL_RH2M) + +#define AX_RX_CTL_MFB 0x0300 /* Maximum Frame size 16384bytes */ +#define AX_RX_CTL_START 0x0080 /* Ethernet MAC start */ +#define AX_RX_CTL_AP 0x0020 /* Accept physcial address from Multicast array */ +#define AX_RX_CTL_AM 0x0010 +#define AX_RX_CTL_AB 0x0008 /* Accetp Brocadcast frames*/ +#define AX_RX_CTL_SEP 0x0004 /* Save error packets */ +#define AX_RX_CTL_AMALL 0x0002 /* Accetp all multicast frames */ +#define AX_RX_CTL_PRO 0x0001 /* Promiscuous Mode */ +#define AX_RX_CTL_STOP 0x0000 /* Stop MAC */ + +#define AX_MONITOR_MODE 0x01 +#define AX_MONITOR_LINK 0x02 +#define AX_MONITOR_MAGIC 0x04 +#define AX_MONITOR_HSFS 0x10 + +#define AX_MCAST_FILTER_SIZE 8 +#define AX_MAX_MCAST 64 +#define AX_INTERRUPT_BUFSIZE 8 + +#define AX_EEPROM_LEN 0x40 +#define AX_EEPROM_MAGIC 0xdeadbeef +#define EEPROMMASK 0x7f + +/* GPIO REGISTER */ +#define AXGPIOS_GPO0EN 0X01 // 1 << 0 +#define AXGPIOS_GPO0 0X02 // 1 << 1 +#define AXGPIOS_GPO1EN 0X04 // 1 << 2 +#define AXGPIOS_GPO1 0X08 // 1 << 3 +#define AXGPIOS_GPO2EN 0X10 // 1 << 4 +#define AXGPIOS_GPO2 0X20 // 1 << 5 +#define AXGPIOS_RSE 0X80 // 1 << 7 + +/* TX-header format */ +#define AX_TX_HDR_CPHI 0x4000 +#define AX_TX_HDR_DICF 0x8000 + +// GMII register definitions +#define GMII_PHY_CONTROL 0x00 // control reg +#define GMII_PHY_STATUS 0x01 // status reg +#define GMII_PHY_OUI 0x02 // most of the OUI bits +#define GMII_PHY_MODEL 0x03 // model/rev bits, and rest of OUI +#define GMII_PHY_ANAR 0x04 // AN advertisement reg +#define GMII_PHY_ANLPAR 0x05 // AN Link Partner +#define GMII_PHY_ANER 0x06 // AN expansion reg +#define GMII_PHY_1000BT_CONTROL 0x09 // control reg for 1000BT +#define GMII_PHY_1000BT_STATUS 0x0A // status reg for 1000BT + +// Bit definitions: GMII Control +#define GMII_CONTROL_RESET 0x8000 // reset bit in control reg +#define GMII_CONTROL_LOOPBACK 0x4000 // loopback bit in control reg +#define GMII_CONTROL_10MB 0x0000 // 10 Mbit +#define GMII_CONTROL_100MB 0x2000 // 100Mbit +#define GMII_CONTROL_1000MB 0x0040 // 1000Mbit +#define GMII_CONTROL_SPEED_BITS 0x2040 // speed bit mask +#define GMII_CONTROL_ENABLE_AUTO 0x1000 // autonegotiate enable +#define GMII_CONTROL_POWER_DOWN 0x0800 +#define GMII_CONTROL_ISOLATE 0x0400 // islolate bit +#define GMII_CONTROL_START_AUTO 0x0200 // restart autonegotiate +#define GMII_CONTROL_FULL_DUPLEX 0x0100 + +// Bit definitions: GMII Status +#define GMII_STATUS_100MB_MASK 0xE000 // any of these indicate 100 Mbit +#define GMII_STATUS_10MB_MASK 0x1800 // either of these indicate 10 Mbit +#define GMII_STATUS_AUTO_DONE 0x0020 // auto negotiation complete +#define GMII_STATUS_AUTO 0x0008 // auto negotiation is available +#define GMII_STATUS_LINK_UP 0x0004 // link status bit +#define GMII_STATUS_EXTENDED 0x0001 // extended regs exist +#define GMII_STATUS_100T4 0x8000 // capable of 100BT4 +#define GMII_STATUS_100TXFD 0x4000 // capable of 100BTX full duplex +#define GMII_STATUS_100TX 0x2000 // capable of 100BTX +#define GMII_STATUS_10TFD 0x1000 // capable of 10BT full duplex +#define GMII_STATUS_10T 0x0800 // capable of 10BT + +// Bit definitions: Auto-Negotiation Advertisement +#define GMII_ANAR_ASYM_PAUSE 0x0800 // support asymetric pause +#define GMII_ANAR_PAUSE 0x0400 // support pause packets +#define GMII_ANAR_100T4 0x0200 // support 100BT4 +#define GMII_ANAR_100TXFD 0x0100 // support 100BTX full duplex +#define GMII_ANAR_100TX 0x0080 // support 100BTX half duplex +#define GMII_ANAR_10TFD 0x0040 // support 10BT full duplex +#define GMII_ANAR_10T 0x0020 // support 10BT half duplex +#define GMII_SELECTOR_FIELD 0x001F // selector field. + +// Bit definitions: Auto-Negotiation Link Partner Ability +#define GMII_ANLPAR_100T4 0x0200 // support 100BT4 +#define GMII_ANLPAR_100TXFD 0x0100 // support 100BTX full duplex +#define GMII_ANLPAR_100TX 0x0080 // support 100BTX half duplex +#define GMII_ANLPAR_10TFD 0x0040 // support 10BT full duplex +#define GMII_ANLPAR_10T 0x0020 // support 10BT half duplex +#define GMII_ANLPAR_PAUSE 0x0400 // support pause packets +#define GMII_ANLPAR_ASYM_PAUSE 0x0800 // support asymetric pause +#define GMII_ANLPAR_ACK 0x4000 // means LCB was successfully rx'd +#define GMII_SELECTOR_8023 0x0001; + +// Bit definitions: 1000BaseT AUX Control +#define GMII_1000_AUX_CTRL_MASTER_SLAVE 0x1000 +#define GMII_1000_AUX_CTRL_FD_CAPABLE 0x0200 // full duplex capable +#define GMII_1000_AUX_CTRL_HD_CAPABLE 0x0100 // half duplex capable + +// Bit definitions: 1000BaseT AUX Status +#define GMII_1000_AUX_STATUS_FD_CAPABLE 0x0800 // full duplex capable +#define GMII_1000_AUX_STATUS_HD_CAPABLE 0x0400 // half duplex capable + +// Cicada MII Registers +#define GMII_AUX_CTRL_STATUS 0x1C +#define GMII_AUX_ANEG_CPLT 0x8000 +#define GMII_AUX_FDX 0x0020 +#define GMII_AUX_SPEED_1000 0x0010 +#define GMII_AUX_SPEED_100 0x0008 + +#ifndef ADVERTISE_PAUSE_CAP +#define ADVERTISE_PAUSE_CAP 0x0400 +#endif + +#ifndef MII_STAT1000 +#define MII_STAT1000 0x000A +#endif + +#ifndef LPA_1000FULL +#define LPA_1000FULL 0x0800 +#endif + +// medium mode register +#define MEDIUM_GIGA_MODE 0x0001 +#define MEDIUM_FULL_DUPLEX_MODE 0x0002 +#define MEDIUM_TX_ABORT_MODE 0x0004 +#define MEDIUM_ENABLE_125MHZ 0x0008 +#define MEDIUM_ENABLE_RX_FLOWCTRL 0x0010 +#define MEDIUM_ENABLE_TX_FLOWCTRL 0x0020 +#define MEDIUM_ENABLE_JUMBO_FRAME 0x0040 +#define MEDIUM_CHECK_PAUSE_FRAME_MODE 0x0080 +#define MEDIUM_ENABLE_RECEIVE 0x0100 +#define MEDIUM_MII_100M_MODE 0x0200 +#define MEDIUM_ENABLE_JAM_PATTERN 0x0400 +#define MEDIUM_ENABLE_STOP_BACKPRESSURE 0x0800 +#define MEDIUM_ENABLE_SUPPER_MAC_SUPPORT 0x1000 + +/* PHY mode */ +#define PHY_MODE_MARVELL 0 +#define PHY_MODE_CICADA_FAMILY 1 +#define PHY_MODE_CICADA_V1 1 +#define PHY_MODE_AGERE_FAMILY 2 +#define PHY_MODE_AGERE_V0 2 +#define PHY_MODE_CICADA_V2 5 +#define PHY_MODE_AGERE_V0_GMII 6 +#define PHY_MODE_CICADA_V2_ASIX 9 +#define PHY_MODE_VSC8601 10 +#define PHY_MODE_RTL8211CL 12 +#define PHY_MODE_RTL8211BN 13 +#define PHY_MODE_RTL8251CL 14 +#define PHY_MODE_ATTANSIC_V0 0x40 +#define PHY_MODE_ATTANSIC_FAMILY 0x40 +#define PHY_MODE_MAC_TO_MAC_GMII 0x7C + +/* */ +#define LED_MODE_MARVELL 0 +#define LED_MODE_CAMEO 1 + +#define MARVELL_LED_CTRL 0x18 +#define MARVELL_MANUAL_LED 0x19 + +#define PHY_IDENTIFIER 0x0002 +#define PHY_AGERE_IDENTIFIER 0x0282 +#define PHY_CICADA_IDENTIFIER 0x000f +#define PHY_MARVELL_IDENTIFIER 0x0141 + +#define PHY_MARVELL_STATUS 0x001b +#define MARVELL_STATUS_HWCFG 0x0004 /* SGMII without clock */ + +#define PHY_MARVELL_CTRL 0x0014 +#define MARVELL_CTRL_RXDELAY 0x0080 +#define MARVELL_CTRL_TXDELAY 0x0002 + +#define PHY_CICADA_EXTPAGE 0x001f +#define CICADA_EXTPAGE_EN 0x0001 +#define CICADA_EXTPAGE_DIS 0x0000 + + +struct {unsigned short value, offset; } CICADA_FAMILY_HWINIT[] = +{ + {0x0001, 0x001f}, {0x1c25, 0x0017}, {0x2a30, 0x001f}, {0x234c, 0x0010}, + {0x2a30, 0x001f}, {0x0212, 0x0008}, {0x52b5, 0x001f}, {0xa7fa, 0x0000}, + {0x0012, 0x0002}, {0x3002, 0x0001}, {0x87fa, 0x0000}, {0x52b5, 0x001f}, + {0xafac, 0x0000}, {0x000d, 0x0002}, {0x001c, 0x0001}, {0x8fac, 0x0000}, + {0x2a30, 0x001f}, {0x0012, 0x0008}, {0x2a30, 0x001f}, {0x0400, 0x0014}, + {0x2a30, 0x001f}, {0x0212, 0x0008}, {0x52b5, 0x001f}, {0xa760, 0x0000}, + {0x0000, 0x0002}, {0xfaff, 0x0001}, {0x8760, 0x0000}, {0x52b5, 0x001f}, + {0xa760, 0x0000}, {0x0000, 0x0002}, {0xfaff, 0x0001}, {0x8760, 0x0000}, + {0x52b5, 0x001f}, {0xafae, 0x0000}, {0x0004, 0x0002}, {0x0671, 0x0001}, + {0x8fae, 0x0000}, {0x2a30, 0x001f}, {0x0012, 0x0008}, {0x0000, 0x001f}, +}; + +struct {unsigned short value, offset; } CICADA_V2_HWINIT[] = +{ + {0x2a30, 0x001f}, {0x0212, 0x0008}, {0x52b5, 0x001f}, {0x000f, 0x0002}, + {0x472a, 0x0001}, {0x8fa4, 0x0000}, {0x2a30, 0x001f}, {0x0212, 0x0008}, + {0x0000, 0x001f}, +}; + +struct {unsigned short value, offset; } CICADA_V2_ASIX_HWINIT[] = +{ + {0x2a30, 0x001f}, {0x0212, 0x0008}, {0x52b5, 0x001f}, {0x0012, 0x0002}, + {0x3002, 0x0001}, {0x87fa, 0x0000}, {0x52b5, 0x001f}, {0x000f, 0x0002}, + {0x472a, 0x0001}, {0x8fa4, 0x0000}, {0x2a30, 0x001f}, {0x0212, 0x0008}, + {0x0000, 0x001f}, +}; + +struct {unsigned short value, offset; } AGERE_FAMILY_HWINIT[] = +{ + {0x0800, 0x0000}, {0x0007, 0x0012}, {0x8805, 0x0010}, {0xb03e, 0x0011}, + {0x8808, 0x0010}, {0xe110, 0x0011}, {0x8806, 0x0010}, {0xb03e, 0x0011}, + {0x8807, 0x0010}, {0xff00, 0x0011}, {0x880e, 0x0010}, {0xb4d3, 0x0011}, + {0x880f, 0x0010}, {0xb4d3, 0x0011}, {0x8810, 0x0010}, {0xb4d3, 0x0011}, + {0x8817, 0x0010}, {0x1c00, 0x0011}, {0x300d, 0x0010}, {0x0001, 0x0011}, + {0x0002, 0x0012}, +}; + +struct ax88178_data { + u16 EepromData; + u16 MediaLink; + int UseGpio0; + int UseRgmii; + u8 PhyMode; + u8 LedMode; + u8 BuffaloOld; +}; + +enum watchdog_state { + AX_NOP = 0, + CHK_LINK, /* Routine A */ + CHK_CABLE_EXIST, /* Called by A */ + CHK_CABLE_EXIST_AGAIN, /* Routine B */ + PHY_POWER_UP, /* Called by B */ + PHY_POWER_UP_BH, + PHY_POWER_DOWN, + CHK_CABLE_STATUS, /* Routine C */ + WAIT_AUTONEG_COMPLETE, + AX_SET_RX_CFG, + AX_CHK_AUTODETACH, +}; + +struct ax88772b_data { + struct usbnet *dev; + struct workqueue_struct *ax_work; + struct work_struct check_link; + unsigned long time_to_chk; + u16 psc; + u8 pw_enabled; + u8 Event; + u8 checksum; + u8 PhySelect:1; + u8 OperationMode:1; + +}; + +// define for MAC or PHY mode +#define OPERATION_MAC_MODE 0 +#define OPERATION_PHY_MODE 1 + +struct ax88772a_data { + struct usbnet *dev; + struct workqueue_struct *ax_work; + struct work_struct check_link; + unsigned long autoneg_start; +#define AX88772B_WATCHDOG (6 * HZ) + u8 Event; + u8 TickToExpire; + u8 DlyIndex; + u8 DlySel; + u16 EepromData; +}; + +struct ax88772_data { + struct usbnet *dev; + struct workqueue_struct *ax_work; + struct work_struct check_link; + unsigned long autoneg_start; + u8 Event; + u8 TickToExpire; +}; + +#define AX_RX_CHECKSUM 1 +#define AX_TX_CHECKSUM 2 + +/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */ +struct ax8817x_data { + u8 multi_filter[AX_MCAST_FILTER_SIZE]; + int (*resume) (struct usb_interface *intf); + int (*suspend) (struct usb_interface *intf, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,10) + pm_message_t message); +#else + u32 message); +#endif +}; + +struct ax88172_int_data { + u16 res1; +#define AX_INT_PPLS_LINK (1 << 0) +#define AX_INT_SPLS_LINK (1 << 1) +#define AX_INT_CABOFF_UNPLUG (1 << 7) + u8 link; + u16 res2; + u8 status; + u16 res3; +} __attribute__ ((packed)); + +#define AX_RXHDR_L4_ERR (1 << 8) +#define AX_RXHDR_L3_ERR (1 << 9) + +#define AX_RXHDR_L4_TYPE_UDP 1 +#define AX_RXHDR_L4_TYPE_ICMP 2 +#define AX_RXHDR_L4_TYPE_IGMP 3 +#define AX_RXHDR_L4_TYPE_TCP 4 +#define AX_RXHDR_L4_TYPE_TCMPV6 5 +#define AX_RXHDR_L4_TYPE_MASK 7 + +#define AX_RXHDR_L3_TYPE_IP 1 +#define AX_RXHDR_L3_TYPE_IPV6 2 + +struct ax88772b_rx_header { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 len:11, + res1:1, + crc:1, + mii:1, + runt:1, + mc_bc:1; + + u16 len_bar:11, + res2:5; + + u8 vlan_ind:3, + vlan_tag_striped:1, + pri:3, + res3:1; + + u8 l4_csum_err:1, + l3_csum_err:1, + l4_type:3, + l3_type:2, + ce:1; +#elif defined (__BIG_ENDIAN_BITFIELD) + u16 mc_bc:1, + runt:1, + mii:1, + crc:1, + res1:1, + len:11; + + u16 res2:5, + len_bar:11; + + u8 res3:1, + pri:3, + vlan_tag_striped:1, + vlan_ind:3; + + u8 ce:1, + l3_type:2, + l4_type:3, + l3_csum_err:1, + l4_csum_err:1; +#else +#error "Please fix <asm/byteorder.h>" +#endif + +} __attribute__ ((packed)); + +#endif /* __LINUX_USBNET_ASIX_H */ + diff --git a/drivers/net/usb/axusbnet.c b/drivers/net/usb/axusbnet.c new file mode 100644 index 000000000000..00393d38a379 --- /dev/null +++ b/drivers/net/usb/axusbnet.c @@ -0,0 +1,1374 @@ +/* + * USB Network driver infrastructure + * Copyright (C) 2000-2005 by David Brownell + * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This is a generic "USB networking" framework that works with several + * kinds of full and high speed networking devices: host-to-host cables, + * smart usb peripherals, and actual Ethernet adapters. + * + * These devices usually differ in terms of control protocols (if they + * even have one!) and sometimes they define new framing to wrap or batch + * Ethernet packets. Otherwise, they talk to USB pretty much the same, + * so interface (un)binding, endpoint I/O queues, fault handling, and other + * issues can usefully be addressed by this framework. + */ + +#define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ctype.h> +#include <linux/ethtool.h> +#include <linux/workqueue.h> +#include <linux/mii.h> +#include <linux/usb.h> +/*#include <linux/usb/usbnet.h>*/ + +#include "asix.h" +#include "axusbnet.h" + +#define DRIVER_VERSION "22-Aug-2005" + +static void axusbnet_unlink_rx_urbs(struct usbnet *); + +extern void +ax8817x_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data); + +/*-------------------------------------------------------------------------*/ + +/* + * Nineteen USB 1.1 max size bulk transactions per frame (ms), max. + * Several dozen bytes of IPv4 data can fit in two such transactions. + * One maximum size Ethernet packet takes twenty four of them. + * For high speed, each frame comfortably fits almost 36 max size + * Ethernet packets (so queues should be bigger). + * + * REVISIT qlens should be members of 'struct usbnet'; the goal is to + * let the USB host controller be busy for 5msec or more before an irq + * is required, under load. Jumbograms change the equation. + */ +#define RX_MAX_QUEUE_MEMORY (60 * 1518) +#define RX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \ + (RX_MAX_QUEUE_MEMORY/(dev)->rx_urb_size) : 4) +#define TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \ + (RX_MAX_QUEUE_MEMORY/(dev)->hard_mtu) : 4) + +// reawaken network queue this soon after stopping; else watchdog barks +//#define TX_TIMEOUT_JIFFIES (5*HZ) +#define TX_TIMEOUT_JIFFIES (30*HZ) + +// throttle rx/tx briefly after some faults, so khubd might disconnect() +// us (it polls at HZ/4 usually) before we report too many false errors. +#define THROTTLE_JIFFIES (HZ/8) + +// between wakeups +#define UNLINK_TIMEOUT_MS 3 + +/*-------------------------------------------------------------------------*/ + +static const char driver_name [] = "axusbnet"; + +/* use ethtool to change the level for any given device */ +static int msg_level = -1; +module_param (msg_level, int, 0); +MODULE_PARM_DESC (msg_level, "Override default message level"); + +/*-------------------------------------------------------------------------*/ + +/* handles CDC Ethernet and many other network "bulk data" interfaces */ +static +int axusbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt = NULL; + struct usb_host_endpoint *in = NULL, *out = NULL; + struct usb_host_endpoint *status = NULL; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = status = NULL; + alt = intf->altsetting + tmp; + + /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; + * ignore other endpoints and altsetttings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int intr = 0; + + e = alt->endpoint + ep; + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!(e->desc.bEndpointAddress & USB_DIR_IN)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: + continue; + } + if (e->desc.bEndpointAddress & USB_DIR_IN) { + if (!intr && !in) + in = e; + else if (intr && !status) + status = e; + } else { + if (!out) + out = e; + } + } + if (in && out) + break; + } + if (!alt || !in || !out) + return -EINVAL; + + if (alt->desc.bAlternateSetting != 0 + || !(dev->driver_info->flags & FLAG_NO_SETINT)) { + tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + return tmp; + } + + dev->in = usb_rcvbulkpipe (dev->udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out = usb_sndbulkpipe (dev->udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->status = status; + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void intr_complete (struct urb *urb, struct pt_regs *regs); +#else +static void intr_complete (struct urb *urb); +#endif + +static int init_status (struct usbnet *dev, struct usb_interface *intf) +{ + char *buf = NULL; + unsigned pipe = 0; + unsigned maxp; + unsigned period; + + if (!dev->driver_info->status) + return 0; + + pipe = usb_rcvintpipe (dev->udev, + dev->status->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + maxp = usb_maxpacket (dev->udev, pipe, 0); + + /* avoid 1 msec chatter: min 8 msec poll rate */ + period = max ((int) dev->status->desc.bInterval, + (dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3); + + buf = kmalloc (maxp, GFP_KERNEL); + if (buf) { + dev->interrupt = usb_alloc_urb (0, GFP_KERNEL); + if (!dev->interrupt) { + kfree (buf); + return -ENOMEM; + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + dev->interrupt->transfer_flags |= URB_ASYNC_UNLINK; +#endif + usb_fill_int_urb(dev->interrupt, dev->udev, pipe, + buf, maxp, intr_complete, dev, period); + devdbg(dev, + "status ep%din, %d bytes period %d", + usb_pipeendpoint(pipe), maxp, period); + } + } + return 0; +} + +/* Passes this packet up the stack, updating its accounting. + * Some link protocols batch packets, so their rx_fixup paths + * can return clones as well as just modify the original skb. + */ +static +void axusbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) +{ + int status; + + skb->dev = dev->net; + skb->protocol = eth_type_trans (skb, dev->net); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + + if (netif_msg_rx_status (dev)) + devdbg (dev, "< rx, len %zu, type 0x%x", + skb->len + sizeof (struct ethhdr), skb->protocol); + memset (skb->cb, 0, sizeof (struct skb_data)); + status = netif_rx (skb); + if (status != NET_RX_SUCCESS && netif_msg_rx_err (dev)) + devdbg (dev, "netif_rx status %d", status); +} + +/*------------------------------------------------------------------------- + * + * Network Device Driver (peer link to "Host Device", from USB host) + * + *-------------------------------------------------------------------------*/ + +static +int axusbnet_change_mtu (struct net_device *net, int new_mtu) +{ + struct usbnet *dev = netdev_priv(net); + int ll_mtu = new_mtu + net->hard_header_len; + int old_hard_mtu = dev->hard_mtu; + int old_rx_urb_size = dev->rx_urb_size; + + if (new_mtu <= 0) + return -EINVAL; + // no second zero-length packet read wanted after mtu-sized packets + if ((ll_mtu % dev->maxpacket) == 0) + return -EDOM; + net->mtu = new_mtu; + + dev->hard_mtu = net->mtu + net->hard_header_len; + if (dev->rx_urb_size == old_hard_mtu) { + dev->rx_urb_size = dev->hard_mtu; + if (dev->rx_urb_size > old_rx_urb_size) + axusbnet_unlink_rx_urbs(dev); + } + + return 0; +} + +static struct net_device_stats *axusbnet_get_stats (struct net_device *net) +{ + struct usbnet *dev = netdev_priv (net); + return &dev->stats; +} + +/*-------------------------------------------------------------------------*/ + +/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from + * completion callbacks. 2.5 should have fixed those bugs... + */ + +static void +defer_bh(struct usbnet *dev, struct sk_buff *skb, struct sk_buff_head *list) +{ + unsigned long flags; + + spin_lock_irqsave(&list->lock, flags); + __skb_unlink(skb, list); + spin_unlock(&list->lock); + spin_lock(&dev->done.lock); + __skb_queue_tail(&dev->done, skb); + if (dev->done.qlen == 1) + tasklet_schedule(&dev->bh); + spin_unlock_irqrestore(&dev->done.lock, flags); +} + +/* some work can't be done in tasklets, so we use keventd + * + * NOTE: annoying asymmetry: if it's active, schedule_work() fails, + * but tasklet_schedule() doesn't. hope the failure is rare. + */ +static +void axusbnet_defer_kevent (struct usbnet *dev, int work) +{ + set_bit (work, &dev->flags); + if (!schedule_work (&dev->kevent)) + deverr (dev, "kevent %d may have been dropped", work); + else + devdbg (dev, "kevent %d scheduled", work); +} + +/*-------------------------------------------------------------------------*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void rx_complete (struct urb *urb, struct pt_regs *regs); +#else +static void rx_complete (struct urb *urb); +#endif + +static void rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags) +{ + struct sk_buff *skb; + struct skb_data *entry; + int retval = 0; + unsigned long lockflags; + size_t size = dev->rx_urb_size; + struct driver_info *info = dev->driver_info; + u8 align; + +#if (AX_FORCE_BUFF_ALIGN) + align = 0; +#else + if (!(info->flags & FLAG_HW_IP_ALIGNMENT)) + align = NET_IP_ALIGN; + else + align = 0; +#endif + + if ((skb = alloc_skb (size + align, flags)) == NULL) { + + if (netif_msg_rx_err (dev)) + devdbg (dev, "no rx skb"); + + if((dev->rx_urb_size > 2048) && dev->rx_size) { + dev->rx_size--; + dev->rx_urb_size = AX88772B_BULKIN_SIZE[dev->rx_size].size; + + ax8817x_write_cmd_async (dev, 0x2A, + AX88772B_BULKIN_SIZE[dev->rx_size].byte_cnt, + AX88772B_BULKIN_SIZE[dev->rx_size].threshold, + 0, NULL); + } + + if (!(dev->flags & EVENT_RX_MEMORY)) + axusbnet_defer_kevent (dev, EVENT_RX_MEMORY); + usb_free_urb (urb); + return; + } + + if (align) + skb_reserve (skb, NET_IP_ALIGN); + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->state = rx_start; + entry->length = 0; + + usb_fill_bulk_urb (urb, dev->udev, dev->in, + skb->data, size, rx_complete, skb); + + spin_lock_irqsave (&dev->rxq.lock, lockflags); + + if (netif_running (dev->net) + && netif_device_present (dev->net) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + switch (retval = usb_submit_urb (urb, GFP_ATOMIC)) { + case -EPIPE: + axusbnet_defer_kevent (dev, EVENT_RX_HALT); + break; + case -ENOMEM: + axusbnet_defer_kevent (dev, EVENT_RX_MEMORY); + break; + case -ENODEV: + if (netif_msg_ifdown (dev)) + devdbg (dev, "device gone"); + netif_device_detach (dev->net); + break; + default: + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx submit, %d", retval); + tasklet_schedule (&dev->bh); + break; + case 0: + __skb_queue_tail (&dev->rxq, skb); + } + } else { + if (netif_msg_ifdown (dev)) + devdbg (dev, "rx: stopped"); + retval = -ENOLINK; + } + spin_unlock_irqrestore (&dev->rxq.lock, lockflags); + if (retval) { + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } +} + + +/*-------------------------------------------------------------------------*/ + +static inline void rx_process (struct usbnet *dev, struct sk_buff *skb) +{ + if (dev->driver_info->rx_fixup + && !dev->driver_info->rx_fixup (dev, skb)) + goto error; + // else network stack removes extra byte if we forced a short packet + + if (skb->len) + axusbnet_skb_return (dev, skb); + else { + if (netif_msg_rx_err (dev)) + devdbg (dev, "drop"); +error: + dev->stats.rx_errors++; + skb_queue_tail (&dev->done, skb); + } +} + +/*-------------------------------------------------------------------------*/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void rx_complete (struct urb *urb, struct pt_regs *regs) +#else +static void rx_complete (struct urb *urb) +#endif +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + int urb_status = urb->status; + + skb_put (skb, urb->actual_length); + entry->state = rx_done; + entry->urb = NULL; + + switch (urb_status) { + /* success */ + case 0: + if (skb->len < dev->net->hard_header_len) { + entry->state = rx_cleanup; + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx length %d", skb->len); + } + break; + + /* stalls need manual reset. this is rare ... except that + * when going through USB 2.0 TTs, unplug appears this way. + * we avoid the highspeed version of the ETIMEDOUT/EILSEQ + * storm, recovering as needed. + */ + case -EPIPE: + dev->stats.rx_errors++; + axusbnet_defer_kevent (dev, EVENT_RX_HALT); + // FALLTHROUGH + + /* software-driven interface shutdown */ + case -ECONNRESET: /* async unlink */ + case -ESHUTDOWN: /* hardware gone */ + if (netif_msg_ifdown (dev)) + devdbg (dev, "rx shutdown, code %d", urb_status); + goto block; + + /* we get controller i/o faults during khubd disconnect() delays. + * throttle down resubmits, to avoid log floods; just temporarily, + * so we still recover when the fault isn't a khubd delay. + */ + case -EPROTO: + case -ETIME: + case -EILSEQ: + dev->stats.rx_errors++; + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, jiffies + THROTTLE_JIFFIES); + if (netif_msg_link (dev)) + devdbg (dev, "rx throttle %d", urb_status); + } +block: + entry->state = rx_cleanup; + entry->urb = urb; + urb = NULL; + break; + + /* data overrun ... flush fifo? */ + case -EOVERFLOW: + dev->stats.rx_over_errors++; + // FALLTHROUGH + + default: + entry->state = rx_cleanup; + dev->stats.rx_errors++; + if (netif_msg_rx_err (dev)) + devdbg (dev, "rx status %d", urb_status); + break; + } + + defer_bh(dev, skb, &dev->rxq); + + if (urb) { + if (netif_running (dev->net) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + rx_submit (dev, urb, GFP_ATOMIC); + return; + } + usb_free_urb (urb); + } + if (netif_msg_rx_err (dev)) + devdbg (dev, "no read resubmitted"); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void intr_complete (struct urb *urb, struct pt_regs *regs) +#else +static void intr_complete (struct urb *urb) +#endif +{ + struct usbnet *dev = urb->context; + int status = urb->status; + + switch (status) { + /* success */ + case 0: + dev->driver_info->status(dev, urb); + break; + + /* software-driven interface shutdown */ + case -ENOENT: /* urb killed */ + case -ESHUTDOWN: /* hardware gone */ + if (netif_msg_ifdown (dev)) + devdbg (dev, "intr shutdown, code %d", status); + return; + + /* NOTE: not throttling like RX/TX, since this endpoint + * already polls infrequently + */ + default: + devdbg (dev, "intr status %d", status); + break; + } + + if (!netif_running (dev->net)) + return; + + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status != 0 && netif_msg_timer (dev)) + deverr(dev, "intr resubmit --> %d", status); +} + +/*-------------------------------------------------------------------------*/ + +// unlink pending rx/tx; completion handlers do all other cleanup + +static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) +{ + unsigned long flags; + struct sk_buff *skb, *skbnext; + int count = 0; + + spin_lock_irqsave (&q->lock, flags); + skb_queue_walk_safe(q, skb, skbnext) { + struct skb_data *entry; + struct urb *urb; + int retval; + + entry = (struct skb_data *) skb->cb; + urb = entry->urb; + + // during some PM-driven resume scenarios, + // these (async) unlinks complete immediately + retval = usb_unlink_urb (urb); + if (retval != -EINPROGRESS && retval != 0) + devdbg (dev, "unlink urb err, %d", retval); + else + count++; + } + spin_unlock_irqrestore (&q->lock, flags); + return count; +} + +// Flush all pending rx urbs +// minidrivers may need to do this when the MTU changes + +static +void axusbnet_unlink_rx_urbs(struct usbnet *dev) +{ + if (netif_running(dev->net)) { + (void) unlink_urbs (dev, &dev->rxq); + tasklet_schedule(&dev->bh); + } +} + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +static +int axusbnet_stop (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct driver_info *info = dev->driver_info; + int temp; + int retval; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18) + DECLARE_WAIT_QUEUE_HEAD_ONSTACK (unlink_wakeup); +#else + DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); +#endif + DECLARE_WAITQUEUE (wait, current); + + netif_stop_queue (net); + + if (netif_msg_ifdown (dev)) + devinfo (dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld", + dev->stats.rx_packets, dev->stats.tx_packets, + dev->stats.rx_errors, dev->stats.tx_errors + ); + + /* allow minidriver to stop correctly (wireless devices to turn off + * radio etc) */ + if (info->stop) { + retval = info->stop(dev); + if (retval < 0 && netif_msg_ifdown(dev)) + devinfo(dev, + "stop fail (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + } + + if (!(info->flags & FLAG_AVOID_UNLINK_URBS)) { + /* ensure there are no more active urbs */ + add_wait_queue(&unlink_wakeup, &wait); + dev->wait = &unlink_wakeup; + temp = unlink_urbs(dev, &dev->txq) + + unlink_urbs(dev, &dev->rxq); + + /* maybe wait for deletions to finish. */ + while (!skb_queue_empty(&dev->rxq) + && !skb_queue_empty(&dev->txq) + && !skb_queue_empty(&dev->done)) { + msleep(UNLINK_TIMEOUT_MS); + if (netif_msg_ifdown(dev)) + devdbg(dev, "waited for %d urb completions", + temp); + } + dev->wait = NULL; + remove_wait_queue(&unlink_wakeup, &wait); + } + + usb_kill_urb(dev->interrupt); + + /* deferred work (task, timer, softirq) must also stop. + * can't flush_scheduled_work() until we drop rtnl (later), + * else workers could deadlock; so make workers a NOP. + */ + dev->flags = 0; + del_timer_sync (&dev->delay); + tasklet_kill (&dev->bh); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +// posts reads, and enables write queuing + +// precondition: never called in_interrupt + +static +int axusbnet_open (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int retval = 0; + struct driver_info *info = dev->driver_info; + + // put into "known safe" state + if (info->reset && (retval = info->reset (dev)) < 0) { + if (netif_msg_ifup (dev)) + devinfo (dev, + "open reset fail (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + goto done; + } + + // insist peer be connected + if (info->check_connect && (retval = info->check_connect (dev)) < 0) { + if (netif_msg_ifup (dev)) + devdbg (dev, "can't open; %d", retval); + goto done; + } + + /* start any status interrupt transfer */ + if (dev->interrupt) { + retval = usb_submit_urb (dev->interrupt, GFP_KERNEL); + if (retval < 0) { + if (netif_msg_ifup (dev)) + deverr (dev, "intr submit %d", retval); + goto done; + } + } + + netif_start_queue (net); + if (netif_msg_ifup (dev)) { + char *framing; + + if (dev->driver_info->flags & FLAG_FRAMING_NC) + framing = "NetChip"; + else if (dev->driver_info->flags & FLAG_FRAMING_GL) + framing = "GeneSys"; + else if (dev->driver_info->flags & FLAG_FRAMING_Z) + framing = "Zaurus"; + else if (dev->driver_info->flags & FLAG_FRAMING_RN) + framing = "RNDIS"; + else if (dev->driver_info->flags & FLAG_FRAMING_AX) + framing = "ASIX"; + else + framing = "simple"; + + devinfo (dev, "open: enable queueing " + "(rx %d, tx %d) mtu %d %s framing", + (int)RX_QLEN (dev), (int)TX_QLEN (dev), dev->net->mtu, + framing); + } + + // delay posting reads until we're fully open + tasklet_schedule (&dev->bh); + return retval; +done: + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* ethtool methods; minidrivers may need to add some more, but + * they'll probably want to use this base set. + */ + +static +int axusbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + + if (!dev->mii.mdio_read) + return -EOPNOTSUPP; + + return mii_ethtool_gset(&dev->mii, cmd); +} + +static +int axusbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + int retval; + + if (!dev->mii.mdio_write) + return -EOPNOTSUPP; + + retval = mii_ethtool_sset(&dev->mii, cmd); + + /* link speed/duplex might have changed */ + if (dev->driver_info->link_reset) + dev->driver_info->link_reset(dev); + + return retval; + +} + +static +u32 axusbnet_get_link (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + /* If a check_connect is defined, return its result */ + if (dev->driver_info->check_connect) + return dev->driver_info->check_connect (dev) == 0; + + /* if the device has mii operations, use those */ + if (dev->mii.mdio_read) + return mii_link_ok(&dev->mii); + + /* Otherwise, dtrt for drivers calling netif_carrier_{on,off} */ + return ethtool_op_get_link(net); +} + +static +int axusbnet_nway_reset(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + if (!dev->mii.mdio_write) + return -EOPNOTSUPP; + + return mii_nway_restart(&dev->mii); +} + +static +void axusbnet_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info) +{ + struct usbnet *dev = netdev_priv(net); + + strncpy (info->driver, dev->driver_name, sizeof info->driver); + strncpy (info->version, DRIVER_VERSION, sizeof info->version); + strncpy (info->fw_version, dev->driver_info->description, + sizeof info->fw_version); + usb_make_path (dev->udev, info->bus_info, sizeof info->bus_info); +} + +static +u32 axusbnet_get_msglevel (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + return dev->msg_enable; +} + +static +void axusbnet_set_msglevel (struct net_device *net, u32 level) +{ + struct usbnet *dev = netdev_priv(net); + + dev->msg_enable = level; +} + +/* drivers may override default ethtool_ops in their bind() routine */ +static struct ethtool_ops axusbnet_ethtool_ops = { + .get_settings = axusbnet_get_settings, + .set_settings = axusbnet_set_settings, + .get_link = axusbnet_get_link, + .nway_reset = axusbnet_nway_reset, + .get_drvinfo = axusbnet_get_drvinfo, + .get_msglevel = axusbnet_get_msglevel, + .set_msglevel = axusbnet_set_msglevel, +}; + +/*-------------------------------------------------------------------------*/ + +/* work that cannot be done in interrupt context uses keventd. + * + * NOTE: with 2.5 we could do more of this using completion callbacks, + * especially now that control transfers can be queued. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void kevent (void *data) +{ + struct usbnet *dev = (struct usbnet *)data; +#else +static void kevent (struct work_struct *work) +{ + struct usbnet *dev = + container_of(work, struct usbnet, kevent); +#endif + int status; + + /* usb_clear_halt() needs a thread context */ + if (test_bit (EVENT_TX_HALT, &dev->flags)) { + + unlink_urbs (dev, &dev->txq); + status = usb_clear_halt (dev->udev, dev->out); + if (status < 0 + && status != -EPIPE + && status != -ESHUTDOWN) { + if (netif_msg_tx_err (dev)) + deverr (dev, "can't clear tx halt, status %d", + status); + } else { + clear_bit (EVENT_TX_HALT, &dev->flags); + if (status != -ESHUTDOWN) + netif_wake_queue (dev->net); + } + } + if (test_bit (EVENT_RX_HALT, &dev->flags)) { + + unlink_urbs (dev, &dev->rxq); + status = usb_clear_halt (dev->udev, dev->in); + if (status < 0 + && status != -EPIPE + && status != -ESHUTDOWN) { + if (netif_msg_rx_err (dev)) + deverr (dev, "can't clear rx halt, status %d", + status); + } else { + clear_bit (EVENT_RX_HALT, &dev->flags); + tasklet_schedule (&dev->bh); + } + } + + /* tasklet could resubmit itself forever if memory is tight */ + if (test_bit (EVENT_RX_MEMORY, &dev->flags)) { + struct urb *urb = NULL; + + if (netif_running (dev->net)) + urb = usb_alloc_urb (0, GFP_KERNEL); + else + clear_bit (EVENT_RX_MEMORY, &dev->flags); + if (urb != NULL) { + clear_bit (EVENT_RX_MEMORY, &dev->flags); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + rx_submit (dev, urb, GFP_KERNEL); + tasklet_schedule (&dev->bh); + } + } + + if (test_bit (EVENT_LINK_RESET, &dev->flags)) { + struct driver_info *info = dev->driver_info; + int retval = 0; + + clear_bit (EVENT_LINK_RESET, &dev->flags); + if(info->link_reset && (retval = info->link_reset(dev)) < 0) { + devinfo(dev, "link reset failed (%d) usbnet usb-%s-%s, %s", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + } + } + + if (dev->flags) + devdbg (dev, "kevent done, flags = 0x%lx", + dev->flags); +} + +/*-------------------------------------------------------------------------*/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +static void tx_complete (struct urb *urb, struct pt_regs *regs) +#else +static void tx_complete (struct urb *urb) +#endif +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + + if (urb->status == 0) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += entry->length; + } else { + dev->stats.tx_errors++; + + switch (urb->status) { + case -EPIPE: + axusbnet_defer_kevent (dev, EVENT_TX_HALT); + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: // async unlink + case -ESHUTDOWN: // hardware gone + break; + + // like rx, tx gets controller i/o faults during khubd delays + // and so it uses the same throttling mechanism. + case -EPROTO: + case -ETIME: + case -EILSEQ: + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, + jiffies + THROTTLE_JIFFIES); + if (netif_msg_link (dev)) + devdbg (dev, "tx throttle %d", + urb->status); + } + netif_stop_queue (dev->net); + break; + default: + if (netif_msg_tx_err (dev)) + devdbg (dev, "tx err %d", entry->urb->status); + break; + } + } + + urb->dev = NULL; + entry->state = tx_done; + defer_bh(dev, skb, &dev->txq); +} + +/*-------------------------------------------------------------------------*/ + +static +void axusbnet_tx_timeout (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + unlink_urbs (dev, &dev->txq); + tasklet_schedule (&dev->bh); + + // FIXME: device recovery -- reset? +} + +/*-------------------------------------------------------------------------*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) +static int +#else +static netdev_tx_t +#endif +axusbnet_start_xmit (struct sk_buff *skb, + struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int length; + struct urb *urb = NULL; + struct skb_data *entry; + struct driver_info *info = dev->driver_info; + unsigned long flags; + int retval; + + // some devices want funky USB-level framing, for + // win32 driver (usually) and/or hardware quirks + if (info->tx_fixup) { + skb = info->tx_fixup (dev, skb, GFP_ATOMIC); + if (!skb) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "can't tx_fixup skb"); + goto drop; + } + } + length = skb->len; + + if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "no urb"); + goto drop; + } + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->state = tx_start; + entry->length = length; + + usb_fill_bulk_urb (urb, dev->udev, dev->out, + skb->data, skb->len, tx_complete, skb); + + /* don't assume the hardware handles USB_ZERO_PACKET + * NOTE: strictly conforming cdc-ether devices should expect + * the ZLP here, but ignore the one-byte packet. + */ + if (!(info->flags & FLAG_SEND_ZLP) && (length % dev->maxpacket) == 0) { + urb->transfer_buffer_length++; + if (skb_tailroom(skb)) { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + } + } + + spin_lock_irqsave (&dev->txq.lock, flags); + + switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { + case -EPIPE: + netif_stop_queue (net); + axusbnet_defer_kevent (dev, EVENT_TX_HALT); + break; + default: + if (netif_msg_tx_err (dev)) + devdbg (dev, "tx: submit urb err %d", retval); + break; + case 0: + net->trans_start = jiffies; + __skb_queue_tail (&dev->txq, skb); + if (dev->txq.qlen >= TX_QLEN (dev)) + netif_stop_queue (net); + } + spin_unlock_irqrestore (&dev->txq.lock, flags); + + if (retval) { + if (netif_msg_tx_err (dev)) + devdbg (dev, "drop, code %d", retval); +drop: + dev->stats.tx_dropped++; + if (skb) + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } else if (netif_msg_tx_queued (dev)) { + devdbg (dev, "> tx, len %d, type 0x%x", + length, skb->protocol); + } + return NETDEV_TX_OK; +} + +/*-------------------------------------------------------------------------*/ + +// tasklet (work deferred from completions, in_irq) or timer + +static void axusbnet_bh (unsigned long param) +{ + struct usbnet *dev = (struct usbnet *) param; + struct sk_buff *skb; + struct skb_data *entry; + + while ((skb = skb_dequeue (&dev->done))) { + entry = (struct skb_data *) skb->cb; + switch (entry->state) { + case rx_done: + entry->state = rx_cleanup; + rx_process (dev, skb); + continue; + case tx_done: + case rx_cleanup: + usb_free_urb (entry->urb); + dev_kfree_skb (skb); + continue; + default: + devdbg (dev, "bogus skb state %d", entry->state); + } + } + + // waiting for all pending urbs to complete? + if (dev->wait) { + if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) { + wake_up (dev->wait); + } + + // or are we maybe short a few urbs? + } else if (netif_running (dev->net) + && netif_device_present (dev->net) + && !timer_pending (&dev->delay) + && !test_bit (EVENT_RX_HALT, &dev->flags)) { + int temp = dev->rxq.qlen; + int qlen = RX_QLEN (dev); + + if (temp < qlen) { + struct urb *urb; + int i; + + // don't refill the queue all at once + for (i = 0; i < 10 && dev->rxq.qlen < qlen; i++) { + urb = usb_alloc_urb (0, GFP_ATOMIC); + if (urb != NULL) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + urb->transfer_flags |= URB_ASYNC_UNLINK; +#endif + rx_submit (dev, urb, GFP_ATOMIC); + } + } + if (temp != dev->rxq.qlen && netif_msg_link (dev)) + devdbg (dev, "rxqlen %d --> %d", + temp, dev->rxq.qlen); + if (dev->rxq.qlen < qlen) + tasklet_schedule (&dev->bh); + } + if (dev->txq.qlen < TX_QLEN (dev)) + netif_wake_queue (dev->net); + } +} + + +/*------------------------------------------------------------------------- + * + * USB Device Driver support + * + *-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +static +void axusbnet_disconnect (struct usb_interface *intf) +{ + struct usbnet *dev; + struct usb_device *xdev; + struct net_device *net; + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + if (!dev) + return; + + xdev = interface_to_usbdev (intf); + + if (netif_msg_probe (dev)) + devinfo (dev, "unregister '%s' usb-%s-%s, %s", + intf->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description); + + net = dev->net; + unregister_netdev (net); + + /* we don't hold rtnl here ... */ + flush_scheduled_work (); + + if (dev->driver_info->unbind) + dev->driver_info->unbind (dev, intf); + + free_netdev(net); + usb_put_dev (xdev); +} + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +static int +axusbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) +{ + struct usbnet *dev; + struct net_device *net; + struct usb_host_interface *interface; + struct driver_info *info; + struct usb_device *xdev; + int status; + const char *name; + + name = udev->dev.driver->name; + info = (struct driver_info *) prod->driver_info; + if (!info) { + printk (KERN_ERR "blacklisted by %s\n", name); + return -ENODEV; + } + xdev = interface_to_usbdev (udev); + interface = udev->cur_altsetting; + + usb_get_dev (xdev); + + status = -ENOMEM; + + // set up our own records + net = alloc_etherdev(sizeof(*dev)); + if (!net) { + dbg ("can't kmalloc dev"); + goto out; + } + + dev = netdev_priv(net); + dev->udev = xdev; + dev->intf = udev; + dev->driver_info = info; + dev->driver_name = name; + dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV + | NETIF_MSG_PROBE | NETIF_MSG_LINK); + skb_queue_head_init (&dev->rxq); + skb_queue_head_init (&dev->txq); + skb_queue_head_init (&dev->done); + dev->bh.func = axusbnet_bh; + dev->bh.data = (unsigned long) dev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK (&dev->kevent, kevent, dev); +#else + INIT_WORK (&dev->kevent, kevent); +#endif + + dev->delay.function = axusbnet_bh; + dev->delay.data = (unsigned long) dev; + init_timer (&dev->delay); +// mutex_init (&dev->phy_mutex); + + dev->net = net; + + /* rx and tx sides can use different message sizes; + * bind() should set rx_urb_size in that case. + */ + dev->hard_mtu = net->mtu + net->hard_header_len; + +#if 0 +// dma_supported() is deeply broken on almost all architectures + // possible with some EHCI controllers + if (dma_supported (&udev->dev, DMA_BIT_MASK(64))) + net->features |= NETIF_F_HIGHDMA; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + net->open = axusbnet_open, + net->stop = axusbnet_stop, + net->hard_start_xmit = axusbnet_start_xmit, + net->tx_timeout = axusbnet_tx_timeout, + net->get_stats = axusbnet_get_stats; +#endif + + net->watchdog_timeo = TX_TIMEOUT_JIFFIES; + net->ethtool_ops = &axusbnet_ethtool_ops; + + // allow device-specific bind/init procedures + // NOTE net->name still not usable ... + status = info->bind (dev, udev); + if (status < 0) { + deverr(dev, "Binding device failed: %d", status); + goto out1; + } + + /* maybe the remote can't receive an Ethernet MTU */ + if (net->mtu > (dev->hard_mtu - net->hard_header_len)) + net->mtu = dev->hard_mtu - net->hard_header_len; + + status = init_status (dev, udev); + if (status < 0) + goto out3; + + if (!dev->rx_urb_size) + dev->rx_urb_size = dev->hard_mtu; + dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1); + + SET_NETDEV_DEV(net, &udev->dev); + status = register_netdev (net); + if (status) { + deverr(dev, "net device registration failed: %d", status); + goto out3; + } + + if (netif_msg_probe (dev)) + devinfo (dev, "register '%s' at usb-%s-%s, %s, %pM", + udev->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description, + net->dev_addr); + + // ok, it's ready to go. + usb_set_intfdata (udev, dev); + + // start as if the link is up + netif_device_attach (net); + + return 0; + +out3: + if (info->unbind) + info->unbind (dev, udev); +out1: + free_netdev(net); +out: + usb_put_dev(xdev); + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* + * suspend the whole driver as soon as the first interface is suspended + * resume only when the last interface is resumed + */ + +static int axusbnet_suspend (struct usb_interface *intf, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,10) +pm_message_t message) +#else +u32 message) +#endif +{ + struct usbnet *dev = usb_get_intfdata(intf); + + if (!dev->suspend_count++) { + /* + * accelerate emptying of the rx and queues, to avoid + * having everything error out. + */ + netif_device_detach (dev->net); + (void) unlink_urbs (dev, &dev->rxq); + (void) unlink_urbs (dev, &dev->txq); + /* + * reattach so runtime management can use and + * wake the device + */ + netif_device_attach (dev->net); + } + return 0; +} + +static int +axusbnet_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + if (!--dev->suspend_count) + tasklet_schedule (&dev->bh); + + return 0; +} + diff --git a/drivers/net/usb/axusbnet.h b/drivers/net/usb/axusbnet.h new file mode 100644 index 000000000000..d492de3b80e1 --- /dev/null +++ b/drivers/net/usb/axusbnet.h @@ -0,0 +1,208 @@ +/* + * USB Networking Link Interface + * + * Copyright (C) 2000-2005 by David Brownell <dbrownell@users.sourceforge.net> + * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_USB_USBNET_H +#define __LINUX_USB_USBNET_H + +#ifndef gfp_t +#define gfp_t int +#endif + +/* interface from usbnet core to each USB networking link we handle */ +struct usbnet { + /* housekeeping */ + struct usb_device *udev; + struct usb_interface *intf; + struct driver_info *driver_info; + const char *driver_name; + void *driver_priv; + wait_queue_head_t *wait; +// struct mutex phy_mutex; + unsigned char suspend_count; + + /* i/o info: pipes etc */ + unsigned in, out; + struct usb_host_endpoint *status; + unsigned maxpacket; + struct timer_list delay; + + /* protocol/interface state */ + struct net_device *net; + struct net_device_stats stats; + int msg_enable; + unsigned long data [5]; + u32 xid; + u32 hard_mtu; /* count any extra framing */ + size_t rx_urb_size; /* size for rx urbs */ + struct mii_if_info mii; + + /* various kinds of pending driver work */ + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head done; + struct sk_buff_head rxq_pause; + struct urb *interrupt; + struct tasklet_struct bh; + + struct work_struct kevent; + unsigned long flags; +# define EVENT_TX_HALT 0 +# define EVENT_RX_HALT 1 +# define EVENT_RX_MEMORY 2 +# define EVENT_STS_SPLIT 3 +# define EVENT_LINK_RESET 4 +# define EVENT_RX_PAUSED 5 + + void *priv; /* point to minidriver private data */ + unsigned char rx_size; +}; + +static inline struct usb_driver *driver_of(struct usb_interface *intf) +{ + return to_usb_driver(intf->dev.driver); +} + +/* interface from the device/framing level "minidriver" to core */ +struct driver_info { + char *description; + + int flags; +/* framing is CDC Ethernet, not writing ZLPs (hw issues), or optionally: */ +#define FLAG_FRAMING_NC 0x0001 /* guard against device dropouts */ +#define FLAG_FRAMING_GL 0x0002 /* genelink batches packets */ +#define FLAG_FRAMING_Z 0x0004 /* zaurus adds a trailer */ +#define FLAG_FRAMING_RN 0x0008 /* RNDIS batches, plus huge header */ + +#define FLAG_NO_SETINT 0x0010 /* device can't set_interface() */ +#define FLAG_ETHER 0x0020 /* maybe use "eth%d" names */ + +#define FLAG_FRAMING_AX 0x0040 /* AX88772/178 packets */ +#define FLAG_WLAN 0x0080 /* use "wlan%d" names */ +#define FLAG_AVOID_UNLINK_URBS 0x0100 /* don't unlink urbs at usbnet_stop() */ +#define FLAG_SEND_ZLP 0x0200 /* hw requires ZLPs are sent */ +#define FLAG_HW_IP_ALIGNMENT 0x0400 /* AX88772B support hardware IP alignment */ + + + /* init device ... can sleep, or cause probe() failure */ + int (*bind)(struct usbnet *, struct usb_interface *); + + /* cleanup device ... can sleep, but can't fail */ + void (*unbind)(struct usbnet *, struct usb_interface *); + + /* reset device ... can sleep */ + int (*reset)(struct usbnet *); + + /* stop device ... can sleep */ + int (*stop)(struct usbnet *); + + /* see if peer is connected ... can sleep */ + int (*check_connect)(struct usbnet *); + + /* for status polling */ + void (*status)(struct usbnet *, struct urb *); + + /* link reset handling, called from defer_kevent */ + int (*link_reset)(struct usbnet *); + + /* fixup rx packet (strip framing) */ + int (*rx_fixup)(struct usbnet *dev, struct sk_buff *skb); + + /* fixup tx packet (add framing) */ + struct sk_buff *(*tx_fixup)(struct usbnet *dev, + struct sk_buff *skb, gfp_t flags); + + /* early initialization code, can sleep. This is for minidrivers + * having 'subminidrivers' that need to do extra initialization + * right after minidriver have initialized hardware. */ + int (*early_init)(struct usbnet *dev); + + /* called by minidriver when receiving indication */ + void (*indication)(struct usbnet *dev, void *ind, int indlen); + + /* for new devices, use the descriptor-reading code instead */ + int in; /* rx endpoint */ + int out; /* tx endpoint */ + + unsigned long data; /* Misc driver specific data */ +}; + +/* Drivers that reuse some of the standard USB CDC infrastructure + * (notably, using multiple interfaces according to the CDC + * union descriptor) get some helper code. + */ +struct cdc_state { + struct usb_cdc_header_desc *header; + struct usb_cdc_union_desc *u; + struct usb_cdc_ether_desc *ether; + struct usb_interface *control; + struct usb_interface *data; +}; + +/* CDC and RNDIS support the same host-chosen packet filters for IN transfers */ +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + |USB_CDC_PACKET_TYPE_PROMISCUOUS \ + |USB_CDC_PACKET_TYPE_DIRECTED) + + +/* we record the state for each of our queued skbs */ +enum skb_state { + illegal = 0, + tx_start, tx_done, + rx_start, rx_done, rx_cleanup +}; + +struct skb_data { /* skb->cb is one of these */ + struct urb *urb; + struct usbnet *dev; + enum skb_state state; + size_t length; +}; + +#ifndef skb_queue_walk_safe +#define skb_queue_walk_safe(queue, skb, tmp) \ + for (skb = (queue)->next, tmp = skb->next; \ + skb != (struct sk_buff *)(queue); \ + skb = tmp, tmp = skb->next) +#endif + +/* messaging support includes the interface name, so it must not be + * used before it has one ... notably, in minidriver bind() calls. + */ +#ifdef DEBUG +#define devdbg(usbnet, fmt, arg...) \ + printk("%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#else +#define devdbg(usbnet, fmt, arg...) \ + ({ if (0) printk("%s: " fmt "\n" , (usbnet)->net->name , \ + ## arg); 0; }) +#endif + +#define deverr(usbnet, fmt, arg...) \ + printk(KERN_ERR "%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#define devwarn(usbnet, fmt, arg...) \ + printk(KERN_WARNING "%s: " fmt "\n" , (usbnet)->net->name , ## arg) + +#define devinfo(usbnet, fmt, arg...) \ + printk(KERN_INFO "%s: " fmt "\n" , (usbnet)->net->name , ## arg); \ + + +#endif /* __LINUX_USB_USBNET_H */ diff --git a/drivers/regulator/tps6586x-regulator.c b/drivers/regulator/tps6586x-regulator.c index 9ab8f3fb68ef..c9cac10c6a3e 100644 --- a/drivers/regulator/tps6586x-regulator.c +++ b/drivers/regulator/tps6586x-regulator.c @@ -64,6 +64,7 @@ struct tps6586x_regulator { int enable_reg[2]; int *voltages; int delay; /* delay in us for regulator to stabilize */ + enum tps6586x_type type; /* for DVM regulators */ int go_reg; @@ -236,6 +237,13 @@ static int tps6586x_sm2_voltages[] = { 4200, 4250, 4300, 4350, 4400, 4450, 4500, 4550, }; +static int tps6586x_sm2fortythree_voltages[] = { + 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, 1525, 1550, 1575, 1600, + 1625, 1650, 1675, 1700, 1725, 1750, 1775, 1800, +}; + static int tps6586x_dvm_voltages[] = { 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, 1075, 1100, @@ -243,7 +251,7 @@ static int tps6586x_dvm_voltages[] = { 1325, 1350, 1375, 1400, 1425, 1450, 1475, 1500, }; -#define TPS6586X_REGULATOR(_id, vdata, _ops, vreg, shift, nbits, \ +#define TPS6586X_REGULATOR(_id, _type, vdata, _ops, vreg, shift, nbits, \ ereg0, ebit0, ereg1, ebit1, en_time) \ .desc = { \ .name = "REG-" #_id, \ @@ -261,43 +269,62 @@ static int tps6586x_dvm_voltages[] = { .enable_reg[1] = TPS6586X_SUPPLY##ereg1, \ .enable_bit[1] = (ebit1), \ .voltages = tps6586x_##vdata##_voltages, \ - .delay = en_time, + .delay = en_time, \ + .type = (_type), #define TPS6586X_REGULATOR_DVM_GOREG(goreg, gobit) \ .go_reg = TPS6586X_##goreg, \ .go_bit = (gobit), -#define TPS6586X_LDO(_id, vdata, vreg, shift, nbits, \ +#define TPS6586X_LDO(_id, type, vdata, vreg, shift, nbits, \ ereg0, ebit0, ereg1, ebit1, en_time) \ { \ - TPS6586X_REGULATOR(_id, vdata, ldo_ops, vreg, shift, nbits, \ - ereg0, ebit0, ereg1, ebit1, en_time) \ + TPS6586X_REGULATOR(_id, type, vdata, ldo_ops, vreg, shift, \ + nbits, ereg0, ebit0, ereg1, ebit1, en_time) \ } -#define TPS6586X_DVM(_id, vdata, vreg, shift, nbits, \ +#define TPS6586X_DVM(_id, type, vdata, vreg, shift, nbits, \ ereg0, ebit0, ereg1, ebit1, goreg, gobit, en_time) \ { \ - TPS6586X_REGULATOR(_id, vdata, dvm_ops, vreg, shift, nbits, \ - ereg0, ebit0, ereg1, ebit1, en_time) \ + TPS6586X_REGULATOR(_id, type, vdata, dvm_ops, vreg, shift, \ + nbits, ereg0, ebit0, ereg1, ebit1, en_time) \ TPS6586X_REGULATOR_DVM_GOREG(goreg, gobit) \ } +/* Note: type ANY means universal, search order matters, place ANY last */ static struct tps6586x_regulator tps6586x_regulator[] = { - TPS6586X_LDO(LDO_0, ldo0, SUPPLYV1, 5, 3, ENC, 0, END, 0, 4000), - TPS6586X_LDO(LDO_1, dvm, SUPPLYV1, 0, 5, ENC, 1, END, 1, 4000), - TPS6586X_LDO(LDO_3, ldo, SUPPLYV4, 0, 3, ENC, 2, END, 2, 3000), - TPS6586X_LDO(LDO_5, ldo, SUPPLYV6, 0, 3, ENE, 6, ENE, 6, 3000), - TPS6586X_LDO(LDO_6, ldo, SUPPLYV3, 0, 3, ENC, 4, END, 4, 15000), - TPS6586X_LDO(LDO_7, ldo, SUPPLYV3, 3, 3, ENC, 5, END, 5, 15000), - TPS6586X_LDO(LDO_8, ldo, SUPPLYV2, 5, 3, ENC, 6, END, 6, 15000), - TPS6586X_LDO(LDO_9, ldo, SUPPLYV6, 3, 3, ENE, 7, ENE, 7, 3000), - TPS6586X_LDO(LDO_RTC, ldo, SUPPLYV4, 3, 3, V4, 7, V4, 7, 0), - TPS6586X_LDO(SM_2, sm2, SUPPLYV2, 0, 5, ENC, 7, END, 7, 0), - - TPS6586X_DVM(LDO_2, dvm, LDO2BV1, 0, 5, ENA, 3, ENB, 3, VCC2, 6, 3000), - TPS6586X_DVM(SM_0, dvm, SM0V1, 0, 5, ENA, 1, ENB, 1, VCC1, 2, 4000), - TPS6586X_DVM(SM_1, dvm, SM1V1, 0, 5, ENA, 0, ENB, 0, VCC1, 0, 4000), - TPS6586X_DVM(LDO_4, ldo4, LDO4V1, 0, 5, ENC, 3, END, 3, VCC1, 6, 15000), + TPS6586X_LDO(LDO_0, TPS6586X_ANY, ldo0, SUPPLYV1, 5, 3, ENC, 0, END, 0, + 4000), + TPS6586X_LDO(LDO_1, TPS6586X_ANY, dvm, SUPPLYV1, 0, 5, ENC, 1, END, 1, + 4000), + TPS6586X_DVM(LDO_2, TPS6586X_ANY, dvm, LDO2BV1, 0, 5, ENA, 3, ENB, 3, + VCC2, 6, 3000), + TPS6586X_LDO(LDO_3, TPS6586X_ANY, ldo, SUPPLYV4, 0, 3, ENC, 2, END, 2, + 3000), + TPS6586X_DVM(LDO_4, TPS6586X_ANY, ldo4, LDO4V1, 0, 5, ENC, 3, END, 3, + VCC1, 6, 15000), + TPS6586X_LDO(LDO_5, TPS6586X_ANY, ldo, SUPPLYV6, 0, 3, ENE, 6, ENE, 6, + 3000), + TPS6586X_LDO(LDO_6, TPS6586X_ANY, ldo, SUPPLYV3, 0, 3, ENC, 4, END, 4, + 15000), + TPS6586X_LDO(LDO_7, TPS6586X_ANY, ldo, SUPPLYV3, 3, 3, ENC, 5, END, 5, + 15000), + TPS6586X_LDO(LDO_8, TPS6586X_ANY, ldo, SUPPLYV2, 5, 3, ENC, 6, END, 6, + 15000), + TPS6586X_LDO(LDO_9, TPS6586X_ANY, ldo, SUPPLYV6, 3, 3, ENE, 7, ENE, 7, + 3000), + TPS6586X_LDO(LDO_RTC, TPS6586X_ANY, ldo, SUPPLYV4, 3, 3, V4, 7, V4, 7, + 0), + TPS6586X_DVM(SM_0, TPS6586X_ANY, dvm, SM0V1, 0, 5, ENA, 1, ENB, 1, + VCC1, 2, 4000), + TPS6586X_DVM(SM_1, TPS6586X_ANY, dvm, SM1V1, 0, 5, ENA, 0, ENB, 0, + VCC1, 0, 4000), + TPS6586X_DVM(SM_2, TPS658623, ldo4, SUPPLYV2, 0, 5, ENC, 3, END, 3, + VCC1, 6, 15000), + TPS6586X_DVM(SM_2, TPS658643, sm2fortythree, SUPPLYV2, 0, 5, ENC, 3, + END, 3, VCC1, 6, 15000), + TPS6586X_LDO(SM_2, TPS6586X_ANY, sm2, SUPPLYV2, 0, 5, ENC, 7, END, 7, + 0), }; /* @@ -399,14 +426,16 @@ static inline int tps6586x_regulator_set_slew_rate(struct platform_device *pdev) return tps6586x_write(parent, reg, setting->slew_rate); } -static inline struct tps6586x_regulator *find_regulator_info(int id) +static inline struct tps6586x_regulator *find_regulator_info(int id, + enum tps6586x_type type) { struct tps6586x_regulator *ri; int i; for (i = 0; i < ARRAY_SIZE(tps6586x_regulator); i++) { ri = &tps6586x_regulator[i]; - if (ri->desc.id == id) + if ((ri->desc.id == id) && ((ri->type == type) || + (ri->type == TPS6586X_ANY))) return ri; } return NULL; @@ -421,7 +450,7 @@ static int __devinit tps6586x_regulator_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "Probing reulator %d\n", id); - ri = find_regulator_info(id); + ri = find_regulator_info(id, tps6586x_gettype(pdev->dev.parent)); if (ri == NULL) { dev_err(&pdev->dev, "invalid regulator ID specified\n"); return -EINVAL; diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index b39f2e1c1fe6..ce9761c79c51 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -205,3 +205,10 @@ config MAX1363_RING_BUFFER help Say yes here to include ring buffer support in the MAX1363 ADC driver. + +config STMPE_ADC + bool "ST Microelectronics STMPE ADC driver" + depends on MFD_STMPE + help + Say yes here to build support for ST Microelectronics STMPE + built in ADC block (stmpe811). diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index f02035139979..e54101c03121 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -40,3 +40,5 @@ obj-$(CONFIG_AD7816) += ad7816.o obj-$(CONFIG_ADT75) += adt75.o obj-$(CONFIG_ADT7310) += adt7310.o obj-$(CONFIG_ADT7410) += adt7410.o + +obj-$(CONFIG_STMPE_ADC) += stmpe-adc.o diff --git a/drivers/staging/iio/adc/stmpe-adc.c b/drivers/staging/iio/adc/stmpe-adc.c new file mode 100644 index 000000000000..3a9d6bee558d --- /dev/null +++ b/drivers/staging/iio/adc/stmpe-adc.c @@ -0,0 +1,334 @@ +/* + * stmpe.c - STMicroelectronics STMPE811 IIO ADC Driver + * + * 4 channel, 10/12-bit ADC + * + * Copyright (C) 2013 Toradex AG <stefan.agner@toradex.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/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/of_platform.h> +#include <linux/err.h> + +#include <linux/mfd/stmpe.h> + +#include "../iio.h" +#include "adc.h" + +#define STMPE_REG_INT_STA 0x0B +#define STMPE_REG_ADC_INT_EN 0x0E +#define STMPE_REG_ADC_INT_STA 0x0F + +#define STMPE_REG_ADC_CTRL1 0x20 +#define STMPE_REG_ADC_CTRL2 0x21 +#define STMPE_REG_ADC_CAPT 0x22 +#define STMPE_REG_ADC_DATA_CH(channel) (0x30 + 2*channel) + +#define STMPE_ADC_CH(channel) ((1 << channel) & 0xff) + +#define STMPE_ADC_TIMEOUT (msecs_to_jiffies(1000)) + +struct stmpe_adc { + struct stmpe *stmpe; + struct clk *clk; + struct device *dev; + unsigned int irq; + + struct completion completion; + + u8 channel; + u32 value; + u8 sample_time; + u8 mod_12b; + u8 ref_sel; + u8 adc_freq; +}; + +static int stmpe_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct stmpe_adc *info = iio_priv(indio_dev); + unsigned long timeout; + + if (mask > 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + info->channel = (u8)chan->scan_index; + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_EN, + STMPE_ADC_CH(info->channel)); + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_CAPT, + STMPE_ADC_CH(info->channel)); + + timeout = wait_for_completion_interruptible_timeout + (&info->completion, STMPE_ADC_TIMEOUT); + + *val = info->value; + + mutex_unlock(&indio_dev->mlock); + + if (timeout == -ERESTARTSYS) + return -EINTR; + + if (timeout == 0) + return -ETIMEDOUT; + + return IIO_VAL_INT; +} + +static irqreturn_t stmpe_adc_isr(int irq, void *dev_id) +{ + struct stmpe_adc *info = (struct stmpe_adc *)dev_id; + u8 data[2]; + int int_sta; + + int_sta = stmpe_reg_read(info->stmpe, STMPE_REG_ADC_INT_STA); + + /* Is the interrupt relevant */ + if (!(int_sta & STMPE_ADC_CH(info->channel))) + return IRQ_NONE; + + /* Read value */ + stmpe_block_read(info->stmpe, STMPE_REG_ADC_DATA_CH(info->channel), 2, + data); + info->value = ((u32)data[0] << 8) + data[1]; + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_STA, int_sta); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info stmpe_adc_iio_info = { + .num_interrupt_lines = 1, + .read_raw = &stmpe_read_raw, + .driver_module = THIS_MODULE, +}; + +#define STMPE_EV_M \ + (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) \ + | IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) +#define STMPE_INFO_MASK (1 << IIO_CHAN_INFO_SCALE_SHARED) + +static const struct iio_chan_spec stmpe_adc_all_iio_channels[] = { + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 0, 0, STMPE_INFO_MASK, + 0, 0, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 1, 0, STMPE_INFO_MASK, + 0, 1, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 2, 0, STMPE_INFO_MASK, + 0, 2, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 3, 0, STMPE_INFO_MASK, + 0, 3, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 4, 0, STMPE_INFO_MASK, + 0, 4, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 5, 0, STMPE_INFO_MASK, + 0, 5, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 6, 0, STMPE_INFO_MASK, + 0, 6, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 7, 0, STMPE_INFO_MASK, + 0, 7, IIO_ST('u', 12, 16, 0), STMPE_EV_M), +}; + +static const struct iio_chan_spec stmpe_adc_iio_channels[] = { + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 4, 0, STMPE_INFO_MASK, + 0, 4, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 5, 0, STMPE_INFO_MASK, + 0, 5, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 6, 0, STMPE_INFO_MASK, + 0, 6, IIO_ST('u', 12, 16, 0), STMPE_EV_M), + IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 7, 0, STMPE_INFO_MASK, + 0, 7, IIO_ST('u', 12, 16, 0), STMPE_EV_M), +}; + +static int stmpe_adc_remove_devices(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int __devinit stmpe_adc_init_hw(struct stmpe_adc *adc) +{ + int ret; + u8 adc_ctrl1, adc_ctrl1_mask; + struct stmpe *stmpe = adc->stmpe; + struct device *dev = adc->dev; + + ret = stmpe_enable(stmpe, STMPE_BLOCK_ADC); + if (ret) { + dev_err(dev, "Could not enable clock for ADC\n"); + return ret; + } + + adc_ctrl1 = SAMPLE_TIME(adc->sample_time) | MOD_12B(adc->mod_12b) | + REF_SEL(adc->ref_sel); + adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff); + + ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1, + adc_ctrl1_mask, adc_ctrl1); + if (ret) { + dev_err(dev, "Could not setup ADC\n"); + return ret; + } + + ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2, + ADC_FREQ(0xff), ADC_FREQ(adc->adc_freq)); + if (ret) { + dev_err(dev, "Could not setup ADC\n"); + return ret; + } + + return 0; +} + + +static int __devinit stmpe_adc_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_platform_data *pdata = stmpe->pdata; + struct stmpe_adc_platform_data *adc_pdata = NULL; + struct stmpe_adc *info = NULL; + struct iio_dev *indio_dev = NULL; + int ret = -ENODEV; + int irq; + + irq = platform_get_irq_byname(pdev, "STMPE_ADC"); + if (irq < 0) + return irq; + + indio_dev = iio_allocate_device(sizeof(struct stmpe_adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->irq = irq; + info->stmpe = stmpe; + + init_completion(&info->completion); + ret = request_threaded_irq(info->irq, NULL, stmpe_adc_isr, IRQF_ONESHOT, + "stmpe-adc", info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", + info->irq); + goto err_free; + } + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &stmpe_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Register TS-Channels only if they are available */ + if (stmpe->pdata->blocks & STMPE_BLOCK_TOUCHSCREEN) + indio_dev->channels = stmpe_adc_iio_channels; + else + indio_dev->channels = stmpe_adc_all_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(stmpe_adc_iio_channels); + + if (pdata) + adc_pdata = pdata->adc; + + if (adc_pdata) { + info->sample_time = adc_pdata->sample_time; + info->mod_12b = adc_pdata->mod_12b; + info->ref_sel = adc_pdata->ref_sel; + info->adc_freq = adc_pdata->adc_freq; + } + + ret = stmpe_adc_init_hw(info); + if (ret) + goto err_irq; + + ret = iio_device_register(indio_dev); + if (ret) + goto err_irq; + + + dev_info(&pdev->dev, "Initialized\n"); + + return 0; + +err_irq: + free_irq(info->irq, info); +err_free: + kfree(indio_dev); + return ret; +} + +static int stmpe_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct stmpe_adc *info = iio_priv(indio_dev); + + device_for_each_child(&pdev->dev, NULL, + stmpe_adc_remove_devices); + iio_device_unregister(indio_dev); + free_irq(info->irq, info); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int stmpe_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct stmpe_adc *info = iio_priv(indio_dev); + + stmpe_adc_init_hw(info); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(stmpe_adc_pm_ops, NULL, stmpe_adc_resume); + +static struct platform_driver stmpe_adc_driver = { + .probe = stmpe_adc_probe, + .remove = stmpe_adc_remove, + .driver = { + .name = "stmpe-adc", + .owner = THIS_MODULE, + .pm = &stmpe_adc_pm_ops, + }, +}; + +module_platform_driver(stmpe_adc_driver); + +MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>"); +MODULE_DESCRIPTION("STMPEXXX ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stmpe-adc"); diff --git a/drivers/staging/iio/industrialio-core.c b/drivers/staging/iio/industrialio-core.c index 19819e7578c6..fd812c343e81 100644 --- a/drivers/staging/iio/industrialio-core.c +++ b/drivers/staging/iio/industrialio-core.c @@ -860,7 +860,10 @@ static ssize_t iio_ev_state_show(struct device *dev, { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); - int val = indio_dev->info->read_event_config(indio_dev, + int val = -ENODEV; + + if (indio_dev->info != NULL && indio_dev->info->read_event_config != NULL) + val = indio_dev->info->read_event_config(indio_dev, this_attr->address); if (val < 0) @@ -875,9 +878,10 @@ static ssize_t iio_ev_value_show(struct device *dev, { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); - int val, ret; + int val, ret = -ENODEV; - ret = indio_dev->info->read_event_value(indio_dev, + if (indio_dev->info != NULL && indio_dev->info->read_event_config != NULL) + ret = indio_dev->info->read_event_value(indio_dev, this_attr->address, &val); if (ret < 0) return ret; diff --git a/drivers/tty/serial/8250.c b/drivers/tty/serial/8250.c index a3f966b39862..295fd2d7c713 100644 --- a/drivers/tty/serial/8250.c +++ b/drivers/tty/serial/8250.c @@ -1641,6 +1641,7 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) struct irq_info *i = dev_id; struct list_head *l, *end = NULL; int pass_counter = 0, handled = 0; + static unsigned int tegra_spurious = 0; DEBUG_INTR("serial8250_interrupt(%d)...", irq); @@ -1674,6 +1675,26 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) handled = 1; end = NULL; + } else if ((up->port.type == PORT_TEGRA) && !handled) { + /* irq 68: nobody cared workaround */ + + /* The irq request flag sometimes does not get reset or is + * asserted immediately, but iir does not indicated this, if + * so we get here with iir set to 0xc1, i.e. no irq pending. */ + + /* Try enabling the transmit register empty interrupt, + * iir becomes 0xc2, irq gets de-asserted and ier is reverted + * by the regular code flow in the ISR. */ + tegra_spurious++; + /* Try this every 4096 spurious irq. */ + if ((tegra_spurious % 0x1000) == 0xfff) { + up->ier |= UART_IER_THRI; + serial_out(up, UART_IER, up->ier); + udelay(1); + handled = 1; + end = NULL; + } else if (end == NULL) + end = l; } else if (end == NULL) end = l; diff --git a/drivers/tty/serial/tegra_hsuart.c b/drivers/tty/serial/tegra_hsuart.c index b2fa281492a7..3f38cca3d6ec 100644 --- a/drivers/tty/serial/tegra_hsuart.c +++ b/drivers/tty/serial/tegra_hsuart.c @@ -819,7 +819,7 @@ static int tegra_uart_hw_init(struct tegra_uart_port *t) * Initialize the UART for a simple default configuration * so that the receive DMA buffer may be enqueued */ t->lcr_shadow = 3; /* no parity, stop, 8 data bits */ - tegra_set_baudrate(t, 115200); + tegra_set_baudrate(t, 9600); t->fcr_shadow |= UART_FCR_DMA_SELECT; uart_writeb(t, t->fcr_shadow, UART_FCR); if (tegra_start_dma_rx(t)) { diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 7b3185ff188a..9f79c267890e 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -399,8 +399,8 @@ static int rndis_function_bind_config(struct android_usb_function *f, rndis_control_intf.bInterfaceProtocol = 0x03; } - return rndis_bind_config(c, rndis->ethaddr, rndis->vendorID, - rndis->manufacturer); + return rndis_bind_config_vendor(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer); } static void rndis_function_unbind_config(struct android_usb_function *f, diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index f187b9eee9e5..25cb5fc8b263 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -774,9 +774,10 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->config, rndis->ethaddr); - if (rndis_set_param_vendor(rndis->config, rndis->vendorID, - rndis->manufacturer)) - goto fail; + if (rndis->manufacturer && rndis->vendorID && + rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -844,20 +845,8 @@ static inline bool can_support_rndis(struct usb_configuration *c) return true; } -/** - * rndis_bind_config - add RNDIS network link to a configuration - * @c: the configuration to support the network link - * @ethaddr: a buffer in which the ethernet address of the host side - * side of the link was recorded - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - * - * Caller must have called @gether_setup(). Caller is also responsible - * for calling @gether_cleanup() before module unload. - */ int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer) { struct f_rndis *rndis; diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index c130dae3a8e9..074a9b1ce6d9 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -52,8 +52,8 @@ #include "tegra_udc.h" -#define DRIVER_DESC "Nvidia Tegra High-Speed USB SOC \ - Device Controller driver" +#define DRIVER_DESC "Nvidia Tegra High-Speed USB SOC " \ + "Device Controller driver" #define DRIVER_AUTHOR "Venkat Moganty/Rakesh Bodla" #define DRIVER_VERSION "Apr 30, 2012" @@ -95,6 +95,11 @@ static const u8 tegra_udc_test_packet[53] = { 0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e }; +#ifdef CONFIG_MACH_COLIBRI_T20 +/* To limit the speed of USB to full speed */ +extern int g_usb_high_speed; +#endif /* CONFIG_MACH_COLIBRI_T20 */ + static struct tegra_udc *the_udc; #ifdef CONFIG_TEGRA_GADGET_BOOST_CPU_FREQ @@ -253,6 +258,16 @@ static int dr_controller_reset(struct tegra_udc *udc) cpu_relax(); } +#ifdef CONFIG_MACH_COLIBRI_T20 + /* To limit the speed of USB to full speed */ + if (!g_usb_high_speed) { + tmp = udc_readl(udc, PORTSCX_REG_OFFSET); + tmp |= PORTSCX_PORT_FORCE_FULL_SPEED; + udc_writel(udc, tmp, PORTSCX_REG_OFFSET); + tmp = udc_readl(udc, PORTSCX_REG_OFFSET); + } +#endif /* CONFIG_MACH_COLIBRI_T20 */ + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h index 46772413f0db..c32227ebac0b 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -111,13 +111,13 @@ int eem_bind_config(struct usb_configuration *c); #ifdef USB_ETH_RNDIS -int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +int rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer); #else static inline int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer) { return 0; @@ -125,4 +125,23 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], #endif +/** + * rndis_bind_config - add RNDIS network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +static inline int rndis_bind_config(struct usb_configuration *c, + u8 ethaddr[ETH_ALEN]) +{ + return rndis_bind_config_vendor(c, ethaddr, 0, NULL); +} + + #endif /* __U_ETHER_H */ diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 4ddb279cfb35..095447d9e3a2 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -23,12 +23,16 @@ #include <mach/usb_phy.h> #include <mach/iomap.h> +#include "../../../arch/arm/mach-tegra/tegra_usb_phy.h" + #if 0 #define EHCI_DBG(stuff...) pr_info("ehci-tegra: " stuff) #else #define EHCI_DBG(stuff...) do {} while (0) #endif +#define TEGRA_USB_PORTSC1_PFSC (1 << 24) + static const char driver_name[] = "tegra-ehci"; #define TEGRA_USB_DMA_ALIGN 32 @@ -51,6 +55,22 @@ struct dma_align_buffer { u8 data[0]; }; +#ifdef CONFIG_MACH_COLIBRI_T20 +/* To limit the speed of USB to full speed */ +int g_usb_high_speed = 0; + +/* To limit the speed of USB to full speed */ +static int __init enable_usb_high_speed(char *s) +{ + if (!(*s) || !strcmp(s, "1")) + g_usb_high_speed = 1; + + return 0; +} +__setup("usb_high_speed=", enable_usb_high_speed); +EXPORT_SYMBOL_GPL(g_usb_high_speed); +#endif /* CONFIG_MACH_COLIBRI_T20 */ + static void free_align_buffer(struct urb *urb) { struct dma_align_buffer *temp = container_of(urb->transfer_buffer, @@ -186,6 +206,71 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) return irq_status; } +static int tegra_ehci_internal_port_reset( + struct ehci_hcd *ehci, + u32 __iomem *portsc_reg +) +{ + u32 temp; + unsigned long flags; + int retval = 0; + int i, tries; + u32 saved_usbintr; + + spin_lock_irqsave(&ehci->lock, flags); + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); + /* disable USB interrupt */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + spin_unlock_irqrestore(&ehci->lock, flags); + + /* + * Here we have to do Port Reset at most twice for + * Port Enable bit to be set. + */ + for (i = 0; i < 2; i++) { + temp = ehci_readl(ehci, portsc_reg); + temp |= PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(10); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(1); + tries = 100; + do { + mdelay(1); + /* + * Up to this point, Port Enable bit is + * expected to be set after 2 ms waiting. + * USB1 usually takes extra 45 ms, for safety, + * we take 100 ms as timeout. + */ + temp = ehci_readl(ehci, portsc_reg); + } while (!(temp & PORT_PE) && tries--); + if (temp & PORT_PE) + break; + } + if (i == 2) + retval = -ETIMEDOUT; + + /* + * Clear Connect Status Change bit if it's set. + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. + */ + if (temp & PORT_CSC) + ehci_writel(ehci, PORT_CSC, portsc_reg); + + /* + * Write to clear any interrupt status bits that might be set + * during port reset. + */ + temp = ehci_readl(ehci, &ehci->regs->status); + ehci_writel(ehci, temp, &ehci->regs->status); + + /* restore original interrupt enable bits */ + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + + return retval; +} static int tegra_ehci_hub_control( struct usb_hcd *hcd, @@ -201,6 +286,29 @@ static int tegra_ehci_hub_control( int retval = 0; u32 __iomem *status_reg; +#ifdef CONFIG_MACH_COLIBRI_T20 + u32 temp; + + /* To limit the speed of USB to full speed */ + if (!g_usb_high_speed) { + /* Check whether port is not 2nd one internally connected to + ASIX Ethernet chip, set PFSC (Port Force Full Speed) only + for externally accessible OTG and host port */ + if (tegra->phy->inst != 1) { + status_reg = &ehci->regs->port_status[(wIndex & 0xff) + - 1]; + temp = ehci_readl(ehci, status_reg); + /* Check whether PFSC bit is already set or not */ + if (!(temp & TEGRA_USB_PORTSC1_PFSC)) { + ehci_writel(ehci, (temp | + TEGRA_USB_PORTSC1_PFSC), + status_reg); + temp = ehci_readl(ehci, status_reg); + } + } + } +#endif /* CONFIG_MACH_COLIBRI_T20 */ + if (!tegra_usb_phy_hw_accessible(tegra->phy)) { if (buf) memset(buf, 0, wLength); @@ -249,6 +357,13 @@ static int tegra_ehci_hub_control( break; } + /* For USB1 port we need to issue Port Reset twice internally */ + if (tegra->phy->inst == 0 && + (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) { + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + return tegra_ehci_internal_port_reset(ehci, status_reg); + } + /* handle ehci hub control request */ retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index bcb3e8680337..e38d2b4ca134 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -25,6 +25,14 @@ if USB || USB_GADGET # # USB Transceiver Drivers # +config USB_COLIBRI_OTG + boolean "Colibri OTG Driver" + depends on USB && ARCH_TEGRA && !USB_TEGRA_OTG + select USB_OTG_UTILS + help + Enable this driver on boards which use a regular GPIO for VBUS + detection. + config USB_GPIO_VBUS tristate "GPIO based peripheral-only VBUS sensing 'transceiver'" depends on GENERIC_GPIO diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 0ef95e45a583..cbedff4e254f 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg_id.o # transceiver drivers obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o +obj-$(CONFIG_USB_COLIBRI_OTG) += colibri-otg.o CFLAGS_tegra-otg.o = -Werror obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o diff --git a/drivers/usb/otg/colibri-otg.c b/drivers/usb/otg/colibri-otg.c new file mode 100644 index 000000000000..54d792536363 --- /dev/null +++ b/drivers/usb/otg/colibri-otg.c @@ -0,0 +1,268 @@ +/* + * drivers/usb/otg/colibri-otg.c + * + * OTG transceiver driver for Tegra UTMI phy with GPIO VBUS detection + * + * Copyright (C) 2010 NVIDIA Corp. + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2012 Toradex, Inc. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/colibri_usb.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/usb/otg.h> + +#include <mach/gpio.h> + +struct colibri_otg_data { + struct otg_transceiver otg; + spinlock_t lock; + int irq; + struct platform_device *host; + struct platform_device *pdev; + struct work_struct work; +}; + +static const char *tegra_state_name(enum usb_otg_state state) +{ + if (state == OTG_STATE_A_HOST) + return "HOST"; + if (state == OTG_STATE_B_PERIPHERAL) + return "PERIPHERAL"; + if (state == OTG_STATE_A_SUSPEND) + return "SUSPEND"; + return "INVALID"; +} + +void tegra_start_host(struct colibri_otg_data *tegra) +{ + struct colibri_otg_platform_data *pdata = tegra->otg.dev->platform_data; + if (!tegra->pdev) { + tegra->pdev = pdata->host_register(); + } +} + +void tegra_stop_host(struct colibri_otg_data *tegra) +{ + struct colibri_otg_platform_data *pdata = tegra->otg.dev->platform_data; + if (tegra->pdev) { + pdata->host_unregister(tegra->pdev); + tegra->pdev = NULL; + } +} + +static void tegra_otg_notify_event(struct otg_transceiver *otg, + enum usb_xceiv_events event) +{ + otg->last_event = event; + atomic_notifier_call_chain(&otg->notifier, event, NULL); +} + +static void irq_work(struct work_struct *work) +{ + struct colibri_otg_data *tegra = + container_of(work, struct colibri_otg_data, work); + struct otg_transceiver *otg = &tegra->otg; + enum usb_otg_state from = otg->state; + enum usb_otg_state to = OTG_STATE_UNDEFINED; + unsigned long flags; + + spin_lock_irqsave(&tegra->lock, flags); + + /* Check client detect (High active) */ + mdelay(100); + if (gpio_get_value(irq_to_gpio(tegra->irq))) + to = OTG_STATE_B_PERIPHERAL; + else + to = OTG_STATE_A_HOST; + + spin_unlock_irqrestore(&tegra->lock, flags); + + if (to != OTG_STATE_UNDEFINED) { + otg->state = to; + + if (from != to) { + dev_info(tegra->otg.dev, "%s --> %s\n", + tegra_state_name(from), tegra_state_name(to)); + + if (to == OTG_STATE_B_PERIPHERAL) { + if (from == OTG_STATE_A_HOST) { + tegra_stop_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } + if (otg->gadget) { + usb_gadget_vbus_connect(otg->gadget); + tegra_otg_notify_event(otg, USB_EVENT_VBUS); + } + } else if (to == OTG_STATE_A_HOST) { + if (otg->gadget && (from == OTG_STATE_B_PERIPHERAL)) { + usb_gadget_vbus_disconnect(otg->gadget); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } + tegra_start_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_ID); + } + } + } +} + +static irqreturn_t colibri_otg_irq(int irq, void *data) +{ + struct colibri_otg_data *tegra = data; + + schedule_work(&tegra->work); + + return IRQ_HANDLED; +} + +static int colibri_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct colibri_otg_data *tegra; + + tegra = container_of(otg, struct colibri_otg_data, otg); + otg->gadget = gadget; + + /* Set initial state */ + schedule_work(&tegra->work); + + return 0; +} + +static int colibri_otg_set_host(struct otg_transceiver *otg, + struct usb_bus *host) +{ + struct colibri_otg_data *tegra; + + tegra = container_of(otg, struct colibri_otg_data, otg); + otg->host = host; + + return 0; +} + +static int colibri_otg_set_power(struct otg_transceiver *otg, unsigned mA) +{ + return 0; +} + +static int colibri_otg_set_suspend(struct otg_transceiver *otg, int suspend) +{ + return 0; +} + +static int colibri_otg_probe(struct platform_device *pdev) +{ + struct colibri_otg_data *tegra; + struct colibri_otg_platform_data *plat = pdev->dev.platform_data; + int err, gpio_cd; + + if (!plat) { + dev_err(&pdev->dev, "no platform data?\n"); + return -ENODEV; + } + + tegra = kzalloc(sizeof(struct colibri_otg_data), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + tegra->otg.dev = &pdev->dev; + tegra->otg.label = "colibri-otg"; + tegra->otg.state = OTG_STATE_UNDEFINED; + tegra->otg.set_host = colibri_otg_set_host; + tegra->otg.set_peripheral = colibri_otg_set_peripheral; + tegra->otg.set_suspend = colibri_otg_set_suspend; + tegra->otg.set_power = colibri_otg_set_power; + spin_lock_init(&tegra->lock); + + platform_set_drvdata(pdev, tegra); + + tegra->otg.state = OTG_STATE_A_SUSPEND; + + err = otg_set_transceiver(&tegra->otg); + if (err) { + dev_err(&pdev->dev, "can't register transceiver (%d)\n", err); + goto err_otg; + } + + gpio_cd = plat->cable_detect_gpio; + err = gpio_request(gpio_cd, "USBC_DET"); + if (err) + goto err_gpio; + gpio_direction_input(gpio_cd); + + tegra->irq = gpio_to_irq(gpio_cd); + err = request_threaded_irq(tegra->irq, colibri_otg_irq, NULL, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "colibri-otg USBC_DET", tegra); + if (err) { + dev_err(&pdev->dev, "Failed to register USB client detect IRQ\n"); + goto err_irq; + } + + INIT_WORK (&tegra->work, irq_work); + + dev_info(&pdev->dev, "otg transceiver registered\n"); + + return 0; + +err_irq: + gpio_free(gpio_cd); +err_gpio: + otg_set_transceiver(NULL); +err_otg: + platform_set_drvdata(pdev, NULL); + kfree(tegra); + return err; +} + +static int __exit colibri_otg_remove(struct platform_device *pdev) +{ + struct colibri_otg_data *tegra = platform_get_drvdata(pdev); + + free_irq(tegra->irq, tegra); + gpio_free(irq_to_gpio(tegra->irq)); + otg_set_transceiver(NULL); + platform_set_drvdata(pdev, NULL); + kfree(tegra); + + return 0; +} + +static struct platform_driver colibri_otg_driver = { + .driver = { + .name = "colibri-otg", + }, + .remove = __exit_p(colibri_otg_remove), + .probe = colibri_otg_probe, +}; + +static int __init colibri_otg_init(void) +{ + return platform_driver_register(&colibri_otg_driver); +} +subsys_initcall(colibri_otg_init); + +static void __exit colibri_otg_exit(void) +{ + platform_driver_unregister(&colibri_otg_driver); +} +module_exit(colibri_otg_exit); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 2a9ed6ec8cb7..ea4e5ebdd35a 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -231,9 +231,11 @@ static void option_instat_callback(struct urb *urb); #define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED 0x8001 #define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED 0x9000 #define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED 0x9001 +#define NOVATELWIRELESS_PRODUCT_E362 0x9010 #define NOVATELWIRELESS_PRODUCT_G1 0xA001 #define NOVATELWIRELESS_PRODUCT_G1_M 0xA002 #define NOVATELWIRELESS_PRODUCT_G2 0xA010 +#define NOVATELWIRELESS_PRODUCT_MC551 0xB001 /* AMOI PRODUCTS */ #define AMOI_VENDOR_ID 0x1614 @@ -265,6 +267,9 @@ static void option_instat_callback(struct urb *urb); #define DELL_PRODUCT_5730_MINICARD_TELUS 0x8181 #define DELL_PRODUCT_5730_MINICARD_VZW 0x8182 +#define DELL_PRODUCT_5800_MINICARD_VZW 0x8195 /* Novatel E362 */ +#define DELL_PRODUCT_5800_V2_MINICARD_VZW 0x8196 /* Novatel E362 */ + #define KYOCERA_VENDOR_ID 0x0c88 #define KYOCERA_PRODUCT_KPC650 0x17da #define KYOCERA_PRODUCT_KPC680 0x180a @@ -715,6 +720,9 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G1) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G1_M) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G2) }, + /* Novatel Ovation MC551 a.k.a. Verizon USB551L */ + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC551, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E362, 0xff, 0xff, 0xff) }, { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01) }, { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01A) }, @@ -737,6 +745,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_SPRINT) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_TELUS) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_VZW) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_MINICARD_VZW, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, DELL_PRODUCT_5800_V2_MINICARD_VZW, 0xff, 0xff, 0xff) }, { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) }, /* ADU-E100, ADU-310 */ { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_500A) }, { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_620UW) }, diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig index 39ac49e0682c..7fce1987ddc7 100644 --- a/drivers/video/logo/Kconfig +++ b/drivers/video/logo/Kconfig @@ -82,4 +82,8 @@ config LOGO_M32R_CLUT224 depends on M32R default y +config LOGO_CUSTOM_CLUT224 + bool "Custom 224-color Linux logo" + default n + endif # LOGO diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile index 3b437813584c..45d4b5346d07 100644 --- a/drivers/video/logo/Makefile +++ b/drivers/video/logo/Makefile @@ -18,6 +18,8 @@ obj-$(CONFIG_LOGO_M32R_CLUT224) += logo_m32r_clut224.o obj-$(CONFIG_SPU_BASE) += logo_spe_clut224.o +obj-$(CONFIG_LOGO_CUSTOM_CLUT224) += logo_custom_clut224.o + # How to generate logo's # Use logo-cfiles to retrieve list of .c files to be built diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index ea7a8ccc830c..6e29415c9790 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -100,6 +100,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth) /* M32R Linux logo */ logo = &logo_m32r_clut224; #endif +#ifdef CONFIG_LOGO_CUSTOM_CLUT224 + /* Custom Linux logo */ + logo = &logo_custom_clut224; +#endif } return logo; } diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index bfd916e0e16e..d4c3f8bc9a49 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -1067,9 +1067,11 @@ static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status) queue_work(system_freezable_wq, &dc->vblank_work); if (status & FRAME_END_INT) { +#ifndef CONFIG_ANDROID struct timespec tm = CURRENT_TIME; dc->frame_end_timestamp = timespec_to_ns(&tm); wake_up(&dc->timestamp_wq); +#endif /* !CONFIG_ANDROID */ /* Mark the frame_end as complete. */ if (!completion_done(&dc->frame_end_complete)) @@ -1079,6 +1081,7 @@ static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status) } } +#ifndef CONFIG_ANDROID /* XXX: Not sure if we limit look ahead to 1 frame */ bool tegra_dc_is_within_n_vsync(struct tegra_dc *dc, s64 ts) { @@ -1094,6 +1097,7 @@ bool tegra_dc_does_vsync_separate(struct tegra_dc *dc, s64 new_ts, s64 old_ts) != div_s64((old_ts - dc->frame_end_timestamp), dc->frametime_ns))); } +#endif /* !CONFIG_ANDROID */ #endif static irqreturn_t tegra_dc_irq(int irq, void *ptr) @@ -1781,7 +1785,9 @@ static int tegra_dc_probe(struct nvhost_device *ndev, mutex_init(&dc->one_shot_lock); init_completion(&dc->frame_end_complete); init_waitqueue_head(&dc->wq); +#ifndef CONFIG_ANDROID init_waitqueue_head(&dc->timestamp_wq); +#endif /* !CONFIG_ANDROID */ #ifdef CONFIG_ARCH_TEGRA_2x_SOC INIT_WORK(&dc->reset_work, tegra_dc_reset_worker); #endif diff --git a/drivers/video/tegra/dc/dc_priv.h b/drivers/video/tegra/dc/dc_priv.h index 75c3a2a29658..e91071f70ddc 100644 --- a/drivers/video/tegra/dc/dc_priv.h +++ b/drivers/video/tegra/dc/dc_priv.h @@ -113,14 +113,18 @@ struct tegra_dc { void *out_data; struct tegra_dc_mode mode; +#ifndef CONFIG_ANDROID s64 frametime_ns; +#endif /* !CONFIG_ANDROID */ struct tegra_dc_win windows[DC_N_WINDOWS]; struct tegra_dc_blend blend; int n_windows; wait_queue_head_t wq; +#ifndef CONFIG_ANDROID wait_queue_head_t timestamp_wq; +#endif /* !CONFIG_ANDROID */ struct mutex lock; struct mutex one_shot_lock; @@ -165,7 +169,9 @@ struct tegra_dc { struct delayed_work underflow_work; u32 one_shot_delay_ms; struct delayed_work one_shot_work; +#ifndef CONFIG_ANDROID s64 frame_end_timestamp; +#endif /* !CONFIG_ANDROID */ }; #define print_mode_info(dc, mode) do { \ diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c index 88273e26c51c..37a6d9bd3f80 100644 --- a/drivers/video/tegra/dc/ext/dev.c +++ b/drivers/video/tegra/dc/ext/dev.c @@ -56,7 +56,9 @@ struct tegra_dc_ext_flip_data { struct tegra_dc_ext *ext; struct work_struct work; struct tegra_dc_ext_flip_win win[DC_N_WINDOWS]; +#ifndef CONFIG_ANDROID struct list_head timestamp_node; +#endif /* !CONFIG_ANDROID */ }; int tegra_dc_ext_get_num_outputs(void) @@ -208,7 +210,9 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext, { int err = 0; struct tegra_dc_ext_win *ext_win = &ext->win[win->idx]; +#ifndef CONFIG_ANDROID s64 timestamp_ns; +#endif /* !CONFIG_ANDROID */ if (flip_win->handle[TEGRA_DC_Y] == NULL) { win->flags = 0; @@ -272,6 +276,7 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext, msecs_to_jiffies(500), NULL); } +#ifndef CONFIG_ANDROID #ifndef CONFIG_TEGRA_SIMULATION_PLATFORM timestamp_ns = timespec_to_ns(&flip_win->attr.timestamp); @@ -286,9 +291,12 @@ static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext, } #endif return err; +#else /* !CONFIG_ANDROID */ + return 0; +#endif /* !CONFIG_ANDROID */ } -static void (*flip_callback)(void); +static int (*flip_callback)(void); static spinlock_t flip_callback_lock; static bool init_tegra_dc_flip_callback_called; @@ -301,7 +309,7 @@ static int __init init_tegra_dc_flip_callback(void) pure_initcall(init_tegra_dc_flip_callback); -int tegra_dc_set_flip_callback(void (*callback)(void)) +int tegra_dc_set_flip_callback(int (*callback)(void)) { WARN_ON(!init_tegra_dc_flip_callback_called); @@ -337,11 +345,14 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work) for (i = 0; i < DC_N_WINDOWS; i++) { struct tegra_dc_ext_flip_win *flip_win = &data->win[i]; - int j = 0, index = flip_win->attr.index; + int index = flip_win->attr.index; struct tegra_dc_win *win; struct tegra_dc_ext_win *ext_win; +#ifndef CONFIG_ANDROID + int j = 0; struct tegra_dc_ext_flip_data *temp = NULL; s64 head_timestamp = 0; +#endif /* !CONFIG_ANDROID */ if (index < 0) continue; @@ -353,6 +364,7 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work) (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR)) skip_flip = true; +#ifndef CONFIG_ANDROID mutex_lock(&ext_win->queue_lock); list_for_each_entry(temp, &ext_win->timestamp_queue, timestamp_node) { @@ -377,6 +389,7 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work) if (!list_empty(&ext_win->timestamp_queue)) list_del(&data->timestamp_node); mutex_unlock(&ext_win->queue_lock); +#endif /* !CONFIG_ANDROID */ if (win->flags & TEGRA_WIN_FLAG_ENABLED) { int j; @@ -409,6 +422,9 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work) flip_callback(); spin_unlock(&flip_callback_lock); } +#ifdef CONFIG_ANDROID + } +#endif /* CONFIG_ANDROID */ for (i = 0; i < DC_N_WINDOWS; i++) { struct tegra_dc_ext_flip_win *flip_win = &data->win[i]; @@ -420,7 +436,9 @@ static void tegra_dc_ext_flip_worker(struct work_struct *work) tegra_dc_incr_syncpt_min(ext->dc, index, flip_win->syncpt_max); } +#ifndef CONFIG_ANDROID } +#endif /* !CONFIG_ANDROID */ /* unpin and deref previous front buffers */ for (i = 0; i < nr_unpin; i++) { @@ -531,7 +549,9 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user, struct tegra_dc_ext_flip_data *data; int work_index = -1; int i, ret = 0; +#ifndef CONFIG_ANDROID bool has_timestamp = false; +#endif /* !CONFIG_ANDROID */ #ifdef CONFIG_ANDROID int index_check[DC_N_WINDOWS] = {0, }; @@ -572,8 +592,10 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user, int index = args->win[i].index; memcpy(&flip_win->attr, &args->win[i], sizeof(flip_win->attr)); +#ifndef CONFIG_ANDROID if (timespec_to_ns(&flip_win->attr.timestamp)) has_timestamp = true; +#endif /* !CONFIG_ANDROID */ if (index < 0) continue; @@ -648,11 +670,13 @@ static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user, ret = -EINVAL; goto unlock; } +#ifndef CONFIG_ANDROID if (has_timestamp) { mutex_lock(&ext->win[work_index].queue_lock); list_add_tail(&data->timestamp_node, &ext->win[work_index].timestamp_queue); mutex_unlock(&ext->win[work_index].queue_lock); } +#endif /* !CONFIG_ANDROID */ queue_work(ext->win[work_index].flip_wq, &data->work); unlock_windows_for_flip(user, args); @@ -993,8 +1017,10 @@ static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext) } mutex_init(&win->lock); +#ifndef CONFIG_ANDROID mutex_init(&win->queue_lock); INIT_LIST_HEAD(&win->timestamp_queue); +#endif /* !CONFIG_ANDROID */ } return 0; diff --git a/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h b/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h index ef7361d1d933..7238e95e59db 100644 --- a/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h +++ b/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h @@ -59,9 +59,11 @@ struct tegra_dc_ext_win { atomic_t nr_pending_flips; +#ifndef CONFIG_ANDROID struct mutex queue_lock; struct list_head timestamp_queue; +#endif /* !CONFIG_ANDROID */ }; struct tegra_dc_ext { diff --git a/drivers/video/tegra/dc/hdmi.c b/drivers/video/tegra/dc/hdmi.c index 55d9163d4faf..225d48ab15ee 100644 --- a/drivers/video/tegra/dc/hdmi.c +++ b/drivers/video/tegra/dc/hdmi.c @@ -1258,6 +1258,9 @@ static bool tegra_dc_hdmi_valid_asp_ratio(const struct tegra_dc *dc, int m_aspratio = 0; int s_aspratio = 0; + if (!mode->yres) + return false; + /* To check the aspect upto two decimal digits, calculate in % */ m_aspratio = (mode->xres*100 / mode->yres); @@ -1345,7 +1348,8 @@ void tegra_dc_hdmi_detect_config(struct tegra_dc *dc, hdmi->dvi = !(specs->misc & FB_MISC_HDMI); - tegra_fb_update_monspecs(dc->fb, specs, tegra_dc_hdmi_mode_filter); + if (dc->fb != NULL) + tegra_fb_update_monspecs(dc->fb, specs, tegra_dc_hdmi_mode_filter); #ifdef CONFIG_SWITCH hdmi->hpd_switch.state = 0; switch_set_state(&hdmi->hpd_switch, 1); @@ -1413,11 +1417,18 @@ static bool tegra_dc_hdmi_detect(struct tegra_dc *dc) struct tegra_dc_hdmi_data *hdmi = tegra_dc_get_outdata(dc); struct fb_monspecs specs; int err; + int cnt = 0; if (!tegra_dc_hdmi_hpd(dc)) goto fail; err = tegra_edid_get_monspecs(hdmi->edid, &specs); + /* retry, maybe hdmi detect is not debounced or the monitor needs some time */ + while ( (err < 0) && (cnt++ < 4) ) { + dev_err(&dc->ndev->dev, "error reading edid, trying again in 500ms\n"); + msleep(500); + err = tegra_edid_get_monspecs(hdmi->edid, &specs); + } if (err < 0) { if (dc->out->n_modes) tegra_dc_enable(dc); diff --git a/drivers/video/tegra/dc/mode.c b/drivers/video/tegra/dc/mode.c index 3a95f2e7ab0e..f5cd4fb0cdea 100644 --- a/drivers/video/tegra/dc/mode.c +++ b/drivers/video/tegra/dc/mode.c @@ -26,6 +26,179 @@ #include "dc_reg.h" #include "dc_priv.h" +const struct fb_videomode tegra_modes[] = { + /* EDT 5.7" ET070080DH or TouchRevolution Fusion 7" */ + { + .name = "800x480", + .refresh = 60, + .xres = 800, + .yres = 480, + .pixclock = 30807, + .left_margin = 128, + .right_margin = 64, + .upper_margin = 22, + .lower_margin = 20, + .hsync_len = 64, + .vsync_len = 3, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .flag = FB_FLAG_RATIO_16_9, + .vmode = FB_VMODE_NONINTERLACED + }, + /* TouchRevolution Fusion 10" aka Chunghwa Picture Tubes + * CLAA100NC05 10.1 inch 1024x600 single channel LVDS panel + */ + { + .name = "1024x600", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = KHZ2PICOS(48000), + .left_margin = 104, + .right_margin = 43, + .upper_margin = 24, + .lower_margin = 20, + .hsync_len = 5, + .vsync_len = 5, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .flag = FB_FLAG_RATIO_16_9, + .vmode = FB_VMODE_NONINTERLACED + }, + { + /* 1366x768 */ + .refresh = 60, + .xres = 1366, + .yres = 768, + .pixclock = KHZ2PICOS(72072), + .hsync_len = 58, /* h_sync_width */ + .vsync_len = 4, /* v_sync_width */ + .left_margin = 58, /* h_back_porch */ + .upper_margin = 4, /* v_back_porch */ + .right_margin = 58, /* h_front_porch */ + .lower_margin = 4, /* v_front_porch */ + .vmode = FB_VMODE_NONINTERLACED, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + }, + { + /* 1680x1050p 59.94/60hz */ + .refresh = 60, + .xres = 1680, + .yres = 1050, + .pixclock = KHZ2PICOS(147140), + .hsync_len = 184, /* h_sync_width */ + .vsync_len = 3, /* v_sync_width */ + .left_margin = 288, /* h_back_porch */ + .upper_margin = 33, /* v_back_porch */ + .right_margin = 104, /* h_front_porch */ + .lower_margin = 1, /* v_front_porch */ + .vmode = FB_VMODE_NONINTERLACED, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + }, + { + /* 1920x1080p 59.94/60hz CVT */ + .refresh = 60, + .xres = 1920, + .yres = 1080, + .pixclock = KHZ2PICOS(148500), + .hsync_len = 44, /* h_sync_width */ + .vsync_len = 5, /* v_sync_width */ + .left_margin = 148, /* h_back_porch */ + .upper_margin = 36, /* v_back_porch */ + .right_margin = 88, /* h_front_porch */ + .lower_margin = 4, /* v_front_porch */ + .vmode = FB_VMODE_NONINTERLACED, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + }, + { + /* 1920x1200p 60hz */ + .refresh = 60, + .xres = 1920, + .yres = 1200, + .pixclock = KHZ2PICOS(154000), + .hsync_len = 32, /* h_sync_width */ + .vsync_len = 6, /* v_sync_width */ + .left_margin = 80, /* h_back_porch */ + .upper_margin = 26, /* v_back_porch */ + .right_margin = 48, /* h_front_porch */ + .lower_margin = 3, /* v_front_porch */ + .vmode = FB_VMODE_NONINTERLACED, + .sync = 0, + }, + /* Portrait modes */ + { + .name = "480x640", + .refresh = 60, + .xres = 480, + .yres = 640, + .pixclock = 55555, + .left_margin = 20, + .right_margin = 8, + .upper_margin = 7, + .lower_margin = 8, + .hsync_len = 4, + .vsync_len = 1, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .name = "540x960", + .refresh = 60, + .xres = 540, + .yres = 960, + .pixclock = 100000, + .left_margin = 32, + .right_margin = 32, + .upper_margin = 1, + .lower_margin = 2, + .hsync_len = 16, + .vsync_len = 1, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, + { + .name = "720x1280", + .refresh = 60, + .xres = 720, + .yres = 1280, + .pixclock = 16282, + .left_margin = 100, + .right_margin = 4, + .upper_margin = 14, + .lower_margin = 4, + .hsync_len = 4, + .vsync_len = 4, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED + }, +}; + +/* try to find best matching mode using our modes, VESA and CEA modes from + * modedb + */ +int tegra_fb_find_mode(struct fb_var_screeninfo *var, struct fb_info *info, + const char* option, unsigned int default_bpp) +{ + int out; + + out = fb_find_mode(var, info, option, tegra_modes, + ARRAY_SIZE(tegra_modes), NULL, default_bpp); + + /* Only accept this mode if we found a reasonable match (resolution) */ + if (out == 1 || out == 2) + return out; + + out = fb_find_mode(&info->var, info, option, + cea_modes, CEA_MODEDB_SIZE, NULL, default_bpp); + + /* Check if we found a full match */ + if (out == 1 || out == 2) + return out; + + return fb_find_mode(&info->var, info, option, + vesa_modes, VESA_MODEDB_SIZE, NULL, default_bpp); +} +EXPORT_SYMBOL(tegra_fb_find_mode); + + /* return non-zero if constraint is violated */ static int calc_h_ref_to_sync(const struct tegra_dc_mode *mode, int *href) { @@ -137,6 +310,7 @@ static bool check_ref_to_sync(struct tegra_dc_mode *mode) return true; } +#ifndef CONFIG_ANDROID static s64 calc_frametime_ns(const struct tegra_dc_mode *m) { long h_total, v_total; @@ -147,6 +321,7 @@ static s64 calc_frametime_ns(const struct tegra_dc_mode *m) return (!m->pclk) ? 0 : (s64)(div_s64(((s64)h_total * v_total * 1000000000ULL), m->pclk)); } +#endif /* !CONFIG_ANDROID */ /* return in 1000ths of a Hertz */ int tegra_dc_calc_refresh(const struct tegra_dc_mode *m) @@ -270,21 +445,108 @@ int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode) { memcpy(&dc->mode, mode, sizeof(dc->mode)); + dev_info(&dc->ndev->dev, "using mode %dx%d pclk=%d href=%d vref=%d\n", + mode->h_active, mode->v_active, mode->pclk, + mode->h_ref_to_sync, mode->v_ref_to_sync + ); + if (dc->out->type == TEGRA_DC_OUT_RGB) panel_sync_rate = tegra_dc_calc_refresh(mode); else if (dc->out->type == TEGRA_DC_OUT_DSI) panel_sync_rate = dc->out->dsi->rated_refresh_rate * 1000; print_mode(dc, mode, __func__); +#ifndef CONFIG_ANDROID dc->frametime_ns = calc_frametime_ns(mode); +#endif /* !CONFIG_ANDROID */ return 0; } EXPORT_SYMBOL(tegra_dc_set_mode); +int tegra_dc_var_to_dc_mode(struct tegra_dc *dc, struct fb_var_screeninfo *var, + struct tegra_dc_mode *mode) +{ + bool stereo_mode = false; + int err; + + if (!var->pixclock) + return -EINVAL; + + mode->pclk = PICOS2KHZ(var->pixclock) * 1000; + mode->h_sync_width = var->hsync_len; + mode->v_sync_width = var->vsync_len; + mode->h_back_porch = var->left_margin; + mode->v_back_porch = var->upper_margin; + mode->h_active = var->xres; + mode->v_active = var->yres; + mode->h_front_porch = var->right_margin; + mode->v_front_porch = var->lower_margin; + mode->stereo_mode = stereo_mode; + + /* + * HACK: + * If v_front_porch is only 1, we would violate Constraint 5/6 + * in this case, increase front porch by 1 + */ + if (mode->v_front_porch <= 1) + mode->v_front_porch = 2; + + + if (dc->out->type == TEGRA_DC_OUT_HDMI) { + /* HDMI controller requires h_ref=1, v_ref=1 */ + mode->h_ref_to_sync = 1; + mode->v_ref_to_sync = 1; + } else { + /* Calculate ref_to_sync signals */ + err = calc_ref_to_sync(mode); + if (err) { + dev_err(&dc->ndev->dev, "display timing ref_to_sync" + "calculation failed with code %d\n", err); + return -EINVAL; + } + dev_info(&dc->ndev->dev, "Calculated sync href=%d vref=%d\n", + mode->h_ref_to_sync, mode->v_ref_to_sync); + } + if (!check_ref_to_sync(mode)) { + dev_err(&dc->ndev->dev, + "display timing doesn't meet restrictions.\n"); + return -EINVAL; + } + +#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT + /* Double the pixel clock and update v_active only for + * frame packed mode */ + if (mode->stereo_mode) { + mode->pclk *= 2; + /* total v_active = yres*2 + activespace */ + mode->v_active = var->yres * 2 + + var->vsync_len + + var->upper_margin + + var->lower_margin; + } +#endif + + mode->flags = 0; + + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + mode->flags |= TEGRA_DC_MODE_FLAG_NEG_H_SYNC; + + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + mode->flags |= TEGRA_DC_MODE_FLAG_NEG_V_SYNC; + + return 0; +} +EXPORT_SYMBOL(tegra_dc_var_to_dc_mode); + +/* + * This method is only used by sysfs interface + * /sys/devices/tegradc.1/nvdps + */ int tegra_dc_set_fb_mode(struct tegra_dc *dc, const struct fb_videomode *fbmode, bool stereo_mode) { + int err; struct tegra_dc_mode mode; if (!fbmode->pixclock) @@ -305,17 +567,28 @@ int tegra_dc_set_fb_mode(struct tegra_dc *dc, mode.h_ref_to_sync = 1; mode.v_ref_to_sync = 1; } else { - calc_ref_to_sync(&mode); + /* + * HACK: + * If v_front_porch is only 1, we would violate Constraint 5/6 + * in this case, increase front porch by 1 + */ + if (mode.v_front_porch <= 1) + mode.v_front_porch = 2; + + err = calc_ref_to_sync(&mode); + if (err) { + dev_err(&dc->ndev->dev, "display timing ref_to_sync" + "calculation failed with code %d\n", err); + return -EINVAL; + } + dev_info(&dc->ndev->dev, "Calculated sync href=%d vref=%d\n", + mode.h_ref_to_sync, mode.v_ref_to_sync); } if (!check_ref_to_sync(&mode)) { dev_err(&dc->ndev->dev, - "Display timing doesn't meet restrictions.\n"); + "display timing doesn't meet restrictions.\n"); return -EINVAL; } - dev_info(&dc->ndev->dev, "Using mode %dx%d pclk=%d href=%d vref=%d\n", - mode.h_active, mode.v_active, mode.pclk, - mode.h_ref_to_sync, mode.v_ref_to_sync - ); #ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT /* Double the pixel clock and update v_active only for diff --git a/drivers/video/tegra/dc/nvhdcp.c b/drivers/video/tegra/dc/nvhdcp.c index 3566e2bd33b5..8489ebf69719 100644 --- a/drivers/video/tegra/dc/nvhdcp.c +++ b/drivers/video/tegra/dc/nvhdcp.c @@ -760,7 +760,7 @@ static int get_repeater_info(struct tegra_nvhdcp *nvhdcp) { int e, retries; u8 b_caps; - u16 b_status; + u16 b_status = 0; nvhdcp_vdbg("repeater found:fetching repeater info\n"); diff --git a/drivers/video/tegra/dc/window.c b/drivers/video/tegra/dc/window.c index cd91fab428ed..af18564f45b9 100644 --- a/drivers/video/tegra/dc/window.c +++ b/drivers/video/tegra/dc/window.c @@ -24,7 +24,9 @@ #include "dc_priv.h" static int no_vsync; +#ifndef CONFIG_ANDROID static atomic_t frame_end_ref = ATOMIC_INIT(0); +#endif /* !CONFIG_ANDROID */ module_param_named(no_vsync, no_vsync, int, S_IRUGO | S_IWUSR); @@ -41,6 +43,7 @@ static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[], return true; } +#ifndef CONFIG_ANDROID int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable) { tegra_dc_writel(dc, FRAME_END_INT, DC_CMD_INT_STATUS); @@ -51,6 +54,7 @@ int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable) tegra_dc_mask_interrupt(dc, FRAME_END_INT); return 0; } +#endif /* !CONFIG_ANDROID */ static int get_topmost_window(u32 *depths, unsigned long *wins) { @@ -418,9 +422,14 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n) FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); } else { clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); - tegra_dc_mask_interrupt(dc, V_BLANK_INT | ALL_UF_INT); + tegra_dc_mask_interrupt(dc, +#ifndef CONFIG_ANDROID + V_BLANK_INT | ALL_UF_INT); if (!atomic_read(&frame_end_ref)) tegra_dc_mask_interrupt(dc, FRAME_END_INT); +#else /* !CONFIG_ANDROID */ + FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); +#endif /* !CONFIG_ANDROID */ } if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) @@ -470,7 +479,11 @@ void tegra_dc_trigger_windows(struct tegra_dc *dc) if (!dirty) { if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) +#ifndef CONFIG_ANDROID && !atomic_read(&frame_end_ref)) +#else /* !CONFIG_ANDROID */ + ) +#endif /* !CONFIG_ANDROID */ tegra_dc_mask_interrupt(dc, FRAME_END_INT); } diff --git a/drivers/video/tegra/fb.c b/drivers/video/tegra/fb.c index f69048f62cc2..b7aa066751fc 100644 --- a/drivers/video/tegra/fb.c +++ b/drivers/video/tegra/fb.c @@ -55,9 +55,6 @@ struct tegra_fb_info { bool valid; struct resource *fb_mem; - - int xres; - int yres; }; /* palette array used by the fbcon */ @@ -75,10 +72,18 @@ static int tegra_fb_check_var(struct fb_var_screeninfo *var, info->screen_size) return -EINVAL; + fb_var_to_videomode(&mode, var); + +#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_COLIBRI_T20) || \ +defined(CONFIG_MACH_COLIBRI_T30) + /* Hack: avoid 24 Hz mode in X resulting in no display at all */ + if (mode.refresh < 50) + return -EINVAL; +#endif /* CONFIG_MACH_APALIS_T30 | CONFIG_MACH_COLIBRI_T20 | + CONFIG_MACH_COLIBRI_T30 */ + /* Apply mode filter for HDMI only -LVDS supports only fix mode */ if (ops && ops->mode_filter) { - - fb_var_to_videomode(&mode, var); if (!ops->mode_filter(dc, &mode)) return -EINVAL; @@ -87,8 +92,35 @@ static int tegra_fb_check_var(struct fb_var_screeninfo *var, } /* Double yres_virtual to allow double buffering through pan_display */ + var->xres_virtual = var->xres; var->yres_virtual = var->yres * 2; + /* we only support RGB ordering for now */ + switch (var->bits_per_pixel) { + case 32: + case 24: + var->bits_per_pixel = 32; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + case 16: + default: + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + } + return 0; } @@ -97,103 +129,61 @@ static int tegra_fb_set_par(struct fb_info *info) struct tegra_fb_info *tegra_fb = info->par; struct fb_var_screeninfo *var = &info->var; struct tegra_dc *dc = tegra_fb->win->dc; + int err; - if (var->bits_per_pixel) { - /* we only support RGB ordering for now */ - switch (var->bits_per_pixel) { - case 32: - var->red.offset = 0; - var->red.length = 8; - var->green.offset = 8; - var->green.length = 8; - var->blue.offset = 16; - var->blue.length = 8; - var->transp.offset = 24; - var->transp.length = 8; - tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8; - break; - case 16: - var->red.offset = 11; - var->red.length = 5; - var->green.offset = 5; - var->green.length = 6; - var->blue.offset = 0; - var->blue.length = 5; - tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5; - break; - - default: - return -EINVAL; - } - /* if line_length unset, then pad the stride */ - info->fix.line_length = var->xres * var->bits_per_pixel / 8; - info->fix.line_length = round_up(info->fix.line_length, - TEGRA_LINEAR_PITCH_ALIGNMENT); - tegra_fb->win->stride = info->fix.line_length; - tegra_fb->win->stride_uv = 0; - tegra_fb->win->phys_addr_u = 0; - tegra_fb->win->phys_addr_v = 0; - } - - if (var->pixclock) { - bool stereo; - unsigned old_len = 0; - struct fb_videomode m; - struct fb_videomode *old_mode = NULL; - - fb_var_to_videomode(&m, var); + struct tegra_dc_mode mode; - /* Load framebuffer info with new mode details*/ - old_mode = info->mode; - old_len = info->fix.line_length; - - info->mode = (struct fb_videomode *) - fb_find_nearest_mode(&m, &info->modelist); - if (!info->mode) { - dev_warn(&tegra_fb->ndev->dev, "can't match video mode\n"); - info->mode = old_mode; - return -EINVAL; - } + /* This is usually altered to 16/32 by tegra_fb_check_var + * above which is called before this function + */ + switch (var->bits_per_pixel) { + case 32: + tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8; + break; + case 16: + tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5; + break; + default: + return -EINVAL; + break; + } - /* Update fix line_length and window stride as per new mode */ - info->fix.line_length = var->xres * var->bits_per_pixel / 8; - info->fix.line_length = round_up(info->fix.line_length, - TEGRA_LINEAR_PITCH_ALIGNMENT); - tegra_fb->win->stride = info->fix.line_length; + /* if line_length unset, then pad the stride */ + info->fix.line_length = var->xres * var->bits_per_pixel / 8; + info->fix.line_length = round_up(info->fix.line_length, + TEGRA_LINEAR_PITCH_ALIGNMENT); + tegra_fb->win->stride = info->fix.line_length; + tegra_fb->win->stride_uv = 0; + tegra_fb->win->phys_addr_u = 0; + tegra_fb->win->phys_addr_v = 0; + + tegra_fb->win->w.full = dfixed_const(var->xres); + tegra_fb->win->h.full = dfixed_const(var->yres); + tegra_fb->win->out_w = var->xres; + tegra_fb->win->out_h = var->yres; + + dev_info(&tegra_fb->ndev->dev, "switching framebuffer to %dx%d\n", + var->xres, var->yres); + + err = tegra_dc_var_to_dc_mode(dc, var, &mode); + if (err) { + dev_warn(&tegra_fb->ndev->dev, "could not convert var %d\n", err); + return -EINVAL; + } - /* - * only enable stereo if the mode supports it and - * client requests it - */ - stereo = !!(var->vmode & info->mode->vmode & -#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT - FB_VMODE_STEREO_FRAME_PACK); -#else - FB_VMODE_STEREO_LEFT_RIGHT); -#endif + err = tegra_dc_set_mode(dc, &mode); + if (err) { + dev_warn(&tegra_fb->ndev->dev, "could not set dc mode %d\n", err); + return -EINVAL; + } - /* Configure DC with new mode */ - if (tegra_dc_set_fb_mode(dc, info->mode, stereo)) { - /* Error while configuring DC, fallback to old mode */ - dev_warn(&tegra_fb->ndev->dev, "can't configure dc with mode %ux%u\n", - info->mode->xres, info->mode->yres); - info->mode = old_mode; - info->fix.line_length = old_len; - tegra_fb->win->stride = old_len; - return -EINVAL; - } + /* Reflect changes on HW */ + if (dc->enabled) + tegra_dc_disable(dc); + tegra_dc_enable(dc); - /* Reflect mode chnage on DC HW */ - if (dc->enabled) - tegra_dc_disable(dc); - tegra_dc_enable(dc); + return err; - tegra_fb->win->w.full = dfixed_const(info->mode->xres); - tegra_fb->win->h.full = dfixed_const(info->mode->yres); - tegra_fb->win->out_w = info->mode->xres; - tegra_fb->win->out_h = info->mode->yres; - } - return 0; } static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green, @@ -417,6 +407,9 @@ static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long } int tegra_fb_get_mode(struct tegra_dc *dc) { + /* Avoid error when reading sysfs */ + if (dc->fb->info->mode == NULL) + return 0; return dc->fb->info->mode->refresh; } @@ -532,13 +525,11 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info, /* Prepare a mode db */ for (i = 0; i < specs->modedb_len; i++) { if (info->fbops->fb_check_var) { - struct fb_videomode m; - /* Call mode filter to check mode */ fb_videomode_to_var(&var, &specs->modedb[i]); if (!(info->fbops->fb_check_var(&var, info))) { - fb_var_to_videomode(&m, &var); - fb_add_videomode(&m, + fb_var_to_videomode(&specs->modedb[i], &var); + fb_add_videomode(&specs->modedb[i], &fb_info->info->modelist); /* EDID stds recommend first detailed mode to be applied as default,but if first mode @@ -584,6 +575,106 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info, mutex_unlock(&fb_info->info->lock); } +struct tegra_dc_out_pin dc_out_pins[4]; + + +static int parse_opt(struct tegra_dc_out *out, char *this_opt) +{ + if (!strncmp(this_opt, "hsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "vsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "outputen:", 9)) { + if (simple_strtoul(this_opt+9, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "pixclockpol:", 12)) { + if (simple_strtoul(this_opt+12, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } + + return -1; +} + +static void tegra_dc_copy_pin_modes(struct tegra_dc_out *out) +{ + int i; + struct tegra_dc_out_pin *def = out->out_pins; + int n_out_pins_default = out->n_out_pins; + + /* Allocate memory for dynamic output pin configuration... */ + out->n_out_pins = 4; + out->out_pins = kmalloc(sizeof(struct tegra_dc_out_pin) * out->n_out_pins, + GFP_KERNEL); + + /* ...set fallback values, we use the pin enum as array index... */ + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].name = TEGRA_DC_OUT_PIN_DATA_ENABLE; + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = TEGRA_DC_OUT_PIN_POL_HIGH; + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].name = TEGRA_DC_OUT_PIN_H_SYNC; + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = TEGRA_DC_OUT_PIN_POL_LOW; + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].name = TEGRA_DC_OUT_PIN_V_SYNC; + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = TEGRA_DC_OUT_PIN_POL_LOW; + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].name = TEGRA_DC_OUT_PIN_PIXEL_CLOCK; + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = TEGRA_DC_OUT_PIN_POL_LOW; + + /* ... and copy the static default config from platform data */ + for (i = 0; i < n_out_pins_default; i++) + out->out_pins[def[i].name].pol = def[i].pol; +} + +static int tegra_parse_options(struct tegra_dc_out *out, struct fb_info *info, + char *option) +{ + char *this_opt; + + /* This off option works perfectly for framebuffer + * device, however the tegra binary driver somehow + * has troubles to handle a missing fb0 + * (then, dc1 gets remapped to fb0, which seems + * to be an issue for the binary driver)... + */ + if (!strcmp(option, "off")) + return -ENODEV; + + while ((this_opt = strsep(&option, ",")) != NULL) { + /* Parse driver specific arguments for RGB output */ + if (out->type == TEGRA_DC_OUT_RGB) { + if (parse_opt(out, this_opt) == 0) + continue; + } + + /* No valid driver specific argument, has to be mode */ + if (!tegra_fb_find_mode(&info->var, info, this_opt, 16)) + return -EINVAL; + } + return 0; +} + struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, struct tegra_dc *dc, struct tegra_fb_data *fb_data, @@ -597,6 +688,9 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, unsigned long fb_phys = 0; int ret = 0; unsigned stride; + char *param_option = NULL; + char *option = NULL; + char driver[10]; win = tegra_dc_get_window(dc, fb_data->win); if (!win) { @@ -615,8 +709,6 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, tegra_fb->win = win; tegra_fb->ndev = ndev; tegra_fb->fb_mem = fb_mem; - tegra_fb->xres = fb_data->xres; - tegra_fb->yres = fb_data->yres; if (fb_mem) { fb_size = resource_size(fb_mem); @@ -653,6 +745,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, info->var.xres_virtual = fb_data->xres; info->var.yres_virtual = fb_data->yres * 2; info->var.bits_per_pixel = fb_data->bits_per_pixel; + info->var.activate = FB_ACTIVATE_VBL; info->var.height = tegra_dc_get_out_height(dc); info->var.width = tegra_dc_get_out_width(dc); @@ -665,11 +758,11 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, info->var.vsync_len = 0; info->var.vmode = FB_VMODE_NONINTERLACED; + /* window settings */ win->x.full = dfixed_const(0); win->y.full = dfixed_const(0); win->w.full = dfixed_const(fb_data->xres); win->h.full = dfixed_const(fb_data->yres); - /* TODO: set to output res dc */ win->out_x = 0; win->out_y = 0; win->out_w = fb_data->xres; @@ -683,6 +776,32 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, win->stride_uv = 0; win->flags = TEGRA_WIN_FLAG_ENABLED; + /* Set/copy default pin modes, if output is RGB... */ + if (dc->out->type == TEGRA_DC_OUT_RGB) + tegra_dc_copy_pin_modes(dc->out); + + /* try to use kernel cmd line specified mode */ + sprintf(driver, "tegrafb%d", ndev->id); + fb_get_options(driver, ¶m_option); + if (param_option != NULL) { + option = param_option; + dev_info(&ndev->dev, "use cmd options for %s: %s\n", + driver, option); + } else { + option = dc->out->default_mode; + dev_info(&ndev->dev, "use default mode for %s: %s\n", + driver, option); + } + + if (option != NULL) { + ret = tegra_parse_options(dc->out, info, option); + if (ret < 0) + goto err_iounmap_fb; + } + + /* Activate current settings (tegra_fb_find_mode has call + * tegra_fb_check_var already) + */ if (fb_mem) tegra_fb_set_par(info); @@ -701,26 +820,6 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, tegra_dc_sync_windows(&tegra_fb->win, 1); } - if (dc->mode.pclk > 1000) { - struct tegra_dc_mode *mode = &dc->mode; - struct fb_videomode vmode; - - if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) - info->var.pixclock = KHZ2PICOS(mode->rated_pclk / 1000); - else - info->var.pixclock = KHZ2PICOS(mode->pclk / 1000); - info->var.left_margin = mode->h_back_porch; - info->var.right_margin = mode->h_front_porch; - info->var.upper_margin = mode->v_back_porch; - info->var.lower_margin = mode->v_front_porch; - info->var.hsync_len = mode->h_sync_width; - info->var.vsync_len = mode->v_sync_width; - - /* Keep info->var consistent with info->modelist. */ - fb_var_to_videomode(&vmode, &info->var); - fb_add_videomode(&vmode, &info->modelist); - } - return tegra_fb; err_iounmap_fb: diff --git a/drivers/video/tegra/host/bus_client.c b/drivers/video/tegra/host/bus_client.c index 4614689ec37c..2b92a62cc0bc 100644 --- a/drivers/video/tegra/host/bus_client.c +++ b/drivers/video/tegra/host/bus_client.c @@ -611,6 +611,7 @@ fail: nvhost_free_channel(ch); return err; } +EXPORT_SYMBOL(nvhost_client_device_init); int nvhost_client_device_suspend(struct nvhost_device *dev) { @@ -658,6 +659,7 @@ fail: return -ENXIO; } +EXPORT_SYMBOL(nvhost_client_device_get_resources); void nvhost_client_device_put_resources(struct nvhost_device *dev) { @@ -670,3 +672,4 @@ void nvhost_client_device_put_resources(struct nvhost_device *dev) release_mem_region(r->start, resource_size(r)); } +EXPORT_SYMBOL(nvhost_client_device_put_resources); diff --git a/drivers/video/tegra/host/nvhost_acm.c b/drivers/video/tegra/host/nvhost_acm.c index 5bde55ad2ff5..f22c91ac03f1 100644 --- a/drivers/video/tegra/host/nvhost_acm.c +++ b/drivers/video/tegra/host/nvhost_acm.c @@ -639,8 +639,10 @@ void nvhost_module_busy_ext(struct nvhost_device *dev) { nvhost_module_busy(dev); } +EXPORT_SYMBOL(nvhost_module_busy_ext); void nvhost_module_idle_ext(struct nvhost_device *dev) { nvhost_module_idle(dev); } +EXPORT_SYMBOL(nvhost_module_idle_ext); diff --git a/drivers/video/tegra/host/nvhost_syncpt.c b/drivers/video/tegra/host/nvhost_syncpt.c index 38c28ca116e7..de3e04f65cdd 100644 --- a/drivers/video/tegra/host/nvhost_syncpt.c +++ b/drivers/video/tegra/host/nvhost_syncpt.c @@ -493,12 +493,14 @@ void nvhost_syncpt_cpu_incr_ext(struct nvhost_device *dev, u32 id) struct nvhost_syncpt *sp = &(nvhost_get_host(dev)->syncpt); nvhost_syncpt_cpu_incr(sp, id); } +EXPORT_SYMBOL(nvhost_syncpt_cpu_incr_ext); u32 nvhost_syncpt_read_ext(struct nvhost_device *dev, u32 id) { struct nvhost_syncpt *sp = &(nvhost_get_host(dev)->syncpt); return nvhost_syncpt_read(sp, id); } +EXPORT_SYMBOL(nvhost_syncpt_read_ext); int nvhost_syncpt_wait_timeout_ext(struct nvhost_device *dev, u32 id, u32 thresh, u32 timeout, u32 *value) @@ -506,3 +508,4 @@ int nvhost_syncpt_wait_timeout_ext(struct nvhost_device *dev, u32 id, u32 thresh struct nvhost_syncpt *sp = &(nvhost_get_host(dev)->syncpt); return nvhost_syncpt_wait_timeout(sp, id, thresh, timeout, value); } +EXPORT_SYMBOL(nvhost_syncpt_wait_timeout_ext); diff --git a/drivers/video/tegra/nvmap/nvmap.c b/drivers/video/tegra/nvmap/nvmap.c index 19b821d41284..e66ca982f2f3 100644 --- a/drivers/video/tegra/nvmap/nvmap.c +++ b/drivers/video/tegra/nvmap/nvmap.c @@ -387,6 +387,7 @@ phys_addr_t nvmap_pin(struct nvmap_client *client, return ret ?: phys; } +EXPORT_SYMBOL(nvmap_pin); phys_addr_t nvmap_handle_address(struct nvmap_client *c, unsigned long id) { @@ -413,6 +414,7 @@ void nvmap_unpin(struct nvmap_client *client, struct nvmap_handle_ref *ref) if (handle_unpin(client, ref->handle, false)) wake_up(&client->share->pin_wait); } +EXPORT_SYMBOL(nvmap_unpin); void nvmap_unpin_handles(struct nvmap_client *client, struct nvmap_handle **h, int nr) @@ -501,6 +503,7 @@ void *nvmap_mmap(struct nvmap_handle_ref *ref) * nvmap_handle_put will be called by unmapping this address */ return p; } +EXPORT_SYMBOL(nvmap_mmap); void nvmap_munmap(struct nvmap_handle_ref *ref, void *addr) { @@ -523,6 +526,7 @@ void nvmap_munmap(struct nvmap_handle_ref *ref, void *addr) } nvmap_handle_put(h); } +EXPORT_SYMBOL(nvmap_munmap); struct nvmap_handle_ref *nvmap_alloc(struct nvmap_client *client, size_t size, size_t align, unsigned int flags, @@ -550,6 +554,7 @@ struct nvmap_handle_ref *nvmap_alloc(struct nvmap_client *client, size_t size, return r; } +EXPORT_SYMBOL(nvmap_alloc); /* allocates memory with specifed iovm_start address. */ struct nvmap_handle_ref *nvmap_alloc_iovm(struct nvmap_client *client, @@ -605,6 +610,7 @@ void nvmap_free(struct nvmap_client *client, struct nvmap_handle_ref *r) nvmap_free_handle_id(client, nvmap_ref_to_id(r)); } +EXPORT_SYMBOL(nvmap_free); int nvmap_mark_global(struct nvmap_client *client, struct nvmap_handle_ref *r) { diff --git a/drivers/video/tegra/nvmap/nvmap_dev.c b/drivers/video/tegra/nvmap/nvmap_dev.c index 3b0db14f16ca..3ea7bd2ea797 100644 --- a/drivers/video/tegra/nvmap/nvmap_dev.c +++ b/drivers/video/tegra/nvmap/nvmap_dev.c @@ -87,6 +87,7 @@ struct nvmap_device { }; struct nvmap_device *nvmap_dev; +EXPORT_SYMBOL(nvmap_dev); static struct backing_dev_info nvmap_bdi = { .ra_pages = 0, @@ -670,6 +671,7 @@ struct nvmap_client *nvmap_create_client(struct nvmap_device *dev, spin_unlock(&dev->clients_lock); return client; } +EXPORT_SYMBOL(nvmap_create_client); static void destroy_client(struct nvmap_client *client) { @@ -756,6 +758,7 @@ void nvmap_client_put(struct nvmap_client *client) if (!atomic_dec_return(&client->count)) destroy_client(client); } +EXPORT_SYMBOL(nvmap_client_put); static int nvmap_open(struct inode *inode, struct file *filp) { diff --git a/drivers/video/tegra/nvmap/nvmap_handle.c b/drivers/video/tegra/nvmap/nvmap_handle.c index 4b7760b22190..c1add8383e83 100644 --- a/drivers/video/tegra/nvmap/nvmap_handle.c +++ b/drivers/video/tegra/nvmap/nvmap_handle.c @@ -572,9 +572,10 @@ static int handle_page_alloc(struct nvmap_client *client, #ifdef CONFIG_NVMAP_PAGE_POOLS struct nvmap_page_pool *pool = NULL; struct nvmap_share *share = nvmap_get_share_from_dev(h->dev); + unsigned long paddr; #endif gfp_t gfp = GFP_NVMAP; - unsigned long kaddr, paddr; + unsigned long kaddr; pte_t **pte = NULL; if (h->userflags & NVMAP_HANDLE_ZEROED_PAGES) { diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 9a94f7e093dd..8d41441b05ed 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -217,17 +217,20 @@ config TEGRA_WATCHDOG depends on ARCH_TEGRA help Say Y here to include support for the watchdog timer - embedded in NVIDIA Tegra SoCs. + embedded in NVIDIA Tegra SoCs. The default timeout + is set to 60 seconds. To compile this driver as a module, choose M here: the module will be called tegra_wdt. -config TEGRA_WATCHDOG_ENABLE_ON_PROBE - tristate "Turn on Tegra watchdog at system start up" - depends on ARCH_TEGRA && TEGRA_WATCHDOG +config TEGRA_WATCHDOG_ENABLE_HEARTBEAT + bool "Use the first Tegra watchdog as kernel heartbeats" + depends on TEGRA_WATCHDOG + default n help - Say Y here to enable the tegra watchdog at driver - probe time, rather than when the device is opened. + Say Y here to enable the first tegra watchdog at driver + probe time. The kernel will reload the watchdog by + itself in an interrupt handler. config EP93XX_WATCHDOG tristate "EP93xx Watchdog" diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c index 1bcd726ebbd6..d32c516b2c55 100644 --- a/drivers/watchdog/tegra_wdt.c +++ b/drivers/watchdog/tegra_wdt.c @@ -4,6 +4,7 @@ * watchdog driver for NVIDIA tegra internal watchdog * * Copyright (c) 2012, NVIDIA Corporation. + * Copyright (c) 2013, Toradex AG * * based on drivers/watchdog/softdog.c and drivers/watchdog/omap_wdt.c * @@ -42,13 +43,33 @@ /* minimum and maximum watchdog trigger periods, in seconds */ #define MIN_WDT_PERIOD 5 #define MAX_WDT_PERIOD 1000 + /* Assign Timer 7 to Timer 10 for WDT0 to WDT3, respectively */ #define TMR_SRC_START 7 +/* + * For spinlock lockup detection to work, the heartbeat should be 2*lockup + * for cases where the spinlock disabled irqs. + * Must be between MIN_WDT_PERIOD and MAX_WDT_PERIOD + */ +#define WDT_DEFAULT_TIME 60 + +static int heartbeat = WDT_DEFAULT_TIME; +static bool nowayout = WATCHDOG_NOWAYOUT; + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds (default=" + __MODULE_STRING(WDT_DEFAULT_TIME)); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif enum tegra_wdt_status { WDT_DISABLED = 1 << 0, WDT_ENABLED = 1 << 1, - WDT_ENABLED_AT_PROBE = 1 << 2, + WDT_KERNEL_HEARTBEAT = 1 << 2, }; struct tegra_wdt { @@ -61,18 +82,15 @@ struct tegra_wdt { void __iomem *wdt_source; void __iomem *wdt_timer; void __iomem *int_base; - int irq; int tmrsrc; int timeout; int status; + int way_out_ok; + struct tasklet_struct tasklet; + int irq; + int irq_counter; }; -/* - * For spinlock lockup detection to work, the heartbeat should be 2*lockup - * for cases where the spinlock disabled irqs. - */ -static int heartbeat = 120; /* must be greater than MIN_WDT_PERIOD and lower than MAX_WDT_PERIOD */ - #if defined(CONFIG_ARCH_TEGRA_2x_SOC) #define TIMER_PTV 0x0 @@ -107,6 +125,16 @@ static void tegra_wdt_disable(struct tegra_wdt *wdt) static inline void tegra_wdt_ping(struct tegra_wdt *wdt) { + /* Reset timer */ + tegra_wdt_enable(wdt); + + /* Reenable IRQ in case an interrupt already happend */ + if (wdt->irq_counter) { + writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); + wdt->irq_counter = 0; + enable_irq(wdt->irq); + } + return; } @@ -114,9 +142,30 @@ static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { struct tegra_wdt *wdt = dev_id; - writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); + wdt->irq_counter++; + + if (wdt->status & WDT_KERNEL_HEARTBEAT) { + tegra_wdt_ping(wdt); + } else { + /* If not in heartbeat mode, disable IRQs to avoid IRQ storm + * We don't acknowledge the interrupt here since the user + * did not reload the timer in time. Upon next interrupt + * the system will reset... + */ + tasklet_schedule(&wdt->tasklet); + disable_irq_nosync(irq); + } + return IRQ_HANDLED; } + +static void tegra_wdt_do_tasklet(unsigned long data) +{ + struct tegra_wdt *wdt = (struct tegra_wdt *)data; + dev_info(wdt->miscdev.parent, "Watchdog interrupt received, system " + "will reset soon if no ping arrives\n"); +} + #elif defined(CONFIG_ARCH_TEGRA_3x_SOC) #define TIMER_PTV 0 @@ -132,6 +181,7 @@ static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) #define WDT_CFG_PMC2CAR_RST_EN (1 << 15) #define WDT_STATUS (4) #define WDT_INTR_STAT (1 << 1) + #define WDT_STATUS_EXPIR_COUNTER (3 << 12) #define WDT_CMD (8) #define WDT_CMD_START_COUNTER (1 << 0) #define WDT_CMD_DISABLE_COUNTER (1 << 1) @@ -169,19 +219,21 @@ static void tegra_wdt_enable(struct tegra_wdt *wdt) val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->wdt_timer + TIMER_PTV); - /* Interrupt handler is not required for user space - * WDT accesses, since the caller is responsible to ping the - * WDT to reset the counter before expiration, through ioctls. + writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); + + /* Interrupt handler is not required for user space since + * a warning in a fourth of time don't make sense. Then + * the interrupt line is also shared, so it can't be disabled + * if one watchdog is about to expire... (interrupt storm) * SYS_RST_EN doesnt work as there is no external reset * from Tegra. */ - val = wdt->tmrsrc | WDT_CFG_PERIOD | /*WDT_CFG_INT_EN |*/ + val = wdt->tmrsrc | WDT_CFG_PERIOD | /* WDT_CFG_INT_EN | */ /*WDT_CFG_SYS_RST_EN |*/ WDT_CFG_PMC2CAR_RST_EN; #ifdef CONFIG_TEGRA_FIQ_DEBUGGER val |= WDT_CFG_FIQ_INT_EN; #endif writel(val, wdt->wdt_source + WDT_CFG); - writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); } static void tegra_wdt_disable(struct tegra_wdt *wdt) @@ -192,17 +244,30 @@ static void tegra_wdt_disable(struct tegra_wdt *wdt) writel(0, wdt->wdt_timer + TIMER_PTV); } +static void tegra_wdt_interrupt_instance(struct tegra_wdt *wdt) +{ + WARN_ON_ONCE(!(wdt->status & WDT_KERNEL_HEARTBEAT)); + + if (wdt->status & WDT_KERNEL_HEARTBEAT) { + tegra_wdt_ping(wdt); + } +} + static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { + struct tegra_wdt *wdt = NULL; unsigned i, status; for (i = 0; i < MAX_NR_CPU_WDT; i++) { - if (tegra_wdt[i] == NULL) + wdt = tegra_wdt[i]; + if (wdt == NULL) continue; - status = readl(tegra_wdt[i]->wdt_source + WDT_STATUS); - if ((tegra_wdt[i]->status & WDT_ENABLED) && - (status & WDT_INTR_STAT)) - tegra_wdt_ping(tegra_wdt[i]); + status = readl(wdt->wdt_source + WDT_STATUS); + if ((wdt->status & WDT_ENABLED) && + (status & WDT_INTR_STAT)) + { + tegra_wdt_interrupt_instance(wdt); + } } return IRQ_HANDLED; @@ -228,6 +293,9 @@ static int tegra_wdt_open(struct inode *inode, struct file *file) if (test_and_set_bit(1, &wdt->users)) return -EBUSY; + /* Reset no way out, we need a new magic again */ + wdt->way_out_ok = 0; + wdt->status |= WDT_ENABLED; wdt->timeout = heartbeat; tegra_wdt_enable(wdt); @@ -239,13 +307,19 @@ static int tegra_wdt_release(struct inode *inode, struct file *file) { struct tegra_wdt *wdt = file->private_data; - if (wdt->status == WDT_ENABLED) { -#ifndef CONFIG_WATCHDOG_NOWAYOUT - tegra_wdt_disable(wdt); - wdt->status = WDT_DISABLED; -#endif + if (wdt->status & WDT_ENABLED && !nowayout) { + if (wdt->way_out_ok) { + tegra_wdt_disable(wdt); + wdt->status = WDT_DISABLED; + } else { + dev_info(wdt->miscdev.parent, "No Magic Close " + "received, watchdog not disabled!\n"); + } + } else if (nowayout) { + dev_info(wdt->miscdev.parent, "No way out is " + "enabled, watchdog not disabled!\n"); } - wdt->users = 0; + clear_bit(1, &wdt->users); return 0; } @@ -288,11 +362,10 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, return put_user(wdt->timeout, (int __user *)arg); case WDIOC_SETOPTIONS: -#ifndef CONFIG_WATCHDOG_NOWAYOUT if (get_user(option, (int __user *)arg)) return -EFAULT; spin_lock(&lock); - if (option & WDIOS_DISABLECARD) { + if (option & WDIOS_DISABLECARD && !nowayout) { wdt->status &= ~WDT_ENABLED; wdt->status |= WDT_DISABLED; tegra_wdt_disable(wdt); @@ -306,9 +379,6 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, } spin_unlock(&lock); return 0; -#else - return -EINVAL; -#endif } return -ENOTTY; } @@ -316,6 +386,19 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, static ssize_t tegra_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { + struct tegra_wdt *wdt = file->private_data; + + /* check if way-out char was written as last data */ + char c; + if(len) { + tegra_wdt_ping(wdt); + + if(get_user(c, data + len - 1)) + return -EFAULT; + else + wdt->way_out_ok = (('V' == c) ? 1 : 0); + } + return len; } @@ -333,8 +416,14 @@ static int tegra_wdt_probe(struct platform_device *pdev) struct resource *res_src, *res_wdt, *res_irq; struct resource *res_int_base = NULL; struct tegra_wdt *wdt; - u32 src; int ret = 0; +#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + int is_heartbeat_wdt = 1; +#else + int is_heartbeat_wdt = !(pdev->id); +#endif +#endif if (pdev->id < -1 && pdev->id > 3) { dev_err(&pdev->dev, "only IDs 3:0 supported\n"); @@ -369,7 +458,6 @@ static int tegra_wdt_probe(struct platform_device *pdev) return -ENOMEM; } - wdt->irq = -1; wdt->miscdev.parent = &pdev->dev; if (pdev->id == -1) { wdt->miscdev.minor = WATCHDOG_MINOR; @@ -410,12 +498,17 @@ static int tegra_wdt_probe(struct platform_device *pdev) goto fail; } - src = readl(wdt->wdt_source); - if (src & BIT(12)) +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + /* Watchdog of Tegra 3 are not at the reset controller regs */ + if (readl(wdt->wdt_source) & BIT(12)) dev_info(&pdev->dev, "last reset due to watchdog timeout\n"); +#endif tegra_wdt_disable(wdt); writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); +#ifndef CONFIG_ARCH_TEGRA_2x_SOC + writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); +#endif if (res_irq != NULL) { #ifdef CONFIG_TEGRA_FIQ_DEBUGGER @@ -436,6 +529,7 @@ static int tegra_wdt_probe(struct platform_device *pdev) goto fail; tegra_wdt_int_priority(wdt); #endif + wdt->irq = -1; ret = request_irq(res_irq->start, tegra_wdt_interrupt, IRQF_DISABLED, dev_name(&pdev->dev), wdt); if (ret) { @@ -464,24 +558,26 @@ static int tegra_wdt_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, wdt); +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + tasklet_init(&wdt->tasklet, tegra_wdt_do_tasklet, (unsigned long)wdt); +#endif -#ifndef CONFIG_ARCH_TEGRA_2x_SOC -#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_ON_PROBE - /* Init and enable watchdog on WDT0 with timer 8 during probe */ - if (!(pdev->id)) { - u32 val = 0; - wdt->status = WDT_ENABLED | WDT_ENABLED_AT_PROBE; +#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT + /* Enable first watchdog during probe */ + if (is_heartbeat_wdt) { + wdt->status = WDT_ENABLED | WDT_KERNEL_HEARTBEAT; wdt->timeout = heartbeat; + set_bit(1, &wdt->users); tegra_wdt_enable(wdt); - val = readl(wdt->wdt_source + WDT_CFG); - val |= WDT_CFG_INT_EN; - writel(val, wdt->wdt_source + WDT_CFG); - pr_info("WDT heartbeat enabled on probe\n"); + pr_info("WDT kernel heartbeat enabled on probe\n"); } #endif + +#ifndef CONFIG_ARCH_TEGRA_2x_SOC tegra_wdt[pdev->id] = wdt; #endif - pr_info("%s done\n", __func__); + + dev_info(&pdev->dev, "%s done\n", __func__); return 0; fail: if (wdt->irq != -1) @@ -543,16 +639,6 @@ static int tegra_wdt_resume(struct platform_device *pdev) if (wdt->status & WDT_ENABLED) tegra_wdt_enable(wdt); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - /* Enable interrupt for WDT3 heartbeat watchdog */ - if (wdt->status & WDT_ENABLED_AT_PROBE) { - u32 val = 0; - val = readl(wdt->wdt_source + WDT_CFG); - val |= WDT_CFG_INT_EN; - writel(val, wdt->wdt_source + WDT_CFG); - pr_info("WDT heartbeat enabled on probe\n"); - } -#endif return 0; } #endif @@ -586,10 +672,6 @@ module_exit(tegra_wdt_exit); MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra Watchdog Driver"); -module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, - "Watchdog heartbeat period in seconds"); - MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:tegra_wdt"); |