Merge branch 'fixes-rc1' into omap-for-v4.2/fixes

This commit is contained in:
Tony Lindgren
2015-07-06 05:33:17 -07:00
10662 changed files with 1039063 additions and 243834 deletions

View File

@@ -49,6 +49,14 @@ config OMAP_GPMC
interfacing to a variety of asynchronous as well as synchronous
memory drives like NOR, NAND, OneNAND, SRAM.
config OMAP_GPMC_DEBUG
bool
depends on OMAP_GPMC
help
Enables verbose debugging mostly to decode the bootloader provided
timings. Enable this during development to configure devices
connected to the GPMC bus.
config MVEBU_DEVBUS
bool "Marvell EBU Device Bus Controller"
default y

View File

@@ -403,7 +403,7 @@ static void gpmc_cs_bool_timings(int cs, const struct gpmc_bool_timings *p)
p->cycle2cyclediffcsen);
}
#ifdef DEBUG
#ifdef CONFIG_OMAP_GPMC_DEBUG
/**
* get_gpmc_timing_reg - read a timing parameter and print DTS settings for it.
* @cs: Chip Select Region
@@ -612,7 +612,7 @@ static int set_gpmc_timing_reg(int cs, int reg, int st_bit, int end_bit, int max
}
l = gpmc_cs_read_reg(cs, reg);
#ifdef DEBUG
#ifdef CONFIG_OMAP_GPMC_DEBUG
pr_info(
"GPMC CS%d: %-17s: %3d ticks, %3lu ns (was %3i ticks) %3d ns\n",
cs, name, ticks, gpmc_get_clk_period(cs, cd) * ticks / 1000,
@@ -767,7 +767,7 @@ int gpmc_cs_set_timings(int cs, const struct gpmc_timings *t,
GPMC_CONFIG1_CLKACTIVATIONTIME_MAX,
clk_activation, GPMC_CD_FCLK);
#ifdef DEBUG
#ifdef CONFIG_OMAP_GPMC_DEBUG
pr_info("GPMC CS%d CLK period is %lu ns (div %d)\n",
cs, (div * gpmc_get_fclk_period()) / 1000, div);
#endif

View File

@@ -5,3 +5,13 @@ config TEGRA_MC
help
This driver supports the Memory Controller (MC) hardware found on
NVIDIA Tegra SoCs.
config TEGRA124_EMC
bool "NVIDIA Tegra124 External Memory Controller driver"
default y
depends on TEGRA_MC && ARCH_TEGRA_124_SOC
help
This driver is for the External Memory Controller (EMC) found on
Tegra124 chips. The EMC controls the external DRAM on the board.
This driver is required to change memory timings / clock rate for
external memory.

View File

@@ -3,5 +3,8 @@ tegra-mc-y := mc.o
tegra-mc-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30.o
tegra-mc-$(CONFIG_ARCH_TEGRA_114_SOC) += tegra114.o
tegra-mc-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124.o
tegra-mc-$(CONFIG_ARCH_TEGRA_132_SOC) += tegra124.o
obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o

View File

@@ -13,6 +13,9 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <soc/tegra/fuse.h>
#include "mc.h"
@@ -48,6 +51,9 @@
#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff
#define MC_EMEM_ARB_MISC0 0xd8
#define MC_EMEM_ADR_CFG 0x54
#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0)
static const struct of_device_id tegra_mc_of_match[] = {
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
{ .compatible = "nvidia,tegra30-mc", .data = &tegra30_mc_soc },
@@ -57,6 +63,9 @@ static const struct of_device_id tegra_mc_of_match[] = {
#endif
#ifdef CONFIG_ARCH_TEGRA_124_SOC
{ .compatible = "nvidia,tegra124-mc", .data = &tegra124_mc_soc },
#endif
#ifdef CONFIG_ARCH_TEGRA_132_SOC
{ .compatible = "nvidia,tegra132-mc", .data = &tegra132_mc_soc },
#endif
{ }
};
@@ -91,6 +100,130 @@ static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
return 0;
}
void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
{
unsigned int i;
struct tegra_mc_timing *timing = NULL;
for (i = 0; i < mc->num_timings; i++) {
if (mc->timings[i].rate == rate) {
timing = &mc->timings[i];
break;
}
}
if (!timing) {
dev_err(mc->dev, "no memory timing registered for rate %lu\n",
rate);
return;
}
for (i = 0; i < mc->soc->num_emem_regs; ++i)
mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]);
}
unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc)
{
u8 dram_count;
dram_count = mc_readl(mc, MC_EMEM_ADR_CFG);
dram_count &= MC_EMEM_ADR_CFG_EMEM_NUMDEV;
dram_count++;
return dram_count;
}
static int load_one_timing(struct tegra_mc *mc,
struct tegra_mc_timing *timing,
struct device_node *node)
{
int err;
u32 tmp;
err = of_property_read_u32(node, "clock-frequency", &tmp);
if (err) {
dev_err(mc->dev,
"timing %s: failed to read rate\n", node->name);
return err;
}
timing->rate = tmp;
timing->emem_data = devm_kcalloc(mc->dev, mc->soc->num_emem_regs,
sizeof(u32), GFP_KERNEL);
if (!timing->emem_data)
return -ENOMEM;
err = of_property_read_u32_array(node, "nvidia,emem-configuration",
timing->emem_data,
mc->soc->num_emem_regs);
if (err) {
dev_err(mc->dev,
"timing %s: failed to read EMEM configuration\n",
node->name);
return err;
}
return 0;
}
static int load_timings(struct tegra_mc *mc, struct device_node *node)
{
struct device_node *child;
struct tegra_mc_timing *timing;
int child_count = of_get_child_count(node);
int i = 0, err;
mc->timings = devm_kcalloc(mc->dev, child_count, sizeof(*timing),
GFP_KERNEL);
if (!mc->timings)
return -ENOMEM;
mc->num_timings = child_count;
for_each_child_of_node(node, child) {
timing = &mc->timings[i++];
err = load_one_timing(mc, timing, child);
if (err)
return err;
}
return 0;
}
static int tegra_mc_setup_timings(struct tegra_mc *mc)
{
struct device_node *node;
u32 ram_code, node_ram_code;
int err;
ram_code = tegra_read_ram_code();
mc->num_timings = 0;
for_each_child_of_node(mc->dev->of_node, node) {
err = of_property_read_u32(node, "nvidia,ram-code",
&node_ram_code);
if (err || (node_ram_code != ram_code)) {
of_node_put(node);
continue;
}
err = load_timings(mc, node);
if (err)
return err;
of_node_put(node);
break;
}
if (mc->num_timings == 0)
dev_warn(mc->dev,
"no memory timings for RAM code %u registered\n",
ram_code);
return 0;
}
static const char *const status_names[32] = {
[ 1] = "External interrupt",
[ 6] = "EMEM address decode error",
@@ -248,6 +381,12 @@ static int tegra_mc_probe(struct platform_device *pdev)
return err;
}
err = tegra_mc_setup_timings(mc);
if (err < 0) {
dev_err(&pdev->dev, "failed to setup timings: %d\n", err);
return err;
}
if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU)) {
mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc);
if (IS_ERR(mc->smmu)) {
@@ -273,8 +412,8 @@ static int tegra_mc_probe(struct platform_device *pdev)
value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR |
MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE |
MC_INT_ARBITRATION_EMEM | MC_INT_SECURITY_VIOLATION |
MC_INT_DECERR_EMEM;
MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM;
mc_writel(mc, value, MC_INTMASK);
return 0;

View File

@@ -37,4 +37,8 @@ extern const struct tegra_mc_soc tegra114_mc_soc;
extern const struct tegra_mc_soc tegra124_mc_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_132_SOC
extern const struct tegra_mc_soc tegra132_mc_soc;
#endif
#endif /* MEMORY_TEGRA_MC_H */

