Skip to content

Commit

Permalink
Merge tag 'omap-for-v3.8/gpmc-signed' of git://git.kernel.org/pub/scm…
Browse files Browse the repository at this point in the history
…/linux/kernel/git/tmlind/linux-omap into next/drivers

From Tony Lindgren <tony@atomide.com>:

omap GPMC (General Purpose Memory Controller) updates via Afzal Mohammed <afzal@ti.com>:

These changes provide a generic gpmc timing calculation method,
migrates existing peripherals that makes use of custom gpmc timing
calculation method to use the new generic one.

The generic timing routine has been tested with onenand, smsc911x,
and tusb6010 devices connected to GPMC in addition to simulating
other devices support in the mainline kernel.

* tag 'omap-for-v3.8/gpmc-signed' of git://git.kernel.org/pub/scm/linux/kernel/git/tmlind/linux-omap:
  ARM: OMAP2+: tusb6010: generic timing calculation
  ARM: OMAP2+: smc91x: generic timing calculation
  ARM: OMAP2+: onenand: generic timing calculation
  ARM: OMAP2+: gpmc: generic timing calculation
  ARM: OMAP2+: gpmc: handle additional timings
  ARM: OMAP2+: nand: remove redundant rounding

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
  • Loading branch information
Arnd Bergmann committed Nov 15, 2012
2 parents cf1fb2d + 8698308 commit b770ea5
Show file tree
Hide file tree
Showing 7 changed files with 709 additions and 292 deletions.
122 changes: 122 additions & 0 deletions Documentation/bus-devices/ti-gpmc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
GPMC (General Purpose Memory Controller):
=========================================

GPMC is an unified memory controller dedicated to interfacing external
memory devices like
* Asynchronous SRAM like memories and application specific integrated
circuit devices.
* Asynchronous, synchronous, and page mode burst NOR flash devices
NAND flash
* Pseudo-SRAM devices

GPMC is found on Texas Instruments SoC's (OMAP based)
IP details: http://www.ti.com/lit/pdf/spruh73 section 7.1


GPMC generic timing calculation:
================================

GPMC has certain timings that has to be programmed for proper
functioning of the peripheral, while peripheral has another set of
timings. To have peripheral work with gpmc, peripheral timings has to
be translated to the form gpmc can understand. The way it has to be
translated depends on the connected peripheral. Also there is a
dependency for certain gpmc timings on gpmc clock frequency. Hence a
generic timing routine was developed to achieve above requirements.

Generic routine provides a generic method to calculate gpmc timings
from gpmc peripheral timings. struct gpmc_device_timings fields has to
be updated with timings from the datasheet of the peripheral that is
connected to gpmc. A few of the peripheral timings can be fed either
in time or in cycles, provision to handle this scenario has been
provided (refer struct gpmc_device_timings definition). It may so
happen that timing as specified by peripheral datasheet is not present
in timing structure, in this scenario, try to correlate peripheral
timing to the one available. If that doesn't work, try to add a new
field as required by peripheral, educate generic timing routine to
handle it, make sure that it does not break any of the existing.
Then there may be cases where peripheral datasheet doesn't mention
certain fields of struct gpmc_device_timings, zero those entries.

Generic timing routine has been verified to work properly on
multiple onenand's and tusb6010 peripherals.

A word of caution: generic timing routine has been developed based
on understanding of gpmc timings, peripheral timings, available
custom timing routines, a kind of reverse engineering without
most of the datasheets & hardware (to be exact none of those supported
in mainline having custom timing routine) and by simulation.

gpmc timing dependency on peripheral timings:
[<gpmc_timing>: <peripheral timing1>, <peripheral timing2> ...]

1. common
cs_on: t_ceasu
adv_on: t_avdasu, t_ceavd

2. sync common
sync_clk: clk
page_burst_access: t_bacc
clk_activation: t_ces, t_avds

3. read async muxed
adv_rd_off: t_avdp_r
oe_on: t_oeasu, t_aavdh
access: t_iaa, t_oe, t_ce, t_aa
rd_cycle: t_rd_cycle, t_cez_r, t_oez

