![Sylwester Nawrocki](/assets/img/avatar_default.png)
This patch restores serial port operation which has been broken since
commit 60e9357547
("serial: samsung: enable clock before clearing
pending interrupts during init")
That commit only uncovered the real issue which was missing clkdev
entries for the "uart" clocks on S3C2440. It went unnoticed so far
because return value of clk API calls were not being checked at all
in the samsung serial port driver.
This patch should be backported to at least 3.10 stable kernel, since
the serial port has not been working on s3c2440 since 3.10-rc5.
Cc: Chander Kashyap <chander.kashyap@linaro.org>
Signed-off-by: Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
[on S3C2440 SoC based Mini2440 board]
Tested-by: Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
Reviewed-by: Tomasz Figa <t.figa@samsung.com>
Tested-by: Juergen Beisert <jbe@pengutronix.de>
Cc: <stable@vger.kernel.org> [3.10]
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
218 lines
5.8 KiB
C
218 lines
5.8 KiB
C
/* linux/arch/arm/mach-s3c2440/clock.c
|
|
*
|
|
* Copyright (c) 2004-2005 Simtec Electronics
|
|
* http://armlinux.simtec.co.uk/
|
|
* Ben Dooks <ben@simtec.co.uk>
|
|
*
|
|
* S3C2440 Clock support
|
|
*
|
|
* 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 Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/serial_core.h>
|
|
|
|
#include <mach/hardware.h>
|
|
#include <linux/atomic.h>
|
|
#include <asm/irq.h>
|
|
|
|
#include <mach/regs-clock.h>
|
|
|
|
#include <plat/clock.h>
|
|
#include <plat/cpu.h>
|
|
#include <plat/regs-serial.h>
|
|
|
|
/* S3C2440 extended clock support */
|
|
|
|
static unsigned long s3c2440_camif_upll_round(struct clk *clk,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long parent_rate = clk_get_rate(clk->parent);
|
|
int div;
|
|
|
|
if (rate > parent_rate)
|
|
return parent_rate;
|
|
|
|
/* note, we remove the +/- 1 calculations for the divisor */
|
|
|
|
div = (parent_rate / rate) / 2;
|
|
|
|
if (div < 1)
|
|
div = 1;
|
|
else if (div > 16)
|
|
div = 16;
|
|
|
|
return parent_rate / (div * 2);
|
|
}
|
|
|
|
static int s3c2440_camif_upll_setrate(struct clk *clk, unsigned long rate)
|
|
{
|
|
unsigned long parent_rate = clk_get_rate(clk->parent);
|
|
unsigned long camdivn = __raw_readl(S3C2440_CAMDIVN);
|
|
|
|
rate = s3c2440_camif_upll_round(clk, rate);
|
|
|
|
camdivn &= ~(S3C2440_CAMDIVN_CAMCLK_SEL | S3C2440_CAMDIVN_CAMCLK_MASK);
|
|
|
|
if (rate != parent_rate) {
|
|
camdivn |= S3C2440_CAMDIVN_CAMCLK_SEL;
|
|
camdivn |= (((parent_rate / rate) / 2) - 1);
|
|
}
|
|
|
|
__raw_writel(camdivn, S3C2440_CAMDIVN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long s3c2440_camif_upll_getrate(struct clk *clk)
|
|
{
|
|
unsigned long parent_rate = clk_get_rate(clk->parent);
|
|
unsigned long camdivn = __raw_readl(S3C2440_CAMDIVN);
|
|
|
|
if (!(camdivn & S3C2440_CAMDIVN_CAMCLK_SEL))
|
|
return parent_rate;
|
|
|
|
camdivn &= S3C2440_CAMDIVN_CAMCLK_MASK;
|
|
|
|
return parent_rate / (camdivn + 1) / 2;
|
|
}
|
|
|
|
/* Extra S3C2440 clocks */
|
|
|
|
static struct clk s3c2440_clk_cam = {
|
|
.name = "camif",
|
|
.enable = s3c2410_clkcon_enable,
|
|
.ctrlbit = S3C2440_CLKCON_CAMERA,
|
|
};
|
|
|
|
static struct clk s3c2440_clk_cam_upll = {
|
|
.name = "camif-upll",
|
|
.ops = &(struct clk_ops) {
|
|
.set_rate = s3c2440_camif_upll_setrate,
|
|
.get_rate = s3c2440_camif_upll_getrate,
|
|
.round_rate = s3c2440_camif_upll_round,
|
|
},
|
|
};
|
|
|
|
static struct clk s3c2440_clk_ac97 = {
|
|
.name = "ac97",
|
|
.enable = s3c2410_clkcon_enable,
|
|
.ctrlbit = S3C2440_CLKCON_AC97,
|
|
};
|
|
|
|
#define S3C24XX_VA_UART0 (S3C_VA_UART)
|
|
#define S3C24XX_VA_UART1 (S3C_VA_UART + 0x4000 )
|
|
#define S3C24XX_VA_UART2 (S3C_VA_UART + 0x8000 )
|
|
#define S3C24XX_VA_UART3 (S3C_VA_UART + 0xC000 )
|
|
|
|
static unsigned long s3c2440_fclk_n_getrate(struct clk *clk)
|
|
{
|
|
unsigned long ucon0, ucon1, ucon2, divisor;
|
|
|
|
/* the fun of calculating the uart divisors on the s3c2440 */
|
|
ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON);
|
|
ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON);
|
|
ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON);
|
|
|
|
ucon0 &= S3C2440_UCON0_DIVMASK;
|
|
ucon1 &= S3C2440_UCON1_DIVMASK;
|
|
ucon2 &= S3C2440_UCON2_DIVMASK;
|
|
|
|
if (ucon0 != 0)
|
|
divisor = (ucon0 >> S3C2440_UCON_DIVSHIFT) + 6;
|
|
else if (ucon1 != 0)
|
|
divisor = (ucon1 >> S3C2440_UCON_DIVSHIFT) + 21;
|
|
else if (ucon2 != 0)
|
|
divisor = (ucon2 >> S3C2440_UCON_DIVSHIFT) + 36;
|
|
else
|
|
/* manual calims 44, seems to be 9 */
|
|
divisor = 9;
|
|
|
|
return clk_get_rate(clk->parent) / divisor;
|
|
}
|
|
|
|
static struct clk s3c2440_clk_fclk_n = {
|
|
.name = "fclk_n",
|
|
.parent = &clk_f,
|
|
.ops = &(struct clk_ops) {
|
|
.get_rate = s3c2440_fclk_n_getrate,
|
|
},
|
|
};
|
|
|
|
static struct clk_lookup s3c2440_clk_lookup[] = {
|
|
CLKDEV_INIT(NULL, "clk_uart_baud1", &s3c24xx_uclk),
|
|
CLKDEV_INIT(NULL, "clk_uart_baud2", &clk_p),
|
|
CLKDEV_INIT(NULL, "clk_uart_baud3", &s3c2440_clk_fclk_n),
|
|
CLKDEV_INIT("s3c2440-uart.0", "uart", &s3c24xx_clk_uart0),
|
|
CLKDEV_INIT("s3c2440-uart.1", "uart", &s3c24xx_clk_uart1),
|
|
CLKDEV_INIT("s3c2440-uart.2", "uart", &s3c24xx_clk_uart2),
|
|
CLKDEV_INIT("s3c2440-camif", "camera", &s3c2440_clk_cam_upll),
|
|
};
|
|
|
|
static int __init_refok s3c2440_clk_add(struct device *dev, struct subsys_interface *sif)
|
|
{
|
|
struct clk *clock_upll;
|
|
struct clk *clock_h;
|
|
struct clk *clock_p;
|
|
|
|
clock_p = clk_get(NULL, "pclk");
|
|
clock_h = clk_get(NULL, "hclk");
|
|
clock_upll = clk_get(NULL, "upll");
|
|
|
|
if (IS_ERR(clock_p) || IS_ERR(clock_h) || IS_ERR(clock_upll)) {
|
|
printk(KERN_ERR "S3C2440: Failed to get parent clocks\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
s3c2440_clk_cam.parent = clock_h;
|
|
s3c2440_clk_ac97.parent = clock_p;
|
|
s3c2440_clk_cam_upll.parent = clock_upll;
|
|
s3c24xx_register_clock(&s3c2440_clk_fclk_n);
|
|
|
|
s3c24xx_register_clock(&s3c2440_clk_ac97);
|
|
s3c24xx_register_clock(&s3c2440_clk_cam);
|
|
s3c24xx_register_clock(&s3c2440_clk_cam_upll);
|
|
clkdev_add_table(s3c2440_clk_lookup, ARRAY_SIZE(s3c2440_clk_lookup));
|
|
|
|
clk_disable(&s3c2440_clk_ac97);
|
|
clk_disable(&s3c2440_clk_cam);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct subsys_interface s3c2440_clk_interface = {
|
|
.name = "s3c2440_clk",
|
|
.subsys = &s3c2440_subsys,
|
|
.add_dev = s3c2440_clk_add,
|
|
};
|
|
|
|
static __init int s3c24xx_clk_init(void)
|
|
{
|
|
return subsys_interface_register(&s3c2440_clk_interface);
|
|
}
|
|
|
|
arch_initcall(s3c24xx_clk_init);
|