/* * Samsung TV Mixer driver * * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. * * Tomasz Stanislawski, <t.stanislaws@samsung.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundiation. either version 2 of the License, * or (at your option) any later version */ #include "mixer.h" #include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/fb.h> #include <linux/delay.h> #include <linux/pm_runtime.h> #include <linux/clk.h> MODULE_AUTHOR("Tomasz Stanislawski, <t.stanislaws@samsung.com>"); MODULE_DESCRIPTION("Samsung MIXER"); MODULE_LICENSE("GPL"); /* --------- DRIVER PARAMETERS ---------- */ static struct mxr_output_conf mxr_output_conf[] = { { .output_name = "S5P HDMI connector", .module_name = "s5p-hdmi", .cookie = 1, }, { .output_name = "S5P SDO connector", .module_name = "s5p-sdo", .cookie = 0, }, }; void mxr_get_mbus_fmt(struct mxr_device *mdev, struct v4l2_mbus_framefmt *mbus_fmt) { struct v4l2_subdev *sd; int ret; mutex_lock(&mdev->mutex); sd = to_outsd(mdev); ret = v4l2_subdev_call(sd, video, g_mbus_fmt, mbus_fmt); WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); mutex_unlock(&mdev->mutex); } void mxr_streamer_get(struct mxr_device *mdev) { mutex_lock(&mdev->mutex); ++mdev->n_streamer; mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); if (mdev->n_streamer == 1) { struct v4l2_subdev *sd = to_outsd(mdev); struct v4l2_mbus_framefmt mbus_fmt; struct mxr_resources *res = &mdev->res; int ret; if (to_output(mdev)->cookie == 0) clk_set_parent(res->sclk_mixer, res->sclk_dac); else clk_set_parent(res->sclk_mixer, res->sclk_hdmi); mxr_reg_s_output(mdev, to_output(mdev)->cookie); ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mbus_fmt); WARN(ret, "failed to get mbus_fmt for output %s\n", sd->name); ret = v4l2_subdev_call(sd, video, s_stream, 1); WARN(ret, "starting stream failed for output %s\n", sd->name); mxr_reg_set_mbus_fmt(mdev, &mbus_fmt); mxr_reg_streamon(mdev); ret = mxr_reg_wait4vsync(mdev); WARN(ret, "failed to get vsync (%d) from output\n", ret); } mutex_unlock(&mdev->mutex); mxr_reg_dump(mdev); /* FIXME: what to do when streaming fails? */ } void mxr_streamer_put(struct mxr_device *mdev) { mutex_lock(&mdev->mutex); --mdev->n_streamer; mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_streamer); if (mdev->n_streamer == 0) { int ret; struct v4l2_subdev *sd = to_outsd(mdev); mxr_reg_streamoff(mdev); /* vsync applies Mixer setup */ ret = mxr_reg_wait4vsync(mdev); WARN(ret, "failed to get vsync (%d) from output\n", ret); ret = v4l2_subdev_call(sd, video, s_stream, 0); WARN(ret, "stopping stream failed for output %s\n", sd->name); } WARN(mdev->n_streamer < 0, "negative number of streamers (%d)\n", mdev->n_streamer); mutex_unlock(&mdev->mutex); mxr_reg_dump(mdev); } void mxr_output_get(struct mxr_device *mdev) { mutex_lock(&mdev->mutex); ++mdev->n_output; mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); /* turn on auxiliary driver */ if (mdev->n_output == 1) v4l2_subdev_call(to_outsd(mdev), core, s_power, 1); mutex_unlock(&mdev->mutex); } void mxr_output_put(struct mxr_device *mdev) { mutex_lock(&mdev->mutex); --mdev->n_output; mxr_dbg(mdev, "%s(%d)\n", __func__, mdev->n_output); /* turn on auxiliary driver */ if (mdev->n_output == 0) v4l2_subdev_call(to_outsd(mdev), core, s_power, 0); WARN(mdev->n_output < 0, "negative number of output users (%d)\n", mdev->n_output); mutex_unlock(&mdev->mutex); } int mxr_power_get(struct mxr_device *mdev) { int ret = pm_runtime_get_sync(mdev->dev); /* returning 1 means that power is already enabled, * so zero success be returned */ if (IS_ERR_VALUE(ret)) return ret; return 0; } void mxr_power_put(struct mxr_device *mdev) { pm_runtime_put_sync(mdev->dev); } /* --------- RESOURCE MANAGEMENT -------------*/ static int mxr_acquire_plat_resources(struct mxr_device *mdev, struct platform_device *pdev) { struct resource *res; int ret; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mxr"); if (res == NULL) { mxr_err(mdev, "get memory resource failed.\n"); ret = -ENXIO; goto fail; } mdev->res.mxr_regs = ioremap(res->start, resource_size(res)); if (mdev->res.mxr_regs == NULL) { mxr_err(mdev, "register mapping failed.\n"); ret = -ENXIO; goto fail; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vp"); if (res == NULL) { mxr_err(mdev, "get memory resource failed.\n"); ret = -ENXIO; goto fail_mxr_regs; } mdev->res.vp_regs = ioremap(res->start, resource_size(res)); if (mdev->res.vp_regs == NULL) { mxr_err(mdev, "register mapping failed.\n"); ret = -ENXIO; goto fail_mxr_regs; } res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq"); if (res == NULL) { mxr_err(mdev, "get interrupt resource failed.\n"); ret = -ENXIO; goto fail_vp_regs; } ret = request_irq(res->start, mxr_irq_handler, 0, "s5p-mixer", mdev); if (ret) { mxr_err(mdev, "request interrupt failed.\n"); goto fail_vp_regs; } mdev->res.irq = res->start; return 0; fail_vp_regs: iounmap(mdev->res.vp_regs); fail_mxr_regs: iounmap(mdev->res.mxr_regs); fail: return ret; } static void mxr_resource_clear_clocks(struct mxr_resources *res) { res->mixer = ERR_PTR(-EINVAL); res->vp = ERR_PTR(-EINVAL); res->sclk_mixer = ERR_PTR(-EINVAL); res->sclk_hdmi = ERR_PTR(-EINVAL); res->sclk_dac = ERR_PTR(-EINVAL); } static void mxr_release_plat_resources(struct mxr_device *mdev) { free_irq(mdev->res.irq, mdev); iounmap(mdev->res.vp_regs); iounmap(mdev->res.mxr_regs); } static void mxr_release_clocks(struct mxr_device *mdev) { struct mxr_resources *res = &mdev->res; if (!IS_ERR(res->sclk_dac)) clk_put(res->sclk_dac); if (!IS_ERR(res->sclk_hdmi)) clk_put(res->sclk_hdmi); if (!IS_ERR(res->sclk_mixer)) clk_put(res->sclk_mixer); if (!IS_ERR(res->vp)) clk_put(res->vp); if (!IS_ERR(res->mixer)) clk_put(res->mixer); } static int mxr_acquire_clocks(struct mxr_device *mdev) { struct mxr_resources *res = &mdev->res; struct device *dev = mdev->dev; mxr_resource_clear_clocks(res); res->mixer = clk_get(dev, "mixer"); if (IS_ERR(res->mixer)) { mxr_err(mdev, "failed to get clock 'mixer'\n"); goto fail; } res->vp = clk_get(dev, "vp"); if (IS_ERR(res->vp)) { mxr_err(mdev, "failed to get clock 'vp'\n"); goto fail; } res->sclk_mixer = clk_get(dev, "sclk_mixer"); if (IS_ERR(res->sclk_mixer)) { mxr_err(mdev, "failed to get clock 'sclk_mixer'\n"); goto fail; } res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); if (IS_ERR(res->sclk_hdmi)) { mxr_err(mdev, "failed to get clock 'sclk_hdmi'\n"); goto fail; } res->sclk_dac = clk_get(dev, "sclk_dac"); if (IS_ERR(res->sclk_dac)) { mxr_err(mdev, "failed to get clock 'sclk_dac'\n"); goto fail; } return 0; fail: mxr_release_clocks(mdev); return -ENODEV; } static int mxr_acquire_resources(struct mxr_device *mdev, struct platform_device *pdev) { int ret; ret = mxr_acquire_plat_resources(mdev, pdev); if (ret) goto fail; ret = mxr_acquire_clocks(mdev); if (ret) goto fail_plat; mxr_info(mdev, "resources acquired\n"); return 0; fail_plat: mxr_release_plat_resources(mdev); fail: mxr_err(mdev, "resources acquire failed\n"); return ret; } static void mxr_release_resources(struct mxr_device *mdev) { mxr_release_clocks(mdev); mxr_release_plat_resources(mdev); memset(&mdev->res, 0, sizeof(mdev->res)); mxr_resource_clear_clocks(&mdev->res); } static void mxr_release_layers(struct mxr_device *mdev) { int i; for (i = 0; i < ARRAY_SIZE(mdev->layer); ++i) if (mdev->layer[i]) mxr_layer_release(mdev->layer[i]); } static int mxr_acquire_layers(struct mxr_device *mdev, struct mxr_platform_data *pdata) { mdev->layer[0] = mxr_graph_layer_create(mdev, 0); mdev->layer[1] = mxr_graph_layer_create(mdev, 1); mdev->layer[2] = mxr_vp_layer_create(mdev, 0); if (!mdev->layer[0] || !mdev->layer[1] || !mdev->layer[2]) { mxr_err(mdev, "failed to acquire layers\n"); goto fail; } return 0; fail: mxr_release_layers(mdev); return -ENODEV; } /* ---------- POWER MANAGEMENT ----------- */ static int mxr_runtime_resume(struct device *dev) { struct mxr_device *mdev = to_mdev(dev); struct mxr_resources *res = &mdev->res; int ret; mxr_dbg(mdev, "resume - start\n"); mutex_lock(&mdev->mutex); /* turn clocks on */ ret = clk_prepare_enable(res->mixer); if (ret < 0) { dev_err(mdev->dev, "clk_prepare_enable(mixer) failed\n"); goto fail; } ret = clk_prepare_enable(res->vp); if (ret < 0) { dev_err(mdev->dev, "clk_prepare_enable(vp) failed\n"); goto fail_mixer; } ret = clk_prepare_enable(res->sclk_mixer); if (ret < 0) { dev_err(mdev->dev, "clk_prepare_enable(sclk_mixer) failed\n"); goto fail_vp; } /* apply default configuration */ mxr_reg_reset(mdev); mxr_dbg(mdev, "resume - finished\n"); mutex_unlock(&mdev->mutex); return 0; fail_vp: clk_disable_unprepare(res->vp); fail_mixer: clk_disable_unprepare(res->mixer); fail: mutex_unlock(&mdev->mutex); dev_err(mdev->dev, "resume failed\n"); return ret; } static int mxr_runtime_suspend(struct device *dev) { struct mxr_device *mdev = to_mdev(dev); struct mxr_resources *res = &mdev->res; mxr_dbg(mdev, "suspend - start\n"); mutex_lock(&mdev->mutex); /* turn clocks off */ clk_disable_unprepare(res->sclk_mixer); clk_disable_unprepare(res->vp); clk_disable_unprepare(res->mixer); mutex_unlock(&mdev->mutex); mxr_dbg(mdev, "suspend - finished\n"); return 0; } static const struct dev_pm_ops mxr_pm_ops = { .runtime_suspend = mxr_runtime_suspend, .runtime_resume = mxr_runtime_resume, }; /* --------- DRIVER INITIALIZATION ---------- */ static int mxr_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mxr_platform_data *pdata = dev->platform_data; struct mxr_device *mdev; int ret; /* mdev does not exist yet so no mxr_dbg is used */ dev_info(dev, "probe start\n"); mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); if (!mdev) { dev_err(dev, "not enough memory.\n"); ret = -ENOMEM; goto fail; } /* setup pointer to master device */ mdev->dev = dev; mutex_init(&mdev->mutex); spin_lock_init(&mdev->reg_slock); init_waitqueue_head(&mdev->event_queue); /* acquire resources: regs, irqs, clocks, regulators */ ret = mxr_acquire_resources(mdev, pdev); if (ret) goto fail_mem; /* configure resources for video output */ ret = mxr_acquire_video(mdev, mxr_output_conf, ARRAY_SIZE(mxr_output_conf)); if (ret) goto fail_resources; /* configure layers */ ret = mxr_acquire_layers(mdev, pdata); if (ret) goto fail_video; pm_runtime_enable(dev); mxr_info(mdev, "probe successful\n"); return 0; fail_video: mxr_release_video(mdev); fail_resources: mxr_release_resources(mdev); fail_mem: kfree(mdev); fail: dev_info(dev, "probe failed\n"); return ret; } static int mxr_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mxr_device *mdev = to_mdev(dev); pm_runtime_disable(dev); mxr_release_layers(mdev); mxr_release_video(mdev); mxr_release_resources(mdev); kfree(mdev); dev_info(dev, "remove successful\n"); return 0; } static struct platform_driver mxr_driver __refdata = { .probe = mxr_probe, .remove = mxr_remove, .driver = { .name = MXR_DRIVER_NAME, .pm = &mxr_pm_ops, } }; static int __init mxr_init(void) { int i, ret; static const char banner[] __initconst = "Samsung TV Mixer driver, " "(c) 2010-2011 Samsung Electronics Co., Ltd.\n"; pr_info("%s\n", banner); /* Loading auxiliary modules */ for (i = 0; i < ARRAY_SIZE(mxr_output_conf); ++i) request_module(mxr_output_conf[i].module_name); ret = platform_driver_register(&mxr_driver); if (ret != 0) { pr_err("s5p-tv: registration of MIXER driver failed\n"); return -ENXIO; } return 0; } module_init(mxr_init); static void __exit mxr_exit(void) { platform_driver_unregister(&mxr_driver); } module_exit(mxr_exit);