4. read async non-muxed
adv_rd_off: t_avdp_r
oe_on: t_oeasu
access: t_iaa, t_oe, t_ce, t_aa
rd_cycle: t_rd_cycle, t_cez_r, t_oez

5. read sync muxed
adv_rd_off: t_avdp_r, t_avdh
oe_on: t_oeasu, t_ach, cyc_aavdh_oe
access: t_iaa, cyc_iaa, cyc_oe
rd_cycle: t_cez_r, t_oez, t_ce_rdyz

6. read sync non-muxed
adv_rd_off: t_avdp_r
oe_on: t_oeasu
access: t_iaa, cyc_iaa, cyc_oe
rd_cycle: t_cez_r, t_oez, t_ce_rdyz

7. write async muxed
adv_wr_off: t_avdp_w
we_on, wr_data_mux_bus: t_weasu, t_aavdh, cyc_aavhd_we
we_off: t_wpl
cs_wr_off: t_wph
wr_cycle: t_cez_w, t_wr_cycle

8. write async non-muxed
adv_wr_off: t_avdp_w
we_on, wr_data_mux_bus: t_weasu
we_off: t_wpl
cs_wr_off: t_wph
wr_cycle: t_cez_w, t_wr_cycle

9. write sync muxed
adv_wr_off: t_avdp_w, t_avdh
we_on, wr_data_mux_bus: t_weasu, t_rdyo, t_aavdh, cyc_aavhd_we
we_off: t_wpl, cyc_wpl
cs_wr_off: t_wph
wr_cycle: t_cez_w, t_ce_rdyz

10. write sync non-muxed
adv_wr_off: t_avdp_w
we_on, wr_data_mux_bus: t_weasu, t_rdyo
we_off: t_wpl, cyc_wpl
cs_wr_off: t_wph
wr_cycle: t_cez_w, t_ce_rdyz


Note: Many of gpmc timings are dependent on other gpmc timings (a few
gpmc timings purely dependent on other gpmc timings, a reason that
some of the gpmc timings are missing above), and it will result in
indirect dependency of peripheral timings to gpmc timings other than
mentioned above, refer timing routine for more details. To know what
these peripheral timings correspond to, please see explanations in
struct gpmc_device_timings definition. And for gpmc timings refer
IP details (link above).
26 changes: 13 additions & 13 deletions arch/arm/mach-omap2/gpmc-nand.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,27 @@ static int omap2_nand_gpmc_retime(

memset(&t, 0, sizeof(t));
t.sync_clk = gpmc_t->sync_clk;
t.cs_on = gpmc_round_ns_to_ticks(gpmc_t->cs_on);
t.adv_on = gpmc_round_ns_to_ticks(gpmc_t->adv_on);
t.cs_on = gpmc_t->cs_on;
t.adv_on = gpmc_t->adv_on;

/* Read */
t.adv_rd_off = gpmc_round_ns_to_ticks(gpmc_t->adv_rd_off);
t.adv_rd_off = gpmc_t->adv_rd_off;
t.oe_on = t.adv_on;
t.access = gpmc_round_ns_to_ticks(gpmc_t->access);
t.oe_off = gpmc_round_ns_to_ticks(gpmc_t->oe_off);
t.cs_rd_off = gpmc_round_ns_to_ticks(gpmc_t->cs_rd_off);
t.rd_cycle = gpmc_round_ns_to_ticks(gpmc_t->rd_cycle);
t.access = gpmc_t->access;
t.oe_off = gpmc_t->oe_off;
t.cs_rd_off = gpmc_t->cs_rd_off;
t.rd_cycle = gpmc_t->rd_cycle;

/* Write */
t.adv_wr_off = gpmc_round_ns_to_ticks(gpmc_t->adv_wr_off);
t.adv_wr_off = gpmc_t->adv_wr_off;
t.we_on = t.oe_on;
if (cpu_is_omap34xx()) {
t.wr_data_mux_bus = gpmc_round_ns_to_ticks(gpmc_t->wr_data_mux_bus);
t.wr_access = gpmc_round_ns_to_ticks(gpmc_t->wr_access);
t.wr_data_mux_bus = gpmc_t->wr_data_mux_bus;
t.wr_access = gpmc_t->wr_access;
}
t.we_off = gpmc_round_ns_to_ticks(gpmc_t->we_off);
t.cs_wr_off = gpmc_round_ns_to_ticks(gpmc_t->cs_wr_off);
t.wr_cycle = gpmc_round_ns_to_ticks(gpmc_t->wr_cycle);
t.we_off = gpmc_t->we_off;
t.cs_wr_off = gpmc_t->cs_wr_off;
t.wr_cycle = gpmc_t->wr_cycle;

/* Configure GPMC */
if (gpmc_nand_data->devsize == NAND_BUSWIDTH_16)
Expand Down
143 changes: 43 additions & 100 deletions arch/arm/mach-omap2/gpmc-onenand.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@

static unsigned onenand_flags;
static unsigned latency;
static int fclk_offset;

static struct omap_onenand_platform_data *gpmc_onenand_data;

Expand All @@ -50,6 +49,7 @@ static struct platform_device gpmc_onenand_device = {

static struct gpmc_timings omap2_onenand_calc_async_timings(void)
{
struct gpmc_device_timings dev_t;
struct gpmc_timings t;

const int t_cer = 15;
Expand All @@ -59,35 +59,24 @@ static struct gpmc_timings omap2_onenand_calc_async_timings(void)
const int t_aa = 76;
const int t_oe = 20;
const int t_cez = 20; /* max of t_cez, t_oez */
const int t_ds = 30;
const int t_wpl = 40;
const int t_wph = 30;

memset(&t, 0, sizeof(t));
t.sync_clk = 0;
t.cs_on = 0;
t.adv_on = 0;

/* Read */
t.adv_rd_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer));
t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(t_aavdh);
t.access = t.adv_on + gpmc_round_ns_to_ticks(t_aa);
t.access = max_t(int, t.access, t.cs_on + gpmc_round_ns_to_ticks(t_ce));
t.access = max_t(int, t.access, t.oe_on + gpmc_round_ns_to_ticks(t_oe));
t.oe_off = t.access + gpmc_round_ns_to_ticks(1);
t.cs_rd_off = t.oe_off;
t.rd_cycle = t.cs_rd_off + gpmc_round_ns_to_ticks(t_cez);

/* Write */
t.adv_wr_off = t.adv_rd_off;
t.we_on = t.oe_on;
if (cpu_is_omap34xx()) {
t.wr_data_mux_bus = t.we_on;
t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds);
}
t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl);
t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph);
t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez);
memset(&dev_t, 0, sizeof(dev_t));

