Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[PATCH] serial: 8250_dw: Add DMA support for non-ACPI platforms

169 views
Skip to first unread message

Ray Jui

unread,
Oct 7, 2014, 8:40:02 PM10/7/14
to
The dma pointer under struct uart_8250_port is currently left
unassigned for non-ACPI platforms. It should be pointing to the dma
member in struct dw8250_data like how it was done for ACPI, so the core
8250 code will try to request for DMA when registering the port

If DMA is not enabled in device tree, request DMA will fail and the
driver will fall back to PIO

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: JD (Jiandong) Zheng <jdz...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
Tested-by: Scott Branden <sbra...@broadcom.com>
---
drivers/tty/serial/8250/8250_dw.c | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 4db7987..1038ea8 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -293,6 +293,14 @@ static int dw8250_probe_of(struct uart_port *p,
if (has_ucv)
dw8250_setup_port(up);

+ /* if we have a valid fifosize, try hooking up DMA here */
+ if (p->fifosize) {
+ up->dma = &data->dma;
+
+ up->dma->rxconf.src_maxburst = p->fifosize / 4;
+ up->dma->txconf.dst_maxburst = p->fifosize / 4;
+ }
+
if (!of_property_read_u32(np, "reg-shift", &val))
p->regshift = val;

--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Ray Jui

unread,
Oct 8, 2014, 12:40:01 AM10/8/14
to
The PL022 SPI driver maps the DMA RX buffer before the DMA TX buffer. In
most cases, the sequence of the mapping does not matter. But in cases
where TX and RX happen to use the same buffer, e.g., spidev, it causes
the cached TX data not written to memory, because the same memory has
been marked invalid when dma_map_sg on the RX buffer is called

The solution is to reverse the sequence so it maps the TX buffer before
the RX buffer

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: JD (Jiandong) Zheng <jdz...@broadcom.com>
Tested-by: Scott Branden <sbra...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/spi/spi-pl022.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c
index 1189cfd..edb7298 100644
--- a/drivers/spi/spi-pl022.c
+++ b/drivers/spi/spi-pl022.c
@@ -773,10 +773,10 @@ static void *next_transfer(struct pl022 *pl022)
static void unmap_free_dma_scatter(struct pl022 *pl022)
{
/* Unmap and free the SG tables */
- dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl,
- pl022->sgt_tx.nents, DMA_TO_DEVICE);
dma_unmap_sg(pl022->dma_rx_channel->device->dev, pl022->sgt_rx.sgl,
pl022->sgt_rx.nents, DMA_FROM_DEVICE);
+ dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl,
+ pl022->sgt_tx.nents, DMA_TO_DEVICE);
sg_free_table(&pl022->sgt_rx);
sg_free_table(&pl022->sgt_tx);
}
@@ -1026,16 +1026,16 @@ static int configure_dma(struct pl022 *pl022)
pl022->cur_transfer->len, &pl022->sgt_tx);

/* Map DMA buffers */
- rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
- pl022->sgt_rx.nents, DMA_FROM_DEVICE);
- if (!rx_sglen)
- goto err_rx_sgmap;
-
tx_sglen = dma_map_sg(txchan->device->dev, pl022->sgt_tx.sgl,
pl022->sgt_tx.nents, DMA_TO_DEVICE);
if (!tx_sglen)
goto err_tx_sgmap;

+ rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
+ pl022->sgt_rx.nents, DMA_FROM_DEVICE);
+ if (!rx_sglen)
+ goto err_rx_sgmap;
+
/* Send both scatterlists */
rxdesc = dmaengine_prep_slave_sg(rxchan,
pl022->sgt_rx.sgl,
@@ -1070,12 +1070,12 @@ err_txdesc:
dmaengine_terminate_all(txchan);
err_rxdesc:
dmaengine_terminate_all(rxchan);
+ dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
+ pl022->sgt_rx.nents, DMA_FROM_DEVICE);
+err_rx_sgmap:
dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl,
pl022->sgt_tx.nents, DMA_TO_DEVICE);
err_tx_sgmap:
- dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
- pl022->sgt_tx.nents, DMA_FROM_DEVICE);
-err_rx_sgmap:
sg_free_table(&pl022->sgt_tx);
err_alloc_tx_sg:
sg_free_table(&pl022->sgt_rx);

Mark Brown

unread,
Oct 8, 2014, 7:30:01 AM10/8/14
to
On Tue, Oct 07, 2014 at 09:38:47PM -0700, Ray Jui wrote:

> The PL022 SPI driver maps the DMA RX buffer before the DMA TX buffer. In
> most cases, the sequence of the mapping does not matter. But in cases
> where TX and RX happen to use the same buffer, e.g., spidev, it causes
> the cached TX data not written to memory, because the same memory has
> been marked invalid when dma_map_sg on the RX buffer is called

This seems like it is a bug in spidev, using the same buffer simultaneously
for both directions isn't something I'd think would be expected to work
reliably unless it was explicitly mapped as bidirectional.
signature.asc

Ray Jui

unread,
Oct 8, 2014, 12:20:01 PM10/8/14
to
Hi Mark,

Thanks for the reply. It looks like you are also the maintainer of
spidev. In this case, could you please help to confirm that you expect
spidev to use separate buffers for TX and RX? If so, I can go ahead and
make the change in spidev.

+ Grant

Thanks,

Ray

Mark Brown

unread,
Oct 8, 2014, 2:30:02 PM10/8/14
to
On Wed, Oct 08, 2014 at 09:14:35AM -0700, Ray Jui wrote:

> Thanks for the reply. It looks like you are also the maintainer of spidev.
> In this case, could you please help to confirm that you expect spidev to use
> separate buffers for TX and RX? If so, I can go ahead and make the change in
> spidev.

Yes, that would be my expectation for maximum robustness (or if it is
going to use one buffer it explicitly maps it for mixed use but I'd
expect that to be asking for trouble).
signature.asc

Ray Jui

unread,
Oct 8, 2014, 2:40:02 PM10/8/14
to
Okay, Mark. I'm going to make the change in the spidev and submit a new
patch.

Thanks for the feedback.

Heikki Krogerus

unread,
Oct 9, 2014, 9:30:02 AM10/9/14
to
On Tue, Oct 07, 2014 at 05:35:47PM -0700, Ray Jui wrote:
> The dma pointer under struct uart_8250_port is currently left
> unassigned for non-ACPI platforms. It should be pointing to the dma
> member in struct dw8250_data like how it was done for ACPI, so the core
> 8250 code will try to request for DMA when registering the port
>
> If DMA is not enabled in device tree, request DMA will fail and the
> driver will fall back to PIO
>
> Signed-off-by: Ray Jui <rj...@broadcom.com>
> Reviewed-by: JD (Jiandong) Zheng <jdz...@broadcom.com>
> Reviewed-by: Scott Branden <sbra...@broadcom.com>
> Tested-by: Scott Branden <sbra...@broadcom.com>

OK by me. FWIW..

Reviewed-by: Heikki Krogerus <heikki....@linux.intel.com>


--
heikki

Geert Uytterhoeven

unread,
Oct 9, 2014, 10:00:01 AM10/9/14
to
Having two separate buffers avoids false successes when running
"spidev_test --loop" on a buggy SPI master driver.
Been there, done that ;-)

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- ge...@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

Ray Jui

unread,
Oct 9, 2014, 2:20:01 PM10/9/14
to
By using separate TX and RX bounce buffers, we avoid potential cache
flush and invalidation sequence issue that may be encountered when a
single bounce buffer is shared between TX and RX

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: JD (Jiandong) Zheng <jdz...@broadcom.com>
---
drivers/spi/spidev.c | 79 +++++++++++++++++++++++++++++++++-----------------
1 file changed, 52 insertions(+), 27 deletions(-)

diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index e3bc23b..e50039f 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -82,10 +82,11 @@ struct spidev_data {
struct spi_device *spi;
struct list_head device_entry;

- /* buffer is NULL unless this device is open (users > 0) */
+ /* TX/RX buffers are NULL unless this device is open (users > 0) */
struct mutex buf_lock;
unsigned users;
- u8 *buffer;
+ u8 *tx_buffer;
+ u8 *rx_buffer;
};

