]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/media/platform/s5p-fimc/mipi-csis.c
Merge branch 'samsung_platform_data' into staging/for_v3.7
[karo-tx-linux.git] / drivers / media / platform / s5p-fimc / mipi-csis.c
similarity index 77%
rename from drivers/media/video/s5p-fimc/mipi-csis.c
rename to drivers/media/platform/s5p-fimc/mipi-csis.c
index 5e898432883a28a848ea206be03453ad051d8fed..22bdf211a2ac8544f6b5a25dfffe9cd9b5bb5909 100644 (file)
@@ -2,7 +2,7 @@
  * Samsung S5P/EXYNOS4 SoC series MIPI-CSI receiver driver
  *
  * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd.
- * Sylwester Nawrocki, <s.nawrocki@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -31,7 +31,7 @@
 
 static int debug;
 module_param(debug, int, 0644);
-MODULE_PARM_DESC(debug, "Debug level (0-1)");
+MODULE_PARM_DESC(debug, "Debug level (0-2)");
 
 /* Register map definition */
 
@@ -60,16 +60,49 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)");
 #define S5PCSIS_CFG_FMT_MASK           (0x3f << 2)
 #define S5PCSIS_CFG_NR_LANE_MASK       3
 
-/* Interrupt mask. */
+/* Interrupt mask */
 #define S5PCSIS_INTMSK                 0x10
-#define S5PCSIS_INTMSK_EN_ALL          0xf000003f
+#define S5PCSIS_INTMSK_EN_ALL          0xf000103f
+#define S5PCSIS_INTMSK_EVEN_BEFORE     (1 << 31)
+#define S5PCSIS_INTMSK_EVEN_AFTER      (1 << 30)
+#define S5PCSIS_INTMSK_ODD_BEFORE      (1 << 29)
+#define S5PCSIS_INTMSK_ODD_AFTER       (1 << 28)
+#define S5PCSIS_INTMSK_ERR_SOT_HS      (1 << 12)
+#define S5PCSIS_INTMSK_ERR_LOST_FS     (1 << 5)
+#define S5PCSIS_INTMSK_ERR_LOST_FE     (1 << 4)
+#define S5PCSIS_INTMSK_ERR_OVER                (1 << 3)
+#define S5PCSIS_INTMSK_ERR_ECC         (1 << 2)
+#define S5PCSIS_INTMSK_ERR_CRC         (1 << 1)
+#define S5PCSIS_INTMSK_ERR_UNKNOWN     (1 << 0)
+
+/* Interrupt source */
 #define S5PCSIS_INTSRC                 0x14
+#define S5PCSIS_INTSRC_EVEN_BEFORE     (1 << 31)
+#define S5PCSIS_INTSRC_EVEN_AFTER      (1 << 30)
+#define S5PCSIS_INTSRC_EVEN            (0x3 << 30)
+#define S5PCSIS_INTSRC_ODD_BEFORE      (1 << 29)
+#define S5PCSIS_INTSRC_ODD_AFTER       (1 << 28)
+#define S5PCSIS_INTSRC_ODD             (0x3 << 28)
+#define S5PCSIS_INTSRC_NON_IMAGE_DATA  (0xff << 28)
+#define S5PCSIS_INTSRC_ERR_SOT_HS      (0xf << 12)
+#define S5PCSIS_INTSRC_ERR_LOST_FS     (1 << 5)
+#define S5PCSIS_INTSRC_ERR_LOST_FE     (1 << 4)
+#define S5PCSIS_INTSRC_ERR_OVER                (1 << 3)
+#define S5PCSIS_INTSRC_ERR_ECC         (1 << 2)
+#define S5PCSIS_INTSRC_ERR_CRC         (1 << 1)
+#define S5PCSIS_INTSRC_ERR_UNKNOWN     (1 << 0)
+#define S5PCSIS_INTSRC_ERRORS          0xf03f
 
 /* Pixel resolution */
 #define S5PCSIS_RESOL                  0x2c
 #define CSIS_MAX_PIX_WIDTH             0xffff
 #define CSIS_MAX_PIX_HEIGHT            0xffff
 