dev_t.mux = true;
dev_t.t_avdp_r = max_t(int, t_avdp, t_cer) * 1000;
dev_t.t_avdp_w = dev_t.t_avdp_r;
dev_t.t_aavdh = t_aavdh * 1000;
dev_t.t_aa = t_aa * 1000;
dev_t.t_ce = t_ce * 1000;
dev_t.t_oe = t_oe * 1000;
dev_t.t_cez_r = t_cez * 1000;
dev_t.t_cez_w = dev_t.t_cez_r;
dev_t.t_wpl = t_wpl * 1000;
dev_t.t_wph = t_wph * 1000;

gpmc_calc_timings(&t, &dev_t);

return t;
}
Expand Down Expand Up @@ -173,18 +162,15 @@ static struct gpmc_timings
omap2_onenand_calc_sync_timings(struct omap_onenand_platform_data *cfg,
int freq)
{
struct gpmc_device_timings dev_t;
struct gpmc_timings t;
const int t_cer = 15;
const int t_avdp = 12;
const int t_cez = 20; /* max of t_cez, t_oez */
const int t_ds = 30;
const int t_wpl = 40;
const int t_wph = 30;
int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_ach, t_aavdh, t_rdyo;
u32 reg;
int div, fclk_offset_ns, gpmc_clk_ns;
int ticks_cez;
int cs = cfg->cs;
int div, gpmc_clk_ns;

if (cfg->flags & ONENAND_SYNC_READ)
onenand_flags = ONENAND_FLAG_SYNCREAD;
Expand Down Expand Up @@ -251,77 +237,35 @@ omap2_onenand_calc_sync_timings(struct omap_onenand_platform_data *cfg,
latency = 4;

/* Set synchronous read timings */
memset(&t, 0, sizeof(t));

if (div == 1) {
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2);
reg |= (1 << 7);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg);
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3);
reg |= (1 << 7);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg);
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4);
reg |= (1 << 7);
reg |= (1 << 23);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg);
} else {
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2);
reg &= ~(1 << 7);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg);
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3);
reg &= ~(1 << 7);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg);
reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4);
reg &= ~(1 << 7);
reg &= ~(1 << 23);
gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg);
}
memset(&dev_t, 0, sizeof(dev_t));

