#include <linux/rtc.h>
 #include <linux/of.h>
 #include <linux/bcd.h>
+#include <linux/delay.h>
 
 /* MCP795 Instructions, see datasheet table 3-1 */
 #define MCP795_EEREAD  0x03
 #define MCP795_CLRWDT  0x44
 #define MCP795_CLRRAM  0x54
 
+/* MCP795 RTCC registers, see datasheet table 4-1 */
+#define MCP795_REG_SECONDS     0x01
+#define MCP795_REG_DAY         0x04
+#define MCP795_REG_MONTH       0x06
+#define MCP795_REG_CONTROL     0x08
+
 #define MCP795_ST_BIT  0x80
 #define MCP795_24_BIT  0x40
 #define MCP795_LP_BIT  BIT(5)
+#define MCP795_EXTOSC_BIT      BIT(3)
+#define MCP795_OSCON_BIT       BIT(5)
 
 static int mcp795_rtcc_read(struct device *dev, u8 addr, u8 *buf, u8 count)
 {
        return ret;
 }
 
+static int mcp795_stop_oscillator(struct device *dev, bool *extosc)
+{
+       int retries = 5;
+       int ret;
+       u8 data;
+
+       ret = mcp795_rtcc_set_bits(dev, MCP795_REG_SECONDS, MCP795_ST_BIT, 0);
+       if (ret)
+               return ret;
+       ret = mcp795_rtcc_read(dev, MCP795_REG_CONTROL, &data, 1);
+       if (ret)
+               return ret;
+       *extosc = !!(data & MCP795_EXTOSC_BIT);
+       ret = mcp795_rtcc_set_bits(
+                               dev, MCP795_REG_CONTROL, MCP795_EXTOSC_BIT, 0);
+       if (ret)
+               return ret;
+       /* wait for the OSCON bit to clear */
+       do {
+               usleep_range(700, 800);
+               ret = mcp795_rtcc_read(dev, MCP795_REG_DAY, &data, 1);
+               if (ret)
+                       break;
+               if (!(data & MCP795_OSCON_BIT))
+                       break;
+
+       } while (--retries);
+
+       return !retries ? -EIO : ret;
+}
+
+static int mcp795_start_oscillator(struct device *dev, bool *extosc)
+{
+       if (extosc) {
+               u8 data = *extosc ? MCP795_EXTOSC_BIT : 0;
+               int ret;
+
+               ret = mcp795_rtcc_set_bits(
+                       dev, MCP795_REG_CONTROL, MCP795_EXTOSC_BIT, data);
+               if (ret)
+                       return ret;
+       }
+       return mcp795_rtcc_set_bits(
+                       dev, MCP795_REG_SECONDS, MCP795_ST_BIT, MCP795_ST_BIT);
+}
+
 static int mcp795_set_time(struct device *dev, struct rtc_time *tim)
 {
        int ret;
        u8 data[7];
+       bool extosc;
+
+       /* Stop RTC and store current value of EXTOSC bit */
+       ret = mcp795_stop_oscillator(dev, &extosc);
+       if (ret)
+               return ret;
 
        /* Read first, so we can leave config bits untouched */
-       ret = mcp795_rtcc_read(dev, 0x01, data, sizeof(data));
+       ret = mcp795_rtcc_read(dev, MCP795_REG_SECONDS, data, sizeof(data));
 
        if (ret)
                return ret;
 
        data[6] = bin2bcd(tim->tm_year);
 
-       ret = mcp795_rtcc_write(dev, 0x01, data, sizeof(data));
+       /* Always write the date and month using a separate Write command.
+        * This is a workaround for a know silicon issue that some combinations
+        * of date and month values may result in the date being reset to 1.
+        */
+       ret = mcp795_rtcc_write(dev, MCP795_REG_SECONDS, data, 5);
+       if (ret)
+               return ret;
+
+       ret = mcp795_rtcc_write(dev, MCP795_REG_MONTH, &data[5], 2);
+       if (ret)
+               return ret;
 
+       /* Start back RTC and restore previous value of EXTOSC bit.
+        * There is no need to clear EXTOSC bit when the previous value was 0
+        * because it was already cleared when stopping the RTC oscillator.
+        */
+       ret = mcp795_start_oscillator(dev, extosc ? &extosc : NULL);
        if (ret)
                return ret;
 
        int ret;
        u8 data[7];
 
-       ret = mcp795_rtcc_read(dev, 0x01, data, sizeof(data));
+       ret = mcp795_rtcc_read(dev, MCP795_REG_SECONDS, data, sizeof(data));
 
        if (ret)
                return ret;
                return ret;
        }
 
-       /* Start the oscillator */
-       mcp795_rtcc_set_bits(&spi->dev, 0x01, MCP795_ST_BIT, MCP795_ST_BIT);
+       /* Start the oscillator but don't set the value of EXTOSC bit */
+       mcp795_start_oscillator(&spi->dev, NULL);
        /* Clear the 12 hour mode flag*/
        mcp795_rtcc_set_bits(&spi->dev, 0x03, MCP795_24_BIT, 0);