static LIST_HEAD(device_list);
@@ -135,7 +136,7 @@ static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
- .tx_buf = spidev->buffer,
+ .tx_buf = spidev->tx_buffer,
.len = len,
};
struct spi_message m;
@@ -149,7 +150,7 @@ static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
- .rx_buf = spidev->buffer,
+ .rx_buf = spidev->rx_buffer,
.len = len,
};
struct spi_message m;
@@ -179,7 +180,7 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
if (status > 0) {
unsigned long missing;

- missing = copy_to_user(buf, spidev->buffer, status);
+ missing = copy_to_user(buf, spidev->rx_buffer, status);
if (missing == status)
status = -EFAULT;
else
@@ -206,7 +207,7 @@ spidev_write(struct file *filp, const char __user *buf,
spidev = filp->private_data;

mutex_lock(&spidev->buf_lock);
- missing = copy_from_user(spidev->buffer, buf, count);
+ missing = copy_from_user(spidev->tx_buffer, buf, count);
if (missing == 0)
status = spidev_sync_write(spidev, count);
else
@@ -224,7 +225,7 @@ static int spidev_message(struct spidev_data *spidev,
struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp;
unsigned n, total;
- u8 *buf;
+ u8 *tx_buf, *rx_buf;
int status = -EFAULT;

spi_message_init(&msg);
@@ -236,7 +237,8 @@ static int spidev_message(struct spidev_data *spidev,
* We walk the array of user-provided transfers, using each one
* to initialize a kernel version of the same transfer.
*/
- buf = spidev->buffer;
+ tx_buf = spidev->tx_buffer;
+ rx_buf = spidev->rx_buffer;
total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n;
@@ -250,20 +252,21 @@ static int spidev_message(struct spidev_data *spidev,
}

if (u_tmp->rx_buf) {
- k_tmp->rx_buf = buf;
+ k_tmp->rx_buf = rx_buf;
if (!access_ok(VERIFY_WRITE, (u8 __user *)
(uintptr_t) u_tmp->rx_buf,
u_tmp->len))
goto done;
}
if (u_tmp->tx_buf) {
- k_tmp->tx_buf = buf;
- if (copy_from_user(buf, (const u8 __user *)
+ k_tmp->tx_buf = tx_buf;
+ if (copy_from_user(tx_buf, (const u8 __user *)
(uintptr_t) u_tmp->tx_buf,
u_tmp->len))
goto done;
}
- buf += k_tmp->len;
+ tx_buf += k_tmp->len;
+ rx_buf += k_tmp->len;

k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->tx_nbits = u_tmp->tx_nbits;
@@ -290,17 +293,17 @@ static int spidev_message(struct spidev_data *spidev,
goto done;

/* copy any rx data out of bounce buffer */
- buf = spidev->buffer;
+ rx_buf = spidev->rx_buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) {
if (__copy_to_user((u8 __user *)
- (uintptr_t) u_tmp->rx_buf, buf,
+ (uintptr_t) u_tmp->rx_buf, rx_buf,
u_tmp->len)) {
status = -EFAULT;
goto done;
}
}
- buf += u_tmp->len;
+ rx_buf += u_tmp->len;
}
status = total;

@@ -508,22 +511,41 @@ static int spidev_open(struct inode *inode, struct file *filp)
break;
}
}
- if (status == 0) {
- if (!spidev->buffer) {
- spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
- if (!spidev->buffer) {
+
+ if (status) {
+ pr_debug("spidev: nothing for minor %d\n", iminor(inode));
+ goto err_find_dev;
+ }
+
+ if (!spidev->tx_buffer) {
+ spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
+ if (!spidev->tx_buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
+ goto err_find_dev;
}
}
- if (status == 0) {
- spidev->users++;
- filp->private_data = spidev;
- nonseekable_open(inode, filp);
+
+ if (!spidev->rx_buffer) {
+ spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
+ if (!spidev->rx_buffer) {
+ dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
+ status = -ENOMEM;
+ goto err_alloc_rx_buf;
}
- } else
- pr_debug("spidev: nothing for minor %d\n", iminor(inode));
+ }
+
+ spidev->users++;
+ filp->private_data = spidev;
+ nonseekable_open(inode, filp);
+
+ mutex_unlock(&device_list_lock);
+ return 0;

+err_alloc_rx_buf:
+ kfree(spidev->tx_buffer);
+ spidev->tx_buffer = NULL;
+err_find_dev:
mutex_unlock(&device_list_lock);
return status;
}
@@ -542,8 +564,11 @@ static int spidev_release(struct inode *inode, struct file *filp)
if (!spidev->users) {
int dofree;

- kfree(spidev->buffer);
- spidev->buffer = NULL;
+ kfree(spidev->tx_buffer);
+ spidev->tx_buffer = NULL;
+
+ kfree(spidev->rx_buffer);
+ spidev->rx_buffer = NULL;

/* ... after we unbound from the underlying device? */
spin_lock_irq(&spidev->spi_lock);
--
1.7.9.5

Ray Jui

unread,
Oct 9, 2014, 2:50:02 PM10/9/14
to
When mapped RX DMA entries are unmapped in an error condition when DMA
is firstly configured in the driver, the number of TX DMA entries was
passed in, which is incorrect

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
drivers/spi/spi-pl022.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c
index f35f723..fc2dd84 100644
--- a/drivers/spi/spi-pl022.c
+++ b/drivers/spi/spi-pl022.c
@@ -1106,7 +1106,7 @@ err_rxdesc:
pl022->sgt_tx.nents, DMA_TO_DEVICE);
err_tx_sgmap:
dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
- pl022->sgt_tx.nents, DMA_FROM_DEVICE);
+ pl022->sgt_rx.nents, DMA_FROM_DEVICE);
err_rx_sgmap:
sg_free_table(&pl022->sgt_tx);
err_alloc_tx_sg:

Mark Brown

unread,
Oct 13, 2014, 7:10:02 AM10/13/14
to
On Thu, Oct 09, 2014 at 11:19:25AM -0700, Ray Jui wrote:
> By using separate TX and RX bounce buffers, we avoid potential cache
> flush and invalidation sequence issue that may be encountered when a
> single bounce buffer is shared between TX and RX

Applied, thanks.
signature.asc

Mark Brown

unread,
Oct 13, 2014, 7:10:02 AM10/13/14
to
On Thu, Oct 09, 2014 at 11:44:54AM -0700, Ray Jui wrote:
> When mapped RX DMA entries are unmapped in an error condition when DMA
> is firstly configured in the driver, the number of TX DMA entries was
> passed in, which is incorrect

Applied, thanks.
signature.asc

Ray Jui

unread,
Oct 13, 2014, 11:10:01 PM10/13/14
to
Thanks, Mark.

Ray Jui

unread,
Oct 13, 2014, 11:10:02 PM10/13/14
to
Thanks, Mark.

Ray Jui

unread,
Oct 16, 2014, 8:50:03 PM10/16/14
to
As part of subsystem that many slave drivers depend on, it's more
appropriate for the pl330 DMA driver to be initialized at
subsys_initcall than device_initcall

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
drivers/dma/pl330.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
index d5149aa..abb4cae 100644
--- a/drivers/dma/pl330.c
+++ b/drivers/dma/pl330.c
@@ -2811,7 +2811,17 @@ static struct amba_driver pl330_driver = {
.remove = pl330_remove,
};

-module_amba_driver(pl330_driver);
+static int __init pl330_init(void)
+{
+ return amba_driver_register(&pl330_driver);
+}
+subsys_initcall(pl330_init);
+
+static void __exit pl330_exit(void)
+{
+ amba_driver_unregister(&pl330_driver);
+}
+module_exit(pl330_exit);

MODULE_AUTHOR("Jaswinder Singh <jassi...@samsung.com>");
MODULE_DESCRIPTION("API Driver for PL330 DMAC");
--
1.7.9.5

Lars-Peter Clausen

unread,
Oct 17, 2014, 3:50:02 AM10/17/14
to
On 10/17/2014 02:48 AM, Ray Jui wrote:
> As part of subsystem that many slave drivers depend on, it's more
> appropriate for the pl330 DMA driver to be initialized at
> subsys_initcall than device_initcall

Well, we do have -EPROBE_DEFER these days to handle these kinds of
dependencies so we no longer have to these kinds of manual init reordering
tricks.

- Lars

Vinod Koul

unread,
Oct 17, 2014, 4:20:02 AM10/17/14
to
On Fri, Oct 17, 2014 at 09:45:45AM +0200, Lars-Peter Clausen wrote:
> On 10/17/2014 02:48 AM, Ray Jui wrote:
> >As part of subsystem that many slave drivers depend on, it's more
> >appropriate for the pl330 DMA driver to be initialized at
> >subsys_initcall than device_initcall
>
> Well, we do have -EPROBE_DEFER these days to handle these kinds of
> dependencies so we no longer have to these kinds of manual init
> reordering tricks.
How ould that work?

Consider for example SPI and dmanegine. SPI driver got probed, then to start
a transaction requested a channel... while dmaengine driver is still getting
probed/not probed yet. So SPI driver didnt get a channel.

--
~Vinod

Krzysztof Kozłowski

unread,
Oct 17, 2014, 5:50:02 AM10/17/14
to
On 17.10.2014 02:48, Ray Jui wrote:
> As part of subsystem that many slave drivers depend on, it's more
> appropriate for the pl330 DMA driver to be initialized at
> subsys_initcall than device_initcall
>
> Signed-off-by: Ray Jui <rj...@broadcom.com>
> ---
> drivers/dma/pl330.c | 12 +++++++++++-
> 1 file changed, 11 insertions(+), 1 deletion(-)

For our setup this was not needed but anyway works fine.
Tested on Trats2 (Exynos4412) and Gear2 (Exynos3250).

Tested-by: Krzysztof Kozlowski <k.koz...@samsung.com>

Best regards,
Krzysztof

>
> diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
> index d5149aa..abb4cae 100644
> --- a/drivers/dma/pl330.c
> +++ b/drivers/dma/pl330.c
> @@ -2811,7 +2811,17 @@ static struct amba_driver pl330_driver = {
> .remove = pl330_remove,
> };
>
> -module_amba_driver(pl330_driver);
> +static int __init pl330_init(void)
> +{
> + return amba_driver_register(&pl330_driver);
> +}
> +subsys_initcall(pl330_init);
> +
> +static void __exit pl330_exit(void)
> +{
> + amba_driver_unregister(&pl330_driver);
> +}
> +module_exit(pl330_exit);
>
> MODULE_AUTHOR("Jaswinder Singh <jassi...@samsung.com>");
> MODULE_DESCRIPTION("API Driver for PL330 DMAC");
>

--

Lars-Peter Clausen

unread,
Oct 17, 2014, 7:20:01 AM10/17/14
to
On 10/17/2014 09:35 AM, Vinod Koul wrote:
> On Fri, Oct 17, 2014 at 09:45:45AM +0200, Lars-Peter Clausen wrote:
>> On 10/17/2014 02:48 AM, Ray Jui wrote:
>>> As part of subsystem that many slave drivers depend on, it's more
>>> appropriate for the pl330 DMA driver to be initialized at
>>> subsys_initcall than device_initcall
>>
>> Well, we do have -EPROBE_DEFER these days to handle these kinds of
>> dependencies so we no longer have to these kinds of manual init
>> reordering tricks.
> How ould that work?
>
> Consider for example SPI and dmanegine. SPI driver got probed, then to start
> a transaction requested a channel... while dmaengine driver is still getting
> probed/not probed yet. So SPI driver didnt get a channel.
>

Ideally the SPI driver requests the channel in probe function and if the DMA
controller is not yet probed returns EPROBE_DEFER. If the SPI driver
requests the channel in the transfer handler it needs to deal with being
able to fall back to non DMA transfers anyway so this shouldn't be a problem.

But in any case fiddling around with the init sequences is just a quick hack
and might makes the problem less likely to appear in some cases, but there
is no guarantee that it works. And I think the proper solution at the moment
is to use probe deferral.

Other subsystems have seen patches which moved drivers from using
subsys_initcall to device_initcall/module_..._driver/ with the reasoning
that this is no longer necessary because of EPROBE_DEFER. So I don't think
we should be doing the exact opposite in DMA framework. Also if we'd apply
this patch it won't take to long until somebody suggest going back to
module_platform_driver() instead of subsys_initcall.

- Lars

Ray Jui

unread,
Oct 17, 2014, 12:20:02 PM10/17/14
to
On 10/17/2014 4:15 AM, Lars-Peter Clausen wrote:
> On 10/17/2014 09:35 AM, Vinod Koul wrote:
>> On Fri, Oct 17, 2014 at 09:45:45AM +0200, Lars-Peter Clausen wrote:
>>> On 10/17/2014 02:48 AM, Ray Jui wrote:
>>>> As part of subsystem that many slave drivers depend on, it's more
>>>> appropriate for the pl330 DMA driver to be initialized at
>>>> subsys_initcall than device_initcall
>>>
>>> Well, we do have -EPROBE_DEFER these days to handle these kinds of
>>> dependencies so we no longer have to these kinds of manual init
>>> reordering tricks.
>> How ould that work?
>>
>> Consider for example SPI and dmanegine. SPI driver got probed, then to
>> start
>> a transaction requested a channel... while dmaengine driver is still
>> getting
>> probed/not probed yet. So SPI driver didnt get a channel.
>>
>
> Ideally the SPI driver requests the channel in probe function and if the
> DMA controller is not yet probed returns EPROBE_DEFER. If the SPI driver
> requests the channel in the transfer handler it needs to deal with being
> able to fall back to non DMA transfers anyway so this shouldn't be a
> problem.
So in the case of the spi-pl022 driver. It requests the channel in probe
function. And obviously DMA is not mandatory, so when the channel
request fails the probe won't fail and instead it falls back to PIO. In
this case, can you recommend a different way to solve this problem
without having the DMA driver probed earlier than its slaves?

>
> But in any case fiddling around with the init sequences is just a quick
> hack and might makes the problem less likely to appear in some cases,
> but there is no guarantee that it works. And I think the proper solution
> at the moment is to use probe deferral.
I think it makes sense to have the DMA driver, as one of the core
components in various SoCs that a lot of peripheral drivers depend on,
to be registered at the level of subsys_init or somewhere close. We are
not changing this just to get SPI to work. We are changing this because
we think DMA should be ready before a lot of its slaves, which are
typically done at device_initcall.

I have no problem relying on EPROBE_DEFER for this, provided that it
works. The issue is, like I mentioned above, for a lot of slave devices
DMA is not mandatory, when DMA fails at probe they would fall back to
PIO and never use DMA. Another disadvantage I see with EPROBE_DEFER is
delayed boot time.

>
> Other subsystems have seen patches which moved drivers from using
> subsys_initcall to device_initcall/module_..._driver/ with the reasoning
> that this is no longer necessary because of EPROBE_DEFER. So I don't
> think we should be doing the exact opposite in DMA framework. Also if
> we'd apply this patch it won't take to long until somebody suggest going
> back to module_platform_driver() instead of subsys_initcall.
>
> - Lars
There are currently 12 DMA drivers under drivers/dma registering
themselves at subsys_init. I don't see why pl330 cannot do the same. Is
there any concern that it may not work for some other SoCs when it's
done at subsys_init? So far I cannot think of any. The only dependency
of pl330 is the ARM apb_pclk, required during AMBA bus probe. But that's
usually ready before subsys_init.

Thanks,

Ray

Lars-Peter Clausen

unread,
Oct 17, 2014, 12:40:03 PM10/17/14
to
dma_request_slave_channel() has the problem that we can't differentiate
between no channel provided and channel provided but the dma driver hasn't
probed yet. The function will return NULL in both cases. But Stephen Warren
added dma_request_slave_channel_reason() a while ago to solve this problem.
This function returns a ERR_PTR. If it returns ERR_PTR(-EPROBE_DEFER) it
means that a channel has been provided but the DMA driver hasn't probed yet.
In this case the SPI driver should return -EPROBE_DEFER to try again later.
If the function returns a different error code that means that it was not
possible to get the DMA channel and it should fall back to PIO.

>
>>
>> But in any case fiddling around with the init sequences is just a quick
>> hack and might makes the problem less likely to appear in some cases,
>> but there is no guarantee that it works. And I think the proper solution
>> at the moment is to use probe deferral.
> I think it makes sense to have the DMA driver, as one of the core components
> in various SoCs that a lot of peripheral drivers depend on, to be registered
> at the level of subsys_init or somewhere close. We are not changing this
> just to get SPI to work. We are changing this because we think DMA should be
> ready before a lot of its slaves, which are typically done at device_initcall.

But if the DMA driver for example depends on a clock driver do you put the
clock driver at a even earlier init level? The problem with using init
levels for solving this problem is that there is only a small amount of init
levels available and representing the dependency chains is neither possible
with it nor were init level ever intended for solving this. EPROBE_DEFER on
the other hand is.

>
> I have no problem relying on EPROBE_DEFER for this, provided that it works.
> The issue is, like I mentioned above, for a lot of slave devices DMA is not
> mandatory, when DMA fails at probe they would fall back to PIO and never use
> DMA. Another disadvantage I see with EPROBE_DEFER is delayed boot time.
>

Yea, the EPROBE_DEFER implementation is not ideal, but that is a problem
that should be solved rather than working around it. I think there are
patches somewhere for example that build a device dependency graph from the
phandles in the devicetree and than probe devices in the correct order to
reduce the number of times probe deferral is necessary.

>>
>> Other subsystems have seen patches which moved drivers from using
>> subsys_initcall to device_initcall/module_..._driver/ with the reasoning
>> that this is no longer necessary because of EPROBE_DEFER. So I don't
>> think we should be doing the exact opposite in DMA framework. Also if
>> we'd apply this patch it won't take to long until somebody suggest going
>> back to module_platform_driver() instead of subsys_initcall.
>>
>> - Lars
> There are currently 12 DMA drivers under drivers/dma registering themselves
> at subsys_init. I don't see why pl330 cannot do the same. Is there any
> concern that it may not work for some other SoCs when it's done at
> subsys_init? So far I cannot think of any. The only dependency of pl330 is
> the ARM apb_pclk, required during AMBA bus probe. But that's usually ready
> before subsys_init.

Those other drivers should be converted to device_initcall rather than
converting the PL330 driver to subsys_init. Using subsys_init for device
drivers is a hack which was used to try to solve ordering problems. But it
doesn't work that great, especially if you have more than two devices in
your dependency chain. The solution that people have come up with to solve
this problem in a better way is probe deferral by the means of -EPROBE_DEFER.

- Lars

Ray Jui

unread,
Oct 17, 2014, 1:00:03 PM10/17/14
to
Thanks for the information. This will solve our problem.
Agreed. Yes, it would be very nice if we can eventually describe the
dependencies of various components in a system by utilizing the device
tree. This way the dependencies can be customized for each individual SoC.

>>>
>>> Other subsystems have seen patches which moved drivers from using
>>> subsys_initcall to device_initcall/module_..._driver/ with the reasoning
>>> that this is no longer necessary because of EPROBE_DEFER. So I don't
>>> think we should be doing the exact opposite in DMA framework. Also if
>>> we'd apply this patch it won't take to long until somebody suggest going
>>> back to module_platform_driver() instead of subsys_initcall.
>>>
>>> - Lars
>> There are currently 12 DMA drivers under drivers/dma registering
>> themselves
>> at subsys_init. I don't see why pl330 cannot do the same. Is there any
>> concern that it may not work for some other SoCs when it's done at
>> subsys_init? So far I cannot think of any. The only dependency of
>> pl330 is
>> the ARM apb_pclk, required during AMBA bus probe. But that's usually
>> ready
>> before subsys_init.
>
> Those other drivers should be converted to device_initcall rather than
> converting the PL330 driver to subsys_init. Using subsys_init for device
> drivers is a hack which was used to try to solve ordering problems. But
> it doesn't work that great, especially if you have more than two devices
> in your dependency chain. The solution that people have come up with to
> solve this problem in a better way is probe deferral by the means of
> -EPROBE_DEFER.
>
> - Lars
>
Thanks, Lars, for providing these information. They are very useful!

Ray

Vinod Koul

unread,
Oct 21, 2014, 7:30:02 AM10/21/14
to
Sure, I don't mind moving the driver to module_ if that solves the problem
with defer probe. I am still not able to wrap my head around on how will
deferral probe solve the problem, for example in above SPI case.

So when we request & channel is not found, it can be all that channels are
given out so nothing to give or controller of interest not available. How do
we distinguish between these and how does controller driver say "looks like
device might not be probed, let me try later"?

--
~Vinod

Vinod Koul

unread,
Oct 21, 2014, 7:30:02 AM10/21/14
to
So when should the SPI here check, if dmaengine is available. The client
doesn't grab channel in its probe, so the client cannot return
-EPROBE_DEFER.

--
~Vinod

Ray Jui

unread,
Oct 21, 2014, 12:20:03 PM10/21/14
to
Currently the spi-pl022 driver requests its DMA channel in
pl022_dma_autoprobe, called during driver probe. Lars suggested changing
the dma_request_slave_channel call to dma_request_slave_channel_reason.
From my understanding, dma_request_slave_channel_reason should return
-EPROBE_DEFER if the DMA controller (pl330) device node and its channels
are declared in device tree but the driver hasn't been probed. The SPI
driver can then return -EPROBE_DEFER, which will cause its probe to be
done again, at a later time. By then the pl330 driver will be ready.

This will solve our problem, because we don't care when SPI is probed,
but we want to use DMA instead of PIO. But for the original problem that
Linus tried to solve by moving spi probe to subsys_initcall in 25c8e03b,
if one wants spi probe to be done that early, it will not be able to use
DMA with the currently proposed change.

Ray Jui

unread,
Nov 27, 2014, 6:50:05 PM11/27/14
to
Device tree binding documentation for Broadcom Cygnus pinctrl driver

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 ++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt

diff --git a/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
new file mode 100644
index 0000000..86e4579
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
@@ -0,0 +1,92 @@
+Broadcom Cygnus Pin Controller
+
+The Cygnus pin controller supports setting the alternate functions of groups
+of pins. Pinmux configuration on individual pins is not supported by the
+Cygnus A0 SoC.
+
+Required properties:
+
+- compatible:
+ Must be "brcm,cygnus-pinctrl"
+
+- reg:
+ Define the base and range of the I/O address space that contain the Cygnus
+pin control registers
+
+- brcm,groups:
+ This can be strings of one or more group names. This defines the group(s)
+that one wants to configure
+
+- brcm,function:
+ This is the alternate function that one wants to configure to. Valid
+alternate functions are "alt1", "alt2", "alt3", "alt4"
+
+Each child node represents a configuration. Client devices reference the the
+child node to enable the mux configuration.
+
+For example:
+
+ pinctrl: pinctrl@0x0301d0c8 {
+ compatible = "brcm,cygnus-pinctrl";
+ reg = <0x0301d0c8 0x2c>;
+
+ i2s_0: i2s_0 {
+ brcm,groups = "smart_card0", "smart_card0_fcb";
+ brcm,function = "alt2";
+ };
+
+ i2s_1: i2s_1 {
+ brcm,groups = "smart_card1", "smart_card1_fcb";
+ brcm,function = "alt2";
+ };
+
+ spi_0: spi_0 {
+ brcm,groups = "spi0";
+ brcm,function = "alt1";
+ };
+ }
+
+ spi0@18028000 {
+ compatible = "arm,pl022", "arm,primecell";
+ reg = <0x18028000 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
+ pinctrl-0 = <&spi_0>;
+ clocks = <&axi81_clk>;
+ clock-names = "apb_pclk";
+ };
+
+Consider the following snapshot of Cygnus pinmux table:
+
+number pin group alt1 alt2 alt3 alt4
+------ --- ---- ---- ---- ---- ----
+42 sc0_clk smart_card0 SMART CARD0 I2S_0 N/A chip_gpio24
+43 sc0_cmdvcc_l smart_card0 SMART CARD0 I2S_0 N/A STRAP
+44 sc0_detect smart_card0 SMART CARD0 I2S_0 N/A chip_gpio25
+45 sc0_fcb smart_card0_fcb SMART CARD0_FCB I2S_0 N/A chip_gpio26
+46 sc0_io smart_card0 SMART CARD0 I2S_0 N/A chip_gpio27
+47 sc0_rst_l smart_card0 SMART CARD0 SPDIF N/A STRAP
+
+Note due to limitation of the Cygnus hardware, pinmux configuration can only
+be group based. To enable I2S_0 function, one needs the following child node
+configuration:
+
+ i2s_0: i2s_0 {
+ brcm,groups = "smart_card0", "smart_card0_fcb";
+ brcm,function = "alt2";
+ };
+
+This tells the Cygnus pin controller to configure groups "smart_card0" and
+"smart_card0_fcb" to I2S_0. With this configuration, pins 42, 43, 44, 45, 46
+become I2C_0, and pin 47 becomes SPDIF
+
+Consider another example, that one wants to configure the above pins as GPIO:
+
+ gpio_24_27: gpio_24_27 {
+ brcm,groups = "smart_card0", "smart_card0_fcb";
+ brcm,function = "alt4";
+ };
+
+With the above configuration, pins 42, 44, 45, 46 become GPIO, and 43 and 47
+become reserved for STRAP
--
1.7.9.5

Ray Jui

unread,
Nov 27, 2014, 6:50:05 PM11/27/14
to
This enables the pinctrl support for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..4c6bf4d 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,11 @@

/include/ "bcm-cygnus-clock.dtsi"

+ pinctrl: pinctrl@0x0301d0c8 {
+ compatible = "brcm,cygnus-pinctrl";
+ reg = <0x0301d0c8 0x2c>;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Nov 27, 2014, 6:50:06 PM11/27/14
to
This adds the initial driver support for the Broadcom Cygnus pinctrl
controller. The Cygnus pinctrl controller supports group based
alternate function configuration

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
Signed-off-by: Fengguang Wu <fenggu...@intel.com>
---
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-bcm-cygnus.c | 753 ++++++++++++++++++++++++++++++++++
3 files changed, 761 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-bcm-cygnus.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index d014f22..4549e9f 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -85,6 +85,13 @@ config PINCTRL_BCM281XX
BCM28145, and BCM28155 SoCs. This driver requires the pinctrl
framework. GPIO is provided by a separate GPIO driver.

+config PINCTRL_BCM_CYGNUS
+ bool "Broadcom Cygnus pinctrl driver"
+ depends on (ARCH_BCM_CYGNUS || COMPILE_TEST)
+ select PINMUX
+ select PINCONF
+ select GENERIC_PINCONF
+
config PINCTRL_LANTIQ
bool
depends on LANTIQ
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index c030b3d..4ed8e8a 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_PINCTRL_BF60x) += pinctrl-adi2-bf60x.o
obj-$(CONFIG_PINCTRL_AT91) += pinctrl-at91.o
obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o
obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o
+obj-$(CONFIG_PINCTRL_BCM_CYGNUS) += pinctrl-bcm-cygnus.o
obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o
obj-$(CONFIG_PINCTRL_PALMAS) += pinctrl-palmas.o
obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
diff --git a/drivers/pinctrl/pinctrl-bcm-cygnus.c b/drivers/pinctrl/pinctrl-bcm-cygnus.c
new file mode 100644
index 0000000..eb6e27a
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-bcm-cygnus.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "pinctrl-utils.h"
+
+/*
+ * Alternate function configuration
+ *
+ * @name: name of the alternate function
+ * @group_names: array of strings of group names that can be supported by this
+ * alternate function
+ * @num_groups: total number of groups that can be supported by this alternate
+ * function
+ * @mux: mux setting for this alternate function to be programed
+ */
+struct cygnus_pin_function {
+ const char *name;
+ const char * const *group_names;
+ const unsigned num_groups;
+ unsigned int mux;
+};
+
+/*
+ * Cygnus allows group based pinmux configuration
+ *
+ * @name: name of the group
+ * @pins: array of pins used by this group
+ * @num_pins: total number of pins used by this group
+ * @offset: register offset for pinmux configuration of this group
+ * @shift: bit shift for pinmux configuration of this group
+ */
+struct cygnus_pin_group {
+ const char *name;
+ const unsigned *pins;
+ const unsigned num_pins;
+ const unsigned int offset;
+ const unsigned int shift;
+};
+
+/*
+ * Cygnus pinctrl core
+ *
+ * @pctl: pointer to pinctrl_dev
+ * @dev: pointer to the device
+ * @base: I/O register base for Cygnus pinctrl configuration
+ *
+ */
+struct cygnus_pinctrl {
+ struct pinctrl_dev *pctl;
+ struct device *dev;
+ void __iomem *base;
+
+ const struct pinctrl_pin_desc *pins;
+ unsigned num_pins;
+
+ const struct cygnus_pin_group *groups;
+ unsigned num_groups;
+
+ const struct cygnus_pin_function *functions;
+ unsigned num_functions;
+};
+
+#define CYGNUS_PIN_GROUP(group_name, off, sh) \
+{ \
+ .name = #group_name, \
+ .pins = group_name##_pins, \
+ .num_pins = ARRAY_SIZE(group_name##_pins), \
+ .offset = off, \
+ .shift = sh, \
+}
+
+/*
+ * The following pin description is based on Cygnus I/O MUX spreadsheet
+ */
+static const struct pinctrl_pin_desc cygnus_pinctrl_pins[] = {
+ PINCTRL_PIN(0, "ext_device_reset_n"),
+ PINCTRL_PIN(1, "chip_mode0"),
+ PINCTRL_PIN(2, "chip_mode1"),
+ PINCTRL_PIN(3, "chip_mode2"),
+ PINCTRL_PIN(4, "chip_mode3"),
+ PINCTRL_PIN(5, "chip_mode4"),
+ PINCTRL_PIN(6, "bsc0_scl"),
+ PINCTRL_PIN(7, "bsc0_sda"),
+ PINCTRL_PIN(8, "bsc1_scl"),
+ PINCTRL_PIN(9, "bsc1_sda"),
+ PINCTRL_PIN(10, "d1w_dq"),
+ PINCTRL_PIN(11, "d1wowstz_l"),
+ PINCTRL_PIN(12, "gpio0"),
+ PINCTRL_PIN(13, "gpio1"),
+ PINCTRL_PIN(14, "gpio2"),
+ PINCTRL_PIN(15, "gpio3"),
+ PINCTRL_PIN(16, "gpio4"),
+ PINCTRL_PIN(17, "gpio5"),
+ PINCTRL_PIN(18, "gpio6"),
+ PINCTRL_PIN(19, "gpio7"),
+ PINCTRL_PIN(20, "gpio8"),
+ PINCTRL_PIN(21, "gpio9"),
+ PINCTRL_PIN(22, "gpio10"),
+ PINCTRL_PIN(23, "gpio11"),
+ PINCTRL_PIN(24, "gpio12"),
+ PINCTRL_PIN(25, "gpio13"),
+ PINCTRL_PIN(26, "gpio14"),
+ PINCTRL_PIN(27, "gpio15"),
+ PINCTRL_PIN(28, "gpio16"),
+ PINCTRL_PIN(29, "gpio17"),
+ PINCTRL_PIN(30, "gpio18"),
+ PINCTRL_PIN(31, "gpio19"),
+ PINCTRL_PIN(32, "gpio20"),
+ PINCTRL_PIN(33, "gpio21"),
+ PINCTRL_PIN(34, "gpio22"),
+ PINCTRL_PIN(35, "gpio23"),
+ PINCTRL_PIN(36, "mdc"),
+ PINCTRL_PIN(37, "mdio"),
+ PINCTRL_PIN(38, "pwm0"),
+ PINCTRL_PIN(39, "pwm1"),
+ PINCTRL_PIN(40, "pwm2"),
+ PINCTRL_PIN(41, "pwm3"),
+ PINCTRL_PIN(42, "sc0_clk"),
+ PINCTRL_PIN(43, "sc0_cmdvcc_l"),
+ PINCTRL_PIN(44, "sc0_detect"),
+ PINCTRL_PIN(45, "sc0_fcb"),
+ PINCTRL_PIN(46, "sc0_io"),
+ PINCTRL_PIN(47, "sc0_rst_l"),
+ PINCTRL_PIN(48, "sc1_clk"),
+ PINCTRL_PIN(49, "sc1_cmdvcc_l"),
+ PINCTRL_PIN(50, "sc1_detect"),
+ PINCTRL_PIN(51, "sc1_fcb"),
+ PINCTRL_PIN(52, "sc1_io"),
+ PINCTRL_PIN(53, "sc1_rst_l"),
+ PINCTRL_PIN(54, "spi0_clk"),
+ PINCTRL_PIN(55, "spi0_mosi"),
+ PINCTRL_PIN(56, "spi0_miso"),
+ PINCTRL_PIN(57, "spi0_ss"),
+ PINCTRL_PIN(58, "spi1_clk"),
+ PINCTRL_PIN(59, "spi1_mosi"),
+ PINCTRL_PIN(60, "spi1_miso"),
+ PINCTRL_PIN(61, "spi1_ss"),
+ PINCTRL_PIN(62, "spi2_clk"),
+ PINCTRL_PIN(63, "spi2_mosi"),
+ PINCTRL_PIN(64, "spi2_miso"),
+ PINCTRL_PIN(65, "spi2_ss"),
+ PINCTRL_PIN(66, "spi3_clk"),
+ PINCTRL_PIN(67, "spi3_mosi"),
+ PINCTRL_PIN(68, "spi3_miso"),
+ PINCTRL_PIN(69, "spi3_ss"),
+ PINCTRL_PIN(70, "uart0_cts"),
+ PINCTRL_PIN(71, "uart0_rts"),
+ PINCTRL_PIN(72, "uart0_rx"),
+ PINCTRL_PIN(73, "uart0_tx"),
+ PINCTRL_PIN(74, "uart1_cts"),
+ PINCTRL_PIN(75, "uart1_dcd"),
+ PINCTRL_PIN(76, "uart1_dsr"),
+ PINCTRL_PIN(77, "uart1_dtr"),
+ PINCTRL_PIN(78, "uart1_ri"),
+ PINCTRL_PIN(79, "uart1_rts"),
+ PINCTRL_PIN(80, "uart1_rx"),
+ PINCTRL_PIN(81, "uart1_tx"),
+ PINCTRL_PIN(82, "uart3_rx"),
+ PINCTRL_PIN(83, "uart3_tx"),
+ PINCTRL_PIN(84, "sdio1_clk_sdcard"),
+ PINCTRL_PIN(85, "sdio1_cmd"),
+ PINCTRL_PIN(86, "sdio1_data0"),
+ PINCTRL_PIN(87, "sdio1_data1"),
+ PINCTRL_PIN(88, "sdio1_data2"),
+ PINCTRL_PIN(89, "sdio1_data3"),
+ PINCTRL_PIN(90, "sdio1_wp_n"),
+ PINCTRL_PIN(91, "sdio1_card_rst"),
+ PINCTRL_PIN(92, "sdio1_led_on"),
+ PINCTRL_PIN(93, "sdio1_cd"),
+ PINCTRL_PIN(94, "sdio0_clk_sdcard"),
+ PINCTRL_PIN(95, "sdio0_cmd"),
+ PINCTRL_PIN(96, "sdio0_data0"),
+ PINCTRL_PIN(97, "sdio0_data1"),
+ PINCTRL_PIN(98, "sdio0_data2"),
+ PINCTRL_PIN(99, "sdio0_data3"),
+ PINCTRL_PIN(100, "sdio0_wp_n"),
+ PINCTRL_PIN(101, "sdio0_card_rst"),
+ PINCTRL_PIN(102, "sdio0_led_on"),
+ PINCTRL_PIN(103, "sdio0_cd"),
+ PINCTRL_PIN(104, "sflash_clk"),
+ PINCTRL_PIN(105, "sflash_cs_l"),
+ PINCTRL_PIN(106, "sflash_mosi"),
+ PINCTRL_PIN(107, "sflash_miso"),
+ PINCTRL_PIN(108, "sflash_wp_n"),
+ PINCTRL_PIN(109, "sflash_hold_n"),
+ PINCTRL_PIN(110, "nand_ale"),
+ PINCTRL_PIN(111, "nand_ce0_l"),
+ PINCTRL_PIN(112, "nand_ce1_l"),
+ PINCTRL_PIN(113, "nand_cle"),
+ PINCTRL_PIN(114, "nand_dq0"),
+ PINCTRL_PIN(115, "nand_dq1"),
+ PINCTRL_PIN(116, "nand_dq2"),
+ PINCTRL_PIN(117, "nand_dq3"),
+ PINCTRL_PIN(118, "nand_dq4"),
+ PINCTRL_PIN(119, "nand_dq5"),
+ PINCTRL_PIN(120, "nand_dq6"),
+ PINCTRL_PIN(121, "nand_dq7"),
+ PINCTRL_PIN(122, "nand_rb_l"),
+ PINCTRL_PIN(123, "nand_re_l"),
+ PINCTRL_PIN(124, "nand_we_l"),
+ PINCTRL_PIN(125, "nand_wp_l"),
+ PINCTRL_PIN(126, "lcd_clac"),
+ PINCTRL_PIN(127, "lcd_clcp"),
+ PINCTRL_PIN(128, "lcd_cld0"),
+ PINCTRL_PIN(129, "lcd_cld1"),
+ PINCTRL_PIN(130, "lcd_cld10"),
+ PINCTRL_PIN(131, "lcd_cld11"),
+ PINCTRL_PIN(132, "lcd_cld12"),
+ PINCTRL_PIN(133, "lcd_cld13"),
+ PINCTRL_PIN(134, "lcd_cld14"),
+ PINCTRL_PIN(135, "lcd_cld15"),
+ PINCTRL_PIN(136, "lcd_cld16"),
+ PINCTRL_PIN(137, "lcd_cld17"),
+ PINCTRL_PIN(138, "lcd_cld18"),
+ PINCTRL_PIN(139, "lcd_cld19"),
+ PINCTRL_PIN(140, "lcd_cld2"),
+ PINCTRL_PIN(141, "lcd_cld20"),
+ PINCTRL_PIN(142, "lcd_cld21"),
+ PINCTRL_PIN(143, "lcd_cld22"),
+ PINCTRL_PIN(144, "lcd_cld23"),
+ PINCTRL_PIN(145, "lcd_cld3"),
+ PINCTRL_PIN(146, "lcd_cld4"),
+ PINCTRL_PIN(147, "lcd_cld5"),
+ PINCTRL_PIN(148, "lcd_cld6"),
+ PINCTRL_PIN(149, "lcd_cld7"),
+ PINCTRL_PIN(150, "lcd_cld8"),
+ PINCTRL_PIN(151, "lcd_cld9"),
+ PINCTRL_PIN(152, "lcd_clfp"),
+ PINCTRL_PIN(153, "lcd_clle"),
+ PINCTRL_PIN(154, "lcd_cllp"),
+ PINCTRL_PIN(155, "lcd_clpower"),
+ PINCTRL_PIN(156, "camera_vsync"),
+ PINCTRL_PIN(157, "camera_trigger"),
+ PINCTRL_PIN(158, "camera_strobe"),
+ PINCTRL_PIN(159, "camera_standby"),
+ PINCTRL_PIN(160, "camera_reset_n"),
+ PINCTRL_PIN(161, "camera_pixdata9"),
+ PINCTRL_PIN(162, "camera_pixdata8"),
+ PINCTRL_PIN(163, "camera_pixdata7"),
+ PINCTRL_PIN(164, "camera_pixdata6"),
+ PINCTRL_PIN(165, "camera_pixdata5"),
+ PINCTRL_PIN(166, "camera_pixdata4"),
+ PINCTRL_PIN(167, "camera_pixdata3"),
+ PINCTRL_PIN(168, "camera_pixdata2"),
+ PINCTRL_PIN(169, "camera_pixdata1"),
+ PINCTRL_PIN(170, "camera_pixdata0"),
+ PINCTRL_PIN(171, "camera_pixclk"),
+ PINCTRL_PIN(172, "camera_hsync"),
+ PINCTRL_PIN(173, "camera_pll_ref_clk"),
+ PINCTRL_PIN(174, "usb_id_indication"),
+ PINCTRL_PIN(175, "usb_vbus_indication"),
+ PINCTRL_PIN(176, "gpio0_3p3"),
+ PINCTRL_PIN(177, "gpio1_3p3"),
+ PINCTRL_PIN(178, "gpio2_3p3"),
+ PINCTRL_PIN(179, "gpio3_3p3"),
+};
+
+/*
+ * List of groups of pins
+ */
+static const unsigned gpio0_pins[] = { 12 };
+static const unsigned gpio1_pins[] = { 13 };
+static const unsigned gpio2_pins[] = { 14 };
+static const unsigned gpio3_pins[] = { 15 };
+static const unsigned gpio4_pins[] = { 16 };
+static const unsigned gpio5_pins[] = { 17 };
+static const unsigned gpio6_pins[] = { 18 };
+static const unsigned gpio7_pins[] = { 19 };
+static const unsigned gpio8_pins[] = { 20 };
+static const unsigned gpio9_pins[] = { 21 };
+static const unsigned gpio10_pins[] = { 22 };
+static const unsigned gpio11_pins[] = { 23 };
+static const unsigned gpio12_pins[] = { 24 };
+static const unsigned gpio13_pins[] = { 25 };
+static const unsigned gpio14_pins[] = { 26 };
+static const unsigned gpio15_pins[] = { 27 };
+static const unsigned gpio16_pins[] = { 28 };
+static const unsigned gpio17_pins[] = { 29 };
+static const unsigned gpio18_pins[] = { 30 };
+static const unsigned gpio19_pins[] = { 31 };
+static const unsigned gpio20_pins[] = { 32 };
+static const unsigned gpio21_pins[] = { 33 };
+static const unsigned gpio22_pins[] = { 34 };
+static const unsigned gpio23_pins[] = { 35 };
+static const unsigned pwm0_pins[] = { 38 };
+static const unsigned pwm1_pins[] = { 39 };
+static const unsigned pwm2_pins[] = { 40 };
+static const unsigned pwm3_pins[] = { 41 };
+static const unsigned sdio0_pins[] = { 94, 95, 96, 97, 98, 99 };
+static const unsigned smart_card0_pins[] = { 42, 43, 44, 46, 47 };
+static const unsigned smart_card1_pins[] = { 48, 49, 50, 52, 53 };
+static const unsigned spi0_pins[] = { 54, 55, 56, 57 };
+static const unsigned spi1_pins[] = { 58, 59, 60, 61 };
+static const unsigned spi2_pins[] = { 62, 63, 64, 65 };
+static const unsigned spi3_pins[] = { 66, 67, 68, 69 };
+static const unsigned d1w_pins[] = { 10, 11 };
+static const unsigned lcd_pins[] = { 126, 127, 128, 129, 130, 131, 132, 133,
+ 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147,
+ 148, 149, 150, 151, 152, 153, 154, 155 };
+static const unsigned uart0_pins[] = { 70, 71, 72, 73 };
+static const unsigned uart1_dte_pins[] = { 75, 76, 77, 78 };
+static const unsigned uart1_pins[] = { 74, 79, 80, 81 };
+static const unsigned uart3_pins[] = { 82, 83 };
+static const unsigned qspi_pins[] = { 104, 105, 106, 107 };
+static const unsigned nand_pins[] = { 110, 111, 112, 113, 114, 115, 116, 117,
+ 118, 119, 120, 121, 122, 123, 124, 125 };
+static const unsigned sdio0_cd_pins[] = { 103 };
+static const unsigned sdio0_mmc_pins[] = { 100, 101, 102 };
+static const unsigned can0_spi4_pins[] = { 86, 87 };
+static const unsigned can1_spi4_pins[] = { 88, 89 };
+static const unsigned sdio1_cd_pins[] = { 93 };
+static const unsigned sdio1_led_pins[] = { 84, 85 };
+static const unsigned sdio1_mmc_pins[] = { 90, 91, 92 };
+static const unsigned camera_led_pins[] = { 156, 157, 158, 159, 160 };
+static const unsigned camera_rgmii_pins[] = { 169, 170, 171, 169, 170, 171,
+ 172, 173 };
+static const unsigned camera_sram_rgmii_pins[] = { 161, 162, 163, 164, 165,
+ 166, 167, 168 };
+static const unsigned qspi_gpio_pins[] = { 108, 109 };
+static const unsigned smart_card0_fcb_pins[] = { 45 };
+static const unsigned smart_card1_fcb_pins[] = { 51 };
+static const unsigned gpio0_3p3_pins[] = { 176 };
+static const unsigned gpio1_3p3_pins[] = { 177 };
+static const unsigned gpio2_3p3_pins[] = { 178 };
+
+/*
+ * List of groups names. Need to match the order in cygnus_pin_groups
+ */
+static const char * const cygnus_pin_group_names[] = {
+ "gpio0",
+ "gpio1",
+ "gpio2",
+ "gpio3",
+ "gpio4",
+ "gpio5",
+ "gpio6",
+ "gpio7",
+ "gpio8",
+ "gpio9",
+ "gpio10",
+ "gpio11",
+ "gpio12",
+ "gpio13",
+ "gpio14",
+ "gpio15",
+ "gpio16",
+ "gpio17",
+ "gpio18",
+ "gpio19",
+ "gpio20",
+ "gpio21",
+ "gpio22",
+ "gpio23",
+ "pwm0",
+ "pwm1",
+ "pwm2",
+ "pwm3",
+ "sdio0",
+ "smart_card0",
+ "smart_card1",
+ "spi0",
+ "spi1",
+ "spi2",
+ "spi3",
+ "d1w",
+ "lcd",
+ "uart0",
+ "uart1_dte",
+ "uart1",
+ "uart3",
+ "qspi",
+ "nand",
+ "sdio0_cd",
+ "sdio0_mmc",
+ "can0_spi4",
+ "can1_spi4",
+ "sdio1_cd",
+ "sdio1_led",
+ "sdio1_mmc",
+ "camera_led",
+ "camera_rgmii",
+ "camera_sram_rgmii",
+ "qspi_gpio",
+ "smart_card0_fcb",
+ "smart_card1_fcb",
+ "gpio0_3p3",
+ "gpio1_3p3",
+ "gpio2_3p3",
+};
+
+/*
+ * List of groups. Need to match the order in cygnus_pin_group_names
+ */
+static const struct cygnus_pin_group cygnus_pin_groups[] = {
+ CYGNUS_PIN_GROUP(gpio0, 0x0, 0),
+ CYGNUS_PIN_GROUP(gpio1, 0x0, 4),
+ CYGNUS_PIN_GROUP(gpio2, 0x0, 8),
+ CYGNUS_PIN_GROUP(gpio3, 0x0, 12),
+ CYGNUS_PIN_GROUP(gpio4, 0x0, 16),
+ CYGNUS_PIN_GROUP(gpio5, 0x0, 20),
+ CYGNUS_PIN_GROUP(gpio6, 0x0, 24),
+ CYGNUS_PIN_GROUP(gpio7, 0x0, 28),
+ CYGNUS_PIN_GROUP(gpio8, 0x4, 0),
+ CYGNUS_PIN_GROUP(gpio9, 0x4, 4),
+ CYGNUS_PIN_GROUP(gpio10, 0x4, 8),
+ CYGNUS_PIN_GROUP(gpio11, 0x4, 12),
+ CYGNUS_PIN_GROUP(gpio12, 0x4, 16),
+ CYGNUS_PIN_GROUP(gpio13, 0x4, 20),
+ CYGNUS_PIN_GROUP(gpio14, 0x4, 24),
+ CYGNUS_PIN_GROUP(gpio15, 0x4, 28),
+ CYGNUS_PIN_GROUP(gpio16, 0x8, 0),
+ CYGNUS_PIN_GROUP(gpio17, 0x8, 4),
+ CYGNUS_PIN_GROUP(gpio18, 0x8, 8),
+ CYGNUS_PIN_GROUP(gpio19, 0x8, 12),
+ CYGNUS_PIN_GROUP(gpio20, 0x8, 16),
+ CYGNUS_PIN_GROUP(gpio21, 0x8, 20),
+ CYGNUS_PIN_GROUP(gpio22, 0x8, 24),
+ CYGNUS_PIN_GROUP(gpio23, 0x8, 28),
+ CYGNUS_PIN_GROUP(pwm0, 0xc, 0),
+ CYGNUS_PIN_GROUP(pwm1, 0xc, 4),
+ CYGNUS_PIN_GROUP(pwm2, 0xc, 8),
+ CYGNUS_PIN_GROUP(pwm3, 0xc, 12),
+ CYGNUS_PIN_GROUP(sdio0, 0xc, 16),
+ CYGNUS_PIN_GROUP(smart_card0, 0xc, 20),
+ CYGNUS_PIN_GROUP(smart_card1, 0xc, 24),
+ CYGNUS_PIN_GROUP(spi0, 0x10, 0),
+ CYGNUS_PIN_GROUP(spi1, 0x10, 4),
+ CYGNUS_PIN_GROUP(spi2, 0x10, 8),
+ CYGNUS_PIN_GROUP(spi3, 0x10, 12),
+ CYGNUS_PIN_GROUP(d1w, 0x10, 16),
+ CYGNUS_PIN_GROUP(lcd, 0x10, 20),
+ CYGNUS_PIN_GROUP(uart0, 0x14, 0),
+ CYGNUS_PIN_GROUP(uart1_dte, 0x14, 4),
+ CYGNUS_PIN_GROUP(uart1, 0x14, 8),
+ CYGNUS_PIN_GROUP(uart3, 0x14, 12),
+ CYGNUS_PIN_GROUP(qspi, 0x14, 16),
+ CYGNUS_PIN_GROUP(nand, 0x14, 20),
+ CYGNUS_PIN_GROUP(sdio0_cd, 0x18, 0),
+ CYGNUS_PIN_GROUP(sdio0_mmc, 0x18, 4),
+ CYGNUS_PIN_GROUP(can0_spi4, 0x18, 8),
+ CYGNUS_PIN_GROUP(can1_spi4, 0x18, 12),
+ CYGNUS_PIN_GROUP(sdio1_cd, 0x18, 16),
+ CYGNUS_PIN_GROUP(sdio1_led, 0x18, 20),
+ CYGNUS_PIN_GROUP(sdio1_mmc, 0x18, 24),
+ CYGNUS_PIN_GROUP(camera_led, 0x1c, 0),
+ CYGNUS_PIN_GROUP(camera_rgmii, 0x1c, 4),
+ CYGNUS_PIN_GROUP(camera_sram_rgmii, 0x1c, 8),
+ CYGNUS_PIN_GROUP(qspi_gpio, 0x1c, 12),
+ CYGNUS_PIN_GROUP(smart_card0_fcb, 0x20, 0),
+ CYGNUS_PIN_GROUP(smart_card1_fcb, 0x20, 4),
+ CYGNUS_PIN_GROUP(gpio0_3p3, 0x28, 0),
+ CYGNUS_PIN_GROUP(gpio1_3p3, 0x28, 4),
+ CYGNUS_PIN_GROUP(gpio2_3p3, 0x28, 8),
+};
+
+#define CYGNUS_PIN_FUNCTION(fcn_name, mux_val) \
+{ \
+ .name = #fcn_name, \
+ .group_names = cygnus_pin_group_names, \
+ .num_groups = ARRAY_SIZE(cygnus_pin_group_names), \
+ .mux = mux_val, \
+}
+
+/*
+ * Cygnus has 4 alternate functions. All groups can be configured to any of
+ * the 4 alternate functions
+ */
+static const struct cygnus_pin_function cygnus_pin_functions[] = {
+ CYGNUS_PIN_FUNCTION(alt1, 0),
+ CYGNUS_PIN_FUNCTION(alt2, 1),
+ CYGNUS_PIN_FUNCTION(alt3, 2),
+ CYGNUS_PIN_FUNCTION(alt4, 3),
+};
+
+static int cygnus_get_groups_count(struct pinctrl_dev *pctrl_dev)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ return pinctrl->num_groups;
+}
+
+static const char *cygnus_get_group_name(struct pinctrl_dev *pctrl_dev,
+ unsigned selector)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ return pinctrl->groups[selector].name;
+}
+
+static int cygnus_get_group_pins(struct pinctrl_dev *pctrl_dev,
+ unsigned selector, const unsigned **pins,
+ unsigned *num_pins)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ *pins = pinctrl->groups[selector].pins;
+ *num_pins = pinctrl->groups[selector].num_pins;
+
+ return 0;
+}
+
+static void cygnus_pin_dbg_show(struct pinctrl_dev *pctrl_dev,
+ struct seq_file *s, unsigned offset)
+{
+ seq_printf(s, " %s", dev_name(pctrl_dev->dev));
+}
+
+static int find_matched_function(const char *function_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cygnus_pin_functions); i++) {
+ if (!strcmp(cygnus_pin_functions[i].name, function_name))
+ return (int)cygnus_pin_functions[i].mux;
+ }
+
+ return -EINVAL;
+}
+
+static int cygnus_dt_node_to_map(struct pinctrl_dev *pctrl_dev,
+ struct device_node *np, struct pinctrl_map **map,
+ unsigned *num_maps)
+{
+ int ret, num_groups;
+ unsigned reserved_maps = 0;
+ struct property *prop;
+ const char *group_name, *function_name;
+
+ *map = NULL;
+ *num_maps = 0;
+
+ num_groups = of_property_count_strings(np, "brcm,groups");
+ if (num_groups < 0) {
+ dev_err(pctrl_dev->dev,
+ "could not parse property brcm,groups\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_string(np, "brcm,function", &function_name);
+ if (ret < 0) {
+ dev_err(pctrl_dev->dev,
+ "could not parse property brcm,function\n");
+ return -EINVAL;
+ }
+
+ /* make sure it's a valid alternate function */
+ ret = find_matched_function(function_name);
+ if (ret < 0) {
+ dev_err(pctrl_dev->dev, "invalid function name: %s\n",
+ function_name);
+ }
+
+ ret = pinctrl_utils_reserve_map(pctrl_dev, map, &reserved_maps,
+ num_maps, num_groups);
+ if (ret) {
+ dev_err(pctrl_dev->dev, "unable to reserve map\n");
+ return ret;
+ }
+
+ of_property_for_each_string(np, "brcm,groups", prop, group_name) {
+ ret = pinctrl_utils_add_map_mux(pctrl_dev, map,
+ &reserved_maps, num_maps, group_name,
+ function_name);
+ if (ret) {
+ dev_err(pctrl_dev->dev, "can't add map: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct pinctrl_ops cygnus_pinctrl_ops = {
+ .get_groups_count = cygnus_get_groups_count,
+ .get_group_name = cygnus_get_group_name,
+ .get_group_pins = cygnus_get_group_pins,
+ .pin_dbg_show = cygnus_pin_dbg_show,
+ .dt_node_to_map = cygnus_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int cygnus_get_functions_count(struct pinctrl_dev *pctrl_dev)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ return pinctrl->num_functions;
+}
+
+static const char *cygnus_get_function_name(struct pinctrl_dev *pctrl_dev,
+ unsigned selector)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ return pinctrl->functions[selector].name;
+}
+
+static int cygnus_get_function_groups(struct pinctrl_dev *pctrl_dev,
+ unsigned selector, const char * const **groups,
+ unsigned * const num_groups)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+
+ *groups = pinctrl->functions[selector].group_names;
+ *num_groups = pinctrl->functions[selector].num_groups;
+
+ return 0;
+}
+
+static int cygnus_pinmux_set_mux(struct pinctrl_dev *pctrl_dev,
+ unsigned function_selector, unsigned group_selector)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ const struct cygnus_pin_function *function =
+ &pinctrl->functions[function_selector];
+ const struct cygnus_pin_group *group =
+ &pinctrl->groups[group_selector];
+ u32 val, mask = 0x7;
+
+ dev_dbg(pctrl_dev->dev,
+ "group:%s with offset:0x%08x shift:%u set to function: %s mux:%u\n",
+ group->name, group->offset, group->shift, function->name,
+ function->mux);
+
+ val = readl(pinctrl->base + group->offset);
+ val &= ~(mask << group->shift);
+ val |= function->mux << group->shift;
+ writel(val, pinctrl->base + group->offset);
+
+ return 0;
+}
+
+static struct pinmux_ops cygnus_pinmux_ops = {
+ .get_functions_count = cygnus_get_functions_count,
+ .get_function_name = cygnus_get_function_name,
+ .get_function_groups = cygnus_get_function_groups,
+ .set_mux = cygnus_pinmux_set_mux,
+};
+
+static struct pinctrl_desc cygnus_pinctrl_desc = {
+ .pctlops = &cygnus_pinctrl_ops,
+ .pmxops = &cygnus_pinmux_ops,
+ .owner = THIS_MODULE,
+};
+
+static int cygnus_pinctrl_probe(struct platform_device *pdev)
+{
+ struct cygnus_pinctrl *pinctrl;
+ struct resource *res;
+
+ pinctrl = devm_kzalloc(&pdev->dev, sizeof(*pinctrl), GFP_KERNEL);
+ if (!pinctrl) {
+ dev_err(&pdev->dev, "unable to allocate memory\n");
+ return -ENOMEM;
+ }
+ pinctrl->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "unable to get resource\n");
+ return -ENOENT;
+ }
+
+ pinctrl->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pinctrl->base)) {
+ dev_err(&pdev->dev, "unable to map I/O space\n");
+ return PTR_ERR(pinctrl->base);
+ }
+
+ pinctrl->pins = cygnus_pinctrl_pins;
+ pinctrl->num_pins = ARRAY_SIZE(cygnus_pinctrl_pins);
+ pinctrl->groups = cygnus_pin_groups;
+ pinctrl->num_groups = ARRAY_SIZE(cygnus_pin_groups);
+ pinctrl->functions = cygnus_pin_functions;
+ pinctrl->num_functions = ARRAY_SIZE(cygnus_pin_functions);
+
+ cygnus_pinctrl_desc.name = dev_name(&pdev->dev);
+ cygnus_pinctrl_desc.pins = cygnus_pinctrl_pins;
+ cygnus_pinctrl_desc.npins = ARRAY_SIZE(cygnus_pinctrl_pins);
+
+ pinctrl->pctl = pinctrl_register(&cygnus_pinctrl_desc, &pdev->dev,
+ pinctrl);
+ if (!pinctrl->pctl) {
+ dev_err(&pdev->dev, "unable to register cygnus pinctrl\n");
+ return -EINVAL;
+ }
+
+ platform_set_drvdata(pdev, pinctrl);
+
+ return 0;
+}
+
+static int cygnus_pinctrl_remove(struct platform_device *pdev)
+{
+ struct cygnus_pinctrl *pinctrl = platform_get_drvdata(pdev);
+
+ pinctrl_unregister(pinctrl->pctl);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct of_device_id cygnus_pinctrl_of_match[] = {
+ { .compatible = "brcm,cygnus-pinctrl", },
+ { },
+};
+
+static struct platform_driver cygnus_pinctrl_driver = {
+ .driver = {
+ .name = "cygnus-pinctrl",
+ .owner = THIS_MODULE,
+ .of_match_table = cygnus_pinctrl_of_match,
+ },
+ .probe = cygnus_pinctrl_probe,
+ .remove = cygnus_pinctrl_remove,
+};
+
+static int __init cygnus_pinctrl_init(void)
+{
+ return platform_driver_register(&cygnus_pinctrl_driver);
+}
+arch_initcall(cygnus_pinctrl_init);
+
+static void __exit cygnus_pinctrl_exit(void)
+{
+ platform_driver_unregister(&cygnus_pinctrl_driver);
+}
+module_exit(cygnus_pinctrl_exit);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Cygnus pinctrl driver");
+MODULE_LICENSE("GPL v2");

Ray Jui

unread,
Nov 27, 2014, 6:50:07 PM11/27/14
to
This patchset contains the initial pinctrl support for the Broadcom Cygnus SoC.
The Cygnus pinctrl controller supports group based alternate function configuration

Ray Jui (4):
pinctrl: Broadcom Cygnus pinctrl device tree binding
pinctrl: cygnus: add initial pinctrl support
ARM: mach-bcm: enable pinctrl support for Cygnus
ARM: dts: enable pinctrl for Broadcom Cygnus

.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 +++
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-bcm-cygnus.c | 753 ++++++++++++++++++++
6 files changed, 859 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
create mode 100644 drivers/pinctrl/pinctrl-bcm-cygnus.c

Ray Jui

unread,
Nov 27, 2014, 6:50:07 PM11/27/14
to
This enables the pinctrl driver for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..b4efff2 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select PINCTRL_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Nov 27, 2014, 8:30:07 PM11/27/14
to
This patchset contains the initial common clock support for Broadcom's iProc
family of SoCs. The iProc clock architecture comprises of various PLLs, e.g.,
ARMPLL, GENPLL, LCPLL0, MIPIPLL, and etc. An onboard crystal serves as the
basic reference clock for these PLLs. Each PLL may have several leaf clocks.
One special group of clocks is the ASIU clocks, which are dervied directly
from the crystal reference clock.

This patchset also contains the basic clock support for the Broadcom Cygnus
SoC, which implements the iProc clock architecture

Ray Jui (4):
clk: iproc: define Broadcom iProc clock binding
clk: iproc: add initial common clock support
clk: cygnus: add clock support for Broadcom Cygnus
ARM: dts: enable clock support for Broadcom Cygnus

arch/arm/boot/dts/bcm-cygnus-clock.dtsi | 110 +++++--
arch/arm/boot/dts/bcm-cygnus.dtsi | 2 +-
brcm,iproc-clocks.txt | 178 ++++++++++++
drivers/clk/Makefile | 2 +-
drivers/clk/bcm/Kconfig | 9 +
drivers/clk/bcm/Makefile | 2 +
drivers/clk/bcm/clk-cygnus.c | 277 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-armpll.c | 286 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-asiu.c | 275 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-clk.c | 238 +++++++++++++++
drivers/clk/bcm/clk-iproc-pll.c | 483 +++++++++++++++++++++++++++++++
drivers/clk/bcm/clk-iproc.h | 155 ++++++++++
include/dt-bindings/clock/bcm-cygnus.h | 77 +++++
13 files changed, 2067 insertions(+), 27 deletions(-)
create mode 100644 brcm,iproc-clocks.txt
create mode 100644 drivers/clk/bcm/clk-cygnus.c
create mode 100644 drivers/clk/bcm/clk-iproc-armpll.c
create mode 100644 drivers/clk/bcm/clk-iproc-asiu.c
create mode 100644 drivers/clk/bcm/clk-iproc-clk.c
create mode 100644 drivers/clk/bcm/clk-iproc-pll.c
create mode 100644 drivers/clk/bcm/clk-iproc.h
create mode 100644 include/dt-bindings/clock/bcm-cygnus.h

Ray Jui

unread,
Nov 27, 2014, 8:30:07 PM11/27/14
to
Replace current device tree dummy clocks with real clock support for
Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbr...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus-clock.dtsi | 110 ++++++++++++++++++++++++-------
arch/arm/boot/dts/bcm-cygnus.dtsi | 2 +-
2 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/arch/arm/boot/dts/bcm-cygnus-clock.dtsi b/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
index 60d8389..f2da5e2 100644
--- a/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
@@ -36,56 +36,116 @@ clocks {
ranges;

osc: oscillator {
+ #clock-cells = <0>;
compatible = "fixed-clock";
- #clock-cells = <1>;
clock-frequency = <25000000>;
};

- apb_clk: apb_clk {
- compatible = "fixed-clock";
+ /* Cygnus ARM PLL */
+ armpll: armpll {
#clock-cells = <0>;
- clock-frequency = <1000000000>;
+ compatible = "brcm,cygnus-armpll";
+ clocks = <&osc>;
+ reg = <0x19000000 0x1000>;
};

- periph_clk: periph_clk {
- compatible = "fixed-clock";
+ /* peripheral clock for system timer */
+ arm_periph_clk: arm_periph_clk {
#clock-cells = <0>;
- clock-frequency = <500000000>;
+ compatible = "fixed-factor-clock";
+ clocks = <&armpll>;
+ clock-div = <2>;
+ clock-mult = <1>;
};

- sdio_clk: lcpll_ch2 {
- compatible = "fixed-clock";
+ /* APB bus clock */
+ apb_clk: apb_clk {
#clock-cells = <0>;
- clock-frequency = <200000000>;
+ compatible = "fixed-factor-clock";
+ clocks = <&armpll>;
+ clock-div = <4>;
+ clock-mult = <1>;
};

- axi81_clk: axi81_clk {
- compatible = "fixed-clock";
+ genpll: genpll {
#clock-cells = <0>;
- clock-frequency = <100000000>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
};

- keypad_clk: keypad_clk {
- compatible = "fixed-clock";
+ /* various clocks running off the GENPLL */
+ genpll_clks: genpll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-genpll-clk";
+ reg = <0x0301d000 0x2c>;
+ clocks = <&genpll>;
+ clock-output-names = "axi21", "250mhz", "ihost_sys",
+ "enet_sw", "audio_125", "can";
+ };
+
+ /* always 1/2 of the axi21 clock */
+ axi41_clk: axi41_clk {
#clock-cells = <0>;
- clock-frequency = <31806>;
+ compatible = "fixed-factor-clock";
+ clocks = <&genpll_clks 0>;
+ clock-div = <2>;
+ clock-mult = <1>;
};

- adc_clk: adc_clk {
- compatible = "fixed-clock";
+ /* always 1/4 of the axi21 clock */
+ axi81_clk: axi81_clk {
#clock-cells = <0>;
- clock-frequency = <1562500>;
+ compatible = "fixed-factor-clock";
+ clocks = <&genpll_clks 0>;
+ clock-div = <4>;
+ clock-mult = <1>;
};

- pwm_clk: pwm_clk {
- compatible = "fixed-clock";
+ lcpll0: lcpll0 {
#clock-cells = <0>;
- clock-frequency = <1000000>;
+ compatible = "brcm,cygnus-lcpll0";
+ reg = <0x0301d02c 0x1c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
};

- lcd_clk: mipipll_ch1 {
- compatible = "fixed-clock";
+ /* various clocks running off the LCPLL0 */
+ lcpll0_clks: lcpll0_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-lcpll0-clk";
+ reg = <0x0301d02c 0x1c>;
+ clocks = <&lcpll0>;
+ clock-output-names = "pcie_phy", "ddr_phy", "sdio",
+ "usb_phy", "smart_card", "ch5";
+ };
+
+ mipipll: mipipll {
#clock-cells = <0>;
- clock-frequency = <100000000>;
+ compatible = "brcm,cygnus-mipipll";
+ reg = <0x180a9800 0x2c>,
+ <0x0301c020 0x4>,
+ <0x180aa024 0x4>;
+ clock-frequency = <1350000000>;
+ clocks = <&osc>;
+ };
+
+ mipipll_clks: mipipll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-mipipll-clk";
+ reg = <0x180a9800 0x2c>;
+ clocks = <&mipipll>;
+ clock-output-names = "ch0_unused", "ch1_lcd", "ch2_unused",
+ "ch3_unused", "ch4_unused", "ch5_unused";
+ };
+
+ asiu_clks: asiu_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-asiu-clk";
+ reg = <0x0301d048 0xc>,
+ <0x180aa024 0x4>;
+ clocks = <&osc>;
+ clock-output-names = "keypad", "adc/touch", "pwm";
};
};
diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..2b99e9c 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -134,7 +134,7 @@
compatible = "arm,cortex-a9-global-timer";
reg = <0x19020200 0x100>;
interrupts = <GIC_PPI 11 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&periph_clk>;
+ clocks = <&arm_periph_clk>;
};

};

Ray Jui

unread,
Nov 27, 2014, 8:30:06 PM11/27/14
to
Document the device tree binding for Broadcom iProc architecture based
clock controller

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
brcm,iproc-clocks.txt | 178 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 178 insertions(+)
create mode 100644 brcm,iproc-clocks.txt

diff --git a/brcm,iproc-clocks.txt b/brcm,iproc-clocks.txt
new file mode 100644
index 0000000..cc64fd2
--- /dev/null
+++ b/brcm,iproc-clocks.txt
@@ -0,0 +1,178 @@
+Broadcom iProc Family Clocks
+
+This binding uses the common clock binding:
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+The iProc clock controller manages clocks that are common to the iProc family.
+An SoC from the iProc family may have several PPLs, e.g., ARMPLL, GENPLL,
+LCPLL0, MIPIPLL, and etc., all derived from an onboard crystal. Each PLL
+comprises of several leaf clocks
+
+Required properties for PLLs:
+- compatible:
+ Should have a value of the form "brcm,<soc>-<pll>". For example, GENPLL on
+Cygnus has a compatible string of "brcm,cygnus-genpll"
+
+- #clock-cells:
+ Must be <0>
+
+- reg:
+ Define the base and range of the I/O address space that contain the iProc
+clock control registers required for the PLL
+
+- clocks:
+ The input parent clock phandle for the PLL. For all iProc PLLs, this is an
+onboard crystal with a fixed rate
+
+Optional Properties for PLLs:
+- clock-frequency:
+ PLL frequency in Hz. If specified, PLL will be configured to run at
+<clock-frequency> instead of the default frequency after chip reset, provided
+that <clock-frequency> and its parameters are defined in the SoC specific
+frequency parameter table
+
+Example:
+
+ osc: oscillator {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <25000000>;
+ };
+
+ genpll: genpll {
+ #clock-cells = <0>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
+ };
+
+Required properties for leaf clocks of a PLL:
+
+- compatible:
+ Should have a value of the form "brcm,<soc>-<pll>-clk". For example, leaf
+clocks derived from the GENPLL on Cygnus SoC have a compatible string of
+"brcm,cygnus-genpll-clk"
+
+- #clock-cells:
+ Have a value of <1> since there are more than 1 leaf clock of a
+given PLL
+
+- reg:
+ Define the base and range of the I/O address space that contain the iProc
+clock control registers required for the PLL leaf clocks
+
+- clocks:
+ The input parent PLL phandle for the leaf clock
+
+- clock-output-names:
+ An ordered list of strings defining the names of the leaf clocks
+
+Example:
+
+ genpll: genpll {
+ #clock-cells = <0>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
+ };
+
+ genpll_clks: genpll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-genpll-clk";
+ reg = <0x0301d000 0x2c>;
+ clocks = <&genpll>;
+ clock-output-names = "axi21", "250mhz", "ihost_sys",
+ "enet_sw", "audio_125", "can";
+ };
+
+Required properties for ASIU clocks:
+
+ASIU clocks are a special case. These clocks are derived directly from the
+reference clock of the onboard crystal
+
+- compatible:
+ Should have a value of the form "brcm,<soc>-asiu-clk". For example, ASIU
+clocks for Cygnus have a compatible string of "brcm,cygnus-asiu-clk"
+
+- #clock-cells:
+ Have a value of <1> since there are more than 1 ASIU clocks
+
+- reg:
+ Define the base and range of the I/O address space that contain the iProc
+clock control registers required for ASIU clocks
+
+- clocks:
+ The input parent clock phandle for the ASIU clock, i.e., the onboard
+crystal
+
+- clock-output-names:
+ An ordered list of strings defining the names of the ASIU clocks
+
+Example:
+
+ osc: oscillator {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <25000000>;
+ };
+
+ asiu_clks: asiu_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-asiu-clk";
+ reg = <0x0301d048 0xc>,
+ <0x180aa024 0x4>;
+ clocks = <&osc>;
+ clock-output-names = "keypad", "adc/touch", "pwm";
+ };
+
+Cygnus
+------
+PLL and leaf clock compatible strings for Cygnus are:
+ "brcm,cygnus-armpll"
+ "brcm,cygnus-genpll"
+ "brcm,cygnus-lcpll0"
+ "brcm,cygnus-mipipll"
+ "brcm,cygnus-genpll-clk"
+ "brcm,cygnus-lcpll0-clk"
+ "brcm,cygnus-mipipll-clk"
+ "brcm,cygnus-asiu-clk"
+
+The following table defines the set of PLL/clock index and ID for Cygnus.
+These clock IDs are defined in:
+ "include/dt-bindings/clock/bcm-cygnus.h"
+
+ Clock Source Index ID
+ --- ----- ----- ---------
+ crystal N/A N/A N/A
+
+ armpll crystal N/A N/A
+ genpll crystal N/A N/A
+ lcpll0 crystal N/A N/A
+ mipipll crystal N/A N/A
+
+ keypad crystal (ASIU) 0 BCM_CYGNUS_ASIU_KEYPAD_CLK
+ adc/tsc crystal (ASIU) 1 BCM_CYGNUS_ASIU_ADC_CLK
+ pwm crystal (ASIU) 2 BCM_CYGNUS_ASIU_PWM_CLK
+
+ axi21 genpll 0 BCM_CYGNUS_GENPLL_AXI21_CLK
+ 250mhz genpll 1 BCM_CYGNUS_GENPLL_250MHZ_CLK
+ ihost_sys genpll 2 BCM_CYGNUS_GENPLL_IHOST_SYS_CLK
+ enet_sw genpll 3 BCM_CYGNUS_GENPLL_ENET_SW_CLK
+ audio_125 genpll 4 BCM_CYGNUS_GENPLL_AUDIO_125_CLK
+ can genpll 5 BCM_CYGNUS_GENPLL_CAN_CLK
+
+ pcie_phy lcpll0 0 BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK
+ ddr_phy lcpll0 1 BCM_CYGNUS_LCPLL0_DDR_PHY_CLK
+ sdio lcpll0 2 BCM_CYGNUS_LCPLL0_SDIO_CLK
+ usb_phy lcpll0 3 BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK
+ smart_card lcpll0 4 BCM_CYGNUS_LCPLL0_SMART_CARD_CLK
+ ch5 lcpll0 5 BCM_CYGNUS_LCPLL0_CH5_UNUSED
+
+ ch0_unused mipipll 0 BCM_CYGNUS_MIPIPLL_CH0_UNUSED
+ ch1_lcd mipipll 1 BCM_CYGNUS_MIPIPLL_CH1_LCD
+ ch2_unused mipipll 2 BCM_CYGNUS_MIPIPLL_CH2_UNUSED
+ ch3_unused mipipll 3 BCM_CYGNUS_MIPIPLL_CH3_UNUSED
+ ch4_unused mipipll 4 BCM_CYGNUS_MIPIPLL_CH4_UNUSED
+ ch5_unused mipipll 5 BCM_CYGNUS_MIPIPLL_CH5_UNUSED

Ray Jui

unread,
Nov 27, 2014, 8:30:07 PM11/27/14
to
The Broadcom Cygnus SoC is architected under the iProc architecture. It
has the following PLLs: ARMPLL, GENPLL, LCPLL0, MIPIPLL, all dervied
from an onboard crystal. Cygnus also has various ASIU clocks that are
derived directly from the onboard crystal.

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/clk/bcm/Makefile | 1 +
drivers/clk/bcm/clk-cygnus.c | 277 ++++++++++++++++++++++++++++++++
include/dt-bindings/clock/bcm-cygnus.h | 77 +++++++++
3 files changed, 355 insertions(+)
create mode 100644 drivers/clk/bcm/clk-cygnus.c
create mode 100644 include/dt-bindings/clock/bcm-cygnus.h

diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index 6926636..afcbe55 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-clk.o clk-iproc-asiu.o
+obj-$(CONFIG_ARCH_BCM_CYGNUS) += clk-cygnus.o
diff --git a/drivers/clk/bcm/clk-cygnus.c b/drivers/clk/bcm/clk-cygnus.c
new file mode 100644
index 0000000..f603d1d
--- /dev/null
+++ b/drivers/clk/bcm/clk-cygnus.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#include <dt-bindings/clock/bcm-cygnus.h>
+#include "clk-iproc.h"
+
+#define reg_val(o, s, w) { .offset = o, .shift = s, .width = w, }
+
+#define aon_val(o, pw, ps, is) { .offset = o, .pwr_width = pw, \
+ .pwr_shift = ps, .iso_shift = is }
+
+#define asiu_div_val(o, es, hs, hw, ls, lw) \
+ { .offset = o, .en_shift = es, .high_shift = hs, \
+ .high_width = hw, .low_shift = ls, .low_width = lw }
+
+#define reset_val(o, rs, prs, kis, kiw, kps, kpw, kas, kaw) { .offset = o, \
+ .reset_shift = rs, .p_reset_shift = prs, .ki_shift = kis, \
+ .ki_width = kiw, .kp_shift = kps, .kp_width = kpw, .ka_shift = kas, \
+ .ka_width = kaw }
+
+#define vco_ctrl_val(uo, lo) { .u_offset = uo, .l_offset = lo }
+
+#define enable_val(o, es, hs, bs) { .offset = o, .enable_shift = es, \
+ .hold_shift = hs, .bypass_shift = bs }
+
+#define asiu_gate_val(o, es) { .offset = o, .en_shift = es }
+
+static const struct iproc_pll_ctrl genpll = {
+ .flags = IPROC_CLK_AON | IPROC_CLK_PLL_HAS_NDIV_FRAC,
+ .aon = aon_val(0x0, 2, 1, 0),
+ .reset = reset_val(0x0, 11, 10, 4, 3, 0, 4, 7, 3),
+ .ndiv_int = reg_val(0x10, 20, 10),
+ .ndiv_frac = reg_val(0x10, 0, 20),
+ .pdiv = reg_val(0x14, 0, 4),
+ .vco_ctrl = vco_ctrl_val(0x18, 0x1c),
+ .status = reg_val(0x28, 12, 1),
+};
+
+static const struct iproc_pll_ctrl lcpll0 = {
+ .flags = IPROC_CLK_AON,
+ .aon = aon_val(0x0, 2, 5, 4),
+ .reset = reset_val(0x0, 31, 30, 27, 3, 23, 4, 19, 4),
+ .ndiv_int = reg_val(0x4, 16, 10),
+ .pdiv = reg_val(0x4, 26, 4),
+ .vco_ctrl = vco_ctrl_val(0x10, 0x14),
+ .status = reg_val(0x18, 12, 1),
+};
+
+/*
+ * MIPI PLL VCO frequency parameter table
+ */
+static const struct iproc_pll_vco_freq_param mipipll_vco_params[] = {
+ /* rate (Hz) ndiv_int ndiv_frac pdiv */
+ { 750000000UL, 30, 0, 1 },
+ { 1000000000UL, 40, 0, 1 },
+ { 1350000000ul, 54, 0, 1 },
+ { 2000000000UL, 80, 0, 1 },
+ { 2100000000UL, 84, 0, 1 },
+ { 2250000000UL, 90, 0, 1 },
+ { 2500000000UL, 100, 0, 1 },
+ { 2700000000UL, 54, 0, 0 },
+ { 2975000000UL, 119, 0, 1 },
+ { 3100000000UL, 124, 0, 1 },
+ { 3150000000UL, 126, 0, 1 },
+};
+
+static const struct iproc_pll_ctrl mipipll = {
+ .flags = IPROC_CLK_PLL_ASIU | IPROC_CLK_PLL_HAS_NDIV_FRAC,
+ .aon = aon_val(0x0, 4, 17, 16),
+ .asiu = asiu_gate_val(0x0, 3),
+ .reset = reset_val(0x0, 11, 10, 4, 3, 0, 4, 7, 4),
+ .ndiv_int = reg_val(0x10, 20, 10),
+ .ndiv_frac = reg_val(0x10, 0, 20),
+ .pdiv = reg_val(0x14, 0, 4),
+ .vco_ctrl = vco_ctrl_val(0x18, 0x1c),
+ .status = reg_val(0x28, 12, 1),
+};
+
+static const struct iproc_clk_ctrl genpll_clk[BCM_CYGNUS_NUM_GENPLL_CLKS] = {
+ [BCM_CYGNUS_GENPLL_AXI21_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_AXI21_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 6, 0, 12),
+ .mdiv = reg_val(0x20, 0, 8),
+ },
+ [BCM_CYGNUS_GENPLL_250MHZ_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_250MHZ_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 7, 1, 13),
+ .mdiv = reg_val(0x20, 10, 8),
+ },
+ [BCM_CYGNUS_GENPLL_IHOST_SYS_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_IHOST_SYS_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 8, 2, 14),
+ .mdiv = reg_val(0x20, 20, 8),
+ },
+ [BCM_CYGNUS_GENPLL_ENET_SW_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_ENET_SW_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 9, 3, 15),
+ .mdiv = reg_val(0x24, 0, 8),
+ },
+ [BCM_CYGNUS_GENPLL_AUDIO_125_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_AUDIO_125_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 10, 4, 16),
+ .mdiv = reg_val(0x24, 10, 8),
+ },
+ [BCM_CYGNUS_GENPLL_CAN_CLK] = {
+ .channel = BCM_CYGNUS_GENPLL_CAN_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x4, 11, 5, 17),
+ .mdiv = reg_val(0x24, 20, 8),
+ },
+};
+
+static const struct iproc_clk_ctrl lcpll0_clk[BCM_CYGNUS_NUM_LCPLL0_CLKS] = {
+ [BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK] = {
+ .channel = BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 7, 1, 13),
+ .mdiv = reg_val(0x8, 0, 8),
+ },
+ [BCM_CYGNUS_LCPLL0_DDR_PHY_CLK] = {
+ .channel = BCM_CYGNUS_LCPLL0_DDR_PHY_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 8, 2, 14),
+ .mdiv = reg_val(0x8, 10, 8),
+ },
+ [BCM_CYGNUS_LCPLL0_SDIO_CLK] = {
+ .channel = BCM_CYGNUS_LCPLL0_SDIO_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 9, 3, 15),
+ .mdiv = reg_val(0x8, 20, 8),
+ },
+ [BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK] = {
+ .channel = BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 10, 4, 16),
+ .mdiv = reg_val(0xc, 0, 8),
+ },
+ [BCM_CYGNUS_LCPLL0_SMART_CARD_CLK] = {
+ .channel = BCM_CYGNUS_LCPLL0_SMART_CARD_CLK,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 11, 5, 17),
+ .mdiv = reg_val(0xc, 10, 8),
+ },
+ [BCM_CYGNUS_LCPLL0_CH5_UNUSED] = {
+ .channel = BCM_CYGNUS_LCPLL0_CH5_UNUSED,
+ .flags = IPROC_CLK_AON,
+ .enable = enable_val(0x0, 12, 6, 18),
+ .mdiv = reg_val(0xc, 20, 8),
+ },
+};
+
+static const struct iproc_clk_ctrl mipipll_clk[BCM_CYGNUS_NUM_MIPIPLL_CLKS] = {
+ [BCM_CYGNUS_MIPIPLL_CH0_UNUSED] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH0_UNUSED,
+ .enable = enable_val(0x4, 12, 6, 18),
+ .mdiv = reg_val(0x20, 0, 8),
+ },
+ [BCM_CYGNUS_MIPIPLL_CH1_LCD] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH1_LCD,
+ .enable = enable_val(0x4, 13, 7, 19),
+ .mdiv = reg_val(0x20, 10, 8),
+ },
+ [BCM_CYGNUS_MIPIPLL_CH2_UNUSED] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH2_UNUSED,
+ .enable = enable_val(0x4, 14, 8, 20),
+ .mdiv = reg_val(0x20, 20, 8),
+ },
+ [BCM_CYGNUS_MIPIPLL_CH3_UNUSED] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH3_UNUSED,
+ .enable = enable_val(0x4, 15, 9, 21),
+ .mdiv = reg_val(0x24, 0, 8),
+ },
+ [BCM_CYGNUS_MIPIPLL_CH4_UNUSED] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH4_UNUSED,
+ .enable = enable_val(0x4, 16, 10, 22),
+ .mdiv = reg_val(0x24, 10, 8),
+ },
+ [BCM_CYGNUS_MIPIPLL_CH5_UNUSED] = {
+ .channel = BCM_CYGNUS_MIPIPLL_CH5_UNUSED,
+ .enable = enable_val(0x4, 17, 11, 23),
+ .mdiv = reg_val(0x24, 20, 8),
+ },
+};
+
+static const struct iproc_asiu_div asiu_div[BCM_CYGNUS_NUM_ASIU_CLKS] = {
+ [BCM_CYGNUS_ASIU_KEYPAD_CLK] =
+ asiu_div_val(0x0, 31, 16, 10, 0, 10),
+ [BCM_CYGNUS_ASIU_ADC_CLK] =
+ asiu_div_val(0x4, 31, 16, 10, 0, 10),
+ [BCM_CYGNUS_ASIU_PWM_CLK] =
+ asiu_div_val(0x8, 31, 16, 10, 0, 10),
+};
+
+static const struct iproc_asiu_gate asiu_gate[BCM_CYGNUS_NUM_ASIU_CLKS] = {
+ [BCM_CYGNUS_ASIU_KEYPAD_CLK] =
+ asiu_gate_val(0x0, 7),
+ [BCM_CYGNUS_ASIU_ADC_CLK] =
+ asiu_gate_val(0x0, 9),
+ [BCM_CYGNUS_ASIU_PWM_CLK] =
+ asiu_gate_val(IPROC_CLK_INVALID_OFFSET, 0),
+};
+
+static void __init cygnus_armpll_init(struct device_node *node)
+{
+ iproc_armpll_setup(node);
+}
+CLK_OF_DECLARE(cygnus_armpll, "brcm,cygnus-armpll", cygnus_armpll_init);
+
+static void __init cygnus_genpll_init(struct device_node *node)
+{
+ iproc_pll_setup(node, &genpll, NULL, 0);
+}
+CLK_OF_DECLARE(cygnus_genpll, "brcm,cygnus-genpll", cygnus_genpll_init);
+
+static void __init cygnus_lcpll0_init(struct device_node *node)
+{
+ iproc_pll_setup(node, &lcpll0, NULL, 0);
+}
+CLK_OF_DECLARE(cygnus_lcpll0, "brcm,cygnus-lcpll0", cygnus_lcpll0_init);
+
+static void __init cygnus_mipipll_init(struct device_node *node)
+{
+ iproc_pll_setup(node, &mipipll, mipipll_vco_params,
+ ARRAY_SIZE(mipipll_vco_params));
+}
+CLK_OF_DECLARE(cygnus_mipipll, "brcm,cygnus-mipipll", cygnus_mipipll_init);
+
+static void __init cygnus_genpll_clk_init(struct device_node *node)
+{
+ iproc_clk_setup(node, genpll_clk, BCM_CYGNUS_NUM_GENPLL_CLKS);
+}
+CLK_OF_DECLARE(cygnus_genpll_clk, "brcm,cygnus-genpll-clk",
+ cygnus_genpll_clk_init);
+
+static void __init cygnus_lcpll0_clk_init(struct device_node *node)
+{
+ iproc_clk_setup(node, lcpll0_clk, BCM_CYGNUS_NUM_LCPLL0_CLKS);
+}
+CLK_OF_DECLARE(cygnus_lcpll0_clk, "brcm,cygnus-lcpll0-clk",
+ cygnus_lcpll0_clk_init);
+
+static void __init cygnus_mipipll_clk_init(struct device_node *node)
+{
+ iproc_clk_setup(node, mipipll_clk, BCM_CYGNUS_NUM_MIPIPLL_CLKS);
+}
+CLK_OF_DECLARE(cygnus_mipipll_clk, "brcm,cygnus-mipipll-clk",
+ cygnus_mipipll_clk_init);
+
+static void __init cygnus_asiu_init(struct device_node *node)
+{
+ iproc_asiu_setup(node, asiu_div, asiu_gate, BCM_CYGNUS_NUM_ASIU_CLKS);
+}
+CLK_OF_DECLARE(cygnus_asiu_clk, "brcm,cygnus-asiu-clk", cygnus_asiu_init);
diff --git a/include/dt-bindings/clock/bcm-cygnus.h b/include/dt-bindings/clock/bcm-cygnus.h
new file mode 100644
index 0000000..45948b6
--- /dev/null
+++ b/include/dt-bindings/clock/bcm-cygnus.h
@@ -0,0 +1,77 @@
+/*
+ * BSD LICENSE
+ *
+ * Copyright(c) 2014 Broadcom Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Broadcom Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER OR CONTRIBUTORS 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.
+ */
+
+#ifndef _CLOCK_BCM_CYGNUS_H
+#define _CLOCK_BCM_CYGNUS_H
+
+/* number of GENPLL clocks */
+#define BCM_CYGNUS_NUM_GENPLL_CLKS 6
+
+/* GENPLL clock channel ID */
+#define BCM_CYGNUS_GENPLL_AXI21_CLK 0
+#define BCM_CYGNUS_GENPLL_250MHZ_CLK 1
+#define BCM_CYGNUS_GENPLL_IHOST_SYS_CLK 2
+#define BCM_CYGNUS_GENPLL_ENET_SW_CLK 3
+#define BCM_CYGNUS_GENPLL_AUDIO_125_CLK 4
+#define BCM_CYGNUS_GENPLL_CAN_CLK 5
+
+/* number of LCPLL0 clocks */
+#define BCM_CYGNUS_NUM_LCPLL0_CLKS 6
+
+/* LCPLL0 clock channel ID */
+#define BCM_CYGNUS_LCPLL0_PCIE_PHY_REF_CLK 0
+#define BCM_CYGNUS_LCPLL0_DDR_PHY_CLK 1
+#define BCM_CYGNUS_LCPLL0_SDIO_CLK 2
+#define BCM_CYGNUS_LCPLL0_USB_PHY_REF_CLK 3
+#define BCM_CYGNUS_LCPLL0_SMART_CARD_CLK 4
+#define BCM_CYGNUS_LCPLL0_CH5_UNUSED 5
+
+/* number of MIPI PLL clocks */
+#define BCM_CYGNUS_NUM_MIPIPLL_CLKS 6
+
+/* MIPI PLL clock channel ID */
+#define BCM_CYGNUS_MIPIPLL_CH0_UNUSED 0
+#define BCM_CYGNUS_MIPIPLL_CH1_LCD 1
+#define BCM_CYGNUS_MIPIPLL_CH2_UNUSED 2
+#define BCM_CYGNUS_MIPIPLL_CH3_UNUSED 3
+#define BCM_CYGNUS_MIPIPLL_CH4_UNUSED 4
+#define BCM_CYGNUS_MIPIPLL_CH5_UNUSED 5
+
+/* number of ASIU clocks */
+#define BCM_CYGNUS_NUM_ASIU_CLKS 3
+
+/* ASIU clock IDs */
+#define BCM_CYGNUS_ASIU_KEYPAD_CLK 0
+#define BCM_CYGNUS_ASIU_ADC_CLK 1
+#define BCM_CYGNUS_ASIU_PWM_CLK 2
+
+#endif /* _CLOCK_BCM_CYGNUS_H */

Ray Jui

unread,
Nov 27, 2014, 8:30:08 PM11/27/14
to
This adds basic and generic support for various iProc PLLs and clocks
including the ARMPLL, GENPLL, LCPLL, MIPIPLL, and ASIU clocks.

SoCs under the iProc architecture can define their specific register
offsets and clock parameters for their PLL and clock controllers. These
parameters can be passed as arugments into the generic iProc PLL and
clock setup functions

Derived from code originally provided by Jonathan Richardson
<jona...@broadcom.com>

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/clk/Makefile | 2 +-
drivers/clk/bcm/Kconfig | 9 +
drivers/clk/bcm/Makefile | 1 +
drivers/clk/bcm/clk-iproc-armpll.c | 286 +++++++++++++++++++++
drivers/clk/bcm/clk-iproc-asiu.c | 275 ++++++++++++++++++++
drivers/clk/bcm/clk-iproc-clk.c | 238 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-pll.c | 483 ++++++++++++++++++++++++++++++++++++
drivers/clk/bcm/clk-iproc.h | 155 ++++++++++++
8 files changed, 1448 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/bcm/clk-iproc-armpll.c
create mode 100644 drivers/clk/bcm/clk-iproc-asiu.c
create mode 100644 drivers/clk/bcm/clk-iproc-clk.c
create mode 100644 drivers/clk/bcm/clk-iproc-pll.c
create mode 100644 drivers/clk/bcm/clk-iproc.h

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d5fba5b..eff0213 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -41,7 +41,7 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o
obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
-obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/
+obj-$(CONFIG_ARCH_BCM) += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/
obj-$(CONFIG_ARCH_HIP04) += hisilicon/
diff --git a/drivers/clk/bcm/Kconfig b/drivers/clk/bcm/Kconfig
index 75506e5..66b5b7f 100644
--- a/drivers/clk/bcm/Kconfig
+++ b/drivers/clk/bcm/Kconfig
@@ -7,3 +7,12 @@ config CLK_BCM_KONA
Enable common clock framework support for Broadcom SoCs
using "Kona" style clock control units, including those
in the BCM281xx and BCM21664 families.
+
+config COMMON_CLK_IPROC
+ bool "Broadcom iProc clock support"
+ depends on ARCH_BCM_IPROC
+ depends on COMMON_CLK
+ default y
+ help
+ Enable common clock framework support for Broadcom SoCs
+ based on the "iProc" architecture
diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index 6297d05..6926636 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
+obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-clk.o clk-iproc-asiu.o
diff --git a/drivers/clk/bcm/clk-iproc-armpll.c b/drivers/clk/bcm/clk-iproc-armpll.c
new file mode 100644
index 0000000..ec9b130
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-armpll.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+
+#define IPROC_CLK_MAX_FREQ_POLICY 0x3
+#define IPROC_CLK_POLICY_FREQ_OFFSET 0x008
+#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT 8
+#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK 0x7
+
+#define IPROC_CLK_PLLARMA_OFFSET 0xc00
+#define IPROC_CLK_PLLARMA_LOCK_SHIFT 28
+#define IPROC_CLK_PLLARMA_PDIV_SHIFT 24
+#define IPROC_CLK_PLLARMA_PDIV_MASK 0xf
+#define IPROC_CLK_PLLARMA_NDIV_INT_SHIFT 8
+#define IPROC_CLK_PLLARMA_NDIV_INT_MASK 0x3ff
+
+#define IPROC_CLK_PLLARMB_OFFSET 0xc04
+#define IPROC_CLK_PLLARMB_NDIV_FRAC_MASK 0xfffff
+
+#define IPROC_CLK_PLLARMC_OFFSET 0xc08
+#define IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT 8
+#define IPROC_CLK_PLLARMC_MDIV_MASK 0xff
+
+#define IPROC_CLK_PLLARMCTL5_OFFSET 0xc20
+#define IPROC_CLK_PLLARMCTL5_H_MDIV_MASK 0xff
+
+#define IPROC_CLK_PLLARM_OFFSET_OFFSET 0xc24
+#define IPROC_CLK_PLLARM_SW_CTL_SHIFT 29
+#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT 20
+#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK 0xff
+#define IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK 0xfffff
+
+#define IPROC_CLK_ARM_DIV_OFFSET 0xe00
+#define IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT 4
+#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK 0xf
+
+#define IPROC_CLK_POLICY_DBG_OFFSET 0xec0
+#define IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT 12
+#define IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK 0x7
+
+enum iproc_arm_pll_fid {
+ ARM_PLL_FID_CRYSTAL_CLK = 0,
+ ARM_PLL_FID_SYS_CLK = 2,
+ ARM_PLL_FID_CH0_SLOW_CLK = 6,
+ ARM_PLL_FID_CH1_FAST_CLK = 7
+};
+
+struct iproc_arm_pll {
+ struct clk_hw hw;
+ void __iomem *base;
+ struct clk_onecell_data clk_data;
+ unsigned long rate;
+};
+
+#define to_iproc_arm_pll(hw) container_of(hw, struct iproc_arm_pll, hw)
+
+static unsigned int __get_fid(struct iproc_arm_pll *pll)
+{
+ u32 val;
+ unsigned int policy, fid, active_fid;
+
+ val = readl(pll->base + IPROC_CLK_ARM_DIV_OFFSET);
+ if (val & (1 << IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT))
+ policy = val & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK;
+ else
+ policy = 0;
+
+ /* something is seriously wrong */
+ BUG_ON(policy > IPROC_CLK_MAX_FREQ_POLICY);
+
+ val = readl(pll->base + IPROC_CLK_POLICY_FREQ_OFFSET);
+ fid = (val >> (IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT * policy)) &
+ IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK;
+
+ val = readl(pll->base + IPROC_CLK_POLICY_DBG_OFFSET);
+ active_fid = IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK &
+ (val >> IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT);
+ if (fid != active_fid) {
+ pr_debug("%s: fid override %u->%u\n", __func__, fid,
+ active_fid);
+ fid = active_fid;
+ }
+
+ pr_debug("%s: active fid: %u\n", __func__, fid);
+
+ return fid;
+}
+
+/*
+ * Determine the mdiv (post divider) based on the frequency ID being used.
+ * There are 4 sources that can be used to derive the output clock rate:
+ * - 25 MHz Crystal
+ * - System clock
+ * - PLL channel 0 (slow clock)
+ * - PLL channel 1 (fast clock)
+ */
+static int __get_mdiv(struct iproc_arm_pll *pll)
+{
+ unsigned int fid;
+ int mdiv;
+ u32 val;
+
+ fid = __get_fid(pll);
+
+ switch (fid) {
+ case ARM_PLL_FID_CRYSTAL_CLK:
+ case ARM_PLL_FID_SYS_CLK:
+ mdiv = 1;
+ break;
+
+ case ARM_PLL_FID_CH0_SLOW_CLK:
+ val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
+ mdiv = val & IPROC_CLK_PLLARMC_MDIV_MASK;
+ if (mdiv == 0)
+ mdiv = 256;
+ break;
+
+ case ARM_PLL_FID_CH1_FAST_CLK:
+ val = readl(pll->base + IPROC_CLK_PLLARMCTL5_OFFSET);
+ mdiv = val & IPROC_CLK_PLLARMCTL5_H_MDIV_MASK;
+ if (mdiv == 0)
+ mdiv = 256;
+ break;
+
+ default:
+ mdiv = -EFAULT;
+ }
+
+ return mdiv;
+}
+
+static unsigned int __get_ndiv(struct iproc_arm_pll *pll)
+{
+ u32 val;
+ unsigned int ndiv_int, ndiv_frac, ndiv;
+
+ val = readl(pll->base + IPROC_CLK_PLLARM_OFFSET_OFFSET);
+ if (val & (1 << IPROC_CLK_PLLARM_SW_CTL_SHIFT)) {
+ /*
+ * offset mode is active. Read the ndiv from the PLLARM OFFSET
+ * register
+ */
+ ndiv_int = (val >> IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT) &
+ IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK;
+ if (ndiv_int == 0)
+ ndiv_int = 256;
+
+ ndiv_frac = val & IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK;
+ } else {
+ /* offset mode not active */
+ val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
+ ndiv_int = (val >> IPROC_CLK_PLLARMA_NDIV_INT_SHIFT) &
+ IPROC_CLK_PLLARMA_NDIV_INT_MASK;
+ if (ndiv_int == 0)
+ ndiv_int = 1024;
+
+ val = readl(pll->base + IPROC_CLK_PLLARMB_OFFSET);
+ ndiv_frac = val & IPROC_CLK_PLLARMB_NDIV_FRAC_MASK;
+ }
+
+ ndiv = (ndiv_int << 20) | ndiv_frac;
+
+ return ndiv;
+}
+
+/*
+ * The output frequency of the ARM PLL is calculated based on the ARM PLL
+ * divider values:
+ * pdiv = ARM PLL pre-divider
+ * ndiv = ARM PLL multiplier
+ * mdiv = ARM PLL post divider
+ *
+ * The frequency is calculated by:
+ * ((ndiv * parent clock rate) / pdiv) / mdiv
+ */
+static unsigned long iproc_arm_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct iproc_arm_pll *pll = to_iproc_arm_pll(hw);
+ u32 val;
+ int mdiv;
+ u64 ndiv;
+ unsigned int pdiv;
+
+ /* in bypass mode, use parent rate */
+ val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
+ if (val & (1 << IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT)) {
+ pll->rate = parent_rate;
+ return pll->rate;
+ }
+
+ /* PLL needs to be locked */
+ val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
+ if (!(val & (1 << IPROC_CLK_PLLARMA_LOCK_SHIFT))) {
+ pll->rate = 0;
+ return 0;
+ }
+
+ pdiv = (val >> IPROC_CLK_PLLARMA_PDIV_SHIFT) &
+ IPROC_CLK_PLLARMA_PDIV_MASK;
+ if (pdiv == 0)
+ pdiv = 16;
+
+ ndiv = __get_ndiv(pll);
+ mdiv = __get_mdiv(pll);
+ if (mdiv <= 0) {
+ pll->rate = 0;
+ return 0;
+ }
+ pll->rate = (ndiv * parent_rate) >> 20;
+ pll->rate = (pll->rate / pdiv) / mdiv;
+
+ pr_debug("%s: ARM PLL rate: %lu. parent rate: %lu\n", __func__,
+ pll->rate, parent_rate);
+ pr_debug("%s: ndiv_int: %u, pdiv: %u, mdiv: %d\n", __func__,
+ (unsigned int)(ndiv >> 20), pdiv, mdiv);
+
+ return pll->rate;
+}
+
+static const struct clk_ops iproc_arm_pll_ops = {
+ .recalc_rate = iproc_arm_pll_recalc_rate,
+};
+
+void __init iproc_armpll_setup(struct device_node *node)
+{
+ int ret;
+ struct clk *clk;
+ struct iproc_arm_pll *pll;
+ struct clk_init_data init;
+ const char *parent_name;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (WARN_ON(!pll))
+ return;
+
+ pll->base = of_iomap(node, 0);
+ if (WARN_ON(!pll->base))
+ goto err_free_pll;
+
+ init.name = node->name;
+ init.ops = &iproc_arm_pll_ops;
+ init.flags = 0;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ pll->hw.init = &init;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_iounmap;
+
+ pll->clk_data.clk_num = 1;
+ pll->clk_data.clks = &clk;
+
+ ret = of_clk_add_provider(node, of_clk_src_onecell_get, &pll->clk_data);
+ if (WARN_ON(ret))
+ goto err_clk_unregister;
+
+ return;
+
+err_clk_unregister:
+ clk_unregister(clk);
+err_iounmap:
+ iounmap(pll->base);
+err_free_pll:
+ kfree(pll);
+}
diff --git a/drivers/clk/bcm/clk-iproc-asiu.c b/drivers/clk/bcm/clk-iproc-asiu.c
new file mode 100644
index 0000000..ab86b8c
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-asiu.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#include "clk-iproc.h"
+
+struct iproc_asiu;
+
+struct iproc_asiu_clk {
+ struct clk_hw hw;
+ const char *name;
+ struct iproc_asiu *asiu;
+ unsigned long rate;
+ struct iproc_asiu_div div;
+ struct iproc_asiu_gate gate;
+};
+
+struct iproc_asiu {
+ void __iomem *div_base;
+ void __iomem *gate_base;
+
+ struct clk_onecell_data clk_data;
+ struct iproc_asiu_clk *clks;
+};
+
+#define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw)
+
+static int iproc_asiu_clk_enable(struct clk_hw *hw)
+{
+ struct iproc_asiu_clk *clk = to_asiu_clk(hw);
+ struct iproc_asiu *asiu = clk->asiu;
+ u32 val;
+
+ /* some clocks at the ASIU level are always enabled */
+ if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
+ return 0;
+
+ val = readl(asiu->gate_base + clk->gate.offset);
+ val |= (1 << clk->gate.en_shift);
+ writel(val, asiu->gate_base + clk->gate.offset);
+
+ return 0;
+}
+
+static void iproc_asiu_clk_disable(struct clk_hw *hw)
+{
+ struct iproc_asiu_clk *clk = to_asiu_clk(hw);
+ struct iproc_asiu *asiu = clk->asiu;
+ u32 val;
+
+ /* some clocks at the ASIU level are always enabled */
+ if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
+ return;
+
+ val = readl(asiu->gate_base + clk->gate.offset);
+ val &= ~(1 << clk->gate.en_shift);
+ writel(val, asiu->gate_base + clk->gate.offset);
+}
+
+static unsigned long iproc_asiu_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct iproc_asiu_clk *clk = to_asiu_clk(hw);
+ struct iproc_asiu *asiu = clk->asiu;
+ u32 val;
+ unsigned int div_h, div_l;
+
+ if (parent_rate == 0) {
+ clk->rate = 0;
+ return 0;
+ }
+
+ /* if clock divisor is not enabled, simply return parent rate */
+ val = readl(asiu->div_base + clk->div.offset);
+ if ((val & (1 << clk->div.en_shift)) == 0) {
+ clk->rate = parent_rate;
+ return parent_rate;
+ }
+
+ /* clock rate = parent rate / (high_div + 1) + (low_div + 1) */
+ div_h = (val >> clk->div.high_shift) & bit_mask(clk->div.high_width);
+ div_h++;
+ div_l = (val >> clk->div.low_shift) & bit_mask(clk->div.low_width);
+ div_l++;
+
+ clk->rate = parent_rate / (div_h + div_l);
+ pr_debug("%s: rate: %lu. parent rate: %lu div_h: %u div_l: %u\n",
+ __func__, clk->rate, parent_rate, div_h, div_l);
+
+ return clk->rate;
+}
+
+static long iproc_asiu_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned int div;
+
+ if (rate == 0 || *parent_rate == 0)
+ return -EINVAL;
+
+ if (rate == *parent_rate)
+ return *parent_rate;
+
+ div = DIV_ROUND_UP(*parent_rate, rate);
+ if (div < 2)
+ return *parent_rate;
+
+ return *parent_rate / div;
+}
+
+static int iproc_asiu_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct iproc_asiu_clk *clk = to_asiu_clk(hw);
+ struct iproc_asiu *asiu = clk->asiu;
+ unsigned int div, div_h, div_l;
+ u32 val;
+
+ if (rate == 0 || parent_rate == 0)
+ return -EINVAL;
+
+ /* simply disable the divisor if one wants the same rate as parent */
+ if (rate == parent_rate) {
+ val = readl(asiu->div_base + clk->div.offset);
+ val &= ~(1 << clk->div.en_shift);
+ writel(val, asiu->div_base + clk->div.offset);
+ return 0;
+ }
+
+ div = DIV_ROUND_UP(parent_rate, rate);
+ if (div < 2)
+ return -EINVAL;
+
+ div_h = div_l = div >> 1;
+ div_h--;
+ div_l--;
+
+ val = readl(asiu->div_base + clk->div.offset);
+ val |= 1 << clk->div.en_shift;
+ if (div_h) {
+ val &= ~(bit_mask(clk->div.high_width)
+ << clk->div.high_shift);
+ val |= div_h << clk->div.high_shift;
+ } else {
+ val &= ~(bit_mask(clk->div.high_width)
+ << clk->div.high_shift);
+ }
+ if (div_l) {
+ val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
+ val |= div_l << clk->div.low_shift;
+ } else {
+ val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
+ }
+ writel(val, asiu->div_base + clk->div.offset);
+
+ return 0;
+}
+
+static const struct clk_ops iproc_asiu_ops = {
+ .enable = iproc_asiu_clk_enable,
+ .disable = iproc_asiu_clk_disable,
+ .recalc_rate = iproc_asiu_clk_recalc_rate,
+ .round_rate = iproc_asiu_clk_round_rate,
+ .set_rate = iproc_asiu_clk_set_rate,
+};
+
+void __init iproc_asiu_setup(struct device_node *node,
+ const struct iproc_asiu_div *div,
+ const struct iproc_asiu_gate *gate, unsigned int num_clks)
+{
+ int i, ret;
+ struct iproc_asiu *asiu;
+
+ if (WARN_ON(!gate || !div))
+ return;
+
+ asiu = kzalloc(sizeof(*asiu), GFP_KERNEL);
+ if (WARN_ON(!asiu))
+ return;
+
+ asiu->clk_data.clk_num = num_clks;
+ asiu->clk_data.clks = kcalloc(num_clks, sizeof(*asiu->clk_data.clks),
+ GFP_KERNEL);
+ if (WARN_ON(!asiu->clk_data.clks))
+ goto err_clks;
+
+ asiu->clks = kcalloc(num_clks, sizeof(*asiu->clks), GFP_KERNEL);
+ if (WARN_ON(!asiu->clks))
+ goto err_asiu_clks;
+
+ asiu->div_base = of_iomap(node, 0);
+ if (WARN_ON(!asiu->div_base))
+ goto err_iomap_div;
+
+ asiu->gate_base = of_iomap(node, 1);
+ if (WARN_ON(!asiu->gate_base))
+ goto err_iomap_gate;
+
+ for (i = 0; i < num_clks; i++) {
+ struct clk_init_data init;
+ struct clk *clk;
+ const char *parent_name;
+ struct iproc_asiu_clk *asiu_clk;
+ const char *clk_name;
+
+ clk_name = kzalloc(IPROC_CLK_NAME_LEN, GFP_KERNEL);
+ if (WARN_ON(!clk_name))
+ goto err_clk_register;
+
+ ret = of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name);
+ if (WARN_ON(ret))
+ goto err_clk_register;
+
+ asiu_clk = &asiu->clks[i];
+ asiu_clk->name = clk_name;
+ asiu_clk->asiu = asiu;
+ asiu_clk->div = div[i];
+ asiu_clk->gate = gate[i];
+ init.name = clk_name;
+ init.ops = &iproc_asiu_ops;
+ init.flags = 0;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ asiu_clk->hw.init = &init;
+
+ clk = clk_register(NULL, &asiu_clk->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_clk_register;
+ asiu->clk_data.clks[i] = clk;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_onecell_get,
+ &asiu->clk_data);
+ if (WARN_ON(ret))
+ goto err_clk_register;
+
+ return;
+
+err_clk_register:
+ for (i = 0; i < num_clks; i++)
+ kfree(asiu->clks[i].name);
+ iounmap(asiu->gate_base);
+
+err_iomap_gate:
+ iounmap(asiu->div_base);
+
+err_iomap_div:
+ kfree(asiu->clks);
+
+err_asiu_clks:
+ kfree(asiu->clk_data.clks);
+
+err_clks:
+ kfree(asiu);
+}
diff --git a/drivers/clk/bcm/clk-iproc-clk.c b/drivers/clk/bcm/clk-iproc-clk.c
new file mode 100644
index 0000000..be3c42c
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-clk.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#include "clk-iproc.h"
+
+struct iproc_pll;
+
+struct iproc_clk {
+ struct clk_hw hw;
+ const char *name;
+ struct iproc_pll *pll;
+ unsigned long rate;
+ const struct iproc_clk_ctrl *ctrl;
+};
+
+struct iproc_pll {
+ void __iomem *base;
+ struct clk_onecell_data clk_data;
+ struct iproc_clk *clks;
+};
+
+#define to_iproc_clk(hw) container_of(hw, struct iproc_clk, hw)
+
+static int iproc_clk_enable(struct clk_hw *hw)
+{
+ struct iproc_clk *clk = to_iproc_clk(hw);
+ const struct iproc_clk_ctrl *ctrl = clk->ctrl;
+ struct iproc_pll *pll = clk->pll;
+ u32 val;
+
+ /* channel enable is active low */
+ val = readl(pll->base + ctrl->enable.offset);
+ val &= ~(1 << ctrl->enable.enable_shift);
+ writel(val, pll->base + ctrl->enable.offset);
+
+ /* also make sure channel is not held */
+ val = readl(pll->base + ctrl->enable.offset);
+ val &= ~(1 << ctrl->enable.hold_shift);
+ writel(val, pll->base + ctrl->enable.offset);
+
+ return 0;
+}
+
+static void iproc_clk_disable(struct clk_hw *hw)
+{
+ struct iproc_clk *clk = to_iproc_clk(hw);
+ const struct iproc_clk_ctrl *ctrl = clk->ctrl;
+ struct iproc_pll *pll = clk->pll;
+ u32 val;
+
+ if (ctrl->flags & IPROC_CLK_AON)
+ return;
+
+ val = readl(pll->base + ctrl->enable.offset);
+ val |= 1 << ctrl->enable.enable_shift;
+ writel(val, pll->base + ctrl->enable.offset);
+}
+
+static unsigned long iproc_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct iproc_clk *clk = to_iproc_clk(hw);
+ const struct iproc_clk_ctrl *ctrl = clk->ctrl;
+ struct iproc_pll *pll = clk->pll;
+ u32 val;
+ unsigned int mdiv;
+
+ if (parent_rate == 0)
+ return 0;
+
+ val = readl(pll->base + ctrl->mdiv.offset);
+ mdiv = (val >> ctrl->mdiv.shift) & bit_mask(ctrl->mdiv.width);
+ if (mdiv == 0)
+ mdiv = 256;
+
+ clk->rate = parent_rate / mdiv;
+
+ return clk->rate;
+}
+
+static long iproc_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned int div;
+
+ if (rate == 0 || *parent_rate == 0)
+ return -EINVAL;
+
+ if (rate == *parent_rate)
+ return *parent_rate;
+
+ div = DIV_ROUND_UP(*parent_rate, rate);
+ if (div < 2)
+ return *parent_rate;
+
+ if (div > 256)
+ div = 256;
+
+ return *parent_rate / div;
+}
+
+static int iproc_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct iproc_clk *clk = to_iproc_clk(hw);
+ const struct iproc_clk_ctrl *ctrl = clk->ctrl;
+ struct iproc_pll *pll = clk->pll;
+ u32 val;
+ unsigned int div;
+
+ if (rate == 0 || parent_rate == 0)
+ return -EINVAL;
+
+ div = DIV_ROUND_UP(parent_rate, rate);
+ if (div > 256)
+ return -EINVAL;
+
+ val = readl(pll->base + ctrl->mdiv.offset);
+ if (div == 256) {
+ val &= ~(bit_mask(ctrl->mdiv.width) << ctrl->mdiv.shift);
+ } else {
+ val &= ~(bit_mask(ctrl->mdiv.width) << ctrl->mdiv.shift);
+ val |= div << ctrl->mdiv.shift;
+ }
+ writel(val, pll->base + ctrl->mdiv.offset);
+ clk->rate = parent_rate / div;
+
+ return 0;
+}
+
+static const struct clk_ops iproc_clk_ops = {
+ .enable = iproc_clk_enable,
+ .disable = iproc_clk_disable,
+ .recalc_rate = iproc_clk_recalc_rate,
+ .round_rate = iproc_clk_round_rate,
+ .set_rate = iproc_clk_set_rate,
+};
+
+void __init iproc_clk_setup(struct device_node *node,
+ const struct iproc_clk_ctrl *ctrl, unsigned int num_clks)
+{
+ int i, ret;
+ struct iproc_pll *pll;
+
+ if (WARN_ON(!ctrl))
+ return;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (WARN_ON(!pll))
+ return;
+
+ pll->clk_data.clk_num = num_clks;
+ pll->clk_data.clks = kcalloc(num_clks, sizeof(*pll->clk_data.clks),
+ GFP_KERNEL);
+ if (WARN_ON(!pll->clk_data.clks))
+ goto err_clks;
+
+ pll->clks = kcalloc(num_clks, sizeof(*pll->clks), GFP_KERNEL);
+ if (WARN_ON(!pll->clks))
+ goto err_pll_clks;
+
+ pll->base = of_iomap(node, 0);
+ if (WARN_ON(!pll->base))
+ goto err_iomap;
+
+ for (i = 0; i < num_clks; i++) {
+ struct clk_init_data init;
+ struct clk *clk;
+ const char *parent_name;
+ struct iproc_clk *iclk;
+ const char *clk_name;
+
+ clk_name = kzalloc(IPROC_CLK_NAME_LEN, GFP_KERNEL);
+ if (WARN_ON(!clk_name))
+ goto err_clk_register;
+
+ ret = of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name);
+ if (WARN_ON(ret))
+ goto err_clk_register;
+
+ iclk = &pll->clks[i];
+ iclk->name = clk_name;
+ iclk->pll = pll;
+ iclk->ctrl = &ctrl[i];
+ init.name = clk_name;
+ init.ops = &iproc_clk_ops;
+ init.flags = 0;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ iclk->hw.init = &init;
+
+ clk = clk_register(NULL, &iclk->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_clk_register;
+ pll->clk_data.clks[i] = clk;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_onecell_get, &pll->clk_data);
+ if (WARN_ON(ret))
+ goto err_clk_register;
+
+ return;
+
+err_clk_register:
+ for (i = 0; i < num_clks; i++)
+ kfree(pll->clks[i].name);
+ iounmap(pll->base);
+
+err_iomap:
+ kfree(pll->clks);
+
+err_pll_clks:
+ kfree(pll->clk_data.clks);
+
+err_clks:
+ kfree(pll);
+}
diff --git a/drivers/clk/bcm/clk-iproc-pll.c b/drivers/clk/bcm/clk-iproc-pll.c
new file mode 100644
index 0000000..cd3bd38
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-pll.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#include "clk-iproc.h"
+
+#define PLL_VCO_HIGH_SHIFT 19
+#define PLL_VCO_LOW_SHIFT 30
+
+/* number of delay loops waiting for PLL to lock */
+#define LOCK_DELAY 100
+
+/* number of VCO frequency bands */
+#define NUM_FREQ_BANDS 8
+
+#define NUM_KP_BANDS 3
+enum kp_band {
+ KP_BAND_MID = 0,
+ KP_BAND_HIGH,
+ KP_BAND_HIGH_HIGH
+};
+
+static const unsigned int kp_table[NUM_KP_BANDS][NUM_FREQ_BANDS] = {
+ { 5, 6, 6, 7, 7, 8, 9, 10 },
+ { 4, 4, 5, 5, 6, 7, 8, 9 },
+ { 4, 5, 5, 6, 7, 8, 9, 10 },
+};
+
+static const unsigned long ref_freq_table[NUM_FREQ_BANDS][2] = {
+ { 10000000, 12500000 },
+ { 12500000, 15000000 },
+ { 15000000, 20000000 },
+ { 20000000, 25000000 },
+ { 25000000, 50000000 },
+ { 50000000, 75000000 },
+ { 75000000, 100000000 },
+ { 100000000, 125000000 },
+};
+
+enum vco_freq_range {
+ VCO_LOW = 700000000U,
+ VCO_MID = 1200000000U,
+ VCO_HIGH = 2200000000U,
+ VCO_HIGH_HIGH = 3100000000U,
+ VCO_MAX = 4000000000U,
+};
+
+struct iproc_pll {
+ struct clk_hw hw;
+ void __iomem *pll_base;
+ void __iomem *pwr_base;
+ void __iomem *asiu_base;
+ struct clk_onecell_data clk_data;
+ const char *name;
+ const struct iproc_pll_ctrl *ctrl;
+ const struct iproc_pll_vco_freq_param *vco_param;
+ unsigned int num_vco_entries;
+ unsigned long rate;
+};
+
+#define to_iproc_pll(hw) container_of(hw, struct iproc_pll, hw)
+
+/*
+ * Get the clock rate based on name
+ */
+static unsigned long __get_rate(const char *clk_name)
+{
+ struct clk *clk;
+
+ clk = __clk_lookup(clk_name);
+ if (!clk) {
+ pr_err("%s: unable to find clock by name: %s\n", __func__,
+ clk_name);
+ return 0;
+ }
+
+ return clk_get_rate(clk);
+}
+
+/*
+ * Based on the target frequency, find a match from the VCO frequency parameter
+ * table and return its index
+ */
+static int pll_get_rate_index(struct iproc_pll *pll, unsigned int target_rate)
+{
+ int i;
+
+ for (i = 0; i < pll->num_vco_entries; i++)
+ if (target_rate == pll->vco_param[i].rate)
+ break;
+
+ if (i >= pll->num_vco_entries)
+ return -EINVAL;
+
+ return i;
+}
+
+static int get_kp(unsigned long ref_freq, enum kp_band kp_index)
+{
+ int i;
+
+ if (ref_freq < ref_freq_table[0][0])
+ return -EINVAL;
+
+ for (i = 0; i < NUM_FREQ_BANDS; i++) {
+ if (ref_freq >= ref_freq_table[i][0] &&
+ ref_freq < ref_freq_table[i][1])
+ return kp_table[kp_index][i];
+ }
+ return -EINVAL;
+}
+
+static int pll_wait_for_lock(struct iproc_pll *pll)
+{
+ int i;
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+
+ for (i = 0; i < LOCK_DELAY; i++) {
+ u32 val = readl(pll->pll_base + ctrl->status.offset);
+
+ if (val & (1 << ctrl->status.shift))
+ return 0;
+ udelay(10);
+ }
+
+ return -EIO;
+}
+
+static void __pll_disable(struct iproc_pll *pll)
+{
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ u32 val;
+
+ if (ctrl->flags & IPROC_CLK_PLL_ASIU) {
+ val = readl(pll->asiu_base + ctrl->asiu.offset);
+ val &= ~(1 << ctrl->asiu.en_shift);
+ writel(val, pll->asiu_base + ctrl->asiu.offset);
+ }
+
+ /* latch input value so core power can be shut down */
+ val = readl(pll->pwr_base + ctrl->aon.offset);
+ val |= (1 << ctrl->aon.iso_shift);
+ writel(val, pll->pwr_base + ctrl->aon.offset);
+
+ /* power down the core */
+ val &= ~(bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift);
+ writel(val, pll->pwr_base + ctrl->aon.offset);
+}
+
+static int __pll_enable(struct iproc_pll *pll)
+{
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ u32 val;
+
+ /* power up the PLL and make sure it's not latched */
+ val = readl(pll->pwr_base + ctrl->aon.offset);
+ val |= bit_mask(ctrl->aon.pwr_width) << ctrl->aon.pwr_shift;
+ val &= ~(1 << ctrl->aon.iso_shift);
+ writel(val, pll->pwr_base + ctrl->aon.offset);
+
+ /* certain PLLs also need to be ungated from the ASIU top level */
+ if (ctrl->flags & IPROC_CLK_PLL_ASIU) {
+ val = readl(pll->asiu_base + ctrl->asiu.offset);
+ val |= (1 << ctrl->asiu.en_shift);
+ writel(val, pll->asiu_base + ctrl->asiu.offset);
+ }
+
+ return 0;
+}
+
+static void __pll_put_in_reset(struct iproc_pll *pll)
+{
+ u32 val;
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ const struct iproc_pll_reset_ctrl *reset = &ctrl->reset;
+
+ val = readl(pll->pll_base + reset->offset);
+ val &= ~(1 << reset->reset_shift | 1 << reset->p_reset_shift);
+ writel(val, pll->pll_base + reset->offset);
+}
+
+static void __pll_bring_out_reset(struct iproc_pll *pll, unsigned int kp,
+ unsigned int ka, unsigned int ki)
+{
+ u32 val;
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ const struct iproc_pll_reset_ctrl *reset = &ctrl->reset;
+
+ val = readl(pll->pll_base + reset->offset);
+ val &= ~(bit_mask(reset->ki_width) << reset->ki_shift |
+ bit_mask(reset->kp_width) << reset->kp_shift |
+ bit_mask(reset->ka_width) << reset->ka_shift);
+ val |= ki << reset->ki_shift | kp << reset->kp_shift |
+ ka << reset->ka_shift;
+ val |= 1 << reset->reset_shift | 1 << reset->p_reset_shift;
+ writel(val, pll->pll_base + reset->offset);
+}
+
+static int pll_set_rate(struct iproc_pll *pll, unsigned int rate_index,
+ unsigned long parent_rate)
+{
+ const struct iproc_pll_vco_freq_param *vco =
+ &pll->vco_param[rate_index];
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ int ka = 0, ki, kp, ret;
+ unsigned long rate = vco->rate;
+ u32 val;
+ enum kp_band kp_index;
+ unsigned long ref_freq;
+
+ /*
+ * reference frequency = parent frequency / PDIV
+ * If PDIV = 0, then it becomes a multiplier (x2)
+ */
+ if (vco->pdiv == 0)
+ ref_freq = parent_rate * 2;
+ else
+ ref_freq = parent_rate / vco->pdiv;
+
+ /* determine Ki and Kp index based on target VCO frequency */
+ if (rate >= VCO_LOW && rate < VCO_HIGH) {
+ ki = 4;
+ kp_index = KP_BAND_MID;
+ } else if (rate >= VCO_HIGH && rate && rate < VCO_HIGH_HIGH) {
+ ki = 3;
+ kp_index = KP_BAND_HIGH;
+ } else if (rate >= VCO_HIGH_HIGH && rate < VCO_MAX) {
+ ki = 3;
+ kp_index = KP_BAND_HIGH_HIGH;
+ } else {
+ pr_err("%s: pll: %s has invalid rate: %lu\n", __func__,
+ pll->name, rate);
+ return -EINVAL;
+ }
+
+ kp = get_kp(ref_freq, kp_index);
+ if (kp < 0) {
+ pr_err("%s: pll: %s has invalid kp\n", __func__, pll->name);
+ return kp;
+ }
+
+ ret = __pll_enable(pll);
+ if (ret) {
+ pr_err("%s: pll: %s fails to enable\n", __func__, pll->name);
+ return ret;
+ }
+
+ /* put PLL in reset */
+ __pll_put_in_reset(pll);
+
+ writel(0, pll->pll_base + ctrl->vco_ctrl.u_offset);
+ val = readl(pll->pll_base + ctrl->vco_ctrl.l_offset);
+
+ if (rate >= VCO_LOW && rate < VCO_MID)
+ val |= (1 << PLL_VCO_LOW_SHIFT);
+
+ if (rate < VCO_HIGH)
+ val &= ~(1 << PLL_VCO_HIGH_SHIFT);
+ else
+ val |= (1 << PLL_VCO_HIGH_SHIFT);
+
+ writel(val, pll->pll_base + ctrl->vco_ctrl.l_offset);
+
+ /* program integer part of NDIV */
+ val = readl(pll->pll_base + ctrl->ndiv_int.offset);
+ val &= ~(bit_mask(ctrl->ndiv_int.width) << ctrl->ndiv_int.shift);
+ val |= vco->ndiv_int << ctrl->ndiv_int.shift;
+ writel(val, pll->pll_base + ctrl->ndiv_int.offset);
+
+ /* program fractional part of NDIV */
+ if (ctrl->flags & IPROC_CLK_PLL_HAS_NDIV_FRAC) {
+ val = readl(pll->pll_base + ctrl->ndiv_frac.offset);
+ val &= ~(bit_mask(ctrl->ndiv_frac.width) <<
+ ctrl->ndiv_frac.shift);
+ val |= vco->ndiv_frac << ctrl->ndiv_frac.shift;
+ writel(val, pll->pll_base + ctrl->ndiv_frac.offset);
+ }
+
+ /* program PDIV */
+ val = readl(pll->pll_base + ctrl->pdiv.offset);
+ val &= ~(bit_mask(ctrl->pdiv.width) << ctrl->pdiv.shift);
+ val |= vco->pdiv << ctrl->pdiv.shift;
+ writel(val, pll->pll_base + ctrl->pdiv.offset);
+
+ __pll_bring_out_reset(pll, kp, ka, ki);
+
+ ret = pll_wait_for_lock(pll);
+ if (ret < 0) {
+ pr_err("%s: pll: %s failed to lock\n", __func__, pll->name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int iproc_pll_enable(struct clk_hw *hw)
+{
+ struct iproc_pll *pll = to_iproc_pll(hw);
+
+ return __pll_enable(pll);
+}
+
+static void iproc_pll_disable(struct clk_hw *hw)
+{
+ struct iproc_pll *pll = to_iproc_pll(hw);
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+
+ if (ctrl->flags & IPROC_CLK_AON)
+ return;
+
+ __pll_disable(pll);
+}
+
+static unsigned long iproc_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct iproc_pll *pll = to_iproc_pll(hw);
+ const struct iproc_pll_ctrl *ctrl = pll->ctrl;
+ u32 val;
+ u64 ndiv;
+ unsigned int ndiv_int, ndiv_frac, pdiv;
+
+ if (parent_rate == 0)
+ return 0;
+
+ /* PLL needs to be locked */
+ val = readl(pll->pll_base + ctrl->status.offset);
+ if ((val & (1 << ctrl->status.shift)) == 0) {
+ pll->rate = 0;
+ return 0;
+ }
+
+ /*
+ * PLL output frequency =
+ *
+ * ((ndiv_int + ndiv_frac / 2^20) * (parent clock rate / pdiv)
+ */
+ val = readl(pll->pll_base + ctrl->ndiv_int.offset);
+ ndiv_int = (val >> ctrl->ndiv_int.shift) &
+ bit_mask(ctrl->ndiv_int.width);
+ ndiv = ndiv_int << ctrl->ndiv_int.shift;
+
+ if (ctrl->flags & IPROC_CLK_PLL_HAS_NDIV_FRAC) {
+ val = readl(pll->pll_base + ctrl->ndiv_frac.offset);
+ ndiv_frac = (val >> ctrl->ndiv_frac.shift) &
+ bit_mask(ctrl->ndiv_frac.width);
+
+ if (ndiv_frac != 0)
+ ndiv = (ndiv_int << ctrl->ndiv_int.shift) | ndiv_frac;
+ }
+
+ val = readl(pll->pll_base + ctrl->pdiv.offset);
+ pdiv = (val >> ctrl->pdiv.shift) & bit_mask(ctrl->pdiv.width);
+
+ pll->rate = (ndiv * parent_rate) >> ctrl->ndiv_int.shift;
+
+ if (pdiv == 0)
+ pll->rate *= 2;
+ else
+ pll->rate /= pdiv;
+
+ return pll->rate;
+}
+
+static const struct clk_ops iproc_pll_ops = {
+ .enable = iproc_pll_enable,
+ .disable = iproc_pll_disable,
+ .recalc_rate = iproc_pll_recalc_rate,
+};
+
+void __init iproc_pll_setup(struct device_node *node,
+ const struct iproc_pll_ctrl *ctrl,
+ const struct iproc_pll_vco_freq_param *vco_param,
+ unsigned int num_vco_entries)
+{
+ int ret;
+ struct clk *clk;
+ struct iproc_pll *pll;
+ struct clk_init_data init;
+ const char *parent_name;
+ unsigned int rate;
+
+ if (WARN_ON(!ctrl))
+ return;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (WARN_ON(!pll))
+ return;
+
+ pll->pll_base = of_iomap(node, 0);
+ if (WARN_ON(!pll->pll_base))
+ goto err_pll_iomap;
+
+ pll->pwr_base = of_iomap(node, 1);
+ if (WARN_ON(!pll->pwr_base))
+ goto err_pwr_iomap;
+
+ /* some PLLs require gating control at the top ASIU level */
+ if (ctrl->flags & IPROC_CLK_PLL_ASIU) {
+ pll->asiu_base = of_iomap(node, 2);
+ if (WARN_ON(!pll->asiu_base))
+ goto err_asiu_iomap;
+ }
+
+ pll->ctrl = ctrl;
+ pll->name = node->name;
+ init.name = node->name;
+ init.ops = &iproc_pll_ops;
+ init.flags = 0;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+ pll->hw.init = &init;
+
+ /* configure the PLL to the desired VCO frequency if specified */
+ ret = of_property_read_u32(node, "clock-frequency", &rate);
+ if (!ret) {
+ unsigned long parent_rate;
+ int rate_index;
+
+ if (WARN_ON(!vco_param))
+ goto err_clk_register;
+
+ pll->num_vco_entries = num_vco_entries;
+ pll->vco_param = vco_param;
+
+ parent_rate = __get_rate(parent_name);
+ if (WARN_ON(!parent_rate))
+ goto err_clk_register;
+
+ rate_index = pll_get_rate_index(pll, rate);
+ if (WARN_ON(rate_index < 0))
+ goto err_clk_register;
+
+ ret = pll_set_rate(pll, rate_index, parent_rate);
+ if (WARN_ON(ret))
+ goto err_clk_register;
+ }
+
+ clk = clk_register(NULL, &pll->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_clk_register;
+
+ pll->clk_data.clk_num = 1;
+ pll->clk_data.clks = &clk;
+
+ ret = of_clk_add_provider(node, of_clk_src_onecell_get,
+ &pll->clk_data);
+ if (WARN_ON(ret))
+ goto err_clk_add;
+
+ return;
+
+err_clk_add:
+ clk_unregister(clk);
+err_clk_register:
+ if (pll->asiu_base)
+ iounmap(pll->asiu_base);
+err_asiu_iomap:
+ iounmap(pll->pwr_base);
+err_pwr_iomap:
+ iounmap(pll->pll_base);
+err_pll_iomap:
+ kfree(pll);
+}
diff --git a/drivers/clk/bcm/clk-iproc.h b/drivers/clk/bcm/clk-iproc.h
new file mode 100644
index 0000000..4aa0479
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CLK_IPROC_H
+#define _CLK_IPROC_H
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/clk-provider.h>
+
+#define IPROC_CLK_NAME_LEN 25
+#define IPROC_CLK_INVALID_OFFSET 0xffffffff
+#define bit_mask(width) ((1 << (width)) - 1)
+
+/* clock should not be disabled at runtime */
+#define IPROC_CLK_AON BIT(0)
+
+/* PLL requires gating through ASIU */
+#define IPROC_CLK_PLL_ASIU BIT(1)
+
+/* PLL has fractional part of the NDIV */
+#define IPROC_CLK_PLL_HAS_NDIV_FRAC BIT(2)
+
+/*
+ * Parameters for VCO frequency configuration
+ *
+ * VCO frequency =
+ * ((ndiv_int + ndiv_frac / 2^20) * (ref freqeuncy / pdiv)
+ */
+struct iproc_pll_vco_freq_param {
+ unsigned long rate;
+ unsigned int ndiv_int;
+ unsigned int ndiv_frac;
+ unsigned int pdiv;
+};
+
+struct iproc_clk_reg_op {
+ unsigned int offset;
+ unsigned int shift;
+ unsigned int width;
+};
+
+/*
+ * Clock gating control at the top ASIU level
+ */
+struct iproc_asiu_gate {
+ unsigned int offset;
+ unsigned int en_shift;
+};
+
+/*
+ * Control of powering on/off of a PLL
+ *
+ * Before powering off a PLL, input isolation (ISO) needs to be enabled
+ */
+struct iproc_pll_aon_pwr_ctrl {
+ unsigned int offset;
+ unsigned int pwr_width;
+ unsigned int pwr_shift;
+ unsigned int iso_shift;
+};
+
+/*
+ * Control of the PLL reset, with Ki, Kp, and Ka parameters
+ */
+struct iproc_pll_reset_ctrl {
+ unsigned int offset;
+ unsigned int reset_shift;
+ unsigned int p_reset_shift;
+ unsigned int ki_shift;
+ unsigned int ki_width;
+ unsigned int kp_shift;
+ unsigned int kp_width;
+ unsigned int ka_shift;
+ unsigned int ka_width;
+};
+
+struct iproc_pll_vco_ctrl {
+ unsigned int u_offset;
+ unsigned int l_offset;
+};
+
+/*
+ * Main PLL control parameters
+ */
+struct iproc_pll_ctrl {
+ unsigned long flags;
+ struct iproc_pll_aon_pwr_ctrl aon;
+ struct iproc_asiu_gate asiu;
+ struct iproc_pll_reset_ctrl reset;
+ struct iproc_clk_reg_op ndiv_int;
+ struct iproc_clk_reg_op ndiv_frac;
+ struct iproc_clk_reg_op pdiv;
+ struct iproc_pll_vco_ctrl vco_ctrl;
+ struct iproc_clk_reg_op status;
+};
+
+/*
+ * Controls enabling/disabling a PLL derived clock
+ */
+struct iproc_clk_enable_ctrl {
+ unsigned int offset;
+ unsigned int enable_shift;
+ unsigned int hold_shift;
+ unsigned int bypass_shift;
+};
+
+/*
+ * Main clock control parameters for clocks derived from the PLLs
+ */
+struct iproc_clk_ctrl {
+ unsigned int channel;
+ unsigned long flags;
+ struct iproc_clk_enable_ctrl enable;
+ struct iproc_clk_reg_op mdiv;
+};
+
+/*
+ * Divisor of the ASIU clocks
+ */
+struct iproc_asiu_div {
+ unsigned int offset;
+ unsigned int en_shift;
+ unsigned int high_shift;
+ unsigned int high_width;
+ unsigned int low_shift;
+ unsigned int low_width;
+};
+
+extern void __init iproc_armpll_setup(struct device_node *node);
+extern void __init iproc_pll_setup(struct device_node *node,
+ const struct iproc_pll_ctrl *ctrl,
+ const struct iproc_pll_vco_freq_param *vco_param,
+ unsigned int num_freqs);
+extern void __init iproc_clk_setup(struct device_node *node,
+ const struct iproc_clk_ctrl *ctrl, unsigned int num_clks);
+extern void __init iproc_asiu_setup(struct device_node *node,
+ const struct iproc_asiu_div *div,
+ const struct iproc_asiu_gate *gate, unsigned int num_clks);
+
+#endif /* _CLK_IPROC_H */

Ray Jui

unread,
Dec 4, 2014, 4:50:05 PM12/4/14
to
Replace current device tree dummy clocks with real clock support for
Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbr...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus-clock.dtsi | 110 ++++++++++++++++++++++++-------
arch/arm/boot/dts/bcm-cygnus.dtsi | 2 +-
2 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/arch/arm/boot/dts/bcm-cygnus-clock.dtsi b/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
index 60d8389..f2da5e2 100644
--- a/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus-clock.dtsi
@@ -36,56 +36,116 @@ clocks {
ranges;

osc: oscillator {
+ #clock-cells = <0>;
compatible = "fixed-clock";
- #clock-cells = <1>;
clock-frequency = <25000000>;
};

- apb_clk: apb_clk {
- compatible = "fixed-clock";
+ /* Cygnus ARM PLL */
+ armpll: armpll {
#clock-cells = <0>;
- clock-frequency = <1000000000>;
+ compatible = "brcm,cygnus-armpll";
+ clocks = <&osc>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
};

- keypad_clk: keypad_clk {
- compatible = "fixed-clock";
+ /* various clocks running off the GENPLL */
+ genpll_clks: genpll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-genpll-clk";
+ reg = <0x0301d000 0x2c>;
+ clocks = <&genpll>;
+ clock-output-names = "axi21", "250mhz", "ihost_sys",
+ "enet_sw", "audio_125", "can";
+ };
+
+ /* always 1/2 of the axi21 clock */
+ axi41_clk: axi41_clk {
#clock-cells = <0>;
- clock-frequency = <31806>;
+ compatible = "fixed-factor-clock";
+ clocks = <&genpll_clks 0>;
+ clock-div = <2>;
+ clock-mult = <1>;
};

- adc_clk: adc_clk {
- compatible = "fixed-clock";
+ /* always 1/4 of the axi21 clock */
+ axi81_clk: axi81_clk {
#clock-cells = <0>;
- clock-frequency = <1562500>;
+ compatible = "fixed-factor-clock";
+ clocks = <&genpll_clks 0>;
+ clock-div = <4>;
+ clock-mult = <1>;
};

- pwm_clk: pwm_clk {
- compatible = "fixed-clock";
+ lcpll0: lcpll0 {
#clock-cells = <0>;
- clock-frequency = <1000000>;
+ compatible = "brcm,cygnus-lcpll0";
+ reg = <0x0301d02c 0x1c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
};

- lcd_clk: mipipll_ch1 {
- compatible = "fixed-clock";
+ /* various clocks running off the LCPLL0 */
+ lcpll0_clks: lcpll0_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-lcpll0-clk";
+ reg = <0x0301d02c 0x1c>;
+ clocks = <&lcpll0>;
+ clock-output-names = "pcie_phy", "ddr_phy", "sdio",
+ "usb_phy", "smart_card", "ch5";
+ };
+
+ mipipll: mipipll {
#clock-cells = <0>;
- clock-frequency = <100000000>;
+ compatible = "brcm,cygnus-mipipll";
+ reg = <0x180a9800 0x2c>,
+ <0x0301c020 0x4>,
+ <0x180aa024 0x4>;
+ clock-frequency = <1350000000>;
+ clocks = <&osc>;
+ };
+
+ mipipll_clks: mipipll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-mipipll-clk";
+ reg = <0x180a9800 0x2c>;
+ clocks = <&mipipll>;
+ clock-output-names = "ch0_unused", "ch1_lcd", "ch2_unused",
+ "ch3_unused", "ch4_unused", "ch5_unused";
+ };
+
+ asiu_clks: asiu_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-asiu-clk";
+ reg = <0x0301d048 0xc>,
+ <0x180aa024 0x4>;
+ clocks = <&osc>;
+ clock-output-names = "keypad", "adc/touch", "pwm";
};
};
diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..2b99e9c 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -134,7 +134,7 @@
compatible = "arm,cortex-a9-global-timer";
reg = <0x19020200 0x100>;
interrupts = <GIC_PPI 11 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&periph_clk>;
+ clocks = <&arm_periph_clk>;
};

};

Ray Jui

unread,
Dec 4, 2014, 4:50:06 PM12/4/14
to
The Broadcom Cygnus SoC is architected under the iProc architecture. It
has the following PLLs: ARMPLL, GENPLL, LCPLL0, MIPIPLL, all dervied
from an onboard crystal. Cygnus also has various ASIU clocks that are
derived directly from the onboard crystal.

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/clk/bcm/Makefile | 1 +
drivers/clk/bcm/clk-cygnus.c | 277 ++++++++++++++++++++++++++++++++
include/dt-bindings/clock/bcm-cygnus.h | 77 +++++++++
3 files changed, 355 insertions(+)
create mode 100644 drivers/clk/bcm/clk-cygnus.c
create mode 100644 include/dt-bindings/clock/bcm-cygnus.h

diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index 6926636..afcbe55 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-clk.o clk-iproc-asiu.o
+obj-$(CONFIG_ARCH_BCM_CYGNUS) += clk-cygnus.o
diff --git a/drivers/clk/bcm/clk-cygnus.c b/drivers/clk/bcm/clk-cygnus.c
new file mode 100644
index 0000000..f603d1d
--- /dev/null
+++ b/drivers/clk/bcm/clk-cygnus.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+};
+
+static const struct iproc_pll_ctrl lcpll0 = {
+ .flags = IPROC_CLK_AON,
+ .aon = aon_val(0x0, 2, 5, 4),
+ .reset = reset_val(0x0, 31, 30, 27, 3, 23, 4, 19, 4),
+ .ndiv_int = reg_val(0x4, 16, 10),
+ .pdiv = reg_val(0x4, 26, 4),
+ .vco_ctrl = vco_ctrl_val(0x10, 0x14),
+ .status = reg_val(0x18, 12, 1),
+};
+
+/*
+ * MIPI PLL VCO frequency parameter table
+ */
+static const struct iproc_pll_vco_freq_param mipipll_vco_params[] = {
+ /* rate (Hz) ndiv_int ndiv_frac pdiv */
+ { 750000000UL, 30, 0, 1 },
+ { 1000000000UL, 40, 0, 1 },
+ { 1350000000ul, 54, 0, 1 },
+ { 2000000000UL, 80, 0, 1 },
+ { 2100000000UL, 84, 0, 1 },
+ { 2250000000UL, 90, 0, 1 },
+ { 2500000000UL, 100, 0, 1 },
+ { 2700000000UL, 54, 0, 0 },
+ { 2975000000UL, 119, 0, 1 },
+ { 3100000000UL, 124, 0, 1 },
+ { 3150000000UL, 126, 0, 1 },
+};
+
+static const struct iproc_pll_ctrl mipipll = {
+ .flags = IPROC_CLK_PLL_ASIU | IPROC_CLK_PLL_HAS_NDIV_FRAC,
+ .aon = aon_val(0x0, 4, 17, 16),
+ .asiu = asiu_gate_val(0x0, 3),
+ .reset = reset_val(0x0, 11, 10, 4, 3, 0, 4, 7, 4),
+ .ndiv_int = reg_val(0x10, 20, 10),
+ .ndiv_frac = reg_val(0x10, 0, 20),
+ .pdiv = reg_val(0x14, 0, 4),
+ .vco_ctrl = vco_ctrl_val(0x18, 0x1c),
+ .status = reg_val(0x28, 12, 1),
+};
+
+ },
+};
+
+ },
+};
+
+ },
+};
+
+static const struct iproc_asiu_div asiu_div[BCM_CYGNUS_NUM_ASIU_CLKS] = {
+ [BCM_CYGNUS_ASIU_KEYPAD_CLK] =
+ asiu_div_val(0x0, 31, 16, 10, 0, 10),
+ [BCM_CYGNUS_ASIU_ADC_CLK] =
+ asiu_div_val(0x4, 31, 16, 10, 0, 10),
+ [BCM_CYGNUS_ASIU_PWM_CLK] =
+ asiu_div_val(0x8, 31, 16, 10, 0, 10),
+};
+
new file mode 100644

Ray Jui

unread,
Dec 4, 2014, 4:50:06 PM12/4/14
to
Document the device tree binding for Broadcom iProc architecture based
clock controller

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
brcm,iproc-clocks.txt | 178 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 178 insertions(+)
create mode 100644 brcm,iproc-clocks.txt

diff --git a/brcm,iproc-clocks.txt b/brcm,iproc-clocks.txt
new file mode 100644
+ osc: oscillator {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <25000000>;
+ };
+
+ genpll: genpll {
+ #clock-cells = <0>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
+ compatible = "brcm,cygnus-genpll";
+ reg = <0x0301d000 0x2c>,
+ <0x0301c020 0x4>;
+ clocks = <&osc>;
+ };
+
+ genpll_clks: genpll_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-genpll-clk";
+ reg = <0x0301d000 0x2c>;
+ clocks = <&genpll>;
+ clock-output-names = "axi21", "250mhz", "ihost_sys",
+ "enet_sw", "audio_125", "can";
+ };
+
+Required properties for ASIU clocks:
+
+ASIU clocks are a special case. These clocks are derived directly from the
+reference clock of the onboard crystal
+
+- compatible:
+ Should have a value of the form "brcm,<soc>-asiu-clk". For example, ASIU
+clocks for Cygnus have a compatible string of "brcm,cygnus-asiu-clk"
+
+- #clock-cells:
+ Have a value of <1> since there are more than 1 ASIU clocks
+
+- reg:
+ Define the base and range of the I/O address space that contain the iProc
+clock control registers required for ASIU clocks
+
+- clocks:
+ The input parent clock phandle for the ASIU clock, i.e., the onboard
+crystal
+
+- clock-output-names:
+ An ordered list of strings defining the names of the ASIU clocks
+
+Example:
+
+ osc: oscillator {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <25000000>;
+ };
+
+ asiu_clks: asiu_clks {
+ #clock-cells = <1>;
+ compatible = "brcm,cygnus-asiu-clk";
+ reg = <0x0301d048 0xc>,
+ <0x180aa024 0x4>;
+ clocks = <&osc>;
+ clock-output-names = "keypad", "adc/touch", "pwm";

Ray Jui

unread,
Dec 4, 2014, 4:50:07 PM12/4/14
to
This patchset contains the initial common clock support for Broadcom's iProc
family of SoCs. The iProc clock architecture comprises of various PLLs, e.g.,
ARMPLL, GENPLL, LCPLL0, MIPIPLL, and etc. An onboard crystal serves as the
basic reference clock for these PLLs. Each PLL may have several leaf clocks.
One special group of clocks is the ASIU clocks, which are dervied directly
from the crystal reference clock.

This patchset also contains the basic clock support for the Broadcom Cygnus
SoC, which implements the iProc clock architecture

Ray Jui (4):
clk: iproc: define Broadcom iProc clock binding
clk: iproc: add initial common clock support
clk: cygnus: add clock support for Broadcom Cygnus
ARM: dts: enable clock support for Broadcom Cygnus

arch/arm/boot/dts/bcm-cygnus-clock.dtsi | 110 +++++--
arch/arm/boot/dts/bcm-cygnus.dtsi | 2 +-
brcm,iproc-clocks.txt | 178 ++++++++++++
drivers/clk/Makefile | 2 +-
drivers/clk/bcm/Kconfig | 9 +
drivers/clk/bcm/Makefile | 2 +
drivers/clk/bcm/clk-cygnus.c | 277 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-armpll.c | 286 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-asiu.c | 275 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-clk.c | 238 +++++++++++++++
drivers/clk/bcm/clk-iproc-pll.c | 483 +++++++++++++++++++++++++++++++
drivers/clk/bcm/clk-iproc.h | 155 ++++++++++
include/dt-bindings/clock/bcm-cygnus.h | 77 +++++
13 files changed, 2067 insertions(+), 27 deletions(-)
create mode 100644 brcm,iproc-clocks.txt
create mode 100644 drivers/clk/bcm/clk-cygnus.c
create mode 100644 drivers/clk/bcm/clk-iproc-armpll.c
create mode 100644 drivers/clk/bcm/clk-iproc-asiu.c
create mode 100644 drivers/clk/bcm/clk-iproc-clk.c
create mode 100644 drivers/clk/bcm/clk-iproc-pll.c
create mode 100644 drivers/clk/bcm/clk-iproc.h
create mode 100644 include/dt-bindings/clock/bcm-cygnus.h

Ray Jui

unread,
Dec 4, 2014, 4:50:08 PM12/4/14
to
This adds basic and generic support for various iProc PLLs and clocks
including the ARMPLL, GENPLL, LCPLL, MIPIPLL, and ASIU clocks.

SoCs under the iProc architecture can define their specific register
offsets and clock parameters for their PLL and clock controllers. These
parameters can be passed as arugments into the generic iProc PLL and
clock setup functions

Derived from code originally provided by Jonathan Richardson
<jona...@broadcom.com>

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/clk/Makefile | 2 +-
drivers/clk/bcm/Kconfig | 9 +
drivers/clk/bcm/Makefile | 1 +
drivers/clk/bcm/clk-iproc-armpll.c | 286 +++++++++++++++++++++
drivers/clk/bcm/clk-iproc-asiu.c | 275 ++++++++++++++++++++
drivers/clk/bcm/clk-iproc-clk.c | 238 ++++++++++++++++++
drivers/clk/bcm/clk-iproc-pll.c | 483 ++++++++++++++++++++++++++++++++++++
drivers/clk/bcm/clk-iproc.h | 155 ++++++++++++
8 files changed, 1448 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/bcm/clk-iproc-armpll.c
create mode 100644 drivers/clk/bcm/clk-iproc-asiu.c
create mode 100644 drivers/clk/bcm/clk-iproc-clk.c
create mode 100644 drivers/clk/bcm/clk-iproc-pll.c
create mode 100644 drivers/clk/bcm/clk-iproc.h

diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index 6297d05..6926636 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
+obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-clk.o clk-iproc-asiu.o
diff --git a/drivers/clk/bcm/clk-iproc-armpll.c b/drivers/clk/bcm/clk-iproc-armpll.c
new file mode 100644
index 0000000..ec9b130
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-armpll.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+}
+
+/*
+}
+
+/*
+}
+
new file mode 100644
index 0000000..ab86b8c
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-asiu.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+}
+
new file mode 100644
index 0000000..be3c42c
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-clk.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+}
+
new file mode 100644
index 0000000..cd3bd38
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc-pll.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+
+#include "clk-iproc.h"
+
+#define PLL_VCO_HIGH_SHIFT 19
+#define PLL_VCO_LOW_SHIFT 30
+
+/* number of delay loops waiting for PLL to lock */
+#define LOCK_DELAY 100
+
+/* number of VCO frequency bands */
+#define NUM_FREQ_BANDS 8
+
+#define NUM_KP_BANDS 3
+enum kp_band {
+ KP_BAND_MID = 0,
+ KP_BAND_HIGH,
+ KP_BAND_HIGH_HIGH
+};
+
+static const unsigned int kp_table[NUM_KP_BANDS][NUM_FREQ_BANDS] = {
+ { 5, 6, 6, 7, 7, 8, 9, 10 },
+ { 4, 4, 5, 5, 6, 7, 8, 9 },
+ { 4, 5, 5, 6, 7, 8, 9, 10 },
+};
+
+}
+
+/*
+}
+
new file mode 100644
index 0000000..4aa0479
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+};
+
+/*
+ * Clock gating control at the top ASIU level
+ */
+struct iproc_asiu_gate {
+ unsigned int offset;
+ unsigned int en_shift;
+};
+
+/*
+ * Control of powering on/off of a PLL
+ *
+ * Before powering off a PLL, input isolation (ISO) needs to be enabled
+ */
+struct iproc_pll_aon_pwr_ctrl {
+ unsigned int offset;
+ unsigned int pwr_width;
+ unsigned int pwr_shift;
+ unsigned int iso_shift;
+};
+
+/*
+ * Control of the PLL reset, with Ki, Kp, and Ka parameters
+ */
+struct iproc_pll_reset_ctrl {
+ unsigned int offset;
+ unsigned int reset_shift;
+ unsigned int p_reset_shift;
+ unsigned int ki_shift;
+ unsigned int ki_width;
+ unsigned int kp_shift;
+ unsigned int kp_width;
+ unsigned int ka_shift;
+ unsigned int ka_width;
+};
+
+struct iproc_pll_vco_ctrl {
+ unsigned int u_offset;
+ unsigned int l_offset;
+};
+
+/*
+ * Main PLL control parameters
+ */
+struct iproc_pll_ctrl {
+ unsigned long flags;
+ struct iproc_pll_aon_pwr_ctrl aon;
+ struct iproc_asiu_gate asiu;
+ struct iproc_pll_reset_ctrl reset;
+ struct iproc_clk_reg_op ndiv_int;
+ struct iproc_clk_reg_op ndiv_frac;
+ struct iproc_clk_reg_op pdiv;
+ struct iproc_pll_vco_ctrl vco_ctrl;
+ struct iproc_clk_reg_op status;
+};
+
+/*
+ * Controls enabling/disabling a PLL derived clock
+ */
+struct iproc_clk_enable_ctrl {
+ unsigned int offset;
+ unsigned int enable_shift;
+ unsigned int hold_shift;
+ unsigned int bypass_shift;
+};
+
+/*
+ * Main clock control parameters for clocks derived from the PLLs
+ */
+struct iproc_clk_ctrl {
+ unsigned int channel;
+ unsigned long flags;
+ struct iproc_clk_enable_ctrl enable;
+ struct iproc_clk_reg_op mdiv;
+};
+
+/*
+ * Divisor of the ASIU clocks
+ */
+struct iproc_asiu_div {
+ unsigned int offset;
+ unsigned int en_shift;
+ unsigned int high_shift;
+ unsigned int high_width;
+ unsigned int low_shift;
+ unsigned int low_width;
+};
+
+extern void __init iproc_armpll_setup(struct device_node *node);
+extern void __init iproc_pll_setup(struct device_node *node,
+ const struct iproc_pll_ctrl *ctrl,
+ const struct iproc_pll_vco_freq_param *vco_param,
+ unsigned int num_freqs);
+extern void __init iproc_clk_setup(struct device_node *node,
+ const struct iproc_clk_ctrl *ctrl, unsigned int num_clks);
+extern void __init iproc_asiu_setup(struct device_node *node,
+ const struct iproc_asiu_div *div,
+ const struct iproc_asiu_gate *gate, unsigned int num_clks);
+
+#endif /* _CLK_IPROC_H */

Ray Jui

unread,
Dec 4, 2014, 5:00:06 PM12/4/14
to
This adds the initial driver support for the Broadcom Cygnus pinctrl
controller. The Cygnus pinctrl controller supports group based
alternate function configuration

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
Signed-off-by: Fengguang Wu <fenggu...@intel.com>
---
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
new file mode 100644
index 0000000..eb6e27a
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-bcm-cygnus.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "pinctrl-utils.h"
+
+/*
+ * Alternate function configuration
+ *
+ * @name: name of the alternate function
+ * @group_names: array of strings of group names that can be supported by this
+ * alternate function
+ * @num_groups: total number of groups that can be supported by this alternate
+ * function
+ * @mux: mux setting for this alternate function to be programed
+ */
+struct cygnus_pin_function {
+ const char *name;
+ const char * const *group_names;
+ const unsigned num_groups;
+ unsigned int mux;
+};
+
+/*
+ * Cygnus allows group based pinmux configuration
+ *
+ * @name: name of the group
+ * @pins: array of pins used by this group
+ * @num_pins: total number of pins used by this group
+ * @offset: register offset for pinmux configuration of this group
+ * @shift: bit shift for pinmux configuration of this group
+ */
+struct cygnus_pin_group {
+ const char *name;
+ const unsigned *pins;
+ const unsigned num_pins;
+ const unsigned int offset;
+ const unsigned int shift;
+};
+
+/*
+ * Cygnus pinctrl core
+ *
+ * @pctl: pointer to pinctrl_dev
+ * @dev: pointer to the device
+ * @base: I/O register base for Cygnus pinctrl configuration
+ *
+ */
+struct cygnus_pinctrl {
+ struct pinctrl_dev *pctl;
+ struct device *dev;
+ void __iomem *base;
+
+ const struct pinctrl_pin_desc *pins;
+ unsigned num_pins;
+
+ const struct cygnus_pin_group *groups;
+ unsigned num_groups;
+
+ const struct cygnus_pin_function *functions;
+ unsigned num_functions;
+};
+
+#define CYGNUS_PIN_GROUP(group_name, off, sh) \
+{ \
+ .name = #group_name, \
+ .pins = group_name##_pins, \
+ .num_pins = ARRAY_SIZE(group_name##_pins), \
+ .offset = off, \
+ .shift = sh, \
+}
+
+/*
+ * The following pin description is based on Cygnus I/O MUX spreadsheet
+ */
+};
+
+/*
+};
+
+/*
+ * List of groups. Need to match the order in cygnus_pin_group_names
+ */
+}
+
+/*
+ * Cygnus has 4 alternate functions. All groups can be configured to any of
+ * the 4 alternate functions
+ */
+
+ return 0;
+}
+
+static void cygnus_pin_dbg_show(struct pinctrl_dev *pctrl_dev,
+ struct seq_file *s, unsigned offset)
+{
+ seq_printf(s, " %s", dev_name(pctrl_dev->dev));
+}
+
+static int find_matched_function(const char *function_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cygnus_pin_functions); i++) {
+ if (!strcmp(cygnus_pin_functions[i].name, function_name))
+ return (int)cygnus_pin_functions[i].mux;
+ }
+
+ return -EINVAL;
+}
+
+static int cygnus_dt_node_to_map(struct pinctrl_dev *pctrl_dev,
+ struct device_node *np, struct pinctrl_map **map,
+ unsigned *num_maps)
+{
+ int ret, num_groups;
+ unsigned reserved_maps = 0;
+ struct property *prop;
+ const char *group_name, *function_name;
+
+ *map = NULL;
+ *num_maps = 0;
+
+ num_groups = of_property_count_strings(np, "brcm,groups");
+ if (num_groups < 0) {
+ dev_err(pctrl_dev->dev,
+ "could not parse property brcm,groups\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_string(np, "brcm,function", &function_name);
+ if (ret < 0) {
+ dev_err(pctrl_dev->dev,
+ "could not parse property brcm,function\n");
+ return -EINVAL;
+ }
+
+ /* make sure it's a valid alternate function */
+ ret = find_matched_function(function_name);
+ if (ret < 0) {
+ dev_err(pctrl_dev->dev, "invalid function name: %s\n",
+ function_name);
+ }
+
+ ret = pinctrl_utils_reserve_map(pctrl_dev, map, &reserved_maps,
+ num_maps, num_groups);
+ if (ret) {
+ dev_err(pctrl_dev->dev, "unable to reserve map\n");
+ return ret;
+ }
+
+ of_property_for_each_string(np, "brcm,groups", prop, group_name) {
+ ret = pinctrl_utils_add_map_mux(pctrl_dev, map,
+ &reserved_maps, num_maps, group_name,
+ function_name);
+ if (ret) {
+ dev_err(pctrl_dev->dev, "can't add map: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+
+ return 0;
+}
+
+static int cygnus_pinmux_set_mux(struct pinctrl_dev *pctrl_dev,
+ unsigned function_selector, unsigned group_selector)
+{
+ struct cygnus_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev);
+ const struct cygnus_pin_function *function =
+ &pinctrl->functions[function_selector];
+ const struct cygnus_pin_group *group =
+ &pinctrl->groups[group_selector];
+ u32 val, mask = 0x7;
+
+ dev_dbg(pctrl_dev->dev,
+ "group:%s with offset:0x%08x shift:%u set to function: %s mux:%u\n",
+ group->name, group->offset, group->shift, function->name,
+ function->mux);
+
+ val = readl(pinctrl->base + group->offset);
+ val &= ~(mask << group->shift);
+ val |= function->mux << group->shift;
+ writel(val, pinctrl->base + group->offset);
+
+ return 0;
+}
+
+ dev_err(&pdev->dev, "unable to register cygnus pinctrl\n");
+ return -EINVAL;
+ }
+
+ platform_set_drvdata(pdev, pinctrl);
+
+ return 0;
+}
+
+static int cygnus_pinctrl_remove(struct platform_device *pdev)
+{
+ struct cygnus_pinctrl *pinctrl = platform_get_drvdata(pdev);
+
+ pinctrl_unregister(pinctrl->pctl);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct of_device_id cygnus_pinctrl_of_match[] = {
+ { .compatible = "brcm,cygnus-pinctrl", },
+ { },
+};
+

Ray Jui

unread,
Dec 4, 2014, 5:00:06 PM12/4/14
to
This enables the pinctrl driver for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..b4efff2 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select PINCTRL_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Dec 4, 2014, 5:00:06 PM12/4/14
to
Device tree binding documentation for Broadcom Cygnus pinctrl driver

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 ++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt

diff --git a/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
new file mode 100644
index 0000000..86e4579
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
@@ -0,0 +1,92 @@
+Broadcom Cygnus Pin Controller
+
+The Cygnus pin controller supports setting the alternate functions of groups
+of pins. Pinmux configuration on individual pins is not supported by the
+Cygnus A0 SoC.
+
+Required properties:
+
+- compatible:
+ Must be "brcm,cygnus-pinctrl"
+
+- reg:
+ Define the base and range of the I/O address space that contain the Cygnus
+pin control registers
+
+- brcm,groups:
+ This can be strings of one or more group names. This defines the group(s)
+that one wants to configure
+
+- brcm,function:
+ This is the alternate function that one wants to configure to. Valid
+alternate functions are "alt1", "alt2", "alt3", "alt4"
+
+Each child node represents a configuration. Client devices reference the the
+child node to enable the mux configuration.
+
+For example:
+
+ pinctrl: pinctrl@0x0301d0c8 {
+ compatible = "brcm,cygnus-pinctrl";
+ reg = <0x0301d0c8 0x2c>;
+

Ray Jui

unread,
Dec 4, 2014, 5:00:06 PM12/4/14
to
This patchset contains the initial pinctrl support for the Broadcom Cygnus SoC.
The Cygnus pinctrl controller supports group based alternate function configuration

Ray Jui (4):
pinctrl: Broadcom Cygnus pinctrl device tree binding
pinctrl: cygnus: add initial pinctrl support
ARM: mach-bcm: enable pinctrl support for Cygnus
ARM: dts: enable pinctrl for Broadcom Cygnus

.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 +++
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-bcm-cygnus.c | 753 ++++++++++++++++++++
6 files changed, 859 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
create mode 100644 drivers/pinctrl/pinctrl-bcm-cygnus.c

Ray Jui

unread,
Dec 4, 2014, 5:00:08 PM12/4/14
to
This enables the pinctrl support for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..4c6bf4d 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,11 @@

/include/ "bcm-cygnus-clock.dtsi"

+ pinctrl: pinctrl@0x0301d0c8 {
+ compatible = "brcm,cygnus-pinctrl";
+ reg = <0x0301d0c8 0x2c>;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Belisko Marek

unread,
Dec 4, 2014, 5:20:09 PM12/4/14
to
^^^^ typo - should be I2S_0
> +
> +Consider another example, that one wants to configure the above pins as GPIO:
> +
> + gpio_24_27: gpio_24_27 {
> + brcm,groups = "smart_card0", "smart_card0_fcb";
> + brcm,function = "alt4";
> + };
> +
> +With the above configuration, pins 42, 44, 45, 46 become GPIO, and 43 and 47
> +become reserved for STRAP
> --
> 1.7.9.5
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-ar...@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

BR,

marek

--
as simple and primitive as possible
-------------------------------------------------
Marek Belisko - OPEN-NANDRA
Freelance Developer

Ruska Nova Ves 219 | Presov, 08005 Slovak Republic
Tel: +421 915 052 184
skype: marekwhite
twitter: #opennandra
web: http://open-nandra.com

Ray Jui

unread,
Dec 4, 2014, 5:40:06 PM12/4/14
to
Oh yeah. Will change from I2C_0 to I2S_0. Thanks.
>> +
>> +Consider another example, that one wants to configure the above pins as GPIO:
>> +
>> + gpio_24_27: gpio_24_27 {
>> + brcm,groups = "smart_card0", "smart_card0_fcb";
>> + brcm,function = "alt4";
>> + };
>> +
>> +With the above configuration, pins 42, 44, 45, 46 become GPIO, and 43 and 47
>> +become reserved for STRAP
>> --
>> 1.7.9.5
>>
>>
>> _______________________________________________
>> linux-arm-kernel mailing list
>> linux-ar...@lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
> BR,
>
> marek
>
--

Ray Jui

unread,
Dec 5, 2014, 2:50:05 PM12/5/14
to
Device tree binding documentation for Broadcom Cygnus pinctrl driver

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 ++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt

diff --git a/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
new file mode 100644
index 0000000..4461aaf
+become I2S_0, and pin 47 becomes SPDIF
+
+Consider another example, that one wants to configure the above pins as GPIO:
+
+ gpio_24_27: gpio_24_27 {
+ brcm,groups = "smart_card0", "smart_card0_fcb";
+ brcm,function = "alt4";
+ };
+
+With the above configuration, pins 42, 44, 45, 46 become GPIO, and 43 and 47
+become reserved for STRAP
--
1.7.9.5

Ray Jui

unread,
Dec 5, 2014, 2:50:05 PM12/5/14
to
This enables the pinctrl driver for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..b4efff2 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select PINCTRL_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Dec 5, 2014, 2:50:06 PM12/5/14
to
This adds the initial driver support for the Broadcom Cygnus pinctrl
controller. The Cygnus pinctrl controller supports group based
alternate function configuration

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
Signed-off-by: Fengguang Wu <fenggu...@intel.com>
---
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
new file mode 100644

Ray Jui

unread,
Dec 5, 2014, 2:50:06 PM12/5/14
to
This enables the pinctrl support for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..4c6bf4d 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,11 @@

/include/ "bcm-cygnus-clock.dtsi"

+ pinctrl: pinctrl@0x0301d0c8 {
+ compatible = "brcm,cygnus-pinctrl";
+ reg = <0x0301d0c8 0x2c>;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Dec 5, 2014, 2:50:06 PM12/5/14
to
This patchset contains the initial pinctrl support for the Broadcom Cygnus SoC.
The Cygnus pinctrl controller supports group based alternate function configuration

Changes from v1:
- Fix a typo in device tree binding document

Ray Jui (4):
pinctrl: Broadcom Cygnus pinctrl device tree binding
pinctrl: cygnus: add initial pinctrl support
ARM: mach-bcm: enable pinctrl support for Cygnus
ARM: dts: enable pinctrl for Broadcom Cygnus

.../bindings/pinctrl/brcm,cygnus-pinctrl.txt | 92 +++
arch/arm/boot/dts/bcm-cygnus.dtsi | 5 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/pinctrl/Kconfig | 7 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-bcm-cygnus.c | 753 ++++++++++++++++++++
6 files changed, 859 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/brcm,cygnus-pinctrl.txt
create mode 100644 drivers/pinctrl/pinctrl-bcm-cygnus.c

Ray Jui

unread,
Dec 5, 2014, 7:40:05 PM12/5/14
to
Signed-off-by: Ray Jui <rj...@broadcom.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7b712d8..5d67204 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2174,6 +2174,13 @@ N: bcm9583*
N: bcm583*
N: bcm113*

+BROADCOM CYGNUS GPIO DRIVER
+M: Ray Jui <rj...@broadcom.com>
+L: bcm-kernel-f...@broadcom.com
+S: Supported
+F: drivers/gpio/gpio-bcm-cygnus.c
+F: Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
+
BROADCOM KONA GPIO DRIVER
M: Ray Jui <rj...@broadcom.com>
L: bcm-kernel-f...@broadcom.com

Ray Jui

unread,
Dec 5, 2014, 7:40:06 PM12/5/14
to
Document the GPIO device tree binding for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 85 ++++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt

diff --git a/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
new file mode 100644
index 0000000..24a1513
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
@@ -0,0 +1,85 @@
+Broadcom Cygnus GPIO Controller
+
+Required properties:
+
+- compatible:
+ Currently supported Cygnus GPIO controllers include:
+ "brcm,cygnus-ccm-gpio": ChipcommonG GPIO controller
+ "brcm,cygnus-asiu-gpio": ASIU GPIO controller
+ "brcm,cygnus-crmu-gpio": CRMU GPIO controller
+
+- reg:
+ Define the base and range of the I/O address space that contain the Cygnus
+GPIO controller registers
+
+- ngpios:
+ Total number of GPIOs the controller provides
+
+- #gpio-cells:
+ Must be two. The first cell is the GPIO pin number (within the
+controller's domain) and the second cell is used for the following:
+ bit[0]: polarity (0 for normal and 1 for inverted)
+ bit[18:16]: internal pull up/down: 0 - pull up/down disabled
+ 1 - pull up enabled
+ 2 - pull down enabled
+ bit[22:20]: drive strength: 0 - 2 mA
+ 1 - 4 mA
+ 2 - 6 mA
+ 3 - 8 mA
+ 4 - 10 mA
+ 5 - 12 mA
+ 6 - 14 mA
+ 7 - 16 mA
+
+- gpio-controller:
+ Specifies that the node is a GPIO controller
+
+Optional properties:
+
+- interrupt-controller:
+ Specifies that the node is an interrupt controller. Not all Cygnus GPIO
+interfaces support interrupt, e.g., the CRMU GPIO controller does not have its
+interrupt routed to the main processor's GIC
+
+- interrupts:
+ The interrupt outputs from the GPIO controller.
+
+- no-interrupt:
+ Specifies that the GPIO interface does not support interrupt
+
+Example:
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-asiu-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-crmu-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-interrupt;
+ };
+
+ /*
+ * Touchscreen that uses the ASIU GPIO 100, with internal pull-up
+ * enabled
+ */
+ tsc {
+ ...
+ ...
+ gpio-event = <&gpio_asiu 100 0x10000>;
+ };
+
+ /* Bluetooth that uses the CRMU GPIO 2, with polarity inverted */
+ bluetooth {
+ ...
+ ...
+ bcm,rfkill-bank-sel = <&gpio_crmu 2 1>
+ }

Ray Jui

unread,
Dec 5, 2014, 7:40:06 PM12/5/14
to
This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller
("brcm,cygnus-asiu-gpio"), 2) the chipCommonG GPIO controller
("brcm,cygnus-ccm-gpio"), and 3) the ALWAYS-ON GPIO controller
("brcm,cygnus-crmu-gpio")

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 719 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 731 insertions(+)
create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 633ec21..3e3b0342 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -126,6 +126,17 @@ config GPIO_74XX_MMIO
8 bits: 74244 (Input), 74273 (Output)
16 bits: 741624 (Input), 7416374 (Output)

+config GPIO_BCM_CYGNUS
+ bool "Broadcom Cygnus GPIO support"
+ depends on ARCH_BCM_CYGNUS && OF_GPIO
+ help
+ Say yes here to turn on GPIO support for Broadcom Cygnus SoC
+
+ The Broadcom Cygnus SoC has 3 GPIO controllers including the ASIU
+ GPIO controller (ASIU), the chipCommonG GPIO controller (CCM), and
+ the always-ON GPIO controller (CRMU). All 3 GPIO controllers are
+ supported by this driver
+
config GPIO_CLPS711X
tristate "CLPS711X GPIO support"
depends on ARCH_CLPS711X || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 81755f1..31eb7e0 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o
obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
+obj-$(CONFIG_GPIO_BCM_CYGNUS) += gpio-bcm-cygnus.o
obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
diff --git a/drivers/gpio/gpio-bcm-cygnus.c b/drivers/gpio/gpio-bcm-cygnus.c
new file mode 100644
index 0000000..1549ea8
--- /dev/null
+++ b/drivers/gpio/gpio-bcm-cygnus.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/ioport.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define CYGNUS_GPIO_DATA_IN_OFFSET 0x00
+#define CYGNUS_GPIO_DATA_OUT_OFFSET 0x04
+#define CYGNUS_GPIO_OUT_EN_OFFSET 0x08
+#define CYGNUS_GPIO_IN_TYPE_OFFSET 0x0c
+#define CYGNUS_GPIO_INT_DE_OFFSET 0x10
+#define CYGNUS_GPIO_INT_EDGE_OFFSET 0x14
+#define CYGNUS_GPIO_INT_MSK_OFFSET 0x18
+#define CYGNUS_GPIO_INT_STAT_OFFSET 0x1c
+#define CYGNUS_GPIO_INT_MSTAT_OFFSET 0x20
+#define CYGNUS_GPIO_INT_CLR_OFFSET 0x24
+#define CYGNUS_GPIO_PAD_RES_OFFSET 0x34
+#define CYGNUS_GPIO_RES_EN_OFFSET 0x38
+
+/* drive strength control for ASIU GPIO */
+#define CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET 0x58
+
+/* drive strength control for CCM GPIO */
+#define CYGNUS_GPIO_CCM_DRV0_CTRL_OFFSET 0x00
+
+#define GPIO_BANK_SIZE 0x200
+#define NGPIOS_PER_BANK 32
+#define GPIO_BIT(pin) ((pin) % NGPIOS_PER_BANK)
+#define GPIO_BANK(pin) ((pin) / NGPIOS_PER_BANK)
+
+#define GPIO_FLAG_BIT_MASK 0xffff
+#define GPIO_PULL_BIT_SHIFT 16
+#define GPIO_PULL_BIT_MASK 0x3
+
+#define GPIO_DRV_STRENGTH_BIT_SHIFT 20
+#define GPIO_DRV_STRENGTH_BITS 3
+#define GPIO_DRV_STRENGTH_BIT_MASK ((1 << GPIO_DRV_STRENGTH_BITS) - 1)
+
+/*
+ * For GPIO internal pull up/down registers
+ */
+enum gpio_pull {
+ GPIO_PULL_NONE = 0,
+ GPIO_PULL_UP,
+ GPIO_PULL_DOWN,
+ GPIO_PULL_INVALID,
+};
+
+/*
+ * GPIO drive strength
+ */
+enum gpio_drv_strength {
+ GPIO_DRV_STRENGTH_2MA = 0,
+ GPIO_DRV_STRENGTH_4MA,
+ GPIO_DRV_STRENGTH_6MA,
+ GPIO_DRV_STRENGTH_8MA,
+ GPIO_DRV_STRENGTH_10MA,
+ GPIO_DRV_STRENGTH_12MA,
+ GPIO_DRV_STRENGTH_14MA,
+ GPIO_DRV_STRENGTH_16MA,
+ GPIO_DRV_STRENGTH_INVALID,
+};
+
+struct bcm_cygnus_gpio {
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *io_ctrl;
+ spinlock_t lock;
+ struct gpio_chip gc;
+ unsigned num_banks;
+ int irq;
+ struct irq_domain *irq_domain;
+};
+
+static unsigned int gpio_base_index;
+
+static inline struct bcm_cygnus_gpio *to_bcm_cygnus_gpio(
+ struct gpio_chip *gc)
+{
+ return container_of(gc, struct bcm_cygnus_gpio, gc);
+}
+
+static inline int bcm_cygnus_gpio_to_irq(struct gpio_chip *gc,
+ unsigned offset)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+
+ return irq_find_mapping(cygnus_gpio->irq_domain, offset);
+}
+
+static inline unsigned int __gpio_reg_offset(
+ struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio)
+{
+ return GPIO_BANK(gpio) * GPIO_BANK_SIZE;
+}
+
+static inline unsigned int __gpio_bitpos(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio)
+{
+ return GPIO_BIT(gpio);
+}
+
+static void bcm_cygnus_gpio_irq_handler(unsigned int irq,
+ struct irq_desc *desc)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int i, bit;
+
+ chained_irq_enter(chip, desc);
+
+ cygnus_gpio = irq_get_handler_data(irq);
+
+ /* go through the entire GPIO banks and handle all interrupts */
+ for (i = 0; i < cygnus_gpio->num_banks; i++) {
+ unsigned long val = readl(cygnus_gpio->base +
+ (i * GPIO_BANK_SIZE) +
+ CYGNUS_GPIO_INT_MSTAT_OFFSET);
+ if (val) {
+ for_each_set_bit(bit, &val, 32) {
+ unsigned pin = NGPIOS_PER_BANK * i + bit;
+ int child_irq = bcm_cygnus_gpio_to_irq(
+ &cygnus_gpio->gc, pin);
+
+ /*
+ * Clear the interrupt before invoking the
+ * handler, so we do not leave any window
+ */
+ writel(1 << bit,
+ cygnus_gpio->base +
+ (i * GPIO_BANK_SIZE) +
+ CYGNUS_GPIO_INT_CLR_OFFSET);
+
+ generic_handle_irq(child_irq);
+ }
+
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void bcm_cygnus_gpio_irq_ack(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_CLR_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static void bcm_cygnus_gpio_irq_mask(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_MSK_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static void bcm_cygnus_gpio_irq_unmask(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_MSK_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static int bcm_cygnus_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int int_type, dual_edge, edge_lvl;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ int_type = 0;
+ dual_edge = 0;
+ edge_lvl = 1;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ int_type = 0;
+ dual_edge = 0;
+ edge_lvl = 0;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ int_type = 0;
+ dual_edge = 1;
+ edge_lvl = 0;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ int_type = 1;
+ dual_edge = 0;
+ edge_lvl = 1;
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ int_type = 1;
+ dual_edge = 0;
+ edge_lvl = 0;
+ break;
+
+ default:
+ dev_err(cygnus_gpio->dev, "invalid GPIO irq type 0x%x\n", type);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_IN_TYPE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= int_type << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_DE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= dual_edge << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_EDGE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= edge_lvl << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ return 0;
+}
+
+static struct irq_chip bcm_cygnus_gpio_irq_chip = {
+ .name = "bcm-cygnus-gpio",
+ .irq_ack = bcm_cygnus_gpio_irq_ack,
+ .irq_mask = bcm_cygnus_gpio_irq_mask,
+ .irq_unmask = bcm_cygnus_gpio_irq_unmask,
+ .irq_set_type = bcm_cygnus_gpio_irq_set_type,
+};
+
+static int bcm_cygnus_gpio_direction_input(struct gpio_chip *gc,
+ unsigned gpio)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_OUT_EN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+
+ return 0;
+}
+
+static int bcm_cygnus_gpio_direction_output(struct gpio_chip *gc,
+ unsigned gpio, int value)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_OUT_EN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_OUT_OFFSET;
+
+ val = readl(cygnus_gpio->base + offset);
+ if (value)
+ val |= 1 << shift;
+ else
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev,
+ "gpio:%u offset:0x%04x shift:%u val:0x%08x\n",
+ gpio, offset, shift, val);
+
+ return 0;
+}
+
+static void bcm_cygnus_gpio_set(struct gpio_chip *gc, unsigned gpio,
+ int value)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_OUT_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ if (value)
+ val |= 1 << shift;
+ else
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev,
+ "gpio:%u offset:0x%04x shift:%u val:0x%08x\n",
+ gpio, offset, shift, val);
+}
+
+static int bcm_cygnus_gpio_get(struct gpio_chip *gc, unsigned gpio)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_IN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = readl(cygnus_gpio->base + offset);
+ val = (val >> shift) & 1;
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u val:%u\n",
+ gpio, offset, shift, val);
+
+ return val;
+}
+
+static struct lock_class_key gpio_lock_class;
+
+static int bcm_cygnus_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ int ret;
+
+ ret = irq_set_chip_data(irq, d->host_data);
+ if (ret < 0)
+ return ret;
+ irq_set_lockdep_class(irq, &gpio_lock_class);
+ irq_set_chip_and_handler(irq, &bcm_cygnus_gpio_irq_chip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+
+ return 0;
+}
+
+static void bcm_cygnus_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static struct irq_domain_ops bcm_cygnus_irq_ops = {
+ .map = bcm_cygnus_gpio_irq_map,
+ .unmap = bcm_cygnus_gpio_irq_unmap,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+#ifdef CONFIG_OF_GPIO
+static void bcm_cygnus_gpio_set_pull(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio, enum gpio_pull pull)
+{
+ unsigned int offset, shift;
+ u32 val, up;
+ unsigned long flags;
+
+ switch (pull) {
+ case GPIO_PULL_NONE:
+ return;
+ case GPIO_PULL_UP:
+ up = 1;
+ break;
+ case GPIO_PULL_DOWN:
+ up = 0;
+ break;
+ case GPIO_PULL_INVALID:
+ default:
+ return;
+ }
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ /* set pull up/down */
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_PAD_RES_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ if (up)
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ /* enable pad */
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_RES_EN_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+}
+
+static void bcm_cygnus_gpio_set_strength(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio, enum gpio_drv_strength strength)
+{
+ struct device *dev = cygnus_gpio->dev;
+ void __iomem *base;
+ unsigned int i, offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ if (of_device_is_compatible(dev->of_node, "brcm,cygnus-asiu-gpio")) {
+ base = cygnus_gpio->base;
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET;
+ } else if (of_device_is_compatible(dev->of_node,
+ "brcm,cygnus-ccm-gpio")) {
+ if (!cygnus_gpio->io_ctrl)
+ return;
+
+ base = cygnus_gpio->io_ctrl;
+ offset = CYGNUS_GPIO_CCM_DRV0_CTRL_OFFSET;
+ } else
+ return;
+
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) {
+ val = readl(base + offset);
+ val &= ~(1 << shift);
+ val |= ((strength >> i) & 0x1) << shift;
+ writel(val, base + offset);
+ offset += 4;
+ }
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+}
+
+static int bcm_cygnus_gpio_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec, u32 *flags)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ enum gpio_pull pull;
+ enum gpio_drv_strength strength;
+
+ if (gc->of_gpio_n_cells < 2)
+ return -EINVAL;
+
+ if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells))
+ return -EINVAL;
+
+ if (gpiospec->args[0] >= gc->ngpio)
+ return -EINVAL;
+
+ pull = (gpiospec->args[1] >> GPIO_PULL_BIT_SHIFT) & GPIO_PULL_BIT_MASK;
+ if (WARN_ON(pull >= GPIO_PULL_INVALID))
+ return -EINVAL;
+
+ strength = (gpiospec->args[1] >> GPIO_DRV_STRENGTH_BIT_SHIFT) &
+ GPIO_DRV_STRENGTH_BIT_MASK;
+
+ if (flags)
+ *flags = gpiospec->args[1] & GPIO_FLAG_BIT_MASK;
+
+ bcm_cygnus_gpio_set_pull(cygnus_gpio, gpiospec->args[0], pull);
+ bcm_cygnus_gpio_set_strength(cygnus_gpio, gpiospec->args[0], strength);
+
+ return gpiospec->args[0];
+}
+#endif
+
+static const struct of_device_id bcm_cygnus_gpio_of_match[] = {
+ { .compatible = "brcm,cygnus-crmu-gpio" },
+ { .compatible = "brcm,cygnus-asiu-gpio" },
+ { .compatible = "brcm,cygnus-ccm-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bcm_cygnus_gpio_of_match);
+
+static int bcm_cygnus_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct resource *res;
+ struct bcm_cygnus_gpio *cygnus_gpio;
+ struct gpio_chip *gc;
+ u32 i, ngpios;
+ int ret;
+
+ match = of_match_device(bcm_cygnus_gpio_of_match, dev);
+ if (!match) {
+ dev_err(&pdev->dev, "failed to find GPIO controller\n");
+ return -ENODEV;
+ }
+
+ cygnus_gpio = devm_kzalloc(dev, sizeof(*cygnus_gpio), GFP_KERNEL);
+ if (!cygnus_gpio)
+ return -ENOMEM;
+
+ cygnus_gpio->dev = dev;
+ platform_set_drvdata(pdev, cygnus_gpio);
+
+ if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) {
+ dev_err(&pdev->dev, "missing ngpios device tree property\n");
+ return -ENODEV;
+ }
+ cygnus_gpio->num_banks = (ngpios + NGPIOS_PER_BANK - 1) /
+ NGPIOS_PER_BANK;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "unable to get I/O resource");
+ return -ENODEV;
+ }
+
+ cygnus_gpio->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(cygnus_gpio->base)) {
+ dev_err(&pdev->dev, "unable to map I/O memory\n");
+ return PTR_ERR(cygnus_gpio->base);
+ }
+
+ /*
+ * Only certain types of Cygnus GPIO interfaces have I/O control
+ * registers
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ cygnus_gpio->io_ctrl = devm_ioremap_resource(dev, res);
+ if (IS_ERR(cygnus_gpio->io_ctrl)) {
+ dev_err(&pdev->dev, "unable to map I/O memory\n");
+ return PTR_ERR(cygnus_gpio->io_ctrl);
+ }
+ }
+
+ spin_lock_init(&cygnus_gpio->lock);
+
+ gc = &cygnus_gpio->gc;
+ gc->base = gpio_base_index;
+ gpio_base_index += ngpios;
+ gc->ngpio = ngpios;
+ gc->label = dev_name(dev);
+ gc->dev = dev;
+#ifdef CONFIG_OF_GPIO
+ gc->of_node = dev->of_node;
+ gc->of_gpio_n_cells = 2;
+ gc->of_xlate = bcm_cygnus_gpio_of_xlate;
+#endif
+ gc->direction_input = bcm_cygnus_gpio_direction_input;
+ gc->direction_output = bcm_cygnus_gpio_direction_output;
+ gc->set = bcm_cygnus_gpio_set;
+ gc->get = bcm_cygnus_gpio_get;
+ gc->to_irq = bcm_cygnus_gpio_to_irq;
+
+ ret = gpiochip_add(gc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to add GPIO chip\n");
+ goto err_dec_gpio_base;
+ }
+
+ /*
+ * Some of the GPIO interfaces do not have interrupt wired to the main
+ * processor
+ */
+ if (of_find_property(dev->of_node, "no-interrupt", NULL))
+ return 0;
+
+ cygnus_gpio->irq_domain = irq_domain_add_linear(dev->of_node,
+ gc->ngpio, &bcm_cygnus_irq_ops, cygnus_gpio);
+ if (!cygnus_gpio->irq_domain) {
+ dev_err(&pdev->dev, "unable to allocate IRQ domain\n");
+ ret = -ENXIO;
+ goto err_rm_gpiochip;
+ }
+
+ cygnus_gpio->irq = platform_get_irq(pdev, 0);
+ if (cygnus_gpio->irq < 0) {
+ dev_err(&pdev->dev, "unable to get IRQ\n");
+ ret = cygnus_gpio->irq;
+ goto err_rm_irq_domain;
+ }
+
+ for (i = 0; i < gc->ngpio; i++) {
+ int irq = irq_create_mapping(cygnus_gpio->irq_domain, i);
+
+ irq_set_lockdep_class(irq, &gpio_lock_class);
+ irq_set_chip_data(irq, cygnus_gpio);
+ irq_set_chip_and_handler(irq, &bcm_cygnus_gpio_irq_chip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+ }
+
+ irq_set_chained_handler(cygnus_gpio->irq, bcm_cygnus_gpio_irq_handler);
+ irq_set_handler_data(cygnus_gpio->irq, cygnus_gpio);
+
+ return 0;
+
+err_rm_irq_domain:
+ irq_domain_remove(cygnus_gpio->irq_domain);
+
+err_rm_gpiochip:
+ gpiochip_remove(gc);
+
+err_dec_gpio_base:
+ gpio_base_index -= ngpios;
+ return ret;
+}
+
+static struct platform_driver bcm_cygnus_gpio_driver = {
+ .driver = {
+ .name = "bcm-cygnus-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = bcm_cygnus_gpio_of_match,
+ },
+ .probe = bcm_cygnus_gpio_probe,
+};
+
+module_platform_driver(bcm_cygnus_gpio_driver);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Cygnus GPIO Driver");
+MODULE_LICENSE("GPL v2");

Ray Jui

unread,
Dec 5, 2014, 7:40:06 PM12/5/14
to
Enable GPIO driver for Broadcom Cygnus SoC by selecting GPIO_BCM_CYGNUS

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..5066d5d 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select GPIO_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Dec 5, 2014, 7:40:06 PM12/5/14
to
This enables all 3 GPIO controllers including the ASIU GPIO, the
chipcommonG GPIO, and the ALWAYS-ON GPIO, for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..c7587c1 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,36 @@

/include/ "bcm-cygnus-clock.dtsi"

+ gpio_ccm: gpio@1800a000 {
+ compatible = "brcm,cygnus-ccm-gpio";
+ reg = <0x1800a000 0x50>,
+ <0x0301d164 0x20>;
+ ngpios = <24>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ };
+
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-asiu-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-crmu-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-interrupt;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Dec 5, 2014, 7:40:05 PM12/5/14
to
This patchset contains the initial GPIO support for the Broadcom Cygnus SoC.
Cygnus has 3 GPIO controllers: 1) the ASIU GPIO; 2) the chipCommonG GPIO;
and 3) the ALWAYS-ON GPIO. All 3 types of GPIO controllers are supported by
the same Cygnus GPIO driver

Ray Jui (5):
gpio: Cygnus: define Broadcom Cygnus GPIO binding
gpio: Cygnus: add GPIO driver
ARM: mach-bcm: Enable GPIO support for Cygnus
ARM: dts: enable GPIO for Broadcom Cygnus
MAINTAINERS: Entry for Cygnus GPIO driver

.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 85 +++
MAINTAINERS | 7 +
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 719 ++++++++++++++++++++
7 files changed, 854 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

Joe Perches

unread,
Dec 5, 2014, 8:30:06 PM12/5/14
to
On Fri, 2014-12-05 at 16:40 -0800, Ray Jui wrote:
> This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
> SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller
> ("brcm,cygnus-asiu-gpio"), 2) the chipCommonG GPIO controller
> ("brcm,cygnus-ccm-gpio"), and 3) the ALWAYS-ON GPIO controller
> ("brcm,cygnus-crmu-gpio")

trivia:

> diff --git a/drivers/gpio/gpio-bcm-cygnus.c b/drivers/gpio/gpio-bcm-cygnus.c

> +static inline struct bcm_cygnus_gpio *to_bcm_cygnus_gpio(
> + struct gpio_chip *gc)
> +{
> + return container_of(gc, struct bcm_cygnus_gpio, gc);
> +}

Probably all of these inlines can just be static.

The compiler does a pretty good job these days
of inlining where appropriate.


> +static void bcm_cygnus_gpio_irq_handler(unsigned int irq,
> + struct irq_desc *desc)
> +{
> + struct bcm_cygnus_gpio *cygnus_gpio;
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + int i, bit;
> +
> + chained_irq_enter(chip, desc);
> +
> + cygnus_gpio = irq_get_handler_data(irq);
> +
> + /* go through the entire GPIO banks and handle all interrupts */
> + for (i = 0; i < cygnus_gpio->num_banks; i++) {
> + unsigned long val = readl(cygnus_gpio->base +
> + (i * GPIO_BANK_SIZE) +
> + CYGNUS_GPIO_INT_MSTAT_OFFSET);
> + if (val) {

This if (val) and indentation isn't really necessary

> + for_each_set_bit(bit, &val, 32) {

for_each_set_bit will effectively do the if above.

32 bit only code?
otherwise isn't this endian unsafe?
> +static struct irq_chip bcm_cygnus_gpio_irq_chip = {
> + .name = "bcm-cygnus-gpio",
> + .irq_ack = bcm_cygnus_gpio_irq_ack,
> + .irq_mask = bcm_cygnus_gpio_irq_mask,
> + .irq_unmask = bcm_cygnus_gpio_irq_unmask,
> + .irq_set_type = bcm_cygnus_gpio_irq_set_type,
> +};

const?

> +static struct irq_domain_ops bcm_cygnus_irq_ops = {
> + .map = bcm_cygnus_gpio_irq_map,
> + .unmap = bcm_cygnus_gpio_irq_unmap,
> + .xlate = irq_domain_xlate_twocell,
> +};

const here too?

> +#ifdef CONFIG_OF_GPIO
> +static void bcm_cygnus_gpio_set_pull(struct bcm_cygnus_gpio *cygnus_gpio,
> + unsigned gpio, enum gpio_pull pull)
> +{
> + unsigned int offset, shift;
> + u32 val, up;

bool up; ?

> + unsigned long flags;
> +
> + switch (pull) {
> + case GPIO_PULL_NONE:
> + return;
> + case GPIO_PULL_UP:
> + up = 1;
> + break;
> + case GPIO_PULL_DOWN:
> + up = 0;
> + break;
> + case GPIO_PULL_INVALID:
> + default:
> + return;
> + }

Maybe more sensible to group GPIO_PULL_NONE with GPIO_PULL_INVALID


> +static int bcm_cygnus_gpio_probe(struct platform_device *pdev)
> +{
[]
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_err(&pdev->dev, "unable to get I/O resource");

missing newline

Ray Jui

unread,
Dec 5, 2014, 9:20:06 PM12/5/14
to
Thanks for your review, Joe:

On 12/5/2014 5:28 PM, Joe Perches wrote:
> On Fri, 2014-12-05 at 16:40 -0800, Ray Jui wrote:
>> This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
>> SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller
>> ("brcm,cygnus-asiu-gpio"), 2) the chipCommonG GPIO controller
>> ("brcm,cygnus-ccm-gpio"), and 3) the ALWAYS-ON GPIO controller
>> ("brcm,cygnus-crmu-gpio")
>
> trivia:
>
>> diff --git a/drivers/gpio/gpio-bcm-cygnus.c b/drivers/gpio/gpio-bcm-cygnus.c
>
>> +static inline struct bcm_cygnus_gpio *to_bcm_cygnus_gpio(
>> + struct gpio_chip *gc)
>> +{
>> + return container_of(gc, struct bcm_cygnus_gpio, gc);
>> +}
>
> Probably all of these inlines can just be static.
>
> The compiler does a pretty good job these days
> of inlining where appropriate.

Okay I can remove all inlines.

>
>
>> +static void bcm_cygnus_gpio_irq_handler(unsigned int irq,
>> + struct irq_desc *desc)
>> +{
>> + struct bcm_cygnus_gpio *cygnus_gpio;
>> + struct irq_chip *chip = irq_desc_get_chip(desc);
>> + int i, bit;
>> +
>> + chained_irq_enter(chip, desc);
>> +
>> + cygnus_gpio = irq_get_handler_data(irq);
>> +
>> + /* go through the entire GPIO banks and handle all interrupts */
>> + for (i = 0; i < cygnus_gpio->num_banks; i++) {
>> + unsigned long val = readl(cygnus_gpio->base +
>> + (i * GPIO_BANK_SIZE) +
>> + CYGNUS_GPIO_INT_MSTAT_OFFSET);
>> + if (val) {
>
> This if (val) and indentation isn't really necessary
>

Note for_each_set_bit in this case iterates 32 times searching for bits
that are set. By having the if (val) check here, it can potentially save
some of such processing in the ISR. I agree with you that it introduces
one extra indent here but I think it's required.

>> + for_each_set_bit(bit, &val, 32) {
>
> for_each_set_bit will effectively do the if above.
>
> 32 bit only code?
> otherwise isn't this endian unsafe?
>

Will change 'unsigned long val' to 'u32 val'.
Sure, will add const to bcm_cygnus_gpio_irq_chip

>> +static struct irq_domain_ops bcm_cygnus_irq_ops = {
>> + .map = bcm_cygnus_gpio_irq_map,
>> + .unmap = bcm_cygnus_gpio_irq_unmap,
>> + .xlate = irq_domain_xlate_twocell,
>> +};
>
> const here too?
>

Yes, will make bcm_cygnus_irq_ops const.

>> +#ifdef CONFIG_OF_GPIO
>> +static void bcm_cygnus_gpio_set_pull(struct bcm_cygnus_gpio *cygnus_gpio,
>> + unsigned gpio, enum gpio_pull pull)
>> +{
>> + unsigned int offset, shift;
>> + u32 val, up;
>
> bool up; ?
>

Okay I'll change the name from 'up' to 'pullup' to make it more readable.

>> + unsigned long flags;
>> +
>> + switch (pull) {
>> + case GPIO_PULL_NONE:
>> + return;
>> + case GPIO_PULL_UP:
>> + up = 1;
>> + break;
>> + case GPIO_PULL_DOWN:
>> + up = 0;
>> + break;
>> + case GPIO_PULL_INVALID:
>> + default:
>> + return;
>> + }
>
> Maybe more sensible to group GPIO_PULL_NONE with GPIO_PULL_INVALID
>
>

Good suggestion, will do the following:

case GPIO_PULL_NONE:
case GPIO_PULL_INVALID:
default:
return;

>> +static int bcm_cygnus_gpio_probe(struct platform_device *pdev)
>> +{
> []
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!res) {
>> + dev_err(&pdev->dev, "unable to get I/O resource");
>
> missing newline
>
>

Right, will fix it with dev_err(&pdev->dev, "unable to get I/O resource\n");

Joe Perches

unread,
Dec 5, 2014, 9:40:05 PM12/5/14
to
On Fri, 2014-12-05 at 18:14 -0800, Ray Jui wrote:
> On 12/5/2014 5:28 PM, Joe Perches wrote:
> > On Fri, 2014-12-05 at 16:40 -0800, Ray Jui wrote:
> >> +static void bcm_cygnus_gpio_irq_handler(unsigned int irq,
> >> + struct irq_desc *desc)
> >> +{
> >> + struct bcm_cygnus_gpio *cygnus_gpio;
> >> + struct irq_chip *chip = irq_desc_get_chip(desc);
> >> + int i, bit;
> >> +
> >> + chained_irq_enter(chip, desc);
> >> +
> >> + cygnus_gpio = irq_get_handler_data(irq);
> >> +
> >> + /* go through the entire GPIO banks and handle all interrupts */
> >> + for (i = 0; i < cygnus_gpio->num_banks; i++) {
> >> + unsigned long val = readl(cygnus_gpio->base +
> >> + (i * GPIO_BANK_SIZE) +
> >> + CYGNUS_GPIO_INT_MSTAT_OFFSET);
> >> + if (val) {
> >
> > This if (val) and indentation isn't really necessary
> >
>
> Note for_each_set_bit in this case iterates 32 times searching for bits
> that are set.

No it doesn't.

#define for_each_set_bit(bit, addr, size) \
for ((bit) = find_first_bit((addr), (size)); \
(bit) < (size); \
(bit) = find_next_bit((addr), (size), (bit) + 1))

find_first_bit:

* Returns the bit number of the first set bit.
* If no bits are set, returns @size.

> By having the if (val) check here, it can potentially save
> some of such processing in the ISR. I agree with you that it introduces
> one extra indent here but I think it's required.
>
> >> + for_each_set_bit(bit, &val, 32) {
> >
> > for_each_set_bit will effectively do the if above.
> >
> > 32 bit only code?
> > otherwise isn't this endian unsafe?
> >
>
> Will change 'unsigned long val' to 'u32 val'.

All the bit operations only work on long *

Ray Jui

unread,
Dec 5, 2014, 10:50:05 PM12/5/14
to
You are right. I reviewed for_each_set_bit but didn't notice
find_next_bit may simply return 32 in our case without doing any
iterative processing. I will get rid of the redundant if (val) check below.

>> By having the if (val) check here, it can potentially save
>> some of such processing in the ISR. I agree with you that it introduces
>> one extra indent here but I think it's required.
>>
>>>> + for_each_set_bit(bit, &val, 32) {
>>>
>>> for_each_set_bit will effectively do the if above.
>>>
>>> 32 bit only code?
>>> otherwise isn't this endian unsafe?
>>>
>>
>> Will change 'unsigned long val' to 'u32 val'.
>
> All the bit operations only work on long *
>
>

Actually, by reviewing the code more deeply, I'm not sure why using
for_each_set_bit here is 'endian unsafe'. Isn't that already taken care
of by macros in bitops.h? Sorry if I'm still missing something here...

Joe Perches

unread,
Dec 5, 2014, 11:30:05 PM12/5/14
to
On Fri, 2014-12-05 at 19:41 -0800, Ray Jui wrote:
> On 12/5/2014 6:34 PM, Joe Perches wrote:
> > On Fri, 2014-12-05 at 18:14 -0800, Ray Jui wrote:
> >> On 12/5/2014 5:28 PM, Joe Perches wrote:
> >>> On Fri, 2014-12-05 at 16:40 -0800, Ray Jui wrote:
> >>>> + for_each_set_bit(bit, &val, 32) {
[]
> Actually, by reviewing the code more deeply, I'm not sure why using
> for_each_set_bit here is 'endian unsafe'.

It's not. The 32 confused me as it was long
and sizeof(long) isn't necessarily 32.

Maybe the 32 should be a #define

Tim Kryger

unread,
Dec 6, 2014, 5:30:06 PM12/6/14
to
It may be best to move the above change into its own commit.
> _______________________________________________
> linux-arm-kernel mailing list
> linux-ar...@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Ray Jui

unread,
Dec 7, 2014, 8:40:06 PM12/7/14
to


On 12/5/2014 8:24 PM, Joe Perches wrote:
> On Fri, 2014-12-05 at 19:41 -0800, Ray Jui wrote:
>> On 12/5/2014 6:34 PM, Joe Perches wrote:
>>> On Fri, 2014-12-05 at 18:14 -0800, Ray Jui wrote:
>>>> On 12/5/2014 5:28 PM, Joe Perches wrote:
>>>>> On Fri, 2014-12-05 at 16:40 -0800, Ray Jui wrote:
>>>>>> + for_each_set_bit(bit, &val, 32) {
> []
>> Actually, by reviewing the code more deeply, I'm not sure why using
>> for_each_set_bit here is 'endian unsafe'.
>
> It's not. The 32 confused me as it was long
> and sizeof(long) isn't necessarily 32.
>
> Maybe the 32 should be a #define
>
>
>
Okay, to improve readability, I will change 32 to NGPIOS_PER_BANK.

Ray Jui

unread,
Dec 7, 2014, 8:40:06 PM12/7/14
to
Okay. Will make this change along with other changes if required. Still
waiting for more code review comments at this point.

Ray Jui

unread,
Dec 7, 2014, 9:10:05 PM12/7/14
to


On 12/5/2014 6:14 PM, Ray Jui wrote:
>>> +static struct irq_chip bcm_cygnus_gpio_irq_chip = {
>>> + .name = "bcm-cygnus-gpio",
>>> + .irq_ack = bcm_cygnus_gpio_irq_ack,
>>> + .irq_mask = bcm_cygnus_gpio_irq_mask,
>>> + .irq_unmask = bcm_cygnus_gpio_irq_unmask,
>>> + .irq_set_type = bcm_cygnus_gpio_irq_set_type,
>>> +};
>>
>> const?
>>
>
>
> Sure, will add const to bcm_cygnus_gpio_irq_chip
>
>>> +static struct irq_domain_ops bcm_cygnus_irq_ops = {
>>> + .map = bcm_cygnus_gpio_irq_map,
>>> + .unmap = bcm_cygnus_gpio_irq_unmap,
>>> + .xlate = irq_domain_xlate_twocell,
>>> +};
>>
>> const here too?
>>
>
> Yes, will make bcm_cygnus_irq_ops const.
>
Actually, I cannot make them const here. Note they are passed into other
APIs which can potentially modifies their values internally.

drivers/gpio/gpio-bcm-cygnus.c: In function ‘bcm_cygnus_gpio_irq_map’:
drivers/gpio/gpio-bcm-cygnus.c:430:4: warning: passing argument 2 of
‘irq_set_chip_and_handler’ discards ‘const’ qualifier from pointer
target type [enabled by default]
handle_simple_irq);
^
In file included from drivers/gpio/gpio-bcm-cygnus.c:17:0:
include/linux/irq.h:461:20: note: expected ‘struct irq_chip *’ but
argument is of type ‘const struct irq_chip *’
static inline void irq_set_chip_and_handler(unsigned int irq, struct
irq_chip *chip,
^
drivers/gpio/gpio-bcm-cygnus.c: In function ‘bcm_cygnus_gpio_probe’:
drivers/gpio/gpio-bcm-cygnus.c:679:5: warning: passing argument 2 of
‘irq_set_chip_and_handler’ discards ‘const’ qualifier from pointer
target type [enabled by default]
handle_simple_irq);
^
In file included from drivers/gpio/gpio-bcm-cygnus.c:17:0:
include/linux/irq.h:461:20: note: expected ‘struct irq_chip *’ but
argument is of type ‘const struct irq_chip *’
static inline void irq_set_chip_and_handler(unsigned int irq, struct
irq_chip *chip,

Ray Jui

unread,
Dec 7, 2014, 9:40:05 PM12/7/14
to
This patchset contains the initial GPIO support for the Broadcom Cygnus SoC.
Cygnus has 3 GPIO controllers: 1) the ASIU GPIO; 2) the chipCommonG GPIO;
and 3) the ALWAYS-ON GPIO. All 3 types of GPIO controllers are supported by
the same Cygnus GPIO driver

Changes from v1:
- Get rid of inline qualifier
- Get rid of redundant check in the ISR
- Other minor fixes to imrove code readability

Ray Jui (5):
gpio: Cygnus: define Broadcom Cygnus GPIO binding
gpio: Cygnus: add GPIO driver
ARM: mach-bcm: Enable GPIO support for Cygnus
ARM: dts: enable GPIO for Broadcom Cygnus
MAINTAINERS: Entry for Cygnus GPIO driver

.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 85 +++
MAINTAINERS | 7 +
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 712 ++++++++++++++++++++
7 files changed, 847 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

--
1.7.9.5

Ray Jui

unread,
Dec 7, 2014, 9:40:05 PM12/7/14
to
Document the GPIO device tree binding for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 85 ++++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt

diff --git a/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
new file mode 100644
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-asiu-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-crmu-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-interrupt;
+ };
+
+ /*
+ * Touchscreen that uses the ASIU GPIO 100, with internal pull-up
+ * enabled
+ */
+ tsc {
+ ...
+ ...
+ gpio-event = <&gpio_asiu 100 0x10000>;
+ };
+
+ /* Bluetooth that uses the CRMU GPIO 2, with polarity inverted */
+ bluetooth {
+ ...
+ ...
+ bcm,rfkill-bank-sel = <&gpio_crmu 2 1>
+ }

Ray Jui

unread,
Dec 7, 2014, 9:40:05 PM12/7/14
to
This enables all 3 GPIO controllers including the ASIU GPIO, the
chipcommonG GPIO, and the ALWAYS-ON GPIO, for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..c7587c1 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,36 @@

/include/ "bcm-cygnus-clock.dtsi"

+ gpio_ccm: gpio@1800a000 {
+ compatible = "brcm,cygnus-ccm-gpio";
+ reg = <0x1800a000 0x50>,
+ <0x0301d164 0x20>;
+ ngpios = <24>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ };
+
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-asiu-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-crmu-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-interrupt;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Dec 7, 2014, 9:40:06 PM12/7/14
to
Signed-off-by: Ray Jui <rj...@broadcom.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e6bff3a..8473422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2202,6 +2202,13 @@ N: bcm9583*
N: bcm583*
N: bcm113*

+BROADCOM CYGNUS GPIO DRIVER
+M: Ray Jui <rj...@broadcom.com>
+L: bcm-kernel-f...@broadcom.com
+S: Supported
+F: drivers/gpio/gpio-bcm-cygnus.c
+F: Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
+
BROADCOM KONA GPIO DRIVER
M: Ray Jui <rj...@broadcom.com>
L: bcm-kernel-f...@broadcom.com

Ray Jui

unread,
Dec 7, 2014, 9:40:06 PM12/7/14
to
Enable GPIO driver for Broadcom Cygnus SoC by selecting GPIO_BCM_CYGNUS

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..5066d5d 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select GPIO_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Dec 7, 2014, 9:40:06 PM12/7/14
to
This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller
("brcm,cygnus-asiu-gpio"), 2) the chipCommonG GPIO controller
("brcm,cygnus-ccm-gpio"), and 3) the ALWAYS-ON GPIO controller
("brcm,cygnus-crmu-gpio")

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 712 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 724 insertions(+)
new file mode 100644
index 0000000..873dce2
--- /dev/null
+++ b/drivers/gpio/gpio-bcm-cygnus.c
@@ -0,0 +1,712 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+};
+
+/*
+ * GPIO drive strength
+ */
+enum gpio_drv_strength {
+ GPIO_DRV_STRENGTH_2MA = 0,
+ GPIO_DRV_STRENGTH_4MA,
+ GPIO_DRV_STRENGTH_6MA,
+ GPIO_DRV_STRENGTH_8MA,
+ GPIO_DRV_STRENGTH_10MA,
+ GPIO_DRV_STRENGTH_12MA,
+ GPIO_DRV_STRENGTH_14MA,
+ GPIO_DRV_STRENGTH_16MA,
+ GPIO_DRV_STRENGTH_INVALID,
+};
+
+struct bcm_cygnus_gpio {
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *io_ctrl;
+ spinlock_t lock;
+ struct gpio_chip gc;
+ unsigned num_banks;
+ int irq;
+ struct irq_domain *irq_domain;
+};
+
+static unsigned int gpio_base_index;
+
+static struct bcm_cygnus_gpio *to_bcm_cygnus_gpio(struct gpio_chip *gc)
+{
+ return container_of(gc, struct bcm_cygnus_gpio, gc);
+}
+
+static int bcm_cygnus_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+
+ return irq_find_mapping(cygnus_gpio->irq_domain, offset);
+}
+
+static unsigned int __gpio_reg_offset(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio)
+{
+ return GPIO_BANK(gpio) * GPIO_BANK_SIZE;
+}
+
+static unsigned int __gpio_bitpos(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio)
+{
+ return GPIO_BIT(gpio);
+}
+
+static void bcm_cygnus_gpio_irq_handler(unsigned int irq,
+ struct irq_desc *desc)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int i, bit;
+
+ chained_irq_enter(chip, desc);
+
+ cygnus_gpio = irq_get_handler_data(irq);
+
+ /* go through the entire GPIO banks and handle all interrupts */
+ for (i = 0; i < cygnus_gpio->num_banks; i++) {
+ unsigned long val = readl(cygnus_gpio->base +
+ (i * GPIO_BANK_SIZE) +
+ CYGNUS_GPIO_INT_MSTAT_OFFSET);
+
+ for_each_set_bit(bit, &val, NGPIOS_PER_BANK) {
+ unsigned pin = NGPIOS_PER_BANK * i + bit;
+ int child_irq =
+ bcm_cygnus_gpio_to_irq(&cygnus_gpio->gc, pin);
+
+ /*
+ * Clear the interrupt before invoking the
+ * handler, so we do not leave any window
+ */
+ writel(1 << bit,
+ cygnus_gpio->base + (i * GPIO_BANK_SIZE) +
+ CYGNUS_GPIO_INT_CLR_OFFSET);
+
+ generic_handle_irq(child_irq);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void bcm_cygnus_gpio_irq_ack(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_CLR_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static void bcm_cygnus_gpio_irq_mask(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_MSK_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static void bcm_cygnus_gpio_irq_unmask(struct irq_data *d)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_MSK_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+}
+
+static int bcm_cygnus_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = irq_data_get_irq_chip_data(d);
+ unsigned gpio = d->hwirq;
+ unsigned int int_type, dual_edge, edge_lvl;
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ int_type = 0;
+ dual_edge = 0;
+ edge_lvl = 1;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ int_type = 0;
+ dual_edge = 0;
+ edge_lvl = 0;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ int_type = 0;
+ dual_edge = 1;
+ edge_lvl = 0;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ int_type = 1;
+ dual_edge = 0;
+ edge_lvl = 1;
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ int_type = 1;
+ dual_edge = 0;
+ edge_lvl = 0;
+ break;
+
+ default:
+ dev_err(cygnus_gpio->dev, "invalid GPIO irq type 0x%x\n", type);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_IN_TYPE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= int_type << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_DE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= dual_edge << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_INT_EDGE_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ val |= edge_lvl << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ return 0;
+}
+
+static struct irq_chip bcm_cygnus_gpio_irq_chip = {
+ .name = "bcm-cygnus-gpio",
+ .irq_ack = bcm_cygnus_gpio_irq_ack,
+ .irq_mask = bcm_cygnus_gpio_irq_mask,
+ .irq_unmask = bcm_cygnus_gpio_irq_unmask,
+ .irq_set_type = bcm_cygnus_gpio_irq_set_type,
+};
+
+static int bcm_cygnus_gpio_direction_input(struct gpio_chip *gc,
+ unsigned gpio)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_OUT_EN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+
+ return 0;
+}
+
+static int bcm_cygnus_gpio_direction_output(struct gpio_chip *gc,
+ unsigned gpio, int value)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_OUT_EN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u\n", gpio,
+ offset, shift);
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_OUT_OFFSET;
+
+ val = readl(cygnus_gpio->base + offset);
+ if (value)
+ val |= 1 << shift;
+ else
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev,
+ "gpio:%u offset:0x%04x shift:%u val:0x%08x\n",
+ gpio, offset, shift, val);
+
+ return 0;
+}
+
+static void bcm_cygnus_gpio_set(struct gpio_chip *gc, unsigned gpio,
+ int value)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_OUT_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ val = readl(cygnus_gpio->base + offset);
+ if (value)
+ val |= 1 << shift;
+ else
+ val &= ~(1 << shift);
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+
+ dev_dbg(cygnus_gpio->dev,
+ "gpio:%u offset:0x%04x shift:%u val:0x%08x\n",
+ gpio, offset, shift, val);
+}
+
+static int bcm_cygnus_gpio_get(struct gpio_chip *gc, unsigned gpio)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ unsigned int offset, shift;
+ u32 val;
+
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_DATA_IN_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = readl(cygnus_gpio->base + offset);
+ val = (val >> shift) & 1;
+
+ dev_dbg(cygnus_gpio->dev, "gpio:%u offset:0x%04x shift:%u val:%u\n",
+ gpio, offset, shift, val);
+
+ return val;
+}
+
+static struct lock_class_key gpio_lock_class;
+
+static int bcm_cygnus_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ int ret;
+
+ ret = irq_set_chip_data(irq, d->host_data);
+ if (ret < 0)
+ return ret;
+ irq_set_lockdep_class(irq, &gpio_lock_class);
+ irq_set_chip_and_handler(irq, &bcm_cygnus_gpio_irq_chip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+
+ return 0;
+}
+
+static void bcm_cygnus_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static struct irq_domain_ops bcm_cygnus_irq_ops = {
+ .map = bcm_cygnus_gpio_irq_map,
+ .unmap = bcm_cygnus_gpio_irq_unmap,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+#ifdef CONFIG_OF_GPIO
+static void bcm_cygnus_gpio_set_pull(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio, enum gpio_pull pull)
+{
+ unsigned int offset, shift;
+ u32 val, pullup;
+ unsigned long flags;
+
+ switch (pull) {
+ case GPIO_PULL_UP:
+ pullup = 1;
+ break;
+ case GPIO_PULL_DOWN:
+ pullup = 0;
+ break;
+ case GPIO_PULL_NONE:
+ case GPIO_PULL_INVALID:
+ default:
+ return;
+ }
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ /* set pull up/down */
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_PAD_RES_OFFSET;
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ val = readl(cygnus_gpio->base + offset);
+ val &= ~(1 << shift);
+ if (pullup)
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ /* enable pad */
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_RES_EN_OFFSET;
+ val = readl(cygnus_gpio->base + offset);
+ val |= 1 << shift;
+ writel(val, cygnus_gpio->base + offset);
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+}
+
+static void bcm_cygnus_gpio_set_strength(struct bcm_cygnus_gpio *cygnus_gpio,
+ unsigned gpio, enum gpio_drv_strength strength)
+{
+ struct device *dev = cygnus_gpio->dev;
+ void __iomem *base;
+ unsigned int i, offset, shift;
+ u32 val;
+ unsigned long flags;
+
+ if (of_device_is_compatible(dev->of_node, "brcm,cygnus-asiu-gpio")) {
+ base = cygnus_gpio->base;
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET;
+ } else if (of_device_is_compatible(dev->of_node,
+ "brcm,cygnus-ccm-gpio")) {
+ if (!cygnus_gpio->io_ctrl)
+ return;
+
+ base = cygnus_gpio->io_ctrl;
+ offset = CYGNUS_GPIO_CCM_DRV0_CTRL_OFFSET;
+ } else
+ return;
+
+ shift = __gpio_bitpos(cygnus_gpio, gpio);
+
+ spin_lock_irqsave(&cygnus_gpio->lock, flags);
+
+ for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) {
+ val = readl(base + offset);
+ val &= ~(1 << shift);
+ val |= ((strength >> i) & 0x1) << shift;
+ writel(val, base + offset);
+ offset += 4;
+ }
+
+ spin_unlock_irqrestore(&cygnus_gpio->lock, flags);
+}
+
+static int bcm_cygnus_gpio_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec, u32 *flags)
+{
+ struct bcm_cygnus_gpio *cygnus_gpio = to_bcm_cygnus_gpio(gc);
+ enum gpio_pull pull;
+ enum gpio_drv_strength strength;
+
+ if (gc->of_gpio_n_cells < 2)
+ return -EINVAL;
+
+ if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells))
+ return -EINVAL;
+
+ if (gpiospec->args[0] >= gc->ngpio)
+ return -EINVAL;
+
+ pull = (gpiospec->args[1] >> GPIO_PULL_BIT_SHIFT) & GPIO_PULL_BIT_MASK;
+ if (WARN_ON(pull >= GPIO_PULL_INVALID))
+ return -EINVAL;
+
+ strength = (gpiospec->args[1] >> GPIO_DRV_STRENGTH_BIT_SHIFT) &
+ GPIO_DRV_STRENGTH_BIT_MASK;
+
+ if (flags)
+ *flags = gpiospec->args[1] & GPIO_FLAG_BIT_MASK;
+
+ bcm_cygnus_gpio_set_pull(cygnus_gpio, gpiospec->args[0], pull);
+ bcm_cygnus_gpio_set_strength(cygnus_gpio, gpiospec->args[0], strength);
+
+ return gpiospec->args[0];
+}
+#endif
+
+static const struct of_device_id bcm_cygnus_gpio_of_match[] = {
+ { .compatible = "brcm,cygnus-crmu-gpio" },
+ { .compatible = "brcm,cygnus-asiu-gpio" },
+ { .compatible = "brcm,cygnus-ccm-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bcm_cygnus_gpio_of_match);
+
+static int bcm_cygnus_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct resource *res;
+ struct bcm_cygnus_gpio *cygnus_gpio;
+ struct gpio_chip *gc;
+ u32 i, ngpios;
+ int ret;
+
+ match = of_match_device(bcm_cygnus_gpio_of_match, dev);
+ if (!match) {
+ dev_err(&pdev->dev, "failed to find GPIO controller\n");
+ return -ENODEV;
+ }
+
+ cygnus_gpio = devm_kzalloc(dev, sizeof(*cygnus_gpio), GFP_KERNEL);
+ if (!cygnus_gpio)
+ return -ENOMEM;
+
+ cygnus_gpio->dev = dev;
+ platform_set_drvdata(pdev, cygnus_gpio);
+
+ if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) {
+ dev_err(&pdev->dev, "missing ngpios device tree property\n");
+ return -ENODEV;
+ }
+ cygnus_gpio->num_banks = (ngpios + NGPIOS_PER_BANK - 1) /
+ NGPIOS_PER_BANK;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "unable to get I/O resource\n");
+ return -ENODEV;
+ }
+
+ cygnus_gpio->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(cygnus_gpio->base)) {
+ dev_err(&pdev->dev, "unable to map I/O memory\n");
+ return PTR_ERR(cygnus_gpio->base);
+ }
+
+ /*
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to add GPIO chip\n");
+ goto err_dec_gpio_base;
+ }
+
+ /*
+ * Some of the GPIO interfaces do not have interrupt wired to the main
+ * processor
+ */
+ if (of_find_property(dev->of_node, "no-interrupt", NULL))
+ return 0;
+
+ cygnus_gpio->irq_domain = irq_domain_add_linear(dev->of_node,
+ gc->ngpio, &bcm_cygnus_irq_ops, cygnus_gpio);
+ if (!cygnus_gpio->irq_domain) {
+ dev_err(&pdev->dev, "unable to allocate IRQ domain\n");
+ ret = -ENXIO;
+ goto err_rm_gpiochip;
+ }
+
+ cygnus_gpio->irq = platform_get_irq(pdev, 0);
+ if (cygnus_gpio->irq < 0) {
+ dev_err(&pdev->dev, "unable to get IRQ\n");
+ ret = cygnus_gpio->irq;
+ goto err_rm_irq_domain;
+ }
+
+ for (i = 0; i < gc->ngpio; i++) {
+ int irq = irq_create_mapping(cygnus_gpio->irq_domain, i);
+
+ irq_set_lockdep_class(irq, &gpio_lock_class);
+ irq_set_chip_data(irq, cygnus_gpio);
+ irq_set_chip_and_handler(irq, &bcm_cygnus_gpio_irq_chip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+ }
+
+ irq_set_chained_handler(cygnus_gpio->irq, bcm_cygnus_gpio_irq_handler);
+ irq_set_handler_data(cygnus_gpio->irq, cygnus_gpio);
+
+ return 0;
+
+err_rm_irq_domain:
+ irq_domain_remove(cygnus_gpio->irq_domain);
+
+err_rm_gpiochip:
+ gpiochip_remove(gc);
+
+err_dec_gpio_base:
+ gpio_base_index -= ngpios;
+ return ret;
+}
+
+static struct platform_driver bcm_cygnus_gpio_driver = {
+ .driver = {
+ .name = "bcm-cygnus-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = bcm_cygnus_gpio_of_match,
+ },
+ .probe = bcm_cygnus_gpio_probe,
+};
+
+module_platform_driver(bcm_cygnus_gpio_driver);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Cygnus GPIO Driver");
+MODULE_LICENSE("GPL v2");

Arnd Bergmann

unread,
Dec 8, 2014, 6:30:11 AM12/8/14
to
On Sunday 07 December 2014 18:38:32 Ray Jui wrote:
> +Required properties:
> +
> +- compatible:
> + Currently supported Cygnus GPIO controllers include:
> + "brcm,cygnus-ccm-gpio": ChipcommonG GPIO controller
> + "brcm,cygnus-asiu-gpio": ASIU GPIO controller
> + "brcm,cygnus-crmu-gpio": CRMU GPIO controller

How different are these? If they are almost the same, would it
be better to use the same compatible string for all of them and
describe the differences in extra properties?

If they are rather different, maybe you should have a separate
binding and driver for each?

Arnd

Ray Jui

unread,
Dec 8, 2014, 12:00:06 PM12/8/14
to


On 12/8/2014 3:22 AM, Arnd Bergmann wrote:
> On Sunday 07 December 2014 18:38:32 Ray Jui wrote:
>> +Required properties:
>> +
>> +- compatible:
>> + Currently supported Cygnus GPIO controllers include:
>> + "brcm,cygnus-ccm-gpio": ChipcommonG GPIO controller
>> + "brcm,cygnus-asiu-gpio": ASIU GPIO controller
>> + "brcm,cygnus-crmu-gpio": CRMU GPIO controller
>
> How different are these? If they are almost the same, would it
> be better to use the same compatible string for all of them and
> describe the differences in extra properties?
>
> If they are rather different, maybe you should have a separate
> binding and driver for each?
>
> Arnd
>
They are quite similar with the following minor differences:
1) ChipcommonG GPIO controller uses a separate register block
(0x0301d164) to control drive stregnth
2) Cannot control drive strength for the CMRU GPIO
3) CRMU GPIO controller's interrupt is not connected to GIC of A9.
Currently that's taken care of by using a "no-interrupt" device tree
property

I can change to use the common compatible string "brcm,cygnus-gpio".
With an introduction of property "no-drv-stregnth" which should be set
for CRMU GPIO controller. For ChipcommonG GPIO, it will have a second
register block defined, so we'll know to use that second register block
for drive strength configuration. For the rest, we assume normal drive
strength configuration (i.e., ASIU in our case).

Looking at this again, it looks like the "no-interrupt" property is
really redundant. For GPIO controller without interrupt connected to A9,
we can simply leave its interrupt properties not defined. I'll get rid
of it along with the above changes.

Arnd Bergmann

unread,
Dec 8, 2014, 12:20:05 PM12/8/14
to
On Monday 08 December 2014 08:55:20 Ray Jui wrote:
>
> On 12/8/2014 3:22 AM, Arnd Bergmann wrote:
> > On Sunday 07 December 2014 18:38:32 Ray Jui wrote:
> >> +Required properties:
> >> +
> >> +- compatible:
> >> + Currently supported Cygnus GPIO controllers include:
> >> + "brcm,cygnus-ccm-gpio": ChipcommonG GPIO controller
> >> + "brcm,cygnus-asiu-gpio": ASIU GPIO controller
> >> + "brcm,cygnus-crmu-gpio": CRMU GPIO controller
> >
> > How different are these? If they are almost the same, would it
> > be better to use the same compatible string for all of them and
> > describe the differences in extra properties?
> >
> > If they are rather different, maybe you should have a separate
> > binding and driver for each?
> >
> > Arnd
> >
> They are quite similar with the following minor differences:
> 1) ChipcommonG GPIO controller uses a separate register block
> (0x0301d164) to control drive stregnth
> 2) Cannot control drive strength for the CMRU GPIO

This can be deducted from having one or two register blocks I
assume.

> 3) CRMU GPIO controller's interrupt is not connected to GIC of A9.
> Currently that's taken care of by using a "no-interrupt" device tree
> property

No need for this property even, just see if there is an "interrupts"
property or not.

> I can change to use the common compatible string "brcm,cygnus-gpio".
> With an introduction of property "no-drv-stregnth" which should be set
> for CRMU GPIO controller.

Ok.

> For ChipcommonG GPIO, it will have a second
> register block defined, so we'll know to use that second register block
> for drive strength configuration. For the rest, we assume normal drive
> strength configuration (i.e., ASIU in our case).

Maybe see if something older than cygnus was already using a compatible
gpio controller and then use the name of that.

> Looking at this again, it looks like the "no-interrupt" property is
> really redundant. For GPIO controller without interrupt connected to A9,
> we can simply leave its interrupt properties not defined. I'll get rid
> of it along with the above changes.

Right.

Arnd

Ray Jui

unread,
Dec 8, 2014, 1:50:05 PM12/8/14
to
Enable GPIO driver for Broadcom Cygnus SoC by selecting GPIO_BCM_CYGNUS

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..5066d5d 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select GPIO_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:
--
1.7.9.5

Ray Jui

unread,
Dec 8, 2014, 1:50:06 PM12/8/14
to
Document the GPIO device tree binding for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 82 ++++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt

diff --git a/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
new file mode 100644
index 0000000..c477271
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
@@ -0,0 +1,82 @@
+Broadcom Cygnus GPIO Controller
+
+Required properties:
+
+- compatible:
+ Must be "brcm,cygnus-gpio"
+- no-drv-stregnth:
+ Specifies the GPIO controller does not support drive strength configuration
+
+Example:
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-crmu-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-drv-stregnth;
+ };
+
+ /*
+ * Touchscreen that uses the ASIU GPIO 100, with internal pull-up
+ * enabled
+ */
+ tsc {
+ ...
+ ...
+ gpio-event = <&gpio_asiu 100 0x10000>;
+ };
+
+ /* Bluetooth that uses the CRMU GPIO 2, with polarity inverted */
+ bluetooth {
+ ...
+ ...
+ bcm,rfkill-bank-sel = <&gpio_crmu 2 1>
+ }

Ray Jui

unread,
Dec 8, 2014, 1:50:05 PM12/8/14
to
Sorry. Please ignore this particular cover letter. It accidentally got
sent along with other v3 patches.

On 12/8/2014 10:47 AM, Ray Jui wrote:
> This patchset contains the initial GPIO support for the Broadcom Cygnus SoC.
> Cygnus has 3 GPIO controllers: 1) the ASIU GPIO; 2) the chipCommonG GPIO;
> and 3) the ALWAYS-ON GPIO. All 3 types of GPIO controllers are supported by
> the same Cygnus GPIO driver
>
> Changes from v1:
> - Get rid of inline qualifier
> - Get rid of redundant check in the ISR
> - Other minor fixes to imrove code readability
>
> Ray Jui (5):
> gpio: Cygnus: define Broadcom Cygnus GPIO binding
> gpio: Cygnus: add GPIO driver
> ARM: mach-bcm: Enable GPIO support for Cygnus
> ARM: dts: enable GPIO for Broadcom Cygnus
> MAINTAINERS: Entry for Cygnus GPIO driver
>
> .../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 85 +++
> MAINTAINERS | 7 +
> arch/arm/boot/dts/bcm-cygnus.dtsi | 30 +
> arch/arm/mach-bcm/Kconfig | 1 +
> drivers/gpio/Kconfig | 11 +
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-bcm-cygnus.c | 712 ++++++++++++++++++++
> 7 files changed, 847 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
> create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

Ray Jui

unread,
Dec 8, 2014, 1:50:06 PM12/8/14
to
1.7.9.5

Ray Jui

unread,
Dec 8, 2014, 1:50:06 PM12/8/14
to
This enables all 3 GPIO controllers including the ASIU GPIO, the
chipcommonG GPIO, and the ALWAYS-ON GPIO, for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..48339d0 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,36 @@

/include/ "bcm-cygnus-clock.dtsi"

+ gpio_ccm: gpio@1800a000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x1800a000 0x50>,
+ <0x0301d164 0x20>;
+ ngpios = <24>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ };
+
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-drv-stregnth;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Dec 8, 2014, 1:50:06 PM12/8/14
to
Signed-off-by: Ray Jui <rj...@broadcom.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e6bff3a..8473422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2202,6 +2202,13 @@ N: bcm9583*
N: bcm583*
N: bcm113*

+BROADCOM CYGNUS GPIO DRIVER
+M: Ray Jui <rj...@broadcom.com>
+L: bcm-kernel-f...@broadcom.com
+S: Supported
+F: drivers/gpio/gpio-bcm-cygnus.c
+F: Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
+
BROADCOM KONA GPIO DRIVER
M: Ray Jui <rj...@broadcom.com>
L: bcm-kernel-f...@broadcom.com

Ray Jui

unread,
Dec 8, 2014, 1:50:07 PM12/8/14
to
This patchset contains the initial GPIO support for the Broadcom Cygnus SoC.
Cygnus has 3 GPIO controllers: 1) the ASIU GPIO; 2) the chipCommonG GPIO;
and 3) the ALWAYS-ON GPIO. All 3 types of GPIO controllers are supported by
the same Cygnus GPIO driver

Changes from v2:
- Consolidate different compatible IDs into "brcm,cygnus-gpio"
- Get rid of redundant "no-interrupt" property

Changes from v1:
- Get rid of inline qualifier
- Get rid of redundant check in the ISR
- Other minor fixes to imrove code readability

Ray Jui (5):
gpio: Cygnus: define Broadcom Cygnus GPIO binding
gpio: Cygnus: add GPIO driver
ARM: mach-bcm: Enable GPIO support for Cygnus
ARM: dts: enable GPIO for Broadcom Cygnus
MAINTAINERS: Entry for Cygnus GPIO driver

.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 82 +++
MAINTAINERS | 7 +
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 705 ++++++++++++++++++++
7 files changed, 837 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

Ray Jui

unread,
Dec 8, 2014, 1:50:16 PM12/8/14
to
This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller, 2) the
chipCommonG GPIO controller, and 3) the ALWAYS-ON GPIO controller

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 705 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 717 insertions(+)
new file mode 100644
index 0000000..f1f69ce
--- /dev/null
+++ b/drivers/gpio/gpio-bcm-cygnus.c
@@ -0,0 +1,705 @@
+
+ /*
+ /* some GPIO controllers do not support drive strength configuration */
+ if (of_find_property(dev->of_node, "no-drv-stregnth", NULL))
+ return;
+
+ if (cygnus_gpio->io_ctrl) {
+ base = cygnus_gpio->io_ctrl;
+ offset = CYGNUS_GPIO_CCM_DRV0_CTRL_OFFSET;
+ } else {
+ base = cygnus_gpio->base;
+ offset = __gpio_reg_offset(cygnus_gpio, gpio) +
+ CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET;
+ }
+ { .compatible = "brcm,cygnus-gpio" },
+ }
+
+ /*
+ }
+
+ /*
+ * Some of the GPIO interfaces do not have interrupt wired to the main
+ * processor
+ */
+ cygnus_gpio->irq = platform_get_irq(pdev, 0);
+ if (cygnus_gpio->irq < 0) {
+ ret = cygnus_gpio->irq;
+ if (ret == -EPROBE_DEFER)
+ goto err_rm_gpiochip;
+
+ dev_info(&pdev->dev, "no interrupt hook\n");
+ }
+
+ cygnus_gpio->irq_domain = irq_domain_add_linear(dev->of_node,
+ gc->ngpio, &bcm_cygnus_irq_ops, cygnus_gpio);
+ if (!cygnus_gpio->irq_domain) {
+ dev_err(&pdev->dev, "unable to allocate IRQ domain\n");
+ ret = -ENXIO;
+ goto err_rm_gpiochip;
+ }
+
+ for (i = 0; i < gc->ngpio; i++) {
+ int irq = irq_create_mapping(cygnus_gpio->irq_domain, i);
+
+ irq_set_lockdep_class(irq, &gpio_lock_class);
+ irq_set_chip_data(irq, cygnus_gpio);
+ irq_set_chip_and_handler(irq, &bcm_cygnus_gpio_irq_chip,
+ handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+ }
+
+ irq_set_chained_handler(cygnus_gpio->irq, bcm_cygnus_gpio_irq_handler);
+ irq_set_handler_data(cygnus_gpio->irq, cygnus_gpio);
+
+ return 0;
+
+err_rm_gpiochip:
+ gpiochip_remove(gc);
+
+err_dec_gpio_base:
+ gpio_base_index -= ngpios;
+ return ret;
+}
+
+static struct platform_driver bcm_cygnus_gpio_driver = {
+ .driver = {
+ .name = "bcm-cygnus-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = bcm_cygnus_gpio_of_match,
+ },
+ .probe = bcm_cygnus_gpio_probe,
+};
+
+module_platform_driver(bcm_cygnus_gpio_driver);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Cygnus GPIO Driver");
+MODULE_LICENSE("GPL v2");

Arnd Bergmann

unread,
Dec 8, 2014, 2:40:08 PM12/8/14
to
On Monday 08 December 2014 10:47:44 Ray Jui wrote:
> +
> +- no-drv-stregnth:
> + Specifies the GPIO controller does not support drive strength configuration
> +
>

Typo:

strength, not stregnth

Otherwise looks good.

Arnd

Ray Jui

unread,
Dec 8, 2014, 2:50:07 PM12/8/14
to


On 12/8/2014 11:38 AM, Arnd Bergmann wrote:
> On Monday 08 December 2014 10:47:44 Ray Jui wrote:
>> +
>> +- no-drv-stregnth:
>> + Specifies the GPIO controller does not support drive strength configuration
>> +
>>
>
> Typo:
>
> strength, not stregnth
>
> Otherwise looks good.
>
> Arnd
>
Right...Let me fix that. Also noticed the following in the device tree
binding example that needs to be fixed:
gpio_crmu: gpio@03024800 {

compatible = "brcm,cygnus-crmu-gpio";
The above line needs to be fixed with:
compatible = "brcm,cygnus-gpio";

reg = <0x03024800 0x50>;
ngpios = <6>;
#gpio-cells = <2>;
gpio-controller;
no-drv-stregnth;
};

Ray Jui

unread,
Dec 8, 2014, 3:40:05 PM12/8/14
to
This patchset contains the initial GPIO support for the Broadcom Cygnus SoC.
Cygnus has 3 GPIO controllers: 1) the ASIU GPIO; 2) the chipCommonG GPIO;
and 3) the ALWAYS-ON GPIO. All 3 types of GPIO controllers are supported by
the same Cygnus GPIO driver

Changes from v3:
- Fix dt property tpyo
- Fix incorrect GPIO compatible ID in device tree binding document example

Changes from v2:
- Consolidate different compatible IDs into "brcm,cygnus-gpio"
- Get rid of redundant "no-interrupt" property

Changes from v1:
- Get rid of inline qualifier
- Get rid of redundant check in the ISR
- Other minor fixes to imrove code readability

Ray Jui (5):
gpio: Cygnus: define Broadcom Cygnus GPIO binding
gpio: Cygnus: add GPIO driver
ARM: mach-bcm: Enable GPIO support for Cygnus
ARM: dts: enable GPIO for Broadcom Cygnus
MAINTAINERS: Entry for Cygnus GPIO driver

.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 82 +++
MAINTAINERS | 7 +
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-bcm-cygnus.c | 705 ++++++++++++++++++++
7 files changed, 837 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
create mode 100644 drivers/gpio/gpio-bcm-cygnus.c

--
1.7.9.5

Ray Jui

unread,
Dec 8, 2014, 3:50:05 PM12/8/14
to
Document the GPIO device tree binding for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/gpio/brcm,cygnus-gpio.txt | 82 ++++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt

diff --git a/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt b/Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
new file mode 100644
index 0000000..dca322a
+- no-drv-strength:
+ Specifies the GPIO controller does not support drive strength configuration
+
+Example:
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-drv-strength;
+ };
+
+ /*
+ * Touchscreen that uses the ASIU GPIO 100, with internal pull-up
+ * enabled
+ */
+ tsc {
+ ...
+ ...
+ gpio-event = <&gpio_asiu 100 0x10000>;
+ };
+
+ /* Bluetooth that uses the CRMU GPIO 2, with polarity inverted */
+ bluetooth {
+ ...
+ ...
+ bcm,rfkill-bank-sel = <&gpio_crmu 2 1>
+ }

Ray Jui

unread,
Dec 8, 2014, 3:50:05 PM12/8/14
to
Signed-off-by: Ray Jui <rj...@broadcom.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e6bff3a..8473422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2202,6 +2202,13 @@ N: bcm9583*
N: bcm583*
N: bcm113*

+BROADCOM CYGNUS GPIO DRIVER
+M: Ray Jui <rj...@broadcom.com>
+L: bcm-kernel-f...@broadcom.com
+S: Supported
+F: drivers/gpio/gpio-bcm-cygnus.c
+F: Documentation/devicetree/bindings/gpio/brcm,cygnus-gpio.txt
+
BROADCOM KONA GPIO DRIVER
M: Ray Jui <rj...@broadcom.com>
L: bcm-kernel-f...@broadcom.com

Ray Jui

unread,
Dec 8, 2014, 3:50:05 PM12/8/14
to
This enables all 3 GPIO controllers including the ASIU GPIO, the
chipcommonG GPIO, and the ALWAYS-ON GPIO, for Broadcom Cygnus SoC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..fbc8257 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -54,6 +54,36 @@

/include/ "bcm-cygnus-clock.dtsi"

+ gpio_ccm: gpio@1800a000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x1800a000 0x50>,
+ <0x0301d164 0x20>;
+ ngpios = <24>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ };
+
+ gpio_asiu: gpio@180a5000 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x180a5000 0x668>;
+ ngpios = <122>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ gpio_crmu: gpio@03024800 {
+ compatible = "brcm,cygnus-gpio";
+ reg = <0x03024800 0x50>;
+ ngpios = <6>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ no-drv-strength;
+ };
+
amba {
#address-cells = <1>;
#size-cells = <1>;

Ray Jui

unread,
Dec 8, 2014, 3:50:06 PM12/8/14
to
Enable GPIO driver for Broadcom Cygnus SoC by selecting GPIO_BCM_CYGNUS

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..5066d5d 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -29,6 +29,7 @@ config ARCH_BCM_IPROC
config ARCH_BCM_CYGNUS
bool "Broadcom Cygnus Support" if ARCH_MULTI_V7
select ARCH_BCM_IPROC
+ select GPIO_BCM_CYGNUS
help
Enable support for the Cygnus family,
which includes the following variants:

Ray Jui

unread,
Dec 8, 2014, 3:50:06 PM12/8/14
to
This GPIO driver supports all 3 GPIO controllers in the Broadcom Cygnus
SoC. The 3 GPIO controllers are 1) the ASIU GPIO controller, 2) the
chipCommonG GPIO controller, and 3) the ALWAYS-ON GPIO controller

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
new file mode 100644
index 0000000..4fd9b73
+
+ /*
+ if (of_find_property(dev->of_node, "no-drv-strength", NULL))
+ }
+
+ /*
+ }
+
+ /*

Ray Jui

unread,
Dec 9, 2014, 7:10:05 PM12/9/14
to
This patchset contains the initial PCIe support for Broadcom iProc family of
SoCs. This driver has been validated with Cygnus and NSP and is expected to
work on other iProc family of SoCs that deploy the same PCIe controller

Ray Jui (4):
pci: iProc: define Broadcom iProc PCIe binding
PCI: iproc: Add Broadcom iProc PCIe driver
ARM: mach-bcm: Enable PCIe support for iProc
ARM: dts: enable PCIe for Broadcom Cygnus

.../devicetree/bindings/pci/brcm,iproc-pcie.txt | 62 ++
arch/arm/boot/dts/bcm-cygnus.dtsi | 43 +
arch/arm/boot/dts/bcm958300k.dts | 8 +
arch/arm/mach-bcm/Kconfig | 1 +
drivers/pci/host/Kconfig | 9 +
drivers/pci/host/Makefile | 1 +
drivers/pci/host/pcie-iproc.c | 896 ++++++++++++++++++++
7 files changed, 1020 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pci/brcm,iproc-pcie.txt
create mode 100644 drivers/pci/host/pcie-iproc.c

Ray Jui

unread,
Dec 9, 2014, 7:10:05 PM12/9/14
to
Enable PCIe driver support for Broadcom iProc family of SoCs by
selecting PCIE_IPROC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..a13a0b2 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -19,6 +19,7 @@ config ARCH_BCM_IPROC
select ARCH_REQUIRE_GPIOLIB
select ARM_AMBA
select PINCTRL
+ select PCIE_IPROC
help
This enables support for systems based on Broadcom IPROC architected SoCs.
The IPROC complex contains one or more ARM CPUs along with common

Ray Jui

unread,
Dec 9, 2014, 7:10:06 PM12/9/14
to
Add PCIe device nodes and its properties in bcm-cygnus.dtsi but keep it
disabled there. Only enable it in bcm958300k.dts because PCIe interfaces
are only populated on that board

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/boot/dts/bcm-cygnus.dtsi | 43 +++++++++++++++++++++++++++++++++++++
arch/arm/boot/dts/bcm958300k.dts | 8 +++++++
2 files changed, 51 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 5126f9e..669bb3b 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -70,6 +70,49 @@
};
};

+ pcie0: pcie@18012000 {
+ compatible = "brcm,iproc-pcie";
+ reg = <0x18012000 0x1000>,
+ <0x18002000 0x1000>;
+ interrupts = <GIC_SPI 96 IRQ_TYPE_NONE>,
+ <GIC_SPI 97 IRQ_TYPE_NONE>,
+ <GIC_SPI 98 IRQ_TYPE_NONE>,
+ <GIC_SPI 99 IRQ_TYPE_NONE>,
+ <GIC_SPI 100 IRQ_TYPE_NONE>,
+ <GIC_SPI 101 IRQ_TYPE_NONE>;
+ bus-range = <0x00 0xFF>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges = <0x81000000 0 0 0x28000000 0 0x00010000
+ 0x82000000 0 0x20000000 0x20000000 0 0x04000000>;
+ phy-addr = <5>;
+ status = "disabled";
+ };
+
+ pcie1: pcie@18013000 {
+ compatible = "brcm,iproc-pcie";
+ reg = <0x18013000 0x1000>,
+ <0x18002000 0x1000>;
+
+ interrupts = <GIC_SPI 102 IRQ_TYPE_NONE>,
+ <GIC_SPI 103 IRQ_TYPE_NONE>,
+ <GIC_SPI 104 IRQ_TYPE_NONE>,
+ <GIC_SPI 105 IRQ_TYPE_NONE>,
+ <GIC_SPI 106 IRQ_TYPE_NONE>,
+ <GIC_SPI 107 IRQ_TYPE_NONE>;
+ bus-range = <0x00 0xFF>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges = <0x81000000 0 0 0x48000000 0 0x00010000
+ 0x82000000 0 0x40000000 0x40000000 0 0x04000000>;
+ phy-addr = <6>;
+ status = "disabled";
+ };
+
uart0: serial@18020000 {
compatible = "snps,dw-apb-uart";
reg = <0x18020000 0x100>;
diff --git a/arch/arm/boot/dts/bcm958300k.dts b/arch/arm/boot/dts/bcm958300k.dts
index f1bb36f..c9eb856 100644
--- a/arch/arm/boot/dts/bcm958300k.dts
+++ b/arch/arm/boot/dts/bcm958300k.dts
@@ -47,6 +47,14 @@
bootargs = "console=ttyS0,115200";
};

+ pcie0: pcie@18012000 {
+ status = "okay";
+ };
+
+ pcie1: pcie@18013000 {
+ status = "okay";
+ };
+
uart3: serial@18023000 {
status = "okay";
};

Ray Jui

unread,
Dec 9, 2014, 7:10:06 PM12/9/14
to
Add initial version of the Broadcom iProc PCIe driver. This driver
has been tested on NSP and Cygnus and is expected to work on all iProc
family of SoCs that deploys the same PCIe host controller

The driver also supports MSI

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/pci/host/Kconfig | 9 +
drivers/pci/host/Makefile | 1 +
drivers/pci/host/pcie-iproc.c | 896 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 906 insertions(+)
create mode 100644 drivers/pci/host/pcie-iproc.c

diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index c4b6568..22322e1 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -102,4 +102,13 @@ config PCI_LAYERSCAPE
help
Say Y here if you want PCIe controller support on Layerscape SoCs.

+config PCIE_IPROC
+ bool "Broadcom iProc PCIe controller"
+ depends on ARCH_BCM_IPROC
+ help
+ Say Y here if you want to enable the PCIe controller driver support
+ on Broadcom's iProc family of SoCs.
+
+ MSI is also supported in the driver.
+
endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 44c2699..1f5e9d2 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o
obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o
obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
+obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o
diff --git a/drivers/pci/host/pcie-iproc.c b/drivers/pci/host/pcie-iproc.c
new file mode 100644
index 0000000..bd9f01c
--- /dev/null
+++ b/drivers/pci/host/pcie-iproc.c
@@ -0,0 +1,896 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/msi.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/mbus.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_pci.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+/* max number of MSI event queues */
+#define MAX_MSI_EQ 6
+#define MAX_IRQS MAX_MSI_EQ
+
+#define MDIO_TIMEOUT_USEC 100
+
+#define OPCODE_WRITE 1
+#define OPCODE_READ 2
+
+#define MII_TA_VAL 2
+#define MII_MDCDIV 62
+
+#define MII_MGMT_CTRL_OFFSET 0x000
+#define MII_MGMT_CTRL_MDCDIV_SHIFT 0
+#define MII_MGMT_CTRL_PRE_SHIFT 7
+#define MII_MGMT_CTRL_BUSY_SHIFT 8
+#define MII_MGMT_CTRL_EXT_SHIFT 9
+#define MII_MGMT_CTRL_BTP_SHIFT 10
+
+#define MII_MGMT_CMD_DATA_OFFSET 0x004
+#define MII_MGMT_CMD_DATA_SHIFT 0
+#define MII_MGMT_CMD_TA_SHIFT 16
+#define MII_MGMT_CMD_RA_SHIFT 18
+#define MII_MGMT_CMD_PA_SHIFT 23
+#define MII_MGMT_CMD_OP_SHIFT 28
+#define MII_MGMT_CMD_SB_SHIFT 30
+#define MII_MGMT_CMD_DATA_MASK 0xFFFF
+
+#define CLK_CONTROL_OFFSET 0x000
+#define EP_PERST_SOURCE_SELECT_SHIFT 2
+#define EP_PERST_SOURCE_SELECT (1 << EP_PERST_SOURCE_SELECT_SHIFT)
+#define EP_MODE_SURVIVE_PERST_SHIFT 1
+#define EP_MODE_SURVIVE_PERST (1 << EP_MODE_SURVIVE_PERST_SHIFT)
+#define RC_PCIE_RST_OUTPUT_SHIFT 0
+#define RC_PCIE_RST_OUTPUT (1 << RC_PCIE_RST_OUTPUT_SHIFT)
+
+#define CFG_IND_ADDR_OFFSET 0x120
+#define CFG_IND_ADDR_MASK 0x00001FFC
+
+#define CFG_IND_DATA_OFFSET 0x124
+
+#define CFG_ADDR_OFFSET 0x1F8
+#define CFG_ADDR_BUS_NUM_SHIFT 20
+#define CFG_ADDR_BUS_NUM_MASK 0x0FF00000
+#define CFG_ADDR_DEV_NUM_SHIFT 15
+#define CFG_ADDR_DEV_NUM_MASK 0x000F8000
+#define CFG_ADDR_FUNC_NUM_SHIFT 12
+#define CFG_ADDR_FUNC_NUM_MASK 0x00007000
+#define CFG_ADDR_REG_NUM_SHIFT 2
+#define CFG_ADDR_REG_NUM_MASK 0x00000FFC
+#define CFG_ADDR_CFG_TYPE_SHIFT 0
+#define CFG_ADDR_CFG_TYPE_MASK 0x00000003
+
+#define CFG_DATA_OFFSET 0x1FC
+
+#define SYS_EQ_PAGE_OFFSET 0x200
+#define SYS_MSI_PAGE_OFFSET 0x204
+
+#define SYS_MSI_INTS_EN_OFFSET 0x208
+
+#define SYS_MSI_CTRL_0_OFFSET 0x210
+#define SYS_MSI_INTR_EN_SHIFT 11
+#define SYS_MSI_INTR_EN (1 << SYS_MSI_INTR_EN_SHIFT)
+#define SYS_MSI_INT_N_EVENT_SHIFT 1
+#define SYS_MSI_INT_N_EVENT (1 << SYS_MSI_INT_N_EVENT_SHIFT)
+#define SYS_MSI_EQ_EN_SHIFT 0
+#define SYS_MSI_EQ_EN (1 << SYS_MSI_EQ_EN_SHIFT)
+
+#define SYS_EQ_HEAD_0_OFFSET 0x250
+#define SYS_EQ_TAIL_0_OFFSET 0x254
+#define SYS_EQ_TAIL_0_MASK 0x3F
+
+#define SYS_RC_INTX_EN 0x330
+#define SYS_RC_INTX_MASK 0xF
+
+#define SYS_RC_INTX_CSR 0x334
+#define SYS_RC_INTX_MASK 0xF
+
+#define OARR_0_OFFSET 0xD20
+#define OAAR_0_ADDR_MASK 0xF0000000
+#define OAAR_0_VALID_SHIFT 0
+#define OAAR_0_VALID (1 << OAAR_0_VALID_SHIFT)
+#define OAAR_0_UPPER_OFFSET 0xD24
+#define OAAR_0_UPPER_ADDR_MASK 0x0000000F
+
+#define PCIE_SYS_RC_INTX_EN_OFFSET 0x330
+
+#define OMAP_0_LOWER_OFFSET 0xD40
+#define OMAP_0_LOWER_ADDR_MASK 0xF0000000
+#define OMAP_0_UPPER_OFFSET 0x0D44
+
+#define PCIE_LINK_STATUS_OFFSET 0xF0C
+#define PCIE_PHYLINKUP_SHITF 3
+#define PCIE_PHYLINKUP (1 << PCIE_PHYLINKUP_SHITF)
+
+#define STRAP_STATUS_OFFSET 0xF10
+#define STRAP_1LANE_SHIFT 2
+#define STRAP_1LANE (1 << STRAP_1LANE_SHIFT)
+#define STRAP_IF_ENABLE_SHIFT 1
+#define STRAP_IF_ENABLE (1 << STRAP_IF_ENABLE_SHIFT)
+#define STRAP_RC_MODE_SHIFT 0
+#define STRAP_RC_MODE (1 << STRAP_RC_MODE_SHIFT)
+
+struct iproc_pcie;
+
+/**
+ * iProc MSI
+ * @pcie: pointer to the iProc PCIe data structure
+ * @irq_in_use: bitmap of MSI IRQs that are in use
+ * @domain: MSI IRQ domain
+ * @chip: MSI controller
+ * @eq_page: memory page to store the iProc MSI event queue
+ * @msi_page: memory page for MSI posted writes
+ */
+struct iproc_msi {
+ struct iproc_pcie *pcie;
+ DECLARE_BITMAP(irq_in_use, MAX_IRQS);
+ struct irq_domain *domain;
+ struct msi_controller chip;
+ unsigned long eq_page;
+ unsigned long msi_page;
+};
+
+/**
+ * iProc PCIe
+ * @dev: pointer to the device
+ * @mii: MII/MDIO management I/O register base
+ * @reg: PCIe I/O register base
+ * @io: PCIe I/O resource
+ * @mem: PCIe memory resource
+ * @busn: PCIe bus resource
+ * @phy_addr: MIDO PHY address
+ * @irqs: Array that stores IRQs
+ * @msi: MSI related info
+ */
+struct iproc_pcie {
+ struct device *dev;
+
+ void __iomem *mii;
+ void __iomem *reg;
+
+ struct resource io;
+ struct resource mem;
+ struct resource busn;
+
+ u32 phy_addr;
+ int irqs[MAX_IRQS];
+
+ struct iproc_msi msi;
+};
+
+static inline int mdio_wait_idle(struct iproc_pcie *pcie)
+{
+ int timeout = MDIO_TIMEOUT_USEC;
+
+ while (readl(pcie->mii + MII_MGMT_CTRL_OFFSET) &
+ (1 << MII_MGMT_CTRL_BUSY_SHIFT)) {
+ udelay(1);
+ if (timeout-- <= 0)
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static void mdio_init(struct iproc_pcie *pcie)
+{
+ u32 val;
+
+ val = MII_MDCDIV << MII_MGMT_CTRL_MDCDIV_SHIFT;
+ val |= (1 << MII_MGMT_CTRL_PRE_SHIFT);
+ writel(val, pcie->mii + MII_MGMT_CTRL_OFFSET);
+
+ WARN_ON(mdio_wait_idle(pcie));
+}
+
+static u16 mdio_read(struct iproc_pcie *pcie, unsigned int phy_addr,
+ unsigned int reg_addr)
+{
+ u32 val;
+
+ WARN_ON(mdio_wait_idle(pcie));
+
+ val = (MII_TA_VAL << MII_MGMT_CMD_TA_SHIFT);
+ val |= (reg_addr << MII_MGMT_CMD_RA_SHIFT);
+ val |= (phy_addr << MII_MGMT_CMD_PA_SHIFT);
+ val |= (OPCODE_READ << MII_MGMT_CMD_OP_SHIFT);
+ val |= (1 << MII_MGMT_CMD_SB_SHIFT);
+ writel(val, pcie->mii + MII_MGMT_CMD_DATA_OFFSET);
+
+ WARN_ON(mdio_wait_idle(pcie));
+
+ val = readl(pcie->mii + MII_MGMT_CMD_DATA_OFFSET) &
+ MII_MGMT_CMD_DATA_MASK;
+
+ return (u16)val;
+}
+
+static void mdio_write(struct iproc_pcie *pcie, unsigned int phy_addr,
+ unsigned int reg_addr, u16 wr_data)
+{
+ u32 val;
+
+ WARN_ON(mdio_wait_idle(pcie));
+
+ val = (MII_TA_VAL << MII_MGMT_CMD_TA_SHIFT);
+ val |= (reg_addr << MII_MGMT_CMD_RA_SHIFT);
+ val |= (phy_addr << MII_MGMT_CMD_PA_SHIFT);
+ val |= (OPCODE_WRITE << MII_MGMT_CMD_OP_SHIFT);
+ val |= (1 << MII_MGMT_CMD_SB_SHIFT);
+ val |= ((u32)wr_data & MII_MGMT_CMD_DATA_MASK);
+ writel(val, pcie->mii + MII_MGMT_CMD_DATA_OFFSET);
+
+ WARN_ON(mdio_wait_idle(pcie));
+}
+
+#define PCIE_PHY_BLK_ADDR_OFFSET 0x1F
+#define PCIE_PHY_BLK_ADDR_MASK 0xFFF0
+#define PCIE_PHY_REG_ADDR_MASK 0xF
+static u16 iproc_pcie_phy_reg_read(struct iproc_pcie *pcie,
+ unsigned int phy_addr, unsigned int reg_addr)
+{
+ u16 val;
+
+ mdio_write(pcie, phy_addr, PCIE_PHY_BLK_ADDR_OFFSET,
+ reg_addr & PCIE_PHY_BLK_ADDR_MASK);
+ val = mdio_read(pcie, phy_addr, reg_addr & PCIE_PHY_REG_ADDR_MASK);
+
+ dev_dbg(pcie->dev, "phy rd: phy: 0x%0x reg: 0x%4x data: 0x%4x\n",
+ phy_addr, reg_addr, val);
+
+ return val;
+}
+
+static void iproc_pcie_phy_reg_write(struct iproc_pcie *pcie,
+ unsigned int phy_addr, unsigned int reg_addr, u16 val)
+{
+ mdio_write(pcie, phy_addr, PCIE_PHY_BLK_ADDR_OFFSET,
+ reg_addr & PCIE_PHY_BLK_ADDR_MASK);
+ mdio_write(pcie, phy_addr, reg_addr & PCIE_PHY_REG_ADDR_MASK, val);
+
+ dev_dbg(pcie->dev, "phy wr: phy: 0x%0x reg: 0x%4x data: 0x%4x\n",
+ phy_addr, reg_addr, val);
+}
+
+static inline struct iproc_pcie *sys_to_pcie(struct pci_sys_data *sys)
+{
+ return sys->private_data;
+}
+
+static void iproc_pcie_reset(struct iproc_pcie *pcie)
+{
+ u32 val;
+
+ /* send a downstream reset */
+ val = EP_MODE_SURVIVE_PERST | RC_PCIE_RST_OUTPUT;
+ writel(val, pcie->reg + CLK_CONTROL_OFFSET);
+ udelay(250);
+ val &= ~EP_MODE_SURVIVE_PERST;
+ writel(val, pcie->reg + CLK_CONTROL_OFFSET);
+ mdelay(250);
+}
+
+#define INVALID_ACCESS_OFFSET 0xFFFFFFFF
+static u32 iproc_pcie_conf_access(struct iproc_pcie *pcie, struct pci_bus *bus,
+ unsigned int devfn, int where)
+{
+ int busno = bus->number;
+ int slot = PCI_SLOT(devfn);
+ int fn = PCI_FUNC(devfn);
+ u32 val;
+
+ /* root complex access */
+ if (busno == 0) {
+ if (slot)
+ return INVALID_ACCESS_OFFSET;
+ writel(where & CFG_IND_ADDR_MASK,
+ pcie->reg + CFG_IND_ADDR_OFFSET);
+ return CFG_IND_DATA_OFFSET;
+ }
+
+ if (fn > 1)
+ return INVALID_ACCESS_OFFSET;
+
+ /* access of EP device */
+ val = (bus->number << CFG_ADDR_BUS_NUM_SHIFT) |
+ (PCI_SLOT(devfn) << CFG_ADDR_DEV_NUM_SHIFT) |
+ (PCI_FUNC(devfn) << CFG_ADDR_FUNC_NUM_SHIFT) |
+ (where & CFG_ADDR_REG_NUM_MASK) |
+ (1 & CFG_ADDR_CFG_TYPE_MASK);
+ writel(val, pcie->reg + CFG_ADDR_OFFSET);
+
+ return CFG_DATA_OFFSET;
+}
+
+#define INVALID_CFG_RD 0xFFFFFFFF
+static int iproc_pci_read_conf(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 *val)
+{
+ u32 offset;
+ struct iproc_pcie *pcie = sys_to_pcie(bus->sysdata);
+
+ *val = INVALID_CFG_RD;
+
+ if (size != 1 && size != 2 && size != 4)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ else if ((size == 2) && (where & 1))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ else if ((size == 4) && (where & 3))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ offset = iproc_pcie_conf_access(pcie, bus, devfn, where);
+ if (offset == INVALID_ACCESS_OFFSET)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ *val = readl(pcie->reg + offset);
+
+ switch (size) {
+ case 4:
+ /* return raw data */
+ break;
+ case 2:
+ *val = (*val >> (8 * (where & 3))) & 0xFFFF;
+ break;
+ case 1:
+ *val = (*val >> (8 * (where & 3))) & 0xFF;
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ dev_dbg(pcie->dev, "conf rd: busn=%d devfn=%d where=%d size=%d val=0x%08x\n",
+ bus->number, devfn, where, size, *val);
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int iproc_pci_write_conf(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 val)
+{
+ int shift;
+ u32 offset, data;
+ struct iproc_pcie *pcie = sys_to_pcie(bus->sysdata);
+
+ if (size != 1 && size != 2 && size != 4)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ else if ((size == 2) && (where & 1))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ else if ((size == 4) && (where & 3))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ offset = iproc_pcie_conf_access(pcie, bus, devfn, where);
+ if (offset == INVALID_ACCESS_OFFSET)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ data = readl(pcie->reg + offset);
+
+ switch (size) {
+ case 4:
+ data = val;
+ break;
+ case 2:
+ shift = 8 * (where & 2);
+ data &= ~(0xFFFF << shift);
+ data |= ((val & 0xFFFF) << shift);
+ break;
+ case 1:
+ shift = 8 * (where & 3);
+ data &= ~(0xFF << shift);
+ data |= ((val & 0xFF) << shift);
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ writel(data, pcie->reg + offset);
+
+ dev_dbg(pcie->dev,
+ "config wr: busn=%d devfn=%d where=%d size=%d data=0x%08x\n",
+ bus->number, devfn, where, size, data);
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops iproc_pcie_ops = {
+ .read = iproc_pci_read_conf,
+ .write = iproc_pci_write_conf,
+};
+
+static int iproc_pcie_check_link(struct iproc_pcie *pcie)
+{
+ int ret;
+ u8 nlw;
+ u16 pos, tmp16;
+ u32 val;
+ struct pci_sys_data sys;
+ struct pci_bus bus;
+
+ val = readl(pcie->reg + PCIE_LINK_STATUS_OFFSET);
+ dev_dbg(pcie->dev, "link status: 0x%08x\n", val);
+
+ val = readl(pcie->reg + STRAP_STATUS_OFFSET);
+ dev_dbg(pcie->dev, "strap status: 0x%08x\n", val);
+
+ memset(&sys, 0, sizeof(sys));
+ memset(&bus, 0, sizeof(bus));
+
+ bus.number = 0;
+ bus.ops = &iproc_pcie_ops;
+ bus.sysdata = &sys;
+ sys.private_data = pcie;
+
+ ret = iproc_pci_read_conf(&bus, 0, PCI_HEADER_TYPE, 1, &val);
+ if (ret != PCIBIOS_SUCCESSFUL || val != PCI_HEADER_TYPE_BRIDGE) {
+ dev_err(pcie->dev, "in EP mode, val=0x08%x\n", val);
+ return -EFAULT;
+ }
+
+ /*
+ * Under RC mode, write to function specific register 0x43c, to change
+ * the CLASS code in configuration space
+ *
+ * After this modification, the CLASS code in configuration space would
+ * be read as PCI_CLASS_BRIDGE_PCI(0x0604)
+ */
+#define PCI_BRIDGE_CTRL_REG_OFFSET 0x43C
+#define PCI_CLASS_BRIDGE_PCI_MASK 0xFF0000FF
+ pci_bus_read_config_dword(&bus, 0, PCI_BRIDGE_CTRL_REG_OFFSET, &val);
+ val = (val & PCI_CLASS_BRIDGE_PCI_MASK) | (PCI_CLASS_BRIDGE_PCI << 8);
+ pci_bus_write_config_dword(&bus, 0, PCI_BRIDGE_CTRL_REG_OFFSET, val);
+
+ /* check link status to see if link is active */
+ pos = pci_bus_find_capability(&bus, 0, PCI_CAP_ID_EXP);
+ pci_bus_read_config_word(&bus, 0, pos + PCI_EXP_LNKSTA, &tmp16);
+ tmp16 &= PCI_EXP_LNKSTA_DLLLA;
+ nlw = (tmp16 & PCI_EXP_LNKSTA_NLW) >> PCI_EXP_LNKSTA_NLW_SHIFT;
+
+ if (nlw == 0) {
+ /* try GEN 1 link speed */
+#define PCI_LINK_STATUS_CTRL_2_OFFSET 0xDC
+#define PCI_TARGET_LINK_SPEED_MASK 0xF
+#define PCI_TARGET_LINK_SPEED_GEN2 0x2
+#define PCI_TARGET_LINK_SPEED_GEN1 0x1
+ pci_bus_read_config_dword(&bus, 0,
+ PCI_LINK_STATUS_CTRL_2_OFFSET, &val);
+ if ((val & PCI_TARGET_LINK_SPEED_MASK) ==
+ PCI_TARGET_LINK_SPEED_GEN2) {
+ val &= ~PCI_TARGET_LINK_SPEED_MASK;
+ val |= PCI_TARGET_LINK_SPEED_GEN1;
+ pci_bus_write_config_dword(&bus, 0,
+ PCI_LINK_STATUS_CTRL_2_OFFSET, val);
+ pci_bus_read_config_dword(&bus, 0,
+ PCI_LINK_STATUS_CTRL_2_OFFSET, &val);
+ mdelay(100);
+
+ pos = pci_bus_find_capability(&bus, 0, PCI_CAP_ID_EXP);
+ pci_bus_read_config_word(&bus, 0, pos + PCI_EXP_LNKSTA,
+ &tmp16);
+ nlw = (tmp16 & PCI_EXP_LNKSTA_NLW) >>
+ PCI_EXP_LNKSTA_NLW_SHIFT;
+ }
+ }
+
+ dev_info(pcie->dev, "link: %s\n", nlw ? "UP" : "DOWN");
+
+ return nlw ? 0 : -ENODEV;
+}
+
+static int iproc_pcie_setup(int nr, struct pci_sys_data *sys)
+{
+ struct iproc_pcie *pcie = sys_to_pcie(sys);
+
+ pci_add_resource(&sys->resources, &pcie->io);
+ pci_add_resource(&sys->resources, &pcie->mem);
+ pci_add_resource(&sys->resources, &pcie->busn);
+
+ return 1;
+}
+
+static struct pci_bus *iproc_pcie_scan_bus(int nr, struct pci_sys_data *sys)
+{
+ struct iproc_pcie *pcie = sys->private_data;
+ struct pci_bus *bus;
+
+ bus = pci_create_root_bus(pcie->dev, sys->busnr, &iproc_pcie_ops, sys,
+ &sys->resources);
+ if (!bus)
+ return NULL;
+
+ if (IS_ENABLED(CONFIG_PCI_MSI))
+ bus->msi = &pcie->msi.chip;
+
+ pci_scan_child_bus(bus);
+
+ return bus;
+}
+
+static int iproc_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+ struct iproc_pcie *pcie = sys_to_pcie(dev->sysdata);
+
+ /* need to use the 5th IRQ for INTx */
+ return pcie->irqs[4];
+}
+
+static struct hw_pci hw;
+
+static void iproc_pcie_enable(struct iproc_pcie *pcie)
+{
+ hw.nr_controllers = 1;
+ hw.private_data = (void **)&pcie;
+ hw.setup = iproc_pcie_setup;
+ hw.scan = iproc_pcie_scan_bus;
+ hw.map_irq = iproc_pcie_map_irq;
+ hw.ops = &iproc_pcie_ops;
+
+ /* enable root complex INTX */
+ writel(SYS_RC_INTX_MASK, pcie->reg + SYS_RC_INTX_EN);
+
+ pci_common_init_dev(pcie->dev, &hw);
+#ifdef CONFIG_PCI_DOMAINS
+ hw.domain++;
+#endif
+}
+
+#define PCIE_PHY_REG_ADDR 0x2103
+#define PCIE_PHY_DATA 0x2B1C
+static void iproc_pcie_mii_phy_init(struct iproc_pcie *pcie, u32 phy_addr)
+{
+ unsigned int reg_addr;
+ u16 val;
+
+ mdio_init(pcie);
+
+ reg_addr = PCIE_PHY_REG_ADDR;
+ val = PCIE_PHY_DATA;
+ iproc_pcie_phy_reg_write(pcie, phy_addr, reg_addr, val);
+ val = iproc_pcie_phy_reg_read(pcie, phy_addr, reg_addr);
+ dev_info(pcie->dev, "phy: 0x%x reg: 0x%4x val: 0x%4x\n", phy_addr,
+ reg_addr, val);
+}
+
+static inline struct iproc_msi *to_iproc_msi(struct msi_controller *chip)
+{
+ return container_of(chip, struct iproc_msi, chip);
+}
+
+static int iproc_msi_irq_assign(struct iproc_msi *chip)
+{
+ int msi;
+
+ msi = find_first_zero_bit(chip->irq_in_use, MAX_IRQS);
+ if (msi < MAX_IRQS)
+ set_bit(msi, chip->irq_in_use);
+ else
+ msi = -ENOSPC;
+
+ return msi;
+}
+
+static void iproc_msi_irq_free(struct iproc_msi *chip, unsigned long irq)
+{
+ clear_bit(irq, chip->irq_in_use);
+}
+
+static int iproc_msi_setup_irq(struct msi_controller *chip,
+ struct pci_dev *pdev, struct msi_desc *desc)
+{
+ struct iproc_msi *msi = to_iproc_msi(chip);
+ struct iproc_pcie *pcie = msi->pcie;
+ struct msi_msg msg;
+ unsigned int irq;
+ int hwirq;
+
+ hwirq = iproc_msi_irq_assign(msi);
+ if (hwirq < 0)
+ return hwirq;
+
+ irq = irq_create_mapping(msi->domain, hwirq);
+ if (!irq) {
+ iproc_msi_irq_free(msi, hwirq);
+ return -EINVAL;
+ }
+
+ dev_dbg(pcie->dev, "mapped irq:%d\n", irq);
+
+ irq_set_msi_desc(irq, desc);
+
+ msg.address_lo = virt_to_phys((void *)msi->msi_page) | (hwirq * 4);
+ msg.address_hi = 0x0;
+ msg.data = hwirq;
+
+ write_msi_msg(irq, &msg);
+
+ return 0;
+}
+
+static void iproc_msi_teardown_irq(struct msi_controller *chip,
+ unsigned int irq)
+{
+ struct iproc_msi *msi = to_iproc_msi(chip);
+ struct irq_data *data = irq_get_irq_data(irq);
+
+ iproc_msi_irq_free(msi, data->hwirq);
+}
+
+static struct irq_chip iproc_msi_irq_chip = {
+ .name = "iProc PCIe MSI",
+ .irq_enable = unmask_msi_irq,
+ .irq_disable = mask_msi_irq,
+ .irq_mask = mask_msi_irq,
+ .irq_unmask = unmask_msi_irq,
+};
+
+static int iproc_msi_map(struct irq_domain *domain, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &iproc_msi_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, domain->host_data);
+ set_irq_flags(irq, IRQF_VALID);
+
+ return 0;
+}
+
+static const struct irq_domain_ops iproc_msi_domain_ops = {
+ .map = iproc_msi_map,
+};
+
+static irqreturn_t iproc_msi_irq(int irq, void *data)
+{
+ struct iproc_pcie *pcie = data;
+ unsigned int eq, head, tail, num_events;
+
+ /* Do not handle INTx interrupt */
+ if ((readl(pcie->reg + SYS_RC_INTX_CSR) & SYS_RC_INTX_MASK) != 0)
+ return IRQ_NONE;
+
+ eq = irq - pcie->irqs[0];
+ BUG_ON(eq >= MAX_MSI_EQ);
+
+ irq = irq_find_mapping(pcie->msi.domain, eq);
+ head = readl(pcie->reg + SYS_EQ_HEAD_0_OFFSET + (eq * 8));
+ do {
+ tail = readl(pcie->reg + SYS_EQ_TAIL_0_OFFSET + (eq * 8));
+ tail &= SYS_EQ_TAIL_0_MASK;
+
+ num_events = (tail < head) ?
+ (64 + (tail - head)) : (tail - head);
+ if (!num_events)
+ break;
+
+ generic_handle_irq(irq);
+
+ head++;
+ head %= 64;
+ writel(head, pcie->reg + SYS_EQ_HEAD_0_OFFSET + (eq * 8));
+ } while (true);
+
+ return IRQ_HANDLED;
+}
+
+static int iproc_pcie_enable_msi(struct iproc_pcie *pcie)
+{
+ struct iproc_msi *msi = &pcie->msi;
+ struct device_node *np = pcie->dev->of_node;
+ int i, ret;
+ u32 val;
+
+ msi->pcie = pcie;
+ msi->chip.dev = pcie->dev;
+ msi->chip.setup_irq = iproc_msi_setup_irq;
+ msi->chip.teardown_irq = iproc_msi_teardown_irq;
+
+ msi->domain = irq_domain_add_linear(pcie->dev->of_node, MAX_IRQS,
+ &iproc_msi_domain_ops, &msi->chip);
+ if (!msi->domain) {
+ dev_err(pcie->dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < MAX_IRQS; i++) {
+ ret = devm_request_irq(pcie->dev, pcie->irqs[i],
+ iproc_msi_irq, IRQF_SHARED, "iproc-pcie", pcie);
+ if (ret < 0) {
+ dev_err(pcie->dev, "failed to request IRQ: %d\n",
+ pcie->irqs[i]);
+ goto err_rm_irq_domain;
+ }
+ }
+
+ msi->eq_page = __get_free_pages(GFP_KERNEL, 0);
+ if (!msi->eq_page) {
+ dev_err(pcie->dev,
+ "failed to allocate memory for MSI event queue\n");
+ ret = -ENOMEM;
+ goto err_rm_irq_domain;
+ }
+
+ msi->msi_page = __get_free_pages(GFP_KERNEL, 0);
+ if (!msi->msi_page) {
+ dev_err(pcie->dev,
+ "failed to allocate memory for MSI\n");
+ ret = -ENOMEM;
+ goto err_free_msi_eq_page;
+ }
+
+ writel(virt_to_phys((void *)msi->eq_page),
+ pcie->reg + SYS_EQ_PAGE_OFFSET);
+ writel(virt_to_phys((void *)msi->msi_page),
+ pcie->reg + SYS_MSI_PAGE_OFFSET);
+
+ for (i = 0; i < MAX_MSI_EQ; i++) {
+ /* enable MSI event queue and interrupt */
+ val = SYS_MSI_INTR_EN | SYS_MSI_INT_N_EVENT | SYS_MSI_EQ_EN;
+ writel(val, pcie->reg + SYS_MSI_CTRL_0_OFFSET + (i * 4));
+ /*
+ * To support legacy platforms that require the MSI interrupt
+ * enable register to be set explicitly
+ */
+ if (of_find_property(np, "have-msi-inten-reg", NULL)) {
+ val = readl(pcie->reg + SYS_MSI_INTS_EN_OFFSET);
+ val |= (1 << i);
+ writel(val, pcie->reg + SYS_MSI_INTS_EN_OFFSET);
+ }
+ }
+
+ dev_info(pcie->dev, "MSI enabled\n");
+ return 0;
+
+err_free_msi_eq_page:
+ free_pages(msi->eq_page, 0);
+
+err_rm_irq_domain:
+ irq_domain_remove(msi->domain);
+ return ret;
+}
+
+static int __init iproc_pcie_probe(struct platform_device *pdev)
+{
+ struct iproc_pcie *pcie;
+ struct device_node *np = pdev->dev.of_node;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ struct resource res, regs;
+ int i, ret;
+
+ pcie = devm_kzalloc(&pdev->dev, sizeof(struct iproc_pcie),
+ GFP_KERNEL);
+ if (!pcie)
+ return -ENOMEM;
+
+ pcie->dev = &pdev->dev;
+ platform_set_drvdata(pdev, pcie);
+
+ if (of_pci_parse_bus_range(pdev->dev.of_node, &pcie->busn)) {
+ dev_err(&pdev->dev, "failed to parse bus-range property\n");
+ return -EINVAL;
+ }
+
+ /* PCIE controller registers */
+ ret = of_address_to_resource(np, 0, &regs);
+ if (ret) {
+ dev_err(pcie->dev, "unable to obtain device resources\n");
+ return -ENODEV;
+ }
+
+ pcie->reg = devm_ioremap(pcie->dev, regs.start, resource_size(&regs));
+ if (!pcie->reg) {
+ dev_err(pcie->dev, "unable to map device reg resources\n");
+ return -ENOMEM;
+ }
+
+ /* MDIO registers */
+ ret = of_address_to_resource(np, 1, &regs);
+ if (ret) {
+ dev_err(pcie->dev, "unable to obtain device resources\n");
+ return -ENODEV;
+ }
+
+ pcie->mii = devm_ioremap(pcie->dev, regs.start, resource_size(&regs));
+ if (!pcie->mii) {
+ dev_err(pcie->dev, "unable to map device mii resources\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < MAX_IRQS; i++) {
+ pcie->irqs[i] = irq_of_parse_and_map(np, i);
+ if (!pcie->irqs[i]) {
+ dev_err(pcie->dev, "unable to parse irq index:%d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ if (of_property_read_u32(np, "phy-addr", &pcie->phy_addr)) {
+ dev_err(pcie->dev, "missing \"phy-addr\" property in DT\n");
+ return -EINVAL;
+ }
+
+ if (of_pci_range_parser_init(&parser, np)) {
+ dev_err(pcie->dev, "missing \"ranges\" property in DT\n");
+ return -EINVAL;
+ }
+
+ /* Get the PCI memory ranges from DT */
+ for_each_of_pci_range(&parser, &range) {
+ of_pci_range_to_resource(&range, np, &res);
+
+ switch (res.flags & IORESOURCE_TYPE_BITS) {
+ case IORESOURCE_IO:
+ memcpy(&pcie->io, &res, sizeof(res));
+ pcie->io.name = "I/O";
+ break;
+
+ case IORESOURCE_MEM:
+ memcpy(&pcie->mem, &res, sizeof(res));
+ pcie->mem.name = "MEM";
+ break;
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
+ ret = iproc_pcie_enable_msi(pcie);
+ if (ret < 0) {
+ dev_err(pcie->dev, "failed to enable MSI support\n");
+ return ret;
+ }
+ }
+
+ iproc_pcie_mii_phy_init(pcie, pcie->phy_addr);
+
+ iproc_pcie_reset(pcie);
+
+ ret = iproc_pcie_check_link(pcie);
+ if (ret) {
+ dev_err(pcie->dev, "no PCIe EP device detected\n");
+ return ret;
+ }
+
+ iproc_pcie_enable(pcie);
+ pci_assign_unassigned_resources();
+
+ return 0;
+}
+
+static const struct of_device_id iproc_pcie_of_match_table[] = {
+ { .compatible = "brcm,iproc-pcie", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
+
+static struct platform_driver iproc_pcie_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "iproc-pcie",
+ .of_match_table =
+ of_match_ptr(iproc_pcie_of_match_table),
+ },
+};
+
+static int __init iproc_pcie_init(void)
+{
+ return platform_driver_probe(&iproc_pcie_driver,
+ iproc_pcie_probe);
+}
+subsys_initcall(iproc_pcie_init);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom iPROC PCIe driver");
+MODULE_LICENSE("GPL v2");

Ray Jui

unread,
Dec 9, 2014, 7:10:07 PM12/9/14
to
Document the PCIe device tree binding for Broadcom iProc family of SoCs

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/pci/brcm,iproc-pcie.txt | 62 ++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pci/brcm,iproc-pcie.txt

diff --git a/Documentation/devicetree/bindings/pci/brcm,iproc-pcie.txt b/Documentation/devicetree/bindings/pci/brcm,iproc-pcie.txt
new file mode 100644
index 0000000..2467628
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/brcm,iproc-pcie.txt
@@ -0,0 +1,62 @@
+* Broadcom iProc PCIe controller
+
+Required properties:
+- compatible: Must be "brcm,iproc-pcie"
+- reg: base address and length of the PCIe controller and the MDIO interface
+ that controls the PCIe PHY
+- interrupts: interrupt IDs
+- bus-range: PCI bus numbers covered
+- #address-cells: set to <3>
+- #size-cells: set to <2>
+- device_type: set to "pci"
+- ranges: ranges for the PCI memory and I/O regions
+- phy-addr: MDC/MDIO adddress of the PCIe PHY
+- have-msi-inten-reg: Required for legacy iProc PCIe controllers that need the
+ MSI interrupt enable register to be set explicitly
+
+The Broadcom iProc PCie driver adapts the multi-domain structure, i.e., each
+interface has its own domain and therefore has its own device node
+Example:
+
+SoC specific DT Entry:
+
+ pcie0: pcie@18012000 {
+ compatible = "brcm,iproc-pcie";
+ reg = <0x18012000 0x1000>,
+ <0x18002000 0x1000>;
+ interrupts = <GIC_SPI 96 IRQ_TYPE_NONE>,
+ <GIC_SPI 97 IRQ_TYPE_NONE>,
+ <GIC_SPI 98 IRQ_TYPE_NONE>,
+ <GIC_SPI 99 IRQ_TYPE_NONE>,
+ <GIC_SPI 100 IRQ_TYPE_NONE>,
+ <GIC_SPI 101 IRQ_TYPE_NONE>;
+ bus-range = <0x00 0xFF>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges = <0x81000000 0 0 0x28000000 0 0x00010000 /* downstream I/O */
+ 0x82000000 0 0x20000000 0x20000000 0 0x04000000>; /* non-prefetchable memory */
+ phy-addr = <5>;
+ };
+
+ pcie1: pcie@18013000 {
+ compatible = "brcm,iproc-pcie";
+ reg = <0x18013000 0x1000>,
+ <0x18002000 0x1000>;
+
+ interrupts = <GIC_SPI 102 IRQ_TYPE_NONE>,
+ <GIC_SPI 103 IRQ_TYPE_NONE>,
+ <GIC_SPI 104 IRQ_TYPE_NONE>,
+ <GIC_SPI 105 IRQ_TYPE_NONE>,
+ <GIC_SPI 106 IRQ_TYPE_NONE>,
+ <GIC_SPI 107 IRQ_TYPE_NONE>;
+ bus-range = <0x00 0xFF>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges = <0x81000000 0 0 0x48000000 0 0x00010000 /* downstream I/O */
+ 0x82000000 0 0x40000000 0x40000000 0 0x04000000>; /* non-prefetchable memory */
+ phy-addr = <6>;
+ };

Ray Jui

unread,
Dec 9, 2014, 8:00:07 PM12/9/14
to
Enable I2C driver support for Broadcom iProc family of SoCs by
selecting I2C_BCM_IPROC

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
arch/arm/mach-bcm/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index aaeec78..86ee90b 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -19,6 +19,7 @@ config ARCH_BCM_IPROC
select ARCH_REQUIRE_GPIOLIB
select ARM_AMBA
select PINCTRL
+ select I2C_BCM_IPROC
help
This enables support for systems based on Broadcom IPROC architected SoCs.
The IPROC complex contains one or more ARM CPUs along with common

Ray Jui

unread,
Dec 9, 2014, 8:00:07 PM12/9/14
to
Add initial support to the Broadcom iProc I2C controller found in the
iProc family of SoCs.

The iProc I2C controller has separate internal TX and RX FIFOs, each has
a size of 64 bytes. The iProc I2C controller supports two bus speeds
including standard mode (100kHz) and fast mode (400kHz)

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
drivers/i2c/busses/Kconfig | 9 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-bcm-iproc.c | 503 ++++++++++++++++++++++++++++++++++++
3 files changed, 513 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-bcm-iproc.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index c1351d9..8a2eb7e 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -372,6 +372,15 @@ config I2C_BCM2835
This support is also available as a module. If so, the module
will be called i2c-bcm2835.

+config I2C_BCM_IPROC
+ tristate "Broadcom iProc I2C controller"
+ depends on ARCH_BCM_IPROC
+ help
+ If you say yes to this option, support will be included for the
+ Broadcom iProc I2C controller.
+
+ If you don't know what to do here, say N.
+
config I2C_BCM_KONA
tristate "BCM Kona I2C adapter"
depends on ARCH_BCM_MOBILE
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 5e6c822..216e7be 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_I2C_AT91) += i2c-at91.o
obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
obj-$(CONFIG_I2C_AXXIA) += i2c-axxia.o
obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
+obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
obj-$(CONFIG_I2C_BLACKFIN_TWI) += i2c-bfin-twi.o
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c
new file mode 100644
index 0000000..0e6e603
--- /dev/null
+++ b/drivers/i2c/busses/i2c-bcm-iproc.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#define CFG_OFFSET 0x00
+#define CFG_RESET_SHIFT 31
+#define CFG_EN_SHIFT 30
+#define CFG_M_RETRY_CNT_SHIFT 16
+#define CFG_M_RETRY_CNT_MASK 0x0f
+
+#define TIM_CFG_OFFSET 0x04
+#define TIME_CFG_MODE_400_SHIFT 31
+
+#define M_FIFO_CTRL_OFFSET 0x0c
+#define M_FIFO_RX_FLUSH_SHIFT 31
+#define M_FIFO_TX_FLUSH_SHIFT 30
+#define M_FIFO_RX_CNT_SHIFT 16
+#define M_FIFO_RX_CNT_MASK 0x7f
+#define M_FIFO_RX_THLD_SHIFT 8
+#define M_FIFO_RX_THLD_MASK 0x3f
+
+#define M_CMD_OFFSET 0x30
+#define M_CMD_START_BUSY_SHIFT 31
+#define M_CMD_STATUS_SHIFT 25
+#define M_CMD_STATUS_MASK 0x07
+#define M_CMD_STATUS_SUCCESS 0x0
+#define M_CMD_STATUS_LOST_ARB 0x1
+#define M_CMD_STATUS_NACK_ADDR 0x2
+#define M_CMD_STATUS_NACK_DATA 0x3
+#define M_CMD_STATUS_TIMEOUT 0x4
+#define M_CMD_PROTOCOL_SHIFT 9
+#define M_CMD_PROTOCOL_MASK 0xf
+#define M_CMD_PROTOCOL_BLK_WR 0x7
+#define M_CMD_PROTOCOL_BLK_RD 0x8
+#define M_CMD_PEC_SHIFT 8
+#define M_CMD_RD_CNT_SHIFT 0
+#define M_CMD_RD_CNT_MASK 0xff
+
+#define IE_OFFSET 0x38
+#define IE_M_RX_FIFO_FULL_SHIFT 31
+#define IE_M_RX_THLD_SHIFT 30
+#define IE_M_START_BUSY_SHIFT 28
+
+#define IS_OFFSET 0x3c
+#define IS_M_RX_FIFO_FULL_SHIFT 31
+#define IS_M_RX_THLD_SHIFT 30
+#define IS_M_START_BUSY_SHIFT 28
+
+#define M_TX_OFFSET 0x40
+#define M_TX_WR_STATUS_SHIFT 31
+#define M_TX_DATA_SHIFT 0
+#define M_TX_DATA_MASK 0xff
+
+#define M_RX_OFFSET 0x44
+#define M_RX_STATUS_SHIFT 30
+#define M_RX_STATUS_MASK 0x03
+#define M_RX_PEC_ERR_SHIFT 29
+#define M_RX_DATA_SHIFT 0
+#define M_RX_DATA_MASK 0xff
+
+#define I2C_TIMEOUT_MESC 100
+#define M_TX_RX_FIFO_SIZE 64
+
+enum bus_speed_index {
+ I2C_SPD_100K = 0,
+ I2C_SPD_400K,
+};
+
+struct bcm_iproc_i2c_dev {
+ struct device *device;
+
+ void __iomem *base;
+ struct i2c_msg *msg;
+
+ struct i2c_adapter adapter;
+
+ struct completion done;
+};
+
+/*
+ * Can be expanded in the future if more interrupt status bits are utilized
+ */
+#define ISR_MASK (1 << IS_M_START_BUSY_SHIFT)
+
+static irqreturn_t bcm_iproc_i2c_isr(int irq, void *data)
+{
+ struct bcm_iproc_i2c_dev *dev = data;
+ u32 status = readl(dev->base + IS_OFFSET);
+
+ status &= ISR_MASK;
+
+ if (!status)
+ return IRQ_NONE;
+
+ writel(status, dev->base + IS_OFFSET);
+ complete_all(&dev->done);
+
+ return IRQ_HANDLED;
+}
+
+static int __wait_for_bus_idle(struct bcm_iproc_i2c_dev *dev)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(I2C_TIMEOUT_MESC);
+
+ while (readl(dev->base + M_CMD_OFFSET) &
+ (1 << M_CMD_START_BUSY_SHIFT)) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(dev->device, "wait for bus idle timeout\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int bcm_iproc_i2c_format_addr(struct bcm_iproc_i2c_dev *dev,
+ struct i2c_msg *msg, u8 *addr)
+{
+
+ if (msg->flags & I2C_M_TEN) {
+ dev_err(dev->device, "no support for 10-bit address\n");
+ return -EINVAL;
+ }
+
+ *addr = (msg->addr << 1) & 0xfe;
+
+ if (msg->flags & I2C_M_RD)
+ *addr |= 1;
+
+ return 0;
+}
+
+static int bcm_iproc_i2c_check_status(struct bcm_iproc_i2c_dev *dev)
+{
+ u32 val;
+
+ val = readl(dev->base + M_CMD_OFFSET);
+ val = (val >> M_CMD_STATUS_SHIFT) & M_CMD_STATUS_MASK;
+
+ switch (val) {
+ case M_CMD_STATUS_SUCCESS:
+ return 0;
+
+ case M_CMD_STATUS_LOST_ARB:
+ dev_err(dev->device, "lost bus arbitration\n");
+ return -EREMOTEIO;
+
+ case M_CMD_STATUS_NACK_ADDR:
+ dev_err(dev->device, "NAK addr:0x%02x\n", dev->msg->addr);
+ return -EREMOTEIO;
+
+ case M_CMD_STATUS_NACK_DATA:
+ dev_err(dev->device, "NAK data\n");
+ return -EREMOTEIO;
+
+ case M_CMD_STATUS_TIMEOUT:
+ dev_err(dev->device, "bus timeout\n");
+ return -ETIMEDOUT;
+
+ default:
+ dev_err(dev->device, "unknown error code=%d\n", val);
+ return -EREMOTEIO;
+ }
+
+ return -EREMOTEIO;
+}
+
+static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *dev,
+ struct i2c_msg *msg)
+{
+ int ret, i;
+ u8 addr;
+ u32 val;
+ unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MESC);
+
+ if (msg->len < 1 || msg->len > M_TX_RX_FIFO_SIZE - 1) {
+ dev_err(dev->device,
+ "supported data length is 1 - %u bytes\n",
+ M_TX_RX_FIFO_SIZE - 1);
+ return -EINVAL;
+ }
+
+ dev->msg = msg;
+ ret = __wait_for_bus_idle(dev);
+ if (ret)
+ return ret;
+
+ ret = bcm_iproc_i2c_format_addr(dev, msg, &addr);
+ if (ret)
+ return ret;
+
+ /* load slave address into the TX FIFO */
+ writel(addr, dev->base + M_TX_OFFSET);
+
+ /* for a write transaction, load data into the TX FIFO */
+ if (!(msg->flags & I2C_M_RD)) {
+ for (i = 0; i < msg->len; i++) {
+ val = msg->buf[i];
+
+ /* mark the last byte */
+ if (i == msg->len - 1)
+ val |= 1 << M_TX_WR_STATUS_SHIFT;
+
+ writel(val, dev->base + M_TX_OFFSET);
+ }
+ }
+
+ /* mark as incomplete before starting the transaction */
+ reinit_completion(&dev->done);
+
+ /*
+ * Enable the "start busy" interrupt, which will be triggered after
+ * the transaction is done
+ */
+ writel(1 << IE_M_START_BUSY_SHIFT, dev->base + IE_OFFSET);
+
+ /*
+ * Now we can activate the transfer. For a read operation, specify the
+ * number of bytes to read
+ */
+ val = 1 << M_CMD_START_BUSY_SHIFT;
+ if (msg->flags & I2C_M_RD) {
+ val |= (M_CMD_PROTOCOL_BLK_RD << M_CMD_PROTOCOL_SHIFT) |
+ (msg->len << M_CMD_RD_CNT_SHIFT);
+ } else {
+ val |= (M_CMD_PROTOCOL_BLK_WR << M_CMD_PROTOCOL_SHIFT);
+ }
+ writel(val, dev->base + M_CMD_OFFSET);
+
+ time_left = wait_for_completion_timeout(&dev->done, time_left);
+
+ /* disable all interrupts */
+ writel(0, dev->base + IE_OFFSET);
+
+ if (!time_left) {
+ dev_err(dev->device, "transaction times out\n");
+
+ /* flush FIFOs */
+ val = (1 << M_FIFO_RX_FLUSH_SHIFT) |
+ (1 << M_FIFO_TX_FLUSH_SHIFT);
+ writel(val, dev->base + M_FIFO_CTRL_OFFSET);
+ return -EREMOTEIO;
+ }
+
+ ret = bcm_iproc_i2c_check_status(dev);
+ if (ret) {
+ /* flush both TX/RX FIFOs */
+ val = (1 << M_FIFO_RX_FLUSH_SHIFT) |
+ (1 << M_FIFO_TX_FLUSH_SHIFT);
+ writel(val, dev->base + M_FIFO_CTRL_OFFSET);
+ return ret;
+ }
+
+ /*
+ * For a read operation, we now need to load the data from FIFO
+ * into the memory buffer
+ */
+ if (msg->flags & I2C_M_RD) {
+ for (i = 0; i < msg->len; i++) {
+ msg->buf[i] = (readl(dev->base + M_RX_OFFSET) >>
+ M_RX_DATA_SHIFT) & M_RX_DATA_MASK;
+ }
+ }
+
+ dev_dbg(dev->device, "xfer %c, addr=0x%02x, len=%d\n",
+ (msg->flags & I2C_M_RD) ? 'R' : 'W', msg->addr,
+ msg->len);
+ dev_dbg(dev->device, "**** data start ****\n");
+ for (i = 0; i < msg->len; i++)
+ dev_dbg(dev->device, "0x%02x ", msg->buf[i]);
+ dev_dbg(dev->device, "**** data end ****\n");
+
+ return 0;
+}
+
+static int bcm_iproc_i2c_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg msgs[], int num)
+{
+ struct bcm_iproc_i2c_dev *dev = i2c_get_adapdata(adapter);
+ int ret, i;
+
+ /* go through all messages */
+ for (i = 0; i < num; i++) {
+ ret = bcm_iproc_i2c_xfer_single_msg(dev, &msgs[i]);
+ if (ret) {
+ dev_err(dev->device, "xfer failed\n");
+ return ret;
+ }
+ }
+
+ return num;
+}
+
+static uint32_t bcm_iproc_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm bcm_iproc_algo = {
+ .master_xfer = bcm_iproc_i2c_xfer,
+ .functionality = bcm_iproc_i2c_functionality,
+};
+
+static int bcm_iproc_i2c_cfg_speed(struct bcm_iproc_i2c_dev *dev)
+{
+ unsigned int bus_speed, speed_bit;
+ u32 val;
+ int ret = of_property_read_u32(dev->device->of_node, "clock-frequency",
+ &bus_speed);
+ if (ret < 0) {
+ dev_err(dev->device, "missing clock-frequency property\n");
+ return -ENODEV;
+ }
+
+ switch (bus_speed) {
+ case 100000:
+ speed_bit = 0;
+ break;
+ case 400000:
+ speed_bit = 1;
+ break;
+ default:
+ dev_err(dev->device, "%d Hz bus speed not supported\n",
+ bus_speed);
+ dev_err(dev->device, "valid speeds are 100khz and 400khz\n");
+ return -EINVAL;
+ }
+
+ val = readl(dev->base + TIM_CFG_OFFSET);
+ val &= ~(1 << TIME_CFG_MODE_400_SHIFT);
+ val |= speed_bit << TIME_CFG_MODE_400_SHIFT;
+ writel(val, dev->base + TIM_CFG_OFFSET);
+
+ dev_info(dev->device, "bus set to %u Hz\n", bus_speed);
+
+ return 0;
+}
+
+static int bcm_iproc_i2c_init(struct bcm_iproc_i2c_dev *dev)
+{
+ u32 val;
+
+ /* put controller in reset */
+ val = readl(dev->base + CFG_OFFSET);
+ val |= 1 << CFG_RESET_SHIFT;
+ val &= ~(1 << CFG_EN_SHIFT);
+ writel(val, dev->base + CFG_OFFSET);
+
+ /* wait 100 usec per spec */
+ udelay(100);
+
+ /* bring controller out of reset */
+ val = readl(dev->base + CFG_OFFSET);
+ val &= ~(1 << CFG_RESET_SHIFT);
+ writel(val, dev->base + CFG_OFFSET);
+
+ /* flush TX/RX FIFOs and set RX FIFO threshold to zero */
+ val = (1 << M_FIFO_RX_FLUSH_SHIFT) | (1 << M_FIFO_TX_FLUSH_SHIFT);
+ writel(val, dev->base + M_FIFO_CTRL_OFFSET);
+
+ /* disable all interrupts */
+ val = 0;
+ writel(val, dev->base + IE_OFFSET);
+
+ /* clear all pending interrupts */
+ val = readl(dev->base + IS_OFFSET);
+ writel(val, dev->base + IS_OFFSET);
+
+ return 0;
+}
+
+static void bcm_iproc_i2c_enable(struct bcm_iproc_i2c_dev *dev)
+{
+ u32 val;
+
+ val = readl(dev->base + CFG_OFFSET);
+ val |= 1 << CFG_EN_SHIFT;
+ writel(val, dev->base + CFG_OFFSET);
+}
+
+static void bcm_iproc_i2c_disable(struct bcm_iproc_i2c_dev *dev)
+{
+ u32 val;
+
+ val = readl(dev->base + CFG_OFFSET);
+ val &= ~(1 << CFG_EN_SHIFT);
+ writel(val, dev->base + CFG_OFFSET);
+}
+
+static int bcm_iproc_i2c_probe(struct platform_device *pdev)
+{
+ int irq, ret = 0;
+ struct bcm_iproc_i2c_dev *dev;
+ struct i2c_adapter *adap;
+ struct resource *res;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, dev);
+ dev->device = &pdev->dev;
+ init_completion(&dev->done);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+ dev->base = devm_ioremap_resource(dev->device, res);
+ if (IS_ERR(dev->base))
+ return -ENOMEM;
+
+ ret = bcm_iproc_i2c_init(dev);
+ if (ret)
+ return ret;
+
+ ret = bcm_iproc_i2c_cfg_speed(dev);
+ if (ret)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev->device, "no irq resource\n");
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, bcm_iproc_i2c_isr,
+ IRQF_SHARED, pdev->name, dev);
+ if (ret) {
+ dev_err(dev->device, "unable to request irq %i\n", irq);
+ return ret;
+ }
+
+ bcm_iproc_i2c_enable(dev);
+
+ adap = &dev->adapter;
+ i2c_set_adapdata(adap, dev);
+ strlcpy(adap->name, "Broadcom iProc I2C adapter", sizeof(adap->name));
+ adap->algo = &bcm_iproc_algo;
+ adap->dev.parent = &pdev->dev;
+ adap->dev.of_node = pdev->dev.of_node;
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ dev_err(dev->device, "failed to add adapter\n");
+ return ret;
+ }
+
+ dev_info(dev->device, "device registered successfully\n");
+
+ return 0;
+}
+
+static int bcm_iproc_i2c_remove(struct platform_device *pdev)
+{
+ struct bcm_iproc_i2c_dev *dev = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&dev->adapter);
+ bcm_iproc_i2c_disable(dev);
+
+ return 0;
+}
+
+static const struct of_device_id bcm_iproc_i2c_of_match[] = {
+ {.compatible = "brcm,iproc-i2c",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm_iproc_i2c_of_match);
+
+static struct platform_driver bcm_iproc_i2c_driver = {
+ .driver = {
+ .name = "bcm-iproc-i2c",
+ .owner = THIS_MODULE,
+ .of_match_table = bcm_iproc_i2c_of_match,
+ },
+ .probe = bcm_iproc_i2c_probe,
+ .remove = bcm_iproc_i2c_remove,
+};
+module_platform_driver(bcm_iproc_i2c_driver);
+
+MODULE_AUTHOR("Ray Jui <rj...@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom iProc I2C Driver");
+MODULE_LICENSE("GPL v2");

Ray Jui

unread,
Dec 9, 2014, 8:00:07 PM12/9/14
to
Document the I2C device tree binding for Broadcom iProc family of
SoCs

Signed-off-by: Ray Jui <rj...@broadcom.com>
Reviewed-by: Scott Branden <sbra...@broadcom.com>
---
.../devicetree/bindings/i2c/brcm,iproc-i2c.txt | 37 ++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/brcm,iproc-i2c.txt

diff --git a/Documentation/devicetree/bindings/i2c/brcm,iproc-i2c.txt b/Documentation/devicetree/bindings/i2c/brcm,iproc-i2c.txt
new file mode 100644
index 0000000..81f982c
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/brcm,iproc-i2c.txt
@@ -0,0 +1,37 @@
+Broadcom iProc I2C controller
+
+Required properties:
+
+- compatible:
+ Must be "brcm,iproc-i2c"
+
+- reg:
+ Define the base and range of the I/O address space that contain the iProc
+ I2C controller registers
+
+- interrupts:
+ Should contain the I2C interrupt
+
+- clock-frequency:
+ This is the I2C bus clock. Need to be either 100000 or 400000
+
+- #address-cells:
+ Always 1 (for I2C addresses)
+
+- #size-cells:
+ Always 0
+
+Example:
+ i2c0: i2c@18008000 {
+ compatible = "brcm,iproc-i2c";
+ reg = <0x18008000 0x100>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_NONE>;
+ clock-frequency = <100000>;
+
+ codec: wm8750@1a {
+ compatible = "wlf,wm8750";
+ reg = <0x1a>;
+ };
+ };
It is loading more messages.
0 new messages