| /* 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); |