t.sync_clk = min_gpmc_clk_period;
t.cs_on = 0;
t.adv_on = 0;
fclk_offset_ns = gpmc_round_ns_to_ticks(max_t(int, t_ces, t_avds));
fclk_offset = gpmc_ns_to_ticks(fclk_offset_ns);
t.page_burst_access = gpmc_clk_ns;

/* Read */
t.adv_rd_off = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_avdh));
t.oe_on = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_ach));
/* Force at least 1 clk between AVD High to OE Low */
if (t.oe_on <= t.adv_rd_off)
t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(1);
t.access = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div);
t.oe_off = t.access + gpmc_round_ns_to_ticks(1);
t.cs_rd_off = t.oe_off;
ticks_cez = ((gpmc_ns_to_ticks(t_cez) + div - 1) / div) * div;
t.rd_cycle = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div +
ticks_cez);

/* Write */
dev_t.mux = true;
dev_t.sync_read = true;
if (onenand_flags & ONENAND_FLAG_SYNCWRITE) {
t.adv_wr_off = t.adv_rd_off;
t.we_on = 0;
t.we_off = t.cs_rd_off;
t.cs_wr_off = t.cs_rd_off;
t.wr_cycle = t.rd_cycle;
if (cpu_is_omap34xx()) {
t.wr_data_mux_bus = gpmc_ticks_to_ns(fclk_offset +
gpmc_ps_to_ticks(min_gpmc_clk_period +
t_rdyo * 1000));
t.wr_access = t.access;
}
dev_t.sync_write = true;
} else {
t.adv_wr_off = gpmc_round_ns_to_ticks(max_t(int,
t_avdp, t_cer));
t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(t_aavdh);
t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl);
t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph);
t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez);
if (cpu_is_omap34xx()) {
t.wr_data_mux_bus = t.we_on;
t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds);
}
dev_t.t_avdp_w = max(t_avdp, t_cer) * 1000;
dev_t.t_wpl = t_wpl * 1000;
dev_t.t_wph = t_wph * 1000;
dev_t.t_aavdh = t_aavdh * 1000;
}
dev_t.ce_xdelay = true;
dev_t.avd_xdelay = true;
dev_t.oe_xdelay = true;
dev_t.we_xdelay = true;
dev_t.clk = min_gpmc_clk_period;
dev_t.t_bacc = dev_t.clk;
dev_t.t_ces = t_ces * 1000;
dev_t.t_avds = t_avds * 1000;
dev_t.t_avdh = t_avdh * 1000;
dev_t.t_ach = t_ach * 1000;
dev_t.cyc_iaa = (latency + 1);
dev_t.t_cez_r = t_cez * 1000;
dev_t.t_cez_w = dev_t.t_cez_r;
dev_t.cyc_aavdh_oe = 1;
dev_t.t_rdyo = t_rdyo * 1000 + min_gpmc_clk_period;

gpmc_calc_timings(&t, &dev_t);

return t;
}
Expand All @@ -338,7 +282,6 @@ static int gpmc_set_sync_mode(int cs, struct gpmc_timings *t)
(sync_read ? GPMC_CONFIG1_READTYPE_SYNC : 0) |
(sync_write ? GPMC_CONFIG1_WRITEMULTIPLE_SUPP : 0) |
(sync_write ? GPMC_CONFIG1_WRITETYPE_SYNC : 0) |
GPMC_CONFIG1_CLKACTIVATIONTIME(fclk_offset) |
GPMC_CONFIG1_PAGE_LEN(2) |
(cpu_is_omap34xx() ? 0 :
(GPMC_CONFIG1_WAIT_READ_MON |
Expand Down
Loading

0 comments on commit b770ea5

Please sign in to comment.