mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00
- Fix a regression in leds-gpio introduced by a recent commit that
inadvertently changed the name of one of the properties used by
the driver (Fabio Estevam).
- Fix a regression in the ACPI backlight driver introduced by a
recent fix that missed one special case that had to be taken
into account (Aaron Lu).
- Drop the level of some new kernel messages from the ACPI core
introduced by a recent commit to KERN_DEBUG which they should
have used from the start and drop some other unuseful KERN_ERR
messages printed by ACPI (Rafael J Wysocki).
- Revert an incorrect commit modifying the cpupower tool
(Prarit Bhargava).
- Fix two regressions introduced by recent commits in the OPP
library and clean up some existing minor issues in that code
(Viresh Kumar).
- Continue to replace CONFIG_PM_RUNTIME with CONFIG_PM throughout
the tree (or drop it where that can be done) in order to make
it possible to eliminate CONFIG_PM_RUNTIME (Rafael J Wysocki,
Ulf Hansson, Ludovic Desroches). There will be one more
"CONFIG_PM_RUNTIME removal" batch after this one, because some
new uses of it have been introduced during the current merge
window, but that should be sufficient to finally get rid of it.
- Make the ACPI EC driver more robust against race conditions
related to GPE handler installation failures (Lv Zheng).
- Prevent the ACPI device PM core code from attempting to
disable GPEs that it has not enabled which confuses ACPICA
and makes it report errors unnecessarily (Rafael J Wysocki).
- Add a "force" command line switch to the intel_pstate driver
to make it possible to override the blacklisting of some
systems in that driver if needed (Ethan Zhao).
- Improve intel_pstate code documentation and add a MAINTAINERS
entry for it (Kristen Carlson Accardi).
- Make the ACPI fan driver create cooling device interfaces
witn names that reflect the IDs of the ACPI device objects
they are associated with, except for "generic" ACPI fans
(PNP ID "PNP0C0B"). That's necessary for user space thermal
management tools to be able to connect the fans with the
parts of the system they are supposed to be cooling properly.
From Srinivas Pandruvada.
/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)
iQIcBAABCAAGBQJUk0IDAAoJEILEb/54YlRx7fgP/3+yF/0TnEW93j2ALDAQFiLF
tSv2A2vQC8vtMJjjWx0z/HqPh86gfaReEFZmUJD/Q/e2LXEnxNZJ+QMjcekPVkDM
mTvcIMc2MR8vOA/oMkgxeaKregrrx7RkCfojd+NWZhVukkjl+mvBHgAnYjXRL+NZ
unDWGlbHG97vq/3kGjPYhDS00nxHblw8NHFBu5HL5RxwABdWoeZJITwqxXWyuPLw
nlqNWlOxmwvtSbw2VMKz0uof1nFHyQLykYsMG0ZsyayCRdWUZYkEqmE7GGpCLkLu
D6yfmlpen6ccIOsEAae0eXBt50IFY9Tihk5lovx1mZmci2SNRg29BqMI105wIn0u
8b8Ej7MNHp7yMxRpB5WfU90p/y7ioJns9guFZxY0CKaRnrI2+BLt3RscMi3MPI06
Cu2/WkSSa09fhDPA+pk+VDYsmWgyVawigesNmMP5/cvYO/yYywVRjOuO1k77qQGp
4dSpFYEHfpxinejZnVZOk2V9MkvSLoSMux6wPV0xM0IE1iD0ulVpHjTJrwp80ph4
+bfUFVr/vrD1y7EKbf1PD363ZKvJhWhvQWDgETsM1vgLf21PfWO7C2kflIAsWsdQ
1ukD5nCBRlP4K73hG7bdM6kRztXhUdR0SHg85/t0KB/ExiVqtcXIzB60D0G1lENd
QlKbq3O4lim1WGuhazQY
=5fo2
-----END PGP SIGNATURE-----
Merge tag 'pm+acpi-3.19-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull more ACPI and power management updates from Rafael Wysocki:
"These are regression fixes (leds-gpio, ACPI backlight driver,
operating performance points library, ACPI device enumeration
messages, cpupower tool), other bug fixes (ACPI EC driver, ACPI device
PM), some cleanups in the operating performance points (OPP)
framework, continuation of CONFIG_PM_RUNTIME elimination, a couple of
minor intel_pstate driver changes, a new MAINTAINERS entry for it and
an ACPI fan driver change needed for better support of thermal
management in user space.
Specifics:
- Fix a regression in leds-gpio introduced by a recent commit that
inadvertently changed the name of one of the properties used by the
driver (Fabio Estevam).
- Fix a regression in the ACPI backlight driver introduced by a
recent fix that missed one special case that had to be taken into
account (Aaron Lu).
- Drop the level of some new kernel messages from the ACPI core
introduced by a recent commit to KERN_DEBUG which they should have
used from the start and drop some other unuseful KERN_ERR messages
printed by ACPI (Rafael J Wysocki).
- Revert an incorrect commit modifying the cpupower tool (Prarit
Bhargava).
- Fix two regressions introduced by recent commits in the OPP library
and clean up some existing minor issues in that code (Viresh
Kumar).
- Continue to replace CONFIG_PM_RUNTIME with CONFIG_PM throughout the
tree (or drop it where that can be done) in order to make it
possible to eliminate CONFIG_PM_RUNTIME (Rafael J Wysocki, Ulf
Hansson, Ludovic Desroches).
There will be one more "CONFIG_PM_RUNTIME removal" batch after this
one, because some new uses of it have been introduced during the
current merge window, but that should be sufficient to finally get
rid of it.
- Make the ACPI EC driver more robust against race conditions related
to GPE handler installation failures (Lv Zheng).
- Prevent the ACPI device PM core code from attempting to disable
GPEs that it has not enabled which confuses ACPICA and makes it
report errors unnecessarily (Rafael J Wysocki).
- Add a "force" command line switch to the intel_pstate driver to
make it possible to override the blacklisting of some systems in
that driver if needed (Ethan Zhao).
- Improve intel_pstate code documentation and add a MAINTAINERS entry
for it (Kristen Carlson Accardi).
- Make the ACPI fan driver create cooling device interfaces witn
names that reflect the IDs of the ACPI device objects they are
associated with, except for "generic" ACPI fans (PNP ID "PNP0C0B").
That's necessary for user space thermal management tools to be able
to connect the fans with the parts of the system they are supposed
to be cooling properly. From Srinivas Pandruvada"
* tag 'pm+acpi-3.19-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (32 commits)
MAINTAINERS: add entry for intel_pstate
ACPI / video: update the skip case for acpi_video_device_in_dod()
power / PM: Eliminate CONFIG_PM_RUNTIME
NFC / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
SCSI / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
ACPI / EC: Fix unexpected ec_remove_handlers() invocations
Revert "tools: cpupower: fix return checks for sysfs_get_idlestate_count()"
tracing / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
x86 / PM: Replace CONFIG_PM_RUNTIME in io_apic.c
PM: Remove the SET_PM_RUNTIME_PM_OPS() macro
mmc: atmel-mci: use SET_RUNTIME_PM_OPS() macro
PM / Kconfig: Replace PM_RUNTIME with PM in dependencies
ARM / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
sound / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
phy / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
video / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
tty / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM
spi: Replace CONFIG_PM_RUNTIME with CONFIG_PM
ACPI / PM: Do not disable wakeup GPEs that have not been enabled
ACPI / utils: Drop error messages from acpi_evaluate_reference()
...
759 lines
20 KiB
C
759 lines
20 KiB
C
/*
|
|
* SuperH Mobile MERAM Driver for SuperH Mobile LCDC Driver
|
|
*
|
|
* Copyright (c) 2011 Damian Hobson-Garcia <dhobsong@igel.co.jp>
|
|
* Takanari Hayama <taki@igel.co.jp>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/genalloc.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <video/sh_mobile_meram.h>
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* MERAM registers
|
|
*/
|
|
|
|
#define MEVCR1 0x4
|
|
#define MEVCR1_RST (1 << 31)
|
|
#define MEVCR1_WD (1 << 30)
|
|
#define MEVCR1_AMD1 (1 << 29)
|
|
#define MEVCR1_AMD0 (1 << 28)
|
|
#define MEQSEL1 0x40
|
|
#define MEQSEL2 0x44
|
|
|
|
#define MExxCTL 0x400
|
|
#define MExxCTL_BV (1 << 31)
|
|
#define MExxCTL_BSZ_SHIFT 28
|
|
#define MExxCTL_MSAR_MASK (0x7ff << MExxCTL_MSAR_SHIFT)
|
|
#define MExxCTL_MSAR_SHIFT 16
|
|
#define MExxCTL_NXT_MASK (0x1f << MExxCTL_NXT_SHIFT)
|
|
#define MExxCTL_NXT_SHIFT 11
|
|
#define MExxCTL_WD1 (1 << 10)
|
|
#define MExxCTL_WD0 (1 << 9)
|
|
#define MExxCTL_WS (1 << 8)
|
|
#define MExxCTL_CB (1 << 7)
|
|
#define MExxCTL_WBF (1 << 6)
|
|
#define MExxCTL_WF (1 << 5)
|
|
#define MExxCTL_RF (1 << 4)
|
|
#define MExxCTL_CM (1 << 3)
|
|
#define MExxCTL_MD_READ (1 << 0)
|
|
#define MExxCTL_MD_WRITE (2 << 0)
|
|
#define MExxCTL_MD_ICB_WB (3 << 0)
|
|
#define MExxCTL_MD_ICB (4 << 0)
|
|
#define MExxCTL_MD_FB (7 << 0)
|
|
#define MExxCTL_MD_MASK (7 << 0)
|
|
#define MExxBSIZE 0x404
|
|
#define MExxBSIZE_RCNT_SHIFT 28
|
|
#define MExxBSIZE_YSZM1_SHIFT 16
|
|
#define MExxBSIZE_XSZM1_SHIFT 0
|
|
#define MExxMNCF 0x408
|
|
#define MExxMNCF_KWBNM_SHIFT 28
|
|
#define MExxMNCF_KRBNM_SHIFT 24
|
|
#define MExxMNCF_BNM_SHIFT 16
|
|
#define MExxMNCF_XBV (1 << 15)
|
|
#define MExxMNCF_CPL_YCBCR444 (1 << 12)
|
|
#define MExxMNCF_CPL_YCBCR420 (2 << 12)
|
|
#define MExxMNCF_CPL_YCBCR422 (3 << 12)
|
|
#define MExxMNCF_CPL_MSK (3 << 12)
|
|
#define MExxMNCF_BL (1 << 2)
|
|
#define MExxMNCF_LNM_SHIFT 0
|
|
#define MExxSARA 0x410
|
|
#define MExxSARB 0x414
|
|
#define MExxSBSIZE 0x418
|
|
#define MExxSBSIZE_HDV (1 << 31)
|
|
#define MExxSBSIZE_HSZ16 (0 << 28)
|
|
#define MExxSBSIZE_HSZ32 (1 << 28)
|
|
#define MExxSBSIZE_HSZ64 (2 << 28)
|
|
#define MExxSBSIZE_HSZ128 (3 << 28)
|
|
#define MExxSBSIZE_SBSIZZ_SHIFT 0
|
|
|
|
#define MERAM_MExxCTL_VAL(next, addr) \
|
|
((((next) << MExxCTL_NXT_SHIFT) & MExxCTL_NXT_MASK) | \
|
|
(((addr) << MExxCTL_MSAR_SHIFT) & MExxCTL_MSAR_MASK))
|
|
#define MERAM_MExxBSIZE_VAL(rcnt, yszm1, xszm1) \
|
|
(((rcnt) << MExxBSIZE_RCNT_SHIFT) | \
|
|
((yszm1) << MExxBSIZE_YSZM1_SHIFT) | \
|
|
((xszm1) << MExxBSIZE_XSZM1_SHIFT))
|
|
|
|
static const unsigned long common_regs[] = {
|
|
MEVCR1,
|
|
MEQSEL1,
|
|
MEQSEL2,
|
|
};
|
|
#define MERAM_REGS_SIZE ARRAY_SIZE(common_regs)
|
|
|
|
static const unsigned long icb_regs[] = {
|
|
MExxCTL,
|
|
MExxBSIZE,
|
|
MExxMNCF,
|
|
MExxSARA,
|
|
MExxSARB,
|
|
MExxSBSIZE,
|
|
};
|
|
#define ICB_REGS_SIZE ARRAY_SIZE(icb_regs)
|
|
|
|
/*
|
|
* sh_mobile_meram_icb - MERAM ICB information
|
|
* @regs: Registers cache
|
|
* @index: ICB index
|
|
* @offset: MERAM block offset
|
|
* @size: MERAM block size in KiB
|
|
* @cache_unit: Bytes to cache per ICB
|
|
* @pixelformat: Video pixel format of the data stored in the ICB
|
|
* @current_reg: Which of Start Address Register A (0) or B (1) is in use
|
|
*/
|
|
struct sh_mobile_meram_icb {
|
|
unsigned long regs[ICB_REGS_SIZE];
|
|
unsigned int index;
|
|
unsigned long offset;
|
|
unsigned int size;
|
|
|
|
unsigned int cache_unit;
|
|
unsigned int pixelformat;
|
|
unsigned int current_reg;
|
|
};
|
|
|
|
#define MERAM_ICB_NUM 32
|
|
|
|
struct sh_mobile_meram_fb_plane {
|
|
struct sh_mobile_meram_icb *marker;
|
|
struct sh_mobile_meram_icb *cache;
|
|
};
|
|
|
|
struct sh_mobile_meram_fb_cache {
|
|
unsigned int nplanes;
|
|
struct sh_mobile_meram_fb_plane planes[2];
|
|
};
|
|
|
|
/*
|
|
* sh_mobile_meram_priv - MERAM device
|
|
* @base: Registers base address
|
|
* @meram: MERAM physical address
|
|
* @regs: Registers cache
|
|
* @lock: Protects used_icb and icbs
|
|
* @used_icb: Bitmask of used ICBs
|
|
* @icbs: ICBs
|
|
* @pool: Allocation pool to manage the MERAM
|
|
*/
|
|
struct sh_mobile_meram_priv {
|
|
void __iomem *base;
|
|
unsigned long meram;
|
|
unsigned long regs[MERAM_REGS_SIZE];
|
|
|
|
struct mutex lock;
|
|
unsigned long used_icb;
|
|
struct sh_mobile_meram_icb icbs[MERAM_ICB_NUM];
|
|
|
|
struct gen_pool *pool;
|
|
};
|
|
|
|
/* settings */
|
|
#define MERAM_GRANULARITY 1024
|
|
#define MERAM_SEC_LINE 15
|
|
#define MERAM_LINE_WIDTH 2048
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Registers access
|
|
*/
|
|
|
|
#define MERAM_ICB_OFFSET(base, idx, off) ((base) + (off) + (idx) * 0x20)
|
|
|
|
static inline void meram_write_icb(void __iomem *base, unsigned int idx,
|
|
unsigned int off, unsigned long val)
|
|
{
|
|
iowrite32(val, MERAM_ICB_OFFSET(base, idx, off));
|
|
}
|
|
|
|
static inline unsigned long meram_read_icb(void __iomem *base, unsigned int idx,
|
|
unsigned int off)
|
|
{
|
|
return ioread32(MERAM_ICB_OFFSET(base, idx, off));
|
|
}
|
|
|
|
static inline void meram_write_reg(void __iomem *base, unsigned int off,
|
|
unsigned long val)
|
|
{
|
|
iowrite32(val, base + off);
|
|
}
|
|
|
|
static inline unsigned long meram_read_reg(void __iomem *base, unsigned int off)
|
|
{
|
|
return ioread32(base + off);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* MERAM allocation and free
|
|
*/
|
|
|
|
static unsigned long meram_alloc(struct sh_mobile_meram_priv *priv, size_t size)
|
|
{
|
|
return gen_pool_alloc(priv->pool, size);
|
|
}
|
|
|
|
static void meram_free(struct sh_mobile_meram_priv *priv, unsigned long mem,
|
|
size_t size)
|
|
{
|
|
gen_pool_free(priv->pool, mem, size);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* LCDC cache planes allocation, init, cleanup and free
|
|
*/
|
|
|
|
/* Allocate ICBs and MERAM for a plane. */
|
|
static int meram_plane_alloc(struct sh_mobile_meram_priv *priv,
|
|
struct sh_mobile_meram_fb_plane *plane,
|
|
size_t size)
|
|
{
|
|
unsigned long mem;
|
|
unsigned long idx;
|
|
|
|
idx = find_first_zero_bit(&priv->used_icb, 28);
|
|
if (idx == 28)
|
|
return -ENOMEM;
|
|
plane->cache = &priv->icbs[idx];
|
|
|
|
idx = find_next_zero_bit(&priv->used_icb, 32, 28);
|
|
if (idx == 32)
|
|
return -ENOMEM;
|
|
plane->marker = &priv->icbs[idx];
|
|
|
|
mem = meram_alloc(priv, size * 1024);
|
|
if (mem == 0)
|
|
return -ENOMEM;
|
|
|
|
__set_bit(plane->marker->index, &priv->used_icb);
|
|
__set_bit(plane->cache->index, &priv->used_icb);
|
|
|
|
plane->marker->offset = mem - priv->meram;
|
|
plane->marker->size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Free ICBs and MERAM for a plane. */
|
|
static void meram_plane_free(struct sh_mobile_meram_priv *priv,
|
|
struct sh_mobile_meram_fb_plane *plane)
|
|
{
|
|
meram_free(priv, priv->meram + plane->marker->offset,
|
|
plane->marker->size * 1024);
|
|
|
|
__clear_bit(plane->marker->index, &priv->used_icb);
|
|
__clear_bit(plane->cache->index, &priv->used_icb);
|
|
}
|
|
|
|
/* Is this a YCbCr(NV12, NV16 or NV24) colorspace? */
|
|
static int is_nvcolor(int cspace)
|
|
{
|
|
if (cspace == SH_MOBILE_MERAM_PF_NV ||
|
|
cspace == SH_MOBILE_MERAM_PF_NV24)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Set the next address to fetch. */
|
|
static void meram_set_next_addr(struct sh_mobile_meram_priv *priv,
|
|
struct sh_mobile_meram_fb_cache *cache,
|
|
unsigned long base_addr_y,
|
|
unsigned long base_addr_c)
|
|
{
|
|
struct sh_mobile_meram_icb *icb = cache->planes[0].marker;
|
|
unsigned long target;
|
|
|
|
icb->current_reg ^= 1;
|
|
target = icb->current_reg ? MExxSARB : MExxSARA;
|
|
|
|
/* set the next address to fetch */
|
|
meram_write_icb(priv->base, cache->planes[0].cache->index, target,
|
|
base_addr_y);
|
|
meram_write_icb(priv->base, cache->planes[0].marker->index, target,
|
|
base_addr_y + cache->planes[0].marker->cache_unit);
|
|
|
|
if (cache->nplanes == 2) {
|
|
meram_write_icb(priv->base, cache->planes[1].cache->index,
|
|
target, base_addr_c);
|
|
meram_write_icb(priv->base, cache->planes[1].marker->index,
|
|
target, base_addr_c +
|
|
cache->planes[1].marker->cache_unit);
|
|
}
|
|
}
|
|
|
|
/* Get the next ICB address. */
|
|
static void
|
|
meram_get_next_icb_addr(struct sh_mobile_meram_info *pdata,
|
|
struct sh_mobile_meram_fb_cache *cache,
|
|
unsigned long *icb_addr_y, unsigned long *icb_addr_c)
|
|
{
|
|
struct sh_mobile_meram_icb *icb = cache->planes[0].marker;
|
|
unsigned long icb_offset;
|
|
|
|
if (pdata->addr_mode == SH_MOBILE_MERAM_MODE0)
|
|
icb_offset = 0x80000000 | (icb->current_reg << 29);
|
|
else
|
|
icb_offset = 0xc0000000 | (icb->current_reg << 23);
|
|
|
|
*icb_addr_y = icb_offset | (cache->planes[0].marker->index << 24);
|
|
if (cache->nplanes == 2)
|
|
*icb_addr_c = icb_offset
|
|
| (cache->planes[1].marker->index << 24);
|
|
}
|
|
|
|
#define MERAM_CALC_BYTECOUNT(x, y) \
|
|
(((x) * (y) + (MERAM_LINE_WIDTH - 1)) & ~(MERAM_LINE_WIDTH - 1))
|
|
|
|
/* Initialize MERAM. */
|
|
static int meram_plane_init(struct sh_mobile_meram_priv *priv,
|
|
struct sh_mobile_meram_fb_plane *plane,
|
|
unsigned int xres, unsigned int yres,
|
|
unsigned int *out_pitch)
|
|
{
|
|
struct sh_mobile_meram_icb *marker = plane->marker;
|
|
unsigned long total_byte_count = MERAM_CALC_BYTECOUNT(xres, yres);
|
|
unsigned long bnm;
|
|
unsigned int lcdc_pitch;
|
|
unsigned int xpitch;
|
|
unsigned int line_cnt;
|
|
unsigned int save_lines;
|
|
|
|
/* adjust pitch to 1024, 2048, 4096 or 8192 */
|
|
lcdc_pitch = (xres - 1) | 1023;
|
|
lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 1);
|
|
lcdc_pitch = lcdc_pitch | (lcdc_pitch >> 2);
|
|
lcdc_pitch += 1;
|
|
|
|
/* derive settings */
|
|
if (lcdc_pitch == 8192 && yres >= 1024) {
|
|
lcdc_pitch = xpitch = MERAM_LINE_WIDTH;
|
|
line_cnt = total_byte_count >> 11;
|
|
*out_pitch = xres;
|
|
save_lines = plane->marker->size / 16 / MERAM_SEC_LINE;
|
|
save_lines *= MERAM_SEC_LINE;
|
|
} else {
|
|
xpitch = xres;
|
|
line_cnt = yres;
|
|
*out_pitch = lcdc_pitch;
|
|
save_lines = plane->marker->size / (lcdc_pitch >> 10) / 2;
|
|
save_lines &= 0xff;
|
|
}
|
|
bnm = (save_lines - 1) << 16;
|
|
|
|
/* TODO: we better to check if we have enough MERAM buffer size */
|
|
|
|
/* set up ICB */
|
|
meram_write_icb(priv->base, plane->cache->index, MExxBSIZE,
|
|
MERAM_MExxBSIZE_VAL(0x0, line_cnt - 1, xpitch - 1));
|
|
meram_write_icb(priv->base, plane->marker->index, MExxBSIZE,
|
|
MERAM_MExxBSIZE_VAL(0xf, line_cnt - 1, xpitch - 1));
|
|
|
|
meram_write_icb(priv->base, plane->cache->index, MExxMNCF, bnm);
|
|
meram_write_icb(priv->base, plane->marker->index, MExxMNCF, bnm);
|
|
|
|
meram_write_icb(priv->base, plane->cache->index, MExxSBSIZE, xpitch);
|
|
meram_write_icb(priv->base, plane->marker->index, MExxSBSIZE, xpitch);
|
|
|
|
/* save a cache unit size */
|
|
plane->cache->cache_unit = xres * save_lines;
|
|
plane->marker->cache_unit = xres * save_lines;
|
|
|
|
/*
|
|
* Set MERAM for framebuffer
|
|
*
|
|
* we also chain the cache_icb and the marker_icb.
|
|
* we also split the allocated MERAM buffer between two ICBs.
|
|
*/
|
|
meram_write_icb(priv->base, plane->cache->index, MExxCTL,
|
|
MERAM_MExxCTL_VAL(plane->marker->index, marker->offset)
|
|
| MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
|
|
MExxCTL_MD_FB);
|
|
meram_write_icb(priv->base, plane->marker->index, MExxCTL,
|
|
MERAM_MExxCTL_VAL(plane->cache->index, marker->offset +
|
|
plane->marker->size / 2) |
|
|
MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
|
|
MExxCTL_MD_FB);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meram_plane_cleanup(struct sh_mobile_meram_priv *priv,
|
|
struct sh_mobile_meram_fb_plane *plane)
|
|
{
|
|
/* disable ICB */
|
|
meram_write_icb(priv->base, plane->cache->index, MExxCTL,
|
|
MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF);
|
|
meram_write_icb(priv->base, plane->marker->index, MExxCTL,
|
|
MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF);
|
|
|
|
plane->cache->cache_unit = 0;
|
|
plane->marker->cache_unit = 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* MERAM operations
|
|
*/
|
|
|
|
unsigned long sh_mobile_meram_alloc(struct sh_mobile_meram_info *pdata,
|
|
size_t size)
|
|
{
|
|
struct sh_mobile_meram_priv *priv = pdata->priv;
|
|
|
|
return meram_alloc(priv, size);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sh_mobile_meram_alloc);
|
|
|
|
void sh_mobile_meram_free(struct sh_mobile_meram_info *pdata, unsigned long mem,
|
|
size_t size)
|
|
{
|
|
struct sh_mobile_meram_priv *priv = pdata->priv;
|
|
|
|
meram_free(priv, mem, size);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sh_mobile_meram_free);
|
|
|
|
/* Allocate memory for the ICBs and mark them as used. */
|
|
static struct sh_mobile_meram_fb_cache *
|
|
meram_cache_alloc(struct sh_mobile_meram_priv *priv,
|
|
const struct sh_mobile_meram_cfg *cfg,
|
|
int pixelformat)
|
|
{
|
|
unsigned int nplanes = is_nvcolor(pixelformat) ? 2 : 1;
|
|
struct sh_mobile_meram_fb_cache *cache;
|
|
int ret;
|
|
|
|
cache = kzalloc(sizeof(*cache), GFP_KERNEL);
|
|
if (cache == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
cache->nplanes = nplanes;
|
|
|
|
ret = meram_plane_alloc(priv, &cache->planes[0],
|
|
cfg->icb[0].meram_size);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
cache->planes[0].marker->current_reg = 1;
|
|
cache->planes[0].marker->pixelformat = pixelformat;
|
|
|
|
if (cache->nplanes == 1)
|
|
return cache;
|
|
|
|
ret = meram_plane_alloc(priv, &cache->planes[1],
|
|
cfg->icb[1].meram_size);
|
|
if (ret < 0) {
|
|
meram_plane_free(priv, &cache->planes[0]);
|
|
goto error;
|
|
}
|
|
|
|
return cache;
|
|
|
|
error:
|
|
kfree(cache);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
void *sh_mobile_meram_cache_alloc(struct sh_mobile_meram_info *pdata,
|
|
const struct sh_mobile_meram_cfg *cfg,
|
|
unsigned int xres, unsigned int yres,
|
|
unsigned int pixelformat, unsigned int *pitch)
|
|
{
|
|
struct sh_mobile_meram_fb_cache *cache;
|
|
struct sh_mobile_meram_priv *priv = pdata->priv;
|
|
struct platform_device *pdev = pdata->pdev;
|
|
unsigned int nplanes = is_nvcolor(pixelformat) ? 2 : 1;
|
|
unsigned int out_pitch;
|
|
|
|
if (priv == NULL)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
if (pixelformat != SH_MOBILE_MERAM_PF_NV &&
|
|
pixelformat != SH_MOBILE_MERAM_PF_NV24 &&
|
|
pixelformat != SH_MOBILE_MERAM_PF_RGB)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
dev_dbg(&pdev->dev, "registering %dx%d (%s)", xres, yres,
|
|
!pixelformat ? "yuv" : "rgb");
|
|
|
|
/* we can't handle wider than 8192px */
|
|
if (xres > 8192) {
|
|
dev_err(&pdev->dev, "width exceeding the limit (> 8192).");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (cfg->icb[0].meram_size == 0)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (nplanes == 2 && cfg->icb[1].meram_size == 0)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
/* We now register the ICBs and allocate the MERAM regions. */
|
|
cache = meram_cache_alloc(priv, cfg, pixelformat);
|
|
if (IS_ERR(cache)) {
|
|
dev_err(&pdev->dev, "MERAM allocation failed (%ld).",
|
|
PTR_ERR(cache));
|
|
goto err;
|
|
}
|
|
|
|
/* initialize MERAM */
|
|
meram_plane_init(priv, &cache->planes[0], xres, yres, &out_pitch);
|
|
*pitch = out_pitch;
|
|
if (pixelformat == SH_MOBILE_MERAM_PF_NV)
|
|
meram_plane_init(priv, &cache->planes[1],
|
|
xres, (yres + 1) / 2, &out_pitch);
|
|
else if (pixelformat == SH_MOBILE_MERAM_PF_NV24)
|
|
meram_plane_init(priv, &cache->planes[1],
|
|
2 * xres, (yres + 1) / 2, &out_pitch);
|
|
|
|
err:
|
|
mutex_unlock(&priv->lock);
|
|
return cache;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_alloc);
|
|
|
|
void
|
|
sh_mobile_meram_cache_free(struct sh_mobile_meram_info *pdata, void *data)
|
|
{
|
|
struct sh_mobile_meram_fb_cache *cache = data;
|
|
struct sh_mobile_meram_priv *priv = pdata->priv;
|
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
/* Cleanup and free. */
|
|
meram_plane_cleanup(priv, &cache->planes[0]);
|
|
meram_plane_free(priv, &cache->planes[0]);
|
|
|
|
if (cache->nplanes == 2) {
|
|
meram_plane_cleanup(priv, &cache->planes[1]);
|
|
meram_plane_free(priv, &cache->planes[1]);
|
|
}
|
|
|
|
kfree(cache);
|
|
|
|
mutex_unlock(&priv->lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_free);
|
|
|
|
void
|
|
sh_mobile_meram_cache_update(struct sh_mobile_meram_info *pdata, void *data,
|
|
unsigned long base_addr_y,
|
|
unsigned long base_addr_c,
|
|
unsigned long *icb_addr_y,
|
|
unsigned long *icb_addr_c)
|
|
{
|
|
struct sh_mobile_meram_fb_cache *cache = data;
|
|
struct sh_mobile_meram_priv *priv = pdata->priv;
|
|
|
|
mutex_lock(&priv->lock);
|
|
|
|
meram_set_next_addr(priv, cache, base_addr_y, base_addr_c);
|
|
meram_get_next_icb_addr(pdata, cache, icb_addr_y, icb_addr_c);
|
|
|
|
mutex_unlock(&priv->lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sh_mobile_meram_cache_update);
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Power management
|
|
*/
|
|
|
|
#ifdef CONFIG_PM
|
|
static int sh_mobile_meram_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
|
|
unsigned int i, j;
|
|
|
|
for (i = 0; i < MERAM_REGS_SIZE; i++)
|
|
priv->regs[i] = meram_read_reg(priv->base, common_regs[i]);
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (!test_bit(i, &priv->used_icb))
|
|
continue;
|
|
for (j = 0; j < ICB_REGS_SIZE; j++) {
|
|
priv->icbs[i].regs[j] =
|
|
meram_read_icb(priv->base, i, icb_regs[j]);
|
|
/* Reset ICB on resume */
|
|
if (icb_regs[j] == MExxCTL)
|
|
priv->icbs[i].regs[j] |=
|
|
MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_meram_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
|
|
unsigned int i, j;
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (!test_bit(i, &priv->used_icb))
|
|
continue;
|
|
for (j = 0; j < ICB_REGS_SIZE; j++)
|
|
meram_write_icb(priv->base, i, icb_regs[j],
|
|
priv->icbs[i].regs[j]);
|
|
}
|
|
|
|
for (i = 0; i < MERAM_REGS_SIZE; i++)
|
|
meram_write_reg(priv->base, common_regs[i], priv->regs[i]);
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
static UNIVERSAL_DEV_PM_OPS(sh_mobile_meram_dev_pm_ops,
|
|
sh_mobile_meram_suspend,
|
|
sh_mobile_meram_resume, NULL);
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Probe/remove and driver init/exit
|
|
*/
|
|
|
|
static int sh_mobile_meram_probe(struct platform_device *pdev)
|
|
{
|
|
struct sh_mobile_meram_priv *priv;
|
|
struct sh_mobile_meram_info *pdata = pdev->dev.platform_data;
|
|
struct resource *regs;
|
|
struct resource *meram;
|
|
unsigned int i;
|
|
int error;
|
|
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "no platform data defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
meram = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
if (regs == NULL || meram == NULL) {
|
|
dev_err(&pdev->dev, "cannot get platform resources\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
dev_err(&pdev->dev, "cannot allocate device data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize private data. */
|
|
mutex_init(&priv->lock);
|
|
priv->used_icb = pdata->reserved_icbs;
|
|
|
|
for (i = 0; i < MERAM_ICB_NUM; ++i)
|
|
priv->icbs[i].index = i;
|
|
|
|
pdata->priv = priv;
|
|
pdata->pdev = pdev;
|
|
|
|
/* Request memory regions and remap the registers. */
|
|
if (!request_mem_region(regs->start, resource_size(regs), pdev->name)) {
|
|
dev_err(&pdev->dev, "MERAM registers region already claimed\n");
|
|
error = -EBUSY;
|
|
goto err_req_regs;
|
|
}
|
|
|
|
if (!request_mem_region(meram->start, resource_size(meram),
|
|
pdev->name)) {
|
|
dev_err(&pdev->dev, "MERAM memory region already claimed\n");
|
|
error = -EBUSY;
|
|
goto err_req_meram;
|
|
}
|
|
|
|
priv->base = ioremap_nocache(regs->start, resource_size(regs));
|
|
if (!priv->base) {
|
|
dev_err(&pdev->dev, "ioremap failed\n");
|
|
error = -EFAULT;
|
|
goto err_ioremap;
|
|
}
|
|
|
|
priv->meram = meram->start;
|
|
|
|
/* Create and initialize the MERAM memory pool. */
|
|
priv->pool = gen_pool_create(ilog2(MERAM_GRANULARITY), -1);
|
|
if (priv->pool == NULL) {
|
|
error = -ENOMEM;
|
|
goto err_genpool;
|
|
}
|
|
|
|
error = gen_pool_add(priv->pool, meram->start, resource_size(meram),
|
|
-1);
|
|
if (error < 0)
|
|
goto err_genpool;
|
|
|
|
/* initialize ICB addressing mode */
|
|
if (pdata->addr_mode == SH_MOBILE_MERAM_MODE1)
|
|
meram_write_reg(priv->base, MEVCR1, MEVCR1_AMD1);
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
dev_info(&pdev->dev, "sh_mobile_meram initialized.");
|
|
|
|
return 0;
|
|
|
|
err_genpool:
|
|
if (priv->pool)
|
|
gen_pool_destroy(priv->pool);
|
|
iounmap(priv->base);
|
|
err_ioremap:
|
|
release_mem_region(meram->start, resource_size(meram));
|
|
err_req_meram:
|
|
release_mem_region(regs->start, resource_size(regs));
|
|
err_req_regs:
|
|
mutex_destroy(&priv->lock);
|
|
kfree(priv);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static int sh_mobile_meram_remove(struct platform_device *pdev)
|
|
{
|
|
struct sh_mobile_meram_priv *priv = platform_get_drvdata(pdev);
|
|
struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
struct resource *meram = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
gen_pool_destroy(priv->pool);
|
|
|
|
iounmap(priv->base);
|
|
release_mem_region(meram->start, resource_size(meram));
|
|
release_mem_region(regs->start, resource_size(regs));
|
|
|
|
mutex_destroy(&priv->lock);
|
|
|
|
kfree(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver sh_mobile_meram_driver = {
|
|
.driver = {
|
|
.name = "sh_mobile_meram",
|
|
.pm = &sh_mobile_meram_dev_pm_ops,
|
|
},
|
|
.probe = sh_mobile_meram_probe,
|
|
.remove = sh_mobile_meram_remove,
|
|
};
|
|
|
|
module_platform_driver(sh_mobile_meram_driver);
|
|
|
|
MODULE_DESCRIPTION("SuperH Mobile MERAM driver");
|
|
MODULE_AUTHOR("Damian Hobson-Garcia / Takanari Hayama");
|
|
MODULE_LICENSE("GPL v2");
|