diff options
Diffstat (limited to 'drivers')
344 files changed, 21785 insertions, 6592 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index 2eced9afba53..8f8bdc9e3d29 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_USB_PHY) += usb/ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_PCI) += usb/ obj-$(CONFIG_USB_GADGET) += usb/ +obj-$(CONFIG_OF) += usb/ obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ obj-$(CONFIG_INPUT) += input/ diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index a82fc022d34b..832e885349b1 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -22,7 +22,7 @@ config ANDROID_BINDER_IPC config ANDROID_BINDER_DEVICES string "Android Binder devices" depends on ANDROID_BINDER_IPC - default "binder" + default "binder,hwbinder" ---help--- Default value for the binder.devices parameter. diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 8a8e403644d6..9ae6681c90ad 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -13,8 +13,22 @@ menuconfig AUXDISPLAY If you say N, all options in this submenu will be skipped and disabled. +config CHARLCD + tristate "Character LCD core support" if COMPILE_TEST + if AUXDISPLAY +config HD44780 + tristate "HD44780 Character LCD support" + depends on GPIOLIB || COMPILE_TEST + select CHARLCD + ---help--- + Enable support for Character LCDs using a HD44780 controller. + The LCD is accessible through the /dev/lcd char device (10, 156). + This code can either be compiled as a module, or linked into the + kernel and started at boot. + If you don't understand what all this is about, say N. + config KS0108 tristate "KS0108 LCD Controller" depends on PARPORT_PC @@ -142,3 +156,293 @@ config HT16K33 LED controller driver with keyscan. endif # AUXDISPLAY + +config ARM_CHARLCD + bool "ARM Ltd. Character LCD Driver" + depends on PLAT_VERSATILE + help + This is a driver for the character LCD found on the ARM Ltd. + Versatile and RealView Platform Baseboards. It doesn't do + very much more than display the text "ARM Linux" on the first + line and the Linux version on the second line, but that's + still useful. + +config PANEL + tristate "Parallel port LCD/Keypad Panel support" + depends on PARPORT + select CHARLCD + ---help--- + Say Y here if you have an HD44780 or KS-0074 LCD connected to your + parallel port. This driver also features 4 and 6-key keypads. The LCD + is accessible through the /dev/lcd char device (10, 156), and the + keypad through /dev/keypad (10, 185). This code can either be + compiled as a module, or linked into the kernel and started at boot. + If you don't understand what all this is about, say N. + +if PANEL + +config PANEL_PARPORT + int "Default parallel port number (0=LPT1)" + range 0 255 + default "0" + ---help--- + This is the index of the parallel port the panel is connected to. One + driver instance only supports one parallel port, so if your keypad + and LCD are connected to two separate ports, you have to start two + modules with different arguments. Numbering starts with '0' for LPT1, + and so on. + +config PANEL_PROFILE + int "Default panel profile (0-5, 0=custom)" + range 0 5 + default "5" + ---help--- + To ease configuration, the driver supports different configuration + profiles for past and recent wirings. These profiles can also be + used to define an approximative configuration, completed by a few + other options. Here are the profiles : + + 0 = custom (see further) + 1 = 2x16 parallel LCD, old keypad + 2 = 2x16 serial LCD (KS-0074), new keypad + 3 = 2x16 parallel LCD (Hantronix), no keypad + 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad + 5 = 2x40 parallel LCD (old one), with old keypad + + Custom configurations allow you to define how your display is + wired to the parallel port, and how it works. This is only intended + for experts. + +config PANEL_KEYPAD + depends on PANEL_PROFILE="0" + int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" + range 0 3 + default 0 + ---help--- + This enables and configures a keypad connected to the parallel port. + The keys will be read from character device 10,185. Valid values are : + + 0 : do not enable this driver + 1 : old 6 keys keypad + 2 : new 6 keys keypad, as used on the server at www.ant-computing.com + 3 : Nexcom NSA1045's 4 keys keypad + + New profiles can be described in the driver source. The driver also + supports simultaneous keys pressed when the keypad supports them. + +config PANEL_LCD + depends on PANEL_PROFILE="0" + int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" + range 0 5 + default 0 + ---help--- + This enables and configures an LCD connected to the parallel port. + The driver includes an interpreter for escape codes starting with + '\e[L' which are specific to the LCD, and a few ANSI codes. The + driver will be registered as character device 10,156, usually + under the name '/dev/lcd'. There are a total of 6 supported types : + + 0 : do not enable the driver + 1 : custom configuration and wiring (see further) + 2 : 2x16 & 2x40 parallel LCD (old wiring) + 3 : 2x16 serial LCD (KS-0074 based) + 4 : 2x16 parallel LCD (Hantronix wiring) + 5 : 2x16 parallel LCD (Nexcom wiring) + + When type '1' is specified, other options will appear to configure + more precise aspects (wiring, dimensions, protocol, ...). Please note + that those values changed from the 2.4 driver for better consistency. + +config PANEL_LCD_HEIGHT + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of lines on the LCD (1-2)" + range 1 2 + default 2 + ---help--- + This is the number of visible character lines on the LCD in custom profile. + It can either be 1 or 2. + +config PANEL_LCD_WIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of characters per line on the LCD (1-40)" + range 1 40 + default 40 + ---help--- + This is the number of characters per line on the LCD in custom profile. + Common values are 16,20,24,40. + +config PANEL_LCD_BWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Internal LCD line width (1-40, 40 by default)" + range 1 40 + default 40 + ---help--- + Most LCDs use a standard controller which supports hardware lines of 40 + characters, although sometimes only 16, 20 or 24 of them are really wired + to the terminal. This results in some non-visible but addressable characters, + and is the case for most parallel LCDs. Other LCDs, and some serial ones, + however, use the same line width internally as what is visible. The KS0074 + for example, uses 16 characters per line for 16 visible characters per line. + + This option lets you configure the value used by your LCD in 'custom' profile. + If you don't know, put '40' here. + +config PANEL_LCD_HWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Hardware LCD line width (1-64, 64 by default)" + range 1 64 + default 64 + ---help--- + Most LCDs use a single address bit to differentiate line 0 and line 1. Since + some of them need to be able to address 40 chars with the lower bits, they + often use the immediately superior power of 2, which is 64, to address the + next line. + + If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and + 64 here for a 2x40. + +config PANEL_LCD_CHARSET + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD character set (0=normal, 1=KS0074)" + range 0 1 + default 0 + ---help--- + Some controllers such as the KS0074 use a somewhat strange character set + where many symbols are at unusual places. The driver knows how to map + 'standard' ASCII characters to the character sets used by these controllers. + Valid values are : + + 0 : normal (untranslated) character set + 1 : KS0074 character set + + If you don't know, use the normal one (0). + +config PANEL_LCD_PROTO + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD communication mode (0=parallel 8 bits, 1=serial)" + range 0 1 + default 0 + ---help--- + This driver now supports any serial or parallel LCD wired to a parallel + port. But before assigning signals, the driver needs to know if it will + be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires + (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals + (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits + parallel LCD, and 1 for a serial LCD. + +config PANEL_LCD_PIN_E + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " + range -17 17 + default 14 + ---help--- + This describes the number of the parallel port pin to which the LCD 'E' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'E' pin in custom profile is '14' (AUTOFEED). + +config PANEL_LCD_PIN_RS + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " + range -17 17 + default 17 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RS' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RS' pin in custom profile is '17' (SELECT IN). + +config PANEL_LCD_PIN_RW + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " + range -17 17 + default 16 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RW' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RW' pin in custom profile is '16' (INIT). + +config PANEL_LCD_PIN_SCL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " + range -17 17 + default 1 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SCL' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SCL' pin in custom profile is '1' (STROBE). + +config PANEL_LCD_PIN_SDA + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " + range -17 17 + default 2 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SDA' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SDA' pin in custom profile is '2' (D0). + +config PANEL_LCD_PIN_BL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " + range -17 17 + default 0 + ---help--- + This describes the number of the parallel port pin to which the LCD 'BL' signal + has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'BL' pin in custom profile is '0' (uncontrolled). + +config PANEL_CHANGE_MESSAGE + bool "Change LCD initialization message ?" + default "n" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, + say 'N' and keep the default message with the version. + +config PANEL_BOOT_MESSAGE + depends on PANEL_CHANGE_MESSAGE="y" + string "New initialization message" + default "" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + An empty message will only clear the display at driver init time. Any other + printf()-formatted message is valid with newline and escape codes. + +endif # PANEL diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index cb3dd847713b..2b8af3dc5e42 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -2,7 +2,11 @@ # Makefile for the kernel auxiliary displays device drivers. # +obj-$(CONFIG_CHARLCD) += charlcd.o +obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_KS0108) += ks0108.o obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o +obj-$(CONFIG_HD44780) += hd44780.o obj-$(CONFIG_HT16K33) += ht16k33.o +obj-$(CONFIG_PANEL) += panel.o diff --git a/drivers/misc/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c index b3176ee92b90..b3176ee92b90 100644 --- a/drivers/misc/arm-charlcd.c +++ b/drivers/auxdisplay/arm-charlcd.c diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c new file mode 100644 index 000000000000..cfeb049a01ef --- /dev/null +++ b/drivers/auxdisplay/charlcd.c @@ -0,0 +1,818 @@ +/* + * Character LCD driver for Linux + * + * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/atomic.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/workqueue.h> + +#include <generated/utsrelease.h> + +#include <misc/charlcd.h> + +#define LCD_MINOR 156 + +#define DEFAULT_LCD_BWIDTH 40 +#define DEFAULT_LCD_HWIDTH 64 + +/* Keep the backlight on this many seconds for each flash */ +#define LCD_BL_TEMPO_PERIOD 4 + +#define LCD_FLAG_B 0x0004 /* Blink on */ +#define LCD_FLAG_C 0x0008 /* Cursor on */ +#define LCD_FLAG_D 0x0010 /* Display on */ +#define LCD_FLAG_F 0x0020 /* Large font mode */ +#define LCD_FLAG_N 0x0040 /* 2-rows mode */ +#define LCD_FLAG_L 0x0080 /* Backlight enabled */ + +/* LCD commands */ +#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ + +#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ +#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ + +#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ +#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ +#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ +#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ + +#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ +#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ +#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ + +#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ +#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ +#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ +#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ + +#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ + +#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ + +#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ +#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ + +struct charlcd_priv { + struct charlcd lcd; + + struct delayed_work bl_work; + struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ + bool bl_tempo; + + bool must_clear; + + /* contains the LCD config state */ + unsigned long int flags; + + /* Contains the LCD X and Y offset */ + struct { + unsigned long int x; + unsigned long int y; + } addr; + + /* Current escape sequence and it's length or -1 if outside */ + struct { + char buf[LCD_ESCAPE_LEN + 1]; + int len; + } esc_seq; + + unsigned long long drvdata[0]; +}; + +#define to_priv(p) container_of(p, struct charlcd_priv, lcd) + +/* Device single-open policy control */ +static atomic_t charlcd_available = ATOMIC_INIT(1); + +/* sleeps that many milliseconds with a reschedule */ +static void long_sleep(int ms) +{ + if (in_interrupt()) + mdelay(ms); + else + schedule_timeout_interruptible(msecs_to_jiffies(ms)); +} + +/* turn the backlight on or off */ +static void charlcd_backlight(struct charlcd *lcd, int on) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo) + lcd->ops->backlight(lcd, on); + mutex_unlock(&priv->bl_tempo_lock); +} + +static void charlcd_bl_off(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charlcd_priv *priv = + container_of(dwork, struct charlcd_priv, bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (priv->bl_tempo) { + priv->bl_tempo = false; + if (!(priv->flags & LCD_FLAG_L)) + priv->lcd.ops->backlight(&priv->lcd, 0); + } + mutex_unlock(&priv->bl_tempo_lock); +} + +/* turn the backlight on for a little while */ +void charlcd_poke(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + cancel_delayed_work_sync(&priv->bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) + lcd->ops->backlight(lcd, 1); + priv->bl_tempo = true; + schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); + mutex_unlock(&priv->bl_tempo_lock); +} +EXPORT_SYMBOL_GPL(charlcd_poke); + +static void charlcd_gotoxy(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + unsigned int addr; + + /* + * we force the cursor to stay at the end of the + * line if it wants to go farther + */ + addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) + : lcd->bwidth - 1; + if (priv->addr.y & 1) + addr += lcd->hwidth; + if (priv->addr.y & 2) + addr += lcd->bwidth; + lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); +} + +static void charlcd_home(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + priv->addr.x = 0; + priv->addr.y = 0; + charlcd_gotoxy(lcd); +} + +static void charlcd_print(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (priv->addr.x < lcd->bwidth) { + if (lcd->char_conv) + c = lcd->char_conv[(unsigned char)c]; + lcd->ops->write_data(lcd, c); + priv->addr.x++; + } + /* prevents the cursor from wrapping onto the next line */ + if (priv->addr.x == lcd->bwidth) + charlcd_gotoxy(lcd); +} + +static void charlcd_clear_fast(struct charlcd *lcd) +{ + int pos; + + charlcd_home(lcd); + + if (lcd->ops->clear_fast) + lcd->ops->clear_fast(lcd); + else + for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) + lcd->ops->write_data(lcd, ' '); + + charlcd_home(lcd); +} + +/* clears the display and resets X/Y */ +static void charlcd_clear_display(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); + priv->addr.x = 0; + priv->addr.y = 0; + /* we must wait a few milliseconds (15) */ + long_sleep(15); +} + +static int charlcd_init_display(struct charlcd *lcd) +{ + void (*write_cmd_raw)(struct charlcd *lcd, int cmd); + struct charlcd_priv *priv = to_priv(lcd); + u8 init; + + if (lcd->ifwidth != 4 && lcd->ifwidth != 8) + return -EINVAL; + + priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | + LCD_FLAG_C | LCD_FLAG_B; + + long_sleep(20); /* wait 20 ms after power-up for the paranoid */ + + /* + * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure + * the LCD is in 8-bit mode afterwards + */ + init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; + if (lcd->ifwidth == 4) { + init >>= 4; + write_cmd_raw = lcd->ops->write_cmd_raw4; + } else { + write_cmd_raw = lcd->ops->write_cmd; + } + write_cmd_raw(lcd, init); + long_sleep(10); + write_cmd_raw(lcd, init); + long_sleep(10); + write_cmd_raw(lcd, init); + long_sleep(10); + + if (lcd->ifwidth == 4) { + /* Switch to 4-bit mode, 1 line, small fonts */ + lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); + long_sleep(10); + } + + /* set font height and lines number */ + lcd->ops->write_cmd(lcd, + LCD_CMD_FUNCTION_SET | + ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | + ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | + ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); + long_sleep(10); + + /* display off, cursor off, blink off */ + lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); + long_sleep(10); + + lcd->ops->write_cmd(lcd, + LCD_CMD_DISPLAY_CTRL | /* set display mode */ + ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | + ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | + ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); + + charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); + + long_sleep(10); + + /* entry mode set : increment, cursor shifting */ + lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); + + charlcd_clear_display(lcd); + return 0; +} + +/* + * These are the file operation function for user access to /dev/lcd + * This function can also be called from inside the kernel, by + * setting file and ppos to NULL. + * + */ + +static inline int handle_lcd_special_code(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* LCD special codes */ + + int processed = 0; + + char *esc = priv->esc_seq.buf + 2; + int oldflags = priv->flags; + + /* check for display mode flags */ + switch (*esc) { + case 'D': /* Display ON */ + priv->flags |= LCD_FLAG_D; + processed = 1; + break; + case 'd': /* Display OFF */ + priv->flags &= ~LCD_FLAG_D; + processed = 1; + break; + case 'C': /* Cursor ON */ + priv->flags |= LCD_FLAG_C; + processed = 1; + break; + case 'c': /* Cursor OFF */ + priv->flags &= ~LCD_FLAG_C; + processed = 1; + break; + case 'B': /* Blink ON */ + priv->flags |= LCD_FLAG_B; + processed = 1; + break; + case 'b': /* Blink OFF */ + priv->flags &= ~LCD_FLAG_B; + processed = 1; + break; + case '+': /* Back light ON */ + priv->flags |= LCD_FLAG_L; + processed = 1; + break; + case '-': /* Back light OFF */ + priv->flags &= ~LCD_FLAG_L; + processed = 1; + break; + case '*': /* Flash back light */ + charlcd_poke(lcd); + processed = 1; + break; + case 'f': /* Small Font */ + priv->flags &= ~LCD_FLAG_F; + processed = 1; + break; + case 'F': /* Large Font */ + priv->flags |= LCD_FLAG_F; + processed = 1; + break; + case 'n': /* One Line */ + priv->flags &= ~LCD_FLAG_N; + processed = 1; + break; + case 'N': /* Two Lines */ + priv->flags |= LCD_FLAG_N; + break; + case 'l': /* Shift Cursor Left */ + if (priv->addr.x > 0) { + /* back one char if not at end of line */ + if (priv->addr.x < lcd->bwidth) + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + priv->addr.x--; + } + processed = 1; + break; + case 'r': /* shift cursor right */ + if (priv->addr.x < lcd->width) { + /* allow the cursor to pass the end of the line */ + if (priv->addr.x < (lcd->bwidth - 1)) + lcd->ops->write_cmd(lcd, + LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); + priv->addr.x++; + } + processed = 1; + break; + case 'L': /* shift display left */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); + processed = 1; + break; + case 'R': /* shift display right */ + lcd->ops->write_cmd(lcd, + LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | + LCD_CMD_SHIFT_RIGHT); + processed = 1; + break; + case 'k': { /* kill end of line */ + int x; + + for (x = priv->addr.x; x < lcd->bwidth; x++) + lcd->ops->write_data(lcd, ' '); + + /* restore cursor position */ + charlcd_gotoxy(lcd); + processed = 1; + break; + } + case 'I': /* reinitialize display */ + charlcd_init_display(lcd); + processed = 1; + break; + case 'G': { + /* Generator : LGcxxxxx...xx; must have <c> between '0' + * and '7', representing the numerical ASCII code of the + * redefined character, and <xx...xx> a sequence of 16 + * hex digits representing 8 bytes for each character. + * Most LCDs will only use 5 lower bits of the 7 first + * bytes. + */ + + unsigned char cgbytes[8]; + unsigned char cgaddr; + int cgoffset; + int shift; + char value; + int addr; + + if (!strchr(esc, ';')) + break; + + esc++; + + cgaddr = *(esc++) - '0'; + if (cgaddr > 7) { + processed = 1; + break; + } + + cgoffset = 0; + shift = 0; + value = 0; + while (*esc && cgoffset < 8) { + shift ^= 4; + if (*esc >= '0' && *esc <= '9') { + value |= (*esc - '0') << shift; + } else if (*esc >= 'A' && *esc <= 'Z') { + value |= (*esc - 'A' + 10) << shift; + } else if (*esc >= 'a' && *esc <= 'z') { + value |= (*esc - 'a' + 10) << shift; + } else { + esc++; + continue; + } + + if (shift == 0) { + cgbytes[cgoffset++] = value; + value = 0; + } + + esc++; + } + + lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); + for (addr = 0; addr < cgoffset; addr++) + lcd->ops->write_data(lcd, cgbytes[addr]); + + /* ensures that we stop writing to CGRAM */ + charlcd_gotoxy(lcd); + processed = 1; + break; + } + case 'x': /* gotoxy : LxXXX[yYYY]; */ + case 'y': /* gotoxy : LyYYY[xXXX]; */ + if (!strchr(esc, ';')) + break; + + while (*esc) { + if (*esc == 'x') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.x) < 0) + break; + } else if (*esc == 'y') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.y) < 0) + break; + } else { + break; + } + } + + charlcd_gotoxy(lcd); + processed = 1; + break; + } + + /* TODO: This indent party here got ugly, clean it! */ + /* Check whether one flag was changed */ + if (oldflags == priv->flags) + return processed; + + /* check whether one of B,C,D flags were changed */ + if ((oldflags ^ priv->flags) & + (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) + /* set display mode */ + lcd->ops->write_cmd(lcd, + LCD_CMD_DISPLAY_CTRL | + ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | + ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | + ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); + /* check whether one of F,N flags was changed */ + else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) + lcd->ops->write_cmd(lcd, + LCD_CMD_FUNCTION_SET | + ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | + ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | + ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); + /* check whether L flag was changed */ + else if ((oldflags ^ priv->flags) & LCD_FLAG_L) + charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); + + return processed; +} + +static void charlcd_write_char(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* first, we'll test if we're in escape mode */ + if ((c != '\n') && priv->esc_seq.len >= 0) { + /* yes, let's add this char to the buffer */ + priv->esc_seq.buf[priv->esc_seq.len++] = c; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + } else { + /* aborts any previous escape sequence */ + priv->esc_seq.len = -1; + + switch (c) { + case LCD_ESCAPE_CHAR: + /* start of an escape sequence */ + priv->esc_seq.len = 0; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + break; + case '\b': + /* go back one char and clear it */ + if (priv->addr.x > 0) { + /* + * check if we're not at the + * end of the line + */ + if (priv->addr.x < lcd->bwidth) + /* back one char */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + priv->addr.x--; + } + /* replace with a space */ + lcd->ops->write_data(lcd, ' '); + /* back one char again */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + break; + case '\014': + /* quickly clear the display */ + charlcd_clear_fast(lcd); + break; + case '\n': + /* + * flush the remainder of the current line and + * go to the beginning of the next line + */ + for (; priv->addr.x < lcd->bwidth; priv->addr.x++) + lcd->ops->write_data(lcd, ' '); + priv->addr.x = 0; + priv->addr.y = (priv->addr.y + 1) % lcd->height; + charlcd_gotoxy(lcd); + break; + case '\r': + /* go to the beginning of the same line */ + priv->addr.x = 0; + charlcd_gotoxy(lcd); + break; + case '\t': + /* print a space instead of the tab */ + charlcd_print(lcd, ' '); + break; + default: + /* simply print this char */ + charlcd_print(lcd, c); + break; + } + } + + /* + * now we'll see if we're in an escape mode and if the current + * escape sequence can be understood. + */ + if (priv->esc_seq.len >= 2) { + int processed = 0; + + if (!strcmp(priv->esc_seq.buf, "[2J")) { + /* clear the display */ + charlcd_clear_fast(lcd); + processed = 1; + } else if (!strcmp(priv->esc_seq.buf, "[H")) { + /* cursor to home */ + charlcd_home(lcd); + processed = 1; + } + /* codes starting with ^[[L */ + else if ((priv->esc_seq.len >= 3) && + (priv->esc_seq.buf[0] == '[') && + (priv->esc_seq.buf[1] == 'L')) { + processed = handle_lcd_special_code(lcd); + } + + /* LCD special escape codes */ + /* + * flush the escape sequence if it's been processed + * or if it is getting too long. + */ + if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) + priv->esc_seq.len = -1; + } /* escape codes */ +} + +static struct charlcd *the_charlcd; + +static ssize_t charlcd_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + const char __user *tmp = buf; + char c; + + for (; count-- > 0; (*ppos)++, tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + if (get_user(c, tmp)) + return -EFAULT; + + charlcd_write_char(the_charlcd, c); + } + + return tmp - buf; +} + +static int charlcd_open(struct inode *inode, struct file *file) +{ + struct charlcd_priv *priv = to_priv(the_charlcd); + + if (!atomic_dec_and_test(&charlcd_available)) + return -EBUSY; /* open only once at a time */ + + if (file->f_mode & FMODE_READ) /* device is write-only */ + return -EPERM; + + if (priv->must_clear) { + charlcd_clear_display(&priv->lcd); + priv->must_clear = false; + } + return nonseekable_open(inode, file); +} + +static int charlcd_release(struct inode *inode, struct file *file) +{ + atomic_inc(&charlcd_available); + return 0; +} + +static const struct file_operations charlcd_fops = { + .write = charlcd_write, + .open = charlcd_open, + .release = charlcd_release, + .llseek = no_llseek, +}; + +static struct miscdevice charlcd_dev = { + .minor = LCD_MINOR, + .name = "lcd", + .fops = &charlcd_fops, +}; + +static void charlcd_puts(struct charlcd *lcd, const char *s) +{ + const char *tmp = s; + int count = strlen(s); + + for (; count-- > 0; tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + charlcd_write_char(lcd, *tmp); + } +} + +/* initialize the LCD driver */ +static int charlcd_init(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + int ret; + + if (lcd->ops->backlight) { + mutex_init(&priv->bl_tempo_lock); + INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); + } + + /* + * before this line, we must NOT send anything to the display. + * Since charlcd_init_display() needs to write data, we have to + * enable mark the LCD initialized just before. + */ + ret = charlcd_init_display(lcd); + if (ret) + return ret; + + /* display a short message */ +#ifdef CONFIG_PANEL_CHANGE_MESSAGE +#ifdef CONFIG_PANEL_BOOT_MESSAGE + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); +#endif +#else + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n"); +#endif + /* clear the display on the next device opening */ + priv->must_clear = true; + charlcd_home(lcd); + return 0; +} + +struct charlcd *charlcd_alloc(unsigned int drvdata_size) +{ + struct charlcd_priv *priv; + struct charlcd *lcd; + + priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); + if (!priv) + return NULL; + + priv->esc_seq.len = -1; + + lcd = &priv->lcd; + lcd->ifwidth = 8; + lcd->bwidth = DEFAULT_LCD_BWIDTH; + lcd->hwidth = DEFAULT_LCD_HWIDTH; + lcd->drvdata = priv->drvdata; + + return lcd; +} +EXPORT_SYMBOL_GPL(charlcd_alloc); + +static int panel_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct charlcd *lcd = the_charlcd; + + switch (code) { + case SYS_DOWN: + charlcd_puts(lcd, + "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_HALT: + charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_POWER_OFF: + charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block panel_notifier = { + panel_notify_sys, + NULL, + 0 +}; + +int charlcd_register(struct charlcd *lcd) +{ + int ret; + + ret = charlcd_init(lcd); + if (ret) + return ret; + + ret = misc_register(&charlcd_dev); + if (ret) + return ret; + + the_charlcd = lcd; + register_reboot_notifier(&panel_notifier); + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_register); + +int charlcd_unregister(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + unregister_reboot_notifier(&panel_notifier); + charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); + misc_deregister(&charlcd_dev); + the_charlcd = NULL; + if (lcd->ops->backlight) { + cancel_delayed_work_sync(&priv->bl_work); + priv->lcd.ops->backlight(&priv->lcd, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_unregister); + +MODULE_LICENSE("GPL"); diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c new file mode 100644 index 000000000000..036eec404289 --- /dev/null +++ b/drivers/auxdisplay/hd44780.c @@ -0,0 +1,326 @@ +/* + * HD44780 Character LCD driver for Linux + * + * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> + +#include <misc/charlcd.h> + + +enum hd44780_pin { + /* Order does matter due to writing to GPIO array subsets! */ + PIN_DATA0, /* Optional */ + PIN_DATA1, /* Optional */ + PIN_DATA2, /* Optional */ + PIN_DATA3, /* Optional */ + PIN_DATA4, + PIN_DATA5, + PIN_DATA6, + PIN_DATA7, + PIN_CTRL_RS, + PIN_CTRL_RW, /* Optional */ + PIN_CTRL_E, + PIN_CTRL_BL, /* Optional */ + PIN_NUM +}; + +struct hd44780 { + struct gpio_desc *pins[PIN_NUM]; +}; + +static void hd44780_backlight(struct charlcd *lcd, int on) +{ + struct hd44780 *hd = lcd->drvdata; + + if (hd->pins[PIN_CTRL_BL]) + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on); +} + +static void hd44780_strobe_gpio(struct hd44780 *hd) +{ + /* Maintain the data during 20 us before the strobe */ + udelay(20); + + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1); + + /* Maintain the strobe during 40 us */ + udelay(40); + + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0); +} + +/* write to an LCD panel register in 8 bit GPIO mode */ +static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs) +{ + int values[10]; /* for DATA[0-7], RS, RW */ + unsigned int i, n; + + for (i = 0; i < 8; i++) + values[PIN_DATA0 + i] = !!(val & BIT(i)); + values[PIN_CTRL_RS] = rs; + n = 9; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values); + + hd44780_strobe_gpio(hd); +} + +/* write to an LCD panel register in 4 bit GPIO mode */ +static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs) +{ + int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */ + unsigned int i, n; + + /* High nibble + RS, RW */ + for (i = 4; i < 8; i++) + values[PIN_DATA0 + i] = !!(val & BIT(i)); + values[PIN_CTRL_RS] = rs; + n = 5; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); + + /* Low nibble */ + for (i = 0; i < 4; i++) + values[PIN_DATA4 + i] = !!(val & BIT(i)); + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); +} + +/* Send a command to the LCD panel in 8 bit GPIO mode */ +static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio8(hd, cmd, 0); + + /* The shortest command takes at least 120 us */ + udelay(120); +} + +/* Send data to the LCD panel in 8 bit GPIO mode */ +static void hd44780_write_data_gpio8(struct charlcd *lcd, int data) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio8(hd, data, 1); + + /* The shortest data takes at least 45 us */ + udelay(45); +} + +static const struct charlcd_ops hd44780_ops_gpio8 = { + .write_cmd = hd44780_write_cmd_gpio8, + .write_data = hd44780_write_data_gpio8, + .backlight = hd44780_backlight, +}; + +/* Send a command to the LCD panel in 4 bit GPIO mode */ +static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio4(hd, cmd, 0); + + /* The shortest command takes at least 120 us */ + udelay(120); +} + +/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */ +static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd) +{ + int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */ + struct hd44780 *hd = lcd->drvdata; + unsigned int i, n; + + /* Command nibble + RS, RW */ + for (i = 0; i < 4; i++) + values[PIN_DATA4 + i] = !!(cmd & BIT(i)); + values[PIN_CTRL_RS] = 0; + n = 5; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); +} + +/* Send data to the LCD panel in 4 bit GPIO mode */ +static void hd44780_write_data_gpio4(struct charlcd *lcd, int data) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio4(hd, data, 1); + + /* The shortest data takes at least 45 us */ + udelay(45); +} + +static const struct charlcd_ops hd44780_ops_gpio4 = { + .write_cmd = hd44780_write_cmd_gpio4, + .write_cmd_raw4 = hd44780_write_cmd_raw_gpio4, + .write_data = hd44780_write_data_gpio4, + .backlight = hd44780_backlight, +}; + +static int hd44780_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned int i, base; + struct charlcd *lcd; + struct hd44780 *hd; + int ifwidth, ret; + + /* Required pins */ + ifwidth = gpiod_count(dev, "data"); + if (ifwidth < 0) + return ifwidth; + + switch (ifwidth) { + case 4: + base = PIN_DATA4; + break; + case 8: + base = PIN_DATA0; + break; + default: + return -EINVAL; + } + + lcd = charlcd_alloc(sizeof(struct hd44780)); + if (!lcd) + return -ENOMEM; + + hd = lcd->drvdata; + + for (i = 0; i < ifwidth; i++) { + hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i, + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[base + i])) { + ret = PTR_ERR(hd->pins[base + i]); + goto fail; + } + } + + hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_E])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_E]); + goto fail; + } + + hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH); + if (IS_ERR(hd->pins[PIN_CTRL_RS])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_RS]); + goto fail; + } + + /* Optional pins */ + hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw", + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_RW])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_RW]); + goto fail; + } + + hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight", + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_BL])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_BL]); + goto fail; + } + + /* Required properties */ + ret = device_property_read_u32(dev, "display-height-chars", + &lcd->height); + if (ret) + goto fail; + ret = device_property_read_u32(dev, "display-width-chars", &lcd->width); + if (ret) + goto fail; + + /* + * On displays with more than two rows, the internal buffer width is + * usually equal to the display width + */ + if (lcd->height > 2) + lcd->bwidth = lcd->width; + + /* Optional properties */ + device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth); + + lcd->ifwidth = ifwidth; + lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4; + + ret = charlcd_register(lcd); + if (ret) + goto fail; + + platform_set_drvdata(pdev, lcd); + return 0; + +fail: + kfree(lcd); + return ret; +} + +static int hd44780_remove(struct platform_device *pdev) +{ + struct charlcd *lcd = platform_get_drvdata(pdev); + + charlcd_unregister(lcd); + return 0; +} + +static const struct of_device_id hd44780_of_match[] = { + { .compatible = "hit,hd44780" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hd44780_of_match); + +static struct platform_driver hd44780_driver = { + .probe = hd44780_probe, + .remove = hd44780_remove, + .driver = { + .name = "hd44780", + .of_match_table = hd44780_of_match, + }, +}; + +module_platform_driver(hd44780_driver); +MODULE_DESCRIPTION("HD44780 Character LCD driver"); +MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index f66b45b235b0..fbfa5b4cc567 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -254,18 +254,22 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) { const unsigned short *keycodes = keypad->dev->keycode; u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; - u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2]; + __le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS]; unsigned long bits_changed; int row, col, code; + int rc; bool pressed = false; - if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) { - dev_err(&keypad->client->dev, "Failed to read key data\n"); + rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40, + sizeof(data), (u8 *)data); + if (rc != sizeof(data)) { + dev_err(&keypad->client->dev, + "Failed to read key data, rc=%d\n", rc); return false; } for (col = 0; col < keypad->cols; col++) { - new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2]; + new_state[col] = le16_to_cpu(data[col]); if (new_state[col]) pressed = true; bits_changed = keypad->last_key_state[col] ^ new_state[col]; @@ -278,7 +282,7 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) } } input_sync(keypad->dev); - memcpy(keypad->last_key_state, new_state, sizeof(new_state)); + memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols); return pressed; } @@ -353,6 +357,12 @@ static int ht16k33_keypad_probe(struct i2c_client *client, err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols); if (err) return err; + if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS || + cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) { + dev_err(&client->dev, "%u rows or %u cols out of range in DT\n", + rows, cols); + return -ERANGE; + } keypad->rows = rows; keypad->cols = cols; diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 83f1439e57fd..25306fa27251 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -220,6 +220,7 @@ static const struct of_device_id img_ascii_lcd_matches[] = { { .compatible = "mti,sead3-lcd", .data = &sead3_config }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); /** * img_ascii_lcd_scroll() - scroll the display by a character diff --git a/drivers/misc/panel.c b/drivers/auxdisplay/panel.c index ef2ece0f26af..e0c014c2356f 100644 --- a/drivers/misc/panel.c +++ b/drivers/auxdisplay/panel.c @@ -1,6 +1,7 @@ /* * Front panel driver for Linux * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> + * Copyright (C) 2016-2017 Glider bvba * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -54,15 +55,12 @@ #include <linux/ctype.h> #include <linux/parport.h> #include <linux/list.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/workqueue.h> -#include <generated/utsrelease.h> #include <linux/io.h> #include <linux/uaccess.h> -#define LCD_MINOR 156 +#include <misc/charlcd.h> + #define KEYPAD_MINOR 185 #define LCD_MAXBYTES 256 /* max burst write */ @@ -76,9 +74,6 @@ /* a key repeats this times INPUT_POLL_TIME */ #define KEYPAD_REP_DELAY (2) -/* keep the light on this many seconds for each flash */ -#define FLASH_LIGHT_TEMPO (4) - /* converts an r_str() input to an active high, bits string : 000BAOSE */ #define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) @@ -120,40 +115,6 @@ #define PIN_SELECP 17 #define PIN_NOT_SET 127 -#define LCD_FLAG_B 0x0004 /* blink on */ -#define LCD_FLAG_C 0x0008 /* cursor on */ -#define LCD_FLAG_D 0x0010 /* display on */ -#define LCD_FLAG_F 0x0020 /* large font mode */ -#define LCD_FLAG_N 0x0040 /* 2-rows mode */ -#define LCD_FLAG_L 0x0080 /* backlight enabled */ - -/* LCD commands */ -#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ - -#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ -#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ - -#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ -#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ -#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ -#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ - -#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ -#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ -#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ - -#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ -#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ -#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ -#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ - -#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ - -#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ - -#define LCD_ESCAPE_LEN 24 /* max chars for LCD escape command */ -#define LCD_ESCAPE_CHAR 27 /* use char 27 for escape command */ - #define NOT_SET -1 /* macros to simplify use of the parallel port */ @@ -245,19 +206,10 @@ static wait_queue_head_t keypad_read_wait; static struct { bool enabled; bool initialized; - bool must_clear; - int height; - int width; - int bwidth; - int hwidth; int charset; int proto; - struct delayed_work bl_work; - struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ - bool bl_tempo; - /* TODO: use union here? */ struct { int e; @@ -268,20 +220,7 @@ static struct { int bl; } pins; - /* contains the LCD config state */ - unsigned long int flags; - - /* Contains the LCD X and Y offset */ - struct { - unsigned long int x; - unsigned long int y; - } addr; - - /* Current escape sequence and it's length or -1 if outside */ - struct { - char buf[LCD_ESCAPE_LEN + 1]; - int len; - } esc_seq; + struct charlcd *charlcd; } lcd; /* Needed only for init */ @@ -464,17 +403,12 @@ static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; /* global variables */ /* Device single-open policy control */ -static atomic_t lcd_available = ATOMIC_INIT(1); static atomic_t keypad_available = ATOMIC_INIT(1); static struct pardevice *pprt; static int keypad_initialized; -static void (*lcd_write_cmd)(int); -static void (*lcd_write_data)(int); -static void (*lcd_clear_fast)(void); - static DEFINE_SPINLOCK(pprt_lock); static struct timer_list scan_timer; @@ -574,8 +508,6 @@ static int keypad_enabled = NOT_SET; module_param(keypad_enabled, int, 0000); MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); -static const unsigned char *lcd_char_conv; - /* for some LCD drivers (ks0074) we need a charset conversion table. */ static const unsigned char lcd_char_conv_ks0074[256] = { /* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */ @@ -752,15 +684,6 @@ static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) } } -/* sleeps that many milliseconds with a reschedule */ -static void long_sleep(int ms) -{ - if (in_interrupt()) - mdelay(ms); - else - schedule_timeout_interruptible(msecs_to_jiffies(ms)); -} - /* * send a serial byte to the LCD panel. The caller is responsible for locking * if needed. @@ -792,8 +715,11 @@ static void lcd_send_serial(int byte) } /* turn the backlight on or off */ -static void __lcd_backlight(int on) +static void lcd_backlight(struct charlcd *charlcd, int on) { + if (lcd.pins.bl == PIN_NONE) + return; + /* The backlight is activated by setting the AUTOFEED line to +5V */ spin_lock_irq(&pprt_lock); if (on) @@ -804,46 +730,8 @@ static void __lcd_backlight(int on) spin_unlock_irq(&pprt_lock); } -static void lcd_backlight(int on) -{ - if (lcd.pins.bl == PIN_NONE) - return; - - mutex_lock(&lcd.bl_tempo_lock); - if (!lcd.bl_tempo) - __lcd_backlight(on); - mutex_unlock(&lcd.bl_tempo_lock); -} - -static void lcd_bl_off(struct work_struct *work) -{ - mutex_lock(&lcd.bl_tempo_lock); - if (lcd.bl_tempo) { - lcd.bl_tempo = false; - if (!(lcd.flags & LCD_FLAG_L)) - __lcd_backlight(0); - } - mutex_unlock(&lcd.bl_tempo_lock); -} - -/* turn the backlight on for a little while */ -static void lcd_poke(void) -{ - if (lcd.pins.bl == PIN_NONE) - return; - - cancel_delayed_work_sync(&lcd.bl_work); - - mutex_lock(&lcd.bl_tempo_lock); - if (!lcd.bl_tempo && !(lcd.flags & LCD_FLAG_L)) - __lcd_backlight(1); - lcd.bl_tempo = true; - schedule_delayed_work(&lcd.bl_work, FLASH_LIGHT_TEMPO * HZ); - mutex_unlock(&lcd.bl_tempo_lock); -} - /* send a command to the LCD panel in serial mode */ -static void lcd_write_cmd_s(int cmd) +static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); lcd_send_serial(0x1F); /* R/W=W, RS=0 */ @@ -854,7 +742,7 @@ static void lcd_write_cmd_s(int cmd) } /* send data to the LCD panel in serial mode */ -static void lcd_write_data_s(int data) +static void lcd_write_data_s(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); lcd_send_serial(0x5F); /* R/W=W, RS=1 */ @@ -865,7 +753,7 @@ static void lcd_write_data_s(int data) } /* send a command to the LCD panel in 8 bits parallel mode */ -static void lcd_write_cmd_p8(int cmd) +static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -887,7 +775,7 @@ static void lcd_write_cmd_p8(int cmd) } /* send data to the LCD panel in 8 bits parallel mode */ -static void lcd_write_data_p8(int data) +static void lcd_write_data_p8(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -909,7 +797,7 @@ static void lcd_write_data_p8(int data) } /* send a command to the TI LCD panel */ -static void lcd_write_cmd_tilcd(int cmd) +static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); /* present the data to the control port */ @@ -919,7 +807,7 @@ static void lcd_write_cmd_tilcd(int cmd) } /* send data to the TI LCD panel */ -static void lcd_write_data_tilcd(int data) +static void lcd_write_data_tilcd(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -928,47 +816,13 @@ static void lcd_write_data_tilcd(int data) spin_unlock_irq(&pprt_lock); } -static void lcd_gotoxy(void) -{ - lcd_write_cmd(LCD_CMD_SET_DDRAM_ADDR - | (lcd.addr.y ? lcd.hwidth : 0) - /* - * we force the cursor to stay at the end of the - * line if it wants to go farther - */ - | ((lcd.addr.x < lcd.bwidth) ? lcd.addr.x & - (lcd.hwidth - 1) : lcd.bwidth - 1)); -} - -static void lcd_home(void) -{ - lcd.addr.x = 0; - lcd.addr.y = 0; - lcd_gotoxy(); -} - -static void lcd_print(char c) -{ - if (lcd.addr.x < lcd.bwidth) { - if (lcd_char_conv) - c = lcd_char_conv[(unsigned char)c]; - lcd_write_data(c); - lcd.addr.x++; - } - /* prevents the cursor from wrapping onto the next line */ - if (lcd.addr.x == lcd.bwidth) - lcd_gotoxy(); -} - /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_s(void) +static void lcd_clear_fast_s(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { lcd_send_serial(0x5F); /* R/W=W, RS=1 */ lcd_send_serial(' ' & 0x0F); lcd_send_serial((' ' >> 4) & 0x0F); @@ -976,19 +830,15 @@ static void lcd_clear_fast_s(void) udelay(40); } spin_unlock_irq(&pprt_lock); - - lcd_home(); } /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_p8(void) +static void lcd_clear_fast_p8(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { /* present the data to the data port */ w_dtr(pprt, ' '); @@ -1010,488 +860,62 @@ static void lcd_clear_fast_p8(void) udelay(45); } spin_unlock_irq(&pprt_lock); - - lcd_home(); } /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_tilcd(void) +static void lcd_clear_fast_tilcd(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { /* present the data to the data port */ w_dtr(pprt, ' '); udelay(60); } spin_unlock_irq(&pprt_lock); - - lcd_home(); -} - -/* clears the display and resets X/Y */ -static void lcd_clear_display(void) -{ - lcd_write_cmd(LCD_CMD_DISPLAY_CLEAR); - lcd.addr.x = 0; - lcd.addr.y = 0; - /* we must wait a few milliseconds (15) */ - long_sleep(15); } -static void lcd_init_display(void) -{ - lcd.flags = ((lcd.height > 1) ? LCD_FLAG_N : 0) - | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B; - - long_sleep(20); /* wait 20 ms after power-up for the paranoid */ - - /* 8bits, 1 line, small fonts; let's do it 3 times */ - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - - /* set font height and lines number */ - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS - | ((lcd.flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) - | ((lcd.flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0) - ); - long_sleep(10); - - /* display off, cursor off, blink off */ - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL); - long_sleep(10); - - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL /* set display mode */ - | ((lcd.flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) - | ((lcd.flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) - | ((lcd.flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0) - ); - - lcd_backlight((lcd.flags & LCD_FLAG_L) ? 1 : 0); - - long_sleep(10); - - /* entry mode set : increment, cursor shifting */ - lcd_write_cmd(LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); +static struct charlcd_ops charlcd_serial_ops = { + .write_cmd = lcd_write_cmd_s, + .write_data = lcd_write_data_s, + .clear_fast = lcd_clear_fast_s, + .backlight = lcd_backlight, +}; - lcd_clear_display(); -} +static struct charlcd_ops charlcd_parallel_ops = { + .write_cmd = lcd_write_cmd_p8, + .write_data = lcd_write_data_p8, + .clear_fast = lcd_clear_fast_p8, + .backlight = lcd_backlight, +}; -/* - * These are the file operation function for user access to /dev/lcd - * This function can also be called from inside the kernel, by - * setting file and ppos to NULL. - * - */ +static struct charlcd_ops charlcd_tilcd_ops = { + .write_cmd = lcd_write_cmd_tilcd, + .write_data = lcd_write_data_tilcd, + .clear_fast = lcd_clear_fast_tilcd, + .backlight = lcd_backlight, +}; -static inline int handle_lcd_special_code(void) +/* initialize the LCD driver */ +static void lcd_init(void) { - /* LCD special codes */ - - int processed = 0; + struct charlcd *charlcd; - char *esc = lcd.esc_seq.buf + 2; - int oldflags = lcd.flags; - - /* check for display mode flags */ - switch (*esc) { - case 'D': /* Display ON */ - lcd.flags |= LCD_FLAG_D; - processed = 1; - break; - case 'd': /* Display OFF */ - lcd.flags &= ~LCD_FLAG_D; - processed = 1; - break; - case 'C': /* Cursor ON */ - lcd.flags |= LCD_FLAG_C; - processed = 1; - break; - case 'c': /* Cursor OFF */ - lcd.flags &= ~LCD_FLAG_C; - processed = 1; - break; - case 'B': /* Blink ON */ - lcd.flags |= LCD_FLAG_B; - processed = 1; - break; - case 'b': /* Blink OFF */ - lcd.flags &= ~LCD_FLAG_B; - processed = 1; - break; - case '+': /* Back light ON */ - lcd.flags |= LCD_FLAG_L; - processed = 1; - break; - case '-': /* Back light OFF */ - lcd.flags &= ~LCD_FLAG_L; - processed = 1; - break; - case '*': - /* flash back light */ - lcd_poke(); - processed = 1; - break; - case 'f': /* Small Font */ - lcd.flags &= ~LCD_FLAG_F; - processed = 1; - break; - case 'F': /* Large Font */ - lcd.flags |= LCD_FLAG_F; - processed = 1; - break; - case 'n': /* One Line */ - lcd.flags &= ~LCD_FLAG_N; - processed = 1; - break; - case 'N': /* Two Lines */ - lcd.flags |= LCD_FLAG_N; - break; - case 'l': /* Shift Cursor Left */ - if (lcd.addr.x > 0) { - /* back one char if not at end of line */ - if (lcd.addr.x < lcd.bwidth) - lcd_write_cmd(LCD_CMD_SHIFT); - lcd.addr.x--; - } - processed = 1; - break; - case 'r': /* shift cursor right */ - if (lcd.addr.x < lcd.width) { - /* allow the cursor to pass the end of the line */ - if (lcd.addr.x < (lcd.bwidth - 1)) - lcd_write_cmd(LCD_CMD_SHIFT | - LCD_CMD_SHIFT_RIGHT); - lcd.addr.x++; - } - processed = 1; - break; - case 'L': /* shift display left */ - lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); - processed = 1; - break; - case 'R': /* shift display right */ - lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | - LCD_CMD_SHIFT_RIGHT); - processed = 1; - break; - case 'k': { /* kill end of line */ - int x; - - for (x = lcd.addr.x; x < lcd.bwidth; x++) - lcd_write_data(' '); - - /* restore cursor position */ - lcd_gotoxy(); - processed = 1; - break; - } - case 'I': /* reinitialize display */ - lcd_init_display(); - processed = 1; - break; - case 'G': { - /* Generator : LGcxxxxx...xx; must have <c> between '0' - * and '7', representing the numerical ASCII code of the - * redefined character, and <xx...xx> a sequence of 16 - * hex digits representing 8 bytes for each character. - * Most LCDs will only use 5 lower bits of the 7 first - * bytes. - */ - - unsigned char cgbytes[8]; - unsigned char cgaddr; - int cgoffset; - int shift; - char value; - int addr; - - if (!strchr(esc, ';')) - break; - - esc++; - - cgaddr = *(esc++) - '0'; - if (cgaddr > 7) { - processed = 1; - break; - } - - cgoffset = 0; - shift = 0; - value = 0; - while (*esc && cgoffset < 8) { - shift ^= 4; - if (*esc >= '0' && *esc <= '9') { - value |= (*esc - '0') << shift; - } else if (*esc >= 'A' && *esc <= 'Z') { - value |= (*esc - 'A' + 10) << shift; - } else if (*esc >= 'a' && *esc <= 'z') { - value |= (*esc - 'a' + 10) << shift; - } else { - esc++; - continue; - } - - if (shift == 0) { - cgbytes[cgoffset++] = value; - value = 0; - } - - esc++; - } - - lcd_write_cmd(LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); - for (addr = 0; addr < cgoffset; addr++) - lcd_write_data(cgbytes[addr]); - - /* ensures that we stop writing to CGRAM */ - lcd_gotoxy(); - processed = 1; - break; - } - case 'x': /* gotoxy : LxXXX[yYYY]; */ - case 'y': /* gotoxy : LyYYY[xXXX]; */ - if (!strchr(esc, ';')) - break; - - while (*esc) { - if (*esc == 'x') { - esc++; - if (kstrtoul(esc, 10, &lcd.addr.x) < 0) - break; - } else if (*esc == 'y') { - esc++; - if (kstrtoul(esc, 10, &lcd.addr.y) < 0) - break; - } else { - break; - } - } - - lcd_gotoxy(); - processed = 1; - break; - } - - /* TODO: This indent party here got ugly, clean it! */ - /* Check whether one flag was changed */ - if (oldflags != lcd.flags) { - /* check whether one of B,C,D flags were changed */ - if ((oldflags ^ lcd.flags) & - (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) - /* set display mode */ - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL - | ((lcd.flags & LCD_FLAG_D) - ? LCD_CMD_DISPLAY_ON : 0) - | ((lcd.flags & LCD_FLAG_C) - ? LCD_CMD_CURSOR_ON : 0) - | ((lcd.flags & LCD_FLAG_B) - ? LCD_CMD_BLINK_ON : 0)); - /* check whether one of F,N flags was changed */ - else if ((oldflags ^ lcd.flags) & (LCD_FLAG_F | LCD_FLAG_N)) - lcd_write_cmd(LCD_CMD_FUNCTION_SET - | LCD_CMD_DATA_LEN_8BITS - | ((lcd.flags & LCD_FLAG_F) - ? LCD_CMD_FONT_5X10_DOTS - : 0) - | ((lcd.flags & LCD_FLAG_N) - ? LCD_CMD_TWO_LINES - : 0)); - /* check whether L flag was changed */ - else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L)) - lcd_backlight(!!(lcd.flags & LCD_FLAG_L)); - } - - return processed; -} - -static void lcd_write_char(char c) -{ - /* first, we'll test if we're in escape mode */ - if ((c != '\n') && lcd.esc_seq.len >= 0) { - /* yes, let's add this char to the buffer */ - lcd.esc_seq.buf[lcd.esc_seq.len++] = c; - lcd.esc_seq.buf[lcd.esc_seq.len] = 0; - } else { - /* aborts any previous escape sequence */ - lcd.esc_seq.len = -1; - - switch (c) { - case LCD_ESCAPE_CHAR: - /* start of an escape sequence */ - lcd.esc_seq.len = 0; - lcd.esc_seq.buf[lcd.esc_seq.len] = 0; - break; - case '\b': - /* go back one char and clear it */ - if (lcd.addr.x > 0) { - /* - * check if we're not at the - * end of the line - */ - if (lcd.addr.x < lcd.bwidth) - /* back one char */ - lcd_write_cmd(LCD_CMD_SHIFT); - lcd.addr.x--; - } - /* replace with a space */ - lcd_write_data(' '); - /* back one char again */ - lcd_write_cmd(LCD_CMD_SHIFT); - break; - case '\014': - /* quickly clear the display */ - lcd_clear_fast(); - break; - case '\n': - /* - * flush the remainder of the current line and - * go to the beginning of the next line - */ - for (; lcd.addr.x < lcd.bwidth; lcd.addr.x++) - lcd_write_data(' '); - lcd.addr.x = 0; - lcd.addr.y = (lcd.addr.y + 1) % lcd.height; - lcd_gotoxy(); - break; - case '\r': - /* go to the beginning of the same line */ - lcd.addr.x = 0; - lcd_gotoxy(); - break; - case '\t': - /* print a space instead of the tab */ - lcd_print(' '); - break; - default: - /* simply print this char */ - lcd_print(c); - break; - } - } + charlcd = charlcd_alloc(0); + if (!charlcd) + return; /* - * now we'll see if we're in an escape mode and if the current - * escape sequence can be understood. + * Init lcd struct with load-time values to preserve exact + * current functionality (at least for now). */ - if (lcd.esc_seq.len >= 2) { - int processed = 0; - - if (!strcmp(lcd.esc_seq.buf, "[2J")) { - /* clear the display */ - lcd_clear_fast(); - processed = 1; - } else if (!strcmp(lcd.esc_seq.buf, "[H")) { - /* cursor to home */ - lcd_home(); - processed = 1; - } - /* codes starting with ^[[L */ - else if ((lcd.esc_seq.len >= 3) && - (lcd.esc_seq.buf[0] == '[') && - (lcd.esc_seq.buf[1] == 'L')) { - processed = handle_lcd_special_code(); - } - - /* LCD special escape codes */ - /* - * flush the escape sequence if it's been processed - * or if it is getting too long. - */ - if (processed || (lcd.esc_seq.len >= LCD_ESCAPE_LEN)) - lcd.esc_seq.len = -1; - } /* escape codes */ -} + charlcd->height = lcd_height; + charlcd->width = lcd_width; + charlcd->bwidth = lcd_bwidth; + charlcd->hwidth = lcd_hwidth; -static ssize_t lcd_write(struct file *file, - const char __user *buf, size_t count, loff_t *ppos) -{ - const char __user *tmp = buf; - char c; - - for (; count-- > 0; (*ppos)++, tmp++) { - if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) - /* - * let's be a little nice with other processes - * that need some CPU - */ - schedule(); - - if (get_user(c, tmp)) - return -EFAULT; - - lcd_write_char(c); - } - - return tmp - buf; -} - -static int lcd_open(struct inode *inode, struct file *file) -{ - if (!atomic_dec_and_test(&lcd_available)) - return -EBUSY; /* open only once at a time */ - - if (file->f_mode & FMODE_READ) /* device is write-only */ - return -EPERM; - - if (lcd.must_clear) { - lcd_clear_display(); - lcd.must_clear = false; - } - return nonseekable_open(inode, file); -} - -static int lcd_release(struct inode *inode, struct file *file) -{ - atomic_inc(&lcd_available); - return 0; -} - -static const struct file_operations lcd_fops = { - .write = lcd_write, - .open = lcd_open, - .release = lcd_release, - .llseek = no_llseek, -}; - -static struct miscdevice lcd_dev = { - .minor = LCD_MINOR, - .name = "lcd", - .fops = &lcd_fops, -}; - -/* public function usable from the kernel for any purpose */ -static void panel_lcd_print(const char *s) -{ - const char *tmp = s; - int count = strlen(s); - - if (lcd.enabled && lcd.initialized) { - for (; count-- > 0; tmp++) { - if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) - /* - * let's be a little nice with other processes - * that need some CPU - */ - schedule(); - - lcd_write_char(*tmp); - } - } -} - -/* initialize the LCD driver */ -static void lcd_init(void) -{ switch (selected_lcd_type) { case LCD_TYPE_OLD: /* parallel mode, 8 bits */ @@ -1500,10 +924,10 @@ static void lcd_init(void) lcd.pins.e = PIN_STROBE; lcd.pins.rs = PIN_AUTOLF; - lcd.width = 40; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 40; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; case LCD_TYPE_KS0074: /* serial mode, ks0074 */ @@ -1513,10 +937,10 @@ static void lcd_init(void) lcd.pins.cl = PIN_STROBE; lcd.pins.da = PIN_D0; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 16; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 16; + charlcd->height = 2; break; case LCD_TYPE_NEXCOM: /* parallel mode, 8 bits, generic */ @@ -1526,10 +950,10 @@ static void lcd_init(void) lcd.pins.rs = PIN_SELECP; lcd.pins.rw = PIN_INITP; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; case LCD_TYPE_CUSTOM: /* customer-defined */ @@ -1545,22 +969,22 @@ static void lcd_init(void) lcd.pins.e = PIN_STROBE; lcd.pins.rs = PIN_SELECP; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; } /* Overwrite with module params set on loading */ if (lcd_height != NOT_SET) - lcd.height = lcd_height; + charlcd->height = lcd_height; if (lcd_width != NOT_SET) - lcd.width = lcd_width; + charlcd->width = lcd_width; if (lcd_bwidth != NOT_SET) - lcd.bwidth = lcd_bwidth; + charlcd->bwidth = lcd_bwidth; if (lcd_hwidth != NOT_SET) - lcd.hwidth = lcd_hwidth; + charlcd->hwidth = lcd_hwidth; if (lcd_charset != NOT_SET) lcd.charset = lcd_charset; if (lcd_proto != NOT_SET) @@ -1579,19 +1003,17 @@ static void lcd_init(void) lcd.pins.bl = lcd_bl_pin; /* this is used to catch wrong and default values */ - if (lcd.width <= 0) - lcd.width = DEFAULT_LCD_WIDTH; - if (lcd.bwidth <= 0) - lcd.bwidth = DEFAULT_LCD_BWIDTH; - if (lcd.hwidth <= 0) - lcd.hwidth = DEFAULT_LCD_HWIDTH; - if (lcd.height <= 0) - lcd.height = DEFAULT_LCD_HEIGHT; + if (charlcd->width <= 0) + charlcd->width = DEFAULT_LCD_WIDTH; + if (charlcd->bwidth <= 0) + charlcd->bwidth = DEFAULT_LCD_BWIDTH; + if (charlcd->hwidth <= 0) + charlcd->hwidth = DEFAULT_LCD_HWIDTH; + if (charlcd->height <= 0) + charlcd->height = DEFAULT_LCD_HEIGHT; if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */ - lcd_write_cmd = lcd_write_cmd_s; - lcd_write_data = lcd_write_data_s; - lcd_clear_fast = lcd_clear_fast_s; + charlcd->ops = &charlcd_serial_ops; if (lcd.pins.cl == PIN_NOT_SET) lcd.pins.cl = DEFAULT_LCD_PIN_SCL; @@ -1599,9 +1021,7 @@ static void lcd_init(void) lcd.pins.da = DEFAULT_LCD_PIN_SDA; } else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */ - lcd_write_cmd = lcd_write_cmd_p8; - lcd_write_data = lcd_write_data_p8; - lcd_clear_fast = lcd_clear_fast_p8; + charlcd->ops = &charlcd_parallel_ops; if (lcd.pins.e == PIN_NOT_SET) lcd.pins.e = DEFAULT_LCD_PIN_E; @@ -1610,9 +1030,7 @@ static void lcd_init(void) if (lcd.pins.rw == PIN_NOT_SET) lcd.pins.rw = DEFAULT_LCD_PIN_RW; } else { - lcd_write_cmd = lcd_write_cmd_tilcd; - lcd_write_data = lcd_write_data_tilcd; - lcd_clear_fast = lcd_clear_fast_tilcd; + charlcd->ops = &charlcd_tilcd_ops; } if (lcd.pins.bl == PIN_NOT_SET) @@ -1635,14 +1053,9 @@ static void lcd_init(void) lcd.charset = DEFAULT_LCD_CHARSET; if (lcd.charset == LCD_CHARSET_KS0074) - lcd_char_conv = lcd_char_conv_ks0074; + charlcd->char_conv = lcd_char_conv_ks0074; else - lcd_char_conv = NULL; - - if (lcd.pins.bl != PIN_NONE) { - mutex_init(&lcd.bl_tempo_lock); - INIT_DELAYED_WORK(&lcd.bl_work, lcd_bl_off); - } + charlcd->char_conv = NULL; pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], lcd_bits[LCD_PORT_C][LCD_BIT_E]); @@ -1657,25 +1070,8 @@ static void lcd_init(void) pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], lcd_bits[LCD_PORT_C][LCD_BIT_DA]); - /* - * before this line, we must NOT send anything to the display. - * Since lcd_init_display() needs to write data, we have to - * enable mark the LCD initialized just before. - */ + lcd.charlcd = charlcd; lcd.initialized = true; - lcd_init_display(); - - /* display a short message */ -#ifdef CONFIG_PANEL_CHANGE_MESSAGE -#ifdef CONFIG_PANEL_BOOT_MESSAGE - panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); -#endif -#else - panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE); -#endif - /* clear the display on the next device opening */ - lcd.must_clear = true; - lcd_home(); } /* @@ -2011,7 +1407,7 @@ static void panel_scan_timer(void) } if (keypressed && lcd.enabled && lcd.initialized) - lcd_poke(); + charlcd_poke(lcd.charlcd); mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); } @@ -2175,35 +1571,6 @@ static void keypad_init(void) /* device initialization */ /**************************************************/ -static int panel_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (lcd.enabled && lcd.initialized) { - switch (code) { - case SYS_DOWN: - panel_lcd_print - ("\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); - break; - case SYS_HALT: - panel_lcd_print - ("\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); - break; - case SYS_POWER_OFF: - panel_lcd_print("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); - break; - default: - break; - } - } - return NOTIFY_DONE; -} - -static struct notifier_block panel_notifier = { - panel_notify_sys, - NULL, - 0 -}; - static void panel_attach(struct parport *port) { struct pardev_cb panel_cb; @@ -2239,7 +1606,7 @@ static void panel_attach(struct parport *port) */ if (lcd.enabled) { lcd_init(); - if (misc_register(&lcd_dev)) + if (!lcd.charlcd || charlcd_register(lcd.charlcd)) goto err_unreg_device; } @@ -2248,13 +1615,14 @@ static void panel_attach(struct parport *port) if (misc_register(&keypad_dev)) goto err_lcd_unreg; } - register_reboot_notifier(&panel_notifier); return; err_lcd_unreg: if (lcd.enabled) - misc_deregister(&lcd_dev); + charlcd_unregister(lcd.charlcd); err_unreg_device: + kfree(lcd.charlcd); + lcd.charlcd = NULL; parport_unregister_device(pprt); pprt = NULL; } @@ -2278,20 +1646,16 @@ static void panel_detach(struct parport *port) } if (lcd.enabled) { - panel_lcd_print("\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); - misc_deregister(&lcd_dev); - if (lcd.pins.bl != PIN_NONE) { - cancel_delayed_work_sync(&lcd.bl_work); - __lcd_backlight(0); - } + charlcd_unregister(lcd.charlcd); lcd.initialized = false; + kfree(lcd.charlcd); + lcd.charlcd = NULL; } /* TODO: free all input signals */ parport_release(pprt); parport_unregister_device(pprt); pprt = NULL; - unregister_reboot_notifier(&panel_notifier); } static struct parport_driver panel_driver = { @@ -2369,10 +1733,6 @@ static int __init panel_init_module(void) * Init lcd struct with load-time values to preserve exact * current functionality (at least for now). */ - lcd.height = lcd_height; - lcd.width = lcd_width; - lcd.bwidth = lcd_bwidth; - lcd.hwidth = lcd_hwidth; lcd.charset = lcd_charset; lcd.proto = lcd_proto; lcd.pins.e = lcd_e_pin; @@ -2381,9 +1741,6 @@ static int __init panel_init_module(void) lcd.pins.cl = lcd_cl_pin; lcd.pins.da = lcd_da_pin; lcd.pins.bl = lcd_bl_pin; - - /* Leave it for now, just in case */ - lcd.esc_seq.len = -1; } switch (selected_keypad_type) { diff --git a/drivers/base/core.c b/drivers/base/core.c index 6bb60fb6a30b..bbecaf9293be 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1607,7 +1607,7 @@ int device_private_init(struct device *dev) */ int device_add(struct device *dev) { - struct device *parent = NULL; + struct device *parent; struct kobject *kobj; struct class_interface *class_intf; int error = -EINVAL; diff --git a/drivers/base/dma-mapping.c b/drivers/base/dma-mapping.c index efd71cf4fdea..51b7061ff7c0 100644 --- a/drivers/base/dma-mapping.c +++ b/drivers/base/dma-mapping.c @@ -309,14 +309,13 @@ void *dma_common_contiguous_remap(struct page *page, size_t size, int i; struct page **pages; void *ptr; - unsigned long pfn; pages = kmalloc(sizeof(struct page *) << get_order(size), GFP_KERNEL); if (!pages) return NULL; - for (i = 0, pfn = page_to_pfn(page); i < (size >> PAGE_SHIFT); i++) - pages[i] = pfn_to_page(pfn + i); + for (i = 0; i < (size >> PAGE_SHIFT); i++) + pages[i] = nth_page(page, i); ptr = dma_common_pages_remap(pages, size, vm_flags, prot, caller); diff --git a/drivers/char/hangcheck-timer.c b/drivers/char/hangcheck-timer.c index 4f337375252e..5406b90bf626 100644 --- a/drivers/char/hangcheck-timer.c +++ b/drivers/char/hangcheck-timer.c @@ -32,7 +32,7 @@ * timer and 180 seconds for the margin of error. IOW, a timer is set * for 60 seconds. When the timer fires, the callback checks the * actual duration that the timer waited. If the duration exceeds the - * alloted time and margin (here 60 + 180, or 240 seconds), the machine + * allotted time and margin (here 60 + 180, or 240 seconds), the machine * is restarted. A healthy machine will have the duration match the * expected timeout very closely. */ diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index 8bdc38d81adf..b941e6d59fd6 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c @@ -575,7 +575,7 @@ static inline unsigned long hpet_time_div(struct hpets *hpets, } static int -hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, +hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg, struct hpet_info *info) { struct hpet_timer __iomem *timer; diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 8069b361b8dd..c9cd1ea6844a 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -109,7 +109,7 @@ static const struct file_operations misc_proc_fops = { }; #endif -static int misc_open(struct inode * inode, struct file * file) +static int misc_open(struct inode *inode, struct file *file) { int minor = iminor(inode); struct miscdevice *c; @@ -150,7 +150,7 @@ static int misc_open(struct inode * inode, struct file * file) err = 0; replace_fops(file, new_fops); if (file->f_op->open) - err = file->f_op->open(inode,file); + err = file->f_op->open(inode, file); fail: mutex_unlock(&misc_mtx); return err; @@ -182,7 +182,7 @@ static const struct file_operations misc_fops = { * failure. */ -int misc_register(struct miscdevice * misc) +int misc_register(struct miscdevice *misc) { dev_t dev; int err = 0; @@ -194,6 +194,7 @@ int misc_register(struct miscdevice * misc) if (is_dynamic) { int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); + if (i >= DYNAMIC_MINORS) { err = -EBUSY; goto out; @@ -287,13 +288,13 @@ static int __init misc_init(void) goto fail_remove; err = -EIO; - if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) + if (register_chrdev(MISC_MAJOR, "misc", &misc_fops)) goto fail_printk; misc_class->devnode = misc_devnode; return 0; fail_printk: - printk("unable to get major %d for misc devices\n", MISC_MAJOR); + pr_err("unable to get major %d for misc devices\n", MISC_MAJOR); class_destroy(misc_class); fail_remove: if (ret) diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c index a9c2fa3c81e5..7b75669d3670 100644 --- a/drivers/char/mspec.c +++ b/drivers/char/mspec.c @@ -43,6 +43,7 @@ #include <linux/string.h> #include <linux/slab.h> #include <linux/numa.h> +#include <linux/refcount.h> #include <asm/page.h> #include <asm/pgtable.h> #include <linux/atomic.h> @@ -89,7 +90,7 @@ static int is_sn2; * protect in fork case where multiple tasks share the vma_data. */ struct vma_data { - atomic_t refcnt; /* Number of vmas sharing the data. */ + refcount_t refcnt; /* Number of vmas sharing the data. */ spinlock_t lock; /* Serialize access to this structure. */ int count; /* Number of pages allocated. */ enum mspec_page_type type; /* Type of pages allocated. */ @@ -144,7 +145,7 @@ mspec_open(struct vm_area_struct *vma) struct vma_data *vdata; vdata = vma->vm_private_data; - atomic_inc(&vdata->refcnt); + refcount_inc(&vdata->refcnt); } /* @@ -162,7 +163,7 @@ mspec_close(struct vm_area_struct *vma) vdata = vma->vm_private_data; - if (!atomic_dec_and_test(&vdata->refcnt)) + if (!refcount_dec_and_test(&vdata->refcnt)) return; last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT; @@ -274,7 +275,7 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma, vdata->vm_end = vma->vm_end; vdata->type = type; spin_lock_init(&vdata->lock); - atomic_set(&vdata->refcnt, 1); + refcount_set(&vdata->refcnt, 1); vma->vm_private_data = vdata; vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c index 9dec9f551b83..322b8a51ffc6 100644 --- a/drivers/char/tpm/tpm-chip.c +++ b/drivers/char/tpm/tpm-chip.c @@ -218,8 +218,6 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev, cdev_init(&chip->cdevs, &tpmrm_fops); chip->cdev.owner = THIS_MODULE; chip->cdevs.owner = THIS_MODULE; - chip->cdev.kobj.parent = &chip->dev.kobj; - chip->cdevs.kobj.parent = &chip->devs.kobj; chip->work_space.context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!chip->work_space.context_buf) { @@ -275,45 +273,24 @@ static int tpm_add_char_device(struct tpm_chip *chip) { int rc; - rc = cdev_add(&chip->cdev, chip->dev.devt, 1); + rc = cdev_device_add(&chip->cdev, &chip->dev); if (rc) { dev_err(&chip->dev, - "unable to cdev_add() %s, major %d, minor %d, err=%d\n", + "unable to cdev_device_add() %s, major %d, minor %d, err=%d\n", dev_name(&chip->dev), MAJOR(chip->dev.devt), MINOR(chip->dev.devt), rc); return rc; } - rc = device_add(&chip->dev); - if (rc) { - dev_err(&chip->dev, - "unable to device_register() %s, major %d, minor %d, err=%d\n", - dev_name(&chip->dev), MAJOR(chip->dev.devt), - MINOR(chip->dev.devt), rc); - - cdev_del(&chip->cdev); - return rc; - } - - if (chip->flags & TPM_CHIP_FLAG_TPM2) - rc = cdev_add(&chip->cdevs, chip->devs.devt, 1); - if (rc) { - dev_err(&chip->dev, - "unable to cdev_add() %s, major %d, minor %d, err=%d\n", - dev_name(&chip->devs), MAJOR(chip->devs.devt), - MINOR(chip->devs.devt), rc); - return rc; - } - - if (chip->flags & TPM_CHIP_FLAG_TPM2) - rc = device_add(&chip->devs); - if (rc) { - dev_err(&chip->dev, - "unable to device_register() %s, major %d, minor %d, err=%d\n", - dev_name(&chip->devs), MAJOR(chip->devs.devt), - MINOR(chip->devs.devt), rc); - cdev_del(&chip->cdevs); - return rc; + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + rc = cdev_device_add(&chip->cdevs, &chip->devs); + if (rc) { + dev_err(&chip->devs, + "unable to cdev_device_add() %s, major %d, minor %d, err=%d\n", + dev_name(&chip->devs), MAJOR(chip->devs.devt), + MINOR(chip->devs.devt), rc); + return rc; + } } /* Make the chip available. */ @@ -326,8 +303,7 @@ static int tpm_add_char_device(struct tpm_chip *chip) static void tpm_del_char_device(struct tpm_chip *chip) { - cdev_del(&chip->cdev); - device_del(&chip->dev); + cdev_device_del(&chip->cdev, &chip->dev); /* Make the chip unavailable. */ mutex_lock(&idr_lock); @@ -449,10 +425,8 @@ void tpm_chip_unregister(struct tpm_chip *chip) { tpm_del_legacy_sysfs(chip); tpm_bios_log_teardown(chip); - if (chip->flags & TPM_CHIP_FLAG_TPM2) { - cdev_del(&chip->cdevs); - device_del(&chip->devs); - } + if (chip->flags & TPM_CHIP_FLAG_TPM2) + cdev_device_del(&chip->cdevs, &chip->devs); tpm_del_char_device(chip); } EXPORT_SYMBOL_GPL(tpm_chip_unregister); diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 87fe111d0be6..7d041d026680 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -2304,7 +2304,7 @@ static int __init init(void) pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL); if (!pdrvdata.debugfs_dir) - pr_warning("Error creating debugfs dir for virtio-ports\n"); + pr_warn("Error creating debugfs dir for virtio-ports\n"); INIT_LIST_HEAD(&pdrvdata.consoles); INIT_LIST_HEAD(&pdrvdata.portdevs); diff --git a/drivers/dax/dax.c b/drivers/dax/dax.c index 806f180c80d8..19795eb35579 100644 --- a/drivers/dax/dax.c +++ b/drivers/dax/dax.c @@ -703,13 +703,8 @@ static void dax_dev_release(struct device *dev) kfree(dax_dev); } -static void unregister_dax_dev(void *dev) +static void kill_dax_dev(struct dax_dev *dax_dev) { - struct dax_dev *dax_dev = to_dax_dev(dev); - struct cdev *cdev = &dax_dev->cdev; - - dev_dbg(dev, "%s\n", __func__); - /* * Note, rcu is not protecting the liveness of dax_dev, rcu is * ensuring that any fault handlers that might have seen @@ -720,8 +715,17 @@ static void unregister_dax_dev(void *dev) dax_dev->alive = false; synchronize_srcu(&dax_srcu); unmap_mapping_range(dax_dev->inode->i_mapping, 0, 0, 1); - cdev_del(cdev); - device_unregister(dev); +} + +static void unregister_dax_dev(void *dev) +{ + struct dax_dev *dax_dev = to_dax_dev(dev); + + dev_dbg(dev, "%s\n", __func__); + + kill_dax_dev(dax_dev); + cdev_device_del(&dax_dev->cdev, dev); + put_device(dev); } struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, @@ -772,18 +776,13 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, goto err_inode; } - /* device_initialize() so cdev can reference kobj parent */ + /* from here on we're committed to teardown via dax_dev_release() */ device_initialize(dev); cdev = &dax_dev->cdev; cdev_init(cdev, &dax_fops); cdev->owner = parent->driver->owner; - cdev->kobj.parent = &dev->kobj; - rc = cdev_add(&dax_dev->cdev, dev_t, 1); - if (rc) - goto err_cdev; - /* from here on we're committed to teardown via dax_dev_release() */ dax_dev->num_resources = count; dax_dev->alive = true; dax_dev->region = dax_region; @@ -795,8 +794,10 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, dev->groups = dax_attribute_groups; dev->release = dax_dev_release; dev_set_name(dev, "dax%d.%d", dax_region->id, dax_dev->id); - rc = device_add(dev); + + rc = cdev_device_add(cdev, dev); if (rc) { + kill_dax_dev(dax_dev); put_device(dev); return ERR_PTR(rc); } @@ -807,8 +808,6 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, return dax_dev; - err_cdev: - iput(dax_dev->inode); err_inode: ida_simple_remove(&dax_minor_ida, minor); err_minor: diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index fc09c76248b4..32f2dc8e4702 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496 This ACPI device is typically found on Intel Baytrail or Cherrytrail based tablets, or other Baytrail / Cherrytrail devices. +config EXTCON_INTEL_CHT_WC + tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver" + depends on INTEL_SOC_PMIC_CHTWC + help + Say Y here to enable extcon support for charger detection / control + on the Intel Cherrytrail Whiskey Cove PMIC. + config EXTCON_MAX14577 tristate "Maxim MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 237ac3f953c2..ecfa95804427 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o +obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ed78b7c26627..e2d78cd7030d 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -51,6 +51,9 @@ #define HPDET_DEBOUNCE 500 #define DEFAULT_MICD_TIMEOUT 2000 +#define ARIZONA_HPDET_WAIT_COUNT 15 +#define ARIZONA_HPDET_WAIT_DELAY_MS 20 + #define QUICK_HEADPHONE_MAX_OHM 3 #define MICROPHONE_MIN_OHM 1257 #define MICROPHONE_MAX_OHM 30000 @@ -1049,6 +1052,40 @@ static void arizona_hpdet_work(struct work_struct *work) mutex_unlock(&info->lock); } +static int arizona_hpdet_wait(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + unsigned int val; + int i, ret; + + for (i = 0; i < ARIZONA_HPDET_WAIT_COUNT; i++) { + ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2, + &val); + if (ret) { + dev_err(arizona->dev, + "Failed to read HPDET state: %d\n", ret); + return ret; + } + + switch (info->hpdet_ip_version) { + case 0: + if (val & ARIZONA_HP_DONE) + return 0; + break; + default: + if (val & ARIZONA_HP_DONE_B) + return 0; + break; + } + + msleep(ARIZONA_HPDET_WAIT_DELAY_MS); + } + + dev_warn(arizona->dev, "HPDET did not appear to complete\n"); + + return -ETIMEDOUT; +} + static irqreturn_t arizona_jackdet(int irq, void *data) { struct arizona_extcon_info *info = data; @@ -1155,6 +1192,15 @@ static irqreturn_t arizona_jackdet(int irq, void *data) "Removal report failed: %d\n", ret); } + /* + * If the jack was removed during a headphone detection we + * need to wait for the headphone detection to finish, as + * it can not be aborted. We don't want to be able to start + * a new headphone detection from a fresh insert until this + * one is finished. + */ + arizona_hpdet_wait(info); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c new file mode 100644 index 000000000000..91a0023074af --- /dev/null +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -0,0 +1,395 @@ +/* + * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> + * + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: + * Copyright (C) 2013-2015 Intel 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, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/extcon.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define CHT_WC_PHYCTRL 0x5e07 + +#define CHT_WC_CHGRCTRL0 0x5e16 +#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) +#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) +#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) +#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) +#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4) +#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5) +#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6) +#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7) + +#define CHT_WC_CHGRCTRL1 0x5e17 + +#define CHT_WC_USBSRC 0x5e29 +#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) +#define CHT_WC_USBSRC_STS_SUCCESS 2 +#define CHT_WC_USBSRC_STS_FAIL 3 +#define CHT_WC_USBSRC_TYPE_SHIFT 2 +#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) +#define CHT_WC_USBSRC_TYPE_NONE 0 +#define CHT_WC_USBSRC_TYPE_SDP 1 +#define CHT_WC_USBSRC_TYPE_DCP 2 +#define CHT_WC_USBSRC_TYPE_CDP 3 +#define CHT_WC_USBSRC_TYPE_ACA 4 +#define CHT_WC_USBSRC_TYPE_SE1 5 +#define CHT_WC_USBSRC_TYPE_MHL 6 +#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7 +#define CHT_WC_USBSRC_TYPE_OTHER 8 +#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 + +#define CHT_WC_PWRSRC_IRQ 0x6e03 +#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f +#define CHT_WC_PWRSRC_STS 0x6e1e +#define CHT_WC_PWRSRC_VBUS BIT(0) +#define CHT_WC_PWRSRC_DC BIT(1) +#define CHT_WC_PWRSRC_BAT BIT(2) +#define CHT_WC_PWRSRC_ID_GND BIT(3) +#define CHT_WC_PWRSRC_ID_FLOAT BIT(4) + +#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d +#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) + +enum cht_wc_usb_id { + USB_ID_OTG, + USB_ID_GND, + USB_ID_FLOAT, + USB_RID_A, + USB_RID_B, + USB_RID_C, +}; + +enum cht_wc_mux_select { + MUX_SEL_PMIC = 0, + MUX_SEL_SOC, +}; + +static const unsigned int cht_wc_extcon_cables[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_ACA, + EXTCON_NONE, +}; + +struct cht_wc_extcon_data { + struct device *dev; + struct regmap *regmap; + struct extcon_dev *edev; + unsigned int previous_cable; + bool usb_host; +}; + +static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) +{ + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND) + return USB_ID_GND; + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT) + return USB_ID_FLOAT; + + /* + * Once we have iio support for the gpadc we should read the USBID + * gpadc channel here and determine ACA role based on that. + */ + return USB_ID_FLOAT; +} + +static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, + bool ignore_errors) +{ + int ret, usbsrc, status; + unsigned long timeout; + + /* Charger detection can take upto 600ms, wait 800ms max. */ + timeout = jiffies + msecs_to_jiffies(800); + do { + ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc); + if (ret) { + dev_err(ext->dev, "Error reading usbsrc: %d\n", ret); + return ret; + } + + status = usbsrc & CHT_WC_USBSRC_STS_MASK; + if (status == CHT_WC_USBSRC_STS_SUCCESS || + status == CHT_WC_USBSRC_STS_FAIL) + break; + + msleep(50); /* Wait a bit before retrying */ + } while (time_before(jiffies, timeout)); + + if (status != CHT_WC_USBSRC_STS_SUCCESS) { + if (ignore_errors) + return EXTCON_CHG_USB_SDP; /* Save fallback */ + + if (status == CHT_WC_USBSRC_STS_FAIL) + dev_warn(ext->dev, "Could not detect charger type\n"); + else + dev_warn(ext->dev, "Timeout detecting charger type\n"); + return EXTCON_CHG_USB_SDP; /* Save fallback */ + } + + usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; + switch (usbsrc) { + default: + dev_warn(ext->dev, + "Unhandled charger type %d, defaulting to SDP\n", + ret); + /* Fall through, treat as SDP */ + case CHT_WC_USBSRC_TYPE_SDP: + case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN: + case CHT_WC_USBSRC_TYPE_OTHER: + return EXTCON_CHG_USB_SDP; + case CHT_WC_USBSRC_TYPE_CDP: + return EXTCON_CHG_USB_CDP; + case CHT_WC_USBSRC_TYPE_DCP: + case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: + case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ + return EXTCON_CHG_USB_DCP; + case CHT_WC_USBSRC_TYPE_ACA: + return EXTCON_CHG_USB_ACA; + } +} + +static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) +{ + int ret; + + ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); + if (ret) + dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); +} + +static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, + bool enable) +{ + int ret, val; + + val = enable ? CHT_WC_VBUS_GPIO_CTLO_OUTPUT : 0; + + /* + * The 5V boost converter is enabled through a gpio on the PMIC, since + * there currently is no gpio driver we access the gpio reg directly. + */ + ret = regmap_update_bits(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, + CHT_WC_VBUS_GPIO_CTLO_OUTPUT, val); + if (ret) + dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret); +} + +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ +static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, + unsigned int cable, bool state) +{ + extcon_set_state_sync(ext->edev, cable, state); + if (cable == EXTCON_CHG_USB_SDP) + extcon_set_state_sync(ext->edev, EXTCON_USB, state); +} + +static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) +{ + int ret, pwrsrc_sts, id; + unsigned int cable = EXTCON_NONE; + /* Ignore errors in host mode, as the 5v boost converter is on then */ + bool ignore_get_charger_errors = ext->usb_host; + + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); + if (ret) { + dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); + return; + } + + id = cht_wc_extcon_get_id(ext, pwrsrc_sts); + if (id == USB_ID_GND) { + /* The 5v boost causes a false VBUS / SDP detect, skip */ + goto charger_det_done; + } + + /* Plugged into a host/charger or not connected? */ + if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { + /* Route D+ and D- to PMIC for future charger detection */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); + goto set_state; + } + + ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors); + if (ret >= 0) + cable = ret; + +charger_det_done: + /* Route D+ and D- to SoC for the host or gadget controller */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC); + +set_state: + if (cable != ext->previous_cable) { + cht_wc_extcon_set_state(ext, cable, true); + cht_wc_extcon_set_state(ext, ext->previous_cable, false); + ext->previous_cable = cable; + } + + ext->usb_host = ((id == USB_ID_GND) || (id == USB_RID_A)); + extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host); +} + +static irqreturn_t cht_wc_extcon_isr(int irq, void *data) +{ + struct cht_wc_extcon_data *ext = data; + int ret, irqs; + + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs); + if (ret) { + dev_err(ext->dev, "Error reading irqs: %d\n", ret); + return IRQ_NONE; + } + + cht_wc_extcon_pwrsrc_event(ext); + + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs); + if (ret) { + dev_err(ext->dev, "Error writing irqs: %d\n", ret); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) +{ + int ret, mask, val; + + mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK; + val = enable ? mask : 0; + ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); + if (ret) + dev_err(ext->dev, "Error setting sw control: %d\n", ret); + + return ret; +} + +static int cht_wc_extcon_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct cht_wc_extcon_data *ext; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL); + if (!ext) + return -ENOMEM; + + ext->dev = &pdev->dev; + ext->regmap = pmic->regmap; + ext->previous_cable = EXTCON_NONE; + + /* Initialize extcon device */ + ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables); + if (IS_ERR(ext->edev)) + return PTR_ERR(ext->edev); + + /* + * When a host-cable is detected the BIOS enables an external 5v boost + * converter to power connected devices there are 2 problems with this: + * 1) This gets seen by the external battery charger as a valid Vbus + * supply and it then tries to feed Vsys from this creating a + * feedback loop which causes aprox. 300 mA extra battery drain + * (and unless we drive the external-charger-disable pin high it + * also tries to charge the battery causing even more feedback). + * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply + * Since the external battery charger has its own 5v boost converter + * which does not have these issues, we simply turn the separate + * external 5v boost converter off and leave it off entirely. + */ + cht_wc_extcon_set_5v_boost(ext, false); + + /* Enable sw control */ + ret = cht_wc_extcon_sw_control(ext, true); + if (ret) + return ret; + + /* Register extcon device */ + ret = devm_extcon_dev_register(ext->dev, ext->edev); + if (ret) { + dev_err(ext->dev, "Error registering extcon device: %d\n", ret); + goto disable_sw_control; + } + + /* Route D+ and D- to PMIC for initial charger detection */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); + + /* Get initial state */ + cht_wc_extcon_pwrsrc_event(ext); + + ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr, + IRQF_ONESHOT, pdev->name, ext); + if (ret) { + dev_err(ext->dev, "Error requesting interrupt: %d\n", ret); + goto disable_sw_control; + } + + /* Unmask irqs */ + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, + (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND | + CHT_WC_PWRSRC_ID_FLOAT)); + if (ret) { + dev_err(ext->dev, "Error writing irq-mask: %d\n", ret); + goto disable_sw_control; + } + + platform_set_drvdata(pdev, ext); + + return 0; + +disable_sw_control: + cht_wc_extcon_sw_control(ext, false); + return ret; +} + +static int cht_wc_extcon_remove(struct platform_device *pdev) +{ + struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); + + cht_wc_extcon_sw_control(ext, false); + + return 0; +} + +static const struct platform_device_id cht_wc_extcon_table[] = { + { .name = "cht_wcove_pwrsrc" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); + +static struct platform_driver cht_wc_extcon_driver = { + .probe = cht_wc_extcon_probe, + .remove = cht_wc_extcon_remove, + .id_table = cht_wc_extcon_table, + .driver = { + .name = "cht_wcove_pwrsrc", + }, +}; +module_platform_driver(cht_wc_extcon_driver); + +MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index ca904e8b3235..2af4369a2d73 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -413,6 +413,12 @@ static int palmas_usb_resume(struct device *dev) if (palmas_usb->enable_gpio_id_detection) disable_irq_wake(palmas_usb->gpio_id_irq); } + + /* check if GPIO states changed while suspend/resume */ + if (palmas_usb->enable_gpio_vbus_detection) + palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb); + palmas_gpio_id_detect(&palmas_usb->wq_detectid.work); + return 0; }; #endif diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index a5e1882b4ca6..9c925b05b7aa 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -26,7 +26,6 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/workqueue.h> -#include <linux/acpi.h> #include <linux/pinctrl/consumer.h> #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ @@ -111,7 +110,7 @@ static int usb_extcon_probe(struct platform_device *pdev) struct usb_extcon_info *info; int ret; - if (!np && !ACPI_HANDLE(dev)) + if (!np) return -EINVAL; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); @@ -195,7 +194,7 @@ static int usb_extcon_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); - device_init_wakeup(dev, true); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ usb_extcon_detect_cable(&info->wq_detcable.work); @@ -282,9 +281,8 @@ static int usb_extcon_resume(struct device *dev) if (info->vbus_gpiod) enable_irq(info->vbus_irq); - if (!device_may_wakeup(dev)) - queue_delayed_work(system_power_efficient_wq, - &info->wq_detcable, 0); + queue_delayed_work(system_power_efficient_wq, + &info->wq_detcable, 0); return ret; } diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index e7750545469f..f422a78ba342 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -230,9 +230,6 @@ struct extcon_cable { }; static struct class *extcon_class; -#if defined(CONFIG_ANDROID) -static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID */ static LIST_HEAD(extcon_dev_list); static DEFINE_MUTEX(extcon_dev_list_lock); @@ -380,7 +377,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, for (i = 0; i < edev->max_supported; i++) { count += sprintf(buf + count, "%s=%d\n", extcon_info[edev->supported_cable[i]].name, - !!(edev->state & (1 << i))); + !!(edev->state & BIT(i))); } return count; @@ -1032,12 +1029,6 @@ static int create_extcon_class(void) if (IS_ERR(extcon_class)) return PTR_ERR(extcon_class); extcon_class->dev_groups = extcon_groups; - -#if defined(CONFIG_ANDROID) - switch_class = class_compat_register("switch"); - if (WARN(!switch_class, "cannot allocate")) - return -ENOMEM; -#endif /* CONFIG_ANDROID */ } return 0; @@ -1259,10 +1250,6 @@ int extcon_dev_register(struct extcon_dev *edev) put_device(&edev->dev); goto err_dev; } -#if defined(CONFIG_ANDROID) - if (switch_class) - ret = class_compat_create_link(switch_class, &edev->dev, NULL); -#endif /* CONFIG_ANDROID */ spin_lock_init(&edev->lock); @@ -1350,10 +1337,6 @@ void extcon_dev_unregister(struct extcon_dev *edev) kfree(edev->cables); } -#if defined(CONFIG_ANDROID) - if (switch_class) - class_compat_remove_link(switch_class, &edev->dev, NULL); -#endif put_device(&edev->dev); } EXPORT_SYMBOL_GPL(extcon_dev_unregister); @@ -1424,9 +1407,6 @@ module_init(extcon_class_init); static void __exit extcon_class_exit(void) { -#if defined(CONFIG_ANDROID) - class_compat_unregister(switch_class); -#endif class_destroy(extcon_class); } module_exit(extcon_class_exit); diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c index 0de83508f321..939d259ddf19 100644 --- a/drivers/firewire/core-topology.c +++ b/drivers/firewire/core-topology.c @@ -124,7 +124,7 @@ static struct fw_node *fw_node_create(u32 sid, int port_count, int color) node->initiated_reset = SELF_ID_PHY_INITIATOR(sid); node->port_count = port_count; - atomic_set(&node->ref_count, 1); + refcount_set(&node->ref_count, 1); INIT_LIST_HEAD(&node->link); return node; diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index e1480ff683d2..c07962ead5e4 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -12,7 +12,7 @@ #include <linux/slab.h> #include <linux/types.h> -#include <linux/atomic.h> +#include <linux/refcount.h> struct device; struct fw_card; @@ -184,7 +184,7 @@ struct fw_node { * local node to this node. */ u8 max_depth:4; /* Maximum depth to any leaf node */ u8 max_hops:4; /* Max hops in this sub tree */ - atomic_t ref_count; + refcount_t ref_count; /* For serializing node topology into a list. */ struct list_head link; @@ -197,14 +197,14 @@ struct fw_node { static inline struct fw_node *fw_node_get(struct fw_node *node) { - atomic_inc(&node->ref_count); + refcount_inc(&node->ref_count); return node; } static inline void fw_node_put(struct fw_node *node) { - if (atomic_dec_and_test(&node->ref_count)) + if (refcount_dec_and_test(&node->ref_count)) kfree(node); } diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 29c8cdda82a1..f16b381a569c 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -1,18 +1,16 @@ -config GOOGLE_FIRMWARE +menuconfig GOOGLE_FIRMWARE bool "Google Firmware Drivers" - depends on X86 default n help These firmware drivers are used by Google's servers. They are only useful if you are working directly on one of their proprietary servers. If in doubt, say "N". -menu "Google Firmware Drivers" - depends on GOOGLE_FIRMWARE +if GOOGLE_FIRMWARE config GOOGLE_SMI tristate "SMI interface for Google platforms" - depends on ACPI && DMI && EFI + depends on X86 && ACPI && DMI && EFI select EFI_VARS help Say Y here if you want to enable SMI callbacks for Google @@ -20,12 +18,57 @@ config GOOGLE_SMI clearing the EFI event log and reading and writing NVRAM variables. +config GOOGLE_COREBOOT_TABLE + tristate + depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF + +config GOOGLE_COREBOOT_TABLE_ACPI + tristate "Coreboot Table Access - ACPI" + depends on ACPI + select GOOGLE_COREBOOT_TABLE + help + This option enables the coreboot_table module, which provides other + firmware modules to access to the coreboot table. The coreboot table + pointer is accessed through the ACPI "GOOGCB00" object. + If unsure say N. + +config GOOGLE_COREBOOT_TABLE_OF + tristate "Coreboot Table Access - Device Tree" + depends on OF + select GOOGLE_COREBOOT_TABLE + help + This option enable the coreboot_table module, which provide other + firmware modules to access coreboot table. The coreboot table pointer + is accessed through the device tree node /firmware/coreboot. + If unsure say N. + config GOOGLE_MEMCONSOLE - tristate "Firmware Memory Console" - depends on DMI + tristate + depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT + +config GOOGLE_MEMCONSOLE_X86_LEGACY + tristate "Firmware Memory Console - X86 Legacy support" + depends on X86 && ACPI && DMI + select GOOGLE_MEMCONSOLE help This option enables the kernel to search for a firmware log in the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log. -endmenu +config GOOGLE_MEMCONSOLE_COREBOOT + tristate "Firmware Memory Console" + depends on GOOGLE_COREBOOT_TABLE + select GOOGLE_MEMCONSOLE + help + This option enables the kernel to search for a firmware log in + the coreboot table. If found, this log is exported to userland + in the file /sys/firmware/log. + +config GOOGLE_VPD + tristate "Vital Product Data" + depends on GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to expose the content of Google VPD + under /sys/firmware/vpd. + +endif # GOOGLE_FIRMWARE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index 54a294e3cb61..bc4de02202ad 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -1,3 +1,11 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o -obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o + +vpd-sysfs-y := vpd.o vpd_decode.o +obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c new file mode 100644 index 000000000000..fb98db2d20e2 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-acpi.c @@ -0,0 +1,88 @@ +/* + * coreboot_table-acpi.c + * + * Using ACPI to locate Coreboot table and provide coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "coreboot_table.h" + +static int coreboot_table_acpi_probe(struct platform_device *pdev) +{ + phys_addr_t phyaddr; + resource_size_t len; + struct coreboot_table_header __iomem *header = NULL; + struct resource *res; + void __iomem *ptr = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + len = resource_size(res); + if (!res->start || !len) + return -EINVAL; + + phyaddr = res->start; + header = ioremap_cache(phyaddr, sizeof(*header)); + if (header == NULL) + return -ENOMEM; + + ptr = ioremap_cache(phyaddr, + header->header_bytes + header->table_bytes); + iounmap(header); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_acpi_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct acpi_device_id cros_coreboot_acpi_match[] = { + { "GOOGCB00", 0 }, + { "BOOT0000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); + +static struct platform_driver coreboot_table_acpi_driver = { + .probe = coreboot_table_acpi_probe, + .remove = coreboot_table_acpi_remove, + .driver = { + .name = "coreboot_table_acpi", + .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match), + }, +}; + +static int __init coreboot_table_acpi_init(void) +{ + return platform_driver_register(&coreboot_table_acpi_driver); +} + +module_init(coreboot_table_acpi_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c new file mode 100644 index 000000000000..727acdc83e83 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-of.c @@ -0,0 +1,82 @@ +/* + * coreboot_table-of.c + * + * Coreboot table access through open firmware. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include "coreboot_table.h" + +static int coreboot_table_of_probe(struct platform_device *pdev) +{ + struct device_node *fw_dn = pdev->dev.of_node; + void __iomem *ptr; + + ptr = of_iomap(fw_dn, 0); + of_node_put(fw_dn); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_of_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct of_device_id coreboot_of_match[] = { + { .compatible = "coreboot" }, + {}, +}; + +static struct platform_driver coreboot_table_of_driver = { + .probe = coreboot_table_of_probe, + .remove = coreboot_table_of_remove, + .driver = { + .name = "coreboot_table_of", + .of_match_table = coreboot_of_match, + }, +}; + +static int __init platform_coreboot_table_of_init(void) +{ + struct platform_device *pdev; + struct device_node *of_node; + + /* Limit device creation to the presence of /firmware/coreboot node */ + of_node = of_find_node_by_path("/firmware/coreboot"); + if (!of_node) + return -ENODEV; + + if (!of_match_node(coreboot_of_match, of_node)) + return -ENODEV; + + pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL); + if (!pdev) + return -ENODEV; + + return platform_driver_register(&coreboot_table_of_driver); +} + +module_init(platform_coreboot_table_of_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c new file mode 100644 index 000000000000..0019d3ec18dd --- /dev/null +++ b/drivers/firmware/google/coreboot_table.c @@ -0,0 +1,94 @@ +/* + * coreboot_table.c + * + * Module providing coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "coreboot_table.h" + +struct coreboot_table_entry { + u32 tag; + u32 size; +}; + +static struct coreboot_table_header __iomem *ptr_header; + +/* + * This function parses the coreboot table for an entry that contains the base + * address of the given entry tag. The coreboot table consists of a header + * directly followed by a number of small, variable-sized entries, which each + * contain an identifying tag and their length as the first two fields. + */ +int coreboot_table_find(int tag, void *data, size_t data_size) +{ + struct coreboot_table_header header; + struct coreboot_table_entry entry; + void *ptr_entry; + int i; + + if (!ptr_header) + return -EPROBE_DEFER; + + memcpy_fromio(&header, ptr_header, sizeof(header)); + + if (strncmp(header.signature, "LBIO", sizeof(header.signature))) { + pr_warn("coreboot_table: coreboot table missing or corrupt!\n"); + return -ENODEV; + } + + ptr_entry = (void *)ptr_header + header.header_bytes; + + for (i = 0; i < header.table_entries; i++) { + memcpy_fromio(&entry, ptr_entry, sizeof(entry)); + if (entry.tag == tag) { + if (data_size < entry.size) + return -EINVAL; + + memcpy_fromio(data, ptr_entry, entry.size); + + return 0; + } + + ptr_entry += entry.size; + } + + return -ENOENT; +} +EXPORT_SYMBOL(coreboot_table_find); + +int coreboot_table_init(void __iomem *ptr) +{ + ptr_header = ptr; + + return 0; +} +EXPORT_SYMBOL(coreboot_table_init); + +int coreboot_table_exit(void) +{ + if (ptr_header) + iounmap(ptr_header); + + return 0; +} +EXPORT_SYMBOL(coreboot_table_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h new file mode 100644 index 000000000000..6eff1ae0c5d3 --- /dev/null +++ b/drivers/firmware/google/coreboot_table.h @@ -0,0 +1,50 @@ +/* + * coreboot_table.h + * + * Internal header for coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __COREBOOT_TABLE_H +#define __COREBOOT_TABLE_H + +#include <linux/io.h> + +/* List of coreboot entry structures that is used */ +struct lb_cbmem_ref { + uint32_t tag; + uint32_t size; + + uint64_t cbmem_addr; +}; + +/* Coreboot table header structure */ +struct coreboot_table_header { + char signature[4]; + u32 header_bytes; + u32 header_checksum; + u32 table_bytes; + u32 table_checksum; + u32 table_entries; +}; + +/* Retrieve coreboot table entry with tag *tag* and copy it to data */ +int coreboot_table_find(int tag, void *data, size_t data_size); + +/* Initialize coreboot table module given a pointer to iomem */ +int coreboot_table_init(void __iomem *ptr); + +/* Cleanup coreboot table module */ +int coreboot_table_exit(void); + +#endif /* __COREBOOT_TABLE_H */ diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c new file mode 100644 index 000000000000..02711114dece --- /dev/null +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -0,0 +1,109 @@ +/* + * memconsole-coreboot.c + * + * Memory based BIOS console accessed through coreboot table. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "memconsole.h" +#include "coreboot_table.h" + +#define CB_TAG_CBMEM_CONSOLE 0x17 + +/* CBMEM firmware console log descriptor. */ +struct cbmem_cons { + u32 buffer_size; + u32 buffer_cursor; + u8 buffer_body[0]; +} __packed; + +static struct cbmem_cons __iomem *cbmem_console; + +static int memconsole_coreboot_init(phys_addr_t physaddr) +{ + struct cbmem_cons __iomem *tmp_cbmc; + + tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB); + + if (!tmp_cbmc) + return -ENOMEM; + + cbmem_console = memremap(physaddr, + tmp_cbmc->buffer_size + sizeof(*cbmem_console), + MEMREMAP_WB); + memunmap(tmp_cbmc); + + if (!cbmem_console) + return -ENOMEM; + + memconsole_setup(cbmem_console->buffer_body, + min(cbmem_console->buffer_cursor, cbmem_console->buffer_size)); + + return 0; +} + +static int memconsole_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry)); + if (ret) + return ret; + + ret = memconsole_coreboot_init(entry.cbmem_addr); + if (ret) + return ret; + + return memconsole_sysfs_init(); +} + +static int memconsole_remove(struct platform_device *pdev) +{ + memconsole_exit(); + + if (cbmem_console) + memunmap(cbmem_console); + + return 0; +} + +static struct platform_driver memconsole_driver = { + .probe = memconsole_probe, + .remove = memconsole_remove, + .driver = { + .name = "memconsole", + }, +}; + +static int __init platform_memconsole_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("memconsole", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + platform_driver_register(&memconsole_driver); + + return 0; +} + +module_init(platform_memconsole_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c new file mode 100644 index 000000000000..1f279ee883b9 --- /dev/null +++ b/drivers/firmware/google/memconsole-x86-legacy.c @@ -0,0 +1,153 @@ +/* + * memconsole-x86-legacy.c + * + * EBDA specific parts of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/mm.h> +#include <asm/bios_ebda.h> +#include <linux/acpi.h> + +#include "memconsole.h" + +#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE +#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) + +struct biosmemcon_ebda { + u32 signature; + union { + struct { + u8 enabled; + u32 buffer_addr; + u16 start; + u16 end; + u16 num_chars; + u8 wrapped; + } __packed v1; + struct { + u32 buffer_addr; + /* Misdocumented as number of pages! */ + u16 num_bytes; + u16 start; + u16 end; + } __packed v2; + }; +} __packed; + +static void found_v1_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n", + hdr->v1.buffer_addr, hdr->v1.start, + hdr->v1.end, hdr->v1.num_chars); + + memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars); +} + +static void found_v2_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n", + hdr->v2.buffer_addr, hdr->v2.start, + hdr->v2.end, hdr->v2.num_bytes); + + memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start), + hdr->v2.end - hdr->v2.start); +} + +/* + * Search through the EBDA for the BIOS Memory Console, and + * set the global variables to point to it. Return true if found. + */ +static bool memconsole_ebda_init(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + pr_info("memconsole: BIOS EBDA non-existent.\n"); + return false; + } + + /* EBDA length is byte 0 of EBDA (in KB) */ + length = *(u8 *)phys_to_virt(address); + length <<= 10; /* convert to bytes */ + + /* + * Search through EBDA for BIOS memory console structure + * note: signature is not necessarily dword-aligned + */ + for (cur = 0; cur < length; cur++) { + struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); + + /* memconsole v1 */ + if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { + found_v1_header(hdr); + return true; + } + + /* memconsole v2 */ + if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { + found_v2_header(hdr); + return true; + } + } + + pr_info("memconsole: BIOS console EBDA structure not found!\n"); + return false; +} + +static struct dmi_system_id memconsole_dmi_table[] __initdata = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); + +static bool __init memconsole_find(void) +{ + if (!dmi_check_system(memconsole_dmi_table)) + return false; + + return memconsole_ebda_init(); +} + +static int __init memconsole_x86_init(void) +{ + if (!memconsole_find()) + return -ENODEV; + + return memconsole_sysfs_init(); +} + +static void __exit memconsole_x86_exit(void) +{ + memconsole_exit(); +} + +module_init(memconsole_x86_init); +module_exit(memconsole_x86_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c index 2f569aaed4c7..94e200ddb4fa 100644 --- a/drivers/firmware/google/memconsole.c +++ b/drivers/firmware/google/memconsole.c @@ -1,66 +1,36 @@ /* * memconsole.c * - * Infrastructure for importing the BIOS memory based console - * into the kernel log ringbuffer. + * Architecture-independent parts of the memory based BIOS console. * - * Copyright 2010 Google Inc. All rights reserved. + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. */ -#include <linux/ctype.h> #include <linux/init.h> -#include <linux/kernel.h> -#include <linux/string.h> #include <linux/sysfs.h> #include <linux/kobject.h> #include <linux/module.h> -#include <linux/dmi.h> -#include <linux/io.h> -#include <asm/bios_ebda.h> -#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE -#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) +#include "memconsole.h" -struct biosmemcon_ebda { - u32 signature; - union { - struct { - u8 enabled; - u32 buffer_addr; - u16 start; - u16 end; - u16 num_chars; - u8 wrapped; - } __packed v1; - struct { - u32 buffer_addr; - /* Misdocumented as number of pages! */ - u16 num_bytes; - u16 start; - u16 end; - } __packed v2; - }; -} __packed; - -static u32 memconsole_baseaddr; +static char *memconsole_baseaddr; static size_t memconsole_length; static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { - char *memconsole; - ssize_t ret; - - memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length); - if (!memconsole) { - pr_err("memconsole: ioremap_cache failed\n"); - return -ENOMEM; - } - ret = memory_read_from_buffer(buf, count, &pos, memconsole, - memconsole_length); - iounmap(memconsole); - return ret; + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); } static struct bin_attribute memconsole_bin_attr = { @@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = { .read = memconsole_read, }; - -static void __init found_v1_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v1 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num = %d\n", - hdr->v1.buffer_addr, hdr->v1.start, - hdr->v1.end, hdr->v1.num_chars); - - memconsole_length = hdr->v1.num_chars; - memconsole_baseaddr = hdr->v1.buffer_addr; -} - -static void __init found_v2_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v2 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num_bytes = %d\n", - hdr->v2.buffer_addr, hdr->v2.start, - hdr->v2.end, hdr->v2.num_bytes); - - memconsole_length = hdr->v2.end - hdr->v2.start; - memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start; -} - -/* - * Search through the EBDA for the BIOS Memory Console, and - * set the global variables to point to it. Return true if found. - */ -static bool __init found_memconsole(void) +void memconsole_setup(void *baseaddr, size_t length) { - unsigned int address; - size_t length, cur; - - address = get_bios_ebda(); - if (!address) { - pr_info("BIOS EBDA non-existent.\n"); - return false; - } - - /* EBDA length is byte 0 of EBDA (in KB) */ - length = *(u8 *)phys_to_virt(address); - length <<= 10; /* convert to bytes */ - - /* - * Search through EBDA for BIOS memory console structure - * note: signature is not necessarily dword-aligned - */ - for (cur = 0; cur < length; cur++) { - struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); - - /* memconsole v1 */ - if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { - found_v1_header(hdr); - return true; - } - - /* memconsole v2 */ - if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { - found_v2_header(hdr); - return true; - } - } - - pr_info("BIOS console EBDA structure not found!\n"); - return false; + memconsole_baseaddr = baseaddr; + memconsole_length = length; } +EXPORT_SYMBOL(memconsole_setup); -static struct dmi_system_id memconsole_dmi_table[] __initdata = { - { - .ident = "Google Board", - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), - }, - }, - {} -}; -MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); - -static int __init memconsole_init(void) +int memconsole_sysfs_init(void) { - if (!dmi_check_system(memconsole_dmi_table)) - return -ENODEV; - - if (!found_memconsole()) - return -ENODEV; - memconsole_bin_attr.size = memconsole_length; return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); } +EXPORT_SYMBOL(memconsole_sysfs_init); -static void __exit memconsole_exit(void) +void memconsole_exit(void) { sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr); } - -module_init(memconsole_init); -module_exit(memconsole_exit); +EXPORT_SYMBOL(memconsole_exit); MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h new file mode 100644 index 000000000000..190fc03a51ae --- /dev/null +++ b/drivers/firmware/google/memconsole.h @@ -0,0 +1,43 @@ +/* + * memconsole.h + * + * Internal headers of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H +#define __FIRMWARE_GOOGLE_MEMCONSOLE_H + +/* + * memconsole_setup + * + * Initialize the memory console from raw (virtual) base + * address and length. + */ +void memconsole_setup(void *baseaddr, size_t length); + +/* + * memconsole_sysfs_init + * + * Update memory console length and create binary file + * for firmware object. + */ +int memconsole_sysfs_init(void); + +/* memconsole_exit + * + * Unmap the console buffer. + */ +void memconsole_exit(void); + +#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */ diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c new file mode 100644 index 000000000000..3ce813110d5e --- /dev/null +++ b/drivers/firmware/google/vpd.c @@ -0,0 +1,332 @@ +/* + * vpd.c + * + * Driver for exporting VPD content to sysfs. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include "coreboot_table.h" +#include "vpd_decode.h" + +#define CB_TAG_VPD 0x2c +#define VPD_CBMEM_MAGIC 0x43524f53 + +static struct kobject *vpd_kobj; + +struct vpd_cbmem { + u32 magic; + u32 version; + u32 ro_size; + u32 rw_size; + u8 blob[0]; +}; + +struct vpd_section { + bool enabled; + const char *name; + char *raw_name; /* the string name_raw */ + struct kobject *kobj; /* vpd/name directory */ + char *baseaddr; + struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */ + struct list_head attribs; /* key/value in vpd_attrib_info list */ +}; + +struct vpd_attrib_info { + char *key; + const char *value; + struct bin_attribute bin_attr; + struct list_head list; +}; + +static struct vpd_section ro_vpd; +static struct vpd_section rw_vpd; + +static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_attrib_info *info = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, info->value, + info->bin_attr.size); +} + +/* + * vpd_section_check_key_name() + * + * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but + * old firmware versions may have entries like "S/N" which are problematic when + * exporting them as sysfs attributes. These keys present in old firmwares are + * ignored. + * + * Returns VPD_OK for a valid key name, VPD_FAIL otherwise. + * + * @key: The key name to check + * @key_len: key name length + */ +static int vpd_section_check_key_name(const u8 *key, s32 key_len) +{ + int c; + + while (key_len-- > 0) { + c = *key++; + + if (!isalnum(c) && c != '_') + return VPD_FAIL; + } + + return VPD_OK; +} + +static int vpd_section_attrib_add(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg) +{ + int ret; + struct vpd_section *sec = arg; + struct vpd_attrib_info *info; + + /* + * Return VPD_OK immediately to decode next entry if the current key + * name contains invalid characters. + */ + if (vpd_section_check_key_name(key, key_len) != VPD_OK) + return VPD_OK; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + info->key = kzalloc(key_len + 1, GFP_KERNEL); + if (!info->key) + return -ENOMEM; + + memcpy(info->key, key, key_len); + + sysfs_bin_attr_init(&info->bin_attr); + info->bin_attr.attr.name = info->key; + info->bin_attr.attr.mode = 0444; + info->bin_attr.size = value_len; + info->bin_attr.read = vpd_attrib_read; + info->bin_attr.private = info; + + info->value = value; + + INIT_LIST_HEAD(&info->list); + list_add_tail(&info->list, &sec->attribs); + + ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr); + if (ret) { + kfree(info->key); + return ret; + } + + return 0; +} + +static void vpd_section_attrib_destroy(struct vpd_section *sec) +{ + struct vpd_attrib_info *info; + struct vpd_attrib_info *temp; + + list_for_each_entry_safe(info, temp, &sec->attribs, list) { + kfree(info->key); + sysfs_remove_bin_file(sec->kobj, &info->bin_attr); + kfree(info); + } +} + +static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_section *sec = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, sec->baseaddr, + sec->bin_attr.size); +} + +static int vpd_section_create_attribs(struct vpd_section *sec) +{ + s32 consumed; + int ret; + + consumed = 0; + do { + ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr, + &consumed, vpd_section_attrib_add, sec); + } while (ret == VPD_OK); + + return 0; +} + +static int vpd_section_init(const char *name, struct vpd_section *sec, + phys_addr_t physaddr, size_t size) +{ + int ret; + int raw_len; + + sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); + if (!sec->baseaddr) + return -ENOMEM; + + sec->name = name; + + /* We want to export the raw partion with name ${name}_raw */ + raw_len = strlen(name) + 5; + sec->raw_name = kzalloc(raw_len, GFP_KERNEL); + strncpy(sec->raw_name, name, raw_len); + strncat(sec->raw_name, "_raw", raw_len); + + sysfs_bin_attr_init(&sec->bin_attr); + sec->bin_attr.attr.name = sec->raw_name; + sec->bin_attr.attr.mode = 0444; + sec->bin_attr.size = size; + sec->bin_attr.read = vpd_section_read; + sec->bin_attr.private = sec; + + ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); + if (ret) + goto free_sec; + + sec->kobj = kobject_create_and_add(name, vpd_kobj); + if (!sec->kobj) { + ret = -EINVAL; + goto sysfs_remove; + } + + INIT_LIST_HEAD(&sec->attribs); + vpd_section_create_attribs(sec); + + sec->enabled = true; + + return 0; + +sysfs_remove: + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + +free_sec: + kfree(sec->raw_name); + iounmap(sec->baseaddr); + + return ret; +} + +static int vpd_section_destroy(struct vpd_section *sec) +{ + if (sec->enabled) { + vpd_section_attrib_destroy(sec); + kobject_del(sec->kobj); + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + kfree(sec->raw_name); + iounmap(sec->baseaddr); + } + + return 0; +} + +static int vpd_sections_init(phys_addr_t physaddr) +{ + struct vpd_cbmem __iomem *temp; + struct vpd_cbmem header; + int ret = 0; + + temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB); + if (!temp) + return -ENOMEM; + + memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem)); + iounmap(temp); + + if (header.magic != VPD_CBMEM_MAGIC) + return -ENODEV; + + if (header.ro_size) { + ret = vpd_section_init("ro", &ro_vpd, + physaddr + sizeof(struct vpd_cbmem), + header.ro_size); + if (ret) + return ret; + } + + if (header.rw_size) { + ret = vpd_section_init("rw", &rw_vpd, + physaddr + sizeof(struct vpd_cbmem) + + header.ro_size, header.rw_size); + if (ret) + return ret; + } + + return 0; +} + +static int vpd_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry)); + if (ret) + return ret; + + return vpd_sections_init(entry.cbmem_addr); +} + +static struct platform_driver vpd_driver = { + .probe = vpd_probe, + .driver = { + .name = "vpd", + }, +}; + +static int __init vpd_platform_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("vpd", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); + if (!vpd_kobj) + return -ENOMEM; + + memset(&ro_vpd, 0, sizeof(ro_vpd)); + memset(&rw_vpd, 0, sizeof(rw_vpd)); + + platform_driver_register(&vpd_driver); + + return 0; +} + +static void __exit vpd_platform_exit(void) +{ + vpd_section_destroy(&ro_vpd); + vpd_section_destroy(&rw_vpd); + kobject_del(vpd_kobj); +} + +module_init(vpd_platform_init); +module_exit(vpd_platform_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c new file mode 100644 index 000000000000..943acaa8aa76 --- /dev/null +++ b/drivers/firmware/google/vpd_decode.c @@ -0,0 +1,99 @@ +/* + * vpd_decode.c + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/export.h> + +#include "vpd_decode.h" + +static int vpd_decode_len(const s32 max_len, const u8 *in, + s32 *length, s32 *decoded_len) +{ + u8 more; + int i = 0; + + if (!length || !decoded_len) + return VPD_FAIL; + + *length = 0; + do { + if (i >= max_len) + return VPD_FAIL; + + more = in[i] & 0x80; + *length <<= 7; + *length |= in[i] & 0x7f; + ++i; + } while (more); + + *decoded_len = i; + + return VPD_OK; +} + +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg) +{ + int type; + int res; + s32 key_len; + s32 value_len; + s32 decoded_len; + const u8 *key; + const u8 *value; + + /* type */ + if (*consumed >= max_len) + return VPD_FAIL; + + type = input_buf[*consumed]; + + switch (type) { + case VPD_TYPE_INFO: + case VPD_TYPE_STRING: + (*consumed)++; + + /* key */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &key_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len >= max_len) + return VPD_FAIL; + + *consumed += decoded_len; + key = &input_buf[*consumed]; + *consumed += key_len; + + /* value */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &value_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len > max_len) + return VPD_FAIL; + + *consumed += decoded_len; + value = &input_buf[*consumed]; + *consumed += value_len; + + if (type == VPD_TYPE_STRING) + return callback(key, key_len, value, value_len, + callback_arg); + break; + + default: + return VPD_FAIL; + } + + return VPD_OK; +} diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h new file mode 100644 index 000000000000..be3d62c5ca2f --- /dev/null +++ b/drivers/firmware/google/vpd_decode.h @@ -0,0 +1,58 @@ +/* + * vpd_decode.h + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __VPD_DECODE_H +#define __VPD_DECODE_H + +#include <linux/types.h> + +enum { + VPD_OK = 0, + VPD_FAIL, +}; + +enum { + VPD_TYPE_TERMINATOR = 0, + VPD_TYPE_STRING, + VPD_TYPE_INFO = 0xfe, + VPD_TYPE_IMPLICIT_TERMINATOR = 0xff, +}; + +/* Callback for vpd_decode_string to invoke. */ +typedef int vpd_decode_callback(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg); + +/* + * vpd_decode_string + * + * Given the encoded string, this function invokes callback with extracted + * (key, value). The *consumed will be plused the number of bytes consumed in + * this function. + * + * The input_buf points to the first byte of the input buffer. + * + * The *consumed starts from 0, which is actually the next byte to be decoded. + * It can be non-zero to be used in multiple calls. + * + * If one entry is successfully decoded, sends it to callback and returns the + * result. + */ +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg); + +#endif /* __VPD_DECODE_H */ diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index ce861a2853a4..161ba9dccede 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -20,6 +20,12 @@ config FPGA_REGION FPGA Regions allow loading FPGA images under control of the Device Tree. +config FPGA_MGR_ICE40_SPI + tristate "Lattice iCE40 SPI" + depends on OF && SPI + help + FPGA manager driver support for Lattice iCE40 FPGAs over SPI. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" depends on ARCH_SOCFPGA || COMPILE_TEST @@ -33,6 +39,20 @@ config FPGA_MGR_SOCFPGA_A10 help FPGA manager driver support for Altera Arria10 SoCFPGA. +config FPGA_MGR_TS73XX + tristate "Technologic Systems TS-73xx SBC FPGA Manager" + depends on ARCH_EP93XX && MACH_TS72XX + help + FPGA manager driver support for the Altera Cyclone II FPGA + present on the TS-73xx SBC boards. + +config FPGA_MGR_XILINX_SPI + tristate "Xilinx Configuration over Slave Serial (SPI)" + depends on SPI + help + FPGA manager driver support for Xilinx FPGA configuration + over slave serial interface. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST @@ -63,6 +83,28 @@ config ALTERA_FREEZE_BRIDGE isolate one region of the FPGA from the busses while that region is being reprogrammed. +config ALTERA_PR_IP_CORE + tristate "Altera Partial Reconfiguration IP Core" + help + Core driver support for Altera Partial Reconfiguration IP component + +config ALTERA_PR_IP_CORE_PLAT + tristate "Platform support of Altera Partial Reconfiguration IP Core" + depends on ALTERA_PR_IP_CORE && OF && HAS_IOMEM + help + Platform driver support for Altera Partial Reconfiguration IP + component + +config XILINX_PR_DECOUPLER + tristate "Xilinx LogiCORE PR Decoupler" + depends on FPGA_BRIDGE + depends on HAS_IOMEM + help + Say Y to enable drivers for Xilinx LogiCORE PR Decoupler. + The PR Decoupler exists in the FPGA fabric to isolate one + region of the FPGA from the busses while that region is + being reprogrammed during partial reconfig. + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 8df07bcf42a6..2a4f0218145c 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -6,14 +6,20 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers +obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o +obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o +obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o +obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o +obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o # FPGA Bridge Drivers obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o +obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o # High Level Interfaces obj-$(CONFIG_FPGA_REGION) += fpga-region.o diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c index 8dcd9fb22cb9..6159cfcf78a2 100644 --- a/drivers/fpga/altera-freeze-bridge.c +++ b/drivers/fpga/altera-freeze-bridge.c @@ -28,6 +28,7 @@ #define FREEZE_CSR_REG_VERSION 12 #define FREEZE_CSR_SUPPORTED_VERSION 2 +#define FREEZE_CSR_OFFICIAL_VERSION 0xad000003 #define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0) #define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1) @@ -203,7 +204,7 @@ static int altera_freeze_br_enable_show(struct fpga_bridge *bridge) return priv->enable; } -static struct fpga_bridge_ops altera_freeze_br_br_ops = { +static const struct fpga_bridge_ops altera_freeze_br_br_ops = { .enable_set = altera_freeze_br_enable_set, .enable_show = altera_freeze_br_enable_show, }; @@ -218,6 +219,7 @@ static int altera_freeze_br_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; + void __iomem *base_addr; struct altera_freeze_br_data *priv; struct resource *res; u32 status, revision; @@ -225,26 +227,32 @@ static int altera_freeze_br_probe(struct platform_device *pdev) if (!np) return -ENODEV; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(base_addr)) + return PTR_ERR(base_addr); + + revision = readl(base_addr + FREEZE_CSR_REG_VERSION); + if ((revision != FREEZE_CSR_SUPPORTED_VERSION) && + (revision != FREEZE_CSR_OFFICIAL_VERSION)) { + dev_err(dev, + "%s unexpected revision 0x%x != 0x%x != 0x%x\n", + __func__, revision, FREEZE_CSR_SUPPORTED_VERSION, + FREEZE_CSR_OFFICIAL_VERSION); + return -EINVAL; + } + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base_addr = devm_ioremap_resource(dev, res); - if (IS_ERR(priv->base_addr)) - return PTR_ERR(priv->base_addr); - - status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET); if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) priv->enable = 1; - revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION); - if (revision != FREEZE_CSR_SUPPORTED_VERSION) - dev_warn(dev, - "%s Freeze Controller unexpected revision %d != %d\n", - __func__, revision, FREEZE_CSR_SUPPORTED_VERSION); + priv->base_addr = base_addr; return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME, &altera_freeze_br_br_ops, priv); diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c index 4b354c79be31..3066b805f2d0 100644 --- a/drivers/fpga/altera-hps2fpga.c +++ b/drivers/fpga/altera-hps2fpga.c @@ -181,15 +181,18 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev) (enable ? "enabling" : "disabling")); ret = _alt_hps2fpga_enable_set(priv, enable); - if (ret) { - fpga_bridge_unregister(&pdev->dev); - return ret; - } + if (ret) + goto err; } } - return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, - priv); + ret = fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, + priv); +err: + if (ret) + clk_disable_unprepare(priv->clk); + + return ret; } static int alt_fpga_bridge_remove(struct platform_device *pdev) diff --git a/drivers/fpga/altera-pr-ip-core-plat.c b/drivers/fpga/altera-pr-ip-core-plat.c new file mode 100644 index 000000000000..8fb36b8b4648 --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core-plat.c @@ -0,0 +1,68 @@ +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull <atull@opensource.altera.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/fpga/altera-pr-ip-core.h> +#include <linux/module.h> +#include <linux/of_device.h> + +static int alt_pr_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + void __iomem *reg_base; + struct resource *res; + + /* First mmio base is for register access */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + reg_base = devm_ioremap_resource(dev, res); + + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + + return alt_pr_register(dev, reg_base); +} + +static int alt_pr_platform_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return alt_pr_unregister(dev); +} + +static const struct of_device_id alt_pr_of_match[] = { + { .compatible = "altr,a10-pr-ip", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, alt_pr_of_match); + +static struct platform_driver alt_pr_platform_driver = { + .probe = alt_pr_platform_probe, + .remove = alt_pr_platform_remove, + .driver = { + .name = "alt_a10_pr_ip", + .of_match_table = alt_pr_of_match, + }, +}; + +module_platform_driver(alt_pr_platform_driver); +MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>"); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Platform Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-pr-ip-core.c b/drivers/fpga/altera-pr-ip-core.c new file mode 100644 index 000000000000..a7b31f9797ce --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core.c @@ -0,0 +1,220 @@ +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull <atull@opensource.altera.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/delay.h> +#include <linux/fpga/altera-pr-ip-core.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/module.h> + +#define ALT_PR_DATA_OFST 0x00 +#define ALT_PR_CSR_OFST 0x04 + +#define ALT_PR_CSR_PR_START BIT(0) +#define ALT_PR_CSR_STATUS_SFT 2 +#define ALT_PR_CSR_STATUS_MSK (7 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_NRESET (0 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_ERR (1 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_CRC_ERR (2 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_BAD_BITS (3 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_IN_PROG (4 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_SUCCESS (5 << ALT_PR_CSR_STATUS_SFT) + +struct alt_pr_priv { + void __iomem *reg_base; +}; + +static enum fpga_mgr_states alt_pr_fpga_state(struct fpga_manager *mgr) +{ + struct alt_pr_priv *priv = mgr->priv; + const char *err = "unknown"; + enum fpga_mgr_states ret = FPGA_MGR_STATE_UNKNOWN; + u32 val; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + val &= ALT_PR_CSR_STATUS_MSK; + + switch (val) { + case ALT_PR_CSR_STATUS_NRESET: + return FPGA_MGR_STATE_RESET; + + case ALT_PR_CSR_STATUS_PR_ERR: + err = "pr error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_CRC_ERR: + err = "crc error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_BAD_BITS: + err = "bad bits"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_PR_IN_PROG: + return FPGA_MGR_STATE_WRITE; + + case ALT_PR_CSR_STATUS_PR_SUCCESS: + return FPGA_MGR_STATE_OPERATING; + + default: + break; + } + + dev_err(&mgr->dev, "encountered error code %d (%s) in %s()\n", + val, err, __func__); + return ret; +} + +static int alt_pr_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct alt_pr_priv *priv = mgr->priv; + u32 val; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&mgr->dev, "%s Partial Reconfiguration flag not set\n", + __func__); + return -EINVAL; + } + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + if (val & ALT_PR_CSR_PR_START) { + dev_err(&mgr->dev, + "%s Partial Reconfiguration already started\n", + __func__); + return -EINVAL; + } + + writel(val | ALT_PR_CSR_PR_START, priv->reg_base + ALT_PR_CSR_OFST); + + return 0; +} + +static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct alt_pr_priv *priv = mgr->priv; + u32 *buffer_32 = (u32 *)buf; + size_t i = 0; + + if (count <= 0) + return -EINVAL; + + /* Write out the complete 32-bit chunks */ + while (count >= sizeof(u32)) { + writel(buffer_32[i++], priv->reg_base); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks */ + switch (count) { + case 3: + writel(buffer_32[i++] & 0x00ffffff, priv->reg_base); + break; + case 2: + writel(buffer_32[i++] & 0x0000ffff, priv->reg_base); + break; + case 1: + writel(buffer_32[i++] & 0x000000ff, priv->reg_base); + break; + case 0: + break; + default: + /* This will never happen */ + return -EFAULT; + } + + if (alt_pr_fpga_state(mgr) == FPGA_MGR_STATE_WRITE_ERR) + return -EIO; + + return 0; +} + +static int alt_pr_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + u32 i = 0; + + do { + switch (alt_pr_fpga_state(mgr)) { + case FPGA_MGR_STATE_WRITE_ERR: + return -EIO; + + case FPGA_MGR_STATE_OPERATING: + dev_info(&mgr->dev, + "successful partial reconfiguration\n"); + return 0; + + default: + break; + } + udelay(1); + } while (info->config_complete_timeout_us > i++); + + dev_err(&mgr->dev, "timed out waiting for write to complete\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops alt_pr_ops = { + .state = alt_pr_fpga_state, + .write_init = alt_pr_fpga_write_init, + .write = alt_pr_fpga_write, + .write_complete = alt_pr_fpga_write_complete, +}; + +int alt_pr_register(struct device *dev, void __iomem *reg_base) +{ + struct alt_pr_priv *priv; + u32 val; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reg_base = reg_base; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + dev_dbg(dev, "%s status=%d start=%d\n", __func__, + (val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT, + (int)(val & ALT_PR_CSR_PR_START)); + + return fpga_mgr_register(dev, dev_name(dev), &alt_pr_ops, priv); +} +EXPORT_SYMBOL_GPL(alt_pr_register); + +int alt_pr_unregister(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + fpga_mgr_unregister(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(alt_pr_unregister); + +MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>"); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 33ee83e6373c..9651aa56244a 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -27,7 +27,7 @@ static DEFINE_IDA(fpga_bridge_ida); static struct class *fpga_bridge_class; /* Lock for adding/removing bridges to linked lists*/ -spinlock_t bridge_list_lock; +static spinlock_t bridge_list_lock; static int fpga_bridge_of_node_match(struct device *dev, const void *data) { @@ -146,11 +146,9 @@ EXPORT_SYMBOL_GPL(fpga_bridge_put); int fpga_bridges_enable(struct list_head *bridge_list) { struct fpga_bridge *bridge; - struct list_head *node; int ret; - list_for_each(node, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); + list_for_each_entry(bridge, bridge_list, node) { ret = fpga_bridge_enable(bridge); if (ret) return ret; @@ -172,11 +170,9 @@ EXPORT_SYMBOL_GPL(fpga_bridges_enable); int fpga_bridges_disable(struct list_head *bridge_list) { struct fpga_bridge *bridge; - struct list_head *node; int ret; - list_for_each(node, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); + list_for_each_entry(bridge, bridge_list, node) { ret = fpga_bridge_disable(bridge); if (ret) return ret; @@ -196,13 +192,10 @@ EXPORT_SYMBOL_GPL(fpga_bridges_disable); */ void fpga_bridges_put(struct list_head *bridge_list) { - struct fpga_bridge *bridge; - struct list_head *node, *next; + struct fpga_bridge *bridge, *next; unsigned long flags; - list_for_each_safe(node, next, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); - + list_for_each_entry_safe(bridge, next, bridge_list, node) { fpga_bridge_put(bridge); spin_lock_irqsave(&bridge_list_lock, flags); diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 86d2cb203533..188ffefa3cc3 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -361,7 +361,7 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -struct fpga_manager *__fpga_mgr_get(struct device *dev) +static struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; int ret = -ENODEV; diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 3222fdbad75a..3b6b2f4182a1 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -245,7 +245,8 @@ static int fpga_region_program_fpga(struct fpga_region *region, mgr = fpga_region_get_manager(region); if (IS_ERR(mgr)) { pr_err("failed to get fpga region manager\n"); - return PTR_ERR(mgr); + ret = PTR_ERR(mgr); + goto err_put_region; } ret = fpga_region_get_bridges(region, overlay); @@ -281,6 +282,7 @@ err_put_br: fpga_bridges_put(®ion->bridge_list); err_put_mgr: fpga_mgr_put(mgr); +err_put_region: fpga_region_put(region); return ret; @@ -337,8 +339,9 @@ static int child_regions_with_firmware(struct device_node *overlay) * The overlay must add either firmware-name or external-fpga-config property * to the FPGA Region. * - * firmware-name : program the FPGA - * external-fpga-config : FPGA is already programmed + * firmware-name : program the FPGA + * external-fpga-config : FPGA is already programmed + * encrypted-fpga-config : FPGA bitstream is encrypted * * The overlay can add other FPGA regions, but child FPGA regions cannot have a * firmware-name property since those regions don't exist yet. @@ -373,6 +376,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, if (of_property_read_bool(nd->overlay, "external-fpga-config")) info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + if (of_property_read_bool(nd->overlay, "encrypted-fpga-config")) + info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM; + of_property_read_string(nd->overlay, "firmware-name", &firmware_name); of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", @@ -381,6 +387,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, of_property_read_u32(nd->overlay, "region-freeze-timeout-us", &info->disable_timeout_us); + of_property_read_u32(nd->overlay, "config-complete-timeout-us", + &info->config_complete_timeout_us); + /* If FPGA was externally programmed, don't specify firmware */ if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { pr_err("error: specified firmware and external-fpga-config"); diff --git a/drivers/fpga/ice40-spi.c b/drivers/fpga/ice40-spi.c new file mode 100644 index 000000000000..7fca82023062 --- /dev/null +++ b/drivers/fpga/ice40-spi.c @@ -0,0 +1,207 @@ +/* + * FPGA Manager Driver for Lattice iCE40. + * + * Copyright (c) 2016 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This driver adds support to the FPGA manager for configuring the SRAM of + * Lattice iCE40 FPGAs through slave SPI. + */ + +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/spi/spi.h> +#include <linux/stringify.h> + +#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */ +#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */ + +#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */ +#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */ + +#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8) + +struct ice40_fpga_priv { + struct spi_device *dev; + struct gpio_desc *reset; + struct gpio_desc *cdone; +}; + +static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING : + FPGA_MGR_STATE_UNKNOWN; +} + +static int ice40_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + struct spi_message message; + struct spi_transfer assert_cs_then_reset_delay = { + .cs_change = 1, + .delay_usecs = ICE40_SPI_RESET_DELAY + }; + struct spi_transfer housekeeping_delay_then_release_cs = { + .delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY + }; + int ret; + + if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&dev->dev, + "Partial reconfiguration is not supported\n"); + return -ENOTSUPP; + } + + /* Lock the bus, assert CRESET_B and SS_B and delay >200ns */ + spi_bus_lock(dev->master); + + gpiod_set_value(priv->reset, 1); + + spi_message_init(&message); + spi_message_add_tail(&assert_cs_then_reset_delay, &message); + ret = spi_sync_locked(dev, &message); + + /* Come out of reset */ + gpiod_set_value(priv->reset, 0); + + /* Abort if the chip-select failed */ + if (ret) + goto fail; + + /* Check CDONE is de-asserted i.e. the FPGA is reset */ + if (gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n"); + ret = -EIO; + goto fail; + } + + /* Wait for the housekeeping to complete, and release SS_B */ + spi_message_init(&message); + spi_message_add_tail(&housekeeping_delay_then_release_cs, &message); + ret = spi_sync_locked(dev, &message); + +fail: + spi_bus_unlock(dev->master); + + return ret; +} + +static int ice40_fpga_ops_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return spi_write(priv->dev, buf, count); +} + +static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0}; + + /* Check CDONE is asserted */ + if (!gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, + "CDONE was not asserted after firmware transfer\n"); + return -EIO; + } + + /* Send of zero-padding to activate the firmware */ + return spi_write(dev, padding, sizeof(padding)); +} + +static const struct fpga_manager_ops ice40_fpga_ops = { + .state = ice40_fpga_ops_state, + .write_init = ice40_fpga_ops_write_init, + .write = ice40_fpga_ops_write, + .write_complete = ice40_fpga_ops_write_complete, +}; + +static int ice40_fpga_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ice40_fpga_priv *priv; + int ret; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = spi; + + /* Check board setup data. */ + if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) { + dev_err(dev, "SPI speed is too high, maximum speed is " + __stringify(ICE40_SPI_MAX_SPEED) "\n"); + return -EINVAL; + } + + if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) { + dev_err(dev, "SPI speed is too low, minimum speed is " + __stringify(ICE40_SPI_MIN_SPEED) "\n"); + return -EINVAL; + } + + if (spi->mode & SPI_CPHA) { + dev_err(dev, "Bad SPI mode, CPHA not supported\n"); + return -EINVAL; + } + + /* Set up the GPIOs */ + priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN); + if (IS_ERR(priv->cdone)) { + ret = PTR_ERR(priv->cdone); + dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret); + return ret; + } + + priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret); + return ret; + } + + /* Register with the FPGA manager */ + return fpga_mgr_register(dev, "Lattice iCE40 FPGA Manager", + &ice40_fpga_ops, priv); +} + +static int ice40_fpga_remove(struct spi_device *spi) +{ + fpga_mgr_unregister(&spi->dev); + return 0; +} + +static const struct of_device_id ice40_fpga_of_match[] = { + { .compatible = "lattice,ice40-fpga-mgr", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ice40_fpga_of_match); + +static struct spi_driver ice40_fpga_driver = { + .probe = ice40_fpga_probe, + .remove = ice40_fpga_remove, + .driver = { + .name = "ice40spi", + .of_match_table = of_match_ptr(ice40_fpga_of_match), + }, +}; + +module_spi_driver(ice40_fpga_driver); + +MODULE_AUTHOR("Joel Holdsworth <joel@airwebreathe.org.uk>"); +MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/ts73xx-fpga.c b/drivers/fpga/ts73xx-fpga.c new file mode 100644 index 000000000000..f6a96b42e2ca --- /dev/null +++ b/drivers/fpga/ts73xx-fpga.c @@ -0,0 +1,156 @@ +/* + * Technologic Systems TS-73xx SBC FPGA loader + * + * Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com> + * + * FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on + * TS-7300, heavily based on load_fpga.c in their vendor tree. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/iopoll.h> +#include <linux/fpga/fpga-mgr.h> + +#define TS73XX_FPGA_DATA_REG 0 +#define TS73XX_FPGA_CONFIG_REG 1 + +#define TS73XX_FPGA_WRITE_DONE 0x1 +#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */ +#define TS73XX_FPGA_RESET 0x2 +#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */ +#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */ +#define TS73XX_FPGA_LOAD_OK 0x4 +#define TS73XX_FPGA_CONFIG_LOAD 0x8 + +struct ts73xx_fpga_priv { + void __iomem *io_base; + struct device *dev; +}; + +static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int ts73xx_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + + /* Reset the FPGA */ + writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_LOW_DELAY); + writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_HIGH_DELAY); + + return 0; +} + +static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + size_t i = 0; + int ret; + u8 reg; + + while (count--) { + ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG, + reg, !(reg & TS73XX_FPGA_WRITE_DONE), + 1, TS73XX_FPGA_WRITE_DONE_TIMEOUT); + if (ret < 0) + return ret; + + writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG); + i++; + } + + return 0; +} + +static int ts73xx_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + u8 reg; + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg |= TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg &= ~TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK) + return -ETIMEDOUT; + + return 0; +} + +static const struct fpga_manager_ops ts73xx_fpga_ops = { + .state = ts73xx_fpga_state, + .write_init = ts73xx_fpga_write_init, + .write = ts73xx_fpga_write, + .write_complete = ts73xx_fpga_write_complete, +}; + +static int ts73xx_fpga_probe(struct platform_device *pdev) +{ + struct device *kdev = &pdev->dev; + struct ts73xx_fpga_priv *priv; + struct resource *res; + + priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = kdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(kdev, res); + if (IS_ERR(priv->io_base)) { + dev_err(kdev, "unable to remap registers\n"); + return PTR_ERR(priv->io_base); + } + + return fpga_mgr_register(kdev, "TS-73xx FPGA Manager", + &ts73xx_fpga_ops, priv); +} + +static int ts73xx_fpga_remove(struct platform_device *pdev) +{ + fpga_mgr_unregister(&pdev->dev); + + return 0; +} + +static struct platform_driver ts73xx_fpga_driver = { + .driver = { + .name = "ts73xx-fpga-mgr", + }, + .probe = ts73xx_fpga_probe, + .remove = ts73xx_fpga_remove, +}; +module_platform_driver(ts73xx_fpga_driver); + +MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>"); +MODULE_DESCRIPTION("TS-73xx FPGA Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c new file mode 100644 index 000000000000..e359930bebc8 --- /dev/null +++ b/drivers/fpga/xilinx-pr-decoupler.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017, National Instruments Corp. + * Copyright (c) 2017, Xilix Inc + * + * FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration + * Decoupler IP Core. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/fpga/fpga-bridge.h> + +#define CTRL_CMD_DECOUPLE BIT(0) +#define CTRL_CMD_COUPLE 0 +#define CTRL_OFFSET 0 + +struct xlnx_pr_decoupler_data { + void __iomem *io_base; + struct clk *clk; +}; + +static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d, + u32 offset, u32 val) +{ + writel(val, d->io_base + offset); +} + +static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d, + u32 offset) +{ + return readl(d->io_base + offset); +} + +static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable) +{ + int err; + struct xlnx_pr_decoupler_data *priv = bridge->priv; + + err = clk_enable(priv->clk); + if (err) + return err; + + if (enable) + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE); + else + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE); + + clk_disable(priv->clk); + + return 0; +} + +static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge) +{ + const struct xlnx_pr_decoupler_data *priv = bridge->priv; + u32 status; + int err; + + err = clk_enable(priv->clk); + if (err) + return err; + + status = readl(priv->io_base); + + clk_disable(priv->clk); + + return !status; +} + +static struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = { + .enable_set = xlnx_pr_decoupler_enable_set, + .enable_show = xlnx_pr_decoupler_enable_show, +}; + +static const struct of_device_id xlnx_pr_decoupler_of_match[] = { + { .compatible = "xlnx,pr-decoupler-1.00", }, + { .compatible = "xlnx,pr-decoupler", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match); + +static int xlnx_pr_decoupler_probe(struct platform_device *pdev) +{ + struct xlnx_pr_decoupler_data *priv; + int err; + struct resource *res; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + priv->clk = devm_clk_get(&pdev->dev, "aclk"); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "input clock not found\n"); + return PTR_ERR(priv->clk); + } + + err = clk_prepare_enable(priv->clk); + if (err) { + dev_err(&pdev->dev, "unable to enable clock\n"); + return err; + } + + clk_disable(priv->clk); + + err = fpga_bridge_register(&pdev->dev, "Xilinx PR Decoupler", + &xlnx_pr_decoupler_br_ops, priv); + + if (err) { + dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler"); + clk_unprepare(priv->clk); + return err; + } + + return 0; +} + +static int xlnx_pr_decoupler_remove(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + struct xlnx_pr_decoupler_data *p = bridge->priv; + + fpga_bridge_unregister(&pdev->dev); + + clk_unprepare(p->clk); + + return 0; +} + +static struct platform_driver xlnx_pr_decoupler_driver = { + .probe = xlnx_pr_decoupler_probe, + .remove = xlnx_pr_decoupler_remove, + .driver = { + .name = "xlnx_pr_decoupler", + .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match), + }, +}; + +module_platform_driver(xlnx_pr_decoupler_driver); + +MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler"); +MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>"); +MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c new file mode 100644 index 000000000000..9b62a4c2a3df --- /dev/null +++ b/drivers/fpga/xilinx-spi.c @@ -0,0 +1,198 @@ +/* + * Xilinx Spartan6 Slave Serial SPI Driver + * + * Copyright (C) 2017 DENX Software Engineering + * + * Anatolij Gustschin <agust@denx.de> + * + * 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. + * + * Manage Xilinx FPGA firmware that is loaded over SPI using + * the slave serial configuration interface. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/of.h> +#include <linux/spi/spi.h> +#include <linux/sizes.h> + +struct xilinx_spi_conf { + struct spi_device *spi; + struct gpio_desc *prog_b; + struct gpio_desc *done; +}; + +static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) +{ + struct xilinx_spi_conf *conf = mgr->priv; + + if (!gpiod_get_value(conf->done)) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int xilinx_spi_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const size_t prog_latency_7500us = 7500; + const size_t prog_pulse_1us = 1; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + gpiod_set_value(conf->prog_b, 1); + + udelay(prog_pulse_1us); /* min is 500 ns */ + + gpiod_set_value(conf->prog_b, 0); + + if (gpiod_get_value(conf->done)) { + dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); + return -EIO; + } + + /* program latency */ + usleep_range(prog_latency_7500us, prog_latency_7500us + 100); + return 0; +} + +static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const char *fw_data = buf; + const char *fw_data_end = fw_data + count; + + while (fw_data < fw_data_end) { + size_t remaining, stride; + int ret; + + remaining = fw_data_end - fw_data; + stride = min_t(size_t, remaining, SZ_4K); + + ret = spi_write(conf->spi, fw_data, stride); + if (ret) { + dev_err(&mgr->dev, "SPI error in firmware write: %d\n", + ret); + return ret; + } + fw_data += stride; + } + + return 0; +} + +static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf) +{ + struct spi_device *spi = conf->spi; + const u8 din_data[1] = { 0xff }; + int ret; + + ret = spi_write(conf->spi, din_data, sizeof(din_data)); + if (ret) + dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret); + + return ret; +} + +static int xilinx_spi_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct xilinx_spi_conf *conf = mgr->priv; + unsigned long timeout; + int ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + + timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + + while (time_before(jiffies, timeout)) { + + ret = xilinx_spi_apply_cclk_cycles(conf); + if (ret) + return ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + } + + dev_err(&mgr->dev, "Timeout after config data transfer.\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops xilinx_spi_ops = { + .state = xilinx_spi_state, + .write_init = xilinx_spi_write_init, + .write = xilinx_spi_write, + .write_complete = xilinx_spi_write_complete, +}; + +static int xilinx_spi_probe(struct spi_device *spi) +{ + struct xilinx_spi_conf *conf; + + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->spi = spi; + + /* PROGRAM_B is active low */ + conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW); + if (IS_ERR(conf->prog_b)) { + dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n", + PTR_ERR(conf->prog_b)); + return PTR_ERR(conf->prog_b); + } + + conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN); + if (IS_ERR(conf->done)) { + dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n", + PTR_ERR(conf->done)); + return PTR_ERR(conf->done); + } + + return fpga_mgr_register(&spi->dev, "Xilinx Slave Serial FPGA Manager", + &xilinx_spi_ops, conf); +} + +static int xilinx_spi_remove(struct spi_device *spi) +{ + fpga_mgr_unregister(&spi->dev); + + return 0; +} + +static const struct of_device_id xlnx_spi_of_match[] = { + { .compatible = "xlnx,fpga-slave-serial", }, + {} +}; +MODULE_DEVICE_TABLE(of, xlnx_spi_of_match); + +static struct spi_driver xilinx_slave_spi_driver = { + .driver = { + .name = "xlnx-slave-spi", + .of_match_table = of_match_ptr(xlnx_spi_of_match), + }, + .probe = xilinx_spi_probe, + .remove = xilinx_spi_remove, +}; + +module_spi_driver(xilinx_slave_spi_driver) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); +MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI"); diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index 34cb98139442..70b15b303471 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -72,6 +72,10 @@ #define CTRL_PCAP_PR_MASK BIT(27) /* Enable PCAP */ #define CTRL_PCAP_MODE_MASK BIT(26) +/* Lower rate to allow decrypt on the fly */ +#define CTRL_PCAP_RATE_EN_MASK BIT(25) +/* System booted in secure mode */ +#define CTRL_SEC_EN_MASK BIT(7) /* Miscellaneous Control Register bit definitions */ /* Internal PCAP loopback */ @@ -266,6 +270,17 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, if (err) return err; + /* check if bitstream is encrypted & and system's still secure */ + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) { + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + if (!(ctrl & CTRL_SEC_EN_MASK)) { + dev_err(&mgr->dev, + "System not secure, can't use crypted bitstreams\n"); + err = -EINVAL; + goto out_err; + } + } + /* don't globally reset PL if we're doing partial reconfig */ if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { if (!zynq_fpga_has_sync(buf, count)) { @@ -337,12 +352,19 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, /* set configuration register with following options: * - enable PCAP interface - * - set throughput for maximum speed + * - set throughput for maximum speed (if bistream not crypted) * - set CPU in user mode */ ctrl = zynq_fpga_read(priv, CTRL_OFFSET); - zynq_fpga_write(priv, CTRL_OFFSET, - (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl)); + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | CTRL_PCAP_RATE_EN_MASK | ctrl)); + else + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | ctrl)); + /* We expect that the command queue is empty right now. */ status = zynq_fpga_read(priv, STATUS_OFFSET); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 1d1fa7248d63..5db44139cef8 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1035,18 +1035,14 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) cdev_init(&gdev->chrdev, &gpio_fileops); gdev->chrdev.owner = THIS_MODULE; - gdev->chrdev.kobj.parent = &gdev->dev.kobj; gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id); - status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1); - if (status < 0) - chip_warn(gdev->chip, "failed to add char device %d:%d\n", - MAJOR(gpio_devt), gdev->id); - else - chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n", - MAJOR(gpio_devt), gdev->id); - status = device_add(&gdev->dev); + + status = cdev_device_add(&gdev->chrdev, &gdev->dev); if (status) - goto err_remove_chardev; + return status; + + chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n", + MAJOR(gpio_devt), gdev->id); status = gpiochip_sysfs_register(gdev); if (status) @@ -1061,9 +1057,7 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) return 0; err_remove_device: - device_del(&gdev->dev); -err_remove_chardev: - cdev_del(&gdev->chrdev); + cdev_device_del(&gdev->chrdev, &gdev->dev); return status; } @@ -1347,8 +1341,7 @@ void gpiochip_remove(struct gpio_chip *chip) * be removed, else it will be dangling until the last user is * gone. */ - cdev_del(&gdev->chrdev); - device_del(&gdev->dev); + cdev_device_del(&gdev->chrdev, &gdev->dev); put_device(&gdev->dev); } EXPORT_SYMBOL_GPL(gpiochip_remove); diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 321b8833fa6f..736ac76d2a6a 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -333,7 +333,7 @@ static int create_gpadl_header(void *kbuffer, u32 size, * Gpadl is u32 and we are using a pointer which could * be 64-bit * This is governed by the guest/host protocol and - * so the hypervisor gurantees that this is ok. + * so the hypervisor guarantees that this is ok. */ for (i = 0; i < pfncurr; i++) gpadl_body->pfn[i] = slow_virt_to_phys( @@ -380,7 +380,7 @@ nomem: } /* - * vmbus_establish_gpadl - Estabish a GPADL for the specified buffer + * vmbus_establish_gpadl - Establish a GPADL for the specified buffer * * @channel: a channel * @kbuffer: from kmalloc or vmalloc @@ -731,7 +731,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, /* Setup the descriptor */ desc.type = VM_PKT_DATA_USING_GPA_DIRECT; desc.flags = flags; - desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */ + desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */ desc.length8 = (u16)(packetlen_aligned >> 3); desc.transactionid = requestid; desc.rangecount = pagecount; @@ -792,7 +792,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, /* Setup the descriptor */ desc->type = VM_PKT_DATA_USING_GPA_DIRECT; desc->flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; - desc->dataoffset8 = desc_size >> 3; /* in 8-bytes grandularity */ + desc->dataoffset8 = desc_size >> 3; /* in 8-bytes granularity */ desc->length8 = (u16)(packetlen_aligned >> 3); desc->transactionid = requestid; desc->rangecount = 1; @@ -842,7 +842,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, /* Setup the descriptor */ desc.type = VM_PKT_DATA_USING_GPA_DIRECT; desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; - desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */ + desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */ desc.length8 = (u16)(packetlen_aligned >> 3); desc.transactionid = requestid; desc.rangecount = 1; diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index fbcb06352308..735f9363f2e4 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -1080,30 +1080,30 @@ static void vmbus_onversion_response( } /* Channel message dispatch table */ -struct vmbus_channel_message_table_entry - channel_message_table[CHANNELMSG_COUNT] = { - {CHANNELMSG_INVALID, 0, NULL}, - {CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer}, - {CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind}, - {CHANNELMSG_REQUESTOFFERS, 0, NULL}, - {CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered}, - {CHANNELMSG_OPENCHANNEL, 0, NULL}, - {CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result}, - {CHANNELMSG_CLOSECHANNEL, 0, NULL}, - {CHANNELMSG_GPADL_HEADER, 0, NULL}, - {CHANNELMSG_GPADL_BODY, 0, NULL}, - {CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created}, - {CHANNELMSG_GPADL_TEARDOWN, 0, NULL}, - {CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown}, - {CHANNELMSG_RELID_RELEASED, 0, NULL}, - {CHANNELMSG_INITIATE_CONTACT, 0, NULL}, - {CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response}, - {CHANNELMSG_UNLOAD, 0, NULL}, - {CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response}, - {CHANNELMSG_18, 0, NULL}, - {CHANNELMSG_19, 0, NULL}, - {CHANNELMSG_20, 0, NULL}, - {CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL}, +const struct vmbus_channel_message_table_entry +channel_message_table[CHANNELMSG_COUNT] = { + { CHANNELMSG_INVALID, 0, NULL }, + { CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer }, + { CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind }, + { CHANNELMSG_REQUESTOFFERS, 0, NULL }, + { CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered }, + { CHANNELMSG_OPENCHANNEL, 0, NULL }, + { CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result }, + { CHANNELMSG_CLOSECHANNEL, 0, NULL }, + { CHANNELMSG_GPADL_HEADER, 0, NULL }, + { CHANNELMSG_GPADL_BODY, 0, NULL }, + { CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created }, + { CHANNELMSG_GPADL_TEARDOWN, 0, NULL }, + { CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown }, + { CHANNELMSG_RELID_RELEASED, 0, NULL }, + { CHANNELMSG_INITIATE_CONTACT, 0, NULL }, + { CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response }, + { CHANNELMSG_UNLOAD, 0, NULL }, + { CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response }, + { CHANNELMSG_18, 0, NULL }, + { CHANNELMSG_19, 0, NULL }, + { CHANNELMSG_20, 0, NULL }, + { CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL }, }; /* diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index a8366fec1458..fce27fb141cc 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -296,44 +296,47 @@ struct vmbus_channel *relid2channel(u32 relid) /* * vmbus_on_event - Process a channel event notification + * + * For batched channels (default) optimize host to guest signaling + * by ensuring: + * 1. While reading the channel, we disable interrupts from host. + * 2. Ensure that we process all posted messages from the host + * before returning from this callback. + * 3. Once we return, enable signaling from the host. Once this + * state is set we check to see if additional packets are + * available to read. In this case we repeat the process. + * If this tasklet has been running for a long time + * then reschedule ourselves. */ void vmbus_on_event(unsigned long data) { struct vmbus_channel *channel = (void *) data; - void (*callback_fn)(void *); + unsigned long time_limit = jiffies + 2; - /* - * A channel once created is persistent even when there - * is no driver handling the device. An unloading driver - * sets the onchannel_callback to NULL on the same CPU - * as where this interrupt is handled (in an interrupt context). - * Thus, checking and invoking the driver specific callback takes - * care of orderly unloading of the driver. - */ - callback_fn = READ_ONCE(channel->onchannel_callback); - if (unlikely(callback_fn == NULL)) - return; - - (*callback_fn)(channel->channel_callback_context); - - if (channel->callback_mode == HV_CALL_BATCHED) { - /* - * This callback reads the messages sent by the host. - * We can optimize host to guest signaling by ensuring: - * 1. While reading the channel, we disable interrupts from - * host. - * 2. Ensure that we process all posted messages from the host - * before returning from this callback. - * 3. Once we return, enable signaling from the host. Once this - * state is set we check to see if additional packets are - * available to read. In this case we repeat the process. + do { + void (*callback_fn)(void *); + + /* A channel once created is persistent even when + * there is no driver handling the device. An + * unloading driver sets the onchannel_callback to NULL. */ - if (hv_end_read(&channel->inbound) != 0) { - hv_begin_read(&channel->inbound); + callback_fn = READ_ONCE(channel->onchannel_callback); + if (unlikely(callback_fn == NULL)) + return; - tasklet_schedule(&channel->callback_event); - } - } + (*callback_fn)(channel->channel_callback_context); + + if (channel->callback_mode != HV_CALL_BATCHED) + return; + + if (likely(hv_end_read(&channel->inbound) == 0)) + return; + + hv_begin_read(&channel->inbound); + } while (likely(time_before(jiffies, time_limit))); + + /* The time limit (2 jiffies) has been reached */ + tasklet_schedule(&channel->callback_event); } /* diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 665a64f1611e..12e7baecb84e 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -254,7 +254,10 @@ int hv_synic_init(unsigned int cpu) shared_sint.as_uint64 = 0; shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR; shared_sint.masked = false; - shared_sint.auto_eoi = true; + if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) + shared_sint.auto_eoi = false; + else + shared_sint.auto_eoi = true; hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64); diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 5fd03e59cee5..f5728deff893 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -722,8 +722,6 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, 5*HZ); post_status(&dm_device); } - - return; } static void hv_online_page(struct page *pg) diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c index a5596a642ed0..daa75bd41f86 100644 --- a/drivers/hv/hv_fcopy.c +++ b/drivers/hv/hv_fcopy.c @@ -186,8 +186,6 @@ static void fcopy_send_data(struct work_struct *dummy) } } kfree(smsg_out); - - return; } /* diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index a1adfe2cfb34..e99ff2ddad40 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -69,7 +69,7 @@ static const int fw_versions[] = { * * While the request/response protocol is guaranteed by the host, we further * ensure this by serializing packet processing in this driver - we do not - * read additional packets from the VMBUs until the current packet is fully + * read additional packets from the VMBUS until the current packet is fully * handled. */ @@ -397,7 +397,7 @@ kvp_send_key(struct work_struct *dummy) * the max lengths specified. We will however, reserve room * for the string terminating character - in the utf16s_utf8s() * function we limit the size of the buffer where the converted - * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee + * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to guarantee * that the strings can be properly terminated! */ @@ -483,8 +483,6 @@ kvp_send_key(struct work_struct *dummy) } kfree(message); - - return; } /* @@ -533,7 +531,7 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error) */ if (error) { /* - * Something failed or we have timedout; + * Something failed or we have timed out; * terminate the current host-side iteration. */ goto response_done; @@ -607,8 +605,8 @@ response_done: * This callback is invoked when we get a KVP message from the host. * The host ensures that only one KVP transaction can be active at a time. * KVP implementation in Linux needs to forward the key to a user-mde - * component to retrive the corresponding value. Consequently, we cannot - * respond to the host in the conext of this callback. Since the host + * component to retrieve the corresponding value. Consequently, we cannot + * respond to the host in the context of this callback. Since the host * guarantees that at most only one transaction can be active at a time, * we stash away the transaction state in a set of global variables. */ diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c index e659d1b94a57..6831efd73394 100644 --- a/drivers/hv/hv_snapshot.c +++ b/drivers/hv/hv_snapshot.c @@ -212,8 +212,6 @@ static void vss_send_op(void) } kfree(vss_msg); - - return; } static void vss_handle_request(struct work_struct *dummy) diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 884f83bba1ab..6113e915c50e 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -218,8 +218,8 @@ struct hv_per_cpu_context { struct hv_context { /* We only support running on top of Hyper-V - * So at this point this really can only contain the Hyper-V ID - */ + * So at this point this really can only contain the Hyper-V ID + */ u64 guestid; void *tsc_page; @@ -248,14 +248,6 @@ struct hv_context { extern struct hv_context hv_context; -struct hv_ring_buffer_debug_info { - u32 current_interrupt_mask; - u32 current_read_index; - u32 current_write_index; - u32 bytes_avail_toread; - u32 bytes_avail_towrite; -}; - /* Hv Interface */ extern int hv_init(void); @@ -289,9 +281,6 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, u64 *requestid, bool raw); -void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info, - struct hv_ring_buffer_debug_info *debug_info); - /* * Maximum channels is determined by the size of the interrupt page * which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt @@ -376,7 +365,7 @@ struct vmbus_channel_message_table_entry { void (*message_handler)(struct vmbus_channel_message_header *msg); }; -extern struct vmbus_channel_message_table_entry +extern const struct vmbus_channel_message_table_entry channel_message_table[CHANNELMSG_COUNT]; @@ -403,17 +392,17 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep); void vmbus_on_event(unsigned long data); void vmbus_on_msg_dpc(unsigned long data); -int hv_kvp_init(struct hv_util_service *); +int hv_kvp_init(struct hv_util_service *srv); void hv_kvp_deinit(void); -void hv_kvp_onchannelcallback(void *); +void hv_kvp_onchannelcallback(void *context); -int hv_vss_init(struct hv_util_service *); +int hv_vss_init(struct hv_util_service *srv); void hv_vss_deinit(void); -void hv_vss_onchannelcallback(void *); +void hv_vss_onchannelcallback(void *context); -int hv_fcopy_init(struct hv_util_service *); +int hv_fcopy_init(struct hv_util_service *srv); void hv_fcopy_deinit(void); -void hv_fcopy_onchannelcallback(void *); +void hv_fcopy_onchannelcallback(void *context); void vmbus_initiate_unload(bool crash); static inline void hv_poll_channel(struct vmbus_channel *channel, diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index c3f1a9e33cef..1f450c39a9b0 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -75,8 +75,6 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel) */ if (old_write == READ_ONCE(rbi->ring_buffer->read_index)) vmbus_setevent(channel); - - return; } /* Get the next write location for the specified ring buffer. */ @@ -210,6 +208,7 @@ void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info, ring_info->ring_buffer->interrupt_mask; } } +EXPORT_SYMBOL_GPL(hv_ringbuffer_get_debuginfo); /* Initialize the ring buffer. */ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info, @@ -269,14 +268,13 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info) int hv_ringbuffer_write(struct vmbus_channel *channel, const struct kvec *kv_list, u32 kv_count) { - int i = 0; + int i; u32 bytes_avail_towrite; - u32 totalbytes_towrite = 0; - + u32 totalbytes_towrite = sizeof(u64); u32 next_write_location; u32 old_write; - u64 prev_indices = 0; - unsigned long flags = 0; + u64 prev_indices; + unsigned long flags; struct hv_ring_buffer_info *outring_info = &channel->outbound; if (channel->rescind) @@ -285,8 +283,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, for (i = 0; i < kv_count; i++) totalbytes_towrite += kv_list[i].iov_len; - totalbytes_towrite += sizeof(u64); - spin_lock_irqsave(&outring_info->ring_lock, flags); bytes_avail_towrite = hv_get_bytes_to_write(outring_info); @@ -349,18 +345,16 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, u64 *requestid, bool raw) { u32 bytes_avail_toread; - u32 next_read_location = 0; + u32 next_read_location; u64 prev_indices = 0; struct vmpacket_descriptor desc; u32 offset; u32 packetlen; - int ret = 0; struct hv_ring_buffer_info *inring_info = &channel->inbound; if (buflen <= 0) return -EINVAL; - *buffer_actual_len = 0; *requestid = 0; @@ -371,7 +365,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, * No error is set when there is even no header, drivers are * supposed to analyze buffer_actual_len. */ - return ret; + return 0; } init_cached_read_index(inring_info); @@ -417,7 +411,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, hv_signal_on_read(channel); - return ret; + return 0; } /* diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 8370b9dc6037..0087b49095eb 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -787,8 +787,6 @@ static void vmbus_shutdown(struct device *child_device) if (drv->shutdown) drv->shutdown(dev); - - return; } @@ -855,7 +853,7 @@ void vmbus_on_msg_dpc(unsigned long data) struct hv_message *msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; struct vmbus_channel_message_header *hdr; - struct vmbus_channel_message_table_entry *entry; + const struct vmbus_channel_message_table_entry *entry; struct onmessage_work_context *ctx; u32 message_type = msg->header.message_type; diff --git a/drivers/hwtracing/coresight/of_coresight.c b/drivers/hwtracing/coresight/of_coresight.c index 629e031b7456..09142e99e915 100644 --- a/drivers/hwtracing/coresight/of_coresight.c +++ b/drivers/hwtracing/coresight/of_coresight.c @@ -149,7 +149,7 @@ struct coresight_platform_data *of_get_coresight_platform_data( continue; /* The local out port number */ - pdata->outports[i] = endpoint.id; + pdata->outports[i] = endpoint.port; /* * Get a handle on the remote port and parent diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 3ff91e02fee3..57c14da5708f 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1719,18 +1719,13 @@ int iio_device_register(struct iio_dev *indio_dev) cdev_init(&indio_dev->chrdev, &iio_buffer_fileops); indio_dev->chrdev.owner = indio_dev->info->driver_module; - indio_dev->chrdev.kobj.parent = &indio_dev->dev.kobj; - ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1); - if (ret < 0) - goto error_unreg_eventset; - ret = device_add(&indio_dev->dev); + ret = cdev_device_add(&indio_dev->chrdev, &indio_dev->dev); if (ret < 0) - goto error_cdev_del; + goto error_unreg_eventset; return 0; -error_cdev_del: - cdev_del(&indio_dev->chrdev); + error_unreg_eventset: iio_device_unregister_eventset(indio_dev); error_free_sysfs: @@ -1751,10 +1746,8 @@ void iio_device_unregister(struct iio_dev *indio_dev) { mutex_lock(&indio_dev->info_exist_lock); - device_del(&indio_dev->dev); + cdev_device_del(&indio_dev->chrdev, &indio_dev->dev); - if (indio_dev->chrdev.dev) - cdev_del(&indio_dev->chrdev); iio_device_unregister_debugfs(indio_dev); iio_disable_all_buffers(indio_dev); diff --git a/drivers/infiniband/core/ucm.c b/drivers/infiniband/core/ucm.c index 80d0fca05c06..112099c86a19 100644 --- a/drivers/infiniband/core/ucm.c +++ b/drivers/infiniband/core/ucm.c @@ -1205,12 +1205,15 @@ static void ib_ucm_release_dev(struct device *dev) struct ib_ucm_device *ucm_dev; ucm_dev = container_of(dev, struct ib_ucm_device, dev); - cdev_del(&ucm_dev->cdev); + kfree(ucm_dev); +} + +static void ib_ucm_free_dev(struct ib_ucm_device *ucm_dev) +{ if (ucm_dev->devnum < IB_UCM_MAX_DEVICES) clear_bit(ucm_dev->devnum, dev_map); else clear_bit(ucm_dev->devnum - IB_UCM_MAX_DEVICES, overflow_map); - kfree(ucm_dev); } static const struct file_operations ucm_fops = { @@ -1266,7 +1269,9 @@ static void ib_ucm_add_one(struct ib_device *device) if (!ucm_dev) return; + device_initialize(&ucm_dev->dev); ucm_dev->ib_dev = device; + ucm_dev->dev.release = ib_ucm_release_dev; devnum = find_first_zero_bit(dev_map, IB_UCM_MAX_DEVICES); if (devnum >= IB_UCM_MAX_DEVICES) { @@ -1286,16 +1291,14 @@ static void ib_ucm_add_one(struct ib_device *device) cdev_init(&ucm_dev->cdev, &ucm_fops); ucm_dev->cdev.owner = THIS_MODULE; kobject_set_name(&ucm_dev->cdev.kobj, "ucm%d", ucm_dev->devnum); - if (cdev_add(&ucm_dev->cdev, base, 1)) - goto err; ucm_dev->dev.class = &cm_class; ucm_dev->dev.parent = device->dev.parent; - ucm_dev->dev.devt = ucm_dev->cdev.dev; - ucm_dev->dev.release = ib_ucm_release_dev; + ucm_dev->dev.devt = base; + dev_set_name(&ucm_dev->dev, "ucm%d", ucm_dev->devnum); - if (device_register(&ucm_dev->dev)) - goto err_cdev; + if (cdev_device_add(&ucm_dev->cdev, &ucm_dev->dev)) + goto err_devnum; if (device_create_file(&ucm_dev->dev, &dev_attr_ibdev)) goto err_dev; @@ -1304,15 +1307,11 @@ static void ib_ucm_add_one(struct ib_device *device) return; err_dev: - device_unregister(&ucm_dev->dev); -err_cdev: - cdev_del(&ucm_dev->cdev); - if (ucm_dev->devnum < IB_UCM_MAX_DEVICES) - clear_bit(devnum, dev_map); - else - clear_bit(devnum, overflow_map); + cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev); +err_devnum: + ib_ucm_free_dev(ucm_dev); err: - kfree(ucm_dev); + put_device(&ucm_dev->dev); return; } @@ -1323,7 +1322,9 @@ static void ib_ucm_remove_one(struct ib_device *device, void *client_data) if (!ucm_dev) return; - device_unregister(&ucm_dev->dev); + cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev); + ib_ucm_free_dev(ucm_dev); + put_device(&ucm_dev->dev); } static CLASS_ATTR_STRING(abi_version, S_IRUGO, diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c index 200422d24299..36a6f5c8914c 100644 --- a/drivers/infiniband/core/user_mad.c +++ b/drivers/infiniband/core/user_mad.c @@ -1187,7 +1187,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num, cdev_init(&port->cdev, &umad_fops); port->cdev.owner = THIS_MODULE; - port->cdev.kobj.parent = &umad_dev->kobj; + cdev_set_parent(&port->cdev, &umad_dev->kobj); kobject_set_name(&port->cdev.kobj, "umad%d", port->dev_num); if (cdev_add(&port->cdev, base, 1)) goto err_cdev; @@ -1206,7 +1206,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num, base += IB_UMAD_MAX_PORTS; cdev_init(&port->sm_cdev, &umad_sm_fops); port->sm_cdev.owner = THIS_MODULE; - port->sm_cdev.kobj.parent = &umad_dev->kobj; + cdev_set_parent(&port->sm_cdev, &umad_dev->kobj); kobject_set_name(&port->sm_cdev.kobj, "issm%d", port->dev_num); if (cdev_add(&port->sm_cdev, base, 1)) goto err_sm_cdev; diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index 3a9883d1257e..3d2609608f58 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -1093,7 +1093,7 @@ static void ib_uverbs_add_one(struct ib_device *device) cdev_init(&uverbs_dev->cdev, NULL); uverbs_dev->cdev.owner = THIS_MODULE; uverbs_dev->cdev.ops = device->mmap ? &uverbs_mmap_fops : &uverbs_fops; - uverbs_dev->cdev.kobj.parent = &uverbs_dev->kobj; + cdev_set_parent(&uverbs_dev->cdev, &uverbs_dev->kobj); kobject_set_name(&uverbs_dev->cdev.kobj, "uverbs%d", uverbs_dev->devnum); if (cdev_add(&uverbs_dev->cdev, base, 1)) goto err_cdev; diff --git a/drivers/infiniband/hw/hfi1/device.c b/drivers/infiniband/hw/hfi1/device.c index bf64b5a7bfd7..bbb6069dec2a 100644 --- a/drivers/infiniband/hw/hfi1/device.c +++ b/drivers/infiniband/hw/hfi1/device.c @@ -69,7 +69,7 @@ int hfi1_cdev_init(int minor, const char *name, cdev_init(cdev, fops); cdev->owner = THIS_MODULE; - cdev->kobj.parent = parent; + cdev_set_parent(cdev, parent); kobject_set_name(&cdev->kobj, name); ret = cdev_add(cdev, dev, 1); diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index e9ae3d500a55..925571475005 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -1354,8 +1354,6 @@ static void evdev_cleanup(struct evdev *evdev) evdev_mark_dead(evdev); evdev_hangup(evdev); - cdev_del(&evdev->cdev); - /* evdev is marked dead so no one else accesses evdev->open */ if (evdev->open) { input_flush_device(handle, NULL); @@ -1416,12 +1414,8 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, goto err_free_evdev; cdev_init(&evdev->cdev, &evdev_fops); - evdev->cdev.kobj.parent = &evdev->dev.kobj; - error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&evdev->dev); + error = cdev_device_add(&evdev->cdev, &evdev->dev); if (error) goto err_cleanup_evdev; @@ -1429,7 +1423,6 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, err_cleanup_evdev: evdev_cleanup(evdev); - err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); @@ -1442,7 +1435,7 @@ static void evdev_disconnect(struct input_handle *handle) { struct evdev *evdev = handle->private; - device_del(&evdev->dev); + cdev_device_del(&evdev->cdev, &evdev->dev); evdev_cleanup(evdev); input_free_minor(MINOR(evdev->dev.devt)); input_unregister_handle(handle); diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index 065e67bf56dd..29d677c714d2 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -742,8 +742,6 @@ static void joydev_cleanup(struct joydev *joydev) joydev_mark_dead(joydev); joydev_hangup(joydev); - cdev_del(&joydev->cdev); - /* joydev is marked dead so no one else accesses joydev->open */ if (joydev->open) input_close_device(handle); @@ -913,12 +911,8 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, goto err_free_joydev; cdev_init(&joydev->cdev, &joydev_fops); - joydev->cdev.kobj.parent = &joydev->dev.kobj; - error = cdev_add(&joydev->cdev, joydev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&joydev->dev); + error = cdev_device_add(&joydev->cdev, &joydev->dev); if (error) goto err_cleanup_joydev; @@ -926,7 +920,6 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, err_cleanup_joydev: joydev_cleanup(joydev); - err_unregister_handle: input_unregister_handle(&joydev->handle); err_free_joydev: put_device(&joydev->dev); @@ -939,7 +932,7 @@ static void joydev_disconnect(struct input_handle *handle) { struct joydev *joydev = handle->private; - device_del(&joydev->dev); + cdev_device_del(&joydev->cdev, &joydev->dev); joydev_cleanup(joydev); input_free_minor(MINOR(joydev->dev.devt)); input_unregister_handle(handle); diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index b604564dec5c..0e0ff84088fd 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -812,8 +812,6 @@ static void mousedev_cleanup(struct mousedev *mousedev) mousedev_mark_dead(mousedev); mousedev_hangup(mousedev); - cdev_del(&mousedev->cdev); - /* mousedev is marked dead so no one else accesses mousedev->open */ if (mousedev->open) input_close_device(handle); @@ -901,12 +899,8 @@ static struct mousedev *mousedev_create(struct input_dev *dev, } cdev_init(&mousedev->cdev, &mousedev_fops); - mousedev->cdev.kobj.parent = &mousedev->dev.kobj; - error = cdev_add(&mousedev->cdev, mousedev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&mousedev->dev); + error = cdev_device_add(&mousedev->cdev, &mousedev->dev); if (error) goto err_cleanup_mousedev; @@ -914,7 +908,6 @@ static struct mousedev *mousedev_create(struct input_dev *dev, err_cleanup_mousedev: mousedev_cleanup(mousedev); - err_unregister_handle: if (!mixdev) input_unregister_handle(&mousedev->handle); err_free_mousedev: @@ -927,7 +920,7 @@ static struct mousedev *mousedev_create(struct input_dev *dev, static void mousedev_destroy(struct mousedev *mousedev) { - device_del(&mousedev->dev); + cdev_device_del(&mousedev->cdev, &mousedev->dev); mousedev_cleanup(mousedev); input_free_minor(MINOR(mousedev->dev.devt)); if (mousedev != mousedev_mix) diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c index 37217e205040..3163e038a364 100644 --- a/drivers/media/cec/cec-core.c +++ b/drivers/media/cec/cec-core.c @@ -137,24 +137,17 @@ static int __must_check cec_devnode_register(struct cec_devnode *devnode, /* Part 2: Initialize and register the character device */ cdev_init(&devnode->cdev, &cec_devnode_fops); - devnode->cdev.kobj.parent = &devnode->dev.kobj; devnode->cdev.owner = owner; - ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1); - if (ret < 0) { - pr_err("%s: cdev_add failed\n", __func__); + ret = cdev_device_add(&devnode->cdev, &devnode->dev); + if (ret) { + pr_err("%s: cdev_device_add failed\n", __func__); goto clr_bit; } - ret = device_add(&devnode->dev); - if (ret) - goto cdev_del; - devnode->registered = true; return 0; -cdev_del: - cdev_del(&devnode->cdev); clr_bit: mutex_lock(&cec_devnode_lock); clear_bit(devnode->minor, cec_devnode_nums); @@ -190,8 +183,7 @@ static void cec_devnode_unregister(struct cec_devnode *devnode) devnode->unregistered = true; mutex_unlock(&devnode->lock); - device_del(&devnode->dev); - cdev_del(&devnode->cdev); + cdev_device_del(&devnode->cdev, &devnode->dev); put_device(&devnode->dev); } diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index ae46753c90cb..423248f577b6 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c @@ -248,31 +248,22 @@ int __must_check media_devnode_register(struct media_device *mdev, dev_set_name(&devnode->dev, "media%d", devnode->minor); device_initialize(&devnode->dev); - /* Part 2: Initialize and register the character device */ + /* Part 2: Initialize the character device */ cdev_init(&devnode->cdev, &media_devnode_fops); devnode->cdev.owner = owner; - devnode->cdev.kobj.parent = &devnode->dev.kobj; - ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1); + /* Part 3: Add the media and char device */ + ret = cdev_device_add(&devnode->cdev, &devnode->dev); if (ret < 0) { - pr_err("%s: cdev_add failed\n", __func__); + pr_err("%s: cdev_device_add failed\n", __func__); goto cdev_add_error; } - /* Part 3: Add the media device */ - ret = device_add(&devnode->dev); - if (ret < 0) { - pr_err("%s: device_add failed\n", __func__); - goto device_add_error; - } - /* Part 4: Activate this minor. The char device can now be used. */ set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); return 0; -device_add_error: - cdev_del(&devnode->cdev); cdev_add_error: mutex_lock(&media_devnode_lock); clear_bit(devnode->minor, media_devnode_nums); @@ -298,9 +289,8 @@ void media_devnode_unregister(struct media_devnode *devnode) { mutex_lock(&media_devnode_lock); /* Delete the cdev on this minor as well */ - cdev_del(&devnode->cdev); + cdev_device_del(&devnode->cdev, &devnode->dev); mutex_unlock(&media_devnode_lock); - device_del(&devnode->dev); devnode->media_dev = NULL; put_device(&devnode->dev); } diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c290990d73ed..39d1acb27452 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -419,16 +419,6 @@ config VMWARE_BALLOON To compile this driver as a module, choose M here: the module will be called vmw_balloon. -config ARM_CHARLCD - bool "ARM Ltd. Character LCD Driver" - depends on PLAT_VERSATILE - help - This is a driver for the character LCD found on the ARM Ltd. - Versatile and RealView Platform Baseboards. It doesn't do - very much more than display the text "ARM Linux" on the first - line and the Linux version on the second line, but that's - still useful. - config PCH_PHUB tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" select GENERIC_NET_UTILS @@ -492,284 +482,13 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. -config PANEL - tristate "Parallel port LCD/Keypad Panel support" - depends on PARPORT - ---help--- - Say Y here if you have an HD44780 or KS-0074 LCD connected to your - parallel port. This driver also features 4 and 6-key keypads. The LCD - is accessible through the /dev/lcd char device (10, 156), and the - keypad through /dev/keypad (10, 185). This code can either be - compiled as a module, or linked into the kernel and started at boot. - If you don't understand what all this is about, say N. - -if PANEL - -config PANEL_PARPORT - int "Default parallel port number (0=LPT1)" - range 0 255 - default "0" - ---help--- - This is the index of the parallel port the panel is connected to. One - driver instance only supports one parallel port, so if your keypad - and LCD are connected to two separate ports, you have to start two - modules with different arguments. Numbering starts with '0' for LPT1, - and so on. - -config PANEL_PROFILE - int "Default panel profile (0-5, 0=custom)" - range 0 5 - default "5" - ---help--- - To ease configuration, the driver supports different configuration - profiles for past and recent wirings. These profiles can also be - used to define an approximative configuration, completed by a few - other options. Here are the profiles : - - 0 = custom (see further) - 1 = 2x16 parallel LCD, old keypad - 2 = 2x16 serial LCD (KS-0074), new keypad - 3 = 2x16 parallel LCD (Hantronix), no keypad - 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad - 5 = 2x40 parallel LCD (old one), with old keypad - - Custom configurations allow you to define how your display is - wired to the parallel port, and how it works. This is only intended - for experts. - -config PANEL_KEYPAD - depends on PANEL_PROFILE="0" - int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" - range 0 3 - default 0 - ---help--- - This enables and configures a keypad connected to the parallel port. - The keys will be read from character device 10,185. Valid values are : - - 0 : do not enable this driver - 1 : old 6 keys keypad - 2 : new 6 keys keypad, as used on the server at www.ant-computing.com - 3 : Nexcom NSA1045's 4 keys keypad - - New profiles can be described in the driver source. The driver also - supports simultaneous keys pressed when the keypad supports them. - -config PANEL_LCD - depends on PANEL_PROFILE="0" - int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" - range 0 5 - default 0 - ---help--- - This enables and configures an LCD connected to the parallel port. - The driver includes an interpreter for escape codes starting with - '\e[L' which are specific to the LCD, and a few ANSI codes. The - driver will be registered as character device 10,156, usually - under the name '/dev/lcd'. There are a total of 6 supported types : - - 0 : do not enable the driver - 1 : custom configuration and wiring (see further) - 2 : 2x16 & 2x40 parallel LCD (old wiring) - 3 : 2x16 serial LCD (KS-0074 based) - 4 : 2x16 parallel LCD (Hantronix wiring) - 5 : 2x16 parallel LCD (Nexcom wiring) - - When type '1' is specified, other options will appear to configure - more precise aspects (wiring, dimensions, protocol, ...). Please note - that those values changed from the 2.4 driver for better consistency. - -config PANEL_LCD_HEIGHT - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Number of lines on the LCD (1-2)" - range 1 2 - default 2 - ---help--- - This is the number of visible character lines on the LCD in custom profile. - It can either be 1 or 2. - -config PANEL_LCD_WIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Number of characters per line on the LCD (1-40)" - range 1 40 - default 40 - ---help--- - This is the number of characters per line on the LCD in custom profile. - Common values are 16,20,24,40. - -config PANEL_LCD_BWIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Internal LCD line width (1-40, 40 by default)" - range 1 40 - default 40 - ---help--- - Most LCDs use a standard controller which supports hardware lines of 40 - characters, although sometimes only 16, 20 or 24 of them are really wired - to the terminal. This results in some non-visible but addressable characters, - and is the case for most parallel LCDs. Other LCDs, and some serial ones, - however, use the same line width internally as what is visible. The KS0074 - for example, uses 16 characters per line for 16 visible characters per line. - - This option lets you configure the value used by your LCD in 'custom' profile. - If you don't know, put '40' here. - -config PANEL_LCD_HWIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Hardware LCD line width (1-64, 64 by default)" - range 1 64 - default 64 - ---help--- - Most LCDs use a single address bit to differentiate line 0 and line 1. Since - some of them need to be able to address 40 chars with the lower bits, they - often use the immediately superior power of 2, which is 64, to address the - next line. - - If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and - 64 here for a 2x40. - -config PANEL_LCD_CHARSET - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "LCD character set (0=normal, 1=KS0074)" - range 0 1 - default 0 - ---help--- - Some controllers such as the KS0074 use a somewhat strange character set - where many symbols are at unusual places. The driver knows how to map - 'standard' ASCII characters to the character sets used by these controllers. - Valid values are : - - 0 : normal (untranslated) character set - 1 : KS0074 character set - - If you don't know, use the normal one (0). - -config PANEL_LCD_PROTO - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "LCD communication mode (0=parallel 8 bits, 1=serial)" - range 0 1 - default 0 - ---help--- - This driver now supports any serial or parallel LCD wired to a parallel - port. But before assigning signals, the driver needs to know if it will - be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires - (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals - (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits - parallel LCD, and 1 for a serial LCD. - -config PANEL_LCD_PIN_E - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " - range -17 17 - default 14 +config ASPEED_LPC_CTRL + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON + tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" ---help--- - This describes the number of the parallel port pin to which the LCD 'E' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'E' pin in custom profile is '14' (AUTOFEED). - -config PANEL_LCD_PIN_RS - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " - range -17 17 - default 17 - ---help--- - This describes the number of the parallel port pin to which the LCD 'RS' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'RS' pin in custom profile is '17' (SELECT IN). - -config PANEL_LCD_PIN_RW - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " - range -17 17 - default 16 - ---help--- - This describes the number of the parallel port pin to which the LCD 'RW' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'RW' pin in custom profile is '16' (INIT). - -config PANEL_LCD_PIN_SCL - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" - int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " - range -17 17 - default 1 - ---help--- - This describes the number of the parallel port pin to which the serial - LCD 'SCL' signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'SCL' pin in custom profile is '1' (STROBE). - -config PANEL_LCD_PIN_SDA - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" - int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " - range -17 17 - default 2 - ---help--- - This describes the number of the parallel port pin to which the serial - LCD 'SDA' signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'SDA' pin in custom profile is '2' (D0). - -config PANEL_LCD_PIN_BL - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " - range -17 17 - default 0 - ---help--- - This describes the number of the parallel port pin to which the LCD 'BL' signal - has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'BL' pin in custom profile is '0' (uncontrolled). - -config PANEL_CHANGE_MESSAGE - bool "Change LCD initialization message ?" - default "n" - ---help--- - This allows you to replace the boot message indicating the kernel version - and the driver version with a custom message. This is useful on appliances - where a simple 'Starting system' message can be enough to stop a customer - from worrying. - - If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, - say 'N' and keep the default message with the version. - -config PANEL_BOOT_MESSAGE - depends on PANEL_CHANGE_MESSAGE="y" - string "New initialization message" - default "" - ---help--- - This allows you to replace the boot message indicating the kernel version - and the driver version with a custom message. This is useful on appliances - where a simple 'Starting system' message can be enough to stop a customer - from worrying. - - An empty message will only clear the display at driver init time. Any other - printf()-formatted message is valid with newline and escape codes. - -endif # PANEL + Control Aspeed ast2400/2500 HOST LPC to BMC mappings through + ioctl()s, the driver also provides a read/write interface to a BMC ram + region where the host LPC read/write region can be buffered. source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7a3ea89339b4..4fb10af2ea1c 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -37,7 +37,6 @@ obj-y += eeprom/ obj-y += cb710/ obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o -obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ @@ -53,7 +52,7 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ -obj-$(CONFIG_PANEL) += panel.o +obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o @@ -62,6 +61,8 @@ lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o +KCOV_INSTRUMENT_lkdtm_rodata.o := n + OBJCOPYFLAGS := OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \ --set-section-flags .text=alloc,readonly \ diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c new file mode 100644 index 000000000000..b5439643f54b --- /dev/null +++ b/drivers/misc/aspeed-lpc-ctrl.c @@ -0,0 +1,266 @@ +/* + * Copyright 2017 IBM Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> + +#include <linux/aspeed-lpc-ctrl.h> + +#define DEVICE_NAME "aspeed-lpc-ctrl" + +#define HICR7 0x8 +#define HICR8 0xc + +struct aspeed_lpc_ctrl { + struct miscdevice miscdev; + struct regmap *regmap; + phys_addr_t mem_base; + resource_size_t mem_size; + u32 pnor_size; + u32 pnor_base; +}; + +static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file) +{ + return container_of(file->private_data, struct aspeed_lpc_ctrl, + miscdev); +} + +static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + unsigned long vsize = vma->vm_end - vma->vm_start; + pgprot_t prot = vma->vm_page_prot; + + if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size) + return -EINVAL; + + /* ast2400/2500 AHB accesses are not cache coherent */ + prot = pgprot_noncached(prot); + + if (remap_pfn_range(vma, vma->vm_start, + (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff, + vsize, prot)) + return -EAGAIN; + + return 0; +} + +static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + void __user *p = (void __user *)param; + struct aspeed_lpc_ctrl_mapping map; + u32 addr; + u32 size; + long rc; + + if (copy_from_user(&map, p, sizeof(map))) + return -EFAULT; + + if (map.flags != 0) + return -EINVAL; + + switch (cmd) { + case ASPEED_LPC_CTRL_IOCTL_GET_SIZE: + /* The flash windows don't report their size */ + if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY) + return -EINVAL; + + /* Support more than one window id in the future */ + if (map.window_id != 0) + return -EINVAL; + + map.size = lpc_ctrl->mem_size; + + return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0; + case ASPEED_LPC_CTRL_IOCTL_MAP: + + /* + * The top half of HICR7 is the MSB of the BMC address of the + * mapping. + * The bottom half of HICR7 is the MSB of the HOST LPC + * firmware space address of the mapping. + * + * The 1 bits in the top of half of HICR8 represent the bits + * (in the requested address) that should be ignored and + * replaced with those from the top half of HICR7. + * The 1 bits in the bottom half of HICR8 represent the bits + * (in the requested address) that should be kept and pass + * into the BMC address space. + */ + + /* + * It doesn't make sense to talk about a size or offset with + * low 16 bits set. Both HICR7 and HICR8 talk about the top 16 + * bits of addresses and sizes. + */ + + if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff)) + return -EINVAL; + + /* + * Because of the way the masks work in HICR8 offset has to + * be a multiple of size. + */ + if (map.offset & (map.size - 1)) + return -EINVAL; + + if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) { + addr = lpc_ctrl->pnor_base; + size = lpc_ctrl->pnor_size; + } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) { + addr = lpc_ctrl->mem_base; + size = lpc_ctrl->mem_size; + } else { + return -EINVAL; + } + + /* Check overflow first! */ + if (map.offset + map.size < map.offset || + map.offset + map.size > size) + return -EINVAL; + + if (map.size == 0 || map.size > size) + return -EINVAL; + + addr += map.offset; + + /* + * addr (host lpc address) is safe regardless of values. This + * simply changes the address the host has to request on its + * side of the LPC bus. This cannot impact the hosts own + * memory space by surprise as LPC specific accessors are + * required. The only strange thing that could be done is + * setting the lower 16 bits but the shift takes care of that. + */ + + rc = regmap_write(lpc_ctrl->regmap, HICR7, + (addr | (map.addr >> 16))); + if (rc) + return rc; + + return regmap_write(lpc_ctrl->regmap, HICR8, + (~(map.size - 1)) | ((map.size >> 16) - 1)); + } + + return -EINVAL; +} + +static const struct file_operations aspeed_lpc_ctrl_fops = { + .owner = THIS_MODULE, + .mmap = aspeed_lpc_ctrl_mmap, + .unlocked_ioctl = aspeed_lpc_ctrl_ioctl, +}; + +static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl; + struct device_node *node; + struct resource resm; + struct device *dev; + int rc; + + dev = &pdev->dev; + + lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL); + if (!lpc_ctrl) + return -ENOMEM; + + node = of_parse_phandle(dev->of_node, "flash", 0); + if (!node) { + dev_err(dev, "Didn't find host pnor flash node\n"); + return -ENODEV; + } + + rc = of_address_to_resource(node, 1, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for flash\n"); + return rc; + } + + lpc_ctrl->pnor_size = resource_size(&resm); + lpc_ctrl->pnor_base = resm.start; + + dev_set_drvdata(&pdev->dev, lpc_ctrl); + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "Didn't find reserved memory\n"); + return -EINVAL; + } + + rc = of_address_to_resource(node, 0, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for reserved memory\n"); + return -ENOMEM; + } + + lpc_ctrl->mem_size = resource_size(&resm); + lpc_ctrl->mem_base = resm.start; + + lpc_ctrl->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_ctrl->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_ctrl->miscdev.name = DEVICE_NAME; + lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops; + lpc_ctrl->miscdev.parent = dev; + rc = misc_register(&lpc_ctrl->miscdev); + if (rc) + dev_err(dev, "Unable to register device\n"); + else + dev_info(dev, "Loaded at %pr\n", &resm); + + return rc; +} + +static int aspeed_lpc_ctrl_remove(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev); + + misc_deregister(&lpc_ctrl->miscdev); + + return 0; +} + +static const struct of_device_id aspeed_lpc_ctrl_match[] = { + { .compatible = "aspeed,ast2400-lpc-ctrl" }, + { .compatible = "aspeed,ast2500-lpc-ctrl" }, + { }, +}; + +static struct platform_driver aspeed_lpc_ctrl_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_lpc_ctrl_match, + }, + .probe = aspeed_lpc_ctrl_probe, + .remove = aspeed_lpc_ctrl_remove, +}; + +module_platform_driver(aspeed_lpc_ctrl_driver); + +MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>"); +MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings"); diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index c7112276a039..28bb495f0cf1 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -227,9 +227,16 @@ static const struct i2c_device_id ds1682_id[] = { }; MODULE_DEVICE_TABLE(i2c, ds1682_id); +static const struct of_device_id ds1682_of_match[] = { + { .compatible = "dallas,ds1682", }, + {} +}; +MODULE_DEVICE_TABLE(of, ds1682_of_match); + static struct i2c_driver ds1682_driver = { .driver = { .name = "ds1682", + .of_match_table = ds1682_of_match, }, .probe = ds1682_probe, .remove = ds1682_remove, diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c index 4a22a1d99395..ab0df6a17690 100644 --- a/drivers/misc/eeprom/idt_89hpesx.c +++ b/drivers/misc/eeprom/idt_89hpesx.c @@ -1541,12 +1541,69 @@ static const struct i2c_device_id idt_ids[] = { }; MODULE_DEVICE_TABLE(i2c, idt_ids); +static const struct of_device_id idt_of_match[] = { + { .compatible = "idt,89hpes8nt2", }, + { .compatible = "idt,89hpes12nt3", }, + + { .compatible = "idt,89hpes24nt6ag2", }, + { .compatible = "idt,89hpes32nt8ag2", }, + { .compatible = "idt,89hpes32nt8bg2", }, + { .compatible = "idt,89hpes12nt12g2", }, + { .compatible = "idt,89hpes16nt16g2", }, + { .compatible = "idt,89hpes24nt24g2", }, + { .compatible = "idt,89hpes32nt24ag2", }, + { .compatible = "idt,89hpes32nt24bg2", }, + + { .compatible = "idt,89hpes12n3", }, + { .compatible = "idt,89hpes12n3a", }, + { .compatible = "idt,89hpes24n3", }, + { .compatible = "idt,89hpes24n3a", }, + + { .compatible = "idt,89hpes32h8", }, + { .compatible = "idt,89hpes32h8g2", }, + { .compatible = "idt,89hpes48h12", }, + { .compatible = "idt,89hpes48h12g2", }, + { .compatible = "idt,89hpes48h12ag2", }, + { .compatible = "idt,89hpes16h16", }, + { .compatible = "idt,89hpes22h16", }, + { .compatible = "idt,89hpes22h16g2", }, + { .compatible = "idt,89hpes34h16", }, + { .compatible = "idt,89hpes34h16g2", }, + { .compatible = "idt,89hpes64h16", }, + { .compatible = "idt,89hpes64h16g2", }, + { .compatible = "idt,89hpes64h16ag2", }, + + { .compatible = "idt,89hpes12t3g2", }, + { .compatible = "idt,89hpes24t3g2", }, + + { .compatible = "idt,89hpes16t4", }, + { .compatible = "idt,89hpes4t4g2", }, + { .compatible = "idt,89hpes10t4g2", }, + { .compatible = "idt,89hpes16t4g2", }, + { .compatible = "idt,89hpes16t4ag2", }, + { .compatible = "idt,89hpes5t5", }, + { .compatible = "idt,89hpes6t5", }, + { .compatible = "idt,89hpes8t5", }, + { .compatible = "idt,89hpes8t5a", }, + { .compatible = "idt,89hpes24t6", }, + { .compatible = "idt,89hpes6t6g2", }, + { .compatible = "idt,89hpes24t6g2", }, + { .compatible = "idt,89hpes16t7", }, + { .compatible = "idt,89hpes32t8", }, + { .compatible = "idt,89hpes32t8g2", }, + { .compatible = "idt,89hpes48t12", }, + { .compatible = "idt,89hpes48t12g2", }, + { }, +}; +MODULE_DEVICE_TABLE(of, idt_of_match); + /* * idt_driver - IDT 89HPESx driver structure */ static struct i2c_driver idt_driver = { .driver = { .name = IDT_NAME, + .of_match_table = idt_of_match, }, .probe = idt_probe, .remove = idt_remove, diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index 67d27be60405..3b4976396ec4 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -27,6 +27,7 @@ void lkdtm_REFCOUNT_ZERO_SUB(void); void lkdtm_REFCOUNT_ZERO_ADD(void); void lkdtm_CORRUPT_LIST_ADD(void); void lkdtm_CORRUPT_LIST_DEL(void); +void lkdtm_CORRUPT_USER_DS(void); /* lkdtm_heap.c */ void lkdtm_OVERWRITE_ALLOCATION(void); diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c index e3f4cd8876b5..d9028ef50fbe 100644 --- a/drivers/misc/lkdtm_bugs.c +++ b/drivers/misc/lkdtm_bugs.c @@ -8,6 +8,8 @@ #include <linux/list.h> #include <linux/refcount.h> #include <linux/sched.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> struct lkdtm_list { struct list_head node; @@ -67,7 +69,7 @@ void lkdtm_WARNING(void) void lkdtm_EXCEPTION(void) { - *((int *) 0) = 0; + *((volatile int *) 0) = 0; } void lkdtm_LOOP(void) @@ -279,3 +281,12 @@ void lkdtm_CORRUPT_LIST_DEL(void) else pr_err("list_del() corruption not detected!\n"); } + +void lkdtm_CORRUPT_USER_DS(void) +{ + pr_info("setting bad task size limit\n"); + set_fs(KERNEL_DS); + + /* Make sure we do not keep running with a KERNEL_DS! */ + force_sig(SIGKILL, current); +} diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index b9a4cd4a9b68..42d2b8e31e6b 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -199,6 +199,7 @@ struct crashtype crashtypes[] = { CRASHTYPE(OVERFLOW), CRASHTYPE(CORRUPT_LIST_ADD), CRASHTYPE(CORRUPT_LIST_DEL), + CRASHTYPE(CORRUPT_USER_DS), CRASHTYPE(CORRUPT_STACK), CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE), CRASHTYPE(OVERWRITE_ALLOCATION), diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 59e6b0aede34..12cceb011a23 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -8,7 +8,6 @@ mei-objs += hbm.o mei-objs += interrupt.o mei-objs += client.o mei-objs += main.o -mei-objs += amthif.o mei-objs += bus.o mei-objs += bus-fixup.o mei-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c deleted file mode 100644 index 0e7406ccb6dd..000000000000 --- a/drivers/misc/mei/amthif.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * - * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-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. - * - */ - -#include <linux/kernel.h> -#include <linux/fs.h> -#include <linux/errno.h> -#include <linux/types.h> -#include <linux/fcntl.h> -#include <linux/ioctl.h> -#include <linux/cdev.h> -#include <linux/list.h> -#include <linux/delay.h> -#include <linux/sched.h> -#include <linux/uuid.h> -#include <linux/jiffies.h> -#include <linux/uaccess.h> -#include <linux/slab.h> - -#include <linux/mei.h> - -#include "mei_dev.h" -#include "hbm.h" -#include "client.h" - -const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, - 0xac, 0xa8, 0x46, 0xe0, - 0xff, 0x65, 0x81, 0x4c); - -/** - * mei_amthif_reset_params - initializes mei device iamthif - * - * @dev: the device structure - */ -void mei_amthif_reset_params(struct mei_device *dev) -{ - /* reset iamthif parameters. */ - dev->iamthif_canceled = false; - dev->iamthif_state = MEI_IAMTHIF_IDLE; - dev->iamthif_stall_timer = 0; - dev->iamthif_open_count = 0; -} - -/** - * mei_amthif_host_init - mei initialization amthif client. - * - * @dev: the device structure - * @me_cl: me client - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl) -{ - struct mei_cl *cl = &dev->iamthif_cl; - int ret; - - mutex_lock(&dev->device_lock); - - if (mei_cl_is_connected(cl)) { - ret = 0; - goto out; - } - - dev->iamthif_state = MEI_IAMTHIF_IDLE; - - mei_cl_init(cl, dev); - - ret = mei_cl_link(cl); - if (ret < 0) { - dev_err(dev->dev, "amthif: failed cl_link %d\n", ret); - goto out; - } - - ret = mei_cl_connect(cl, me_cl, NULL); - -out: - mutex_unlock(&dev->device_lock); - return ret; -} - -/** - * mei_amthif_read_start - queue message for sending read credential - * - * @cl: host client - * @fp: file pointer of message recipient - * - * Return: 0 on success, <0 on failure. - */ -static int mei_amthif_read_start(struct mei_cl *cl, const struct file *fp) -{ - struct mei_device *dev = cl->dev; - struct mei_cl_cb *cb; - - cb = mei_cl_enqueue_ctrl_wr_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, fp); - if (!cb) - return -ENOMEM; - - cl->rx_flow_ctrl_creds++; - - dev->iamthif_state = MEI_IAMTHIF_READING; - cl->fp = cb->fp; - - return 0; -} - -/** - * mei_amthif_run_next_cmd - send next amt command from queue - * - * @dev: the device structure - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_run_next_cmd(struct mei_device *dev) -{ - struct mei_cl *cl = &dev->iamthif_cl; - struct mei_cl_cb *cb; - int ret; - - dev->iamthif_canceled = false; - - dev_dbg(dev->dev, "complete amthif cmd_list cb.\n"); - - cb = list_first_entry_or_null(&dev->amthif_cmd_list, typeof(*cb), list); - if (!cb) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->fp = NULL; - return 0; - } - - list_del_init(&cb->list); - dev->iamthif_state = MEI_IAMTHIF_WRITING; - cl->fp = cb->fp; - - ret = mei_cl_write(cl, cb); - if (ret < 0) - return ret; - - if (cb->completed) - cb->status = mei_amthif_read_start(cl, cb->fp); - - return 0; -} - -/** - * mei_amthif_write - write amthif data to amthif client - * - * @cl: host client - * @cb: mei call back struct - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb) -{ - - struct mei_device *dev = cl->dev; - - list_add_tail(&cb->list, &dev->amthif_cmd_list); - - /* - * The previous request is still in processing, queue this one. - */ - if (dev->iamthif_state != MEI_IAMTHIF_IDLE) - return 0; - - return mei_amthif_run_next_cmd(dev); -} - -/** - * mei_amthif_poll - the amthif poll function - * - * @file: pointer to file structure - * @wait: pointer to poll_table structure - * - * Return: poll mask - * - * Locking: called under "dev->device_lock" lock - */ -unsigned int mei_amthif_poll(struct file *file, poll_table *wait) -{ - struct mei_cl *cl = file->private_data; - struct mei_cl_cb *cb = mei_cl_read_cb(cl, file); - unsigned int mask = 0; - - poll_wait(file, &cl->rx_wait, wait); - if (cb) - mask |= POLLIN | POLLRDNORM; - - return mask; -} - -/** - * mei_amthif_irq_write - write iamthif command in irq thread context. - * - * @cl: private data of the file object. - * @cb: callback block. - * @cmpl_list: complete list. - * - * Return: 0, OK; otherwise, error. - */ -int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, - struct list_head *cmpl_list) -{ - int ret; - - ret = mei_cl_irq_write(cl, cb, cmpl_list); - if (ret) - return ret; - - if (cb->completed) - cb->status = mei_amthif_read_start(cl, cb->fp); - - return 0; -} - -/** - * mei_amthif_irq_read_msg - read routine after ISR to - * handle the read amthif message - * - * @cl: mei client - * @mei_hdr: header of amthif message - * @cmpl_list: completed callbacks list - * - * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status - */ -int mei_amthif_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list) -{ - struct mei_device *dev; - int ret; - - dev = cl->dev; - - if (dev->iamthif_state != MEI_IAMTHIF_READING) { - mei_irq_discard_msg(dev, mei_hdr); - return 0; - } - - ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); - if (ret) - return ret; - - if (!mei_hdr->msg_complete) - return 0; - - dev_dbg(dev->dev, "completed amthif read.\n "); - dev->iamthif_stall_timer = 0; - - return 0; -} - -/** - * mei_amthif_complete - complete amthif callback. - * - * @cl: host client - * @cb: callback block. - */ -void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb) -{ - struct mei_device *dev = cl->dev; - - dev_dbg(dev->dev, "completing amthif call back.\n"); - switch (cb->fop_type) { - case MEI_FOP_WRITE: - if (!cb->status) { - dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; - mei_schedule_stall_timer(dev); - mei_io_cb_free(cb); - return; - } - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->fp = NULL; - if (!dev->iamthif_canceled) { - /* - * in case of error enqueue the write cb to complete - * read list so it can be propagated to the reader - */ - list_add_tail(&cb->list, &cl->rd_completed); - wake_up_interruptible(&cl->rx_wait); - } else { - mei_io_cb_free(cb); - } - break; - case MEI_FOP_READ: - if (!dev->iamthif_canceled) { - list_add_tail(&cb->list, &cl->rd_completed); - dev_dbg(dev->dev, "amthif read completed\n"); - wake_up_interruptible(&cl->rx_wait); - } else { - mei_io_cb_free(cb); - } - - dev->iamthif_stall_timer = 0; - mei_amthif_run_next_cmd(dev); - break; - default: - WARN_ON(1); - } -} - -/** -* mei_amthif_release - the release function -* -* @dev: device structure -* @fp: pointer to file structure -* -* Return: 0 on success, <0 on error -*/ -int mei_amthif_release(struct mei_device *dev, struct file *fp) -{ - struct mei_cl *cl = fp->private_data; - - if (dev->iamthif_open_count > 0) - dev->iamthif_open_count--; - - if (cl->fp == fp && dev->iamthif_state != MEI_IAMTHIF_IDLE) { - - dev_dbg(dev->dev, "amthif canceled iamthif state %d\n", - dev->iamthif_state); - dev->iamthif_canceled = true; - } - - /* Don't clean ctrl_rd_list here, the reads has to be completed */ - mei_io_list_free_fp(&dev->amthif_cmd_list, fp); - mei_io_list_free_fp(&cl->rd_completed, fp); - - return 0; -} diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 29f2daed37e0..0208c4b027c5 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -110,12 +110,13 @@ struct mkhi_msg { u8 data[0]; } __packed; +#define MKHI_OSVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ + sizeof(struct mkhi_fwcaps) + \ + sizeof(struct mei_os_ver)) static int mei_osver(struct mei_cl_device *cldev) { - const size_t size = sizeof(struct mkhi_msg_hdr) + - sizeof(struct mkhi_fwcaps) + - sizeof(struct mei_os_ver); - char buf[size]; + const size_t size = MKHI_OSVER_BUF_LEN; + char buf[MKHI_OSVER_BUF_LEN]; struct mkhi_msg *req; struct mkhi_fwcaps *fwcaps; struct mei_os_ver *os_ver; diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index df5f78ae3d25..d1928fdd0f43 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -1076,12 +1076,6 @@ void mei_cl_bus_rescan_work(struct work_struct *work) { struct mei_device *bus = container_of(work, struct mei_device, bus_rescan_work); - struct mei_me_client *me_cl; - - me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid); - if (me_cl) - mei_amthif_host_init(bus, me_cl); - mei_me_cl_put(me_cl); mei_cl_bus_rescan(bus); } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index d3e3372424d6..be64969d986a 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -428,7 +428,7 @@ static inline void mei_io_list_free_cl(struct list_head *head, * @head: io list * @fp: file pointer (matching cb file object), may be NULL */ -void mei_io_list_free_fp(struct list_head *head, const struct file *fp) +static void mei_io_list_free_fp(struct list_head *head, const struct file *fp) { struct mei_cl_cb *cb, *next; @@ -554,7 +554,7 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) * @cl: host client to be initialized * @dev: mei device */ -void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) +static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) { memset(cl, 0, sizeof(struct mei_cl)); init_waitqueue_head(&cl->wait); @@ -600,7 +600,6 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) int mei_cl_link(struct mei_cl *cl) { struct mei_device *dev; - long open_handle_count; int id; if (WARN_ON(!cl || !cl->dev)) @@ -614,8 +613,7 @@ int mei_cl_link(struct mei_cl *cl) return -EMFILE; } - open_handle_count = dev->open_handle_count + dev->iamthif_open_count; - if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { + if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { dev_err(dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; @@ -649,8 +647,7 @@ int mei_cl_unlink(struct mei_cl *cl) if (!cl) return 0; - /* amthif might not be initialized */ - if (!cl->dev) + if (WARN_ON(!cl->dev)) return 0; dev = cl->dev; @@ -763,7 +760,6 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) mei_io_list_free_cl(&dev->write_waiting_list, cl); mei_io_list_flush_cl(&dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&dev->ctrl_wr_list, cl); - mei_io_list_free_cl(&dev->amthif_cmd_list, cl); mei_cl_wake_all(cl); cl->rx_flow_ctrl_creds = 0; cl->tx_flow_ctrl_creds = 0; @@ -1478,7 +1474,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) return -ENOTTY; } - if (mei_cl_is_fixed_address(cl) || cl == &dev->iamthif_cl) + if (mei_cl_is_fixed_address(cl)) return 0; /* HW currently supports only one pending read */ diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 545ae319ba90..5371df4d8af3 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -83,15 +83,12 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl) * MEI IO Functions */ void mei_io_cb_free(struct mei_cl_cb *priv_cb); -void mei_io_list_free_fp(struct list_head *head, const struct file *fp); /* * MEI Host Client Functions */ struct mei_cl *mei_cl_allocate(struct mei_device *dev); -void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); - int mei_cl_link(struct mei_cl *cl); int mei_cl_unlink(struct mei_cl *cl); @@ -205,8 +202,6 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp); -int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr, - struct list_head *cmpl_list); int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index ba3a774c8d71..fe6595fe94f1 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -166,9 +166,8 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len) * * Return: 0 on success, <0 on failure. */ -static inline -int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl, - u8 hbm_cmd, u8 *buf, size_t len) +static inline int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl, + u8 hbm_cmd, void *buf, size_t len) { struct mei_msg_hdr mei_hdr; @@ -632,11 +631,11 @@ static int mei_hbm_stop_req(struct mei_device *dev) */ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_flow_control); - u8 buf[len]; + struct hbm_flow_control req; cl_dbg(dev, cl, "sending flow control\n"); - return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD, + &req, sizeof(req)); } /** @@ -710,10 +709,10 @@ static void mei_hbm_cl_tx_flow_ctrl_creds_res(struct mei_device *dev, */ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_request); - u8 buf[len]; + struct hbm_client_connect_request req; - return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD, + &req, sizeof(req)); } /** @@ -726,10 +725,10 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) */ int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_response); - u8 buf[len]; + struct hbm_client_connect_response resp; - return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD, + &resp, sizeof(resp)); } /** @@ -763,10 +762,10 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct mei_cl *cl, */ int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_request); - u8 buf[len]; + struct hbm_client_connect_request req; - return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD, + &req, sizeof(req)); } /** diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 13c55b8f9261..c8ad9ee7cb80 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -146,18 +146,9 @@ int mei_reset(struct mei_device *dev) /* fall through and remove the sw state even if hw reset has failed */ /* no need to clean up software state in case of power up */ - if (state != MEI_DEV_INITIALIZING && - state != MEI_DEV_POWER_UP) { - - /* remove all waiting requests */ + if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_POWER_UP) mei_cl_all_disconnect(dev); - /* remove entry if already in list */ - dev_dbg(dev->dev, "remove iamthif from the file list.\n"); - mei_cl_unlink(&dev->iamthif_cl); - mei_amthif_reset_params(dev); - } - mei_hbm_reset(dev); dev->rd_msg_hdr = 0; @@ -401,9 +392,6 @@ void mei_device_init(struct mei_device *dev, INIT_WORK(&dev->reset_work, mei_reset_work); INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work); - INIT_LIST_HEAD(&dev->iamthif_cl.link); - INIT_LIST_HEAD(&dev->amthif_cmd_list); - bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); dev->open_handle_count = 0; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 406e9e2b2fff..c14e35201721 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -47,10 +47,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list) list_del_init(&cb->list); dev_dbg(dev->dev, "completing call back.\n"); - if (cl == &dev->iamthif_cl) - mei_amthif_complete(cl, cb); - else - mei_cl_complete(cl, cb); + mei_cl_complete(cl, cb); } } EXPORT_SYMBOL_GPL(mei_irq_compl_handler); @@ -76,7 +73,7 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl, * @dev: mei device * @hdr: message header */ -void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) +static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) { /* * no need to check for size as it is guarantied @@ -96,9 +93,9 @@ void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) * * Return: always 0 */ -int mei_cl_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list) +static int mei_cl_irq_read_msg(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr, + struct list_head *cmpl_list) { struct mei_device *dev = cl->dev; struct mei_cl_cb *cb; @@ -313,11 +310,7 @@ int mei_irq_read_handler(struct mei_device *dev, goto end; } - if (cl == &dev->iamthif_cl) { - ret = mei_amthif_irq_read_msg(cl, mei_hdr, cmpl_list); - } else { - ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); - } + ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); reset_slots: @@ -423,10 +416,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) dev_dbg(dev->dev, "complete write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_list, list) { cl = cb->cl; - if (cl == &dev->iamthif_cl) - ret = mei_amthif_irq_write(cl, cb, cmpl_list); - else - ret = mei_cl_irq_write(cl, cb, cmpl_list); + ret = mei_cl_irq_write(cl, cb, cmpl_list); if (ret) return ret; } @@ -512,20 +502,6 @@ void mei_timer(struct work_struct *work) } } - if (!mei_cl_is_connected(&dev->iamthif_cl)) - goto out; - - if (dev->iamthif_stall_timer) { - if (--dev->iamthif_stall_timer == 0) { - dev_err(dev->dev, "timer: amthif hanged.\n"); - mei_reset(dev); - - mei_amthif_run_next_cmd(dev); - goto out; - } - reschedule_timer = true; - } - out: if (dev->dev_state != MEI_DEV_DISABLED && reschedule_timer) mei_schedule_stall_timer(dev); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index bf816449cd40..e825f013e54e 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -103,10 +103,7 @@ static int mei_release(struct inode *inode, struct file *file) dev = cl->dev; mutex_lock(&dev->device_lock); - if (cl == &dev->iamthif_cl) { - rets = mei_amthif_release(dev, file); - goto out; - } + rets = mei_cl_disconnect(cl); mei_cl_flush_queues(cl, file); @@ -117,7 +114,7 @@ static int mei_release(struct inode *inode, struct file *file) file->private_data = NULL; kfree(cl); -out: + mutex_unlock(&dev->device_lock); return rets; } @@ -182,8 +179,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - -again: mutex_unlock(&dev->device_lock); if (wait_event_interruptible(cl->rx_wait, !list_empty(&cl->rd_completed) || @@ -201,14 +196,6 @@ again: cb = mei_cl_read_cb(cl, file); if (!cb) { - /* - * For amthif all the waiters are woken up, - * but only fp with matching cb->fp get the cb, - * the others have to return to wait on read. - */ - if (cl == &dev->iamthif_cl) - goto again; - rets = 0; goto out; } @@ -319,13 +306,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; } - if (cl == &dev->iamthif_cl) { - rets = mei_amthif_write(cl, cb); - if (!rets) - rets = length; - goto out; - } - rets = mei_cl_write(cl, cb); out: mutex_unlock(&dev->device_lock); @@ -388,30 +368,6 @@ static int mei_ioctl_connect_client(struct file *file, dev_dbg(dev->dev, "FW Client - Max Msg Len = %d\n", me_cl->props.max_msg_length); - /* if we're connecting to amthif client then we will use the - * existing connection - */ - if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) { - dev_dbg(dev->dev, "FW Client is amthi\n"); - if (!mei_cl_is_connected(&dev->iamthif_cl)) { - rets = -ENODEV; - goto end; - } - mei_cl_unlink(cl); - - kfree(cl); - cl = NULL; - dev->iamthif_open_count++; - file->private_data = &dev->iamthif_cl; - - client = &data->out_client_properties; - client->max_msg_length = me_cl->props.max_msg_length; - client->protocol_version = me_cl->props.protocol_version; - rets = dev->iamthif_cl.status; - - goto end; - } - /* prepare the output buffer */ client = &data->out_client_properties; client->max_msg_length = me_cl->props.max_msg_length; @@ -615,11 +571,6 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) mask |= POLLPRI; } - if (cl == &dev->iamthif_cl) { - mask |= mei_amthif_poll(file, wait); - goto out; - } - if (req_events & (POLLIN | POLLRDNORM)) { poll_wait(file, &cl->rx_wait, wait); @@ -635,6 +586,77 @@ out: } /** + * mei_cl_is_write_queued - check if the client has pending writes. + * + * @cl: writing host client + * + * Return: true if client is writing, false otherwise. + */ +static bool mei_cl_is_write_queued(struct mei_cl *cl) +{ + struct mei_device *dev = cl->dev; + struct mei_cl_cb *cb; + + list_for_each_entry(cb, &dev->write_list, list) + if (cb->cl == cl) + return true; + list_for_each_entry(cb, &dev->write_waiting_list, list) + if (cb->cl == cl) + return true; + return false; +} + +/** + * mei_fsync - the fsync handler + * + * @fp: pointer to file structure + * @start: unused + * @end: unused + * @datasync: unused + * + * Return: 0 on success, -ENODEV if client is not connected + */ +static int mei_fsync(struct file *fp, loff_t start, loff_t end, int datasync) +{ + struct mei_cl *cl = fp->private_data; + struct mei_device *dev; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->dev_state != MEI_DEV_ENABLED || !mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + + while (mei_cl_is_write_queued(cl)) { + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + !mei_cl_is_connected(cl)); + mutex_lock(&dev->device_lock); + if (rets) { + if (signal_pending(current)) + rets = -EINTR; + goto out; + } + if (!mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + } + rets = 0; +out: + mutex_unlock(&dev->device_lock); + return rets; +} + +/** * mei_fasync - asynchronous io support * * @fd: file descriptor @@ -749,6 +771,7 @@ static const struct file_operations mei_fops = { .release = mei_release, .write = mei_write, .poll = mei_poll, + .fsync = mei_fsync, .fasync = mei_fasync, .llseek = no_llseek }; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d41aac53a2ac..63a67c99fc78 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -26,12 +26,6 @@ #include "hw.h" #include "hbm.h" - -/* - * AMTHI Client UUID - */ -extern const uuid_le mei_amthif_guid; - #define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32)) /* @@ -78,12 +72,6 @@ enum mei_dev_state { const char *mei_dev_state_str(int state); -enum iamthif_states { - MEI_IAMTHIF_IDLE, - MEI_IAMTHIF_WRITING, - MEI_IAMTHIF_READING, -}; - enum mei_file_transaction_states { MEI_IDLE, MEI_WRITING, @@ -418,13 +406,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @allow_fixed_address: allow user space to connect a fixed client * @override_fixed_address: force allow fixed address behavior * - * @amthif_cmd_list : amthif list for cmd waiting - * @iamthif_cl : amthif host client - * @iamthif_open_count : number of opened amthif connections - * @iamthif_stall_timer : timer to detect amthif hang - * @iamthif_state : amthif processor state - * @iamthif_canceled : current amthif command is canceled - * * @reset_work : work item for the device reset * @bus_rescan_work : work item for the bus rescan * @@ -500,14 +481,6 @@ struct mei_device { bool allow_fixed_address; bool override_fixed_address; - /* amthif list for cmd waiting */ - struct list_head amthif_cmd_list; - struct mei_cl iamthif_cl; - long iamthif_open_count; - u32 iamthif_stall_timer; - enum iamthif_states iamthif_state; - bool iamthif_canceled; - struct work_struct reset_work; struct work_struct bus_rescan_work; @@ -579,28 +552,6 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list); void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list); /* - * AMTHIF - AMT Host Interface Functions - */ -void mei_amthif_reset_params(struct mei_device *dev); - -int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl); - -unsigned int mei_amthif_poll(struct file *file, poll_table *wait); - -int mei_amthif_release(struct mei_device *dev, struct file *file); - -int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb); -int mei_amthif_run_next_cmd(struct mei_device *dev); -int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, - struct list_head *cmpl_list); - -void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb); -int mei_amthif_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list); -int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); - -/* * Register Access Function */ @@ -711,8 +662,6 @@ bool mei_hbuf_acquire(struct mei_device *dev); bool mei_write_is_idle(struct mei_device *dev); -void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr); - #if IS_ENABLED(CONFIG_DEBUG_FS) int mei_dbgfs_register(struct mei_device *dev, const char *name); void mei_dbgfs_deregister(struct mei_device *dev); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 0a668fdfbbe9..8621a198a2ce 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -242,11 +242,38 @@ end: } /** + * mei_me_shutdown - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_me_shutdown is called from the reboot notifier + * it's a simplified version of remove so we go down + * faster. + */ +static void mei_me_shutdown(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + dev_dbg(&pdev->dev, "shutdown\n"); + mei_stop(dev); + + if (!pci_dev_run_wake(pdev)) + mei_me_unset_pm_domain(dev); + + mei_disable_interrupts(dev); + free_irq(pdev->irq, dev); +} + +/** * mei_me_remove - Device Removal Routine * * @pdev: PCI device structure * - * mei_remove is called by the PCI subsystem to alert the driver + * mei_me_remove is called by the PCI subsystem to alert the driver * that it should release a PCI device. */ static void mei_me_remove(struct pci_dev *pdev) @@ -456,7 +483,7 @@ static struct pci_driver mei_me_driver = { .id_table = mei_me_pci_tbl, .probe = mei_me_probe, .remove = mei_me_remove, - .shutdown = mei_me_remove, + .shutdown = mei_me_shutdown, .driver.pm = MEI_ME_PM_OPS, }; diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index fe088b40daf9..f811cd524468 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -161,6 +161,33 @@ end: } /** + * mei_txe_remove - Device Shutdown Routine + * + * @pdev: PCI device structure + * + * mei_txe_shutdown is called from the reboot notifier + * it's a simplified version of remove so we go down + * faster. + */ +static void mei_txe_shutdown(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + dev_dbg(&pdev->dev, "shutdown\n"); + mei_stop(dev); + + if (!pci_dev_run_wake(pdev)) + mei_txe_unset_pm_domain(dev); + + mei_disable_interrupts(dev); + free_irq(pdev->irq, dev); +} + +/** * mei_txe_remove - Device Removal Routine * * @pdev: PCI device structure @@ -386,7 +413,7 @@ static struct pci_driver mei_txe_driver = { .id_table = mei_txe_pci_tbl, .probe = mei_txe_probe, .remove = mei_txe_remove, - .shutdown = mei_txe_remove, + .shutdown = mei_txe_shutdown, .driver.pm = MEI_TXE_PM_OPS, }; diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c index 87a13374fdc0..adf46072cb37 100644 --- a/drivers/misc/tsl2550.c +++ b/drivers/misc/tsl2550.c @@ -443,9 +443,16 @@ static const struct i2c_device_id tsl2550_id[] = { }; MODULE_DEVICE_TABLE(i2c, tsl2550_id); +static const struct of_device_id tsl2550_of_match[] = { + { .compatible = "taos,tsl2550" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2550_of_match); + static struct i2c_driver tsl2550_driver = { .driver = { .name = TSL2550_DRV_NAME, + .of_match_table = tsl2550_of_match, .pm = TSL2550_PM_OPS, }, .probe = tsl2550_probe, diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 77513195f50e..8bae3731d039 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -421,41 +421,6 @@ static void dev_release(struct device *dev) } /** - * ubi_sysfs_init - initialize sysfs for an UBI device. - * @ubi: UBI device description object - * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was - * taken - * - * This function returns zero in case of success and a negative error code in - * case of failure. - */ -static int ubi_sysfs_init(struct ubi_device *ubi, int *ref) -{ - int err; - - ubi->dev.release = dev_release; - ubi->dev.devt = ubi->cdev.dev; - ubi->dev.class = &ubi_class; - ubi->dev.groups = ubi_dev_groups; - dev_set_name(&ubi->dev, UBI_NAME_STR"%d", ubi->ubi_num); - err = device_register(&ubi->dev); - if (err) - return err; - - *ref = 1; - return 0; -} - -/** - * ubi_sysfs_close - close sysfs for an UBI device. - * @ubi: UBI device description object - */ -static void ubi_sysfs_close(struct ubi_device *ubi) -{ - device_unregister(&ubi->dev); -} - -/** * kill_volumes - destroy all user volumes. * @ubi: UBI device description object */ @@ -471,27 +436,19 @@ static void kill_volumes(struct ubi_device *ubi) /** * uif_init - initialize user interfaces for an UBI device. * @ubi: UBI device description object - * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was - * taken, otherwise set to %0 * * This function initializes various user interfaces for an UBI device. If the * initialization fails at an early stage, this function frees all the - * resources it allocated, returns an error, and @ref is set to %0. However, - * if the initialization fails after the UBI device was registered in the - * driver core subsystem, this function takes a reference to @ubi->dev, because - * otherwise the release function ('dev_release()') would free whole @ubi - * object. The @ref argument is set to %1 in this case. The caller has to put - * this reference. + * resources it allocated, returns an error. * * This function returns zero in case of success and a negative error code in * case of failure. */ -static int uif_init(struct ubi_device *ubi, int *ref) +static int uif_init(struct ubi_device *ubi) { int i, err; dev_t dev; - *ref = 0; sprintf(ubi->ubi_name, UBI_NAME_STR "%d", ubi->ubi_num); /* @@ -508,20 +465,17 @@ static int uif_init(struct ubi_device *ubi, int *ref) return err; } + ubi->dev.devt = dev; + ubi_assert(MINOR(dev) == 0); cdev_init(&ubi->cdev, &ubi_cdev_operations); dbg_gen("%s major is %u", ubi->ubi_name, MAJOR(dev)); ubi->cdev.owner = THIS_MODULE; - err = cdev_add(&ubi->cdev, dev, 1); - if (err) { - ubi_err(ubi, "cannot add character device"); - goto out_unreg; - } - - err = ubi_sysfs_init(ubi, ref); + dev_set_name(&ubi->dev, UBI_NAME_STR "%d", ubi->ubi_num); + err = cdev_device_add(&ubi->cdev, &ubi->dev); if (err) - goto out_sysfs; + goto out_unreg; for (i = 0; i < ubi->vtbl_slots; i++) if (ubi->volumes[i]) { @@ -536,11 +490,7 @@ static int uif_init(struct ubi_device *ubi, int *ref) out_volumes: kill_volumes(ubi); -out_sysfs: - if (*ref) - get_device(&ubi->dev); - ubi_sysfs_close(ubi); - cdev_del(&ubi->cdev); + cdev_device_del(&ubi->cdev, &ubi->dev); out_unreg: unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); ubi_err(ubi, "cannot initialize UBI %s, error %d", @@ -559,8 +509,7 @@ out_unreg: static void uif_close(struct ubi_device *ubi) { kill_volumes(ubi); - ubi_sysfs_close(ubi); - cdev_del(&ubi->cdev); + cdev_device_del(&ubi->cdev, &ubi->dev); unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); } @@ -857,7 +806,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024) { struct ubi_device *ubi; - int i, err, ref = 0; + int i, err; if (max_beb_per1024 < 0 || max_beb_per1024 > MAX_MTD_UBI_BEB_LIMIT) return -EINVAL; @@ -919,6 +868,11 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, if (!ubi) return -ENOMEM; + device_initialize(&ubi->dev); + ubi->dev.release = dev_release; + ubi->dev.class = &ubi_class; + ubi->dev.groups = ubi_dev_groups; + ubi->mtd = mtd; ubi->ubi_num = ubi_num; ubi->vid_hdr_offset = vid_hdr_offset; @@ -995,7 +949,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, /* Make device "available" before it becomes accessible via sysfs */ ubi_devices[ubi_num] = ubi; - err = uif_init(ubi, &ref); + err = uif_init(ubi); if (err) goto out_detach; @@ -1045,8 +999,6 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, out_debugfs: ubi_debugfs_exit_dev(ubi); out_uif: - get_device(&ubi->dev); - ubi_assert(ref); uif_close(ubi); out_detach: ubi_devices[ubi_num] = NULL; @@ -1056,10 +1008,7 @@ out_detach: out_free: vfree(ubi->peb_buf); vfree(ubi->fm_buf); - if (ref) - put_device(&ubi->dev); - else - kfree(ubi); + put_device(&ubi->dev); return err; } @@ -1120,12 +1069,6 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) if (ubi->bgt_thread) kthread_stop(ubi->bgt_thread); - /* - * Get a reference to the device in order to prevent 'dev_release()' - * from freeing the @ubi object. - */ - get_device(&ubi->dev); - ubi_debugfs_exit_dev(ubi); uif_close(ubi); diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c index 7ac78c13dd1c..85237cf661f9 100644 --- a/drivers/mtd/ubi/vmt.c +++ b/drivers/mtd/ubi/vmt.c @@ -155,11 +155,10 @@ static void vol_release(struct device *dev) */ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) { - int i, err, vol_id = req->vol_id, do_free = 1; + int i, err, vol_id = req->vol_id; struct ubi_volume *vol; struct ubi_vtbl_record vtbl_rec; struct ubi_eba_table *eba_tbl = NULL; - dev_t dev; if (ubi->ro_mode) return -EROFS; @@ -168,6 +167,12 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) if (!vol) return -ENOMEM; + device_initialize(&vol->dev); + vol->dev.release = vol_release; + vol->dev.parent = &ubi->dev; + vol->dev.class = &ubi_class; + vol->dev.groups = volume_dev_groups; + spin_lock(&ubi->volumes_lock); if (vol_id == UBI_VOL_NUM_AUTO) { /* Find unused volume ID */ @@ -268,24 +273,13 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) /* Register character device for the volume */ cdev_init(&vol->cdev, &ubi_vol_cdev_operations); vol->cdev.owner = THIS_MODULE; - dev = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1); - err = cdev_add(&vol->cdev, dev, 1); - if (err) { - ubi_err(ubi, "cannot add character device"); - goto out_mapping; - } - - vol->dev.release = vol_release; - vol->dev.parent = &ubi->dev; - vol->dev.devt = dev; - vol->dev.class = &ubi_class; - vol->dev.groups = volume_dev_groups; + vol->dev.devt = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1); dev_set_name(&vol->dev, "%s_%d", ubi->ubi_name, vol->vol_id); - err = device_register(&vol->dev); + err = cdev_device_add(&vol->cdev, &vol->dev); if (err) { - ubi_err(ubi, "cannot register device"); - goto out_cdev; + ubi_err(ubi, "cannot add device"); + goto out_mapping; } /* Fill volume table record */ @@ -318,28 +312,17 @@ out_sysfs: * We have registered our device, we should not free the volume * description object in this function in case of an error - it is * freed by the release function. - * - * Get device reference to prevent the release function from being - * called just after sysfs has been closed. */ - do_free = 0; - get_device(&vol->dev); - device_unregister(&vol->dev); -out_cdev: - cdev_del(&vol->cdev); + cdev_device_del(&vol->cdev, &vol->dev); out_mapping: - if (do_free) - ubi_eba_destroy_table(eba_tbl); + ubi_eba_destroy_table(eba_tbl); out_acc: spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs -= vol->reserved_pebs; ubi->avail_pebs += vol->reserved_pebs; out_unlock: spin_unlock(&ubi->volumes_lock); - if (do_free) - kfree(vol); - else - put_device(&vol->dev); + put_device(&vol->dev); ubi_err(ubi, "cannot create volume %d, error %d", vol_id, err); return err; } @@ -391,8 +374,8 @@ int ubi_remove_volume(struct ubi_volume_desc *desc, int no_vtbl) goto out_err; } - cdev_del(&vol->cdev); - device_unregister(&vol->dev); + cdev_device_del(&vol->cdev, &vol->dev); + put_device(&vol->dev); spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs -= reserved_pebs; diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index c502c139d3bc..47a8103610bc 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -549,7 +549,8 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { if (bond->params.arp_targets[i]) { - nla_put_be32(skb, i, bond->params.arp_targets[i]); + if (nla_put_be32(skb, i, bond->params.arp_targets[i])) + goto nla_put_failure; targets_added = 1; } } diff --git a/drivers/net/dsa/dsa_loop.c b/drivers/net/dsa/dsa_loop.c index f0fc4de4fc9a..a19e1781e9bb 100644 --- a/drivers/net/dsa/dsa_loop.c +++ b/drivers/net/dsa/dsa_loop.c @@ -256,6 +256,9 @@ static int dsa_loop_drv_probe(struct mdio_device *mdiodev) return -ENOMEM; ps = devm_kzalloc(&mdiodev->dev, sizeof(*ps), GFP_KERNEL); + if (!ps) + return -ENOMEM; + ps->netdev = dev_get_by_name(&init_net, pdata->netdev); if (!ps->netdev) return -EPROBE_DEFER; diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c index cdb02991f249..9ee1c5016784 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c @@ -755,7 +755,7 @@ void aq_nic_get_stats(struct aq_nic_s *self, u64 *data) count = 0U; for (i = 0U, aq_vec = self->aq_vec[0]; - self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) { + aq_vec && self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) { data += count; aq_vec_get_sw_stats(aq_vec, data, &count); } @@ -959,8 +959,10 @@ void aq_nic_free_hot_resources(struct aq_nic_s *self) goto err_exit; for (i = AQ_DIMOF(self->aq_vec); i--;) { - if (self->aq_vec[i]) + if (self->aq_vec[i]) { aq_vec_free(self->aq_vec[i]); + self->aq_vec[i] = NULL; + } } err_exit:; diff --git a/drivers/net/ethernet/atheros/alx/main.c b/drivers/net/ethernet/atheros/alx/main.c index a8c2db881b75..567ee54504bc 100644 --- a/drivers/net/ethernet/atheros/alx/main.c +++ b/drivers/net/ethernet/atheros/alx/main.c @@ -838,7 +838,7 @@ static int alx_enable_msix(struct alx_priv *alx) err = pci_alloc_irq_vectors(alx->hw.pdev, num_vec, num_vec, PCI_IRQ_MSIX); - if (err) { + if (err < 0) { netdev_warn(alx->dev, "Enabling MSI-X interrupts failed!\n"); return err; } @@ -904,7 +904,7 @@ static int alx_init_intr(struct alx_priv *alx) ret = pci_alloc_irq_vectors(alx->hw.pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY); - if (ret) + if (ret < 0) return ret; alx->num_vec = 1; diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index b3ba66032980..b56c54d68d5e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -3000,7 +3000,8 @@ static int bnxt_alloc_ntp_fltrs(struct bnxt *bp) INIT_HLIST_HEAD(&bp->ntp_fltr_hash_tbl[i]); bp->ntp_fltr_count = 0; - bp->ntp_fltr_bmap = kzalloc(BITS_TO_LONGS(BNXT_NTP_FLTR_MAX_FLTR), + bp->ntp_fltr_bmap = kcalloc(BITS_TO_LONGS(BNXT_NTP_FLTR_MAX_FLTR), + sizeof(long), GFP_KERNEL); if (!bp->ntp_fltr_bmap) diff --git a/drivers/net/ethernet/brocade/bna/bfa_ioc.c b/drivers/net/ethernet/brocade/bna/bfa_ioc.c index 0f6811860ad5..a36e38676640 100644 --- a/drivers/net/ethernet/brocade/bna/bfa_ioc.c +++ b/drivers/net/ethernet/brocade/bna/bfa_ioc.c @@ -2845,7 +2845,7 @@ bfa_ioc_get_adapter_optrom_ver(struct bfa_ioc *ioc, char *optrom_ver) static void bfa_ioc_get_adapter_manufacturer(struct bfa_ioc *ioc, char *manufacturer) { - memcpy(manufacturer, BFA_MFG_NAME, BFA_ADAPTER_MFG_NAME_LEN); + strncpy(manufacturer, BFA_MFG_NAME, BFA_ADAPTER_MFG_NAME_LEN); } static void diff --git a/drivers/net/ethernet/brocade/bna/bnad_ethtool.c b/drivers/net/ethernet/brocade/bna/bnad_ethtool.c index 286593922139..31032de5843b 100644 --- a/drivers/net/ethernet/brocade/bna/bnad_ethtool.c +++ b/drivers/net/ethernet/brocade/bna/bnad_ethtool.c @@ -547,8 +547,8 @@ bnad_get_strings(struct net_device *netdev, u32 stringset, u8 *string) for (i = 0; i < BNAD_ETHTOOL_STATS_NUM; i++) { BUG_ON(!(strlen(bnad_net_stats_strings[i]) < ETH_GSTRING_LEN)); - memcpy(string, bnad_net_stats_strings[i], - ETH_GSTRING_LEN); + strncpy(string, bnad_net_stats_strings[i], + ETH_GSTRING_LEN); string += ETH_GSTRING_LEN; } bmap = bna_tx_rid_mask(&bnad->bna); diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h index 163543b1ea0b..862e0083420b 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h @@ -108,6 +108,12 @@ enum { PAUSE_AUTONEG = 1 << 2 }; +enum { + FEC_AUTO = 1 << 0, /* IEEE 802.3 "automatic" */ + FEC_RS = 1 << 1, /* Reed-Solomon */ + FEC_BASER_RS = 1 << 2 /* BaseR/Reed-Solomon */ +}; + struct port_stats { u64 tx_octets; /* total # of octets in good frames */ u64 tx_frames; /* all good frames */ @@ -432,6 +438,9 @@ struct link_config { unsigned int speed; /* actual link speed */ unsigned char requested_fc; /* flow control user has requested */ unsigned char fc; /* actual link flow control */ + unsigned char auto_fec; /* Forward Error Correction: */ + unsigned char requested_fec; /* "automatic" (IEEE 802.3), */ + unsigned char fec; /* requested, and actual in use */ unsigned char autoneg; /* autonegotiating? */ unsigned char link_ok; /* link up? */ unsigned char link_down_rc; /* link down reason */ diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c index 0de8eb72325c..aded42b96f6d 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c @@ -3707,7 +3707,8 @@ int t4_link_l1cfg(struct adapter *adap, unsigned int mbox, unsigned int port, struct link_config *lc) { struct fw_port_cmd c; - unsigned int fc = 0, mdi = FW_PORT_CAP_MDI_V(FW_PORT_CAP_MDI_AUTO); + unsigned int mdi = FW_PORT_CAP_MDI_V(FW_PORT_CAP_MDI_AUTO); + unsigned int fc = 0, fec = 0, fw_fec = 0; lc->link_ok = 0; if (lc->requested_fc & PAUSE_RX) @@ -3715,6 +3716,13 @@ int t4_link_l1cfg(struct adapter *adap, unsigned int mbox, unsigned int port, if (lc->requested_fc & PAUSE_TX) fc |= FW_PORT_CAP_FC_TX; + fec = lc->requested_fec & FEC_AUTO ? lc->auto_fec : lc->requested_fec; + + if (fec & FEC_RS) + fw_fec |= FW_PORT_CAP_FEC_RS; + if (fec & FEC_BASER_RS) + fw_fec |= FW_PORT_CAP_FEC_BASER_RS; + memset(&c, 0, sizeof(c)); c.op_to_portid = cpu_to_be32(FW_CMD_OP_V(FW_PORT_CMD) | FW_CMD_REQUEST_F | FW_CMD_EXEC_F | @@ -3725,13 +3733,15 @@ int t4_link_l1cfg(struct adapter *adap, unsigned int mbox, unsigned int port, if (!(lc->supported & FW_PORT_CAP_ANEG)) { c.u.l1cfg.rcap = cpu_to_be32((lc->supported & ADVERT_MASK) | - fc); + fc | fw_fec); lc->fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX); } else if (lc->autoneg == AUTONEG_DISABLE) { - c.u.l1cfg.rcap = cpu_to_be32(lc->requested_speed | fc | mdi); + c.u.l1cfg.rcap = cpu_to_be32(lc->requested_speed | fc | + fw_fec | mdi); lc->fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX); } else - c.u.l1cfg.rcap = cpu_to_be32(lc->advertising | fc | mdi); + c.u.l1cfg.rcap = cpu_to_be32(lc->advertising | fc | + fw_fec | mdi); return t4_wr_mbox(adap, mbox, &c, sizeof(c), NULL); } @@ -7407,13 +7417,26 @@ static void get_pci_mode(struct adapter *adapter, struct pci_params *p) * Initializes the SW state maintained for each link, including the link's * capabilities and default speed/flow-control/autonegotiation settings. */ -static void init_link_config(struct link_config *lc, unsigned int caps) +static void init_link_config(struct link_config *lc, unsigned int pcaps, + unsigned int acaps) { - lc->supported = caps; + lc->supported = pcaps; lc->lp_advertising = 0; lc->requested_speed = 0; lc->speed = 0; lc->requested_fc = lc->fc = PAUSE_RX | PAUSE_TX; + lc->auto_fec = 0; + + /* For Forward Error Control, we default to whatever the Firmware + * tells us the Link is currently advertising. + */ + if (acaps & FW_PORT_CAP_FEC_RS) + lc->auto_fec |= FEC_RS; + if (acaps & FW_PORT_CAP_FEC_BASER_RS) + lc->auto_fec |= FEC_BASER_RS; + lc->requested_fec = FEC_AUTO; + lc->fec = lc->auto_fec; + if (lc->supported & FW_PORT_CAP_ANEG) { lc->advertising = lc->supported & ADVERT_MASK; lc->autoneg = AUTONEG_ENABLE; @@ -7991,7 +8014,8 @@ int t4_init_portinfo(struct port_info *pi, int mbox, pi->port_type = FW_PORT_CMD_PTYPE_G(ret); pi->mod_type = FW_PORT_MOD_TYPE_NA; - init_link_config(&pi->link_cfg, be16_to_cpu(c.u.info.pcap)); + init_link_config(&pi->link_cfg, be16_to_cpu(c.u.info.pcap), + be16_to_cpu(c.u.info.acap)); return 0; } diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h b/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h index 8f8c079d0d2b..251a35e9795c 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h +++ b/drivers/net/ethernet/chelsio/cxgb4/t4fw_api.h @@ -2263,9 +2263,9 @@ enum fw_port_cap { FW_PORT_CAP_ANEG = 0x0100, FW_PORT_CAP_MDIX = 0x0200, FW_PORT_CAP_MDIAUTO = 0x0400, - FW_PORT_CAP_FEC = 0x0800, - FW_PORT_CAP_TECHKR = 0x1000, - FW_PORT_CAP_TECHKX4 = 0x2000, + FW_PORT_CAP_FEC_RS = 0x0800, + FW_PORT_CAP_FEC_BASER_RS = 0x1000, + FW_PORT_CAP_FEC_RESERVED = 0x2000, FW_PORT_CAP_802_3_PAUSE = 0x4000, FW_PORT_CAP_802_3_ASM_DIR = 0x8000, }; diff --git a/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c b/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c index 829be21f97b2..28ea0af89aef 100644 --- a/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c +++ b/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c @@ -765,7 +765,7 @@ int ql_core_dump(struct ql_adapter *qdev, struct ql_mpi_coredump *mpi_coredump) sizeof(struct mpi_coredump_global_header); mpi_coredump->mpi_global_header.imageSize = sizeof(struct ql_mpi_coredump); - memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", + strncpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", sizeof(mpi_coredump->mpi_global_header.idString)); /* Get generic NIC reg dump */ @@ -1255,7 +1255,7 @@ static void ql_gen_reg_dump(struct ql_adapter *qdev, sizeof(struct mpi_coredump_global_header); mpi_coredump->mpi_global_header.imageSize = sizeof(struct ql_reg_dump); - memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", + strncpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", sizeof(mpi_coredump->mpi_global_header.idString)); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c index 39be96779145..22f910795be4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c @@ -70,11 +70,8 @@ static int stmmac_pci_find_phy_addr(struct stmmac_pci_info *info) return -ENODEV; } -static void stmmac_default_data(struct plat_stmmacenet_data *plat) +static void common_default_data(struct plat_stmmacenet_data *plat) { - plat->bus_id = 1; - plat->phy_addr = 0; - plat->interface = PHY_INTERFACE_MODE_GMII; plat->clk_csr = 2; /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ plat->has_gmac = 1; plat->force_sf_dma_mode = 1; @@ -82,10 +79,6 @@ static void stmmac_default_data(struct plat_stmmacenet_data *plat) plat->mdio_bus_data->phy_reset = NULL; plat->mdio_bus_data->phy_mask = 0; - plat->dma_cfg->pbl = 32; - plat->dma_cfg->pblx8 = true; - /* TODO: AXI */ - /* Set default value for multicast hash bins */ plat->multicast_filter_bins = HASH_TABLE_SIZE; @@ -107,12 +100,29 @@ static void stmmac_default_data(struct plat_stmmacenet_data *plat) plat->rx_queues_cfg[0].pkt_route = 0x0; } +static void stmmac_default_data(struct plat_stmmacenet_data *plat) +{ + /* Set common default data first */ + common_default_data(plat); + + plat->bus_id = 1; + plat->phy_addr = 0; + plat->interface = PHY_INTERFACE_MODE_GMII; + + plat->dma_cfg->pbl = 32; + plat->dma_cfg->pblx8 = true; + /* TODO: AXI */ +} + static int quark_default_data(struct plat_stmmacenet_data *plat, struct stmmac_pci_info *info) { struct pci_dev *pdev = info->pdev; int ret; + /* Set common default data first */ + common_default_data(plat); + /* * Refuse to load the driver and register net device if MAC controller * does not connect to any PHY interface. @@ -124,27 +134,12 @@ static int quark_default_data(struct plat_stmmacenet_data *plat, plat->bus_id = PCI_DEVID(pdev->bus->number, pdev->devfn); plat->phy_addr = ret; plat->interface = PHY_INTERFACE_MODE_RMII; - plat->clk_csr = 2; - plat->has_gmac = 1; - plat->force_sf_dma_mode = 1; - - plat->mdio_bus_data->phy_reset = NULL; - plat->mdio_bus_data->phy_mask = 0; plat->dma_cfg->pbl = 16; plat->dma_cfg->pblx8 = true; plat->dma_cfg->fixed_burst = 1; /* AXI (TODO) */ - /* Set default value for multicast hash bins */ - plat->multicast_filter_bins = HASH_TABLE_SIZE; - - /* Set default value for unicast filter entries */ - plat->unicast_filter_entries = 1; - - /* Set the maxmtu to a default of JUMBO_LEN */ - plat->maxmtu = JUMBO_LEN; - return 0; } diff --git a/drivers/net/hamradio/yam.c b/drivers/net/hamradio/yam.c index b6891ada1d7b..7a7c5224a336 100644 --- a/drivers/net/hamradio/yam.c +++ b/drivers/net/hamradio/yam.c @@ -976,12 +976,10 @@ static int yam_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) case SIOCYAMSMCS: if (netif_running(dev)) return -EINVAL; /* Cannot change this parameter when up */ - if ((ym = kmalloc(sizeof(struct yamdrv_ioctl_mcs), GFP_KERNEL)) == NULL) - return -ENOBUFS; - if (copy_from_user(ym, ifr->ifr_data, sizeof(struct yamdrv_ioctl_mcs))) { - kfree(ym); - return -EFAULT; - } + ym = memdup_user(ifr->ifr_data, + sizeof(struct yamdrv_ioctl_mcs)); + if (IS_ERR(ym)) + return PTR_ERR(ym); if (ym->bitrate > YAM_MAXBITRATE) { kfree(ym); return -EINVAL; diff --git a/drivers/net/hippi/rrunner.c b/drivers/net/hippi/rrunner.c index dd7fc6659ad4..365c4d987a01 100644 --- a/drivers/net/hippi/rrunner.c +++ b/drivers/net/hippi/rrunner.c @@ -1615,17 +1615,14 @@ static int rr_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) return -EPERM; } - image = kmalloc(EEPROM_WORDS * sizeof(u32), GFP_KERNEL); - oldimage = kmalloc(EEPROM_WORDS * sizeof(u32), GFP_KERNEL); - if (!image || !oldimage) { - error = -ENOMEM; - goto wf_out; - } + image = memdup_user(rq->ifr_data, EEPROM_BYTES); + if (IS_ERR(image)) + return PTR_ERR(image); - error = copy_from_user(image, rq->ifr_data, EEPROM_BYTES); - if (error) { - error = -EFAULT; - goto wf_out; + oldimage = kmalloc(EEPROM_BYTES, GFP_KERNEL); + if (!oldimage) { + kfree(image); + return -ENOMEM; } if (rrpriv->fw_running){ diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index bb3f71f9fbde..b5cec1824a78 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1088,6 +1088,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) u16 n = 0, index, ndplen; u8 ready2send = 0; u32 delayed_ndp_size; + size_t padding_count; /* When our NDP gets written in cdc_ncm_ndp(), then skb_out->len gets updated * accordingly. Otherwise, we should check here. @@ -1244,11 +1245,13 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) * a ZLP after full sized NTBs. */ if (!(dev->driver_info->flags & FLAG_SEND_ZLP) && - skb_out->len > ctx->min_tx_pkt) - memset(skb_put(skb_out, ctx->tx_max - skb_out->len), 0, - ctx->tx_max - skb_out->len); - else if (skb_out->len < ctx->tx_max && (skb_out->len % dev->maxpacket) == 0) + skb_out->len > ctx->min_tx_pkt) { + padding_count = ctx->tx_max - skb_out->len; + memset(skb_put(skb_out, padding_count), 0, padding_count); + } else if (skb_out->len < ctx->tx_max && + (skb_out->len % dev->maxpacket) == 0) { *skb_put(skb_out, 1) = 0; /* force short packet */ + } /* set final frame length */ nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data; diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 650f1b1797ad..101ced4c84be 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -13,6 +13,17 @@ menuconfig NVMEM if NVMEM +config NVMEM_IMX_IIM + tristate "i.MX IC Identification Module support" + depends on ARCH_MXC || COMPILE_TEST + help + This is a driver for the IC Identification Module (IIM) available on + i.MX SoCs, providing access to 4 Kbits of programmable + eFuses. + + This driver can also be built as a module. If so, the module + will be called nvmem-imx-iim. + config NVMEM_IMX_OCOTP tristate "i.MX6 On-Chip OTP Controller support" depends on SOC_IMX6 || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 86e45995fdad..173140658693 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -8,6 +8,8 @@ nvmem_core-y := core.o # Devices obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o nvmem-bcm-ocotp-y := bcm-ocotp.o +obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-imx-iim.o +nvmem-imx-iim-y := imx-iim.o obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o nvmem-imx-ocotp-y := imx-ocotp.o obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 408b521ee520..8c830a80a648 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -468,7 +468,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) np = config->dev->of_node; nvmem->dev.of_node = np; dev_set_name(&nvmem->dev, "%s%d", - config->name ? : "nvmem", config->id); + config->name ? : "nvmem", + config->name ? config->id : nvmem->id); nvmem->read_only = of_property_read_bool(np, "read-only") | config->read_only; diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c new file mode 100644 index 000000000000..52ff65e0673f --- /dev/null +++ b/drivers/nvmem/imx-iim.c @@ -0,0 +1,173 @@ +/* + * i.MX IIM driver + * + * Copyright (c) 2017 Pengutronix, Michael Grzeschik <m.grzeschik@pengutronix.de> + * + * Based on the barebox iim driver, + * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, + * Orex Computed Radiography + * + * 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. + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#define IIM_BANK_BASE(n) (0x800 + 0x400 * (n)) + +struct imx_iim_drvdata { + unsigned int nregs; +}; + +struct iim_priv { + void __iomem *base; + struct clk *clk; + struct nvmem_config nvmem; +}; + +static int imx_iim_read(void *context, unsigned int offset, + void *buf, size_t bytes) +{ + struct iim_priv *iim = context; + int i, ret; + u8 *buf8 = buf; + + ret = clk_prepare_enable(iim->clk); + if (ret) + return ret; + + for (i = offset; i < offset + bytes; i++) { + int bank = i >> 5; + int reg = i & 0x1f; + + *buf8++ = readl(iim->base + IIM_BANK_BASE(bank) + reg * 4); + } + + clk_disable_unprepare(iim->clk); + + return 0; +} + +static struct imx_iim_drvdata imx27_drvdata = { + .nregs = 2 * 32, +}; + +static struct imx_iim_drvdata imx25_imx31_imx35_drvdata = { + .nregs = 3 * 32, +}; + +static struct imx_iim_drvdata imx51_drvdata = { + .nregs = 4 * 32, +}; + +static struct imx_iim_drvdata imx53_drvdata = { + .nregs = 4 * 32 + 16, +}; + +static const struct of_device_id imx_iim_dt_ids[] = { + { + .compatible = "fsl,imx25-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx27-iim", + .data = &imx27_drvdata, + }, { + .compatible = "fsl,imx31-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx35-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx51-iim", + .data = &imx51_drvdata, + }, { + .compatible = "fsl,imx53-iim", + .data = &imx53_drvdata, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, imx_iim_dt_ids); + +static int imx_iim_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct resource *res; + struct iim_priv *iim; + struct nvmem_device *nvmem; + struct nvmem_config *cfg; + const struct imx_iim_drvdata *drvdata = NULL; + + iim = devm_kzalloc(dev, sizeof(*iim), GFP_KERNEL); + if (!iim) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iim->base = devm_ioremap_resource(dev, res); + if (IS_ERR(iim->base)) + return PTR_ERR(iim->base); + + of_id = of_match_device(imx_iim_dt_ids, dev); + if (!of_id) + return -ENODEV; + + drvdata = of_id->data; + + iim->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(iim->clk)) + return PTR_ERR(iim->clk); + + cfg = &iim->nvmem; + + cfg->name = "imx-iim", + cfg->read_only = true, + cfg->word_size = 1, + cfg->stride = 1, + cfg->owner = THIS_MODULE, + cfg->reg_read = imx_iim_read, + cfg->dev = dev; + cfg->size = drvdata->nregs; + cfg->priv = iim; + + nvmem = nvmem_register(cfg); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int imx_iim_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static struct platform_driver imx_iim_driver = { + .probe = imx_iim_probe, + .remove = imx_iim_remove, + .driver = { + .name = "imx-iim", + .of_match_table = imx_iim_dt_ids, + }, +}; +module_platform_driver(imx_iim_driver); + +MODULE_AUTHOR("Michael Grzeschik <m.grzeschik@pengutronix.de>"); +MODULE_DESCRIPTION("i.MX IIM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index b8ca1e677b01..193ca8fd350a 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -7,6 +7,9 @@ * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, * Orex Computed Radiography * + * Write support based on the fsl_otp driver, + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc + * * 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. @@ -24,14 +27,88 @@ #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/delay.h> + +#define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the + * OTP Bank0 Word0 + */ +#define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr + * of two consecutive OTP words. + */ + +#define IMX_OCOTP_ADDR_CTRL 0x0000 +#define IMX_OCOTP_ADDR_CTRL_SET 0x0004 +#define IMX_OCOTP_ADDR_CTRL_CLR 0x0008 +#define IMX_OCOTP_ADDR_TIMING 0x0010 +#define IMX_OCOTP_ADDR_DATA 0x0020 + +#define IMX_OCOTP_BM_CTRL_ADDR 0x0000007F +#define IMX_OCOTP_BM_CTRL_BUSY 0x00000100 +#define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 +#define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400 + +#define DEF_RELAX 20 /* > 16.5ns */ +#define IMX_OCOTP_WR_UNLOCK 0x3E770000 +#define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA + +static DEFINE_MUTEX(ocotp_mutex); struct ocotp_priv { struct device *dev; struct clk *clk; void __iomem *base; unsigned int nregs; + struct nvmem_config *config; }; +static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags) +{ + int count; + u32 c, mask; + + mask = IMX_OCOTP_BM_CTRL_BUSY | IMX_OCOTP_BM_CTRL_ERROR | flags; + + for (count = 10000; count >= 0; count--) { + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & mask)) + break; + cpu_relax(); + } + + if (count < 0) { + /* HW_OCOTP_CTRL[ERROR] will be set under the following + * conditions: + * - A write is performed to a shadow register during a shadow + * reload (essentially, while HW_OCOTP_CTRL[RELOAD_SHADOWS] is + * set. In addition, the contents of the shadow register shall + * not be updated. + * - A write is performed to a shadow register which has been + * locked. + * - A read is performed to from a shadow register which has + * been read locked. + * - A program is performed to a fuse word which has been locked + * - A read is performed to from a fuse word which has been read + * locked. + */ + if (c & IMX_OCOTP_BM_CTRL_ERROR) + return -EPERM; + return -ETIMEDOUT; + } + + return 0; +} + +static void imx_ocotp_clr_err_if_set(void __iomem *base) +{ + u32 c; + + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & IMX_OCOTP_BM_CTRL_ERROR)) + return; + + writel(IMX_OCOTP_BM_CTRL_ERROR, base + IMX_OCOTP_ADDR_CTRL_CLR); +} + static int imx_ocotp_read(void *context, unsigned int offset, void *val, size_t bytes) { @@ -47,26 +124,188 @@ static int imx_ocotp_read(void *context, unsigned int offset, if (count > (priv->nregs - index)) count = priv->nregs - index; + mutex_lock(&ocotp_mutex); + ret = clk_prepare_enable(priv->clk); if (ret < 0) { + mutex_unlock(&ocotp_mutex); dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); return ret; } - for (i = index; i < (index + count); i++) - *buf++ = readl(priv->base + 0x400 + i * 0x10); + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during read setup\n"); + goto read_end; + } + + for (i = index; i < (index + count); i++) { + *buf++ = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 + + i * IMX_OCOTP_OFFSET_PER_WORD); + + /* 47.3.1.2 + * For "read locked" registers 0xBADABADA will be returned and + * HW_OCOTP_CTRL[ERROR] will be set. It must be cleared by + * software before any new write, read or reload access can be + * issued + */ + if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL) + imx_ocotp_clr_err_if_set(priv->base); + } + ret = 0; + +read_end: clk_disable_unprepare(priv->clk); + mutex_unlock(&ocotp_mutex); + return ret; +} - return 0; +static int imx_ocotp_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct ocotp_priv *priv = context; + u32 *buf = val; + int ret; + + unsigned long clk_rate = 0; + unsigned long strobe_read, relax, strobe_prog; + u32 timing = 0; + u32 ctrl; + u8 waddr; + + /* allow only writing one complete OTP word at a time */ + if ((bytes != priv->config->word_size) || + (offset % priv->config->word_size)) + return -EINVAL; + + mutex_lock(&ocotp_mutex); + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + mutex_unlock(&ocotp_mutex); + dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); + return ret; + } + + /* 47.3.1.3.1 + * Program HW_OCOTP_TIMING[STROBE_PROG] and HW_OCOTP_TIMING[RELAX] + * fields with timing values to match the current frequency of the + * ipg_clk. OTP writes will work at maximum bus frequencies as long + * as the HW_OCOTP_TIMING parameters are set correctly. + */ + clk_rate = clk_get_rate(priv->clk); + + relax = clk_rate / (1000000000 / DEF_RELAX) - 1; + strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; + strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; + + timing = strobe_prog & 0x00000FFF; + timing |= (relax << 12) & 0x0000F000; + timing |= (strobe_read << 16) & 0x003F0000; + + writel(timing, priv->base + IMX_OCOTP_ADDR_TIMING); + + /* 47.3.1.3.2 + * Check that HW_OCOTP_CTRL[BUSY] and HW_OCOTP_CTRL[ERROR] are clear. + * Overlapped accesses are not supported by the controller. Any pending + * write or reload must be completed before a write access can be + * requested. + */ + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during timing setup\n"); + goto write_end; + } + + /* 47.3.1.3.3 + * Write the requested address to HW_OCOTP_CTRL[ADDR] and program the + * unlock code into HW_OCOTP_CTRL[WR_UNLOCK]. This must be programmed + * for each write access. The lock code is documented in the register + * description. Both the unlock code and address can be written in the + * same operation. + */ + /* OTP write/read address specifies one of 128 word address locations */ + waddr = offset / 4; + + ctrl = readl(priv->base + IMX_OCOTP_ADDR_CTRL); + ctrl &= ~IMX_OCOTP_BM_CTRL_ADDR; + ctrl |= waddr & IMX_OCOTP_BM_CTRL_ADDR; + ctrl |= IMX_OCOTP_WR_UNLOCK; + + writel(ctrl, priv->base + IMX_OCOTP_ADDR_CTRL); + + /* 47.3.1.3.4 + * Write the data to the HW_OCOTP_DATA register. This will automatically + * set HW_OCOTP_CTRL[BUSY] and clear HW_OCOTP_CTRL[WR_UNLOCK]. To + * protect programming same OTP bit twice, before program OCOTP will + * automatically read fuse value in OTP and use read value to mask + * program data. The controller will use masked program data to program + * a 32-bit word in the OTP per the address in HW_OCOTP_CTRL[ADDR]. Bit + * fields with 1's will result in that OTP bit being programmed. Bit + * fields with 0's will be ignored. At the same time that the write is + * accepted, the controller makes an internal copy of + * HW_OCOTP_CTRL[ADDR] which cannot be updated until the next write + * sequence is initiated. This copy guarantees that erroneous writes to + * HW_OCOTP_CTRL[ADDR] will not affect an active write operation. It + * should also be noted that during the programming HW_OCOTP_DATA will + * shift right (with zero fill). This shifting is required to program + * the OTP serially. During the write operation, HW_OCOTP_DATA cannot be + * modified. + */ + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA); + + /* 47.4.1.4.5 + * Once complete, the controller will clear BUSY. A write request to a + * protected or locked region will result in no OTP access and no + * setting of HW_OCOTP_CTRL[BUSY]. In addition HW_OCOTP_CTRL[ERROR] will + * be set. It must be cleared by software before any new write access + * can be issued. + */ + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + if (ret == -EPERM) { + dev_err(priv->dev, "failed write to locked region"); + imx_ocotp_clr_err_if_set(priv->base); + } else { + dev_err(priv->dev, "timeout during data write\n"); + } + goto write_end; + } + + /* 47.3.1.4 + * Write Postamble: Due to internal electrical characteristics of the + * OTP during writes, all OTP operations following a write must be + * separated by 2 us after the clearing of HW_OCOTP_CTRL_BUSY following + * the write. + */ + udelay(2); + + /* reload all shadow registers */ + writel(IMX_OCOTP_BM_CTRL_REL_SHADOWS, + priv->base + IMX_OCOTP_ADDR_CTRL_SET); + ret = imx_ocotp_wait_for_busy(priv->base, + IMX_OCOTP_BM_CTRL_REL_SHADOWS); + if (ret < 0) { + dev_err(priv->dev, "timeout during shadow register reload\n"); + goto write_end; + } + +write_end: + clk_disable_unprepare(priv->clk); + mutex_unlock(&ocotp_mutex); + if (ret < 0) + return ret; + return bytes; } static struct nvmem_config imx_ocotp_nvmem_config = { .name = "imx-ocotp", - .read_only = true, + .read_only = false, .word_size = 4, .stride = 4, .owner = THIS_MODULE, .reg_read = imx_ocotp_read, + .reg_write = imx_ocotp_write, }; static const struct of_device_id imx_ocotp_dt_ids[] = { @@ -74,6 +313,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6sl-ocotp", (void *)64 }, { .compatible = "fsl,imx6sx-ocotp", (void *)128 }, { .compatible = "fsl,imx6ul-ocotp", (void *)128 }, + { .compatible = "fsl,imx7d-ocotp", (void *)64 }, { }, }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); @@ -90,12 +330,14 @@ static int imx_ocotp_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); - priv->clk = devm_clk_get(&pdev->dev, NULL); + priv->clk = devm_clk_get(dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); @@ -104,7 +346,9 @@ static int imx_ocotp_probe(struct platform_device *pdev) imx_ocotp_nvmem_config.size = 4 * priv->nregs; imx_ocotp_nvmem_config.dev = dev; imx_ocotp_nvmem_config.priv = priv; + priv->config = &imx_ocotp_nvmem_config; nvmem = nvmem_register(&imx_ocotp_nvmem_config); + if (IS_ERR(nvmem)) return PTR_ERR(nvmem); diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c index 1567ccca8de3..0d6648be93b8 100644 --- a/drivers/nvmem/sunxi_sid.c +++ b/drivers/nvmem/sunxi_sid.c @@ -17,13 +17,24 @@ #include <linux/device.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/module.h> #include <linux/nvmem-provider.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/random.h> +/* Registers and special values for doing register-based SID readout on H3 */ +#define SUN8I_SID_PRCTL 0x40 +#define SUN8I_SID_RDKEY 0x60 + +#define SUN8I_SID_OFFSET_MASK 0x1FF +#define SUN8I_SID_OFFSET_SHIFT 16 +#define SUN8I_SID_OP_LOCK (0xAC << 8) +#define SUN8I_SID_READ BIT(1) + static struct nvmem_config econfig = { .name = "sunxi-sid", .read_only = true, @@ -32,8 +43,15 @@ static struct nvmem_config econfig = { .owner = THIS_MODULE, }; +struct sunxi_sid_cfg { + u32 value_offset; + u32 size; + bool need_register_readout; +}; + struct sunxi_sid { void __iomem *base; + u32 value_offset; }; /* We read the entire key, due to a 32 bit read alignment requirement. Since we @@ -58,12 +76,36 @@ static int sunxi_sid_read(void *context, unsigned int offset, struct sunxi_sid *sid = context; u8 *buf = val; + /* Offset the read operation to the real position of SID */ + offset += sid->value_offset; + while (bytes--) *buf++ = sunxi_sid_read_byte(sid, offset++); return 0; } +static int sun8i_sid_register_readout(const struct sunxi_sid *sid, + const unsigned int word) +{ + u32 reg_val; + int ret; + + /* Set word, lock access, and set read command */ + reg_val = (word & SUN8I_SID_OFFSET_MASK) + << SUN8I_SID_OFFSET_SHIFT; + reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; + writel(reg_val, sid->base + SUN8I_SID_PRCTL); + + ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, + !(reg_val & SUN8I_SID_READ), 100, 250000); + if (ret) + return ret; + + writel(0, sid->base + SUN8I_SID_PRCTL); + return 0; +} + static int sunxi_sid_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -72,18 +114,42 @@ static int sunxi_sid_probe(struct platform_device *pdev) struct sunxi_sid *sid; int ret, i, size; char *randomness; + const struct sunxi_sid_cfg *cfg; sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); if (!sid) return -ENOMEM; + cfg = of_device_get_match_data(dev); + if (!cfg) + return -EINVAL; + sid->value_offset = cfg->value_offset; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); sid->base = devm_ioremap_resource(dev, res); if (IS_ERR(sid->base)) return PTR_ERR(sid->base); - size = resource_size(res) - 1; - econfig.size = resource_size(res); + size = cfg->size; + + if (cfg->need_register_readout) { + /* + * H3's SID controller have a bug that the value at 0x200 + * offset is not the correct value when the hardware is reseted. + * However, after doing a register-based read operation, the + * value become right. + * Do a full read operation here, but ignore its value + * (as it's more fast to read by direct MMIO value than + * with registers) + */ + for (i = 0; i < (size >> 2); i++) { + ret = sun8i_sid_register_readout(sid, i); + if (ret) + return ret; + } + } + + econfig.size = size; econfig.dev = dev; econfig.reg_read = sunxi_sid_read; econfig.priv = sid; @@ -119,9 +185,24 @@ static int sunxi_sid_remove(struct platform_device *pdev) return nvmem_unregister(nvmem); } +static const struct sunxi_sid_cfg sun4i_a10_cfg = { + .size = 0x10, +}; + +static const struct sunxi_sid_cfg sun7i_a20_cfg = { + .size = 0x200, +}; + +static const struct sunxi_sid_cfg sun8i_h3_cfg = { + .value_offset = 0x200, + .size = 0x100, + .need_register_readout = true, +}; + static const struct of_device_id sunxi_sid_of_match[] = { - { .compatible = "allwinner,sun4i-a10-sid" }, - { .compatible = "allwinner,sun7i-a20-sid" }, + { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, {/* sentinel */}, }; MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 005cadb7a3f8..afaf7b643eeb 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -439,6 +439,25 @@ config PHY_STIH407_USB Enable this support to enable the picoPHY device used by USB2 and USB3 controllers on STMicroelectronics STiH407 SoC families. +config PHY_QCOM_QMP + tristate "Qualcomm QMP PHY Driver" + depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) + select GENERIC_PHY + help + Enable this to support the QMP PHY transceiver that is used + with controllers such as PCIe, UFS, and USB on Qualcomm chips. + +config PHY_QCOM_QUSB2 + tristate "Qualcomm QUSB2 PHY Driver" + depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on NVMEM || !NVMEM + select GENERIC_PHY + help + Enable this to support the HighSpeed QUSB2 PHY transceiver for USB + controllers on Qualcomm chips. This driver supports the high-speed + PHY which is usually paired with either the ChipIdea or Synopsys DWC3 + USB IPs on MSM SOCs. + config PHY_QCOM_UFS tristate "Qualcomm UFS PHY driver" depends on OF && ARCH_QCOM diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index dd8f3b5d2918..f8047b4639fa 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -50,6 +50,8 @@ obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o obj-$(CONFIG_PHY_XGENE) += phy-xgene.o obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o +obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o +obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o diff --git a/drivers/phy/phy-bcm-ns-usb3.c b/drivers/phy/phy-bcm-ns-usb3.c index f420fa4bebfc..22b5e7047fa6 100644 --- a/drivers/phy/phy-bcm-ns-usb3.c +++ b/drivers/phy/phy-bcm-ns-usb3.c @@ -2,6 +2,7 @@ * Broadcom Northstar USB 3.0 PHY Driver * * Copyright (C) 2016 RafaÅ‚ MiÅ‚ecki <rafal@milecki.pl> + * Copyright (C) 2016 Broadcom * * All magic values used for initialization (and related comments) were obtained * from Broadcom's SDK: @@ -23,6 +24,23 @@ #define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ +#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f +#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 +#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 +#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 + +/* Registers of PLL30 block */ +#define BCM_NS_USB3_PLL_CONTROL 0x01 +#define BCM_NS_USB3_PLLA_CONTROL0 0x0a +#define BCM_NS_USB3_PLLA_CONTROL1 0x0b + +/* Registers of TX PMD block */ +#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 + +/* Registers of PIPE block */ +#define BCM_NS_USB3_LFPS_CMP 0x02 +#define BCM_NS_USB3_LFPS_DEGLITCH 0x03 + enum bcm_ns_family { BCM_NS_UNKNOWN, BCM_NS_AX, @@ -76,8 +94,10 @@ static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); } -static int bcm_ns_usb3_mii_mng_write32(struct bcm_ns_usb3 *usb3, u32 value) +static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) { + u32 tmp = 0; int err; err = bcm_ns_usb3_mii_mng_wait_idle(usb3); @@ -86,7 +106,11 @@ static int bcm_ns_usb3_mii_mng_write32(struct bcm_ns_usb3 *usb3, u32 value) return err; } - writel(value, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + /* TODO: Use a proper MDIO bus layer */ + tmp |= 0x58020000; /* Magic value for MDIO PHY write */ + tmp |= reg << 18; + tmp |= value; + writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); return 0; } @@ -102,21 +126,22 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) udelay(2); /* USB3 PLL Block */ - err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000); + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); if (err < 0) return err; /* Assert Ana_Pllseq start */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x58061000); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); /* Assert CML Divider ratio to 26 */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); /* Asserting PLL Reset */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x582ec000); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); /* Deaaserting PLL Reset */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x582e8000); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); /* Waiting MII Mgt interface idle */ bcm_ns_usb3_mii_mng_wait_idle(usb3); @@ -125,22 +150,24 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) writel(0, usb3->dmp + BCMA_RESET_CTL); /* PLL frequency monitor enable */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x58069000); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); /* PIPE Block */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8060); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PIPE_BLOCK); /* CMPMAX & CMPMINTH setting */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x580af30d); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); /* DEGLITCH MIN & MAX setting */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x580e6302); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); /* TXPMD block */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); /* Enabling SSC */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); /* Waiting MII Mgt interface idle */ bcm_ns_usb3_mii_mng_wait_idle(usb3); @@ -159,22 +186,24 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) udelay(2); /* PLL30 block */ - err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000); + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); if (err < 0) return err; - bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); - bcm_ns_usb3_mii_mng_write32(usb3, 0x587e80e0); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); - bcm_ns_usb3_mii_mng_write32(usb3, 0x580a009c); + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); /* Enable SSC */ - bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); - bcm_ns_usb3_mii_mng_write32(usb3, 0x580a21d3); + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); - bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003); + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); /* Waiting MII Mgt interface idle */ bcm_ns_usb3_mii_mng_wait_idle(usb3); diff --git a/drivers/phy/phy-exynos-dp-video.c b/drivers/phy/phy-exynos-dp-video.c index 34b06154e5d9..bb3279dbf88c 100644 --- a/drivers/phy/phy-exynos-dp-video.c +++ b/drivers/phy/phy-exynos-dp-video.c @@ -14,12 +14,12 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mfd/syscon.h> -#include <linux/mfd/syscon/exynos5-pmu.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> struct exynos_dp_video_phy_drvdata { u32 phy_ctrl_offset; @@ -36,7 +36,7 @@ static int exynos_dp_video_phy_power_on(struct phy *phy) /* Disable power isolation on DP-PHY */ return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, - EXYNOS5_PHY_ENABLE, EXYNOS5_PHY_ENABLE); + EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE); } static int exynos_dp_video_phy_power_off(struct phy *phy) @@ -45,7 +45,7 @@ static int exynos_dp_video_phy_power_off(struct phy *phy) /* Enable power isolation on DP-PHY */ return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, - EXYNOS5_PHY_ENABLE, 0); + EXYNOS4_PHY_ENABLE, 0); } static const struct phy_ops exynos_dp_video_phy_ops = { diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/phy-exynos-mipi-video.c index 6bee04cc4d53..c198886f80a3 100644 --- a/drivers/phy/phy-exynos-mipi-video.c +++ b/drivers/phy/phy-exynos-mipi-video.c @@ -12,8 +12,6 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/kernel.h> -#include <linux/mfd/syscon/exynos4-pmu.h> -#include <linux/mfd/syscon/exynos5-pmu.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> @@ -21,6 +19,7 @@ #include <linux/phy/phy.h> #include <linux/regmap.h> #include <linux/spinlock.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> #include <linux/mfd/syscon.h> enum exynos_mipi_phy_id { @@ -64,7 +63,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = { { /* EXYNOS_MIPI_PHY_ID_CSIS0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_val = EXYNOS4_PHY_ENABLE, .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, @@ -73,7 +72,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_DSIM0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_val = EXYNOS4_PHY_ENABLE, .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, @@ -82,7 +81,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_CSIS1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, - .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_val = EXYNOS4_PHY_ENABLE, .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, @@ -91,7 +90,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_DSIM1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, - .enable_val = EXYNOS4_MIPI_PHY_ENABLE, + .enable_val = EXYNOS4_PHY_ENABLE, .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, @@ -109,47 +108,47 @@ static const struct mipi_phy_device_desc exynos5420_mipi_phy = { { /* EXYNOS_MIPI_PHY_ID_CSIS0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), .resetn_map = EXYNOS_MIPI_REGMAP_PMU, }, { /* EXYNOS_MIPI_PHY_ID_DSIM0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS5_MIPI_PHY_M_RESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), .resetn_map = EXYNOS_MIPI_REGMAP_PMU, }, { /* EXYNOS_MIPI_PHY_ID_CSIS1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), .resetn_map = EXYNOS_MIPI_REGMAP_PMU, }, { /* EXYNOS_MIPI_PHY_ID_DSIM1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS5_MIPI_PHY_M_RESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), .resetn_map = EXYNOS_MIPI_REGMAP_PMU, }, { /* EXYNOS_MIPI_PHY_ID_CSIS2 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY2_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS5_MIPI_PHY_S_RESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY2_CONTROL, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), .resetn_map = EXYNOS_MIPI_REGMAP_PMU, }, }, @@ -172,8 +171,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = { { /* EXYNOS_MIPI_PHY_ID_CSIS0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = BIT(0), .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, @@ -181,8 +180,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_DSIM0 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = BIT(0), .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, @@ -190,8 +189,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_CSIS1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = BIT(1), .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, @@ -199,8 +198,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_DSIM1 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = BIT(1), .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, @@ -208,8 +207,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = { }, { /* EXYNOS_MIPI_PHY_ID_CSIS2 */ .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS5_PHY_ENABLE, - .enable_reg = EXYNOS5433_MIPI_PHY2_CONTROL, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2), .enable_map = EXYNOS_MIPI_REGMAP_PMU, .resetn_val = BIT(0), .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, diff --git a/drivers/phy/phy-exynos-pcie.c b/drivers/phy/phy-exynos-pcie.c index 60baf25d98e2..a89c12faff39 100644 --- a/drivers/phy/phy-exynos-pcie.c +++ b/drivers/phy/phy-exynos-pcie.c @@ -14,8 +14,8 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/iopoll.h> +#include <linux/init.h> #include <linux/mfd/syscon.h> -#include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_platform.h> @@ -228,7 +228,6 @@ static const struct of_device_id exynos_pcie_phy_match[] = { }, {}, }; -MODULE_DEVICE_TABLE(of, exynos_pcie_phy_match); static int exynos_pcie_phy_probe(struct platform_device *pdev) { @@ -278,8 +277,5 @@ static struct platform_driver exynos_pcie_phy_driver = { .name = "exynos_pcie_phy", } }; -module_platform_driver(exynos_pcie_phy_driver); -MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC PCIe PHY driver"); -MODULE_AUTHOR("Jaehoon Chung <jh80.chung@samsung.com>"); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver(exynos_pcie_phy_driver); diff --git a/drivers/phy/phy-exynos5-usbdrd.c b/drivers/phy/phy-exynos5-usbdrd.c index 07ed608905ac..7c41daa2c625 100644 --- a/drivers/phy/phy-exynos5-usbdrd.c +++ b/drivers/phy/phy-exynos5-usbdrd.c @@ -22,9 +22,9 @@ #include <linux/platform_device.h> #include <linux/mutex.h> #include <linux/mfd/syscon.h> -#include <linux/mfd/syscon/exynos5-pmu.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/soc/samsung/exynos-regs-pmu.h> /* Exynos USB PHY registers */ #define EXYNOS5_FSEL_9MHZ6 0x0 @@ -235,10 +235,10 @@ static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst, if (!inst->reg_pmu) return; - val = on ? 0 : EXYNOS5_PHY_ENABLE; + val = on ? 0 : EXYNOS4_PHY_ENABLE; regmap_update_bits(inst->reg_pmu, inst->pmu_offset, - EXYNOS5_PHY_ENABLE, val); + EXYNOS4_PHY_ENABLE, val); } /* diff --git a/drivers/phy/phy-meson8b-usb2.c b/drivers/phy/phy-meson8b-usb2.c index 33c9f4ba157d..30f56a6a411f 100644 --- a/drivers/phy/phy-meson8b-usb2.c +++ b/drivers/phy/phy-meson8b-usb2.c @@ -81,9 +81,9 @@ #define REG_ADP_BC_ACA_PIN_GND BIT(25) #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26) -#define REG_DBG_UART 0x14 +#define REG_DBG_UART 0x10 -#define REG_TEST 0x18 +#define REG_TEST 0x14 #define REG_TEST_DATA_IN_MASK GENMASK(3, 0) #define REG_TEST_EN_MASK GENMASK(7, 4) #define REG_TEST_ADDR_MASK GENMASK(11, 8) @@ -93,7 +93,7 @@ #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16) #define REG_TEST_DISABLE_ID_PULLUP BIT(20) -#define REG_TUNE 0x1c +#define REG_TUNE 0x18 #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0) #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2) #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4) diff --git a/drivers/phy/phy-mt65xx-usb3.c b/drivers/phy/phy-mt65xx-usb3.c index d9720675b9db..59b110f795c3 100644 --- a/drivers/phy/phy-mt65xx-usb3.c +++ b/drivers/phy/phy-mt65xx-usb3.c @@ -23,47 +23,55 @@ #include <linux/phy/phy.h> #include <linux/platform_device.h> -/* - * for sifslv2 register, but exclude port's; - * relative to USB3_SIF2_BASE base address - */ -#define SSUSB_SIFSLV_SPLLC 0x0000 -#define SSUSB_SIFSLV_U2FREQ 0x0100 - -/* offsets of sub-segment in each port registers */ -#define SSUSB_SIFSLV_U2PHY_COM_BASE 0x0000 -#define SSUSB_SIFSLV_U3PHYD_BASE 0x0100 -#define SSUSB_USB30_PHYA_SIV_B_BASE 0x0300 -#define SSUSB_SIFSLV_U3PHYA_DA_BASE 0x0400 - -#define U3P_USBPHYACR0 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0000) +/* version V1 sub-banks offset base address */ +/* banks shared by multiple phys */ +#define SSUSB_SIFSLV_V1_SPLLC 0x000 /* shared by u3 phys */ +#define SSUSB_SIFSLV_V1_U2FREQ 0x100 /* shared by u2 phys */ +/* u2 phy bank */ +#define SSUSB_SIFSLV_V1_U2PHY_COM 0x000 +/* u3 phy banks */ +#define SSUSB_SIFSLV_V1_U3PHYD 0x000 +#define SSUSB_SIFSLV_V1_U3PHYA 0x200 + +/* version V2 sub-banks offset base address */ +/* u2 phy banks */ +#define SSUSB_SIFSLV_V2_MISC 0x000 +#define SSUSB_SIFSLV_V2_U2FREQ 0x100 +#define SSUSB_SIFSLV_V2_U2PHY_COM 0x300 +/* u3 phy banks */ +#define SSUSB_SIFSLV_V2_SPLLC 0x000 +#define SSUSB_SIFSLV_V2_CHIP 0x100 +#define SSUSB_SIFSLV_V2_U3PHYD 0x200 +#define SSUSB_SIFSLV_V2_U3PHYA 0x400 + +#define U3P_USBPHYACR0 0x000 #define PA0_RG_U2PLL_FORCE_ON BIT(15) +#define PA0_RG_USB20_INTR_EN BIT(5) -#define U3P_USBPHYACR2 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0008) +#define U3P_USBPHYACR2 0x008 #define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18) -#define U3P_USBPHYACR5 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0014) +#define U3P_USBPHYACR5 0x014 #define PA5_RG_U2_HSTX_SRCAL_EN BIT(15) #define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12) #define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12) #define PA5_RG_U2_HS_100U_U3_EN BIT(11) -#define U3P_USBPHYACR6 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0018) -#define PA6_RG_U2_ISO_EN BIT(31) +#define U3P_USBPHYACR6 0x018 #define PA6_RG_U2_BC11_SW_EN BIT(23) #define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20) #define PA6_RG_U2_SQTH GENMASK(3, 0) #define PA6_RG_U2_SQTH_VAL(x) (0xf & (x)) -#define U3P_U2PHYACR4 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0020) +#define U3P_U2PHYACR4 0x020 #define P2C_RG_USB20_GPIO_CTL BIT(9) #define P2C_USB20_GPIO_MODE BIT(8) #define P2C_U2_GPIO_CTR_MSK (P2C_RG_USB20_GPIO_CTL | P2C_USB20_GPIO_MODE) -#define U3D_U2PHYDCR0 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0060) +#define U3D_U2PHYDCR0 0x060 #define P2C_RG_SIF_U2PLL_FORCE_ON BIT(24) -#define U3P_U2PHYDTM0 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0068) +#define U3P_U2PHYDTM0 0x068 #define P2C_FORCE_UART_EN BIT(26) #define P2C_FORCE_DATAIN BIT(23) #define P2C_FORCE_DM_PULLDOWN BIT(21) @@ -85,47 +93,56 @@ P2C_FORCE_TERMSEL | P2C_RG_DMPULLDOWN | \ P2C_RG_DPPULLDOWN | P2C_RG_TERMSEL) -#define U3P_U2PHYDTM1 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x006C) +#define U3P_U2PHYDTM1 0x06C #define P2C_RG_UART_EN BIT(16) #define P2C_RG_VBUSVALID BIT(5) #define P2C_RG_SESSEND BIT(4) #define P2C_RG_AVALID BIT(2) -#define U3P_U3_PHYA_REG0 (SSUSB_USB30_PHYA_SIV_B_BASE + 0x0000) -#define P3A_RG_U3_VUSB10_ON BIT(5) - -#define U3P_U3_PHYA_REG6 (SSUSB_USB30_PHYA_SIV_B_BASE + 0x0018) +#define U3P_U3_PHYA_REG6 0x018 #define P3A_RG_TX_EIDLE_CM GENMASK(31, 28) #define P3A_RG_TX_EIDLE_CM_VAL(x) ((0xf & (x)) << 28) -#define U3P_U3_PHYA_REG9 (SSUSB_USB30_PHYA_SIV_B_BASE + 0x0024) +#define U3P_U3_PHYA_REG9 0x024 #define P3A_RG_RX_DAC_MUX GENMASK(5, 1) #define P3A_RG_RX_DAC_MUX_VAL(x) ((0x1f & (x)) << 1) -#define U3P_U3PHYA_DA_REG0 (SSUSB_SIFSLV_U3PHYA_DA_BASE + 0x0000) +#define U3P_U3_PHYA_DA_REG0 0x100 #define P3A_RG_XTAL_EXT_EN_U3 GENMASK(11, 10) #define P3A_RG_XTAL_EXT_EN_U3_VAL(x) ((0x3 & (x)) << 10) -#define U3P_PHYD_CDR1 (SSUSB_SIFSLV_U3PHYD_BASE + 0x005c) +#define U3P_U3_PHYD_LFPS1 0x00c +#define P3D_RG_FWAKE_TH GENMASK(21, 16) +#define P3D_RG_FWAKE_TH_VAL(x) ((0x3f & (x)) << 16) + +#define U3P_U3_PHYD_CDR1 0x05c #define P3D_RG_CDR_BIR_LTD1 GENMASK(28, 24) #define P3D_RG_CDR_BIR_LTD1_VAL(x) ((0x1f & (x)) << 24) #define P3D_RG_CDR_BIR_LTD0 GENMASK(12, 8) #define P3D_RG_CDR_BIR_LTD0_VAL(x) ((0x1f & (x)) << 8) -#define U3P_XTALCTL3 (SSUSB_SIFSLV_SPLLC + 0x0018) +#define U3P_U3_PHYD_RXDET1 0x128 +#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9) +#define P3D_RG_RXDET_STB2_SET_VAL(x) ((0x1ff & (x)) << 9) + +#define U3P_U3_PHYD_RXDET2 0x12c +#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0) +#define P3D_RG_RXDET_STB2_SET_P3_VAL(x) (0x1ff & (x)) + +#define U3P_SPLLC_XTALCTL3 0x018 #define XC3_RG_U3_XTAL_RX_PWD BIT(9) #define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8) -#define U3P_U2FREQ_FMCR0 (SSUSB_SIFSLV_U2FREQ + 0x00) +#define U3P_U2FREQ_FMCR0 0x00 #define P2F_RG_MONCLK_SEL GENMASK(27, 26) #define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26) #define P2F_RG_FREQDET_EN BIT(24) #define P2F_RG_CYCLECNT GENMASK(23, 0) #define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x)) -#define U3P_U2FREQ_VALUE (SSUSB_SIFSLV_U2FREQ + 0x0c) +#define U3P_U2FREQ_VALUE 0x0c -#define U3P_U2FREQ_FMMONR1 (SSUSB_SIFSLV_U2FREQ + 0x10) +#define U3P_U2FREQ_FMMONR1 0x10 #define P2F_USB_FM_VALID BIT(0) #define P2F_RG_FRCK_EN BIT(8) @@ -134,21 +151,46 @@ #define U3P_SR_COEF_DIVISOR 1000 #define U3P_FM_DET_CYCLE_CNT 1024 +enum mt_phy_version { + MT_PHY_V1 = 1, + MT_PHY_V2, +}; + struct mt65xx_phy_pdata { /* avoid RX sensitivity level degradation only for mt8173 */ bool avoid_rx_sen_degradation; + enum mt_phy_version version; +}; + +struct u2phy_banks { + void __iomem *misc; + void __iomem *fmreg; + void __iomem *com; +}; + +struct u3phy_banks { + void __iomem *spllc; + void __iomem *chip; + void __iomem *phyd; /* include u3phyd_bank2 */ + void __iomem *phya; /* include u3phya_da */ }; struct mt65xx_phy_instance { struct phy *phy; void __iomem *port_base; + union { + struct u2phy_banks u2_banks; + struct u3phy_banks u3_banks; + }; + struct clk *ref_clk; /* reference clock of anolog phy */ u32 index; u8 type; }; struct mt65xx_u3phy { struct device *dev; - void __iomem *sif_base; /* include sif2, but exclude port's */ + void __iomem *sif_base; /* only shared sif */ + /* deprecated, use @ref_clk instead in phy instance */ struct clk *u3phya_ref; /* reference clock of usb3 anolog phy */ const struct mt65xx_phy_pdata *pdata; struct mt65xx_phy_instance **phys; @@ -158,49 +200,53 @@ struct mt65xx_u3phy { static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { - void __iomem *sif_base = u3phy->sif_base; + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *fmreg = u2_banks->fmreg; + void __iomem *com = u2_banks->com; int calibration_val; int fm_out; u32 tmp; /* enable USB ring oscillator */ - tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp = readl(com + U3P_USBPHYACR5); tmp |= PA5_RG_U2_HSTX_SRCAL_EN; - writel(tmp, instance->port_base + U3P_USBPHYACR5); + writel(tmp, com + U3P_USBPHYACR5); udelay(1); /*enable free run clock */ - tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp = readl(fmreg + U3P_U2FREQ_FMMONR1); tmp |= P2F_RG_FRCK_EN; - writel(tmp, sif_base + U3P_U2FREQ_FMMONR1); + writel(tmp, fmreg + U3P_U2FREQ_FMMONR1); /* set cycle count as 1024, and select u2 channel */ - tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp = readl(fmreg + U3P_U2FREQ_FMCR0); tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL); tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT); - tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index); - writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + if (u3phy->pdata->version == MT_PHY_V1) + tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index >> 1); + + writel(tmp, fmreg + U3P_U2FREQ_FMCR0); /* enable frequency meter */ - tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp = readl(fmreg + U3P_U2FREQ_FMCR0); tmp |= P2F_RG_FREQDET_EN; - writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + writel(tmp, fmreg + U3P_U2FREQ_FMCR0); /* ignore return value */ - readl_poll_timeout(sif_base + U3P_U2FREQ_FMMONR1, tmp, - (tmp & P2F_USB_FM_VALID), 10, 200); + readl_poll_timeout(fmreg + U3P_U2FREQ_FMMONR1, tmp, + (tmp & P2F_USB_FM_VALID), 10, 200); - fm_out = readl(sif_base + U3P_U2FREQ_VALUE); + fm_out = readl(fmreg + U3P_U2FREQ_VALUE); /* disable frequency meter */ - tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp = readl(fmreg + U3P_U2FREQ_FMCR0); tmp &= ~P2F_RG_FREQDET_EN; - writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + writel(tmp, fmreg + U3P_U2FREQ_FMCR0); /*disable free run clock */ - tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp = readl(fmreg + U3P_U2FREQ_FMMONR1); tmp &= ~P2F_RG_FRCK_EN; - writel(tmp, sif_base + U3P_U2FREQ_FMMONR1); + writel(tmp, fmreg + U3P_U2FREQ_FMMONR1); if (fm_out) { /* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */ @@ -215,85 +261,125 @@ static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy, instance->index, fm_out, calibration_val); /* set HS slew rate */ - tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp = readl(com + U3P_USBPHYACR5); tmp &= ~PA5_RG_U2_HSTX_SRCTRL; tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val); - writel(tmp, instance->port_base + U3P_USBPHYACR5); + writel(tmp, com + U3P_USBPHYACR5); /* disable USB ring oscillator */ - tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp = readl(com + U3P_USBPHYACR5); tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN; - writel(tmp, instance->port_base + U3P_USBPHYACR5); + writel(tmp, com + U3P_USBPHYACR5); +} + +static void u3_phy_instance_init(struct mt65xx_u3phy *u3phy, + struct mt65xx_phy_instance *instance) +{ + struct u3phy_banks *u3_banks = &instance->u3_banks; + u32 tmp; + + /* gating PCIe Analog XTAL clock */ + tmp = readl(u3_banks->spllc + U3P_SPLLC_XTALCTL3); + tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD; + writel(tmp, u3_banks->spllc + U3P_SPLLC_XTALCTL3); + + /* gating XSQ */ + tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0); + tmp &= ~P3A_RG_XTAL_EXT_EN_U3; + tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2); + writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0); + + tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG9); + tmp &= ~P3A_RG_RX_DAC_MUX; + tmp |= P3A_RG_RX_DAC_MUX_VAL(4); + writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG9); + + tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG6); + tmp &= ~P3A_RG_TX_EIDLE_CM; + tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe); + writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG6); + + tmp = readl(u3_banks->phyd + U3P_U3_PHYD_CDR1); + tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1); + tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3); + writel(tmp, u3_banks->phyd + U3P_U3_PHYD_CDR1); + + tmp = readl(u3_banks->phyd + U3P_U3_PHYD_LFPS1); + tmp &= ~P3D_RG_FWAKE_TH; + tmp |= P3D_RG_FWAKE_TH_VAL(0x34); + writel(tmp, u3_banks->phyd + U3P_U3_PHYD_LFPS1); + + tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1); + tmp &= ~P3D_RG_RXDET_STB2_SET; + tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10); + writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1); + + tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2); + tmp &= ~P3D_RG_RXDET_STB2_SET_P3; + tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10); + writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2); + + dev_dbg(u3phy->dev, "%s(%d)\n", __func__, instance->index); } static void phy_instance_init(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { - void __iomem *port_base = instance->port_base; + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *com = u2_banks->com; u32 index = instance->index; u32 tmp; /* switch to USB function. (system register, force ip into usb mode) */ - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp &= ~P2C_FORCE_UART_EN; tmp |= P2C_RG_XCVRSEL_VAL(1) | P2C_RG_DATAIN_VAL(0); - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); - tmp = readl(port_base + U3P_U2PHYDTM1); + tmp = readl(com + U3P_U2PHYDTM1); tmp &= ~P2C_RG_UART_EN; - writel(tmp, port_base + U3P_U2PHYDTM1); + writel(tmp, com + U3P_U2PHYDTM1); + + tmp = readl(com + U3P_USBPHYACR0); + tmp |= PA0_RG_USB20_INTR_EN; + writel(tmp, com + U3P_USBPHYACR0); + + /* disable switch 100uA current to SSUSB */ + tmp = readl(com + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HS_100U_U3_EN; + writel(tmp, com + U3P_USBPHYACR5); if (!index) { - tmp = readl(port_base + U3P_U2PHYACR4); + tmp = readl(com + U3P_U2PHYACR4); tmp &= ~P2C_U2_GPIO_CTR_MSK; - writel(tmp, port_base + U3P_U2PHYACR4); + writel(tmp, com + U3P_U2PHYACR4); } if (u3phy->pdata->avoid_rx_sen_degradation) { if (!index) { - tmp = readl(port_base + U3P_USBPHYACR2); + tmp = readl(com + U3P_USBPHYACR2); tmp |= PA2_RG_SIF_U2PLL_FORCE_EN; - writel(tmp, port_base + U3P_USBPHYACR2); + writel(tmp, com + U3P_USBPHYACR2); - tmp = readl(port_base + U3D_U2PHYDCR0); + tmp = readl(com + U3D_U2PHYDCR0); tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); + writel(tmp, com + U3D_U2PHYDCR0); } else { - tmp = readl(port_base + U3D_U2PHYDCR0); + tmp = readl(com + U3D_U2PHYDCR0); tmp |= P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); + writel(tmp, com + U3D_U2PHYDCR0); - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); } } - tmp = readl(port_base + U3P_USBPHYACR6); + tmp = readl(com + U3P_USBPHYACR6); tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */ tmp &= ~PA6_RG_U2_SQTH; tmp |= PA6_RG_U2_SQTH_VAL(2); - writel(tmp, port_base + U3P_USBPHYACR6); - - tmp = readl(port_base + U3P_U3PHYA_DA_REG0); - tmp &= ~P3A_RG_XTAL_EXT_EN_U3; - tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2); - writel(tmp, port_base + U3P_U3PHYA_DA_REG0); - - tmp = readl(port_base + U3P_U3_PHYA_REG9); - tmp &= ~P3A_RG_RX_DAC_MUX; - tmp |= P3A_RG_RX_DAC_MUX_VAL(4); - writel(tmp, port_base + U3P_U3_PHYA_REG9); - - tmp = readl(port_base + U3P_U3_PHYA_REG6); - tmp &= ~P3A_RG_TX_EIDLE_CM; - tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe); - writel(tmp, port_base + U3P_U3_PHYA_REG6); - - tmp = readl(port_base + U3P_PHYD_CDR1); - tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1); - tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3); - writel(tmp, port_base + U3P_PHYD_CDR1); + writel(tmp, com + U3P_USBPHYACR6); dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index); } @@ -301,58 +387,35 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy, static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { - void __iomem *port_base = instance->port_base; + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *com = u2_banks->com; u32 index = instance->index; u32 tmp; - if (!index) { - /* Set RG_SSUSB_VUSB10_ON as 1 after VUSB10 ready */ - tmp = readl(port_base + U3P_U3_PHYA_REG0); - tmp |= P3A_RG_U3_VUSB10_ON; - writel(tmp, port_base + U3P_U3_PHYA_REG0); - } - /* (force_suspendm=0) (let suspendm=1, enable usb 480MHz pll) */ - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp &= ~(P2C_FORCE_SUSPENDM | P2C_RG_XCVRSEL); tmp &= ~(P2C_RG_DATAIN | P2C_DTM0_PART_MASK); - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); /* OTG Enable */ - tmp = readl(port_base + U3P_USBPHYACR6); + tmp = readl(com + U3P_USBPHYACR6); tmp |= PA6_RG_U2_OTG_VBUSCMP_EN; - writel(tmp, port_base + U3P_USBPHYACR6); + writel(tmp, com + U3P_USBPHYACR6); - if (!index) { - tmp = readl(u3phy->sif_base + U3P_XTALCTL3); - tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD; - writel(tmp, u3phy->sif_base + U3P_XTALCTL3); - - /* switch 100uA current to SSUSB */ - tmp = readl(port_base + U3P_USBPHYACR5); - tmp |= PA5_RG_U2_HS_100U_U3_EN; - writel(tmp, port_base + U3P_USBPHYACR5); - } - - tmp = readl(port_base + U3P_U2PHYDTM1); + tmp = readl(com + U3P_U2PHYDTM1); tmp |= P2C_RG_VBUSVALID | P2C_RG_AVALID; tmp &= ~P2C_RG_SESSEND; - writel(tmp, port_base + U3P_U2PHYDTM1); - - /* USB 2.0 slew rate calibration */ - tmp = readl(port_base + U3P_USBPHYACR5); - tmp &= ~PA5_RG_U2_HSTX_SRCTRL; - tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(4); - writel(tmp, port_base + U3P_USBPHYACR5); + writel(tmp, com + U3P_U2PHYDTM1); if (u3phy->pdata->avoid_rx_sen_degradation && index) { - tmp = readl(port_base + U3D_U2PHYDCR0); + tmp = readl(com + U3D_U2PHYDCR0); tmp |= P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); + writel(tmp, com + U3D_U2PHYDCR0); - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); } dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index); } @@ -360,48 +423,36 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, static void phy_instance_power_off(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { - void __iomem *port_base = instance->port_base; + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *com = u2_banks->com; u32 index = instance->index; u32 tmp; - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN); tmp |= P2C_FORCE_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); /* OTG Disable */ - tmp = readl(port_base + U3P_USBPHYACR6); + tmp = readl(com + U3P_USBPHYACR6); tmp &= ~PA6_RG_U2_OTG_VBUSCMP_EN; - writel(tmp, port_base + U3P_USBPHYACR6); - - if (!index) { - /* switch 100uA current back to USB2.0 */ - tmp = readl(port_base + U3P_USBPHYACR5); - tmp &= ~PA5_RG_U2_HS_100U_U3_EN; - writel(tmp, port_base + U3P_USBPHYACR5); - } + writel(tmp, com + U3P_USBPHYACR6); /* let suspendm=0, set utmi into analog power down */ - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp &= ~P2C_RG_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); udelay(1); - tmp = readl(port_base + U3P_U2PHYDTM1); + tmp = readl(com + U3P_U2PHYDTM1); tmp &= ~(P2C_RG_VBUSVALID | P2C_RG_AVALID); tmp |= P2C_RG_SESSEND; - writel(tmp, port_base + U3P_U2PHYDTM1); - - if (!index) { - tmp = readl(port_base + U3P_U3_PHYA_REG0); - tmp &= ~P3A_RG_U3_VUSB10_ON; - writel(tmp, port_base + U3P_U3_PHYA_REG0); - } + writel(tmp, com + U3P_U2PHYDTM1); if (u3phy->pdata->avoid_rx_sen_degradation && index) { - tmp = readl(port_base + U3D_U2PHYDCR0); + tmp = readl(com + U3D_U2PHYDCR0); tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); + writel(tmp, com + U3D_U2PHYDCR0); } dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index); @@ -410,18 +461,55 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy, static void phy_instance_exit(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { - void __iomem *port_base = instance->port_base; + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *com = u2_banks->com; u32 index = instance->index; u32 tmp; if (u3phy->pdata->avoid_rx_sen_degradation && index) { - tmp = readl(port_base + U3D_U2PHYDCR0); + tmp = readl(com + U3D_U2PHYDCR0); tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON; - writel(tmp, port_base + U3D_U2PHYDCR0); + writel(tmp, com + U3D_U2PHYDCR0); - tmp = readl(port_base + U3P_U2PHYDTM0); + tmp = readl(com + U3P_U2PHYDTM0); tmp &= ~P2C_FORCE_SUSPENDM; - writel(tmp, port_base + U3P_U2PHYDTM0); + writel(tmp, com + U3P_U2PHYDTM0); + } +} + +static void phy_v1_banks_init(struct mt65xx_u3phy *u3phy, + struct mt65xx_phy_instance *instance) +{ + struct u2phy_banks *u2_banks = &instance->u2_banks; + struct u3phy_banks *u3_banks = &instance->u3_banks; + + if (instance->type == PHY_TYPE_USB2) { + u2_banks->misc = NULL; + u2_banks->fmreg = u3phy->sif_base + SSUSB_SIFSLV_V1_U2FREQ; + u2_banks->com = instance->port_base + SSUSB_SIFSLV_V1_U2PHY_COM; + } else if (instance->type == PHY_TYPE_USB3) { + u3_banks->spllc = u3phy->sif_base + SSUSB_SIFSLV_V1_SPLLC; + u3_banks->chip = NULL; + u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD; + u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA; + } +} + +static void phy_v2_banks_init(struct mt65xx_u3phy *u3phy, + struct mt65xx_phy_instance *instance) +{ + struct u2phy_banks *u2_banks = &instance->u2_banks; + struct u3phy_banks *u3_banks = &instance->u3_banks; + + if (instance->type == PHY_TYPE_USB2) { + u2_banks->misc = instance->port_base + SSUSB_SIFSLV_V2_MISC; + u2_banks->fmreg = instance->port_base + SSUSB_SIFSLV_V2_U2FREQ; + u2_banks->com = instance->port_base + SSUSB_SIFSLV_V2_U2PHY_COM; + } else if (instance->type == PHY_TYPE_USB3) { + u3_banks->spllc = instance->port_base + SSUSB_SIFSLV_V2_SPLLC; + u3_banks->chip = instance->port_base + SSUSB_SIFSLV_V2_CHIP; + u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V2_U3PHYD; + u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V2_U3PHYA; } } @@ -437,7 +525,17 @@ static int mt65xx_phy_init(struct phy *phy) return ret; } - phy_instance_init(u3phy, instance); + ret = clk_prepare_enable(instance->ref_clk); + if (ret) { + dev_err(u3phy->dev, "failed to enable ref_clk\n"); + return ret; + } + + if (instance->type == PHY_TYPE_USB2) + phy_instance_init(u3phy, instance); + else + u3_phy_instance_init(u3phy, instance); + return 0; } @@ -446,8 +544,10 @@ static int mt65xx_phy_power_on(struct phy *phy) struct mt65xx_phy_instance *instance = phy_get_drvdata(phy); struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); - phy_instance_power_on(u3phy, instance); - hs_slew_rate_calibrate(u3phy, instance); + if (instance->type == PHY_TYPE_USB2) { + phy_instance_power_on(u3phy, instance); + hs_slew_rate_calibrate(u3phy, instance); + } return 0; } @@ -456,7 +556,9 @@ static int mt65xx_phy_power_off(struct phy *phy) struct mt65xx_phy_instance *instance = phy_get_drvdata(phy); struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); - phy_instance_power_off(u3phy, instance); + if (instance->type == PHY_TYPE_USB2) + phy_instance_power_off(u3phy, instance); + return 0; } @@ -465,7 +567,10 @@ static int mt65xx_phy_exit(struct phy *phy) struct mt65xx_phy_instance *instance = phy_get_drvdata(phy); struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); - phy_instance_exit(u3phy, instance); + if (instance->type == PHY_TYPE_USB2) + phy_instance_exit(u3phy, instance); + + clk_disable_unprepare(instance->ref_clk); clk_disable_unprepare(u3phy->u3phya_ref); return 0; } @@ -478,7 +583,6 @@ static struct phy *mt65xx_phy_xlate(struct device *dev, struct device_node *phy_np = args->np; int index; - if (args->args_count != 1) { dev_err(dev, "invalid number of cells in 'phy' property\n"); return ERR_PTR(-EINVAL); @@ -496,13 +600,21 @@ static struct phy *mt65xx_phy_xlate(struct device *dev, } instance->type = args->args[0]; - if (!(instance->type == PHY_TYPE_USB2 || instance->type == PHY_TYPE_USB3)) { dev_err(dev, "unsupported device type: %d\n", instance->type); return ERR_PTR(-EINVAL); } + if (u3phy->pdata->version == MT_PHY_V1) { + phy_v1_banks_init(u3phy, instance); + } else if (u3phy->pdata->version == MT_PHY_V2) { + phy_v2_banks_init(u3phy, instance); + } else { + dev_err(dev, "phy version is not supported\n"); + return ERR_PTR(-EINVAL); + } + return instance->phy; } @@ -516,14 +628,22 @@ static const struct phy_ops mt65xx_u3phy_ops = { static const struct mt65xx_phy_pdata mt2701_pdata = { .avoid_rx_sen_degradation = false, + .version = MT_PHY_V1, +}; + +static const struct mt65xx_phy_pdata mt2712_pdata = { + .avoid_rx_sen_degradation = false, + .version = MT_PHY_V2, }; static const struct mt65xx_phy_pdata mt8173_pdata = { .avoid_rx_sen_degradation = true, + .version = MT_PHY_V1, }; static const struct of_device_id mt65xx_u3phy_id_table[] = { { .compatible = "mediatek,mt2701-u3phy", .data = &mt2701_pdata }, + { .compatible = "mediatek,mt2712-u3phy", .data = &mt2712_pdata }, { .compatible = "mediatek,mt8173-u3phy", .data = &mt8173_pdata }, { }, }; @@ -559,17 +679,23 @@ static int mt65xx_u3phy_probe(struct platform_device *pdev) u3phy->dev = dev; platform_set_drvdata(pdev, u3phy); - sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - u3phy->sif_base = devm_ioremap_resource(dev, sif_res); - if (IS_ERR(u3phy->sif_base)) { - dev_err(dev, "failed to remap sif regs\n"); - return PTR_ERR(u3phy->sif_base); + if (u3phy->pdata->version == MT_PHY_V1) { + /* get banks shared by multiple phys */ + sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + u3phy->sif_base = devm_ioremap_resource(dev, sif_res); + if (IS_ERR(u3phy->sif_base)) { + dev_err(dev, "failed to remap sif regs\n"); + return PTR_ERR(u3phy->sif_base); + } } + /* it's deprecated, make it optional for backward compatibility */ u3phy->u3phya_ref = devm_clk_get(dev, "u3phya_ref"); if (IS_ERR(u3phy->u3phya_ref)) { - dev_err(dev, "error to get u3phya_ref\n"); - return PTR_ERR(u3phy->u3phya_ref); + if (PTR_ERR(u3phy->u3phya_ref) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + u3phy->u3phya_ref = NULL; } port = 0; @@ -610,6 +736,17 @@ static int mt65xx_u3phy_probe(struct platform_device *pdev) instance->index = port; phy_set_drvdata(phy, instance); port++; + + /* if deprecated clock is provided, ignore instance's one */ + if (u3phy->u3phya_ref) + continue; + + instance->ref_clk = devm_clk_get(&phy->dev, "ref"); + if (IS_ERR(instance->ref_clk)) { + dev_err(dev, "failed to get ref_clk(id-%d)\n", port); + retval = PTR_ERR(instance->ref_clk); + goto put_child; + } } provider = devm_of_phy_provider_register(dev, mt65xx_phy_xlate); diff --git a/drivers/phy/phy-qcom-qmp.c b/drivers/phy/phy-qcom-qmp.c new file mode 100644 index 000000000000..727e23be7cac --- /dev/null +++ b/drivers/phy/phy-qcom-qmp.c @@ -0,0 +1,1153 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <dt-bindings/phy/phy.h> + +/* QMP PHY QSERDES COM registers */ +#define QSERDES_COM_BG_TIMER 0x00c +#define QSERDES_COM_SSC_EN_CENTER 0x010 +#define QSERDES_COM_SSC_ADJ_PER1 0x014 +#define QSERDES_COM_SSC_ADJ_PER2 0x018 +#define QSERDES_COM_SSC_PER1 0x01c +#define QSERDES_COM_SSC_PER2 0x020 +#define QSERDES_COM_SSC_STEP_SIZE1 0x024 +#define QSERDES_COM_SSC_STEP_SIZE2 0x028 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034 +#define QSERDES_COM_CLK_ENABLE1 0x038 +#define QSERDES_COM_SYS_CLK_CTRL 0x03c +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040 +#define QSERDES_COM_PLL_IVCO 0x048 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c +#define QSERDES_COM_LOCK_CMP2_MODE0 0x050 +#define QSERDES_COM_LOCK_CMP3_MODE0 0x054 +#define QSERDES_COM_LOCK_CMP1_MODE1 0x058 +#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c +#define QSERDES_COM_LOCK_CMP3_MODE1 0x060 +#define QSERDES_COM_BG_TRIM 0x070 +#define QSERDES_COM_CLK_EP_DIV 0x074 +#define QSERDES_COM_CP_CTRL_MODE0 0x078 +#define QSERDES_COM_CP_CTRL_MODE1 0x07c +#define QSERDES_COM_PLL_RCTRL_MODE0 0x084 +#define QSERDES_COM_PLL_RCTRL_MODE1 0x088 +#define QSERDES_COM_PLL_CCTRL_MODE0 0x090 +#define QSERDES_COM_PLL_CCTRL_MODE1 0x094 +#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac +#define QSERDES_COM_RESETSM_CNTRL 0x0b4 +#define QSERDES_COM_RESTRIM_CTRL 0x0bc +#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4 +#define QSERDES_COM_LOCK_CMP_EN 0x0c8 +#define QSERDES_COM_LOCK_CMP_CFG 0x0cc +#define QSERDES_COM_DEC_START_MODE0 0x0d0 +#define QSERDES_COM_DEC_START_MODE1 0x0d4 +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0 +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4 +#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8 +#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec +#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114 +#define QSERDES_COM_VCO_TUNE_CTRL 0x124 +#define QSERDES_COM_VCO_TUNE_MAP 0x128 +#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c +#define QSERDES_COM_VCO_TUNE2_MODE0 0x130 +#define QSERDES_COM_VCO_TUNE1_MODE1 0x134 +#define QSERDES_COM_VCO_TUNE2_MODE1 0x138 +#define QSERDES_COM_VCO_TUNE_TIMER1 0x144 +#define QSERDES_COM_VCO_TUNE_TIMER2 0x148 +#define QSERDES_COM_BG_CTRL 0x170 +#define QSERDES_COM_CLK_SELECT 0x174 +#define QSERDES_COM_HSCLK_SEL 0x178 +#define QSERDES_COM_CORECLK_DIV 0x184 +#define QSERDES_COM_CORE_CLK_EN 0x18c +#define QSERDES_COM_C_READY_STATUS 0x190 +#define QSERDES_COM_CMN_CONFIG 0x194 +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c +#define QSERDES_COM_DEBUG_BUS0 0x1a0 +#define QSERDES_COM_DEBUG_BUS1 0x1a4 +#define QSERDES_COM_DEBUG_BUS2 0x1a8 +#define QSERDES_COM_DEBUG_BUS3 0x1ac +#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0 +#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc + +/* QMP PHY TX registers */ +#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054 +#define QSERDES_TX_DEBUG_BUS_SEL 0x064 +#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068 +#define QSERDES_TX_LANE_MODE 0x094 +#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac + +/* QMP PHY RX registers */ +#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010 +#define QSERDES_RX_UCDR_SO_GAIN 0x01c +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040 +#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048 +#define QSERDES_RX_RX_TERM_BW 0x090 +#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4 +#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8 +#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc +#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0 +#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108 +#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c +#define QSERDES_RX_SIGDET_ENABLES 0x110 +#define QSERDES_RX_SIGDET_CNTRL 0x114 +#define QSERDES_RX_SIGDET_LVL 0x118 +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c +#define QSERDES_RX_RX_BAND 0x120 +#define QSERDES_RX_RX_INTERFACE_MODE 0x12c + +/* QMP PHY PCS registers */ +#define QPHY_POWER_DOWN_CONTROL 0x04 +#define QPHY_TXDEEMPH_M6DB_V0 0x24 +#define QPHY_TXDEEMPH_M3P5DB_V0 0x28 +#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54 +#define QPHY_RX_IDLE_DTCT_CNTRL 0x58 +#define QPHY_POWER_STATE_CONFIG1 0x60 +#define QPHY_POWER_STATE_CONFIG2 0x64 +#define QPHY_POWER_STATE_CONFIG4 0x6c +#define QPHY_LOCK_DETECT_CONFIG1 0x80 +#define QPHY_LOCK_DETECT_CONFIG2 0x84 +#define QPHY_LOCK_DETECT_CONFIG3 0x88 +#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0 +#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4 + +/* QPHY_SW_RESET bit */ +#define SW_RESET BIT(0) +/* QPHY_POWER_DOWN_CONTROL */ +#define SW_PWRDN BIT(0) +#define REFCLK_DRV_DSBL BIT(1) +/* QPHY_START_CONTROL bits */ +#define SERDES_START BIT(0) +#define PCS_START BIT(1) +#define PLL_READY_GATE_EN BIT(3) +/* QPHY_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) +/* QPHY_COM_PCS_READY_STATUS bit */ +#define PCS_READY BIT(0) + +#define PHY_INIT_COMPLETE_TIMEOUT 1000 +#define POWER_DOWN_DELAY_US_MIN 10 +#define POWER_DOWN_DELAY_US_MAX 11 + +#define MAX_PROP_NAME 32 + +struct qmp_phy_init_tbl { + unsigned int offset; + unsigned int val; + /* + * register part of layout ? + * if yes, then offset gives index in the reg-layout + */ + int in_layout; +}; + +#define QMP_PHY_INIT_CFG(o, v) \ + { \ + .offset = o, \ + .val = v, \ + } + +#define QMP_PHY_INIT_CFG_L(o, v) \ + { \ + .offset = o, \ + .val = v, \ + .in_layout = 1, \ + } + +/* set of registers with offsets different per-PHY */ +enum qphy_reg_layout { + /* Common block control registers */ + QPHY_COM_SW_RESET, + QPHY_COM_POWER_DOWN_CONTROL, + QPHY_COM_START_CONTROL, + QPHY_COM_PCS_READY_STATUS, + /* PCS registers */ + QPHY_PLL_LOCK_CHK_DLY_TIME, + QPHY_FLL_CNTRL1, + QPHY_FLL_CNTRL2, + QPHY_FLL_CNT_VAL_L, + QPHY_FLL_CNT_VAL_H_TOL, + QPHY_FLL_MAN_CODE, + QPHY_SW_RESET, + QPHY_START_CTRL, + QPHY_PCS_READY_STATUS, +}; + +static const unsigned int pciephy_regs_layout[] = { + [QPHY_COM_SW_RESET] = 0x400, + [QPHY_COM_POWER_DOWN_CONTROL] = 0x404, + [QPHY_COM_START_CONTROL] = 0x408, + [QPHY_COM_PCS_READY_STATUS] = 0x448, + [QPHY_PLL_LOCK_CHK_DLY_TIME] = 0xa8, + [QPHY_FLL_CNTRL1] = 0xc4, + [QPHY_FLL_CNTRL2] = 0xc8, + [QPHY_FLL_CNT_VAL_L] = 0xcc, + [QPHY_FLL_CNT_VAL_H_TOL] = 0xd0, + [QPHY_FLL_MAN_CODE] = 0xd4, + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_READY_STATUS] = 0x174, +}; + +static const unsigned int usb3phy_regs_layout[] = { + [QPHY_FLL_CNTRL1] = 0xc0, + [QPHY_FLL_CNTRL2] = 0xc4, + [QPHY_FLL_CNT_VAL_L] = 0xc8, + [QPHY_FLL_CNT_VAL_H_TOL] = 0xcc, + [QPHY_FLL_MAN_CODE] = 0xd0, + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_READY_STATUS] = 0x17c, +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09), + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19), + QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), + QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c), + QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00), + QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01), + + QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05), + + QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05), + QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3), + QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04), + /* PLL and Loop filter settings */ + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a), + /* SSC settings */ + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), + QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = { + /* FLL settings */ + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42), + QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85), + + /* Lock Det settings */ + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1), + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f), + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08), +}; + +/* struct qmp_phy_cfg - per-PHY initialization config */ +struct qmp_phy_cfg { + /* phy-type - PCIE/UFS/USB */ + unsigned int type; + /* number of lanes provided by phy */ + int nlanes; + + /* Init sequence for PHY blocks - serdes, tx, rx, pcs */ + const struct qmp_phy_init_tbl *serdes_tbl; + int serdes_tbl_num; + const struct qmp_phy_init_tbl *tx_tbl; + int tx_tbl_num; + const struct qmp_phy_init_tbl *rx_tbl; + int rx_tbl_num; + const struct qmp_phy_init_tbl *pcs_tbl; + int pcs_tbl_num; + + /* clock ids to be requested */ + const char * const *clk_list; + int num_clks; + /* resets to be requested */ + const char * const *reset_list; + int num_resets; + /* regulators to be requested */ + const char * const *vreg_list; + int num_vregs; + + /* array of registers with different offsets */ + const unsigned int *regs; + + unsigned int start_ctrl; + unsigned int pwrdn_ctrl; + unsigned int mask_pcs_ready; + unsigned int mask_com_pcs_ready; + + /* true, if PHY has a separate PHY_COM control block */ + bool has_phy_com_ctrl; + /* true, if PHY has a reset for individual lanes */ + bool has_lane_rst; + /* true, if PHY needs delay after POWER_DOWN */ + bool has_pwrdn_delay; + /* power_down delay in usec */ + int pwrdn_delay_min; + int pwrdn_delay_max; +}; + +/** + * struct qmp_phy - per-lane phy descriptor + * + * @phy: generic phy + * @tx: iomapped memory space for lane's tx + * @rx: iomapped memory space for lane's rx + * @pcs: iomapped memory space for lane's pcs + * @pipe_clk: pipe lock + * @index: lane index + * @qmp: QMP phy to which this lane belongs + * @lane_rst: lane's reset controller + */ +struct qmp_phy { + struct phy *phy; + void __iomem *tx; + void __iomem *rx; + void __iomem *pcs; + struct clk *pipe_clk; + unsigned int index; + struct qcom_qmp *qmp; + struct reset_control *lane_rst; +}; + +/** + * struct qcom_qmp - structure holding QMP phy block attributes + * + * @dev: device + * @serdes: iomapped memory space for phy's serdes + * + * @clks: array of clocks required by phy + * @resets: array of resets required by phy + * @vregs: regulator supplies bulk data + * + * @cfg: phy specific configuration + * @phys: array of per-lane phy descriptors + * @phy_mutex: mutex lock for PHY common block initialization + * @init_count: phy common block initialization count + */ +struct qcom_qmp { + struct device *dev; + void __iomem *serdes; + + struct clk **clks; + struct reset_control **resets; + struct regulator_bulk_data *vregs; + + const struct qmp_phy_cfg *cfg; + struct qmp_phy **phys; + + struct mutex phy_mutex; + int init_count; +}; + +static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +/* list of clocks required by phy */ +static const char * const msm8996_phy_clk_l[] = { + "aux", "cfg_ahb", "ref", +}; + +/* list of resets */ +static const char * const msm8996_pciephy_reset_l[] = { + "phy", "common", "cfg", +}; + +static const char * const msm8996_usb3phy_reset_l[] = { + "phy", "common", +}; + +/* list of regulators */ +static const char * const msm8996_phy_vreg_l[] = { + "vdda-phy", "vdda-pll", +}; + +static const struct qmp_phy_cfg msm8996_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 3, + + .serdes_tbl = msm8996_pcie_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8996_pcie_serdes_tbl), + .tx_tbl = msm8996_pcie_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8996_pcie_tx_tbl), + .rx_tbl = msm8996_pcie_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8996_pcie_rx_tbl), + .pcs_tbl = msm8996_pcie_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(msm8996_pcie_pcs_tbl), + .clk_list = msm8996_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), + .reset_list = msm8996_pciephy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l), + .vreg_list = msm8996_phy_vreg_l, + .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .regs = pciephy_regs_layout, + + .start_ctrl = PCS_START | PLL_READY_GATE_EN, + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + .mask_com_pcs_ready = PCS_READY, + + .has_phy_com_ctrl = true, + .has_lane_rst = true, + .has_pwrdn_delay = true, + .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, + .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, +}; + +static const struct qmp_phy_cfg msm8996_usb3phy_cfg = { + .type = PHY_TYPE_USB3, + .nlanes = 1, + + .serdes_tbl = msm8996_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8996_usb3_serdes_tbl), + .tx_tbl = msm8996_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl), + .rx_tbl = msm8996_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8996_usb3_rx_tbl), + .pcs_tbl = msm8996_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(msm8996_usb3_pcs_tbl), + .clk_list = msm8996_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), + .reset_list = msm8996_usb3phy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), + .vreg_list = msm8996_phy_vreg_l, + .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .regs = usb3phy_regs_layout, + + .start_ctrl = SERDES_START | PCS_START, + .pwrdn_ctrl = SW_PWRDN, + .mask_pcs_ready = PHYSTATUS, +}; + +static void qcom_qmp_phy_configure(void __iomem *base, + const unsigned int *regs, + const struct qmp_phy_init_tbl tbl[], + int num) +{ + int i; + const struct qmp_phy_init_tbl *t = tbl; + + if (!t) + return; + + for (i = 0; i < num; i++, t++) { + if (t->in_layout) + writel(t->val, base + regs[t->offset]); + else + writel(t->val, base + t->offset); + } +} + +static int qcom_qmp_phy_poweron(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + int num = qmp->cfg->num_vregs; + int ret; + + dev_vdbg(&phy->dev, "Powering on QMP phy\n"); + + /* turn on regulator supplies */ + ret = regulator_bulk_enable(num, qmp->vregs); + if (ret) { + dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(qphy->pipe_clk); + if (ret) { + dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret); + regulator_bulk_disable(num, qmp->vregs); + return ret; + } + + return 0; +} + +static int qcom_qmp_phy_poweroff(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + + clk_disable_unprepare(qphy->pipe_clk); + + regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs); + + return 0; +} + +static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *serdes = qmp->serdes; + int ret, i; + + mutex_lock(&qmp->phy_mutex); + if (qmp->init_count++) { + mutex_unlock(&qmp->phy_mutex); + return 0; + } + + for (i = 0; i < cfg->num_resets; i++) { + ret = reset_control_deassert(qmp->resets[i]); + if (ret) { + dev_err(qmp->dev, "%s reset deassert failed\n", + qmp->cfg->reset_list[i]); + while (--i >= 0) + reset_control_assert(qmp->resets[i]); + goto err_rst; + } + } + + if (cfg->has_phy_com_ctrl) + qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], + SW_PWRDN); + + /* Serdes configuration */ + qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl, + cfg->serdes_tbl_num); + + if (cfg->has_phy_com_ctrl) { + void __iomem *status; + unsigned int mask, val; + + qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET); + qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], + SERDES_START | PCS_START); + + status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS]; + mask = cfg->mask_com_pcs_ready; + + ret = readl_poll_timeout(status, val, (val & mask), 10, + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, + "phy common block init timed-out\n"); + goto err_com_init; + } + } + + mutex_unlock(&qmp->phy_mutex); + + return 0; + +err_com_init: + while (--i >= 0) + reset_control_assert(qmp->resets[i]); +err_rst: + mutex_unlock(&qmp->phy_mutex); + return ret; +} + +static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *serdes = qmp->serdes; + int i = cfg->num_resets; + + mutex_lock(&qmp->phy_mutex); + if (--qmp->init_count) { + mutex_unlock(&qmp->phy_mutex); + return 0; + } + + if (cfg->has_phy_com_ctrl) { + qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], + SERDES_START | PCS_START); + qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], + SW_RESET); + qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], + SW_PWRDN); + } + + while (--i >= 0) + reset_control_assert(qmp->resets[i]); + + mutex_unlock(&qmp->phy_mutex); + + return 0; +} + +/* PHY Initialization */ +static int qcom_qmp_phy_init(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *tx = qphy->tx; + void __iomem *rx = qphy->rx; + void __iomem *pcs = qphy->pcs; + void __iomem *status; + unsigned int mask, val; + int ret, i; + + dev_vdbg(qmp->dev, "Initializing QMP phy\n"); + + for (i = 0; i < qmp->cfg->num_clks; i++) { + ret = clk_prepare_enable(qmp->clks[i]); + if (ret) { + dev_err(qmp->dev, "failed to enable %s clk, err=%d\n", + qmp->cfg->clk_list[i], ret); + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + } + } + + ret = qcom_qmp_phy_com_init(qmp); + if (ret) + goto err_com_init; + + if (cfg->has_lane_rst) { + ret = reset_control_deassert(qphy->lane_rst); + if (ret) { + dev_err(qmp->dev, "lane%d reset deassert failed\n", + qphy->index); + goto err_lane_rst; + } + } + + /* Tx, Rx, and PCS configurations */ + qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); + qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); + qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); + + /* + * Pull out PHY from POWER DOWN state. + * This is active low enable signal to power-down PHY. + */ + qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + + if (cfg->has_pwrdn_delay) + usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); + + /* start SerDes and Phy-Coding-Sublayer */ + qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); + + /* Pull PHY out of reset state */ + qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + + status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; + mask = cfg->mask_pcs_ready; + + ret = readl_poll_timeout(status, val, !(val & mask), 1, + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, "phy initialization timed-out\n"); + goto err_pcs_ready; + } + + return ret; + +err_pcs_ready: + if (cfg->has_lane_rst) + reset_control_assert(qphy->lane_rst); +err_lane_rst: + qcom_qmp_phy_com_exit(qmp); +err_com_init: + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + + return ret; +} + +static int qcom_qmp_phy_exit(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + int i = cfg->num_clks; + + /* PHY reset */ + qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + + /* stop SerDes and Phy-Coding-Sublayer */ + qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); + + /* Put PHY into POWER DOWN state: active low */ + qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + + if (cfg->has_lane_rst) + reset_control_assert(qphy->lane_rst); + + qcom_qmp_phy_com_exit(qmp); + + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + + return 0; +} + +static int qcom_qmp_phy_vreg_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int num = qmp->cfg->num_vregs; + int i; + + qmp->vregs = devm_kcalloc(dev, num, sizeof(qmp->vregs), GFP_KERNEL); + if (!qmp->vregs) + return -ENOMEM; + + for (i = 0; i < num; i++) + qmp->vregs[i].supply = qmp->cfg->vreg_list[i]; + + return devm_regulator_bulk_get(dev, num, qmp->vregs); +} + +static int qcom_qmp_phy_reset_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int i; + + qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets, + sizeof(*qmp->resets), GFP_KERNEL); + if (!qmp->resets) + return -ENOMEM; + + for (i = 0; i < qmp->cfg->num_resets; i++) { + struct reset_control *rst; + const char *name = qmp->cfg->reset_list[i]; + + rst = devm_reset_control_get(dev, name); + if (IS_ERR(rst)) { + dev_err(dev, "failed to get %s reset\n", name); + return PTR_ERR(rst); + } + qmp->resets[i] = rst; + } + + return 0; +} + +static int qcom_qmp_phy_clk_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int ret, i; + + qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks, + sizeof(*qmp->clks), GFP_KERNEL); + if (!qmp->clks) + return -ENOMEM; + + for (i = 0; i < qmp->cfg->num_clks; i++) { + struct clk *_clk; + const char *name = qmp->cfg->clk_list[i]; + + _clk = devm_clk_get(dev, name); + if (IS_ERR(_clk)) { + ret = PTR_ERR(_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get %s clk, %d\n", + name, ret); + return ret; + } + qmp->clks[i] = _clk; + } + + return 0; +} + +/* + * Register a fixed rate pipe clock. + * + * The <s>_pipe_clksrc generated by PHY goes to the GCC that gate + * controls it. The <s>_pipe_clk coming out of the GCC is requested + * by the PHY driver for its operations. + * We register the <s>_pipe_clksrc here. The gcc driver takes care + * of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk. + * Below picture shows this relationship. + * + * +---------------+ + * | PHY block |<<---------------------------------------+ + * | | | + * | +-------+ | +-----+ | + * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+ + * clk | +-------+ | +-----+ + * +---------------+ + */ +static int phy_pipe_clk_register(struct qcom_qmp *qmp, int id) +{ + char name[24]; + struct clk_fixed_rate *fixed; + struct clk_init_data init = { }; + + switch (qmp->cfg->type) { + case PHY_TYPE_USB3: + snprintf(name, sizeof(name), "usb3_phy_pipe_clk_src"); + break; + case PHY_TYPE_PCIE: + snprintf(name, sizeof(name), "pcie_%d_pipe_clk_src", id); + break; + default: + /* not all phys register pipe clocks, so return success */ + return 0; + } + + fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL); + if (!fixed) + return -ENOMEM; + + init.name = name; + init.ops = &clk_fixed_rate_ops; + + /* controllers using QMP phys use 125MHz pipe clock interface */ + fixed->fixed_rate = 125000000; + fixed->hw.init = &init; + + return devm_clk_hw_register(qmp->dev, &fixed->hw); +} + +static const struct phy_ops qcom_qmp_phy_gen_ops = { + .init = qcom_qmp_phy_init, + .exit = qcom_qmp_phy_exit, + .power_on = qcom_qmp_phy_poweron, + .power_off = qcom_qmp_phy_poweroff, + .owner = THIS_MODULE, +}; + +static +int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct phy *generic_phy; + struct qmp_phy *qphy; + char prop_name[MAX_PROP_NAME]; + int ret; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + /* + * Get memory resources for each phy lane: + * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. + */ + qphy->tx = of_iomap(np, 0); + if (IS_ERR(qphy->tx)) + return PTR_ERR(qphy->tx); + + qphy->rx = of_iomap(np, 1); + if (IS_ERR(qphy->rx)) + return PTR_ERR(qphy->rx); + + qphy->pcs = of_iomap(np, 2); + if (IS_ERR(qphy->pcs)) + return PTR_ERR(qphy->pcs); + + /* + * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3 + * based phys, so they essentially have pipe clock. So, + * we return error in case phy is USB3 or PIPE type. + * Otherwise, we initialize pipe clock to NULL for + * all phys that don't need this. + */ + snprintf(prop_name, sizeof(prop_name), "pipe%d", id); + qphy->pipe_clk = of_clk_get_by_name(np, prop_name); + if (IS_ERR(qphy->pipe_clk)) { + if (qmp->cfg->type == PHY_TYPE_PCIE || + qmp->cfg->type == PHY_TYPE_USB3) { + ret = PTR_ERR(qphy->pipe_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get lane%d pipe_clk, %d\n", + id, ret); + return ret; + } + qphy->pipe_clk = NULL; + } + + /* Get lane reset, if any */ + if (qmp->cfg->has_lane_rst) { + snprintf(prop_name, sizeof(prop_name), "lane%d", id); + qphy->lane_rst = of_reset_control_get(np, prop_name); + if (IS_ERR(qphy->lane_rst)) { + dev_err(dev, "failed to get lane%d reset\n", id); + return PTR_ERR(qphy->lane_rst); + } + } + + generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + dev_err(dev, "failed to create qphy %d\n", ret); + return ret; + } + + qphy->phy = generic_phy; + qphy->index = id; + qphy->qmp = qmp; + qmp->phys[id] = qphy; + phy_set_drvdata(generic_phy, qphy); + + return 0; +} + +static const struct of_device_id qcom_qmp_phy_of_match_table[] = { + { + .compatible = "qcom,msm8996-qmp-pcie-phy", + .data = &msm8996_pciephy_cfg, + }, { + .compatible = "qcom,msm8996-qmp-usb3-phy", + .data = &msm8996_usb3phy_cfg, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table); + +static int qcom_qmp_phy_probe(struct platform_device *pdev) +{ + struct qcom_qmp *qmp; + struct device *dev = &pdev->dev; + struct resource *res; + struct device_node *child; + struct phy_provider *phy_provider; + void __iomem *base; + int num, id; + int ret; + + qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL); + if (!qmp) + return -ENOMEM; + + qmp->dev = dev; + dev_set_drvdata(dev, qmp); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* per PHY serdes; usually located at base address */ + qmp->serdes = base; + + mutex_init(&qmp->phy_mutex); + + /* Get the specific init parameters of QMP phy */ + qmp->cfg = of_device_get_match_data(dev); + + ret = qcom_qmp_phy_clk_init(dev); + if (ret) + return ret; + + ret = qcom_qmp_phy_reset_init(dev); + if (ret) + return ret; + + ret = qcom_qmp_phy_vreg_init(dev); + if (ret) { + dev_err(dev, "failed to get regulator supplies\n"); + return ret; + } + + num = of_get_available_child_count(dev->of_node); + /* do we have a rogue child node ? */ + if (num > qmp->cfg->nlanes) + return -EINVAL; + + qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL); + if (!qmp->phys) + return -ENOMEM; + + id = 0; + for_each_available_child_of_node(dev->of_node, child) { + /* Create per-lane phy */ + ret = qcom_qmp_phy_create(dev, child, id); + if (ret) { + dev_err(dev, "failed to create lane%d phy, %d\n", + id, ret); + return ret; + } + + /* + * Register the pipe clock provided by phy. + * See function description to see details of this pipe clock. + */ + ret = phy_pipe_clk_register(qmp, id); + if (ret) { + dev_err(qmp->dev, + "failed to register pipe clock source\n"); + return ret; + } + id++; + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Qcom-QMP phy\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver qcom_qmp_phy_driver = { + .probe = qcom_qmp_phy_probe, + .driver = { + .name = "qcom-qmp-phy", + .of_match_table = qcom_qmp_phy_of_match_table, + }, +}; + +module_platform_driver(qcom_qmp_phy_driver); + +MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>"); +MODULE_DESCRIPTION("Qualcomm QMP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-qusb2.c b/drivers/phy/phy-qcom-qusb2.c new file mode 100644 index 000000000000..6c575244c0fb --- /dev/null +++ b/drivers/phy/phy-qcom-qusb2.c @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#define QUSB2PHY_PLL_TEST 0x04 +#define CLK_REF_SEL BIT(7) + +#define QUSB2PHY_PLL_TUNE 0x08 +#define QUSB2PHY_PLL_USER_CTL1 0x0c +#define QUSB2PHY_PLL_USER_CTL2 0x10 +#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c +#define QUSB2PHY_PLL_PWR_CTRL 0x18 + +#define QUSB2PHY_PLL_STATUS 0x38 +#define PLL_LOCKED BIT(5) + +#define QUSB2PHY_PORT_TUNE1 0x80 +#define QUSB2PHY_PORT_TUNE2 0x84 +#define QUSB2PHY_PORT_TUNE3 0x88 +#define QUSB2PHY_PORT_TUNE4 0x8c +#define QUSB2PHY_PORT_TUNE5 0x90 +#define QUSB2PHY_PORT_TEST2 0x9c + +#define QUSB2PHY_PORT_POWERDOWN 0xb4 +#define CLAMP_N_EN BIT(5) +#define FREEZIO_N BIT(1) +#define POWER_DOWN BIT(0) + +#define QUSB2PHY_REFCLK_ENABLE BIT(0) + +#define PHY_CLK_SCHEME_SEL BIT(0) + +struct qusb2_phy_init_tbl { + unsigned int offset; + unsigned int val; +}; + +#define QUSB2_PHY_INIT_CFG(o, v) \ + { \ + .offset = o, \ + .val = v, \ + } + +static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), +}; + +struct qusb2_phy_cfg { + const struct qusb2_phy_init_tbl *tbl; + /* number of entries in the table */ + unsigned int tbl_num; + /* offset to PHY_CLK_SCHEME register in TCSR map */ + unsigned int clk_scheme_offset; +}; + +static const struct qusb2_phy_cfg msm8996_phy_cfg = { + .tbl = msm8996_init_tbl, + .tbl_num = ARRAY_SIZE(msm8996_init_tbl), +}; + +static const char * const qusb2_phy_vreg_names[] = { + "vdda-pll", "vdda-phy-dpdm", +}; + +#define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names) + +/** + * struct qusb2_phy - structure holding qusb2 phy attributes + * + * @phy: generic phy + * @base: iomapped memory space for qubs2 phy + * + * @cfg_ahb_clk: AHB2PHY interface clock + * @ref_clk: phy reference clock + * @iface_clk: phy interface clock + * @phy_reset: phy reset control + * @vregs: regulator supplies bulk data + * + * @tcsr: TCSR syscon register map + * @cell: nvmem cell containing phy tuning value + * + * @cfg: phy config data + * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme + */ +struct qusb2_phy { + struct phy *phy; + void __iomem *base; + + struct clk *cfg_ahb_clk; + struct clk *ref_clk; + struct clk *iface_clk; + struct reset_control *phy_reset; + struct regulator_bulk_data vregs[QUSB2_NUM_VREGS]; + + struct regmap *tcsr; + struct nvmem_cell *cell; + + const struct qusb2_phy_cfg *cfg; + bool has_se_clk_scheme; +}; + +static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* Ensure above write is completed */ + readl(base + offset); +} + +static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* Ensure above write is completed */ + readl(base + offset); +} + +static inline +void qcom_qusb2_phy_configure(void __iomem *base, + const struct qusb2_phy_init_tbl tbl[], int num) +{ + int i; + + for (i = 0; i < num; i++) + writel(tbl[i].val, base + tbl[i].offset); +} + +/* + * Fetches HS Tx tuning value from nvmem and sets the + * QUSB2PHY_PORT_TUNE2 register. + * For error case, skip setting the value and use the default value. + */ +static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) +{ + struct device *dev = &qphy->phy->dev; + u8 *val; + + /* + * Read efuse register having TUNE2 parameter's high nibble. + * If efuse register shows value as 0x0, or if we fail to find + * a valid efuse register settings, then use default value + * as 0xB for high nibble that we have already set while + * configuring phy. + */ + val = nvmem_cell_read(qphy->cell, NULL); + if (IS_ERR(val) || !val[0]) { + dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); + return; + } + + /* Fused TUNE2 value is the higher nibble only */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4); +} + +static int qusb2_phy_poweron(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + int num = ARRAY_SIZE(qphy->vregs); + int ret; + + dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__); + + /* turn on regulator supplies */ + ret = regulator_bulk_enable(num, qphy->vregs); + if (ret) + return ret; + + ret = clk_prepare_enable(qphy->iface_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); + regulator_bulk_disable(num, qphy->vregs); + return ret; + } + + return 0; +} + +static int qusb2_phy_poweroff(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + + clk_disable_unprepare(qphy->iface_clk); + + regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + + return 0; +} + +static int qusb2_phy_init(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + unsigned int val; + unsigned int clk_scheme; + int ret; + + dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__); + + /* enable ahb interface clock to program phy */ + ret = clk_prepare_enable(qphy->cfg_ahb_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret); + return ret; + } + + /* Perform phy reset */ + ret = reset_control_assert(qphy->phy_reset); + if (ret) { + dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret); + goto disable_ahb_clk; + } + + /* 100 us delay to keep PHY in reset mode */ + usleep_range(100, 150); + + ret = reset_control_deassert(qphy->phy_reset); + if (ret) { + dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret); + goto disable_ahb_clk; + } + + /* Disable the PHY */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, + CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + + /* save reset value to override reference clock scheme later */ + val = readl(qphy->base + QUSB2PHY_PLL_TEST); + + qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl, + qphy->cfg->tbl_num); + + /* Set efuse value for tuning the PHY */ + qusb2_phy_set_tune2_param(qphy); + + /* Enable the PHY */ + qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN); + + /* Required to get phy pll lock successfully */ + usleep_range(150, 160); + + /* Default is single-ended clock on msm8996 */ + qphy->has_se_clk_scheme = true; + /* + * read TCSR_PHY_CLK_SCHEME register to check if single-ended + * clock scheme is selected. If yes, then disable differential + * ref_clk and use single-ended clock, otherwise use differential + * ref_clk only. + */ + if (qphy->tcsr) { + ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset, + &clk_scheme); + if (ret) { + dev_err(&phy->dev, "failed to read clk scheme reg\n"); + goto assert_phy_reset; + } + + /* is it a differential clock scheme ? */ + if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) { + dev_vdbg(&phy->dev, "%s(): select differential clk\n", + __func__); + qphy->has_se_clk_scheme = false; + } else { + dev_vdbg(&phy->dev, "%s(): select single-ended clk\n", + __func__); + } + } + + if (!qphy->has_se_clk_scheme) { + val &= ~CLK_REF_SEL; + ret = clk_prepare_enable(qphy->ref_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable ref clk, %d\n", + ret); + goto assert_phy_reset; + } + } else { + val |= CLK_REF_SEL; + } + + writel(val, qphy->base + QUSB2PHY_PLL_TEST); + + /* ensure above write is through */ + readl(qphy->base + QUSB2PHY_PLL_TEST); + + /* Required to get phy pll lock successfully */ + usleep_range(100, 110); + + val = readb(qphy->base + QUSB2PHY_PLL_STATUS); + if (!(val & PLL_LOCKED)) { + dev_err(&phy->dev, + "QUSB2PHY pll lock failed: status reg = %x\n", val); + ret = -EBUSY; + goto disable_ref_clk; + } + + return 0; + +disable_ref_clk: + if (!qphy->has_se_clk_scheme) + clk_disable_unprepare(qphy->ref_clk); +assert_phy_reset: + reset_control_assert(qphy->phy_reset); +disable_ahb_clk: + clk_disable_unprepare(qphy->cfg_ahb_clk); + return ret; +} + +static int qusb2_phy_exit(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + + /* Disable the PHY */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, + CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + + if (!qphy->has_se_clk_scheme) + clk_disable_unprepare(qphy->ref_clk); + + reset_control_assert(qphy->phy_reset); + + clk_disable_unprepare(qphy->cfg_ahb_clk); + + return 0; +} + +static const struct phy_ops qusb2_phy_gen_ops = { + .init = qusb2_phy_init, + .exit = qusb2_phy_exit, + .power_on = qusb2_phy_poweron, + .power_off = qusb2_phy_poweroff, + .owner = THIS_MODULE, +}; + +static const struct of_device_id qusb2_phy_of_match_table[] = { + { + .compatible = "qcom,msm8996-qusb2-phy", + .data = &msm8996_phy_cfg, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table); + +static int qusb2_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qusb2_phy *qphy; + struct phy_provider *phy_provider; + struct phy *generic_phy; + struct resource *res; + int ret, i; + int num; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb"); + if (IS_ERR(qphy->cfg_ahb_clk)) { + ret = PTR_ERR(qphy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get cfg ahb clk, %d\n", ret); + return ret; + } + + qphy->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(qphy->ref_clk)) { + ret = PTR_ERR(qphy->ref_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get ref clk, %d\n", ret); + return ret; + } + + qphy->iface_clk = devm_clk_get(dev, "iface"); + if (IS_ERR(qphy->iface_clk)) { + ret = PTR_ERR(qphy->iface_clk); + if (ret == -EPROBE_DEFER) + return ret; + qphy->iface_clk = NULL; + dev_dbg(dev, "failed to get iface clk, %d\n", ret); + } + + qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); + if (IS_ERR(qphy->phy_reset)) { + dev_err(dev, "failed to get phy core reset\n"); + return PTR_ERR(qphy->phy_reset); + } + + num = ARRAY_SIZE(qphy->vregs); + for (i = 0; i < num; i++) + qphy->vregs[i].supply = qusb2_phy_vreg_names[i]; + + ret = devm_regulator_bulk_get(dev, num, qphy->vregs); + if (ret) { + dev_err(dev, "failed to get regulator supplies\n"); + return ret; + } + + /* Get the specific init parameters of QMP phy */ + qphy->cfg = of_device_get_match_data(dev); + + qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node, + "qcom,tcsr-syscon"); + if (IS_ERR(qphy->tcsr)) { + dev_dbg(dev, "failed to lookup TCSR regmap\n"); + qphy->tcsr = NULL; + } + + qphy->cell = devm_nvmem_cell_get(dev, NULL); + if (IS_ERR(qphy->cell)) { + if (PTR_ERR(qphy->cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + qphy->cell = NULL; + dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); + } + + generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + dev_err(dev, "failed to create phy, %d\n", ret); + return ret; + } + qphy->phy = generic_phy; + + dev_set_drvdata(dev, qphy); + phy_set_drvdata(generic_phy, qphy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Qcom-QUSB2 phy\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver qusb2_phy_driver = { + .probe = qusb2_phy_probe, + .driver = { + .name = "qcom-qusb2-phy", + .of_match_table = qusb2_phy_of_match_table, + }, +}; + +module_platform_driver(qusb2_phy_driver); + +MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>"); +MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c index afb4d048d3e9..54c34298a000 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -20,6 +20,7 @@ #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/workqueue.h> @@ -395,7 +396,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) struct rcar_gen3_chan *channel; struct phy_provider *provider; struct resource *res; - int irq; + int irq, ret = 0; if (!dev->of_node) { dev_err(dev, "This driver needs device tree\n"); @@ -434,17 +435,24 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) } } - /* devm_phy_create() will call pm_runtime_enable(dev); */ + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); if (IS_ERR(channel->phy)) { dev_err(dev, "Failed to create USB2 PHY\n"); - return PTR_ERR(channel->phy); + ret = PTR_ERR(channel->phy); + goto error; } channel->vbus = devm_regulator_get_optional(dev, "vbus"); if (IS_ERR(channel->vbus)) { - if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) - return PTR_ERR(channel->vbus); + if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) { + ret = PTR_ERR(channel->vbus); + goto error; + } channel->vbus = NULL; } @@ -454,15 +462,22 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(provider)) { dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; } else if (channel->has_otg) { int ret; ret = device_create_file(dev, &dev_attr_role); if (ret < 0) - return ret; + goto error; } - return PTR_ERR_OR_ZERO(provider); + return 0; + +error: + pm_runtime_disable(dev); + + return ret; } static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) @@ -472,6 +487,8 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) if (channel->has_otg) device_remove_file(&pdev->dev, &dev_attr_role); + pm_runtime_disable(&pdev->dev); + return 0; }; diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c index 4ea95c28a66f..8efe78a49916 100644 --- a/drivers/phy/phy-rockchip-inno-usb2.c +++ b/drivers/phy/phy-rockchip-inno-usb2.c @@ -554,8 +554,7 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) case USB_CHG_STATE_DETECTED: switch (rphy->chg_type) { case POWER_SUPPLY_TYPE_USB: - dev_dbg(&rport->phy->dev, - "sdp cable is connecetd\n"); + dev_dbg(&rport->phy->dev, "sdp cable is connected\n"); rockchip_usb2phy_power_on(rport->phy); rport->state = OTG_STATE_B_PERIPHERAL; notify_charger = true; @@ -563,16 +562,14 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) cable = EXTCON_CHG_USB_SDP; break; case POWER_SUPPLY_TYPE_USB_DCP: - dev_dbg(&rport->phy->dev, - "dcp cable is connecetd\n"); + dev_dbg(&rport->phy->dev, "dcp cable is connected\n"); rockchip_usb2phy_power_off(rport->phy); notify_charger = true; sch_work = true; cable = EXTCON_CHG_USB_DCP; break; case POWER_SUPPLY_TYPE_USB_CDP: - dev_dbg(&rport->phy->dev, - "cdp cable is connecetd\n"); + dev_dbg(&rport->phy->dev, "cdp cable is connected\n"); rockchip_usb2phy_power_on(rport->phy); rport->state = OTG_STATE_B_PERIPHERAL; notify_charger = true; @@ -1141,6 +1138,49 @@ disable_clks: return ret; } +static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { + { + .reg = 0x100, + .num_ports = 2, + .clkout_ctl = { 0x108, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0110, 2, 2, 0, 1 }, + .bvalid_det_st = { 0x0114, 2, 2, 0, 1 }, + .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 }, + .ls_det_en = { 0x0110, 0, 0, 0, 1 }, + .ls_det_st = { 0x0114, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0118, 0, 0, 0, 1 }, + .utmi_avalid = { 0x0120, 10, 10, 0, 1 }, + .utmi_bvalid = { 0x0120, 9, 9, 0, 1 }, + .utmi_ls = { 0x0120, 5, 4, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x104, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x110, 1, 1, 0, 1 }, + .ls_det_st = { 0x114, 1, 1, 0, 1 }, + .ls_det_clr = { 0x118, 1, 1, 0, 1 }, + .utmi_ls = { 0x120, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x120, 19, 19, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0100, 3, 0, 5, 1 }, + .cp_det = { 0x0120, 24, 24, 0, 1 }, + .dcp_det = { 0x0120, 23, 23, 0, 1 }, + .dp_det = { 0x0120, 25, 25, 0, 1 }, + .idm_sink_en = { 0x0108, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0108, 7, 7, 0, 1 }, + .idp_src_en = { 0x0108, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0108, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0108, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { { .reg = 0x700, @@ -1223,6 +1263,7 @@ static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { }; static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, {} diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 734987fa0ad7..3378eeb7a562 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -66,6 +66,7 @@ struct rockchip_usb_phy { struct phy *phy; bool uart_enabled; struct reset_control *reset; + struct regulator *vbus; }; static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, @@ -88,6 +89,9 @@ static void rockchip_usb_phy480m_disable(struct clk_hw *hw) struct rockchip_usb_phy, clk480m_hw); + if (phy->vbus) + regulator_disable(phy->vbus); + /* Power down usb phy analog blocks by set siddq 1 */ rockchip_usb_phy_power(phy, 1); } @@ -143,6 +147,14 @@ static int rockchip_usb_phy_power_on(struct phy *_phy) if (phy->uart_enabled) return -EBUSY; + if (phy->vbus) { + int ret; + + ret = regulator_enable(phy->vbus); + if (ret) + return ret; + } + return clk_prepare_enable(phy->clk480m); } @@ -268,6 +280,13 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, } phy_set_drvdata(rk_phy->phy, rk_phy); + rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus"); + if (IS_ERR(rk_phy->vbus)) { + if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER) + return PTR_ERR(rk_phy->vbus); + rk_phy->vbus = NULL; + } + /* * When acting as uart-pipe, just keep clock on otherwise * only power up usb phy when it use, so disable it when init diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index a21b5f24a340..bbf06cfe5898 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -49,12 +49,14 @@ #define REG_PHYBIST 0x08 #define REG_PHYTUNE 0x0c #define REG_PHYCTL_A33 0x10 -#define REG_PHY_UNK_H3 0x20 +#define REG_PHY_OTGCTL 0x20 #define REG_PMU_UNK1 0x10 #define PHYCTL_DATA BIT(7) +#define OTGCTL_ROUTE_MUSB BIT(0) + #define SUNXI_AHB_ICHR8_EN BIT(10) #define SUNXI_AHB_INCR4_BURST_EN BIT(9) #define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) @@ -110,6 +112,7 @@ struct sun4i_usb_phy_cfg { u8 phyctl_offset; bool dedicated_clocks; bool enable_pmu_unk1; + bool phy0_dual_route; }; struct sun4i_usb_phy_data { @@ -188,10 +191,8 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, spin_lock_irqsave(&phy_data->reg_lock, flags); - if (phy_data->cfg->type == sun8i_a33_phy || - phy_data->cfg->type == sun50i_a64_phy || - phy_data->cfg->type == sun8i_v3s_phy) { - /* A33 or A64 needs us to set phyctl to 0 explicitly */ + if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) { + /* SoCs newer than A33 need us to set phyctl to 0 explicitly */ writel(0, phyctl); } @@ -271,23 +272,16 @@ static int sun4i_usb_phy_init(struct phy *_phy) writel(val & ~2, phy->pmu + REG_PMU_UNK1); } - if (data->cfg->type == sun8i_h3_phy) { - if (phy->index == 0) { - val = readl(data->base + REG_PHY_UNK_H3); - writel(val & ~1, data->base + REG_PHY_UNK_H3); - } - } else { - /* Enable USB 45 Ohm resistor calibration */ - if (phy->index == 0) - sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); + /* Enable USB 45 Ohm resistor calibration */ + if (phy->index == 0) + sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); - /* Adjust PHY's magnitude and rate */ - sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); - /* Disconnect threshold adjustment */ - sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, - data->cfg->disc_thresh, 2); - } + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, 2); sun4i_usb_phy_passby(phy, 1); @@ -486,6 +480,21 @@ static const struct phy_ops sun4i_usb_phy_ops = { .owner = THIS_MODULE, }; +static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, int id_det) +{ + u32 regval; + + regval = readl(data->base + REG_PHY_OTGCTL); + if (id_det == 0) { + /* Host mode. Route phy0 to EHCI/OHCI */ + regval &= ~OTGCTL_ROUTE_MUSB; + } else { + /* Peripheral mode. Route phy0 to MUSB */ + regval |= OTGCTL_ROUTE_MUSB; + } + writel(regval, data->base + REG_PHY_OTGCTL); +} + static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) { struct sun4i_usb_phy_data *data = @@ -546,6 +555,10 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) sun4i_usb_phy0_set_vbus_detect(phy0, 1); mutex_unlock(&phy0->mutex); } + + /* Re-route PHY0 if necessary */ + if (data->cfg->phy0_dual_route) + sun4i_usb_phy0_reroute(data, id_det); } if (vbus_notify) @@ -700,7 +713,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) return PTR_ERR(phy->reset); } - if (i) { /* No pmu for usbc0 */ + if (i || data->cfg->phy0_dual_route) { /* No pmu for musb */ snprintf(name, sizeof(name), "pmu%d", i); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); @@ -823,8 +836,10 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { .num_phys = 4, .type = sun8i_h3_phy, .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, .dedicated_clocks = true, .enable_pmu_unk1 = true, + .phy0_dual_route = true, }; static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { @@ -843,6 +858,7 @@ static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { .phyctl_offset = REG_PHYCTL_A33, .dedicated_clocks = true, .enable_pmu_unk1 = true, + .phy0_dual_route = true, }; static const struct of_device_id sun4i_usb_phy_of_match[] = { diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6f09da4dadb8..6aa120cd0574 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -391,7 +391,6 @@ static int ec_device_probe(struct platform_device *pdev) int retval = -ENOMEM; struct device *dev = &pdev->dev; struct cros_ec_platform *ec_platform = dev_get_platdata(dev); - dev_t devno = MKDEV(ec_major, pdev->id); struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); if (!ec) @@ -407,23 +406,11 @@ static int ec_device_probe(struct platform_device *pdev) cdev_init(&ec->cdev, &fops); /* - * Add the character device - * Link cdev to the class device to be sure device is not used - * before unbinding it. - */ - ec->cdev.kobj.parent = &ec->class_dev.kobj; - retval = cdev_add(&ec->cdev, devno, 1); - if (retval) { - dev_err(dev, ": failed to add character device\n"); - goto cdev_add_failed; - } - - /* * Add the class device * Link to the character device for creating the /dev entry * in devtmpfs. */ - ec->class_dev.devt = ec->cdev.dev; + ec->class_dev.devt = MKDEV(ec_major, pdev->id); ec->class_dev.class = &cros_class; ec->class_dev.parent = dev; ec->class_dev.release = __remove; @@ -431,13 +418,13 @@ static int ec_device_probe(struct platform_device *pdev) retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); if (retval) { dev_err(dev, "dev_set_name failed => %d\n", retval); - goto set_named_failed; + goto failed; } - retval = device_add(&ec->class_dev); + retval = cdev_device_add(&ec->cdev, &ec->class_dev); if (retval) { - dev_err(dev, "device_register failed => %d\n", retval); - goto dev_reg_failed; + dev_err(dev, "cdev_device_add failed => %d\n", retval); + goto failed; } /* check whether this EC is a sensor hub. */ @@ -446,12 +433,8 @@ static int ec_device_probe(struct platform_device *pdev) return 0; -dev_reg_failed: -set_named_failed: - dev_set_drvdata(dev, NULL); - cdev_del(&ec->cdev); -cdev_add_failed: - kfree(ec); +failed: + put_device(&ec->class_dev); return retval; } diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 2b21033f11f0..2de1e603bd2b 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -1,8 +1,8 @@ /* - * Copyright (C) 2011 Google, Inc. * Copyright (C) 2012 Intel, Inc. * Copyright (C) 2013 Intel, Inc. * Copyright (C) 2014 Linaro Limited + * Copyright (C) 2011-2016 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -46,6 +46,7 @@ * exchange is properly mapped during a transfer. */ + #include <linux/module.h> #include <linux/interrupt.h> #include <linux/kernel.h> @@ -63,122 +64,232 @@ #include <linux/acpi.h> /* + * Update this when something changes in the driver's behavior so the host + * can benefit from knowing it + */ +enum { + PIPE_DRIVER_VERSION = 2, + PIPE_CURRENT_DEVICE_VERSION = 2 +}; + +/* * IMPORTANT: The following constants must match the ones used and defined * in external/qemu/hw/goldfish_pipe.c in the Android source tree. */ -/* pipe device registers */ -#define PIPE_REG_COMMAND 0x00 /* write: value = command */ -#define PIPE_REG_STATUS 0x04 /* read */ -#define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */ -#define PIPE_REG_CHANNEL_HIGH 0x30 /* read/write: channel id */ -#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */ -#define PIPE_REG_ADDRESS 0x10 /* write: physical address */ -#define PIPE_REG_ADDRESS_HIGH 0x34 /* write: physical address */ -#define PIPE_REG_WAKES 0x14 /* read: wake flags */ -#define PIPE_REG_PARAMS_ADDR_LOW 0x18 /* read/write: batch data address */ -#define PIPE_REG_PARAMS_ADDR_HIGH 0x1c /* read/write: batch data address */ -#define PIPE_REG_ACCESS_PARAMS 0x20 /* write: batch access */ -#define PIPE_REG_VERSION 0x24 /* read: device version */ - -/* list of commands for PIPE_REG_COMMAND */ -#define CMD_OPEN 1 /* open new channel */ -#define CMD_CLOSE 2 /* close channel (from guest) */ -#define CMD_POLL 3 /* poll read/write status */ - /* List of bitflags returned in status of CMD_POLL command */ -#define PIPE_POLL_IN (1 << 0) -#define PIPE_POLL_OUT (1 << 1) -#define PIPE_POLL_HUP (1 << 2) - -/* The following commands are related to write operations */ -#define CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */ -#define CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing - is possible */ -#define CMD_READ_BUFFER 6 /* receive a user buffer from the emulator */ -#define CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading - * is possible */ +enum PipePollFlags { + PIPE_POLL_IN = 1 << 0, + PIPE_POLL_OUT = 1 << 1, + PIPE_POLL_HUP = 1 << 2 +}; /* Possible status values used to signal errors - see goldfish_pipe_error_convert */ -#define PIPE_ERROR_INVAL -1 -#define PIPE_ERROR_AGAIN -2 -#define PIPE_ERROR_NOMEM -3 -#define PIPE_ERROR_IO -4 +enum PipeErrors { + PIPE_ERROR_INVAL = -1, + PIPE_ERROR_AGAIN = -2, + PIPE_ERROR_NOMEM = -3, + PIPE_ERROR_IO = -4 +}; /* Bit-flags used to signal events from the emulator */ -#define PIPE_WAKE_CLOSED (1 << 0) /* emulator closed pipe */ -#define PIPE_WAKE_READ (1 << 1) /* pipe can now be read from */ -#define PIPE_WAKE_WRITE (1 << 2) /* pipe can now be written to */ - -struct access_params { - unsigned long channel; - u32 size; - unsigned long address; - u32 cmd; - u32 result; - /* reserved for future extension */ +enum PipeWakeFlags { + PIPE_WAKE_CLOSED = 1 << 0, /* emulator closed pipe */ + PIPE_WAKE_READ = 1 << 1, /* pipe can now be read from */ + PIPE_WAKE_WRITE = 1 << 2 /* pipe can now be written to */ +}; + +/* Bit flags for the 'flags' field */ +enum PipeFlagsBits { + BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */ + BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */ + BIT_WAKE_ON_READ = 2, /* want to be woken on reads */ +}; + +enum PipeRegs { + PIPE_REG_CMD = 0, + + PIPE_REG_SIGNAL_BUFFER_HIGH = 4, + PIPE_REG_SIGNAL_BUFFER = 8, + PIPE_REG_SIGNAL_BUFFER_COUNT = 12, + + PIPE_REG_OPEN_BUFFER_HIGH = 20, + PIPE_REG_OPEN_BUFFER = 24, + + PIPE_REG_VERSION = 36, + + PIPE_REG_GET_SIGNALLED = 48, +}; + +enum PipeCmdCode { + PIPE_CMD_OPEN = 1, /* to be used by the pipe device itself */ + PIPE_CMD_CLOSE, + PIPE_CMD_POLL, + PIPE_CMD_WRITE, + PIPE_CMD_WAKE_ON_WRITE, + PIPE_CMD_READ, + PIPE_CMD_WAKE_ON_READ, + + /* + * TODO(zyy): implement a deferred read/write execution to allow + * parallel processing of pipe operations on the host. + */ + PIPE_CMD_WAKE_ON_DONE_IO, +}; + +enum { + MAX_BUFFERS_PER_COMMAND = 336, + MAX_SIGNALLED_PIPES = 64, + INITIAL_PIPES_CAPACITY = 64 +}; + +struct goldfish_pipe_dev; +struct goldfish_pipe; +struct goldfish_pipe_command; + +/* A per-pipe command structure, shared with the host */ +struct goldfish_pipe_command { + s32 cmd; /* PipeCmdCode, guest -> host */ + s32 id; /* pipe id, guest -> host */ + s32 status; /* command execution status, host -> guest */ + s32 reserved; /* to pad to 64-bit boundary */ + union { + /* Parameters for PIPE_CMD_{READ,WRITE} */ + struct { + /* number of buffers, guest -> host */ + u32 buffers_count; + /* number of consumed bytes, host -> guest */ + s32 consumed_size; + /* buffer pointers, guest -> host */ + u64 ptrs[MAX_BUFFERS_PER_COMMAND]; + /* buffer sizes, guest -> host */ + u32 sizes[MAX_BUFFERS_PER_COMMAND]; + } rw_params; + }; +}; + +/* A single signalled pipe information */ +struct signalled_pipe_buffer { + u32 id; u32 flags; }; -/* The global driver data. Holds a reference to the i/o page used to - * communicate with the emulator, and a wake queue for blocked tasks - * waiting to be awoken. - */ -struct goldfish_pipe_dev { - spinlock_t lock; - unsigned char __iomem *base; - struct access_params *aps; - int irq; - u32 version; +/* Parameters for the PIPE_CMD_OPEN command */ +struct open_command_param { + u64 command_buffer_ptr; + u32 rw_params_max_count; }; -static struct goldfish_pipe_dev pipe_dev[1]; +/* Device-level set of buffers shared with the host */ +struct goldfish_pipe_dev_buffers { + struct open_command_param open_command_params; + struct signalled_pipe_buffer signalled_pipe_buffers[ + MAX_SIGNALLED_PIPES]; +}; /* This data type models a given pipe instance */ struct goldfish_pipe { - struct goldfish_pipe_dev *dev; - struct mutex lock; + /* pipe ID - index into goldfish_pipe_dev::pipes array */ + u32 id; + /* The wake flags pipe is waiting for + * Note: not protected with any lock, uses atomic operations + * and barriers to make it thread-safe. + */ unsigned long flags; + /* wake flags host have signalled, + * - protected by goldfish_pipe_dev::lock + */ + unsigned long signalled_flags; + + /* A pointer to command buffer */ + struct goldfish_pipe_command *command_buffer; + + /* doubly linked list of signalled pipes, protected by + * goldfish_pipe_dev::lock + */ + struct goldfish_pipe *prev_signalled; + struct goldfish_pipe *next_signalled; + + /* + * A pipe's own lock. Protects the following: + * - *command_buffer - makes sure a command can safely write its + * parameters to the host and read the results back. + */ + struct mutex lock; + + /* A wake queue for sleeping until host signals an event */ wait_queue_head_t wake_queue; + /* Pointer to the parent goldfish_pipe_dev instance */ + struct goldfish_pipe_dev *dev; }; +/* The global driver data. Holds a reference to the i/o page used to + * communicate with the emulator, and a wake queue for blocked tasks + * waiting to be awoken. + */ +struct goldfish_pipe_dev { + /* + * Global device spinlock. Protects the following members: + * - pipes, pipes_capacity + * - [*pipes, *pipes + pipes_capacity) - array data + * - first_signalled_pipe, + * goldfish_pipe::prev_signalled, + * goldfish_pipe::next_signalled, + * goldfish_pipe::signalled_flags - all singnalled-related fields, + * in all allocated pipes + * - open_command_params - PIPE_CMD_OPEN-related buffers + * + * It looks like a lot of different fields, but the trick is that + * the only operation that happens often is the signalled pipes array + * manipulation. That's why it's OK for now to keep the rest of the + * fields under the same lock. If we notice too much contention because + * of PIPE_CMD_OPEN, then we should add a separate lock there. + */ + spinlock_t lock; -/* Bit flags for the 'flags' field */ -enum { - BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */ - BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */ - BIT_WAKE_ON_READ = 2, /* want to be woken on reads */ + /* + * Array of the pipes of |pipes_capacity| elements, + * indexed by goldfish_pipe::id + */ + struct goldfish_pipe **pipes; + u32 pipes_capacity; + + /* Pointers to the buffers host uses for interaction with this driver */ + struct goldfish_pipe_dev_buffers *buffers; + + /* Head of a doubly linked list of signalled pipes */ + struct goldfish_pipe *first_signalled_pipe; + + /* Some device-specific data */ + int irq; + int version; + unsigned char __iomem *base; }; +struct goldfish_pipe_dev pipe_dev[1] = {}; -static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd_locked(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - u32 status; - struct goldfish_pipe_dev *dev = pipe->dev; - - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - spin_unlock_irqrestore(&dev->lock, flags); - return status; + pipe->command_buffer->cmd = cmd; + /* failure by default */ + pipe->command_buffer->status = PIPE_ERROR_INVAL; + writel(pipe->id, pipe->dev->base + PIPE_REG_CMD); + return pipe->command_buffer->status; } -static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - struct goldfish_pipe_dev *dev = pipe->dev; + int status; - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - spin_unlock_irqrestore(&dev->lock, flags); + if (mutex_lock_interruptible(&pipe->lock)) + return PIPE_ERROR_IO; + status = goldfish_cmd_locked(pipe, cmd); + mutex_unlock(&pipe->lock); + return status; } -/* This function converts an error code returned by the emulator through +/* + * This function converts an error code returned by the emulator through * the PIPE_REG_STATUS i/o register into a valid negative errno value. */ static int goldfish_pipe_error_convert(int status) @@ -195,184 +306,202 @@ static int goldfish_pipe_error_convert(int status) } } -/* - * Notice: QEMU will return 0 for un-known register access, indicating - * param_acess is supported or not - */ -static int valid_batchbuffer_addr(struct goldfish_pipe_dev *dev, - struct access_params *aps) +static int pin_user_pages(unsigned long first_page, unsigned long last_page, + unsigned int last_page_size, int is_write, + struct page *pages[MAX_BUFFERS_PER_COMMAND], + unsigned int *iter_last_page_size) { - u32 aph, apl; - u64 paddr; - aph = readl(dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - apl = readl(dev->base + PIPE_REG_PARAMS_ADDR_LOW); + int ret; + int requested_pages = ((last_page - first_page) >> PAGE_SHIFT) + 1; + + if (requested_pages > MAX_BUFFERS_PER_COMMAND) { + requested_pages = MAX_BUFFERS_PER_COMMAND; + *iter_last_page_size = PAGE_SIZE; + } else { + *iter_last_page_size = last_page_size; + } + + ret = get_user_pages_fast( + first_page, requested_pages, !is_write, pages); + if (ret <= 0) + return -EFAULT; + if (ret < requested_pages) + *iter_last_page_size = PAGE_SIZE; + return ret; - paddr = ((u64)aph << 32) | apl; - if (paddr != (__pa(aps))) - return 0; - return 1; } -/* 0 on success */ -static int setup_access_params_addr(struct platform_device *pdev, - struct goldfish_pipe_dev *dev) +static void release_user_pages(struct page **pages, int pages_count, + int is_write, s32 consumed_size) { - dma_addr_t dma_handle; - struct access_params *aps; + int i; - aps = dmam_alloc_coherent(&pdev->dev, sizeof(struct access_params), - &dma_handle, GFP_KERNEL); - if (!aps) - return -ENOMEM; + for (i = 0; i < pages_count; i++) { + if (!is_write && consumed_size > 0) + set_page_dirty(pages[i]); + put_page(pages[i]); + } +} + +/* Populate the call parameters, merging adjacent pages together */ +static void populate_rw_params( + struct page **pages, int pages_count, + unsigned long address, unsigned long address_end, + unsigned long first_page, unsigned long last_page, + unsigned int iter_last_page_size, int is_write, + struct goldfish_pipe_command *command) +{ + /* + * Process the first page separately - it's the only page that + * needs special handling for its start address. + */ + unsigned long xaddr = page_to_phys(pages[0]); + unsigned long xaddr_prev = xaddr; + int buffer_idx = 0; + int i = 1; + int size_on_page = first_page == last_page + ? (int)(address_end - address) + : (PAGE_SIZE - (address & ~PAGE_MASK)); + command->rw_params.ptrs[0] = (u64)(xaddr | (address & ~PAGE_MASK)); + command->rw_params.sizes[0] = size_on_page; + for (; i < pages_count; ++i) { + xaddr = page_to_phys(pages[i]); + size_on_page = (i == pages_count - 1) ? + iter_last_page_size : PAGE_SIZE; + if (xaddr == xaddr_prev + PAGE_SIZE) { + command->rw_params.sizes[buffer_idx] += size_on_page; + } else { + ++buffer_idx; + command->rw_params.ptrs[buffer_idx] = (u64)xaddr; + command->rw_params.sizes[buffer_idx] = size_on_page; + } + xaddr_prev = xaddr; + } + command->rw_params.buffers_count = buffer_idx + 1; +} - writel(upper_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - writel(lower_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_LOW); +static int transfer_max_buffers(struct goldfish_pipe *pipe, + unsigned long address, unsigned long address_end, int is_write, + unsigned long last_page, unsigned int last_page_size, + s32 *consumed_size, int *status) +{ + static struct page *pages[MAX_BUFFERS_PER_COMMAND]; + unsigned long first_page = address & PAGE_MASK; + unsigned int iter_last_page_size; + int pages_count = pin_user_pages(first_page, last_page, + last_page_size, is_write, + pages, &iter_last_page_size); - if (valid_batchbuffer_addr(dev, aps)) { - dev->aps = aps; - return 0; - } else - return -1; + if (pages_count < 0) + return pages_count; + + /* Serialize access to the pipe command buffers */ + if (mutex_lock_interruptible(&pipe->lock)) + return -ERESTARTSYS; + + populate_rw_params(pages, pages_count, address, address_end, + first_page, last_page, iter_last_page_size, is_write, + pipe->command_buffer); + + /* Transfer the data */ + *status = goldfish_cmd_locked(pipe, + is_write ? PIPE_CMD_WRITE : PIPE_CMD_READ); + + *consumed_size = pipe->command_buffer->rw_params.consumed_size; + + release_user_pages(pages, pages_count, is_write, *consumed_size); + + mutex_unlock(&pipe->lock); + + return 0; } -/* A value that will not be set by qemu emulator */ -#define INITIAL_BATCH_RESULT (0xdeadbeaf) -static int access_with_param(struct goldfish_pipe_dev *dev, const int cmd, - unsigned long address, unsigned long avail, - struct goldfish_pipe *pipe, int *status) +static int wait_for_host_signal(struct goldfish_pipe *pipe, int is_write) { - struct access_params *aps = dev->aps; + u32 wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ; - if (aps == NULL) - return -1; + set_bit(wakeBit, &pipe->flags); + + /* Tell the emulator we're going to wait for a wake event */ + (void)goldfish_cmd(pipe, + is_write ? PIPE_CMD_WAKE_ON_WRITE : PIPE_CMD_WAKE_ON_READ); + + while (test_bit(wakeBit, &pipe->flags)) { + if (wait_event_interruptible( + pipe->wake_queue, + !test_bit(wakeBit, &pipe->flags))) + return -ERESTARTSYS; + + if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) + return -EIO; + } - aps->result = INITIAL_BATCH_RESULT; - aps->channel = (unsigned long)pipe; - aps->size = avail; - aps->address = address; - aps->cmd = cmd; - writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS); - /* - * If the aps->result has not changed, that means - * that the batch command failed - */ - if (aps->result == INITIAL_BATCH_RESULT) - return -1; - *status = aps->result; return 0; } -static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, - size_t bufflen, int is_write) +static ssize_t goldfish_pipe_read_write(struct file *filp, + char __user *buffer, size_t bufflen, int is_write) { - unsigned long irq_flags; struct goldfish_pipe *pipe = filp->private_data; - struct goldfish_pipe_dev *dev = pipe->dev; - unsigned long address, address_end; int count = 0, ret = -EINVAL; + unsigned long address, address_end, last_page; + unsigned int last_page_size; /* If the emulator already closed the pipe, no need to go further */ - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) + if (unlikely(test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))) return -EIO; - /* Null reads or writes succeeds */ if (unlikely(bufflen == 0)) return 0; - /* Check the buffer range for access */ - if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ, - buffer, bufflen)) + if (unlikely(!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ, + buffer, bufflen))) return -EFAULT; - /* Serialize access to the pipe */ - if (mutex_lock_interruptible(&pipe->lock)) - return -ERESTARTSYS; - - address = (unsigned long)(void *)buffer; + address = (unsigned long)buffer; address_end = address + bufflen; + last_page = (address_end - 1) & PAGE_MASK; + last_page_size = ((address_end - 1) & ~PAGE_MASK) + 1; while (address < address_end) { - unsigned long page_end = (address & PAGE_MASK) + PAGE_SIZE; - unsigned long next = page_end < address_end ? page_end - : address_end; - unsigned long avail = next - address; - int status, wakeBit; - struct page *page; - - /* Either vaddr or paddr depending on the device version */ - unsigned long xaddr; + s32 consumed_size; + int status; - /* - * We grab the pages on a page-by-page basis in case user - * space gives us a potentially huge buffer but the read only - * returns a small amount, then there's no need to pin that - * much memory to the process. - */ - ret = get_user_pages_unlocked(address, 1, &page, - is_write ? 0 : FOLL_WRITE); + ret = transfer_max_buffers(pipe, address, address_end, is_write, + last_page, last_page_size, &consumed_size, + &status); if (ret < 0) break; - if (dev->version) { - /* Device version 1 or newer (qemu-android) expects the - * physical address. + if (consumed_size > 0) { + /* No matter what's the status, we've transferred + * something. */ - xaddr = page_to_phys(page) | (address & ~PAGE_MASK); - } else { - /* Device version 0 (classic emulator) expects the - * virtual address. - */ - xaddr = address; + count += consumed_size; + address += consumed_size; } - - /* Now, try to transfer the bytes in the current page */ - spin_lock_irqsave(&dev->lock, irq_flags); - if (access_with_param(dev, - is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - xaddr, avail, pipe, &status)) { - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(avail, dev->base + PIPE_REG_SIZE); - gf_write_ptr((void *)xaddr, - dev->base + PIPE_REG_ADDRESS, - dev->base + PIPE_REG_ADDRESS_HIGH); - writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - } - spin_unlock_irqrestore(&dev->lock, irq_flags); - - if (status > 0 && !is_write) - set_page_dirty(page); - put_page(page); - - if (status > 0) { /* Correct transfer */ - count += status; - address += status; + if (status > 0) continue; - } else if (status == 0) { /* EOF */ + if (status == 0) { + /* EOF */ ret = 0; break; - } else if (status < 0 && count > 0) { + } + if (count > 0) { /* - * An error occurred and we already transferred - * something on one of the previous pages. + * An error occurred, but we already transferred + * something on one of the previous iterations. * Just return what we already copied and log this * err. - * - * Note: This seems like an incorrect approach but - * cannot change it until we check if any user space - * ABI relies on this behavior. */ if (status != PIPE_ERROR_AGAIN) - pr_info_ratelimited("goldfish_pipe: backend returned error %d on %s\n", + pr_info_ratelimited("goldfish_pipe: backend error %d on %s\n", status, is_write ? "write" : "read"); - ret = 0; break; } /* - * If the error is not PIPE_ERROR_AGAIN, or if we are not in + * If the error is not PIPE_ERROR_AGAIN, or if we are in * non-blocking mode, just return the error code. */ if (status != PIPE_ERROR_AGAIN || @@ -381,139 +510,214 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, break; } - /* - * The backend blocked the read/write, wait until the backend - * tells us it's ready to process more data. - */ - wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ; - set_bit(wakeBit, &pipe->flags); - - /* Tell the emulator we're going to wait for a wake event */ - goldfish_cmd(pipe, - is_write ? CMD_WAKE_ON_WRITE : CMD_WAKE_ON_READ); - - /* Unlock the pipe, then wait for the wake signal */ - mutex_unlock(&pipe->lock); - - while (test_bit(wakeBit, &pipe->flags)) { - if (wait_event_interruptible( - pipe->wake_queue, - !test_bit(wakeBit, &pipe->flags))) - return -ERESTARTSYS; - - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) - return -EIO; - } - - /* Try to re-acquire the lock */ - if (mutex_lock_interruptible(&pipe->lock)) - return -ERESTARTSYS; + status = wait_for_host_signal(pipe, is_write); + if (status < 0) + return status; } - mutex_unlock(&pipe->lock); - if (ret < 0) - return ret; - else + if (count > 0) return count; + return ret; } static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer, - size_t bufflen, loff_t *ppos) + size_t bufflen, loff_t *ppos) { - return goldfish_pipe_read_write(filp, buffer, bufflen, 0); + return goldfish_pipe_read_write(filp, buffer, bufflen, + /* is_write */ 0); } static ssize_t goldfish_pipe_write(struct file *filp, const char __user *buffer, size_t bufflen, loff_t *ppos) { - return goldfish_pipe_read_write(filp, (char __user *)buffer, - bufflen, 1); + return goldfish_pipe_read_write(filp, + /* cast away the const */(char __user *)buffer, bufflen, + /* is_write */ 1); } - static unsigned int goldfish_pipe_poll(struct file *filp, poll_table *wait) { struct goldfish_pipe *pipe = filp->private_data; unsigned int mask = 0; int status; - mutex_lock(&pipe->lock); - poll_wait(filp, &pipe->wake_queue, wait); - status = goldfish_cmd_status(pipe, CMD_POLL); - - mutex_unlock(&pipe->lock); + status = goldfish_cmd(pipe, PIPE_CMD_POLL); + if (status < 0) + return -ERESTARTSYS; if (status & PIPE_POLL_IN) mask |= POLLIN | POLLRDNORM; - if (status & PIPE_POLL_OUT) mask |= POLLOUT | POLLWRNORM; - if (status & PIPE_POLL_HUP) mask |= POLLHUP; - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) mask |= POLLERR; return mask; } -static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) +static void signalled_pipes_add_locked(struct goldfish_pipe_dev *dev, + u32 id, u32 flags) { - struct goldfish_pipe_dev *dev = dev_id; - unsigned long irq_flags; - int count = 0; + struct goldfish_pipe *pipe; - /* - * We're going to read from the emulator a list of (channel,flags) - * pairs corresponding to the wake events that occurred on each - * blocked pipe (i.e. channel). - */ - spin_lock_irqsave(&dev->lock, irq_flags); - for (;;) { - /* First read the channel, 0 means the end of the list */ - struct goldfish_pipe *pipe; - unsigned long wakes; - unsigned long channel = 0; + if (WARN_ON(id >= dev->pipes_capacity)) + return; + + pipe = dev->pipes[id]; + if (!pipe) + return; + pipe->signalled_flags |= flags; + + if (pipe->prev_signalled || pipe->next_signalled + || dev->first_signalled_pipe == pipe) + return; /* already in the list */ + pipe->next_signalled = dev->first_signalled_pipe; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = pipe; + dev->first_signalled_pipe = pipe; +} -#ifdef CONFIG_64BIT - channel = (u64)readl(dev->base + PIPE_REG_CHANNEL_HIGH) << 32; +static void signalled_pipes_remove_locked(struct goldfish_pipe_dev *dev, + struct goldfish_pipe *pipe) { + if (pipe->prev_signalled) + pipe->prev_signalled->next_signalled = pipe->next_signalled; + if (pipe->next_signalled) + pipe->next_signalled->prev_signalled = pipe->prev_signalled; + if (pipe == dev->first_signalled_pipe) + dev->first_signalled_pipe = pipe->next_signalled; + pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; +} - if (channel == 0) - break; -#endif - channel |= readl(dev->base + PIPE_REG_CHANNEL); +static struct goldfish_pipe *signalled_pipes_pop_front( + struct goldfish_pipe_dev *dev, int *wakes) +{ + struct goldfish_pipe *pipe; + unsigned long flags; - if (channel == 0) - break; + spin_lock_irqsave(&dev->lock, flags); - /* Convert channel to struct pipe pointer + read wake flags */ - wakes = readl(dev->base + PIPE_REG_WAKES); - pipe = (struct goldfish_pipe *)(ptrdiff_t)channel; + pipe = dev->first_signalled_pipe; + if (pipe) { + *wakes = pipe->signalled_flags; + pipe->signalled_flags = 0; + /* + * This is an optimized version of + * signalled_pipes_remove_locked() + * - We want to make it as fast as possible to + * wake the sleeping pipe operations faster. + */ + dev->first_signalled_pipe = pipe->next_signalled; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; + } - /* Did the emulator just closed a pipe? */ + spin_unlock_irqrestore(&dev->lock, flags); + return pipe; +} + +static void goldfish_interrupt_task(unsigned long unused) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + /* Iterate over the signalled pipes and wake them one by one */ + struct goldfish_pipe *pipe; + int wakes; + + while ((pipe = signalled_pipes_pop_front(dev, &wakes)) != NULL) { if (wakes & PIPE_WAKE_CLOSED) { - set_bit(BIT_CLOSED_ON_HOST, &pipe->flags); - wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE; + pipe->flags = 1 << BIT_CLOSED_ON_HOST; + } else { + if (wakes & PIPE_WAKE_READ) + clear_bit(BIT_WAKE_ON_READ, &pipe->flags); + if (wakes & PIPE_WAKE_WRITE) + clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags); } - if (wakes & PIPE_WAKE_READ) - clear_bit(BIT_WAKE_ON_READ, &pipe->flags); - if (wakes & PIPE_WAKE_WRITE) - clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags); - + /* + * wake_up_interruptible() implies a write barrier, so don't + * explicitly add another one here. + */ wake_up_interruptible(&pipe->wake_queue); - count++; } - spin_unlock_irqrestore(&dev->lock, irq_flags); +} +DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0); - return (count == 0) ? IRQ_NONE : IRQ_HANDLED; +/* + * The general idea of the interrupt handling: + * + * 1. device raises an interrupt if there's at least one signalled pipe + * 2. IRQ handler reads the signalled pipes and their count from the device + * 3. device writes them into a shared buffer and returns the count + * it only resets the IRQ if it has returned all signalled pipes, + * otherwise it leaves it raised, so IRQ handler will be called + * again for the next chunk + * 4. IRQ handler adds all returned pipes to the device's signalled pipes list + * 5. IRQ handler launches a tasklet to process the signalled pipes from the + * list in a separate context + */ +static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) +{ + u32 count; + u32 i; + unsigned long flags; + struct goldfish_pipe_dev *dev = dev_id; + + if (dev != pipe_dev) + return IRQ_NONE; + + /* Request the signalled pipes from the device */ + spin_lock_irqsave(&dev->lock, flags); + + count = readl(dev->base + PIPE_REG_GET_SIGNALLED); + if (count == 0) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_NONE; + } + if (count > MAX_SIGNALLED_PIPES) + count = MAX_SIGNALLED_PIPES; + + for (i = 0; i < count; ++i) + signalled_pipes_add_locked(dev, + dev->buffers->signalled_pipe_buffers[i].id, + dev->buffers->signalled_pipe_buffers[i].flags); + + spin_unlock_irqrestore(&dev->lock, flags); + + tasklet_schedule(&goldfish_interrupt_tasklet); + return IRQ_HANDLED; +} + +static int get_free_pipe_id_locked(struct goldfish_pipe_dev *dev) +{ + int id; + + for (id = 0; id < dev->pipes_capacity; ++id) + if (!dev->pipes[id]) + return id; + + { + /* Reallocate the array */ + u32 new_capacity = 2 * dev->pipes_capacity; + struct goldfish_pipe **pipes = + kcalloc(new_capacity, sizeof(*pipes), GFP_KERNEL); + if (!pipes) + return -ENOMEM; + memcpy(pipes, dev->pipes, sizeof(*pipes) * dev->pipes_capacity); + kfree(dev->pipes); + dev->pipes = pipes; + id = dev->pipes_capacity; + dev->pipes_capacity = new_capacity; + } + return id; } /** - * goldfish_pipe_open - open a channel to the AVD + * goldfish_pipe_open - open a channel to the AVD * @inode: inode of device * @file: file struct of opener * @@ -525,12 +729,13 @@ static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) */ static int goldfish_pipe_open(struct inode *inode, struct file *file) { - struct goldfish_pipe *pipe; struct goldfish_pipe_dev *dev = pipe_dev; - int32_t status; + unsigned long flags; + int id; + int status; /* Allocate new pipe kernel object */ - pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + struct goldfish_pipe *pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); if (pipe == NULL) return -ENOMEM; @@ -539,29 +744,69 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file) init_waitqueue_head(&pipe->wake_queue); /* - * Now, tell the emulator we're opening a new pipe. We use the - * pipe object's address as the channel identifier for simplicity. + * Command buffer needs to be allocated on its own page to make sure + * it is physically contiguous in host's address space. */ + pipe->command_buffer = + (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL); + if (!pipe->command_buffer) { + status = -ENOMEM; + goto err_pipe; + } - status = goldfish_cmd_status(pipe, CMD_OPEN); - if (status < 0) { - kfree(pipe); - return status; + spin_lock_irqsave(&dev->lock, flags); + + id = get_free_pipe_id_locked(dev); + if (id < 0) { + status = id; + goto err_id_locked; } + dev->pipes[id] = pipe; + pipe->id = id; + pipe->command_buffer->id = id; + + /* Now tell the emulator we're opening a new pipe. */ + dev->buffers->open_command_params.rw_params_max_count = + MAX_BUFFERS_PER_COMMAND; + dev->buffers->open_command_params.command_buffer_ptr = + (u64)(unsigned long)__pa(pipe->command_buffer); + status = goldfish_cmd_locked(pipe, PIPE_CMD_OPEN); + spin_unlock_irqrestore(&dev->lock, flags); + if (status < 0) + goto err_cmd; /* All is done, save the pipe into the file's private data field */ file->private_data = pipe; return 0; + +err_cmd: + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[id] = NULL; +err_id_locked: + spin_unlock_irqrestore(&dev->lock, flags); + free_page((unsigned long)pipe->command_buffer); +err_pipe: + kfree(pipe); + return status; } static int goldfish_pipe_release(struct inode *inode, struct file *filp) { + unsigned long flags; struct goldfish_pipe *pipe = filp->private_data; + struct goldfish_pipe_dev *dev = pipe->dev; /* The guest is closing the channel, so tell the emulator right now */ - goldfish_cmd(pipe, CMD_CLOSE); - kfree(pipe); + (void)goldfish_cmd(pipe, PIPE_CMD_CLOSE); + + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[pipe->id] = NULL; + signalled_pipes_remove_locked(dev, pipe); + spin_unlock_irqrestore(&dev->lock, flags); + filp->private_data = NULL; + free_page((unsigned long)pipe->command_buffer); + kfree(pipe); return 0; } @@ -574,18 +819,91 @@ static const struct file_operations goldfish_pipe_fops = { .release = goldfish_pipe_release, }; -static struct miscdevice goldfish_pipe_device = { +static struct miscdevice goldfish_pipe_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "goldfish_pipe", .fops = &goldfish_pipe_fops, }; +static int goldfish_pipe_device_init(struct platform_device *pdev) +{ + char *page; + struct goldfish_pipe_dev *dev = pipe_dev; + int err = devm_request_irq(&pdev->dev, dev->irq, + goldfish_pipe_interrupt, + IRQF_SHARED, "goldfish_pipe", dev); + if (err) { + dev_err(&pdev->dev, "unable to allocate IRQ for v2\n"); + return err; + } + + err = misc_register(&goldfish_pipe_dev); + if (err) { + dev_err(&pdev->dev, "unable to register v2 device\n"); + return err; + } + + dev->first_signalled_pipe = NULL; + dev->pipes_capacity = INITIAL_PIPES_CAPACITY; + dev->pipes = kcalloc(dev->pipes_capacity, sizeof(*dev->pipes), + GFP_KERNEL); + if (!dev->pipes) + return -ENOMEM; + + /* + * We're going to pass two buffers, open_command_params and + * signalled_pipe_buffers, to the host. This means each of those buffers + * needs to be contained in a single physical page. The easiest choice + * is to just allocate a page and place the buffers in it. + */ + if (WARN_ON(sizeof(*dev->buffers) > PAGE_SIZE)) + return -ENOMEM; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + kfree(dev->pipes); + return -ENOMEM; + } + dev->buffers = (struct goldfish_pipe_dev_buffers *)page; + + /* Send the buffer addresses to the host */ + { + u64 paddr = __pa(&dev->buffers->signalled_pipe_buffers); + + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_SIGNAL_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_SIGNAL_BUFFER); + writel((u32)MAX_SIGNALLED_PIPES, + dev->base + PIPE_REG_SIGNAL_BUFFER_COUNT); + + paddr = __pa(&dev->buffers->open_command_params); + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_OPEN_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_OPEN_BUFFER); + } + return 0; +} + +static void goldfish_pipe_device_deinit(struct platform_device *pdev) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + + misc_deregister(&goldfish_pipe_dev); + kfree(dev->pipes); + free_page((unsigned long)dev->buffers); +} + static int goldfish_pipe_probe(struct platform_device *pdev) { int err; struct resource *r; struct goldfish_pipe_dev *dev = pipe_dev; + if (WARN_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE)) + return -ENOMEM; + /* not thread safe, but this should not happen */ WARN_ON(dev->base != NULL); @@ -609,26 +927,21 @@ static int goldfish_pipe_probe(struct platform_device *pdev) } dev->irq = r->start; - err = devm_request_irq(&pdev->dev, dev->irq, goldfish_pipe_interrupt, - IRQF_SHARED, "goldfish_pipe", dev); - if (err) { - dev_err(&pdev->dev, "unable to allocate IRQ\n"); - goto error; - } - - err = misc_register(&goldfish_pipe_device); - if (err) { - dev_err(&pdev->dev, "unable to register device\n"); - goto error; - } - setup_access_params_addr(pdev, dev); - - /* Although the pipe device in the classic Android emulator does not - * recognize the 'version' register, it won't treat this as an error - * either and will simply return 0, which is fine. + /* + * Exchange the versions with the host device + * + * Note: v1 driver used to not report its version, so we write it before + * reading device version back: this allows the host implementation to + * detect the old driver (if there was no version write before read). */ + writel((u32)PIPE_DRIVER_VERSION, dev->base + PIPE_REG_VERSION); dev->version = readl(dev->base + PIPE_REG_VERSION); - return 0; + if (WARN_ON(dev->version < PIPE_CURRENT_DEVICE_VERSION)) + return -EINVAL; + + err = goldfish_pipe_device_init(pdev); + if (!err) + return 0; error: dev->base = NULL; @@ -638,7 +951,7 @@ error: static int goldfish_pipe_remove(struct platform_device *pdev) { struct goldfish_pipe_dev *dev = pipe_dev; - misc_deregister(&goldfish_pipe_device); + goldfish_pipe_device_deinit(pdev); dev->base = NULL; return 0; } @@ -655,17 +968,16 @@ static const struct of_device_id goldfish_pipe_of_match[] = { }; MODULE_DEVICE_TABLE(of, goldfish_pipe_of_match); -static struct platform_driver goldfish_pipe = { +static struct platform_driver goldfish_pipe_driver = { .probe = goldfish_pipe_probe, .remove = goldfish_pipe_remove, .driver = { .name = "goldfish_pipe", - .owner = THIS_MODULE, .of_match_table = goldfish_pipe_of_match, .acpi_match_table = ACPI_PTR(goldfish_pipe_acpi_match), } }; -module_platform_driver(goldfish_pipe); +module_platform_driver(goldfish_pipe_driver); MODULE_AUTHOR("David Turner <digit@google.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 2f07cd615665..6eb0db37dd88 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -64,6 +64,43 @@ static int pps_cdev_fasync(int fd, struct file *file, int on) return fasync_helper(fd, file, on, &pps->async_queue); } +static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) +{ + unsigned int ev = pps->last_ev; + int err = 0; + + /* Manage the timeout */ + if (fdata->timeout.flags & PPS_TIME_INVALID) + err = wait_event_interruptible(pps->queue, + ev != pps->last_ev); + else { + unsigned long ticks; + + dev_dbg(pps->dev, "timeout %lld.%09d\n", + (long long) fdata->timeout.sec, + fdata->timeout.nsec); + ticks = fdata->timeout.sec * HZ; + ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ); + + if (ticks != 0) { + err = wait_event_interruptible_timeout( + pps->queue, + ev != pps->last_ev, + ticks); + if (err == 0) + return -ETIMEDOUT; + } + } + + /* Check for pending signals */ + if (err == -ERESTARTSYS) { + dev_dbg(pps->dev, "pending signal caught\n"); + return -EINTR; + } + + return 0; +} + static long pps_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -144,7 +181,6 @@ static long pps_cdev_ioctl(struct file *file, case PPS_FETCH: { struct pps_fdata fdata; - unsigned int ev; dev_dbg(pps->dev, "PPS_FETCH\n"); @@ -152,36 +188,9 @@ static long pps_cdev_ioctl(struct file *file, if (err) return -EFAULT; - ev = pps->last_ev; - - /* Manage the timeout */ - if (fdata.timeout.flags & PPS_TIME_INVALID) - err = wait_event_interruptible(pps->queue, - ev != pps->last_ev); - else { - unsigned long ticks; - - dev_dbg(pps->dev, "timeout %lld.%09d\n", - (long long) fdata.timeout.sec, - fdata.timeout.nsec); - ticks = fdata.timeout.sec * HZ; - ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ); - - if (ticks != 0) { - err = wait_event_interruptible_timeout( - pps->queue, - ev != pps->last_ev, - ticks); - if (err == 0) - return -ETIMEDOUT; - } - } - - /* Check for pending signals */ - if (err == -ERESTARTSYS) { - dev_dbg(pps->dev, "pending signal caught\n"); - return -EINTR; - } + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; /* Return the fetched timestamp */ spin_lock_irq(&pps->lock); @@ -242,6 +251,57 @@ static long pps_cdev_ioctl(struct file *file, return 0; } +#ifdef CONFIG_COMPAT +static long pps_cdev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct pps_device *pps = file->private_data; + void __user *uarg = (void __user *) arg; + + cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); + + if (cmd == PPS_FETCH) { + struct pps_fdata_compat compat; + struct pps_fdata fdata; + int err; + + dev_dbg(pps->dev, "PPS_FETCH\n"); + + err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat)); + if (err) + return -EFAULT; + + memcpy(&fdata.timeout, &compat.timeout, + sizeof(struct pps_ktime_compat)); + + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; + + /* Return the fetched timestamp */ + spin_lock_irq(&pps->lock); + + compat.info.assert_sequence = pps->assert_sequence; + compat.info.clear_sequence = pps->clear_sequence; + compat.info.current_mode = pps->current_mode; + + memcpy(&compat.info.assert_tu, &pps->assert_tu, + sizeof(struct pps_ktime_compat)); + memcpy(&compat.info.clear_tu, &pps->clear_tu, + sizeof(struct pps_ktime_compat)); + + spin_unlock_irq(&pps->lock); + + return copy_to_user(uarg, &compat, + sizeof(struct pps_fdata_compat)) ? -EFAULT : 0; + } + + return pps_cdev_ioctl(file, cmd, arg); +} +#else +#define pps_cdev_compat_ioctl NULL +#endif + static int pps_cdev_open(struct inode *inode, struct file *file) { struct pps_device *pps = container_of(inode->i_cdev, @@ -268,6 +328,7 @@ static const struct file_operations pps_cdev_fops = { .llseek = no_llseek, .poll = pps_cdev_poll, .fasync = pps_cdev_fasync, + .compat_ioctl = pps_cdev_compat_ioctl, .unlocked_ioctl = pps_cdev_ioctl, .open = pps_cdev_open, .release = pps_cdev_release, diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index 50b617af81bd..5beb0c361076 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -2444,31 +2444,25 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport) mutex_init(&md->buf_mutex); mutex_init(&md->file_mutex); INIT_LIST_HEAD(&md->file_list); - cdev_init(&md->cdev, &mport_fops); - md->cdev.owner = THIS_MODULE; - ret = cdev_add(&md->cdev, MKDEV(MAJOR(dev_number), mport->id), 1); - if (ret < 0) { - kfree(md); - rmcd_error("Unable to register a device, err=%d", ret); - return NULL; - } - md->dev.devt = md->cdev.dev; + device_initialize(&md->dev); + md->dev.devt = MKDEV(MAJOR(dev_number), mport->id); md->dev.class = dev_class; md->dev.parent = &mport->dev; md->dev.release = mport_device_release; dev_set_name(&md->dev, DEV_NAME "%d", mport->id); atomic_set(&md->active, 1); - ret = device_register(&md->dev); + cdev_init(&md->cdev, &mport_fops); + md->cdev.owner = THIS_MODULE; + + ret = cdev_device_add(&md->cdev, &md->dev); if (ret) { rmcd_error("Failed to register mport %d (err=%d)", mport->id, ret); goto err_cdev; } - get_device(&md->dev); - INIT_LIST_HEAD(&md->doorbells); spin_lock_init(&md->db_lock); INIT_LIST_HEAD(&md->portwrites); @@ -2513,8 +2507,7 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport) return md; err_cdev: - cdev_del(&md->cdev); - kfree(md); + put_device(&md->dev); return NULL; } @@ -2578,7 +2571,7 @@ static void mport_cdev_remove(struct mport_dev *md) atomic_set(&md->active, 0); mport_cdev_terminate_dma(md); rio_del_mport_pw_handler(md->mport, md, rio_mport_pw_handler); - cdev_del(&(md->cdev)); + cdev_device_del(&md->cdev, &md->dev); mport_cdev_kill_fasync(md); flush_workqueue(dma_wq); @@ -2603,7 +2596,6 @@ static void mport_cdev_remove(struct mport_dev *md) rio_release_inb_dbell(md->mport, 0, 0x0fff); - device_unregister(&md->dev); put_device(&md->dev); } diff --git a/drivers/rapidio/rio-sysfs.c b/drivers/rapidio/rio-sysfs.c index eda41563d06d..73e4b407f162 100644 --- a/drivers/rapidio/rio-sysfs.c +++ b/drivers/rapidio/rio-sysfs.c @@ -108,15 +108,11 @@ static struct attribute *rio_dev_attrs[] = { &dev_attr_lprev.attr, &dev_attr_destid.attr, &dev_attr_modalias.attr, - NULL, -}; -static const struct attribute_group rio_dev_group = { - .attrs = rio_dev_attrs, -}; - -const struct attribute_group *rio_dev_groups[] = { - &rio_dev_group, + /* Switch-only attributes */ + &dev_attr_routes.attr, + &dev_attr_lnext.attr, + &dev_attr_hopcount.attr, NULL, }; @@ -259,46 +255,40 @@ static struct bin_attribute rio_config_attr = { .write = rio_write_config, }; -/** - * rio_create_sysfs_dev_files - create RIO specific sysfs files - * @rdev: device whose entries should be created - * - * Create files when @rdev is added to sysfs. - */ -int rio_create_sysfs_dev_files(struct rio_dev *rdev) -{ - int err = 0; - - err = device_create_bin_file(&rdev->dev, &rio_config_attr); +static struct bin_attribute *rio_dev_bin_attrs[] = { + &rio_config_attr, + NULL, +}; - if (!err && (rdev->pef & RIO_PEF_SWITCH)) { - err |= device_create_file(&rdev->dev, &dev_attr_routes); - err |= device_create_file(&rdev->dev, &dev_attr_lnext); - err |= device_create_file(&rdev->dev, &dev_attr_hopcount); +static umode_t rio_dev_is_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct rio_dev *rdev = to_rio_dev(kobj_to_dev(kobj)); + umode_t mode = attr->mode; + + if (!(rdev->pef & RIO_PEF_SWITCH) && + (attr == &dev_attr_routes.attr || + attr == &dev_attr_lnext.attr || + attr == &dev_attr_hopcount.attr)) { + /* + * Hide switch-specific attributes for a non-switch device. + */ + mode = 0; } - if (err) - pr_warning("RIO: Failed to create attribute file(s) for %s\n", - rio_name(rdev)); - - return err; + return mode; } -/** - * rio_remove_sysfs_dev_files - cleanup RIO specific sysfs files - * @rdev: device whose entries we should free - * - * Cleanup when @rdev is removed from sysfs. - */ -void rio_remove_sysfs_dev_files(struct rio_dev *rdev) -{ - device_remove_bin_file(&rdev->dev, &rio_config_attr); - if (rdev->pef & RIO_PEF_SWITCH) { - device_remove_file(&rdev->dev, &dev_attr_routes); - device_remove_file(&rdev->dev, &dev_attr_lnext); - device_remove_file(&rdev->dev, &dev_attr_hopcount); - } -} +static const struct attribute_group rio_dev_group = { + .attrs = rio_dev_attrs, + .is_visible = rio_dev_is_attr_visible, + .bin_attrs = rio_dev_bin_attrs, +}; + +const struct attribute_group *rio_dev_groups[] = { + &rio_dev_group, + NULL, +}; static ssize_t bus_scan_store(struct bus_type *bus, const char *buf, size_t count) diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c index 37042858c2db..38d949405618 100644 --- a/drivers/rapidio/rio.c +++ b/drivers/rapidio/rio.c @@ -192,8 +192,6 @@ int rio_add_device(struct rio_dev *rdev) } spin_unlock(&rio_global_list_lock); - rio_create_sysfs_dev_files(rdev); - return 0; } EXPORT_SYMBOL_GPL(rio_add_device); @@ -220,7 +218,6 @@ void rio_del_device(struct rio_dev *rdev, enum rio_device_state state) } } spin_unlock(&rio_global_list_lock); - rio_remove_sysfs_dev_files(rdev); device_unregister(&rdev->dev); } EXPORT_SYMBOL_GPL(rio_del_device); diff --git a/drivers/rapidio/rio.h b/drivers/rapidio/rio.h index 9796b3fee70d..b2abf8576397 100644 --- a/drivers/rapidio/rio.h +++ b/drivers/rapidio/rio.h @@ -27,8 +27,6 @@ extern u32 rio_mport_get_efb(struct rio_mport *port, int local, u16 destid, u8 hopcount, u32 from); extern int rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid, u8 hopcount); -extern int rio_create_sysfs_dev_files(struct rio_dev *rdev); -extern void rio_remove_sysfs_dev_files(struct rio_dev *rdev); extern int rio_lock_device(struct rio_mport *port, u16 destid, u8 hopcount, int wait_ms); extern int rio_unlock_device(struct rio_mport *port, u16 destid, u8 hopcount); diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 74fd9746aeca..5fb439897fe1 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c @@ -195,6 +195,8 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev, goto exit_ida; } + device_initialize(&rtc->dev); + rtc->id = id; rtc->ops = ops; rtc->owner = owner; @@ -233,14 +235,19 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev, rtc_dev_prepare(rtc); - err = device_register(&rtc->dev); + err = cdev_device_add(&rtc->char_dev, &rtc->dev); if (err) { + dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n", + rtc->name, MAJOR(rtc->dev.devt), rtc->id); + /* This will free both memory and the ID */ put_device(&rtc->dev); goto exit; + } else { + dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name, + MAJOR(rtc->dev.devt), rtc->id); } - rtc_dev_add_device(rtc); rtc_proc_add_device(rtc); dev_info(dev, "rtc core: registered %s as %s\n", @@ -271,9 +278,8 @@ void rtc_device_unregister(struct rtc_device *rtc) * Remove innards of this RTC, then disable it, before * letting any rtc_class_open() users access it again */ - rtc_dev_del_device(rtc); rtc_proc_del_device(rtc); - device_del(&rtc->dev); + cdev_device_del(&rtc->char_dev, &rtc->dev); rtc->ops = NULL; mutex_unlock(&rtc->ops_lock); put_device(&rtc->dev); diff --git a/drivers/rtc/rtc-core.h b/drivers/rtc/rtc-core.h index a098aea197fc..7a4ed2f7c7d7 100644 --- a/drivers/rtc/rtc-core.h +++ b/drivers/rtc/rtc-core.h @@ -3,8 +3,6 @@ extern void __init rtc_dev_init(void); extern void __exit rtc_dev_exit(void); extern void rtc_dev_prepare(struct rtc_device *rtc); -extern void rtc_dev_add_device(struct rtc_device *rtc); -extern void rtc_dev_del_device(struct rtc_device *rtc); #else @@ -20,14 +18,6 @@ static inline void rtc_dev_prepare(struct rtc_device *rtc) { } -static inline void rtc_dev_add_device(struct rtc_device *rtc) -{ -} - -static inline void rtc_dev_del_device(struct rtc_device *rtc) -{ -} - #endif #ifdef CONFIG_RTC_INTF_PROC diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c index 6dc8f29697ab..e81a8711fea7 100644 --- a/drivers/rtc/rtc-dev.c +++ b/drivers/rtc/rtc-dev.c @@ -477,23 +477,6 @@ void rtc_dev_prepare(struct rtc_device *rtc) cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc->char_dev.owner = rtc->owner; - rtc->char_dev.kobj.parent = &rtc->dev.kobj; -} - -void rtc_dev_add_device(struct rtc_device *rtc) -{ - if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1)) - dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n", - rtc->name, MAJOR(rtc_devt), rtc->id); - else - dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name, - MAJOR(rtc_devt), rtc->id); -} - -void rtc_dev_del_device(struct rtc_device *rtc) -{ - if (rtc->dev.devt) - cdev_del(&rtc->char_dev); } void __init rtc_dev_init(void) diff --git a/drivers/scsi/osd/osd_uld.c b/drivers/scsi/osd/osd_uld.c index ed948025112c..0e56f1eb05dc 100644 --- a/drivers/scsi/osd/osd_uld.c +++ b/drivers/scsi/osd/osd_uld.c @@ -400,9 +400,6 @@ static void __remove(struct device *dev) kfree(oud->odi.osdname); - if (oud->cdev.owner) - cdev_del(&oud->cdev); - osd_dev_fini(&oud->od); scsi_device_put(scsi_device); @@ -411,7 +408,6 @@ static void __remove(struct device *dev) if (oud->disk) put_disk(oud->disk); - ida_remove(&osd_minor_ida, oud->minor); kfree(oud); } @@ -446,8 +442,21 @@ static int osd_probe(struct device *dev) if (NULL == oud) goto err_retract_minor; + /* class device member */ + device_initialize(&oud->class_dev); dev_set_drvdata(dev, oud); oud->minor = minor; + oud->class_dev.devt = MKDEV(SCSI_OSD_MAJOR, oud->minor); + oud->class_dev.class = &osd_uld_class; + oud->class_dev.parent = dev; + oud->class_dev.release = __remove; + + /* hold one more reference to the scsi_device that will get released + * in __release, in case a logout is happening while fs is mounted + */ + if (scsi_device_get(scsi_device)) + goto err_retract_minor; + osd_dev_init(&oud->od, scsi_device); /* allocate a disk and set it up */ /* FIXME: do we need this since sg has already done that */ @@ -461,61 +470,34 @@ static int osd_probe(struct device *dev) sprintf(disk->disk_name, "osd%d", oud->minor); oud->disk = disk; - /* hold one more reference to the scsi_device that will get released - * in __release, in case a logout is happening while fs is mounted - */ - if (scsi_device_get(scsi_device)) - goto err_put_disk; - osd_dev_init(&oud->od, scsi_device); - /* Detect the OSD Version */ error = __detect_osd(oud); if (error) { OSD_ERR("osd detection failed, non-compatible OSD device\n"); - goto err_put_sdev; + goto err_free_osd; } /* init the char-device for communication with user-mode */ cdev_init(&oud->cdev, &osd_fops); oud->cdev.owner = THIS_MODULE; - error = cdev_add(&oud->cdev, - MKDEV(SCSI_OSD_MAJOR, oud->minor), 1); - if (error) { - OSD_ERR("cdev_add failed\n"); - goto err_put_disk; - } - /* class device member */ - oud->class_dev.devt = oud->cdev.dev; - oud->class_dev.class = &osd_uld_class; - oud->class_dev.parent = dev; - oud->class_dev.release = __remove; error = dev_set_name(&oud->class_dev, "%s", disk->disk_name); if (error) { OSD_ERR("dev_set_name failed => %d\n", error); - goto err_put_cdev; + goto err_free_osd; } - error = device_register(&oud->class_dev); + error = cdev_device_add(&oud->cdev, &oud->class_dev); if (error) { OSD_ERR("device_register failed => %d\n", error); - goto err_put_cdev; + goto err_free_osd; } - get_device(&oud->class_dev); - OSD_INFO("osd_probe %s\n", disk->disk_name); return 0; -err_put_cdev: - cdev_del(&oud->cdev); -err_put_sdev: - scsi_device_put(scsi_device); -err_put_disk: - put_disk(disk); err_free_osd: - dev_set_drvdata(dev, NULL); - kfree(oud); + put_device(&oud->class_dev); err_retract_minor: ida_remove(&osd_minor_ida, minor); return error; @@ -531,9 +513,10 @@ static int osd_remove(struct device *dev) dev, oud->od.scsi_device, scsi_device); } - device_unregister(&oud->class_dev); - + cdev_device_del(&oud->cdev, &oud->class_dev); + ida_remove(&osd_minor_ida, oud->minor); put_device(&oud->class_dev); + return 0; } diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 4c360f8071a8..b1fc626125aa 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -104,4 +104,6 @@ source "drivers/staging/vc04_services/Kconfig" source "drivers/staging/bcm2835-audio/Kconfig" +source "drivers/staging/typec/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 29cec5aa2945..682127c20da5 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -1,6 +1,7 @@ # Makefile for staging directory obj-y += media/ +obj-y += typec/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ obj-$(CONFIG_COMEDI) += comedi/ obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/ @@ -41,4 +42,3 @@ obj-$(CONFIG_KS7010) += ks7010/ obj-$(CONFIG_GREYBUS) += greybus/ obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/ obj-$(CONFIG_SND_BCM2835) += bcm2835-audio/ - diff --git a/drivers/staging/typec/Kconfig b/drivers/staging/typec/Kconfig new file mode 100644 index 000000000000..37a0781b0d0c --- /dev/null +++ b/drivers/staging/typec/Kconfig @@ -0,0 +1,24 @@ +menu "USB Power Delivery and Type-C drivers" + +config TYPEC_TCPM + tristate "USB Type-C Port Controller Manager" + depends on USB + select TYPEC + help + The Type-C Port Controller Manager provides a USB PD and USB Type-C + state machine for use with Type-C Port Controllers. + +if TYPEC_TCPM + +config TYPEC_TCPCI + tristate "Type-C Port Controller Interface driver" + depends on I2C + select REGMAP_I2C + help + Type-C Port Controller driver for TCPCI-compliant controller. + +source "drivers/staging/typec/fusb302/Kconfig" + +endif + +endmenu diff --git a/drivers/staging/typec/Makefile b/drivers/staging/typec/Makefile new file mode 100644 index 000000000000..30a7e29cbc9e --- /dev/null +++ b/drivers/staging/typec/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_TYPEC_TCPM) += tcpm.o +obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-y += fusb302/ diff --git a/drivers/staging/typec/TODO b/drivers/staging/typec/TODO new file mode 100644 index 000000000000..bc1f97a2d1bf --- /dev/null +++ b/drivers/staging/typec/TODO @@ -0,0 +1,15 @@ +tcpm: +- Add documentation (at the very least for the API to low level drivers) +- Split PD code into separate file +- Check if it makes sense to use tracepoints instead of debugfs for debug logs +- Implement Alternate Mode handling +- Address "#if 0" code if not addressed with the above +- Validate all comments marked with "XXX"; either address or remove comments +- Add support for USB PD 3.0. While not mandatory, at least fast role swap + as well as authentication support would be very desirable. + +tcpci: +- Test with real hardware + +Please send patches to Guenter Roeck <linux@roeck-us.net> and copy +Heikki Krogerus <heikki.krogerus@linux.intel.com>. diff --git a/drivers/staging/typec/fusb302/Kconfig b/drivers/staging/typec/fusb302/Kconfig new file mode 100644 index 000000000000..fce099ff39fe --- /dev/null +++ b/drivers/staging/typec/fusb302/Kconfig @@ -0,0 +1,7 @@ +config TYPEC_FUSB302 + tristate "Fairchild FUSB302 Type-C chip driver" + depends on I2C + help + The Fairchild FUSB302 Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. diff --git a/drivers/staging/typec/fusb302/Makefile b/drivers/staging/typec/fusb302/Makefile new file mode 100644 index 000000000000..207efa5fbab8 --- /dev/null +++ b/drivers/staging/typec/fusb302/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o diff --git a/drivers/staging/typec/fusb302/TODO b/drivers/staging/typec/fusb302/TODO new file mode 100644 index 000000000000..4933a1d92c32 --- /dev/null +++ b/drivers/staging/typec/fusb302/TODO @@ -0,0 +1,6 @@ +fusb302: +- Find a better logging scheme, at least not having the same debugging/logging + code replicated here and in tcpm +- Find a non-hacky way to coordinate between PM and I2C access +- Documentation? The FUSB302 datasheet provides information on the chip to help + understand the code. But it may still be helpful to have a documentation. diff --git a/drivers/staging/typec/fusb302/fusb302.c b/drivers/staging/typec/fusb302/fusb302.c new file mode 100644 index 000000000000..2cee9a952c9b --- /dev/null +++ b/drivers/staging/typec/fusb302/fusb302.c @@ -0,0 +1,1815 @@ +/* + * Copyright 2016-2017 Google, 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. + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pinctrl/consumer.h> +#include <linux/proc_fs.h> +#include <linux/regulator/consumer.h> +#include <linux/sched/clock.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/usb/typec.h> +#include <linux/workqueue.h> + +#include "fusb302_reg.h" +#include "../tcpm.h" +#include "../pd.h" + +/* + * When the device is SNK, BC_LVL interrupt is used to monitor cc pins + * for the current capability offered by the SRC. As FUSB302 chip fires + * the BC_LVL interrupt on PD signalings, cc lvl should be handled after + * a delay to avoid measuring on PD activities. The delay is slightly + * longer than PD_T_PD_DEBPUNCE (10-20ms). + */ +#define T_BC_LVL_DEBOUNCE_DELAY_MS 30 + +enum toggling_mode { + TOGGLINE_MODE_OFF, + TOGGLING_MODE_DRP, + TOGGLING_MODE_SNK, + TOGGLING_MODE_SRC, +}; + +static const char * const toggling_mode_name[] = { + [TOGGLINE_MODE_OFF] = "toggling_OFF", + [TOGGLING_MODE_DRP] = "toggling_DRP", + [TOGGLING_MODE_SNK] = "toggling_SNK", + [TOGGLING_MODE_SRC] = "toggling_SRC", +}; + +enum src_current_status { + SRC_CURRENT_DEFAULT, + SRC_CURRENT_MEDIUM, + SRC_CURRENT_HIGH, +}; + +static const u8 ra_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 4, /* 210mV */ + [SRC_CURRENT_MEDIUM] = 9, /* 420mV */ + [SRC_CURRENT_HIGH] = 18, /* 798mV */ +}; + +static const u8 rd_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */ + [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */ + [SRC_CURRENT_HIGH] = 61, /* 2604mV */ +}; + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +struct fusb302_chip { + struct device *dev; + struct i2c_client *i2c_client; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc_dev; + + struct regulator *vbus; + + int gpio_int_n; + int gpio_int_n_irq; + + struct workqueue_struct *wq; + struct delayed_work bc_lvl_handler; + + atomic_t pm_suspend; + atomic_t i2c_busy; + + /* lock for sharing chip states */ + struct mutex lock; + + /* chip status */ + enum toggling_mode toggling_mode; + enum src_current_status src_current_status; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + + /* port status */ + bool pull_up; + bool vconn_on; + bool vbus_on; + bool charge_on; + bool vbus_present; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1; + enum typec_cc_status cc2; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + /* lock for log buffer access */ + struct mutex logbuffer_lock; + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS + +static bool fusb302_log_full(struct fusb302_chip *chip) +{ + return chip->logbuffer_tail == + (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, + va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + if (!chip->logbuffer[chip->logbuffer_head]) { + chip->logbuffer[chip->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!chip->logbuffer[chip->logbuffer_head]) + return; + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + mutex_lock(&chip->logbuffer_lock); + + if (fusb302_log_full(chip)) { + chip->logbuffer_head = max(chip->logbuffer_head - 1, 0); + strlcpy(tmpbuffer, "overflow", sizeof(tmpbuffer)); + } + + if (chip->logbuffer_head < 0 || + chip->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(chip->dev, + "Bad log buffer index %d\n", chip->logbuffer_head); + goto abort; + } + + if (!chip->logbuffer[chip->logbuffer_head]) { + dev_warn(chip->dev, + "Log buffer index %d is NULL\n", chip->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(chip->logbuffer[chip->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + chip->logbuffer_head = (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&chip->logbuffer_lock); +} + +static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _fusb302_log(chip, fmt, args); + va_end(args); +} + +static int fusb302_seq_show(struct seq_file *s, void *v) +{ + struct fusb302_chip *chip = (struct fusb302_chip *)s->private; + int tail; + + mutex_lock(&chip->logbuffer_lock); + tail = chip->logbuffer_tail; + while (tail != chip->logbuffer_head) { + seq_printf(s, "%s\n", chip->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + chip->logbuffer_tail = tail; + mutex_unlock(&chip->logbuffer_lock); + + return 0; +} + +static int fusb302_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, fusb302_seq_show, inode->i_private); +} + +static const struct file_operations fusb302_debug_operations = { + .open = fusb302_debug_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static struct dentry *rootdir; + +static int fusb302_debugfs_init(struct fusb302_chip *chip) +{ + mutex_init(&chip->logbuffer_lock); + if (!rootdir) { + rootdir = debugfs_create_dir("fusb302", NULL); + if (!rootdir) + return -ENOMEM; + } + + chip->dentry = debugfs_create_file(dev_name(chip->dev), + S_IFREG | 0444, rootdir, + chip, &fusb302_debug_operations); + + return 0; +} + +static void fusb302_debugfs_exit(struct fusb302_chip *chip) +{ + debugfs_remove(chip->dentry); +} + +#else + +static void fusb302_log(const struct fusb302_chip *chip, + const char *fmt, ...) { } +static int fusb302_debugfs_init(const struct fusb302_chip *chip) { return 0; } +static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } + +#endif + +#define FUSB302_RESUME_RETRY 10 +#define FUSB302_RESUME_RETRY_SLEEP 50 +static int fusb302_i2c_write(struct fusb302_chip *chip, + u8 address, u8 data) +{ + int retry_cnt; + int ret = 0; + + atomic_set(&chip->i2c_busy, 1); + for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { + if (atomic_read(&chip->pm_suspend)) { + pr_err("fusb302_i2c: pm suspend, retry %d/%d\n", + retry_cnt + 1, FUSB302_RESUME_RETRY); + msleep(FUSB302_RESUME_RETRY_SLEEP); + } else { + break; + } + } + ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data); + if (ret < 0) + fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d", + data, address, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address, + u8 length, const u8 *data) +{ + int retry_cnt; + int ret = 0; + + if (length <= 0) + return ret; + atomic_set(&chip->i2c_busy, 1); + for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { + if (atomic_read(&chip->pm_suspend)) { + pr_err("fusb302_i2c: pm suspend, retry %d/%d\n", + retry_cnt + 1, FUSB302_RESUME_RETRY); + msleep(FUSB302_RESUME_RETRY_SLEEP); + } else { + break; + } + } + ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) + fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d", + address, length, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_read(struct fusb302_chip *chip, + u8 address, u8 *data) +{ + int retry_cnt; + int ret = 0; + + atomic_set(&chip->i2c_busy, 1); + for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { + if (atomic_read(&chip->pm_suspend)) { + pr_err("fusb302_i2c: pm suspend, retry %d/%d\n", + retry_cnt + 1, FUSB302_RESUME_RETRY); + msleep(FUSB302_RESUME_RETRY_SLEEP); + } else { + break; + } + } + ret = i2c_smbus_read_byte_data(chip->i2c_client, address); + *data = (u8)ret; + if (ret < 0) + fusb302_log(chip, "cannot read %02x, ret=%d", address, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address, + u8 length, u8 *data) +{ + int retry_cnt; + int ret = 0; + + if (length <= 0) + return ret; + atomic_set(&chip->i2c_busy, 1); + for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { + if (atomic_read(&chip->pm_suspend)) { + pr_err("fusb302_i2c: pm suspend, retry %d/%d\n", + retry_cnt + 1, FUSB302_RESUME_RETRY); + msleep(FUSB302_RESUME_RETRY_SLEEP); + } else { + break; + } + } + ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) { + fusb302_log(chip, "cannot block read 0x%02x, len=%d, ret=%d", + address, length, ret); + return ret; + } + if (ret != length) { + fusb302_log(chip, "only read %d/%d bytes from 0x%02x", + ret, length, address); + return -EIO; + } + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address, + u8 mask, u8 value) +{ + int ret = 0; + u8 data; + + ret = fusb302_i2c_read(chip, address, &data); + if (ret < 0) + return ret; + data &= ~mask; + data |= value; + ret = fusb302_i2c_write(chip, address, data); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address, + u8 set_bits) +{ + return fusb302_i2c_mask_write(chip, address, 0x00, set_bits); +} + +static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address, + u8 clear_bits) +{ + return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00); +} + +static int fusb302_sw_reset(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_RESET, + FUSB_REG_RESET_SW_RESET); + if (ret < 0) + fusb302_log(chip, "cannot sw reset the chip, ret=%d", ret); + else + fusb302_log(chip, "sw reset"); + + return ret; +} + +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_N_RETRIES_3 | + FUSB_REG_CONTROL3_AUTO_RETRY); + + return ret; +} + +/* + * initialize interrupt on the chip + * - unmasked interrupt: VBUS_OK + */ +static int fusb302_init_interrupt(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_MASK, + 0xFF & ~FUSB_REG_MASK_VBUSOK); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_INT_MASK); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode); + + return ret; +} + +static int tcpm_init(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 data; + + ret = fusb302_sw_reset(chip); + if (ret < 0) + return ret; + ret = fusb302_enable_tx_auto_retries(chip); + if (ret < 0) + return ret; + ret = fusb302_init_interrupt(chip); + if (ret < 0) + return ret; + ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL); + if (ret < 0) + return ret; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data); + if (ret < 0) + return ret; + chip->vbus_present = !!(FUSB_REG_STATUS0 & FUSB_REG_STATUS0_VBUSOK); + ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data); + if (ret < 0) + return ret; + fusb302_log(chip, "fusb302 device ID: 0x%02x", data); + + return ret; +} + +static int tcpm_get_vbus(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = chip->vbus_present ? 1 : 0; + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_set_cc_pull(struct fusb302_chip *chip, + bool pull_up, bool pull_down) +{ + int ret = 0; + u8 data = 0x00; + u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + + if (pull_up) + data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN : + FUSB_REG_SWITCHES0_CC2_PU_EN; + if (pull_down) + data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + mask, data); + if (ret < 0) + return ret; + chip->pull_up = pull_up; + + return ret; +} + +static int fusb302_set_src_current(struct fusb302_chip *chip, + enum src_current_status status) +{ + int ret = 0; + + chip->src_current_status = status; + switch (status) { + case SRC_CURRENT_DEFAULT: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_DEF); + break; + case SRC_CURRENT_MEDIUM: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_MED); + break; + case SRC_CURRENT_HIGH: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_HIGH); + break; + default: + break; + } + + return ret; +} + +static int fusb302_set_toggling(struct fusb302_chip *chip, + enum toggling_mode mode) +{ + int ret = 0; + + /* first disable toggling */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* mask interrupts for SRC or SNK */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) + return ret; + chip->intr_bc_lvl = false; + chip->intr_comp_chng = false; + /* configure toggling mode: none/snk/src/drp */ + switch (mode) { + case TOGGLINE_MODE_OFF: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_NONE); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SNK: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_UFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SRC: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_DRP: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DRP); + if (ret < 0) + return ret; + break; + default: + break; + } + + if (mode == TOGGLINE_MODE_OFF) { + /* mask TOGDONE interrupt */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = false; + } else { + /* unmask TOGDONE interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = true; + /* start toggling */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* during toggling, consider cc as Open */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + } + chip->toggling_mode = mode; + + return ret; +} + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const enum src_current_status cc_src_current[] = { + [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM, + [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH, +}; + +static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + bool pull_up, pull_down; + u8 rd_mda; + + mutex_lock(&chip->lock); + switch (cc) { + case TYPEC_CC_OPEN: + pull_up = false; + pull_down = false; + break; + case TYPEC_CC_RD: + pull_up = false; + pull_down = true; + break; + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + pull_up = true; + pull_down = false; + break; + default: + fusb302_log(chip, "unsupported cc value %s", + typec_cc_status_name[cc]); + ret = -EINVAL; + goto done; + } + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, "cannot stop toggling, ret=%d", ret); + goto done; + } + ret = fusb302_set_cc_pull(chip, pull_up, pull_down); + if (ret < 0) { + fusb302_log(chip, + "cannot set cc pulling up %s, down %s, ret = %d", + pull_up ? "True" : "False", + pull_down ? "True" : "False", + ret); + goto done; + } + /* reset the cc status */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + /* adjust current for SRC */ + if (pull_up) { + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "cannot set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + } + /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ + if (pull_up) { + rd_mda = rd_mda_value[cc_src_current[cc]]; + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) { + fusb302_log(chip, + "cannot set SRC measure value, ret=%d", + ret); + goto done; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_bc_lvl = false; + chip->intr_comp_chng = true; + } + if (pull_down) { + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_bc_lvl = true; + chip->intr_comp_chng = false; + } + fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + mutex_lock(&chip->lock); + *cc1 = chip->cc1; + *cc2 = chip->cc2; + fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1], + typec_cc_status_name[*cc2]); + mutex_unlock(&chip->lock); + + return 0; +} + +static int tcpm_set_polarity(struct tcpc_dev *dev, + enum typec_cc_polarity polarity) +{ + return 0; +} + +static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches0_data = 0x00; + u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2; + + mutex_lock(&chip->lock); + if (chip->vconn_on == on) { + fusb302_log(chip, "vconn is already %s", on ? "On" : "Off"); + goto done; + } + if (on) { + switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_VCONN_CC2 : + FUSB_REG_SWITCHES0_VCONN_CC1; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) + goto done; + chip->vconn_on = on; + fusb302_log(chip, "vconn := %s", on ? "On" : "Off"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (chip->vbus_on == on) { + fusb302_log(chip, "vbus is already %s", on ? "On" : "Off"); + } else { + if (on) + ret = regulator_enable(chip->vbus); + else + ret = regulator_disable(chip->vbus); + if (ret < 0) { + fusb302_log(chip, "cannot %s vbus regulator, ret=%d", + on ? "enable" : "disable", ret); + goto done; + } + chip->vbus_on = on; + fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); + } + if (chip->charge_on == charge) + fusb302_log(chip, "charge is already %s", + charge ? "On" : "Off"); + else + chip->charge_on = charge; + +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)", + max_ma, mv); + + return 0; +} + +static int fusb302_pd_tx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_TX_FLUSH); +} + +static int fusb302_pd_rx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1, + FUSB_REG_CONTROL1_RX_FLUSH); +} + +static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on) +{ + if (on) + return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); + return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); +} + +static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on) +{ + int ret = 0; + u8 mask_interrupts = FUSB_REG_MASK_COLLISION; + u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL | + FUSB_REG_MASKA_HARDSENT | + FUSB_REG_MASKA_TX_SUCCESS | + FUSB_REG_MASKA_HARDRESET; + u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT; + + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts); + return ret; +} + +static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = fusb302_pd_rx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_tx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd tx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_set_auto_goodcrc(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d", + on ? "on" : "off", ret); + goto done; + } + ret = fusb302_pd_set_interrupts(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d", + on ? "on" : "off", ret); + goto done; + } + fusb302_log(chip, "pd := %s", on ? "on" : "off"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static const char * const typec_role_name[] = { + [TYPEC_SINK] = "Sink", + [TYPEC_SOURCE] = "Source", +}; + +static const char * const typec_data_role_name[] = { + [TYPEC_DEVICE] = "Device", + [TYPEC_HOST] = "Host", +}; + +static int tcpm_set_roles(struct tcpc_dev *dev, bool attached, + enum typec_role pwr, enum typec_data_role data) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE | + FUSB_REG_SWITCHES1_DATAROLE; + u8 switches1_data = 0x00; + + mutex_lock(&chip->lock); + if (pwr == TYPEC_SOURCE) + switches1_data |= FUSB_REG_SWITCHES1_POWERROLE; + if (data == TYPEC_HOST) + switches1_data |= FUSB_REG_SWITCHES1_DATAROLE; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) { + fusb302_log(chip, "unable to set pd header %s, %s, ret=%d", + typec_role_name[pwr], typec_data_role_name[data], + ret); + goto done; + } + fusb302_log(chip, "pd header := %s, %s", typec_role_name[pwr], + typec_data_role_name[data]); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_start_drp_toggling(struct tcpc_dev *dev, + enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "unable to set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP); + if (ret < 0) { + fusb302_log(chip, + "unable to start drp toggling, ret=%d", ret); + goto done; + } + fusb302_log(chip, "start drp toggling"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_pd_send_message(struct fusb302_chip *chip, + const struct pd_message *msg) +{ + int ret = 0; + u8 buf[40]; + u8 pos = 0; + int len; + + /* SOP tokens */ + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC2; + + len = pd_header_cnt(msg->header) * 4; + /* plug 2 for header */ + len += 2; + if (len > 0x1F) { + fusb302_log(chip, + "PD message too long %d (incl. header)", len); + return -EINVAL; + } + /* packsym tells the FUSB302 chip that the next X bytes are payload */ + buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F); + buf[pos++] = msg->header & 0xFF; + buf[pos++] = (msg->header >> 8) & 0xFF; + + len -= 2; + memcpy(&buf[pos], msg->payload, len); + pos += len; + + /* CRC */ + buf[pos++] = FUSB302_TKN_JAMCRC; + /* EOP */ + buf[pos++] = FUSB302_TKN_EOP; + /* turn tx off after sending message */ + buf[pos++] = FUSB302_TKN_TXOFF; + /* start transmission */ + buf[pos++] = FUSB302_TKN_TXON; + + ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); + if (ret < 0) + return ret; + fusb302_log(chip, "sending PD message header: %x", msg->header); + fusb302_log(chip, "sending PD message len: %d", len); + + return ret; +} + +static int fusb302_pd_send_hardreset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_SEND_HARDRESET); +} + +static const char * const transmit_type_name[] = { + [TCPC_TX_SOP] = "SOP", + [TCPC_TX_SOP_PRIME] = "SOP'", + [TCPC_TX_SOP_PRIME_PRIME] = "SOP''", + [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'", + [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''", + [TCPC_TX_HARD_RESET] = "HARD_RESET", + [TCPC_TX_CABLE_RESET] = "CABLE_RESET", + [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2", +}; + +static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + switch (type) { + case TCPC_TX_SOP: + ret = fusb302_pd_send_message(chip, msg); + if (ret < 0) + fusb302_log(chip, + "cannot send PD message, ret=%d", ret); + break; + case TCPC_TX_HARD_RESET: + ret = fusb302_pd_send_hardreset(chip); + if (ret < 0) + fusb302_log(chip, + "cannot send hardreset, ret=%d", ret); + break; + default: + fusb302_log(chip, "type %s not supported", + transmit_type_name[type]); + ret = -EINVAL; + } + mutex_unlock(&chip->lock); + + return ret; +} + +static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl) +{ + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX) + return TYPEC_CC_RP_3_0; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230) + return TYPEC_CC_RP_1_5; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600) + return TYPEC_CC_RP_DEF; + return TYPEC_CC_OPEN; +} + +static void fusb302_bc_lvl_handler_work(struct work_struct *work) +{ + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, + bc_lvl_handler.work); + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_status cc_status; + + mutex_lock(&chip->lock); + if (!chip->intr_bc_lvl) { + fusb302_log(chip, "BC_LVL interrupt is turned off, abort"); + goto done; + } + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, "BC_LVL handler, status0=0x%02x", status0); + if (status0 & FUSB_REG_STATUS0_ACTIVITY) { + fusb302_log(chip, "CC activities detected, delay handling"); + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + goto done; + } + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status = fusb302_bc_lvl_to_cc(bc_lvl); + if (chip->cc_polarity == TYPEC_POLARITY_CC1) { + if (chip->cc1 != cc_status) { + fusb302_log(chip, "cc1: %s -> %s", + typec_cc_status_name[chip->cc1], + typec_cc_status_name[cc_status]); + chip->cc1 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } else { + if (chip->cc2 != cc_status) { + fusb302_log(chip, "cc2: %s -> %s", + typec_cc_status_name[chip->cc2], + typec_cc_status_name[cc_status]); + chip->cc2 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } + +done: + mutex_unlock(&chip->lock); +} + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), +}; + +static const struct tcpc_config fusb302_tcpc_config = { + .src_pdo = src_pdo, + .nr_src_pdo = ARRAY_SIZE(src_pdo), + .snk_pdo = snk_pdo, + .nr_snk_pdo = ARRAY_SIZE(snk_pdo), + .max_snk_mv = 9000, + .max_snk_ma = 3000, + .max_snk_mw = 27000, + .operating_snk_mw = 2500, + .type = TYPEC_PORT_DRP, + .default_role = TYPEC_SINK, + .alt_modes = NULL, +}; + +static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) +{ + fusb302_tcpc_dev->config = &fusb302_tcpc_config; + fusb302_tcpc_dev->init = tcpm_init; + fusb302_tcpc_dev->get_vbus = tcpm_get_vbus; + fusb302_tcpc_dev->set_cc = tcpm_set_cc; + fusb302_tcpc_dev->get_cc = tcpm_get_cc; + fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; + fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; + fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; + fusb302_tcpc_dev->set_current_limit = tcpm_set_current_limit; + fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; + fusb302_tcpc_dev->set_roles = tcpm_set_roles; + fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; + fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; + fusb302_tcpc_dev->mux = NULL; +} + +static const char * const cc_polarity_name[] = { + [TYPEC_POLARITY_CC1] = "Polarity_CC1", + [TYPEC_POLARITY_CC2] = "Polarity_CC2", +}; + +static int fusb302_set_cc_polarity(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity) +{ + int ret = 0; + u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2 | + FUSB_REG_SWITCHES0_MEAS_CC1 | + FUSB_REG_SWITCHES0_MEAS_CC2; + u8 switches0_data = 0x00; + u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN | + FUSB_REG_SWITCHES1_TXCC2_EN; + u8 switches1_data = 0x00; + + if (cc_polarity == TYPEC_POLARITY_CC1) { + switches0_data = FUSB_REG_SWITCHES0_MEAS_CC1; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2; + if (chip->pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN; + } else { + switches0_data = FUSB_REG_SWITCHES0_MEAS_CC2; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1; + if (chip->pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) + return ret; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) + return ret; + chip->cc_polarity = cc_polarity; + + return ret; +} + +static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, + u8 togdone_result) +{ + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set pull_up, pull_down */ + ret = fusb302_set_cc_pull(chip, false, true); + if (ret < 0) { + fusb302_log(chip, "cannot set cc to pull down, ret=%d", ret); + return ret; + } + /* set polarity */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity(chip, cc_polarity); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl); + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + fusb302_log(chip, "restart toggling as CC_OPEN detected"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, + "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* unmask bc_lvl interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask bc_lcl interrupt, ret=%d", ret); + return ret; + } + chip->intr_bc_lvl = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone_src(struct fusb302_chip *chip, + u8 togdone_result) +{ + /* + * - set polarity (measure cc, vconn, tx) + * - set pull_up, pull_down + * - set cc1, cc2, and update to tcpm_port + * - set I_COMP interrupt on + */ + int ret = 0; + u8 status0; + u8 ra_mda = ra_mda_value[chip->src_current_status]; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + bool ra_comp, rd_comp; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set pull_up, pull_down */ + ret = fusb302_set_cc_pull(chip, true, false); + if (ret < 0) { + fusb302_log(chip, "cannot set cc to pull up, ret=%d", ret); + return ret; + } + /* set polarity */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity(chip, cc_polarity); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + rd_comp = !!(status0 & FUSB_REG_STATUS0_COMP); + if (!rd_comp) { + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda); + if (ret < 0) + return ret; + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + ra_comp = !!(status0 & FUSB_REG_STATUS0_COMP); + } + if (rd_comp) + cc_status_active = TYPEC_CC_OPEN; + else if (ra_comp) + cc_status_active = TYPEC_CC_RD; + else + /* Ra is not supported, report as Open */ + cc_status_active = TYPEC_CC_OPEN; + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + fusb302_log(chip, "restart toggling as CC_OPEN detected"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, + "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + /* unmask comp_chng interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask bc_lcl interrupt, ret=%d", ret); + return ret; + } + chip->intr_comp_chng = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone(struct fusb302_chip *chip) +{ + int ret = 0; + u8 status1a; + u8 togdone_result; + + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); + if (ret < 0) + return ret; + togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) & + FUSB_REG_STATUS1A_TOGSS_MASK; + switch (togdone_result) { + case FUSB_REG_STATUS1A_TOGSS_SNK1: + case FUSB_REG_STATUS1A_TOGSS_SNK2: + return fusb302_handle_togdone_snk(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_SRC1: + case FUSB_REG_STATUS1A_TOGSS_SRC2: + return fusb302_handle_togdone_src(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_AA: + /* doesn't support */ + fusb302_log(chip, "AudioAccessory not supported"); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + default: + fusb302_log(chip, "TOGDONE with an invalid state: %d", + togdone_result); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + } + return ret; +} + +static int fusb302_pd_reset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_RESET, + FUSB_REG_RESET_PD_RESET); +} + +static int fusb302_pd_read_message(struct fusb302_chip *chip, + struct pd_message *msg) +{ + int ret = 0; + u8 token; + u8 crc[4]; + int len; + + /* first SOP token */ + ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token); + if (ret < 0) + return ret; + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2, + (u8 *)&msg->header); + if (ret < 0) + return ret; + len = pd_header_cnt(msg->header) * 4; + /* add 4 to length to include the CRC */ + if (len > PD_MAX_PAYLOAD * 4) { + fusb302_log(chip, "PD message too long %d", len); + return -EINVAL; + } + if (len > 0) { + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len, + (u8 *)msg->payload); + if (ret < 0) + return ret; + } + /* another 4 bytes to read CRC out */ + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); + if (ret < 0) + return ret; + fusb302_log(chip, "PD message header: %x", msg->header); + fusb302_log(chip, "PD message len: %d", len); + + return ret; +} + +static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) +{ + struct fusb302_chip *chip = dev_id; + int ret = 0; + u8 interrupt; + u8 interrupta; + u8 interruptb; + u8 status0; + bool vbus_present; + bool comp_result; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + struct pd_message pd_msg; + + mutex_lock(&chip->lock); + /* grab a snapshot of intr flags */ + intr_togdone = chip->intr_togdone; + intr_bc_lvl = chip->intr_bc_lvl; + intr_comp_chng = chip->intr_comp_chng; + + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, + "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x", + interrupt, interrupta, interruptb, status0); + + if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { + vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); + fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", + vbus_present ? "On" : "Off"); + if (vbus_present != chip->vbus_present) { + chip->vbus_present = vbus_present; + tcpm_vbus_change(chip->tcpm_port); + } + } + + if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { + fusb302_log(chip, "IRQ: TOGDONE"); + ret = fusb302_handle_togdone(chip); + if (ret < 0) { + fusb302_log(chip, + "handle togdone error, ret=%d", ret); + goto done; + } + } + + if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { + fusb302_log(chip, "IRQ: BC_LVL, handler pending"); + /* + * as BC_LVL interrupt can be affected by PD activity, + * apply delay to for the handler to wait for the PD + * signaling to finish. + */ + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + } + + if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { + comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); + fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", + comp_result ? "true" : "false"); + if (comp_result) { + /* cc level > Rd_threashold, detach */ + if (chip->cc_polarity == TYPEC_POLARITY_CC1) + chip->cc1 = TYPEC_CC_OPEN; + else + chip->cc2 = TYPEC_CC_OPEN; + tcpm_cc_change(chip->tcpm_port); + } + } + + if (interrupt & FUSB_REG_INTERRUPT_COLLISION) { + fusb302_log(chip, "IRQ: PD collision"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) { + fusb302_log(chip, "IRQ: PD retry failed"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) { + fusb302_log(chip, "IRQ: PD hardreset sent"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + } + + if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { + fusb302_log(chip, "IRQ: PD tx success"); + /* read out the received good CRC */ + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, "cannot read in GCRC, ret=%d", ret); + goto done; + } + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { + fusb302_log(chip, "IRQ: PD received hardreset"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_hard_reset(chip->tcpm_port); + } + + if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { + fusb302_log(chip, "IRQ: PD sent good CRC"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, + "cannot read in PD message, ret=%d", ret); + goto done; + } + tcpm_pd_receive(chip->tcpm_port, &pd_msg); + } +done: + mutex_unlock(&chip->lock); + + return IRQ_HANDLED; +} + +static int init_gpio(struct fusb302_chip *chip) +{ + struct device_node *node; + int ret = 0; + + node = chip->dev->of_node; + chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0); + if (!gpio_is_valid(chip->gpio_int_n)) { + ret = chip->gpio_int_n; + fusb302_log(chip, "cannot get named GPIO Int_N, ret=%d", ret); + return ret; + } + ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n"); + if (ret < 0) { + fusb302_log(chip, "cannot request GPIO Int_N, ret=%d", ret); + return ret; + } + ret = gpio_direction_input(chip->gpio_int_n); + if (ret < 0) { + fusb302_log(chip, + "cannot set GPIO Int_N to input, ret=%d", ret); + gpio_free(chip->gpio_int_n); + return ret; + } + ret = gpio_to_irq(chip->gpio_int_n); + if (ret < 0) { + fusb302_log(chip, + "cannot request IRQ for GPIO Int_N, ret=%d", ret); + gpio_free(chip->gpio_int_n); + return ret; + } + chip->gpio_int_n_irq = ret; + return 0; +} + +static int fusb302_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct fusb302_chip *chip; + struct i2c_adapter *adapter; + int ret = 0; + + adapter = to_i2c_adapter(client->dev.parent); + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, + "I2C/SMBus block functionality not supported!\n"); + return -ENODEV; + } + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->i2c_client = client; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->lock); + + ret = fusb302_debugfs_init(chip); + if (ret < 0) + return ret; + + chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); + if (!chip->wq) { + ret = -ENOMEM; + goto clear_client_data; + } + INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); + init_tcpc_dev(&chip->tcpc_dev); + + chip->vbus = devm_regulator_get(chip->dev, "vbus"); + if (IS_ERR(chip->vbus)) { + ret = PTR_ERR(chip->vbus); + goto destroy_workqueue; + } + + ret = init_gpio(chip); + if (ret < 0) + goto destroy_workqueue; + + chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) { + ret = PTR_ERR(chip->tcpm_port); + fusb302_log(chip, "cannot register tcpm port, ret=%d", ret); + goto destroy_workqueue; + } + + ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq, + NULL, fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); + if (ret < 0) { + fusb302_log(chip, + "cannot request IRQ for GPIO Int_N, ret=%d", ret); + goto tcpm_unregister_port; + } + enable_irq_wake(chip->gpio_int_n_irq); + return ret; + +tcpm_unregister_port: + tcpm_unregister_port(chip->tcpm_port); +destroy_workqueue: + destroy_workqueue(chip->wq); +clear_client_data: + i2c_set_clientdata(client, NULL); + fusb302_debugfs_exit(chip); + + return ret; +} + +static int fusb302_remove(struct i2c_client *client) +{ + struct fusb302_chip *chip = i2c_get_clientdata(client); + + tcpm_unregister_port(chip->tcpm_port); + destroy_workqueue(chip->wq); + i2c_set_clientdata(client, NULL); + fusb302_debugfs_exit(chip); + + return 0; +} + +static int fusb302_pm_suspend(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + + if (atomic_read(&chip->i2c_busy)) + return -EBUSY; + atomic_set(&chip->pm_suspend, 1); + + return 0; +} + +static int fusb302_pm_resume(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + + atomic_set(&chip->pm_suspend, 0); + + return 0; +} + +static const struct of_device_id fusb302_dt_match[] = { + {.compatible = "fcs,fusb302"}, + {}, +}; + +static const struct i2c_device_id fusb302_i2c_device_id[] = { + {"typec_fusb302", 0}, + {}, +}; + +static const struct dev_pm_ops fusb302_pm_ops = { + .suspend = fusb302_pm_suspend, + .resume = fusb302_pm_resume, +}; + +static struct i2c_driver fusb302_driver = { + .driver = { + .name = "typec_fusb302", + .pm = &fusb302_pm_ops, + .of_match_table = of_match_ptr(fusb302_dt_match), + }, + .probe = fusb302_probe, + .remove = fusb302_remove, + .id_table = fusb302_i2c_device_id, +}; +module_i2c_driver(fusb302_driver); + +MODULE_AUTHOR("Yueyao Zhu <yueyao.zhu@gmail.com>"); +MODULE_DESCRIPTION("Fairchild FUSB302 Type-C Chip Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/typec/fusb302/fusb302_reg.h b/drivers/staging/typec/fusb302/fusb302_reg.h new file mode 100644 index 000000000000..0682e63de773 --- /dev/null +++ b/drivers/staging/typec/fusb302/fusb302_reg.h @@ -0,0 +1,186 @@ +/* + * Copyright 2016-2017 Google, 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. + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#ifndef FUSB302_REG_H +#define FUSB302_REG_H + +#define FUSB_REG_DEVICE_ID 0x01 +#define FUSB_REG_SWITCHES0 0x02 +#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7) +#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6) +#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5) +#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4) +#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3) +#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2) +#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1) +#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0) +#define FUSB_REG_SWITCHES1 0x03 +#define FUSB_REG_SWITCHES1_POWERROLE BIT(7) +#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6) +#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5) +#define FUSB_REG_SWITCHES1_DATAROLE BIT(4) +#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2) +#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) +#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) +#define FUSB_REG_MEASURE 0x04 +#define FUSB_REG_MEASURE_MDAC5 BIT(7) +#define FUSB_REG_MEASURE_MDAC4 BIT(6) +#define FUSB_REG_MEASURE_MDAC3 BIT(5) +#define FUSB_REG_MEASURE_MDAC2 BIT(4) +#define FUSB_REG_MEASURE_MDAC1 BIT(3) +#define FUSB_REG_MEASURE_MDAC0 BIT(2) +#define FUSB_REG_MEASURE_VBUS BIT(1) +#define FUSB_REG_MEASURE_XXXX5 BIT(0) +#define FUSB_REG_CONTROL0 0x06 +#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) +#define FUSB_REG_CONTROL0_INT_MASK BIT(5) +#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8) +#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4) +#define FUSB_REG_CONTROL0_TX_START BIT(0) +#define FUSB_REG_CONTROL1 0x07 +#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6) +#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5) +#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4) +#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2) +#define FUSB_REG_CONTROL1_ENSOP2 BIT(1) +#define FUSB_REG_CONTROL1_ENSOP1 BIT(0) +#define FUSB_REG_CONTROL2 0x08 +#define FUSB_REG_CONTROL2_MODE BIT(1) +#define FUSB_REG_CONTROL2_MODE_MASK (0x6) +#define FUSB_REG_CONTROL2_MODE_DFP (0x6) +#define FUSB_REG_CONTROL2_MODE_UFP (0x4) +#define FUSB_REG_CONTROL2_MODE_DRP (0x2) +#define FUSB_REG_CONTROL2_MODE_NONE (0x0) +#define FUSB_REG_CONTROL2_TOGGLE BIT(0) +#define FUSB_REG_CONTROL3 0x09 +#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6) +#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */ +#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4) +#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3) +#define FUSB_REG_CONTROL3_N_RETRIES BIT(1) +#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4) +#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2) +#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0) +#define FUSB_REG_MASK 0x0A +#define FUSB_REG_MASK_VBUSOK BIT(7) +#define FUSB_REG_MASK_ACTIVITY BIT(6) +#define FUSB_REG_MASK_COMP_CHNG BIT(5) +#define FUSB_REG_MASK_CRC_CHK BIT(4) +#define FUSB_REG_MASK_ALERT BIT(3) +#define FUSB_REG_MASK_WAKE BIT(2) +#define FUSB_REG_MASK_COLLISION BIT(1) +#define FUSB_REG_MASK_BC_LVL BIT(0) +#define FUSB_REG_POWER 0x0B +#define FUSB_REG_POWER_PWR BIT(0) +#define FUSB_REG_POWER_PWR_LOW 0x1 +#define FUSB_REG_POWER_PWR_MEDIUM 0x3 +#define FUSB_REG_POWER_PWR_HIGH 0x7 +#define FUSB_REG_POWER_PWR_ALL 0xF +#define FUSB_REG_RESET 0x0C +#define FUSB_REG_RESET_PD_RESET BIT(1) +#define FUSB_REG_RESET_SW_RESET BIT(0) +#define FUSB_REG_MASKA 0x0E +#define FUSB_REG_MASKA_OCP_TEMP BIT(7) +#define FUSB_REG_MASKA_TOGDONE BIT(6) +#define FUSB_REG_MASKA_SOFTFAIL BIT(5) +#define FUSB_REG_MASKA_RETRYFAIL BIT(4) +#define FUSB_REG_MASKA_HARDSENT BIT(3) +#define FUSB_REG_MASKA_TX_SUCCESS BIT(2) +#define FUSB_REG_MASKA_SOFTRESET BIT(1) +#define FUSB_REG_MASKA_HARDRESET BIT(0) +#define FUSB_REG_MASKB 0x0F +#define FUSB_REG_MASKB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0A 0x3C +#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5) +#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4) +#define FUSB_REG_STATUS0A_POWER BIT(2) +#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) +#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) +#define FUSB_REG_STATUS1A 0x3D +#define FUSB_REG_STATUS1A_TOGSS BIT(3) +#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 +#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 +#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 +#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5 +#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6 +#define FUSB_REG_STATUS1A_TOGSS_AA 0x7 +#define FUSB_REG_STATUS1A_TOGSS_POS (3) +#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7) +#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2) +#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1) +#define FUSB_REG_STATUS1A_RXSOP BIT(0) +#define FUSB_REG_INTERRUPTA 0x3E +#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7) +#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6) +#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5) +#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4) +#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3) +#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2) +#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1) +#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0) +#define FUSB_REG_INTERRUPTB 0x3F +#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0 0x40 +#define FUSB_REG_STATUS0_VBUSOK BIT(7) +#define FUSB_REG_STATUS0_ACTIVITY BIT(6) +#define FUSB_REG_STATUS0_COMP BIT(5) +#define FUSB_REG_STATUS0_CRC_CHK BIT(4) +#define FUSB_REG_STATUS0_ALERT BIT(3) +#define FUSB_REG_STATUS0_WAKE BIT(2) +#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03 +#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0 +#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1 +#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2 +#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3 +#define FUSB_REG_STATUS0_BC_LVL1 BIT(1) +#define FUSB_REG_STATUS0_BC_LVL0 BIT(0) +#define FUSB_REG_STATUS1 0x41 +#define FUSB_REG_STATUS1_RXSOP2 BIT(7) +#define FUSB_REG_STATUS1_RXSOP1 BIT(6) +#define FUSB_REG_STATUS1_RX_EMPTY BIT(5) +#define FUSB_REG_STATUS1_RX_FULL BIT(4) +#define FUSB_REG_STATUS1_TX_EMPTY BIT(3) +#define FUSB_REG_STATUS1_TX_FULL BIT(2) +#define FUSB_REG_INTERRUPT 0x42 +#define FUSB_REG_INTERRUPT_VBUSOK BIT(7) +#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6) +#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5) +#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4) +#define FUSB_REG_INTERRUPT_ALERT BIT(3) +#define FUSB_REG_INTERRUPT_WAKE BIT(2) +#define FUSB_REG_INTERRUPT_COLLISION BIT(1) +#define FUSB_REG_INTERRUPT_BC_LVL BIT(0) +#define FUSB_REG_FIFOS 0x43 + +/* Tokens defined for the FUSB302 TX FIFO */ +enum fusb302_txfifo_tokens { + FUSB302_TKN_TXON = 0xA1, + FUSB302_TKN_SYNC1 = 0x12, + FUSB302_TKN_SYNC2 = 0x13, + FUSB302_TKN_SYNC3 = 0x1B, + FUSB302_TKN_RST1 = 0x15, + FUSB302_TKN_RST2 = 0x16, + FUSB302_TKN_PACKSYM = 0x80, + FUSB302_TKN_JAMCRC = 0xFF, + FUSB302_TKN_EOP = 0x14, + FUSB302_TKN_TXOFF = 0xFE, +}; + +#endif diff --git a/drivers/staging/typec/pd.h b/drivers/staging/typec/pd.h new file mode 100644 index 000000000000..8d97bdb95f23 --- /dev/null +++ b/drivers/staging/typec/pd.h @@ -0,0 +1,281 @@ +/* + * Copyright 2015-2017 Google, 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. + */ + +#ifndef __LINUX_USB_PD_H +#define __LINUX_USB_PD_H + +#include <linux/types.h> +#include <linux/usb/typec.h> + +/* USB PD Messages */ +enum pd_ctrl_msg_type { + /* 0 Reserved */ + PD_CTRL_GOOD_CRC = 1, + PD_CTRL_GOTO_MIN = 2, + PD_CTRL_ACCEPT = 3, + PD_CTRL_REJECT = 4, + PD_CTRL_PING = 5, + PD_CTRL_PS_RDY = 6, + PD_CTRL_GET_SOURCE_CAP = 7, + PD_CTRL_GET_SINK_CAP = 8, + PD_CTRL_DR_SWAP = 9, + PD_CTRL_PR_SWAP = 10, + PD_CTRL_VCONN_SWAP = 11, + PD_CTRL_WAIT = 12, + PD_CTRL_SOFT_RESET = 13, + /* 14-15 Reserved */ +}; + +enum pd_data_msg_type { + /* 0 Reserved */ + PD_DATA_SOURCE_CAP = 1, + PD_DATA_REQUEST = 2, + PD_DATA_BIST = 3, + PD_DATA_SINK_CAP = 4, + /* 5-14 Reserved */ + PD_DATA_VENDOR_DEF = 15, +}; + +#define PD_REV10 0x0 +#define PD_REV20 0x1 + +#define PD_HEADER_CNT_SHIFT 12 +#define PD_HEADER_CNT_MASK 0x7 +#define PD_HEADER_ID_SHIFT 9 +#define PD_HEADER_ID_MASK 0x7 +#define PD_HEADER_PWR_ROLE BIT(8) +#define PD_HEADER_REV_SHIFT 6 +#define PD_HEADER_REV_MASK 0x3 +#define PD_HEADER_DATA_ROLE BIT(5) +#define PD_HEADER_TYPE_SHIFT 0 +#define PD_HEADER_TYPE_MASK 0xf + +#define PD_HEADER(type, pwr, data, id, cnt) \ + ((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) | \ + ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) | \ + ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) | \ + (PD_REV20 << PD_HEADER_REV_SHIFT) | \ + (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) | \ + (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT)) + +#define PD_HEADER_LE(type, pwr, data, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt))) + +static inline unsigned int pd_header_cnt(u16 header) +{ + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +} + +static inline unsigned int pd_header_cnt_le(__le16 header) +{ + return pd_header_cnt(le16_to_cpu(header)); +} + +static inline unsigned int pd_header_type(u16 header) +{ + return (header >> PD_HEADER_TYPE_SHIFT) & PD_HEADER_TYPE_MASK; +} + +static inline unsigned int pd_header_type_le(__le16 header) +{ + return pd_header_type(le16_to_cpu(header)); +} + +#define PD_MAX_PAYLOAD 7 + +struct pd_message { + __le16 header; + __le32 payload[PD_MAX_PAYLOAD]; +} __packed; + +/* PDO: Power Data Object */ +#define PDO_MAX_OBJECTS 7 + +enum pd_pdo_type { + PDO_TYPE_FIXED = 0, + PDO_TYPE_BATT = 1, + PDO_TYPE_VAR = 2, +}; + +#define PDO_TYPE_SHIFT 30 +#define PDO_TYPE_MASK 0x3 + +#define PDO_TYPE(t) ((t) << PDO_TYPE_SHIFT) + +#define PDO_VOLT_MASK 0x3ff +#define PDO_CURR_MASK 0x3ff +#define PDO_PWR_MASK 0x3ff + +#define PDO_FIXED_DUAL_ROLE BIT(29) /* Power role swap supported */ +#define PDO_FIXED_SUSPEND BIT(28) /* USB Suspend supported (Source) */ +#define PDO_FIXED_HIGHER_CAP BIT(28) /* Requires more than vSafe5V (Sink) */ +#define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */ +#define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */ +#define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */ +#define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_FIXED_CURR_SHIFT 0 /* 10mA units */ + +#define PDO_FIXED_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT) +#define PDO_FIXED_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT) + +#define PDO_FIXED(mv, ma, flags) \ + (PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \ + PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma)) + +#define PDO_BATT_MAX_VOLT_SHIFT 20 /* 50mV units */ +#define PDO_BATT_MIN_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */ + +#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MIN_VOLT_SHIFT) +#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MAX_VOLT_SHIFT) +#define PDO_BATT_MAX_POWER(mw) ((((mw) / 250) & PDO_PWR_MASK) << PDO_BATT_MAX_PWR_SHIFT) + +#define PDO_BATT(min_mv, max_mv, max_mw) \ + (PDO_TYPE(PDO_TYPE_BATT) | PDO_BATT_MIN_VOLT(min_mv) | \ + PDO_BATT_MAX_VOLT(max_mv) | PDO_BATT_MAX_POWER(max_mw)) + +#define PDO_VAR_MAX_VOLT_SHIFT 20 /* 50mV units */ +#define PDO_VAR_MIN_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_VAR_MAX_CURR_SHIFT 0 /* 10mA units */ + +#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT) +#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT) +#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT) + +#define PDO_VAR(min_mv, max_mv, max_ma) \ + (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) | \ + PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma)) + +static inline enum pd_pdo_type pdo_type(u32 pdo) +{ + return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; +} + +static inline unsigned int pdo_fixed_voltage(u32 pdo) +{ + return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_min_voltage(u32 pdo) +{ + return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_max_voltage(u32 pdo) +{ + return ((pdo >> PDO_VAR_MAX_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_max_current(u32 pdo) +{ + return ((pdo >> PDO_VAR_MAX_CURR_SHIFT) & PDO_CURR_MASK) * 10; +} + +static inline unsigned int pdo_max_power(u32 pdo) +{ + return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250; +} + +/* RDO: Request Data Object */ +#define RDO_OBJ_POS_SHIFT 28 +#define RDO_OBJ_POS_MASK 0x7 +#define RDO_GIVE_BACK BIT(27) /* Supports reduced operating current */ +#define RDO_CAP_MISMATCH BIT(26) /* Not satisfied by source caps */ +#define RDO_USB_COMM BIT(25) /* USB communications capable */ +#define RDO_NO_SUSPEND BIT(24) /* USB Suspend not supported */ + +#define RDO_PWR_MASK 0x3ff +#define RDO_CURR_MASK 0x3ff + +#define RDO_FIXED_OP_CURR_SHIFT 10 +#define RDO_FIXED_MAX_CURR_SHIFT 0 + +#define RDO_OBJ(idx) (((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT) + +#define PDO_FIXED_OP_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_OP_CURR_SHIFT) +#define PDO_FIXED_MAX_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_MAX_CURR_SHIFT) + +#define RDO_FIXED(idx, op_ma, max_ma, flags) \ + (RDO_OBJ(idx) | (flags) | \ + PDO_FIXED_OP_CURR(op_ma) | PDO_FIXED_MAX_CURR(max_ma)) + +#define RDO_BATT_OP_PWR_SHIFT 10 /* 250mW units */ +#define RDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */ + +#define RDO_BATT_OP_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_OP_PWR_SHIFT) +#define RDO_BATT_MAX_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_MAX_PWR_SHIFT) + +#define RDO_BATT(idx, op_mw, max_mw, flags) \ + (RDO_OBJ(idx) | (flags) | \ + RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw)) + +static inline unsigned int rdo_index(u32 rdo) +{ + return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK; +} + +static inline unsigned int rdo_op_current(u32 rdo) +{ + return ((rdo >> RDO_FIXED_OP_CURR_SHIFT) & RDO_CURR_MASK) * 10; +} + +static inline unsigned int rdo_max_current(u32 rdo) +{ + return ((rdo >> RDO_FIXED_MAX_CURR_SHIFT) & + RDO_CURR_MASK) * 10; +} + +static inline unsigned int rdo_op_power(u32 rdo) +{ + return ((rdo >> RDO_BATT_OP_PWR_SHIFT) & RDO_PWR_MASK) * 250; +} + +static inline unsigned int rdo_max_power(u32 rdo) +{ + return ((rdo >> RDO_BATT_MAX_PWR_SHIFT) & RDO_PWR_MASK) * 250; +} + +/* USB PD timers and counters */ +#define PD_T_NO_RESPONSE 5000 /* 4.5 - 5.5 seconds */ +#define PD_T_DB_DETECT 10000 /* 10 - 15 seconds */ +#define PD_T_SEND_SOURCE_CAP 150 /* 100 - 200 ms */ +#define PD_T_SENDER_RESPONSE 60 /* 24 - 30 ms, relaxed */ +#define PD_T_SOURCE_ACTIVITY 45 +#define PD_T_SINK_ACTIVITY 135 +#define PD_T_SINK_WAIT_CAP 240 +#define PD_T_PS_TRANSITION 500 +#define PD_T_SRC_TRANSITION 35 +#define PD_T_DRP_SNK 40 +#define PD_T_DRP_SRC 30 +#define PD_T_PS_SOURCE_OFF 920 +#define PD_T_PS_SOURCE_ON 480 +#define PD_T_PS_HARD_RESET 30 +#define PD_T_SRC_RECOVER 760 +#define PD_T_SRC_RECOVER_MAX 1000 +#define PD_T_SRC_TURN_ON 275 +#define PD_T_SAFE_0V 650 +#define PD_T_VCONN_SOURCE_ON 100 +#define PD_T_SINK_REQUEST 100 /* 100 ms minimum */ +#define PD_T_ERROR_RECOVERY 100 /* minimum 25 is insufficient */ + +#define PD_T_DRP_TRY 100 /* 75 - 150 ms */ +#define PD_T_DRP_TRYWAIT 600 /* 400 - 800 ms */ + +#define PD_T_CC_DEBOUNCE 200 /* 100 - 200 ms */ +#define PD_T_PD_DEBOUNCE 20 /* 10 - 20 ms */ + +#define PD_N_CAPS_COUNT (PD_T_NO_RESPONSE / PD_T_SEND_SOURCE_CAP) +#define PD_N_HARD_RESET_COUNT 2 + +#endif /* __LINUX_USB_PD_H */ diff --git a/drivers/w1/w1_log.h b/drivers/staging/typec/pd_bdo.h index dd1422b6afbb..90b94d9fea5d 100644 --- a/drivers/w1/w1_log.h +++ b/drivers/staging/typec/pd_bdo.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + * Copyright 2015-2017 Google, 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 @@ -12,20 +12,20 @@ * GNU General Public License for more details. */ -#ifndef __W1_LOG_H -#define __W1_LOG_H +#ifndef __LINUX_USB_PD_BDO_H +#define __LINUX_USB_PD_BDO_H -#define DEBUG +/* BDO : BIST Data Object */ +#define BDO_MODE_RECV (0 << 28) +#define BDO_MODE_TRANSMIT (1 << 28) +#define BDO_MODE_COUNTERS (2 << 28) +#define BDO_MODE_CARRIER0 (3 << 28) +#define BDO_MODE_CARRIER1 (4 << 28) +#define BDO_MODE_CARRIER2 (5 << 28) +#define BDO_MODE_CARRIER3 (6 << 28) +#define BDO_MODE_EYE (7 << 28) +#define BDO_MODE_TESTDATA (8 << 28) -#ifdef W1_DEBUG -# define assert(expr) do {} while (0) -#else -# define assert(expr) \ - if(unlikely(!(expr))) { \ - pr_err("Assertion failed! %s,%s,%s,line=%d\n", \ - #expr, __FILE__, __func__, __LINE__); \ - } -#endif - -#endif /* __W1_LOG_H */ +#define BDO_MODE_MASK(mode) ((mode) & 0xf0000000) +#endif diff --git a/drivers/staging/typec/pd_vdo.h b/drivers/staging/typec/pd_vdo.h new file mode 100644 index 000000000000..dba172e0e0d1 --- /dev/null +++ b/drivers/staging/typec/pd_vdo.h @@ -0,0 +1,249 @@ +/* + * Copyright 2015-2017 Google, 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. + */ + +#ifndef __LINUX_USB_PD_VDO_H +#define __LINUX_USB_PD_VDO_H + +#include "pd.h" + +/* + * VDO : Vendor Defined Message Object + * VDM object is minimum of VDM header + 6 additional data objects. + */ + +/* + * VDM header + * ---------- + * <31:16> :: SVID + * <15> :: VDM type ( 1b == structured, 0b == unstructured ) + * <14:13> :: Structured VDM version (can only be 00 == 1.0 currently) + * <12:11> :: reserved + * <10:8> :: object position (1-7 valid ... used for enter/exit mode only) + * <7:6> :: command type (SVDM only?) + * <5> :: reserved (SVDM), command type (UVDM) + * <4:0> :: command + */ +#define VDO_MAX_SIZE 7 +#define VDO(vid, type, custom) \ + (((vid) << 16) | \ + ((type) << 15) | \ + ((custom) & 0x7FFF)) + +#define VDO_SVDM_TYPE (1 << 15) +#define VDO_SVDM_VERS(x) ((x) << 13) +#define VDO_OPOS(x) ((x) << 8) +#define VDO_CMDT(x) ((x) << 6) +#define VDO_OPOS_MASK VDO_OPOS(0x7) +#define VDO_CMDT_MASK VDO_CMDT(0x3) + +#define CMDT_INIT 0 +#define CMDT_RSP_ACK 1 +#define CMDT_RSP_NAK 2 +#define CMDT_RSP_BUSY 3 + +/* reserved for SVDM ... for Google UVDM */ +#define VDO_SRC_INITIATOR (0 << 5) +#define VDO_SRC_RESPONDER (1 << 5) + +#define CMD_DISCOVER_IDENT 1 +#define CMD_DISCOVER_SVID 2 +#define CMD_DISCOVER_MODES 3 +#define CMD_ENTER_MODE 4 +#define CMD_EXIT_MODE 5 +#define CMD_ATTENTION 6 + +#define VDO_CMD_VENDOR(x) (((10 + (x)) & 0x1f)) + +/* ChromeOS specific commands */ +#define VDO_CMD_VERSION VDO_CMD_VENDOR(0) +#define VDO_CMD_SEND_INFO VDO_CMD_VENDOR(1) +#define VDO_CMD_READ_INFO VDO_CMD_VENDOR(2) +#define VDO_CMD_REBOOT VDO_CMD_VENDOR(5) +#define VDO_CMD_FLASH_ERASE VDO_CMD_VENDOR(6) +#define VDO_CMD_FLASH_WRITE VDO_CMD_VENDOR(7) +#define VDO_CMD_ERASE_SIG VDO_CMD_VENDOR(8) +#define VDO_CMD_PING_ENABLE VDO_CMD_VENDOR(10) +#define VDO_CMD_CURRENT VDO_CMD_VENDOR(11) +#define VDO_CMD_FLIP VDO_CMD_VENDOR(12) +#define VDO_CMD_GET_LOG VDO_CMD_VENDOR(13) +#define VDO_CMD_CCD_EN VDO_CMD_VENDOR(14) + +#define PD_VDO_VID(vdo) ((vdo) >> 16) +#define PD_VDO_SVDM(vdo) (((vdo) >> 15) & 1) +#define PD_VDO_OPOS(vdo) (((vdo) >> 8) & 0x7) +#define PD_VDO_CMD(vdo) ((vdo) & 0x1f) +#define PD_VDO_CMDT(vdo) (((vdo) >> 6) & 0x3) + +/* + * SVDM Identity request -> response + * + * Request is simply properly formatted SVDM header + * + * Response is 4 data objects: + * [0] :: SVDM header + * [1] :: Identitiy header + * [2] :: Cert Stat VDO + * [3] :: (Product | Cable) VDO + * [4] :: AMA VDO + * + */ +#define VDO_INDEX_HDR 0 +#define VDO_INDEX_IDH 1 +#define VDO_INDEX_CSTAT 2 +#define VDO_INDEX_CABLE 3 +#define VDO_INDEX_PRODUCT 3 +#define VDO_INDEX_AMA 4 + +/* + * SVDM Identity Header + * -------------------- + * <31> :: data capable as a USB host + * <30> :: data capable as a USB device + * <29:27> :: product type + * <26> :: modal operation supported (1b == yes) + * <25:16> :: Reserved, Shall be set to zero + * <15:0> :: USB-IF assigned VID for this cable vendor + */ +#define IDH_PTYPE_UNDEF 0 +#define IDH_PTYPE_HUB 1 +#define IDH_PTYPE_PERIPH 2 +#define IDH_PTYPE_PCABLE 3 +#define IDH_PTYPE_ACABLE 4 +#define IDH_PTYPE_AMA 5 + +#define VDO_IDH(usbh, usbd, ptype, is_modal, vid) \ + ((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27 \ + | (is_modal) << 26 | ((vid) & 0xffff)) + +#define PD_IDH_PTYPE(vdo) (((vdo) >> 27) & 0x7) +#define PD_IDH_VID(vdo) ((vdo) & 0xffff) +#define PD_IDH_MODAL_SUPP(vdo) ((vdo) & (1 << 26)) + +/* + * Cert Stat VDO + * ------------- + * <31:0> : USB-IF assigned XID for this cable + */ +#define PD_CSTAT_XID(vdo) (vdo) + +/* + * Product VDO + * ----------- + * <31:16> : USB Product ID + * <15:0> : USB bcdDevice + */ +#define VDO_PRODUCT(pid, bcd) (((pid) & 0xffff) << 16 | ((bcd) & 0xffff)) +#define PD_PRODUCT_PID(vdo) (((vdo) >> 16) & 0xffff) + +/* + * Cable VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:20> :: Reserved, Shall be set to zero + * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C) + * <17> :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle) + * <16:13> :: cable latency (0001 == <10ns(~1m length)) + * <12:11> :: cable termination type (11b == both ends active VCONN req) + * <10> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <9> :: SSTX2 Directionality support + * <8> :: SSRX1 Directionality support + * <7> :: SSRX2 Directionality support + * <6:5> :: Vbus current handling capability + * <4> :: Vbus through cable (0b == no, 1b == yes) + * <3> :: SOP" controller present? (0b == no, 1b == yes) + * <2:0> :: USB SS Signaling support + */ +#define CABLE_ATYPE 0 +#define CABLE_BTYPE 1 +#define CABLE_CTYPE 2 +#define CABLE_PLUG 0 +#define CABLE_RECEPTACLE 1 +#define CABLE_CURR_1A5 0 +#define CABLE_CURR_3A 1 +#define CABLE_CURR_5A 2 +#define CABLE_USBSS_U2_ONLY 0 +#define CABLE_USBSS_U31_GEN1 1 +#define CABLE_USBSS_U31_GEN2 2 +#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d, tx2d, rx1d, rx2d, cur,\ + vps, sopp, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18 \ + | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11 \ + | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 \ + | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3 \ + | ((usbss) & 0x7)) + +/* + * AMA VDO + * --------- + * <31:28> :: Cable HW version + * <27:24> :: Cable FW version + * <23:12> :: Reserved, Shall be set to zero + * <11> :: SSTX1 Directionality support (0b == fixed, 1b == cfgable) + * <10> :: SSTX2 Directionality support + * <9> :: SSRX1 Directionality support + * <8> :: SSRX2 Directionality support + * <7:5> :: Vconn power + * <4> :: Vconn power required + * <3> :: Vbus power required + * <2:0> :: USB SS Signaling support + */ +#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \ + (((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 \ + | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8 \ + | ((vcpwr) & 0x7) << 5 | (vcr) << 4 | (vbr) << 3 \ + | ((usbss) & 0x7)) + +#define PD_VDO_AMA_VCONN_REQ(vdo) (((vdo) >> 4) & 1) +#define PD_VDO_AMA_VBUS_REQ(vdo) (((vdo) >> 3) & 1) + +#define AMA_VCONN_PWR_1W 0 +#define AMA_VCONN_PWR_1W5 1 +#define AMA_VCONN_PWR_2W 2 +#define AMA_VCONN_PWR_3W 3 +#define AMA_VCONN_PWR_4W 4 +#define AMA_VCONN_PWR_5W 5 +#define AMA_VCONN_PWR_6W 6 +#define AMA_USBSS_U2_ONLY 0 +#define AMA_USBSS_U31_GEN1 1 +#define AMA_USBSS_U31_GEN2 2 +#define AMA_USBSS_BBONLY 3 + +/* + * SVDM Discover SVIDs request -> response + * + * Request is properly formatted VDM Header with discover SVIDs command. + * Response is a set of SVIDs of all all supported SVIDs with all zero's to + * mark the end of SVIDs. If more than 12 SVIDs are supported command SHOULD be + * repeated. + */ +#define VDO_SVID(svid0, svid1) (((svid0) & 0xffff) << 16 | ((svid1) & 0xffff)) +#define PD_VDO_SVID_SVID0(vdo) ((vdo) >> 16) +#define PD_VDO_SVID_SVID1(vdo) ((vdo) & 0xffff) + +/* USB-IF SIDs */ +#define USB_SID_PD 0xff00 /* power delivery */ +#define USB_SID_DISPLAYPORT 0xff01 +#define USB_SID_MHL 0xff02 /* Mobile High-Definition Link */ + +/* VDM command timeouts (in ms) */ + +#define PD_T_VDM_UNSTRUCTURED 500 +#define PD_T_VDM_BUSY 100 +#define PD_T_VDM_WAIT_MODE_E 100 +#define PD_T_VDM_SNDR_RSP 30 +#define PD_T_VDM_E_MODE 25 +#define PD_T_VDM_RCVR_RSP 15 + +#endif /* __LINUX_USB_PD_VDO_H */ diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c new file mode 100644 index 000000000000..5e5be74c7850 --- /dev/null +++ b/drivers/staging/typec/tcpci.c @@ -0,0 +1,526 @@ +/* + * Copyright 2015-2017 Google, 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. + * + * USB Type-C Port Controller Interface. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/usb/typec.h> + +#include "pd.h" +#include "tcpci.h" +#include "tcpm.h" + +#define PD_RETRY_COUNT 3 + +struct tcpci { + struct device *dev; + struct i2c_client *client; + + struct tcpm_port *port; + + struct regmap *regmap; + + bool controls_vbus; + + struct tcpc_dev tcpc; +}; + +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) +{ + return container_of(tcpc, struct tcpci, tcpc); +} + +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, + unsigned int *val) +{ + return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); +} + +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val) +{ + return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); +} + +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + switch (cc) { + case TYPEC_CC_RA: + reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + case TYPEC_CC_RD: + reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + case TYPEC_CC_RP_DEF: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_1_5: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_1_5 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_3_0: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_3_0 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_OPEN: + default: + reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + } + + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc, + enum typec_cc_status cc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = TCPC_ROLE_CTRL_DRP; + + switch (cc) { + default: + case TYPEC_CC_RP_DEF: + reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_1_5: + reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_3_0: + reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + } + + return regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); +} + +static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink) +{ + switch (cc) { + case 0x1: + return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA; + case 0x2: + return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD; + case 0x3: + if (sink) + return TYPEC_CC_RP_3_0; + case 0x0: + default: + return TYPEC_CC_OPEN; + } +} + +static int tcpci_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, enum typec_cc_status *cc2) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, ®); + if (ret < 0) + return ret; + + *cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) & + TCPC_CC_STATUS_CC1_MASK, + reg & TCPC_CC_STATUS_TERM); + *cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) & + TCPC_CC_STATUS_CC2_MASK, + reg & TCPC_CC_STATUS_TERM); + + return 0; +} + +static int tcpci_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity polarity) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, + (polarity == TYPEC_POLARITY_CC2) ? + TCPC_TCPC_CTRL_ORIENTATION : 0); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL, + enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT; + if (role == TYPEC_SOURCE) + reg |= TCPC_MSG_HDR_INFO_PWR_ROLE; + if (data == TYPEC_HOST) + reg |= TCPC_MSG_HDR_INFO_DATA_ROLE; + ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = 0; + int ret; + + if (enable) + reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_get_vbus(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + + return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); +} + +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + /* Disable both source and sink first before enabling anything */ + + if (!source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SRC_VBUS); + if (ret < 0) + return ret; + } + + if (!sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SINK_VBUS); + if (ret < 0) + return ret; + } + + if (source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SRC_VBUS_DEFAULT); + if (ret < 0) + return ret; + } + + if (sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SINK_VBUS); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tcpci_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg, cnt, header; + int ret; + + cnt = msg ? pd_header_cnt(msg->header) * 4 : 0; + ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); + if (ret < 0) + return ret; + + header = msg ? msg->header : 0; + ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); + if (ret < 0) + return ret; + + if (cnt > 0) { + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, + &msg->payload, cnt); + if (ret < 0) + return ret; + } + + reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | + (type << TCPC_TRANSMIT_TYPE_SHIFT); + ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_init(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */ + unsigned int reg; + int ret; + + while (time_before_eq(jiffies, timeout)) { + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + if (!(reg & TCPC_POWER_STATUS_UNINIT)) + break; + usleep_range(10000, 20000); + } + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + /* Clear all events */ + ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff); + if (ret < 0) + return ret; + + if (tcpci->controls_vbus) + reg = TCPC_POWER_STATUS_VBUS_PRES; + else + reg = 0; + ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg); + if (ret < 0) + return ret; + + reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED | + TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; + if (tcpci->controls_vbus) + reg |= TCPC_ALERT_POWER_STATUS; + return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); +} + +static irqreturn_t tcpci_irq(int irq, void *dev_id) +{ + struct tcpci *tcpci = dev_id; + unsigned int status, reg; + + tcpci_read16(tcpci, TCPC_ALERT, &status); + + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) + tcpci_write16(tcpci, TCPC_ALERT, + status & ~TCPC_ALERT_RX_STATUS); + + if (status & TCPC_ALERT_CC_STATUS) + tcpm_cc_change(tcpci->port); + + if (status & TCPC_ALERT_POWER_STATUS) { + regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, ®); + + /* + * If power status mask has been reset, then the TCPC + * has reset. + */ + if (reg == 0xff) + tcpm_tcpc_reset(tcpci->port); + else + tcpm_vbus_change(tcpci->port); + } + + if (status & TCPC_ALERT_RX_STATUS) { + struct pd_message msg; + unsigned int cnt; + + regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); + + tcpci_read16(tcpci, TCPC_RX_HDR, ®); + msg.header = reg; + + if (WARN_ON(cnt > sizeof(msg.payload))) + cnt = sizeof(msg.payload); + + if (cnt > 0) + regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, + &msg.payload, cnt); + + /* Read complete, clear RX status alert bit */ + tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + + tcpm_pd_receive(tcpci->port, &msg); + } + + if (status & TCPC_ALERT_RX_HARD_RST) + tcpm_pd_hard_reset(tcpci->port); + + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED); + + return IRQ_HANDLED; +} + +static const struct regmap_config tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */ +}; + +const struct tcpc_config tcpci_tcpc_config = { + .type = TYPEC_PORT_DFP, + .default_role = TYPEC_SINK, +}; + +static int tcpci_parse_config(struct tcpci *tcpci) +{ + tcpci->controls_vbus = true; /* XXX */ + + /* TODO: Populate struct tcpc_config from ACPI/device-tree */ + tcpci->tcpc.config = &tcpci_tcpc_config; + + return 0; +} + +static int tcpci_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct tcpci *tcpci; + int err; + + tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL); + if (!tcpci) + return -ENOMEM; + + tcpci->client = client; + tcpci->dev = &client->dev; + i2c_set_clientdata(client, tcpci); + tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); + if (IS_ERR(tcpci->regmap)) + return PTR_ERR(tcpci->regmap); + + tcpci->tcpc.init = tcpci_init; + tcpci->tcpc.get_vbus = tcpci_get_vbus; + tcpci->tcpc.set_vbus = tcpci_set_vbus; + tcpci->tcpc.set_cc = tcpci_set_cc; + tcpci->tcpc.get_cc = tcpci_get_cc; + tcpci->tcpc.set_polarity = tcpci_set_polarity; + tcpci->tcpc.set_vconn = tcpci_set_vconn; + tcpci->tcpc.start_drp_toggling = tcpci_start_drp_toggling; + + tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx; + tcpci->tcpc.set_roles = tcpci_set_roles; + tcpci->tcpc.pd_transmit = tcpci_pd_transmit; + + err = tcpci_parse_config(tcpci); + if (err < 0) + return err; + + /* Disable chip interrupts */ + tcpci_write16(tcpci, TCPC_ALERT_MASK, 0); + + err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL, + tcpci_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + dev_name(tcpci->dev), tcpci); + if (err < 0) + return err; + + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); + return PTR_ERR_OR_ZERO(tcpci->port); +} + +static int tcpci_remove(struct i2c_client *client) +{ + struct tcpci *tcpci = i2c_get_clientdata(client); + + tcpm_unregister_port(tcpci->port); + + return 0; +} + +static const struct i2c_device_id tcpci_id[] = { + { "tcpci", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcpci_id); + +#ifdef CONFIG_OF +static const struct of_device_id tcpci_of_match[] = { + { .compatible = "usb,tcpci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tcpci_of_match); +#endif + +static struct i2c_driver tcpci_i2c_driver = { + .driver = { + .name = "tcpci", + .of_match_table = of_match_ptr(tcpci_of_match), + }, + .probe = tcpci_probe, + .remove = tcpci_remove, + .id_table = tcpci_id, +}; +module_i2c_driver(tcpci_i2c_driver); + +MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/typec/tcpci.h b/drivers/staging/typec/tcpci.h new file mode 100644 index 000000000000..10b04c8723da --- /dev/null +++ b/drivers/staging/typec/tcpci.h @@ -0,0 +1,133 @@ +/* + * Copyright 2015-2017 Google, 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. + * + * USB Type-C Port Controller Interface. + */ + +#ifndef __LINUX_USB_TCPCI_H +#define __LINUX_USB_TCPCI_H + +#define TCPC_VENDOR_ID 0x0 +#define TCPC_PRODUCT_ID 0x2 +#define TCPC_BCD_DEV 0x4 +#define TCPC_TC_REV 0x6 +#define TCPC_PD_REV 0x8 +#define TCPC_PD_INT_REV 0xa + +#define TCPC_ALERT 0x10 +#define TCPC_ALERT_VBUS_DISCNCT BIT(11) +#define TCPC_ALERT_RX_BUF_OVF BIT(10) +#define TCPC_ALERT_FAULT BIT(9) +#define TCPC_ALERT_V_ALARM_LO BIT(8) +#define TCPC_ALERT_V_ALARM_HI BIT(7) +#define TCPC_ALERT_TX_SUCCESS BIT(6) +#define TCPC_ALERT_TX_DISCARDED BIT(5) +#define TCPC_ALERT_TX_FAILED BIT(4) +#define TCPC_ALERT_RX_HARD_RST BIT(3) +#define TCPC_ALERT_RX_STATUS BIT(2) +#define TCPC_ALERT_POWER_STATUS BIT(1) +#define TCPC_ALERT_CC_STATUS BIT(0) + +#define TCPC_ALERT_MASK 0x12 +#define TCPC_POWER_STATUS_MASK 0x14 +#define TCPC_FAULT_STATUS_MASK 0x15 +#define TCPC_CONFIG_STD_OUTPUT 0x18 + +#define TCPC_TCPC_CTRL 0x19 +#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) + +#define TCPC_ROLE_CTRL 0x1a +#define TCPC_ROLE_CTRL_DRP BIT(6) +#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 +#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 +#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 +#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 +#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 +#define TCPC_ROLE_CTRL_CC2_SHIFT 2 +#define TCPC_ROLE_CTRL_CC2_MASK 0x3 +#define TCPC_ROLE_CTRL_CC1_SHIFT 0 +#define TCPC_ROLE_CTRL_CC1_MASK 0x3 +#define TCPC_ROLE_CTRL_CC_RA 0x0 +#define TCPC_ROLE_CTRL_CC_RP 0x1 +#define TCPC_ROLE_CTRL_CC_RD 0x2 +#define TCPC_ROLE_CTRL_CC_OPEN 0x3 + +#define TCPC_FAULT_CTRL 0x1b + +#define TCPC_POWER_CTRL 0x1c +#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) + +#define TCPC_CC_STATUS 0x1d +#define TCPC_CC_STATUS_TERM BIT(4) +#define TCPC_CC_STATUS_CC2_SHIFT 2 +#define TCPC_CC_STATUS_CC2_MASK 0x3 +#define TCPC_CC_STATUS_CC1_SHIFT 0 +#define TCPC_CC_STATUS_CC1_MASK 0x3 + +#define TCPC_POWER_STATUS 0x1e +#define TCPC_POWER_STATUS_UNINIT BIT(6) +#define TCPC_POWER_STATUS_VBUS_DET BIT(3) +#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) + +#define TCPC_FAULT_STATUS 0x1f + +#define TCPC_COMMAND 0x23 +#define TCPC_CMD_WAKE_I2C 0x11 +#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 +#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 +#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 +#define TCPC_CMD_SINK_VBUS 0x55 +#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 +#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 +#define TCPC_CMD_SRC_VBUS_HIGH 0x88 +#define TCPC_CMD_LOOK4CONNECTION 0x99 +#define TCPC_CMD_RXONEMORE 0xAA +#define TCPC_CMD_I2C_IDLE 0xFF + +#define TCPC_DEV_CAP_1 0x24 +#define TCPC_DEV_CAP_2 0x26 +#define TCPC_STD_INPUT_CAP 0x28 +#define TCPC_STD_OUTPUT_CAP 0x29 + +#define TCPC_MSG_HDR_INFO 0x2e +#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) +#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) +#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 +#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 + +#define TCPC_RX_DETECT 0x2f +#define TCPC_RX_DETECT_HARD_RESET BIT(5) +#define TCPC_RX_DETECT_SOP BIT(0) + +#define TCPC_RX_BYTE_CNT 0x30 +#define TCPC_RX_BUF_FRAME_TYPE 0x31 +#define TCPC_RX_HDR 0x32 +#define TCPC_RX_DATA 0x34 /* through 0x4f */ + +#define TCPC_TRANSMIT 0x50 +#define TCPC_TRANSMIT_RETRY_SHIFT 4 +#define TCPC_TRANSMIT_RETRY_MASK 0x3 +#define TCPC_TRANSMIT_TYPE_SHIFT 0 +#define TCPC_TRANSMIT_TYPE_MASK 0x7 + +#define TCPC_TX_BYTE_CNT 0x51 +#define TCPC_TX_HDR 0x52 +#define TCPC_TX_DATA 0x54 /* through 0x6f */ + +#define TCPC_VBUS_VOLTAGE 0x70 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 +#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 +#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 +#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 + +#endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/staging/typec/tcpm.c b/drivers/staging/typec/tcpm.c new file mode 100644 index 000000000000..abba655ba00a --- /dev/null +++ b/drivers/staging/typec/tcpm.c @@ -0,0 +1,3465 @@ +/* + * Copyright 2015-2017 Google, 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. + * + * USB Power Delivery protocol stack. + */ + +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/proc_fs.h> +#include <linux/sched/clock.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/usb/typec.h> +#include <linux/workqueue.h> + +#include "pd.h" +#include "pd_vdo.h" +#include "pd_bdo.h" +#include "tcpm.h" + +#define FOREACH_STATE(S) \ + S(INVALID_STATE), \ + S(DRP_TOGGLING), \ + S(SRC_UNATTACHED), \ + S(SRC_ATTACH_WAIT), \ + S(SRC_ATTACHED), \ + S(SRC_STARTUP), \ + S(SRC_SEND_CAPABILITIES), \ + S(SRC_NEGOTIATE_CAPABILITIES), \ + S(SRC_TRANSITION_SUPPLY), \ + S(SRC_READY), \ + S(SRC_WAIT_NEW_CAPABILITIES), \ + \ + S(SNK_UNATTACHED), \ + S(SNK_ATTACH_WAIT), \ + S(SNK_DEBOUNCED), \ + S(SNK_ATTACHED), \ + S(SNK_STARTUP), \ + S(SNK_DISCOVERY), \ + S(SNK_DISCOVERY_DEBOUNCE), \ + S(SNK_DISCOVERY_DEBOUNCE_DONE), \ + S(SNK_WAIT_CAPABILITIES), \ + S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_TRANSITION_SINK), \ + S(SNK_TRANSITION_SINK_VBUS), \ + S(SNK_READY), \ + \ + S(ACC_UNATTACHED), \ + S(DEBUG_ACC_ATTACHED), \ + S(AUDIO_ACC_ATTACHED), \ + S(AUDIO_ACC_DEBOUNCE), \ + \ + S(HARD_RESET_SEND), \ + S(HARD_RESET_START), \ + S(SRC_HARD_RESET_VBUS_OFF), \ + S(SRC_HARD_RESET_VBUS_ON), \ + S(SNK_HARD_RESET_SINK_OFF), \ + S(SNK_HARD_RESET_WAIT_VBUS), \ + S(SNK_HARD_RESET_SINK_ON), \ + \ + S(SOFT_RESET), \ + S(SOFT_RESET_SEND), \ + \ + S(DR_SWAP_ACCEPT), \ + S(DR_SWAP_SEND), \ + S(DR_SWAP_SEND_TIMEOUT), \ + S(DR_SWAP_CANCEL), \ + S(DR_SWAP_CHANGE_DR), \ + \ + S(PR_SWAP_ACCEPT), \ + S(PR_SWAP_SEND), \ + S(PR_SWAP_SEND_TIMEOUT), \ + S(PR_SWAP_CANCEL), \ + S(PR_SWAP_START), \ + S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(PR_SWAP_SRC_SNK_SINK_ON), \ + S(PR_SWAP_SNK_SRC_SINK_OFF), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON), \ + \ + S(VCONN_SWAP_ACCEPT), \ + S(VCONN_SWAP_SEND), \ + S(VCONN_SWAP_SEND_TIMEOUT), \ + S(VCONN_SWAP_CANCEL), \ + S(VCONN_SWAP_START), \ + S(VCONN_SWAP_WAIT_FOR_VCONN), \ + S(VCONN_SWAP_TURN_ON_VCONN), \ + S(VCONN_SWAP_TURN_OFF_VCONN), \ + \ + S(SNK_TRY), \ + S(SNK_TRY_WAIT), \ + S(SRC_TRYWAIT), \ + S(SRC_TRYWAIT_UNATTACHED), \ + \ + S(SRC_TRY), \ + S(SRC_TRY_DEBOUNCE), \ + S(SNK_TRYWAIT), \ + S(SNK_TRYWAIT_DEBOUNCE), \ + S(SNK_TRYWAIT_VBUS), \ + S(BIST_RX), \ + \ + S(ERROR_RECOVERY), \ + S(ERROR_RECOVERY_WAIT_OFF) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum tcpm_state { + FOREACH_STATE(GENERATE_ENUM) +}; + +static const char * const tcpm_states[] = { + FOREACH_STATE(GENERATE_STRING) +}; + +enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, + VDM_STATE_ERR_TMOUT = -1, + VDM_STATE_DONE = 0, + /* Anything >0 represents an active state */ + VDM_STATE_READY = 1, + VDM_STATE_BUSY = 2, + VDM_STATE_WAIT_RSP_BUSY = 3, +}; + +enum pd_msg_request { + PD_MSG_NONE = 0, + PD_MSG_CTRL_REJECT, + PD_MSG_CTRL_WAIT, + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, +}; + +/* Events from low level driver */ + +#define TCPM_CC_EVENT BIT(0) +#define TCPM_VBUS_EVENT BIT(1) +#define TCPM_RESET_EVENT BIT(2) + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +/* Alternate mode support */ + +#define SVID_DISCOVERY_MAX 16 + +struct pd_mode_data { + int svid_index; /* current SVID index */ + int nsvids; + u16 svids[SVID_DISCOVERY_MAX]; + int altmodes; /* number of alternate modes */ + struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; +}; + +struct tcpm_port { + struct device *dev; + + struct mutex lock; /* tcpm state machine lock */ + struct workqueue_struct *wq; + + struct typec_capability typec_caps; + struct typec_port *typec_port; + + struct tcpc_dev *tcpc; + + enum typec_role vconn_role; + enum typec_role pwr_role; + enum typec_data_role data_role; + enum typec_pwr_opmode pwr_opmode; + + struct usb_pd_identity partner_ident; + struct typec_partner_desc partner_desc; + struct typec_partner *partner; + + enum typec_cc_status cc_req; + + enum typec_cc_status cc1; + enum typec_cc_status cc2; + enum typec_cc_polarity polarity; + + bool attached; + bool connected; + bool vbus_present; + bool vbus_never_low; + bool vbus_source; + bool vbus_charge; + + bool send_discover; + bool op_vsafe5v; + + int try_role; + int try_snk_count; + int try_src_count; + + enum pd_msg_request queued_message; + + enum tcpm_state enter_state; + enum tcpm_state prev_state; + enum tcpm_state state; + enum tcpm_state delayed_state; + unsigned long delayed_runtime; + unsigned long delay_ms; + + spinlock_t pd_event_lock; + u32 pd_events; + + struct work_struct event_work; + struct delayed_work state_machine; + struct delayed_work vdm_state_machine; + bool state_machine_running; + + struct completion tx_complete; + enum tcpm_transmit_status tx_status; + + struct mutex swap_lock; /* swap command lock */ + bool swap_pending; + struct completion swap_complete; + int swap_status; + + unsigned int message_id; + unsigned int caps_count; + unsigned int hard_reset_count; + bool pd_capable; + bool explicit_contract; + + /* Partner capabilities/requests */ + u32 sink_request; + u32 source_caps[PDO_MAX_OBJECTS]; + unsigned int nr_source_caps; + u32 sink_caps[PDO_MAX_OBJECTS]; + unsigned int nr_sink_caps; + + /* Local capabilities */ + u32 src_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_src_pdo; + u32 snk_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_snk_pdo; + + unsigned int max_snk_mv; + unsigned int max_snk_ma; + unsigned int max_snk_mw; + unsigned int operating_snk_mw; + + /* Requested current / voltage */ + u32 current_limit; + u32 supply_voltage; + + u32 bist_request; + + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; + /* next Vendor Defined Message to send */ + u32 vdo_data[VDO_MAX_SIZE]; + u8 vdo_count; + /* VDO to retry if UFP responder replied busy */ + u32 vdo_retry; + + /* Alternate mode data */ + + struct pd_mode_data mode_data; + struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; + struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +struct pd_rx_event { + struct work_struct work; + struct tcpm_port *port; + struct pd_message msg; +}; + +#define tcpm_cc_is_sink(cc) \ + ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ + (cc) == TYPEC_CC_RP_3_0) + +#define tcpm_port_is_sink(port) \ + ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \ + (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1))) + +#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD) +#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA) +#define tcpm_cc_is_open(cc) ((cc) == TYPEC_CC_OPEN) + +#define tcpm_port_is_source(port) \ + ((tcpm_cc_is_source((port)->cc1) && \ + !tcpm_cc_is_source((port)->cc2)) || \ + (tcpm_cc_is_source((port)->cc2) && \ + !tcpm_cc_is_source((port)->cc1))) + +#define tcpm_port_is_debug(port) \ + (tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) + +#define tcpm_port_is_audio(port) \ + (tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2)) + +#define tcpm_port_is_audio_detached(port) \ + ((tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_open((port)->cc2)) || \ + (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1))) + +#define tcpm_try_snk(port) \ + ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK) + +#define tcpm_try_src(port) \ + ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE) + +static enum tcpm_state tcpm_default_state(struct tcpm_port *port) +{ + if (port->try_role == TYPEC_SINK) + return SNK_UNATTACHED; + else if (port->try_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else if (port->tcpc->config->default_role == TYPEC_SINK) + return SNK_UNATTACHED; + return SRC_UNATTACHED; +} + +static inline +struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap) +{ + return container_of(cap, struct tcpm_port, typec_caps); +} + +static bool tcpm_port_is_disconnected(struct tcpm_port *port) +{ + return (!port->attached && port->cc1 == TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN) || + (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 && + port->cc1 == TYPEC_CC_OPEN) || + (port->polarity == TYPEC_POLARITY_CC2 && + port->cc2 == TYPEC_CC_OPEN))); +} + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS + +static bool tcpm_log_full(struct tcpm_port *port) +{ + return port->logbuffer_tail == + (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + if (!port->logbuffer[port->logbuffer_head]) { + port->logbuffer[port->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!port->logbuffer[port->logbuffer_head]) + return; + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + mutex_lock(&port->logbuffer_lock); + + if (tcpm_log_full(port)) { + port->logbuffer_head = max(port->logbuffer_head - 1, 0); + strcpy(tmpbuffer, "overflow"); + } + + if (port->logbuffer_head < 0 || + port->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(port->dev, + "Bad log buffer index %d\n", port->logbuffer_head); + goto abort; + } + + if (!port->logbuffer[port->logbuffer_head]) { + dev_warn(port->dev, + "Log buffer index %d is NULL\n", port->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(port->logbuffer[port->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&port->logbuffer_lock); +} + +static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + /* Do not log while disconnected and unattached */ + if (tcpm_port_is_disconnected(port) && + (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || + port->state == DRP_TOGGLING)) + return; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +static void tcpm_log_source_caps(struct tcpm_port *port) +{ + int i; + + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + enum pd_pdo_type type = pdo_type(pdo); + char msg[64]; + + switch (type) { + case PDO_TYPE_FIXED: + scnprintf(msg, sizeof(msg), + "%u mV, %u mA [%s%s%s%s%s%s]", + pdo_fixed_voltage(pdo), + pdo_max_current(pdo), + (pdo & PDO_FIXED_DUAL_ROLE) ? + "R" : "", + (pdo & PDO_FIXED_SUSPEND) ? + "S" : "", + (pdo & PDO_FIXED_HIGHER_CAP) ? + "H" : "", + (pdo & PDO_FIXED_USB_COMM) ? + "U" : "", + (pdo & PDO_FIXED_DATA_SWAP) ? + "D" : "", + (pdo & PDO_FIXED_EXTPOWER) ? + "E" : ""); + break; + case PDO_TYPE_VAR: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mA", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_current(pdo)); + break; + case PDO_TYPE_BATT: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mW", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_power(pdo)); + break; + default: + strcpy(msg, "undefined"); + break; + } + tcpm_log(port, " PDO %d: type %d, %s", + i, type, msg); + } +} + +static int tcpm_seq_show(struct seq_file *s, void *v) +{ + struct tcpm_port *port = (struct tcpm_port *)s->private; + int tail; + + mutex_lock(&port->logbuffer_lock); + tail = port->logbuffer_tail; + while (tail != port->logbuffer_head) { + seq_printf(s, "%s\n", port->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + port->logbuffer_tail = tail; + mutex_unlock(&port->logbuffer_lock); + + return 0; +} + +static int tcpm_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, tcpm_seq_show, inode->i_private); +} + +static const struct file_operations tcpm_debug_operations = { + .open = tcpm_debug_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static struct dentry *rootdir; + +static int tcpm_debugfs_init(struct tcpm_port *port) +{ + mutex_init(&port->logbuffer_lock); + /* /sys/kernel/debug/tcpm/usbcX */ + if (!rootdir) { + rootdir = debugfs_create_dir("tcpm", NULL); + if (!rootdir) + return -ENOMEM; + } + + port->dentry = debugfs_create_file(dev_name(port->dev), + S_IFREG | 0444, rootdir, + port, &tcpm_debug_operations); + + return 0; +} + +static void tcpm_debugfs_exit(struct tcpm_port *port) +{ + debugfs_remove(port->dentry); +} + +#else + +static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { } +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { } +static void tcpm_log_source_caps(struct tcpm_port *port) { } +static int tcpm_debugfs_init(const struct tcpm_port *port) { return 0; } +static void tcpm_debugfs_exit(const struct tcpm_port *port) { } + +#endif + +static int tcpm_pd_transmit(struct tcpm_port *port, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + unsigned long timeout; + int ret; + + if (msg) + tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); + else + tcpm_log(port, "PD TX, type: %#x", type); + + reinit_completion(&port->tx_complete); + ret = port->tcpc->pd_transmit(port->tcpc, type, msg); + if (ret < 0) + return ret; + + mutex_unlock(&port->lock); + timeout = wait_for_completion_timeout(&port->tx_complete, + msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT)); + mutex_lock(&port->lock); + if (!timeout) + return -ETIMEDOUT; + + switch (port->tx_status) { + case TCPC_TX_SUCCESS: + port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; + return 0; + case TCPC_TX_DISCARDED: + return -EAGAIN; + case TCPC_TX_FAILED: + default: + return -EIO; + } +} + +void tcpm_pd_transmit_complete(struct tcpm_port *port, + enum tcpm_transmit_status status) +{ + tcpm_log(port, "PD TX complete, status: %u", status); + port->tx_status = status; + complete(&port->tx_complete); +} +EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); + +static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, + enum tcpc_usb_switch config) +{ + int ret = 0; + + tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d", + mode, config, port->polarity); + + if (port->tcpc->mux) + ret = port->tcpc->mux->set(port->tcpc->mux, mode, config, + port->polarity); + + return ret; +} + +static int tcpm_set_polarity(struct tcpm_port *port, + enum typec_cc_polarity polarity) +{ + int ret; + + tcpm_log(port, "polarity %d", polarity); + + ret = port->tcpc->set_polarity(port->tcpc, polarity); + if (ret < 0) + return ret; + + port->polarity = polarity; + + return 0; +} + +static int tcpm_set_vconn(struct tcpm_port *port, bool enable) +{ + int ret; + + tcpm_log(port, "vconn:=%d", enable); + + ret = port->tcpc->set_vconn(port->tcpc, enable); + if (!ret) { + port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK; + typec_set_vconn_role(port->typec_port, port->vconn_role); + } + + return ret; +} + +static u32 tcpm_get_current_limit(struct tcpm_port *port) +{ + enum typec_cc_status cc; + u32 limit; + + cc = port->polarity ? port->cc2 : port->cc1; + switch (cc) { + case TYPEC_CC_RP_1_5: + limit = 1500; + break; + case TYPEC_CC_RP_3_0: + limit = 3000; + break; + case TYPEC_CC_RP_DEF: + default: + limit = 0; + break; + } + + return limit; +} + +static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) +{ + int ret = -EOPNOTSUPP; + + tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma); + + if (port->tcpc->set_current_limit) + ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv); + + return ret; +} + +/* + * Determine RP value to set based on maximum current supported + * by a port if configured as source. + * Returns CC value to report to link partner. + */ +static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) +{ + const u32 *src_pdo = port->src_pdo; + int nr_pdo = port->nr_src_pdo; + int i; + + /* + * Search for first entry with matching voltage. + * It should report the maximum supported current. + */ + for (i = 0; i < nr_pdo; i++) { + const u32 pdo = src_pdo[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED && + pdo_fixed_voltage(pdo) == 5000) { + unsigned int curr = pdo_max_current(pdo); + + if (curr >= 3000) + return TYPEC_CC_RP_3_0; + else if (curr >= 1500) + return TYPEC_CC_RP_1_5; + return TYPEC_CC_RP_DEF; + } + } + + return TYPEC_CC_RP_DEF; +} + +static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) +{ + return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role, + port->data_role); +} + +static int tcpm_set_roles(struct tcpm_port *port, bool attached, + enum typec_role role, enum typec_data_role data) +{ + int ret; + + if (data == TYPEC_HOST) + ret = tcpm_mux_set(port, TYPEC_MUX_USB, + TCPC_USB_SWITCH_CONNECT); + else + ret = tcpm_mux_set(port, TYPEC_MUX_NONE, + TCPC_USB_SWITCH_DISCONNECT); + if (ret < 0) + return ret; + + ret = port->tcpc->set_roles(port->tcpc, attached, role, data); + if (ret < 0) + return ret; + + port->pwr_role = role; + port->data_role = data; + typec_set_data_role(port->typec_port, data); + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role) +{ + int ret; + + ret = port->tcpc->set_roles(port->tcpc, true, role, + port->data_role); + if (ret < 0) + return ret; + + port->pwr_role = role; + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +static int tcpm_pd_send_source_caps(struct tcpm_port *port) +{ + struct pd_message msg; + int i; + + memset(&msg, 0, sizeof(msg)); + if (!port->nr_src_pdo) { + /* No source capabilities defined, sink only */ + msg.header = PD_HEADER_LE(PD_CTRL_REJECT, + port->pwr_role, + port->data_role, + port->message_id, 0); + } else { + msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, + port->pwr_role, + port->data_role, + port->message_id, + port->nr_src_pdo); + } + for (i = 0; i < port->nr_src_pdo; i++) + msg.payload[i] = cpu_to_le32(port->src_pdo[i]); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_sink_caps(struct tcpm_port *port) +{ + struct pd_message msg; + int i; + + memset(&msg, 0, sizeof(msg)); + if (!port->nr_snk_pdo) { + /* No sink capabilities defined, source only */ + msg.header = PD_HEADER_LE(PD_CTRL_REJECT, + port->pwr_role, + port->data_role, + port->message_id, 0); + } else { + msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, + port->pwr_role, + port->data_role, + port->message_id, + port->nr_snk_pdo); + } + for (i = 0; i < port->nr_snk_pdo; i++) + msg.payload[i] = cpu_to_le32(port->snk_pdo[i]); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (delay_ms) { + tcpm_log(port, "pending state change %s -> %s @ %u ms", + tcpm_states[port->state], tcpm_states[state], + delay_ms); + port->delayed_state = state; + mod_delayed_work(port->wq, &port->state_machine, + msecs_to_jiffies(delay_ms)); + port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms); + port->delay_ms = delay_ms; + } else { + tcpm_log(port, "state change %s -> %s", + tcpm_states[port->state], tcpm_states[state]); + port->delayed_state = INVALID_STATE; + port->prev_state = port->state; + port->state = state; + /* + * Don't re-queue the state machine work item if we're currently + * in the state machine and we're immediately changing states. + * tcpm_state_machine_work() will continue running the state + * machine. + */ + if (!port->state_machine_running) + mod_delayed_work(port->wq, &port->state_machine, 0); + } +} + +static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (port->enter_state == port->state) + tcpm_set_state(port, state, delay_ms); + else + tcpm_log(port, + "skipped %sstate change %s -> %s [%u ms], context state %s", + delay_ms ? "delayed " : "", + tcpm_states[port->state], tcpm_states[state], + delay_ms, tcpm_states[port->enter_state]); +} + +static void tcpm_queue_message(struct tcpm_port *port, + enum pd_msg_request message) +{ + port->queued_message = message; + mod_delayed_work(port->wq, &port->state_machine, 0); +} + +/* + * VDM/VDO handling functions + */ +static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + const u32 *data, int cnt) +{ + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; + memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt); + /* Set ready, vdm state machine will actually send */ + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_READY; +} + +static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]); + u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]); + + memset(&port->mode_data, 0, sizeof(port->mode_data)); + +#if 0 /* Not really a match */ + switch (PD_IDH_PTYPE(vdo)) { + case IDH_PTYPE_UNDEF: + port->partner.type = TYPEC_PARTNER_NONE; /* no longer exists */ + break; + case IDH_PTYPE_HUB: + break; + case IDH_PTYPE_PERIPH: + break; + case IDH_PTYPE_PCABLE: + break; + case IDH_PTYPE_ACABLE: + break; + case IDH_PTYPE_AMA: + port->partner.type = TYPEC_PARTNER_ALTMODE; + break; + default: + break; + } +#endif + + port->partner_ident.id_header = vdo; + port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]); + port->partner_ident.product = product; + + typec_partner_set_identity(port->partner); + + tcpm_log(port, "Identity: %04x:%04x.%04x", + PD_IDH_VID(vdo), + PD_PRODUCT_PID(product), product & 0xffff); +} + +static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + struct pd_mode_data *pmdata = &port->mode_data; + int i; + + for (i = 1; i < cnt; i++) { + u32 p = le32_to_cpu(payload[i]); + u16 svid; + + svid = (p >> 16) & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + + svid = p & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + } + return true; +abort: + tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX); + return false; +} + +static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + struct pd_mode_data *pmdata = &port->mode_data; + struct typec_altmode_desc *paltmode; + struct typec_mode_desc *pmode; + int i; + + if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + + paltmode = &pmdata->altmode_desc[pmdata->altmodes]; + memset(paltmode, 0, sizeof(*paltmode)); + + paltmode->svid = pmdata->svids[pmdata->svid_index]; + + tcpm_log(port, " Alternate mode %d: SVID 0x%04x", + pmdata->altmodes, paltmode->svid); + + for (i = 1; i < cnt && paltmode->n_modes < ALTMODE_MAX_MODES; i++) { + pmode = &paltmode->modes[paltmode->n_modes]; + memset(pmode, 0, sizeof(*pmode)); + pmode->vdo = le32_to_cpu(payload[i]); + pmode->index = i - 1; + paltmode->n_modes++; + tcpm_log(port, " VDO %d: 0x%08x", + pmode->index, pmode->vdo); + } + port->partner_altmode[pmdata->altmodes] = + typec_partner_register_altmode(port->partner, paltmode); + if (port->partner_altmode[pmdata->altmodes] == NULL) { + tcpm_log(port, + "Failed to register alternate modes for SVID 0x%04x", + paltmode->svid); + return; + } + pmdata->altmodes++; +} + +#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) + +static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, + u32 *response) +{ + u32 p0 = le32_to_cpu(payload[0]); + int cmd_type = PD_VDO_CMDT(p0); + int cmd = PD_VDO_CMD(p0); + struct pd_mode_data *modep; + int rlen = 0; + u16 svid; + + tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", + p0, cmd_type, cmd, cnt); + + modep = &port->mode_data; + + switch (cmd_type) { + case CMDT_INIT: + switch (cmd) { + case CMD_DISCOVER_IDENT: + break; + case CMD_DISCOVER_SVID: + break; + case CMD_DISCOVER_MODES: + break; + case CMD_ENTER_MODE: + break; + case CMD_EXIT_MODE: + break; + case CMD_ATTENTION: + break; + default: + break; + } + if (rlen >= 1) { + response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK); + } else if (rlen == 0) { + response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + } else { + response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY); + rlen = 1; + } + break; + case CMDT_RSP_ACK: + /* silently drop message if we are not connected */ + if (!port->partner) + break; + + switch (cmd) { + case CMD_DISCOVER_IDENT: + /* 6.4.4.3.1 */ + svdm_consume_identity(port, payload, cnt); + response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); + rlen = 1; + break; + case CMD_DISCOVER_SVID: + /* 6.4.4.3.2 */ + if (svdm_consume_svids(port, payload, cnt)) { + response[0] = VDO(USB_SID_PD, 1, + CMD_DISCOVER_SVID); + rlen = 1; + } else if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, + CMD_DISCOVER_MODES); + rlen = 1; + } + break; + case CMD_DISCOVER_MODES: + /* 6.4.4.3.3 */ + svdm_consume_modes(port, payload, cnt); + modep->svid_index++; + if (modep->svid_index < modep->nsvids) { + svid = modep->svids[modep->svid_index]; + response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); + rlen = 1; + } else { +#if 0 + response[0] = pd_dfp_enter_mode(port, 0, 0); + if (response[0]) + rlen = 1; +#endif + } + break; + case CMD_ENTER_MODE: + break; + default: + break; + } + break; + default: + break; + } + + return rlen; +} + +static void tcpm_handle_vdm_request(struct tcpm_port *port, + const __le32 *payload, int cnt) +{ + int rlen = 0; + u32 response[8] = { }; + u32 p0 = le32_to_cpu(payload[0]); + + if (port->vdm_state == VDM_STATE_BUSY) { + /* If UFP responded busy retry after timeout */ + if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) { + port->vdm_state = VDM_STATE_WAIT_RSP_BUSY; + port->vdo_retry = (p0 & ~VDO_CMDT_MASK) | + CMDT_INIT; + mod_delayed_work(port->wq, &port->vdm_state_machine, + msecs_to_jiffies(PD_T_VDM_BUSY)); + return; + } + port->vdm_state = VDM_STATE_DONE; + } + + if (PD_VDO_SVDM(p0)) + rlen = tcpm_pd_svdm(port, payload, cnt, response); +#if 0 + else + rlen = tcpm_pd_custom_vdm(port, cnt, payload, response); +#endif + + if (rlen > 0) { + tcpm_queue_vdm(port, response[0], &response[1], rlen - 1); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + } +} + +static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, + const u32 *data, int count) +{ + u32 header; + + if (WARN_ON(count > VDO_MAX_SIZE - 1)) + count = VDO_MAX_SIZE - 1; + + /* set VDM header with VID & CMD */ + header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? + 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); + tcpm_queue_vdm(port, header, data, count); + + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); +} + +static unsigned int vdm_ready_timeout(u32 vdm_hdr) +{ + unsigned int timeout; + int cmd = PD_VDO_CMD(vdm_hdr); + + /* its not a structured VDM command */ + if (!PD_VDO_SVDM(vdm_hdr)) + return PD_T_VDM_UNSTRUCTURED; + + switch (PD_VDO_CMDT(vdm_hdr)) { + case CMDT_INIT: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_WAIT_MODE_E; + else + timeout = PD_T_VDM_SNDR_RSP; + break; + default: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_E_MODE; + else + timeout = PD_T_VDM_RCVR_RSP; + break; + } + return timeout; +} + +static void vdm_run_state_machine(struct tcpm_port *port) +{ + struct pd_message msg; + int i, res; + + switch (port->vdm_state) { + case VDM_STATE_READY: + /* Only transmit VDM if attached */ + if (!port->attached) { + port->vdm_state = VDM_STATE_ERR_BUSY; + break; + } + + /* + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ + if (port->state != SRC_READY && port->state != SNK_READY) + break; + + /* Prepare and send VDM */ + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->message_id, port->vdo_count); + for (i = 0; i < port->vdo_count; i++) + msg.payload[i] = cpu_to_le32(port->vdo_data[i]); + res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + if (res < 0) { + port->vdm_state = VDM_STATE_ERR_SEND; + } else { + unsigned long timeout; + + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_BUSY; + timeout = vdm_ready_timeout(port->vdo_data[0]); + mod_delayed_work(port->wq, &port->vdm_state_machine, + timeout); + } + break; + case VDM_STATE_WAIT_RSP_BUSY: + port->vdo_data[0] = port->vdo_retry; + port->vdo_count = 1; + port->vdm_state = VDM_STATE_READY; + break; + case VDM_STATE_BUSY: + port->vdm_state = VDM_STATE_ERR_TMOUT; + break; + case VDM_STATE_ERR_SEND: + /* + * A partner which does not support USB PD will not reply, + * so this is not a fatal error. At the same time, some + * devices may not return GoodCRC under some circumstances, + * so we need to retry. + */ + if (port->vdm_retries < 3) { + tcpm_log(port, "VDM Tx error, retry"); + port->vdm_retries++; + port->vdm_state = VDM_STATE_READY; + } + break; + default: + break; + } +} + +static void vdm_state_machine_work(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + vdm_state_machine.work); + enum vdm_states prev_state; + + mutex_lock(&port->lock); + + /* + * Continue running as long as the port is not busy and there was + * a state change. + */ + do { + prev_state = port->vdm_state; + vdm_run_state_machine(port); + } while (port->vdm_state != prev_state && + port->vdm_state != VDM_STATE_BUSY); + + mutex_unlock(&port->lock); +} + +/* + * PD (data, control) command handling functions + */ +static void tcpm_pd_data_request(struct tcpm_port *port, + const struct pd_message *msg) +{ + enum pd_data_msg_type type = pd_header_type_le(msg->header); + unsigned int cnt = pd_header_cnt_le(msg->header); + unsigned int i; + + switch (type) { + case PD_DATA_SOURCE_CAP: + if (port->pwr_role != TYPEC_SINK) + break; + + for (i = 0; i < cnt; i++) + port->source_caps[i] = le32_to_cpu(msg->payload[i]); + + port->nr_source_caps = cnt; + + tcpm_log_source_caps(port); + + /* + * This message may be received even if VBUS is not + * present. This is quite unexpected; see USB PD + * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. + * However, at the same time, we must be ready to + * receive this message and respond to it 15ms after + * receiving PS_RDY during power swap operations, no matter + * if VBUS is available or not (USB PD specification, + * section 6.5.9.2). + * So we need to accept the message either way, + * but be prepared to keep waiting for VBUS after it was + * handled. + */ + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + break; + case PD_DATA_REQUEST: + if (port->pwr_role != TYPEC_SOURCE || + cnt != 1) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + port->sink_request = le32_to_cpu(msg->payload[0]); + tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); + break; + case PD_DATA_SINK_CAP: + /* We don't do anything with this at the moment... */ + for (i = 0; i < cnt; i++) + port->sink_caps[i] = le32_to_cpu(msg->payload[i]); + port->nr_sink_caps = cnt; + break; + case PD_DATA_VENDOR_DEF: + tcpm_handle_vdm_request(port, msg->payload, cnt); + break; + case PD_DATA_BIST: + if (port->state == SRC_READY || port->state == SNK_READY) { + port->bist_request = le32_to_cpu(msg->payload[0]); + tcpm_set_state(port, BIST_RX, 0); + } + break; + default: + tcpm_log(port, "Unhandled data message type %#x", type); + break; + } +} + +static void tcpm_pd_ctrl_request(struct tcpm_port *port, + const struct pd_message *msg) +{ + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); + enum tcpm_state next_state; + + switch (type) { + case PD_CTRL_GOOD_CRC: + case PD_CTRL_PING: + break; + case PD_CTRL_GET_SOURCE_CAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + break; + case PD_CTRL_GET_SINK_CAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + break; + case PD_CTRL_GOTO_MIN: + break; + case PD_CTRL_PS_RDY: + switch (port->state) { + case SNK_TRANSITION_SINK: + if (port->vbus_present) { + tcpm_set_current_limit(port, + port->current_limit, + port->supply_voltage); + tcpm_set_state(port, SNK_READY, 0); + } else { + /* + * Seen after power swap. Keep waiting for VBUS + * in a transitional state. + */ + tcpm_set_state(port, + SNK_TRANSITION_SINK_VBUS, 0); + } + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0); + break; + default: + break; + } + break; + case PD_CTRL_REJECT: + case PD_CTRL_WAIT: + switch (port->state) { + case SNK_NEGOTIATE_CAPABILITIES: + /* USB PD specification, Figure 8-43 */ + if (port->explicit_contract) + next_state = SNK_READY; + else + next_state = SNK_WAIT_CAPABILITIES; + tcpm_set_state(port, next_state, 0); + break; + case DR_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, DR_SWAP_CANCEL, 0); + break; + case PR_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, PR_SWAP_CANCEL, 0); + break; + case VCONN_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, VCONN_SWAP_CANCEL, 0); + break; + default: + break; + } + break; + case PD_CTRL_ACCEPT: + switch (port->state) { + case SNK_NEGOTIATE_CAPABILITIES: + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SOFT_RESET_SEND: + port->message_id = 0; + if (port->pwr_role == TYPEC_SOURCE) + next_state = SRC_SEND_CAPABILITIES; + else + next_state = SNK_WAIT_CAPABILITIES; + tcpm_set_state(port, next_state, 0); + break; + case DR_SWAP_SEND: + tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); + break; + case PR_SWAP_SEND: + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + default: + break; + } + break; + case PD_CTRL_SOFT_RESET: + tcpm_set_state(port, SOFT_RESET, 0); + break; + case PD_CTRL_DR_SWAP: + if (port->typec_caps.type != TYPEC_PORT_DRP) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + */ + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, DR_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + break; + case PD_CTRL_PR_SWAP: + if (port->typec_caps.type != TYPEC_PORT_DRP) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, PR_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + break; + case PD_CTRL_VCONN_SWAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + break; + default: + tcpm_log(port, "Unhandled ctrl message type %#x", type); + break; + } +} + +static void tcpm_pd_rx_handler(struct work_struct *work) +{ + struct pd_rx_event *event = container_of(work, + struct pd_rx_event, work); + const struct pd_message *msg = &event->msg; + unsigned int cnt = pd_header_cnt_le(msg->header); + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); + + tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), + port->attached); + + if (port->attached) { + /* + * If both ends believe to be DFP/host, we have a data role + * mismatch. + */ + if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == + (port->data_role == TYPEC_HOST)) { + tcpm_log(port, + "Data role mismatch, initiating error recovery"); + tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + if (cnt) + tcpm_pd_data_request(port, msg); + else + tcpm_pd_ctrl_request(port, msg); + } + } + + mutex_unlock(&port->lock); + kfree(event); +} + +void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) +{ + struct pd_rx_event *event; + + event = kzalloc(sizeof(*event), GFP_ATOMIC); + if (!event) + return; + + INIT_WORK(&event->work, tcpm_pd_rx_handler); + event->port = port; + memcpy(&event->msg, msg, sizeof(*msg)); + queue_work(port->wq, &event->work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_receive); + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type) +{ + struct pd_message msg; + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(type, port->pwr_role, + port->data_role, + port->message_id, 0); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +/* + * Send queued message without affecting state. + * Return true if state machine should go back to sleep, + * false otherwise. + */ +static bool tcpm_send_queued_message(struct tcpm_port *port) +{ + enum pd_msg_request queued_message; + + do { + queued_message = port->queued_message; + port->queued_message = PD_MSG_NONE; + + switch (queued_message) { + case PD_MSG_CTRL_WAIT: + tcpm_pd_send_control(port, PD_CTRL_WAIT); + break; + case PD_MSG_CTRL_REJECT: + tcpm_pd_send_control(port, PD_CTRL_REJECT); + break; + case PD_MSG_DATA_SINK_CAP: + tcpm_pd_send_sink_caps(port); + break; + case PD_MSG_DATA_SOURCE_CAP: + tcpm_pd_send_source_caps(port); + break; + default: + break; + } + } while (port->queued_message != PD_MSG_NONE); + + if (port->delayed_state != INVALID_STATE) { + if (time_is_after_jiffies(port->delayed_runtime)) { + mod_delayed_work(port->wq, &port->state_machine, + port->delayed_runtime - jiffies); + return true; + } + port->delayed_state = INVALID_STATE; + } + return false; +} + +static int tcpm_pd_check_request(struct tcpm_port *port) +{ + u32 pdo, rdo = port->sink_request; + unsigned int max, op, pdo_max, index; + enum pd_pdo_type type; + + index = rdo_index(rdo); + if (!index || index > port->nr_src_pdo) + return -EINVAL; + + pdo = port->src_pdo[index - 1]; + type = pdo_type(pdo); + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: + max = rdo_max_current(rdo); + op = rdo_op_current(rdo); + pdo_max = pdo_max_current(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + + if (type == PDO_TYPE_FIXED) + tcpm_log(port, + "Requested %u mV, %u mA for %u / %u mA", + pdo_fixed_voltage(pdo), pdo_max, op, max); + else + tcpm_log(port, + "Requested %u -> %u mV, %u mA for %u / %u mA", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + case PDO_TYPE_BATT: + max = rdo_max_power(rdo); + op = rdo_op_power(rdo); + pdo_max = pdo_max_power(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + tcpm_log(port, + "Requested %u -> %u mV, %u mW for %u / %u mW", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + default: + return -EINVAL; + } + + port->op_vsafe5v = index == 1; + + return 0; +} + +static int tcpm_pd_select_pdo(struct tcpm_port *port) +{ + unsigned int i, max_mw = 0, max_mv = 0; + int ret = -EINVAL; + + /* + * Select the source PDO providing the most power while staying within + * the board's voltage limits. Prefer PDO providing exp + */ + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + enum pd_pdo_type type = pdo_type(pdo); + unsigned int mv, ma, mw; + + if (type == PDO_TYPE_FIXED) + mv = pdo_fixed_voltage(pdo); + else + mv = pdo_min_voltage(pdo); + + if (type == PDO_TYPE_BATT) { + mw = pdo_max_power(pdo); + } else { + ma = min(pdo_max_current(pdo), + port->max_snk_ma); + mw = ma * mv / 1000; + } + + /* Perfer higher voltages if available */ + if ((mw > max_mw || (mw == max_mw && mv > max_mv)) && + mv <= port->max_snk_mv) { + ret = i; + max_mw = mw; + max_mv = mv; + } + } + + return ret; +} + +static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) +{ + unsigned int mv, ma, mw, flags; + unsigned int max_ma, max_mw; + enum pd_pdo_type type; + int index; + u32 pdo; + + index = tcpm_pd_select_pdo(port); + if (index < 0) + return -EINVAL; + pdo = port->source_caps[index]; + type = pdo_type(pdo); + + if (type == PDO_TYPE_FIXED) + mv = pdo_fixed_voltage(pdo); + else + mv = pdo_min_voltage(pdo); + + /* Select maximum available current within the board's power limit */ + if (type == PDO_TYPE_BATT) { + mw = pdo_max_power(pdo); + ma = 1000 * min(mw, port->max_snk_mw) / mv; + } else { + ma = min(pdo_max_current(pdo), + 1000 * port->max_snk_mw / mv); + } + ma = min(ma, port->max_snk_ma); + + /* XXX: Any other flags need to be set? */ + flags = 0; + + /* Set mismatch bit if offered power is less than operating power */ + mw = ma * mv / 1000; + max_ma = ma; + max_mw = mw; + if (mw < port->operating_snk_mw) { + flags |= RDO_CAP_MISMATCH; + max_mw = port->operating_snk_mw; + max_ma = max_mw * 1000 / mv; + } + + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + if (type == PDO_TYPE_BATT) { + *rdo = RDO_BATT(index + 1, mw, max_mw, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", + index, mv, mw, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } else { + *rdo = RDO_FIXED(index + 1, ma, max_ma, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", + index, mv, ma, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } + + port->current_limit = ma; + port->supply_voltage = mv; + + return 0; +} + +static int tcpm_pd_send_request(struct tcpm_port *port) +{ + struct pd_message msg; + int ret; + u32 rdo; + + ret = tcpm_pd_build_request(port, &rdo); + if (ret < 0) + return ret; + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, + port->pwr_role, + port->data_role, + port->message_id, 1); + msg.payload[0] = cpu_to_le32(rdo); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_set_vbus(struct tcpm_port *port, bool enable) +{ + int ret; + + if (enable && port->vbus_charge) + return -EINVAL; + + tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge); + + ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge); + if (ret < 0) + return ret; + + port->vbus_source = enable; + return 0; +} + +static int tcpm_set_charge(struct tcpm_port *port, bool charge) +{ + int ret; + + if (charge && port->vbus_source) + return -EINVAL; + + if (charge != port->vbus_charge) { + tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge); + ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source, + charge); + if (ret < 0) + return ret; + } + port->vbus_charge = charge; + return 0; +} + +static bool tcpm_start_drp_toggling(struct tcpm_port *port) +{ + int ret; + + if (port->tcpc->start_drp_toggling && + port->typec_caps.type == TYPEC_PORT_DRP) { + tcpm_log_force(port, "Start DRP toggling"); + ret = port->tcpc->start_drp_toggling(port->tcpc, + tcpm_rp_cc(port)); + if (!ret) + return true; + } + + return false; +} + +static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) +{ + tcpm_log(port, "cc:=%d", cc); + port->cc_req = cc; + port->tcpc->set_cc(port->tcpc, cc); +} + +static int tcpm_init_vbus(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vbus(port->tcpc, false, false); + port->vbus_source = false; + port->vbus_charge = false; + return ret; +} + +static int tcpm_init_vconn(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vconn(port->tcpc, false); + port->vconn_role = TYPEC_SINK; + return ret; +} + +static void tcpm_typec_connect(struct tcpm_port *port) +{ + if (!port->connected) { + /* Make sure we don't report stale identity information */ + memset(&port->partner_ident, 0, sizeof(port->partner_ident)); + port->partner_desc.usb_pd = port->pd_capable; + if (tcpm_port_is_debug(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; + else if (tcpm_port_is_audio(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_AUDIO; + else + port->partner_desc.accessory = TYPEC_ACCESSORY_NONE; + port->partner = typec_register_partner(port->typec_port, + &port->partner_desc); + port->connected = true; + } +} + +static int tcpm_src_attach(struct tcpm_port *port) +{ + enum typec_cc_polarity polarity = + port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2 + : TYPEC_POLARITY_CC1; + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, polarity); + if (ret < 0) + return ret; + + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + if (ret < 0) + return ret; + + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) + goto out_disable_mux; + + /* + * USB Type-C specification, version 1.2, + * chapter 4.5.2.2.8.1 (Attached.SRC Requirements) + * Enable VCONN only if the non-RD port is set to RA. + */ + if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) || + (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) { + ret = tcpm_set_vconn(port, true); + if (ret < 0) + goto out_disable_pd; + } + + ret = tcpm_set_vbus(port, true); + if (ret < 0) + goto out_disable_vconn; + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + port->send_discover = true; + + return 0; + +out_disable_vconn: + tcpm_set_vconn(port, false); +out_disable_pd: + port->tcpc->set_pd_rx(port->tcpc, false); +out_disable_mux: + tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT); + return ret; +} + +static void tcpm_typec_disconnect(struct tcpm_port *port) +{ + if (port->connected) { + typec_unregister_partner(port->partner); + port->partner = NULL; + port->connected = false; + } +} + +static void tcpm_unregister_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + int i; + + for (i = 0; i < modep->altmodes; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } + + memset(modep, 0, sizeof(*modep)); +} + +static void tcpm_reset_port(struct tcpm_port *port) +{ + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; + port->pd_capable = false; + + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_init_vbus(port); /* also disables charging */ + tcpm_init_vconn(port); + tcpm_set_current_limit(port, 0, 0); + tcpm_set_polarity(port, TYPEC_POLARITY_CC1); + tcpm_set_attached_state(port, false); + port->try_src_count = 0; + port->try_snk_count = 0; +} + +static void tcpm_detach(struct tcpm_port *port) +{ + if (!port->attached) + return; + + if (tcpm_port_is_disconnected(port)) + port->hard_reset_count = 0; + + tcpm_reset_port(port); +} + +static void tcpm_src_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static int tcpm_snk_attach(struct tcpm_port *port) +{ + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ? + TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1); + if (ret < 0) + return ret; + + ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE); + if (ret < 0) + return ret; + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + port->send_discover = true; + + return 0; +} + +static void tcpm_snk_detach(struct tcpm_port *port) +{ + tcpm_detach(port); + + /* XXX: (Dis)connect SuperSpeed mux? */ +} + +static int tcpm_acc_attach(struct tcpm_port *port) +{ + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + if (ret < 0) + return ret; + + port->partner = NULL; + + tcpm_typec_connect(port); + + port->attached = true; + + return 0; +} + +static void tcpm_acc_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) +{ + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) + return HARD_RESET_SEND; + if (port->pd_capable) + return ERROR_RECOVERY; + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + if (port->state == SNK_WAIT_CAPABILITIES) + return SNK_READY; + return SNK_UNATTACHED; +} + +static inline enum tcpm_state ready_state(struct tcpm_port *port) +{ + if (port->pwr_role == TYPEC_SOURCE) + return SRC_READY; + else + return SNK_READY; +} + +static inline enum tcpm_state unattached_state(struct tcpm_port *port) +{ + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else + return SNK_UNATTACHED; +} + +static void tcpm_check_send_discover(struct tcpm_port *port) +{ + if (port->data_role == TYPEC_HOST && port->send_discover && + port->pd_capable) { + tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); + port->send_discover = false; + } +} + +static void tcpm_swap_complete(struct tcpm_port *port, int result) +{ + if (port->swap_pending) { + port->swap_status = result; + port->swap_pending = false; + complete(&port->swap_complete); + } +} + +static void run_state_machine(struct tcpm_port *port) +{ + int ret; + + port->enter_state = port->state; + switch (port->state) { + case DRP_TOGGLING: + break; + /* SRC states */ + case SRC_UNATTACHED: + tcpm_swap_complete(port, -ENOTCONN); + tcpm_src_detach(port); + if (tcpm_start_drp_toggling(port)) { + tcpm_set_state(port, DRP_TOGGLING, 0); + break; + } + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (port->typec_caps.type == TYPEC_PORT_DRP) + tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_source(port)) + tcpm_set_state(port, + tcpm_try_snk(port) ? SNK_TRY + : SRC_ATTACHED, + PD_T_CC_DEBOUNCE); + break; + + case SNK_TRY: + port->try_snk_count++; + /* + * Requirements: + * - Do not drive vconn or vbus + * - Terminate CC pins (both) to Rd + * Action: + * - Wait for tDRPTry (PD_T_DRP_TRY). + * Until then, ignore any state changes. + */ + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY); + break; + case SNK_TRY_WAIT: + if (port->vbus_present && tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (!tcpm_port_is_sink(port)) { + tcpm_set_state(port, SRC_TRYWAIT, + PD_T_PD_DEBOUNCE); + break; + } + /* No vbus, cc state is sink or open */ + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, PD_T_DRP_TRYWAIT); + break; + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (!port->vbus_present && tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE); + else + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + PD_T_DRP_TRY); + break; + case SRC_TRYWAIT_UNATTACHED: + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + + case SRC_ATTACHED: + ret = tcpm_src_attach(port); + tcpm_set_state(port, SRC_UNATTACHED, + ret < 0 ? 0 : PD_T_PS_SOURCE_ON); + break; + case SRC_STARTUP: + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB); + port->pwr_opmode = TYPEC_PWR_MODE_USB; + port->caps_count = 0; + port->message_id = 0; + port->explicit_contract = false; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + break; + case SRC_SEND_CAPABILITIES: + port->caps_count++; + if (port->caps_count > PD_N_CAPS_COUNT) { + tcpm_set_state(port, SRC_READY, 0); + break; + } + ret = tcpm_pd_send_source_caps(port); + if (ret < 0) { + tcpm_set_state(port, SRC_SEND_CAPABILITIES, + PD_T_SEND_SOURCE_CAP); + } else { + /* + * Per standard, we should clear the reset counter here. + * However, that can result in state machine hang-ups. + * Reset it only in READY state to improve stability. + */ + /* port->hard_reset_count = 0; */ + port->caps_count = 0; + port->pd_capable = true; + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SEND_SOURCE_CAP); + } + break; + case SRC_NEGOTIATE_CAPABILITIES: + ret = tcpm_pd_check_request(port); + if (ret < 0) { + tcpm_pd_send_control(port, PD_CTRL_REJECT); + if (!port->explicit_contract) { + tcpm_set_state(port, + SRC_WAIT_NEW_CAPABILITIES, 0); + } else { + tcpm_set_state(port, SRC_READY, 0); + } + } else { + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, SRC_TRANSITION_SUPPLY, + PD_T_SRC_TRANSITION); + } + break; + case SRC_TRANSITION_SUPPLY: + /* XXX: regulator_set_voltage(vbus, ...) */ + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + port->explicit_contract = true; + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + tcpm_set_state_cond(port, SRC_READY, 0); + break; + case SRC_READY: +#if 1 + port->hard_reset_count = 0; +#endif + port->try_src_count = 0; + + tcpm_typec_connect(port); + + tcpm_check_send_discover(port); + /* + * 6.3.5 + * Sending ping messages is not necessary if + * - the source operates at vSafe5V + * or + * - The system is not operating in PD mode + * or + * - Both partners are connected using a Type-C connector + * XXX How do we know that ? + */ + if (port->pwr_opmode == TYPEC_PWR_MODE_PD && + !port->op_vsafe5v) { + tcpm_pd_send_control(port, PD_CTRL_PING); + tcpm_set_state_cond(port, SRC_READY, + PD_T_SOURCE_ACTIVITY); + } + break; + case SRC_WAIT_NEW_CAPABILITIES: + /* Nothing to do... */ + break; + + /* SNK states */ + case SNK_UNATTACHED: + tcpm_swap_complete(port, -ENOTCONN); + tcpm_snk_detach(port); + if (tcpm_start_drp_toggling(port)) { + tcpm_set_state(port, DRP_TOGGLING, 0); + break; + } + tcpm_set_cc(port, TYPEC_CC_RD); + if (port->typec_caps.type == TYPEC_PORT_DRP) + tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); + break; + case SNK_ATTACH_WAIT: + if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + tcpm_set_state(port, SNK_DEBOUNCED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + else if (port->vbus_present) + tcpm_set_state(port, + tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + else + /* Wait for VBUS, but not forever */ + tcpm_set_state(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); + break; + + case SRC_TRY: + port->try_src_count++; + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SNK_TRYWAIT, PD_T_DRP_TRY); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE); + break; + case SNK_TRYWAIT: + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, PD_T_CC_DEBOUNCE); + break; + case SNK_TRYWAIT_DEBOUNCE: + if (port->vbus_present) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (tcpm_port_is_disconnected(port)) { + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + break; + } + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACHED, 0); + /* XXX Are we supposed to stay in this state ? */ + break; + case SNK_TRYWAIT_VBUS: + tcpm_set_state(port, SNK_ATTACHED, PD_T_CC_DEBOUNCE); + break; + + case SNK_ATTACHED: + ret = tcpm_snk_attach(port); + if (ret < 0) + tcpm_set_state(port, SNK_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_STARTUP, 0); + break; + case SNK_STARTUP: + /* XXX: callback into infrastructure */ + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB); + port->pwr_opmode = TYPEC_PWR_MODE_USB; + port->message_id = 0; + port->explicit_contract = false; + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + case SNK_DISCOVERY: + if (port->vbus_present) { + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + tcpm_set_charge(port, true); + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + break; + } + /* + * For DRP, timeouts differ. Also, handling is supposed to be + * different and much more complex (dead battery detection; + * see USB power delivery specification, section 8.3.3.6.1.5.1). + */ + tcpm_set_state(port, hard_reset_state(port), + port->typec_caps.type == TYPEC_PORT_DRP ? + PD_T_DB_DETECT : PD_T_NO_RESPONSE); + break; + case SNK_DISCOVERY_DEBOUNCE: + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE, + PD_T_CC_DEBOUNCE); + break; + case SNK_DISCOVERY_DEBOUNCE_DONE: + if (!tcpm_port_is_disconnected(port) && + tcpm_port_is_sink(port) && + time_is_after_jiffies(port->delayed_runtime)) { + tcpm_set_state(port, SNK_DISCOVERY, + port->delayed_runtime - jiffies); + break; + } + tcpm_set_state(port, unattached_state(port), 0); + break; + case SNK_WAIT_CAPABILITIES: + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) { + tcpm_set_state(port, SNK_READY, 0); + break; + } + /* + * If VBUS has never been low, and we time out waiting + * for source cap, try a soft reset first, in case we + * were already in a stable contract before this boot. + * Do this only once. + */ + if (port->vbus_never_low) { + port->vbus_never_low = false; + tcpm_set_state(port, SOFT_RESET_SEND, + PD_T_SINK_WAIT_CAP); + } else { + tcpm_set_state(port, hard_reset_state(port), + PD_T_SINK_WAIT_CAP); + } + break; + case SNK_NEGOTIATE_CAPABILITIES: + port->pd_capable = true; + port->hard_reset_count = 0; + ret = tcpm_pd_send_request(port); + if (ret < 0) { + /* Let the Source send capabilities again. */ + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_TRANSITION); + break; + case SNK_READY: + port->try_snk_count = 0; + port->explicit_contract = true; + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + + tcpm_typec_connect(port); + + tcpm_check_send_discover(port); + break; + + /* Accessory states */ + case ACC_UNATTACHED: + tcpm_acc_detach(port); + tcpm_set_state(port, SRC_UNATTACHED, 0); + break; + case DEBUG_ACC_ATTACHED: + case AUDIO_ACC_ATTACHED: + ret = tcpm_acc_attach(port); + if (ret < 0) + tcpm_set_state(port, ACC_UNATTACHED, 0); + break; + case AUDIO_ACC_DEBOUNCE: + tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE); + break; + + /* Hard_Reset states */ + case HARD_RESET_SEND: + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + break; + case HARD_RESET_START: + port->hard_reset_count++; + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->send_discover = true; + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); + else + tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0); + break; + case SRC_HARD_RESET_VBUS_OFF: + tcpm_set_vconn(port, true); + tcpm_set_vbus(port, false); + tcpm_set_roles(port, false, TYPEC_SOURCE, TYPEC_HOST); + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_vbus(port, true); + port->tcpc->set_pd_rx(port->tcpc, true); + tcpm_set_attached_state(port, true); + tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); + break; + case SNK_HARD_RESET_SINK_OFF: + tcpm_set_vconn(port, false); + tcpm_set_charge(port, false); + tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE); + /* + * VBUS may or may not toggle, depending on the adapter. + * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON + * directly after timeout. + */ + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V); + break; + case SNK_HARD_RESET_WAIT_VBUS: + /* Assume we're disconnected if VBUS doesn't come back. */ + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); + break; + case SNK_HARD_RESET_SINK_ON: + /* Note: There is no guarantee that VBUS is on in this state */ + /* + * XXX: + * The specification suggests that dual mode ports in sink + * mode should transition to state PE_SRC_Transition_to_default. + * See USB power delivery specification chapter 8.3.3.6.1.3. + * This would mean to to + * - turn off VCONN, reset power supply + * - request hardware reset + * - turn on VCONN + * - Transition to state PE_Src_Startup + * SNK only ports shall transition to state Snk_Startup + * (see chapter 8.3.3.3.8). + * Similar, dual-mode ports in source mode should transition + * to PE_SNK_Transition_to_default. + */ + tcpm_set_attached_state(port, true); + tcpm_set_state(port, SNK_STARTUP, 0); + break; + + /* Soft_Reset states */ + case SOFT_RESET: + port->message_id = 0; + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + break; + case SOFT_RESET_SEND: + port->message_id = 0; + if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + break; + + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP); + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case DR_SWAP_CHANGE_DR: + if (port->data_role == TYPEC_HOST) { + tcpm_unregister_altmodes(port); + tcpm_set_roles(port, true, port->pwr_role, + TYPEC_DEVICE); + } else { + tcpm_set_roles(port, true, port->pwr_role, + TYPEC_HOST); + port->send_discover = true; + } + tcpm_swap_complete(port, 0); + tcpm_set_state(port, ready_state(port), 0); + break; + + /* PR_Swap states */ + case PR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case PR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_PR_SWAP); + tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case PR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case PR_SWAP_START: + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF, + PD_T_SRC_TRANSITION); + else + tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0); + break; + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + tcpm_set_vbus(port, false); + port->explicit_contract = false; + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, + PD_T_PS_SOURCE_OFF); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF: + tcpm_set_cc(port, TYPEC_CC_RD); + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); + break; + case PR_SWAP_SRC_SNK_SINK_ON: + tcpm_set_pwr_role(port, TYPEC_SINK); + tcpm_swap_complete(port, 0); + tcpm_set_state(port, SNK_STARTUP, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + tcpm_set_charge(port, false); + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_SOURCE_OFF); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON: + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_vbus(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_set_pwr_role(port, TYPEC_SOURCE); + tcpm_swap_complete(port, 0); + tcpm_set_state(port, SRC_STARTUP, 0); + break; + + case VCONN_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP); + tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case VCONN_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_START: + if (port->vconn_role == TYPEC_SOURCE) + tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0); + else + tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, hard_reset_state(port), + PD_T_VCONN_SOURCE_ON); + break; + case VCONN_SWAP_TURN_ON_VCONN: + tcpm_set_vconn(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_swap_complete(port, 0); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_TURN_OFF_VCONN: + tcpm_set_vconn(port, false); + tcpm_swap_complete(port, 0); + tcpm_set_state(port, ready_state(port), 0); + break; + + case DR_SWAP_CANCEL: + case PR_SWAP_CANCEL: + case VCONN_SWAP_CANCEL: + tcpm_swap_complete(port, port->swap_status); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_READY, 0); + else + tcpm_set_state(port, SNK_READY, 0); + break; + + case BIST_RX: + switch (BDO_MODE_MASK(port->bist_request)) { + case BDO_MODE_CARRIER2: + tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL); + break; + default: + break; + } + /* Always switch to unattached state */ + tcpm_set_state(port, unattached_state(port), 0); + break; + case ERROR_RECOVERY: + tcpm_swap_complete(port, -EPROTO); + tcpm_reset_port(port); + + tcpm_set_cc(port, TYPEC_CC_OPEN); + tcpm_set_state(port, ERROR_RECOVERY_WAIT_OFF, + PD_T_ERROR_RECOVERY); + break; + case ERROR_RECOVERY_WAIT_OFF: + tcpm_set_state(port, + tcpm_default_state(port), + port->vbus_present ? PD_T_PS_SOURCE_OFF : 0); + break; + default: + WARN(1, "Unexpected port state %d\n", port->state); + break; + } +} + +static void tcpm_state_machine_work(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + state_machine.work); + enum tcpm_state prev_state; + + mutex_lock(&port->lock); + port->state_machine_running = true; + + if (port->queued_message && tcpm_send_queued_message(port)) + goto done; + + /* If we were queued due to a delayed state change, update it now */ + if (port->delayed_state) { + tcpm_log(port, "state change %s -> %s [delayed %ld ms]", + tcpm_states[port->state], + tcpm_states[port->delayed_state], port->delay_ms); + port->prev_state = port->state; + port->state = port->delayed_state; + port->delayed_state = INVALID_STATE; + } + + /* + * Continue running as long as we have (non-delayed) state changes + * to make. + */ + do { + prev_state = port->state; + run_state_machine(port); + if (port->queued_message) + tcpm_send_queued_message(port); + } while (port->state != prev_state && !port->delayed_state); + +done: + port->state_machine_running = false; + mutex_unlock(&port->lock); +} + +static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, + enum typec_cc_status cc2) +{ + enum typec_cc_status old_cc1, old_cc2; + enum tcpm_state new_state; + + old_cc1 = port->cc1; + old_cc2 = port->cc2; + port->cc1 = cc1; + port->cc2 = cc2; + + tcpm_log_force(port, + "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]", + old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state], + port->polarity, + tcpm_port_is_disconnected(port) ? "disconnected" + : "connected"); + + switch (port->state) { + case DRP_TOGGLING: + if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + else if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SRC_UNATTACHED: + case ACC_UNATTACHED: + if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_disconnected(port) || + tcpm_port_is_audio_detached(port)) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else if (cc1 != old_cc1 || cc2 != old_cc2) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + break; + case SRC_ATTACHED: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SRC_UNATTACHED, 0); + break; + + case SNK_UNATTACHED: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_ATTACH_WAIT: + if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + new_state = SNK_DEBOUNCED; + else if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else + break; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else if (port->vbus_present) + new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED; + else + new_state = SNK_UNATTACHED; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_DEBOUNCED, 0); + break; + case SNK_READY: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, unattached_state(port), 0); + else if (!port->pd_capable && + (cc1 != old_cc1 || cc2 != old_cc2)) + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + break; + + case AUDIO_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0); + break; + case AUDIO_ACC_DEBOUNCE: + if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0); + break; + + case DEBUG_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, ACC_UNATTACHED, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + + case SNK_DISCOVERY: + /* CC line is unstable, wait for debounce */ + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0); + break; + case SNK_DISCOVERY_DEBOUNCE: + break; + + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (!port->vbus_present && tcpm_port_is_source(port)) + new_state = SRC_ATTACHED; + else + new_state = SRC_TRYWAIT_UNATTACHED; + + if (new_state != port->delayed_state) + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT: + if (port->vbus_present && tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (!tcpm_port_is_sink(port)) + new_state = SRC_TRYWAIT; + else + new_state = SRC_TRYWAIT_UNATTACHED; + + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_TRY_WAIT, 0); + break; + + case SRC_TRY: + tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_TRY, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + if (port->vbus_present) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (tcpm_port_is_source(port)) { + tcpm_set_state(port, SRC_ATTACHED, 0); + break; + } + if (tcpm_port_is_disconnected(port) && + port->delayed_state != SNK_UNATTACHED) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); + break; + + case PR_SWAP_SNK_SRC_SINK_OFF: + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF: + /* + * CC state change is expected here; we just turned off power. + * Ignore it. + */ + break; + + default: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, unattached_state(port), 0); + break; + } +} + +static void _tcpm_pd_vbus_on(struct tcpm_port *port) +{ + enum tcpm_state new_state; + + tcpm_log_force(port, "VBUS on"); + port->vbus_present = true; + switch (port->state) { + case SNK_TRANSITION_SINK_VBUS: + tcpm_set_state(port, SNK_READY, 0); + break; + case SNK_DISCOVERY: + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + + case SNK_DEBOUNCED: + tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + break; + case SNK_HARD_RESET_WAIT_VBUS: + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0); + break; + case SRC_ATTACHED: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (port->delayed_state != SRC_TRYWAIT_UNATTACHED) + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT: + if (tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (!tcpm_port_is_sink(port)) + new_state = SRC_TRYWAIT; + else + new_state = SRC_TRYWAIT_UNATTACHED; + + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_TRY_WAIT, 0); + break; + case SNK_TRYWAIT: + tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); + break; + + default: + break; + } +} + +static void _tcpm_pd_vbus_off(struct tcpm_port *port) +{ + enum tcpm_state new_state; + + tcpm_log_force(port, "VBUS off"); + port->vbus_present = false; + port->vbus_never_low = false; + switch (port->state) { + case SNK_HARD_RESET_SINK_OFF: + tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); + break; + case SRC_HARD_RESET_VBUS_OFF: + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0); + break; + case HARD_RESET_SEND: + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (tcpm_port_is_source(port)) + new_state = SRC_ATTACHED; + else + new_state = SRC_TRYWAIT_UNATTACHED; + if (new_state != port->delayed_state) + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT: + if (!tcpm_port_is_sink(port)) + new_state = SRC_TRYWAIT; + else + new_state = SRC_TRYWAIT_UNATTACHED; + + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_TRY_WAIT, 0); + break; + case SNK_TRYWAIT_VBUS: + tcpm_set_state(port, SNK_TRYWAIT, 0); + break; + + case SNK_ATTACH_WAIT: + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + + case SNK_NEGOTIATE_CAPABILITIES: + break; + + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0); + break; + + case PR_SWAP_SNK_SRC_SINK_OFF: + /* Do nothing, expected */ + break; + + case ERROR_RECOVERY_WAIT_OFF: + tcpm_set_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_UNATTACHED : SNK_UNATTACHED, + 0); + break; + + default: + if (port->pwr_role == TYPEC_SINK && + port->attached) + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + } +} + +static void _tcpm_pd_hard_reset(struct tcpm_port *port) +{ + tcpm_log_force(port, "Received hard reset"); + /* + * If we keep receiving hard reset requests, executing the hard reset + * must have failed. Revert to error recovery if that happens. + */ + tcpm_set_state(port, + port->hard_reset_count < PD_N_HARD_RESET_COUNT ? + HARD_RESET_START : ERROR_RECOVERY, + 0); +} + +static void tcpm_pd_event_handler(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + event_work); + u32 events; + + mutex_lock(&port->lock); + + spin_lock(&port->pd_event_lock); + while (port->pd_events) { + events = port->pd_events; + port->pd_events = 0; + spin_unlock(&port->pd_event_lock); + if (events & TCPM_RESET_EVENT) + _tcpm_pd_hard_reset(port); + if (events & TCPM_VBUS_EVENT) { + bool vbus; + + vbus = port->tcpc->get_vbus(port->tcpc); + if (vbus) + _tcpm_pd_vbus_on(port); + else + _tcpm_pd_vbus_off(port); + } + if (events & TCPM_CC_EVENT) { + enum typec_cc_status cc1, cc2; + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + } + spin_lock(&port->pd_event_lock); + } + spin_unlock(&port->pd_event_lock); + mutex_unlock(&port->lock); +} + +void tcpm_cc_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_CC_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_cc_change); + +void tcpm_vbus_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_VBUS_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_vbus_change); + +void tcpm_pd_hard_reset(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events = TCPM_RESET_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset); + +static int tcpm_dr_set(const struct typec_capability *cap, + enum typec_data_role data) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->typec_caps.type != TYPEC_PORT_DRP || !port->pd_capable) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (port->data_role == data) { + ret = 0; + goto port_unlock; + } + + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + * Reject data role swap request in this case. + */ + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + tcpm_set_state(port, DR_SWAP_SEND, 0); + mutex_unlock(&port->lock); + + wait_for_completion(&port->swap_complete); + + ret = port->swap_status; + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_pr_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->typec_caps.type != TYPEC_PORT_DRP) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->pwr_role) { + ret = 0; + goto port_unlock; + } + + if (!port->pd_capable) { + /* + * If the partner is not PD capable, reset the port to + * trigger a role change. This can only work if a preferred + * role is configured, and if it matches the requested role. + */ + if (port->try_role == TYPEC_NO_PREFERRED_ROLE || + port->try_role == port->pwr_role) { + ret = -EINVAL; + goto port_unlock; + } + tcpm_set_state(port, HARD_RESET_SEND, 0); + ret = 0; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + tcpm_set_state(port, PR_SWAP_SEND, 0); + mutex_unlock(&port->lock); + + wait_for_completion(&port->swap_complete); + + ret = port->swap_status; + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_vconn_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->vconn_role) { + ret = 0; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + tcpm_set_state(port, VCONN_SWAP_SEND, 0); + mutex_unlock(&port->lock); + + wait_for_completion(&port->swap_complete); + + ret = port->swap_status; + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_try_role(const struct typec_capability *cap, int role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + struct tcpc_dev *tcpc = port->tcpc; + int ret = 0; + + mutex_lock(&port->lock); + if (tcpc->try_role) + ret = tcpc->try_role(tcpc, role); + if (!ret && !tcpc->config->try_role_hw) + port->try_role = role; + port->try_src_count = 0; + port->try_snk_count = 0; + mutex_unlock(&port->lock); + + return ret; +} + +static void tcpm_init(struct tcpm_port *port) +{ + enum typec_cc_status cc1, cc2; + + port->tcpc->init(port->tcpc); + + tcpm_reset_port(port); + + /* + * XXX + * Should possibly wait for VBUS to settle if it was enabled locally + * since tcpm_reset_port() will disable VBUS. + */ + port->vbus_present = port->tcpc->get_vbus(port->tcpc); + if (port->vbus_present) + port->vbus_never_low = true; + + tcpm_set_state(port, tcpm_default_state(port), 0); + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + + /* + * Some adapters need a clean slate at startup, and won't recover + * otherwise. So do not try to be fancy and force a clean disconnect. + */ + tcpm_set_state(port, ERROR_RECOVERY, 0); +} + +void tcpm_tcpc_reset(struct tcpm_port *port) +{ + mutex_lock(&port->lock); + /* XXX: Maintain PD connection if possible? */ + tcpm_init(port); + mutex_unlock(&port->lock); +} +EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); + +static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo, + unsigned int nr_pdo) +{ + unsigned int i; + + if (nr_pdo > PDO_MAX_OBJECTS) + nr_pdo = PDO_MAX_OBJECTS; + + for (i = 0; i < nr_pdo; i++) + dest_pdo[i] = src_pdo[i]; + + return nr_pdo; +} + +void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo) +{ + mutex_lock(&port->lock); + port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo); + switch (port->state) { + case SRC_UNATTACHED: + case SRC_ATTACH_WAIT: + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + break; + case SRC_SEND_CAPABILITIES: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + break; + default: + break; + } + mutex_unlock(&port->lock); +} +EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities); + +void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo, + unsigned int max_snk_mv, + unsigned int max_snk_ma, + unsigned int max_snk_mw, + unsigned int operating_snk_mw) +{ + mutex_lock(&port->lock); + port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo); + port->max_snk_mv = max_snk_mv; + port->max_snk_ma = max_snk_ma; + port->max_snk_mw = max_snk_mw; + port->operating_snk_mw = operating_snk_mw; + + switch (port->state) { + case SNK_NEGOTIATE_CAPABILITIES: + case SNK_READY: + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + break; + default: + break; + } + mutex_unlock(&port->lock); +} +EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities); + +struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) +{ + struct tcpm_port *port; + int i, err; + + if (!dev || !tcpc || !tcpc->config || + !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc || + !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus || + !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit) + return ERR_PTR(-EINVAL); + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + port->dev = dev; + port->tcpc = tcpc; + + mutex_init(&port->lock); + mutex_init(&port->swap_lock); + + port->wq = create_singlethread_workqueue(dev_name(dev)); + if (!port->wq) + return ERR_PTR(-ENOMEM); + INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work); + INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work); + INIT_WORK(&port->event_work, tcpm_pd_event_handler); + + spin_lock_init(&port->pd_event_lock); + + init_completion(&port->tx_complete); + init_completion(&port->swap_complete); + + port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo, + tcpc->config->nr_src_pdo); + port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo, + tcpc->config->nr_snk_pdo); + + port->max_snk_mv = tcpc->config->max_snk_mv; + port->max_snk_ma = tcpc->config->max_snk_ma; + port->max_snk_mw = tcpc->config->max_snk_mw; + port->operating_snk_mw = tcpc->config->operating_snk_mw; + if (!tcpc->config->try_role_hw) + port->try_role = tcpc->config->default_role; + else + port->try_role = TYPEC_NO_PREFERRED_ROLE; + + port->typec_caps.prefer_role = tcpc->config->default_role; + port->typec_caps.type = tcpc->config->type; + port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ + port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ + port->typec_caps.dr_set = tcpm_dr_set; + port->typec_caps.pr_set = tcpm_pr_set; + port->typec_caps.vconn_set = tcpm_vconn_set; + port->typec_caps.try_role = tcpm_try_role; + + port->partner_desc.identity = &port->partner_ident; + + /* + * TODO: + * - alt_modes, set_alt_mode + * - {debug,audio}_accessory + */ + + port->typec_port = typec_register_port(port->dev, &port->typec_caps); + if (!port->typec_port) { + err = -ENOMEM; + goto out_destroy_wq; + } + + if (tcpc->config->alt_modes) { + struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; + + i = 0; + while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) { + port->port_altmode[i] = + typec_port_register_altmode(port->typec_port, + paltmode); + if (!port->port_altmode[i]) { + tcpm_log(port, + "%s: failed to register port alternate mode 0x%x", + dev_name(dev), paltmode->svid); + break; + } + i++; + paltmode++; + } + } + + tcpm_debugfs_init(port); + mutex_lock(&port->lock); + tcpm_init(port); + mutex_unlock(&port->lock); + + tcpm_log(port, "%s: registered", dev_name(dev)); + return port; + +out_destroy_wq: + destroy_workqueue(port->wq); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(tcpm_register_port); + +void tcpm_unregister_port(struct tcpm_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) + typec_unregister_altmode(port->port_altmode[i]); + typec_unregister_port(port->typec_port); + tcpm_debugfs_exit(port); + destroy_workqueue(port->wq); +} +EXPORT_SYMBOL_GPL(tcpm_unregister_port); + +MODULE_AUTHOR("Guenter Roeck <groeck@chromium.org>"); +MODULE_DESCRIPTION("USB Type-C Port Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/typec/tcpm.h b/drivers/staging/typec/tcpm.h new file mode 100644 index 000000000000..969b365e6549 --- /dev/null +++ b/drivers/staging/typec/tcpm.h @@ -0,0 +1,150 @@ +/* + * Copyright 2015-2017 Google, 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. + */ + +#ifndef __LINUX_USB_TCPM_H +#define __LINUX_USB_TCPM_H + +#include <linux/bitops.h> +#include <linux/usb/typec.h> +#include "pd.h" + +enum typec_cc_status { + TYPEC_CC_OPEN, + TYPEC_CC_RA, + TYPEC_CC_RD, + TYPEC_CC_RP_DEF, + TYPEC_CC_RP_1_5, + TYPEC_CC_RP_3_0, +}; + +enum typec_cc_polarity { + TYPEC_POLARITY_CC1, + TYPEC_POLARITY_CC2, +}; + +/* Time to wait for TCPC to complete transmit */ +#define PD_T_TCPC_TX_TIMEOUT 100 + +enum tcpm_transmit_status { + TCPC_TX_SUCCESS = 0, + TCPC_TX_DISCARDED = 1, + TCPC_TX_FAILED = 2, +}; + +enum tcpm_transmit_type { + TCPC_TX_SOP = 0, + TCPC_TX_SOP_PRIME = 1, + TCPC_TX_SOP_PRIME_PRIME = 2, + TCPC_TX_SOP_DEBUG_PRIME = 3, + TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4, + TCPC_TX_HARD_RESET = 5, + TCPC_TX_CABLE_RESET = 6, + TCPC_TX_BIST_MODE_2 = 7 +}; + +struct tcpc_config { + const u32 *src_pdo; + unsigned int nr_src_pdo; + + const u32 *snk_pdo; + unsigned int nr_snk_pdo; + + unsigned int max_snk_mv; + unsigned int max_snk_ma; + unsigned int max_snk_mw; + unsigned int operating_snk_mw; + + enum typec_port_type type; + enum typec_role default_role; + bool try_role_hw; /* try.{src,snk} implemented in hardware */ + + struct typec_altmode_desc *alt_modes; +}; + +enum tcpc_usb_switch { + TCPC_USB_SWITCH_CONNECT, + TCPC_USB_SWITCH_DISCONNECT, + TCPC_USB_SWITCH_RESTORE, /* TODO FIXME */ +}; + +/* Mux state attributes */ +#define TCPC_MUX_USB_ENABLED BIT(0) /* USB enabled */ +#define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */ +#define TCPC_MUX_POLARITY_INVERTED BIT(2) /* Polarity inverted */ + +/* Mux modes, decoded to attributes */ +enum tcpc_mux_mode { + TYPEC_MUX_NONE = 0, /* Open switch */ + TYPEC_MUX_USB = TCPC_MUX_USB_ENABLED, /* USB only */ + TYPEC_MUX_DP = TCPC_MUX_DP_ENABLED, /* DP only */ + TYPEC_MUX_DOCK = TCPC_MUX_USB_ENABLED | /* Both USB and DP */ + TCPC_MUX_DP_ENABLED, +}; + +struct tcpc_mux_dev { + int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode, + enum tcpc_usb_switch usb_config, + enum typec_cc_polarity polarity); + bool dfp_only; + void *priv_data; +}; + +struct tcpc_dev { + const struct tcpc_config *config; + + int (*init)(struct tcpc_dev *dev); + int (*get_vbus)(struct tcpc_dev *dev); + int (*set_cc)(struct tcpc_dev *dev, enum typec_cc_status cc); + int (*get_cc)(struct tcpc_dev *dev, enum typec_cc_status *cc1, + enum typec_cc_status *cc2); + int (*set_polarity)(struct tcpc_dev *dev, + enum typec_cc_polarity polarity); + int (*set_vconn)(struct tcpc_dev *dev, bool on); + int (*set_vbus)(struct tcpc_dev *dev, bool on, bool charge); + int (*set_current_limit)(struct tcpc_dev *dev, u32 max_ma, u32 mv); + int (*set_pd_rx)(struct tcpc_dev *dev, bool on); + int (*set_roles)(struct tcpc_dev *dev, bool attached, + enum typec_role role, enum typec_data_role data); + int (*start_drp_toggling)(struct tcpc_dev *dev, + enum typec_cc_status cc); + int (*try_role)(struct tcpc_dev *dev, int role); + int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type, + const struct pd_message *msg); + struct tcpc_mux_dev *mux; +}; + +struct tcpm_port; + +struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc); +void tcpm_unregister_port(struct tcpm_port *port); + +void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo); +void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo, + unsigned int max_snk_mv, + unsigned int max_snk_ma, + unsigned int max_snk_mw, + unsigned int operating_snk_mw); + +void tcpm_vbus_change(struct tcpm_port *port); +void tcpm_cc_change(struct tcpm_port *port); +void tcpm_pd_receive(struct tcpm_port *port, + const struct pd_message *msg); +void tcpm_pd_transmit_complete(struct tcpm_port *port, + enum tcpm_transmit_status status); +void tcpm_pd_hard_reset(struct tcpm_port *port); +void tcpm_tcpc_reset(struct tcpm_port *port); + +#endif /* __LINUX_USB_TCPM_H */ diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 60ce7fd54e89..1c196f87e9d9 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -66,7 +66,7 @@ static ssize_t map_size_show(struct uio_mem *mem, char *buf) static ssize_t map_offset_show(struct uio_mem *mem, char *buf) { - return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr & ~PAGE_MASK); + return sprintf(buf, "0x%llx\n", (unsigned long long)mem->offs); } struct map_sysfs_entry { diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c index d1f95a1567bb..35187c052e8c 100644 --- a/drivers/uio/uio_mf624.c +++ b/drivers/uio/uio_mf624.c @@ -127,6 +127,24 @@ static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) return 0; } +static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name) +{ + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + + mem->name = name; + mem->addr = start & PAGE_MASK; + mem->offs = start & ~PAGE_MASK; + if (!mem->addr) + return -ENODEV; + mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK; + mem->memtype = UIO_MEM_PHYS; + mem->internal_addr = pci_ioremap_bar(dev, bar); + if (!mem->internal_addr) + return -ENODEV; + return 0; +} + static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; @@ -147,37 +165,15 @@ static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) /* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */ /* BAR0 */ - info->mem[0].name = "PCI chipset, interrupts, status " - "bits, special functions"; - info->mem[0].addr = pci_resource_start(dev, 0); - if (!info->mem[0].addr) + if (mf624_setup_mem(dev, 0, &info->mem[0], "PCI chipset, interrupts, status " + "bits, special functions")) goto out_release; - info->mem[0].size = pci_resource_len(dev, 0); - info->mem[0].memtype = UIO_MEM_PHYS; - info->mem[0].internal_addr = pci_ioremap_bar(dev, 0); - if (!info->mem[0].internal_addr) - goto out_release; - /* BAR2 */ - info->mem[1].name = "ADC, DAC, DIO"; - info->mem[1].addr = pci_resource_start(dev, 2); - if (!info->mem[1].addr) - goto out_unmap0; - info->mem[1].size = pci_resource_len(dev, 2); - info->mem[1].memtype = UIO_MEM_PHYS; - info->mem[1].internal_addr = pci_ioremap_bar(dev, 2); - if (!info->mem[1].internal_addr) + if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO")) goto out_unmap0; /* BAR4 */ - info->mem[2].name = "Counter/timer chip"; - info->mem[2].addr = pci_resource_start(dev, 4); - if (!info->mem[2].addr) - goto out_unmap1; - info->mem[2].size = pci_resource_len(dev, 4); - info->mem[2].memtype = UIO_MEM_PHYS; - info->mem[2].internal_addr = pci_ioremap_bar(dev, 4); - if (!info->mem[2].internal_addr) + if (mf624_setup_mem(dev, 4, &info->mem[2], "Counter/timer chip")) goto out_unmap1; info->irq = dev->irq; diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index fbe493d44e81..939a63bca82f 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -35,7 +35,6 @@ config USB_COMMON config USB_ARCH_HAS_HCD def_bool y -# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. config USB tristate "Support for Host-side USB" depends on USB_ARCH_HAS_HCD @@ -73,6 +72,17 @@ config USB To compile this driver as a module, choose M here: the module will be called usbcore. +config USB_PCI + bool "PCI based USB host interface" + depends on PCI + default y + ---help--- + A lot of embeded system SOC (e.g. freescale T2080) have both + PCI and USB modules. But USB module is controlled by registers + directly, it have no relationship with PCI module. + + When say N here it will not build PCI related code in USB driver. + if USB source "drivers/usb/core/Kconfig" @@ -152,6 +162,8 @@ source "drivers/usb/phy/Kconfig" source "drivers/usb/gadget/Kconfig" +source "drivers/usb/typec/Kconfig" + config USB_LED_TRIG bool "USB LED Triggers" depends on LEDS_CLASS && LEDS_TRIGGERS diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 53d1356bbd06..9650b351c26c 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -14,7 +14,7 @@ obj-$(CONFIG_USB_ISP1760) += isp1760/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MTU3) += mtu3/ -obj-$(CONFIG_PCI) += host/ +obj-$(CONFIG_USB_PCI) += host/ obj-$(CONFIG_USB_EHCI_HCD) += host/ obj-$(CONFIG_USB_ISP116X_HCD) += host/ obj-$(CONFIG_USB_OHCI_HCD) += host/ @@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/ obj-$(CONFIG_USB_COMMON) += common/ obj-$(CONFIG_USBIP_CORE) += usbip/ + +obj-$(CONFIG_TYPEC) += typec/ diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index f9fe86b6f7b5..d65a64c29b85 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -474,7 +474,7 @@ static ssize_t cxacru_sysfs_store_adsl_config(struct device *dev, ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp); if (ret < 2) return -EINVAL; - if (index < 0 || index > 0x7f) + if (index > 0x7f) return -EINVAL; if (tmp < 0 || tmp > len - pos) return -EINVAL; diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index fc96f5cdcb5c..51f4157bbecf 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -20,7 +20,7 @@ config USB_CHIPIDEA_OF config USB_CHIPIDEA_PCI tristate - depends on PCI + depends on USB_PCI depends on NOP_USB_XCEIV default USB_CHIPIDEA diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 59e22389c10b..6743f85b1b7a 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -177,6 +177,7 @@ struct hw_bank { * @td_pool: allocation pool for transfer descriptors * @gadget: device side representation for peripheral controller * @driver: gadget driver + * @resume_state: save the state of gadget suspend from * @hw_ep_max: total number of endpoints supported by hardware * @ci_hw_ep: array of endpoints * @ep0_dir: ep0 direction @@ -227,6 +228,7 @@ struct ci_hdrc { struct usb_gadget gadget; struct usb_gadget_driver *driver; + enum usb_device_state resume_state; unsigned hw_ep_max; struct ci_hw_ep ci_hw_ep[ENDPT_MAX]; u32 ep0_dir; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 79ad8e91632e..9e217b1361ea 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -783,9 +783,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, } pdev->dev.parent = dev; - pdev->dev.dma_mask = dev->dma_mask; - pdev->dev.dma_parms = dev->dma_parms; - dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask); ret = platform_device_add_resources(pdev, res, nres); if (ret) @@ -841,6 +838,56 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) } } +static ssize_t ci_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", ci_role(ci)->name); +} + +static ssize_t ci_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + enum ci_role role; + int ret; + + if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) { + dev_warn(dev, "Current configuration is not dual-role, quit\n"); + return -EPERM; + } + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (!strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; + + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; + + pm_runtime_get_sync(dev); + disable_irq(ci->irq); + ci_role_stop(ci); + ret = ci_role_start(ci, role); + if (!ret && ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + enable_irq(ci->irq); + pm_runtime_put_sync(dev); + + return (ret == 0) ? n : ret; +} +static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store); + +static struct attribute *ci_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static struct attribute_group ci_attr_group = { + .attrs = ci_attrs, +}; + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1007,11 +1054,18 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_hdrc_otg_fsm_start(ci); device_set_wakeup_capable(&pdev->dev, true); - ret = dbg_create_files(ci); - if (!ret) - return 0; + if (ret) + goto stop; + + ret = sysfs_create_group(&dev->kobj, &ci_attr_group); + if (ret) + goto remove_debug; + + return 0; +remove_debug: + dbg_remove_files(ci); stop: ci_role_destroy(ci); deinit_phy: @@ -1033,6 +1087,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); + sysfs_remove_group(&ci->dev->kobj, &ci_attr_group); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 915f3e91586e..18cb8e46262d 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -123,7 +123,8 @@ static int host_start(struct ci_hdrc *ci) if (usb_disabled()) return -ENODEV; - hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev)); + hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent, + ci->dev, dev_name(ci->dev), NULL); if (!hcd) return -ENOMEM; diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index f88e9157fad0..56d2d3213076 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwreq->req.status = -EALREADY; - ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); + ret = usb_gadget_map_request_by_dev(ci->dev->parent, + &hwreq->req, hwep->dir); if (ret) return ret; @@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_del_init(&node->td); } - usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); hwreq->req.actual += actual; @@ -1845,27 +1847,32 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci) if (USBi_PCI & intr) { ci->gadget.speed = hw_port_is_high_speed(ci) ? USB_SPEED_HIGH : USB_SPEED_FULL; - if (ci->suspended && ci->driver->resume) { - spin_unlock(&ci->lock); - ci->driver->resume(&ci->gadget); - spin_lock(&ci->lock); + if (ci->suspended) { + if (ci->driver->resume) { + spin_unlock(&ci->lock); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); } } if (USBi_UI & intr) isr_tr_complete_handler(ci); - if (USBi_SLI & intr) { + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; if (ci->gadget.speed != USB_SPEED_UNKNOWN && ci->driver->suspend) { - ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); - usb_gadget_set_state(&ci->gadget, - USB_STATE_SUSPENDED); spin_lock(&ci->lock); } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); } retval = IRQ_HANDLED; } else { @@ -1899,13 +1906,13 @@ static int udc_start(struct ci_hdrc *ci) INIT_LIST_HEAD(&ci->gadget.ep_list); /* alloc resources */ - ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, sizeof(struct ci_hw_qh), 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci_hw_td", dev, + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, sizeof(struct ci_hw_td), 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { @@ -1973,6 +1980,8 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci) */ if (ci->is_otg) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); + + ci->vbus_active = 0; } /** diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig index bb8b73682a70..971385fe9abc 100644 --- a/drivers/usb/class/Kconfig +++ b/drivers/usb/class/Kconfig @@ -12,7 +12,7 @@ config USB_ACM Please read <file:Documentation/usb/acm.txt> for details. If your modem only reports "Cls=ff(vend.)" in the descriptors in - /proc/bus/usb/devices, then your modem will not work with this + /sys/kernel/debug/usb/devices, then your modem will not work with this driver. To compile this driver as a module, choose M here: the diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index d5388938bc7a..5357d83bbda2 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -36,6 +36,7 @@ #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> +#include <linux/log2.h> #include <linux/tty.h> #include <linux/serial.h> #include <linux/tty_driver.h> @@ -283,39 +284,13 @@ static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL); * Interrupt handlers for various ACM device responses */ -/* control interface reports status changes with "interrupt" transfers */ -static void acm_ctrl_irq(struct urb *urb) +static void acm_process_notification(struct acm *acm, unsigned char *buf) { - struct acm *acm = urb->context; - struct usb_cdc_notification *dr = urb->transfer_buffer; - unsigned char *data; int newctrl; int difference; - int retval; - int status = urb->status; + struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf; + unsigned char *data = buf + sizeof(struct usb_cdc_notification); - switch (status) { - case 0: - /* success */ - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - /* this urb is terminated, clean up */ - dev_dbg(&acm->control->dev, - "%s - urb shutting down with status: %d\n", - __func__, status); - return; - default: - dev_dbg(&acm->control->dev, - "%s - nonzero urb status received: %d\n", - __func__, status); - goto exit; - } - - usb_mark_last_busy(acm->dev); - - data = (unsigned char *)(dr + 1); switch (dr->bNotificationType) { case USB_CDC_NOTIFY_NETWORK_CONNECTION: dev_dbg(&acm->control->dev, @@ -323,7 +298,15 @@ static void acm_ctrl_irq(struct urb *urb) break; case USB_CDC_NOTIFY_SERIAL_STATE: + if (le16_to_cpu(dr->wLength) != 2) { + dev_dbg(&acm->control->dev, + "%s - malformed serial state\n", __func__); + break; + } + newctrl = get_unaligned_le16(data); + dev_dbg(&acm->control->dev, + "%s - serial state: 0x%x\n", __func__, newctrl); if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { dev_dbg(&acm->control->dev, @@ -359,13 +342,86 @@ static void acm_ctrl_irq(struct urb *urb) default: dev_dbg(&acm->control->dev, - "%s - unknown notification %d received: index %d " - "len %d data0 %d data1 %d\n", + "%s - unknown notification %d received: index %d len %d\n", __func__, - dr->bNotificationType, dr->wIndex, - dr->wLength, data[0], data[1]); + dr->bNotificationType, dr->wIndex, dr->wLength); + } +} + +/* control interface reports status changes with "interrupt" transfers */ +static void acm_ctrl_irq(struct urb *urb) +{ + struct acm *acm = urb->context; + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned int current_size = urb->actual_length; + unsigned int expected_size, copy_size, alloc_size; + int retval; + int status = urb->status; + + switch (status) { + case 0: + /* success */ break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + acm->nb_index = 0; + dev_dbg(&acm->control->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&acm->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(acm->dev); + + if (acm->nb_index) + dr = (struct usb_cdc_notification *)acm->notification_buffer; + + /* size = notification-header + (optional) data */ + expected_size = sizeof(struct usb_cdc_notification) + + le16_to_cpu(dr->wLength); + + if (current_size < expected_size) { + /* notification is transmitted fragmented, reassemble */ + if (acm->nb_size < expected_size) { + if (acm->nb_size) { + kfree(acm->notification_buffer); + acm->nb_size = 0; + } + alloc_size = roundup_pow_of_two(expected_size); + /* + * kmalloc ensures a valid notification_buffer after a + * use of kfree in case the previous allocation was too + * small. Final freeing is done on disconnect. + */ + acm->notification_buffer = + kmalloc(alloc_size, GFP_ATOMIC); + if (!acm->notification_buffer) + goto exit; + acm->nb_size = alloc_size; + } + + copy_size = min(current_size, + expected_size - acm->nb_index); + + memcpy(&acm->notification_buffer[acm->nb_index], + urb->transfer_buffer, copy_size); + acm->nb_index += copy_size; + current_size = acm->nb_index; + } + + if (current_size >= expected_size) { + /* notification complete */ + acm_process_notification(acm, (unsigned char *)dr); + acm->nb_index = 0; } + exit: retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval && retval != -EPERM) @@ -1174,6 +1230,7 @@ static int acm_probe(struct usb_interface *intf, int combined_interfaces = 0; struct device *tty_dev; int rv = -ENOMEM; + int res; /* normal quirks */ quirks = (unsigned long)id->driver_info; @@ -1274,23 +1331,12 @@ static int acm_probe(struct usb_interface *intf, return -EINVAL; } look_for_collapsed_interface: - for (i = 0; i < 3; i++) { - struct usb_endpoint_descriptor *ep; - ep = &data_interface->cur_altsetting->endpoint[i].desc; - - if (usb_endpoint_is_int_in(ep)) - epctrl = ep; - else if (usb_endpoint_is_bulk_out(ep)) - epwrite = ep; - else if (usb_endpoint_is_bulk_in(ep)) - epread = ep; - else - return -EINVAL; - } - if (!epctrl || !epread || !epwrite) - return -ENODEV; - else - goto made_compressed_probe; + res = usb_find_common_endpoints(data_interface->cur_altsetting, + &epread, &epwrite, &epctrl, NULL); + if (res) + return res; + + goto made_compressed_probe; } skip_normal_probe: @@ -1488,6 +1534,9 @@ skip_countries: epctrl->bInterval ? epctrl->bInterval : 16); acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; acm->ctrlurb->transfer_dma = acm->ctrl_dma; + acm->notification_buffer = NULL; + acm->nb_index = 0; + acm->nb_size = 0; dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); @@ -1580,6 +1629,8 @@ static void acm_disconnect(struct usb_interface *intf) usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); acm_read_buffers_free(acm); + kfree(acm->notification_buffer); + if (!acm->combined_interfaces) usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : acm->control); diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index c980f11cdf56..7a2b3deafc90 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -98,7 +98,9 @@ struct acm { struct acm_wb *putbuffer; /* for acm_tty_put_char() */ int rx_buflimit; spinlock_t read_lock; - int write_used; /* number of non-empty write buffers */ + u8 *notification_buffer; /* to reassemble fragmented notifications */ + unsigned int nb_index; + unsigned int nb_size; int transmitting; spinlock_t write_lock; struct mutex mutex; diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 8fda45a45bd3..08669fee6d7f 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -58,7 +58,6 @@ MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_SUSPENDING 8 #define WDM_RESETTING 9 #define WDM_OVERFLOW 10 -#define WDM_DRAIN_ON_OPEN 11 #define WDM_MAX 16 @@ -182,7 +181,7 @@ static void wdm_in_callback(struct urb *urb) "nonzero urb status received: -ESHUTDOWN\n"); goto skip_error; case -EPIPE: - dev_dbg(&desc->intf->dev, + dev_err(&desc->intf->dev, "nonzero urb status received: -EPIPE\n"); break; default: @@ -210,25 +209,6 @@ static void wdm_in_callback(struct urb *urb) desc->reslength = length; } } - - /* - * Handling devices with the WDM_DRAIN_ON_OPEN flag set: - * If desc->resp_count is unset, then the urb was submitted - * without a prior notification. If the device returned any - * data, then this implies that it had messages queued without - * notifying us. Continue reading until that queue is flushed. - */ - if (!desc->resp_count) { - if (!length) { - /* do not propagate the expected -EPIPE */ - desc->rerr = 0; - goto unlock; - } - dev_dbg(&desc->intf->dev, "got %d bytes without notification\n", length); - set_bit(WDM_RESPONDING, &desc->flags); - usb_submit_urb(desc->response, GFP_ATOMIC); - } - skip_error: set_bit(WDM_READ, &desc->flags); wake_up(&desc->wait); @@ -243,7 +223,6 @@ skip_error: service_outstanding_interrupt(desc); } -unlock: spin_unlock(&desc->iuspin); } @@ -686,17 +665,6 @@ static int wdm_open(struct inode *inode, struct file *file) dev_err(&desc->intf->dev, "Error submitting int urb - %d\n", rv); rv = usb_translate_errors(rv); - } else if (test_bit(WDM_DRAIN_ON_OPEN, &desc->flags)) { - /* - * Some devices keep pending messages queued - * without resending notifications. We must - * flush the message queue before we can - * assume a one-to-one relationship between - * notifications and messages in the queue - */ - dev_dbg(&desc->intf->dev, "draining queued data\n"); - set_bit(WDM_RESPONDING, &desc->flags); - rv = usb_submit_urb(desc->response, GFP_KERNEL); } } else { rv = 0; @@ -803,8 +771,7 @@ static void wdm_rxwork(struct work_struct *work) /* --- hotplug --- */ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, - u16 bufsize, int (*manage_power)(struct usb_interface *, int), - bool drain_on_open) + u16 bufsize, int (*manage_power)(struct usb_interface *, int)) { int rv = -ENOMEM; struct wdm_device *desc; @@ -891,68 +858,6 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor desc->manage_power = manage_power; - /* - * "drain_on_open" enables a hack to work around a firmware - * issue observed on network functions, in particular MBIM - * functions. - * - * Quoting section 7 of the CDC-WMC r1.1 specification: - * - * "The firmware shall interpret GetEncapsulatedResponse as a - * request to read response bytes. The firmware shall send - * the next wLength bytes from the response. The firmware - * shall allow the host to retrieve data using any number of - * GetEncapsulatedResponse requests. The firmware shall - * return a zero- length reply if there are no data bytes - * available. - * - * The firmware shall send ResponseAvailable notifications - * periodically, using any appropriate algorithm, to inform - * the host that there is data available in the reply - * buffer. The firmware is allowed to send ResponseAvailable - * notifications even if there is no data available, but - * this will obviously reduce overall performance." - * - * These requirements, although they make equally sense, are - * often not implemented by network functions. Some firmwares - * will queue data indefinitely, without ever resending a - * notification. The result is that the driver and firmware - * loses "syncronization" if the driver ever fails to respond - * to a single notification, something which easily can happen - * on release(). When this happens, the driver will appear to - * never receive notifications for the most current data. Each - * notification will only cause a single read, which returns - * the oldest data in the firmware's queue. - * - * The "drain_on_open" hack resolves the situation by draining - * data from the firmware until none is returned, without a - * prior notification. - * - * This will inevitably race with the firmware, risking that - * we read data from the device before handling the associated - * notification. To make things worse, some of the devices - * needing the hack do not implement the "return zero if no - * data is available" requirement either. Instead they return - * an error on the subsequent read in this case. This means - * that "winning" the race can cause an unexpected EIO to - * userspace. - * - * "winning" the race is more likely on resume() than on - * open(), and the unexpected error is more harmful in the - * middle of an open session. The hack is therefore only - * applied on open(), and not on resume() where it logically - * would be equally necessary. So we define open() as the only - * driver <-> device "syncronization point". Should we happen - * to lose a notification after open(), then syncronization - * will be lost until release() - * - * The hack should not be enabled for CDC WDM devices - * conforming to the CDC-WMC r1.1 specification. This is - * ensured by setting drain_on_open to false in wdm_probe(). - */ - if (drain_on_open) - set_bit(WDM_DRAIN_ON_OPEN, &desc->flags); - spin_lock(&wdm_device_list_lock); list_add(&desc->device_list, &wdm_device_list); spin_unlock(&wdm_device_list_lock); @@ -1006,7 +911,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) goto err; ep = &iface->endpoint[0].desc; - rv = wdm_create(intf, ep, maxcom, &wdm_manage_power, false); + rv = wdm_create(intf, ep, maxcom, &wdm_manage_power); err: return rv; @@ -1038,7 +943,7 @@ struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, { int rv = -EINVAL; - rv = wdm_create(intf, ep, bufsize, manage_power, true); + rv = wdm_create(intf, ep, bufsize, manage_power); if (rv < 0) goto err; diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index cc61055fb9be..fb87c17ed6fa 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -294,7 +294,7 @@ static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int dir, i /* * See the description for usblp_select_alts() below for the usage - * explanation. Look into your /proc/bus/usb/devices and dmesg in + * explanation. Look into your /sys/kernel/debug/usb/devices and dmesg in * case of any trouble. */ static int proto_bias = -1; @@ -1239,8 +1239,9 @@ static int usblp_select_alts(struct usblp *usblp) { struct usb_interface *if_alt; struct usb_host_interface *ifd; - struct usb_endpoint_descriptor *epd, *epwrite, *epread; - int p, i, e; + struct usb_endpoint_descriptor *epwrite, *epread; + int p, i; + int res; if_alt = usblp->intf; @@ -1260,31 +1261,21 @@ static int usblp_select_alts(struct usblp *usblp) ifd->desc.bInterfaceProtocol > USBLP_LAST_PROTOCOL) continue; - /* Look for bulk OUT and IN endpoints. */ - epwrite = epread = NULL; - for (e = 0; e < ifd->desc.bNumEndpoints; e++) { - epd = &ifd->endpoint[e].desc; - - if (usb_endpoint_is_bulk_out(epd)) - if (!epwrite) - epwrite = epd; - - if (usb_endpoint_is_bulk_in(epd)) - if (!epread) - epread = epd; + /* Look for the expected bulk endpoints. */ + if (ifd->desc.bInterfaceProtocol > 1) { + res = usb_find_common_endpoints(ifd, + &epread, &epwrite, NULL, NULL); + } else { + epread = NULL; + res = usb_find_bulk_out_endpoint(ifd, &epwrite); } /* Ignore buggy hardware without the right endpoints. */ - if (!epwrite || (ifd->desc.bInterfaceProtocol > 1 && !epread)) + if (res) continue; - /* - * Turn off reads for USB_CLASS_PRINTER/1/1 (unidirectional) - * interfaces and buggy bidirectional printers. - */ - if (ifd->desc.bInterfaceProtocol == 1) { - epread = NULL; - } else if (usblp->quirks & USBLP_QUIRK_BIDIR) { + /* Turn off reads for buggy bidirectional printers. */ + if (usblp->quirks & USBLP_QUIRK_BIDIR) { printk(KERN_INFO "usblp%d: Disabling reads from " "problematic bidirectional printer\n", usblp->minor); diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 8fb309a0ff6b..578f424decc2 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1375,7 +1375,7 @@ static int usbtmc_probe(struct usb_interface *intf, { struct usbtmc_device_data *data; struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; + struct usb_endpoint_descriptor *bulk_in, *bulk_out, *int_in; int n; int retcode; @@ -1421,49 +1421,29 @@ static int usbtmc_probe(struct usb_interface *intf, iface_desc = data->intf->cur_altsetting; data->ifnum = iface_desc->desc.bInterfaceNumber; - /* Find bulk in endpoint */ - for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { - endpoint = &iface_desc->endpoint[n].desc; - - if (usb_endpoint_is_bulk_in(endpoint)) { - data->bulk_in = endpoint->bEndpointAddress; - dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", - data->bulk_in); - break; - } - } - - /* Find bulk out endpoint */ - for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { - endpoint = &iface_desc->endpoint[n].desc; - - if (usb_endpoint_is_bulk_out(endpoint)) { - data->bulk_out = endpoint->bEndpointAddress; - dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", - data->bulk_out); - break; - } - } - - if (!data->bulk_out || !data->bulk_in) { + /* Find bulk endpoints */ + retcode = usb_find_common_endpoints(iface_desc, + &bulk_in, &bulk_out, NULL, NULL); + if (retcode) { dev_err(&intf->dev, "bulk endpoints not found\n"); - retcode = -ENODEV; goto err_put; } + data->bulk_in = bulk_in->bEndpointAddress; + dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", data->bulk_in); + + data->bulk_out = bulk_out->bEndpointAddress; + dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", data->bulk_out); + /* Find int endpoint */ - for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { - endpoint = &iface_desc->endpoint[n].desc; - - if (usb_endpoint_is_int_in(endpoint)) { - data->iin_ep_present = 1; - data->iin_ep = endpoint->bEndpointAddress; - data->iin_wMaxPacketSize = usb_endpoint_maxp(endpoint); - data->iin_interval = endpoint->bInterval; - dev_dbg(&intf->dev, "Found Int in endpoint at %u\n", + retcode = usb_find_int_in_endpoint(iface_desc, &int_in); + if (!retcode) { + data->iin_ep_present = 1; + data->iin_ep = int_in->bEndpointAddress; + data->iin_wMaxPacketSize = usb_endpoint_maxp(int_in); + data->iin_interval = int_in->bInterval; + dev_dbg(&intf->dev, "Found Int in endpoint at %u\n", data->iin_ep); - break; - } } retcode = get_capabilities(data); diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c index 2f537bbdda09..b8fe31e409a5 100644 --- a/drivers/usb/common/usb-otg-fsm.c +++ b/drivers/usb/common/usb-otg-fsm.c @@ -31,6 +31,13 @@ #include <linux/usb/otg.h> #include <linux/usb/otg-fsm.h> +#ifdef VERBOSE +#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \ + __func__, ## args) +#else +#define VDBG(stuff...) do {} while (0) +#endif + /* Change USB protocol when there is a protocol change */ static int otg_set_protocol(struct otg_fsm *fsm, int protocol) { diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index b99b871c4b9d..250ec1d662d9 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -8,7 +8,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o usbcore-y += port.o usbcore-$(CONFIG_OF) += of.o -usbcore-$(CONFIG_PCI) += hcd-pci.o +usbcore-$(CONFIG_USB_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o obj-$(CONFIG_USB) += usbcore.o diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index b9bf6e2eb6fe..b64568cf572c 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -66,7 +66,7 @@ int hcd_buffer_create(struct usb_hcd *hcd) int i, size; if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!hcd->self.controller->dma_mask && + (!is_device_dma_capable(hcd->self.sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) return 0; @@ -75,7 +75,7 @@ int hcd_buffer_create(struct usb_hcd *hcd) if (!size) continue; snprintf(name, sizeof(name), "buffer-%d", size); - hcd->pool[i] = dma_pool_create(name, hcd->self.controller, + hcd->pool[i] = dma_pool_create(name, hcd->self.sysdev, size, size, 0); if (!hcd->pool[i]) { hcd_buffer_destroy(hcd); @@ -130,7 +130,7 @@ void *hcd_buffer_alloc( /* some USB hosts just use PIO */ if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!bus->controller->dma_mask && + (!is_device_dma_capable(bus->sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) { *dma = ~(dma_addr_t) 0; return kmalloc(size, mem_flags); @@ -140,7 +140,7 @@ void *hcd_buffer_alloc( if (size <= pool_max[i]) return dma_pool_alloc(hcd->pool[i], mem_flags, dma); } - return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags); + return dma_alloc_coherent(hcd->self.sysdev, size, dma, mem_flags); } void hcd_buffer_free( @@ -157,7 +157,7 @@ void hcd_buffer_free( return; if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!bus->controller->dma_mask && + (!is_device_dma_capable(bus->sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) { kfree(addr); return; @@ -169,5 +169,5 @@ void hcd_buffer_free( return; } } - dma_free_coherent(hcd->self.controller, size, addr, dma); + dma_free_coherent(hcd->self.sysdev, size, addr, dma); } diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index f2987ddb1cde..55dea2e7828f 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -24,7 +24,7 @@ * <mountpoint>/devices contains USB topology, device, config, class, * interface, & endpoint data. * - * I considered using /proc/bus/usb/devices/device# for each device + * I considered using /dev/bus/usb/device# for each device * as it is attached or detached, but I didn't like this for some * reason -- maybe it's just too deep of a directory structure. * I also don't like looking in multiple places to gather and view @@ -40,7 +40,7 @@ * Converted the whole proc stuff to real * read methods. Now not the whole device list needs to fit * into one page, only the device list for one bus. - * Added a poll method to /proc/bus/usb/devices, to wake + * Added a poll method to /sys/kernel/debug/usb/devices, to wake * up an eventual usbd * 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch> * Turned into its own filesystem diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index cdee5130638b..eb87a259d55c 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1331,6 +1331,24 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) */ if (udev->parent && !PMSG_IS_AUTO(msg)) status = 0; + + /* + * If the device is inaccessible, don't try to resume + * suspended interfaces and just return the error. + */ + if (status && status != -EBUSY) { + int err; + u16 devstat; + + err = usb_get_status(udev, USB_RECIP_DEVICE, 0, + &devstat); + if (err) { + dev_err(&udev->dev, + "Failed to suspend device, error %d\n", + status); + goto done; + } + } } /* If the suspend failed, resume interfaces that did get suspended */ @@ -1763,6 +1781,9 @@ static int autosuspend_check(struct usb_device *udev) int w, i; struct usb_interface *intf; + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + /* Fail if autosuspend is disabled, or any interfaces are in use, or * any interface drivers require remote wakeup but it isn't available. */ diff --git a/drivers/usb/core/file.c b/drivers/usb/core/file.c index e26bd5e773ad..87ad6b6bfee8 100644 --- a/drivers/usb/core/file.c +++ b/drivers/usb/core/file.c @@ -29,6 +29,7 @@ #define MAX_USB_MINORS 256 static const struct file_operations *usb_minors[MAX_USB_MINORS]; static DECLARE_RWSEM(minor_rwsem); +static DEFINE_MUTEX(init_usb_class_mutex); static int usb_open(struct inode *inode, struct file *file) { @@ -111,8 +112,9 @@ static void release_usb_class(struct kref *kref) static void destroy_usb_class(void) { - if (usb_class) - kref_put(&usb_class->kref, release_usb_class); + mutex_lock(&init_usb_class_mutex); + kref_put(&usb_class->kref, release_usb_class); + mutex_unlock(&init_usb_class_mutex); } int usb_major_init(void) @@ -173,7 +175,10 @@ int usb_register_dev(struct usb_interface *intf, if (intf->minor >= 0) return -EADDRINUSE; + mutex_lock(&init_usb_class_mutex); retval = init_usb_class(); + mutex_unlock(&init_usb_class_mutex); + if (retval) return retval; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 79bdca5cb9c7..49550790a3cb 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1076,6 +1076,7 @@ static void usb_deregister_bus (struct usb_bus *bus) static int register_root_hub(struct usb_hcd *hcd) { struct device *parent_dev = hcd->self.controller; + struct device *sysdev = hcd->self.sysdev; struct usb_device *usb_dev = hcd->self.root_hub; const int devnum = 1; int retval; @@ -1122,7 +1123,7 @@ static int register_root_hub(struct usb_hcd *hcd) /* Did the HC die before the root hub was registered? */ if (HCD_DEAD(hcd)) usb_hc_died (hcd); /* This time clean up */ - usb_dev->dev.of_node = parent_dev->of_node; + usb_dev->dev.of_node = sysdev->of_node; } mutex_unlock(&usb_bus_idr_lock); @@ -1435,7 +1436,7 @@ void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb) { if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_SETUP_MAP_SINGLE)) - dma_unmap_single(hcd->self.controller, + dma_unmap_single(hcd->self.sysdev, urb->setup_dma, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); @@ -1468,19 +1469,19 @@ void usb_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_SG)) - dma_unmap_sg(hcd->self.controller, + dma_unmap_sg(hcd->self.sysdev, urb->sg, urb->num_sgs, dir); else if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_PAGE)) - dma_unmap_page(hcd->self.controller, + dma_unmap_page(hcd->self.sysdev, urb->transfer_dma, urb->transfer_buffer_length, dir); else if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_SINGLE)) - dma_unmap_single(hcd->self.controller, + dma_unmap_single(hcd->self.sysdev, urb->transfer_dma, urb->transfer_buffer_length, dir); @@ -1523,11 +1524,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, return ret; if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) { urb->setup_dma = dma_map_single( - hcd->self.controller, + hcd->self.sysdev, urb->setup_packet, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->setup_dma)) return -EAGAIN; urb->transfer_flags |= URB_SETUP_MAP_SINGLE; @@ -1558,7 +1559,7 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } n = dma_map_sg( - hcd->self.controller, + hcd->self.sysdev, urb->sg, urb->num_sgs, dir); @@ -1573,12 +1574,12 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } else if (urb->sg) { struct scatterlist *sg = urb->sg; urb->transfer_dma = dma_map_page( - hcd->self.controller, + hcd->self.sysdev, sg_page(sg), sg->offset, urb->transfer_buffer_length, dir); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->transfer_dma)) ret = -EAGAIN; else @@ -1588,11 +1589,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, ret = -EAGAIN; } else { urb->transfer_dma = dma_map_single( - hcd->self.controller, + hcd->self.sysdev, urb->transfer_buffer, urb->transfer_buffer_length, dir); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->transfer_dma)) ret = -EAGAIN; else @@ -2498,24 +2499,8 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh) tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh); } -/** - * usb_create_shared_hcd - create and initialize an HCD structure - * @driver: HC driver that will use this hcd - * @dev: device for this HC, stored in hcd->self.controller - * @bus_name: value to store in hcd->self.bus_name - * @primary_hcd: a pointer to the usb_hcd structure that is sharing the - * PCI device. Only allocate certain resources for the primary HCD - * Context: !in_interrupt() - * - * Allocate a struct usb_hcd, with extra space at the end for the - * HC driver's private data. Initialize the generic members of the - * hcd structure. - * - * Return: On success, a pointer to the created and initialized HCD structure. - * On failure (e.g. if memory is unavailable), %NULL. - */ -struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, - struct device *dev, const char *bus_name, +struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver, + struct device *sysdev, struct device *dev, const char *bus_name, struct usb_hcd *primary_hcd) { struct usb_hcd *hcd; @@ -2556,8 +2541,9 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, usb_bus_init(&hcd->self); hcd->self.controller = dev; + hcd->self.sysdev = sysdev; hcd->self.bus_name = bus_name; - hcd->self.uses_dma = (dev->dma_mask != NULL); + hcd->self.uses_dma = (sysdev->dma_mask != NULL); init_timer(&hcd->rh_timer); hcd->rh_timer.function = rh_timer_func; @@ -2572,6 +2558,30 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, "USB Host Controller"; return hcd; } +EXPORT_SYMBOL_GPL(__usb_create_hcd); + +/** + * usb_create_shared_hcd - create and initialize an HCD structure + * @driver: HC driver that will use this hcd + * @dev: device for this HC, stored in hcd->self.controller + * @bus_name: value to store in hcd->self.bus_name + * @primary_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Only allocate certain resources for the primary HCD + * Context: !in_interrupt() + * + * Allocate a struct usb_hcd, with extra space at the end for the + * HC driver's private data. Initialize the generic members of the + * hcd structure. + * + * Return: On success, a pointer to the created and initialized HCD structure. + * On failure (e.g. if memory is unavailable), %NULL. + */ +struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name, + struct usb_hcd *primary_hcd) +{ + return __usb_create_hcd(driver, dev, dev, bus_name, primary_hcd); +} EXPORT_SYMBOL_GPL(usb_create_shared_hcd); /** @@ -2591,7 +2601,7 @@ EXPORT_SYMBOL_GPL(usb_create_shared_hcd); struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name) { - return usb_create_shared_hcd(driver, dev, bus_name, NULL); + return __usb_create_hcd(driver, dev, dev, bus_name, NULL); } EXPORT_SYMBOL_GPL(usb_create_hcd); @@ -2718,7 +2728,7 @@ int usb_add_hcd(struct usb_hcd *hcd, struct usb_device *rhdev; if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) { - struct usb_phy *phy = usb_get_phy_dev(hcd->self.controller, 0); + struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); if (IS_ERR(phy)) { retval = PTR_ERR(phy); @@ -2736,7 +2746,7 @@ int usb_add_hcd(struct usb_hcd *hcd, } if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) { - struct phy *phy = phy_get(hcd->self.controller, "usb"); + struct phy *phy = phy_get(hcd->self.sysdev, "usb"); if (IS_ERR(phy)) { retval = PTR_ERR(phy); @@ -2784,7 +2794,7 @@ int usb_add_hcd(struct usb_hcd *hcd, */ retval = hcd_buffer_create(hcd); if (retval != 0) { - dev_dbg(hcd->self.controller, "pool alloc failed\n"); + dev_dbg(hcd->self.sysdev, "pool alloc failed\n"); goto err_create_buf; } @@ -2794,7 +2804,7 @@ int usb_add_hcd(struct usb_hcd *hcd, rhdev = usb_alloc_dev(NULL, &hcd->self, 0); if (rhdev == NULL) { - dev_err(hcd->self.controller, "unable to allocate root hub\n"); + dev_err(hcd->self.sysdev, "unable to allocate root hub\n"); retval = -ENOMEM; goto err_allocate_root_hub; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5286bf67869a..9dca59ef18b3 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1066,6 +1066,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) portstatus = portchange = 0; status = hub_port_status(hub, port1, &portstatus, &portchange); + if (status) + goto abort; + if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) dev_dbg(&port_dev->dev, "status %04x change %04x\n", portstatus, portchange); @@ -1198,7 +1201,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) /* Scan all ports that need attention */ kick_hub_wq(hub); - + abort: if (type == HUB_INIT2 || type == HUB_INIT3) { /* Allow autosuspend if it was suppressed */ disconnected: @@ -2084,6 +2087,12 @@ void usb_disconnect(struct usb_device **pdev) dev_info(&udev->dev, "USB disconnect, device number %d\n", udev->devnum); + /* + * Ensure that the pm runtime code knows that the USB device + * is in the process of being disconnected. + */ + pm_runtime_barrier(&udev->dev); + usb_lock_device(udev); hub_disconnect_children(udev); diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c index 3de4f8873984..d787f195a9a6 100644 --- a/drivers/usb/core/of.c +++ b/drivers/usb/core/of.c @@ -18,6 +18,7 @@ */ #include <linux/of.h> +#include <linux/of_platform.h> #include <linux/usb/of.h> /** @@ -46,3 +47,25 @@ struct device_node *usb_of_get_child_node(struct device_node *parent, } EXPORT_SYMBOL_GPL(usb_of_get_child_node); +/** + * usb_of_get_companion_dev - Find the companion device + * @dev: the device pointer to find a companion + * + * Find the companion device from platform bus. + * + * Return: On success, a pointer to the companion device, %NULL on failure. + */ +struct device *usb_of_get_companion_dev(struct device *dev) +{ + struct device_node *node; + struct platform_device *pdev = NULL; + + node = of_parse_phandle(dev->of_node, "companion", 0); + if (node) + pdev = of_find_device_by_node(node); + + of_node_put(node); + + return pdev ? &pdev->dev : NULL; +} +EXPORT_SYMBOL_GPL(usb_of_get_companion_dev); diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index a2ccc69fb45c..28b053cacc90 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -74,6 +74,140 @@ MODULE_PARM_DESC(autosuspend, "default autosuspend delay"); #define usb_autosuspend_delay 0 #endif +static bool match_endpoint(struct usb_endpoint_descriptor *epd, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + switch (usb_endpoint_type(epd)) { + case USB_ENDPOINT_XFER_BULK: + if (usb_endpoint_dir_in(epd)) { + if (bulk_in && !*bulk_in) { + *bulk_in = epd; + break; + } + } else { + if (bulk_out && !*bulk_out) { + *bulk_out = epd; + break; + } + } + + return false; + case USB_ENDPOINT_XFER_INT: + if (usb_endpoint_dir_in(epd)) { + if (int_in && !*int_in) { + *int_in = epd; + break; + } + } else { + if (int_out && !*int_out) { + *int_out = epd; + break; + } + } + + return false; + default: + return false; + } + + return (!bulk_in || *bulk_in) && (!bulk_out || *bulk_out) && + (!int_in || *int_in) && (!int_out || *int_out); +} + +/** + * usb_find_common_endpoints() -- look up common endpoint descriptors + * @alt: alternate setting to search + * @bulk_in: pointer to descriptor pointer, or NULL + * @bulk_out: pointer to descriptor pointer, or NULL + * @int_in: pointer to descriptor pointer, or NULL + * @int_out: pointer to descriptor pointer, or NULL + * + * Search the alternate setting's endpoint descriptors for the first bulk-in, + * bulk-out, interrupt-in and interrupt-out endpoints and return them in the + * provided pointers (unless they are NULL). + * + * If a requested endpoint is not found, the corresponding pointer is set to + * NULL. + * + * Return: Zero if all requested descriptors were found, or -ENXIO otherwise. + */ +int usb_find_common_endpoints(struct usb_host_interface *alt, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + struct usb_endpoint_descriptor *epd; + int i; + + if (bulk_in) + *bulk_in = NULL; + if (bulk_out) + *bulk_out = NULL; + if (int_in) + *int_in = NULL; + if (int_out) + *int_out = NULL; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) { + epd = &alt->endpoint[i].desc; + + if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out)) + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(usb_find_common_endpoints); + +/** + * usb_find_common_endpoints_reverse() -- look up common endpoint descriptors + * @alt: alternate setting to search + * @bulk_in: pointer to descriptor pointer, or NULL + * @bulk_out: pointer to descriptor pointer, or NULL + * @int_in: pointer to descriptor pointer, or NULL + * @int_out: pointer to descriptor pointer, or NULL + * + * Search the alternate setting's endpoint descriptors for the last bulk-in, + * bulk-out, interrupt-in and interrupt-out endpoints and return them in the + * provided pointers (unless they are NULL). + * + * If a requested endpoint is not found, the corresponding pointer is set to + * NULL. + * + * Return: Zero if all requested descriptors were found, or -ENXIO otherwise. + */ +int usb_find_common_endpoints_reverse(struct usb_host_interface *alt, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + struct usb_endpoint_descriptor *epd; + int i; + + if (bulk_in) + *bulk_in = NULL; + if (bulk_out) + *bulk_out = NULL; + if (int_in) + *int_in = NULL; + if (int_out) + *int_out = NULL; + + for (i = alt->desc.bNumEndpoints - 1; i >= 0; --i) { + epd = &alt->endpoint[i].desc; + + if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out)) + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(usb_find_common_endpoints_reverse); /** * usb_find_alt_setting() - Given a configuration, find the alternate setting @@ -453,9 +587,9 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, * Note: calling dma_set_mask() on a USB device would set the * mask for the entire HCD, so don't do that. */ - dev->dev.dma_mask = bus->controller->dma_mask; - dev->dev.dma_pfn_offset = bus->controller->dma_pfn_offset; - set_dev_node(&dev->dev, dev_to_node(bus->controller)); + dev->dev.dma_mask = bus->sysdev->dma_mask; + dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset; + set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); dev->state = USB_STATE_ATTACHED; dev->lpm_disable_count = 1; atomic_set(&dev->urbnum, 0); @@ -803,7 +937,7 @@ struct urb *usb_buffer_map(struct urb *urb) if (!urb || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return NULL; if (controller->dma_mask) { @@ -841,7 +975,7 @@ void usb_buffer_dmasync(struct urb *urb) || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return; if (controller->dma_mask) { @@ -875,7 +1009,7 @@ void usb_buffer_unmap(struct urb *urb) || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return; if (controller->dma_mask) { @@ -925,7 +1059,7 @@ int usb_buffer_map_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return -EINVAL; @@ -961,7 +1095,7 @@ void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return; @@ -989,7 +1123,7 @@ void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return; diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index e838701d6dd5..b6a495e98fd8 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -54,7 +54,7 @@ endchoice config USB_DWC2_PCI tristate "DWC2 PCI" - depends on PCI + depends on USB_PCI depends on USB_GADGET || !USB_GADGET default n select NOP_USB_XCEIV diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 1a7e83005082..8367d4f985c1 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -423,6 +423,10 @@ enum dwc2_ep0_state { * needed. * 0 - No (default) * 1 - Yes + * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO + * register. + * 0 - Deactivate the transceiver (default) + * 1 - Activate the transceiver * @g_dma: Enables gadget dma usage (default: autodetect). * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). * @g_rx_fifo_size: The periodic rx fifo size for the device, in @@ -477,6 +481,7 @@ struct dwc2_core_params { bool uframe_sched; bool external_id_pin_ctl; bool hibernation; + bool activate_stm_fs_transceiver; u16 max_packet_count; u32 max_transfer_size; u32 ahbcfg; diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index a73722e27d07..740c7e86d31b 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -121,7 +121,7 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - u32 usbcfg, i2cctl; + u32 usbcfg, ggpio, i2cctl; int retval = 0; /* @@ -145,6 +145,19 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) return retval; } } + + if (hsotg->params.activate_stm_fs_transceiver) { + ggpio = dwc2_readl(hsotg->regs + GGPIO); + if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) { + dev_dbg(hsotg->dev, "Activating transceiver\n"); + /* + * STM32F4x9 uses the GGPIO register as general + * core configuration register. + */ + ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN; + dwc2_writel(ggpio, hsotg->regs + GGPIO); + } + } } /* @@ -3264,6 +3277,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_disconnect(hsotg); dwc2_hsotg_core_init_disconnected(hsotg, false); spin_unlock_irqrestore(&hsotg->lock, flags); dwc2_hsotg_core_connect(hsotg); diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index bde72489ae66..4592012c4743 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -225,6 +225,8 @@ #define GPVNDCTL HSOTG_REG(0x0034) #define GGPIO HSOTG_REG(0x0038) +#define GGPIO_STM32_OTG_GCCFG_PWRDWN BIT(16) + #define GUID HSOTG_REG(0x003c) #define GSNPSID HSOTG_REG(0x0040) #define GHWCFG1 HSOTG_REG(0x0044) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 2990c347289f..9cd8722f24f6 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -120,6 +120,22 @@ static void dwc2_set_amcc_params(struct dwc2_hsotg *hsotg) p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; } +static void dwc2_set_stm32f4x9_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->uframe_sched = false; + p->activate_stm_fs_transceiver = true; +} + const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, @@ -133,6 +149,9 @@ const struct of_device_id dwc2_of_match_table[] = { { .compatible = "amlogic,meson-gxbb-usb", .data = dwc2_set_amlogic_params }, { .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "st,stm32f4x9-fsotg", + .data = dwc2_set_stm32f4x9_fsotg_params }, + { .compatible = "st,stm32f4x9-hsotg" }, {}, }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 9564bc76c56f..daf0d37acb37 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -214,20 +214,11 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2"); if (IS_ERR(hsotg->reset)) { ret = PTR_ERR(hsotg->reset); - switch (ret) { - case -ENOENT: - case -ENOTSUPP: - hsotg->reset = NULL; - break; - default: - dev_err(hsotg->dev, "error getting reset control %d\n", - ret); - return ret; - } + dev_err(hsotg->dev, "error getting reset control %d\n", ret); + return ret; } - if (hsotg->reset) - reset_control_deassert(hsotg->reset); + reset_control_deassert(hsotg->reset); /* Set default UTMI width */ hsotg->phyif = GUSBCFG_PHYIF16; @@ -326,8 +317,7 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->ll_hw_enabled) dwc2_lowlevel_hw_disable(hsotg); - if (hsotg->reset) - reset_control_assert(hsotg->reset); + reset_control_assert(hsotg->reset); return 0; } diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index c5aa235863e8..ab8c0e0d3b60 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -41,6 +41,7 @@ config USB_DWC3_GADGET config USB_DWC3_DUAL_ROLE bool "Dual Role mode" depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3)) + depends on (EXTCON=y || EXTCON=USB_DWC3) help This is the default mode of working of DWC3 controller where both host and gadget features are enabled. @@ -70,7 +71,7 @@ config USB_DWC3_EXYNOS config USB_DWC3_PCI tristate "PCIe-based Platforms" - depends on PCI && ACPI + depends on USB_PCI && ACPI default USB_DWC3 help If you're using the DesignWare Core IP with a PCIe, please say diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index ffca34029b21..f15fabbd1e59 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) dwc3-y += gadget.o ep0.o endif +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),) + dwc3-y += drd.o +endif + ifneq ($(CONFIG_USB_DWC3_ULPI),) dwc3-y += ulpi.o endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 369bab16a824..455d89a1cd6d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) return 0; } -void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +static void dwc3_event_buffers_cleanup(struct dwc3 *dwc); +static int dwc3_event_buffers_setup(struct dwc3 *dwc); + +static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) { u32 reg; @@ -110,6 +113,69 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } +static void __dwc3_set_mode(struct work_struct *work) +{ + struct dwc3 *dwc = work_to_dwc(work); + unsigned long flags; + int ret; + + if (!dwc->desired_dr_role) + return; + + if (dwc->desired_dr_role == dwc->current_dr_role) + return; + + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + default: + break; + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_set_prtcap(dwc, dwc->desired_dr_role); + + dwc->current_dr_role = dwc->desired_dr_role; + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (dwc->desired_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + ret = dwc3_host_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize host\n"); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_event_buffers_setup(dwc); + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + default: + break; + } +} + +void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +{ + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_dr_role = mode; + spin_unlock_irqrestore(&dwc->lock, flags); + + queue_work(system_power_efficient_wq, &dwc->drd_work); +} + u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type) { struct dwc3 *dwc = dep->dwc; @@ -397,8 +463,7 @@ static void dwc3_core_num_eps(struct dwc3 *dwc) { struct dwc3_hwparams *parms = &dwc->hwparams; - dwc->num_in_eps = DWC3_NUM_IN_EPS(parms); - dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps; + dwc->num_eps = DWC3_NUM_EPS(parms); } static void dwc3_cache_hwparams(struct dwc3 *dwc) @@ -432,6 +497,12 @@ static int dwc3_phy_setup(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); /* + * Make sure UX_EXIT_PX is cleared as that causes issues with some + * PHYs. Also, this bit is not supposed to be used in normal operation. + */ + reg &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX; + + /* * Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY * to '0' during coreConsultant configuration. So default value * will be '0' when the core is reset. Application needs to set it @@ -714,21 +785,6 @@ static int dwc3_core_init(struct dwc3 *dwc) goto err4; } - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - break; - case USB_DR_MODE_HOST: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); - break; - case USB_DR_MODE_OTG: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); - break; - default: - dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode); - break; - } - /* * ENDXFER polling is available on version 3.10a and later of * the DWC_usb3 controller. It is NOT available in the @@ -846,6 +902,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); ret = dwc3_gadget_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -854,6 +911,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_HOST: + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); ret = dwc3_host_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -862,17 +920,11 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: - ret = dwc3_host_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize host\n"); - return ret; - } - - ret = dwc3_gadget_init(dwc); + INIT_WORK(&dwc->drd_work, __dwc3_set_mode); + ret = dwc3_drd_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize gadget\n"); + dev_err(dev, "failed to initialize dual-role\n"); return ret; } break; @@ -894,8 +946,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_host_exit(dwc); break; case USB_DR_MODE_OTG: - dwc3_host_exit(dwc); - dwc3_gadget_exit(dwc); + dwc3_drd_exit(dwc); break; default: /* do nothing */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 2b9e4ca3c932..981c77f5628e 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -23,10 +23,12 @@ #include <linux/spinlock.h> #include <linux/ioport.h> #include <linux/list.h> +#include <linux/bitops.h> #include <linux/dma-mapping.h> #include <linux/mm.h> #include <linux/debugfs.h> #include <linux/wait.h> +#include <linux/workqueue.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -39,9 +41,8 @@ /* Global constants */ #define DWC3_PULL_UP_TIMEOUT 500 /* ms */ -#define DWC3_ZLP_BUF_SIZE 1024 /* size of a superspeed bulk */ #define DWC3_BOUNCE_SIZE 1024 /* size of a superspeed bulk */ -#define DWC3_EP0_BOUNCE_SIZE 512 +#define DWC3_EP0_SETUP_SIZE 512 #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 @@ -66,7 +67,7 @@ #define DWC3_DEVICE_EVENT_OVERFLOW 11 #define DWC3_GEVNTCOUNT_MASK 0xfffc -#define DWC3_GEVNTCOUNT_EHB (1 << 31) +#define DWC3_GEVNTCOUNT_EHB BIT(31) #define DWC3_GSNPSID_MASK 0xffff0000 #define DWC3_GSNPSREV_MASK 0xffff @@ -116,20 +117,20 @@ #define DWC3_VER_NUMBER 0xc1a0 #define DWC3_VER_TYPE 0xc1a4 -#define DWC3_GUSB2PHYCFG(n) (0xc200 + (n * 0x04)) -#define DWC3_GUSB2I2CCTL(n) (0xc240 + (n * 0x04)) +#define DWC3_GUSB2PHYCFG(n) (0xc200 + ((n) * 0x04)) +#define DWC3_GUSB2I2CCTL(n) (0xc240 + ((n) * 0x04)) -#define DWC3_GUSB2PHYACC(n) (0xc280 + (n * 0x04)) +#define DWC3_GUSB2PHYACC(n) (0xc280 + ((n) * 0x04)) -#define DWC3_GUSB3PIPECTL(n) (0xc2c0 + (n * 0x04)) +#define DWC3_GUSB3PIPECTL(n) (0xc2c0 + ((n) * 0x04)) -#define DWC3_GTXFIFOSIZ(n) (0xc300 + (n * 0x04)) -#define DWC3_GRXFIFOSIZ(n) (0xc380 + (n * 0x04)) +#define DWC3_GTXFIFOSIZ(n) (0xc300 + ((n) * 0x04)) +#define DWC3_GRXFIFOSIZ(n) (0xc380 + ((n) * 0x04)) -#define DWC3_GEVNTADRLO(n) (0xc400 + (n * 0x10)) -#define DWC3_GEVNTADRHI(n) (0xc404 + (n * 0x10)) -#define DWC3_GEVNTSIZ(n) (0xc408 + (n * 0x10)) -#define DWC3_GEVNTCOUNT(n) (0xc40c + (n * 0x10)) +#define DWC3_GEVNTADRLO(n) (0xc400 + ((n) * 0x10)) +#define DWC3_GEVNTADRHI(n) (0xc404 + ((n) * 0x10)) +#define DWC3_GEVNTSIZ(n) (0xc408 + ((n) * 0x10)) +#define DWC3_GEVNTCOUNT(n) (0xc40c + ((n) * 0x10)) #define DWC3_GHWPARAMS8 0xc600 #define DWC3_GFLADJ 0xc630 @@ -143,13 +144,13 @@ #define DWC3_DGCMD 0xc714 #define DWC3_DALEPENA 0xc720 -#define DWC3_DEP_BASE(n) (0xc800 + (n * 0x10)) +#define DWC3_DEP_BASE(n) (0xc800 + ((n) * 0x10)) #define DWC3_DEPCMDPAR2 0x00 #define DWC3_DEPCMDPAR1 0x04 #define DWC3_DEPCMDPAR0 0x08 #define DWC3_DEPCMD 0x0c -#define DWC3_DEV_IMOD(n) (0xca00 + (n * 0x4)) +#define DWC3_DEV_IMOD(n) (0xca00 + ((n) * 0x4)) /* OTG Registers */ #define DWC3_OCFG 0xcc00 @@ -176,11 +177,11 @@ /* Global RX Threshold Configuration Register */ #define DWC3_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 19) #define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24) -#define DWC3_GRXTHRCFG_PKTCNTSEL (1 << 29) +#define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29) /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) -#define DWC3_GCTL_U2RSTECN (1 << 16) +#define DWC3_GCTL_U2RSTECN BIT(16) #define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) #define DWC3_GCTL_CLK_BUS (0) #define DWC3_GCTL_CLK_PIPE (1) @@ -193,24 +194,24 @@ #define DWC3_GCTL_PRTCAP_DEVICE 2 #define DWC3_GCTL_PRTCAP_OTG 3 -#define DWC3_GCTL_CORESOFTRESET (1 << 11) -#define DWC3_GCTL_SOFITPSYNC (1 << 10) +#define DWC3_GCTL_CORESOFTRESET BIT(11) +#define DWC3_GCTL_SOFITPSYNC BIT(10) #define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) #define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) -#define DWC3_GCTL_DISSCRAMBLE (1 << 3) -#define DWC3_GCTL_U2EXIT_LFPS (1 << 2) -#define DWC3_GCTL_GBLHIBERNATIONEN (1 << 1) -#define DWC3_GCTL_DSBLCLKGTNG (1 << 0) +#define DWC3_GCTL_DISSCRAMBLE BIT(3) +#define DWC3_GCTL_U2EXIT_LFPS BIT(2) +#define DWC3_GCTL_GBLHIBERNATIONEN BIT(1) +#define DWC3_GCTL_DSBLCLKGTNG BIT(0) /* Global User Control 1 Register */ -#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW (1 << 24) +#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) /* Global USB2 PHY Configuration Register */ -#define DWC3_GUSB2PHYCFG_PHYSOFTRST (1 << 31) -#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS (1 << 30) -#define DWC3_GUSB2PHYCFG_SUSPHY (1 << 6) -#define DWC3_GUSB2PHYCFG_ULPI_UTMI (1 << 4) -#define DWC3_GUSB2PHYCFG_ENBLSLPM (1 << 8) +#define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) +#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS BIT(30) +#define DWC3_GUSB2PHYCFG_SUSPHY BIT(6) +#define DWC3_GUSB2PHYCFG_ULPI_UTMI BIT(4) +#define DWC3_GUSB2PHYCFG_ENBLSLPM BIT(8) #define DWC3_GUSB2PHYCFG_PHYIF(n) (n << 3) #define DWC3_GUSB2PHYCFG_PHYIF_MASK DWC3_GUSB2PHYCFG_PHYIF(1) #define DWC3_GUSB2PHYCFG_USBTRDTIM(n) (n << 10) @@ -221,25 +222,26 @@ #define UTMI_PHYIF_8_BIT 0 /* Global USB2 PHY Vendor Control Register */ -#define DWC3_GUSB2PHYACC_NEWREGREQ (1 << 25) -#define DWC3_GUSB2PHYACC_BUSY (1 << 23) -#define DWC3_GUSB2PHYACC_WRITE (1 << 22) +#define DWC3_GUSB2PHYACC_NEWREGREQ BIT(25) +#define DWC3_GUSB2PHYACC_BUSY BIT(23) +#define DWC3_GUSB2PHYACC_WRITE BIT(22) #define DWC3_GUSB2PHYACC_ADDR(n) (n << 16) #define DWC3_GUSB2PHYACC_EXTEND_ADDR(n) (n << 8) #define DWC3_GUSB2PHYACC_DATA(n) (n & 0xff) /* Global USB3 PIPE Control Register */ -#define DWC3_GUSB3PIPECTL_PHYSOFTRST (1 << 31) -#define DWC3_GUSB3PIPECTL_U2SSINP3OK (1 << 29) -#define DWC3_GUSB3PIPECTL_DISRXDETINP3 (1 << 28) -#define DWC3_GUSB3PIPECTL_REQP1P2P3 (1 << 24) +#define DWC3_GUSB3PIPECTL_PHYSOFTRST BIT(31) +#define DWC3_GUSB3PIPECTL_U2SSINP3OK BIT(29) +#define DWC3_GUSB3PIPECTL_DISRXDETINP3 BIT(28) +#define DWC3_GUSB3PIPECTL_UX_EXIT_PX BIT(27) +#define DWC3_GUSB3PIPECTL_REQP1P2P3 BIT(24) #define DWC3_GUSB3PIPECTL_DEP1P2P3(n) ((n) << 19) #define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK DWC3_GUSB3PIPECTL_DEP1P2P3(7) #define DWC3_GUSB3PIPECTL_DEP1P2P3_EN DWC3_GUSB3PIPECTL_DEP1P2P3(1) -#define DWC3_GUSB3PIPECTL_DEPOCHANGE (1 << 18) -#define DWC3_GUSB3PIPECTL_SUSPHY (1 << 17) -#define DWC3_GUSB3PIPECTL_LFPSFILT (1 << 9) -#define DWC3_GUSB3PIPECTL_RX_DETOPOLL (1 << 8) +#define DWC3_GUSB3PIPECTL_DEPOCHANGE BIT(18) +#define DWC3_GUSB3PIPECTL_SUSPHY BIT(17) +#define DWC3_GUSB3PIPECTL_LFPSFILT BIT(9) +#define DWC3_GUSB3PIPECTL_RX_DETOPOLL BIT(8) #define DWC3_GUSB3PIPECTL_TX_DEEPH_MASK DWC3_GUSB3PIPECTL_TX_DEEPH(3) #define DWC3_GUSB3PIPECTL_TX_DEEPH(n) ((n) << 1) @@ -248,7 +250,7 @@ #define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) /* Global Event Size Registers */ -#define DWC3_GEVNTSIZ_INTMASK (1 << 31) +#define DWC3_GEVNTSIZ_INTMASK BIT(31) #define DWC3_GEVNTSIZ_SIZE(n) ((n) & 0xffff) /* Global HWPARAMS0 Register */ @@ -289,18 +291,18 @@ #define DWC3_MAX_HIBER_SCRATCHBUFS 15 /* Global HWPARAMS6 Register */ -#define DWC3_GHWPARAMS6_EN_FPGA (1 << 7) +#define DWC3_GHWPARAMS6_EN_FPGA BIT(7) /* Global HWPARAMS7 Register */ #define DWC3_GHWPARAMS7_RAM1_DEPTH(n) ((n) & 0xffff) #define DWC3_GHWPARAMS7_RAM2_DEPTH(n) (((n) >> 16) & 0xffff) /* Global Frame Length Adjustment Register */ -#define DWC3_GFLADJ_30MHZ_SDBND_SEL (1 << 7) +#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7) #define DWC3_GFLADJ_30MHZ_MASK 0x3f /* Global User Control Register 2 */ -#define DWC3_GUCTL2_RST_ACTBITLATER (1 << 14) +#define DWC3_GUCTL2_RST_ACTBITLATER BIT(14) /* Device Configuration Register */ #define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) @@ -310,23 +312,23 @@ #define DWC3_DCFG_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */ #define DWC3_DCFG_SUPERSPEED (4 << 0) #define DWC3_DCFG_HIGHSPEED (0 << 0) -#define DWC3_DCFG_FULLSPEED (1 << 0) +#define DWC3_DCFG_FULLSPEED BIT(0) #define DWC3_DCFG_LOWSPEED (2 << 0) #define DWC3_DCFG_NUMP_SHIFT 17 #define DWC3_DCFG_NUMP(n) (((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f) #define DWC3_DCFG_NUMP_MASK (0x1f << DWC3_DCFG_NUMP_SHIFT) -#define DWC3_DCFG_LPM_CAP (1 << 22) +#define DWC3_DCFG_LPM_CAP BIT(22) /* Device Control Register */ -#define DWC3_DCTL_RUN_STOP (1 << 31) -#define DWC3_DCTL_CSFTRST (1 << 30) -#define DWC3_DCTL_LSFTRST (1 << 29) +#define DWC3_DCTL_RUN_STOP BIT(31) +#define DWC3_DCTL_CSFTRST BIT(30) +#define DWC3_DCTL_LSFTRST BIT(29) #define DWC3_DCTL_HIRD_THRES_MASK (0x1f << 24) #define DWC3_DCTL_HIRD_THRES(n) ((n) << 24) -#define DWC3_DCTL_APPL1RES (1 << 23) +#define DWC3_DCTL_APPL1RES BIT(23) /* These apply for core versions 1.87a and earlier */ #define DWC3_DCTL_TRGTULST_MASK (0x0f << 17) @@ -341,15 +343,15 @@ #define DWC3_DCTL_LPM_ERRATA_MASK DWC3_DCTL_LPM_ERRATA(0xf) #define DWC3_DCTL_LPM_ERRATA(n) ((n) << 20) -#define DWC3_DCTL_KEEP_CONNECT (1 << 19) -#define DWC3_DCTL_L1_HIBER_EN (1 << 18) -#define DWC3_DCTL_CRS (1 << 17) -#define DWC3_DCTL_CSS (1 << 16) +#define DWC3_DCTL_KEEP_CONNECT BIT(19) +#define DWC3_DCTL_L1_HIBER_EN BIT(18) +#define DWC3_DCTL_CRS BIT(17) +#define DWC3_DCTL_CSS BIT(16) -#define DWC3_DCTL_INITU2ENA (1 << 12) -#define DWC3_DCTL_ACCEPTU2ENA (1 << 11) -#define DWC3_DCTL_INITU1ENA (1 << 10) -#define DWC3_DCTL_ACCEPTU1ENA (1 << 9) +#define DWC3_DCTL_INITU2ENA BIT(12) +#define DWC3_DCTL_ACCEPTU2ENA BIT(11) +#define DWC3_DCTL_INITU1ENA BIT(10) +#define DWC3_DCTL_ACCEPTU1ENA BIT(9) #define DWC3_DCTL_TSTCTRL_MASK (0xf << 1) #define DWC3_DCTL_ULSTCHNGREQ_MASK (0x0f << 5) @@ -364,36 +366,36 @@ #define DWC3_DCTL_ULSTCHNG_LOOPBACK (DWC3_DCTL_ULSTCHNGREQ(11)) /* Device Event Enable Register */ -#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN (1 << 12) -#define DWC3_DEVTEN_EVNTOVERFLOWEN (1 << 11) -#define DWC3_DEVTEN_CMDCMPLTEN (1 << 10) -#define DWC3_DEVTEN_ERRTICERREN (1 << 9) -#define DWC3_DEVTEN_SOFEN (1 << 7) -#define DWC3_DEVTEN_EOPFEN (1 << 6) -#define DWC3_DEVTEN_HIBERNATIONREQEVTEN (1 << 5) -#define DWC3_DEVTEN_WKUPEVTEN (1 << 4) -#define DWC3_DEVTEN_ULSTCNGEN (1 << 3) -#define DWC3_DEVTEN_CONNECTDONEEN (1 << 2) -#define DWC3_DEVTEN_USBRSTEN (1 << 1) -#define DWC3_DEVTEN_DISCONNEVTEN (1 << 0) +#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN BIT(12) +#define DWC3_DEVTEN_EVNTOVERFLOWEN BIT(11) +#define DWC3_DEVTEN_CMDCMPLTEN BIT(10) +#define DWC3_DEVTEN_ERRTICERREN BIT(9) +#define DWC3_DEVTEN_SOFEN BIT(7) +#define DWC3_DEVTEN_EOPFEN BIT(6) +#define DWC3_DEVTEN_HIBERNATIONREQEVTEN BIT(5) +#define DWC3_DEVTEN_WKUPEVTEN BIT(4) +#define DWC3_DEVTEN_ULSTCNGEN BIT(3) +#define DWC3_DEVTEN_CONNECTDONEEN BIT(2) +#define DWC3_DEVTEN_USBRSTEN BIT(1) +#define DWC3_DEVTEN_DISCONNEVTEN BIT(0) /* Device Status Register */ -#define DWC3_DSTS_DCNRD (1 << 29) +#define DWC3_DSTS_DCNRD BIT(29) /* This applies for core versions 1.87a and earlier */ -#define DWC3_DSTS_PWRUPREQ (1 << 24) +#define DWC3_DSTS_PWRUPREQ BIT(24) /* These apply for core versions 1.94a and later */ -#define DWC3_DSTS_RSS (1 << 25) -#define DWC3_DSTS_SSS (1 << 24) +#define DWC3_DSTS_RSS BIT(25) +#define DWC3_DSTS_SSS BIT(24) -#define DWC3_DSTS_COREIDLE (1 << 23) -#define DWC3_DSTS_DEVCTRLHLT (1 << 22) +#define DWC3_DSTS_COREIDLE BIT(23) +#define DWC3_DSTS_DEVCTRLHLT BIT(22) #define DWC3_DSTS_USBLNKST_MASK (0x0f << 18) #define DWC3_DSTS_USBLNKST(n) (((n) & DWC3_DSTS_USBLNKST_MASK) >> 18) -#define DWC3_DSTS_RXFIFOEMPTY (1 << 17) +#define DWC3_DSTS_RXFIFOEMPTY BIT(17) #define DWC3_DSTS_SOFFN_MASK (0x3fff << 3) #define DWC3_DSTS_SOFFN(n) (((n) & DWC3_DSTS_SOFFN_MASK) >> 3) @@ -403,7 +405,7 @@ #define DWC3_DSTS_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */ #define DWC3_DSTS_SUPERSPEED (4 << 0) #define DWC3_DSTS_HIGHSPEED (0 << 0) -#define DWC3_DSTS_FULLSPEED (1 << 0) +#define DWC3_DSTS_FULLSPEED BIT(0) #define DWC3_DSTS_LOWSPEED (2 << 0) /* Device Generic Command Register */ @@ -421,26 +423,26 @@ #define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK 0x10 #define DWC3_DGCMD_STATUS(n) (((n) >> 12) & 0x0F) -#define DWC3_DGCMD_CMDACT (1 << 10) -#define DWC3_DGCMD_CMDIOC (1 << 8) +#define DWC3_DGCMD_CMDACT BIT(10) +#define DWC3_DGCMD_CMDIOC BIT(8) /* Device Generic Command Parameter Register */ -#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT (1 << 0) +#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT BIT(0) #define DWC3_DGCMDPAR_FIFO_NUM(n) ((n) << 0) #define DWC3_DGCMDPAR_RX_FIFO (0 << 5) -#define DWC3_DGCMDPAR_TX_FIFO (1 << 5) +#define DWC3_DGCMDPAR_TX_FIFO BIT(5) #define DWC3_DGCMDPAR_LOOPBACK_DIS (0 << 0) -#define DWC3_DGCMDPAR_LOOPBACK_ENA (1 << 0) +#define DWC3_DGCMDPAR_LOOPBACK_ENA BIT(0) /* Device Endpoint Command Register */ #define DWC3_DEPCMD_PARAM_SHIFT 16 #define DWC3_DEPCMD_PARAM(x) ((x) << DWC3_DEPCMD_PARAM_SHIFT) #define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) #define DWC3_DEPCMD_STATUS(x) (((x) >> 12) & 0x0F) -#define DWC3_DEPCMD_HIPRI_FORCERM (1 << 11) -#define DWC3_DEPCMD_CLEARPENDIN (1 << 11) -#define DWC3_DEPCMD_CMDACT (1 << 10) -#define DWC3_DEPCMD_CMDIOC (1 << 8) +#define DWC3_DEPCMD_HIPRI_FORCERM BIT(11) +#define DWC3_DEPCMD_CLEARPENDIN BIT(11) +#define DWC3_DEPCMD_CMDACT BIT(10) +#define DWC3_DEPCMD_CMDIOC BIT(8) #define DWC3_DEPCMD_DEPSTARTCFG (0x09 << 0) #define DWC3_DEPCMD_ENDTRANSFER (0x08 << 0) @@ -458,7 +460,7 @@ #define DWC3_DEPCMD_CMD(x) ((x) & 0xf) /* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ -#define DWC3_DALEPENA_EP(n) (1 << n) +#define DWC3_DALEPENA_EP(n) BIT(n) #define DWC3_DEPCMD_TYPE_CONTROL 0 #define DWC3_DEPCMD_TYPE_ISOC 1 @@ -500,8 +502,8 @@ struct dwc3_event_buffer { struct dwc3 *dwc; }; -#define DWC3_EP_FLAG_STALLED (1 << 0) -#define DWC3_EP_FLAG_WEDGED (1 << 1) +#define DWC3_EP_FLAG_STALLED BIT(0) +#define DWC3_EP_FLAG_WEDGED BIT(1) #define DWC3_EP_DIRECTION_TX true #define DWC3_EP_DIRECTION_RX false @@ -550,17 +552,17 @@ struct dwc3_ep { u32 saved_state; unsigned flags; -#define DWC3_EP_ENABLED (1 << 0) -#define DWC3_EP_STALL (1 << 1) -#define DWC3_EP_WEDGE (1 << 2) -#define DWC3_EP_BUSY (1 << 4) -#define DWC3_EP_PENDING_REQUEST (1 << 5) -#define DWC3_EP_MISSED_ISOC (1 << 6) -#define DWC3_EP_END_TRANSFER_PENDING (1 << 7) -#define DWC3_EP_TRANSFER_STARTED (1 << 8) +#define DWC3_EP_ENABLED BIT(0) +#define DWC3_EP_STALL BIT(1) +#define DWC3_EP_WEDGE BIT(2) +#define DWC3_EP_BUSY BIT(4) +#define DWC3_EP_PENDING_REQUEST BIT(5) +#define DWC3_EP_MISSED_ISOC BIT(6) +#define DWC3_EP_END_TRANSFER_PENDING BIT(7) +#define DWC3_EP_TRANSFER_STARTED BIT(8) /* This last one is specific to EP0 */ -#define DWC3_EP0_DIR_IN (1 << 31) +#define DWC3_EP0_DIR_IN BIT(31) /* * IMPORTANT: we *know* we have 256 TRBs in our @trb_pool, so we will @@ -638,13 +640,13 @@ enum dwc3_link_state { #define DWC3_TRB_STS_XFER_IN_PROG 4 /* TRB Control */ -#define DWC3_TRB_CTRL_HWO (1 << 0) -#define DWC3_TRB_CTRL_LST (1 << 1) -#define DWC3_TRB_CTRL_CHN (1 << 2) -#define DWC3_TRB_CTRL_CSP (1 << 3) +#define DWC3_TRB_CTRL_HWO BIT(0) +#define DWC3_TRB_CTRL_LST BIT(1) +#define DWC3_TRB_CTRL_CHN BIT(2) +#define DWC3_TRB_CTRL_CSP BIT(3) #define DWC3_TRB_CTRL_TRBCTL(n) (((n) & 0x3f) << 4) -#define DWC3_TRB_CTRL_ISP_IMI (1 << 10) -#define DWC3_TRB_CTRL_IOC (1 << 11) +#define DWC3_TRB_CTRL_ISP_IMI BIT(10) +#define DWC3_TRB_CTRL_IOC BIT(11) #define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14) #define DWC3_TRBCTL_TYPE(n) ((n) & (0x3f << 4)) @@ -746,6 +748,7 @@ struct dwc3_request { unsigned direction:1; unsigned mapped:1; unsigned started:1; + unsigned zero:1; }; /* @@ -758,15 +761,11 @@ struct dwc3_scratchpad_array { /** * struct dwc3 - representation of our controller - * @ctrl_req: usb control request which is used for ep0 + * @drd_work - workqueue used for role swapping * @ep0_trb: trb which is used for the ctrl_req - * @ep0_bounce: bounce buffer for ep0 - * @zlp_buf: used when request->zero is set * @setup_buf: used while precessing STD USB requests - * @ctrl_req_addr: dma address of ctrl_req * @ep0_trb: dma address of ep0_trb * @ep0_usb_req: dummy req used while handling STD USB requests - * @ep0_bounce_addr: dma address of ep0_bounce * @scratch_addr: dma address of scratchbuf * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing @@ -784,6 +783,10 @@ struct dwc3_scratchpad_array { * @maximum_speed: maximum speed requested (mainly for testing purposes) * @revision: revision register contents * @dr_mode: requested mode of operation + * @current_dr_role: current role of operation when in dual-role mode + * @desired_dr_role: desired role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW @@ -799,8 +802,7 @@ struct dwc3_scratchpad_array { * @u2pel: parameter from Set SEL request. * @u1sel: parameter from Set SEL request. * @u1pel: parameter from Set SEL request. - * @num_out_eps: number of out endpoints - * @num_in_eps: number of in endpoints + * @num_eps: number of endpoints * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state @@ -858,17 +860,13 @@ struct dwc3_scratchpad_array { * increments or 0 to disable. */ struct dwc3 { - struct usb_ctrlrequest *ctrl_req; + struct work_struct drd_work; struct dwc3_trb *ep0_trb; void *bounce; - void *ep0_bounce; - void *zlp_buf; void *scratchbuf; u8 *setup_buf; - dma_addr_t ctrl_req_addr; dma_addr_t ep0_trb_addr; dma_addr_t bounce_addr; - dma_addr_t ep0_bounce_addr; dma_addr_t scratch_addr; struct dwc3_request ep0_usb_req; struct completion ep0_in_setup; @@ -900,6 +898,10 @@ struct dwc3 { size_t regs_size; enum usb_dr_mode dr_mode; + u32 current_dr_role; + u32 desired_dr_role; + struct extcon_dev *edev; + struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; u32 fladj; @@ -960,8 +962,7 @@ struct dwc3 { u8 speed; - u8 num_out_eps; - u8 num_in_eps; + u8 num_eps; struct dwc3_hwparams hwparams; struct dentry *root; @@ -1010,7 +1011,7 @@ struct dwc3 { u16 imod_interval; }; -/* -------------------------------------------------------------------------- */ +#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work)) /* -------------------------------------------------------------------------- */ @@ -1054,13 +1055,13 @@ struct dwc3_event_depevt { u32 status:4; /* Within XferNotReady */ -#define DEPEVT_STATUS_TRANSFER_ACTIVE (1 << 3) +#define DEPEVT_STATUS_TRANSFER_ACTIVE BIT(3) /* Within XferComplete */ -#define DEPEVT_STATUS_BUSERR (1 << 0) -#define DEPEVT_STATUS_SHORT (1 << 1) -#define DEPEVT_STATUS_IOC (1 << 2) -#define DEPEVT_STATUS_LST (1 << 3) +#define DEPEVT_STATUS_BUSERR BIT(0) +#define DEPEVT_STATUS_SHORT BIT(1) +#define DEPEVT_STATUS_IOC BIT(2) +#define DEPEVT_STATUS_LST BIT(3) /* Stream event only */ #define DEPEVT_STREAMEVT_FOUND 1 @@ -1221,6 +1222,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, { return 0; } #endif +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +#endif + /* power management interface */ #if !IS_ENABLED(CONFIG_USB_DWC3_HOST) int dwc3_gadget_suspend(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index eeed4ffd8131..cb2d8d3f7f3d 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -124,6 +124,34 @@ dwc3_gadget_link_string(enum dwc3_link_state link_state) } } +/** + * dwc3_trb_type_string - returns TRB type as a string + * @type: the type of the TRB + */ +static inline const char *dwc3_trb_type_string(unsigned int type) +{ + switch (type) { + case DWC3_TRBCTL_NORMAL: + return "normal"; + case DWC3_TRBCTL_CONTROL_SETUP: + return "setup"; + case DWC3_TRBCTL_CONTROL_STATUS2: + return "status2"; + case DWC3_TRBCTL_CONTROL_STATUS3: + return "status3"; + case DWC3_TRBCTL_CONTROL_DATA: + return "data"; + case DWC3_TRBCTL_ISOCHRONOUS_FIRST: + return "isoc-first"; + case DWC3_TRBCTL_ISOCHRONOUS: + return "isoc"; + case DWC3_TRBCTL_LINK_TRB: + return "link"; + default: + return "UNKNOWN"; + } +} + static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) { switch (state) { diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 31926dda43c9..7be963dd8e3b 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -300,7 +300,7 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) seq_printf(s, "device\n"); break; case DWC3_GCTL_PRTCAP_OTG: - seq_printf(s, "OTG\n"); + seq_printf(s, "otg\n"); break; default: seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); @@ -319,7 +319,6 @@ static ssize_t dwc3_mode_write(struct file *file, { struct seq_file *s = file->private_data; struct dwc3 *dwc = s->private; - unsigned long flags; u32 mode = 0; char buf[32]; @@ -327,19 +326,16 @@ static ssize_t dwc3_mode_write(struct file *file, return -EFAULT; if (!strncmp(buf, "host", 4)) - mode |= DWC3_GCTL_PRTCAP_HOST; + mode = DWC3_GCTL_PRTCAP_HOST; if (!strncmp(buf, "device", 6)) - mode |= DWC3_GCTL_PRTCAP_DEVICE; + mode = DWC3_GCTL_PRTCAP_DEVICE; if (!strncmp(buf, "otg", 3)) - mode |= DWC3_GCTL_PRTCAP_OTG; + mode = DWC3_GCTL_PRTCAP_OTG; + + dwc3_set_mode(dwc, mode); - if (mode) { - spin_lock_irqsave(&dwc->lock, flags); - dwc3_set_mode(dwc, mode); - spin_unlock_irqrestore(&dwc->lock, flags); - } return count; } @@ -446,52 +442,7 @@ static int dwc3_link_state_show(struct seq_file *s, void *unused) state = DWC3_DSTS_USBLNKST(reg); spin_unlock_irqrestore(&dwc->lock, flags); - switch (state) { - case DWC3_LINK_STATE_U0: - seq_printf(s, "U0\n"); - break; - case DWC3_LINK_STATE_U1: - seq_printf(s, "U1\n"); - break; - case DWC3_LINK_STATE_U2: - seq_printf(s, "U2\n"); - break; - case DWC3_LINK_STATE_U3: - seq_printf(s, "U3\n"); - break; - case DWC3_LINK_STATE_SS_DIS: - seq_printf(s, "SS.Disabled\n"); - break; - case DWC3_LINK_STATE_RX_DET: - seq_printf(s, "Rx.Detect\n"); - break; - case DWC3_LINK_STATE_SS_INACT: - seq_printf(s, "SS.Inactive\n"); - break; - case DWC3_LINK_STATE_POLL: - seq_printf(s, "Poll\n"); - break; - case DWC3_LINK_STATE_RECOV: - seq_printf(s, "Recovery\n"); - break; - case DWC3_LINK_STATE_HRESET: - seq_printf(s, "HRESET\n"); - break; - case DWC3_LINK_STATE_CMPLY: - seq_printf(s, "Compliance\n"); - break; - case DWC3_LINK_STATE_LPBK: - seq_printf(s, "Loopback\n"); - break; - case DWC3_LINK_STATE_RESET: - seq_printf(s, "Reset\n"); - break; - case DWC3_LINK_STATE_RESUME: - seq_printf(s, "Resume\n"); - break; - default: - seq_printf(s, "UNKNOWN %d\n", state); - } + seq_printf(s, "%s\n", dwc3_gadget_link_string(state)); return 0; } @@ -689,30 +640,6 @@ out: return 0; } -static inline const char *dwc3_trb_type_string(struct dwc3_trb *trb) -{ - switch (DWC3_TRBCTL_TYPE(trb->ctrl)) { - case DWC3_TRBCTL_NORMAL: - return "normal"; - case DWC3_TRBCTL_CONTROL_SETUP: - return "control-setup"; - case DWC3_TRBCTL_CONTROL_STATUS2: - return "control-status2"; - case DWC3_TRBCTL_CONTROL_STATUS3: - return "control-status3"; - case DWC3_TRBCTL_CONTROL_DATA: - return "control-data"; - case DWC3_TRBCTL_ISOCHRONOUS_FIRST: - return "isoc-first"; - case DWC3_TRBCTL_ISOCHRONOUS: - return "isoc"; - case DWC3_TRBCTL_LINK_TRB: - return "link"; - default: - return "UNKNOWN"; - } -} - static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) { struct dwc3_ep *dep = s->private; @@ -733,10 +660,11 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) for (i = 0; i < DWC3_TRB_NUM; i++) { struct dwc3_trb *trb = &dep->trb_pool[i]; + unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl); seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d\n", trb->bph, trb->bpl, trb->size, - dwc3_trb_type_string(trb), + dwc3_trb_type_string(type), !!(trb->ctrl & DWC3_TRB_CTRL_IOC), !!(trb->ctrl & DWC3_TRB_CTRL_ISP_IMI), !!(trb->ctrl & DWC3_TRB_CTRL_CSP), @@ -822,19 +750,8 @@ static void dwc3_debugfs_create_endpoint_dirs(struct dwc3 *dwc, { int i; - for (i = 0; i < dwc->num_in_eps; i++) { - u8 epnum = (i << 1) | 1; - struct dwc3_ep *dep = dwc->eps[epnum]; - - if (!dep) - continue; - - dwc3_debugfs_create_endpoint_dir(dep, parent); - } - - for (i = 0; i < dwc->num_out_eps; i++) { - u8 epnum = (i << 1); - struct dwc3_ep *dep = dwc->eps[epnum]; + for (i = 0; i < dwc->num_eps; i++) { + struct dwc3_ep *dep = dwc->eps[i]; if (!dep) continue; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 000000000000..2765c51c7ef5 --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,85 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual-role support + * + * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Roger Quadros <rogerq@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/extcon.h> + +#include "debug.h" +#include "core.h" +#include "gadget.h" + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); +} + +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + + dwc3_set_mode(dwc, event ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + + return NOTIFY_DONE; +} + +int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret; + + if (dwc->dev->of_node) { + if (of_property_read_bool(dwc->dev->of_node, "extcon")) + dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); + + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "couldn't register cable notifier\n"); + return ret; + } + } + + dwc3_drd_update(dwc); + + return 0; +} + +void dwc3_drd_exit(struct dwc3 *dwc) +{ + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + flush_work(&dwc->drd_work); + dwc3_gadget_exit(dwc); +} diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 1515d45ebcec..98f74ff66120 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -147,53 +147,53 @@ static int dwc3_exynos_probe(struct platform_device *pdev) exynos->vdd33 = devm_regulator_get(dev, "vdd33"); if (IS_ERR(exynos->vdd33)) { ret = PTR_ERR(exynos->vdd33); - goto err2; + goto vdd33_err; } ret = regulator_enable(exynos->vdd33); if (ret) { dev_err(dev, "Failed to enable VDD33 supply\n"); - goto err2; + goto vdd33_err; } exynos->vdd10 = devm_regulator_get(dev, "vdd10"); if (IS_ERR(exynos->vdd10)) { ret = PTR_ERR(exynos->vdd10); - goto err3; + goto vdd10_err; } ret = regulator_enable(exynos->vdd10); if (ret) { dev_err(dev, "Failed to enable VDD10 supply\n"); - goto err3; + goto vdd10_err; } ret = dwc3_exynos_register_phys(exynos); if (ret) { dev_err(dev, "couldn't register PHYs\n"); - goto err4; + goto phys_err; } if (node) { ret = of_platform_populate(node, NULL, NULL, dev); if (ret) { dev_err(dev, "failed to add dwc3 core\n"); - goto err5; + goto populate_err; } } else { dev_err(dev, "no device node, failed to add dwc3 core\n"); ret = -ENODEV; - goto err5; + goto populate_err; } return 0; -err5: +populate_err: platform_device_unregister(exynos->usb2_phy); platform_device_unregister(exynos->usb3_phy); -err4: +phys_err: regulator_disable(exynos->vdd10); -err3: +vdd10_err: regulator_disable(exynos->vdd33); -err2: +vdd33_err: clk_disable_unprepare(exynos->axius_clk); axius_clk_err: clk_disable_unprepare(exynos->susp_clk); diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index f8d0747810e7..98926504b55b 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -79,40 +79,40 @@ #define USBOTGSS_DEBUG_OFFSET 0x0600 /* SYSCONFIG REGISTER */ -#define USBOTGSS_SYSCONFIG_DMADISABLE (1 << 16) +#define USBOTGSS_SYSCONFIG_DMADISABLE BIT(16) /* IRQ_EOI REGISTER */ -#define USBOTGSS_IRQ_EOI_LINE_NUMBER (1 << 0) +#define USBOTGSS_IRQ_EOI_LINE_NUMBER BIT(0) /* IRQS0 BITS */ -#define USBOTGSS_IRQO_COREIRQ_ST (1 << 0) +#define USBOTGSS_IRQO_COREIRQ_ST BIT(0) /* IRQMISC BITS */ -#define USBOTGSS_IRQMISC_DMADISABLECLR (1 << 17) -#define USBOTGSS_IRQMISC_OEVT (1 << 16) -#define USBOTGSS_IRQMISC_DRVVBUS_RISE (1 << 13) -#define USBOTGSS_IRQMISC_CHRGVBUS_RISE (1 << 12) -#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE (1 << 11) -#define USBOTGSS_IRQMISC_IDPULLUP_RISE (1 << 8) -#define USBOTGSS_IRQMISC_DRVVBUS_FALL (1 << 5) -#define USBOTGSS_IRQMISC_CHRGVBUS_FALL (1 << 4) -#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL (1 << 3) -#define USBOTGSS_IRQMISC_IDPULLUP_FALL (1 << 0) +#define USBOTGSS_IRQMISC_DMADISABLECLR BIT(17) +#define USBOTGSS_IRQMISC_OEVT BIT(16) +#define USBOTGSS_IRQMISC_DRVVBUS_RISE BIT(13) +#define USBOTGSS_IRQMISC_CHRGVBUS_RISE BIT(12) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE BIT(11) +#define USBOTGSS_IRQMISC_IDPULLUP_RISE BIT(8) +#define USBOTGSS_IRQMISC_DRVVBUS_FALL BIT(5) +#define USBOTGSS_IRQMISC_CHRGVBUS_FALL BIT(4) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL BIT(3) +#define USBOTGSS_IRQMISC_IDPULLUP_FALL BIT(0) /* UTMI_OTG_STATUS REGISTER */ -#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS (1 << 5) -#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS (1 << 4) -#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS (1 << 3) -#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP (1 << 0) +#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS BIT(5) +#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS BIT(4) +#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS BIT(3) +#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP BIT(0) /* UTMI_OTG_CTRL REGISTER */ -#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE (1 << 31) -#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT (1 << 9) -#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE (1 << 8) -#define USBOTGSS_UTMI_OTG_CTRL_IDDIG (1 << 4) -#define USBOTGSS_UTMI_OTG_CTRL_SESSEND (1 << 3) -#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID (1 << 2) -#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID (1 << 1) +#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE BIT(31) +#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT BIT(9) +#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE BIT(8) +#define USBOTGSS_UTMI_OTG_CTRL_IDDIG BIT(4) +#define USBOTGSS_UTMI_OTG_CTRL_SESSEND BIT(3) +#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID BIT(2) +#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID BIT(1) struct dwc3_omap { struct device *dev; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index e689cede9b0e..a78c78e7a8c3 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -39,14 +39,13 @@ static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep); static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, struct dwc3_ep *dep, struct dwc3_request *req); -static void dwc3_ep0_prepare_one_trb(struct dwc3 *dwc, u8 epnum, +static void dwc3_ep0_prepare_one_trb(struct dwc3_ep *dep, dma_addr_t buf_dma, u32 len, u32 type, bool chain) { struct dwc3_trb *trb; - struct dwc3_ep *dep; - - dep = dwc->eps[epnum]; + struct dwc3 *dwc; + dwc = dep->dwc; trb = &dwc->ep0_trb[dep->trb_enqueue]; if (chain) @@ -69,16 +68,17 @@ static void dwc3_ep0_prepare_one_trb(struct dwc3 *dwc, u8 epnum, trace_dwc3_prepare_trb(dep, trb); } -static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum) +static int dwc3_ep0_start_trans(struct dwc3_ep *dep) { struct dwc3_gadget_ep_cmd_params params; - struct dwc3_ep *dep; + struct dwc3 *dwc; int ret; - dep = dwc->eps[epnum]; if (dep->flags & DWC3_EP_BUSY) return 0; + dwc = dep->dwc; + memset(¶ms, 0, sizeof(params)); params.param0 = upper_32_bits(dwc->ep0_trb_addr); params.param1 = lower_32_bits(dwc->ep0_trb_addr); @@ -279,13 +279,15 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) void dwc3_ep0_out_start(struct dwc3 *dwc) { + struct dwc3_ep *dep; int ret; complete(&dwc->ep0_in_setup); - dwc3_ep0_prepare_one_trb(dwc, 0, dwc->ctrl_req_addr, 8, + dep = dwc->eps[0]; + dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 8, DWC3_TRBCTL_CONTROL_SETUP, false); - ret = dwc3_ep0_start_trans(dwc, 0); + ret = dwc3_ep0_start_trans(dep); WARN_ON(ret < 0); } @@ -794,7 +796,7 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { - struct usb_ctrlrequest *ctrl = dwc->ctrl_req; + struct usb_ctrlrequest *ctrl = (void *) dwc->ep0_trb; int ret = -EINVAL; u32 len; @@ -834,7 +836,6 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, struct usb_request *ur; struct dwc3_trb *trb; struct dwc3_ep *ep0; - unsigned transfer_size = 0; unsigned maxp; unsigned remaining_ur_length; void *buf; @@ -847,9 +848,7 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, ep0 = dwc->eps[0]; dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; - trb = dwc->ep0_trb; - trace_dwc3_complete_trb(ep0, trb); r = next_request(&ep0->pending_list); @@ -870,58 +869,23 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, remaining_ur_length = ur->length; length = trb->size & DWC3_TRB_SIZE_MASK; - maxp = ep0->endpoint.maxpacket; - - if (dwc->ep0_bounced) { - /* - * Handle the first TRB before handling the bounce buffer if - * the request length is greater than the bounce buffer size - */ - if (ur->length > DWC3_EP0_BOUNCE_SIZE) { - transfer_size = ALIGN(ur->length - maxp, maxp); - transferred = transfer_size - length; - buf = (u8 *)buf + transferred; - ur->actual += transferred; - remaining_ur_length -= transferred; - - trb++; - length = trb->size & DWC3_TRB_SIZE_MASK; - - ep0->trb_enqueue = 0; - } - - transfer_size = roundup((ur->length - transfer_size), - maxp); - - transferred = min_t(u32, remaining_ur_length, - transfer_size - length); - memcpy(buf, dwc->ep0_bounce, transferred); - } else { - transferred = ur->length - length; - } - + transferred = ur->length - length; ur->actual += transferred; - if ((epnum & 1) && ur->actual < ur->length) { - /* for some reason we did not get everything out */ + if ((IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) && + ur->length && ur->zero) || dwc->ep0_bounced) { + trb++; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + trace_dwc3_complete_trb(ep0, trb); + ep0->trb_enqueue = 0; + dwc->ep0_bounced = false; + } + if ((epnum & 1) && ur->actual < ur->length) dwc3_ep0_stall_and_restart(dwc); - } else { + else dwc3_gadget_giveback(ep0, r, 0); - - if (IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) && - ur->length && ur->zero) { - int ret; - - dwc->ep0_next_event = DWC3_EP0_COMPLETE; - - dwc3_ep0_prepare_one_trb(dwc, epnum, dwc->ctrl_req_addr, - 0, DWC3_TRBCTL_CONTROL_DATA, false); - ret = dwc3_ep0_start_trans(dwc, epnum); - WARN_ON(ret < 0); - } - } } static void dwc3_ep0_complete_status(struct dwc3 *dwc, @@ -997,14 +961,13 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, req->direction = !!dep->number; if (req->request.length == 0) { - dwc3_ep0_prepare_one_trb(dwc, dep->number, - dwc->ctrl_req_addr, 0, + dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0, DWC3_TRBCTL_CONTROL_DATA, false); - ret = dwc3_ep0_start_trans(dwc, dep->number); + ret = dwc3_ep0_start_trans(dep); } else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) && (dep->number == 0)) { - u32 transfer_size = 0; u32 maxpacket; + u32 rem; ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, dep->number); @@ -1012,36 +975,55 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, return; maxpacket = dep->endpoint.maxpacket; + rem = req->request.length % maxpacket; + dwc->ep0_bounced = true; - if (req->request.length > DWC3_EP0_BOUNCE_SIZE) { - transfer_size = ALIGN(req->request.length - maxpacket, - maxpacket); - dwc3_ep0_prepare_one_trb(dwc, dep->number, - req->request.dma, - transfer_size, - DWC3_TRBCTL_CONTROL_DATA, - true); - } - - transfer_size = roundup((req->request.length - transfer_size), - maxpacket); + /* prepare normal TRB */ + dwc3_ep0_prepare_one_trb(dep, req->request.dma, + req->request.length, + DWC3_TRBCTL_CONTROL_DATA, + true); + + /* Now prepare one extra TRB to align transfer size */ + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, + maxpacket - rem, + DWC3_TRBCTL_CONTROL_DATA, + false); + ret = dwc3_ep0_start_trans(dep); + } else if (IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) && + req->request.length && req->request.zero) { + u32 maxpacket; + u32 rem; - dwc->ep0_bounced = true; + ret = usb_gadget_map_request_by_dev(dwc->sysdev, + &req->request, dep->number); + if (ret) + return; - dwc3_ep0_prepare_one_trb(dwc, dep->number, - dwc->ep0_bounce_addr, transfer_size, - DWC3_TRBCTL_CONTROL_DATA, false); - ret = dwc3_ep0_start_trans(dwc, dep->number); + maxpacket = dep->endpoint.maxpacket; + rem = req->request.length % maxpacket; + + /* prepare normal TRB */ + dwc3_ep0_prepare_one_trb(dep, req->request.dma, + req->request.length, + DWC3_TRBCTL_CONTROL_DATA, + true); + + /* Now prepare one extra TRB to align transfer size */ + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, + 0, DWC3_TRBCTL_CONTROL_DATA, + false); + ret = dwc3_ep0_start_trans(dep); } else { ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, dep->number); if (ret) return; - dwc3_ep0_prepare_one_trb(dwc, dep->number, req->request.dma, + dwc3_ep0_prepare_one_trb(dep, req->request.dma, req->request.length, DWC3_TRBCTL_CONTROL_DATA, false); - ret = dwc3_ep0_start_trans(dwc, dep->number); + ret = dwc3_ep0_start_trans(dep); } WARN_ON(ret < 0); @@ -1055,9 +1037,8 @@ static int dwc3_ep0_start_control_status(struct dwc3_ep *dep) type = dwc->three_stage_setup ? DWC3_TRBCTL_CONTROL_STATUS3 : DWC3_TRBCTL_CONTROL_STATUS2; - dwc3_ep0_prepare_one_trb(dwc, dep->number, - dwc->ctrl_req_addr, 0, type, false); - return dwc3_ep0_start_trans(dwc, dep->number); + dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0, type, false); + return dwc3_ep0_start_trans(dep); } static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 79e7a3480d51..6f6f0b3be3ad 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -171,7 +171,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status) { struct dwc3 *dwc = dep->dwc; - unsigned int unmap_after_complete = false; req->started = false; list_del(&req->list); @@ -181,19 +180,8 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, if (req->request.status == -EINPROGRESS) req->request.status = status; - /* - * NOTICE we don't want to unmap before calling ->complete() if we're - * dealing with a bounced ep0 request. If we unmap it here, we would end - * up overwritting the contents of req->buf and this could confuse the - * gadget driver. - */ - if (dwc->ep0_bounced && dep->number <= 1) { - dwc->ep0_bounced = false; - unmap_after_complete = true; - } else { - usb_gadget_unmap_request_by_dev(dwc->sysdev, - &req->request, req->direction); - } + usb_gadget_unmap_request_by_dev(dwc->sysdev, + &req->request, req->direction); trace_dwc3_gadget_giveback(req); @@ -201,10 +189,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, usb_gadget_giveback_request(&dep->endpoint, &req->request); spin_lock(&dwc->lock); - if (unmap_after_complete) - usb_gadget_unmap_request_by_dev(dwc->sysdev, - &req->request, req->direction); - if (dep->number > 1) pm_runtime_put(dwc->dev); } @@ -1060,6 +1044,22 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep, false, 0, req->request.stream_id, req->request.short_not_ok, req->request.no_interrupt); + } else if (req->request.zero && req->request.length && + (IS_ALIGNED(req->request.length,dep->endpoint.maxpacket))) { + struct dwc3 *dwc = dep->dwc; + struct dwc3_trb *trb; + + req->zero = true; + + /* prepare normal TRB */ + dwc3_prepare_one_trb(dep, req, true, 0); + + /* Now prepare one extra TRB to handle ZLP */ + trb = &dep->trb_pool[dep->trb_enqueue]; + __dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, 0, + false, 0, req->request.stream_id, + req->request.short_not_ok, + req->request.no_interrupt); } else { dwc3_prepare_one_trb(dep, req, false, 0); } @@ -1184,8 +1184,11 @@ static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, return; } - /* 4 micro frames in the future */ - uf = cur_uf + dep->interval * 4; + /* + * Schedule the first trb for one interval in the future or at + * least 4 microframes. + */ + uf = cur_uf + max_t(u32, 4, dep->interval); __dwc3_gadget_kick_transfer(dep, uf); } @@ -1272,31 +1275,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) return ret; } -static void __dwc3_gadget_ep_zlp_complete(struct usb_ep *ep, - struct usb_request *request) -{ - dwc3_gadget_ep_free_request(ep, request); -} - -static int __dwc3_gadget_ep_queue_zlp(struct dwc3 *dwc, struct dwc3_ep *dep) -{ - struct dwc3_request *req; - struct usb_request *request; - struct usb_ep *ep = &dep->endpoint; - - request = dwc3_gadget_ep_alloc_request(ep, GFP_ATOMIC); - if (!request) - return -ENOMEM; - - request->length = 0; - request->buf = dwc->zlp_buf; - request->complete = __dwc3_gadget_ep_zlp_complete; - - req = to_dwc3_request(request); - - return __dwc3_gadget_ep_queue(dep, req); -} - static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, gfp_t gfp_flags) { @@ -1310,17 +1288,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_queue(dep, req); - - /* - * Okay, here's the thing, if gadget driver has requested for a ZLP by - * setting request->zero, instead of doing magic, we will just queue an - * extra usb_request ourselves so that it gets handled the same way as - * any other request. - */ - if (ret == 0 && request->zero && request->length && - (request->length % ep->maxpacket == 0)) - ret = __dwc3_gadget_ep_queue_zlp(dwc, dep); - spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -1400,7 +1367,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, dwc3_ep_inc_deq(dep); } - if (r->unaligned) { + if (r->unaligned || r->zero) { trb = r->trb + r->num_pending_sgs + 1; trb->ctrl &= ~DWC3_TRB_CTRL_HWO; dwc3_ep_inc_deq(dep); @@ -1411,7 +1378,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, trb->ctrl &= ~DWC3_TRB_CTRL_HWO; dwc3_ep_inc_deq(dep); - if (r->unaligned) { + if (r->unaligned || r->zero) { trb = r->trb + 1; trb->ctrl &= ~DWC3_TRB_CTRL_HWO; dwc3_ep_inc_deq(dep); @@ -2006,14 +1973,15 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { /* -------------------------------------------------------------------------- */ -static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, - u8 num, u32 direction) +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) { struct dwc3_ep *dep; - u8 i; + u8 epnum; + + INIT_LIST_HEAD(&dwc->gadget.ep_list); - for (i = 0; i < num; i++) { - u8 epnum = (i << 1) | (direction ? 1 : 0); + for (epnum = 0; epnum < num; epnum++) { + bool direction = epnum & 1; dep = kzalloc(sizeof(*dep), GFP_KERNEL); if (!dep) @@ -2021,12 +1989,12 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, dep->dwc = dwc; dep->number = epnum; - dep->direction = !!direction; + dep->direction = direction; dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); dwc->eps[epnum] = dep; snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1, - (epnum & 1) ? "in" : "out"); + direction ? "in" : "out"); dep->endpoint.name = dep->name; @@ -2053,7 +2021,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, /* MDWIDTH is represented in bits, we need it in bytes */ mdwidth /= 8; - size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(i)); + size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1)); size = DWC3_GTXFIFOSIZ_TXFDEF(size); /* FIFO Depth is in MDWDITH bytes. Multiply */ @@ -2103,7 +2071,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, dep->endpoint.caps.type_int = true; } - dep->endpoint.caps.dir_in = !!direction; + dep->endpoint.caps.dir_in = direction; dep->endpoint.caps.dir_out = !direction; INIT_LIST_HEAD(&dep->pending_list); @@ -2113,27 +2081,6 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, return 0; } -static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) -{ - int ret; - - INIT_LIST_HEAD(&dwc->gadget.ep_list); - - ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); - if (ret < 0) { - dev_err(dwc->dev, "failed to initialize OUT endpoints\n"); - return ret; - } - - ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); - if (ret < 0) { - dev_err(dwc->dev, "failed to initialize IN endpoints\n"); - return ret; - } - - return 0; -} - static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) { struct dwc3_ep *dep; @@ -2197,7 +2144,7 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, * with one TRB pending in the ring. We need to manually clear HWO bit * from that TRB. */ - if (req->unaligned && (trb->ctrl & DWC3_TRB_CTRL_HWO)) { + if ((req->zero || req->unaligned) && (trb->ctrl & DWC3_TRB_CTRL_HWO)) { trb->ctrl &= ~DWC3_TRB_CTRL_HWO; return 1; } @@ -2291,11 +2238,12 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, event, status, chain); } - if (req->unaligned) { + if (req->unaligned || req->zero) { trb = &dep->trb_pool[dep->trb_dequeue]; ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, event, status, false); req->unaligned = false; + req->zero = false; } req->request.actual = length - req->remaining; @@ -3161,49 +3109,26 @@ int dwc3_gadget_init(struct dwc3 *dwc) dwc->irq_gadget = irq; - dwc->ctrl_req = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req), - &dwc->ctrl_req_addr, GFP_KERNEL); - if (!dwc->ctrl_req) { - dev_err(dwc->dev, "failed to allocate ctrl request\n"); - ret = -ENOMEM; - goto err0; - } - dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, &dwc->ep0_trb_addr, GFP_KERNEL); if (!dwc->ep0_trb) { dev_err(dwc->dev, "failed to allocate ep0 trb\n"); ret = -ENOMEM; - goto err1; + goto err0; } - dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL); + dwc->setup_buf = kzalloc(DWC3_EP0_SETUP_SIZE, GFP_KERNEL); if (!dwc->setup_buf) { ret = -ENOMEM; - goto err2; - } - - dwc->ep0_bounce = dma_alloc_coherent(dwc->sysdev, - DWC3_EP0_BOUNCE_SIZE, &dwc->ep0_bounce_addr, - GFP_KERNEL); - if (!dwc->ep0_bounce) { - dev_err(dwc->dev, "failed to allocate ep0 bounce buffer\n"); - ret = -ENOMEM; - goto err3; - } - - dwc->zlp_buf = kzalloc(DWC3_ZLP_BUF_SIZE, GFP_KERNEL); - if (!dwc->zlp_buf) { - ret = -ENOMEM; - goto err4; + goto err1; } dwc->bounce = dma_alloc_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, &dwc->bounce_addr, GFP_KERNEL); if (!dwc->bounce) { ret = -ENOMEM; - goto err5; + goto err2; } init_completion(&dwc->ep0_in_setup); @@ -3241,39 +3166,31 @@ int dwc3_gadget_init(struct dwc3 *dwc) * sure we're starting from a well known location. */ - ret = dwc3_gadget_init_endpoints(dwc); + ret = dwc3_gadget_init_endpoints(dwc, dwc->num_eps); if (ret) - goto err6; + goto err3; ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); if (ret) { dev_err(dwc->dev, "failed to register udc\n"); - goto err6; + goto err4; } return 0; -err6: - dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, - dwc->bounce_addr); - -err5: - kfree(dwc->zlp_buf); err4: dwc3_gadget_free_endpoints(dwc); - dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE, - dwc->ep0_bounce, dwc->ep0_bounce_addr); err3: - kfree(dwc->setup_buf); + dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, + dwc->bounce_addr); err2: - dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, - dwc->ep0_trb, dwc->ep0_trb_addr); + kfree(dwc->setup_buf); err1: - dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req), - dwc->ctrl_req, dwc->ctrl_req_addr); + dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, + dwc->ep0_trb, dwc->ep0_trb_addr); err0: return ret; @@ -3284,22 +3201,12 @@ err0: void dwc3_gadget_exit(struct dwc3 *dwc) { usb_del_gadget_udc(&dwc->gadget); - dwc3_gadget_free_endpoints(dwc); - dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, - dwc->bounce_addr); - dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE, - dwc->ep0_bounce, dwc->ep0_bounce_addr); - + dwc->bounce_addr); kfree(dwc->setup_buf); - kfree(dwc->zlp_buf); - dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, - dwc->ep0_trb, dwc->ep0_trb_addr); - - dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req), - dwc->ctrl_req, dwc->ctrl_req_addr); + dwc->ep0_trb, dwc->ep0_trb_addr); } int dwc3_gadget_suspend(struct dwc3 *dwc) diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 265e223ab645..e4602d0e515b 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -29,16 +29,16 @@ struct dwc3; /* DEPCFG parameter 1 */ #define DWC3_DEPCFG_INT_NUM(n) (((n) & 0x1f) << 0) -#define DWC3_DEPCFG_XFER_COMPLETE_EN (1 << 8) -#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN (1 << 9) -#define DWC3_DEPCFG_XFER_NOT_READY_EN (1 << 10) -#define DWC3_DEPCFG_FIFO_ERROR_EN (1 << 11) -#define DWC3_DEPCFG_STREAM_EVENT_EN (1 << 13) +#define DWC3_DEPCFG_XFER_COMPLETE_EN BIT(8) +#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN BIT(9) +#define DWC3_DEPCFG_XFER_NOT_READY_EN BIT(10) +#define DWC3_DEPCFG_FIFO_ERROR_EN BIT(11) +#define DWC3_DEPCFG_STREAM_EVENT_EN BIT(12) #define DWC3_DEPCFG_BINTERVAL_M1(n) (((n) & 0xff) << 16) -#define DWC3_DEPCFG_STREAM_CAPABLE (1 << 24) +#define DWC3_DEPCFG_STREAM_CAPABLE BIT(24) #define DWC3_DEPCFG_EP_NUMBER(n) (((n) & 0x1f) << 25) -#define DWC3_DEPCFG_BULK_BASED (1 << 30) -#define DWC3_DEPCFG_FIFO_BASED (1 << 31) +#define DWC3_DEPCFG_BULK_BASED BIT(30) +#define DWC3_DEPCFG_FIFO_BASED BIT(31) /* DEPCFG parameter 0 */ #define DWC3_DEPCFG_EP_TYPE(n) (((n) & 0x3) << 1) @@ -47,10 +47,10 @@ struct dwc3; #define DWC3_DEPCFG_BURST_SIZE(n) (((n) & 0xf) << 22) #define DWC3_DEPCFG_DATA_SEQ_NUM(n) ((n) << 26) /* This applies for core versions earlier than 1.94a */ -#define DWC3_DEPCFG_IGN_SEQ_NUM (1 << 31) +#define DWC3_DEPCFG_IGN_SEQ_NUM BIT(31) /* These apply for core versions 1.94a and later */ #define DWC3_DEPCFG_ACTION_INIT (0 << 30) -#define DWC3_DEPCFG_ACTION_RESTORE (1 << 30) +#define DWC3_DEPCFG_ACTION_RESTORE BIT(30) #define DWC3_DEPCFG_ACTION_MODIFY (2 << 30) /* DEPXFERCFG parameter 0 */ diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 2b124f94d858..f1bd444d22a3 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -27,31 +27,6 @@ #include "core.h" #include "debug.h" -DECLARE_EVENT_CLASS(dwc3_log_msg, - TP_PROTO(struct va_format *vaf), - TP_ARGS(vaf), - TP_STRUCT__entry(__dynamic_array(char, msg, DWC3_MSG_MAX)), - TP_fast_assign( - vsnprintf(__get_str(msg), DWC3_MSG_MAX, vaf->fmt, *vaf->va); - ), - TP_printk("%s", __get_str(msg)) -); - -DEFINE_EVENT(dwc3_log_msg, dwc3_gadget, - TP_PROTO(struct va_format *vaf), - TP_ARGS(vaf) -); - -DEFINE_EVENT(dwc3_log_msg, dwc3_core, - TP_PROTO(struct va_format *vaf), - TP_ARGS(vaf) -); - -DEFINE_EVENT(dwc3_log_msg, dwc3_ep0, - TP_PROTO(struct va_format *vaf), - TP_ARGS(vaf) -); - DECLARE_EVENT_CLASS(dwc3_log_io, TP_PROTO(void *base, u32 offset, u32 value), TP_ARGS(base, offset, value), @@ -198,7 +173,7 @@ DECLARE_EVENT_CLASS(dwc3_log_generic_cmd, __entry->param = param; __entry->status = status; ), - TP_printk("cmd '%s' [%d] param %08x --> status: %s", + TP_printk("cmd '%s' [%x] param %08x --> status: %s", dwc3_gadget_generic_cmd_string(__entry->cmd), __entry->cmd, __entry->param, dwc3_gadget_generic_cmd_status_string(__entry->status) @@ -298,36 +273,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's', __entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's', __entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c', - ({char *s; - switch (__entry->ctrl & 0x3f0) { - case DWC3_TRBCTL_NORMAL: - s = "normal"; - break; - case DWC3_TRBCTL_CONTROL_SETUP: - s = "setup"; - break; - case DWC3_TRBCTL_CONTROL_STATUS2: - s = "status2"; - break; - case DWC3_TRBCTL_CONTROL_STATUS3: - s = "status3"; - break; - case DWC3_TRBCTL_CONTROL_DATA: - s = "data"; - break; - case DWC3_TRBCTL_ISOCHRONOUS_FIRST: - s = "isoc-first"; - break; - case DWC3_TRBCTL_ISOCHRONOUS: - s = "isoc"; - break; - case DWC3_TRBCTL_LINK_TRB: - s = "link"; - break; - default: - s = "UNKNOWN"; - break; - } s; }) + dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl)) ) ); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 8ad203296079..c164d6b788c3 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -212,7 +212,7 @@ config USB_F_TCM # this first set of drivers all depend on bulk-capable hardware. config USB_CONFIGFS - tristate "USB functions configurable through configfs" + tristate "USB Gadget functions configurable through configfs" select USB_LIBCOMPOSITE help A Linux USB "gadget" can be set up through configfs. @@ -458,8 +458,9 @@ config USB_CONFIGFS_F_TCM UAS utilizes the USB 3.0 feature called streams support. choice - tristate "USB Gadget Drivers" + tristate "USB Gadget precomposed configurations" default USB_ETH + optional help A Linux "Gadget Driver" talks to the USB Peripheral Controller driver through the abstract "gadget" API. Some other operating @@ -476,6 +477,12 @@ choice not be able work with that controller, or might need to implement a less common variant of a device class protocol. + The available choices each represent a single precomposed USB + gadget configuration. In the device model, each option contains + both the device instantiation as a child for a USB gadget + controller, and the relevant drivers for each function declared + by the device. + source "drivers/usb/gadget/legacy/Kconfig" endchoice diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index a0085571824d..71dd27c0d7f2 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -246,7 +246,6 @@ EXPORT_SYMBOL_GPL(ffs_lock); static struct ffs_dev *_ffs_find_dev(const char *name); static struct ffs_dev *_ffs_alloc_dev(void); -static int _ffs_name_dev(struct ffs_dev *dev, const char *name); static void _ffs_free_dev(struct ffs_dev *dev); static void *ffs_acquire_dev(const char *dev_name); static void ffs_release_dev(struct ffs_data *ffs_data); @@ -1571,14 +1570,14 @@ static void ffs_data_get(struct ffs_data *ffs) { ENTER(); - atomic_inc(&ffs->ref); + refcount_inc(&ffs->ref); } static void ffs_data_opened(struct ffs_data *ffs) { ENTER(); - atomic_inc(&ffs->ref); + refcount_inc(&ffs->ref); if (atomic_add_return(1, &ffs->opened) == 1 && ffs->state == FFS_DEACTIVATED) { ffs->state = FFS_CLOSING; @@ -1590,7 +1589,7 @@ static void ffs_data_put(struct ffs_data *ffs) { ENTER(); - if (unlikely(atomic_dec_and_test(&ffs->ref))) { + if (unlikely(refcount_dec_and_test(&ffs->ref))) { pr_info("%s(): freeing\n", __func__); ffs_data_clear(ffs); BUG_ON(waitqueue_active(&ffs->ev.waitq) || @@ -1635,7 +1634,7 @@ static struct ffs_data *ffs_data_new(void) ENTER(); - atomic_set(&ffs->ref, 1); + refcount_set(&ffs->ref, 1); atomic_set(&ffs->opened, 0); ffs->state = FFS_READ_DESCRIPTORS; mutex_init(&ffs->mutex); @@ -3302,9 +3301,10 @@ static struct ffs_dev *_ffs_do_find_dev(const char *name) { struct ffs_dev *dev; + if (!name) + return NULL; + list_for_each_entry(dev, &ffs_devices, entry) { - if (!dev->name || !name) - continue; if (strcmp(dev->name, name) == 0) return dev; } @@ -3380,42 +3380,11 @@ static void ffs_free_inst(struct usb_function_instance *f) kfree(opts); } -#define MAX_INST_NAME_LEN 40 - static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name) { - struct f_fs_opts *opts; - char *ptr; - const char *tmp; - int name_len, ret; - - name_len = strlen(name) + 1; - if (name_len > MAX_INST_NAME_LEN) + if (strlen(name) >= FIELD_SIZEOF(struct ffs_dev, name)) return -ENAMETOOLONG; - - ptr = kstrndup(name, name_len, GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - opts = to_f_fs_opts(fi); - tmp = NULL; - - ffs_dev_lock(); - - tmp = opts->dev->name_allocated ? opts->dev->name : NULL; - ret = _ffs_name_dev(opts->dev, ptr); - if (ret) { - kfree(ptr); - ffs_dev_unlock(); - return ret; - } - opts->dev->name_allocated = true; - - ffs_dev_unlock(); - - kfree(tmp); - - return 0; + return ffs_name_dev(to_f_fs_opts(fi)->dev, name); } static struct usb_function_instance *ffs_alloc_inst(void) @@ -3545,32 +3514,19 @@ static struct ffs_dev *_ffs_alloc_dev(void) return dev; } -/* - * ffs_lock must be taken by the caller of this function - * The caller is responsible for "name" being available whenever f_fs needs it - */ -static int _ffs_name_dev(struct ffs_dev *dev, const char *name) +int ffs_name_dev(struct ffs_dev *dev, const char *name) { struct ffs_dev *existing; + int ret = 0; - existing = _ffs_do_find_dev(name); - if (existing) - return -EBUSY; - - dev->name = name; - - return 0; -} + ffs_dev_lock(); -/* - * The caller is responsible for "name" being available whenever f_fs needs it - */ -int ffs_name_dev(struct ffs_dev *dev, const char *name) -{ - int ret; + existing = _ffs_do_find_dev(name); + if (!existing) + strlcpy(dev->name, name, ARRAY_SIZE(dev->name)); + else if (existing != dev) + ret = -EBUSY; - ffs_dev_lock(); - ret = _ffs_name_dev(dev, name); ffs_dev_unlock(); return ret; @@ -3600,8 +3556,6 @@ EXPORT_SYMBOL_GPL(ffs_single_dev); static void _ffs_free_dev(struct ffs_dev *dev) { list_del(&dev->entry); - if (dev->name_allocated) - kfree(dev->name); /* Clear the private_data pointer to stop incorrect dev access */ if (dev->ffs_data) diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index c3cab77181d4..a8b40d07e927 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -178,6 +178,7 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req); static int rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) { + struct usb_gadget *g = dev->gadget; struct sk_buff *skb; int retval = -ENOMEM; size_t size = 0; @@ -209,8 +210,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) */ size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA; size += dev->port_usb->header_len; - size += out->maxpacket - 1; - size -= size % out->maxpacket; + + if (g->quirk_ep_out_aligned_size) { + size += out->maxpacket - 1; + size -= size % out->maxpacket; + } if (dev->port_usb->is_fixed) size = max_t(size_t, size, dev->port_usb->fixed_out_len); @@ -401,13 +405,12 @@ done: static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) { struct usb_request *req; + struct usb_request *tmp; unsigned long flags; /* fill unused rxq slots with some skb */ spin_lock_irqsave(&dev->req_lock, flags); - while (!list_empty(&dev->rx_reqs)) { - req = container_of(dev->rx_reqs.next, - struct usb_request, list); + list_for_each_entry_safe(req, tmp, &dev->rx_reqs, list) { list_del_init(&req->list); spin_unlock_irqrestore(&dev->req_lock, flags); @@ -527,7 +530,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, return NETDEV_TX_BUSY; } - req = container_of(dev->tx_reqs.next, struct usb_request, list); + req = list_first_entry(&dev->tx_reqs, struct usb_request, list); list_del(&req->list); /* temporarily stop TX queue when the freelist empties */ @@ -1122,6 +1125,7 @@ void gether_disconnect(struct gether *link) { struct eth_dev *dev = link->ioport; struct usb_request *req; + struct usb_request *tmp; WARN_ON(!dev); if (!dev) @@ -1138,9 +1142,7 @@ void gether_disconnect(struct gether *link) */ usb_ep_disable(link->in_ep); spin_lock(&dev->req_lock); - while (!list_empty(&dev->tx_reqs)) { - req = container_of(dev->tx_reqs.next, - struct usb_request, list); + list_for_each_entry_safe(req, tmp, &dev->tx_reqs, list) { list_del(&req->list); spin_unlock(&dev->req_lock); @@ -1152,9 +1154,7 @@ void gether_disconnect(struct gether *link) usb_ep_disable(link->out_ep); spin_lock(&dev->req_lock); - while (!list_empty(&dev->rx_reqs)) { - req = container_of(dev->rx_reqs.next, - struct usb_request, list); + list_for_each_entry_safe(req, tmp, &dev->rx_reqs, list) { list_del(&req->list); spin_unlock(&dev->req_lock); diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 4b6969451cdc..4378cc2fcac3 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -20,6 +20,7 @@ #include <linux/list.h> #include <linux/mutex.h> #include <linux/workqueue.h> +#include <linux/refcount.h> #ifdef VERBOSE_DEBUG #ifndef pr_vdebug @@ -39,15 +40,16 @@ struct f_fs_opts; struct ffs_dev { - const char *name; - bool name_allocated; - bool mounted; - bool desc_ready; - bool single; struct ffs_data *ffs_data; struct f_fs_opts *opts; struct list_head entry; + char name[41]; + + bool mounted; + bool desc_ready; + bool single; + int (*ffs_ready_callback)(struct ffs_data *ffs); void (*ffs_closed_callback)(struct ffs_data *ffs); void *(*ffs_acquire_dev_callback)(struct ffs_dev *dev); @@ -177,7 +179,7 @@ struct ffs_data { struct completion ep0req_completion; /* P: mutex */ /* reference counter */ - atomic_t ref; + refcount_t ref; /* how many files are opened (EP0 and others) */ atomic_t opened; diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 4e037d2a7a60..844cb738bafd 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -2125,7 +2125,7 @@ static struct configfs_item_operations uvc_item_ops = { .release = uvc_attr_release, }; -#define UVCG_OPTS_ATTR(cname, conv, str2u, uxx, vnoc, limit) \ +#define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ static ssize_t f_uvc_opts_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -2168,16 +2168,16 @@ end: \ return ret; \ } \ \ -UVC_ATTR(f_uvc_opts_, cname, aname) +UVC_ATTR(f_uvc_opts_, cname, cname) #define identity_conv(x) (x) -UVCG_OPTS_ATTR(streaming_interval, identity_conv, kstrtou8, u8, identity_conv, - 16); -UVCG_OPTS_ATTR(streaming_maxpacket, le16_to_cpu, kstrtou16, u16, le16_to_cpu, - 3072); -UVCG_OPTS_ATTR(streaming_maxburst, identity_conv, kstrtou8, u8, identity_conv, - 15); +UVCG_OPTS_ATTR(streaming_interval, streaming_interval, identity_conv, + kstrtou8, u8, identity_conv, 16); +UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, le16_to_cpu, + kstrtou16, u16, le16_to_cpu, 3072); +UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, identity_conv, + kstrtou8, u8, identity_conv, 15); #undef identity_conv diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index a2c916869293..b9ca0a26cbd9 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -27,6 +27,7 @@ #include <linux/mmu_context.h> #include <linux/aio.h> #include <linux/uio.h> +#include <linux/refcount.h> #include <linux/device.h> #include <linux/moduleparam.h> @@ -114,7 +115,7 @@ enum ep0_state { struct dev_data { spinlock_t lock; - atomic_t count; + refcount_t count; enum ep0_state state; /* P: lock */ struct usb_gadgetfs_event event [N_EVENT]; unsigned ev_next; @@ -150,12 +151,12 @@ struct dev_data { static inline void get_dev (struct dev_data *data) { - atomic_inc (&data->count); + refcount_inc (&data->count); } static void put_dev (struct dev_data *data) { - if (likely (!atomic_dec_and_test (&data->count))) + if (likely (!refcount_dec_and_test (&data->count))) return; /* needs no more cleanup */ BUG_ON (waitqueue_active (&data->wait)); @@ -170,7 +171,7 @@ static struct dev_data *dev_new (void) if (!dev) return NULL; dev->state = STATE_DEV_DISABLED; - atomic_set (&dev->count, 1); + refcount_set (&dev->count, 1); spin_lock_init (&dev->lock); INIT_LIST_HEAD (&dev->epfiles); init_waitqueue_head (&dev->wait); @@ -190,7 +191,7 @@ enum ep_state { struct ep_data { struct mutex lock; enum ep_state state; - atomic_t count; + refcount_t count; struct dev_data *dev; /* must hold dev->lock before accessing ep or req */ struct usb_ep *ep; @@ -205,12 +206,12 @@ struct ep_data { static inline void get_ep (struct ep_data *data) { - atomic_inc (&data->count); + refcount_inc (&data->count); } static void put_ep (struct ep_data *data) { - if (likely (!atomic_dec_and_test (&data->count))) + if (likely (!refcount_dec_and_test (&data->count))) return; put_dev (data->dev); /* needs no more cleanup */ @@ -1561,7 +1562,7 @@ static int activate_ep_files (struct dev_data *dev) init_waitqueue_head (&data->wait); strncpy (data->name, ep->name, sizeof (data->name) - 1); - atomic_set (&data->count, 1); + refcount_set (&data->count, 1); data->dev = dev; get_dev (dev); diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 4b69f28a9af9..1c14c283cc47 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -62,8 +62,9 @@ config USB_ATMEL_USBA The fifo_mode parameter is used to select endpoint allocation mode. fifo_mode = 0 is used to let the driver autoconfigure the endpoints. - In this case 2 banks are allocated for isochronous endpoints and - only one bank is allocated for the rest of the endpoints. + In this case, for ep1 2 banks are allocated if it works in isochronous + mode and only 1 bank otherwise. For the rest of the endpoints + only 1 bank is allocated. fifo_mode = 1 is a generic maximum fifo size (1024 bytes) configuration allowing the usage of ep1 - ep6 @@ -191,6 +192,7 @@ config USB_RENESAS_USBHS_UDC config USB_RENESAS_USB3 tristate 'Renesas USB3.0 Peripheral controller' depends on ARCH_RENESAS || COMPILE_TEST + depends on EXTCON help Renesas USB3.0 Peripheral controller is a USB peripheral controller that supports super, high, and full speed USB 3.0 data transfers. @@ -253,6 +255,20 @@ config USB_MV_U3D MARVELL PXA2128 Processor series include a super speed USB3.0 device controller, which support super speed USB peripheral. +config USB_SNP_CORE + depends on USB_AMD5536UDC + tristate + help + This enables core driver support for Synopsys USB 2.0 Device + controller. + + This will be enabled when PCI or Platform driver for this UDC is + selected. Currently, this will be enabled by USB_SNP_UDC_PLAT or + USB_AMD5536UDC options. + + This IP is different to the High Speed OTG IP that can be enabled + by selecting USB_DWC2 or USB_DWC3 options. + # # Controllers available in both integrated and discrete versions # @@ -277,7 +293,8 @@ source "drivers/usb/gadget/udc/bdc/Kconfig" config USB_AMD5536UDC tristate "AMD5536 UDC" - depends on PCI + depends on USB_PCI + select USB_SNP_CORE help The AMD5536 UDC is part of the AMD Geode CS5536, an x86 southbridge. It is a USB Highspeed DMA capable USB device controller. Beside ep0 @@ -285,6 +302,9 @@ config USB_AMD5536UDC The UDC port supports OTG operation, and may be used as a host port if it's not being used to implement peripheral or OTG roles. + This UDC is based on Synopsys USB device controller IP and selects + CONFIG_USB_SNP_CORE option to build the core driver. + Say "y" to link the driver statically, or "m" to build a dynamically linked module called "amd5536udc" and force all gadget drivers to also be dynamically linked. @@ -327,7 +347,7 @@ config USB_NET2272_DMA config USB_NET2280 tristate "NetChip NET228x / PLX USB3x8x" - depends on PCI + depends on USB_PCI help NetChip 2280 / 2282 is a PCI based USB peripheral controller which supports both full and high speed USB 2.0 data transfers. @@ -352,7 +372,7 @@ config USB_NET2280 config USB_GOKU tristate "Toshiba TC86C001 'Goku-S'" - depends on PCI + depends on USB_PCI help The Toshiba TC86C001 is a PCI device which includes controllers for full speed USB devices, IDE, I2C, SIO, plus a USB host (OHCI). @@ -366,7 +386,7 @@ config USB_GOKU config USB_EG20T tristate "Intel QUARK X1000/EG20T PCH/LAPIS Semiconductor IOH(ML7213/ML7831) UDC" - depends on PCI + depends on USB_PCI help This is a USB device driver for EG20T PCH. EG20T PCH is the platform controller hub that is used in Intel's diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 98e74ed9f555..626e1f1c62da 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -10,7 +10,8 @@ obj-$(CONFIG_USB_GADGET) += udc-core.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2272) += net2272.o obj-$(CONFIG_USB_NET2280) += net2280.o -obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o +obj-$(CONFIG_USB_SNP_CORE) += amd5536udc.o +obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o diff --git a/drivers/usb/gadget/udc/amd5536udc.c b/drivers/usb/gadget/udc/amd5536udc.c index ea03ca7ae29a..4ecd2f20ea48 100644 --- a/drivers/usb/gadget/udc/amd5536udc.c +++ b/drivers/usb/gadget/udc/amd5536udc.c @@ -11,27 +11,15 @@ */ /* - * The AMD5536 UDC is part of the x86 southbridge AMD Geode CS5536. - * It is a USB Highspeed DMA capable USB device controller. Beside ep0 it - * provides 4 IN and 4 OUT endpoints (bulk or interrupt type). - * - * Make sure that UDC is assigned to port 4 by BIOS settings (port can also - * be used as host port) and UOC bits PAD_EN and APU are set (should be done - * by BIOS init). - * - * UDC DMA requires 32-bit aligned buffers so DMA with gadget ether does not - * work without updating NET_IP_ALIGN. Or PIO mode (module param "use_dma=0") - * can be used with gadget ether. + * This file does the core driver implementation for the UDC that is based + * on Synopsys device controller IP (different than HS OTG IP) that is either + * connected through PCI bus or integrated to SoC platforms. */ -/* debug control */ -/* #define UDC_VERBOSE */ - /* Driver strings */ -#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" +#define UDC_MOD_DESCRIPTION "Synopsys USB Device Controller" #define UDC_DRIVER_VERSION_STRING "01.00.0206" -/* system */ #include <linux/module.h> #include <linux/pci.h> #include <linux/kernel.h> @@ -46,23 +34,12 @@ #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/dmapool.h> -#include <linux/moduleparam.h> -#include <linux/device.h> -#include <linux/io.h> -#include <linux/irq.h> #include <linux/prefetch.h> - +#include <linux/moduleparam.h> #include <asm/byteorder.h> #include <asm/unaligned.h> - -/* gadget stack */ -#include <linux/usb/ch9.h> -#include <linux/usb/gadget.h> - -/* udc specific */ #include "amd5536udc.h" - static void udc_tasklet_disconnect(unsigned long); static void empty_req_queue(struct udc_ep *); static void udc_setup_endpoints(struct udc *dev); @@ -72,7 +49,7 @@ static void udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq); /* description */ static const char mod_desc[] = UDC_MOD_DESCRIPTION; -static const char name[] = "amd5536udc"; +static const char name[] = "udc"; /* structure to hold endpoint function pointers */ static const struct usb_ep_ops udc_ep_ops; @@ -208,30 +185,11 @@ static const struct { #undef EP_INFO }; -/* DMA usage flag */ -static bool use_dma = 1; -/* packet per buffer dma */ -static bool use_dma_ppb = 1; -/* with per descr. update */ -static bool use_dma_ppb_du; /* buffer fill mode */ static int use_dma_bufferfill_mode; -/* full speed only mode */ -static bool use_fullspeed; /* tx buffer size for high speed */ static unsigned long hs_tx_buf = UDC_EPIN_BUFF_SIZE; -/* module parameters */ -module_param(use_dma, bool, S_IRUGO); -MODULE_PARM_DESC(use_dma, "true for DMA"); -module_param(use_dma_ppb, bool, S_IRUGO); -MODULE_PARM_DESC(use_dma_ppb, "true for DMA in packet per buffer mode"); -module_param(use_dma_ppb_du, bool, S_IRUGO); -MODULE_PARM_DESC(use_dma_ppb_du, - "true for DMA in packet per buffer mode with descriptor update"); -module_param(use_fullspeed, bool, S_IRUGO); -MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); - /*---------------------------------------------------------------------------*/ /* Prints UDC device registers and endpoint irq registers */ static void print_regs(struct udc *dev) @@ -267,7 +225,7 @@ static void print_regs(struct udc *dev) } /* Masks unused interrupts */ -static int udc_mask_unused_interrupts(struct udc *dev) +int udc_mask_unused_interrupts(struct udc *dev) { u32 tmp; @@ -287,6 +245,7 @@ static int udc_mask_unused_interrupts(struct udc *dev) return 0; } +EXPORT_SYMBOL_GPL(udc_mask_unused_interrupts); /* Enables endpoint 0 interrupts */ static int udc_enable_ep0_interrupts(struct udc *dev) @@ -306,7 +265,7 @@ static int udc_enable_ep0_interrupts(struct udc *dev) } /* Enables device interrupts for SET_INTF and SET_CONFIG */ -static int udc_enable_dev_setup_interrupts(struct udc *dev) +int udc_enable_dev_setup_interrupts(struct udc *dev) { u32 tmp; @@ -325,6 +284,7 @@ static int udc_enable_dev_setup_interrupts(struct udc *dev) return 0; } +EXPORT_SYMBOL_GPL(udc_enable_dev_setup_interrupts); /* Calculates fifo start of endpoint based on preceding endpoints */ static int udc_set_txfifo_addr(struct udc_ep *ep) @@ -583,7 +543,7 @@ udc_alloc_request(struct usb_ep *usbep, gfp_t gfp) if (ep->dma) { /* ep0 in requests are allocated from data pool here */ - dma_desc = pci_pool_alloc(ep->dev->data_requests, gfp, + dma_desc = dma_pool_alloc(ep->dev->data_requests, gfp, &req->td_phys); if (!dma_desc) { kfree(req); @@ -608,27 +568,23 @@ udc_alloc_request(struct usb_ep *usbep, gfp_t gfp) } /* frees pci pool descriptors of a DMA chain */ -static int udc_free_dma_chain(struct udc *dev, struct udc_request *req) +static void udc_free_dma_chain(struct udc *dev, struct udc_request *req) { - int ret_val = 0; - struct udc_data_dma *td; - struct udc_data_dma *td_last = NULL; + struct udc_data_dma *td = req->td_data; unsigned int i; + dma_addr_t addr_next = 0x00; + dma_addr_t addr = (dma_addr_t)td->next; + DBG(dev, "free chain req = %p\n", req); /* do not free first desc., will be done by free for request */ - td_last = req->td_data; - td = phys_to_virt(td_last->next); - for (i = 1; i < req->chain_len; i++) { - pci_pool_free(dev->data_requests, td, - (dma_addr_t)td_last->next); - td_last = td; - td = phys_to_virt(td_last->next); + td = phys_to_virt(addr); + addr_next = (dma_addr_t)td->next; + dma_pool_free(dev->data_requests, td, addr); + addr = addr_next; } - - return ret_val; } /* Frees request packet, called by gadget driver */ @@ -652,7 +608,7 @@ udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq) if (req->chain_len > 1) udc_free_dma_chain(ep->dev, req); - pci_pool_free(ep->dev->data_requests, req->td_data, + dma_pool_free(ep->dev->data_requests, req->td_data, req->td_phys); } kfree(req); @@ -847,7 +803,7 @@ static int udc_create_dma_chain( for (i = buf_len; i < bytes; i += buf_len) { /* create or determine next desc. */ if (create_new_chain) { - td = pci_pool_alloc(ep->dev->data_requests, + td = dma_pool_alloc(ep->dev->data_requests, gfp_flags, &dma_addr); if (!td) return -ENOMEM; @@ -1507,7 +1463,7 @@ static void make_ep_lists(struct udc *dev) } /* Inits UDC context */ -static void udc_basic_init(struct udc *dev) +void udc_basic_init(struct udc *dev) { u32 tmp; @@ -1543,6 +1499,7 @@ static void udc_basic_init(struct udc *dev) dev->data_ep_enabled = 0; dev->data_ep_queued = 0; } +EXPORT_SYMBOL_GPL(udc_basic_init); /* init registers at driver load time */ static int startup_registers(struct udc *dev) @@ -3031,7 +2988,7 @@ __acquires(dev->lock) } /* Interrupt Service Routine, see Linux Kernel Doc for parameters */ -static irqreturn_t udc_irq(int irq, void *pdev) +irqreturn_t udc_irq(int irq, void *pdev) { struct udc *dev = pdev; u32 reg; @@ -3083,16 +3040,18 @@ static irqreturn_t udc_irq(int irq, void *pdev) spin_unlock(&dev->lock); return ret_val; } +EXPORT_SYMBOL_GPL(udc_irq); /* Tears down device */ -static void gadget_release(struct device *pdev) +void gadget_release(struct device *pdev) { struct amd5536udc *dev = dev_get_drvdata(pdev); kfree(dev); } +EXPORT_SYMBOL_GPL(gadget_release); /* Cleanup on device remove */ -static void udc_remove(struct udc *dev) +void udc_remove(struct udc *dev) { /* remove timer */ stop_timer++; @@ -3108,9 +3067,10 @@ static void udc_remove(struct udc *dev) del_timer_sync(&udc_pollstall_timer); udc = NULL; } +EXPORT_SYMBOL_GPL(udc_remove); /* free all the dma pools */ -static void free_dma_pools(struct udc *dev) +void free_dma_pools(struct udc *dev) { dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td, dev->ep[UDC_EP0OUT_IX].td_phys); @@ -3119,35 +3079,10 @@ static void free_dma_pools(struct udc *dev) dma_pool_destroy(dev->stp_requests); dma_pool_destroy(dev->data_requests); } - -/* Reset all pci context */ -static void udc_pci_remove(struct pci_dev *pdev) -{ - struct udc *dev; - - dev = pci_get_drvdata(pdev); - - usb_del_gadget_udc(&udc->gadget); - /* gadget driver must not be registered */ - if (WARN_ON(dev->driver)) - return; - - /* dma pool cleanup */ - free_dma_pools(dev); - - /* reset controller */ - writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); - free_irq(pdev->irq, dev); - iounmap(dev->virt_addr); - release_mem_region(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - pci_disable_device(pdev); - - udc_remove(dev); -} +EXPORT_SYMBOL_GPL(free_dma_pools); /* create dma pools on init */ -static int init_dma_pools(struct udc *dev) +int init_dma_pools(struct udc *dev) { struct udc_stp_dma *td_stp; struct udc_data_dma *td_data; @@ -3210,9 +3145,10 @@ err_create_dma_pool: dev->data_requests = NULL; return retval; } +EXPORT_SYMBOL_GPL(init_dma_pools); /* general probe */ -static int udc_probe(struct udc *dev) +int udc_probe(struct udc *dev) { char tmp[128]; u32 reg; @@ -3276,137 +3212,7 @@ static int udc_probe(struct udc *dev) finished: return retval; } - -/* Called by pci bus driver to init pci context */ -static int udc_pci_probe( - struct pci_dev *pdev, - const struct pci_device_id *id -) -{ - struct udc *dev; - unsigned long resource; - unsigned long len; - int retval = 0; - - /* one udc only */ - if (udc) { - dev_dbg(&pdev->dev, "already probed\n"); - return -EBUSY; - } - - /* init */ - dev = kzalloc(sizeof(struct udc), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - /* pci setup */ - if (pci_enable_device(pdev) < 0) { - retval = -ENODEV; - goto err_pcidev; - } - - /* PCI resource allocation */ - resource = pci_resource_start(pdev, 0); - len = pci_resource_len(pdev, 0); - - if (!request_mem_region(resource, len, name)) { - dev_dbg(&pdev->dev, "pci device used already\n"); - retval = -EBUSY; - goto err_memreg; - } - - dev->virt_addr = ioremap_nocache(resource, len); - if (!dev->virt_addr) { - dev_dbg(&pdev->dev, "start address cannot be mapped\n"); - retval = -EFAULT; - goto err_ioremap; - } - - if (!pdev->irq) { - dev_err(&pdev->dev, "irq not set\n"); - retval = -ENODEV; - goto err_irq; - } - - spin_lock_init(&dev->lock); - /* udc csr registers base */ - dev->csr = dev->virt_addr + UDC_CSR_ADDR; - /* dev registers base */ - dev->regs = dev->virt_addr + UDC_DEVCFG_ADDR; - /* ep registers base */ - dev->ep_regs = dev->virt_addr + UDC_EPREGS_ADDR; - /* fifo's base */ - dev->rxfifo = (u32 __iomem *)(dev->virt_addr + UDC_RXFIFO_ADDR); - dev->txfifo = (u32 __iomem *)(dev->virt_addr + UDC_TXFIFO_ADDR); - - if (request_irq(pdev->irq, udc_irq, IRQF_SHARED, name, dev) != 0) { - dev_dbg(&pdev->dev, "request_irq(%d) fail\n", pdev->irq); - retval = -EBUSY; - goto err_irq; - } - - pci_set_drvdata(pdev, dev); - - /* chip revision for Hs AMD5536 */ - dev->chiprev = pdev->revision; - - pci_set_master(pdev); - pci_try_set_mwi(pdev); - - /* init dma pools */ - if (use_dma) { - retval = init_dma_pools(dev); - if (retval != 0) - goto err_dma; - } - - dev->phys_addr = resource; - dev->irq = pdev->irq; - dev->pdev = pdev; - - /* general probing */ - if (udc_probe(dev)) { - retval = -ENODEV; - goto err_probe; - } - return 0; - -err_probe: - if (use_dma) - free_dma_pools(dev); -err_dma: - free_irq(pdev->irq, dev); -err_irq: - iounmap(dev->virt_addr); -err_ioremap: - release_mem_region(resource, len); -err_memreg: - pci_disable_device(pdev); -err_pcidev: - kfree(dev); - return retval; -} - -/* PCI device parameters */ -static const struct pci_device_id pci_id[] = { - { - PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), - .class = PCI_CLASS_SERIAL_USB_DEVICE, - .class_mask = 0xffffffff, - }, - {}, -}; -MODULE_DEVICE_TABLE(pci, pci_id); - -/* PCI functions */ -static struct pci_driver udc_pci_driver = { - .name = (char *) name, - .id_table = pci_id, - .probe = udc_pci_probe, - .remove = udc_pci_remove, -}; - -module_pci_driver(udc_pci_driver); +EXPORT_SYMBOL_GPL(udc_probe); MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); MODULE_AUTHOR("Thomas Dahlmann"); diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index 4638d707f169..fae49bf3833e 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -13,6 +13,12 @@ #ifndef AMD5536UDC_H #define AMD5536UDC_H +/* debug control */ +/* #define UDC_VERBOSE */ + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + /* various constants */ #define UDC_RDE_TIMER_SECONDS 1 #define UDC_RDE_TIMER_DIV 10 @@ -545,8 +551,8 @@ struct udc { u32 __iomem *txfifo; /* DMA desc pools */ - struct pci_pool *data_requests; - struct pci_pool *stp_requests; + struct dma_pool *data_requests; + struct dma_pool *stp_requests; /* device data */ unsigned long phys_addr; @@ -567,6 +573,36 @@ union udc_setup_data { struct usb_ctrlrequest request; }; +/* Function declarations */ +int udc_enable_dev_setup_interrupts(struct udc *dev); +int udc_mask_unused_interrupts(struct udc *dev); +irqreturn_t udc_irq(int irq, void *pdev); +void gadget_release(struct device *pdev); +void udc_basic_init(struct udc *dev); +void free_dma_pools(struct udc *dev); +int init_dma_pools(struct udc *dev); +void udc_remove(struct udc *dev); +int udc_probe(struct udc *dev); + +/* DMA usage flag */ +static bool use_dma = 1; +/* packet per buffer dma */ +static bool use_dma_ppb = 1; +/* with per descr. update */ +static bool use_dma_ppb_du; +/* full speed only mode */ +static bool use_fullspeed; + +/* module parameters */ +module_param(use_dma, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma, "true for DMA"); +module_param(use_dma_ppb, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma_ppb, "true for DMA in packet per buffer mode"); +module_param(use_dma_ppb_du, bool, S_IRUGO); +MODULE_PARM_DESC(use_dma_ppb_du, + "true for DMA in packet per buffer mode with descriptor update"); +module_param(use_fullspeed, bool, S_IRUGO); +MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); /* *--------------------------------------------------------------------------- * SET and GET bitfields in u32 values diff --git a/drivers/usb/gadget/udc/amd5536udc_pci.c b/drivers/usb/gadget/udc/amd5536udc_pci.c new file mode 100644 index 000000000000..2a2d0a96fe24 --- /dev/null +++ b/drivers/usb/gadget/udc/amd5536udc_pci.c @@ -0,0 +1,217 @@ +/* + * amd5536udc_pci.c -- AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2005-2007 AMD (http://www.amd.com) + * Author: Thomas Dahlmann + * + * 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. + */ + +/* + * The AMD5536 UDC is part of the x86 southbridge AMD Geode CS5536. + * It is a USB Highspeed DMA capable USB device controller. Beside ep0 it + * provides 4 IN and 4 OUT endpoints (bulk or interrupt type). + * + * Make sure that UDC is assigned to port 4 by BIOS settings (port can also + * be used as host port) and UOC bits PAD_EN and APU are set (should be done + * by BIOS init). + * + * UDC DMA requires 32-bit aligned buffers so DMA with gadget ether does not + * work without updating NET_IP_ALIGN. Or PIO mode (module param "use_dma=0") + * can be used with gadget ether. + * + * This file does pci device registration, and the core driver implementation + * is done in amd5536udc.c + * + * The driver is split so as to use the core UDC driver which is based on + * Synopsys device controller IP (different than HS OTG IP) in UDCs + * integrated to SoC platforms. + * + */ + +/* Driver strings */ +#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" + +/* system */ +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/prefetch.h> +#include <linux/pci.h> + +/* udc specific */ +#include "amd5536udc.h" + +/* pointer to device object */ +static struct udc *udc; + +/* description */ +static const char mod_desc[] = UDC_MOD_DESCRIPTION; +static const char name[] = "amd5536udc-pci"; + +/* Reset all pci context */ +static void udc_pci_remove(struct pci_dev *pdev) +{ + struct udc *dev; + + dev = pci_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + /* gadget driver must not be registered */ + if (WARN_ON(dev->driver)) + return; + + /* dma pool cleanup */ + free_dma_pools(dev); + + /* reset controller */ + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + free_irq(pdev->irq, dev); + iounmap(dev->virt_addr); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + pci_disable_device(pdev); + + udc_remove(dev); +} + +/* Called by pci bus driver to init pci context */ +static int udc_pci_probe( + struct pci_dev *pdev, + const struct pci_device_id *id +) +{ + struct udc *dev; + unsigned long resource; + unsigned long len; + int retval = 0; + + /* one udc only */ + if (udc) { + dev_dbg(&pdev->dev, "already probed\n"); + return -EBUSY; + } + + /* init */ + dev = kzalloc(sizeof(struct udc), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* pci setup */ + if (pci_enable_device(pdev) < 0) { + retval = -ENODEV; + goto err_pcidev; + } + + /* PCI resource allocation */ + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + + if (!request_mem_region(resource, len, name)) { + dev_dbg(&pdev->dev, "pci device used already\n"); + retval = -EBUSY; + goto err_memreg; + } + + dev->virt_addr = ioremap_nocache(resource, len); + if (!dev->virt_addr) { + dev_dbg(&pdev->dev, "start address cannot be mapped\n"); + retval = -EFAULT; + goto err_ioremap; + } + + if (!pdev->irq) { + dev_err(&pdev->dev, "irq not set\n"); + retval = -ENODEV; + goto err_irq; + } + + spin_lock_init(&dev->lock); + /* udc csr registers base */ + dev->csr = dev->virt_addr + UDC_CSR_ADDR; + /* dev registers base */ + dev->regs = dev->virt_addr + UDC_DEVCFG_ADDR; + /* ep registers base */ + dev->ep_regs = dev->virt_addr + UDC_EPREGS_ADDR; + /* fifo's base */ + dev->rxfifo = (u32 __iomem *)(dev->virt_addr + UDC_RXFIFO_ADDR); + dev->txfifo = (u32 __iomem *)(dev->virt_addr + UDC_TXFIFO_ADDR); + + if (request_irq(pdev->irq, udc_irq, IRQF_SHARED, name, dev) != 0) { + dev_dbg(&pdev->dev, "request_irq(%d) fail\n", pdev->irq); + retval = -EBUSY; + goto err_irq; + } + + pci_set_drvdata(pdev, dev); + + /* chip revision for Hs AMD5536 */ + dev->chiprev = pdev->revision; + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + /* init dma pools */ + if (use_dma) { + retval = init_dma_pools(dev); + if (retval != 0) + goto err_dma; + } + + dev->phys_addr = resource; + dev->irq = pdev->irq; + dev->pdev = pdev; + + /* general probing */ + if (udc_probe(dev)) { + retval = -ENODEV; + goto err_probe; + } + return 0; + +err_probe: + if (use_dma) + free_dma_pools(dev); +err_dma: + free_irq(pdev->irq, dev); +err_irq: + iounmap(dev->virt_addr); +err_ioremap: + release_mem_region(resource, len); +err_memreg: + pci_disable_device(pdev); +err_pcidev: + kfree(dev); + return retval; +} + +/* PCI device parameters */ +static const struct pci_device_id pci_id[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + }, + {}, +}; +MODULE_DEVICE_TABLE(pci, pci_id); + +/* PCI functions */ +static struct pci_driver udc_pci_driver = { + .name = (char *) name, + .id_table = pci_id, + .probe = udc_pci_probe, + .remove = udc_pci_remove, +}; +module_pci_driver(udc_pci_driver); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Thomas Dahlmann"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 2035906b8ced..3ccc34176a5a 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -321,7 +321,6 @@ static inline void usba_cleanup_debugfs(struct usba_udc *udc) static ushort fifo_mode; -/* "modprobe ... fifo_mode=1" etc */ module_param(fifo_mode, ushort, 0x0); MODULE_PARM_DESC(fifo_mode, "Endpoint configuration mode"); @@ -371,7 +370,7 @@ static struct usba_fifo_cfg mode_4_cfg[] = { }; /* Add additional configurations here */ -int usba_config_fifo_table(struct usba_udc *udc) +static int usba_config_fifo_table(struct usba_udc *udc) { int n; @@ -1076,11 +1075,9 @@ static int atmel_usba_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver); static int atmel_usba_stop(struct usb_gadget *gadget); -static struct usb_ep *atmel_usba_match_ep( - struct usb_gadget *gadget, - struct usb_endpoint_descriptor *desc, - struct usb_ss_ep_comp_descriptor *ep_comp -) +static struct usb_ep *atmel_usba_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp) { struct usb_ep *_ep; struct usba_ep *ep; @@ -1100,7 +1097,6 @@ found_ep: ep = to_usba_ep(_ep); switch (usb_endpoint_type(desc)) { - case USB_ENDPOINT_XFER_CONTROL: break; @@ -1141,7 +1137,7 @@ found_ep: ep->udc->configured_ep++; } -return _ep; + return _ep; } static const struct usb_gadget_ops usba_udc_ops = { @@ -1855,8 +1851,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) * but it's clearly harmless... */ if (!(usba_ep_readl(ep0, CFG) & USBA_EPT_MAPPED)) - dev_dbg(&udc->pdev->dev, - "ODD: EP0 configuration is invalid!\n"); + dev_err(&udc->pdev->dev, + "ODD: EP0 configuration is invalid!\n"); /* Preallocate other endpoints */ n = fifo_mode ? udc->num_ep : udc->configured_ep; @@ -1864,8 +1860,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) ep = &udc->usba_ep[i]; usba_ep_writel(ep, CFG, ep->ept_cfg); if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) - dev_dbg(&udc->pdev->dev, - "ODD: EP%d configuration is invalid!\n", i); + dev_err(&udc->pdev->dev, + "ODD: EP%d configuration is invalid!\n", i); } } @@ -2089,8 +2085,9 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, while ((pp = of_get_next_child(np, pp))) udc->num_ep++; udc->configured_ep = 1; - } else + } else { udc->num_ep = usba_config_fifo_table(udc); + } eps = devm_kzalloc(&pdev->dev, sizeof(struct usba_ep) * udc->num_ep, GFP_KERNEL); @@ -2118,14 +2115,34 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, dev_err(&pdev->dev, "of_probe: fifo-size error(%d)\n", ret); goto err; } - ep->fifo_size = fifo_mode ? udc->fifo_cfg[i].fifo_size : val; + if (fifo_mode) { + if (val < udc->fifo_cfg[i].fifo_size) { + dev_warn(&pdev->dev, + "Using max fifo-size value from DT\n"); + ep->fifo_size = val; + } else { + ep->fifo_size = udc->fifo_cfg[i].fifo_size; + } + } else { + ep->fifo_size = val; + } ret = of_property_read_u32(pp, "atmel,nb-banks", &val); if (ret) { dev_err(&pdev->dev, "of_probe: nb-banks error(%d)\n", ret); goto err; } - ep->nr_banks = fifo_mode ? udc->fifo_cfg[i].nr_banks : val; + if (fifo_mode) { + if (val < udc->fifo_cfg[i].nr_banks) { + dev_warn(&pdev->dev, + "Using max nb-banks value from DT\n"); + ep->nr_banks = val; + } else { + ep->nr_banks = udc->fifo_cfg[i].nr_banks; + } + } else { + ep->nr_banks = val; + } ep->can_dma = of_property_read_bool(pp, "atmel,can-dma"); ep->can_isoc = of_property_read_bool(pp, "atmel,can-isoc"); diff --git a/drivers/usb/gadget/udc/bdc/Kconfig b/drivers/usb/gadget/udc/bdc/Kconfig index 0d7b8c9f72fd..eb8b55392360 100644 --- a/drivers/usb/gadget/udc/bdc/Kconfig +++ b/drivers/usb/gadget/udc/bdc/Kconfig @@ -14,7 +14,7 @@ if USB_BDC_UDC comment "Platform Support" config USB_BDC_PCI tristate "BDC support for PCIe based platforms" - depends on PCI + depends on USB_PCI default USB_BDC_UDC help Enable support for platforms which have BDC connected through PCIe, such as Lego3 FPGA platform. diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index d685d82dcf48..efce68e9a8e0 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1273,6 +1273,7 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) flush_work(&gadget->work); device_unregister(&udc->dev); device_unregister(&gadget->dev); + memset(&gadget->dev, 0x00, sizeof(gadget->dev)); } EXPORT_SYMBOL_GPL(usb_del_gadget_udc); diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 8cabc5944d5f..c79081952ea0 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -2062,16 +2062,13 @@ static int dummy_hub_control( } break; case USB_PORT_FEAT_POWER: - if (hcd->speed == HCD_USB3) { - if (dum_hcd->port_status & USB_PORT_STAT_POWER) - dev_dbg(dummy_dev(dum_hcd), - "power-off\n"); - } else - if (dum_hcd->port_status & - USB_SS_PORT_STAT_POWER) - dev_dbg(dummy_dev(dum_hcd), - "power-off\n"); - /* FALLS THROUGH */ + dev_dbg(dummy_dev(dum_hcd), "power-off\n"); + if (hcd->speed == HCD_USB3) + dum_hcd->port_status &= ~USB_SS_PORT_STAT_POWER; + else + dum_hcd->port_status &= ~USB_PORT_STAT_POWER; + set_link_state(dum_hcd); + break; default: dum_hcd->port_status &= ~(1 << wValue); set_link_state(dum_hcd); @@ -2242,14 +2239,13 @@ static int dummy_hub_control( if ((dum_hcd->port_status & USB_SS_PORT_STAT_POWER) != 0) { dum_hcd->port_status |= (1 << wValue); - set_link_state(dum_hcd); } } else if ((dum_hcd->port_status & USB_PORT_STAT_POWER) != 0) { dum_hcd->port_status |= (1 << wValue); - set_link_state(dum_hcd); } + set_link_state(dum_hcd); } break; case GetPortErrorCount: diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index b76fcdb763a0..6f2f71c054be 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2676,6 +2676,8 @@ static const struct platform_device_id fsl_udc_devtype[] = { }, { .name = "imx-udc-mx51", }, { + .name = "fsl-usb2-udc", + }, { /* sentinel */ } }; diff --git a/drivers/usb/gadget/udc/mv_u3d_core.c b/drivers/usb/gadget/udc/mv_u3d_core.c index d365449a295a..772049afe166 100644 --- a/drivers/usb/gadget/udc/mv_u3d_core.c +++ b/drivers/usb/gadget/udc/mv_u3d_core.c @@ -1835,13 +1835,18 @@ static int mv_u3d_probe(struct platform_device *dev) } /* we will access controller register, so enable the u3d controller */ - clk_enable(u3d->clk); + retval = clk_enable(u3d->clk); + if (retval) { + dev_err(&dev->dev, "clk_enable error %d\n", retval); + goto err_u3d_enable; + } if (pdata->phy_init) { retval = pdata->phy_init(u3d->phy_regs); if (retval) { dev_err(&dev->dev, "init phy error %d\n", retval); - goto err_u3d_enable; + clk_disable(u3d->clk); + goto err_phy_init; } } @@ -1974,15 +1979,13 @@ err_alloc_trb_pool: dma_free_coherent(&dev->dev, u3d->ep_context_size, u3d->ep_context, u3d->ep_context_dma); err_alloc_ep_context: - if (pdata->phy_deinit) - pdata->phy_deinit(u3d->phy_regs); - clk_disable(u3d->clk); +err_phy_init: err_u3d_enable: iounmap(u3d->cap_regs); err_map_cap_regs: err_get_cap_regs: -err_get_clk: clk_put(u3d->clk); +err_get_clk: kfree(u3d); err_alloc_private: err_pdata: diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 27ebb0d5449d..76f56c5762f9 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -445,7 +445,8 @@ static int mv_ep_enable(struct usb_ep *_ep, struct mv_dqh *dqh; u16 max = 0; u32 bit_pos, epctrlx, direction; - unsigned char zlt = 0, ios = 0, mult = 0; + const unsigned char zlt = 1; + unsigned char ios, mult; unsigned long flags; ep = container_of(_ep, struct mv_ep, ep); @@ -465,8 +466,6 @@ static int mv_ep_enable(struct usb_ep *_ep, * disable HW zero length termination select * driver handles zero length packet through req->req.zero */ - zlt = 1; - bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num); /* Check if the Endpoint is Primed */ @@ -481,16 +480,16 @@ static int mv_ep_enable(struct usb_ep *_ep, (unsigned)bit_pos); goto en_done; } + /* Set the max packet length, interrupt on Setup and Mult fields */ + ios = 0; + mult = 0; switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { case USB_ENDPOINT_XFER_BULK: - zlt = 1; - mult = 0; + case USB_ENDPOINT_XFER_INT: break; case USB_ENDPOINT_XFER_CONTROL: ios = 1; - case USB_ENDPOINT_XFER_INT: - mult = 0; break; case USB_ENDPOINT_XFER_ISOC: /* Calculate transactions needed for high bandwidth iso */ diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index 7dc0102abdfe..8f85a51bd2b3 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -653,7 +653,7 @@ net2272_request_dma(struct net2272 *dev, unsigned ep, u32 buf, dev->dma_busy = 1; /* initialize platform's dma */ -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI /* NET2272 addr, buffer addr, length, etc. */ switch (dev->dev_id) { case PCI_DEVICE_ID_RDK1: @@ -701,7 +701,7 @@ static void net2272_start_dma(struct net2272 *dev) { /* start platform's dma controller */ -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI switch (dev->dev_id) { case PCI_DEVICE_ID_RDK1: writeb((1 << CHANNEL_ENABLE) | (1 << CHANNEL_START), @@ -797,7 +797,7 @@ net2272_kick_dma(struct net2272_ep *ep, struct net2272_request *req) static void net2272_cancel_dma(struct net2272 *dev) { -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI switch (dev->dev_id) { case PCI_DEVICE_ID_RDK1: writeb(0, dev->rdk1.plx9054_base_addr + DMACSR0); @@ -2306,7 +2306,7 @@ err_add_udc: return ret; } -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI /* * wrap this driver around the specified device, but diff --git a/drivers/usb/gadget/udc/net2272.h b/drivers/usb/gadget/udc/net2272.h index 127ab03fcde3..69bc9c3c6ce4 100644 --- a/drivers/usb/gadget/udc/net2272.h +++ b/drivers/usb/gadget/udc/net2272.h @@ -472,7 +472,7 @@ struct net2272 { unsigned int base_shift; u16 __iomem *base_addr; union { -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI struct { void __iomem *plx9054_base_addr; void __iomem *epld_base_addr; diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 3828c2ec8623..6cf07857eaca 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -569,7 +569,7 @@ static struct usb_request if (ep->dma) { struct net2280_dma *td; - td = pci_pool_alloc(ep->dev->requests, gfp_flags, + td = dma_pool_alloc(ep->dev->requests, gfp_flags, &req->td_dma); if (!td) { kfree(req); @@ -597,7 +597,7 @@ static void net2280_free_request(struct usb_ep *_ep, struct usb_request *_req) req = container_of(_req, struct net2280_request, req); WARN_ON(!list_empty(&req->queue)); if (req->td) - pci_pool_free(ep->dev->requests, req->td, req->td_dma); + dma_pool_free(ep->dev->requests, req->td, req->td_dma); kfree(req); } @@ -3579,10 +3579,10 @@ static void net2280_remove(struct pci_dev *pdev) for (i = 1; i < 5; i++) { if (!dev->ep[i].dummy) continue; - pci_pool_free(dev->requests, dev->ep[i].dummy, + dma_pool_free(dev->requests, dev->ep[i].dummy, dev->ep[i].td_dma); } - pci_pool_destroy(dev->requests); + dma_pool_destroy(dev->requests); } if (dev->got_irq) free_irq(pdev->irq, dev); @@ -3724,7 +3724,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* DMA setup */ /* NOTE: we know only the 32 LSBs of dma addresses may be nonzero */ - dev->requests = pci_pool_create("requests", pdev, + dev->requests = dma_pool_create("requests", &pdev->dev, sizeof(struct net2280_dma), 0 /* no alignment requirements */, 0 /* or page-crossing issues */); @@ -3736,7 +3736,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) for (i = 1; i < 5; i++) { struct net2280_dma *td; - td = pci_pool_alloc(dev->requests, GFP_KERNEL, + td = dma_pool_alloc(dev->requests, GFP_KERNEL, &dev->ep[i].td_dma); if (!td) { ep_dbg(dev, "can't get dummy %d\n", i); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 2736a95751c3..1088c3745999 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -187,7 +187,7 @@ struct net2280 { struct usb338x_ll_chi_regs __iomem *ll_chicken_reg; struct usb338x_pl_regs __iomem *plregs; - struct pci_pool *requests; + struct dma_pool *requests; /* statistics...*/ }; diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 8a365aad66fe..84dcbcd756f0 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -355,8 +355,8 @@ struct pch_udc_dev { vbus_session:1, set_cfg_not_acked:1, waiting_zlp_ack:1; - struct pci_pool *data_requests; - struct pci_pool *stp_requests; + struct dma_pool *data_requests; + struct dma_pool *stp_requests; dma_addr_t dma_addr; struct usb_ctrlrequest setup_data; void __iomem *base_addr; @@ -1522,7 +1522,8 @@ static void pch_udc_free_dma_chain(struct pch_udc_dev *dev, /* do not free first desc., will be done by free for request */ td = phys_to_virt(addr); addr2 = (dma_addr_t)td->next; - pci_pool_free(dev->data_requests, td, addr); + dma_pool_free(dev->data_requests, td, addr); + td->next = 0x00; addr = addr2; } req->chain_len = 1; @@ -1538,7 +1539,7 @@ static void pch_udc_free_dma_chain(struct pch_udc_dev *dev, * * Return codes: * 0: success, - * -ENOMEM: pci_pool_alloc invocation fails + * -ENOMEM: dma_pool_alloc invocation fails */ static int pch_udc_create_dma_chain(struct pch_udc_ep *ep, struct pch_udc_request *req, @@ -1564,7 +1565,7 @@ static int pch_udc_create_dma_chain(struct pch_udc_ep *ep, if (bytes <= buf_len) break; last = td; - td = pci_pool_alloc(ep->dev->data_requests, gfp_flags, + td = dma_pool_alloc(ep->dev->data_requests, gfp_flags, &dma_addr); if (!td) goto nomem; @@ -1769,7 +1770,7 @@ static struct usb_request *pch_udc_alloc_request(struct usb_ep *usbep, if (!ep->dev->dma_addr) return &req->req; /* ep0 in requests are allocated from data pool here */ - dma_desc = pci_pool_alloc(ep->dev->data_requests, gfp, + dma_desc = dma_pool_alloc(ep->dev->data_requests, gfp, &req->td_data_phys); if (NULL == dma_desc) { kfree(req); @@ -1808,7 +1809,7 @@ static void pch_udc_free_request(struct usb_ep *usbep, if (req->td_data != NULL) { if (req->chain_len > 1) pch_udc_free_dma_chain(ep->dev, req); - pci_pool_free(ep->dev->data_requests, req->td_data, + dma_pool_free(ep->dev->data_requests, req->td_data, req->td_data_phys); } kfree(req); @@ -2913,7 +2914,7 @@ static int init_dma_pools(struct pch_udc_dev *dev) void *ep0out_buf; /* DMA setup */ - dev->data_requests = pci_pool_create("data_requests", dev->pdev, + dev->data_requests = dma_pool_create("data_requests", &dev->pdev->dev, sizeof(struct pch_udc_data_dma_desc), 0, 0); if (!dev->data_requests) { dev_err(&dev->pdev->dev, "%s: can't get request data pool\n", @@ -2922,7 +2923,7 @@ static int init_dma_pools(struct pch_udc_dev *dev) } /* dma desc for setup data */ - dev->stp_requests = pci_pool_create("setup requests", dev->pdev, + dev->stp_requests = dma_pool_create("setup requests", &dev->pdev->dev, sizeof(struct pch_udc_stp_dma_desc), 0, 0); if (!dev->stp_requests) { dev_err(&dev->pdev->dev, "%s: can't get setup request pool\n", @@ -2930,7 +2931,7 @@ static int init_dma_pools(struct pch_udc_dev *dev) return -ENOMEM; } /* setup */ - td_stp = pci_pool_alloc(dev->stp_requests, GFP_KERNEL, + td_stp = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, &dev->ep[UDC_EP0OUT_IDX].td_stp_phys); if (!td_stp) { dev_err(&dev->pdev->dev, @@ -2940,7 +2941,7 @@ static int init_dma_pools(struct pch_udc_dev *dev) dev->ep[UDC_EP0OUT_IDX].td_stp = td_stp; /* data: 0 packets !? */ - td_data = pci_pool_alloc(dev->data_requests, GFP_KERNEL, + td_data = dma_pool_alloc(dev->data_requests, GFP_KERNEL, &dev->ep[UDC_EP0OUT_IDX].td_data_phys); if (!td_data) { dev_err(&dev->pdev->dev, @@ -3020,22 +3021,21 @@ static void pch_udc_remove(struct pci_dev *pdev) dev_err(&pdev->dev, "%s: gadget driver still bound!!!\n", __func__); /* dma pool cleanup */ - if (dev->data_requests) - pci_pool_destroy(dev->data_requests); + dma_pool_destroy(dev->data_requests); if (dev->stp_requests) { /* cleanup DMA desc's for ep0in */ if (dev->ep[UDC_EP0OUT_IDX].td_stp) { - pci_pool_free(dev->stp_requests, + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IDX].td_stp, dev->ep[UDC_EP0OUT_IDX].td_stp_phys); } if (dev->ep[UDC_EP0OUT_IDX].td_data) { - pci_pool_free(dev->stp_requests, + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IDX].td_data, dev->ep[UDC_EP0OUT_IDX].td_data_phys); } - pci_pool_destroy(dev->stp_requests); + dma_pool_destroy(dev->stp_requests); } if (dev->dma_addr) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 832c4fdbe985..d48e239660c3 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -1608,9 +1608,6 @@ static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active) return 0; } -static void udc_enable(struct pxa_udc *udc); -static void udc_disable(struct pxa_udc *udc); - /** * pxa_udc_vbus_session - Called by external transceiver to enable/disable udc * @_gadget: usb gadget diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 2218f91e92a6..5a2d845fb1a6 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -10,6 +10,7 @@ #include <linux/delay.h> #include <linux/err.h> +#include <linux/extcon.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> @@ -37,6 +38,9 @@ #define USB3_USB_INT_ENA_2 0x22c #define USB3_STUP_DAT_0 0x230 #define USB3_STUP_DAT_1 0x234 +#define USB3_USB_OTG_STA 0x268 +#define USB3_USB_OTG_INT_STA 0x26c +#define USB3_USB_OTG_INT_ENA 0x270 #define USB3_P0_MOD 0x280 #define USB3_P0_CON 0x288 #define USB3_P0_STA 0x28c @@ -124,6 +128,9 @@ /* USB_INT_ENA_2 and USB_INT_STA_2 */ #define USB_INT_2_PIPE(n) BIT(n) +/* USB_OTG_STA, USB_OTG_INT_STA and USB_OTG_INT_ENA */ +#define USB_OTG_IDMON BIT(4) + /* P0_MOD */ #define P0_MOD_DIR BIT(6) @@ -257,6 +264,8 @@ struct renesas_usb3 { struct usb_gadget gadget; struct usb_gadget_driver *driver; + struct extcon_dev *extcon; + struct work_struct extcon_work; struct renesas_usb3_ep *usb3_ep; int num_usb3_eps; @@ -269,6 +278,8 @@ struct renesas_usb3 { u8 ep0_buf[USB3_EP0_BUF_SIZE]; bool softconnect; bool workaround_for_vbus; + bool extcon_host; /* check id and set EXTCON_USB_HOST */ + bool extcon_usb; /* check vbus and set EXTCON_USB */ }; #define gadget_to_renesas_usb3(_gadget) \ @@ -332,6 +343,15 @@ static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask, return -EBUSY; } +static void renesas_usb3_extcon_work(struct work_struct *work) +{ + struct renesas_usb3 *usb3 = container_of(work, struct renesas_usb3, + extcon_work); + + extcon_set_state_sync(usb3->extcon, EXTCON_USB_HOST, usb3->extcon_host); + extcon_set_state_sync(usb3->extcon, EXTCON_USB, usb3->extcon_usb); +} + static void usb3_enable_irq_1(struct renesas_usb3 *usb3, u32 bits) { usb3_set_bit(usb3, bits, USB3_USB_INT_ENA_1); @@ -352,6 +372,11 @@ static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num) usb3_clear_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); } +static bool usb3_is_host(struct renesas_usb3 *usb3) +{ + return !(usb3_read(usb3, USB3_DRD_CON) & DRD_CON_PERI_CON); +} + static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) { /* Set AXI_INT */ @@ -362,10 +387,6 @@ static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) static void usb3_init_epc_registers(struct renesas_usb3 *usb3) { - /* FIXME: How to change host / peripheral mode as well? */ - usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); - usb3_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON); - usb3_write(usb3, ~0, USB3_USB_INT_STA_1); usb3_enable_irq_1(usb3, USB_INT_1_VBUS_CNG); } @@ -531,18 +552,70 @@ static void usb3_check_vbus(struct renesas_usb3 *usb3) if (usb3->workaround_for_vbus) { usb3_connect(usb3); } else { - if (usb3_read(usb3, USB3_USB_STA) & USB_STA_VBUS_STA) + usb3->extcon_usb = !!(usb3_read(usb3, USB3_USB_STA) & + USB_STA_VBUS_STA); + if (usb3->extcon_usb) usb3_connect(usb3); else usb3_disconnect(usb3); + + schedule_work(&usb3->extcon_work); } } +static void usb3_set_mode(struct renesas_usb3 *usb3, bool host) +{ + if (host) + usb3_clear_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); + else + usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); +} + +static void usb3_vbus_out(struct renesas_usb3 *usb3, bool enable) +{ + if (enable) + usb3_set_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON); + else + usb3_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON); +} + +static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3_set_mode(usb3, host); + usb3_vbus_out(usb3, a_dev); + if (!host && a_dev) /* for A-Peripheral */ + usb3_connect(usb3); + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static bool usb3_is_a_device(struct renesas_usb3 *usb3) +{ + return !(usb3_read(usb3, USB3_USB_OTG_STA) & USB_OTG_IDMON); +} + +static void usb3_check_id(struct renesas_usb3 *usb3) +{ + usb3->extcon_host = usb3_is_a_device(usb3); + + if (usb3->extcon_host) + usb3_mode_config(usb3, true, true); + else + usb3_mode_config(usb3, false, false); + + schedule_work(&usb3->extcon_work); +} + static void renesas_usb3_init_controller(struct renesas_usb3 *usb3) { usb3_init_axi_bridge(usb3); usb3_init_epc_registers(usb3); + usb3_write(usb3, USB_OTG_IDMON, USB3_USB_OTG_INT_STA); + usb3_write(usb3, USB_OTG_IDMON, USB3_USB_OTG_INT_ENA); + usb3_check_id(usb3); usb3_check_vbus(usb3); } @@ -551,6 +624,7 @@ static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3) usb3_disconnect(usb3); usb3_write(usb3, 0, USB3_P0_INT_ENA); usb3_write(usb3, 0, USB3_PN_INT_ENA); + usb3_write(usb3, 0, USB3_USB_OTG_INT_ENA); usb3_write(usb3, 0, USB3_USB_INT_ENA_1); usb3_write(usb3, 0, USB3_USB_INT_ENA_2); usb3_write(usb3, 0, USB3_AXI_INT_ENA); @@ -1474,10 +1548,22 @@ static void usb3_irq_epc_int_2(struct renesas_usb3 *usb3, u32 int_sta_2) } } +static void usb3_irq_idmon_change(struct renesas_usb3 *usb3) +{ + usb3_check_id(usb3); +} + +static void usb3_irq_otg_int(struct renesas_usb3 *usb3, u32 otg_int_sta) +{ + if (otg_int_sta & USB_OTG_IDMON) + usb3_irq_idmon_change(usb3); +} + static void usb3_irq_epc(struct renesas_usb3 *usb3) { u32 int_sta_1 = usb3_read(usb3, USB3_USB_INT_STA_1); u32 int_sta_2 = usb3_read(usb3, USB3_USB_INT_STA_2); + u32 otg_int_sta = usb3_read(usb3, USB3_USB_OTG_INT_STA); int_sta_1 &= usb3_read(usb3, USB3_USB_INT_ENA_1); if (int_sta_1) { @@ -1488,6 +1574,12 @@ static void usb3_irq_epc(struct renesas_usb3 *usb3) int_sta_2 &= usb3_read(usb3, USB3_USB_INT_ENA_2); if (int_sta_2) usb3_irq_epc_int_2(usb3, int_sta_2); + + otg_int_sta &= usb3_read(usb3, USB3_USB_OTG_INT_ENA); + if (otg_int_sta) { + usb3_write(usb3, otg_int_sta, USB3_USB_OTG_INT_STA); + usb3_irq_otg_int(usb3, otg_int_sta); + } } static irqreturn_t renesas_usb3_irq(int irq, void *_usb3) @@ -1756,11 +1848,49 @@ static const struct usb_gadget_ops renesas_usb3_gadget_ops = { .set_selfpowered = renesas_usb3_set_selfpowered, }; +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + bool new_mode_is_host; + + if (!usb3->driver) + return -ENODEV; + + if (!strncmp(buf, "host", strlen("host"))) + new_mode_is_host = true; + else if (!strncmp(buf, "peripheral", strlen("peripheral"))) + new_mode_is_host = false; + else + return -EINVAL; + + if (new_mode_is_host == usb3_is_host(usb3)) + return -EINVAL; + + usb3_mode_config(usb3, new_mode_is_host, usb3_is_a_device(usb3)); + + return count; +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + + if (!usb3->driver) + return -ENODEV; + + return sprintf(buf, "%s\n", usb3_is_host(usb3) ? "host" : "peripheral"); +} +static DEVICE_ATTR_RW(role); + /*------- platform_driver ------------------------------------------------*/ static int renesas_usb3_remove(struct platform_device *pdev) { struct renesas_usb3 *usb3 = platform_get_drvdata(pdev); + device_remove_file(&pdev->dev, &dev_attr_role); + pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -1894,6 +2024,12 @@ static const struct of_device_id usb3_of_match[] = { }; MODULE_DEVICE_TABLE(of, usb3_of_match); +static const unsigned int renesas_usb3_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + static int renesas_usb3_probe(struct platform_device *pdev) { struct renesas_usb3 *usb3; @@ -1937,6 +2073,17 @@ static int renesas_usb3_probe(struct platform_device *pdev) if (ret < 0) return ret; + INIT_WORK(&usb3->extcon_work, renesas_usb3_extcon_work); + usb3->extcon = devm_extcon_dev_allocate(&pdev->dev, renesas_usb3_cable); + if (IS_ERR(usb3->extcon)) + return PTR_ERR(usb3->extcon); + + ret = devm_extcon_dev_register(&pdev->dev, usb3->extcon); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register extcon\n"); + return ret; + } + /* for ep0 handling */ usb3->ep0_req = __renesas_usb3_ep_alloc_request(GFP_KERNEL); if (!usb3->ep0_req) @@ -1946,6 +2093,10 @@ static int renesas_usb3_probe(struct platform_device *pdev) if (ret < 0) goto err_add_udc; + ret = device_create_file(&pdev->dev, &dev_attr_role); + if (ret < 0) + goto err_dev_create; + usb3->workaround_for_vbus = priv->workaround_for_vbus; pm_runtime_enable(&pdev->dev); @@ -1955,6 +2106,9 @@ static int renesas_usb3_probe(struct platform_device *pdev) return 0; +err_dev_create: + usb_del_gadget_udc(&usb3->gadget); + err_add_udc: __renesas_usb3_ep_free_request(usb3->ep0_req); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 407d947b34ea..ababb91d654a 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -30,7 +30,7 @@ if USB_XHCI_HCD config USB_XHCI_PCI tristate - depends on PCI + depends on USB_PCI default y config USB_XHCI_PLATFORM @@ -139,7 +139,7 @@ if USB_EHCI_HCD config USB_EHCI_PCI tristate - depends on PCI + depends on USB_PCI default y config USB_EHCI_HCD_PMC_MSP @@ -188,7 +188,7 @@ config USB_EHCI_HCD_OMAP config USB_EHCI_HCD_ORION tristate "Support for Marvell EBU on-chip EHCI USB controller" - depends on USB_EHCI_HCD && PLAT_ORION + depends on USB_EHCI_HCD && (PLAT_ORION || ARCH_MVEBU) default y ---help--- Enables support for the on-chip EHCI controller on Marvell's @@ -525,7 +525,7 @@ config USB_OHCI_HCD_PPC_OF config USB_OHCI_HCD_PCI tristate "OHCI support for PCI-bus USB controllers" - depends on PCI + depends on USB_PCI default y select USB_OHCI_LITTLE_ENDIAN ---help--- @@ -606,7 +606,7 @@ endif # USB_OHCI_HCD config USB_UHCI_HCD tristate "UHCI HCD (most Intel and VIA) support" - depends on PCI || USB_UHCI_SUPPORT_NON_PCI_HC + depends on USB_PCI || USB_UHCI_SUPPORT_NON_PCI_HC ---help--- The Universal Host Controller Interface is a standard by Intel for accessing the USB hardware in the PC (which is also called the USB @@ -739,7 +739,7 @@ config USB_RENESAS_USBHS_HCD config USB_WHCI_HCD tristate "Wireless USB Host Controller Interface (WHCI) driver" - depends on PCI && USB && UWB + depends on USB_PCI && USB && UWB select USB_WUSB select UWB_WHCI help diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 2644537b7bcf..c77b0a38557b 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -27,9 +27,7 @@ endif obj-$(CONFIG_USB_WHCI_HCD) += whci/ -ifneq ($(CONFIG_USB), ) - obj-$(CONFIG_PCI) += pci-quirks.o -endif +obj-$(CONFIG_USB_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o obj-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 1a2614aae42c..cbb9b8e12c3c 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -803,7 +803,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf) size -= temp; next += temp; -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI /* EHCI 0.96 and later may have "extended capabilities" */ if (dev_is_pci(hcd->self.controller)) { struct pci_dev *pdev; diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 3733aab46efe..4a08b70c81aa 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -96,8 +96,8 @@ static int fsl_ehci_drv_probe(struct platform_device *pdev) } irq = res->start; - hcd = usb_create_hcd(&fsl_ehci_hc_driver, &pdev->dev, - dev_name(&pdev->dev)); + hcd = __usb_create_hcd(&fsl_ehci_hc_driver, pdev->dev.parent, + &pdev->dev, dev_name(&pdev->dev), NULL); if (!hcd) { retval = -ENOMEM; goto err1; diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index ac2c4eab478d..6e834b83a104 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -597,7 +597,7 @@ static int ehci_run (struct usb_hcd *hcd) /* * hcc_params controls whether ehci->regs->segment must (!!!) * be used; it constrains QH/ITD/SITD and QTD locations. - * pci_pool consistent memory always uses segment zero. + * dma_pool consistent memory always uses segment zero. * streaming mappings for I/O buffers, like pci_map_single(), * can return segments above 4GB, if the device allows. * diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 4de43011df23..9b7e63977215 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -138,7 +138,7 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci) ehci->sitd_pool = NULL; if (ehci->periodic) - dma_free_coherent (ehci_to_hcd(ehci)->self.controller, + dma_free_coherent(ehci_to_hcd(ehci)->self.sysdev, ehci->periodic_size * sizeof (u32), ehci->periodic, ehci->periodic_dma); ehci->periodic = NULL; @@ -155,7 +155,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* QTDs for control/bulk/intr transfers */ ehci->qtd_pool = dma_pool_create ("ehci_qtd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_qtd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -165,7 +165,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* QHs for control/bulk/intr transfers */ ehci->qh_pool = dma_pool_create ("ehci_qh", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof(struct ehci_qh_hw), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -179,7 +179,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* ITD for high speed ISO transfers */ ehci->itd_pool = dma_pool_create ("ehci_itd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_itd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -189,7 +189,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* SITD for full/low speed split ISO transfers */ ehci->sitd_pool = dma_pool_create ("ehci_sitd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_sitd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -199,7 +199,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* Hardware periodic table */ ehci->periodic = (__le32 *) - dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller, + dma_alloc_coherent(ehci_to_hcd(ehci)->self.sysdev, ehci->periodic_size * sizeof(__le32), &ehci->periodic_dma, flags); if (ehci->periodic == NULL) { diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index ee8d5faa0194..1aec87ec68df 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -47,6 +47,18 @@ #define USB_PHY_IVREF_CTRL 0x440 #define USB_PHY_TST_GRP_CTRL 0x450 +#define USB_SBUSCFG 0x90 + +/* BAWR = BARD = 3 : Align read/write bursts packets larger than 128 bytes */ +#define USB_SBUSCFG_BAWR_ALIGN_128B (0x3 << 6) +#define USB_SBUSCFG_BARD_ALIGN_128B (0x3 << 3) +/* AHBBRST = 3 : Align AHB Burst to INCR16 (64 bytes) */ +#define USB_SBUSCFG_AHBBRST_INCR16 (0x3 << 0) + +#define USB_SBUSCFG_DEF_VAL (USB_SBUSCFG_BAWR_ALIGN_128B \ + | USB_SBUSCFG_BARD_ALIGN_128B \ + | USB_SBUSCFG_AHBBRST_INCR16) + #define DRIVER_DESC "EHCI orion driver" #define hcd_to_orion_priv(h) ((struct orion_ehci_hcd *)hcd_to_ehci(h)->priv) @@ -151,8 +163,31 @@ ehci_orion_conf_mbus_windows(struct usb_hcd *hcd, } } +static int ehci_orion_drv_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + int ret; + + ret = ehci_setup(hcd); + if (ret) + return ret; + + /* + * For SoC without hlock, need to program sbuscfg value to guarantee + * AHB master's burst would not overrun or underrun FIFO. + * + * sbuscfg reg has to be set after usb controller reset, otherwise + * the value would be override to 0. + */ + if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci")) + wrl(USB_SBUSCFG, USB_SBUSCFG_DEF_VAL); + + return ret; +} + static const struct ehci_driver_overrides orion_overrides __initconst = { .extra_priv_size = sizeof(struct orion_ehci_hcd), + .reset = ehci_orion_drv_reset, }; static int ehci_orion_drv_probe(struct platform_device *pdev) @@ -310,6 +345,7 @@ static int ehci_orion_drv_remove(struct platform_device *pdev) static const struct of_device_id ehci_orion_dt_ids[] = { { .compatible = "marvell,orion-ehci", }, + { .compatible = "marvell,armada-3700-ehci", }, {}, }; MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index a268d9e8d6cf..bc7b9be12f54 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -34,6 +34,7 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/ehci_pdriver.h> +#include <linux/usb/of.h> #include "ehci.h" @@ -220,6 +221,9 @@ static int ehci_platform_probe(struct platform_device *dev) if (IS_ERR(priv->phys[phy_num])) { err = PTR_ERR(priv->phys[phy_num]); goto err_put_hcd; + } else if (!hcd->phy) { + /* Avoiding phy_get() in usb_add_hcd() */ + hcd->phy = priv->phys[phy_num]; } } @@ -297,6 +301,7 @@ static int ehci_platform_probe(struct platform_device *dev) goto err_power; device_wakeup_enable(hcd->self.controller); + device_enable_async_suspend(hcd->self.controller); platform_set_drvdata(dev, hcd); return err; @@ -370,6 +375,7 @@ static int ehci_platform_resume(struct device *dev) struct usb_ehci_pdata *pdata = dev_get_platdata(dev); struct platform_device *pdev = to_platform_device(dev); struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + struct device *companion_dev; if (pdata->power_on) { int err = pdata->power_on(pdev); @@ -377,6 +383,10 @@ static int ehci_platform_resume(struct device *dev) return err; } + companion_dev = usb_of_get_companion_dev(hcd->self.controller); + if (companion_dev) + device_pm_wait_for_dev(hcd->self.controller, companion_dev); + ehci_resume(hcd, priv->reset_on_resume); return 0; } diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 1c5b34b74860..ced08dc229ad 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -5047,7 +5047,7 @@ static int fotg210_run(struct usb_hcd *hcd) /* * hcc_params controls whether fotg210->regs->segment must (!!!) * be used; it constrains QH/ITD/SITD and QTD locations. - * pci_pool consistent memory always uses segment zero. + * dma_pool consistent memory always uses segment zero. * streaming mappings for I/O buffers, like pci_map_single(), * can return segments above 4GB, if the device allows. * diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index b6daf2e69989..44924824fa41 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -231,7 +231,8 @@ static int ohci_urb_enqueue ( /* Start up the I/O watchdog timer, if it's not running */ if (!timer_pending(&ohci->io_watchdog) && - list_empty(&ohci->eds_in_use)) { + list_empty(&ohci->eds_in_use) && + !(ohci->flags & OHCI_QUIRK_QEMU)) { ohci->prev_frame_no = ohci_frame_no(ohci); mod_timer(&ohci->io_watchdog, jiffies + IO_WATCHDOG_DELAY); @@ -994,7 +995,7 @@ static void ohci_stop (struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ -#if defined(CONFIG_PM) || defined(CONFIG_PCI) +#if defined(CONFIG_PM) || defined(CONFIG_USB_PCI) /* must not be called from interrupt context */ int ohci_restart(struct ohci_hcd *ohci) diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index bb1509675727..a84aebe9b0a9 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -164,6 +164,15 @@ static int ohci_quirk_amd700(struct usb_hcd *hcd) return 0; } +static int ohci_quirk_qemu(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci->flags |= OHCI_QUIRK_QEMU; + ohci_dbg(ohci, "enabled qemu quirk\n"); + return 0; +} + /* List of quirks for OHCI */ static const struct pci_device_id ohci_pci_quirks[] = { { @@ -214,6 +223,13 @@ static const struct pci_device_id ohci_pci_quirks[] = { PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4399), .driver_data = (unsigned long)ohci_quirk_amd700, }, + { + .vendor = PCI_VENDOR_ID_APPLE, + .device = 0x003f, + .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, + .subdevice = PCI_SUBDEVICE_ID_QEMU, + .driver_data = (unsigned long)ohci_quirk_qemu, + }, /* FIXME for some of the early AMD 760 southbridges, OHCI * won't work at all. blacklist them. diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 898b74086c12..6368fce43197 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -183,6 +183,9 @@ static int ohci_platform_probe(struct platform_device *dev) if (IS_ERR(priv->phys[phy_num])) { err = PTR_ERR(priv->phys[phy_num]); goto err_put_hcd; + } else if (!hcd->phy) { + /* Avoiding phy_get() in usb_add_hcd() */ + hcd->phy = priv->phys[phy_num]; } } diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 37f1725e7a46..12742d002d2d 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -418,6 +418,7 @@ struct ohci_hcd { #define OHCI_QUIRK_AMD_PLL 0x200 /* AMD PLL quirk*/ #define OHCI_QUIRK_AMD_PREFETCH 0x400 /* pre-fetch for ISO transfer */ #define OHCI_QUIRK_GLOBAL_SUSPEND 0x800 /* must suspend ports */ +#define OHCI_QUIRK_QEMU 0x1000 /* relax timing expectations */ // there are also chip quirks/bugs in init logic @@ -438,7 +439,7 @@ struct ohci_hcd { }; -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI static inline int quirk_nec(struct ohci_hcd *ohci) { return ohci->flags & OHCI_QUIRK_NEC; diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index bcf531c44c70..ed20fb34c897 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -2708,7 +2708,7 @@ static int oxu_run(struct usb_hcd *hcd) /* hcc_params controls whether oxu->regs->segment must (!!!) * be used; it constrains QH/ITD/SITD and QTD locations. - * pci_pool consistent memory always uses segment zero. + * dma_pool consistent memory always uses segment zero. * streaming mappings for I/O buffers, like pci_map_single(), * can return segments above 4GB, if the device allows. * diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index c622ddf21c94..0222195bd5b0 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -1,7 +1,7 @@ #ifndef __LINUX_USB_PCI_QUIRKS_H #define __LINUX_USB_PCI_QUIRKS_H -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI void uhci_reset_hc(struct pci_dev *pdev, unsigned long base); int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base); int usb_amd_find_chipset_info(void); @@ -21,6 +21,6 @@ static inline void usb_amd_quirk_pll_enable(void) {} static inline void usb_amd_dev_put(void) {} static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} static inline void sb800_prefetch(struct device *dev, int on) {} -#endif /* CONFIG_PCI */ +#endif /* CONFIG_USB_PCI */ #endif /* __LINUX_USB_PCI_QUIRKS_H */ diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 683098afa93e..94b150196d4f 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -837,7 +837,7 @@ static int uhci_count_ports(struct usb_hcd *hcd) static const char hcd_name[] = "uhci_hcd"; -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI #include "uhci-pci.c" #define PCI_DRIVER uhci_pci_driver #endif diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 6f986d82472d..7fa318a3091d 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -530,7 +530,7 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) #else /* Support non-PCI host controllers */ -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI /* Support PCI and non-PCI host controllers */ #define uhci_has_pci_registers(u) ((u)->io_addr != 0) #else diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 2b4a00fa735d..2c83b37ae8f2 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -254,157 +254,6 @@ void xhci_print_registers(struct xhci_hcd *xhci) xhci_print_ports(xhci); } -void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb) -{ - int i; - for (i = 0; i < 4; i++) - xhci_dbg(xhci, "Offset 0x%x = 0x%x\n", - i*4, trb->generic.field[i]); -} - -/** - * Debug a transfer request block (TRB). - */ -void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb) -{ - u64 address; - u32 type = le32_to_cpu(trb->link.control) & TRB_TYPE_BITMASK; - - switch (type) { - case TRB_TYPE(TRB_LINK): - xhci_dbg(xhci, "Link TRB:\n"); - xhci_print_trb_offsets(xhci, trb); - - address = le64_to_cpu(trb->link.segment_ptr); - xhci_dbg(xhci, "Next ring segment DMA address = 0x%llx\n", address); - - xhci_dbg(xhci, "Interrupter target = 0x%x\n", - GET_INTR_TARGET(le32_to_cpu(trb->link.intr_target))); - xhci_dbg(xhci, "Cycle bit = %u\n", - le32_to_cpu(trb->link.control) & TRB_CYCLE); - xhci_dbg(xhci, "Toggle cycle bit = %u\n", - le32_to_cpu(trb->link.control) & LINK_TOGGLE); - xhci_dbg(xhci, "No Snoop bit = %u\n", - le32_to_cpu(trb->link.control) & TRB_NO_SNOOP); - break; - case TRB_TYPE(TRB_TRANSFER): - address = le64_to_cpu(trb->trans_event.buffer); - /* - * FIXME: look at flags to figure out if it's an address or if - * the data is directly in the buffer field. - */ - xhci_dbg(xhci, "DMA address or buffer contents= %llu\n", address); - break; - case TRB_TYPE(TRB_COMPLETION): - address = le64_to_cpu(trb->event_cmd.cmd_trb); - xhci_dbg(xhci, "Command TRB pointer = %llu\n", address); - xhci_dbg(xhci, "Completion status = %u\n", - GET_COMP_CODE(le32_to_cpu(trb->event_cmd.status))); - xhci_dbg(xhci, "Flags = 0x%x\n", - le32_to_cpu(trb->event_cmd.flags)); - break; - default: - xhci_dbg(xhci, "Unknown TRB with TRB type ID %u\n", - (unsigned int) type>>10); - xhci_print_trb_offsets(xhci, trb); - break; - } -} - -/** - * Debug a segment with an xHCI ring. - * - * @return The Link TRB of the segment, or NULL if there is no Link TRB - * (which is a bug, since all segments must have a Link TRB). - * - * Prints out all TRBs in the segment, even those after the Link TRB. - * - * XXX: should we print out TRBs that the HC owns? As long as we don't - * write, that should be fine... We shouldn't expect that the memory pointed to - * by the TRB is valid at all. Do we care about ones the HC owns? Probably, - * for HC debugging. - */ -void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg) -{ - int i; - u64 addr = seg->dma; - union xhci_trb *trb = seg->trbs; - - for (i = 0; i < TRBS_PER_SEGMENT; i++) { - trb = &seg->trbs[i]; - xhci_dbg(xhci, "@%016llx %08x %08x %08x %08x\n", addr, - lower_32_bits(le64_to_cpu(trb->link.segment_ptr)), - upper_32_bits(le64_to_cpu(trb->link.segment_ptr)), - le32_to_cpu(trb->link.intr_target), - le32_to_cpu(trb->link.control)); - addr += sizeof(*trb); - } -} - -void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring) -{ - xhci_dbg(xhci, "Ring deq = %p (virt), 0x%llx (dma)\n", - ring->dequeue, - (unsigned long long)xhci_trb_virt_to_dma(ring->deq_seg, - ring->dequeue)); - xhci_dbg(xhci, "Ring deq updated %u times\n", - ring->deq_updates); - xhci_dbg(xhci, "Ring enq = %p (virt), 0x%llx (dma)\n", - ring->enqueue, - (unsigned long long)xhci_trb_virt_to_dma(ring->enq_seg, - ring->enqueue)); - xhci_dbg(xhci, "Ring enq updated %u times\n", - ring->enq_updates); -} - -/** - * Debugging for an xHCI ring, which is a queue broken into multiple segments. - * - * Print out each segment in the ring. Check that the DMA address in - * each link segment actually matches the segment's stored DMA address. - * Check that the link end bit is only set at the end of the ring. - * Check that the dequeue and enqueue pointers point to real data in this ring - * (not some other ring). - */ -void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring) -{ - /* FIXME: Throw an error if any segment doesn't have a Link TRB */ - struct xhci_segment *seg; - struct xhci_segment *first_seg = ring->first_seg; - xhci_debug_segment(xhci, first_seg); - - if (!ring->enq_updates && !ring->deq_updates) { - xhci_dbg(xhci, " Ring has not been updated\n"); - return; - } - for (seg = first_seg->next; seg != first_seg; seg = seg->next) - xhci_debug_segment(xhci, seg); -} - -void xhci_dbg_ep_rings(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_virt_ep *ep) -{ - int i; - struct xhci_ring *ring; - - if (ep->ep_state & EP_HAS_STREAMS) { - for (i = 1; i < ep->stream_info->num_streams; i++) { - ring = ep->stream_info->stream_rings[i]; - xhci_dbg(xhci, "Dev %d endpoint %d stream ID %d:\n", - slot_id, ep_index, i); - xhci_debug_segment(xhci, ring->deq_seg); - } - } else { - ring = ep->ring; - if (!ring) - return; - xhci_dbg(xhci, "Dev %d endpoint ring %d:\n", - slot_id, ep_index); - xhci_debug_segment(xhci, ring->deq_seg); - } -} - void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst) { u64 addr = erst->erst_dma_addr; @@ -434,166 +283,13 @@ void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci) upper_32_bits(val)); } -/* Print the last 32 bytes for 64-byte contexts */ -static void dbg_rsvd64(struct xhci_hcd *xhci, u64 *ctx, dma_addr_t dma) -{ - int i; - for (i = 0; i < 4; i++) { - xhci_dbg(xhci, "@%p (virt) @%08llx " - "(dma) %#08llx - rsvd64[%d]\n", - &ctx[4 + i], (unsigned long long)dma, - ctx[4 + i], i); - dma += 8; - } -} - char *xhci_get_slot_state(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) { struct xhci_slot_ctx *slot_ctx = xhci_get_slot_ctx(xhci, ctx); + int state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); - switch (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state))) { - case SLOT_STATE_ENABLED: - return "enabled/disabled"; - case SLOT_STATE_DEFAULT: - return "default"; - case SLOT_STATE_ADDRESSED: - return "addressed"; - case SLOT_STATE_CONFIGURED: - return "configured"; - default: - return "reserved"; - } -} - -static void xhci_dbg_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) -{ - /* Fields are 32 bits wide, DMA addresses are in bytes */ - int field_size = 32 / 8; - int i; - - struct xhci_slot_ctx *slot_ctx = xhci_get_slot_ctx(xhci, ctx); - dma_addr_t dma = ctx->dma + - ((unsigned long)slot_ctx - (unsigned long)ctx->bytes); - int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); - - xhci_dbg(xhci, "Slot Context:\n"); - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info\n", - &slot_ctx->dev_info, - (unsigned long long)dma, slot_ctx->dev_info); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info2\n", - &slot_ctx->dev_info2, - (unsigned long long)dma, slot_ctx->dev_info2); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tt_info\n", - &slot_ctx->tt_info, - (unsigned long long)dma, slot_ctx->tt_info); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_state\n", - &slot_ctx->dev_state, - (unsigned long long)dma, slot_ctx->dev_state); - dma += field_size; - for (i = 0; i < 4; i++) { - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", - &slot_ctx->reserved[i], (unsigned long long)dma, - slot_ctx->reserved[i], i); - dma += field_size; - } - - if (csz) - dbg_rsvd64(xhci, (u64 *)slot_ctx, dma); -} - -static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, - struct xhci_container_ctx *ctx, - unsigned int last_ep) -{ - int i, j; - int last_ep_ctx = 31; - /* Fields are 32 bits wide, DMA addresses are in bytes */ - int field_size = 32 / 8; - int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); - - if (last_ep < 31) - last_ep_ctx = last_ep + 1; - for (i = 0; i < last_ep_ctx; i++) { - unsigned int epaddr = xhci_get_endpoint_address(i); - struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i); - dma_addr_t dma = ctx->dma + - ((unsigned long)ep_ctx - (unsigned long)ctx->bytes); - - xhci_dbg(xhci, "%s Endpoint %02d Context (ep_index %02d):\n", - usb_endpoint_out(epaddr) ? "OUT" : "IN", - epaddr & USB_ENDPOINT_NUMBER_MASK, i); - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", - &ep_ctx->ep_info, - (unsigned long long)dma, ep_ctx->ep_info); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info2\n", - &ep_ctx->ep_info2, - (unsigned long long)dma, ep_ctx->ep_info2); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08llx - deq\n", - &ep_ctx->deq, - (unsigned long long)dma, ep_ctx->deq); - dma += 2*field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tx_info\n", - &ep_ctx->tx_info, - (unsigned long long)dma, ep_ctx->tx_info); - dma += field_size; - for (j = 0; j < 3; j++) { - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", - &ep_ctx->reserved[j], - (unsigned long long)dma, - ep_ctx->reserved[j], j); - dma += field_size; - } - - if (csz) - dbg_rsvd64(xhci, (u64 *)ep_ctx, dma); - } -} - -void xhci_dbg_ctx(struct xhci_hcd *xhci, - struct xhci_container_ctx *ctx, - unsigned int last_ep) -{ - int i; - /* Fields are 32 bits wide, DMA addresses are in bytes */ - int field_size = 32 / 8; - dma_addr_t dma = ctx->dma; - int csz = HCC_64BYTE_CONTEXT(xhci->hcc_params); - - if (ctx->type == XHCI_CTX_TYPE_INPUT) { - struct xhci_input_control_ctx *ctrl_ctx = - xhci_get_input_control_ctx(ctx); - if (!ctrl_ctx) { - xhci_warn(xhci, "Could not get input context, bad type.\n"); - return; - } - - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - drop flags\n", - &ctrl_ctx->drop_flags, (unsigned long long)dma, - ctrl_ctx->drop_flags); - dma += field_size; - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - add flags\n", - &ctrl_ctx->add_flags, (unsigned long long)dma, - ctrl_ctx->add_flags); - dma += field_size; - for (i = 0; i < 6; i++) { - xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd2[%d]\n", - &ctrl_ctx->rsvd2[i], (unsigned long long)dma, - ctrl_ctx->rsvd2[i], i); - dma += field_size; - } - - if (csz) - dbg_rsvd64(xhci, (u64 *)ctrl_ctx, dma); - } - - xhci_dbg_slot_ctx(xhci, ctx); - xhci_dbg_ep_ctx(xhci, ctx, last_ep); + return xhci_slot_state_string(state); } void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 3bddeaa1e2d7..5e3e9d4c6956 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -392,10 +392,8 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend) trace_xhci_stop_device(virt_dev); cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO); - if (!cmd) { - xhci_dbg(xhci, "Couldn't allocate command structure.\n"); + if (!cmd) return -ENOMEM; - } spin_lock_irqsave(&xhci->lock, flags); for (i = LAST_EP_INDEX; i > 0; i--) { @@ -540,6 +538,119 @@ static int xhci_get_ports(struct usb_hcd *hcd, __le32 __iomem ***port_array) return max_ports; } +static __le32 __iomem *xhci_get_port_io_addr(struct usb_hcd *hcd, int index) +{ + __le32 __iomem **port_array; + + xhci_get_ports(hcd, &port_array); + return port_array[index]; +} + +/* + * xhci_set_port_power() must be called with xhci->lock held. + * It will release and re-aquire the lock while calling ACPI + * method. + */ +static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd, + u16 index, bool on, unsigned long *flags) +{ + __le32 __iomem *addr; + u32 temp; + + addr = xhci_get_port_io_addr(hcd, index); + temp = readl(addr); + temp = xhci_port_state_to_neutral(temp); + if (on) { + /* Power on */ + writel(temp | PORT_POWER, addr); + temp = readl(addr); + xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", + index, temp); + } else { + /* Power off */ + writel(temp & ~PORT_POWER, addr); + } + + spin_unlock_irqrestore(&xhci->lock, *flags); + temp = usb_acpi_power_manageable(hcd->self.root_hub, + index); + if (temp) + usb_acpi_set_power_state(hcd->self.root_hub, + index, on); + spin_lock_irqsave(&xhci->lock, *flags); +} + +static void xhci_port_set_test_mode(struct xhci_hcd *xhci, + u16 test_mode, u16 wIndex) +{ + u32 temp; + __le32 __iomem *addr; + + /* xhci only supports test mode for usb2 ports, i.e. xhci->main_hcd */ + addr = xhci_get_port_io_addr(xhci->main_hcd, wIndex); + temp = readl(addr + PORTPMSC); + temp |= test_mode << PORT_TEST_MODE_SHIFT; + writel(temp, addr + PORTPMSC); + xhci->test_mode = test_mode; + if (test_mode == TEST_FORCE_EN) + xhci_start(xhci); +} + +static int xhci_enter_test_mode(struct xhci_hcd *xhci, + u16 test_mode, u16 wIndex, unsigned long *flags) +{ + int i, retval; + + /* Disable all Device Slots */ + xhci_dbg(xhci, "Disable all slots\n"); + for (i = 1; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) { + retval = xhci_disable_slot(xhci, NULL, i); + if (retval) + xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n", + i, retval); + } + /* Put all ports to the Disable state by clear PP */ + xhci_dbg(xhci, "Disable all port (PP = 0)\n"); + /* Power off USB3 ports*/ + for (i = 0; i < xhci->num_usb3_ports; i++) + xhci_set_port_power(xhci, xhci->shared_hcd, i, false, flags); + /* Power off USB2 ports*/ + for (i = 0; i < xhci->num_usb2_ports; i++) + xhci_set_port_power(xhci, xhci->main_hcd, i, false, flags); + /* Stop the controller */ + xhci_dbg(xhci, "Stop controller\n"); + retval = xhci_halt(xhci); + if (retval) + return retval; + /* Disable runtime PM for test mode */ + pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller); + /* Set PORTPMSC.PTC field to enter selected test mode */ + /* Port is selected by wIndex. port_id = wIndex + 1 */ + xhci_dbg(xhci, "Enter Test Mode: %d, Port_id=%d\n", + test_mode, wIndex + 1); + xhci_port_set_test_mode(xhci, test_mode, wIndex); + return retval; +} + +static int xhci_exit_test_mode(struct xhci_hcd *xhci) +{ + int retval; + + if (!xhci->test_mode) { + xhci_err(xhci, "Not in test mode, do nothing.\n"); + return 0; + } + if (xhci->test_mode == TEST_FORCE_EN && + !(xhci->xhc_state & XHCI_STATE_HALTED)) { + retval = xhci_halt(xhci); + if (retval) + return retval; + } + pm_runtime_allow(xhci_to_hcd(xhci)->self.controller); + xhci->test_mode = 0; + return xhci_reset(xhci); +} + void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, int port_id, u32 link_state) { @@ -895,6 +1006,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 link_state = 0; u16 wake_mask = 0; u16 timeout = 0; + u16 test_mode = 0; max_ports = xhci_get_ports(hcd, &port_array); bus_state = &xhci->bus_state[hcd_index(hcd)]; @@ -935,7 +1047,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; wIndex--; temp = readl(port_array[wIndex]); - if (temp == 0xffffffff) { + if (temp == ~(u32)0) { + xhci_hc_died(xhci); retval = -ENODEV; break; } @@ -968,6 +1081,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, link_state = (wIndex & 0xff00) >> 3; if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) wake_mask = wIndex & 0xff00; + if (wValue == USB_PORT_FEAT_TEST) + test_mode = (wIndex & 0xff00) >> 8; /* The MSB of wIndex is the U1/U2 timeout */ timeout = (wIndex & 0xff00) >> 8; wIndex &= 0xff; @@ -975,7 +1090,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; wIndex--; temp = readl(port_array[wIndex]); - if (temp == 0xffffffff) { + if (temp == ~(u32)0) { + xhci_hc_died(xhci); retval = -ENODEV; break; } @@ -1092,18 +1208,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * However, hub_wq will ignore the roothub events until * the roothub is registered. */ - writel(temp | PORT_POWER, port_array[wIndex]); - - temp = readl(port_array[wIndex]); - xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp); - - spin_unlock_irqrestore(&xhci->lock, flags); - temp = usb_acpi_power_manageable(hcd->self.root_hub, - wIndex); - if (temp) - usb_acpi_set_power_state(hcd->self.root_hub, - wIndex, true); - spin_lock_irqsave(&xhci->lock, flags); + xhci_set_port_power(xhci, hcd, wIndex, true, &flags); break; case USB_PORT_FEAT_RESET: temp = (temp | PORT_RESET); @@ -1142,6 +1247,15 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp |= PORT_U2_TIMEOUT(timeout); writel(temp, port_array[wIndex] + PORTPMSC); break; + case USB_PORT_FEAT_TEST: + /* 4.19.6 Port Test Modes (USB2 Test Mode) */ + if (hcd->speed != HCD_USB2) + goto error; + if (test_mode > TEST_FORCE_EN || test_mode < TEST_J) + goto error; + retval = xhci_enter_test_mode(xhci, test_mode, wIndex, + &flags); + break; default: goto error; } @@ -1153,7 +1267,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; wIndex--; temp = readl(port_array[wIndex]); - if (temp == 0xffffffff) { + if (temp == ~(u32)0) { + xhci_hc_died(xhci); retval = -ENODEV; break; } @@ -1207,15 +1322,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, port_array[wIndex], temp); break; case USB_PORT_FEAT_POWER: - writel(temp & ~PORT_POWER, port_array[wIndex]); - - spin_unlock_irqrestore(&xhci->lock, flags); - temp = usb_acpi_power_manageable(hcd->self.root_hub, - wIndex); - if (temp) - usb_acpi_set_power_state(hcd->self.root_hub, - wIndex, false); - spin_lock_irqsave(&xhci->lock, flags); + xhci_set_port_power(xhci, hcd, wIndex, false, &flags); + break; + case USB_PORT_FEAT_TEST: + retval = xhci_exit_test_mode(xhci); break; default: goto error; @@ -1269,7 +1379,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) /* For each port, did anything change? If so, set that bit in buf. */ for (i = 0; i < max_ports; i++) { temp = readl(port_array[i]); - if (temp == 0xffffffff) { + if (temp == ~(u32)0) { + xhci_hc_died(xhci); retval = -ENODEV; break; } diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ba1853f4e407..bbe22bcc550a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -288,6 +288,8 @@ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) if (!ring) return; + trace_xhci_ring_free(ring); + if (ring->first_seg) { if (ring->type == TYPE_STREAM) xhci_remove_stream_mapping(ring); @@ -313,9 +315,6 @@ static void xhci_initialize_ring_info(struct xhci_ring *ring, * handling ring expansion, set the cycle state equal to the old ring. */ ring->cycle_state = cycle_state; - /* Not necessary for new rings, but needed for re-initialized rings */ - ring->enq_updates = 0; - ring->deq_updates = 0; /* * Each segment has a link TRB, and leave an extra TRB for SW @@ -400,6 +399,7 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, cpu_to_le32(LINK_TOGGLE); } xhci_initialize_ring_info(ring, cycle_state); + trace_xhci_ring_alloc(ring); return ring; fail: @@ -504,6 +504,7 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, } xhci_link_rings(xhci, ring, first, last, num_segs); + trace_xhci_ring_expansion(ring); xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, "ring expansion succeed, now has %d segments", ring->num_segs); @@ -586,7 +587,7 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, struct xhci_stream_ctx *stream_ctx, dma_addr_t dma) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; if (size > MEDIUM_STREAM_ARRAY_SIZE) @@ -614,7 +615,7 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, dma_addr_t *dma, gfp_t mem_flags) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; if (size > MEDIUM_STREAM_ARRAY_SIZE) @@ -1502,6 +1503,17 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, */ max_esit_payload = xhci_get_max_esit_payload(udev, ep); interval = xhci_get_endpoint_interval(udev, ep); + + /* Periodic endpoint bInterval limit quirk */ + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + if ((xhci->quirks & XHCI_LIMIT_ENDPOINT_INTERVAL_7) && + udev->speed >= USB_SPEED_HIGH && + interval >= 7) { + interval = 6; + } + } + mult = xhci_get_endpoint_mult(udev, ep); max_packet = usb_endpoint_maxp(&ep->desc); max_burst = xhci_get_endpoint_max_burst(udev, ep); @@ -1686,7 +1698,7 @@ void xhci_slot_copy(struct xhci_hcd *xhci, static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) { int i; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; int num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); xhci_dbg_trace(xhci, trace_xhci_dbg_init, @@ -1709,36 +1721,27 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) if (!xhci->scratchpad->sp_buffers) goto fail_sp3; - xhci->scratchpad->sp_dma_buffers = - kzalloc(sizeof(dma_addr_t) * num_sp, flags); - - if (!xhci->scratchpad->sp_dma_buffers) - goto fail_sp4; - xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma); for (i = 0; i < num_sp; i++) { dma_addr_t dma; void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma, flags); if (!buf) - goto fail_sp5; + goto fail_sp4; xhci->scratchpad->sp_array[i] = dma; xhci->scratchpad->sp_buffers[i] = buf; - xhci->scratchpad->sp_dma_buffers[i] = dma; } return 0; - fail_sp5: + fail_sp4: for (i = i - 1; i >= 0; i--) { dma_free_coherent(dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], - xhci->scratchpad->sp_dma_buffers[i]); + xhci->scratchpad->sp_array[i]); } - kfree(xhci->scratchpad->sp_dma_buffers); - fail_sp4: kfree(xhci->scratchpad->sp_buffers); fail_sp3: @@ -1758,7 +1761,7 @@ static void scratchpad_free(struct xhci_hcd *xhci) { int num_sp; int i; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; if (!xhci->scratchpad) return; @@ -1768,9 +1771,8 @@ static void scratchpad_free(struct xhci_hcd *xhci) for (i = 0; i < num_sp; i++) { dma_free_coherent(dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], - xhci->scratchpad->sp_dma_buffers[i]); + xhci->scratchpad->sp_array[i]); } - kfree(xhci->scratchpad->sp_dma_buffers); kfree(xhci->scratchpad->sp_buffers); dma_free_coherent(dev, num_sp * sizeof(u64), xhci->scratchpad->sp_array, @@ -1831,7 +1833,7 @@ void xhci_free_command(struct xhci_hcd *xhci, void xhci_mem_cleanup(struct xhci_hcd *xhci) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; int size; int i, j, num_ports; @@ -2373,7 +2375,7 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { dma_addr_t dma; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; unsigned int val, val2; u64 val_64; struct xhci_segment *seg; @@ -2419,7 +2421,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) writel(val, &xhci->op_regs->config_reg); /* - * Section 5.4.8 - doorbell array must be + * xHCI section 5.4.6 - doorbell array must be * "physically contiguous and 64-byte (cache line) aligned". */ xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, @@ -2480,7 +2482,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) (xhci->cmd_ring->first_seg->dma & (u64) ~CMD_RING_RSVD_BITS) | xhci->cmd_ring->cycle_state; xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Setting command ring address to 0x%x", val); + "// Setting command ring address to 0x%016llx", val_64); xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); xhci_dbg_cmd_ptrs(xhci); @@ -2601,7 +2603,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) return 0; fail: - xhci_warn(xhci, "Couldn't initialize memory\n"); xhci_halt(xhci); xhci_reset(xhci); xhci_mem_cleanup(xhci); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index fc99f51d12e1..7b86508ac8cf 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -199,6 +199,9 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == 0x1042) xhci->quirks |= XHCI_BROKEN_STREAMS; + if (pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241) + xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_7; + if (xhci->quirks & XHCI_RESET_ON_RESUME) xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, "QUIRK: Resetting on resume"); diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 6ed468fa7d5e..7c2a9e7c8e0f 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -14,6 +14,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h> #include <linux/module.h> +#include <linux/pci.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/usb/phy.h> @@ -54,6 +55,16 @@ static int xhci_priv_init_quirk(struct usb_hcd *hcd) return priv->init_quirk(hcd); } +static int xhci_priv_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->resume_quirk) + return 0; + + return priv->resume_quirk(hcd); +} + static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { /* @@ -92,18 +103,21 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, .init_quirk = xhci_rcar_init_quirk, .plat_start = xhci_rcar_start, + .resume_quirk = xhci_rcar_resume_quirk, }; static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V2, .init_quirk = xhci_rcar_init_quirk, .plat_start = xhci_rcar_start, + .resume_quirk = xhci_rcar_resume_quirk, }; static const struct xhci_plat_priv xhci_plat_renesas_rcar_r8a7796 = { .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V3, .init_quirk = xhci_rcar_init_quirk, .plat_start = xhci_rcar_start, + .resume_quirk = xhci_rcar_resume_quirk, }; static const struct of_device_id usb_xhci_of_match[] = { @@ -148,6 +162,7 @@ static int xhci_plat_probe(struct platform_device *pdev) { const struct of_device_id *match; const struct hc_driver *driver; + struct device *sysdev; struct xhci_hcd *xhci; struct resource *res; struct usb_hcd *hcd; @@ -164,24 +179,47 @@ static int xhci_plat_probe(struct platform_device *pdev) if (irq < 0) return -ENODEV; + /* + * sysdev must point to a device that is known to the system firmware + * or PCI hardware. We handle these three cases here: + * 1. xhci_plat comes from firmware + * 2. xhci_plat is child of a device from firmware (dwc3-plat) + * 3. xhci_plat is grandchild of a pci device (dwc3-pci) + */ + sysdev = &pdev->dev; + if (sysdev->parent && !sysdev->of_node && sysdev->parent->of_node) + sysdev = sysdev->parent; +#ifdef CONFIG_PCI + else if (sysdev->parent && sysdev->parent->parent && + sysdev->parent->parent->bus == &pci_bus_type) + sysdev = sysdev->parent->parent; +#endif + /* Try to set 64-bit DMA first */ - if (!pdev->dev.dma_mask) + if (WARN_ON(!sysdev->dma_mask)) /* Platform did not initialize dma_mask */ - ret = dma_coerce_mask_and_coherent(&pdev->dev, + ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); else - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ if (ret) { - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32)); if (ret) return ret; } - hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); - if (!hcd) - return -ENOMEM; + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), NULL); + if (!hcd) { + ret = -ENOMEM; + goto disable_runtime; + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hcd->regs = devm_ioremap_resource(&pdev->dev, res); @@ -222,20 +260,20 @@ static int xhci_plat_probe(struct platform_device *pdev) xhci->clk = clk; xhci->main_hcd = hcd; - xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, + xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, dev_name(&pdev->dev), hcd); if (!xhci->shared_hcd) { ret = -ENOMEM; goto disable_clk; } - if (device_property_read_bool(&pdev->dev, "usb3-lpm-capable")) + if (device_property_read_bool(sysdev, "usb3-lpm-capable")) xhci->quirks |= XHCI_LPM_SUPPORT; if (device_property_read_bool(&pdev->dev, "quirk-broken-port-ped")) xhci->quirks |= XHCI_BROKEN_PORT_PED; - hcd->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0); if (IS_ERR(hcd->usb_phy)) { ret = PTR_ERR(hcd->usb_phy); if (ret == -EPROBE_DEFER) @@ -258,6 +296,15 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto dealloc_usb2_hcd; + device_enable_async_suspend(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + + /* + * Prevent runtime pm from being on as default, users should enable + * runtime pm using power/control in sysfs. + */ + pm_runtime_forbid(&pdev->dev); + return 0; @@ -277,6 +324,10 @@ disable_clk: put_hcd: usb_put_hcd(hcd); +disable_runtime: + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return ret; } @@ -298,14 +349,17 @@ static int xhci_plat_remove(struct platform_device *dev) clk_disable_unprepare(clk); usb_put_hcd(hcd); + pm_runtime_set_suspended(&dev->dev); + pm_runtime_disable(&dev->dev); + return 0; } -#ifdef CONFIG_PM_SLEEP -static int xhci_plat_suspend(struct device *dev) +static int __maybe_unused xhci_plat_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; /* * xhci_suspend() needs `do_wakeup` to know whether host is allowed @@ -315,24 +369,53 @@ static int xhci_plat_suspend(struct device *dev) * reconsider this when xhci_plat_suspend enlarges its scope, e.g., * also applies to runtime suspend. */ - return xhci_suspend(xhci, device_may_wakeup(dev)); + ret = xhci_suspend(xhci, device_may_wakeup(dev)); + + if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk)) + clk_disable_unprepare(xhci->clk); + + return ret; } -static int xhci_plat_resume(struct device *dev) +static int __maybe_unused xhci_plat_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk)) + clk_prepare_enable(xhci->clk); + + ret = xhci_priv_resume_quirk(hcd); + if (ret) + return ret; + + return xhci_resume(xhci, 0); +} + +static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return xhci_suspend(xhci, true); +} + +static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); return xhci_resume(xhci, 0); } static const struct dev_pm_ops xhci_plat_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume) + + SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, + xhci_plat_runtime_resume, + NULL) }; -#define DEV_PM_OPS (&xhci_plat_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif /* CONFIG_PM */ static const struct acpi_device_id usb_xhci_acpi_match[] = { /* XHCI-compliant USB Controller */ @@ -347,7 +430,7 @@ static struct platform_driver usb_xhci_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "xhci-hcd", - .pm = DEV_PM_OPS, + .pm = &xhci_plat_pm_ops, .of_match_table = of_match_ptr(usb_xhci_of_match), .acpi_match_table = ACPI_PTR(usb_xhci_acpi_match), }, diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 9af0cb48053f..29b227895b07 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -17,6 +17,7 @@ struct xhci_plat_priv { const char *firmware_name; void (*plat_start)(struct usb_hcd *); int (*init_quirk)(struct usb_hcd *); + int (*resume_quirk)(struct usb_hcd *); }; #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index d28df386e780..07278228214b 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -198,3 +198,14 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd) return xhci_rcar_download_firmware(hcd); } + +int xhci_rcar_resume_quirk(struct usb_hcd *hcd) +{ + int ret; + + ret = xhci_rcar_download_firmware(hcd); + if (!ret) + xhci_rcar_start(hcd); + + return ret; +} diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h index d2ffe20401cf..d247951147a1 100644 --- a/drivers/usb/host/xhci-rcar.h +++ b/drivers/usb/host/xhci-rcar.h @@ -18,6 +18,7 @@ #if IS_ENABLED(CONFIG_USB_XHCI_RCAR) void xhci_rcar_start(struct usb_hcd *hcd); int xhci_rcar_init_quirk(struct usb_hcd *hcd); +int xhci_rcar_resume_quirk(struct usb_hcd *hcd); #else static inline void xhci_rcar_start(struct usb_hcd *hcd) { @@ -27,5 +28,10 @@ static inline int xhci_rcar_init_quirk(struct usb_hcd *hcd) { return 0; } + +static inline int xhci_rcar_resume_quirk(struct usb_hcd *hcd) +{ + return 0; +} #endif #endif /* _XHCI_RCAR_H */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index a3309aa02993..74bf5c60a260 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -167,8 +167,6 @@ static void next_trb(struct xhci_hcd *xhci, */ static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) { - ring->deq_updates++; - /* event ring doesn't have link trbs, check for last trb */ if (ring->type == TYPE_EVENT) { if (!last_trb_on_seg(ring->deq_seg, ring->dequeue)) { @@ -191,6 +189,9 @@ static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) ring->deq_seg = ring->deq_seg->next; ring->dequeue = ring->deq_seg->trbs; } + + trace_xhci_inc_deq(ring); + return; } @@ -223,7 +224,6 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, ring->num_trbs_free--; next = ++(ring->enqueue); - ring->enq_updates++; /* Update the dequeue pointer further if that was a link TRB */ while (trb_is_link(next)) { @@ -259,6 +259,8 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, ring->enqueue = ring->enq_seg->trbs; next = ring->enqueue; } + + trace_xhci_inc_enq(ring); } /* @@ -359,21 +361,19 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) xhci_write_64(xhci, temp_64 | CMD_RING_ABORT, &xhci->op_regs->cmd_ring); - /* Section 4.6.1.2 of xHCI 1.0 spec says software should - * time the completion od all xHCI commands, including - * the Command Abort operation. If software doesn't see - * CRR negated in a timely manner (e.g. longer than 5 - * seconds), then it should assume that the there are - * larger problems with the xHC and assert HCRST. + /* Section 4.6.1.2 of xHCI 1.0 spec says software should also time the + * completion of the Command Abort operation. If CRR is not negated in 5 + * seconds then driver handles it as if host died (-ENODEV). + * In the future we should distinguish between -ENODEV and -ETIMEDOUT + * and try to recover a -ETIMEDOUT with a host controller reset. */ ret = xhci_handshake(&xhci->op_regs->cmd_ring, CMD_RING_RUNNING, 0, 5 * 1000 * 1000); if (ret < 0) { - xhci_err(xhci, - "Stop command ring failed, maybe the host is dead\n"); - xhci->xhc_state |= XHCI_STATE_DYING; + xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret); xhci_halt(xhci); - return -ESHUTDOWN; + xhci_hc_died(xhci); + return ret; } /* * Writing the CMD_RING_ABORT bit should cause a cmd completion event, @@ -689,6 +689,8 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_virt_ep *ep; struct xhci_td *cur_td = NULL; struct xhci_td *last_unlinked_td; + struct xhci_ep_ctx *ep_ctx; + struct xhci_virt_device *vdev; struct xhci_dequeue_state deq_state; @@ -702,6 +704,11 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, memset(&deq_state, 0, sizeof(deq_state)); ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + + vdev = xhci->devs[slot_id]; + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + trace_xhci_handle_cmd_stop_ep(ep_ctx); + ep = &xhci->devs[slot_id]->eps[ep_index]; last_unlinked_td = list_last_entry(&ep->cancelled_td_list, struct xhci_td, cancelled_td_list); @@ -866,6 +873,40 @@ static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, } } +/* + * host controller died, register read returns 0xffffffff + * Complete pending commands, mark them ABORTED. + * URBs need to be given back as usb core might be waiting with device locks + * held for the URBs to finish during device disconnect, blocking host remove. + * + * Call with xhci->lock held. + * lock is relased and re-acquired while giving back urb. + */ +void xhci_hc_died(struct xhci_hcd *xhci) +{ + int i, j; + + if (xhci->xhc_state & XHCI_STATE_DYING) + return; + + xhci_err(xhci, "xHCI host controller not responding, assume dead\n"); + xhci->xhc_state |= XHCI_STATE_DYING; + + xhci_cleanup_command_queue(xhci); + + /* return any pending urbs, remove may be waiting for them */ + for (i = 0; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) { + if (!xhci->devs[i]) + continue; + for (j = 0; j < 31; j++) + xhci_kill_endpoint_urbs(xhci, i, j); + } + + /* inform usb core hc died if PCI remove isn't already handling it */ + if (!(xhci->xhc_state & XHCI_STATE_REMOVING)) + usb_hc_died(xhci_to_hcd(xhci)); +} + /* Watchdog timer function for when a stop endpoint command fails to complete. * In this case, we assume the host controller is broken or dying or dead. The * host may still be completing some other events, so we have to be careful to @@ -887,7 +928,6 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg) { struct xhci_hcd *xhci; struct xhci_virt_ep *ep; - int ret, i, j; unsigned long flags; ep = (struct xhci_virt_ep *) arg; @@ -904,52 +944,22 @@ void xhci_stop_endpoint_command_watchdog(unsigned long arg) } xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n"); - xhci_warn(xhci, "Assuming host is dying, halting host.\n"); - /* Oops, HC is dead or dying or at least not responding to the stop - * endpoint command. - */ - - xhci->xhc_state |= XHCI_STATE_DYING; ep->ep_state &= ~EP_STOP_CMD_PENDING; - /* Disable interrupts from the host controller and start halting it */ - xhci_quiesce(xhci); - spin_unlock_irqrestore(&xhci->lock, flags); + xhci_halt(xhci); - ret = xhci_halt(xhci); + /* + * handle a stop endpoint cmd timeout as if host died (-ENODEV). + * In the future we could distinguish between -ENODEV and -ETIMEDOUT + * and try to recover a -ETIMEDOUT with a host controller reset + */ + xhci_hc_died(xhci); - spin_lock_irqsave(&xhci->lock, flags); - if (ret < 0) { - /* This is bad; the host is not responding to commands and it's - * not allowing itself to be halted. At least interrupts are - * disabled. If we call usb_hc_died(), it will attempt to - * disconnect all device drivers under this host. Those - * disconnect() methods will wait for all URBs to be unlinked, - * so we must complete them. - */ - xhci_warn(xhci, "Non-responsive xHCI host is not halting.\n"); - xhci_warn(xhci, "Completing active URBs anyway.\n"); - /* We could turn all TDs on the rings to no-ops. This won't - * help if the host has cached part of the ring, and is slow if - * we want to preserve the cycle bit. Skip it and hope the host - * doesn't touch the memory. - */ - } - for (i = 0; i < MAX_HC_SLOTS; i++) { - if (!xhci->devs[i]) - continue; - for (j = 0; j < 31; j++) - xhci_kill_endpoint_urbs(xhci, i, j); - } spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Calling usb_hc_died()"); - usb_hc_died(xhci_to_hcd(xhci)); - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "xHCI host controller is dead."); } - static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, struct xhci_virt_device *dev, struct xhci_ring *ep_ring, @@ -1029,6 +1039,8 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); + trace_xhci_handle_cmd_set_deq(slot_ctx); + trace_xhci_handle_cmd_set_deq_ep(ep_ctx); if (cmd_comp_code != COMP_SUCCESS) { unsigned int ep_state; @@ -1099,9 +1111,15 @@ cleanup: static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, union xhci_trb *trb, u32 cmd_comp_code) { + struct xhci_virt_device *vdev; + struct xhci_ep_ctx *ep_ctx; unsigned int ep_index; ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + vdev = xhci->devs[slot_id]; + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + trace_xhci_handle_cmd_reset_ep(ep_ctx); + /* This command will only fail if the endpoint wasn't halted, * but we don't care. */ @@ -1114,11 +1132,11 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, */ if (xhci->quirks & XHCI_RESET_EP_QUIRK) { struct xhci_command *command; + command = xhci_alloc_command(xhci, false, false, GFP_ATOMIC); - if (!command) { - xhci_warn(xhci, "WARN Cannot submit cfg ep: ENOMEM\n"); + if (!command) return; - } + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, "Queueing configure endpoint command"); xhci_queue_configure_endpoint(xhci, command, @@ -1143,10 +1161,15 @@ static void xhci_handle_cmd_enable_slot(struct xhci_hcd *xhci, int slot_id, static void xhci_handle_cmd_disable_slot(struct xhci_hcd *xhci, int slot_id) { struct xhci_virt_device *virt_dev; + struct xhci_slot_ctx *slot_ctx; virt_dev = xhci->devs[slot_id]; if (!virt_dev) return; + + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_handle_cmd_disable_slot(slot_ctx); + if (xhci->quirks & XHCI_EP_LIMIT_QUIRK) /* Delete default control endpoint resources */ xhci_free_device_endpoint_resources(xhci, virt_dev, true); @@ -1158,6 +1181,7 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, { struct xhci_virt_device *virt_dev; struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; unsigned int ep_index; unsigned int ep_state; u32 add_flags, drop_flags; @@ -1182,6 +1206,9 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, /* Input ctx add_flags are the endpoint index plus one */ ep_index = xhci_last_valid_endpoint(add_flags) - 1; + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, ep_index); + trace_xhci_handle_cmd_config_ep(ep_ctx); + /* A usb_set_interface() call directly after clearing a halted * condition may race on this quirky hardware. Not worth * worrying about, since this is prototype hardware. Not sure @@ -1206,9 +1233,26 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, return; } +static void xhci_handle_cmd_addr_dev(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; + + vdev = xhci->devs[slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_handle_cmd_addr_dev(slot_ctx); +} + static void xhci_handle_cmd_reset_dev(struct xhci_hcd *xhci, int slot_id, struct xhci_event_cmd *event) { + struct xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; + + vdev = xhci->devs[slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_handle_cmd_reset_dev(slot_ctx); + xhci_dbg(xhci, "Completed reset device command.\n"); if (!xhci->devs[slot_id]) xhci_warn(xhci, "Reset device command completion " @@ -1250,7 +1294,6 @@ void xhci_cleanup_command_queue(struct xhci_hcd *xhci) void xhci_handle_command_timeout(struct work_struct *work) { struct xhci_hcd *xhci; - int ret; unsigned long flags; u64 hw_ring_state; @@ -1271,22 +1314,17 @@ void xhci_handle_command_timeout(struct work_struct *work) /* Make sure command ring is running before aborting it */ hw_ring_state = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + if (hw_ring_state == ~(u64)0) { + xhci_hc_died(xhci); + goto time_out_completed; + } + if ((xhci->cmd_ring_state & CMD_RING_STATE_RUNNING) && (hw_ring_state & CMD_RING_RUNNING)) { /* Prevent new doorbell, and start command abort */ xhci->cmd_ring_state = CMD_RING_STATE_ABORTED; xhci_dbg(xhci, "Command timeout\n"); - ret = xhci_abort_cmd_ring(xhci, flags); - if (unlikely(ret == -ESHUTDOWN)) { - xhci_err(xhci, "Abort command ring failed\n"); - xhci_cleanup_command_queue(xhci); - spin_unlock_irqrestore(&xhci->lock, flags); - usb_hc_died(xhci_to_hcd(xhci)->primary_hcd); - xhci_dbg(xhci, "xHCI host controller is dead.\n"); - - return; - } - + xhci_abort_cmd_ring(xhci, flags); goto time_out_completed; } @@ -1384,6 +1422,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, case TRB_EVAL_CONTEXT: break; case TRB_ADDR_DEV: + xhci_handle_cmd_addr_dev(xhci, slot_id); break; case TRB_STOP_RING: WARN_ON(slot_id != TRB_TO_SLOT_ID( @@ -2243,7 +2282,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); xdev = xhci->devs[slot_id]; if (!xdev) { - xhci_err(xhci, "ERROR Transfer event pointed to bad slot\n"); + xhci_err(xhci, "ERROR Transfer event pointed to bad slot %u\n", + slot_id); xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", (unsigned long long) xhci_trb_virt_to_dma( xhci->event_ring->deq_seg, @@ -2252,8 +2292,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, upper_32_bits(le64_to_cpu(event->buffer)), le32_to_cpu(event->transfer_len), le32_to_cpu(event->flags)); - xhci_dbg(xhci, "Event ring:\n"); - xhci_debug_segment(xhci, xhci->event_ring->deq_seg); return -ENODEV; } @@ -2263,8 +2301,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); if (!ep_ring || GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { - xhci_err(xhci, "ERROR Transfer event for disabled endpoint " - "or incorrect stream ring\n"); + xhci_err(xhci, + "ERROR Transfer event for disabled endpoint slot %u ep %u or incorrect stream ring\n", + slot_id, ep_index); xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", (unsigned long long) xhci_trb_virt_to_dma( xhci->event_ring->deq_seg, @@ -2273,8 +2312,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, upper_32_bits(le64_to_cpu(event->buffer)), le32_to_cpu(event->transfer_len), le32_to_cpu(event->flags)); - xhci_dbg(xhci, "Event ring:\n"); - xhci_debug_segment(xhci, xhci->event_ring->deq_seg); return -ENODEV; } @@ -2298,45 +2335,62 @@ static int handle_tx_event(struct xhci_hcd *xhci, trb_comp_code = COMP_SHORT_PACKET; else xhci_warn_ratelimited(xhci, - "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n"); + "WARN Successful completion on short TX for slot %u ep %u: needs XHCI_TRUST_TX_LENGTH quirk?\n", + slot_id, ep_index); case COMP_SHORT_PACKET: break; case COMP_STOPPED: - xhci_dbg(xhci, "Stopped on Transfer TRB\n"); + xhci_dbg(xhci, "Stopped on Transfer TRB for slot %u ep %u\n", + slot_id, ep_index); break; case COMP_STOPPED_LENGTH_INVALID: - xhci_dbg(xhci, "Stopped on No-op or Link TRB\n"); + xhci_dbg(xhci, + "Stopped on No-op or Link TRB for slot %u ep %u\n", + slot_id, ep_index); break; case COMP_STOPPED_SHORT_PACKET: - xhci_dbg(xhci, "Stopped with short packet transfer detected\n"); + xhci_dbg(xhci, + "Stopped with short packet transfer detected for slot %u ep %u\n", + slot_id, ep_index); break; case COMP_STALL_ERROR: - xhci_dbg(xhci, "Stalled endpoint\n"); + xhci_dbg(xhci, "Stalled endpoint for slot %u ep %u\n", slot_id, + ep_index); ep->ep_state |= EP_HALTED; status = -EPIPE; break; case COMP_TRB_ERROR: - xhci_warn(xhci, "WARN: TRB error on endpoint\n"); + xhci_warn(xhci, + "WARN: TRB error for slot %u ep %u on endpoint\n", + slot_id, ep_index); status = -EILSEQ; break; case COMP_SPLIT_TRANSACTION_ERROR: case COMP_USB_TRANSACTION_ERROR: - xhci_dbg(xhci, "Transfer error on endpoint\n"); + xhci_dbg(xhci, "Transfer error for slot %u ep %u on endpoint\n", + slot_id, ep_index); status = -EPROTO; break; case COMP_BABBLE_DETECTED_ERROR: - xhci_dbg(xhci, "Babble error on endpoint\n"); + xhci_dbg(xhci, "Babble error for slot %u ep %u on endpoint\n", + slot_id, ep_index); status = -EOVERFLOW; break; case COMP_DATA_BUFFER_ERROR: - xhci_warn(xhci, "WARN: HC couldn't access mem fast enough\n"); + xhci_warn(xhci, + "WARN: HC couldn't access mem fast enough for slot %u ep %u\n", + slot_id, ep_index); status = -ENOSR; break; case COMP_BANDWIDTH_OVERRUN_ERROR: - xhci_warn(xhci, "WARN: bandwidth overrun event on endpoint\n"); + xhci_warn(xhci, + "WARN: bandwidth overrun event for slot %u ep %u on endpoint\n", + slot_id, ep_index); break; case COMP_ISOCH_BUFFER_OVERRUN: - xhci_warn(xhci, "WARN: buffer overrun event on endpoint\n"); + xhci_warn(xhci, + "WARN: buffer overrun event for slot %u ep %u on endpoint", + slot_id, ep_index); break; case COMP_RING_UNDERRUN: /* @@ -2360,7 +2414,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, ep_index); goto cleanup; case COMP_INCOMPATIBLE_DEVICE_ERROR: - xhci_warn(xhci, "WARN: detect an incompatible device"); + xhci_warn(xhci, + "WARN: detect an incompatible device for slot %u ep %u", + slot_id, ep_index); status = -EPROTO; break; case COMP_MISSED_SERVICE_ERROR: @@ -2371,19 +2427,24 @@ static int handle_tx_event(struct xhci_hcd *xhci, * short transfer when process the ep_ring next time. */ ep->skip = true; - xhci_dbg(xhci, "Miss service interval error, set skip flag\n"); + xhci_dbg(xhci, + "Miss service interval error for slot %u ep %u, set skip flag\n", + slot_id, ep_index); goto cleanup; case COMP_NO_PING_RESPONSE_ERROR: ep->skip = true; - xhci_dbg(xhci, "No Ping response error, Skip one Isoc TD\n"); + xhci_dbg(xhci, + "No Ping response error for slot %u ep %u, Skip one Isoc TD\n", + slot_id, ep_index); goto cleanup; default: if (xhci_is_vendor_info_code(xhci, trb_comp_code)) { status = 0; break; } - xhci_warn(xhci, "ERROR Unknown event condition %u, HC probably busted\n", - trb_comp_code); + xhci_warn(xhci, + "ERROR Unknown event condition %u for slot %u ep %u , HC probably busted\n", + trb_comp_code, slot_id, ep_index); goto cleanup; } @@ -2402,15 +2463,11 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n", TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), ep_index); - xhci_dbg(xhci, "Event TRB with TRB type ID %u\n", - (le32_to_cpu(event->flags) & - TRB_TYPE_BITMASK)>>10); - xhci_print_trb_offsets(xhci, (union xhci_trb *) event); } if (ep->skip) { ep->skip = false; - xhci_dbg(xhci, "td_list is empty while skip " - "flag set. Clear skip flag.\n"); + xhci_dbg(xhci, "td_list is empty while skip flag set. Clear skip flag for slot %u ep %u.\n", + slot_id, ep_index); } goto cleanup; } @@ -2418,8 +2475,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, /* We've skipped all the TDs on the ep ring when ep->skip set */ if (ep->skip && td_num == 0) { ep->skip = false; - xhci_dbg(xhci, "All tds on the ep_ring skipped. " - "Clear skip flag.\n"); + xhci_dbg(xhci, "All tds on the ep_ring skipped. Clear skip flag for slot %u ep %u.\n", + slot_id, ep_index); goto cleanup; } @@ -2478,7 +2535,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, ep_ring->last_td_was_short = false; if (ep->skip) { - xhci_dbg(xhci, "Found td. Clear skip flag.\n"); + xhci_dbg(xhci, + "Found td. Clear skip flag for slot %u ep %u.\n", + slot_id, ep_index); ep->skip = false; } @@ -2495,7 +2554,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, * the TD. */ if (trb_is_noop(ep_trb)) { - xhci_dbg(xhci, "ep_trb is a no-op TRB. Skip it\n"); + xhci_dbg(xhci, + "ep_trb is a no-op TRB. Skip it for slot %u ep %u\n", + slot_id, ep_index); goto cleanup; } @@ -2623,7 +2684,8 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) spin_lock(&xhci->lock); /* Check if the xHC generated the interrupt, or the irq is shared */ status = readl(&xhci->op_regs->status); - if (status == 0xffffffff) { + if (status == ~(u32)0) { + xhci_hc_died(xhci); ret = IRQ_HANDLED; goto out; } @@ -3942,10 +4004,8 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, /* This function gets called from contexts where it cannot sleep */ cmd = xhci_alloc_command(xhci, false, false, GFP_ATOMIC); - if (!cmd) { - xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr: ENOMEM\n"); + if (!cmd) return; - } ep->queued_deq_seg = deq_state->new_deq_seg; ep->queued_deq_ptr = deq_state->new_deq_ptr; diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index 1ac2cdf8eece..8ce96de10e8a 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -231,6 +231,7 @@ DECLARE_EVENT_CLASS(xhci_log_urb, __field(int, epnum) __field(int, dir_in) __field(int, type) + __field(int, slot_id) ), TP_fast_assign( __entry->urb = urb; @@ -245,8 +246,9 @@ DECLARE_EVENT_CLASS(xhci_log_urb, __entry->epnum = usb_endpoint_num(&urb->ep->desc); __entry->dir_in = usb_endpoint_dir_in(&urb->ep->desc); __entry->type = usb_endpoint_type(&urb->ep->desc); + __entry->slot_id = urb->dev->slot_id; ), - TP_printk("ep%d%s-%s: urb %p pipe %u length %d/%d sgs %d/%d stream %d flags %08x", + TP_printk("ep%d%s-%s: urb %p pipe %u slot %d length %d/%d sgs %d/%d stream %d flags %08x", __entry->epnum, __entry->dir_in ? "in" : "out", ({ char *s; switch (__entry->type) { @@ -264,8 +266,8 @@ DECLARE_EVENT_CLASS(xhci_log_urb, break; default: s = "UNKNOWN"; - } s; }), __entry->urb, __entry->pipe, __entry->actual, - __entry->length, __entry->num_mapped_sgs, + } s; }), __entry->urb, __entry->pipe, __entry->slot_id, + __entry->actual, __entry->length, __entry->num_mapped_sgs, __entry->num_sgs, __entry->stream, __entry->flags ) ); @@ -285,6 +287,172 @@ DEFINE_EVENT(xhci_log_urb, xhci_urb_dequeue, TP_ARGS(urb) ); +DECLARE_EVENT_CLASS(xhci_log_ep_ctx, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u64, deq) + __field(u32, tx_info) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->ep_info); + __entry->info2 = le32_to_cpu(ctx->ep_info2); + __entry->deq = le64_to_cpu(ctx->deq); + __entry->tx_info = le32_to_cpu(ctx->tx_info); + ), + TP_printk("%s", xhci_decode_ep_context(__entry->info, + __entry->info2, __entry->deq, __entry->tx_info) + ) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_stop_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_set_deq_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_reset_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_config_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_slot_ctx, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u32, tt_info) + __field(u32, state) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->dev_info); + __entry->info2 = le32_to_cpu(ctx->dev_info2); + __entry->tt_info = le64_to_cpu(ctx->tt_info); + __entry->state = le32_to_cpu(ctx->dev_state); + ), + TP_printk("%s", xhci_decode_slot_context(__entry->info, + __entry->info2, __entry->tt_info, + __entry->state) + ) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_alloc_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_free_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_disable_slot, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_discover_or_reset_device, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_setup_device_slot, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_addr_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_reset_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_set_deq, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_ring, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring), + TP_STRUCT__entry( + __field(u32, type) + __field(void *, ring) + __field(dma_addr_t, enq) + __field(dma_addr_t, deq) + __field(dma_addr_t, enq_seg) + __field(dma_addr_t, deq_seg) + __field(unsigned int, num_segs) + __field(unsigned int, stream_id) + __field(unsigned int, cycle_state) + __field(unsigned int, num_trbs_free) + __field(unsigned int, bounce_buf_len) + ), + TP_fast_assign( + __entry->ring = ring; + __entry->type = ring->type; + __entry->num_segs = ring->num_segs; + __entry->stream_id = ring->stream_id; + __entry->enq_seg = ring->enq_seg->dma; + __entry->deq_seg = ring->deq_seg->dma; + __entry->cycle_state = ring->cycle_state; + __entry->num_trbs_free = ring->num_trbs_free; + __entry->bounce_buf_len = ring->bounce_buf_len; + __entry->enq = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + __entry->deq = xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); + ), + TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d free_trbs %d bounce %d cycle %d", + xhci_ring_type_string(__entry->type), __entry->ring, + &__entry->enq, &__entry->enq_seg, + &__entry->deq, &__entry->deq_seg, + __entry->num_segs, + __entry->stream_id, + __entry->num_trbs_free, + __entry->bounce_buf_len, + __entry->cycle_state + ) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_alloc, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_free, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_expansion, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_inc_enq, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_inc_deq, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); #endif /* __XHCI_TRACE_H */ /* this part must be outside header guard */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 953fd8f62df0..2d1310220832 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -125,7 +125,7 @@ int xhci_halt(struct xhci_hcd *xhci) /* * Set the run bit and wait for the host to be running. */ -static int xhci_start(struct xhci_hcd *xhci) +int xhci_start(struct xhci_hcd *xhci) { u32 temp; int ret; @@ -216,31 +216,21 @@ int xhci_reset(struct xhci_hcd *xhci) return ret; } -#ifdef CONFIG_PCI -static int xhci_free_msi(struct xhci_hcd *xhci) -{ - int i; - - if (!xhci->msix_entries) - return -EINVAL; - - for (i = 0; i < xhci->msix_count; i++) - if (xhci->msix_entries[i].vector) - free_irq(xhci->msix_entries[i].vector, - xhci_to_hcd(xhci)); - return 0; -} +#ifdef CONFIG_USB_PCI /* * Set up MSI */ static int xhci_setup_msi(struct xhci_hcd *xhci) { int ret; + /* + * TODO:Check with MSI Soc for sysdev + */ struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); - ret = pci_enable_msi(pdev); - if (ret) { + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (ret < 0) { xhci_dbg_trace(xhci, trace_xhci_dbg_init, "failed to allocate MSI entry"); return ret; @@ -251,35 +241,13 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) if (ret) { xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable MSI interrupt"); - pci_disable_msi(pdev); + pci_free_irq_vectors(pdev); } return ret; } /* - * Free IRQs - * free all IRQs request - */ -static void xhci_free_irq(struct xhci_hcd *xhci) -{ - struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); - int ret; - - /* return if using legacy interrupt */ - if (xhci_to_hcd(xhci)->irq > 0) - return; - - ret = xhci_free_msi(xhci); - if (!ret) - return; - if (pdev->irq > 0) - free_irq(pdev->irq, xhci_to_hcd(xhci)); - - return; -} - -/* * Set up MSI-X */ static int xhci_setup_msix(struct xhci_hcd *xhci) @@ -298,28 +266,17 @@ static int xhci_setup_msix(struct xhci_hcd *xhci) xhci->msix_count = min(num_online_cpus() + 1, HCS_MAX_INTRS(xhci->hcs_params1)); - xhci->msix_entries = - kmalloc((sizeof(struct msix_entry))*xhci->msix_count, - GFP_KERNEL); - if (!xhci->msix_entries) - return -ENOMEM; - - for (i = 0; i < xhci->msix_count; i++) { - xhci->msix_entries[i].entry = i; - xhci->msix_entries[i].vector = 0; - } - - ret = pci_enable_msix_exact(pdev, xhci->msix_entries, xhci->msix_count); - if (ret) { + ret = pci_alloc_irq_vectors(pdev, xhci->msix_count, xhci->msix_count, + PCI_IRQ_MSIX); + if (ret < 0) { xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Failed to enable MSI-X"); - goto free_entries; + return ret; } for (i = 0; i < xhci->msix_count; i++) { - ret = request_irq(xhci->msix_entries[i].vector, - xhci_msi_irq, - 0, "xhci_hcd", xhci_to_hcd(xhci)); + ret = request_irq(pci_irq_vector(pdev, i), xhci_msi_irq, 0, + "xhci_hcd", xhci_to_hcd(xhci)); if (ret) goto disable_msix; } @@ -329,11 +286,9 @@ static int xhci_setup_msix(struct xhci_hcd *xhci) disable_msix: xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable MSI-X interrupt"); - xhci_free_irq(xhci); - pci_disable_msix(pdev); -free_entries: - kfree(xhci->msix_entries); - xhci->msix_entries = NULL; + while (--i >= 0) + free_irq(pci_irq_vector(pdev, i), xhci_to_hcd(xhci)); + pci_free_irq_vectors(pdev); return ret; } @@ -346,27 +301,33 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci) if (xhci->quirks & XHCI_PLAT) return; - xhci_free_irq(xhci); + /* return if using legacy interrupt */ + if (hcd->irq > 0) + return; + + if (hcd->msix_enabled) { + int i; - if (xhci->msix_entries) { - pci_disable_msix(pdev); - kfree(xhci->msix_entries); - xhci->msix_entries = NULL; + for (i = 0; i < xhci->msix_count; i++) + free_irq(pci_irq_vector(pdev, i), xhci_to_hcd(xhci)); } else { - pci_disable_msi(pdev); + free_irq(pci_irq_vector(pdev, 0), xhci_to_hcd(xhci)); } + pci_free_irq_vectors(pdev); hcd->msix_enabled = 0; - return; } static void __maybe_unused xhci_msix_sync_irqs(struct xhci_hcd *xhci) { - int i; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + + if (hcd->msix_enabled) { + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int i; - if (xhci->msix_entries) { for (i = 0; i < xhci->msix_count; i++) - synchronize_irq(xhci->msix_entries[i].vector); + synchronize_irq(pci_irq_vector(pdev, i)); } } @@ -539,7 +500,7 @@ static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci) * device contexts (?), set up a command ring segment (or two?), create event * ring (one for now). */ -int xhci_init(struct usb_hcd *hcd) +static int xhci_init(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); int retval = 0; @@ -619,16 +580,10 @@ int xhci_run(struct usb_hcd *hcd) if (ret) return ret; - xhci_dbg(xhci, "Command ring memory map follows:\n"); - xhci_debug_ring(xhci, xhci->cmd_ring); - xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); xhci_dbg_cmd_ptrs(xhci); xhci_dbg(xhci, "ERST memory map follows:\n"); xhci_dbg_erst(xhci, &xhci->erst); - xhci_dbg(xhci, "Event ring:\n"); - xhci_debug_ring(xhci, xhci->event_ring); - xhci_dbg_ring_ptrs(xhci, xhci->event_ring); temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); temp_64 &= ~ERST_PTR_MASK; xhci_dbg_trace(xhci, trace_xhci_dbg_init, @@ -661,9 +616,11 @@ int xhci_run(struct usb_hcd *hcd) if (xhci->quirks & XHCI_NEC_HOST) { struct xhci_command *command; + command = xhci_alloc_command(xhci, false, false, GFP_KERNEL); if (!command) return -ENOMEM; + xhci_queue_vendor_command(xhci, command, 0, 0, 0, TRB_TYPE(TRB_NEC_GET_FW)); } @@ -682,28 +639,28 @@ EXPORT_SYMBOL_GPL(xhci_run); * Disable device contexts, disable IRQs, and quiesce the HC. * Reset the HC, finish any completed transactions, and cleanup memory. */ -void xhci_stop(struct usb_hcd *hcd) +static void xhci_stop(struct usb_hcd *hcd) { u32 temp; struct xhci_hcd *xhci = hcd_to_xhci(hcd); mutex_lock(&xhci->mutex); - if (!(xhci->xhc_state & XHCI_STATE_HALTED)) { - spin_lock_irq(&xhci->lock); - - xhci->xhc_state |= XHCI_STATE_HALTED; - xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; - xhci_halt(xhci); - xhci_reset(xhci); - spin_unlock_irq(&xhci->lock); - } - + /* Only halt host and free memory after both hcds are removed */ if (!usb_hcd_is_primary_hcd(hcd)) { + /* usb core will free this hcd shortly, unset pointer */ + xhci->shared_hcd = NULL; mutex_unlock(&xhci->mutex); return; } + spin_lock_irq(&xhci->lock); + xhci->xhc_state |= XHCI_STATE_HALTED; + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + xhci_halt(xhci); + xhci_reset(xhci); + spin_unlock_irq(&xhci->lock); + xhci_cleanup_msix(xhci); /* Deleting Compliance Mode Recovery Timer */ @@ -721,7 +678,7 @@ void xhci_stop(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Disabling event ring interrupts"); temp = readl(&xhci->op_regs->status); - writel(temp & ~STS_EINT, &xhci->op_regs->status); + writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); temp = readl(&xhci->ir_set->irq_pending); writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); xhci_print_ir_set(xhci, 0); @@ -743,12 +700,12 @@ void xhci_stop(struct usb_hcd *hcd) * * This will only ever be called with the main usb_hcd (the USB3 roothub). */ -void xhci_shutdown(struct usb_hcd *hcd) +static void xhci_shutdown(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); if (xhci->quirks & XHCI_SPURIOUS_REBOOT) - usb_disable_xhci_ports(to_pci_dev(hcd->self.controller)); + usb_disable_xhci_ports(to_pci_dev(hcd->self.sysdev)); spin_lock_irq(&xhci->lock); xhci_halt(xhci); @@ -765,7 +722,7 @@ void xhci_shutdown(struct usb_hcd *hcd) /* Yet another workaround for spurious wakeups at shutdown with HSW */ if (xhci->quirks & XHCI_SPURIOUS_WAKEUP) - pci_set_power_state(to_pci_dev(hcd->self.controller), PCI_D3hot); + pci_set_power_state(to_pci_dev(hcd->self.sysdev), PCI_D3hot); } #ifdef CONFIG_PM @@ -1054,7 +1011,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "// Disabling event ring interrupts\n"); temp = readl(&xhci->op_regs->status); - writel(temp & ~STS_EINT, &xhci->op_regs->status); + writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); temp = readl(&xhci->ir_set->irq_pending); writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); xhci_print_ir_set(xhci, 0); @@ -1176,7 +1133,7 @@ unsigned int xhci_get_endpoint_address(unsigned int ep_index) * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is * bit 1, etc. */ -unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) +static unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) { return 1 << (xhci_get_endpoint_index(desc) + 1); } @@ -1185,7 +1142,7 @@ unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is * bit 1, etc. */ -unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index) +static unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index) { return 1 << (ep_index + 1); } @@ -1306,11 +1263,6 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id, ctrl_ctx->add_flags = cpu_to_le32(EP0_FLAG); ctrl_ctx->drop_flags = 0; - xhci_dbg(xhci, "Slot %d input context\n", slot_id); - xhci_dbg_ctx(xhci, command->in_ctx, ep_index); - xhci_dbg(xhci, "Slot %d output context\n", slot_id); - xhci_dbg_ctx(xhci, out_ctx, ep_index); - ret = xhci_configure_endpoint(xhci, urb->dev, command, true, false); @@ -1329,7 +1281,7 @@ command_cleanup: * non-error returns are a promise to giveback() the urb later * we drop ownership so next owner (or urb unlink) can get it */ -int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); unsigned long flags; @@ -1465,7 +1417,7 @@ free_priv: * Note that this function can be called in any context, or so says * usb_hcd_unlink_urb() */ -int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { unsigned long flags; int ret, i; @@ -1501,10 +1453,16 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) if (!ep || !ep_ring) goto err_giveback; + /* If xHC is dead take it down and return ALL URBs in xhci_hc_died() */ temp = readl(&xhci->op_regs->status); - if (temp == 0xffffffff || (xhci->xhc_state & XHCI_STATE_HALTED)) { + if (temp == ~(u32)0 || xhci->xhc_state & XHCI_STATE_DYING) { + xhci_hc_died(xhci); + goto done; + } + + if (xhci->xhc_state & XHCI_STATE_HALTED) { xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "HW died, freeing TD."); + "HC halted, freeing TD manually."); for (i = urb_priv->num_tds_done; i < urb_priv->num_tds; i++) { @@ -1576,7 +1534,7 @@ err_giveback: * disabled, so there's no need for mutual exclusion to protect * the xhci->devs[slot_id] structure. */ -int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, +static int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep) { struct xhci_hcd *xhci; @@ -1659,7 +1617,7 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, * configuration or alt setting is installed in the device, so there's no need * for mutual exclusion to protect the xhci->devs[slot_id] structure. */ -int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, +static int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep) { struct xhci_hcd *xhci; @@ -1852,7 +1810,6 @@ static int xhci_evaluate_context_result(struct xhci_hcd *xhci, struct usb_device *udev, u32 *cmd_status) { int ret; - struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; switch (*cmd_status) { case COMP_COMMAND_ABORTED: @@ -1873,7 +1830,6 @@ static int xhci_evaluate_context_result(struct xhci_hcd *xhci, case COMP_CONTEXT_STATE_ERROR: dev_warn(&udev->dev, "WARN: invalid context state for evaluate context command.\n"); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 1); ret = -EINVAL; break; case COMP_INCOMPATIBLE_DEVICE_ERROR: @@ -2330,7 +2286,7 @@ static unsigned int xhci_get_ss_bw_consumed(struct xhci_bw_info *ep_bw) } -void xhci_drop_ep_from_interval_table(struct xhci_hcd *xhci, +static void xhci_drop_ep_from_interval_table(struct xhci_hcd *xhci, struct xhci_bw_info *ep_bw, struct xhci_interval_bw_table *bw_table, struct usb_device *udev, @@ -2595,6 +2551,12 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, return -EINVAL; spin_lock_irqsave(&xhci->lock, flags); + + if (xhci->xhc_state & XHCI_STATE_DYING) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -ESHUTDOWN; + } + virt_dev = xhci->devs[udev->slot_id]; ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); @@ -2689,7 +2651,7 @@ static void xhci_check_bw_drop_ep_streams(struct xhci_hcd *xhci, * else should be touching the xhci->devs[slot_id] structure, so we * don't need to take the xhci->lock for manipulating that. */ -int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) { int i; int ret = 0; @@ -2746,9 +2708,6 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) break; } } - xhci_dbg(xhci, "New Input Control Context:\n"); - xhci_dbg_ctx(xhci, virt_dev->in_ctx, - LAST_CTX_TO_EP_NUM(le32_to_cpu(slot_ctx->dev_info))); ret = xhci_configure_endpoint(xhci, udev, command, false, false); @@ -2756,10 +2715,6 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) /* Callee should call reset_bandwidth() */ goto command_cleanup; - xhci_dbg(xhci, "Output context after successful config ep cmd:\n"); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, - LAST_CTX_TO_EP_NUM(le32_to_cpu(slot_ctx->dev_info))); - /* Free any rings that were dropped, but not changed. */ for (i = 1; i < 31; i++) { if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) && @@ -2793,7 +2748,7 @@ command_cleanup: return ret; } -void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +static void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci; struct xhci_virt_device *virt_dev; @@ -2826,9 +2781,6 @@ static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, ctrl_ctx->drop_flags = cpu_to_le32(drop_flags); xhci_slot_copy(xhci, in_ctx, out_ctx); ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); - - xhci_dbg(xhci, "Input Context:\n"); - xhci_dbg_ctx(xhci, in_ctx, xhci_last_valid_endpoint(add_flags)); } static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, @@ -2919,7 +2871,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, * Context: in_interrupt */ -void xhci_endpoint_reset(struct usb_hcd *hcd, +static void xhci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { struct xhci_hcd *xhci; @@ -3095,7 +3047,7 @@ static u32 xhci_calculate_no_streams_bitmask(struct xhci_hcd *xhci, * hardware or endpoints claim they can't support the number of requested * stream IDs. */ -int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, +static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint **eps, unsigned int num_eps, unsigned int num_streams, gfp_t mem_flags) { @@ -3129,10 +3081,9 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, } config_cmd = xhci_alloc_command(xhci, true, true, mem_flags); - if (!config_cmd) { - xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); + if (!config_cmd) return -ENOMEM; - } + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -3259,7 +3210,7 @@ cleanup: * Modify the endpoint context state, submit a configure endpoint command, * and free all endpoint rings for streams if that completes successfully. */ -int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, +static int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint **eps, unsigned int num_eps, gfp_t mem_flags) { @@ -3391,7 +3342,8 @@ void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, * re-initialization during S3/S4. In this case, call xhci_alloc_dev() to * re-allocate the device. */ -int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + struct usb_device *udev) { int ret, i; unsigned long flags; @@ -3443,6 +3395,8 @@ int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) SLOT_STATE_DISABLED) return 0; + trace_xhci_discover_or_reset_device(slot_ctx); + xhci_dbg(xhci, "Resetting device with slot ID %u\n", slot_id); /* Allocate the command structure that holds the struct completion. * Assume we're in process context, since the normal device reset @@ -3539,9 +3493,6 @@ int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev) } /* If necessary, update the number of active TTs on this root port */ xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); - - xhci_dbg(xhci, "Output context after successful reset device cmd:\n"); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, last_freed_endpoint); ret = 0; command_cleanup: @@ -3554,12 +3505,11 @@ command_cleanup: * disconnected, and all traffic has been stopped and the endpoints have been * disabled. Free any HC data structures associated with that device. */ -void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) +static void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_virt_device *virt_dev; - unsigned long flags; - u32 state; + struct xhci_slot_ctx *slot_ctx; int i, ret; struct xhci_command *command; @@ -3587,6 +3537,8 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) } virt_dev = xhci->devs[udev->slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_free_dev(slot_ctx); /* Stop any wayward timer functions (which may grab the lock) */ for (i = 0; i < 31; i++) { @@ -3594,30 +3546,50 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) del_timer_sync(&virt_dev->eps[i].stop_cmd_timer); } + xhci_disable_slot(xhci, command, udev->slot_id); + /* + * Event command completion handler will free any data structures + * associated with the slot. XXX Can free sleep? + */ +} + +int xhci_disable_slot(struct xhci_hcd *xhci, struct xhci_command *command, + u32 slot_id) +{ + unsigned long flags; + u32 state; + int ret = 0; + struct xhci_virt_device *virt_dev; + + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return -EINVAL; + if (!command) + command = xhci_alloc_command(xhci, false, false, GFP_KERNEL); + if (!command) + return -ENOMEM; + spin_lock_irqsave(&xhci->lock, flags); /* Don't disable the slot if the host controller is dead. */ state = readl(&xhci->op_regs->status); if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) || (xhci->xhc_state & XHCI_STATE_HALTED)) { - xhci_free_virt_device(xhci, udev->slot_id); + xhci_free_virt_device(xhci, slot_id); spin_unlock_irqrestore(&xhci->lock, flags); kfree(command); - return; + return ret; } - if (xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, - udev->slot_id)) { + ret = xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, + slot_id); + if (ret) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); - return; + return ret; } xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); - - /* - * Event command completion handler will free any data structures - * associated with the slot. XXX Can free sleep? - */ + return ret; } /* @@ -3650,6 +3622,8 @@ static int xhci_reserve_host_control_ep_resources(struct xhci_hcd *xhci) int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; unsigned long flags; int ret, slot_id; struct xhci_command *command; @@ -3705,6 +3679,10 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); goto disable_slot; } + vdev = xhci->devs[slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_alloc_dev(slot_ctx); + udev->slot_id = slot_id; #ifndef CONFIG_USB_DEFAULT_PERSIST @@ -3724,15 +3702,10 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) disable_slot: /* Disable slot, if we can do it without mem alloc */ - spin_lock_irqsave(&xhci->lock, flags); kfree(command->completion); command->completion = NULL; command->status = 0; - if (!xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, - udev->slot_id)) - xhci_ring_cmd_db(xhci); - spin_unlock_irqrestore(&xhci->lock, flags); - return 0; + return xhci_disable_slot(xhci, command, udev->slot_id); } /* @@ -3779,9 +3752,10 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, ret = -EINVAL; goto out; } + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_setup_device_slot(slot_ctx); if (setup == SETUP_CONTEXT_ONLY) { - slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); if (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)) == SLOT_STATE_DEFAULT) { xhci_dbg(xhci, "Slot already in default state\n"); @@ -3818,8 +3792,6 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); ctrl_ctx->drop_flags = 0; - xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); trace_xhci_address_ctx(xhci, virt_dev->in_ctx, le32_to_cpu(slot_ctx->dev_info) >> 27); @@ -3872,8 +3844,6 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, xhci_err(xhci, "ERROR: unexpected setup %s command completion code 0x%x.\n", act, command->status); - xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 2); trace_xhci_address_ctx(xhci, virt_dev->out_ctx, 1); ret = -EINVAL; break; @@ -3892,17 +3862,12 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg_trace(xhci, trace_xhci_dbg_address, "Output Context DMA address = %#08llx", (unsigned long long)virt_dev->out_ctx->dma); - xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); trace_xhci_address_ctx(xhci, virt_dev->in_ctx, le32_to_cpu(slot_ctx->dev_info) >> 27); - xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 2); /* * USB core uses address 1 for the roothubs, so we add one to the * address given back to us by the HC. */ - slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); trace_xhci_address_ctx(xhci, virt_dev->out_ctx, le32_to_cpu(slot_ctx->dev_info) >> 27); /* Zero the input context control for later use */ @@ -3921,12 +3886,12 @@ out: return ret; } -int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) { return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS); } -int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev) { return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY); } @@ -4003,14 +3968,10 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci, xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, "Set up evaluate context for LPM MEL change."); - xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, command->in_ctx, 0); /* Issue and wait for the evaluate context command. */ ret = xhci_configure_endpoint(xhci, udev, command, true, true); - xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); if (!ret) { spin_lock_irqsave(&xhci->lock, flags); @@ -4083,7 +4044,7 @@ static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev) return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm); } -int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, +static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, struct usb_device *udev, int enable) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -4207,7 +4168,7 @@ static int xhci_check_usb2_port_capability(struct xhci_hcd *xhci, int port, return 0; } -int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); int portnum = udev->portnum - 1; @@ -4616,7 +4577,7 @@ static int calculate_max_exit_latency(struct usb_device *udev, } /* Returns the USB3 hub-encoded value for the U1/U2 timeout. */ -int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, +static int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { struct xhci_hcd *xhci; @@ -4647,7 +4608,7 @@ int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, return hub_encoded_timeout; } -int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, +static int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { struct xhci_hcd *xhci; @@ -4663,24 +4624,24 @@ int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, } #else /* CONFIG_PM */ -int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, +static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, struct usb_device *udev, int enable) { return 0; } -int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +static int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { return 0; } -int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, +static int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { return USB3_LPM_DISABLED; } -int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, +static int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { return 0; @@ -4692,7 +4653,7 @@ int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, /* Once a hub descriptor is fetched for a device, we need to update the xHC's * internal data structures for the device. */ -int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, +static int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, struct usb_tt *tt, gfp_t mem_flags) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -4713,11 +4674,11 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, xhci_warn(xhci, "Cannot update hub desc for unknown device.\n"); return -EINVAL; } + config_cmd = xhci_alloc_command(xhci, true, true, mem_flags); - if (!config_cmd) { - xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); + if (!config_cmd) return -ENOMEM; - } + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -4778,8 +4739,6 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, xhci_dbg(xhci, "Set up %s for hub device.\n", (xhci->hci_version > 0x95) ? "configure endpoint" : "evaluate context"); - xhci_dbg(xhci, "Slot %u Input Context:\n", hdev->slot_id); - xhci_dbg_ctx(xhci, config_cmd->in_ctx, 0); /* Issue and wait for the configure endpoint or * evaluate context command. @@ -4791,14 +4750,11 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, ret = xhci_configure_endpoint(xhci, hdev, config_cmd, true, false); - xhci_dbg(xhci, "Slot %u Output Context:\n", hdev->slot_id); - xhci_dbg_ctx(xhci, vdev->out_ctx, 0); - xhci_free_command(xhci, config_cmd); return ret; } -int xhci_get_frame(struct usb_hcd *hcd) +static int xhci_get_frame(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); /* EHCI mods by the periodic size. Why? */ @@ -4808,7 +4764,11 @@ int xhci_get_frame(struct usb_hcd *hcd) int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) { struct xhci_hcd *xhci; - struct device *dev = hcd->self.controller; + /* + * TODO: Check with DWC3 clients for sysdev according to + * quirks + */ + struct device *dev = hcd->self.sysdev; int retval; /* Accept arbitrarily long scatter-gather lists */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index da3eb695fe54..73a28a986d5e 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -425,6 +425,7 @@ struct xhci_op_regs { #define PORT_L1DS_MASK (0xff << 8) #define PORT_L1DS(p) (((p) & 0xff) << 8) #define PORT_HLE (1 << 16) +#define PORT_TEST_MODE_SHIFT 28 /* USB3 Protocol PORTLI Port Link Information */ #define PORT_RX_LANES(p) (((p) >> 16) & 0xf) @@ -617,6 +618,7 @@ struct xhci_slot_ctx { #define ROUTE_STRING_MASK (0xfffff) /* Device speed - values defined by PORTSC Device Speed field - 20:23 */ #define DEV_SPEED (0xf << 20) +#define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) /* bit 24 reserved */ /* Is this LS/FS device connected through a HS hub? - bit 25 */ #define DEV_MTT (0x1 << 25) @@ -637,6 +639,7 @@ struct xhci_slot_ctx { #define DEVINFO_TO_ROOT_HUB_PORT(p) (((p) >> 16) & 0xff) /* Maximum number of ports under a hub device */ #define XHCI_MAX_PORTS(p) (((p) & 0xff) << 24) +#define DEVINFO_TO_MAX_PORTS(p) (((p) & (0xff << 24)) >> 24) /* tt_info bitmasks */ /* @@ -651,6 +654,7 @@ struct xhci_slot_ctx { */ #define TT_PORT (0xff << 8) #define TT_THINK_TIME(p) (((p) & 0x3) << 16) +#define GET_TT_THINK_TIME(p) (((p) & (0x3 << 16)) >> 16) /* dev_state bitmasks */ /* USB device address - assigned by the HC */ @@ -1562,10 +1566,8 @@ struct xhci_ring { struct xhci_segment *last_seg; union xhci_trb *enqueue; struct xhci_segment *enq_seg; - unsigned int enq_updates; union xhci_trb *dequeue; struct xhci_segment *deq_seg; - unsigned int deq_updates; struct list_head td_list; /* * Write the cycle state into the TRB cycle field to give ownership of @@ -1604,7 +1606,6 @@ struct xhci_scratchpad { u64 *sp_array; dma_addr_t sp_dma; void **sp_buffers; - dma_addr_t *sp_dma_buffers; }; struct urb_priv { @@ -1722,7 +1723,6 @@ struct xhci_hcd { int page_shift; /* msi-x vectors */ int msix_count; - struct msix_entry *msix_entries; /* optional clock */ struct clk *clk; /* data structures */ @@ -1818,6 +1818,7 @@ struct xhci_hcd { #define XHCI_MISSING_CAS (1 << 24) /* For controller with a broken Port Disable implementation */ #define XHCI_BROKEN_PORT_PED (1 << 25) +#define XHCI_LIMIT_ENDPOINT_INTERVAL_7 (1 << 26) unsigned int num_active_eps; unsigned int limit_active_eps; @@ -1843,6 +1844,7 @@ struct xhci_hcd { /* Compliance Mode Recovery Data */ struct timer_list comp_mode_recovery_timer; u32 port_status_u0; + u16 test_mode; /* Compliance Mode Timer Triggered every 2 seconds */ #define COMP_MODE_RCVRY_MSECS 2000 @@ -1918,19 +1920,10 @@ void xhci_print_ir_set(struct xhci_hcd *xhci, int set_num); void xhci_print_registers(struct xhci_hcd *xhci); void xhci_dbg_regs(struct xhci_hcd *xhci); void xhci_print_run_regs(struct xhci_hcd *xhci); -void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb); -void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb); -void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg); -void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci); -void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring); -void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int last_ep); char *xhci_get_slot_state(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); -void xhci_dbg_ep_rings(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_virt_ep *ep); void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), const char *fmt, ...); @@ -1944,16 +1937,8 @@ void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, struct usb_device *udev); unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); unsigned int xhci_get_endpoint_address(unsigned int ep_index); -unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); -unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index); unsigned int xhci_last_valid_endpoint(u32 added_ctxs); void xhci_endpoint_zero(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_host_endpoint *ep); -void xhci_drop_ep_from_interval_table(struct xhci_hcd *xhci, - struct xhci_bw_info *ep_bw, - struct xhci_interval_bw_table *bw_table, - struct usb_device *udev, - struct xhci_virt_ep *virt_ep, - struct xhci_tt_bw_info *tt_info); void xhci_update_tt_active_eps(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, int old_active_eps); @@ -2010,53 +1995,25 @@ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); +int xhci_start(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); -int xhci_init(struct usb_hcd *hcd); int xhci_run(struct usb_hcd *hcd); -void xhci_stop(struct usb_hcd *hcd); -void xhci_shutdown(struct usb_hcd *hcd); int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); void xhci_init_driver(struct hc_driver *drv, const struct xhci_driver_overrides *over); +int xhci_disable_slot(struct xhci_hcd *xhci, + struct xhci_command *command, u32 slot_id); -#ifdef CONFIG_PM int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup); int xhci_resume(struct xhci_hcd *xhci, bool hibernated); -#else -#define xhci_suspend NULL -#define xhci_resume NULL -#endif -int xhci_get_frame(struct usb_hcd *hcd); irqreturn_t xhci_irq(struct usb_hcd *hcd); irqreturn_t xhci_msi_irq(int irq, void *hcd); int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); -void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_alloc_tt_info(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_device *hdev, struct usb_tt *tt, gfp_t mem_flags); -int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, - struct usb_host_endpoint **eps, unsigned int num_eps, - unsigned int num_streams, gfp_t mem_flags); -int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, - struct usb_host_endpoint **eps, unsigned int num_eps, - gfp_t mem_flags); -int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); -int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev); -int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev); -int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, - struct usb_device *udev, int enable); -int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, - struct usb_tt *tt, gfp_t mem_flags); -int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); -int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); -int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); -int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); -void xhci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); -int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev); -int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); -void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); @@ -2100,9 +2057,6 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, struct xhci_dequeue_state *deq_state); void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, struct xhci_td *td); -void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - struct xhci_dequeue_state *deq_state); void xhci_stop_endpoint_command_watchdog(unsigned long arg); void xhci_handle_command_timeout(struct work_struct *work); @@ -2113,16 +2067,13 @@ void xhci_cleanup_command_queue(struct xhci_hcd *xhci); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, int port_id, u32 link_state); -int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, - struct usb_device *udev, enum usb3_link_state state); -int xhci_disable_usb3_lpm_timeout(struct usb_hcd *hcd, - struct usb_device *udev, enum usb3_link_state state); void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, int port_id, u32 port_bit); int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1); +void xhci_hc_died(struct xhci_hcd *xhci); #ifdef CONFIG_PM int xhci_bus_suspend(struct usb_hcd *hcd); @@ -2153,6 +2104,22 @@ static inline struct xhci_ring *xhci_urb_to_transfer_ring(struct xhci_hcd *xhci, urb->stream_id); } +static inline char *xhci_slot_state_string(u32 state) +{ + switch (state) { + case SLOT_STATE_ENABLED: + return "enabled/disabled"; + case SLOT_STATE_DEFAULT: + return "default"; + case SLOT_STATE_ADDRESSED: + return "addressed"; + case SLOT_STATE_CONFIGURED: + return "configured"; + default: + return "reserved"; + } +} + static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, u32 field3) { @@ -2162,14 +2129,12 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, switch (type) { case TRB_LINK: sprintf(str, - "TRB %08x%08x status '%s' len %d slot %d ep %d type '%s' flags %c:%c", - field1, field0, - xhci_trb_comp_code_string(GET_COMP_CODE(field2)), - EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), - /* Macro decrements 1, maybe it shouldn't?!? */ - TRB_TO_EP_INDEX(field3) + 1, - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), - field3 & EVENT_DATA ? 'E' : 'e', + "LINK %08x%08x intr %d type '%s' flags %c:%c:%c:%c", + field1, field0, GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_TC ? 'T' : 't', field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_TRANSFER: @@ -2187,37 +2152,52 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), /* Macro decrements 1, maybe it shouldn't?!? */ TRB_TO_EP_INDEX(field3) + 1, - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field3 & EVENT_DATA ? 'E' : 'e', field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_SETUP: - sprintf(str, - "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c:%c", - field0 & 0xff, - (field0 & 0xff00) >> 8, - (field0 & 0xff000000) >> 24, - (field0 & 0xff0000) >> 16, - (field1 & 0xff00) >> 8, - field1 & 0xff, - (field1 & 0xff000000) >> 16 | - (field1 & 0xff0000) >> 16, - TRB_LEN(field2), GET_TD_SIZE(field2), - GET_INTR_TARGET(field2), - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), - field3 & TRB_BEI ? 'B' : 'b', - field3 & TRB_IDT ? 'I' : 'i', - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CHAIN ? 'C' : 'c', - field3 & TRB_NO_SNOOP ? 'S' : 's', - field3 & TRB_ISP ? 'I' : 'i', - field3 & TRB_ENT ? 'E' : 'e', - field3 & TRB_CYCLE ? 'C' : 'c'); + sprintf(str, "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c", + field0 & 0xff, + (field0 & 0xff00) >> 8, + (field0 & 0xff000000) >> 24, + (field0 & 0xff0000) >> 16, + (field1 & 0xff00) >> 8, + field1 & 0xff, + (field1 & 0xff000000) >> 16 | + (field1 & 0xff0000) >> 16, + TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CYCLE ? 'C' : 'c'); break; - case TRB_NORMAL: case TRB_DATA: + sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; case TRB_STATUS: + sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_NORMAL: case TRB_ISOC: case TRB_EVENT_DATA: case TRB_TR_NOOP: @@ -2225,7 +2205,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c:%c", field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), GET_INTR_TARGET(field2), - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field3 & TRB_BEI ? 'B' : 'b', field3 & TRB_IDT ? 'I' : 'i', field3 & TRB_IOC ? 'I' : 'i', @@ -2240,21 +2220,21 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_ENABLE_SLOT: sprintf(str, "%s: flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_DISABLE_SLOT: case TRB_NEG_BANDWIDTH: sprintf(str, "%s: slot %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_ADDR_DEV: sprintf(str, "%s: ctx %08x%08x slot %d flags %c:%c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_SLOT_ID(field3), field3 & TRB_BSR ? 'B' : 'b', @@ -2263,7 +2243,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_CONFIG_EP: sprintf(str, "%s: ctx %08x%08x slot %d flags %c:%c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_SLOT_ID(field3), field3 & TRB_DC ? 'D' : 'd', @@ -2272,7 +2252,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_EVAL_CONTEXT: sprintf(str, "%s: ctx %08x%08x slot %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_SLOT_ID(field3), field3 & TRB_CYCLE ? 'C' : 'c'); @@ -2280,7 +2260,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_RESET_EP: sprintf(str, "%s: ctx %08x%08x slot %d ep %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_SLOT_ID(field3), /* Macro decrements 1, maybe it shouldn't?!? */ @@ -2290,7 +2270,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_STOP_RING: sprintf(str, "%s: slot %d sp %d ep %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), TRB_TO_SUSPEND_PORT(field3), /* Macro decrements 1, maybe it shouldn't?!? */ @@ -2300,7 +2280,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_SET_DEQ: sprintf(str, "%s: deq %08x%08x stream %d slot %d ep %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_STREAM_ID(field2), TRB_TO_SLOT_ID(field3), @@ -2311,14 +2291,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_RESET_DEV: sprintf(str, "%s: slot %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_FORCE_EVENT: sprintf(str, "%s: event %08x%08x vf intr %d vf id %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_VF_INTR_TARGET(field2), TRB_TO_VF_ID(field3), @@ -2327,14 +2307,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_SET_LT: sprintf(str, "%s: belt %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), TRB_TO_BELT(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_GET_BW: sprintf(str, "%s: ctx %08x%08x slot %d speed %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field1, field0, TRB_TO_SLOT_ID(field3), TRB_TO_DEV_SPEED(field3), @@ -2343,7 +2323,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_FORCE_HEADER: sprintf(str, "%s: info %08x%08x%08x pkt type %d roothub port %d flags %c", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field2, field1, field0 & 0xffffffe0, TRB_TO_PACKET_TYPE(field0), TRB_TO_ROOTHUB_PORT(field3), @@ -2352,12 +2332,155 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, default: sprintf(str, "type '%s' -> raw %08x %08x %08x %08x", - xhci_trb_type_string(TRB_FIELD_TO_TYPE(field3)), + xhci_trb_type_string(type), field0, field1, field2, field3); } return str; } +static inline const char *xhci_decode_slot_context(u32 info, u32 info2, + u32 tt_info, u32 state) +{ + static char str[1024]; + u32 speed; + u32 hub; + u32 mtt; + int ret = 0; + + speed = info & DEV_SPEED; + hub = info & DEV_HUB; + mtt = info & DEV_MTT; + + ret = sprintf(str, "RS %05x %s%s%s Ctx Entries %d MEL %d us Port# %d/%d", + info & ROUTE_STRING_MASK, + ({ char *s; + switch (speed) { + case SLOT_SPEED_FS: + s = "full-speed"; + break; + case SLOT_SPEED_LS: + s = "low-speed"; + break; + case SLOT_SPEED_HS: + s = "high-speed"; + break; + case SLOT_SPEED_SS: + s = "super-speed"; + break; + case SLOT_SPEED_SSP: + s = "super-speed plus"; + break; + default: + s = "UNKNOWN speed"; + } s; }), + mtt ? " multi-TT" : "", + hub ? " Hub" : "", + (info & LAST_CTX_MASK) >> 27, + info2 & MAX_EXIT, + DEVINFO_TO_ROOT_HUB_PORT(info2), + DEVINFO_TO_MAX_PORTS(info2)); + + ret += sprintf(str + ret, " [TT Slot %d Port# %d TTT %d Intr %d] Addr %d State %s", + tt_info & TT_SLOT, (tt_info & TT_PORT) >> 8, + GET_TT_THINK_TIME(tt_info), GET_INTR_TARGET(tt_info), + state & DEV_ADDR_MASK, + xhci_slot_state_string(GET_SLOT_STATE(state))); + + return str; +} + +static inline const char *xhci_ep_state_string(u8 state) +{ + switch (state) { + case EP_STATE_DISABLED: + return "disabled"; + case EP_STATE_RUNNING: + return "running"; + case EP_STATE_HALTED: + return "halted"; + case EP_STATE_STOPPED: + return "stopped"; + case EP_STATE_ERROR: + return "error"; + default: + return "INVALID"; + } +} + +static inline const char *xhci_ep_type_string(u8 type) +{ + switch (type) { + case ISOC_OUT_EP: + return "Isoc OUT"; + case BULK_OUT_EP: + return "Bulk OUT"; + case INT_OUT_EP: + return "Int OUT"; + case CTRL_EP: + return "Ctrl"; + case ISOC_IN_EP: + return "Isoc IN"; + case BULK_IN_EP: + return "Bulk IN"; + case INT_IN_EP: + return "Int IN"; + default: + return "INVALID"; + } +} + +static inline const char *xhci_decode_ep_context(u32 info, u32 info2, u64 deq, + u32 tx_info) +{ + static char str[1024]; + int ret; + + u32 esit; + u16 maxp; + u16 avg; + + u8 max_pstr; + u8 ep_state; + u8 interval; + u8 ep_type; + u8 burst; + u8 cerr; + u8 mult; + u8 lsa; + u8 hid; + + esit = EP_MAX_ESIT_PAYLOAD_HI(info) << 16 | + EP_MAX_ESIT_PAYLOAD_LO(tx_info); + + ep_state = info & EP_STATE_MASK; + max_pstr = info & EP_MAXPSTREAMS_MASK; + interval = CTX_TO_EP_INTERVAL(info); + mult = CTX_TO_EP_MULT(info) + 1; + lsa = info & EP_HAS_LSA; + + cerr = (info2 & (3 << 1)) >> 1; + ep_type = CTX_TO_EP_TYPE(info2); + hid = info2 & (1 << 7); + burst = CTX_TO_MAX_BURST(info2); + maxp = MAX_PACKET_DECODED(info2); + + avg = EP_AVG_TRB_LENGTH(tx_info); + + ret = sprintf(str, "State %s mult %d max P. Streams %d %s", + xhci_ep_state_string(ep_state), mult, + max_pstr, lsa ? "LSA " : ""); + + ret += sprintf(str + ret, "interval %d us max ESIT payload %d CErr %d ", + (1 << interval) * 125, esit, cerr); + + ret += sprintf(str + ret, "Type %s %sburst %d maxp %d deq %016llx ", + xhci_ep_type_string(ep_type), hid ? "HID" : "", + burst, maxp, deq); + + ret += sprintf(str + ret, "avg trb len %d", avg); + + return str; +} #endif /* __LINUX_XHCI_HCD_H */ diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c index 79205b31e4a9..bc68bbab7fa1 100644 --- a/drivers/usb/isp1760/isp1760-if.c +++ b/drivers/usb/isp1760/isp1760-if.c @@ -21,11 +21,11 @@ #include "isp1760-core.h" #include "isp1760-regs.h" -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI #include <linux/pci.h> #endif -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI static int isp1761_pci_init(struct pci_dev *dev) { resource_size_t mem_start; @@ -286,7 +286,7 @@ static int __init isp1760_init(void) ret = platform_driver_register(&isp1760_plat_driver); if (!ret) any_ret = 0; -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI ret = pci_register_driver(&isp1761_pci_driver); if (!ret) any_ret = 0; @@ -301,7 +301,7 @@ module_init(isp1760_init); static void __exit isp1760_exit(void) { platform_driver_unregister(&isp1760_plat_driver); -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_PCI pci_unregister_driver(&isp1761_pci_driver); #endif isp1760_deinit_kmem_cache(); diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c index db9a9e6ff6be..dfd54ea4808f 100644 --- a/drivers/usb/misc/adutux.c +++ b/drivers/usb/misc/adutux.c @@ -655,24 +655,15 @@ static int adu_probe(struct usb_interface *interface, { struct usb_device *udev = interface_to_usbdev(interface); struct adu_device *dev = NULL; - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - int retval = -ENODEV; + int retval = -ENOMEM; int in_end_size; int out_end_size; - int i; - - if (udev == NULL) { - dev_err(&interface->dev, "udev is NULL.\n"); - goto exit; - } + int res; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL); - if (!dev) { - retval = -ENOMEM; - goto exit; - } + if (!dev) + return -ENOMEM; mutex_init(&dev->mtx); spin_lock_init(&dev->buflock); @@ -680,24 +671,13 @@ static int adu_probe(struct usb_interface *interface, init_waitqueue_head(&dev->read_wait); init_waitqueue_head(&dev->write_wait); - iface_desc = &interface->altsetting[0]; - - /* set up the endpoint information */ - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_int_in(endpoint)) - dev->interrupt_in_endpoint = endpoint; - - if (usb_endpoint_is_int_out(endpoint)) - dev->interrupt_out_endpoint = endpoint; - } - if (dev->interrupt_in_endpoint == NULL) { - dev_err(&interface->dev, "interrupt in endpoint not found\n"); - goto error; - } - if (dev->interrupt_out_endpoint == NULL) { - dev_err(&interface->dev, "interrupt out endpoint not found\n"); + res = usb_find_common_endpoints_reverse(&interface->altsetting[0], + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (res) { + dev_err(&interface->dev, "interrupt endpoints not found\n"); + retval = res; goto error; } @@ -705,10 +685,8 @@ static int adu_probe(struct usb_interface *interface, out_end_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL); - if (!dev->read_buffer_primary) { - retval = -ENOMEM; + if (!dev->read_buffer_primary) goto error; - } /* debug code prime the buffer */ memset(dev->read_buffer_primary, 'a', in_end_size); @@ -717,10 +695,8 @@ static int adu_probe(struct usb_interface *interface, memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size); dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL); - if (!dev->read_buffer_secondary) { - retval = -ENOMEM; + if (!dev->read_buffer_secondary) goto error; - } /* debug code prime the buffer */ memset(dev->read_buffer_secondary, 'e', in_end_size); @@ -748,6 +724,7 @@ static int adu_probe(struct usb_interface *interface, if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number, sizeof(dev->serial_number))) { dev_err(&interface->dev, "Could not retrieve serial number\n"); + retval = -EIO; goto error; } dev_dbg(&interface->dev,"serial_number=%s", dev->serial_number); @@ -770,8 +747,8 @@ static int adu_probe(struct usb_interface *interface, dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d\n", le16_to_cpu(udev->descriptor.idProduct), dev->serial_number, (dev->minor - ADU_MINOR_BASE)); -exit: - return retval; + + return 0; error: adu_delete(dev); diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c index da5ff401a354..8efdc500e790 100644 --- a/drivers/usb/misc/appledisplay.c +++ b/drivers/usb/misc/appledisplay.c @@ -212,28 +212,21 @@ static int appledisplay_probe(struct usb_interface *iface, struct backlight_properties props; struct appledisplay *pdata; struct usb_device *udev = interface_to_usbdev(iface); - struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; int int_in_endpointAddr = 0; - int i, retval = -ENOMEM, brightness; + int retval, brightness; char bl_name[20]; /* set up the endpoint information */ /* use only the first interrupt-in endpoint */ - iface_desc = iface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { - endpoint = &iface_desc->endpoint[i].desc; - if (!int_in_endpointAddr && usb_endpoint_is_int_in(endpoint)) { - /* we found an interrupt in endpoint */ - int_in_endpointAddr = endpoint->bEndpointAddress; - break; - } - } - if (!int_in_endpointAddr) { + retval = usb_find_int_in_endpoint(iface->cur_altsetting, &endpoint); + if (retval) { dev_err(&iface->dev, "Could not find int-in endpoint\n"); - return -EIO; + return retval; } + int_in_endpointAddr = endpoint->bEndpointAddress; + /* allocate memory for our device state and initialize it */ pdata = kzalloc(sizeof(struct appledisplay), GFP_KERNEL); if (!pdata) { diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c index aa350dc9eb25..e9cae4d82af2 100644 --- a/drivers/usb/misc/chaoskey.c +++ b/drivers/usb/misc/chaoskey.c @@ -117,28 +117,26 @@ static int chaoskey_probe(struct usb_interface *interface, { struct usb_device *udev = interface_to_usbdev(interface); struct usb_host_interface *altsetting = interface->cur_altsetting; - int i; - int in_ep = -1; + struct usb_endpoint_descriptor *epd; + int in_ep; struct chaoskey *dev; int result = -ENOMEM; int size; + int res; usb_dbg(interface, "probe %s-%s", udev->product, udev->serial); /* Find the first bulk IN endpoint and its packet size */ - for (i = 0; i < altsetting->desc.bNumEndpoints; i++) { - if (usb_endpoint_is_bulk_in(&altsetting->endpoint[i].desc)) { - in_ep = usb_endpoint_num(&altsetting->endpoint[i].desc); - size = usb_endpoint_maxp(&altsetting->endpoint[i].desc); - break; - } + res = usb_find_bulk_in_endpoint(altsetting, &epd); + if (res) { + usb_dbg(interface, "no IN endpoint found"); + return res; } + in_ep = usb_endpoint_num(epd); + size = usb_endpoint_maxp(epd); + /* Validate endpoint and size */ - if (in_ep == -1) { - usb_dbg(interface, "no IN endpoint found"); - return -ENODEV; - } if (size <= 0) { usb_dbg(interface, "invalid size (%d)", size); return -ENODEV; diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c index 01a9373b7e18..8291499d0581 100644 --- a/drivers/usb/misc/ftdi-elan.c +++ b/drivers/usb/misc/ftdi-elan.c @@ -2700,10 +2700,8 @@ static int ftdi_elan_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - size_t buffer_size; - int i; - int retval = -ENOMEM; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; + int retval; struct usb_ftdi *ftdi; ftdi = kzalloc(sizeof(struct usb_ftdi), GFP_KERNEL); @@ -2720,31 +2718,25 @@ static int ftdi_elan_probe(struct usb_interface *interface, ftdi->interface = interface; mutex_init(&ftdi->u132_lock); ftdi->expected = 4; + iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - if (!ftdi->bulk_in_endpointAddr && - usb_endpoint_is_bulk_in(endpoint)) { - buffer_size = usb_endpoint_maxp(endpoint); - ftdi->bulk_in_size = buffer_size; - ftdi->bulk_in_endpointAddr = endpoint->bEndpointAddress; - ftdi->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); - if (!ftdi->bulk_in_buffer) { - retval = -ENOMEM; - goto error; - } - } - if (!ftdi->bulk_out_endpointAddr && - usb_endpoint_is_bulk_out(endpoint)) { - ftdi->bulk_out_endpointAddr = - endpoint->bEndpointAddress; - } - } - if (!(ftdi->bulk_in_endpointAddr && ftdi->bulk_out_endpointAddr)) { + retval = usb_find_common_endpoints(iface_desc, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { dev_err(&ftdi->udev->dev, "Could not find both bulk-in and bulk-out endpoints\n"); - retval = -ENODEV; goto error; } + + ftdi->bulk_in_size = usb_endpoint_maxp(bulk_in); + ftdi->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + ftdi->bulk_in_buffer = kmalloc(ftdi->bulk_in_size, GFP_KERNEL); + if (!ftdi->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + + ftdi->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + dev_info(&ftdi->udev->dev, "interface %d has I=%02X O=%02X\n", iface_desc->desc.bInterfaceNumber, ftdi->bulk_in_endpointAddr, ftdi->bulk_out_endpointAddr); diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c index 502bfe30a077..81fcbf024c65 100644 --- a/drivers/usb/misc/idmouse.c +++ b/drivers/usb/misc/idmouse.c @@ -360,26 +360,22 @@ static int idmouse_probe(struct usb_interface *interface, dev->interface = interface; /* set up the endpoint information - use only the first bulk-in endpoint */ - endpoint = &iface_desc->endpoint[0].desc; - if (!dev->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) { - /* we found a bulk in endpoint */ - dev->orig_bi_size = usb_endpoint_maxp(endpoint); - dev->bulk_in_size = 0x200; /* works _much_ faster */ - dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; - dev->bulk_in_buffer = - kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL); - - if (!dev->bulk_in_buffer) { - idmouse_delete(dev); - return -ENOMEM; - } + result = usb_find_bulk_in_endpoint(iface_desc, &endpoint); + if (result) { + dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n"); + idmouse_delete(dev); + return result; } - if (!(dev->bulk_in_endpointAddr)) { - dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n"); + dev->orig_bi_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_size = 0x200; /* works _much_ faster */ + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { idmouse_delete(dev); - return -ENODEV; + return -ENOMEM; } + /* allow device read, write and ioctl */ dev->present = 1; diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 37c63cb39714..77569531b78a 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -756,9 +756,8 @@ static int iowarrior_probe(struct usb_interface *interface, struct usb_device *udev = interface_to_usbdev(interface); struct iowarrior *dev = NULL; struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - int i; int retval = -ENOMEM; + int res; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL); @@ -781,27 +780,19 @@ static int iowarrior_probe(struct usb_interface *interface, iface_desc = interface->cur_altsetting; dev->product_id = le16_to_cpu(udev->descriptor.idProduct); - /* set up the endpoint information */ - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_int_in(endpoint)) - dev->int_in_endpoint = endpoint; - if (usb_endpoint_is_int_out(endpoint)) - /* this one will match for the IOWarrior56 only */ - dev->int_out_endpoint = endpoint; - } - - if (!dev->int_in_endpoint) { + res = usb_find_last_int_in_endpoint(iface_desc, &dev->int_in_endpoint); + if (res) { dev_err(&interface->dev, "no interrupt-in endpoint found\n"); - retval = -ENODEV; + retval = res; goto error; } if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56) { - if (!dev->int_out_endpoint) { + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->int_out_endpoint); + if (res) { dev_err(&interface->dev, "no interrupt-out endpoint found\n"); - retval = -ENODEV; + retval = res; goto error; } } diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c index 3bc5356832db..9d9487c66f87 100644 --- a/drivers/usb/misc/ldusb.c +++ b/drivers/usb/misc/ldusb.c @@ -122,13 +122,13 @@ MODULE_SUPPORTED_DEVICE("LD USB Devices"); * avoid racing conditions and get better performance of the driver. */ static int ring_buffer_size = 128; -module_param(ring_buffer_size, int, 0); +module_param(ring_buffer_size, int, 0000); MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); /* The write_buffer can contain more than one interrupt out transfer. */ static int write_buffer_size = 10; -module_param(write_buffer_size, int, 0); +module_param(write_buffer_size, int, 0000); MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports"); /* As of kernel version 2.6.4 ehci-hcd uses an @@ -141,30 +141,30 @@ MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports"); * or set to 1 to use the standard interval from the endpoint descriptors. */ static int min_interrupt_in_interval = 2; -module_param(min_interrupt_in_interval, int, 0); +module_param(min_interrupt_in_interval, int, 0000); MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms"); static int min_interrupt_out_interval = 2; -module_param(min_interrupt_out_interval, int, 0); +module_param(min_interrupt_out_interval, int, 0000); MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms"); /* Structure to hold all of our device specific stuff */ struct ld_usb { struct mutex mutex; /* locks this structure */ - struct usb_interface* intf; /* save off the usb interface pointer */ + struct usb_interface *intf; /* save off the usb interface pointer */ int open_count; /* number of times this port has been opened */ - char* ring_buffer; + char *ring_buffer; unsigned int ring_head; unsigned int ring_tail; wait_queue_head_t read_wait; wait_queue_head_t write_wait; - char* interrupt_in_buffer; - struct usb_endpoint_descriptor* interrupt_in_endpoint; - struct urb* interrupt_in_urb; + char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; int interrupt_in_interval; size_t interrupt_in_endpoint_size; int interrupt_in_running; @@ -172,9 +172,9 @@ struct ld_usb { int buffer_overflow; spinlock_t rbsl; - char* interrupt_out_buffer; - struct usb_endpoint_descriptor* interrupt_out_endpoint; - struct urb* interrupt_out_urb; + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; int interrupt_out_interval; size_t interrupt_out_endpoint_size; int interrupt_out_busy; @@ -244,7 +244,7 @@ static void ld_usb_interrupt_in_callback(struct urb *urb) if (urb->actual_length > 0) { next_ring_head = (dev->ring_head+1) % ring_buffer_size; if (next_ring_head != dev->ring_tail) { - actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_head*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_head * (sizeof(size_t)+dev->interrupt_in_endpoint_size)); /* actual_buffer gets urb->actual_length + interrupt_in_buffer */ *actual_buffer = urb->actual_length; memcpy(actual_buffer+1, dev->interrupt_in_buffer, urb->actual_length); @@ -483,7 +483,7 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count, } /* actual_buffer contains actual_length + interrupt_in_buffer */ - actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_tail*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size)); bytes_to_read = min(count, *actual_buffer); if (bytes_to_read < *actual_buffer) dev_warn(&dev->intf->dev, "Read buffer overflow, %zd bytes dropped\n", @@ -561,7 +561,7 @@ static ssize_t ld_usb_write(struct file *file, const char __user *buffer, /* write the data into interrupt_out_buffer from userspace */ bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size); if (bytes_to_write < count) - dev_warn(&dev->intf->dev, "Write buffer overflow, %zd bytes dropped\n",count-bytes_to_write); + dev_warn(&dev->intf->dev, "Write buffer overflow, %zd bytes dropped\n", count-bytes_to_write); dev_dbg(&dev->intf->dev, "%s: count = %zd, bytes_to_write = %zd\n", __func__, count, bytes_to_write); @@ -650,10 +650,9 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id * struct usb_device *udev = interface_to_usbdev(intf); struct ld_usb *dev = NULL; struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; char *buffer; - int i; int retval = -ENOMEM; + int res; /* allocate memory for our device state and initialize it */ @@ -681,21 +680,17 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id * iface_desc = intf->cur_altsetting; - /* set up the endpoint information */ - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_int_in(endpoint)) - dev->interrupt_in_endpoint = endpoint; - - if (usb_endpoint_is_int_out(endpoint)) - dev->interrupt_out_endpoint = endpoint; - } - if (dev->interrupt_in_endpoint == NULL) { + res = usb_find_last_int_in_endpoint(iface_desc, + &dev->interrupt_in_endpoint); + if (res) { dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + retval = res; goto error; } - if (dev->interrupt_out_endpoint == NULL) + + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->interrupt_out_endpoint); + if (res) dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n"); dev->interrupt_in_endpoint_size = usb_endpoint_maxp(dev->interrupt_in_endpoint); diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c index 322a042d6e59..aa3c280fdf8d 100644 --- a/drivers/usb/misc/legousbtower.c +++ b/drivers/usb/misc/legousbtower.c @@ -317,9 +317,16 @@ static int tower_open (struct inode *inode, struct file *file) int subminor; int retval = 0; struct usb_interface *interface; - struct tower_reset_reply reset_reply; + struct tower_reset_reply *reset_reply; int result; + reset_reply = kmalloc(sizeof(*reset_reply), GFP_KERNEL); + + if (!reset_reply) { + retval = -ENOMEM; + goto exit; + } + nonseekable_open(inode, file); subminor = iminor(inode); @@ -364,8 +371,8 @@ static int tower_open (struct inode *inode, struct file *file) USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, 0, 0, - &reset_reply, - sizeof(reset_reply), + reset_reply, + sizeof(*reset_reply), 1000); if (result < 0) { dev_err(&dev->udev->dev, @@ -406,6 +413,7 @@ unlock_exit: mutex_unlock(&dev->lock); exit: + kfree(reset_reply); return retval; } @@ -806,10 +814,7 @@ static int tower_probe (struct usb_interface *interface, const struct usb_device struct device *idev = &interface->dev; struct usb_device *udev = interface_to_usbdev(interface); struct lego_usb_tower *dev = NULL; - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor* endpoint; - struct tower_get_version_reply get_version_reply; - int i; + struct tower_get_version_reply *get_version_reply = NULL; int retval = -ENOMEM; int result; @@ -846,25 +851,13 @@ static int tower_probe (struct usb_interface *interface, const struct usb_device dev->interrupt_out_urb = NULL; dev->interrupt_out_busy = 0; - iface_desc = interface->cur_altsetting; - - /* set up the endpoint information */ - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_xfer_int(endpoint)) { - if (usb_endpoint_dir_in(endpoint)) - dev->interrupt_in_endpoint = endpoint; - else - dev->interrupt_out_endpoint = endpoint; - } - } - if(dev->interrupt_in_endpoint == NULL) { - dev_err(idev, "interrupt in endpoint not found\n"); - goto error; - } - if (dev->interrupt_out_endpoint == NULL) { - dev_err(idev, "interrupt out endpoint not found\n"); + result = usb_find_common_endpoints_reverse(interface->cur_altsetting, + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (result) { + dev_err(idev, "interrupt endpoints not found\n"); + retval = result; goto error; } @@ -886,6 +879,13 @@ static int tower_probe (struct usb_interface *interface, const struct usb_device dev->interrupt_in_interval = interrupt_in_interval ? interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; dev->interrupt_out_interval = interrupt_out_interval ? interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + get_version_reply = kmalloc(sizeof(*get_version_reply), GFP_KERNEL); + + if (!get_version_reply) { + retval = -ENOMEM; + goto error; + } + /* get the firmware version and log it */ result = usb_control_msg (udev, usb_rcvctrlpipe(udev, 0), @@ -893,18 +893,19 @@ static int tower_probe (struct usb_interface *interface, const struct usb_device USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, 0, 0, - &get_version_reply, - sizeof(get_version_reply), + get_version_reply, + sizeof(*get_version_reply), 1000); if (result < 0) { dev_err(idev, "LEGO USB Tower get version control request failed\n"); retval = result; goto error; } - dev_info(&interface->dev, "LEGO USB Tower firmware version is %d.%d " - "build %d\n", get_version_reply.major, - get_version_reply.minor, - le16_to_cpu(get_version_reply.build_no)); + dev_info(&interface->dev, + "LEGO USB Tower firmware version is %d.%d build %d\n", + get_version_reply->major, + get_version_reply->minor, + le16_to_cpu(get_version_reply->build_no)); /* we can register the device now, as it is ready */ usb_set_intfdata (interface, dev); @@ -928,6 +929,7 @@ exit: return retval; error: + kfree(get_version_reply); tower_delete(dev); return retval; } diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c index d3d124753266..2142132a1f82 100644 --- a/drivers/usb/misc/lvstest.c +++ b/drivers/usb/misc/lvstest.c @@ -193,7 +193,7 @@ static ssize_t u2_timeout_store(struct device *dev, return ret; } - if (val < 0 || val > 127) + if (val > 127) return -EINVAL; ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), @@ -222,7 +222,7 @@ static ssize_t u1_timeout_store(struct device *dev, return ret; } - if (val < 0 || val > 127) + if (val > 127) return -EINVAL; ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), @@ -367,10 +367,9 @@ static int lvs_rh_probe(struct usb_interface *intf, hdev = interface_to_usbdev(intf); desc = intf->cur_altsetting; - if (desc->desc.bNumEndpoints < 1) - return -ENODEV; - - endpoint = &desc->endpoint[0].desc; + ret = usb_find_int_in_endpoint(desc, &endpoint); + if (ret) + return ret; /* valid only for SS root hub */ if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) { @@ -433,6 +432,7 @@ static void lvs_rh_disconnect(struct usb_interface *intf) struct lvs_rh *lvs = usb_get_intfdata(intf); sysfs_remove_group(&intf->dev.kobj, &lvs_attr_group); + usb_poison_urb(lvs->urb); /* used in scheduled work */ flush_work(&lvs->rh_work); usb_free_urb(lvs->urb); } diff --git a/drivers/usb/misc/sisusbvga/sisusb_con.c b/drivers/usb/misc/sisusbvga/sisusb_con.c index 4b5777ec1501..3c6948af726a 100644 --- a/drivers/usb/misc/sisusbvga/sisusb_con.c +++ b/drivers/usb/misc/sisusbvga/sisusb_con.c @@ -816,7 +816,7 @@ sisusbcon_scroll_area(struct vc_data *c, struct sisusb_usb_data *sisusb, mutex_unlock(&sisusb->lock); - return 1; + return true; } /* Interface routine */ diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c index 9f48419abc46..0f5ad896c7e3 100644 --- a/drivers/usb/misc/usblcd.c +++ b/drivers/usb/misc/usblcd.c @@ -313,16 +313,15 @@ static int lcd_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_lcd *dev = NULL; - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - size_t buffer_size; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; int i; - int retval = -ENOMEM; + int retval; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) - goto error; + return -ENOMEM; + kref_init(&dev->kref); sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES); init_usb_anchor(&dev->submitted); @@ -338,33 +337,24 @@ static int lcd_probe(struct usb_interface *interface, /* set up the endpoint information */ /* use only the first bulk-in and bulk-out endpoints */ - iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (!dev->bulk_in_endpointAddr && - usb_endpoint_is_bulk_in(endpoint)) { - /* we found a bulk in endpoint */ - buffer_size = usb_endpoint_maxp(endpoint); - dev->bulk_in_size = buffer_size; - dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; - dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); - if (!dev->bulk_in_buffer) - goto error; - } - - if (!dev->bulk_out_endpointAddr && - usb_endpoint_is_bulk_out(endpoint)) { - /* we found a bulk out endpoint */ - dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; - } - } - if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { dev_err(&interface->dev, "Could not find both bulk-in and bulk-out endpoints\n"); goto error; } + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); @@ -390,8 +380,7 @@ static int lcd_probe(struct usb_interface *interface, return 0; error: - if (dev) - kref_put(&dev->kref, lcd_delete); + kref_put(&dev->kref, lcd_delete); return retval; } diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 17c081068257..eee82ca55b7b 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -124,6 +124,20 @@ static struct usb_device *testdev_to_usbdev(struct usbtest_dev *test) /*-------------------------------------------------------------------------*/ +static inline void endpoint_update(int edi, + struct usb_host_endpoint **in, + struct usb_host_endpoint **out, + struct usb_host_endpoint *e) +{ + if (edi) { + if (!*in) + *in = e; + } else { + if (!*out) + *out = e; + } +} + static int get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf) { @@ -151,46 +165,26 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf) */ for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { struct usb_host_endpoint *e; + int edi; e = alt->endpoint + ep; + edi = usb_endpoint_dir_in(&e->desc); + switch (usb_endpoint_type(&e->desc)) { case USB_ENDPOINT_XFER_BULK: - break; + endpoint_update(edi, &in, &out, e); + continue; case USB_ENDPOINT_XFER_INT: if (dev->info->intr) - goto try_intr; + endpoint_update(edi, &int_in, &int_out, e); + continue; case USB_ENDPOINT_XFER_ISOC: if (dev->info->iso) - goto try_iso; + endpoint_update(edi, &iso_in, &iso_out, e); /* FALLTHROUGH */ default: continue; } - if (usb_endpoint_dir_in(&e->desc)) { - if (!in) - in = e; - } else { - if (!out) - out = e; - } - continue; -try_intr: - if (usb_endpoint_dir_in(&e->desc)) { - if (!int_in) - int_in = e; - } else { - if (!int_out) - int_out = e; - } - continue; -try_iso: - if (usb_endpoint_dir_in(&e->desc)) { - if (!iso_in) - iso_in = e; - } else { - if (!iso_out) - iso_out = e; - } } if ((in && out) || iso_in || iso_out || int_in || int_out) goto found; diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 07014cad6dbe..5947373700a1 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -689,7 +689,7 @@ static int uss720_probe(struct usb_interface *intf, { struct usb_device *usbdev = usb_get_dev(interface_to_usbdev(intf)); struct usb_host_interface *interface; - struct usb_host_endpoint *endpoint; + struct usb_endpoint_descriptor *epd; struct parport_uss720_private *priv; struct parport *pp; unsigned char reg; @@ -745,9 +745,11 @@ static int uss720_probe(struct usb_interface *intf, get_1284_register(pp, 0, ®, GFP_KERNEL); dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg); - endpoint = &interface->endpoint[2]; - dev_dbg(&intf->dev, "epaddr %d interval %d\n", - endpoint->desc.bEndpointAddress, endpoint->desc.bInterval); + i = usb_find_last_int_in_endpoint(interface, &epd); + if (!i) { + dev_dbg(&intf->dev, "epaddr %d interval %d\n", + epd->bEndpointAddress, epd->bInterval); + } parport_announce_port(pp); usb_set_intfdata(intf, pp); diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c index 54e53ac4c08f..58abdf28620a 100644 --- a/drivers/usb/misc/yurex.c +++ b/drivers/usb/misc/yurex.c @@ -195,8 +195,8 @@ static int yurex_probe(struct usb_interface *interface, const struct usb_device_ struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; int retval = -ENOMEM; - int i; DEFINE_WAIT(wait); + int res; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); @@ -212,20 +212,14 @@ static int yurex_probe(struct usb_interface *interface, const struct usb_device_ /* set up the endpoint information */ iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_int_in(endpoint)) { - dev->int_in_endpointAddr = endpoint->bEndpointAddress; - break; - } - } - if (!dev->int_in_endpointAddr) { - retval = -ENODEV; + res = usb_find_int_in_endpoint(iface_desc, &endpoint); + if (res) { dev_err(&interface->dev, "Could not find endpoints\n"); + retval = res; goto error; } + dev->int_in_endpointAddr = endpoint->bEndpointAddress; /* allocate control URB */ dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL); diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c index 1a8987e7c5b0..11a0d3b84c5e 100644 --- a/drivers/usb/mtu3/mtu3_dr.c +++ b/drivers/usb/mtu3/mtu3_dr.c @@ -223,25 +223,25 @@ static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx) return 0; otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier; - ret = extcon_register_notifier(edev, EXTCON_USB, + ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB, &otg_sx->vbus_nb); if (ret < 0) dev_err(ssusb->dev, "failed to register notifier for USB\n"); otg_sx->id_nb.notifier_call = ssusb_id_notifier; - ret = extcon_register_notifier(edev, EXTCON_USB_HOST, + ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST, &otg_sx->id_nb); if (ret < 0) dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n"); dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n", - extcon_get_cable_state_(edev, EXTCON_USB), - extcon_get_cable_state_(edev, EXTCON_USB_HOST)); + extcon_get_state(edev, EXTCON_USB), + extcon_get_state(edev, EXTCON_USB_HOST)); /* default as host, switch to device mode if needed */ - if (extcon_get_cable_state_(edev, EXTCON_USB_HOST) == false) + if (extcon_get_state(edev, EXTCON_USB_HOST) == false) ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT); - if (extcon_get_cable_state_(edev, EXTCON_USB) == true) + if (extcon_get_state(edev, EXTCON_USB) == true) ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID); return 0; @@ -367,13 +367,6 @@ void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) cancel_delayed_work(&otg_sx->extcon_reg_dwork); - if (otg_sx->edev) { - extcon_unregister_notifier(otg_sx->edev, - EXTCON_USB, &otg_sx->vbus_nb); - extcon_unregister_notifier(otg_sx->edev, - EXTCON_USB_HOST, &otg_sx->id_nb); - } - if (otg_sx->manual_drd_enabled) ssusb_debugfs_exit(ssusb); } diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 72a2a5040848..5506a9c03c1f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -160,8 +160,8 @@ config USB_TI_CPPI_DMA Enable DMA transfers when TI CPPI DMA is available. config USB_TI_CPPI41_DMA - bool 'TI CPPI 4.1 (AM335x)' - depends on ARCH_OMAP && DMADEVICES + bool 'TI CPPI 4.1' + depends on (ARCH_OMAP || ARCH_DAVINCI_DA8XX) && DMADEVICES select TI_CPPI41 config USB_TUSB_OMAP_DMA diff --git a/drivers/usb/musb/cppi_dma.c b/drivers/usb/musb/cppi_dma.c index c4fabe952ca6..a13bd3625043 100644 --- a/drivers/usb/musb/cppi_dma.c +++ b/drivers/usb/musb/cppi_dma.c @@ -582,9 +582,10 @@ cppi_next_tx_segment(struct musb *musb, struct cppi_channel *tx) maxpacket = length; n_bds = 1; } else { - n_bds = length / maxpacket; - if (!length || (length % maxpacket)) - n_bds++; + if (length) + n_bds = DIV_ROUND_UP(length, maxpacket); + else + n_bds = 1; n_bds = min(n_bds, (unsigned) NUM_TXCHAN_BD); length = min(n_bds * maxpacket, length); } @@ -790,9 +791,7 @@ cppi_next_rx_segment(struct musb *musb, struct cppi_channel *rx, int onepacket) n_bds = 0xffff / maxpacket; length = n_bds * maxpacket; } else { - n_bds = length / maxpacket; - if (length % maxpacket) - n_bds++; + n_bds = DIV_ROUND_UP(length, maxpacket); } if (n_bds == 1) onepacket = 1; diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index d79c288ccbf2..df88123274ca 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -33,6 +33,7 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> +#include <linux/of_platform.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> @@ -456,12 +457,41 @@ static inline u8 get_vbus_power(struct device *dev) return current_uA / 1000 / 2; } +#ifdef CONFIG_USB_TI_CPPI41_DMA +static void da8xx_dma_controller_callback(struct dma_controller *c) +{ + struct musb *musb = c->musb; + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0); +} + +static struct dma_controller * +da8xx_dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct dma_controller *controller; + + controller = cppi41_dma_controller_create(musb, base); + if (IS_ERR_OR_NULL(controller)) + return controller; + + controller->dma_callback = da8xx_dma_controller_callback; + + return controller; +} +#endif + static const struct musb_platform_ops da8xx_ops = { - .quirks = MUSB_INDEXED_EP | MUSB_PRESERVE_SESSION, + .quirks = MUSB_INDEXED_EP | MUSB_PRESERVE_SESSION | + MUSB_DMA_CPPI41 | MUSB_DA8XX, .init = da8xx_musb_init, .exit = da8xx_musb_exit, .fifo_mode = 2, +#ifdef CONFIG_USB_TI_CPPI41_DMA + .dma_init = da8xx_dma_controller_create, + .dma_exit = cppi41_dma_controller_destroy, +#endif .enable = da8xx_musb_enable, .disable = da8xx_musb_disable, @@ -483,6 +513,12 @@ static const struct musb_hdrc_config da8xx_config = { .multipoint = 1, }; +static struct of_dev_auxdata da8xx_auxdata_lookup[] = { + OF_DEV_AUXDATA("ti,da830-cppi41", 0x01e01000, "cppi41-dmaengine", + NULL), + {} +}; + static int da8xx_probe(struct platform_device *pdev) { struct resource musb_resources[2]; @@ -533,6 +569,11 @@ static int da8xx_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, glue); + ret = of_platform_populate(pdev->dev.of_node, NULL, + da8xx_auxdata_lookup, &pdev->dev); + if (ret) + return ret; + memset(musb_resources, 0x00, sizeof(*musb_resources) * ARRAY_SIZE(musb_resources)); diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 0c3664ab705e..870da18f5077 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2332,7 +2332,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) setup_timer(&musb->otg_timer, musb_otg_timer_func, (unsigned long) musb); /* attach to the IRQ */ - if (request_irq(nIrq, musb->isr, 0, dev_name(dev), musb)) { + if (request_irq(nIrq, musb->isr, IRQF_SHARED, dev_name(dev), musb)) { dev_err(dev, "request_irq %d failed!\n", nIrq); status = -ENODEV; goto fail3; diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 5b708be6d1d1..3e98d4268a64 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -172,6 +172,7 @@ struct musb_io; */ struct musb_platform_ops { +#define MUSB_DA8XX BIT(8) #define MUSB_PRESERVE_SESSION BIT(7) #define MUSB_DMA_UX500 BIT(6) #define MUSB_DMA_CPPI41 BIT(5) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 355655f8a3fb..e7c8b1b8bf22 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -571,6 +571,10 @@ static int cppi41_dma_channel_abort(struct dma_channel *channel) } } + /* DA8xx Advisory 2.3.27: wait 250 ms before to start the teardown */ + if (musb->io.quirks & MUSB_DA8XX) + mdelay(250); + tdbit = 1 << cppi41_channel->port_num; if (is_tx) tdbit <<= 16; diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 61cef7511a50..3006f569c068 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -74,13 +74,6 @@ config AM335X_PHY_USB This driver provides PHY support for that phy which part for the AM335x SoC. -config SAMSUNG_USBPHY - tristate - help - Enable this to support Samsung USB phy helper driver for Samsung SoCs. - This driver provides common interface to interact, for Samsung USB 2.0 PHY - driver and later for Samsung USB 3.0 PHY driver. - config TWL6030_USB tristate "TWL6030 USB Transceiver Driver" depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index b433e5d89be4..e7c9ca8cafb0 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -14,7 +14,6 @@ obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o -obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index 392ab422163c..cf8f40ae6e01 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -44,6 +44,13 @@ #include "phy-fsl-usb.h" +#ifdef VERBOSE +#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \ + __func__, ## args) +#else +#define VDBG(stuff...) do {} while (0) +#endif + #define DRIVER_VERSION "Rev. 1.55" #define DRIVER_AUTHOR "Jerry Huang/Li Yang" #define DRIVER_DESC "Freescale USB OTG Transceiver Driver" diff --git a/drivers/usb/serial/aircable.c b/drivers/usb/serial/aircable.c index 80a9845cd93f..569c2200ba42 100644 --- a/drivers/usb/serial/aircable.c +++ b/drivers/usb/serial/aircable.c @@ -29,12 +29,6 @@ * is any other control code, I will simply check for the first * one. * - * The driver registers himself with the USB-serial core and the USB Core. I had - * to implement a probe function against USB-serial, because other way, the - * driver was attaching himself to both interfaces. I have tried with different - * configurations of usb_serial_driver with out exit, only the probe function - * could handle this correctly. - * * I have taken some info from a Greg Kroah-Hartman article: * http://www.linuxjournal.com/article/6573 * And from Linux Device Driver Kit CD, which is a great work, the authors taken @@ -93,30 +87,17 @@ static int aircable_prepare_write_buffer(struct usb_serial_port *port, return count + HCI_HEADER_LENGTH; } -static int aircable_probe(struct usb_serial *serial, - const struct usb_device_id *id) +static int aircable_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { - struct usb_host_interface *iface_desc = serial->interface-> - cur_altsetting; - struct usb_endpoint_descriptor *endpoint; - int num_bulk_out = 0; - int i; - - for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { - endpoint = &iface_desc->endpoint[i].desc; - if (usb_endpoint_is_bulk_out(endpoint)) { - dev_dbg(&serial->dev->dev, - "found bulk out on endpoint %d\n", i); - ++num_bulk_out; - } - } - - if (num_bulk_out == 0) { - dev_dbg(&serial->dev->dev, "Invalid interface, discarding\n"); + /* Ignore the first interface, which has no bulk endpoints. */ + if (epds->num_bulk_out == 0) { + dev_dbg(&serial->interface->dev, + "ignoring interface with no bulk-out endpoints\n"); return -ENODEV; } - return 0; + return 1; } static int aircable_process_packet(struct usb_serial_port *port, @@ -164,9 +145,8 @@ static struct usb_serial_driver aircable_device = { .name = "aircable", }, .id_table = id_table, - .num_ports = 1, .bulk_out_size = HCI_COMPLETE_FRAME, - .probe = aircable_probe, + .calc_num_ports = aircable_calc_num_ports, .process_read_urb = aircable_process_read_urb, .prepare_write_buffer = aircable_prepare_write_buffer, .throttle = usb_serial_generic_throttle, diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index 2779e59c30f1..0adbd38b4eea 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -122,19 +122,6 @@ static inline int calc_divisor(int bps) return (12000000 + 2*bps) / (4*bps); } -static int ark3116_attach(struct usb_serial *serial) -{ - /* make sure we have our end-points */ - if (serial->num_bulk_in == 0 || - serial->num_bulk_out == 0 || - serial->num_interrupt_in == 0) { - dev_err(&serial->interface->dev, "missing endpoint\n"); - return -ENODEV; - } - - return 0; -} - static int ark3116_port_probe(struct usb_serial_port *port) { struct usb_serial *serial = port->serial; @@ -671,7 +658,9 @@ static struct usb_serial_driver ark3116_device = { }, .id_table = id_table, .num_ports = 1, - .attach = ark3116_attach, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .port_probe = ark3116_port_probe, .port_remove = ark3116_port_remove, .set_termios = ark3116_set_termios, diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 80260b08398b..47fbd9f0c0c7 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -50,7 +50,6 @@ #define CYBERJACK_PRODUCT_ID 0x0100 /* Function prototypes */ -static int cyberjack_attach(struct usb_serial *serial); static int cyberjack_port_probe(struct usb_serial_port *port); static int cyberjack_port_remove(struct usb_serial_port *port); static int cyberjack_open(struct tty_struct *tty, @@ -78,7 +77,7 @@ static struct usb_serial_driver cyberjack_device = { .description = "Reiner SCT Cyberjack USB card reader", .id_table = id_table, .num_ports = 1, - .attach = cyberjack_attach, + .num_bulk_out = 1, .port_probe = cyberjack_port_probe, .port_remove = cyberjack_port_remove, .open = cyberjack_open, @@ -102,14 +101,6 @@ struct cyberjack_private { short wrsent; /* Data already sent */ }; -static int cyberjack_attach(struct usb_serial *serial) -{ - if (serial->num_bulk_out < serial->num_ports) - return -ENODEV; - - return 0; -} - static int cyberjack_port_probe(struct usb_serial_port *port) { struct cyberjack_private *priv; diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 6537d3ca2797..2ce39af32cfa 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -273,6 +273,8 @@ static struct usb_serial_driver digi_acceleport_2_device = { .description = "Digi 2 port USB adapter", .id_table = id_table_2, .num_ports = 3, + .num_bulk_in = 4, + .num_bulk_out = 4, .open = digi_open, .close = digi_close, .dtr_rts = digi_dtr_rts, @@ -302,6 +304,8 @@ static struct usb_serial_driver digi_acceleport_4_device = { .description = "Digi 4 port USB adapter", .id_table = id_table_4, .num_ports = 4, + .num_bulk_in = 5, + .num_bulk_out = 5, .open = digi_open, .close = digi_close, .write = digi_write, @@ -1251,27 +1255,8 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) static int digi_startup(struct usb_serial *serial) { - struct device *dev = &serial->interface->dev; struct digi_serial *serial_priv; int ret; - int i; - - /* check whether the device has the expected number of endpoints */ - if (serial->num_port_pointers < serial->type->num_ports + 1) { - dev_err(dev, "OOB endpoints missing\n"); - return -ENODEV; - } - - for (i = 0; i < serial->type->num_ports + 1 ; i++) { - if (!serial->port[i]->read_urb) { - dev_err(dev, "bulk-in endpoint missing\n"); - return -ENODEV; - } - if (!serial->port[i]->write_urb) { - dev_err(dev, "bulk-out endpoint missing\n"); - return -ENODEV; - } - } serial_priv = kzalloc(sizeof(*serial_priv), GFP_KERNEL); if (!serial_priv) diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c index 22f23a429a95..3d616a2a9f96 100644 --- a/drivers/usb/serial/f81534.c +++ b/drivers/usb/serial/f81534.c @@ -611,20 +611,30 @@ static int f81534_find_config_idx(struct usb_serial *serial, u8 *index) * The f81534_calc_num_ports() will run to "new style" with checking * F81534_PORT_UNAVAILABLE section. */ -static int f81534_calc_num_ports(struct usb_serial *serial) +static int f81534_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { + struct device *dev = &serial->interface->dev; + int size_bulk_in = usb_endpoint_maxp(epds->bulk_in[0]); + int size_bulk_out = usb_endpoint_maxp(epds->bulk_out[0]); u8 setting[F81534_CUSTOM_DATA_SIZE]; u8 setting_idx; u8 num_port = 0; int status; size_t i; + if (size_bulk_out != F81534_WRITE_BUFFER_SIZE || + size_bulk_in != F81534_MAX_RECEIVE_BLOCK_SIZE) { + dev_err(dev, "unsupported endpoint max packet size\n"); + return -ENODEV; + } + /* Check had custom setting */ status = f81534_find_config_idx(serial, &setting_idx); if (status) { dev_err(&serial->interface->dev, "%s: find idx failed: %d\n", __func__, status); - return 0; + return status; } /* @@ -640,7 +650,7 @@ static int f81534_calc_num_ports(struct usb_serial *serial) dev_err(&serial->interface->dev, "%s: get custom data failed: %d\n", __func__, status); - return 0; + return status; } dev_dbg(&serial->interface->dev, @@ -656,7 +666,7 @@ static int f81534_calc_num_ports(struct usb_serial *serial) dev_err(&serial->interface->dev, "%s: read failed: %d\n", __func__, status); - return 0; + return status; } dev_dbg(&serial->interface->dev, "%s: read default config\n", @@ -671,12 +681,24 @@ static int f81534_calc_num_ports(struct usb_serial *serial) ++num_port; } - if (num_port) - return num_port; + if (!num_port) { + dev_warn(&serial->interface->dev, + "no config found, assuming 4 ports\n"); + num_port = 4; /* Nothing found, oldest version IC */ + } + + /* + * Setup bulk-out endpoint multiplexing. All ports share the same + * bulk-out endpoint. + */ + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < F81534_NUM_PORT); + + for (i = 1; i < num_port; ++i) + epds->bulk_out[i] = epds->bulk_out[0]; - dev_warn(&serial->interface->dev, "%s: Read Failed. default 4 ports\n", - __func__); - return 4; /* Nothing found, oldest version IC */ + epds->num_bulk_out = num_port; + + return num_port; } static void f81534_set_termios(struct tty_struct *tty, @@ -1067,96 +1089,6 @@ static void f81534_write_usb_callback(struct urb *urb) } } -static int f81534_setup_ports(struct usb_serial *serial) -{ - struct usb_serial_port *port; - u8 port0_out_address; - int buffer_size; - size_t i; - - /* - * In our system architecture, we had 2 or 4 serial ports, - * but only get 1 set of bulk in/out endpoints. - * - * The usb-serial subsystem will generate port 0 data, - * but port 1/2/3 will not. It's will generate write URB and buffer - * by following code and use the port0 read URB for read operation. - */ - for (i = 1; i < serial->num_ports; ++i) { - port0_out_address = serial->port[0]->bulk_out_endpointAddress; - buffer_size = serial->port[0]->bulk_out_size; - port = serial->port[i]; - - if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) - return -ENOMEM; - - port->bulk_out_size = buffer_size; - port->bulk_out_endpointAddress = port0_out_address; - - port->write_urbs[0] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->write_urbs[0]) - return -ENOMEM; - - port->bulk_out_buffers[0] = kzalloc(buffer_size, GFP_KERNEL); - if (!port->bulk_out_buffers[0]) - return -ENOMEM; - - usb_fill_bulk_urb(port->write_urbs[0], serial->dev, - usb_sndbulkpipe(serial->dev, - port0_out_address), - port->bulk_out_buffers[0], buffer_size, - serial->type->write_bulk_callback, port); - - port->write_urb = port->write_urbs[0]; - port->bulk_out_buffer = port->bulk_out_buffers[0]; - } - - return 0; -} - -static int f81534_probe(struct usb_serial *serial, - const struct usb_device_id *id) -{ - struct usb_endpoint_descriptor *endpoint; - struct usb_host_interface *iface_desc; - struct device *dev; - int num_bulk_in = 0; - int num_bulk_out = 0; - int size_bulk_in = 0; - int size_bulk_out = 0; - int i; - - dev = &serial->interface->dev; - iface_desc = serial->interface->cur_altsetting; - - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_bulk_in(endpoint)) { - ++num_bulk_in; - size_bulk_in = usb_endpoint_maxp(endpoint); - } - - if (usb_endpoint_is_bulk_out(endpoint)) { - ++num_bulk_out; - size_bulk_out = usb_endpoint_maxp(endpoint); - } - } - - if (num_bulk_in != 1 || num_bulk_out != 1) { - dev_err(dev, "expected endpoints not found\n"); - return -ENODEV; - } - - if (size_bulk_out != F81534_WRITE_BUFFER_SIZE || - size_bulk_in != F81534_MAX_RECEIVE_BLOCK_SIZE) { - dev_err(dev, "unsupported endpoint max packet size\n"); - return -ENODEV; - } - - return 0; -} - static int f81534_attach(struct usb_serial *serial) { struct f81534_serial_private *serial_priv; @@ -1173,10 +1105,6 @@ static int f81534_attach(struct usb_serial *serial) mutex_init(&serial_priv->urb_mutex); - status = f81534_setup_ports(serial); - if (status) - return status; - /* Check had custom setting */ status = f81534_find_config_idx(serial, &serial_priv->setting_idx); if (status) { @@ -1380,12 +1308,13 @@ static struct usb_serial_driver f81534_device = { }, .description = DRIVER_DESC, .id_table = f81534_id_table, + .num_bulk_in = 1, + .num_bulk_out = 1, .open = f81534_open, .close = f81534_close, .write = f81534_write, .tx_empty = f81534_tx_empty, .calc_num_ports = f81534_calc_num_ports, - .probe = f81534_probe, .attach = f81534_attach, .port_probe = f81534_port_probe, .dtr_rts = f81534_dtr_rts, diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index c540de15aad2..d38780fa8788 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -873,6 +873,7 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, USB_CLASS_VENDOR_SPEC, USB_SUBCLASS_VENDOR_SPEC, 0x00) }, + { USB_DEVICE_INTERFACE_NUMBER(ACTEL_VID, MICROSEMI_ARROW_SF2PLUS_BOARD_PID, 2) }, { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, @@ -1406,6 +1407,9 @@ static int write_latency_timer(struct usb_serial_port *port) int rv; int l = priv->latency; + if (priv->chip_type == SIO || priv->chip_type == FT8U232AM) + return -EINVAL; + if (priv->flags & ASYNC_LOW_LATENCY) l = 1; @@ -1422,7 +1426,7 @@ static int write_latency_timer(struct usb_serial_port *port) return rv; } -static int read_latency_timer(struct usb_serial_port *port) +static int _read_latency_timer(struct usb_serial_port *port) { struct ftdi_private *priv = usb_get_serial_port_data(port); struct usb_device *udev = port->serial->dev; @@ -1440,11 +1444,10 @@ static int read_latency_timer(struct usb_serial_port *port) 0, priv->interface, buf, 1, WDR_TIMEOUT); if (rv < 1) { - dev_err(&port->dev, "Unable to read latency timer: %i\n", rv); if (rv >= 0) rv = -EIO; } else { - priv->latency = buf[0]; + rv = buf[0]; } kfree(buf); @@ -1452,6 +1455,25 @@ static int read_latency_timer(struct usb_serial_port *port) return rv; } +static int read_latency_timer(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + int rv; + + if (priv->chip_type == SIO || priv->chip_type == FT8U232AM) + return -EINVAL; + + rv = _read_latency_timer(port); + if (rv < 0) { + dev_err(&port->dev, "Unable to read latency timer: %i\n", rv); + return rv; + } + + priv->latency = rv; + + return 0; +} + static int get_serial_info(struct usb_serial_port *port, struct serial_struct __user *retinfo) { @@ -1603,9 +1625,19 @@ static void ftdi_determine_type(struct usb_serial_port *port) priv->baud_base = 12000000 / 16; } else if (version < 0x400) { /* Assume it's an FT8U232AM (or FT8U245AM) */ - /* (It might be a BM because of the iSerialNumber bug, - * but it will still work as an AM device.) */ priv->chip_type = FT8U232AM; + /* + * It might be a BM type because of the iSerialNumber bug. + * If iSerialNumber==0 and the latency timer is readable, + * assume it is BM type. + */ + if (udev->descriptor.iSerialNumber == 0 && + _read_latency_timer(port) >= 0) { + dev_dbg(&port->dev, + "%s: has latency timer so not an AM type\n", + __func__); + priv->chip_type = FT232BM; + } } else if (version < 0x600) { /* Assume it's an FT232BM (or FT245BM) */ priv->chip_type = FT232BM; @@ -1685,9 +1717,12 @@ static ssize_t latency_timer_store(struct device *dev, { struct usb_serial_port *port = to_usb_serial_port(dev); struct ftdi_private *priv = usb_get_serial_port_data(port); - int v = simple_strtoul(valbuf, NULL, 10); + u8 v; int rv; + if (kstrtou8(valbuf, 10, &v)) + return -EINVAL; + priv->latency = v; rv = write_latency_timer(port); if (rv < 0) @@ -1704,10 +1739,13 @@ static ssize_t store_event_char(struct device *dev, struct usb_serial_port *port = to_usb_serial_port(dev); struct ftdi_private *priv = usb_get_serial_port_data(port); struct usb_device *udev = port->serial->dev; - int v = simple_strtoul(valbuf, NULL, 10); + unsigned int v; int rv; - dev_dbg(&port->dev, "%s: setting event char = %i\n", __func__, v); + if (kstrtouint(valbuf, 0, &v) || v >= 0x200) + return -EINVAL; + + dev_dbg(&port->dev, "%s: setting event char = 0x%03x\n", __func__, v); rv = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 48ee04c94a75..71fb9e59db71 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -873,6 +873,12 @@ #define FIC_VID 0x1457 #define FIC_NEO1973_DEBUG_PID 0x5118 +/* + * Actel / Microsemi + */ +#define ACTEL_VID 0x1514 +#define MICROSEMI_ARROW_SF2PLUS_BOARD_PID 0x2008 + /* Olimex */ #define OLIMEX_VID 0x15BA #define OLIMEX_ARM_USB_OCD_PID 0x0003 diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 49ce2be90fa0..35cb8c0e584f 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -37,13 +37,41 @@ MODULE_PARM_DESC(product, "User specified USB idProduct"); static struct usb_device_id generic_device_ids[2]; /* Initially all zeroes. */ -struct usb_serial_driver usb_serial_generic_device = { +static int usb_serial_generic_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct device *dev = &serial->interface->dev; + + dev_info(dev, "The \"generic\" usb-serial driver is only for testing and one-off prototypes.\n"); + dev_info(dev, "Tell linux-usb@vger.kernel.org to add your device to a proper driver.\n"); + + return 0; +} + +static int usb_serial_generic_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct device *dev = &serial->interface->dev; + int num_ports; + + num_ports = max(epds->num_bulk_in, epds->num_bulk_out); + + if (num_ports == 0) { + dev_err(dev, "device has no bulk endpoints\n"); + return -ENODEV; + } + + return num_ports; +} + +static struct usb_serial_driver usb_serial_generic_device = { .driver = { .owner = THIS_MODULE, .name = "generic", }, .id_table = generic_device_ids, - .num_ports = 1, + .probe = usb_serial_generic_probe, + .calc_num_ports = usb_serial_generic_calc_num_ports, .throttle = usb_serial_generic_throttle, .unthrottle = usb_serial_generic_unthrottle, .resume = usb_serial_generic_resume, diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index bb7673e80a57..bdf8bd814a9a 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -1544,11 +1544,6 @@ static void edge_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { struct edgeport_port *edge_port = usb_get_serial_port_data(port); - unsigned int cflag; - - cflag = tty->termios.c_cflag; - dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, tty->termios.c_cflag, tty->termios.c_iflag); - dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, old_termios->c_cflag, old_termios->c_iflag); if (edge_port == NULL) return; @@ -2844,14 +2839,9 @@ static int edge_startup(struct usb_serial *serial) bool interrupt_in_found; bool bulk_in_found; bool bulk_out_found; - static __u32 descriptor[3] = { EDGE_COMPATIBILITY_MASK0, - EDGE_COMPATIBILITY_MASK1, - EDGE_COMPATIBILITY_MASK2 }; - - if (serial->num_bulk_in < 1 || serial->num_interrupt_in < 1) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } + static const __u32 descriptor[3] = { EDGE_COMPATIBILITY_MASK0, + EDGE_COMPATIBILITY_MASK1, + EDGE_COMPATIBILITY_MASK2 }; dev = serial->dev; @@ -3120,6 +3110,9 @@ static struct usb_serial_driver edgeport_2port_device = { .description = "Edgeport 2 port adapter", .id_table = edgeport_2port_id_table, .num_ports = 2, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, @@ -3152,6 +3145,9 @@ static struct usb_serial_driver edgeport_4port_device = { .description = "Edgeport 4 port adapter", .id_table = edgeport_4port_id_table, .num_ports = 4, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, @@ -3184,6 +3180,9 @@ static struct usb_serial_driver edgeport_8port_device = { .description = "Edgeport 8 port adapter", .id_table = edgeport_8port_id_table, .num_ports = 8, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, @@ -3216,6 +3215,9 @@ static struct usb_serial_driver epic_device = { .description = "EPiC device", .id_table = Epic_port_id_table, .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index a76b95d32157..87798e625d6c 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -1933,13 +1933,6 @@ static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) if (edge_serial->num_ports_open == 0) { /* we are the first port to open, post the interrupt urb */ urb = edge_serial->serial->port[0]->interrupt_in_urb; - if (!urb) { - dev_err(&port->dev, - "%s - no interrupt urb present, exiting\n", - __func__); - status = -EINVAL; - goto release_es_lock; - } urb->context = edge_serial; status = usb_submit_urb(urb, GFP_KERNEL); if (status) { @@ -1959,12 +1952,6 @@ static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) /* start up our bulk read urb */ urb = port->read_urb; - if (!urb) { - dev_err(&port->dev, "%s - no read urb present, exiting\n", - __func__); - status = -EINVAL; - goto unlink_int_urb; - } edge_port->ep_read_urb_state = EDGE_READ_URB_RUNNING; urb->context = edge_port; status = usb_submit_urb(urb, GFP_KERNEL); @@ -2385,14 +2372,6 @@ static void edge_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { struct edgeport_port *edge_port = usb_get_serial_port_data(port); - unsigned int cflag; - - cflag = tty->termios.c_cflag; - - dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, - tty->termios.c_cflag, tty->termios.c_iflag); - dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, - old_termios->c_cflag, old_termios->c_iflag); if (edge_port == NULL) return; @@ -2544,19 +2523,31 @@ static void edge_heartbeat_work(struct work_struct *work) edge_heartbeat_schedule(serial); } -static int edge_startup(struct usb_serial *serial) +static int edge_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { - struct edgeport_serial *edge_serial; - int status; - u16 product_id; + struct device *dev = &serial->interface->dev; + unsigned char num_ports = serial->type->num_ports; /* Make sure we have the required endpoints when in download mode. */ if (serial->interface->cur_altsetting->desc.bNumEndpoints > 1) { - if (serial->num_bulk_in < serial->num_ports || - serial->num_bulk_out < serial->num_ports) + if (epds->num_bulk_in < num_ports || + epds->num_bulk_out < num_ports || + epds->num_interrupt_in < 1) { + dev_err(dev, "required endpoints missing\n"); return -ENODEV; + } } + return num_ports; +} + +static int edge_startup(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial; + int status; + u16 product_id; + /* create our private serial structure */ edge_serial = kzalloc(sizeof(struct edgeport_serial), GFP_KERNEL); if (!edge_serial) @@ -2736,11 +2727,13 @@ static struct usb_serial_driver edgeport_1port_device = { .description = "Edgeport TI 1 port adapter", .id_table = edgeport_1port_id_table, .num_ports = 1, + .num_bulk_out = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, + .calc_num_ports = edge_calc_num_ports, .disconnect = edge_disconnect, .release = edge_release, .port_probe = edge_port_probe, @@ -2773,11 +2766,13 @@ static struct usb_serial_driver edgeport_2port_device = { .description = "Edgeport TI 2 port adapter", .id_table = edgeport_2port_id_table, .num_ports = 2, + .num_bulk_out = 1, .open = edge_open, .close = edge_close, .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, + .calc_num_ports = edge_calc_num_ports, .disconnect = edge_disconnect, .release = edge_release, .port_probe = edge_port_probe, diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c index ec1b8f2c1183..cde0dcdce9c4 100644 --- a/drivers/usb/serial/ipaq.c +++ b/drivers/usb/serial/ipaq.c @@ -33,7 +33,8 @@ static int initial_wait; /* Function prototypes for an ipaq */ static int ipaq_open(struct tty_struct *tty, struct usb_serial_port *port); -static int ipaq_calc_num_ports(struct usb_serial *serial); +static int ipaq_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); static int ipaq_startup(struct usb_serial *serial); static const struct usb_device_id ipaq_id_table[] = { @@ -550,42 +551,38 @@ static int ipaq_open(struct tty_struct *tty, return usb_serial_generic_open(tty, port); } -static int ipaq_calc_num_ports(struct usb_serial *serial) +static int ipaq_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { /* - * some devices have 3 endpoints, the 3rd of which - * must be ignored as it would make the core - * create a second port which oopses when used + * Some of the devices in ipaq_id_table[] are composite, and we + * shouldn't bind to all the interfaces. This test will rule out + * some obviously invalid possibilities. */ - int ipaq_num_ports = 1; - - dev_dbg(&serial->dev->dev, "%s - numberofendpoints: %d\n", __func__, - (int)serial->interface->cur_altsetting->desc.bNumEndpoints); + if (epds->num_bulk_in == 0 || epds->num_bulk_out == 0) + return -ENODEV; /* - * a few devices have 4 endpoints, seemingly Yakuma devices, - * and we need the second pair, so let them have 2 ports - * - * TODO: can we drop port 1 ? + * A few devices have four endpoints, seemingly Yakuma devices, and + * we need the second pair. */ - if (serial->interface->cur_altsetting->desc.bNumEndpoints > 3) { - ipaq_num_ports = 2; + if (epds->num_bulk_in > 1 && epds->num_bulk_out > 1) { + epds->bulk_in[0] = epds->bulk_in[1]; + epds->bulk_out[0] = epds->bulk_out[1]; } - return ipaq_num_ports; -} + /* + * Other devices have 3 endpoints, but we only use the first bulk in + * and out endpoints. + */ + epds->num_bulk_in = 1; + epds->num_bulk_out = 1; + return 1; +} static int ipaq_startup(struct usb_serial *serial) { - /* Some of the devices in ipaq_id_table[] are composite, and we - * shouldn't bind to all the interfaces. This test will rule out - * some obviously invalid possibilities. - */ - if (serial->num_bulk_in < serial->num_ports || - serial->num_bulk_out < serial->num_ports) - return -ENODEV; - if (serial->dev->actconfig->desc.bConfigurationValue != 1) { /* * FIXME: HP iPaq rx3715, possibly others, have 1 config that @@ -597,10 +594,6 @@ static int ipaq_startup(struct usb_serial *serial) return -ENODEV; } - dev_dbg(&serial->dev->dev, - "%s - iPAQ module configured for %d ports\n", __func__, - serial->num_ports); - return usb_reset_configuration(serial->dev); } diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index 030390f37b0a..18fc992a245f 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -68,16 +68,6 @@ struct iuu_private { u32 clk; }; -static int iuu_attach(struct usb_serial *serial) -{ - unsigned char num_ports = serial->num_ports; - - if (serial->num_bulk_in < num_ports || serial->num_bulk_out < num_ports) - return -ENODEV; - - return 0; -} - static int iuu_port_probe(struct usb_serial_port *port) { struct iuu_private *priv; @@ -598,9 +588,8 @@ static void read_buf_callback(struct urb *urb) } dev_dbg(&port->dev, "%s - %i chars to write\n", __func__, urb->actual_length); - if (data == NULL) - dev_dbg(&port->dev, "%s - data is NULL !!!\n", __func__); - if (urb->actual_length && data) { + + if (urb->actual_length) { tty_insert_flip_string(&port->port, data, urb->actual_length); tty_flip_buffer_push(&port->port); } @@ -665,10 +654,8 @@ static void iuu_uart_read_callback(struct urb *urb) /* error stop all */ return; } - if (data == NULL) - dev_dbg(&port->dev, "%s - data is NULL !!!\n", __func__); - if (urb->actual_length == 1 && data != NULL) + if (urb->actual_length == 1) len = (int) data[0]; if (urb->actual_length > 1) { @@ -1183,6 +1170,8 @@ static struct usb_serial_driver iuu_device = { }, .id_table = id_table, .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, .bulk_in_size = 512, .bulk_out_size = 512, .open = iuu_open, @@ -1193,7 +1182,6 @@ static struct usb_serial_driver iuu_device = { .tiocmset = iuu_tiocmset, .set_termios = iuu_set_termios, .init_termios = iuu_init_termios, - .attach = iuu_attach, .port_probe = iuu_port_probe, .port_remove = iuu_port_remove, }; diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index d2dab2a341b8..196908dd25a1 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -708,19 +708,6 @@ MODULE_FIRMWARE("keyspan_pda/keyspan_pda.fw"); MODULE_FIRMWARE("keyspan_pda/xircom_pgs.fw"); #endif -static int keyspan_pda_attach(struct usb_serial *serial) -{ - unsigned char num_ports = serial->num_ports; - - if (serial->num_bulk_out < num_ports || - serial->num_interrupt_in < num_ports) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - - return 0; -} - static int keyspan_pda_port_probe(struct usb_serial_port *port) { @@ -784,6 +771,8 @@ static struct usb_serial_driver keyspan_pda_device = { .description = "Keyspan PDA", .id_table = id_table_std, .num_ports = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .dtr_rts = keyspan_pda_dtr_rts, .open = keyspan_pda_open, .close = keyspan_pda_close, @@ -798,7 +787,6 @@ static struct usb_serial_driver keyspan_pda_device = { .break_ctl = keyspan_pda_break_ctl, .tiocmget = keyspan_pda_tiocmget, .tiocmset = keyspan_pda_tiocmset, - .attach = keyspan_pda_attach, .port_probe = keyspan_pda_port_probe, .port_remove = keyspan_pda_port_remove, }; diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 813035f51fe7..3024b9b25360 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -51,7 +51,6 @@ /* Function prototypes */ -static int kobil_attach(struct usb_serial *serial); static int kobil_port_probe(struct usb_serial_port *probe); static int kobil_port_remove(struct usb_serial_port *probe); static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port); @@ -87,7 +86,7 @@ static struct usb_serial_driver kobil_device = { .description = "KOBIL USB smart card terminal", .id_table = id_table, .num_ports = 1, - .attach = kobil_attach, + .num_interrupt_out = 1, .port_probe = kobil_port_probe, .port_remove = kobil_port_remove, .ioctl = kobil_ioctl, @@ -115,16 +114,6 @@ struct kobil_private { }; -static int kobil_attach(struct usb_serial *serial) -{ - if (serial->num_interrupt_out < serial->num_ports) { - dev_err(&serial->interface->dev, "missing interrupt-out endpoint\n"); - return -ENODEV; - } - - return 0; -} - static int kobil_port_probe(struct usb_serial_port *port) { struct usb_serial *serial = port->serial; diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index f075121c6e32..a453965f9e9a 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -973,11 +973,24 @@ static void mos7720_bulk_out_data_callback(struct urb *urb) tty_port_tty_wakeup(&mos7720_port->port->port); } -static int mos77xx_calc_num_ports(struct usb_serial *serial) +static int mos77xx_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { u16 product = le16_to_cpu(serial->dev->descriptor.idProduct); - if (product == MOSCHIP_DEVICE_ID_7715) + + if (product == MOSCHIP_DEVICE_ID_7715) { + /* + * The 7715 uses the first bulk in/out endpoint pair for the + * parallel port, and the second for the serial port. We swap + * the endpoint descriptors here so that the the first and + * only registered port structure uses the serial-port + * endpoints. + */ + swap(epds->bulk_in[0], epds->bulk_in[1]); + swap(epds->bulk_out[0], epds->bulk_out[1]); + return 1; + } return 2; } @@ -1395,7 +1408,7 @@ struct divisor_table_entry { /* Define table of divisors for moschip 7720 hardware * * These assume a 3.6864MHz crystal, the standard /16, and * * MCR.7 = 0. */ -static struct divisor_table_entry divisor_table[] = { +static const struct divisor_table_entry divisor_table[] = { { 50, 2304}, { 110, 1047}, /* 2094.545455 => 230450 => .0217 % over */ { 134, 857}, /* 1713.011152 => 230398.5 => .00065% under */ @@ -1675,7 +1688,6 @@ static void mos7720_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { int status; - unsigned int cflag; struct usb_serial *serial; struct moschip_port *mos7720_port; @@ -1691,16 +1703,6 @@ static void mos7720_set_termios(struct tty_struct *tty, return; } - dev_dbg(&port->dev, "setting termios - ASPIRE\n"); - - cflag = tty->termios.c_cflag; - - dev_dbg(&port->dev, "%s - cflag %08x iflag %08x\n", __func__, - tty->termios.c_cflag, RELEVANT_IFLAG(tty->termios.c_iflag)); - - dev_dbg(&port->dev, "%s - old cflag %08x old iflag %08x\n", __func__, - old_termios->c_cflag, RELEVANT_IFLAG(old_termios->c_iflag)); - /* change the port settings to the new ones specified */ change_port_settings(tty, mos7720_port, old_termios); @@ -1900,54 +1902,24 @@ static int mos7720_startup(struct usb_serial *serial) u16 product; int ret_val; - if (serial->num_bulk_in < 2 || serial->num_bulk_out < 2) { - dev_err(&serial->interface->dev, "missing bulk endpoints\n"); - return -ENODEV; - } - product = le16_to_cpu(serial->dev->descriptor.idProduct); dev = serial->dev; - /* - * The 7715 uses the first bulk in/out endpoint pair for the parallel - * port, and the second for the serial port. Because the usbserial core - * assumes both pairs are serial ports, we must engage in a bit of - * subterfuge and swap the pointers for ports 0 and 1 in order to make - * port 0 point to the serial port. However, both moschip devices use a - * single interrupt-in endpoint for both ports (as mentioned a little - * further down), and this endpoint was assigned to port 0. So after - * the swap, we must copy the interrupt endpoint elements from port 1 - * (as newly assigned) to port 0, and null out port 1 pointers. - */ - if (product == MOSCHIP_DEVICE_ID_7715) { - struct usb_serial_port *tmp = serial->port[0]; - serial->port[0] = serial->port[1]; - serial->port[1] = tmp; - serial->port[0]->interrupt_in_urb = tmp->interrupt_in_urb; - serial->port[0]->interrupt_in_buffer = tmp->interrupt_in_buffer; - serial->port[0]->interrupt_in_endpointAddress = - tmp->interrupt_in_endpointAddress; - serial->port[1]->interrupt_in_urb = NULL; - serial->port[1]->interrupt_in_buffer = NULL; - - if (serial->port[0]->interrupt_in_urb) { - struct urb *urb = serial->port[0]->interrupt_in_urb; - - urb->complete = mos7715_interrupt_callback; - } - } - /* setting configuration feature to one */ usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), (__u8)0x03, 0x00, 0x01, 0x00, NULL, 0x00, 5000); -#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT if (product == MOSCHIP_DEVICE_ID_7715) { + struct urb *urb = serial->port[0]->interrupt_in_urb; + + urb->complete = mos7715_interrupt_callback; + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT ret_val = mos7715_parport_init(serial); if (ret_val < 0) return ret_val; - } #endif + } /* start the interrupt urb */ ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL); if (ret_val) { @@ -2039,6 +2011,9 @@ static struct usb_serial_driver moschip7720_2port_driver = { }, .description = "Moschip 2 port adapter", .id_table = id_table, + .num_bulk_in = 2, + .num_bulk_out = 2, + .num_interrupt_in = 1, .calc_num_ports = mos77xx_calc_num_ports, .open = mos7720_open, .close = mos7720_close, diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 3821c53fcee9..e8669aae14b3 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -1868,7 +1868,6 @@ static void mos7840_set_termios(struct tty_struct *tty, struct ktermios *old_termios) { int status; - unsigned int cflag; struct usb_serial *serial; struct moschip_port *mos7840_port; @@ -1890,15 +1889,6 @@ static void mos7840_set_termios(struct tty_struct *tty, return; } - dev_dbg(&port->dev, "%s", "setting termios - \n"); - - cflag = tty->termios.c_cflag; - - dev_dbg(&port->dev, "%s - clfag %08x iflag %08x\n", __func__, - tty->termios.c_cflag, RELEVANT_IFLAG(tty->termios.c_iflag)); - dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, - old_termios->c_cflag, RELEVANT_IFLAG(old_termios->c_iflag)); - /* change the port settings to the new ones specified */ mos7840_change_port_settings(tty, mos7840_port, old_termios); @@ -2104,26 +2094,27 @@ out: return 0; } -static int mos7840_calc_num_ports(struct usb_serial *serial) +static int mos7840_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { int device_type = (unsigned long)usb_get_serial_data(serial); - int mos7840_num_ports; + int num_ports; - mos7840_num_ports = (device_type >> 4) & 0x000F; + num_ports = (device_type >> 4) & 0x000F; - return mos7840_num_ports; -} + /* + * num_ports is currently never zero as device_type is one of + * MOSCHIP_DEVICE_ID_78{1,2,4}0. + */ + if (num_ports == 0) + return -ENODEV; -static int mos7840_attach(struct usb_serial *serial) -{ - if (serial->num_bulk_in < serial->num_ports || - serial->num_bulk_out < serial->num_ports || - serial->num_interrupt_in < 1) { + if (epds->num_bulk_in < num_ports || epds->num_bulk_out < num_ports) { dev_err(&serial->interface->dev, "missing endpoints\n"); return -ENODEV; } - return 0; + return num_ports; } static int mos7840_port_probe(struct usb_serial_port *port) @@ -2384,7 +2375,7 @@ static struct usb_serial_driver moschip7840_4port_device = { }, .description = DRIVER_DESC, .id_table = id_table, - .num_ports = 4, + .num_interrupt_in = 1, .open = mos7840_open, .close = mos7840_close, .write = mos7840_write, @@ -2401,7 +2392,6 @@ static struct usb_serial_driver moschip7840_4port_device = { .tiocmset = mos7840_tiocmset, .tiocmiwait = usb_serial_generic_tiocmiwait, .get_icount = usb_serial_generic_get_icount, - .attach = mos7840_attach, .port_probe = mos7840_port_probe, .port_remove = mos7840_port_remove, .read_bulk_callback = mos7840_bulk_in_callback, diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index c88215a0fa3d..3aef091fe88b 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -946,20 +946,39 @@ out: * Determine how many ports this device has dynamically. It will be * called after the probe() callback is called, but before attach(). */ -static int mxuport_calc_num_ports(struct usb_serial *serial) +static int mxuport_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { unsigned long features = (unsigned long)usb_get_serial_data(serial); + int num_ports; + int i; - if (features & MX_UPORT_2_PORT) - return 2; - if (features & MX_UPORT_4_PORT) - return 4; - if (features & MX_UPORT_8_PORT) - return 8; - if (features & MX_UPORT_16_PORT) - return 16; + if (features & MX_UPORT_2_PORT) { + num_ports = 2; + } else if (features & MX_UPORT_4_PORT) { + num_ports = 4; + } else if (features & MX_UPORT_8_PORT) { + num_ports = 8; + } else if (features & MX_UPORT_16_PORT) { + num_ports = 16; + } else { + dev_warn(&serial->interface->dev, + "unknown device, assuming two ports\n"); + num_ports = 2; + } - return 0; + /* + * Setup bulk-out endpoint multiplexing. All ports share the same + * bulk-out endpoint. + */ + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < 16); + + for (i = 1; i < num_ports; ++i) + epds->bulk_out[i] = epds->bulk_out[0]; + + epds->num_bulk_out = num_ports; + + return num_ports; } /* Get the version of the firmware currently running. */ @@ -1142,102 +1161,11 @@ static int mxuport_port_probe(struct usb_serial_port *port) port->port_number); } -static int mxuport_alloc_write_urb(struct usb_serial *serial, - struct usb_serial_port *port, - struct usb_serial_port *port0, - int j) -{ - struct usb_device *dev = interface_to_usbdev(serial->interface); - - set_bit(j, &port->write_urbs_free); - port->write_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->write_urbs[j]) - return -ENOMEM; - - port->bulk_out_buffers[j] = kmalloc(port0->bulk_out_size, GFP_KERNEL); - if (!port->bulk_out_buffers[j]) - return -ENOMEM; - - usb_fill_bulk_urb(port->write_urbs[j], dev, - usb_sndbulkpipe(dev, port->bulk_out_endpointAddress), - port->bulk_out_buffers[j], - port->bulk_out_size, - serial->type->write_bulk_callback, - port); - return 0; -} - - -static int mxuport_alloc_write_urbs(struct usb_serial *serial, - struct usb_serial_port *port, - struct usb_serial_port *port0) -{ - int j; - int ret; - - for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) { - ret = mxuport_alloc_write_urb(serial, port, port0, j); - if (ret) - return ret; - } - return 0; -} - - static int mxuport_attach(struct usb_serial *serial) { struct usb_serial_port *port0 = serial->port[0]; struct usb_serial_port *port1 = serial->port[1]; - struct usb_serial_port *port; int err; - int i; - int j; - - /* - * Throw away all but the first allocated write URBs so we can - * set them up again to fit the multiplexing scheme. - */ - for (i = 1; i < serial->num_bulk_out; ++i) { - port = serial->port[i]; - for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) { - usb_free_urb(port->write_urbs[j]); - kfree(port->bulk_out_buffers[j]); - port->write_urbs[j] = NULL; - port->bulk_out_buffers[j] = NULL; - } - port->write_urbs_free = 0; - } - - /* - * All write data is sent over the first bulk out endpoint, - * with an added header to indicate the port. Allocate URBs - * for each port to the first bulk out endpoint. - */ - for (i = 1; i < serial->num_ports; ++i) { - port = serial->port[i]; - port->bulk_out_size = port0->bulk_out_size; - port->bulk_out_endpointAddress = - port0->bulk_out_endpointAddress; - - err = mxuport_alloc_write_urbs(serial, port, port0); - if (err) - return err; - - port->write_urb = port->write_urbs[0]; - port->bulk_out_buffer = port->bulk_out_buffers[0]; - - /* - * Ensure each port has a fifo. The framework only - * allocates a fifo to ports with a bulk out endpoint, - * where as we need one for every port. - */ - if (!kfifo_initialized(&port->write_fifo)) { - err = kfifo_alloc(&port->write_fifo, PAGE_SIZE, - GFP_KERNEL); - if (err) - return err; - } - } /* * All data from the ports is received on the first bulk in @@ -1366,7 +1294,8 @@ static struct usb_serial_driver mxuport_device = { }, .description = "MOXA UPort", .id_table = mxuport_idtable, - .num_ports = 0, + .num_bulk_in = 2, + .num_bulk_out = 1, .probe = mxuport_probe, .port_probe = mxuport_port_probe, .attach = mxuport_attach, diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c index dd706953b466..efcd7feed6f4 100644 --- a/drivers/usb/serial/omninet.c +++ b/drivers/usb/serial/omninet.c @@ -1,6 +1,8 @@ /* * USB ZyXEL omni.net LCD PLUS driver * + * Copyright (C) 2013,2017 Johan Hovold <johan@kernel.org> + * * 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. @@ -32,12 +34,10 @@ /* function prototypes */ static void omninet_process_read_urb(struct urb *urb); -static void omninet_write_bulk_callback(struct urb *urb); -static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port, - const unsigned char *buf, int count); -static int omninet_write_room(struct tty_struct *tty); -static void omninet_disconnect(struct usb_serial *serial); -static int omninet_attach(struct usb_serial *serial); +static int omninet_prepare_write_buffer(struct usb_serial_port *port, + void *buf, size_t count); +static int omninet_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); static int omninet_port_probe(struct usb_serial_port *port); static int omninet_port_remove(struct usb_serial_port *port); @@ -55,15 +55,12 @@ static struct usb_serial_driver zyxel_omninet_device = { }, .description = "ZyXEL - omni.net lcd plus usb", .id_table = id_table, - .num_ports = 1, - .attach = omninet_attach, + .num_bulk_out = 2, + .calc_num_ports = omninet_calc_num_ports, .port_probe = omninet_port_probe, .port_remove = omninet_port_remove, - .write = omninet_write, - .write_room = omninet_write_room, - .write_bulk_callback = omninet_write_bulk_callback, .process_read_urb = omninet_process_read_urb, - .disconnect = omninet_disconnect, + .prepare_write_buffer = omninet_prepare_write_buffer, }; static struct usb_serial_driver * const serial_drivers[] = { @@ -104,15 +101,14 @@ struct omninet_data { __u8 od_outseq; /* Sequence number for bulk_out URBs */ }; -static int omninet_attach(struct usb_serial *serial) +static int omninet_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { - /* The second bulk-out endpoint is used for writing. */ - if (serial->num_bulk_out < 2) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } + /* We need only the second bulk-out for our single-port device. */ + epds->bulk_out[0] = epds->bulk_out[1]; + epds->num_bulk_out = 1; - return 0; + return 1; } static int omninet_port_probe(struct usb_serial_port *port) @@ -159,96 +155,24 @@ static void omninet_process_read_urb(struct urb *urb) tty_flip_buffer_push(&port->port); } -static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port, - const unsigned char *buf, int count) +static int omninet_prepare_write_buffer(struct usb_serial_port *port, + void *buf, size_t count) { - struct usb_serial *serial = port->serial; - struct usb_serial_port *wport = serial->port[1]; - struct omninet_data *od = usb_get_serial_port_data(port); - struct omninet_header *header = (struct omninet_header *) - wport->write_urb->transfer_buffer; - - int result; - - if (count == 0) { - dev_dbg(&port->dev, "%s - write request of 0 bytes\n", __func__); - return 0; - } - - if (!test_and_clear_bit(0, &port->write_urbs_free)) { - dev_dbg(&port->dev, "%s - already writing\n", __func__); - return 0; - } - - count = (count > OMNINET_PAYLOADSIZE) ? OMNINET_PAYLOADSIZE : count; - - memcpy(wport->write_urb->transfer_buffer + OMNINET_HEADERLEN, - buf, count); - - usb_serial_debug_data(&port->dev, __func__, count, - wport->write_urb->transfer_buffer); - - header->oh_seq = od->od_outseq++; - header->oh_len = count; - header->oh_xxx = 0x03; - header->oh_pad = 0x00; - - /* send the data out the bulk port, always 64 bytes */ - wport->write_urb->transfer_buffer_length = OMNINET_BULKOUTSIZE; - - result = usb_submit_urb(wport->write_urb, GFP_ATOMIC); - if (result) { - set_bit(0, &wport->write_urbs_free); - dev_err_console(port, - "%s - failed submitting write urb, error %d\n", - __func__, result); - } else - result = count; - - return result; -} + struct omninet_header *header = buf; + count = min_t(size_t, count, OMNINET_PAYLOADSIZE); -static int omninet_write_room(struct tty_struct *tty) -{ - struct usb_serial_port *port = tty->driver_data; - struct usb_serial *serial = port->serial; - struct usb_serial_port *wport = serial->port[1]; - - int room = 0; /* Default: no room */ - - if (test_bit(0, &wport->write_urbs_free)) - room = wport->bulk_out_size - OMNINET_HEADERLEN; - - dev_dbg(&port->dev, "%s - returns %d\n", __func__, room); - - return room; -} - -static void omninet_write_bulk_callback(struct urb *urb) -{ -/* struct omninet_header *header = (struct omninet_header *) - urb->transfer_buffer; */ - struct usb_serial_port *port = urb->context; - int status = urb->status; - - set_bit(0, &port->write_urbs_free); - if (status) { - dev_dbg(&port->dev, "%s - nonzero write bulk status received: %d\n", - __func__, status); - return; - } + count = kfifo_out_locked(&port->write_fifo, buf + OMNINET_HEADERLEN, + count, &port->lock); - usb_serial_port_softint(port); -} - - -static void omninet_disconnect(struct usb_serial *serial) -{ - struct usb_serial_port *wport = serial->port[1]; + header->oh_seq = od->od_outseq++; + header->oh_len = count; + header->oh_xxx = 0x03; + header->oh_pad = 0x00; - usb_kill_urb(wport->write_urb); + /* always 64 bytes */ + return OMNINET_BULKOUTSIZE; } module_usb_serial_driver(serial_drivers, id_table); diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index 3937b9c3cc69..58657d64678b 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -367,16 +367,6 @@ static int opticon_ioctl(struct tty_struct *tty, return -ENOIOCTLCMD; } -static int opticon_startup(struct usb_serial *serial) -{ - if (!serial->num_bulk_in) { - dev_err(&serial->dev->dev, "no bulk in endpoint\n"); - return -ENODEV; - } - - return 0; -} - static int opticon_port_probe(struct usb_serial_port *port) { struct opticon_private *priv; @@ -408,8 +398,8 @@ static struct usb_serial_driver opticon_device = { }, .id_table = id_table, .num_ports = 1, + .num_bulk_in = 1, .bulk_in_size = 256, - .attach = opticon_startup, .port_probe = opticon_port_probe, .port_remove = opticon_port_remove, .open = opticon_open, diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index b8bf52bf7a94..b11eead469ee 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -134,7 +134,6 @@ static int oti6858_chars_in_buffer(struct tty_struct *tty); static int oti6858_tiocmget(struct tty_struct *tty); static int oti6858_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); -static int oti6858_attach(struct usb_serial *serial); static int oti6858_port_probe(struct usb_serial_port *port); static int oti6858_port_remove(struct usb_serial_port *port); @@ -146,6 +145,9 @@ static struct usb_serial_driver oti6858_device = { }, .id_table = id_table, .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, .open = oti6858_open, .close = oti6858_close, .write = oti6858_write, @@ -159,7 +161,6 @@ static struct usb_serial_driver oti6858_device = { .write_bulk_callback = oti6858_write_bulk_callback, .write_room = oti6858_write_room, .chars_in_buffer = oti6858_chars_in_buffer, - .attach = oti6858_attach, .port_probe = oti6858_port_probe, .port_remove = oti6858_port_remove, }; @@ -326,20 +327,6 @@ static void send_data(struct work_struct *work) usb_serial_port_softint(port); } -static int oti6858_attach(struct usb_serial *serial) -{ - unsigned char num_ports = serial->num_ports; - - if (serial->num_bulk_in < num_ports || - serial->num_bulk_out < num_ports || - serial->num_interrupt_in < num_ports) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - - return 0; -} - static int oti6858_port_probe(struct usb_serial_port *port) { struct oti6858_private *priv; diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index ca69eb42071b..c9ebefd8f35f 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -33,9 +33,11 @@ #define PL2303_QUIRK_UART_STATE_IDX0 BIT(0) #define PL2303_QUIRK_LEGACY BIT(1) +#define PL2303_QUIRK_ENDPOINT_HACK BIT(2) static const struct usb_device_id id_table[] = { - { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) }, @@ -48,7 +50,8 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ZTEK) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, - { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID2) }, { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) }, { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) }, @@ -68,7 +71,8 @@ static const struct usb_device_id id_table[] = { .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75), .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, - { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */ { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) }, { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) }, @@ -78,7 +82,8 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) }, { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) }, { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) }, - { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID) }, + { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) }, { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) }, { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) }, @@ -218,20 +223,68 @@ static int pl2303_probe(struct usb_serial *serial, return 0; } +/* + * Use interrupt endpoint from first interface if available. + * + * This is needed due to the looney way its endpoints are set up. + */ +static int pl2303_endpoint_hack(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct usb_interface *interface = serial->interface; + struct usb_device *dev = serial->dev; + struct device *ddev = &interface->dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + unsigned int i; + + if (interface == dev->actconfig->interface[0]) + return 0; + + /* check out the endpoints of the other interface */ + iface_desc = dev->actconfig->interface[0]->cur_altsetting; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + continue; + + dev_dbg(ddev, "found interrupt in on separate interface\n"); + if (epds->num_interrupt_in < ARRAY_SIZE(epds->interrupt_in)) + epds->interrupt_in[epds->num_interrupt_in++] = endpoint; + } + + return 0; +} + +static int pl2303_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + unsigned long quirks = (unsigned long)usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int ret; + + if (quirks & PL2303_QUIRK_ENDPOINT_HACK) { + ret = pl2303_endpoint_hack(serial, epds); + if (ret) + return ret; + } + + if (epds->num_interrupt_in < 1) { + dev_err(dev, "required interrupt-in endpoint missing\n"); + return -ENODEV; + } + + return 1; +} + static int pl2303_startup(struct usb_serial *serial) { struct pl2303_serial_private *spriv; - unsigned char num_ports = serial->num_ports; enum pl2303_type type = TYPE_01; unsigned char *buf; - if (serial->num_bulk_in < num_ports || - serial->num_bulk_out < num_ports || - serial->num_interrupt_in < num_ports) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); if (!spriv) return -ENOMEM; @@ -938,7 +991,9 @@ static struct usb_serial_driver pl2303_device = { .name = "pl2303", }, .id_table = id_table, - .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 0, /* see pl2303_calc_num_ports */ .bulk_in_size = 256, .bulk_out_size = 256, .open = pl2303_open, @@ -954,6 +1009,7 @@ static struct usb_serial_driver pl2303_device = { .process_read_urb = pl2303_process_read_urb, .read_int_callback = pl2303_read_int_callback, .probe = pl2303_probe, + .calc_num_ports = pl2303_calc_num_ports, .attach = pl2303_startup, .release = pl2303_release, .port_probe = pl2303_port_probe, diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index fdbb904d153f..60e17d1444c3 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -246,7 +246,8 @@ static inline int update_mctrl(struct qt2_port_private *port_priv, return status; } -static int qt2_calc_num_ports(struct usb_serial *serial) +static int qt2_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { struct qt2_device_detail d; int i; @@ -600,7 +601,6 @@ static void qt2_process_read_urb(struct urb *urb) escapeflag = true; break; case QT2_CONTROL_ESCAPE: - tty_buffer_request_room(&port->port, 2); tty_insert_flip_string(&port->port, ch, 2); i += 2; escapeflag = true; @@ -615,8 +615,7 @@ static void qt2_process_read_urb(struct urb *urb) continue; } - tty_buffer_request_room(&port->port, 1); - tty_insert_flip_string(&port->port, ch, 1); + tty_insert_flip_char(&port->port, *ch, TTY_NORMAL); } tty_flip_buffer_push(&port->port); diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 465e851b2815..4c4ac4705ac0 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -85,7 +85,8 @@ static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable) USB_CTRL_SET_TIMEOUT); /* int timeout */ } -static int sierra_calc_num_ports(struct usb_serial *serial) +static int sierra_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { int num_ports = 0; u8 ifnum, numendpoints; diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index ddfd787c461c..5167b6564c8b 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -154,19 +154,6 @@ static int spcp8x5_probe(struct usb_serial *serial, return 0; } -static int spcp8x5_attach(struct usb_serial *serial) -{ - unsigned char num_ports = serial->num_ports; - - if (serial->num_bulk_in < num_ports || - serial->num_bulk_out < num_ports) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - - return 0; -} - static int spcp8x5_port_probe(struct usb_serial_port *port) { const struct usb_device_id *id = usb_get_serial_data(port->serial); @@ -488,6 +475,8 @@ static struct usb_serial_driver spcp8x5_device = { }, .id_table = id_table, .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, .open = spcp8x5_open, .dtr_rts = spcp8x5_dtr_rts, .carrier_raised = spcp8x5_carrier_raised, @@ -496,7 +485,6 @@ static struct usb_serial_driver spcp8x5_device = { .tiocmget = spcp8x5_tiocmget, .tiocmset = spcp8x5_tiocmset, .probe = spcp8x5_probe, - .attach = spcp8x5_attach, .port_probe = spcp8x5_port_probe, .port_remove = spcp8x5_port_remove, }; diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c index 37f3ad15ed06..0d1727232d0c 100644 --- a/drivers/usb/serial/symbolserial.c +++ b/drivers/usb/serial/symbolserial.c @@ -147,16 +147,6 @@ static void symbol_unthrottle(struct tty_struct *tty) } } -static int symbol_startup(struct usb_serial *serial) -{ - if (!serial->num_interrupt_in) { - dev_err(&serial->dev->dev, "no interrupt-in endpoint\n"); - return -ENODEV; - } - - return 0; -} - static int symbol_port_probe(struct usb_serial_port *port) { struct symbol_private *priv; @@ -188,7 +178,7 @@ static struct usb_serial_driver symbol_device = { }, .id_table = id_table, .num_ports = 1, - .attach = symbol_startup, + .num_interrupt_in = 1, .port_probe = symbol_port_probe, .port_remove = symbol_port_remove, .open = symbol_open, diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 3107bf5d1c96..8fc3854e5e69 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -427,6 +427,7 @@ static struct usb_serial_driver ti_1port_device = { .description = "TI USB 3410 1 port adapter", .id_table = ti_id_table_3410, .num_ports = 1, + .num_bulk_out = 1, .attach = ti_startup, .release = ti_release, .port_probe = ti_port_probe, @@ -459,6 +460,7 @@ static struct usb_serial_driver ti_2port_device = { .description = "TI USB 5052 2 port adapter", .id_table = ti_id_table_5052, .num_ports = 2, + .num_bulk_out = 1, .attach = ti_startup, .release = ti_release, .port_probe = ti_port_probe, @@ -927,7 +929,6 @@ static void ti_set_termios(struct tty_struct *tty, { struct ti_port *tport = usb_get_serial_port_data(port); struct ti_uart_config *config; - tcflag_t cflag, iflag; int baud; int status; int port_number = port->port_number; @@ -935,13 +936,6 @@ static void ti_set_termios(struct tty_struct *tty, u16 wbaudrate; u16 wflags = 0; - cflag = tty->termios.c_cflag; - iflag = tty->termios.c_iflag; - - dev_dbg(&port->dev, "%s - cflag %08x, iflag %08x\n", __func__, cflag, iflag); - dev_dbg(&port->dev, "%s - old clfag %08x, old iflag %08x\n", __func__, - old_termios->c_cflag, old_termios->c_iflag); - config = kmalloc(sizeof(*config), GFP_KERNEL); if (!config) return; diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 4a037b4a79cf..c7ca95f64edc 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -38,7 +38,6 @@ #include <linux/usb/serial.h> #include <linux/kfifo.h> #include <linux/idr.h> -#include "pl2303.h" #define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@linuxfoundation.org>" #define DRIVER_DESC "USB Serial Driver core" @@ -710,6 +709,39 @@ static const struct tty_port_operations serial_port_ops = { .shutdown = serial_port_shutdown, }; +static void find_endpoints(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct device *dev = &serial->interface->dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *epd; + unsigned int i; + + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_in) < USB_MAXENDPOINTS / 2); + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < USB_MAXENDPOINTS / 2); + BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_in) < USB_MAXENDPOINTS / 2); + BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_out) < USB_MAXENDPOINTS / 2); + + iface_desc = serial->interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + epd = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(epd)) { + dev_dbg(dev, "found bulk in on endpoint %u\n", i); + epds->bulk_in[epds->num_bulk_in++] = epd; + } else if (usb_endpoint_is_bulk_out(epd)) { + dev_dbg(dev, "found bulk out on endpoint %u\n", i); + epds->bulk_out[epds->num_bulk_out++] = epd; + } else if (usb_endpoint_is_int_in(epd)) { + dev_dbg(dev, "found interrupt in on endpoint %u\n", i); + epds->interrupt_in[epds->num_interrupt_in++] = epd; + } else if (usb_endpoint_is_int_out(epd)) { + dev_dbg(dev, "found interrupt out on endpoint %u\n", i); + epds->interrupt_out[epds->num_interrupt_out++] = epd; + } + } +} + static int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -717,23 +749,15 @@ static int usb_serial_probe(struct usb_interface *interface, struct usb_device *dev = interface_to_usbdev(interface); struct usb_serial *serial = NULL; struct usb_serial_port *port; - struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; - struct usb_endpoint_descriptor *interrupt_in_endpoint[MAX_NUM_PORTS]; - struct usb_endpoint_descriptor *interrupt_out_endpoint[MAX_NUM_PORTS]; - struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_NUM_PORTS]; - struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_NUM_PORTS]; + struct usb_serial_endpoints *epds; struct usb_serial_driver *type = NULL; int retval; int buffer_size; int i; int j; - int num_interrupt_in = 0; - int num_interrupt_out = 0; - int num_bulk_in = 0; - int num_bulk_out = 0; int num_ports = 0; - int max_endpoints; + unsigned char max_endpoints; mutex_lock(&table_lock); type = search_serial_device(interface); @@ -752,8 +776,8 @@ static int usb_serial_probe(struct usb_interface *interface, serial = create_serial(dev, interface, type); if (!serial) { - module_put(type->driver.owner); - return -ENOMEM; + retval = -ENOMEM; + goto err_put_module; } /* if this device type has a probe function, call it */ @@ -765,129 +789,48 @@ static int usb_serial_probe(struct usb_interface *interface, if (retval) { dev_dbg(ddev, "sub driver rejected device\n"); - usb_serial_put(serial); - module_put(type->driver.owner); - return retval; + goto err_put_serial; } } /* descriptor matches, let's find the endpoints needed */ - /* check out the endpoints */ - iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_bulk_in(endpoint)) { - /* we found a bulk in endpoint */ - dev_dbg(ddev, "found bulk in on endpoint %d\n", i); - if (num_bulk_in < MAX_NUM_PORTS) { - bulk_in_endpoint[num_bulk_in] = endpoint; - ++num_bulk_in; - } - } - - if (usb_endpoint_is_bulk_out(endpoint)) { - /* we found a bulk out endpoint */ - dev_dbg(ddev, "found bulk out on endpoint %d\n", i); - if (num_bulk_out < MAX_NUM_PORTS) { - bulk_out_endpoint[num_bulk_out] = endpoint; - ++num_bulk_out; - } - } - - if (usb_endpoint_is_int_in(endpoint)) { - /* we found a interrupt in endpoint */ - dev_dbg(ddev, "found interrupt in on endpoint %d\n", i); - if (num_interrupt_in < MAX_NUM_PORTS) { - interrupt_in_endpoint[num_interrupt_in] = - endpoint; - ++num_interrupt_in; - } - } - - if (usb_endpoint_is_int_out(endpoint)) { - /* we found an interrupt out endpoint */ - dev_dbg(ddev, "found interrupt out on endpoint %d\n", i); - if (num_interrupt_out < MAX_NUM_PORTS) { - interrupt_out_endpoint[num_interrupt_out] = - endpoint; - ++num_interrupt_out; - } - } + epds = kzalloc(sizeof(*epds), GFP_KERNEL); + if (!epds) { + retval = -ENOMEM; + goto err_put_serial; } -#if IS_ENABLED(CONFIG_USB_SERIAL_PL2303) - /* BEGIN HORRIBLE HACK FOR PL2303 */ - /* this is needed due to the looney way its endpoints are set up */ - if (((le16_to_cpu(dev->descriptor.idVendor) == PL2303_VENDOR_ID) && - (le16_to_cpu(dev->descriptor.idProduct) == PL2303_PRODUCT_ID)) || - ((le16_to_cpu(dev->descriptor.idVendor) == ATEN_VENDOR_ID) && - (le16_to_cpu(dev->descriptor.idProduct) == ATEN_PRODUCT_ID)) || - ((le16_to_cpu(dev->descriptor.idVendor) == ALCOR_VENDOR_ID) && - (le16_to_cpu(dev->descriptor.idProduct) == ALCOR_PRODUCT_ID)) || - ((le16_to_cpu(dev->descriptor.idVendor) == SIEMENS_VENDOR_ID) && - (le16_to_cpu(dev->descriptor.idProduct) == SIEMENS_PRODUCT_ID_EF81))) { - if (interface != dev->actconfig->interface[0]) { - /* check out the endpoints of the other interface*/ - iface_desc = dev->actconfig->interface[0]->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - if (usb_endpoint_is_int_in(endpoint)) { - /* we found a interrupt in endpoint */ - dev_dbg(ddev, "found interrupt in for Prolific device on separate interface\n"); - if (num_interrupt_in < MAX_NUM_PORTS) { - interrupt_in_endpoint[num_interrupt_in] = endpoint; - ++num_interrupt_in; - } - } - } - } + find_endpoints(serial, epds); - /* Now make sure the PL-2303 is configured correctly. - * If not, give up now and hope this hack will work - * properly during a later invocation of usb_serial_probe - */ - if (num_bulk_in == 0 || num_bulk_out == 0) { - dev_info(ddev, "PL-2303 hack: descriptors matched but endpoints did not\n"); - usb_serial_put(serial); - module_put(type->driver.owner); - return -ENODEV; - } - } - /* END HORRIBLE HACK FOR PL2303 */ -#endif - -#ifdef CONFIG_USB_SERIAL_GENERIC - if (type == &usb_serial_generic_device) { - num_ports = num_bulk_out; - if (num_ports == 0) { - dev_err(ddev, "Generic device with no bulk out, not allowed.\n"); - usb_serial_put(serial); - module_put(type->driver.owner); - return -EIO; - } - dev_info(ddev, "The \"generic\" usb-serial driver is only for testing and one-off prototypes.\n"); - dev_info(ddev, "Tell linux-usb@vger.kernel.org to add your device to a proper driver.\n"); + if (epds->num_bulk_in < type->num_bulk_in || + epds->num_bulk_out < type->num_bulk_out || + epds->num_interrupt_in < type->num_interrupt_in || + epds->num_interrupt_out < type->num_interrupt_out) { + dev_err(ddev, "required endpoints missing\n"); + retval = -ENODEV; + goto err_free_epds; } -#endif - if (!num_ports) { - /* if this device type has a calc_num_ports function, call it */ - if (type->calc_num_ports) - num_ports = type->calc_num_ports(serial); - if (!num_ports) - num_ports = type->num_ports; + + if (type->calc_num_ports) { + retval = type->calc_num_ports(serial, epds); + if (retval < 0) + goto err_free_epds; + num_ports = retval; } + if (!num_ports) + num_ports = type->num_ports; + if (num_ports > MAX_NUM_PORTS) { dev_warn(ddev, "too many ports requested: %d\n", num_ports); num_ports = MAX_NUM_PORTS; } - serial->num_ports = num_ports; - serial->num_bulk_in = num_bulk_in; - serial->num_bulk_out = num_bulk_out; - serial->num_interrupt_in = num_interrupt_in; - serial->num_interrupt_out = num_interrupt_out; + serial->num_ports = (unsigned char)num_ports; + serial->num_bulk_in = epds->num_bulk_in; + serial->num_bulk_out = epds->num_bulk_out; + serial->num_interrupt_in = epds->num_interrupt_in; + serial->num_interrupt_out = epds->num_interrupt_out; /* found all that we need */ dev_info(ddev, "%s converter detected\n", type->description); @@ -895,10 +838,10 @@ static int usb_serial_probe(struct usb_interface *interface, /* create our ports, we need as many as the max endpoints */ /* we don't use num_ports here because some devices have more endpoint pairs than ports */ - max_endpoints = max(num_bulk_in, num_bulk_out); - max_endpoints = max(max_endpoints, num_interrupt_in); - max_endpoints = max(max_endpoints, num_interrupt_out); - max_endpoints = max(max_endpoints, (int)serial->num_ports); + max_endpoints = max(epds->num_bulk_in, epds->num_bulk_out); + max_endpoints = max(max_endpoints, epds->num_interrupt_in); + max_endpoints = max(max_endpoints, epds->num_interrupt_out); + max_endpoints = max(max_endpoints, serial->num_ports); serial->num_port_pointers = max_endpoints; dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints); @@ -923,8 +866,8 @@ static int usb_serial_probe(struct usb_interface *interface, } /* set up the endpoint information */ - for (i = 0; i < num_bulk_in; ++i) { - endpoint = bulk_in_endpoint[i]; + for (i = 0; i < epds->num_bulk_in; ++i) { + endpoint = epds->bulk_in[i]; port = serial->port[i]; buffer_size = max_t(int, serial->type->bulk_in_size, usb_endpoint_maxp(endpoint)); @@ -952,8 +895,8 @@ static int usb_serial_probe(struct usb_interface *interface, port->bulk_in_buffer = port->bulk_in_buffers[0]; } - for (i = 0; i < num_bulk_out; ++i) { - endpoint = bulk_out_endpoint[i]; + for (i = 0; i < epds->num_bulk_out; ++i) { + endpoint = epds->bulk_out[i]; port = serial->port[i]; if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) goto probe_error; @@ -985,8 +928,8 @@ static int usb_serial_probe(struct usb_interface *interface, } if (serial->type->read_int_callback) { - for (i = 0; i < num_interrupt_in; ++i) { - endpoint = interrupt_in_endpoint[i]; + for (i = 0; i < epds->num_interrupt_in; ++i) { + endpoint = epds->interrupt_in[i]; port = serial->port[i]; port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); if (!port->interrupt_in_urb) @@ -1005,13 +948,13 @@ static int usb_serial_probe(struct usb_interface *interface, serial->type->read_int_callback, port, endpoint->bInterval); } - } else if (num_interrupt_in) { + } else if (epds->num_interrupt_in) { dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n"); } if (serial->type->write_int_callback) { - for (i = 0; i < num_interrupt_out; ++i) { - endpoint = interrupt_out_endpoint[i]; + for (i = 0; i < epds->num_interrupt_out; ++i) { + endpoint = epds->interrupt_out[i]; port = serial->port[i]; port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); if (!port->interrupt_out_urb) @@ -1031,7 +974,7 @@ static int usb_serial_probe(struct usb_interface *interface, serial->type->write_int_callback, port, endpoint->bInterval); } - } else if (num_interrupt_out) { + } else if (epds->num_interrupt_out) { dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n"); } @@ -1053,12 +996,6 @@ static int usb_serial_probe(struct usb_interface *interface, serial->attached = 1; } - /* Avoid race with tty_open and serial_install by setting the - * disconnected flag and not clearing it until all ports have been - * registered. - */ - serial->disconnected = 1; - if (allocate_minors(serial, num_ports)) { dev_err(ddev, "No more free serial minor numbers\n"); goto probe_error; @@ -1076,18 +1013,23 @@ static int usb_serial_probe(struct usb_interface *interface, dev_err(ddev, "Error registering port device, continuing\n"); } - serial->disconnected = 0; - if (num_ports > 0) usb_serial_console_init(serial->port[0]->minor); exit: + kfree(epds); module_put(type->driver.owner); return 0; probe_error: + retval = -EIO; +err_free_epds: + kfree(epds); +err_put_serial: usb_serial_put(serial); +err_put_module: module_put(type->driver.owner); - return -EIO; + + return retval; } static void usb_serial_disconnect(struct usb_interface *interface) diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c index 92f7e5c21162..12f4c5a91e62 100644 --- a/drivers/usb/serial/usb_debug.c +++ b/drivers/usb/serial/usb_debug.c @@ -17,7 +17,7 @@ #define USB_DEBUG_MAX_PACKET_SIZE 8 #define USB_DEBUG_BRK_SIZE 8 -static char USB_DEBUG_BRK[USB_DEBUG_BRK_SIZE] = { +static const char USB_DEBUG_BRK[USB_DEBUG_BRK_SIZE] = { 0x00, 0xff, 0x01, diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c index 337a0be89fcf..9f3317a940ef 100644 --- a/drivers/usb/serial/visor.c +++ b/drivers/usb/serial/visor.c @@ -40,11 +40,12 @@ static int visor_open(struct tty_struct *tty, struct usb_serial_port *port); static void visor_close(struct usb_serial_port *port); static int visor_probe(struct usb_serial *serial, const struct usb_device_id *id); -static int visor_calc_num_ports(struct usb_serial *serial); +static int visor_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); +static int clie_5_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); static void visor_read_int_callback(struct urb *urb); static int clie_3_5_startup(struct usb_serial *serial); -static int treo_attach(struct usb_serial *serial); -static int clie_5_attach(struct usb_serial *serial); static int palm_os_3_probe(struct usb_serial *serial, const struct usb_device_id *id); static int palm_os_4_probe(struct usb_serial *serial, @@ -174,7 +175,6 @@ static struct usb_serial_driver handspring_device = { .close = visor_close, .throttle = usb_serial_generic_throttle, .unthrottle = usb_serial_generic_unthrottle, - .attach = treo_attach, .probe = visor_probe, .calc_num_ports = visor_calc_num_ports, .read_int_callback = visor_read_int_callback, @@ -189,14 +189,14 @@ static struct usb_serial_driver clie_5_device = { .description = "Sony Clie 5.0", .id_table = clie_id_5_table, .num_ports = 2, + .num_bulk_out = 2, .bulk_out_size = 256, .open = visor_open, .close = visor_close, .throttle = usb_serial_generic_throttle, .unthrottle = usb_serial_generic_unthrottle, - .attach = clie_5_attach, .probe = visor_probe, - .calc_num_ports = visor_calc_num_ports, + .calc_num_ports = clie_5_calc_num_ports, .read_int_callback = visor_read_int_callback, }; @@ -466,16 +466,60 @@ static int visor_probe(struct usb_serial *serial, return retval; } -static int visor_calc_num_ports(struct usb_serial *serial) +static int visor_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) { + unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor); int num_ports = (int)(long)(usb_get_serial_data(serial)); if (num_ports) usb_set_serial_data(serial, NULL); + /* + * Only swap the bulk endpoints for the Handspring devices with + * interrupt in endpoints, which for now are the Treo devices. + */ + if (!(vid == HANDSPRING_VENDOR_ID || vid == KYOCERA_VENDOR_ID) || + epds->num_interrupt_in == 0) + goto out; + + if (epds->num_bulk_in < 2 || epds->num_interrupt_in < 2) { + dev_err(&serial->interface->dev, "missing endpoints\n"); + return -ENODEV; + } + + /* + * It appears that Treos and Kyoceras want to use the + * 1st bulk in endpoint to communicate with the 2nd bulk out endpoint, + * so let's swap the 1st and 2nd bulk in and interrupt endpoints. + * Note that swapping the bulk out endpoints would break lots of + * apps that want to communicate on the second port. + */ + swap(epds->bulk_in[0], epds->bulk_in[1]); + swap(epds->interrupt_in[0], epds->interrupt_in[1]); +out: return num_ports; } +static int clie_5_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + /* + * TH55 registers 2 ports. + * Communication in from the UX50/TH55 uses the first bulk-in + * endpoint, while communication out to the UX50/TH55 uses the second + * bulk-out endpoint. + */ + + /* + * FIXME: Should we swap the descriptors instead of using the same + * bulk-out endpoint for both ports? + */ + epds->bulk_out[0] = epds->bulk_out[1]; + + return serial->type->num_ports; +} + static int clie_3_5_startup(struct usb_serial *serial) { struct device *dev = &serial->dev->dev; @@ -531,94 +575,6 @@ out: return result; } -static int treo_attach(struct usb_serial *serial) -{ - struct usb_serial_port *swap_port; - - /* Only do this endpoint hack for the Handspring devices with - * interrupt in endpoints, which for now are the Treo devices. */ - if (!((le16_to_cpu(serial->dev->descriptor.idVendor) - == HANDSPRING_VENDOR_ID) || - (le16_to_cpu(serial->dev->descriptor.idVendor) - == KYOCERA_VENDOR_ID)) || - (serial->num_interrupt_in == 0)) - return 0; - - if (serial->num_bulk_in < 2 || serial->num_interrupt_in < 2) { - dev_err(&serial->interface->dev, "missing endpoints\n"); - return -ENODEV; - } - - /* - * It appears that Treos and Kyoceras want to use the - * 1st bulk in endpoint to communicate with the 2nd bulk out endpoint, - * so let's swap the 1st and 2nd bulk in and interrupt endpoints. - * Note that swapping the bulk out endpoints would break lots of - * apps that want to communicate on the second port. - */ -#define COPY_PORT(dest, src) \ - do { \ - int i; \ - \ - for (i = 0; i < ARRAY_SIZE(src->read_urbs); ++i) { \ - dest->read_urbs[i] = src->read_urbs[i]; \ - dest->read_urbs[i]->context = dest; \ - dest->bulk_in_buffers[i] = src->bulk_in_buffers[i]; \ - } \ - dest->read_urb = src->read_urb; \ - dest->bulk_in_endpointAddress = src->bulk_in_endpointAddress;\ - dest->bulk_in_buffer = src->bulk_in_buffer; \ - dest->bulk_in_size = src->bulk_in_size; \ - dest->interrupt_in_urb = src->interrupt_in_urb; \ - dest->interrupt_in_urb->context = dest; \ - dest->interrupt_in_endpointAddress = \ - src->interrupt_in_endpointAddress;\ - dest->interrupt_in_buffer = src->interrupt_in_buffer; \ - } while (0); - - swap_port = kmalloc(sizeof(*swap_port), GFP_KERNEL); - if (!swap_port) - return -ENOMEM; - COPY_PORT(swap_port, serial->port[0]); - COPY_PORT(serial->port[0], serial->port[1]); - COPY_PORT(serial->port[1], swap_port); - kfree(swap_port); - - return 0; -} - -static int clie_5_attach(struct usb_serial *serial) -{ - struct usb_serial_port *port; - unsigned int pipe; - int j; - - /* TH55 registers 2 ports. - Communication in from the UX50/TH55 uses bulk_in_endpointAddress - from port 0. Communication out to the UX50/TH55 uses - bulk_out_endpointAddress from port 1 - - Lets do a quick and dirty mapping - */ - - /* some sanity check */ - if (serial->num_bulk_out < 2) { - dev_err(&serial->interface->dev, "missing bulk out endpoints\n"); - return -ENODEV; - } - - /* port 0 now uses the modified endpoint Address */ - port = serial->port[0]; - port->bulk_out_endpointAddress = - serial->port[1]->bulk_out_endpointAddress; - - pipe = usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress); - for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) - port->write_urbs[j]->pipe = pipe; - - return 0; -} - module_usb_serial_driver(serial_drivers, id_table_combined); MODULE_AUTHOR(DRIVER_AUTHOR); diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 5ab65eb1dacc..55cebc1e6fec 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -80,8 +80,6 @@ static int whiteheat_firmware_download(struct usb_serial *serial, static int whiteheat_firmware_attach(struct usb_serial *serial); /* function prototypes for the Connect Tech WhiteHEAT serial converter */ -static int whiteheat_probe(struct usb_serial *serial, - const struct usb_device_id *id); static int whiteheat_attach(struct usb_serial *serial); static void whiteheat_release(struct usb_serial *serial); static int whiteheat_port_probe(struct usb_serial_port *port); @@ -118,7 +116,8 @@ static struct usb_serial_driver whiteheat_device = { .description = "Connect Tech - WhiteHEAT", .id_table = id_table_std, .num_ports = 4, - .probe = whiteheat_probe, + .num_bulk_in = 5, + .num_bulk_out = 5, .attach = whiteheat_attach, .release = whiteheat_release, .port_probe = whiteheat_port_probe, @@ -221,33 +220,6 @@ static int whiteheat_firmware_attach(struct usb_serial *serial) * Connect Tech's White Heat serial driver functions *****************************************************************************/ -static int whiteheat_probe(struct usb_serial *serial, - const struct usb_device_id *id) -{ - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - size_t num_bulk_in = 0; - size_t num_bulk_out = 0; - size_t min_num_bulk; - unsigned int i; - - iface_desc = serial->interface->cur_altsetting; - - for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { - endpoint = &iface_desc->endpoint[i].desc; - if (usb_endpoint_is_bulk_in(endpoint)) - ++num_bulk_in; - if (usb_endpoint_is_bulk_out(endpoint)) - ++num_bulk_out; - } - - min_num_bulk = COMMAND_PORT + 1; - if (num_bulk_in < min_num_bulk || num_bulk_out < min_num_bulk) - return -ENODEV; - - return 0; -} - static int whiteheat_attach(struct usb_serial *serial) { struct usb_serial_port *command_port; diff --git a/drivers/usb/storage/karma.c b/drivers/usb/storage/karma.c index f9d407f0b508..b05ba4929f00 100644 --- a/drivers/usb/storage/karma.c +++ b/drivers/usb/storage/karma.c @@ -105,7 +105,7 @@ static struct us_unusual_dev karma_unusual_dev_list[] = { */ static int rio_karma_send_command(char cmd, struct us_data *us) { - int result, partial; + int result; unsigned long timeout; static unsigned char seq = 1; struct karma_data *data = (struct karma_data *) us->extra; @@ -119,12 +119,12 @@ static int rio_karma_send_command(char cmd, struct us_data *us) timeout = jiffies + msecs_to_jiffies(6000); for (;;) { result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, - us->iobuf, RIO_SEND_LEN, &partial); + us->iobuf, RIO_SEND_LEN, NULL); if (result != USB_STOR_XFER_GOOD) goto err; result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, - data->recv, RIO_RECV_LEN, &partial); + data->recv, RIO_RECV_LEN, NULL); if (result != USB_STOR_XFER_GOOD) goto err; diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 9129f6cb8230..5a70c33ef0e0 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -42,7 +42,7 @@ * - a patch that adds the entry for your device, including your * email address right above the entry (plus maybe a brief * explanation of the reason for the entry), - * - a copy of /proc/bus/usb/devices with your device plugged in + * - a copy of /sys/kernel/debug/usb/devices with your device plugged in * running with this patch. * Send your submission to either Phil Dibowitz <phil@ipom.com> or * Alan Stern <stern@rowland.harvard.edu>, and don't forget to CC: the @@ -176,7 +176,7 @@ UNUSUAL_DEV( 0x0420, 0x0001, 0x0100, 0x0100, /* * Reported by Andrew Nayenko <relan@bk.ru> - * Updated for new firmware by Phillip Potter <phillipinda@hotmail.com> + * Updated for new firmware by Phillip Potter <phil@philpotter.co.uk> */ UNUSUAL_DEV( 0x0421, 0x0019, 0x0592, 0x0610, "Nokia", diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 615bea08ec0a..06615934fed1 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -737,13 +737,11 @@ static void get_protocol(struct us_data *us) /* Get the pipe settings */ static int get_pipes(struct us_data *us) { - struct usb_host_interface *altsetting = - us->pusb_intf->cur_altsetting; - int i; - struct usb_endpoint_descriptor *ep; - struct usb_endpoint_descriptor *ep_in = NULL; - struct usb_endpoint_descriptor *ep_out = NULL; - struct usb_endpoint_descriptor *ep_int = NULL; + struct usb_host_interface *alt = us->pusb_intf->cur_altsetting; + struct usb_endpoint_descriptor *ep_in; + struct usb_endpoint_descriptor *ep_out; + struct usb_endpoint_descriptor *ep_int; + int res; /* * Find the first endpoint of each type we need. @@ -751,28 +749,16 @@ static int get_pipes(struct us_data *us) * An optional interrupt-in is OK (necessary for CBI protocol). * We will ignore any others. */ - for (i = 0; i < altsetting->desc.bNumEndpoints; i++) { - ep = &altsetting->endpoint[i].desc; - - if (usb_endpoint_xfer_bulk(ep)) { - if (usb_endpoint_dir_in(ep)) { - if (!ep_in) - ep_in = ep; - } else { - if (!ep_out) - ep_out = ep; - } - } - - else if (usb_endpoint_is_int_in(ep)) { - if (!ep_int) - ep_int = ep; - } + res = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); + if (res) { + usb_stor_dbg(us, "bulk endpoints not found\n"); + return res; } - if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) { - usb_stor_dbg(us, "Endpoint sanity check failed! Rejecting dev.\n"); - return -EIO; + res = usb_find_int_in_endpoint(alt, &ep_int); + if (res && us->protocol == USB_PR_CBI) { + usb_stor_dbg(us, "interrupt endpoint not found\n"); + return res; } /* Calculate and store the pipe values */ diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig new file mode 100644 index 000000000000..dfcfe459b7cf --- /dev/null +++ b/drivers/usb/typec/Kconfig @@ -0,0 +1,22 @@ + +menu "USB Power Delivery and Type-C drivers" + +config TYPEC + tristate + +config TYPEC_WCOVE + tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver" + depends on ACPI + depends on INTEL_SOC_PMIC + depends on INTEL_PMC_IPC + depends on BXT_WC_PMIC_OPREGION + select TYPEC + help + This driver adds support for USB Type-C detection on Intel Broxton + platforms that have Intel Whiskey Cove PMIC. The driver can detect the + role and cable orientation. + + To compile this driver as module, choose M here: the module will be + called typec_wcove + +endmenu diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile new file mode 100644 index 000000000000..b9cb862221af --- /dev/null +++ b/drivers/usb/typec/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TYPEC) += typec.o +obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c new file mode 100644 index 000000000000..89e540bb7ff3 --- /dev/null +++ b/drivers/usb/typec/typec.c @@ -0,0 +1,1262 @@ +/* + * USB Type-C Connector Class + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb/typec.h> + +struct typec_mode { + int index; + u32 vdo; + char *desc; + enum typec_port_type roles; + + struct typec_altmode *alt_mode; + + unsigned int active:1; + + char group_name[6]; + struct attribute_group group; + struct attribute *attrs[5]; + struct device_attribute vdo_attr; + struct device_attribute desc_attr; + struct device_attribute active_attr; + struct device_attribute roles_attr; +}; + +struct typec_altmode { + struct device dev; + u16 svid; + int n_modes; + struct typec_mode modes[ALTMODE_MAX_MODES]; + const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; +}; + +struct typec_plug { + struct device dev; + enum typec_plug_index index; +}; + +struct typec_cable { + struct device dev; + enum typec_plug_type type; + struct usb_pd_identity *identity; + unsigned int active:1; +}; + +struct typec_partner { + struct device dev; + unsigned int usb_pd:1; + struct usb_pd_identity *identity; + enum typec_accessory accessory; +}; + +struct typec_port { + unsigned int id; + struct device dev; + + int prefer_role; + enum typec_data_role data_role; + enum typec_role pwr_role; + enum typec_role vconn_role; + enum typec_pwr_opmode pwr_opmode; + + const struct typec_capability *cap; +}; + +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) +#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) +#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) +#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) +#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) + +static const struct device_type typec_partner_dev_type; +static const struct device_type typec_cable_dev_type; +static const struct device_type typec_plug_dev_type; +static const struct device_type typec_port_dev_type; + +#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) +#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) +#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) +#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) + +static DEFINE_IDA(typec_index_ida); +static struct class *typec_class; + +/* Common attributes */ + +static const char * const typec_accessory_modes[] = { + [TYPEC_ACCESSORY_NONE] = "none", + [TYPEC_ACCESSORY_AUDIO] = "analog_audio", + [TYPEC_ACCESSORY_DEBUG] = "debug", +}; + +static struct usb_pd_identity *get_pd_identity(struct device *dev) +{ + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + return partner->identity; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + return cable->identity; + } + return NULL; +} + +static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->id_header); +} +static DEVICE_ATTR_RO(id_header); + +static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->cert_stat); +} +static DEVICE_ATTR_RO(cert_stat); + +static ssize_t product_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->product); +} +static DEVICE_ATTR_RO(product); + +static struct attribute *usb_pd_id_attrs[] = { + &dev_attr_id_header.attr, + &dev_attr_cert_stat.attr, + &dev_attr_product.attr, + NULL +}; + +static const struct attribute_group usb_pd_id_group = { + .name = "identity", + .attrs = usb_pd_id_attrs, +}; + +static const struct attribute_group *usb_pd_id_groups[] = { + &usb_pd_id_group, + NULL, +}; + +static void typec_report_identity(struct device *dev) +{ + sysfs_notify(&dev->kobj, "identity", "id_header"); + sysfs_notify(&dev->kobj, "identity", "cert_stat"); + sysfs_notify(&dev->kobj, "identity", "product"); +} + +/* ------------------------------------------------------------------------- */ +/* Alternate Modes */ + +/** + * typec_altmode_update_active - Report Enter/Exit mode + * @alt: Handle to the alternate mode + * @mode: Mode index + * @active: True when the mode has been entered + * + * If a partner or cable plug executes Enter/Exit Mode command successfully, the + * drivers use this routine to report the updated state of the mode. + */ +void typec_altmode_update_active(struct typec_altmode *alt, int mode, + bool active) +{ + struct typec_mode *m = &alt->modes[mode]; + char dir[6]; + + if (m->active == active) + return; + + m->active = active; + snprintf(dir, sizeof(dir), "mode%d", mode); + sysfs_notify(&alt->dev.kobj, dir, "active"); + kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_altmode_update_active); + +/** + * typec_altmode2port - Alternate Mode to USB Type-C port + * @alt: The Alternate Mode + * + * Returns handle to the port that a cable plug or partner with @alt is + * connected to. + */ +struct typec_port *typec_altmode2port(struct typec_altmode *alt) +{ + if (is_typec_plug(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent->parent); + if (is_typec_partner(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent); + if (is_typec_port(alt->dev.parent)) + return to_typec_port(alt->dev.parent); + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode2port); + +static ssize_t +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + vdo_attr); + + return sprintf(buf, "0x%08x\n", mode->vdo); +} + +static ssize_t +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + desc_attr); + + return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); +} + +static ssize_t +typec_altmode_active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + + return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); +} + +static ssize_t +typec_altmode_active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + struct typec_port *port = typec_altmode2port(mode->alt_mode); + bool activate; + int ret; + + if (!port->cap->activate_mode) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &activate); + if (ret) + return ret; + + ret = port->cap->activate_mode(port->cap, mode->index, activate); + if (ret) + return ret; + + return size; +} + +static ssize_t +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + roles_attr); + ssize_t ret; + + switch (mode->roles) { + case TYPEC_PORT_DFP: + ret = sprintf(buf, "source\n"); + break; + case TYPEC_PORT_UFP: + ret = sprintf(buf, "sink\n"); + break; + case TYPEC_PORT_DRP: + default: + ret = sprintf(buf, "source sink\n"); + break; + } + return ret; +} + +static void typec_init_modes(struct typec_altmode *alt, + struct typec_mode_desc *desc, bool is_port) +{ + int i; + + for (i = 0; i < alt->n_modes; i++, desc++) { + struct typec_mode *mode = &alt->modes[i]; + + /* Not considering the human readable description critical */ + mode->desc = kstrdup(desc->desc, GFP_KERNEL); + if (desc->desc && !mode->desc) + dev_err(&alt->dev, "failed to copy mode%d desc\n", i); + + mode->alt_mode = alt; + mode->vdo = desc->vdo; + mode->roles = desc->roles; + mode->index = desc->index; + sprintf(mode->group_name, "mode%d", desc->index); + + sysfs_attr_init(&mode->vdo_attr.attr); + mode->vdo_attr.attr.name = "vdo"; + mode->vdo_attr.attr.mode = 0444; + mode->vdo_attr.show = typec_altmode_vdo_show; + + sysfs_attr_init(&mode->desc_attr.attr); + mode->desc_attr.attr.name = "description"; + mode->desc_attr.attr.mode = 0444; + mode->desc_attr.show = typec_altmode_desc_show; + + sysfs_attr_init(&mode->active_attr.attr); + mode->active_attr.attr.name = "active"; + mode->active_attr.attr.mode = 0644; + mode->active_attr.show = typec_altmode_active_show; + mode->active_attr.store = typec_altmode_active_store; + + mode->attrs[0] = &mode->vdo_attr.attr; + mode->attrs[1] = &mode->desc_attr.attr; + mode->attrs[2] = &mode->active_attr.attr; + + /* With ports, list the roles that the mode is supported with */ + if (is_port) { + sysfs_attr_init(&mode->roles_attr.attr); + mode->roles_attr.attr.name = "supported_roles"; + mode->roles_attr.attr.mode = 0444; + mode->roles_attr.show = typec_altmode_roles_show; + + mode->attrs[3] = &mode->roles_attr.attr; + } + + mode->group.attrs = mode->attrs; + mode->group.name = mode->group_name; + + alt->mode_groups[i] = &mode->group; + } +} + +static ssize_t svid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_altmode *alt = to_altmode(dev); + + return sprintf(buf, "%04x\n", alt->svid); +} +static DEVICE_ATTR_RO(svid); + +static struct attribute *typec_altmode_attrs[] = { + &dev_attr_svid.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_altmode); + +static void typec_altmode_release(struct device *dev) +{ + struct typec_altmode *alt = to_altmode(dev); + int i; + + for (i = 0; i < alt->n_modes; i++) + kfree(alt->modes[i].desc); + kfree(alt); +} + +static const struct device_type typec_altmode_dev_type = { + .name = "typec_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; + +static struct typec_altmode * +typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc) +{ + struct typec_altmode *alt; + int ret; + + alt = kzalloc(sizeof(*alt), GFP_KERNEL); + if (!alt) + return NULL; + + alt->svid = desc->svid; + alt->n_modes = desc->n_modes; + typec_init_modes(alt, desc->modes, is_typec_port(parent)); + + alt->dev.parent = parent; + alt->dev.groups = alt->mode_groups; + alt->dev.type = &typec_altmode_dev_type; + dev_set_name(&alt->dev, "svid-%04x", alt->svid); + + ret = device_register(&alt->dev); + if (ret) { + dev_err(parent, "failed to register alternate mode (%d)\n", + ret); + put_device(&alt->dev); + return NULL; + } + + return alt; +} + +/** + * typec_unregister_altmode - Unregister Alternate Mode + * @alt: The alternate mode to be unregistered + * + * Unregister device created with typec_partner_register_altmode(), + * typec_plug_register_altmode() or typec_port_register_altmode(). + */ +void typec_unregister_altmode(struct typec_altmode *alt) +{ + if (alt) + device_unregister(&alt->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_altmode); + +/* ------------------------------------------------------------------------- */ +/* Type-C Partners */ + +static ssize_t accessory_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); +} +static DEVICE_ATTR_RO(accessory_mode); + +static ssize_t supports_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); +} +static DEVICE_ATTR_RO(supports_usb_power_delivery); + +static struct attribute *typec_partner_attrs[] = { + &dev_attr_accessory_mode.attr, + &dev_attr_supports_usb_power_delivery.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_partner); + +static void typec_partner_release(struct device *dev) +{ + struct typec_partner *partner = to_typec_partner(dev); + + kfree(partner); +} + +static const struct device_type typec_partner_dev_type = { + .name = "typec_partner", + .groups = typec_partner_groups, + .release = typec_partner_release, +}; + +/** + * typec_partner_set_identity - Report result from Discover Identity command + * @partner: The partner updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_partner_set_identity(struct typec_partner *partner) +{ + if (!partner->identity) + return -EINVAL; + + typec_report_identity(&partner->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_identity); + +/** + * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode + * @partner: USB Type-C Partner that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that + * @partner has listed in response to Discover SVIDs command. The modes for a + * SVID listed in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_partner_register_altmode(struct typec_partner *partner, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&partner->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_partner_register_altmode); + +/** + * typec_register_partner - Register a USB Type-C Partner + * @port: The USB Type-C Port the partner is connected to + * @desc: Description of the partner + * + * Registers a device for USB Type-C Partner described in @desc. + * + * Returns handle to the partner on success or NULL on failure. + */ +struct typec_partner *typec_register_partner(struct typec_port *port, + struct typec_partner_desc *desc) +{ + struct typec_partner *partner; + int ret; + + partner = kzalloc(sizeof(*partner), GFP_KERNEL); + if (!partner) + return NULL; + + partner->usb_pd = desc->usb_pd; + partner->accessory = desc->accessory; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + partner->dev.groups = usb_pd_id_groups; + partner->identity = desc->identity; + } + + partner->dev.class = typec_class; + partner->dev.parent = &port->dev; + partner->dev.type = &typec_partner_dev_type; + dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); + + ret = device_register(&partner->dev); + if (ret) { + dev_err(&port->dev, "failed to register partner (%d)\n", ret); + put_device(&partner->dev); + return NULL; + } + + return partner; +} +EXPORT_SYMBOL_GPL(typec_register_partner); + +/** + * typec_unregister_partner - Unregister a USB Type-C Partner + * @partner: The partner to be unregistered + * + * Unregister device created with typec_register_partner(). + */ +void typec_unregister_partner(struct typec_partner *partner) +{ + if (partner) + device_unregister(&partner->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_partner); + +/* ------------------------------------------------------------------------- */ +/* Type-C Cable Plugs */ + +static void typec_plug_release(struct device *dev) +{ + struct typec_plug *plug = to_typec_plug(dev); + + kfree(plug); +} + +static const struct device_type typec_plug_dev_type = { + .name = "typec_plug", + .release = typec_plug_release, +}; + +/** + * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode + * @plug: USB Type-C Cable Plug that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that @plug + * has listed in response to Discover SVIDs command. The modes for a SVID that + * the plug lists in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_plug_register_altmode(struct typec_plug *plug, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&plug->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_plug_register_altmode); + +/** + * typec_register_plug - Register a USB Type-C Cable Plug + * @cable: USB Type-C Cable with the plug + * @desc: Description of the cable plug + * + * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C + * Cable Plug represents a plug with electronics in it that can response to USB + * Power Delivery SOP Prime or SOP Double Prime packages. + * + * Returns handle to the cable plug on success or NULL on failure. + */ +struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc) +{ + struct typec_plug *plug; + char name[8]; + int ret; + + plug = kzalloc(sizeof(*plug), GFP_KERNEL); + if (!plug) + return NULL; + + sprintf(name, "plug%d", desc->index); + + plug->index = desc->index; + plug->dev.class = typec_class; + plug->dev.parent = &cable->dev; + plug->dev.type = &typec_plug_dev_type; + dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); + + ret = device_register(&plug->dev); + if (ret) { + dev_err(&cable->dev, "failed to register plug (%d)\n", ret); + put_device(&plug->dev); + return NULL; + } + + return plug; +} +EXPORT_SYMBOL_GPL(typec_register_plug); + +/** + * typec_unregister_plug - Unregister a USB Type-C Cable Plug + * @plug: The cable plug to be unregistered + * + * Unregister device created with typec_register_plug(). + */ +void typec_unregister_plug(struct typec_plug *plug) +{ + if (plug) + device_unregister(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_plug); + +/* Type-C Cables */ + +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); +} +static DEVICE_ATTR_RO(type); + +static const char * const typec_plug_types[] = { + [USB_PLUG_NONE] = "unknown", + [USB_PLUG_TYPE_A] = "type-a", + [USB_PLUG_TYPE_B] = "type-b", + [USB_PLUG_TYPE_C] = "type-c", + [USB_PLUG_CAPTIVE] = "captive", +}; + +static ssize_t plug_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", typec_plug_types[cable->type]); +} +static DEVICE_ATTR_RO(plug_type); + +static struct attribute *typec_cable_attrs[] = { + &dev_attr_type.attr, + &dev_attr_plug_type.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_cable); + +static void typec_cable_release(struct device *dev) +{ + struct typec_cable *cable = to_typec_cable(dev); + + kfree(cable); +} + +static const struct device_type typec_cable_dev_type = { + .name = "typec_cable", + .groups = typec_cable_groups, + .release = typec_cable_release, +}; + +/** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_cable_set_identity(struct typec_cable *cable) +{ + if (!cable->identity) + return -EINVAL; + + typec_report_identity(&cable->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_cable_set_identity); + +/** + * typec_register_cable - Register a USB Type-C Cable + * @port: The USB Type-C Port the cable is connected to + * @desc: Description of the cable + * + * Registers a device for USB Type-C Cable described in @desc. The cable will be + * parent for the optional cable plug devises. + * + * Returns handle to the cable on success or NULL on failure. + */ +struct typec_cable *typec_register_cable(struct typec_port *port, + struct typec_cable_desc *desc) +{ + struct typec_cable *cable; + int ret; + + cable = kzalloc(sizeof(*cable), GFP_KERNEL); + if (!cable) + return NULL; + + cable->type = desc->type; + cable->active = desc->active; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + cable->dev.groups = usb_pd_id_groups; + cable->identity = desc->identity; + } + + cable->dev.class = typec_class; + cable->dev.parent = &port->dev; + cable->dev.type = &typec_cable_dev_type; + dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); + + ret = device_register(&cable->dev); + if (ret) { + dev_err(&port->dev, "failed to register cable (%d)\n", ret); + put_device(&cable->dev); + return NULL; + } + + return cable; +} +EXPORT_SYMBOL_GPL(typec_register_cable); + +/** + * typec_unregister_cable - Unregister a USB Type-C Cable + * @cable: The cable to be unregistered + * + * Unregister device created with typec_register_cable(). + */ +void typec_unregister_cable(struct typec_cable *cable) +{ + if (cable) + device_unregister(&cable->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_cable); + +/* ------------------------------------------------------------------------- */ +/* USB Type-C ports */ + +static const char * const typec_roles[] = { + [TYPEC_SINK] = "sink", + [TYPEC_SOURCE] = "source", +}; + +static const char * const typec_data_roles[] = { + [TYPEC_DEVICE] = "device", + [TYPEC_HOST] = "host", +}; + +static ssize_t +preferred_role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int role; + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "Preferred role only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->try_role) { + dev_dbg(dev, "Setting preferred role not supported\n"); + return -EOPNOTSUPP; + } + + role = sysfs_match_string(typec_roles, buf); + if (role < 0) { + if (sysfs_streq(buf, "none")) + role = TYPEC_NO_PREFERRED_ROLE; + else + return -EINVAL; + } + + ret = port->cap->try_role(port->cap, role); + if (ret) + return ret; + + port->prefer_role = role; + return size; +} + +static ssize_t +preferred_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type != TYPEC_PORT_DRP) + return 0; + + if (port->prefer_role < 0) + return 0; + + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); +} +static DEVICE_ATTR_RW(preferred_role); + +static ssize_t data_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "data role swap only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->dr_set) { + dev_dbg(dev, "data role swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_data_roles, buf); + if (ret < 0) + return ret; + + ret = port->cap->dr_set(port->cap, ret); + if (ret) + return ret; + + return size; +} + +static ssize_t data_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? + "[host] device" : "host [device]"); + + return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); +} +static DEVICE_ATTR_RW(data_role); + +static ssize_t power_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret = size; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "USB Power Delivery not supported\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->pr_set) { + dev_dbg(dev, "power role swapping not supported\n"); + return -EOPNOTSUPP; + } + + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { + dev_dbg(dev, "partner unable to swap power role\n"); + return -EIO; + } + + ret = sysfs_match_string(typec_roles, buf); + if (ret < 0) + return ret; + + ret = port->cap->pr_set(port->cap, ret); + if (ret) + return ret; + + return size; +} + +static ssize_t power_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? + "[source] sink" : "source [sink]"); + + return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); +} +static DEVICE_ATTR_RW(power_role); + +static const char * const typec_pwr_opmodes[] = { + [TYPEC_PWR_MODE_USB] = "default", + [TYPEC_PWR_MODE_1_5A] = "1.5A", + [TYPEC_PWR_MODE_3_0A] = "3.0A", + [TYPEC_PWR_MODE_PD] = "usb_power_delivery", +}; + +static ssize_t power_operation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); +} +static DEVICE_ATTR_RO(power_operation_mode); + +static ssize_t vconn_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + bool source; + int ret; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->vconn_set) { + dev_dbg(dev, "VCONN swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = kstrtobool(buf, &source); + if (ret) + return ret; + + ret = port->cap->vconn_set(port->cap, (enum typec_role)source); + if (ret) + return ret; + + return size; +} + +static ssize_t vconn_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", + port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); +} +static DEVICE_ATTR_RW(vconn_source); + +static ssize_t supported_accessory_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + ssize_t ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { + if (port->cap->accessory[i]) + ret += sprintf(buf + ret, "%s ", + typec_accessory_modes[port->cap->accessory[i]]); + } + + if (!ret) + return sprintf(buf, "none\n"); + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RO(supported_accessory_modes); + +static ssize_t usb_typec_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + u16 rev = port->cap->revision; + + return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); +} +static DEVICE_ATTR_RO(usb_typec_revision); + +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *p = to_typec_port(dev); + + return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); +} +static DEVICE_ATTR_RO(usb_power_delivery_revision); + +static struct attribute *typec_attrs[] = { + &dev_attr_data_role.attr, + &dev_attr_power_operation_mode.attr, + &dev_attr_power_role.attr, + &dev_attr_preferred_role.attr, + &dev_attr_supported_accessory_modes.attr, + &dev_attr_usb_power_delivery_revision.attr, + &dev_attr_usb_typec_revision.attr, + &dev_attr_vconn_source.attr, + NULL, +}; +ATTRIBUTE_GROUPS(typec); + +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent TYPEC_PORT\n"); + + return ret; +} + +static void typec_release(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + ida_simple_remove(&typec_index_ida, port->id); + kfree(port); +} + +static const struct device_type typec_port_dev_type = { + .name = "typec_port", + .groups = typec_groups, + .uevent = typec_uevent, + .release = typec_release, +}; + +/* --------------------------------------- */ +/* Driver callbacks to report role updates */ + +/** + * typec_set_data_role - Report data role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report data role changes. + */ +void typec_set_data_role(struct typec_port *port, enum typec_data_role role) +{ + if (port->data_role == role) + return; + + port->data_role = role; + sysfs_notify(&port->dev.kobj, NULL, "data_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_data_role); + +/** + * typec_set_pwr_role - Report power role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report power role changes. + */ +void typec_set_pwr_role(struct typec_port *port, enum typec_role role) +{ + if (port->pwr_role == role) + return; + + port->pwr_role = role; + sysfs_notify(&port->dev.kobj, NULL, "power_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_role); + +/** + * typec_set_pwr_role - Report VCONN source change + * @port: The USB Type-C Port which VCONN role changed + * @role: Source when @port is sourcing VCONN, or Sink when it's not + * + * This routine is used by the port drivers to report if the VCONN source is + * changes. + */ +void typec_set_vconn_role(struct typec_port *port, enum typec_role role) +{ + if (port->vconn_role == role) + return; + + port->vconn_role = role; + sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_vconn_role); + +/** + * typec_set_pwr_opmode - Report changed power operation mode + * @port: The USB Type-C Port where the mode was changed + * @opmode: New power operation mode + * + * This routine is used by the port drivers to report changed power operation + * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB + * Type-C specification, and "USB Power Delivery" when the power levels are + * negotiated with methods defined in USB Power Delivery specification. + */ +void typec_set_pwr_opmode(struct typec_port *port, + enum typec_pwr_opmode opmode) +{ + if (port->pwr_opmode == opmode) + return; + + port->pwr_opmode = opmode; + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); + +/* --------------------------------------- */ + +/** + * typec_port_register_altmode - Register USB Type-C Port Alternate Mode + * @port: USB Type-C Port that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register an alternate mode that @port is capable of + * supporting. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_port_register_altmode(struct typec_port *port, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&port->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_port_register_altmode); + +/** + * typec_register_port - Register a USB Type-C Port + * @parent: Parent device + * @cap: Description of the port + * + * Registers a device for USB Type-C Port described in @cap. + * + * Returns handle to the port on success or NULL on failure. + */ +struct typec_port *typec_register_port(struct device *parent, + const struct typec_capability *cap) +{ + struct typec_port *port; + int role; + int ret; + int id; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return NULL; + + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + kfree(port); + return NULL; + } + + if (cap->type == TYPEC_PORT_DFP) + role = TYPEC_SOURCE; + else if (cap->type == TYPEC_PORT_UFP) + role = TYPEC_SINK; + else + role = cap->prefer_role; + + if (role == TYPEC_SOURCE) { + port->data_role = TYPEC_HOST; + port->pwr_role = TYPEC_SOURCE; + port->vconn_role = TYPEC_SOURCE; + } else { + port->data_role = TYPEC_DEVICE; + port->pwr_role = TYPEC_SINK; + port->vconn_role = TYPEC_SINK; + } + + port->id = id; + port->cap = cap; + port->prefer_role = cap->prefer_role; + + port->dev.class = typec_class; + port->dev.parent = parent; + port->dev.fwnode = cap->fwnode; + port->dev.type = &typec_port_dev_type; + dev_set_name(&port->dev, "port%d", id); + + ret = device_register(&port->dev); + if (ret) { + dev_err(parent, "failed to register port (%d)\n", ret); + put_device(&port->dev); + return NULL; + } + + return port; +} +EXPORT_SYMBOL_GPL(typec_register_port); + +/** + * typec_unregister_port - Unregister a USB Type-C Port + * @port: The port to be unregistered + * + * Unregister device created with typec_register_port(). + */ +void typec_unregister_port(struct typec_port *port) +{ + if (port) + device_unregister(&port->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_port); + +static int __init typec_init(void) +{ + typec_class = class_create(THIS_MODULE, "typec"); + return PTR_ERR_OR_ZERO(typec_class); +} +subsys_initcall(typec_init); + +static void __exit typec_exit(void) +{ + class_destroy(typec_class); + ida_destroy(&typec_index_ida); +} +module_exit(typec_exit); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c new file mode 100644 index 000000000000..d5a7b21fa3f1 --- /dev/null +++ b/drivers/usb/typec/typec_wcove.c @@ -0,0 +1,377 @@ +/** + * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver + * + * Copyright (C) 2017 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/usb/typec.h> +#include <linux/platform_device.h> +#include <linux/mfd/intel_soc_pmic.h> + +/* Register offsets */ +#define WCOVE_CHGRIRQ0 0x4e09 +#define WCOVE_PHYCTRL 0x5e07 + +#define USBC_CONTROL1 0x7001 +#define USBC_CONTROL2 0x7002 +#define USBC_CONTROL3 0x7003 +#define USBC_CC1_CTRL 0x7004 +#define USBC_CC2_CTRL 0x7005 +#define USBC_STATUS1 0x7007 +#define USBC_STATUS2 0x7008 +#define USBC_STATUS3 0x7009 +#define USBC_IRQ1 0x7015 +#define USBC_IRQ2 0x7016 +#define USBC_IRQMASK1 0x7017 +#define USBC_IRQMASK2 0x7018 + +/* Register bits */ + +#define USBC_CONTROL1_MODE_DRP(r) (((r) & ~0x7) | 4) + +#define USBC_CONTROL2_UNATT_SNK BIT(0) +#define USBC_CONTROL2_UNATT_SRC BIT(1) +#define USBC_CONTROL2_DIS_ST BIT(2) + +#define USBC_CONTROL3_PD_DIS BIT(1) + +#define USBC_CC_CTRL_VCONN_EN BIT(1) + +#define USBC_STATUS1_DET_ONGOING BIT(6) +#define USBC_STATUS1_RSLT(r) ((r) & 0xf) +#define USBC_RSLT_NOTHING 0 +#define USBC_RSLT_SRC_DEFAULT 1 +#define USBC_RSLT_SRC_1_5A 2 +#define USBC_RSLT_SRC_3_0A 3 +#define USBC_RSLT_SNK 4 +#define USBC_RSLT_DEBUG_ACC 5 +#define USBC_RSLT_AUDIO_ACC 6 +#define USBC_RSLT_UNDEF 15 +#define USBC_STATUS1_ORIENT(r) (((r) >> 4) & 0x3) +#define USBC_ORIENT_NORMAL 1 +#define USBC_ORIENT_REVERSE 2 + +#define USBC_STATUS2_VBUS_REQ BIT(5) + +#define USBC_IRQ1_ADCDONE1 BIT(2) +#define USBC_IRQ1_OVERTEMP BIT(1) +#define USBC_IRQ1_SHORT BIT(0) + +#define USBC_IRQ2_CC_CHANGE BIT(7) +#define USBC_IRQ2_RX_PD BIT(6) +#define USBC_IRQ2_RX_HR BIT(5) +#define USBC_IRQ2_RX_CR BIT(4) +#define USBC_IRQ2_TX_SUCCESS BIT(3) +#define USBC_IRQ2_TX_FAIL BIT(2) + +#define USBC_IRQMASK1_ALL (USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \ + USBC_IRQ1_SHORT) + +#define USBC_IRQMASK2_ALL (USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \ + USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \ + USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL) + +struct wcove_typec { + struct mutex lock; /* device lock */ + struct device *dev; + struct regmap *regmap; + struct typec_port *port; + struct typec_capability cap; + struct typec_partner *partner; +}; + +enum wcove_typec_func { + WCOVE_FUNC_DRIVE_VBUS = 1, + WCOVE_FUNC_ORIENTATION, + WCOVE_FUNC_ROLE, + WCOVE_FUNC_DRIVE_VCONN, +}; + +enum wcove_typec_orientation { + WCOVE_ORIENTATION_NORMAL, + WCOVE_ORIENTATION_REVERSE, +}; + +enum wcove_typec_role { + WCOVE_ROLE_HOST, + WCOVE_ROLE_DEVICE, +}; + +static uuid_le uuid = UUID_LE(0x482383f0, 0x2876, 0x4e49, + 0x86, 0x85, 0xdb, 0x66, 0x21, 0x1a, 0xf0, 0x37); + +static int wcove_typec_func(struct wcove_typec *wcove, + enum wcove_typec_func func, int param) +{ + union acpi_object *obj; + union acpi_object tmp; + union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); + + tmp.type = ACPI_TYPE_INTEGER; + tmp.integer.value = param; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), uuid.b, 1, func, + &argv4); + if (!obj) { + dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static irqreturn_t wcove_typec_irq(int irq, void *data) +{ + enum typec_role role = TYPEC_SINK; + struct typec_partner_desc partner; + struct wcove_typec *wcove = data; + unsigned int cc1_ctrl; + unsigned int cc2_ctrl; + unsigned int cc_irq1; + unsigned int cc_irq2; + unsigned int status1; + unsigned int status2; + int ret; + + mutex_lock(&wcove->lock); + + ret = regmap_read(wcove->regmap, USBC_IRQ1, &cc_irq1); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_IRQ2, &cc_irq2); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_STATUS1, &status1); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_STATUS2, &status2); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1_ctrl); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_CC2_CTRL, &cc2_ctrl); + if (ret) + goto err; + + if (cc_irq1) { + if (cc_irq1 & USBC_IRQ1_OVERTEMP) + dev_err(wcove->dev, "VCONN Switch Over Temperature!\n"); + if (cc_irq1 & USBC_IRQ1_SHORT) + dev_err(wcove->dev, "VCONN Switch Short Circuit!\n"); + ret = regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1); + if (ret) + goto err; + } + + if (cc_irq2) { + ret = regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2); + if (ret) + goto err; + /* + * Ignoring any PD communication interrupts until the PD support + * is available + */ + if (cc_irq2 & ~USBC_IRQ2_CC_CHANGE) { + dev_WARN(wcove->dev, "USB PD handling missing\n"); + goto err; + } + } + + if (status1 & USBC_STATUS1_DET_ONGOING) + goto out; + + if (USBC_STATUS1_RSLT(status1) == USBC_RSLT_NOTHING) { + if (wcove->partner) { + typec_unregister_partner(wcove->partner); + wcove->partner = NULL; + } + + wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, + WCOVE_ORIENTATION_NORMAL); + + /* This makes sure the device controller is disconnected */ + wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST); + + /* Port to default role */ + typec_set_data_role(wcove->port, TYPEC_DEVICE); + typec_set_pwr_role(wcove->port, TYPEC_SINK); + typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_USB); + + goto out; + } + + if (wcove->partner) + goto out; + + switch (USBC_STATUS1_ORIENT(status1)) { + case USBC_ORIENT_NORMAL: + wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, + WCOVE_ORIENTATION_NORMAL); + break; + case USBC_ORIENT_REVERSE: + wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, + WCOVE_ORIENTATION_REVERSE); + default: + break; + } + + memset(&partner, 0, sizeof(partner)); + + switch (USBC_STATUS1_RSLT(status1)) { + case USBC_RSLT_SRC_DEFAULT: + typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_USB); + break; + case USBC_RSLT_SRC_1_5A: + typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_1_5A); + break; + case USBC_RSLT_SRC_3_0A: + typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_3_0A); + break; + case USBC_RSLT_SNK: + role = TYPEC_SOURCE; + break; + case USBC_RSLT_DEBUG_ACC: + partner.accessory = TYPEC_ACCESSORY_DEBUG; + break; + case USBC_RSLT_AUDIO_ACC: + partner.accessory = TYPEC_ACCESSORY_AUDIO; + break; + default: + dev_WARN(wcove->dev, "%s Undefined result\n", __func__); + goto err; + } + + if (role == TYPEC_SINK) { + wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_DEVICE); + typec_set_data_role(wcove->port, TYPEC_DEVICE); + typec_set_pwr_role(wcove->port, TYPEC_SINK); + } else { + wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST); + typec_set_pwr_role(wcove->port, TYPEC_SOURCE); + typec_set_data_role(wcove->port, TYPEC_HOST); + } + + wcove->partner = typec_register_partner(wcove->port, &partner); + if (!wcove->partner) + dev_err(wcove->dev, "failed register partner\n"); +out: + /* If either CC pins is requesting VCONN, we turn it on */ + if ((cc1_ctrl & USBC_CC_CTRL_VCONN_EN) || + (cc2_ctrl & USBC_CC_CTRL_VCONN_EN)) + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, true); + else + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); + + /* Relying on the FSM to know when we need to drive VBUS. */ + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS, + !!(status2 & USBC_STATUS2_VBUS_REQ)); +err: + /* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */ + regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5)); + + mutex_unlock(&wcove->lock); + return IRQ_HANDLED; +} + +static int wcove_typec_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct wcove_typec *wcove; + unsigned int val; + int ret; + + wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL); + if (!wcove) + return -ENOMEM; + + mutex_init(&wcove->lock); + wcove->dev = &pdev->dev; + wcove->regmap = pmic->regmap; + + ret = regmap_irq_get_virq(pmic->irq_chip_data_level2, + platform_get_irq(pdev, 0)); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(&pdev->dev, ret, NULL, + wcove_typec_irq, IRQF_ONESHOT, + "wcove_typec", wcove); + if (ret) + return ret; + + if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), uuid.b, 0, 0x1f)) { + dev_err(&pdev->dev, "Missing _DSM functions\n"); + return -ENODEV; + } + + wcove->cap.type = TYPEC_PORT_DRP; + wcove->cap.revision = USB_TYPEC_REV_1_1; + wcove->cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + + /* Make sure the PD PHY is disabled until USB PD is available */ + regmap_read(wcove->regmap, USBC_CONTROL3, &val); + regmap_write(wcove->regmap, USBC_CONTROL3, val | USBC_CONTROL3_PD_DIS); + + /* DRP mode without accessory support */ + regmap_read(wcove->regmap, USBC_CONTROL1, &val); + regmap_write(wcove->regmap, USBC_CONTROL1, USBC_CONTROL1_MODE_DRP(val)); + + wcove->port = typec_register_port(&pdev->dev, &wcove->cap); + if (!wcove->port) + return -ENODEV; + + /* Unmask everything */ + regmap_read(wcove->regmap, USBC_IRQMASK1, &val); + regmap_write(wcove->regmap, USBC_IRQMASK1, val & ~USBC_IRQMASK1_ALL); + regmap_read(wcove->regmap, USBC_IRQMASK2, &val); + regmap_write(wcove->regmap, USBC_IRQMASK2, val & ~USBC_IRQMASK2_ALL); + + platform_set_drvdata(pdev, wcove); + return 0; +} + +static int wcove_typec_remove(struct platform_device *pdev) +{ + struct wcove_typec *wcove = platform_get_drvdata(pdev); + unsigned int val; + + /* Mask everything */ + regmap_read(wcove->regmap, USBC_IRQMASK1, &val); + regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL); + regmap_read(wcove->regmap, USBC_IRQMASK2, &val); + regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL); + + typec_unregister_partner(wcove->partner); + typec_unregister_port(wcove->port); + return 0; +} + +static struct platform_driver wcove_typec_driver = { + .driver = { + .name = "bxt_wcove_usbc", + }, + .probe = wcove_typec_probe, + .remove = wcove_typec_remove, +}; + +module_platform_driver(wcove_typec_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver"); +MODULE_ALIAS("platform:bxt_wcove_usbc"); diff --git a/drivers/usb/usb-skeleton.c b/drivers/usb/usb-skeleton.c index 5133a0792eb0..bb0bd732e29a 100644 --- a/drivers/usb/usb-skeleton.c +++ b/drivers/usb/usb-skeleton.c @@ -491,16 +491,14 @@ static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_skel *dev; - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint; - size_t buffer_size; - int i; - int retval = -ENOMEM; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; + int retval; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) - goto error; + return -ENOMEM; + kref_init(&dev->kref); sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); mutex_init(&dev->io_mutex); @@ -513,36 +511,29 @@ static int skel_probe(struct usb_interface *interface, /* set up the endpoint information */ /* use only the first bulk-in and bulk-out endpoints */ - iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (!dev->bulk_in_endpointAddr && - usb_endpoint_is_bulk_in(endpoint)) { - /* we found a bulk in endpoint */ - buffer_size = usb_endpoint_maxp(endpoint); - dev->bulk_in_size = buffer_size; - dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; - dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); - if (!dev->bulk_in_buffer) - goto error; - dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->bulk_in_urb) - goto error; - } - - if (!dev->bulk_out_endpointAddr && - usb_endpoint_is_bulk_out(endpoint)) { - /* we found a bulk out endpoint */ - dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; - } - } - if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { dev_err(&interface->dev, "Could not find both bulk-in and bulk-out endpoints\n"); goto error; } + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); @@ -563,9 +554,9 @@ static int skel_probe(struct usb_interface *interface, return 0; error: - if (dev) - /* this frees allocated memory */ - kref_put(&dev->kref, skel_delete); + /* this frees allocated memory */ + kref_put(&dev->kref, skel_delete); + return retval; } diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index e4cb9f0625e8..5d8b2c261940 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -430,36 +430,8 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, return retval; } -static struct vhci_device *get_vdev(struct usb_device *udev) +static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) { - struct platform_device *pdev; - struct usb_hcd *hcd; - struct vhci_hcd *vhci; - int pdev_nr, rhport; - - if (!udev) - return NULL; - - for (pdev_nr = 0; pdev_nr < vhci_num_controllers; pdev_nr++) { - pdev = *(vhci_pdevs + pdev_nr); - if (pdev == NULL) - continue; - hcd = platform_get_drvdata(pdev); - if (hcd == NULL) - continue; - vhci = hcd_to_vhci(hcd); - for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - if (vhci->vdev[rhport].udev == udev) - return &vhci->vdev[rhport]; - } - } - - return NULL; -} - -static void vhci_tx_urb(struct urb *urb) -{ - struct vhci_device *vdev = get_vdev(urb->dev); struct vhci_priv *priv; struct vhci_hcd *vhci; unsigned long flags; @@ -601,7 +573,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, } out: - vhci_tx_urb(urb); + vhci_tx_urb(urb, vdev); spin_unlock_irqrestore(&vhci->lock, flags); return 0; diff --git a/drivers/vme/vme.c b/drivers/vme/vme.c index 0035cf79760a..6a3ead42aba8 100644 --- a/drivers/vme/vme.c +++ b/drivers/vme/vme.c @@ -76,9 +76,16 @@ static struct vme_bridge *find_bridge(struct vme_resource *resource) } } -/* +/** + * vme_free_consistent - Allocate contiguous memory. + * @resource: Pointer to VME resource. + * @size: Size of allocation required. + * @dma: Pointer to variable to store physical address of allocation. + * * Allocate a contiguous block of memory for use by the driver. This is used to * create the buffers for the slave windows. + * + * Return: Virtual address of allocation on success, NULL on failure. */ void *vme_alloc_consistent(struct vme_resource *resource, size_t size, dma_addr_t *dma) @@ -111,8 +118,14 @@ void *vme_alloc_consistent(struct vme_resource *resource, size_t size, } EXPORT_SYMBOL(vme_alloc_consistent); -/* - * Free previously allocated contiguous block of memory. +/** + * vme_free_consistent - Free previously allocated memory. + * @resource: Pointer to VME resource. + * @size: Size of allocation to free. + * @vaddr: Virtual address of allocation. + * @dma: Physical address of allocation. + * + * Free previously allocated block of contiguous memory. */ void vme_free_consistent(struct vme_resource *resource, size_t size, void *vaddr, dma_addr_t dma) @@ -145,6 +158,16 @@ void vme_free_consistent(struct vme_resource *resource, size_t size, } EXPORT_SYMBOL(vme_free_consistent); +/** + * vme_get_size - Helper function returning size of a VME window + * @resource: Pointer to VME slave or master resource. + * + * Determine the size of the VME window provided. This is a helper + * function, wrappering the call to vme_master_get or vme_slave_get + * depending on the type of window resource handed to it. + * + * Return: Size of the window on success, zero on failure. + */ size_t vme_get_size(struct vme_resource *resource) { int enabled, retval; @@ -259,9 +282,16 @@ static u32 vme_get_aspace(int am) return 0; } -/* - * Request a slave image with specific attributes, return some unique - * identifier. +/** + * vme_slave_request - Request a VME slave window resource. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @address: Required VME address space. + * @cycle: Required VME data transfer cycle type. + * + * Request use of a VME window resource capable of being set for the requested + * address space and data transfer cycle. + * + * Return: Pointer to VME resource on success, NULL on failure. */ struct vme_resource *vme_slave_request(struct vme_dev *vdev, u32 address, u32 cycle) @@ -327,6 +357,23 @@ err_bus: } EXPORT_SYMBOL(vme_slave_request); +/** + * vme_slave_set - Set VME slave window configuration. + * @resource: Pointer to VME slave resource. + * @enabled: State to which the window should be configured. + * @vme_base: Base address for the window. + * @size: Size of the VME window. + * @buf_base: Based address of buffer used to provide VME slave window storage. + * @aspace: VME address space for the VME window. + * @cycle: VME data transfer cycle type for the VME window. + * + * Set configuration for provided VME slave window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device, if an invalid resource has been provided or invalid + * attributes are provided. Hardware specific errors may also be + * returned. + */ int vme_slave_set(struct vme_resource *resource, int enabled, unsigned long long vme_base, unsigned long long size, dma_addr_t buf_base, u32 aspace, u32 cycle) @@ -362,6 +409,21 @@ int vme_slave_set(struct vme_resource *resource, int enabled, } EXPORT_SYMBOL(vme_slave_set); +/** + * vme_slave_get - Retrieve VME slave window configuration. + * @resource: Pointer to VME slave resource. + * @enabled: Pointer to variable for storing state. + * @vme_base: Pointer to variable for storing window base address. + * @size: Pointer to variable for storing window size. + * @buf_base: Pointer to variable for storing slave buffer base address. + * @aspace: Pointer to variable for storing VME address space. + * @cycle: Pointer to variable for storing VME data transfer cycle type. + * + * Return configuration for provided VME slave window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if an invalid resource has been provided. + */ int vme_slave_get(struct vme_resource *resource, int *enabled, unsigned long long *vme_base, unsigned long long *size, dma_addr_t *buf_base, u32 *aspace, u32 *cycle) @@ -386,6 +448,12 @@ int vme_slave_get(struct vme_resource *resource, int *enabled, } EXPORT_SYMBOL(vme_slave_get); +/** + * vme_slave_free - Free VME slave window + * @resource: Pointer to VME slave resource. + * + * Free the provided slave resource so that it may be reallocated. + */ void vme_slave_free(struct vme_resource *resource) { struct vme_slave_resource *slave_image; @@ -415,9 +483,17 @@ void vme_slave_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_slave_free); -/* - * Request a master image with specific attributes, return some unique - * identifier. +/** + * vme_master_request - Request a VME master window resource. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @address: Required VME address space. + * @cycle: Required VME data transfer cycle type. + * @dwidth: Required VME data transfer width. + * + * Request use of a VME window resource capable of being set for the requested + * address space, data transfer cycle and width. + * + * Return: Pointer to VME resource on success, NULL on failure. */ struct vme_resource *vme_master_request(struct vme_dev *vdev, u32 address, u32 cycle, u32 dwidth) @@ -486,6 +562,23 @@ err_bus: } EXPORT_SYMBOL(vme_master_request); +/** + * vme_master_set - Set VME master window configuration. + * @resource: Pointer to VME master resource. + * @enabled: State to which the window should be configured. + * @vme_base: Base address for the window. + * @size: Size of the VME window. + * @aspace: VME address space for the VME window. + * @cycle: VME data transfer cycle type for the VME window. + * @dwidth: VME data transfer width for the VME window. + * + * Set configuration for provided VME master window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device, if an invalid resource has been provided or invalid + * attributes are provided. Hardware specific errors may also be + * returned. + */ int vme_master_set(struct vme_resource *resource, int enabled, unsigned long long vme_base, unsigned long long size, u32 aspace, u32 cycle, u32 dwidth) @@ -522,6 +615,21 @@ int vme_master_set(struct vme_resource *resource, int enabled, } EXPORT_SYMBOL(vme_master_set); +/** + * vme_master_get - Retrieve VME master window configuration. + * @resource: Pointer to VME master resource. + * @enabled: Pointer to variable for storing state. + * @vme_base: Pointer to variable for storing window base address. + * @size: Pointer to variable for storing window size. + * @aspace: Pointer to variable for storing VME address space. + * @cycle: Pointer to variable for storing VME data transfer cycle type. + * @dwidth: Pointer to variable for storing VME data transfer width. + * + * Return configuration for provided VME master window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if an invalid resource has been provided. + */ int vme_master_get(struct vme_resource *resource, int *enabled, unsigned long long *vme_base, unsigned long long *size, u32 *aspace, u32 *cycle, u32 *dwidth) @@ -546,8 +654,20 @@ int vme_master_get(struct vme_resource *resource, int *enabled, } EXPORT_SYMBOL(vme_master_get); -/* - * Read data out of VME space into a buffer. +/** + * vme_master_write - Read data from VME space into a buffer. + * @resource: Pointer to VME master resource. + * @buf: Pointer to buffer where data should be transferred. + * @count: Number of bytes to transfer. + * @offset: Offset into VME master window at which to start transfer. + * + * Perform read of count bytes of data from location on VME bus which maps into + * the VME master window at offset to buf. + * + * Return: Number of bytes read, -EINVAL if resource is not a VME master + * resource or read operation is not supported. -EFAULT returned if + * invalid offset is provided. Hardware specific errors may also be + * returned. */ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count, loff_t offset) @@ -583,8 +703,20 @@ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count, } EXPORT_SYMBOL(vme_master_read); -/* - * Write data out to VME space from a buffer. +/** + * vme_master_write - Write data out to VME space from a buffer. + * @resource: Pointer to VME master resource. + * @buf: Pointer to buffer holding data to transfer. + * @count: Number of bytes to transfer. + * @offset: Offset into VME master window at which to start transfer. + * + * Perform write of count bytes of data from buf to location on VME bus which + * maps into the VME master window at offset. + * + * Return: Number of bytes written, -EINVAL if resource is not a VME master + * resource or write operation is not supported. -EFAULT returned if + * invalid offset is provided. Hardware specific errors may also be + * returned. */ ssize_t vme_master_write(struct vme_resource *resource, void *buf, size_t count, loff_t offset) @@ -619,8 +751,24 @@ ssize_t vme_master_write(struct vme_resource *resource, void *buf, } EXPORT_SYMBOL(vme_master_write); -/* - * Perform RMW cycle to provided location. +/** + * vme_master_rmw - Perform read-modify-write cycle. + * @resource: Pointer to VME master resource. + * @mask: Bits to be compared and swapped in operation. + * @compare: Bits to be compared with data read from offset. + * @swap: Bits to be swapped in data read from offset. + * @offset: Offset into VME master window at which to perform operation. + * + * Perform read-modify-write cycle on provided location: + * - Location on VME bus is read. + * - Bits selected by mask are compared with compare. + * - Where a selected bit matches that in compare and are selected in swap, + * the bit is swapped. + * - Result written back to location on VME bus. + * + * Return: Bytes written on success, -EINVAL if resource is not a VME master + * resource or RMW operation is not supported. Hardware specific + * errors may also be returned. */ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, unsigned int compare, unsigned int swap, loff_t offset) @@ -644,6 +792,17 @@ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, } EXPORT_SYMBOL(vme_master_rmw); +/** + * vme_master_mmap - Mmap region of VME master window. + * @resource: Pointer to VME master resource. + * @vma: Pointer to definition of user mapping. + * + * Memory map a region of the VME master window into user space. + * + * Return: Zero on success, -EINVAL if resource is not a VME master + * resource or -EFAULT if map exceeds window size. Other generic mmap + * errors may also be returned. + */ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma) { struct vme_master_resource *image; @@ -670,6 +829,12 @@ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma) } EXPORT_SYMBOL(vme_master_mmap); +/** + * vme_master_free - Free VME master window + * @resource: Pointer to VME master resource. + * + * Free the provided master resource so that it may be reallocated. + */ void vme_master_free(struct vme_resource *resource) { struct vme_master_resource *master_image; @@ -699,9 +864,15 @@ void vme_master_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_master_free); -/* - * Request a DMA controller with specific attributes, return some unique - * identifier. +/** + * vme_dma_request - Request a DMA controller. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @route: Required src/destination combination. + * + * Request a VME DMA controller with capability to perform transfers bewteen + * requested source/destination combination. + * + * Return: Pointer to VME DMA resource on success, NULL on failure. */ struct vme_resource *vme_dma_request(struct vme_dev *vdev, u32 route) { @@ -768,8 +939,15 @@ err_bus: } EXPORT_SYMBOL(vme_dma_request); -/* - * Start new list +/** + * vme_new_dma_list - Create new VME DMA list. + * @resource: Pointer to VME DMA resource. + * + * Create a new VME DMA list. It is the responsibility of the user to free + * the list once it is no longer required with vme_dma_list_free(). + * + * Return: Pointer to new VME DMA list, NULL on allocation failure or invalid + * VME DMA resource. */ struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource) { @@ -796,8 +974,16 @@ struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource) } EXPORT_SYMBOL(vme_new_dma_list); -/* - * Create "Pattern" type attributes +/** + * vme_dma_pattern_attribute - Create "Pattern" type VME DMA list attribute. + * @pattern: Value to use used as pattern + * @type: Type of pattern to be written. + * + * Create VME DMA list attribute for pattern generation. It is the + * responsibility of the user to free used attributes using + * vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, u32 type) { @@ -831,8 +1017,15 @@ err_attr: } EXPORT_SYMBOL(vme_dma_pattern_attribute); -/* - * Create "PCI" type attributes +/** + * vme_dma_pci_attribute - Create "PCI" type VME DMA list attribute. + * @address: PCI base address for DMA transfer. + * + * Create VME DMA list attribute pointing to a location on PCI for DMA + * transfers. It is the responsibility of the user to free used attributes + * using vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t address) { @@ -869,8 +1062,18 @@ err_attr: } EXPORT_SYMBOL(vme_dma_pci_attribute); -/* - * Create "VME" type attributes +/** + * vme_dma_vme_attribute - Create "VME" type VME DMA list attribute. + * @address: VME base address for DMA transfer. + * @aspace: VME address space to use for DMA transfer. + * @cycle: VME bus cycle to use for DMA transfer. + * @dwidth: VME data width to use for DMA transfer. + * + * Create VME DMA list attribute pointing to a location on the VME bus for DMA + * transfers. It is the responsibility of the user to free used attributes + * using vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long address, u32 aspace, u32 cycle, u32 dwidth) @@ -908,8 +1111,12 @@ err_attr: } EXPORT_SYMBOL(vme_dma_vme_attribute); -/* - * Free attribute +/** + * vme_dma_free_attribute - Free DMA list attribute. + * @attributes: Pointer to DMA list attribute. + * + * Free VME DMA list attribute. VME DMA list attributes can be safely freed + * once vme_dma_list_add() has returned. */ void vme_dma_free_attribute(struct vme_dma_attr *attributes) { @@ -918,6 +1125,23 @@ void vme_dma_free_attribute(struct vme_dma_attr *attributes) } EXPORT_SYMBOL(vme_dma_free_attribute); +/** + * vme_dma_list_add - Add enty to a VME DMA list. + * @list: Pointer to VME list. + * @src: Pointer to DMA list attribute to use as source. + * @dest: Pointer to DMA list attribute to use as destination. + * @count: Number of bytes to transfer. + * + * Add an entry to the provided VME DMA list. Entry requires pointers to source + * and destination DMA attributes and a count. + * + * Please note, the attributes supported as source and destinations for + * transfers are hardware dependent. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if the link list has already been submitted for execution. + * Hardware specific errors also possible. + */ int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src, struct vme_dma_attr *dest, size_t count) { @@ -942,6 +1166,16 @@ int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src, } EXPORT_SYMBOL(vme_dma_list_add); +/** + * vme_dma_list_exec - Queue a VME DMA list for execution. + * @list: Pointer to VME list. + * + * Queue the provided VME DMA list for execution. The call will return once the + * list has been executed. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device. Hardware specific errors also possible. + */ int vme_dma_list_exec(struct vme_dma_list *list) { struct vme_bridge *bridge = list->parent->parent; @@ -962,6 +1196,15 @@ int vme_dma_list_exec(struct vme_dma_list *list) } EXPORT_SYMBOL(vme_dma_list_exec); +/** + * vme_dma_list_free - Free a VME DMA list. + * @list: Pointer to VME list. + * + * Free the provided DMA list and all its entries. + * + * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource + * is still in use. Hardware specific errors also possible. + */ int vme_dma_list_free(struct vme_dma_list *list) { struct vme_bridge *bridge = list->parent->parent; @@ -994,6 +1237,15 @@ int vme_dma_list_free(struct vme_dma_list *list) } EXPORT_SYMBOL(vme_dma_list_free); +/** + * vme_dma_free - Free a VME DMA resource. + * @resource: Pointer to VME DMA resource. + * + * Free the provided DMA resource so that it may be reallocated. + * + * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource + * is still active. + */ int vme_dma_free(struct vme_resource *resource) { struct vme_dma_resource *ctrlr; @@ -1099,6 +1351,22 @@ void vme_irq_handler(struct vme_bridge *bridge, int level, int statid) } EXPORT_SYMBOL(vme_irq_handler); +/** + * vme_irq_request - Request a specific VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority being requested. + * @statid: Interrupt vector being requested. + * @callback: Pointer to callback function called when VME interrupt/vector + * received. + * @priv_data: Generic pointer that will be passed to the callback function. + * + * Request callback to be attached as a handler for VME interrupts with provided + * level and statid. + * + * Return: Zero on success, -EINVAL on invalid vme device, level or if the + * function is not supported, -EBUSY if the level/statid combination is + * already in use. Hardware specific errors also possible. + */ int vme_irq_request(struct vme_dev *vdev, int level, int statid, void (*callback)(int, int, void *), void *priv_data) @@ -1142,6 +1410,14 @@ int vme_irq_request(struct vme_dev *vdev, int level, int statid, } EXPORT_SYMBOL(vme_irq_request); +/** + * vme_irq_free - Free a VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority of interrupt being freed. + * @statid: Interrupt vector of interrupt being freed. + * + * Remove previously attached callback from VME interrupt priority/vector. + */ void vme_irq_free(struct vme_dev *vdev, int level, int statid) { struct vme_bridge *bridge; @@ -1177,6 +1453,18 @@ void vme_irq_free(struct vme_dev *vdev, int level, int statid) } EXPORT_SYMBOL(vme_irq_free); +/** + * vme_irq_generate - Generate VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority at which to assert the interrupt. + * @statid: Interrupt vector to associate with the interrupt. + * + * Generate a VME interrupt of the provided level and with the provided + * statid. + * + * Return: Zero on success, -EINVAL on invalid vme device, level or if the + * function is not supported. Hardware specific errors also possible. + */ int vme_irq_generate(struct vme_dev *vdev, int level, int statid) { struct vme_bridge *bridge; @@ -1201,8 +1489,15 @@ int vme_irq_generate(struct vme_dev *vdev, int level, int statid) } EXPORT_SYMBOL(vme_irq_generate); -/* - * Request the location monitor, return resource or NULL +/** + * vme_lm_request - Request a VME location monitor + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Allocate a location monitor resource to the driver. A location monitor + * allows the driver to monitor accesses to a contiguous number of + * addresses on the VME bus. + * + * Return: Pointer to a VME resource on success or NULL on failure. */ struct vme_resource *vme_lm_request(struct vme_dev *vdev) { @@ -1218,7 +1513,7 @@ struct vme_resource *vme_lm_request(struct vme_dev *vdev) goto err_bus; } - /* Loop through DMA resources */ + /* Loop through LM resources */ list_for_each(lm_pos, &bridge->lm_resources) { lm = list_entry(lm_pos, struct vme_lm_resource, list); @@ -1264,6 +1559,17 @@ err_bus: } EXPORT_SYMBOL(vme_lm_request); +/** + * vme_lm_count - Determine number of VME Addresses monitored + * @resource: Pointer to VME location monitor resource. + * + * The number of contiguous addresses monitored is hardware dependent. + * Return the number of contiguous addresses monitored by the + * location monitor. + * + * Return: Count of addresses monitored or -EINVAL when provided with an + * invalid location monitor resource. + */ int vme_lm_count(struct vme_resource *resource) { struct vme_lm_resource *lm; @@ -1279,6 +1585,20 @@ int vme_lm_count(struct vme_resource *resource) } EXPORT_SYMBOL(vme_lm_count); +/** + * vme_lm_set - Configure location monitor + * @resource: Pointer to VME location monitor resource. + * @lm_base: Base address to monitor. + * @aspace: VME address space to monitor. + * @cycle: VME bus cycle type to monitor. + * + * Set the base address, address space and cycle type of accesses to be + * monitored by the location monitor. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base, u32 aspace, u32 cycle) { @@ -1301,6 +1621,20 @@ int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base, } EXPORT_SYMBOL(vme_lm_set); +/** + * vme_lm_get - Retrieve location monitor settings + * @resource: Pointer to VME location monitor resource. + * @lm_base: Pointer used to output the base address monitored. + * @aspace: Pointer used to output the address space monitored. + * @cycle: Pointer used to output the VME bus cycle type monitored. + * + * Retrieve the base address, address space and cycle type of accesses to + * be monitored by the location monitor. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base, u32 *aspace, u32 *cycle) { @@ -1323,6 +1657,21 @@ int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base, } EXPORT_SYMBOL(vme_lm_get); +/** + * vme_lm_attach - Provide callback for location monitor address + * @resource: Pointer to VME location monitor resource. + * @monitor: Offset to which callback should be attached. + * @callback: Pointer to callback function called when triggered. + * @data: Generic pointer that will be passed to the callback function. + * + * Attach a callback to the specificed offset into the location monitors + * monitored addresses. A generic pointer is provided to allow data to be + * passed to the callback when called. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_attach(struct vme_resource *resource, int monitor, void (*callback)(void *), void *data) { @@ -1345,6 +1694,18 @@ int vme_lm_attach(struct vme_resource *resource, int monitor, } EXPORT_SYMBOL(vme_lm_attach); +/** + * vme_lm_detach - Remove callback for location monitor address + * @resource: Pointer to VME location monitor resource. + * @monitor: Offset to which callback should be removed. + * + * Remove the callback associated with the specificed offset into the + * location monitors monitored addresses. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_detach(struct vme_resource *resource, int monitor) { struct vme_bridge *bridge = find_bridge(resource); @@ -1366,6 +1727,18 @@ int vme_lm_detach(struct vme_resource *resource, int monitor) } EXPORT_SYMBOL(vme_lm_detach); +/** + * vme_lm_free - Free allocated VME location monitor + * @resource: Pointer to VME location monitor resource. + * + * Free allocation of a VME location monitor. + * + * WARNING: This function currently expects that any callbacks that have + * been attached to the location monitor have been removed. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource. + */ void vme_lm_free(struct vme_resource *resource) { struct vme_lm_resource *lm; @@ -1392,6 +1765,16 @@ void vme_lm_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_lm_free); +/** + * vme_slot_num - Retrieve slot ID + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Retrieve the slot ID associated with the provided VME device. + * + * Return: The slot ID on success, -EINVAL if VME bridge cannot be determined + * or the function is not supported. Hardware specific errors may also + * be returned. + */ int vme_slot_num(struct vme_dev *vdev) { struct vme_bridge *bridge; @@ -1411,6 +1794,15 @@ int vme_slot_num(struct vme_dev *vdev) } EXPORT_SYMBOL(vme_slot_num); +/** + * vme_bus_num - Retrieve bus number + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Retrieve the bus enumeration associated with the provided VME device. + * + * Return: The bus number on success, -EINVAL if VME bridge cannot be + * determined. + */ int vme_bus_num(struct vme_dev *vdev) { struct vme_bridge *bridge; @@ -1556,6 +1948,15 @@ static int __vme_register_driver(struct vme_driver *drv, unsigned int ndevs) return err; } +/** + * vme_register_driver - Register a VME driver + * @drv: Pointer to VME driver structure to register. + * @ndevs: Maximum number of devices to allow to be enumerated. + * + * Register a VME device driver with the VME subsystem. + * + * Return: Zero on success, error value on registration failure. + */ int vme_register_driver(struct vme_driver *drv, unsigned int ndevs) { int err; @@ -1576,6 +1977,12 @@ int vme_register_driver(struct vme_driver *drv, unsigned int ndevs) } EXPORT_SYMBOL(vme_register_driver); +/** + * vme_unregister_driver - Unregister a VME driver + * @drv: Pointer to VME driver structure to unregister. + * + * Unregister a VME device driver from the VME subsystem. + */ void vme_unregister_driver(struct vme_driver *drv) { struct vme_dev *dev, *dev_tmp; diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c index 3749db8b4396..97a676bf5989 100644 --- a/drivers/w1/masters/matrox_w1.c +++ b/drivers/w1/masters/matrox_w1.c @@ -36,7 +36,6 @@ #include "../w1.h" #include "../w1_int.h" -#include "../w1_log.h" MODULE_LICENSE("GPL"); MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); @@ -157,9 +156,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent struct matrox_device *dev; int err; - assert(pdev != NULL); - assert(ent != NULL); - if (pdev->vendor != PCI_VENDOR_ID_MATROX || pdev->device != PCI_DEVICE_ID_MATROX_G400) return -ENODEV; @@ -224,8 +220,6 @@ static void matrox_w1_remove(struct pci_dev *pdev) { struct matrox_device *dev = pci_get_drvdata(pdev); - assert(dev != NULL); - if (dev->found) { w1_remove_master_device(dev->bus_master); iounmap(dev->virt_addr); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 0ef9f2663dbd..fb68465908f2 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -86,6 +86,12 @@ config W1_SLAVE_DS2433_CRC Each block has 30 bytes of data and a two byte CRC16. Full block writes are only allowed if the CRC is valid. +config W1_SLAVE_DS2438 + tristate "DS2438 Smart Battery Monitor 0x26 family support" + help + Say Y here if you want to use a 1-wire + DS2438 Smart Battery Monitor device support + config W1_SLAVE_DS2760 tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)" help diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index b4a358955ef9..54c63e420302 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o +obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c new file mode 100644 index 000000000000..5ededb4965e1 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.c @@ -0,0 +1,390 @@ +/* + * 1-Wire implementation for the ds2438 chip + * + * Copyright (c) 2017 Mariusz Bialonczyk <manio@skyboo.net> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include "../w1.h" +#include "../w1_family.h" + +#define W1_DS2438_RETRIES 3 + +/* Memory commands */ +#define W1_DS2438_READ_SCRATCH 0xBE +#define W1_DS2438_WRITE_SCRATCH 0x4E +#define W1_DS2438_COPY_SCRATCH 0x48 +#define W1_DS2438_RECALL_MEMORY 0xB8 +/* Register commands */ +#define W1_DS2438_CONVERT_TEMP 0x44 +#define W1_DS2438_CONVERT_VOLTAGE 0xB4 + +#define DS2438_PAGE_SIZE 8 +#define DS2438_ADC_INPUT_VAD 0 +#define DS2438_ADC_INPUT_VDD 1 +#define DS2438_MAX_CONVERSION_TIME 10 /* ms */ + +/* Page #0 definitions */ +#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */ +#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */ +#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */ +#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */ +#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */ +#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */ +#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */ +#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */ + +#define DS2438_TEMP_LSB 0x01 +#define DS2438_TEMP_MSB 0x02 +#define DS2438_VOLTAGE_LSB 0x03 +#define DS2438_VOLTAGE_MSB 0x04 +#define DS2438_CURRENT_LSB 0x05 +#define DS2438_CURRENT_MSB 0x06 +#define DS2438_THRESHOLD 0x07 + +int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[2]; + u8 crc; + size_t count; + + while (retries--) { + crc = 0; + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1); + if (count == DS2438_PAGE_SIZE + 1) { + crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE); + + /* check for correct CRC */ + if ((u8)buf[DS2438_PAGE_SIZE] == crc) + return 0; + } + } + return -1; +} + +int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[3]; + u8 status; + int perform_write = 0; + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + /* reading one byte of result */ + status = w1_read_8(sl->master); + + /* if bit0=1, set a value to a mask for easy compare */ + if (value) + value = mask; + + if ((status & mask) == value) + return 0; /* already set as requested */ + else { + /* changing bit */ + status ^= mask; + perform_write = 1; + } + break; + } + + if (perform_write) { + retries = W1_DS2438_RETRIES; + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_WRITE_SCRATCH; + w1_buf[1] = 0x00; + w1_buf[2] = status; + w1_write_block(sl->master, w1_buf, 3); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_COPY_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + return 0; + } + } + return -1; +} + +uint16_t w1_ds2438_get_voltage(struct w1_slave *sl, int adc_input, uint16_t *voltage) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) { + ret = -1; + goto pre_unlock; + } + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]); + ret = 0; + } else + ret = -1; + +pre_unlock: + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static ssize_t iad_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0) + ret = 1; + else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t page0_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + memcpy(buf, &w1_buf, DS2438_PAGE_SIZE); + ret = DS2438_PAGE_SIZE; + } else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t temperature_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + int16_t temp; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_temperature(sl, &temp) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", temp); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static ssize_t vad_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static ssize_t vdd_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static BIN_ATTR(iad, S_IRUGO | S_IWUSR | S_IWGRP, NULL, iad_write, 1); +static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE); +static BIN_ATTR_RO(temperature, 0/* real length varies */); +static BIN_ATTR_RO(vad, 0/* real length varies */); +static BIN_ATTR_RO(vdd, 0/* real length varies */); + +static struct bin_attribute *w1_ds2438_bin_attrs[] = { + &bin_attr_iad, + &bin_attr_page0, + &bin_attr_temperature, + &bin_attr_vad, + &bin_attr_vdd, + NULL, +}; + +static const struct attribute_group w1_ds2438_group = { + .bin_attrs = w1_ds2438_bin_attrs, +}; + +static const struct attribute_group *w1_ds2438_groups[] = { + &w1_ds2438_group, + NULL, +}; + +static struct w1_family_ops w1_ds2438_fops = { + .groups = w1_ds2438_groups, +}; + +static struct w1_family w1_ds2438_family = { + .fid = W1_FAMILY_DS2438, + .fops = &w1_ds2438_fops, +}; +module_w1_family(w1_ds2438_family); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>"); +MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438)); diff --git a/drivers/w1/slaves/w1_ds2760.h b/drivers/w1/slaves/w1_ds2760.h index 58e774141568..24168c94eeae 100644 --- a/drivers/w1/slaves/w1_ds2760.h +++ b/drivers/w1/slaves/w1_ds2760.h @@ -24,11 +24,13 @@ #define DS2760_DATA_SIZE 0x40 #define DS2760_PROTECTION_REG 0x00 + #define DS2760_STATUS_REG 0x01 - #define DS2760_STATUS_IE (1 << 2) - #define DS2760_STATUS_SWEN (1 << 3) - #define DS2760_STATUS_RNAOP (1 << 4) - #define DS2760_STATUS_PMOD (1 << 5) +#define DS2760_STATUS_IE (1 << 2) +#define DS2760_STATUS_SWEN (1 << 3) +#define DS2760_STATUS_RNAOP (1 << 4) +#define DS2760_STATUS_PMOD (1 << 5) + #define DS2760_EEPROM_REG 0x07 #define DS2760_SPECIAL_FEATURE_REG 0x08 #define DS2760_VOLTAGE_MSB 0x0c diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 90a3d9338fd2..8511d1685db9 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -29,7 +29,6 @@ #include <linux/atomic.h> #include "w1.h" -#include "w1_log.h" #include "w1_int.h" #include "w1_family.h" #include "w1_netlink.h" diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index c4a6b257a367..869a3ff87d29 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -29,6 +29,7 @@ #define W1_COUNTER_DS2423 0x1D #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 +#define W1_FAMILY_DS2438 0x26 #define W1_THERM_DS18B20 0x28 #define W1_FAMILY_DS2408 0x29 #define W1_EEPROM_DS2431 0x2D diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 2cae7b29bb5f..1072a2e620bb 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -22,7 +22,6 @@ #include <linux/moduleparam.h> #include "w1.h" -#include "w1_log.h" #include "w1_netlink.h" #include "w1_int.h" diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index de8bebc27896..1134e6b1eb02 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -19,7 +19,6 @@ #include <linux/module.h> #include "w1.h" -#include "w1_log.h" static int w1_delay_parm = 1; module_param_named(delay_coef, w1_delay_parm, int, 0); diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index 49e520ca79c5..027950f997d1 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -18,13 +18,10 @@ #include <linux/connector.h> #include "w1.h" -#include "w1_log.h" #include "w1_netlink.h" #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) - /* Bundle together everything required to process a request in one memory * allocation. */ @@ -598,7 +595,7 @@ static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd)); } - reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size); + reply_size = min(CONNECTOR_MAX_MSG_SIZE, reply_size); /* allocate space for the block, a copy of the original message, * one node per cmd to point into the original message, diff --git a/drivers/zorro/zorro-driver.c b/drivers/zorro/zorro-driver.c index eacae1434b73..fa23b7366b98 100644 --- a/drivers/zorro/zorro-driver.c +++ b/drivers/zorro/zorro-driver.c @@ -14,6 +14,8 @@ #include <linux/module.h> #include <linux/zorro.h> +#include "zorro.h" + /** * zorro_match_device - Tell if a Zorro device structure has a matching @@ -161,12 +163,13 @@ static int zorro_uevent(struct device *dev, struct kobj_uevent_env *env) } struct bus_type zorro_bus_type = { - .name = "zorro", - .dev_name = "zorro", - .match = zorro_bus_match, - .uevent = zorro_uevent, - .probe = zorro_device_probe, - .remove = zorro_device_remove, + .name = "zorro", + .dev_name = "zorro", + .dev_groups = zorro_device_attribute_groups, + .match = zorro_bus_match, + .uevent = zorro_uevent, + .probe = zorro_device_probe, + .remove = zorro_device_remove, }; EXPORT_SYMBOL(zorro_bus_type); diff --git a/drivers/zorro/zorro-sysfs.c b/drivers/zorro/zorro-sysfs.c index 9282dbf5abdb..3d34dba9bb2d 100644 --- a/drivers/zorro/zorro-sysfs.c +++ b/drivers/zorro/zorro-sysfs.c @@ -23,33 +23,33 @@ /* show configuration fields */ #define zorro_config_attr(name, field, format_string) \ -static ssize_t \ -show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ struct zorro_dev *z; \ \ z = to_zorro_dev(dev); \ return sprintf(buf, format_string, z->field); \ } \ -static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); +static DEVICE_ATTR_RO(name); zorro_config_attr(id, id, "0x%08x\n"); zorro_config_attr(type, rom.er_Type, "0x%02x\n"); zorro_config_attr(slotaddr, slotaddr, "0x%04x\n"); zorro_config_attr(slotsize, slotsize, "0x%04x\n"); -static ssize_t -show_serial(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t serial_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct zorro_dev *z; z = to_zorro_dev(dev); return sprintf(buf, "0x%08x\n", be32_to_cpu(z->rom.er_SerialNumber)); } +static DEVICE_ATTR_RO(serial); -static DEVICE_ATTR(serial, S_IRUGO, show_serial, NULL); - -static ssize_t zorro_show_resource(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t resource_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct zorro_dev *z = to_zorro_dev(dev); @@ -58,8 +58,27 @@ static ssize_t zorro_show_resource(struct device *dev, struct device_attribute * (unsigned long)zorro_resource_end(z), zorro_resource_flags(z)); } +static DEVICE_ATTR_RO(resource); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct zorro_dev *z = to_zorro_dev(dev); -static DEVICE_ATTR(resource, S_IRUGO, zorro_show_resource, NULL); + return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *zorro_device_attrs[] = { + &dev_attr_id.attr, + &dev_attr_type.attr, + &dev_attr_serial.attr, + &dev_attr_slotaddr.attr, + &dev_attr_slotsize.attr, + &dev_attr_resource.attr, + &dev_attr_modalias.attr, + NULL +}; static ssize_t zorro_read_config(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, @@ -88,32 +107,17 @@ static struct bin_attribute zorro_config_attr = { .read = zorro_read_config, }; -static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct zorro_dev *z = to_zorro_dev(dev); - - return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id); -} - -static DEVICE_ATTR(modalias, S_IRUGO, modalias_show, NULL); +static struct bin_attribute *zorro_device_bin_attrs[] = { + &zorro_config_attr, + NULL +}; -int zorro_create_sysfs_dev_files(struct zorro_dev *z) -{ - struct device *dev = &z->dev; - int error; - - /* current configuration's attributes */ - if ((error = device_create_file(dev, &dev_attr_id)) || - (error = device_create_file(dev, &dev_attr_type)) || - (error = device_create_file(dev, &dev_attr_serial)) || - (error = device_create_file(dev, &dev_attr_slotaddr)) || - (error = device_create_file(dev, &dev_attr_slotsize)) || - (error = device_create_file(dev, &dev_attr_resource)) || - (error = device_create_file(dev, &dev_attr_modalias)) || - (error = sysfs_create_bin_file(&dev->kobj, &zorro_config_attr))) - return error; - - return 0; -} +static const struct attribute_group zorro_device_attr_group = { + .attrs = zorro_device_attrs, + .bin_attrs = zorro_device_bin_attrs, +}; +const struct attribute_group *zorro_device_attribute_groups[] = { + &zorro_device_attr_group, + NULL +}; diff --git a/drivers/zorro/zorro.c b/drivers/zorro/zorro.c index d295d9878dff..cc1b1ac57d61 100644 --- a/drivers/zorro/zorro.c +++ b/drivers/zorro/zorro.c @@ -197,9 +197,6 @@ static int __init amiga_zorro_probe(struct platform_device *pdev) put_device(&z->dev); continue; } - error = zorro_create_sysfs_dev_files(z); - if (error) - dev_err(&z->dev, "Error creating sysfs files\n"); } /* Mark all available Zorro II memory */ diff --git a/drivers/zorro/zorro.h b/drivers/zorro/zorro.h index 34119fb4e560..4f805c01cfbc 100644 --- a/drivers/zorro/zorro.h +++ b/drivers/zorro/zorro.h @@ -5,5 +5,4 @@ extern void zorro_name_device(struct zorro_dev *z); static inline void zorro_name_device(struct zorro_dev *dev) { } #endif -extern int zorro_create_sysfs_dev_files(struct zorro_dev *z); - +extern const struct attribute_group *zorro_device_attribute_groups[]; |