|  | /* linux/drivers/usb/phy/phy-samsung-usb.c | 
|  | * | 
|  | * Copyright (c) 2012 Samsung Electronics Co., Ltd. | 
|  | *              http://www.samsung.com | 
|  | * | 
|  | * Author: Praveen Paneri <p.paneri@samsung.com> | 
|  | * | 
|  | * Samsung USB-PHY helper driver with common function calls; | 
|  | * interacts with Samsung USB 2.0 PHY controller driver and later | 
|  | * with Samsung USB 3.0 PHY driver. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License version 2 as | 
|  | * published by the Free Software Foundation. | 
|  | * | 
|  | * 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/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/device.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/usb/samsung_usb_phy.h> | 
|  |  | 
|  | #include "phy-samsung-usb.h" | 
|  |  | 
|  | int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy) | 
|  | { | 
|  | struct device_node *usbphy_sys; | 
|  |  | 
|  | /* Getting node for system controller interface for usb-phy */ | 
|  | usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys"); | 
|  | if (!usbphy_sys) { | 
|  | dev_err(sphy->dev, "No sys-controller interface for usb-phy\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | sphy->pmuregs = of_iomap(usbphy_sys, 0); | 
|  |  | 
|  | if (sphy->pmuregs == NULL) { | 
|  | dev_err(sphy->dev, "Can't get usb-phy pmu control register\n"); | 
|  | goto err0; | 
|  | } | 
|  |  | 
|  | sphy->sysreg = of_iomap(usbphy_sys, 1); | 
|  |  | 
|  | /* | 
|  | * Not returning error code here, since this situation is not fatal. | 
|  | * Few SoCs may not have this switch available | 
|  | */ | 
|  | if (sphy->sysreg == NULL) | 
|  | dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n"); | 
|  |  | 
|  | of_node_put(usbphy_sys); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err0: | 
|  | of_node_put(usbphy_sys); | 
|  | return -ENXIO; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt); | 
|  |  | 
|  | /* | 
|  | * Set isolation here for phy. | 
|  | * Here 'on = true' would mean USB PHY block is isolated, hence | 
|  | * de-activated and vice-versa. | 
|  | */ | 
|  | void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, bool on) | 
|  | { | 
|  | void __iomem *reg = NULL; | 
|  | u32 reg_val; | 
|  | u32 en_mask = 0; | 
|  |  | 
|  | if (!sphy->pmuregs) { | 
|  | dev_warn(sphy->dev, "Can't set pmu isolation\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { | 
|  | reg = sphy->pmuregs + sphy->drv_data->devphy_reg_offset; | 
|  | en_mask = sphy->drv_data->devphy_en_mask; | 
|  | } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { | 
|  | reg = sphy->pmuregs + sphy->drv_data->hostphy_reg_offset; | 
|  | en_mask = sphy->drv_data->hostphy_en_mask; | 
|  | } | 
|  |  | 
|  | reg_val = readl(reg); | 
|  |  | 
|  | if (on) | 
|  | reg_val &= ~en_mask; | 
|  | else | 
|  | reg_val |= en_mask; | 
|  |  | 
|  | writel(reg_val, reg); | 
|  |  | 
|  | if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) { | 
|  | writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL0); | 
|  | writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL1); | 
|  | } | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation_4210); | 
|  |  | 
|  | /* | 
|  | * Configure the mode of working of usb-phy here: HOST/DEVICE. | 
|  | */ | 
|  | void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy) | 
|  | { | 
|  | u32 reg; | 
|  |  | 
|  | if (!sphy->sysreg) { | 
|  | dev_warn(sphy->dev, "Can't configure specified phy mode\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | reg = readl(sphy->sysreg); | 
|  |  | 
|  | if (sphy->phy_type == USB_PHY_TYPE_DEVICE) | 
|  | reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK; | 
|  | else if (sphy->phy_type == USB_PHY_TYPE_HOST) | 
|  | reg |= EXYNOS_USB20PHY_CFG_HOST_LINK; | 
|  |  | 
|  | writel(reg, sphy->sysreg); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_cfg_sel); | 
|  |  | 
|  | /* | 
|  | * PHYs are different for USB Device and USB Host. | 
|  | * This make sure that correct PHY type is selected before | 
|  | * any operation on PHY. | 
|  | */ | 
|  | int samsung_usbphy_set_type(struct usb_phy *phy, | 
|  | enum samsung_usb_phy_type phy_type) | 
|  | { | 
|  | struct samsung_usbphy *sphy = phy_to_sphy(phy); | 
|  |  | 
|  | sphy->phy_type = phy_type; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_set_type); | 
|  |  | 
|  | int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy, | 
|  | unsigned long rate) | 
|  | { | 
|  | unsigned int clksel; | 
|  |  | 
|  | switch (rate) { | 
|  | case 12 * MHZ: | 
|  | clksel = PHYCLK_CLKSEL_12M; | 
|  | break; | 
|  | case 24 * MHZ: | 
|  | clksel = PHYCLK_CLKSEL_24M; | 
|  | break; | 
|  | case 48 * MHZ: | 
|  | clksel = PHYCLK_CLKSEL_48M; | 
|  | break; | 
|  | default: | 
|  | dev_err(sphy->dev, | 
|  | "Invalid reference clock frequency: %lu\n", rate); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return clksel; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_64xx); | 
|  |  | 
|  | int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy, | 
|  | unsigned long rate) | 
|  | { | 
|  | unsigned int clksel; | 
|  |  | 
|  | switch (rate) { | 
|  | case 9600 * KHZ: | 
|  | clksel = FSEL_CLKSEL_9600K; | 
|  | break; | 
|  | case 10 * MHZ: | 
|  | clksel = FSEL_CLKSEL_10M; | 
|  | break; | 
|  | case 12 * MHZ: | 
|  | clksel = FSEL_CLKSEL_12M; | 
|  | break; | 
|  | case 19200 * KHZ: | 
|  | clksel = FSEL_CLKSEL_19200K; | 
|  | break; | 
|  | case 20 * MHZ: | 
|  | clksel = FSEL_CLKSEL_20M; | 
|  | break; | 
|  | case 24 * MHZ: | 
|  | clksel = FSEL_CLKSEL_24M; | 
|  | break; | 
|  | case 50 * MHZ: | 
|  | clksel = FSEL_CLKSEL_50M; | 
|  | break; | 
|  | default: | 
|  | dev_err(sphy->dev, | 
|  | "Invalid reference clock frequency: %lu\n", rate); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return clksel; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_4x12); | 
|  |  | 
|  | /* | 
|  | * Returns reference clock frequency selection value | 
|  | */ | 
|  | int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) | 
|  | { | 
|  | struct clk *ref_clk; | 
|  | unsigned long rate; | 
|  | int refclk_freq; | 
|  |  | 
|  | /* | 
|  | * In exynos5250 USB host and device PHY use | 
|  | * external crystal clock XXTI | 
|  | */ | 
|  | if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) | 
|  | ref_clk = clk_get(sphy->dev, "ext_xtal"); | 
|  | else | 
|  | ref_clk = clk_get(sphy->dev, "xusbxti"); | 
|  | if (IS_ERR(ref_clk)) { | 
|  | dev_err(sphy->dev, "Failed to get reference clock\n"); | 
|  | return PTR_ERR(ref_clk); | 
|  | } | 
|  |  | 
|  | rate = clk_get_rate(ref_clk); | 
|  | refclk_freq = sphy->drv_data->rate_to_clksel(sphy, rate); | 
|  |  | 
|  | clk_put(ref_clk); | 
|  |  | 
|  | return refclk_freq; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq); |