/* 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(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; } switch (sphy->drv_data->cpu_type) { case TYPE_S3C64XX: /* * Do nothing: We will add here once S3C64xx goes for DT support */ break; case TYPE_EXYNOS4210: /* * Fall through since exynos4210 and exynos5250 have similar * register architecture: two separate registers for host and * device phy control with enable bit at position 0. */ case TYPE_EXYNOS5250: 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; } break; default: dev_err(sphy->dev, "Invalid SoC type\n"); return; } reg_val = readl(reg); if (on) reg_val &= ~en_mask; else reg_val |= en_mask; writel(reg_val, reg); } EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation); /* * 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); /* * Returns reference clock frequency selection value */ int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) { struct clk *ref_clk; int refclk_freq = 0; /* * In exynos5250 USB host and device PHY use * external crystal clock XXTI */ if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) ref_clk = devm_clk_get(sphy->dev, "ext_xtal"); else ref_clk = devm_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); } if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { /* set clock frequency for PLL */ switch (clk_get_rate(ref_clk)) { case 9600 * KHZ: refclk_freq = FSEL_CLKSEL_9600K; break; case 10 * MHZ: refclk_freq = FSEL_CLKSEL_10M; break; case 12 * MHZ: refclk_freq = FSEL_CLKSEL_12M; break; case 19200 * KHZ: refclk_freq = FSEL_CLKSEL_19200K; break; case 20 * MHZ: refclk_freq = FSEL_CLKSEL_20M; break; case 50 * MHZ: refclk_freq = FSEL_CLKSEL_50M; break; case 24 * MHZ: default: /* default reference clock */ refclk_freq = FSEL_CLKSEL_24M; break; } } else { switch (clk_get_rate(ref_clk)) { case 12 * MHZ: refclk_freq = PHYCLK_CLKSEL_12M; break; case 24 * MHZ: refclk_freq = PHYCLK_CLKSEL_24M; break; case 48 * MHZ: refclk_freq = PHYCLK_CLKSEL_48M; break; default: if (sphy->drv_data->cpu_type == TYPE_S3C64XX) refclk_freq = PHYCLK_CLKSEL_48M; else refclk_freq = PHYCLK_CLKSEL_24M; break; } } clk_put(ref_clk); return refclk_freq; } EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq);