diff mbox

[v7,3/5] misc: fuse: Add efuse driver for Tegra

Message ID 1401973754-19701-4-git-send-email-pdeschrijver@nvidia.com (mailing list archive)
State New, archived
Headers show

Commit Message

Peter De Schrijver June 5, 2014, 1:09 p.m. UTC
Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.

Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
---
 Documentation/ABI/testing/sysfs-driver-tegra-fuse |   11 +
 drivers/misc/fuse/Makefile                        |    1 +
 drivers/misc/fuse/tegra/Makefile                  |    7 +
 drivers/misc/fuse/tegra/fuse-tegra.c              |  250 +++++++++++++++++
 drivers/misc/fuse/tegra/fuse-tegra20.c            |  134 ++++++++++
 drivers/misc/fuse/tegra/fuse-tegra30.c            |  177 +++++++++++++
 drivers/misc/fuse/tegra/fuse.h                    |   82 ++++++
 drivers/misc/fuse/tegra/speedo-tegra114.c         |  110 ++++++++
 drivers/misc/fuse/tegra/speedo-tegra124.c         |  180 +++++++++++++
 drivers/misc/fuse/tegra/speedo-tegra20.c          |  110 ++++++++
 drivers/misc/fuse/tegra/speedo-tegra30.c          |  294 +++++++++++++++++++++
 include/linux/tegra-soc.h                         |    4 +
 12 files changed, 1360 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-tegra-fuse
 create mode 100644 drivers/misc/fuse/Makefile
 create mode 100644 drivers/misc/fuse/tegra/Makefile
 create mode 100644 drivers/misc/fuse/tegra/fuse-tegra.c
 create mode 100644 drivers/misc/fuse/tegra/fuse-tegra20.c
 create mode 100644 drivers/misc/fuse/tegra/fuse-tegra30.c
 create mode 100644 drivers/misc/fuse/tegra/fuse.h
 create mode 100644 drivers/misc/fuse/tegra/speedo-tegra114.c
 create mode 100644 drivers/misc/fuse/tegra/speedo-tegra124.c
 create mode 100644 drivers/misc/fuse/tegra/speedo-tegra20.c
 create mode 100644 drivers/misc/fuse/tegra/speedo-tegra30.c

Comments

Tuomas Tynkkynen June 5, 2014, 5:01 p.m. UTC | #1
On 05/06/14 16:09, Peter De Schrijver wrote:
> +       dev_info(dev,
> +               "Tegra Revision: %s SKU: %d CPU Process: %d Core Process: %d\n",
> +               tegra_revision_name[sku_info->revision],
> +               sku_info->sku_id, sku_info->cpu_process_id,
> +               sku_info->core_process_id);

sku_info->sku_id doesn't seem to get initialized anywhere - with an 
older revision of this set I see SKU: 0 in place of what the L4T kernel 
prints (SKU: 0x81).
Stephen Warren June 5, 2014, 6:09 p.m. UTC | #2
On 06/05/2014 07:09 AM, Peter De Schrijver wrote:
> Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.

This series isn't bisectable; building at either patch 3 or patch 4 yields:

> In file included from arch/arm/mach-tegra/fuse.c:28:0:
> arch/arm/mach-tegra/fuse.h:40:5: error: conflicting types for ‘tegra_fuse_readl’
> In file included from arch/arm/mach-tegra/fuse.c:26:0:
> include/linux/tegra-soc.h:43:5: note: previous declaration of ‘tegra_fuse_readl’ was here
> arch/arm/mach-tegra/fuse.c:98:5: error: conflicting types for ‘tegra_fuse_readl’
> In file included from arch/arm/mach-tegra/fuse.c:26:0:
> include/linux/tegra-soc.h:43:5: note: previous declaration of ‘tegra_fuse_readl’ was here
> make[1]: *** [arch/arm/mach-tegra/fuse.o] Error 1
> make[1]: *** Waiting for unfinished jobs....
> make: *** [arch/arm/mach-tegra] Error 2
> make: *** Waiting for unfinished jobs....
Stephen Warren June 5, 2014, 6:37 p.m. UTC | #3
On 06/05/2014 07:09 AM, Peter De Schrijver wrote:
> Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.
> 
> Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
> ---
>  Documentation/ABI/testing/sysfs-driver-tegra-fuse |   11 +
>  drivers/misc/fuse/Makefile                        |    1 +
>  drivers/misc/fuse/tegra/Makefile                  |    7 +
>  drivers/misc/fuse/tegra/fuse-tegra.c              |  250 +++++++++++++++++

I wonder if we shouldn't put this into drivers/soc/tegra?

> diff --git a/drivers/misc/fuse/tegra/fuse-tegra.c b/drivers/misc/fuse/tegra/fuse-tegra.c

> +void __init tegra_init_fuse(void)
> +{
> +	struct device_node *np;
> +	u32 id;
> +	void __iomem *car_base;
> +
> +	np = of_find_matching_node(NULL, apbmisc_match);
> +	apbmisc_base = of_iomap(np, 0);
> +	if (!apbmisc_base) {
> +		pr_warn("ioremap tegra apbmisc failed. using %08x instead\n",
> +			APBMISC_BASE);
> +		apbmisc_base = ioremap(APBMISC_BASE, APBMISC_SIZE);
> +	}
> +
> +	id = tegra_read_chipid();
> +	tegra_chip_id = (id >> 8) & 0xff;

So there's a fallback using APBMIS_BASE above if the node is missing, so
those last 2 lines always happen. However, if any of the following fail:

> +	strapping_base = of_iomap(np, 0);
...
> +	np = of_find_matching_node(NULL, tegra_fuse_match);
...
> +	np = of_find_matching_node(NULL, car_match);

Then this doesn't get executed:

> +	tegra_get_revision(id);

Isn't that important?

I guess that can't run if the lookup for tegra_fuse_match isn't
successful, since that tegra_get_revision may call
tegra20_spare_fuse_early() which uses fuse_base which is set up in
response to succesfully calling on of those node lookups. Isn't a
fallback needed there too?

I'm also a bit concerned that the driver probes, rather than the early
function tegra_init_fuse(), are doing things like setting up the speedo
data initialization, randomness addition, etc. For one, those won't
happen any more unless the DT nodes are present, and secondly,
triggering all those from driver probe rather than a function that's
called from the machine descriptor makes guaranteeing the timing
problematic.

I'd prefer to see the driver probes *just* set up the sysfs, and have
the code initialization stay unchanged relative to the code currently in
arch/arm/mach-tegra/ if possible.
Peter De Schrijver June 5, 2014, 10:09 p.m. UTC | #4
On Thu, Jun 05, 2014 at 08:37:26PM +0200, Stephen Warren wrote:
> On 06/05/2014 07:09 AM, Peter De Schrijver wrote:
> > Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.
> > 
> > Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
> > ---
> >  Documentation/ABI/testing/sysfs-driver-tegra-fuse |   11 +
> >  drivers/misc/fuse/Makefile                        |    1 +
> >  drivers/misc/fuse/tegra/Makefile                  |    7 +
> >  drivers/misc/fuse/tegra/fuse-tegra.c              |  250 +++++++++++++++++
> 
> I wonder if we shouldn't put this into drivers/soc/tegra?
> 
> > diff --git a/drivers/misc/fuse/tegra/fuse-tegra.c b/drivers/misc/fuse/tegra/fuse-tegra.c
> 
> > +void __init tegra_init_fuse(void)
> > +{
> > +	struct device_node *np;
> > +	u32 id;
> > +	void __iomem *car_base;
> > +
> > +	np = of_find_matching_node(NULL, apbmisc_match);
> > +	apbmisc_base = of_iomap(np, 0);
> > +	if (!apbmisc_base) {
> > +		pr_warn("ioremap tegra apbmisc failed. using %08x instead\n",
> > +			APBMISC_BASE);
> > +		apbmisc_base = ioremap(APBMISC_BASE, APBMISC_SIZE);
> > +	}
> > +
> > +	id = tegra_read_chipid();
> > +	tegra_chip_id = (id >> 8) & 0xff;
> 
> So there's a fallback using APBMIS_BASE above if the node is missing, so
> those last 2 lines always happen. However, if any of the following fail:
> 
> > +	strapping_base = of_iomap(np, 0);
> ...
> > +	np = of_find_matching_node(NULL, tegra_fuse_match);
> ...
> > +	np = of_find_matching_node(NULL, car_match);
> 
> Then this doesn't get executed:
> 
> > +	tegra_get_revision(id);
> 
> Isn't that important?
> 

No. It's only used to populate /sys/devices/soc0/revision. I don't think
that's particularly important.

> I guess that can't run if the lookup for tegra_fuse_match isn't
> successful, since that tegra_get_revision may call
> tegra20_spare_fuse_early() which uses fuse_base which is set up in
> response to succesfully calling on of those node lookups. Isn't a
> fallback needed there too?
> 

tegra20_spare_fuse_early() will not be called if fuse_base is NULL.

> I'm also a bit concerned that the driver probes, rather than the early
> function tegra_init_fuse(), are doing things like setting up the speedo
> data initialization, randomness addition, etc. For one, those won't
> happen any more unless the DT nodes are present, and secondly,
> triggering all those from driver probe rather than a function that's
> called from the machine descriptor makes guaranteeing the timing
> problematic.
> 

Today there are no users of the speedo ID in upstream. Looking at chromeos
the users of the speedo ID are: CPU dvfs, GPU dvfs and sdhci. The last 2 also
need regulators and therefor will need to support deferred probing anyway.
CPU dvfs isn't critical at all, we don't care when it gets initialized. So
deferred probe is fine.
sdhci needs this for faster modes I guess which will also need extra DT
properties looking at the chromeos driver. The others definitely will need
an updated DT. For randomness I haven't seen any appreciable difference in when
the 'random: nonblocking pool is initialized' message appears between having
the randomness addition or not. Most likely the bulk of the randomness comes
from serial interrupts rather than the fuse data. So I don't think the move to
a driver probe will cause any problem. Nor do I think the lack of an updated
DT will cause problems.

Cheers,

Peter.
Stephen Warren June 5, 2014, 10:54 p.m. UTC | #5
On 06/05/2014 04:09 PM, Peter De Schrijver wrote:
> On Thu, Jun 05, 2014 at 08:37:26PM +0200, Stephen Warren wrote:
>> On 06/05/2014 07:09 AM, Peter De Schrijver wrote:
>>> Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.
>>>
>>> Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
>>> ---
>>>  Documentation/ABI/testing/sysfs-driver-tegra-fuse |   11 +
>>>  drivers/misc/fuse/Makefile                        |    1 +
>>>  drivers/misc/fuse/tegra/Makefile                  |    7 +
>>>  drivers/misc/fuse/tegra/fuse-tegra.c              |  250 +++++++++++++++++
>>
>> I wonder if we shouldn't put this into drivers/soc/tegra?
>>
>>> diff --git a/drivers/misc/fuse/tegra/fuse-tegra.c b/drivers/misc/fuse/tegra/fuse-tegra.c
>>
>>> +void __init tegra_init_fuse(void)
>>> +{
>>> +	struct device_node *np;
>>> +	u32 id;
>>> +	void __iomem *car_base;
>>> +
>>> +	np = of_find_matching_node(NULL, apbmisc_match);
>>> +	apbmisc_base = of_iomap(np, 0);
>>> +	if (!apbmisc_base) {
>>> +		pr_warn("ioremap tegra apbmisc failed. using %08x instead\n",
>>> +			APBMISC_BASE);
>>> +		apbmisc_base = ioremap(APBMISC_BASE, APBMISC_SIZE);
>>> +	}
>>> +
>>> +	id = tegra_read_chipid();
>>> +	tegra_chip_id = (id >> 8) & 0xff;
>>
>> So there's a fallback using APBMIS_BASE above if the node is missing, so
>> those last 2 lines always happen. However, if any of the following fail:
>>
>>> +	strapping_base = of_iomap(np, 0);
>> ...
>>> +	np = of_find_matching_node(NULL, tegra_fuse_match);
>> ...
>>> +	np = of_find_matching_node(NULL, car_match);
>>
>> Then this doesn't get executed:
>>
>>> +	tegra_get_revision(id);
>>
>> Isn't that important?
> 
> No. It's only used to populate /sys/devices/soc0/revision. I don't think
> that's particularly important.

But it's a feature that works today. Why should we break it?

>> I guess that can't run if the lookup for tegra_fuse_match isn't
>> successful, since that tegra_get_revision may call
>> tegra20_spare_fuse_early() which uses fuse_base which is set up in
>> response to succesfully calling on of those node lookups. Isn't a
>> fallback needed there too?
> 
> tegra20_spare_fuse_early() will not be called if fuse_base is NULL.

Oh yes. We can at least call the function then even if the fuses can't
be mapped.

But to avoid regressions, we should simply make sure the fuses can be
mapped.

>> I'm also a bit concerned that the driver probes, rather than the early
>> function tegra_init_fuse(), are doing things like setting up the speedo
>> data initialization, randomness addition, etc. For one, those won't
>> happen any more unless the DT nodes are present, and secondly,
>> triggering all those from driver probe rather than a function that's
>> called from the machine descriptor makes guaranteeing the timing
>> problematic.
> 
> Today there are no users of the speedo ID in upstream.

Well, except people reading kernel log messages. Accurate speedo-related
log messages have help pin-point a problem or two in the past.

> Looking at chromeos
> the users of the speedo ID are: CPU dvfs, GPU dvfs and sdhci. The last 2 also
> need regulators and therefor will need to support deferred probing anyway.
> CPU dvfs isn't critical at all, we don't care when it gets initialized. So
> deferred probe is fine.

What condition will those modules/drivers use to defer their probe?

> sdhci needs this for faster modes I guess which will also need extra DT
> properties looking at the chromeos driver. The others definitely will need
> an updated DT. For randomness I haven't seen any appreciable difference in when
> the 'random: nonblocking pool is initialized' message appears between having
> the randomness addition or not. Most likely the bulk of the randomness comes
> from serial interrupts rather than the fuse data. So I don't think the move to
> a driver probe will cause any problem. Nor do I think the lack of an updated
> DT will cause problems.

But what advantage do we have by actively changing it?
Peter De Schrijver June 6, 2014, 7:35 a.m. UTC | #6
On Fri, Jun 06, 2014 at 12:54:00AM +0200, Stephen Warren wrote:
> On 06/05/2014 04:09 PM, Peter De Schrijver wrote:
> > On Thu, Jun 05, 2014 at 08:37:26PM +0200, Stephen Warren wrote:
> >> On 06/05/2014 07:09 AM, Peter De Schrijver wrote:
> >>> Implement fuse driver for Tegra20, Tegra30, Tegra114 and Tegra124.
> >>>
> >>> Signed-off-by: Peter De Schrijver <pdeschrijver@nvidia.com>
> >>> ---
> >>>  Documentation/ABI/testing/sysfs-driver-tegra-fuse |   11 +
> >>>  drivers/misc/fuse/Makefile                        |    1 +
> >>>  drivers/misc/fuse/tegra/Makefile                  |    7 +
> >>>  drivers/misc/fuse/tegra/fuse-tegra.c              |  250 +++++++++++++++++
> >>
> >> I wonder if we shouldn't put this into drivers/soc/tegra?
> >>
> >>> diff --git a/drivers/misc/fuse/tegra/fuse-tegra.c b/drivers/misc/fuse/tegra/fuse-tegra.c
> >>
> >>> +void __init tegra_init_fuse(void)
> >>> +{
> >>> +	struct device_node *np;
> >>> +	u32 id;
> >>> +	void __iomem *car_base;
> >>> +
> >>> +	np = of_find_matching_node(NULL, apbmisc_match);
> >>> +	apbmisc_base = of_iomap(np, 0);
> >>> +	if (!apbmisc_base) {
> >>> +		pr_warn("ioremap tegra apbmisc failed. using %08x instead\n",
> >>> +			APBMISC_BASE);
> >>> +		apbmisc_base = ioremap(APBMISC_BASE, APBMISC_SIZE);
> >>> +	}
> >>> +
> >>> +	id = tegra_read_chipid();
> >>> +	tegra_chip_id = (id >> 8) & 0xff;
> >>
> >> So there's a fallback using APBMIS_BASE above if the node is missing, so
> >> those last 2 lines always happen. However, if any of the following fail:
> >>
> >>> +	strapping_base = of_iomap(np, 0);
> >> ...
> >>> +	np = of_find_matching_node(NULL, tegra_fuse_match);
> >> ...
> >>> +	np = of_find_matching_node(NULL, car_match);
> >>
> >> Then this doesn't get executed:
> >>
> >>> +	tegra_get_revision(id);
> >>
> >> Isn't that important?
> > 
> > No. It's only used to populate /sys/devices/soc0/revision. I don't think
> > that's particularly important.
> 
> But it's a feature that works today. Why should we break it?
> 

I don't expect people to not update their DT actually...

> >> I guess that can't run if the lookup for tegra_fuse_match isn't
> >> successful, since that tegra_get_revision may call
> >> tegra20_spare_fuse_early() which uses fuse_base which is set up in
> >> response to succesfully calling on of those node lookups. Isn't a
> >> fallback needed there too?
> > 
> > tegra20_spare_fuse_early() will not be called if fuse_base is NULL.
> 
> Oh yes. We can at least call the function then even if the fuses can't
> be mapped.
> 
> But to avoid regressions, we should simply make sure the fuses can be
> mapped.
> 
> >> I'm also a bit concerned that the driver probes, rather than the early
> >> function tegra_init_fuse(), are doing things like setting up the speedo
> >> data initialization, randomness addition, etc. For one, those won't
> >> happen any more unless the DT nodes are present, and secondly,
> >> triggering all those from driver probe rather than a function that's
> >> called from the machine descriptor makes guaranteeing the timing
> >> problematic.
> > 
> > Today there are no users of the speedo ID in upstream.
> 
> Well, except people reading kernel log messages. Accurate speedo-related
> log messages have help pin-point a problem or two in the past.
> 

It's not gone if you use the new DT, it's unfortunate, but I don't think that
should be a problem. We've had the same when introducing clocks in DT for
example.

> > Looking at chromeos
> > the users of the speedo ID are: CPU dvfs, GPU dvfs and sdhci. The last 2 also
> > need regulators and therefor will need to support deferred probing anyway.
> > CPU dvfs isn't critical at all, we don't care when it gets initialized. So
> > deferred probe is fine.
> 
> What condition will those modules/drivers use to defer their probe?
> 

The function to get the speedo ID will return an appropriate error.

> > sdhci needs this for faster modes I guess which will also need extra DT
> > properties looking at the chromeos driver. The others definitely will need
> > an updated DT. For randomness I haven't seen any appreciable difference in when
> > the 'random: nonblocking pool is initialized' message appears between having
> > the randomness addition or not. Most likely the bulk of the randomness comes
> > from serial interrupts rather than the fuse data. So I don't think the move to
> > a driver probe will cause any problem. Nor do I think the lack of an updated
> > DT will cause problems.
> 
> But what advantage do we have by actively changing it?

We need to move the code anyway when we will have 64bit SoCs. Using DT also
allows us to reuse the code even when the base address changes in the future.
If it weren't for Tegra20 A03p, we could also drop the hack to enable the
clocks directly, but use CCF instead.

Cheers,

Peter.
Stephen Warren June 9, 2014, 6:29 p.m. UTC | #7
On 06/06/2014 01:35 AM, Peter De Schrijver wrote:
> On Fri, Jun 06, 2014 at 12:54:00AM +0200, Stephen Warren wrote:
...
>>> No. It's only used to populate /sys/devices/soc0/revision. I don't think
>>> that's particularly important.
>>
>> But it's a feature that works today. Why should we break it?
> 
> I don't expect people to not update their DT actually...

But that's not how DT works; old DTs must continue to work.

>>> sdhci needs this for faster modes I guess which will also need extra DT
>>> properties looking at the chromeos driver. The others definitely will need
>>> an updated DT. For randomness I haven't seen any appreciable difference in when
>>> the 'random: nonblocking pool is initialized' message appears between having
>>> the randomness addition or not. Most likely the bulk of the randomness comes
>>> from serial interrupts rather than the fuse data. So I don't think the move to
>>> a driver probe will cause any problem. Nor do I think the lack of an updated
>>> DT will cause problems.
>>
>> But what advantage do we have by actively changing it?
> 
> We need to move the code anyway when we will have 64bit SoCs. Using DT also
> allows us to reuse the code even when the base address changes in the future.
> If it weren't for Tegra20 A03p, we could also drop the hack to enable the
> clocks directly, but use CCF instead.

Sure we need to move the code out of arch/arm so it can be shared with
arm64. However, that doesn't imply that we need to change anything about
the way the code works or is initialized; we can still do all the
initialization in response to a function call from the arch/board
support, and not in response to driver probe.
Mikko Perttunen June 11, 2014, 12:47 p.m. UTC | #8
On 05/06/14 16:09, Peter De Schrijver wrote:
...
> +int tegra_fuse_readl(u32 offset, u32 *val)
> +{
> +       if (!fuse_readl)
> +               return -ENXIO;
> +
> +       *val = fuse_readl(offset);
> +
> +       return 0;
> +}
> +

-EPROBE_DEFER would be a better error value, so that drivers can work 
even if they are initially probed before the fuse driver. Of course, if 
the fuse initialization is moved into machine init then this is a non-issue.
Peter De Schrijver June 11, 2014, 3:25 p.m. UTC | #9
On Wed, Jun 11, 2014 at 02:47:31PM +0200, Mikko Perttunen wrote:
> On 05/06/14 16:09, Peter De Schrijver wrote:
> ...
> > +int tegra_fuse_readl(u32 offset, u32 *val)
> > +{
> > +       if (!fuse_readl)
> > +               return -ENXIO;
> > +
> > +       *val = fuse_readl(offset);
> > +
> > +       return 0;
> > +}
> > +
> 
> -EPROBE_DEFER would be a better error value, so that drivers can work 

Ok.

> even if they are initially probed before the fuse driver. Of course, if 
> the fuse initialization is moved into machine init then this is a non-issue.

The exported function will always be initialized later because on Tegra20 it
requires APB DMA to be available. If you read the fuses directly, the system
sometimes hangs.

Cheers,

Peter.
Stephen Warren June 11, 2014, 3:58 p.m. UTC | #10
On 06/11/2014 09:25 AM, Peter De Schrijver wrote:
> On Wed, Jun 11, 2014 at 02:47:31PM +0200, Mikko Perttunen wrote:
>> On 05/06/14 16:09, Peter De Schrijver wrote:
>> ...
>>> +int tegra_fuse_readl(u32 offset, u32 *val)
>>> +{
>>> +       if (!fuse_readl)
>>> +               return -ENXIO;
>>> +
>>> +       *val = fuse_readl(offset);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>
>> -EPROBE_DEFER would be a better error value, so that drivers can work 
> 
> Ok.
> 
>> even if they are initially probed before the fuse driver. Of course, if 
>> the fuse initialization is moved into machine init then this is a non-issue.
> 
> The exported function will always be initialized later because on Tegra20 it
> requires APB DMA to be available. If you read the fuses directly, the system
> sometimes hangs.

That's not true in the current code. IIRC, the bug was that *if* an APB
DMA access to anything and a CPU access to the fuses happen at the same
time, then there can be a hang. As such, the current fuse code accesses
the fuses directly (without potential for a hang) if the APB DMA driver
is not available, but once the driver becomes available, it reads the
fuses through DMA instead. Does the new code not do that?
Peter De Schrijver June 11, 2014, 4:19 p.m. UTC | #11
On Wed, Jun 11, 2014 at 05:58:28PM +0200, Stephen Warren wrote:
> On 06/11/2014 09:25 AM, Peter De Schrijver wrote:
> > On Wed, Jun 11, 2014 at 02:47:31PM +0200, Mikko Perttunen wrote:
> >> On 05/06/14 16:09, Peter De Schrijver wrote:
> >> ...
> >>> +int tegra_fuse_readl(u32 offset, u32 *val)
> >>> +{
> >>> +       if (!fuse_readl)
> >>> +               return -ENXIO;
> >>> +
> >>> +       *val = fuse_readl(offset);
> >>> +
> >>> +       return 0;
> >>> +}
> >>> +
> >>
> >> -EPROBE_DEFER would be a better error value, so that drivers can work 
> > 
> > Ok.
> > 
> >> even if they are initially probed before the fuse driver. Of course, if 
> >> the fuse initialization is moved into machine init then this is a non-issue.
> > 
> > The exported function will always be initialized later because on Tegra20 it
> > requires APB DMA to be available. If you read the fuses directly, the system
> > sometimes hangs.
> 
> That's not true in the current code. IIRC, the bug was that *if* an APB
> DMA access to anything and a CPU access to the fuses happen at the same
> time, then there can be a hang. As such, the current fuse code accesses
> the fuses directly (without potential for a hang) if the APB DMA driver
> is not available, but once the driver becomes available, it reads the
> fuses through DMA instead. Does the new code not do that?
> 

I'm not so sure about that. I have seen the hang when dumping all fuses using
sysfs in an otherwise idle system booted from initrd. I don't think there
should be any APB DMA activity going on then?

Cheers,

Peter.
Stephen Warren June 11, 2014, 4:29 p.m. UTC | #12
On 06/11/2014 10:19 AM, Peter De Schrijver wrote:
> On Wed, Jun 11, 2014 at 05:58:28PM +0200, Stephen Warren wrote:
>> On 06/11/2014 09:25 AM, Peter De Schrijver wrote:
>>> On Wed, Jun 11, 2014 at 02:47:31PM +0200, Mikko Perttunen wrote:
>>>> On 05/06/14 16:09, Peter De Schrijver wrote:
>>>> ...
>>>>> +int tegra_fuse_readl(u32 offset, u32 *val)
>>>>> +{
>>>>> +       if (!fuse_readl)
>>>>> +               return -ENXIO;
>>>>> +
>>>>> +       *val = fuse_readl(offset);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>
>>>> -EPROBE_DEFER would be a better error value, so that drivers can work 
>>>
>>> Ok.
>>>
>>>> even if they are initially probed before the fuse driver. Of course, if 
>>>> the fuse initialization is moved into machine init then this is a non-issue.
>>>
>>> The exported function will always be initialized later because on Tegra20 it
>>> requires APB DMA to be available. If you read the fuses directly, the system
>>> sometimes hangs.
>>
>> That's not true in the current code. IIRC, the bug was that *if* an APB
>> DMA access to anything and a CPU access to the fuses happen at the same
>> time, then there can be a hang. As such, the current fuse code accesses
>> the fuses directly (without potential for a hang) if the APB DMA driver
>> is not available, but once the driver becomes available, it reads the
>> fuses through DMA instead. Does the new code not do that?
>>
> 
> I'm not so sure about that. I have seen the hang when dumping all fuses using
> sysfs in an otherwise idle system booted from initrd. I don't think there
> should be any APB DMA activity going on then?

Hmm. Perhaps I'm misremembering the trigger for the bug then. Still, the
existing code works as I described. Perhaps that's dangerous and it
shouldn't though. Either way, I think we should have a standalone commit
that removes tegra_apb_readl_using_dma()'s fallback to
tegra_apb_readl_direct(), so any behaviour change that causes a problem
can be bisected easily.
diff mbox

Patch

diff --git a/Documentation/ABI/testing/sysfs-driver-tegra-fuse b/Documentation/ABI/testing/sysfs-driver-tegra-fuse
new file mode 100644
index 0000000..69f5af6
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-tegra-fuse
@@ -0,0 +1,11 @@ 
+What:		/sys/devices/*/<our-device>/fuse
+Date:		February 2014
+Contact:	Peter De Schrijver <pdeschrijver@nvidia.com>
+Description:	read-only access to the efuses on Tegra20, Tegra30, Tegra114
+		and Tegra124 SoC's from NVIDIA. The efuses contain write once
+		data programmed at the factory. The data is layed out in 32bit
+		words in LSB first format. Each bit represents a single value
+		as decoded from the fuse registers. Bits order/assignment
+		exactly matches the HW registers, including any unused bits.
+Users:		any user space application which wants to read the efuses on
+		Tegra SoC's
diff --git a/drivers/misc/fuse/Makefile b/drivers/misc/fuse/Makefile
new file mode 100644
index 0000000..0679c4f
--- /dev/null
+++ b/drivers/misc/fuse/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
diff --git a/drivers/misc/fuse/tegra/Makefile b/drivers/misc/fuse/tegra/Makefile
new file mode 100644
index 0000000..3a5b37f
--- /dev/null
+++ b/drivers/misc/fuse/tegra/Makefile
@@ -0,0 +1,7 @@ 
+obj-y					+= fuse-tegra.o
+obj-y					+= fuse-tegra30.o
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= fuse-tegra20.o
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC)		+= speedo-tegra20.o
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC)		+= speedo-tegra30.o
+obj-$(CONFIG_ARCH_TEGRA_114_SOC)	+= speedo-tegra114.o
+obj-$(CONFIG_ARCH_TEGRA_124_SOC)	+= speedo-tegra124.o
diff --git a/drivers/misc/fuse/tegra/fuse-tegra.c b/drivers/misc/fuse/tegra/fuse-tegra.c
new file mode 100644
index 0000000..e26501c
--- /dev/null
+++ b/drivers/misc/fuse/tegra/fuse-tegra.c
@@ -0,0 +1,250 @@ 
+/*
+ * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/kobject.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/io.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define APBMISC_BASE	0x70000800
+#define APBMISC_SIZE	0x64
+
+int tegra_chip_id;
+enum tegra_revision tegra_revision;
+
+/*
+ * The BCT to use at boot is specified by board straps that can be read
+ * through a APB misc register and decoded. 2 bits, i.e. 4 possible BCTs.
+ */
+
+#define TEGRA_RAM_ID_SHIFT	4
+#define TEGRA_RAM_ID_MASK	3
+
+static u32 (*fuse_readl)(const unsigned int offset);
+static int fuse_size;
+static void __iomem *fuse_base;
+static void __iomem *apbmisc_base;
+static void __iomem *strapping_base;
+
+static const char *tegra_revision_name[TEGRA_REVISION_MAX] = {
+	[TEGRA_REVISION_UNKNOWN] = "unknown",
+	[TEGRA_REVISION_A01]     = "A01",
+	[TEGRA_REVISION_A02]     = "A02",
+	[TEGRA_REVISION_A03]     = "A03",
+	[TEGRA_REVISION_A03p]    = "A03 prime",
+	[TEGRA_REVISION_A04]     = "A04",
+};
+
+static u8 fuse_readb(const unsigned int offset)
+{
+	u32 val;
+
+	val = fuse_readl(round_down(offset, 4));
+	val >>= (offset % 4) * 8;
+	val &= 0xff;
+
+	return val;
+}
+
+static ssize_t fuse_read(struct file *fd, struct kobject *kobj,
+			struct bin_attribute *attr, char *buf,
+			loff_t pos, size_t size)
+{
+	int i;
+
+	if (pos < 0 || pos >= fuse_size)
+		return 0;
+
+	if (size > fuse_size - pos)
+		size = fuse_size - pos;
+
+	for (i = 0; i < size; i++)
+		buf[i] = fuse_readb(pos + i);
+
+	return i;
+}
+
+static struct bin_attribute fuse_bin_attr = {
+	.attr = { .name = "fuse", .mode = S_IRUGO, },
+	.read = fuse_read,
+};
+
+static const struct of_device_id tegra_fuse_match[] __initconst = {
+	{ .compatible = "nvidia,tegra20-efuse", },
+	{ .compatible = "nvidia,tegra30-efuse", },
+	{ .compatible = "nvidia,tegra114-efuse", },
+	{ .compatible = "nvidia,tegra124-efuse", },
+	{},
+};
+
+static const struct of_device_id car_match[] __initconst = {
+	{ .compatible = "nvidia,tegra20-car", },
+	{ .compatible = "nvidia,tegra30-car", },
+	{ .compatible = "nvidia,tegra114-car", },
+	{ .compatible = "nvidia,tegra124-car", },
+	{},
+};
+
+static const struct of_device_id apbmisc_match[] __initconst = {
+	{ .compatible = "nvidia,tegra20-apbmisc", },
+	{},
+};
+
+static void tegra_get_revision(u32 id)
+{
+	u32 minor_rev = (id >> 16) & 0xf;
+
+	switch (minor_rev) {
+	case 1:
+		tegra_revision = TEGRA_REVISION_A01;
+		break;
+	case 2:
+		tegra_revision = TEGRA_REVISION_A02;
+		break;
+	case 3:
+		if (tegra_chip_id == TEGRA20 && fuse_base &&
+			(tegra20_spare_fuse_early(18, fuse_base) ||
+			 tegra20_spare_fuse_early(19, fuse_base)))
+			tegra_revision = TEGRA_REVISION_A03p;
+		else
+			tegra_revision = TEGRA_REVISION_A03;
+		break;
+	case 4:
+		tegra_revision = TEGRA_REVISION_A04;
+		break;
+	default:
+		tegra_revision = TEGRA_REVISION_UNKNOWN;
+	}
+}
+
+static void tegra_enable_fuse_clk(void __iomem *base)
+{
+	u32 reg;
+
+	reg = readl_relaxed(base + 0x48);
+	reg |= 1 << 28;
+	writel(reg, base + 0x48);
+
+	/*
+	 * Enable FUSE clock. This needs to be hardcoded because the clock
+	 * subsystem is not active during early boot.
+	 */
+	reg = readl(base + 0x14);
+	reg |= 1 << 7;
+	writel(reg, base + 0x14);
+}
+
+u32 tegra_read_straps(void)
+{
+	if (strapping_base)
+		return readl(strapping_base);
+	else
+		return 0;
+}
+
+u32 tegra_read_chipid(void)
+{
+	return readl_relaxed(apbmisc_base + 4);
+}
+
+int tegra_fuse_readl(u32 offset, u32 *val)
+{
+	if (!fuse_readl)
+		return -ENXIO;
+
+	*val = fuse_readl(offset);
+
+	return 0;
+}
+
+int tegra_fuse_create_sysfs(struct device *dev, int size,
+		     u32 (*readl)(const unsigned int offset),
+		     struct tegra_sku_info *sku_info)
+{
+	int err;
+
+	if (fuse_size)
+		return -ENODEV;
+
+	fuse_bin_attr.size = size;
+	fuse_bin_attr.read = fuse_read;
+
+	fuse_size = size;
+	fuse_readl = readl;
+
+	err = device_create_bin_file(dev, &fuse_bin_attr);
+	if (err)
+		return err;
+
+	dev_info(dev,
+		"Tegra Revision: %s SKU: %d CPU Process: %d Core Process: %d\n",
+		tegra_revision_name[sku_info->revision],
+		sku_info->sku_id, sku_info->cpu_process_id,
+		sku_info->core_process_id);
+
+	return 0;
+}
+
+void __init tegra_init_fuse(void)
+{
+	struct device_node *np;
+	u32 id;
+	void __iomem *car_base;
+
+	np = of_find_matching_node(NULL, apbmisc_match);
+	apbmisc_base = of_iomap(np, 0);
+	if (!apbmisc_base) {
+		pr_warn("ioremap tegra apbmisc failed. using %08x instead\n",
+			APBMISC_BASE);
+		apbmisc_base = ioremap(APBMISC_BASE, APBMISC_SIZE);
+	}
+
+	id = tegra_read_chipid();
+	tegra_chip_id = (id >> 8) & 0xff;
+
+	strapping_base = of_iomap(np, 0);
+	if (!strapping_base) {
+		pr_err("ioremap tegra strapping_base failed\n");
+		return;
+	}
+
+	np = of_find_matching_node(NULL, tegra_fuse_match);
+	fuse_base = of_iomap(np, 0);
+	if (!fuse_base) {
+		pr_err("ioremap tegra fuse failed\n");
+		return;
+	}
+
+	np = of_find_matching_node(NULL, car_match);
+	car_base = of_iomap(np, 0);
+	if (car_base) {
+		tegra_enable_fuse_clk(car_base);
+		iounmap(car_base);
+	} else {
+		pr_err("Could not enable fuse clk. ioremap tegra car failed.\n");
+		iounmap(fuse_base);
+		return;
+	}
+
+	tegra_get_revision(id);
+}
diff --git a/drivers/misc/fuse/tegra/fuse-tegra20.c b/drivers/misc/fuse/tegra/fuse-tegra20.c
new file mode 100644
index 0000000..852da92
--- /dev/null
+++ b/drivers/misc/fuse/tegra/fuse-tegra20.c
@@ -0,0 +1,134 @@ 
+/*
+ * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Based on drivers/misc/eeprom/sunxi_sid.c
+ */
+
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/random.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define FUSE_BEGIN	0x100
+#define FUSE_SIZE	0x1f8
+#define FUSE_SKU_INFO	0x10
+#define FUSE_UID_LOW	0x08
+#define FUSE_UID_HIGH	0x0c
+
+static phys_addr_t fuse_phys;
+static struct clk *fuse_clk;
+static struct tegra_sku_info sku_info;
+
+static u32 tegra20_fuse_readl(const unsigned int offset)
+{
+	int ret;
+	u32 val;
+
+	clk_prepare_enable(fuse_clk);
+
+	ret = tegra_apb_readl_using_dma(fuse_phys + FUSE_BEGIN + offset, &val);
+
+	clk_disable_unprepare(fuse_clk);
+
+	return (ret < 0) ? 0 : val;
+}
+
+static void tegra20_fuse_add_randomness(void)
+{
+	u32 randomness[7];
+
+	randomness[0] = tegra20_fuse_readl(FUSE_SKU_INFO);
+	randomness[1] = tegra_read_straps();
+	randomness[2] = tegra_read_chipid();
+	randomness[3] = sku_info.cpu_process_id << 16;
+	randomness[3] |= sku_info.core_process_id;
+	randomness[4] = sku_info.cpu_speedo_id << 16 | sku_info.soc_speedo_id;
+	randomness[5] = tegra20_fuse_readl(FUSE_UID_LOW);
+	randomness[6] = tegra20_fuse_readl(FUSE_UID_HIGH);
+
+	add_device_randomness(randomness, sizeof(randomness));
+}
+
+bool tegra20_spare_fuse(int spare_bit)
+{
+	u32 offset = spare_bit * 4 + 0x100;
+
+	return tegra20_fuse_readl(offset) & 1;
+}
+
+bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base)
+{
+	u32 offset = spare_bit * 4 + 0x100;
+
+	return readl_relaxed(fuse_base + FUSE_BEGIN + offset);
+}
+
+static const struct of_device_id tegra20_fuse_of_match[] = {
+	{ .compatible = "nvidia,tegra20-efuse" },
+	{},
+};
+
+static int tegra20_fuse_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+
+	fuse_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(fuse_clk)) {
+		dev_err(&pdev->dev, "missing clock");
+		return PTR_ERR(fuse_clk);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+	fuse_phys = res->start;
+
+	sku_info.revision = tegra_revision;
+	tegra20_init_speedo_data(&sku_info, &pdev->dev);
+	dev_dbg(&pdev->dev, "Soc Speedo ID %d", sku_info.soc_speedo_id);
+
+	tegra20_fuse_add_randomness();
+
+	if (tegra_fuse_create_sysfs(&pdev->dev, FUSE_SIZE, tegra20_fuse_readl,
+			     &sku_info))
+		return -ENODEV;
+
+	dev_dbg(&pdev->dev, "loaded\n");
+
+	return 0;
+}
+
+static struct platform_driver tegra20_fuse_driver = {
+	.probe = tegra20_fuse_probe,
+	.driver = {
+		.name = "tegra20_fuse",
+		.owner = THIS_MODULE,
+		.of_match_table = tegra20_fuse_of_match,
+	}
+};
+
+static int __init tegra20_fuse_init(void)
+{
+	return platform_driver_register(&tegra20_fuse_driver);
+}
+postcore_initcall(tegra20_fuse_init);
diff --git a/drivers/misc/fuse/tegra/fuse-tegra30.c b/drivers/misc/fuse/tegra/fuse-tegra30.c
new file mode 100644
index 0000000..4aa6490
--- /dev/null
+++ b/drivers/misc/fuse/tegra/fuse-tegra30.c
@@ -0,0 +1,177 @@ 
+/*
+ * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/random.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define FUSE_BEGIN	0x100
+
+#define FUSE_SKU_INFO		0x10
+
+/* Tegra30 and later */
+#define FUSE_VENDOR_CODE	0x100
+#define FUSE_FAB_CODE		0x104
+#define FUSE_LOT_CODE_0		0x108
+#define FUSE_LOT_CODE_1		0x10c
+#define FUSE_WAFER_ID		0x110
+#define FUSE_X_COORDINATE	0x114
+#define FUSE_Y_COORDINATE	0x118
+
+#define FUSE_HAS_REVISION_INFO	BIT(0)
+
+struct tegra_fuse_info {
+	int	size;
+	int	spare_bit;
+	void	(*init_speedo_data)(struct tegra_sku_info *sku_info,
+				    struct device *dev);
+};
+
+static void __iomem *fuse_base;
+static struct clk *fuse_clk;
+static struct tegra_fuse_info *fuse_info;
+static struct tegra_sku_info sku_info;
+
+u32 tegra30_fuse_readl(const unsigned int offset)
+{
+	u32 val;
+
+	clk_prepare_enable(fuse_clk);
+
+	val = readl_relaxed(fuse_base + FUSE_BEGIN + offset);
+
+	clk_disable_unprepare(fuse_clk);
+
+	return val;
+}
+
+bool tegra30_spare_fuse(int spare_bit)
+{
+	u32 offset = fuse_info->spare_bit + spare_bit * 4;
+
+	return tegra30_fuse_readl(offset) & 1;
+}
+
+static void tegra30_fuse_add_randomness(void)
+{
+	u32 randomness[12];
+
+	randomness[0] = tegra30_fuse_readl(FUSE_SKU_INFO);
+	randomness[1] = tegra_read_straps();
+	randomness[2] = tegra_read_chipid();
+	randomness[3] = sku_info.cpu_process_id << 16;
+	randomness[3] |= sku_info.core_process_id;
+	randomness[4] = sku_info.cpu_speedo_id << 16;
+	randomness[4] |= sku_info.soc_speedo_id;
+	randomness[5] = tegra30_fuse_readl(FUSE_VENDOR_CODE);
+	randomness[6] = tegra30_fuse_readl(FUSE_FAB_CODE);
+	randomness[7] = tegra30_fuse_readl(FUSE_LOT_CODE_0);
+	randomness[8] = tegra30_fuse_readl(FUSE_LOT_CODE_1);
+	randomness[9] = tegra30_fuse_readl(FUSE_WAFER_ID);
+	randomness[10] = tegra30_fuse_readl(FUSE_X_COORDINATE);
+	randomness[11] = tegra30_fuse_readl(FUSE_Y_COORDINATE);
+
+	add_device_randomness(randomness, sizeof(randomness));
+}
+
+static struct tegra_fuse_info tegra30_info = {
+	.size			= 0x2a4,
+	.spare_bit		= 0x144,
+	.init_speedo_data	= tegra30_init_speedo_data,
+};
+
+static struct tegra_fuse_info tegra114_info = {
+	.size			= 0x2a0,
+	.init_speedo_data	= tegra114_init_speedo_data,
+};
+
+static struct tegra_fuse_info tegra124_info = {
+	.size			= 0x300,
+	.init_speedo_data	= tegra124_init_speedo_data,
+};
+
+static const struct of_device_id tegra30_fuse_of_match[] = {
+	{ .compatible = "nvidia,tegra30-efuse", .data = &tegra30_info },
+	{ .compatible = "nvidia,tegra114-efuse", .data = &tegra114_info },
+	{ .compatible = "nvidia,tegra124-efuse", .data = &tegra124_info },
+	{},
+};
+
+static int tegra30_fuse_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_dev_id;
+	struct resource *res;
+
+	of_dev_id = of_match_device(tegra30_fuse_of_match, &pdev->dev);
+	if (!of_dev_id)
+		return -ENODEV;
+	fuse_info = (struct tegra_fuse_info *)of_dev_id->data;
+
+	fuse_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(fuse_clk)) {
+		dev_err(&pdev->dev, "missing clock");
+		return PTR_ERR(fuse_clk);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	fuse_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(fuse_base)) {
+		dev_err(&pdev->dev, "unable to map base address");
+		return PTR_ERR(fuse_base);
+	}
+
+	sku_info.revision = tegra_revision;
+	fuse_info->init_speedo_data(&sku_info, &pdev->dev);
+	dev_dbg(&pdev->dev, "CPU Speedo ID %d, Soc Speedo ID %d",
+		sku_info.cpu_speedo_id, sku_info.soc_speedo_id);
+
+	tegra30_fuse_add_randomness();
+
+	platform_set_drvdata(pdev, NULL);
+
+	if (tegra_fuse_create_sysfs(&pdev->dev, fuse_info->size,
+				    tegra30_fuse_readl, &sku_info))
+		return -ENODEV;
+
+	dev_dbg(&pdev->dev, "loaded\n");
+
+	return 0;
+}
+
+static struct platform_driver tegra30_fuse_driver = {
+	.probe = tegra30_fuse_probe,
+	.driver = {
+		.name = "tegra_fuse",
+		.owner = THIS_MODULE,
+		.of_match_table = tegra30_fuse_of_match,
+	}
+};
+
+static int __init tegra30_fuse_init(void)
+{
+	return platform_driver_register(&tegra30_fuse_driver);
+}
+postcore_initcall(tegra30_fuse_init);
+
diff --git a/drivers/misc/fuse/tegra/fuse.h b/drivers/misc/fuse/tegra/fuse.h
new file mode 100644
index 0000000..0baf82b
--- /dev/null
+++ b/drivers/misc/fuse/tegra/fuse.h
@@ -0,0 +1,82 @@ 
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Author:
+ *	Colin Cross <ccross@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __DRIVERS_MISC_TEGRA_FUSE_H
+#define __DRIVERS_MISC_TEGRA_FUSE_H
+
+struct tegra_sku_info {
+	int sku_id;
+	int cpu_process_id;
+	int cpu_speedo_id;
+	int cpu_speedo_value;
+	int cpu_iddq_value;
+	int core_process_id;
+	int soc_speedo_id;
+	int gpu_speedo_id;
+	int gpu_process_id;
+	int gpu_speedo_value;
+	enum tegra_revision revision;
+};
+
+int tegra_fuse_create_sysfs(struct device *dev, int size,
+		     u32 (*readl)(const unsigned int offset),
+		     struct tegra_sku_info *sku_info);
+
+bool tegra30_spare_fuse(int bit);
+u32 tegra30_fuse_readl(const unsigned int offset);
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
+			      struct device *dev);
+bool tegra20_spare_fuse(int bit);
+bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base);
+#else
+static inline void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
+					    struct device *dev) {}
+static inline bool tegra20_spare_fuse(int bit) { return false; }
+static inline bool tegra20_spare_fuse_early(int spare_bit, void *fuse_base)
+{
+			return false;
+}
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA_3x_SOC
+void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
+			      struct device *dev);
+#else
+static inline void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
+					    struct device *dev) {}
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA_114_SOC
+void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
+			       struct device *dev);
+#else
+static inline void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
+					     struct device *dev) {}
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA_124_SOC
+void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
+			       struct device *dev);
+#else
+static inline void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
+					     struct device *dev) {}
+#endif
+
+#endif
diff --git a/drivers/misc/fuse/tegra/speedo-tegra114.c b/drivers/misc/fuse/tegra/speedo-tegra114.c
new file mode 100644
index 0000000..7be8ba5
--- /dev/null
+++ b/drivers/misc/fuse/tegra/speedo-tegra114.c
@@ -0,0 +1,110 @@ 
+/*
+ * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/bug.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define CORE_PROCESS_CORNERS_NUM	2
+#define CPU_PROCESS_CORNERS_NUM		2
+
+enum {
+	THRESHOLD_INDEX_0,
+	THRESHOLD_INDEX_1,
+	THRESHOLD_INDEX_COUNT,
+};
+
+static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
+	{1123,     UINT_MAX},
+	{0,        UINT_MAX},
+};
+
+static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
+	{1695,     UINT_MAX},
+	{0,        UINT_MAX},
+};
+
+static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
+				  int *threshold, struct device *dev)
+{
+	u32 tmp;
+	u32 sku = sku_info->sku_id;
+	enum tegra_revision rev = sku_info->revision;
+
+	switch (sku) {
+	case 0x00:
+	case 0x10:
+	case 0x05:
+	case 0x06:
+		sku_info->cpu_speedo_id = 1;
+		sku_info->soc_speedo_id = 0;
+		*threshold = THRESHOLD_INDEX_0;
+		break;
+
+	case 0x03:
+	case 0x04:
+		sku_info->cpu_speedo_id = 2;
+		sku_info->soc_speedo_id = 1;
+		*threshold = THRESHOLD_INDEX_1;
+		break;
+
+	default:
+		dev_err(dev, "Unknown SKU %d\n", sku);
+		sku_info->cpu_speedo_id = 0;
+		sku_info->soc_speedo_id = 0;
+		*threshold = THRESHOLD_INDEX_0;
+		break;
+	}
+
+	if (rev == TEGRA_REVISION_A01) {
+		tmp = tegra30_fuse_readl(0x270) << 1;
+		tmp |= tegra30_fuse_readl(0x26c);
+		if (!tmp)
+			sku_info->cpu_speedo_id = 0;
+	}
+}
+
+void tegra114_init_speedo_data(struct tegra_sku_info *sku_info,
+			       struct device *dev)
+{
+	u32 cpu_speedo_val;
+	u32 core_speedo_val;
+	int threshold;
+	int i;
+
+	BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+	BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+
+	rev_sku_to_speedo_ids(sku_info, &threshold, dev);
+
+	cpu_speedo_val = tegra30_fuse_readl(0x12c) + 1024;
+	core_speedo_val = tegra30_fuse_readl(0x134);
+
+	for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++)
+		if (cpu_speedo_val < cpu_process_speedos[threshold][i])
+			break;
+	sku_info->cpu_process_id = i;
+
+	for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++)
+		if (core_speedo_val < core_process_speedos[threshold][i])
+			break;
+	sku_info->core_process_id = i;
+}
diff --git a/drivers/misc/fuse/tegra/speedo-tegra124.c b/drivers/misc/fuse/tegra/speedo-tegra124.c
new file mode 100644
index 0000000..97bc6b6
--- /dev/null
+++ b/drivers/misc/fuse/tegra/speedo-tegra124.c
@@ -0,0 +1,180 @@ 
+/*
+ * Copyright (c) 2013-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/bug.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define CPU_PROCESS_CORNERS_NUM	2
+#define GPU_PROCESS_CORNERS_NUM	2
+#define CORE_PROCESS_CORNERS_NUM	2
+
+#define FUSE_CPU_SPEEDO_0	0x14
+#define FUSE_CPU_SPEEDO_1	0x2c
+#define FUSE_CPU_SPEEDO_2	0x30
+#define FUSE_SOC_SPEEDO_0	0x34
+#define FUSE_SOC_SPEEDO_1	0x38
+#define FUSE_SOC_SPEEDO_2	0x3c
+#define FUSE_CPU_IDDQ		0x18
+#define FUSE_SOC_IDDQ		0x40
+#define FUSE_GPU_IDDQ		0x128
+#define FUSE_FT_REV		0x28
+
+enum {
+	THRESHOLD_INDEX_0,
+	THRESHOLD_INDEX_1,
+	THRESHOLD_INDEX_COUNT,
+};
+
+static int cpu_speedo_0_value;
+static int cpu_speedo_1_value;
+static int soc_speedo_0_value;
+static int soc_speedo_1_value;
+static int soc_speedo_2_value;
+static int cpu_iddq_value;
+static int gpu_iddq_value;
+static int soc_iddq_value;
+
+static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
+	{2190,	UINT_MAX},
+	{0,	UINT_MAX},
+};
+
+static const u32 gpu_process_speedos[][GPU_PROCESS_CORNERS_NUM] = {
+	{1965,	UINT_MAX},
+	{0,	UINT_MAX},
+};
+
+static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
+	{2101,	UINT_MAX},
+	{0,	UINT_MAX},
+};
+
+static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
+				  int *threshold, struct device *dev)
+{
+	int sku = sku_info->sku_id;
+
+	/* Assign to default */
+	sku_info->cpu_speedo_id = 0;
+	sku_info->soc_speedo_id = 0;
+	sku_info->gpu_speedo_id = 0;
+	*threshold = THRESHOLD_INDEX_0;
+
+	switch (sku) {
+	case 0x00: /* Eng sku */
+	case 0x0F:
+	case 0x23:
+		/* Using the default */
+		break;
+	case 0x83:
+		sku_info->cpu_speedo_id = 2;
+		break;
+
+	case 0x1F:
+	case 0x87:
+	case 0x27:
+		sku_info->cpu_speedo_id = 2;
+		sku_info->soc_speedo_id = 0;
+		sku_info->gpu_speedo_id = 1;
+		*threshold = THRESHOLD_INDEX_0;
+		break;
+	case 0x81:
+	case 0x21:
+	case 0x07:
+		sku_info->cpu_speedo_id = 1;
+		sku_info->soc_speedo_id = 1;
+		sku_info->gpu_speedo_id = 1;
+		*threshold = THRESHOLD_INDEX_1;
+		break;
+	case 0x49:
+	case 0x4A:
+	case 0x48:
+		sku_info->cpu_speedo_id = 4;
+		sku_info->soc_speedo_id = 2;
+		sku_info->gpu_speedo_id = 3;
+		*threshold = THRESHOLD_INDEX_1;
+		break;
+	default:
+		dev_err(dev, "Unknown SKU %d\n", sku);
+		/* Using the default for the error case */
+		break;
+	}
+}
+
+void tegra124_init_speedo_data(struct tegra_sku_info *sku_info,
+			       struct device *dev)
+{
+	int i;
+	int threshold;
+
+	BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+	BUILD_BUG_ON(ARRAY_SIZE(gpu_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+	BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+
+	cpu_speedo_0_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_0);
+	cpu_speedo_1_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_1);
+
+	/* GPU Speedo is stored in CPU_SPEEDO_2 */
+	sku_info->gpu_speedo_value = tegra30_fuse_readl(FUSE_CPU_SPEEDO_2);
+
+	soc_speedo_0_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_0);
+	soc_speedo_1_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_1);
+	soc_speedo_2_value = tegra30_fuse_readl(FUSE_SOC_SPEEDO_2);
+
+	cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);
+	soc_iddq_value = tegra30_fuse_readl(FUSE_SOC_IDDQ);
+	gpu_iddq_value = tegra30_fuse_readl(FUSE_GPU_IDDQ);
+
+	sku_info->cpu_speedo_value = cpu_speedo_0_value;
+
+	if (sku_info->cpu_speedo_value == 0) {
+		dev_warn(dev, "Warning: Speedo value not fused.\n");
+		WARN_ON(1);
+		return;
+	}
+
+	rev_sku_to_speedo_ids(sku_info, &threshold, dev);
+
+	sku_info->cpu_iddq_value = tegra30_fuse_readl(FUSE_CPU_IDDQ);
+
+	for (i = 0; i < GPU_PROCESS_CORNERS_NUM; i++)
+		if (sku_info->gpu_speedo_value <
+			gpu_process_speedos[threshold][i])
+			break;
+	sku_info->gpu_process_id = i;
+
+	for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++)
+		if (sku_info->cpu_speedo_value <
+			cpu_process_speedos[threshold][i])
+				break;
+	sku_info->cpu_process_id = i;
+
+	for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++)
+		if (soc_speedo_0_value <
+			core_process_speedos[threshold][i])
+			break;
+	sku_info->core_process_id = i;
+
+	dev_dbg(dev, "GPU Speedo ID=%d, Speedo Value=%d\n",
+			sku_info->gpu_speedo_id, sku_info->gpu_speedo_value);
+}
diff --git a/drivers/misc/fuse/tegra/speedo-tegra20.c b/drivers/misc/fuse/tegra/speedo-tegra20.c
new file mode 100644
index 0000000..af8e86a
--- /dev/null
+++ b/drivers/misc/fuse/tegra/speedo-tegra20.c
@@ -0,0 +1,110 @@ 
+/*
+ * Copyright (c) 2012-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/bug.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define CPU_SPEEDO_LSBIT		20
+#define CPU_SPEEDO_MSBIT		29
+#define CPU_SPEEDO_REDUND_LSBIT		30
+#define CPU_SPEEDO_REDUND_MSBIT		39
+#define CPU_SPEEDO_REDUND_OFFS	(CPU_SPEEDO_REDUND_MSBIT - CPU_SPEEDO_MSBIT)
+
+#define CORE_SPEEDO_LSBIT		40
+#define CORE_SPEEDO_MSBIT		47
+#define CORE_SPEEDO_REDUND_LSBIT	48
+#define CORE_SPEEDO_REDUND_MSBIT	55
+#define CORE_SPEEDO_REDUND_OFFS	(CORE_SPEEDO_REDUND_MSBIT - CORE_SPEEDO_MSBIT)
+
+#define SPEEDO_MULT			4
+
+#define PROCESS_CORNERS_NUM		4
+
+#define SPEEDO_ID_SELECT_0(rev)		((rev) <= 2)
+#define SPEEDO_ID_SELECT_1(sku)		\
+	(((sku) != 20) && ((sku) != 23) && ((sku) != 24) && \
+	 ((sku) != 27) && ((sku) != 28))
+
+enum {
+	SPEEDO_ID_0,
+	SPEEDO_ID_1,
+	SPEEDO_ID_2,
+	SPEEDO_ID_COUNT,
+};
+
+static const u32 cpu_process_speedos[][PROCESS_CORNERS_NUM] = {
+	{315, 366, 420, UINT_MAX},
+	{303, 368, 419, UINT_MAX},
+	{316, 331, 383, UINT_MAX},
+};
+
+static const u32 core_process_speedos[][PROCESS_CORNERS_NUM] = {
+	{165, 195, 224, UINT_MAX},
+	{165, 195, 224, UINT_MAX},
+	{165, 195, 224, UINT_MAX},
+};
+
+void tegra20_init_speedo_data(struct tegra_sku_info *sku_info,
+			      struct device *dev)
+{
+	u32 reg;
+	u32 val;
+	int i;
+
+	BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) != SPEEDO_ID_COUNT);
+	BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) != SPEEDO_ID_COUNT);
+
+	if (SPEEDO_ID_SELECT_0(sku_info->revision))
+		sku_info->soc_speedo_id = SPEEDO_ID_0;
+	else if (SPEEDO_ID_SELECT_1(sku_info->sku_id))
+		sku_info->soc_speedo_id = SPEEDO_ID_1;
+	else
+		sku_info->soc_speedo_id = SPEEDO_ID_2;
+
+	val = 0;
+	for (i = CPU_SPEEDO_MSBIT; i >= CPU_SPEEDO_LSBIT; i--) {
+		reg = tegra20_spare_fuse(i) |
+			tegra20_spare_fuse(i + CPU_SPEEDO_REDUND_OFFS);
+		val = (val << 1) | (reg & 0x1);
+	}
+	val = val * SPEEDO_MULT;
+	dev_dbg(dev, "CPU speedo value %u\n", val);
+
+	for (i = 0; i < (PROCESS_CORNERS_NUM - 1); i++) {
+		if (val <= cpu_process_speedos[sku_info->soc_speedo_id][i])
+			break;
+	}
+	sku_info->cpu_process_id = i;
+
+	val = 0;
+	for (i = CORE_SPEEDO_MSBIT; i >= CORE_SPEEDO_LSBIT; i--) {
+		reg = tegra20_spare_fuse(i) |
+			tegra20_spare_fuse(i + CORE_SPEEDO_REDUND_OFFS);
+		val = (val << 1) | (reg & 0x1);
+	}
+	val = val * SPEEDO_MULT;
+	dev_dbg(dev, "Core speedo value %u\n", val);
+
+	for (i = 0; i < (PROCESS_CORNERS_NUM - 1); i++) {
+		if (val <= core_process_speedos[sku_info->soc_speedo_id][i])
+			break;
+	}
+	sku_info->core_process_id = i;
+}
diff --git a/drivers/misc/fuse/tegra/speedo-tegra30.c b/drivers/misc/fuse/tegra/speedo-tegra30.c
new file mode 100644
index 0000000..11cf0cd
--- /dev/null
+++ b/drivers/misc/fuse/tegra/speedo-tegra30.c
@@ -0,0 +1,294 @@ 
+/*
+ * Copyright (c) 2012-2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/bug.h>
+#include <linux/tegra-soc.h>
+
+#include "fuse.h"
+
+#define CORE_PROCESS_CORNERS_NUM	1
+#define CPU_PROCESS_CORNERS_NUM		6
+
+#define FUSE_SPEEDO_CALIB_0	0x14
+#define FUSE_PACKAGE_INFO	0XFC
+#define FUSE_TEST_PROG_VER	0X28
+
+#define G_SPEEDO_BIT_MINUS1	58
+#define G_SPEEDO_BIT_MINUS1_R	59
+#define G_SPEEDO_BIT_MINUS2	60
+#define G_SPEEDO_BIT_MINUS2_R	61
+#define LP_SPEEDO_BIT_MINUS1	62
+#define LP_SPEEDO_BIT_MINUS1_R	63
+#define LP_SPEEDO_BIT_MINUS2	64
+#define LP_SPEEDO_BIT_MINUS2_R	65
+
+enum {
+	THRESHOLD_INDEX_0,
+	THRESHOLD_INDEX_1,
+	THRESHOLD_INDEX_2,
+	THRESHOLD_INDEX_3,
+	THRESHOLD_INDEX_4,
+	THRESHOLD_INDEX_5,
+	THRESHOLD_INDEX_6,
+	THRESHOLD_INDEX_7,
+	THRESHOLD_INDEX_8,
+	THRESHOLD_INDEX_9,
+	THRESHOLD_INDEX_10,
+	THRESHOLD_INDEX_11,
+	THRESHOLD_INDEX_COUNT,
+};
+
+static const u32 core_process_speedos[][CORE_PROCESS_CORNERS_NUM] = {
+	{180},
+	{170},
+	{195},
+	{180},
+	{168},
+	{192},
+	{180},
+	{170},
+	{195},
+	{180},
+	{180},
+	{180},
+};
+
+static const u32 cpu_process_speedos[][CPU_PROCESS_CORNERS_NUM] = {
+	{306, 338, 360, 376, UINT_MAX},
+	{295, 336, 358, 375, UINT_MAX},
+	{325, 325, 358, 375, UINT_MAX},
+	{325, 325, 358, 375, UINT_MAX},
+	{292, 324, 348, 364, UINT_MAX},
+	{324, 324, 348, 364, UINT_MAX},
+	{324, 324, 348, 364, UINT_MAX},
+	{295, 336, 358, 375, UINT_MAX},
+	{358, 358, 358, 358, 397, UINT_MAX},
+	{364, 364, 364, 364, 397, UINT_MAX},
+	{295, 336, 358, 375, 391, UINT_MAX},
+	{295, 336, 358, 375, 391, UINT_MAX},
+};
+
+static int threshold_index;
+static int package_id;
+
+static void fuse_speedo_calib(u32 *speedo_g, u32 *speedo_lp,
+			      struct device *dev)
+{
+	u32 reg;
+	int ate_ver;
+	int bit_minus1;
+	int bit_minus2;
+
+	reg = tegra30_fuse_readl(FUSE_SPEEDO_CALIB_0);
+
+	*speedo_lp = (reg & 0xFFFF) * 4;
+	*speedo_g = ((reg >> 16) & 0xFFFF) * 4;
+
+	ate_ver = tegra30_fuse_readl(FUSE_TEST_PROG_VER);
+	dev_dbg(dev, "ATE prog ver %d.%d\n", ate_ver/10, ate_ver%10);
+
+	if (ate_ver >= 26) {
+		bit_minus1 = tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS1);
+		bit_minus1 |= tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS1_R);
+		bit_minus2 = tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS2);
+		bit_minus2 |= tegra30_spare_fuse(LP_SPEEDO_BIT_MINUS2_R);
+		*speedo_lp |= (bit_minus1 << 1) | bit_minus2;
+
+		bit_minus1 = tegra30_spare_fuse(G_SPEEDO_BIT_MINUS1);
+		bit_minus1 |= tegra30_spare_fuse(G_SPEEDO_BIT_MINUS1_R);
+		bit_minus2 = tegra30_spare_fuse(G_SPEEDO_BIT_MINUS2);
+		bit_minus2 |= tegra30_spare_fuse(G_SPEEDO_BIT_MINUS2_R);
+		*speedo_g |= (bit_minus1 << 1) | bit_minus2;
+	} else {
+		*speedo_lp |= 0x3;
+		*speedo_g |= 0x3;
+	}
+}
+
+static void rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info,
+				  struct device *dev)
+{
+	switch (sku_info->revision) {
+	case TEGRA_REVISION_A01:
+		sku_info->cpu_speedo_id = 0;
+		sku_info->soc_speedo_id = 0;
+		threshold_index = THRESHOLD_INDEX_0;
+		break;
+	case TEGRA_REVISION_A02:
+	case TEGRA_REVISION_A03:
+		switch (sku_info->sku_id) {
+		case 0x87:
+		case 0x82:
+			sku_info->cpu_speedo_id = 1;
+			sku_info->soc_speedo_id = 1;
+			threshold_index = THRESHOLD_INDEX_1;
+			break;
+		case 0x81:
+			switch (package_id) {
+			case 1:
+				sku_info->cpu_speedo_id = 2;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_2;
+				break;
+			case 2:
+				sku_info->cpu_speedo_id = 4;
+				sku_info->soc_speedo_id = 1;
+				threshold_index = THRESHOLD_INDEX_7;
+				break;
+			default:
+				dev_err(dev, "Unknown pkg %d\n", package_id);
+				BUG();
+				break;
+			}
+			break;
+		case 0x80:
+			switch (package_id) {
+			case 1:
+				sku_info->cpu_speedo_id = 5;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_8;
+				break;
+			case 2:
+				sku_info->cpu_speedo_id = 6;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_9;
+				break;
+			default:
+				dev_err(dev, "Unknown pkg %d\n", package_id);
+				BUG();
+				break;
+			}
+			break;
+		case 0x83:
+			switch (package_id) {
+			case 1:
+				sku_info->cpu_speedo_id = 7;
+				sku_info->soc_speedo_id = 1;
+				threshold_index = THRESHOLD_INDEX_10;
+				break;
+			case 2:
+				sku_info->cpu_speedo_id = 3;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_3;
+				break;
+			default:
+				dev_err(dev, "Unknown pkg %d\n", package_id);
+				BUG();
+				break;
+			}
+			break;
+		case 0x8F:
+			sku_info->cpu_speedo_id = 8;
+			sku_info->soc_speedo_id = 1;
+			threshold_index = THRESHOLD_INDEX_11;
+			break;
+		case 0x08:
+			sku_info->cpu_speedo_id = 1;
+			sku_info->soc_speedo_id = 1;
+			threshold_index = THRESHOLD_INDEX_4;
+			break;
+		case 0x02:
+			sku_info->cpu_speedo_id = 2;
+			sku_info->soc_speedo_id = 2;
+			threshold_index = THRESHOLD_INDEX_5;
+			break;
+		case 0x04:
+			sku_info->cpu_speedo_id = 3;
+			sku_info->soc_speedo_id = 2;
+			threshold_index = THRESHOLD_INDEX_6;
+			break;
+		case 0:
+			switch (package_id) {
+			case 1:
+				sku_info->cpu_speedo_id = 2;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_2;
+				break;
+			case 2:
+				sku_info->cpu_speedo_id = 3;
+				sku_info->soc_speedo_id = 2;
+				threshold_index = THRESHOLD_INDEX_3;
+				break;
+			default:
+				dev_err(dev, "Unknown pkg %d\n", package_id);
+				BUG();
+				break;
+			}
+			break;
+		default:
+			dev_warn(dev, "Unknown SKU %d\n", sku_info->sku_id);
+			sku_info->cpu_speedo_id = 0;
+			sku_info->soc_speedo_id = 0;
+			threshold_index = THRESHOLD_INDEX_0;
+			break;
+		}
+		break;
+	default:
+		dev_warn(dev, "Unknown chip rev %d\n", sku_info->revision);
+		sku_info->cpu_speedo_id = 0;
+		sku_info->soc_speedo_id = 0;
+		threshold_index = THRESHOLD_INDEX_0;
+		break;
+	}
+}
+
+void tegra30_init_speedo_data(struct tegra_sku_info *sku_info,
+			      struct device *dev)
+{
+	u32 cpu_speedo_val;
+	u32 core_speedo_val;
+	int i;
+
+	BUILD_BUG_ON(ARRAY_SIZE(cpu_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+	BUILD_BUG_ON(ARRAY_SIZE(core_process_speedos) !=
+			THRESHOLD_INDEX_COUNT);
+
+	package_id = tegra30_fuse_readl(FUSE_PACKAGE_INFO) & 0x0F;
+
+	rev_sku_to_speedo_ids(sku_info, dev);
+	fuse_speedo_calib(&cpu_speedo_val, &core_speedo_val, dev);
+	dev_dbg(dev, "CPU speedo value %u\n", cpu_speedo_val);
+	dev_dbg(dev, "Core speedo value %u\n", core_speedo_val);
+
+	for (i = 0; i < CPU_PROCESS_CORNERS_NUM; i++) {
+		if (cpu_speedo_val < cpu_process_speedos[threshold_index][i])
+			break;
+	}
+	sku_info->cpu_process_id = i - 1;
+
+	if (sku_info->cpu_process_id == -1) {
+		dev_warn(dev, "CPU speedo value %3d out of range",
+		       cpu_speedo_val);
+		sku_info->cpu_process_id = 0;
+		sku_info->cpu_speedo_id = 1;
+	}
+
+	for (i = 0; i < CORE_PROCESS_CORNERS_NUM; i++) {
+		if (core_speedo_val < core_process_speedos[threshold_index][i])
+			break;
+	}
+	sku_info->core_process_id = i - 1;
+
+	if (sku_info->core_process_id == -1) {
+		dev_warn(dev, "CORE speedo value %3d out of range",
+		       core_speedo_val);
+		sku_info->core_process_id = 0;
+		sku_info->soc_speedo_id = 1;
+	}
+}
diff --git a/include/linux/tegra-soc.h b/include/linux/tegra-soc.h
index db93b77..066e526 100644
--- a/include/linux/tegra-soc.h
+++ b/include/linux/tegra-soc.h
@@ -22,6 +22,9 @@ 
 #define TEGRA114	0x35
 #define TEGRA124	0x40
 
+#define TEGRA_FUSE_SKU_CALIB_0	0xf0
+#define TEGRA30_FUSE_SATA_CALIB	0x124
+
 #ifndef __ASSEMBLY__
 
 enum tegra_revision {
@@ -37,6 +40,7 @@  enum tegra_revision {
 u32 tegra_read_straps(void);
 u32 tegra_read_chipid(void);
 void tegra_init_fuse(void);
+int tegra_fuse_readl(u32 offset, u32 *val);
 
 extern int tegra_chip_id;
 extern enum tegra_revision tegra_revision;