| /* |
| * 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; |
| struct v4l2_subdev_format fmt = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| }; |
| int ret; |
| |
| mutex_lock(&mdev->mutex); |
| sd = to_outsd(mdev); |
| ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); |
| *mbus_fmt = fmt.format; |
| 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_subdev_format fmt = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| }; |
| struct v4l2_mbus_framefmt *mbus_fmt = &fmt.format; |
| 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, pad, get_fmt, NULL, &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 (ret < 0) |
| 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); |