summaryrefslogblamecommitdiff
path: root/drivers/phy/ti/phy-tusb1210.c
blob: 04baed24469f12df56be44cd753474b21d1dc871 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                        
  




                                                            

                         
                           
                              
                            
                                
                               

                                                    


                                                             








                                     


























                                                                                        







                                                                 

                                                                   











                                                      
                                                                              


                                                     
               
 
                                                            




                               
                                                 


                                                     

                                                              

                                 
                                              

                                                      

                                                              





                             
                                                             

 
                                       

                                        
                                      




                                            

                              
                




                                                                   

                          












                                                                       





                                                                                


                                                                        
 
                                                            

                                                                                      

                                                       

                                                                                       

                                

                                                                                   
 



                                                                        
                                     




                                                    














                                                         

                                                  


















                                               
// SPDX-License-Identifier: GPL-2.0-only
/*
 * tusb1210.c - TUSB1210 USB ULPI PHY driver
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
 */
#include <linux/module.h>
#include <linux/bitfield.h>
#include <linux/ulpi/driver.h>
#include <linux/ulpi/regs.h>
#include <linux/gpio/consumer.h>
#include <linux/phy/ulpi_phy.h>

#define TUSB1210_VENDOR_SPECIFIC2		0x80
#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK	GENMASK(3, 0)
#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK	GENMASK(5, 4)
#define TUSB1210_VENDOR_SPECIFIC2_DP_MASK	BIT(6)

struct tusb1210 {
	struct ulpi *ulpi;
	struct phy *phy;
	struct gpio_desc *gpio_reset;
	struct gpio_desc *gpio_cs;
	u8 vendor_specific2;
};

static int tusb1210_ulpi_write(struct tusb1210 *tusb, u8 reg, u8 val)
{
	int ret;

	ret = ulpi_write(tusb->ulpi, reg, val);
	if (ret)
		dev_err(&tusb->ulpi->dev, "error %d writing val 0x%02x to reg 0x%02x\n",
			ret, val, reg);

	return ret;
}

static int tusb1210_ulpi_read(struct tusb1210 *tusb, u8 reg, u8 *val)
{
	int ret;

	ret = ulpi_read(tusb->ulpi, reg);
	if (ret >= 0) {
		*val = ret;
		ret = 0;
	} else {
		dev_err(&tusb->ulpi->dev, "error %d reading reg 0x%02x\n", ret, reg);
	}

	return ret;
}

static int tusb1210_power_on(struct phy *phy)
{
	struct tusb1210 *tusb = phy_get_drvdata(phy);

	gpiod_set_value_cansleep(tusb->gpio_reset, 1);
	gpiod_set_value_cansleep(tusb->gpio_cs, 1);

	/* Restore the optional eye diagram optimization value */
	return tusb1210_ulpi_write(tusb, TUSB1210_VENDOR_SPECIFIC2,
				   tusb->vendor_specific2);
}

static int tusb1210_power_off(struct phy *phy)
{
	struct tusb1210 *tusb = phy_get_drvdata(phy);

	gpiod_set_value_cansleep(tusb->gpio_reset, 0);
	gpiod_set_value_cansleep(tusb->gpio_cs, 0);

	return 0;
}

static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
	struct tusb1210 *tusb = phy_get_drvdata(phy);
	int ret;
	u8 reg;

	ret = tusb1210_ulpi_read(tusb, ULPI_OTG_CTRL, &reg);
	if (ret < 0)
		return ret;

	switch (mode) {
	case PHY_MODE_USB_HOST:
		reg |= (ULPI_OTG_CTRL_DRVVBUS_EXT
			| ULPI_OTG_CTRL_ID_PULLUP
			| ULPI_OTG_CTRL_DP_PULLDOWN
			| ULPI_OTG_CTRL_DM_PULLDOWN);
		tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
		reg |= ULPI_OTG_CTRL_DRVVBUS;
		break;
	case PHY_MODE_USB_DEVICE:
		reg &= ~(ULPI_OTG_CTRL_DRVVBUS
			 | ULPI_OTG_CTRL_DP_PULLDOWN
			 | ULPI_OTG_CTRL_DM_PULLDOWN);
		tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
		reg &= ~ULPI_OTG_CTRL_DRVVBUS_EXT;
		break;
	default:
		/* nothing */
		return 0;
	}

	return tusb1210_ulpi_write(tusb, ULPI_OTG_CTRL, reg);
}

static const struct phy_ops phy_ops = {
	.power_on = tusb1210_power_on,
	.power_off = tusb1210_power_off,
	.set_mode = tusb1210_set_mode,
	.owner = THIS_MODULE,
};

static int tusb1210_probe(struct ulpi *ulpi)
{
	struct tusb1210 *tusb;
	u8 val, reg;
	int ret;

	tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL);
	if (!tusb)
		return -ENOMEM;

	tusb->ulpi = ulpi;

	tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset",
						   GPIOD_OUT_LOW);
	if (IS_ERR(tusb->gpio_reset))
		return PTR_ERR(tusb->gpio_reset);

	gpiod_set_value_cansleep(tusb->gpio_reset, 1);

	tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs",
						GPIOD_OUT_LOW);
	if (IS_ERR(tusb->gpio_cs))
		return PTR_ERR(tusb->gpio_cs);

	gpiod_set_value_cansleep(tusb->gpio_cs, 1);

	/*
	 * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye
	 * diagram optimization and DP/DM swap.
	 */

	ret = tusb1210_ulpi_read(tusb, TUSB1210_VENDOR_SPECIFIC2, &reg);
	if (ret)
		return ret;

	/* High speed output drive strength configuration */
	if (!device_property_read_u8(&ulpi->dev, "ihstx", &val))
		u8p_replace_bits(&reg, val, (u8)TUSB1210_VENDOR_SPECIFIC2_IHSTX_MASK);

	/* High speed output impedance configuration */
	if (!device_property_read_u8(&ulpi->dev, "zhsdrv", &val))
		u8p_replace_bits(&reg, val, (u8)TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_MASK);

	/* DP/DM swap control */
	if (!device_property_read_u8(&ulpi->dev, "datapolarity", &val))
		u8p_replace_bits(&reg, val, (u8)TUSB1210_VENDOR_SPECIFIC2_DP_MASK);

	ret = tusb1210_ulpi_write(tusb, TUSB1210_VENDOR_SPECIFIC2, reg);
	if (ret)
		return ret;

	tusb->vendor_specific2 = reg;

	tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
	if (IS_ERR(tusb->phy))
		return PTR_ERR(tusb->phy);

	phy_set_drvdata(tusb->phy, tusb);
	ulpi_set_drvdata(ulpi, tusb);
	return 0;
}

static void tusb1210_remove(struct ulpi *ulpi)
{
	struct tusb1210 *tusb = ulpi_get_drvdata(ulpi);

	ulpi_phy_destroy(ulpi, tusb->phy);
}

#define TI_VENDOR_ID 0x0451

static const struct ulpi_device_id tusb1210_ulpi_id[] = {
	{ TI_VENDOR_ID, 0x1507, },  /* TUSB1210 */
	{ TI_VENDOR_ID, 0x1508, },  /* TUSB1211 */
	{ },
};
MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id);

static struct ulpi_driver tusb1210_driver = {
	.id_table = tusb1210_ulpi_id,
	.probe = tusb1210_probe,
	.remove = tusb1210_remove,
	.driver = {
		.name = "tusb1210",
		.owner = THIS_MODULE,
	},
};

module_ulpi_driver(tusb1210_driver);

MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver");