* [PATCH/RESEND v4 1/2] spi_topcliff_pch: support new device ML7213 IOH
[not found] <y>
@ 2009-01-01 23:29 ` y
2011-06-07 5:54 ` Tomoya MORINAGA
2009-01-01 23:29 ` [PATCH/RESEND 2/2] spi_topcliff_pch: DMA support y
` (159 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-01-01 23:29 UTC (permalink / raw)
To: David Brownell, Grant Likely, spi-devel-general, linux-kernel
Cc: qi.wang, yong.y.wang, joel.clark, kok.howg.ewe, toshiharu-linux,
Tomoya MORINAGA
From: Tomoya MORINAGA <tomoya-linux@dsn.okisemi.com>
***Modify Grant's comments.
- Delete unrelated whitespace
- Prevent device driver from accessing platform data
- Add __devinit and __devexit
- Save pdev->dev to pd_dev->dev.parent
- Have own suspend/resume processing in platform_driver.
- Care returned value in pch_spi_init
- Change unregister order
Support ML7213 device of OKI SEMICONDUCTOR.
ML7213 is companion chip of Intel Atom E6xx series for IVI(In-Vehicle Infotainment).
ML7213 is compatible for Intel EG20T PCH.
Signed-off-by: Tomoya MORINAGA <tomoya-linux@dsn.okisemi.com>
---
drivers/spi/Kconfig | 5 +-
drivers/spi/spi_topcliff_pch.c | 599 +++++++++++++++++++++-------------------
2 files changed, 317 insertions(+), 287 deletions(-)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index fc14b8d..ed6134b 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -380,12 +380,15 @@ config SPI_TI_SSP
module will be called ti-ssp-spi.
config SPI_TOPCLIFF_PCH
- tristate "Topcliff PCH SPI Controller"
+ tristate "Intel EG20T PCH/OKI SEMICONDUCTOR ML7213 IOH SPI controller"
depends on PCI
help
SPI driver for the Topcliff PCH (Platform Controller Hub) SPI bus
used in some x86 embedded processors.
+ This driver also supports the ML7213, a companion chip for the
+ Atom E6xx series and compatible with the Intel EG20T PCH.
+
config SPI_TXX9
tristate "Toshiba TXx9 SPI controller"
depends on GENERIC_GPIO && CPU_TX49XX
diff --git a/drivers/spi/spi_topcliff_pch.c b/drivers/spi/spi_topcliff_pch.c
index 79e48d4..88bd472 100644
--- a/drivers/spi/spi_topcliff_pch.c
+++ b/drivers/spi/spi_topcliff_pch.c
@@ -26,6 +26,7 @@
#include <linux/spi/spidev.h>
#include <linux/module.h>
#include <linux/device.h>
+#include <linux/platform_device.h>
/* Register offsets */
#define PCH_SPCR 0x00 /* SPI control register */
@@ -35,6 +36,7 @@
#define PCH_SPDRR 0x10 /* SPI read data register */
#define PCH_SSNXCR 0x18 /* SSN Expand Control Register */
#define PCH_SRST 0x1C /* SPI reset register */
+#define PCH_SPI_ADDRESS_SIZE 0x20
#define PCH_SPSR_TFD 0x000007C0
#define PCH_SPSR_RFD 0x0000F800
@@ -75,7 +77,8 @@
#define SPSR_FI_BIT (1 << 2)
#define SPBRR_SIZE_BIT (1 << 10)
-#define PCH_ALL (SPCR_TFIE_BIT|SPCR_RFIE_BIT|SPCR_FIE_BIT|SPCR_ORIE_BIT|SPCR_MDFIE_BIT)
+#define PCH_ALL (SPCR_TFIE_BIT|SPCR_RFIE_BIT|SPCR_FIE_BIT|\
+ SPCR_ORIE_BIT|SPCR_MDFIE_BIT)
#define SPCR_RFIC_FIELD 20
#define SPCR_TFIC_FIELD 16
@@ -88,6 +91,16 @@
#define PCH_CLOCK_HZ 50000000
#define PCH_MAX_SPBR 1023
+/* Definition for ML7213 by OKI SEMICONDUCTOR */
+#define PCI_VENDOR_ID_ROHM 0x10DB
+#define PCI_DEVICE_ID_ML7213_SPI 0x802c
+
+/*
+ * Set the number of SPI instance max
+ * Intel EG20T PCH : 1ch
+ * OKI SEMICONDUCTOR ML7213 IOH : 2ch
+*/
+#define PCH_SPI_MAX_DEV 2
/**
* struct pch_spi_data - Holds the SPI channel specific details
@@ -121,6 +134,9 @@
* @cur_trans: The current transfer that this SPI driver is
* handling
* @board_dat: Reference to the SPI device data structure
+ * @plat_dev: platform_device structure
+ * @ch: SPI channel number
+ * @irq_reg_sts: Status of IRQ registration
*/
struct pch_spi_data {
void __iomem *io_remap_addr;
@@ -144,27 +160,33 @@ struct pch_spi_data {
struct spi_message *current_msg;
struct spi_transfer *cur_trans;
struct pch_spi_board_data *board_dat;
+ struct platform_device *plat_dev;
+ int ch;
+ u8 irq_reg_sts;
};
/**
* struct pch_spi_board_data - Holds the SPI device specific details
* @pdev: Pointer to the PCI device
- * @irq_reg_sts: Status of IRQ registration
- * @pci_req_sts: Status of pci_request_regions
* @suspend_sts: Status of suspend
- * @data: Pointer to SPI channel data structure
+ * @num: The number of SPI device instance
*/
struct pch_spi_board_data {
struct pci_dev *pdev;
- u8 irq_reg_sts;
- u8 pci_req_sts;
u8 suspend_sts;
- struct pch_spi_data *data;
+ int num;
+};
+
+struct pch_pd_dev_save {
+ int num;
+ struct platform_device *pd_save[PCH_SPI_MAX_DEV];
+ struct pch_spi_board_data *board_dat;
};
static struct pci_device_id pch_spi_pcidev_id[] = {
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_GE_SPI)},
- {0,}
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_GE_SPI), 1, },
+ { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7213_SPI), 2, },
+ { }
};
/**
@@ -283,11 +305,11 @@ static void pch_spi_handler_sub(struct pch_spi_data *data, u32 reg_spsr_val,
static irqreturn_t pch_spi_handler(int irq, void *dev_id)
{
u32 reg_spsr_val;
- struct pch_spi_data *data;
void __iomem *spsr;
void __iomem *io_remap_addr;
irqreturn_t ret = IRQ_NONE;
- struct pch_spi_board_data *board_dat = dev_id;
+ struct pch_spi_data *data = dev_id;
+ struct pch_spi_board_data *board_dat = data->board_dat;
if (board_dat->suspend_sts) {
dev_dbg(&board_dat->pdev->dev,
@@ -295,10 +317,8 @@ static irqreturn_t pch_spi_handler(int irq, void *dev_id)
return IRQ_NONE;
}
- data = board_dat->data;
io_remap_addr = data->io_remap_addr;
spsr = io_remap_addr + PCH_SPSR;
-
reg_spsr_val = ioread32(spsr);
/* Check if the interrupt is for SPI device */
@@ -412,7 +432,6 @@ static int pch_spi_setup(struct spi_device *pspi)
static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
{
-
struct spi_transfer *transfer;
struct pch_spi_data *data = spi_master_get_devdata(pspi->master);
int retval;
@@ -547,7 +566,8 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
/* set bits per word if needed */
if (data->cur_trans->bits_per_word &&
- (data->current_msg->spi->bits_per_word != data->cur_trans->bits_per_word)) {
+ (data->current_msg->spi->bits_per_word !=\
+ data->cur_trans->bits_per_word)) {
dev_dbg(&data->master->dev, "%s:set bits per word\n", __func__);
pch_spi_set_bits_per_word(data->master,
data->cur_trans->bits_per_word);
@@ -621,7 +641,6 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
data->transfer_active = true;
}
-
static void pch_spi_nomore_transfer(struct pch_spi_data *data,
struct spi_message *pmsg)
{
@@ -697,7 +716,8 @@ static void pch_spi_set_ir(struct pch_spi_data *data)
"%s:invoking pch_spi_set_enable to enable SPI\n", __func__);
/* SPI set enable */
- pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, SPCR_SPE_BIT, 0);
+ pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, SPCR_SPE_BIT,
+ 0);
/* Wait until the transfer completes; go to sleep after
initiating the transfer. */
@@ -742,7 +762,6 @@ static void pch_spi_copy_rx_data(struct pch_spi_data *data, int bpw)
}
}
-
static void pch_spi_process_messages(struct work_struct *pwork)
{
struct spi_message *pmsg;
@@ -868,117 +887,49 @@ static void pch_spi_process_messages(struct work_struct *pwork)
} while (data->cur_trans != NULL);
}
-static void pch_spi_free_resources(struct pch_spi_board_data *board_dat)
+static void pch_spi_free_resources(struct pch_spi_board_data *board_dat,
+ struct pch_spi_data *data)
{
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
/* free workqueue */
- if (board_dat->data->wk != NULL) {
- destroy_workqueue(board_dat->data->wk);
- board_dat->data->wk = NULL;
+ if (data->wk != NULL) {
+ destroy_workqueue(data->wk);
+ data->wk = NULL;
dev_dbg(&board_dat->pdev->dev,
"%s destroy_workqueue invoked successfully\n",
__func__);
}
-
- /* disable interrupts & free IRQ */
- if (board_dat->irq_reg_sts) {
- /* disable interrupts */
- pch_spi_setclr_reg(board_dat->data->master, PCH_SPCR, 0,
- PCH_ALL);
-
- /* free IRQ */
- free_irq(board_dat->pdev->irq, board_dat);
-
- dev_dbg(&board_dat->pdev->dev,
- "%s free_irq invoked successfully\n", __func__);
-
- board_dat->irq_reg_sts = false;
- }
-
- /* unmap PCI base address */
- if (board_dat->data->io_remap_addr != 0) {
- pci_iounmap(board_dat->pdev, board_dat->data->io_remap_addr);
-
- board_dat->data->io_remap_addr = 0;
-
- dev_dbg(&board_dat->pdev->dev,
- "%s pci_iounmap invoked successfully\n", __func__);
- }
-
- /* release PCI region */
- if (board_dat->pci_req_sts) {
- pci_release_regions(board_dat->pdev);
- dev_dbg(&board_dat->pdev->dev,
- "%s pci_release_regions invoked successfully\n",
- __func__);
- board_dat->pci_req_sts = false;
- }
}
-static int pch_spi_get_resources(struct pch_spi_board_data *board_dat)
+static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
+ struct pch_spi_data *data)
{
- void __iomem *io_remap_addr;
- int retval;
+ int retval = 0;
+
dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
/* create workqueue */
- board_dat->data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
- if (!board_dat->data->wk) {
+ data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
+ if (!data->wk) {
dev_err(&board_dat->pdev->dev,
"%s create_singlet hread_workqueue failed\n", __func__);
retval = -EBUSY;
goto err_return;
}
- dev_dbg(&board_dat->pdev->dev,
- "%s create_singlethread_workqueue success\n", __func__);
-
- retval = pci_request_regions(board_dat->pdev, KBUILD_MODNAME);
- if (retval != 0) {
- dev_err(&board_dat->pdev->dev,
- "%s request_region failed\n", __func__);
- goto err_return;
- }
-
- board_dat->pci_req_sts = true;
-
- io_remap_addr = pci_iomap(board_dat->pdev, 1, 0);
- if (io_remap_addr == 0) {
- dev_err(&board_dat->pdev->dev,
- "%s pci_iomap failed\n", __func__);
- retval = -ENOMEM;
- goto err_return;
- }
-
- /* calculate base address for all channels */
- board_dat->data->io_remap_addr = io_remap_addr;
-
/* reset PCH SPI h/w */
- pch_spi_reset(board_dat->data->master);
+ pch_spi_reset(data->master);
dev_dbg(&board_dat->pdev->dev,
"%s pch_spi_reset invoked successfully\n", __func__);
- /* register IRQ */
- retval = request_irq(board_dat->pdev->irq, pch_spi_handler,
- IRQF_SHARED, KBUILD_MODNAME, board_dat);
- if (retval != 0) {
- dev_err(&board_dat->pdev->dev,
- "%s request_irq failed\n", __func__);
- goto err_return;
- }
-
- dev_dbg(&board_dat->pdev->dev, "%s request_irq returned=%d\n",
- __func__, retval);
-
- board_dat->irq_reg_sts = true;
dev_dbg(&board_dat->pdev->dev, "%s data->irq_reg_sts=true\n", __func__);
err_return:
if (retval != 0) {
dev_err(&board_dat->pdev->dev,
"%s FAIL:invoking pch_spi_free_resources\n", __func__);
- pch_spi_free_resources(board_dat);
+ pch_spi_free_resources(board_dat, data);
}
dev_dbg(&board_dat->pdev->dev, "%s Return=%d\n", __func__, retval);
@@ -986,255 +937,343 @@ err_return:
return retval;
}
-static int pch_spi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
{
-
+ int ret;
struct spi_master *master;
+ struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
+ struct pch_spi_data *data;
- struct pch_spi_board_data *board_dat;
- int retval;
-
- dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
-
- /* allocate memory for private data */
- board_dat = kzalloc(sizeof(struct pch_spi_board_data), GFP_KERNEL);
- if (board_dat == NULL) {
- dev_err(&pdev->dev,
- " %s memory allocation for private data failed\n",
- __func__);
- retval = -ENOMEM;
- goto err_kmalloc;
- }
-
- dev_dbg(&pdev->dev,
- "%s memory allocation for private data success\n", __func__);
-
- /* enable PCI device */
- retval = pci_enable_device(pdev);
- if (retval != 0) {
- dev_err(&pdev->dev, "%s pci_enable_device FAILED\n", __func__);
-
- goto err_pci_en_device;
+ master = spi_alloc_master(&board_dat->pdev->dev,
+ sizeof(struct pch_spi_data));
+ if (!master) {
+ dev_err(&plat_dev->dev, "spi_alloc_master[%d] failed.\n",
+ plat_dev->id);
+ return -ENOMEM;
}
- dev_dbg(&pdev->dev, "%s pci_enable_device returned=%d\n",
- __func__, retval);
+ data = spi_master_get_devdata(master);
+ data->master = master;
- board_dat->pdev = pdev;
+ platform_set_drvdata(plat_dev, data);
- /* alllocate memory for SPI master */
- master = spi_alloc_master(&pdev->dev, sizeof(struct pch_spi_data));
- if (master == NULL) {
- retval = -ENOMEM;
- dev_err(&pdev->dev, "%s Fail.\n", __func__);
- goto err_spi_alloc_master;
+ /* baseaddress + 0x20(offset) */
+ data->io_remap_addr = pci_iomap(board_dat->pdev, 1, 0) +
+ 0x20 * plat_dev->id;
+ if (!data->io_remap_addr) {
+ dev_err(&plat_dev->dev, "%s pci_iomap failed\n", __func__);
+ ret = -ENOMEM;
+ goto err_pci_iomap;
}
- dev_dbg(&pdev->dev,
- "%s spi_alloc_master returned non NULL\n", __func__);
+ dev_dbg(&plat_dev->dev, "[ch%d] remap_addr=%p\n",
+ plat_dev->id, data->io_remap_addr);
/* initialize members of SPI master */
- master->bus_num = -1;
+ master->bus_num = plat_dev->id;
master->num_chipselect = PCH_MAX_CS;
master->setup = pch_spi_setup;
master->transfer = pch_spi_transfer;
- dev_dbg(&pdev->dev,
- "%s transfer member of SPI master initialized\n", __func__);
- board_dat->data = spi_master_get_devdata(master);
+ data->board_dat = board_dat;
+ data->plat_dev = plat_dev;
+ data->n_curnt_chip = 255;
+ data->status = STATUS_RUNNING;
+ data->ch = plat_dev->id;
- board_dat->data->master = master;
- board_dat->data->n_curnt_chip = 255;
- board_dat->data->board_dat = board_dat;
- board_dat->data->status = STATUS_RUNNING;
+ INIT_LIST_HEAD(&data->queue);
+ spin_lock_init(&data->lock);
+ INIT_WORK(&data->work, pch_spi_process_messages);
+ init_waitqueue_head(&data->wait);
- INIT_LIST_HEAD(&board_dat->data->queue);
- spin_lock_init(&board_dat->data->lock);
- INIT_WORK(&board_dat->data->work, pch_spi_process_messages);
- init_waitqueue_head(&board_dat->data->wait);
-
- /* allocate resources for PCH SPI */
- retval = pch_spi_get_resources(board_dat);
- if (retval) {
- dev_err(&pdev->dev, "%s fail(retval=%d)\n", __func__, retval);
+ ret = pch_spi_get_resources(board_dat, data);
+ if (ret) {
+ dev_err(&plat_dev->dev, "%s fail(retval=%d)\n", __func__, ret);
goto err_spi_get_resources;
}
- dev_dbg(&pdev->dev, "%s pch_spi_get_resources returned=%d\n",
- __func__, retval);
-
- /* save private data in dev */
- pci_set_drvdata(pdev, board_dat);
- dev_dbg(&pdev->dev, "%s invoked pci_set_drvdata\n", __func__);
+ ret = request_irq(board_dat->pdev->irq, pch_spi_handler,
+ IRQF_SHARED, KBUILD_MODNAME, data);
+ if (ret) {
+ dev_err(&plat_dev->dev,
+ "%s request_irq failed\n", __func__);
+ goto err_request_irq;
+ }
+ data->irq_reg_sts = true;
- /* set master mode */
pch_spi_set_master_mode(master);
- dev_dbg(&pdev->dev,
- "%s invoked pch_spi_set_master_mode\n", __func__);
- /* Register the controller with the SPI core. */
- retval = spi_register_master(master);
- if (retval != 0) {
- dev_err(&pdev->dev,
+ ret = spi_register_master(master);
+ if (ret != 0) {
+ dev_err(&plat_dev->dev,
"%s spi_register_master FAILED\n", __func__);
- goto err_spi_reg_master;
+ goto err_spi_register_master;
}
- dev_dbg(&pdev->dev, "%s spi_register_master returned=%d\n",
- __func__, retval);
-
-
return 0;
-err_spi_reg_master:
- spi_unregister_master(master);
+err_spi_register_master:
+ free_irq(board_dat->pdev->irq, board_dat);
+err_request_irq:
+ pch_spi_free_resources(board_dat, data);
err_spi_get_resources:
-err_spi_alloc_master:
+ pci_iounmap(board_dat->pdev, data->io_remap_addr);
+err_pci_iomap:
spi_master_put(master);
- pci_disable_device(pdev);
-err_pci_en_device:
- kfree(board_dat);
-err_kmalloc:
- return retval;
+
+ return ret;
}
-static void pch_spi_remove(struct pci_dev *pdev)
+static int __devexit pch_spi_pd_remove(struct platform_device *plat_dev)
{
- struct pch_spi_board_data *board_dat = pci_get_drvdata(pdev);
+ struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
+ struct pch_spi_data *data = platform_get_drvdata(plat_dev);
int count;
- dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
-
- if (!board_dat) {
- dev_err(&pdev->dev,
- "%s pci_get_drvdata returned NULL\n", __func__);
- return;
- }
-
+ dev_dbg(&plat_dev->dev, "%s:[ch%d] irq=%d\n",
+ __func__, plat_dev->id, board_dat->pdev->irq);
/* check for any pending messages; no action is taken if the queue
* is still full; but at least we tried. Unload anyway */
count = 500;
- spin_lock(&board_dat->data->lock);
- board_dat->data->status = STATUS_EXITING;
- while ((list_empty(&board_dat->data->queue) == 0) && --count) {
+ spin_lock(&data->lock);
+ data->status = STATUS_EXITING;
+ while ((list_empty(&data->queue) == 0) && --count) {
dev_dbg(&board_dat->pdev->dev, "%s :queue not empty\n",
__func__);
- spin_unlock(&board_dat->data->lock);
+ spin_unlock(&data->lock);
msleep(PCH_SLEEP_TIME);
- spin_lock(&board_dat->data->lock);
+ spin_lock(&data->lock);
}
- spin_unlock(&board_dat->data->lock);
-
- /* Free resources allocated for PCH SPI */
- pch_spi_free_resources(board_dat);
-
- spi_unregister_master(board_dat->data->master);
-
- /* free memory for private data */
- kfree(board_dat);
+ spin_unlock(&data->lock);
- pci_set_drvdata(pdev, NULL);
+ pch_spi_free_resources(board_dat, data);
+ /* disable interrupts & free IRQ */
+ if (data->irq_reg_sts) {
+ /* disable interrupts */
+ pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
+ data->irq_reg_sts = false;
+ free_irq(board_dat->pdev->irq, data);
+ }
- /* disable PCI device */
- pci_disable_device(pdev);
+ pci_iounmap(board_dat->pdev, data->io_remap_addr);
+ spi_unregister_master(data->master);
+ spi_master_put(data->master);
+ platform_set_drvdata(plat_dev, NULL);
- dev_dbg(&pdev->dev, "%s invoked pci_disable_device\n", __func__);
+ return 0;
}
-
#ifdef CONFIG_PM
-static int pch_spi_suspend(struct pci_dev *pdev, pm_message_t state)
+static int pch_spi_pd_suspend(struct platform_device *pd_dev,
+ pm_message_t state)
{
u8 count;
- int retval;
-
- struct pch_spi_board_data *board_dat = pci_get_drvdata(pdev);
+ struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev);
+ struct pch_spi_data *data = platform_get_drvdata(pd_dev);
- dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
+ dev_dbg(&pd_dev->dev, "%s ENTRY\n", __func__);
if (!board_dat) {
- dev_err(&pdev->dev,
+ dev_err(&pd_dev->dev,
"%s pci_get_drvdata returned NULL\n", __func__);
return -EFAULT;
}
- retval = 0;
- board_dat->suspend_sts = true;
-
/* check if the current message is processed:
Only after thats done the transfer will be suspended */
count = 255;
- while ((--count) > 0) {
- if (!(board_dat->data->bcurrent_msg_processing)) {
- dev_dbg(&pdev->dev, "%s board_dat->data->bCurrent_"
- "msg_processing = false\n", __func__);
+ while ((--count) > 0)
+ if (!(data->bcurrent_msg_processing)) {
break;
- } else {
- dev_dbg(&pdev->dev, "%s board_dat->data->bCurrent_msg_"
- "processing = true\n", __func__);
- }
msleep(PCH_SLEEP_TIME);
}
/* Free IRQ */
- if (board_dat->irq_reg_sts) {
+ if (data->irq_reg_sts) {
/* disable all interrupts */
- pch_spi_setclr_reg(board_dat->data->master, PCH_SPCR, 0,
- PCH_ALL);
- pch_spi_reset(board_dat->data->master);
-
- free_irq(board_dat->pdev->irq, board_dat);
+ pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
+ pch_spi_reset(data->master);
+ free_irq(board_dat->pdev->irq, data);
- board_dat->irq_reg_sts = false;
- dev_dbg(&pdev->dev,
+ data->irq_reg_sts = false;
+ dev_dbg(&pd_dev->dev,
"%s free_irq invoked successfully.\n", __func__);
}
+ return 0;
+}
+
+static int pch_spi_pd_resume(struct platform_device *pd_dev)
+{
+ struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev);
+ struct pch_spi_data *data = platform_get_drvdata(pd_dev);
+ int retval;
+
+ if (!board_dat) {
+ dev_err(&pd_dev->dev,
+ "%s pci_get_drvdata returned NULL\n", __func__);
+ return -EFAULT;
+ }
+
+ if (!data->irq_reg_sts) {
+ /* register IRQ */
+ retval = request_irq(board_dat->pdev->irq, pch_spi_handler,
+ IRQF_SHARED, KBUILD_MODNAME, data);
+ if (retval < 0) {
+ dev_err(&pd_dev->dev,
+ "%s request_irq failed\n", __func__);
+ return retval;
+ }
+
+ /* reset PCH SPI h/w */
+ pch_spi_reset(data->master);
+ pch_spi_set_master_mode(data->master);
+ data->irq_reg_sts = true;
+ }
+ return 0;
+}
+#else
+#define pch_spi_pd_suspend NULL
+#define pch_spi_pd_resume NULL
+#endif
+
+static struct platform_driver pch_spi_pd_driver = {
+ .driver = {
+ .name = "pch-spi",
+ .owner = THIS_MODULE,
+ },
+ .probe = pch_spi_pd_probe,
+ .remove = __devexit_p(pch_spi_pd_remove),
+ .suspend = pch_spi_pd_suspend,
+ .resume = pch_spi_pd_resume
+};
+
+static int __devinit pch_spi_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct pch_spi_board_data *board_dat;
+ struct platform_device *pd_dev = NULL;
+ int retval;
+ int i;
+ struct pch_pd_dev_save *pd_dev_save;
+
+ pd_dev_save = kzalloc(sizeof(struct pch_pd_dev_save), GFP_KERNEL);
+ if (!pd_dev_save) {
+ dev_err(&pdev->dev, "%s Can't allocate pd_dev_sav\n", __func__);
+ return -ENOMEM;
+ }
+
+ board_dat = kzalloc(sizeof(struct pch_spi_board_data), GFP_KERNEL);
+ if (!board_dat) {
+ dev_err(&pdev->dev, "%s Can't allocate board_dat\n", __func__);
+ retval = -ENOMEM;
+ goto err_no_mem;
+ }
+
+ retval = pci_request_regions(pdev, KBUILD_MODNAME);
+ if (retval) {
+ dev_err(&pdev->dev, "%s request_region failed\n", __func__);
+ goto pci_request_regions;
+ }
+
+ board_dat->pdev = pdev;
+ board_dat->num = id->driver_data;
+ pd_dev_save->num = id->driver_data;
+ pd_dev_save->board_dat = board_dat;
+
+ retval = pci_enable_device(pdev);
+ if (retval) {
+ dev_err(&pdev->dev, "%s pci_enable_device failed\n", __func__);
+ goto pci_enable_device;
+ }
+
+ for (i = 0; i < board_dat->num; i++) {
+ pd_dev = platform_device_alloc("pch-spi", i);
+ if (!pd_dev) {
+ dev_err(&pdev->dev, "platform_device_alloc failed\n");
+ goto err_platform_device;
+ }
+ pd_dev_save->pd_save[i] = pd_dev;
+ pd_dev->dev.parent = &pdev->dev;
+
+ retval = platform_device_add_data(pd_dev, board_dat,
+ sizeof(*board_dat));
+ if (retval) {
+ dev_err(&pdev->dev,
+ "platform_device_add_data failed\n");
+ platform_device_put(pd_dev);
+ goto err_platform_device;
+ }
+
+ retval = platform_device_add(pd_dev);
+ if (retval) {
+ dev_err(&pdev->dev, "platform_device_add failed\n");
+ platform_device_put(pd_dev);
+ goto err_platform_device;
+ }
+ }
+
+ pci_set_drvdata(pdev, pd_dev_save);
+
+ return 0;
+
+err_platform_device:
+ pci_disable_device(pdev);
+pci_enable_device:
+ pci_release_regions(pdev);
+pci_request_regions:
+ kfree(board_dat);
+err_no_mem:
+ kfree(pd_dev_save);
+
+ return retval;
+}
+
+static void __devexit pch_spi_remove(struct pci_dev *pdev)
+{
+ int i;
+ struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s ENTRY:pdev=%p\n", __func__, pdev);
+
+ for (i = 0; i < pd_dev_save->num; i++)
+ platform_device_unregister(pd_dev_save->pd_save[i]);
+
+ pci_disable_device(pdev);
+ pci_release_regions(pdev);
+ kfree(pd_dev_save->board_dat);
+ kfree(pd_dev_save);
+}
+
+#ifdef CONFIG_PM
+static int pch_spi_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+ int retval;
+ struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
+
+ pd_dev_save->board_dat->suspend_sts = true;
+
/* save config space */
retval = pci_save_state(pdev);
-
if (retval == 0) {
- dev_dbg(&pdev->dev, "%s pci_save_state returned=%d\n",
- __func__, retval);
- /* disable PM notifications */
pci_enable_wake(pdev, PCI_D3hot, 0);
- dev_dbg(&pdev->dev,
- "%s pci_enable_wake invoked successfully\n", __func__);
- /* disable PCI device */
pci_disable_device(pdev);
- dev_dbg(&pdev->dev,
- "%s pci_disable_device invoked successfully\n",
- __func__);
- /* move device to D3hot state */
pci_set_power_state(pdev, PCI_D3hot);
- dev_dbg(&pdev->dev,
- "%s pci_set_power_state invoked successfully\n",
- __func__);
} else {
dev_err(&pdev->dev, "%s pci_save_state failed\n", __func__);
}
- dev_dbg(&pdev->dev, "%s return=%d\n", __func__, retval);
-
return retval;
}
static int pch_spi_resume(struct pci_dev *pdev)
{
int retval;
-
- struct pch_spi_board_data *board = pci_get_drvdata(pdev);
+ struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
- if (!board) {
- dev_err(&pdev->dev,
- "%s pci_get_drvdata returned NULL\n", __func__);
- return -EFAULT;
- }
-
- /* move device to DO power state */
pci_set_power_state(pdev, PCI_D0);
-
- /* restore state */
pci_restore_state(pdev);
retval = pci_enable_device(pdev);
@@ -1242,34 +1281,12 @@ static int pch_spi_resume(struct pci_dev *pdev)
dev_err(&pdev->dev,
"%s pci_enable_device failed\n", __func__);
} else {
- /* disable PM notifications */
pci_enable_wake(pdev, PCI_D3hot, 0);
- /* register IRQ handler */
- if (!board->irq_reg_sts) {
- /* register IRQ */
- retval = request_irq(board->pdev->irq, pch_spi_handler,
- IRQF_SHARED, KBUILD_MODNAME,
- board);
- if (retval < 0) {
- dev_err(&pdev->dev,
- "%s request_irq failed\n", __func__);
- return retval;
- }
- board->irq_reg_sts = true;
-
- /* reset PCH SPI h/w */
- pch_spi_reset(board->data->master);
- pch_spi_set_master_mode(board->data->master);
-
- /* set suspend status to false */
- board->suspend_sts = false;
-
- }
+ /* set suspend status to false */
+ pd_dev_save->board_dat->suspend_sts = false;
}
- dev_dbg(&pdev->dev, "%s returning=%d\n", __func__, retval);
-
return retval;
}
#else
@@ -1289,15 +1306,25 @@ static struct pci_driver pch_spi_pcidev = {
static int __init pch_spi_init(void)
{
- return pci_register_driver(&pch_spi_pcidev);
+ int ret;
+ ret = platform_driver_register(&pch_spi_pd_driver);
+ if (ret)
+ return ret;
+
+ ret = pci_register_driver(&pch_spi_pcidev);
+ if (ret)
+ return ret;
+
+ return 0;
}
module_init(pch_spi_init);
static void __exit pch_spi_exit(void)
{
pci_unregister_driver(&pch_spi_pcidev);
+ platform_driver_unregister(&pch_spi_pd_driver);
}
module_exit(pch_spi_exit);
MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Topcliff PCH SPI PCI Driver");
+MODULE_DESCRIPTION("Intel EG20T PCH/OKI SEMICONDUCTOR ML7213 IOH SPI Driver");
--
1.7.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH/RESEND v4 1/2] spi_topcliff_pch: support new device ML7213 IOH
2009-01-01 23:29 ` [PATCH/RESEND v4 1/2] spi_topcliff_pch: support new device ML7213 IOH y
@ 2011-06-07 5:54 ` Tomoya MORINAGA
0 siblings, 0 replies; 743+ messages in thread
From: Tomoya MORINAGA @ 2011-06-07 5:54 UTC (permalink / raw)
To: Grant Likely
Cc: David Brownell, spi-devel-general, linux-kernel, qi.wang,
yong.y.wang, joel.clark, kok.howg.ewe, toshiharu-linux
Sorry for invalid patch "From y"
It seems My PC's clock is reset.
(2009/01/02 8:29), y wrote:
> From: Tomoya MORINAGA<tomoya-linux@dsn.okisemi.com>
>
> ***Modify Grant's comments.
> - Delete unrelated whitespace
> - Prevent device driver from accessing platform data
> - Add __devinit and __devexit
> - Save pdev->dev to pd_dev->dev.parent
> - Have own suspend/resume processing in platform_driver.
> - Care returned value in pch_spi_init
> - Change unregister order
>
> Support ML7213 device of OKI SEMICONDUCTOR.
> ML7213 is companion chip of Intel Atom E6xx series for IVI(In-Vehicle Infotainment).
> ML7213 is compatible for Intel EG20T PCH.
>
> Signed-off-by: Tomoya MORINAGA<tomoya-linux@dsn.okisemi.com>
> ---
> drivers/spi/Kconfig | 5 +-
> drivers/spi/spi_topcliff_pch.c | 599 +++++++++++++++++++++-------------------
> 2 files changed, 317 insertions(+), 287 deletions(-)
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index fc14b8d..ed6134b 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -380,12 +380,15 @@ config SPI_TI_SSP
> module will be called ti-ssp-spi.
>
> config SPI_TOPCLIFF_PCH
> - tristate "Topcliff PCH SPI Controller"
> + tristate "Intel EG20T PCH/OKI SEMICONDUCTOR ML7213 IOH SPI controller"
> depends on PCI
> help
> SPI driver for the Topcliff PCH (Platform Controller Hub) SPI bus
> used in some x86 embedded processors.
>
> + This driver also supports the ML7213, a companion chip for the
> + Atom E6xx series and compatible with the Intel EG20T PCH.
> +
> config SPI_TXX9
> tristate "Toshiba TXx9 SPI controller"
> depends on GENERIC_GPIO&& CPU_TX49XX
> diff --git a/drivers/spi/spi_topcliff_pch.c b/drivers/spi/spi_topcliff_pch.c
> index 79e48d4..88bd472 100644
> --- a/drivers/spi/spi_topcliff_pch.c
> +++ b/drivers/spi/spi_topcliff_pch.c
> @@ -26,6 +26,7 @@
> #include<linux/spi/spidev.h>
> #include<linux/module.h>
> #include<linux/device.h>
> +#include<linux/platform_device.h>
>
> /* Register offsets */
> #define PCH_SPCR 0x00 /* SPI control register */
> @@ -35,6 +36,7 @@
> #define PCH_SPDRR 0x10 /* SPI read data register */
> #define PCH_SSNXCR 0x18 /* SSN Expand Control Register */
> #define PCH_SRST 0x1C /* SPI reset register */
> +#define PCH_SPI_ADDRESS_SIZE 0x20
>
> #define PCH_SPSR_TFD 0x000007C0
> #define PCH_SPSR_RFD 0x0000F800
> @@ -75,7 +77,8 @@
> #define SPSR_FI_BIT (1<< 2)
> #define SPBRR_SIZE_BIT (1<< 10)
>
> -#define PCH_ALL (SPCR_TFIE_BIT|SPCR_RFIE_BIT|SPCR_FIE_BIT|SPCR_ORIE_BIT|SPCR_MDFIE_BIT)
> +#define PCH_ALL (SPCR_TFIE_BIT|SPCR_RFIE_BIT|SPCR_FIE_BIT|\
> + SPCR_ORIE_BIT|SPCR_MDFIE_BIT)
>
> #define SPCR_RFIC_FIELD 20
> #define SPCR_TFIC_FIELD 16
> @@ -88,6 +91,16 @@
> #define PCH_CLOCK_HZ 50000000
> #define PCH_MAX_SPBR 1023
>
> +/* Definition for ML7213 by OKI SEMICONDUCTOR */
> +#define PCI_VENDOR_ID_ROHM 0x10DB
> +#define PCI_DEVICE_ID_ML7213_SPI 0x802c
> +
> +/*
> + * Set the number of SPI instance max
> + * Intel EG20T PCH : 1ch
> + * OKI SEMICONDUCTOR ML7213 IOH : 2ch
> +*/
> +#define PCH_SPI_MAX_DEV 2
>
> /**
> * struct pch_spi_data - Holds the SPI channel specific details
> @@ -121,6 +134,9 @@
> * @cur_trans: The current transfer that this SPI driver is
> * handling
> * @board_dat: Reference to the SPI device data structure
> + * @plat_dev: platform_device structure
> + * @ch: SPI channel number
> + * @irq_reg_sts: Status of IRQ registration
> */
> struct pch_spi_data {
> void __iomem *io_remap_addr;
> @@ -144,27 +160,33 @@ struct pch_spi_data {
> struct spi_message *current_msg;
> struct spi_transfer *cur_trans;
> struct pch_spi_board_data *board_dat;
> + struct platform_device *plat_dev;
> + int ch;
> + u8 irq_reg_sts;
> };
>
> /**
> * struct pch_spi_board_data - Holds the SPI device specific details
> * @pdev: Pointer to the PCI device
> - * @irq_reg_sts: Status of IRQ registration
> - * @pci_req_sts: Status of pci_request_regions
> * @suspend_sts: Status of suspend
> - * @data: Pointer to SPI channel data structure
> + * @num: The number of SPI device instance
> */
> struct pch_spi_board_data {
> struct pci_dev *pdev;
> - u8 irq_reg_sts;
> - u8 pci_req_sts;
> u8 suspend_sts;
> - struct pch_spi_data *data;
> + int num;
> +};
> +
> +struct pch_pd_dev_save {
> + int num;
> + struct platform_device *pd_save[PCH_SPI_MAX_DEV];
> + struct pch_spi_board_data *board_dat;
> };
>
> static struct pci_device_id pch_spi_pcidev_id[] = {
> - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_GE_SPI)},
> - {0,}
> + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_GE_SPI), 1, },
> + { PCI_VDEVICE(ROHM, PCI_DEVICE_ID_ML7213_SPI), 2, },
> + { }
> };
>
> /**
> @@ -283,11 +305,11 @@ static void pch_spi_handler_sub(struct pch_spi_data *data, u32 reg_spsr_val,
> static irqreturn_t pch_spi_handler(int irq, void *dev_id)
> {
> u32 reg_spsr_val;
> - struct pch_spi_data *data;
> void __iomem *spsr;
> void __iomem *io_remap_addr;
> irqreturn_t ret = IRQ_NONE;
> - struct pch_spi_board_data *board_dat = dev_id;
> + struct pch_spi_data *data = dev_id;
> + struct pch_spi_board_data *board_dat = data->board_dat;
>
> if (board_dat->suspend_sts) {
> dev_dbg(&board_dat->pdev->dev,
> @@ -295,10 +317,8 @@ static irqreturn_t pch_spi_handler(int irq, void *dev_id)
> return IRQ_NONE;
> }
>
> - data = board_dat->data;
> io_remap_addr = data->io_remap_addr;
> spsr = io_remap_addr + PCH_SPSR;
> -
> reg_spsr_val = ioread32(spsr);
>
> /* Check if the interrupt is for SPI device */
> @@ -412,7 +432,6 @@ static int pch_spi_setup(struct spi_device *pspi)
>
> static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
> {
> -
> struct spi_transfer *transfer;
> struct pch_spi_data *data = spi_master_get_devdata(pspi->master);
> int retval;
> @@ -547,7 +566,8 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
>
> /* set bits per word if needed */
> if (data->cur_trans->bits_per_word&&
> - (data->current_msg->spi->bits_per_word != data->cur_trans->bits_per_word)) {
> + (data->current_msg->spi->bits_per_word !=\
> + data->cur_trans->bits_per_word)) {
> dev_dbg(&data->master->dev, "%s:set bits per word\n", __func__);
> pch_spi_set_bits_per_word(data->master,
> data->cur_trans->bits_per_word);
> @@ -621,7 +641,6 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
> data->transfer_active = true;
> }
>
> -
> static void pch_spi_nomore_transfer(struct pch_spi_data *data,
> struct spi_message *pmsg)
> {
> @@ -697,7 +716,8 @@ static void pch_spi_set_ir(struct pch_spi_data *data)
> "%s:invoking pch_spi_set_enable to enable SPI\n", __func__);
>
> /* SPI set enable */
> - pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, SPCR_SPE_BIT, 0);
> + pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, SPCR_SPE_BIT,
> + 0);
>
> /* Wait until the transfer completes; go to sleep after
> initiating the transfer. */
> @@ -742,7 +762,6 @@ static void pch_spi_copy_rx_data(struct pch_spi_data *data, int bpw)
> }
> }
>
> -
> static void pch_spi_process_messages(struct work_struct *pwork)
> {
> struct spi_message *pmsg;
> @@ -868,117 +887,49 @@ static void pch_spi_process_messages(struct work_struct *pwork)
> } while (data->cur_trans != NULL);
> }
>
> -static void pch_spi_free_resources(struct pch_spi_board_data *board_dat)
> +static void pch_spi_free_resources(struct pch_spi_board_data *board_dat,
> + struct pch_spi_data *data)
> {
> dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
>
> /* free workqueue */
> - if (board_dat->data->wk != NULL) {
> - destroy_workqueue(board_dat->data->wk);
> - board_dat->data->wk = NULL;
> + if (data->wk != NULL) {
> + destroy_workqueue(data->wk);
> + data->wk = NULL;
> dev_dbg(&board_dat->pdev->dev,
> "%s destroy_workqueue invoked successfully\n",
> __func__);
> }
> -
> - /* disable interrupts& free IRQ */
> - if (board_dat->irq_reg_sts) {
> - /* disable interrupts */
> - pch_spi_setclr_reg(board_dat->data->master, PCH_SPCR, 0,
> - PCH_ALL);
> -
> - /* free IRQ */
> - free_irq(board_dat->pdev->irq, board_dat);
> -
> - dev_dbg(&board_dat->pdev->dev,
> - "%s free_irq invoked successfully\n", __func__);
> -
> - board_dat->irq_reg_sts = false;
> - }
> -
> - /* unmap PCI base address */
> - if (board_dat->data->io_remap_addr != 0) {
> - pci_iounmap(board_dat->pdev, board_dat->data->io_remap_addr);
> -
> - board_dat->data->io_remap_addr = 0;
> -
> - dev_dbg(&board_dat->pdev->dev,
> - "%s pci_iounmap invoked successfully\n", __func__);
> - }
> -
> - /* release PCI region */
> - if (board_dat->pci_req_sts) {
> - pci_release_regions(board_dat->pdev);
> - dev_dbg(&board_dat->pdev->dev,
> - "%s pci_release_regions invoked successfully\n",
> - __func__);
> - board_dat->pci_req_sts = false;
> - }
> }
>
> -static int pch_spi_get_resources(struct pch_spi_board_data *board_dat)
> +static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
> + struct pch_spi_data *data)
> {
> - void __iomem *io_remap_addr;
> - int retval;
> + int retval = 0;
> +
> dev_dbg(&board_dat->pdev->dev, "%s ENTRY\n", __func__);
>
> /* create workqueue */
> - board_dat->data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
> - if (!board_dat->data->wk) {
> + data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
> + if (!data->wk) {
> dev_err(&board_dat->pdev->dev,
> "%s create_singlet hread_workqueue failed\n", __func__);
> retval = -EBUSY;
> goto err_return;
> }
>
> - dev_dbg(&board_dat->pdev->dev,
> - "%s create_singlethread_workqueue success\n", __func__);
> -
> - retval = pci_request_regions(board_dat->pdev, KBUILD_MODNAME);
> - if (retval != 0) {
> - dev_err(&board_dat->pdev->dev,
> - "%s request_region failed\n", __func__);
> - goto err_return;
> - }
> -
> - board_dat->pci_req_sts = true;
> -
> - io_remap_addr = pci_iomap(board_dat->pdev, 1, 0);
> - if (io_remap_addr == 0) {
> - dev_err(&board_dat->pdev->dev,
> - "%s pci_iomap failed\n", __func__);
> - retval = -ENOMEM;
> - goto err_return;
> - }
> -
> - /* calculate base address for all channels */
> - board_dat->data->io_remap_addr = io_remap_addr;
> -
> /* reset PCH SPI h/w */
> - pch_spi_reset(board_dat->data->master);
> + pch_spi_reset(data->master);
> dev_dbg(&board_dat->pdev->dev,
> "%s pch_spi_reset invoked successfully\n", __func__);
>
> - /* register IRQ */
> - retval = request_irq(board_dat->pdev->irq, pch_spi_handler,
> - IRQF_SHARED, KBUILD_MODNAME, board_dat);
> - if (retval != 0) {
> - dev_err(&board_dat->pdev->dev,
> - "%s request_irq failed\n", __func__);
> - goto err_return;
> - }
> -
> - dev_dbg(&board_dat->pdev->dev, "%s request_irq returned=%d\n",
> - __func__, retval);
> -
> - board_dat->irq_reg_sts = true;
> dev_dbg(&board_dat->pdev->dev, "%s data->irq_reg_sts=true\n", __func__);
>
> err_return:
> if (retval != 0) {
> dev_err(&board_dat->pdev->dev,
> "%s FAIL:invoking pch_spi_free_resources\n", __func__);
> - pch_spi_free_resources(board_dat);
> + pch_spi_free_resources(board_dat, data);
> }
>
> dev_dbg(&board_dat->pdev->dev, "%s Return=%d\n", __func__, retval);
> @@ -986,255 +937,343 @@ err_return:
> return retval;
> }
>
> -static int pch_spi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> +static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
> {
> -
> + int ret;
> struct spi_master *master;
> + struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
> + struct pch_spi_data *data;
>
> - struct pch_spi_board_data *board_dat;
> - int retval;
> -
> - dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
> -
> - /* allocate memory for private data */
> - board_dat = kzalloc(sizeof(struct pch_spi_board_data), GFP_KERNEL);
> - if (board_dat == NULL) {
> - dev_err(&pdev->dev,
> - " %s memory allocation for private data failed\n",
> - __func__);
> - retval = -ENOMEM;
> - goto err_kmalloc;
> - }
> -
> - dev_dbg(&pdev->dev,
> - "%s memory allocation for private data success\n", __func__);
> -
> - /* enable PCI device */
> - retval = pci_enable_device(pdev);
> - if (retval != 0) {
> - dev_err(&pdev->dev, "%s pci_enable_device FAILED\n", __func__);
> -
> - goto err_pci_en_device;
> + master = spi_alloc_master(&board_dat->pdev->dev,
> + sizeof(struct pch_spi_data));
> + if (!master) {
> + dev_err(&plat_dev->dev, "spi_alloc_master[%d] failed.\n",
> + plat_dev->id);
> + return -ENOMEM;
> }
>
> - dev_dbg(&pdev->dev, "%s pci_enable_device returned=%d\n",
> - __func__, retval);
> + data = spi_master_get_devdata(master);
> + data->master = master;
>
> - board_dat->pdev = pdev;
> + platform_set_drvdata(plat_dev, data);
>
> - /* alllocate memory for SPI master */
> - master = spi_alloc_master(&pdev->dev, sizeof(struct pch_spi_data));
> - if (master == NULL) {
> - retval = -ENOMEM;
> - dev_err(&pdev->dev, "%s Fail.\n", __func__);
> - goto err_spi_alloc_master;
> + /* baseaddress + 0x20(offset) */
> + data->io_remap_addr = pci_iomap(board_dat->pdev, 1, 0) +
> + 0x20 * plat_dev->id;
> + if (!data->io_remap_addr) {
> + dev_err(&plat_dev->dev, "%s pci_iomap failed\n", __func__);
> + ret = -ENOMEM;
> + goto err_pci_iomap;
> }
>
> - dev_dbg(&pdev->dev,
> - "%s spi_alloc_master returned non NULL\n", __func__);
> + dev_dbg(&plat_dev->dev, "[ch%d] remap_addr=%p\n",
> + plat_dev->id, data->io_remap_addr);
>
> /* initialize members of SPI master */
> - master->bus_num = -1;
> + master->bus_num = plat_dev->id;
> master->num_chipselect = PCH_MAX_CS;
> master->setup = pch_spi_setup;
> master->transfer = pch_spi_transfer;
> - dev_dbg(&pdev->dev,
> - "%s transfer member of SPI master initialized\n", __func__);
>
> - board_dat->data = spi_master_get_devdata(master);
> + data->board_dat = board_dat;
> + data->plat_dev = plat_dev;
> + data->n_curnt_chip = 255;
> + data->status = STATUS_RUNNING;
> + data->ch = plat_dev->id;
>
> - board_dat->data->master = master;
> - board_dat->data->n_curnt_chip = 255;
> - board_dat->data->board_dat = board_dat;
> - board_dat->data->status = STATUS_RUNNING;
> + INIT_LIST_HEAD(&data->queue);
> + spin_lock_init(&data->lock);
> + INIT_WORK(&data->work, pch_spi_process_messages);
> + init_waitqueue_head(&data->wait);
>
> - INIT_LIST_HEAD(&board_dat->data->queue);
> - spin_lock_init(&board_dat->data->lock);
> - INIT_WORK(&board_dat->data->work, pch_spi_process_messages);
> - init_waitqueue_head(&board_dat->data->wait);
> -
> - /* allocate resources for PCH SPI */
> - retval = pch_spi_get_resources(board_dat);
> - if (retval) {
> - dev_err(&pdev->dev, "%s fail(retval=%d)\n", __func__, retval);
> + ret = pch_spi_get_resources(board_dat, data);
> + if (ret) {
> + dev_err(&plat_dev->dev, "%s fail(retval=%d)\n", __func__, ret);
> goto err_spi_get_resources;
> }
>
> - dev_dbg(&pdev->dev, "%s pch_spi_get_resources returned=%d\n",
> - __func__, retval);
> -
> - /* save private data in dev */
> - pci_set_drvdata(pdev, board_dat);
> - dev_dbg(&pdev->dev, "%s invoked pci_set_drvdata\n", __func__);
> + ret = request_irq(board_dat->pdev->irq, pch_spi_handler,
> + IRQF_SHARED, KBUILD_MODNAME, data);
> + if (ret) {
> + dev_err(&plat_dev->dev,
> + "%s request_irq failed\n", __func__);
> + goto err_request_irq;
> + }
> + data->irq_reg_sts = true;
>
> - /* set master mode */
> pch_spi_set_master_mode(master);
> - dev_dbg(&pdev->dev,
> - "%s invoked pch_spi_set_master_mode\n", __func__);
>
> - /* Register the controller with the SPI core. */
> - retval = spi_register_master(master);
> - if (retval != 0) {
> - dev_err(&pdev->dev,
> + ret = spi_register_master(master);
> + if (ret != 0) {
> + dev_err(&plat_dev->dev,
> "%s spi_register_master FAILED\n", __func__);
> - goto err_spi_reg_master;
> + goto err_spi_register_master;
> }
>
> - dev_dbg(&pdev->dev, "%s spi_register_master returned=%d\n",
> - __func__, retval);
> -
> -
> return 0;
>
> -err_spi_reg_master:
> - spi_unregister_master(master);
> +err_spi_register_master:
> + free_irq(board_dat->pdev->irq, board_dat);
> +err_request_irq:
> + pch_spi_free_resources(board_dat, data);
> err_spi_get_resources:
> -err_spi_alloc_master:
> + pci_iounmap(board_dat->pdev, data->io_remap_addr);
> +err_pci_iomap:
> spi_master_put(master);
> - pci_disable_device(pdev);
> -err_pci_en_device:
> - kfree(board_dat);
> -err_kmalloc:
> - return retval;
> +
> + return ret;
> }
>
> -static void pch_spi_remove(struct pci_dev *pdev)
> +static int __devexit pch_spi_pd_remove(struct platform_device *plat_dev)
> {
> - struct pch_spi_board_data *board_dat = pci_get_drvdata(pdev);
> + struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
> + struct pch_spi_data *data = platform_get_drvdata(plat_dev);
> int count;
>
> - dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
> -
> - if (!board_dat) {
> - dev_err(&pdev->dev,
> - "%s pci_get_drvdata returned NULL\n", __func__);
> - return;
> - }
> -
> + dev_dbg(&plat_dev->dev, "%s:[ch%d] irq=%d\n",
> + __func__, plat_dev->id, board_dat->pdev->irq);
> /* check for any pending messages; no action is taken if the queue
> * is still full; but at least we tried. Unload anyway */
> count = 500;
> - spin_lock(&board_dat->data->lock);
> - board_dat->data->status = STATUS_EXITING;
> - while ((list_empty(&board_dat->data->queue) == 0)&& --count) {
> + spin_lock(&data->lock);
> + data->status = STATUS_EXITING;
> + while ((list_empty(&data->queue) == 0)&& --count) {
> dev_dbg(&board_dat->pdev->dev, "%s :queue not empty\n",
> __func__);
> - spin_unlock(&board_dat->data->lock);
> + spin_unlock(&data->lock);
> msleep(PCH_SLEEP_TIME);
> - spin_lock(&board_dat->data->lock);
> + spin_lock(&data->lock);
> }
> - spin_unlock(&board_dat->data->lock);
> -
> - /* Free resources allocated for PCH SPI */
> - pch_spi_free_resources(board_dat);
> -
> - spi_unregister_master(board_dat->data->master);
> -
> - /* free memory for private data */
> - kfree(board_dat);
> + spin_unlock(&data->lock);
>
> - pci_set_drvdata(pdev, NULL);
> + pch_spi_free_resources(board_dat, data);
> + /* disable interrupts& free IRQ */
> + if (data->irq_reg_sts) {
> + /* disable interrupts */
> + pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
> + data->irq_reg_sts = false;
> + free_irq(board_dat->pdev->irq, data);
> + }
>
> - /* disable PCI device */
> - pci_disable_device(pdev);
> + pci_iounmap(board_dat->pdev, data->io_remap_addr);
> + spi_unregister_master(data->master);
> + spi_master_put(data->master);
> + platform_set_drvdata(plat_dev, NULL);
>
> - dev_dbg(&pdev->dev, "%s invoked pci_disable_device\n", __func__);
> + return 0;
> }
> -
> #ifdef CONFIG_PM
> -static int pch_spi_suspend(struct pci_dev *pdev, pm_message_t state)
> +static int pch_spi_pd_suspend(struct platform_device *pd_dev,
> + pm_message_t state)
> {
> u8 count;
> - int retval;
> -
> - struct pch_spi_board_data *board_dat = pci_get_drvdata(pdev);
> + struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev);
> + struct pch_spi_data *data = platform_get_drvdata(pd_dev);
>
> - dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
> + dev_dbg(&pd_dev->dev, "%s ENTRY\n", __func__);
>
> if (!board_dat) {
> - dev_err(&pdev->dev,
> + dev_err(&pd_dev->dev,
> "%s pci_get_drvdata returned NULL\n", __func__);
> return -EFAULT;
> }
>
> - retval = 0;
> - board_dat->suspend_sts = true;
> -
> /* check if the current message is processed:
> Only after thats done the transfer will be suspended */
> count = 255;
> - while ((--count)> 0) {
> - if (!(board_dat->data->bcurrent_msg_processing)) {
> - dev_dbg(&pdev->dev, "%s board_dat->data->bCurrent_"
> - "msg_processing = false\n", __func__);
> + while ((--count)> 0)
> + if (!(data->bcurrent_msg_processing)) {
> break;
> - } else {
> - dev_dbg(&pdev->dev, "%s board_dat->data->bCurrent_msg_"
> - "processing = true\n", __func__);
> - }
> msleep(PCH_SLEEP_TIME);
> }
>
> /* Free IRQ */
> - if (board_dat->irq_reg_sts) {
> + if (data->irq_reg_sts) {
> /* disable all interrupts */
> - pch_spi_setclr_reg(board_dat->data->master, PCH_SPCR, 0,
> - PCH_ALL);
> - pch_spi_reset(board_dat->data->master);
> -
> - free_irq(board_dat->pdev->irq, board_dat);
> + pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
> + pch_spi_reset(data->master);
> + free_irq(board_dat->pdev->irq, data);
>
> - board_dat->irq_reg_sts = false;
> - dev_dbg(&pdev->dev,
> + data->irq_reg_sts = false;
> + dev_dbg(&pd_dev->dev,
> "%s free_irq invoked successfully.\n", __func__);
> }
>
> + return 0;
> +}
> +
> +static int pch_spi_pd_resume(struct platform_device *pd_dev)
> +{
> + struct pch_spi_board_data *board_dat = dev_get_platdata(&pd_dev->dev);
> + struct pch_spi_data *data = platform_get_drvdata(pd_dev);
> + int retval;
> +
> + if (!board_dat) {
> + dev_err(&pd_dev->dev,
> + "%s pci_get_drvdata returned NULL\n", __func__);
> + return -EFAULT;
> + }
> +
> + if (!data->irq_reg_sts) {
> + /* register IRQ */
> + retval = request_irq(board_dat->pdev->irq, pch_spi_handler,
> + IRQF_SHARED, KBUILD_MODNAME, data);
> + if (retval< 0) {
> + dev_err(&pd_dev->dev,
> + "%s request_irq failed\n", __func__);
> + return retval;
> + }
> +
> + /* reset PCH SPI h/w */
> + pch_spi_reset(data->master);
> + pch_spi_set_master_mode(data->master);
> + data->irq_reg_sts = true;
> + }
> + return 0;
> +}
> +#else
> +#define pch_spi_pd_suspend NULL
> +#define pch_spi_pd_resume NULL
> +#endif
> +
> +static struct platform_driver pch_spi_pd_driver = {
> + .driver = {
> + .name = "pch-spi",
> + .owner = THIS_MODULE,
> + },
> + .probe = pch_spi_pd_probe,
> + .remove = __devexit_p(pch_spi_pd_remove),
> + .suspend = pch_spi_pd_suspend,
> + .resume = pch_spi_pd_resume
> +};
> +
> +static int __devinit pch_spi_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + struct pch_spi_board_data *board_dat;
> + struct platform_device *pd_dev = NULL;
> + int retval;
> + int i;
> + struct pch_pd_dev_save *pd_dev_save;
> +
> + pd_dev_save = kzalloc(sizeof(struct pch_pd_dev_save), GFP_KERNEL);
> + if (!pd_dev_save) {
> + dev_err(&pdev->dev, "%s Can't allocate pd_dev_sav\n", __func__);
> + return -ENOMEM;
> + }
> +
> + board_dat = kzalloc(sizeof(struct pch_spi_board_data), GFP_KERNEL);
> + if (!board_dat) {
> + dev_err(&pdev->dev, "%s Can't allocate board_dat\n", __func__);
> + retval = -ENOMEM;
> + goto err_no_mem;
> + }
> +
> + retval = pci_request_regions(pdev, KBUILD_MODNAME);
> + if (retval) {
> + dev_err(&pdev->dev, "%s request_region failed\n", __func__);
> + goto pci_request_regions;
> + }
> +
> + board_dat->pdev = pdev;
> + board_dat->num = id->driver_data;
> + pd_dev_save->num = id->driver_data;
> + pd_dev_save->board_dat = board_dat;
> +
> + retval = pci_enable_device(pdev);
> + if (retval) {
> + dev_err(&pdev->dev, "%s pci_enable_device failed\n", __func__);
> + goto pci_enable_device;
> + }
> +
> + for (i = 0; i< board_dat->num; i++) {
> + pd_dev = platform_device_alloc("pch-spi", i);
> + if (!pd_dev) {
> + dev_err(&pdev->dev, "platform_device_alloc failed\n");
> + goto err_platform_device;
> + }
> + pd_dev_save->pd_save[i] = pd_dev;
> + pd_dev->dev.parent =&pdev->dev;
> +
> + retval = platform_device_add_data(pd_dev, board_dat,
> + sizeof(*board_dat));
> + if (retval) {
> + dev_err(&pdev->dev,
> + "platform_device_add_data failed\n");
> + platform_device_put(pd_dev);
> + goto err_platform_device;
> + }
> +
> + retval = platform_device_add(pd_dev);
> + if (retval) {
> + dev_err(&pdev->dev, "platform_device_add failed\n");
> + platform_device_put(pd_dev);
> + goto err_platform_device;
> + }
> + }
> +
> + pci_set_drvdata(pdev, pd_dev_save);
> +
> + return 0;
> +
> +err_platform_device:
> + pci_disable_device(pdev);
> +pci_enable_device:
> + pci_release_regions(pdev);
> +pci_request_regions:
> + kfree(board_dat);
> +err_no_mem:
> + kfree(pd_dev_save);
> +
> + return retval;
> +}
> +
> +static void __devexit pch_spi_remove(struct pci_dev *pdev)
> +{
> + int i;
> + struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
> +
> + dev_dbg(&pdev->dev, "%s ENTRY:pdev=%p\n", __func__, pdev);
> +
> + for (i = 0; i< pd_dev_save->num; i++)
> + platform_device_unregister(pd_dev_save->pd_save[i]);
> +
> + pci_disable_device(pdev);
> + pci_release_regions(pdev);
> + kfree(pd_dev_save->board_dat);
> + kfree(pd_dev_save);
> +}
> +
> +#ifdef CONFIG_PM
> +static int pch_spi_suspend(struct pci_dev *pdev, pm_message_t state)
> +{
> + int retval;
> + struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
> +
> + dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
> +
> + pd_dev_save->board_dat->suspend_sts = true;
> +
> /* save config space */
> retval = pci_save_state(pdev);
> -
> if (retval == 0) {
> - dev_dbg(&pdev->dev, "%s pci_save_state returned=%d\n",
> - __func__, retval);
> - /* disable PM notifications */
> pci_enable_wake(pdev, PCI_D3hot, 0);
> - dev_dbg(&pdev->dev,
> - "%s pci_enable_wake invoked successfully\n", __func__);
> - /* disable PCI device */
> pci_disable_device(pdev);
> - dev_dbg(&pdev->dev,
> - "%s pci_disable_device invoked successfully\n",
> - __func__);
> - /* move device to D3hot state */
> pci_set_power_state(pdev, PCI_D3hot);
> - dev_dbg(&pdev->dev,
> - "%s pci_set_power_state invoked successfully\n",
> - __func__);
> } else {
> dev_err(&pdev->dev, "%s pci_save_state failed\n", __func__);
> }
>
> - dev_dbg(&pdev->dev, "%s return=%d\n", __func__, retval);
> -
> return retval;
> }
>
> static int pch_spi_resume(struct pci_dev *pdev)
> {
> int retval;
> -
> - struct pch_spi_board_data *board = pci_get_drvdata(pdev);
> + struct pch_pd_dev_save *pd_dev_save = pci_get_drvdata(pdev);
> dev_dbg(&pdev->dev, "%s ENTRY\n", __func__);
>
> - if (!board) {
> - dev_err(&pdev->dev,
> - "%s pci_get_drvdata returned NULL\n", __func__);
> - return -EFAULT;
> - }
> -
> - /* move device to DO power state */
> pci_set_power_state(pdev, PCI_D0);
> -
> - /* restore state */
> pci_restore_state(pdev);
>
> retval = pci_enable_device(pdev);
> @@ -1242,34 +1281,12 @@ static int pch_spi_resume(struct pci_dev *pdev)
> dev_err(&pdev->dev,
> "%s pci_enable_device failed\n", __func__);
> } else {
> - /* disable PM notifications */
> pci_enable_wake(pdev, PCI_D3hot, 0);
>
> - /* register IRQ handler */
> - if (!board->irq_reg_sts) {
> - /* register IRQ */
> - retval = request_irq(board->pdev->irq, pch_spi_handler,
> - IRQF_SHARED, KBUILD_MODNAME,
> - board);
> - if (retval< 0) {
> - dev_err(&pdev->dev,
> - "%s request_irq failed\n", __func__);
> - return retval;
> - }
> - board->irq_reg_sts = true;
> -
> - /* reset PCH SPI h/w */
> - pch_spi_reset(board->data->master);
> - pch_spi_set_master_mode(board->data->master);
> -
> - /* set suspend status to false */
> - board->suspend_sts = false;
> -
> - }
> + /* set suspend status to false */
> + pd_dev_save->board_dat->suspend_sts = false;
> }
>
> - dev_dbg(&pdev->dev, "%s returning=%d\n", __func__, retval);
> -
> return retval;
> }
> #else
> @@ -1289,15 +1306,25 @@ static struct pci_driver pch_spi_pcidev = {
>
> static int __init pch_spi_init(void)
> {
> - return pci_register_driver(&pch_spi_pcidev);
> + int ret;
> + ret = platform_driver_register(&pch_spi_pd_driver);
> + if (ret)
> + return ret;
> +
> + ret = pci_register_driver(&pch_spi_pcidev);
> + if (ret)
> + return ret;
> +
> + return 0;
> }
> module_init(pch_spi_init);
>
> static void __exit pch_spi_exit(void)
> {
> pci_unregister_driver(&pch_spi_pcidev);
> + platform_driver_unregister(&pch_spi_pd_driver);
> }
> module_exit(pch_spi_exit);
>
> MODULE_LICENSE("GPL");
> -MODULE_DESCRIPTION("Topcliff PCH SPI PCI Driver");
> +MODULE_DESCRIPTION("Intel EG20T PCH/OKI SEMICONDUCTOR ML7213 IOH SPI Driver");
--
tomoya
OKI SEMICONDUCTOR CO., LTD.
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH/RESEND 2/2] spi_topcliff_pch: DMA support
[not found] <y>
2009-01-01 23:29 ` [PATCH/RESEND v4 1/2] spi_topcliff_pch: support new device ML7213 IOH y
@ 2009-01-01 23:29 ` y
2009-10-06 2:35 ` [PATCH 1/1] perf tools: elf_sym__is_function should accept "zero" sized functions Arnaldo Carvalho de Melo
` (158 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: y @ 2009-01-01 23:29 UTC (permalink / raw)
To: David Brownell, Grant Likely, spi-devel-general, linux-kernel
Cc: qi.wang, yong.y.wang, joel.clark, kok.howg.ewe, toshiharu-linux,
Tomoya MORINAGA
From: Tomoya MORINAGA <tomoya-linux@dsn.okisemi.com>
This patch enables this SPI driver works with DMA mode.
Signed-off-by: Tomoya MORINAGA <tomoya-linux@dsn.okisemi.com>
---
drivers/spi/spi_topcliff_pch.c | 612 ++++++++++++++++++++++++++++++++--------
1 files changed, 492 insertions(+), 120 deletions(-)
diff --git a/drivers/spi/spi_topcliff_pch.c b/drivers/spi/spi_topcliff_pch.c
index 88bd472..f543ff6 100644
--- a/drivers/spi/spi_topcliff_pch.c
+++ b/drivers/spi/spi_topcliff_pch.c
@@ -28,6 +28,9 @@
#include <linux/device.h>
#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/pch_dma.h>
+
/* Register offsets */
#define PCH_SPCR 0x00 /* SPI control register */
#define PCH_SPBRR 0x04 /* SPI baud rate register */
@@ -36,7 +39,7 @@
#define PCH_SPDRR 0x10 /* SPI read data register */
#define PCH_SSNXCR 0x18 /* SSN Expand Control Register */
#define PCH_SRST 0x1C /* SPI reset register */
-#define PCH_SPI_ADDRESS_SIZE 0x20
+#define PCH_ADDRESS_SIZE 0x20
#define PCH_SPSR_TFD 0x000007C0
#define PCH_SPSR_RFD 0x0000F800
@@ -54,8 +57,6 @@
#define STATUS_EXITING 2
#define PCH_SLEEP_TIME 10
-#define PCH_ADDRESS_SIZE 0x20
-
#define SSN_LOW 0x02U
#define SSN_NO_CONTROL 0x00U
#define PCH_MAX_CS 0xFF
@@ -75,6 +76,7 @@
#define SPSR_TFI_BIT (1 << 0)
#define SPSR_RFI_BIT (1 << 1)
#define SPSR_FI_BIT (1 << 2)
+#define SPSR_ORF_BIT (1 << 3)
#define SPBRR_SIZE_BIT (1 << 10)
#define PCH_ALL (SPCR_TFIE_BIT|SPCR_RFIE_BIT|SPCR_FIE_BIT|\
@@ -83,10 +85,9 @@
#define SPCR_RFIC_FIELD 20
#define SPCR_TFIC_FIELD 16
-#define SPSR_INT_BITS 0x1F
-#define MASK_SPBRR_SPBR_BITS (~((1 << 10) - 1))
-#define MASK_RFIC_SPCR_BITS (~(0xf << 20))
-#define MASK_TFIC_SPCR_BITS (~(0xf000f << 12))
+#define MASK_SPBRR_SPBR_BITS ((1 << 10) - 1)
+#define MASK_RFIC_SPCR_BITS (0xf << SPCR_RFIC_FIELD)
+#define MASK_TFIC_SPCR_BITS (0xf << SPCR_TFIC_FIELD)
#define PCH_CLOCK_HZ 50000000
#define PCH_MAX_SPBR 1023
@@ -102,6 +103,28 @@
*/
#define PCH_SPI_MAX_DEV 2
+#define PCH_BUF_SIZE 4096
+#define PCH_DMA_TRANS_SIZE 12
+
+static int use_dma = 1;
+
+struct pch_spi_dma_ctrl {
+ struct dma_async_tx_descriptor *desc_tx;
+ struct dma_async_tx_descriptor *desc_rx;
+ struct pch_dma_slave param_tx;
+ struct pch_dma_slave param_rx;
+ struct dma_chan *chan_tx;
+ struct dma_chan *chan_rx;
+ struct scatterlist *sg_tx_p;
+ struct scatterlist *sg_rx_p;
+ struct scatterlist sg_tx;
+ struct scatterlist sg_rx;
+ int nent;
+ void *tx_buf_virt;
+ void *rx_buf_virt;
+ dma_addr_t tx_buf_dma;
+ dma_addr_t rx_buf_dma;
+};
/**
* struct pch_spi_data - Holds the SPI channel specific details
* @io_remap_addr: The remapped PCI base address
@@ -140,6 +163,7 @@
*/
struct pch_spi_data {
void __iomem *io_remap_addr;
+ unsigned long io_base_addr;
struct spi_master *master;
struct work_struct work;
struct workqueue_struct *wk;
@@ -162,6 +186,8 @@ struct pch_spi_data {
struct pch_spi_board_data *board_dat;
struct platform_device *plat_dev;
int ch;
+ struct pch_spi_dma_ctrl dma;
+ int use_dma;
u8 irq_reg_sts;
};
@@ -273,10 +299,10 @@ static void pch_spi_handler_sub(struct pch_spi_data *data, u32 reg_spsr_val,
reg_spcr_val &= ~SPCR_RFIE_BIT; /* disable RFI */
/* reset rx threshold */
- reg_spcr_val &= MASK_RFIC_SPCR_BITS;
+ reg_spcr_val &= ~MASK_RFIC_SPCR_BITS;
reg_spcr_val |= (PCH_RX_THOLD_MAX << SPCR_RFIC_FIELD);
- iowrite32(((reg_spcr_val) &= (~(SPCR_RFIE_BIT))),
- (io_remap_addr + PCH_SPCR));
+
+ iowrite32(reg_spcr_val, (io_remap_addr + PCH_SPCR));
}
/* update counts */
@@ -287,12 +313,15 @@ static void pch_spi_handler_sub(struct pch_spi_data *data, u32 reg_spsr_val,
/* if transfer complete interrupt */
if (reg_spsr_val & SPSR_FI_BIT) {
- /* disable FI & RFI interrupts */
- pch_spi_setclr_reg(data->master, PCH_SPCR, 0,
- SPCR_FIE_BIT | SPCR_RFIE_BIT);
+ if (tx_index < bpw_len)
+ dev_err(&data->master->dev,
+ "%s : Transfer is not completed", __func__);
+ /* disable interrupts */
+ pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
/* transfer is completed;inform pch_spi_process_messages */
data->transfer_complete = true;
+ data->transfer_active = false;
wake_up(&data->wait);
}
}
@@ -316,11 +345,16 @@ static irqreturn_t pch_spi_handler(int irq, void *dev_id)
"%s returning due to suspend\n", __func__);
return IRQ_NONE;
}
+ if (data->use_dma)
+ return IRQ_NONE;
io_remap_addr = data->io_remap_addr;
spsr = io_remap_addr + PCH_SPSR;
reg_spsr_val = ioread32(spsr);
+ if (reg_spsr_val & SPSR_ORF_BIT)
+ dev_err(&board_dat->pdev->dev, "%s Over run error", __func__);
+
/* Check if the interrupt is for SPI device */
if (reg_spsr_val & (SPSR_FI_BIT | SPSR_RFI_BIT)) {
pch_spi_handler_sub(data, reg_spsr_val, io_remap_addr);
@@ -346,7 +380,7 @@ static void pch_spi_set_baud_rate(struct spi_master *master, u32 speed_hz)
if (n_spbr > PCH_MAX_SPBR)
n_spbr = PCH_MAX_SPBR;
- pch_spi_setclr_reg(master, PCH_SPBRR, n_spbr, ~MASK_SPBRR_SPBR_BITS);
+ pch_spi_setclr_reg(master, PCH_SPBRR, n_spbr, MASK_SPBRR_SPBR_BITS);
}
/**
@@ -454,26 +488,27 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
dev_dbg(&pspi->dev, "%s Transfer List not empty. "
"Transfer Speed is set.\n", __func__);
+ spin_lock_irqsave(&data->lock, flags);
/* validate Tx/Rx buffers and Transfer length */
list_for_each_entry(transfer, &pmsg->transfers, transfer_list) {
if (!transfer->tx_buf && !transfer->rx_buf) {
dev_err(&pspi->dev,
"%s Tx and Rx buffer NULL\n", __func__);
retval = -EINVAL;
- goto err_out;
+ goto err_return_spinlock;
}
if (!transfer->len) {
dev_err(&pspi->dev, "%s Transfer length invalid\n",
__func__);
retval = -EINVAL;
- goto err_out;
+ goto err_return_spinlock;
}
dev_dbg(&pspi->dev, "%s Tx/Rx buffer valid. Transfer length"
" valid\n", __func__);
- /* if baud rate hs been specified validate the same */
+ /* if baud rate has been specified validate the same */
if (transfer->speed_hz > PCH_MAX_BAUDRATE)
transfer->speed_hz = PCH_MAX_BAUDRATE;
@@ -484,25 +519,24 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
retval = -EINVAL;
dev_err(&pspi->dev,
"%s Invalid bits per word\n", __func__);
- goto err_out;
+ goto err_return_spinlock;
}
}
}
-
- spin_lock_irqsave(&data->lock, flags);
+ spin_unlock_irqrestore(&data->lock, flags);
/* We won't process any messages if we have been asked to terminate */
if (data->status == STATUS_EXITING) {
dev_err(&pspi->dev, "%s status = STATUS_EXITING.\n", __func__);
retval = -ESHUTDOWN;
- goto err_return_spinlock;
+ goto err_out;
}
/* If suspended ,return -EINVAL */
if (data->board_dat->suspend_sts) {
dev_err(&pspi->dev, "%s suspend; returning EINVAL\n", __func__);
retval = -EINVAL;
- goto err_return_spinlock;
+ goto err_out;
}
/* set status of message */
@@ -510,9 +544,11 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
dev_dbg(&pspi->dev, "%s - pmsg->status =%d\n", __func__, pmsg->status);
pmsg->status = -EINPROGRESS;
-
+ spin_lock_irqsave(&data->lock, flags);
/* add message to queue */
list_add_tail(&pmsg->queue, &data->queue);
+ spin_unlock_irqrestore(&data->lock, flags);
+
dev_dbg(&pspi->dev, "%s - Invoked list_add_tail\n", __func__);
/* schedule work queue to run */
@@ -521,11 +557,13 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
retval = 0;
-err_return_spinlock:
- spin_unlock_irqrestore(&data->lock, flags);
err_out:
dev_dbg(&pspi->dev, "%s RETURN=%d\n", __func__, retval);
return retval;
+err_return_spinlock:
+ dev_dbg(&pspi->dev, "%s RETURN=%d\n", __func__, retval);
+ spin_unlock_irqrestore(&data->lock, flags);
+ return retval;
}
static inline void pch_spi_select_chip(struct pch_spi_data *data,
@@ -546,8 +584,7 @@ static inline void pch_spi_select_chip(struct pch_spi_data *data,
pch_spi_setup_transfer(pspi);
}
-static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
- struct spi_message **ppmsg)
+static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw)
{
int size;
u32 n_writes;
@@ -556,8 +593,6 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
const u8 *tx_buf;
const u16 *tx_sbuf;
- pmsg = *ppmsg;
-
/* set baud rate if needed */
if (data->cur_trans->speed_hz) {
dev_dbg(&data->master->dev, "%s:setting baud rate\n", __func__);
@@ -566,8 +601,8 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
/* set bits per word if needed */
if (data->cur_trans->bits_per_word &&
- (data->current_msg->spi->bits_per_word !=\
- data->cur_trans->bits_per_word)) {
+ (data->current_msg->spi->bits_per_word !=\
+ data->cur_trans->bits_per_word)) {
dev_dbg(&data->master->dev, "%s:set bits per word\n", __func__);
pch_spi_set_bits_per_word(data->master,
data->cur_trans->bits_per_word);
@@ -641,9 +676,9 @@ static void pch_spi_set_tx(struct pch_spi_data *data, int *bpw,
data->transfer_active = true;
}
-static void pch_spi_nomore_transfer(struct pch_spi_data *data,
- struct spi_message *pmsg)
+static void pch_spi_nomore_transfer(struct pch_spi_data *data)
{
+ struct spi_message *pmsg;
dev_dbg(&data->master->dev, "%s called\n", __func__);
/* Invoke complete callback
* [To the spi core..indicating end of transfer] */
@@ -694,30 +729,21 @@ static void pch_spi_nomore_transfer(struct pch_spi_data *data,
static void pch_spi_set_ir(struct pch_spi_data *data)
{
- /* enable interrupts */
- if ((data->bpw_len) > PCH_MAX_FIFO_DEPTH) {
+ /* enable interrupts, set threshold, enable SPI */
+ if ((data->bpw_len) > PCH_MAX_FIFO_DEPTH)
/* set receive threshold to PCH_RX_THOLD */
pch_spi_setclr_reg(data->master, PCH_SPCR,
- PCH_RX_THOLD << SPCR_RFIC_FIELD,
- ~MASK_RFIC_SPCR_BITS);
- /* enable FI and RFI interrupts */
- pch_spi_setclr_reg(data->master, PCH_SPCR,
- SPCR_RFIE_BIT | SPCR_FIE_BIT, 0);
- } else {
+ PCH_RX_THOLD << SPCR_RFIC_FIELD |
+ SPCR_FIE_BIT | SPCR_RFIE_BIT |
+ SPCR_ORIE_BIT | SPCR_SPE_BIT,
+ MASK_RFIC_SPCR_BITS | PCH_ALL);
+ else
/* set receive threshold to maximum */
pch_spi_setclr_reg(data->master, PCH_SPCR,
- PCH_RX_THOLD_MAX << SPCR_TFIC_FIELD,
- ~MASK_TFIC_SPCR_BITS);
- /* enable FI interrupt */
- pch_spi_setclr_reg(data->master, PCH_SPCR, SPCR_FIE_BIT, 0);
- }
-
- dev_dbg(&data->master->dev,
- "%s:invoking pch_spi_set_enable to enable SPI\n", __func__);
-
- /* SPI set enable */
- pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, SPCR_SPE_BIT,
- 0);
+ PCH_RX_THOLD_MAX << SPCR_RFIC_FIELD |
+ SPCR_FIE_BIT | SPCR_ORIE_BIT |
+ SPCR_SPE_BIT,
+ MASK_RFIC_SPCR_BITS | PCH_ALL);
/* Wait until the transfer completes; go to sleep after
initiating the transfer. */
@@ -730,15 +756,13 @@ static void pch_spi_set_ir(struct pch_spi_data *data)
dev_dbg(&data->master->dev,
"%s:no more control over SSN-writing 0 to SSNXCR.", __func__);
- data->transfer_active = false;
- dev_dbg(&data->master->dev,
- "%s set data->transfer_active = false\n", __func__);
-
/* clear all interrupts */
pch_spi_writereg(data->master, PCH_SPSR,
pch_spi_readreg(data->master, PCH_SPSR));
- /* disable interrupts */
- pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL);
+ /* Disable interrupts and SPI transfer */
+ pch_spi_setclr_reg(data->master, PCH_SPCR, 0, PCH_ALL | SPCR_SPE_BIT);
+ /* clear FIFO */
+ pch_spi_clear_fifo(data->master);
}
static void pch_spi_copy_rx_data(struct pch_spi_data *data, int bpw)
@@ -762,6 +786,328 @@ static void pch_spi_copy_rx_data(struct pch_spi_data *data, int bpw)
}
}
+static void pch_spi_copy_rx_data_for_dma(struct pch_spi_data *data, int bpw)
+{
+ int j;
+ u8 *rx_buf;
+ u16 *rx_sbuf;
+ const u8 *rx_dma_buf;
+ const u16 *rx_dma_sbuf;
+
+ /* copy Rx Data */
+ if (!data->cur_trans->rx_buf)
+ return;
+
+ if (bpw == 8) {
+ rx_buf = data->cur_trans->rx_buf;
+ rx_dma_buf = data->dma.rx_buf_virt;
+ for (j = 0; j < data->bpw_len; j++)
+ *rx_buf++ = *rx_dma_buf++ & 0xFF;
+ } else {
+ rx_sbuf = data->cur_trans->rx_buf;
+ rx_dma_sbuf = data->dma.rx_buf_virt;
+ for (j = 0; j < data->bpw_len; j++)
+ *rx_sbuf++ = *rx_dma_sbuf++;
+ }
+}
+
+static void pch_spi_start_transfer(struct pch_spi_data *data)
+{
+ struct pch_spi_dma_ctrl *dma;
+ unsigned long flags;
+
+ dma = &data->dma;
+
+ spin_lock_irqsave(&data->lock, flags);
+
+ /* disable interrupts, SPI set enable */
+ pch_spi_setclr_reg(data->master, PCH_SPCR, SPCR_SPE_BIT, PCH_ALL);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ /* Wait until the transfer completes; go to sleep after
+ initiating the transfer. */
+ dev_dbg(&data->master->dev,
+ "%s:waiting for transfer to get over\n", __func__);
+ wait_event_interruptible(data->wait, data->transfer_complete);
+
+ dma_sync_sg_for_cpu(&data->master->dev, dma->sg_rx_p, dma->nent,
+ DMA_FROM_DEVICE);
+ async_tx_ack(dma->desc_rx);
+ async_tx_ack(dma->desc_tx);
+ kfree(dma->sg_tx_p);
+ kfree(dma->sg_rx_p);
+
+ spin_lock_irqsave(&data->lock, flags);
+ pch_spi_writereg(data->master, PCH_SSNXCR, SSN_NO_CONTROL);
+ dev_dbg(&data->master->dev,
+ "%s:no more control over SSN-writing 0 to SSNXCR.", __func__);
+
+ /* clear fifo threshold, disable interrupts, disable SPI transfer */
+ pch_spi_setclr_reg(data->master, PCH_SPCR, 0,
+ MASK_RFIC_SPCR_BITS | MASK_TFIC_SPCR_BITS | PCH_ALL |
+ SPCR_SPE_BIT);
+ /* clear all interrupts */
+ pch_spi_writereg(data->master, PCH_SPSR,
+ pch_spi_readreg(data->master, PCH_SPSR));
+ /* clear FIFO */
+ pch_spi_clear_fifo(data->master);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static void pch_dma_rx_complete(void *arg)
+{
+ struct pch_spi_data *data = arg;
+
+ /* transfer is completed;inform pch_spi_process_messages_dma */
+ data->transfer_complete = true;
+ wake_up_interruptible(&data->wait);
+}
+
+static bool pch_spi_filter(struct dma_chan *chan, void *slave)
+{
+ struct pch_dma_slave *param = slave;
+
+ if ((chan->chan_id == param->chan_id) &&
+ (param->dma_dev == chan->device->dev)) {
+ chan->private = param;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void pch_spi_request_dma(struct pch_spi_data *data, int bpw)
+{
+ dma_cap_mask_t mask;
+ struct dma_chan *chan;
+ struct pci_dev *dma_dev;
+ struct pch_dma_slave *param;
+ struct pch_spi_dma_ctrl *dma;
+ unsigned int width;
+
+ if (bpw == 8)
+ width = PCH_DMA_WIDTH_1_BYTE;
+ else
+ width = PCH_DMA_WIDTH_2_BYTES;
+
+ dma = &data->dma;
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ /* Get DMA's dev information */
+ dma_dev = pci_get_bus_and_slot(2, PCI_DEVFN(12, 0));
+
+ /* Set Tx DMA */
+ param = &dma->param_tx;
+ param->dma_dev = &dma_dev->dev;
+ param->chan_id = data->master->bus_num * 2; /* Tx = 0, 2 */
+ param->tx_reg = data->io_base_addr + PCH_SPDWR;
+ param->width = width;
+ chan = dma_request_channel(mask, pch_spi_filter, param);
+ if (!chan) {
+ dev_err(&data->master->dev,
+ "ERROR: dma_request_channel FAILS(Tx)\n");
+ data->use_dma = 0;
+ return;
+ }
+ dma->chan_tx = chan;
+
+ /* Set Rx DMA */
+ param = &dma->param_rx;
+ param->dma_dev = &dma_dev->dev;
+ param->chan_id = data->master->bus_num * 2 + 1; /* Rx = Tx + 1 */
+ param->rx_reg = data->io_base_addr + PCH_SPDRR;
+ param->width = width;
+ chan = dma_request_channel(mask, pch_spi_filter, param);
+ if (!chan) {
+ dev_err(&data->master->dev,
+ "ERROR: dma_request_channel FAILS(Rx)\n");
+ dma_release_channel(dma->chan_tx);
+ dma->chan_tx = NULL;
+ data->use_dma = 0;
+ return;
+ }
+ dma->chan_rx = chan;
+}
+
+static void pch_spi_release_dma(struct pch_spi_data *data)
+{
+ struct pch_spi_dma_ctrl *dma;
+
+ dma = &data->dma;
+ if (dma->chan_tx) {
+ dma_release_channel(dma->chan_tx);
+ dma->chan_tx = NULL;
+ }
+ if (dma->chan_rx) {
+ dma_release_channel(dma->chan_rx);
+ dma->chan_rx = NULL;
+ }
+ return;
+}
+
+static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw)
+{
+ const u8 *tx_buf;
+ const u16 *tx_sbuf;
+ u8 *tx_dma_buf;
+ u16 *tx_dma_sbuf;
+ struct scatterlist *sg;
+ struct dma_async_tx_descriptor *desc_tx;
+ struct dma_async_tx_descriptor *desc_rx;
+ int num;
+ int i;
+ int size;
+ int rem;
+ unsigned long flags;
+ struct pch_spi_dma_ctrl *dma;
+
+ dma = &data->dma;
+
+ /* set baud rate if needed */
+ if (data->cur_trans->speed_hz) {
+ dev_dbg(&data->master->dev, "%s:setting baud rate\n", __func__);
+ spin_lock_irqsave(&data->lock, flags);
+ pch_spi_set_baud_rate(data->master, data->cur_trans->speed_hz);
+ spin_unlock_irqrestore(&data->lock, flags);
+ }
+
+ /* set bits per word if needed */
+ if (data->cur_trans->bits_per_word &&
+ (data->current_msg->spi->bits_per_word !=
+ data->cur_trans->bits_per_word)) {
+ dev_dbg(&data->master->dev, "%s:set bits per word\n", __func__);
+ spin_lock_irqsave(&data->lock, flags);
+ pch_spi_set_bits_per_word(data->master,
+ data->cur_trans->bits_per_word);
+ spin_unlock_irqrestore(&data->lock, flags);
+ *bpw = data->cur_trans->bits_per_word;
+ } else {
+ *bpw = data->current_msg->spi->bits_per_word;
+ }
+ data->bpw_len = data->cur_trans->len / (*bpw / 8);
+
+ /* copy Tx Data */
+ if (data->cur_trans->tx_buf != NULL) {
+ if (*bpw == 8) {
+ tx_buf = data->cur_trans->tx_buf;
+ tx_dma_buf = dma->tx_buf_virt;
+ for (i = 0; i < data->bpw_len; i++)
+ *tx_dma_buf++ = *tx_buf++;
+ } else {
+ tx_sbuf = data->cur_trans->tx_buf;
+ tx_dma_sbuf = dma->tx_buf_virt;
+ for (i = 0; i < data->bpw_len; i++)
+ *tx_dma_sbuf++ = *tx_sbuf++;
+ }
+ }
+ if (data->bpw_len > PCH_DMA_TRANS_SIZE) {
+ num = data->bpw_len / PCH_DMA_TRANS_SIZE + 1;
+ size = PCH_DMA_TRANS_SIZE;
+ rem = data->bpw_len % PCH_DMA_TRANS_SIZE;
+ } else {
+ num = 1;
+ size = data->bpw_len;
+ rem = data->bpw_len;
+ }
+ dev_dbg(&data->master->dev, "%s num=%d size=%d rem=%d\n",
+ __func__, num, size, rem);
+ spin_lock_irqsave(&data->lock, flags);
+
+ /* set receive fifo threshold and transmit fifo threshold */
+ pch_spi_setclr_reg(data->master, PCH_SPCR,
+ ((size - 1) << SPCR_RFIC_FIELD) |
+ ((PCH_MAX_FIFO_DEPTH - PCH_DMA_TRANS_SIZE) <<
+ SPCR_TFIC_FIELD),
+ MASK_RFIC_SPCR_BITS | MASK_TFIC_SPCR_BITS);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ /* RX */
+ dma->sg_rx_p = kzalloc(sizeof(struct scatterlist)*num, GFP_ATOMIC);
+ sg_init_table(dma->sg_rx_p, num); /* Initialize SG table */
+ /* offset, length setting */
+ sg = dma->sg_rx_p;
+ for (i = 0; i < num; i++, sg++) {
+ if (i == 0) {
+ sg->offset = 0;
+ sg_set_page(sg, virt_to_page(dma->rx_buf_virt), rem,
+ sg->offset);
+ sg_dma_len(sg) = rem;
+ } else {
+ sg->offset = rem + size * (i - 1);
+ sg->offset = sg->offset * (*bpw / 8);
+ sg_set_page(sg, virt_to_page(dma->rx_buf_virt), size,
+ sg->offset);
+ sg_dma_len(sg) = size;
+ }
+ sg_dma_address(sg) = dma->rx_buf_dma + sg->offset;
+ }
+ sg = dma->sg_rx_p;
+ desc_rx = dma->chan_rx->device->device_prep_slave_sg(dma->chan_rx, sg,
+ num, DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_rx) {
+ dev_err(&data->master->dev, "%s:device_prep_slave_sg Failed\n",
+ __func__);
+ return;
+ }
+ dma_sync_sg_for_device(&data->master->dev, sg, num, DMA_FROM_DEVICE);
+ desc_rx->callback = pch_dma_rx_complete;
+ desc_rx->callback_param = data;
+ dma->nent = num;
+ dma->desc_rx = desc_rx;
+
+ /* TX */
+ dma->sg_tx_p = kzalloc(sizeof(struct scatterlist)*num, GFP_ATOMIC);
+ sg_init_table(dma->sg_tx_p, num); /* Initialize SG table */
+ /* offset, length setting */
+ sg = dma->sg_tx_p;
+ for (i = 0; i < num; i++, sg++) {
+ if (i == 0) {
+ sg->offset = 0;
+ sg_set_page(sg, virt_to_page(dma->tx_buf_virt), rem,
+ sg->offset);
+ sg_dma_len(sg) = rem;
+ } else {
+ sg->offset = rem + size * (i - 1);
+ sg->offset = sg->offset * (*bpw / 8);
+ sg_set_page(sg, virt_to_page(dma->tx_buf_virt), size,
+ sg->offset);
+ sg_dma_len(sg) = size;
+ }
+ sg_dma_address(sg) = dma->tx_buf_dma + sg->offset;
+ }
+ sg = dma->sg_tx_p;
+ desc_tx = dma->chan_tx->device->device_prep_slave_sg(dma->chan_tx,
+ sg, num, DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_tx) {
+ dev_err(&data->master->dev, "%s:device_prep_slave_sg Failed\n",
+ __func__);
+ return;
+ }
+ dma_sync_sg_for_device(&data->master->dev, sg, num, DMA_TO_DEVICE);
+ desc_tx->callback = NULL;
+ desc_tx->callback_param = data;
+ dma->nent = num;
+ dma->desc_tx = desc_tx;
+
+ dev_dbg(&data->master->dev, "\n%s:Pulling down SSN low - writing "
+ "0x2 to SSNXCR\n", __func__);
+
+ spin_lock_irqsave(&data->lock, flags);
+ pch_spi_writereg(data->master, PCH_SSNXCR, SSN_LOW);
+ desc_rx->tx_submit(desc_rx);
+ desc_tx->tx_submit(desc_tx);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ /* reset transfer complete flag */
+ data->transfer_complete = false;
+}
+
static void pch_spi_process_messages(struct work_struct *pwork)
{
struct spi_message *pmsg;
@@ -772,30 +1118,23 @@ static void pch_spi_process_messages(struct work_struct *pwork)
dev_dbg(&data->master->dev, "%s data initialized\n", __func__);
spin_lock(&data->lock);
-
/* check if suspend has been initiated;if yes flush queue */
if (data->board_dat->suspend_sts || (data->status == STATUS_EXITING)) {
- dev_dbg(&data->master->dev,
- "%s suspend/remove initiated,flushing queue\n",
- __func__);
-
+ dev_dbg(&data->master->dev, "%s suspend/remove initiated,"
+ "flushing queue\n", __func__);
list_for_each_entry(pmsg, data->queue.next, queue) {
pmsg->status = -EIO;
-
if (pmsg->complete != 0) {
spin_unlock(&data->lock);
pmsg->complete(pmsg->context);
spin_lock(&data->lock);
}
-
/* delete from queue */
list_del_init(&pmsg->queue);
}
-
spin_unlock(&data->lock);
return;
}
-
data->bcurrent_msg_processing = true;
dev_dbg(&data->master->dev,
"%s Set data->bcurrent_msg_processing= true\n", __func__);
@@ -803,62 +1142,47 @@ static void pch_spi_process_messages(struct work_struct *pwork)
/* Get the message from the queue and delete it from there. */
data->current_msg = list_entry(data->queue.next, struct spi_message,
queue);
-
list_del_init(&data->current_msg->queue);
-
data->current_msg->status = 0;
-
pch_spi_select_chip(data, data->current_msg->spi);
-
spin_unlock(&data->lock);
+ if (data->use_dma)
+ pch_spi_request_dma(data,
+ data->current_msg->spi->bits_per_word);
do {
/* If we are already processing a message get the next
transfer structure from the message otherwise retrieve
the 1st transfer request from the message. */
spin_lock(&data->lock);
-
if (data->cur_trans == NULL) {
data->cur_trans =
- list_entry(data->current_msg->transfers.
- next, struct spi_transfer,
- transfer_list);
- dev_dbg(&data->master->dev,
- "%s :Getting 1st transfer message\n", __func__);
+ list_entry(data->current_msg->transfers.next,
+ struct spi_transfer, transfer_list);
+ dev_dbg(&data->master->dev, "%s "
+ ":Getting 1st transfer message\n", __func__);
} else {
data->cur_trans =
- list_entry(data->cur_trans->transfer_list.next,
- struct spi_transfer,
- transfer_list);
- dev_dbg(&data->master->dev,
- "%s :Getting next transfer message\n",
- __func__);
+ list_entry(data->cur_trans->transfer_list.next,
+ struct spi_transfer, transfer_list);
+ dev_dbg(&data->master->dev, "%s "
+ ":Getting next transfer message\n", __func__);
}
-
spin_unlock(&data->lock);
- pch_spi_set_tx(data, &bpw, &pmsg);
-
- /* Control interrupt*/
- pch_spi_set_ir(data);
-
- /* Disable SPI transfer */
- pch_spi_setclr_reg(data->current_chip->master, PCH_SPCR, 0,
- SPCR_SPE_BIT);
-
- /* clear FIFO */
- pch_spi_clear_fifo(data->master);
-
- /* copy Rx Data */
- pch_spi_copy_rx_data(data, bpw);
-
- /* free memory */
- kfree(data->pkt_rx_buff);
- data->pkt_rx_buff = NULL;
-
- kfree(data->pkt_tx_buff);
- data->pkt_tx_buff = NULL;
-
+ if (data->use_dma) {
+ pch_spi_handle_dma(data, &bpw);
+ pch_spi_start_transfer(data);
+ pch_spi_copy_rx_data_for_dma(data, bpw);
+ } else {
+ pch_spi_set_tx(data, &bpw);
+ pch_spi_set_ir(data);
+ pch_spi_copy_rx_data(data, bpw);
+ kfree(data->pkt_rx_buff);
+ data->pkt_rx_buff = NULL;
+ kfree(data->pkt_tx_buff);
+ data->pkt_tx_buff = NULL;
+ }
/* increment message count */
data->current_msg->actual_length += data->cur_trans->len;
@@ -875,16 +1199,17 @@ static void pch_spi_process_messages(struct work_struct *pwork)
}
spin_lock(&data->lock);
-
/* No more transfer in this message. */
if ((data->cur_trans->transfer_list.next) ==
&(data->current_msg->transfers)) {
- pch_spi_nomore_transfer(data, pmsg);
+ pch_spi_nomore_transfer(data);
}
-
spin_unlock(&data->lock);
} while (data->cur_trans != NULL);
+
+ if (data->use_dma)
+ pch_spi_release_dma(data);
}
static void pch_spi_free_resources(struct pch_spi_board_data *board_dat,
@@ -911,6 +1236,7 @@ static int pch_spi_get_resources(struct pch_spi_board_data *board_dat,
/* create workqueue */
data->wk = create_singlethread_workqueue(KBUILD_MODNAME);
+
if (!data->wk) {
dev_err(&board_dat->pdev->dev,
"%s create_singlet hread_workqueue failed\n", __func__);
@@ -937,6 +1263,35 @@ err_return:
return retval;
}
+static void pch_free_dma_buf(struct pch_spi_board_data *board_dat,
+ struct pch_spi_data *data)
+{
+ struct pch_spi_dma_ctrl *dma;
+
+ dma = &data->dma;
+ if (dma->tx_buf_dma)
+ dma_free_coherent(&board_dat->pdev->dev, PCH_BUF_SIZE,
+ dma->tx_buf_virt, dma->tx_buf_dma);
+ if (dma->rx_buf_dma)
+ dma_free_coherent(&board_dat->pdev->dev, PCH_BUF_SIZE,
+ dma->rx_buf_virt, dma->rx_buf_dma);
+ return;
+}
+
+static void pch_alloc_dma_buf(struct pch_spi_board_data *board_dat,
+ struct pch_spi_data *data)
+{
+ struct pch_spi_dma_ctrl *dma;
+
+ dma = &data->dma;
+ /* Get Consistent memory for Tx DMA */
+ dma->tx_buf_virt = dma_alloc_coherent(&board_dat->pdev->dev,
+ PCH_BUF_SIZE, &dma->tx_buf_dma, GFP_KERNEL);
+ /* Get Consistent memory for Rx DMA */
+ dma->rx_buf_virt = dma_alloc_coherent(&board_dat->pdev->dev,
+ PCH_BUF_SIZE, &dma->rx_buf_dma, GFP_KERNEL);
+}
+
static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
{
int ret;
@@ -944,6 +1299,8 @@ static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
struct pch_spi_data *data;
+ dev_dbg(&plat_dev->dev, "%s:debug\n", __func__);
+
master = spi_alloc_master(&board_dat->pdev->dev,
sizeof(struct pch_spi_data));
if (!master) {
@@ -957,9 +1314,11 @@ static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
platform_set_drvdata(plat_dev, data);
- /* baseaddress + 0x20(offset) */
+ /* baseaddress + address offset) */
+ data->io_base_addr = pci_resource_start(board_dat->pdev, 1) +
+ PCH_ADDRESS_SIZE * plat_dev->id;
data->io_remap_addr = pci_iomap(board_dat->pdev, 1, 0) +
- 0x20 * plat_dev->id;
+ PCH_ADDRESS_SIZE * plat_dev->id;
if (!data->io_remap_addr) {
dev_err(&plat_dev->dev, "%s pci_iomap failed\n", __func__);
ret = -ENOMEM;
@@ -980,6 +1339,7 @@ static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
data->n_curnt_chip = 255;
data->status = STATUS_RUNNING;
data->ch = plat_dev->id;
+ data->use_dma = use_dma;
INIT_LIST_HEAD(&data->queue);
spin_lock_init(&data->lock);
@@ -1010,6 +1370,11 @@ static int __devinit pch_spi_pd_probe(struct platform_device *plat_dev)
goto err_spi_register_master;
}
+ if (use_dma) {
+ dev_info(&plat_dev->dev, "Use DMA for data transfers\n");
+ pch_alloc_dma_buf(board_dat, data);
+ }
+
return 0;
err_spi_register_master:
@@ -1029,22 +1394,27 @@ static int __devexit pch_spi_pd_remove(struct platform_device *plat_dev)
struct pch_spi_board_data *board_dat = dev_get_platdata(&plat_dev->dev);
struct pch_spi_data *data = platform_get_drvdata(plat_dev);
int count;
+ unsigned long flags;
dev_dbg(&plat_dev->dev, "%s:[ch%d] irq=%d\n",
__func__, plat_dev->id, board_dat->pdev->irq);
+
+ if (use_dma)
+ pch_free_dma_buf(board_dat, data);
+
/* check for any pending messages; no action is taken if the queue
* is still full; but at least we tried. Unload anyway */
count = 500;
- spin_lock(&data->lock);
+ spin_lock_irqsave(&data->lock, flags);
data->status = STATUS_EXITING;
while ((list_empty(&data->queue) == 0) && --count) {
dev_dbg(&board_dat->pdev->dev, "%s :queue not empty\n",
__func__);
- spin_unlock(&data->lock);
+ spin_unlock_irqrestore(&data->lock, flags);
msleep(PCH_SLEEP_TIME);
- spin_lock(&data->lock);
+ spin_lock_irqsave(&data->lock, flags);
}
- spin_unlock(&data->lock);
+ spin_unlock_irqrestore(&data->lock, flags);
pch_spi_free_resources(board_dat, data);
/* disable interrupts & free IRQ */
@@ -1081,8 +1451,8 @@ static int pch_spi_pd_suspend(struct platform_device *pd_dev,
/* check if the current message is processed:
Only after thats done the transfer will be suspended */
count = 255;
- while ((--count) > 0)
- if (!(data->bcurrent_msg_processing)) {
+ while ((--count) > 0) {
+ if (!(data->bcurrent_msg_processing))
break;
msleep(PCH_SLEEP_TIME);
}
@@ -1123,7 +1493,6 @@ static int pch_spi_pd_resume(struct platform_device *pd_dev)
"%s request_irq failed\n", __func__);
return retval;
}
-
/* reset PCH SPI h/w */
pch_spi_reset(data->master);
pch_spi_set_master_mode(data->master);
@@ -1213,7 +1582,6 @@ static int __devinit pch_spi_probe(struct pci_dev *pdev,
}
pci_set_drvdata(pdev, pd_dev_save);
-
return 0;
err_platform_device:
@@ -1326,5 +1694,9 @@ static void __exit pch_spi_exit(void)
}
module_exit(pch_spi_exit);
+module_param(use_dma, int, 0644);
+MODULE_PARM_DESC(use_dma,
+ "to use DMA for data transfers pass 1 else 0; default 1");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel EG20T PCH/OKI SEMICONDUCTOR ML7213 IOH SPI Driver");
--
1.7.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1] perf tools: elf_sym__is_function should accept "zero" sized functions
[not found] <y>
2009-01-01 23:29 ` [PATCH/RESEND v4 1/2] spi_topcliff_pch: support new device ML7213 IOH y
2009-01-01 23:29 ` [PATCH/RESEND 2/2] spi_topcliff_pch: DMA support y
@ 2009-10-06 2:35 ` Arnaldo Carvalho de Melo
2009-10-07 14:26 ` [PATCH 1/1] perf tools: Install the docs by default Arnaldo Carvalho de Melo
` (157 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-06 2:35 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo,
Frédéric Weisbecker, Peter Zijlstra, Mike Galbraith
Asm routines that end up having size equal to zero are not really zero
sized, and as now we do kernel_maps__fixup_sym_end, at least for kernel
routines this gets fixed.
A similar fixup needs to be done for the userspace bits as well, but as
this fixup started only because in /proc/kallsyms we don't have the end
address nor the function size, it appeared here first.
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Mike Galbraith <efault@gmx.de>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/symbol.c | 3 +--
1 files changed, 1 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index e3eebdd..582ce72 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -396,8 +396,7 @@ static inline int elf_sym__is_function(const GElf_Sym *sym)
{
return elf_sym__type(sym) == STT_FUNC &&
sym->st_name != 0 &&
- sym->st_shndx != SHN_UNDEF &&
- sym->st_size != 0;
+ sym->st_shndx != SHN_UNDEF;
}
static inline int elf_sym__is_label(const GElf_Sym *sym)
--
1.6.2.5
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1] perf tools: Install the docs by default
[not found] <y>
` (2 preceding siblings ...)
2009-10-06 2:35 ` [PATCH 1/1] perf tools: elf_sym__is_function should accept "zero" sized functions Arnaldo Carvalho de Melo
@ 2009-10-07 14:26 ` Arnaldo Carvalho de Melo
2009-10-08 17:26 ` Ingo Molnar
2009-10-07 16:48 ` [PATCH 1/1] perf tools: Improve kernel/modules symbol lookup Arnaldo Carvalho de Melo
` (156 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-07 14:26 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith, Jesse Brandeburg
So that we get matching docs when installing a new version of the tools.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Jesse Brandeburg <jesse.brandeburg@gmail.com>
Suggested-by: Jesse Brandeburg <jesse.brandeburg@gmail.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/Makefile | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 5a42996..e714bd1 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -895,6 +895,7 @@ ifneq (,$X)
$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) perf$X)), $(RM) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$p';)
endif
endif
+ $(MAKE) -C Documentation install
install-doc:
$(MAKE) -C Documentation install
--
1.6.2.5
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 1/1] perf tools: Install the docs by default
2009-10-07 14:26 ` [PATCH 1/1] perf tools: Install the docs by default Arnaldo Carvalho de Melo
@ 2009-10-08 17:26 ` Ingo Molnar
0 siblings, 0 replies; 743+ messages in thread
From: Ingo Molnar @ 2009-10-08 17:26 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: linux-kernel, Frederic Weisbecker, Peter Zijlstra,
Paul Mackerras, Mike Galbraith, Jesse Brandeburg
* Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> So that we get matching docs when installing a new version of the tools.
>
> Cc: Frederic Weisbecker <fweisbec@gmail.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Paul Mackerras <paulus@samba.org>
> Cc: Mike Galbraith <efault@gmx.de>
> Cc: Paul Mackerras <paulus@samba.org>
> Cc: Jesse Brandeburg <jesse.brandeburg@gmail.com>
> Suggested-by: Jesse Brandeburg <jesse.brandeburg@gmail.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> ---
> tools/perf/Makefile | 1 +
> 1 files changed, 1 insertions(+), 0 deletions(-)
>
> diff --git a/tools/perf/Makefile b/tools/perf/Makefile
> index 5a42996..e714bd1 100644
> --- a/tools/perf/Makefile
> +++ b/tools/perf/Makefile
> @@ -895,6 +895,7 @@ ifneq (,$X)
> $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) perf$X)), $(RM) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$p';)
> endif
> endif
> + $(MAKE) -C Documentation install
>
> install-doc:
> $(MAKE) -C Documentation install
hm, i didnt want to do that because many systems dont have asciidoc and
xmlto installed by default.
We can still do it, if you can auto-detect these two new tools and emit
a warning (and dont build+install) documentation if they are missing -
instead of failing the build.
Ingo
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1] perf tools: Improve kernel/modules symbol lookup
[not found] <y>
` (3 preceding siblings ...)
2009-10-07 14:26 ` [PATCH 1/1] perf tools: Install the docs by default Arnaldo Carvalho de Melo
@ 2009-10-07 16:48 ` Arnaldo Carvalho de Melo
2009-10-08 17:31 ` [tip:perf/core] " tip-bot for Arnaldo Carvalho de Melo
2009-10-11 16:40 ` [PATCH 1/1] perf tools: Move threads & last_match to threads.c Arnaldo Carvalho de Melo
` (155 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-07 16:48 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith
This removes the ovelapping of vmlinux addresses with modules, using the
ELF section name when using --vmlinux and creating a unique DSO name
when using /proc/kallsyms ([kernel].N).
This is done by creating multiple 'struct map' instances for address
ranges backed by DSOs that have just the symbols for that range and a
name that is derived from the ELF section name.o
Now it is possible to ask for just the symbols in some particular kernel
section:
$ perf report -m --vmlinux ../build/tip-recvmmsg/vmlinux \
--dsos [kernel].vsyscall_fn | head -15
52.73% Xorg [.] vread_hpet
18.61% firefox [.] vread_hpet
14.50% npviewer.bin [.] vread_hpet
6.83% compiz [.] vread_hpet
5.73% glxgears [.] vread_hpet
0.63% java [.] vread_hpet
0.30% gnome-terminal [.] vread_hpet
0.23% perf [.] vread_hpet
0.18% xchat [.] vread_hpet
$
Now we don't have to first lookup the list of modules and then, if it
fails, vmlinux symbols, its just a simple lookup for the map then the
symbols, just like for threads.
Reports generated using /proc/kallsyms and --vmlinux should provide the
same results, modulo the DSO name for sections other than ".text".
But they don't right now because things like:
ffffffff81011c20-ffffffff81012068 system_call
ffffffff81011c30-ffffffff81011c9b system_call_after_swapgs
ffffffff81011c9c-ffffffff81011cb6 system_call_fastpath
ffffffff81011cb7-ffffffff81011cbb ret_from_sys_call
I.e. overlapping symbols, again some ASM special case that we have to
fixup.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/symbol.c | 288 ++++++++++++++++++++++++++++++++--------------
1 files changed, 201 insertions(+), 87 deletions(-)
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index a6887f9..faa84f5 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -26,27 +26,35 @@ enum dso_origin {
static void dsos__add(struct dso *dso);
static struct dso *dsos__find(const char *name);
+static struct map *map__new2(u64 start, struct dso *dso);
+static void kernel_maps__insert(struct map *map);
static struct rb_root kernel_maps;
-static void dso__set_symbols_end(struct dso *self)
+static void dso__fixup_sym_end(struct dso *self)
{
struct rb_node *nd, *prevnd = rb_first(&self->syms);
+ struct symbol *curr, *prev;
if (prevnd == NULL)
return;
+ curr = rb_entry(prevnd, struct symbol, rb_node);
+
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
- struct symbol *prev = rb_entry(prevnd, struct symbol, rb_node),
- *curr = rb_entry(nd, struct symbol, rb_node);
+ prev = curr;
+ curr = rb_entry(nd, struct symbol, rb_node);
if (prev->end == prev->start)
prev->end = curr->start - 1;
- prevnd = nd;
}
+
+ /* Last entry */
+ if (curr->end == curr->start)
+ curr->end = roundup(curr->start, 4096);
}
-static void kernel_maps__fixup_sym_end(void)
+static void kernel_maps__fixup_end(void)
{
struct map *prev, *curr;
struct rb_node *nd, *prevnd = rb_first(&kernel_maps);
@@ -55,13 +63,17 @@ static void kernel_maps__fixup_sym_end(void)
return;
curr = rb_entry(prevnd, struct map, rb_node);
- dso__set_symbols_end(curr->dso);
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
prev = curr;
curr = rb_entry(nd, struct map, rb_node);
prev->end = curr->start - 1;
- dso__set_symbols_end(curr->dso);
+ }
+
+ nd = rb_last(&curr->dso->syms);
+ if (nd) {
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ curr->end = sym->end;
}
}
@@ -200,13 +212,16 @@ size_t dso__fprintf(struct dso *self, FILE *fp)
return ret;
}
-static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
+/*
+ * Loads the function entries in /proc/kallsyms into kernel_map->dso,
+ * so that we can in the next step set the symbol ->end address and then
+ * call kernel_maps__split_kallsyms.
+ */
+static int kernel_maps__load_all_kallsyms(int v)
{
- struct map *map = kernel_map;
char *line = NULL;
size_t n;
FILE *file = fopen("/proc/kallsyms", "r");
- int count = 0;
if (file == NULL)
goto out_failure;
@@ -216,7 +231,7 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
struct symbol *sym;
int line_len, len;
char symbol_type;
- char *module, *symbol_name;
+ char *symbol_name;
line_len = getline(&line, &n, file);
if (line_len < 0)
@@ -241,20 +256,55 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
continue;
symbol_name = line + len + 2;
- module = strchr(symbol_name, '\t');
- if (module) {
- char *module_name_end;
+ /*
+ * Will fix up the end later, when we have all symbols sorted.
+ */
+ sym = symbol__new(start, 0, symbol_name,
+ kernel_map->dso->sym_priv_size, v);
+ if (sym == NULL)
+ goto out_delete_line;
+
+ dso__insert_symbol(kernel_map->dso, sym);
+ }
+
+ free(line);
+ fclose(file);
+
+ return 0;
+
+out_delete_line:
+ free(line);
+out_failure:
+ return -1;
+}
+
+/*
+ * Split the symbols into maps, making sure there are no overlaps, i.e. the
+ * kernel range is broken in several maps, named [kernel].N, as we don't have
+ * the original ELF section names vmlinux have.
+ */
+static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules)
+{
+ struct map *map = kernel_map;
+ struct symbol *pos;
+ int count = 0;
+ struct rb_node *next = rb_first(&kernel_map->dso->syms);
+ int kernel_range = 0;
+
+ while (next) {
+ char *module;
+
+ pos = rb_entry(next, struct symbol, rb_node);
+ next = rb_next(&pos->rb_node);
+
+ module = strchr(pos->name, '\t');
+ if (module) {
if (!use_modules)
- continue;
- *module = '\0';
- module = strchr(module + 1, '[');
- if (!module)
- continue;
- module_name_end = strchr(module + 1, ']');
- if (!module_name_end)
- continue;
- *(module_name_end + 1) = '\0';
+ goto delete_symbol;
+
+ *module++ = '\0';
+
if (strcmp(map->dso->name, module)) {
map = kernel_maps__find_by_dso_name(module);
if (!map) {
@@ -263,50 +313,77 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
return -1;
}
}
- start = map->map_ip(map, start);
- } else
- map = kernel_map;
- /*
- * Well fix up the end later, when we have all sorted.
- */
- sym = symbol__new(start, 0, symbol_name,
- map->dso->sym_priv_size, v);
+ /*
+ * So that we look just like we get from .ko files,
+ * i.e. not prelinked, relative to map->start.
+ */
+ pos->start = map->map_ip(map, pos->start);
+ pos->end = map->map_ip(map, pos->end);
+ } else if (map != kernel_map) {
+ char dso_name[PATH_MAX];
+ struct dso *dso;
+
+ snprintf(dso_name, sizeof(dso_name), "[kernel].%d",
+ kernel_range++);
+
+ dso = dso__new(dso_name,
+ kernel_map->dso->sym_priv_size);
+ if (dso == NULL)
+ return -1;
+
+ map = map__new2(pos->start, dso);
+ if (map == NULL) {
+ dso__delete(dso);
+ return -1;
+ }
- if (sym == NULL)
- goto out_delete_line;
+ map->map_ip = vdso__map_ip;
+ kernel_maps__insert(map);
+ ++kernel_range;
+ }
- if (filter && filter(map, sym))
- symbol__delete(sym, map->dso->sym_priv_size);
- else {
- dso__insert_symbol(map->dso, sym);
+ if (filter && filter(map, pos)) {
+delete_symbol:
+ rb_erase(&pos->rb_node, &kernel_map->dso->syms);
+ symbol__delete(pos, kernel_map->dso->sym_priv_size);
+ } else {
+ if (map != kernel_map) {
+ rb_erase(&pos->rb_node, &kernel_map->dso->syms);
+ dso__insert_symbol(map->dso, pos);
+ }
count++;
}
}
- free(line);
- fclose(file);
-
return count;
+}
-out_delete_line:
- free(line);
-out_failure:
- return -1;
+
+static int kernel_maps__load_kallsyms(symbol_filter_t filter,
+ int use_modules, int v)
+{
+ if (kernel_maps__load_all_kallsyms(v))
+ return -1;
+
+ dso__fixup_sym_end(kernel_map->dso);
+
+ return kernel_maps__split_kallsyms(filter, use_modules);
}
-static size_t kernel_maps__fprintf(FILE *fp)
+static size_t kernel_maps__fprintf(FILE *fp, int v)
{
size_t printed = fprintf(stderr, "Kernel maps:\n");
struct rb_node *nd;
- printed += map__fprintf(kernel_map, fp);
- printed += dso__fprintf(kernel_map->dso, fp);
-
for (nd = rb_first(&kernel_maps); nd; nd = rb_next(nd)) {
struct map *pos = rb_entry(nd, struct map, rb_node);
+ printed += fprintf(fp, "Map:");
printed += map__fprintf(pos, fp);
- printed += dso__fprintf(pos->dso, fp);
+ if (v > 1) {
+ printed += dso__fprintf(pos->dso, fp);
+ printed += fprintf(fp, "--\n");
+ }
}
return printed + fprintf(stderr, "END kernel maps\n");
@@ -594,6 +671,9 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
int fd, symbol_filter_t filter, int kernel,
int kmodule, int v)
{
+ struct map *curr_map = map;
+ struct dso *curr_dso = self;
+ size_t dso_name_len = strlen(self->short_name);
Elf_Data *symstrs, *secstrs;
uint32_t nr_syms;
int err = -1;
@@ -660,10 +740,9 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) {
struct symbol *f;
const char *elf_name;
- char *demangled;
+ char *demangled = NULL;
int is_label = elf_sym__is_label(&sym);
const char *section_name;
- u64 sh_offset = 0;
if (!is_label && !elf_sym__is_function(&sym))
continue;
@@ -677,14 +756,51 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
if (is_label && !elf_sec__is_text(&shdr, secstrs))
continue;
+ elf_name = elf_sym__name(&sym, symstrs);
section_name = elf_sec__name(&shdr, secstrs);
- if ((kernel || kmodule)) {
- if (strstr(section_name, ".init"))
- sh_offset = shdr.sh_offset;
+ if (kernel || kmodule) {
+ char dso_name[PATH_MAX];
+
+ if (strcmp(section_name,
+ curr_dso->short_name + dso_name_len) == 0)
+ goto new_symbol;
+
+ if (strcmp(section_name, ".text") == 0) {
+ curr_map = map;
+ curr_dso = self;
+ goto new_symbol;
+ }
+
+ snprintf(dso_name, sizeof(dso_name),
+ "%s%s", self->short_name, section_name);
+
+ curr_map = kernel_maps__find_by_dso_name(dso_name);
+ if (curr_map == NULL) {
+ u64 start = sym.st_value;
+
+ if (kmodule)
+ start += map->start + shdr.sh_offset;
+
+ curr_dso = dso__new(dso_name, self->sym_priv_size);
+ if (curr_dso == NULL)
+ goto out_elf_end;
+ curr_map = map__new2(start, curr_dso);
+ if (curr_map == NULL) {
+ dso__delete(curr_dso);
+ goto out_elf_end;
+ }
+ curr_map->map_ip = vdso__map_ip;
+ curr_dso->origin = DSO__ORIG_KERNEL;
+ kernel_maps__insert(curr_map);
+ dsos__add(curr_dso);
+ } else
+ curr_dso = curr_map->dso;
+
+ goto new_symbol;
}
- if (self->adjust_symbols) {
+ if (curr_dso->adjust_symbols) {
if (v > 2)
printf("adjusting symbol: st_value: %Lx sh_addr: %Lx sh_offset: %Lx\n",
(u64)sym.st_value, (u64)shdr.sh_addr, (u64)shdr.sh_offset);
@@ -696,25 +812,29 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
* DWARF DW_compile_unit has this, but we don't always have access
* to it...
*/
- elf_name = elf_sym__name(&sym, symstrs);
demangled = bfd_demangle(NULL, elf_name, DMGL_PARAMS | DMGL_ANSI);
if (demangled != NULL)
elf_name = demangled;
-
- f = symbol__new(sym.st_value + sh_offset, sym.st_size, elf_name,
- self->sym_priv_size, v);
+new_symbol:
+ f = symbol__new(sym.st_value, sym.st_size, elf_name,
+ curr_dso->sym_priv_size, v);
free(demangled);
if (!f)
goto out_elf_end;
- if (filter && filter(map, f))
- symbol__delete(f, self->sym_priv_size);
+ if (filter && filter(curr_map, f))
+ symbol__delete(f, curr_dso->sym_priv_size);
else {
- dso__insert_symbol(self, f);
+ dso__insert_symbol(curr_dso, f);
nr++;
}
}
+ /*
+ * For misannotated, zeroed, ASM function sizes.
+ */
+ if (nr > 0)
+ dso__fixup_sym_end(self);
err = nr;
out_elf_end:
elf_end(elf);
@@ -883,27 +1003,17 @@ static void kernel_maps__insert(struct map *map)
struct symbol *kernel_maps__find_symbol(u64 ip, struct map **mapp)
{
- /*
- * We can't have kernel_map in kernel_maps because it spans an address
- * space that includes the modules. The right way to fix this is to
- * create several maps, so that we don't have overlapping ranges with
- * modules. For now lets look first on the kernel dso.
- */
struct map *map = maps__find(&kernel_maps, ip);
- struct symbol *sym;
+
+ if (mapp)
+ *mapp = map;
if (map) {
ip = map->map_ip(map, ip);
- sym = map->dso->find_symbol(map->dso, ip);
- } else {
- map = kernel_map;
- sym = map->dso->find_symbol(map->dso, ip);
+ return map->dso->find_symbol(map->dso, ip);
}
- if (mapp)
- *mapp = map;
-
- return sym;
+ return NULL;
}
struct map *kernel_maps__find_by_dso_name(const char *name)
@@ -994,6 +1104,14 @@ static int dsos__load_modules_sym_dir(char *dirname,
last = rb_last(&map->dso->syms);
if (last) {
struct symbol *sym;
+ /*
+ * We do this here as well, even having the
+ * symbol size found in the symtab because
+ * misannotated ASM symbols may have the size
+ * set to zero.
+ */
+ dso__fixup_sym_end(map->dso);
+
sym = rb_entry(last, struct symbol, rb_node);
map->end = map->start + sym->end;
}
@@ -1163,17 +1281,11 @@ int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size,
}
if (err <= 0)
- err = maps__load_kallsyms(filter, use_modules, v);
+ err = kernel_maps__load_kallsyms(filter, use_modules, v);
if (err > 0) {
struct rb_node *node = rb_first(&dso->syms);
struct symbol *sym = rb_entry(node, struct symbol, rb_node);
- /*
- * Now that we have all sorted out, just set the ->end of all
- * symbols that still don't have it.
- */
- dso__set_symbols_end(dso);
- kernel_maps__fixup_sym_end();
kernel_map->start = sym->start;
node = rb_last(&dso->syms);
@@ -1181,14 +1293,16 @@ int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size,
kernel_map->end = sym->end;
dso->origin = DSO__ORIG_KERNEL;
+ kernel_maps__insert(kernel_map);
/*
- * XXX See kernel_maps__find_symbol comment
- * kernel_maps__insert(kernel_map)
+ * Now that we have all sorted out, just set the ->end of all
+ * maps:
*/
+ kernel_maps__fixup_end();
dsos__add(dso);
if (v > 0)
- kernel_maps__fprintf(stderr);
+ kernel_maps__fprintf(stderr, v);
}
return err;
--
1.6.2.5
^ permalink raw reply [flat|nested] 743+ messages in thread
* [tip:perf/core] perf tools: Improve kernel/modules symbol lookup
2009-10-07 16:48 ` [PATCH 1/1] perf tools: Improve kernel/modules symbol lookup Arnaldo Carvalho de Melo
@ 2009-10-08 17:31 ` tip-bot for Arnaldo Carvalho de Melo
0 siblings, 0 replies; 743+ messages in thread
From: tip-bot for Arnaldo Carvalho de Melo @ 2009-10-08 17:31 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, paulus, acme, hpa, mingo, efault, peterz, fweisbec,
tglx, mingo
Commit-ID: 2e538c4a1847291cf01218d4fe7bb4dc60fef7cf
Gitweb: http://git.kernel.org/tip/2e538c4a1847291cf01218d4fe7bb4dc60fef7cf
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
AuthorDate: Wed, 7 Oct 2009 13:48:56 -0300
Committer: Ingo Molnar <mingo@elte.hu>
CommitDate: Thu, 8 Oct 2009 19:27:11 +0200
perf tools: Improve kernel/modules symbol lookup
This removes the ovelapping of vmlinux addresses with modules,
using the ELF section name when using --vmlinux and creating a
unique DSO name when using /proc/kallsyms ([kernel].N).
This is done by creating multiple 'struct map' instances for
address ranges backed by DSOs that have just the symbols for that
range and a name that is derived from the ELF section name.o
Now it is possible to ask for just the symbols in some particular
kernel section:
$ perf report -m --vmlinux ../build/tip-recvmmsg/vmlinux \
--dsos [kernel].vsyscall_fn | head -15
52.73% Xorg [.] vread_hpet
18.61% firefox [.] vread_hpet
14.50% npviewer.bin [.] vread_hpet
6.83% compiz [.] vread_hpet
5.73% glxgears [.] vread_hpet
0.63% java [.] vread_hpet
0.30% gnome-terminal [.] vread_hpet
0.23% perf [.] vread_hpet
0.18% xchat [.] vread_hpet
$
Now we don't have to first lookup the list of modules and then, if
it fails, vmlinux symbols, its just a simple lookup for the map
then the symbols, just like for threads.
Reports generated using /proc/kallsyms and --vmlinux should provide
the same results, modulo the DSO name for sections other than
".text".
But they don't right now because things like:
ffffffff81011c20-ffffffff81012068 system_call
ffffffff81011c30-ffffffff81011c9b system_call_after_swapgs
ffffffff81011c9c-ffffffff81011cb6 system_call_fastpath
ffffffff81011cb7-ffffffff81011cbb ret_from_sys_call
I.e. overlapping symbols, again some ASM special case that we have
to fixup.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <1254934136-8503-1-git-send-email-acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
---
tools/perf/util/symbol.c | 288 ++++++++++++++++++++++++++++++++--------------
1 files changed, 201 insertions(+), 87 deletions(-)
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index a6887f9..faa84f5 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -26,27 +26,35 @@ enum dso_origin {
static void dsos__add(struct dso *dso);
static struct dso *dsos__find(const char *name);
+static struct map *map__new2(u64 start, struct dso *dso);
+static void kernel_maps__insert(struct map *map);
static struct rb_root kernel_maps;
-static void dso__set_symbols_end(struct dso *self)
+static void dso__fixup_sym_end(struct dso *self)
{
struct rb_node *nd, *prevnd = rb_first(&self->syms);
+ struct symbol *curr, *prev;
if (prevnd == NULL)
return;
+ curr = rb_entry(prevnd, struct symbol, rb_node);
+
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
- struct symbol *prev = rb_entry(prevnd, struct symbol, rb_node),
- *curr = rb_entry(nd, struct symbol, rb_node);
+ prev = curr;
+ curr = rb_entry(nd, struct symbol, rb_node);
if (prev->end == prev->start)
prev->end = curr->start - 1;
- prevnd = nd;
}
+
+ /* Last entry */
+ if (curr->end == curr->start)
+ curr->end = roundup(curr->start, 4096);
}
-static void kernel_maps__fixup_sym_end(void)
+static void kernel_maps__fixup_end(void)
{
struct map *prev, *curr;
struct rb_node *nd, *prevnd = rb_first(&kernel_maps);
@@ -55,13 +63,17 @@ static void kernel_maps__fixup_sym_end(void)
return;
curr = rb_entry(prevnd, struct map, rb_node);
- dso__set_symbols_end(curr->dso);
for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
prev = curr;
curr = rb_entry(nd, struct map, rb_node);
prev->end = curr->start - 1;
- dso__set_symbols_end(curr->dso);
+ }
+
+ nd = rb_last(&curr->dso->syms);
+ if (nd) {
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ curr->end = sym->end;
}
}
@@ -200,13 +212,16 @@ size_t dso__fprintf(struct dso *self, FILE *fp)
return ret;
}
-static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
+/*
+ * Loads the function entries in /proc/kallsyms into kernel_map->dso,
+ * so that we can in the next step set the symbol ->end address and then
+ * call kernel_maps__split_kallsyms.
+ */
+static int kernel_maps__load_all_kallsyms(int v)
{
- struct map *map = kernel_map;
char *line = NULL;
size_t n;
FILE *file = fopen("/proc/kallsyms", "r");
- int count = 0;
if (file == NULL)
goto out_failure;
@@ -216,7 +231,7 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
struct symbol *sym;
int line_len, len;
char symbol_type;
- char *module, *symbol_name;
+ char *symbol_name;
line_len = getline(&line, &n, file);
if (line_len < 0)
@@ -241,20 +256,55 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
continue;
symbol_name = line + len + 2;
- module = strchr(symbol_name, '\t');
- if (module) {
- char *module_name_end;
+ /*
+ * Will fix up the end later, when we have all symbols sorted.
+ */
+ sym = symbol__new(start, 0, symbol_name,
+ kernel_map->dso->sym_priv_size, v);
+ if (sym == NULL)
+ goto out_delete_line;
+
+ dso__insert_symbol(kernel_map->dso, sym);
+ }
+
+ free(line);
+ fclose(file);
+
+ return 0;
+
+out_delete_line:
+ free(line);
+out_failure:
+ return -1;
+}
+
+/*
+ * Split the symbols into maps, making sure there are no overlaps, i.e. the
+ * kernel range is broken in several maps, named [kernel].N, as we don't have
+ * the original ELF section names vmlinux have.
+ */
+static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules)
+{
+ struct map *map = kernel_map;
+ struct symbol *pos;
+ int count = 0;
+ struct rb_node *next = rb_first(&kernel_map->dso->syms);
+ int kernel_range = 0;
+
+ while (next) {
+ char *module;
+
+ pos = rb_entry(next, struct symbol, rb_node);
+ next = rb_next(&pos->rb_node);
+
+ module = strchr(pos->name, '\t');
+ if (module) {
if (!use_modules)
- continue;
- *module = '\0';
- module = strchr(module + 1, '[');
- if (!module)
- continue;
- module_name_end = strchr(module + 1, ']');
- if (!module_name_end)
- continue;
- *(module_name_end + 1) = '\0';
+ goto delete_symbol;
+
+ *module++ = '\0';
+
if (strcmp(map->dso->name, module)) {
map = kernel_maps__find_by_dso_name(module);
if (!map) {
@@ -263,50 +313,77 @@ static int maps__load_kallsyms(symbol_filter_t filter, int use_modules, int v)
return -1;
}
}
- start = map->map_ip(map, start);
- } else
- map = kernel_map;
- /*
- * Well fix up the end later, when we have all sorted.
- */
- sym = symbol__new(start, 0, symbol_name,
- map->dso->sym_priv_size, v);
+ /*
+ * So that we look just like we get from .ko files,
+ * i.e. not prelinked, relative to map->start.
+ */
+ pos->start = map->map_ip(map, pos->start);
+ pos->end = map->map_ip(map, pos->end);
+ } else if (map != kernel_map) {
+ char dso_name[PATH_MAX];
+ struct dso *dso;
+
+ snprintf(dso_name, sizeof(dso_name), "[kernel].%d",
+ kernel_range++);
+
+ dso = dso__new(dso_name,
+ kernel_map->dso->sym_priv_size);
+ if (dso == NULL)
+ return -1;
+
+ map = map__new2(pos->start, dso);
+ if (map == NULL) {
+ dso__delete(dso);
+ return -1;
+ }
- if (sym == NULL)
- goto out_delete_line;
+ map->map_ip = vdso__map_ip;
+ kernel_maps__insert(map);
+ ++kernel_range;
+ }
- if (filter && filter(map, sym))
- symbol__delete(sym, map->dso->sym_priv_size);
- else {
- dso__insert_symbol(map->dso, sym);
+ if (filter && filter(map, pos)) {
+delete_symbol:
+ rb_erase(&pos->rb_node, &kernel_map->dso->syms);
+ symbol__delete(pos, kernel_map->dso->sym_priv_size);
+ } else {
+ if (map != kernel_map) {
+ rb_erase(&pos->rb_node, &kernel_map->dso->syms);
+ dso__insert_symbol(map->dso, pos);
+ }
count++;
}
}
- free(line);
- fclose(file);
-
return count;
+}
-out_delete_line:
- free(line);
-out_failure:
- return -1;
+
+static int kernel_maps__load_kallsyms(symbol_filter_t filter,
+ int use_modules, int v)
+{
+ if (kernel_maps__load_all_kallsyms(v))
+ return -1;
+
+ dso__fixup_sym_end(kernel_map->dso);
+
+ return kernel_maps__split_kallsyms(filter, use_modules);
}
-static size_t kernel_maps__fprintf(FILE *fp)
+static size_t kernel_maps__fprintf(FILE *fp, int v)
{
size_t printed = fprintf(stderr, "Kernel maps:\n");
struct rb_node *nd;
- printed += map__fprintf(kernel_map, fp);
- printed += dso__fprintf(kernel_map->dso, fp);
-
for (nd = rb_first(&kernel_maps); nd; nd = rb_next(nd)) {
struct map *pos = rb_entry(nd, struct map, rb_node);
+ printed += fprintf(fp, "Map:");
printed += map__fprintf(pos, fp);
- printed += dso__fprintf(pos->dso, fp);
+ if (v > 1) {
+ printed += dso__fprintf(pos->dso, fp);
+ printed += fprintf(fp, "--\n");
+ }
}
return printed + fprintf(stderr, "END kernel maps\n");
@@ -594,6 +671,9 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
int fd, symbol_filter_t filter, int kernel,
int kmodule, int v)
{
+ struct map *curr_map = map;
+ struct dso *curr_dso = self;
+ size_t dso_name_len = strlen(self->short_name);
Elf_Data *symstrs, *secstrs;
uint32_t nr_syms;
int err = -1;
@@ -660,10 +740,9 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) {
struct symbol *f;
const char *elf_name;
- char *demangled;
+ char *demangled = NULL;
int is_label = elf_sym__is_label(&sym);
const char *section_name;
- u64 sh_offset = 0;
if (!is_label && !elf_sym__is_function(&sym))
continue;
@@ -677,14 +756,51 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
if (is_label && !elf_sec__is_text(&shdr, secstrs))
continue;
+ elf_name = elf_sym__name(&sym, symstrs);
section_name = elf_sec__name(&shdr, secstrs);
- if ((kernel || kmodule)) {
- if (strstr(section_name, ".init"))
- sh_offset = shdr.sh_offset;
+ if (kernel || kmodule) {
+ char dso_name[PATH_MAX];
+
+ if (strcmp(section_name,
+ curr_dso->short_name + dso_name_len) == 0)
+ goto new_symbol;
+
+ if (strcmp(section_name, ".text") == 0) {
+ curr_map = map;
+ curr_dso = self;
+ goto new_symbol;
+ }
+
+ snprintf(dso_name, sizeof(dso_name),
+ "%s%s", self->short_name, section_name);
+
+ curr_map = kernel_maps__find_by_dso_name(dso_name);
+ if (curr_map == NULL) {
+ u64 start = sym.st_value;
+
+ if (kmodule)
+ start += map->start + shdr.sh_offset;
+
+ curr_dso = dso__new(dso_name, self->sym_priv_size);
+ if (curr_dso == NULL)
+ goto out_elf_end;
+ curr_map = map__new2(start, curr_dso);
+ if (curr_map == NULL) {
+ dso__delete(curr_dso);
+ goto out_elf_end;
+ }
+ curr_map->map_ip = vdso__map_ip;
+ curr_dso->origin = DSO__ORIG_KERNEL;
+ kernel_maps__insert(curr_map);
+ dsos__add(curr_dso);
+ } else
+ curr_dso = curr_map->dso;
+
+ goto new_symbol;
}
- if (self->adjust_symbols) {
+ if (curr_dso->adjust_symbols) {
if (v > 2)
printf("adjusting symbol: st_value: %Lx sh_addr: %Lx sh_offset: %Lx\n",
(u64)sym.st_value, (u64)shdr.sh_addr, (u64)shdr.sh_offset);
@@ -696,25 +812,29 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name,
* DWARF DW_compile_unit has this, but we don't always have access
* to it...
*/
- elf_name = elf_sym__name(&sym, symstrs);
demangled = bfd_demangle(NULL, elf_name, DMGL_PARAMS | DMGL_ANSI);
if (demangled != NULL)
elf_name = demangled;
-
- f = symbol__new(sym.st_value + sh_offset, sym.st_size, elf_name,
- self->sym_priv_size, v);
+new_symbol:
+ f = symbol__new(sym.st_value, sym.st_size, elf_name,
+ curr_dso->sym_priv_size, v);
free(demangled);
if (!f)
goto out_elf_end;
- if (filter && filter(map, f))
- symbol__delete(f, self->sym_priv_size);
+ if (filter && filter(curr_map, f))
+ symbol__delete(f, curr_dso->sym_priv_size);
else {
- dso__insert_symbol(self, f);
+ dso__insert_symbol(curr_dso, f);
nr++;
}
}
+ /*
+ * For misannotated, zeroed, ASM function sizes.
+ */
+ if (nr > 0)
+ dso__fixup_sym_end(self);
err = nr;
out_elf_end:
elf_end(elf);
@@ -883,27 +1003,17 @@ static void kernel_maps__insert(struct map *map)
struct symbol *kernel_maps__find_symbol(u64 ip, struct map **mapp)
{
- /*
- * We can't have kernel_map in kernel_maps because it spans an address
- * space that includes the modules. The right way to fix this is to
- * create several maps, so that we don't have overlapping ranges with
- * modules. For now lets look first on the kernel dso.
- */
struct map *map = maps__find(&kernel_maps, ip);
- struct symbol *sym;
+
+ if (mapp)
+ *mapp = map;
if (map) {
ip = map->map_ip(map, ip);
- sym = map->dso->find_symbol(map->dso, ip);
- } else {
- map = kernel_map;
- sym = map->dso->find_symbol(map->dso, ip);
+ return map->dso->find_symbol(map->dso, ip);
}
- if (mapp)
- *mapp = map;
-
- return sym;
+ return NULL;
}
struct map *kernel_maps__find_by_dso_name(const char *name)
@@ -994,6 +1104,14 @@ static int dsos__load_modules_sym_dir(char *dirname,
last = rb_last(&map->dso->syms);
if (last) {
struct symbol *sym;
+ /*
+ * We do this here as well, even having the
+ * symbol size found in the symtab because
+ * misannotated ASM symbols may have the size
+ * set to zero.
+ */
+ dso__fixup_sym_end(map->dso);
+
sym = rb_entry(last, struct symbol, rb_node);
map->end = map->start + sym->end;
}
@@ -1163,17 +1281,11 @@ int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size,
}
if (err <= 0)
- err = maps__load_kallsyms(filter, use_modules, v);
+ err = kernel_maps__load_kallsyms(filter, use_modules, v);
if (err > 0) {
struct rb_node *node = rb_first(&dso->syms);
struct symbol *sym = rb_entry(node, struct symbol, rb_node);
- /*
- * Now that we have all sorted out, just set the ->end of all
- * symbols that still don't have it.
- */
- dso__set_symbols_end(dso);
- kernel_maps__fixup_sym_end();
kernel_map->start = sym->start;
node = rb_last(&dso->syms);
@@ -1181,14 +1293,16 @@ int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size,
kernel_map->end = sym->end;
dso->origin = DSO__ORIG_KERNEL;
+ kernel_maps__insert(kernel_map);
/*
- * XXX See kernel_maps__find_symbol comment
- * kernel_maps__insert(kernel_map)
+ * Now that we have all sorted out, just set the ->end of all
+ * maps:
*/
+ kernel_maps__fixup_end();
dsos__add(dso);
if (v > 0)
- kernel_maps__fprintf(stderr);
+ kernel_maps__fprintf(stderr, v);
}
return err;
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1] perf tools: Move threads & last_match to threads.c
[not found] <y>
` (4 preceding siblings ...)
2009-10-07 16:48 ` [PATCH 1/1] perf tools: Improve kernel/modules symbol lookup Arnaldo Carvalho de Melo
@ 2009-10-11 16:40 ` Arnaldo Carvalho de Melo
2009-10-12 9:11 ` Ingo Molnar
2009-10-19 16:56 ` [PATCH] perf tools: Shut up compiler warning Arnaldo Carvalho de Melo
` (154 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-11 16:40 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith
This was just being copy'n'pasted all over.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-annotate.c | 18 +++++++-----------
tools/perf/builtin-report.c | 17 +++++++----------
tools/perf/builtin-sched.c | 15 ++++++---------
tools/perf/builtin-trace.c | 9 +++------
tools/perf/util/thread.c | 40 ++++++++++++++++++----------------------
tools/perf/util/thread.h | 12 ++++--------
6 files changed, 45 insertions(+), 66 deletions(-)
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 8c84320..b64029f 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -37,10 +37,6 @@ static int print_line;
static unsigned long page_size;
static unsigned long mmap_window = 32;
-static struct rb_root threads;
-static struct thread *last_match;
-
-
struct sym_ext {
struct rb_node node;
double percent;
@@ -101,7 +97,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
struct map *map = NULL;
struct symbol *sym = NULL;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
dump_printf("%p [%p]: PERF_EVENT (IP, %d): %d: %p\n",
(void *)(offset + head),
@@ -169,7 +165,7 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
struct thread *thread;
struct map *map = map__new(&event->mmap, NULL, 0);
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -196,7 +192,7 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread;
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -218,8 +214,8 @@ process_fork_event(event_t *event, unsigned long offset, unsigned long head)
struct thread *thread;
struct thread *parent;
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
+ thread = threads__findnew(event->fork.pid);
+ parent = threads__findnew(event->fork.ppid);
dump_printf("%p [%p]: PERF_RECORD_FORK: %d:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -558,7 +554,7 @@ static int __cmd_annotate(void)
uint32_t size;
char *buf;
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
input = open(input_name, O_RDONLY);
if (input < 0) {
@@ -659,7 +655,7 @@ more:
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index f57a23b..0a3b014 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -55,9 +55,6 @@ static char callchain_default_opt[] = "fractal,0.5";
static char *cwd;
static int cwdlen;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -601,7 +598,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
struct ip_callchain *chain = NULL;
int cpumode;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_PERIOD) {
period = *(u64 *)more_data;
@@ -688,7 +685,7 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
struct thread *thread;
struct map *map = map__new(&event->mmap, cwd, cwdlen);
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d/%d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -716,7 +713,7 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread;
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -739,8 +736,8 @@ process_task_event(event_t *event, unsigned long offset, unsigned long head)
struct thread *thread;
struct thread *parent;
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
+ thread = threads__findnew(event->fork.pid);
+ parent = threads__findnew(event->fork.ppid);
dump_printf("%p [%p]: PERF_RECORD_%s: (%d:%d):(%d:%d)\n",
(void *)(offset + head),
@@ -857,7 +854,7 @@ static int __cmd_report(void)
struct thread *idle;
int ret;
- idle = register_idle_thread(&threads, &last_match);
+ idle = register_idle_thread();
thread__comm_adjust(idle);
if (show_threads)
@@ -881,7 +878,7 @@ static int __cmd_report(void)
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 25b91e7..d66b8e2 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -24,9 +24,6 @@ static char const *input_name = "perf.data";
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -638,7 +635,7 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread;
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: perf_event_comm: %s:%d\n",
(void *)(offset + head),
@@ -1039,7 +1036,7 @@ threads__findnew_from_ctx(u32 pid, struct trace_switch_event *switch_event)
{
struct thread *th;
- th = threads__findnew_nocomm(pid, &threads, &last_match);
+ th = threads__findnew_nocomm(pid);
if (th->comm)
return th;
@@ -1055,7 +1052,7 @@ threads__findnew_from_wakeup(struct trace_wakeup_event *wakeup_event)
{
struct thread *th;
- th = threads__findnew_nocomm(wakeup_event->pid, &threads, &last_match);
+ th = threads__findnew_nocomm(wakeup_event->pid);
if (th->comm)
return th;
@@ -1130,7 +1127,7 @@ latency_runtime_event(struct trace_runtime_event *runtime_event,
BUG_ON(cpu >= MAX_CPUS || cpu < 0);
- thread = threads__findnew(runtime_event->pid, &threads, &last_match);
+ thread = threads__findnew(runtime_event->pid);
atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
if (!atoms) {
thread_atoms_insert(thread);
@@ -1589,7 +1586,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
if (!(sample_type & PERF_SAMPLE_RAW))
return 0;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -1662,7 +1659,7 @@ static struct perf_file_handler file_handler = {
static int read_events(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index fb3f3c2..7dcd8f9 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -19,9 +19,6 @@ static char const *input_name = "perf.data";
static unsigned long total = 0;
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -34,7 +31,7 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread;
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -61,7 +58,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
u64 period = 1;
void *more_data = event->ip.__more_data;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -135,7 +132,7 @@ static struct perf_file_handler file_handler = {
static int __cmd_trace(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
index 8bd5ca2..0b0ea59 100644
--- a/tools/perf/util/thread.c
+++ b/tools/perf/util/thread.c
@@ -6,6 +6,9 @@
#include "util.h"
#include "debug.h"
+static struct rb_root threads;
+static struct thread *last_match;
+
static struct thread *thread__new(pid_t pid, int set_comm)
{
struct thread *self = calloc(1, sizeof(*self));
@@ -52,12 +55,9 @@ static size_t thread__fprintf(struct thread *self, FILE *fp)
return ret;
}
-static struct thread *
-__threads__findnew(pid_t pid, struct rb_root *threads,
- struct thread **last_match,
- int set_comm)
+static struct thread *__threads__findnew(pid_t pid, int set_comm)
{
- struct rb_node **p = &threads->rb_node;
+ struct rb_node **p = &threads.rb_node;
struct rb_node *parent = NULL;
struct thread *th;
@@ -66,15 +66,15 @@ __threads__findnew(pid_t pid, struct rb_root *threads,
* so most of the time we dont have to look up
* the full rbtree:
*/
- if (*last_match && (*last_match)->pid == pid)
- return *last_match;
+ if (last_match && last_match->pid == pid)
+ return last_match;
while (*p != NULL) {
parent = *p;
th = rb_entry(parent, struct thread, rb_node);
if (th->pid == pid) {
- *last_match = th;
+ last_match = th;
return th;
}
@@ -88,30 +88,26 @@ __threads__findnew(pid_t pid, struct rb_root *threads,
if (th != NULL) {
rb_link_node(&th->rb_node, parent, p);
- rb_insert_color(&th->rb_node, threads);
- *last_match = th;
+ rb_insert_color(&th->rb_node, &threads);
+ last_match = th;
}
return th;
}
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
+struct thread *threads__findnew(pid_t pid)
{
- return __threads__findnew(pid, threads, last_match, 1);
+ return __threads__findnew(pid, 1);
}
-struct thread *
-threads__findnew_nocomm(pid_t pid, struct rb_root *threads,
- struct thread **last_match)
+struct thread *threads__findnew_nocomm(pid_t pid)
{
- return __threads__findnew(pid, threads, last_match, 0);
+ return __threads__findnew(pid, 0);
}
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match)
+struct thread *register_idle_thread(void)
{
- struct thread *thread = threads__findnew(0, threads, last_match);
+ struct thread *thread = threads__findnew(0);
if (!thread || thread__set_comm(thread, "swapper")) {
fprintf(stderr, "problem inserting idle task.\n");
@@ -215,12 +211,12 @@ int thread__fork(struct thread *self, struct thread *parent)
return 0;
}
-size_t threads__fprintf(FILE *fp, struct rb_root *threads)
+size_t threads__fprintf(FILE *fp)
{
size_t ret = 0;
struct rb_node *nd;
- for (nd = rb_first(threads); nd; nd = rb_next(nd)) {
+ for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
struct thread *pos = rb_entry(nd, struct thread, rb_node);
ret += thread__fprintf(pos, fp);
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
index 75bc843..40ace42 100644
--- a/tools/perf/util/thread.h
+++ b/tools/perf/util/thread.h
@@ -15,16 +15,12 @@ struct thread {
};
int thread__set_comm(struct thread *self, const char *comm);
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match);
-struct thread *
-threads__findnew_nocomm(pid_t pid, struct rb_root *threads,
- struct thread **last_match);
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match);
+struct thread *threads__findnew(pid_t pid);
+struct thread *threads__findnew_nocomm(pid_t pid);
+struct thread *register_idle_thread(void);
void thread__insert_map(struct thread *self, struct map *map);
int thread__fork(struct thread *self, struct thread *parent);
-size_t threads__fprintf(FILE *fp, struct rb_root *threads);
+size_t threads__fprintf(FILE *fp);
void maps__insert(struct rb_root *maps, struct map *map);
struct map *maps__find(struct rb_root *maps, u64 ip);
--
1.6.2.5
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 1/1] perf tools: Move threads & last_match to threads.c
2009-10-11 16:40 ` [PATCH 1/1] perf tools: Move threads & last_match to threads.c Arnaldo Carvalho de Melo
@ 2009-10-12 9:11 ` Ingo Molnar
2009-10-13 14:16 ` [PATCH 1/1 v2] " Arnaldo Carvalho de Melo
0 siblings, 1 reply; 743+ messages in thread
From: Ingo Molnar @ 2009-10-12 9:11 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: linux-kernel, Frederic Weisbecker, Peter Zijlstra,
Paul Mackerras, Mike Galbraith
* Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> This was just being copy'n'pasted all over.
>
> Cc: Frederic Weisbecker <fweisbec@gmail.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Paul Mackerras <paulus@samba.org>
> Cc: Mike Galbraith <efault@gmx.de>
> Cc: Paul Mackerras <paulus@samba.org>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> ---
> tools/perf/builtin-annotate.c | 18 +++++++-----------
> tools/perf/builtin-report.c | 17 +++++++----------
> tools/perf/builtin-sched.c | 15 ++++++---------
> tools/perf/builtin-trace.c | 9 +++------
> tools/perf/util/thread.c | 40 ++++++++++++++++++----------------------
> tools/perf/util/thread.h | 12 ++++--------
> 6 files changed, 45 insertions(+), 66 deletions(-)
hm, mind refreshing this against latest -tip? It doesnt apply anymore:
Hunk #2 FAILED at 635.
Hunk #3 FAILED at 1036.
Hunk #4 FAILED at 1052.
Hunk #5 succeeded at 1122 (offset -5 lines).
Hunk #6 succeeded at 1651 (offset 65 lines).
Hunk #7 succeeded at 1657 (offset -2 lines).
3 out of 7 hunks FAILED -- rejects in file tools/perf/builtin-sched.c
patching file tools/perf/builtin-trace.c
patching file tools/perf/util/thread.c
Hunk #1 FAILED at 6.
Hunk #2 FAILED at 55.
Hunk #3 succeeded at 62 (offset -4 lines).
Hunk #4 FAILED at 84.
Hunk #5 succeeded at 197 (offset -14 lines).
3 out of 5 hunks FAILED -- rejects in file tools/perf/util/thread.c
patching file tools/perf/util/thread.h
Hunk #1 FAILED at 15.
1 out of 1 hunk FAILED -- rejects in file tools/perf/util/thread.h
or have i missed some prereq patch?
Ingo
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1 v2] perf tools: Move threads & last_match to threads.c
2009-10-12 9:11 ` Ingo Molnar
@ 2009-10-13 14:16 ` Arnaldo Carvalho de Melo
2009-10-13 15:19 ` [tip:perf/core] " tip-bot for Arnaldo Carvalho de Melo
0 siblings, 1 reply; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-13 14:16 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Frederic Weisbecker, Peter Zijlstra,
Paul Mackerras, Mike Galbraith
Em Mon, Oct 12, 2009 at 11:11:34AM +0200, Ingo Molnar escreveu:
> hm, mind refreshing this against latest -tip? It doesnt apply anymore:
Sure:
>From 4a1ab396733881fbd7b89693c6beeb40d1ae67ed Mon Sep 17 00:00:00 2001
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Date: Tue, 13 Oct 2009 09:14:46 -0200
Subject: [PATCH 1/1] perf tools: Move threads & last_match to threads.c
This was just being copy'n'pasted all over.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-annotate.c | 25 +++++++------------------
tools/perf/builtin-report.c | 26 +++++++-------------------
tools/perf/builtin-sched.c | 30 +++++++++++-------------------
tools/perf/builtin-trace.c | 13 +++----------
tools/perf/util/thread.c | 27 ++++++++++++++-------------
tools/perf/util/thread.h | 8 +++-----
6 files changed, 45 insertions(+), 84 deletions(-)
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 8c84320..3fe0de0 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -37,10 +37,6 @@ static int print_line;
static unsigned long page_size;
static unsigned long mmap_window = 32;
-static struct rb_root threads;
-static struct thread *last_match;
-
-
struct sym_ext {
struct rb_node node;
double percent;
@@ -96,12 +92,10 @@ static int
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
- struct thread *thread;
u64 ip = event->ip.ip;
struct map *map = NULL;
struct symbol *sym = NULL;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
dump_printf("%p [%p]: PERF_EVENT (IP, %d): %d: %p\n",
(void *)(offset + head),
@@ -166,10 +160,8 @@ got_map:
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
struct map *map = map__new(&event->mmap, NULL, 0);
-
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -194,9 +186,8 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
+ struct thread *thread = threads__findnew(event->comm.pid);
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -215,11 +206,9 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_fork_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
- struct thread *parent;
+ struct thread *thread = threads__findnew(event->fork.pid);
+ struct thread *parent = threads__findnew(event->fork.ppid);
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
dump_printf("%p [%p]: PERF_RECORD_FORK: %d:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -558,7 +547,7 @@ static int __cmd_annotate(void)
uint32_t size;
char *buf;
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
input = open(input_name, O_RDONLY);
if (input < 0) {
@@ -659,7 +648,7 @@ more:
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index f57a23b..015c797 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -55,9 +55,6 @@ static char callchain_default_opt[] = "fractal,0.5";
static char *cwd;
static int cwdlen;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -593,15 +590,13 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
struct symbol *sym = NULL;
- struct thread *thread;
u64 ip = event->ip.ip;
u64 period = 1;
struct map *map = NULL;
void *more_data = event->ip.__more_data;
struct ip_callchain *chain = NULL;
int cpumode;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_PERIOD) {
period = *(u64 *)more_data;
@@ -685,10 +680,8 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
struct map *map = map__new(&event->mmap, cwd, cwdlen);
-
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d/%d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -714,9 +707,7 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -736,11 +727,8 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_task_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
- struct thread *parent;
-
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->fork.pid);
+ struct thread *parent = threads__findnew(event->fork.ppid);
dump_printf("%p [%p]: PERF_RECORD_%s: (%d:%d):(%d:%d)\n",
(void *)(offset + head),
@@ -857,7 +845,7 @@ static int __cmd_report(void)
struct thread *idle;
int ret;
- idle = register_idle_thread(&threads, &last_match);
+ idle = register_idle_thread();
thread__comm_adjust(idle);
if (show_threads)
@@ -881,7 +869,7 @@ static int __cmd_report(void)
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 62585b2..c9c6856 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -24,9 +24,6 @@ static char const *input_name = "perf.data";
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -641,9 +638,7 @@ static void test_calibrations(void)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.tid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.tid);
dump_printf("%p [%p]: perf_event_comm: %s:%d\n",
(void *)(offset + head),
@@ -1086,8 +1081,8 @@ latency_switch_event(struct trace_switch_event *switch_event,
die("hm, delta: %Ld < 0 ?\n", delta);
- sched_out = threads__findnew(switch_event->prev_pid, &threads, &last_match);
- sched_in = threads__findnew(switch_event->next_pid, &threads, &last_match);
+ sched_out = threads__findnew(switch_event->prev_pid);
+ sched_in = threads__findnew(switch_event->next_pid);
out_events = thread_atoms_search(&atom_root, sched_out, &cmp_pid);
if (!out_events) {
@@ -1120,13 +1115,10 @@ latency_runtime_event(struct trace_runtime_event *runtime_event,
u64 timestamp,
struct thread *this_thread __used)
{
- struct work_atoms *atoms;
- struct thread *thread;
+ struct thread *thread = threads__findnew(runtime_event->pid);
+ struct work_atoms *atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
BUG_ON(cpu >= MAX_CPUS || cpu < 0);
-
- thread = threads__findnew(runtime_event->pid, &threads, &last_match);
- atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
if (!atoms) {
thread_atoms_insert(thread);
atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
@@ -1153,7 +1145,7 @@ latency_wakeup_event(struct trace_wakeup_event *wakeup_event,
if (!wakeup_event->success)
return;
- wakee = threads__findnew(wakeup_event->pid, &threads, &last_match);
+ wakee = threads__findnew(wakeup_event->pid);
atoms = thread_atoms_search(&atom_root, wakee, &cmp_pid);
if (!atoms) {
thread_atoms_insert(wakee);
@@ -1202,7 +1194,7 @@ latency_migrate_task_event(struct trace_migrate_task_event *migrate_task_event,
if (profile_cpu == -1)
return;
- migrant = threads__findnew(migrate_task_event->pid, &threads, &last_match);
+ migrant = threads__findnew(migrate_task_event->pid);
atoms = thread_atoms_search(&atom_root, migrant, &cmp_pid);
if (!atoms) {
thread_atoms_insert(migrant);
@@ -1458,8 +1450,8 @@ map_switch_event(struct trace_switch_event *switch_event,
die("hm, delta: %Ld < 0 ?\n", delta);
- sched_out = threads__findnew(switch_event->prev_pid, &threads, &last_match);
- sched_in = threads__findnew(switch_event->next_pid, &threads, &last_match);
+ sched_out = threads__findnew(switch_event->prev_pid);
+ sched_in = threads__findnew(switch_event->next_pid);
curr_thread[this_cpu] = sched_in;
@@ -1649,7 +1641,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
if (!(sample_type & PERF_SAMPLE_RAW))
return 0;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -1725,7 +1717,7 @@ static struct perf_file_handler file_handler = {
static int read_events(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index fb3f3c2..ccf867d 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -19,9 +19,6 @@ static char const *input_name = "perf.data";
static unsigned long total = 0;
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -32,9 +29,7 @@ static int cwdlen;
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -54,14 +49,12 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
u64 ip = event->ip.ip;
u64 timestamp = -1;
u32 cpu = -1;
u64 period = 1;
void *more_data = event->ip.__more_data;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -135,7 +128,7 @@ static struct perf_file_handler file_handler = {
static int __cmd_trace(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
index 3b56aeb..f53fad7 100644
--- a/tools/perf/util/thread.c
+++ b/tools/perf/util/thread.c
@@ -6,6 +6,9 @@
#include "util.h"
#include "debug.h"
+static struct rb_root threads;
+static struct thread *last_match;
+
static struct thread *thread__new(pid_t pid)
{
struct thread *self = calloc(1, sizeof(*self));
@@ -50,10 +53,9 @@ static size_t thread__fprintf(struct thread *self, FILE *fp)
return ret;
}
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
+struct thread *threads__findnew(pid_t pid)
{
- struct rb_node **p = &threads->rb_node;
+ struct rb_node **p = &threads.rb_node;
struct rb_node *parent = NULL;
struct thread *th;
@@ -62,15 +64,15 @@ threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
* so most of the time we dont have to look up
* the full rbtree:
*/
- if (*last_match && (*last_match)->pid == pid)
- return *last_match;
+ if (last_match && last_match->pid == pid)
+ return last_match;
while (*p != NULL) {
parent = *p;
th = rb_entry(parent, struct thread, rb_node);
if (th->pid == pid) {
- *last_match = th;
+ last_match = th;
return th;
}
@@ -83,17 +85,16 @@ threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
th = thread__new(pid);
if (th != NULL) {
rb_link_node(&th->rb_node, parent, p);
- rb_insert_color(&th->rb_node, threads);
- *last_match = th;
+ rb_insert_color(&th->rb_node, &threads);
+ last_match = th;
}
return th;
}
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match)
+struct thread *register_idle_thread(void)
{
- struct thread *thread = threads__findnew(0, threads, last_match);
+ struct thread *thread = threads__findnew(0);
if (!thread || thread__set_comm(thread, "swapper")) {
fprintf(stderr, "problem inserting idle task.\n");
@@ -197,12 +198,12 @@ int thread__fork(struct thread *self, struct thread *parent)
return 0;
}
-size_t threads__fprintf(FILE *fp, struct rb_root *threads)
+size_t threads__fprintf(FILE *fp)
{
size_t ret = 0;
struct rb_node *nd;
- for (nd = rb_first(threads); nd; nd = rb_next(nd)) {
+ for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
struct thread *pos = rb_entry(nd, struct thread, rb_node);
ret += thread__fprintf(pos, fp);
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
index 845d9b6..1abef3b 100644
--- a/tools/perf/util/thread.h
+++ b/tools/perf/util/thread.h
@@ -15,13 +15,11 @@ struct thread {
};
int thread__set_comm(struct thread *self, const char *comm);
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match);
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match);
+struct thread *threads__findnew(pid_t pid);
+struct thread *register_idle_thread(void);
void thread__insert_map(struct thread *self, struct map *map);
int thread__fork(struct thread *self, struct thread *parent);
-size_t threads__fprintf(FILE *fp, struct rb_root *threads);
+size_t threads__fprintf(FILE *fp);
void maps__insert(struct rb_root *maps, struct map *map);
struct map *maps__find(struct rb_root *maps, u64 ip);
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [tip:perf/core] perf tools: Move threads & last_match to threads.c
2009-10-13 14:16 ` [PATCH 1/1 v2] " Arnaldo Carvalho de Melo
@ 2009-10-13 15:19 ` tip-bot for Arnaldo Carvalho de Melo
0 siblings, 0 replies; 743+ messages in thread
From: tip-bot for Arnaldo Carvalho de Melo @ 2009-10-13 15:19 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, paulus, acme, hpa, mingo, efault, peterz, fweisbec,
tglx, mingo
Commit-ID: d5b889f2ecec7849e851ddd31c34bdfb3482b5de
Gitweb: http://git.kernel.org/tip/d5b889f2ecec7849e851ddd31c34bdfb3482b5de
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
AuthorDate: Tue, 13 Oct 2009 11:16:29 -0300
Committer: Ingo Molnar <mingo@elte.hu>
CommitDate: Tue, 13 Oct 2009 17:12:18 +0200
perf tools: Move threads & last_match to threads.c
This was just being copy'n'pasted all over.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <20091013141629.GD21809@ghostprotocols.net>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
---
tools/perf/builtin-annotate.c | 25 +++++++------------------
tools/perf/builtin-report.c | 26 +++++++-------------------
tools/perf/builtin-sched.c | 30 +++++++++++-------------------
tools/perf/builtin-trace.c | 13 +++----------
tools/perf/util/thread.c | 27 ++++++++++++++-------------
tools/perf/util/thread.h | 8 +++-----
6 files changed, 45 insertions(+), 84 deletions(-)
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 8c84320..3fe0de0 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -37,10 +37,6 @@ static int print_line;
static unsigned long page_size;
static unsigned long mmap_window = 32;
-static struct rb_root threads;
-static struct thread *last_match;
-
-
struct sym_ext {
struct rb_node node;
double percent;
@@ -96,12 +92,10 @@ static int
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
- struct thread *thread;
u64 ip = event->ip.ip;
struct map *map = NULL;
struct symbol *sym = NULL;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
dump_printf("%p [%p]: PERF_EVENT (IP, %d): %d: %p\n",
(void *)(offset + head),
@@ -166,10 +160,8 @@ got_map:
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
struct map *map = map__new(&event->mmap, NULL, 0);
-
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -194,9 +186,8 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
+ struct thread *thread = threads__findnew(event->comm.pid);
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -215,11 +206,9 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_fork_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
- struct thread *parent;
+ struct thread *thread = threads__findnew(event->fork.pid);
+ struct thread *parent = threads__findnew(event->fork.ppid);
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
dump_printf("%p [%p]: PERF_RECORD_FORK: %d:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
@@ -558,7 +547,7 @@ static int __cmd_annotate(void)
uint32_t size;
char *buf;
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
input = open(input_name, O_RDONLY);
if (input < 0) {
@@ -659,7 +648,7 @@ more:
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index f57a23b..015c797 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -55,9 +55,6 @@ static char callchain_default_opt[] = "fractal,0.5";
static char *cwd;
static int cwdlen;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -593,15 +590,13 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
struct symbol *sym = NULL;
- struct thread *thread;
u64 ip = event->ip.ip;
u64 period = 1;
struct map *map = NULL;
void *more_data = event->ip.__more_data;
struct ip_callchain *chain = NULL;
int cpumode;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_PERIOD) {
period = *(u64 *)more_data;
@@ -685,10 +680,8 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
struct map *map = map__new(&event->mmap, cwd, cwdlen);
-
- thread = threads__findnew(event->mmap.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->mmap.pid);
dump_printf("%p [%p]: PERF_RECORD_MMAP %d/%d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
@@ -714,9 +707,7 @@ process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -736,11 +727,8 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_task_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
- struct thread *parent;
-
- thread = threads__findnew(event->fork.pid, &threads, &last_match);
- parent = threads__findnew(event->fork.ppid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->fork.pid);
+ struct thread *parent = threads__findnew(event->fork.ppid);
dump_printf("%p [%p]: PERF_RECORD_%s: (%d:%d):(%d:%d)\n",
(void *)(offset + head),
@@ -857,7 +845,7 @@ static int __cmd_report(void)
struct thread *idle;
int ret;
- idle = register_idle_thread(&threads, &last_match);
+ idle = register_idle_thread();
thread__comm_adjust(idle);
if (show_threads)
@@ -881,7 +869,7 @@ static int __cmd_report(void)
return 0;
if (verbose > 3)
- threads__fprintf(stdout, &threads);
+ threads__fprintf(stdout);
if (verbose > 2)
dsos__fprintf(stdout);
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 387a442..73bdad0 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -24,9 +24,6 @@ static char const *input_name = "perf.data";
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -641,9 +638,7 @@ static void test_calibrations(void)
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.tid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.tid);
dump_printf("%p [%p]: perf_event_comm: %s:%d\n",
(void *)(offset + head),
@@ -1086,8 +1081,8 @@ latency_switch_event(struct trace_switch_event *switch_event,
die("hm, delta: %Ld < 0 ?\n", delta);
- sched_out = threads__findnew(switch_event->prev_pid, &threads, &last_match);
- sched_in = threads__findnew(switch_event->next_pid, &threads, &last_match);
+ sched_out = threads__findnew(switch_event->prev_pid);
+ sched_in = threads__findnew(switch_event->next_pid);
out_events = thread_atoms_search(&atom_root, sched_out, &cmp_pid);
if (!out_events) {
@@ -1120,13 +1115,10 @@ latency_runtime_event(struct trace_runtime_event *runtime_event,
u64 timestamp,
struct thread *this_thread __used)
{
- struct work_atoms *atoms;
- struct thread *thread;
+ struct thread *thread = threads__findnew(runtime_event->pid);
+ struct work_atoms *atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
BUG_ON(cpu >= MAX_CPUS || cpu < 0);
-
- thread = threads__findnew(runtime_event->pid, &threads, &last_match);
- atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
if (!atoms) {
thread_atoms_insert(thread);
atoms = thread_atoms_search(&atom_root, thread, &cmp_pid);
@@ -1153,7 +1145,7 @@ latency_wakeup_event(struct trace_wakeup_event *wakeup_event,
if (!wakeup_event->success)
return;
- wakee = threads__findnew(wakeup_event->pid, &threads, &last_match);
+ wakee = threads__findnew(wakeup_event->pid);
atoms = thread_atoms_search(&atom_root, wakee, &cmp_pid);
if (!atoms) {
thread_atoms_insert(wakee);
@@ -1202,7 +1194,7 @@ latency_migrate_task_event(struct trace_migrate_task_event *migrate_task_event,
if (profile_cpu == -1)
return;
- migrant = threads__findnew(migrate_task_event->pid, &threads, &last_match);
+ migrant = threads__findnew(migrate_task_event->pid);
atoms = thread_atoms_search(&atom_root, migrant, &cmp_pid);
if (!atoms) {
thread_atoms_insert(migrant);
@@ -1458,8 +1450,8 @@ map_switch_event(struct trace_switch_event *switch_event,
die("hm, delta: %Ld < 0 ?\n", delta);
- sched_out = threads__findnew(switch_event->prev_pid, &threads, &last_match);
- sched_in = threads__findnew(switch_event->next_pid, &threads, &last_match);
+ sched_out = threads__findnew(switch_event->prev_pid);
+ sched_in = threads__findnew(switch_event->next_pid);
curr_thread[this_cpu] = sched_in;
@@ -1649,7 +1641,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
if (!(sample_type & PERF_SAMPLE_RAW))
return 0;
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -1725,7 +1717,7 @@ static struct perf_file_handler file_handler = {
static int read_events(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index fb3f3c2..ccf867d 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -19,9 +19,6 @@ static char const *input_name = "perf.data";
static unsigned long total = 0;
static unsigned long total_comm = 0;
-static struct rb_root threads;
-static struct thread *last_match;
-
static struct perf_header *header;
static u64 sample_type;
@@ -32,9 +29,7 @@ static int cwdlen;
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
-
- thread = threads__findnew(event->comm.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->comm.pid);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
@@ -54,14 +49,12 @@ process_comm_event(event_t *event, unsigned long offset, unsigned long head)
static int
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
- struct thread *thread;
u64 ip = event->ip.ip;
u64 timestamp = -1;
u32 cpu = -1;
u64 period = 1;
void *more_data = event->ip.__more_data;
-
- thread = threads__findnew(event->ip.pid, &threads, &last_match);
+ struct thread *thread = threads__findnew(event->ip.pid);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
@@ -135,7 +128,7 @@ static struct perf_file_handler file_handler = {
static int __cmd_trace(void)
{
- register_idle_thread(&threads, &last_match);
+ register_idle_thread();
register_perf_file_handler(&file_handler);
return mmap_dispatch_perf_file(&header, input_name, 0, 0, &cwdlen, &cwd);
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
index 3b56aeb..f53fad7 100644
--- a/tools/perf/util/thread.c
+++ b/tools/perf/util/thread.c
@@ -6,6 +6,9 @@
#include "util.h"
#include "debug.h"
+static struct rb_root threads;
+static struct thread *last_match;
+
static struct thread *thread__new(pid_t pid)
{
struct thread *self = calloc(1, sizeof(*self));
@@ -50,10 +53,9 @@ static size_t thread__fprintf(struct thread *self, FILE *fp)
return ret;
}
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
+struct thread *threads__findnew(pid_t pid)
{
- struct rb_node **p = &threads->rb_node;
+ struct rb_node **p = &threads.rb_node;
struct rb_node *parent = NULL;
struct thread *th;
@@ -62,15 +64,15 @@ threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
* so most of the time we dont have to look up
* the full rbtree:
*/
- if (*last_match && (*last_match)->pid == pid)
- return *last_match;
+ if (last_match && last_match->pid == pid)
+ return last_match;
while (*p != NULL) {
parent = *p;
th = rb_entry(parent, struct thread, rb_node);
if (th->pid == pid) {
- *last_match = th;
+ last_match = th;
return th;
}
@@ -83,17 +85,16 @@ threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
th = thread__new(pid);
if (th != NULL) {
rb_link_node(&th->rb_node, parent, p);
- rb_insert_color(&th->rb_node, threads);
- *last_match = th;
+ rb_insert_color(&th->rb_node, &threads);
+ last_match = th;
}
return th;
}
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match)
+struct thread *register_idle_thread(void)
{
- struct thread *thread = threads__findnew(0, threads, last_match);
+ struct thread *thread = threads__findnew(0);
if (!thread || thread__set_comm(thread, "swapper")) {
fprintf(stderr, "problem inserting idle task.\n");
@@ -197,12 +198,12 @@ int thread__fork(struct thread *self, struct thread *parent)
return 0;
}
-size_t threads__fprintf(FILE *fp, struct rb_root *threads)
+size_t threads__fprintf(FILE *fp)
{
size_t ret = 0;
struct rb_node *nd;
- for (nd = rb_first(threads); nd; nd = rb_next(nd)) {
+ for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
struct thread *pos = rb_entry(nd, struct thread, rb_node);
ret += thread__fprintf(pos, fp);
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
index 845d9b6..1abef3b 100644
--- a/tools/perf/util/thread.h
+++ b/tools/perf/util/thread.h
@@ -15,13 +15,11 @@ struct thread {
};
int thread__set_comm(struct thread *self, const char *comm);
-struct thread *
-threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match);
-struct thread *
-register_idle_thread(struct rb_root *threads, struct thread **last_match);
+struct thread *threads__findnew(pid_t pid);
+struct thread *register_idle_thread(void);
void thread__insert_map(struct thread *self, struct map *map);
int thread__fork(struct thread *self, struct thread *parent);
-size_t threads__fprintf(FILE *fp, struct rb_root *threads);
+size_t threads__fprintf(FILE *fp);
void maps__insert(struct rb_root *maps, struct map *map);
struct map *maps__find(struct rb_root *maps, u64 ip);
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] perf tools: Shut up compiler warning
[not found] <y>
` (5 preceding siblings ...)
2009-10-11 16:40 ` [PATCH 1/1] perf tools: Move threads & last_match to threads.c Arnaldo Carvalho de Melo
@ 2009-10-19 16:56 ` Arnaldo Carvalho de Melo
2009-10-20 5:54 ` Ingo Molnar
2009-10-26 21:23 ` [PATCH 1/3] perf record: process can disappear while reading its /proc/pid/tasks Arnaldo Carvalho de Melo
` (153 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-19 16:56 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Steven Rostedt,
Peter Zijlstra, Frederic Weisbecker
cc1: warnings being treated as errors
util/trace-event-parse.c: In function ‘parse_ftrace_printk’:
util/trace-event-parse.c:289: warning: ‘fmt’ may be used uninitialized in this function
Cc: Steven Rostedt <srostedt@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/trace-event-parse.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c
index 4b61b49..0b8a01b 100644
--- a/tools/perf/util/trace-event-parse.c
+++ b/tools/perf/util/trace-event-parse.c
@@ -286,7 +286,7 @@ void parse_ftrace_printk(char *file, unsigned int size __unused)
char *line;
char *next = NULL;
char *addr_str;
- char *fmt;
+ char *fmt = NULL;
int i;
line = strtok_r(file, "\n", &next);
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] perf tools: Shut up compiler warning
2009-10-19 16:56 ` [PATCH] perf tools: Shut up compiler warning Arnaldo Carvalho de Melo
@ 2009-10-20 5:54 ` Ingo Molnar
2009-10-20 13:19 ` Steven Rostedt
0 siblings, 1 reply; 743+ messages in thread
From: Ingo Molnar @ 2009-10-20 5:54 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: linux-kernel, Steven Rostedt, Peter Zijlstra, Frederic Weisbecker
* Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> cc1: warnings being treated as errors
> util/trace-event-parse.c: In function ‘parse_ftrace_printk’:
> util/trace-event-parse.c:289: warning: ‘fmt’ may be used uninitialized in this function
>
> Cc: Steven Rostedt <srostedt@redhat.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Frederic Weisbecker <fweisbec@gmail.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> ---
> tools/perf/util/trace-event-parse.c | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c
> index 4b61b49..0b8a01b 100644
> --- a/tools/perf/util/trace-event-parse.c
> +++ b/tools/perf/util/trace-event-parse.c
> @@ -286,7 +286,7 @@ void parse_ftrace_printk(char *file, unsigned int size __unused)
> char *line;
> char *next = NULL;
> char *addr_str;
> - char *fmt;
> + char *fmt = NULL;
> int i;
>
> line = strtok_r(file, "\n", &next);
Actually, this might be a real bug that GCC pointed out: what makes sure
that strtok_r() does not return a NULL (no more tokens) and hence fmt
remains NULL (crashing the rest of the parser)?
Ingo
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] perf tools: Shut up compiler warning
2009-10-20 5:54 ` Ingo Molnar
@ 2009-10-20 13:19 ` Steven Rostedt
0 siblings, 0 replies; 743+ messages in thread
From: Steven Rostedt @ 2009-10-20 13:19 UTC (permalink / raw)
To: Ingo Molnar
Cc: Arnaldo Carvalho de Melo, linux-kernel, Peter Zijlstra,
Frederic Weisbecker
On Tue, 2009-10-20 at 07:54 +0200, Ingo Molnar wrote:
> * Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
>
> > cc1: warnings being treated as errors
> > util/trace-event-parse.c: In function ‘parse_ftrace_printk’:
> > util/trace-event-parse.c:289: warning: ‘fmt’ may be used uninitialized in this function
> >
> > Cc: Steven Rostedt <srostedt@redhat.com>
> > Cc: Peter Zijlstra <peterz@infradead.org>
> > Cc: Frederic Weisbecker <fweisbec@gmail.com>
> > Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> > ---
> > tools/perf/util/trace-event-parse.c | 2 +-
> > 1 files changed, 1 insertions(+), 1 deletions(-)
> >
> > diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c
> > index 4b61b49..0b8a01b 100644
> > --- a/tools/perf/util/trace-event-parse.c
> > +++ b/tools/perf/util/trace-event-parse.c
> > @@ -286,7 +286,7 @@ void parse_ftrace_printk(char *file, unsigned int size __unused)
> > char *line;
> > char *next = NULL;
> > char *addr_str;
> > - char *fmt;
> > + char *fmt = NULL;
> > int i;
> >
> > line = strtok_r(file, "\n", &next);
>
> Actually, this might be a real bug that GCC pointed out: what makes sure
> that strtok_r() does not return a NULL (no more tokens) and hence fmt
> remains NULL (crashing the rest of the parser)?
Ug, yeah that's a bug. That was one of the later changes I did at night.
I'll go look at it and see how it should really be.
Thanks,
-- Steve
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/3] perf record: process can disappear while reading its /proc/pid/tasks
[not found] <y>
` (6 preceding siblings ...)
2009-10-19 16:56 ` [PATCH] perf tools: Shut up compiler warning Arnaldo Carvalho de Melo
@ 2009-10-26 21:23 ` Arnaldo Carvalho de Melo
2009-10-26 21:23 ` [PATCH 2/3] perf tools: Generalize event synthesizing routines Arnaldo Carvalho de Melo
2009-10-27 13:03 ` [tip:perf/core] perf record: Fix race where process can disappear while reading its /proc/pid/tasks tip-bot for Arnaldo Carvalho de Melo
2009-11-02 20:45 ` [PATCH] trivial: remove duplicated MIN macro from tehuti Thiago Farina
` (152 subsequent siblings)
160 siblings, 2 replies; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-26 21:23 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-record.c | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index ac5ddff..9e1638c 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -206,6 +206,7 @@ static pid_t pid_synthesize_comm_event(pid_t pid, int full)
fp = fopen(filename, "r");
if (fp == NULL) {
+out_race:
/*
* We raced with a task exiting - just return:
*/
@@ -247,6 +248,9 @@ static pid_t pid_synthesize_comm_event(pid_t pid, int full)
snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
tasks = opendir(filename);
+ if (tasks == NULL)
+ goto out_race;
+
while (!readdir_r(tasks, &dirent, &next) && next) {
char *end;
pid = strtol(dirent.d_name, &end, 10);
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 2/3] perf tools: Generalize event synthesizing routines
2009-10-26 21:23 ` [PATCH 1/3] perf record: process can disappear while reading its /proc/pid/tasks Arnaldo Carvalho de Melo
@ 2009-10-26 21:23 ` Arnaldo Carvalho de Melo
2009-10-26 21:23 ` [PATCH 3/3] perf top: Support userspace symbols too Arnaldo Carvalho de Melo
2009-10-27 13:03 ` [tip:perf/core] perf tools: Generalize event synthesizing routines tip-bot for Arnaldo Carvalho de Melo
2009-10-27 13:03 ` [tip:perf/core] perf record: Fix race where process can disappear while reading its /proc/pid/tasks tip-bot for Arnaldo Carvalho de Melo
1 sibling, 2 replies; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-26 21:23 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith
Because we will need it in 'perf top' to support userspace symbols for existing
threads.
Now we pass a callback that will receive the synthesized event and then write
it to the output file in 'perf record' and in the upcoming patch for 'perf top'
we will just immediatelly create the in memory representation of threads and
maps.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/Makefile | 1 +
tools/perf/builtin-record.c | 181 +++----------------------------------------
tools/perf/util/event.c | 177 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/event.h | 3 +
4 files changed, 191 insertions(+), 171 deletions(-)
create mode 100644 tools/perf/util/event.c
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 39a84be..800783d 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -380,6 +380,7 @@ LIB_OBJS += util/alias.o
LIB_OBJS += util/config.o
LIB_OBJS += util/ctype.o
LIB_OBJS += util/environment.o
+LIB_OBJS += util/event.o
LIB_OBJS += util/exec_cmd.o
LIB_OBJS += util/help.o
LIB_OBJS += util/levenshtein.o
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 9e1638c..4a73d89 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -109,6 +109,12 @@ static void write_output(void *buf, size_t size)
}
}
+static int process_synthesized_event(event_t *event)
+{
+ write_output(event, event->header.size);
+ return 0;
+}
+
static void mmap_read(struct mmap_data *md)
{
unsigned int head = mmap_read_head(md);
@@ -191,172 +197,6 @@ static void sig_atexit(void)
kill(getpid(), signr);
}
-static pid_t pid_synthesize_comm_event(pid_t pid, int full)
-{
- struct comm_event comm_ev;
- char filename[PATH_MAX];
- char bf[BUFSIZ];
- FILE *fp;
- size_t size = 0;
- DIR *tasks;
- struct dirent dirent, *next;
- pid_t tgid = 0;
-
- snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
-out_race:
- /*
- * We raced with a task exiting - just return:
- */
- if (verbose)
- fprintf(stderr, "couldn't open %s\n", filename);
- return 0;
- }
-
- memset(&comm_ev, 0, sizeof(comm_ev));
- while (!comm_ev.comm[0] || !comm_ev.pid) {
- if (fgets(bf, sizeof(bf), fp) == NULL)
- goto out_failure;
-
- if (memcmp(bf, "Name:", 5) == 0) {
- char *name = bf + 5;
- while (*name && isspace(*name))
- ++name;
- size = strlen(name) - 1;
- memcpy(comm_ev.comm, name, size++);
- } else if (memcmp(bf, "Tgid:", 5) == 0) {
- char *tgids = bf + 5;
- while (*tgids && isspace(*tgids))
- ++tgids;
- tgid = comm_ev.pid = atoi(tgids);
- }
- }
-
- comm_ev.header.type = PERF_RECORD_COMM;
- size = ALIGN(size, sizeof(u64));
- comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size);
-
- if (!full) {
- comm_ev.tid = pid;
-
- write_output(&comm_ev, comm_ev.header.size);
- goto out_fclose;
- }
-
- snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
-
- tasks = opendir(filename);
- if (tasks == NULL)
- goto out_race;
-
- while (!readdir_r(tasks, &dirent, &next) && next) {
- char *end;
- pid = strtol(dirent.d_name, &end, 10);
- if (*end)
- continue;
-
- comm_ev.tid = pid;
-
- write_output(&comm_ev, comm_ev.header.size);
- }
- closedir(tasks);
-
-out_fclose:
- fclose(fp);
- return tgid;
-
-out_failure:
- fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n",
- filename);
- exit(EXIT_FAILURE);
-}
-
-static void pid_synthesize_mmap_samples(pid_t pid, pid_t tgid)
-{
- char filename[PATH_MAX];
- FILE *fp;
-
- snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
- /*
- * We raced with a task exiting - just return:
- */
- if (verbose)
- fprintf(stderr, "couldn't open %s\n", filename);
- return;
- }
- while (1) {
- char bf[BUFSIZ], *pbf = bf;
- struct mmap_event mmap_ev = {
- .header = { .type = PERF_RECORD_MMAP },
- };
- int n;
- size_t size;
- if (fgets(bf, sizeof(bf), fp) == NULL)
- break;
-
- /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
- n = hex2u64(pbf, &mmap_ev.start);
- if (n < 0)
- continue;
- pbf += n + 1;
- n = hex2u64(pbf, &mmap_ev.len);
- if (n < 0)
- continue;
- pbf += n + 3;
- if (*pbf == 'x') { /* vm_exec */
- char *execname = strchr(bf, '/');
-
- /* Catch VDSO */
- if (execname == NULL)
- execname = strstr(bf, "[vdso]");
-
- if (execname == NULL)
- continue;
-
- size = strlen(execname);
- execname[size - 1] = '\0'; /* Remove \n */
- memcpy(mmap_ev.filename, execname, size);
- size = ALIGN(size, sizeof(u64));
- mmap_ev.len -= mmap_ev.start;
- mmap_ev.header.size = (sizeof(mmap_ev) -
- (sizeof(mmap_ev.filename) - size));
- mmap_ev.pid = tgid;
- mmap_ev.tid = pid;
-
- write_output(&mmap_ev, mmap_ev.header.size);
- }
- }
-
- fclose(fp);
-}
-
-static void synthesize_all(void)
-{
- DIR *proc;
- struct dirent dirent, *next;
-
- proc = opendir("/proc");
-
- while (!readdir_r(proc, &dirent, &next) && next) {
- char *end;
- pid_t pid, tgid;
-
- pid = strtol(dirent.d_name, &end, 10);
- if (*end) /* only interested in proper numerical dirents */
- continue;
-
- tgid = pid_synthesize_comm_event(pid, 1);
- pid_synthesize_mmap_samples(pid, tgid);
- }
-
- closedir(proc);
-}
-
static int group_fd;
static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int nr)
@@ -608,11 +448,10 @@ static int __cmd_record(int argc, const char **argv)
if (file_new)
perf_header__write(header, output);
- if (!system_wide) {
- pid_t tgid = pid_synthesize_comm_event(pid, 0);
- pid_synthesize_mmap_samples(pid, tgid);
- } else
- synthesize_all();
+ if (!system_wide)
+ event__synthesize_thread(pid, process_synthesized_event);
+ else
+ event__synthesize_threads(process_synthesized_event);
if (target_pid == -1 && argc) {
pid = fork();
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
new file mode 100644
index 0000000..1dae7e3
--- /dev/null
+++ b/tools/perf/util/event.c
@@ -0,0 +1,177 @@
+#include <linux/types.h>
+#include "event.h"
+#include "debug.h"
+#include "string.h"
+
+static pid_t event__synthesize_comm(pid_t pid, int full,
+ int (*process)(event_t *event))
+{
+ event_t ev;
+ char filename[PATH_MAX];
+ char bf[BUFSIZ];
+ FILE *fp;
+ size_t size = 0;
+ DIR *tasks;
+ struct dirent dirent, *next;
+ pid_t tgid = 0;
+
+ snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+out_race:
+ /*
+ * We raced with a task exiting - just return:
+ */
+ pr_debug("couldn't open %s\n", filename);
+ return 0;
+ }
+
+ memset(&ev.comm, 0, sizeof(ev.comm));
+ while (!ev.comm.comm[0] || !ev.comm.pid) {
+ if (fgets(bf, sizeof(bf), fp) == NULL)
+ goto out_failure;
+
+ if (memcmp(bf, "Name:", 5) == 0) {
+ char *name = bf + 5;
+ while (*name && isspace(*name))
+ ++name;
+ size = strlen(name) - 1;
+ memcpy(ev.comm.comm, name, size++);
+ } else if (memcmp(bf, "Tgid:", 5) == 0) {
+ char *tgids = bf + 5;
+ while (*tgids && isspace(*tgids))
+ ++tgids;
+ tgid = ev.comm.pid = atoi(tgids);
+ }
+ }
+
+ ev.comm.header.type = PERF_RECORD_COMM;
+ size = ALIGN(size, sizeof(u64));
+ ev.comm.header.size = sizeof(ev.comm) - (sizeof(ev.comm.comm) - size);
+
+ if (!full) {
+ ev.comm.tid = pid;
+
+ process(&ev);
+ goto out_fclose;
+ }
+
+ snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
+
+ tasks = opendir(filename);
+ if (tasks == NULL)
+ goto out_race;
+
+ while (!readdir_r(tasks, &dirent, &next) && next) {
+ char *end;
+ pid = strtol(dirent.d_name, &end, 10);
+ if (*end)
+ continue;
+
+ ev.comm.tid = pid;
+
+ process(&ev);
+ }
+ closedir(tasks);
+
+out_fclose:
+ fclose(fp);
+ return tgid;
+
+out_failure:
+ pr_warning("couldn't get COMM and pgid, malformed %s\n", filename);
+ return -1;
+}
+
+static int event__synthesize_mmap_events(pid_t pid, pid_t tgid,
+ int (*process)(event_t *event))
+{
+ char filename[PATH_MAX];
+ FILE *fp;
+
+ snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ /*
+ * We raced with a task exiting - just return:
+ */
+ pr_debug("couldn't open %s\n", filename);
+ return -1;
+ }
+
+ while (1) {
+ char bf[BUFSIZ], *pbf = bf;
+ event_t ev = {
+ .header = { .type = PERF_RECORD_MMAP },
+ };
+ int n;
+ size_t size;
+ if (fgets(bf, sizeof(bf), fp) == NULL)
+ break;
+
+ /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
+ n = hex2u64(pbf, &ev.mmap.start);
+ if (n < 0)
+ continue;
+ pbf += n + 1;
+ n = hex2u64(pbf, &ev.mmap.len);
+ if (n < 0)
+ continue;
+ pbf += n + 3;
+ if (*pbf == 'x') { /* vm_exec */
+ char *execname = strchr(bf, '/');
+
+ /* Catch VDSO */
+ if (execname == NULL)
+ execname = strstr(bf, "[vdso]");
+
+ if (execname == NULL)
+ continue;
+
+ size = strlen(execname);
+ execname[size - 1] = '\0'; /* Remove \n */
+ memcpy(ev.mmap.filename, execname, size);
+ size = ALIGN(size, sizeof(u64));
+ ev.mmap.len -= ev.mmap.start;
+ ev.mmap.header.size = (sizeof(ev.mmap) -
+ (sizeof(ev.mmap.filename) - size));
+ ev.mmap.pid = tgid;
+ ev.mmap.tid = pid;
+
+ process(&ev);
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+int event__synthesize_thread(pid_t pid, int (*process)(event_t *event))
+{
+ pid_t tgid = event__synthesize_comm(pid, 1, process);
+ if (tgid == -1)
+ return -1;
+ return event__synthesize_mmap_events(pid, tgid, process);
+}
+
+void event__synthesize_threads(int (*process)(event_t *event))
+{
+ DIR *proc;
+ struct dirent dirent, *next;
+
+ proc = opendir("/proc");
+
+ while (!readdir_r(proc, &dirent, &next) && next) {
+ char *end;
+ pid_t pid = strtol(dirent.d_name, &end, 10);
+
+ if (*end) /* only interested in proper numerical dirents */
+ continue;
+
+ event__synthesize_thread(pid, process);
+ }
+
+ closedir(proc);
+}
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
index d972b4b..2ae1177 100644
--- a/tools/perf/util/event.h
+++ b/tools/perf/util/event.h
@@ -111,4 +111,7 @@ struct map *map__clone(struct map *self);
int map__overlap(struct map *l, struct map *r);
size_t map__fprintf(struct map *self, FILE *fp);
+int event__synthesize_thread(pid_t pid, int (*process)(event_t *event));
+void event__synthesize_threads(int (*process)(event_t *event));
+
#endif /* __PERF_RECORD_H */
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 3/3] perf top: Support userspace symbols too
2009-10-26 21:23 ` [PATCH 2/3] perf tools: Generalize event synthesizing routines Arnaldo Carvalho de Melo
@ 2009-10-26 21:23 ` Arnaldo Carvalho de Melo
2009-10-27 13:00 ` Ingo Molnar
2009-10-27 13:04 ` [tip:perf/core] " tip-bot for Arnaldo Carvalho de Melo
2009-10-27 13:03 ` [tip:perf/core] perf tools: Generalize event synthesizing routines tip-bot for Arnaldo Carvalho de Melo
1 sibling, 2 replies; 743+ messages in thread
From: Arnaldo Carvalho de Melo @ 2009-10-26 21:23 UTC (permalink / raw)
To: Ingo Molnar
Cc: linux-kernel, Arnaldo Carvalho de Melo, Frederic Weisbecker,
Peter Zijlstra, Paul Mackerras, Mike Galbraith
Example:
Compiling the kernel with 'make -k 22 allyesconfig'
[root@emilia linux-2.6-tip]# perf top -r 90
------------------------------------------------------------------------------
PerfTop: 3669 irqs/sec kernel:59.9% [1000Hz cycles], (all, 8 CPUs)
------------------------------------------------------------------------------
samples pcnt function DSO
_______ _____ ________________________________ ________________
3062.00 6.5% clear_page_c [kernel]
2233.00 4.8% _int_malloc /lib64/libc-2.5.so
2100.00 4.5% yylex /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
2029.00 4.3% memset /lib64/libc-2.5.so
1224.00 2.6% page_fault [kernel]
1075.00 2.3% __GI_strlen /lib64/libc-2.5.so
863.00 1.8% sub_preempt_count [kernel]
822.00 1.8% __GI_memcpy /lib64/libc-2.5.so
810.00 1.7% __GI_vfprintf /lib64/libc-2.5.so
786.00 1.7% _int_free /lib64/libc-2.5.so
775.00 1.7% __GI_strcmp /lib64/libc-2.5.so
748.00 1.6% _spin_lock [kernel]
699.00 1.5% main /home/acme/git/build/allyesconfig/scripts/basic/fixdep
659.00 1.4% add_preempt_count [kernel]
649.00 1.4% yyparse /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
645.00 1.4% preempt_trace [kernel]
635.00 1.4% __GI___libc_free /lib64/libc-2.5.so
597.00 1.3% trace_preempt_on [kernel]
551.00 1.2% __GI___libc_malloc /lib64/libc-2.5.so
516.00 1.1% _spin_lock_irqsave [kernel]
481.00 1.0% copy_user_generic_string [kernel]
479.00 1.0% unmap_vmas [kernel]
429.00 0.9% _IO_file_xsputn_internal /lib64/libc-2.5.so
425.00 0.9% __GI_strncpy /lib64/libc-2.5.so
416.00 0.9% get_page_from_freelist [kernel]
414.00 0.9% malloc_consolidate /lib64/libc-2.5.so
406.00 0.9% get_parent_ip [kernel]
362.00 0.8% __rmqueue [kernel]
347.00 0.7% in_lock_functions [kernel]
316.00 0.7% __d_lookup [kernel]
[root@emilia linux-2.6-tip]#
More polishing is needed to print just DSO basename when not --verbose, etc.
Supporting a 'comm' column requires some more reworking of 'perf top' internals
as we will need to use something like the hist entries 'perf report' uses and
will be done in another patch.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-top.c | 143 ++++++++++++++++++++++++++++++++-------------
1 files changed, 101 insertions(+), 42 deletions(-)
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 4a9fe22..a02fc41 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -318,7 +318,7 @@ static void show_details(struct sym_entry *syme)
}
/*
- * Symbols will be added here in record_ip and will get out
+ * Symbols will be added here in event__process_sample and will get out
* after decayed.
*/
static LIST_HEAD(active_symbols);
@@ -459,18 +459,18 @@ static void print_sym_table(void)
}
if (nr_counters == 1)
- printf(" samples pcnt");
+ printf(" samples pcnt");
else
- printf(" weight samples pcnt");
+ printf(" weight samples pcnt");
if (verbose)
printf(" RIP ");
- printf(" kernel function\n");
- printf(" %s _______ _____",
+ printf(" function DSO\n");
+ printf(" %s _______ _____",
nr_counters == 1 ? " " : "______");
if (verbose)
- printf(" ________________");
- printf(" _______________\n\n");
+ printf(" ________________");
+ printf(" ________________________________ ________________\n\n");
for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) {
struct symbol *sym;
@@ -486,16 +486,15 @@ static void print_sym_table(void)
sum_ksamples));
if (nr_counters == 1 || !display_weighted)
- printf("%20.2f - ", syme->weight);
+ printf("%20.2f ", syme->weight);
else
- printf("%9.1f %10ld - ", syme->weight, syme->snap_count);
+ printf("%9.1f %10ld ", syme->weight, syme->snap_count);
percent_color_fprintf(stdout, "%4.1f%%", pcnt);
if (verbose)
- printf(" - %016llx", sym->start);
- printf(" : %s", sym->name);
- if (syme->map->dso->name[0] == '[')
- printf(" \t%s", syme->map->dso->name);
+ printf(" %016llx", sym->start);
+ printf(" %-32s", sym->name);
+ printf(" %s", syme->map->dso->short_name);
printf("\n");
}
}
@@ -818,41 +817,97 @@ static int parse_symbols(void)
return 0;
}
-/*
- * Binary search in the histogram table and record the hit:
- */
-static void record_ip(u64 ip, int counter)
+static void event__process_sample(const event_t *self, int counter)
{
+ u64 ip = self->ip.ip;
struct map *map;
- struct symbol *sym = kernel_maps__find_symbol(ip, &map);
-
- if (sym != NULL) {
- struct sym_entry *syme = dso__sym_priv(map->dso, sym);
-
- if (!syme->skip) {
- syme->count[counter]++;
- record_precise_ip(syme, counter, ip);
- pthread_mutex_lock(&active_symbols_lock);
- if (list_empty(&syme->node) || !syme->node.next)
- __list_insert_active_sym(syme);
- pthread_mutex_unlock(&active_symbols_lock);
+ struct sym_entry *syme;
+ struct symbol *sym;
+
+ switch (self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK) {
+ case PERF_RECORD_MISC_USER: {
+ struct thread *thread = threads__findnew(self->ip.pid);
+
+ if (thread == NULL)
return;
+
+ map = thread__find_map(thread, ip);
+ if (map != NULL) {
+ ip = map->map_ip(map, ip);
+ sym = map->dso->find_symbol(map->dso, ip);
+ if (sym == NULL)
+ return;
+ userspace_samples++;
+ break;
}
}
+ /*
+ * If this is outside of all known maps,
+ * and is a negative address, try to look it
+ * up in the kernel dso, as it might be a
+ * vsyscall or vdso (which executes in user-mode).
+ */
+ if ((long long)ip >= 0)
+ return;
+ /* Fall thru */
+ case PERF_RECORD_MISC_KERNEL:
+ sym = kernel_maps__find_symbol(ip, &map);
+ if (sym == NULL)
+ return;
+ break;
+ default:
+ return;
+ }
+
+ syme = dso__sym_priv(map->dso, sym);
- samples--;
+ if (!syme->skip) {
+ syme->count[counter]++;
+ record_precise_ip(syme, counter, ip);
+ pthread_mutex_lock(&active_symbols_lock);
+ if (list_empty(&syme->node) || !syme->node.next)
+ __list_insert_active_sym(syme);
+ pthread_mutex_unlock(&active_symbols_lock);
+ ++samples;
+ return;
+ }
}
-static void process_event(u64 ip, int counter, int user)
+static void event__process_mmap(event_t *self)
{
- samples++;
+ struct thread *thread = threads__findnew(self->mmap.pid);
+
+ if (thread != NULL) {
+ struct map *map = map__new(&self->mmap, NULL, 0,
+ sizeof(struct sym_entry),
+ symbol_filter);
+ if (map != NULL)
+ thread__insert_map(thread, map);
+ }
+}
- if (user) {
- userspace_samples++;
- return;
+static void event__process_comm(event_t *self)
+{
+ struct thread *thread = threads__findnew(self->comm.pid);
+
+ if (thread != NULL)
+ thread__set_comm(thread, self->comm.comm);
+}
+
+static int event__process(event_t *event)
+{
+ switch (event->header.type) {
+ case PERF_RECORD_COMM:
+ event__process_comm(event);
+ break;
+ case PERF_RECORD_MMAP:
+ event__process_mmap(event);
+ break;
+ default:
+ break;
}
- record_ip(ip, counter);
+ return 0;
}
struct mmap_data {
@@ -925,13 +980,11 @@ static void mmap_read_counter(struct mmap_data *md)
event = &event_copy;
}
+ if (event->header.type == PERF_RECORD_SAMPLE)
+ event__process_sample(event, md->counter);
+ else
+ event__process(event);
old += size;
-
- if (event->header.type == PERF_RECORD_SAMPLE) {
- int user =
- (event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_USER;
- process_event(event->ip.ip, md->counter, user);
- }
}
md->prev = old;
@@ -973,6 +1026,7 @@ static void start_counter(int i, int counter)
}
attr->inherit = (cpu < 0) && inherit;
+ attr->mmap = 1;
try_again:
fd[i][counter] = sys_perf_event_open(attr, target_pid, cpu, group_fd, 0);
@@ -1031,6 +1085,11 @@ static int __cmd_top(void)
int i, counter;
int ret;
+ if (target_pid != -1)
+ event__synthesize_thread(target_pid, event__process);
+ else
+ event__synthesize_threads(event__process);
+
for (i = 0; i < nr_cpus; i++) {
group_fd = -1;
for (counter = 0; counter < nr_counters; counter++)
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 3/3] perf top: Support userspace symbols too
2009-10-26 21:23 ` [PATCH 3/3] perf top: Support userspace symbols too Arnaldo Carvalho de Melo
@ 2009-10-27 13:00 ` Ingo Molnar
2009-10-27 13:04 ` [tip:perf/core] " tip-bot for Arnaldo Carvalho de Melo
1 sibling, 0 replies; 743+ messages in thread
From: Ingo Molnar @ 2009-10-27 13:00 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: linux-kernel, Frederic Weisbecker, Peter Zijlstra,
Paul Mackerras, Mike Galbraith
* Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> Example:
>
> Compiling the kernel with 'make -k 22 allyesconfig'
>
> [root@emilia linux-2.6-tip]# perf top -r 90
> ------------------------------------------------------------------------------
> PerfTop: 3669 irqs/sec kernel:59.9% [1000Hz cycles], (all, 8 CPUs)
> ------------------------------------------------------------------------------
>
> samples pcnt function DSO
> _______ _____ ________________________________ ________________
>
> 3062.00 6.5% clear_page_c [kernel]
> 2233.00 4.8% _int_malloc /lib64/libc-2.5.so
> 2100.00 4.5% yylex /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
> 2029.00 4.3% memset /lib64/libc-2.5.so
> 1224.00 2.6% page_fault [kernel]
> 1075.00 2.3% __GI_strlen /lib64/libc-2.5.so
> 863.00 1.8% sub_preempt_count [kernel]
> 822.00 1.8% __GI_memcpy /lib64/libc-2.5.so
> 810.00 1.7% __GI_vfprintf /lib64/libc-2.5.so
> 786.00 1.7% _int_free /lib64/libc-2.5.so
> 775.00 1.7% __GI_strcmp /lib64/libc-2.5.so
> 748.00 1.6% _spin_lock [kernel]
> 699.00 1.5% main /home/acme/git/build/allyesconfig/scripts/basic/fixdep
> 659.00 1.4% add_preempt_count [kernel]
> 649.00 1.4% yyparse /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
> 645.00 1.4% preempt_trace [kernel]
> 635.00 1.4% __GI___libc_free /lib64/libc-2.5.so
> 597.00 1.3% trace_preempt_on [kernel]
> 551.00 1.2% __GI___libc_malloc /lib64/libc-2.5.so
> 516.00 1.1% _spin_lock_irqsave [kernel]
> 481.00 1.0% copy_user_generic_string [kernel]
> 479.00 1.0% unmap_vmas [kernel]
> 429.00 0.9% _IO_file_xsputn_internal /lib64/libc-2.5.so
> 425.00 0.9% __GI_strncpy /lib64/libc-2.5.so
> 416.00 0.9% get_page_from_freelist [kernel]
> 414.00 0.9% malloc_consolidate /lib64/libc-2.5.so
> 406.00 0.9% get_parent_ip [kernel]
> 362.00 0.8% __rmqueue [kernel]
> 347.00 0.7% in_lock_functions [kernel]
> 316.00 0.7% __d_lookup [kernel]
>
> [root@emilia linux-2.6-tip]#
That's a really nice feature! I tried it and works well here too.
Ingo
^ permalink raw reply [flat|nested] 743+ messages in thread
* [tip:perf/core] perf top: Support userspace symbols too
2009-10-26 21:23 ` [PATCH 3/3] perf top: Support userspace symbols too Arnaldo Carvalho de Melo
2009-10-27 13:00 ` Ingo Molnar
@ 2009-10-27 13:04 ` tip-bot for Arnaldo Carvalho de Melo
1 sibling, 0 replies; 743+ messages in thread
From: tip-bot for Arnaldo Carvalho de Melo @ 2009-10-27 13:04 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, paulus, acme, hpa, mingo, efault, peterz, fweisbec,
tglx, mingo
Commit-ID: 5b2bb75a0d4b08cd16bc35ecd674f957fc3b0eb7
Gitweb: http://git.kernel.org/tip/5b2bb75a0d4b08cd16bc35ecd674f957fc3b0eb7
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
AuthorDate: Mon, 26 Oct 2009 19:23:19 -0200
Committer: Ingo Molnar <mingo@elte.hu>
CommitDate: Tue, 27 Oct 2009 13:51:54 +0100
perf top: Support userspace symbols too
Example:
Compiling the kernel with 'make -k 22 allyesconfig'
[root@emilia linux-2.6-tip]# perf top -r 90
------------------------------------------------------------------------------
PerfTop: 3669 irqs/sec kernel:59.9% [1000Hz cycles], (all, 8 CPUs)
------------------------------------------------------------------------------
samples pcnt function DSO
_______ _____ ________________________________ ________________
3062.00 6.5% clear_page_c [kernel]
2233.00 4.8% _int_malloc /lib64/libc-2.5.so
2100.00 4.5% yylex /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
2029.00 4.3% memset /lib64/libc-2.5.so
1224.00 2.6% page_fault [kernel]
1075.00 2.3% __GI_strlen /lib64/libc-2.5.so
863.00 1.8% sub_preempt_count [kernel]
822.00 1.8% __GI_memcpy /lib64/libc-2.5.so
810.00 1.7% __GI_vfprintf /lib64/libc-2.5.so
786.00 1.7% _int_free /lib64/libc-2.5.so
775.00 1.7% __GI_strcmp /lib64/libc-2.5.so
748.00 1.6% _spin_lock [kernel]
699.00 1.5% main /home/acme/git/build/allyesconfig/scripts/basic/fixdep
659.00 1.4% add_preempt_count [kernel]
649.00 1.4% yyparse /home/acme/git/build/allyesconfig/scripts/genksyms/genksyms
645.00 1.4% preempt_trace [kernel]
635.00 1.4% __GI___libc_free /lib64/libc-2.5.so
597.00 1.3% trace_preempt_on [kernel]
551.00 1.2% __GI___libc_malloc /lib64/libc-2.5.so
516.00 1.1% _spin_lock_irqsave [kernel]
481.00 1.0% copy_user_generic_string [kernel]
479.00 1.0% unmap_vmas [kernel]
429.00 0.9% _IO_file_xsputn_internal /lib64/libc-2.5.so
425.00 0.9% __GI_strncpy /lib64/libc-2.5.so
416.00 0.9% get_page_from_freelist [kernel]
414.00 0.9% malloc_consolidate /lib64/libc-2.5.so
406.00 0.9% get_parent_ip [kernel]
362.00 0.8% __rmqueue [kernel]
347.00 0.7% in_lock_functions [kernel]
316.00 0.7% __d_lookup [kernel]
[root@emilia linux-2.6-tip]#
More polishing is needed to print just DSO basename when not
--verbose, etc.
Supporting a 'comm' column requires some more reworking of 'perf
top' internals as we will need to use something like the hist
entries 'perf report' uses and will be done in another patch.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <1256592199-9608-3-git-send-email-acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
---
tools/perf/builtin-top.c | 143 ++++++++++++++++++++++++++++++++-------------
1 files changed, 101 insertions(+), 42 deletions(-)
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 4a9fe22..a02fc41 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -318,7 +318,7 @@ static void show_details(struct sym_entry *syme)
}
/*
- * Symbols will be added here in record_ip and will get out
+ * Symbols will be added here in event__process_sample and will get out
* after decayed.
*/
static LIST_HEAD(active_symbols);
@@ -459,18 +459,18 @@ static void print_sym_table(void)
}
if (nr_counters == 1)
- printf(" samples pcnt");
+ printf(" samples pcnt");
else
- printf(" weight samples pcnt");
+ printf(" weight samples pcnt");
if (verbose)
printf(" RIP ");
- printf(" kernel function\n");
- printf(" %s _______ _____",
+ printf(" function DSO\n");
+ printf(" %s _______ _____",
nr_counters == 1 ? " " : "______");
if (verbose)
- printf(" ________________");
- printf(" _______________\n\n");
+ printf(" ________________");
+ printf(" ________________________________ ________________\n\n");
for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) {
struct symbol *sym;
@@ -486,16 +486,15 @@ static void print_sym_table(void)
sum_ksamples));
if (nr_counters == 1 || !display_weighted)
- printf("%20.2f - ", syme->weight);
+ printf("%20.2f ", syme->weight);
else
- printf("%9.1f %10ld - ", syme->weight, syme->snap_count);
+ printf("%9.1f %10ld ", syme->weight, syme->snap_count);
percent_color_fprintf(stdout, "%4.1f%%", pcnt);
if (verbose)
- printf(" - %016llx", sym->start);
- printf(" : %s", sym->name);
- if (syme->map->dso->name[0] == '[')
- printf(" \t%s", syme->map->dso->name);
+ printf(" %016llx", sym->start);
+ printf(" %-32s", sym->name);
+ printf(" %s", syme->map->dso->short_name);
printf("\n");
}
}
@@ -818,41 +817,97 @@ static int parse_symbols(void)
return 0;
}
-/*
- * Binary search in the histogram table and record the hit:
- */
-static void record_ip(u64 ip, int counter)
+static void event__process_sample(const event_t *self, int counter)
{
+ u64 ip = self->ip.ip;
struct map *map;
- struct symbol *sym = kernel_maps__find_symbol(ip, &map);
-
- if (sym != NULL) {
- struct sym_entry *syme = dso__sym_priv(map->dso, sym);
-
- if (!syme->skip) {
- syme->count[counter]++;
- record_precise_ip(syme, counter, ip);
- pthread_mutex_lock(&active_symbols_lock);
- if (list_empty(&syme->node) || !syme->node.next)
- __list_insert_active_sym(syme);
- pthread_mutex_unlock(&active_symbols_lock);
+ struct sym_entry *syme;
+ struct symbol *sym;
+
+ switch (self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK) {
+ case PERF_RECORD_MISC_USER: {
+ struct thread *thread = threads__findnew(self->ip.pid);
+
+ if (thread == NULL)
return;
+
+ map = thread__find_map(thread, ip);
+ if (map != NULL) {
+ ip = map->map_ip(map, ip);
+ sym = map->dso->find_symbol(map->dso, ip);
+ if (sym == NULL)
+ return;
+ userspace_samples++;
+ break;
}
}
+ /*
+ * If this is outside of all known maps,
+ * and is a negative address, try to look it
+ * up in the kernel dso, as it might be a
+ * vsyscall or vdso (which executes in user-mode).
+ */
+ if ((long long)ip >= 0)
+ return;
+ /* Fall thru */
+ case PERF_RECORD_MISC_KERNEL:
+ sym = kernel_maps__find_symbol(ip, &map);
+ if (sym == NULL)
+ return;
+ break;
+ default:
+ return;
+ }
+
+ syme = dso__sym_priv(map->dso, sym);
- samples--;
+ if (!syme->skip) {
+ syme->count[counter]++;
+ record_precise_ip(syme, counter, ip);
+ pthread_mutex_lock(&active_symbols_lock);
+ if (list_empty(&syme->node) || !syme->node.next)
+ __list_insert_active_sym(syme);
+ pthread_mutex_unlock(&active_symbols_lock);
+ ++samples;
+ return;
+ }
}
-static void process_event(u64 ip, int counter, int user)
+static void event__process_mmap(event_t *self)
{
- samples++;
+ struct thread *thread = threads__findnew(self->mmap.pid);
+
+ if (thread != NULL) {
+ struct map *map = map__new(&self->mmap, NULL, 0,
+ sizeof(struct sym_entry),
+ symbol_filter);
+ if (map != NULL)
+ thread__insert_map(thread, map);
+ }
+}
- if (user) {
- userspace_samples++;
- return;
+static void event__process_comm(event_t *self)
+{
+ struct thread *thread = threads__findnew(self->comm.pid);
+
+ if (thread != NULL)
+ thread__set_comm(thread, self->comm.comm);
+}
+
+static int event__process(event_t *event)
+{
+ switch (event->header.type) {
+ case PERF_RECORD_COMM:
+ event__process_comm(event);
+ break;
+ case PERF_RECORD_MMAP:
+ event__process_mmap(event);
+ break;
+ default:
+ break;
}
- record_ip(ip, counter);
+ return 0;
}
struct mmap_data {
@@ -925,13 +980,11 @@ static void mmap_read_counter(struct mmap_data *md)
event = &event_copy;
}
+ if (event->header.type == PERF_RECORD_SAMPLE)
+ event__process_sample(event, md->counter);
+ else
+ event__process(event);
old += size;
-
- if (event->header.type == PERF_RECORD_SAMPLE) {
- int user =
- (event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_USER;
- process_event(event->ip.ip, md->counter, user);
- }
}
md->prev = old;
@@ -973,6 +1026,7 @@ static void start_counter(int i, int counter)
}
attr->inherit = (cpu < 0) && inherit;
+ attr->mmap = 1;
try_again:
fd[i][counter] = sys_perf_event_open(attr, target_pid, cpu, group_fd, 0);
@@ -1031,6 +1085,11 @@ static int __cmd_top(void)
int i, counter;
int ret;
+ if (target_pid != -1)
+ event__synthesize_thread(target_pid, event__process);
+ else
+ event__synthesize_threads(event__process);
+
for (i = 0; i < nr_cpus; i++) {
group_fd = -1;
for (counter = 0; counter < nr_counters; counter++)
^ permalink raw reply [flat|nested] 743+ messages in thread
* [tip:perf/core] perf tools: Generalize event synthesizing routines
2009-10-26 21:23 ` [PATCH 2/3] perf tools: Generalize event synthesizing routines Arnaldo Carvalho de Melo
2009-10-26 21:23 ` [PATCH 3/3] perf top: Support userspace symbols too Arnaldo Carvalho de Melo
@ 2009-10-27 13:03 ` tip-bot for Arnaldo Carvalho de Melo
1 sibling, 0 replies; 743+ messages in thread
From: tip-bot for Arnaldo Carvalho de Melo @ 2009-10-27 13:03 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, paulus, acme, hpa, mingo, efault, peterz, fweisbec,
tglx, mingo
Commit-ID: 234fbbf508c58c5084292b11b242377553897459
Gitweb: http://git.kernel.org/tip/234fbbf508c58c5084292b11b242377553897459
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
AuthorDate: Mon, 26 Oct 2009 19:23:18 -0200
Committer: Ingo Molnar <mingo@elte.hu>
CommitDate: Tue, 27 Oct 2009 13:51:53 +0100
perf tools: Generalize event synthesizing routines
Because we will need it in 'perf top' to support userspace
symbols for existing threads.
Now we pass a callback that will receive the synthesized event
and then write it to the output file in 'perf record' and in the
upcoming patch for 'perf top' we will just immediatelly create
the in memory representation of threads and maps.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <1256592199-9608-2-git-send-email-acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
---
tools/perf/Makefile | 1 +
tools/perf/builtin-record.c | 181 +++----------------------------------------
tools/perf/util/event.c | 177 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/event.h | 3 +
4 files changed, 191 insertions(+), 171 deletions(-)
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 0a40c29..9f4488d 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -380,6 +380,7 @@ LIB_OBJS += util/alias.o
LIB_OBJS += util/config.o
LIB_OBJS += util/ctype.o
LIB_OBJS += util/environment.o
+LIB_OBJS += util/event.o
LIB_OBJS += util/exec_cmd.o
LIB_OBJS += util/help.o
LIB_OBJS += util/levenshtein.o
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 9e1638c..4a73d89 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -109,6 +109,12 @@ static void write_output(void *buf, size_t size)
}
}
+static int process_synthesized_event(event_t *event)
+{
+ write_output(event, event->header.size);
+ return 0;
+}
+
static void mmap_read(struct mmap_data *md)
{
unsigned int head = mmap_read_head(md);
@@ -191,172 +197,6 @@ static void sig_atexit(void)
kill(getpid(), signr);
}
-static pid_t pid_synthesize_comm_event(pid_t pid, int full)
-{
- struct comm_event comm_ev;
- char filename[PATH_MAX];
- char bf[BUFSIZ];
- FILE *fp;
- size_t size = 0;
- DIR *tasks;
- struct dirent dirent, *next;
- pid_t tgid = 0;
-
- snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
-out_race:
- /*
- * We raced with a task exiting - just return:
- */
- if (verbose)
- fprintf(stderr, "couldn't open %s\n", filename);
- return 0;
- }
-
- memset(&comm_ev, 0, sizeof(comm_ev));
- while (!comm_ev.comm[0] || !comm_ev.pid) {
- if (fgets(bf, sizeof(bf), fp) == NULL)
- goto out_failure;
-
- if (memcmp(bf, "Name:", 5) == 0) {
- char *name = bf + 5;
- while (*name && isspace(*name))
- ++name;
- size = strlen(name) - 1;
- memcpy(comm_ev.comm, name, size++);
- } else if (memcmp(bf, "Tgid:", 5) == 0) {
- char *tgids = bf + 5;
- while (*tgids && isspace(*tgids))
- ++tgids;
- tgid = comm_ev.pid = atoi(tgids);
- }
- }
-
- comm_ev.header.type = PERF_RECORD_COMM;
- size = ALIGN(size, sizeof(u64));
- comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size);
-
- if (!full) {
- comm_ev.tid = pid;
-
- write_output(&comm_ev, comm_ev.header.size);
- goto out_fclose;
- }
-
- snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
-
- tasks = opendir(filename);
- if (tasks == NULL)
- goto out_race;
-
- while (!readdir_r(tasks, &dirent, &next) && next) {
- char *end;
- pid = strtol(dirent.d_name, &end, 10);
- if (*end)
- continue;
-
- comm_ev.tid = pid;
-
- write_output(&comm_ev, comm_ev.header.size);
- }
- closedir(tasks);
-
-out_fclose:
- fclose(fp);
- return tgid;
-
-out_failure:
- fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n",
- filename);
- exit(EXIT_FAILURE);
-}
-
-static void pid_synthesize_mmap_samples(pid_t pid, pid_t tgid)
-{
- char filename[PATH_MAX];
- FILE *fp;
-
- snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
- /*
- * We raced with a task exiting - just return:
- */
- if (verbose)
- fprintf(stderr, "couldn't open %s\n", filename);
- return;
- }
- while (1) {
- char bf[BUFSIZ], *pbf = bf;
- struct mmap_event mmap_ev = {
- .header = { .type = PERF_RECORD_MMAP },
- };
- int n;
- size_t size;
- if (fgets(bf, sizeof(bf), fp) == NULL)
- break;
-
- /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
- n = hex2u64(pbf, &mmap_ev.start);
- if (n < 0)
- continue;
- pbf += n + 1;
- n = hex2u64(pbf, &mmap_ev.len);
- if (n < 0)
- continue;
- pbf += n + 3;
- if (*pbf == 'x') { /* vm_exec */
- char *execname = strchr(bf, '/');
-
- /* Catch VDSO */
- if (execname == NULL)
- execname = strstr(bf, "[vdso]");
-
- if (execname == NULL)
- continue;
-
- size = strlen(execname);
- execname[size - 1] = '\0'; /* Remove \n */
- memcpy(mmap_ev.filename, execname, size);
- size = ALIGN(size, sizeof(u64));
- mmap_ev.len -= mmap_ev.start;
- mmap_ev.header.size = (sizeof(mmap_ev) -
- (sizeof(mmap_ev.filename) - size));
- mmap_ev.pid = tgid;
- mmap_ev.tid = pid;
-
- write_output(&mmap_ev, mmap_ev.header.size);
- }
- }
-
- fclose(fp);
-}
-
-static void synthesize_all(void)
-{
- DIR *proc;
- struct dirent dirent, *next;
-
- proc = opendir("/proc");
-
- while (!readdir_r(proc, &dirent, &next) && next) {
- char *end;
- pid_t pid, tgid;
-
- pid = strtol(dirent.d_name, &end, 10);
- if (*end) /* only interested in proper numerical dirents */
- continue;
-
- tgid = pid_synthesize_comm_event(pid, 1);
- pid_synthesize_mmap_samples(pid, tgid);
- }
-
- closedir(proc);
-}
-
static int group_fd;
static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int nr)
@@ -608,11 +448,10 @@ static int __cmd_record(int argc, const char **argv)
if (file_new)
perf_header__write(header, output);
- if (!system_wide) {
- pid_t tgid = pid_synthesize_comm_event(pid, 0);
- pid_synthesize_mmap_samples(pid, tgid);
- } else
- synthesize_all();
+ if (!system_wide)
+ event__synthesize_thread(pid, process_synthesized_event);
+ else
+ event__synthesize_threads(process_synthesized_event);
if (target_pid == -1 && argc) {
pid = fork();
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
new file mode 100644
index 0000000..1dae7e3
--- /dev/null
+++ b/tools/perf/util/event.c
@@ -0,0 +1,177 @@
+#include <linux/types.h>
+#include "event.h"
+#include "debug.h"
+#include "string.h"
+
+static pid_t event__synthesize_comm(pid_t pid, int full,
+ int (*process)(event_t *event))
+{
+ event_t ev;
+ char filename[PATH_MAX];
+ char bf[BUFSIZ];
+ FILE *fp;
+ size_t size = 0;
+ DIR *tasks;
+ struct dirent dirent, *next;
+ pid_t tgid = 0;
+
+ snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+out_race:
+ /*
+ * We raced with a task exiting - just return:
+ */
+ pr_debug("couldn't open %s\n", filename);
+ return 0;
+ }
+
+ memset(&ev.comm, 0, sizeof(ev.comm));
+ while (!ev.comm.comm[0] || !ev.comm.pid) {
+ if (fgets(bf, sizeof(bf), fp) == NULL)
+ goto out_failure;
+
+ if (memcmp(bf, "Name:", 5) == 0) {
+ char *name = bf + 5;
+ while (*name && isspace(*name))
+ ++name;
+ size = strlen(name) - 1;
+ memcpy(ev.comm.comm, name, size++);
+ } else if (memcmp(bf, "Tgid:", 5) == 0) {
+ char *tgids = bf + 5;
+ while (*tgids && isspace(*tgids))
+ ++tgids;
+ tgid = ev.comm.pid = atoi(tgids);
+ }
+ }
+
+ ev.comm.header.type = PERF_RECORD_COMM;
+ size = ALIGN(size, sizeof(u64));
+ ev.comm.header.size = sizeof(ev.comm) - (sizeof(ev.comm.comm) - size);
+
+ if (!full) {
+ ev.comm.tid = pid;
+
+ process(&ev);
+ goto out_fclose;
+ }
+
+ snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
+
+ tasks = opendir(filename);
+ if (tasks == NULL)
+ goto out_race;
+
+ while (!readdir_r(tasks, &dirent, &next) && next) {
+ char *end;
+ pid = strtol(dirent.d_name, &end, 10);
+ if (*end)
+ continue;
+
+ ev.comm.tid = pid;
+
+ process(&ev);
+ }
+ closedir(tasks);
+
+out_fclose:
+ fclose(fp);
+ return tgid;
+
+out_failure:
+ pr_warning("couldn't get COMM and pgid, malformed %s\n", filename);
+ return -1;
+}
+
+static int event__synthesize_mmap_events(pid_t pid, pid_t tgid,
+ int (*process)(event_t *event))
+{
+ char filename[PATH_MAX];
+ FILE *fp;
+
+ snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ /*
+ * We raced with a task exiting - just return:
+ */
+ pr_debug("couldn't open %s\n", filename);
+ return -1;
+ }
+
+ while (1) {
+ char bf[BUFSIZ], *pbf = bf;
+ event_t ev = {
+ .header = { .type = PERF_RECORD_MMAP },
+ };
+ int n;
+ size_t size;
+ if (fgets(bf, sizeof(bf), fp) == NULL)
+ break;
+
+ /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
+ n = hex2u64(pbf, &ev.mmap.start);
+ if (n < 0)
+ continue;
+ pbf += n + 1;
+ n = hex2u64(pbf, &ev.mmap.len);
+ if (n < 0)
+ continue;
+ pbf += n + 3;
+ if (*pbf == 'x') { /* vm_exec */
+ char *execname = strchr(bf, '/');
+
+ /* Catch VDSO */
+ if (execname == NULL)
+ execname = strstr(bf, "[vdso]");
+
+ if (execname == NULL)
+ continue;
+
+ size = strlen(execname);
+ execname[size - 1] = '\0'; /* Remove \n */
+ memcpy(ev.mmap.filename, execname, size);
+ size = ALIGN(size, sizeof(u64));
+ ev.mmap.len -= ev.mmap.start;
+ ev.mmap.header.size = (sizeof(ev.mmap) -
+ (sizeof(ev.mmap.filename) - size));
+ ev.mmap.pid = tgid;
+ ev.mmap.tid = pid;
+
+ process(&ev);
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+int event__synthesize_thread(pid_t pid, int (*process)(event_t *event))
+{
+ pid_t tgid = event__synthesize_comm(pid, 1, process);
+ if (tgid == -1)
+ return -1;
+ return event__synthesize_mmap_events(pid, tgid, process);
+}
+
+void event__synthesize_threads(int (*process)(event_t *event))
+{
+ DIR *proc;
+ struct dirent dirent, *next;
+
+ proc = opendir("/proc");
+
+ while (!readdir_r(proc, &dirent, &next) && next) {
+ char *end;
+ pid_t pid = strtol(dirent.d_name, &end, 10);
+
+ if (*end) /* only interested in proper numerical dirents */
+ continue;
+
+ event__synthesize_thread(pid, process);
+ }
+
+ closedir(proc);
+}
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
index d972b4b..2ae1177 100644
--- a/tools/perf/util/event.h
+++ b/tools/perf/util/event.h
@@ -111,4 +111,7 @@ struct map *map__clone(struct map *self);
int map__overlap(struct map *l, struct map *r);
size_t map__fprintf(struct map *self, FILE *fp);
+int event__synthesize_thread(pid_t pid, int (*process)(event_t *event));
+void event__synthesize_threads(int (*process)(event_t *event));
+
#endif /* __PERF_RECORD_H */
^ permalink raw reply [flat|nested] 743+ messages in thread
* [tip:perf/core] perf record: Fix race where process can disappear while reading its /proc/pid/tasks
2009-10-26 21:23 ` [PATCH 1/3] perf record: process can disappear while reading its /proc/pid/tasks Arnaldo Carvalho de Melo
2009-10-26 21:23 ` [PATCH 2/3] perf tools: Generalize event synthesizing routines Arnaldo Carvalho de Melo
@ 2009-10-27 13:03 ` tip-bot for Arnaldo Carvalho de Melo
1 sibling, 0 replies; 743+ messages in thread
From: tip-bot for Arnaldo Carvalho de Melo @ 2009-10-27 13:03 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, paulus, acme, hpa, mingo, efault, peterz, fweisbec,
tglx, mingo
Commit-ID: 7f3bedcc93f935631d2363f23de1cc80f04fdf3e
Gitweb: http://git.kernel.org/tip/7f3bedcc93f935631d2363f23de1cc80f04fdf3e
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
AuthorDate: Mon, 26 Oct 2009 19:23:17 -0200
Committer: Ingo Molnar <mingo@elte.hu>
CommitDate: Tue, 27 Oct 2009 13:51:53 +0100
perf record: Fix race where process can disappear while reading its /proc/pid/tasks
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <1256592199-9608-1-git-send-email-acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
---
tools/perf/builtin-record.c | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index ac5ddff..9e1638c 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -206,6 +206,7 @@ static pid_t pid_synthesize_comm_event(pid_t pid, int full)
fp = fopen(filename, "r");
if (fp == NULL) {
+out_race:
/*
* We raced with a task exiting - just return:
*/
@@ -247,6 +248,9 @@ static pid_t pid_synthesize_comm_event(pid_t pid, int full)
snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
tasks = opendir(filename);
+ if (tasks == NULL)
+ goto out_race;
+
while (!readdir_r(tasks, &dirent, &next) && next) {
char *end;
pid = strtol(dirent.d_name, &end, 10);
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] trivial: remove duplicated MIN macro from tehuti.
[not found] <y>
` (7 preceding siblings ...)
2009-10-26 21:23 ` [PATCH 1/3] perf record: process can disappear while reading its /proc/pid/tasks Arnaldo Carvalho de Melo
@ 2009-11-02 20:45 ` Thiago Farina
2009-11-04 1:36 ` Yang Hongyang
2009-11-23 8:41 ` Microblaze ftrace support y
` (151 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Thiago Farina @ 2009-11-02 20:45 UTC (permalink / raw)
To: trivial
Cc: baum, andy, davem, shemminger, apkm, ben, yanghy, netdev,
linux-kernel, Thiago Farina
Since the kernel api already has the macro "min",
just use it instead of declaring another one.
Signed-off-by: Thiago Farina <tfransosi@gmail.com>
---
drivers/net/tehuti.c | 4 ++--
drivers/net/tehuti.h | 2 --
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/net/tehuti.c b/drivers/net/tehuti.c
index ec9dfb2..8d116a9 100644
--- a/drivers/net/tehuti.c
+++ b/drivers/net/tehuti.c
@@ -1878,7 +1878,7 @@ static void bdx_tx_push_desc_safe(struct bdx_priv *priv, void *data, int size)
udelay(50); /* give hw a chance to clean fifo */
continue;
}
- avail = MIN(avail, size);
+ avail = min(avail, size);
DBG("about to push %d bytes starting %p size %d\n", avail,
data, size);
bdx_tx_push_desc(priv, data, avail);
@@ -1889,7 +1889,7 @@ static void bdx_tx_push_desc_safe(struct bdx_priv *priv, void *data, int size)
}
static const struct net_device_ops bdx_netdev_ops = {
- .ndo_open = bdx_open,
+ .ndo_open = bdx_open,
.ndo_stop = bdx_close,
.ndo_start_xmit = bdx_tx_transmit,
.ndo_validate_addr = eth_validate_addr,
diff --git a/drivers/net/tehuti.h b/drivers/net/tehuti.h
index 4fc875e..1241419 100644
--- a/drivers/net/tehuti.h
+++ b/drivers/net/tehuti.h
@@ -76,8 +76,6 @@
#define FIFO_SIZE 4096
#define FIFO_EXTRA_SPACE 1024
-#define MIN(x, y) ((x) < (y) ? (x) : (y))
-
#if BITS_PER_LONG == 64
# define H32_64(x) (u32) ((u64)(x) >> 32)
# define L32_64(x) (u32) ((u64)(x) & 0xffffffff)
--
1.6.5.1.61.ge79999
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] trivial: remove duplicated MIN macro from tehuti.
2009-11-02 20:45 ` [PATCH] trivial: remove duplicated MIN macro from tehuti Thiago Farina
@ 2009-11-04 1:36 ` Yang Hongyang
0 siblings, 0 replies; 743+ messages in thread
From: Yang Hongyang @ 2009-11-04 1:36 UTC (permalink / raw)
To: Thiago Farina
Cc: trivial, baum, andy, davem, shemminger, apkm, ben, netdev, linux-kernel
Thiago Farina wrote:
> Since the kernel api already has the macro "min",
> just use it instead of declaring another one.
>
> Signed-off-by: Thiago Farina <tfransosi@gmail.com>
> ---
> drivers/net/tehuti.c | 4 ++--
> drivers/net/tehuti.h | 2 --
> 2 files changed, 2 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/tehuti.c b/drivers/net/tehuti.c
> index ec9dfb2..8d116a9 100644
> --- a/drivers/net/tehuti.c
> +++ b/drivers/net/tehuti.c
> @@ -1878,7 +1878,7 @@ static void bdx_tx_push_desc_safe(struct bdx_priv *priv, void *data, int size)
> udelay(50); /* give hw a chance to clean fifo */
> continue;
> }
> - avail = MIN(avail, size);
> + avail = min(avail, size);
> DBG("about to push %d bytes starting %p size %d\n", avail,
> data, size);
> bdx_tx_push_desc(priv, data, avail);
> @@ -1889,7 +1889,7 @@ static void bdx_tx_push_desc_safe(struct bdx_priv *priv, void *data, int size)
> }
>
> static const struct net_device_ops bdx_netdev_ops = {
> - .ndo_open = bdx_open,
> + .ndo_open = bdx_open,
Why are you change TAB to SPACES here?
> .ndo_stop = bdx_close,
> .ndo_start_xmit = bdx_tx_transmit,
> .ndo_validate_addr = eth_validate_addr,
> diff --git a/drivers/net/tehuti.h b/drivers/net/tehuti.h
> index 4fc875e..1241419 100644
> --- a/drivers/net/tehuti.h
> +++ b/drivers/net/tehuti.h
> @@ -76,8 +76,6 @@
> #define FIFO_SIZE 4096
> #define FIFO_EXTRA_SPACE 1024
>
> -#define MIN(x, y) ((x) < (y) ? (x) : (y))
> -
> #if BITS_PER_LONG == 64
> # define H32_64(x) (u32) ((u64)(x) >> 32)
> # define L32_64(x) (u32) ((u64)(x) & 0xffffffff)
--
Regards
Yang Hongyang
^ permalink raw reply [flat|nested] 743+ messages in thread
* Microblaze ftrace support
[not found] <y>
` (8 preceding siblings ...)
2009-11-02 20:45 ` [PATCH] trivial: remove duplicated MIN macro from tehuti Thiago Farina
@ 2009-11-23 8:41 ` y
2009-11-23 8:41 ` [PATCH 1/5] microblaze: ftrace: add static function tracer y
2010-01-06 19:24 ` [PATCH] mfd: Update WM8350 drivers for changed interrupt numbers Mark Brown
` (150 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt
Hi all,
I did ftrace support for Microblaze. I would like to ask you for review.
I don't expect too much troubles. The main thing is part of recordmcount.pl
- here could be a small problem because of MIPS patches.
It is based on latest Linus tree.
I have that patches in my tree and they are in next tree too. They will go through my tree.
Thanks,
Michal
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/5] microblaze: ftrace: add static function tracer
2009-11-23 8:41 ` Microblaze ftrace support y
@ 2009-11-23 8:41 ` y
2009-11-23 8:41 ` [PATCH 2/5] microblaze: ftrace: enable HAVE_FUNCTION_TRACE_MCOUNT_TEST y
0 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt, Michal Simek
From: Michal Simek <monstr@monstr.eu>
If -pg of gcc is enabled with CONFIG_FUNCTION_TRACER=y. a calling to
_mcount will be inserted into each kernel function. so, there is a
possibility to trace the kernel functions in _mcount.
This patch add the specific _mcount support for static function
tracing. by default, ftrace_trace_function is initialized as
ftrace_stub(an empty function), so, the default _mcount will introduce
very little overhead. after enabling ftrace in user-space, it will jump
to a real tracing function and do static function tracing for us.
Commit message from Wu Zhangjin <wuzhangjin@gmail.com>
Signed-off-by: Michal Simek <monstr@monstr.eu>
---
arch/microblaze/Kconfig | 1 +
arch/microblaze/include/asm/ftrace.h | 14 ++++
arch/microblaze/kernel/Makefile | 11 +++
arch/microblaze/kernel/cpu/Makefile | 4 +
arch/microblaze/kernel/mcount.S | 105 +++++++++++++++++++++++++++++
arch/microblaze/kernel/microblaze_ksyms.c | 5 ++
6 files changed, 140 insertions(+), 0 deletions(-)
create mode 100644 arch/microblaze/kernel/mcount.S
diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig
index 57eeaa6..18003ca 100644
--- a/arch/microblaze/Kconfig
+++ b/arch/microblaze/Kconfig
@@ -6,6 +6,7 @@ mainmenu "Linux/Microblaze Kernel Configuration"
config MICROBLAZE
def_bool y
select HAVE_LMB
+ select HAVE_FUNCTION_TRACER
select USB_ARCH_HAS_EHCI
select ARCH_WANT_OPTIONAL_GPIOLIB
diff --git a/arch/microblaze/include/asm/ftrace.h b/arch/microblaze/include/asm/ftrace.h
index 8b13789..22beec5 100644
--- a/arch/microblaze/include/asm/ftrace.h
+++ b/arch/microblaze/include/asm/ftrace.h
@@ -1 +1,15 @@
+#ifndef _ASM_MICROBLAZE_FTRACE
+#define _ASM_MICROBLAZE_FTRACE
+#ifdef CONFIG_FUNCTION_TRACER
+
+#define MCOUNT_ADDR ((long)(_mcount))
+#define MCOUNT_INSN_SIZE 8 /* sizeof mcount call */
+
+#ifndef __ASSEMBLY__
+extern void _mcount(void);
+extern void ftrace_call_graph(void);
+#endif
+
+#endif /* CONFIG_FUNCTION_TRACER */
+#endif /* _ASM_MICROBLAZE_FTRACE */
diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile
index c465a5c..d5ee326 100644
--- a/arch/microblaze/kernel/Makefile
+++ b/arch/microblaze/kernel/Makefile
@@ -2,6 +2,16 @@
# Makefile
#
+ifdef CONFIG_FUNCTION_TRACER
+# Do not trace early boot code and low level code
+CFLAGS_REMOVE_timer.o = -pg
+CFLAGS_REMOVE_intc.o = -pg
+CFLAGS_REMOVE_early_printk.o = -pg
+CFLAGS_REMOVE_selfmod.o = -pg
+CFLAGS_REMOVE_heartbeat.o = -pg
+CFLAGS_REMOVE_ftrace.o = -pg
+endif
+
extra-y := head.o vmlinux.lds
obj-y += exceptions.o \
@@ -17,5 +27,6 @@ obj-$(CONFIG_HEART_BEAT) += heartbeat.o
obj-$(CONFIG_MODULES) += microblaze_ksyms.o module.o
obj-$(CONFIG_MMU) += misc.o
obj-$(CONFIG_STACKTRACE) += stacktrace.o
+obj-$(CONFIG_FUNCTION_TRACER) += mcount.o
obj-y += entry$(MMU).o
diff --git a/arch/microblaze/kernel/cpu/Makefile b/arch/microblaze/kernel/cpu/Makefile
index 20646e5..59cc7bc 100644
--- a/arch/microblaze/kernel/cpu/Makefile
+++ b/arch/microblaze/kernel/cpu/Makefile
@@ -2,6 +2,10 @@
# Build the appropriate CPU version support
#
+ifdef CONFIG_FUNCTION_TRACER
+CFLAGS_REMOVE_cache.o = -pg
+endif
+
EXTRA_CFLAGS += -DCPU_MAJOR=$(CPU_MAJOR) -DCPU_MINOR=$(CPU_MINOR) \
-DCPU_REV=$(CPU_REV)
diff --git a/arch/microblaze/kernel/mcount.S b/arch/microblaze/kernel/mcount.S
new file mode 100644
index 0000000..a257a1b
--- /dev/null
+++ b/arch/microblaze/kernel/mcount.S
@@ -0,0 +1,105 @@
+/*
+ * Low-level ftrace handling
+ *
+ * Copyright (C) 2009 Michal Simek <monstr@monstr.eu>
+ * Copyright (C) 2009 PetaLogix
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file COPYING in the main directory of this
+ * archive for more details.
+ */
+
+#include <linux/linkage.h>
+
+#define NOALIGN_ENTRY(name) .globl name; name:
+
+/* FIXME MS: I think that I don't need to save all regs */
+#define SAVE_REGS \
+ addik r1, r1, -120; \
+ swi r2, r1, 4; \
+ swi r3, r1, 8; \
+ swi r4, r1, 12; \
+ swi r5, r1, 116; \
+ swi r6, r1, 16; \
+ swi r7, r1, 20; \
+ swi r8, r1, 24; \
+ swi r9, r1, 28; \
+ swi r10, r1, 32; \
+ swi r11, r1, 36; \
+ swi r12, r1, 40; \
+ swi r13, r1, 44; \
+ swi r14, r1, 48; \
+ swi r16, r1, 52; \
+ swi r17, r1, 56; \
+ swi r18, r1, 60; \
+ swi r19, r1, 64; \
+ swi r20, r1, 68; \
+ swi r21, r1, 72; \
+ swi r22, r1, 76; \
+ swi r23, r1, 80; \
+ swi r24, r1, 84; \
+ swi r25, r1, 88; \
+ swi r26, r1, 92; \
+ swi r27, r1, 96; \
+ swi r28, r1, 100; \
+ swi r29, r1, 104; \
+ swi r30, r1, 108; \
+ swi r31, r1, 112;
+
+#define RESTORE_REGS \
+ lwi r2, r1, 4; \
+ lwi r3, r1, 8; \
+ lwi r4, r1, 12; \
+ lwi r5, r1, 116; \
+ lwi r6, r1, 16; \
+ lwi r7, r1, 20; \
+ lwi r8, r1, 24; \
+ lwi r9, r1, 28; \
+ lwi r10, r1, 32; \
+ lwi r11, r1, 36; \
+ lwi r12, r1, 40; \
+ lwi r13, r1, 44; \
+ lwi r14, r1, 48; \
+ lwi r16, r1, 52; \
+ lwi r17, r1, 56; \
+ lwi r18, r1, 60; \
+ lwi r19, r1, 64; \
+ lwi r20, r1, 68; \
+ lwi r21, r1, 72; \
+ lwi r22, r1, 76; \
+ lwi r23, r1, 80; \
+ lwi r24, r1, 84; \
+ lwi r25, r1, 88; \
+ lwi r26, r1, 92; \
+ lwi r27, r1, 96; \
+ lwi r28, r1, 100; \
+ lwi r29, r1, 104; \
+ lwi r30, r1, 108; \
+ lwi r31, r1, 112; \
+ addik r1, r1, 120;
+
+ENTRY(ftrace_stub)
+ rtsd r15, 8;
+ nop;
+
+ENTRY(_mcount)
+ SAVE_REGS
+ swi r15, r1, 0;
+ /* MS: test function trace if is taken or not */
+ lwi r20, r0, ftrace_trace_function;
+ addik r6, r0, ftrace_stub;
+ cmpu r5, r20, r6; /* ftrace_trace_function != ftrace_stub */
+ beqid r5, end; /* MS: not taken -> jump over */
+ nop;
+/* static normal trace */
+ lwi r6, r1, 120; /* MS: load parent addr */
+ addik r5, r15, 0; /* MS: load current function addr */
+ /* MS: here is dependency on previous code */
+ brald r15, r20; /* MS: jump to ftrace handler */
+ nop;
+end:
+ lwi r15, r1, 0;
+ RESTORE_REGS
+
+ rtsd r15, 8; /* MS: jump back */
+ nop;
diff --git a/arch/microblaze/kernel/microblaze_ksyms.c b/arch/microblaze/kernel/microblaze_ksyms.c
index 59ff20e..bc4dcb7 100644
--- a/arch/microblaze/kernel/microblaze_ksyms.c
+++ b/arch/microblaze/kernel/microblaze_ksyms.c
@@ -18,6 +18,7 @@
#include <linux/io.h>
#include <asm/page.h>
#include <asm/system.h>
+#include <linux/ftrace.h>
#include <linux/uaccess.h>
/*
@@ -47,3 +48,7 @@ extern void __umodsi3(void);
EXPORT_SYMBOL(__umodsi3);
extern char *_ebss;
EXPORT_SYMBOL_GPL(_ebss);
+#ifdef CONFIG_FUNCTION_TRACER
+extern void _mcount(void);
+EXPORT_SYMBOL(_mcount);
+#endif
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 2/5] microblaze: ftrace: enable HAVE_FUNCTION_TRACE_MCOUNT_TEST
2009-11-23 8:41 ` [PATCH 1/5] microblaze: ftrace: add static function tracer y
@ 2009-11-23 8:41 ` y
2009-11-23 8:41 ` [PATCH 3/5] microblaze: ftrace: Add dynamic trace support y
0 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt, Michal Simek
From: Michal Simek <monstr@monstr.eu>
Implement MCOUNT_TEST in asm code - it is faster than use
generic code
Signed-off-by: Michal Simek <monstr@monstr.eu>
---
arch/microblaze/Kconfig | 1 +
arch/microblaze/kernel/mcount.S | 5 +++++
2 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig
index 18003ca..cccf3ad 100644
--- a/arch/microblaze/Kconfig
+++ b/arch/microblaze/Kconfig
@@ -7,6 +7,7 @@ config MICROBLAZE
def_bool y
select HAVE_LMB
select HAVE_FUNCTION_TRACER
+ select HAVE_FUNCTION_TRACE_MCOUNT_TEST
select USB_ARCH_HAS_EHCI
select ARCH_WANT_OPTIONAL_GPIOLIB
diff --git a/arch/microblaze/kernel/mcount.S b/arch/microblaze/kernel/mcount.S
index a257a1b..97eef3e 100644
--- a/arch/microblaze/kernel/mcount.S
+++ b/arch/microblaze/kernel/mcount.S
@@ -85,6 +85,11 @@ ENTRY(ftrace_stub)
ENTRY(_mcount)
SAVE_REGS
swi r15, r1, 0;
+ /* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST begin of checking */
+ lwi r5, r0, function_trace_stop;
+ bneid r5, end;
+ nop;
+ /* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST end of checking */
/* MS: test function trace if is taken or not */
lwi r20, r0, ftrace_trace_function;
addik r6, r0, ftrace_stub;
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 3/5] microblaze: ftrace: Add dynamic trace support
2009-11-23 8:41 ` [PATCH 2/5] microblaze: ftrace: enable HAVE_FUNCTION_TRACE_MCOUNT_TEST y
@ 2009-11-23 8:41 ` y
2009-11-23 8:41 ` [PATCH 4/5] microblaze: ftrace: add function graph support y
0 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt, Michal Simek
From: Michal Simek <monstr@monstr.eu>
With dynamic function tracer, by default, _mcount is defined as an
"empty" function, it returns directly without any more action. When
enabling it in user-space, it will jump to a real tracing
function(ftrace_caller), and do the real job for us.
Differ from the static function tracer, dynamic function tracer provides
two functions ftrace_make_call()/ftrace_make_nop() to enable/disable the
tracing of some indicated kernel functions(set_ftrace_filter).
In the kernel version, there is only one "_mcount" string for every
kernel function, so, we just need to match this one in mcount_regex of
scripts/recordmcount.pl.
For more information please look at code and Documentation/trace folder.
Signed-off-by: Michal Simek <monstr@monstr.eu>
---
arch/microblaze/Kconfig | 2 +
arch/microblaze/include/asm/ftrace.h | 11 +++
arch/microblaze/kernel/Makefile | 2 +-
arch/microblaze/kernel/ftrace.c | 151 ++++++++++++++++++++++++++++++++++
arch/microblaze/kernel/mcount.S | 13 +++
scripts/recordmcount.pl | 3 +
6 files changed, 181 insertions(+), 1 deletions(-)
create mode 100644 arch/microblaze/kernel/ftrace.c
diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig
index cccf3ad..102d73a 100644
--- a/arch/microblaze/Kconfig
+++ b/arch/microblaze/Kconfig
@@ -8,6 +8,8 @@ config MICROBLAZE
select HAVE_LMB
select HAVE_FUNCTION_TRACER
select HAVE_FUNCTION_TRACE_MCOUNT_TEST
+ select HAVE_DYNAMIC_FTRACE
+ select HAVE_FTRACE_MCOUNT_RECORD
select USB_ARCH_HAS_EHCI
select ARCH_WANT_OPTIONAL_GPIOLIB
diff --git a/arch/microblaze/include/asm/ftrace.h b/arch/microblaze/include/asm/ftrace.h
index 22beec5..fd2fa2e 100644
--- a/arch/microblaze/include/asm/ftrace.h
+++ b/arch/microblaze/include/asm/ftrace.h
@@ -11,5 +11,16 @@ extern void _mcount(void);
extern void ftrace_call_graph(void);
#endif
+#ifdef CONFIG_DYNAMIC_FTRACE
+/* reloction of mcount call site is the same as the address */
+static inline unsigned long ftrace_call_adjust(unsigned long addr)
+{
+ return addr;
+}
+
+struct dyn_arch_ftrace {
+};
+#endif /* CONFIG_DYNAMIC_FTRACE */
+
#endif /* CONFIG_FUNCTION_TRACER */
#endif /* _ASM_MICROBLAZE_FTRACE */
diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile
index d5ee326..b07594e 100644
--- a/arch/microblaze/kernel/Makefile
+++ b/arch/microblaze/kernel/Makefile
@@ -27,6 +27,6 @@ obj-$(CONFIG_HEART_BEAT) += heartbeat.o
obj-$(CONFIG_MODULES) += microblaze_ksyms.o module.o
obj-$(CONFIG_MMU) += misc.o
obj-$(CONFIG_STACKTRACE) += stacktrace.o
-obj-$(CONFIG_FUNCTION_TRACER) += mcount.o
+obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o mcount.o
obj-y += entry$(MMU).o
diff --git a/arch/microblaze/kernel/ftrace.c b/arch/microblaze/kernel/ftrace.c
new file mode 100644
index 0000000..c1889b1
--- /dev/null
+++ b/arch/microblaze/kernel/ftrace.c
@@ -0,0 +1,151 @@
+/*
+ * Ftrace support for Microblaze.
+ *
+ * Copyright (C) 2009 Michal Simek <monstr@monstr.eu>
+ * Copyright (C) 2009 PetaLogix
+ *
+ * Based on MIPS and PowerPC ftrace code
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <asm/cacheflush.h>
+#include <linux/ftrace.h>
+
+#ifdef CONFIG_DYNAMIC_FTRACE
+/* save value to addr - it is save to do it in asm */
+static int ftrace_modify_code(unsigned long addr, unsigned int value)
+{
+ int faulted = 0;
+
+ __asm__ __volatile__(" 1: swi %2, %1, 0; \
+ addik %0, r0, 0; \
+ 2: \
+ .section .fixup, \"ax\"; \
+ 3: brid 2b; \
+ addik %0, r0, 1; \
+ .previous; \
+ .section __ex_table,\"a\"; \
+ .word 1b,3b; \
+ .previous;" \
+ : "=r" (faulted)
+ : "r" (addr), "r" (value)
+ );
+
+ if (unlikely(faulted))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define MICROBLAZE_NOP 0x80000000
+#define MICROBLAZE_BRI 0xb800000C
+
+static unsigned int recorded; /* if save was or not */
+static unsigned int imm; /* saving whole imm instruction */
+
+/* There are two approaches howto solve ftrace_make nop function - look below */
+#undef USE_FTRACE_NOP
+
+#ifdef USE_FTRACE_NOP
+static unsigned int bralid; /* saving whole bralid instruction */
+#endif
+
+int ftrace_make_nop(struct module *mod,
+ struct dyn_ftrace *rec, unsigned long addr)
+{
+ /* we have this part of code which we are working with
+ * b000c000 imm -16384
+ * b9fc8e30 bralid r15, -29136 // c0008e30 <_mcount>
+ * 80000000 or r0, r0, r0
+ *
+ * The first solution (!USE_FTRACE_NOP-could be called branch solution)
+ * b000c000 bri 12 (0xC - jump to any other instruction)
+ * b9fc8e30 bralid r15, -29136 // c0008e30 <_mcount>
+ * 80000000 or r0, r0, r0
+ * any other instruction
+ *
+ * The second solution (USE_FTRACE_NOP) - no jump just nops
+ * 80000000 or r0, r0, r0
+ * 80000000 or r0, r0, r0
+ * 80000000 or r0, r0, r0
+ */
+ int ret = 0;
+
+ if (recorded == 0) {
+ recorded = 1;
+ imm = *(unsigned int *)rec->ip;
+ pr_debug("%s: imm:0x%x\n", __func__, imm);
+#ifdef USE_FTRACE_NOP
+ bralid = *(unsigned int *)(rec->ip + 4);
+ pr_debug("%s: bralid 0x%x\n", __func__, bralid);
+#endif /* USE_FTRACE_NOP */
+ }
+
+#ifdef USE_FTRACE_NOP
+ ret = ftrace_modify_code(rec->ip, MICROBLAZE_NOP);
+ ret += ftrace_modify_code(rec->ip + 4, MICROBLAZE_NOP);
+#else /* USE_FTRACE_NOP */
+ ret = ftrace_modify_code(rec->ip, MICROBLAZE_BRI);
+#endif /* USE_FTRACE_NOP */
+ return ret;
+}
+
+static int ret_addr; /* initialized as 0 by default */
+
+/* I believe that first is called ftrace_make_nop before this function */
+int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
+{
+ int ret;
+ ret_addr = addr; /* saving where the barrier jump is */
+ pr_debug("%s: addr:0x%x, rec->ip: 0x%x, imm:0x%x\n",
+ __func__, (unsigned int)addr, (unsigned int)rec->ip, imm);
+ ret = ftrace_modify_code(rec->ip, imm);
+#ifdef USE_FTRACE_NOP
+ pr_debug("%s: bralid:0x%x\n", __func__, bralid);
+ ret += ftrace_modify_code(rec->ip + 4, bralid);
+#endif /* USE_FTRACE_NOP */
+ return ret;
+}
+
+int __init ftrace_dyn_arch_init(void *data)
+{
+ /* The return code is retured via data */
+ *(unsigned long *)data = 0;
+
+ return 0;
+}
+
+int ftrace_update_ftrace_func(ftrace_func_t func)
+{
+ unsigned long ip = (unsigned long)(&ftrace_call);
+ unsigned int upper = (unsigned int)func;
+ unsigned int lower = (unsigned int)func;
+ int ret = 0;
+
+ /* create proper saving to ftrace_call poll */
+ upper = 0xb0000000 + (upper >> 16); /* imm func_upper */
+ lower = 0x32800000 + (lower & 0xFFFF); /* addik r20, r0, func_lower */
+
+ pr_debug("%s: func=0x%x, ip=0x%x, upper=0x%x, lower=0x%x\n",
+ __func__, (unsigned int)func, (unsigned int)ip, upper, lower);
+
+ /* save upper and lower code */
+ ret = ftrace_modify_code(ip, upper);
+ ret += ftrace_modify_code(ip + 4, lower);
+
+ /* We just need to remove the rtsd r15, 8 by NOP */
+ BUG_ON(!ret_addr);
+ if (ret_addr)
+ ret += ftrace_modify_code(ret_addr, MICROBLAZE_NOP);
+ else
+ ret = 1; /* fault */
+
+ /* All changes are done - lets do caches consistent */
+ flush_icache();
+ return ret;
+}
+
+#endif /* CONFIG_DYNAMIC_FTRACE */
diff --git a/arch/microblaze/kernel/mcount.S b/arch/microblaze/kernel/mcount.S
index 97eef3e..30aaf8f 100644
--- a/arch/microblaze/kernel/mcount.S
+++ b/arch/microblaze/kernel/mcount.S
@@ -83,6 +83,12 @@ ENTRY(ftrace_stub)
nop;
ENTRY(_mcount)
+#ifdef CONFIG_DYNAMIC_FTRACE
+ENTRY(ftrace_caller)
+ /* MS: It is just barrier which is removed from C code */
+ rtsd r15, 8
+ nop
+#endif /* CONFIG_DYNAMIC_FTRACE */
SAVE_REGS
swi r15, r1, 0;
/* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST begin of checking */
@@ -90,12 +96,19 @@ ENTRY(_mcount)
bneid r5, end;
nop;
/* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST end of checking */
+#ifndef CONFIG_DYNAMIC_FTRACE
/* MS: test function trace if is taken or not */
lwi r20, r0, ftrace_trace_function;
addik r6, r0, ftrace_stub;
cmpu r5, r20, r6; /* ftrace_trace_function != ftrace_stub */
beqid r5, end; /* MS: not taken -> jump over */
nop;
+#else /* CONFIG_DYNAMIC_FTRACE */
+NOALIGN_ENTRY(ftrace_call)
+/* instruction for setup imm FUNC_part1, addik r20, r0, FUNC_part2 */
+ nop
+ nop
+#endif /* CONFIG_DYNAMIC_FTRACE */
/* static normal trace */
lwi r6, r1, 120; /* MS: load parent addr */
addik r5, r15, 0; /* MS: load current function addr */
diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl
index 090d300..b30dabd 100755
--- a/scripts/recordmcount.pl
+++ b/scripts/recordmcount.pl
@@ -245,6 +245,9 @@ if ($arch eq "x86_64") {
$ld .= " -m elf64_sparc";
$cc .= " -m64";
$objcopy .= " -O elf64-sparc";
+} elsif ($arch eq "microblaze") {
+ # Microblaze calls '_mcount' instead of plain 'mcount'.
+ $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$";
} else {
die "Arch $arch is not supported with CONFIG_FTRACE_MCOUNT_RECORD";
}
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 4/5] microblaze: ftrace: add function graph support
2009-11-23 8:41 ` [PATCH 3/5] microblaze: ftrace: Add dynamic trace support y
@ 2009-11-23 8:41 ` y
2009-11-23 8:41 ` [PATCH 5/5] microblaze: ftrace: Add dynamic function graph tracer y
0 siblings, 1 reply; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt, Michal Simek
From: Michal Simek <monstr@monstr.eu>
For more information look at Documentation/trace folder.
Signed-off-by: Michal Simek <monstr@monstr.eu>
---
arch/microblaze/Kconfig | 1 +
arch/microblaze/kernel/ftrace.c | 58 +++++++++++++++++++++++++++++++++++++++
arch/microblaze/kernel/mcount.S | 41 +++++++++++++++++++++++++++
3 files changed, 100 insertions(+), 0 deletions(-)
diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig
index 102d73a..e297802 100644
--- a/arch/microblaze/Kconfig
+++ b/arch/microblaze/Kconfig
@@ -8,6 +8,7 @@ config MICROBLAZE
select HAVE_LMB
select HAVE_FUNCTION_TRACER
select HAVE_FUNCTION_TRACE_MCOUNT_TEST
+ select HAVE_FUNCTION_GRAPH_TRACER
select HAVE_DYNAMIC_FTRACE
select HAVE_FTRACE_MCOUNT_RECORD
select USB_ARCH_HAS_EHCI
diff --git a/arch/microblaze/kernel/ftrace.c b/arch/microblaze/kernel/ftrace.c
index c1889b1..0952a8b 100644
--- a/arch/microblaze/kernel/ftrace.c
+++ b/arch/microblaze/kernel/ftrace.c
@@ -14,6 +14,64 @@
#include <asm/cacheflush.h>
#include <linux/ftrace.h>
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+/*
+ * Hook the return address and push it in the stack of return addrs
+ * in current thread info.
+ */
+void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr)
+{
+ unsigned long old;
+ int faulted, err;
+ struct ftrace_graph_ent trace;
+ unsigned long return_hooker = (unsigned long)
+ &return_to_handler;
+
+ if (unlikely(atomic_read(¤t->tracing_graph_pause)))
+ return;
+
+ /*
+ * Protect against fault, even if it shouldn't
+ * happen. This tool is too much intrusive to
+ * ignore such a protection.
+ */
+ asm volatile(" 1: lwi %0, %2, 0; \
+ 2: swi %3, %2, 0; \
+ addik %1, r0, 0; \
+ 3: \
+ .section .fixup, \"ax\"; \
+ 4: brid 3b; \
+ addik %1, r0, 1; \
+ .previous; \
+ .section __ex_table,\"a\"; \
+ .word 1b,4b; \
+ .word 2b,4b; \
+ .previous;" \
+ : "=&r" (old), "=r" (faulted)
+ : "r" (parent), "r" (return_hooker)
+ );
+
+ if (unlikely(faulted)) {
+ ftrace_graph_stop();
+ WARN_ON(1);
+ return;
+ }
+
+ err = ftrace_push_return_trace(old, self_addr, &trace.depth, 0);
+ if (err == -EBUSY) {
+ *parent = old;
+ return;
+ }
+
+ trace.func = self_addr;
+ /* Only trace if the calling function expects to */
+ if (!ftrace_graph_entry(&trace)) {
+ current->curr_ret_stack--;
+ *parent = old;
+ }
+}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
+
#ifdef CONFIG_DYNAMIC_FTRACE
/* save value to addr - it is save to do it in asm */
static int ftrace_modify_code(unsigned long addr, unsigned int value)
diff --git a/arch/microblaze/kernel/mcount.S b/arch/microblaze/kernel/mcount.S
index 30aaf8f..84a1945 100644
--- a/arch/microblaze/kernel/mcount.S
+++ b/arch/microblaze/kernel/mcount.S
@@ -96,6 +96,27 @@ ENTRY(ftrace_caller)
bneid r5, end;
nop;
/* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST end of checking */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+ lwi r5, r0, ftrace_graph_return;
+ addik r6, r0, ftrace_stub; /* asm implementation */
+ cmpu r5, r5, r6; /* ftrace_graph_return != ftrace_stub */
+ beqid r5, end_graph_tracer;
+ nop;
+
+ lwi r6, r0, ftrace_graph_entry;
+ addik r5, r0, ftrace_graph_entry_stub; /* implemented in C */
+ cmpu r5, r5, r6; /* ftrace_graph_entry != ftrace_graph_entry_stub */
+ beqid r5, end_graph_tracer;
+ nop;
+ addik r5, r1, 120; /* MS: load parent addr */
+ addik r6, r15, 0; /* MS: load current function addr */
+ bralid r15, prepare_ftrace_return;
+ nop;
+ /* MS: graph was taken that's why - can jump over function trace */
+ brid end;
+ nop;
+end_graph_tracer:
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
#ifndef CONFIG_DYNAMIC_FTRACE
/* MS: test function trace if is taken or not */
lwi r20, r0, ftrace_trace_function;
@@ -121,3 +142,23 @@ end:
rtsd r15, 8; /* MS: jump back */
nop;
+
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+ENTRY(return_to_handler)
+ nop; /* MS: just barrier for rtsd r15, 8 */
+ nop;
+ SAVE_REGS
+ swi r15, r1, 0;
+
+ /* MS: find out returning address */
+ bralid r15, ftrace_return_to_handler;
+ nop;
+
+ /* MS: return value from ftrace_return_to_handler is my returning addr
+ * must be before restore regs because I have to restore r3 content */
+ addik r15, r3, 0;
+ RESTORE_REGS
+
+ rtsd r15, 8; /* MS: jump back */
+ nop;
+#endif /* CONFIG_FUNCTION_TRACER */
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 5/5] microblaze: ftrace: Add dynamic function graph tracer
2009-11-23 8:41 ` [PATCH 4/5] microblaze: ftrace: add function graph support y
@ 2009-11-23 8:41 ` y
0 siblings, 0 replies; 743+ messages in thread
From: y @ 2009-11-23 8:41 UTC (permalink / raw)
To: linux-kernel; +Cc: michal.simek, wuzhangjin, mingo, rostedt, Michal Simek
From: Michal Simek <monstr@monstr.eu>
This patch add support for dynamic function graph tracer.
There is one my expactation that I can do flush_icache after
all code modification. On microblaze is this safer than do
flush for every entry. For icache is used name flush but
correct should be invalidation - this will be fix in upcomming
new cache implementaion and WB support.
Signed-off-by: Michal Simek <monstr@monstr.eu>
---
arch/microblaze/kernel/ftrace.c | 28 ++++++++++++++++++++++++++++
arch/microblaze/kernel/mcount.S | 6 ++++++
2 files changed, 34 insertions(+), 0 deletions(-)
diff --git a/arch/microblaze/kernel/ftrace.c b/arch/microblaze/kernel/ftrace.c
index 0952a8b..388b31c 100644
--- a/arch/microblaze/kernel/ftrace.c
+++ b/arch/microblaze/kernel/ftrace.c
@@ -206,4 +206,32 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
return ret;
}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+unsigned int old_jump; /* saving place for jump instruction */
+
+int ftrace_enable_ftrace_graph_caller(void)
+{
+ unsigned int ret;
+ unsigned long ip = (unsigned long)(&ftrace_call_graph);
+
+ old_jump = *(unsigned int *)ip; /* save jump over instruction */
+ ret = ftrace_modify_code(ip, MICROBLAZE_NOP);
+ flush_icache();
+
+ pr_debug("%s: Replace instruction: 0x%x\n", __func__, old_jump);
+ return ret;
+}
+
+int ftrace_disable_ftrace_graph_caller(void)
+{
+ unsigned int ret;
+ unsigned long ip = (unsigned long)(&ftrace_call_graph);
+
+ ret = ftrace_modify_code(ip, old_jump);
+ flush_icache();
+
+ pr_debug("%s\n", __func__);
+ return ret;
+}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
#endif /* CONFIG_DYNAMIC_FTRACE */
diff --git a/arch/microblaze/kernel/mcount.S b/arch/microblaze/kernel/mcount.S
index 84a1945..e7eaa7a 100644
--- a/arch/microblaze/kernel/mcount.S
+++ b/arch/microblaze/kernel/mcount.S
@@ -97,6 +97,7 @@ ENTRY(ftrace_caller)
nop;
/* MS: HAVE_FUNCTION_TRACE_MCOUNT_TEST end of checking */
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+#ifndef CONFIG_DYNAMIC_FTRACE
lwi r5, r0, ftrace_graph_return;
addik r6, r0, ftrace_stub; /* asm implementation */
cmpu r5, r5, r6; /* ftrace_graph_return != ftrace_stub */
@@ -108,6 +109,11 @@ ENTRY(ftrace_caller)
cmpu r5, r5, r6; /* ftrace_graph_entry != ftrace_graph_entry_stub */
beqid r5, end_graph_tracer;
nop;
+#else /* CONFIG_DYNAMIC_FTRACE */
+NOALIGN_ENTRY(ftrace_call_graph)
+ /* MS: jump over graph function - replaced from C code */
+ bri end_graph_tracer
+#endif /* CONFIG_DYNAMIC_FTRACE */
addik r5, r1, 120; /* MS: load parent addr */
addik r6, r15, 0; /* MS: load current function addr */
bralid r15, prepare_ftrace_return;
--
1.5.5.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] mfd: Update WM8350 drivers for changed interrupt numbers
[not found] <y>
` (9 preceding siblings ...)
2009-11-23 8:41 ` Microblaze ftrace support y
@ 2010-01-06 19:24 ` Mark Brown
2010-01-06 19:50 ` Samuel Ortiz
2010-03-11 18:01 ` [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH Nathaniel McCallum
` (149 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Mark Brown @ 2010-01-06 19:24 UTC (permalink / raw)
To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown
The headphone detect and charger are using the IRQ numbers so need
to take account of irq_base with the genirq conversion. I obviously
picked the wrong system for initial testing.
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
---
drivers/power/wm8350_power.c | 2 +-
sound/soc/codecs/wm8350.c | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c
index 3839a5e..0693902 100644
--- a/drivers/power/wm8350_power.c
+++ b/drivers/power/wm8350_power.c
@@ -190,7 +190,7 @@ static irqreturn_t wm8350_charger_handler(int irq, void *data)
struct wm8350_power *power = &wm8350->power;
struct wm8350_charger_policy *policy = power->policy;
- switch (irq) {
+ switch (irq - wm8350->irq_base) {
case WM8350_IRQ_CHG_BAT_FAIL:
dev_err(wm8350->dev, "battery failed\n");
break;
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
index f45845a..93f1e4e 100644
--- a/sound/soc/codecs/wm8350.c
+++ b/sound/soc/codecs/wm8350.c
@@ -1349,7 +1349,7 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
int mask;
struct wm8350_jack_data *jack = NULL;
- switch (irq) {
+ switch (irq - wm8350->irq_base) {
case WM8350_IRQ_CODEC_JCK_DET_L:
jack = &priv->hpl;
mask = WM8350_JACK_L_LVL;
@@ -1424,7 +1424,7 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
/* Sync status */
- wm8350_hp_jack_handler(irq, priv);
+ wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
return 0;
}
--
1.6.5.7
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] mfd: Update WM8350 drivers for changed interrupt numbers
2010-01-06 19:24 ` [PATCH] mfd: Update WM8350 drivers for changed interrupt numbers Mark Brown
@ 2010-01-06 19:50 ` Samuel Ortiz
0 siblings, 0 replies; 743+ messages in thread
From: Samuel Ortiz @ 2010-01-06 19:50 UTC (permalink / raw)
To: Mark Brown; +Cc: linux-kernel
Hi Mark,
On Wed, Jan 06, 2010 at 07:24:25PM +0000, Mark Brown wrote:
> The headphone detect and charger are using the IRQ numbers so need
> to take account of irq_base with the genirq conversion. I obviously
> picked the wrong system for initial testing.
Patch applied to my for-next branch, thanks.
Cheers,
Samuel.
> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> ---
> drivers/power/wm8350_power.c | 2 +-
> sound/soc/codecs/wm8350.c | 4 ++--
> 2 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c
> index 3839a5e..0693902 100644
> --- a/drivers/power/wm8350_power.c
> +++ b/drivers/power/wm8350_power.c
> @@ -190,7 +190,7 @@ static irqreturn_t wm8350_charger_handler(int irq, void *data)
> struct wm8350_power *power = &wm8350->power;
> struct wm8350_charger_policy *policy = power->policy;
>
> - switch (irq) {
> + switch (irq - wm8350->irq_base) {
> case WM8350_IRQ_CHG_BAT_FAIL:
> dev_err(wm8350->dev, "battery failed\n");
> break;
> diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
> index f45845a..93f1e4e 100644
> --- a/sound/soc/codecs/wm8350.c
> +++ b/sound/soc/codecs/wm8350.c
> @@ -1349,7 +1349,7 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
> int mask;
> struct wm8350_jack_data *jack = NULL;
>
> - switch (irq) {
> + switch (irq - wm8350->irq_base) {
> case WM8350_IRQ_CODEC_JCK_DET_L:
> jack = &priv->hpl;
> mask = WM8350_JACK_L_LVL;
> @@ -1424,7 +1424,7 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
> wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
>
> /* Sync status */
> - wm8350_hp_jack_handler(irq, priv);
> + wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
>
> return 0;
> }
> --
> 1.6.5.7
>
--
Intel Open Source Technology Centre
http://oss.intel.com/
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH
[not found] <y>
` (10 preceding siblings ...)
2010-01-06 19:24 ` [PATCH] mfd: Update WM8350 drivers for changed interrupt numbers Mark Brown
@ 2010-03-11 18:01 ` Nathaniel McCallum
2010-03-11 18:01 ` [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option Nathaniel McCallum
` (148 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:01 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 847b805..08f8a7d 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -288,7 +288,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
-#define MAXON_VENDOR_ID 0x16d8
+#define CMOTECH_VENDOR_ID 0x16d8
#define TELIT_VENDOR_ID 0x1bc7
#define TELIT_PRODUCT_UC864E 0x1003
@@ -547,7 +547,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) },
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
- { USB_DEVICE(MAXON_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option
[not found] <y>
` (11 preceding siblings ...)
2010-03-11 18:01 ` [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH Nathaniel McCallum
@ 2010-03-11 18:01 ` Nathaniel McCallum
2010-03-11 18:01 ` [PATCH 3/3] add support for a new CMOTECH device to usb/serial/option Nathaniel McCallum
` (147 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:01 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 08f8a7d..d949432 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -289,6 +289,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_6280 0x6280
#define TELIT_VENDOR_ID 0x1bc7
#define TELIT_PRODUCT_UC864E 0x1003
@@ -547,7 +548,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) },
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
- { USB_DEVICE(CMOTECH_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6280) }, /* BP3-USB & BP3-EXT HSDPA */
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 3/3] add support for a new CMOTECH device to usb/serial/option
[not found] <y>
` (12 preceding siblings ...)
2010-03-11 18:01 ` [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option Nathaniel McCallum
@ 2010-03-11 18:01 ` Nathaniel McCallum
2010-03-11 18:09 ` [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH Nathaniel McCallum
` (146 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:01 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index d949432..f26c3c5 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -289,6 +289,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_6008 0x6008
#define CMOTECH_PRODUCT_6280 0x6280
#define TELIT_VENDOR_ID 0x1bc7
@@ -549,6 +550,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
{ USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6008) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH
[not found] <y>
` (13 preceding siblings ...)
2010-03-11 18:01 ` [PATCH 3/3] add support for a new CMOTECH device to usb/serial/option Nathaniel McCallum
@ 2010-03-11 18:09 ` Nathaniel McCallum
2010-03-11 18:09 ` [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option Nathaniel McCallum
` (145 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:09 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 847b805..08f8a7d 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -288,7 +288,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
-#define MAXON_VENDOR_ID 0x16d8
+#define CMOTECH_VENDOR_ID 0x16d8
#define TELIT_VENDOR_ID 0x1bc7
#define TELIT_PRODUCT_UC864E 0x1003
@@ -547,7 +547,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) },
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
- { USB_DEVICE(MAXON_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option
[not found] <y>
` (14 preceding siblings ...)
2010-03-11 18:09 ` [PATCH 1/3] fix incorrect manufacturer name in usb/serial/option: MAXON->CMOTECH Nathaniel McCallum
@ 2010-03-11 18:09 ` Nathaniel McCallum
2010-03-11 18:09 ` [PATCH 3/3] add support for a new CMOTECH device to usb/serial/option Nathaniel McCallum
` (144 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:09 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 08f8a7d..d949432 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -289,6 +289,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_6280 0x6280
#define TELIT_VENDOR_ID 0x1bc7
#define TELIT_PRODUCT_UC864E 0x1003
@@ -547,7 +548,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) },
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
- { USB_DEVICE(CMOTECH_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6280) }, /* BP3-USB & BP3-EXT HSDPA */
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 3/3] add support for a new CMOTECH device to usb/serial/option
[not found] <y>
` (15 preceding siblings ...)
2010-03-11 18:09 ` [PATCH 2/3] move hardcoded PID to a macro in usb/serial/option Nathaniel McCallum
@ 2010-03-11 18:09 ` Nathaniel McCallum
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
` (143 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Nathaniel McCallum @ 2010-03-11 18:09 UTC (permalink / raw)
To: linux-usb
Cc: linux-kernel, gregkh, smurf, gernot, r0bertz, stern, Nathaniel McCallum
Signed-off-by: Nathaniel McCallum <nathaniel@natemccallum.com>
---
drivers/usb/serial/option.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index d949432..f26c3c5 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -289,6 +289,7 @@ static int option_resume(struct usb_serial *serial);
#define QUALCOMM_VENDOR_ID 0x05C6
#define CMOTECH_VENDOR_ID 0x16d8
+#define CMOTECH_PRODUCT_6008 0x6008
#define CMOTECH_PRODUCT_6280 0x6280
#define TELIT_VENDOR_ID 0x1bc7
@@ -549,6 +550,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */
{ USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */
{ USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6280) }, /* BP3-USB & BP3-EXT HSDPA */
+ { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_6008) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) },
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864G) },
{ USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622, 0xff, 0xff, 0xff) }, /* ZTE WCDMA products */
--
1.7.0.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] EHCI support for on-chip PMC MSP USB controller.
@ 2010-12-21 11:06 ` Anoop P
2010-12-21 16:00 ` Alan Stern
` (3 more replies)
0 siblings, 4 replies; 743+ messages in thread
From: Anoop P @ 2010-12-21 11:06 UTC (permalink / raw)
To: Ralf Baechle, gregkh, dbrownell, stern, sarah.a.sharp, andiry.xu,
agust, ddaney, gadiyar, linux-mips, linux-kernel, linux-usb
Cc: Anoop P A
From: Anoop P A <anoop.pa@gmail.com>
This patch includes.
1. USB host driver for MSP71xx family SoC on-chip USB controller.
2. Platform support for USB controller.
Signed-off-by: Anoop P A <anoop.pa@gmail.com>
---
.../mips/include/asm/pmc-sierra/msp71xx/msp_regs.h | 17 +-
arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h | 144 +++++
arch/mips/pmc-sierra/Kconfig | 8 +
arch/mips/pmc-sierra/msp71xx/Makefile | 2 +-
arch/mips/pmc-sierra/msp71xx/msp_usb.c | 239 +++++++---
drivers/usb/core/hub.c | 31 ++
drivers/usb/host/Kconfig | 15 +-
drivers/usb/host/ehci-hcd.c | 12 +
drivers/usb/host/ehci-pmcmsp.c | 555 ++++++++++++++++++++
9 files changed, 949 insertions(+), 74 deletions(-)
create mode 100644 arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
create mode 100644 drivers/usb/host/ehci-pmcmsp.c
diff --git a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
index 603eb73..692c1b6 100644
--- a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
+++ b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
@@ -91,12 +91,10 @@
/* MAC C device registers */
#define MSP_ADSL2_BASE (MSP_MSB_BASE + 0xA80000)
/* ADSL2 device registers */
-#define MSP_USB_BASE (MSP_MSB_BASE + 0xB40000)
- /* USB device registers */
-#define MSP_USB_BASE_START (MSP_MSB_BASE + 0xB40100)
- /* USB device registers */
-#define MSP_USB_BASE_END (MSP_MSB_BASE + 0xB401FF)
- /* USB device registers */
+#define MSP_USB0_BASE (MSP_MSB_BASE + 0xB00000)
+ /* USB0 device registers */
+#define MSP_USB1_BASE (MSP_MSB_BASE + 0x300000)
+ /* USB1 device registers */
#define MSP_CPUIF_BASE (MSP_MSB_BASE + 0xC00000)
/* CPU interface registers */
@@ -319,8 +317,11 @@
#define CPU_ERR2_REG regptr(MSP_SLP_BASE + 0x184)
/* CPU/SLP Error status 1 */
-#define EXTENDED_GPIO_REG regptr(MSP_SLP_BASE + 0x188)
- /* Extended GPIO register */
+/* Extended GPIO registers */
+#define EXTENDED_GPIO1_REG regptr(MSP_SLP_BASE + 0x188)
+#define EXTENDED_GPIO2_REG regptr(MSP_SLP_BASE + 0x18c)
+#define EXTENDED_GPIO_REG EXTENDED_GPIO1_REG
+ /* Backward-compatibility */
/* System Error registers */
#define SLP_ERR_STS_REG regptr(MSP_SLP_BASE + 0x190)
diff --git a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
new file mode 100644
index 0000000..4c9348d
--- /dev/null
+++ b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
@@ -0,0 +1,144 @@
+/******************************************************************
+ * Copyright (c) 2000-2007 PMC-Sierra INC.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
+ * 02139, USA.
+ *
+ * PMC-SIERRA INC. DISCLAIMS ANY LIABILITY OF ANY KIND
+ * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS
+ * SOFTWARE.
+ */
+#ifndef MSP_USB_H_
+#define MSP_USB_H_
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+#define NUM_USB_DEVS 2
+#else
+#define NUM_USB_DEVS 1
+#endif
+
+/* Register spaces for USB host 0 */
+#define MSP_USB0_MAB_START (MSP_USB0_BASE + 0x0)
+#define MSP_USB0_MAB_END (MSP_USB0_BASE + 0x17)
+#define MSP_USB0_ID_START (MSP_USB0_BASE + 0x40000)
+#define MSP_USB0_ID_END (MSP_USB0_BASE + 0x4008f)
+#define MSP_USB0_HS_START (MSP_USB0_BASE + 0x40100)
+#define MSP_USB0_HS_END (MSP_USB0_BASE + 0x401FF)
+
+/* Register spaces for USB host 1 */
+#define MSP_USB1_MAB_START (MSP_USB1_BASE + 0x0)
+#define MSP_USB1_MAB_END (MSP_USB1_BASE + 0x17)
+#define MSP_USB1_ID_START (MSP_USB1_BASE + 0x40000)
+#define MSP_USB1_ID_END (MSP_USB1_BASE + 0x4008f)
+#define MSP_USB1_HS_START (MSP_USB1_BASE + 0x40100)
+#define MSP_USB1_HS_END (MSP_USB1_BASE + 0x401ff)
+
+/* USB Identification registers */
+struct msp_usbid_regs {
+ u32 id; /* 0x0: Identification register */
+ u32 hwgen; /* 0x4: General HW params */
+ u32 hwhost; /* 0x8: Host HW params */
+ u32 hwdev; /* 0xc: Device HW params */
+ u32 hwtxbuf; /* 0x10: Tx buffer HW params */
+ u32 hwrxbuf; /* 0x14: Rx buffer HW params */
+ u32 reserved[26];
+ u32 timer0_load; /* 0x80: General-purpose timer 0 load*/
+ u32 timer0_ctrl; /* 0x84: General-purpose timer 0 control */
+ u32 timer1_load; /* 0x88: General-purpose timer 1 load*/
+ u32 timer1_ctrl; /* 0x8c: General-purpose timer 1 control */
+};
+
+/* MSBus to AMBA registers */
+struct msp_mab_regs {
+ u32 isr; /* 0x0: Interrupt status */
+ u32 imr; /* 0x4: Interrupt mask */
+ u32 thcr0; /* 0x8: Transaction header capture 0 */
+ u32 thcr1; /* 0xc: Transaction header capture 1 */
+ u32 int_stat; /* 0x10: Interrupt status summary */
+ u32 phy_cfg; /* 0x14: USB phy config */
+};
+
+/* EHCI registers */
+struct msp_usbhs_regs {
+ u32 hciver; /* 0x0: Version and offset to operational regs */
+ u32 hcsparams; /* 0x4: Host control structural parameters */
+ u32 hccparams; /* 0x8: Host control capability parameters */
+ u32 reserved0[5];
+ u32 dciver; /* 0x20: Device interface version */
+ u32 dccparams; /* 0x24: Device control capability parameters */
+ u32 reserved1[6];
+ u32 cmd; /* 0x40: USB command */
+ u32 sts; /* 0x44: USB status */
+ u32 int_ena; /* 0x48: USB interrupt enable */
+ u32 frindex; /* 0x4c: Frame index */
+ u32 reserved3;
+ union {
+ struct {
+ u32 flb_addr; /* 0x54: Frame list base address */
+ u32 next_async_addr; /* 0x58: next asynchronous addr */
+ u32 ttctrl; /* 0x5c: embedded transaction translator
+ async buffer status */
+ u32 burst_size; /* 0x60: Controller burst size */
+ u32 tx_fifo_ctrl; /* 0x64: Tx latency FIFO tuning */
+ u32 reserved0[4];
+ u32 endpt_nak; /* 0x78: Endpoint NAK */
+ u32 endpt_nak_ena; /* 0x7c: Endpoint NAK enable */
+ u32 cfg_flag; /* 0x80: Config flag */
+ u32 port_sc1; /* 0x84: Port status & control 1 */
+ u32 reserved1[7];
+ u32 otgsc; /* 0xa4: OTG status & control */
+ u32 mode; /* 0xa8: USB controller mode */
+ } host;
+
+ struct {
+ u32 dev_addr; /* 0x54: Device address */
+ u32 endpt_list_addr; /* 0x58: Endpoint list address */
+ u32 reserved0[7];
+ u32 endpt_nak; /* 0x74 */
+ u32 endpt_nak_ctrl; /* 0x78 */
+ u32 cfg_flag; /* 0x80 */
+ u32 port_sc1; /* 0x84: Port status & control 1 */
+ u32 reserved[7];
+ u32 otgsc; /* 0xa4: OTG status & control */
+ u32 mode; /* 0xa8: USB controller mode */
+ u32 endpt_setup_stat; /* 0xac */
+ u32 endpt_prime; /* 0xb0 */
+ u32 endpt_flush; /* 0xb4 */
+ u32 endpt_stat; /* 0xb8 */
+ u32 endpt_complete; /* 0xbc */
+ u32 endpt_ctrl0; /* 0xc0 */
+ u32 endpt_ctrl1; /* 0xc4 */
+ u32 endpt_ctrl2; /* 0xc8 */
+ u32 endpt_ctrl3; /* 0xcc */
+ } device;
+ } u;
+};
+/*
+ * Container for the more-generic platform_device.
+ * This exists mainly as a way to map the non-standard register
+ * spaces and make them accessible to the USB ISR.
+ */
+struct mspusb_device {
+ struct msp_mab_regs __iomem *mab_regs;
+ struct msp_usbid_regs __iomem *usbid_regs;
+ struct msp_usbhs_regs __iomem *usbhs_regs;
+ struct platform_device dev;
+};
+
+#define to_mspusb_device(x) container_of((x), struct mspusb_device, dev)
+#define TO_HOST_ID(x) ((x) & 0x3)
+#endif /*MSP_USB_H_*/
diff --git a/arch/mips/pmc-sierra/Kconfig b/arch/mips/pmc-sierra/Kconfig
index 8d79849..a80ad25 100644
--- a/arch/mips/pmc-sierra/Kconfig
+++ b/arch/mips/pmc-sierra/Kconfig
@@ -23,6 +23,7 @@ config PMC_MSP7120_GW
select SYS_SUPPORTS_MULTITHREADING
select IRQ_MSP_CIC
select HW_HAS_PCI
+ select MSP_HAS_USB
config PMC_MSP7120_FPGA
bool "PMC-Sierra MSP7120 FPGA"
@@ -35,3 +36,10 @@ endchoice
config HYPERTRANSPORT
bool "Hypertransport Support for PMC-Sierra Yosemite"
depends on PMC_YOSEMITE
+
+
+config MSP_HAS_USB
+ boolean
+ depends on PMC_MSP
+ select USB_ARCH_HAS_EHCI
+ select USB_ARCH_HAS_HCD
diff --git a/arch/mips/pmc-sierra/msp71xx/Makefile b/arch/mips/pmc-sierra/msp71xx/Makefile
index 09627ae..380d39d 100644
--- a/arch/mips/pmc-sierra/msp71xx/Makefile
+++ b/arch/mips/pmc-sierra/msp71xx/Makefile
@@ -9,5 +9,5 @@ obj-$(CONFIG_IRQ_MSP_SLP) += msp_irq_slp.o
obj-$(CONFIG_IRQ_MSP_CIC) += msp_irq_cic.o msp_irq_per.o
obj-$(CONFIG_PCI) += msp_pci.o
obj-$(CONFIG_MSPETH) += msp_eth.o
-obj-$(CONFIG_USB_MSP71XX) += msp_usb.o
+obj-$(CONFIG_MSP_HAS_USB) += msp_usb.o
obj-$(CONFIG_MIPS_MT_SMP) += msp_smp.o
diff --git a/arch/mips/pmc-sierra/msp71xx/msp_usb.c b/arch/mips/pmc-sierra/msp71xx/msp_usb.c
index 0ee01e3..9a1aef8 100644
--- a/arch/mips/pmc-sierra/msp71xx/msp_usb.c
+++ b/arch/mips/pmc-sierra/msp71xx/msp_usb.c
@@ -1,7 +1,7 @@
/*
* The setup file for USB related hardware on PMC-Sierra MSP processors.
*
- * Copyright 2006-2007 PMC-Sierra, Inc.
+ * Copyright 2006 PMC-Sierra, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -23,8 +23,8 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
+#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
-#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
@@ -34,40 +34,56 @@
#include <msp_regs.h>
#include <msp_int.h>
#include <msp_prom.h>
+#include <msp_usb.h>
+
#if defined(CONFIG_USB_EHCI_HCD)
-static struct resource msp_usbhost_resources [] = {
- [0] = {
- .start = MSP_USB_BASE_START,
- .end = MSP_USB_BASE_END,
- .flags = IORESOURCE_MEM,
+static struct resource msp_usbhost0_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB0_HS_START,
+ .end = MSP_USB0_HS_END,
+ .flags = IORESOURCE_MEM,
},
[1] = {
- .start = MSP_INT_USB,
- .end = MSP_INT_USB,
- .flags = IORESOURCE_IRQ,
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB0_MAB_START,
+ .end = MSP_USB0_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB0_ID_START,
+ .end = MSP_USB0_ID_END,
+ .flags = IORESOURCE_MEM,
},
};
-static u64 msp_usbhost_dma_mask = DMA_BIT_MASK(32);
+static u64 msp_usbhost0_dma_mask = 0xffffffffUL;
-static struct platform_device msp_usbhost_device = {
- .name = "pmcmsp-ehci",
- .id = 0,
+static struct mspusb_device msp_usbhost0_device = {
.dev = {
- .dma_mask = &msp_usbhost_dma_mask,
- .coherent_dma_mask = DMA_BIT_MASK(32),
+ .name = "pmcmsp-ehci",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbhost0_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbhost0_resources),
+ .resource = msp_usbhost0_resources,
},
- .num_resources = ARRAY_SIZE(msp_usbhost_resources),
- .resource = msp_usbhost_resources,
};
-#endif /* CONFIG_USB_EHCI_HCD */
-#if defined(CONFIG_USB_GADGET)
-static struct resource msp_usbdev_resources [] = {
- [0] = {
- .start = MSP_USB_BASE,
- .end = MSP_USB_BASE_END,
+/* MSP7140/MSP82XX has two USB2 hosts. */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+static u64 msp_usbhost1_dma_mask = 0xffffffffUL;
+
+static struct resource msp_usbhost1_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB1_HS_START,
+ .end = MSP_USB1_HS_END,
.flags = IORESOURCE_MEM,
},
[1] = {
@@ -75,76 +91,173 @@ static struct resource msp_usbdev_resources [] = {
.end = MSP_INT_USB,
.flags = IORESOURCE_IRQ,
},
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB1_MAB_START,
+ .end = MSP_USB1_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB1_ID_START,
+ .end = MSP_USB1_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct mspusb_device msp_usbhost1_device = {
+ .dev = {
+ .name = "pmcmsp-ehci",
+ .id = 1,
+ .dev = {
+ .dma_mask = &msp_usbhost1_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbhost1_resources),
+ .resource = msp_usbhost1_resources,
+ },
};
+#endif /* CONFIG_MSP_HAS_DUAL_USB */
+#endif /* CONFIG_USB_EHCI_HCD */
-static u64 msp_usbdev_dma_mask = DMA_BIT_MASK(32);
+#if defined(CONFIG_USB_GADGET)
+static struct resource msp_usbdev0_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB0_HS_START,
+ .end = MSP_USB0_HS_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB0_MAB_START,
+ .end = MSP_USB0_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB0_ID_START,
+ .end = MSP_USB0_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
-static struct platform_device msp_usbdev_device = {
- .name = "msp71xx_udc",
- .id = 0,
+static u64 msp_usbdev_dma_mask = 0xffffffffUL;
+
+/* This may need to be converted to a mspusb_device, too. */
+static struct mspusb_device msp_usbdev0_device = {
.dev = {
- .dma_mask = &msp_usbdev_dma_mask,
- .coherent_dma_mask = DMA_BIT_MASK(32),
+ .name = "msp71xx_udc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbdev_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbdev0_resources),
+ .resource = msp_usbdev0_resources,
},
- .num_resources = ARRAY_SIZE(msp_usbdev_resources),
- .resource = msp_usbdev_resources,
};
-#endif /* CONFIG_USB_GADGET */
-#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
-static struct platform_device *msp_devs[1];
-#endif
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+static struct resource msp_usbdev1_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB1_HS_START,
+ .end = MSP_USB1_HS_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB1_MAB_START,
+ .end = MSP_USB1_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB1_ID_START,
+ .end = MSP_USB1_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
+/* This may need to be converted to a mspusb_device, too. */
+static struct mspusb_device msp_usbdev1_device = {
+ .dev = {
+ .name = "msp71xx_udc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbdev_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbdev1_resources),
+ .resource = msp_usbdev1_resources,
+ },
+};
+
+#endif /* CONFIG_MSP_HAS_DUAL_USB */
+#endif /* CONFIG_USB_GADGET */
static int __init msp_usb_setup(void)
{
-#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
- char *strp;
- char envstr[32];
- unsigned int val = 0;
- int result = 0;
+ char *strp;
+ char envstr[32];
+ struct platform_device *msp_devs[NUM_USB_DEVS];
+ unsigned int val;
+ /* construct environment name usbmode */
+ /* set usbmode <host/device> as pmon environment var */
/*
- * construct environment name usbmode
- * set usbmode <host/device> as pmon environment var
+ * Could this perhaps be integrated into the "features" env var?
+ * Use the features key "U", and follow with "H" for host-mode,
+ * "D" for device-mode. If it works for Ethernet, why not USB...
+ * -- hammtrev, 2007/03/22
*/
snprintf((char *)&envstr[0], sizeof(envstr), "usbmode");
-#if defined(CONFIG_USB_EHCI_HCD)
- /* default to host mode */
+ /* set default host mode */
val = 1;
-#endif
/* get environment string */
strp = prom_getenv((char *)&envstr[0]);
if (strp) {
+ /* compare string */
if (!strcmp(strp, "device"))
val = 0;
}
if (val) {
#if defined(CONFIG_USB_EHCI_HCD)
- /* get host mode device */
- msp_devs[0] = &msp_usbhost_device;
- ppfinit("platform add USB HOST done %s.\n",
- msp_devs[0]->name);
-
- result = platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
-#endif /* CONFIG_USB_EHCI_HCD */
- }
+ msp_devs[0] = &msp_usbhost0_device.dev;
+ ppfinit("platform add USB HOST done %s.\n", msp_devs[0]->name);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ msp_devs[1] = &msp_usbhost1_device.dev;
+ ppfinit("platform add USB HOST done %s.\n", msp_devs[1]->name);
+#endif
+#else
+ ppfinit("%s: echi_hcd not supported\n", __FILE__);
+#endif /* CONFIG_USB_EHCI_HCD */
+ } else {
#if defined(CONFIG_USB_GADGET)
- else {
/* get device mode structure */
- msp_devs[0] = &msp_usbdev_device;
- ppfinit("platform add USB DEVICE done %s.\n",
- msp_devs[0]->name);
-
- result = platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
+ msp_devs[0] = &msp_usbdev0_device.dev;
+ ppfinit("platform add USB DEVICE done %s.\n"
+ , msp_devs[0]->name);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ msp_devs[1] = &msp_usbdev1_device.dev;
+ ppfinit("platform add USB DEVICE done %s.\n"
+ , msp_devs[1]->name);
+#endif
+#else
+ ppfinit("%s: usb_gadget not supported\n", __FILE__);
+#endif /* CONFIG_USB_GADGET */
}
-#endif /* CONFIG_USB_GADGET */
-#endif /* CONFIG_USB_EHCI_HCD || CONFIG_USB_GADGET */
+ /* add device */
+ platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
- return result;
+ return 0;
}
subsys_initcall(msp_usb_setup);
+#endif /* CONFIG_USB_EHCI_HCD || CONFIG_USB_GADGET */
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 27115b4..f2a45ba 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3377,12 +3377,43 @@ static void hub_events(void)
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+#define OVER_CURR_DELAY 100
+ /* clear OCC bit */
+ clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_OVER_CURRENT);
+
+ /* This step is required to toggle the PP bit
+ * to 0 and 1 (by hub_power_on) in order the
+ * CSC bit to be transitioned
+ * properly for device hotplug
+ */
+ /* clear PP bit */
+ clear_port_feature(hdev, i,
+ USB_PORT_FEAT_POWER);
+
+ /* resume power */
+ hub_power_on(hub, true);
+
+ /* delay 100 usec */
+ udelay(OVER_CURR_DELAY);
+
+ /* read OCA bit */
+ if (portstatus &
+ (1<<USB_PORT_FEAT_OVER_CURRENT)) {
+ /* declare overcurrent */
+ dev_err(hub_dev,
+ "over-current change on port %d\n",
+ i);
+ }
+#else
dev_err (hub_dev,
"over-current change on port %d\n",
i);
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_OVER_CURRENT);
hub_power_on(hub, true);
+#endif
}
if (portchange & USB_PORT_STAT_C_RESET) {
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 2391c39..bc955d0 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -91,17 +91,28 @@ config USB_EHCI_TT_NEWSCHED
If unsure, say Y.
+config USB_EHCI_HCD_PMC_MSP
+ tristate "EHCI support for on-chip PMC MSP USB controller"
+ depends on USB_EHCI_HCD && MSP_HAS_USB
+ default y
+ select USB_EHCI_BIG_ENDIAN_DESC
+ select USB_EHCI_BIG_ENDIAN_MMIO
+ ---help---
+ Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
+ If unsure, say N.
+
config USB_EHCI_BIG_ENDIAN_MMIO
bool
depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX || \
ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x || CPU_CAVIUM_OCTEON)
+ PPC_MPC512x || CPU_CAVIUM_OCTEON || \
+ MSP_HAS_USB)
default y
config USB_EHCI_BIG_ENDIAN_DESC
bool
depends on USB_EHCI_HCD && (440EPX || ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x)
+ PPC_MPC512x || MSP_HAS_USB)
default y
config XPS_USB_HCD_XILINX
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 502a7e6..833d96a 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -120,6 +120,9 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n");
#include "ehci-dbg.c"
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+extern void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci);
+#endif
static void
timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
@@ -259,6 +262,10 @@ static void tdi_reset (struct ehci_hcd *ehci)
if (ehci_big_endian_mmio(ehci))
tmp |= USBMODE_BE;
ehci_writel(ehci, tmp, reg_ptr);
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+ /* set controller in host mode */
+ usb_hcd_tdi_set_mode(ehci);
+#endif
}
/* reset a non-running (STS_HALT == 1) controller */
@@ -1216,6 +1223,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ehci_octeon_driver
#endif
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+#include "ehci-pmcmsp.c"
+#define PLATFORM_DRIVER ehci_hcd_msp_driver
+#endif
+
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
!defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
new file mode 100644
index 0000000..b1b4f21
--- /dev/null
+++ b/drivers/usb/host/ehci-pmcmsp.c
@@ -0,0 +1,555 @@
+/*
+ * PMC MSP EHCI (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 2006 PMC-Sierra Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <msp_usb.h>
+
+/* includes */
+#define USB_CTRL_MODE_HOST 0x3
+ /* host mode */
+#define USB_CTRL_MODE_BIG_ENDIAN 0x4
+ /* big endian */
+#define USB_CTRL_MODE_STREAM_DISABLE 0x10
+ /* stream disable*/
+#define USB_CTRL_FIFO_THRESH 0x00300000
+ /* thresh hold */
+#define USB_EHCI_REG_USB_MODE 0x68
+ /* register offset for usb_mode */
+#define USB_EHCI_REG_USB_FIFO 0x24
+ /* register offset for usb fifo */
+#define USB_EHCI_REG_USB_STATUS 0x44
+ /* register offset for usb status */
+#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
+ /* serial/parallel transceiver */
+#define MSP_PIN_USB0_HOST_DEV 49
+ /* TWI USB0 host device pin */
+#define MSP_PIN_USB1_HOST_DEV 50
+ /* TWI USB1 host device pin */
+
+extern int usb_disabled(void);
+
+void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
+{
+ u8 *base;
+ u8 *statreg;
+ u8 *fiforeg;
+ u32 val;
+ struct ehci_regs *reg_base = ehci->regs;
+
+ /* get register base */
+ base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
+ statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
+ fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
+
+ /* set the controller to host mode and BIG ENDIAN */
+ ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
+ | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
+
+ /* clear STS to select parallel transceiver interface */
+ val = ehci_readl(ehci, (u32 *)statreg);
+ val = val & ~USB_EHCI_REG_BIT_STAT_STS;
+ ehci_writel(ehci, val, (u32 *)statreg);
+
+ /* write to set the proper fifo threshold */
+ ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
+
+ /* set TWI GPIO USB_HOST_DEV pin high */
+ gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
+#endif
+}
+
+/* called after powerup, by probe or system-pm "wakeup" */
+static int ehci_msp_reinit(struct ehci_hcd *ehci)
+{
+ ehci_port_power(ehci, 0);
+
+ return 0;
+}
+
+/* called during probe() after chip reset completes */
+static int ehci_msp_setup(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ int retval;
+#if 1
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+ ehci->caps = hcd->regs;
+ ehci->regs = hcd->regs +
+ HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ /* cache this readonly data; minimize chip reads */
+ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+#endif
+ hcd->has_tt = 1;
+ tdi_reset(ehci);
+
+ retval = ehci_halt(ehci);
+ if (retval)
+ return retval;
+
+ ehci_reset(ehci);
+
+ /* data structure init */
+ retval = ehci_init(hcd);
+ if (retval)
+ return retval;
+
+ temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
+ temp &= 0x0f;
+ if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
+ ehci_dbg(ehci, "bogus port configuration: "
+ "cc=%d x pcc=%d < ports=%d\n",
+ HCS_N_CC(ehci->hcs_params),
+ HCS_N_PCC(ehci->hcs_params),
+ HCS_N_PORTS(ehci->hcs_params));
+ }
+
+ retval = ehci_msp_reinit(ehci);
+
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_PM
+
+/* suspend/resume, section 4.3 */
+
+/* These routines rely on the bus glue
+ * to handle powerdown and wakeup, and currently also on
+ * transceivers that don't need any software attention to set up
+ * the right sort of wakeup.
+ * Also they depend on separate root hub suspend/resume.
+ */
+
+static int ehci_msp_suspend(struct usb_hcd *hcd, pm_message_t message)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned long flags;
+ int rc = 0;
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible, bail out if RH has been resumed. Use
+ * the spinlock to properly synchronize with possible pending
+ * RH suspend or resume activity.
+ *
+ * This is still racy as hcd->state is manipulated outside of
+ * any locks =P But that will be a different fix.
+ */
+ spin_lock_irqsave(&ehci->lock, flags);
+ if (hcd->state != HC_STATE_SUSPENDED) {
+ rc = -EINVAL;
+ goto bail;
+ }
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ bail:
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /* could save FLADJ in case of Vaux power loss
+ ... we'd only use it to handle clock skew */
+
+ return rc;
+}
+
+static int ehci_msp_resume(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned port;
+ struct usb_device *root = hcd->self.root_hub;
+ int retval = -EINVAL;
+
+ /* maybe restore FLADJ */
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(100);
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ /* If CF is clear, we lost PCI Vaux power and need to restart. */
+ if (ehci_readl(ehci, &ehci->regs->configured_flag) != FLAG_CF)
+ goto restart;
+
+ /* If any port is suspended (or owned by the companion),
+ * we know we can/must resume the HC (and mustn't reset it).
+ * We just defer that to the root hub code.
+ */
+ for (port = HCS_N_PORTS(ehci->hcs_params); port > 0; ) {
+ u32 status;
+ port--;
+ status = ehci_readl(ehci, &ehci->regs->port_status[port]);
+ if (!(status & PORT_POWER))
+ continue;
+ if (status & (PORT_SUSPEND | PORT_RESUME | PORT_OWNER)) {
+ usb_hcd_resume_root_hub(hcd);
+ return 0;
+ }
+ }
+
+restart:
+ ehci_dbg(ehci, "lost power, restarting\n");
+ for (port = HCS_N_PORTS(ehci->hcs_params); port > 0; ) {
+ port--;
+ if (!root->children[port])
+ continue;
+ usb_set_device_state(root->children[port],
+ USB_STATE_NOTATTACHED);
+ }
+
+ /* Else reset, to cope with power loss or flush-to-storage
+ * style "resume" having let BIOS kick in during reboot.
+ */
+ (void) ehci_halt(ehci);
+ (void) ehci_reset(ehci);
+ (void) ehci_msp_reinit(ehci, pdev);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ ehci->reclaim_ready = 1;
+ ehci_work(ehci, NULL);
+ spin_unlock_irq(&ehci->lock);
+
+ /* restart; khubd will disconnect devices */
+ retval = ehci_run(hcd);
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ return retval;
+}
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+static void msp_start_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": starting PMC MSP EHCI USB Controller\n");
+
+ /*
+ * Now, carefully enable the USB clock, and take
+ * the USB host controller out of reset.
+ */
+ printk(KERN_DEBUG __FILE__
+ ": Clock to USB host has been enabled\n");
+}
+
+static void msp_stop_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": stopping PMC MSP EHCI USB Controller\n");
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
+{
+ struct resource *res;
+ struct platform_device *pdev = &dev->dev;
+ u32 res_len;
+ int retval;
+
+ /* MAB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res == NULL)
+ return -ENOMEM;
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "mab regs"))
+ return -EBUSY;
+
+ dev->mab_regs = ioremap_nocache(res->start, res_len);
+ if (dev->mab_regs == NULL) {
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ /* MSP USB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res == NULL) {
+ retval = -ENOMEM;
+ goto err2;
+ }
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "usbid regs")) {
+ retval = -EBUSY;
+ goto err2;
+ }
+ dev->usbid_regs = ioremap_nocache(res->start, res_len);
+ if (dev->usbid_regs == NULL) {
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ return 0;
+err3:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+err2:
+ iounmap(dev->mab_regs);
+err1:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+ dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
+ return retval;
+}
+
+/**
+ * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+int usb_hcd_msp_probe(const struct hc_driver *driver,
+ struct platform_device *dev)
+{
+ int retval;
+ struct usb_hcd *hcd;
+ struct resource *res;
+ struct ehci_hcd *ehci ;
+
+ hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
+ if (!hcd)
+ return -ENOMEM;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ pr_debug("No IOMEM resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err1;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = res->end - res->start + 1;
+ if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
+ retval = -EBUSY;
+ goto err1;
+ }
+ hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
+ if (!hcd->regs) {
+ pr_debug("ioremap failed");
+ retval = -ENOMEM;
+ goto err2;
+ }
+ msp_start_hc(dev);
+
+ res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ /* Map non-EHCI register spaces */
+ retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
+ if (retval != 0)
+ goto err3;
+
+ ehci = hcd_to_ehci(hcd);
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+
+ retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
+ if (retval == 0)
+ return 0;
+
+ usb_remove_hcd(hcd);
+err3:
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+err2:
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+err1:
+ usb_put_hcd(hcd);
+
+ return retval;
+}
+
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/**
+ * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_msp_probe(), first invoking
+ * the HCD's stop() method. It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+ */
+void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
+{
+ usb_remove_hcd(hcd);
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+}
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+/*-------------------------------------------------------------------------*/
+/*
+ * Wrapper around the main ehci_irq. Since both USB host controllers are
+ * sharing the same IRQ, need to first determine whether we're the intended
+ * recipient of this interrupt.
+ */
+static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
+{
+ u32 int_src;
+ struct device *dev = hcd->self.controller;
+ struct platform_device *pdev;
+ struct mspusb_device *mdev;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ /* need to reverse-map a couple of containers to get our device */
+ pdev = to_platform_device(dev);
+ mdev = to_mspusb_device(pdev);
+
+ /* Check to see if this interrupt is for this host controller */
+ int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
+ if (int_src & (1 << pdev->id))
+ return ehci_irq(hcd);
+
+ /* Not for this device */
+ return IRQ_NONE;
+}
+/*-------------------------------------------------------------------------*/
+#endif /* DUAL_USB */
+
+static const struct hc_driver ehci_msp_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "PMC MSP EHCI",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ .irq = ehci_msp_irq,
+#else
+ .irq = ehci_irq,
+#endif
+ .flags = HCD_MEMORY | HCD_USB2,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = ehci_msp_setup,
+ .start = ehci_run,
+#ifdef CONFIG_PM
+ .suspend = ehci_msp_suspend,
+ .resume = ehci_msp_resume,
+#endif /*CONFIG_PM*/
+ .stop = ehci_stop,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ehci_urb_enqueue,
+ .urb_dequeue = ehci_urb_dequeue,
+ .endpoint_disable = ehci_endpoint_disable,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ehci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = ehci_hub_control,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("In ehci_hcd_msp_drv_probe");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
+#endif
+
+ ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
+
+ return ret;
+}
+
+static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_hcd_msp_remove(hcd, pdev);
+
+ /* free TWI GPIO USB_HOST_DEV pin */
+ gpio_free(MSP_PIN_USB0_HOST_DEV);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_free(MSP_PIN_USB1_HOST_DEV);
+#endif
+
+ return 0;
+}
+
+MODULE_ALIAS("pmcmsp-ehci");
+static struct platform_driver ehci_hcd_msp_driver = {
+ .probe = ehci_hcd_msp_drv_probe,
+ .remove = ehci_hcd_msp_drv_remove,
+ .driver = {
+ .name = "pmcmsp-ehci",
+ },
+};
--
1.7.0.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] EHCI support for on-chip PMC MSP USB controller.
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
@ 2010-12-21 16:00 ` Alan Stern
2010-12-21 17:59 ` Greg KH
2010-12-22 14:34 ` [PATCH V2 0/2] " Anoop P.A
` (2 subsequent siblings)
3 siblings, 1 reply; 743+ messages in thread
From: Alan Stern @ 2010-12-21 16:00 UTC (permalink / raw)
To: Anoop P
Cc: Ralf Baechle, gregkh, dbrownell, sarah.a.sharp, andiry.xu, agust,
ddaney, gadiyar, linux-mips, linux-kernel, linux-usb
On Tue, 21 Dec 2010, Anoop P wrote:
> From: Anoop P A <anoop.pa@gmail.com>
>
> This patch includes.
>
> 1. USB host driver for MSP71xx family SoC on-chip USB controller.
> 2. Platform support for USB controller.
It also contains changes to the core USB hub driver code. You should
mention things like that in the patch description.
...
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 27115b4..f2a45ba 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -3377,12 +3377,43 @@ static void hub_events(void)
> }
>
> if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
> +#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
> +#define OVER_CURR_DELAY 100
What happens if CONFIG_USB_EHCI_HCD_PMC_MSP is defined, but an
overcurrent status is detected on an external hub instead of the root
hub?
> + /* clear OCC bit */
> + clear_port_feature(hdev, i,
> + USB_PORT_FEAT_C_OVER_CURRENT);
> +
> + /* This step is required to toggle the PP bit
> + * to 0 and 1 (by hub_power_on) in order the
> + * CSC bit to be transitioned
> + * properly for device hotplug
> + */
> + /* clear PP bit */
> + clear_port_feature(hdev, i,
> + USB_PORT_FEAT_POWER);
> +
> + /* resume power */
> + hub_power_on(hub, true);
> +
> + /* delay 100 usec */
> + udelay(OVER_CURR_DELAY);
> +
> + /* read OCA bit */
> + if (portstatus &
> + (1<<USB_PORT_FEAT_OVER_CURRENT)) {
> + /* declare overcurrent */
> + dev_err(hub_dev,
> + "over-current change on port %d\n",
> + i);
> + }
> +#else
> dev_err (hub_dev,
> "over-current change on port %d\n",
> i);
> clear_port_feature(hdev, i,
> USB_PORT_FEAT_C_OVER_CURRENT);
> hub_power_on(hub, true);
> +#endif
> }
"#ifdef" inside code like this is strongly discouraged. This should be
written using a separate subroutine.
...
> diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
> new file mode 100644
> index 0000000..b1b4f21
> --- /dev/null
> +++ b/drivers/usb/host/ehci-pmcmsp.c
> +static const struct hc_driver ehci_msp_hc_driver = {
> + .description = hcd_name,
> + .product_desc = "PMC MSP EHCI",
> + .hcd_priv_size = sizeof(struct ehci_hcd),
> +
> + /*
> + * generic hardware linkage
> + */
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> + .irq = ehci_msp_irq,
> +#else
> + .irq = ehci_irq,
> +#endif
> + .flags = HCD_MEMORY | HCD_USB2,
> +
> + /*
> + * basic lifecycle operations
> + */
> + .reset = ehci_msp_setup,
> + .start = ehci_run,
> +#ifdef CONFIG_PM
> + .suspend = ehci_msp_suspend,
> + .resume = ehci_msp_resume,
> +#endif /*CONFIG_PM*/
> + .stop = ehci_stop,
> +
> + /*
> + * managing i/o requests and associated device resources
> + */
> + .urb_enqueue = ehci_urb_enqueue,
> + .urb_dequeue = ehci_urb_dequeue,
> + .endpoint_disable = ehci_endpoint_disable,
> +
> + /*
> + * scheduling support
> + */
> + .get_frame_number = ehci_get_frame,
> +
> + /*
> + * root hub support
> + */
> + .hub_status_data = ehci_hub_status_data,
> + .hub_control = ehci_hub_control,
> +};
This appears to have been copied from a really old version of
ehci-pci.c. You should start with the most up-to-date code.
Alan Stern
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] EHCI support for on-chip PMC MSP USB controller.
2010-12-21 16:00 ` Alan Stern
@ 2010-12-21 17:59 ` Greg KH
0 siblings, 0 replies; 743+ messages in thread
From: Greg KH @ 2010-12-21 17:59 UTC (permalink / raw)
To: Alan Stern, Anoop P, Ralf Baechle, dbrownell, sarah.a.sharp,
andiry.xu, agust, ddaney, gadiyar, linux-mips, linux-kernel,
linux-usb
On Tue, Dec 21, 2010 at 11:00:02AM -0500, Alan Stern wrote:
> On Tue, 21 Dec 2010, Anoop P wrote:
>
> > From: Anoop P A <anoop.pa@gmail.com>
> >
> > This patch includes.
> >
> > 1. USB host driver for MSP71xx family SoC on-chip USB controller.
> > 2. Platform support for USB controller.
>
> It also contains changes to the core USB hub driver code. You should
> mention things like that in the patch description.
And that portion of the code should be split into a different patch to
make it easier to review.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH V2 0/2] EHCI support for on-chip PMC MSP USB controller
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
2010-12-21 16:00 ` Alan Stern
@ 2010-12-22 14:34 ` Anoop P.A
2010-12-22 14:36 ` [PATCH V2 1/2] " Anoop P.A
2010-12-22 14:36 ` [PATCH V2 2/2] MSP onchip root hub over current quirk Anoop P.A
3 siblings, 0 replies; 743+ messages in thread
From: Anoop P.A @ 2010-12-22 14:34 UTC (permalink / raw)
To: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, Alan Stern, linux-mips, linux-kernel, linux-usb,
Sarah Sharp, Oliver Neukum, Hans de Goede, Paul Mortier,
Andiry Xu
Cc: Anoop P A
From: Anoop P A <anoop.pa@gmail.com>
Changes Since V1:
1.Updated driver code with changes from ehci-pci.c
2.Moved over current fixup to different patch.
3.Removed #ifdef and added quirk list entry for overcurrent fixup.
Anoop P A (2):
EHCI support for on-chip PMC MSP USB controller.
MSP onchip root hub over current quirk.
.../mips/include/asm/pmc-sierra/msp71xx/msp_regs.h | 17 +-
arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h | 144 +++++
arch/mips/pmc-sierra/Kconfig | 8 +
arch/mips/pmc-sierra/msp71xx/Makefile | 2 +-
arch/mips/pmc-sierra/msp71xx/msp_usb.c | 239 +++++++---
drivers/usb/core/hub.c | 45 ++-
drivers/usb/core/quirks.c | 3 +
drivers/usb/host/Kconfig | 15 +-
drivers/usb/host/ehci-hcd.c | 12 +
drivers/usb/host/ehci-pmcmsp.c | 551 ++++++++++++++++++++
include/linux/usb/quirks.h | 3 +
11 files changed, 959 insertions(+), 80 deletions(-)
create mode 100644 arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
create mode 100644 drivers/usb/host/ehci-pmcmsp.c
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH V2 1/2] EHCI support for on-chip PMC MSP USB controller.
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
2010-12-21 16:00 ` Alan Stern
2010-12-22 14:34 ` [PATCH V2 0/2] " Anoop P.A
@ 2010-12-22 14:36 ` Anoop P.A
2010-12-22 14:58 ` Anoop P A
2010-12-24 9:44 ` Shane McDonald
2010-12-22 14:36 ` [PATCH V2 2/2] MSP onchip root hub over current quirk Anoop P.A
3 siblings, 2 replies; 743+ messages in thread
From: Anoop P.A @ 2010-12-22 14:36 UTC (permalink / raw)
To: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, Alan Stern, linux-mips, linux-kernel, linux-usb
Cc: Anoop P A
From: Anoop P A <anoop.pa@gmail.com>
This patch includes.
1. USB host driver for MSP71xx family SoC on-chip USB controller.
2. Platform support for USB controller.
Signed-off-by: Anoop P A <anoop.pa@gmail.com>
---
.../mips/include/asm/pmc-sierra/msp71xx/msp_regs.h | 17 +-
arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h | 144 +++++
arch/mips/pmc-sierra/Kconfig | 8 +
arch/mips/pmc-sierra/msp71xx/Makefile | 2 +-
arch/mips/pmc-sierra/msp71xx/msp_usb.c | 239 +++++++---
drivers/usb/host/Kconfig | 15 +-
drivers/usb/host/ehci-hcd.c | 12 +
drivers/usb/host/ehci-pmcmsp.c | 551 ++++++++++++++++++++
8 files changed, 914 insertions(+), 74 deletions(-)
create mode 100644 arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
create mode 100644 drivers/usb/host/ehci-pmcmsp.c
diff --git a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
index 603eb73..692c1b6 100644
--- a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
+++ b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_regs.h
@@ -91,12 +91,10 @@
/* MAC C device registers */
#define MSP_ADSL2_BASE (MSP_MSB_BASE + 0xA80000)
/* ADSL2 device registers */
-#define MSP_USB_BASE (MSP_MSB_BASE + 0xB40000)
- /* USB device registers */
-#define MSP_USB_BASE_START (MSP_MSB_BASE + 0xB40100)
- /* USB device registers */
-#define MSP_USB_BASE_END (MSP_MSB_BASE + 0xB401FF)
- /* USB device registers */
+#define MSP_USB0_BASE (MSP_MSB_BASE + 0xB00000)
+ /* USB0 device registers */
+#define MSP_USB1_BASE (MSP_MSB_BASE + 0x300000)
+ /* USB1 device registers */
#define MSP_CPUIF_BASE (MSP_MSB_BASE + 0xC00000)
/* CPU interface registers */
@@ -319,8 +317,11 @@
#define CPU_ERR2_REG regptr(MSP_SLP_BASE + 0x184)
/* CPU/SLP Error status 1 */
-#define EXTENDED_GPIO_REG regptr(MSP_SLP_BASE + 0x188)
- /* Extended GPIO register */
+/* Extended GPIO registers */
+#define EXTENDED_GPIO1_REG regptr(MSP_SLP_BASE + 0x188)
+#define EXTENDED_GPIO2_REG regptr(MSP_SLP_BASE + 0x18c)
+#define EXTENDED_GPIO_REG EXTENDED_GPIO1_REG
+ /* Backward-compatibility */
/* System Error registers */
#define SLP_ERR_STS_REG regptr(MSP_SLP_BASE + 0x190)
diff --git a/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
new file mode 100644
index 0000000..4c9348d
--- /dev/null
+++ b/arch/mips/include/asm/pmc-sierra/msp71xx/msp_usb.h
@@ -0,0 +1,144 @@
+/******************************************************************
+ * Copyright (c) 2000-2007 PMC-Sierra INC.
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
+ * 02139, USA.
+ *
+ * PMC-SIERRA INC. DISCLAIMS ANY LIABILITY OF ANY KIND
+ * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS
+ * SOFTWARE.
+ */
+#ifndef MSP_USB_H_
+#define MSP_USB_H_
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+#define NUM_USB_DEVS 2
+#else
+#define NUM_USB_DEVS 1
+#endif
+
+/* Register spaces for USB host 0 */
+#define MSP_USB0_MAB_START (MSP_USB0_BASE + 0x0)
+#define MSP_USB0_MAB_END (MSP_USB0_BASE + 0x17)
+#define MSP_USB0_ID_START (MSP_USB0_BASE + 0x40000)
+#define MSP_USB0_ID_END (MSP_USB0_BASE + 0x4008f)
+#define MSP_USB0_HS_START (MSP_USB0_BASE + 0x40100)
+#define MSP_USB0_HS_END (MSP_USB0_BASE + 0x401FF)
+
+/* Register spaces for USB host 1 */
+#define MSP_USB1_MAB_START (MSP_USB1_BASE + 0x0)
+#define MSP_USB1_MAB_END (MSP_USB1_BASE + 0x17)
+#define MSP_USB1_ID_START (MSP_USB1_BASE + 0x40000)
+#define MSP_USB1_ID_END (MSP_USB1_BASE + 0x4008f)
+#define MSP_USB1_HS_START (MSP_USB1_BASE + 0x40100)
+#define MSP_USB1_HS_END (MSP_USB1_BASE + 0x401ff)
+
+/* USB Identification registers */
+struct msp_usbid_regs {
+ u32 id; /* 0x0: Identification register */
+ u32 hwgen; /* 0x4: General HW params */
+ u32 hwhost; /* 0x8: Host HW params */
+ u32 hwdev; /* 0xc: Device HW params */
+ u32 hwtxbuf; /* 0x10: Tx buffer HW params */
+ u32 hwrxbuf; /* 0x14: Rx buffer HW params */
+ u32 reserved[26];
+ u32 timer0_load; /* 0x80: General-purpose timer 0 load*/
+ u32 timer0_ctrl; /* 0x84: General-purpose timer 0 control */
+ u32 timer1_load; /* 0x88: General-purpose timer 1 load*/
+ u32 timer1_ctrl; /* 0x8c: General-purpose timer 1 control */
+};
+
+/* MSBus to AMBA registers */
+struct msp_mab_regs {
+ u32 isr; /* 0x0: Interrupt status */
+ u32 imr; /* 0x4: Interrupt mask */
+ u32 thcr0; /* 0x8: Transaction header capture 0 */
+ u32 thcr1; /* 0xc: Transaction header capture 1 */
+ u32 int_stat; /* 0x10: Interrupt status summary */
+ u32 phy_cfg; /* 0x14: USB phy config */
+};
+
+/* EHCI registers */
+struct msp_usbhs_regs {
+ u32 hciver; /* 0x0: Version and offset to operational regs */
+ u32 hcsparams; /* 0x4: Host control structural parameters */
+ u32 hccparams; /* 0x8: Host control capability parameters */
+ u32 reserved0[5];
+ u32 dciver; /* 0x20: Device interface version */
+ u32 dccparams; /* 0x24: Device control capability parameters */
+ u32 reserved1[6];
+ u32 cmd; /* 0x40: USB command */
+ u32 sts; /* 0x44: USB status */
+ u32 int_ena; /* 0x48: USB interrupt enable */
+ u32 frindex; /* 0x4c: Frame index */
+ u32 reserved3;
+ union {
+ struct {
+ u32 flb_addr; /* 0x54: Frame list base address */
+ u32 next_async_addr; /* 0x58: next asynchronous addr */
+ u32 ttctrl; /* 0x5c: embedded transaction translator
+ async buffer status */
+ u32 burst_size; /* 0x60: Controller burst size */
+ u32 tx_fifo_ctrl; /* 0x64: Tx latency FIFO tuning */
+ u32 reserved0[4];
+ u32 endpt_nak; /* 0x78: Endpoint NAK */
+ u32 endpt_nak_ena; /* 0x7c: Endpoint NAK enable */
+ u32 cfg_flag; /* 0x80: Config flag */
+ u32 port_sc1; /* 0x84: Port status & control 1 */
+ u32 reserved1[7];
+ u32 otgsc; /* 0xa4: OTG status & control */
+ u32 mode; /* 0xa8: USB controller mode */
+ } host;
+
+ struct {
+ u32 dev_addr; /* 0x54: Device address */
+ u32 endpt_list_addr; /* 0x58: Endpoint list address */
+ u32 reserved0[7];
+ u32 endpt_nak; /* 0x74 */
+ u32 endpt_nak_ctrl; /* 0x78 */
+ u32 cfg_flag; /* 0x80 */
+ u32 port_sc1; /* 0x84: Port status & control 1 */
+ u32 reserved[7];
+ u32 otgsc; /* 0xa4: OTG status & control */
+ u32 mode; /* 0xa8: USB controller mode */
+ u32 endpt_setup_stat; /* 0xac */
+ u32 endpt_prime; /* 0xb0 */
+ u32 endpt_flush; /* 0xb4 */
+ u32 endpt_stat; /* 0xb8 */
+ u32 endpt_complete; /* 0xbc */
+ u32 endpt_ctrl0; /* 0xc0 */
+ u32 endpt_ctrl1; /* 0xc4 */
+ u32 endpt_ctrl2; /* 0xc8 */
+ u32 endpt_ctrl3; /* 0xcc */
+ } device;
+ } u;
+};
+/*
+ * Container for the more-generic platform_device.
+ * This exists mainly as a way to map the non-standard register
+ * spaces and make them accessible to the USB ISR.
+ */
+struct mspusb_device {
+ struct msp_mab_regs __iomem *mab_regs;
+ struct msp_usbid_regs __iomem *usbid_regs;
+ struct msp_usbhs_regs __iomem *usbhs_regs;
+ struct platform_device dev;
+};
+
+#define to_mspusb_device(x) container_of((x), struct mspusb_device, dev)
+#define TO_HOST_ID(x) ((x) & 0x3)
+#endif /*MSP_USB_H_*/
diff --git a/arch/mips/pmc-sierra/Kconfig b/arch/mips/pmc-sierra/Kconfig
index 8d79849..a80ad25 100644
--- a/arch/mips/pmc-sierra/Kconfig
+++ b/arch/mips/pmc-sierra/Kconfig
@@ -23,6 +23,7 @@ config PMC_MSP7120_GW
select SYS_SUPPORTS_MULTITHREADING
select IRQ_MSP_CIC
select HW_HAS_PCI
+ select MSP_HAS_USB
config PMC_MSP7120_FPGA
bool "PMC-Sierra MSP7120 FPGA"
@@ -35,3 +36,10 @@ endchoice
config HYPERTRANSPORT
bool "Hypertransport Support for PMC-Sierra Yosemite"
depends on PMC_YOSEMITE
+
+
+config MSP_HAS_USB
+ boolean
+ depends on PMC_MSP
+ select USB_ARCH_HAS_EHCI
+ select USB_ARCH_HAS_HCD
diff --git a/arch/mips/pmc-sierra/msp71xx/Makefile b/arch/mips/pmc-sierra/msp71xx/Makefile
index 09627ae..380d39d 100644
--- a/arch/mips/pmc-sierra/msp71xx/Makefile
+++ b/arch/mips/pmc-sierra/msp71xx/Makefile
@@ -9,5 +9,5 @@ obj-$(CONFIG_IRQ_MSP_SLP) += msp_irq_slp.o
obj-$(CONFIG_IRQ_MSP_CIC) += msp_irq_cic.o msp_irq_per.o
obj-$(CONFIG_PCI) += msp_pci.o
obj-$(CONFIG_MSPETH) += msp_eth.o
-obj-$(CONFIG_USB_MSP71XX) += msp_usb.o
+obj-$(CONFIG_MSP_HAS_USB) += msp_usb.o
obj-$(CONFIG_MIPS_MT_SMP) += msp_smp.o
diff --git a/arch/mips/pmc-sierra/msp71xx/msp_usb.c b/arch/mips/pmc-sierra/msp71xx/msp_usb.c
index 0ee01e3..9a1aef8 100644
--- a/arch/mips/pmc-sierra/msp71xx/msp_usb.c
+++ b/arch/mips/pmc-sierra/msp71xx/msp_usb.c
@@ -1,7 +1,7 @@
/*
* The setup file for USB related hardware on PMC-Sierra MSP processors.
*
- * Copyright 2006-2007 PMC-Sierra, Inc.
+ * Copyright 2006 PMC-Sierra, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -23,8 +23,8 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
+#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
-#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
@@ -34,40 +34,56 @@
#include <msp_regs.h>
#include <msp_int.h>
#include <msp_prom.h>
+#include <msp_usb.h>
+
#if defined(CONFIG_USB_EHCI_HCD)
-static struct resource msp_usbhost_resources [] = {
- [0] = {
- .start = MSP_USB_BASE_START,
- .end = MSP_USB_BASE_END,
- .flags = IORESOURCE_MEM,
+static struct resource msp_usbhost0_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB0_HS_START,
+ .end = MSP_USB0_HS_END,
+ .flags = IORESOURCE_MEM,
},
[1] = {
- .start = MSP_INT_USB,
- .end = MSP_INT_USB,
- .flags = IORESOURCE_IRQ,
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB0_MAB_START,
+ .end = MSP_USB0_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB0_ID_START,
+ .end = MSP_USB0_ID_END,
+ .flags = IORESOURCE_MEM,
},
};
-static u64 msp_usbhost_dma_mask = DMA_BIT_MASK(32);
+static u64 msp_usbhost0_dma_mask = 0xffffffffUL;
-static struct platform_device msp_usbhost_device = {
- .name = "pmcmsp-ehci",
- .id = 0,
+static struct mspusb_device msp_usbhost0_device = {
.dev = {
- .dma_mask = &msp_usbhost_dma_mask,
- .coherent_dma_mask = DMA_BIT_MASK(32),
+ .name = "pmcmsp-ehci",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbhost0_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbhost0_resources),
+ .resource = msp_usbhost0_resources,
},
- .num_resources = ARRAY_SIZE(msp_usbhost_resources),
- .resource = msp_usbhost_resources,
};
-#endif /* CONFIG_USB_EHCI_HCD */
-#if defined(CONFIG_USB_GADGET)
-static struct resource msp_usbdev_resources [] = {
- [0] = {
- .start = MSP_USB_BASE,
- .end = MSP_USB_BASE_END,
+/* MSP7140/MSP82XX has two USB2 hosts. */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+static u64 msp_usbhost1_dma_mask = 0xffffffffUL;
+
+static struct resource msp_usbhost1_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB1_HS_START,
+ .end = MSP_USB1_HS_END,
.flags = IORESOURCE_MEM,
},
[1] = {
@@ -75,76 +91,173 @@ static struct resource msp_usbdev_resources [] = {
.end = MSP_INT_USB,
.flags = IORESOURCE_IRQ,
},
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB1_MAB_START,
+ .end = MSP_USB1_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB1_ID_START,
+ .end = MSP_USB1_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct mspusb_device msp_usbhost1_device = {
+ .dev = {
+ .name = "pmcmsp-ehci",
+ .id = 1,
+ .dev = {
+ .dma_mask = &msp_usbhost1_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbhost1_resources),
+ .resource = msp_usbhost1_resources,
+ },
};
+#endif /* CONFIG_MSP_HAS_DUAL_USB */
+#endif /* CONFIG_USB_EHCI_HCD */
-static u64 msp_usbdev_dma_mask = DMA_BIT_MASK(32);
+#if defined(CONFIG_USB_GADGET)
+static struct resource msp_usbdev0_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB0_HS_START,
+ .end = MSP_USB0_HS_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB0_MAB_START,
+ .end = MSP_USB0_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB0_ID_START,
+ .end = MSP_USB0_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
-static struct platform_device msp_usbdev_device = {
- .name = "msp71xx_udc",
- .id = 0,
+static u64 msp_usbdev_dma_mask = 0xffffffffUL;
+
+/* This may need to be converted to a mspusb_device, too. */
+static struct mspusb_device msp_usbdev0_device = {
.dev = {
- .dma_mask = &msp_usbdev_dma_mask,
- .coherent_dma_mask = DMA_BIT_MASK(32),
+ .name = "msp71xx_udc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbdev_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbdev0_resources),
+ .resource = msp_usbdev0_resources,
},
- .num_resources = ARRAY_SIZE(msp_usbdev_resources),
- .resource = msp_usbdev_resources,
};
-#endif /* CONFIG_USB_GADGET */
-#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
-static struct platform_device *msp_devs[1];
-#endif
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+static struct resource msp_usbdev1_resources[] = {
+ [0] = { /* EHCI-HS operational and capabilities registers */
+ .start = MSP_USB1_HS_START,
+ .end = MSP_USB1_HS_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MSP_INT_USB,
+ .end = MSP_INT_USB,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = { /* MSBus-to-AMBA bridge register space */
+ .start = MSP_USB1_MAB_START,
+ .end = MSP_USB1_MAB_END,
+ .flags = IORESOURCE_MEM,
+ },
+ [3] = { /* Identification and general hardware parameters */
+ .start = MSP_USB1_ID_START,
+ .end = MSP_USB1_ID_END,
+ .flags = IORESOURCE_MEM,
+ },
+};
+/* This may need to be converted to a mspusb_device, too. */
+static struct mspusb_device msp_usbdev1_device = {
+ .dev = {
+ .name = "msp71xx_udc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &msp_usbdev_dma_mask,
+ .coherent_dma_mask = 0xffffffffUL,
+ },
+ .num_resources = ARRAY_SIZE(msp_usbdev1_resources),
+ .resource = msp_usbdev1_resources,
+ },
+};
+
+#endif /* CONFIG_MSP_HAS_DUAL_USB */
+#endif /* CONFIG_USB_GADGET */
static int __init msp_usb_setup(void)
{
-#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_GADGET)
- char *strp;
- char envstr[32];
- unsigned int val = 0;
- int result = 0;
+ char *strp;
+ char envstr[32];
+ struct platform_device *msp_devs[NUM_USB_DEVS];
+ unsigned int val;
+ /* construct environment name usbmode */
+ /* set usbmode <host/device> as pmon environment var */
/*
- * construct environment name usbmode
- * set usbmode <host/device> as pmon environment var
+ * Could this perhaps be integrated into the "features" env var?
+ * Use the features key "U", and follow with "H" for host-mode,
+ * "D" for device-mode. If it works for Ethernet, why not USB...
+ * -- hammtrev, 2007/03/22
*/
snprintf((char *)&envstr[0], sizeof(envstr), "usbmode");
-#if defined(CONFIG_USB_EHCI_HCD)
- /* default to host mode */
+ /* set default host mode */
val = 1;
-#endif
/* get environment string */
strp = prom_getenv((char *)&envstr[0]);
if (strp) {
+ /* compare string */
if (!strcmp(strp, "device"))
val = 0;
}
if (val) {
#if defined(CONFIG_USB_EHCI_HCD)
- /* get host mode device */
- msp_devs[0] = &msp_usbhost_device;
- ppfinit("platform add USB HOST done %s.\n",
- msp_devs[0]->name);
-
- result = platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
-#endif /* CONFIG_USB_EHCI_HCD */
- }
+ msp_devs[0] = &msp_usbhost0_device.dev;
+ ppfinit("platform add USB HOST done %s.\n", msp_devs[0]->name);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ msp_devs[1] = &msp_usbhost1_device.dev;
+ ppfinit("platform add USB HOST done %s.\n", msp_devs[1]->name);
+#endif
+#else
+ ppfinit("%s: echi_hcd not supported\n", __FILE__);
+#endif /* CONFIG_USB_EHCI_HCD */
+ } else {
#if defined(CONFIG_USB_GADGET)
- else {
/* get device mode structure */
- msp_devs[0] = &msp_usbdev_device;
- ppfinit("platform add USB DEVICE done %s.\n",
- msp_devs[0]->name);
-
- result = platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
+ msp_devs[0] = &msp_usbdev0_device.dev;
+ ppfinit("platform add USB DEVICE done %s.\n"
+ , msp_devs[0]->name);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ msp_devs[1] = &msp_usbdev1_device.dev;
+ ppfinit("platform add USB DEVICE done %s.\n"
+ , msp_devs[1]->name);
+#endif
+#else
+ ppfinit("%s: usb_gadget not supported\n", __FILE__);
+#endif /* CONFIG_USB_GADGET */
}
-#endif /* CONFIG_USB_GADGET */
-#endif /* CONFIG_USB_EHCI_HCD || CONFIG_USB_GADGET */
+ /* add device */
+ platform_add_devices(msp_devs, ARRAY_SIZE(msp_devs));
- return result;
+ return 0;
}
subsys_initcall(msp_usb_setup);
+#endif /* CONFIG_USB_EHCI_HCD || CONFIG_USB_GADGET */
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 2391c39..bc955d0 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -91,17 +91,28 @@ config USB_EHCI_TT_NEWSCHED
If unsure, say Y.
+config USB_EHCI_HCD_PMC_MSP
+ tristate "EHCI support for on-chip PMC MSP USB controller"
+ depends on USB_EHCI_HCD && MSP_HAS_USB
+ default y
+ select USB_EHCI_BIG_ENDIAN_DESC
+ select USB_EHCI_BIG_ENDIAN_MMIO
+ ---help---
+ Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
+ If unsure, say N.
+
config USB_EHCI_BIG_ENDIAN_MMIO
bool
depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX || \
ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x || CPU_CAVIUM_OCTEON)
+ PPC_MPC512x || CPU_CAVIUM_OCTEON || \
+ MSP_HAS_USB)
default y
config USB_EHCI_BIG_ENDIAN_DESC
bool
depends on USB_EHCI_HCD && (440EPX || ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x)
+ PPC_MPC512x || MSP_HAS_USB)
default y
config XPS_USB_HCD_XILINX
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 502a7e6..833d96a 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -120,6 +120,9 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n");
#include "ehci-dbg.c"
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+extern void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci);
+#endif
static void
timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
@@ -259,6 +262,10 @@ static void tdi_reset (struct ehci_hcd *ehci)
if (ehci_big_endian_mmio(ehci))
tmp |= USBMODE_BE;
ehci_writel(ehci, tmp, reg_ptr);
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+ /* set controller in host mode */
+ usb_hcd_tdi_set_mode(ehci);
+#endif
}
/* reset a non-running (STS_HALT == 1) controller */
@@ -1216,6 +1223,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ehci_octeon_driver
#endif
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+#include "ehci-pmcmsp.c"
+#define PLATFORM_DRIVER ehci_hcd_msp_driver
+#endif
+
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
!defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
new file mode 100644
index 0000000..547f63c
--- /dev/null
+++ b/drivers/usb/host/ehci-pmcmsp.c
@@ -0,0 +1,551 @@
+/*
+ * PMC MSP EHCI (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 2006-2010 PMC-Sierra Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <msp_usb.h>
+
+/* includes */
+#define USB_CTRL_MODE_HOST 0x3
+ /* host mode */
+#define USB_CTRL_MODE_BIG_ENDIAN 0x4
+ /* big endian */
+#define USB_CTRL_MODE_STREAM_DISABLE 0x10
+ /* stream disable*/
+#define USB_CTRL_FIFO_THRESH 0x00300000
+ /* thresh hold */
+#define USB_EHCI_REG_USB_MODE 0x68
+ /* register offset for usb_mode */
+#define USB_EHCI_REG_USB_FIFO 0x24
+ /* register offset for usb fifo */
+#define USB_EHCI_REG_USB_STATUS 0x44
+ /* register offset for usb status */
+#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
+ /* serial/parallel transceiver */
+#define MSP_PIN_USB0_HOST_DEV 49
+ /* TWI USB0 host device pin */
+#define MSP_PIN_USB1_HOST_DEV 50
+ /* TWI USB1 host device pin */
+
+extern int usb_disabled(void);
+
+void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
+{
+ u8 *base;
+ u8 *statreg;
+ u8 *fiforeg;
+ u32 val;
+ struct ehci_regs *reg_base = ehci->regs;
+
+ /* get register base */
+ base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
+ statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
+ fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
+
+ /* set the controller to host mode and BIG ENDIAN */
+ ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
+ | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
+
+ /* clear STS to select parallel transceiver interface */
+ val = ehci_readl(ehci, (u32 *)statreg);
+ val = val & ~USB_EHCI_REG_BIT_STAT_STS;
+ ehci_writel(ehci, val, (u32 *)statreg);
+
+ /* write to set the proper fifo threshold */
+ ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
+
+ /* set TWI GPIO USB_HOST_DEV pin high */
+ gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
+#endif
+}
+
+/* called after powerup, by probe or system-pm "wakeup" */
+static int ehci_msp_reinit(struct ehci_hcd *ehci)
+{
+ ehci_port_power(ehci, 0);
+
+ return 0;
+}
+
+/* called during probe() after chip reset completes */
+static int ehci_msp_setup(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ int retval;
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+ ehci->caps = hcd->regs;
+ ehci->regs = hcd->regs +
+ HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ /* cache this readonly data; minimize chip reads */
+ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+ hcd->has_tt = 1;
+ tdi_reset(ehci);
+
+ retval = ehci_halt(ehci);
+ if (retval)
+ return retval;
+
+ ehci_reset(ehci);
+
+ /* data structure init */
+ retval = ehci_init(hcd);
+ if (retval)
+ return retval;
+
+ temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
+ temp &= 0x0f;
+ if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
+ ehci_dbg(ehci, "bogus port configuration: "
+ "cc=%d x pcc=%d < ports=%d\n",
+ HCS_N_CC(ehci->hcs_params),
+ HCS_N_PCC(ehci->hcs_params),
+ HCS_N_PORTS(ehci->hcs_params));
+ }
+
+ retval = ehci_msp_reinit(ehci);
+
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void msp_start_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": starting PMC MSP EHCI USB Controller\n");
+
+ /*
+ * Now, carefully enable the USB clock, and take
+ * the USB host controller out of reset.
+ */
+ printk(KERN_DEBUG __FILE__
+ ": Clock to USB host has been enabled\n");
+}
+
+static void msp_stop_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": stopping PMC MSP EHCI USB Controller\n");
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_PM
+
+/* suspend/resume, section 4.3 */
+
+/* These routines rely on the bus glue
+ * to handle powerdown and wakeup, and currently also on
+ * transceivers that don't need any software attention to set up
+ * the right sort of wakeup.
+ * Also they depend on separate root hub suspend/resume.
+ */
+static int ehci_msp_suspend(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned long flags;
+ int rc;
+
+ return 0;
+ rc = 0;
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible. The PM and USB cores make sure that
+ * the root hub is either suspended or stopped.
+ */
+ spin_lock_irqsave(&ehci->lock, flags);
+ ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /* could save FLADJ in case of Vaux power loss
+ ... we'd only use it to handle clock skew */
+
+ return rc;
+}
+
+static int ehci_msp_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+
+ /* maybe restore FLADJ */
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(100);
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ /* If CF is still set, we maintained PCI Vaux power.
+ * Just undo the effect of ehci_pci_suspend().
+ */
+ if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
+ int mask = INTR_MASK;
+
+ ehci_prepare_ports_for_controller_resume(ehci);
+ if (!hcd->self.root_hub->do_remote_wakeup)
+ mask &= ~STS_PCD;
+ ehci_writel(ehci, mask, &ehci->regs->intr_enable);
+ ehci_readl(ehci, &ehci->regs->intr_enable);
+ return 0;
+ }
+
+ ehci_dbg(ehci, "lost power, restarting\n");
+ usb_root_hub_lost_power(hcd->self.root_hub);
+
+ /* Else reset, to cope with power loss or flush-to-storage
+ * style "resume" having let BIOS kick in during reboot.
+ */
+ (void) ehci_halt(ehci);
+ (void) ehci_reset(ehci);
+ (void) ehci_msp_reinit(ehci);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+ ehci_work(ehci);
+ spin_unlock_irq(&ehci->lock);
+
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ hcd->state = HC_STATE_SUSPENDED;
+
+ return 0;
+}
+
+static const struct dev_pm_ops ehci_msp_pmops = {
+ .suspend = ehci_msp_suspend,
+ .resume = ehci_msp_resume,
+};
+#endif
+
+
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
+{
+ struct resource *res;
+ struct platform_device *pdev = &dev->dev;
+ u32 res_len;
+ int retval;
+
+ /* MAB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res == NULL)
+ return -ENOMEM;
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "mab regs"))
+ return -EBUSY;
+
+ dev->mab_regs = ioremap_nocache(res->start, res_len);
+ if (dev->mab_regs == NULL) {
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ /* MSP USB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res == NULL) {
+ retval = -ENOMEM;
+ goto err2;
+ }
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "usbid regs")) {
+ retval = -EBUSY;
+ goto err2;
+ }
+ dev->usbid_regs = ioremap_nocache(res->start, res_len);
+ if (dev->usbid_regs == NULL) {
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ return 0;
+err3:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+err2:
+ iounmap(dev->mab_regs);
+err1:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+ dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
+ return retval;
+}
+
+/**
+ * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+int usb_hcd_msp_probe(const struct hc_driver *driver,
+ struct platform_device *dev)
+{
+ int retval;
+ struct usb_hcd *hcd;
+ struct resource *res;
+ struct ehci_hcd *ehci ;
+
+ hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
+ if (!hcd)
+ return -ENOMEM;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ pr_debug("No IOMEM resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err1;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = res->end - res->start + 1;
+ if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
+ retval = -EBUSY;
+ goto err1;
+ }
+ hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
+ if (!hcd->regs) {
+ pr_debug("ioremap failed");
+ retval = -ENOMEM;
+ goto err2;
+ }
+ msp_start_hc(dev);
+
+ res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ /* Map non-EHCI register spaces */
+ retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
+ if (retval != 0)
+ goto err3;
+
+ ehci = hcd_to_ehci(hcd);
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+
+ retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
+ if (retval == 0)
+ return 0;
+
+ usb_remove_hcd(hcd);
+err3:
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+err2:
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+err1:
+ usb_put_hcd(hcd);
+
+ return retval;
+}
+
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/**
+ * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_msp_probe(), first invoking
+ * the HCD's stop() method. It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+ */
+void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
+{
+ usb_remove_hcd(hcd);
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+}
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+/*-------------------------------------------------------------------------*/
+/*
+ * Wrapper around the main ehci_irq. Since both USB host controllers are
+ * sharing the same IRQ, need to first determine whether we're the intended
+ * recipient of this interrupt.
+ */
+static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
+{
+ u32 int_src;
+ struct device *dev = hcd->self.controller;
+ struct platform_device *pdev;
+ struct mspusb_device *mdev;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ /* need to reverse-map a couple of containers to get our device */
+ pdev = to_platform_device(dev);
+ mdev = to_mspusb_device(pdev);
+
+ /* Check to see if this interrupt is for this host controller */
+ int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
+ if (int_src & (1 << pdev->id))
+ return ehci_irq(hcd);
+
+ /* Not for this device */
+ return IRQ_NONE;
+}
+/*-------------------------------------------------------------------------*/
+#endif /* DUAL_USB */
+
+static const struct hc_driver ehci_msp_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "PMC MSP EHCI",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ .irq = ehci_msp_irq,
+#else
+ .irq = ehci_irq,
+#endif
+ .flags = HCD_MEMORY | HCD_USB2,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = ehci_msp_setup,
+ .start = ehci_run,
+ .shutdown = ehci_shutdown,
+ .start = ehci_run,
+ .stop = ehci_stop,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ehci_urb_enqueue,
+ .urb_dequeue = ehci_urb_dequeue,
+ .endpoint_disable = ehci_endpoint_disable,
+ .endpoint_reset = ehci_endpoint_reset,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ehci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = ehci_hub_control,
+ .bus_suspend = ehci_bus_suspend,
+ .bus_resume = ehci_bus_resume,
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
+
+ .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+};
+
+static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("In ehci_hcd_msp_drv_probe");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
+#endif
+
+ ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
+
+ return ret;
+}
+
+static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_hcd_msp_remove(hcd, pdev);
+
+ /* free TWI GPIO USB_HOST_DEV pin */
+ gpio_free(MSP_PIN_USB0_HOST_DEV);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_free(MSP_PIN_USB1_HOST_DEV);
+#endif
+
+ return 0;
+}
+
+MODULE_ALIAS("pmcmsp-ehci");
+
+static struct platform_driver ehci_hcd_msp_driver = {
+ .probe = ehci_hcd_msp_drv_probe,
+ .remove = ehci_hcd_msp_drv_remove,
+ .driver = {
+ .name = "pmcmsp-ehci",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &ehci_msp_pmops,
+#endif
+ },
+};
--
1.7.0.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V2 1/2] EHCI support for on-chip PMC MSP USB controller.
2010-12-22 14:36 ` [PATCH V2 1/2] " Anoop P.A
@ 2010-12-22 14:58 ` Anoop P A
2010-12-24 9:44 ` Shane McDonald
1 sibling, 0 replies; 743+ messages in thread
From: Anoop P A @ 2010-12-22 14:58 UTC (permalink / raw)
To: Ralf Baechle
Cc: Greg Kroah-Hartman, Anatolij Gustschin, Anand Gadiyar,
Alan Stern, linux-mips, linux-kernel, linux-usb
On Wed, 2010-12-22 at 20:06 +0530, Anoop P.A wrote:
> From: Anoop P A <anoop.pa@gmail.com>
>
> This patch includes.
>
> 1. USB host driver for MSP71xx family SoC on-chip USB controller.
> ehci_writel(ehci, tmp, reg_ptr);
> +#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
> + /* set controller in host mode */
> + usb_hcd_tdi_set_mode(ehci);
> +#endif
Missed this one while cleaning :(
> }
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V2 1/2] EHCI support for on-chip PMC MSP USB controller.
2010-12-22 14:36 ` [PATCH V2 1/2] " Anoop P.A
2010-12-22 14:58 ` Anoop P A
@ 2010-12-24 9:44 ` Shane McDonald
2011-01-27 11:28 ` [PATCH v3] EHCI bus glue " Anoop P.A
1 sibling, 1 reply; 743+ messages in thread
From: Shane McDonald @ 2010-12-24 9:44 UTC (permalink / raw)
To: Anoop P.A
Cc: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, Alan Stern, linux-mips, linux-kernel, linux-usb
Hi Anoop:
On Wed, Dec 22, 2010 at 8:36 AM, Anoop P.A <anoop.pa@gmail.com> wrote:
> From: Anoop P A <anoop.pa@gmail.com>
>
> This patch includes.
>
> 1. USB host driver for MSP71xx family SoC on-chip USB controller.
> 2. Platform support for USB controller.
>
> Signed-off-by: Anoop P A <anoop.pa@gmail.com>
I tried to apply this patch to a pristine linux-mips.org 2.6.37-rc6 kernel,
but the patch failed on arch/mips/pmc-sierra/msp71xx/Makefile.
I think you must have applied an SMP patch to your tree before
generating this patch. That was easy to fix, and once I did that,
I tried testing this on a PMC-Sierra MSP7120 Garibaldi evaluation
board. Using your original changes to drivers/usb/core/hub.c in
addition to this patch, I was able to boot a system, plug a USB hard
drive in, and that drive was recognized by the Garibaldi.
You can add my:
Tested-by: Shane McDonald <mcdonald.shane@gmail.com>
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH v3] EHCI bus glue for on-chip PMC MSP USB controller.
2010-12-24 9:44 ` Shane McDonald
@ 2011-01-27 11:28 ` Anoop P.A
2011-02-04 19:56 ` Greg KH
` (2 more replies)
0 siblings, 3 replies; 743+ messages in thread
From: Anoop P.A @ 2011-01-27 11:28 UTC (permalink / raw)
To: gregkh, dbrownell, ust, pkondeti, stern, gadiyar, alek.du,
jacob.jun.pan, linux-usb, linux-kernel, linux-mips, ralf
Cc: Anoop P A
From: Anoop P A <anoop.pa@gmail.com>
Signed-off-by: Anoop P A <anoop.pa@gmail.com>
Tested-by: Shane McDonald <mcdonald.shane@gmail.com>
---
drivers/usb/host/Kconfig | 15 +-
drivers/usb/host/ehci-hcd.c | 7 +
drivers/usb/host/ehci-pmcmsp.c | 552 ++++++++++++++++++++++++++++++++++++++++
drivers/usb/host/ehci.h | 3 +
4 files changed, 575 insertions(+), 2 deletions(-)
create mode 100644 drivers/usb/host/ehci-pmcmsp.c
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 24046c0..1f73127 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -91,17 +91,28 @@ config USB_EHCI_TT_NEWSCHED
If unsure, say Y.
+config USB_EHCI_HCD_PMC_MSP
+ tristate "EHCI support for on-chip PMC MSP USB controller"
+ depends on USB_EHCI_HCD && MSP_HAS_USB
+ default y
+ select USB_EHCI_BIG_ENDIAN_DESC
+ select USB_EHCI_BIG_ENDIAN_MMIO
+ ---help---
+ Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
+ If unsure, say N.
+
config USB_EHCI_BIG_ENDIAN_MMIO
bool
depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX || \
ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x || CPU_CAVIUM_OCTEON)
+ PPC_MPC512x || CPU_CAVIUM_OCTEON || \
+ PMC_MSP)
default y
config USB_EHCI_BIG_ENDIAN_DESC
bool
depends on USB_EHCI_HCD && (440EPX || ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x)
+ PPC_MPC512x || PMC_MSP)
default y
config XPS_USB_HCD_XILINX
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 6fee3cd..a591890 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -262,6 +262,8 @@ static void tdi_reset (struct ehci_hcd *ehci)
if (ehci_big_endian_mmio(ehci))
tmp |= USBMODE_BE;
ehci_writel(ehci, tmp, reg_ptr);
+ if (ehci->pmc_msp_tdi)
+ usb_hcd_tdi_set_mode(ehci);
}
/* reset a non-running (STS_HALT == 1) controller */
@@ -1249,6 +1251,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ehci_msm_driver
#endif
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+#include "ehci-pmcmsp.c"
+#define PLATFORM_DRIVER ehci_hcd_msp_driver
+#endif
+
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
!defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
new file mode 100644
index 0000000..28dd26c
--- /dev/null
+++ b/drivers/usb/host/ehci-pmcmsp.c
@@ -0,0 +1,552 @@
+/*
+ * PMC MSP EHCI (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 2006-2010 PMC-Sierra Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <msp_usb.h>
+
+/* includes */
+#define USB_CTRL_MODE_HOST 0x3
+ /* host mode */
+#define USB_CTRL_MODE_BIG_ENDIAN 0x4
+ /* big endian */
+#define USB_CTRL_MODE_STREAM_DISABLE 0x10
+ /* stream disable*/
+#define USB_CTRL_FIFO_THRESH 0x00300000
+ /* thresh hold */
+#define USB_EHCI_REG_USB_MODE 0x68
+ /* register offset for usb_mode */
+#define USB_EHCI_REG_USB_FIFO 0x24
+ /* register offset for usb fifo */
+#define USB_EHCI_REG_USB_STATUS 0x44
+ /* register offset for usb status */
+#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
+ /* serial/parallel transceiver */
+#define MSP_PIN_USB0_HOST_DEV 49
+ /* TWI USB0 host device pin */
+#define MSP_PIN_USB1_HOST_DEV 50
+ /* TWI USB1 host device pin */
+
+extern int usb_disabled(void);
+
+void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
+{
+ u8 *base;
+ u8 *statreg;
+ u8 *fiforeg;
+ u32 val;
+ struct ehci_regs *reg_base = ehci->regs;
+
+ /* get register base */
+ base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
+ statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
+ fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
+
+ /* set the controller to host mode and BIG ENDIAN */
+ ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
+ | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
+
+ /* clear STS to select parallel transceiver interface */
+ val = ehci_readl(ehci, (u32 *)statreg);
+ val = val & ~USB_EHCI_REG_BIT_STAT_STS;
+ ehci_writel(ehci, val, (u32 *)statreg);
+
+ /* write to set the proper fifo threshold */
+ ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
+
+ /* set TWI GPIO USB_HOST_DEV pin high */
+ gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
+#endif
+}
+
+/* called after powerup, by probe or system-pm "wakeup" */
+static int ehci_msp_reinit(struct ehci_hcd *ehci)
+{
+ ehci_port_power(ehci, 0);
+
+ return 0;
+}
+
+/* called during probe() after chip reset completes */
+static int ehci_msp_setup(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ int retval;
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+ ehci->pmc_msp_tdi = 1;
+
+ ehci->caps = hcd->regs;
+ ehci->regs = hcd->regs +
+ HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ /* cache this readonly data; minimize chip reads */
+ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+ hcd->has_tt = 1;
+ tdi_reset(ehci);
+
+ retval = ehci_halt(ehci);
+ if (retval)
+ return retval;
+
+ ehci_reset(ehci);
+
+ /* data structure init */
+ retval = ehci_init(hcd);
+ if (retval)
+ return retval;
+
+ temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
+ temp &= 0x0f;
+ if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
+ ehci_dbg(ehci, "bogus port configuration: "
+ "cc=%d x pcc=%d < ports=%d\n",
+ HCS_N_CC(ehci->hcs_params),
+ HCS_N_PCC(ehci->hcs_params),
+ HCS_N_PORTS(ehci->hcs_params));
+ }
+
+ retval = ehci_msp_reinit(ehci);
+
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void msp_start_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": starting PMC MSP EHCI USB Controller\n");
+
+ /*
+ * Now, carefully enable the USB clock, and take
+ * the USB host controller out of reset.
+ */
+ printk(KERN_DEBUG __FILE__
+ ": Clock to USB host has been enabled\n");
+}
+
+static void msp_stop_hc(struct platform_device *dev)
+{
+ printk(KERN_DEBUG __FILE__
+ ": stopping PMC MSP EHCI USB Controller\n");
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_PM
+
+/* suspend/resume, section 4.3 */
+
+/* These routines rely on the bus glue
+ * to handle powerdown and wakeup, and currently also on
+ * transceivers that don't need any software attention to set up
+ * the right sort of wakeup.
+ * Also they depend on separate root hub suspend/resume.
+ */
+static int ehci_msp_suspend(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned long flags;
+ int rc;
+
+ return 0;
+ rc = 0;
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible. The PM and USB cores make sure that
+ * the root hub is either suspended or stopped.
+ */
+ spin_lock_irqsave(&ehci->lock, flags);
+ ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /* could save FLADJ in case of Vaux power loss
+ ... we'd only use it to handle clock skew */
+
+ return rc;
+}
+
+static int ehci_msp_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+
+ /* maybe restore FLADJ */
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(100);
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ /* If CF is still set, we maintained PCI Vaux power.
+ * Just undo the effect of ehci_pci_suspend().
+ */
+ if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
+ int mask = INTR_MASK;
+
+ ehci_prepare_ports_for_controller_resume(ehci);
+ if (!hcd->self.root_hub->do_remote_wakeup)
+ mask &= ~STS_PCD;
+ ehci_writel(ehci, mask, &ehci->regs->intr_enable);
+ ehci_readl(ehci, &ehci->regs->intr_enable);
+ return 0;
+ }
+
+ ehci_dbg(ehci, "lost power, restarting\n");
+ usb_root_hub_lost_power(hcd->self.root_hub);
+
+ /* Else reset, to cope with power loss or flush-to-storage
+ * style "resume" having let BIOS kick in during reboot.
+ */
+ (void) ehci_halt(ehci);
+ (void) ehci_reset(ehci);
+ (void) ehci_msp_reinit(ehci);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+ ehci_work(ehci);
+ spin_unlock_irq(&ehci->lock);
+
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ hcd->state = HC_STATE_SUSPENDED;
+
+ return 0;
+}
+
+static const struct dev_pm_ops ehci_msp_pmops = {
+ .suspend = ehci_msp_suspend,
+ .resume = ehci_msp_resume,
+};
+#endif
+
+
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
+{
+ struct resource *res;
+ struct platform_device *pdev = &dev->dev;
+ u32 res_len;
+ int retval;
+
+ /* MAB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res == NULL)
+ return -ENOMEM;
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "mab regs"))
+ return -EBUSY;
+
+ dev->mab_regs = ioremap_nocache(res->start, res_len);
+ if (dev->mab_regs == NULL) {
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ /* MSP USB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res == NULL) {
+ retval = -ENOMEM;
+ goto err2;
+ }
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "usbid regs")) {
+ retval = -EBUSY;
+ goto err2;
+ }
+ dev->usbid_regs = ioremap_nocache(res->start, res_len);
+ if (dev->usbid_regs == NULL) {
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ return 0;
+err3:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+err2:
+ iounmap(dev->mab_regs);
+err1:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+ dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
+ return retval;
+}
+
+/**
+ * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+int usb_hcd_msp_probe(const struct hc_driver *driver,
+ struct platform_device *dev)
+{
+ int retval;
+ struct usb_hcd *hcd;
+ struct resource *res;
+ struct ehci_hcd *ehci ;
+
+ hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
+ if (!hcd)
+ return -ENOMEM;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ pr_debug("No IOMEM resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err1;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = res->end - res->start + 1;
+ if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
+ retval = -EBUSY;
+ goto err1;
+ }
+ hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
+ if (!hcd->regs) {
+ pr_debug("ioremap failed");
+ retval = -ENOMEM;
+ goto err2;
+ }
+ msp_start_hc(dev);
+
+ res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ /* Map non-EHCI register spaces */
+ retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
+ if (retval != 0)
+ goto err3;
+
+ ehci = hcd_to_ehci(hcd);
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+
+ retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
+ if (retval == 0)
+ return 0;
+
+ usb_remove_hcd(hcd);
+err3:
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+err2:
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+err1:
+ usb_put_hcd(hcd);
+
+ return retval;
+}
+
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/**
+ * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_msp_probe(), first invoking
+ * the HCD's stop() method. It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+ */
+void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
+{
+ usb_remove_hcd(hcd);
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+}
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+/*-------------------------------------------------------------------------*/
+/*
+ * Wrapper around the main ehci_irq. Since both USB host controllers are
+ * sharing the same IRQ, need to first determine whether we're the intended
+ * recipient of this interrupt.
+ */
+static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
+{
+ u32 int_src;
+ struct device *dev = hcd->self.controller;
+ struct platform_device *pdev;
+ struct mspusb_device *mdev;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ /* need to reverse-map a couple of containers to get our device */
+ pdev = to_platform_device(dev);
+ mdev = to_mspusb_device(pdev);
+
+ /* Check to see if this interrupt is for this host controller */
+ int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
+ if (int_src & (1 << pdev->id))
+ return ehci_irq(hcd);
+
+ /* Not for this device */
+ return IRQ_NONE;
+}
+/*-------------------------------------------------------------------------*/
+#endif /* DUAL_USB */
+
+static const struct hc_driver ehci_msp_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "PMC MSP EHCI",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ .irq = ehci_msp_irq,
+#else
+ .irq = ehci_irq,
+#endif
+ .flags = HCD_MEMORY | HCD_USB2,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = ehci_msp_setup,
+ .start = ehci_run,
+ .shutdown = ehci_shutdown,
+ .start = ehci_run,
+ .stop = ehci_stop,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ehci_urb_enqueue,
+ .urb_dequeue = ehci_urb_dequeue,
+ .endpoint_disable = ehci_endpoint_disable,
+ .endpoint_reset = ehci_endpoint_reset,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ehci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = ehci_hub_control,
+ .bus_suspend = ehci_bus_suspend,
+ .bus_resume = ehci_bus_resume,
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
+
+ .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+};
+
+static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("In ehci_hcd_msp_drv_probe");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
+#endif
+
+ ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
+
+ return ret;
+}
+
+static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_hcd_msp_remove(hcd, pdev);
+
+ /* free TWI GPIO USB_HOST_DEV pin */
+ gpio_free(MSP_PIN_USB0_HOST_DEV);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_free(MSP_PIN_USB1_HOST_DEV);
+#endif
+
+ return 0;
+}
+
+MODULE_ALIAS("pmcmsp-ehci");
+
+static struct platform_driver ehci_hcd_msp_driver = {
+ .probe = ehci_hcd_msp_drv_probe,
+ .remove = ehci_hcd_msp_drv_remove,
+ .driver = {
+ .name = "pmcmsp-ehci",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &ehci_msp_pmops,
+#endif
+ },
+};
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 799ac16..1b71d6a 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
unsigned amd_l1_fix:1;
unsigned fs_i_thresh:1; /* Intel iso scheduling */
unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
+ unsigned pmc_msp_tdi:1; /* PMC MSP tdi quirk*/
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
@@ -162,6 +163,8 @@ struct ehci_hcd { /* one per controller */
#endif
};
+extern void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci);
+
/* convert between an HCD pointer and the corresponding EHCI_HCD */
static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd)
{
--
1.7.0.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH v3] EHCI bus glue for on-chip PMC MSP USB controller.
2011-01-27 11:28 ` [PATCH v3] EHCI bus glue " Anoop P.A
@ 2011-02-04 19:56 ` Greg KH
2011-02-09 14:12 ` Anoop P A
[not found] ` <4D52AE7E.8000907@parrot.com>
2011-02-15 10:43 ` [PATCH v4] " Anoop P.A
2 siblings, 1 reply; 743+ messages in thread
From: Greg KH @ 2011-02-04 19:56 UTC (permalink / raw)
To: Anoop P.A
Cc: gregkh, dbrownell, ust, pkondeti, stern, gadiyar, alek.du,
jacob.jun.pan, linux-usb, linux-kernel, linux-mips, ralf
On Thu, Jan 27, 2011 at 04:58:56PM +0530, Anoop P.A wrote:
> From: Anoop P A <anoop.pa@gmail.com>
>
> Signed-off-by: Anoop P A <anoop.pa@gmail.com>
> Tested-by: Shane McDonald <mcdonald.shane@gmail.com>
Care to provide a "real" changelog comment for this patch? We need
something here.
> ---
> drivers/usb/host/Kconfig | 15 +-
> drivers/usb/host/ehci-hcd.c | 7 +
> drivers/usb/host/ehci-pmcmsp.c | 552 ++++++++++++++++++++++++++++++++++++++++
> drivers/usb/host/ehci.h | 3 +
> 4 files changed, 575 insertions(+), 2 deletions(-)
> create mode 100644 drivers/usb/host/ehci-pmcmsp.c
>
> diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
> index 24046c0..1f73127 100644
> --- a/drivers/usb/host/Kconfig
> +++ b/drivers/usb/host/Kconfig
> @@ -91,17 +91,28 @@ config USB_EHCI_TT_NEWSCHED
>
> If unsure, say Y.
>
> +config USB_EHCI_HCD_PMC_MSP
> + tristate "EHCI support for on-chip PMC MSP USB controller"
> + depends on USB_EHCI_HCD && MSP_HAS_USB
> + default y
> + select USB_EHCI_BIG_ENDIAN_DESC
> + select USB_EHCI_BIG_ENDIAN_MMIO
> + ---help---
> + Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
> + If unsure, say N.
> +
> config USB_EHCI_BIG_ENDIAN_MMIO
> bool
> depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX || \
> ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
> - PPC_MPC512x || CPU_CAVIUM_OCTEON)
> + PPC_MPC512x || CPU_CAVIUM_OCTEON || \
> + PMC_MSP)
> default y
>
> config USB_EHCI_BIG_ENDIAN_DESC
> bool
> depends on USB_EHCI_HCD && (440EPX || ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
> - PPC_MPC512x)
> + PPC_MPC512x || PMC_MSP)
> default y
>
> config XPS_USB_HCD_XILINX
> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> index 6fee3cd..a591890 100644
> --- a/drivers/usb/host/ehci-hcd.c
> +++ b/drivers/usb/host/ehci-hcd.c
> @@ -262,6 +262,8 @@ static void tdi_reset (struct ehci_hcd *ehci)
> if (ehci_big_endian_mmio(ehci))
> tmp |= USBMODE_BE;
> ehci_writel(ehci, tmp, reg_ptr);
> + if (ehci->pmc_msp_tdi)
> + usb_hcd_tdi_set_mode(ehci);
> }
>
> /* reset a non-running (STS_HALT == 1) controller */
> @@ -1249,6 +1251,11 @@ MODULE_LICENSE ("GPL");
> #define PLATFORM_DRIVER ehci_msm_driver
> #endif
>
> +#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
> +#include "ehci-pmcmsp.c"
> +#define PLATFORM_DRIVER ehci_hcd_msp_driver
> +#endif
> +
> #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
> !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
> !defined(XILINX_OF_PLATFORM_DRIVER)
> diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
> new file mode 100644
> index 0000000..28dd26c
> --- /dev/null
> +++ b/drivers/usb/host/ehci-pmcmsp.c
> @@ -0,0 +1,552 @@
> +/*
> + * PMC MSP EHCI (Host Controller Driver) for USB.
> + *
> + * (C) Copyright 2006-2010 PMC-Sierra Inc
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
Are you sure about "any later version"? Is this acceptable to your
company lawyers?
> + *
> + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
> + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
> + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
> + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
> + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 675 Mass Ave, Cambridge, MA 02139, USA.
These two paragraphs are not needed, please remove them.
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/gpio.h>
> +#include <msp_usb.h>
> +
> +/* includes */
Um, includes for what? Are we writing comments for the previous lines?
> +#define USB_CTRL_MODE_HOST 0x3
> + /* host mode */
> +#define USB_CTRL_MODE_BIG_ENDIAN 0x4
> + /* big endian */
> +#define USB_CTRL_MODE_STREAM_DISABLE 0x10
> + /* stream disable*/
> +#define USB_CTRL_FIFO_THRESH 0x00300000
> + /* thresh hold */
> +#define USB_EHCI_REG_USB_MODE 0x68
> + /* register offset for usb_mode */
> +#define USB_EHCI_REG_USB_FIFO 0x24
> + /* register offset for usb fifo */
> +#define USB_EHCI_REG_USB_STATUS 0x44
> + /* register offset for usb status */
> +#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
> + /* serial/parallel transceiver */
> +#define MSP_PIN_USB0_HOST_DEV 49
> + /* TWI USB0 host device pin */
> +#define MSP_PIN_USB1_HOST_DEV 50
> + /* TWI USB1 host device pin */
Ok, I see we are. That's horrible, please fix it up.
> +
> +extern int usb_disabled(void);
Why is this in a .c file? externs should never be in a .c file.
> +
> +void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
> +{
> + u8 *base;
> + u8 *statreg;
> + u8 *fiforeg;
> + u32 val;
> + struct ehci_regs *reg_base = ehci->regs;
> +
> + /* get register base */
> + base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
> + statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
> + fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
> +
> + /* set the controller to host mode and BIG ENDIAN */
> + ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
> + | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
> +
> + /* clear STS to select parallel transceiver interface */
> + val = ehci_readl(ehci, (u32 *)statreg);
> + val = val & ~USB_EHCI_REG_BIT_STAT_STS;
> + ehci_writel(ehci, val, (u32 *)statreg);
> +
> + /* write to set the proper fifo threshold */
> + ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
> +
> + /* set TWI GPIO USB_HOST_DEV pin high */
> + gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> + gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
> +#endif
Please don't put #defines in .c files.
> +}
> +
> +/* called after powerup, by probe or system-pm "wakeup" */
> +static int ehci_msp_reinit(struct ehci_hcd *ehci)
> +{
> + ehci_port_power(ehci, 0);
> +
> + return 0;
> +}
> +
> +/* called during probe() after chip reset completes */
> +static int ehci_msp_setup(struct usb_hcd *hcd)
> +{
> + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> + u32 temp;
> + int retval;
> + ehci->big_endian_mmio = 1;
> + ehci->big_endian_desc = 1;
> + ehci->pmc_msp_tdi = 1;
> +
> + ehci->caps = hcd->regs;
> + ehci->regs = hcd->regs +
> + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
> + dbg_hcs_params(ehci, "reset");
> + dbg_hcc_params(ehci, "reset");
> +
> + /* cache this readonly data; minimize chip reads */
> + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
> + hcd->has_tt = 1;
> + tdi_reset(ehci);
> +
> + retval = ehci_halt(ehci);
> + if (retval)
> + return retval;
> +
> + ehci_reset(ehci);
> +
> + /* data structure init */
> + retval = ehci_init(hcd);
> + if (retval)
> + return retval;
> +
> + temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
> + temp &= 0x0f;
> + if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
> + ehci_dbg(ehci, "bogus port configuration: "
> + "cc=%d x pcc=%d < ports=%d\n",
> + HCS_N_CC(ehci->hcs_params),
> + HCS_N_PCC(ehci->hcs_params),
> + HCS_N_PORTS(ehci->hcs_params));
> + }
> +
> + retval = ehci_msp_reinit(ehci);
> +
> + return retval;
> +}
> +
> +/*-------------------------------------------------------------------------*/
> +
> +static void msp_start_hc(struct platform_device *dev)
> +{
> + printk(KERN_DEBUG __FILE__
> + ": starting PMC MSP EHCI USB Controller\n");
Why? Who really cares? And, if you _really_ want to do this, please
use a dev_dbg() call instead, which ties it properly into the dynamic
printk system _and_ properly identifies this deivce.
> +
> + /*
> + * Now, carefully enable the USB clock, and take
> + * the USB host controller out of reset.
> + */
> + printk(KERN_DEBUG __FILE__
> + ": Clock to USB host has been enabled\n");
> +}
You never enabled anything, yet you said you did? Somethings wrong
here.
> +
> +static void msp_stop_hc(struct platform_device *dev)
> +{
> + printk(KERN_DEBUG __FILE__
> + ": stopping PMC MSP EHCI USB Controller\n");
> +}
Same for this printk, you didn't stop anything.
Also fix it up and don't use printk, see above.
> +
> +
> +/*-------------------------------------------------------------------------*/
> +
> +/*-------------------------------------------------------------------------*/
> +
> +#ifdef CONFIG_PM
> +
> +/* suspend/resume, section 4.3 */
> +
> +/* These routines rely on the bus glue
> + * to handle powerdown and wakeup, and currently also on
> + * transceivers that don't need any software attention to set up
> + * the right sort of wakeup.
> + * Also they depend on separate root hub suspend/resume.
> + */
> +static int ehci_msp_suspend(struct device *dev)
> +{
> + struct usb_hcd *hcd = dev_get_drvdata(dev);
> + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> + unsigned long flags;
> + int rc;
> +
> + return 0;
> + rc = 0;
> +
> + if (time_before(jiffies, ehci->next_statechange))
> + msleep(10);
Short sleep, why?
> +
> + /* Root hub was already suspended. Disable irq emission and
> + * mark HW unaccessible. The PM and USB cores make sure that
> + * the root hub is either suspended or stopped.
> + */
> + spin_lock_irqsave(&ehci->lock, flags);
> + ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
> + ehci_writel(ehci, 0, &ehci->regs->intr_enable);
> + (void)ehci_readl(ehci, &ehci->regs->intr_enable);
> +
> + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
> + spin_unlock_irqrestore(&ehci->lock, flags);
> +
> + /* could save FLADJ in case of Vaux power loss
> + ... we'd only use it to handle clock skew */
Huh?
> +
> + return rc;
> +}
> +
> +static int ehci_msp_resume(struct device *dev)
> +{
> + struct usb_hcd *hcd = dev_get_drvdata(dev);
> + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> +
> +
> + /* maybe restore FLADJ */
Don't you know?
> +
> + if (time_before(jiffies, ehci->next_statechange))
> + msleep(100);
That's a long sleep, are you sure that's ok on the resume path?
> +
> + /* Mark hardware accessible again as we are out of D3 state by now */
> + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
> +
> + /* If CF is still set, we maintained PCI Vaux power.
> + * Just undo the effect of ehci_pci_suspend().
> + */
> + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
> + int mask = INTR_MASK;
> +
> + ehci_prepare_ports_for_controller_resume(ehci);
> + if (!hcd->self.root_hub->do_remote_wakeup)
> + mask &= ~STS_PCD;
> + ehci_writel(ehci, mask, &ehci->regs->intr_enable);
> + ehci_readl(ehci, &ehci->regs->intr_enable);
> + return 0;
> + }
> +
> + ehci_dbg(ehci, "lost power, restarting\n");
> + usb_root_hub_lost_power(hcd->self.root_hub);
> +
> + /* Else reset, to cope with power loss or flush-to-storage
> + * style "resume" having let BIOS kick in during reboot.
> + */
> + (void) ehci_halt(ehci);
> + (void) ehci_reset(ehci);
> + (void) ehci_msp_reinit(ehci);
> +
> + /* emptying the schedule aborts any urbs */
> + spin_lock_irq(&ehci->lock);
> + if (ehci->reclaim)
> + end_unlink_async(ehci);
> + ehci_work(ehci);
> + spin_unlock_irq(&ehci->lock);
> +
> + ehci_writel(ehci, ehci->command, &ehci->regs->command);
> + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
> + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
> +
> + /* here we "know" root ports should always stay powered */
> + ehci_port_power(ehci, 1);
> +
> + hcd->state = HC_STATE_SUSPENDED;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops ehci_msp_pmops = {
> + .suspend = ehci_msp_suspend,
> + .resume = ehci_msp_resume,
> +};
> +#endif
> +
> +
> +/* configure so an HC device and id are always provided */
> +/* always called with process context; sleeping is OK */
> +
> +static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
> +{
> + struct resource *res;
> + struct platform_device *pdev = &dev->dev;
> + u32 res_len;
> + int retval;
> +
> + /* MAB register space */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + if (res == NULL)
> + return -ENOMEM;
> + res_len = res->end - res->start + 1;
> + if (!request_mem_region(res->start, res_len, "mab regs"))
> + return -EBUSY;
> +
> + dev->mab_regs = ioremap_nocache(res->start, res_len);
> + if (dev->mab_regs == NULL) {
> + retval = -ENOMEM;
> + goto err1;
> + }
> +
> + /* MSP USB register space */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> + if (res == NULL) {
> + retval = -ENOMEM;
> + goto err2;
> + }
> + res_len = res->end - res->start + 1;
> + if (!request_mem_region(res->start, res_len, "usbid regs")) {
> + retval = -EBUSY;
> + goto err2;
> + }
> + dev->usbid_regs = ioremap_nocache(res->start, res_len);
> + if (dev->usbid_regs == NULL) {
> + retval = -ENOMEM;
> + goto err3;
> + }
> +
> + return 0;
> +err3:
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> + res_len = res->end - res->start + 1;
> + release_mem_region(res->start, res_len);
> +err2:
> + iounmap(dev->mab_regs);
> +err1:
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + res_len = res->end - res->start + 1;
> + release_mem_region(res->start, res_len);
> + dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
> + return retval;
> +}
> +
> +/**
> + * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
> + * Context: !in_interrupt()
> + *
> + * Allocates basic resources for this USB host controller, and
> + * then invokes the start() method for the HCD associated with it
> + * through the hotplug entry's driver_data.
> + *
> + */
> +int usb_hcd_msp_probe(const struct hc_driver *driver,
> + struct platform_device *dev)
> +{
> + int retval;
> + struct usb_hcd *hcd;
> + struct resource *res;
> + struct ehci_hcd *ehci ;
> +
> + hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
> + if (!hcd)
> + return -ENOMEM;
> +
> + res = platform_get_resource(dev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + pr_debug("No IOMEM resource info for %s.\n", dev->name);
> + retval = -ENOMEM;
> + goto err1;
> + }
> + hcd->rsrc_start = res->start;
> + hcd->rsrc_len = res->end - res->start + 1;
> + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
> + retval = -EBUSY;
> + goto err1;
> + }
> + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
> + if (!hcd->regs) {
> + pr_debug("ioremap failed");
> + retval = -ENOMEM;
> + goto err2;
> + }
> + msp_start_hc(dev);
> +
> + res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
> + if (res == NULL) {
> + dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
> + retval = -ENOMEM;
> + goto err3;
> + }
> +
> + /* Map non-EHCI register spaces */
> + retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
> + if (retval != 0)
> + goto err3;
> +
> + ehci = hcd_to_ehci(hcd);
> + ehci->big_endian_mmio = 1;
> + ehci->big_endian_desc = 1;
> +
> +
> + retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
> + if (retval == 0)
> + return 0;
> +
> + usb_remove_hcd(hcd);
> +err3:
> + msp_stop_hc(dev);
> + iounmap(hcd->regs);
> +err2:
> + release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
> +err1:
> + usb_put_hcd(hcd);
> +
> + return retval;
> +}
> +
> +
> +/* may be called without controller electrically present */
> +/* may be called with controller, bus, and devices active */
> +
What may be called?
> +/**
> + * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
> + * @dev: USB Host Controller being removed
> + * Context: !in_interrupt()
> + *
> + * Reverses the effect of usb_hcd_msp_probe(), first invoking
> + * the HCD's stop() method. It is always called from a thread
> + * context, normally "rmmod", "apmd", or something similar.
> + *
> + */
> +void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
> +{
> + usb_remove_hcd(hcd);
> + msp_stop_hc(dev);
> + iounmap(hcd->regs);
> + release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
> + usb_put_hcd(hcd);
> +}
> +
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> +/*-------------------------------------------------------------------------*/
> +/*
> + * Wrapper around the main ehci_irq. Since both USB host controllers are
> + * sharing the same IRQ, need to first determine whether we're the intended
> + * recipient of this interrupt.
> + */
> +static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
> +{
> + u32 int_src;
> + struct device *dev = hcd->self.controller;
> + struct platform_device *pdev;
> + struct mspusb_device *mdev;
> + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> +
> + /* need to reverse-map a couple of containers to get our device */
> + pdev = to_platform_device(dev);
> + mdev = to_mspusb_device(pdev);
> +
> + /* Check to see if this interrupt is for this host controller */
> + int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
> + if (int_src & (1 << pdev->id))
> + return ehci_irq(hcd);
> +
> + /* Not for this device */
> + return IRQ_NONE;
> +}
> +/*-------------------------------------------------------------------------*/
> +#endif /* DUAL_USB */
> +
> +static const struct hc_driver ehci_msp_hc_driver = {
> + .description = hcd_name,
> + .product_desc = "PMC MSP EHCI",
> + .hcd_priv_size = sizeof(struct ehci_hcd),
> +
> + /*
> + * generic hardware linkage
> + */
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> + .irq = ehci_msp_irq,
> +#else
> + .irq = ehci_irq,
> +#endif
> + .flags = HCD_MEMORY | HCD_USB2,
> +
> + /*
> + * basic lifecycle operations
> + */
> + .reset = ehci_msp_setup,
> + .start = ehci_run,
> + .shutdown = ehci_shutdown,
> + .start = ehci_run,
> + .stop = ehci_stop,
> +
> + /*
> + * managing i/o requests and associated device resources
> + */
> + .urb_enqueue = ehci_urb_enqueue,
> + .urb_dequeue = ehci_urb_dequeue,
> + .endpoint_disable = ehci_endpoint_disable,
> + .endpoint_reset = ehci_endpoint_reset,
> +
> + /*
> + * scheduling support
> + */
> + .get_frame_number = ehci_get_frame,
> +
> + /*
> + * root hub support
> + */
> + .hub_status_data = ehci_hub_status_data,
> + .hub_control = ehci_hub_control,
> + .bus_suspend = ehci_bus_suspend,
> + .bus_resume = ehci_bus_resume,
> + .relinquish_port = ehci_relinquish_port,
> + .port_handed_over = ehci_port_handed_over,
> +
> + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
> +};
> +
> +static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
> +{
> + int ret;
> +
> + pr_debug("In ehci_hcd_msp_drv_probe");
> +
> + if (usb_disabled())
> + return -ENODEV;
> +
> + gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> + gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
> +#endif
> +
> + ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
> +
> + return ret;
> +}
> +
> +static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
> +{
> + struct usb_hcd *hcd = platform_get_drvdata(pdev);
> +
> + usb_hcd_msp_remove(hcd, pdev);
> +
> + /* free TWI GPIO USB_HOST_DEV pin */
> + gpio_free(MSP_PIN_USB0_HOST_DEV);
> +#ifdef CONFIG_MSP_HAS_DUAL_USB
> + gpio_free(MSP_PIN_USB1_HOST_DEV);
> +#endif
> +
> + return 0;
> +}
> +
> +MODULE_ALIAS("pmcmsp-ehci");
> +
> +static struct platform_driver ehci_hcd_msp_driver = {
> + .probe = ehci_hcd_msp_drv_probe,
> + .remove = ehci_hcd_msp_drv_remove,
> + .driver = {
> + .name = "pmcmsp-ehci",
> + .owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> + .pm = &ehci_msp_pmops,
> +#endif
> + },
> +};
> diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
> index 799ac16..1b71d6a 100644
> --- a/drivers/usb/host/ehci.h
> +++ b/drivers/usb/host/ehci.h
> @@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
> unsigned amd_l1_fix:1;
> unsigned fs_i_thresh:1; /* Intel iso scheduling */
> unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
> + unsigned pmc_msp_tdi:1; /* PMC MSP tdi quirk*/
This part of the patch doesn't apply cleanly anymore, care to refresh it
against linux-next, and make all of the other fixes and resend it?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH v3] EHCI bus glue for on-chip PMC MSP USB controller.
2011-02-04 19:56 ` Greg KH
@ 2011-02-09 14:12 ` Anoop P A
2011-02-09 17:20 ` Greg KH
0 siblings, 1 reply; 743+ messages in thread
From: Anoop P A @ 2011-02-09 14:12 UTC (permalink / raw)
To: Greg KH
Cc: gregkh, dbrownell, ust, pkondeti, stern, gadiyar, alek.du,
jacob.jun.pan, linux-usb, linux-kernel, linux-mips, ralf
On Fri, 2011-02-04 at 11:56 -0800, Greg KH wrote:
> On Thu, Jan 27, 2011 at 04:58:56PM +0530, Anoop P.A wrote:
> > From: Anoop P A <anoop.pa@gmail.com>
> >
> > Signed-off-by: Anoop P A <anoop.pa@gmail.com>
> > Tested-by: Shane McDonald <mcdonald.shane@gmail.com>
>
> Care to provide a "real" changelog comment for this patch? We need
> something here.
Will do.
>
> > ---
> > drivers/usb/host/Kconfig | 15 +-
> > + * PMC MSP EHCI (Host Controller Driver) for USB.
> > + *
> > + * (C) Copyright 2006-2010 PMC-Sierra Inc
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of the GNU General Public License as published by the
> > + * Free Software Foundation; either version 2 of the License, or (at your
> > + * option) any later version.
>
> Are you sure about "any later version"? Is this acceptable to your
> company lawyers?
>
Will fix it.
> > + *
> > + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
> > + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
> > + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
> > + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> > + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> > + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
> > + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
> > + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> > + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> > + *
> > + * You should have received a copy of the GNU General Public License along
> > + * with this program; if not, write to the Free Software Foundation, Inc.,
> > + * 675 Mass Ave, Cambridge, MA 02139, USA.
>
> These two paragraphs are not needed, please remove them.
>
Ok.
> > + */
> > +
> > +#include <linux/platform_device.h>
> > +#include <linux/gpio.h>
> > +#include <msp_usb.h>
> > +
> > +/* includes */
>
> Um, includes for what? Are we writing comments for the previous lines?
>
> > +#define USB_CTRL_MODE_HOST 0x3
> > + /* host mode */
> > +#define USB_CTRL_MODE_BIG_ENDIAN 0x4
> > + /* big endian */
> > +#define USB_CTRL_MODE_STREAM_DISABLE 0x10
> > + /* stream disable*/
> > +#define USB_CTRL_FIFO_THRESH 0x00300000
> > + /* thresh hold */
> > +#define USB_EHCI_REG_USB_MODE 0x68
> > + /* register offset for usb_mode */
> > +#define USB_EHCI_REG_USB_FIFO 0x24
> > + /* register offset for usb fifo */
> > +#define USB_EHCI_REG_USB_STATUS 0x44
> > + /* register offset for usb status */
> > +#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
> > + /* serial/parallel transceiver */
> > +#define MSP_PIN_USB0_HOST_DEV 49
> > + /* TWI USB0 host device pin */
> > +#define MSP_PIN_USB1_HOST_DEV 50
> > + /* TWI USB1 host device pin */
>
> Ok, I see we are. That's horrible, please fix it up.
Ok.
>
> > +
> > +extern int usb_disabled(void);
>
> Why is this in a .c file? externs should never be in a .c file.
>
Ok
>
> > +
> > +void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
> > +{
> > + u8 *base;
> > + u8 *statreg;
> > + u8 *fiforeg;
> > + u32 val;
> > + struct ehci_regs *reg_base = ehci->regs;
> > +
> > + /* get register base */
> > + base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
> > + statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
> > + fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
> > +
> > + /* set the controller to host mode and BIG ENDIAN */
> > + ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
> > + | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
> > +
> > + /* clear STS to select parallel transceiver interface */
> > + val = ehci_readl(ehci, (u32 *)statreg);
> > + val = val & ~USB_EHCI_REG_BIT_STAT_STS;
> > + ehci_writel(ehci, val, (u32 *)statreg);
> > +
> > + /* write to set the proper fifo threshold */
> > + ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
> > +
> > + /* set TWI GPIO USB_HOST_DEV pin high */
> > + gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
> > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > + gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
> > +#endif
>
> Please don't put #defines in .c files.
You mean #ifdef ???
>
> > +}
> > +
> > +/* called after powerup, by probe or system-pm "wakeup" */
> > +static int ehci_msp_reinit(struct ehci_hcd *ehci)
> > +{
> > + ehci_port_power(ehci, 0);
> > +
> > + return 0;
> > +}
> > +
> > +/* called during probe() after chip reset completes */
> > +static int ehci_msp_setup(struct usb_hcd *hcd)
> > +{
> > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > + u32 temp;
> > + int retval;
> > + ehci->big_endian_mmio = 1;
> > + ehci->big_endian_desc = 1;
> > + ehci->pmc_msp_tdi = 1;
> > +
> > + ehci->caps = hcd->regs;
> > + ehci->regs = hcd->regs +
> > + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
> > + dbg_hcs_params(ehci, "reset");
> > + dbg_hcc_params(ehci, "reset");
> > +
> > + /* cache this readonly data; minimize chip reads */
> > + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
> > + hcd->has_tt = 1;
> > + tdi_reset(ehci);
> > +
> > + retval = ehci_halt(ehci);
> > + if (retval)
> > + return retval;
> > +
> > + ehci_reset(ehci);
> > +
> > + /* data structure init */
> > + retval = ehci_init(hcd);
> > + if (retval)
> > + return retval;
> > +
> > + temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
> > + temp &= 0x0f;
> > + if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
> > + ehci_dbg(ehci, "bogus port configuration: "
> > + "cc=%d x pcc=%d < ports=%d\n",
> > + HCS_N_CC(ehci->hcs_params),
> > + HCS_N_PCC(ehci->hcs_params),
> > + HCS_N_PORTS(ehci->hcs_params));
> > + }
> > +
> > + retval = ehci_msp_reinit(ehci);
> > +
> > + return retval;
> > +}
> > +
> > +/*-------------------------------------------------------------------------*/
> > +
> > +static void msp_start_hc(struct platform_device *dev)
> > +{
> > + printk(KERN_DEBUG __FILE__
> > + ": starting PMC MSP EHCI USB Controller\n");
>
> Why? Who really cares? And, if you _really_ want to do this, please
> use a dev_dbg() call instead, which ties it properly into the dynamic
> printk system _and_ properly identifies this deivce.
OK.
>
> > +
> > + /*
> > + * Now, carefully enable the USB clock, and take
> > + * the USB host controller out of reset.
> > + */
> > + printk(KERN_DEBUG __FILE__
> > + ": Clock to USB host has been enabled\n");
> > +}
>
>
> You never enabled anything, yet you said you did? Somethings wrong
> here.
Forget to remove those comments.
>
> > +
> > +static void msp_stop_hc(struct platform_device *dev)
> > +{
> > + printk(KERN_DEBUG __FILE__
> > + ": stopping PMC MSP EHCI USB Controller\n");
> > +}
>
> Same for this printk, you didn't stop anything.
>
> Also fix it up and don't use printk, see above.
Ok.
>
> > +
> > +
> > +/*-------------------------------------------------------------------------*/
> > +
> > +/*-------------------------------------------------------------------------*/
> > +
> > +#ifdef CONFIG_PM
> > +
> > +/* suspend/resume, section 4.3 */
> > +
> > +/* These routines rely on the bus glue
> > + * to handle powerdown and wakeup, and currently also on
> > + * transceivers that don't need any software attention to set up
> > + * the right sort of wakeup.
> > + * Also they depend on separate root hub suspend/resume.
> > + */
> > +static int ehci_msp_suspend(struct device *dev)
> > +{
> > + struct usb_hcd *hcd = dev_get_drvdata(dev);
> > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > + unsigned long flags;
> > + int rc;
> > +
> > + return 0;
> > + rc = 0;
> > +
> > + if (time_before(jiffies, ehci->next_statechange))
> > + msleep(10);
>
> Short sleep, why?
I am not very sure. Person who originally wrote this driver is
unreachable.Any potential issues??
>
> > +
> > + /* Root hub was already suspended. Disable irq emission and
> > + * mark HW unaccessible. The PM and USB cores make sure that
> > + * the root hub is either suspended or stopped.
> > + */
> > + spin_lock_irqsave(&ehci->lock, flags);
> > + ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
> > + ehci_writel(ehci, 0, &ehci->regs->intr_enable);
> > + (void)ehci_readl(ehci, &ehci->regs->intr_enable);
> > +
> > + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
> > + spin_unlock_irqrestore(&ehci->lock, flags);
> > +
> > + /* could save FLADJ in case of Vaux power loss
> > + ... we'd only use it to handle clock skew */
>
> Huh?
Looks like some comments missed while cleaning up
>
> > +
> > + return rc;
> > +}
> > +
> > +static int ehci_msp_resume(struct device *dev)
> > +{
> > + struct usb_hcd *hcd = dev_get_drvdata(dev);
> > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > +
> > +
> > + /* maybe restore FLADJ */
>
> Don't you know?
Not really :)
>
> > +
> > + if (time_before(jiffies, ehci->next_statechange))
> > + msleep(100);
>
> That's a long sleep, are you sure that's ok on the resume path?
>
Again any potential issue you can guess ?
> > +
> > + /* Mark hardware accessible again as we are out of D3 state by now */
> > + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
> > +
> > + /* If CF is still set, we maintained PCI Vaux power.
> > + * Just undo the effect of ehci_pci_suspend().
> > + */
> > + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
> > + int mask = INTR_MASK;
> > +
> > + ehci_prepare_ports_for_controller_resume(ehci);
> > + if (!hcd->self.root_hub->do_remote_wakeup)
> > + mask &= ~STS_PCD;
> > + ehci_writel(ehci, mask, &ehci->regs->intr_enable);
> > + ehci_readl(ehci, &ehci->regs->intr_enable);
> > + return 0;
> > + }
> > +
> > + ehci_dbg(ehci, "lost power, restarting\n");
> > + usb_root_hub_lost_power(hcd->self.root_hub);
> > +
> > + /* Else reset, to cope with power loss or flush-to-storage
> > + * style "resume" having let BIOS kick in during reboot.
> > + */
> > + (void) ehci_halt(ehci);
> > + (void) ehci_reset(ehci);
> > + (void) ehci_msp_reinit(ehci);
> > +
> > + /* emptying the schedule aborts any urbs */
> > + spin_lock_irq(&ehci->lock);
> > + if (ehci->reclaim)
> > + end_unlink_async(ehci);
> > + ehci_work(ehci);
> > + spin_unlock_irq(&ehci->lock);
> > +
> > + ehci_writel(ehci, ehci->command, &ehci->regs->command);
> > + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
> > + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
> > +
> > + /* here we "know" root ports should always stay powered */
> > + ehci_port_power(ehci, 1);
> > +
> > + hcd->state = HC_STATE_SUSPENDED;
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops ehci_msp_pmops = {
> > + .suspend = ehci_msp_suspend,
> > + .resume = ehci_msp_resume,
> > +};
> > +#endif
> > +
> > +
> > +/* configure so an HC device and id are always provided */
> > +/* always called with process context; sleeping is OK */
> > +
> > +static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
> > +{
> > + struct resource *res;
> > + struct platform_device *pdev = &dev->dev;
> > + u32 res_len;
> > + int retval;
> > +
> > + /* MAB register space */
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> > + if (res == NULL)
> > + return -ENOMEM;
> > + res_len = res->end - res->start + 1;
> > + if (!request_mem_region(res->start, res_len, "mab regs"))
> > + return -EBUSY;
> > +
> > + dev->mab_regs = ioremap_nocache(res->start, res_len);
> > + if (dev->mab_regs == NULL) {
> > + retval = -ENOMEM;
> > + goto err1;
> > + }
> > +
> > + /* MSP USB register space */
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> > + if (res == NULL) {
> > + retval = -ENOMEM;
> > + goto err2;
> > + }
> > + res_len = res->end - res->start + 1;
> > + if (!request_mem_region(res->start, res_len, "usbid regs")) {
> > + retval = -EBUSY;
> > + goto err2;
> > + }
> > + dev->usbid_regs = ioremap_nocache(res->start, res_len);
> > + if (dev->usbid_regs == NULL) {
> > + retval = -ENOMEM;
> > + goto err3;
> > + }
> > +
> > + return 0;
> > +err3:
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> > + res_len = res->end - res->start + 1;
> > + release_mem_region(res->start, res_len);
> > +err2:
> > + iounmap(dev->mab_regs);
> > +err1:
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> > + res_len = res->end - res->start + 1;
> > + release_mem_region(res->start, res_len);
> > + dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
> > + return retval;
> > +}
> > +
> > +/**
> > + * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
> > + * Context: !in_interrupt()
> > + *
> > + * Allocates basic resources for this USB host controller, and
> > + * then invokes the start() method for the HCD associated with it
> > + * through the hotplug entry's driver_data.
> > + *
> > + */
> > +int usb_hcd_msp_probe(const struct hc_driver *driver,
> > + struct platform_device *dev)
> > +{
> > + int retval;
> > + struct usb_hcd *hcd;
> > + struct resource *res;
> > + struct ehci_hcd *ehci ;
> > +
> > + hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
> > + if (!hcd)
> > + return -ENOMEM;
> > +
> > + res = platform_get_resource(dev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + pr_debug("No IOMEM resource info for %s.\n", dev->name);
> > + retval = -ENOMEM;
> > + goto err1;
> > + }
> > + hcd->rsrc_start = res->start;
> > + hcd->rsrc_len = res->end - res->start + 1;
> > + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
> > + retval = -EBUSY;
> > + goto err1;
> > + }
> > + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
> > + if (!hcd->regs) {
> > + pr_debug("ioremap failed");
> > + retval = -ENOMEM;
> > + goto err2;
> > + }
> > + msp_start_hc(dev);
> > +
> > + res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
> > + if (res == NULL) {
> > + dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
> > + retval = -ENOMEM;
> > + goto err3;
> > + }
> > +
> > + /* Map non-EHCI register spaces */
> > + retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
> > + if (retval != 0)
> > + goto err3;
> > +
> > + ehci = hcd_to_ehci(hcd);
> > + ehci->big_endian_mmio = 1;
> > + ehci->big_endian_desc = 1;
> > +
> > +
> > + retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
> > + if (retval == 0)
> > + return 0;
> > +
> > + usb_remove_hcd(hcd);
> > +err3:
> > + msp_stop_hc(dev);
> > + iounmap(hcd->regs);
> > +err2:
> > + release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
> > +err1:
> > + usb_put_hcd(hcd);
> > +
> > + return retval;
> > +}
> > +
> > +
> > +/* may be called without controller electrically present */
> > +/* may be called with controller, bus, and devices active */
> > +
>
> What may be called?
may be usb_hcd_msp_remove :)
>
> > +/**
> > + * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
> > + * @dev: USB Host Controller being removed
> > + * Context: !in_interrupt()
> > + *
> > + * Reverses the effect of usb_hcd_msp_probe(), first invoking
> > + * the HCD's stop() method. It is always called from a thread
> > + * context, normally "rmmod", "apmd", or something similar.
> > + *
> > + */
> > +void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
> > +{
> > + usb_remove_hcd(hcd);
> > + msp_stop_hc(dev);
> > + iounmap(hcd->regs);
> > + release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
> > + usb_put_hcd(hcd);
> > +}
> > +
> > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > +/*-------------------------------------------------------------------------*/
> > +/*
> > + * Wrapper around the main ehci_irq. Since both USB host controllers are
> > + * sharing the same IRQ, need to first determine whether we're the intended
> > + * recipient of this interrupt.
> > + */
> > +static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
> > +{
> > + u32 int_src;
> > + struct device *dev = hcd->self.controller;
> > + struct platform_device *pdev;
> > + struct mspusb_device *mdev;
> > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > +
> > + /* need to reverse-map a couple of containers to get our device */
> > + pdev = to_platform_device(dev);
> > + mdev = to_mspusb_device(pdev);
> > +
> > + /* Check to see if this interrupt is for this host controller */
> > + int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
> > + if (int_src & (1 << pdev->id))
> > + return ehci_irq(hcd);
> > +
> > + /* Not for this device */
> > + return IRQ_NONE;
> > +}
> > +/*-------------------------------------------------------------------------*/
> > +#endif /* DUAL_USB */
> > +
> > +static const struct hc_driver ehci_msp_hc_driver = {
> > + .description = hcd_name,
> > + .product_desc = "PMC MSP EHCI",
> > + .hcd_priv_size = sizeof(struct ehci_hcd),
> > +
> > + /*
> > + * generic hardware linkage
> > + */
> > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > + .irq = ehci_msp_irq,
> > +#else
> > + .irq = ehci_irq,
> > +#endif
> > + .flags = HCD_MEMORY | HCD_USB2,
> > +
> > + /*
> > + * basic lifecycle operations
> > + */
> > + .reset = ehci_msp_setup,
> > + .start = ehci_run,
> > + .shutdown = ehci_shutdown,
> > + .start = ehci_run,
> > + .stop = ehci_stop,
> > +
> > + /*
> > + * managing i/o requests and associated device resources
> > + */
> > + .urb_enqueue = ehci_urb_enqueue,
> > + .urb_dequeue = ehci_urb_dequeue,
> > + .endpoint_disable = ehci_endpoint_disable,
> > + .endpoint_reset = ehci_endpoint_reset,
> > +
> > + /*
> > + * scheduling support
> > + */
> > + .get_frame_number = ehci_get_frame,
> > +
> > + /*
> > + * root hub support
> > + */
> > + .hub_status_data = ehci_hub_status_data,
> > + .hub_control = ehci_hub_control,
> > + .bus_suspend = ehci_bus_suspend,
> > + .bus_resume = ehci_bus_resume,
> > + .relinquish_port = ehci_relinquish_port,
> > + .port_handed_over = ehci_port_handed_over,
> > +
> > + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
> > +};
> > +
> > +static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
> > +{
> > + int ret;
> > +
> > + pr_debug("In ehci_hcd_msp_drv_probe");
> > +
> > + if (usb_disabled())
> > + return -ENODEV;
> > +
> > + gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
> > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > + gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
> > +#endif
> > +
> > + ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
> > +
> > + return ret;
> > +}
> > +
> > +static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
> > +{
> > + struct usb_hcd *hcd = platform_get_drvdata(pdev);
> > +
> > + usb_hcd_msp_remove(hcd, pdev);
> > +
> > + /* free TWI GPIO USB_HOST_DEV pin */
> > + gpio_free(MSP_PIN_USB0_HOST_DEV);
> > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > + gpio_free(MSP_PIN_USB1_HOST_DEV);
> > +#endif
> > +
> > + return 0;
> > +}
> > +
> > +MODULE_ALIAS("pmcmsp-ehci");
> > +
> > +static struct platform_driver ehci_hcd_msp_driver = {
> > + .probe = ehci_hcd_msp_drv_probe,
> > + .remove = ehci_hcd_msp_drv_remove,
> > + .driver = {
> > + .name = "pmcmsp-ehci",
> > + .owner = THIS_MODULE,
> > +#ifdef CONFIG_PM
> > + .pm = &ehci_msp_pmops,
> > +#endif
> > + },
> > +};
> > diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
> > index 799ac16..1b71d6a 100644
> > --- a/drivers/usb/host/ehci.h
> > +++ b/drivers/usb/host/ehci.h
> > @@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
> > unsigned amd_l1_fix:1;
> > unsigned fs_i_thresh:1; /* Intel iso scheduling */
> > unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
> > + unsigned pmc_msp_tdi:1; /* PMC MSP tdi quirk*/
>
> This part of the patch doesn't apply cleanly anymore, care to refresh it
> against linux-next, and make all of the other fixes and resend it?
Ok. originally created against linux-mips git head. Will resend the
revised patch once I fix issues you have pointed
>
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH v3] EHCI bus glue for on-chip PMC MSP USB controller.
2011-02-09 14:12 ` Anoop P A
@ 2011-02-09 17:20 ` Greg KH
0 siblings, 0 replies; 743+ messages in thread
From: Greg KH @ 2011-02-09 17:20 UTC (permalink / raw)
To: Anoop P A
Cc: gregkh, dbrownell, ust, pkondeti, stern, gadiyar, alek.du,
jacob.jun.pan, linux-usb, linux-kernel, linux-mips, ralf
On Wed, Feb 09, 2011 at 07:42:33PM +0530, Anoop P A wrote:
> > > +#ifdef CONFIG_MSP_HAS_DUAL_USB
> > > + gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
> > > +#endif
> >
> > Please don't put #defines in .c files.
> You mean #ifdef ???
Yes, sorry.
> > > +static int ehci_msp_suspend(struct device *dev)
> > > +{
> > > + struct usb_hcd *hcd = dev_get_drvdata(dev);
> > > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > > + unsigned long flags;
> > > + int rc;
> > > +
> > > + return 0;
> > > + rc = 0;
> > > +
> > > + if (time_before(jiffies, ehci->next_statechange))
> > > + msleep(10);
> >
> > Short sleep, why?
> I am not very sure. Person who originally wrote this driver is
> unreachable.Any potential issues??
Yes, suspend/resume time delays are not nice for some systems. I would
verify that this really is necessary, and, as you are going to be the one
maintaining and responsible for the code, it would be good for you to
figure out exactly what it is doing, and why.
> > > +static int ehci_msp_resume(struct device *dev)
> > > +{
> > > + struct usb_hcd *hcd = dev_get_drvdata(dev);
> > > + struct ehci_hcd *ehci = hcd_to_ehci(hcd);
> > > +
> > > +
> > > + /* maybe restore FLADJ */
> >
> > Don't you know?
> Not really :)
Heh, you should.
> > > +/* may be called without controller electrically present */
> > > +/* may be called with controller, bus, and devices active */
> > > +
> >
> > What may be called?
> may be usb_hcd_msp_remove :)
Then put it in the comment block for that function, not above it, with
an extra space between it, that just causes confusion.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 743+ messages in thread
[parent not found: <4D52AE7E.8000907@parrot.com>]
* Re: [PATCH v3] EHCI bus glue for on-chip PMC MSP USB controller.
[not found] ` <4D52AE7E.8000907@parrot.com>
@ 2011-02-09 15:44 ` Anoop P A
0 siblings, 0 replies; 743+ messages in thread
From: Anoop P A @ 2011-02-09 15:44 UTC (permalink / raw)
To: Matthieu CASTET
Cc: gregkh, dbrownell, ust, pkondeti, stern, gadiyar, alek.du,
jacob.jun.pan, linux-usb, linux-kernel, linux-mips, ralf
On Wed, 2011-02-09 at 16:10 +0100, Matthieu CASTET wrote:
> Anoop P.A a écrit :
>
> > config XPS_USB_HCD_XILINX
> > diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> > index 6fee3cd..a591890 100644
> > --- a/drivers/usb/host/ehci-hcd.c
> > +++ b/drivers/usb/host/ehci-hcd.c
> > @@ -262,6 +262,8 @@ static void tdi_reset (struct ehci_hcd *ehci)
> > if (ehci_big_endian_mmio(ehci))
> > tmp |= USBMODE_BE;
> > ehci_writel(ehci, tmp, reg_ptr);
> > + if (ehci->pmc_msp_tdi)
> > + usb_hcd_tdi_set_mode(ehci);
> > }
> This is ugly to add callback to your driver here.
> How this will build on other platform, usb_hcd_tdi_set_mode is only
> defined on ehci-pmcmsp.c
I got that will remove it from patch and resend.the patch got carried
from an older kernel :( .
Thanks
>
>
> > +void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
> > +{
> > + u8 *base;
> > + u8 *statreg;
> > + u8 *fiforeg;
> > + u32 val;
> > + struct ehci_regs *reg_base = ehci->regs;
> > +
> > + /* get register base */
> > + base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
> > + statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
> > + fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
> > +
> > + /* set the controller to host mode and BIG ENDIAN */
> > + ehci_writel(ehci, (USB_CTRL_MODE_HOST | USB_CTRL_MODE_BIG_ENDIAN
> > + | USB_CTRL_MODE_STREAM_DISABLE), (u32 *)base);
> > +
> We have done that in tdi_reset, why do you do it again ?
>
>
> Matthieu
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH v4] EHCI bus glue for on-chip PMC MSP USB controller
2011-01-27 11:28 ` [PATCH v3] EHCI bus glue " Anoop P.A
2011-02-04 19:56 ` Greg KH
[not found] ` <4D52AE7E.8000907@parrot.com>
@ 2011-02-15 10:43 ` Anoop P.A
2 siblings, 0 replies; 743+ messages in thread
From: Anoop P.A @ 2011-02-15 10:43 UTC (permalink / raw)
To: gregkh, dbrownell, stern, pkondeti, jacob.jun.pan, linux-usb,
alek.du, linux-kernel, gadiyar, ralf, linux-mips, Greg KH
Cc: anoop.pa
From: Anoop <paanoop1@paanoop1-desktop.(none)>
This patch add bus glue for USB controller commonly found in PMC-Sierra MSP71xx family of SoC's.
Patch includes a tdi reset quirk as well .
Signed-off-by: Anoop P A <anoop.pa@gmail.com>
Tested-by: Shane McDonald <mcdonald.shane@gmail.com>
---
Changes.
ehci-pmcmsp.c is based on latest ehci-pci.c.Addressed some stylistic issue pointed by Greg.
Addressed review comments of Matthieu CASTET.
drivers/usb/host/Kconfig | 15 +-
drivers/usb/host/ehci-hcd.c | 7 +
drivers/usb/host/ehci-pmcmsp.c | 530 ++++++++++++++++++++++++++++++++++++++++
drivers/usb/host/ehci.h | 8 +
4 files changed, 558 insertions(+), 2 deletions(-)
create mode 100644 drivers/usb/host/ehci-pmcmsp.c
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 0e6afa2..1b01c99 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -91,17 +91,28 @@ config USB_EHCI_TT_NEWSCHED
If unsure, say Y.
+config USB_EHCI_HCD_PMC_MSP
+ tristate "EHCI support for on-chip PMC MSP USB controller"
+ depends on USB_EHCI_HCD && MSP_HAS_USB
+ default y
+ select USB_EHCI_BIG_ENDIAN_DESC
+ select USB_EHCI_BIG_ENDIAN_MMIO
+ ---help---
+ Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's.
+ If unsure, say N.
+
config USB_EHCI_BIG_ENDIAN_MMIO
bool
depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX || \
ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x || CPU_CAVIUM_OCTEON)
+ PPC_MPC512x || CPU_CAVIUM_OCTEON || \
+ PMC_MSP)
default y
config USB_EHCI_BIG_ENDIAN_DESC
bool
depends on USB_EHCI_HCD && (440EPX || ARCH_IXP4XX || XPS_USB_HCD_XILINX || \
- PPC_MPC512x)
+ PPC_MPC512x || PMC_MSP)
default y
config XPS_USB_HCD_XILINX
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index cbf451a..913e7df 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -260,6 +260,8 @@ static void tdi_reset (struct ehci_hcd *ehci)
if (ehci_big_endian_mmio(ehci))
tmp |= USBMODE_BE;
ehci_writel(ehci, tmp, reg_ptr);
+ if (ehci->pmc_msp_tdi)
+ usb_hcd_tdi_set_mode(ehci);
}
/* reset a non-running (STS_HALT == 1) controller */
@@ -1250,6 +1252,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ehci_msm_driver
#endif
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+#include "ehci-pmcmsp.c"
+#define PLATFORM_DRIVER ehci_hcd_msp_driver
+#endif
+
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
!defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c
new file mode 100644
index 0000000..dff3d92
--- /dev/null
+++ b/drivers/usb/host/ehci-pmcmsp.c
@@ -0,0 +1,530 @@
+/*
+ * PMC MSP EHCI (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 2006-2010 PMC-Sierra Inc
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ */
+
+/* includes */
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/usb.h>
+#include <msp_usb.h>
+
+/* host mode */
+#define USB_CTRL_MODE_HOST 0x3
+
+/* big endian */
+#define USB_CTRL_MODE_BIG_ENDIAN 0x4
+
+/* stream disable*/
+#define USB_CTRL_MODE_STREAM_DISABLE 0x10
+
+/* thresh hold */
+#define USB_CTRL_FIFO_THRESH 0x00300000
+
+/* register offset for usb_mode */
+#define USB_EHCI_REG_USB_MODE 0x68
+
+/* register offset for usb fifo */
+#define USB_EHCI_REG_USB_FIFO 0x24
+
+/* register offset for usb status */
+#define USB_EHCI_REG_USB_STATUS 0x44
+
+/* serial/parallel transceiver */
+#define USB_EHCI_REG_BIT_STAT_STS (1<<29)
+
+/* TWI USB0 host device pin */
+#define MSP_PIN_USB0_HOST_DEV 49
+
+/* TWI USB1 host device pin */
+#define MSP_PIN_USB1_HOST_DEV 50
+
+
+void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
+{
+ u8 *base;
+ u8 *statreg;
+ u8 *fiforeg;
+ u32 val;
+ struct ehci_regs *reg_base = ehci->regs;
+
+ /* get register base */
+ base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE;
+ statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS;
+ fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO;
+
+ /* Disable controller mode stream */
+ val = ehci_readl(ehci, (u32 *)base);
+ ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE),
+ (u32 *)base);
+
+ /* clear STS to select parallel transceiver interface */
+ val = ehci_readl(ehci, (u32 *)statreg);
+ val = val & ~USB_EHCI_REG_BIT_STAT_STS;
+ ehci_writel(ehci, val, (u32 *)statreg);
+
+ /* write to set the proper fifo threshold */
+ ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg);
+
+ /* set TWI GPIO USB_HOST_DEV pin high */
+ gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_direction_output(MSP_PIN_USB1_HOST_DEV, 1);
+#endif
+}
+
+/* called after powerup, by probe or system-pm "wakeup" */
+static int ehci_msp_reinit(struct ehci_hcd *ehci)
+{
+ ehci_port_power(ehci, 0);
+
+ return 0;
+}
+
+/* called during probe() after chip reset completes */
+static int ehci_msp_setup(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ int retval;
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+ ehci->pmc_msp_tdi = 1;
+
+ ehci->caps = hcd->regs;
+ ehci->regs = hcd->regs +
+ HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ /* cache this readonly data; minimize chip reads */
+ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+ hcd->has_tt = 1;
+ tdi_reset(ehci);
+
+ retval = ehci_halt(ehci);
+ if (retval)
+ return retval;
+
+ ehci_reset(ehci);
+
+ /* data structure init */
+ retval = ehci_init(hcd);
+ if (retval)
+ return retval;
+
+ temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
+ temp &= 0x0f;
+ if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
+ ehci_dbg(ehci, "bogus port configuration: "
+ "cc=%d x pcc=%d < ports=%d\n",
+ HCS_N_CC(ehci->hcs_params),
+ HCS_N_PCC(ehci->hcs_params),
+ HCS_N_PORTS(ehci->hcs_params));
+ }
+
+ retval = ehci_msp_reinit(ehci);
+
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void msp_start_hc(struct platform_device *dev)
+{
+}
+
+static void msp_stop_hc(struct platform_device *dev)
+{
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_PM
+
+/* suspend/resume, section 4.3 */
+
+/* These routines rely on the bus glue
+ * to handle powerdown and wakeup, and currently also on
+ * transceivers that don't need any software attention to set up
+ * the right sort of wakeup.
+ * Also they depend on separate root hub suspend/resume.
+ */
+static int ehci_msp_suspend(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned long flags;
+ int rc;
+
+ return 0;
+ rc = 0;
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible. The PM and USB cores make sure that
+ * the root hub is either suspended or stopped.
+ */
+ spin_lock_irqsave(&ehci->lock, flags);
+ ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ return rc;
+}
+
+static int ehci_msp_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(100);
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ /* If CF is still set, we maintained PCI Vaux power.
+ * Just undo the effect of ehci_pci_suspend().
+ */
+ if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
+ int mask = INTR_MASK;
+
+ ehci_prepare_ports_for_controller_resume(ehci);
+ if (!hcd->self.root_hub->do_remote_wakeup)
+ mask &= ~STS_PCD;
+ ehci_writel(ehci, mask, &ehci->regs->intr_enable);
+ ehci_readl(ehci, &ehci->regs->intr_enable);
+ return 0;
+ }
+
+ ehci_dbg(ehci, "lost power, restarting\n");
+ usb_root_hub_lost_power(hcd->self.root_hub);
+
+ /* Else reset, to cope with power loss or flush-to-storage
+ * style "resume" having let BIOS kick in during reboot.
+ */
+ (void) ehci_halt(ehci);
+ (void) ehci_reset(ehci);
+ (void) ehci_msp_reinit(ehci);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+ ehci_work(ehci);
+ spin_unlock_irq(&ehci->lock);
+
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ hcd->state = HC_STATE_SUSPENDED;
+
+ return 0;
+}
+
+static const struct dev_pm_ops ehci_msp_pmops = {
+ .suspend = ehci_msp_suspend,
+ .resume = ehci_msp_resume,
+};
+#endif
+
+
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+static int usb_hcd_msp_map_regs(struct mspusb_device *dev)
+{
+ struct resource *res;
+ struct platform_device *pdev = &dev->dev;
+ u32 res_len;
+ int retval;
+
+ /* MAB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res == NULL)
+ return -ENOMEM;
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "mab regs"))
+ return -EBUSY;
+
+ dev->mab_regs = ioremap_nocache(res->start, res_len);
+ if (dev->mab_regs == NULL) {
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ /* MSP USB register space */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res == NULL) {
+ retval = -ENOMEM;
+ goto err2;
+ }
+ res_len = res->end - res->start + 1;
+ if (!request_mem_region(res->start, res_len, "usbid regs")) {
+ retval = -EBUSY;
+ goto err2;
+ }
+ dev->usbid_regs = ioremap_nocache(res->start, res_len);
+ if (dev->usbid_regs == NULL) {
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ return 0;
+err3:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+err2:
+ iounmap(dev->mab_regs);
+err1:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_len = res->end - res->start + 1;
+ release_mem_region(res->start, res_len);
+ dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n");
+ return retval;
+}
+
+/**
+ * usb_hcd_msp_probe - initialize PMC MSP-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+int usb_hcd_msp_probe(const struct hc_driver *driver,
+ struct platform_device *dev)
+{
+ int retval;
+ struct usb_hcd *hcd;
+ struct resource *res;
+ struct ehci_hcd *ehci ;
+
+ hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp");
+ if (!hcd)
+ return -ENOMEM;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ pr_debug("No IOMEM resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err1;
+ }
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = res->end - res->start + 1;
+ if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) {
+ retval = -EBUSY;
+ goto err1;
+ }
+ hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
+ if (!hcd->regs) {
+ pr_debug("ioremap failed");
+ retval = -ENOMEM;
+ goto err2;
+ }
+ msp_start_hc(dev);
+
+ res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name);
+ retval = -ENOMEM;
+ goto err3;
+ }
+
+ /* Map non-EHCI register spaces */
+ retval = usb_hcd_msp_map_regs(to_mspusb_device(dev));
+ if (retval != 0)
+ goto err3;
+
+ ehci = hcd_to_ehci(hcd);
+ ehci->big_endian_mmio = 1;
+ ehci->big_endian_desc = 1;
+
+
+ retval = usb_add_hcd(hcd, res->start, IRQF_SHARED);
+ if (retval == 0)
+ return 0;
+
+ usb_remove_hcd(hcd);
+err3:
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+err2:
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+err1:
+ usb_put_hcd(hcd);
+
+ return retval;
+}
+
+
+
+/**
+ * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_msp_probe(), first invoking
+ * the HCD's stop() method. It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+ * may be called without controller electrically present
+ * may be called with controller, bus, and devices active
+ */
+void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev)
+{
+ usb_remove_hcd(hcd);
+ msp_stop_hc(dev);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+}
+
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+/*-------------------------------------------------------------------------*/
+/*
+ * Wrapper around the main ehci_irq. Since both USB host controllers are
+ * sharing the same IRQ, need to first determine whether we're the intended
+ * recipient of this interrupt.
+ */
+static irqreturn_t ehci_msp_irq(struct usb_hcd *hcd)
+{
+ u32 int_src;
+ struct device *dev = hcd->self.controller;
+ struct platform_device *pdev;
+ struct mspusb_device *mdev;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ /* need to reverse-map a couple of containers to get our device */
+ pdev = to_platform_device(dev);
+ mdev = to_mspusb_device(pdev);
+
+ /* Check to see if this interrupt is for this host controller */
+ int_src = ehci_readl(ehci, &mdev->mab_regs->int_stat);
+ if (int_src & (1 << pdev->id))
+ return ehci_irq(hcd);
+
+ /* Not for this device */
+ return IRQ_NONE;
+}
+/*-------------------------------------------------------------------------*/
+#endif /* DUAL_USB */
+
+static const struct hc_driver ehci_msp_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "PMC MSP EHCI",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ .irq = ehci_msp_irq,
+#else
+ .irq = ehci_irq,
+#endif
+ .flags = HCD_MEMORY | HCD_USB2,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = ehci_msp_setup,
+ .start = ehci_run,
+ .shutdown = ehci_shutdown,
+ .start = ehci_run,
+ .stop = ehci_stop,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ehci_urb_enqueue,
+ .urb_dequeue = ehci_urb_dequeue,
+ .endpoint_disable = ehci_endpoint_disable,
+ .endpoint_reset = ehci_endpoint_reset,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ehci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = ehci_hub_control,
+ .bus_suspend = ehci_bus_suspend,
+ .bus_resume = ehci_bus_resume,
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
+
+ .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+};
+
+static int ehci_hcd_msp_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("In ehci_hcd_msp_drv_probe");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO");
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_request(MSP_PIN_USB1_HOST_DEV, "USB1_HOST_DEV_GPIO");
+#endif
+
+ ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev);
+
+ return ret;
+}
+
+static int ehci_hcd_msp_drv_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_hcd_msp_remove(hcd, pdev);
+
+ /* free TWI GPIO USB_HOST_DEV pin */
+ gpio_free(MSP_PIN_USB0_HOST_DEV);
+#ifdef CONFIG_MSP_HAS_DUAL_USB
+ gpio_free(MSP_PIN_USB1_HOST_DEV);
+#endif
+
+ return 0;
+}
+
+MODULE_ALIAS("pmcmsp-ehci");
+
+static struct platform_driver ehci_hcd_msp_driver = {
+ .probe = ehci_hcd_msp_drv_probe,
+ .remove = ehci_hcd_msp_drv_remove,
+ .driver = {
+ .name = "pmcmsp-ehci",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &ehci_msp_pmops,
+#endif
+ },
+};
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index f86d3fa..be739b9 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -134,6 +134,7 @@ struct ehci_hcd { /* one per controller */
unsigned amd_pll_fix:1;
unsigned fs_i_thresh:1; /* Intel iso scheduling */
unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
+ unsigned pmc_msp_tdi:1; /* PMC MSP tdi quirk*/
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
@@ -162,6 +163,13 @@ struct ehci_hcd { /* one per controller */
#endif
};
+#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP
+extern void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci);
+#else
+static inline void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci)
+{ }
+#endif
+
/* convert between an HCD pointer and the corresponding EHCI_HCD */
static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd)
{
--
1.7.0.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH V2 2/2] MSP onchip root hub over current quirk.
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
` (2 preceding siblings ...)
2010-12-22 14:36 ` [PATCH V2 1/2] " Anoop P.A
@ 2010-12-22 14:36 ` Anoop P.A
2010-12-23 2:18 ` Alan Stern
3 siblings, 1 reply; 743+ messages in thread
From: Anoop P.A @ 2010-12-22 14:36 UTC (permalink / raw)
To: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, Alan Stern, linux-mips, linux-kernel, linux-usb,
Sarah Sharp, Oliver Neukum, Hans de Goede, Paul Mortier,
Andiry Xu
Cc: Anoop P A
From: Anoop P A <anoop.pa@gmail.com>
Adding chip specific code under quirk.
Signed-off-by: Anoop P A <anoop.pa@gmail.com>
---
drivers/usb/core/hub.c | 45 ++++++++++++++++++++++++++++++++++++++-----
drivers/usb/core/quirks.c | 3 ++
include/linux/usb/quirks.h | 3 ++
3 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 27115b4..4bff994 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3377,12 +3377,45 @@ static void hub_events(void)
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
- dev_err (hub_dev,
- "over-current change on port %d\n",
- i);
- clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_OVER_CURRENT);
- hub_power_on(hub, true);
+ usb_detect_quirks(hdev);
+ if (hdev->quirks & USB_QUIRK_MSP_OVERCURRENT) {
+ /* clear OCC bit */
+ clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_OVER_CURRENT);
+
+ /* This step is required to toggle the
+ * PP bit to 0 and 1 (by hub_power_on)
+ * in order the CSC bit to be
+ * transitioned properly for device
+ * hotplug
+ */
+ /* clear PP bit */
+ clear_port_feature(hdev, i,
+ USB_PORT_FEAT_POWER);
+
+ /* resume power */
+ hub_power_on(hub, true);
+
+ /* delay 100 usec */
+ udelay(100);
+
+ /* read OCA bit */
+ if (portstatus &
+ (1<<USB_PORT_FEAT_OVER_CURRENT)) {
+ /* declare overcurrent */
+ dev_err(hub_dev,
+ "over-current change \
+ on port %d\n", i);
+ }
+ } else {
+ dev_err(hub_dev,
+ "over-current change \
+ on port %d\n", i);
+ clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_OVER_CURRENT);
+ hub_power_on(hub, true);
+ }
+
}
if (portchange & USB_PORT_STAT_C_RESET) {
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 25719da..59843b9 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -88,6 +88,9 @@ static const struct usb_device_id usb_quirk_list[] = {
/* INTEL VALUE SSD */
{ USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME },
+ /* PMC MSP over current quirk */
+ { USB_DEVICE(0x1d6b, 0x0002), .driver_info = USB_QUIRK_MSP_OVERCURRENT },
+
{ } /* terminating entry must be last */
};
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index 3e93de7..97ab168 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -30,4 +30,7 @@
descriptor */
#define USB_QUIRK_DELAY_INIT 0x00000040
+/*MSP SoC onchip EHCI overcurrent issue */
+#define USB_QUIRK_MSP_OVERCURRENT 0x00000080
+
#endif /* __LINUX_USB_QUIRKS_H */
--
1.7.0.4
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V2 2/2] MSP onchip root hub over current quirk.
2010-12-22 14:36 ` [PATCH V2 2/2] MSP onchip root hub over current quirk Anoop P.A
@ 2010-12-23 2:18 ` Alan Stern
2010-12-23 9:29 ` Anoop P A
0 siblings, 1 reply; 743+ messages in thread
From: Alan Stern @ 2010-12-23 2:18 UTC (permalink / raw)
To: Anoop P.A
Cc: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, linux-mips, linux-kernel, linux-usb, Sarah Sharp,
Oliver Neukum, Hans de Goede, Paul Mortier, Andiry Xu
On Wed, 22 Dec 2010, Anoop P.A wrote:
> From: Anoop P A <anoop.pa@gmail.com>
>
> Adding chip specific code under quirk.
NAK. See below.
> Signed-off-by: Anoop P A <anoop.pa@gmail.com>
> ---
> drivers/usb/core/hub.c | 45 ++++++++++++++++++++++++++++++++++++++-----
> drivers/usb/core/quirks.c | 3 ++
> include/linux/usb/quirks.h | 3 ++
> 3 files changed, 45 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 27115b4..4bff994 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -3377,12 +3377,45 @@ static void hub_events(void)
> }
>
> if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
> - dev_err (hub_dev,
> - "over-current change on port %d\n",
> - i);
> - clear_port_feature(hdev, i,
> - USB_PORT_FEAT_C_OVER_CURRENT);
> - hub_power_on(hub, true);
> + usb_detect_quirks(hdev);
This line is wrong. usb_detect_quirks() gets called only once per
device, when the device is initialized. Besides, you probably want to
use a hub-specific flag for this rather than a device-specific flag.
> + if (hdev->quirks & USB_QUIRK_MSP_OVERCURRENT) {
Also, it would be better to put this code in a separate subroutine
instead of indenting it so far.
> + /* clear OCC bit */
> + clear_port_feature(hdev, i,
> + USB_PORT_FEAT_C_OVER_CURRENT);
> +
> + /* This step is required to toggle the
> + * PP bit to 0 and 1 (by hub_power_on)
> + * in order the CSC bit to be
> + * transitioned properly for device
> + * hotplug
> + */
> + /* clear PP bit */
> + clear_port_feature(hdev, i,
> + USB_PORT_FEAT_POWER);
> +
> + /* resume power */
> + hub_power_on(hub, true);
> +
> + /* delay 100 usec */
> + udelay(100);
> +
> + /* read OCA bit */
> + if (portstatus &
> + (1<<USB_PORT_FEAT_OVER_CURRENT)) {
> + /* declare overcurrent */
> + dev_err(hub_dev,
> + "over-current change \
> + on port %d\n", i);
> + }
> + } else {
> + dev_err(hub_dev,
> + "over-current change \
> + on port %d\n", i);
> + clear_port_feature(hdev, i,
> + USB_PORT_FEAT_C_OVER_CURRENT);
> + hub_power_on(hub, true);
> + }
> +
> }
>
> if (portchange & USB_PORT_STAT_C_RESET) {
> diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
> index 25719da..59843b9 100644
> --- a/drivers/usb/core/quirks.c
> +++ b/drivers/usb/core/quirks.c
> @@ -88,6 +88,9 @@ static const struct usb_device_id usb_quirk_list[] = {
> /* INTEL VALUE SSD */
> { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME },
>
> + /* PMC MSP over current quirk */
> + { USB_DEVICE(0x1d6b, 0x0002), .driver_info = USB_QUIRK_MSP_OVERCURRENT },
> +
This implementation is completely wrong. It applies to all USB-2.0
root hubs in Linux, not just the PMC MSP.
> { } /* terminating entry must be last */
> };
>
> diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
> index 3e93de7..97ab168 100644
> --- a/include/linux/usb/quirks.h
> +++ b/include/linux/usb/quirks.h
> @@ -30,4 +30,7 @@
> descriptor */
> #define USB_QUIRK_DELAY_INIT 0x00000040
>
> +/*MSP SoC onchip EHCI overcurrent issue */
> +#define USB_QUIRK_MSP_OVERCURRENT 0x00000080
> +
> #endif /* __LINUX_USB_QUIRKS_H */
>
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V2 2/2] MSP onchip root hub over current quirk.
2010-12-23 2:18 ` Alan Stern
@ 2010-12-23 9:29 ` Anoop P A
2010-12-23 16:08 ` Alan Stern
0 siblings, 1 reply; 743+ messages in thread
From: Anoop P A @ 2010-12-23 9:29 UTC (permalink / raw)
To: Alan Stern
Cc: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, linux-mips, linux-kernel, linux-usb, Sarah Sharp,
Oliver Neukum, Hans de Goede, Paul Mortier, Andiry Xu
On Wed, 2010-12-22 at 21:18 -0500, Alan Stern wrote:
> On Wed, 22 Dec 2010, Anoop P.A wrote:
>
> > From: Anoop P A <anoop.pa@gmail.com>
> >
> > Adding chip specific code under quirk.
>
> NAK. See below.
>
> > Signed-off-by: Anoop P A <anoop.pa@gmail.com>
> > ---
> > drivers/usb/core/hub.c | 45 ++++++++++++++++++++++++++++++++++++++-----
> > drivers/usb/core/quirks.c | 3 ++
> > include/linux/usb/quirks.h | 3 ++
> > 3 files changed, 45 insertions(+), 6 deletions(-)
> >
> > diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> > index 27115b4..4bff994 100644
> > --- a/drivers/usb/core/hub.c
> > +++ b/drivers/usb/core/hub.c
> > @@ -3377,12 +3377,45 @@ static void hub_events(void)
> > }
> >
> > if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
> > - dev_err (hub_dev,
> > - "over-current change on port %d\n",
> > - i);
> > - clear_port_feature(hdev, i,
> > - USB_PORT_FEAT_C_OVER_CURRENT);
> > - hub_power_on(hub, true);
> > + usb_detect_quirks(hdev);
>
> This line is wrong. usb_detect_quirks() gets called only once per
> device, when the device is initialized. Besides, you probably want to
> use a hub-specific flag for this rather than a device-specific flag.
Can you point me to an example for the recommended way of doing the
hack. I don't have much exposure to USB subsystem.
>
> > + if (hdev->quirks & USB_QUIRK_MSP_OVERCURRENT) {
>
> Also, it would be better to put this code in a separate subroutine
> instead of indenting it so far.
>
> > + /* clear OCC bit */
> > + clear_port_feature(hdev, i,
> > + USB_PORT_FEAT_C_OVER_CURRENT);
> > +
> > + /* This step is required to toggle the
> > + * PP bit to 0 and 1 (by hub_power_on)
> > + * in order the CSC bit to be
> > + * transitioned properly for device
> > + * hotplug
> > + */
> > + /* clear PP bit */
> > + clear_port_feature(hdev, i,
> > + USB_PORT_FEAT_POWER);
> > +
> > + /* resume power */
> > + hub_power_on(hub, true);
> > +
> > + /* delay 100 usec */
> > + udelay(100);
> > +
> > + /* read OCA bit */
> > + if (portstatus &
> > + (1<<USB_PORT_FEAT_OVER_CURRENT)) {
> > + /* declare overcurrent */
> > + dev_err(hub_dev,
> > + "over-current change \
> > + on port %d\n", i);
> > + }
> > + } else {
> > + dev_err(hub_dev,
> > + "over-current change \
> > + on port %d\n", i);
> > + clear_port_feature(hdev, i,
> > + USB_PORT_FEAT_C_OVER_CURRENT);
> > + hub_power_on(hub, true);
> > + }
> > +
> > }
> >
> > if (portchange & USB_PORT_STAT_C_RESET) {
> > diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
> > index 25719da..59843b9 100644
> > --- a/drivers/usb/core/quirks.c
> > +++ b/drivers/usb/core/quirks.c
> > @@ -88,6 +88,9 @@ static const struct usb_device_id usb_quirk_list[] = {
> > /* INTEL VALUE SSD */
> > { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME },
> >
> > + /* PMC MSP over current quirk */
> > + { USB_DEVICE(0x1d6b, 0x0002), .driver_info = USB_QUIRK_MSP_OVERCURRENT },
> > +
>
> This implementation is completely wrong. It applies to all USB-2.0
> root hubs in Linux, not just the PMC MSP.
>
> > { } /* terminating entry must be last */
> > };
> >
> > diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
> > index 3e93de7..97ab168 100644
> > --- a/include/linux/usb/quirks.h
> > +++ b/include/linux/usb/quirks.h
> > @@ -30,4 +30,7 @@
> > descriptor */
> > #define USB_QUIRK_DELAY_INIT 0x00000040
> >
> > +/*MSP SoC onchip EHCI overcurrent issue */
> > +#define USB_QUIRK_MSP_OVERCURRENT 0x00000080
> > +
> > #endif /* __LINUX_USB_QUIRKS_H */
> >
>
Thanks
Anoop
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V2 2/2] MSP onchip root hub over current quirk.
2010-12-23 9:29 ` Anoop P A
@ 2010-12-23 16:08 ` Alan Stern
0 siblings, 0 replies; 743+ messages in thread
From: Alan Stern @ 2010-12-23 16:08 UTC (permalink / raw)
To: Anoop P A
Cc: Ralf Baechle, Greg Kroah-Hartman, Anatolij Gustschin,
Anand Gadiyar, linux-mips, linux-kernel, linux-usb, Sarah Sharp,
Oliver Neukum, Hans de Goede, Paul Mortier, Andiry Xu
On Thu, 23 Dec 2010, Anoop P A wrote:
> > > + usb_detect_quirks(hdev);
> >
> > This line is wrong. usb_detect_quirks() gets called only once per
> > device, when the device is initialized. Besides, you probably want to
> > use a hub-specific flag for this rather than a device-specific flag.
>
> Can you point me to an example for the recommended way of doing the
> hack. I don't have much exposure to USB subsystem.
One example, suitable for PCI devices, can be found in
drivers/usb/host/ehci-pci.c:ehci_pci_setup().
However the best approach would be for you to avoid adding any
special-purpose code at all. Is it possible to handle
overcurrent-change events in a way that will work just as well for
normal hubs as for your MSP root hub?
Alan Stern
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/2] PCIe, AER, use pre-generated prefix in error information printing
[not found] <y>
` (17 preceding siblings ...)
2010-12-21 11:06 ` [PATCH] EHCI support for on-chip PMC MSP USB controller Anoop P
@ 2011-01-31 1:36 ` Huang Ying
2011-01-31 1:36 ` [PATCH 2/2] ACPI, APEI, Add PCIe AER error information printing support Huang Ying
` (141 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Huang Ying @ 2011-01-31 1:36 UTC (permalink / raw)
To: Len Brown
Cc: linux-kernel, Andi Kleen, Tony Luck, ying.huang, linux-acpi,
Jesse Barnes, Zhang Yanmin
When printing PCIe AER error information, each line is prefixed with
PCIe device and driver information. In original implementation, the
prefix is generated when each line is printed. In fact, all lines
share the same prefix. So this patch pre-generated the prefix, and
use that one when each line is printed.
In addition to common prefix can be pre-generated, the trailing white
spaces in string constants and NULLs in char * array constants can be
removed too. These can reduce the object file size further.
The size of object file before and after changing is as follow:
text data bss dec
before: 3038 0 0 3038
after: 2118 0 0 2118
Signed-off-by: Huang Ying <ying.huang@intel.com>
CC: Jesse Barnes <jbarnes@virtuousgeek.org>
CC: Zhang Yanmin <yanmin.zhang@intel.com>
---
drivers/pci/pcie/aer/aerdrv_errprint.c | 123 ++++++++++++---------------------
1 file changed, 48 insertions(+), 75 deletions(-)
--- a/drivers/pci/pcie/aer/aerdrv_errprint.c
+++ b/drivers/pci/pcie/aer/aerdrv_errprint.c
@@ -57,86 +57,44 @@
(e & AER_DATA_LINK_LAYER_ERROR_MASK(t)) ? AER_DATA_LINK_LAYER_ERROR : \
AER_TRANSACTION_LAYER_ERROR)
-#define AER_PR(info, pdev, fmt, args...) \
- printk("%s%s %s: " fmt, (info->severity == AER_CORRECTABLE) ? \
- KERN_WARNING : KERN_ERR, dev_driver_string(&pdev->dev), \
- dev_name(&pdev->dev), ## args)
-
/*
* AER error strings
*/
-static char *aer_error_severity_string[] = {
+static const char * const aer_error_severity_string[] = {
"Uncorrected (Non-Fatal)",
"Uncorrected (Fatal)",
"Corrected"
};
-static char *aer_error_layer[] = {
+static const char * const aer_error_layer[] = {
"Physical Layer",
"Data Link Layer",
"Transaction Layer"
};
-static char *aer_correctable_error_string[] = {
- "Receiver Error ", /* Bit Position 0 */
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- "Bad TLP ", /* Bit Position 6 */
- "Bad DLLP ", /* Bit Position 7 */
- "RELAY_NUM Rollover ", /* Bit Position 8 */
- NULL,
- NULL,
- NULL,
- "Replay Timer Timeout ", /* Bit Position 12 */
- "Advisory Non-Fatal ", /* Bit Position 13 */
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
+
+static const char * const aer_correctable_error_string[] = {
+ "Receiver Error", /* Bit Position 0 */
NULL,
NULL,
NULL,
NULL,
NULL,
+ "Bad TLP", /* Bit Position 6 */
+ "Bad DLLP", /* Bit Position 7 */
+ "RELAY_NUM Rollover", /* Bit Position 8 */
NULL,
NULL,
NULL,
+ "Replay Timer Timeout", /* Bit Position 12 */
+ "Advisory Non-Fatal", /* Bit Position 13 */
};
-static char *aer_uncorrectable_error_string[] = {
- NULL,
- NULL,
- NULL,
- NULL,
- "Data Link Protocol ", /* Bit Position 4 */
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- "Poisoned TLP ", /* Bit Position 12 */
- "Flow Control Protocol ", /* Bit Position 13 */
- "Completion Timeout ", /* Bit Position 14 */
- "Completer Abort ", /* Bit Position 15 */
- "Unexpected Completion ", /* Bit Position 16 */
- "Receiver Overflow ", /* Bit Position 17 */
- "Malformed TLP ", /* Bit Position 18 */
- "ECRC ", /* Bit Position 19 */
- "Unsupported Request ", /* Bit Position 20 */
+static const char * const aer_uncorrectable_error_string[] = {
NULL,
NULL,
NULL,
NULL,
+ "Data Link Protocol", /* Bit Position 4 */
NULL,
NULL,
NULL,
@@ -144,19 +102,29 @@ static char *aer_uncorrectable_error_str
NULL,
NULL,
NULL,
+ "Poisoned TLP", /* Bit Position 12 */
+ "Flow Control Protocol", /* Bit Position 13 */
+ "Completion Timeout", /* Bit Position 14 */
+ "Completer Abort", /* Bit Position 15 */
+ "Unexpected Completion", /* Bit Position 16 */
+ "Receiver Overflow", /* Bit Position 17 */
+ "Malformed TLP", /* Bit Position 18 */
+ "ECRC", /* Bit Position 19 */
+ "Unsupported Request", /* Bit Position 20 */
};
-static char *aer_agent_string[] = {
+static const char * const aer_agent_string[] = {
"Receiver ID",
"Requester ID",
"Completer ID",
"Transmitter ID"
};
-static void __aer_print_error(struct aer_err_info *info, struct pci_dev *dev)
+static void __aer_print_error(const char *prefix,
+ struct aer_err_info *info)
{
int i, status;
- char *errmsg = NULL;
+ const char *errmsg = NULL;
status = (info->status & ~info->mask);
@@ -165,15 +133,17 @@ static void __aer_print_error(struct aer
continue;
if (info->severity == AER_CORRECTABLE)
- errmsg = aer_correctable_error_string[i];
+ errmsg = i < ARRAY_SIZE(aer_correctable_error_string) ?
+ aer_correctable_error_string[i] : NULL;
else
- errmsg = aer_uncorrectable_error_string[i];
+ errmsg = i < ARRAY_SIZE(aer_uncorrectable_error_string) ?
+ aer_uncorrectable_error_string[i] : NULL;
if (errmsg)
- AER_PR(info, dev, " [%2d] %s%s\n", i, errmsg,
+ printk("%s"" [%2d] %-22s%s\n", prefix, i, errmsg,
info->first_error == i ? " (First)" : "");
else
- AER_PR(info, dev, " [%2d] Unknown Error Bit%s\n", i,
+ printk("%s"" [%2d] Unknown Error Bit%s\n", prefix, i,
info->first_error == i ? " (First)" : "");
}
}
@@ -181,11 +151,15 @@ static void __aer_print_error(struct aer
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info)
{
int id = ((dev->bus->number << 8) | dev->devfn);
+ char prefix[44];
+
+ snprintf(prefix, sizeof(prefix), "%s%s %s: ",
+ (info->severity == AER_CORRECTABLE) ? KERN_WARNING : KERN_ERR,
+ dev_driver_string(&dev->dev), dev_name(&dev->dev));
if (info->status == 0) {
- AER_PR(info, dev,
- "PCIe Bus Error: severity=%s, type=Unaccessible, "
- "id=%04x(Unregistered Agent ID)\n",
+ printk("%s""PCIe Bus Error: severity=%s, type=Unaccessible, "
+ "id=%04x(Unregistered Agent ID)\n", prefix,
aer_error_severity_string[info->severity], id);
} else {
int layer, agent;
@@ -193,23 +167,22 @@ void aer_print_error(struct pci_dev *dev
layer = AER_GET_LAYER_ERROR(info->severity, info->status);
agent = AER_GET_AGENT(info->severity, info->status);
- AER_PR(info, dev,
- "PCIe Bus Error: severity=%s, type=%s, id=%04x(%s)\n",
- aer_error_severity_string[info->severity],
+ printk("%s""PCIe Bus Error: severity=%s, type=%s, id=%04x(%s)\n",
+ prefix, aer_error_severity_string[info->severity],
aer_error_layer[layer], id, aer_agent_string[agent]);
- AER_PR(info, dev,
- " device [%04x:%04x] error status/mask=%08x/%08x\n",
- dev->vendor, dev->device, info->status, info->mask);
+ printk("%s"" device [%04x:%04x] error status/mask=%08x/%08x\n",
+ prefix, dev->vendor, dev->device,
+ info->status, info->mask);
- __aer_print_error(info, dev);
+ __aer_print_error(prefix, info);
if (info->tlp_header_valid) {
unsigned char *tlp = (unsigned char *) &info->tlp;
- AER_PR(info, dev, " TLP Header:"
+ printk("%s"" TLP Header:"
" %02x%02x%02x%02x %02x%02x%02x%02x"
" %02x%02x%02x%02x %02x%02x%02x%02x\n",
- *(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp,
+ prefix, *(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp,
*(tlp + 7), *(tlp + 6), *(tlp + 5), *(tlp + 4),
*(tlp + 11), *(tlp + 10), *(tlp + 9),
*(tlp + 8), *(tlp + 15), *(tlp + 14),
@@ -218,8 +191,8 @@ void aer_print_error(struct pci_dev *dev
}
if (info->id && info->error_dev_num > 1 && info->id == id)
- AER_PR(info, dev,
- " Error of this Agent(%04x) is reported first\n", id);
+ printk("%s"" Error of this Agent(%04x) is reported first\n",
+ prefix, id);
}
void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info)
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 2/2] ACPI, APEI, Add PCIe AER error information printing support
[not found] <y>
` (18 preceding siblings ...)
2011-01-31 1:36 ` [PATCH 1/2] PCIe, AER, use pre-generated prefix in error information printing Huang Ying
@ 2011-01-31 1:36 ` Huang Ying
2011-02-21 8:13 ` [PATCH V5] ST SPEAr: PCIE gadget suppport y
` (140 subsequent siblings)
160 siblings, 0 replies; 743+ messages in thread
From: Huang Ying @ 2011-01-31 1:36 UTC (permalink / raw)
To: Len Brown
Cc: linux-kernel, Andi Kleen, Tony Luck, ying.huang, linux-acpi,
Jesse Barnes, Zhang Yanmin
The AER error information printing support is implemented in
drivers/pci/pcie/aer/aer_print.c. So some string constants, functions
and macros definitions can be re-used without being exported.
The original PCIe AER error information printing function is not
re-used directly because the overall format is quite different. And
changing the original printing format may make some original users'
scripts broken.
Signed-off-by: Huang Ying <ying.huang@intel.com>
CC: Jesse Barnes <jbarnes@virtuousgeek.org>
CC: Zhang Yanmin <yanmin.zhang@intel.com>
---
Documentation/acpi/apei/output_format.txt | 25 ++++++++++++
drivers/acpi/apei/Kconfig | 7 +++
drivers/acpi/apei/cper.c | 18 +++++++--
drivers/pci/pcie/aer/aerdrv.h | 9 ----
drivers/pci/pcie/aer/aerdrv_errprint.c | 59 ++++++++++++++++++++++++++++++
include/linux/aer.h | 24 ++++++++++++
include/linux/cper.h | 2 +
7 files changed, 132 insertions(+), 12 deletions(-)
--- a/Documentation/acpi/apei/output_format.txt
+++ b/Documentation/acpi/apei/output_format.txt
@@ -92,6 +92,11 @@ vendor_id: <integer>, device_id: <intege
class_code: <integer>]
[serial number: <integer>, <integer>]
[bridge: secondary_status: <integer>, control: <integer>]
+[aer_status: <integer>, aer_mask: <integer>
+<aer status string>
+[aer_uncor_severity: <integer>]
+aer_layer=<aer layer string>, aer_agent=<aer agent string>
+aer_tlp_header: <integer> <integer> <integer> <integer>]
<pcie port type string>* := PCIe end point | legacy PCI end point | \
unknown | unknown | root port | upstream switch port | \
@@ -99,6 +104,26 @@ downstream switch port | PCIe to PCI/PCI
PCI/PCI-X to PCIe bridge | root complex integrated endpoint device | \
root complex event collector
+if section severity is fatal or recoverable
+<aer status string># :=
+unknown | unknown | unknown | unknown | Data Link Protocol | \
+unknown | unknown | unknown | unknown | unknown | unknown | unknown | \
+Poisoned TLP | Flow Control Protocol | Completion Timeout | \
+Completer Abort | Unexpected Completion | Receiver Overflow | \
+Malformed TLP | ECRC | Unsupported Request
+else
+<aer status string># :=
+Receiver Error | unknown | unknown | unknown | unknown | unknown | \
+Bad TLP | Bad DLLP | RELAY_NUM Rollover | unknown | unknown | unknown | \
+Replay Timer Timeout | Advisory Non-Fatal
+fi
+
+<aer layer string> :=
+Physical Layer | Data Link Layer | Transaction Layer
+
+<aer agent string> :=
+Receiver ID | Requester ID | Completer ID | Transmitter ID
+
Where, [] designate corresponding content is optional
All <field string> description with * has the following format:
--- a/drivers/acpi/apei/Kconfig
+++ b/drivers/acpi/apei/Kconfig
@@ -21,6 +21,13 @@ config ACPI_APEI_GHES
by firmware to produce more valuable hardware error
information for Linux.
+config ACPI_APEI_PCIEAER
+ bool "APEI PCIe AER logging/recovering support"
+ depends on ACPI_APEI && PCIEAER
+ help
+ PCIe AER errors may be reported via APEI firmware first mode.
+ Turn on this option to enable the corresponding support.
+
config ACPI_APEI_EINJ
tristate "APEI Error INJection (EINJ)"
depends on ACPI_APEI && DEBUG_FS
--- a/drivers/acpi/apei/cper.c
+++ b/drivers/acpi/apei/cper.c
@@ -29,6 +29,7 @@
#include <linux/time.h>
#include <linux/cper.h>
#include <linux/acpi.h>
+#include <linux/aer.h>
/*
* CPER record ID need to be unique even after reboot, because record
@@ -70,8 +71,8 @@ static const char *cper_severity_str(uns
* If the output length is longer than 80, multiple line will be
* printed, with @pfx is printed at the beginning of each line.
*/
-static void cper_print_bits(const char *pfx, unsigned int bits,
- const char *strs[], unsigned int strs_size)
+void cper_print_bits(const char *pfx, unsigned int bits,
+ const char *strs[], unsigned int strs_size)
{
int i, len = 0;
const char *str;
@@ -81,6 +82,8 @@ static void cper_print_bits(const char *
if (!(bits & (1U << i)))
continue;
str = strs[i];
+ if (!str)
+ continue;
if (len && len + strlen(str) + 2 > 80) {
printk("%s\n", buf);
len = 0;
@@ -243,7 +246,8 @@ static const char *cper_pcie_port_type_s
"root complex event collector",
};
-static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie)
+static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie,
+ const struct acpi_hest_generic_data *gdata)
{
if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE)
printk("%s""port_type: %d, %s\n", pfx, pcie->port_type,
@@ -276,6 +280,12 @@ static void cper_print_pcie(const char *
printk(
"%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n",
pfx, pcie->bridge.secondary_status, pcie->bridge.control);
+#ifdef CONFIG_ACPI_APEI_PCIEAER
+ if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) {
+ struct aer_capability_regs *aer_regs = (void *)pcie->aer_info;
+ cper_print_aer(pfx, gdata->error_severity, aer_regs);
+ }
+#endif
}
static const char *apei_estatus_section_flag_strs[] = {
@@ -322,7 +332,7 @@ static void apei_estatus_print_section(
struct cper_sec_pcie *pcie = (void *)(gdata + 1);
printk("%s""section_type: PCIe error\n", pfx);
if (gdata->error_data_length >= sizeof(*pcie))
- cper_print_pcie(pfx, pcie);
+ cper_print_pcie(pfx, pcie, gdata);
else
goto err_section_too_small;
} else
--- a/drivers/pci/pcie/aer/aerdrv.h
+++ b/drivers/pci/pcie/aer/aerdrv.h
@@ -35,13 +35,6 @@
PCI_ERR_UNC_UNX_COMP| \
PCI_ERR_UNC_MALF_TLP)
-struct header_log_regs {
- unsigned int dw0;
- unsigned int dw1;
- unsigned int dw2;
- unsigned int dw3;
-};
-
#define AER_MAX_MULTI_ERR_DEVICES 5 /* Not likely to have more */
struct aer_err_info {
struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES];
@@ -59,7 +52,7 @@ struct aer_err_info {
unsigned int status; /* COR/UNCOR Error Status */
unsigned int mask; /* COR/UNCOR Error Mask */
- struct header_log_regs tlp; /* TLP Header */
+ struct aer_header_log_regs tlp; /* TLP Header */
};
struct aer_err_source {
--- a/drivers/pci/pcie/aer/aerdrv_errprint.c
+++ b/drivers/pci/pcie/aer/aerdrv_errprint.c
@@ -19,6 +19,7 @@
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/suspend.h>
+#include <linux/cper.h>
#include "aerdrv.h"
@@ -201,3 +202,61 @@ void aer_print_port_info(struct pci_dev
info->multi_error_valid ? "Multiple " : "",
aer_error_severity_string[info->severity], info->id);
}
+
+#ifdef CONFIG_ACPI_APEI_PCIEAER
+static int cper_severity_to_aer(int cper_severity)
+{
+ switch (cper_severity) {
+ case CPER_SEV_RECOVERABLE:
+ return AER_NONFATAL;
+ case CPER_SEV_FATAL:
+ return AER_FATAL;
+ default:
+ return AER_CORRECTABLE;
+ }
+}
+
+void cper_print_aer(const char *prefix, int cper_severity,
+ struct aer_capability_regs *aer)
+{
+ int aer_severity, layer, agent, status_strs_size, tlp_header_valid = 0;
+ u32 status, mask;
+ const char **status_strs;
+
+ aer_severity = cper_severity_to_aer(cper_severity);
+ if (aer_severity == AER_CORRECTABLE) {
+ status = aer->cor_status;
+ mask = aer->cor_mask;
+ status_strs = aer_correctable_error_string;
+ status_strs_size = ARRAY_SIZE(aer_correctable_error_string);
+ } else {
+ status = aer->uncor_status;
+ mask = aer->uncor_mask;
+ status_strs = aer_uncorrectable_error_string;
+ status_strs_size = ARRAY_SIZE(aer_uncorrectable_error_string);
+ tlp_header_valid = status & AER_LOG_TLP_MASKS;
+ }
+ layer = AER_GET_LAYER_ERROR(aer_severity, status);
+ agent = AER_GET_AGENT(aer_severity, status);
+ printk("%s""aer_status: 0x%08x, aer_mask: 0x%08x\n",
+ prefix, status, mask);
+ cper_print_bits(prefix, status, status_strs, status_strs_size);
+ printk("%s""aer_layer=%s, aer_agent=%s\n", prefix,
+ aer_error_layer[layer], aer_agent_string[agent]);
+ if (aer_severity != AER_CORRECTABLE)
+ printk("%s""aer_uncor_severity: 0x%08x\n",
+ prefix, aer->uncor_severity);
+ if (tlp_header_valid) {
+ const unsigned char *tlp;
+ tlp = (const unsigned char *)&aer->header_log;
+ printk("%s""aer_tlp_header:"
+ " %02x%02x%02x%02x %02x%02x%02x%02x"
+ " %02x%02x%02x%02x %02x%02x%02x%02x\n",
+ prefix, *(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp,
+ *(tlp + 7), *(tlp + 6), *(tlp + 5), *(tlp + 4),
+ *(tlp + 11), *(tlp + 10), *(tlp + 9),
+ *(tlp + 8), *(tlp + 15), *(tlp + 14),
+ *(tlp + 13), *(tlp + 12));
+ }
+}
+#endif
--- a/include/linux/aer.h
+++ b/include/linux/aer.h
@@ -7,6 +7,28 @@
#ifndef _AER_H_
#define _AER_H_
+struct aer_header_log_regs {
+ unsigned int dw0;
+ unsigned int dw1;
+ unsigned int dw2;
+ unsigned int dw3;
+};
+
+struct aer_capability_regs {
+ u32 header;
+ u32 uncor_status;
+ u32 uncor_mask;
+ u32 uncor_severity;
+ u32 cor_status;
+ u32 cor_mask;
+ u32 cap_control;
+ struct aer_header_log_regs header_log;
+ u32 root_command;
+ u32 root_status;
+ u16 cor_err_source;
+ u16 uncor_err_source;
+};
+
#if defined(CONFIG_PCIEAER)
/* pci-e port driver needs this function to enable aer */
extern int pci_enable_pcie_error_reporting(struct pci_dev *dev);
@@ -27,5 +49,7 @@ static inline int pci_cleanup_aer_uncorr
}
#endif
+extern void cper_print_aer(const char *prefix, int cper_severity,
+ struct aer_capability_regs *aer);
#endif //_AER_H_
--- a/include/linux/cper.h
+++ b/include/linux/cper.h
@@ -388,5 +388,7 @@ struct cper_sec_pcie {
#pragma pack()
u64 cper_next_record_id(void);
+void cper_print_bits(const char *prefix, unsigned int bits,
+ const char *strs[], unsigned int strs_size);
#endif
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH V5] ST SPEAr: PCIE gadget suppport
[not found] <y>
` (19 preceding siblings ...)
2011-01-31 1:36 ` [PATCH 2/2] ACPI, APEI, Add PCIe AER error information printing support Huang Ying
@ 2011-02-21 8:13 ` y
2011-02-21 8:17 ` pratyush
2011-03-22 23:27 ` [PATCH] Add secdata to unix_streams Pat Kane
` (139 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: y @ 2011-02-21 8:13 UTC (permalink / raw)
To: akpm, rdunlap
Cc: linux-kernel, linux-pci, linux-arm-kernel, gregkh, jbarnes,
viresh.kumar, shiraz.hashim, Pratyush Anand
From: Pratyush Anand <pratyush.anand@st.com>
This is a configurable gadget. can be configured by configfs interface. Any
IP available at PCIE bus can be programmed to be used by host
controller.It supoorts both INTX and MSI.
By default, gadget is configured for INTX and SYSRAM1 is mapped to BAR0
with size 0x1000
Changes since V4:
- All documentation related comments incorporated
Changes since V3:
- support for multiple instances of such device
- changes to minimzie portability issue on 64 bit machine
- unnecessary typecast removed
- sysfs_streq used in place of complex code
Changes since V2:
- driver has been moved from sysfs to configfs
- Documentation/ABI directory has also been updated
- typo error in documenation has been corrected
- clk value is checked after encapsulating by IS_ERR
Changes since V1:
- __iomem added for register addresses
- kerneldoc comment removed whereever not required.
- help node moved from sysfs to documentation/misc-devices
- strict_strtoul used instead of sscanf
Signed-off-by: Pratyush Anand <pratyush.anand@st.com>
---
.../ABI/testing/configfs-spear-pcie-gadget | 30 +
Documentation/misc-devices/spear-pcie-gadget.txt | 129 +++
drivers/misc/Kconfig | 10 +
drivers/misc/Makefile | 1 +
drivers/misc/spear13xx_pcie_gadget.c | 908 ++++++++++++++++++++
5 files changed, 1078 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/configfs-spear-pcie-gadget
create mode 100644 Documentation/misc-devices/spear-pcie-gadget.txt
create mode 100644 drivers/misc/spear13xx_pcie_gadget.c
diff --git a/Documentation/ABI/testing/configfs-spear-pcie-gadget b/Documentation/ABI/testing/configfs-spear-pcie-gadget
new file mode 100644
index 0000000..29593d0
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-spear-pcie-gadget
@@ -0,0 +1,30 @@
+What: /config/pcie-gadget
+Date: Feb 2011
+KernelVersion: 2.6.37
+Contact: Pratyush Anand <pratyush.anand@st.com>
+Description:
+
+ Interface is used to configure selected dual mode PCIe controller
+ as device and then program its various registers to configure it
+ as a particular device type.
+ This interfaces can be used to show spear's PCIe device capability.
+
+ Nodes are only visible when configfs is mounted. To mount configfs
+ in /config directory use:
+ # mount -t configfs none /config/
+
+ /config/pcie-gadget/
+ link ... used to enable ltssm and read its status.
+ int_type ...used to configure and read type of supported
+ interrupt
+ no_of_msi ... used to configure number of MSI vector needed and
+ to read no of MSI granted.
+ inta ... write 1 to assert INTA and 0 to de-assert.
+ send_msi ... write MSI vector to be sent.
+ vendor_id ... used to write and read vendor id (hex)
+ device_id ... used to write and read device id (hex)
+ bar0_size ... used to write and read bar0_size
+ bar0_address ... used to write and read bar0 mapped area in hex.
+ bar0_rw_offset ... used to write and read offset of bar0 where
+ bar0_data will be written or read.
+ bar0_data ... used to write and read data at bar0_rw_offset.
diff --git a/Documentation/misc-devices/spear-pcie-gadget.txt b/Documentation/misc-devices/spear-pcie-gadget.txt
new file mode 100644
index 0000000..7b86b80
--- /dev/null
+++ b/Documentation/misc-devices/spear-pcie-gadget.txt
@@ -0,0 +1,129 @@
+Spear PCIe Gadget Driver:
+
+Author
+=============
+Pratyush Anand (pratyush.anand@st.com)
+
+Location
+============
+driver/misc/spear13xx_pcie_gadget.c
+
+Supported Chip:
+===================
+SPEAr1300
+SPEAr1310
+
+Menuconfig option:
+==========================
+Device Drivers
+ Misc devices
+ PCIe gadget support for SPEAr13XX platform
+purpose
+===========
+This driver has several nodes which can be read/written by configfs interface.
+Its main purpose is to configure selected dual mode PCIe controller as device
+and then program its various registers to configure it as a particular device
+type. This driver can be used to show spear's PCIe device capability.
+
+Description of different nodes:
+=================================
+
+read behavior of nodes:
+------------------------------
+link :gives ltssm status.
+int_type :type of supported interrupt
+no_of_msi :zero if MSI is not enabled by host. A positive value is the
+ number of MSI vector granted.
+vendor_id :returns programmed vendor id (hex)
+device_id :returns programmed device id(hex)
+bar0_size: :returns size of bar0 in hex.
+bar0_address :returns address of bar0 mapped area in hex.
+bar0_rw_offset :returns offset of bar0 for which bar0_data will return value.
+bar0_data :returns data at bar0_rw_offset.
+
+write behavior of nodes:
+------------------------------
+link :write UP to enable ltsmm DOWN to disable
+int_type :write interrupt type to be configured and (int_type could be
+ INTA, MSI or NO_INT). Select MSI only when you have programmed
+ no_of_msi node.
+no_of_msi :number of MSI vector needed.
+inta :write 1 to assert INTA and 0 to de-assert.
+send_msi :write MSI vector to be sent.
+vendor_id :write vendor id(hex) to be programmed.
+device_id :write device id(hex) to be programmed.
+bar0_size :write size of bar0 in hex. default bar0 size is 1000 (hex)
+ bytes.
+bar0_address :write address of bar0 mapped area in hex. (default mapping of
+ bar0 is SYSRAM1(E0800000). Always program bar size before bar
+ address. Kernel might modify bar size and address for alignment, so
+ read back bar size and address after writing to cross check.
+bar0_rw_offset :write offset of bar0 for which bar0_data will write value.
+bar0_data :write data to be written at bar0_rw_offset.
+
+Node programming example
+===========================
+Program all PCIe registers in such a way that when this device is connected
+to the PCIe host, then host sees this device as 1MB RAM.
+#mount -t configfs none /Config
+# cd /config/pcie_gadget/
+Now you have all the nodes in this directory.
+program vendor id as 0x104a
+# echo 104A >> vendor_id
+
+program device id as 0xCD80
+# echo CD80 >> device_id
+
+program BAR0 size as 1MB
+# echo 100000 >> bar0_size
+
+check for programmed bar0 size
+# cat bar0_size
+
+Program BAR0 Address as DDR (0x2100000). This is the physical address of
+memory, which is to be made visible to PCIe host. Similarly any other peripheral
+can also be made visible to PCIe host. E.g., if you program base address of UART
+as BAR0 address then when this device will be connected to a host, it will be
+visible as UART.
+# echo 2100000 >> bar0_address
+
+program interrupt type : INTA
+# echo INTA >> int_type
+
+go for link up now.
+# echo UP >> link
+
+It will have to be insured that, once link up is done on gadget, then only host
+is initialized and start to search PCIe devices on its port.
+
+/*wait till link is up*/
+# cat link
+wait till it returns UP.
+
+To assert INTA
+# echo 1 >> inta
+
+To de-assert INTA
+# echo 0 >> inta
+
+if MSI is to be used as interrupt, program no of msi vector needed (say4)
+# echo 4 >> no_of_msi
+
+select MSI as interrupt type
+# echo MSI >> int_type
+
+go for link up now
+# echo UP >> link
+
+wait till link is up
+# cat link
+An application can repetitively read this node till link is found UP. It can
+sleep between two read.
+
+wait till msi is enabled
+# cat no_of_msi
+Should return 4 (number of requested MSI vector)
+
+to send msi vector 2
+# echo 2 >> send_msi
+#cd -
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4d073f1..dea052d 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -394,6 +394,16 @@ config DS1682
This driver can also be built as a module. If so, the module
will be called ds1682.
+config SPEAR13XX_PCIE_GADGET
+ bool "PCIe gadget support for SPEAr13XX platform"
+ depends on ARCH_SPEAR13XX
+ default n
+ help
+ This option enables gadget support for PCIe controller. If
+ board file defines any controller as PCIe endpoint then a sysfs
+ entry will be created for that controller. User can use these
+ sysfs node to configure PCIe EP as per his requirements.
+
config TI_DAC7512
tristate "Texas Instruments DAC7512"
depends on SPI && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 98009cc..c489536 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
obj-$(CONFIG_HMC6352) += hmc6352.o
obj-y += eeprom/
obj-y += cb710/
+obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
diff --git a/drivers/misc/spear13xx_pcie_gadget.c b/drivers/misc/spear13xx_pcie_gadget.c
new file mode 100644
index 0000000..ec3b8c9
--- /dev/null
+++ b/drivers/misc/spear13xx_pcie_gadget.c
@@ -0,0 +1,908 @@
+/*
+ * drivers/misc/spear13xx_pcie_gadget.c
+ *
+ * Copyright (C) 2010 ST Microelectronics
+ * Pratyush Anand<pratyush.anand@st.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pci_regs.h>
+#include <linux/configfs.h>
+#include <mach/pcie.h>
+#include <mach/misc_regs.h>
+
+#define IN0_MEM_SIZE (200 * 1024 * 1024 - 1)
+/* In current implementation address translation is done using IN0 only.
+ * So IN1 start address and IN0 end address has been kept same
+*/
+#define IN1_MEM_SIZE (0 * 1024 * 1024 - 1)
+#define IN_IO_SIZE (20 * 1024 * 1024 - 1)
+#define IN_CFG0_SIZE (12 * 1024 * 1024 - 1)
+#define IN_CFG1_SIZE (12 * 1024 * 1024 - 1)
+#define IN_MSG_SIZE (12 * 1024 * 1024 - 1)
+/* Keep default BAR size as 4K*/
+/* AORAM would be mapped by default*/
+#define INBOUND_ADDR_MASK (SPEAR13XX_SYSRAM1_SIZE - 1)
+
+#define INT_TYPE_NO_INT 0
+#define INT_TYPE_INTX 1
+#define INT_TYPE_MSI 2
+struct spear_pcie_gadget_config {
+ void __iomem *base;
+ void __iomem *va_app_base;
+ void __iomem *va_dbi_base;
+ char int_type[10];
+ ulong requested_msi;
+ ulong configured_msi;
+ ulong bar0_size;
+ ulong bar0_rw_offset;
+ void __iomem *va_bar0_address;
+};
+
+struct pcie_gadget_target {
+ struct configfs_subsystem subsys;
+ struct spear_pcie_gadget_config config;
+};
+
+struct pcie_gadget_target_attr {
+ struct configfs_attribute attr;
+ ssize_t (*show)(struct spear_pcie_gadget_config *config,
+ char *buf);
+ ssize_t (*store)(struct spear_pcie_gadget_config *config,
+ const char *buf,
+ size_t count);
+};
+
+static void enable_dbi_access(struct pcie_app_reg __iomem *app_reg)
+{
+ /* Enable DBI access */
+ writel(readl(&app_reg->slv_armisc) | (1 << AXI_OP_DBI_ACCESS_ID),
+ &app_reg->slv_armisc);
+ writel(readl(&app_reg->slv_awmisc) | (1 << AXI_OP_DBI_ACCESS_ID),
+ &app_reg->slv_awmisc);
+
+}
+
+static void disable_dbi_access(struct pcie_app_reg __iomem *app_reg)
+{
+ /* disable DBI access */
+ writel(readl(&app_reg->slv_armisc) & ~(1 << AXI_OP_DBI_ACCESS_ID),
+ &app_reg->slv_armisc);
+ writel(readl(&app_reg->slv_awmisc) & ~(1 << AXI_OP_DBI_ACCESS_ID),
+ &app_reg->slv_awmisc);
+
+}
+
+static void spear_dbi_read_reg(struct spear_pcie_gadget_config *config,
+ int where, int size, u32 *val)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ ulong va_address;
+
+ /* Enable DBI access */
+ enable_dbi_access(app_reg);
+
+ va_address = (ulong)config->va_dbi_base + (where & ~0x3);
+
+ *val = readl(va_address);
+
+ if (size == 1)
+ *val = (*val >> (8 * (where & 3))) & 0xff;
+ else if (size == 2)
+ *val = (*val >> (8 * (where & 3))) & 0xffff;
+
+ /* Disable DBI access */
+ disable_dbi_access(app_reg);
+}
+
+static void spear_dbi_write_reg(struct spear_pcie_gadget_config *config,
+ int where, int size, u32 val)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ ulong va_address;
+
+ /* Enable DBI access */
+ enable_dbi_access(app_reg);
+
+ va_address = (ulong)config->va_dbi_base + (where & ~0x3);
+
+ if (size == 4)
+ writel(val, va_address);
+ else if (size == 2)
+ writew(val, va_address + (where & 2));
+ else if (size == 1)
+ writeb(val, va_address + (where & 3));
+
+ /* Disable DBI access */
+ disable_dbi_access(app_reg);
+}
+
+#define PCI_FIND_CAP_TTL 48
+
+static int pci_find_own_next_cap_ttl(struct spear_pcie_gadget_config *config,
+ u32 pos, int cap, int *ttl)
+{
+ u32 id;
+
+ while ((*ttl)--) {
+ spear_dbi_read_reg(config, pos, 1, &pos);
+ if (pos < 0x40)
+ break;
+ pos &= ~3;
+ spear_dbi_read_reg(config, pos + PCI_CAP_LIST_ID, 1, &id);
+ if (id == 0xff)
+ break;
+ if (id == cap)
+ return pos;
+ pos += PCI_CAP_LIST_NEXT;
+ }
+ return 0;
+}
+
+static int pci_find_own_next_cap(struct spear_pcie_gadget_config *config,
+ u32 pos, int cap)
+{
+ int ttl = PCI_FIND_CAP_TTL;
+
+ return pci_find_own_next_cap_ttl(config, pos, cap, &ttl);
+}
+
+static int pci_find_own_cap_start(struct spear_pcie_gadget_config *config,
+ u8 hdr_type)
+{
+ u32 status;
+
+ spear_dbi_read_reg(config, PCI_STATUS, 2, &status);
+ if (!(status & PCI_STATUS_CAP_LIST))
+ return 0;
+
+ switch (hdr_type) {
+ case PCI_HEADER_TYPE_NORMAL:
+ case PCI_HEADER_TYPE_BRIDGE:
+ return PCI_CAPABILITY_LIST;
+ case PCI_HEADER_TYPE_CARDBUS:
+ return PCI_CB_CAPABILITY_LIST;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Tell if a device supports a given PCI capability.
+ * Returns the address of the requested capability structure within the
+ * device's PCI configuration space or 0 in case the device does not
+ * support it. Possible values for @cap:
+ *
+ * %PCI_CAP_ID_PM Power Management
+ * %PCI_CAP_ID_AGP Accelerated Graphics Port
+ * %PCI_CAP_ID_VPD Vital Product Data
+ * %PCI_CAP_ID_SLOTID Slot Identification
+ * %PCI_CAP_ID_MSI Message Signalled Interrupts
+ * %PCI_CAP_ID_CHSWP CompactPCI HotSwap
+ * %PCI_CAP_ID_PCIX PCI-X
+ * %PCI_CAP_ID_EXP PCI Express
+ */
+static int pci_find_own_capability(struct spear_pcie_gadget_config *config,
+ int cap)
+{
+ u32 pos;
+ u32 hdr_type;
+
+ spear_dbi_read_reg(config, PCI_HEADER_TYPE, 1, &hdr_type);
+
+ pos = pci_find_own_cap_start(config, hdr_type);
+ if (pos)
+ pos = pci_find_own_next_cap(config, pos, cap);
+
+ return pos;
+}
+
+static irqreturn_t spear_pcie_gadget_irq(int irq, void *dev_id)
+{
+ return 0;
+}
+
+/*
+ * configfs interfaces show/store functions
+ */
+static ssize_t pcie_gadget_show_link(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+
+ if (readl(&app_reg->app_status_1) & ((u32)1 << XMLH_LINK_UP_ID))
+ return sprintf(buf, "UP");
+ else
+ return sprintf(buf, "DOWN");
+}
+
+static ssize_t pcie_gadget_store_link(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+
+ if (sysfs_streq(buf, "UP"))
+ writel(readl(&app_reg->app_ctrl_0) | (1 << APP_LTSSM_ENABLE_ID),
+ &app_reg->app_ctrl_0);
+ else if (sysfs_streq(buf, "DOWN"))
+ writel(readl(&app_reg->app_ctrl_0)
+ & ~(1 << APP_LTSSM_ENABLE_ID),
+ &app_reg->app_ctrl_0);
+ else
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t pcie_gadget_show_int_type(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ return sprintf(buf, "%s", config->int_type);
+}
+
+static ssize_t pcie_gadget_store_int_type(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ u32 cap, vec, flags;
+ ulong vector;
+
+ if (sysfs_streq(buf, "INTA"))
+ spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 1);
+
+ else if (sysfs_streq(buf, "MSI")) {
+ vector = config->requested_msi;
+ vec = 0;
+ while (vector > 1) {
+ vector /= 2;
+ vec++;
+ }
+ spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 0);
+ cap = pci_find_own_capability(config, PCI_CAP_ID_MSI);
+ spear_dbi_read_reg(config, cap + PCI_MSI_FLAGS, 1, &flags);
+ flags &= ~PCI_MSI_FLAGS_QMASK;
+ flags |= vec << 1;
+ spear_dbi_write_reg(config, cap + PCI_MSI_FLAGS, 1, flags);
+ } else
+ return -EINVAL;
+
+ strcpy(config->int_type, buf);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_no_of_msi(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ u32 cap, vec, flags;
+ ulong vector;
+
+ if ((readl(&app_reg->msg_status) & (1 << CFG_MSI_EN_ID))
+ != (1 << CFG_MSI_EN_ID))
+ vector = 0;
+ else {
+ cap = pci_find_own_capability(config, PCI_CAP_ID_MSI);
+ spear_dbi_read_reg(config, cap + PCI_MSI_FLAGS, 1, &flags);
+ flags &= ~PCI_MSI_FLAGS_QSIZE;
+ vec = flags >> 4;
+ vector = 1;
+ while (vec--)
+ vector *= 2;
+ }
+ config->configured_msi = vector;
+
+ return sprintf(buf, "%lu", vector);
+}
+
+static ssize_t pcie_gadget_store_no_of_msi(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ if (strict_strtoul(buf, 0, &config->requested_msi))
+ return -EINVAL;
+ if (config->requested_msi > 32)
+ config->requested_msi = 32;
+
+ return count;
+}
+
+static ssize_t pcie_gadget_store_inta(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ ulong en;
+
+ if (strict_strtoul(buf, 0, &en))
+ return -EINVAL;
+
+ if (en)
+ writel(readl(&app_reg->app_ctrl_0) | (1 << SYS_INT_ID),
+ &app_reg->app_ctrl_0);
+ else
+ writel(readl(&app_reg->app_ctrl_0) & ~(1 << SYS_INT_ID),
+ &app_reg->app_ctrl_0);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_store_send_msi(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ ulong vector;
+ u32 ven_msi;
+
+ if (strict_strtoul(buf, 0, &vector))
+ return -EINVAL;
+
+ if (!config->configured_msi)
+ return -EINVAL;
+
+ if (vector >= config->configured_msi)
+ return -EINVAL;
+
+ ven_msi = readl(&app_reg->ven_msi_1);
+ ven_msi &= ~VEN_MSI_FUN_NUM_MASK;
+ ven_msi |= 0 << VEN_MSI_FUN_NUM_ID;
+ ven_msi &= ~VEN_MSI_TC_MASK;
+ ven_msi |= 0 << VEN_MSI_TC_ID;
+ ven_msi &= ~VEN_MSI_VECTOR_MASK;
+ ven_msi |= vector << VEN_MSI_VECTOR_ID;
+
+ /* generating interrupt for msi vector */
+ ven_msi |= VEN_MSI_REQ_EN;
+ writel(ven_msi, &app_reg->ven_msi_1);
+ udelay(1);
+ ven_msi &= ~VEN_MSI_REQ_EN;
+ writel(ven_msi, &app_reg->ven_msi_1);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_vendor_id(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ u32 id;
+
+ spear_dbi_read_reg(config, PCI_VENDOR_ID, 2, &id);
+
+ return sprintf(buf, "%x", id);
+}
+
+static ssize_t pcie_gadget_store_vendor_id(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ ulong id;
+
+ if (strict_strtoul(buf, 0, &id))
+ return -EINVAL;
+
+ spear_dbi_write_reg(config, PCI_VENDOR_ID, 2, id);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_device_id(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ u32 id;
+
+ spear_dbi_read_reg(config, PCI_DEVICE_ID, 2, &id);
+
+ return sprintf(buf, "%x", id);
+}
+
+static ssize_t pcie_gadget_store_device_id(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ ulong id;
+
+ if (strict_strtoul(buf, 0, &id))
+ return -EINVAL;
+
+ spear_dbi_write_reg(config, PCI_DEVICE_ID, 2, id);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_bar0_size(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ return sprintf(buf, "%lx", config->bar0_size);
+}
+
+static ssize_t pcie_gadget_store_bar0_size(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ ulong size;
+ u32 pos, pos1;
+ u32 no_of_bit = 0;
+
+ if (strict_strtoul(buf, 0, &size))
+ return -EINVAL;
+ /* min bar size is 256 */
+ if (size <= 0x100)
+ size = 0x100;
+ /* max bar size is 1MB*/
+ else if (size >= 0x100000)
+ size = 0x100000;
+ else {
+ pos = 0;
+ pos1 = 0;
+ while (pos < 21) {
+ pos = find_next_bit((ulong *)&size, 21, pos);
+ if (pos != 21)
+ pos1 = pos + 1;
+ pos++;
+ no_of_bit++;
+ }
+ if (no_of_bit == 2)
+ pos1--;
+
+ size = 1 << pos1;
+ }
+ config->bar0_size = size;
+ spear_dbi_write_reg(config, PCIE_BAR0_MASK_REG, 4, size - 1);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_bar0_address(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+
+ u32 address = readl(&app_reg->pim0_mem_addr_start);
+
+ return sprintf(buf, "%x", address);
+}
+
+static ssize_t pcie_gadget_store_bar0_address(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+ ulong address;
+
+ if (strict_strtoul(buf, 0, &address))
+ return -EINVAL;
+
+ address &= ~(config->bar0_size - 1);
+ if (config->va_bar0_address)
+ iounmap(config->va_bar0_address);
+ config->va_bar0_address = ioremap(address, config->bar0_size);
+ if (!config->va_bar0_address)
+ return -ENOMEM;
+
+ writel(address, &app_reg->pim0_mem_addr_start);
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_bar0_rw_offset(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ return sprintf(buf, "%lx", config->bar0_rw_offset);
+}
+
+static ssize_t pcie_gadget_store_bar0_rw_offset(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ ulong offset;
+
+ if (strict_strtoul(buf, 0, &offset))
+ return -EINVAL;
+
+ if (offset % 4)
+ return -EINVAL;
+
+ config->bar0_rw_offset = offset;
+
+ return count;
+}
+
+static ssize_t pcie_gadget_show_bar0_data(
+ struct spear_pcie_gadget_config *config,
+ char *buf)
+{
+ ulong data;
+
+ if (!config->va_bar0_address)
+ return -ENOMEM;
+
+ data = readl((ulong)config->va_bar0_address + config->bar0_rw_offset);
+
+ return sprintf(buf, "%lx", data);
+}
+
+static ssize_t pcie_gadget_store_bar0_data(
+ struct spear_pcie_gadget_config *config,
+ const char *buf, size_t count)
+{
+ ulong data;
+
+ if (strict_strtoul(buf, 0, &data))
+ return -EINVAL;
+
+ if (!config->va_bar0_address)
+ return -ENOMEM;
+
+ writel(data, (ulong)config->va_bar0_address + config->bar0_rw_offset);
+
+ return count;
+}
+
+/*
+ * Attribute definitions.
+ */
+
+#define PCIE_GADGET_TARGET_ATTR_RO(_name) \
+static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
+ __CONFIGFS_ATTR(_name, S_IRUGO, pcie_gadget_show_##_name, NULL)
+
+#define PCIE_GADGET_TARGET_ATTR_WO(_name) \
+static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
+ __CONFIGFS_ATTR(_name, S_IWUSR, NULL, pcie_gadget_store_##_name)
+
+#define PCIE_GADGET_TARGET_ATTR_RW(_name) \
+static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
+ __CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, pcie_gadget_show_##_name, \
+ pcie_gadget_store_##_name)
+PCIE_GADGET_TARGET_ATTR_RW(link);
+PCIE_GADGET_TARGET_ATTR_RW(int_type);
+PCIE_GADGET_TARGET_ATTR_RW(no_of_msi);
+PCIE_GADGET_TARGET_ATTR_WO(inta);
+PCIE_GADGET_TARGET_ATTR_WO(send_msi);
+PCIE_GADGET_TARGET_ATTR_RW(vendor_id);
+PCIE_GADGET_TARGET_ATTR_RW(device_id);
+PCIE_GADGET_TARGET_ATTR_RW(bar0_size);
+PCIE_GADGET_TARGET_ATTR_RW(bar0_address);
+PCIE_GADGET_TARGET_ATTR_RW(bar0_rw_offset);
+PCIE_GADGET_TARGET_ATTR_RW(bar0_data);
+
+static struct configfs_attribute *pcie_gadget_target_attrs[] = {
+ &pcie_gadget_target_link.attr,
+ &pcie_gadget_target_int_type.attr,
+ &pcie_gadget_target_no_of_msi.attr,
+ &pcie_gadget_target_inta.attr,
+ &pcie_gadget_target_send_msi.attr,
+ &pcie_gadget_target_vendor_id.attr,
+ &pcie_gadget_target_device_id.attr,
+ &pcie_gadget_target_bar0_size.attr,
+ &pcie_gadget_target_bar0_address.attr,
+ &pcie_gadget_target_bar0_rw_offset.attr,
+ &pcie_gadget_target_bar0_data.attr,
+ NULL,
+};
+
+static struct pcie_gadget_target *to_target(struct config_item *item)
+{
+ return item ?
+ container_of(to_configfs_subsystem(to_config_group(item)),
+ struct pcie_gadget_target, subsys) : NULL;
+}
+
+/*
+ * Item operations and type for pcie_gadget_target.
+ */
+
+static ssize_t pcie_gadget_target_attr_show(struct config_item *item,
+ struct configfs_attribute *attr,
+ char *buf)
+{
+ ssize_t ret = -EINVAL;
+ struct pcie_gadget_target *target = to_target(item);
+ struct pcie_gadget_target_attr *t_attr =
+ container_of(attr, struct pcie_gadget_target_attr, attr);
+
+ if (t_attr->show)
+ ret = t_attr->show(&target->config, buf);
+ return ret;
+}
+
+static ssize_t pcie_gadget_target_attr_store(struct config_item *item,
+ struct configfs_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ ssize_t ret = -EINVAL;
+ struct pcie_gadget_target *target = to_target(item);
+ struct pcie_gadget_target_attr *t_attr =
+ container_of(attr, struct pcie_gadget_target_attr, attr);
+
+ if (t_attr->store)
+ ret = t_attr->store(&target->config, buf, count);
+ return ret;
+}
+
+static struct configfs_item_operations pcie_gadget_target_item_ops = {
+ .show_attribute = pcie_gadget_target_attr_show,
+ .store_attribute = pcie_gadget_target_attr_store,
+};
+
+static struct config_item_type pcie_gadget_target_type = {
+ .ct_attrs = pcie_gadget_target_attrs,
+ .ct_item_ops = &pcie_gadget_target_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static void spear13xx_pcie_device_init(struct spear_pcie_gadget_config *config)
+{
+ struct pcie_app_reg __iomem *app_reg = config->va_app_base;
+
+ /*setup registers for outbound translation */
+
+ writel(config->base, &app_reg->in0_mem_addr_start);
+ writel(app_reg->in0_mem_addr_start + IN0_MEM_SIZE,
+ &app_reg->in0_mem_addr_limit);
+ writel(app_reg->in0_mem_addr_limit + 1, &app_reg->in1_mem_addr_start);
+ writel(app_reg->in1_mem_addr_start + IN1_MEM_SIZE,
+ &app_reg->in1_mem_addr_limit);
+ writel(app_reg->in1_mem_addr_limit + 1, &app_reg->in_io_addr_start);
+ writel(app_reg->in_io_addr_start + IN_IO_SIZE,
+ &app_reg->in_io_addr_limit);
+ writel(app_reg->in_io_addr_limit + 1, &app_reg->in_cfg0_addr_start);
+ writel(app_reg->in_cfg0_addr_start + IN_CFG0_SIZE,
+ &app_reg->in_cfg0_addr_limit);
+ writel(app_reg->in_cfg0_addr_limit + 1, &app_reg->in_cfg1_addr_start);
+ writel(app_reg->in_cfg1_addr_start + IN_CFG1_SIZE,
+ &app_reg->in_cfg1_addr_limit);
+ writel(app_reg->in_cfg1_addr_limit + 1, &app_reg->in_msg_addr_start);
+ writel(app_reg->in_msg_addr_start + IN_MSG_SIZE,
+ &app_reg->in_msg_addr_limit);
+
+ writel(app_reg->in0_mem_addr_start, &app_reg->pom0_mem_addr_start);
+ writel(app_reg->in1_mem_addr_start, &app_reg->pom1_mem_addr_start);
+ writel(app_reg->in_io_addr_start, &app_reg->pom_io_addr_start);
+
+ /*setup registers for inbound translation */
+
+ /* Keep AORAM mapped at BAR0 as default */
+ config->bar0_size = INBOUND_ADDR_MASK + 1;
+ spear_dbi_write_reg(config, PCIE_BAR0_MASK_REG, 4, INBOUND_ADDR_MASK);
+ spear_dbi_write_reg(config, PCI_BASE_ADDRESS_0, 4, 0xC);
+ config->va_bar0_address = ioremap(SPEAR13XX_SYSRAM1_BASE,
+ config->bar0_size);
+
+ writel(SPEAR13XX_SYSRAM1_BASE, &app_reg->pim0_mem_addr_start);
+ writel(0, &app_reg->pim1_mem_addr_start);
+ writel(INBOUND_ADDR_MASK + 1, &app_reg->mem0_addr_offset_limit);
+
+ writel(0x0, &app_reg->pim_io_addr_start);
+ writel(0x0, &app_reg->pim_io_addr_start);
+ writel(0x0, &app_reg->pim_rom_addr_start);
+
+ writel(DEVICE_TYPE_EP | (1 << MISCTRL_EN_ID)
+ | ((u32)1 << REG_TRANSLATION_ENABLE),
+ &app_reg->app_ctrl_0);
+ /* disable all rx interrupts */
+ writel(0, &app_reg->int_mask);
+
+ /* Select INTA as default*/
+ spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 1);
+}
+
+static int __devinit spear_pcie_gadget_probe(struct platform_device *pdev)
+{
+ struct resource *res0, *res1;
+ unsigned int status = 0;
+ int irq;
+ struct clk *clk;
+ static struct pcie_gadget_target *target;
+ struct spear_pcie_gadget_config *config;
+ struct config_item *cg_item;
+ struct configfs_subsystem *subsys;
+
+ /* get resource for application registers*/
+
+ res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res0) {
+ dev_err(&pdev->dev, "no resource defined\n");
+ return -EBUSY;
+ }
+ if (!request_mem_region(res0->start, resource_size(res0),
+ pdev->name)) {
+ dev_err(&pdev->dev, "pcie gadget region already claimed\n");
+ return -EBUSY;
+ }
+ /* get resource for dbi registers*/
+
+ res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res1) {
+ dev_err(&pdev->dev, "no resource defined\n");
+ goto err_rel_res0;
+ }
+ if (!request_mem_region(res1->start, resource_size(res1),
+ pdev->name)) {
+ dev_err(&pdev->dev, "pcie gadget region already claimed\n");
+ goto err_rel_res0;
+ }
+
+ target = kzalloc(sizeof(*target), GFP_KERNEL);
+ if (!target) {
+ dev_err(&pdev->dev, "out of memory\n");
+ status = -ENOMEM;
+ goto err_rel_res;
+ }
+
+ cg_item = &target->subsys.su_group.cg_item;
+ sprintf(cg_item->ci_namebuf, "pcie_gadget.%d", pdev->id);
+ cg_item->ci_type = &pcie_gadget_target_type;
+ config = &target->config;
+ config->va_app_base = (void __iomem *)ioremap(res0->start,
+ resource_size(res0));
+ if (!config->va_app_base) {
+ dev_err(&pdev->dev, "ioremap fail\n");
+ status = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ config->base = (void __iomem *)res1->start;
+
+ config->va_dbi_base = (void __iomem *)ioremap(res1->start,
+ resource_size(res1));
+ if (!config->va_dbi_base) {
+ dev_err(&pdev->dev, "ioremap fail\n");
+ status = -ENOMEM;
+ goto err_iounmap_app;
+ }
+
+ dev_set_drvdata(&pdev->dev, target);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no update irq?\n");
+ status = irq;
+ goto err_iounmap;
+ }
+
+ status = request_irq(irq, spear_pcie_gadget_irq, 0, pdev->name, NULL);
+ if (status) {
+ dev_err(&pdev->dev, "pcie gadget interrupt IRQ%d already \
+ claimed\n", irq);
+ goto err_iounmap;
+ }
+
+ /* Register configfs hooks */
+ subsys = &target->subsys;
+ config_group_init(&subsys->su_group);
+ mutex_init(&subsys->su_mutex);
+ status = configfs_register_subsystem(subsys);
+ if (status)
+ goto err_irq;
+
+ /*
+ * init basic pcie application registers
+ * do not enable clock if it is PCIE0.Ideally , all controller should
+ * have been independent from others with respect to clock. But PCIE1
+ * and 2 depends on PCIE0.So PCIE0 clk is provided during board init.
+ */
+ if (pdev->id == 1) {
+ /*
+ * Ideally CFG Clock should have been also enabled here. But
+ * it is done currently during board init routne
+ */
+ clk = clk_get_sys("pcie1", NULL);
+ if (IS_ERR(clk)) {
+ pr_err("%s:couldn't get clk for pcie1\n", __func__);
+ goto err_irq;
+ }
+ if (clk_enable(clk)) {
+ pr_err("%s:couldn't enable clk for pcie1\n", __func__);
+ goto err_irq;
+ }
+ } else if (pdev->id == 2) {
+ /*
+ * Ideally CFG Clock should have been also enabled here. But
+ * it is done currently during board init routne
+ */
+ clk = clk_get_sys("pcie2", NULL);
+ if (IS_ERR(clk)) {
+ pr_err("%s:couldn't get clk for pcie2\n", __func__);
+ goto err_irq;
+ }
+ if (clk_enable(clk)) {
+ pr_err("%s:couldn't enable clk for pcie2\n", __func__);
+ goto err_irq;
+ }
+ }
+ spear13xx_pcie_device_init(config);
+
+ return 0;
+err_irq:
+ free_irq(irq, NULL);
+err_iounmap:
+ iounmap(config->va_dbi_base);
+err_iounmap_app:
+ iounmap(config->va_app_base);
+err_kzalloc:
+ kfree(config);
+err_rel_res:
+ release_mem_region(res1->start, resource_size(res1));
+err_rel_res0:
+ release_mem_region(res0->start, resource_size(res0));
+ return status;
+}
+
+static int __devexit spear_pcie_gadget_remove(struct platform_device *pdev)
+{
+ struct resource *res0, *res1;
+ static struct pcie_gadget_target *target;
+ struct spear_pcie_gadget_config *config;
+ int irq;
+
+ res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ irq = platform_get_irq(pdev, 0);
+ target = dev_get_drvdata(&pdev->dev);
+ config = &target->config;
+
+ free_irq(irq, NULL);
+ iounmap(config->va_dbi_base);
+ iounmap(config->va_app_base);
+ release_mem_region(res1->start, resource_size(res1));
+ release_mem_region(res0->start, resource_size(res0));
+ configfs_unregister_subsystem(&target->subsys);
+ kfree(target);
+
+ return 0;
+}
+
+static void spear_pcie_gadget_shutdown(struct platform_device *pdev)
+{
+}
+
+static struct platform_driver spear_pcie_gadget_driver = {
+ .probe = spear_pcie_gadget_probe,
+ .remove = spear_pcie_gadget_remove,
+ .shutdown = spear_pcie_gadget_shutdown,
+ .driver = {
+ .name = "pcie-gadget-spear",
+ .bus = &platform_bus_type
+ },
+};
+
+static int __init spear_pcie_gadget_init(void)
+{
+ return platform_driver_register(&spear_pcie_gadget_driver);
+}
+module_init(spear_pcie_gadget_init);
+
+static void __exit spear_pcie_gadget_exit(void)
+{
+ platform_driver_unregister(&spear_pcie_gadget_driver);
+}
+module_exit(spear_pcie_gadget_exit);
+
+MODULE_ALIAS("pcie-gadget-spear");
+MODULE_AUTHOR("Pratyush Anand");
+MODULE_LICENSE("GPL");
--
1.6.0.2
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH V5] ST SPEAr: PCIE gadget suppport
2011-02-21 8:13 ` [PATCH V5] ST SPEAr: PCIE gadget suppport y
@ 2011-02-21 8:17 ` pratyush
0 siblings, 0 replies; 743+ messages in thread
From: pratyush @ 2011-02-21 8:17 UTC (permalink / raw)
Cc: akpm, rdunlap, linux-kernel, linux-pci, linux-arm-kernel, gregkh,
jbarnes, Viresh KUMAR, Shiraz HASHIM
Sorry, Please discard this patch.
Regards
Pratyush
On 2/21/2011 1:43 PM, y wrote:
> From: Pratyush Anand <pratyush.anand@st.com>
>
> This is a configurable gadget. can be configured by configfs interface. Any
> IP available at PCIE bus can be programmed to be used by host
> controller.It supoorts both INTX and MSI.
> By default, gadget is configured for INTX and SYSRAM1 is mapped to BAR0
> with size 0x1000
>
> Changes since V4:
> - All documentation related comments incorporated
>
> Changes since V3:
> - support for multiple instances of such device
> - changes to minimzie portability issue on 64 bit machine
> - unnecessary typecast removed
> - sysfs_streq used in place of complex code
>
> Changes since V2:
> - driver has been moved from sysfs to configfs
> - Documentation/ABI directory has also been updated
> - typo error in documenation has been corrected
> - clk value is checked after encapsulating by IS_ERR
>
> Changes since V1:
> - __iomem added for register addresses
> - kerneldoc comment removed whereever not required.
> - help node moved from sysfs to documentation/misc-devices
> - strict_strtoul used instead of sscanf
>
> Signed-off-by: Pratyush Anand <pratyush.anand@st.com>
> ---
> .../ABI/testing/configfs-spear-pcie-gadget | 30 +
> Documentation/misc-devices/spear-pcie-gadget.txt | 129 +++
> drivers/misc/Kconfig | 10 +
> drivers/misc/Makefile | 1 +
> drivers/misc/spear13xx_pcie_gadget.c | 908 ++++++++++++++++++++
> 5 files changed, 1078 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/ABI/testing/configfs-spear-pcie-gadget
> create mode 100644 Documentation/misc-devices/spear-pcie-gadget.txt
> create mode 100644 drivers/misc/spear13xx_pcie_gadget.c
>
> diff --git a/Documentation/ABI/testing/configfs-spear-pcie-gadget b/Documentation/ABI/testing/configfs-spear-pcie-gadget
> new file mode 100644
> index 0000000..29593d0
> --- /dev/null
> +++ b/Documentation/ABI/testing/configfs-spear-pcie-gadget
> @@ -0,0 +1,30 @@
> +What: /config/pcie-gadget
> +Date: Feb 2011
> +KernelVersion: 2.6.37
> +Contact: Pratyush Anand <pratyush.anand@st.com>
> +Description:
> +
> + Interface is used to configure selected dual mode PCIe controller
> + as device and then program its various registers to configure it
> + as a particular device type.
> + This interfaces can be used to show spear's PCIe device capability.
> +
> + Nodes are only visible when configfs is mounted. To mount configfs
> + in /config directory use:
> + # mount -t configfs none /config/
> +
> + /config/pcie-gadget/
> + link ... used to enable ltssm and read its status.
> + int_type ...used to configure and read type of supported
> + interrupt
> + no_of_msi ... used to configure number of MSI vector needed and
> + to read no of MSI granted.
> + inta ... write 1 to assert INTA and 0 to de-assert.
> + send_msi ... write MSI vector to be sent.
> + vendor_id ... used to write and read vendor id (hex)
> + device_id ... used to write and read device id (hex)
> + bar0_size ... used to write and read bar0_size
> + bar0_address ... used to write and read bar0 mapped area in hex.
> + bar0_rw_offset ... used to write and read offset of bar0 where
> + bar0_data will be written or read.
> + bar0_data ... used to write and read data at bar0_rw_offset.
> diff --git a/Documentation/misc-devices/spear-pcie-gadget.txt b/Documentation/misc-devices/spear-pcie-gadget.txt
> new file mode 100644
> index 0000000..7b86b80
> --- /dev/null
> +++ b/Documentation/misc-devices/spear-pcie-gadget.txt
> @@ -0,0 +1,129 @@
> +Spear PCIe Gadget Driver:
> +
> +Author
> +=============
> +Pratyush Anand (pratyush.anand@st.com)
> +
> +Location
> +============
> +driver/misc/spear13xx_pcie_gadget.c
> +
> +Supported Chip:
> +===================
> +SPEAr1300
> +SPEAr1310
> +
> +Menuconfig option:
> +==========================
> +Device Drivers
> + Misc devices
> + PCIe gadget support for SPEAr13XX platform
> +purpose
> +===========
> +This driver has several nodes which can be read/written by configfs interface.
> +Its main purpose is to configure selected dual mode PCIe controller as device
> +and then program its various registers to configure it as a particular device
> +type. This driver can be used to show spear's PCIe device capability.
> +
> +Description of different nodes:
> +=================================
> +
> +read behavior of nodes:
> +------------------------------
> +link :gives ltssm status.
> +int_type :type of supported interrupt
> +no_of_msi :zero if MSI is not enabled by host. A positive value is the
> + number of MSI vector granted.
> +vendor_id :returns programmed vendor id (hex)
> +device_id :returns programmed device id(hex)
> +bar0_size: :returns size of bar0 in hex.
> +bar0_address :returns address of bar0 mapped area in hex.
> +bar0_rw_offset :returns offset of bar0 for which bar0_data will return value.
> +bar0_data :returns data at bar0_rw_offset.
> +
> +write behavior of nodes:
> +------------------------------
> +link :write UP to enable ltsmm DOWN to disable
> +int_type :write interrupt type to be configured and (int_type could be
> + INTA, MSI or NO_INT). Select MSI only when you have programmed
> + no_of_msi node.
> +no_of_msi :number of MSI vector needed.
> +inta :write 1 to assert INTA and 0 to de-assert.
> +send_msi :write MSI vector to be sent.
> +vendor_id :write vendor id(hex) to be programmed.
> +device_id :write device id(hex) to be programmed.
> +bar0_size :write size of bar0 in hex. default bar0 size is 1000 (hex)
> + bytes.
> +bar0_address :write address of bar0 mapped area in hex. (default mapping of
> + bar0 is SYSRAM1(E0800000). Always program bar size before bar
> + address. Kernel might modify bar size and address for alignment, so
> + read back bar size and address after writing to cross check.
> +bar0_rw_offset :write offset of bar0 for which bar0_data will write value.
> +bar0_data :write data to be written at bar0_rw_offset.
> +
> +Node programming example
> +===========================
> +Program all PCIe registers in such a way that when this device is connected
> +to the PCIe host, then host sees this device as 1MB RAM.
> +#mount -t configfs none /Config
> +# cd /config/pcie_gadget/
> +Now you have all the nodes in this directory.
> +program vendor id as 0x104a
> +# echo 104A >> vendor_id
> +
> +program device id as 0xCD80
> +# echo CD80 >> device_id
> +
> +program BAR0 size as 1MB
> +# echo 100000 >> bar0_size
> +
> +check for programmed bar0 size
> +# cat bar0_size
> +
> +Program BAR0 Address as DDR (0x2100000). This is the physical address of
> +memory, which is to be made visible to PCIe host. Similarly any other peripheral
> +can also be made visible to PCIe host. E.g., if you program base address of UART
> +as BAR0 address then when this device will be connected to a host, it will be
> +visible as UART.
> +# echo 2100000 >> bar0_address
> +
> +program interrupt type : INTA
> +# echo INTA >> int_type
> +
> +go for link up now.
> +# echo UP >> link
> +
> +It will have to be insured that, once link up is done on gadget, then only host
> +is initialized and start to search PCIe devices on its port.
> +
> +/*wait till link is up*/
> +# cat link
> +wait till it returns UP.
> +
> +To assert INTA
> +# echo 1 >> inta
> +
> +To de-assert INTA
> +# echo 0 >> inta
> +
> +if MSI is to be used as interrupt, program no of msi vector needed (say4)
> +# echo 4 >> no_of_msi
> +
> +select MSI as interrupt type
> +# echo MSI >> int_type
> +
> +go for link up now
> +# echo UP >> link
> +
> +wait till link is up
> +# cat link
> +An application can repetitively read this node till link is found UP. It can
> +sleep between two read.
> +
> +wait till msi is enabled
> +# cat no_of_msi
> +Should return 4 (number of requested MSI vector)
> +
> +to send msi vector 2
> +# echo 2 >> send_msi
> +#cd -
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 4d073f1..dea052d 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -394,6 +394,16 @@ config DS1682
> This driver can also be built as a module. If so, the module
> will be called ds1682.
>
> +config SPEAR13XX_PCIE_GADGET
> + bool "PCIe gadget support for SPEAr13XX platform"
> + depends on ARCH_SPEAR13XX
> + default n
> + help
> + This option enables gadget support for PCIe controller. If
> + board file defines any controller as PCIe endpoint then a sysfs
> + entry will be created for that controller. User can use these
> + sysfs node to configure PCIe EP as per his requirements.
> +
> config TI_DAC7512
> tristate "Texas Instruments DAC7512"
> depends on SPI && SYSFS
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index 98009cc..c489536 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -37,6 +37,7 @@ obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
> obj-$(CONFIG_HMC6352) += hmc6352.o
> obj-y += eeprom/
> obj-y += cb710/
> +obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
> obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
> obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
> obj-$(CONFIG_PCH_PHUB) += pch_phub.o
> diff --git a/drivers/misc/spear13xx_pcie_gadget.c b/drivers/misc/spear13xx_pcie_gadget.c
> new file mode 100644
> index 0000000..ec3b8c9
> --- /dev/null
> +++ b/drivers/misc/spear13xx_pcie_gadget.c
> @@ -0,0 +1,908 @@
> +/*
> + * drivers/misc/spear13xx_pcie_gadget.c
> + *
> + * Copyright (C) 2010 ST Microelectronics
> + * Pratyush Anand<pratyush.anand@st.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pci_regs.h>
> +#include <linux/configfs.h>
> +#include <mach/pcie.h>
> +#include <mach/misc_regs.h>
> +
> +#define IN0_MEM_SIZE (200 * 1024 * 1024 - 1)
> +/* In current implementation address translation is done using IN0 only.
> + * So IN1 start address and IN0 end address has been kept same
> +*/
> +#define IN1_MEM_SIZE (0 * 1024 * 1024 - 1)
> +#define IN_IO_SIZE (20 * 1024 * 1024 - 1)
> +#define IN_CFG0_SIZE (12 * 1024 * 1024 - 1)
> +#define IN_CFG1_SIZE (12 * 1024 * 1024 - 1)
> +#define IN_MSG_SIZE (12 * 1024 * 1024 - 1)
> +/* Keep default BAR size as 4K*/
> +/* AORAM would be mapped by default*/
> +#define INBOUND_ADDR_MASK (SPEAR13XX_SYSRAM1_SIZE - 1)
> +
> +#define INT_TYPE_NO_INT 0
> +#define INT_TYPE_INTX 1
> +#define INT_TYPE_MSI 2
> +struct spear_pcie_gadget_config {
> + void __iomem *base;
> + void __iomem *va_app_base;
> + void __iomem *va_dbi_base;
> + char int_type[10];
> + ulong requested_msi;
> + ulong configured_msi;
> + ulong bar0_size;
> + ulong bar0_rw_offset;
> + void __iomem *va_bar0_address;
> +};
> +
> +struct pcie_gadget_target {
> + struct configfs_subsystem subsys;
> + struct spear_pcie_gadget_config config;
> +};
> +
> +struct pcie_gadget_target_attr {
> + struct configfs_attribute attr;
> + ssize_t (*show)(struct spear_pcie_gadget_config *config,
> + char *buf);
> + ssize_t (*store)(struct spear_pcie_gadget_config *config,
> + const char *buf,
> + size_t count);
> +};
> +
> +static void enable_dbi_access(struct pcie_app_reg __iomem *app_reg)
> +{
> + /* Enable DBI access */
> + writel(readl(&app_reg->slv_armisc) | (1 << AXI_OP_DBI_ACCESS_ID),
> + &app_reg->slv_armisc);
> + writel(readl(&app_reg->slv_awmisc) | (1 << AXI_OP_DBI_ACCESS_ID),
> + &app_reg->slv_awmisc);
> +
> +}
> +
> +static void disable_dbi_access(struct pcie_app_reg __iomem *app_reg)
> +{
> + /* disable DBI access */
> + writel(readl(&app_reg->slv_armisc) & ~(1 << AXI_OP_DBI_ACCESS_ID),
> + &app_reg->slv_armisc);
> + writel(readl(&app_reg->slv_awmisc) & ~(1 << AXI_OP_DBI_ACCESS_ID),
> + &app_reg->slv_awmisc);
> +
> +}
> +
> +static void spear_dbi_read_reg(struct spear_pcie_gadget_config *config,
> + int where, int size, u32 *val)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + ulong va_address;
> +
> + /* Enable DBI access */
> + enable_dbi_access(app_reg);
> +
> + va_address = (ulong)config->va_dbi_base + (where & ~0x3);
> +
> + *val = readl(va_address);
> +
> + if (size == 1)
> + *val = (*val >> (8 * (where & 3))) & 0xff;
> + else if (size == 2)
> + *val = (*val >> (8 * (where & 3))) & 0xffff;
> +
> + /* Disable DBI access */
> + disable_dbi_access(app_reg);
> +}
> +
> +static void spear_dbi_write_reg(struct spear_pcie_gadget_config *config,
> + int where, int size, u32 val)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + ulong va_address;
> +
> + /* Enable DBI access */
> + enable_dbi_access(app_reg);
> +
> + va_address = (ulong)config->va_dbi_base + (where & ~0x3);
> +
> + if (size == 4)
> + writel(val, va_address);
> + else if (size == 2)
> + writew(val, va_address + (where & 2));
> + else if (size == 1)
> + writeb(val, va_address + (where & 3));
> +
> + /* Disable DBI access */
> + disable_dbi_access(app_reg);
> +}
> +
> +#define PCI_FIND_CAP_TTL 48
> +
> +static int pci_find_own_next_cap_ttl(struct spear_pcie_gadget_config *config,
> + u32 pos, int cap, int *ttl)
> +{
> + u32 id;
> +
> + while ((*ttl)--) {
> + spear_dbi_read_reg(config, pos, 1, &pos);
> + if (pos < 0x40)
> + break;
> + pos &= ~3;
> + spear_dbi_read_reg(config, pos + PCI_CAP_LIST_ID, 1, &id);
> + if (id == 0xff)
> + break;
> + if (id == cap)
> + return pos;
> + pos += PCI_CAP_LIST_NEXT;
> + }
> + return 0;
> +}
> +
> +static int pci_find_own_next_cap(struct spear_pcie_gadget_config *config,
> + u32 pos, int cap)
> +{
> + int ttl = PCI_FIND_CAP_TTL;
> +
> + return pci_find_own_next_cap_ttl(config, pos, cap, &ttl);
> +}
> +
> +static int pci_find_own_cap_start(struct spear_pcie_gadget_config *config,
> + u8 hdr_type)
> +{
> + u32 status;
> +
> + spear_dbi_read_reg(config, PCI_STATUS, 2, &status);
> + if (!(status & PCI_STATUS_CAP_LIST))
> + return 0;
> +
> + switch (hdr_type) {
> + case PCI_HEADER_TYPE_NORMAL:
> + case PCI_HEADER_TYPE_BRIDGE:
> + return PCI_CAPABILITY_LIST;
> + case PCI_HEADER_TYPE_CARDBUS:
> + return PCI_CB_CAPABILITY_LIST;
> + default:
> + return 0;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Tell if a device supports a given PCI capability.
> + * Returns the address of the requested capability structure within the
> + * device's PCI configuration space or 0 in case the device does not
> + * support it. Possible values for @cap:
> + *
> + * %PCI_CAP_ID_PM Power Management
> + * %PCI_CAP_ID_AGP Accelerated Graphics Port
> + * %PCI_CAP_ID_VPD Vital Product Data
> + * %PCI_CAP_ID_SLOTID Slot Identification
> + * %PCI_CAP_ID_MSI Message Signalled Interrupts
> + * %PCI_CAP_ID_CHSWP CompactPCI HotSwap
> + * %PCI_CAP_ID_PCIX PCI-X
> + * %PCI_CAP_ID_EXP PCI Express
> + */
> +static int pci_find_own_capability(struct spear_pcie_gadget_config *config,
> + int cap)
> +{
> + u32 pos;
> + u32 hdr_type;
> +
> + spear_dbi_read_reg(config, PCI_HEADER_TYPE, 1, &hdr_type);
> +
> + pos = pci_find_own_cap_start(config, hdr_type);
> + if (pos)
> + pos = pci_find_own_next_cap(config, pos, cap);
> +
> + return pos;
> +}
> +
> +static irqreturn_t spear_pcie_gadget_irq(int irq, void *dev_id)
> +{
> + return 0;
> +}
> +
> +/*
> + * configfs interfaces show/store functions
> + */
> +static ssize_t pcie_gadget_show_link(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> +
> + if (readl(&app_reg->app_status_1) & ((u32)1 << XMLH_LINK_UP_ID))
> + return sprintf(buf, "UP");
> + else
> + return sprintf(buf, "DOWN");
> +}
> +
> +static ssize_t pcie_gadget_store_link(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> +
> + if (sysfs_streq(buf, "UP"))
> + writel(readl(&app_reg->app_ctrl_0) | (1 << APP_LTSSM_ENABLE_ID),
> + &app_reg->app_ctrl_0);
> + else if (sysfs_streq(buf, "DOWN"))
> + writel(readl(&app_reg->app_ctrl_0)
> + & ~(1 << APP_LTSSM_ENABLE_ID),
> + &app_reg->app_ctrl_0);
> + else
> + return -EINVAL;
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_int_type(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + return sprintf(buf, "%s", config->int_type);
> +}
> +
> +static ssize_t pcie_gadget_store_int_type(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + u32 cap, vec, flags;
> + ulong vector;
> +
> + if (sysfs_streq(buf, "INTA"))
> + spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 1);
> +
> + else if (sysfs_streq(buf, "MSI")) {
> + vector = config->requested_msi;
> + vec = 0;
> + while (vector > 1) {
> + vector /= 2;
> + vec++;
> + }
> + spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 0);
> + cap = pci_find_own_capability(config, PCI_CAP_ID_MSI);
> + spear_dbi_read_reg(config, cap + PCI_MSI_FLAGS, 1, &flags);
> + flags &= ~PCI_MSI_FLAGS_QMASK;
> + flags |= vec << 1;
> + spear_dbi_write_reg(config, cap + PCI_MSI_FLAGS, 1, flags);
> + } else
> + return -EINVAL;
> +
> + strcpy(config->int_type, buf);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_no_of_msi(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + u32 cap, vec, flags;
> + ulong vector;
> +
> + if ((readl(&app_reg->msg_status) & (1 << CFG_MSI_EN_ID))
> + != (1 << CFG_MSI_EN_ID))
> + vector = 0;
> + else {
> + cap = pci_find_own_capability(config, PCI_CAP_ID_MSI);
> + spear_dbi_read_reg(config, cap + PCI_MSI_FLAGS, 1, &flags);
> + flags &= ~PCI_MSI_FLAGS_QSIZE;
> + vec = flags >> 4;
> + vector = 1;
> + while (vec--)
> + vector *= 2;
> + }
> + config->configured_msi = vector;
> +
> + return sprintf(buf, "%lu", vector);
> +}
> +
> +static ssize_t pcie_gadget_store_no_of_msi(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + if (strict_strtoul(buf, 0, &config->requested_msi))
> + return -EINVAL;
> + if (config->requested_msi > 32)
> + config->requested_msi = 32;
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_store_inta(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + ulong en;
> +
> + if (strict_strtoul(buf, 0, &en))
> + return -EINVAL;
> +
> + if (en)
> + writel(readl(&app_reg->app_ctrl_0) | (1 << SYS_INT_ID),
> + &app_reg->app_ctrl_0);
> + else
> + writel(readl(&app_reg->app_ctrl_0) & ~(1 << SYS_INT_ID),
> + &app_reg->app_ctrl_0);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_store_send_msi(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + ulong vector;
> + u32 ven_msi;
> +
> + if (strict_strtoul(buf, 0, &vector))
> + return -EINVAL;
> +
> + if (!config->configured_msi)
> + return -EINVAL;
> +
> + if (vector >= config->configured_msi)
> + return -EINVAL;
> +
> + ven_msi = readl(&app_reg->ven_msi_1);
> + ven_msi &= ~VEN_MSI_FUN_NUM_MASK;
> + ven_msi |= 0 << VEN_MSI_FUN_NUM_ID;
> + ven_msi &= ~VEN_MSI_TC_MASK;
> + ven_msi |= 0 << VEN_MSI_TC_ID;
> + ven_msi &= ~VEN_MSI_VECTOR_MASK;
> + ven_msi |= vector << VEN_MSI_VECTOR_ID;
> +
> + /* generating interrupt for msi vector */
> + ven_msi |= VEN_MSI_REQ_EN;
> + writel(ven_msi, &app_reg->ven_msi_1);
> + udelay(1);
> + ven_msi &= ~VEN_MSI_REQ_EN;
> + writel(ven_msi, &app_reg->ven_msi_1);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_vendor_id(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + u32 id;
> +
> + spear_dbi_read_reg(config, PCI_VENDOR_ID, 2, &id);
> +
> + return sprintf(buf, "%x", id);
> +}
> +
> +static ssize_t pcie_gadget_store_vendor_id(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + ulong id;
> +
> + if (strict_strtoul(buf, 0, &id))
> + return -EINVAL;
> +
> + spear_dbi_write_reg(config, PCI_VENDOR_ID, 2, id);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_device_id(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + u32 id;
> +
> + spear_dbi_read_reg(config, PCI_DEVICE_ID, 2, &id);
> +
> + return sprintf(buf, "%x", id);
> +}
> +
> +static ssize_t pcie_gadget_store_device_id(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + ulong id;
> +
> + if (strict_strtoul(buf, 0, &id))
> + return -EINVAL;
> +
> + spear_dbi_write_reg(config, PCI_DEVICE_ID, 2, id);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_bar0_size(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + return sprintf(buf, "%lx", config->bar0_size);
> +}
> +
> +static ssize_t pcie_gadget_store_bar0_size(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + ulong size;
> + u32 pos, pos1;
> + u32 no_of_bit = 0;
> +
> + if (strict_strtoul(buf, 0, &size))
> + return -EINVAL;
> + /* min bar size is 256 */
> + if (size <= 0x100)
> + size = 0x100;
> + /* max bar size is 1MB*/
> + else if (size >= 0x100000)
> + size = 0x100000;
> + else {
> + pos = 0;
> + pos1 = 0;
> + while (pos < 21) {
> + pos = find_next_bit((ulong *)&size, 21, pos);
> + if (pos != 21)
> + pos1 = pos + 1;
> + pos++;
> + no_of_bit++;
> + }
> + if (no_of_bit == 2)
> + pos1--;
> +
> + size = 1 << pos1;
> + }
> + config->bar0_size = size;
> + spear_dbi_write_reg(config, PCIE_BAR0_MASK_REG, 4, size - 1);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_bar0_address(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> +
> + u32 address = readl(&app_reg->pim0_mem_addr_start);
> +
> + return sprintf(buf, "%x", address);
> +}
> +
> +static ssize_t pcie_gadget_store_bar0_address(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> + ulong address;
> +
> + if (strict_strtoul(buf, 0, &address))
> + return -EINVAL;
> +
> + address &= ~(config->bar0_size - 1);
> + if (config->va_bar0_address)
> + iounmap(config->va_bar0_address);
> + config->va_bar0_address = ioremap(address, config->bar0_size);
> + if (!config->va_bar0_address)
> + return -ENOMEM;
> +
> + writel(address, &app_reg->pim0_mem_addr_start);
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_bar0_rw_offset(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + return sprintf(buf, "%lx", config->bar0_rw_offset);
> +}
> +
> +static ssize_t pcie_gadget_store_bar0_rw_offset(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + ulong offset;
> +
> + if (strict_strtoul(buf, 0, &offset))
> + return -EINVAL;
> +
> + if (offset % 4)
> + return -EINVAL;
> +
> + config->bar0_rw_offset = offset;
> +
> + return count;
> +}
> +
> +static ssize_t pcie_gadget_show_bar0_data(
> + struct spear_pcie_gadget_config *config,
> + char *buf)
> +{
> + ulong data;
> +
> + if (!config->va_bar0_address)
> + return -ENOMEM;
> +
> + data = readl((ulong)config->va_bar0_address + config->bar0_rw_offset);
> +
> + return sprintf(buf, "%lx", data);
> +}
> +
> +static ssize_t pcie_gadget_store_bar0_data(
> + struct spear_pcie_gadget_config *config,
> + const char *buf, size_t count)
> +{
> + ulong data;
> +
> + if (strict_strtoul(buf, 0, &data))
> + return -EINVAL;
> +
> + if (!config->va_bar0_address)
> + return -ENOMEM;
> +
> + writel(data, (ulong)config->va_bar0_address + config->bar0_rw_offset);
> +
> + return count;
> +}
> +
> +/*
> + * Attribute definitions.
> + */
> +
> +#define PCIE_GADGET_TARGET_ATTR_RO(_name) \
> +static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
> + __CONFIGFS_ATTR(_name, S_IRUGO, pcie_gadget_show_##_name, NULL)
> +
> +#define PCIE_GADGET_TARGET_ATTR_WO(_name) \
> +static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
> + __CONFIGFS_ATTR(_name, S_IWUSR, NULL, pcie_gadget_store_##_name)
> +
> +#define PCIE_GADGET_TARGET_ATTR_RW(_name) \
> +static struct pcie_gadget_target_attr pcie_gadget_target_##_name = \
> + __CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, pcie_gadget_show_##_name, \
> + pcie_gadget_store_##_name)
> +PCIE_GADGET_TARGET_ATTR_RW(link);
> +PCIE_GADGET_TARGET_ATTR_RW(int_type);
> +PCIE_GADGET_TARGET_ATTR_RW(no_of_msi);
> +PCIE_GADGET_TARGET_ATTR_WO(inta);
> +PCIE_GADGET_TARGET_ATTR_WO(send_msi);
> +PCIE_GADGET_TARGET_ATTR_RW(vendor_id);
> +PCIE_GADGET_TARGET_ATTR_RW(device_id);
> +PCIE_GADGET_TARGET_ATTR_RW(bar0_size);
> +PCIE_GADGET_TARGET_ATTR_RW(bar0_address);
> +PCIE_GADGET_TARGET_ATTR_RW(bar0_rw_offset);
> +PCIE_GADGET_TARGET_ATTR_RW(bar0_data);
> +
> +static struct configfs_attribute *pcie_gadget_target_attrs[] = {
> + &pcie_gadget_target_link.attr,
> + &pcie_gadget_target_int_type.attr,
> + &pcie_gadget_target_no_of_msi.attr,
> + &pcie_gadget_target_inta.attr,
> + &pcie_gadget_target_send_msi.attr,
> + &pcie_gadget_target_vendor_id.attr,
> + &pcie_gadget_target_device_id.attr,
> + &pcie_gadget_target_bar0_size.attr,
> + &pcie_gadget_target_bar0_address.attr,
> + &pcie_gadget_target_bar0_rw_offset.attr,
> + &pcie_gadget_target_bar0_data.attr,
> + NULL,
> +};
> +
> +static struct pcie_gadget_target *to_target(struct config_item *item)
> +{
> + return item ?
> + container_of(to_configfs_subsystem(to_config_group(item)),
> + struct pcie_gadget_target, subsys) : NULL;
> +}
> +
> +/*
> + * Item operations and type for pcie_gadget_target.
> + */
> +
> +static ssize_t pcie_gadget_target_attr_show(struct config_item *item,
> + struct configfs_attribute *attr,
> + char *buf)
> +{
> + ssize_t ret = -EINVAL;
> + struct pcie_gadget_target *target = to_target(item);
> + struct pcie_gadget_target_attr *t_attr =
> + container_of(attr, struct pcie_gadget_target_attr, attr);
> +
> + if (t_attr->show)
> + ret = t_attr->show(&target->config, buf);
> + return ret;
> +}
> +
> +static ssize_t pcie_gadget_target_attr_store(struct config_item *item,
> + struct configfs_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + ssize_t ret = -EINVAL;
> + struct pcie_gadget_target *target = to_target(item);
> + struct pcie_gadget_target_attr *t_attr =
> + container_of(attr, struct pcie_gadget_target_attr, attr);
> +
> + if (t_attr->store)
> + ret = t_attr->store(&target->config, buf, count);
> + return ret;
> +}
> +
> +static struct configfs_item_operations pcie_gadget_target_item_ops = {
> + .show_attribute = pcie_gadget_target_attr_show,
> + .store_attribute = pcie_gadget_target_attr_store,
> +};
> +
> +static struct config_item_type pcie_gadget_target_type = {
> + .ct_attrs = pcie_gadget_target_attrs,
> + .ct_item_ops = &pcie_gadget_target_item_ops,
> + .ct_owner = THIS_MODULE,
> +};
> +
> +static void spear13xx_pcie_device_init(struct spear_pcie_gadget_config *config)
> +{
> + struct pcie_app_reg __iomem *app_reg = config->va_app_base;
> +
> + /*setup registers for outbound translation */
> +
> + writel(config->base, &app_reg->in0_mem_addr_start);
> + writel(app_reg->in0_mem_addr_start + IN0_MEM_SIZE,
> + &app_reg->in0_mem_addr_limit);
> + writel(app_reg->in0_mem_addr_limit + 1, &app_reg->in1_mem_addr_start);
> + writel(app_reg->in1_mem_addr_start + IN1_MEM_SIZE,
> + &app_reg->in1_mem_addr_limit);
> + writel(app_reg->in1_mem_addr_limit + 1, &app_reg->in_io_addr_start);
> + writel(app_reg->in_io_addr_start + IN_IO_SIZE,
> + &app_reg->in_io_addr_limit);
> + writel(app_reg->in_io_addr_limit + 1, &app_reg->in_cfg0_addr_start);
> + writel(app_reg->in_cfg0_addr_start + IN_CFG0_SIZE,
> + &app_reg->in_cfg0_addr_limit);
> + writel(app_reg->in_cfg0_addr_limit + 1, &app_reg->in_cfg1_addr_start);
> + writel(app_reg->in_cfg1_addr_start + IN_CFG1_SIZE,
> + &app_reg->in_cfg1_addr_limit);
> + writel(app_reg->in_cfg1_addr_limit + 1, &app_reg->in_msg_addr_start);
> + writel(app_reg->in_msg_addr_start + IN_MSG_SIZE,
> + &app_reg->in_msg_addr_limit);
> +
> + writel(app_reg->in0_mem_addr_start, &app_reg->pom0_mem_addr_start);
> + writel(app_reg->in1_mem_addr_start, &app_reg->pom1_mem_addr_start);
> + writel(app_reg->in_io_addr_start, &app_reg->pom_io_addr_start);
> +
> + /*setup registers for inbound translation */
> +
> + /* Keep AORAM mapped at BAR0 as default */
> + config->bar0_size = INBOUND_ADDR_MASK + 1;
> + spear_dbi_write_reg(config, PCIE_BAR0_MASK_REG, 4, INBOUND_ADDR_MASK);
> + spear_dbi_write_reg(config, PCI_BASE_ADDRESS_0, 4, 0xC);
> + config->va_bar0_address = ioremap(SPEAR13XX_SYSRAM1_BASE,
> + config->bar0_size);
> +
> + writel(SPEAR13XX_SYSRAM1_BASE, &app_reg->pim0_mem_addr_start);
> + writel(0, &app_reg->pim1_mem_addr_start);
> + writel(INBOUND_ADDR_MASK + 1, &app_reg->mem0_addr_offset_limit);
> +
> + writel(0x0, &app_reg->pim_io_addr_start);
> + writel(0x0, &app_reg->pim_io_addr_start);
> + writel(0x0, &app_reg->pim_rom_addr_start);
> +
> + writel(DEVICE_TYPE_EP | (1 << MISCTRL_EN_ID)
> + | ((u32)1 << REG_TRANSLATION_ENABLE),
> + &app_reg->app_ctrl_0);
> + /* disable all rx interrupts */
> + writel(0, &app_reg->int_mask);
> +
> + /* Select INTA as default*/
> + spear_dbi_write_reg(config, PCI_INTERRUPT_LINE, 1, 1);
> +}
> +
> +static int __devinit spear_pcie_gadget_probe(struct platform_device *pdev)
> +{
> + struct resource *res0, *res1;
> + unsigned int status = 0;
> + int irq;
> + struct clk *clk;
> + static struct pcie_gadget_target *target;
> + struct spear_pcie_gadget_config *config;
> + struct config_item *cg_item;
> + struct configfs_subsystem *subsys;
> +
> + /* get resource for application registers*/
> +
> + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res0) {
> + dev_err(&pdev->dev, "no resource defined\n");
> + return -EBUSY;
> + }
> + if (!request_mem_region(res0->start, resource_size(res0),
> + pdev->name)) {
> + dev_err(&pdev->dev, "pcie gadget region already claimed\n");
> + return -EBUSY;
> + }
> + /* get resource for dbi registers*/
> +
> + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + if (!res1) {
> + dev_err(&pdev->dev, "no resource defined\n");
> + goto err_rel_res0;
> + }
> + if (!request_mem_region(res1->start, resource_size(res1),
> + pdev->name)) {
> + dev_err(&pdev->dev, "pcie gadget region already claimed\n");
> + goto err_rel_res0;
> + }
> +
> + target = kzalloc(sizeof(*target), GFP_KERNEL);
> + if (!target) {
> + dev_err(&pdev->dev, "out of memory\n");
> + status = -ENOMEM;
> + goto err_rel_res;
> + }
> +
> + cg_item = &target->subsys.su_group.cg_item;
> + sprintf(cg_item->ci_namebuf, "pcie_gadget.%d", pdev->id);
> + cg_item->ci_type = &pcie_gadget_target_type;
> + config = &target->config;
> + config->va_app_base = (void __iomem *)ioremap(res0->start,
> + resource_size(res0));
> + if (!config->va_app_base) {
> + dev_err(&pdev->dev, "ioremap fail\n");
> + status = -ENOMEM;
> + goto err_kzalloc;
> + }
> +
> + config->base = (void __iomem *)res1->start;
> +
> + config->va_dbi_base = (void __iomem *)ioremap(res1->start,
> + resource_size(res1));
> + if (!config->va_dbi_base) {
> + dev_err(&pdev->dev, "ioremap fail\n");
> + status = -ENOMEM;
> + goto err_iounmap_app;
> + }
> +
> + dev_set_drvdata(&pdev->dev, target);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(&pdev->dev, "no update irq?\n");
> + status = irq;
> + goto err_iounmap;
> + }
> +
> + status = request_irq(irq, spear_pcie_gadget_irq, 0, pdev->name, NULL);
> + if (status) {
> + dev_err(&pdev->dev, "pcie gadget interrupt IRQ%d already \
> + claimed\n", irq);
> + goto err_iounmap;
> + }
> +
> + /* Register configfs hooks */
> + subsys = &target->subsys;
> + config_group_init(&subsys->su_group);
> + mutex_init(&subsys->su_mutex);
> + status = configfs_register_subsystem(subsys);
> + if (status)
> + goto err_irq;
> +
> + /*
> + * init basic pcie application registers
> + * do not enable clock if it is PCIE0.Ideally , all controller should
> + * have been independent from others with respect to clock. But PCIE1
> + * and 2 depends on PCIE0.So PCIE0 clk is provided during board init.
> + */
> + if (pdev->id == 1) {
> + /*
> + * Ideally CFG Clock should have been also enabled here. But
> + * it is done currently during board init routne
> + */
> + clk = clk_get_sys("pcie1", NULL);
> + if (IS_ERR(clk)) {
> + pr_err("%s:couldn't get clk for pcie1\n", __func__);
> + goto err_irq;
> + }
> + if (clk_enable(clk)) {
> + pr_err("%s:couldn't enable clk for pcie1\n", __func__);
> + goto err_irq;
> + }
> + } else if (pdev->id == 2) {
> + /*
> + * Ideally CFG Clock should have been also enabled here. But
> + * it is done currently during board init routne
> + */
> + clk = clk_get_sys("pcie2", NULL);
> + if (IS_ERR(clk)) {
> + pr_err("%s:couldn't get clk for pcie2\n", __func__);
> + goto err_irq;
> + }
> + if (clk_enable(clk)) {
> + pr_err("%s:couldn't enable clk for pcie2\n", __func__);
> + goto err_irq;
> + }
> + }
> + spear13xx_pcie_device_init(config);
> +
> + return 0;
> +err_irq:
> + free_irq(irq, NULL);
> +err_iounmap:
> + iounmap(config->va_dbi_base);
> +err_iounmap_app:
> + iounmap(config->va_app_base);
> +err_kzalloc:
> + kfree(config);
> +err_rel_res:
> + release_mem_region(res1->start, resource_size(res1));
> +err_rel_res0:
> + release_mem_region(res0->start, resource_size(res0));
> + return status;
> +}
> +
> +static int __devexit spear_pcie_gadget_remove(struct platform_device *pdev)
> +{
> + struct resource *res0, *res1;
> + static struct pcie_gadget_target *target;
> + struct spear_pcie_gadget_config *config;
> + int irq;
> +
> + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + irq = platform_get_irq(pdev, 0);
> + target = dev_get_drvdata(&pdev->dev);
> + config = &target->config;
> +
> + free_irq(irq, NULL);
> + iounmap(config->va_dbi_base);
> + iounmap(config->va_app_base);
> + release_mem_region(res1->start, resource_size(res1));
> + release_mem_region(res0->start, resource_size(res0));
> + configfs_unregister_subsystem(&target->subsys);
> + kfree(target);
> +
> + return 0;
> +}
> +
> +static void spear_pcie_gadget_shutdown(struct platform_device *pdev)
> +{
> +}
> +
> +static struct platform_driver spear_pcie_gadget_driver = {
> + .probe = spear_pcie_gadget_probe,
> + .remove = spear_pcie_gadget_remove,
> + .shutdown = spear_pcie_gadget_shutdown,
> + .driver = {
> + .name = "pcie-gadget-spear",
> + .bus = &platform_bus_type
> + },
> +};
> +
> +static int __init spear_pcie_gadget_init(void)
> +{
> + return platform_driver_register(&spear_pcie_gadget_driver);
> +}
> +module_init(spear_pcie_gadget_init);
> +
> +static void __exit spear_pcie_gadget_exit(void)
> +{
> + platform_driver_unregister(&spear_pcie_gadget_driver);
> +}
> +module_exit(spear_pcie_gadget_exit);
> +
> +MODULE_ALIAS("pcie-gadget-spear");
> +MODULE_AUTHOR("Pratyush Anand");
> +MODULE_LICENSE("GPL");
> --
> 1.6.0.2
>
> .
>
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] Add secdata to unix_streams.
[not found] <y>
` (20 preceding siblings ...)
2011-02-21 8:13 ` [PATCH V5] ST SPEAr: PCIE gadget suppport y
@ 2011-03-22 23:27 ` Pat Kane
2011-03-22 23:32 ` Randy Dunlap
[not found] ` <1310310203-12288-1-git-send-email-sundaram@ti.com>
` (138 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Pat Kane @ 2011-03-22 23:27 UTC (permalink / raw)
To: linux-kernel; +Cc: Pat Kane
The unix_dgram routines add secdata to socket message,
but the unix_stream routines do not. We add the two
missing lines of code.
Signed-off-by: Pat Kane <pekane52@gmail.com>
---
net/unix/af_unix.c | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 1663e1a..3873523 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1642,6 +1642,8 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,
max_level = err + 1;
fds_sent = true;
+ unix_get_secdata(siocb->scm, skb);
+
err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
if (err) {
kfree_skb(skb);
@@ -1930,6 +1932,7 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
} else {
/* Copy credentials */
scm_set_cred(siocb->scm, UNIXCB(skb).pid, UNIXCB(skb).cred);
+ unix_set_secdata(siocb->scm, skb);
check_creds = 1;
}
--
1.7.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] Add secdata to unix_streams.
2011-03-22 23:27 ` [PATCH] Add secdata to unix_streams Pat Kane
@ 2011-03-22 23:32 ` Randy Dunlap
0 siblings, 0 replies; 743+ messages in thread
From: Randy Dunlap @ 2011-03-22 23:32 UTC (permalink / raw)
To: Pat Kane; +Cc: linux-kernel
On Tue, 22 Mar 2011 18:27:02 -0500 Pat Kane wrote:
> The unix_dgram routines add secdata to socket message,
> but the unix_stream routines do not. We add the two
> missing lines of code.
Please copy netdev@vger.kernel.org on networking patches.
We who? :)
> Signed-off-by: Pat Kane <pekane52@gmail.com>
> ---
> net/unix/af_unix.c | 3 +++
> 1 files changed, 3 insertions(+), 0 deletions(-)
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 1663e1a..3873523 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1642,6 +1642,8 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,
> max_level = err + 1;
> fds_sent = true;
>
> + unix_get_secdata(siocb->scm, skb);
> +
> err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
> if (err) {
> kfree_skb(skb);
> @@ -1930,6 +1932,7 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
> } else {
> /* Copy credentials */
> scm_set_cred(siocb->scm, UNIXCB(skb).pid, UNIXCB(skb).cred);
> + unix_set_secdata(siocb->scm, skb);
> check_creds = 1;
> }
>
> --
gmail (?) mucked up the indentation tabs -- they are all spaces. Not good.
---
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***
^ permalink raw reply [flat|nested] 743+ messages in thread
[parent not found: <1310310203-12288-1-git-send-email-sundaram@ti.com>]
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
[not found] ` <1310310203-12288-1-git-send-email-sundaram@ti.com>
@ 2011-07-11 9:28 ` Linus Walleij
2011-07-11 21:39 ` Dan Williams
2011-07-12 4:17 ` Jassi Brar
1 sibling, 1 reply; 743+ messages in thread
From: Linus Walleij @ 2011-07-11 9:28 UTC (permalink / raw)
To: Sundaram Raju
Cc: linux-arm-kernel, linux-kernel, davinci-linux-open-source, linux,
dan.j.williams, linux-omap
2011/7/10 Sundaram Raju <sundaram@ti.com>:
> Added new dma_ctrl_cmd TI_DMA_STRIDE_CONFIG to pass the TI DMA
> controller specific configurations on how a buffer must be walked
> through and how data is picked for transfer based on a specified
> pattern over the channel.
>
> The configuration passed is specific to the TI DMA controller used.
>
> Signed-off-by: Sundaram Raju <sundaram@ti.com>
This is exactly how I think we should do this.
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Thanks,
Linus Walleij
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-11 9:28 ` [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration Linus Walleij
@ 2011-07-11 21:39 ` Dan Williams
2011-07-12 9:58 ` Linus Walleij
0 siblings, 1 reply; 743+ messages in thread
From: Dan Williams @ 2011-07-11 21:39 UTC (permalink / raw)
To: Linus Walleij
Cc: Sundaram Raju, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, linux-omap
On Mon, Jul 11, 2011 at 2:28 AM, Linus Walleij <linus.walleij@linaro.org> wrote:
> 2011/7/10 Sundaram Raju <sundaram@ti.com>:
>
>> Added new dma_ctrl_cmd TI_DMA_STRIDE_CONFIG to pass the TI DMA
>> controller specific configurations on how a buffer must be walked
>> through and how data is picked for transfer based on a specified
>> pattern over the channel.
>>
>> The configuration passed is specific to the TI DMA controller used.
...and I suspect the slave device drivers that use TI DMA are not
expected to ever work with other dmaengines? Likely the case, but
just wondering out loud.
>> Signed-off-by: Sundaram Raju <sundaram@ti.com>
>
> This is exactly how I think we should do this.
> Acked-by: Linus Walleij <linus.walleij@linaro.org>
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-11 21:39 ` Dan Williams
@ 2011-07-12 9:58 ` Linus Walleij
2011-07-12 10:15 ` Raju, Sundaram
0 siblings, 1 reply; 743+ messages in thread
From: Linus Walleij @ 2011-07-12 9:58 UTC (permalink / raw)
To: Dan Williams
Cc: Sundaram Raju, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, linux-omap
On Mon, Jul 11, 2011 at 11:39 PM, Dan Williams <dan.j.williams@intel.com> wrote:
> On Mon, Jul 11, 2011 at 2:28 AM, Linus Walleij <linus.walleij@linaro.org> wrote:
> ...and I suspect the slave device drivers that use TI DMA are not
> expected to ever work with other dmaengines? Likely the case, but
> just wondering out loud.
Typically the OMAP/TI drivers are one-to-one with this specific DMA
controller, but they *can* support controllers without stride options, and
notice that striding will only be used for the display driver IIRC,
pseudo-code:
ret = dmaengine_device_control(chan, TI_DMA_STRIDE_CONFIG,
(unsigned long) &my_stride_config);
if (ret) {
/*
* OK no striding on this DMA engine, fall back to something else,
* such as creating an SGlist which emulates the striding with one
* sglist element per stride.
*/
}
By injecting an error in the stride config path this can even be
properly tested. So it will become an optional acceleration.
Thanks,
Linus Walleij
^ permalink raw reply [flat|nested] 743+ messages in thread
* RE: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 9:58 ` Linus Walleij
@ 2011-07-12 10:15 ` Raju, Sundaram
0 siblings, 0 replies; 743+ messages in thread
From: Raju, Sundaram @ 2011-07-12 10:15 UTC (permalink / raw)
To: Linus Walleij, Dan Williams
Cc: linux-arm-kernel, linux-kernel, davinci-linux-open-source, linux,
linux-omap
> -----Original Message-----
> From: Linus Walleij [mailto:linus.walleij@linaro.org]
> Sent: Tuesday, July 12, 2011 3:28 PM
> To: Dan Williams
> Cc: Raju, Sundaram; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
> linux@arm.linux.org.uk; linux-omap@vger.kernel.org
> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
> configuration
>
> On Mon, Jul 11, 2011 at 11:39 PM, Dan Williams <dan.j.williams@intel.com>
> wrote:
> > On Mon, Jul 11, 2011 at 2:28 AM, Linus Walleij <linus.walleij@linaro.org>
> wrote:
>
> > ...and I suspect the slave device drivers that use TI DMA are not
> > expected to ever work with other dmaengines? Likely the case, but
> > just wondering out loud.
>
> Typically the OMAP/TI drivers are one-to-one with this specific DMA
> controller, but they *can* support controllers without stride options, and
> notice that striding will only be used for the display driver IIRC,
> pseudo-code:
>
> ret = dmaengine_device_control(chan, TI_DMA_STRIDE_CONFIG,
> (unsigned long) &my_stride_config);
> if (ret) {
> /*
> * OK no striding on this DMA engine, fall back to something else,
> * such as creating an SGlist which emulates the striding with one
> * sglist element per stride.
> */
> }
>
> By injecting an error in the stride config path this can even be
> properly tested. So it will become an optional acceleration.
Yes, this is exactly what I also wanted to say. :)
But if the client driver does not implement a fallback like this then that
driver will work only with TI DMA and not any other dmaengines.
(Mentioning this because, there are client drivers which are tightly
coupled to this special configuration)
Keeping this in mind, I had started the original discussion with
the suggestion of modifying the existing prepare API and adding
an extra argument to it, which can be used to pass special configuration.
And I also wanted to generalize that configuration passed.
Anyways that design also will come down to this same path, and instead
of modifying the existing API signatures, I think this is the best way
we can go.
Regards,
Sundaram
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
[not found] ` <1310310203-12288-1-git-send-email-sundaram@ti.com>
2011-07-11 9:28 ` [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration Linus Walleij
@ 2011-07-12 4:17 ` Jassi Brar
2011-07-12 10:03 ` Linus Walleij
1 sibling, 1 reply; 743+ messages in thread
From: Jassi Brar @ 2011-07-12 4:17 UTC (permalink / raw)
To: Sundaram Raju
Cc: linux-arm-kernel, linux-kernel, davinci-linux-open-source, linux,
linus.walleij, dan.j.williams, linux-omap
On Sun, Jul 10, 2011 at 8:33 PM, Sundaram Raju <sundaram@ti.com> wrote:
> Added new dma_ctrl_cmd TI_DMA_STRIDE_CONFIG to pass the TI DMA
> controller specific configurations on how a buffer must be walked
> through and how data is picked for transfer based on a specified
> pattern over the channel.
>
> The configuration passed is specific to the TI DMA controller used.
>
> Signed-off-by: Sundaram Raju <sundaram@ti.com>
> ---
> include/linux/dmaengine.h | 5 +++++
> 1 files changed, 5 insertions(+), 0 deletions(-)
>
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index eee7add..51dadc4 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -123,6 +123,10 @@ enum dma_ctrl_flags {
> * command.
> * @FSLDMA_EXTERNAL_START: this command will put the Freescale DMA controller
> * into external start mode.
> + * @TI_DMA_STRIDE_CONFIG: this command is only implemented by TI DMA
> + * controllers that need to pass special configuration on how to walk through
> + * the buffer to pick up data in a specified pattern to be transferred in
> + * the channel.
> */
> enum dma_ctrl_cmd {
> DMA_TERMINATE_ALL,
> @@ -130,6 +134,7 @@ enum dma_ctrl_cmd {
> DMA_RESUME,
> DMA_SLAVE_CONFIG,
> FSLDMA_EXTERNAL_START,
> + TI_DMA_STRIDE_CONFIG,
> };
IMHO this isn't very correct.
1) Striding, in one form or other, is supported by other DMACs as well.
The number will only increase in future.
Are we to add <VENDOR>_DMA_STRIDE_CONFIG for each case ?
2) As Dan noted, client drivers are going to have ifdef hackery in
order to be common
to other SoCs.
3) TI may not have just one DMAC IP used in all the SoCs. So if you want
vendor specific defines anyway, please atleast also add DMAC version to it.
Something like
> DMA_SLAVE_CONFIG,
> FSLDMA_EXTERNAL_START,
> + TI_DMA_v1_STRIDE_CONFIG,
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 4:17 ` Jassi Brar
@ 2011-07-12 10:03 ` Linus Walleij
2011-07-12 10:56 ` Raju, Sundaram
2011-07-12 11:20 ` Jassi Brar
0 siblings, 2 replies; 743+ messages in thread
From: Linus Walleij @ 2011-07-12 10:03 UTC (permalink / raw)
To: Jassi Brar
Cc: Sundaram Raju, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com> wrote:
> 1) Striding, in one form or other, is supported by other DMACs as well.
> The number will only increase in future.
> Are we to add <VENDOR>_DMA_STRIDE_CONFIG for each case ?
If we are sure about this and striding will work in a similar way on all
then let's have the enum named DMA_STRIDE_CONFIG and move the
passed-in struct to <linux/dmaengine.h) then?
Would that be:
struct dma_stride_config {
u32 read_bytes;
u32 skip_bytes;
};
Or something more complex?
> 2) As Dan noted, client drivers are going to have ifdef hackery in
> order to be common
> to other SoCs.
Don't think so, why? This is a runtime config entirely, and I just illustrated
in mail to Dan how that can be handled by falling back to a sglist I believe?
We can *maybe* even put the fallback code into dmaengine, so that an
emulated sglist in place for the DMAengine is done automatically of the
DMA controller does not support striding.
> 3) TI may not have just one DMAC IP used in all the SoCs. So if you want
> vendor specific defines anyway, please atleast also add DMAC version to it.
> Something like
>> DMA_SLAVE_CONFIG,
>> FSLDMA_EXTERNAL_START,
>> + TI_DMA_v1_STRIDE_CONFIG,
Yep unless we make it generic DMA_STRIDE_CONFIG simply, this makes
a lot of sense.
Linus Walleij
^ permalink raw reply [flat|nested] 743+ messages in thread
* RE: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 10:03 ` Linus Walleij
@ 2011-07-12 10:56 ` Raju, Sundaram
2011-07-12 11:09 ` Linus Walleij
2011-07-12 11:20 ` Jassi Brar
1 sibling, 1 reply; 743+ messages in thread
From: Raju, Sundaram @ 2011-07-12 10:56 UTC (permalink / raw)
To: Linus Walleij, Jassi Brar
Cc: linux-arm-kernel, linux-kernel, davinci-linux-open-source, linux,
dan.j.williams, linux-omap
> -----Original Message-----
> From: Linus Walleij [mailto:linus.walleij@linaro.org]
> Sent: Tuesday, July 12, 2011 3:33 PM
> To: Jassi Brar
> Cc: Raju, Sundaram; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
> linux@arm.linux.org.uk; dan.j.williams@intel.com; linux-omap@vger.kernel.org
> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
> configuration
>
> On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com>
> wrote:
>
> > 1) Striding, in one form or other, is supported by other DMACs as well.
> > The number will only increase in future.
> > Are we to add <VENDOR>_DMA_STRIDE_CONFIG for each case ?
>
> If we are sure about this and striding will work in a similar way on all
> then let's have the enum named DMA_STRIDE_CONFIG and move the
> passed-in struct to <linux/dmaengine.h) then?
>
> Would that be:
>
> struct dma_stride_config {
> u32 read_bytes;
> u32 skip_bytes;
> };
>
> Or something more complex?
>
When I started this discussion on stride config, I received comments like
this is too specific to TI DMAC, and there are not many DMACs which can
do this. I actually wanted to generalize the configuration passed and put
a comment on it similar to the one on top of dma_slave_config, which says
|
|/**
<snip>
| * The rationale for adding configuration information to this struct
| * is as follows: if it is likely that most DMA slave controllers in
| * the world will support the configuration option, then make it
| * generic. If not: if it is fixed so that it be sent in static from
| * the platform data, then prefer to do that. Else, if it is neither
| * fixed at runtime, nor generic enough (such as bus mastership on
| * some CPU family and whatnot) then create a custom slave config
| * struct and pass that, then make this config a member of that
| * struct, if applicable.
| */
|
If any other DMAC can do similar stride configuration,
then we can generalize it.
Till we generalize this stride configuration I think a custom
configuration aligned between the client driver and
the offload engine driver can be used.
> > 2) As Dan noted, client drivers are going to have ifdef hackery in
> > order to be common
> > to other SoCs.
>
> Don't think so, why? This is a runtime config entirely, and I just illustrated
> in mail to Dan how that can be handled by falling back to a sglist I believe?
>
> We can *maybe* even put the fallback code into dmaengine, so that an
> emulated sglist in place for the DMAengine is done automatically of the
> DMA controller does not support striding.
>
Good Idea.
But the client might always have a better way to handle this fallback than
this suggested fallback code in dmaengine, which will be a common
implementation based on the received sg_list and the DMAC capabilities.
If this is done then preference should be provided to the client's fallback
implementation, if present.
> > 3) TI may not have just one DMAC IP used in all the SoCs. So if you want
> > vendor specific defines anyway, please atleast also add DMAC version to it.
> > Something like
> >> DMA_SLAVE_CONFIG,
> >> FSLDMA_EXTERNAL_START,
> >> + TI_DMA_v1_STRIDE_CONFIG,
>
> Yep unless we make it generic DMA_STRIDE_CONFIG simply, this makes
> a lot of sense.
>
Okay, I can add one cmd for the EDMAC in DaVinci series of SoCs and
one for SDMAC in OMAP series of SoCs.
Regards,
Sundaram
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 10:56 ` Raju, Sundaram
@ 2011-07-12 11:09 ` Linus Walleij
0 siblings, 0 replies; 743+ messages in thread
From: Linus Walleij @ 2011-07-12 11:09 UTC (permalink / raw)
To: Raju, Sundaram
Cc: Jassi Brar, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
On Tue, Jul 12, 2011 at 12:56 PM, Raju, Sundaram <sundaram@ti.com> wrote:
> [Me]
>> [Jassi]
>> > 3) TI may not have just one DMAC IP used in all the SoCs. So if you want
>> > vendor specific defines anyway, please atleast also add DMAC version to it.
>> > Something like
>> >> DMA_SLAVE_CONFIG,
>> >> FSLDMA_EXTERNAL_START,
>> >> + TI_DMA_v1_STRIDE_CONFIG,
>>
>> Yep unless we make it generic DMA_STRIDE_CONFIG simply, this makes
>> a lot of sense.
>
> Okay, I can add one cmd for the EDMAC in DaVinci series of SoCs and
> one for SDMAC in OMAP series of SoCs.
Wait, that's two different silicon blocks right? Then you already have proof
that this spans more than one DMAC and then you can just go for a generic
DMA_STRIDE_CONFIG from day one.
That both are TI does not matter, if they are totally unrelated implementations.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 10:03 ` Linus Walleij
2011-07-12 10:56 ` Raju, Sundaram
@ 2011-07-12 11:20 ` Jassi Brar
2011-07-12 11:31 ` Raju, Sundaram
1 sibling, 1 reply; 743+ messages in thread
From: Jassi Brar @ 2011-07-12 11:20 UTC (permalink / raw)
To: Linus Walleij
Cc: Sundaram Raju, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
On Tue, Jul 12, 2011 at 3:33 PM, Linus Walleij <linus.walleij@linaro.org> wrote:
> On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com> wrote:
>
>> 1) Striding, in one form or other, is supported by other DMACs as well.
>> The number will only increase in future.
>> Are we to add <VENDOR>_DMA_STRIDE_CONFIG for each case ?
>
> If we are sure about this and striding will work in a similar way on all
> then let's have the enum named DMA_STRIDE_CONFIG and move the
> passed-in struct to <linux/dmaengine.h) then?
>
> Would that be:
>
> struct dma_stride_config {
> u32 read_bytes;
> u32 skip_bytes;
> };
>
> Or something more complex?
Well, I am not sure if striding needs any special treatment at all.
Why not have client drivers prepare and submit sg-list.
Let the DMAC drivers interpret/parse the sg-list and program it
as strides if the h/w supports it.
If anything, we should make preparation and submission of sg-list
as efficient as possible.
>> 2) As Dan noted, client drivers are going to have ifdef hackery in
>> order to be common
>> to other SoCs.
>
> Don't think so, why? This is a runtime config entirely, and I just illustrated
> in mail to Dan how that can be handled by falling back to a sglist I believe?
Runtime decision isn't neat either.
What if a client driver is common to 'N' SoCs each with different DMACs ?
We would need a switch construct !
^ permalink raw reply [flat|nested] 743+ messages in thread
* RE: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 11:20 ` Jassi Brar
@ 2011-07-12 11:31 ` Raju, Sundaram
2011-07-12 12:45 ` Jassi Brar
0 siblings, 1 reply; 743+ messages in thread
From: Raju, Sundaram @ 2011-07-12 11:31 UTC (permalink / raw)
To: Jassi Brar, Linus Walleij
Cc: linux-arm-kernel, linux-kernel, davinci-linux-open-source, linux,
dan.j.williams, linux-omap
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="utf-8", Size: 1926 bytes --]
> -----Original Message-----
> From: Jassi Brar [mailto:jassisinghbrar@gmail.com]
> Sent: Tuesday, July 12, 2011 4:51 PM
> To: Linus Walleij
> Cc: Raju, Sundaram; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
> linux@arm.linux.org.uk; dan.j.williams@intel.com; linux-omap@vger.kernel.org
> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
> configuration
>
> On Tue, Jul 12, 2011 at 3:33 PM, Linus Walleij <linus.walleij@linaro.org>
> wrote:
> > On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com>
> wrote:
> >
> >> 1) Striding, in one form or other, is supported by other DMACs as well.
> >> Â The number will only increase in future.
> >>  Are we to add  <VENDOR>_DMA_STRIDE_CONFIG for each case ?
> >
> > If we are sure about this and striding will work in a similar way on all
> > then let's have the enum named DMA_STRIDE_CONFIG and move the
> > passed-in struct to <linux/dmaengine.h) then?
> >
> > Would that be:
> >
> > struct dma_stride_config {
> > Â Â u32 read_bytes;
> > Â Â u32 skip_bytes;
> > };
> >
> > Or something more complex?
> Well, I am not sure if striding needs any special treatment at all.
> Why not have client drivers prepare and submit sg-list.
> Let the DMAC drivers interpret/parse the sg-list and program it
> as strides if the h/w supports it.
> If anything, we should make preparation and submission of sg-list
> as efficient as possible.
Jassi,
sg_lists describe only a bunch of disjoint buffers. But what if the
DMAC can skip and read the bytes within each of the buffers in
the sg_list? (like TI EDMAC and TI SDMAC)
How can that information be passed to the offload
engine driver from the client?
~Sundaram
ÿôèº{.nÇ+·®+%Ëÿ±éݶ\x17¥wÿº{.nÇ+·¥{±þG«éÿ{ayº\x1dÊÚë,j\a¢f£¢·hïêÿêçz_è®\x03(éÝ¢j"ú\x1a¶^[m§ÿÿ¾\a«þG«éÿ¢¸?¨èÚ&£ø§~á¶iOæ¬z·vØ^\x14\x04\x1a¶^[m§ÿÿÃ\fÿ¶ìÿ¢¸?I¥
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 11:31 ` Raju, Sundaram
@ 2011-07-12 12:45 ` Jassi Brar
2011-07-18 7:51 ` Raju, Sundaram
0 siblings, 1 reply; 743+ messages in thread
From: Jassi Brar @ 2011-07-12 12:45 UTC (permalink / raw)
To: Raju, Sundaram
Cc: Linus Walleij, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
On Tue, Jul 12, 2011 at 5:01 PM, Raju, Sundaram <sundaram@ti.com> wrote:
>> -----Original Message-----
>> From: Jassi Brar [mailto:jassisinghbrar@gmail.com]
>> Sent: Tuesday, July 12, 2011 4:51 PM
>> To: Linus Walleij
>> Cc: Raju, Sundaram; linux-arm-kernel@lists.infradead.org; linux-
>> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
>> linux@arm.linux.org.uk; dan.j.williams@intel.com; linux-omap@vger.kernel.org
>> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
>> configuration
>>
>> On Tue, Jul 12, 2011 at 3:33 PM, Linus Walleij <linus.walleij@linaro.org>
>> wrote:
>> > On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com>
>> wrote:
>> >
>> >> 1) Striding, in one form or other, is supported by other DMACs as well.
>> >> The number will only increase in future.
>> >> Are we to add <VENDOR>_DMA_STRIDE_CONFIG for each case ?
>> >
>> > If we are sure about this and striding will work in a similar way on all
>> > then let's have the enum named DMA_STRIDE_CONFIG and move the
>> > passed-in struct to <linux/dmaengine.h) then?
>> >
>> > Would that be:
>> >
>> > struct dma_stride_config {
>> > u32 read_bytes;
>> > u32 skip_bytes;
>> > };
>> >
>> > Or something more complex?
>> Well, I am not sure if striding needs any special treatment at all.
>> Why not have client drivers prepare and submit sg-list.
>> Let the DMAC drivers interpret/parse the sg-list and program it
>> as strides if the h/w supports it.
>> If anything, we should make preparation and submission of sg-list
>> as efficient as possible.
> Jassi,
>
> sg_lists describe only a bunch of disjoint buffers. But what if the
> DMAC can skip and read the bytes within each of the buffers in
> the sg_list? (like TI EDMAC and TI SDMAC)
> How can that information be passed to the offload
> engine driver from the client?
>
OK, I overlooked.
We do need something new to handle these ultra-fine-grained sg-lists.
But still we shouldn't add SoC specific API to the common sub-systems.
Maybe a new api to pass fixed-format variable-length encoded message
to the DMAC drivers?
Which could be interpreted by DMAC drivers to extract all the needed xfer
parameters from the 'header' section and instructions to program the xfers
in the DMAC from the variable length body of the 'message' buffer.
It might sound complicated but we can have helpers to make the job easy.
Btw, the regular single/sg-list xfers could also be expressed by this method.
^ permalink raw reply [flat|nested] 743+ messages in thread
* RE: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-12 12:45 ` Jassi Brar
@ 2011-07-18 7:51 ` Raju, Sundaram
2011-07-23 20:35 ` Jassi Brar
0 siblings, 1 reply; 743+ messages in thread
From: Raju, Sundaram @ 2011-07-18 7:51 UTC (permalink / raw)
To: Jassi Brar
Cc: Linus Walleij, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="utf-8", Size: 3672 bytes --]
> -----Original Message-----
> From: Jassi Brar [mailto:jassisinghbrar@gmail.com]
> Sent: Tuesday, July 12, 2011 6:15 PM
> To: Raju, Sundaram
> Cc: Linus Walleij; linux-arm-kernel@lists.infradead.org; linux-
> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
> linux@arm.linux.org.uk; dan.j.williams@intel.com; linux-omap@vger.kernel.org
> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
> configuration
>
> On Tue, Jul 12, 2011 at 5:01 PM, Raju, Sundaram <sundaram@ti.com> wrote:
> >> -----Original Message-----
> >> From: Jassi Brar [mailto:jassisinghbrar@gmail.com]
> >> Sent: Tuesday, July 12, 2011 4:51 PM
> >> To: Linus Walleij
> >> Cc: Raju, Sundaram; linux-arm-kernel@lists.infradead.org; linux-
> >> kernel@vger.kernel.org; davinci-linux-open-source@linux.davincidsp.com;
> >> linux@arm.linux.org.uk; dan.j.williams@intel.com; linux-
> omap@vger.kernel.org
> >> Subject: Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride
> >> configuration
> >>
> >> On Tue, Jul 12, 2011 at 3:33 PM, Linus Walleij <linus.walleij@linaro.org>
> >> wrote:
> >> > On Tue, Jul 12, 2011 at 6:17 AM, Jassi Brar <jassisinghbrar@gmail.com>
> >> wrote:
> >> >
> >> >> 1) Striding, in one form or other, is supported by other DMACs as well.
> >> >> Â The number will only increase in future.
> >> >>  Are we to add  <VENDOR>_DMA_STRIDE_CONFIG for each case ?
> >> >
> >> > If we are sure about this and striding will work in a similar way on all
> >> > then let's have the enum named DMA_STRIDE_CONFIG and move the
> >> > passed-in struct to <linux/dmaengine.h) then?
> >> >
> >> > Would that be:
> >> >
> >> > struct dma_stride_config {
> >> > Â Â u32 read_bytes;
> >> > Â Â u32 skip_bytes;
> >> > };
> >> >
> >> > Or something more complex?
> >> Well, I am not sure if striding needs any special treatment at all.
> >> Why not have client drivers prepare and submit sg-list.
> >> Let the DMAC drivers interpret/parse the sg-list and program it
> >> as strides if the h/w supports it.
> >> If anything, we should make preparation and submission of sg-list
> >> as efficient as possible.
> > Jassi,
> >
> > sg_lists describe only a bunch of disjoint buffers. But what if the
> > DMAC can skip and read the bytes within each of the buffers in
> > the sg_list? (like TI EDMAC and TI SDMAC)
> > How can that information be passed to the offload
> > engine driver from the client?
> >
> OK, I overlooked.
> We do need something new to handle these ultra-fine-grained sg-lists.
> But still we shouldn't add SoC specific API to the common sub-systems.
>
> Maybe a new api to pass fixed-format variable-length encoded message
> to the DMAC drivers?
> Which could be interpreted by DMAC drivers to extract all the needed xfer
> parameters from the 'header' section and instructions to program the xfers
> in the DMAC from the variable length body of the 'message' buffer.
> It might sound complicated but we can have helpers to make the job easy.
> Btw, the regular single/sg-list xfers could also be expressed by this method.
Do you expect this variable length body of the message to be DMAC
independent? I don't think so. In that case how is this different from what
we have here already?
If it can be DMAC independent, can you illustrate more on how this can
be done? But the point to note is, if this can be made DMAC independent
then the control command we have also can be made DMAC independent
by generalizing the configuration structure passed to it.
~ Sundaram
ÿôèº{.nÇ+·®+%Ëÿ±éݶ\x17¥wÿº{.nÇ+·¥{±þG«éÿ{ayº\x1dÊÚë,j\a¢f£¢·hïêÿêçz_è®\x03(éÝ¢j"ú\x1a¶^[m§ÿÿ¾\a«þG«éÿ¢¸?¨èÚ&£ø§~á¶iOæ¬z·vØ^\x14\x04\x1a¶^[m§ÿÿÃ\fÿ¶ìÿ¢¸?I¥
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] dmaengine: add dma_ctrl_cmd to pass buffer stride configuration
2011-07-18 7:51 ` Raju, Sundaram
@ 2011-07-23 20:35 ` Jassi Brar
0 siblings, 0 replies; 743+ messages in thread
From: Jassi Brar @ 2011-07-23 20:35 UTC (permalink / raw)
To: Raju, Sundaram
Cc: Linus Walleij, linux-arm-kernel, linux-kernel,
davinci-linux-open-source, linux, dan.j.williams, linux-omap
On Mon, Jul 18, 2011 at 1:21 PM, Raju, Sundaram <sundaram@ti.com> wrote:
>>
>> Maybe a new api to pass fixed-format variable-length encoded message
>> to the DMAC drivers?
>> Which could be interpreted by DMAC drivers to extract all the needed xfer
>> parameters from the 'header' section and instructions to program the xfers
>> in the DMAC from the variable length body of the 'message' buffer.
>> It might sound complicated but we can have helpers to make the job easy.
>> Btw, the regular single/sg-list xfers could also be expressed by this method.
>
> Do you expect this variable length body of the message to be DMAC
> independent? I don't think so. In that case how is this different from what
> we have here already?
Yes, this whould be DMAC independent.
> If it can be DMAC independent, can you illustrate more on how this can
> be done? But the point to note is, if this can be made DMAC independent
> then the control command we have also can be made DMAC independent
> by generalizing the configuration structure passed to it.
The 'header' I suggest, would in fact be a structure body, only an extra pointer
would point to the 'instructions' to convey actual location and sizes
of mico-xfers.
I don't think it is possible to have general definition of such transfers fully
within a structure. If I understand what you ask.
I have just posted an RFC. I kept the terms same so that it is easier
to understand.
Please have a look. You are CC'ed too.
Thanks,
Jassi
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 0/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
[not found] <y>
` (22 preceding siblings ...)
[not found] ` <1310310203-12288-1-git-send-email-sundaram@ti.com>
@ 2011-07-14 0:33 ` Ayman El-Khashab
2011-07-14 0:33 ` [PATCH 1/1] " Ayman El-Khashab
2011-08-30 11:53 ` high speed (921.6 kbit/s) marcus.folkesson
` (136 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Ayman El-Khashab @ 2011-07-14 0:33 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Tony Breeds,
linuxppc-dev, linux-kernel
Cc: Ayman El-Khashab
Old code didn't check the proper registers for the 460sx and so the link check
would always fail. This changes the link check to use the config space reg and
adds the definition for the register. Also changes the vc0 to a pll check.
Sets up the port for gen2 speeds.
This uses the check_link function to specialize the link detect for the 460sx.
Tested on an eiger and custom board. Note that is swaps the order of the link
check and dcr initialization since the config space needs the DCRs setup before
it can be mapped and used to check the link.
Ayman El-Khashab (1):
powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
arch/powerpc/sysdev/ppc4xx_pci.c | 83 +++++++++++++++++++++++++++++--------
arch/powerpc/sysdev/ppc4xx_pci.h | 3 +
2 files changed, 68 insertions(+), 18 deletions(-)
--
1.7.4.3
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 0:33 ` [PATCH 0/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx Ayman El-Khashab
@ 2011-07-14 0:33 ` Ayman El-Khashab
2011-07-14 1:16 ` Tony Breeds
` (2 more replies)
0 siblings, 3 replies; 743+ messages in thread
From: Ayman El-Khashab @ 2011-07-14 0:33 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Tony Breeds,
linuxppc-dev, linux-kernel
Cc: Ayman El-Khashab
Adds a register to the config space for the 460sx. Changes the vc0
detect to a pll detect. maps configuration space to test the link
status. changes the setup to enable gen2 devices to operate at gen2
speeds. fixes mapping that was not correct for the 460sx.
tested on the 460sx eiger and custom board
Signed-off-by: Ayman El-Khashab <ayman@elkhashab.com>
---
arch/powerpc/sysdev/ppc4xx_pci.c | 83 +++++++++++++++++++++++++++++--------
arch/powerpc/sysdev/ppc4xx_pci.h | 3 +
2 files changed, 68 insertions(+), 18 deletions(-)
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.c b/arch/powerpc/sysdev/ppc4xx_pci.c
index ad330fe..273963b 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.c
+++ b/arch/powerpc/sysdev/ppc4xx_pci.c
@@ -1092,6 +1092,10 @@ static int __init ppc460sx_pciex_core_init(struct device_node *np)
mtdcri(SDR0, PESDR1_460SX_HSSSLEW, 0xFFFF0000);
mtdcri(SDR0, PESDR2_460SX_HSSSLEW, 0xFFFF0000);
+ /* Set HSS PRBS enabled */
+ mtdcri(SDR0, PESDR0_460SX_HSSCTLSET, 0x00001130);
+ mtdcri(SDR0, PESDR2_460SX_HSSCTLSET, 0x00001130);
+
udelay(100);
/* De-assert PLLRESET */
@@ -1132,9 +1136,6 @@ static int ppc460sx_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
dcri_clrset(SDR0, port->sdr_base + PESDRn_UTLSET2,
0, 0x01000000);
- /*Gen-1*/
- mtdcri(SDR0, port->sdr_base + PESDRn_460SX_RCEI, 0x08000000);
-
dcri_clrset(SDR0, port->sdr_base + PESDRn_RCSSET,
(PESDRx_RCSSET_RSTGU | PESDRx_RCSSET_RSTDL),
PESDRx_RCSSET_RSTPYN);
@@ -1148,14 +1149,42 @@ static int ppc460sx_pciex_init_utl(struct ppc4xx_pciex_port *port)
{
/* Max 128 Bytes */
out_be32 (port->utl_base + PEUTL_PBBSZ, 0x00000000);
+ /* Assert VRB and TXE - per datasheet turn off addr validation */
+ out_be32(port->utl_base + PEUTL_PCTL, 0x80800000);
return 0;
}
+static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
+{
+ void __iomem *mbase;
+ int attempt = 50;
+
+ port->link = 0;
+
+ mbase = ioremap(port->cfg_space.start + 0x00000000, 0x1000);
+ if (mbase == NULL) {
+ printk(KERN_ERR "%s: Can't map internal config space !",
+ port->node->full_name);
+ goto done;
+ }
+
+ while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
+ & 0x00000010))) {
+ attempt--;
+ mdelay(10);
+ }
+ if (attempt)
+ port->link = 1;
+done:
+ iounmap(mbase);
+
+}
+
static struct ppc4xx_pciex_hwops ppc460sx_pcie_hwops __initdata = {
.core_init = ppc460sx_pciex_core_init,
.port_init_hw = ppc460sx_pciex_init_port_hw,
.setup_utl = ppc460sx_pciex_init_utl,
- .check_link = ppc4xx_pciex_check_link_sdr,
+ .check_link = ppc460sx_pciex_check_link,
};
#endif /* CONFIG_44x */
@@ -1338,15 +1367,15 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
if (rc != 0)
return rc;
- if (ppc4xx_pciex_hwops->check_link)
- ppc4xx_pciex_hwops->check_link(port);
-
/*
* Initialize mapping: disable all regions and configure
* CFG and REG regions based on resources in the device tree
*/
ppc4xx_pciex_port_init_mapping(port);
+ if (ppc4xx_pciex_hwops->check_link)
+ ppc4xx_pciex_hwops->check_link(port);
+
/*
* Map UTL
*/
@@ -1360,13 +1389,23 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
ppc4xx_pciex_hwops->setup_utl(port);
/*
- * Check for VC0 active and assert RDY.
+ * Check for VC0 active or PLL Locked and assert RDY.
*/
if (port->sdr_base) {
- if (port->link &&
- ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
- 1 << 16, 1 << 16, 5000)) {
- printk(KERN_INFO "PCIE%d: VC0 not active\n", port->index);
+ if (of_device_is_compatible(port->node,
+ "ibm,plb-pciex-460sx")){
+ if (port->link && ppc4xx_pciex_wait_on_sdr(port,
+ PESDRn_RCSSTS,
+ 1 << 12, 1 << 12, 5000)) {
+ printk(KERN_INFO "PCIE%d: PLL not locked\n",
+ port->index);
+ port->link = 0;
+ }
+ } else if (port->link &&
+ ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
+ 1 << 16, 1 << 16, 5000)) {
+ printk(KERN_INFO "PCIE%d: VC0 not active\n",
+ port->index);
port->link = 0;
}
@@ -1565,6 +1604,10 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
pcial = RES_TO_U32_LOW(pci_addr);
sa = (0xffffffffu << ilog2(size)) | 0x1;
+ /* Enabled and single region */
+ sa |= (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx")) ?
+ 0x5 : 0x3;
+
/* Program register values */
switch (index) {
case 0:
@@ -1573,8 +1616,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa);
break;
case 1:
out_le32(mbase + PECFG_POM1LAH, pciah);
@@ -1582,8 +1624,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa);
break;
case 2:
out_le32(mbase + PECFG_POM2LAH, pciah);
@@ -1591,8 +1632,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | IO space !!! */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa);
break;
}
@@ -1693,6 +1733,9 @@ static void __init ppc4xx_configure_pciex_PIMs(struct ppc4xx_pciex_port *port,
if (res->flags & IORESOURCE_PREFETCH)
sa |= 0x8;
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ sa |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+
out_le32(mbase + PECFG_BAR0HMPA, RES_TO_U32_HIGH(sa));
out_le32(mbase + PECFG_BAR0LMPA, RES_TO_U32_LOW(sa));
@@ -1854,6 +1897,10 @@ static void __init ppc4xx_pciex_port_setup_hose(struct ppc4xx_pciex_port *port)
}
out_le16(mbase + 0x202, val);
+ /* Enable Bus master, memory, and io space */
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ out_le16(mbase + 0x204, 0x7);
+
if (!port->endpoint) {
/* Set Class Code to PCI-PCI bridge and Revision Id to 1 */
out_le32(mbase + 0x208, 0x06040001);
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.h b/arch/powerpc/sysdev/ppc4xx_pci.h
index 56d9e5d..28f0bb0 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.h
+++ b/arch/powerpc/sysdev/ppc4xx_pci.h
@@ -464,6 +464,9 @@
#define PECFG_POM2LAL 0x390
#define PECFG_POM2LAH 0x394
+/* 460sx only */
+#define PECFG_460SX_DLLSTA 0x3f8
+
/* SDR Bit Mappings */
#define PESDRx_RCSSET_HLDPLB 0x10000000
#define PESDRx_RCSSET_RSTGU 0x01000000
--
1.7.4.3
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 0:33 ` [PATCH 1/1] " Ayman El-Khashab
@ 2011-07-14 1:16 ` Tony Breeds
2011-07-14 16:04 ` Ayman El-Khashab
2011-07-15 16:40 ` [v2 PATCH " Ayman Elkhashab
2011-07-20 13:02 ` [v3 PATCH 0/1] " Ayman Elkhashab
2 siblings, 1 reply; 743+ messages in thread
From: Tony Breeds @ 2011-07-14 1:16 UTC (permalink / raw)
To: Ayman El-Khashab
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev, linux-kernel
On Wed, Jul 13, 2011 at 07:33:31PM -0500, Ayman El-Khashab wrote:
> Adds a register to the config space for the 460sx. Changes the vc0
> detect to a pll detect. maps configuration space to test the link
> status. changes the setup to enable gen2 devices to operate at gen2
> speeds. fixes mapping that was not correct for the 460sx.
>
> tested on the 460sx eiger and custom board
Hi Ayman.
Just a few comments.
<snip>
> +static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
> +{
> + void __iomem *mbase;
> + int attempt = 50;
> +
> + port->link = 0;
> +
> + mbase = ioremap(port->cfg_space.start + 0x00000000, 0x1000);
Why + 0x00000000 ? ppc4xx_pciex_port_setup_hose() does:
mbase = ioremap(port->cfg_space.start + 0x10000000, 0x1000);
so isn't one of these statements is wrong?
> + if (mbase == NULL) {
> + printk(KERN_ERR "%s: Can't map internal config space !",
> + port->node->full_name);
> + goto done;
> + }
> +
> + while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
> + & 0x00000010))) {
Nitpicking, I think it'd be nice if there was #define for 0x00000010
perhaps: #define PECFG_460SX_DLLSTA_LINKUP 0x00000010
> + attempt--;
> + mdelay(10);
> + }
> + if (attempt)
> + port->link = 1;
> +done:
> + iounmap(mbase);
> +
> +}
> +
> static struct ppc4xx_pciex_hwops ppc460sx_pcie_hwops __initdata = {
> .core_init = ppc460sx_pciex_core_init,
> .port_init_hw = ppc460sx_pciex_init_port_hw,
> .setup_utl = ppc460sx_pciex_init_utl,
> - .check_link = ppc4xx_pciex_check_link_sdr,
> + .check_link = ppc460sx_pciex_check_link,
> };
>
> #endif /* CONFIG_44x */
> @@ -1338,15 +1367,15 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
> if (rc != 0)
> return rc;
>
> - if (ppc4xx_pciex_hwops->check_link)
> - ppc4xx_pciex_hwops->check_link(port);
> -
> /*
> * Initialize mapping: disable all regions and configure
> * CFG and REG regions based on resources in the device tree
> */
> ppc4xx_pciex_port_init_mapping(port);
>
> + if (ppc4xx_pciex_hwops->check_link)
> + ppc4xx_pciex_hwops->check_link(port);
> +
Why move this? You already iorempat the cfg space.
> /*
> * Map UTL
> */
> @@ -1360,13 +1389,23 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
> ppc4xx_pciex_hwops->setup_utl(port);
>
> /*
> - * Check for VC0 active and assert RDY.
> + * Check for VC0 active or PLL Locked and assert RDY.
> */
> if (port->sdr_base) {
> - if (port->link &&
> - ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
> - 1 << 16, 1 << 16, 5000)) {
> - printk(KERN_INFO "PCIE%d: VC0 not active\n", port->index);
> + if (of_device_is_compatible(port->node,
> + "ibm,plb-pciex-460sx")){
> + if (port->link && ppc4xx_pciex_wait_on_sdr(port,
> + PESDRn_RCSSTS,
> + 1 << 12, 1 << 12, 5000)) {
> + printk(KERN_INFO "PCIE%d: PLL not locked\n",
> + port->index);
> + port->link = 0;
> + }
> + } else if (port->link &&
> + ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
> + 1 << 16, 1 << 16, 5000)) {
> + printk(KERN_INFO "PCIE%d: VC0 not active\n",
> + port->index);
> port->link = 0;
> }
>
> @@ -1565,6 +1604,10 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> pcial = RES_TO_U32_LOW(pci_addr);
> sa = (0xffffffffu << ilog2(size)) | 0x1;
>
> + /* Enabled and single region */
> + sa |= (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx")) ?
> + 0x5 : 0x3;
> +
> /* Program register values */
> switch (index) {
> case 0:
> @@ -1573,8 +1616,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAH, lah);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAL, lal);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKH, 0x7fffffff);
> - /* Note that 3 here means enabled | single region */
> - dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa | 3);
> + dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa);
> break;
> case 1:
> out_le32(mbase + PECFG_POM1LAH, pciah);
> @@ -1582,8 +1624,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
> - /* Note that 3 here means enabled | single region */
> - dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
> + dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa);
> break;
> case 2:
> out_le32(mbase + PECFG_POM2LAH, pciah);
> @@ -1591,8 +1632,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAH, lah);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
> - /* Note that 3 here means enabled | IO space !!! */
> - dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
> + dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa);
> break;
> }
I think you really want to check the definitions for OMRs 2 and 3 to verify that this is right.
Yours Tony
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 1:16 ` Tony Breeds
@ 2011-07-14 16:04 ` Ayman El-Khashab
2011-07-14 22:36 ` Benjamin Herrenschmidt
0 siblings, 1 reply; 743+ messages in thread
From: Ayman El-Khashab @ 2011-07-14 16:04 UTC (permalink / raw)
To: Tony Breeds
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev, linux-kernel
Thanks Tony, some comments below.
On Thu, Jul 14, 2011 at 11:16:27AM +1000, Tony Breeds wrote:
>
> > +static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
> > +{
> > + void __iomem *mbase;
> > + int attempt = 50;
> > +
> > + port->link = 0;
> > +
> > + mbase = ioremap(port->cfg_space.start + 0x00000000, 0x1000);
>
> Why + 0x00000000 ? ppc4xx_pciex_port_setup_hose() does:
> mbase = ioremap(port->cfg_space.start + 0x10000000, 0x1000);
> so isn't one of these statements is wrong?
yes, that doesn't look right. I'll verify that and make
sure that it works correctly and resubmit the patch.
>
> > + if (mbase == NULL) {
> > + printk(KERN_ERR "%s: Can't map internal config space !",
> > + port->node->full_name);
> > + goto done;
> > + }
> > +
> > + while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
> > + & 0x00000010))) {
>
> Nitpicking, I think it'd be nice if there was #define for 0x00000010
> perhaps: #define PECFG_460SX_DLLSTA_LINKUP 0x00000010
ok.
> >
> > - if (ppc4xx_pciex_hwops->check_link)
> > - ppc4xx_pciex_hwops->check_link(port);
> > -
> > /*
> > * Initialize mapping: disable all regions and configure
> > * CFG and REG regions based on resources in the device tree
> > */
> > ppc4xx_pciex_port_init_mapping(port);
> >
> > + if (ppc4xx_pciex_hwops->check_link)
> > + ppc4xx_pciex_hwops->check_link(port);
> > +
>
> Why move this? You already iorempat the cfg space.
This was what I was asking about before. The reason that I
swapped the order of the init_mapping and check_link is
because the init_mapping currently sets up the cfgbax
registers. Those setup the base address of the
configuration space on the PLB side of the bus. As far as I
could determine, I cannot access the config space until
those registers are configured. I need to touch the config
space in order to do the check_link b/c the 460sx uses the
extended config space to keep track of the link status. I
looked at init mapping and based on what it did I did not
see any potential adverse effects.
> > out_le32(mbase + PECFG_POM2LAH, pciah);
> > @@ -1591,8 +1632,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAH, lah);
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
> > - /* Note that 3 here means enabled | IO space !!! */
> > - dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
> > + dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa);
> > break;
> > }
>
> I think you really want to check the definitions for OMRs 2 and 3 to verify that this is right.
Thanks, good catch. I'll change the first case block to
include a switch on the 460sx. The first case statement
needs to be | 0x5, while the others need to stay 0x3.
Ayman
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 16:04 ` Ayman El-Khashab
@ 2011-07-14 22:36 ` Benjamin Herrenschmidt
0 siblings, 0 replies; 743+ messages in thread
From: Benjamin Herrenschmidt @ 2011-07-14 22:36 UTC (permalink / raw)
To: Ayman El-Khashab; +Cc: Tony Breeds, Paul Mackerras, linuxppc-dev, linux-kernel
On Thu, 2011-07-14 at 11:04 -0500, Ayman El-Khashab wrote:
> Thanks Tony, some comments below.
>
> On Thu, Jul 14, 2011 at 11:16:27AM +1000, Tony Breeds wrote:
> >
> > > +static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
> > > +{
> > > + void __iomem *mbase;
> > > + int attempt = 50;
> > > +
> > > + port->link = 0;
> > > +
> > > + mbase = ioremap(port->cfg_space.start + 0x00000000, 0x1000);
> >
> > Why + 0x00000000 ? ppc4xx_pciex_port_setup_hose() does:
> > mbase = ioremap(port->cfg_space.start + 0x10000000, 0x1000);
> > so isn't one of these statements is wrong?
>
> yes, that doesn't look right. I'll verify that and make
> sure that it works correctly and resubmit the patch.
The state of that top bit is obscure. I couldn't figure out from docs
whether it's actually useful or not (it doesn't seem to make a
difference on the HW we've played with here).
It's possible that some parts need it to access the RC config space vs
emitting type 1 cycles, and some don't, in which case the address
decoding just wraps and the bit is ignored ?
> > > + if (mbase == NULL) {
> > > + printk(KERN_ERR "%s: Can't map internal config space !",
> > > + port->node->full_name);
> > > + goto done;
> > > + }
> > > +
> > > + while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
> > > + & 0x00000010))) {
> >
> > Nitpicking, I think it'd be nice if there was #define for 0x00000010
> > perhaps: #define PECFG_460SX_DLLSTA_LINKUP 0x00000010
>
> ok.
>
> > >
> > > - if (ppc4xx_pciex_hwops->check_link)
> > > - ppc4xx_pciex_hwops->check_link(port);
> > > -
> > > /*
> > > * Initialize mapping: disable all regions and configure
> > > * CFG and REG regions based on resources in the device tree
> > > */
> > > ppc4xx_pciex_port_init_mapping(port);
> > >
> > > + if (ppc4xx_pciex_hwops->check_link)
> > > + ppc4xx_pciex_hwops->check_link(port);
> > > +
> >
> > Why move this? You already iorempat the cfg space.
>
> This was what I was asking about before. The reason that I
> swapped the order of the init_mapping and check_link is
> because the init_mapping currently sets up the cfgbax
> registers. Those setup the base address of the
> configuration space on the PLB side of the bus. As far as I
> could determine, I cannot access the config space until
> those registers are configured. I need to touch the config
> space in order to do the check_link b/c the 460sx uses the
> extended config space to keep track of the link status. I
> looked at init mapping and based on what it did I did not
> see any potential adverse effects.
I think it makes sense to setup mappings before checking the link. There
may be a couple of mechanical issues in the code with this, I haven't
looked, but I agree in principle.
> > > out_le32(mbase + PECFG_POM2LAH, pciah);
> > > @@ -1591,8 +1632,7 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> > > dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAH, lah);
> > > dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
> > > dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
> > > - /* Note that 3 here means enabled | IO space !!! */
> > > - dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
> > > + dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa);
> > > break;
> > > }
> >
> > I think you really want to check the definitions for OMRs 2 and 3 to verify that this is right.
>
> Thanks, good catch. I'll change the first case block to
> include a switch on the 460sx. The first case statement
> needs to be | 0x5, while the others need to stay 0x3.
Please make it constants using #define or something... those bits tend
to subtly change between ASICs too I noticed.
Cheers,
Ben.
> Ayman
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
^ permalink raw reply [flat|nested] 743+ messages in thread
* [v2 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 0:33 ` [PATCH 1/1] " Ayman El-Khashab
2011-07-14 1:16 ` Tony Breeds
@ 2011-07-15 16:40 ` Ayman Elkhashab
2011-07-18 4:01 ` Tony Breeds
2011-07-20 13:02 ` [v3 PATCH 0/1] " Ayman Elkhashab
2 siblings, 1 reply; 743+ messages in thread
From: Ayman Elkhashab @ 2011-07-15 16:40 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Tony Breeds,
linuxppc-dev, linux-kernel
Cc: Ayman El-Khashab
From: Ayman El-Khashab <ayman@elkhashab.com>
Adds a register to the config space for the 460sx. Changes the vc0
detect to a pll detect. maps configuration space to test the link
status. changes the setup to enable gen2 devices to operate at gen2
speeds. fixes mapping that was not correct for the 460sx. added
bit definitions for the OMRxMSKL registers
tested on the 460sx eiger and custom board
Signed-off-by: Ayman El-Khashab <ayman@elkhashab.com>
---
arch/powerpc/sysdev/ppc4xx_pci.c | 89 ++++++++++++++++++++++++++++++-------
arch/powerpc/sysdev/ppc4xx_pci.h | 12 +++++
2 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.c b/arch/powerpc/sysdev/ppc4xx_pci.c
index ad330fe..eeeeb12 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.c
+++ b/arch/powerpc/sysdev/ppc4xx_pci.c
@@ -1092,6 +1092,10 @@ static int __init ppc460sx_pciex_core_init(struct device_node *np)
mtdcri(SDR0, PESDR1_460SX_HSSSLEW, 0xFFFF0000);
mtdcri(SDR0, PESDR2_460SX_HSSSLEW, 0xFFFF0000);
+ /* Set HSS PRBS enabled */
+ mtdcri(SDR0, PESDR0_460SX_HSSCTLSET, 0x00001130);
+ mtdcri(SDR0, PESDR2_460SX_HSSCTLSET, 0x00001130);
+
udelay(100);
/* De-assert PLLRESET */
@@ -1132,9 +1136,6 @@ static int ppc460sx_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
dcri_clrset(SDR0, port->sdr_base + PESDRn_UTLSET2,
0, 0x01000000);
- /*Gen-1*/
- mtdcri(SDR0, port->sdr_base + PESDRn_460SX_RCEI, 0x08000000);
-
dcri_clrset(SDR0, port->sdr_base + PESDRn_RCSSET,
(PESDRx_RCSSET_RSTGU | PESDRx_RCSSET_RSTDL),
PESDRx_RCSSET_RSTPYN);
@@ -1148,14 +1149,42 @@ static int ppc460sx_pciex_init_utl(struct ppc4xx_pciex_port *port)
{
/* Max 128 Bytes */
out_be32 (port->utl_base + PEUTL_PBBSZ, 0x00000000);
+ /* Assert VRB and TXE - per datasheet turn off addr validation */
+ out_be32(port->utl_base + PEUTL_PCTL, 0x80800000);
return 0;
}
+static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
+{
+ void __iomem *mbase;
+ int attempt = 50;
+
+ port->link = 0;
+
+ mbase = ioremap(port->cfg_space.start + 0x10000000, 0x1000);
+ if (mbase == NULL) {
+ printk(KERN_ERR "%s: Can't map internal config space !",
+ port->node->full_name);
+ goto done;
+ }
+
+ while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
+ & PECFG_460SX_DLLSTA_LINKUP))) {
+ attempt--;
+ mdelay(10);
+ }
+ if (attempt)
+ port->link = 1;
+done:
+ iounmap(mbase);
+
+}
+
static struct ppc4xx_pciex_hwops ppc460sx_pcie_hwops __initdata = {
.core_init = ppc460sx_pciex_core_init,
.port_init_hw = ppc460sx_pciex_init_port_hw,
.setup_utl = ppc460sx_pciex_init_utl,
- .check_link = ppc4xx_pciex_check_link_sdr,
+ .check_link = ppc460sx_pciex_check_link,
};
#endif /* CONFIG_44x */
@@ -1338,15 +1367,15 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
if (rc != 0)
return rc;
- if (ppc4xx_pciex_hwops->check_link)
- ppc4xx_pciex_hwops->check_link(port);
-
/*
* Initialize mapping: disable all regions and configure
* CFG and REG regions based on resources in the device tree
*/
ppc4xx_pciex_port_init_mapping(port);
+ if (ppc4xx_pciex_hwops->check_link)
+ ppc4xx_pciex_hwops->check_link(port);
+
/*
* Map UTL
*/
@@ -1360,13 +1389,23 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
ppc4xx_pciex_hwops->setup_utl(port);
/*
- * Check for VC0 active and assert RDY.
+ * Check for VC0 active or PLL Locked and assert RDY.
*/
if (port->sdr_base) {
- if (port->link &&
- ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
- 1 << 16, 1 << 16, 5000)) {
- printk(KERN_INFO "PCIE%d: VC0 not active\n", port->index);
+ if (of_device_is_compatible(port->node,
+ "ibm,plb-pciex-460sx")){
+ if (port->link && ppc4xx_pciex_wait_on_sdr(port,
+ PESDRn_RCSSTS,
+ 1 << 12, 1 << 12, 5000)) {
+ printk(KERN_INFO "PCIE%d: PLL not locked\n",
+ port->index);
+ port->link = 0;
+ }
+ } else if (port->link &&
+ ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
+ 1 << 16, 1 << 16, 5000)) {
+ printk(KERN_INFO "PCIE%d: VC0 not active\n",
+ port->index);
port->link = 0;
}
@@ -1573,8 +1612,15 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa | 3);
+ /*Enabled and single region */
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL,
+ sa | DCRO_PEGPL_460SX_OMR1MSKL_UOT
+ | DCRO_PEGPL_OMRxMSKL_VAL);
+ else
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL,
+ sa | DCRO_PEGPL_OMR1MSKL_UOT
+ | DCRO_PEGPL_OMRxMSKL_VAL);
break;
case 1:
out_le32(mbase + PECFG_POM1LAH, pciah);
@@ -1582,8 +1628,8 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL,
+ sa | DCRO_PEGPL_OMRxMSKL_VAL);
break;
case 2:
out_le32(mbase + PECFG_POM2LAH, pciah);
@@ -1592,7 +1638,9 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
/* Note that 3 here means enabled | IO space !!! */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL,
+ sa | DCRO_PEGPL_OMR3MSKL_IO
+ | DCRO_PEGPL_OMRxMSKL_VAL);
break;
}
@@ -1693,6 +1741,9 @@ static void __init ppc4xx_configure_pciex_PIMs(struct ppc4xx_pciex_port *port,
if (res->flags & IORESOURCE_PREFETCH)
sa |= 0x8;
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ sa |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+
out_le32(mbase + PECFG_BAR0HMPA, RES_TO_U32_HIGH(sa));
out_le32(mbase + PECFG_BAR0LMPA, RES_TO_U32_LOW(sa));
@@ -1854,6 +1905,10 @@ static void __init ppc4xx_pciex_port_setup_hose(struct ppc4xx_pciex_port *port)
}
out_le16(mbase + 0x202, val);
+ /* Enable Bus master, memory, and io space */
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ out_le16(mbase + 0x204, 0x7);
+
if (!port->endpoint) {
/* Set Class Code to PCI-PCI bridge and Revision Id to 1 */
out_le32(mbase + 0x208, 0x06040001);
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.h b/arch/powerpc/sysdev/ppc4xx_pci.h
index 56d9e5d..61b3659 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.h
+++ b/arch/powerpc/sysdev/ppc4xx_pci.h
@@ -464,6 +464,18 @@
#define PECFG_POM2LAL 0x390
#define PECFG_POM2LAH 0x394
+/* 460sx only */
+#define PECFG_460SX_DLLSTA 0x3f8
+
+/* 460sx Bit Mappings */
+#define PECFG_460SX_DLLSTA_LINKUP 0x00000010
+#define DCRO_PEGPL_460SX_OMR1MSKL_UOT 0x00000004
+
+/* PEGPL Bit Mappings */
+#define DCRO_PEGPL_OMRxMSKL_VAL 0x00000001
+#define DCRO_PEGPL_OMR1MSKL_UOT 0x00000002
+#define DCRO_PEGPL_OMR3MSKL_IO 0x00000002
+
/* SDR Bit Mappings */
#define PESDRx_RCSSET_HLDPLB 0x10000000
#define PESDRx_RCSSET_RSTGU 0x01000000
--
1.7.4.3
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [v2 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-15 16:40 ` [v2 PATCH " Ayman Elkhashab
@ 2011-07-18 4:01 ` Tony Breeds
2011-07-18 13:31 ` Ayman El-Khashab
0 siblings, 1 reply; 743+ messages in thread
From: Tony Breeds @ 2011-07-18 4:01 UTC (permalink / raw)
To: Ayman Elkhashab
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev,
linux-kernel, Ayman El-Khashab
On Fri, Jul 15, 2011 at 11:40:27AM -0500, Ayman Elkhashab wrote:
> @@ -1582,8 +1628,8 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
> dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
> - /* Note that 3 here means enabled | single region */
> - dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
> + dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL,
> + sa | DCRO_PEGPL_OMRxMSKL_VAL);
Didn't you just change "sa | 3" to "sa | 1" ?
<snip>
> index 56d9e5d..61b3659 100644
> --- a/arch/powerpc/sysdev/ppc4xx_pci.h
> +++ b/arch/powerpc/sysdev/ppc4xx_pci.h
> @@ -464,6 +464,18 @@
> #define PECFG_POM2LAL 0x390
> #define PECFG_POM2LAH 0x394
>
> +/* 460sx only */
> +#define PECFG_460SX_DLLSTA 0x3f8
> +
> +/* 460sx Bit Mappings */
> +#define PECFG_460SX_DLLSTA_LINKUP 0x00000010
> +#define DCRO_PEGPL_460SX_OMR1MSKL_UOT 0x00000004
> +
> +/* PEGPL Bit Mappings */
> +#define DCRO_PEGPL_OMRxMSKL_VAL 0x00000001
> +#define DCRO_PEGPL_OMR1MSKL_UOT 0x00000002
> +#define DCRO_PEGPL_OMR3MSKL_IO 0x00000002
> +
> /* SDR Bit Mappings */
> #define PESDRx_RCSSET_HLDPLB 0x10000000
> #define PESDRx_RCSSET_RSTGU 0x01000000
Yours Tony
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [v2 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-18 4:01 ` Tony Breeds
@ 2011-07-18 13:31 ` Ayman El-Khashab
2011-07-19 1:23 ` Tony Breeds
0 siblings, 1 reply; 743+ messages in thread
From: Ayman El-Khashab @ 2011-07-18 13:31 UTC (permalink / raw)
To: Tony Breeds
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev, linux-kernel
On Mon, Jul 18, 2011 at 02:01:15PM +1000, Tony Breeds wrote:
> On Fri, Jul 15, 2011 at 11:40:27AM -0500, Ayman Elkhashab wrote:
>
> > @@ -1582,8 +1628,8 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
> > dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
> > - /* Note that 3 here means enabled | single region */
> > - dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
> > + dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL,
> > + sa | DCRO_PEGPL_OMRxMSKL_VAL);
>
> Didn't you just change "sa | 3" to "sa | 1" ?
>
Yes, but I think that is correct for it to be "1". The data
sheets for these parts that I checked had bit 1 marked as
reserved. Only OMR1MSKL and OMR3MSKL had extra definitions
such as the _IO and _UOT. The parts I checked which were
the sheets for the EX and SX (which cover another 6 or 7
parts) all had it with just a single bit defined on that
register.
Ayman
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [v2 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-18 13:31 ` Ayman El-Khashab
@ 2011-07-19 1:23 ` Tony Breeds
0 siblings, 0 replies; 743+ messages in thread
From: Tony Breeds @ 2011-07-19 1:23 UTC (permalink / raw)
To: Ayman El-Khashab
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev, linux-kernel
On Mon, Jul 18, 2011 at 08:31:01AM -0500, Ayman El-Khashab wrote:
> Yes, but I think that is correct for it to be "1". The data
> sheets for these parts that I checked had bit 1 marked as
> reserved. Only OMR1MSKL and OMR3MSKL had extra definitions
> such as the _IO and _UOT. The parts I checked which were
> the sheets for the EX and SX (which cover another 6 or 7
> parts) all had it with just a single bit defined on that
> register.
Ahh okay. I kind of think that this may need to be a seperate change. At the
very least it needs to be explicitly mentioned in the change log.
Yours Tony
^ permalink raw reply [flat|nested] 743+ messages in thread
* [v3 PATCH 0/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-14 0:33 ` [PATCH 1/1] " Ayman El-Khashab
2011-07-14 1:16 ` Tony Breeds
2011-07-15 16:40 ` [v2 PATCH " Ayman Elkhashab
@ 2011-07-20 13:02 ` Ayman Elkhashab
2011-07-20 13:02 ` [v3 PATCH 1/1] " Ayman Elkhashab
2 siblings, 1 reply; 743+ messages in thread
From: Ayman Elkhashab @ 2011-07-20 13:02 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Tony Breeds,
linuxppc-dev, linux-kernel
Cc: Ayman El-Khashab
From: Ayman El-Khashab <ayman@elkhashab.com>
Changes from v1->v2
Added definitions for the bits in the OMRxMSKL registers
Refactored the setting of the OMRxMSKL registers for the 460SX
Added bit defines for the PECFG_460SX_DLLSTA register
Changes from v2->v3
Fixed commit message to be more clear as to what was done
Ayman El-Khashab (1):
powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
arch/powerpc/sysdev/ppc4xx_pci.c | 89 ++++++++++++++++++++++++++++++-------
arch/powerpc/sysdev/ppc4xx_pci.h | 12 +++++
2 files changed, 84 insertions(+), 17 deletions(-)
--
1.7.4.3
^ permalink raw reply [flat|nested] 743+ messages in thread
* [v3 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-20 13:02 ` [v3 PATCH 0/1] " Ayman Elkhashab
@ 2011-07-20 13:02 ` Ayman Elkhashab
2011-07-21 0:59 ` Tony Breeds
0 siblings, 1 reply; 743+ messages in thread
From: Ayman Elkhashab @ 2011-07-20 13:02 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Tony Breeds,
linuxppc-dev, linux-kernel
Cc: Ayman El-Khashab
From: Ayman El-Khashab <ayman@elkhashab.com>
Adds a register to the config space for the 460sx. Changes the vc0
detect to a pll detect. maps configuration space to test the link
status. changes the setup to enable gen2 devices to operate at gen2
speeds. fixes mapping that was not correct for the 460sx. added
bit definitions for the OMRxMSKL registers. Removed reserved bit
that was set incorrectly in the OMR2MSKL register.
tested on the 460sx eiger and custom board
Signed-off-by: Ayman El-Khashab <ayman@elkhashab.com>
---
arch/powerpc/sysdev/ppc4xx_pci.c | 89 ++++++++++++++++++++++++++++++-------
arch/powerpc/sysdev/ppc4xx_pci.h | 12 +++++
2 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.c b/arch/powerpc/sysdev/ppc4xx_pci.c
index ad330fe..eeeeb12 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.c
+++ b/arch/powerpc/sysdev/ppc4xx_pci.c
@@ -1092,6 +1092,10 @@ static int __init ppc460sx_pciex_core_init(struct device_node *np)
mtdcri(SDR0, PESDR1_460SX_HSSSLEW, 0xFFFF0000);
mtdcri(SDR0, PESDR2_460SX_HSSSLEW, 0xFFFF0000);
+ /* Set HSS PRBS enabled */
+ mtdcri(SDR0, PESDR0_460SX_HSSCTLSET, 0x00001130);
+ mtdcri(SDR0, PESDR2_460SX_HSSCTLSET, 0x00001130);
+
udelay(100);
/* De-assert PLLRESET */
@@ -1132,9 +1136,6 @@ static int ppc460sx_pciex_init_port_hw(struct ppc4xx_pciex_port *port)
dcri_clrset(SDR0, port->sdr_base + PESDRn_UTLSET2,
0, 0x01000000);
- /*Gen-1*/
- mtdcri(SDR0, port->sdr_base + PESDRn_460SX_RCEI, 0x08000000);
-
dcri_clrset(SDR0, port->sdr_base + PESDRn_RCSSET,
(PESDRx_RCSSET_RSTGU | PESDRx_RCSSET_RSTDL),
PESDRx_RCSSET_RSTPYN);
@@ -1148,14 +1149,42 @@ static int ppc460sx_pciex_init_utl(struct ppc4xx_pciex_port *port)
{
/* Max 128 Bytes */
out_be32 (port->utl_base + PEUTL_PBBSZ, 0x00000000);
+ /* Assert VRB and TXE - per datasheet turn off addr validation */
+ out_be32(port->utl_base + PEUTL_PCTL, 0x80800000);
return 0;
}
+static void __init ppc460sx_pciex_check_link(struct ppc4xx_pciex_port *port)
+{
+ void __iomem *mbase;
+ int attempt = 50;
+
+ port->link = 0;
+
+ mbase = ioremap(port->cfg_space.start + 0x10000000, 0x1000);
+ if (mbase == NULL) {
+ printk(KERN_ERR "%s: Can't map internal config space !",
+ port->node->full_name);
+ goto done;
+ }
+
+ while (attempt && (0 == (in_le32(mbase + PECFG_460SX_DLLSTA)
+ & PECFG_460SX_DLLSTA_LINKUP))) {
+ attempt--;
+ mdelay(10);
+ }
+ if (attempt)
+ port->link = 1;
+done:
+ iounmap(mbase);
+
+}
+
static struct ppc4xx_pciex_hwops ppc460sx_pcie_hwops __initdata = {
.core_init = ppc460sx_pciex_core_init,
.port_init_hw = ppc460sx_pciex_init_port_hw,
.setup_utl = ppc460sx_pciex_init_utl,
- .check_link = ppc4xx_pciex_check_link_sdr,
+ .check_link = ppc460sx_pciex_check_link,
};
#endif /* CONFIG_44x */
@@ -1338,15 +1367,15 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
if (rc != 0)
return rc;
- if (ppc4xx_pciex_hwops->check_link)
- ppc4xx_pciex_hwops->check_link(port);
-
/*
* Initialize mapping: disable all regions and configure
* CFG and REG regions based on resources in the device tree
*/
ppc4xx_pciex_port_init_mapping(port);
+ if (ppc4xx_pciex_hwops->check_link)
+ ppc4xx_pciex_hwops->check_link(port);
+
/*
* Map UTL
*/
@@ -1360,13 +1389,23 @@ static int __init ppc4xx_pciex_port_init(struct ppc4xx_pciex_port *port)
ppc4xx_pciex_hwops->setup_utl(port);
/*
- * Check for VC0 active and assert RDY.
+ * Check for VC0 active or PLL Locked and assert RDY.
*/
if (port->sdr_base) {
- if (port->link &&
- ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
- 1 << 16, 1 << 16, 5000)) {
- printk(KERN_INFO "PCIE%d: VC0 not active\n", port->index);
+ if (of_device_is_compatible(port->node,
+ "ibm,plb-pciex-460sx")){
+ if (port->link && ppc4xx_pciex_wait_on_sdr(port,
+ PESDRn_RCSSTS,
+ 1 << 12, 1 << 12, 5000)) {
+ printk(KERN_INFO "PCIE%d: PLL not locked\n",
+ port->index);
+ port->link = 0;
+ }
+ } else if (port->link &&
+ ppc4xx_pciex_wait_on_sdr(port, PESDRn_RCSSTS,
+ 1 << 16, 1 << 16, 5000)) {
+ printk(KERN_INFO "PCIE%d: VC0 not active\n",
+ port->index);
port->link = 0;
}
@@ -1573,8 +1612,15 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL, sa | 3);
+ /*Enabled and single region */
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL,
+ sa | DCRO_PEGPL_460SX_OMR1MSKL_UOT
+ | DCRO_PEGPL_OMRxMSKL_VAL);
+ else
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR1MSKL,
+ sa | DCRO_PEGPL_OMR1MSKL_UOT
+ | DCRO_PEGPL_OMRxMSKL_VAL);
break;
case 1:
out_le32(mbase + PECFG_POM1LAH, pciah);
@@ -1582,8 +1628,8 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAH, lah);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKH, 0x7fffffff);
- /* Note that 3 here means enabled | single region */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR2MSKL,
+ sa | DCRO_PEGPL_OMRxMSKL_VAL);
break;
case 2:
out_le32(mbase + PECFG_POM2LAH, pciah);
@@ -1592,7 +1638,9 @@ static int __init ppc4xx_setup_one_pciex_POM(struct ppc4xx_pciex_port *port,
dcr_write(port->dcrs, DCRO_PEGPL_OMR3BAL, lal);
dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKH, 0x7fffffff);
/* Note that 3 here means enabled | IO space !!! */
- dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL, sa | 3);
+ dcr_write(port->dcrs, DCRO_PEGPL_OMR3MSKL,
+ sa | DCRO_PEGPL_OMR3MSKL_IO
+ | DCRO_PEGPL_OMRxMSKL_VAL);
break;
}
@@ -1693,6 +1741,9 @@ static void __init ppc4xx_configure_pciex_PIMs(struct ppc4xx_pciex_port *port,
if (res->flags & IORESOURCE_PREFETCH)
sa |= 0x8;
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ sa |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+
out_le32(mbase + PECFG_BAR0HMPA, RES_TO_U32_HIGH(sa));
out_le32(mbase + PECFG_BAR0LMPA, RES_TO_U32_LOW(sa));
@@ -1854,6 +1905,10 @@ static void __init ppc4xx_pciex_port_setup_hose(struct ppc4xx_pciex_port *port)
}
out_le16(mbase + 0x202, val);
+ /* Enable Bus master, memory, and io space */
+ if (of_device_is_compatible(port->node, "ibm,plb-pciex-460sx"))
+ out_le16(mbase + 0x204, 0x7);
+
if (!port->endpoint) {
/* Set Class Code to PCI-PCI bridge and Revision Id to 1 */
out_le32(mbase + 0x208, 0x06040001);
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.h b/arch/powerpc/sysdev/ppc4xx_pci.h
index 56d9e5d..61b3659 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.h
+++ b/arch/powerpc/sysdev/ppc4xx_pci.h
@@ -464,6 +464,18 @@
#define PECFG_POM2LAL 0x390
#define PECFG_POM2LAH 0x394
+/* 460sx only */
+#define PECFG_460SX_DLLSTA 0x3f8
+
+/* 460sx Bit Mappings */
+#define PECFG_460SX_DLLSTA_LINKUP 0x00000010
+#define DCRO_PEGPL_460SX_OMR1MSKL_UOT 0x00000004
+
+/* PEGPL Bit Mappings */
+#define DCRO_PEGPL_OMRxMSKL_VAL 0x00000001
+#define DCRO_PEGPL_OMR1MSKL_UOT 0x00000002
+#define DCRO_PEGPL_OMR3MSKL_IO 0x00000002
+
/* SDR Bit Mappings */
#define PESDRx_RCSSET_HLDPLB 0x10000000
#define PESDRx_RCSSET_RSTGU 0x01000000
--
1.7.4.3
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [v3 PATCH 1/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx
2011-07-20 13:02 ` [v3 PATCH 1/1] " Ayman Elkhashab
@ 2011-07-21 0:59 ` Tony Breeds
0 siblings, 0 replies; 743+ messages in thread
From: Tony Breeds @ 2011-07-21 0:59 UTC (permalink / raw)
To: Ayman Elkhashab
Cc: Benjamin Herrenschmidt, Paul Mackerras, linuxppc-dev,
linux-kernel, Ayman El-Khashab
On Wed, Jul 20, 2011 at 08:02:29AM -0500, Ayman Elkhashab wrote:
> From: Ayman El-Khashab <ayman@elkhashab.com>
>
> Adds a register to the config space for the 460sx. Changes the vc0
> detect to a pll detect. maps configuration space to test the link
> status. changes the setup to enable gen2 devices to operate at gen2
> speeds. fixes mapping that was not correct for the 460sx. added
> bit definitions for the OMRxMSKL registers. Removed reserved bit
> that was set incorrectly in the OMR2MSKL register.
FWIW Looks good to me.
Yours Tony
^ permalink raw reply [flat|nested] 743+ messages in thread
* high speed (921.6 kbit/s).
[not found] <y>
` (23 preceding siblings ...)
2011-07-14 0:33 ` [PATCH 0/1] powerpc/4xx: enable and fix pcie gen1/gen2 on the 460sx Ayman El-Khashab
@ 2011-08-30 11:53 ` marcus.folkesson
2011-08-30 11:53 ` [PATCH] serial: pxa: work around for errata #20 marcus.folkesson
2011-11-08 14:59 ` [PATCH] MIPS: Kernel hangs occasionally during boot Al Cooper
` (135 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: marcus.folkesson @ 2011-08-30 11:53 UTC (permalink / raw)
To: Alan Cox, linux-serial; +Cc: linux-kernel
After this work around was implemented the system seems to behave good.
In fact, I don't really see why the system should hang since there is a
max counter, so the loop cannot run forever.
The patch is tested on kernel version 2.6.32, but the driver is allmost
the same.
Please take a look.
Feedback is very much appreciated
>From Marcus Folkesson <marcus.folkesson@combitech.se> # This line is ignored.
From: Marcus Folkesson <marcus.folkesson@combitech.se>
Subject: [PATCH] serial: pxa: a brief description
In-Reply-To: marcus.folkesson@gmail.com
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] serial: pxa: work around for errata #20
2011-08-30 11:53 ` high speed (921.6 kbit/s) marcus.folkesson
@ 2011-08-30 11:53 ` marcus.folkesson
0 siblings, 0 replies; 743+ messages in thread
From: marcus.folkesson @ 2011-08-30 11:53 UTC (permalink / raw)
To: Alan Cox, linux-serial; +Cc: linux-kernel, Marcus Folkesson
From: Marcus Folkesson <marcus.folkesson@gmail.com>
Errata E20: UART: Character Timeout interrupt remains set under certain
software conditions.
Implication: The software servicing the UART can be trapped in an infinite loop.
Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
drivers/tty/serial/pxa.c | 20 ++++++++++++++++++++
1 files changed, 20 insertions(+), 0 deletions(-)
diff --git a/drivers/tty/serial/pxa.c b/drivers/tty/serial/pxa.c
index 531931c..5c8e3bb 100644
--- a/drivers/tty/serial/pxa.c
+++ b/drivers/tty/serial/pxa.c
@@ -100,6 +100,16 @@ static inline void receive_chars(struct uart_pxa_port *up, int *status)
int max_count = 256;
do {
+ /* work around Errata #20 according to
+ * Intel(R) PXA27x Processor Family
+ * Specification Update (May 2005)
+ *
+ * Step 2
+ * Disable the Reciever Time Out Interrupt via IER[RTOEI]
+ */
+ up->ier &= ~UART_IER_RTOIE;
+ serial_out(up, UART_IER, up->ier);
+
ch = serial_in(up, UART_RX);
flag = TTY_NORMAL;
up->port.icount.rx++;
@@ -156,6 +166,16 @@ static inline void receive_chars(struct uart_pxa_port *up, int *status)
*status = serial_in(up, UART_LSR);
} while ((*status & UART_LSR_DR) && (max_count-- > 0));
tty_flip_buffer_push(tty);
+
+ /* work around Errata #20 according to
+ * Intel(R) PXA27x Processor Family
+ * Specification Update (May 2005)
+ *
+ * Step 6:
+ * No more data in FIFO: Re-enable RTO interrupt via IER[RTOIE]
+ */
+ up->ier |= UART_IER_RTOIE;
+ serial_out(up, UART_IER, up->ier);
}
static void transmit_chars(struct uart_pxa_port *up)
--
1.7.4.1
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] MIPS: Kernel hangs occasionally during boot.
[not found] <y>
` (24 preceding siblings ...)
2011-08-30 11:53 ` high speed (921.6 kbit/s) marcus.folkesson
@ 2011-11-08 14:59 ` Al Cooper
2011-11-08 17:55 ` Ralf Baechle
2011-11-10 1:07 ` [PATCH] spi: QUP based bus driver for Qualcomm MSM chipsets Harini Jayaraman
` (134 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Al Cooper @ 2011-11-08 14:59 UTC (permalink / raw)
To: ralf, linux-mips, linux-kernel; +Cc: Al Cooper
The Kernel hangs occasionally during boot after
"Calibrating delay loop..". This is caused by the
c0_compare_int_usable() routine in cevt-r4k.c returning false which
causes the system to disable the timer and hang later. The false
return happens because the routine is using a series of four calls to
irq_disable_hazard() as a delay while it waits for the timer changes
to propagate to the cp0 cause register. On newer MIPS cores, like the 74K,
the series of irq_disable_hazard() calls turn into ehb instructions and
can take as little as a few clock ticks for all 4 instructions. This
is not enough of a delay, so the routine thinks the timer is not working.
This fix uses up to a max number of cycle counter ticks for the delay
and uses back_to_back_c0_hazard() instead of irq_disable_hazard() to
handle the hazard condition between cp0 writes and cp0 reads.
Signed-off-by: Al Cooper <alcooperx@gmail.com>
---
arch/mips/kernel/cevt-r4k.c | 38 +++++++++++++++++++-------------------
1 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/arch/mips/kernel/cevt-r4k.c b/arch/mips/kernel/cevt-r4k.c
index 98c5a97..e2d8e19 100644
--- a/arch/mips/kernel/cevt-r4k.c
+++ b/arch/mips/kernel/cevt-r4k.c
@@ -103,19 +103,10 @@ static int c0_compare_int_pending(void)
/*
* Compare interrupt can be routed and latched outside the core,
- * so a single execution hazard barrier may not be enough to give
- * it time to clear as seen in the Cause register. 4 time the
- * pipeline depth seems reasonably conservative, and empirically
- * works better in configurations with high CPU/bus clock ratios.
+ * so wait up to worst case number of cycle counter ticks for timer interrupt
+ * changes to propagate to the cause register.
*/
-
-#define compare_change_hazard() \
- do { \
- irq_disable_hazard(); \
- irq_disable_hazard(); \
- irq_disable_hazard(); \
- irq_disable_hazard(); \
- } while (0)
+#define COMPARE_INT_SEEN_TICKS 50
int c0_compare_int_usable(void)
{
@@ -126,8 +117,12 @@ int c0_compare_int_usable(void)
* IP7 already pending? Try to clear it by acking the timer.
*/
if (c0_compare_int_pending()) {
- write_c0_compare(read_c0_count());
- compare_change_hazard();
+ cnt = read_c0_count();
+ write_c0_compare(cnt);
+ back_to_back_c0_hazard();
+ while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
+ if (!c0_compare_int_pending())
+ break;
if (c0_compare_int_pending())
return 0;
}
@@ -136,7 +131,7 @@ int c0_compare_int_usable(void)
cnt = read_c0_count();
cnt += delta;
write_c0_compare(cnt);
- compare_change_hazard();
+ back_to_back_c0_hazard();
if ((int)(read_c0_count() - cnt) < 0)
break;
/* increase delta if the timer was already expired */
@@ -145,12 +140,17 @@ int c0_compare_int_usable(void)
while ((int)(read_c0_count() - cnt) <= 0)
; /* Wait for expiry */
- compare_change_hazard();
+ while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
+ if (c0_compare_int_pending())
+ break;
if (!c0_compare_int_pending())
return 0;
-
- write_c0_compare(read_c0_count());
- compare_change_hazard();
+ cnt = read_c0_count();
+ write_c0_compare(cnt);
+ back_to_back_c0_hazard();
+ while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
+ if (!c0_compare_int_pending())
+ break;
if (c0_compare_int_pending())
return 0;
--
1.7.6
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] MIPS: Kernel hangs occasionally during boot.
2011-11-08 14:59 ` [PATCH] MIPS: Kernel hangs occasionally during boot Al Cooper
@ 2011-11-08 17:55 ` Ralf Baechle
2011-11-09 7:40 ` Gleb O. Raiko
0 siblings, 1 reply; 743+ messages in thread
From: Ralf Baechle @ 2011-11-08 17:55 UTC (permalink / raw)
To: Al Cooper; +Cc: linux-mips, linux-kernel
On Tue, Nov 08, 2011 at 09:59:01AM -0500, Al Cooper wrote:
> arch/mips/kernel/cevt-r4k.c | 38 +++++++++++++++++++-------------------
> 1 files changed, 19 insertions(+), 19 deletions(-)
>
> diff --git a/arch/mips/kernel/cevt-r4k.c b/arch/mips/kernel/cevt-r4k.c
> index 98c5a97..e2d8e19 100644
> --- a/arch/mips/kernel/cevt-r4k.c
> +++ b/arch/mips/kernel/cevt-r4k.c
> @@ -103,19 +103,10 @@ static int c0_compare_int_pending(void)
>
> /*
> * Compare interrupt can be routed and latched outside the core,
> - * so a single execution hazard barrier may not be enough to give
> - * it time to clear as seen in the Cause register. 4 time the
> - * pipeline depth seems reasonably conservative, and empirically
> - * works better in configurations with high CPU/bus clock ratios.
> + * so wait up to worst case number of cycle counter ticks for timer interrupt
> + * changes to propagate to the cause register.
> */
> -
> -#define compare_change_hazard() \
> - do { \
> - irq_disable_hazard(); \
> - irq_disable_hazard(); \
> - irq_disable_hazard(); \
> - irq_disable_hazard(); \
> - } while (0)
> +#define COMPARE_INT_SEEN_TICKS 50
>
> int c0_compare_int_usable(void)
> {
> @@ -126,8 +117,12 @@ int c0_compare_int_usable(void)
> * IP7 already pending? Try to clear it by acking the timer.
> */
> if (c0_compare_int_pending()) {
> - write_c0_compare(read_c0_count());
> - compare_change_hazard();
> + cnt = read_c0_count();
> + write_c0_compare(cnt);
> + back_to_back_c0_hazard();
back_to_back_c0_hazard is to separate cp0 writes from subsequent reads from
the same cp0 register. So I think no back_to_back_c0_hazard() is needed
here.
> + while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
> + if (!c0_compare_int_pending())
> + break;
> if (c0_compare_int_pending())
> return 0;
> }
> @@ -136,7 +131,7 @@ int c0_compare_int_usable(void)
> cnt = read_c0_count();
> cnt += delta;
> write_c0_compare(cnt);
> - compare_change_hazard();
> + back_to_back_c0_hazard();
Same comment as above.
> if ((int)(read_c0_count() - cnt) < 0)
> break;
> /* increase delta if the timer was already expired */
> @@ -145,12 +140,17 @@ int c0_compare_int_usable(void)
> while ((int)(read_c0_count() - cnt) <= 0)
> ; /* Wait for expiry */
>
> - compare_change_hazard();
> + while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
> + if (c0_compare_int_pending())
> + break;
> if (!c0_compare_int_pending())
> return 0;
> -
> - write_c0_compare(read_c0_count());
> - compare_change_hazard();
> + cnt = read_c0_count();
> + write_c0_compare(cnt);
> + back_to_back_c0_hazard();
> + while (read_c0_count() < (cnt + COMPARE_INT_SEEN_TICKS))
> + if (!c0_compare_int_pending())
> + break;
> if (c0_compare_int_pending())
> return 0;
I've applied your patch but we may need another hazard barrier to
replace back_to_back_c0_hazard().
Ralf
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] MIPS: Kernel hangs occasionally during boot.
2011-11-08 17:55 ` Ralf Baechle
@ 2011-11-09 7:40 ` Gleb O. Raiko
2011-11-09 9:13 ` Ralf Baechle
2011-11-09 10:34 ` Ralf Baechle
0 siblings, 2 replies; 743+ messages in thread
From: Gleb O. Raiko @ 2011-11-09 7:40 UTC (permalink / raw)
To: Ralf Baechle; +Cc: Al Cooper, linux-mips, linux-kernel
On 08.11.2011 21:55, Ralf Baechle wrote:
> but we may need another hazard barrier to
> replace back_to_back_c0_hazard().
Urgently. We need some ticks to wait until counter state machine has
been updated. The amount of ticks may occasionally be the same as in
case of back_to_back_hazard for some cpus. It's completely different for
others, I sure. Original compare_change_hazard waits up to 12 ticks for
r4k. While I don't think this amount should depend on irq_disable_hazard
as old code assumes, we may still need 12 or so ticks for old cpus.
> Author: Al Cooper <alcooperx@gmail.com> Tue Nov 8 09:59:01 2011 -0500
> Comitter: Ralf Baechle <ralf@linux-mips.org> Tue Nov 8 16:52:51 2011 +0000
> Commit: 9121470d99c029493bd55daa11607b398fe9aea3
> Gitweb: http://git.linux-mips.org/g/linux/9121470d
Could you fix those links, it's broken after you moved git repo in?
Regards,
Gleb.
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] MIPS: Kernel hangs occasionally during boot.
2011-11-09 7:40 ` Gleb O. Raiko
@ 2011-11-09 9:13 ` Ralf Baechle
2011-11-09 10:34 ` Ralf Baechle
1 sibling, 0 replies; 743+ messages in thread
From: Ralf Baechle @ 2011-11-09 9:13 UTC (permalink / raw)
To: Gleb O. Raiko; +Cc: Al Cooper, linux-mips, linux-kernel
On Wed, Nov 09, 2011 at 11:40:21AM +0400, Gleb O. Raiko wrote:
> >Author: Al Cooper <alcooperx@gmail.com> Tue Nov 8 09:59:01 2011 -0500
> >Comitter: Ralf Baechle <ralf@linux-mips.org> Tue Nov 8 16:52:51 2011 +0000
> >Commit: 9121470d99c029493bd55daa11607b398fe9aea3
> >Gitweb: http://git.linux-mips.org/g/linux/9121470d
> Could you fix those links, it's broken after you moved git repo in?
Future emails will use URLs that look like
http://git.linux-mips.org/g/ralf/linux/9121470d
with the full repository path sans the .git suffix. There also is a
compat hack to keep the URLs from a few thousand old commit mails working.
Thanks for reporting!
Ralf
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] MIPS: Kernel hangs occasionally during boot.
2011-11-09 7:40 ` Gleb O. Raiko
2011-11-09 9:13 ` Ralf Baechle
@ 2011-11-09 10:34 ` Ralf Baechle
2011-11-09 11:26 ` Gleb O. Raiko
1 sibling, 1 reply; 743+ messages in thread
From: Ralf Baechle @ 2011-11-09 10:34 UTC (permalink / raw)
To: Gleb O. Raiko; +Cc: Al Cooper, linux-mips, linux-kernel
On Wed, Nov 09, 2011 at 11:40:21AM +0400, Gleb O. Raiko wrote:
> On 08.11.2011 21:55, Ralf Baechle wrote:
> >but we may need another hazard barrier to
> >replace back_to_back_c0_hazard().
> Urgently. We need some ticks to wait until counter state machine has
> been updated. The amount of ticks may occasionally be the same as in
> case of back_to_back_hazard for some cpus. It's completely different
> for others, I sure. Original compare_change_hazard waits up to 12
> ticks for r4k. While I don't think this amount should depend on
> irq_disable_hazard as old code assumes, we may still need 12 or so
> ticks for old cpus.
Hmm... Looking at the R4000 manual which generall has the longest
pipeline hazards, mtc0 gets executed at stage 7, interrupts get sampled
at stage 3 meaning there is a (7 - 3 - 1) = 3 cycles hazard. Does
that one statisfy your constraints? Or are additional cycles needed
for a hazard that's generated outside of the CPU's pipeline?
Ralf
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] MIPS: Kernel hangs occasionally during boot.
2011-11-09 10:34 ` Ralf Baechle
@ 2011-11-09 11:26 ` Gleb O. Raiko
0 siblings, 0 replies; 743+ messages in thread
From: Gleb O. Raiko @ 2011-11-09 11:26 UTC (permalink / raw)
To: Ralf Baechle; +Cc: Al Cooper, linux-mips, linux-kernel
On 09.11.2011 14:34, Ralf Baechle wrote:
> Hmm... Looking at the R4000 manual which generall has the longest
> pipeline hazards, mtc0 gets executed at stage 7, interrupts get sampled
> at stage 3 meaning there is a (7 - 3 - 1) = 3 cycles hazard. Does
> that one statisfy your constraints? Or are additional cycles needed
> for a hazard that's generated outside of the CPU's pipeline?
In fact, current back_to_back_hazard is more than enough for cpus I deal
with. I guess, required time to wait equals number of stages between EX
(or RD) and WB stages for modern cpus, because CP0 CAUSE is updated
during WB nowadays.
I suspect, the time required to update internal counter logic for
original r4k might be bigger though. At least old code waited 12 cycles
(4*irq_disable_hazard which is 3 for r4k). Perhaps, we should keep this
code and insert the same amount of nops for old cpus at least.
Regards,
Gleb.
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH] spi: QUP based bus driver for Qualcomm MSM chipsets
[not found] <y>
` (25 preceding siblings ...)
2011-11-08 14:59 ` [PATCH] MIPS: Kernel hangs occasionally during boot Al Cooper
@ 2011-11-10 1:07 ` Harini Jayaraman
2011-11-10 1:34 ` Bryan Huntsman
2011-11-14 21:58 ` [PATCH v2] " Harini Jayaraman
` (133 subsequent siblings)
160 siblings, 1 reply; 743+ messages in thread
From: Harini Jayaraman @ 2011-11-10 1:07 UTC (permalink / raw)
To: grant.likely
Cc: davidb, bryanh, kheitke, linux-arm-msm, linux-arm-kernel,
Harini Jayaraman, spi-devel-general, linux-kernel
This bus driver supports the QUP SPI hardware controller in the Qualcomm
MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
purpose data path engine with input/output FIFOs and an embedded SPI
mini-core. The driver currently supports only FIFO mode.
Signed-off-by: Harini Jayaraman <harinij@codeaurora.org>
---
drivers/spi/Kconfig | 10 +
drivers/spi/Makefile | 1 +
drivers/spi/msm_spi_qsd.h | 436 +++++++++++++
drivers/spi/spi-qsd.c | 1144 +++++++++++++++++++++++++++++++++
include/linux/platform_data/msm_spi.h | 19 +
5 files changed, 1610 insertions(+), 0 deletions(-)
create mode 100644 drivers/spi/msm_spi_qsd.h
create mode 100644 drivers/spi/spi-qsd.c
create mode 100644 include/linux/platform_data/msm_spi.h
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 52e2900..af55a0a 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -280,6 +280,16 @@ config SPI_PXA2XX
config SPI_PXA2XX_PCI
def_bool SPI_PXA2XX && X86_32 && PCI
+config SPI_QUP
+ tristate "Qualcomm MSM SPI QUPe Support"
+ depends on ARCH_MSM
+ help
+ Support for Serial Peripheral Interface for Qualcomm Universal
+ Peripheral.
+
+ This driver can also be built as a module. If so, the module
+ will be called spi-qsd.
+
config SPI_S3C24XX
tristate "Samsung S3C24XX series SPI"
depends on ARCH_S3C2410 && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 61c3261..f426c89 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o
obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
+obj-$(CONFIG_SPI_QUP) += spi-qsd.o
obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
spi-s3c24xx-hw-y := spi-s3c24xx.o
spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o
diff --git a/drivers/spi/msm_spi_qsd.h b/drivers/spi/msm_spi_qsd.h
new file mode 100644
index 0000000..d141d08
--- /dev/null
+++ b/drivers/spi/msm_spi_qsd.h
@@ -0,0 +1,436 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#ifndef _MSM_SPI_QSD_H
+#define _MSM_SPI_QSD_H
+
+#define SPI_DRV_NAME "spi_qsd"
+
+#define QUP_CONFIG 0x0000 /* N & NO_INPUT/NO_OUPUT bits */
+#define QUP_ERROR_FLAGS 0x0308
+#define QUP_ERROR_FLAGS_EN 0x030C
+#define QUP_ERR_MASK 0x3
+#define SPI_OUTPUT_FIFO_WORD_CNT 0x010C
+#define SPI_INPUT_FIFO_WORD_CNT 0x0214
+#define QUP_MX_WRITE_COUNT 0x0150
+#define QUP_MX_WRITE_CNT_CURRENT 0x0154
+
+#define QUP_CONFIG_SPI_MODE 0x0100
+
+#define GSBI_CTRL_REG 0x0
+#define GSBI_SPI_CONFIG 0x30
+
+#define SPI_CONFIG 0x0300
+#define SPI_IO_CONTROL 0x0304
+#define SPI_IO_MODES 0x0008
+#define SPI_SW_RESET 0x000C
+#define SPI_TIME_OUT 0x0010
+#define SPI_TIME_OUT_CURRENT 0x0014
+#define SPI_MX_OUTPUT_COUNT 0x0100
+#define SPI_MX_OUTPUT_CNT_CURRENT 0x0104
+#define SPI_MX_INPUT_COUNT 0x0200
+#define SPI_MX_INPUT_CNT_CURRENT 0x0204
+#define SPI_MX_READ_COUNT 0x0208
+#define SPI_MX_READ_CNT_CURRENT 0x020C
+#define SPI_OPERATIONAL 0x0018
+#define SPI_ERROR_FLAGS 0x001C
+#define SPI_ERROR_FLAGS_EN 0x0020
+#define SPI_DEASSERT_WAIT 0x0310
+#define SPI_OUTPUT_DEBUG 0x0108
+#define SPI_INPUT_DEBUG 0x0210
+#define SPI_TEST_CTRL 0x0024
+#define SPI_OUTPUT_FIFO 0x0110
+#define SPI_INPUT_FIFO 0x0218
+#define SPI_STATE 0x0004
+
+/* SPI_CONFIG fields */
+#define SPI_CFG_INPUT_FIRST 0x00000200
+#define SPI_NO_INPUT 0x00000080
+#define SPI_NO_OUTPUT 0x00000040
+#define SPI_CFG_LOOPBACK 0x00000100
+#define SPI_CFG_N 0x0000001F
+
+/* SPI_IO_CONTROL fields */
+#define SPI_IO_C_CLK_IDLE_HIGH 0x00000400
+#define SPI_IO_C_MX_CS_MODE 0x00000100
+#define SPI_IO_C_CS_N_POLARITY 0x000000F0
+#define SPI_IO_C_CS_N_POLARITY_0 0x00000010
+#define SPI_IO_C_CS_SELECT 0x0000000C
+#define SPI_IO_C_TRISTATE_CS 0x00000002
+#define SPI_IO_C_NO_TRI_STATE 0x00000001
+
+/* SPI_IO_MODES fields */
+#define SPI_IO_M_PACK_EN 0x00008000
+#define SPI_IO_M_UNPACK_EN 0x00004000
+#define SPI_IO_M_INPUT_MODE 0x00003000
+#define SPI_IO_M_OUTPUT_MODE 0x00000C00
+#define SPI_IO_M_INPUT_FIFO_SIZE 0x00000380
+#define SPI_IO_M_INPUT_BLOCK_SIZE 0x00000060
+#define SPI_IO_M_OUTPUT_FIFO_SIZE 0x0000001C
+#define SPI_IO_M_OUTPUT_BLOCK_SIZE 0x00000003
+
+#define INPUT_BLOCK_SZ_SHIFT 5
+#define INPUT_FIFO_SZ_SHIFT 7
+#define OUTPUT_BLOCK_SZ_SHIFT 0
+#define OUTPUT_FIFO_SZ_SHIFT 2
+#define OUTPUT_MODE_SHIFT 10
+#define INPUT_MODE_SHIFT 12
+
+/* SPI_OPERATIONAL fields */
+#define SPI_OP_MAX_INPUT_DONE_FLAG 0x00000800
+#define SPI_OP_MAX_OUTPUT_DONE_FLAG 0x00000400
+#define SPI_OP_INPUT_SERVICE_FLAG 0x00000200
+#define SPI_OP_OUTPUT_SERVICE_FLAG 0x00000100
+#define SPI_OP_INPUT_FIFO_FULL 0x00000080
+#define SPI_OP_OUTPUT_FIFO_FULL 0x00000040
+#define SPI_OP_IP_FIFO_NOT_EMPTY 0x00000020
+#define SPI_OP_OP_FIFO_NOT_EMPTY 0x00000010
+#define SPI_OP_STATE_VALID 0x00000004
+#define SPI_OP_STATE 0x00000003
+#define SPI_OP_STATE_CLEAR_BITS 0x2
+
+/* SPI_ERROR_FLAGS fields */
+#define SPI_ERR_OUTPUT_OVER_RUN_ERR 0x00000020
+#define SPI_ERR_INPUT_UNDER_RUN_ERR 0x00000010
+#define SPI_ERR_OUTPUT_UNDER_RUN_ERR 0x00000008
+#define SPI_ERR_INPUT_OVER_RUN_ERR 0x00000004
+#define SPI_ERR_CLK_OVER_RUN_ERR 0x00000002
+#define SPI_ERR_CLK_UNDER_RUN_ERR 0x00000001
+
+#define SPI_NUM_CHIPSELECTS 4
+#define SPI_SUPPORTED_MODES (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP)
+
+#define SPI_DELAY_THRESHOLD 1
+/* Default timeout is 10 milliseconds */
+#define SPI_DEFAULT_TIMEOUT 10
+
+enum msm_spi_state {
+ SPI_OP_STATE_RESET = 0x00000000,
+ SPI_OP_STATE_RUN = 0x00000001,
+ SPI_OP_STATE_PAUSE = 0x00000003,
+};
+
+enum msm_spi_mode {
+ SPI_FIFO_MODE = 0x0, /* 00 */
+ SPI_BLOCK_MODE = 0x1, /* 01 */
+ SPI_DMOV_MODE = 0x2, /* 10 */
+ SPI_MODE_NONE = 0xFF, /* invalid value */
+};
+
+#ifdef CONFIG_DEBUG_FS
+static const struct {
+ const char *name;
+ mode_t mode;
+ int offset;
+} debugfs_spi_regs[] = {
+ {"config", S_IRUGO | S_IWUSR, SPI_CONFIG},
+ {"io_control", S_IRUGO | S_IWUSR, SPI_IO_CONTROL},
+ {"io_modes", S_IRUGO | S_IWUSR, SPI_IO_MODES},
+ {"sw_reset", S_IWUSR, SPI_SW_RESET},
+ {"time_out", S_IRUGO | S_IWUSR, SPI_TIME_OUT},
+ {"time_out_current", S_IRUGO, SPI_TIME_OUT_CURRENT},
+ {"mx_output_count", S_IRUGO | S_IWUSR, SPI_MX_OUTPUT_COUNT},
+ {"mx_output_cnt_current", S_IRUGO, SPI_MX_OUTPUT_CNT_CURRENT},
+ {"mx_input_count", S_IRUGO | S_IWUSR, SPI_MX_INPUT_COUNT},
+ {"mx_input_cnt_current", S_IRUGO, SPI_MX_INPUT_CNT_CURRENT},
+ {"mx_read_count", S_IRUGO | S_IWUSR, SPI_MX_READ_COUNT},
+ {"mx_read_cnt_current", S_IRUGO, SPI_MX_READ_CNT_CURRENT},
+ {"operational", S_IRUGO | S_IWUSR, SPI_OPERATIONAL},
+ {"error_flags", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS},
+ {"error_flags_en", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS_EN},
+ {"deassert_wait", S_IRUGO | S_IWUSR, SPI_DEASSERT_WAIT},
+ {"output_debug", S_IRUGO, SPI_OUTPUT_DEBUG},
+ {"input_debug", S_IRUGO, SPI_INPUT_DEBUG},
+ {"test_ctrl", S_IRUGO | S_IWUSR, SPI_TEST_CTRL},
+ {"output_fifo", S_IWUSR, SPI_OUTPUT_FIFO},
+ {"input_fifo" , S_IRUSR, SPI_INPUT_FIFO},
+ {"spi_state", S_IRUGO | S_IWUSR, SPI_STATE},
+ {"qup_config", S_IRUGO | S_IWUSR, QUP_CONFIG},
+ {"qup_error_flags", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS},
+ {"qup_error_flags_en", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS_EN},
+ {"mx_write_cnt", S_IRUGO | S_IWUSR, QUP_MX_WRITE_COUNT},
+ {"mx_write_cnt_current", S_IRUGO, QUP_MX_WRITE_CNT_CURRENT},
+ {"output_fifo_word_cnt", S_IRUGO, SPI_OUTPUT_FIFO_WORD_CNT},
+ {"input_fifo_word_cnt", S_IRUGO, SPI_INPUT_FIFO_WORD_CNT},
+};
+#endif
+
+/**
+ * struct msm_spi
+ * @read_buf: rx_buf from the spi_transfer.
+ * @write_buf: tx_buf from the spi_transfer.
+ * @base: location of QUP controller I/O area in memory.
+ * @dev: parent platform device.
+ * @queue_lock: lock to protect queue.
+ * @core_lock: mutex used to protect this struct.
+ * @queue: to log SPI transfer requests.
+ * @workqueue: workqueue for the SPI transfer requests.
+ * @work_data: work.
+ * @cur_msg: the current spi_message being processed.
+ * @cur_transfer: the current spi_transfer being processed.
+ * @transfer_complete: completion function to signal the end of a spi_transfer.
+ * @clk: the SPI core clock
+ * @pclk: hardware core clock. Needs to be enabled to access the QUP register
+ * @mem_phys_addr: physical address of the QUP controller.
+ * @mem_size: size of the QUP controller block.
+ * @input_fifo_size: the input FIFO size (in bytes).
+ * @output_fifo_size: the output FIFO size (in bytes).
+ * @rx_bytes_remaining: the number of rx bytes remaining to be transferred.
+ * @tx_bytes_remaining: the number of tx bytes remaining to be transferred.
+ * @clock_speed: SPI clock speed.
+ * @irq_in: assigned interrupt line for QUP interrupts.
+ * @read_xfr_cnt: number of words read from the FIFO (per transfer).
+ * @write_xfr_cnt: number of words written to the FIFO (per transfer).
+ * @write_len: the total number of tx bytes to be transferred, per
+ * spi_message. This is valid only for WR-WR and WR-RD transfers
+ * in a single spi_message.
+ * @read_len: the total number of rx bytes to be transferred, per
+ * spi_message. This is valid only for WR-WR and WR-RD transfers
+ * in a single spi_message.
+ * @bytes_per_word: bytes per word
+ * @suspended: the suspend state.
+ * @transfer_pending: when set indicates a pending transfer.
+ * @continue_suspend: head of wait queue.
+ * @mode: mode for SPI operation.
+ * @input_block_size: the input block size (in bytes).
+ * @output_block_size: the output block size (in bytes).
+ * @stat_rx: count of input interrupts handled.
+ * @stat_tx: count of output interrupts handled.
+ * @dent_spi: used for debug purposes.
+ * @debugfs_spi_regs: used for debug purposes.
+ * @pdata: platform data
+ * @multi_xfr: when set indicates multiple spi_transfers in a single
+ * spi_message.
+ * @done: flag used to signal completion.
+ * @cur_msg_len: combined length of all the transfers in a single
+ * spi_message (in bytes).
+ * @cur_tx_transfer: the current tx transfer being processed. Used in
+ * FIFO mode only.
+ * @cur_rx_transfer: the current rx transfer being processed. Used in
+ * FIFO mode only.
+ *
+ * Early QUP controller used three separate interrupt lines for input, output,
+ * and error interrupts. Later versions share a single interrupt line.
+ */
+struct msm_spi {
+ u8 *read_buf;
+ const u8 *write_buf;
+ void __iomem *base;
+ struct device *dev;
+ spinlock_t queue_lock;
+ struct mutex core_lock;
+ struct list_head queue;
+ struct workqueue_struct *workqueue;
+ struct work_struct work_data;
+ struct spi_message *cur_msg;
+ struct spi_transfer *cur_transfer;
+ struct completion transfer_complete;
+ struct clk *clk;
+ struct clk *pclk;
+ unsigned long mem_phys_addr;
+ size_t mem_size;
+ int input_fifo_size;
+ int output_fifo_size;
+ u32 rx_bytes_remaining;
+ u32 tx_bytes_remaining;
+ u32 clock_speed;
+ int irq_in;
+ int read_xfr_cnt;
+ int write_xfr_cnt;
+ int write_len;
+ int read_len;
+ int bytes_per_word;
+ bool suspended;
+ bool transfer_pending;
+ wait_queue_head_t continue_suspend;
+ enum msm_spi_mode mode;
+ int input_block_size;
+ int output_block_size;
+ int stat_rx;
+ int stat_tx;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dent_spi;
+ struct dentry *debugfs_spi_regs[ARRAY_SIZE(debugfs_spi_regs)];
+#endif
+ struct msm_spi_platform_data *pdata;
+ bool multi_xfr;
+ bool done;
+ u32 cur_msg_len;
+ struct spi_transfer *cur_tx_transfer;
+ struct spi_transfer *cur_rx_transfer;
+};
+
+/* Forward declaration */
+static irqreturn_t msm_spi_input_irq(int irq, void *dev_id);
+static irqreturn_t msm_spi_output_irq(int irq, void *dev_id);
+static irqreturn_t msm_spi_error_irq(int irq, void *dev_id);
+static inline int msm_spi_set_state(struct msm_spi *dd,
+ enum msm_spi_state state);
+static void msm_spi_write_word_to_fifo(struct msm_spi *dd);
+static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd);
+
+/* In QUP the same interrupt line is used for input, output and error */
+static inline int msm_spi_get_irq_data(struct msm_spi *dd,
+ struct platform_device *pdev)
+{
+ dd->irq_in = platform_get_irq_byname(pdev, "spi_irq_in");
+ if (dd->irq_in < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int msm_spi_configure_gsbi(struct msm_spi *dd,
+ struct platform_device *pdev)
+{
+ struct resource *resource;
+ unsigned long gsbi_mem_phys_addr;
+ size_t gsbi_mem_size;
+ void __iomem *gsbi_base;
+
+ resource = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "gsbi_base");
+ if (!resource)
+ return -ENXIO;
+
+ gsbi_mem_phys_addr = resource->start;
+ gsbi_mem_size = resource_size(resource);
+ if (!devm_request_mem_region(&pdev->dev, gsbi_mem_phys_addr,
+ gsbi_mem_size, SPI_DRV_NAME))
+ return -ENXIO;
+
+ gsbi_base = devm_ioremap(&pdev->dev, gsbi_mem_phys_addr,
+ gsbi_mem_size);
+ if (!gsbi_base)
+ return -ENXIO;
+
+ /* Set GSBI to SPI mode */
+ writel(GSBI_SPI_CONFIG, gsbi_base + GSBI_CTRL_REG);
+
+ return 0;
+}
+
+/* Figure which irq occured and call the relevant functions */
+static irqreturn_t msm_spi_qup_irq(int irq, void *dev_id)
+{
+ u32 op, ret = IRQ_NONE;
+ struct msm_spi *dd = dev_id;
+
+ if (readl(dd->base + SPI_ERROR_FLAGS) ||
+ readl(dd->base + QUP_ERROR_FLAGS)) {
+ struct spi_master *master = dev_get_drvdata(dd->dev);
+ ret |= msm_spi_error_irq(irq, master);
+ }
+
+ op = readl(dd->base + SPI_OPERATIONAL);
+ if (op & SPI_OP_INPUT_SERVICE_FLAG) {
+ writel(SPI_OP_INPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
+ ret |= msm_spi_input_irq(irq, dev_id);
+ }
+
+ if (op & SPI_OP_OUTPUT_SERVICE_FLAG) {
+ writel(SPI_OP_OUTPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
+ ret |= msm_spi_output_irq(irq, dev_id);
+ }
+
+ if (dd->done) {
+ complete(&dd->transfer_complete);
+ dd->done = 0;
+ }
+ return ret;
+}
+
+static inline int msm_spi_request_irq(struct msm_spi *dd,
+ const char *name,
+ struct spi_master *master)
+{
+ return request_irq(dd->irq_in, msm_spi_qup_irq, IRQF_TRIGGER_HIGH,
+ name, dd);
+}
+
+static inline void msm_spi_free_irq(struct msm_spi *dd,
+ struct spi_master *master)
+{
+ free_irq(dd->irq_in, dd);
+}
+
+static inline void msm_spi_disable_irqs(struct msm_spi *dd)
+{
+ disable_irq(dd->irq_in);
+}
+
+static inline void msm_spi_enable_irqs(struct msm_spi *dd)
+{
+ enable_irq(dd->irq_in);
+}
+
+static inline void msm_spi_get_clk_err(struct msm_spi *dd, u32 *spi_err)
+{
+ *spi_err = readl(dd->base + QUP_ERROR_FLAGS);
+}
+
+static inline void msm_spi_ack_clk_err(struct msm_spi *dd)
+{
+ writel(QUP_ERR_MASK, dd->base + QUP_ERROR_FLAGS);
+}
+
+static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n);
+
+/* QUP has no_input, no_output, and N bits at QUP_CONFIG */
+static inline void msm_spi_set_qup_config(struct msm_spi *dd, int bpw)
+{
+ u32 qup_config = readl(dd->base + QUP_CONFIG);
+
+ msm_spi_add_configs(dd, &qup_config, bpw-1);
+ writel(qup_config | QUP_CONFIG_SPI_MODE, dd->base + QUP_CONFIG);
+}
+
+static inline int msm_spi_prepare_for_write(struct msm_spi *dd)
+{
+ if (msm_spi_set_state(dd, SPI_OP_STATE_RUN))
+ return -EINVAL;
+
+ if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline void msm_spi_start_write(struct msm_spi *dd, u32 read_count)
+{
+ if (read_count <= dd->input_fifo_size)
+ msm_spi_write_rmn_to_fifo(dd);
+ else
+ msm_spi_write_word_to_fifo(dd);
+}
+
+static inline void msm_spi_set_write_count(struct msm_spi *dd, int val)
+{
+ writel(val, dd->base + QUP_MX_WRITE_COUNT);
+}
+
+static inline void msm_spi_complete(struct msm_spi *dd)
+{
+ dd->done = 1;
+}
+
+static inline void msm_spi_enable_error_flags(struct msm_spi *dd)
+{
+ writel_relaxed(0x00000078, dd->base + SPI_ERROR_FLAGS_EN);
+}
+
+static inline void msm_spi_clear_error_flags(struct msm_spi *dd)
+{
+ writel_relaxed(0x0000007C, dd->base + SPI_ERROR_FLAGS);
+}
+
+#endif
diff --git a/drivers/spi/spi-qsd.c b/drivers/spi/spi-qsd.c
new file mode 100644
index 0000000..d949020
--- /dev/null
+++ b/drivers/spi/spi-qsd.c
@@ -0,0 +1,1144 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/msm_spi.h>
+#include "msm_spi_qsd.h"
+
+static void msm_spi_clock_set(struct msm_spi *dd, int speed)
+{
+ int rc;
+
+ rc = clk_set_rate(dd->clk, speed);
+ if (!rc)
+ dd->clock_speed = speed;
+}
+
+static int msm_spi_calculate_size(int *fifo_size,
+ int *block_size,
+ int block,
+ int mult)
+{
+ int words;
+
+ switch (block) {
+ case 0:
+ words = 1; /* 4 bytes */
+ break;
+ case 1:
+ words = 4; /* 16 bytes */
+ break;
+ case 2:
+ words = 8; /* 32 bytes */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (mult) {
+ case 0:
+ *fifo_size = words * 2;
+ break;
+ case 1:
+ *fifo_size = words * 4;
+ break;
+ case 2:
+ *fifo_size = words * 8;
+ break;
+ case 3:
+ *fifo_size = words * 16;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *block_size = words * sizeof(u32); /* in bytes */
+ return 0;
+}
+
+static void __init msm_spi_calculate_fifo_size(struct msm_spi *dd)
+{
+ u32 spi_iom;
+ int block;
+ int mult;
+
+ spi_iom = readl(dd->base + SPI_IO_MODES);
+ block = (spi_iom & SPI_IO_M_INPUT_BLOCK_SIZE) >> INPUT_BLOCK_SZ_SHIFT;
+ mult = (spi_iom & SPI_IO_M_INPUT_FIFO_SIZE) >> INPUT_FIFO_SZ_SHIFT;
+ if (msm_spi_calculate_size(&dd->input_fifo_size, &dd->input_block_size,
+ block, mult)) {
+ goto fifo_size_err;
+ }
+
+ block = (spi_iom & SPI_IO_M_OUTPUT_BLOCK_SIZE) >> OUTPUT_BLOCK_SZ_SHIFT;
+ mult = (spi_iom & SPI_IO_M_OUTPUT_FIFO_SIZE) >> OUTPUT_FIFO_SZ_SHIFT;
+ if (msm_spi_calculate_size(&dd->output_fifo_size,
+ &dd->output_block_size, block, mult)) {
+ goto fifo_size_err;
+ }
+
+ return;
+
+fifo_size_err:
+ pr_err("%s: Invalid FIFO size,SPI_IO_MODES=0x%x\n", __func__, spi_iom);
+ return;
+}
+
+static void msm_spi_read_word_from_fifo(struct msm_spi *dd)
+{
+ u32 data_in;
+ int i;
+ int shift;
+
+ data_in = readl(dd->base + SPI_INPUT_FIFO);
+
+ if (dd->read_buf) {
+ for (i = 0; (i < dd->bytes_per_word) &&
+ dd->rx_bytes_remaining; i++) {
+ /*
+ * The data format depends on bytes_per_word:
+ * 4 bytes: 0x12345678
+ * 3 bytes: 0x00123456
+ * 2 bytes: 0x00001234
+ * 1 byte : 0x00000012
+ */
+ shift = 8 * (dd->bytes_per_word - i - 1);
+ *dd->read_buf++ = (data_in & (0xFF << shift)) >> shift;
+ dd->rx_bytes_remaining--;
+ }
+ } else {
+ if (dd->rx_bytes_remaining >= dd->bytes_per_word)
+ dd->rx_bytes_remaining -= dd->bytes_per_word;
+ else
+ dd->rx_bytes_remaining = 0;
+ }
+
+ dd->read_xfr_cnt++;
+ if (dd->multi_xfr) {
+ if (!dd->rx_bytes_remaining)
+ dd->read_xfr_cnt = 0;
+ else if ((dd->read_xfr_cnt * dd->bytes_per_word) ==
+ dd->read_len) {
+ struct spi_transfer *t = dd->cur_rx_transfer;
+ if (t->transfer_list.next != &dd->cur_msg->transfers) {
+ t = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ dd->read_buf = t->rx_buf;
+ dd->read_len = t->len;
+ dd->read_xfr_cnt = 0;
+ dd->cur_rx_transfer = t;
+ }
+ }
+ }
+}
+
+static inline bool msm_spi_is_valid_state(struct msm_spi *dd)
+{
+ u32 spi_op = readl(dd->base + SPI_STATE);
+
+ return spi_op & SPI_OP_STATE_VALID;
+}
+
+static inline int msm_spi_wait_valid(struct msm_spi *dd)
+{
+ unsigned long delay;
+ unsigned long timeout;
+
+ if (dd->clock_speed == 0)
+ return -EINVAL;
+
+ /*
+ * Based on the SPI clock speed, sufficient time
+ * should be given for the SPI state transition to occur
+ */
+ delay = (10 * USEC_PER_SEC) / dd->clock_speed;
+ /* For small delay values, the default timeout would be one jiffy */
+ if (delay < SPI_DELAY_THRESHOLD)
+ delay = SPI_DELAY_THRESHOLD;
+
+ timeout = jiffies + msecs_to_jiffies(delay * SPI_DEFAULT_TIMEOUT) + 1;
+ while (!msm_spi_is_valid_state(dd)) {
+ if (time_after(jiffies, timeout)) {
+ if (!msm_spi_is_valid_state(dd)) {
+ if (dd->cur_msg)
+ dd->cur_msg->status = -EIO;
+ dev_err(dd->dev, "%s: SPI operational state"
+ "not valid\n", __func__);
+ return -ETIMEDOUT;
+ } else
+ return 0;
+ }
+ /*
+ * For smaller values of delay, context switch time
+ * would negate the usage of usleep
+ */
+ if (delay > 20)
+ usleep_range(delay, delay);
+ else if (delay)
+ udelay(delay);
+ }
+
+ return 0;
+}
+
+static inline int msm_spi_set_state(struct msm_spi *dd,
+ enum msm_spi_state state)
+{
+ enum msm_spi_state cur_state;
+
+ if (msm_spi_wait_valid(dd))
+ return -EIO;
+
+ cur_state = readl(dd->base + SPI_STATE);
+ /*
+ * Per spec:
+ * For PAUSE_STATE to RESET_STATE, two writes of (10) are required
+ */
+ if (((cur_state & SPI_OP_STATE) == SPI_OP_STATE_PAUSE) &&
+ (state == SPI_OP_STATE_RESET)) {
+ writel(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE);
+ writel(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE);
+ } else {
+ writel((cur_state & ~SPI_OP_STATE) | state,
+ dd->base + SPI_STATE);
+ }
+
+ if (msm_spi_wait_valid(dd))
+ return -EIO;
+
+ return 0;
+}
+
+static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n)
+{
+ *config &= ~(SPI_NO_INPUT|SPI_NO_OUTPUT);
+ if (n != (*config & SPI_CFG_N))
+ *config = (*config & ~SPI_CFG_N) | n;
+}
+
+static void msm_spi_set_config(struct msm_spi *dd, int bpw)
+{
+ u32 spi_config;
+
+ spi_config = readl(dd->base + SPI_CONFIG);
+ if (dd->cur_msg->spi->mode & SPI_CPHA)
+ spi_config &= ~SPI_CFG_INPUT_FIRST;
+ else
+ spi_config |= SPI_CFG_INPUT_FIRST;
+
+ if (dd->cur_msg->spi->mode & SPI_LOOP)
+ spi_config |= SPI_CFG_LOOPBACK;
+ else
+ spi_config &= ~SPI_CFG_LOOPBACK;
+
+ msm_spi_add_configs(dd, &spi_config, bpw-1);
+ writel(spi_config, dd->base + SPI_CONFIG);
+ msm_spi_set_qup_config(dd, bpw);
+}
+
+static irqreturn_t msm_spi_input_irq(int irq, void *dev_id)
+{
+ struct msm_spi *dd = dev_id;
+
+ dd->stat_rx++;
+ if (dd->mode == SPI_FIFO_MODE) {
+ while ((readl(dd->base + SPI_OPERATIONAL) &
+ SPI_OP_IP_FIFO_NOT_EMPTY) &&
+ (dd->rx_bytes_remaining > 0)) {
+ msm_spi_read_word_from_fifo(dd);
+ }
+ if (dd->rx_bytes_remaining == 0)
+ msm_spi_complete(dd);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void msm_spi_write_word_to_fifo(struct msm_spi *dd)
+{
+ u32 word = 0;
+ u8 byte;
+ int i;
+
+ if (dd->write_buf) {
+ for (i = 0; (i < dd->bytes_per_word) &&
+ dd->tx_bytes_remaining; i++) {
+ dd->tx_bytes_remaining--;
+ byte = *dd->write_buf++;
+ word |= (byte << (BITS_PER_BYTE * (3 - i)));
+ }
+ } else
+ if (dd->tx_bytes_remaining > dd->bytes_per_word)
+ dd->tx_bytes_remaining -= dd->bytes_per_word;
+ else
+ dd->tx_bytes_remaining = 0;
+
+ dd->write_xfr_cnt++;
+ if (dd->multi_xfr) {
+ if (!dd->tx_bytes_remaining)
+ dd->write_xfr_cnt = 0;
+ else if ((dd->write_xfr_cnt * dd->bytes_per_word) ==
+ dd->write_len) {
+ struct spi_transfer *t = dd->cur_tx_transfer;
+ if (t->transfer_list.next != &dd->cur_msg->transfers) {
+ t = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ dd->write_buf = t->tx_buf;
+ dd->write_len = t->len;
+ dd->write_xfr_cnt = 0;
+ dd->cur_tx_transfer = t;
+ }
+ }
+ }
+
+ writel_relaxed(word, dd->base + SPI_OUTPUT_FIFO);
+}
+
+static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd)
+{
+ int count = 0;
+
+ while ((dd->tx_bytes_remaining > 0) && (count < dd->input_fifo_size) &&
+ !(readl(dd->base + SPI_OPERATIONAL) &
+ SPI_OP_OUTPUT_FIFO_FULL)) {
+ msm_spi_write_word_to_fifo(dd);
+ count++;
+ }
+}
+
+static irqreturn_t msm_spi_output_irq(int irq, void *dev_id)
+{
+ struct msm_spi *dd = dev_id;
+
+ dd->stat_tx++;
+ /* Output FIFO is empty. Transmit any outstanding write data. */
+ if (dd->mode == SPI_FIFO_MODE)
+ msm_spi_write_rmn_to_fifo(dd);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t msm_spi_error_irq(int irq, void *dev_id)
+{
+ struct spi_master *master = dev_id;
+ struct msm_spi *dd = spi_master_get_devdata(master);
+ u32 spi_err;
+
+ spi_err = readl(dd->base + SPI_ERROR_FLAGS);
+ if (spi_err & SPI_ERR_OUTPUT_OVER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI output overrun error\n");
+
+ if (spi_err & SPI_ERR_INPUT_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI input underrun error\n");
+
+ if (spi_err & SPI_ERR_OUTPUT_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI output underrun error\n");
+
+ msm_spi_get_clk_err(dd, &spi_err);
+ if (spi_err & SPI_ERR_CLK_OVER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI clock overrun error\n");
+
+ if (spi_err & SPI_ERR_CLK_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI clock underrun error\n");
+
+ msm_spi_clear_error_flags(dd);
+ msm_spi_ack_clk_err(dd);
+
+ return IRQ_HANDLED;
+}
+
+static void msm_spi_process_transfer(struct msm_spi *dd)
+{
+ u8 bpw;
+ u32 spi_ioc;
+ u32 spi_iom;
+ u32 spi_ioc_orig;
+ u32 max_speed;
+ u32 chip_select;
+ u32 read_count;
+ u32 timeout;
+ u32 int_loopback = 0;
+
+ dd->tx_bytes_remaining = dd->cur_msg_len;
+ dd->rx_bytes_remaining = dd->cur_msg_len;
+ dd->read_buf = dd->cur_transfer->rx_buf;
+ dd->write_buf = dd->cur_transfer->tx_buf;
+ init_completion(&dd->transfer_complete);
+ if (dd->cur_transfer->bits_per_word)
+ bpw = dd->cur_transfer->bits_per_word;
+ else
+ if (dd->cur_msg->spi->bits_per_word)
+ bpw = dd->cur_msg->spi->bits_per_word;
+ else
+ bpw = 8;
+
+ dd->bytes_per_word = (bpw + 7) / 8;
+ if (dd->cur_transfer->speed_hz)
+ max_speed = dd->cur_transfer->speed_hz;
+ else
+ max_speed = dd->cur_msg->spi->max_speed_hz;
+
+ if (!dd->clock_speed || max_speed != dd->clock_speed)
+ msm_spi_clock_set(dd, max_speed);
+
+ read_count = DIV_ROUND_UP(dd->cur_msg_len, dd->bytes_per_word);
+ if (dd->cur_msg->spi->mode & SPI_LOOP)
+ int_loopback = 1;
+
+ if (int_loopback && dd->multi_xfr &&
+ (read_count > dd->input_fifo_size)) {
+ if (dd->read_len && dd->write_len)
+ pr_err(
+ "%s:Internal Loopback does not support > fifo size"
+ "for write-then-read transactions\n",
+ __func__);
+ else if (dd->write_len && !dd->read_len)
+ pr_err(
+ "%s:Internal Loopback does not support > fifo size"
+ "for write-then-write transactions\n",
+ __func__);
+
+ return;
+ }
+
+ dd->mode = SPI_FIFO_MODE;
+ if (dd->multi_xfr) {
+ dd->read_len = dd->cur_transfer->len;
+ dd->write_len = dd->cur_transfer->len;
+ }
+
+ /*
+ * read_count cannot exceed fifo_size, and only one READ COUNT
+ * interrupt is generated per transaction, so for transactions
+ * larger than fifo size READ COUNT must be disabled.
+ */
+ if (read_count <= dd->input_fifo_size) {
+ writel(read_count, dd->base + SPI_MX_READ_COUNT);
+ msm_spi_set_write_count(dd, read_count);
+ } else {
+ writel(0, dd->base + SPI_MX_READ_COUNT);
+ msm_spi_set_write_count(dd, 0);
+ }
+
+ spi_iom = readl(dd->base + SPI_IO_MODES);
+ spi_iom &= ~(SPI_IO_M_INPUT_MODE | SPI_IO_M_OUTPUT_MODE);
+ spi_iom = (spi_iom | (dd->mode << OUTPUT_MODE_SHIFT));
+ spi_iom = (spi_iom | (dd->mode << INPUT_MODE_SHIFT));
+ spi_iom &= ~(SPI_IO_M_PACK_EN | SPI_IO_M_UNPACK_EN);
+
+ writel(spi_iom, dd->base + SPI_IO_MODES);
+ msm_spi_set_config(dd, bpw);
+ spi_ioc = readl(dd->base + SPI_IO_CONTROL);
+ spi_ioc_orig = spi_ioc;
+ if (dd->cur_msg->spi->mode & SPI_CPOL)
+ spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH;
+ else
+ spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH;
+
+ chip_select = dd->cur_msg->spi->chip_select << 2;
+ if ((spi_ioc & SPI_IO_C_CS_SELECT) != chip_select)
+ spi_ioc = (spi_ioc & ~SPI_IO_C_CS_SELECT) | chip_select;
+
+ if (!dd->cur_transfer->cs_change)
+ spi_ioc |= SPI_IO_C_MX_CS_MODE;
+
+ if (spi_ioc != spi_ioc_orig)
+ writel(spi_ioc, dd->base + SPI_IO_CONTROL);
+
+ /*
+ * The output fifo interrupt handler will handle all writes
+ * after the first. Restricting this to one write avoids
+ * contention issues and race conditions between this thread
+ * and the int handler.
+ */
+ if (msm_spi_prepare_for_write(dd))
+ goto transfer_end;
+ msm_spi_start_write(dd, read_count);
+
+ /*
+ * Only enter the RUN state after the first word is written into
+ * the output FIFO. Otherwise, the output FIFO EMPTY interrupt
+ * might fire before the first word is written resulting in a
+ * possible race condition.
+ */
+ if (msm_spi_set_state(dd, SPI_OP_STATE_RUN))
+ goto transfer_end;
+
+ timeout = 100 * msecs_to_jiffies(
+ DIV_ROUND_UP(dd->cur_msg_len * 8,
+ DIV_ROUND_UP(max_speed, MSEC_PER_SEC)));
+
+ /* Assume success, this might change later upon transaction result */
+ dd->cur_msg->status = 0;
+ if (!wait_for_completion_timeout(&dd->transfer_complete, timeout)) {
+ dev_err(dd->dev, "%s: SPI transaction timeout\n", __func__);
+ dd->cur_msg->status = -EIO;
+ }
+
+transfer_end:
+ dd->mode = SPI_MODE_NONE;
+ msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ writel(spi_ioc & ~SPI_IO_C_MX_CS_MODE, dd->base + SPI_IO_CONTROL);
+}
+
+static void get_transfer_length(struct msm_spi *dd)
+{
+ struct spi_transfer *tr;
+ int num_xfrs = 0;
+ int readlen = 0;
+ int writelen = 0;
+
+ dd->cur_msg_len = 0;
+ dd->multi_xfr = 0;
+ dd->read_len = dd->write_len = 0;
+
+ list_for_each_entry(tr, &dd->cur_msg->transfers, transfer_list) {
+ if (tr->tx_buf)
+ writelen += tr->len;
+ if (tr->rx_buf)
+ readlen += tr->len;
+ dd->cur_msg_len += tr->len;
+ num_xfrs++;
+ }
+
+ if (num_xfrs == 2) {
+ struct spi_transfer *first_xfr = dd->cur_transfer;
+
+ dd->multi_xfr = 1;
+ tr = list_entry(first_xfr->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ /*
+ * We update dd->read_len and dd->write_len only
+ * for WR-WR and WR-RD transfers.
+ */
+ if ((first_xfr->tx_buf) && (!first_xfr->rx_buf)) {
+ if (((tr->tx_buf) && (!tr->rx_buf)) ||
+ ((!tr->tx_buf) && (tr->rx_buf))) {
+ dd->read_len = readlen;
+ dd->write_len = writelen;
+ }
+ }
+ } else if (num_xfrs > 1)
+ dd->multi_xfr = 1;
+}
+
+static inline int combine_transfers(struct msm_spi *dd)
+{
+ struct spi_transfer *t = dd->cur_transfer;
+ struct spi_transfer *nxt;
+ int xfrs_grped = 1;
+
+ dd->cur_msg_len = dd->cur_transfer->len;
+ while (t->transfer_list.next != &dd->cur_msg->transfers) {
+ nxt = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ if (t->cs_change != nxt->cs_change)
+ return xfrs_grped;
+ dd->cur_msg_len += nxt->len;
+ xfrs_grped++;
+ t = nxt;
+ }
+
+ return xfrs_grped;
+}
+
+static void msm_spi_process_message(struct msm_spi *dd)
+{
+ int xfrs_grped = 0;
+ dd->write_xfr_cnt = dd->read_xfr_cnt = 0;
+
+ dd->cur_transfer = list_first_entry(&dd->cur_msg->transfers,
+ struct spi_transfer,
+ transfer_list);
+ get_transfer_length(dd);
+ if (dd->multi_xfr && !dd->read_len && !dd->write_len) {
+ /* Handling of multi-transfers. FIFO mode is used by default */
+ list_for_each_entry(dd->cur_transfer,
+ &dd->cur_msg->transfers,
+ transfer_list) {
+ if (!dd->cur_transfer->len)
+ return;
+
+ if (xfrs_grped) {
+ xfrs_grped--;
+ continue;
+ } else {
+ dd->read_len = dd->write_len = 0;
+ xfrs_grped = combine_transfers(dd);
+ }
+
+ dd->cur_tx_transfer = dd->cur_transfer;
+ dd->cur_rx_transfer = dd->cur_transfer;
+ msm_spi_process_transfer(dd);
+ xfrs_grped--;
+ }
+ } else {
+ dd->cur_tx_transfer = dd->cur_rx_transfer = dd->cur_transfer;
+ msm_spi_process_transfer(dd);
+ }
+}
+
+/* workqueue - pull messages from queue and process */
+static void msm_spi_workq(struct work_struct *work)
+{
+ struct msm_spi *dd =
+ container_of(work, struct msm_spi, work_data);
+ unsigned long flags;
+ u32 status_error = 0;
+
+ mutex_lock(&dd->core_lock);
+ clk_enable(dd->clk);
+ clk_enable(dd->pclk);
+ msm_spi_enable_irqs(dd);
+ if (!msm_spi_is_valid_state(dd)) {
+ dev_err(dd->dev, "%s: SPI operational state not valid\n",
+ __func__);
+ status_error = 1;
+ }
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ while (!list_empty(&dd->queue)) {
+ dd->cur_msg = list_entry(dd->queue.next,
+ struct spi_message, queue);
+ list_del_init(&dd->cur_msg->queue);
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ if (status_error)
+ dd->cur_msg->status = -EIO;
+ else
+ msm_spi_process_message(dd);
+
+ if (dd->cur_msg->complete)
+ dd->cur_msg->complete(dd->cur_msg->context);
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ }
+
+ dd->transfer_pending = 0;
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+
+ msm_spi_disable_irqs(dd);
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ mutex_unlock(&dd->core_lock);
+
+ /*
+ * If needed, this can be done after the current message is complete,
+ * and work can be continued upon resume. No motivation for now.
+ */
+ if (dd->suspended)
+ wake_up_interruptible(&dd->continue_suspend);
+}
+
+static int msm_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct msm_spi *dd;
+ unsigned long flags;
+ struct spi_transfer *tr;
+
+ dd = spi_master_get_devdata(spi->master);
+ if (dd->suspended)
+ return -EBUSY;
+
+ if (list_empty(&msg->transfers) || !msg->complete)
+ return -EINVAL;
+
+ list_for_each_entry(tr, &msg->transfers, transfer_list) {
+ /* Check message parameters */
+ if (tr->speed_hz > dd->pdata->max_clock_speed ||
+ (tr->bits_per_word &&
+ (tr->bits_per_word < 4 || tr->bits_per_word > 32)) ||
+ (tr->tx_buf == NULL && tr->rx_buf == NULL)) {
+ dev_err(&spi->dev, "Invalid transfer: %d Hz, %d bpw"
+ "tx=%p, rx=%p\n",
+ tr->speed_hz, tr->bits_per_word,
+ tr->tx_buf, tr->rx_buf);
+ return -EINVAL;
+ }
+ }
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ if (dd->suspended) {
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ return -EBUSY;
+ }
+
+ dd->transfer_pending = 1;
+ list_add_tail(&msg->queue, &dd->queue);
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ queue_work(dd->workqueue, &dd->work_data);
+ return 0;
+}
+
+static int msm_spi_setup(struct spi_device *spi)
+{
+ struct msm_spi *dd;
+ int rc = 0;
+ u32 spi_ioc;
+ u32 spi_config;
+ u32 mask;
+
+ if (spi->bits_per_word < 4 || spi->bits_per_word > 32) {
+ dev_err(&spi->dev, "%s: invalid bits_per_word %d\n",
+ __func__, spi->bits_per_word);
+ rc = -EINVAL;
+ }
+
+ if (spi->chip_select > SPI_NUM_CHIPSELECTS-1) {
+ dev_err(&spi->dev, "%s, chip select %d exceeds max value %d\n",
+ __func__, spi->chip_select, SPI_NUM_CHIPSELECTS - 1);
+ rc = -EINVAL;
+ }
+
+ if (rc)
+ goto err_setup_exit;
+
+ dd = spi_master_get_devdata(spi->master);
+
+ mutex_lock(&dd->core_lock);
+ if (dd->suspended) {
+ mutex_unlock(&dd->core_lock);
+ return -EBUSY;
+ }
+
+ clk_enable(dd->clk);
+ clk_enable(dd->pclk);
+ spi_ioc = readl(dd->base + SPI_IO_CONTROL);
+ mask = SPI_IO_C_CS_N_POLARITY_0 << spi->chip_select;
+ if (spi->mode & SPI_CS_HIGH)
+ spi_ioc |= mask;
+ else
+ spi_ioc &= ~mask;
+
+ if (spi->mode & SPI_CPOL)
+ spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH;
+ else
+ spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH;
+
+ writel(spi_ioc, dd->base + SPI_IO_CONTROL);
+ spi_config = readl(dd->base + SPI_CONFIG);
+ if (spi->mode & SPI_LOOP)
+ spi_config |= SPI_CFG_LOOPBACK;
+ else
+ spi_config &= ~SPI_CFG_LOOPBACK;
+
+ if (spi->mode & SPI_CPHA)
+ spi_config &= ~SPI_CFG_INPUT_FIRST;
+ else
+ spi_config |= SPI_CFG_INPUT_FIRST;
+
+ writel(spi_config, dd->base + SPI_CONFIG);
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ mutex_unlock(&dd->core_lock);
+
+err_setup_exit:
+ return rc;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int debugfs_iomem_x32_set(void *data, u64 val)
+{
+ iowrite32(val, data);
+ /* Ensure the previous write completed. */
+ wmb();
+ return 0;
+}
+
+static int debugfs_iomem_x32_get(void *data, u64 *val)
+{
+ *val = ioread32(data);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get,
+ debugfs_iomem_x32_set, "0x%08llx\n");
+
+static void spi_debugfs_init(struct msm_spi *dd)
+{
+ dd->dent_spi = debugfs_create_dir(dev_name(dd->dev), NULL);
+ if (dd->dent_spi) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++) {
+ dd->debugfs_spi_regs[i] =
+ debugfs_create_file(
+ debugfs_spi_regs[i].name,
+ debugfs_spi_regs[i].mode,
+ dd->dent_spi,
+ dd->base + debugfs_spi_regs[i].offset,
+ &fops_iomem_x32);
+ }
+ }
+}
+
+static void spi_debugfs_exit(struct msm_spi *dd)
+{
+ if (dd->dent_spi) {
+ int i;
+
+ debugfs_remove_recursive(dd->dent_spi);
+ dd->dent_spi = NULL;
+ for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++)
+ dd->debugfs_spi_regs[i] = NULL;
+ }
+}
+#else
+static void spi_debugfs_init(struct msm_spi *dd) {}
+static void spi_debugfs_exit(struct msm_spi *dd) {}
+#endif
+
+/* ===Device attributes begin=== */
+static ssize_t show_stats(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct msm_spi *dd = spi_master_get_devdata(master);
+
+ return snprintf(buf, PAGE_SIZE,
+ "Device %s\n"
+ "rx fifo_size = %d spi words\n"
+ "tx fifo_size = %d spi words\n"
+ "rx block size = %d bytes\n"
+ "tx block size = %d bytes\n"
+ "--statistics--\n"
+ "Rx isrs = %d\n"
+ "Tx isrs = %d\n"
+ "--debug--\n"
+ "NA yet\n",
+ dev_name(dev),
+ dd->input_fifo_size,
+ dd->output_fifo_size,
+ dd->input_block_size,
+ dd->output_block_size,
+ dd->stat_rx,
+ dd->stat_tx
+ );
+}
+
+/* Reset statistics on write */
+static ssize_t set_stats(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct msm_spi *dd = dev_get_drvdata(dev);
+
+ dd->stat_rx = 0;
+ dd->stat_tx = 0;
+ return count;
+}
+
+static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats);
+
+static struct attribute *dev_attrs[] = {
+ &dev_attr_stats.attr,
+ NULL,
+};
+
+static struct attribute_group dev_attr_grp = {
+ .attrs = dev_attrs,
+};
+/* ===Device attributes end=== */
+
+static int __init msm_spi_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct msm_spi *dd;
+ struct resource *resource;
+ int rc = -ENXIO;
+ int locked = 0;
+ int clk_enabled = 0;
+ int pclk_enabled = 0;
+ struct msm_spi_platform_data *pdata = pdev->dev.platform_data;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct msm_spi));
+ if (!master) {
+ rc = -ENOMEM;
+ dev_err(&pdev->dev, "master allocation failed\n");
+ goto err_probe_exit;
+ }
+
+ master->bus_num = pdev->id;
+ master->mode_bits = SPI_SUPPORTED_MODES;
+ master->num_chipselect = SPI_NUM_CHIPSELECTS;
+ master->setup = msm_spi_setup;
+ master->transfer = msm_spi_transfer;
+ platform_set_drvdata(pdev, master);
+ dd = spi_master_get_devdata(master);
+
+ dd->pdata = pdata;
+ rc = msm_spi_get_irq_data(dd, pdev);
+ if (rc)
+ goto err_probe_res;
+
+ resource = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "spi_base");
+ if (!resource) {
+ rc = -ENXIO;
+ goto err_probe_res;
+ }
+
+ dd->mem_phys_addr = resource->start;
+ dd->mem_size = resource_size(resource);
+
+ spin_lock_init(&dd->queue_lock);
+ mutex_init(&dd->core_lock);
+ INIT_LIST_HEAD(&dd->queue);
+ INIT_WORK(&dd->work_data, msm_spi_workq);
+ init_waitqueue_head(&dd->continue_suspend);
+ dd->workqueue = create_singlethread_workqueue(
+ dev_name(master->dev.parent));
+ if (!dd->workqueue)
+ goto err_probe_res;
+
+ if (!devm_request_mem_region(&pdev->dev, dd->mem_phys_addr,
+ dd->mem_size, SPI_DRV_NAME)) {
+ rc = -ENXIO;
+ goto err_probe_reqmem;
+ }
+
+ dd->base = devm_ioremap(&pdev->dev, dd->mem_phys_addr, dd->mem_size);
+ if (!dd->base) {
+ rc = -ENOMEM;
+ goto err_probe_reqmem;
+ }
+
+
+ mutex_lock(&dd->core_lock);
+ locked = 1;
+ dd->dev = &pdev->dev;
+ dd->clk = clk_get(&pdev->dev, "spi_clk");
+ if (IS_ERR(dd->clk)) {
+ dev_err(&pdev->dev, "%s: unable to get spi_clk\n", __func__);
+ rc = PTR_ERR(dd->clk);
+ goto err_probe_clk_get;
+ }
+
+ dd->pclk = clk_get(&pdev->dev, "spi_pclk");
+ if (IS_ERR(dd->pclk)) {
+ dev_err(&pdev->dev, "%s: unable to get spi_pclk\n", __func__);
+ rc = PTR_ERR(dd->pclk);
+ goto err_probe_pclk_get;
+ }
+
+ if (pdata && pdata->max_clock_speed)
+ msm_spi_clock_set(dd, dd->pdata->max_clock_speed);
+
+ rc = clk_enable(dd->clk);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to enable spi_clk\n",
+ __func__);
+ goto err_probe_clk_enable;
+ }
+
+ clk_enabled = 1;
+ rc = clk_enable(dd->pclk);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to enable spi_pclk\n",
+ __func__);
+ goto err_probe_pclk_enable;
+ }
+
+ pclk_enabled = 1;
+ rc = msm_spi_configure_gsbi(dd, pdev);
+ if (rc)
+ goto err_probe_gsbi;
+
+ msm_spi_calculate_fifo_size(dd);
+
+ /* Initialize registers */
+ writel(0x00000001, dd->base + SPI_SW_RESET);
+ msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ writel(0x00000000, dd->base + SPI_OPERATIONAL);
+ writel(0x00000000, dd->base + SPI_CONFIG);
+ writel(0x00000000, dd->base + SPI_IO_MODES);
+ /*
+ * The SPI core generates a bogus input overrun error on some targets,
+ * when a transition from run to reset state occurs and if the FIFO has
+ * an odd number of entries. Hence we disable the INPUT_OVER_RUN_ERR_EN
+ * bit.
+ */
+ msm_spi_enable_error_flags(dd);
+
+ writel(SPI_IO_C_NO_TRI_STATE, dd->base + SPI_IO_CONTROL);
+ rc = msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ if (rc)
+ goto err_probe_state;
+
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ clk_enabled = 0;
+ pclk_enabled = 0;
+
+ dd->suspended = 0;
+ dd->transfer_pending = 0;
+ dd->multi_xfr = 0;
+ dd->mode = SPI_MODE_NONE;
+
+ rc = msm_spi_request_irq(dd, pdev->name, master);
+ if (rc)
+ goto err_probe_irq;
+
+ msm_spi_disable_irqs(dd);
+ mutex_unlock(&dd->core_lock);
+ locked = 0;
+
+ rc = spi_register_master(master);
+ if (rc)
+ goto err_probe_reg_master;
+
+ rc = sysfs_create_group(&(dd->dev->kobj), &dev_attr_grp);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc);
+ goto err_attrs;
+ }
+
+ spi_debugfs_init(dd);
+
+ return 0;
+
+err_attrs:
+ spi_unregister_master(master);
+err_probe_reg_master:
+ msm_spi_free_irq(dd, master);
+err_probe_irq:
+err_probe_state:
+err_probe_gsbi:
+ if (pclk_enabled)
+ clk_disable(dd->pclk);
+err_probe_pclk_enable:
+ if (clk_enabled)
+ clk_disable(dd->clk);
+err_probe_clk_enable:
+ clk_put(dd->pclk);
+err_probe_pclk_get:
+ clk_put(dd->clk);
+err_probe_clk_get:
+ if (locked)
+ mutex_unlock(&dd->core_lock);
+err_probe_reqmem:
+ destroy_workqueue(dd->workqueue);
+err_probe_res:
+ spi_master_put(master);
+err_probe_exit:
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int msm_spi_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd;
+ unsigned long flags;
+
+ if (!master)
+ goto suspend_exit;
+
+ dd = spi_master_get_devdata(master);
+ if (!dd)
+ goto suspend_exit;
+
+ /* Make sure nothing is added to the queue while we're suspending */
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ dd->suspended = 1;
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+
+ /* Wait for transactions to end, or time out */
+ wait_event_interruptible(dd->continue_suspend, !dd->transfer_pending);
+
+suspend_exit:
+ return 0;
+}
+
+static int msm_spi_resume(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd;
+
+ if (!master)
+ goto resume_exit;
+
+ dd = spi_master_get_devdata(master);
+ if (!dd)
+ goto resume_exit;
+
+ dd->suspended = 0;
+resume_exit:
+ return 0;
+}
+#else
+#define msm_spi_suspend NULL
+#define msm_spi_resume NULL
+#endif /* CONFIG_PM */
+
+static int __devexit msm_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd = spi_master_get_devdata(master);
+
+ spi_debugfs_exit(dd);
+ sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp);
+
+ msm_spi_free_irq(dd, master);
+ clk_put(dd->clk);
+ clk_put(dd->pclk);
+ destroy_workqueue(dd->workqueue);
+ platform_set_drvdata(pdev, 0);
+ spi_unregister_master(master);
+ spi_master_put(master);
+
+ return 0;
+}
+
+static struct platform_driver msm_spi_driver = {
+ .driver = {
+ .name = SPI_DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .suspend = msm_spi_suspend,
+ .resume = msm_spi_resume,
+ .remove = __exit_p(msm_spi_remove),
+};
+
+static int __init msm_spi_init(void)
+{
+ return platform_driver_probe(&msm_spi_driver, msm_spi_probe);
+}
+module_init(msm_spi_init);
+
+static void __exit msm_spi_exit(void)
+{
+ platform_driver_unregister(&msm_spi_driver);
+}
+module_exit(msm_spi_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.3");
+MODULE_ALIAS("platform:"SPI_DRV_NAME);
diff --git a/include/linux/platform_data/msm_spi.h b/include/linux/platform_data/msm_spi.h
new file mode 100644
index 0000000..a37ef6d
--- /dev/null
+++ b/include/linux/platform_data/msm_spi.h
@@ -0,0 +1,19 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_SPI_H
+#define __ARCH_ARM_MACH_MSM_SPI_H
+
+struct msm_spi_platform_data {
+ u32 max_clock_speed;
+};
+#endif
--
1.7.3.3
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH] spi: QUP based bus driver for Qualcomm MSM chipsets
2011-11-10 1:07 ` [PATCH] spi: QUP based bus driver for Qualcomm MSM chipsets Harini Jayaraman
@ 2011-11-10 1:34 ` Bryan Huntsman
0 siblings, 0 replies; 743+ messages in thread
From: Bryan Huntsman @ 2011-11-10 1:34 UTC (permalink / raw)
To: Harini Jayaraman
Cc: grant.likely, davidb, kheitke, linux-arm-msm, linux-arm-kernel,
spi-devel-general, linux-kernel
On 11/09/2011 05:07 PM, Harini Jayaraman wrote:
> This bus driver supports the QUP SPI hardware controller in the Qualcomm
> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
> purpose data path engine with input/output FIFOs and an embedded SPI
> mini-core. The driver currently supports only FIFO mode.
>
> Signed-off-by: Harini Jayaraman <harinij@codeaurora.org>
> ---
> drivers/spi/Kconfig | 10 +
> drivers/spi/Makefile | 1 +
> drivers/spi/msm_spi_qsd.h | 436 +++++++++++++
> drivers/spi/spi-qsd.c | 1144 +++++++++++++++++++++++++++++++++
> include/linux/platform_data/msm_spi.h | 19 +
> 5 files changed, 1610 insertions(+), 0 deletions(-)
> create mode 100644 drivers/spi/msm_spi_qsd.h
> create mode 100644 drivers/spi/spi-qsd.c
> create mode 100644 include/linux/platform_data/msm_spi.h
A few high-level comments to start. Let's drop 'qsd' from all the
names. We haven't used that naming convention for several years now.
Better to just call this 'spi-qup.[ch]'. Also, please update the
copyright dates. This driver existed before 2011. Thanks.
- Bryan
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 743+ messages in thread
* [PATCH v2] spi: QUP based bus driver for Qualcomm MSM chipsets
[not found] <y>
` (26 preceding siblings ...)
2011-11-10 1:07 ` [PATCH] spi: QUP based bus driver for Qualcomm MSM chipsets Harini Jayaraman
@ 2011-11-14 21:58 ` Harini Jayaraman
2011-12-07 22:37 ` Wolfram Sang
2012-01-23 10:42 ` Russell King - ARM Linux
2012-01-08 16:26 ` [PATCH v6 0/8] Reduce cross CPU IPI interference Gilad Ben-Yossef
` (132 subsequent siblings)
160 siblings, 2 replies; 743+ messages in thread
From: Harini Jayaraman @ 2011-11-14 21:58 UTC (permalink / raw)
To: grant.likely, bryanh
Cc: davidb, kheitke, linux-arm-msm, linux-arm-kernel,
Harini Jayaraman, spi-devel-general, linux-kernel
This bus driver supports the QUP SPI hardware controller in the Qualcomm
MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
purpose data path engine with input/output FIFOs and an embedded SPI
mini-core. The driver currently supports only FIFO mode.
Signed-off-by: Harini Jayaraman <harinij@codeaurora.org>
---
v2: Updated copyright information (addresses comments from Bryan Huntsman).
Files renamed.
---
drivers/spi/Kconfig | 10 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-qup.c | 1144 +++++++++++++++++++++++++++++++++
drivers/spi/spi-qup.h | 436 +++++++++++++
include/linux/platform_data/msm_spi.h | 19 +
5 files changed, 1610 insertions(+), 0 deletions(-)
create mode 100644 drivers/spi/spi-qup.c
create mode 100644 drivers/spi/spi-qup.h
create mode 100644 include/linux/platform_data/msm_spi.h
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 52e2900..88ea7c5 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -280,6 +280,16 @@ config SPI_PXA2XX
config SPI_PXA2XX_PCI
def_bool SPI_PXA2XX && X86_32 && PCI
+config SPI_QUP
+ tristate "Qualcomm MSM SPI QUPe Support"
+ depends on ARCH_MSM
+ help
+ Support for Serial Peripheral Interface for Qualcomm Universal
+ Peripheral.
+
+ This driver can also be built as a module. If so, the module
+ will be called spi-qup.
+
config SPI_S3C24XX
tristate "Samsung S3C24XX series SPI"
depends on ARCH_S3C2410 && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 61c3261..4d840ff 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o
obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
+obj-$(CONFIG_SPI_QUP) += spi-qup.o
obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
spi-s3c24xx-hw-y := spi-s3c24xx.o
spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o
diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
new file mode 100644
index 0000000..4b411d8
--- /dev/null
+++ b/drivers/spi/spi-qup.c
@@ -0,0 +1,1144 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/msm_spi.h>
+#include "spi-qup.h"
+
+static void msm_spi_clock_set(struct msm_spi *dd, int speed)
+{
+ int rc;
+
+ rc = clk_set_rate(dd->clk, speed);
+ if (!rc)
+ dd->clock_speed = speed;
+}
+
+static int msm_spi_calculate_size(int *fifo_size,
+ int *block_size,
+ int block,
+ int mult)
+{
+ int words;
+
+ switch (block) {
+ case 0:
+ words = 1; /* 4 bytes */
+ break;
+ case 1:
+ words = 4; /* 16 bytes */
+ break;
+ case 2:
+ words = 8; /* 32 bytes */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (mult) {
+ case 0:
+ *fifo_size = words * 2;
+ break;
+ case 1:
+ *fifo_size = words * 4;
+ break;
+ case 2:
+ *fifo_size = words * 8;
+ break;
+ case 3:
+ *fifo_size = words * 16;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *block_size = words * sizeof(u32); /* in bytes */
+ return 0;
+}
+
+static void __init msm_spi_calculate_fifo_size(struct msm_spi *dd)
+{
+ u32 spi_iom;
+ int block;
+ int mult;
+
+ spi_iom = readl(dd->base + SPI_IO_MODES);
+ block = (spi_iom & SPI_IO_M_INPUT_BLOCK_SIZE) >> INPUT_BLOCK_SZ_SHIFT;
+ mult = (spi_iom & SPI_IO_M_INPUT_FIFO_SIZE) >> INPUT_FIFO_SZ_SHIFT;
+ if (msm_spi_calculate_size(&dd->input_fifo_size, &dd->input_block_size,
+ block, mult)) {
+ goto fifo_size_err;
+ }
+
+ block = (spi_iom & SPI_IO_M_OUTPUT_BLOCK_SIZE) >> OUTPUT_BLOCK_SZ_SHIFT;
+ mult = (spi_iom & SPI_IO_M_OUTPUT_FIFO_SIZE) >> OUTPUT_FIFO_SZ_SHIFT;
+ if (msm_spi_calculate_size(&dd->output_fifo_size,
+ &dd->output_block_size, block, mult)) {
+ goto fifo_size_err;
+ }
+
+ return;
+
+fifo_size_err:
+ pr_err("%s: Invalid FIFO size,SPI_IO_MODES=0x%x\n", __func__, spi_iom);
+ return;
+}
+
+static void msm_spi_read_word_from_fifo(struct msm_spi *dd)
+{
+ u32 data_in;
+ int i;
+ int shift;
+
+ data_in = readl(dd->base + SPI_INPUT_FIFO);
+
+ if (dd->read_buf) {
+ for (i = 0; (i < dd->bytes_per_word) &&
+ dd->rx_bytes_remaining; i++) {
+ /*
+ * The data format depends on bytes_per_word:
+ * 4 bytes: 0x12345678
+ * 3 bytes: 0x00123456
+ * 2 bytes: 0x00001234
+ * 1 byte : 0x00000012
+ */
+ shift = 8 * (dd->bytes_per_word - i - 1);
+ *dd->read_buf++ = (data_in & (0xFF << shift)) >> shift;
+ dd->rx_bytes_remaining--;
+ }
+ } else {
+ if (dd->rx_bytes_remaining >= dd->bytes_per_word)
+ dd->rx_bytes_remaining -= dd->bytes_per_word;
+ else
+ dd->rx_bytes_remaining = 0;
+ }
+
+ dd->read_xfr_cnt++;
+ if (dd->multi_xfr) {
+ if (!dd->rx_bytes_remaining)
+ dd->read_xfr_cnt = 0;
+ else if ((dd->read_xfr_cnt * dd->bytes_per_word) ==
+ dd->read_len) {
+ struct spi_transfer *t = dd->cur_rx_transfer;
+ if (t->transfer_list.next != &dd->cur_msg->transfers) {
+ t = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ dd->read_buf = t->rx_buf;
+ dd->read_len = t->len;
+ dd->read_xfr_cnt = 0;
+ dd->cur_rx_transfer = t;
+ }
+ }
+ }
+}
+
+static inline bool msm_spi_is_valid_state(struct msm_spi *dd)
+{
+ u32 spi_op = readl(dd->base + SPI_STATE);
+
+ return spi_op & SPI_OP_STATE_VALID;
+}
+
+static inline int msm_spi_wait_valid(struct msm_spi *dd)
+{
+ unsigned long delay;
+ unsigned long timeout;
+
+ if (dd->clock_speed == 0)
+ return -EINVAL;
+
+ /*
+ * Based on the SPI clock speed, sufficient time
+ * should be given for the SPI state transition to occur
+ */
+ delay = (10 * USEC_PER_SEC) / dd->clock_speed;
+ /* For small delay values, the default timeout would be one jiffy */
+ if (delay < SPI_DELAY_THRESHOLD)
+ delay = SPI_DELAY_THRESHOLD;
+
+ timeout = jiffies + msecs_to_jiffies(delay * SPI_DEFAULT_TIMEOUT) + 1;
+ while (!msm_spi_is_valid_state(dd)) {
+ if (time_after(jiffies, timeout)) {
+ if (!msm_spi_is_valid_state(dd)) {
+ if (dd->cur_msg)
+ dd->cur_msg->status = -EIO;
+ dev_err(dd->dev, "%s: SPI operational state"
+ "not valid\n", __func__);
+ return -ETIMEDOUT;
+ } else
+ return 0;
+ }
+ /*
+ * For smaller values of delay, context switch time
+ * would negate the usage of usleep
+ */
+ if (delay > 20)
+ usleep_range(delay, delay);
+ else if (delay)
+ udelay(delay);
+ }
+
+ return 0;
+}
+
+static inline int msm_spi_set_state(struct msm_spi *dd,
+ enum msm_spi_state state)
+{
+ enum msm_spi_state cur_state;
+
+ if (msm_spi_wait_valid(dd))
+ return -EIO;
+
+ cur_state = readl(dd->base + SPI_STATE);
+ /*
+ * Per spec:
+ * For PAUSE_STATE to RESET_STATE, two writes of (10) are required
+ */
+ if (((cur_state & SPI_OP_STATE) == SPI_OP_STATE_PAUSE) &&
+ (state == SPI_OP_STATE_RESET)) {
+ writel(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE);
+ writel(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE);
+ } else {
+ writel((cur_state & ~SPI_OP_STATE) | state,
+ dd->base + SPI_STATE);
+ }
+
+ if (msm_spi_wait_valid(dd))
+ return -EIO;
+
+ return 0;
+}
+
+static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n)
+{
+ *config &= ~(SPI_NO_INPUT|SPI_NO_OUTPUT);
+ if (n != (*config & SPI_CFG_N))
+ *config = (*config & ~SPI_CFG_N) | n;
+}
+
+static void msm_spi_set_config(struct msm_spi *dd, int bpw)
+{
+ u32 spi_config;
+
+ spi_config = readl(dd->base + SPI_CONFIG);
+ if (dd->cur_msg->spi->mode & SPI_CPHA)
+ spi_config &= ~SPI_CFG_INPUT_FIRST;
+ else
+ spi_config |= SPI_CFG_INPUT_FIRST;
+
+ if (dd->cur_msg->spi->mode & SPI_LOOP)
+ spi_config |= SPI_CFG_LOOPBACK;
+ else
+ spi_config &= ~SPI_CFG_LOOPBACK;
+
+ msm_spi_add_configs(dd, &spi_config, bpw-1);
+ writel(spi_config, dd->base + SPI_CONFIG);
+ msm_spi_set_qup_config(dd, bpw);
+}
+
+static irqreturn_t msm_spi_input_irq(int irq, void *dev_id)
+{
+ struct msm_spi *dd = dev_id;
+
+ dd->stat_rx++;
+ if (dd->mode == SPI_FIFO_MODE) {
+ while ((readl(dd->base + SPI_OPERATIONAL) &
+ SPI_OP_IP_FIFO_NOT_EMPTY) &&
+ (dd->rx_bytes_remaining > 0)) {
+ msm_spi_read_word_from_fifo(dd);
+ }
+ if (dd->rx_bytes_remaining == 0)
+ msm_spi_complete(dd);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void msm_spi_write_word_to_fifo(struct msm_spi *dd)
+{
+ u32 word = 0;
+ u8 byte;
+ int i;
+
+ if (dd->write_buf) {
+ for (i = 0; (i < dd->bytes_per_word) &&
+ dd->tx_bytes_remaining; i++) {
+ dd->tx_bytes_remaining--;
+ byte = *dd->write_buf++;
+ word |= (byte << (BITS_PER_BYTE * (3 - i)));
+ }
+ } else
+ if (dd->tx_bytes_remaining > dd->bytes_per_word)
+ dd->tx_bytes_remaining -= dd->bytes_per_word;
+ else
+ dd->tx_bytes_remaining = 0;
+
+ dd->write_xfr_cnt++;
+ if (dd->multi_xfr) {
+ if (!dd->tx_bytes_remaining)
+ dd->write_xfr_cnt = 0;
+ else if ((dd->write_xfr_cnt * dd->bytes_per_word) ==
+ dd->write_len) {
+ struct spi_transfer *t = dd->cur_tx_transfer;
+ if (t->transfer_list.next != &dd->cur_msg->transfers) {
+ t = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ dd->write_buf = t->tx_buf;
+ dd->write_len = t->len;
+ dd->write_xfr_cnt = 0;
+ dd->cur_tx_transfer = t;
+ }
+ }
+ }
+
+ writel_relaxed(word, dd->base + SPI_OUTPUT_FIFO);
+}
+
+static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd)
+{
+ int count = 0;
+
+ while ((dd->tx_bytes_remaining > 0) && (count < dd->input_fifo_size) &&
+ !(readl(dd->base + SPI_OPERATIONAL) &
+ SPI_OP_OUTPUT_FIFO_FULL)) {
+ msm_spi_write_word_to_fifo(dd);
+ count++;
+ }
+}
+
+static irqreturn_t msm_spi_output_irq(int irq, void *dev_id)
+{
+ struct msm_spi *dd = dev_id;
+
+ dd->stat_tx++;
+ /* Output FIFO is empty. Transmit any outstanding write data. */
+ if (dd->mode == SPI_FIFO_MODE)
+ msm_spi_write_rmn_to_fifo(dd);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t msm_spi_error_irq(int irq, void *dev_id)
+{
+ struct spi_master *master = dev_id;
+ struct msm_spi *dd = spi_master_get_devdata(master);
+ u32 spi_err;
+
+ spi_err = readl(dd->base + SPI_ERROR_FLAGS);
+ if (spi_err & SPI_ERR_OUTPUT_OVER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI output overrun error\n");
+
+ if (spi_err & SPI_ERR_INPUT_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI input underrun error\n");
+
+ if (spi_err & SPI_ERR_OUTPUT_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI output underrun error\n");
+
+ msm_spi_get_clk_err(dd, &spi_err);
+ if (spi_err & SPI_ERR_CLK_OVER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI clock overrun error\n");
+
+ if (spi_err & SPI_ERR_CLK_UNDER_RUN_ERR)
+ dev_warn(master->dev.parent, "SPI clock underrun error\n");
+
+ msm_spi_clear_error_flags(dd);
+ msm_spi_ack_clk_err(dd);
+
+ return IRQ_HANDLED;
+}
+
+static void msm_spi_process_transfer(struct msm_spi *dd)
+{
+ u8 bpw;
+ u32 spi_ioc;
+ u32 spi_iom;
+ u32 spi_ioc_orig;
+ u32 max_speed;
+ u32 chip_select;
+ u32 read_count;
+ u32 timeout;
+ u32 int_loopback = 0;
+
+ dd->tx_bytes_remaining = dd->cur_msg_len;
+ dd->rx_bytes_remaining = dd->cur_msg_len;
+ dd->read_buf = dd->cur_transfer->rx_buf;
+ dd->write_buf = dd->cur_transfer->tx_buf;
+ init_completion(&dd->transfer_complete);
+ if (dd->cur_transfer->bits_per_word)
+ bpw = dd->cur_transfer->bits_per_word;
+ else
+ if (dd->cur_msg->spi->bits_per_word)
+ bpw = dd->cur_msg->spi->bits_per_word;
+ else
+ bpw = 8;
+
+ dd->bytes_per_word = (bpw + 7) / 8;
+ if (dd->cur_transfer->speed_hz)
+ max_speed = dd->cur_transfer->speed_hz;
+ else
+ max_speed = dd->cur_msg->spi->max_speed_hz;
+
+ if (!dd->clock_speed || max_speed != dd->clock_speed)
+ msm_spi_clock_set(dd, max_speed);
+
+ read_count = DIV_ROUND_UP(dd->cur_msg_len, dd->bytes_per_word);
+ if (dd->cur_msg->spi->mode & SPI_LOOP)
+ int_loopback = 1;
+
+ if (int_loopback && dd->multi_xfr &&
+ (read_count > dd->input_fifo_size)) {
+ if (dd->read_len && dd->write_len)
+ pr_err(
+ "%s:Internal Loopback does not support > fifo size"
+ "for write-then-read transactions\n",
+ __func__);
+ else if (dd->write_len && !dd->read_len)
+ pr_err(
+ "%s:Internal Loopback does not support > fifo size"
+ "for write-then-write transactions\n",
+ __func__);
+
+ return;
+ }
+
+ dd->mode = SPI_FIFO_MODE;
+ if (dd->multi_xfr) {
+ dd->read_len = dd->cur_transfer->len;
+ dd->write_len = dd->cur_transfer->len;
+ }
+
+ /*
+ * read_count cannot exceed fifo_size, and only one READ COUNT
+ * interrupt is generated per transaction, so for transactions
+ * larger than fifo size READ COUNT must be disabled.
+ */
+ if (read_count <= dd->input_fifo_size) {
+ writel(read_count, dd->base + SPI_MX_READ_COUNT);
+ msm_spi_set_write_count(dd, read_count);
+ } else {
+ writel(0, dd->base + SPI_MX_READ_COUNT);
+ msm_spi_set_write_count(dd, 0);
+ }
+
+ spi_iom = readl(dd->base + SPI_IO_MODES);
+ spi_iom &= ~(SPI_IO_M_INPUT_MODE | SPI_IO_M_OUTPUT_MODE);
+ spi_iom = (spi_iom | (dd->mode << OUTPUT_MODE_SHIFT));
+ spi_iom = (spi_iom | (dd->mode << INPUT_MODE_SHIFT));
+ spi_iom &= ~(SPI_IO_M_PACK_EN | SPI_IO_M_UNPACK_EN);
+
+ writel(spi_iom, dd->base + SPI_IO_MODES);
+ msm_spi_set_config(dd, bpw);
+ spi_ioc = readl(dd->base + SPI_IO_CONTROL);
+ spi_ioc_orig = spi_ioc;
+ if (dd->cur_msg->spi->mode & SPI_CPOL)
+ spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH;
+ else
+ spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH;
+
+ chip_select = dd->cur_msg->spi->chip_select << 2;
+ if ((spi_ioc & SPI_IO_C_CS_SELECT) != chip_select)
+ spi_ioc = (spi_ioc & ~SPI_IO_C_CS_SELECT) | chip_select;
+
+ if (!dd->cur_transfer->cs_change)
+ spi_ioc |= SPI_IO_C_MX_CS_MODE;
+
+ if (spi_ioc != spi_ioc_orig)
+ writel(spi_ioc, dd->base + SPI_IO_CONTROL);
+
+ /*
+ * The output fifo interrupt handler will handle all writes
+ * after the first. Restricting this to one write avoids
+ * contention issues and race conditions between this thread
+ * and the int handler.
+ */
+ if (msm_spi_prepare_for_write(dd))
+ goto transfer_end;
+ msm_spi_start_write(dd, read_count);
+
+ /*
+ * Only enter the RUN state after the first word is written into
+ * the output FIFO. Otherwise, the output FIFO EMPTY interrupt
+ * might fire before the first word is written resulting in a
+ * possible race condition.
+ */
+ if (msm_spi_set_state(dd, SPI_OP_STATE_RUN))
+ goto transfer_end;
+
+ timeout = 100 * msecs_to_jiffies(
+ DIV_ROUND_UP(dd->cur_msg_len * 8,
+ DIV_ROUND_UP(max_speed, MSEC_PER_SEC)));
+
+ /* Assume success, this might change later upon transaction result */
+ dd->cur_msg->status = 0;
+ if (!wait_for_completion_timeout(&dd->transfer_complete, timeout)) {
+ dev_err(dd->dev, "%s: SPI transaction timeout\n", __func__);
+ dd->cur_msg->status = -EIO;
+ }
+
+transfer_end:
+ dd->mode = SPI_MODE_NONE;
+ msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ writel(spi_ioc & ~SPI_IO_C_MX_CS_MODE, dd->base + SPI_IO_CONTROL);
+}
+
+static void get_transfer_length(struct msm_spi *dd)
+{
+ struct spi_transfer *tr;
+ int num_xfrs = 0;
+ int readlen = 0;
+ int writelen = 0;
+
+ dd->cur_msg_len = 0;
+ dd->multi_xfr = 0;
+ dd->read_len = dd->write_len = 0;
+
+ list_for_each_entry(tr, &dd->cur_msg->transfers, transfer_list) {
+ if (tr->tx_buf)
+ writelen += tr->len;
+ if (tr->rx_buf)
+ readlen += tr->len;
+ dd->cur_msg_len += tr->len;
+ num_xfrs++;
+ }
+
+ if (num_xfrs == 2) {
+ struct spi_transfer *first_xfr = dd->cur_transfer;
+
+ dd->multi_xfr = 1;
+ tr = list_entry(first_xfr->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ /*
+ * We update dd->read_len and dd->write_len only
+ * for WR-WR and WR-RD transfers.
+ */
+ if ((first_xfr->tx_buf) && (!first_xfr->rx_buf)) {
+ if (((tr->tx_buf) && (!tr->rx_buf)) ||
+ ((!tr->tx_buf) && (tr->rx_buf))) {
+ dd->read_len = readlen;
+ dd->write_len = writelen;
+ }
+ }
+ } else if (num_xfrs > 1)
+ dd->multi_xfr = 1;
+}
+
+static inline int combine_transfers(struct msm_spi *dd)
+{
+ struct spi_transfer *t = dd->cur_transfer;
+ struct spi_transfer *nxt;
+ int xfrs_grped = 1;
+
+ dd->cur_msg_len = dd->cur_transfer->len;
+ while (t->transfer_list.next != &dd->cur_msg->transfers) {
+ nxt = list_entry(t->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ if (t->cs_change != nxt->cs_change)
+ return xfrs_grped;
+ dd->cur_msg_len += nxt->len;
+ xfrs_grped++;
+ t = nxt;
+ }
+
+ return xfrs_grped;
+}
+
+static void msm_spi_process_message(struct msm_spi *dd)
+{
+ int xfrs_grped = 0;
+ dd->write_xfr_cnt = dd->read_xfr_cnt = 0;
+
+ dd->cur_transfer = list_first_entry(&dd->cur_msg->transfers,
+ struct spi_transfer,
+ transfer_list);
+ get_transfer_length(dd);
+ if (dd->multi_xfr && !dd->read_len && !dd->write_len) {
+ /* Handling of multi-transfers. FIFO mode is used by default */
+ list_for_each_entry(dd->cur_transfer,
+ &dd->cur_msg->transfers,
+ transfer_list) {
+ if (!dd->cur_transfer->len)
+ return;
+
+ if (xfrs_grped) {
+ xfrs_grped--;
+ continue;
+ } else {
+ dd->read_len = dd->write_len = 0;
+ xfrs_grped = combine_transfers(dd);
+ }
+
+ dd->cur_tx_transfer = dd->cur_transfer;
+ dd->cur_rx_transfer = dd->cur_transfer;
+ msm_spi_process_transfer(dd);
+ xfrs_grped--;
+ }
+ } else {
+ dd->cur_tx_transfer = dd->cur_rx_transfer = dd->cur_transfer;
+ msm_spi_process_transfer(dd);
+ }
+}
+
+/* workqueue - pull messages from queue and process */
+static void msm_spi_workq(struct work_struct *work)
+{
+ struct msm_spi *dd =
+ container_of(work, struct msm_spi, work_data);
+ unsigned long flags;
+ u32 status_error = 0;
+
+ mutex_lock(&dd->core_lock);
+ clk_enable(dd->clk);
+ clk_enable(dd->pclk);
+ msm_spi_enable_irqs(dd);
+ if (!msm_spi_is_valid_state(dd)) {
+ dev_err(dd->dev, "%s: SPI operational state not valid\n",
+ __func__);
+ status_error = 1;
+ }
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ while (!list_empty(&dd->queue)) {
+ dd->cur_msg = list_entry(dd->queue.next,
+ struct spi_message, queue);
+ list_del_init(&dd->cur_msg->queue);
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ if (status_error)
+ dd->cur_msg->status = -EIO;
+ else
+ msm_spi_process_message(dd);
+
+ if (dd->cur_msg->complete)
+ dd->cur_msg->complete(dd->cur_msg->context);
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ }
+
+ dd->transfer_pending = 0;
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+
+ msm_spi_disable_irqs(dd);
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ mutex_unlock(&dd->core_lock);
+
+ /*
+ * If needed, this can be done after the current message is complete,
+ * and work can be continued upon resume. No motivation for now.
+ */
+ if (dd->suspended)
+ wake_up_interruptible(&dd->continue_suspend);
+}
+
+static int msm_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct msm_spi *dd;
+ unsigned long flags;
+ struct spi_transfer *tr;
+
+ dd = spi_master_get_devdata(spi->master);
+ if (dd->suspended)
+ return -EBUSY;
+
+ if (list_empty(&msg->transfers) || !msg->complete)
+ return -EINVAL;
+
+ list_for_each_entry(tr, &msg->transfers, transfer_list) {
+ /* Check message parameters */
+ if (tr->speed_hz > dd->pdata->max_clock_speed ||
+ (tr->bits_per_word &&
+ (tr->bits_per_word < 4 || tr->bits_per_word > 32)) ||
+ (tr->tx_buf == NULL && tr->rx_buf == NULL)) {
+ dev_err(&spi->dev, "Invalid transfer: %d Hz, %d bpw"
+ "tx=%p, rx=%p\n",
+ tr->speed_hz, tr->bits_per_word,
+ tr->tx_buf, tr->rx_buf);
+ return -EINVAL;
+ }
+ }
+
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ if (dd->suspended) {
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ return -EBUSY;
+ }
+
+ dd->transfer_pending = 1;
+ list_add_tail(&msg->queue, &dd->queue);
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+ queue_work(dd->workqueue, &dd->work_data);
+ return 0;
+}
+
+static int msm_spi_setup(struct spi_device *spi)
+{
+ struct msm_spi *dd;
+ int rc = 0;
+ u32 spi_ioc;
+ u32 spi_config;
+ u32 mask;
+
+ if (spi->bits_per_word < 4 || spi->bits_per_word > 32) {
+ dev_err(&spi->dev, "%s: invalid bits_per_word %d\n",
+ __func__, spi->bits_per_word);
+ rc = -EINVAL;
+ }
+
+ if (spi->chip_select > SPI_NUM_CHIPSELECTS-1) {
+ dev_err(&spi->dev, "%s, chip select %d exceeds max value %d\n",
+ __func__, spi->chip_select, SPI_NUM_CHIPSELECTS - 1);
+ rc = -EINVAL;
+ }
+
+ if (rc)
+ goto err_setup_exit;
+
+ dd = spi_master_get_devdata(spi->master);
+
+ mutex_lock(&dd->core_lock);
+ if (dd->suspended) {
+ mutex_unlock(&dd->core_lock);
+ return -EBUSY;
+ }
+
+ clk_enable(dd->clk);
+ clk_enable(dd->pclk);
+ spi_ioc = readl(dd->base + SPI_IO_CONTROL);
+ mask = SPI_IO_C_CS_N_POLARITY_0 << spi->chip_select;
+ if (spi->mode & SPI_CS_HIGH)
+ spi_ioc |= mask;
+ else
+ spi_ioc &= ~mask;
+
+ if (spi->mode & SPI_CPOL)
+ spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH;
+ else
+ spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH;
+
+ writel(spi_ioc, dd->base + SPI_IO_CONTROL);
+ spi_config = readl(dd->base + SPI_CONFIG);
+ if (spi->mode & SPI_LOOP)
+ spi_config |= SPI_CFG_LOOPBACK;
+ else
+ spi_config &= ~SPI_CFG_LOOPBACK;
+
+ if (spi->mode & SPI_CPHA)
+ spi_config &= ~SPI_CFG_INPUT_FIRST;
+ else
+ spi_config |= SPI_CFG_INPUT_FIRST;
+
+ writel(spi_config, dd->base + SPI_CONFIG);
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ mutex_unlock(&dd->core_lock);
+
+err_setup_exit:
+ return rc;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int debugfs_iomem_x32_set(void *data, u64 val)
+{
+ iowrite32(val, data);
+ /* Ensure the previous write completed. */
+ wmb();
+ return 0;
+}
+
+static int debugfs_iomem_x32_get(void *data, u64 *val)
+{
+ *val = ioread32(data);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get,
+ debugfs_iomem_x32_set, "0x%08llx\n");
+
+static void spi_debugfs_init(struct msm_spi *dd)
+{
+ dd->dent_spi = debugfs_create_dir(dev_name(dd->dev), NULL);
+ if (dd->dent_spi) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++) {
+ dd->debugfs_spi_regs[i] =
+ debugfs_create_file(
+ debugfs_spi_regs[i].name,
+ debugfs_spi_regs[i].mode,
+ dd->dent_spi,
+ dd->base + debugfs_spi_regs[i].offset,
+ &fops_iomem_x32);
+ }
+ }
+}
+
+static void spi_debugfs_exit(struct msm_spi *dd)
+{
+ if (dd->dent_spi) {
+ int i;
+
+ debugfs_remove_recursive(dd->dent_spi);
+ dd->dent_spi = NULL;
+ for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++)
+ dd->debugfs_spi_regs[i] = NULL;
+ }
+}
+#else
+static void spi_debugfs_init(struct msm_spi *dd) {}
+static void spi_debugfs_exit(struct msm_spi *dd) {}
+#endif
+
+/* ===Device attributes begin=== */
+static ssize_t show_stats(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct msm_spi *dd = spi_master_get_devdata(master);
+
+ return snprintf(buf, PAGE_SIZE,
+ "Device %s\n"
+ "rx fifo_size = %d spi words\n"
+ "tx fifo_size = %d spi words\n"
+ "rx block size = %d bytes\n"
+ "tx block size = %d bytes\n"
+ "--statistics--\n"
+ "Rx isrs = %d\n"
+ "Tx isrs = %d\n"
+ "--debug--\n"
+ "NA yet\n",
+ dev_name(dev),
+ dd->input_fifo_size,
+ dd->output_fifo_size,
+ dd->input_block_size,
+ dd->output_block_size,
+ dd->stat_rx,
+ dd->stat_tx
+ );
+}
+
+/* Reset statistics on write */
+static ssize_t set_stats(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct msm_spi *dd = dev_get_drvdata(dev);
+
+ dd->stat_rx = 0;
+ dd->stat_tx = 0;
+ return count;
+}
+
+static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats);
+
+static struct attribute *dev_attrs[] = {
+ &dev_attr_stats.attr,
+ NULL,
+};
+
+static struct attribute_group dev_attr_grp = {
+ .attrs = dev_attrs,
+};
+/* ===Device attributes end=== */
+
+static int __init msm_spi_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct msm_spi *dd;
+ struct resource *resource;
+ int rc = -ENXIO;
+ int locked = 0;
+ int clk_enabled = 0;
+ int pclk_enabled = 0;
+ struct msm_spi_platform_data *pdata = pdev->dev.platform_data;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct msm_spi));
+ if (!master) {
+ rc = -ENOMEM;
+ dev_err(&pdev->dev, "master allocation failed\n");
+ goto err_probe_exit;
+ }
+
+ master->bus_num = pdev->id;
+ master->mode_bits = SPI_SUPPORTED_MODES;
+ master->num_chipselect = SPI_NUM_CHIPSELECTS;
+ master->setup = msm_spi_setup;
+ master->transfer = msm_spi_transfer;
+ platform_set_drvdata(pdev, master);
+ dd = spi_master_get_devdata(master);
+
+ dd->pdata = pdata;
+ rc = msm_spi_get_irq_data(dd, pdev);
+ if (rc)
+ goto err_probe_res;
+
+ resource = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "spi_base");
+ if (!resource) {
+ rc = -ENXIO;
+ goto err_probe_res;
+ }
+
+ dd->mem_phys_addr = resource->start;
+ dd->mem_size = resource_size(resource);
+
+ spin_lock_init(&dd->queue_lock);
+ mutex_init(&dd->core_lock);
+ INIT_LIST_HEAD(&dd->queue);
+ INIT_WORK(&dd->work_data, msm_spi_workq);
+ init_waitqueue_head(&dd->continue_suspend);
+ dd->workqueue = create_singlethread_workqueue(
+ dev_name(master->dev.parent));
+ if (!dd->workqueue)
+ goto err_probe_res;
+
+ if (!devm_request_mem_region(&pdev->dev, dd->mem_phys_addr,
+ dd->mem_size, SPI_DRV_NAME)) {
+ rc = -ENXIO;
+ goto err_probe_reqmem;
+ }
+
+ dd->base = devm_ioremap(&pdev->dev, dd->mem_phys_addr, dd->mem_size);
+ if (!dd->base) {
+ rc = -ENOMEM;
+ goto err_probe_reqmem;
+ }
+
+
+ mutex_lock(&dd->core_lock);
+ locked = 1;
+ dd->dev = &pdev->dev;
+ dd->clk = clk_get(&pdev->dev, "spi_clk");
+ if (IS_ERR(dd->clk)) {
+ dev_err(&pdev->dev, "%s: unable to get spi_clk\n", __func__);
+ rc = PTR_ERR(dd->clk);
+ goto err_probe_clk_get;
+ }
+
+ dd->pclk = clk_get(&pdev->dev, "spi_pclk");
+ if (IS_ERR(dd->pclk)) {
+ dev_err(&pdev->dev, "%s: unable to get spi_pclk\n", __func__);
+ rc = PTR_ERR(dd->pclk);
+ goto err_probe_pclk_get;
+ }
+
+ if (pdata && pdata->max_clock_speed)
+ msm_spi_clock_set(dd, dd->pdata->max_clock_speed);
+
+ rc = clk_enable(dd->clk);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to enable spi_clk\n",
+ __func__);
+ goto err_probe_clk_enable;
+ }
+
+ clk_enabled = 1;
+ rc = clk_enable(dd->pclk);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to enable spi_pclk\n",
+ __func__);
+ goto err_probe_pclk_enable;
+ }
+
+ pclk_enabled = 1;
+ rc = msm_spi_configure_gsbi(dd, pdev);
+ if (rc)
+ goto err_probe_gsbi;
+
+ msm_spi_calculate_fifo_size(dd);
+
+ /* Initialize registers */
+ writel(0x00000001, dd->base + SPI_SW_RESET);
+ msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ writel(0x00000000, dd->base + SPI_OPERATIONAL);
+ writel(0x00000000, dd->base + SPI_CONFIG);
+ writel(0x00000000, dd->base + SPI_IO_MODES);
+ /*
+ * The SPI core generates a bogus input overrun error on some targets,
+ * when a transition from run to reset state occurs and if the FIFO has
+ * an odd number of entries. Hence we disable the INPUT_OVER_RUN_ERR_EN
+ * bit.
+ */
+ msm_spi_enable_error_flags(dd);
+
+ writel(SPI_IO_C_NO_TRI_STATE, dd->base + SPI_IO_CONTROL);
+ rc = msm_spi_set_state(dd, SPI_OP_STATE_RESET);
+ if (rc)
+ goto err_probe_state;
+
+ clk_disable(dd->clk);
+ clk_disable(dd->pclk);
+ clk_enabled = 0;
+ pclk_enabled = 0;
+
+ dd->suspended = 0;
+ dd->transfer_pending = 0;
+ dd->multi_xfr = 0;
+ dd->mode = SPI_MODE_NONE;
+
+ rc = msm_spi_request_irq(dd, pdev->name, master);
+ if (rc)
+ goto err_probe_irq;
+
+ msm_spi_disable_irqs(dd);
+ mutex_unlock(&dd->core_lock);
+ locked = 0;
+
+ rc = spi_register_master(master);
+ if (rc)
+ goto err_probe_reg_master;
+
+ rc = sysfs_create_group(&(dd->dev->kobj), &dev_attr_grp);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc);
+ goto err_attrs;
+ }
+
+ spi_debugfs_init(dd);
+
+ return 0;
+
+err_attrs:
+ spi_unregister_master(master);
+err_probe_reg_master:
+ msm_spi_free_irq(dd, master);
+err_probe_irq:
+err_probe_state:
+err_probe_gsbi:
+ if (pclk_enabled)
+ clk_disable(dd->pclk);
+err_probe_pclk_enable:
+ if (clk_enabled)
+ clk_disable(dd->clk);
+err_probe_clk_enable:
+ clk_put(dd->pclk);
+err_probe_pclk_get:
+ clk_put(dd->clk);
+err_probe_clk_get:
+ if (locked)
+ mutex_unlock(&dd->core_lock);
+err_probe_reqmem:
+ destroy_workqueue(dd->workqueue);
+err_probe_res:
+ spi_master_put(master);
+err_probe_exit:
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int msm_spi_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd;
+ unsigned long flags;
+
+ if (!master)
+ goto suspend_exit;
+
+ dd = spi_master_get_devdata(master);
+ if (!dd)
+ goto suspend_exit;
+
+ /* Make sure nothing is added to the queue while we're suspending */
+ spin_lock_irqsave(&dd->queue_lock, flags);
+ dd->suspended = 1;
+ spin_unlock_irqrestore(&dd->queue_lock, flags);
+
+ /* Wait for transactions to end, or time out */
+ wait_event_interruptible(dd->continue_suspend, !dd->transfer_pending);
+
+suspend_exit:
+ return 0;
+}
+
+static int msm_spi_resume(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd;
+
+ if (!master)
+ goto resume_exit;
+
+ dd = spi_master_get_devdata(master);
+ if (!dd)
+ goto resume_exit;
+
+ dd->suspended = 0;
+resume_exit:
+ return 0;
+}
+#else
+#define msm_spi_suspend NULL
+#define msm_spi_resume NULL
+#endif /* CONFIG_PM */
+
+static int __devexit msm_spi_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct msm_spi *dd = spi_master_get_devdata(master);
+
+ spi_debugfs_exit(dd);
+ sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp);
+
+ msm_spi_free_irq(dd, master);
+ clk_put(dd->clk);
+ clk_put(dd->pclk);
+ destroy_workqueue(dd->workqueue);
+ platform_set_drvdata(pdev, 0);
+ spi_unregister_master(master);
+ spi_master_put(master);
+
+ return 0;
+}
+
+static struct platform_driver msm_spi_driver = {
+ .driver = {
+ .name = SPI_DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .suspend = msm_spi_suspend,
+ .resume = msm_spi_resume,
+ .remove = __exit_p(msm_spi_remove),
+};
+
+static int __init msm_spi_init(void)
+{
+ return platform_driver_probe(&msm_spi_driver, msm_spi_probe);
+}
+module_init(msm_spi_init);
+
+static void __exit msm_spi_exit(void)
+{
+ platform_driver_unregister(&msm_spi_driver);
+}
+module_exit(msm_spi_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.3");
+MODULE_ALIAS("platform:"SPI_DRV_NAME);
diff --git a/drivers/spi/spi-qup.h b/drivers/spi/spi-qup.h
new file mode 100644
index 0000000..be0dc66
--- /dev/null
+++ b/drivers/spi/spi-qup.h
@@ -0,0 +1,436 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#ifndef _SPI_QUP_H
+#define _SPI_QUP_H
+
+#define SPI_DRV_NAME "spi_qup"
+
+#define QUP_CONFIG 0x0000 /* N & NO_INPUT/NO_OUPUT bits */
+#define QUP_ERROR_FLAGS 0x0308
+#define QUP_ERROR_FLAGS_EN 0x030C
+#define QUP_ERR_MASK 0x3
+#define SPI_OUTPUT_FIFO_WORD_CNT 0x010C
+#define SPI_INPUT_FIFO_WORD_CNT 0x0214
+#define QUP_MX_WRITE_COUNT 0x0150
+#define QUP_MX_WRITE_CNT_CURRENT 0x0154
+
+#define QUP_CONFIG_SPI_MODE 0x0100
+
+#define GSBI_CTRL_REG 0x0
+#define GSBI_SPI_CONFIG 0x30
+
+#define SPI_CONFIG 0x0300
+#define SPI_IO_CONTROL 0x0304
+#define SPI_IO_MODES 0x0008
+#define SPI_SW_RESET 0x000C
+#define SPI_TIME_OUT 0x0010
+#define SPI_TIME_OUT_CURRENT 0x0014
+#define SPI_MX_OUTPUT_COUNT 0x0100
+#define SPI_MX_OUTPUT_CNT_CURRENT 0x0104
+#define SPI_MX_INPUT_COUNT 0x0200
+#define SPI_MX_INPUT_CNT_CURRENT 0x0204
+#define SPI_MX_READ_COUNT 0x0208
+#define SPI_MX_READ_CNT_CURRENT 0x020C
+#define SPI_OPERATIONAL 0x0018
+#define SPI_ERROR_FLAGS 0x001C
+#define SPI_ERROR_FLAGS_EN 0x0020
+#define SPI_DEASSERT_WAIT 0x0310
+#define SPI_OUTPUT_DEBUG 0x0108
+#define SPI_INPUT_DEBUG 0x0210
+#define SPI_TEST_CTRL 0x0024
+#define SPI_OUTPUT_FIFO 0x0110
+#define SPI_INPUT_FIFO 0x0218
+#define SPI_STATE 0x0004
+
+/* SPI_CONFIG fields */
+#define SPI_CFG_INPUT_FIRST 0x00000200
+#define SPI_NO_INPUT 0x00000080
+#define SPI_NO_OUTPUT 0x00000040
+#define SPI_CFG_LOOPBACK 0x00000100
+#define SPI_CFG_N 0x0000001F
+
+/* SPI_IO_CONTROL fields */
+#define SPI_IO_C_CLK_IDLE_HIGH 0x00000400
+#define SPI_IO_C_MX_CS_MODE 0x00000100
+#define SPI_IO_C_CS_N_POLARITY 0x000000F0
+#define SPI_IO_C_CS_N_POLARITY_0 0x00000010
+#define SPI_IO_C_CS_SELECT 0x0000000C
+#define SPI_IO_C_TRISTATE_CS 0x00000002
+#define SPI_IO_C_NO_TRI_STATE 0x00000001
+
+/* SPI_IO_MODES fields */
+#define SPI_IO_M_PACK_EN 0x00008000
+#define SPI_IO_M_UNPACK_EN 0x00004000
+#define SPI_IO_M_INPUT_MODE 0x00003000
+#define SPI_IO_M_OUTPUT_MODE 0x00000C00
+#define SPI_IO_M_INPUT_FIFO_SIZE 0x00000380
+#define SPI_IO_M_INPUT_BLOCK_SIZE 0x00000060
+#define SPI_IO_M_OUTPUT_FIFO_SIZE 0x0000001C
+#define SPI_IO_M_OUTPUT_BLOCK_SIZE 0x00000003
+
+#define INPUT_BLOCK_SZ_SHIFT 5
+#define INPUT_FIFO_SZ_SHIFT 7
+#define OUTPUT_BLOCK_SZ_SHIFT 0
+#define OUTPUT_FIFO_SZ_SHIFT 2
+#define OUTPUT_MODE_SHIFT 10
+#define INPUT_MODE_SHIFT 12
+
+/* SPI_OPERATIONAL fields */
+#define SPI_OP_MAX_INPUT_DONE_FLAG 0x00000800
+#define SPI_OP_MAX_OUTPUT_DONE_FLAG 0x00000400
+#define SPI_OP_INPUT_SERVICE_FLAG 0x00000200
+#define SPI_OP_OUTPUT_SERVICE_FLAG 0x00000100
+#define SPI_OP_INPUT_FIFO_FULL 0x00000080
+#define SPI_OP_OUTPUT_FIFO_FULL 0x00000040
+#define SPI_OP_IP_FIFO_NOT_EMPTY 0x00000020
+#define SPI_OP_OP_FIFO_NOT_EMPTY 0x00000010
+#define SPI_OP_STATE_VALID 0x00000004
+#define SPI_OP_STATE 0x00000003
+#define SPI_OP_STATE_CLEAR_BITS 0x2
+
+/* SPI_ERROR_FLAGS fields */
+#define SPI_ERR_OUTPUT_OVER_RUN_ERR 0x00000020
+#define SPI_ERR_INPUT_UNDER_RUN_ERR 0x00000010
+#define SPI_ERR_OUTPUT_UNDER_RUN_ERR 0x00000008
+#define SPI_ERR_INPUT_OVER_RUN_ERR 0x00000004
+#define SPI_ERR_CLK_OVER_RUN_ERR 0x00000002
+#define SPI_ERR_CLK_UNDER_RUN_ERR 0x00000001
+
+#define SPI_NUM_CHIPSELECTS 4
+#define SPI_SUPPORTED_MODES (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP)
+
+#define SPI_DELAY_THRESHOLD 1
+/* Default timeout is 10 milliseconds */
+#define SPI_DEFAULT_TIMEOUT 10
+
+enum msm_spi_state {
+ SPI_OP_STATE_RESET = 0x00000000,
+ SPI_OP_STATE_RUN = 0x00000001,
+ SPI_OP_STATE_PAUSE = 0x00000003,
+};
+
+enum msm_spi_mode {
+ SPI_FIFO_MODE = 0x0, /* 00 */
+ SPI_BLOCK_MODE = 0x1, /* 01 */
+ SPI_DMOV_MODE = 0x2, /* 10 */
+ SPI_MODE_NONE = 0xFF, /* invalid value */
+};
+
+#ifdef CONFIG_DEBUG_FS
+static const struct {
+ const char *name;
+ mode_t mode;
+ int offset;
+} debugfs_spi_regs[] = {
+ {"config", S_IRUGO | S_IWUSR, SPI_CONFIG},
+ {"io_control", S_IRUGO | S_IWUSR, SPI_IO_CONTROL},
+ {"io_modes", S_IRUGO | S_IWUSR, SPI_IO_MODES},
+ {"sw_reset", S_IWUSR, SPI_SW_RESET},
+ {"time_out", S_IRUGO | S_IWUSR, SPI_TIME_OUT},
+ {"time_out_current", S_IRUGO, SPI_TIME_OUT_CURRENT},
+ {"mx_output_count", S_IRUGO | S_IWUSR, SPI_MX_OUTPUT_COUNT},
+ {"mx_output_cnt_current", S_IRUGO, SPI_MX_OUTPUT_CNT_CURRENT},
+ {"mx_input_count", S_IRUGO | S_IWUSR, SPI_MX_INPUT_COUNT},
+ {"mx_input_cnt_current", S_IRUGO, SPI_MX_INPUT_CNT_CURRENT},
+ {"mx_read_count", S_IRUGO | S_IWUSR, SPI_MX_READ_COUNT},
+ {"mx_read_cnt_current", S_IRUGO, SPI_MX_READ_CNT_CURRENT},
+ {"operational", S_IRUGO | S_IWUSR, SPI_OPERATIONAL},
+ {"error_flags", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS},
+ {"error_flags_en", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS_EN},
+ {"deassert_wait", S_IRUGO | S_IWUSR, SPI_DEASSERT_WAIT},
+ {"output_debug", S_IRUGO, SPI_OUTPUT_DEBUG},
+ {"input_debug", S_IRUGO, SPI_INPUT_DEBUG},
+ {"test_ctrl", S_IRUGO | S_IWUSR, SPI_TEST_CTRL},
+ {"output_fifo", S_IWUSR, SPI_OUTPUT_FIFO},
+ {"input_fifo" , S_IRUSR, SPI_INPUT_FIFO},
+ {"spi_state", S_IRUGO | S_IWUSR, SPI_STATE},
+ {"qup_config", S_IRUGO | S_IWUSR, QUP_CONFIG},
+ {"qup_error_flags", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS},
+ {"qup_error_flags_en", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS_EN},
+ {"mx_write_cnt", S_IRUGO | S_IWUSR, QUP_MX_WRITE_COUNT},
+ {"mx_write_cnt_current", S_IRUGO, QUP_MX_WRITE_CNT_CURRENT},
+ {"output_fifo_word_cnt", S_IRUGO, SPI_OUTPUT_FIFO_WORD_CNT},
+ {"input_fifo_word_cnt", S_IRUGO, SPI_INPUT_FIFO_WORD_CNT},
+};
+#endif
+
+/**
+ * struct msm_spi
+ * @read_buf: rx_buf from the spi_transfer.
+ * @write_buf: tx_buf from the spi_transfer.
+ * @base: location of QUP controller I/O area in memory.
+ * @dev: parent platform device.
+ * @queue_lock: lock to protect queue.
+ * @core_lock: mutex used to protect this struct.
+ * @queue: to log SPI transfer requests.
+ * @workqueue: workqueue for the SPI transfer requests.
+ * @work_data: work.
+ * @cur_msg: the current spi_message being processed.
+ * @cur_transfer: the current spi_transfer being processed.
+ * @transfer_complete: completion function to signal the end of a spi_transfer.
+ * @clk: the SPI core clock
+ * @pclk: hardware core clock. Needs to be enabled to access the QUP register
+ * @mem_phys_addr: physical address of the QUP controller.
+ * @mem_size: size of the QUP controller block.
+ * @input_fifo_size: the input FIFO size (in bytes).
+ * @output_fifo_size: the output FIFO size (in bytes).
+ * @rx_bytes_remaining: the number of rx bytes remaining to be transferred.
+ * @tx_bytes_remaining: the number of tx bytes remaining to be transferred.
+ * @clock_speed: SPI clock speed.
+ * @irq_in: assigned interrupt line for QUP interrupts.
+ * @read_xfr_cnt: number of words read from the FIFO (per transfer).
+ * @write_xfr_cnt: number of words written to the FIFO (per transfer).
+ * @write_len: the total number of tx bytes to be transferred, per
+ * spi_message. This is valid only for WR-WR and WR-RD transfers
+ * in a single spi_message.
+ * @read_len: the total number of rx bytes to be transferred, per
+ * spi_message. This is valid only for WR-WR and WR-RD transfers
+ * in a single spi_message.
+ * @bytes_per_word: bytes per word
+ * @suspended: the suspend state.
+ * @transfer_pending: when set indicates a pending transfer.
+ * @continue_suspend: head of wait queue.
+ * @mode: mode for SPI operation.
+ * @input_block_size: the input block size (in bytes).
+ * @output_block_size: the output block size (in bytes).
+ * @stat_rx: count of input interrupts handled.
+ * @stat_tx: count of output interrupts handled.
+ * @dent_spi: used for debug purposes.
+ * @debugfs_spi_regs: used for debug purposes.
+ * @pdata: platform data
+ * @multi_xfr: when set indicates multiple spi_transfers in a single
+ * spi_message.
+ * @done: flag used to signal completion.
+ * @cur_msg_len: combined length of all the transfers in a single
+ * spi_message (in bytes).
+ * @cur_tx_transfer: the current tx transfer being processed. Used in
+ * FIFO mode only.
+ * @cur_rx_transfer: the current rx transfer being processed. Used in
+ * FIFO mode only.
+ *
+ * Early QUP controller used three separate interrupt lines for input, output,
+ * and error interrupts. Later versions share a single interrupt line.
+ */
+struct msm_spi {
+ u8 *read_buf;
+ const u8 *write_buf;
+ void __iomem *base;
+ struct device *dev;
+ spinlock_t queue_lock;
+ struct mutex core_lock;
+ struct list_head queue;
+ struct workqueue_struct *workqueue;
+ struct work_struct work_data;
+ struct spi_message *cur_msg;
+ struct spi_transfer *cur_transfer;
+ struct completion transfer_complete;
+ struct clk *clk;
+ struct clk *pclk;
+ unsigned long mem_phys_addr;
+ size_t mem_size;
+ int input_fifo_size;
+ int output_fifo_size;
+ u32 rx_bytes_remaining;
+ u32 tx_bytes_remaining;
+ u32 clock_speed;
+ int irq_in;
+ int read_xfr_cnt;
+ int write_xfr_cnt;
+ int write_len;
+ int read_len;
+ int bytes_per_word;
+ bool suspended;
+ bool transfer_pending;
+ wait_queue_head_t continue_suspend;
+ enum msm_spi_mode mode;
+ int input_block_size;
+ int output_block_size;
+ int stat_rx;
+ int stat_tx;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dent_spi;
+ struct dentry *debugfs_spi_regs[ARRAY_SIZE(debugfs_spi_regs)];
+#endif
+ struct msm_spi_platform_data *pdata;
+ bool multi_xfr;
+ bool done;
+ u32 cur_msg_len;
+ struct spi_transfer *cur_tx_transfer;
+ struct spi_transfer *cur_rx_transfer;
+};
+
+/* Forward declaration */
+static irqreturn_t msm_spi_input_irq(int irq, void *dev_id);
+static irqreturn_t msm_spi_output_irq(int irq, void *dev_id);
+static irqreturn_t msm_spi_error_irq(int irq, void *dev_id);
+static inline int msm_spi_set_state(struct msm_spi *dd,
+ enum msm_spi_state state);
+static void msm_spi_write_word_to_fifo(struct msm_spi *dd);
+static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd);
+
+/* In QUP the same interrupt line is used for input, output and error */
+static inline int msm_spi_get_irq_data(struct msm_spi *dd,
+ struct platform_device *pdev)
+{
+ dd->irq_in = platform_get_irq_byname(pdev, "spi_irq_in");
+ if (dd->irq_in < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int msm_spi_configure_gsbi(struct msm_spi *dd,
+ struct platform_device *pdev)
+{
+ struct resource *resource;
+ unsigned long gsbi_mem_phys_addr;
+ size_t gsbi_mem_size;
+ void __iomem *gsbi_base;
+
+ resource = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "gsbi_base");
+ if (!resource)
+ return -ENXIO;
+
+ gsbi_mem_phys_addr = resource->start;
+ gsbi_mem_size = resource_size(resource);
+ if (!devm_request_mem_region(&pdev->dev, gsbi_mem_phys_addr,
+ gsbi_mem_size, SPI_DRV_NAME))
+ return -ENXIO;
+
+ gsbi_base = devm_ioremap(&pdev->dev, gsbi_mem_phys_addr,
+ gsbi_mem_size);
+ if (!gsbi_base)
+ return -ENXIO;
+
+ /* Set GSBI to SPI mode */
+ writel(GSBI_SPI_CONFIG, gsbi_base + GSBI_CTRL_REG);
+
+ return 0;
+}
+
+/* Figure which irq occured and call the relevant functions */
+static irqreturn_t msm_spi_qup_irq(int irq, void *dev_id)
+{
+ u32 op, ret = IRQ_NONE;
+ struct msm_spi *dd = dev_id;
+
+ if (readl(dd->base + SPI_ERROR_FLAGS) ||
+ readl(dd->base + QUP_ERROR_FLAGS)) {
+ struct spi_master *master = dev_get_drvdata(dd->dev);
+ ret |= msm_spi_error_irq(irq, master);
+ }
+
+ op = readl(dd->base + SPI_OPERATIONAL);
+ if (op & SPI_OP_INPUT_SERVICE_FLAG) {
+ writel(SPI_OP_INPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
+ ret |= msm_spi_input_irq(irq, dev_id);
+ }
+
+ if (op & SPI_OP_OUTPUT_SERVICE_FLAG) {
+ writel(SPI_OP_OUTPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
+ ret |= msm_spi_output_irq(irq, dev_id);
+ }
+
+ if (dd->done) {
+ complete(&dd->transfer_complete);
+ dd->done = 0;
+ }
+ return ret;
+}
+
+static inline int msm_spi_request_irq(struct msm_spi *dd,
+ const char *name,
+ struct spi_master *master)
+{
+ return request_irq(dd->irq_in, msm_spi_qup_irq, IRQF_TRIGGER_HIGH,
+ name, dd);
+}
+
+static inline void msm_spi_free_irq(struct msm_spi *dd,
+ struct spi_master *master)
+{
+ free_irq(dd->irq_in, dd);
+}
+
+static inline void msm_spi_disable_irqs(struct msm_spi *dd)
+{
+ disable_irq(dd->irq_in);
+}
+
+static inline void msm_spi_enable_irqs(struct msm_spi *dd)
+{
+ enable_irq(dd->irq_in);
+}
+
+static inline void msm_spi_get_clk_err(struct msm_spi *dd, u32 *spi_err)
+{
+ *spi_err = readl(dd->base + QUP_ERROR_FLAGS);
+}
+
+static inline void msm_spi_ack_clk_err(struct msm_spi *dd)
+{
+ writel(QUP_ERR_MASK, dd->base + QUP_ERROR_FLAGS);
+}
+
+static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n);
+
+/* QUP has no_input, no_output, and N bits at QUP_CONFIG */
+static inline void msm_spi_set_qup_config(struct msm_spi *dd, int bpw)
+{
+ u32 qup_config = readl(dd->base + QUP_CONFIG);
+
+ msm_spi_add_configs(dd, &qup_config, bpw-1);
+ writel(qup_config | QUP_CONFIG_SPI_MODE, dd->base + QUP_CONFIG);
+}
+
+static inline int msm_spi_prepare_for_write(struct msm_spi *dd)
+{
+ if (msm_spi_set_state(dd, SPI_OP_STATE_RUN))
+ return -EINVAL;
+
+ if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline void msm_spi_start_write(struct msm_spi *dd, u32 read_count)
+{
+ if (read_count <= dd->input_fifo_size)
+ msm_spi_write_rmn_to_fifo(dd);
+ else
+ msm_spi_write_word_to_fifo(dd);
+}
+
+static inline void msm_spi_set_write_count(struct msm_spi *dd, int val)
+{
+ writel(val, dd->base + QUP_MX_WRITE_COUNT);
+}
+
+static inline void msm_spi_complete(struct msm_spi *dd)
+{
+ dd->done = 1;
+}
+
+static inline void msm_spi_enable_error_flags(struct msm_spi *dd)
+{
+ writel_relaxed(0x00000078, dd->base + SPI_ERROR_FLAGS_EN);
+}
+
+static inline void msm_spi_clear_error_flags(struct msm_spi *dd)
+{
+ writel_relaxed(0x0000007C, dd->base + SPI_ERROR_FLAGS);
+}
+
+#endif
diff --git a/include/linux/platform_data/msm_spi.h b/include/linux/platform_data/msm_spi.h
new file mode 100644
index 0000000..a37ef6d
--- /dev/null
+++ b/include/linux/platform_data/msm_spi.h
@@ -0,0 +1,19 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_SPI_H
+#define __ARCH_ARM_MACH_MSM_SPI_H
+
+struct msm_spi_platform_data {
+ u32 max_clock_speed;
+};
+#endif
--
1.7.3.3
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH v2] spi: QUP based bus driver for Qualcomm MSM chipsets
2011-11-14 21:58 ` [PATCH v2] " Harini Jayaraman
@ 2011-12-07 22:37 ` Wolfram Sang
2011-12-12 22:28 ` Harini Jayaraman
2012-01-23 12:56 ` Grant Likely
2012-01-23 10:42 ` Russell King - ARM Linux
1 sibling, 2 replies; 743+ messages in thread
From: Wolfram Sang @ 2011-12-07 22:37 UTC (permalink / raw)
To: Harini Jayaraman
Cc: grant.likely, bryanh, linux-arm-msm, linux-kernel, kheitke,
spi-devel-general, davidb, linux-arm-kernel
[-- Attachment #1: Type: text/plain, Size: 27146 bytes --]
On Mon, Nov 14, 2011 at 02:58:27PM -0700, Harini Jayaraman wrote:
> This bus driver supports the QUP SPI hardware controller in the Qualcomm
> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
> purpose data path engine with input/output FIFOs and an embedded SPI
> mini-core. The driver currently supports only FIFO mode.
>
> Signed-off-by: Harini Jayaraman <harinij@codeaurora.org>
Wow, this driver is huge. This is a rough review only, mainly to see
what can go away. This will make further reviews easier.
> ---
> v2: Updated copyright information (addresses comments from Bryan Huntsman).
> Files renamed.
> ---
> drivers/spi/Kconfig | 10 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-qup.c | 1144 +++++++++++++++++++++++++++++++++
> drivers/spi/spi-qup.h | 436 +++++++++++++
> include/linux/platform_data/msm_spi.h | 19 +
> 5 files changed, 1610 insertions(+), 0 deletions(-)
> create mode 100644 drivers/spi/spi-qup.c
> create mode 100644 drivers/spi/spi-qup.h
> create mode 100644 include/linux/platform_data/msm_spi.h
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 52e2900..88ea7c5 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -280,6 +280,16 @@ config SPI_PXA2XX
> config SPI_PXA2XX_PCI
> def_bool SPI_PXA2XX && X86_32 && PCI
>
> +config SPI_QUP
> + tristate "Qualcomm MSM SPI QUPe Support"
> + depends on ARCH_MSM
> + help
> + Support for Serial Peripheral Interface for Qualcomm Universal
> + Peripheral.
> +
> + This driver can also be built as a module. If so, the module
> + will be called spi-qup.
> +
> config SPI_S3C24XX
> tristate "Samsung S3C24XX series SPI"
> depends on ARCH_S3C2410 && EXPERIMENTAL
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 61c3261..4d840ff 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o
> obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
> obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o
> obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
> +obj-$(CONFIG_SPI_QUP) += spi-qup.o
> obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
> spi-s3c24xx-hw-y := spi-s3c24xx.o
> spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o
> diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
> new file mode 100644
> index 0000000..4b411d8
> --- /dev/null
> +++ b/drivers/spi/spi-qup.c
> @@ -0,0 +1,1144 @@
> +/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only 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/version.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/spinlock.h>
> +#include <linux/list.h>
> +#include <linux/irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/spi/spi.h>
> +#include <linux/interrupt.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/workqueue.h>
> +#include <linux/io.h>
> +#include <linux/debugfs.h>
> +#include <linux/sched.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_data/msm_spi.h>
> +#include "spi-qup.h"
> +
> +static void msm_spi_clock_set(struct msm_spi *dd, int speed)
> +{
> + int rc;
> +
> + rc = clk_set_rate(dd->clk, speed);
> + if (!rc)
> + dd->clock_speed = speed;
> +}
> +
> +static int msm_spi_calculate_size(int *fifo_size,
> + int *block_size,
> + int block,
> + int mult)
> +{
> + int words;
> +
> + switch (block) {
> + case 0:
> + words = 1; /* 4 bytes */
> + break;
> + case 1:
> + words = 4; /* 16 bytes */
> + break;
> + case 2:
> + words = 8; /* 32 bytes */
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + switch (mult) {
> + case 0:
> + *fifo_size = words * 2;
> + break;
> + case 1:
> + *fifo_size = words * 4;
> + break;
> + case 2:
> + *fifo_size = words * 8;
> + break;
> + case 3:
> + *fifo_size = words * 16;
> + break;
> + default:
> + return -EINVAL;
> + }
I think this can be simplified. Example for this switch:
if (mult > 3)
return -EINVAL;
*fifo_size = (words * 2) << mult;
Probably the whole function can be optimized away somehow?
> +
> + *block_size = words * sizeof(u32); /* in bytes */
> + return 0;
> +}
...
> +#ifdef CONFIG_DEBUG_FS
> +static int debugfs_iomem_x32_set(void *data, u64 val)
> +{
> + iowrite32(val, data);
> + /* Ensure the previous write completed. */
> + wmb();
> + return 0;
> +}
> +
> +static int debugfs_iomem_x32_get(void *data, u64 *val)
> +{
> + *val = ioread32(data);
> + return 0;
> +}
> +
> +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get,
> + debugfs_iomem_x32_set, "0x%08llx\n");
> +
> +static void spi_debugfs_init(struct msm_spi *dd)
> +{
> + dd->dent_spi = debugfs_create_dir(dev_name(dd->dev), NULL);
> + if (dd->dent_spi) {
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++) {
> + dd->debugfs_spi_regs[i] =
> + debugfs_create_file(
> + debugfs_spi_regs[i].name,
> + debugfs_spi_regs[i].mode,
> + dd->dent_spi,
> + dd->base + debugfs_spi_regs[i].offset,
> + &fops_iomem_x32);
> + }
> + }
> +}
> +
> +static void spi_debugfs_exit(struct msm_spi *dd)
> +{
> + if (dd->dent_spi) {
> + int i;
> +
> + debugfs_remove_recursive(dd->dent_spi);
> + dd->dent_spi = NULL;
> + for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++)
> + dd->debugfs_spi_regs[i] = NULL;
> + }
> +}
> +#else
> +static void spi_debugfs_init(struct msm_spi *dd) {}
> +static void spi_debugfs_exit(struct msm_spi *dd) {}
> +#endif
That interface should go away. It might have been nice when developing
the driver, but we other mechanisms to read out register values.
> +
> +/* ===Device attributes begin=== */
> +static ssize_t show_stats(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct msm_spi *dd = spi_master_get_devdata(master);
> +
> + return snprintf(buf, PAGE_SIZE,
> + "Device %s\n"
> + "rx fifo_size = %d spi words\n"
> + "tx fifo_size = %d spi words\n"
> + "rx block size = %d bytes\n"
> + "tx block size = %d bytes\n"
> + "--statistics--\n"
> + "Rx isrs = %d\n"
> + "Tx isrs = %d\n"
> + "--debug--\n"
> + "NA yet\n",
> + dev_name(dev),
> + dd->input_fifo_size,
> + dd->output_fifo_size,
> + dd->input_block_size,
> + dd->output_block_size,
> + dd->stat_rx,
> + dd->stat_tx
> + );
> +}
> +
> +/* Reset statistics on write */
> +static ssize_t set_stats(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct msm_spi *dd = dev_get_drvdata(dev);
> +
> + dd->stat_rx = 0;
> + dd->stat_tx = 0;
> + return count;
> +}
> +
> +static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats);
> +
> +static struct attribute *dev_attrs[] = {
> + &dev_attr_stats.attr,
> + NULL,
> +};
> +
> +static struct attribute_group dev_attr_grp = {
> + .attrs = dev_attrs,
> +};
> +/* ===Device attributes end=== */
This should go as well. Not really needed.
> +
> +static int __init msm_spi_probe(struct platform_device *pdev)
> +{
> + struct spi_master *master;
> + struct msm_spi *dd;
> + struct resource *resource;
> + int rc = -ENXIO;
> + int locked = 0;
> + int clk_enabled = 0;
> + int pclk_enabled = 0;
> + struct msm_spi_platform_data *pdata = pdev->dev.platform_data;
> +
> + master = spi_alloc_master(&pdev->dev, sizeof(struct msm_spi));
> + if (!master) {
> + rc = -ENOMEM;
> + dev_err(&pdev->dev, "master allocation failed\n");
> + goto err_probe_exit;
> + }
> +
> + master->bus_num = pdev->id;
> + master->mode_bits = SPI_SUPPORTED_MODES;
> + master->num_chipselect = SPI_NUM_CHIPSELECTS;
> + master->setup = msm_spi_setup;
> + master->transfer = msm_spi_transfer;
> + platform_set_drvdata(pdev, master);
> + dd = spi_master_get_devdata(master);
> +
> + dd->pdata = pdata;
> + rc = msm_spi_get_irq_data(dd, pdev);
> + if (rc)
> + goto err_probe_res;
> +
> + resource = platform_get_resource_byname(pdev,
> + IORESOURCE_MEM, "spi_base");
> + if (!resource) {
> + rc = -ENXIO;
> + goto err_probe_res;
> + }
> +
> + dd->mem_phys_addr = resource->start;
> + dd->mem_size = resource_size(resource);
> +
> + spin_lock_init(&dd->queue_lock);
> + mutex_init(&dd->core_lock);
> + INIT_LIST_HEAD(&dd->queue);
> + INIT_WORK(&dd->work_data, msm_spi_workq);
> + init_waitqueue_head(&dd->continue_suspend);
> + dd->workqueue = create_singlethread_workqueue(
> + dev_name(master->dev.parent));
> + if (!dd->workqueue)
> + goto err_probe_res;
> +
> + if (!devm_request_mem_region(&pdev->dev, dd->mem_phys_addr,
> + dd->mem_size, SPI_DRV_NAME)) {
> + rc = -ENXIO;
> + goto err_probe_reqmem;
> + }
> +
> + dd->base = devm_ioremap(&pdev->dev, dd->mem_phys_addr, dd->mem_size);
> + if (!dd->base) {
> + rc = -ENOMEM;
> + goto err_probe_reqmem;
> + }
> +
> +
> + mutex_lock(&dd->core_lock);
> + locked = 1;
> + dd->dev = &pdev->dev;
> + dd->clk = clk_get(&pdev->dev, "spi_clk");
> + if (IS_ERR(dd->clk)) {
> + dev_err(&pdev->dev, "%s: unable to get spi_clk\n", __func__);
> + rc = PTR_ERR(dd->clk);
> + goto err_probe_clk_get;
> + }
> +
> + dd->pclk = clk_get(&pdev->dev, "spi_pclk");
> + if (IS_ERR(dd->pclk)) {
> + dev_err(&pdev->dev, "%s: unable to get spi_pclk\n", __func__);
> + rc = PTR_ERR(dd->pclk);
> + goto err_probe_pclk_get;
> + }
> +
> + if (pdata && pdata->max_clock_speed)
> + msm_spi_clock_set(dd, dd->pdata->max_clock_speed);
> +
> + rc = clk_enable(dd->clk);
> + if (rc) {
> + dev_err(&pdev->dev, "%s: unable to enable spi_clk\n",
> + __func__);
> + goto err_probe_clk_enable;
> + }
> +
> + clk_enabled = 1;
> + rc = clk_enable(dd->pclk);
> + if (rc) {
> + dev_err(&pdev->dev, "%s: unable to enable spi_pclk\n",
> + __func__);
> + goto err_probe_pclk_enable;
> + }
> +
> + pclk_enabled = 1;
> + rc = msm_spi_configure_gsbi(dd, pdev);
> + if (rc)
> + goto err_probe_gsbi;
> +
> + msm_spi_calculate_fifo_size(dd);
> +
> + /* Initialize registers */
> + writel(0x00000001, dd->base + SPI_SW_RESET);
> + msm_spi_set_state(dd, SPI_OP_STATE_RESET);
> + writel(0x00000000, dd->base + SPI_OPERATIONAL);
> + writel(0x00000000, dd->base + SPI_CONFIG);
> + writel(0x00000000, dd->base + SPI_IO_MODES);
0x0, or 0x1 will do. Save the leading 0s.
> + /*
> + * The SPI core generates a bogus input overrun error on some targets,
> + * when a transition from run to reset state occurs and if the FIFO has
> + * an odd number of entries. Hence we disable the INPUT_OVER_RUN_ERR_EN
> + * bit.
> + */
> + msm_spi_enable_error_flags(dd);
> +
> + writel(SPI_IO_C_NO_TRI_STATE, dd->base + SPI_IO_CONTROL);
> + rc = msm_spi_set_state(dd, SPI_OP_STATE_RESET);
> + if (rc)
> + goto err_probe_state;
> +
> + clk_disable(dd->clk);
> + clk_disable(dd->pclk);
> + clk_enabled = 0;
> + pclk_enabled = 0;
> +
> + dd->suspended = 0;
> + dd->transfer_pending = 0;
> + dd->multi_xfr = 0;
> + dd->mode = SPI_MODE_NONE;
> +
> + rc = msm_spi_request_irq(dd, pdev->name, master);
There is also devm_request_irq
> + if (rc)
> + goto err_probe_irq;
> +
> + msm_spi_disable_irqs(dd);
> + mutex_unlock(&dd->core_lock);
> + locked = 0;
> +
> + rc = spi_register_master(master);
> + if (rc)
> + goto err_probe_reg_master;
> +
> + rc = sysfs_create_group(&(dd->dev->kobj), &dev_attr_grp);
> + if (rc) {
> + dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc);
> + goto err_attrs;
> + }
> +
> + spi_debugfs_init(dd);
> +
> + return 0;
> +
> +err_attrs:
> + spi_unregister_master(master);
> +err_probe_reg_master:
> + msm_spi_free_irq(dd, master);
> +err_probe_irq:
> +err_probe_state:
> +err_probe_gsbi:
> + if (pclk_enabled)
> + clk_disable(dd->pclk);
> +err_probe_pclk_enable:
> + if (clk_enabled)
> + clk_disable(dd->clk);
> +err_probe_clk_enable:
> + clk_put(dd->pclk);
> +err_probe_pclk_get:
> + clk_put(dd->clk);
> +err_probe_clk_get:
> + if (locked)
> + mutex_unlock(&dd->core_lock);
> +err_probe_reqmem:
> + destroy_workqueue(dd->workqueue);
> +err_probe_res:
> + spi_master_put(master);
> +err_probe_exit:
> + return rc;
> +}
> +
...
> +static struct platform_driver msm_spi_driver = {
> + .driver = {
> + .name = SPI_DRV_NAME,
> + .owner = THIS_MODULE,
> + },
> + .suspend = msm_spi_suspend,
> + .resume = msm_spi_resume,
> + .remove = __exit_p(msm_spi_remove),
> +};
What about using module_platform_driver?
> +
> +static int __init msm_spi_init(void)
> +{
> + return platform_driver_probe(&msm_spi_driver, msm_spi_probe);
> +}
> +module_init(msm_spi_init);
> +
> +static void __exit msm_spi_exit(void)
> +{
> + platform_driver_unregister(&msm_spi_driver);
> +}
> +module_exit(msm_spi_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION("0.3");
MODULE_VERSION is not really needed these days.
> +MODULE_ALIAS("platform:"SPI_DRV_NAME);
> diff --git a/drivers/spi/spi-qup.h b/drivers/spi/spi-qup.h
> new file mode 100644
> index 0000000..be0dc66
> --- /dev/null
> +++ b/drivers/spi/spi-qup.h
> @@ -0,0 +1,436 @@
> +/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only 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.
> + */
> +
...
> +#ifdef CONFIG_DEBUG_FS
> +static const struct {
> + const char *name;
> + mode_t mode;
> + int offset;
> +} debugfs_spi_regs[] = {
> + {"config", S_IRUGO | S_IWUSR, SPI_CONFIG},
> + {"io_control", S_IRUGO | S_IWUSR, SPI_IO_CONTROL},
> + {"io_modes", S_IRUGO | S_IWUSR, SPI_IO_MODES},
> + {"sw_reset", S_IWUSR, SPI_SW_RESET},
> + {"time_out", S_IRUGO | S_IWUSR, SPI_TIME_OUT},
> + {"time_out_current", S_IRUGO, SPI_TIME_OUT_CURRENT},
> + {"mx_output_count", S_IRUGO | S_IWUSR, SPI_MX_OUTPUT_COUNT},
> + {"mx_output_cnt_current", S_IRUGO, SPI_MX_OUTPUT_CNT_CURRENT},
> + {"mx_input_count", S_IRUGO | S_IWUSR, SPI_MX_INPUT_COUNT},
> + {"mx_input_cnt_current", S_IRUGO, SPI_MX_INPUT_CNT_CURRENT},
> + {"mx_read_count", S_IRUGO | S_IWUSR, SPI_MX_READ_COUNT},
> + {"mx_read_cnt_current", S_IRUGO, SPI_MX_READ_CNT_CURRENT},
> + {"operational", S_IRUGO | S_IWUSR, SPI_OPERATIONAL},
> + {"error_flags", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS},
> + {"error_flags_en", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS_EN},
> + {"deassert_wait", S_IRUGO | S_IWUSR, SPI_DEASSERT_WAIT},
> + {"output_debug", S_IRUGO, SPI_OUTPUT_DEBUG},
> + {"input_debug", S_IRUGO, SPI_INPUT_DEBUG},
> + {"test_ctrl", S_IRUGO | S_IWUSR, SPI_TEST_CTRL},
> + {"output_fifo", S_IWUSR, SPI_OUTPUT_FIFO},
> + {"input_fifo" , S_IRUSR, SPI_INPUT_FIFO},
> + {"spi_state", S_IRUGO | S_IWUSR, SPI_STATE},
> + {"qup_config", S_IRUGO | S_IWUSR, QUP_CONFIG},
> + {"qup_error_flags", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS},
> + {"qup_error_flags_en", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS_EN},
> + {"mx_write_cnt", S_IRUGO | S_IWUSR, QUP_MX_WRITE_COUNT},
> + {"mx_write_cnt_current", S_IRUGO, QUP_MX_WRITE_CNT_CURRENT},
> + {"output_fifo_word_cnt", S_IRUGO, SPI_OUTPUT_FIFO_WORD_CNT},
> + {"input_fifo_word_cnt", S_IRUGO, SPI_INPUT_FIFO_WORD_CNT},
> +};
> +#endif
Again, drop it.
> +
> +/**
> + * struct msm_spi
> + * @read_buf: rx_buf from the spi_transfer.
> + * @write_buf: tx_buf from the spi_transfer.
> + * @base: location of QUP controller I/O area in memory.
> + * @dev: parent platform device.
> + * @queue_lock: lock to protect queue.
> + * @core_lock: mutex used to protect this struct.
> + * @queue: to log SPI transfer requests.
> + * @workqueue: workqueue for the SPI transfer requests.
> + * @work_data: work.
> + * @cur_msg: the current spi_message being processed.
> + * @cur_transfer: the current spi_transfer being processed.
> + * @transfer_complete: completion function to signal the end of a spi_transfer.
> + * @clk: the SPI core clock
> + * @pclk: hardware core clock. Needs to be enabled to access the QUP register
> + * @mem_phys_addr: physical address of the QUP controller.
> + * @mem_size: size of the QUP controller block.
> + * @input_fifo_size: the input FIFO size (in bytes).
> + * @output_fifo_size: the output FIFO size (in bytes).
> + * @rx_bytes_remaining: the number of rx bytes remaining to be transferred.
> + * @tx_bytes_remaining: the number of tx bytes remaining to be transferred.
> + * @clock_speed: SPI clock speed.
> + * @irq_in: assigned interrupt line for QUP interrupts.
> + * @read_xfr_cnt: number of words read from the FIFO (per transfer).
> + * @write_xfr_cnt: number of words written to the FIFO (per transfer).
> + * @write_len: the total number of tx bytes to be transferred, per
> + * spi_message. This is valid only for WR-WR and WR-RD transfers
> + * in a single spi_message.
> + * @read_len: the total number of rx bytes to be transferred, per
> + * spi_message. This is valid only for WR-WR and WR-RD transfers
> + * in a single spi_message.
> + * @bytes_per_word: bytes per word
> + * @suspended: the suspend state.
> + * @transfer_pending: when set indicates a pending transfer.
> + * @continue_suspend: head of wait queue.
> + * @mode: mode for SPI operation.
> + * @input_block_size: the input block size (in bytes).
> + * @output_block_size: the output block size (in bytes).
> + * @stat_rx: count of input interrupts handled.
> + * @stat_tx: count of output interrupts handled.
> + * @dent_spi: used for debug purposes.
> + * @debugfs_spi_regs: used for debug purposes.
> + * @pdata: platform data
> + * @multi_xfr: when set indicates multiple spi_transfers in a single
> + * spi_message.
> + * @done: flag used to signal completion.
> + * @cur_msg_len: combined length of all the transfers in a single
> + * spi_message (in bytes).
> + * @cur_tx_transfer: the current tx transfer being processed. Used in
> + * FIFO mode only.
> + * @cur_rx_transfer: the current rx transfer being processed. Used in
> + * FIFO mode only.
> + *
> + * Early QUP controller used three separate interrupt lines for input, output,
> + * and error interrupts. Later versions share a single interrupt line.
> + */
> +struct msm_spi {
> + u8 *read_buf;
> + const u8 *write_buf;
> + void __iomem *base;
> + struct device *dev;
> + spinlock_t queue_lock;
> + struct mutex core_lock;
> + struct list_head queue;
> + struct workqueue_struct *workqueue;
> + struct work_struct work_data;
> + struct spi_message *cur_msg;
> + struct spi_transfer *cur_transfer;
> + struct completion transfer_complete;
> + struct clk *clk;
> + struct clk *pclk;
> + unsigned long mem_phys_addr;
> + size_t mem_size;
> + int input_fifo_size;
> + int output_fifo_size;
> + u32 rx_bytes_remaining;
> + u32 tx_bytes_remaining;
> + u32 clock_speed;
> + int irq_in;
> + int read_xfr_cnt;
> + int write_xfr_cnt;
> + int write_len;
> + int read_len;
> + int bytes_per_word;
> + bool suspended;
> + bool transfer_pending;
> + wait_queue_head_t continue_suspend;
> + enum msm_spi_mode mode;
> + int input_block_size;
> + int output_block_size;
> + int stat_rx;
> + int stat_tx;
> +#ifdef CONFIG_DEBUG_FS
> + struct dentry *dent_spi;
> + struct dentry *debugfs_spi_regs[ARRAY_SIZE(debugfs_spi_regs)];
> +#endif
> + struct msm_spi_platform_data *pdata;
> + bool multi_xfr;
> + bool done;
> + u32 cur_msg_len;
> + struct spi_transfer *cur_tx_transfer;
> + struct spi_transfer *cur_rx_transfer;
> +};
This looks excessive. Please check if all of this is really needed?
> +
> +/* Forward declaration */
> +static irqreturn_t msm_spi_input_irq(int irq, void *dev_id);
> +static irqreturn_t msm_spi_output_irq(int irq, void *dev_id);
> +static irqreturn_t msm_spi_error_irq(int irq, void *dev_id);
> +static inline int msm_spi_set_state(struct msm_spi *dd,
> + enum msm_spi_state state);
> +static void msm_spi_write_word_to_fifo(struct msm_spi *dd);
> +static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd);
> +
> +/* In QUP the same interrupt line is used for input, output and error */
> +static inline int msm_spi_get_irq_data(struct msm_spi *dd,
> + struct platform_device *pdev)
> +{
> + dd->irq_in = platform_get_irq_byname(pdev, "spi_irq_in");
> + if (dd->irq_in < 0)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static inline int msm_spi_configure_gsbi(struct msm_spi *dd,
> + struct platform_device *pdev)
> +{
> + struct resource *resource;
> + unsigned long gsbi_mem_phys_addr;
> + size_t gsbi_mem_size;
> + void __iomem *gsbi_base;
> +
> + resource = platform_get_resource_byname(pdev,
> + IORESOURCE_MEM, "gsbi_base");
> + if (!resource)
> + return -ENXIO;
> +
> + gsbi_mem_phys_addr = resource->start;
> + gsbi_mem_size = resource_size(resource);
> + if (!devm_request_mem_region(&pdev->dev, gsbi_mem_phys_addr,
> + gsbi_mem_size, SPI_DRV_NAME))
> + return -ENXIO;
> +
> + gsbi_base = devm_ioremap(&pdev->dev, gsbi_mem_phys_addr,
> + gsbi_mem_size);
> + if (!gsbi_base)
> + return -ENXIO;
> +
> + /* Set GSBI to SPI mode */
> + writel(GSBI_SPI_CONFIG, gsbi_base + GSBI_CTRL_REG);
> +
> + return 0;
> +}
> +
> +/* Figure which irq occured and call the relevant functions */
> +static irqreturn_t msm_spi_qup_irq(int irq, void *dev_id)
> +{
> + u32 op, ret = IRQ_NONE;
> + struct msm_spi *dd = dev_id;
> +
> + if (readl(dd->base + SPI_ERROR_FLAGS) ||
> + readl(dd->base + QUP_ERROR_FLAGS)) {
> + struct spi_master *master = dev_get_drvdata(dd->dev);
> + ret |= msm_spi_error_irq(irq, master);
> + }
> +
> + op = readl(dd->base + SPI_OPERATIONAL);
> + if (op & SPI_OP_INPUT_SERVICE_FLAG) {
> + writel(SPI_OP_INPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
> + ret |= msm_spi_input_irq(irq, dev_id);
> + }
> +
> + if (op & SPI_OP_OUTPUT_SERVICE_FLAG) {
> + writel(SPI_OP_OUTPUT_SERVICE_FLAG, dd->base + SPI_OPERATIONAL);
> + ret |= msm_spi_output_irq(irq, dev_id);
> + }
> +
> + if (dd->done) {
> + complete(&dd->transfer_complete);
> + dd->done = 0;
> + }
> + return ret;
> +}
Too much code for a header file IMO.
> +
> +static inline int msm_spi_request_irq(struct msm_spi *dd,
> + const char *name,
> + struct spi_master *master)
> +{
> + return request_irq(dd->irq_in, msm_spi_qup_irq, IRQF_TRIGGER_HIGH,
> + name, dd);
> +}
> +
> +static inline void msm_spi_free_irq(struct msm_spi *dd,
> + struct spi_master *master)
> +{
> + free_irq(dd->irq_in, dd);
> +}
> +
> +static inline void msm_spi_disable_irqs(struct msm_spi *dd)
> +{
> + disable_irq(dd->irq_in);
> +}
> +
> +static inline void msm_spi_enable_irqs(struct msm_spi *dd)
> +{
> + enable_irq(dd->irq_in);
> +}
> +
> +static inline void msm_spi_get_clk_err(struct msm_spi *dd, u32 *spi_err)
> +{
> + *spi_err = readl(dd->base + QUP_ERROR_FLAGS);
> +}
> +
> +static inline void msm_spi_ack_clk_err(struct msm_spi *dd)
> +{
> + writel(QUP_ERR_MASK, dd->base + QUP_ERROR_FLAGS);
> +}
> +
> +static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n);
> +
> +/* QUP has no_input, no_output, and N bits at QUP_CONFIG */
> +static inline void msm_spi_set_qup_config(struct msm_spi *dd, int bpw)
> +{
> + u32 qup_config = readl(dd->base + QUP_CONFIG);
> +
> + msm_spi_add_configs(dd, &qup_config, bpw-1);
> + writel(qup_config | QUP_CONFIG_SPI_MODE, dd->base + QUP_CONFIG);
> +}
> +
> +static inline int msm_spi_prepare_for_write(struct msm_spi *dd)
> +{
> + if (msm_spi_set_state(dd, SPI_OP_STATE_RUN))
> + return -EINVAL;
> +
> + if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static inline void msm_spi_start_write(struct msm_spi *dd, u32 read_count)
> +{
> + if (read_count <= dd->input_fifo_size)
> + msm_spi_write_rmn_to_fifo(dd);
> + else
> + msm_spi_write_word_to_fifo(dd);
> +}
> +
> +static inline void msm_spi_set_write_count(struct msm_spi *dd, int val)
> +{
> + writel(val, dd->base + QUP_MX_WRITE_COUNT);
> +}
> +
> +static inline void msm_spi_complete(struct msm_spi *dd)
> +{
> + dd->done = 1;
> +}
> +
> +static inline void msm_spi_enable_error_flags(struct msm_spi *dd)
> +{
> + writel_relaxed(0x00000078, dd->base + SPI_ERROR_FLAGS_EN);
> +}
> +
> +static inline void msm_spi_clear_error_flags(struct msm_spi *dd)
> +{
> + writel_relaxed(0x0000007C, dd->base + SPI_ERROR_FLAGS);
> +}
While most of these one liners are too few code for a header file IMO.
No need to encapsulate most of it, e.g use the *_irq-calls directly.
Also, please don't use magic values (0x7c) but combine the proper
defines, please.
Thanks,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply [flat|nested] 743+ messages in thread
* Re: [PATCH v2] spi: QUP based bus driver for Qualcomm MSM chipsets
2011-12-07 22:37 ` Wolfram Sang
@ 2011-12-12 22:28 ` Harini Jayaraman
2011-12-12 22:36 ` David Brown
2012-01-23 12:56 ` Grant Likely
1 sibling, 1 reply; 743+ messages in thread
From: Harini Jayaraman @ 2011-12-12 22:28 UTC (permalink / raw)
To: Wolfram Sang
Cc: grant.likely, bryanh, linux-arm-msm, linux-kernel, kheitke,
spi-devel-general, davidb, linux-arm-kernel
On 12/07/2011 03:37 PM, Wolfram Sang wrote:
> On Mon, Nov 14, 2011 at 02:58:27PM -0700, Harini Jayaraman wrote:
>> This bus driver supports the QUP SPI hardware controller in the Qualcomm
>> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
>> purpose data path engine with input/output FIFOs and an embedded SPI
>> mini-core. The driver currently supports only FIFO mode.
>>
>> Signed-off-by: Harini Jayaraman<harinij@codeaurora.org>
> Wow, this driver is huge. This is a rough review only, mainly to see
> what can go away. This will make further reviews easier.
>
Thanks Wolfram for taking time to review this patch. I appreciate your
comments and will incorporate them in to the next version of the patch.
>> ---
>> v2: Updated copyright information (addresses comments from Bryan Huntsman).
>> Files renamed.
>> ---
>> drivers/spi/Kconfig | 10 +
>> drivers/spi/Makefile | 1 +
>> drivers/spi/spi-qup.c | 1144 +++++++++++++++++++++++++++++++++
>> drivers/spi/spi-qup.h | 436 +++++++++++++
>> include/linux/platform_data/msm_spi.h | 19 +
>> 5 files changed, 1610 insertions(+), 0 deletions(-)
>> create mode 100644 drivers/spi/spi-qup.c
>> create mode 100644 drivers/spi/spi-qup.h
>> create mode 100644 include/linux/platform_data/msm_spi.h
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 52e2900..88ea7c5 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -280,6 +280,16 @@ config SPI_PXA2XX
>> config SPI_PXA2XX_PCI
>> def_bool SPI_PXA2XX&& X86_32&& PCI
>>
>> +config SPI_QUP
>> + tristate "Qualcomm MSM SPI QUPe Support"
>> + depends on ARCH_MSM
>> + help
>> + Support for Serial Peripheral Interface for Qualcomm Universal
>> + Peripheral.
>> +
>> + This driver can also be built as a module. If so, the module
>> + will be called spi-qup.
>> +
>> config SPI_S3C24XX
>> tristate "Samsung S3C24XX series SPI"
>> depends on ARCH_S3C2410&& EXPERIMENTAL
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index 61c3261..4d840ff 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o
>> obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
>> obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o
>> obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
>> +obj-$(CONFIG_SPI_QUP) += spi-qup.o
>> obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
>> spi-s3c24xx-hw-y := spi-s3c24xx.o
>> spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o
>> diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
>> new file mode 100644
>> index 0000000..4b411d8
>> --- /dev/null
>> +++ b/drivers/spi/spi-qup.c
>> @@ -0,0 +1,1144 @@
>> +/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only 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/version.h>
>> +#include<linux/kernel.h>
>> +#include<linux/init.h>
>> +#include<linux/spinlock.h>
>> +#include<linux/list.h>
>> +#include<linux/irq.h>
>> +#include<linux/platform_device.h>
>> +#include<linux/spi/spi.h>
>> +#include<linux/interrupt.h>
>> +#include<linux/err.h>
>> +#include<linux/clk.h>
>> +#include<linux/delay.h>
>> +#include<linux/workqueue.h>
>> +#include<linux/io.h>
>> +#include<linux/debugfs.h>
>> +#include<linux/sched.h>
>> +#include<linux/mutex.h>
>> +#include<linux/platform_data/msm_spi.h>
>> +#include "spi-qup.h"
>> +
>> +static void msm_spi_clock_set(struct msm_spi *dd, int speed)
>> +{
>> + int rc;
>> +
>> + rc = clk_set_rate(dd->clk, speed);
>> + if (!rc)
>> + dd->clock_speed = speed;
>> +}
>> +
>> +static int msm_spi_calculate_size(int *fifo_size,
>> + int *block_size,
>> + int block,
>> + int mult)
>> +{
>> + int words;
>> +
>> + switch (block) {
>> + case 0:
>> + words = 1; /* 4 bytes */
>> + break;
>> + case 1:
>> + words = 4; /* 16 bytes */
>> + break;
>> + case 2:
>> + words = 8; /* 32 bytes */
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + switch (mult) {
>> + case 0:
>> + *fifo_size = words * 2;
>> + break;
>> + case 1:
>> + *fifo_size = words * 4;
>> + break;
>> + case 2:
>> + *fifo_size = words * 8;
>> + break;
>> + case 3:
>> + *fifo_size = words * 16;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
> I think this can be simplified. Example for this switch:
>
> if (mult> 3)
> return -EINVAL;
>
> *fifo_size = (words * 2)<< mult;
>
> Probably the whole function can be optimized away somehow?
>
>> +
>> + *block_size = words * sizeof(u32); /* in bytes */
>> + return 0;
>> +}
> ...
>
>
>> +#ifdef CONFIG_DEBUG_FS
>> +static int debugfs_iomem_x32_set(void *data, u64 val)
>> +{
>> + iowrite32(val, data);
>> + /* Ensure the previous write completed. */
>> + wmb();
>> + return 0;
>> +}
>> +
>> +static int debugfs_iomem_x32_get(void *data, u64 *val)
>> +{
>> + *val = ioread32(data);
>> + return 0;
>> +}
>> +
>> +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get,
>> + debugfs_iomem_x32_set, "0x%08llx\n");
>> +
>> +static void spi_debugfs_init(struct msm_spi *dd)
>> +{
>> + dd->dent_spi = debugfs_create_dir(dev_name(dd->dev), NULL);
>> + if (dd->dent_spi) {
>> + int i;
>> +
>> + for (i = 0; i< ARRAY_SIZE(debugfs_spi_regs); i++) {
>> + dd->debugfs_spi_regs[i] =
>> + debugfs_create_file(
>> + debugfs_spi_regs[i].name,
>> + debugfs_spi_regs[i].mode,
>> + dd->dent_spi,
>> + dd->base + debugfs_spi_regs[i].offset,
>> + &fops_iomem_x32);
>> + }
>> + }
>> +}
>> +
>> +static void spi_debugfs_exit(struct msm_spi *dd)
>> +{
>> + if (dd->dent_spi) {
>> + int i;
>> +
>> + debugfs_remove_recursive(dd->dent_spi);
>> + dd->dent_spi = NULL;
>> + for (i = 0; i< ARRAY_SIZE(debugfs_spi_regs); i++)
>> + dd->debugfs_spi_regs[i] = NULL;
>> + }
>> +}
>> +#else
>> +static void spi_debugfs_init(struct msm_spi *dd) {}
>> +static void spi_debugfs_exit(struct msm_spi *dd) {}
>> +#endif
> That interface should go away. It might have been nice when developing
> the driver, but we other mechanisms to read out register values.
>
>> +
>> +/* ===Device attributes begin=== */
>> +static ssize_t show_stats(struct device *dev, struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct spi_master *master = dev_get_drvdata(dev);
>> + struct msm_spi *dd = spi_master_get_devdata(master);
>> +
>> + return snprintf(buf, PAGE_SIZE,
>> + "Device %s\n"
>> + "rx fifo_size = %d spi words\n"
>> + "tx fifo_size = %d spi words\n"
>> + "rx block size = %d bytes\n"
>> + "tx block size = %d bytes\n"
>> + "--statistics--\n"
>> + "Rx isrs = %d\n"
>> + "Tx isrs = %d\n"
>> + "--debug--\n"
>> + "NA yet\n",
>> + dev_name(dev),
>> + dd->input_fifo_size,
>> + dd->output_fifo_size,
>> + dd->input_block_size,
>> + dd->output_block_size,
>> + dd->stat_rx,
>> + dd->stat_tx
>> + );
>> +}
>> +
>> +/* Reset statistics on write */
>> +static ssize_t set_stats(struct device *dev, struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct msm_spi *dd = dev_get_drvdata(dev);
>> +
>> + dd->stat_rx = 0;
>> + dd->stat_tx = 0;
>> + return count;
>> +}
>> +
>> +static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats);
>> +
>> +static struct attribute *dev_attrs[] = {
>> + &dev_attr_stats.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute_group dev_attr_grp = {
>> + .attrs = dev_attrs,
>> +};
>> +/* ===Device attributes end=== */
> This should go as well. Not really needed.
>
>> +
>> +static int __init msm_spi_probe(struct platform_device *pdev)
>> +{
>> + struct spi_master *master;
>> + struct msm_spi *dd;
>> + struct resource *resource;
>> + int rc = -ENXIO;
>> + int locked = 0;
>> + int clk_enabled = 0;
>> + int pclk_enabled = 0;
>> + struct msm_spi_platform_data *pdata = pdev->dev.platform_data;
>> +
>> + master = spi_alloc_master(&pdev->dev, sizeof(struct msm_spi));
>> + if (!master) {
>> + rc = -ENOMEM;
>> + dev_err(&pdev->dev, "master allocation failed\n");
>> + goto err_probe_exit;
>> + }
>> +
>> + master->bus_num = pdev->id;
>> + master->mode_bits = SPI_SUPPORTED_MODES;
>> + master->num_chipselect = SPI_NUM_CHIPSELECTS;
>> + master->setup = msm_spi_setup;
>> + master->transfer = msm_spi_transfer;
>> + platform_set_drvdata(pdev, master);
>> + dd = spi_master_get_devdata(master);
>> +
>> + dd->pdata = pdata;
>> + rc = msm_spi_get_irq_data(dd, pdev);
>> + if (rc)
>> + goto err_probe_res;
>> +
>> + resource = platform_get_resource_byname(pdev,
>> + IORESOURCE_MEM, "spi_base");
>> + if (!resource) {
>> + rc = -ENXIO;
>> + goto err_probe_res;
>> + }
>> +
>> + dd->mem_phys_addr = resource->start;
>> + dd->mem_size = resource_size(resource);
>> +
>> + spin_lock_init(&dd->queue_lock);
>> + mutex_init(&dd->core_lock);
>> + INIT_LIST_HEAD(&dd->queue);
>> + INIT_WORK(&dd->work_data, msm_spi_workq);
>> + init_waitqueue_head(&dd->continue_suspend);
>> + dd->workqueue = create_singlethread_workqueue(
>> + dev_name(master->dev.parent));
>> + if (!dd->workqueue)
>> + goto err_probe_res;
>> +
>> + if (!devm_request_mem_region(&pdev->dev, dd->mem_phys_addr,
>> + dd->mem_size, SPI_DRV_NAME)) {
>> + rc = -ENXIO;
>> + goto err_probe_reqmem;
>> + }
>> +
>> + dd->base = devm_ioremap(&pdev->dev, dd->mem_phys_addr, dd->mem_size);
>> + if (!dd->base) {
>> + rc = -ENOMEM;
>> + goto err_probe_reqmem;
>> + }
>> +
>> +
>> + mutex_lock(&dd->core_lock);
>> + locked = 1;
>> + dd->dev =&pdev->dev;
>> + dd->clk = clk_get(&pdev->dev, "spi_clk");
>> + if (IS_ERR(dd->clk)) {
>> + dev_err(&pdev->dev, "%s: unable to get spi_clk\n", __func__);
>> + rc = PTR_ERR(dd->clk);
>> + goto err_probe_clk_get;
>> + }
>> +
>> + dd->pclk = clk_get(&pdev->dev, "spi_pclk");
>> + if (IS_ERR(dd->pclk)) {
>> + dev_err(&pdev->dev, "%s: unable to get spi_pclk\n", __func__);
>> + rc = PTR_ERR(dd->pclk);
>> + goto err_probe_pclk_get;
>> + }
>> +
>> + if (pdata&& pdata->max_clock_speed)
>> + msm_spi_clock_set(dd, dd->pdata->max_clock_speed);
>> +
>> + rc = clk_enable(dd->clk);
>> + if (rc) {
>> + dev_err(&pdev->dev, "%s: unable to enable spi_clk\n",
>> + __func__);
>> + goto err_probe_clk_enable;
>> + }
>> +
>> + clk_enabled = 1;
>> + rc = clk_enable(dd->pclk);
>> + if (rc) {
>> + dev_err(&pdev->dev, "%s: unable to enable spi_pclk\n",
>> + __func__);
>> + goto err_probe_pclk_enable;
>> + }
>> +
>> + pclk_enabled = 1;
>> + rc = msm_spi_configure_gsbi(dd, pdev);
>> + if (rc)
>> + goto err_probe_gsbi;
>> +
>> + msm_spi_calculate_fifo_size(dd);
>> +
>> + /* Initialize registers */
>> + writel(0x00000001, dd->base + SPI_SW_RESET);
>> + msm_spi_set_state(dd, SPI_OP_STATE_RESET);
>> + writel(0x00000000, dd->base + SPI_OPERATIONAL);
>> + writel(0x00000000, dd->base + SPI_CONFIG);
>> + writel(0x00000000, dd->base + SPI_IO_MODES);
> 0x0, or 0x1 will do. Save the leading 0s.
>
>> + /*
>> + * The SPI core generates a bogus input overrun error on some targets,
>> + * when a transition from run to reset state occurs and if the FIFO has
>> + * an odd number of entries. Hence we disable the INPUT_OVER_RUN_ERR_EN
>> + * bit.
>> + */
>> + msm_spi_enable_error_flags(dd);
>> +
>> + writel(SPI_IO_C_NO_TRI_STATE, dd->base + SPI_IO_CONTROL);
>> + rc = msm_spi_set_state(dd, SPI_OP_STATE_RESET);
>> + if (rc)
>> + goto err_probe_state;
>> +
>> + clk_disable(dd->clk);
>> + clk_disable(dd->pclk);
>> + clk_enabled = 0;
>> + pclk_enabled = 0;
>> +
>> + dd->suspended = 0;
>> + dd->transfer_pending = 0;
>> + dd->multi_xfr = 0;
>> + dd->mode = SPI_MODE_NONE;
>> +
>> + rc = msm_spi_request_irq(dd, pdev->name, master);
> There is also devm_request_irq
>
>> + if (rc)
>> + goto err_probe_irq;
>> +
>> + msm_spi_disable_irqs(dd);
>> + mutex_unlock(&dd->core_lock);
>> + locked = 0;
>> +
>> + rc = spi_register_master(master);
>> + if (rc)
>> + goto err_probe_reg_master;
>> +
>> + rc = sysfs_create_group(&(dd->dev->kobj),&dev_attr_grp);
>> + if (rc) {
>> + dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc);
>> + goto err_attrs;
>> + }
>> +
>> + spi_debugfs_init(dd);
>> +
>> + return 0;
>> +
>> +err_attrs:
>> + spi_unregister_master(master);
>> +err_probe_reg_master:
>> + msm_spi_free_irq(dd, master);
>> +err_probe_irq:
>> +err_probe_state:
>> +err_probe_gsbi:
>> + if (pclk_enabled)
>> + clk_disable(dd->pclk);
>> +err_probe_pclk_enable:
>> + if (clk_enabled)
>> + clk_disable(dd->clk);
>> +err_probe_clk_enable:
>> + clk_put(dd->pclk);
>> +err_probe_pclk_get:
>> + clk_put(dd->clk);
>> +err_probe_clk_get:
>> + if (locked)
>> + mutex_unlock(&dd->core_lock);
>> +err_probe_reqmem:
>> + destroy_workqueue(dd->workqueue);
>> +err_probe_res:
>> + spi_master_put(master);
>> +err_probe_exit:
>> + return rc;
>> +}
>> +
> ...
>
>> +static struct platform_driver msm_spi_driver = {
>> + .driver = {
>> + .name = SPI_DRV_NAME,
>> + .owner = THIS_MODULE,
>> + },
>> + .suspend = msm_spi_suspend,
>> + .resume = msm_spi_resume,
>> + .remove = __exit_p(msm_spi_remove),
>> +};
> What about using module_platform_driver?
>
>> +
>> +static int __init msm_spi_init(void)
>> +{
>> + return platform_driver_probe(&msm_spi_driver, msm_spi_probe);
>> +}
>> +module_init(msm_spi_init);
>> +
>> +static void __exit msm_spi_exit(void)
>> +{
>> + platform_driver_unregister(&msm_spi_driver);
>> +}
>> +module_exit(msm_spi_exit);
>> +
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_VERSION("0.3");
> MODULE_VERSION is not really needed these days.
>
>> +MODULE_ALIAS("platform:"SPI_DRV_NAME);
>> diff --git a/drivers/spi/spi-qup.h b/drivers/spi/spi-qup.h
>> new file mode 100644
>> index 0000000..be0dc66
>> --- /dev/null
>> +++ b/drivers/spi/spi-qup.h
>> @@ -0,0 +1,436 @@
>> +/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only 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.
>> + */
>> +
> ...
>
>> +#ifdef CONFIG_DEBUG_FS
>> +static const struct {
>> + const char *name;
>> + mode_t mode;
>> + int offset;
>> +} debugfs_spi_regs[] = {
>> + {"config", S_IRUGO | S_IWUSR, SPI_CONFIG},
>> + {"io_control", S_IRUGO | S_IWUSR, SPI_IO_CONTROL},
>> + {"io_modes", S_IRUGO | S_IWUSR, SPI_IO_MODES},
>> + {"sw_reset", S_IWUSR, SPI_SW_RESET},
>> + {"time_out", S_IRUGO | S_IWUSR, SPI_TIME_OUT},
>> + {"time_out_current", S_IRUGO, SPI_TIME_OUT_CURRENT},
>> + {"mx_output_count", S_IRUGO | S_IWUSR, SPI_MX_OUTPUT_COUNT},
>> + {"mx_output_cnt_current", S_IRUGO, SPI_MX_OUTPUT_CNT_CURRENT},
>> + {"mx_input_count", S_IRUGO | S_IWUSR, SPI_MX_INPUT_COUNT},
>> + {"mx_input_cnt_current", S_IRUGO, SPI_MX_INPUT_CNT_CURRENT},
>> + {"mx_read_count", S_IRUGO | S_IWUSR, SPI_MX_READ_COUNT},
>> + {"mx_read_cnt_current", S_IRUGO, SPI_MX_READ_CNT_CURRENT},
>> + {"operational", S_IRUGO | S_IWUSR, SPI_OPERATIONAL},
>> + {"error_flags", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS},
>> + {"error_flags_en", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS_EN},
>> + {"deassert_wait", S_IRUGO | S_IWUSR, SPI_DEASSERT_WAIT},
>> + {"output_debug", S_IRUGO, SPI_OUTPUT_DEBUG},
>> + {"input_debug", S_IRUGO, SPI_INPUT_DEBUG},
>> + {"test_ctrl", S_IRUGO | S_IWUSR, SPI_TEST_CTRL},
>> + {"output_fifo", S_IWUSR, SPI_OUTPUT_FIFO},
>> + {"input_fifo" , S_IRUSR, SPI_INPUT_FIFO},
>> + {"spi_state", S_IRUGO | S_IWUSR, SPI_STATE},
>> + {"qup_config", S_IRUGO | S_IWUSR, QUP_CONFIG},
>> + {"qup_error_flags", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS},
>> + {"qup_error_flags_en", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS_EN},
>> + {"mx_write_cnt", S_IRUGO | S_IWUSR, QUP_MX_WRITE_COUNT},
>> + {"mx_write_cnt_current", S_IRUGO, QUP_MX_WRITE_CNT_CURRENT},
>> + {"output_fifo_word_cnt", S_IRUGO, SPI_OUTPUT_FIFO_WORD_CNT},
>> + {"input_fifo_word_cnt", S_IRUGO, SPI_INPUT_FIFO_WORD_CNT},
>> +};
>> +#endif
> Again, drop it.
>
>> +
>> +/**
>> + * struct msm_spi
>> + * @read_buf: rx_buf from the spi_transfer.
>> + * @write_buf: tx_buf from the spi_transfer.
>> + * @base: location of QUP controller I/O area in memory.
>> + * @dev: parent platform device.
>> + * @queue_lock: lock to protect queue.
>> + * @core_lock: mutex used to protect this struct.
>> + * @queue: to log SPI transfer requests.
>> + * @workqueue: workqueue for the SPI transfer requests.
>> + * @work_data: work.
>> + * @cur_msg: the current spi_message being processed.
>> + * @cur_transfer: the current spi_transfer being processed.
>> + * @transfer_complete: completion function to signal the end of a spi_transfer.
>> + * @clk: the SPI core clock
>> + * @pclk: hardware core clock. Needs to be enabled to access the QUP register
>> + * @mem_phys_addr: physical address of the QUP controller.
>> + * @mem_size: size of the QUP controller block.
>> + * @input_fifo_size: the input FIFO size (in bytes).
>> + * @output_fifo_size: the output FIFO size (in bytes).
>> + * @rx_bytes_remaining: the number of rx bytes remaining to be transferred.
>> + * @tx_bytes_remaining: the number of tx bytes remaining to be transferred.
>> + * @clock_speed: SPI clock speed.
>> + * @irq_in: assigned interrupt line for QUP interrupts.
>> + * @read_xfr_cnt: number of words read from the FIFO (per transfer).
>> + * @write_xfr_cnt: number of words written to the FIFO (per transfer).
>> + * @write_len: the total number of tx bytes to be transferred, per
>> + * spi_message. This is valid only for WR-WR and WR-RD transfers
>> + * in a single spi_message.
>> + * @read_len: the total number of rx bytes to be transferred, per
>> + * spi_message. This is valid only for WR-WR and WR-RD transfers
>> + * in a single spi_message.
>> + * @bytes_per_word: bytes per word
>> + * @suspended: the suspend state.
>> + * @transfer_pending: when set indicates a pending transfer.
>> + * @continue_suspend: head of wait queue.
>> + * @mode: mode for SPI operation.
>> + * @input_block_size: the input block size (in bytes).
>> + * @output_block_size: the output block size (in bytes).
>> + * @stat_rx: count of input interrupts handled.
>> + * @stat_tx: count of output interrupts handled.
>> + * @dent_spi: used for debug purposes.
>> + * @debugfs_spi_regs: used for debug purposes.
>> + * @pdata: platform data
>> + * @multi_xfr: when set indicates multiple spi_transfers in a single
>> + * spi_message.
>> + * @d