/* * drivers/video/tegra/host/mpe/mpe.c * * Tegra Graphics Host MPE * * Copyright (c) 2010-2012, NVIDIA Corporation. * * 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 . */ #include "nvhost_hwctx.h" #include "nvhost_channel.h" #include "dev.h" #include "host1x/host1x_hardware.h" #include "host1x/host1x_channel.h" #include "host1x/host1x_syncpt.h" #include "host1x/host1x_hwctx.h" #include "t20/t20.h" #include #include #include #include #include "bus_client.h" enum { HWCTX_REGINFO_NORMAL = 0, HWCTX_REGINFO_STASH, HWCTX_REGINFO_CALCULATE, HWCTX_REGINFO_WRITEBACK }; const struct hwctx_reginfo ctxsave_regs_mpe[] = { HWCTX_REGINFO(0x124, 1, STASH), HWCTX_REGINFO(0x123, 1, STASH), HWCTX_REGINFO(0x103, 1, STASH), HWCTX_REGINFO(0x074, 1, STASH), HWCTX_REGINFO(0x021, 1, NORMAL), HWCTX_REGINFO(0x020, 1, STASH), HWCTX_REGINFO(0x024, 2, NORMAL), HWCTX_REGINFO(0x0e6, 1, NORMAL), HWCTX_REGINFO(0x3fc, 1, NORMAL), HWCTX_REGINFO(0x3d0, 1, NORMAL), HWCTX_REGINFO(0x3d4, 1, NORMAL), HWCTX_REGINFO(0x013, 1, NORMAL), HWCTX_REGINFO(0x022, 1, NORMAL), HWCTX_REGINFO(0x030, 4, NORMAL), HWCTX_REGINFO(0x023, 1, NORMAL), HWCTX_REGINFO(0x070, 1, NORMAL), HWCTX_REGINFO(0x0a0, 9, NORMAL), HWCTX_REGINFO(0x071, 1, NORMAL), HWCTX_REGINFO(0x100, 4, NORMAL), HWCTX_REGINFO(0x104, 2, NORMAL), HWCTX_REGINFO(0x108, 9, NORMAL), HWCTX_REGINFO(0x112, 2, NORMAL), HWCTX_REGINFO(0x114, 1, STASH), HWCTX_REGINFO(0x014, 1, NORMAL), HWCTX_REGINFO(0x072, 1, NORMAL), HWCTX_REGINFO(0x200, 1, NORMAL), HWCTX_REGINFO(0x0d1, 1, NORMAL), HWCTX_REGINFO(0x0d0, 1, NORMAL), HWCTX_REGINFO(0x0c0, 1, NORMAL), HWCTX_REGINFO(0x0c3, 2, NORMAL), HWCTX_REGINFO(0x0d2, 1, NORMAL), HWCTX_REGINFO(0x0d8, 1, NORMAL), HWCTX_REGINFO(0x0e0, 2, NORMAL), HWCTX_REGINFO(0x07f, 2, NORMAL), HWCTX_REGINFO(0x084, 8, NORMAL), HWCTX_REGINFO(0x0d3, 1, NORMAL), HWCTX_REGINFO(0x040, 13, NORMAL), HWCTX_REGINFO(0x050, 6, NORMAL), HWCTX_REGINFO(0x058, 1, NORMAL), HWCTX_REGINFO(0x057, 1, NORMAL), HWCTX_REGINFO(0x111, 1, NORMAL), HWCTX_REGINFO(0x130, 3, NORMAL), HWCTX_REGINFO(0x201, 1, NORMAL), HWCTX_REGINFO(0x068, 2, NORMAL), HWCTX_REGINFO(0x08c, 1, NORMAL), HWCTX_REGINFO(0x0cf, 1, NORMAL), HWCTX_REGINFO(0x082, 2, NORMAL), HWCTX_REGINFO(0x075, 1, NORMAL), HWCTX_REGINFO(0x0e8, 1, NORMAL), HWCTX_REGINFO(0x056, 1, NORMAL), HWCTX_REGINFO(0x057, 1, NORMAL), HWCTX_REGINFO(0x073, 1, CALCULATE), HWCTX_REGINFO(0x074, 1, NORMAL), HWCTX_REGINFO(0x075, 1, NORMAL), HWCTX_REGINFO(0x076, 1, STASH), HWCTX_REGINFO(0x11a, 9, NORMAL), HWCTX_REGINFO(0x123, 1, NORMAL), HWCTX_REGINFO(0x124, 1, NORMAL), HWCTX_REGINFO(0x12a, 5, NORMAL), HWCTX_REGINFO(0x12f, 1, STASH), HWCTX_REGINFO(0x125, 2, NORMAL), HWCTX_REGINFO(0x034, 1, NORMAL), HWCTX_REGINFO(0x133, 2, NORMAL), HWCTX_REGINFO(0x127, 1, NORMAL), HWCTX_REGINFO(0x106, 1, WRITEBACK), HWCTX_REGINFO(0x107, 1, WRITEBACK) }; #define NR_STASHES 8 #define NR_WRITEBACKS 2 #define RC_RAM_LOAD_CMD 0x115 #define RC_RAM_LOAD_DATA 0x116 #define RC_RAM_READ_CMD 0x128 #define RC_RAM_READ_DATA 0x129 #define RC_RAM_SIZE 692 #define IRFR_RAM_LOAD_CMD 0xc5 #define IRFR_RAM_LOAD_DATA 0xc6 #define IRFR_RAM_READ_CMD 0xcd #define IRFR_RAM_READ_DATA 0xce #define IRFR_RAM_SIZE 408 struct mpe_save_info { u32 in[NR_STASHES]; u32 out[NR_WRITEBACKS]; unsigned in_pos; unsigned out_pos; u32 h264_mode; }; /*** restore ***/ static unsigned int restore_size; static void restore_begin(struct host1x_hwctx_handler *h, u32 *ptr) { /* set class to host */ ptr[0] = nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, NV_CLASS_HOST_INCR_SYNCPT_BASE, 1); /* increment sync point base */ ptr[1] = nvhost_class_host_incr_syncpt_base(h->waitbase, 1); /* set class to MPE */ ptr[2] = nvhost_opcode_setclass(NV_VIDEO_ENCODE_MPEG_CLASS_ID, 0, 0); } #define RESTORE_BEGIN_SIZE 3 static void restore_ram(u32 *ptr, unsigned words, unsigned cmd_reg, unsigned data_reg) { ptr[0] = nvhost_opcode_imm(cmd_reg, words); ptr[1] = nvhost_opcode_nonincr(data_reg, words); } #define RESTORE_RAM_SIZE 2 static void restore_end(struct host1x_hwctx_handler *h, u32 *ptr) { /* syncpt increment to track restore gather. */ ptr[0] = nvhost_opcode_imm_incr_syncpt(NV_SYNCPT_OP_DONE, h->syncpt); } #define RESTORE_END_SIZE 1 static u32 *setup_restore_regs(u32 *ptr, const struct hwctx_reginfo *regs, unsigned int nr_regs) { const struct hwctx_reginfo *rend = regs + nr_regs; for ( ; regs != rend; ++regs) { u32 offset = regs->offset; u32 count = regs->count; *ptr++ = nvhost_opcode_incr(offset, count); ptr += count; } return ptr; } static u32 *setup_restore_ram(u32 *ptr, unsigned words, unsigned cmd_reg, unsigned data_reg) { restore_ram(ptr, words, cmd_reg, data_reg); return ptr + (RESTORE_RAM_SIZE + words); } static void setup_restore(struct host1x_hwctx_handler *h, u32 *ptr) { restore_begin(h, ptr); ptr += RESTORE_BEGIN_SIZE; ptr = setup_restore_regs(ptr, ctxsave_regs_mpe, ARRAY_SIZE(ctxsave_regs_mpe)); ptr = setup_restore_ram(ptr, RC_RAM_SIZE, RC_RAM_LOAD_CMD, RC_RAM_LOAD_DATA); ptr = setup_restore_ram(ptr, IRFR_RAM_SIZE, IRFR_RAM_LOAD_CMD, IRFR_RAM_LOAD_DATA); restore_end(h, ptr); wmb(); } /*** save ***/ struct save_info { u32 *ptr; unsigned int save_count; unsigned int restore_count; }; static void __init save_begin(struct host1x_hwctx_handler *h, u32 *ptr) { /* MPE: when done, increment syncpt to base+1 */ ptr[0] = nvhost_opcode_setclass(NV_VIDEO_ENCODE_MPEG_CLASS_ID, 0, 0); ptr[1] = nvhost_opcode_imm_incr_syncpt(NV_SYNCPT_OP_DONE, h->syncpt); /* host: wait for syncpt base+1 */ ptr[2] = nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, NV_CLASS_HOST_WAIT_SYNCPT_BASE, 1); ptr[3] = nvhost_class_host_wait_syncpt_base(h->syncpt, h->waitbase, 1); /* host: signal context read thread to start reading */ ptr[4] = nvhost_opcode_imm_incr_syncpt(NV_SYNCPT_IMMEDIATE, h->syncpt); } #define SAVE_BEGIN_SIZE 5 static void __init save_direct(u32 *ptr, u32 start_reg, u32 count) { ptr[0] = nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, NV_CLASS_HOST_INDOFF, 1); ptr[1] = nvhost_class_host_indoff_reg_read(NV_HOST_MODULE_MPE, start_reg, true); ptr[2] = nvhost_opcode_nonincr(NV_CLASS_HOST_INDDATA, count); } #define SAVE_DIRECT_SIZE 3 static void __init save_set_ram_cmd(u32 *ptr, u32 cmd_reg, u32 count) { ptr[0] = nvhost_opcode_setclass(NV_VIDEO_ENCODE_MPEG_CLASS_ID, cmd_reg, 1); ptr[1] = count; } #define SAVE_SET_RAM_CMD_SIZE 2 static void __init save_read_ram_data_nasty(u32 *ptr, u32 data_reg) { ptr[0] = nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, NV_CLASS_HOST_INDOFF, 1); ptr[1] = nvhost_class_host_indoff_reg_read(NV_HOST_MODULE_MPE, data_reg, false); ptr[2] = nvhost_opcode_imm(NV_CLASS_HOST_INDDATA, 0); /* write junk data to avoid 'cached problem with register memory' */ ptr[3] = nvhost_opcode_setclass(NV_VIDEO_ENCODE_MPEG_CLASS_ID, data_reg, 1); ptr[4] = 0x99; } #define SAVE_READ_RAM_DATA_NASTY_SIZE 5 static void __init save_end(struct host1x_hwctx_handler *h, u32 *ptr) { /* Wait for context read service to finish (cpu incr 3) */ ptr[0] = nvhost_opcode_setclass(NV_HOST1X_CLASS_ID, NV_CLASS_HOST_WAIT_SYNCPT_BASE, 1); ptr[1] = nvhost_class_host_wait_syncpt_base(h->syncpt, h->waitbase, 3); /* Advance syncpoint base */ ptr[2] = nvhost_opcode_nonincr(NV_CLASS_HOST_INCR_SYNCPT_BASE, 1); ptr[3] = nvhost_class_host_incr_syncpt_base(h->waitbase, 3); /* set class back to the unit */ ptr[4] = nvhost_opcode_setclass(NV_VIDEO_ENCODE_MPEG_CLASS_ID, 0, 0); } #define SAVE_END_SIZE 5 static void __init setup_save_regs(struct save_info *info, const struct hwctx_reginfo *regs, unsigned int nr_regs) { const struct hwctx_reginfo *rend = regs + nr_regs; u32 *ptr = info->ptr; unsigned int save_count = info->save_count; unsigned int restore_count = info->restore_count; for ( ; regs != rend; ++regs) { u32 offset = regs->offset; u32 count = regs->count; if (regs->type != HWCTX_REGINFO_WRITEBACK) { if (ptr) { save_direct(ptr, offset, count); ptr += SAVE_DIRECT_SIZE; memset(ptr, 0, count * 4); ptr += count; } save_count += (SAVE_DIRECT_SIZE + count); } restore_count += (1 + count); } info->ptr = ptr; info->save_count = save_count; info->restore_count = restore_count; } static void __init setup_save_ram_nasty(struct save_info *info, unsigned words, unsigned cmd_reg, unsigned data_reg) { u32 *ptr = info->ptr; unsigned int save_count = info->save_count; unsigned int restore_count = info->restore_count; unsigned i; if (ptr) { save_set_ram_cmd(ptr, cmd_reg, words); ptr += SAVE_SET_RAM_CMD_SIZE; for (i = words; i; --i) { save_read_ram_data_nasty(ptr, data_reg); ptr += SAVE_READ_RAM_DATA_NASTY_SIZE; } } save_count += SAVE_SET_RAM_CMD_SIZE; save_count += words * SAVE_READ_RAM_DATA_NASTY_SIZE; restore_count += (RESTORE_RAM_SIZE + words); info->ptr = ptr; info->save_count = save_count; info->restore_count = restore_count; } static void __init setup_save(struct host1x_hwctx_handler *h, u32 *ptr) { struct save_info info = { ptr, SAVE_BEGIN_SIZE, RESTORE_BEGIN_SIZE }; if (info.ptr) { save_begin(h, info.ptr); info.ptr += SAVE_BEGIN_SIZE; } setup_save_regs(&info, ctxsave_regs_mpe, ARRAY_SIZE(ctxsave_regs_mpe)); setup_save_ram_nasty(&info, RC_RAM_SIZE, RC_RAM_READ_CMD, RC_RAM_READ_DATA); setup_save_ram_nasty(&info, IRFR_RAM_SIZE, IRFR_RAM_READ_CMD, IRFR_RAM_READ_DATA); if (info.ptr) { save_end(h, info.ptr); info.ptr += SAVE_END_SIZE; } wmb(); h->save_size = info.save_count + SAVE_END_SIZE; restore_size = info.restore_count + RESTORE_END_SIZE; } static u32 calculate_mpe(u32 word, struct mpe_save_info *msi) { u32 buffer_full_read = msi->in[0] & 0x01ffffff; u32 byte_len = msi->in[1]; u32 drain = (msi->in[2] >> 2) & 0x007fffff; u32 rep_frame = msi->in[3] & 0x0000ffff; u32 h264_mode = (msi->in[4] >> 11) & 1; int new_buffer_full; if (h264_mode) byte_len >>= 3; new_buffer_full = buffer_full_read + byte_len - (drain * 4); msi->out[0] = max(0, new_buffer_full); msi->out[1] = rep_frame; if (rep_frame == 0) word &= 0xffff0000; return word; } static u32 *save_regs(u32 *ptr, unsigned int *pending, struct nvhost_channel *channel, const struct hwctx_reginfo *regs, unsigned int nr_regs, struct mpe_save_info *msi) { const struct hwctx_reginfo *rend = regs + nr_regs; for ( ; regs != rend; ++regs) { u32 count = regs->count; ++ptr; /* restore incr */ if (regs->type == HWCTX_REGINFO_NORMAL) { host1x_drain_read_fifo(channel->aperture, ptr, count, pending); ptr += count; } else { u32 word; if (regs->type == HWCTX_REGINFO_WRITEBACK) { BUG_ON(msi->out_pos >= NR_WRITEBACKS); word = msi->out[msi->out_pos++]; } else { host1x_drain_read_fifo(channel->aperture, &word, 1, pending); if (regs->type == HWCTX_REGINFO_STASH) { BUG_ON(msi->in_pos >= NR_STASHES); msi->in[msi->in_pos++] = word; } else { word = calculate_mpe(word, msi); } } *ptr++ = word; } } return ptr; } static u32 *save_ram(u32 *ptr, unsigned int *pending, struct nvhost_channel *channel, unsigned words, unsigned cmd_reg, unsigned data_reg) { int err = 0; ptr += RESTORE_RAM_SIZE; err = host1x_drain_read_fifo(channel->aperture, ptr, words, pending); WARN_ON(err); return ptr + words; } /*** ctxmpe ***/ static struct nvhost_hwctx *ctxmpe_alloc(struct nvhost_hwctx_handler *h, struct nvhost_channel *ch) { struct nvmap_client *nvmap = nvhost_get_host(ch->dev)->nvmap; struct host1x_hwctx_handler *p = to_host1x_hwctx_handler(h); struct host1x_hwctx *ctx; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; ctx->restore = nvmap_alloc(nvmap, restore_size * 4, 32, NVMAP_HANDLE_WRITE_COMBINE, 0); if (IS_ERR_OR_NULL(ctx->restore)) { kfree(ctx); return NULL; } ctx->restore_virt = nvmap_mmap(ctx->restore); if (!ctx->restore_virt) { nvmap_free(nvmap, ctx->restore); kfree(ctx); return NULL; } kref_init(&ctx->hwctx.ref); ctx->hwctx.h = &p->h; ctx->hwctx.channel = ch; ctx->hwctx.valid = false; ctx->save_incrs = 3; ctx->save_thresh = 2; ctx->save_slots = p->save_slots; ctx->restore_phys = nvmap_pin(nvmap, ctx->restore); ctx->restore_size = restore_size; ctx->restore_incrs = 1; setup_restore(p, ctx->restore_virt); return &ctx->hwctx; } static void ctxmpe_get(struct nvhost_hwctx *ctx) { kref_get(&ctx->ref); } static void ctxmpe_free(struct kref *ref) { struct nvhost_hwctx *nctx = container_of(ref, struct nvhost_hwctx, ref); struct host1x_hwctx *ctx = to_host1x_hwctx(nctx); struct nvmap_client *nvmap = nvhost_get_host(nctx->channel->dev)->nvmap; if (ctx->restore_virt) nvmap_munmap(ctx->restore, ctx->restore_virt); nvmap_unpin(nvmap, ctx->restore); nvmap_free(nvmap, ctx->restore); kfree(ctx); } static void ctxmpe_put(struct nvhost_hwctx *ctx) { kref_put(&ctx->ref, ctxmpe_free); } static void ctxmpe_save_push(struct nvhost_hwctx *nctx, struct nvhost_cdma *cdma) { struct host1x_hwctx *ctx = to_host1x_hwctx(nctx); struct host1x_hwctx_handler *h = host1x_hwctx_handler(ctx); nvhost_cdma_push_gather(cdma, nvhost_get_host(nctx->channel->dev)->nvmap, h->save_buf, 0, nvhost_opcode_gather(h->save_size), h->save_phys); } static void ctxmpe_save_service(struct nvhost_hwctx *nctx) { struct host1x_hwctx *ctx = to_host1x_hwctx(nctx); struct host1x_hwctx_handler *h = host1x_hwctx_handler(ctx); u32 *ptr = (u32 *)ctx->restore_virt + RESTORE_BEGIN_SIZE; unsigned int pending = 0; struct mpe_save_info msi; msi.in_pos = 0; msi.out_pos = 0; ptr = save_regs(ptr, &pending, nctx->channel, ctxsave_regs_mpe, ARRAY_SIZE(ctxsave_regs_mpe), &msi); ptr = save_ram(ptr, &pending, nctx->channel, RC_RAM_SIZE, RC_RAM_READ_CMD, RC_RAM_READ_DATA); ptr = save_ram(ptr, &pending, nctx->channel, IRFR_RAM_SIZE, IRFR_RAM_READ_CMD, IRFR_RAM_READ_DATA); wmb(); nvhost_syncpt_cpu_incr(&nvhost_get_host(nctx->channel->dev)->syncpt, h->syncpt); } struct nvhost_hwctx_handler *nvhost_mpe_ctxhandler_init(u32 syncpt, u32 waitbase, struct nvhost_channel *ch) { struct nvmap_client *nvmap; u32 *save_ptr; struct host1x_hwctx_handler *p; p = kmalloc(sizeof(*p), GFP_KERNEL); if (!p) return NULL; nvmap = nvhost_get_host(ch->dev)->nvmap; p->syncpt = syncpt; p->waitbase = waitbase; setup_save(p, NULL); p->save_buf = nvmap_alloc(nvmap, p->save_size * 4, 32, NVMAP_HANDLE_WRITE_COMBINE, 0); if (IS_ERR(p->save_buf)) { p->save_buf = NULL; return NULL; } save_ptr = nvmap_mmap(p->save_buf); if (!save_ptr) { nvmap_free(nvmap, p->save_buf); p->save_buf = NULL; return NULL; } p->save_phys = nvmap_pin(nvmap, p->save_buf); p->save_slots = 1; setup_save(p, save_ptr); p->h.alloc = ctxmpe_alloc; p->h.save_push = ctxmpe_save_push; p->h.save_service = ctxmpe_save_service; p->h.get = ctxmpe_get; p->h.put = ctxmpe_put; return &p->h; } int nvhost_mpe_prepare_power_off(struct nvhost_device *dev) { return host1x_save_context(dev, NVSYNCPT_MPE); } enum mpe_ip_ver { mpe_01, mpe_02, }; struct mpe_desc { int (*prepare_poweroff)(struct nvhost_device *dev); struct nvhost_hwctx_handler *(*alloc_hwctx_handler)(u32 syncpt, u32 waitbase, struct nvhost_channel *ch); }; static const struct mpe_desc mpe[] = { [mpe_01] = { .prepare_poweroff = nvhost_mpe_prepare_power_off, .alloc_hwctx_handler = nvhost_mpe_ctxhandler_init, }, [mpe_02] = { .prepare_poweroff = nvhost_mpe_prepare_power_off, .alloc_hwctx_handler = nvhost_mpe_ctxhandler_init, }, }; static struct nvhost_device_id mpe_id[] = { { "mpe01", mpe_01 }, { "mpe02", mpe_02 }, { }, }; MODULE_DEVICE_TABLE(nvhost, mpe_id); static int __devinit mpe_probe(struct nvhost_device *dev, struct nvhost_device_id *id_table) { int err = 0; int index = 0; struct nvhost_driver *drv = to_nvhost_driver(dev->dev.driver); index = id_table->driver_data; drv->prepare_poweroff = mpe[index].prepare_poweroff; drv->alloc_hwctx_handler = mpe[index].alloc_hwctx_handler; /* reset device name so that consistent device name can be * found in clock tree */ dev->name = "mpe"; err = nvhost_client_device_get_resources(dev); if (err) return err; return nvhost_client_device_init(dev); } static int __exit mpe_remove(struct nvhost_device *dev) { /* Add clean-up */ return 0; } static int mpe_suspend(struct nvhost_device *dev, pm_message_t state) { return nvhost_client_device_suspend(dev); } static int mpe_resume(struct nvhost_device *dev) { dev_info(&dev->dev, "resuming\n"); return 0; } static struct resource mpe_resources = { .name = "regs", .start = TEGRA_MPE_BASE, .end = TEGRA_MPE_BASE + TEGRA_MPE_SIZE - 1, .flags = IORESOURCE_MEM, }; struct nvhost_device *mpe_device; static struct nvhost_driver mpe_driver = { .probe = mpe_probe, .remove = __exit_p(mpe_remove), #ifdef CONFIG_PM .suspend = mpe_suspend, .resume = mpe_resume, #endif .driver = { .owner = THIS_MODULE, .name = "mpe", }, .id_table = mpe_id, }; static int __init mpe_init(void) { int err; mpe_device = nvhost_get_device("mpe"); if (!mpe_device) return -ENXIO; /* use ARRAY_SIZE macro if resources are more than 1 */ mpe_device->resource = &mpe_resources; mpe_device->num_resources = 1; err = nvhost_device_register(mpe_device); if (err) return err; return nvhost_driver_register(&mpe_driver); } static void __exit mpe_exit(void) { nvhost_driver_unregister(&mpe_driver); } module_init(mpe_init); module_exit(mpe_exit);