+/* Non-image packet data buffers */
+#define S5PCSIS_PKTDATA_ODD            0x2000
+#define S5PCSIS_PKTDATA_EVEN           0x3000
+#define S5PCSIS_PKTDATA_SIZE           SZ_4K
+
 enum {
        CSIS_CLK_MUX,
        CSIS_CLK_GATE,
@@ -82,8 +115,8 @@ static char *csi_clock_name[] = {
 #define NUM_CSIS_CLOCKS        ARRAY_SIZE(csi_clock_name)
 
 static const char * const csis_supply_name[] = {
-       "vdd11", /* 1.1V or 1.2V (s5pc100) MIPI CSI suppply */
-       "vdd18", /* VDD 1.8V and MIPI CSI PLL supply */
+       "vddcore",  /* CSIS Core (1.0V, 1.1V or 1.2V) suppply */
+       "vddio",    /* CSIS I/O and PLL (1.8V) supply */
 };
 #define CSIS_NUM_SUPPLIES ARRAY_SIZE(csis_supply_name)
 
@@ -93,6 +126,34 @@ enum {
        ST_SUSPENDED    = 4,
 };
 
+struct s5pcsis_event {
+       u32 mask;
+       const char * const name;
+       unsigned int counter;
+};
+
+static const struct s5pcsis_event s5pcsis_events[] = {
+       /* Errors */
+       { S5PCSIS_INTSRC_ERR_SOT_HS,    "SOT Error" },
+       { S5PCSIS_INTSRC_ERR_LOST_FS,   "Lost Frame Start Error" },
+       { S5PCSIS_INTSRC_ERR_LOST_FE,   "Lost Frame End Error" },
+       { S5PCSIS_INTSRC_ERR_OVER,      "FIFO Overflow Error" },
+       { S5PCSIS_INTSRC_ERR_ECC,       "ECC Error" },
+       { S5PCSIS_INTSRC_ERR_CRC,       "CRC Error" },
+       { S5PCSIS_INTSRC_ERR_UNKNOWN,   "Unknown Error" },
+       /* Non-image data receive events */
+       { S5PCSIS_INTSRC_EVEN_BEFORE,   "Non-image data before even frame" },
+       { S5PCSIS_INTSRC_EVEN_AFTER,    "Non-image data after even frame" },
+       { S5PCSIS_INTSRC_ODD_BEFORE,    "Non-image data before odd frame" },
+       { S5PCSIS_INTSRC_ODD_AFTER,     "Non-image data after odd frame" },
+};
+#define S5PCSIS_NUM_EVENTS ARRAY_SIZE(s5pcsis_events)
+
+struct csis_pktbuf {
+       u32 *data;
+       unsigned int len;
+};
+
 /**
  * struct csis_state - the driver's internal state data structure
  * @lock: mutex serializing the subdev and power management operations,
@@ -101,11 +162,15 @@ enum {
  * @sd: v4l2_subdev associated with CSIS device instance
  * @pdev: CSIS platform device
  * @regs: mmaped I/O registers memory
+ * @supplies: CSIS regulator supplies
  * @clock: CSIS clocks
  * @irq: requested s5p-mipi-csis irq number
  * @flags: the state variable for power and streaming control
  * @csis_fmt: current CSIS pixel format
  * @format: common media bus format for the source and sink pad
+ * @slock: spinlock protecting structure members below
+ * @pkt_buf: the frame embedded (non-image) data buffer
+ * @events: MIPI-CSIS event (error) counters
  */
 struct csis_state {
        struct mutex lock;
@@ -119,6 +184,10 @@ struct csis_state {
        u32 flags;
        const struct csis_pix_format *csis_fmt;
        struct v4l2_mbus_framefmt format;
+
+       struct spinlock slock;
+       struct csis_pktbuf pkt_buf;
+       struct s5pcsis_event events[S5PCSIS_NUM_EVENTS];
 };
 
 /**
@@ -145,7 +214,11 @@ static const struct csis_pix_format s5pcsis_formats[] = {
                .code = V4L2_MBUS_FMT_JPEG_1X8,
                .fmt_reg = S5PCSIS_CFG_FMT_USER(1),
                .data_alignment = 32,
-       },
+       }, {
+               .code = V4L2_MBUS_FMT_S5C_UYVY_JPEG_1X8,
+               .fmt_reg = S5PCSIS_CFG_FMT_USER(1),
+               .data_alignment = 32,
+       }
 };
 
 #define s5pcsis_write(__csis, __r, __v) writel(__v, __csis->regs + __r)
@@ -209,7 +282,7 @@ static void __s5pcsis_set_format(struct csis_state *state)
        struct v4l2_mbus_framefmt *mf = &state->format;
        u32 val;
 
-       v4l2_dbg(1, debug, &state->sd, "fmt: %d, %d x %d\n",
+       v4l2_dbg(1, debug, &state->sd, "fmt: %#x, %d x %d\n",
                 mf->code, mf->width, mf->height);
 
        /* Color format */
@@ -292,17 +365,6 @@ err:
        return -ENXIO;
 }
 
-static int s5pcsis_s_power(struct v4l2_subdev *sd, int on)
-{
-       struct csis_state *state = sd_to_csis_state(sd);
-       struct device *dev = &state->pdev->dev;
-
-       if (on)
-               return pm_runtime_get_sync(dev);
-
-       return pm_runtime_put_sync(dev);
-}
-
 static void s5pcsis_start_stream(struct csis_state *state)
 {
        s5pcsis_reset(state);
@@ -317,7 +379,47 @@ static void s5pcsis_stop_stream(struct csis_state *state)
        s5pcsis_system_enable(state, false);
 }
 
-/* v4l2_subdev operations */
+static void s5pcsis_clear_counters(struct csis_state *state)
+{
+       unsigned long flags;
+       int i;
+
+       spin_lock_irqsave(&state->slock, flags);
+       for (i = 0; i < S5PCSIS_NUM_EVENTS; i++)
+               state->events[i].counter = 0;
+       spin_unlock_irqrestore(&state->slock, flags);
+}
+
+static void s5pcsis_log_counters(struct csis_state *state, bool non_errors)
+{
+       int i = non_errors ? S5PCSIS_NUM_EVENTS : S5PCSIS_NUM_EVENTS - 4;
+       unsigned long flags;
+
+       spin_lock_irqsave(&state->slock, flags);
+
+       for (i--; i >= 0; i--)
+               if (state->events[i].counter >= 0)
+                       v4l2_info(&state->sd, "%s events: %d\n",
+                                 state->events[i].name,
+                                 state->events[i].counter);
+
+       spin_unlock_irqrestore(&state->slock, flags);
+}
+
+/*
+ * V4L2 subdev operations
+ */
+static int s5pcsis_s_power(struct v4l2_subdev *sd, int on)
+{
+       struct csis_state *state = sd_to_csis_state(sd);
+       struct device *dev = &state->pdev->dev;
+
+       if (on)
+               return pm_runtime_get_sync(dev);
+
+       return pm_runtime_put_sync(dev);
+}
+
 static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable)
 {
        struct csis_state *state = sd_to_csis_state(sd);
@@ -327,10 +429,12 @@ static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable)
                 __func__, enable, state->flags);
 
        if (enable) {
+               s5pcsis_clear_counters(state);
                ret = pm_runtime_get_sync(&state->pdev->dev);
                if (ret && ret != 1)
                        return ret;
        }
