Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06fb0137 authored by Kamil Debski's avatar Kamil Debski Committed by Kishon Vijay Abraham I
Browse files

phy: Add new Exynos USB 2.0 PHY driver



Add a new driver for the Exynos USB 2.0 PHY. The new driver uses the generic
PHY framework. The driver includes support for the Exynos 4x10 and 4x12
SoC families.

Signed-off-by: default avatarKamil Debski <k.debski@samsung.com>
Reviewed-by: default avatarTomasz Figa <t.figa@samsung.com>
Signed-off-by: default avatarKishon Vijay Abraham I <kishon@ti.com>
parent b5d682f4
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -20,3 +20,56 @@ Required properties:
- compatible : should be "samsung,exynos5250-dp-video-phy";
- reg : offset and length of the Display Port PHY register set;
- #phy-cells : from the generic PHY bindings, must be 0;

Samsung S5P/EXYNOS SoC series USB PHY
-------------------------------------------------

Required properties:
- compatible : should be one of the listed compatibles:
	- "samsung,exynos4210-usb2-phy"
	- "samsung,exynos4x12-usb2-phy"
- reg : a list of registers used by phy driver
	- first and obligatory is the location of phy modules registers
- samsung,sysreg-phandle - handle to syscon used to control the system registers
- samsung,pmureg-phandle - handle to syscon used to control PMU registers
- #phy-cells : from the generic phy bindings, must be 1;
- clocks and clock-names:
	- the "phy" clock is required by the phy module, used as a gate
	- the "ref" clock is used to get the rate of the clock provided to the
	  PHY module

The first phandle argument in the PHY specifier identifies the PHY, its
meaning is compatible dependent. For the currently supported SoCs (Exynos 4210
and Exynos 4212) it is as follows:
  0 - USB device ("device"),
  1 - USB host ("host"),
  2 - HSIC0 ("hsic0"),
  3 - HSIC1 ("hsic1"),

Exynos 4210 and Exynos 4212 use mode switching and require that mode switch
register is supplied.

Example:

For Exynos 4412 (compatible with Exynos 4212):

usbphy: phy@125b0000 {
	compatible = "samsung,exynos4x12-usb2-phy";
	reg = <0x125b0000 0x100>;
	clocks = <&clock 305>, <&clock 2>;
	clock-names = "phy", "ref";
	status = "okay";
	#phy-cells = <1>;
	samsung,sysreg-phandle = <&sys_reg>;
	samsung,pmureg-phandle = <&pmu_reg>;
};

Then the PHY can be used in other nodes such as:

phy-consumer@12340000 {
	phys = <&usbphy 2>;
	phy-names = "phy";
};

Refer to DT bindings documentation of particular PHY consumer devices for more
information about required PHYs and the way of specification.
+135 −0
Original line number Diff line number Diff line
.------------------------------------------------------------------------------+
|			Samsung USB 2.0 PHY adaptation layer		       |
+-----------------------------------------------------------------------------+'

| 1. Description
+----------------

The architecture of the USB 2.0 PHY module in Samsung SoCs is similar
among many SoCs. In spite of the similarities it proved difficult to
create a one driver that would fit all these PHY controllers. Often
the differences were minor and were found in particular bits of the
registers of the PHY. In some rare cases the order of register writes or
the PHY powering up process had to be altered. This adaptation layer is
a compromise between having separate drivers and having a single driver
with added support for many special cases.

| 2. Files description
+----------------------

- phy-samsung-usb2.c
   This is the main file of the adaptation layer. This file contains
   the probe function and provides two callbacks to the Generic PHY
   Framework. This two callbacks are used to power on and power off the
   phy. They carry out the common work that has to be done on all version
   of the PHY module. Depending on which SoC was chosen they execute SoC
   specific callbacks. The specific SoC version is selected by choosing
   the appropriate compatible string. In addition, this file contains
   struct of_device_id definitions for particular SoCs.

- phy-samsung-usb2.h
   This is the include file. It declares the structures used by this
   driver. In addition it should contain extern declarations for
   structures that describe particular SoCs.

| 3. Supporting SoCs
+--------------------

To support a new SoC a new file should be added to the drivers/phy
directory. Each SoC's configuration is stored in an instance of the
struct samsung_usb2_phy_config.