View File

@@ -896,22 +896,22 @@ static const struct tegra_mc_client tegra114_mc_clients[] = {
};
static const struct tegra_smmu_swgroup tegra114_swgroups[] = {
{ .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 },
{ .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c },
{ .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 },
{ .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 },
{ .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
{ .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 },
{ .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 },
{ .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c },
{ .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 },
{ .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 },
{ .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c },
{ .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .name = "nv", .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 },
{ .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .name = "msenc", .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 },
{ .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
{ .name = "isp", .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 },
{ .name = "xusb_host", .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 },
{ .name = "xusb_dev", .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c },
{ .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 },
};
static void tegra114_flush_dcache(struct page *page, unsigned long offset,

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,48 @@
#include "mc.h"
#define MC_EMEM_ARB_CFG 0x90
#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
#define MC_EMEM_ARB_TIMING_RCD 0x98
#define MC_EMEM_ARB_TIMING_RP 0x9c
#define MC_EMEM_ARB_TIMING_RC 0xa0
#define MC_EMEM_ARB_TIMING_RAS 0xa4
#define MC_EMEM_ARB_TIMING_FAW 0xa8
#define MC_EMEM_ARB_TIMING_RRD 0xac
#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0
#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4
#define MC_EMEM_ARB_TIMING_R2R 0xb8
#define MC_EMEM_ARB_TIMING_W2W 0xbc
#define MC_EMEM_ARB_TIMING_R2W 0xc0
#define MC_EMEM_ARB_TIMING_W2R 0xc4
#define MC_EMEM_ARB_DA_TURNS 0xd0
#define MC_EMEM_ARB_DA_COVERS 0xd4
#define MC_EMEM_ARB_MISC0 0xd8
#define MC_EMEM_ARB_MISC1 0xdc
#define MC_EMEM_ARB_RING1_THROTTLE 0xe0
static const unsigned long tegra124_mc_emem_regs[] = {
MC_EMEM_ARB_CFG,
MC_EMEM_ARB_OUTSTANDING_REQ,
MC_EMEM_ARB_TIMING_RCD,
MC_EMEM_ARB_TIMING_RP,
MC_EMEM_ARB_TIMING_RC,
MC_EMEM_ARB_TIMING_RAS,
MC_EMEM_ARB_TIMING_FAW,
MC_EMEM_ARB_TIMING_RRD,
MC_EMEM_ARB_TIMING_RAP2PRE,
MC_EMEM_ARB_TIMING_WAP2PRE,
MC_EMEM_ARB_TIMING_R2R,
MC_EMEM_ARB_TIMING_W2W,
MC_EMEM_ARB_TIMING_R2W,
MC_EMEM_ARB_TIMING_W2R,
MC_EMEM_ARB_DA_TURNS,
MC_EMEM_ARB_DA_COVERS,
MC_EMEM_ARB_MISC0,
MC_EMEM_ARB_MISC1,
MC_EMEM_ARB_RING1_THROTTLE
};
static const struct tegra_mc_client tegra124_mc_clients[] = {
{
.id = 0x00,
@@ -934,29 +976,29 @@ static const struct tegra_mc_client tegra124_mc_clients[] = {
};
static const struct tegra_smmu_swgroup tegra124_swgroups[] = {
{ .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 },
{ .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 },
{ .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 },
{ .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 },
{ .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 },
{ .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c },
{ .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 },
{ .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 },
{ .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 },
{ .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaac },
{ .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 },
{ .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 },
{ .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c },
{ .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 },
{ .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 },
{ .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
{ .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 },
{ .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .name = "msenc", .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 },
{ .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .name = "sata", .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 },
{ .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .name = "isp2", .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 },
{ .name = "xusb_host", .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 },
{ .name = "xusb_dev", .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c },
{ .name = "isp2b", .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 },
{ .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 },
{ .name = "a9avp", .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 },
{ .name = "gpu", .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaac },
{ .name = "sdmmc1a", .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 },
{ .name = "sdmmc2a", .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 },
{ .name = "sdmmc3a", .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c },
{ .name = "sdmmc4a", .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 },
{ .name = "vic", .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 },
{ .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
};
#ifdef CONFIG_ARCH_TEGRA_124_SOC
@@ -991,5 +1033,40 @@ const struct tegra_mc_soc tegra124_mc_soc = {
.num_address_bits = 34,
.atom_size = 32,
.smmu = &tegra124_smmu_soc,
.emem_regs = tegra124_mc_emem_regs,
.num_emem_regs = ARRAY_SIZE(tegra124_mc_emem_regs),
};
#endif /* CONFIG_ARCH_TEGRA_124_SOC */
#ifdef CONFIG_ARCH_TEGRA_132_SOC
static void tegra132_flush_dcache(struct page *page, unsigned long offset,
size_t size)
{
void *virt = page_address(page) + offset;
__flush_dcache_area(virt, size);
}
static const struct tegra_smmu_ops tegra132_smmu_ops = {
.flush_dcache = tegra132_flush_dcache,
};
static const struct tegra_smmu_soc tegra132_smmu_soc = {
.clients = tegra124_mc_clients,
.num_clients = ARRAY_SIZE(tegra124_mc_clients),
.swgroups = tegra124_swgroups,
.num_swgroups = ARRAY_SIZE(tegra124_swgroups),
.supports_round_robin_arbitration = true,
.supports_request_limit = true,
.num_asids = 128,
.ops = &tegra132_smmu_ops,
};
const struct tegra_mc_soc tegra132_mc_soc = {
.clients = tegra124_mc_clients,
.num_clients = ARRAY_SIZE(tegra124_mc_clients),
.num_address_bits = 34,
.atom_size = 32,
.smmu = &tegra132_smmu_soc,
};
#endif /* CONFIG_ARCH_TEGRA_132_SOC */

View File

@@ -918,22 +918,22 @@ static const struct tegra_mc_client tegra30_mc_clients[] = {
};
static const struct tegra_smmu_swgroup tegra30_swgroups[] = {
{ .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 },
{ .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c },
{ .swgroup = TEGRA_SWGROUP_MPE, .reg = 0x264 },
{ .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
{ .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 },
{ .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 },
{ .swgroup = TEGRA_SWGROUP_NV2, .reg = 0x26c },
{ .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x278 },
{ .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 },
{ .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 },
{ .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 },
{ .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 },
{ .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c },
{ .name = "mpe", .swgroup = TEGRA_SWGROUP_MPE, .reg = 0x264 },
{ .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
{ .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 },
{ .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c },
{ .name = "nv", .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 },
{ .name = "nv2", .swgroup = TEGRA_SWGROUP_NV2, .reg = 0x26c },
{ .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 },
{ .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 },
{ .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 },
{ .name = "sata", .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x278 },
{ .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c },
{ .name = "isp", .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 },
};
static void tegra30_flush_dcache(struct page *page, unsigned long offset,