All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ajay Gupta <ajayg@nvidia.com>
To: Peter Rosin <peda@axentia.se>,
	"wsa@the-dreams.de" <wsa@the-dreams.de>,
	"heikki.krogerus@linux.intel.com"
	<heikki.krogerus@linux.intel.com>
Cc: "linux-usb@vger.kernel.org" <linux-usb@vger.kernel.org>,
	"linux-i2c@vger.kernel.org" <linux-i2c@vger.kernel.org>
Subject: [v6,1/2] i2c: buses: add i2c bus driver for NVIDIA GPU
Date: Thu, 6 Sep 2018 17:39:01 +0000	[thread overview]
Message-ID: <cfc600089070467493d7ed325e2c8b65@bgmail101.nvidia.com> (raw)

Hi Peter,

> > Latest NVIDIA GPU card has USB Type-C interface. There is a Type-C
> > controller which can be accessed over I2C.
> >
> > This driver adds I2C bus driver to communicate with Type-C controller.
> > I2C client driver will be part of USB Type-C UCSI driver.
> >
> > Signed-off-by: Ajay Gupta <ajayg@nvidia.com>

> > +static void enable_i2c_bus(struct gpu_i2c_dev *i2cd)
> 
> Please prefix all your functions with gpu_, or nvidia_gpu_ or something like
> that (because gpu sounds like a something graphics, not that nvidia_gpu helps
> with that problem though...)
Ok, will prefix them.
 
> > +{
> > +	u32 val;
> > +
> > +	/* enable I2C */
> > +	val = readl(i2cd->regs + I2C_MST_HYBRID_PADCTL);
> > +	val |= I2C_MST_HYBRID_PADCTL_MODE_I2C |
> > +		I2C_MST_HYBRID_PADCTL_I2C_SCL_INPUT_RCV |
> > +		I2C_MST_HYBRID_PADCTL_I2C_SDA_INPUT_RCV;
> > +	writel(val, i2cd->regs + I2C_MST_HYBRID_PADCTL);
> > +
> > +	/* enable 100KHZ mode */
> > +	val = I2C_MST_I2C0_TIMING_SCL_PERIOD_100KHZ;
> > +	val |= (I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT_MAX
> > +	    << I2C_MST_I2C0_TIMING_TIMEOUT_CLK_CNT);
> > +	val |= I2C_MST_I2C0_TIMING_TIMEOUT_CHECK;
> > +	writel(val, i2cd->regs + I2C_MST_I2C0_TIMING); }
> > +
> > +static int i2c_check_status(struct gpu_i2c_dev *i2cd) {
> > +	unsigned long target = jiffies + msecs_to_jiffies(1000);
> > +	u32 val;
> > +
> > +	do {
> > +		val = readl(i2cd->regs + I2C_MST_CNTL);
> > +		if (!(val & I2C_MST_CNTL_CYCLE_TRIGGER))
> > +			break;
> > +		if ((val & I2C_MST_CNTL_STATUS) !=
> > +				I2C_MST_CNTL_STATUS_BUS_BUSY)
> > +			break;
> > +		usleep_range(1000, 2000);
> > +	} while (time_is_after_jiffies(target));
> > +	if (time_is_before_jiffies(target))
> > +		return -EIO;
> > +
> > +	val = readl(i2cd->regs + I2C_MST_CNTL);
> > +	switch (val & I2C_MST_CNTL_STATUS) {
> > +	case I2C_MST_CNTL_STATUS_OKAY:
> > +		return 0;
> > +	case I2C_MST_CNTL_STATUS_NO_ACK:
> > +		return -EIO;
> > +	case I2C_MST_CNTL_STATUS_TIMEOUT:
> > +		return -ETIME;
> > +	case I2C_MST_CNTL_STATUS_BUS_BUSY:
> > +		return -EBUSY;
> > +	default:
> > +		return 0;
> > +	}
> > +}
> > +
> > +static int i2c_read(struct gpu_i2c_dev *i2cd, u8 *data, u16 len) {
> > +	int status;
> > +	u32 val;
> > +
> > +	val = I2C_MST_CNTL_GEN_START | I2C_MST_CNTL_GEN_STOP |
> > +		I2C_MST_CNTL_CMD_READ | (len <<
> I2C_MST_CNTL_BURST_SIZE_SHIFT) |
> > +		I2C_MST_CNTL_CYCLE_TRIGGER |
> I2C_MST_CNTL_GEN_NACK;
> > +	val &= ~I2C_MST_CNTL_GEN_RAB;
> > +	writel(val, i2cd->regs + I2C_MST_CNTL);
> > +
> > +	status = i2c_check_status(i2cd);
> > +	if (status < 0)
> > +		return status;
> > +
> > +	val = readl(i2cd->regs + I2C_MST_DATA);
> > +	switch (len) {
> > +	case 1:
> > +		data[0] = val;
> > +		break;
> > +	case 2:
> > +		put_unaligned_be16(val, data);
> > +		break;
> > +	case 3:
> > +		put_unaligned_be16(val >> 8, data);
> > +		data[2] = val;
> > +		break;
> > +	case 4:
> > +		put_unaligned_be32(val, data);
> > +		break;
> > +	default:
> 
> This seems to behave rather strange for len > 4, and I do not see anything
> preventing that from happening.
Actually the check is in ccg_read() of the 
client driver at https://marc.info/?l=linux-usb&m=153618608301206&w=2 

> Am I missing something, or do you need to add a quirk for max_read_len?
I will add a check in this function as well.

> > +		break;
> > +	}
> > +	return status;
> > +}
> > +
> > +static int i2c_start(struct gpu_i2c_dev *i2cd, u16 addr) {
> > +	u32 val;
> > +
> > +	val = addr << I2C_MST_ADDR_DAB;
> > +	writel(val, i2cd->regs + I2C_MST_ADDR);
> > +
> > +	val = I2C_MST_CNTL_GEN_START | I2C_MST_CNTL_CMD_NONE |
> > +		I2C_MST_CNTL_GEN_NACK;
> > +	val &= ~(I2C_MST_CNTL_GEN_STOP | I2C_MST_CNTL_GEN_RAB);
> > +	writel(val, i2cd->regs + I2C_MST_CNTL);
> > +
> > +	return i2c_check_status(i2cd);
> > +}
> > +
> > +static int i2c_stop(struct gpu_i2c_dev *i2cd) {
> > +	u32 val;
> > +
> > +	val = I2C_MST_CNTL_GEN_STOP | I2C_MST_CNTL_CMD_NONE |
> > +		I2C_MST_CNTL_GEN_NACK;
> > +	val &= ~(I2C_MST_CNTL_GEN_START | I2C_MST_CNTL_GEN_RAB);
> > +	writel(val, i2cd->regs + I2C_MST_CNTL);
> > +
> > +	return i2c_check_status(i2cd);
> > +}
> > +
> > +static int i2c_write(struct gpu_i2c_dev *i2cd, u8 data) {
> > +	u32 val;
> > +
> > +	writel(data, i2cd->regs + I2C_MST_DATA);
> > +
> > +	val = I2C_MST_CNTL_CMD_WRITE | (1 <<
> I2C_MST_CNTL_BURST_SIZE_SHIFT) |
> > +		I2C_MST_CNTL_GEN_NACK;
> > +	val &= ~(I2C_MST_CNTL_GEN_START | I2C_MST_CNTL_GEN_STOP |
> > +		 I2C_MST_CNTL_GEN_RAB);
> > +	writel(val, i2cd->regs + I2C_MST_CNTL);
> > +
> > +	return i2c_check_status(i2cd);
> > +}
> > +
> > +static int gpu_i2c_master_xfer(struct i2c_adapter *adap,
> > +			       struct i2c_msg *msgs, int num) {
> > +	struct gpu_i2c_dev *i2cd = i2c_get_adapdata(adap);
> > +	struct device *dev = i2cd->dev;
> > +	int status;
> > +	int i, j;
> > +
> > +	mutex_lock(&i2cd->mutex);
> > +	for (i = 0; i < num; i++) {
> 
> I don't get how this loop works. You do not seem to always start with a start.
> E.g., if the first message is I2C_M_RD, i2c_read() is called before i2c_start(). Is
> that correct?
That’s correct and it is because i2_read() itself does the START and STOP.
 
> Also, if a message has I2C_M_STOP but not I2C_M_RD, the call to i2c_stop()
> happens before the call the i2c_write(). What's up with that?
Client driver sends I2C_M_STOP along with every write message.
 
> I would expect an i2c_start() before the loop or first in the loop, and a
> i2c_stop() after the loop.
I2c_read() function itself takes care of it.

> As is, the stop after the loop is only called on error.
To be exact, whenever there is error in i2c_write().

> In addition, I would expect messages that have I2C_M_STOP to trigger an
> i2c_stop() call *after* the message, even if the message is not last in the loop.
Yes, its like that for all write messages. 
 
> What am I missing?
> 
> > +		if (msgs[i].flags & I2C_M_RD) {
> > +			status = i2c_read(i2cd, msgs[i].buf, msgs[i].len);
> > +			if (status < 0) {
> > +				dev_err(dev, "i2c_read error %x", status);
> > +				goto unlock;
> > +			}
> > +			i2cd->do_start = true;
> > +		} else if (msgs[i].flags & I2C_M_STOP) {
> > +			status = i2c_stop(i2cd);
> > +			if (status < 0) {
> > +				dev_err(dev, "i2c_stop error %x", status);
> > +				goto unlock;
> > +			}
> > +			i2cd->do_start = true;
> > +		} else {
> > +			if (i2cd->do_start) {
> > +				status = i2c_start(i2cd, msgs[i].addr);
> > +				if (status < 0) {
> > +					dev_err(dev, "i2c_start error %x",
> > +						status);
> > +					goto unlock;
> > +				}
> > +				status = i2c_write(i2cd, msgs[i].addr << 1);
> > +				if (status < 0) {
> > +					dev_err(dev, "i2c_write error %x",
> > +						status);
> > +					goto stop;
> > +				}
> > +				i2cd->do_start = false;
> > +			}
> > +			for (j = 0; j < msgs[i].len; j++) {
> > +				status = i2c_write(i2cd, *(msgs[i].buf + j));
> > +				if (status < 0) {
> > +					dev_err(dev, "i2c_write error %x",
> > +						status);
> > +					goto stop;
> > +				}
> > +			}
> > +		}
> > +	}
> > +	status = i;
> > +	goto unlock;
<here>

> > +stop:
> > +	status = i2c_stop(i2cd);
> > +	if (status < 0)
> > +		dev_err(dev, "i2c_stop error %x", status);
> 
> Hmm, every time you call one of i2c_start, i2c_read, i2c_write or i2c_stop you
> check for error and issue a generic dev_err message. I think you should move
> these error messages into the functions instead or repeating them for every
> use.
Ok, will fix.
 
> > +unlock:
> > +	mutex_unlock(&i2cd->mutex);
> > +	return status;
> 
> Here you return status, which seems to be zero when everything is fine.
> That is incorrect for sure; you should return num on success.
When everything is fine then status is written with number of messages
"i". Please see above.

Thanks
Ajay

--
nvpublic
--
 
> Cheers,
> Peter
> 
> > +}
> > +
> > +static u32 gpu_i2c_functionality(struct i2c_adapter *adap) {
> > +	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; }
> > +
> > +static const struct i2c_algorithm gpu_i2c_algorithm = {
> > +	.master_xfer	= gpu_i2c_master_xfer,
> > +	.functionality	= gpu_i2c_functionality,
> > +};
> > +
> > +/*
> > + * This driver is for Nvidia GPU cards with USB Type-C interface.
> > + * We want to identify the cards using vendor ID and class code only
> > + * to avoid dependency of adding product id for any new card which
> > + * requires this driver.
> > + * Currently there is no class code defined for UCSI device over PCI
> > + * so using UNKNOWN class for now and it will be updated when UCSI
> > + * over PCI gets a class code.
> > + * There is no other NVIDIA cards with UNKNOWN class code. Even if
> > +the
> > + * driver gets loaded for an undesired card then eventually
> > +i2c_read()
> > + * (initiated from UCSI i2c_client) will timeout or UCSI commands
> > +will
> > + * timeout.
> > + */
> > +#define PCI_CLASS_SERIAL_UNKNOWN	0x0c80
> > +static const struct pci_device_id gpu_i2c_ids[] = {
> > +	{ PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
> > +		PCI_CLASS_SERIAL_UNKNOWN << 8, 0xffffff00},
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(pci, gpu_i2c_ids);
> > +
> > +static int gpu_populate_client(struct gpu_i2c_dev *i2cd, int irq) {
> > +	static struct i2c_board_info gpu_ccgx_ucsi = {
> > +		I2C_BOARD_INFO("ccgx-ucsi", 0x8),
> > +	};
> > +
> > +	gpu_ccgx_ucsi.irq = irq;
> > +	i2cd->client = i2c_new_device(&i2cd->adapter, &gpu_ccgx_ucsi);
> > +	if (!i2cd->client) {
> > +		dev_err(i2cd->dev, "i2c_new_device failed\n");
> > +		return -ENODEV;
> > +	}
> > +	return 0;
> > +}
> > +
> > +static int gpu_i2c_probe(struct pci_dev *pdev, const struct
> > +pci_device_id *id) {
> > +	struct gpu_i2c_dev *i2cd;
> > +	int status;
> > +
> > +	i2cd = devm_kzalloc(&pdev->dev, sizeof(struct gpu_i2c_dev),
> GFP_KERNEL);
> > +	if (!i2cd)
> > +		return -ENOMEM;
> > +
> > +	i2cd->dev = &pdev->dev;
> > +	dev_set_drvdata(&pdev->dev, i2cd);
> > +
> > +	status = pcim_enable_device(pdev);
> > +	if (status < 0) {
> > +		dev_err(&pdev->dev, "pcim_enable_device failed %d\n",
> status);
> > +		return status;
> > +	}
> > +
> > +	pci_set_master(pdev);
> > +
> > +	i2cd->regs = pcim_iomap(pdev, 0, 0);
> > +	if (!i2cd->regs) {
> > +		dev_err(&pdev->dev, "pcim_iomap failed\n");
> > +		return -ENOMEM;
> > +	}
> > +
> > +	status = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
> > +	if (status < 0) {
> > +		dev_err(&pdev->dev, "pci_alloc_irq_vectors err %d\n",
> status);
> > +		return status;
> > +	}
> > +
> > +	i2cd->do_start = true;
> > +	mutex_init(&i2cd->mutex);
> > +	enable_i2c_bus(i2cd);
> > +
> > +	i2c_set_adapdata(&i2cd->adapter, i2cd);
> > +	i2cd->adapter.owner = THIS_MODULE;
> > +	strlcpy(i2cd->adapter.name, "NVIDIA GPU I2C adapter",
> > +		sizeof(i2cd->adapter.name));
> > +	i2cd->adapter.algo = &gpu_i2c_algorithm;
> > +	i2cd->adapter.dev.parent = &pdev->dev;
> > +	status = i2c_add_adapter(&i2cd->adapter);
> > +	if (status < 0) {
> > +		dev_err(&pdev->dev, "i2c_add_adapter failed %d\n", status);
> > +		goto free_irq_vectors;
> > +	}
> > +
> > +	status = gpu_populate_client(i2cd, pdev->irq);
> > +	if (status < 0) {
> > +		dev_err(&pdev->dev, "gpu_populate_client failed %d\n",
> status);
> > +		goto del_adapter;
> > +	}
> > +
> > +	return 0;
> > +
> > +del_adapter:
> > +	i2c_del_adapter(&i2cd->adapter);
> > +free_irq_vectors:
> > +	pci_free_irq_vectors(pdev);
> > +	return status;
> > +}
> > +
> > +static void gpu_i2c_remove(struct pci_dev *pdev) {
> > +	struct gpu_i2c_dev *i2cd = dev_get_drvdata(&pdev->dev);
> > +
> > +	i2c_del_adapter(&i2cd->adapter);
> > +	pci_free_irq_vectors(pdev);
> > +}
> > +
> > +static int gpu_i2c_resume(struct device *dev) {
> > +	struct gpu_i2c_dev *i2cd = dev_get_drvdata(dev);
> > +
> > +	enable_i2c_bus(i2cd);
> > +	return 0;
> > +}
> > +
> > +static int gpu_i2c_idle(struct device *dev) {
> > +	struct gpu_i2c_dev *i2cd = dev_get_drvdata(dev);
> > +
> > +	if (!mutex_trylock(&i2cd->mutex)) {
> > +		dev_info(dev, "-EBUSY\n");
> > +		return -EBUSY;
> > +	}
> > +	mutex_unlock(&i2cd->mutex);
> > +
> > +	return 0;
> > +}
> > +
> > +UNIVERSAL_DEV_PM_OPS(gpu_i2c_driver_pm, NULL, gpu_i2c_resume,
> > +gpu_i2c_idle);
> > +
> > +static struct pci_driver gpu_i2c_driver = {
> > +	.name		= "nvidia-gpu",
> > +	.id_table	= gpu_i2c_ids,
> > +	.probe		= gpu_i2c_probe,
> > +	.remove		= gpu_i2c_remove,
> > +	.driver		= {
> > +		.pm	= &gpu_i2c_driver_pm,
> > +	},
> > +};
> > +
> > +module_pci_driver(gpu_i2c_driver);
> > +
> > +MODULE_AUTHOR("Ajay Gupta <ajayg@nvidia.com>");
> > +MODULE_DESCRIPTION("Nvidia GPU I2C controller Driver");
> > +MODULE_LICENSE("GPL v2");
> >

             reply	other threads:[~2018-09-06 17:39 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-09-06 17:39 Ajay Gupta [this message]
  -- strict thread matches above, loose matches on Subject: below --
2018-09-06 19:40 [v6,1/2] i2c: buses: add i2c bus driver for NVIDIA GPU Ajay Gupta
2018-09-06 18:15 Peter Rosin
2018-09-06 14:43 Peter Rosin
2018-09-06 11:26 Heikki Krogerus
2018-09-06  6:17 Andy Shevchenko
2018-09-05 22:20 Ajay Gupta

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cfc600089070467493d7ed325e2c8b65@bgmail101.nvidia.com \
    --to=ajayg@nvidia.com \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=peda@axentia.se \
    --cc=wsa@the-dreams.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.