struct samsung_usb2_phy_config {
	const struct samsung_usb2_common_phy *phys;
	int (*rate_to_clk)(unsigned long, u32 *);
	unsigned int num_phys;
	bool has_mode_switch;
};

The num_phys is the number of phys handled by the driver. *phys is an
array that contains the configuration for each phy. The has_mode_switch
property is a boolean flag that determines whether the SoC has USB host
and device on a single pair of pins. If so, a special register has to
be modified to change the internal routing of these pins between a USB
device or host module.

For example the configuration for Exynos 4210 is following:

const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = {
	.has_mode_switch        = 0,
	.num_phys		= EXYNOS4210_NUM_PHYS,
	.phys			= exynos4210_phys,
	.rate_to_clk		= exynos4210_rate_to_clk,
}

- int (*rate_to_clk)(unsigned long, u32 *)
	The rate_to_clk callback is to convert the rate of the clock
	used as the reference clock for the PHY module to the value
	that should be written in the hardware register.

The exynos4210_phys configuration array is as follows:

static const struct samsung_usb2_common_phy exynos4210_phys[] = {
	{
		.label		= "device",
		.id		= EXYNOS4210_DEVICE,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "host",
		.id		= EXYNOS4210_HOST,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "hsic0",
		.id		= EXYNOS4210_HSIC0,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "hsic1",
		.id		= EXYNOS4210_HSIC1,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{},
};

- int (*power_on)(struct samsung_usb2_phy_instance *);
- int (*power_off)(struct samsung_usb2_phy_instance *);
	These two callbacks are used to power on and power off the phy
	by modifying appropriate registers.

Final change to the driver is adding appropriate compatible value to the
phy-samsung-usb2.c file. In case of Exynos 4210 the following lines were
added to the struct of_device_id samsung_usb2_phy_of_match[] array:

#ifdef CONFIG_PHY_EXYNOS4210_USB2
	{
		.compatible = "samsung,exynos4210-usb2-phy",
		.data = &exynos4210_usb2_phy_config,
	},
#endif

To add further flexibility to the driver the Kconfig file enables to
include support for selected SoCs in the compiled driver. The Kconfig
entry for Exynos 4210 is following:

config PHY_EXYNOS4210_USB2
	bool "Support for Exynos 4210"
	depends on PHY_SAMSUNG_USB2
	depends on CPU_EXYNOS4210
	help
	  Enable USB PHY support for Exynos 4210. This option requires that
	  Samsung USB 2.0 PHY driver is enabled and means that support for this
	  particular SoC is compiled in the driver. In case of Exynos 4210 four
	  phys are available - device, host, HSCI0 and HSCI1.

The newly created file that supports the new SoC has to be also added to the
Makefile. In case of Exynos 4210 the added line is following:

obj-$(CONFIG_PHY_EXYNOS4210_USB2)       += phy-exynos4210-usb2.o

After completing these steps the support for the new SoC should be ready.
+29 −0
Original line number Diff line number Diff line
@@ -107,4 +107,33 @@ config PHY_SUN4I_USB
	  This driver controls the entire USB PHY block, both the USB OTG
	  parts, as well as the 2 regular USB 2 host PHYs.

config PHY_SAMSUNG_USB2
	tristate "Samsung USB 2.0 PHY driver"
	select GENERIC_PHY
	select MFD_SYSCON
	help
	  Enable this to support the Samsung USB 2.0 PHY driver for Samsung
	  SoCs. This driver provides the interface for USB 2.0 PHY. Support for
	  particular SoCs has to be enabled in addition to this driver. Number
	  and type of supported phys depends on the SoC.

config PHY_EXYNOS4210_USB2
	bool "Support for Exynos 4210"
	depends on PHY_SAMSUNG_USB2
	depends on CPU_EXYNOS4210
	help
	  Enable USB PHY support for Exynos 4210. This option requires that
	  Samsung USB 2.0 PHY driver is enabled and means that support for this
	  particular SoC is compiled in the driver. In case of Exynos 4210 four
	  phys are available - device, host, HSIC0 and HSIC1.

config PHY_EXYNOS4X12_USB2
	bool "Support for Exynos 4x12"
	depends on PHY_SAMSUNG_USB2
	depends on (SOC_EXYNOS4212 || SOC_EXYNOS4412)
	help
	  Enable USB PHY support for Exynos 4x12. This option requires that
	  Samsung USB 2.0 PHY driver is enabled and means that support for this
	  particular SoC is compiled in the driver. In case of Exynos 4x12 four
	  phys are available - device, host, HSIC0 and HSIC1.
endmenu
+3 −0
Original line number Diff line number Diff line
@@ -12,3 +12,6 @@ obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
obj-$(CONFIG_TWL4030_USB)		+= phy-twl4030-usb.o
obj-$(CONFIG_PHY_EXYNOS5250_SATA)	+= phy-exynos5250-sata.o
obj-$(CONFIG_PHY_SUN4I_USB)		+= phy-sun4i-usb.o
obj-$(CONFIG_PHY_SAMSUNG_USB2)		+= phy-samsung-usb2.o
obj-$(CONFIG_PHY_EXYNOS4210_USB2)	+= phy-exynos4210-usb2.o
obj-$(CONFIG_PHY_EXYNOS4X12_USB2)	+= phy-exynos4x12-usb2.o
+261 −0
Original line number Diff line number Diff line
/*
 * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support
 *
 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
 * Author: Kamil Debski <k.debski@samsung.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/delay.h>
#include <linux/io.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#include "phy-samsung-usb2.h"

/* Exynos USB PHY registers */

/* PHY power control */
#define EXYNOS_4210_UPHYPWR			0x0

#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND	BIT(0)
#define EXYNOS_4210_UPHYPWR_PHY0_PWR		BIT(3)
#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR	BIT(4)
#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP		BIT(5)
#define EXYNOS_4210_UPHYPWR_PHY0	( \
	EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \
	EXYNOS_4210_UPHYPWR_PHY0_PWR | \
	EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \
	EXYNOS_4210_UPHYPWR_PHY0_SLEEP)

#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND	BIT(6)
#define EXYNOS_4210_UPHYPWR_PHY1_PWR		BIT(7)
#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP		BIT(8)
#define EXYNOS_4210_UPHYPWR_PHY1 ( \
	EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \
	EXYNOS_4210_UPHYPWR_PHY1_PWR | \
	EXYNOS_4210_UPHYPWR_PHY1_SLEEP)

#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND	BIT(9)
#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP		BIT(10)
#define EXYNOS_4210_UPHYPWR_HSIC0 ( \
	EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \
	EXYNOS_4210_UPHYPWR_HSIC0_SLEEP)

#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND	BIT(11)
#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP		BIT(12)
#define EXYNOS_4210_UPHYPWR_HSIC1 ( \
	EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \
	EXYNOS_4210_UPHYPWR_HSIC1_SLEEP)

/* PHY clock control */
#define EXYNOS_4210_UPHYCLK			0x4

#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK	(0x3 << 0)
#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET	0
#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ	(0x0 << 0)
#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ	(0x3 << 0)
#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ	(0x2 << 0)

#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP	BIT(2)
#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON	BIT(4)
#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON	BIT(7)

/* PHY reset control */
#define EXYNOS_4210_UPHYRST			0x8

#define EXYNOS_4210_URSTCON_PHY0		BIT(0)
#define EXYNOS_4210_URSTCON_OTG_HLINK		BIT(1)
#define EXYNOS_4210_URSTCON_OTG_PHYLINK		BIT(2)
#define EXYNOS_4210_URSTCON_PHY1_ALL		BIT(3)
#define EXYNOS_4210_URSTCON_PHY1_P0		BIT(4)
#define EXYNOS_4210_URSTCON_PHY1_P1P2		BIT(5)
#define EXYNOS_4210_URSTCON_HOST_LINK_ALL	BIT(6)
#define EXYNOS_4210_URSTCON_HOST_LINK_P0	BIT(7)
#define EXYNOS_4210_URSTCON_HOST_LINK_P1	BIT(8)
#define EXYNOS_4210_URSTCON_HOST_LINK_P2	BIT(9)

/* Isolation, configured in the power management unit */
#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET	0x704
#define EXYNOS_4210_USB_ISOL_DEVICE		BIT(0)
#define EXYNOS_4210_USB_ISOL_HOST_OFFSET	0x708
#define EXYNOS_4210_USB_ISOL_HOST		BIT(0)

/* USBYPHY1 Floating prevention */
#define EXYNOS_4210_UPHY1CON			0x34
#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION	0x1

/* Mode switching SUB Device <-> Host */
#define EXYNOS_4210_MODE_SWITCH_OFFSET		0x21c
#define EXYNOS_4210_MODE_SWITCH_MASK		1
#define EXYNOS_4210_MODE_SWITCH_DEVICE		0
#define EXYNOS_4210_MODE_SWITCH_HOST		1

enum exynos4210_phy_id {
	EXYNOS4210_DEVICE,
	EXYNOS4210_HOST,
	EXYNOS4210_HSIC0,
	EXYNOS4210_HSIC1,
	EXYNOS4210_NUM_PHYS,
};

/*
 * exynos4210_rate_to_clk() converts the supplied clock rate to the value that
 * can be written to the phy register.
 */
static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg)
{
	switch (rate) {
	case 12 * MHZ:
		*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ;
		break;
	case 24 * MHZ:
		*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ;
		break;
	case 48 * MHZ:
		*reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on)
{
	struct samsung_usb2_phy_driver *drv = inst->drv;
	u32 offset;
	u32 mask;

	switch (inst->cfg->id) {
	case EXYNOS4210_DEVICE:
		offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET;
		mask = EXYNOS_4210_USB_ISOL_DEVICE;
		break;
	case EXYNOS4210_HOST:
		offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET;
		mask = EXYNOS_4210_USB_ISOL_HOST;
		break;
	default:
		return;
	};

	regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask);
}

static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
{
	struct samsung_usb2_phy_driver *drv = inst->drv;
	u32 rstbits = 0;
	u32 phypwr = 0;
	u32 rst;
	u32 pwr;
	u32 clk;

	switch (inst->cfg->id) {
	case EXYNOS4210_DEVICE:
		phypwr =	EXYNOS_4210_UPHYPWR_PHY0;
		rstbits =	EXYNOS_4210_URSTCON_PHY0;
		break;
	case EXYNOS4210_HOST:
		phypwr =	EXYNOS_4210_UPHYPWR_PHY1;
		rstbits =	EXYNOS_4210_URSTCON_PHY1_ALL |
				EXYNOS_4210_URSTCON_PHY1_P0 |
				EXYNOS_4210_URSTCON_PHY1_P1P2 |
				EXYNOS_4210_URSTCON_HOST_LINK_ALL |
				EXYNOS_4210_URSTCON_HOST_LINK_P0;
		writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON);
		break;
	case EXYNOS4210_HSIC0:
		phypwr =	EXYNOS_4210_UPHYPWR_HSIC0;
		rstbits =	EXYNOS_4210_URSTCON_PHY1_P1P2 |
				EXYNOS_4210_URSTCON_HOST_LINK_P1;
		break;
	case EXYNOS4210_HSIC1:
		phypwr =	EXYNOS_4210_UPHYPWR_HSIC1;
		rstbits =	EXYNOS_4210_URSTCON_PHY1_P1P2 |
				EXYNOS_4210_URSTCON_HOST_LINK_P2;
		break;
	};

	if (on) {
		clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK);
		clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK;
		clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET;
		writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK);

		pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
		pwr &= ~phypwr;
		writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);

		rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST);
		rst |= rstbits;
		writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
		udelay(10);
		rst &= ~rstbits;
		writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST);
		/* The following delay is necessary for the reset sequence to be
		 * completed */
		udelay(80);
	} else {
		pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR);
		pwr |= phypwr;
		writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR);
	}
}

static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst)
{
	/* Order of initialisation is important - first power then isolation */
	exynos4210_phy_pwr(inst, 1);
	exynos4210_isol(inst, 0);

	return 0;
}

static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst)
{
	exynos4210_isol(inst, 1);
	exynos4210_phy_pwr(inst, 0);

	return 0;
}


static const struct samsung_usb2_common_phy exynos4210_phys[] = {
	{
		.label		= "device",
		.id		= EXYNOS4210_DEVICE,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "host",
		.id		= EXYNOS4210_HOST,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "hsic0",
		.id		= EXYNOS4210_HSIC0,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{
		.label		= "hsic1",
		.id		= EXYNOS4210_HSIC1,
		.power_on	= exynos4210_power_on,
		.power_off	= exynos4210_power_off,
	},
	{},
};

const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = {
	.has_mode_switch	= 0,
	.num_phys		= EXYNOS4210_NUM_PHYS,
	.phys			= exynos4210_phys,
	.rate_to_clk		= exynos4210_rate_to_clk,
};
Loading