+
        mutex_lock(&state->lock);
        if (enable) {
                if (state->flags & ST_SUSPENDED) {
@@ -342,6 +446,8 @@ static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable)
        } else {
                s5pcsis_stop_stream(state);
                state->flags &= ~ST_STREAMING;
+               if (debug > 0)
+                       s5pcsis_log_counters(state, true);
        }
 unlock:
        mutex_unlock(&state->lock);
@@ -439,6 +545,30 @@ static int s5pcsis_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
        return 0;
 }
 
+static int s5pcsis_s_rx_buffer(struct v4l2_subdev *sd, void *buf,
+                              unsigned int *size)
+{
+       struct csis_state *state = sd_to_csis_state(sd);
+       unsigned long flags;
+
+       *size = min_t(unsigned int, *size, S5PCSIS_PKTDATA_SIZE);
+
+       spin_lock_irqsave(&state->slock, flags);
+       state->pkt_buf.data = buf;
+       state->pkt_buf.len = *size;
+       spin_unlock_irqrestore(&state->slock, flags);
+
+       return 0;
+}
+
+static int s5pcsis_log_status(struct v4l2_subdev *sd)
+{
+       struct csis_state *state = sd_to_csis_state(sd);
+
+       s5pcsis_log_counters(state, true);
+       return 0;
+}
+
 static int s5pcsis_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 {
        struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0);
@@ -458,6 +588,7 @@ static const struct v4l2_subdev_internal_ops s5pcsis_sd_internal_ops = {
 
 static struct v4l2_subdev_core_ops s5pcsis_core_ops = {
        .s_power = s5pcsis_s_power,
+       .log_status = s5pcsis_log_status,
 };
 
 static struct v4l2_subdev_pad_ops s5pcsis_pad_ops = {
@@ -467,6 +598,7 @@ static struct v4l2_subdev_pad_ops s5pcsis_pad_ops = {
 };
 
 static struct v4l2_subdev_video_ops s5pcsis_video_ops = {
+       .s_rx_buffer = s5pcsis_s_rx_buffer,
        .s_stream = s5pcsis_s_stream,
 };
 
@@ -479,12 +611,42 @@ static struct v4l2_subdev_ops s5pcsis_subdev_ops = {
 static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id)
 {
        struct csis_state *state = dev_id;
-       u32 val;
+       struct csis_pktbuf *pktbuf = &state->pkt_buf;
+       unsigned long flags;
+       u32 status;
 
-       /* Just clear the interrupt pending bits. */
-       val = s5pcsis_read(state, S5PCSIS_INTSRC);
-       s5pcsis_write(state, S5PCSIS_INTSRC, val);
+       status = s5pcsis_read(state, S5PCSIS_INTSRC);
+       spin_lock_irqsave(&state->slock, flags);
 
+       if ((status & S5PCSIS_INTSRC_NON_IMAGE_DATA) && pktbuf->data) {
+               u32 offset;
+
+               if (status & S5PCSIS_INTSRC_EVEN)
+                       offset = S5PCSIS_PKTDATA_EVEN;
+               else
+                       offset = S5PCSIS_PKTDATA_ODD;
+
+               memcpy(pktbuf->data, state->regs + offset, pktbuf->len);
+               pktbuf->data = NULL;
+               rmb();
+       }
+
+       /* Update the event/error counters */
+       if ((status & S5PCSIS_INTSRC_ERRORS) || debug) {
+               int i;
+               for (i = 0; i < S5PCSIS_NUM_EVENTS; i++) {
+                       if (!(status & state->events[i].mask))
+                               continue;
+                       state->events[i].counter++;
+                       v4l2_dbg(2, debug, &state->sd, "%s: %d\n",
+                                state->events[i].name,
+                                state->events[i].counter);
+               }
+               v4l2_dbg(2, debug, &state->sd, "status: %08x\n", status);
+       }
+       spin_unlock_irqrestore(&state->slock, flags);
+
+       s5pcsis_write(state, S5PCSIS_INTSRC, status);
        return IRQ_HANDLED;
 }
 
@@ -501,6 +663,8 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev)
                return -ENOMEM;
 
        mutex_init(&state->lock);
+       spin_lock_init(&state->slock);
+
        state->pdev = pdev;
 
        pdata = pdev->dev.platform_data;
@@ -577,6 +741,8 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev)
        /* .. and a pointer to the subdev. */
        platform_set_drvdata(pdev, &state->sd);
 
+       memcpy(state->events, s5pcsis_events, sizeof(state->events));
+
        pm_runtime_enable(&pdev->dev);
        return 0;